imops 0.8.8__cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. _build_utils.py +113 -0
  2. imops/__init__.py +10 -0
  3. imops/__version__.py +1 -0
  4. imops/_configs.py +29 -0
  5. imops/backend.py +95 -0
  6. imops/box.py +74 -0
  7. imops/cpp/cpp_modules.cpython-38-i386-linux-gnu.so +0 -0
  8. imops/cpp/interp2d/delaunator/delaunator-header-only.hpp +33 -0
  9. imops/cpp/interp2d/delaunator/delaunator.cpp +645 -0
  10. imops/cpp/interp2d/delaunator/delaunator.hpp +170 -0
  11. imops/cpp/interp2d/interpolator.h +52 -0
  12. imops/cpp/interp2d/triangulator.h +198 -0
  13. imops/cpp/interp2d/utils.h +63 -0
  14. imops/cpp/main.cpp +13 -0
  15. imops/crop.py +120 -0
  16. imops/interp1d.py +207 -0
  17. imops/interp2d.py +120 -0
  18. imops/measure.py +228 -0
  19. imops/morphology.py +525 -0
  20. imops/numeric.py +384 -0
  21. imops/pad.py +253 -0
  22. imops/py.typed +0 -0
  23. imops/radon.py +247 -0
  24. imops/src/__init__.py +0 -0
  25. imops/src/_backprojection.c +27339 -0
  26. imops/src/_backprojection.cpython-38-i386-linux-gnu.so +0 -0
  27. imops/src/_fast_backprojection.c +27339 -0
  28. imops/src/_fast_backprojection.cpython-38-i386-linux-gnu.so +0 -0
  29. imops/src/_fast_measure.c +33810 -0
  30. imops/src/_fast_measure.cpython-38-i386-linux-gnu.so +0 -0
  31. imops/src/_fast_morphology.c +26089 -0
  32. imops/src/_fast_morphology.cpython-38-i386-linux-gnu.so +0 -0
  33. imops/src/_fast_numeric.c +48651 -0
  34. imops/src/_fast_numeric.cpython-38-i386-linux-gnu.so +0 -0
  35. imops/src/_fast_radon.c +30714 -0
  36. imops/src/_fast_radon.cpython-38-i386-linux-gnu.so +0 -0
  37. imops/src/_fast_zoom.c +57203 -0
  38. imops/src/_fast_zoom.cpython-38-i386-linux-gnu.so +0 -0
  39. imops/src/_measure.c +33810 -0
  40. imops/src/_measure.cpython-38-i386-linux-gnu.so +0 -0
  41. imops/src/_morphology.c +26089 -0
  42. imops/src/_morphology.cpython-38-i386-linux-gnu.so +0 -0
  43. imops/src/_numba_zoom.py +503 -0
  44. imops/src/_numeric.c +48651 -0
  45. imops/src/_numeric.cpython-38-i386-linux-gnu.so +0 -0
  46. imops/src/_radon.c +30714 -0
  47. imops/src/_radon.cpython-38-i386-linux-gnu.so +0 -0
  48. imops/src/_zoom.c +57203 -0
  49. imops/src/_zoom.cpython-38-i386-linux-gnu.so +0 -0
  50. imops/testing.py +57 -0
  51. imops/utils.py +205 -0
  52. imops/zoom.py +297 -0
  53. imops-0.8.8.dist-info/LICENSE +21 -0
  54. imops-0.8.8.dist-info/METADATA +218 -0
  55. imops-0.8.8.dist-info/RECORD +58 -0
  56. imops-0.8.8.dist-info/WHEEL +6 -0
  57. imops-0.8.8.dist-info/top_level.txt +2 -0
  58. imops.libs/libgomp-65f46eca.so.1.0.0 +0 -0
imops/testing.py ADDED
@@ -0,0 +1,57 @@
1
+ import warnings
2
+
3
+ import numpy as np
4
+ from skimage.transform import iradon as iradon_, radon as radon_
5
+
6
+
7
+ def sk_iradon(xs):
8
+ with warnings.catch_warnings():
9
+ warnings.filterwarnings('ignore', module='numpy')
10
+ warnings.simplefilter('ignore', DeprecationWarning)
11
+ warnings.simplefilter('ignore', np.VisibleDeprecationWarning)
12
+ return np.stack([iradon_(x) for x in xs])
13
+
14
+
15
+ def sk_radon(xs):
16
+ with warnings.catch_warnings():
17
+ warnings.filterwarnings('ignore', module='numpy')
18
+ warnings.simplefilter('ignore', DeprecationWarning)
19
+ warnings.simplefilter('ignore', np.VisibleDeprecationWarning)
20
+ warnings.filterwarnings('error', '.*image must be zero.*', module='skimage')
21
+ return np.stack([radon_(x) for x in xs])
22
+
23
+
24
+ def sample_ct(n_slices, size, fill=0):
25
+ shape = (n_slices, size, size)
26
+
27
+ water = np.random.randn(*shape) * 100
28
+ air = np.random.randn(*shape) * 100 - 1000
29
+ choice = np.random.binomial(1, 0.5, shape).astype(bool)
30
+
31
+ image = choice * water + ~choice * air
32
+ return fill_outside(image, fill)
33
+
34
+
35
+ def fill_outside(x, fill):
36
+ x = x.copy()
37
+ size = x.shape[-1]
38
+ radius = size // 2
39
+ xpr, ypr = np.mgrid[:size, :size] - radius
40
+ x[:, (xpr**2 + ypr**2) > radius**2] = fill
41
+ return x
42
+
43
+
44
+ def seeded_by(seed):
45
+ def wrapper(func):
46
+ def inner(*args, **kwargs):
47
+ old_state = np.random.get_state()
48
+ np.random.seed(seed)
49
+
50
+ try:
51
+ return func(*args, **kwargs)
52
+ finally:
53
+ np.random.set_state(old_state)
54
+
55
+ return inner
56
+
57
+ return wrapper
imops/utils.py ADDED
@@ -0,0 +1,205 @@
1
+ import os
2
+ from contextlib import contextmanager
3
+ from itertools import permutations
4
+ from typing import Callable, Optional, Sequence, Tuple, Union
5
+ from warnings import warn
6
+
7
+ import numpy as np
8
+
9
+ from .backend import BACKEND_NAME2ENV_NUM_THREADS_VAR_NAME, SINGLE_THREADED_BACKENDS, Backend
10
+
11
+
12
+ AxesLike = Union[int, Sequence[int]]
13
+ AxesParams = Union[float, Sequence[float]]
14
+
15
+ ZOOM_SRC_DIM = 4
16
+ # TODO: define imops-specific environment variable like `OMP_NUM_THREADS`?
17
+ IMOPS_NUM_THREADS = None
18
+
19
+
20
+ def set_num_threads(num_threads: int) -> int:
21
+ assert isinstance(num_threads, int) or num_threads is None, 'Number of threads must be int value or None.'
22
+ global IMOPS_NUM_THREADS
23
+ current = IMOPS_NUM_THREADS
24
+ IMOPS_NUM_THREADS = num_threads
25
+ return current
26
+
27
+
28
+ @contextmanager
29
+ def imops_num_threads(num_threads: int):
30
+ previous = set_num_threads(num_threads)
31
+ try:
32
+ yield
33
+ finally:
34
+ set_num_threads(previous)
35
+
36
+
37
+ def normalize_num_threads(num_threads: int, backend: Backend, warn_stacklevel: int = 1) -> int:
38
+ """Calculate the effective number of threads"""
39
+
40
+ global IMOPS_NUM_THREADS
41
+ if backend.name in SINGLE_THREADED_BACKENDS:
42
+ if num_threads != -1:
43
+ warn(
44
+ f'"{backend.name}" backend is single-threaded. Setting `num_threads` has no effect.',
45
+ stacklevel=warn_stacklevel,
46
+ )
47
+ return 1
48
+
49
+ env_num_threads_var_name = BACKEND_NAME2ENV_NUM_THREADS_VAR_NAME[backend.name]
50
+ # here we also handle the case `env_num_threads_var_name`=" " gracefully
51
+ env_num_threads = os.environ.get(env_num_threads_var_name, '').strip()
52
+ env_num_threads = int(env_num_threads) if env_num_threads else None
53
+ # TODO: maybe let user set the absolute maximum number of threads?
54
+ num_available_cpus = len(os.sched_getaffinity(0))
55
+
56
+ max_num_threads = min(filter(bool, [IMOPS_NUM_THREADS, env_num_threads, num_available_cpus]))
57
+
58
+ if num_threads >= 0:
59
+ # FIXME
60
+ if backend.name == 'Numba':
61
+ warn(
62
+ 'Setting `num_threads` has no effect with "Numba" backend. '
63
+ 'Use `NUMBA_NUM_THREADS` environment variable.',
64
+ stacklevel=warn_stacklevel,
65
+ )
66
+ return num_threads
67
+
68
+ if num_threads > max_num_threads:
69
+ if max_num_threads == IMOPS_NUM_THREADS:
70
+ warn(
71
+ f'Required number of threads ({num_threads}) is greater than `IMOPS_NUM_THREADS` '
72
+ f'({IMOPS_NUM_THREADS}). Using {IMOPS_NUM_THREADS} threads.',
73
+ stacklevel=warn_stacklevel,
74
+ )
75
+ elif max_num_threads == env_num_threads:
76
+ warn(
77
+ f'Required number of threads ({num_threads}) is greater than `{env_num_threads_var_name}` '
78
+ f'({env_num_threads}). Using {env_num_threads} threads.',
79
+ stacklevel=warn_stacklevel,
80
+ )
81
+ else:
82
+ warn(
83
+ f'Required number of threads ({num_threads}) is greater than number of available CPU-s '
84
+ f'({num_available_cpus}). Using {num_available_cpus} threads.',
85
+ stacklevel=warn_stacklevel,
86
+ )
87
+ return min(num_threads, max_num_threads)
88
+
89
+ return max_num_threads + num_threads + 1
90
+
91
+
92
+ def get_c_contiguous_permutaion(array: np.ndarray) -> Optional[np.ndarray]:
93
+ for permutation in permutations(range(array.ndim)):
94
+ if np.transpose(array, permutation).data.c_contiguous:
95
+ return np.array(permutation)
96
+
97
+ return None
98
+
99
+
100
+ def inverse_permutation(permutation: np.ndarray) -> np.ndarray:
101
+ inverse_permutation = np.arange(permutation.shape[0])
102
+ inverse_permutation[permutation] = inverse_permutation.copy()
103
+
104
+ return inverse_permutation
105
+
106
+
107
+ def axis_from_dim(axis: Union[AxesLike, None], dim: int) -> tuple:
108
+ if axis is None:
109
+ return tuple(range(dim))
110
+
111
+ return np.core.numeric.normalize_axis_tuple(axis, dim, 'axis')
112
+
113
+
114
+ def broadcast_axis(axis: Union[AxesLike, None], dim: int, *values: Union[AxesLike, AxesParams]):
115
+ axis = axis_from_dim(axis, dim)
116
+ values = [to_axis(axis, x) for x in values]
117
+ sizes = set(map(len, values))
118
+ if not sizes <= {len(axis)}:
119
+ raise ValueError(f"Params sizes don't match with the axes: {axis} vs {sizes}.")
120
+
121
+ return (axis, *values)
122
+
123
+
124
+ def to_axis(axis, value):
125
+ value = np.atleast_1d(value)
126
+ if len(value) == 1:
127
+ value = np.repeat(value, len(axis), 0)
128
+
129
+ return value
130
+
131
+
132
+ def fill_by_indices(target, values, indices):
133
+ target = np.array(target)
134
+ target[list(indices)] = values
135
+
136
+ return tuple(target)
137
+
138
+
139
+ def broadcast_to_axis(axis: AxesLike, *arrays: AxesParams):
140
+ if not arrays:
141
+ raise ValueError('No arrays provided.')
142
+
143
+ arrays = list(map(np.atleast_1d, arrays))
144
+ lengths = list(map(len, arrays))
145
+ if axis is None:
146
+ raise ValueError('`axis` cannot be None.')
147
+
148
+ if not all(len(axis) == x or x == 1 for x in lengths):
149
+ raise ValueError(f'Axes and arrays are not broadcastable: {len(axis)} vs {", ".join(map(str, lengths))}.')
150
+
151
+ return tuple(np.repeat(x, len(axis) // len(x), 0) for x in arrays)
152
+
153
+
154
+ def morphology_composition_args(f, g) -> Callable:
155
+ def wrapper(
156
+ image: np.ndarray,
157
+ footprint: np.ndarray,
158
+ output: np.ndarray,
159
+ num_threads: int,
160
+ ):
161
+ temp = np.empty_like(image, dtype=bool)
162
+ temp = g(image, footprint, temp, num_threads)
163
+
164
+ return f(temp, footprint, output, num_threads)
165
+
166
+ return wrapper
167
+
168
+
169
+ def build_slices(start: Sequence[int], stop: Sequence[int] = None, step: Sequence[int] = None) -> Tuple[slice, ...]:
170
+ """
171
+ Returns a tuple of slices built from `start` and `stop` with `step`.
172
+
173
+ Examples
174
+ --------
175
+ ```python
176
+ build_slices([1, 2, 3], [4, 5, 6])
177
+ (slice(1, 4), slice(2, 5), slice(3, 6))
178
+ build_slices([10, 11])
179
+ (slice(10), slice(11))
180
+ ```
181
+ """
182
+
183
+ check_len(*filter(lambda x: x is not None, [start, stop, step]))
184
+
185
+ if stop is None and step is None:
186
+ return tuple(map(slice, start))
187
+
188
+ args = [
189
+ start,
190
+ stop if stop is not None else [None for _ in start],
191
+ step if step is not None else [None for _ in start],
192
+ ]
193
+
194
+ return tuple(map(slice, *args))
195
+
196
+
197
+ def check_len(*args) -> None:
198
+ lengths = list(map(len, args))
199
+ if any(length != lengths[0] for length in lengths):
200
+ raise ValueError(f'Arguments of equal length are required: {", ".join(map(str, lengths))}')
201
+
202
+
203
+ def assert_subdtype(dtype, ref_dtype, name):
204
+ if not np.issubdtype(dtype, ref_dtype):
205
+ raise ValueError(f'`{name}` must be of {ref_dtype.__name__} dtype, got {dtype}')
imops/zoom.py ADDED
@@ -0,0 +1,297 @@
1
+ from platform import python_version
2
+ from typing import Callable, Sequence, Union
3
+ from warnings import warn
4
+
5
+ import numpy as np
6
+ from scipy.ndimage import zoom as _scipy_zoom
7
+
8
+ from .backend import BackendLike, resolve_backend
9
+ from .src._fast_zoom import (
10
+ _zoom3d_linear as cython_fast_zoom3d_linear,
11
+ _zoom3d_nearest as cython_fast_zoom3d_nearest,
12
+ _zoom4d_linear as cython_fast_zoom4d_linear,
13
+ _zoom4d_nearest as cython_fast_zoom4d_nearest,
14
+ )
15
+ from .src._zoom import (
16
+ _zoom3d_linear as cython_zoom3d_linear,
17
+ _zoom3d_nearest as cython_zoom3d_nearest,
18
+ _zoom4d_linear as cython_zoom4d_linear,
19
+ _zoom4d_nearest as cython_zoom4d_nearest,
20
+ )
21
+ from .utils import (
22
+ AxesLike,
23
+ AxesParams,
24
+ broadcast_axis,
25
+ fill_by_indices,
26
+ get_c_contiguous_permutaion,
27
+ inverse_permutation,
28
+ normalize_num_threads,
29
+ )
30
+
31
+
32
+ def scipy_zoom(*args, grid_mode, **kwargs):
33
+ return _scipy_zoom(*args, **kwargs)
34
+
35
+
36
+ scipy_zoom = scipy_zoom if python_version()[:3] == '3.6' else _scipy_zoom
37
+
38
+
39
+ def _choose_cython_zoom(ndim: int, order: int, fast: bool) -> Callable:
40
+ assert ndim <= 4, ndim
41
+ assert order in (0, 1), order
42
+
43
+ if ndim <= 3:
44
+ if order == 0:
45
+ return cython_fast_zoom3d_nearest if fast else cython_zoom3d_nearest
46
+
47
+ return cython_fast_zoom3d_linear if fast else cython_zoom3d_linear
48
+
49
+ if order == 0:
50
+ return cython_fast_zoom4d_nearest if fast else cython_zoom4d_nearest
51
+
52
+ return cython_fast_zoom4d_linear if fast else cython_zoom4d_linear
53
+
54
+
55
+ def _choose_numba_zoom(ndim: int, order: int) -> Callable:
56
+ assert ndim <= 4, ndim
57
+ assert order in (0, 1), order
58
+
59
+ if ndim <= 3:
60
+ if order == 0:
61
+ from .src._numba_zoom import _zoom3d_nearest as numba_zoom
62
+ else:
63
+ from .src._numba_zoom import _zoom3d_linear as numba_zoom
64
+ elif order == 0:
65
+ from .src._numba_zoom import _zoom4d_nearest as numba_zoom
66
+ else:
67
+ from .src._numba_zoom import _zoom4d_linear as numba_zoom
68
+
69
+ return numba_zoom
70
+
71
+
72
+ def zoom(
73
+ x: np.ndarray,
74
+ scale_factor: AxesParams,
75
+ axis: AxesLike = None,
76
+ order: int = 1,
77
+ fill_value: Union[float, Callable] = 0,
78
+ num_threads: int = -1,
79
+ backend: BackendLike = None,
80
+ ) -> np.ndarray:
81
+ """
82
+ Rescale `x` according to `scale_factor` along the `axis`.
83
+
84
+ Uses a fast parallelizable implementation for fp32-fp64 and bool-int16-32-64-uint8-16-32 if order == 0 inputs,
85
+ ndim <= 4 and order = 0 or 1.
86
+
87
+ Parameters
88
+ ----------
89
+ x: np.ndarray
90
+ n-dimensional array
91
+ scale_factor: AxesParams
92
+ float or sequence of floats describing how to scale along axes
93
+ axis: AxesLike
94
+ axis along which array will be scaled
95
+ order: int
96
+ order of interpolation
97
+ fill_value: float | Callable
98
+ value to fill past edges. If Callable (e.g. `numpy.min`) - `fill_value(x)` will be used
99
+ num_threads: int
100
+ the number of threads to use for computation. Default = the cpu count. If negative value passed
101
+ cpu count + num_threads + 1 threads will be used
102
+ backend: BackendLike
103
+ which backend to use. `numba`, `cython` and `scipy` are available, `cython` is used by default
104
+
105
+ Returns
106
+ -------
107
+ zoomed: np.ndarray
108
+ zoomed array
109
+
110
+ Examples
111
+ --------
112
+ ```python
113
+ zoomed = zoom(x, 2, axis=[0, 1]) # 3d array
114
+ zoomed = zoom(x, [1, 2, 3]) # different scales along each axes
115
+ zoomed = zoom(x.astype(int)) # will fall back to scipy's implementation because of int dtype
116
+ ```
117
+ """
118
+ x = np.asarray(x)
119
+ axis, scale_factor = broadcast_axis(axis, x.ndim, scale_factor)
120
+ scale_factor = fill_by_indices(np.ones(x.ndim, 'float64'), scale_factor, axis)
121
+
122
+ if callable(fill_value):
123
+ fill_value = fill_value(x)
124
+
125
+ # TODO: does `fill_value/cval` change anythng?
126
+ return _zoom(x, scale_factor, order=order, cval=fill_value, num_threads=num_threads, backend=backend)
127
+
128
+
129
+ def zoom_to_shape(
130
+ x: np.ndarray,
131
+ shape: AxesLike,
132
+ axis: AxesLike = None,
133
+ order: int = 1,
134
+ fill_value: Union[float, Callable] = 0,
135
+ num_threads: int = -1,
136
+ backend: BackendLike = None,
137
+ ) -> np.ndarray:
138
+ """
139
+ Rescale `x` to match `shape` along the `axis`.
140
+
141
+ Uses a fast parallelizable implementation for fp32-fp64 and bool-int16-32-64-uint8-16-32 if order == 0 inputs,
142
+ ndim <= 4 and order = 0 or 1.
143
+
144
+ Parameters
145
+ ----------
146
+ x: np.ndarray
147
+ n-dimensional array
148
+ shape: AxesLike
149
+ float or sequence of floats describing desired lengths along axes
150
+ axis: AxesLike
151
+ axis along which array will be scaled
152
+ order: int
153
+ order of interpolation
154
+ fill_value: float | Callable
155
+ value to fill past edges. If Callable (e.g. `numpy.min`) - `fill_value(x)` will be used
156
+ num_threads: int
157
+ the number of threads to use for computation. Default = the cpu count. If negative value passed
158
+ cpu count + num_threads + 1 threads will be used
159
+ backend: BackendLike
160
+ which backend to use. `numba`, `cython` and `scipy` are available, `cython` is used by default
161
+
162
+ Returns
163
+ -------
164
+ zoomed: np.ndarray
165
+ zoomed array
166
+
167
+ Examples
168
+ --------
169
+ ```python
170
+ zoomed = zoom_to_shape(x, [3, 4, 5]) # 3d array
171
+ zoomed = zoom_to_shape(x, [6, 7], axis=[1, 2]) # zoom to shape along specified axes
172
+ zoomed = zoom_to_shape(x.astype(int)) # will fall back to scipy's implementation because of int dtype
173
+ ```
174
+ """
175
+ x = np.asarray(x)
176
+ axis, shape = broadcast_axis(axis, x.ndim, shape)
177
+ old_shape = np.array(x.shape, 'float64')
178
+ new_shape = np.array(fill_by_indices(x.shape, shape, axis), 'float64')
179
+
180
+ return zoom(
181
+ x,
182
+ new_shape / old_shape,
183
+ range(x.ndim),
184
+ order=order,
185
+ fill_value=fill_value,
186
+ num_threads=num_threads,
187
+ backend=backend,
188
+ )
189
+
190
+
191
+ def _zoom(
192
+ image: np.ndarray,
193
+ zoom: Sequence[float],
194
+ output: np.ndarray = None,
195
+ order: int = 1,
196
+ mode: str = 'constant',
197
+ cval: float = 0.0,
198
+ prefilter: bool = True,
199
+ *,
200
+ grid_mode: bool = False,
201
+ num_threads: int = -1,
202
+ backend: BackendLike = None,
203
+ ) -> np.ndarray:
204
+ """
205
+ Faster parallelizable version of `scipy.ndimage.zoom` for fp32-fp64 and bool-int16-32-64-uint8-16-32 if order == 0
206
+
207
+ Works faster only for ndim <= 4. Shares interface with `scipy.ndimage.zoom`
208
+ except for
209
+ - `num_threads` argument defining how many threads to use (all available threads are used by default).
210
+ - `backend` argument defining which backend to use. `numba`, `cython` and `scipy` are available,
211
+ `cython` is used by default.
212
+
213
+ See `https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.zoom.html`
214
+ """
215
+ backend = resolve_backend(backend, warn_stacklevel=4)
216
+ if backend.name not in ('Scipy', 'Numba', 'Cython'):
217
+ raise ValueError(f'Unsupported backend "{backend.name}".')
218
+
219
+ ndim = image.ndim
220
+ dtype = image.dtype
221
+ cval = np.dtype(dtype).type(cval)
222
+ zoom = fill_by_indices(np.ones(image.ndim, 'float64'), zoom, range(image.ndim))
223
+ num_threads = normalize_num_threads(num_threads, backend, warn_stacklevel=4)
224
+
225
+ if backend.name == 'Scipy':
226
+ return scipy_zoom(
227
+ image, zoom, output=output, order=order, mode=mode, cval=cval, prefilter=prefilter, grid_mode=grid_mode
228
+ )
229
+
230
+ if (
231
+ (order not in (0, 1))
232
+ or (
233
+ dtype not in (np.float32, np.float64)
234
+ if order == 1
235
+ else dtype
236
+ not in (bool, np.float32, np.float64, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32)
237
+ )
238
+ or ndim > 4
239
+ or output is not None
240
+ or mode != 'constant'
241
+ or grid_mode
242
+ ):
243
+ warn(
244
+ 'Fast zoom is only supported for ndim<=4, dtype=fp32-fp64 and bool-int16-32-64-uint8-16-32 if order == 0, '
245
+ "output=None, order=0 or 1 , mode='constant', grid_mode=False. Falling back to scipy's implementation.",
246
+ stacklevel=3,
247
+ )
248
+ return scipy_zoom(
249
+ image, zoom, output=output, order=order, mode=mode, cval=cval, prefilter=prefilter, grid_mode=grid_mode
250
+ )
251
+
252
+ if backend.name == 'Cython':
253
+ src_zoom = _choose_cython_zoom(ndim, order, backend.fast)
254
+
255
+ if backend.name == 'Numba':
256
+ from numba import get_num_threads, njit, set_num_threads
257
+
258
+ old_num_threads = get_num_threads()
259
+ set_num_threads(num_threads)
260
+
261
+ njit_kwargs = {kwarg: getattr(backend, kwarg) for kwarg in backend.__dataclass_fields__.keys()}
262
+ src_zoom = njit(**njit_kwargs)(_choose_numba_zoom(ndim, order))
263
+
264
+ n_dummy = 3 - ndim if ndim <= 3 else 0
265
+
266
+ if n_dummy:
267
+ image = image[(None,) * n_dummy]
268
+ zoom = [*(1,) * n_dummy, *zoom]
269
+
270
+ zoom = np.array(zoom, dtype=np.float64)
271
+ is_contiguous = image.data.c_contiguous
272
+ c_contiguous_permutaion = None
273
+ args = () if backend.name in ('Numba',) else (num_threads,)
274
+
275
+ if not is_contiguous:
276
+ c_contiguous_permutaion = get_c_contiguous_permutaion(image)
277
+ if c_contiguous_permutaion is not None:
278
+ out = src_zoom(
279
+ np.transpose(image, c_contiguous_permutaion),
280
+ zoom[c_contiguous_permutaion],
281
+ cval,
282
+ *args,
283
+ )
284
+ else:
285
+ warn("Input array can't be represented as C-contiguous, performance can drop a lot.", stacklevel=3)
286
+ out = src_zoom(image, zoom, cval, *args)
287
+ else:
288
+ out = src_zoom(image, zoom, cval, *args)
289
+
290
+ if c_contiguous_permutaion is not None:
291
+ out = np.transpose(out, inverse_permutation(c_contiguous_permutaion))
292
+ if n_dummy:
293
+ out = out[(0,) * n_dummy]
294
+ if backend.name == 'Numba':
295
+ set_num_threads(old_num_threads)
296
+
297
+ return out
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 NeuroML
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.