The SMINT workflow¶
This page follows one dataset from raw image to an integrated table. For each step it lists what goes in, how to run it from Python, the command line or napari, what comes out, and how long it took on real data. Use it to find where you are and what the next step needs from you.
flowchart TD
IMG[/"Whole-slide image<br/>OME-TIFF, TIFF or OME-Zarr"/] --> SEG
subgraph S1 ["1 · Segmentation"]
SEG["Segment each tile + overlap<br/>Cellpose 3 / 4 on Dask workers"]
STI["Stitch duplicates<br/>merge → trim → remove"]
REC["Reconcile cells and nuclei<br/>one nucleus, one cell"]
SEG --> STI --> REC
end
REC --> TAB[/"cells / nuclei outlines and summaries<br/>image pixels"/]
TAB --> Q{"Was the second modality<br/>measured on the same section<br/>as the ST data?"}
subgraph S2 ["2a · Same section"]
CEN["Multiround centroid registration"]
SNAP["Snap nuclei onto their cells"]
CEN --> SNAP
end
subgraph S3 ["2b · Sequential sections"]
PRE["Pre-register<br/>flip → rotate → scale → translate"]
LM["Pick paired landmarks"]
LD["STalign LDDMM"]
PRE --> LM --> LD
end
Q -- "yes: post-stain nuclei" --> CEN
Q -- "no: e.g. MALDI on a<br/>neighbouring section" --> PRE
SNAP --> OUT[/"Tables in the ST frame<br/>x_transformed, y_transformed"/]
LD --> OUT
OUT --> R["3 · Downstream R scripts<br/>transcripts and metabolites → cells"]
classDef io fill:#2b6cb02e,stroke:#2b6cb0
class IMG,TAB,OUT io
Blue boxes are files you hand from one step to the next. The registration branch is the one decision you have to make; see Choosing a registration method.
Throughout, the spatial transcriptomics (ST) data is the fixed reference: every other modality moves onto it, and every final coordinate is in the ST frame, in microns. Coordinate frames explains each frame on the way.
1 · Segment and stitch¶
Cellpose finds cells and nuclei in tiles of the slide. SMINT reads each tile with an overlap margin, so cells on a tile boundary are seen whole, then stitches the duplicates this creates and reconciles cells with nuclei. The stitching runs automatically at the end of the distributed run; you only see it through its outputs. Tiling and stitching shows each phase.
| Inputs | A multichannel whole-slide image (OME-TIFF, TIFF, Zarr or OME-Zarr) on storage every worker can read; a cell and/or nucleus Cellpose model (built-in name or path to a fine-tuned model); per-model diameter, flow_threshold, cellprob_threshold, channels, usually in a YAML config |
| Python | smint.segmentation.run_distributed_segmentation(image_path, output_dir, config_path=...); stitching on its own: smint.segmentation.distributed_seg.stitch_and_reconcile |
| Command line | smint-segment-distributed --image_path ... --output_dir ... |
| napari | — (segmentation runs as a job; the napari plugin covers registration) |
| Outputs | cells_outlines.csv, nuclei_outlines.csv (one row per vertex: global_cell_id, x, y); cells_summary.csv (global_cell_id, x_centroid, y_centroid, area, n_nuclei); nuclei_summary.csv (cell_id in place of n_nuclei); raw label masks cells.zarr, nuclei.zarr. All in full-image pixels. |
| Typical runtime | Sample 3, 4096 × 4096 px, 16 tiles on 4 A30 GPUs: 0.6 min (44 cells, 44 nuclei). A 2048 px crop with Cellpose 4 and a fine-tuned Cellpose-SAM model on one A30: 23 s. |
from smint.segmentation import run_distributed_segmentation
out = run_distributed_segmentation(
"slide.ome.tif", "seg_out/",
config_path="segmentation.yaml", # models, diameters, channels
backend="slurm", # or "local" for the GPUs on this machine
n_workers=4,
tile_overlap=256, # > the largest cell radius, in pixels
)
print(out["total_cells"], out["total_nuclei"], out["cells_summary_path"])
One GPU without stitching
smint.segmentation.process_large_image segments a slide on one GPU in
non-overlapping chunks and writes <base>_cells.csv / <base>_nuclei.csv.
It does not stitch, so cells cut by a chunk border stay cut. On one
machine with one GPU you can still get stitched outputs from
run_distributed_segmentation(..., backend="local").
How-to: Segmentation · Running on HPC · tutorials Segment a whole slide and Distributed segmentation on SLURM · reference Configuration.
2a · Same section: centroid registration¶
Use this branch when the second modality was imaged on the same physical
section as the ST data, for example nuclei stained and imaged after the Xenium
run. Each nucleus then has a partner Xenium cell, so registration is a
point-matching problem. SMINT fits a coarse-to-fine transform by displacement
voting (multiround), then snaps each confidently matched nucleus exactly
onto its cell.
| Inputs | Source (moving) centroid CSV, e.g. nucleus centroids; target (fixed) centroid CSV, e.g. Xenium x_centroid, y_centroid. Both in microns and within roughly max_distance (default 100 µm) of each other. |
| Python | smint.alignment.register_centroid_files(source_file, target_file, output_path=...), or register_centroids + refine_by_assignment on arrays |
| Command line | smint-alignment centroid --source ... --target ... --output-dir ... (add --submit for SLURM) |
| napari | 4. Register widget, round centroid |
| Outputs | centroids_transformed.csv: the source table plus x_transformed, y_transformed (snapped positions), x_transformed_smooth, y_transformed_smooth (transform only), registration_status, target_index, neighbour_deviation; status.json with the validation metrics |
| Typical runtime | Sample 2 z5, 88,906 nuclei onto 74,247 Xenium cells: 3 s for multiround |
from smint.alignment import register_centroid_files
table, result = register_centroid_files(
"nuclei_um.csv", "xenium_cells.csv",
source_cols=("x_centroid", "y_centroid"),
target_cols=("x_centroid", "y_centroid"),
output_path="nuclei_registered.csv",
)
print(result["tre_validation"]) # held-out, the number to trust
print(result["refinement"]["n_snapped"])
Segmentation outputs are in pixels
cells_summary.csv and nuclei_summary.csv hold image pixels, and the
centroid CLI defaults to source columns centroid_x, centroid_y. Convert
the centroids to microns in the ST frame first, and name the columns you
pass. See Coordinate frames.
How-to: Alignment · tutorial Same-section registration · reference Command line.
2b · Sequential sections: pre-registration, landmarks and LDDMM¶
Use this branch when the second modality comes from a neighbouring section, such as MALDI spatial metabolomics. The tissue really differs between sections, so there are no one-to-one partners. SMINT instead brings the two datasets roughly together, takes a few paired landmarks from you, and lets STalign's LDDMM find a smooth, invertible warp.
Pre-register¶
| Inputs | Source CSV (SM pixels with x, y, or any table with detectable coordinate columns); reference CSV (ST cells) |
| Python | build_pretransform, apply_pretransform, overlap_score, save_transform |
| Command line | smint-alignment pretransform --source ... --target ... --output ... (--search grid-searches rotation and flips by overlap) |
| napari | 1. Load datasets, then 2. Pre-register (sliders, live overlap score) |
| Outputs | The source table with its coordinate columns overwritten in the reference frame, and <output>.transform.json (3 × 3 matrix plus its decomposition) |
| Typical runtime | Interactive |
Pick landmarks¶
| Inputs | The pre-registered SM table (sm_pre.csv) and the ST table; pass sm_kwargs={"scale_xy": 1.0, "keep_positive": False} because it is already in the ST frame |
| Python | prepare_landmark_inputs(st_file, sm_file, output_prefix) rasterises both and writes .npz files for STalign's point_annotator.py |
| napari | 3. Landmarks: place points on both layers, then save |
| Outputs | <prefix>_st_points.npy, <prefix>_sm_points.npy: the same features, in the same order, in both |
| Typical runtime | Manual; Sample 2 used 11 landmark pairs |
Register with LDDMM¶
| Inputs | ST CSV, the pre-registered SM CSV, the two landmark files, and the same sm_kwargs as for the landmarks |
| Python | smint.alignment.register_sm_to_st(st_file, sm_file, st_points_file, sm_points_file, orientation="stalign", ...) |
| Command line | smint-alignment st-sm --st ... --sm ... --st-points ... --sm-points ... --output-dir ... --orientation stalign (add --submit for SLURM) |
| napari | 4. Register widget, round st_sm |
| Outputs | sm_transformed.csv: every SM pixel with all m/z columns plus x_transformed, y_transformed; status.json |
| Typical runtime | Sample 2, section Z2 with MALDI section B: 459,969 SM pixels onto 53,622 ST cells with 11 landmarks, about 72 s on one A30 |
from smint.alignment import register_sm_to_st
df = register_sm_to_st(
st_file="st_cells.csv", sm_file="sm_pre.csv",
st_points_file="z2_st_points.npy", sm_points_file="z2_sm_points.npy",
output_path="sm_registered.csv",
sm_kwargs={"scale_xy": 1.0, "keep_positive": False}, # already pre-registered
orientation="stalign", # x, y in the ST frame
)
STalign needs its own environment
STalign requires numpy < 2. SMINT runs the LDDMM step in a separate worker
interpreter (smint-register, launched for you by the CLI and napari), so
the rest of SMINT can stay on numpy 2. See Installation.
How-to: Alignment · napari plugin · tutorial Sequential-section registration · reference Data formats.
3 · Integrate in R¶
After registration every table shares the ST coordinate frame, so joining modalities is a spatial join. The repository ships R templates for the two joins; edit the input paths at the top of each.
| Script | What it does |
|---|---|
assignment/transcript_assignment.R |
Joins Xenium transcripts (x_location, y_location, filtered to qv >= 20) to aligned cell polygons with sf, and builds a SpatialExperiment of counts per cell |
integration/metabolomics_integration.R |
Takes the registered SM pixels, averages every metabolite over the pixels within 20 µm of each ST cell centroid, and combines the result with the ST data in one SpatialExperiment |
stitching/stitching.Rmd |
The original R implementation of the stitching that SMINT ports to Python |
From Python you can launch them with smint.r_integration.run_r_script.
How-to: R integration · tutorial
One table per cell (the metabolite join in
Python, on Sample 2).
Next steps¶
- Decide your branch: Choosing a registration method.
- Check your units before registering: Coordinate frames.
- Tune segmentation tiles: Tiling and stitching.
- Run it: Tutorials.