Skip to content

Commit 9d763aa

Browse files
committed
docs: add more information on NUFFT with ORC.
1 parent 60f823d commit 9d763aa

File tree

6 files changed

+127
-25
lines changed

6 files changed

+127
-25
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ To build the documentation locally, you can run the following commands :
149149
# you can specify a specfic backend to run the example with an environment variable:
150150
export MRINUFFT_BACKEND=finufft
151151
python -m sphinx-build docs docs_build
152+
# If you don't care about examples you can use
153+
python -m sphinx-build docs docs_build -D plot_gallery=0
152154
# view the documentation in your browser
153155
python -m http.server --directory docs_build 8080
154156
# open localhost:8080 in your browser

docs/mrinufft_convention.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.. include:: <isonum.txt>
2-
2+
.. _mri-nufft-interface:
33
===============================
44
MRI-NUFFT Interfaces Convention
55
===============================

docs/nufft.rst

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.. include:: <isonum.txt>
22
.. _NUFFT:
3+
34
====================
45
The NUFFT Operator
56
====================
@@ -96,6 +97,10 @@ Extension of the Acquisition model
9697
----------------------------------
9798
The MRI acquisition model can be extended in two main ways. First by taking into account Parallel Imaging, where multiple coils are receiving data, each with a dedicated sensitivity profile.
9899

100+
.. tip::
101+
MRI-NUFFT provides the `FourierOperator` interface to implement all the physical model described below. See :ref:`nufft-interface` for the standard, and :class:`FourierOperatorBase <mrinufft.operators.base.FourierOperatorBase>`
102+
103+
99104
Parallel Imaging Model
100105
~~~~~~~~~~~~~~~~~~~~~~
101106

@@ -124,6 +129,7 @@ Where :math:`S_1, \dots, S_L` are the sensitivity maps of each coil. Such sensit
124129
TODO Add ref to SENSE and CG-Sense
125130
126131
.. _nufft-orc:
132+
127133
Off-resonance correction model
128134
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
129135

@@ -152,13 +158,23 @@ Yielding the following model, where :math:`L \ll M, N` regular Fourier transform
152158
153159
x(\boldsymbol{u_n}) = \sum_{\ell=1}^L c_{\ell, n} \sum_{m}^M y(t_m) b_{m, \ell} e^{2\imath\pi \boldsymbol{u} \cdot \boldsymbol{\nu_i}}
154160
155-
The coefficients :math:`B=(b_{m, \ell}) \in \mathbb{C}^{M\times L}` and :math:`C=(c_\ell, n) \in \mathbb{C}^{L\times N}` can be (optimally) estimated for any given :math:`L` by solving the following matrix factorisation problem [3]_:
161+
The coefficients :math:`B=(b_{m, \ell}) \in \mathbb{C}^{M\times L}` and :math:`C=(c_\ell, n) \in \mathbb{C}^{L\times N}` can be (optimally) estimated for any given :math:`L` by solving the following matrix factorisation problem [4]_:
156162

157163
.. math::
158164
159165
\hat{B}, \hat{C} = \arg\min_{B,C} \| E- BC\|_{fro}^2
160166
161-
Where :math:`E_mn = e^{i\Delta\omega_0(u_n)t_m}`.
167+
Where :math:`E_{mn} = e^{i\Delta\omega_0(u_n)t_m}`.
168+
169+
.. note::
170+
171+
The estimation of the B and C methods are provided in the :py:mod:`mrinufft.extras.field_map` module.
172+
Other methods like MTI [5]_ and MFI [6]_ are also available.
173+
174+
.. tip::
175+
176+
You can use the method :func:`.with_off_resonance_correction <mrinufft.operators.base.FourierOperatorBase.with_off_resonance_correction>` to augment an existing operator with off-resonance correction capability.
177+
162178

163179

164180
.. TODO Add Reference to the Code doing this.
@@ -219,8 +235,10 @@ the projection operator :math:`\boldsymbol{\Phi}` commutes with the Fourier tran
219235
220236
that is, computation now involves :math:`K \ll T` Fourier Transform operations, each with the same sampling trajectory, which can be computed by levaraging efficient NUFFT implementations for conventional static MRI.
221237

222-
The Non Uniform Fast Fourier Transform
223-
======================================
238+
.. _nufft-algo:
239+
240+
The Non Uniform Fast Fourier Transform in practice
241+
==================================================
224242

225243

226244
In order to lower the computational cost of the Non-Uniform Fourier Transform, the main idea is to move back to a regular grid where an FFT would be performed (going from a typical :math:`O(MN)` complexity to :math:`O(M\log(N))`). Thus, the main steps of the *Non-Uniform Fast Fourier Transform* are for the type 1:
@@ -229,20 +247,46 @@ In order to lower the computational cost of the Non-Uniform Fourier Transform, t
229247
2. Perform the (I)FFT on this image
230248
3. Downsampling to the final grid, and apply some bias correction.
231249

232-
This package exposes interfaces to the main NUFFT libraries available. The choice of the spreading method (ie the interpolation kernel) in step 1. and the correction applied in step 3. are the main theoretical differences between the methods.
250+
This package exposes interfaces to the main NUFFT libraries available (See :mod:`mrinufft.operators.interfaces`). The choice of the spreading method (ie the interpolation kernel) in step 1. and the correction applied in step 3. are the main theoretical differences between the methods.
233251

234252
Type 2 transforms perform those steps in reversed order.
235253

236-
.. TODO Add Reference to all the NUFFT methods article
237-
Maybe to Fessler never-going-to-be-published book.
238-
239254

240255
Density Compensation
241256
====================
242257

243258

259+
In non-uniform sampling, such as radial or spiral MRI, the acquired k-space samples :math:`k_m` are not equally spaced. As a result, each sample does not contribute equally to the final image. To account for the non-uniform sampling density, a set of weights :math:`w_m`—called the density compensation function (DCF)—is applied to the measured data :math:`y_m`.
260+
261+
In the adjoint NUFFT (type 2), which maps from non-uniform k-space onto the image grid, the operation can be mathematically written as:
262+
263+
.. math::
264+
265+
x_n = \sum_{m=1}^M y_m \, w_m \, e^{-i 2\pi k_m \cdot x_n}
266+
267+
where:
268+
269+
- :math:`x_n` is the reconstructed pixel value at position :math:`x_n`,
270+
- :math:`y_m` are the measured non-uniform k-space data,
271+
- :math:`w_m` is the density compensation weight for sample :math:`m`,
272+
- :math:`k_m` is the k-space sampling location.
273+
274+
The choice of :math:`w_m` depends on the trajectory:
275+
276+
- **Analytical weights**: For simple trajectories (e.g., radial), :math:`w_m` may have closed forms.
277+
- **Voronoi weights**: :math:`w_m` corresponds to the area/volume of Voronoi cells around each sample :math:`k_m`.
278+
- **Iterative methods**: For arbitrary trajectories, :math:`w_m` can be estimated via iterative algorithms that minimize reconstruction artifacts.
279+
280+
The DCF is typically applied before the NNUFFT ensuring each k-space measurement contributes proportionally to its neighborhood. Proper density compensation is crucial for artifact-free, quantitatively accurate image reconstruction.
281+
282+
.. note::
283+
In ``mri-nufft``, density compensation can be specified when initializing the NUFFT operator (via the ``density`` argument) as either a precomputed array, a method name (e.g., ``'voronoi'``, ``'pipe'``), or by providing your own function.
284+
See :class:`FourierOperatorBase <mrinufft.operators.base.FourierOperatorBase>` and the ``compute_density`` API for more details. Several geometry-based and NUFFT-based DCF methods are available in the :mod:`mrinufft.density` module.
244285

286+
.. tip::
287+
For consistent scaling, density compensation weights should be normalized, so that the total signal energy is preserved across different trajectories and density choices. If you supply your own weights (See the for instance the normalization done for the :func:`pipe <mrinufft.operators.interfaces.cufinufft.MRICufinufft.pipe>` method)
245288

289+
246290
Other Application
247291
=================
248292
Apart from MRI, The NUFFT operator is also used for:
@@ -261,3 +305,5 @@ References
261305
.. [2] Noll, D. C., Meyer, C. H., Pauly, J. M., Nishimura, D. G., Macovski, A., "A homogeneity correction method for magnetic resonance imaging with time-varying gradients", IEEE Transaction on Medical Imaging (1991), pp. 629-637.
262306
.. [3] Fessler, J. A., Lee, S., Olafsson, V. T., Shi, H. R., Noll, D. C., "Toeplitz-based iterative image reconstruction for MRI with correction for magnetic field inhomogeneity", IEEE Transactions on Signal Processing 53.9 (2005), pp. 3393–3402.
263307
.. [4] D. F. McGivney et al., "SVD Compression for Magnetic Resonance Fingerprinting in the Time Domain," IEEE Transactions on Medical Imaging (2014), pp. 2311-2322.
308+
.. [5] D. C. Noll, C. H. Meyer, J. M. Pauly, D. G. Nishimura and A. Macovski, "A homogeneity correction method for magnetic resonance imaging with time-varying gradients," in IEEE Transactions on Medical Imaging, vol. 10, no. 4, pp. 629-637, Dec. 1991, doi: 10.1109/42.108599
309+
.. [6] Man, L.-C., Pauly, J.M. and Macovski, A. (1997), Multifrequency interpolation for fast off-resonance correction. Magn. Reson. Med., 37: 785-792. https://doi.org/10.1002/mrm.1910370523

src/mrinufft/extras/field_map.py

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
"""Field map generator module."""
1+
"""Field map Module.
2+
3+
This module provides methods to generate dummy B0map as well as estimation of the
4+
bilinearization of the off-resonance model (See :ref:`nufft-orc` for a detailed
5+
explanation).
6+
"""
27

38
import numpy as np
9+
from collections.abc import Callable
410

511
from numpy.typing import ArrayLike, NDArray
612

@@ -149,6 +155,10 @@ def _make_sphere(shape, frac_radius=0.3):
149155
time points; nt = len(readout_time).
150156
E: NDArray
151157
[nbins, nt] exponential off-resonance phase matrix at input histogram bins.
158+
159+
See Also
160+
--------
161+
:ref:`nufft-orc`
152162
""",
153163
)
154164

@@ -180,7 +190,7 @@ def get_complex_fieldmap_rad(
180190
181191
See Also
182192
--------
183-
:ref:`_nufft-orc`
193+
:ref:`nufft-orc`
184194
185195
"""
186196
xp = get_array_module(b0_map)
@@ -242,6 +252,14 @@ def _create_histogram(
242252
class C_lazy:
243253
"""A lazy version of the C interpolator.
244254
255+
Instead of storing C as a ``(L, Nx, Ny, Nz)`` array, we store the quantize
256+
version of shape L, N_bins, and the indexes mapping each voxel of the
257+
complex-valued field-map to the bins values.
258+
259+
When accessing the L-th interpolator, the full ``(Nx, Ny, Nz)`` associated array is
260+
generated.
261+
262+
245263
Parameters
246264
----------
247265
C_sr: NDArray of shape (L, Nbinsreal, Nbinsimag)
@@ -282,14 +300,17 @@ def __getitem__(self, i: int) -> NDArray:
282300

283301
@with_numpy_cupy
284302
def _full_C(
285-
field_map: NDArray,
303+
field_map: NDArray[np.complex64],
286304
C_small: NDArray,
287305
n_bins: tuple[int, int],
288306
lazy: bool = False,
289307
):
290308
"""
291309
Generate a full spatial interpolator C from a quantized version.
292310
311+
If `lazy=True` uses the an array like structure to generate the
312+
interpolators on the fly.
313+
293314
Parameters
294315
----------
295316
field_map: NDArray
@@ -300,7 +321,12 @@ def _full_C(
300321
Number of bins for the real and imaginary part
301322
lazy: bool, default False
302323
If True, returns a lazy version of the C matrix.
303-
Return
324+
325+
Returns
326+
-------
327+
NDArray if lazy = False
328+
329+
C_Lazy if lazy = True
304330
"""
305331
xp = get_array_module(field_map)
306332
fr = field_map.real.ravel()
@@ -329,7 +355,9 @@ def _full_C(
329355
return xp.ascontiguousarray(C_big.reshape(-1, *field_map.shape))
330356

331357

332-
def _get_svds(xp, partial_svd):
358+
def _get_svds(
359+
xp, partial_svd
360+
) -> Callable[[NDArray, int], tuple[NDArray, NDArray, NDArray]]:
333361
if partial_svd:
334362
if xp.__name__ == "cupy":
335363
from cupyx.scipy.sparse.linalg import svds
@@ -355,22 +383,29 @@ def compute_svd_coefficients(
355383
lazy: bool = False,
356384
partial_svd: bool = True,
357385
):
358-
"""
359-
Compute off-resonance correction coefficients using tSVD.
386+
r"""
387+
Compute off-resonance correction coefficients using an SVD.
388+
389+
Solves :math:`\arg\min_{B,C} = \|E - BC \|_{F}^2`
390+
391+
In practise it utilizes truncated SVD to extract L leading singular vectors from
392+
the weighted exponentiated phase matrix sampled at field map histogram centers.
360393
361-
Utilizes truncated SVD to extract L leading singular vectors from the weighted
362-
exponentiated phase matrix sampled at field map histogram centers. Outputs tSVD
363-
basis (B), interpolation matrix (C), and exponential phase matrix (E).
364394
365395
Parameters
366396
----------
367397
${base_params}
368-
369398
partial_svd: bool, default True
370399
If True, only compute the L components required for the estimation, not
371400
the full SVD.
372401
373402
${returns}
403+
404+
References
405+
----------
406+
Sutton BP, Noll DC, Fessler JA. Fast, iterative image reconstruction for MRI
407+
in the presence of field inhomogeneities. IEEE Trans Med Imaging. 2003
408+
Feb;22(2):178-88. doi: 10.1109/tmi.2002.808360. PMID: 12715994.
374409
"""
375410
field_map = get_complex_fieldmap_rad(field_map)
376411
xp = get_array_module(field_map)
@@ -416,6 +451,13 @@ def compute_mti_coefficients(
416451
${base_params}
417452
418453
${returns}
454+
455+
References
456+
----------
457+
D. C. Noll, C. H. Meyer, J. M. Pauly, D. G. Nishimura and A. Macovski, "A
458+
homogeneity correction method for magnetic resonance imaging with
459+
time-varying gradients," in IEEE Transactions on Medical Imaging, vol. 10,
460+
no. 4, pp. 629-637, Dec. 1991, doi: 10.1109/42.108599
419461
"""
420462
field_map = get_complex_fieldmap_rad(field_map)
421463
xp = get_array_module(field_map)
@@ -483,6 +525,13 @@ def compute_mfi_coefficients(
483525
${base_params}
484526
485527
${returns}
528+
529+
References
530+
----------
531+
Man, L.-C., Pauly, J.M. and Macovski, A. (1997), Multifrequency
532+
interpolation for fast off-resonance correction. Magn. Reson. Med., 37:
533+
785-792. https://doi.org/10.1002/mrm.1910370523
534+
486535
"""
487536
# Format the input and apply the weight option
488537

src/mrinufft/operators/base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def _safe_squeeze(self, arr):
206206
return arr
207207

208208
@abstractmethod
209-
def op(self, data):
209+
def op(self, data: NDArray) -> NDArray:
210210
"""Compute operator transform.
211211
212212
Parameters
@@ -222,7 +222,7 @@ def op(self, data):
222222
pass
223223

224224
@abstractmethod
225-
def adj_op(self, coeffs):
225+
def adj_op(self, coeffs: NDArray) -> NDArray:
226226
"""Compute adjoint operator transform.
227227
228228
Parameters
@@ -237,7 +237,7 @@ def adj_op(self, coeffs):
237237
"""
238238
pass
239239

240-
def data_consistency(self, image_data, obs_data):
240+
def data_consistency(self, image_data: NDArray, obs_data: NDArray) -> NDArray:
241241
"""Compute the gradient data consistency.
242242
243243
This is the naive implementation using adj_op(op(x)-y).

src/mrinufft/operators/off_resonance.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,22 @@ class MRIFourierCorrected(FourierOperatorBase):
5454
- If ``{"name":name, **kwargs}`` use an existing
5555
method in `extra_field_map.py` parameterize by kwargs.
5656
- If``tuple[NDArray, NDArray]`` use this directly as the decomposition
57-
(B and C)
57+
(B and C)
5858
5959
Notes
6060
-----
6161
The total field map used to calculate the field coefficients is
6262
``field_map = R2*_map + 1j * B0_map``. If R2* is not provided,
6363
the field is purely imaginary: ``field_map = 1j * B0_map``.
6464
65+
You can also use the method :py:func:`.with_off_resonance_correction
66+
<mrinufft.operators.base.FourierOperatorBase.with_off_resonance_correction>`
67+
to augment an existing operator with off-resonance correction capability.
68+
69+
6570
See Also
6671
--------
67-
:ref:`_nufft-orc`
72+
:ref:`nufft-orc`
6873
6974
"""
7075

0 commit comments

Comments
 (0)