diff --git a/src/spatialdata/_core/spatialdata.py b/src/spatialdata/_core/spatialdata.py index 8be3c2682..9f5c91c7a 100644 --- a/src/spatialdata/_core/spatialdata.py +++ b/src/spatialdata/_core/spatialdata.py @@ -215,9 +215,9 @@ def validate_table_in_spatialdata(self, table: AnnData) -> None: else: dtype = element.index.dtype instance_dtype = self.get_instance_key_column(table).dtype - if dtype != instance_dtype and ( - pd.api.types.is_string_dtype(dtype) or pd.api.types.is_string_dtype(instance_dtype) - ): + # Only a string vs non-string mismatch is an error: string ids may be stored as `object` or + # as a pandas string dtype (the default with pandas>=3) depending on the reader. + if pd.api.types.is_string_dtype(dtype) != pd.api.types.is_string_dtype(instance_dtype): raise TypeError( f"Table instance_key column ({instance_key}) has a dtype " f"({instance_dtype}) that does not match the dtype of the indices of " diff --git a/tests/datasets/test_datasets.py b/tests/datasets/test_datasets.py index 3fc66370c..d71afedd2 100644 --- a/tests/datasets/test_datasets.py +++ b/tests/datasets/test_datasets.py @@ -2,10 +2,11 @@ from pathlib import Path +import pandas as pd import pooch import pytest -from spatialdata import SpatialData +from spatialdata import SpatialData, read_zarr from spatialdata.datasets import _cache_dir, _shipped_registry, blobs, cells, raccoon @@ -70,3 +71,23 @@ def test_cells_download(tmp_path) -> None: assert len(sdata.shapes["nucleus_boundaries"]) == 94 assert len(sdata.points["transcripts"].compute()) == 19479 assert sdata.tables["table"].shape == (94, 5101) + + +@pytest.mark.network +def test_cells_string_instance_key_dtype(tmp_path) -> None: + # Regression: the `cell_boundaries` index is read as the pandas>=3 `str` dtype while the table's `cell_id` + # column is read as `object`; both hold string ids, so annotating by `cell_id` must validate and round-trip. + sdata = cells(path=str(tmp_path / "cache")) + table = sdata.tables["table"] + table.obs["region"] = pd.Categorical(["cell_boundaries"] * table.n_obs) + sdata.set_table_annotates_spatialelement("table", region="cell_boundaries", instance_key="cell_id") + sdata.validate_table_in_spatialdata(table) + + sdata.write(tmp_path / "data.zarr") + sdata_read = read_zarr(tmp_path / "data.zarr") + assert sdata_read.tables["table"].uns["spatialdata_attrs"]["instance_key"] == "cell_id" + + # a string vs integer mismatch is still an error + sdata.set_table_annotates_spatialelement("table", region="cell_boundaries", instance_key="cell_labels") + with pytest.raises(TypeError, match="does not match the dtype"): + sdata.validate_table_in_spatialdata(table)