Skip to content

Commit 3f651e2

Browse files
authored
fix incompatibilities with new cupy version (13.5) (#118)
* fix test failures * fix test failures * fix test failures * remove cupy<=13.4 in install instructions * test with array-api-strict=~2.0
1 parent bcc48d2 commit 3f651e2

File tree

9 files changed

+94
-70
lines changed

9 files changed

+94
-70
lines changed

.github/workflows/c_python_build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ jobs:
8686
- name: Install tests dependencies
8787
run: |
8888
pip install pytest pytest-cov
89-
pip install array-api-strict~=1.0
89+
pip install array-api-strict~=2.0
9090
9191
- if: matrix.os == 'ubuntu-latest'
9292
name: Run Tests

docs/source/installation.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ To install parallelproj and cupy (optional and only if you have a CUDA GPU) from
4040

4141
.. code-block:: console
4242
43-
$ mamba install parallelproj cupy<=13.4
43+
$ mamba install parallelproj cupy
4444
4545
.. tab-item:: conda
4646

4747
.. code-block:: console
4848
49-
$ conda install -c conda-forge parallelproj cupy<=13.4
49+
$ conda install -c conda-forge parallelproj cupy
5050
5151
.. note::
5252
On conda-forge, CPU, CUDA 11 and CUDA 12 builds of `libparallelproj` are availalbe.
@@ -65,7 +65,7 @@ To install parallelproj and pytorch (optional) from conda-forge, run
6565

6666
.. code-block:: console
6767
68-
$ mamba install parallelproj pytorch cupy<=13.4
68+
$ mamba install parallelproj pytorch cupy
6969
7070
.. tab-item:: mamba, pytorch without CUDA support
7171

@@ -77,7 +77,7 @@ To install parallelproj and pytorch (optional) from conda-forge, run
7777

7878
.. code-block:: console
7979
80-
$ conda install -c conda-forge parallelproj pytorch cupy<=13.4
80+
$ conda install -c conda-forge parallelproj pytorch cupy
8181
8282
.. tab-item:: conda pytorch without CUDA support
8383

src/parallelproj/backend.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@
327327

328328
# ---------------------------------------------------------------------------------------
329329
if cupy_enabled:
330+
import cupy as cp
331+
330332
if "PARALLELPROJ_CUDA_KERNEL_FILE" in os.environ:
331333
cuda_kernel_file = Path(os.environ["PARALLELPROJ_CUDA_KERNEL_FILE"])
332334
else:
@@ -536,7 +538,9 @@ def joseph3d_fwd(
536538
np.asarray(img.shape, dtype=np.int32),
537539
)
538540

539-
return xp.asarray(img_fwd, device=array_api_compat.device(img))
541+
return array_api_compat.to_device(
542+
xp.from_dlpack(img_fwd), array_api_compat.device(img)
543+
)
540544

541545

542546
def joseph3d_back(
@@ -651,7 +655,9 @@ def joseph3d_back(
651655
np.asarray(back_img.shape, dtype=np.int32),
652656
)
653657

654-
return xp.asarray(back_img, device=array_api_compat.device(img_fwd))
658+
return array_api_compat.to_device(
659+
xp.from_dlpack(back_img), array_api_compat.device(img_fwd)
660+
)
655661

656662

657663
def joseph3d_fwd_tof_sino(
@@ -816,7 +822,9 @@ def joseph3d_fwd_tof_sino(
816822
lor_dependent_tofcenter_offset,
817823
)
818824

819-
return xp.asarray(img_fwd, device=array_api_compat.device(img))
825+
return array_api_compat.to_device(
826+
xp.from_dlpack(img_fwd), array_api_compat.device(img)
827+
)
820828

821829

822830
def joseph3d_back_tof_sino(
@@ -995,7 +1003,9 @@ def joseph3d_back_tof_sino(
9951003
lor_dependent_tofcenter_offset,
9961004
)
9971005

998-
return xp.asarray(back_img, device=array_api_compat.device(img_fwd))
1006+
return array_api_compat.to_device(
1007+
xp.from_dlpack(back_img), array_api_compat.device(img_fwd)
1008+
)
9991009

10001010

10011011
def joseph3d_fwd_tof_lm(
@@ -1163,7 +1173,9 @@ def joseph3d_fwd_tof_lm(
11631173
lor_dependent_tofcenter_offset,
11641174
)
11651175

1166-
return xp.asarray(img_fwd, device=array_api_compat.device(img))
1176+
return array_api_compat.to_device(
1177+
xp.from_dlpack(img_fwd), array_api_compat.device(img)
1178+
)
11671179

11681180

11691181
def joseph3d_back_tof_lm(
@@ -1343,7 +1355,9 @@ def joseph3d_back_tof_lm(
13431355
lor_dependent_tofcenter_offset,
13441356
)
13451357

1346-
return xp.asarray(back_img, device=array_api_compat.device(img_fwd))
1358+
return array_api_compat.to_device(
1359+
xp.from_dlpack(back_img), array_api_compat.device(img_fwd)
1360+
)
13471361

13481362

13491363
if cupy_enabled:
@@ -1439,7 +1453,7 @@ def count_event_multiplicity(events: Array) -> Array:
14391453
else:
14401454
tmp = np.unique(events, axis=0, return_counts=True, return_inverse=True)
14411455

1442-
mu = xp.asarray(tmp[2][tmp[1]], device=dev)
1456+
mu = array_api_compat.to_device(xp.from_dlpack(tmp[2][tmp[1]]), dev)
14431457
mu = xp.reshape(mu, (array_api_compat.size(mu),))
14441458

14451459
return mu

src/parallelproj/operators.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -479,9 +479,11 @@ def _apply(self, x: Array) -> Array:
479479
else:
480480
sigma = self._sigma
481481

482-
return xp.asarray(
483-
ndimagex.gaussian_filter(cp.asarray(x), sigma=sigma, **self._kwargs),
484-
device=device(x),
482+
return array_api_compat.to_device(
483+
xp.from_dlpack(
484+
ndimagex.gaussian_filter(cp.asarray(x), sigma=sigma, **self._kwargs)
485+
),
486+
device(x),
485487
)
486488
else:
487489
import scipy.ndimage as ndimage
@@ -491,9 +493,11 @@ def _apply(self, x: Array) -> Array:
491493
else:
492494
sigma = self._sigma
493495

494-
return xp.asarray(
495-
ndimage.gaussian_filter(np.asarray(x), sigma=sigma, **self._kwargs),
496-
device=device(x),
496+
return array_api_compat.to_device(
497+
xp.from_dlpack(
498+
ndimage.gaussian_filter(np.asarray(x), sigma=sigma, **self._kwargs)
499+
),
500+
device(x),
497501
)
498502

499503
def _adjoint(self, y: Array) -> Array:

src/parallelproj/pet_lors.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,8 @@ def get_lor_coordinates(
179179
)
180180

181181
for i, block_pair_num in enumerate(block_pair_nums):
182-
bs = self._all_block_pairs[block_pair_num, 0]
183-
be = self._all_block_pairs[block_pair_num, 1]
182+
bs = int(self._all_block_pairs[block_pair_num, 0])
183+
be = int(self._all_block_pairs[block_pair_num, 1])
184184

185185
eps = self.scanner.get_lor_endpoints(
186186
self.xp.asarray([bs], device=self.dev),
@@ -408,14 +408,22 @@ def _setup_view_indices(self) -> None:
408408
)
409409

410410
for view in np.arange(self._num_views):
411-
self._start_in_ring_index[view, :] = (
412-
self.xp.concat((self.xp.arange(m) // 2, self.xp.asarray([n // 2])))
413-
- view
414-
)[self._radial_trim : -self._radial_trim]
415-
self._end_in_ring_index[view, :] = (
416-
self.xp.concat((self.xp.asarray([-1]), -((self.xp.arange(m) + 4) // 2)))
417-
- view
418-
)[self._radial_trim : -self._radial_trim]
411+
self._start_in_ring_index[view, :] = self.xp.astype(
412+
(
413+
self.xp.concat((self.xp.arange(m) // 2, self.xp.asarray([n // 2])))
414+
- int(view)
415+
)[self._radial_trim : -self._radial_trim],
416+
self.xp.int32,
417+
)
418+
self._end_in_ring_index[view, :] = self.xp.astype(
419+
(
420+
self.xp.concat(
421+
(self.xp.asarray([-1]), -((self.xp.arange(m) + 4) // 2))
422+
)
423+
- int(view)
424+
)[self._radial_trim : -self._radial_trim],
425+
self.xp.int32,
426+
)
419427

420428
# shift the negative indices
421429
self._start_in_ring_index = self.xp.where(

src/parallelproj/pet_scanners.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import abc
66
from parallelproj import Array
77
import matplotlib.pyplot as plt
8+
import numpy as np
9+
import array_api_compat
810

911
from types import ModuleType
1012
from array_api_compat import size
@@ -226,27 +228,21 @@ def __init__(
226228
self._spacing = spacing
227229

228230
# calculate the LOR endpoints
229-
x0 = spacing[0] * (
230-
xp.arange(shape[0], device=dev, dtype=xp.float32) - (shape[0] - 1) / 2
231-
)
232-
x1 = spacing[1] * (
233-
xp.arange(shape[1], device=dev, dtype=xp.float32) - (shape[1] - 1) / 2
234-
)
235-
x2 = spacing[2] * (
236-
xp.arange(shape[2], device=dev, dtype=xp.float32) - (shape[2] - 1) / 2
237-
)
231+
x0 = spacing[0] * (np.arange(shape[0], dtype=np.float32) - (shape[0] - 1) / 2)
232+
x1 = spacing[1] * (np.arange(shape[1], dtype=np.float32) - (shape[1] - 1) / 2)
233+
x2 = spacing[2] * (np.arange(shape[2], dtype=np.float32) - (shape[2] - 1) / 2)
238234

239-
X0, X1, X2 = xp.meshgrid(x0, x1, x2, indexing="ij")
235+
# in the current version (1.12.0) of array_api_compat.torch the indexing kwargs is ignored
236+
# which is why we stick to numpy
237+
X0, X1, X2 = np.meshgrid(x0, x1, x2, indexing="ij")
240238

241-
self._lor_endpoints = xp.stack(
242-
(
243-
xp.reshape(X0, (-1,)),
244-
xp.reshape(X1, (-1,)),
245-
xp.reshape(X2, (-1,)),
246-
),
239+
self._lor_endpoints = np.stack(
240+
(X0.ravel(), X1.ravel(), X2.ravel()),
247241
axis=-1,
248242
)
249243

244+
self._lor_endpoints = xp.asarray(self._lor_endpoints, device=dev)
245+
250246
if affine_transformation_matrix is not None:
251247
tmp = xp.ones((self._lor_endpoints.shape[0], 4), device=dev)
252248
tmp[:, :-1] = self._lor_endpoints
@@ -500,7 +496,7 @@ def setup_all_lor_endpoints(self) -> None:
500496
self._all_lor_endpoints_index_offset[i] + module.num_lor_endpoints
501497
),
502498
:,
503-
] = module.get_lor_endpoints()
499+
] = self.xp.astype(module.get_lor_endpoints(), self.xp.float32)
504500

505501
self._all_lor_endpoints_module_number = [
506502
int(self._num_lor_endpoints_per_module[i]) * [i]

tests/test_nontof_joseph.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,18 +136,18 @@ def test_adjointness(
136136
# generate random LORs on a sphere around the image volume
137137
R = 0.8 * xp.max((xp.asarray(img_dim, dtype=xp.float32, device=dev) * voxel_size))
138138

139-
phis = xp.asarray(np.random.rand(nLORs) * 2 * np.pi, device=dev)
140-
costheta = xp.asarray(np.random.rand(nLORs) * 2 - 1, device=dev)
141-
sintheta = xp.sqrt(1 - costheta ** 2)
139+
phis = xp.asarray(np.random.rand(nLORs) * 2 * np.pi, device=dev, dtype=xp.float32)
140+
costheta = xp.asarray(np.random.rand(nLORs) * 2 - 1, device=dev, dtype=xp.float32)
141+
sintheta = xp.astype(xp.sqrt(1 - costheta**2), xp.float32)
142142

143143
xstart = xp.zeros((nLORs, 3), dtype=xp.float32, device=dev)
144144
xstart[:, 0] = R * sintheta * xp.cos(phis)
145145
xstart[:, 1] = R * sintheta * xp.sin(phis)
146146
xstart[:, 2] = R * costheta
147147

148-
phis = xp.asarray(np.random.rand(nLORs) * 2 * np.pi, device=dev)
149-
costheta = xp.asarray(np.random.rand(nLORs) * 2 - 1, device=dev)
150-
sintheta = xp.sqrt(1 - costheta ** 2)
148+
phis = xp.asarray(np.random.rand(nLORs) * 2 * np.pi, device=dev, dtype=xp.float32)
149+
costheta = xp.asarray(np.random.rand(nLORs) * 2 - 1, device=dev, dtype=xp.float32)
150+
sintheta = xp.astype(xp.sqrt(1 - costheta**2), xp.float32)
151151

152152
xend = xp.zeros((nLORs, 3), dtype=xp.float32, device=dev)
153153
xend[:, 0] = R * sintheta * xp.cos(phis)

tests/test_toflm_joseph.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def test_tof_lm_fwd(
5757
num_tofbins = max(4 * int(n * vsize / delta / 2) + 1, 11)
5858

5959
trunc_factor = 1.0 / erf(ns / math.sqrt(2))
60-
trunc_dist = ns * math.sqrt(sig_t ** 2 + (delta ** 2) / 12)
60+
trunc_dist = ns * math.sqrt(sig_t**2 + (delta**2) / 12)
6161

6262
for i in range(num_tofbins // 2):
6363
p_tof = parallelproj.joseph3d_fwd_tof_lm(
@@ -92,10 +92,10 @@ def test_tof_lm_fwd(
9292
sig_t,
9393
i,
9494
theory_value,
95-
float(p_tof[0] - theory_value),
95+
float(p_tof[0]) - theory_value,
9696
)
9797

98-
abs_diff = abs(p_tof[0] - theory_value)
98+
abs_diff = abs(float(p_tof[0]) - theory_value)
9999
assert abs_diff < atol
100100

101101
rel_diff = abs_diff / theory_value
@@ -156,18 +156,18 @@ def test_adjointness(
156156
# generate random LORs on a sphere around the image volume
157157
R = 0.8 * xp.max((xp.asarray(img_dim, dtype=xp.float32, device=dev) * voxel_size))
158158

159-
phis = xp.asarray(np.random.rand(nLORs) * 2 * np.pi, device=dev)
160-
costheta = xp.asarray(np.random.rand(nLORs) * 2 - 1, device=dev)
161-
sintheta = xp.sqrt(1 - costheta ** 2)
159+
phis = xp.asarray(np.random.rand(nLORs) * 2 * np.pi, device=dev, dtype=xp.float32)
160+
costheta = xp.asarray(np.random.rand(nLORs) * 2 - 1, device=dev, dtype=xp.float32)
161+
sintheta = xp.sqrt(1 - costheta**2)
162162

163163
xstart = xp.zeros((nLORs, 3), dtype=xp.float32, device=dev)
164164
xstart[:, 0] = R * sintheta * xp.cos(phis)
165165
xstart[:, 1] = R * sintheta * xp.sin(phis)
166166
xstart[:, 2] = R * costheta
167167

168-
phis = xp.asarray(np.random.rand(nLORs) * 2 * np.pi, device=dev)
169-
costheta = xp.asarray(np.random.rand(nLORs) * 2 - 1, device=dev)
170-
sintheta = xp.sqrt(1 - costheta ** 2)
168+
phis = xp.asarray(np.random.rand(nLORs) * 2 * np.pi, device=dev, dtype=xp.float32)
169+
costheta = xp.asarray(np.random.rand(nLORs) * 2 - 1, device=dev, dtype=xp.float32)
170+
sintheta = xp.sqrt(1 - costheta**2)
171171

172172
xend = xp.zeros((nLORs, 3), dtype=xp.float32, device=dev)
173173
xend[:, 0] = R * sintheta * xp.cos(phis)

tests/test_tofsino_joseph.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def test_tof_sino_fwd(
7373
)
7474

7575
trunc_factor = 1.0 / erf(ns / math.sqrt(2))
76-
trunc_dist = ns * math.sqrt(sig_t ** 2 + (delta ** 2) / 12)
76+
trunc_dist = ns * math.sqrt(sig_t**2 + (delta**2) / 12)
7777

7878
for i in range(num_tofbins // 2):
7979

@@ -99,10 +99,12 @@ def test_tof_sino_fwd(
9999
sig_t,
100100
i,
101101
theory_value,
102-
float(p_tof[0, num_tofbins // 2 + i] - theory_value),
102+
float(p_tof[0, num_tofbins // 2 + i]) - theory_value,
103103
)
104104

105-
abs_diff = abs(p_tof[0, num_tofbins // 2 + i] - theory_value)
105+
abs_diff = abs(
106+
float(p_tof[0, num_tofbins // 2 + i]) - theory_value
107+
)
106108
assert abs_diff < atol
107109

108110
rel_diff = abs_diff / theory_value
@@ -133,18 +135,18 @@ def test_adjointness(
133135
# generate random LORs on a sphere around the image volume
134136
R = 0.8 * xp.max((xp.asarray(img_dim, dtype=xp.float32, device=dev) * voxel_size))
135137

136-
phis = xp.asarray(np.random.rand(nLORs) * 2 * np.pi, device=dev)
137-
costheta = xp.asarray(np.random.rand(nLORs) * 2 - 1, device=dev)
138-
sintheta = xp.sqrt(1 - costheta ** 2)
138+
phis = xp.asarray(np.random.rand(nLORs) * 2 * np.pi, device=dev, dtype=xp.float32)
139+
costheta = xp.asarray(np.random.rand(nLORs) * 2 - 1, device=dev, dtype=xp.float32)
140+
sintheta = xp.sqrt(1 - costheta**2)
139141

140142
xstart = xp.zeros((nLORs, 3), dtype=xp.float32, device=dev)
141143
xstart[:, 0] = R * sintheta * xp.cos(phis)
142144
xstart[:, 1] = R * sintheta * xp.sin(phis)
143145
xstart[:, 2] = R * costheta
144146

145-
phis = xp.asarray(np.random.rand(nLORs) * 2 * np.pi, device=dev)
146-
costheta = xp.asarray(np.random.rand(nLORs) * 2 - 1, device=dev)
147-
sintheta = xp.sqrt(1 - costheta ** 2)
147+
phis = xp.asarray(np.random.rand(nLORs) * 2 * np.pi, device=dev, dtype=xp.float32)
148+
costheta = xp.asarray(np.random.rand(nLORs) * 2 - 1, device=dev, dtype=xp.float32)
149+
sintheta = xp.sqrt(1 - costheta**2)
148150

149151
xend = xp.zeros((nLORs, 3), dtype=xp.float32, device=dev)
150152
xend[:, 0] = R * sintheta * xp.cos(phis)
@@ -220,7 +222,7 @@ def test_tof_sino_fwd_sum(
220222
n = 101
221223
vsize = 1.0
222224
voxsize = xp.asarray([vsize, vsize, vsize], dtype=xp.float32, device=dev)
223-
tmp = (-0.5 * n + 0.5) * voxsize[0]
225+
tmp = float((-0.5 * n + 0.5) * voxsize[0])
224226
img_origin = xp.asarray([tmp, tmp, tmp], dtype=xp.float32, device=dev)
225227
num_off = 30
226228

0 commit comments

Comments
 (0)