Spaces:
Runtime error
Runtime error
File size: 5,478 Bytes
bd4a96a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | #!/usr/bin/env python3
"""Build a combined brain parcellation atlas for mindVisualizer.
Combines three complementary atlases (all in MNI152 1mm space) into a single
NIfTI volume with a unified label map:
Layer 1 β Harvard-Oxford Cortical (48 regions):
Broad cortical coverage with LLM-friendly names.
Layer 2 β Harvard-Oxford Subcortical (17 regions):
Thalamus, putamen, caudate, hippocampus, amygdala, etc.
Layer 3 β Julich-Brain cytoarchitectonic (62 regions, HIGHEST PRIORITY):
Fine-grained motor (BA4a/4p), somatosensory (BA1-3), visual (V1-V5),
auditory (TE1.0-1.2), Broca's (BA44/45), hippocampal subfields,
amygdala subdivisions, white matter tracts.
Where Julich-Brain has a label, it overrides the coarser Harvard-Oxford label.
This gives maximum spatial coverage with maximum detail where available.
Output:
data/extra_parcellation/combined_atlas.nii.gz (NIfTI volume, ~700KB)
data/extra_parcellation/combined_atlas_labels.json (label ID β name map)
Requirements:
pip install nilearn nibabel
Usage:
python scripts/setup_extra_parcellation.py
"""
import json
import os
import sys
from pathlib import Path
# Ensure project root is importable
ROOT = Path(__file__).resolve().parent.parent
OUT_DIR = ROOT / "data" / "extra_parcellation"
def main():
try:
import nibabel as nib
import numpy as np
except ImportError:
print("ERROR: nibabel and numpy are required.")
print(" pip install nibabel numpy")
sys.exit(1)
try:
import nilearn.datasets as ds
except ImportError:
print("ERROR: nilearn is required to fetch the source atlases.")
print(" pip install nilearn")
sys.exit(1)
OUT_DIR.mkdir(parents=True, exist_ok=True)
out_nii = OUT_DIR / "combined_atlas.nii.gz"
out_labels = OUT_DIR / "combined_atlas_labels.json"
if out_nii.exists() and out_labels.exists():
print(f"[setup] Combined atlas already exists: {out_nii}")
print("[setup] Delete it and re-run to rebuild.")
return
# ---- Fetch source atlases via nilearn (auto-downloads) ----
print("[setup] Fetching Harvard-Oxford cortical atlas ...")
ho_cort = ds.fetch_atlas_harvard_oxford("cort-maxprob-thr25-1mm")
print("[setup] Fetching Harvard-Oxford subcortical atlas ...")
ho_sub = ds.fetch_atlas_harvard_oxford("sub-maxprob-thr25-1mm")
print("[setup] Fetching Julich-Brain cytoarchitectonic atlas ...")
juelich = ds.fetch_atlas_juelich("maxprob-thr25-1mm")
# ---- Load NIfTI images ----
def _load(maps):
return maps if hasattr(maps, "dataobj") else nib.load(maps)
ho_cort_img = _load(ho_cort["maps"])
ho_sub_img = _load(ho_sub["maps"])
juelich_img = _load(juelich["maps"])
ho_cort_data = np.asarray(ho_cort_img.dataobj)
ho_sub_data = np.asarray(ho_sub_img.dataobj)
juelich_data = np.asarray(juelich_img.dataobj)
assert ho_cort_data.shape == ho_sub_data.shape == juelich_data.shape, \
"Atlas shapes do not match β cannot combine"
assert np.allclose(ho_cort_img.affine, juelich_img.affine), \
"Atlas affines do not match β not in the same MNI space"
# ---- Combine: lowest priority first, highest last ----
combined = np.zeros(ho_cort_data.shape, dtype=np.int32)
combined_labels = {}
# Layer 1: Harvard-Oxford Cortical (label IDs 1β48)
for i in range(1, len(ho_cort["labels"])):
combined[ho_cort_data == i] = i
combined_labels[str(i)] = str(ho_cort["labels"][i])
# Layer 2: Harvard-Oxford Subcortical (label IDs 100+)
for i in range(1, len(ho_sub["labels"])):
name = str(ho_sub["labels"][i])
# Skip overly broad labels
if "Cortex" in name or "White Matter" in name:
continue
label_id = 100 + i
combined[ho_sub_data == i] = label_id
combined_labels[str(label_id)] = name
# Layer 3: Julich-Brain (label IDs 200+, overwrites everything)
for i in range(1, len(juelich["labels"])):
label_id = 200 + i
combined[juelich_data == i] = label_id
name = str(juelich["labels"][i])
# Clean up prefixes
if name.startswith("GM "):
name = name[3:]
elif name.startswith("WM "):
name = "WM: " + name[3:]
combined_labels[str(label_id)] = name
# ---- Save ----
combined_img = nib.Nifti1Image(combined, ho_cort_img.affine)
nib.save(combined_img, str(out_nii))
with open(out_labels, "w", encoding="utf-8") as f:
json.dump(combined_labels, f, indent=2, ensure_ascii=False)
n_labels = len(combined_labels)
coverage = int((combined > 0).sum())
total = int(combined.size)
pct = 100 * coverage / total
print(f"\n[setup] Combined atlas saved:")
print(f" NIfTI: {out_nii} ({os.path.getsize(out_nii):,} bytes)")
print(f" Labels: {out_labels} ({n_labels} labels)")
print(f" Coverage: {coverage:,} / {total:,} voxels ({pct:.1f}%)")
print(f" Sources:")
n_ho_c = sum(1 for k in combined_labels if 1 <= int(k) <= 99)
n_ho_s = sum(1 for k in combined_labels if 100 <= int(k) <= 199)
n_jue = sum(1 for k in combined_labels if 200 <= int(k) <= 299)
print(f" Harvard-Oxford Cortical: {n_ho_c} labels")
print(f" Harvard-Oxford Subcortical: {n_ho_s} labels")
print(f" Julich-Brain: {n_jue} labels")
if __name__ == "__main__":
main()
|