Expand description
Field mapping stage
Multi-echo phase → B0 field map in ppm. Implements the canonical field mapping pipeline: phase offset removal → bipolar correction → unwrapping → B0 estimation, or MEDI’s: complex fit of the wrapped echoes → unwrapping.
§Reference pipeline
The default (FieldMappingConfig::default: MCPC-3D-S offset removal, ROMEO template
unwrapping, :phase_snr weighted average) is the ROMEO authors’ B0 pipeline: the romeo CLI
with --compute-B0 turns MCPC-3D-S on for multi-echo data (ROMEO.jl 08776d4,
ext/RomeoApp/caller.jl:80-91), unwraps temporally from echo 1 (argparse.jl:101-105) and
combines with phase_snr (argparse.jl:67-70; MriResearchTools 771eadf
src/romeofunctions.jl:13-24), whose docstring requires the phase offsets to have been
removed first (romeofunctions.jl:3-7). Where QSM.rs differs:
- Smoothing sigma.
phase_offset_sigma_mmdefaults to the ROMEO command line’s 7 mm per axis, divided by the voxel size (argparse.jl:79-91,caller.jl:137-141); MriResearchTools’mcpc3dsfunction itself defaults to[10, 10, 5]voxels (src/mcpc3ds.jl:17,39). The command line says to reduce it on a small field of view and to use 0 along an axis with a single slice; that is left to the caller. - Masks. MCPC-3D-S makes its own
robustmaskofsqrt(|HIP|), as the referencemcpc3dsdoes (mcpc3ds.jl:44-46;crate::utils::phase_offset_removal), and bipolar correction runs inside that same mask ([crate::utils::mcpc3ds_mask]), becausemcpc3dscallsbipolar_correction!with it (mcpc3ds.jl:55). This used to be the brain mask: ontests/mritools_parity.rs‘s phantom, whose mask differs from that robust mask at about a tenth of their voxels, it left the corrected phase up to 0.22 rad frommcpc3ds(...; bipolar_correction = true)inside the brain; withmcpc3ds’ mask it agrees to 1.8e-15 rad (QSM.rs#330). ROMEO uses the brain mask passed in, as the CLI does when given one (-k); without one the CLI unwraps insiderobustmaskof the template magnitude (caller.jl:201-204). - ROMEO settings. The default is ROMEO.jl’s API defaults
(
RomeoParams::default::romeoweights,temporal_uncertain_unwrapping0.5). The CLI turns-w romeointoromeo3when a magnitude is given and does no temporal re-unwrapping;RomeoParams::romeo_clisets those, and with it the result matches the CLI up to the CLI’s single-precision arithmetic (seecrate::unwrap::romeo, Agreement with the reference). - Where MCPC-3D-S’s offset is undefined. As in the reference, the corrected phase is NaN
there, ROMEO does not grow through it, and the field is undefined: the result’s
FieldMappingResult::valid_maskis the brain mask less those voxels, the field is 0 on the rest of the brain mask, and every later stage must work invalid_mask(crate::utils::phase_offset_removalhas the measurements; until 2026-11 those voxels kept their raw phase, which caused ring artefacts on GE and Philips data). On every other pathvalid_maskis the brain mask. - Offset removal off, or Laplacian unwrapping. Phase-offset removal and bipolar correction
run only with ROMEO (
do_offsetbelow); with Laplacian they are skipped whatever the config says. Without them ROMEO unwraps the raw echoes exactly as on the offset-removal path (crate::unwrap::unwrap_romeo_multi_echowithromeo_params: template or individual mode, each echo’s own magnitude, ROMEO.jl’sphase2; until 2026-11 this path unwrapped each echo with echo 1’s magnitude and the next echo asphase2whatever the mode), and the per-echo whole turns are aligned before combining (seefield_mapping_direct; a no-op whenever ROMEO’s own global correction ran or the echoes were unwrapped temporally). - Bipolar correction is opt-in in the reference too (
argparse.jl:71-76, “requires ≥ 3 echoes”); with fewer than 3 echoes it is skipped here silently.
Field strength enters only in the conversion to ppm; nothing assumes a resolution or B0 direction.
§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): field-mapping.phase-offsets, field-mapping.bipolar-needs-three-echoes, field-mapping.bipolar-not-with-laplacian, field-mapping.phase-offset-sigma.
Structs§
- Laplacian
Region 🔒 - Where Laplacian unwrapping works (
FieldMappingConfig::laplacian_unmasked): on the phase zeroed outside the mask, keeping the result inside it (the default); or on the raw phase. The raw phase’s result is kept over the whole volume only for QSMART’s echo fit, which fits every voxel (echofit.m); the other combinations use only the voxels inside the mask.
Functions§
- align_
per_ 🔒echo_ constants - Remove, from each separately unwrapped echo, the constant its unwrapper leaves undetermined,
so that the echoes agree before they are averaged. See
field_mapping_direct. - field_
mapping_ 🔒complex_ fit B0EstimationMethod::ComplexFit: MEDI’s field map.Fit_ppm_complexon the wrapped complex echoes gives the phase per echo spacing, wrapped; it is unwrapped spatially, as MEDI’siFreq = unwrapPhase(iMag, iFreq_raw, matrix_size)does, and divided by the echo spacing. The noise map (dp1, MEDI’sN_std) is converted the same way, to ppm. The fit itself is returned too (FieldMappingResult::complex_fit:iFreq_rawandN_stdin MEDI’s units).- field_
mapping_ 🔒direct - Path B: Multi-echo without phase offset removal (per-echo unwrap + B0 estimation).
- field_
mapping_ 🔒single_ echo - Path C: Single echo unwrap
- field_
mapping_ 🔒with_ offset - Path A: Multi-echo with phase offset removal.
- mask_
phase 🔒 phasewith every voxel outsidemaskset to zero.- qsmart_
fit 🔒 B0EstimationMethod::QsmartEchofiton unwrapped echoes: QSMART’sechofit.m(qsmart_echofit) with QSMART’s γ, giving the total field over every voxel and the reliability mapR_0.- remove_
masked_ 🔒mean - Subtract the mean over
maskfromvaluesinsidemask(outside is left untouched). - require_
magnitude_ 🔒for_ medi - MEDI’s region growing orders its growth by the magnitude and has no fallback without one
(
unwrapPhase(iMag, ...)), so a run without magnitudes is refused rather than grown over a uniform magnitude, where every voxel would tie. - robust_
fit_ 🔒echoes multi_echo_robust_fiton unwrapped echoes: the field in rad/s and the report. Echo count and params are checked byrun_field_mapping.- rss_
magnitude 🔒 - MEDI’s
iMag = sqrt(sum(abs(iField).^2, 4)): the root-sum-of-squares of the echo magnitudes. - run_
field_ mapping - Run field mapping: convert per-echo phase data to a B0 field map in ppm.
- run_
field_ mapping_ per_ echo - The field of each echo on its own, for a per-echo reconstruction
(
QsmPipelineConfig::qsm_per_echo; seesuper::per_echo): echoe‘s phase is unwrapped withFieldMappingConfig::unwrapping_algorithm(ROMEO with that echo’s magnitude) and divided by its own echo time, exactly as a single-echo series is (run_field_mappingwith one echo). The echo combination (FieldMappingConfig::b0_estimation), phase-offset removal and bipolar correction combine or compare echoes, so they do not apply and are not run. This is the DECOMPOSE authors’ per-echo field:MRPhaseUnwrapof each echo’s raw phase, then that echo’s background removal and STAR-QSM at its TE (Chen et al. 2021, NeuroImage 242:118477, §3.6). - single_
echo_ 🔒result - One echo’s field from its unwrapped phase: the masked mean a Laplacian unwrap leaves undetermined removed (so one echo gives the same field as each echo of a series does), then divided by the echo time.
- unwrap_
one_ 🔒echo - One echo’s phase, unwrapped. Laplacian: as on the multi-echo path, the phase is zeroed outside
the mask first (a global Poisson solve otherwise carries the noise there into the brain; not
with
laplacian_unmasked). - unwrap_
single 🔒 - Unwrap a single echo on its own using the configured algorithm (ROMEO without
phase2).