diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 9ee64033..6ab13a6f 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -646,7 +646,7 @@ def _render_shapes( outline_color_vector: Any = None if col_for_outline_color is not None: # When the outline column lives in a table that hasn't been joined yet - # (no fill table, or a different table than fill's), inner-join it onto + # (no fill table, or a different table than fill's), left-join it onto # the element so the lookup is aligned and the element row count matches # the outline vector length. if outline_table_name is not None and outline_table_name != table_name: diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index c1ef029f..82b3e7a2 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -444,7 +444,11 @@ def _join_table_for_element( element: str, table_name: str, ) -> tuple[Any, AnnData]: - """Inner-join ``element`` with its annotating ``table_name``. + """Left-join ``element`` with its annotating ``table_name``. + + A left join keeps every shape, including those without a table row (they get no color value and + are rendered with ``na_color``), matching the points/labels behaviour instead of silently dropping + unannotated shapes. Wraps the workaround for scverse/spatialdata#1099: ``join_spatialelement_table`` calls ``table.obs.reset_index()`` which fails when the obs index name matches @@ -470,7 +474,7 @@ def _join_table_for_element( try: element_dict, joined_table = join_spatialelement_table( - sdata, spatial_element_names=element, table_name=table_name, how="inner" + sdata, spatial_element_names=element, table_name=table_name, how="left" ) finally: if _saved_index is not None: diff --git a/tests/_images/Shapes_can_color_two_queried_shapes_elements_by_annotation.png b/tests/_images/Shapes_can_color_two_queried_shapes_elements_by_annotation.png index 240934d2..77d14762 100644 Binary files a/tests/_images/Shapes_can_color_two_queried_shapes_elements_by_annotation.png and b/tests/_images/Shapes_can_color_two_queried_shapes_elements_by_annotation.png differ diff --git a/tests/_images/Shapes_can_do_non_matching_table.png b/tests/_images/Shapes_can_do_non_matching_table.png index 4f5272e4..3a464fbf 100644 Binary files a/tests/_images/Shapes_can_do_non_matching_table.png and b/tests/_images/Shapes_can_do_non_matching_table.png differ diff --git a/tests/_images/Shapes_can_plot_queried_with_annotation_despite_random_shuffling.png b/tests/_images/Shapes_can_plot_queried_with_annotation_despite_random_shuffling.png index 3031fde4..77d1ac75 100644 Binary files a/tests/_images/Shapes_can_plot_queried_with_annotation_despite_random_shuffling.png and b/tests/_images/Shapes_can_plot_queried_with_annotation_despite_random_shuffling.png differ diff --git a/tests/_images/Shapes_can_plot_with_annotation_despite_random_shuffling.png b/tests/_images/Shapes_can_plot_with_annotation_despite_random_shuffling.png index 07f37d02..a50eae7c 100644 Binary files a/tests/_images/Shapes_can_plot_with_annotation_despite_random_shuffling.png and b/tests/_images/Shapes_can_plot_with_annotation_despite_random_shuffling.png differ diff --git a/tests/_images/Shapes_fill_and_outline_both_obs_columns.png b/tests/_images/Shapes_fill_and_outline_both_obs_columns.png index 9d9f81ac..57f0d6ef 100644 Binary files a/tests/_images/Shapes_fill_and_outline_both_obs_columns.png and b/tests/_images/Shapes_fill_and_outline_both_obs_columns.png differ diff --git a/tests/_images/Shapes_respects_custom_colors_from_uns.png b/tests/_images/Shapes_respects_custom_colors_from_uns.png index 3923c496..42810c8b 100644 Binary files a/tests/_images/Shapes_respects_custom_colors_from_uns.png and b/tests/_images/Shapes_respects_custom_colors_from_uns.png differ diff --git a/tests/_images/Shapes_shapes_unannotated_by_table_hidden_with_na_color_none.png b/tests/_images/Shapes_shapes_unannotated_by_table_hidden_with_na_color_none.png new file mode 100644 index 00000000..1eae50e4 Binary files /dev/null and b/tests/_images/Shapes_shapes_unannotated_by_table_hidden_with_na_color_none.png differ diff --git a/tests/_images/Shapes_shapes_unannotated_by_table_render_with_na_color.png b/tests/_images/Shapes_shapes_unannotated_by_table_render_with_na_color.png new file mode 100644 index 00000000..b4196c97 Binary files /dev/null and b/tests/_images/Shapes_shapes_unannotated_by_table_render_with_na_color.png differ diff --git a/tests/pl/test_render_shapes.py b/tests/pl/test_render_shapes.py index f51cbf36..1a7803f1 100644 --- a/tests/pl/test_render_shapes.py +++ b/tests/pl/test_render_shapes.py @@ -439,6 +439,20 @@ def test_render_shapes_raises_for_missing_column_in_table(self, sdata_blobs_shap element="blobs_polygons", color="not_a_column", table_name="table" ) + def test_plot_shapes_unannotated_by_table_render_with_na_color(self, sdata_blobs_shapes_annotated: SpatialData): + # Regression for #710: blobs_polygons instance 0 has no row in the table (instance_id starts + # at 1), so coloring by a table column must render it with na_color, not drop it. + sdata_blobs_shapes_annotated.pl.render_shapes( + "blobs_polygons", color="channel_0_sum", na_color="red" + ).pl.show() + + def test_plot_shapes_unannotated_by_table_hidden_with_na_color_none(self, sdata_blobs_shapes_annotated: SpatialData): + # Counterpart to the test above: na_color=None makes the unannotated polygon (instance 0) + # transparent, opting back into hiding it. + sdata_blobs_shapes_annotated.pl.render_shapes( + "blobs_polygons", color="channel_0_sum", na_color=None + ).pl.show() + def test_plot_can_plot_shapes_after_spatial_query(self, sdata_blobs: SpatialData): # subset to only shapes, should be unnecessary after rasterizeation of multiscale images is included blob = SpatialData.init_from_elements(