disco-zero / tools /scanner_brain_bmap.py
rfick's picture
Squash history: one commit holding the current files
4b23c8f
Raw History Blame Contribute Delete
3.91 kB
"""The b each brain voxel is delivered on the scan's b = 1000 shell (96 directions, δ 25 / Δ 55 ms, TE 100 ms,
square pulses) by each machine, the head centre at isocentre: b = gamma^2 int (int eps g)^2 with the delivered
waveform eps(t) [s(t) q_v,i + g0_v] (bore.delivered_moments), whose three time integrals are taken once.
ASSET=/asset/dir python tools/scanner_brain_bmap.py
"""
import json, os, sys
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import dmipy_sim as d
from dmipy_sim.constants import GAMMA
from dmipy_sim.phantom.bore import delivered_moments
from space import pipeline as P
from space.sources import brain as B
cfg = P.config(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "space", "brain.toml")); cfg["asset"] = {"local": os.environ["ASSET"]}
cfg["packs"] = {"wm": [{"label": "w", "uri": "x"}], "gm": [{"label": "g", "uri": "y"}]}
br = B.Brain(cfg)
grid, idx = br.bore_grid
t = B.Brain.shape_table(cfg, os.environ["ASSET"])["scan"]
prot = B.scan_protocol(br.asset.manifest, name="scan"); meas = P.measurements(prot, br.shapes)
rows = np.flatnonzero(np.round(meas.bvals) == 1000)
u = meas.dirs[rows] # image frame = the bore grid's frame
seq = d.pgse([[0.0, 0.0, 1.0]], t["delta"], t["Delta"], bvalues=[1e9], TE=t["TE"], n_t=2000, slew_rate=np.inf)
g = float(np.abs(np.asarray(seq.G)).max()); s = np.asarray(seq.G)[0, :, 2] / g
sgn = seq.rf.sign(np.arange(seq.n_t) * seq.dt)
Fs = np.cumsum(sgn * s) * seq.dt; F0 = np.cumsum(sgn) * seq.dt
I = {k: GAMMA ** 2 * np.sum(a * b_) * seq.dt for k, (a, b_) in dict(ss=(Fs, Fs), s0=(Fs, F0), oo=(F0, F0)).items()}
b_nom = g ** 2 * I["ss"]
out = {}
maps = {}
for key in ("hyperfine_swoop_64mT", "siemens_magnetom_prisma_3T", "siemens_magnetom_terra_7T"):
L = P.limits(key)
radius = L.b0_validity_radius or L.gnl_validity_radius or 1.0
r = np.linalg.norm(grid.offset_m(idx), axis=1)
inside = r <= radius
q_all = np.full((len(idx), len(rows), 3), np.nan); g0_all = np.zeros((len(idx), 3))
dm = delivered_moments(L, grid, s, np.full(len(rows), g), u, voxels=idx[inside], transmit=True)
q_all[inside] = dm.q
if dm.g0 is not None:
g0_all[inside] = dm.g0
bq = np.einsum("vmi,vmi->vm", q_all, q_all) * I["ss"] + 2 * np.einsum("vmi,vi->vm", q_all, g0_all) * I["s0"] + (g0_all ** 2).sum(1)[:, None] * I["oo"]
frac = bq / b_nom
mean = np.nanmean(frac, 1)
vol = np.full(br.mask.shape, np.nan); vol[tuple(br.vox.T)] = mean
maps[key] = vol
kap = np.full(len(idx), np.nan)
if dm.kappa is not None:
kap[inside] = dm.kappa
out[key] = dict(voxels=int(len(idx)), inside_anchor=int(inside.sum()), anchor_cm=radius * 100,
direction_mean_b_fraction=dict(median=float(np.nanmedian(mean)), p01=float(np.nanquantile(mean, 0.01)), p99=float(np.nanquantile(mean, 0.99)),
min=float(np.nanmin(mean)), max=float(np.nanmax(mean))),
per_direction_b_fraction=dict(p01=float(np.nanquantile(frac, 0.01)), p99=float(np.nanquantile(frac, 0.99)),
min=float(np.nanmin(frac)), max=float(np.nanmax(frac))),
direction_spread_median=float(np.nanmedian(np.nanmax(frac, 1) - np.nanmin(frac, 1))),
voxels_off_by_more_than_5pc=float(np.nanmean(np.abs(mean - 1) > 0.05)),
transmit_scale=None if dm.kappa is None else dict(median=float(np.nanmedian(kap)), min=float(np.nanmin(kap)), max=float(np.nanmax(kap))),
G_needed_mT_m=g * 1e3, G_max_mT_m=float(L.G_max) * 1e3)
print(key, json.dumps(out[key], indent=1), flush=True)
np.savez_compressed("brain_bmap.npz", **{k: v for k, v in maps.items()}, affine=br.affine)
json.dump(out, open("brain_bmap.json", "w"), indent=1)