croma

A distributional successor to PathoROB's Robustness Index for pathology foundation models

A Python package measuring 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.

This is a direct follow-up to PathoROB (Nature Communications, 2026), which introduced the Robustness Index and showed that technical variation is strongly encoded in pathology foundation model representations. croma takes RI as its starting point and asks what its formulation leaves on the table. The diagnosis, and the two metrics that follow from it, are set out in Beyond Counts.

RI is theirs, and three of the four benchmark cohorts scored here are theirs too — if you use this package, please cite PathoROB alongside the paper.

Installation

pip install croma

Quick start

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="paired_2x2")

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)

What RI leaves on the table

All three metrics ask the same question of every sample: among its nearest neighbours in feature space, does it sit closer to samples sharing its biology or its confounder? They differ in how they turn that neighbourhood into a number — and that difference is the whole argument.

  • RI — PathoROB’s original. Counts favourable versus unfavourable neighbours inside a fixed k. Every neighbour weighs the same, whether it is adjacent to the sample or at the edge of the window.
  • MaRI — the obvious repair. Keeps the window, but weights each neighbour by distance, so near neighbours dominate. It changes surprisingly little, which is the useful negative result: discarding distance was not the real problem.
  • CRoMa — drops the window entirely and reports a signed margin, \((d_{OS} - d_{SO}) / (d_{OS} + d_{SO})\), positive when biology dominates.

The deeper limitation is RI’s pooled, fixed-neighbourhood design. Pooling hides how unevenly robustness is distributed across a cohort — two models with the same score can have very different tails. And a sample whose top-k happens to contain no informative neighbour is simply undefined, so RI ends up scoring a subset of the data that varies from model to model.

CRoMa searches outward until it finds informative neighbours of both kinds, so it stays defined for every sample, and reports robustness as a cohort-wide distribution rather than one pooled number. In the paper this exposes a confounder-dominated lower tail in every tile encoder evaluated — invisible to a pooled score, and predictive of how much a model degrades under shortcut-inducing adaptation.

Features

  • A clean re-implementation of PathoROB’s RI alongside the two new metrics, so all three run on the same inputs and are directly comparable
  • Metrics only: never loads models or reads images — you provide the embeddings
  • Minimal dependencies (numpy, pandas, scikit-learn, tqdm)
  • Python API and CLI
  • Benchmark results for 20 tile-level and 4 slide-level encoders; three of the four cohorts (Camelyon, TCGA-4×4, Tolkach-ESCA) come from PathoROB

See the documentation for the full metric definitions, manifest format, and benchmark tables.