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()