Skip to main content

Load an SDO

Open a Spatial Data Object in Python.

Each cell screening run produces a Spatial Data Object (SDO) as a RunID.zarr.zip archive. The SDO comes from on-instrument analysis on the AVITI24™ System, the multiomics workflow, or a standalone Cells2Stats run.

The SDO contains the per-cell tables, polony points, images, and segmentation for a run. It follows the scverse SpatialData format and is compatible with AnnData, Scanpy, and other scverse tools. You can load an SDO in three ways:

  • SpatialData: Unzip the archive and load all elements into a single object. Recommended for full access to images, segmentation, tables, and points.
  • AnnData: Read an individual table from an unzipped store.
  • Loader: Load tables, points, and run metadata directly from the zipped archive, on local storage or Amazon S3.

Load with SpatialData​

Unzip the archive​

The archive contains a single top-level RunID.zarr/ directory. Extract it to a working directory:

unzip /path/to/RunID.zarr.zip -d /path/to/workdir/
Note:

The extracted store requires approximately the same disk space as the archive. Retain the .zarr.zip archive after extraction, because CytoCanvas requires the zipped SDO.

Load the store​

import spatialdata as sd

sdata = sd.read_zarr("/path/to/workdir/RunID.zarr")
print(sdata)

Access tables​

Tables are stored as AnnData objects, with one row per segmented cell. List the available tables, then access a table by name:

print(sorted(sdata.tables))

adata = sdata.tables["global-table-ThreePrimeUntargeted"]
img = sdata.tables["global-table-imaging"]

The tables in an SDO depend on the assays included in the run:

TableContents
global-table-ThreePrimeUntargeted3′ transcriptome counts
global-table-imagingCellPaint morphology and protein IF features
global-table-{batch}-{panel}, such as global-table-B01-A549Counts for each target in one OPS / specialized panel, such as a guide RNA
global-table-metadataRun-level metadata (no cell data)

Access images and segmentation​

Images, labels (segmentation masks), and shapes (cell boundaries) are stored per well, with keys such as A1-images-cell_paint, A1-labels-Cell, and A1-shapes-Cell. For the full layout, see SDO structure.

To plot a well, use plot_image from the multiomics Python package. Pass a table and a column name to color cells by a measurement or annotation:

from elembio_spatialdata_analysis.vis.spatial import plot_image

plot_image(sdata, well="A1", outlines=True)
plot_image(sdata, well="A1", adata=adata, color="leiden") # after clustering

To access pixel data directly, select a level of the image pyramid. Images load lazily, so only the selected region is read from disk:

image = sdata.images["A1-images-cell_paint"]["scale0"].ds["image"] # (c, y, x)
nucleus = image.sel(c="Nucleus").isel(y=slice(0, 1024), x=slice(0, 1024)).values

Load a table with AnnData​

To read a single table without loading the full store, pass the path of the table directory in the unzipped store to anndata.read_zarr:

import anndata as ad

adata = ad.read_zarr("/path/to/workdir/RunID.zarr/tables/global-table-ThreePrimeUntargeted")

Load with Loader​

Loader, part of the multiomics Python package, loads tables, points, and run metadata directly from the zipped archive, on local storage or Amazon S3. Use Loader to keep the SDO zipped when you do not need images or segmentation. Loader also reports the contents of a run and combines tables from multiple runs.

from elembio_spatialdata_analysis.load import Loader

loader = Loader("/path/to/RunID.zarr.zip")
print(loader.summary())

Loader also accepts the following inputs:

# Amazon S3 (pass storage_options to specify an AWS profile)
loader = Loader("s3://bucket/path/to/RunID.zarr.zip", storage_options={"profile": "your-profile"})

# Public S3 bucket, no credentials
loader = Loader("s3://public-bucket/path/to/RunID.zarr.zip", storage_options={"anon": True})

# Unzipped store
loader = Loader("/path/to/workdir/RunID.zarr")

# SpatialData object loaded with sd.read_zarr
loader = Loader(sdata)

# Multiple runs
loader = Loader(["/path/to/run1.zarr.zip", "/path/to/run2.zarr.zip"])

Load tables​

Loader refers to tables by name without the global-table- prefix:

print(sorted(loader.available_tables)) # e.g. ['ThreePrimeUntargeted', 'imaging']

adata = loader.load_tables(table="ThreePrimeUntargeted", lazy=False)
img = loader.load_tables(table="imaging", lazy=False)

Set lazy=False to load the table into memory, as most Scanpy functions require. When Loader is given multiple runs, load_tables returns a single AnnData object with a run_name column in obs. For OPS / specialized panel tables, set include_target_assignment=True to add each cell's target assignment to obs.

Load points​

load_points returns polony locations as a Polars LazyFrame. A run contains many more polonies than cells, so filter by target type, batch, or well before calling .collect():

print(sorted(loader.available_target_types))

points = loader.load_points(target_type="ThreePrimeUntargeted", well="A1").collect()

Link cells across elements​

adata.obs["cell_id_global"] uniquely identifies each cell in a run. The same ID appears as the pixel value in {well}-labels-Cell, the index of {well}-shapes-Cell, and the label_Cell_global column in points. Use it to join tables or to locate the pixels and polonies for a cell. For details, see How SDO elements link together.

Next steps​

See also​