imops 0.8.1__cp39-cp39-win32.whl → 0.8.3__cp39-cp39-win32.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.

Potentially problematic release.


This version of imops might be problematic. Click here for more details.

Files changed (53) hide show
  1. _build_utils.py +87 -0
  2. imops/__init__.py +1 -0
  3. imops/__version__.py +1 -1
  4. imops/backend.py +14 -10
  5. imops/box.py +20 -29
  6. imops/crop.py +18 -2
  7. imops/interp1d.py +16 -13
  8. imops/measure.py +12 -9
  9. imops/morphology.py +155 -35
  10. imops/numeric.py +376 -0
  11. imops/pad.py +41 -5
  12. imops/radon.py +9 -7
  13. imops/src/_backprojection.c +83 -83
  14. imops/src/_backprojection.cp39-win32.pyd +0 -0
  15. imops/src/_fast_backprojection.c +96 -96
  16. imops/src/_fast_backprojection.cp39-win32.pyd +0 -0
  17. imops/src/_fast_measure.c +96 -96
  18. imops/src/_fast_measure.cp39-win32.pyd +0 -0
  19. imops/src/_fast_morphology.c +2847 -1587
  20. imops/src/_fast_morphology.cp39-win32.pyd +0 -0
  21. imops/src/_fast_morphology.pyx +315 -131
  22. imops/src/_fast_numeric.c +20545 -4996
  23. imops/src/_fast_numeric.cp39-win32.pyd +0 -0
  24. imops/src/_fast_numeric.pyx +208 -30
  25. imops/src/_fast_radon.c +96 -96
  26. imops/src/_fast_radon.cp39-win32.pyd +0 -0
  27. imops/src/_fast_zoom.c +5945 -3342
  28. imops/src/_fast_zoom.cp39-win32.pyd +0 -0
  29. imops/src/_fast_zoom.pyx +1 -0
  30. imops/src/_measure.c +83 -83
  31. imops/src/_measure.cp39-win32.pyd +0 -0
  32. imops/src/_morphology.c +2768 -1553
  33. imops/src/_morphology.cp39-win32.pyd +0 -0
  34. imops/src/_morphology.pyx +315 -131
  35. imops/src/_numeric.c +20532 -4983
  36. imops/src/_numeric.cp39-win32.pyd +0 -0
  37. imops/src/_numeric.pyx +208 -30
  38. imops/src/_radon.c +83 -83
  39. imops/src/_radon.cp39-win32.pyd +0 -0
  40. imops/src/_zoom.c +5932 -3329
  41. imops/src/_zoom.cp39-win32.pyd +0 -0
  42. imops/src/_zoom.pyx +1 -0
  43. imops/utils.py +113 -13
  44. imops/zoom.py +9 -9
  45. {imops-0.8.1.dist-info → imops-0.8.3.dist-info}/METADATA +40 -19
  46. imops-0.8.3.dist-info/RECORD +60 -0
  47. {imops-0.8.1.dist-info → imops-0.8.3.dist-info}/WHEEL +1 -1
  48. imops-0.8.3.dist-info/top_level.txt +2 -0
  49. _pyproject_build.py +0 -49
  50. imops/_numeric.py +0 -124
  51. imops-0.8.1.dist-info/RECORD +0 -60
  52. imops-0.8.1.dist-info/top_level.txt +0 -2
  53. {imops-0.8.1.dist-info → imops-0.8.3.dist-info}/LICENSE +0 -0
Binary file
@@ -11,135 +11,319 @@ cimport numpy as np
11
11
 
12
12
  from cython.parallel import prange
13
13
 
14
+ from libc.stdlib cimport abort, free, malloc
14
15
 
15
- # TODO: Move generic functions like this to the separate file
16
- cdef inline np.uint8_t get_pixel3d(np.uint8_t* input,
17
- Py_ssize_t rows, Py_ssize_t cols, Py_ssize_t dims,
18
- Py_ssize_t r, Py_ssize_t c, Py_ssize_t d,
19
- np.uint8_t cval, layout = b'C') nogil:
20
- if (r < 0) or (r >= rows) or (c < 0) or (c >= cols) or (d < 0) or (d >= dims):
21
- return cval
22
-
23
- if layout == b'C':
24
- return input[r * cols * dims + c * dims + d]
25
-
26
- if layout == b'F':
27
- return input[rows * cols * d + rows * c + r]
28
-
29
-
30
- cdef inline np.uint8_t max_in_footprint(np.uint8_t* input, np.uint8_t* footprint,
31
- Py_ssize_t r, Py_ssize_t c, Py_ssize_t d,
32
- Py_ssize_t rows, Py_ssize_t cols, Py_ssize_t dims,
33
- Py_ssize_t f_rows, Py_ssize_t f_cols, Py_ssize_t f_dims) nogil:
34
- cdef Py_ssize_t i, j, k
35
- cdef Py_ssize_t i_r, i_c, i_d
36
-
37
- cdef int rows_shift = r + f_rows // 2
38
- cdef int cols_shift = c + f_cols // 2
39
- cdef int dims_shift = d + f_dims // 2
40
-
41
- for i in range(f_rows):
42
- i_r = rows_shift - i
43
- for j in range(f_cols):
44
- i_c = cols_shift - j
45
- for k in range(f_dims):
46
- i_d = dims_shift - k
47
-
48
- if (
49
- get_pixel3d(
50
- footprint,
51
- f_rows, f_cols, f_dims,
52
- i, j, k,
53
- False,
54
- ) and
55
- get_pixel3d(
56
- input,
57
- rows, cols, dims,
58
- i_r, i_c, i_d,
59
- False,
60
- )
61
- ):
62
- return True
63
-
64
- return False
65
-
66
-
67
- cdef inline np.uint8_t min_in_footprint(np.uint8_t* input, np.uint8_t* footprint,
68
- Py_ssize_t r, Py_ssize_t c, Py_ssize_t d,
69
- Py_ssize_t rows, Py_ssize_t cols, Py_ssize_t dims,
70
- Py_ssize_t f_rows, Py_ssize_t f_cols, Py_ssize_t f_dims) nogil:
71
- cdef Py_ssize_t i, j, k
72
- cdef Py_ssize_t i_r, i_c, i_d
73
-
74
- cdef int rows_shift = r - f_rows // 2
75
- cdef int cols_shift = c - f_cols // 2
76
- cdef int dims_shift = d - f_dims // 2
77
-
78
- for i in range(f_rows):
79
- i_r = rows_shift + i
80
- for j in range(f_cols):
81
- i_c = cols_shift + j
82
- for k in range(f_dims):
83
- i_d = dims_shift + k
84
-
85
- if (
86
- get_pixel3d(
87
- footprint,
88
- f_rows, f_cols, f_dims,
89
- i, j, k,
90
- False,
91
- ) and not
92
- get_pixel3d(
93
- input,
94
- rows, cols, dims,
95
- i_r, i_c, i_d,
96
- True,
97
- )
98
- ):
99
- return False
100
-
101
- return True
102
-
103
-
104
- def _binary_dilation(np.uint8_t[:, :, :] input, np.uint8_t[:, :, :] footprint, Py_ssize_t num_threads):
105
- cdef np.uint8_t[:, :, ::1] contiguous_input = np.ascontiguousarray(input)
106
- cdef np.uint8_t[:, :, ::1] contiguous_footprint = np.ascontiguousarray(footprint)
107
-
108
- cdef np.uint8_t[:, :, ::1] dilated = np.zeros_like(input, dtype=np.uint8)
109
-
110
- cdef Py_ssize_t rows = input.shape[0], cols = input.shape[1], dims = input.shape[2]
111
- cdef Py_ssize_t f_rows = footprint.shape[0], f_cols = footprint.shape[1], f_dims = footprint.shape[2]
112
- cdef Py_ssize_t i, j, k
113
-
114
- for i in prange(rows, nogil=True, num_threads=num_threads):
115
- for j in prange(cols):
116
- for k in prange(dims):
117
- dilated[i, j, k] = max_in_footprint(
118
- &contiguous_input[0, 0, 0],
119
- &contiguous_footprint[0, 0, 0],
120
- i, j, k, rows, cols, dims, f_rows, f_cols, f_dims
121
- )
122
-
123
- return np.asarray(dilated)
124
-
125
-
126
- def _binary_erosion(np.uint8_t[:, :, :] input, np.uint8_t[:, :, :] footprint, Py_ssize_t num_threads):
127
- cdef np.uint8_t[:, :, ::1] contiguous_input = np.ascontiguousarray(input)
128
- cdef np.uint8_t[:, :, ::1] contiguous_footprint = np.ascontiguousarray(footprint)
129
-
130
- cdef np.uint8_t[:, :, ::1] eroded = np.zeros_like(input, dtype=np.uint8)
131
-
132
- cdef Py_ssize_t rows = input.shape[0], cols = input.shape[1], dims = input.shape[2]
133
- cdef Py_ssize_t f_rows = footprint.shape[0], f_cols = footprint.shape[1], f_dims = footprint.shape[2]
134
- cdef Py_ssize_t i, j, k
135
-
136
- for i in prange(rows, nogil=True, num_threads=num_threads):
137
- for j in prange(cols):
138
- for k in prange(dims):
139
- eroded[i, j, k] = min_in_footprint(
140
- &contiguous_input[0, 0, 0],
141
- &contiguous_footprint[0, 0, 0],
142
- i, j, k, rows, cols, dims, f_rows, f_cols, f_dims
143
- )
144
-
145
- return np.asarray(eroded)
16
+
17
+ cdef struct array_iterator:
18
+ int rank_m1
19
+ int dimensions[3]
20
+ int coordinates[3]
21
+ int strides[3]
22
+ int backstrides[3]
23
+
24
+
25
+ cdef int init_array_iterator(array_iterator *arr_iter, int *shape, int *strides) nogil:
26
+ cdef int i
27
+
28
+ arr_iter.rank_m1 = 2
29
+
30
+ for i in range(3):
31
+ arr_iter[0].dimensions[i] = shape[i] - 1
32
+ arr_iter[0].coordinates[i] = 0
33
+ arr_iter[0].strides[i] = strides[i]
34
+ arr_iter[0].backstrides[i] = strides[i] * arr_iter[0].dimensions[i]
35
+
36
+ return 0
37
+
38
+
39
+ cdef struct filter_iterator:
40
+ int strides[3]
41
+ int backstrides[3]
42
+ int bound1[3]
43
+ int bound2[3]
44
+
45
+
46
+ cdef int init_filter_iterator(
47
+ filter_iterator *filter_iter,
48
+ int *a_shape,
49
+ int *f_shape,
50
+ int filter_size,
51
+ np.uint8_t is_dilation
52
+ ) nogil:
53
+ cdef int i, rank = 3, step, orgn
54
+
55
+ filter_iter[0].strides[rank - 1] = filter_size
56
+ for i in range(rank - 2, -1, -1):
57
+ step = a_shape[i + 1] if a_shape[i + 1] < f_shape[i + 1] else f_shape[i + 1]
58
+ filter_iter[0].strides[i] = filter_iter[0].strides[i + 1] * step
59
+
60
+ for i in range(rank):
61
+ step = a_shape[i] if a_shape[i] < f_shape[i] else f_shape[i]
62
+ orgn = f_shape[i] // 2
63
+ if is_dilation and f_shape[i] % 2 == 0:
64
+ orgn -= 1
65
+
66
+ filter_iter[0].backstrides[i] = (step - 1) * filter_iter[0].strides[i]
67
+ filter_iter[0].bound1[i] = orgn
68
+ filter_iter[0].bound2[i] = a_shape[i] - f_shape[i] + orgn
69
+
70
+ return 0
71
+
72
+
73
+ cdef int init_filter_offsets(
74
+ int *a_shape, int *a_strides,
75
+ np.uint8_t *footprint, int *f_shape,
76
+ int footprint_size,
77
+ int **offsets, int *border_flag_value,
78
+ np.uint8_t is_dilation
79
+ ) nogil:
80
+ cdef int i, j, k
81
+ cdef int filter_size = 1, offsets_size = 1
82
+ cdef int max_size = 0, max_stride = 0
83
+ cdef int coordinates[3]
84
+ cdef int position[3]
85
+ cdef int *po
86
+ cdef int stride, offset, orgn, cc
87
+
88
+ for i in range(3):
89
+ filter_size *= f_shape[i]
90
+
91
+ for i in range(3):
92
+ offsets_size *= (a_shape[i] if a_shape[i] < f_shape[i] else f_shape[i])
93
+
94
+ offsets[0] = <int*>malloc(offsets_size * footprint_size * sizeof(int))
95
+ if offsets[0] == NULL:
96
+ abort()
97
+
98
+ for i in range(3):
99
+ if a_shape[i] > max_size:
100
+ max_size = a_shape[i]
101
+
102
+ stride = -a_strides[i] if a_strides[i] < 0 else a_strides[i]
103
+
104
+ if stride > max_stride:
105
+ max_stride = stride
106
+
107
+ coordinates[i] = 0
108
+ position[i] = 0
109
+
110
+ border_flag_value[0] = max_size * max_stride + 1
111
+ po = offsets[0]
112
+
113
+ for j in range(offsets_size):
114
+ for k in range(filter_size):
115
+ offset = 0
116
+
117
+ # only calculate an offset if the footprint is 1:
118
+ if footprint[k]:
119
+ # find offsets along all axes:
120
+ for i in range(3):
121
+ orgn = f_shape[i] // 2
122
+ if is_dilation and f_shape[i] % 2 == 0:
123
+ orgn -= 1
124
+ cc = coordinates[i] - orgn + position[i]
125
+
126
+ if cc < 0 or cc >= a_shape[i]:
127
+ offset = border_flag_value[0]
128
+ break
129
+ else:
130
+ # use an offset that is possibly mapped from outside the border:
131
+ cc -= position[i]
132
+ offset += a_strides[i] * cc
133
+
134
+ # store the offset
135
+ po[0] = offset
136
+ po += 1
137
+
138
+ # next point in the filter:
139
+ for i in range(3 - 1, -1, -1):
140
+ if coordinates[i] < f_shape[i] - 1:
141
+ coordinates[i] += 1
142
+ break
143
+ else:
144
+ coordinates[i] = 0
145
+
146
+ # move to the next array region:
147
+ for i in range(3 - 1, -1, -1):
148
+ orgn = f_shape[i] // 2
149
+ if is_dilation and f_shape[i] % 2 == 0:
150
+ orgn -= 1
151
+
152
+ if position[i] == orgn:
153
+ position[i] += a_shape[i] - f_shape[i] + 1
154
+
155
+ if position[i] <= orgn:
156
+ position[i] = orgn + 1
157
+ else:
158
+ position[i] += 1
159
+
160
+ if position[i] < a_shape[i]:
161
+ break
162
+ else:
163
+ position[i] = 0
164
+
165
+ return 0
166
+
167
+
168
+ cdef inline int filter_iterator_offset(filter_iterator *filter_iter, int *coordinates) nogil:
169
+ cdef int i, position, offset = 0
170
+
171
+ for i in range(3):
172
+ if filter_iter[0].bound1[i] > filter_iter[0].bound2[i]:
173
+ position = coordinates[i]
174
+ elif coordinates[i] < filter_iter[0].bound1[i]:
175
+ position = coordinates[i]
176
+ elif coordinates[i] >= filter_iter[0].bound2[i]:
177
+ position = filter_iter[0].bound1[i] + coordinates[i] - filter_iter[0].bound2[i]
178
+ else:
179
+ position = filter_iter[0].bound1[i]
180
+
181
+ offset += filter_iter[0].strides[i] * position
182
+
183
+ return offset
184
+
185
+
186
+ cdef inline int worker(
187
+ np.uint8_t *input, np.uint8_t *footprint, np.uint8_t *output,
188
+ int *a_shape, int *a_strides, int *f_shape,
189
+ int footprint_size,
190
+ int *offsets, int border_flag_value,
191
+ int start, int end,
192
+ np.uint8_t border_value,
193
+ np.uint8_t is_dilation,
194
+ ) nogil:
195
+ cdef np.uint8_t _true = not is_dilation
196
+ cdef np.uint8_t _false = not _true
197
+
198
+ cdef array_iterator input_iter
199
+ cdef filter_iterator filter_iter
200
+
201
+ init_array_iterator(&input_iter, a_shape, a_strides)
202
+
203
+ init_filter_iterator(&filter_iter, a_shape, f_shape, footprint_size, is_dilation)
204
+
205
+ cdef int temp = start
206
+ for i in range(3):
207
+ input_iter.coordinates[i] = temp // input_iter.strides[i]
208
+ temp %= input_iter.strides[i]
209
+
210
+ cdef np.uint8_t *pi = input + start
211
+ cdef np.uint8_t *po = output + start
212
+ cdef int *oo = offsets + filter_iterator_offset(&filter_iter, <int*>input_iter.coordinates)
213
+
214
+ cdef np.uint8_t out
215
+ cdef int _oo, _pp
216
+
217
+ for j in range(start, end):
218
+ out = _true
219
+
220
+ for i in range(footprint_size):
221
+ _oo = oo[i]
222
+
223
+ if _oo == border_flag_value:
224
+ if border_value == _false:
225
+ out = _false
226
+ break
227
+ elif pi[_oo] == _false:
228
+ out = _false
229
+ break
230
+
231
+ po[0] = out
232
+
233
+ # pointers and filter next
234
+ for i in range(input_iter.rank_m1, -1, -1):
235
+ _pp = input_iter.coordinates[i]
236
+
237
+ if _pp < input_iter.dimensions[i]:
238
+ if _pp < filter_iter.bound1[i] or _pp >= filter_iter.bound2[i]:
239
+ oo += filter_iter.strides[i]
240
+
241
+ input_iter.coordinates[i] += 1
242
+ break
243
+ else:
244
+ input_iter.coordinates[i] = 0
245
+
246
+ oo -= filter_iter.backstrides[i]
247
+
248
+ pi += 1
249
+ po += 1
250
+
251
+ return 0
252
+
253
+
254
+ def _binary_operation(
255
+ np.uint8_t[:, :, :] input,
256
+ np.uint8_t[:, :, :] footprint,
257
+ np.uint8_t[:, :, ::1] out,
258
+ Py_ssize_t num_threads,
259
+ np.uint8_t border_value,
260
+ np.uint8_t is_dilation,
261
+ ) -> np.ndarray:
262
+ cdef np.uint8_t[:, :, ::1] c_input = np.ascontiguousarray(input)
263
+ cdef np.uint8_t[:, :, ::1] c_footprint = np.ascontiguousarray(footprint)
264
+
265
+ cdef int f_shape[3]
266
+ cdef int a_shape[3]
267
+ cdef int a_strides[3]
268
+ for i in range(3):
269
+ f_shape[i] = c_footprint.shape[i]
270
+ a_shape[i] = c_input.shape[i]
271
+ a_strides[i] = c_input.strides[i]
272
+
273
+ cdef int footprint_size = np.sum(c_footprint)
274
+ cdef int size = np.size(c_input)
275
+
276
+ cdef int *offsets
277
+ cdef int border_flag_value
278
+
279
+ init_filter_offsets(
280
+ a_shape, a_strides,
281
+ &c_footprint[0, 0, 0], f_shape, footprint_size,
282
+ &offsets, &border_flag_value,
283
+ is_dilation
284
+ )
285
+
286
+ cdef int task, start, chunk
287
+
288
+ cdef int _mod = size % num_threads, _div = size // num_threads
289
+
290
+ for task in prange(num_threads, nogil=True, num_threads=num_threads):
291
+ if task < _mod:
292
+ chunk = _div + 1
293
+ start = chunk * task
294
+ else:
295
+ chunk = _div
296
+ start = chunk * task + _mod
297
+
298
+ worker(
299
+ &c_input[0, 0, 0], &c_footprint[0, 0, 0], &out[0, 0, 0],
300
+ a_shape, a_strides, f_shape,
301
+ footprint_size,
302
+ offsets, border_flag_value,
303
+ start, start + chunk,
304
+ border_value,
305
+ is_dilation
306
+ )
307
+
308
+ free(offsets)
309
+
310
+ return np.asarray(out)
311
+
312
+
313
+ def _binary_erosion(
314
+ np.uint8_t[:, :, :] input,
315
+ np.uint8_t[:, :, :] footprint,
316
+ np.uint8_t[:, :, ::1] out,
317
+ Py_ssize_t num_threads,
318
+ ) -> np.ndarray:
319
+ return _binary_operation(input, footprint, out, num_threads, True, False)
320
+
321
+
322
+ def _binary_dilation(
323
+ np.uint8_t[:, :, :] input,
324
+ np.uint8_t[:, :, :] footprint,
325
+ np.uint8_t[:, :, ::1] out,
326
+ Py_ssize_t num_threads,
327
+ ) -> np.ndarray:
328
+ inverted_footprint = np.array(footprint[::-1, ::-1, ::-1])
329
+ return _binary_operation(input, inverted_footprint, out, num_threads, False, True)