codex-celltype assigns cell types to segmented cells in CODEX or other
multiplexed images using marker fingerprints, QuPath positive-marker calls, and
local marker intensity. It is a configurable and tested replacement for the
dataset-specific scripts that originally lived in this working folder.
The pipeline reads tables, not image pixels. A typical input is QuPath's tab-delimited cell measurement export containing:
- one row per cell;
- an object ID and X/Y centroid coordinates;
- marker intensity columns such as
GCG: Cell: Mean; and - either a colon-separated
Classificationvalue such asPAX6:GCG, or explicit+/-columns for every marker.
Raw CODEX images and patient/donor tables are deliberately not included in this repository.
- A single command handles any number of samples.
- All paths, column names, marker aliases, QC rules, and algorithm parameters live in one YAML configuration.
- A sample manifest removes assumptions about QuPath filenames and directory structure.
codex-celltype validatecatches missing files, columns, and markers before a long run starts.- Coordinate headers with
µm, the common mangledµm,um, or pixels are detected automatically. - Markers that are absent, failed QC, excluded, or have constant intensity are handled safely.
- Ambiguous first-round calls use a regularized intensity-profile tie breaker; if no profile exists, the result is deterministic.
- Every output includes scores, margins, tie information, positive markers, a cell-type summary, marker enrichment tables, and a machine-readable run report.
- A synthetic example, unit tests, packaging metadata, and GitHub Actions CI make the analysis reproducible.
The project is tested with Python 3.10–3.12. In conda:
conda create -n codex-celltype python=3.10 -y
conda activate codex-celltype
python -m pip install -e ".[dev]"Run the bundled synthetic example:
codex-celltype validate --config examples/demo/config.yaml
codex-celltype run --config examples/demo/config.yamlResults appear under examples/demo/results/. Run the tests with:
pytestCreate a small project skeleton:
codex-celltype init my_datasetThis creates:
my_dataset/
├── config.yaml # column names, algorithm, QC, and output settings
├── fingerprint.csv # cell-type/marker rules
├── samples.csv # one row per image/table
└── data/ # place QuPath exports here, or use absolute paths
Then:
- Copy or link each QuPath measurement table into
my_dataset/data/. - Add one row per table to
samples.csv. - Edit
fingerprint.csvfor the tissue and antibody panel. - Edit
config.yamlonly where your QuPath schema differs. - Validate first, then run:
codex-celltype validate --config my_dataset/config.yaml
codex-celltype run --config my_dataset/config.yamlThe pipeline refuses to overwrite per-sample results unless --force is
provided.
samples.csv has two required columns and one optional column:
sample_id,measurement_file,hierarchy_file
donor_01,data/donor_01_measurements.tsv,
donor_02,/absolute/path/donor_02_measurements.tsv,/absolute/path/donor_02_hierarchy.txtsample_idmust be unique and is used in output filenames.measurement_filemay be relative toconfig.yamlor absolute.hierarchy_fileis needed only whenregion_filter.enabled: true.
Default required columns are:
| Purpose | Default or example |
|---|---|
| Cell identifier | Object ID |
| X coordinate | auto-detected, e.g. Centroid X µm |
| Y coordinate | auto-detected, e.g. Centroid Y µm |
| Positive calls | Classification |
| Marker intensity | {marker}: Cell: Mean |
Override any nonstandard name under input: in config.yaml.
QuPath Classification values are interpreted as a configurable delimiter-
separated list of positive markers. All panel markers not listed for that cell
are treated as negative.
If marker calls are stored in separate columns, configure them explicitly:
positive_calls:
mode: columns
columns:
GCG: GCG positive
CPEP: CPEP positive
CD45: CD45 positive
positive_values: ["+", 1, true, positive]Numeric values greater than zero are also accepted as positive.
The fingerprint is a CSV with three columns:
cell_type,marker,weight
Endocrine,PAX6,specific
Endocrine|alpha,GCG,specific
Endocrine|beta,CPEP,specific
Immune,CD45,specific
Immune|Macrophage,CD11c,positiveWeights may be written as:
specificor2: a defining marker. It contributes+2to this cell type and its descendants and-1to other types.positiveor1: a supporting marker. It contributes+1to this cell type and its descendants without penalizing other types.
The pipe character defines hierarchy. For example, a marker on Endocrine
also applies to Endocrine|alpha. NONE is reserved for cells that do not
exceed the score threshold.
The original pediatric-pancreas rules are available as
fingerprints/pediatric_pancreas.csv.
They are a starting point and still require validation for the antibody panel,
age group, tissue processing, and QuPath thresholds in a new study.
For panels that differ by image, set:
input:
availability_table: marker_availability.tsv
availability_pass_values: [G, OK]The table needs an image, sample, or sample_id column whose values match
the manifest. Marker names are the other columns. Explanatory legend lines
before the header are allowed. Only values listed in
availability_pass_values enter that sample's model.
For each sample, the pipeline:
- intersects the fingerprint with intensity columns, QC-passing markers, and configured exclusions;
- z-standardizes every usable marker intensity within the image;
- represents each cell's positive/negative marker evidence;
- upgrades a positive call from
1.0to1.2when the cell has greater intensity than its nearby cells for that marker; - multiplies this evidence by the hierarchical fingerprint matrix;
- assigns the highest-scoring type above the configured threshold; and
- resolves exact ties from intensity profiles of unambiguous cells, with a variance floor for small or constant groups.
The main parameters are under algorithm::
algorithm:
n_neighbors: 3
max_neighbor_distance: 12
score_threshold: 0.2Distances use the units of the exported centroid coordinates.
The legacy pancreas workflow suppressed endocrine calls outside QuPath islet annotations. This behavior is optional and off by default:
region_filter:
enabled: true
parent_prefix: Islet
restricted_markers: [PAX6, CHGA, GCG, CPEP, SST, GHRL, PPY]When enabled, each manifest row needs a whitespace-delimited hierarchy file whose first field is the parent annotation and whose last field is the cell object ID.
The output directory contains:
| File | Contents |
|---|---|
{sample_id}_annotated.csv |
Original QuPath columns plus annotation results |
cell_type_summary.csv |
Cell count and fraction by sample and cell type |
marker_z_enrichment_long.csv |
Mean marker Z-enrichment across images |
marker_z_enrichment_wide.csv |
Cell type × marker enrichment matrix |
marker_z_enrichment_n_images.csv |
Number of contributing images |
run_report.json |
Input files, cell counts, markers used, and tie counts |
Important annotation columns are:
Cell_type_fingerprints: final label;Cell_type_score: winning fingerprint score;Cell_type_margin: separation from the next non-tied score;Cell_type_tie_broken: whether the second round chose the label;Cell_type_candidate_count: number of tied first-round candidates; andPositive_markers_used: calls used after QC and optional region filtering.
This is a rule-based annotation tool, not a trained universal classifier. Before using it on another tissue or antibody panel:
- revise the fingerprint with domain experts;
- confirm QuPath positivity thresholds and segmentation quality;
- inspect
NONE, low-margin, and tie-broken cells; - compare calls with marker-expression plots and tissue morphology; and
- treat the bundled example as a software test, not a validated biological reference.
The original run_HANDELP.py, functions.py, enrichment figures, NumPy arrays,
workbook, and dataset-specific availability table are retained locally for
reference but ignored by Git. The public project contains the reusable package,
synthetic example, tests, and documentation only.
The software is released under the MIT License. Citation metadata is
provided in CITATION.cff.