Spaces:
Running on Zero
Running on Zero
File size: 8,514 Bytes
6bc44d6 | 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 | """The development fixture of the brain Space: the B.A.T.M.A.N. tutorial subject written in the layout of the brain
asset (``manifest.json``, ``fod_wm.npy``, ``fractions.npy``, ``mask.npy``, ``labels.npy``, ``regions.json``,
``stop_mask.npy``, ``mean_b0.npy``), so the ``Brain`` source is developed and tested against the asset's contract
before the MASiVar asset exists. It stays local (BATMAN's licence, dmipy-sim#193) and is for tests only.
What each file is:
* ``fod_wm.npy`` -- ``wmfod_norm.mif`` (MRtrix3 multi-tissue CSD, lmax 8, the MRtrix3 basis, which is dmipy-sim's
required basis and dmipy-fit's) turned from the scanner frame into the image frame by ``R.T`` (``R`` from
:meth:`dmipy_sim.phantom.Grid.from_oblique_affine`), zero outside the brain mask.
* ``fractions.npy`` -- WM / GM / CSF from ``5tt_coreg.mif`` (on the 1 mm T1 grid), averaged into every 2.5 mm
voxel on a 3^3 sub-grid as ``examples/rph/brain_from_csd.py`` does: GM is cortical + sub-cortical GM, WM where
the FOD has no positive l = 0 coefficient is counted as GM (no FOD, no oriented WM), zero outside the mask.
* ``labels.npy`` / ``regions.json`` -- there is no parcellation of this subject, so the GM voxels (GM fraction
above one half) are split into 84 blocks by k-means on their coordinates (42 per hemisphere, a fixed seed),
named ``fixture <hemisphere> <k>`` and given to 7 lobes by position: a fixture of the 84-node layout, no anatomy.
* ``stop_mask.npy`` -- the voxels whose 5TT WM fraction is above one half: where a streamline may continue.
* ``mean_b0.npy`` -- ``mean_b0_preprocessed.mif``.
* ``manifest.json`` -- the grid (the image's affine, voxel -> scanner RAS mm), the tutorial's gradient table
(scanner frame) with the capstone's pulse timing (TE 100 / delta 25 / Delta 55 ms, which the 100 ms packs play).
python tools/build_brain_fixture.py [--batman ~/dmrai-ws/data/batman] [--out ~/dmrai-ws/data/batman/brain_fixture]
"""
import argparse
import json
import os
import subprocess
import numpy as np
GM_COLS, WM_COL, CSF_COL = (0, 1), 2, 3 # the 5TT columns: cortical GM, sub-cortical GM, WM, CSF
LOBES = ("frontal", "insula", "cingulate", "temporal", "parietal", "occipital", "subcortical")
TIMING = dict(TE_s=0.100, delta_s=0.025, Delta_s=0.055) # the BATMAN capstone's (dmipy-sim#193)
SEED = 20260930
def fractions_on(shape, target_affine, tt, sub=3):
"""The five 5TT fractions averaged into every voxel of the target grid: ``sub^3`` points per voxel mapped
through both affines and sampled trilinearly on the T1 grid; ``(X, Y, Z, 5)``."""
from scipy.ndimage import map_coordinates
off = (np.arange(sub) + 0.5) / sub - 0.5
o = np.stack(np.meshgrid(off, off, off, indexing="ij"), -1).reshape(-1, 3)
ijk = np.stack(np.meshgrid(*[np.arange(n) for n in shape], indexing="ij"), -1).reshape(-1, 3)
pts = (ijk[:, None, :] + o[None, :, :]).reshape(-1, 3)
xyz = pts @ target_affine[:3, :3].T + target_affine[:3, 3]
inv = np.linalg.inv(tt.affine)
src = xyz @ inv[:3, :3].T + inv[:3, 3]
out = np.stack([map_coordinates(tt.data[..., t], src.T, order=1, mode="constant", cval=0.0) for t in range(5)], -1)
return out.reshape(tuple(shape) + (sub ** 3, 5)).mean(axis=3)
def parcellate(gm, shape):
"""84 k-means blocks of the GM voxels (42 per hemisphere, split at the median x of the brain), ``(labels,
regions)``: labels 1..42 left, 43..84 right; per hemisphere the six blocks nearest the brain's centre are
'subcortical' and the other 36, sorted front to back (image y), fill the six cortical lobes six at a time."""
from scipy.cluster.vq import kmeans2
ijk = np.argwhere(gm).astype(np.float64)
centre = ijk.mean(0)
labels = np.zeros(shape, np.int16)
regions = []
for h, (hemi, side) in enumerate((("left", ijk[:, 0] < centre[0]), ("right", ijk[:, 0] >= centre[0]))):
pts = ijk[side]
cents, lab = kmeans2(pts, 42, seed=SEED + h, minit="++", iter=50)
order = np.argsort(np.linalg.norm(cents - centre, axis=1))
sub = list(order[:6])
cortical = sorted(order[6:], key=lambda k: -cents[k, 1]) # anterior (large y) first
lobe_of = {k: "subcortical" for k in sub}
for i, k in enumerate(cortical):
lobe_of[k] = LOBES[i // 6]
for rank, k in enumerate(sorted(range(42), key=lambda k: (LOBES.index(lobe_of[k]), -cents[k, 1]))):
rid = 1 + 42 * h + rank
v = pts[lab == k].astype(int)
labels[v[:, 0], v[:, 1], v[:, 2]] = rid
regions.append(dict(id=rid, name=f"fixture {hemi} {rank + 1:02d}", hemisphere=hemi, lobe=lobe_of[k]))
return labels, regions
def main(argv=None):
from dmipy_sim.io.mrtrix import read_mif
from dmipy_sim.phantom import Grid
from dmipy_sim.replay.so3 import rotate_sh
ap = argparse.ArgumentParser()
ap.add_argument("--batman", default=os.path.expanduser("~/dmrai-ws/data/batman"))
ap.add_argument("--out", default=os.path.expanduser("~/dmrai-ws/data/batman/brain_fixture"))
a = ap.parse_args(argv)
os.makedirs(a.out, exist_ok=True)
fod = read_mif(os.path.join(a.batman, "wmfod_norm.mif"))
mask = np.asarray(read_mif(os.path.join(a.batman, "mask_den_unr_preproc_unb.mif")).data, bool)
tt = read_mif(os.path.join(a.batman, "5tt_coreg.mif"))
b0 = read_mif(os.path.join(a.batman, "mean_b0_preprocessed.mif"))
shape = fod.shape[:3]
_, R = Grid.from_oblique_affine(fod.affine, shape)
sh = rotate_sh(np.asarray(fod.data, np.float64), np.asarray(R).T) * mask[..., None]
F = fractions_on(shape, fod.affine, tt) * mask[..., None]
has_fod = sh[..., 0] > 0
f_wm = F[..., WM_COL] * has_fod
f_gm = F[..., GM_COLS[0]] + F[..., GM_COLS[1]] + F[..., WM_COL] * ~has_fod
f_csf = F[..., CSF_COL]
fractions = np.stack([f_wm, f_gm, f_csf], -1)
labels, regions = parcellate(mask & (f_gm > 0.5), shape)
stop = mask & (F[..., WM_COL] > 0.5)
g = np.loadtxt(os.path.join(a.batman, "dwipreproc_grad.b"))
commit = subprocess.run(["git", "-C", os.path.dirname(os.path.abspath(__file__)), "rev-parse", "HEAD"], capture_output=True, text=True).stdout.strip()
groups = []
for r in regions:
name = f"{r['hemisphere'][0].upper()} {r['lobe']}"
grp = next((x for x in groups if x["name"] == name), None)
if grp is None:
grp = dict(name=name, hemisphere=r["hemisphere"], lobe=r["lobe"], ids=[]); groups.append(grp)
grp["ids"].append(r["id"])
manifest = {
"subject": "BATMAN (fixture)",
"source": {"dataset": "B.A.T.M.A.N. MRtrix3 tutorial (Tahedl 2018), Supplementary_Files", "doi": "10.17605/OSF.IO/FKYHT",
"paper_doi": None, "license": "local development fixture only (dmipy-sim#193 item 1)"},
"grid": {"shape": list(shape), "voxel_size_mm": [float(x) for x in np.linalg.norm(fod.affine[:3, :3], axis=0)],
"affine": np.asarray(fod.affine, float).tolist()},
"protocol": {"bvals_s_mm2": [float(round(b)) for b in g[:, 3]], "bvecs": g[:, :3].tolist(), "bvec_frame": "scanner", **TIMING},
"fod": {"basis": "tournier07", "lmax": 8, "frame": "image"},
"fractions": ["wm", "gm", "csf"],
"reconstruction": {"by": "MRtrix3 dwi2fod msmt_csd (the tutorial's wmfod_norm.mif), 5TT fractions", "fixture": True},
"parcellation": {"method": "fixture: k-means blocks of the GM voxels, 42 per hemisphere, seed %d" % SEED, "n_regions": len(regions)},
"built_by": {"tool": "disco-space tools/build_brain_fixture.py", "commit": commit},
}
np.save(os.path.join(a.out, "fod_wm.npy"), sh.astype(np.float32))
np.save(os.path.join(a.out, "fractions.npy"), fractions.astype(np.float16))
np.save(os.path.join(a.out, "mask.npy"), mask)
np.save(os.path.join(a.out, "labels.npy"), labels)
np.save(os.path.join(a.out, "stop_mask.npy"), stop)
np.save(os.path.join(a.out, "mean_b0.npy"), np.asarray(b0.data, np.float32))
with open(os.path.join(a.out, "regions.json"), "w") as f:
json.dump(dict(regions=regions, groups=groups), f, indent=1)
with open(os.path.join(a.out, "manifest.json"), "w") as f:
json.dump(manifest, f, indent=1)
print(f"{a.out}: grid {shape}, {int(mask.sum())} brain voxels, {int((f_wm > 0).sum())} with WM, {int(stop.sum())} in the stop mask, "
f"{len(regions)} regions ({len(groups)} lobar groups), {len(g)} measurements")
if __name__ == "__main__":
main()
|