Skip to content

Commit 4039070

Browse files
chaithyagrchaithyagrpaquiteauAsma TANABENE
authored
Add support for fiNUFFT spread interpolate (#224)
* Added * Fix * Remove bymistake add * Fix * Fixed lint * Lint * Added refbackend * Fix NDFT * feat: use finufft as ref backend. * feat(tests): move ndft vs nufft tests to own file. * Add support for pipe * \!docs_build try to run cufinufft tests * \!docs_build fix style * Added next235 for stability * Fix lint * Fix CUPY * WIP * Updates * fix back learn examples * move tto flatiron * fix black * Move to test on GPU * Update pyproject toml and use it in test-ci, to prevent duplication of dependencies and actually test them! * Make CI build shorter * Test run to run * \!docs_build Added * adding density normalization * Start support for finufft spread and interpolate (pipe) * Add support for density comp * Add support for normlize back * Added a bunch of extra codes * PEP fixes * Update siemens.py * Added fixes * add [docs] * Fixes and updates on the locatuions * Update the codes to be in sync with cufinufft / finufft 2.4.0 * Merged to master * [style] * Fix toml ffile [docs] * Fix toml ffile [docs] * Update testbatch stuff * Update the tests * Fixed tests againnnn * All set * Add finufft * Updates * Fixes * More fixes * make tests less strict again * remove bymistake * Remove strictness, everything works, hopefully [docs] * [docs] setup cufinufft also * [docs] fix style * [docs] more updates, fixes * pywavelets * [docs] fixes further * [docs] try again * [docs] final comments * [docs] method class use --------- Co-authored-by: chaithyagr <[email protected]> Co-authored-by: Pierre-antoine Comby <[email protected]> Co-authored-by: Asma TANABENE <[email protected]>
1 parent a2682fc commit 4039070

File tree

16 files changed

+248
-117
lines changed

16 files changed

+248
-117
lines changed

.github/workflows/test-ci.yml

Lines changed: 29 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -51,36 +51,9 @@ jobs:
5151
${{ env.create_venv }}
5252
${{ env.activate_venv }}
5353
python -m pip install --upgrade pip
54-
python -m pip install -e .[test]
55-
56-
- name: Install pynfft
57-
if: ${{ matrix.backend == 'pynfft' || env.ref_backend == 'pynfft' }}
58-
shell: bash
59-
run: |
60-
${{ env.activate_venv }}
61-
python -m pip install "pynfft2>=1.4.3"
62-
63-
- name: Install pynufft
64-
if: ${{ matrix.backend == 'pynufft-cpu' || env.ref_backend == 'pynufft-cpu' }}
65-
run: |
66-
${{ env.activate_venv }}
67-
python -m pip install pynufft
68-
69-
- name: Install finufft
70-
if: ${{ matrix.backend == 'finufft' || env.ref_backend == 'finufft'}}
71-
shell: bash
72-
run: |
73-
${{ env.activate_venv }}
74-
python -m pip install finufft
75-
76-
- name: Install Sigpy
77-
if: ${{ matrix.backend == 'sigpy' || env.ref_backend == 'sigpy'}}
78-
shell: bash
79-
run: |
80-
${{ env.activate_venv }}
81-
python -m pip install sigpy
82-
83-
- name: Install BART
54+
python -m pip install -e .[test,${{ env.ref_backend }},${{ matrix.backend }}]
55+
56+
- name: Install BART if needed
8457
if: ${{ matrix.backend == 'bart' || env.ref_backend == 'bart'}}
8558
shell: bash
8659
run: |
@@ -92,13 +65,6 @@ jobs:
9265
make
9366
echo $PWD >> $GITHUB_PATH
9467
95-
- name: Install torchkbnufft-cpu
96-
if: ${{ matrix.backend == 'torchkbnufft-cpu' || env.ref_backend == 'torchkbnufft-cpu'}}
97-
run: |
98-
${{ env.activate_venv }}
99-
python -m pip install torchkbnufft
100-
101-
10268
- name: Run Tests
10369
shell: bash
10470
run: |
@@ -127,33 +93,22 @@ jobs:
12793
- name: Install mri-nufft and finufft
12894
shell: bash
12995
run: |
130-
${{ env.create_venv }}
131-
${{ env.activate_venv }}
132-
python -m pip install --upgrade pip wheel
133-
python -m pip install -e .[test]
134-
python -m pip install cupy-cuda12x finufft "numpy<2.0"
96+
cd $RUNNER_WORKSPACE
97+
python --version
98+
python -m venv venv
99+
source $RUNNER_WORKSPACE/venv/bin/activate
100+
pip install --upgrade pip wheel
101+
pip install -e mri-nufft[test,finufft]
135102
136-
- name: Install torch with CUDA 12.1
137-
shell: bash
138-
if: ${{ matrix.backend != 'tensorflow'}}
139-
run: |
140-
${{ env.activate_venv }}
141-
${{ env.setup_cuda }}
142-
python -m pip install torch
143103
144104
- name: Install backend
145105
shell: bash
146106
run: |
147-
${{ env.activate_venv }}
148-
if [[ ${{ matrix.backend }} == "torchkbnufft-gpu" ]]; then
149-
python -m pip install torchkbnufft
150-
elif [[ ${{ matrix.backend }} == "tensorflow" ]]; then
151-
python -m pip install tensorflow-mri==0.21.0 tensorflow-probability==0.17.0 tensorflow-io==0.27.0 matplotlib==3.7
152-
elif [[ ${{ matrix.backend }} == "cufinufft" ]]; then
153-
python -m pip install "cufinufft<2.3"
154-
else
155-
python -m pip install ${{ matrix.backend }}
156-
fi
107+
source $RUNNER_WORKSPACE/venv/bin/activate
108+
export CUDA_BIN_PATH=/usr/local/cuda-12.4/
109+
export PATH=/usr/local/cuda-12.4/bin/:${PATH}
110+
export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64/:${LD_LIBRARY_PATH}
111+
pip install -e .[${{ matrix.backend }},autodiff]
157112
158113
- name: Run Tests
159114
shell: bash
@@ -270,13 +225,14 @@ jobs:
270225
python -m pip install -e .[extra,test,dev]
271226
python -m pip install finufft pooch brainweb-dl torch fastmri
272227
273-
- name: Install GPU related interfaces
228+
- name: Install Python deps
229+
shell: bash
274230
run: |
275231
${{ env.activate_venv }}
276232
${{ env.setup_cuda }}
277-
pip install cupy-cuda12x torch
278-
python -m pip install gpuNUFFT "cufinufft<2.3" sigpy scikit-image fastmri
279-
233+
python -m pip install --upgrade pip
234+
python -m pip install -e .[test,dev,finufft,cufinufft,gpuNUFFT,sigpy,smaps,autodiff,doc]
235+
280236
- name: Run examples
281237
shell: bash
282238
run: |
@@ -377,23 +333,22 @@ jobs:
377333
uses: actions/setup-python@v5
378334
with:
379335
python-version: "3.10"
380-
336+
337+
- name: Point to CUDA 12.4
338+
run: |
339+
export CUDA_BIN_PATH=/usr/local/cuda-12.4/
340+
export PATH=/usr/local/cuda-12.4/bin/:${PATH}
341+
export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64/:${LD_LIBRARY_PATH}
342+
381343
- name: Install dependencies
382344
shell: bash -l {0}
383345
run: |
384346
${{ env.create_venv }}
385347
${{ env.activate_venv }}
386348
python -m pip install --upgrade pip
387-
python -m pip install .[doc,extra]
388-
python -m pip install finufft
389-
390-
- name: Install GPU related interfaces
391-
run: |
392-
${{ env.activate_venv }}
393-
${{ env.setup_cuda }}
394-
pip install cupy-cuda12x torch
395-
python -m pip install gpuNUFFT "cufinufft<2.3" sigpy scikit-image fastmri
396-
349+
python -m pip install .[doc,finufft,autodiff,gpunufft,cufinufft,sigpy,extra] fastmri
350+
351+
397352
- name: Build API documentation
398353
run: |
399354
${{ env.activate_venv }}

docs/binder/environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ dependencies:
1010
- joblib
1111
- brainweb-dl
1212
- torch
13+
- pywavelets

examples/GPU/example_density.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@
142142
# If this method is widely used in the literature, there exists no convergence guarantees for it.
143143
#
144144
# .. note::
145-
# The Pipe method is currently only implemented for gpuNUFFT.
145+
# The Pipe method is currently only implemented for gpuNUFFT and cufinufft backend.
146146

147147
# %%
148148
flat_traj = traj.reshape(-1, 2)
@@ -158,3 +158,21 @@
158158
axs[2].imshow(abs(adjoint_manual))
159159
axs[2].set_title("Pipe density compensation")
160160
print(nufft.density)
161+
162+
# %%
163+
# We can also do density compensation using cufinufft backend
164+
165+
# %%
166+
flat_traj = traj.reshape(-1, 2)
167+
nufft = get_operator("cufinufft")(
168+
traj, shape=mri_2D.shape, density={"name": "pipe", "osf": 2}
169+
)
170+
adjoint_manual = nufft.adj_op(kspace)
171+
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
172+
axs[0].imshow(abs(mri_2D))
173+
axs[0].set_title("Ground Truth")
174+
axs[1].imshow(abs(adjoint))
175+
axs[1].set_title("no density compensation")
176+
axs[2].imshow(np.squeeze(abs(adjoint_manual)))
177+
axs[2].set_title("Pipe density compensation")
178+
print(nufft.density)

examples/GPU/example_learn_samples.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def __init__(self, inital_trajectory):
5454
data=torch.Tensor(inital_trajectory),
5555
requires_grad=True,
5656
)
57-
self.operator = get_operator("gpunufft", wrt_data=True, wrt_traj=True)(
57+
self.operator = get_operator("cufinufft", wrt_data=True, wrt_traj=True)(
5858
self.trajectory.detach().cpu().numpy(),
5959
shape=(256, 256),
6060
density=True,

examples/GPU/example_learn_samples_multicoil.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def __init__(self, inital_trajectory, n_coils, img_size=(256, 256)):
7575
squeeze_dims=False,
7676
)
7777
# A simple density compensated adjoint SENSE operator with sensitivity maps `smaps`.
78-
self.sense_op = get_operator("gpunufft", wrt_data=True, wrt_traj=True)(
78+
self.sense_op = get_operator("cufinufft", wrt_data=True, wrt_traj=True)(
7979
sample_points,
8080
shape=img_size,
8181
density=True,
@@ -103,7 +103,7 @@ def forward(self, x):
103103
self.trajectory.detach().numpy(),
104104
self.img_size,
105105
kspace.detach(),
106-
backend="gpunufft",
106+
backend="cufinufft",
107107
density=self.sense_op.density,
108108
blurr_factor=20,
109109
)

examples/example_learn_samples_multires.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def __init__(
8383
self.operator = get_operator("finufft", wrt_data=True, wrt_traj=True)(
8484
sample_points,
8585
shape=img_size,
86+
density=True,
8687
squeeze_dims=False,
8788
)
8889
self.img_size = img_size

pyproject.toml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,23 @@ dynamic = ["version"]
1212
[project.optional-dependencies]
1313

1414
gpunufft = ["gpuNUFFT>=0.9.0", "cupy-cuda12x"]
15+
1516
torchkbnufft = ["torchkbnufft", "cupy-cuda12x"]
16-
cufinufft = ["cufinufft<2.3", "cupy-cuda12x"]
17-
finufft = ["finufft"]
17+
torchkbnufft-cpu = ["torchkbnufft", "cupy-cuda12x"]
18+
torchkbnufft-gpu = ["torchkbnufft", "cupy-cuda12x"]
19+
20+
cufinufft = ["cufinufft==2.4.0b1", "cupy-cuda12x"]
21+
tensorflow = ["tensorflow-mri==0.21.0", "tensorflow-probability==0.17.0", "tensorflow-io==0.27.0", "matplotlib==3.7"]
22+
finufft = ["finufft==2.4.0rc1"]
23+
sigpy = ["sigpy"]
1824
pynfft = ["pynfft3"]
25+
1926
pynufft = ["pynufft"]
27+
pynufft-cpu = ["pynufft"]
28+
pynufft-gpu = ["pynufft"]
29+
30+
io = ["pymapvbvd"]
31+
smaps = ["scikit-image"]
2032
extra = ["pymapvbvd", "scikit-image", "scikit-learn", "pywavelets"]
2133
autodiff = ["torch"]
2234

src/mrinufft/operators/base.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,24 @@
1414
import numpy as np
1515
from numpy.typing import NDArray
1616

17-
from mrinufft._array_compat import with_numpy, with_numpy_cupy, AUTOGRAD_AVAILABLE
17+
from mrinufft._array_compat import (
18+
with_numpy,
19+
with_numpy_cupy,
20+
AUTOGRAD_AVAILABLE,
21+
CUPY_AVAILABLE,
22+
)
1823
from mrinufft._utils import auto_cast, power_method
1924
from mrinufft.density import get_density
2025
from mrinufft.extras import get_smaps
2126
from mrinufft.operators.interfaces.utils import is_cuda_array, is_host_array
2227

28+
29+
if AUTOGRAD_AVAILABLE:
30+
from mrinufft.operators.autodiff import MRINufftAutoGrad
31+
if CUPY_AVAILABLE:
32+
import cupy as cp
33+
34+
2335
# Mapping between numpy float and complex types.
2436
DTYPE_R2C = {"float32": "complex64", "float64": "complex128"}
2537

@@ -309,8 +321,10 @@ def compute_density(self, method=None):
309321
if `backend` is `tensorflow`, `gpunufft` or `torchkbnufft-cpu`
310322
or `torchkbnufft-gpu`.
311323
"""
312-
if isinstance(method, np.ndarray):
313-
self._density = method
324+
if isinstance(method, np.ndarray) or (
325+
CUPY_AVAILABLE and isinstance(method, cp.ndarray)
326+
):
327+
self.density = method
314328
return None
315329
if not method:
316330
self._density = None

src/mrinufft/operators/interfaces/cufinufft.py

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,6 @@
3939
DTYPE_R2C = {"float32": "complex64", "float64": "complex128"}
4040

4141

42-
def _error_check(ier, msg):
43-
if ier != 0:
44-
raise RuntimeError(msg)
45-
46-
4742
class RawCufinufftPlan:
4843
"""Light wrapper around the guru interface of finufft."""
4944

@@ -64,7 +59,9 @@ def __init__(
6459
# and type 2 with 2.
6560
self.plans = [None, None, None]
6661
self.grad_plan = None
67-
62+
self._kx = cp.array(samples[:, 0], copy=False)
63+
self._ky = cp.array(samples[:, 1], copy=False)
64+
self._kz = cp.array(samples[:, 2], copy=False) if self.ndim == 3 else None
6865
for i in [1, 2]:
6966
self._make_plan(i, **kwargs)
7067
self._set_pts(i, samples)
@@ -278,7 +275,7 @@ def samples(self, new_samples):
278275
for typ in [1, 2, "grad"]:
279276
if typ == "grad" and not self._grad_wrt_traj:
280277
continue
281-
self.raw_op._set_pts(typ, self._samples)
278+
self.raw_op._set_pts(typ, samples=self._samples)
282279
self.compute_density(self._density_method)
283280

284281
@FourierOperatorBase.density.setter
@@ -850,3 +847,55 @@ def toggle_grad_traj(self):
850847
if self.uses_sense:
851848
self.smaps = self.smaps.conj()
852849
self.raw_op.toggle_grad_traj()
850+
851+
@classmethod
852+
def pipe(
853+
cls,
854+
kspace_loc,
855+
volume_shape,
856+
num_iterations=10,
857+
osf=2,
858+
normalize=True,
859+
**kwargs,
860+
):
861+
"""Compute the density compensation weights for a given set of kspace locations.
862+
863+
Parameters
864+
----------
865+
kspace_loc: np.ndarray
866+
the kspace locations
867+
volume_shape: np.ndarray
868+
the volume shape
869+
num_iterations: int default 10
870+
the number of iterations for density estimation
871+
osf: float or int
872+
The oversampling factor the volume shape
873+
normalize: bool
874+
Whether to normalize the density compensation.
875+
We normalize such that the energy of PSF = 1
876+
"""
877+
if CUFINUFFT_AVAILABLE is False:
878+
raise ValueError(
879+
"cufinufft is not available, cannot estimate the density compensation"
880+
)
881+
grid_op = cls(
882+
samples=kspace_loc,
883+
shape=volume_shape,
884+
upsampfac=osf,
885+
gpu_spreadinterponly=1,
886+
gpu_kerevalmeth=0,
887+
**kwargs,
888+
)
889+
density_comp = cp.ones(kspace_loc.shape[0], dtype=grid_op.cpx_dtype)
890+
for _ in range(num_iterations):
891+
density_comp /= cp.abs(
892+
grid_op.op(
893+
grid_op.adj_op(density_comp.astype(grid_op.cpx_dtype))
894+
).squeeze()
895+
)
896+
if normalize:
897+
test_op = cls(samples=kspace_loc, shape=volume_shape, **kwargs)
898+
test_im = cp.ones(volume_shape, dtype=test_op.cpx_dtype)
899+
test_im_recon = test_op.adj_op(density_comp * test_op.op(test_im))
900+
density_comp /= cp.mean(cp.abs(test_im_recon))
901+
return density_comp.squeeze()

0 commit comments

Comments
 (0)