Spatial Genomics ingest
Load one ROI of a Spatial Genomics (GenePS / sganalysis) export into NimbusImage. Needs
pip install 'nimbusimage[sg]'. Every step is also a subcommand of the nimbusimage-sg
command.
nimbusimage.sg.export
Reading one ROI of a Spatial Genomics (GenePS / sganalysis) data export.
An export holds one project folder with two trees per ROI::
<project>/basic_results/roi_N/
roi_N_Boundaries.geojson cell polygons, MICRONS, id = cell label
roi_N_CellCoordinates.csv label, area (um^2), center_x, center_y
roi_N_CellxGene.csv raw counts, one row per label
roi_N_TranscriptList.csv name, x, y, z (microns)
roi_N_Segmentation.tiff uint32 cell-label mask
roi_N_DAPI.tiff duplicate of images/dapi.tiff
<project>/mini_project/roi_N/
images/*.tiff OME-TIFF channels (dapi, GFP_image, ...)
clusters/matrix_*_cells.h5ad AnnData: obs leiden, obsm X_umap
genes/<project>.hdf5 per-gene points in PIXELS (+ UNUSED*)
sganalysis.session JSON: shape, res (um / pixel)
The cell label is the join key everywhere: the GeoJSON feature id, the CellxGene row index, the h5ad obs index and the segmentation mask value.
SGRoi
One ROI of a Spatial Genomics export.
SGRoi(export_dir, "roi_3"); export_dir may be the project
folder or a parent of it (see :func:find_project_dir). Files are read
lazily, and a missing one raises :class:SGError.
clusters_path
property
The cells' h5ad (clusters/matrix_*_cells.h5ad), or None.
pixel_size
property
Microns per image pixel (res in sganalysis.session).
shape
property
Image (rows, cols) in pixels.
image_paths()
(channel name, path) for each image, DAPI first.
polygons()
(labels, rings): each ring is an [n, 2] array of micron
vertices with the closing vertex dropped; outer ring only.
coordinates()
CellCoordinates as a DataFrame indexed by label: area
(um^2), center_x, center_y (microns).
counts()
(labels, symbols, names, counts): raw counts as a
cells x genes scipy.sparse.csr_matrix; symbols are the
names as NimbusImage stores them (:func:safe_symbol).
clusters()
From the cells' h5ad: labels, leiden (int, -1 for
missing), umap ([n, 2] or None) and gene_ids
({name: Ensembl id}). None when the export has no h5ad.
transcripts()
(gene_names, gene_codes, xyz): sorted names (stored form,
:func:safe_symbol, matching the table's symbols), an int index
into them per molecule, and [n, 3] float32 microns.
find_project_dir(path)
The folder holding basic_results/ and mini_project/.
Accepts that folder itself or any parent up to two levels above it
(DataExport_X/ or the folder the zip was extracted into), as long
as exactly one project folder is found.
list_rois(path)
The ROI names of an export (roi_1, roi_2, ...) in order.
channel_name(path)
dapi.tiff -> DAPI; GFP_image.tiff -> GFP.
NimbusImage's filename parser splits on _, so the name must not
contain one.
nimbusimage.sg.cells
The cell map: Spatial Genomics cell label -> NimbusImage annotation id.
Saved by upload_polygons together with what it belongs to — the
dataset, the ROI and project, and the pixel size the polygons were drawn
with — so a later step can refuse a map of another dataset or ROI before
writing anything.
SGCellMap
labels[i] was uploaded as annotation ids[i].
check(ds, roi=None)
Refuse a map of another dataset, ROI, or pixel size.
verify_cells(ds, roi, cells, sample=50, seed=0)
Read back a random sample of the map's annotations and compare each
first vertex with the GeoJSON's (at cells.pixel_size). Returns the
number checked; raises on any mismatch or missing annotation.
nimbusimage.sg.ingest
Upload steps of the Spatial Genomics ingest.
Reuses the Xenium machinery where the data model is the same (channel staging, polygon annotations, property helpers, the spatial-table upload that refuses another dataset's table); what differs is how each input is read and that the transcript list must be converted into a store.
upload_images(client, roi, name, parent_folder_id=None, wait=True)
A dataset from the ROI's mini_project/roi_N/images, one channel
per file (DAPI first), named by file (GFP_image -> GFP).
Waits for the transcode unless wait=False.
upload_polygons(ds, roi, *, tags=None, batch=5000, limit=None)
Bulk-create the ROI's cell polygons (px = um / pixel_size).
Returns the cell map; save it (.save(path)), every later step
takes it. Polygons with fewer than three vertices are skipped.
upload_cell_data(ds, roi, cells, *, base_tags=None, chunk=5000, verify_sample=20)
From the export: per cell
- tags
base_tags + ["Leiden k"](categorical, for the tag filter and selection summary; replaces the cell's tags).base_tagsdefaults to the tags the polygons were created with (cells.tags), SG Area / area_um2(CellCoordinates),UMAP / x, yandClustering / leiden(the h5ad) — the numeric cluster is what Color by Property (and so the overview raster) can color by.
Property values are replaced, not merged (a re-run is exact). Returns cluster sizes.
build_spatial_table(roi, cells, out)
Write the dataset's spatial.zarr.zip from CellxGene.csv.
::
X raw counts, cells x genes, CSC
layers/X_csr the same, CSR
obs annotation_id (the join key), cell_index (= label),
cell_type ("Leiden k"), leiden
var index = symbol; gene_id (Ensembl, from the h5ad);
feature_type "gene"; feature_name (as exported)
obsm/X_umap when the h5ad has one
uns/nimbus schemaVersion, datasetId, source, created
Rows are the mapped cells (others are dropped and logged).
upload_spatial_table(ds, path)
Upload and register a table built for ds (refuses another
dataset's).
build_transcript_store(roi, out)
Convert TranscriptList.csv into a registrable transcript store
(see :mod:nimbusimage.sg.transcripts).
transcript_store_source(path)
The nimbus_source a store was written with ({} if none).
register_transcripts(ds, cells, path)
Upload a store from :func:build_transcript_store and register it
at the cell map's pixel size — the frame the polygons were drawn in.
Refuses a store written for another ROI.
nimbusimage.sg.transcripts
Write molecules as a transcript store the SpatialPlugin can register.
The plugin reads one layout, 10x Xenium's transcripts.zarr.zip (see
codebaseDocumentation/SPATIAL_PLUGIN.md, "the transcript layer"), so a
Spatial Genomics transcript list is rewritten into it::
attrs gene_names
grids attrs number_levels, grid_size [250], grid_keys, grid_number_objects
grids/{L}/{gx},{gy} tiles of 250 um * 2**L from the origin, rows sorted
by gene; gene_offset[g] = [lowStart, lowEnd,
highStart, highEnd]
level 0 location (x, y, z um), quality_score,
gene_identity, id
level L > 0 each gene's molecules merged on a grid of
0.5 um * 2**L: location (centroid),
gene_identity, cluster_count
density/gene attrs rows, cols, grid_size [10]; CSR over
(gene * rows + row) of a 10 um grid
The store is written in zarr format 2 (the server reads with zarr 2), through either zarr 2 or zarr 3 on the client.
write_transcript_store(gene_names, genes, xyz, out, *, source=None, quality=DEFAULT_QUALITY, levels=LEVELS)
Write out (a .zarr.zip) from per-molecule gene indices into
gene_names and [n, 3] micron positions. source is stored as
the root attribute nimbus_source (register_transcripts checks
it against the cell map). Returns out.
nimbusimage.sg.verify
Local checks of an export before (and independent of) any upload.
- :func:
check_frame: cell centroids land on their own label in the segmentation mask atfloor(um / pixel_size)— the frame the polygons and molecules will be drawn in. - :func:
recount: assigning every molecule to the mask label under it reproducesCellxGene.csv— molecules, cells and the matrix agree.
Mask lookups decode only the tiles they touch, so neither check reads a whole (up to ~11 GB) label image.
labels_at(path, x, y)
Values of a (tiled) label TIFF's full-resolution level at integer
pixel (x, y); out-of-image positions give 0.
check_frame(roi, sample=300, seed=0)
Fraction of sampled cell centroids that land on their own label.
recount(roi)
Reassign every molecule by the mask and compare with CellxGene.csv.
Returns molecule totals (inside cells, and in the matrix), the fraction of identical matrix entries, the correlation, and the largest per-entry difference.
nimbusimage.sg.errors
Errors raised by the Spatial Genomics ingest.
SGError
Bases: Exception
A Spatial Genomics export, input file, or upload does not match expectations.
Raised instead of writing wrong data: a missing file, labels that do not join, a cell map of another dataset or ROI, a server that created fewer annotations than were sent.