drdetect: Diabetic Retinopathy Screening Model
Research prototype. NOT a medical device. NOT a substitute for clinical judgement.
EfficientNet-B0 (regression-loss ordinal head) for 5-class ICDR diabetic retinopathy grading, trained on APTOS 2019 and evaluated once, honestly, on a locked external test set (Messidor-2 + IDRiD). Full code, every experiment, and the complete evidence trail: github.com/adarshcod30/Diabetic-Retinopathy-Detection.
Why this checkpoint
This is not the checkpoint with the best internal-validation accuracy — a plain
cross-entropy baseline scored higher there. It is the checkpoint that won a pre-registered,
one-time external evaluation on data neither model was tuned on: referable-DR AUC 0.9242 vs.
0.8878 for the CE baseline (DeLong test, p=6.1×10⁻¹⁰). See the full write-up:
docs/22_PHASE8_VALIDATION_RESULTS.md.
Files
best.ckpt— PyTorch Lightning checkpoint (EfficientNet-B0, regression head, 512×512 input).efficientnet_b0_regression_512px.onnx— ONNX export, parity-verified against the PyTorch module (max abs diff 2.4×10⁻⁷).
Headline results (locked external test, run once)
| QWK | Sensitivity | Specificity | Referable AUC | |
|---|---|---|---|---|
| This model | 0.6995 | 0.441 | 0.976 | 0.9242 |
Read the limitation, not just the AUC: referable-DR sensitivity is 44.1% against a >=90%
target — well below every published comparator. This is diagnosed (not just disclosed) as a
threshold-transfer/calibration failure, not a pure discrimination failure: the frozen operating
threshold from internal validation does not transfer to this external population, even though the
model's ranking ability (AUC) held up in a range comparable to a cited external-validation drop in
the literature. Any real use of this model's binary referable/non-referable output requires
fitting a new threshold on a local calibration set first. Full detail:
MODEL_CARD.md.
Usage
import torch
from drdetect.grading.model import build_model # from the GitHub repo's src/
model = build_model("efficientnet_b0", num_outputs=1, pretrained=False, freeze_bn=True)
ckpt = torch.load("best.ckpt", map_location="cpu", weights_only=False)
state = ckpt.get("state_dict", ckpt)
state = {k.removeprefix("model."): v for k, v in state.items() if k.startswith("model.")}
model.load_state_dict(state)
model.eval()
Or with the repo's own pipeline (handles preprocessing, quality gating, and decoding):
from drdetect.serve.pipeline import load_grader, run_pipeline
model = load_grader("best.ckpt", backbone="efficientnet_b0", loss_name="regression", device="cpu")
result = run_pipeline(image_rgb, model, loss_name="regression", size=512, device="cpu")
License
Research use only. Derived from training data under mixed licenses that restrict
redistribution (APTOS/Kaggle competition rules, Messidor-2's ADCIS terms) — see
DATASET_CARD.md.
Not licensed for any clinical, diagnostic, or commercial product.