Skip to content

Commit e34f7db

Browse files
committed
fix: render shapes unannotated by the table with na_color instead of dropping them (#710)
render_shapes joined the element to its table with how="inner", silently dropping shapes whose instance had no table row. Points (how="left" merge) and labels (raster + na_color) keep unannotated elements, so shapes were the inconsistent outlier (and it broke the labels<->shapes interchangeability). Switch _join_table_for_element to how="left": all shapes survive, and the per-shape color lookup (_extract_color_column, reindexed to the element) yields NaN -> na_color for the unannotated ones, via the existing NaN-handling path. Fully-annotated data is unaffected (verified pixel-identical: inner == left when nothing is dropped), so only partial-annotation output changes. Adds a visual regression test: coloring blobs_polygons by a table column now renders the one unannotated polygon (instance 0 has no table row) with na_color instead of dropping it.
1 parent 8d74219 commit e34f7db

3 files changed

Lines changed: 14 additions & 3 deletions

File tree

src/spatialdata_plot/pl/render.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ def _render_shapes(
646646
outline_color_vector: Any = None
647647
if col_for_outline_color is not None:
648648
# When the outline column lives in a table that hasn't been joined yet
649-
# (no fill table, or a different table than fill's), inner-join it onto
649+
# (no fill table, or a different table than fill's), left-join it onto
650650
# the element so the lookup is aligned and the element row count matches
651651
# the outline vector length.
652652
if outline_table_name is not None and outline_table_name != table_name:

src/spatialdata_plot/pl/utils.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,11 @@ def _join_table_for_element(
444444
element: str,
445445
table_name: str,
446446
) -> tuple[Any, AnnData]:
447-
"""Inner-join ``element`` with its annotating ``table_name``.
447+
"""Left-join ``element`` with its annotating ``table_name``.
448+
449+
A left join keeps every shape, including those without a table row (they get no color value and
450+
are rendered with ``na_color``), matching the points/labels behaviour instead of silently dropping
451+
unannotated shapes.
448452
449453
Wraps the workaround for scverse/spatialdata#1099: ``join_spatialelement_table``
450454
calls ``table.obs.reset_index()`` which fails when the obs index name matches
@@ -470,7 +474,7 @@ def _join_table_for_element(
470474

471475
try:
472476
element_dict, joined_table = join_spatialelement_table(
473-
sdata, spatial_element_names=element, table_name=table_name, how="inner"
477+
sdata, spatial_element_names=element, table_name=table_name, how="left"
474478
)
475479
finally:
476480
if _saved_index is not None:

tests/pl/test_render_shapes.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,13 @@ def test_render_shapes_raises_for_missing_column_in_table(self, sdata_blobs_shap
439439
element="blobs_polygons", color="not_a_column", table_name="table"
440440
)
441441

442+
def test_plot_shapes_unannotated_by_table_render_with_na_color(self, sdata_blobs_shapes_annotated: SpatialData):
443+
# Regression for #710: blobs_polygons instance 0 has no row in the table (instance_id starts
444+
# at 1), so coloring by a table column must render it with na_color, not drop it.
445+
sdata_blobs_shapes_annotated.pl.render_shapes(
446+
"blobs_polygons", color="channel_0_sum", na_color="red"
447+
).pl.show()
448+
442449
def test_plot_can_plot_shapes_after_spatial_query(self, sdata_blobs: SpatialData):
443450
# subset to only shapes, should be unnecessary after rasterizeation of multiscale images is included
444451
blob = SpatialData.init_from_elements(

0 commit comments

Comments
 (0)