Skip to content

Commit 5a5900e

Browse files
committed
fix: raise clear error on disjoint instance IDs in render_shapes
Closes #603. Signed-off-by: SAY-5 <say.apm35@gmail.com>
1 parent 53abe71 commit 5a5900e

2 files changed

Lines changed: 45 additions & 0 deletions

File tree

src/spatialdata_plot/pl/render.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,18 @@ def _render_shapes(
345345
if _saved_index_name is not None and _saved_index_name in _obs.columns:
346346
_obs.index.name = None
347347

348+
# Guard against a disjoint instance_key (#603): when no table rows annotate any element
349+
# row, the upstream join crashes with `KeyError: None` from deep inside pandas. Raise
350+
# a clear, actionable ValueError before invoking the join.
351+
_, _region_key, _instance_key = get_table_keys(sdata[table_name])
352+
_annotating = sdata[table_name].obs[sdata[table_name].obs[_region_key].isin([element])]
353+
if len(_annotating) > 0 and set(_annotating[_instance_key]).isdisjoint(set(sdata[element].index)):
354+
raise ValueError(
355+
f"No instance IDs overlap between table '{table_name}' (instance_key='{_instance_key}') "
356+
f"and element '{element}'. Check that the table's '{_instance_key}' column matches the "
357+
f"element's index."
358+
)
359+
348360
try:
349361
element_dict, joined_table = join_spatialelement_table(
350362
sdata, spatial_element_names=element, table_name=table_name, how="inner"

tests/pl/test_render_shapes.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,6 +1323,39 @@ def test_render_shapes_color_with_conflicting_index_name():
13231323
sdata.pl.render_shapes("shapes", color="cell_type", table_name="table").pl.show()
13241324

13251325

1326+
def test_render_shapes_disjoint_instance_ids_clear_error():
1327+
# Regression test for #603: when a table annotates the element (region key matches) but no
1328+
# instance_id values overlap, the call used to crash with a bare `KeyError: None` from deep
1329+
# inside spatialdata's join path. Replace with a clear, actionable ValueError.
1330+
from shapely.geometry import Point
1331+
1332+
shapes = ShapesModel.parse(
1333+
gpd.GeoDataFrame({"geometry": [Point(5, 5), Point(15, 5), Point(25, 5)], "radius": [2.0] * 3})
1334+
)
1335+
obs = pd.DataFrame(
1336+
{
1337+
"instance_id": [99, 100, 101], # element has IDs 0, 1, 2 -- no overlap
1338+
"region": pd.Categorical(["s"] * 3),
1339+
"cat": pd.Categorical(["A", "B", "C"]),
1340+
}
1341+
)
1342+
obs.index = obs.index.astype(str)
1343+
table = TableModel.parse(
1344+
AnnData(X=np.zeros((3, 1)), obs=obs),
1345+
region=["s"],
1346+
region_key="region",
1347+
instance_key="instance_id",
1348+
)
1349+
sdata = SpatialData(shapes={"s": shapes}, tables={"t": table})
1350+
1351+
fig, ax = plt.subplots()
1352+
try:
1353+
with pytest.raises(ValueError, match=r"No instance IDs overlap.*table 't'.*element 's'"):
1354+
sdata.pl.render_shapes("s", color="cat", table_name="t").pl.show(ax=ax)
1355+
finally:
1356+
plt.close(fig)
1357+
1358+
13261359
def test_datashader_colorbar_range_matches_data(sdata_blobs: SpatialData):
13271360
"""Datashader colorbar range must not exceed the actual data range for shapes.
13281361

0 commit comments

Comments
 (0)