Remove non-research HF push helper
Browse files- code/push_to_hf.py +0 -611
code/push_to_hf.py
DELETED
|
@@ -1,611 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python
|
| 2 |
-
"""Push complete fundus benchmark to HuggingFace in academic-research format.
|
| 3 |
-
|
| 4 |
-
Creates two repos under user DoB24:
|
| 5 |
-
- DoB24/fundus-10class-augmented (dataset)
|
| 6 |
-
- DoB24/fundus-9model-benchmark (model)
|
| 7 |
-
|
| 8 |
-
Uploads: all 9 fine-tuned weights, results JSON, ensemble report, split
|
| 9 |
-
manifest, dataset (augmented), comprehensive academic-format README with
|
| 10 |
-
methods, results, statistics, citations.
|
| 11 |
-
"""
|
| 12 |
-
from __future__ import annotations
|
| 13 |
-
|
| 14 |
-
import json
|
| 15 |
-
import os
|
| 16 |
-
import shutil
|
| 17 |
-
from pathlib import Path
|
| 18 |
-
|
| 19 |
-
from huggingface_hub import HfApi, create_repo
|
| 20 |
-
|
| 21 |
-
TOKEN = os.environ["HF_TOKEN"]
|
| 22 |
-
USER = "DoB24"
|
| 23 |
-
MODEL_REPO = f"{USER}/fundus-9model-benchmark"
|
| 24 |
-
DATASET_REPO = f"{USER}/fundus-10class-augmented"
|
| 25 |
-
|
| 26 |
-
ROOT = Path("/home/bytical/fundus_project")
|
| 27 |
-
STAGE = ROOT / "_hf_stage"
|
| 28 |
-
STAGE_MODEL = STAGE / "model_repo"
|
| 29 |
-
STAGE_DATA = STAGE / "dataset_repo"
|
| 30 |
-
|
| 31 |
-
api = HfApi(token=TOKEN)
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
# ---------------------------------------------------------------------------
|
| 35 |
-
# 1. Load all results
|
| 36 |
-
# ---------------------------------------------------------------------------
|
| 37 |
-
|
| 38 |
-
results_dir = ROOT / "final_experiments_all"
|
| 39 |
-
report = json.loads((results_dir / "ensemble_report.json").read_text())
|
| 40 |
-
|
| 41 |
-
per_model = report["per_model"]
|
| 42 |
-
ensemble = report.get("ensemble", {})
|
| 43 |
-
mcnemar = report.get("mcnemar_bonferroni", {})
|
| 44 |
-
conformal = report.get("conformal", {})
|
| 45 |
-
|
| 46 |
-
# Order models by accuracy (desc)
|
| 47 |
-
model_order = sorted(per_model.keys(), key=lambda k: -per_model[k]["acc"])
|
| 48 |
-
|
| 49 |
-
CLASSES = [
|
| 50 |
-
"Central Serous Chorioretinopathy",
|
| 51 |
-
"Diabetic Retinopathy",
|
| 52 |
-
"Disc Edema",
|
| 53 |
-
"Glaucoma",
|
| 54 |
-
"Healthy",
|
| 55 |
-
"Macular Scar",
|
| 56 |
-
"Myopia",
|
| 57 |
-
"Pterygium",
|
| 58 |
-
"Retinal Detachment",
|
| 59 |
-
"Retinitis Pigmentosa",
|
| 60 |
-
]
|
| 61 |
-
|
| 62 |
-
# Class counts (augmented dataset)
|
| 63 |
-
aug_dir = ROOT / "Database" / "Augmented_Dataset"
|
| 64 |
-
class_counts_aug = {}
|
| 65 |
-
if aug_dir.exists():
|
| 66 |
-
for c in sorted(aug_dir.iterdir()):
|
| 67 |
-
if c.is_dir():
|
| 68 |
-
class_counts_aug[c.name] = len(list(c.iterdir()))
|
| 69 |
-
|
| 70 |
-
orig_dir = ROOT / "Database" / "Original_Dataset"
|
| 71 |
-
class_counts_orig = {}
|
| 72 |
-
if orig_dir.exists():
|
| 73 |
-
for c in sorted(orig_dir.iterdir()):
|
| 74 |
-
if c.is_dir():
|
| 75 |
-
class_counts_orig[c.name] = len(list(c.iterdir()))
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
# ---------------------------------------------------------------------------
|
| 79 |
-
# 2. Build model-repo README (academic paper format)
|
| 80 |
-
# ---------------------------------------------------------------------------
|
| 81 |
-
|
| 82 |
-
def fmt_pct(x: float) -> str:
|
| 83 |
-
return f"{x * 100:.2f}"
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
def model_table_md() -> str:
|
| 87 |
-
lines = [
|
| 88 |
-
"| Rank | Model | Test Acc (%) | 95% CI | F1 (%) | Kappa | Brier | ROC-AUC |",
|
| 89 |
-
"|------|-------|--------------|--------|--------|-------|-------|---------|",
|
| 90 |
-
]
|
| 91 |
-
for i, m in enumerate(model_order, 1):
|
| 92 |
-
v = per_model[m]
|
| 93 |
-
ci = v.get("ci95", [0, 0])
|
| 94 |
-
roc = v.get("roc_auc", v.get("roc", 0))
|
| 95 |
-
lines.append(
|
| 96 |
-
f"| {i} | `{m}` | **{fmt_pct(v['acc'])}** | "
|
| 97 |
-
f"[{fmt_pct(ci[0])}, {fmt_pct(ci[1])}] | "
|
| 98 |
-
f"{fmt_pct(v['f1'])} | {v.get('kappa', 0):.3f} | "
|
| 99 |
-
f"{v.get('brier', 0):.3f} | {roc:.4f} |"
|
| 100 |
-
)
|
| 101 |
-
if ensemble:
|
| 102 |
-
ci = ensemble.get("ci95", [0, 0])
|
| 103 |
-
lines.append(
|
| 104 |
-
f"| — | **9-Model Ensemble** | **{fmt_pct(ensemble.get('acc', 0))}** | "
|
| 105 |
-
f"[{fmt_pct(ci[0])}, {fmt_pct(ci[1])}] | "
|
| 106 |
-
f"**{fmt_pct(ensemble.get('f1', 0))}** | — | — | "
|
| 107 |
-
f"**{ensemble.get('roc_auc', ensemble.get('roc', 0)):.4f}** |"
|
| 108 |
-
)
|
| 109 |
-
return "\n".join(lines)
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
def mcnemar_table_md() -> str:
|
| 113 |
-
pairs = mcnemar.get("pairs", [])
|
| 114 |
-
if not pairs:
|
| 115 |
-
return "_McNemar table unavailable._"
|
| 116 |
-
lines = [
|
| 117 |
-
f"Total pairs: {mcnemar.get('n_pairs', len(pairs))} | Bonferroni-corrected at α=0.05",
|
| 118 |
-
"",
|
| 119 |
-
"| Model A | Model B | b | c | Raw p | Adj. p (Bonf.) | Sig. |",
|
| 120 |
-
"|---------|---------|---|---|-------|----------------|------|",
|
| 121 |
-
]
|
| 122 |
-
for r in pairs:
|
| 123 |
-
sig = "**\\***" if r.get("sig_005") else " "
|
| 124 |
-
lines.append(
|
| 125 |
-
f"| `{r['model_a']}` | `{r['model_b']}` | "
|
| 126 |
-
f"{r['b_count']} | {r['c_count']} | "
|
| 127 |
-
f"{r['p']:.3g} | {r['p_bonferroni']:.3g} | {sig} |"
|
| 128 |
-
)
|
| 129 |
-
return "\n".join(lines)
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
def class_table_md() -> str:
|
| 133 |
-
lines = [
|
| 134 |
-
"| # | Class | Original | Augmented |",
|
| 135 |
-
"|---|-------|----------|-----------|",
|
| 136 |
-
]
|
| 137 |
-
keys = sorted(set(class_counts_orig) | set(class_counts_aug))
|
| 138 |
-
total_o = total_a = 0
|
| 139 |
-
for i, k in enumerate(keys, 1):
|
| 140 |
-
o = class_counts_orig.get(k, 0)
|
| 141 |
-
a = class_counts_aug.get(k, 0)
|
| 142 |
-
total_o += o
|
| 143 |
-
total_a += a
|
| 144 |
-
lines.append(f"| {i} | {k} | {o:,} | {a:,} |")
|
| 145 |
-
lines.append(f"| — | **Total** | **{total_o:,}** | **{total_a:,}** |")
|
| 146 |
-
return "\n".join(lines)
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
readme_model = f"""---
|
| 150 |
-
license: apache-2.0
|
| 151 |
-
library_name: pytorch
|
| 152 |
-
tags:
|
| 153 |
-
- medical-imaging
|
| 154 |
-
- ophthalmology
|
| 155 |
-
- fundus
|
| 156 |
-
- image-classification
|
| 157 |
-
- retinal-disease
|
| 158 |
-
- benchmark
|
| 159 |
-
- ensemble
|
| 160 |
-
- pytorch
|
| 161 |
-
datasets:
|
| 162 |
-
- {DATASET_REPO}
|
| 163 |
-
metrics:
|
| 164 |
-
- accuracy
|
| 165 |
-
- f1
|
| 166 |
-
- roc-auc
|
| 167 |
-
- cohen-kappa
|
| 168 |
-
- brier-score
|
| 169 |
-
pipeline_tag: image-classification
|
| 170 |
-
---
|
| 171 |
-
|
| 172 |
-
# Fundus Lesion Image Classification — 9-Model Comparative Benchmark
|
| 173 |
-
|
| 174 |
-
> **Companion artifact for the Master's thesis _"Classification of Fundus
|
| 175 |
-
> Lesion Images Using Deep Learning Models"_ (Xidian University, 2026), by
|
| 176 |
-
> Daryl Panashe Katiyo.**
|
| 177 |
-
>
|
| 178 |
-
> Reproducible weights, predictions, and full statistical analysis for nine
|
| 179 |
-
> deep-learning backbones evaluated on a 10-class colour-fundus dataset
|
| 180 |
-
> with a group-aware (perceptual-hash) test split.
|
| 181 |
-
|
| 182 |
-
---
|
| 183 |
-
|
| 184 |
-
## 1. Abstract
|
| 185 |
-
|
| 186 |
-
Automatic interpretation of colour fundus photographs is a foundational
|
| 187 |
-
task for screening prevalent blinding diseases such as diabetic
|
| 188 |
-
retinopathy, glaucoma and age-related macular degeneration. We
|
| 189 |
-
benchmark **nine deep-learning backbones** spanning four architectural
|
| 190 |
-
families — classical CNNs (VGG-19, ResNet-50, ResNet-101, DenseNet-121,
|
| 191 |
-
Inception-v3), vision-language pretraining (OpenAI CLIP ViT-B/16),
|
| 192 |
-
self-supervised vision transformers (DINOv2-L/14), hierarchical
|
| 193 |
-
transformers (Swin-B), and a domain-specific MAE pretraining
|
| 194 |
-
(RETFound MAE ViT-L/16) — on a 10-class fundus dataset of 16 242
|
| 195 |
-
augmented images. To suppress augmentation-induced label leakage we
|
| 196 |
-
construct a **group-aware (perceptual-hash) stratified split** and
|
| 197 |
-
report bootstrap 95% confidence intervals together with Bonferroni-
|
| 198 |
-
corrected McNemar tests and 90% Mondrian conformal sets.
|
| 199 |
-
|
| 200 |
-
**Headline result.** A DenseNet-121 trained with CLAHE preprocessing,
|
| 201 |
-
RandAugment, weighted sampling, MixUp + CutMix and 6-view test-time
|
| 202 |
-
augmentation reaches **{fmt_pct(per_model[model_order[0]]['acc'])}%
|
| 203 |
-
accuracy** (F1 = {fmt_pct(per_model[model_order[0]]['f1'])}%, κ =
|
| 204 |
-
{per_model[model_order[0]].get('kappa', 0):.3f}) on the held-out test
|
| 205 |
-
set. An F1-weighted soft-vote ensemble over all nine models attains
|
| 206 |
-
**ROC-AUC = {ensemble.get('roc_auc', ensemble.get('roc', 0)):.4f}**.
|
| 207 |
-
|
| 208 |
-
---
|
| 209 |
-
|
| 210 |
-
## 2. Motivation & Model Selection
|
| 211 |
-
|
| 212 |
-
Modern fundus screening pipelines are increasingly built on
|
| 213 |
-
pre-trained image backbones, but the question _"which backbone family
|
| 214 |
-
is best for fundus disease classification on a moderately-sized,
|
| 215 |
-
imbalanced dataset?"_ has no consensus answer. We deliberately chose
|
| 216 |
-
backbones that exercise four distinct **inductive biases / pretraining
|
| 217 |
-
regimes**:
|
| 218 |
-
|
| 219 |
-
| Family | Backbone(s) | Why we included it |
|
| 220 |
-
|--------|-------------|--------------------|
|
| 221 |
-
| Classical CNNs | VGG-19, ResNet-50, ResNet-101, DenseNet-121, Inception-v3 | Established baselines used in virtually all prior fundus benchmarks ([Gulshan 2016][1], [Ting 2017][2]). Locally-connected convolutions are well-suited to texture-dominant retinal pathology. |
|
| 222 |
-
| Vision-language (CLIP) | OpenAI CLIP ViT-B/16 | Tests whether 400 M-pair web-scale contrastive pretraining transfers to a tightly-constrained medical domain. |
|
| 223 |
-
| Self-supervised ViT | DINOv2-L/14 | State-of-the-art general-purpose features without language supervision ([Oquab 2024][3]); reportedly strong on dense prediction. |
|
| 224 |
-
| Hierarchical ViT | Swin-B | Adds hierarchy + shifted windows; competitive on ImageNet at lower compute than ViT-L ([Liu 2021][4]). |
|
| 225 |
-
| Domain MAE | RETFound MAE ViT-L/16 | Pretrained on **1.6 M colour fundus images** ([Zhou 2023, Nature][5]); the strongest published prior on this exact modality, so essential to compare against. |
|
| 226 |
-
|
| 227 |
-
This grid lets us isolate three confounders: (i) **scale**
|
| 228 |
-
(ResNet-50 vs ResNet-101, ViT-B vs ViT-L); (ii) **modality of
|
| 229 |
-
pretraining** (ImageNet supervised vs CLIP language-supervised vs
|
| 230 |
-
DINOv2 self-supervised vs RETFound domain-MAE); and (iii)
|
| 231 |
-
**architecture class** (CNN vs ViT vs hierarchical).
|
| 232 |
-
|
| 233 |
-
---
|
| 234 |
-
|
| 235 |
-
## 3. Dataset
|
| 236 |
-
|
| 237 |
-
- **Source.** Fundus-image set of [Mendeley Data][6] (10 classes; 5 335 original images).
|
| 238 |
-
- **Augmentation.** Class-balancing augmentation expanded the training pool to
|
| 239 |
-
16 242 images (rotation, horizontal flip, brightness/contrast jitter,
|
| 240 |
-
Gaussian blur). Augmented images carry the same diagnostic label as
|
| 241 |
-
their source image.
|
| 242 |
-
- **Companion dataset on the Hub:** [{DATASET_REPO}](https://huggingface.co/datasets/{DATASET_REPO}).
|
| 243 |
-
|
| 244 |
-
### Class distribution
|
| 245 |
-
|
| 246 |
-
{class_table_md()}
|
| 247 |
-
|
| 248 |
-
### Group-aware splitting (data-leakage prevention)
|
| 249 |
-
|
| 250 |
-
Because the augmented set contains visually-near-duplicate copies of
|
| 251 |
-
each original image, a naïve `train_test_split` over the augmented
|
| 252 |
-
pool would let the model memorise patient-level identities. We
|
| 253 |
-
therefore:
|
| 254 |
-
|
| 255 |
-
1. Compute a 64-bit perceptual hash (`pHash`) on every image (original + augmented).
|
| 256 |
-
2. Link each augmented image to its nearest original at Hamming distance ≤ 8 → defines a `group_id`.
|
| 257 |
-
3. Run scikit-learn `StratifiedGroupKFold` (k = 5, only fold-0 used here) so that **all augmented children of a given original sit in exactly one split**.
|
| 258 |
-
|
| 259 |
-
The final splits are 15 068 train / 3 301 val / 3 208 test. All metrics
|
| 260 |
-
reported below are on the held-out test split. The exact manifest
|
| 261 |
-
(`holdout_split_augmented.json`, 3.2 MB) is included in this repo.
|
| 262 |
-
|
| 263 |
-
---
|
| 264 |
-
|
| 265 |
-
## 4. Training Protocol (CNN backbones)
|
| 266 |
-
|
| 267 |
-
| Hyper-parameter | Value |
|
| 268 |
-
|-----------------|-------|
|
| 269 |
-
| Optimizer | AdamW (β=0.9/0.999, weight-decay 1e-4) |
|
| 270 |
-
| Initial LR | 2e-4 (head LR 1e-3 for the foundation models, body LR 1e-5 for full-FT) |
|
| 271 |
-
| Schedule | 3-epoch linear warm-up + cosine decay |
|
| 272 |
-
| Epochs | up to 60 (CNNs/CLIP), 20 LP + 15 FT (DINOv2/Swin/RETFound) |
|
| 273 |
-
| Early stopping | patience = 12 (CNNs), 8 (FMs), on val F1 |
|
| 274 |
-
| Batch size | 32 (CNNs), 24 (foundation models) |
|
| 275 |
-
| Image size | 224 × 224 (Inception-v3 = 299) |
|
| 276 |
-
| Preprocessing | CLAHE on LAB L-channel → RandAugment (n=2, m=9) → ImageNet normalisation |
|
| 277 |
-
| Imbalance handling | `WeightedRandomSampler` with weights ∝ 1 / class_count |
|
| 278 |
-
| Regularisation | MixUp (α=0.2) + CutMix (α=1.0), applied with p=0.7 |
|
| 279 |
-
| Mixed precision | `torch.amp.autocast('cuda')` + `GradScaler` |
|
| 280 |
-
| Test-time aug | 6 views (centre + 4 corners + horizontal flip), soft-vote |
|
| 281 |
-
| Backend | PyTorch 2.11 + CUDA 12.8, 1 × NVIDIA Tesla T4 (16 GB) |
|
| 282 |
-
|
| 283 |
-
For DINOv2-L, Swin-B and RETFound we use a **two-stage** schedule:
|
| 284 |
-
linear-probe (head only) for 20 epochs at LR 1e-3, then full
|
| 285 |
-
fine-tuning for 15 epochs at backbone-LR 1e-5 / head-LR 1e-4.
|
| 286 |
-
|
| 287 |
-
---
|
| 288 |
-
|
| 289 |
-
## 5. Results
|
| 290 |
-
|
| 291 |
-
### 5.1 Headline accuracy
|
| 292 |
-
|
| 293 |
-
{model_table_md()}
|
| 294 |
-
|
| 295 |
-
CI columns are non-parametric percentile bootstrap (n = 1 000 resamples)
|
| 296 |
-
on the test set.
|
| 297 |
-
|
| 298 |
-
### 5.2 Pairwise statistical significance — McNemar with Bonferroni
|
| 299 |
-
|
| 300 |
-
We compare every pair of models on per-sample errors. With 9 models
|
| 301 |
-
that is 36 pairs (subset shown below; full table in
|
| 302 |
-
`ensemble_report.json` → `mcnemar_bonferroni`):
|
| 303 |
-
|
| 304 |
-
{mcnemar_table_md()}
|
| 305 |
-
|
| 306 |
-
A `*` indicates Bonferroni-corrected significance at α=0.05.
|
| 307 |
-
The cluster of CNN models (VGG-19, ResNet-50/101, DenseNet-121,
|
| 308 |
-
Inception-v3) is **statistically indistinguishable** from each other
|
| 309 |
-
and from DINOv2-L; CLIP, Swin-B and RETFound all separate
|
| 310 |
-
significantly (worse) from this top cluster.
|
| 311 |
-
|
| 312 |
-
### 5.3 Conformal sets (90% coverage, Mondrian per class)
|
| 313 |
-
|
| 314 |
-
Conformal-prediction sets were computed per class on the validation
|
| 315 |
-
split and applied to the test split (Mondrian variant; see
|
| 316 |
-
`ensemble_report.json` → `conformal`). Average set size for the
|
| 317 |
-
ensemble is < 1.5 at α = 0.1 for most classes, indicating well-
|
| 318 |
-
calibrated predictive intervals.
|
| 319 |
-
|
| 320 |
-
### 5.4 Take-aways
|
| 321 |
-
|
| 322 |
-
1. **DenseNet-121 wins on raw accuracy** but is statistically tied with VGG-19, ResNet-101, Inception-v3, ResNet-50 and **DINOv2-L** (adj. p ≥ 0.66 on all such pairs).
|
| 323 |
-
2. **DINOv2-L is the best transformer** (89.50%), confirming general-purpose self-supervised features now match domain CNNs on fundus.
|
| 324 |
-
3. **RETFound under-performs** (83.88%) on this benchmark. Its linear-probe stage saturates at ≈ 47% val-acc, suggesting that 15 epochs of full fine-tuning at LR 1e-5 are insufficient to recover the highly-specialised MAE representation under our augmentation regime. A longer FT schedule (e.g. 50 epochs with discriminative LRs) is a likely fix and is left as future work.
|
| 325 |
-
4. **The 9-model ensemble does not dominate** the best individual model on accuracy because the CNN cluster is heavily correlated — but its **ROC-AUC of {ensemble.get('roc_auc', ensemble.get('roc', 0)):.4f}** is the highest of any reported system, useful for thresholded screening deployment.
|
| 326 |
-
|
| 327 |
-
---
|
| 328 |
-
|
| 329 |
-
## 6. Reproducibility
|
| 330 |
-
|
| 331 |
-
All training scripts, evaluation utilities and the launch orchestrator
|
| 332 |
-
are in [`comparison_experiment/`](./comparison_experiment) (mirrored
|
| 333 |
-
from the [GitHub repo](https://github.com/) — please open an Issue if
|
| 334 |
-
you need access to the private mirror).
|
| 335 |
-
|
| 336 |
-
### Quick start (PyTorch ≥ 2.6)
|
| 337 |
-
|
| 338 |
-
```python
|
| 339 |
-
import torch, timm
|
| 340 |
-
from huggingface_hub import hf_hub_download
|
| 341 |
-
|
| 342 |
-
ckpt = hf_hub_download("{MODEL_REPO}", "weights/densenet121_v2_final.pth")
|
| 343 |
-
model = timm.create_model("densenet121", num_classes=10)
|
| 344 |
-
state = torch.load(ckpt, map_location="cpu", weights_only=False)
|
| 345 |
-
model.load_state_dict(state["model"] if "model" in state else state)
|
| 346 |
-
model.eval()
|
| 347 |
-
```
|
| 348 |
-
|
| 349 |
-
For RETFound and DINOv2-L the same pattern works; the backbones must
|
| 350 |
-
first be created via `torch.hub.load('facebookresearch/dinov2', 'dinov2_vitl14')`
|
| 351 |
-
and `timm.create_model('vit_large_patch16_224', pretrained=False)` respectively.
|
| 352 |
-
|
| 353 |
-
---
|
| 354 |
-
|
| 355 |
-
## 7. Files in this repository
|
| 356 |
-
|
| 357 |
-
| Path | Description |
|
| 358 |
-
|------|-------------|
|
| 359 |
-
| `weights/<model>_v2_final.pth` (×9) | Final fine-tuned weights, dict with keys `model`/`optimizer`/`epoch` |
|
| 360 |
-
| `results/<model>_test.json` (×9) | Per-model test metrics (acc, F1, κ, Brier, ROC-AUC, per-class) |
|
| 361 |
-
| `results/<model>_test_preds.json` (×9) | Per-sample test predictions & soft probabilities |
|
| 362 |
-
| `results/ensemble_report.json` | Combined per-model + ensemble + McNemar + conformal report |
|
| 363 |
-
| `splits/holdout_split_augmented.json` | Full pHash-grouped 5-fold manifest (3.2 MB) |
|
| 364 |
-
| `code/` | Training, evaluation and ensemble scripts (frozen snapshot) |
|
| 365 |
-
|
| 366 |
-
---
|
| 367 |
-
|
| 368 |
-
## 8. Citation
|
| 369 |
-
|
| 370 |
-
```bibtex
|
| 371 |
-
@mastersthesis{{katiyo2026fundus,
|
| 372 |
-
author = {{Katiyo, Daryl Panashe}},
|
| 373 |
-
title = {{Classification of Fundus Lesion Images Using Deep Learning Models}},
|
| 374 |
-
school = {{Xidian University}},
|
| 375 |
-
year = {{2026}},
|
| 376 |
-
note = {{Companion artifact: \\url{{https://huggingface.co/{MODEL_REPO}}}}}
|
| 377 |
-
}}
|
| 378 |
-
```
|
| 379 |
-
|
| 380 |
-
If you use the augmented data split, please also cite the source dataset:
|
| 381 |
-
|
| 382 |
-
```bibtex
|
| 383 |
-
@dataset{{nayan2023fundus,
|
| 384 |
-
author = {{Nayan, Asma U. and Saha, Sajib K. et al.}},
|
| 385 |
-
title = {{A Curated Dataset of Retinal Fundus Images for Disease Classification}},
|
| 386 |
-
year = {{2023}},
|
| 387 |
-
doi = {{10.17632/s9bfhswzjb.1}},
|
| 388 |
-
url = {{https://data.mendeley.com/datasets/s9bfhswzjb/1}}
|
| 389 |
-
}}
|
| 390 |
-
```
|
| 391 |
-
|
| 392 |
-
---
|
| 393 |
-
|
| 394 |
-
## 9. References
|
| 395 |
-
|
| 396 |
-
[1]: https://doi.org/10.1001/jama.2016.17216
|
| 397 |
-
[2]: https://doi.org/10.1001/jama.2017.18152
|
| 398 |
-
[3]: https://arxiv.org/abs/2304.07193
|
| 399 |
-
[4]: https://arxiv.org/abs/2103.14030
|
| 400 |
-
[5]: https://www.nature.com/articles/s41586-023-06555-x
|
| 401 |
-
[6]: https://data.mendeley.com/datasets/s9bfhswzjb/1
|
| 402 |
-
|
| 403 |
-
1. **Gulshan V., Peng L., et al.** "Development and Validation of a Deep Learning Algorithm for Detection of Diabetic Retinopathy in Retinal Fundus Photographs." *JAMA* 316.22 (2016): 2402-2410.
|
| 404 |
-
2. **Ting D.S.W., Cheung C.Y., et al.** "Development and Validation of a Deep Learning System for Diabetic Retinopathy and Related Eye Diseases Using Retinal Images From Multiethnic Populations With Diabetes." *JAMA* 318.22 (2017): 2211-2223.
|
| 405 |
-
3. **Oquab M., Darcet T., et al.** "DINOv2: Learning Robust Visual Features without Supervision." arXiv:2304.07193 (2023).
|
| 406 |
-
4. **Liu Z., Lin Y., et al.** "Swin Transformer: Hierarchical Vision Transformer using Shifted Windows." ICCV 2021.
|
| 407 |
-
5. **Zhou Y., Chia M.A., et al.** "A foundation model for generalizable disease detection from retinal images." *Nature* 622 (2023): 156-163.
|
| 408 |
-
6. **He K., et al.** "Deep Residual Learning for Image Recognition." CVPR 2016.
|
| 409 |
-
7. **Simonyan K., Zisserman A.** "Very Deep Convolutional Networks for Large-Scale Image Recognition." ICLR 2015.
|
| 410 |
-
8. **Huang G., et al.** "Densely Connected Convolutional Networks." CVPR 2017.
|
| 411 |
-
9. **Szegedy C., et al.** "Rethinking the Inception Architecture for Computer Vision." CVPR 2016.
|
| 412 |
-
10. **Radford A., et al.** "Learning Transferable Visual Models From Natural Language Supervision." ICML 2021.
|
| 413 |
-
11. **Zhang H., et al.** "mixup: Beyond Empirical Risk Minimization." ICLR 2018.
|
| 414 |
-
12. **Yun S., et al.** "CutMix: Regularization Strategy to Train Strong Classifiers." ICCV 2019.
|
| 415 |
-
13. **Cubuk E.D., et al.** "RandAugment: Practical Automated Data Augmentation." NeurIPS 2020.
|
| 416 |
-
14. **Vovk V., Gammerman A., Shafer G.** "Algorithmic Learning in a Random World." Springer, 2005. *(Conformal prediction)*
|
| 417 |
-
15. **Bonferroni C.E.** "Teoria statistica delle classi e calcolo delle probabilità." 1936.
|
| 418 |
-
|
| 419 |
-
---
|
| 420 |
-
|
| 421 |
-
## 10. License & contact
|
| 422 |
-
|
| 423 |
-
Apache-2.0 for code and weights. Original Mendeley dataset retains its
|
| 424 |
-
own licence (CC BY 4.0).
|
| 425 |
-
|
| 426 |
-
Questions / collaboration: open an issue on the Hub repo.
|
| 427 |
-
"""
|
| 428 |
-
|
| 429 |
-
# ---------------------------------------------------------------------------
|
| 430 |
-
# 3. Build dataset-repo README
|
| 431 |
-
# ---------------------------------------------------------------------------
|
| 432 |
-
|
| 433 |
-
readme_dataset = f"""---
|
| 434 |
-
license: cc-by-4.0
|
| 435 |
-
task_categories:
|
| 436 |
-
- image-classification
|
| 437 |
-
language:
|
| 438 |
-
- en
|
| 439 |
-
tags:
|
| 440 |
-
- medical-imaging
|
| 441 |
-
- ophthalmology
|
| 442 |
-
- fundus
|
| 443 |
-
- retinal-disease
|
| 444 |
-
size_categories:
|
| 445 |
-
- 10K<n<100K
|
| 446 |
-
---
|
| 447 |
-
|
| 448 |
-
# Fundus 10-Class Augmented Dataset
|
| 449 |
-
|
| 450 |
-
> Augmented and split-curated version of the [Mendeley fundus
|
| 451 |
-
> dataset](https://data.mendeley.com/datasets/s9bfhswzjb/1), used in
|
| 452 |
-
> the Master's thesis _"Classification of Fundus Lesion Images Using
|
| 453 |
-
> Deep Learning Models"_ (Xidian University, 2026).
|
| 454 |
-
>
|
| 455 |
-
> Models trained on this dataset: [{MODEL_REPO}](https://huggingface.co/{MODEL_REPO}).
|
| 456 |
-
|
| 457 |
-
## Summary
|
| 458 |
-
|
| 459 |
-
- **10 disease classes** (see table below).
|
| 460 |
-
- **{sum(class_counts_orig.values()):,} original** images; **{sum(class_counts_aug.values()):,} augmented** images (rotation, flip, brightness/contrast jitter, Gaussian blur).
|
| 461 |
-
- **Group-aware test split** (`splits/holdout_split_augmented.json`): augmented children of every original image are confined to a single split, eliminating intra-patient leakage.
|
| 462 |
-
- Image size: variable (resized to 224×224 / 299×299 during training).
|
| 463 |
-
|
| 464 |
-
## Class distribution
|
| 465 |
-
|
| 466 |
-
{class_table_md()}
|
| 467 |
-
|
| 468 |
-
## File layout
|
| 469 |
-
|
| 470 |
-
```
|
| 471 |
-
images/augmented/<class_name>/<file>.jpg # 16K augmented imgs
|
| 472 |
-
images/original/<class_name>/<file>.jpg # 5K original imgs
|
| 473 |
-
splits/holdout_split_augmented.json # pHash-grouped 5-fold manifest
|
| 474 |
-
```
|
| 475 |
-
|
| 476 |
-
## Group-aware splitting
|
| 477 |
-
|
| 478 |
-
We computed a 64-bit perceptual hash (`pHash`) per image and linked
|
| 479 |
-
each augmented image to its nearest original at Hamming distance ≤ 8
|
| 480 |
-
to define a `group_id`. A `StratifiedGroupKFold` (k=5, fold-0
|
| 481 |
-
reported) produced 15 068 train / 3 301 val / 3 208 test images.
|
| 482 |
-
|
| 483 |
-
## Loading
|
| 484 |
-
|
| 485 |
-
```python
|
| 486 |
-
from datasets import load_dataset
|
| 487 |
-
ds = load_dataset("{DATASET_REPO}")
|
| 488 |
-
```
|
| 489 |
-
|
| 490 |
-
Or download manually:
|
| 491 |
-
|
| 492 |
-
```python
|
| 493 |
-
from huggingface_hub import snapshot_download
|
| 494 |
-
snapshot_download("{DATASET_REPO}", repo_type="dataset", local_dir="./fundus_data")
|
| 495 |
-
```
|
| 496 |
-
|
| 497 |
-
## Source & license
|
| 498 |
-
|
| 499 |
-
Original images: [Mendeley dataset DOI 10.17632/s9bfhswzjb.1](https://data.mendeley.com/datasets/s9bfhswzjb/1) — **CC BY 4.0**.
|
| 500 |
-
Augmented derivatives inherit CC BY 4.0.
|
| 501 |
-
|
| 502 |
-
## Citation
|
| 503 |
-
|
| 504 |
-
```bibtex
|
| 505 |
-
@dataset{{nayan2023fundus,
|
| 506 |
-
title = {{A Curated Dataset of Retinal Fundus Images for Disease Classification}},
|
| 507 |
-
year = {{2023}},
|
| 508 |
-
doi = {{10.17632/s9bfhswzjb.1}},
|
| 509 |
-
url = {{https://data.mendeley.com/datasets/s9bfhswzjb/1}}
|
| 510 |
-
}}
|
| 511 |
-
```
|
| 512 |
-
"""
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
# ---------------------------------------------------------------------------
|
| 516 |
-
# 4. Stage files
|
| 517 |
-
# ---------------------------------------------------------------------------
|
| 518 |
-
|
| 519 |
-
print("[stage] cleaning stage dir")
|
| 520 |
-
if STAGE.exists():
|
| 521 |
-
shutil.rmtree(STAGE)
|
| 522 |
-
STAGE_MODEL.mkdir(parents=True)
|
| 523 |
-
STAGE_DATA.mkdir(parents=True)
|
| 524 |
-
|
| 525 |
-
# --- model repo staging ---
|
| 526 |
-
(STAGE_MODEL / "weights").mkdir()
|
| 527 |
-
(STAGE_MODEL / "results").mkdir()
|
| 528 |
-
(STAGE_MODEL / "splits").mkdir()
|
| 529 |
-
(STAGE_MODEL / "code").mkdir()
|
| 530 |
-
|
| 531 |
-
for src in (ROOT / "weights_v2").glob("*.pth"):
|
| 532 |
-
print(f"[stage] copy weight {src.name}")
|
| 533 |
-
shutil.copy(src, STAGE_MODEL / "weights" / src.name)
|
| 534 |
-
for src in (ROOT / "weights_v3").glob("*.pth"):
|
| 535 |
-
print(f"[stage] copy weight {src.name}")
|
| 536 |
-
shutil.copy(src, STAGE_MODEL / "weights" / src.name)
|
| 537 |
-
|
| 538 |
-
for src in (ROOT / "final_experiments_all").iterdir():
|
| 539 |
-
if src.is_file():
|
| 540 |
-
shutil.copy(src, STAGE_MODEL / "results" / src.name)
|
| 541 |
-
|
| 542 |
-
split_src = ROOT / "holdout_split_augmented.json"
|
| 543 |
-
if split_src.exists():
|
| 544 |
-
shutil.copy(split_src, STAGE_MODEL / "splits" / split_src.name)
|
| 545 |
-
|
| 546 |
-
code_src = ROOT / "comparison_experiment"
|
| 547 |
-
if code_src.exists():
|
| 548 |
-
for f in code_src.iterdir():
|
| 549 |
-
if f.is_file() and f.suffix in (".py", ".sh"):
|
| 550 |
-
shutil.copy(f, STAGE_MODEL / "code" / f.name)
|
| 551 |
-
|
| 552 |
-
(STAGE_MODEL / "README.md").write_text(readme_model)
|
| 553 |
-
|
| 554 |
-
# --- dataset repo staging (symlinks to save copy time / disk) ---
|
| 555 |
-
(STAGE_DATA / "images" / "augmented").mkdir(parents=True)
|
| 556 |
-
(STAGE_DATA / "images" / "original").mkdir(parents=True)
|
| 557 |
-
(STAGE_DATA / "splits").mkdir()
|
| 558 |
-
|
| 559 |
-
if aug_dir.exists():
|
| 560 |
-
for c in aug_dir.iterdir():
|
| 561 |
-
if c.is_dir():
|
| 562 |
-
target = STAGE_DATA / "images" / "augmented" / c.name
|
| 563 |
-
if not target.exists():
|
| 564 |
-
target.symlink_to(c.resolve())
|
| 565 |
-
|
| 566 |
-
if orig_dir.exists():
|
| 567 |
-
for c in orig_dir.iterdir():
|
| 568 |
-
if c.is_dir():
|
| 569 |
-
target = STAGE_DATA / "images" / "original" / c.name
|
| 570 |
-
if not target.exists():
|
| 571 |
-
target.symlink_to(c.resolve())
|
| 572 |
-
|
| 573 |
-
if split_src.exists():
|
| 574 |
-
shutil.copy(split_src, STAGE_DATA / "splits" / split_src.name)
|
| 575 |
-
|
| 576 |
-
(STAGE_DATA / "README.md").write_text(readme_dataset)
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
# ---------------------------------------------------------------------------
|
| 580 |
-
# 5. Create repos
|
| 581 |
-
# ---------------------------------------------------------------------------
|
| 582 |
-
|
| 583 |
-
print(f"[hf] create_repo {MODEL_REPO}")
|
| 584 |
-
create_repo(MODEL_REPO, repo_type="model", exist_ok=True, token=TOKEN)
|
| 585 |
-
print(f"[hf] create_repo {DATASET_REPO}")
|
| 586 |
-
create_repo(DATASET_REPO, repo_type="dataset", exist_ok=True, token=TOKEN)
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
# ---------------------------------------------------------------------------
|
| 590 |
-
# 6. Upload
|
| 591 |
-
# ---------------------------------------------------------------------------
|
| 592 |
-
|
| 593 |
-
print(f"[hf] upload model repo from {STAGE_MODEL}")
|
| 594 |
-
api.upload_folder(
|
| 595 |
-
folder_path=str(STAGE_MODEL),
|
| 596 |
-
repo_id=MODEL_REPO,
|
| 597 |
-
repo_type="model",
|
| 598 |
-
commit_message="Add 9-model fundus benchmark: weights + results + splits + code + README",
|
| 599 |
-
)
|
| 600 |
-
|
| 601 |
-
print(f"[hf] upload dataset repo from {STAGE_DATA}")
|
| 602 |
-
api.upload_folder(
|
| 603 |
-
folder_path=str(STAGE_DATA),
|
| 604 |
-
repo_id=DATASET_REPO,
|
| 605 |
-
repo_type="dataset",
|
| 606 |
-
commit_message="Add fundus 10-class augmented dataset + pHash-grouped split",
|
| 607 |
-
)
|
| 608 |
-
|
| 609 |
-
print("\n=== DONE ===")
|
| 610 |
-
print(f"Model: https://huggingface.co/{MODEL_REPO}")
|
| 611 |
-
print(f"Dataset: https://huggingface.co/datasets/{DATASET_REPO}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|