Skip to content

render_shapes crashes with KeyError: None when table instance IDs don't match element #603

@timtreis

Description

@timtreis

render_shapes crashes with KeyError: None when table instance IDs don't match element

Description

When calling render_shapes("s", color="cat", table_name="t") where the table correctly annotates element "s" (region key matches) but none of the table's instance_id values overlap with the element's actual instance IDs, the call crashes with a bare KeyError: None from deep inside pandas. The user gets a 15+ frame traceback that terminates in element.loc[None, :] with no indication of what went wrong or how to fix it.

Root cause: join_spatialelement_table(..., how="inner") returns an empty result. The downstream code passes None as an index to element.loc, which pandas raises as KeyError: None.

Environment

spatialdata-plot: 0.3.4.dev (main, 5cfedc7)
spatialdata: 0.5.0
Python: 3.13

Minimal Reproducible Example

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import geopandas as gpd
import anndata as ad
import spatialdata as sd
from spatialdata.models import ShapesModel, TableModel
import spatialdata_plot
from shapely.geometry import Point

shapes = ShapesModel.parse(gpd.GeoDataFrame({
    "geometry": [Point(5, 5), Point(15, 5), Point(25, 5)],
    "radius": [2.0] * 3,
}))

obs = pd.DataFrame({
    "instance_id": [99, 100, 101],  # element has IDs 0, 1, 2 — no overlap
    "region": pd.Categorical(["s"] * 3),
    "cat": pd.Categorical(["A", "B", "C"]),
})
obs.index = obs.index.astype(str)

table = TableModel.parse(
    ad.AnnData(X=np.zeros((3, 1)), obs=obs),
    region=["s"], region_key="region", instance_key="instance_id",
)
sdata = sd.SpatialData(shapes={"s": shapes}, tables={"t": table})

fig, ax = plt.subplots()
sdata.pl.render_shapes("s", color="cat", table_name="t").pl.show(ax=ax)

Actual Output

KeyError: None

Full traceback ends at element.loc[None, :] buried in spatialdata internals (_get_masked_element). No indication that the instance ID sets are disjoint.

Expected Output

Either:

  • All shapes rendered in na_color with a UserWarning: "No instance IDs in table 't' overlap with element 's'. Shapes will be rendered with na_color."
  • Or a clear ValueError: "No instance IDs overlap between table 't' (instance_key='instance_id') and element 's'. Check that the table's instance_id column matches the element's index."

Fix Sketch

In _render_shapes (and analogously _render_labels, _render_points), before calling join_spatialelement_table, check whether the intersection of table instance IDs and element instance IDs is empty. If so, warn and skip coloring (fall back to na_color) or raise a descriptive ValueError.

element_ids = set(element.index)
table_ids = set(table.obs[instance_key])
if element_ids.isdisjoint(table_ids):
    raise ValueError(
        f"No instance IDs overlap between table '{table_name}' "
        f"(instance_key='{instance_key}') and element '{element_name}'. "
        f"Check that the table's instance_id column matches the element's index."
    )

Note: the root cause lives upstream in spatialdata._get_masked_element, but a guard in spatialdata-plot provides immediate user-facing relief without requiring a coordinated upstream fix.


Triage tier: Tier 2

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions