qsm_core/lib.rs
1//! # QSM-Core
2//!
3//! A Rust library for Quantitative Susceptibility Mapping (QSM) of the brain.
4//!
5//! QSM-Core reconstructs magnetic susceptibility maps from MRI phase data:
6//! brain extraction, phase unwrapping, background field removal, dipole
7//! inversion, and susceptibility source separation.
8//!
9//! ## Which API should I use?
10//!
11//! The crate offers two entry points at different levels:
12//!
13//! - **[`pipeline`] — the high-level API.** Describe a scan with [`ScanMetadata`]
14//! and a [`QsmPipelineConfig`], then call the `run_*` stage functions. This is
15//! the easiest way to go from phase data to a susceptibility map and the
16//! recommended starting point.
17//! - **Algorithm building blocks — the low-level API.** Each algorithm
18//! ([`bgremove::vsharp()`], [`inversion::tv_admm()`], …) is a plain function taking
19//! a [`Grid`], a `*Params` struct, and (for iterative methods) a progress
20//! callback. Use these when you want to wire stages together yourself.
21//!
22//! ```no_run
23//! use qsm_core::{Grid, bet, unwrap, bgremove, inversion};
24//! use qsm_core::bet::BetParams;
25//! use qsm_core::bgremove::VsharpParams;
26//! use qsm_core::inversion::TvParams;
27//!
28//! # fn run(phase: &[f64], magnitude: &[f64]) {
29//! let grid = Grid::new(128, 128, 64, 1.0, 1.0, 1.0);
30//! let bdir = (0.0, 0.0, 1.0);
31//!
32//! let mask = bet::run_bet(magnitude, &grid, &BetParams::default(), bet::FslLeftRightOrder::Radiological, |_, _| {});
33//! let unwrapped = unwrap::laplacian_unwrap(phase, &mask, &grid, unwrap::LaplacianSolver::Dct);
34//! let (local, eroded) = bgremove::vsharp(&unwrapped, &mask, &grid, &VsharpParams::default(), |_, _| {}).unwrap();
35//! let chi = inversion::tv_admm(&local, &eroded, &grid, bdir, &TvParams::default(), |_, _| {});
36//! # let _ = chi;
37//! # }
38//! ```
39//!
40//! ## Modules
41//!
42//! **High-level pipeline**
43//! - [`pipeline`] — config-driven full reconstruction
44//!
45//! **Algorithm building blocks**
46//! - [`bet`] — brain extraction (BET)
47//! - [`unwrap`] — phase unwrapping (ROMEO, Laplacian, best path, PRELUDE)
48//! - [`bgremove`] — background field removal (V-SHARP, SHARP, RESHARP, PDF, iSMV, LBV, HARPERELLA)
49//! - [`inversion`] — dipole inversion (TKD, TSVD, Tikhonov, TV, NLTV, RTS, MEDI incl. MEDI-mSMV, iLSQR, STAR-QSM, TGV)
50//! - [`separation`] — paramagnetic/diamagnetic source separation (χ-separation, R2\*-QSM, WaveSep)
51//! - [`swi`] — susceptibility weighted imaging (CLEAR-SWI, SMWI)
52//! - [`complex`] — real/imaginary input to magnitude/phase
53//! - [`fieldmap`] — multi-echo phase combination and B0 field mapping
54//! - [`r2star`] — R2\*/T2\* mapping (ARLO)
55//! - [`mask`] — mask thresholding and morphology
56//! - [`homogeneity`] — receive-field bias correction
57//!
58//! **Core types & I/O**
59//! - [`Grid`] — 3D volume descriptor shared by every algorithm
60//! - [`io`] — NIfTI read/write
61//!
62//! ## Feature Flags
63//!
64//! - **`parallel`** — enables [Rayon](https://docs.rs/rayon)-based multi-threading for FFT and iterative solvers
65//! - **`simd`** — enables SIMD acceleration via the [`wide`](https://docs.rs/wide) crate
66//!
67//! ## Algorithms
68//!
69//! | Stage | Methods |
70//! |-------|---------|
71//! | Brain extraction | BET |
72//! | Phase unwrapping | ROMEO, Laplacian, best path (3D-SRNCP), PRELUDE |
73//! | Background removal | V-SHARP, SHARP, RESHARP, PDF, iSMV, LBV, SDF |
74//! | Dipole inversion | TKD, TSVD, Tikhonov, TV-ADMM, NLTV, RTS, MEDI, TGV, iLSQR, STAR-QSM |
75//! | Combined unwrap+BFR | HARPERELLA, iHARPERELLA |
76//! | SWI | CLEAR-SWI |
77//! | Separation | Chi-separation (Shin 2021 iLSQR-initialized, MEDI-based) |
78//! | Input | Real/imaginary to magnitude/phase (slice-chopping correction) |
79//! | Multi-echo | MCPC-3D-S, R2\*/T2\* (ARLO), bias correction |
80//! | Utilities | Frangi vesselness, surface curvature, Otsu thresholding, QSMART |
81
82// ============================================================================
83// Internal plumbing — public so advanced/downstream code can reach it, but
84// hidden from the documented API surface. No stability guarantees.
85// ============================================================================
86#[macro_use]
87#[doc(hidden)]
88pub mod par;
89#[doc(hidden)]
90pub mod grid;
91#[doc(hidden)]
92pub mod fft;
93#[doc(hidden)]
94pub mod priority_queue;
95#[doc(hidden)]
96pub mod region_grow;
97#[doc(hidden)]
98pub mod kernels;
99#[doc(hidden)]
100pub mod solvers;
101#[doc(hidden)]
102pub mod utils;
103
104// ============================================================================
105// Core types
106// ============================================================================
107// `SliceGapError` is deliberately not re-exported here: it is reached as
108// `qsm_core::grid::SliceGapError`, matching `models::onnx::OnnxError` and
109// `models::download::DownloadError`. Each error type stays with the concern that
110// produces it rather than accumulating at the crate root. The one shared by every
111// algorithm entry point, for input it cannot process, is `qsm_core::error::InputError`.
112pub use grid::Grid;
113pub use utils::echo_time::EchoTime;
114
115/// [`InputError`](error::InputError): what an algorithm returns instead of panicking when it is
116/// handed input it cannot process.
117pub mod error;
118
119/// Physical constants (gyromagnetic ratio).
120pub mod constants;
121
122// ============================================================================
123// Deep-learning model registry and weight management
124// ============================================================================
125/// Registry of deep-learning QSM models and their downloadable ONNX weights.
126///
127/// Query the catalog with [`models::all_models`] / [`models::find_model`]. A host can
128/// supply weights from memory with [`models::provide_weights`] (the only source in WASM);
129/// native hosts can also use `$QSM_MODEL_DIR`, the cache, or fetch on use with the `download`
130/// feature. The `onnx` feature runs them via the pure-Rust `tract` engine (native and WASM).
131pub mod models;
132
133// ============================================================================
134// High-level pipeline
135// ============================================================================
136pub mod pipeline;
137
138// ============================================================================
139// Algorithm building blocks
140// ============================================================================
141pub mod bet;
142pub mod segment;
143pub mod unwrap;
144pub mod bgremove;
145pub mod inversion;
146pub mod separation;
147pub mod swi;
148
149/// Real/imaginary input: conversion to magnitude and phase.
150///
151/// [`complex_to_mag_phase`](complex::complex_to_mag_phase) also detects and undoes
152/// alternate-slice π "chopping" (a reconstruction artefact common in GE real/imaginary
153/// exports), deciding once for all echoes, and can negate the phase for vendors (GE) whose
154/// real/imaginary phase runs opposite to the Siemens/Philips convention.
155pub mod complex {
156 pub use crate::utils::complex::{
157 complex_to_mag_phase, slice_chopping_coherence, ChopCorrection, ComplexToMagPhaseParams, MagPhaseEchoes,
158 SliceChopping, CHOP_AMBIGUOUS_COHERENCE,
159 };
160}
161
162/// Multi-echo phase combination and B0 field mapping.
163///
164/// Building blocks for turning multi-echo wrapped phase into a B0 field map:
165/// phase-offset removal (MCPC-3D-S / ASPIRE), weighted B0 estimation,
166/// linear multi-echo fitting, and MEDI's complex fit (`Fit_ppm_complex`).
167pub mod fieldmap {
168 pub use crate::utils::multi_echo::{
169 phase_offset_removal, mcpc3ds_combine, calculate_b0_weighted, multi_echo_linear_fit,
170 bipolar_correction, field_to_hz, nan_box_smooth_3d, nan_box_smooth_3d_phase, gaussian_box_sizes,
171 PhaseOffsetParams, PhaseOffsetRemoval, CoilCombinationResult, LinearFitParams, LinearFitResult, B0WeightType,
172 multi_echo_robust_fit, RobustFitParams, RobustFitResult, RobustLoss, EchoQuality,
173 fit_ppm_complex, fit_ppm_complex_noise, FitPpmComplexParams, FitPpmComplexResult,
174 echo_spacing_problem, FIT_PPM_COMPLEX_SPACING_TOLERANCE_S,
175 };
176}
177
178/// R2\*/T2\* mapping from multi-echo magnitude (ARLO).
179pub mod r2star {
180 pub use crate::utils::r2star::{
181 arlo, arlo_echo_times_problem, t2star_from_r2star,
182 };
183}
184
185/// R2/T2 mapping from multi-echo spin-echo (EPG), and R2' = R2* − R2.
186///
187/// EPG-based fitting models imperfect refocusing (B1 < 1) so it removes the
188/// stimulated-echo bias that a mono-exponential fit suffers. [`r2prime`] combines
189/// the spin-echo R2 with a gradient-echo R2* (from [`r2star`]) for chi-separation,
190/// and returns the region R2 was measured over alongside the map — a spin-echo
191/// acquisition is often a slab rather than the whole head, and R2' is undefined
192/// outside it.
193///
194/// With no spin-echo acquisition there is no R2 to subtract; [`r2primenet`] predicts
195/// R2' from the GRE-derived R2* with a trained network (`onnx` feature), which is what
196/// lets the R2'-consuming [`separation`] methods run on GRE-only data.
197pub mod relaxometry {
198 pub use crate::utils::epg::{
199 epg_cpmg_echoes, r2_epg, r2prime, R2EpgParams, R2PrimeMap, EPG_TE_TOLERANCE_S,
200 };
201 // The parameters and limits are always exported, so hosts built without `onnx` can carry
202 // R2PRIMEnet settings and check a patch; only the inference functions need the feature.
203 pub use crate::utils::r2primenet::{
204 R2PrimeNetNorm, R2PrimeNetParams, AUTHORS_PATCH, WASM_PATCH, B0_TOLERANCE_T, TRAINED_B0_T,
205 POOL_MULTIPLE,
206 };
207 #[cfg(feature = "onnx")]
208 pub use crate::utils::r2primenet::{r2primenet, r2primenet_from_magnitude};
209}
210
211/// MP-PCA denoising for multi-volume data (e.g. multi-echo magnitude).
212///
213/// Random-matrix-theory denoising (Veraart 2016) that removes noise along the
214/// volume dimension while preserving spatial edges. Applying [`mppca_denoise`]
215/// to multi-echo magnitude before R2*/R2 fitting reduces relaxation-rate
216/// variance without blurring structure.
217pub mod denoise {
218 pub use crate::utils::denoise::mppca_denoise;
219}
220
221/// Gibbs-ringing removal by subvoxel shifts for k-space-truncated images: Bautista et al.'s 3-D
222/// method for 3-D acquisitions (the default), Kellner et al.'s 2-D method for 2-D multi-slice
223/// acquisitions (MRtrix3's implementation), and DIPY's 2-D implementation as a parity option.
224/// See [`GibbsMode`](crate::utils::gibbs::GibbsMode). Complementary to [`denoise`]: unringing
225/// removes truncation ringing, MP-PCA removes random noise. Recommended before R2*/R2 fitting.
226pub mod unring {
227 pub use crate::utils::gibbs::{gibbs_unring, gibbs_unring_masked, gibbs_unring_volume, GibbsMode};
228 pub use crate::utils::gibbs_mrtrix::{
229 gibbs_unring_2d, gibbs_unring_2d_complex, gibbs_unring_3d, gibbs_unring_3d_complex, MrtrixUnringParams,
230 };
231}
232
233/// Brain-mask thresholding and morphology.
234///
235/// Mask generation ([`otsu_threshold`](crate::mask::otsu_threshold)) plus
236/// morphological operations (erode/dilate/close/fill-holes, signal-gated erosion) and sphere
237/// masks.
238pub mod mask {
239 pub use crate::utils::mask::{
240 create_sphere_mask, apply_mask_zero, scanner_masked_phase, ScannerMaskedPhase,
241 SCANNER_FILL_MIN_FRACTION,
242 };
243 pub use crate::utils::threshold::otsu_threshold;
244 pub use crate::utils::morphology::{
245 erode_mask, dilate_mask, morphological_close, fill_holes,
246 signal_gated_erosion, SignalErosionParams,
247 };
248}
249
250/// Receive-field (B1−) bias correction for magnitude images.
251pub mod homogeneity {
252 pub use crate::utils::bias_correction::{
253 makehomogeneous, get_sensitivity, HomogeneityParams,
254 };
255}
256
257// ============================================================================
258// I/O
259// ============================================================================
260/// Cropping reconstruction to the region that carries signal, and putting the answer back.
261///
262/// FFT-based stages cost `O(N log N)` in the whole grid, not in the brain. See [`crop`] for why
263/// the box is also rounded up to FFT-friendly sizes, and for the wrap-around caveat.
264pub mod crop;
265
266/// Scan geometry from the NIfTI affine: B0 direction, obliquity, and resampling to an axial grid.
267///
268/// The dipole kernel lives in the voxel grid, so an oblique acquisition must either supply the
269/// true B0 direction or be resampled to a cardinal-aligned grid. Wrapped phase has to be
270/// resampled in the complex domain — see [`geometry::resample_complex_to_axial`].
271pub mod geometry;
272
273/// Rigid-body (6-DOF) registration between two volumes: recovering the transform that
274/// [`geometry`] can already apply.
275///
276/// Multi-orientation QSM needs two things from it — the orientations on one common grid, and
277/// the B0 direction each one contributes to that grid. The second comes out of the recovered
278/// rotation, because the object rotates and B0 does not; see
279/// [`registration::RigidTransform::b0_direction_in_fixed`].
280pub mod registration;
281
282/// Retrospective motion correction: co-register a series of repeats, runs or echoes onto one of
283/// them, in the complex domain, and re-derive B0 for each.
284///
285/// The image-domain half of motion correction — what a library consuming reconstructed NIfTI can
286/// do, as opposed to the navigator-driven prospective methods that need raw k-space. Two things
287/// it exists to get right: wrapped phase only ever moves through the complex domain
288/// ([`motion::MotionSeries::apply_complex`]), and a series that rotated has one B0 direction per
289/// volume ([`motion::VolumeMotion::bdir`]), not one for the series.
290pub mod motion;
291
292pub mod io;