Proposal: spatial-transcriptomics adapter + Visium proof-of-concept
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Quiet
- Tech stack
- python
- Domain
- data, documentation
Research direction
Start with the Visium proof-of-concept in the issue and the existing focal.hotspots, convolution, proximity, zonal, and kde entry points. Validate the workflow as a user-guide example against a public Visium HD dataset before deciding the adapter module and optional AnnData boundary. Done means the example works at the stated scale and the bridge scope and unresolved API choices are documented.
Written by the indexing model from the issue text.
Description
Reason or Problem
Spatial transcriptomics (ST) pairs spatial coordinates with high-dimensional gene-expression vectors, the same shape of data xarray-spatial already models as a many-band, Dask/GPU-backed DataArray. Two ST regimes are becoming raster-native:
- 10x Visium HD produces a continuous 2 µm bin grid: a regular north-up lattice with one value per gene per bin, structurally identical to a multiband raster.
- Imaging platforms (MERFISH, seqFISH, Xenium, CosMX) emit individual transcript molecules or cell centroids as points, at billions-of-points scale.
The existing ST tooling (Squidpy on AnnData/GeoPandas, SpatialData) covers the statistics catalog well, but it is graph/point oriented and handles very large dense grids awkwardly. xarray-spatial's out-of-core Dask + GPU raster engine fits that scale regime, and none of its raster ops are currently reachable from an ST data model.
User persona
"Priya", a computational biologist / bioinformatics engineer on a spatial-omics team. She works in the scanpy/scverse Python stack and is comfortable with AnnData and numpy, but she is not a GIS specialist. Her Visium HD runs are large enough that per-gene spatial statistics in the point/graph tools are slow, and she has GPUs available but no easy way to use them for spatial-domain analysis. She wants to find spatially variable genes and expression hotspots over a tissue section without rewriting her pipeline.
Proposal
A thin adapter that bridges an ST data object into xarray-spatial's raster model, then lets the existing ops run with Dask/GPU scale-out. This is not a reimplementation of Squidpy. It is a bridge plus a documented workflow.
Design:
- A
from_anndata(adata, genes=..., like=None, resolution=...)helper in a new small module (for examplexrspatial.experimental.transcriptomics) that takes spot/bin coordinates fromadata.obsm['spatial']and expression columns and returns a(gene, y, x)DataArray. Visium HD bins map directly onto their grid; classic Visium spots bin to their lattice. The point-to-grid step is a plain coordinate-binning scatter (see the proof-of-concept below);rasterizestays available for the polygon and line cases. - Once the data is in the DataArray model, the existing ops apply unchanged:
focal.hotspots(Getis-Ord Gi*) per gene for expression hotspots,focal.focal_stats/convolve_2dfor smoothing,proximityfor cell-type distance fields,zonalfor per-domain aggregation, andkdefor molecule density from imaging platforms. - Keep it at the experimental/advanced tier with no new hard dependencies. AnnData is handled as an optional import, following the existing optional-import pattern.
Usage / Visium proof-of-concept (shapely-free):
import numpy as np
import xarray as xr
from xrspatial.focal import hotspots
from xrspatial.convolution import circle_kernel
# --- inputs from a Visium/Visium HD AnnData ---
# xy: (n_spots, 2) tissue-space coordinates from adata.obsm['spatial']
# expr: (n_spots,) counts for one gene of interest (e.g. adata[:, 'EPCAM'].X)
xy = np.asarray(adata.obsm['spatial'], dtype='float64')
expr = np.asarray(adata[:, gene].X).ravel().astype('float64')
# 1. bin spot coordinates onto a regular grid -> (y, x) DataArray, no GDAL/shapely
x0, y0 = xy.min(0)
ix = np.floor((xy[:, 0] - x0) / bin_size).astype('int64')
iy = np.floor((xy[:, 1] - y0) / bin_size).astype('int64')
h, w = iy.max() + 1, ix.max() + 1
grid = np.full((h, w), np.nan, dtype='float64')
grid[iy, ix] = expr # scatter expression into bins
agg = xr.DataArray(
grid, dims=('y', 'x'),
coords={'y': y0 + (np.arange(h) + 0.5) * bin_size,
'x': x0 + (np.arange(w) + 0.5) * bin_size},
name=gene,
)
# 2. Getis-Ord Gi* hotspots of expression over a local neighborhood
kernel = circle_kernel(bin_size, bin_size, radius=3 * bin_size)
hot = hotspots(agg, kernel) # signed z-score classes: hot / cold / not-significant
# 3. `hot` is a north-up raster of statistically significant expression
# hot/cold spots for that gene, ready to overlay on the H&E image,
# and it runs on numpy, cupy, or Dask by swapping the input backend
# (wrap `grid` with dask.array / cupy before building `agg`).
For Visium HD the same code runs against the native 2 µm bin grid; for classic Visium it runs against the ~55 µm spot lattice. Swapping agg for a Dask- or CuPy-backed DataArray scales the identical call out-of-core or onto the GPU.
Value: Reuses shipped, cross-backend ops (rasterize, focal.hotspots, proximity, zonal, kde) to reach a new domain with a small surface area. The differentiator is scale: GPU/Dask focal and hotspot statistics over dense expression grids, where the incumbent point/graph tooling slows down. It also grows the user base beyond GIS and earth observation into spatial-omics.
Stakeholders and Impacts
Stakeholders are the maintainers (a small new experimental module plus an optional AnnData import) and ST practitioners in the scverse ecosystem. The impact is additive because it reuses existing ops; the only new code is the adapter and docs/example. It touches a new experimental module, optional-import handling, and one user-guide example notebook.
Drawbacks
- Squidpy and SpatialData already own the ST analysis workflow and integrate with single-cell downstream steps this library does not touch, so there is a risk of duplicating a served niche if scope creeps past the bridge.
- ST domain conventions (coordinate systems, units, QC) differ from GIS, and getting them subtly wrong erodes trust with the target persona.
- It adds a domain the maintainers may not have expertise to support long-term.
Alternatives
- Do nothing and point ST users at Squidpy (status quo).
- Ship only documentation showing how to hand-roll the DataArray from AnnData, with no adapter code.
- Contribute the Dask/GPU raster ops upstream to SpatialData instead of pulling ST into this repo.
Unresolved Questions
- Scope: bridge-only, or a curated set of ST-flavored convenience wrappers?
- Where the adapter lives (
experimentalvs a separate contrib package) and how AnnData is depended on. - Whether to target Visium HD first (the cleanest raster fit) and defer imaging platforms.
Additional Notes or Context
The best first deliverable is the Visium proof-of-concept above, written as a user-guide example against a public Visium HD dataset, to validate the ergonomics before committing to an adapter API.
- Dominant language
- Python
- Stars
- 972
- Forks
- 92
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 7
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from xarray-contrib/xarray-spatial
-
Difficulty 1/5 Under an hour Newbie friendliness 68/100
xarray-contrib/xarray-spatial#3726 ·
-
api area:surface bug severity:medium sweep-api-consistency
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
xarray-contrib/xarray-spatial#3712 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
xarray-contrib/xarray-spatial#3710 ·
-
bug
Difficulty 1/5 1-3 hours Newbie friendliness 88/100
xarray-contrib/xarray-spatial#3707 ·
-
area:surface documentation user-guide-example
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
xarray-contrib/xarray-spatial#3464 ·
All issues in xarray-contrib/xarray-spatial
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 90/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
zostera/django-bootstrap4#894 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
use-agent-os/agent-os#3276 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
zephyrproject-rtos/zephyr#119726 ·
-
area/auth bug comp/agent P3 platform/discord type/security
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
NousResearch/hermes-agent#117848 ·