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.
- _build_utils.py +113 -0
- imops/__init__.py +10 -0
- imops/__version__.py +1 -0
- imops/_configs.py +29 -0
- imops/backend.py +95 -0
- imops/box.py +74 -0
- imops/cpp/cpp_modules.cpython-38-i386-linux-gnu.so +0 -0
- imops/cpp/interp2d/delaunator/delaunator-header-only.hpp +33 -0
- imops/cpp/interp2d/delaunator/delaunator.cpp +645 -0
- imops/cpp/interp2d/delaunator/delaunator.hpp +170 -0
- imops/cpp/interp2d/interpolator.h +52 -0
- imops/cpp/interp2d/triangulator.h +198 -0
- imops/cpp/interp2d/utils.h +63 -0
- imops/cpp/main.cpp +13 -0
- imops/crop.py +120 -0
- imops/interp1d.py +207 -0
- imops/interp2d.py +120 -0
- imops/measure.py +228 -0
- imops/morphology.py +525 -0
- imops/numeric.py +384 -0
- imops/pad.py +253 -0
- imops/py.typed +0 -0
- imops/radon.py +247 -0
- imops/src/__init__.py +0 -0
- imops/src/_backprojection.c +27339 -0
- imops/src/_backprojection.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_fast_backprojection.c +27339 -0
- imops/src/_fast_backprojection.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_fast_measure.c +33810 -0
- imops/src/_fast_measure.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_fast_morphology.c +26089 -0
- imops/src/_fast_morphology.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_fast_numeric.c +48651 -0
- imops/src/_fast_numeric.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_fast_radon.c +30714 -0
- imops/src/_fast_radon.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_fast_zoom.c +57203 -0
- imops/src/_fast_zoom.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_measure.c +33810 -0
- imops/src/_measure.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_morphology.c +26089 -0
- imops/src/_morphology.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_numba_zoom.py +503 -0
- imops/src/_numeric.c +48651 -0
- imops/src/_numeric.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_radon.c +30714 -0
- imops/src/_radon.cpython-38-i386-linux-gnu.so +0 -0
- imops/src/_zoom.c +57203 -0
- imops/src/_zoom.cpython-38-i386-linux-gnu.so +0 -0
- imops/testing.py +57 -0
- imops/utils.py +205 -0
- imops/zoom.py +297 -0
- imops-0.8.8.dist-info/LICENSE +21 -0
- imops-0.8.8.dist-info/METADATA +218 -0
- imops-0.8.8.dist-info/RECORD +58 -0
- imops-0.8.8.dist-info/WHEEL +6 -0
- imops-0.8.8.dist-info/top_level.txt +2 -0
- imops.libs/libgomp-65f46eca.so.1.0.0 +0 -0
|
Binary file
|
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.
|