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.
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_seed123Each 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.
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-v1The DINOv2 backbone downloads automatically from timm on first use.
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_bceFull 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 testOther 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 fullResults 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.
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.
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.
- 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
timmto the version inrequirements.txt— the DINOv2 model name and preprocessing have changed across releases.
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}
}