← About · Back to the dashboard

The physics in this tool

These are the models and relations the analysis assumes, with a link to the lines of code that implement each, so that they can be checked directly. Build: 7988056203, built 2026-10-20.

The signal models and geometry

All of these are in one short file, nmrdash/physics.py, which uses only NumPy; every other part of the tool calls these functions, so what you read there is what runs.

Exponential T₂ decay

S(t) = A · exp(−t / T₂)

Transverse relaxation of one population, measured as the decay of the echo amplitudes in a CPMG train.

The code: physics.py: exp_decay, physics.py: decay_factor

Multi-exponential decay

S(t) = Σᵢ Aᵢ · exp(−t / T₂ᵢ) + c

Several populations relaxing independently (slow exchange), plus a baseline c. The multi-exponential fit.

The code: physics.py: multi_exp

Saturation recovery (T₁)

S(t) = Σᵢ Aᵢ · (1 − exp(−t / T₁ᵢ)) + c

Recovery of the longitudinal magnetisation after saturation, for one or two T₁ populations.

The code: physics.py: saturation_recovery, physics.py: multi_saturation, physics.py: recovery_factor

Diffusion attenuation

exp(−b · D)

Attenuation of the signal by free diffusion with coefficient D at diffusion weighting b.

The code: physics.py: diffusion_attenuation

Diffusion weighting b of the stimulated echo

b = (γG)² · τ² · (Δ + 2τ/3)

How strongly each diffusion delay τ weights diffusion, for a stimulated echo with storage time Δ in a static gradient G. Calculated from the gradient setting, Δ and τ in acqu.par (the echo time does not enter).

The code: physics.py: b_value_stimulated_echo, physics.py: geometric_grid, physics.py: gradient_implied_by_kea

T₂–D signal

S(t, b) = Σₖ Aₖ · exp(−t / T₂ₖ) · exp(−b · Dₖ), Aₖ ≥ 0

Several populations, each with its own T₂ and D (the few-population T₂–D model; no offset).

The code: physics.py: t2d_signal, physics.py: t2d_factors

Inversion kernels

K₁D[i, j] = exp(−tᵢ / T₂ⱼ); K_D[i, j] = exp(−bᵢ Dⱼ); K₂D = K₁D ⊗ K_D

The matrices the regularised inversions invert to obtain a T₂ distribution (1D) or a T₂–D map (2D, separable).

The code: physics.py: t2_kernel, physics.py: diffusion_kernel

Range of D the b-values can resolve

1 / b_max ≤ D ≤ 5 / b_min

A rule of thumb for which diffusion coefficients the measured b-values can tell apart; outside it a regularised map is extrapolation.

The code: physics.py: resolvable_diffusion_window

Position along the gradient

z = f / (γ·G); Δz = 1 / (N · dwell · γ·G)

Frequency to position for the static-gradient profile (FT of each echo), and the spatial resolution set by the acquisition window.

The code: physics.py: z_um_from_frequency, physics.py: native_resolution_um

What is not modelled

How numbers are estimated from the models

Noise, goodness of fit, model selection, intervals and the regularisation of the inversions are in nmrdash/inference.py.

Phasing and integrating the echoes

θ = −arg(Σ all points); decay_k = Σ_points Re(e^{iθ} · echo_k)

One zero-order phase puts the signal in the real channel; each echo is then summed to one number, giving the decay.

The code: inference.py: phase_and_integrate

Noise level from the imaginary channel

σ = 1.4826 · median(|x − median(x)|)

After phasing the imaginary channel holds only noise, so its robust spread measures the noise on the real decay without any model.

The code: inference.py: noise_sigma_mad

Fit quality

RSS = Σ (yᵢ − mᵢ)²; χ²_red = RSS / (σ² · dof)

The residual sum of squares and its comparison with the measured noise (about 1 when the model explains everything the noise allows).

The code: inference.py: sum_squared_residuals, inference.py: reduced_chi2, inference.py: chi2_per_point

Choosing the number of components

AIC = N ln(RSS/N) + 2k; BIC = N ln(RSS/N) + k ln N

Penalised measures for comparing fits with different numbers of components; only differences between models mean anything.

The code: inference.py: information_criteria

Confidence intervals from least squares

estimate ± t(level, dof) · SE

Intervals from the least-squares covariance with a Student-t multiplier (derived quantities such as fractions use the delta method).

The code: inference.py: student_t_critical

Regularisation of the inversions

min ‖K x − y‖² + w² ‖D₂ x‖², x ≥ 0; w = 1/√(pasdata² · pas³ · α)

The smoothness-penalised, non-negative least-squares problem the 1D and 2D inversions solve; larger α means less smoothing.

The code: inference.py: second_derivative_matrix, inference.py: tikhonov_weighting