protein-spillover is an end-to-end workflow for measuring, diagnosing, and correcting cell-segmentation-associated protein spillover in Xenium and other multiplex protein images stored in SpatialData.
The workflow does more than report one corrected intensity. For every cell and protein, it preserves the original measurement, extracts spatial evidence for possible spillover, evaluates multiple correction scenarios, estimates overcorrection risk, and recommends a corrected value with structured and human-readable reasons.
The default correction path is annotation-free. Cell-type labels are optional and are never required.
The CLI performs the complete pipeline in one program:
- loads the SpatialData table, protein image, label raster, and optional QC mask;
- validates the authoritative table-to-raster label mapping;
- crops the selected ROI on the verified native image grid;
- preprocesses image intensities correctly for generic, precorrected, or Xenium XOA inputs;
- extracts whole-cell, interior, boundary, outer-ring, and neighboring-cell protein features;
- builds a direct cell-contact graph;
- calculates segmentation geometry, local density, and dense-small-cell protection scores;
- estimates marker-specific neighbor exposure and likely spillover sources;
- evaluates multiple correction scenarios;
- recommends a scenario or marks the result unresolved;
- saves confidence, overcorrection risk, reason codes, and readable explanations;
- writes corrected features into an ROI AnnData object and produces QC reports.
The input Zarr remains read-only. Results are written only to the configured output directory.
The workflow is appropriate when all of the following are true:
- The table has one row per segmented biological cell.
- A positive integer table column directly identifies the same cell in the label raster.
- The protein image and label raster share the same full-resolution native
y × xgrid. - Table spatial coordinates can be related to native image pixels using an axis-aligned transformation consisting of:
- one pixel-size scale;
- optional x and/or y reflection;
- optional global x and y origins.
- The original image values should remain available for auditing.
- The workflow should not write back into the authoritative SpatialData Zarr.
The CLI does not infer a label crosswalk from centroid matching or polygon overlap. The table label column is treated as authoritative and checked against the cropped label raster.
protein_spillover_cli.py Main end-to-end CLI and analysis workflow
protein_spillover_config.json Complete configuration template
protein_spillover_README.md This guide
requirements.txt Dependency list
pyproject.toml Editable installation and console entry point
Use Python 3.10 or newer in an isolated environment containing the SpatialData stack required by the project.
From the distribution directory:
python -m pip install -e .This installs:
protein-spilloverpython protein_spillover_cli.py --helpprotein-spillover --help
protein-spillover run --helpUse inspect before building a production configuration.
protein-spillover inspect \
--zarr /data/project/spatial_data.zarrA more detailed inspection:
protein-spillover inspect \
--zarr /data/project/spatial_data.zarr \
--table cell_table \
--image morphology_focus \
--labels cell_labels \
--roi-col roiUse this output to identify:
- the table name;
- protein image and label element names;
- the stable cell-ID column;
- the authoritative integer raster-label column;
- the
obsmkey containing x/y coordinates; - available protein channels;
- optional ROI, sample, batch, and annotation columns.
Generate a complete template:
protein-spillover template-config \
--output protein_spillover_config.jsonRun with the configuration:
protein-spillover run \
--config protein_spillover_config.jsonCLI values override JSON values:
protein-spillover run \
--config protein_spillover_config.json \
--roi-value ROI_002 \
--outdir /results/protein_spillover/ROI_002protein-spillover run \
--zarr /data/project/spatial_data.zarr \
--table cell_table \
--image morphology_focus \
--labels cell_labels \
--outdir /results/protein_spillover \
--cell-id-col cell_id \
--table-label-col cell_labels \
--spatial-key spatial \
--pixel-size-um 0.2125 \
--orientation no_flip \
--roi-col roi \
--roi-value ROI_001 \
--intensity-mode xenium_xoa \
--channel CD3E \
--channel CD8ATo analyze the full table rather than one ROI:
protein-spillover run \
--config protein_spillover_config.json \
--all-cellsWhen roi_col is configured but no ROI value is supplied, the workflow chooses the ROI whose cell count is closest to the median ROI cell count. This is intended for pilot testing, not for automatic all-ROI processing.
| JSON key | CLI flag | Meaning |
|---|---|---|
sdata_zarr_path |
--zarr |
Input SpatialData Zarr |
table_name |
--table |
AnnData table stored in SpatialData |
protein_image_name |
--image |
Multichannel protein image |
cell_labels_name |
--labels |
Positive-integer cell-label raster |
outdir |
--outdir |
Output directory |
cell_id_col |
--cell-id-col |
Stable cell identifier in table.obs |
table_cell_label_col |
--table-label-col |
Authoritative raster label in table.obs |
spatial_key |
--spatial-key |
table.obsm key containing x/y coordinates |
native_pixel_size_um |
--pixel-size-um |
Native pixel size in table coordinate units |
Table label values must be unique positive integers within the selected ROI.
The workflow uses an explicit axis-aligned native-pixel mapping rather than trusting stored transformations.
| Value | Meaning |
|---|---|
no_flip |
x and y increase with native image indices |
x_flip |
x is reflected |
y_flip |
y is reflected |
xy_flip |
both axes are reflected |
Arbitrary rotation, shear, and nonlinear warping are not supported. Such images should be registered before this workflow runs.
For no_flip:
x_table = native_origin_x_um + x_native × native_pixel_size_um
y_table = native_origin_y_um + y_native × native_pixel_size_um
Relevant options:
--pixel-size-um
--origin-x-um
--origin-y-um
--orientation
Before a production run, verify registration on several spatially separated cells.
{
"roi_col": "roi",
"pilot_roi": "ROI_001"
}Set roi_col and leave pilot_roi as null.
{
"roi_col": null,
"pilot_roi": null
}or pass --all-cells.
Select individual channels:
--channel CD3E --channel CD8A --channel CD20Use all channels except exclusions:
"analysis_channels": null,
"exclude_channels": ["DAPI"]Repeated CLI channel options replace the JSON list.
The workflow supports three modes.
Use this for Xenium Onboard Analysis protein images.
XOA protein images are already deconvolved, autofluorescence-background-subtracted, saturation-masked, and spectrally corrected. XOA normally stores these values with a positive offset of 100.
The workflow:
- subtracts the configured XOA storage offset;
- retains signed offset-adjusted values for intensity summaries;
- creates a clipped nonnegative signal representation for thresholds and ratios;
- does not apply a second Gaussian background subtraction.
{
"input_intensity_mode": "xenium_xoa",
"xenium_xoa_intensity_offset": 100.0
}Use supplied values directly as signed analysis intensities.
Estimate and subtract a broad Gaussian background for generic multiplex images that have not already been background corrected.
A channel-matched QC-mask image may be supplied.
For Xenium XOA saturation masks, valid pixels are usually 0 and masked pixels are 255.
{
"protein_qc_mask_name": "morphology_focus_qc_masks",
"qc_mask_valid_value": 0,
"xenium_require_qc_mask": false
}Invalid pixels are excluded from intensity summaries, thresholds, directionality metrics, and QC distributions.
For every segmented cell, the workflow derives:
- whole cell;
- eroded interior;
- internal boundary;
- outer ring;
- outer noncell ring;
- outer other-cell ring.
Configure with:
--inner-erosion 2
--outer-ring 2These values are full-resolution native pixels.
These thresholds are used for image-localization and directionality evidence. They are not biological protein gates.
Default quantile:
--threshold-quantile 0.90Channel-specific quantiles:
--channel-threshold-quantile CD3E=0.85 \
--channel-threshold-quantile PD1=0.95Manual image thresholds:
--manual-threshold CD3E=120Manual thresholds take precedence.
Cell-type labels are optional.
The default is:
"annotation_mode": "disabled"Supported modes:
| Mode | Behavior |
|---|---|
disabled |
Labels do not affect correction or recommendation |
reporting_only |
Labels are copied to outputs only |
validation_only |
Labels are used only in QC summaries |
weak_prior |
Labels may weakly influence recommendation scoring |
The annotation-free result remains the primary technical correction path. Labels should never force a marker to zero or define a correction by themselves.
The workflow computes per-cell and local-neighborhood features including:
- segmentation area;
- perimeter and shared-boundary exposure;
- number of direct contacts;
- total and maximum shared-boundary edge counts;
- local neighbor density;
- small-cell rank;
- dense-neighborhood rank;
- dense-small-cell geometry score.
This score is deliberately geometric. It does not claim that a cell is a lymphocyte.
Dense-small-cell protection reduces correction aggressiveness when a cell is small, crowded, and surrounded by several similarly exposed neighbors. This is intended to limit density-dependent overcorrection in lymphocyte-rich regions.
Main settings:
{
"dense_small_cell_area_quantile": 0.35,
"dense_neighbor_count_quantile": 0.70,
"dense_shared_boundary_quantile": 0.70,
"dense_protection_strength": 0.65
}For each cell-protein pair, the workflow evaluates directly contacting cells using:
- shared-boundary evidence;
- neighbor intensity;
- neighbor-to-focal contrast;
- dominant-neighbor contribution;
- top-neighbor contributions;
- boundary-versus-interior signal localization;
- anisotropy and angular boundary coverage;
- dense-small-cell protection.
The workflow distinguishes:
- one dominant bright source;
- several strong sources;
- many weak neighbors;
- dense homogeneous neighborhoods with no clear source.
This reusable evidence is calculated once and reused by all correction scenarios.
The workflow supports a configurable scenario engine. At minimum, the required anchors are none, conservative, medium, and strong.
Default scenarios:
| Scenario | Purpose |
|---|---|
none |
Preserve the original value when spillover evidence is weak |
conservative |
Protect dense small-cell regions and limit removed signal |
medium |
Standard correction for moderate directional evidence |
strong |
Aggressive correction for compelling, high-confidence spillover |
dominant_neighbor |
Use only the strongest plausible source |
top_neighbors |
Use only the strongest configured number of sources |
high_specificity |
Correct only when evidence exceeds a stringent threshold |
Scenario behavior is controlled by:
"scenario_shrinkage": {
"none": 0.0,
"conservative": 0.30,
"medium": 0.60,
"strong": 0.90,
"dominant_neighbor": 0.70,
"top_neighbors": 0.65,
"high_specificity": 0.75
}and by maximum removable fractions:
"scenario_max_fraction_removed": {
"none": 0.0,
"conservative": 0.25,
"medium": 0.50,
"strong": 0.80,
"dominant_neighbor": 0.55,
"top_neighbors": 0.60,
"high_specificity": 0.65
}These are sensitivity scenarios, not claims that one universal correction strength is correct for every cell.
For each cell-protein pair, the workflow selects one applicable scenario or returns unresolved.
The recommendation considers:
- evidence that spillover exists;
- source-attribution confidence;
- segmentation confidence;
- boundary localization;
- neighbor-to-focal contrast;
- dense-small-cell protection;
- proposed fraction removed;
- disagreement between scenarios;
- recommendation margin between the best and second-best scenarios.
The output includes:
- suggested scenario;
- suggested signed corrected intensity;
- suggested nonnegative corrected intensity;
- estimated contamination removed;
- second-best scenario;
- selection margin;
- recommendation confidence;
- overcorrection risk;
- structured reason codes;
- readable reason text.
The suggested value remains auditable because all alternative scenarios are retained.
Confidence is intentionally decomposed.
| Field | Meaning |
|---|---|
| segmentation confidence | Reliability of the focal cell mask and derived geometry |
| localization confidence | Strength of boundary-versus-interior evidence |
| source-attribution confidence | Ability to assign spillover to one or more neighbors |
| bleeding confidence | Evidence that meaningful spillover is present |
| recommendation confidence | Confidence in selecting one correction scenario |
| overcorrection risk | Risk that subtraction removes genuine focal-cell signal |
These values do not represent biological protein-positivity confidence. Biological gating belongs in the downstream gating and phenotyping workflow.
Per-neighbor output may become large.
{
"save_neighbor_contributions": "top",
"max_saved_neighbors_per_cell_protein": 5
}Modes:
| Value | Behavior |
|---|---|
none |
Save aggregate exposure only |
top |
Save the strongest contributors per cell-protein pair |
all |
Save every eligible neighbor contribution |
Use all cautiously on large datasets.
The workflow contains fourteen ordered stages:
01_roi_selection
02_cropped_arrays
03_corrected_image
04_thresholds
05_spillover_features
06_merged_features
07_contact_graph
08_geometry_density
09_neighbor_exposure
10_correction_scenarios
11_recommendations
12_roi_h5ad
13_qc_plots
14_summary
Resume is enabled by default. Each checkpoint includes a configuration signature and upstream signature.
Changing correction parameters invalidates only the correction and downstream stages. It does not force image cropping or pixel-feature extraction to rerun.
Disable resume:
--no-resumeForce one stage and every downstream stage:
--force-stage 10_correction_scenariosLarge array checkpoints:
--save-array-checkpoints
--memory-map-arrays
--cleanup-arraysFor ROI <ROI>, outputs are written beneath:
<outdir>/<ROI>/
Important outputs include:
| Output | Description |
|---|---|
spillover_features_<ROI>.parquet |
Original per-cell protein and spatial features joined to metadata |
cell_contact_pairs_<ROI>.parquet |
Direct contact graph with shared-boundary evidence |
geometry_density_<ROI>.parquet |
Cell geometry, contact density, and dense-small-cell scores |
neighbor_exposure_<ROI>.parquet |
Cell-protein spillover evidence and source attribution |
neighbor_contributions_<ROI>.parquet |
Optional retained source-neighbor contributions |
correction_scenarios_<ROI>.parquet |
All scenario-level corrected values and contamination estimates |
suggested_corrections_<ROI>.parquet |
Recommended scenarios, confidence, risks, and reasons |
correction_features_wide_<ROI>.parquet |
Per-cell wide correction columns for AnnData integration |
channel_thresholds_<ROI>.csv |
Exploratory image thresholds by marker |
cell_id_to_raster_label_<ROI>.parquet |
Table-label validation and centroid diagnostics |
raster_mapping_summary_<ROI>.json |
Mapping coverage and validation summary |
roi_with_spillover_features_<ROI>.h5ad |
ROI AnnData with original features and correction outputs in .obs |
spillover_summary_<ROI>.json |
Final run summary |
RUN_COMPLETE.json |
Successful completion marker |
qc_plots/ |
Intensity and feature QC |
correction_qc/ |
Correction magnitude, density, and recommendation QC |
checkpoints/ |
Stage markers and cached artifacts |
If Parquet support is unavailable, tables fall back to compressed CSV files ending in .csv.gz.
Original image-derived features use names such as:
protein_<channel>_raw_whole_mean
protein_<channel>_whole_mean
protein_<channel>_inner_mean
protein_<channel>_boundary_mean
protein_<channel>_outer_othercell_mean
protein_<channel>_boundary_to_inner_log2
protein_<channel>_boundary_angular_coverage
protein_<channel>_boundary_anisotropy
Correction features written to AnnData include scenario-specific and suggested outputs using safe marker and scenario names.
Do not discard the original columns after correction.
A well-behaved correction should generally show:
- limited correction when no brighter neighbor is present;
- stronger correction when one bright neighbor dominates and boundary evidence is directional;
- reduced aggressiveness in dense small-cell regions;
- no severe downward trend in corrected lineage-marker intensity with local density;
- preservation of isolated high-intensity cells;
- a manageable unresolved fraction;
- few extreme negative signed corrected values.
Warnings include:
- corrected intensity decreasing sharply as local density increases;
- strong corrections driven by many weak neighbors;
- large fractions removed without a dominant source;
- widespread strong correction in dense immune aggregates;
- scenario disagreement across a large fraction of cells;
- low source-attribution confidence with high recommended correction.
Check pixel size, orientation, origins, ROI coordinates, table-label column, crop margin, and whether the selected image and labels belong to the same acquisition.
Do not lower minimum_raster_mapping_fraction merely to bypass registration problems.
Review:
- dense-small-cell score;
- neighbor count;
- shared-boundary fraction;
- dominant-source fraction;
- correction scenario selected;
- fraction removed;
- correction versus density QC plots.
Consider increasing dense_protection_strength, lowering conservative shrinkage, or reducing the conservative maximum removable fraction. Re-run from 08_geometry_density or 10_correction_scenarios, depending on which settings changed.
Increase strong_neighbor_focal_contrast, increase recommendation_minimum_confidence, or increase recommendation_minimum_margin.
Inspect whether scenario scores are genuinely close. Lowering the minimum margin may reduce unresolved calls but can create false precision.
Use save_neighbor_contributions: top or none and reduce max_saved_neighbors_per_cell_protein.
Signed corrected values are retained intentionally. Use the separate nonnegative corrected output when a downstream method requires nonnegative values. Do not silently overwrite signed values.
Analyze one ROI at a time, reduce channels, retain memory mapping, use top-neighbor output, or increase QC downsampling. Scenario evaluation itself should be relatively inexpensive because it reuses cached exposure evidence.
- Run
inspectand record exact element, table, column, and channel names. - Verify the authoritative table label against the raster.
- Confirm image-label native-grid equality.
- Verify pixel size, orientation, and origins on several cells.
- Start with one representative ROI and a small channel set.
- Review raster-mapping and intensity QC.
- Review whole-cell, interior, boundary, and outer-other-cell features.
- Review geometry-density and neighbor-exposure outputs.
- Compare all correction scenarios before trusting the suggested value.
- Specifically examine dense lymphocyte-rich regions for overcorrection.
- Only then expand to all channels and ROIs.
- Pass original, alternative, and suggested corrected values to the downstream gating and phenotyping CLI.
protein-spillover run --helpGenerate the exact configuration schema:
protein-spillover template-config \
--output full_protein_spillover_config.json