Skip to main content

Module mindgrab

Module mindgrab 

Source
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:

  1. conform the image as niimath’s -conform does (a port of FastSurfer’s conform.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 to u8;
  2. normalise to the unit interval between the 2nd and 98th percentiles, clipping outside;
  3. run the network on the whole 256³ volume (optionally also on its left–right mirror, summing the logits, as brainchop’s --tta does);
  4. take the argmax, keep the largest 26-connected component (niimath -bwlabel 26);
  5. 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::affine in the pipeline, which refuses to run MindGrab without it); a wrong affine gives the network a wrongly oriented head.
  • Defaults. brainchop --skull-strip without --tta; MindGrabParams::default matches.
  • 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) give find_model("mindgrab").peak_bytes([256; 3], Heap::Wasm32) = 363 MB + 311 B × 256³ ≈ 5.58 GB, above the 4 GiB a wasm32 heap 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.
MindGrabParams
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’s mri_convert uses 0.999).
NORM_POOL 🔒
Side of the AveragePool window pool_instance_norm_means puts in front of each instance-norm mean: it divides CONFORM_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 conformVox2Vox with toRAS = 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 voxel dims / 2.
flip_axis0 🔒
Reverse the first spatial axis of channels C-order (x, y, z) 256³ volumes (the sagittal flip of brainchop’s TTA).
inv44 🔒
nifti_mat44_inverse: the inverse of the affine, computed in f64 and rounded to f32. None if singular.
mindgrab
Brain mask via MindGrab.
mul44 🔒
nifti_mat44_mul: f32 products accumulated in f32.
mul_vec 🔒
nifti_vect44mat44_mul: m · (v, 1) in f32.
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 ReduceMean over the spatial axes [2, 3, 4] (instance normalisation’s mean and variance) as an AveragePool of NORM_POOL³ windows, stride NORM_POOL, followed by the same ReduceMean over the window means. Mathematically the same mean (equal windows tiling the cube); numerically it keeps tract’s f32 sums 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 doReslice onto the 256³ grid, rounded to u8 (-odt char).
row_yz 🔒
niimath’s per-row partial sums y·m[r][1] + z·m[r][2] + m[r][3], in f32.
scale_to_u8_range 🔒
niimath’s voxelIntensityScale (FastSurfer getscale + scalecrop) to [0, 255], with f_low = 0 and f_high = 0.98.
vox2vox_inverse 🔒
niimath’s resliceVox2Vox inverse: destination voxel → source voxel, i.e. inv(inv(dst) · src), for resampling a src-grid image onto dst. None if singular.

Type Aliases§

M44 🔒
Row-major 4×4 in f32, as niimath’s mat44.