imops 0.8.8__cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.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-312-x86_64-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-312-x86_64-linux-gnu.so +0 -0
- imops/src/_fast_backprojection.c +27374 -0
- imops/src/_fast_backprojection.cpython-312-x86_64-linux-gnu.so +0 -0
- imops/src/_fast_measure.c +33845 -0
- imops/src/_fast_measure.cpython-312-x86_64-linux-gnu.so +0 -0
- imops/src/_fast_morphology.c +26124 -0
- imops/src/_fast_morphology.cpython-312-x86_64-linux-gnu.so +0 -0
- imops/src/_fast_numeric.c +48686 -0
- imops/src/_fast_numeric.cpython-312-x86_64-linux-gnu.so +0 -0
- imops/src/_fast_radon.c +30749 -0
- imops/src/_fast_radon.cpython-312-x86_64-linux-gnu.so +0 -0
- imops/src/_fast_zoom.c +57238 -0
- imops/src/_fast_zoom.cpython-312-x86_64-linux-gnu.so +0 -0
- imops/src/_measure.c +33810 -0
- imops/src/_measure.cpython-312-x86_64-linux-gnu.so +0 -0
- imops/src/_morphology.c +26089 -0
- imops/src/_morphology.cpython-312-x86_64-linux-gnu.so +0 -0
- imops/src/_numba_zoom.py +503 -0
- imops/src/_numeric.c +48651 -0
- imops/src/_numeric.cpython-312-x86_64-linux-gnu.so +0 -0
- imops/src/_radon.c +30714 -0
- imops/src/_radon.cpython-312-x86_64-linux-gnu.so +0 -0
- imops/src/_zoom.c +57203 -0
- imops/src/_zoom.cpython-312-x86_64-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-a34b3233.so.1.0.0 +0 -0
imops/morphology.py
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
from typing import Callable, Tuple, Union
|
|
2
|
+
from warnings import warn
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from edt import edt
|
|
6
|
+
from scipy.ndimage import distance_transform_edt as scipy_distance_transform_edt, generate_binary_structure
|
|
7
|
+
from scipy.ndimage._nd_image import euclidean_feature_transform
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from scipy.ndimage._morphology import _ni_support
|
|
12
|
+
except ImportError:
|
|
13
|
+
from scipy.ndimage.morphology import _ni_support
|
|
14
|
+
|
|
15
|
+
from skimage.morphology import (
|
|
16
|
+
binary_closing as scipy_binary_closing,
|
|
17
|
+
binary_dilation as scipy_binary_dilation,
|
|
18
|
+
binary_erosion as scipy_binary_erosion,
|
|
19
|
+
binary_opening as scipy_binary_opening,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from .backend import BackendLike, Cython, Scipy, resolve_backend
|
|
23
|
+
from .box import add_margin, box_to_shape, mask_to_box, shape_to_box
|
|
24
|
+
from .crop import crop_to_box
|
|
25
|
+
from .pad import restore_crop
|
|
26
|
+
from .src._fast_morphology import (
|
|
27
|
+
_binary_dilation as cython_fast_binary_dilation,
|
|
28
|
+
_binary_erosion as cython_fast_binary_erosion,
|
|
29
|
+
)
|
|
30
|
+
from .src._morphology import _binary_dilation as cython_binary_dilation, _binary_erosion as cython_binary_erosion
|
|
31
|
+
from .utils import morphology_composition_args, normalize_num_threads
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def morphology_op_wrapper(
|
|
35
|
+
op_name: str, backend2src_op: Callable[[np.ndarray, np.ndarray, int], np.ndarray]
|
|
36
|
+
) -> Callable:
|
|
37
|
+
def wrapped(
|
|
38
|
+
image: np.ndarray,
|
|
39
|
+
footprint: np.ndarray = None,
|
|
40
|
+
output: np.ndarray = None,
|
|
41
|
+
boxed: bool = False,
|
|
42
|
+
num_threads: int = -1,
|
|
43
|
+
backend: BackendLike = None,
|
|
44
|
+
) -> np.ndarray:
|
|
45
|
+
backend = resolve_backend(backend, warn_stacklevel=4)
|
|
46
|
+
if backend.name not in {x.name for x in backend2src_op.keys()}:
|
|
47
|
+
raise ValueError(f'Unsupported backend "{backend.name}".')
|
|
48
|
+
|
|
49
|
+
ndim = image.ndim
|
|
50
|
+
num_threads = normalize_num_threads(num_threads, backend, warn_stacklevel=4)
|
|
51
|
+
|
|
52
|
+
if footprint is None:
|
|
53
|
+
footprint = generate_binary_structure(ndim, 1)
|
|
54
|
+
elif not footprint.size:
|
|
55
|
+
raise RuntimeError('Footprint must not be empty.')
|
|
56
|
+
|
|
57
|
+
if output is None:
|
|
58
|
+
output = np.empty_like(image, dtype=bool)
|
|
59
|
+
elif boxed:
|
|
60
|
+
raise ValueError('`boxed==True` is incompatible with provided `output`')
|
|
61
|
+
elif output.shape != image.shape:
|
|
62
|
+
raise ValueError('Input image and output image shapes must be the same.')
|
|
63
|
+
elif output.dtype != bool:
|
|
64
|
+
raise ValueError(f'Output image must have `bool` dtype, got {output.dtype}.')
|
|
65
|
+
elif not output.data.c_contiguous:
|
|
66
|
+
# TODO: Implement morphology for `output` of arbitrary layout
|
|
67
|
+
raise ValueError('`output` must be a C-contiguous array.')
|
|
68
|
+
|
|
69
|
+
src_op = backend2src_op[backend]
|
|
70
|
+
|
|
71
|
+
if backend.name == 'Scipy':
|
|
72
|
+
if boxed:
|
|
73
|
+
raise ValueError('`boxed==True` is incompatible with "Scipy" backend.')
|
|
74
|
+
src_op(image, footprint, out=output)
|
|
75
|
+
|
|
76
|
+
return output
|
|
77
|
+
|
|
78
|
+
if ndim > 3:
|
|
79
|
+
warn(
|
|
80
|
+
f"Fast {' '.join(op_name.split('_'))} is only supported for ndim<=3. "
|
|
81
|
+
"Falling back to scipy's implementation.",
|
|
82
|
+
stacklevel=3,
|
|
83
|
+
)
|
|
84
|
+
backend2src_op[Scipy()](image, footprint, out=output)
|
|
85
|
+
|
|
86
|
+
return output
|
|
87
|
+
|
|
88
|
+
if footprint.ndim != image.ndim:
|
|
89
|
+
raise ValueError('Input image and footprint number of dimensions must be the same.')
|
|
90
|
+
|
|
91
|
+
if not image.any():
|
|
92
|
+
warn(f'{op_name} is applied to the fully False mask (mask.any() == False).', stacklevel=3) # noqa
|
|
93
|
+
output.fill(False)
|
|
94
|
+
|
|
95
|
+
return output
|
|
96
|
+
|
|
97
|
+
if image.all():
|
|
98
|
+
warn(f'{op_name} is applied to the fully True mask (mask.all() == True).', stacklevel=3) # noqa
|
|
99
|
+
output.fill(True)
|
|
100
|
+
|
|
101
|
+
return output
|
|
102
|
+
|
|
103
|
+
n_dummy = 3 - ndim
|
|
104
|
+
|
|
105
|
+
if n_dummy:
|
|
106
|
+
image = image[(None,) * n_dummy]
|
|
107
|
+
output = output[(None,) * n_dummy]
|
|
108
|
+
footprint = footprint[(None,) * n_dummy]
|
|
109
|
+
|
|
110
|
+
src_op_args = (image.astype(bool, copy=False), footprint.astype(bool, copy=False), output, num_threads)
|
|
111
|
+
output = boxed_morphology(src_op, op_name)(*src_op_args) if boxed else src_op(*src_op_args)
|
|
112
|
+
|
|
113
|
+
if n_dummy:
|
|
114
|
+
output = output[(0,) * n_dummy]
|
|
115
|
+
|
|
116
|
+
return output
|
|
117
|
+
|
|
118
|
+
return wrapped
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def boxed_morphology(func, op_name) -> Callable:
|
|
122
|
+
# TODO: for consistency support exotic footprints which alter border pixels in Scikit-Image different from the
|
|
123
|
+
# current implementation, e.g. footrint [[1, 1], [1, 0]] sets border pixel to 1 for `binary_erosion`
|
|
124
|
+
def wrapped(
|
|
125
|
+
image: np.ndarray,
|
|
126
|
+
footprint: np.ndarray,
|
|
127
|
+
output: np.ndarray,
|
|
128
|
+
num_threads: int,
|
|
129
|
+
) -> np.ndarray:
|
|
130
|
+
box_delta = np.asarray(footprint.shape) // 2
|
|
131
|
+
|
|
132
|
+
image_box = shape_to_box(image.shape)
|
|
133
|
+
tight_box = mask_to_box(image)
|
|
134
|
+
supp_box = add_margin(tight_box, 2 * box_delta)
|
|
135
|
+
|
|
136
|
+
# TODO: generalize to "anisotropic" images
|
|
137
|
+
# TODO: make separate class for `Box` and implement comparison operators?
|
|
138
|
+
if (supp_box[0] < image_box[0]).any() or (image_box[1] < supp_box[1]).any():
|
|
139
|
+
return func(image, footprint, output, num_threads)
|
|
140
|
+
|
|
141
|
+
final_crop_box = add_margin(tight_box, box_delta)
|
|
142
|
+
|
|
143
|
+
supp_image = crop_to_box(image, supp_box)
|
|
144
|
+
supp_output = np.empty_like(supp_image, dtype=bool)
|
|
145
|
+
|
|
146
|
+
cropped = crop_to_box(
|
|
147
|
+
func(supp_image, footprint, supp_output, num_threads),
|
|
148
|
+
add_margin(shape_to_box(box_to_shape(supp_box)), -box_delta), # crop border values of supp_box
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
output = restore_crop(cropped, final_crop_box, image.shape, False)
|
|
152
|
+
|
|
153
|
+
return output
|
|
154
|
+
|
|
155
|
+
return wrapped
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
_binary_dilation = morphology_op_wrapper(
|
|
159
|
+
'binary_dilation',
|
|
160
|
+
{
|
|
161
|
+
Scipy(): scipy_binary_dilation,
|
|
162
|
+
Cython(fast=False): cython_binary_dilation,
|
|
163
|
+
Cython(fast=True): cython_fast_binary_dilation,
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def binary_dilation(
|
|
169
|
+
image: np.ndarray,
|
|
170
|
+
footprint: np.ndarray = None,
|
|
171
|
+
output: np.ndarray = None,
|
|
172
|
+
boxed: bool = False,
|
|
173
|
+
num_threads: int = -1,
|
|
174
|
+
backend: BackendLike = None,
|
|
175
|
+
) -> np.ndarray:
|
|
176
|
+
"""
|
|
177
|
+
Fast parallelizable binary morphological dilation of an image
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
image: np.ndarray
|
|
182
|
+
input image
|
|
183
|
+
footprint: np.ndarray
|
|
184
|
+
the neighborhood expressed as a n-D array of 1's and 0's. If None, use a cross-shaped footprint (connectivity=1)
|
|
185
|
+
output: np.ndarray
|
|
186
|
+
array of the same shape as input, into which the output is placed (must be C-contiguous). By default, a new
|
|
187
|
+
array is created
|
|
188
|
+
boxed: bool
|
|
189
|
+
if True, dilation is performed on cropped image which may speed up computation depedning on how localized True
|
|
190
|
+
pixels are. This may induce differences with Scikit-Image implementation at border pixels if footprint is
|
|
191
|
+
exotic (has even shape or center pixel is False)
|
|
192
|
+
num_threads: int
|
|
193
|
+
the number of threads to use for computation. Default = the cpu count. If negative value passed
|
|
194
|
+
cpu count + num_threads + 1 threads will be used
|
|
195
|
+
backend: BackendLike
|
|
196
|
+
which backend to use. `cython` and `scipy` are available, `cython` is used by default
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
dilated: np.ndarray
|
|
201
|
+
the result of morphological dilation
|
|
202
|
+
|
|
203
|
+
Examples
|
|
204
|
+
--------
|
|
205
|
+
```python
|
|
206
|
+
dilated = binary_dilation(x)
|
|
207
|
+
```
|
|
208
|
+
"""
|
|
209
|
+
return _binary_dilation(image, footprint, output, boxed, num_threads, backend)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
_binary_erosion = morphology_op_wrapper(
|
|
213
|
+
'binary_erosion',
|
|
214
|
+
{
|
|
215
|
+
Scipy(): scipy_binary_erosion,
|
|
216
|
+
Cython(fast=False): cython_binary_erosion,
|
|
217
|
+
Cython(fast=True): cython_fast_binary_erosion,
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def binary_erosion(
|
|
223
|
+
image: np.ndarray,
|
|
224
|
+
footprint: np.ndarray = None,
|
|
225
|
+
output: np.ndarray = None,
|
|
226
|
+
boxed: bool = False,
|
|
227
|
+
num_threads: int = -1,
|
|
228
|
+
backend: BackendLike = None,
|
|
229
|
+
) -> np.ndarray:
|
|
230
|
+
"""
|
|
231
|
+
Fast parallelizable binary morphological erosion of an image
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
image: np.ndarray
|
|
236
|
+
input image
|
|
237
|
+
footprint: np.ndarray
|
|
238
|
+
the neighborhood expressed as a n-D array of 1's and 0's. If None, use a cross-shaped footprint (connectivity=1)
|
|
239
|
+
output: np.ndarray
|
|
240
|
+
array of the same shape as input, into which the output is placed (must be C-contiguous). By default, a new
|
|
241
|
+
array is created
|
|
242
|
+
boxed: bool
|
|
243
|
+
if True, erosion is performed on cropped image which may speed up computation depedning on how localized True
|
|
244
|
+
pixels are. This may induce differences with Scikit-Image implementation at border pixels if footprint is
|
|
245
|
+
exotic (has even shape or center pixel is False)
|
|
246
|
+
num_threads: int
|
|
247
|
+
the number of threads to use for computation. Default = the cpu count. If negative value passed
|
|
248
|
+
cpu count + num_threads + 1 threads will be used
|
|
249
|
+
backend: BackendLike
|
|
250
|
+
which backend to use. `cython` and `scipy` are available, `cython` is used by default
|
|
251
|
+
|
|
252
|
+
Returns
|
|
253
|
+
-------
|
|
254
|
+
eroded: np.ndarray
|
|
255
|
+
the result of morphological erosion
|
|
256
|
+
|
|
257
|
+
Examples
|
|
258
|
+
--------
|
|
259
|
+
```python
|
|
260
|
+
eroded = binary_erosion(x)
|
|
261
|
+
```
|
|
262
|
+
"""
|
|
263
|
+
return _binary_erosion(image, footprint, output, boxed, num_threads, backend)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
_binary_closing = morphology_op_wrapper(
|
|
267
|
+
'binary_closing',
|
|
268
|
+
{
|
|
269
|
+
Scipy(): scipy_binary_closing,
|
|
270
|
+
Cython(fast=False): morphology_composition_args(cython_binary_erosion, cython_binary_dilation),
|
|
271
|
+
Cython(fast=True): morphology_composition_args(cython_fast_binary_erosion, cython_fast_binary_dilation),
|
|
272
|
+
},
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def binary_closing(
|
|
277
|
+
image: np.ndarray,
|
|
278
|
+
footprint: np.ndarray = None,
|
|
279
|
+
output: np.ndarray = None,
|
|
280
|
+
boxed: bool = False,
|
|
281
|
+
num_threads: int = -1,
|
|
282
|
+
backend: BackendLike = None,
|
|
283
|
+
) -> np.ndarray:
|
|
284
|
+
"""
|
|
285
|
+
Fast parallelizable binary morphological closing of an image
|
|
286
|
+
|
|
287
|
+
Parameters
|
|
288
|
+
----------
|
|
289
|
+
image: np.ndarray
|
|
290
|
+
input image
|
|
291
|
+
footprint: np.ndarray
|
|
292
|
+
the neighborhood expressed as a n-D array of 1's and 0's. If None, use a cross-shaped footprint (connectivity=1)
|
|
293
|
+
output: np.ndarray
|
|
294
|
+
array of the same shape as input, into which the output is placed (must be C-contiguous). By default, a new
|
|
295
|
+
array is created
|
|
296
|
+
boxed: bool
|
|
297
|
+
if True, closing is performed on cropped image which may speed up computation depedning on how localized True
|
|
298
|
+
pixels are. This may induce differences with Scikit-Image implementation at border pixels if footprint is
|
|
299
|
+
exotic (has even shape or center pixel is False)
|
|
300
|
+
num_threads: int
|
|
301
|
+
the number of threads to use for computation. Default = the cpu count. If negative value passed
|
|
302
|
+
cpu count + num_threads + 1 threads will be used
|
|
303
|
+
backend: BackendLike
|
|
304
|
+
which backend to use. `cython` and `scipy` are available, `cython` is used by default
|
|
305
|
+
|
|
306
|
+
Returns
|
|
307
|
+
-------
|
|
308
|
+
closed: np.ndarray
|
|
309
|
+
the result of morphological closing
|
|
310
|
+
|
|
311
|
+
Examples
|
|
312
|
+
--------
|
|
313
|
+
```python
|
|
314
|
+
closed = binary_closing(x)
|
|
315
|
+
```
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
return _binary_closing(image, footprint, output, boxed, num_threads, backend)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
_binary_opening = morphology_op_wrapper(
|
|
322
|
+
'binary_opening',
|
|
323
|
+
{
|
|
324
|
+
Scipy(): scipy_binary_opening,
|
|
325
|
+
Cython(fast=False): morphology_composition_args(cython_binary_dilation, cython_binary_erosion),
|
|
326
|
+
Cython(fast=True): morphology_composition_args(cython_fast_binary_dilation, cython_fast_binary_erosion),
|
|
327
|
+
},
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def binary_opening(
|
|
332
|
+
image: np.ndarray,
|
|
333
|
+
footprint: np.ndarray = None,
|
|
334
|
+
output: np.ndarray = None,
|
|
335
|
+
boxed: bool = False,
|
|
336
|
+
num_threads: int = -1,
|
|
337
|
+
backend: BackendLike = None,
|
|
338
|
+
) -> np.ndarray:
|
|
339
|
+
"""
|
|
340
|
+
Fast parallelizable binary morphological opening of an image
|
|
341
|
+
|
|
342
|
+
Parameters
|
|
343
|
+
----------
|
|
344
|
+
image: np.ndarray
|
|
345
|
+
input image
|
|
346
|
+
footprint: np.ndarray
|
|
347
|
+
the neighborhood expressed as a n-D array of 1's and 0's. If None, use a cross-shaped footprint (connectivity=1)
|
|
348
|
+
output: np.ndarray
|
|
349
|
+
array of the same shape as input, into which the output is placed (must be C-contiguous). By default, a new
|
|
350
|
+
array is created
|
|
351
|
+
boxed: bool
|
|
352
|
+
if True, opening is performed on cropped image which may speed up computation depedning on how localized True
|
|
353
|
+
pixels are. This may induce differences with Scikit-Image implementation at border pixels if footprint is
|
|
354
|
+
exotic (has even shape or center pixel is False)
|
|
355
|
+
num_threads: int
|
|
356
|
+
the number of threads to use for computation. Default = the cpu count. If negative value passed
|
|
357
|
+
cpu count + num_threads + 1 threads will be used
|
|
358
|
+
backend: BackendLike
|
|
359
|
+
which backend to use. `cython` and `scipy` are available, `cython` is used by default
|
|
360
|
+
|
|
361
|
+
Returns
|
|
362
|
+
-------
|
|
363
|
+
opened: np.ndarray
|
|
364
|
+
the result of morphological opening
|
|
365
|
+
|
|
366
|
+
Examples
|
|
367
|
+
--------
|
|
368
|
+
```python
|
|
369
|
+
opened = binary_opening(x)
|
|
370
|
+
```
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
return _binary_opening(image, footprint, output, boxed, num_threads, backend)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def distance_transform_edt(
|
|
377
|
+
image: np.ndarray,
|
|
378
|
+
sampling: Tuple[float] = None,
|
|
379
|
+
return_distances: bool = True,
|
|
380
|
+
return_indices: bool = False,
|
|
381
|
+
num_threads: int = -1,
|
|
382
|
+
backend: BackendLike = None,
|
|
383
|
+
) -> Union[np.ndarray, Tuple[np.ndarray]]:
|
|
384
|
+
"""
|
|
385
|
+
Fast parallelizable Euclidean distance transform for <= 3D inputs
|
|
386
|
+
|
|
387
|
+
This function calculates the distance transform of the `image`, by
|
|
388
|
+
replacing each foreground (non-zero) element, with its
|
|
389
|
+
shortest distance to the background (any zero-valued element).
|
|
390
|
+
|
|
391
|
+
In addition to the distance transform, the feature transform can
|
|
392
|
+
be calculated. In this case the index of the closest background
|
|
393
|
+
element to each foreground element is returned in a separate array.
|
|
394
|
+
|
|
395
|
+
Parameters
|
|
396
|
+
----------
|
|
397
|
+
image : array_like
|
|
398
|
+
input data to transform. Can be any type but will be converted
|
|
399
|
+
into binary: 1 wherever input equates to True, 0 elsewhere
|
|
400
|
+
sampling : tuple of `image.ndim` floats, optional
|
|
401
|
+
spacing of elements along each dimension. If a sequence, must be of
|
|
402
|
+
length equal to the input rank; if a single number, this is used for
|
|
403
|
+
all axes. If not specified, a grid spacing of unity is implied
|
|
404
|
+
return_distances : bool, optional
|
|
405
|
+
whether to calculate the distance transform.
|
|
406
|
+
Default is True
|
|
407
|
+
return_indices : bool, optional
|
|
408
|
+
whether to calculate the feature transform.
|
|
409
|
+
Default is False
|
|
410
|
+
num_threads: int
|
|
411
|
+
the number of threads to use for computation. Default = the cpu count. If negative value passed
|
|
412
|
+
cpu count + num_threads + 1 threads will be used
|
|
413
|
+
backend: BackendLike
|
|
414
|
+
which backend to use. `cython` and `scipy` are available, `cython` is used by default
|
|
415
|
+
|
|
416
|
+
Returns
|
|
417
|
+
-------
|
|
418
|
+
distances : float32 ndarray, optional
|
|
419
|
+
the calculated distance transform. Returned only when
|
|
420
|
+
`return_distances` is True and `distances` is not supplied.
|
|
421
|
+
It will have the same shape as the input array
|
|
422
|
+
indices : int32 ndarray, optional
|
|
423
|
+
the calculated feature transform. It has an input-shaped array for each
|
|
424
|
+
dimension of the input. See example below.
|
|
425
|
+
Returned only when `return_indices` is True and `indices` is not
|
|
426
|
+
supplied
|
|
427
|
+
|
|
428
|
+
Notes
|
|
429
|
+
-----
|
|
430
|
+
The Euclidean distance transform gives values of the Euclidean
|
|
431
|
+
distance::
|
|
432
|
+
|
|
433
|
+
n
|
|
434
|
+
y_i = sqrt(sum (x[i]-b[i])**2)
|
|
435
|
+
i
|
|
436
|
+
|
|
437
|
+
where b[i] is the background point (value 0) with the smallest
|
|
438
|
+
Euclidean distance to input points x[i], and n is the
|
|
439
|
+
number of dimensions.
|
|
440
|
+
|
|
441
|
+
Examples
|
|
442
|
+
--------
|
|
443
|
+
import numpy as np
|
|
444
|
+
a = np.array(([0,1,1,1,1],
|
|
445
|
+
[0,0,1,1,1],
|
|
446
|
+
[0,1,1,1,1],
|
|
447
|
+
[0,1,1,1,0],
|
|
448
|
+
[0,1,1,0,0]))
|
|
449
|
+
distance_transform_edt(a)
|
|
450
|
+
array([[ 0. , 1. , 1.4142, 2.2361, 3. ],
|
|
451
|
+
[ 0. , 0. , 1. , 2. , 2. ],
|
|
452
|
+
[ 0. , 1. , 1.4142, 1.4142, 1. ],
|
|
453
|
+
[ 0. , 1. , 1.4142, 1. , 0. ],
|
|
454
|
+
[ 0. , 1. , 1. , 0. , 0. ]])
|
|
455
|
+
|
|
456
|
+
With a sampling of 2 units along x, 1 along y:
|
|
457
|
+
|
|
458
|
+
distance_transform_edt(a, sampling=[2, 1])
|
|
459
|
+
array([[ 0. , 1. , 2. , 2.8284, 3.6056],
|
|
460
|
+
[ 0. , 0. , 1. , 2. , 3. ],
|
|
461
|
+
[ 0. , 1. , 2. , 2.2361, 2. ],
|
|
462
|
+
[ 0. , 1. , 2. , 1. , 0. ],
|
|
463
|
+
[ 0. , 1. , 1. , 0. , 0. ]])
|
|
464
|
+
|
|
465
|
+
Asking for indices as well:
|
|
466
|
+
|
|
467
|
+
edt, inds = distance_transform_edt(a, return_indices=True)
|
|
468
|
+
inds
|
|
469
|
+
array([[[0, 0, 1, 1, 3],
|
|
470
|
+
[1, 1, 1, 1, 3],
|
|
471
|
+
[2, 2, 1, 3, 3],
|
|
472
|
+
[3, 3, 4, 4, 3],
|
|
473
|
+
[4, 4, 4, 4, 4]],
|
|
474
|
+
[[0, 0, 1, 1, 4],
|
|
475
|
+
[0, 1, 1, 1, 4],
|
|
476
|
+
[0, 0, 1, 4, 4],
|
|
477
|
+
[0, 0, 3, 3, 4],
|
|
478
|
+
[0, 0, 3, 3, 4]]])
|
|
479
|
+
"""
|
|
480
|
+
backend = resolve_backend(backend, warn_stacklevel=3)
|
|
481
|
+
if backend.name not in ('Scipy', 'Cython'):
|
|
482
|
+
raise ValueError(f'Unsupported backend "{backend.name}".')
|
|
483
|
+
|
|
484
|
+
num_threads = normalize_num_threads(num_threads, backend, warn_stacklevel=3)
|
|
485
|
+
|
|
486
|
+
if backend.name == 'Scipy':
|
|
487
|
+
return scipy_distance_transform_edt(image, sampling, return_distances, return_indices)
|
|
488
|
+
|
|
489
|
+
if image.ndim > 3:
|
|
490
|
+
warn("Fast Euclidean Distance Transform is only supported for ndim<=3. Falling back to scipy's implementation.")
|
|
491
|
+
return scipy_distance_transform_edt(image, sampling, return_distances, return_indices)
|
|
492
|
+
|
|
493
|
+
if (not return_distances) and (not return_indices):
|
|
494
|
+
raise RuntimeError('At least one of `return_distances`/`return_indices` must be True')
|
|
495
|
+
if image.dtype != bool:
|
|
496
|
+
image = np.atleast_1d(np.where(image, 1, 0))
|
|
497
|
+
if sampling is not None:
|
|
498
|
+
sampling = _ni_support._normalize_sequence(sampling, image.ndim)
|
|
499
|
+
sampling = np.asarray(sampling, dtype=np.float64)
|
|
500
|
+
if not sampling.flags.contiguous:
|
|
501
|
+
sampling = sampling.copy()
|
|
502
|
+
|
|
503
|
+
if return_indices:
|
|
504
|
+
ft = np.zeros((image.ndim,) + image.shape, dtype=np.int32)
|
|
505
|
+
euclidean_feature_transform(image, sampling, ft)
|
|
506
|
+
|
|
507
|
+
if return_distances:
|
|
508
|
+
if sampling is not None:
|
|
509
|
+
dt = edt(image, anisotropy=sampling.astype(np.float32), parallel=num_threads)
|
|
510
|
+
else:
|
|
511
|
+
dt = edt(image, parallel=num_threads)
|
|
512
|
+
|
|
513
|
+
result = []
|
|
514
|
+
if return_distances:
|
|
515
|
+
result.append(dt)
|
|
516
|
+
if return_indices:
|
|
517
|
+
result.append(ft)
|
|
518
|
+
|
|
519
|
+
if len(result) == 2:
|
|
520
|
+
return tuple(result)
|
|
521
|
+
|
|
522
|
+
if len(result) == 1:
|
|
523
|
+
return result[0]
|
|
524
|
+
|
|
525
|
+
return None
|