diff --git a/CHANGELOG.md b/CHANGELOG.md index ea577174..9243c123 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning][]. - Multiscale image handling: user can specify a scale, else the best scale is selected automatically given the figure size and dpi (#164) - Large images are automatically rasterized to speed up performance (#164) - Added better error message for mismatch in cs and ax number (#185) +- Can now stack render commands (#190, #192) ### Fixed diff --git a/src/spatialdata_plot/pl/basic.py b/src/spatialdata_plot/pl/basic.py index c4352f05..c5592fb6 100644 --- a/src/spatialdata_plot/pl/basic.py +++ b/src/spatialdata_plot/pl/basic.py @@ -567,7 +567,7 @@ def show( ] # prepare rendering params - render_cmds = OrderedDict() + render_cmds = [] for cmd, params in plotting_tree.items(): # strip prefix from cmd and verify it's valid cmd = "_".join(cmd.split("_")[1:]) @@ -577,9 +577,9 @@ def show( if "render" in cmd: # verify that rendering commands have been called before - render_cmds[cmd] = params + render_cmds.append((cmd, params)) - if len(render_cmds.keys()) == 0: + if len(render_cmds) == 0: raise TypeError("Please specify what to plot using the 'render_*' functions before calling 'imshow()'.") if title is not None: @@ -609,7 +609,7 @@ def show( # Check if user specified only certain elements to be plotted cs_contents = _get_cs_contents(sdata) elements_to_be_rendered = [] - for cmd, params in render_cmds.items(): + for cmd, params in render_cmds: if cmd == "render_images" and cs_contents.query(f"cs == '{cs}'")["has_images"][0]: # noqa: SIM114 if params.elements is not None: elements_to_be_rendered += ( @@ -632,13 +632,14 @@ def show( ) # filter out cs without relevant elements + cmds = [cmd for cmd, _ in render_cmds] coordinate_systems = _get_valid_cs( sdata=sdata, coordinate_systems=coordinate_systems, - render_images="render_images" in render_cmds, - render_labels="render_labels" in render_cmds, - render_points="render_points" in render_cmds, - render_shapes="render_shapes" in render_cmds, + render_images="render_images" in cmds, + render_labels="render_labels" in cmds, + render_points="render_points" in cmds, + render_shapes="render_shapes" in cmds, elements=elements_to_be_rendered, ) @@ -689,7 +690,7 @@ def show( wants_shapes = False wanted_elements = [] - for cmd, params in render_cmds.items(): + for cmd, params in render_cmds: if cmd == "render_images" and has_images: wants_images = True wanted_images = params.elements if params.elements is not None else list(sdata.images.keys()) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 03d95662..1445c1b6 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -406,10 +406,13 @@ def _render_images( else: cmap = _get_linear_colormap([render_params.palette], "k")[0] + # Overwrite alpha in cmap: https://stackoverflow.com/a/10127675 + cmap._init() + cmap._lut[:, -1] = render_params.alpha + im = ax.imshow( - layer, # get rid of the channel dimension + layer, cmap=cmap, - alpha=render_params.alpha, ) im.set_transform(trans_data) @@ -514,6 +517,12 @@ def _render_labels( ) -> None: elements = render_params.elements + if not isinstance(render_params.outline, bool): + raise TypeError("Parameter 'outline' must be a boolean.") + + if not isinstance(render_params.contour_px, int): + raise TypeError("Parameter 'contour_px' must be an integer.") + if render_params.groups is not None: if isinstance(render_params.groups, str): render_params.groups = [render_params.groups] diff --git a/tests/_images/Images_can_stack_render_images.png b/tests/_images/Images_can_stack_render_images.png new file mode 100644 index 00000000..ee45140f Binary files /dev/null and b/tests/_images/Images_can_stack_render_images.png differ diff --git a/tests/_images/Labels_can_stack_render_labels.png b/tests/_images/Labels_can_stack_render_labels.png new file mode 100644 index 00000000..5bdb53a0 Binary files /dev/null and b/tests/_images/Labels_can_stack_render_labels.png differ diff --git a/tests/_images/Points_can_stack_render_points.png b/tests/_images/Points_can_stack_render_points.png new file mode 100644 index 00000000..28128121 Binary files /dev/null and b/tests/_images/Points_can_stack_render_points.png differ diff --git a/tests/_images/Shapes_can_stack_render_shapes.png b/tests/_images/Shapes_can_stack_render_shapes.png new file mode 100644 index 00000000..a093b558 Binary files /dev/null and b/tests/_images/Shapes_can_stack_render_shapes.png differ diff --git a/tests/pl/test_render_images.py b/tests/pl/test_render_images.py index a1b732ff..f2903a52 100644 --- a/tests/pl/test_render_images.py +++ b/tests/pl/test_render_images.py @@ -77,3 +77,10 @@ def test_plot_can_stop_rasterization_with_scale_full(self, sdata_blobs: SpatialD sdata_blobs["blobs_giant_image"] = img sdata_blobs.pl.render_images("blobs_giant_image", scale="full").pl.show() + + def test_plot_can_stack_render_images(self, sdata_blobs: SpatialData): + ( + sdata_blobs.pl.render_images(elements="blobs_image", channel=0, palette="red", alpha=0.5) + .pl.render_images(elements="blobs_image", channel=1, palette="blue", alpha=0.5) + .pl.show() + ) diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index 52dada28..fe0ef717 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -59,3 +59,14 @@ def test_plot_can_stop_rasterization_with_scale_full(self, sdata_blobs: SpatialD sdata_blobs.table.uns["spatialdata_attrs"]["region"] = "blobs_giant_labels" sdata_blobs.pl.render_labels("blobs_giant_labels", scale="full").pl.show() + + def test_plot_can_stack_render_labels(self, sdata_blobs: SpatialData): + ( + sdata_blobs.pl.render_labels( + elements="blobs_labels", na_color="red", fill_alpha=1, outline_alpha=0, outline=False + ) + .pl.render_labels( + elements="blobs_labels", na_color="blue", fill_alpha=0, outline_alpha=1, outline=True, contour_px=10 + ) + .pl.show() + ) diff --git a/tests/pl/test_render_points.py b/tests/pl/test_render_points.py index 43379517..a1a55534 100644 --- a/tests/pl/test_render_points.py +++ b/tests/pl/test_render_points.py @@ -30,3 +30,10 @@ def test_plot_coloring_with_palette(self, sdata_blobs: SpatialData): def test_plot_coloring_with_cmap(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_points(color="genes", cmap="rainbow").pl.show() + + def test_plot_can_stack_render_points(self, sdata_blobs: SpatialData): + ( + sdata_blobs.pl.render_points(elements="blobs_points", na_color="red", size=30) + .pl.render_points(elements="blobs_points", na_color="blue", size=10) + .pl.show() + ) diff --git a/tests/pl/test_render_shapes.py b/tests/pl/test_render_shapes.py index 941b1970..c70e7ebf 100644 --- a/tests/pl/test_render_shapes.py +++ b/tests/pl/test_render_shapes.py @@ -254,3 +254,10 @@ def test_plot_can_color_two_queried_shapes_elements_by_annotation(self, sdata_bl del sdata_cropped.labels["blobs_multiscale_labels"] sdata_cropped.pl.render_shapes(["blobs_circles", "blobs_polygons"], color="annotation").pl.show() + + def test_plot_can_stack_render_shapes(self, sdata_blobs: SpatialData): + ( + sdata_blobs.pl.render_shapes(elements="blobs_circles", na_color="red", fill_alpha=0.5) + .pl.render_shapes(elements="blobs_polygons", na_color="blue", fill_alpha=0.5) + .pl.show() + )