Skip to main content

Module field_mapping

Module field_mapping 

Source
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_mm defaults to the ROMEO command line’s 7 mm per axis, divided by the voxel size (argparse.jl:79-91, caller.jl:137-141); MriResearchTools’ mcpc3ds function 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 robustmask of sqrt(|HIP|), as the reference mcpc3ds does (mcpc3ds.jl:44-46; crate::utils::phase_offset_removal), and bipolar correction runs inside that same mask ([crate::utils::mcpc3ds_mask]), because mcpc3ds calls bipolar_correction! with it (mcpc3ds.jl:55). This used to be the brain mask: on tests/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 from mcpc3ds(...; bipolar_correction = true) inside the brain; with mcpc3ds’ 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 inside robustmask of the template magnitude (caller.jl:201-204).
  • ROMEO settings. The default is ROMEO.jl’s API defaults (RomeoParams::default: :romeo weights, temporal_uncertain_unwrapping 0.5). The CLI turns -w romeo into romeo3 when a magnitude is given and does no temporal re-unwrapping; RomeoParams::romeo_cli sets those, and with it the result matches the CLI up to the CLI’s single-precision arithmetic (see crate::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_mask is the brain mask less those voxels, the field is 0 on the rest of the brain mask, and every later stage must work in valid_mask (crate::utils::phase_offset_removal has the measurements; until 2026-11 those voxels kept their raw phase, which caused ring artefacts on GE and Philips data). On every other path valid_mask is the brain mask.
  • Offset removal off, or Laplacian unwrapping. Phase-offset removal and bipolar correction run only with ROMEO (do_offset below); 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_echo with romeo_params: template or individual mode, each echo’s own magnitude, ROMEO.jl’s phase2; until 2026-11 this path unwrapped each echo with echo 1’s magnitude and the next echo as phase2 whatever the mode), and the per-echo whole turns are aligned before combining (see field_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§

LaplacianRegion 🔒
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_complex on the wrapped complex echoes gives the phase per echo spacing, wrapped; it is unwrapped spatially, as MEDI’s iFreq = unwrapPhase(iMag, iFreq_raw, matrix_size) does, and divided by the echo spacing. The noise map (dp1, MEDI’s N_std) is converted the same way, to ppm. The fit itself is returned too (FieldMappingResult::complex_fit: iFreq_raw and N_std in 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 🔒
phase with every voxel outside mask set to zero.
qsmart_fit 🔒
B0EstimationMethod::QsmartEchofit on unwrapped echoes: QSMART’s echofit.m (qsmart_echofit) with QSMART’s γ, giving the total field over every voxel and the reliability map R_0.
remove_masked_mean 🔒
Subtract the mean over mask from values inside mask (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_fit on unwrapped echoes: the field in rad/s and the report. Echo count and params are checked by run_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; see super::per_echo): echo e‘s phase is unwrapped with FieldMappingConfig::unwrapping_algorithm (ROMEO with that echo’s magnitude) and divided by its own echo time, exactly as a single-echo series is (run_field_mapping with 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: MRPhaseUnwrap of 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).