Expand description
MindGrab deep-learning brain extraction (onnx feature).
MindGrab is a 146 k-parameter MeshNet — 25 dilated 3×3×3 convolutions (15 channels,
dilations 16-8-4-2-1 five times over, each followed by instance normalisation and GELU) and a
1×1×1 two-class head — trained only on synthetic, modality-agnostic data, so it skull-strips
head images of any contrast. This module ports the pipeline brainchop-cli runs around the
network (mindgrab.onnx, see crate::models) for brainchop --skull-strip:
- conform the image as niimath’s
-conformdoes (a port of FastSurfer’sconform.py): scale intensities to 0–255 over the robust range (0 to the 98th percentile of the histogram), then reslice trilinearly to a 256³, 1 mm, LIA grid centred on the image, and round tou8; - normalise to the unit interval between the 2nd and 98th percentiles, clipping outside;
- run the network on the whole 256³ volume (optionally also on its left–right mirror,
summing the logits, as brainchop’s
--ttadoes); - take the argmax, keep the largest 26-connected component (niimath
-bwlabel 26); - reslice the mask back onto the input grid with nearest neighbour (niimath
-reslice_nn).
The conform step needs the image’s voxel→world affine: the network expects its input in a fixed anatomical orientation, and that is the only way to find it. Oblique acquisitions are resliced, not just permuted, as niimath does.
Instance normalisation runs over the whole volume, so the network cannot be tiled: one pass
holds a few 15-channel 256³ activations (≈1 GB each in f32).
§Instance-norm statistics (why the graph is restated)
Each block normalises every channel by its mean and variance over all 256³ = 16.8 M voxels
(ReduceMean over axes 2–4 in mindgrab.onnx). tract sums such a reduction in f32 along
one long lane (a handful of running accumulators, each over millions of voxels; consecutive
reductions, and reductions split by reshapes, are merged back into that one lane), and once a
running sum is large the small terms lose most of their bits. On the KKI s002001
bridge-run1 head (QSM-CI’s MindGrab pair) the first block’s per-channel variances came out
up to 2.2 % off and its means 0.11 % off, against f64. Over 25 blocks that moved the
logit margin (brain − background) by up to 1.28 (mean 0.035), and 533 conformed voxels
whose f64 margin was within 0.90 of zero took the other class: the 517 voxels by which the
port’s mask differed from brainchop-cli’s after the reslice. brainchop-cli (tinygrad) is
within 1e-4 of the f64 margins everywhere, with no voxel of another class: its result is
the network’s, and the port’s was tract’s rounding. Measured 2026-11-03, the network
evaluated in f64 (numpy) from the same weights and input.
mindgrab therefore loads the graph with every such ReduceMean restated as an
AveragePool of 16³ windows (stride 16) followed by the ReduceMean of the 16³ window means
(pool_instance_norm_means). The windows tile the 256³ cube exactly, so the mean of the
window means is the same mean; each sum now runs over 4 096 terms, which tract does not
merge with the reduction after it. The variances then agree with f64 to 6e-6, the margins
to 5e-4, and the mask is brainchop-cli’s to the voxel (0 of 16.8 M conformed voxels, 0 of
8.4 M on the input grid). Same runtime and peak memory. The hosted weights are unchanged,
so the edit is made on the parsed protobuf at load time (OnnxModel::load_edited).
§Normalisation arithmetic
brainchop’s qnormalize works in f32: numpy’s quantiles of the f32 volume are f32,
and (v − q_low) / (q_high − q_low + 1e-3) is evaluated in f32. normalize does the
same (quantiles in f64, rounded to f32). In f64 throughout, 1.28 M of the 16.8 M inputs
were one f32 ulp off brainchop’s, which changed no voxel of the mask; in f32 the
network input is brainchop’s bit for bit.
§Reference pipeline
- Contrast and resolution. Any: the image is conformed to 256³ at 1 mm whatever its resolution, and the network was trained on synthetic data of random contrast. QSM.rs has no Dice against a reference brain mask on GRE recorded.
- Orientation. Needs the voxel→world affine (
ScanMetadata::affinein the pipeline, which refuses to run MindGrab without it); a wrong affine gives the network a wrongly oriented head. - Defaults. brainchop
--skull-stripwithout--tta;MindGrabParams::defaultmatches. - WASM. The network always runs on the whole 256³ grid (
CONFORM_DIM) and cannot be tiled. Its registry figures (models::registry,mem(…, 363_000_000, 311)for WASM) givefind_model("mindgrab").peak_bytes([256; 3], Heap::Wasm32)= 363 MB + 311 B × 256³ ≈ 5.58 GB, above the 4 GiB awasm32heap can hold (natively ≈7.3 GB measured at 256³,models::memory_calibration). MindGrab therefore does not run in a 32-bit WASM host.
Reference: Fani, A., Masoud, M., Rorden, C., Plis, S. (2025). “MindGrab for BrainChop: Fast and Accurate Skull Stripping for Command Line and Browser.” arXiv:2506.11860. https://doi.org/10.48550/arXiv.2506.11860
Reference implementation: https://github.com/neuroneural/brainchop-cli (MIT), with niimath (https://github.com/rordenlab/niimath, BSD-2-Clause) for conform, labelling and reslicing.
§Pipeline rules
The facts above that a configuration can depart from are published as rules in
crate::pipeline::rules::RULES (each with its strength, basis, reason and source): mindgrab.wasm-heap.
Structs§
- Conformed 🔒
- The input conformed to the 256³ 1 mm LIA grid, plus what is needed to map a mask back.
- Mind
Grab Params - MindGrab inference parameters.
Constants§
- CONFORM_
DIM - Side of the conformed cube, in voxels (1 mm each).
- CONFORM_
F_ 🔒HIGH - Upper fraction of the histogram kept when scaling to
u8(niimath-conform; FreeSurfer’smri_convertuses 0.999). - NORM_
POOL 🔒 - Side of the
AveragePoolwindowpool_instance_norm_meansputs in front of each instance-norm mean: it dividesCONFORM_DIM, so the windows tile the cube exactly.
Functions§
- argmax_
mask 🔒 - Two-class logits
(1, 2, x, y, z)(C order) → column-major mask: class 1 where its logit beats class 0 (ties go to class 0, as an argmax does). - conform_
affine 🔒 - The conformed grid’s voxel→world affine (niimath’s
conformVox2VoxwithtoRAS = false): 1 mm voxels along LIA (+i left, +j inferior, +k anterior), with the cube’s centre(128, 128, 128)on the world position of the input’s voxeldims / 2. - flip_
axis0 🔒 - Reverse the first spatial axis of
channelsC-order(x, y, z)256³ volumes (the sagittal flip of brainchop’s TTA). - inv44 🔒
nifti_mat44_inverse: the inverse of the affine, computed inf64and rounded tof32.Noneif singular.- mindgrab
- Brain mask via MindGrab.
- mul44 🔒
nifti_mat44_mul:f32products accumulated inf32.- mul_vec 🔒
nifti_vect44mat44_mul:m · (v, 1)inf32.- normalize 🔒
- brainchop’s
qnormalize: map the 2nd..98th percentile (numpy’s linear interpolation) to[0, 1]and clip. Returns the network input in C order(x, y, z)— brainchop feeds the conformed volume to the network as(x, y, z), the transpose of its column-major layout. - pool_
instance_ 🔒norm_ means - Restate every
ReduceMeanover the spatial axes[2, 3, 4](instance normalisation’s mean and variance) as anAveragePoolofNORM_POOL³ windows, strideNORM_POOL, followed by the sameReduceMeanover the window means. Mathematically the same mean (equal windows tiling the cube); numerically it keeps tract’sf32sums short (module docs, Instance-norm statistics). Fails if the graph has no such reduction, so a re-exported graph cannot skip it silently. - reslice_
linear 🔒 - niimath’s linear
doResliceonto the 256³ grid, rounded tou8(-odt char). - row_yz 🔒
- niimath’s per-row partial sums
y·m[r][1] + z·m[r][2] + m[r][3], inf32. - scale_
to_ 🔒u8_ range - niimath’s
voxelIntensityScale(FastSurfergetscale+scalecrop) to[0, 255], withf_low = 0andf_high = 0.98. - vox2vox_
inverse 🔒 - niimath’s
resliceVox2Voxinverse: destination voxel → source voxel, i.e.inv(inv(dst) · src), for resampling asrc-grid image ontodst.Noneif singular.
Type Aliases§
- M44 🔒
- Row-major 4×4 in
f32, as niimath’smat44.