SpatialData¶
SCIMAP Pro speaks SpatialData natively. There
is no separate API and no conversion step in your analysis code: the same
data argument that takes an AnnData takes a SpatialData object or a path
to a .zarr store.
import scimappro as sp
import spatialdata as sd
sdata = sd.read_zarr("sample.zarr")
sdata = sp.tl.spatialDistance(sdata, sdataTable="table")
sdata.tables["table"].uns["spatial_distance"]
What data accepts¶
| You pass | scimappro does |
|---|---|
AnnData |
works on it directly |
"cells.h5ad" |
reads it (or streams it, with streamData=True) |
SpatialData |
works on the table named by sdataTable |
"sample.zarr" |
read_zarr, then as above |
All four go through one resolver, scimappro._data, so every function behaves
the same way. There is no isinstance branch scattered through the codebase and
no function that supports only some of the four.
Choosing a table¶
sdataTable names the table to operate on. It is always the last named
parameter of every function, so the positional arguments of functions with a
required second argument are unaffected.
Leave it out when the store has exactly one table. With several, scimappro
raises a ValueError that lists them:
ValueError: This SpatialData has several tables; pass sdataTable to say which
one to use. Available tables: ['cells', 'nuclei', 'membranes'].
Naming a table that does not exist raises a KeyError listing the ones that do.
Coordinates and image ids¶
scimappro reads coordinates from obs['X_centroid'] / obs['Y_centroid'] and
groups cells by obs['imageid']. A SpatialData table usually has neither,
because that information lives in the elements the table annotates.
When those columns are missing, scimappro derives them and stores them on the table, preserving row order:
imageidcomes from the table's region-key column.X_centroid/Y_centroidcome from the annotated shapes, points, or labels — centroids of the geometry each row refers to.
You do not have to do anything for this to happen; pass the store and the
functions find what they need. Existing columns are never overwritten, so if
your table already has X_centroid, that is what gets used.
sp.tl.spatialCooccurrence(sdata, sdataTable="cells")
# Derived obs columns ['imageid', 'X_centroid', 'Y_centroid'] from the
# SpatialData elements.
Reading and writing¶
outputDir decides whether you get an object back or a store on disk.
The table is modified in place inside the container, and the same
SpatialData comes back.
Functions that replace the table¶
tl.classify, tl.rename,
pp.dropFeatures, and
helpers.addROI_omero build a new table
rather than annotating the existing one. For SpatialData input they replace the
table inside the container you passed and return that container.
Keep the annotation columns
The replacement table must keep its region_key and instance_key obs
columns intact, or SpatialData's TableModel will refuse the write-back.
pp.dropFeatures(sdata, dropMetaColumns=["region"]) will fail for exactly
this reason.
Converting an existing cell table¶
pp.toSpatialData turns a scimap-style AnnData
(or .h5ad) into a SpatialData: one circles element per image holding
that image's cell centroids, with the whole table attached to those elements.
sdata = sp.pp.toSpatialData(adata) # radius from obs['Area']
sp.pp.toSpatialData(adata, outputDir="converted") # writes converted/<name>.zarr
Details worth knowing:
- Every
obscolumn is preserved, so nothing is lost even though the geometry is two-dimensional. radiustakes anobscolumn name or a plain number when you do not want the defaultsqrt(Area / pi).CellIDis generated as1..nwhen the table has no instance-key column.- Image ids that are not valid SpatialData element names are sanitised into a
separate
regioncolumn;imageiditself is left untouched.
Streaming¶
streamData=True applies to .h5ad paths only — see
Streaming.
Round trip¶
import scimappro as sp
import spatialdata as sd
adata = sp.pp.mcmicro_to_scimap("quantification/quant.csv")
adata = sp.pp.rescale(adata, gate="manual_gates.csv")
adata = sp.tl.phenotype(adata, phenotype="phenotype_workflow.csv")
sp.pp.toSpatialData(adata, outputDir="converted")
sdata = sd.read_zarr("converted/adata_scimap.zarr")
sp.tl.spatialCooccurrence(sdata, outputDir="converted/adata_scimap.zarr")