diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index d97bb474..7ddd957f 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -1654,6 +1654,13 @@ def _render_labels( _, region_key, instance_key = get_table_keys(sdata[table_name]) table = sdata[table_name][sdata[table_name].obs[region_key].isin([element])] + if (table.obs[instance_key] == 0).any(): + raise ValueError( + f"Table '{table_name}' contains instance_id=0 for element '{element}'. Label value 0 is " + "reserved for background and must not appear in the annotation table. Remove the row with " + "instance_id=0 before plotting." + ) + # get instance id based on subsetted table instance_id = np.unique(table.obs[instance_key].values) diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index e2684e24..431a0dc1 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -490,3 +490,34 @@ def test_render_labels_rejects_float_dtype(dtype): sdata.pl.render_labels("lbl").pl.show(ax=ax) finally: plt.close(fig) + + +def test_render_labels_rejects_background_instance_id_in_table(): + # Regression test for #607: table row with instance_id=0 (background) + # used to crash with obnscure error. + labels_data = np.zeros((20, 20), dtype=np.int32) + labels_data[3:8, 3:8] = 1 + labels_data[12:17, 12:17] = 2 + labels = Labels2DModel.parse(labels_data, dims=["y", "x"]) + + obs = pd.DataFrame( + { + "region": pd.Categorical(["lbl"] * 3), + "instance_id": [0, 1, 2], + "score": [99.0, 1.0, 2.0], + } + ) + table = TableModel.parse( + AnnData(X=np.zeros((3, 1)), obs=obs), + region="lbl", + region_key="region", + instance_key="instance_id", + ) + sdata = SpatialData(labels={"lbl": labels}, tables={"t": table}) + + fig, ax = plt.subplots() + try: + with pytest.raises(ValueError, match=r"instance_id=0.*background"): + sdata.pl.render_labels("lbl", color="score", table_name="t").pl.show(ax=ax) + finally: + plt.close(fig)