fn unwrap_individual<P: AsRef<[f64]> + Sync, M: AsRef<[f64]> + Sync>(
phases: &[P],
mags: &[M],
tes: &[f64],
mask: &[u8],
params: &RomeoParams,
nx: usize,
ny: usize,
nz: usize,
) -> Vec<Vec<f64>>Expand description
Individual multi-echo unwrapping: ROMEO.jl’s unwrap_individual! (src/unwrapping.jl,
v1.6.0).
Each echo is spatially unwrapped on its own, with its own magnitude and the previous echo (echo
2 for echo 1) as phase2 for the gradient-coherence weight and the seed’s turn. With
correct_global, as in ROMEO.jl, two corrections follow:
- per echo, inside the 3D
unwrap!thatunwrap_individual!calls with all its keyword arguments:wrapped .-= 2π·median(round(φ[mask] / 2π))(finite voxels only), so each echo’s median turn is 0; - then across echoes,
correct_multi_echo_wraps!(correct_multi_echo_wraps).
Step 1 was missing here until 2026-11; on the data where it was measured (Q0748 audit, workstream C) every echo’s median turn was already 0, so it changed nothing there, but in general it sets echo 1’s global turn and with it every echo’s.
phase2 and threads. ROMEO.jl runs the echoes in a Threads.@threads loop and reads
phase2 = wrapped[:,:,:,e2] while the loop unwraps wrapped in place, so whether echo i
sees echo i − 1 wrapped or already unwrapped depends on the thread schedule (≈1 % of voxels of
a 16-echo 7 T scan’s last echo varied between runs; Q0748 audit). Here the echoes run in order,
which is ROMEO.jl with one thread, the deterministic reading of its code: echo i ≥ 2 sees
echo i − 1 unwrapped (and globally corrected), echo 1 sees echo 2 wrapped. It matters beyond
the seed’s turn because ROMEO.jl’s γ wraps only once (src/utility.jl), so the
gradient-coherence weight of an unwrapped phase2 differs where neighbours differ by more than
3π (on tests/romeo_validity_romeojl_parity.rs’s phantom, 46 voxels of echo 3 changed their
turns between the two readings). Until 2026-11 QSM.rs always read the wrapped echo (and ran the
echoes in parallel), which is what ROMEO.jl reads when the echoes start at the same time.
Medians. ROMEO.jl’s median of an even number of turn counts averages the two middle
ones, which can give a half turn (a shift by π); here the upper middle count is taken, so the
correction is always a whole number of turns. The two agree whenever the middle counts are
equal, as they are on any real mask.