From 9bf4edd43a9fef9188509eb5b80c26087d7dba8e Mon Sep 17 00:00:00 2001 From: selmanozleyen Date: Sun, 2 Aug 2026 22:29:18 +0200 Subject: [PATCH 1/3] fix: ensure radius values stored in AnnData.uns are writable by converting tuples to lists --- src/squidpy/gr/neighbors.py | 14 +++++++++++--- tests/graph/test_spatial_neighbors.py | 25 ++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/squidpy/gr/neighbors.py b/src/squidpy/gr/neighbors.py index 5a2bb7514..f3d932c4f 100644 --- a/src/squidpy/gr/neighbors.py +++ b/src/squidpy/gr/neighbors.py @@ -51,6 +51,11 @@ GraphPostprocessor = Callable[[GraphMatrixT, GraphMatrixT], tuple[GraphMatrixT, GraphMatrixT]] +def _radius_to_uns(radius: float | tuple[float, float] | None) -> float | list[float] | None: + """Store an interval radius as a list: :mod:`anndata` cannot write tuples.""" + return list(radius) if isinstance(radius, tuple) else radius + + class GraphBuilder[CoordT, GraphMatrixT](ABC): """Base class for spatial graph construction strategies. @@ -88,7 +93,10 @@ def postprocessors(self) -> Sequence[GraphPostprocessor[GraphMatrixT]]: @abstractmethod def uns_params(self) -> dict[str, Any]: - """Parameters stored in :attr:`anndata.AnnData.uns` after graph construction.""" + """Parameters stored in :attr:`anndata.AnnData.uns` after graph construction. + + Values must be writable by :mod:`anndata`, e.g. a :class:`list`, not a :class:`tuple`. + """ def combine( self, @@ -239,7 +247,7 @@ def __init__( def uns_params(self) -> dict[str, Any]: return { "coord_type": CoordType.GENERIC.v, - "radius": self.radius, + "radius": _radius_to_uns(self.radius), "transform": self.transform.v, } @@ -308,7 +316,7 @@ def __init__( def uns_params(self) -> dict[str, Any]: return { "coord_type": CoordType.GENERIC.v, - "radius": self.radius, + "radius": _radius_to_uns(self.radius), "transform": self.transform.v, } diff --git a/tests/graph/test_spatial_neighbors.py b/tests/graph/test_spatial_neighbors.py index 63b441c84..a82fca037 100644 --- a/tests/graph/test_spatial_neighbors.py +++ b/tests/graph/test_spatial_neighbors.py @@ -12,7 +12,13 @@ from squidpy._constants._constants import Transform from squidpy._constants._pkg_constants import Key -from squidpy.gr import mask_graph, spatial_neighbors, spatial_neighbors_from_builder +from squidpy.gr import ( + mask_graph, + spatial_neighbors, + spatial_neighbors_delaunay, + spatial_neighbors_from_builder, + spatial_neighbors_radius, +) from squidpy.gr.neighbors import ( DelaunayBuilder, GridBuilder, @@ -277,6 +283,23 @@ def test_delaunay_builder_scalar_radius_equals_zero_max_tuple(self, non_visium_a np.testing.assert_array_equal(scalar.connectivities.toarray(), interval.connectivities.toarray()) np.testing.assert_allclose(scalar.distances.toarray(), interval.distances.toarray()) + @pytest.mark.parametrize( + ("func", "radius", "expected"), + [ + (spatial_neighbors_radius, 5.0, 5.0), + (spatial_neighbors_radius, (2.0, 4.0), [2.0, 4.0]), + (spatial_neighbors_delaunay, (2.0, 4.0), [2.0, 4.0]), + (spatial_neighbors_delaunay, 5.0, [0.0, 5.0]), + ], + ids=["radius_scalar", "radius_interval", "delaunay_interval", "delaunay_scalar"], + ) + def test_radius_stored_in_uns_is_writable(self, non_visium_adata: AnnData, tmp_path, func, radius, expected): + func(non_visium_adata, radius=radius) + + # an interval radius is stored as a list: `anndata` cannot write tuples + assert non_visium_adata.uns[Key.uns.spatial_neighs()]["params"]["radius"] == expected + non_visium_adata.write_h5ad(tmp_path / "adata.h5ad") + def test_delaunay_mode_warns_on_n_neighs(self, non_visium_adata: AnnData): with pytest.warns(FutureWarning, match=r"Parameter `n_neighs` is ignored when `delaunay=True`"): spatial_neighbors(non_visium_adata, coord_type="generic", delaunay=True, n_neighs=3, copy=True) From 62fd617c0b60cac620cf8116ca17ea808b4ae0fe Mon Sep 17 00:00:00 2001 From: "selman.ozleyen" Date: Tue, 4 Aug 2026 16:55:26 +0200 Subject: [PATCH 2/3] rename func: cast to tuple if list --- src/squidpy/gr/neighbors.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/squidpy/gr/neighbors.py b/src/squidpy/gr/neighbors.py index f3d932c4f..538d9d81a 100644 --- a/src/squidpy/gr/neighbors.py +++ b/src/squidpy/gr/neighbors.py @@ -51,8 +51,7 @@ GraphPostprocessor = Callable[[GraphMatrixT, GraphMatrixT], tuple[GraphMatrixT, GraphMatrixT]] -def _radius_to_uns(radius: float | tuple[float, float] | None) -> float | list[float] | None: - """Store an interval radius as a list: :mod:`anndata` cannot write tuples.""" +def _cast_to_tuple_if_list(radius: float | tuple[float, float] | None) -> float | list[float] | None: return list(radius) if isinstance(radius, tuple) else radius @@ -247,7 +246,7 @@ def __init__( def uns_params(self) -> dict[str, Any]: return { "coord_type": CoordType.GENERIC.v, - "radius": _radius_to_uns(self.radius), + "radius": _cast_to_tuple_if_list(self.radius), "transform": self.transform.v, } @@ -316,7 +315,7 @@ def __init__( def uns_params(self) -> dict[str, Any]: return { "coord_type": CoordType.GENERIC.v, - "radius": _radius_to_uns(self.radius), + "radius": _cast_to_tuple_if_list(self.radius), "transform": self.transform.v, } From 4dd1c90f44a9238a56f63dbf119343f1cb18e4c9 Mon Sep 17 00:00:00 2001 From: "selman.ozleyen" Date: Tue, 4 Aug 2026 17:01:16 +0200 Subject: [PATCH 3/3] inline the normalization --- src/squidpy/gr/neighbors.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/squidpy/gr/neighbors.py b/src/squidpy/gr/neighbors.py index 538d9d81a..865be7372 100644 --- a/src/squidpy/gr/neighbors.py +++ b/src/squidpy/gr/neighbors.py @@ -51,10 +51,6 @@ GraphPostprocessor = Callable[[GraphMatrixT, GraphMatrixT], tuple[GraphMatrixT, GraphMatrixT]] -def _cast_to_tuple_if_list(radius: float | tuple[float, float] | None) -> float | list[float] | None: - return list(radius) if isinstance(radius, tuple) else radius - - class GraphBuilder[CoordT, GraphMatrixT](ABC): """Base class for spatial graph construction strategies. @@ -241,12 +237,13 @@ def __init__( percentile=percentile, postprocessors=postprocessors, ) - self.radius = radius + # Store intervals as a list: :mod:`anndata` cannot write tuples to ``uns``. + self.radius = list(radius) if isinstance(radius, tuple) else radius def uns_params(self) -> dict[str, Any]: return { "coord_type": CoordType.GENERIC.v, - "radius": _cast_to_tuple_if_list(self.radius), + "radius": self.radius, "transform": self.transform.v, } @@ -310,12 +307,13 @@ def __init__( percentile=percentile, postprocessors=postprocessors, ) - self.radius = radius + # Store intervals as a list: :mod:`anndata` cannot write tuples to ``uns``. + self.radius = list(radius) if isinstance(radius, tuple) else radius def uns_params(self) -> dict[str, Any]: return { "coord_type": CoordType.GENERIC.v, - "radius": _cast_to_tuple_if_list(self.radius), + "radius": self.radius, "transform": self.transform.v, }