Spaces:
Runtime error
Runtime error
| """Pull healthy 2D slices from radiata-ai/brain-structure into dataset_v8. | |
| Phase 1 (continued) of the v9c data-expansion plan. The radiata-ai dataset | |
| aggregates 5 public studies (DLBS / IXI / NKI-RS / OASIS-1 / OASIS-2) as | |
| T1-weighted MPRAGE NIfTI scans, all skull-stripped and MNI152-registered | |
| at 113×137×113 / 1.5mm³. This script: | |
| 1. Reads metadata.csv to pick a stratified sample of cognitively-normal | |
| subjects across sources (skipping IXI since iamkzntsv/IXI2D already | |
| covers it at 2D-slice resolution). | |
| 2. Downloads each subject's NIfTI individually via hf_hub_download | |
| (~2 MB each, cached by HuggingFace under ~/.cache/huggingface). | |
| 3. Extracts 5 mid-axial slices per scan, normalises with per-volume | |
| 1-99 percentile windowing, and writes to dataset_v8/train/{images, | |
| masks} as 3-channel PNG + zero mask. | |
| Default: 200 subjects each from NKI-RS / OASIS-1 / OASIS-2 / DLBS = 800 | |
| subjects × 5 slices = 4000 new healthy slices. | |
| CLI: | |
| python scripts/fetch_radiata_for_training.py [--per_study N] | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import csv | |
| import io | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import cv2 | |
| import nibabel as nib | |
| import numpy as np | |
| ROOT = Path(__file__).resolve().parent.parent | |
| DATASET = ROOT / 'dataset_v8' | |
| # Source studies to include. IXI excluded (already covered by IXI2D fetcher). | |
| # Order also drives the stratification: deeper/larger sources first so a | |
| # small per_study N still includes the well-curated DLBS/OASIS data. | |
| TARGET_STUDIES = ['NKI-RS', 'OASIS-1', 'OASIS-2', 'DLBS'] | |
| # Axial slice indices in MNI 113-z space. Mid-brain region ~ z=40-80. | |
| AXIAL_SLICE_INDICES = [45, 55, 65, 75, 85] | |
| def _norm_uint8(slice2d: np.ndarray) -> np.ndarray: | |
| arr = slice2d.astype(np.float32) | |
| nz = arr[arr > 0] | |
| if nz.size == 0: | |
| return arr.astype(np.uint8) | |
| lo, hi = np.percentile(nz, (1, 99)) | |
| arr = np.clip((arr - lo) / max(hi - lo, 1e-6), 0, 1) * 255 | |
| return arr.astype(np.uint8) | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument('--per_study', type=int, default=200, | |
| help='Subjects to sample per study (default 200)') | |
| ap.add_argument('--limit_total', type=int, default=10_000, | |
| help='Hard cap on total slices to add (default 10000)') | |
| ap.add_argument('--val_fraction', type=float, default=0.10) | |
| args = ap.parse_args() | |
| try: | |
| from huggingface_hub import hf_hub_download | |
| except ImportError: | |
| sys.exit('pip install huggingface_hub') | |
| print('[1/4] fetching metadata.csv ...') | |
| mp = hf_hub_download(repo_id='radiata-ai/brain-structure', | |
| filename='metadata.csv', repo_type='dataset') | |
| with open(mp, encoding='utf-8') as f: | |
| rows = list(csv.DictReader(f)) | |
| print(f' {len(rows)} subjects in metadata') | |
| # Filter to healthy + in our target studies | |
| healthy = [r for r in rows | |
| if r['clinical_diagnosis'] == 'cognitively_normal' | |
| and r['study'] in TARGET_STUDIES] | |
| print(f' {len(healthy)} healthy subjects in target studies') | |
| # Stratified sample | |
| selected: list[dict] = [] | |
| for study in TARGET_STUDIES: | |
| pool = [r for r in healthy if r['study'] == study] | |
| pool = sorted(pool, key=lambda r: r['radiata_id']) # deterministic | |
| selected.extend(pool[:args.per_study]) | |
| print(f'[2/4] selected {len(selected)} subjects ({args.per_study} per study)') | |
| for split in ('train', 'val'): | |
| (DATASET / split / 'images').mkdir(parents=True, exist_ok=True) | |
| (DATASET / split / 'masks').mkdir(parents=True, exist_ok=True) | |
| n_added = n_failed = n_skipped = 0 | |
| t0 = time.perf_counter() | |
| last_print = t0 | |
| for i, sub in enumerate(selected): | |
| if n_added >= args.limit_total: | |
| print(f' limit_total={args.limit_total} hit; stopping') | |
| break | |
| try: | |
| nii_path = hf_hub_download(repo_id='radiata-ai/brain-structure', | |
| filename=sub['t1_local_path'], | |
| repo_type='dataset') | |
| vol = nib.load(nii_path).get_fdata() | |
| # vol shape ~ (113, 137, 113); axial = axis 2 | |
| for sl_idx in AXIAL_SLICE_INDICES: | |
| if sl_idx >= vol.shape[2]: | |
| continue | |
| sl = vol[:, :, sl_idx] | |
| if (sl > 0).sum() < 200: # skip near-empty edge slices | |
| continue | |
| img_u8 = _norm_uint8(sl) | |
| img_resized = cv2.resize(img_u8, (192, 192), | |
| interpolation=cv2.INTER_LINEAR) | |
| img_rgb = np.stack([img_resized] * 3, axis=-1) | |
| split = ('val' if (n_added % int(1/args.val_fraction) == 0) | |
| else 'train') | |
| fname = (f'radiata_{sub["study"]}_' | |
| f'sub{sub["participant_id"]}_z{sl_idx:03d}.png') | |
| img_out = DATASET / split / 'images' / fname | |
| mask_out = DATASET / split / 'masks' / fname | |
| if img_out.exists() and mask_out.exists(): | |
| n_skipped += 1 | |
| continue | |
| cv2.imwrite(str(img_out), | |
| cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)) | |
| cv2.imwrite(str(mask_out), | |
| np.zeros((192, 192), dtype=np.uint8)) | |
| n_added += 1 | |
| except Exception as exc: | |
| n_failed += 1 | |
| if n_failed <= 3: | |
| print(f' [fail] {sub["t1_local_path"]}: ' | |
| f'{type(exc).__name__}: {str(exc)[:100]}') | |
| if time.perf_counter() - last_print > 30: | |
| last_print = time.perf_counter() | |
| print(f' [{i+1}/{len(selected)}] added={n_added} ' | |
| f'failed={n_failed} elapsed={time.perf_counter()-t0:.0f}s') | |
| elapsed = time.perf_counter() - t0 | |
| print(f'\n[3/4] done in {elapsed/60:.1f} min: ' | |
| f'added={n_added} skipped_existing={n_skipped} failed={n_failed}') | |
| print('\n[4/4] new dataset_v8 source coverage:') | |
| for split in ('train', 'val'): | |
| img_dir = DATASET / split / 'images' | |
| radiata = sum(1 for _ in img_dir.glob('radiata_*.png')) | |
| ixi = sum(1 for _ in img_dir.glob('ixi2d_*.png')) | |
| oneuro = sum(1 for _ in img_dir.glob('oneuro_*.png')) | |
| kaggle = sum(1 for _ in img_dir.glob('neg_kaggle*.png')) | |
| total = sum(1 for _ in img_dir.glob('*.png')) | |
| print(f' {split:5s} total={total:5d} kaggle_neg={kaggle:5d} ' | |
| f'openneuro={oneuro:5d} ixi2d={ixi:5d} radiata={radiata:5d}') | |
| if __name__ == '__main__': | |
| main() | |