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
29 changes: 26 additions & 3 deletions src/squidpy/_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,9 +442,20 @@ def decorator2(obj: Any) -> Any:
A dictionary mapping element names of the SpatialData object to coordinate systems.
The elements can be either Shapes or Labels. For compatibility, the spatialdata table must annotate
all regions keys. Must not be ``None`` if ``adata`` is a :class:`spatialdata.SpatialData`.
With ``coordinate_source="table"``, the element names are used to check table associations and instance
order. Coordinates are read as stored; the coordinate-system values do not trigger a transformation.
table_key
Key in :attr:`spatialdata.SpatialData.tables` where the spatialdata table is stored. Must not be ``None`` if
``adata`` is a :class:`spatialdata.SpatialData`."""
``adata`` is a :class:`spatialdata.SpatialData`.
coordinate_source
Source of coordinates for :class:`spatialdata.SpatialData` inputs. ``"elements"`` (default) computes
element centroids in the mapped coordinate systems and stores them under ``spatial_key``.
``"table"`` reads the selected table's existing ``obsm[spatial_key]`` coordinates, skipping centroid
computation and coordinate transformation. Supply a real numeric NumPy array of shape ``(n_obs, 2)``
with finite values and rows in table observation order. The coordinates must already be in the desired
coordinate system, with units appropriate for the graph builder. Missing or invalid coordinates raise
an error without falling back to element centroids. Both values use existing ``obsm[spatial_key]``
coordinates for :class:`anndata.AnnData` inputs."""
_graph_common_params = """\
percentile
Percentile of the distances to use as threshold.
Expand All @@ -463,15 +474,26 @@ def decorator2(obj: Any) -> Any:
{_n_jobs_thread_rules}
Speedup is sub-linear (memory-bandwidth bound), so parallelism mainly pays off for many
large libraries."""
_spatial_neighbors_returns = """\
_spatial_neighbors_copy_notes = """\
For SpatialData inputs, ``copy=True`` builds the graph in a lightweight temporary AnnData and leaves
the source table unchanged. The expression matrix is neither copied nor loaded. Table mode gives the
temporary object its own copy of the coordinates. With ``copy=False``, graph results are saved to the
selected table. Element mode writes computed coordinates to ``obsm[spatial_key]``; table mode uses
the existing coordinates."""
_spatial_neighbors_returns = (
"""\
If ``copy = True``, returns a :class:`~squidpy.types.SpatialNeighborsResult` with the
spatial connectivities and distances matrices.

Otherwise, modifies the ``adata`` with the following keys:

- :attr:`anndata.AnnData.obsp` ``['{key_added}_connectivities']`` - the spatial connectivities.
- :attr:`anndata.AnnData.obsp` ``['{key_added}_distances']`` - the spatial distances.
- :attr:`anndata.AnnData.uns` ``['{key_added}']`` - :class:`dict` containing parameters."""
- :attr:`anndata.AnnData.uns` ``['{key_added}']`` - :class:`dict` containing parameters.

"""
+ _spatial_neighbors_copy_notes
)

d = DocstringProcessor(
adata=_adata,
Expand Down Expand Up @@ -532,4 +554,5 @@ def decorator2(obj: Any) -> Any:
graph_common_params=_graph_common_params,
n_jobs_libraries=_n_jobs_libraries,
spatial_neighbors_returns=_spatial_neighbors_returns,
spatial_neighbors_copy_notes=_spatial_neighbors_copy_notes,
)
78 changes: 58 additions & 20 deletions src/squidpy/gr/_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ def spatial_neighbors(
adata: AnnData | SpatialData,
*,
spatial_key: str = Key.obsm.spatial,
coordinate_source: Literal["elements", "table"] = "elements",
elements_to_coordinate_systems: dict[str, str] | None = None,
table_key: str | None = None,
library_key: str | None = None,
Expand Down Expand Up @@ -182,15 +183,7 @@ def spatial_neighbors(
----------
%(adata)s
%(spatial_key)s
If `adata` is a :class:`spatialdata.SpatialData`, the coordinates of the centroids will be stored in the
`adata` with this key.
elements_to_coordinate_systems
A dictionary mapping element names of the SpatialData object to coordinate systems.
The elements can be either Shapes or Labels. For compatibility, the spatialdata table must annotate
all regions keys. Must not be `None` if `adata` is a :class:`spatialdata.SpatialData`.
table_key
Key in :attr:`spatialdata.SpatialData.tables` where the spatialdata table is stored. Must not be `None` if
`adata` is a :class:`spatialdata.SpatialData`.
%(sdata_params)s
%(library_key)s
coord_type
Type of coordinate system. Valid options are:
Expand Down Expand Up @@ -307,6 +300,8 @@ def spatial_neighbors(
- :attr:`anndata.AnnData.obsp` ``['{key_added}_connectivities']`` - the spatial connectivities.
- :attr:`anndata.AnnData.obsp` ``['{key_added}_distances']`` - the spatial distances.
- :attr:`anndata.AnnData.uns` ``['{key_added}']`` - :class:`dict` containing parameters.

%(spatial_neighbors_copy_notes)s
"""
warnings.warn(
"Calling `spatial_neighbors` is deprecated and will be removed in squidpy "
Expand All @@ -319,6 +314,7 @@ def spatial_neighbors(
adata, library_key = _prepare_spatial_neighbors_input(
adata,
spatial_key=spatial_key,
coordinate_source=coordinate_source,
elements_to_coordinate_systems=elements_to_coordinate_systems,
table_key=table_key,
library_key=library_key,
Expand Down Expand Up @@ -353,9 +349,12 @@ def _resolve_data(
elements_to_coordinate_systems: dict[str, str] | None,
table_key: str | None = None,
spatial_key: str = Key.obsm.spatial,
coordinate_source: Literal["elements", "table"] = "elements",
library_key: str | None = None,
copy: bool = False,
) -> tuple[AnnData, str | None]:
if coordinate_source not in ("elements", "table"):
raise ValueError(f"Invalid `coordinate_source` {coordinate_source!r}. Expected 'elements' or 'table'.")
if not isinstance(data, SpatialData):
return data, library_key

Expand Down Expand Up @@ -385,27 +384,40 @@ def _resolve_data(
elem_instances.append(element_instances)

element_instances = pd.concat(elem_instances)
if (not np.all(element_instances.values == table.obs[instance_key].values)) or (
not np.all(ordered_regions_in_table == regions)
if not np.array_equal(element_instances.values, table.obs[instance_key].values) or not np.array_equal(
ordered_regions_in_table, regions
):
raise ValueError(
"The spatialdata table must annotate all elements keys. Some elements are missing or not ordered correctly, please check the `elements_to_coordinate_systems` dictionary."
)
centroids = []
for region_ in ordered_regions_in_table:
cs = elements_to_coordinate_systems[region_]
centroid = get_centroids(data[region_], coordinate_system=cs)[["x", "y"]].compute()
if coordinate_source == "table":
coords = table.obsm[spatial_key]
if not isinstance(coords, np.ndarray) or coords.dtype.kind not in "iuf":
raise TypeError(f"`table.obsm[{spatial_key!r}]` must be a real numeric NumPy array.")
if coords.shape != (table.n_obs, 2):
raise ValueError(f"`table.obsm[{spatial_key!r}]` must have shape ({table.n_obs}, 2), found {coords.shape}.")
if not np.isfinite(coords).all():
raise ValueError(f"`table.obsm[{spatial_key!r}]` must contain only finite coordinates.")
if copy:
coords = coords.copy()
else:
centroids = []
for region_ in ordered_regions_in_table:
cs = elements_to_coordinate_systems[region_]
centroid = get_centroids(data[region_], coordinate_system=cs)[["x", "y"]].compute()

# TODO: remove this after https://github.com/scverse/spatialdata/issues/614
if remove_centroids[region_]:
centroid = centroid[1:].copy()
centroids.append(centroid)
# TODO: remove this after https://github.com/scverse/spatialdata/issues/614
if remove_centroids[region_]:
centroid = centroid[1:].copy()
centroids.append(centroid)
coords = np.concatenate(centroids)

if copy:
# Graph construction only needs observations, coordinates and the uns keys
# used by legacy graph dispatch. Do not copy or load expression matrices.
table = AnnData(obs=table.obs.copy(), uns=table.uns.copy())
table.obsm[spatial_key] = np.concatenate(centroids)
if copy or coordinate_source == "elements":
table.obsm[spatial_key] = coords
return table, region_key


Expand All @@ -416,6 +428,7 @@ def spatial_neighbors_from_builder(
*,
builder: GraphBuilder[Any, Any],
spatial_key: str = Key.obsm.spatial,
coordinate_source: Literal["elements", "table"] = "elements",
elements_to_coordinate_systems: dict[str, str] | None = None,
table_key: str | None = None,
library_key: str | None = None,
Expand Down Expand Up @@ -471,6 +484,7 @@ def spatial_neighbors_from_builder(
adata, library_key = _prepare_spatial_neighbors_input(
data,
spatial_key=spatial_key,
coordinate_source=coordinate_source,
elements_to_coordinate_systems=elements_to_coordinate_systems,
table_key=table_key,
library_key=library_key,
Expand All @@ -491,6 +505,7 @@ def _prepare_spatial_neighbors_input(
data: AnnData | SpatialData,
*,
spatial_key: str,
coordinate_source: Literal["elements", "table"] = "elements",
elements_to_coordinate_systems: dict[str, str] | None,
table_key: str | None,
library_key: str | None,
Expand All @@ -500,6 +515,7 @@ def _prepare_spatial_neighbors_input(
adata, library_key = _resolve_data(
data,
spatial_key=spatial_key,
coordinate_source=coordinate_source,
elements_to_coordinate_systems=elements_to_coordinate_systems,
table_key=table_key,
library_key=library_key,
Expand All @@ -514,6 +530,7 @@ def spatial_neighbors_knn(
data: AnnData | SpatialData,
*,
spatial_key: str = Key.obsm.spatial,
coordinate_source: Literal["elements", "table"] = "elements",
elements_to_coordinate_systems: dict[str, str] | None = None,
table_key: str | None = None,
library_key: str | None = None,
Expand Down Expand Up @@ -562,6 +579,7 @@ def spatial_neighbors_knn(
adata, library_key = _prepare_spatial_neighbors_input(
data,
spatial_key=spatial_key,
coordinate_source=coordinate_source,
elements_to_coordinate_systems=elements_to_coordinate_systems,
table_key=table_key,
library_key=library_key,
Expand All @@ -584,6 +602,7 @@ def spatial_neighbors_radius(
*,
radius: float | tuple[float, float],
spatial_key: str = Key.obsm.spatial,
coordinate_source: Literal["elements", "table"] = "elements",
elements_to_coordinate_systems: dict[str, str] | None = None,
table_key: str | None = None,
library_key: str | None = None,
Expand Down Expand Up @@ -620,6 +639,20 @@ def spatial_neighbors_radius(
-------
%(spatial_neighbors_returns)s

Examples
--------
Reuse coordinates stored in a SpatialData table. The coordinates must already
be in the desired coordinate system and use the same units as ``radius``.

>>> sq.gr.spatial_neighbors_radius(
... sdata,
... table_key="table",
... elements_to_coordinate_systems={"cells": "global"},
... spatial_key="precomputed_spatial",
... coordinate_source="table",
... radius=40,
... )

See Also
--------
spatial_neighbors_from_builder : Use :class:`~squidpy.gr.neighbors.RadiusBuilder` directly for advanced customization.
Expand All @@ -634,6 +667,7 @@ def spatial_neighbors_radius(
adata, library_key = _prepare_spatial_neighbors_input(
data,
spatial_key=spatial_key,
coordinate_source=coordinate_source,
elements_to_coordinate_systems=elements_to_coordinate_systems,
table_key=table_key,
library_key=library_key,
Expand All @@ -655,6 +689,7 @@ def spatial_neighbors_delaunay(
data: AnnData | SpatialData,
*,
spatial_key: str = Key.obsm.spatial,
coordinate_source: Literal["elements", "table"] = "elements",
elements_to_coordinate_systems: dict[str, str] | None = None,
table_key: str | None = None,
library_key: str | None = None,
Expand Down Expand Up @@ -710,6 +745,7 @@ def spatial_neighbors_delaunay(
adata, library_key = _prepare_spatial_neighbors_input(
data,
spatial_key=spatial_key,
coordinate_source=coordinate_source,
elements_to_coordinate_systems=elements_to_coordinate_systems,
table_key=table_key,
library_key=library_key,
Expand All @@ -731,6 +767,7 @@ def spatial_neighbors_grid(
data: AnnData | SpatialData,
*,
spatial_key: str = Key.obsm.spatial,
coordinate_source: Literal["elements", "table"] = "elements",
elements_to_coordinate_systems: dict[str, str] | None = None,
table_key: str | None = None,
library_key: str | None = None,
Expand Down Expand Up @@ -799,6 +836,7 @@ def spatial_neighbors_grid(
adata, library_key = _prepare_spatial_neighbors_input(
data,
spatial_key=spatial_key,
coordinate_source=coordinate_source,
elements_to_coordinate_systems=elements_to_coordinate_systems,
table_key=table_key,
library_key=library_key,
Expand Down
Loading
Loading