@@ -311,6 +311,27 @@ def _add_legend_and_colorbar(
311311 )
312312
313313
314+ def _check_instance_ids_overlap (
315+ sdata : sd .SpatialData ,
316+ table_name : str ,
317+ element_name : str ,
318+ element_index : abc .Iterable [Any ],
319+ ) -> None :
320+ """Raise a clear error when a table annotates an element but no instance IDs overlap (#603).
321+
322+ Without this guard the downstream join/lookup crashes with an opaque ``KeyError: None`` from
323+ deep inside pandas, giving no hint that the ``instance_key`` value sets are disjoint.
324+ """
325+ _ , region_key , instance_key = get_table_keys (sdata [table_name ])
326+ annotating = sdata [table_name ].obs [sdata [table_name ].obs [region_key ].isin ([element_name ])]
327+ if len (annotating ) > 0 and set (annotating [instance_key ]).isdisjoint (set (element_index )):
328+ raise ValueError (
329+ f"No instance IDs overlap between table '{ table_name } ' (instance_key='{ instance_key } ') "
330+ f"and element '{ element_name } '. Check that the table's '{ instance_key } ' column matches the "
331+ f"element's index."
332+ )
333+
334+
314335def _render_shapes (
315336 sdata : sd .SpatialData ,
316337 render_params : ShapesRenderParams ,
@@ -336,6 +357,10 @@ def _render_shapes(
336357 table = None
337358 shapes = sdata_filt [element ]
338359 else :
360+ # Guard against a disjoint instance_key (#603) *before* mutating obs.index.name below,
361+ # so a failure here can never leave the index name in a half-restored state.
362+ _check_instance_ids_overlap (sdata , table_name , element , sdata [element ].index )
363+
339364 # Workaround for upstream spatialdata bug (scverse/spatialdata#1099):
340365 # join_spatialelement_table calls table.obs.reset_index() which fails when
341366 # the obs index name matches an existing column (e.g. "EntityID" in Merfish data).
@@ -742,6 +767,10 @@ def _render_points(
742767
743768 added_color_from_table = False
744769 if col_for_color is not None and col_for_color not in points .columns :
770+ if table_name is not None :
771+ # Guard against disjoint instance IDs (#603): without this the table lookup below
772+ # crashes with an opaque `KeyError: None` instead of explaining the mismatch.
773+ _check_instance_ids_overlap (sdata , table_name , element , points .index )
745774 color_values = get_values (
746775 value_key = col_for_color ,
747776 sdata = sdata_filt ,
@@ -1651,6 +1680,7 @@ def _render_labels(
16511680 instance_id = np .unique (label )
16521681 table = None
16531682 else :
1683+ _check_instance_ids_overlap (sdata , table_name , element , np .unique (label .values ))
16541684 _ , region_key , instance_key = get_table_keys (sdata [table_name ])
16551685 table = sdata [table_name ][sdata [table_name ].obs [region_key ].isin ([element ])]
16561686
0 commit comments