Inverse — dmipy-fit
Analytical multi-compartment fitting. The measured signal is
with T2 and surface relaxivity as composable occupancy-gated factors on any compartment.
import numpy as np
from dmipy_fit.core.acquisition_scheme import acquisition_scheme_from_bvalues
from dmipy_fit.signal_models.cylinder_models import C1Stick
from dmipy_fit.signal_models.gaussian_models import G1Ball, G2Zeppelin
from dmipy_fit.core.modeling_framework import MultiCompartmentModel
# a small two-shell scheme (b in s/m^2 — multiply s/mm^2 by 1e6) + your DWI voxels
rng = np.random.default_rng(0)
bvals = np.r_[0.0, np.full(32, 1e9), np.full(32, 2e9)]
bvecs = np.zeros((65, 3)); v = rng.standard_normal((64, 3))
bvecs[1:] = v / np.linalg.norm(v, axis=1, keepdims=True)
scheme = acquisition_scheme_from_bvalues(bvals, bvecs, delta=0.01, Delta=0.03)
data = rng.uniform(0.1, 1.0, size=(10, 65)) # <- swap in your DWI voxels
model = MultiCompartmentModel([G1Ball(), G2Zeppelin(), C1Stick()])
fit = model.fit(scheme, data, solver="jax")
fractions = fit.fitted_parameters["partial_volume_2"]
- Compartments: sticks/cylinders, sphere, ball/zeppelin, plane, capped cylinder.
- Dispersion: Watson / Bingham; Gamma diameter distributions.
- CSD: Tournier / cvxpy / OSQP-JAX; DTI, IVIM.
- White matter:
white_matter.build_white_matter_model()(a decoupled diffusion-only canonical model — surface relaxivity reweights intra vs extra) andwhite_matter.t2_spectrum_mwf()(standard NNLS myelin-water fraction).
See the Model catalog for every published inverse model (NODDI, Ball&Stick, Standard Model, SMT, NEXI, VERDICT, SANDI, …) built from these primitives in a few lines.
What's new since the original dmipy
The original dmipy (Fick, Wassermann & Deriche 2019)
established the compartment-model grammar — compose sticks, zeppelins, spheres and
dispersion distributions into a MultiCompartmentModel and fit it. This engine keeps that
grammar verbatim and rebuilds the machinery underneath it. The headline changes:
GPU fitting (solver="jax")
Fitting is a JAX program. The forward model is JIT-compiled and the optimiser
(bounded L-BFGS-B, with a coarse spherical brute-grid initialisation) is vmap-ed across
voxels, so a whole brain fits in one vectorised GPU call instead of the original's
per-voxel CPU loop. The same code runs on CPU (JAX_PLATFORMS=cpu) for reference and CI.
fit = model.fit(scheme, data, solver="jax") # vmap over voxels, GPU if available
OccupancyGatedModel — physics beyond diffusion
The original was diffusion-only: one compartment, one E(b). Here any compartment can be
wrapped in an OccupancyGatedModel that carries composable, opt-in physics factors —
transverse relaxation (T2) and intra-pore + exterior surface relaxivity — so the signal
is diffusion × relaxation × surface, per compartment:
from dmipy_fit.signal_models.gaussian_models import G2Zeppelin
from dmipy_fit.signal_models.attenuation import (
OccupancyGatedModel, TransverseRelaxation, ExteriorSurfaceRelaxivity)
extra = OccupancyGatedModel(G2Zeppelin(), [
ExteriorSurfaceRelaxivity(S_ext_over_V=2e5), TransverseRelaxation()])
This is what lets the unified white-matter model carry the surface-relaxivity inter-compartment weighting (surface relaxivity reweights intra vs extra) that plain stick+zeppelin cannot represent.
Noise-aware fitting
The signal magnitude is Rician, not Gaussian, at the SNR of real dMRI. Pass eta=True and the
forward model carries a Rician noise floor η, fitting against \(\sqrt{S^2 + \eta^2}\) so
parameters are not biased upward by the noise floor at high b:
model = MultiCompartmentModel([G1Ball(), C1Stick()], eta=True) # jointly fit the noise floor
fit = model.fit(scheme, data, solver="jax")
noise_floor = fit.fitted_parameters["eta"] # dimensionless, ~ 1/SNR
The CSD solvers accept a matching eta= bias correction (\(\sqrt{\max(S^2-\eta^2,\,0)}\)) before
the QP solve.
Citation graph & auto-generated methods
Every signal model and every physical constant carries its own _citations. Walk the graph of
whatever you composed and get a ready-to-paste Methods paragraph and BibTeX — reproducibility
falls out of the model object itself:
from dmipy_fit.audit import walk_citation_graph, generate_methods_section, generate_bibtex
graph = walk_citation_graph(model)
print(generate_methods_section(graph)) # "...modeled using ... Zhang H et al. (2012) ..."
open("refs.bib", "w").write(generate_bibtex(graph))
Physical constants come from one cited catalogue — see Biophysical constants.
One physical representation, shared with the forward truth
The acquisition and substrate objects are sim-owned and shared: the same from_pgse(...)
scheme drives both the analytical fit here and the Monte-Carlo ground truth in dmipy-sim, and
the same Substrate parametrises both. There was no forward-simulation counterpart in the
original. See Acquisition sequences and Substrate & geometry.
Current scope (this release)
The mission is a physics-complete, sequence- and substrate-agnostic MRI computational
forward model — the free waveform G(t) and an arbitrary substrate as the base representation,
every physical effect on the same footing — paired with its analytical inverse. This release
is the transverse-magnetisation slice of it: diffusion + T2 + surface relaxivity + permeable
exchange + magnetization transfer, with ideal instantaneous pulses. Susceptibility, gradient-/stimulated-echo and T1 are
part of the model but not in the released public scope yet — so the boundary above is a
release boundary, not the ceiling.