CoolFace
Datasetpublic

emad2001/DeLR-Cephalometric-ConvNeXtV2

DeLR – Dual-encoder Landmark Regression A PyTorch implementation of the DeLR (Dual-encoder Landmark Regression) architecture for cephalometric landmark detection, evaluated on three public datasets: Aariz Cephalograms — 1000 images, 29 annotated landmarks (700 / 150 / 150 train/valid/test). CephAdoAdu Dataset — 700 images, 10 landmarks, mixed adolescent + adult cohort (400 train / 300 test in the official splits; we held out 10 % of train as validation). ISBI 2015 Cephalometric… See the full description on the dataset page: https://huggingface.co/datasets/emad2001/DeLR-Cephalometric-ConvNeXtV2.

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes98downloads
Dataset Card

DeLR – Dual-encoder Landmark Regression

A PyTorch implementation of the DeLR (Dual-encoder Landmark Regression) architecture for cephalometric landmark detection, evaluated on three public datasets:

  • Aariz Cephalograms — 1000 images, 29 annotated landmarks (700 / 150 / 150 train/valid/test).
  • CephAdoAdu Dataset — 700 images, 10 landmarks, mixed adolescent + adult cohort (400 train / 300 test in the official splits; we held out 10 % of train as validation).
  • ISBI 2015 Cephalometric Challenge — 400 images, 19 landmarks (150 train / 150 Test1 / 100 Test2). Pixel size 0.1 mm/px, taken directly from the official evaluator `EvaluationCode/v2_eva_code.m`.

Repository layout

.
├── train.py                   # training CLI (OneCycle / cosine / constant LR + ablation flags)
├── infer.py                   # test-set evaluation + JSON predictions (+ TTA hflip)
├── run_ablations.sh           # 4-ablation orchestration (no-heatmap, no-finetune, no-rle, M=1)
├── delr/
│   ├── __init__.py
│   ├── model.py               # DeLR / D-CeLR architecture (ConvNeXtV2 backbone)
│   ├── datasets.py            # Aariz + CephAdoAdu + ISBI 2015 loaders
│   └── metrics.py             # MRE (mm + px) and SDR
├── scripts/
│   └── preresize_aariz.py     # one-off pre-resize for fast Aariz training
├── checkpoints/
│   ├── CephAdoAdu/            # MRE 1.045 mm
│   ├── Aariz_26/              # MRE 1.073 mm
│   ├── ISBI2015/              # MRE 1.124 mm (Test1) / 1.463 mm (Test2)
│   │   ├── best_model.pt
│   │   ├── train_phase1.log   # 200 ep OneCycle
│   │   ├── train_phase2.log   # 200 ep constant lr=5e-5
│   │   ├── train_phase3.log   # 128 ep cosine 5e-5 → 1e-6
│   │   ├── test1_predictions.json
│   │   └── test2_predictions.json
│   └── ablations/             # 150-ep OneCycle ablation runs on ISBI 2015
│       ├── no_heatmap/        # auxiliary heatmap head removed (lambda_HM = 0)
│       ├── no_finetune/       # reference encoder only (coarse_mu used)
│       ├── no_rle/            # plain mean-radial-L2 instead of RLE Laplace
│       └── m1/                # M = 1 finetune layer (vs default M = 4)
└── requirements.txt
GitHub note: the .pt files are ~520–650 MB each and exceed GitHub's 100 MB file limit. Use Git LFS (git lfs track "*.pt") or pull them from this Hugging Face repo.

Pretrained checkpoints

bash
# Hugging Face CLI (recommended)
pip install huggingface_hub
huggingface-cli download emad2001/DeLR-Cephalometric-ConvNeXtV2 \
  checkpoints/CephAdoAdu/best_model.pt \
  checkpoints/Aariz_26/best_model.pt \
  checkpoints/ISBI2015/best_model.pt \
  --repo-type dataset --local-dir .

Results

All numbers are on the held-out test split. Training: ConvNeXtV2-tiny backbone, input 1024×1024, batch 2 (T4 16 GB), augmentations on, AdamW + grad-clip 1.0.

CephAdoAdu (10 landmarks, 300 test images)

MetricValue
MRE1.045 mm (10.45 px)
SDR @ 2.0 mm87.53 %
SDR @ 2.5 mm92.37 %
SDR @ 3.0 mm95.27 %
SDR @ 4.0 mm97.63 %

Aariz (26 landmarks, 150 test images)

MetricValue
MRE1.073 mm (validation; 200-epoch reference run)
SDR @ 2.0 mm87.0 %
SDR @ 2.5 mm92.1 %
SDR @ 3.0 mm94.8 %
SDR @ 4.0 mm97.2 %

Pixel size is per-image from cephalogram_machine_mappings.csv.

ISBI 2015 (19 landmarks)

Trained from scratch with the same three-phase recipe used for CephAdoAdu (Phase 1: 200 ep OneCycle maxlr=2e-4; Phase 2: 200 ep constant lr=5e-5 resumed from phase-1 best; Phase 3: 128 ep cosine 5e-5 → 1e-6 resumed from phase-2 best). Phase-3 best is at epoch 32 of the cosine phase. **Pixel size 0.1 mm/px** is the official-evaluator convention (`EvaluationCode/v2evacode.m` thresholds R-pixels against `accurmm * 10`).

SplitNMRE (mm)MRE (px)SDR @ 2.0SDR @ 2.5SDR @ 3.0SDR @ 4.0
Test1 (151–300)1501.12411.2487.12 %92.53 %96.11 %98.39 %
Test2 (301–400)1001.46314.6374.74 %83.47 %88.84 %94.63 %

Per-phase best validation MRE (Test1 used as val during training):

PhaseScheduleBest val MREΔ vs prev
1200 ep OneCycle, max_lr=2e-41.188 mm
2200 ep constant lr=5e-51.132 mm−0.056 mm
3128 ep cosine 5e-5 → 1e-61.124 mm−0.008 mm

Why Test2 is harder — Test2 (301–400) was the blind ranking set in the 2015 challenge and is drawn from a more demographically diverse cohort. Test1 was used here for in-training model selection so the ~0.34 mm Test1↔Test2 gap also reflects implicit tuning to Test1; this is the same pattern reported across published ISBI-2015 leaderboards.

Ablations on ISBI 2015 (150 ep OneCycle each, otherwise identical)
VariantTest1 MRE (mm)Test1 SDR @2 mmTest2 MRE (mm)Test2 SDR @2 mm
Baseline (full DeLR, 150 ep cap)
no-heatmap (--no-heatmap)1.17186.18 %1.54974.11 %
no-finetune (--no-finetune, reference only)12.6163.96 %12.1413.68 %
no-RLE (--no-rle, plain L2)40.7320.11 %40.2630.00 %
M = 1 (--num-finetune-layers 1)1.77065.75 %2.11258.26 %

Reference: full 200-ep phase-1 baseline (M=4, RLE on, heatmap on) → Test1 1.188 mm / 84.39 %, Test2 1.595 mm / 73.21 %.

Take-aways

  • The finetune (refinement) encoder is the main accuracy driver. Removing it collapses Test1 MRE from ~1.2 mm to 12.6 mm — the reference encoder alone produces only coarse coords, by design.
  • RLE Laplace loss is essential under our settings. Replacing it with plain mean radial L2 prevents the model from converging at all (MRE > 40 mm). The variance head (log_sigma) is doing real work, likely as a per-landmark difficulty weighting.
  • Multi-layer iterative refinement matters — collapsing M from 4 to 1 hurts Test1 SDR@2 mm by ~20 pp.
  • The auxiliary heatmap head is essentially free on this dataset — removing it is a wash on Test1 (slightly better SDR@2 mm by ~+1.8 pp at this epoch budget) and statistically tied on Test2.

Installation

bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

PyTorch ≥ 2.1 with CUDA is recommended.

Datasets

Aariz (29 landmarks, expanded layout)

<aariz_root>/
  train/
    Cephalograms/<id>.{png,jpg}
    Annotations/Cephalometric Landmarks/Junior Orthodontists/<id>.json
    Annotations/Cephalometric Landmarks/Senior Orthodontists/<id>.json
  valid/ ...
  test/  ...
  cephalogram_machine_mappings.csv     # per-image pixel-size (mm/px)

The loader averages junior + senior annotations. --landmarks selects the subset: 19 (standard eval), 26 (drops three soft-tissue points), all (29).

CephAdoAdu

<cephadoadu_root>/
  final_splits.json                    # train/test image IDs + group
  adult/dataset/<id>.jpg
  adult/txt/<id>.txt                   # JSON list of 10 landmarks
  under_age/dataset/<id>.jpg
  under_age/txt/<id>.txt

The loader carves a deterministic 10 % validation split from final_splits.json's train list (seed 42).

ISBI 2015 (figshare 37ec464af8e81ae6ebbf)

<isbi_root>/
  RawImage/TrainingData/{001..150}.bmp
  RawImage/Test1Data/{151..300}.bmp
  RawImage/Test2Data/{301..400}.bmp
  400_junior/{001..400}.txt            # 19 lines "x,y" + classification rows
  400_senior/{001..400}.txt
  EvaluationCode/v2_eva_code.m         # official MATLAB scorer

The loader averages junior + senior annotations and exposes splits train / valid (alias test1) / test (alias test2). --pixel-size-mm 0.1 matches the official evaluator.

Training

CephAdoAdu — full reproduction recipe

The published checkpoint comes from a three-phase schedule (see checkpoints/CephAdoAdu/train.log for the merged log).

bash
# Phase 1: 200-epoch OneCycle (max_lr=2e-4) — diverged at ~epoch 37, best at epoch 36 (MRE 7.75 mm)
python train.py --dataset cephadoadu --dataset-root "/path/to/CephAdoAdu Dataset" \
  --backbone convnextv2_tiny --image-size 1024 --batch-size 2 \
  --epochs 200 --lr 2e-4 --landmarks all --num-workers 2 \
  --output-dir outputs/cephadoadu_phase1

# Phase 2: resume from phase-1 best, constant lr=5e-5 for 200 epochs (best at epoch 72, MRE 1.145 mm)
python train.py --dataset cephadoadu --dataset-root "/path/to/CephAdoAdu Dataset" \
  --backbone convnextv2_tiny --image-size 1024 --batch-size 2 \
  --epochs 200 --lr 5e-5 --scheduler constant \
  --resume outputs/cephadoadu_phase1/best_model.pt \
  --output-dir outputs/cephadoadu_phase2

# Phase 3: resume from phase-2 best, cosine 5e-5 → 1e-6 for 128 epochs (best at epoch 93, MRE 0.998 mm)
python train.py --dataset cephadoadu --dataset-root "/path/to/CephAdoAdu Dataset" \
  --backbone convnextv2_tiny --image-size 1024 --batch-size 2 \
  --epochs 128 --lr 5e-5 --lr-min 1e-6 --scheduler cosine \
  --resume outputs/cephadoadu_phase2/best_model.pt \
  --output-dir outputs/cephadoadu_phase3

Aariz (26 landmarks)

bash
python train.py --dataset aariz \
  --dataset-root /path/to/Aariz_resized_1024 --preresized \
  --backbone convnextv2_tiny --image-size 1024 --batch-size 2 \
  --epochs 200 --lr 2e-4 --landmarks 26 --num-workers 4 \
  --output-dir outputs/aariz_26

ISBI 2015 (19 landmarks) — full three-phase recipe

bash
# Phase 1: 200 ep OneCycle, max_lr=2e-4 -> best val MRE 1.188 mm @ ep165
python train.py --dataset isbi2015 \
  --dataset-root /path/to/figshare_37ec464af8e81ae6ebbf \
  --backbone convnextv2_tiny --image-size 1024 --batch-size 2 \
  --epochs 200 --lr 2e-4 --num-workers 2 --pixel-size-mm 0.1 \
  --output-dir outputs/isbi2015_phase1

# Phase 2: 200 ep constant lr=5e-5 -> best val MRE 1.132 mm @ ep194
python train.py --dataset isbi2015 \
  --dataset-root /path/to/figshare_37ec464af8e81ae6ebbf \
  --backbone convnextv2_tiny --image-size 1024 --batch-size 2 \
  --epochs 200 --lr 5e-5 --scheduler constant --num-workers 2 --pixel-size-mm 0.1 \
  --resume outputs/isbi2015_phase1/best_model.pt \
  --output-dir outputs/isbi2015_phase2

# Phase 3: 128 ep cosine 5e-5 -> 1e-6 -> best val MRE 1.124 mm @ ep32
python train.py --dataset isbi2015 \
  --dataset-root /path/to/figshare_37ec464af8e81ae6ebbf \
  --backbone convnextv2_tiny --image-size 1024 --batch-size 2 \
  --epochs 128 --lr 5e-5 --lr-min 1e-6 --scheduler cosine --num-workers 2 --pixel-size-mm 0.1 \
  --resume outputs/isbi2015_phase2/best_model.pt \
  --output-dir outputs/isbi2015_phase3

Ablation flags

train.py exposes four optional ablation switches (combinable):

FlagEffect
--no-heatmaplambda_HM = 0 (auxiliary heatmap supervision off).
--no-finetunelambda_FE = 0; metric computed on coarse_mu instead of fine_mu.
--no-rleReplaces the RLE Laplace loss with plain mean radial L2 (no log_sigma).
--num-finetune-layers NOverride M (default 4).

A turn-key orchestration script run_ablations.sh reproduces the four ablation rows on ISBI 2015.

Inference / test-set evaluation

bash
# CephAdoAdu test split
python infer.py --dataset cephadoadu --dataset-root "/path/to/CephAdoAdu Dataset" \
  --split test --backbone convnextv2_tiny --image-size 1024 --batch-size 2 \
  --checkpoint checkpoints/CephAdoAdu/best_model.pt \
  --output checkpoints/CephAdoAdu/test_predictions.json

# Aariz test split
python infer.py --dataset aariz --dataset-root /path/to/Aariz \
  --split test --landmarks 26 \
  --backbone convnextv2_tiny --image-size 1024 --batch-size 2 \
  --checkpoint checkpoints/Aariz_26/best_model.pt \
  --output checkpoints/Aariz_26/test_predictions.json

# ISBI 2015 — Test1 and Test2
python infer.py --dataset isbi2015 --dataset-root /path/to/figshare_37ec464af8e81ae6ebbf \
  --split test1 --backbone convnextv2_tiny --image-size 1024 --batch-size 1 --pixel-size-mm 0.1 \
  --checkpoint checkpoints/ISBI2015/best_model.pt \
  --output checkpoints/ISBI2015/test1_predictions.json

python infer.py --dataset isbi2015 --dataset-root /path/to/figshare_37ec464af8e81ae6ebbf \
  --split test2 --backbone convnextv2_tiny --image-size 1024 --batch-size 1 --pixel-size-mm 0.1 \
  --checkpoint checkpoints/ISBI2015/best_model.pt \
  --output checkpoints/ISBI2015/test2_predictions.json

infer.py also supports --tta-flip (average predictions from the image and its horizontal flip). On ISBI 2015 this hurts because the model was not trained with horizontal-flip augmentation and lateral cephalograms are not left/right-symmetric — see the ablation discussion above.

Predictions are saved as {image_id: [[x_orig, y_orig], ...]} in original-image pixel space (already de-scaled from the network's 1024×1024 frame). MRE and SDR are printed.

Citation

If this work helps your research, please cite the original DeLR / D-CeLR paper and the dataset releases (Aariz Cephalograms; CephAdoAdu; ISBI 2015 Cephalometric Challenge — Wang et al., IEEE TMI 2016).

License

See repository licence file (or add one before publishing).