Skip to main content

Module ir2qsm

Module ir2qsm 

Source
Expand description

IR2QSM dipole inversion (onnx feature).

IR2QSM (Li et al., Med. Phys. 2025; arXiv:2406.12300) maps the local (tissue) field in ppm directly to susceptibility in ppm with a single “IR2U-net”: a 3D U-net (depth=4) run for iterations=4 unrolled passes with reverse concatenations and a recurrent SRU middle module, then a learned integration of the four per-iteration residual estimates (latest_out, the network’s final product). Unlike LPCNN there is no separate physics/unroll here — the entire network is the exported ONNX graph, and this glue only does the surrounding I/O pipeline:

  1. No normalization. IR2QSM consumes the ppm local field directly and emits ppm susceptibility — no dataset mean/std, no norm_factor (there are no baked scalar constants). Matches IR2QSM/Evaluate/test_util.py.
  2. Zero-pad to a multiple of 8. The U-net has 3 pool/deconv levels, so each spatial dim must be divisible by 2³ = 8; we center-pad exactly as the reference zero_padding(image, 8) (low offset = ceil((target − shape)/2)), run the net, then crop back.
  3. Mask. The result is multiplied by the supplied brain mask on the original grid.

Determinism. IR2Unet.forward has an ungated inference-time AddNoise in the decoder (torch.rand(1) > 0.3 per iteration) that makes plain PyTorch inference mildly stochastic. The ONNX was exported with that call pinned to its noise-free branch (identity), so this net is deterministic. See export_ir2qsm.py.

Input is the background-removed local field in ppm (single orientation; the net is orientation-agnostic — no dipole kernel, no b_vec). Weights are not bundled; the caller passes ir2qsm.onnx.

§Reference pipeline

From Li et al., arXiv:2406.12300, “Experimental Setup”:

  • Training data: 14,400 64³ patches of 1 mm isotropic 3T χ maps (best-path unwrapping, RESHARP, iLSQR), with local fields simulated by the forward model, as for xQSM.
  • Testing: only 1 mm isotropic 3T data; the authors state other resolutions are untested (Discussion). The authors’ Evaluate/test_util.py does not resample.

§Agreement with the original

tests/models_onnx/dipole.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, IR2QSM 1ddb147 (ir2qsm) (QSMxT/QSM-CI#316; the fixture is regenerated in Actions by tests/data/dl_dipole_qsmci_small.py): NRMSE 5.4e-4 %, max |Δ| 2.7e-6 ppm (measured 2026-10-30): float32 rounding. The original runs with add_noise=false, the noise-free branch this export pins (above); with the authors’ default, noise on, it differs from itself by 12.6 % NRMSE run to run.

Constants§

SIZE_DIVISOR 🔒
U-net pool/deconv depth requirement: spatial dims are padded to a multiple of this.

Functions§

ir2qsm
Run IR2QSM on a background-removed local field (ppm), column-major (nx,ny,nz). Returns χ (ppm), masked, on the original grid.
ir2qsm_tiled
Memory-bounded IR2QSM via overlap-tiling — the IR2U-net run patch-by-patch (for 32-bit WASM, where whole-volume ir2qsm overflows the heap on clinical data). No normalization; the net’s 3 pool levels require a /8 patch. Approximates whole-volume up to tile-boundary error; see crate::inversion::tiled.