Skip to content

Repository files navigation

OrdinalSvdH

Code and checkpoints for:

Ordinal DINOv2 Transfer for Joint-Level SvdH Scoring: What Helps and What Does Not Michele Cannito, Riccardo Renzulli, Marco Grangetto — RHEUMAI Workshop, MICCAI 2026

Partially fine-tuned DINOv2 ViT-S/14 with a cumulative ordinal loss for joint-level Sharp–van der Heijde scoring (bone erosion / joint-space narrowing) on RAM-H1200. Result tables and figures are in the paper; this repo is for retraining and for evaluating the released checkpoints.

Quickstart

git clone https://github.com/EIDOSLAB/OrdinalSvdH.git && cd OrdinalSvdH
pip install -r requirements.txt

export RAM_H1200_ROOT=/path/to/RAM-H1200-v1   # see "Dataset" below to get it

python scripts/download_checkpoints.py
python scripts/evaluate.py --checkpoint checkpoints/be_ordinal_bce_pct100_seed42
python scripts/evaluate.py --checkpoint checkpoints/jsn_ordinal_bce_pct100_seed123

Each evaluate.py call prints test QWK / accuracy / MAE / macro-F1 / severe-error rate, checks it against the value recorded in the checkpoint, and writes eval_metrics.json + predictions.csv next to it. Checkpoints (mcannito56/OrdinalSvdH) are the best-seed run per task (BE seed 42, QWK 0.626; JSN seed 123, QWK 0.640) — the paper's reported numbers are the mean over three seeds (BE 0.591 ± 0.028, JSN 0.634 ± 0.006); retrain the full grid below to reproduce those.

Dataset

RAM-H1200 images are not redistributed here. Only the scoring crops are needed (~1 GB):

huggingface-cli download TokyoTechMagicYang/RAM-H1200-v1 \
  --repo-type dataset --include "SvdH_Scoring/**" "Metadata.xlsx" \
  --local-dir /path/to/RAM-H1200-v1

The DINOv2 backbone downloads automatically from timm on first use.

Train

cd finetuning_ordinal
python finetune_be_ordinal.py --percentages 100 --seeds 42 --loss-modes ordinal_bce
python finetune_jsn_ordinal.py --percentages 100 --seeds 42 --loss-modes ordinal_bce

Full paper grid (25/50/100% labels × seeds 42/123/456 × ordinal_bce / ordinal_bce_weighted):

bash run_ordinal_experiments.sh full          # or "dry-run" for a 2-minute smoke test

Other configurations:

# cross-entropy baseline
python finetuning_ce/finetune_ce.py --task be_score --encoders dinov2_vits14 --percentages 100 --seed 42

# ablation: joint-type conditioning
cd finetuning_joint_embedding && bash run_joint_embedding_experiments.sh full

# ablation: BE-JSN multi-task on the shared joints
cd finetuning_multitask_be_jsn && bash run_experiments.sh full

Results land under results/<experiment>/dinov2_vits14/pct<NN>_seed<S>_<loss>/ (metrics.json, train_curve.csv, test_predictions.csv, best_model.pt). One BE run at 100% labels takes ~25 min on an A100. All scripts share --dataset-root, --splits-dir, --output-dir, --batch-size, --device; defaults come from config/paths.env.

Protocol

DINOv2 ViT-S/14, last 4 transformer blocks + head fine-tuned, input letterboxed to 518×518. AdamW (encoder lr 1e-5, head lr 1e-4, wd 1e-4), batch 32, flip p=0.5 + rotation ±5° p=0.3, max 30 epochs / patience 8, checkpoint selected on best validation QWK. Ordinal head: K−1 cumulative BCE thresholds decoded at 0.5. BE grades are {0,1,2,3,5} — grade 4 never occurs.

Layout

finetuning_ordinal/           main method (DINOv2 + cumulative ordinal BCE)
finetuning_ce/                cross-entropy baseline
finetuning_joint_embedding/   ablation: joint-type conditioning
finetuning_multitask_be_jsn/  ablation: BE-JSN multi-task learning
lib/                          preprocessing, ground-truth loading, partial ViT freeze
scripts/                      evaluate.py, download_checkpoints.py, compare_ordinal_ce.py
paper_splits/                 frozen label-fraction subsets used in the paper
config/paths.env              dataset / splits / cache paths

The training code is unmodified from what produced the paper's numbers.

Notes

  • Splits follow RAM-H1200's own train/val/test partition; paper_splits/ only subsamples the training set for the 25%/50% label fractions.
  • WristFM-v2 (in-domain SSL, paper Table 4) is not released — pretrained on private hospital data; it did not beat public DINOv2 on this task.
  • Pin timm to the version in requirements.txt — the DINOv2 model name and preprocessing have changed across releases.

License and citation

Code: MIT (see LICENSE). RAM-H1200 is distributed separately under its own terms.

@inproceedings{cannito2026ordinalsvdh,
  title     = {Ordinal {DINOv2} Transfer for Joint-Level {SvdH} Scoring: What Helps and What Does Not},
  author    = {Cannito, Michele and Renzulli, Riccardo and Grangetto, Marco},
  booktitle = {RHEUMAI Workshop, MICCAI},
  year      = {2026}
}

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages