TRUE-Colon: Exposing a Consistent Transfer Asymmetry in Real-Time Polyp Detection @ MICCAI 2026 EndoLINA
[Preprint] [PyPI] [Pretrained Weights] [Citation]
The same seven REAL-Colon frames through all four detectors. Ground truth in green, predictions in red. The last rows are the point: on flat, blurred and obliquely-viewed lesions every architecture misses, and curated benchmarks contain almost none of them.
Overview
Computer-aided detection (CADe) systems for colonoscopy promise to reduce clinical miss rates, yet reliable real-world deployment remains elusive. This translational gap stems in part from a structural flaw in model development: the reliance on curated datasets that under-represent the long negative stretches and procedure-related artifacts characteristic of routine examinations. Training and evaluating architectures strictly on these lesion-centric benchmarks creates an illusion of success, since such benchmarks cannot capture clinically crucial metrics such as false-alert burden, detection latency, and temporal reliability.
To expose this gap, we establish TRUE-Colon, a standardized benchmarking protocol that measures these deployment characteristics alongside localization accuracy, and evaluate four real-time architectures (Faster R-CNN, YOLOv8, YOLOv11, RT-DETR) across curated benchmarks (SUN, PICCOLO) and 60 unedited, full-length procedures (REAL-Colon).
Key Contributions
- A standardized, deployment-oriented evaluation protocol. Four complementary tiers measured at a matched false-alert operating point, so architectures are compared at equal clinical cost rather than at an arbitrary shared confidence value.
- A consistent transfer asymmetry. Models trained on curated clips collapse on full procedures (YOLOv11 mAP50 drops from 0.724 on SUN to 0.164 on REAL-Colon), while procedure-trained models largely retain their accuracy on curated benchmarks.
- An accuracy-versus-throughput trade-off that fixed-threshold comparisons obscure: at a matched false-alert burden the Transformer detector leads on sensitivity, latency and temporal persistence, while the convolutional detectors stay competitive at markedly higher throughput.
What the protocol measures
| Tier | Question it answers | Why curated benchmarks cannot answer it |
|---|---|---|
| Detection | Are the boxes in the right place? | — (standard COCO mAP) |
| Frame | How often does it alert on a lesion-free frame? | Curated clips are almost all lesion-bearing |
| Lesion | How fast is the first detection, and does the box persist? | Requires contiguous, unedited video |
| AFROC | Sensitivity against the fraction of clean frames raising an alert | Needs a realistic negative base rate |
Installation
pip install true-colon
The evaluator depends only on numpy and pycocotools — no deep-learning framework.
Optional extras:
pip install "true-colon[plots]" # FROC / AFROC curve rendering
pip install "true-colon[streaming]" # streaming ingest for very large prediction files
To reproduce the paper's experiments, clone and install the reproduction layer:
git clone https://github.com/sdoerrich97/true-colon.git
cd true-colon
pip install -e ".[experiments,plots,streaming]"
The full pipeline runs from experiments/: prepare_data.py to build the splits,
train.py and predict.py per detector, evaluate.py for the protocol, then
aggregate_seeds.py and make_tables.py for the paper's tables and figures. Each script
carries the published settings as its argparse defaults, so the commands below are
complete as written.
Quick Start
TRUE-Colon evaluates any detector. It reads a COCO ground-truth file and a COCO predictions file, so if your model can export COCO results, you can run the protocol on it — no retraining, no GPU, no images required.
import true_colon as tc
evaluator = tc.create_evaluator(
dataset="realcolon", # or None, and configure for your own data
id_source="pred_string", # "gt_integer" for Detectron2-style integer image ids
)
result = evaluator.evaluate_paths("ground_truth.json", "predictions.json")
print(f"mAP50 {result.coco.ap50:.3f}")
print(f"frame TPR / FPR {result.frame.tpr:.3f} / {result.frame.fpr:.3f}")
print(f"lesions detected {result.lesion.summary.detected_any}/{result.lesion.summary.n_lesions}")
print(f"mean latency {result.lesion.summary.mean_latency_frames:.1f} frames")
Matching the operating point
Raw confidence scores are not comparable across architectures. To compare detectors at an equal false-alert burden, find the threshold that hits a target frame-level false-positive rate — on your validation split, then evaluate at it:
matched = evaluator.match_operating_point(val_gt, val_preds, target_fpr=0.045)
print(matched.threshold, matched.achieved_fpr, matched.feasible)
From the command line:
python experiments/evaluate.py \
--gt ground_truth.json \
--pred predictions.json \
--dataset realcolon --id-source pred_string \
--target-fpr 0.045 --out-dir outputs/
Runnable notebooks
All three are committed with their outputs, so they read without being run:
| Notebook | What it shows |
|---|---|
01_evaluate_your_detector |
The protocol end to end on a synthetic procedure, and why one number hides three stories |
02_threshold_matching |
Why a shared threshold is not a shared operating point, and the trap in fixing it |
03_verify_annotations |
Checking that a dataset conversion preserved the boxes, including what a padding bug looks like |
Tables and figures
Aggregate several seeds, then render. Nothing downstream of aggregate_seeds.py
recomputes a number, so a table cannot disagree with the results it came from:
python experiments/aggregate_seeds.py \
--model yolov11 outputs/y11m_s0/**/results.json outputs/y11m_s42/**/results.json \
--model rtdetr outputs/rtdetr_s0/**/results.json \
--out outputs/tables/realcolon.tsv
python experiments/make_tables.py outputs/tables/realcolon.tsv \
--out outputs/tables/realcolon.tex
python experiments/figures/froc_afroc.py \
--model "yolov11=outputs/y11m_s0/results.json,outputs/y11m_s42/results.json" \
--out-dir outputs/figures/
python experiments/figures/lesion_bars.py \
--model "yolov11=outputs/y11m_s0/results.json,outputs/y11m_s42/results.json" \
--meta-root /data/realcolon --out-dir outputs/figures/
Model names, ordering, colours and metric labels come from
experiments/configs/presentation.py, so figures and tables cannot drift apart. The
markers on the FROC/AFROC curves default to the published matched thresholds rather than
being retyped per figure.
Averaging curves across seeds requires the same confidence grid, and a mismatch is an error: point-by-point averaging of different grids yields a smooth curve that is an average of unrelated operating points.
Preparing the datasets
Evaluation needs only a ground-truth JSON and a predictions JSON, so nothing above requires the raw datasets. Reproducing the training runs does. One CLI covers all three:
# REAL-Colon, the route behind the published YOLOv8 / YOLOv11 / RT-DETR checkpoints
python experiments/prepare_data.py realcolon \
--src /data/realcolon --dst /data/prepared/realColon_640x640 --stats
# REAL-Colon, the route behind the published Faster R-CNN checkpoint
python experiments/prepare_data.py realcolon \
--src /data/realcolon --dst /data/prepared/realcolon_frcnn --route coco-export
python experiments/prepare_data.py sun --src /data/sun --dst /data/prepared/sun_split
python experiments/prepare_data.py piccolo --src /data/piccolo --dst /data/prepared/piccolo_split
REAL-Colon has two published routes and they are not interchangeable: they write
different layouts and read --negative-ratio differently. The default reproduces the
canonical one. See experiments/data/realcolon.py for how the two were told apart.
Each run writes a split_manifest.json recording a checksum of every split's membership.
A seed only reproduces a split if the RNG behaves identically, which is an assumption
across Python versions rather than a guarantee, so check rather than trust:
python experiments/prepare_data.py realcolon \
--src /data/realcolon --dst /data/prepared/realColon_640x640 --verify
--verify re-resolves membership without reading or writing a single frame, and exits
non-zero if the splits have changed. --stats prints a sanity report over a prepared
root: per-split frame and box counts, image/label agreement, and whether any lesion
appears in more than one split.
Model Zoo
Nine REAL-Colon checkpoints behind the paper's main table, grouped in a HuggingFace Collection.
| Detector | Training data | Input | Seeds | Identifier |
|---|---|---|---|---|
| YOLOv8-M | REAL-Colon | 640 | 0, 42, 123 | yolov8 |
| YOLOv11-M | REAL-Colon | 640 | 0, 42, 123 | yolov11 |
| RT-DETR | REAL-Colon | 640 | 0, 42, 123 | rtdetr |
from experiments.reference.weights import download_checkpoint
path = download_checkpoint("yolov11", seed=42)
Not released. The Faster R-CNN checkpoints and every SUN- and PICCOLO-trained model did not survive the original author's offboarding and could not be recovered, so the transfer-direction rows of the paper have no released artifact behind them. We would rather say this plainly than quietly ship a partial zoo.
Reproducibility
Because the protocol is framework-agnostic and CPU-only, every table in the paper can be regenerated from stored prediction files inside a hash-locked container, with no GPU and no dataset:
docker build --target runtime -t true_colon:latest .
docker run --rm -v /path/to/artifacts:/data:ro -v $PWD/outputs:/outputs true_colon:latest \
python experiments/evaluate.py --gt /data/gt.json --pred /data/predictions.json \
--dataset realcolon --out-dir /outputs
Detector training is a different matter: Detectron2 and Ultralytics pin incompatible
torch builds, so they get their own out-of-lock Docker targets (ultralytics,
detectron2). Only the evaluation runtime is covered by uv.lock and the hashed
requirements.txt.
Ultralytics must be >= 8.3.223. Below that, RT-DETR's exported predictions.json
carries wrong box coordinates while its metrics still look plausible; the fix landed
upstream in that release. See
PATCH_NOTES.md.
Training and inference
Both entry points take a prepared root and inject the dataset, seed and output directory, so nothing is hardcoded:
docker build --target ultralytics -t true_colon:ultralytics .
# Train. The published runs used seeds 0, 42 and 123.
python experiments/train.py --detector yolov11 \
--data /data/prepared/realColon_640x640 --seed 42 --out-dir /outputs/y11m_s42
# Export predictions, which is what evaluation consumes.
python experiments/predict.py --detector yolov11 \
--data /data/prepared/realColon_640x640 --split test \
--weights /outputs/y11m_s42/weights/best.pt --out-dir /outputs/y11m_s42
--published-seed fetches a released checkpoint from HuggingFace instead of --weights,
so predictions can be reproduced without retraining. --print-config on train.py shows
the resolved hyperparameters without starting a run.
Predictions are exported with a low confidence pre-filter (0.001) on purpose: thresholding at export time would destroy the information the FROC, AFROC and matched operating-point analyses need. The protocol thresholds at evaluation time instead.
Note that the four detectors do not all read the same prepared root. Faster R-CNN was
trained from the COCO-export route (--route coco-export above); the other three from the
letterbox route. experiments/data/realcolon.py explains how the two were told apart.
Evaluation itself is deterministic and seed-free. The published split seed (1000) and
training seeds (0, 42, 123) are frozen in experiments/configs/_published.py.
Honest limitation. This repository reproduces the pipeline, not the thesis's exact numbers: the original datasets, checkpoints and result files were not recoverable, so no published configuration can be re-executed and no drift figure is quoted. The port is instead verified by a differential test against the original implementation, which matches it exactly across every metric tier: the preparation routes, the split samplers and all five metric tiers were checked frame-by-frame against the code that produced the paper.
Project Structure
true_colon/ the protocol (the installed package; MIT, numpy + pycocotools only)
experiments/ reproduction layer (not in the wheel)
data/ dataset preparation, one module per dataset, plus sanity checks
reference/ the four detector baselines, each with its upstream LICENSE
figures/ the paper's figure generators
configs/ frozen published constants and the paper's presentation choices
examples/ runnable notebooks
Citation
@inproceedings{doerrich2026truecolon,
title = {TRUE-Colon: Exposing a Consistent Transfer Asymmetry in Real-Time Polyp Detection},
author = {Doerrich, Sebastian and Schwab, Andreas Franz and Di Salvo, Francesco
and Rai, Shyam Nandan and Nguyen, Hanh Huyen My and Ledig, Christian},
booktitle = {MICCAI Workshop on Endoscopic Lesion Identification and Analysis (EndoLINA)},
year = {2026},
eprint = {2608.13711},
archivePrefix = {arXiv},
primaryClass = {eess.IV},
doi = {10.48550/arXiv.2608.13711}
}
Sebastian Doerrich and Andreas Franz Schwab contributed equally. The LNCS proceedings entry will add volume and page numbers; until then the arXiv record above is the resolvable version.
Licensing
The components carry different licenses and are deliberately kept distinct.
| Component | License |
|---|---|
true_colon/ and project-authored experiments/, examples/ |
MIT |
experiments/reference/yolo_ultralytics/, rtdetr_ultralytics/ |
AGPL-3.0 (Ultralytics) |
experiments/reference/frcnn_detectron2/ |
Apache-2.0 (Detectron2) |
| Released model weights | AGPL-3.0 — trained with Ultralytics |
| REAL-Colon, PICCOLO datasets | CC BY 4.0 (not redistributed, apart from the frames in assets/) |
| SUN dataset | Research agreement required (not redistributed here) |
The protocol package never imports Ultralytics, which is what keeps it MIT-licensable.
The frames in assets/qualitative_comparison.png are from
REAL-Colon (Biffi et al., Scientific Data
11:539, 2024), used and redistributed here under CC BY 4.0. The overlaid boxes are
this project's. No other dataset imagery is redistributed.
If you use the released weights, the AGPL's obligations apply to your use of them.
Changelog
v0.1.1
- Packaging and documentation only; no change to
true_colonbehaviour or to any reported number. - Ship
experiments/figures/, the paper's figure generators. An unanchored.gitignorerule had kept them out of v0.1.0, so the figure commands documented below did not work from a clean checkout. - Correct the citation: six authors with Doerrich first, plus the arXiv eprint and DOI.
- Link the preprint, arXiv:2608.13711.
- Add
CITATION.cff.
v0.1.0
- Initial release accompanying the MICCAI 2026 EndoLINA paper.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file true_colon-0.1.1.tar.gz.
File metadata
- Download URL: true_colon-0.1.1.tar.gz
- Upload date:
- Size: 96.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
207779ca7a8d45239987d3a6a3395d95872778caec28a0cc86627dede2d2d604
|
|
| MD5 |
a83593aa050da0015d78b48037384ad9
|
|
| BLAKE2b-256 |
315f4dd6b8e54ec46fd3ee83502959c291b85f8f8d577a705170834117b09244
|
File details
Details for the file true_colon-0.1.1-py3-none-any.whl.
File metadata
- Download URL: true_colon-0.1.1-py3-none-any.whl
- Upload date:
- Size: 49.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e77c49db14505797559beae78752790d8b5501096d01cbf88744275082ae7ef2
|
|
| MD5 |
113ba1cfb7d455833061ea766cf48eb2
|
|
| BLAKE2b-256 |
238b17a216769f8537b5a7e2e3206b6f3921970517569fe249ac52e529a09f7a
|