imaging-mass-cytometry/interactive-annotation/SKILL.md
Interactive cell annotation and image QC for IMC/MIBI using napari, napari-imc, Mantis Viewer, and cytomapper, covering the pixels-to-cell-table bridge, overlaying masks to catch segmentation/spillover artifacts, inter-annotator variability as the accuracy ceiling, contrast-as-threshold, and building class-balanced ground-truth label sets. Use when manually labeling cells, generating training data for a classifier, QC-ing segmentation on the image, confirming clusters are spatially real, or choosing an annotation viewer.
npx skillsauth add GPTomics/bioSkills bio-imaging-mass-cytometry-interactive-annotationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference examples tested with: napari 0.4.18+, napari-imc 0.7+, numpy 1.26+, scikit-learn 1.4+
Before using code patterns, verify installed versions match. If versions differ:
pip show <package> then help(module.function) to check signaturesIf 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: core napari cannot open .mcd -- the napari-imc plugin reads raw Fluidigm/Standard BioTools files in machine coordinates. napari add_labels edits integer masks (segmentation QC); add_points with a features table holds per-cell categorical labels. Mantis Viewer is a standalone Electron app from CANDELbio/Parker Institute (NOT the Bodenmiller group). napari has no journal paper -- cite the Zenodo DOI.
"Manually annotate cell types in my IMC data" -> Look at the image with masks overlaid to label cells, generate ground truth, and catch the artifacts a cell table hides.
napari.Viewer with napari-imc (raw .mcd), add_labels/add_pointscytomapper::cytomapperShiny (gate then see the gate on tissue)The IMC pipeline collapses a multi-GB pixel stack into a cell-by-marker table via a segmentation mask and mean-intensity extraction, and that table has thrown away three things only the image still contains: whether the mask boundary matches a real cell, where a marker's signal physically sits (its spillover provenance), and morphology/context. None of these surface as an outlier in the table -- a merged CD3+CD20+ "cell" looks like a plausible rare double-positive, not an error -- so the table is seductive precisely because it is tidy and statistical. Annotation is the only QC step that lets pixels veto the table. The expert habit is distrust of any cell-table claim (a cluster, a double-positive population, a rare type) until it has been seen on the image with its mask overlaid. Three concrete bridge operations follow: overlay the mask on the DNA + summed-membrane channels and walk the image (the irreplaceable segmentation QC); paint clusters back onto tissue, where a real cluster forms coherent structures (a tumor nest, a T-cell zone) and an artifact cluster scatters as salt-and-pepper haze along the boundary between two real clusters (spatial incoherence is the tell); and gate in image space, not only expression space, because a biaxial gate drawn on the table is a thresholding decision made blind to the pixels. Two hard limits frame all of it: manual labels are not ground truth (expert-vs-expert concordance is only ~86%, and a substantial share of disagreements reflect genuinely ambiguous cells, so chasing >90% classifier accuracy is chasing noise above human agreement), and the display contrast limit IS a positivity threshold, so auto-scaling per image silently moves what counts as "positive" field to field.
| Tool | Stack | What it is for |
|------|-------|----------------|
| napari + napari-imc | Python | open raw .mcd, overlay channels + masks + annotation layers, scriptable |
| napari-steinpose | napari plugin | human-in-the-loop Cellpose segmentation inside napari |
| Mantis Viewer | Electron (CANDELbio/Parker) | dedicated ground-truth label + region/population curation on huge images |
| cytomapper / cytomapperShiny | R/Bioconductor | gate on up to ~24 markers and see the gated cells painted on tissue |
| cytoviewer | R/Bioconductor | interactive image + mask overlay colored by cell metadata |
| TissUUmaps 3 | browser/WebGL | whole-slide QC of marker/point overlays at 10^7+ points |
| QuPath | Java | whole-slide pathology; map clusters/cells back to tissue for validation |
| Task | Tool | Why |
|------|------|-----|
| Open and inspect a raw .mcd | napari + napari-imc | only it reads IMC in machine coordinates |
| Generate ground-truth labels for a classifier | Mantis Viewer or napari points | built for population curation across large images |
| Gate a population and confirm it on tissue | cytomapperShiny | the gate-then-see-on-tissue loop |
| Whole-slide point-overlay QC | TissUUmaps | scales to 10^7 points |
| Segmentation mask QC/edit | napari add_labels / napari-steinpose | paint/fill integer masks |
| Confirm a cluster is real | paint cluster back on tissue (any viewer) | spatial incoherence = artifact |
Goal: Put the raw channels, the segmentation mask, and an annotation layer in one canvas.
Approach: Use napari-imc for the .mcd, overlay the mask outlines on DNA + a summed membrane channel, and add a labels or points layer for annotation. Fix the contrast limits explicitly so "positive" means the same thing across fields.
import napari
viewer = napari.Viewer()
viewer.open('slide.mcd', plugin='napari-imc') # reads acquisitions + panoramas
viewer.add_labels(cell_masks, name='masks') # outlines to audit boundaries
# fix contrast (a display limit IS a positivity threshold) -- record it in the protocol
viewer.add_image(dna, name='DNA', contrast_limits=[0, 20], colormap='gray', blending='additive')
napari.run()
Goal: Decide whether a data-driven cluster is real biology or an artifact.
Approach: Color the mask by cluster and look: a real cluster forms coherent spatial structures; an artifact cluster scatters along the boundary between two real clusters.
import numpy as np
def cluster_label_image(masks, cell_ids, cluster_of_cell):
# paint each cell's cluster id back onto its mask; view in napari and demand spatial
# coherence (nests/zones/sheets). Salt-and-pepper haze along a boundary = artifact.
out = np.zeros_like(masks)
lut = dict(zip(cell_ids, cluster_of_cell))
for cid, cl in lut.items():
out[masks == cid] = cl + 1
return out
Goal: Produce a training set that learns rare types and generalizes across batches.
Approach: Tissue is wildly imbalanced (hundreds vs tens-of-thousands of cells per class), so deliberately over-sample rare types when choosing what to label, and spread annotation across multiple patients/compartments -- label breadth beats label depth. Aim for hundreds-to-low-thousands of confident cells per class.
import numpy as np
def sample_cells_to_annotate(cell_ids, cell_types, per_class=300, rng=None):
# over-sample rare classes toward a target count; annotating random fields lets rare
# types stay unlearnably sparse
rng = rng or np.random.default_rng(0)
picks = []
for ct in np.unique(cell_types):
pool = cell_ids[cell_types == ct]
picks.append(rng.choice(pool, size=min(per_class, len(pool)), replace=False))
return np.concatenate(picks)
Trigger: a CD3+CD20+ population from the cell table. Mechanism: under-segmentation or lateral spillover makes a chimeric vector that looks like a plausible rare type. Symptom: a "novel doublet lineage". Fix: overlay mask + both channels on those exact cells; two abutting nuclei means a segmentation artifact.
Trigger: letting the viewer auto-scale each field. Mechanism: the contrast limit is a positivity threshold; auto-scaling moves it per field. Symptom: "positive" drifts image to image; inconsistent labels. Fix: fix and record contrast limits; apply the same transform to every annotator and image.
Trigger: treating manual labels as ground truth. Mechanism: expert-vs-expert concordance is ~86%, and many disagreements reflect genuinely ambiguous cells. Symptom: overfitting to one annotator's noise. Fix: use multi-annotator consensus for the evaluation set; report inter-annotator agreement as the ceiling.
Trigger: labeling many cells in a single field. Mechanism: batch/staining variation between images is a top failure mode. Symptom: the classifier generalizes only to that ROI. Fix: spread annotation across patients/images and compartments; breadth over depth.
| Threshold | Source | Rationale | |-----------|--------|-----------| | Inter-annotator concordance ~86% | Amitay 2023 Nat Commun 14:4302 | the realistic accuracy ceiling; many disagreements are genuinely ambiguous | | Hundreds-to-low-thousands confident cells/class | Amitay 2023; Shaban 2024 | enough to train; rare classes and inter-image variation bind, not total count | | Over-sample rare classes + Poisson-resample augmentation | Amitay 2023 Nat Commun 14:4302 | tissue is imbalanced; signal is ion counts | | Labels NOT harvested from clustering | annotation hygiene | clustering-derived labels re-import the double-positive artifact |
| Error / symptom | Cause | Solution |
|-----------------|-------|----------|
| .mcd will not open in napari | core napari has no IMC reader | install and use the napari-imc plugin |
| Annotation layer behaves unexpectedly | wrong layer type | add_labels for masks, add_points (with features) for per-cell labels |
| Cluster looks tight but is biologically odd | spillover/segmentation artifact | paint it on tissue; demand spatial coherence |
| Rare cell type unlearnable | random-field annotation | deliberately over-sample rare types; augment |
| Classifier plateaus below expectation | exceeding inter-annotator agreement | accept the ~86% ceiling; consensus-label the evaluation set |
tools
--- name: bio-phasing-imputation-foundations description: Frames the phasing/imputation pipeline before any tool runs: phasing and imputation are one Li-Stephens copying HMM (recombination is the transition, mutation the emission, the genetic map and Ne set the rates), imputation's honest output is a dosage with a self-estimated quality (INFO/R2/DR2) not a hard genotype, and the stages are ordered and each fails silently (QC, align build and strand to the panel, phase, impute per chromosome, fil
tools
Chooses the enrichment generation before any tool runs, mapping the input shape to a method class - a pre-selected gene list plus a background to over-representation analysis (ORA, hypergeometric), a ranked statistic for all genes to gene set enrichment (GSEA), a signed signaling topology to pathway-topology (SPIA) - then making the null explicit (competitive vs self-contained, gene vs subject sampling) and running a trustworthiness checklist (testable-gene universe, FDR, redundancy collapse, leading-edge check, version reporting). Covers why every clusterProfiler GSEA is the inter-gene-correlation-uncorrected competitive null, why the background not the gene list decides ORA significance, and why no method is universally best. Use when deciding ORA vs GSEA vs topology, which gene-set DB, whether a result is trustworthy, or which null a tool computes. For ORA see go-enrichment, GSEA see gsea, databases kegg-pathways/reactome-pathways/wikipathways; the ranking comes from differential-expression/de-results.
testing
End-to-end GWAS workflow from VCF to association results. Covers PLINK QC, population structure correction, and association testing for case-control or quantitative traits. Use when running genome-wide association studies.
development
Orchestrates the full path from differential expression results to redundancy-collapsed functional enrichment: choose ORA vs GSEA, convert gene IDs per method, run enrichGO/enrichKEGG/enrichPathway/enrichWP or gseGO/gseKEGG (clusterProfiler, ReactomePA, rWikiPathways), and visualize. Routes the ORA-vs-GSEA generation fork and the null/universe/reproducibility theory to pathway-analysis/enrichment-foundations. Use when a DESeq2/edgeR/limma result must become enriched GO terms, KEGG/Reactome/WikiPathways pathways, or a GSEA leading edge; when deciding whether a ranking exists for all genes (GSEA, named decreasing vector) or only a pre-selected list (ORA plus a defensible background universe); or when assembling DE-to-pathway end to end. The DE list and ranking statistic come from differential-expression/de-results; per-method nuance lives in the pathway-analysis skills.