diff --git a/.github/workflows/c_python_build.yml b/.github/workflows/c_python_build.yml index 3fe77832..04786b42 100644 --- a/.github/workflows/c_python_build.yml +++ b/.github/workflows/c_python_build.yml @@ -23,7 +23,8 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] # not building on mac-os because there is no OpenMP - python-version: ['3.9','3.11'] + python-version: [3.9, 3.12] + test-env: [test-numpy-1, test-numpy-2] steps: - uses: actions/checkout@v4 @@ -47,7 +48,7 @@ jobs: pdm show --version - name: Install dependencies and package run: | - pdm install -d -G test + pdm install -d -G ${{ matrix.test-env }} - if: matrix.os == 'ubuntu-latest' name: Run Tests diff --git a/examples/05_algorithms/03_run_mlem_projection_data.py b/examples/05_algorithms/03_run_mlem_projection_data.py index b8f1048d..a3be6d2c 100644 --- a/examples/05_algorithms/03_run_mlem_projection_data.py +++ b/examples/05_algorithms/03_run_mlem_projection_data.py @@ -28,7 +28,7 @@ # %% from __future__ import annotations -from array_api_strict._array_object import Array +from parallelproj import Array import array_api_compat.numpy as xp diff --git a/examples/05_algorithms/04_run_osem_projection_data.py b/examples/05_algorithms/04_run_osem_projection_data.py index b63aea59..4e5d418a 100644 --- a/examples/05_algorithms/04_run_osem_projection_data.py +++ b/examples/05_algorithms/04_run_osem_projection_data.py @@ -28,7 +28,7 @@ # %% from __future__ import annotations -from array_api_strict._array_object import Array +from parallelproj import Array import array_api_compat.numpy as xp diff --git a/examples/05_algorithms/07_run_mlem_open_geometry.py b/examples/05_algorithms/07_run_mlem_open_geometry.py index 284e8bbb..7afe544d 100644 --- a/examples/05_algorithms/07_run_mlem_open_geometry.py +++ b/examples/05_algorithms/07_run_mlem_open_geometry.py @@ -29,7 +29,7 @@ # %% from __future__ import annotations -from array_api_strict._array_object import Array +from parallelproj import Array import array_api_compat.numpy as xp diff --git a/examples/05_algorithms/utils.py b/examples/05_algorithms/utils.py index abc47231..8553a2b0 100644 --- a/examples/05_algorithms/utils.py +++ b/examples/05_algorithms/utils.py @@ -1,6 +1,6 @@ from collections.abc import Sequence import abc -from array_api_strict._array_object import Array +from parallelproj import Array from types import ModuleType diff --git a/examples/06_listmode_algorithms/01_listmode_mlem.py b/examples/06_listmode_algorithms/01_listmode_mlem.py index d579b95c..8e222241 100644 --- a/examples/06_listmode_algorithms/01_listmode_mlem.py +++ b/examples/06_listmode_algorithms/01_listmode_mlem.py @@ -30,7 +30,7 @@ # %% from __future__ import annotations -from array_api_strict._array_object import Array +from parallelproj import Array import array_api_compat.numpy as xp diff --git a/examples/06_listmode_algorithms/02_listmode_osem.py b/examples/06_listmode_algorithms/02_listmode_osem.py index e2d892ea..1c0933f6 100644 --- a/examples/06_listmode_algorithms/02_listmode_osem.py +++ b/examples/06_listmode_algorithms/02_listmode_osem.py @@ -30,7 +30,7 @@ # %% from __future__ import annotations -from array_api_strict._array_object import Array +from parallelproj import Array import array_api_compat.numpy as xp diff --git a/pyproject.toml b/pyproject.toml index 3e53a597..19c23684 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,13 +6,12 @@ authors = [ {name = "Georg Schramm", email = "georg.schramm@kuleuven.be"}, ] dependencies = [ - "numpy~=1.23", + "numpy>=1.23", "scipy~=1.0", "array-api-compat~=1.7", - "array-api-strict~=1.0", "matplotlib~=3.8", ] -requires-python = ">=3.9" +requires-python = ">=3.9,<3.13" readme = "README.md" license = {text = "MIT"} classifiers = [ @@ -31,13 +30,20 @@ build-backend = "pdm.backend" distribution = true [tool.pdm.dev-dependencies] -test = [ +test-numpy-1 = [ "pytest>=8.1.1", "pytest-cov>=4.1.0", - "torch~=2.0", + "array-api-strict~=1.0", + "numpy<2", + "torch~=2.0" ] -numpy122 = [ +test-numpy-2 = [ + "pytest>=8.1.1", + "pytest-cov>=4.1.0", + "array-api-strict~=1.0", + "numpy>=2" ] + [tool.coverage.report] exclude_lines = [ "def __repr__", @@ -61,6 +67,9 @@ exclude_lines = [ "if cupy_enabled", "lib_parallelproj_c_fname", "empty_cuda_cache", - "__str__" + "__str__", + "Array =", + "elif not cupy_enabled and torch_enabled", + "elif cupy_enabled and not torch_enabled" ] diff --git a/src/parallelproj/__init__.py b/src/parallelproj/__init__.py index 86c17c0e..a02c0757 100644 --- a/src/parallelproj/__init__.py +++ b/src/parallelproj/__init__.py @@ -18,6 +18,7 @@ lib_parallelproj_c_fname, lib_parallelproj_cuda_fname, cuda_kernel_file, + Array, ) from .operators import ( @@ -95,9 +96,10 @@ "SinogramSpatialAxisOrder", "RegularPolygonPETLORDescriptor", "EqualBlockPETLORDescriptor", + "Array", ] -if os.getenv('PARALLELPROJ_SILENT_IMPORT') is None: +if os.getenv("PARALLELPROJ_SILENT_IMPORT") is None: print( f""" - - - - - - - - - - - - diff --git a/src/parallelproj/backend.py b/src/parallelproj/backend.py index 2d1aff71..31c70edd 100644 --- a/src/parallelproj/backend.py +++ b/src/parallelproj/backend.py @@ -17,9 +17,10 @@ import numpy as np import array_api_compat import numpy.ctypeslib as npct -from array_api_strict._array_object import Array from types import ModuleType +from typing import Union + # check if cuda is present cuda_present = shutil.which("nvidia-smi") is not None @@ -30,9 +31,26 @@ # check if cupy is available torch_enabled = importlib.util.find_spec("torch") is not None -# define type for cupy or numpy array -if cupy_enabled: +if cupy_enabled and torch_enabled: import array_api_compat.cupy as cp + import array_api_compat.torch as torch + + # type alias for array + Array = Union[np.ndarray, cp.ndarray, torch.Tensor] +elif cupy_enabled and not torch_enabled: + import array_api_compat.cupy as cp + + # type alias for array + Array = Union[np.ndarray, cp.ndarray] +elif not cupy_enabled and torch_enabled: + import array_api_compat.torch as torch + + # type alias for array + Array = Union[np.ndarray, torch.Tensor] +else: + # type alias for array + Array = np.ndarray + # numpy ctypes lib array definitions ar_1d_single = npct.ndpointer(dtype=ctypes.c_float, ndim=1, flags="C") @@ -1434,10 +1452,12 @@ def count_event_multiplicity(events: Array) -> Array: tmp = np.unique(events, axis=0, return_counts=True, return_inverse=True) mu = xp.asarray(tmp[2][tmp[1]], device=dev) + mu = xp.reshape(mu, (array_api_compat.size(mu),)) return mu + def to_numpy_array(x: Array) -> np.ndarray: """convert an array to a numpy array diff --git a/src/parallelproj/operators.py b/src/parallelproj/operators.py index a3200998..d053196d 100644 --- a/src/parallelproj/operators.py +++ b/src/parallelproj/operators.py @@ -7,7 +7,7 @@ import numpy as np import array_api_compat from array_api_compat import device -from array_api_strict._array_object import Array +from parallelproj import Array from collections.abc import Sequence import parallelproj diff --git a/src/parallelproj/pet_lors.py b/src/parallelproj/pet_lors.py index c4035c0a..d0ac4739 100644 --- a/src/parallelproj/pet_lors.py +++ b/src/parallelproj/pet_lors.py @@ -5,7 +5,7 @@ import abc import enum import array_api_compat.numpy as np -from array_api_strict._array_object import Array +from parallelproj import Array import matplotlib.pyplot as plt from mpl_toolkits.mplot3d.art3d import Line3DCollection @@ -107,7 +107,7 @@ def __init__( self._scanner = scanner self._all_block_pairs = all_block_pairs self._num_lorendpoints_per_block = self.scanner.modules[0].num_lor_endpoints - self._num_lors_per_block_pair = self._num_lorendpoints_per_block ** 2 + self._num_lors_per_block_pair = self._num_lorendpoints_per_block**2 @property def all_block_pairs(self) -> Array: diff --git a/src/parallelproj/pet_scanners.py b/src/parallelproj/pet_scanners.py index 97b8b2aa..65fccb88 100644 --- a/src/parallelproj/pet_scanners.py +++ b/src/parallelproj/pet_scanners.py @@ -3,7 +3,7 @@ from __future__ import annotations import abc -from array_api_strict._array_object import Array +from parallelproj import Array import matplotlib.pyplot as plt from types import ModuleType diff --git a/src/parallelproj/projectors.py b/src/parallelproj/projectors.py index b6ae4a67..c131bbe2 100644 --- a/src/parallelproj/projectors.py +++ b/src/parallelproj/projectors.py @@ -3,7 +3,7 @@ from __future__ import annotations import array_api_compat.numpy as np -from array_api_strict._array_object import Array +from parallelproj import Array import array_api_compat import matplotlib.pyplot as plt from matplotlib.patches import Rectangle @@ -885,21 +885,21 @@ def convert_sinogram_to_listmode( num_events_ss = int(self.xp.sum(ss)) - event_start_coords[ - event_offset : (event_offset + num_events_ss), : - ] = self.xp.take(xstart, event_sino_inds, axis=0) - event_end_coords[ - event_offset : (event_offset + num_events_ss), : - ] = self.xp.take(xend, event_sino_inds, axis=0) + event_start_coords[event_offset : (event_offset + num_events_ss), :] = ( + self.xp.take(xstart, event_sino_inds, axis=0) + ) + event_end_coords[event_offset : (event_offset + num_events_ss), :] = ( + self.xp.take(xend, event_sino_inds, axis=0) + ) if self.tof: - event_tofbins[ - event_offset : (event_offset + num_events_ss) - ] = self.xp.full( - num_events_ss, - it - num_tofbins // 2, - device=self._dev, - dtype=self.xp.int16, + event_tofbins[event_offset : (event_offset + num_events_ss)] = ( + self.xp.full( + num_events_ss, + it - num_tofbins // 2, + device=self._dev, + dtype=self.xp.int16, + ) ) event_offset += num_events_ss diff --git a/tests/test_projectors.py b/tests/test_projectors.py index de03f54f..f7717531 100644 --- a/tests/test_projectors.py +++ b/tests/test_projectors.py @@ -191,8 +191,6 @@ def test_lmprojector( xstart, xend, img_dim, voxel_size, img_origin ) - assert lm_proj.xp == xp - assert xp.all(lm_proj.event_start_coordinates == xstart) assert xp.all(lm_proj.event_end_coordinates == xend) diff --git a/tox.ini b/tox.ini index 4c66b14c..a02f464e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,16 @@ [tox] minversion = 4.0 -envlist = py{39,312}-np{122,126} +envlist = py{39,312}-numpy{123,20} [testenv] groups = test deps = - np122: numpy>=1.22,<1.23, setuptools - np126: numpy>=1.26,<1.27 + setuptools + pytest>=8.1.1 + pytest-cov>=4.1.0 + array-api-strict~=1.0 + numpy123: numpy==1.23 + numpy20: numpy>=2.0 passenv = MPLBACKEND, PARALLELPROJ_* commands = pytest tests -v --cov-report term-missing --cov "{envsitepackagesdir}/parallelproj" --cov-fail-under=100