--- name: bio-imaging-mass-cytometry-quality-metrics description: Quality control for IMC/MIBI data across pixel, channel, image, slide, and batch levels, covering Poisson-count SNR (cell-level Gaussian-mixture and empty-channel comparison), spillover-matrix QC (the three physical sources), drift and the missing EQ-bead analog, acquisition artifacts, and sample-of-origin batch effects. Use when deciding whether to keep or drop a channel, ROI, or slide, distinguishing a dim antibody from a failed one, reading a spillover matrix, or diagnosing batch-driven clustering before analysis. tool_type: mixed primary_tool: CATALYST --- ## Version Compatibility Reference examples tested with: numpy 1.26+, scikit-learn 1.4+, scanpy 1.10+, CATALYST 1.28+ (R), spillR 1.0+ (R) Before using code patterns, verify installed versions match. If versions differ: - Python: `pip show ` then `help(module.function)` to check signatures - R: `packageVersion('')` then `?function_name` to verify parameters If code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying. Notes specific to this skill: IMC values are integer ion (dual) counts, so SNR must respect Poisson statistics, not fluorescence intuition; biology lives in 1-2 count differences. There is no EQ-bead in-line drift normalizer for ablated tissue. CATALYST `normCytof` is for SUSPENSION bead normalization, not IMC images -- do not apply it to image data. Compensate raw pixels before transformation. # IMC Quality Metrics **"Assess the quality of my IMC acquisition"** -> Gate the data at the level each failure lives at -- pixel, channel, image, slide, batch -- before analysis, not by normalizing after. - Python: `numpy`/`scikit-learn` for SNR, artifacts, batch diagnosis - R: `CATALYST::plotSpillmat`, `spillR` for spillover QC ## The Single Most Important Modern Insight -- QC is multi-level, and every metric is blind at some level IMC/MIBI QC is not one number. The data live in a Poisson ion-count regime where "noise" has a defined statistical meaning, and the failures that actually destroy an experiment -- a dead antibody, an unbalanced batch, cells that cluster by which slide they came from rather than by phenotype -- are panel/staining/batch problems that are invisible to per-image SNR. So a single metric is always blind at some level, and the discipline is to gate (drop a channel, ROI, or slide) before analysis rather than normalize after, because correction moves a distribution but never creates the positive/negative separation that staining never produced. Three corollaries a postdoc internalizes. (1) Counts are Poisson: a real floor-abundance epitope genuinely yields a few counts, so "2 counts" is signal or noise depending on dwell, area, and the aggregation level -- SNR grows as sqrt(N) when pixels are pooled into a cell. (2) Dim is not failed: a correctly-titrated antibody to a sparse antigen is supposed to be dim; failure is INSEPARABILITY of positive and negative populations, judged against a known-empty channel, not low absolute intensity. (3) IMC has no EQ-bead drift normalizer -- ablated fixed tissue cannot be spiked with calibration beads, so the only honest cross-batch yardstick is an anchor reference sample included in every run (Casanova 2025), and the absence of an in-line standard is itself expert knowledge. ## Multi-Level QC Framework | Level | What to measure | Characteristic failure | Blind to | |-------|-----------------|------------------------|----------| | Pixel | hot pixels, shot noise, dynamic range | detector spikes; Poisson noise on dim signal | whether the channel is biologically real | | Channel (marker) | cell-level SNR, spillover in/out, vs empty channel | dead antibody, crosstalk, oxide/+-1 leak | spatial artifacts, batch | | Image / ROI | mean intensity, cell coverage, ablation completeness | failed ablation, folding, off-target ROI | cross-sample comparability | | Slide / acquisition | detector drift over time, tune (Lu duals) | within-run sensitivity decay, mis-tune | between-slide offset | | Batch / cohort | sample-of-origin clustering, lot effects | the cohort clusters by batch not biology | nothing -- the top level | ## Decision Tree by Scenario | Observation | Decision | Basis | |-------------|----------|-------| | Cell-level positive/negative mixture won't separate; signal ~ empty/80ArAr channel | DROP (failed antibody) | inseparability, not intensity | | Low absolute counts but clean separation, pattern matches biology + control tissue | KEEP (dim-but-real); use at aggregated levels | dim != failed | | High signal, low SNR (everything "positive") | DROP or re-titrate | non-specific binding | | Heavy +16 oxide or impurity from a co-expressed partner | DROP or re-mass the panel | unrescuable by compensation | | Striping / incomplete-ablation banding | DROP the ROI | physical failure, not correctable noise | | DNA/Ir dropout over a region | MASK the region, keep the rest | non-ablation/tissue loss | | Tune fails (Lu duals below panel criterion) | RE-TUNE / re-acquire | instrument not in spec | | Cells cluster by slide/patient not phenotype | batch-correct; if it won't mix, the contrast is confounded | sample-of-origin effect | ## Cell-Level SNR (the decision-relevant number) **Goal:** Judge marker adequacy at the unit of analysis (the cell), in a count-aware way. **Approach:** Fit a two-component Gaussian mixture to per-cell mean counts (on non-transformed counts) and take mean(positive)/mean(negative); a failed antibody is one whose components do not separate, regardless of brightness. Compare the distribution to a known-empty channel as the operational "did this antibody work" test. ```python import numpy as np from sklearn.mixture import GaussianMixture def cell_snr(per_cell_counts): # two-component mixture on raw per-cell means: separation, not brightness, defines success gm = GaussianMixture(n_components=2, random_state=0).fit(per_cell_counts.reshape(-1, 1)) pos, neg = np.sort(gm.means_.ravel())[::-1] return pos / neg if neg > 0 else np.inf def matches_empty(channel_counts, empty_channel_counts, q=95, tol=2.0): # compare the POSITIVE tail (q-th percentile), not the median: a real-but-sparse marker # carries its signal in the tail while a failed channel's tail sits at the empty floor. # True -> indistinguishable from 80ArAr / an unconjugated lanthanide -> the honest "failed" test return np.percentile(channel_counts, q) - np.percentile(empty_channel_counts, q) <= tol ``` ## Spillover Matrix QC **Goal:** Decide whether a panel's crosstalk is acceptable before compensating. **Approach:** Generate the matrix from single-stain controls and read whole rows, not just neighbors -- spillover has three physically distinct sources with different mass signatures and different fixes. Acceptability is co-expression-dependent: the same percentage is fine between unrelated markers and fatal between co-expressed ones. ```r library(CATALYST) sce <- readSCEfromTXT('spillover/') # single-metal-spotted slides; filenames carry the metal sce <- prepData(sce, transform = TRUE, cofactor = 5) sce <- assignPrelim(sce); sce <- applyCutoffs(estCutoffs(sce)) sm <- computeSpillmat(sce) plotSpillmat(sce, sm) # inspect M+-1 (abundance), M+16 (oxide), and # any bright off-diagonal at a NON-adjacent mass (impurity) ``` ## Batch / Sample-of-Origin QC **Goal:** Catch the dominant real-world failure -- cells clustering by slide/patient rather than phenotype -- which no per-image metric reports. **Approach:** Embed cells and color by patient, slide, day, and antibody lot; if cells separate by sample, there is a batch problem. Diagnose before correcting, and correct at the batch layer with an anchor reference sample. ```python import scanpy as sc sc.pp.pca(adata); sc.pp.neighbors(adata); sc.tl.umap(adata) sc.pl.umap(adata, color=['patient', 'slide', 'acquisition_day', 'antibody_lot']) # separation by these = batch, not biology; a dead/unbalanced channel cannot be normalized into life ``` ## Per-Source Failure Modes ### "2 counts is noise" **Trigger:** discarding low-count channels by fluorescence intuition. **Mechanism:** at 1 um^2/~1 ms dwell a floor-abundance epitope yields a few counts; biology lives in 1-2 count differences. **Symptom:** real dim markers dropped. **Fix:** judge adequacy at the aggregation level analyzed; SNR scales sqrt(N) with pooled pixels; mean expression > ~7 is effectively noise-immune. ### Pixel correlation read as spillover **Trigger:** flagging high pixel-channel Pearson correlation as spillover. **Mechanism:** co-expressed real markers correlate too; spillover is a directional, mass-structured leak. **Symptom:** false spillover calls, missed real ones. **Fix:** read the single-stain spillover matrix; diagnose by mass signature (+-1, +16, named impurity mass), not correlation. ### Compensating a saturated or co-expressed channel **Trigger:** trusting compensation on very bright donors or co-expressed pairs. **Mechanism:** the matrix is linear only in the linear range and cannot separate real co-expression from leak. **Symptom:** over/under-shoot; subtracted real biology. **Fix:** keep total per-pair spillover low by panel design; use NNLS (CATALYST) or flag-and-replace (spillR); the real fix is upstream mass assignment. ### Per-image QC declared sufficient **Trigger:** passing per-image SNR and skipping cross-sample QC. **Mechanism:** FFPE/ischemia/lot variation shifts baselines by batch. **Symptom:** unsupervised analysis groups by sample-of-origin. **Fix:** UMAP/ridgeline by patient/slide/day/lot; include anchor samples; correct at the batch layer. ## Quantitative Thresholds | Threshold | Source | Rationale | |-----------|--------|-----------| | Mean expression > ~7 ~ noise-immune | Lu 2023 *Nat Commun* 14:1601 | below it shot noise perturbs per-cell values | | Abundance sensitivity (M+-1) < 0.3% (Tb) | Han 2018 *Nat Protoc* 13:2121 | TOF peak-tail spec; tuning target, not guarantee | | Oxide (M+16) < 3% (La) | Han 2018 *Nat Protoc* 13:2121 | plasma-oxide spec; worst for abundant structural markers | | Isotopic impurity up to ~4% at a named mass | Han 2018 *Nat Protoc* 13:2121 | not predictable from mass proximity -- read the lot | | Tune ~ Lu >= 1500 dual counts | panel-specific convention | a pass criterion, stated in dual counts (unit matters) | | Pixel foreground (Otsu) signal < 2/image -> flag | steinbock/IMCDataAnalysis | image-level marker filter | Where the field has NO accepted threshold (itself expert knowledge): a universal SNR cutoff for a "good marker"; a single spillover percentage defining an "acceptable panel" (acceptability is co-expression-dependent); an in-line pixel-level drift-normalization standard equivalent to EQ beads; a fixed hot-pixel count threshold (DIMR/KNN are adaptive precisely because a fixed cutoff fails across brightnesses). ## Common Errors | Error / symptom | Cause | Solution | |-----------------|-------|----------| | Dropped a real sparse marker | judged by absolute intensity | drop on inseparability + match-to-empty + wrong spatial pattern | | Spillover "fixed" but double-positives persist | compensated co-expressed/saturated channel | re-mass the panel; NNLS/spillR; compensate raw pixels | | Cohort clusters by patient | unaddressed batch | anchor reference sample; diagnose before correcting | | Threshold "1500" or "2" ambiguous | unit omitted | always state dual counts; thresholds are panel/instrument-specific | | Striped ROI "denoised" | physical ablation failure treated as noise | drop the ROI | | Indium nuclear signal taken as a marker (MIBI) | In localizes to nuclei | treat as artifact unless validated; use 197Au + background masking | ## References - Giesen C, Wang HAO, Schapiro D, et al. 2014. Highly multiplexed imaging of tumor tissues with subcellular resolution by mass cytometry. *Nat Methods* 11(4):417-422. — IMC origin; ~50 copies/um^2 detection floor. - Chevrier S, Crowell HL, Zanotelli VRT, et al. 2018. Compensation of Signal Spillover in Suspension and Imaging Mass Cytometry. *Cell Syst* 6(5):612-620.e5. — spillover sources, single-stain beads, less accurate at high ion load, CATALYST. - Han G, Spitzer MH, Bendall SC, Fantl WJ, Nolan GP. 2018. Metal-isotope-tagged monoclonal antibodies for high-dimensional mass cytometry. *Nat Protoc* 13(10):2121-2148. — M+-1/M+16/impurity specs and panel design. - Finck R, Simonds EF, Jager A, et al. 2013. Normalization of mass cytometry data with bead standards. *Cytometry A* 83A(5):483-494. — EQ four-element bead normalization (suspension). - Ijsselsteijn ME, Somarakis A, Lelieveldt BPF, Hollt T, de Miranda NFCC. 2021. Semi-automated background removal limits data loss and normalizes imaging mass cytometry data. *Cytometry A* 99(12):1187-1197. — sample-of-origin clustering, FFPE/ischemia variation. - Baranski A, Milo I, Greenbaum S, et al. 2021. MAUI: An image processing pipeline for Multiplexed Mass Based Imaging. *PLoS Comput Biol* 17(4):e1008887. — MIBI artifacts, gold/indium, 1-2 count biology. - Lu P, Oetjen KA, Bender DE, et al. 2023. IMC-Denoise: a content aware denoising pipeline to enhance Imaging Mass Cytometry. *Nat Commun* 14:1601. — Poisson noise model, mean>7 noise-immune. - Guazzini M, Reisach AG, Weichwald S, Seiler C. 2024. spillR: spillover compensation in mass cytometry data. *Bioinformatics* 40(6):btae337. — flag-and-replace compensation, preserves correlations. - Windhager J, Zanotelli VRT, Schulz D, et al. 2023. An end-to-end workflow for multiplexed image processing and analysis. *Nat Protoc* 18(11):3565-3613. — image/cell-level QC and SNR conventions. - Casanova C, et al. 2025. Standardization of Suspension and Imaging Mass Cytometry Single-Cell Readouts for Clinical Decision Making. *Cytometry A* 107(6):390-403. — anchor/reference samples for batch-level drift correction. ## Related Skills - data-preprocessing - hot-pixel removal, denoising, and NNLS spillover compensation - cell-segmentation - segmentation QC and the impossible-co-expression monitor - phenotyping - failed channels and batch corrupt cell-type calls - differential-analysis - batch as a covariate when comparing across conditions - flow-cytometry/cytometry-qc - suspension bead normalization and channel QC background