Expand description
iQSM single-step deep-learning reconstruction (onnx feature).
iQSM (Gao 2022) maps raw wrapped MRI phase (radians) directly to
susceptibility (ppm) — unwrapping, background removal and dipole inversion in
one network (a learnable-Laplacian “LoT” front-end + a U-Net). The exported
graph takes four inputs: phase, mask, te (s, scalar), b0 (T, scalar).
This mirrors the authors’ inference.run_iqsm: flip the phase sign, erode the
mask by a radius-3 sphere, centre-pad each dim to a multiple of 16, run the
net, multiply by the (padded) mask, and crop back. For multi-echo data each
echo is reconstructed and combined with (magnitude·TE)² weighting by default
(iqsm_multi_echo, IqsmEchoWeighting).
The authors run the whole volume in one pass, and so does iqsm. Its activations scale with
the volume; the multi-echo entry point can instead run each echo patch by patch (a
TileConfig), with memory bounded by the patch: an approximation, since the network’s
unwrapping, background removal and inversion are not local, as for the other tiled networks
(see super::tiled).
Weights are not bundled; the caller passes the exported iqsm.onnx bytes.
§Reference pipeline
From the authors’ code (sunhongfu/iQSM):
- Normalisation: the LoT layer scales its output by
3 · 20e-3 / (B0 · TE)(models/unet_blocks.py:21-22), i.e. to the 3 T, TE = 20 ms reference, so any field strength and TE are handled through thete/b0inputs. - Masking: with no mask supplied,
run.py:499-506runs FSLbet2 -m -f 0.5on the first magnitude volume (bet2_utils.py:47,64). - Resolution: “for data with resolution finer than 0.7 mm isotropic, interpolate to 1 mm
before reconstruction” (
README.md:9; alsolegacy/matlab/demo_single_echo.m:21-23). The MATLAB demo interpolates anisotropic data to an isotropic grid at the finest voxel size in the complex domain (demo_single_echo.m:52-60). The Pythoninference.pyacceptsvoxel_sizebut does not resample. This crate does not resample. - Phase masking: the MATLAB
Save_Input.m:25multiplies the phase by the eroded mask; the Pythoninference.pydoes not, and neither does this crate. The 3×3×3 LoT stencil output is multiplied by the eroded mask, so only the outermost shell of the eroded mask can differ.
§Agreement with the original
tests/models_onnx/field.rs runs it on a 36×32×28 box of in-vivo data (1 mm, 2.89 T, across the
brain’s edge) against its original’s verified QSM-CI entry, iQSM bb163ef (iqsm)
(QSMxT/QSM-CI#316; the fixture is regenerated in Actions by
tests/data/dl_field_qsmci_small.py): per echo, NRMSE 9.8e-4 to 2.0e-3 %, max |Δ| up to 3.9e-6
ppm: float32 rounding.
The original runs one echo at a time and combines the echoes afterwards. The authors’ two
implementations combine them differently (see IqsmEchoWeighting): their MATLAB echo fit,
published with the paper, weights by mag·TE², and their Python driver by (mag·TE)². This crate
defaults to (mag·TE)², the inverse-variance weight (each echo’s estimate has noise variance
∝ 1/(mag·TE)²), and offers the MATLAB’s mag·TE² as an option, for parity with it. On the box’s first and third echoes the driver’s combination
is (mag·TE)² of its per-echo outputs to 6e-6 %, and the two weightings differ by 3.0 % NRMSE;
iqsm_multi_echo matches the MATLAB fit of the original’s per-echo outputs to 9.4e-4 %
(measured 2026-10-30), and with the Python weighting their (mag·TE)² combination to 9.5e-4 %
(measured 2026-10-31).
§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): iqsm.mask-bet.
Re-exports§
pub use super::iqsm_echo_weighting::IqsmEchoWeighting;
Functions§
- iqsm
- Run iQSM on a single echo of wrapped phase.
- iqsm_
multi_ echo - Multi-echo iQSM: reconstruct each echo and combine them with
weighting(seeIqsmEchoWeighting; the default is (magnitude·TE)², the inverse-variance weight).phases/magnitudesare per-echo volumes;testhe echo times (s). Withoutmagnitudesthe weights are TE² alone (uniform magnitude). - iqsm_
with 🔒 - lot_
multi_ 🔒echo iqsm_multi_echofor either head of the network:model_id("iqsm"or"iqfm") names the registry entry whose tile cost the tiled path budgets with.- sphere_
erode 🔒 - Binary erosion by a solid sphere of the given radius (voxels), matching
scipy.ndimage.binary_erosionwithborder_value=0(out-of-bounds = false). - tiled_
echo 🔒 - One echo patch by patch: erode the mask on the whole volume, then run
run_patch(the network with no erosion of its own) on each tile.costis the network’s tile cost, whose divisor (16: the LoT-Unet has four pooling levels) pads each patch.
Type Aliases§
- Net 🔒
- Runs the network on its input tensors: a whole-volume model (re-planned per shape) or a plan built once for a tile’s shape.