Skip to main content

croma

arXiv PyPI version Documentation Code style: Black License

croma measures how much a pathology foundation model's representation is driven by biology rather than by non-biological technical variation -- staining, scanning, tissue preparation -- across centers.

Metric Name What it does
RI Robustness Index Counts favourable vs. unfavourable neighbours
MaRI Margin-aware Robustness Index Weights that same evidence by feature distance
CRoMa Cross-confounder Robustness Margin A signed margin, with tail-aware reporting

RI was introduced in the PathoROB study. croma provides a clean re-implementation of it, adds MaRI as its margin-aware extension, and introduces CRoMa, which overcomes limitations of both. MaRI and CRoMa are described in Beyond counts: A distributional robustness margin for pathology foundation models.

For downstream shortcut-susceptibility experiments, croma also provides the confounder-biased probe protocol, PathoROB's APD reduction, and normalized integrated performance degradation (nIPD). nIPD expresses degradation relative to the above-chance baseline performance available to lose and integrates it over Cramér's V. See the downstream API.

📖 Documentation

Install

pip install croma

The core package depends only on numpy, pandas, scikit-learn and tqdm. It never loads a model or reads an image -- you bring the embeddings. Add the paper-reproduction utilities with pip install "croma[repro]".

Quickstart

You need a manifest CSV with one row per sample, and an embeddings array of shape (N, D) whose row i is the embedding of manifest row i. Don't normalize them -- croma L2-normalizes internally and compares neighbours by cosine distance.

import numpy as np
import pandas as pd
from croma import CRoMa, MaRI, RI

manifest = pd.read_csv("manifest.csv")
features = np.load("embeddings.npy")

common = dict(confounder_column="center")  # evaluation_design defaults to "all"

ri = RI.compute(features, manifest, k_candidates=[5, 11, 21], **common)
mari = MaRI.compute(features, manifest, k_candidates=[5, 11, 21], **common)
croma = CRoMa.compute(features, manifest, **common)

print(f"RI    {ri.value:.3f}  (k={ri.k}, undefined {ri.undefined_frac:.1%})")
print(f"MaRI  {mari.value:.3f}  (tau={mari.tau:.4f})")
print(f"CRoMa {croma.value:+.3f}  (lower-tail mean {croma.ltm_alpha:+.3f})")

Results

Twenty-six encoders — twenty-five pathology foundation models and one natural-image control (†) — scored on three tile cohorts from PathoROB.

Model mean rank CRoMa rank tail rank Camelyon
CRoMa/LTM₁₀
TCGA-4×4
CRoMa/LTM₁₀
Tolkach-ESCA
CRoMa/LTM₁₀
Mascaret 1.7 2.0 1.3 0.29/-0.02 0.27/-0.11 0.51/0.01
RudolfV-2-S 4.3 2.3 6.3 0.32/-0.02 0.19/-0.16 0.49/-0.00
RudolfV-2 4.5 4.3 4.7 0.24/-0.04 0.17/-0.12 0.41/-0.01
RudolfV-2-B 5.5 4.7 6.3 0.24/-0.05 0.17/-0.14 0.41/-0.02
CONCHv1.5 7.5 7.3 7.7 0.19/-0.14 0.15/-0.13 0.39/-0.03
GenBio-PathFM 8.5 7.0 10.0 0.19/-0.07 0.16/-0.19 0.39/-0.02
CONCH 9.2 6.0 12.3 0.20/-0.20 0.15/-0.15 0.44/-0.04
Virchow2 9.2 8.3 10.0 0.20/-0.11 0.13/-0.17 0.35/-0.04

Top 8 of 25 ranked pathology encoders, over 3 tile cohorts; the DINOv2-B control is shown unranked in the full table. The CRoMa and tail ranks are the means of that encoder's within-cohort ranks — by median CRoMa, and by tail severity LTM₁₀ — and the mean rank averages those two. It orders the table; it does not replace them, because a strong median can hide a brittle tail and only the two columns show that — which is why each cohort shows both, median CRoMa/LTM₁₀. Bold marks the Pareto frontier: the encoders no other pathology encoder beats on both axes at once.

📊 Full panel, per-cohort detail and the distributions

Reading the numbers

RI and MaRI live in [0, 1]; above 0.5, biological evidence outweighs confounder evidence. CRoMa lives in (-1, 1) and is neutral at 0, positive when biology dominates.

Three habits will keep you out of trouble:

  1. Always read undefined_frac next to RI and MaRI. Samples with no informative neighbour in their top k are excluded from the score, so a high RI over a thin support is not a strong result.
  2. Never pin tau. It defaults to None, which resolves it per model on the scale of that model's own neighbour distances. One fixed tau shared across models sharpens the margin for some and flattens it for others -- exactly the distortion MaRI exists to remove. See Choosing tau.
  3. Read the tail, not just the mean. croma.ltm_alpha is the mean of the worst 10% of samples. Pooled scores hide brittle subgroups.

Also in the docs

  • Evaluation designs -- all, the default, scores every manifest row together and gives one number over the whole cohort at sample level; paired_2x2 controls what is compared and reports occurrence-level outputs. Includes the manifest contract and minimal valid examples for each.
  • Benchmarking -- the multi-model pipeline that produced the paper's numbers, split into embed / compute / render steps under scripts/.

Citing

If you use MaRI or CRoMa, please cite the paper — and the PathoROB study that introduced the Robustness Index these build on.

@article{grisi2026beyond,
  title   = {Beyond counts: A distributional robustness margin for pathology foundation models},
  author  = {Grisi, Cl{\'e}ment and van der Laak, Jeroen and Litjens, Geert},
  journal = {arXiv preprint arXiv:2607.25497},
  year    = {2026},
  doi     = {10.48550/arXiv.2607.25497},
  url     = {https://arxiv.org/abs/2607.25497}
}

To cite the software itself rather than the method, use CITATION.cff directly; GitHub's Cite this repository button resolves to the paper above.

License

Apache 2.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

croma-0.3.0.tar.gz (2.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

croma-0.3.0-py3-none-any.whl (59.5 kB view details)

Uploaded Python 3

File details

Details for the file croma-0.3.0.tar.gz.

File metadata

  • Download URL: croma-0.3.0.tar.gz
  • Upload date:
  • Size: 2.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for croma-0.3.0.tar.gz
Algorithm Hash digest
SHA256 65ea8edd8a1ff50f68d9a1be914ffab7cdf8b559ffa1e5b34bcaf014b87fd858
MD5 63b738857026814268ef8fcbeaab6577
BLAKE2b-256 f6640b3e745eecb01d6331e43ae41b7a5b6b0927faac0fdbc184e6e310a5d08a

See more details on using hashes here.

File details

Details for the file croma-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: croma-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 59.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for croma-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b4a9f167cc09764e2b66d83dd5c4c0fdd12854b0ad12e1f3fd4071ec29e80df0
MD5 6afea3771736ac58360d310d20979827
BLAKE2b-256 96512363df0d995368a89ae1a76dd5ffb38145d0bf83fabc528dce6e46d89f96

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page