From 0c2830449850962652c6f272bd0bb77adac603cb Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 15:51:15 -0400 Subject: [PATCH 1/4] enabled edgecase; refactor --- src/spatialdata_plot/pl/render.py | 114 +++++------------------------- 1 file changed, 19 insertions(+), 95 deletions(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 6be76107..91264377 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -650,11 +650,9 @@ def _render_images( stacked = np.stack([layers[c] for c in channels], axis=-1) else: # -> use given cmap for each channel channel_cmaps = [render_params.cmap_params.cmap] * n_channels - # Apply cmaps to each channel, add up and normalize to [0, 1] stacked = ( np.stack([channel_cmaps[ind](layers[ch]) for ind, ch in enumerate(channels)], 0).sum(0) / n_channels ) - # Remove alpha channel so we can overwrite it from render_params.alpha stacked = stacked[:, :, :3] logger.warning( "One cmap was given for multiple channels and is now used for each channel. " @@ -676,11 +674,7 @@ def _render_images( seed_colors = _get_colors_for_categorical_obs(list(range(n_channels))) channel_cmaps = [_get_linear_colormap([c], "k")[0] for c in seed_colors] - - # Apply cmaps to each channel and add up colored = np.stack([channel_cmaps[ind](layers[ch]) for ind, ch in enumerate(channels)], 0).sum(0) - - # Remove alpha channel so we can overwrite it from render_params.alpha colored = colored[:, :, :3] _ax_show_and_transform(colored, trans_data, ax, render_params.alpha, zorder=render_params.zorder) @@ -691,24 +685,16 @@ def _render_images( raise ValueError("If 'palette' is provided, its length must match the number of channels.") channel_cmaps = [_get_linear_colormap([c], "k")[0] for c in palette if isinstance(c, str)] - - # Apply cmaps to each channel and add up colored = np.stack([channel_cmaps[i](layers[c]) for i, c in enumerate(channels)], 0).sum(0) - - # Remove alpha channel so we can overwrite it from render_params.alpha colored = colored[:, :, :3] _ax_show_and_transform(colored, trans_data, ax, render_params.alpha, zorder=render_params.zorder) elif palette is None and got_multiple_cmaps: channel_cmaps = [cp.cmap for cp in render_params.cmap_params] # type: ignore[union-attr] - - # Apply cmaps to each channel, add up and normalize to [0, 1] colored = ( np.stack([channel_cmaps[ind](layers[ch]) for ind, ch in enumerate(channels)], 0).sum(0) / n_channels ) - - # Remove alpha channel so we can overwrite it from render_params.alpha colored = colored[:, :, :3] _ax_show_and_transform(colored, trans_data, ax, render_params.alpha, zorder=render_params.zorder) @@ -794,22 +780,21 @@ def _render_labels( table_name=table_name, ) - # default case: no contour, just fill - # if fill_alpha and outline_alpha are the same, we're technically also at a no-outline situation - if render_params.outline_alpha == 0.0 or render_params.outline_alpha == render_params.fill_alpha: - labels_infill = _map_color_seg( + def _draw_labels(seg_erosionpx: int | None, seg_boundaries: bool) -> matplotlib.image.AxesImage: + labels = _map_color_seg( seg=label.values, cell_id=instance_id, color_vector=color_vector, color_source_vector=color_source_vector, cmap_params=render_params.cmap_params, - seg_erosionpx=None, - seg_boundaries=False, + seg_erosionpx=seg_erosionpx, + seg_boundaries=seg_boundaries, na_color=render_params.cmap_params.na_color, na_color_modified_by_user=render_params.cmap_params.na_color_modified_by_user, ) + _cax = ax.imshow( - labels_infill, + labels, rasterized=True, cmap=None if categorical else render_params.cmap_params.cmap, norm=None if categorical else render_params.cmap_params.norm, @@ -819,94 +804,33 @@ def _render_labels( ) _cax.set_transform(trans_data) cax = ax.add_image(_cax) + return cax # noqa: RET504 + + # default case: no contour, just fill + if render_params.fill_alpha > 0.0 and render_params.outline_alpha == 0.0: + cax = _draw_labels(seg_erosionpx=None, seg_boundaries=False) alpha_to_decorate_ax = render_params.fill_alpha # outline-only case - if render_params.fill_alpha == 0.0 and render_params.outline_alpha != 0.0: - labels_contour = _map_color_seg( - seg=label.values, - cell_id=instance_id, - color_vector=color_vector, - color_source_vector=color_source_vector, - cmap_params=render_params.cmap_params, - seg_erosionpx=render_params.contour_px, - seg_boundaries=True, - na_color=render_params.cmap_params.na_color, - na_color_modified_by_user=render_params.cmap_params.na_color_modified_by_user, - ) - _cax = ax.imshow( - labels_contour, - rasterized=True, - cmap=None if categorical else render_params.cmap_params.cmap, - norm=None if categorical else render_params.cmap_params.norm, - alpha=render_params.outline_alpha, - origin="lower", - zorder=render_params.zorder, - ) - _cax.set_transform(trans_data) - cax = ax.add_image(_cax) + elif render_params.fill_alpha == 0.0 and render_params.outline_alpha > 0.0: + cax = _draw_labels(seg_erosionpx=render_params.contour_px, seg_boundaries=True) alpha_to_decorate_ax = render_params.outline_alpha # pretty case: both outline and infill - if ( - render_params.fill_alpha > 0.0 - and render_params.outline_alpha > 0.0 - and render_params.fill_alpha != render_params.outline_alpha - ): + elif render_params.fill_alpha > 0.0 and render_params.outline_alpha > 0.0: # first plot the infill ... - label_infill = _map_color_seg( - seg=label.values, - cell_id=instance_id, - color_vector=color_vector, - color_source_vector=color_source_vector, - cmap_params=render_params.cmap_params, - seg_erosionpx=None, - seg_boundaries=False, - na_color=render_params.cmap_params.na_color, - na_color_modified_by_user=render_params.cmap_params.na_color_modified_by_user, - ) - - _cax_infill = ax.imshow( - label_infill, - rasterized=True, - cmap=None if categorical else render_params.cmap_params.cmap, - norm=None if categorical else render_params.cmap_params.norm, - alpha=render_params.fill_alpha, - origin="lower", - zorder=render_params.zorder, - ) - _cax_infill.set_transform(trans_data) - cax_infill = ax.add_image(_cax_infill) + cax_infill = _draw_labels(seg_erosionpx=None, seg_boundaries=False) # ... then overlay the contour - label_contour = _map_color_seg( - seg=label.values, - cell_id=instance_id, - color_vector=color_vector, - color_source_vector=color_source_vector, - cmap_params=render_params.cmap_params, - seg_erosionpx=render_params.contour_px, - seg_boundaries=True, - na_color=render_params.cmap_params.na_color, - na_color_modified_by_user=render_params.cmap_params.na_color_modified_by_user, - ) - - _cax_contour = ax.imshow( - label_contour, - rasterized=True, - cmap=None if categorical else render_params.cmap_params.cmap, - norm=None if categorical else render_params.cmap_params.norm, - alpha=render_params.outline_alpha, - origin="lower", - zorder=render_params.zorder, - ) - _cax_contour.set_transform(trans_data) - cax_contour = ax.add_image(_cax_contour) + cax_contour = _draw_labels(seg_erosionpx=render_params.contour_px, seg_boundaries=True) # pass the less-transparent _cax for the legend cax = cax_infill if render_params.fill_alpha > render_params.outline_alpha else cax_contour alpha_to_decorate_ax = max(render_params.fill_alpha, render_params.outline_alpha) + else: + raise ValueError("Parameters 'fill_alpha' and 'outline_alpha' cannot both be 0.") + _ = _decorate_axs( ax=ax, cax=cax, From 6cc7c0692db1ca93266cbf938b5b71a39eb6c23c Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 16:08:04 -0400 Subject: [PATCH 2/4] added test --- src/spatialdata_plot/pl/render.py | 16 ++++++++++------ tests/pl/test_render_labels.py | 11 +++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 91264377..486e0a66 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -780,7 +780,7 @@ def _render_labels( table_name=table_name, ) - def _draw_labels(seg_erosionpx: int | None, seg_boundaries: bool) -> matplotlib.image.AxesImage: + def _draw_labels(seg_erosionpx: int | None, seg_boundaries: bool, alpha: float) -> matplotlib.image.AxesImage: labels = _map_color_seg( seg=label.values, cell_id=instance_id, @@ -798,7 +798,7 @@ def _draw_labels(seg_erosionpx: int | None, seg_boundaries: bool) -> matplotlib. rasterized=True, cmap=None if categorical else render_params.cmap_params.cmap, norm=None if categorical else render_params.cmap_params.norm, - alpha=render_params.fill_alpha, + alpha=alpha, origin="lower", zorder=render_params.zorder, ) @@ -808,21 +808,25 @@ def _draw_labels(seg_erosionpx: int | None, seg_boundaries: bool) -> matplotlib. # default case: no contour, just fill if render_params.fill_alpha > 0.0 and render_params.outline_alpha == 0.0: - cax = _draw_labels(seg_erosionpx=None, seg_boundaries=False) + cax = _draw_labels(seg_erosionpx=None, seg_boundaries=False, alpha=render_params.fill_alpha) alpha_to_decorate_ax = render_params.fill_alpha # outline-only case elif render_params.fill_alpha == 0.0 and render_params.outline_alpha > 0.0: - cax = _draw_labels(seg_erosionpx=render_params.contour_px, seg_boundaries=True) + cax = _draw_labels( + seg_erosionpx=render_params.contour_px, seg_boundaries=True, alpha=render_params.outline_alpha + ) alpha_to_decorate_ax = render_params.outline_alpha # pretty case: both outline and infill elif render_params.fill_alpha > 0.0 and render_params.outline_alpha > 0.0: # first plot the infill ... - cax_infill = _draw_labels(seg_erosionpx=None, seg_boundaries=False) + cax_infill = _draw_labels(seg_erosionpx=None, seg_boundaries=False, alpha=render_params.fill_alpha) # ... then overlay the contour - cax_contour = _draw_labels(seg_erosionpx=render_params.contour_px, seg_boundaries=True) + cax_contour = _draw_labels( + seg_erosionpx=render_params.contour_px, seg_boundaries=True, alpha=render_params.outline_alpha + ) # pass the less-transparent _cax for the legend cax = cax_infill if render_params.fill_alpha > render_params.outline_alpha else cax_contour diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index 639c51d9..370120d0 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -79,6 +79,17 @@ def test_plot_can_stack_render_labels(self, sdata_blobs: SpatialData): .pl.show() ) + def test_plot_can_use_contour_px_to_enlarge_label_even_if_identical_alphas(self, sdata_blobs: SpatialData): + # adresses https://github.com/scverse/spatialdata-plot/issues/335 + ( + sdata_blobs.pl.render_labels( + element="blobs_labels", + fill_alpha=0.6, + outline_alpha=0.6, + contour_px=30, + ).pl.show() + ) + def test_plot_can_color_labels_by_continuous_variable(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_labels("blobs_labels", color="channel_0_sum").pl.show() From 7b801978024e859a438152f81408c5e0873180c1 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 16:19:48 -0400 Subject: [PATCH 3/4] Reverted behaviour, was correct --- src/spatialdata_plot/pl/render.py | 9 ++++++++- tests/pl/test_render_labels.py | 11 ----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 486e0a66..00753ea0 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -807,7 +807,14 @@ def _draw_labels(seg_erosionpx: int | None, seg_boundaries: bool, alpha: float) return cax # noqa: RET504 # default case: no contour, just fill - if render_params.fill_alpha > 0.0 and render_params.outline_alpha == 0.0: + # since contour_px is passed to skimage.morphology.erosion to create the contour, + # any border thickness is only within the label, not outside. Therefore, the case + # of fill_alpha == outline_alpha is equivalent to fill-only + if ( + (render_params.fill_alpha > 0.0 + and render_params.outline_alpha == 0.0) + or (render_params.fill_alpha == render_params.outline_alpha) + ): cax = _draw_labels(seg_erosionpx=None, seg_boundaries=False, alpha=render_params.fill_alpha) alpha_to_decorate_ax = render_params.fill_alpha diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index 370120d0..639c51d9 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -79,17 +79,6 @@ def test_plot_can_stack_render_labels(self, sdata_blobs: SpatialData): .pl.show() ) - def test_plot_can_use_contour_px_to_enlarge_label_even_if_identical_alphas(self, sdata_blobs: SpatialData): - # adresses https://github.com/scverse/spatialdata-plot/issues/335 - ( - sdata_blobs.pl.render_labels( - element="blobs_labels", - fill_alpha=0.6, - outline_alpha=0.6, - contour_px=30, - ).pl.show() - ) - def test_plot_can_color_labels_by_continuous_variable(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_labels("blobs_labels", color="channel_0_sum").pl.show() From 86ecc440d6d990612930e24f1fc23d41451dee57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:20:05 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/spatialdata_plot/pl/render.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 00753ea0..d96464c8 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -810,10 +810,8 @@ def _draw_labels(seg_erosionpx: int | None, seg_boundaries: bool, alpha: float) # since contour_px is passed to skimage.morphology.erosion to create the contour, # any border thickness is only within the label, not outside. Therefore, the case # of fill_alpha == outline_alpha is equivalent to fill-only - if ( - (render_params.fill_alpha > 0.0 - and render_params.outline_alpha == 0.0) - or (render_params.fill_alpha == render_params.outline_alpha) + if (render_params.fill_alpha > 0.0 and render_params.outline_alpha == 0.0) or ( + render_params.fill_alpha == render_params.outline_alpha ): cax = _draw_labels(seg_erosionpx=None, seg_boundaries=False, alpha=render_params.fill_alpha) alpha_to_decorate_ax = render_params.fill_alpha