3D Pipeline Tutorial#

This tutorial demonstrates how to use InterSCellar for 3D spatial omics analysis. The 3D pipeline has three steps:

  1. Neighbor Graph: detect neighboring cells and build a cell neighbor graph.

  2. Volume: compute the interscellar volume between each pair of neighboring cells, using either the absolute or the adaptive pathway.

  3. Score: quantify biomarker expression inside each interscellar volume.

Step 1: Neighbor Graph#

First, detect cell neighbors in 3D space:

import interscellar

neighbors_3d, adata, conn = interscellar.find_cell_neighbors_3d(
    ome_zarr_path="data/segmentation.zarr",
    metadata_csv_path="data/cell_metadata.csv",
    max_distance_um=0.5,
    voxel_size_um=(0.56, 0.28, 0.28),
    db_path="results/sample_neighbor_graph.db",
    output_csv="results/sample_neighbors_3d.csv",
    n_jobs=8
)

Parameters#

  • ome_zarr_path: Path to OME-Zarr file containing 3D segmentation

  • metadata_csv_path: Path to CSV file with cell metadata

  • max_distance_um: Maximum surface-to-surface distance in micrometers for neighbor detection

  • voxel_size_um: Tuple of (z, y, x) voxel sizes in micrometers

  • db_path: Output neighbor graph database

  • output_csv: Output CSV of neighbor pairs

  • n_jobs: Number of parallel jobs

Output#

The function returns:

  • neighbors_3d: DataFrame with neighbor pairs

  • adata: AnnData object (if available) with graph information

  • conn: Database connection object

Step 2: Volume#

After detecting neighbors, compute the interscellar volumes. There are two pathways, which differ in how far each volume reaches into its two cells:

  • (a) Absolute: dilates inwards from the cell surface by a fixed, user-defined distance.

  • (b) Adaptive: dilates inwards from the cell surface by a depth ratio relative to the cell's maximum reach.

(a) Absolute#

volumes_3d, adata, conn = interscellar.compute_interscellar_volumes_3d(
    ome_zarr_path="data/segmentation.zarr",
    neighbor_pairs_csv="results/sample_neighbors_3d.csv",
    neighbor_db_path="results/sample_neighbor_graph.db",
    voxel_size_um=(0.56, 0.28, 0.28),
    max_distance_um=3.0,
    intracellular_threshold_um=1.0,
    n_jobs=8
)

Parameters

  • ome_zarr_path: Path to OME-Zarr file containing 3D segmentation

  • neighbor_pairs_csv: Path to CSV file with neighbor pairs from step 1

  • neighbor_db_path: Path to database file with neighbor pairs from step 1

  • voxel_size_um: Tuple of (z, y, x) voxel sizes in micrometers

  • max_distance_um: Maximum distance for volume computation

  • intracellular_threshold_um: Fixed depth the volume reaches into each cell

  • n_jobs: Number of parallel jobs

Output

The function returns:

  • volumes_3d: DataFrame with interscellar volume measurements

  • adata: AnnData object with volume information

  • conn: Database connection object

Outputs are written next to the neighbor pairs CSV as sample_absolute_volumes.csv, sample_absolute_interscellar_volumes.zarr and sample_absolute_cell_only_volumes.zarr.

(b) Adaptive#

volumes_3d = interscellar.compute_interscellar_volumes_3d_adaptive(
    ome_zarr_path="data/segmentation.zarr",
    neighbor_pairs_csv="results/sample_neighbors_3d.csv",
    voxel_size_um=(0.56, 0.28, 0.28),
    max_distance_um=3.0,
    rho_threshold=0.5,
    n_jobs=8
)

Parameters

  • ome_zarr_path: Path to OME-Zarr file containing 3D segmentation

  • neighbor_pairs_csv: Path to CSV file with neighbor pairs from step 1

  • voxel_size_um: Tuple of (z, y, x) voxel sizes in micrometers

  • max_distance_um: Maximum distance for volume computation

  • rho_threshold: Depth ratio, relative to each cell's maximum reach, that the volume extends into the cell

  • n_jobs: Number of parallel jobs

Output

The function returns:

  • volumes_3d: DataFrame with interscellar volume measurements

Outputs are written next to the neighbor pairs CSV as sample_adaptive_volumes.csv and sample_adaptive_interscellar_volumes.zarr. Pairs with no extracellular corridor connecting the two cells are rejected and listed in sample_adaptive_rejected_pairs.csv.

Cell-only volumes#

After computing interscellar volumes with either pathway, you can compute cell-only volumes by subtracting the interscellar volumes from the original segmentation. Every voxel that lies in any interscellar volume is removed from its cell, so a voxel shared by overlapping adaptive volumes is removed once:

cellonly_3d = interscellar.compute_cell_only_volumes_3d(
    ome_zarr_path="data/segmentation.zarr",
    interscellar_volumes_zarr="results/sample_adaptive_interscellar_volumes.zarr",
    n_jobs=8
)

Parameters

  • ome_zarr_path: Path to original OME-Zarr file containing 3D segmentation

  • interscellar_volumes_zarr: Interscellar volumes zarr from either pathway of step 2

  • nuclei_segmentation_zarr (optional): Nuclei segmentation zarr on the same grid; every voxel inside a nucleus is also removed

  • n_jobs: Number of parallel jobs

The output zarr is saved next to the interscellar volumes zarr, with _interscellar_volumes replaced by _cell_only_volumes (e.g. sample_adaptive_cell_only_volumes.zarr).

Output

The function returns:

  • cellonly_3d: DataFrame with one row per cell: cell_id, cell_only_volume_voxels, cell_only_volume_um3 and cell_type

The same computation is available from the command line:

compute-cell-only-volumes-3d \
  --cell-segmentation-zarr "data/segmentation.zarr" \
  --interscellar-zarr "results/sample_adaptive_interscellar_volumes.zarr" \
  --n-jobs 8

Step 3: Score#

Finally, quantify biomarker expression inside each interscellar volume.

Points (transcriptomics, punctate proteomics)#

The centrality score counts each spot inside a volume, weighted by its distance from the pair's contact interface:

scores_3d = interscellar.calculate_interscellar_scores_3d(
    interscellar_volumes_zarr="results/sample_adaptive_interscellar_volumes.zarr",
    spot_zarrs={"GZMB": "data/GZMB_spots.zarr"},
    n_jobs=8
)

Parameters

  • interscellar_volumes_zarr: Interscellar volumes zarr from the adaptive pathway of step 2

  • spot_zarrs: Mapping of biomarker name to spot mask zarr on the same voxel grid

  • n_jobs: Number of parallel jobs

Output

The function returns:

  • scores_3d: DataFrame with one row per pair and biomarker, also written as sample_adaptive_scores.csv

Immunofluorescence (metabolomics, intensity proteomics)#

Intensity statistics per volume are computed from a raw expression image:

python -m interscellar.core.calculate_interscellar_scores_3d_intensity \
  --segmentation-zarr "results/sample_adaptive_interscellar_volumes.zarr" \
  --raw-expression-zarr "data/raw_expression.zarr" \
  --n-jobs 8

Parameters

  • --segmentation-zarr: Interscellar volumes zarr from either pathway of step 2, or a cell-only volumes zarr

  • --raw-expression-zarr: Raw expression OME-Zarr on the same voxel grid

  • --channels / --channel-names (optional): Channels to measure and names for them

  • --object-id-column (optional): ID column name; use cell_id for cell-only volumes

  • --n-jobs: Number of parallel jobs

Output

A CSV with one row per volume and, for each channel, the sum, mean, std, min, max and nonzero_fraction of the raw intensities inside it. Statistics include every voxel of the volume, zero-intensity voxels too. Adaptive volumes are read from their lossless per-pair archive, so overlapping pairs each keep their shared voxels. By default the CSV is written next to the input as sample_adaptive_interscellar_volumes_features_3d.csv.