Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/spatialdata_plot/pl/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,21 @@ def _render_shapes(
table = None
shapes = sdata_filt[element]
else:
element_dict, joined_table = join_spatialelement_table(
sdata, spatial_element_names=element, table_name=table_name, how="inner"
)
# Workaround for upstream spatialdata bug (scverse/spatialdata#1099):
# join_spatialelement_table calls table.obs.reset_index() which fails when
# the obs index name matches an existing column (e.g. "EntityID" in Merfish data).
# Temporarily drop the conflicting index name for the join, then restore it.
_obs = sdata[table_name].obs
_saved_index_name = _obs.index.name
if _saved_index_name is not None and _saved_index_name in _obs.columns:
_obs.index.name = None

try:
element_dict, joined_table = join_spatialelement_table(
sdata, spatial_element_names=element, table_name=table_name, how="inner"
)
finally:
_obs.index.name = _saved_index_name
sdata_filt[element] = shapes = element_dict[element]
joined_table.uns["spatialdata_attrs"]["region"] = (
joined_table.obs[joined_table.uns["spatialdata_attrs"]["region_key"]].unique().tolist()
Expand Down
33 changes: 33 additions & 0 deletions tests/pl/test_render_shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1142,3 +1142,36 @@ def test_datashader_alpha_not_applied_twice(sdata_blobs: SpatialData):
"on top of the alpha already in the RGBA channels — causing double transparency."
)
plt.close(fig)


def test_render_shapes_color_with_conflicting_index_name():
"""render_shapes(color=...) must not crash when obs.index.name matches an existing column.

Regression test for https://github.com/scverse/spatialdata-plot/issues/441.
In Merfish data, the table's instance_key (e.g. 'EntityID') can appear as both
the obs index name and an obs column. Upstream spatialdata's join_spatialelement_table
calls reset_index() which raises ValueError when the index name collides with a column.
"""
n = 10
rng = get_standard_RNG()
circles = [Point(i, i) for i in range(n)]
shapes_df = gpd.GeoDataFrame({"geometry": circles, "radius": np.ones(n)})
shapes_df = ShapesModel.parse(shapes_df)

obs = pd.DataFrame(
{
"region": pd.Categorical(["shapes"] * n),
"EntityID": np.arange(n),
"cell_type": pd.Categorical(rng.choice(["A", "B", "C"], n)),
}
)
table = AnnData(obs=obs)
table = TableModel.parse(table, region="shapes", region_key="region", instance_key="EntityID")

sdata = SpatialData(shapes={"shapes": shapes_df}, tables={"table": table})

# Introduce the conflicting state: index name == existing column name
sdata["table"].obs.index = pd.Index(np.arange(n), name="EntityID")

# Should not raise ValueError: cannot insert EntityID, already exists
sdata.pl.render_shapes("shapes", color="cell_type", table_name="table").pl.show()
Loading