numba-morph 0.2.2__tar.gz → 0.2.3__tar.gz

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 (45) hide show
  1. {numba_morph-0.2.2/src/numba_morph.egg-info → numba_morph-0.2.3}/PKG-INFO +1 -1
  2. {numba_morph-0.2.2 → numba_morph-0.2.3}/pyproject.toml +1 -1
  3. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/__init__.py +1 -1
  4. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/_tiled_propagation.py +36 -62
  5. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/erosion.py +23 -4
  6. {numba_morph-0.2.2 → numba_morph-0.2.3/src/numba_morph.egg-info}/PKG-INFO +1 -1
  7. {numba_morph-0.2.2 → numba_morph-0.2.3}/tests/test_reconstuction.py +1 -1
  8. {numba_morph-0.2.2 → numba_morph-0.2.3}/LICENSE.md +0 -0
  9. {numba_morph-0.2.2 → numba_morph-0.2.3}/README.md +0 -0
  10. {numba_morph-0.2.2 → numba_morph-0.2.3}/setup.cfg +0 -0
  11. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/_chamfer.py +0 -0
  12. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/_edt.py +0 -0
  13. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/_propagation.py +0 -0
  14. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/_scan.py +0 -0
  15. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/_skeletonise.py +0 -0
  16. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/_watershed.py +0 -0
  17. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/cdt.py +0 -0
  18. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/dilation.py +0 -0
  19. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/fill_holes.py +0 -0
  20. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/gradient.py +0 -0
  21. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/h_min_max.py +0 -0
  22. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/laplace.py +0 -0
  23. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/local_min_max.py +0 -0
  24. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/open_close.py +0 -0
  25. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/reconstruction.py +0 -0
  26. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/reconstruction_old.py +0 -0
  27. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/skeletonise.py +0 -0
  28. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/top_hats.py +0 -0
  29. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/utils.py +0 -0
  30. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/watershed.py +0 -0
  31. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph/welford.py +0 -0
  32. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph.egg-info/SOURCES.txt +0 -0
  33. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph.egg-info/dependency_links.txt +0 -0
  34. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph.egg-info/requires.txt +0 -0
  35. {numba_morph-0.2.2 → numba_morph-0.2.3}/src/numba_morph.egg-info/top_level.txt +0 -0
  36. {numba_morph-0.2.2 → numba_morph-0.2.3}/tests/test_cdt.py +0 -0
  37. {numba_morph-0.2.2 → numba_morph-0.2.3}/tests/test_dilation.py +0 -0
  38. {numba_morph-0.2.2 → numba_morph-0.2.3}/tests/test_erosion.py +0 -0
  39. {numba_morph-0.2.2 → numba_morph-0.2.3}/tests/test_gradient.py +0 -0
  40. {numba_morph-0.2.2 → numba_morph-0.2.3}/tests/test_h_min_max.py +0 -0
  41. {numba_morph-0.2.2 → numba_morph-0.2.3}/tests/test_laplace.py +0 -0
  42. {numba_morph-0.2.2 → numba_morph-0.2.3}/tests/test_local_min_max.py +0 -0
  43. {numba_morph-0.2.2 → numba_morph-0.2.3}/tests/test_open_close.py +0 -0
  44. {numba_morph-0.2.2 → numba_morph-0.2.3}/tests/test_tophats.py +0 -0
  45. {numba_morph-0.2.2 → numba_morph-0.2.3}/tests/test_watershed.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: numba-morph
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Some image morphology operations optimized with Numba
5
5
  Author-email: Yuyao Huang <yuyaoh6@gmail.com>
6
6
  License-Expression: MIT
@@ -9,7 +9,7 @@ exclude = ["tests*"]
9
9
 
10
10
  [project]
11
11
  name = "numba-morph"
12
- version = "0.2.2"
12
+ version = "0.2.3"
13
13
  authors = [
14
14
  { name="Yuyao Huang", email="yuyaoh6@gmail.com" },
15
15
  ]
@@ -1,4 +1,4 @@
1
- __version__ = "0.2.2"
1
+ __version__ = "0.2.3"
2
2
 
3
3
  from .cdt import distance_transform_cdt
4
4
  from .dilation import dilation
@@ -73,11 +73,11 @@ def _compute_dirty_3d_batched(changed, n_tiles_d, n_tiles_h, n_tiles_w):
73
73
  return dirty
74
74
 
75
75
  @njit(parallel=True, fastmath=True, cache=True)
76
- def _tiled_sweep_2d(src, dst, mask, offsets, mode_code, cval, erosion, dtype,
76
+ def _tiled_sweep_2d(marker, mask, offsets, mode_code, cval, erosion, dtype,
77
77
  tile_h, tile_w, n_tiles_w, n_tiles,
78
78
  dirty_idx, scratch_marker, scratch_mask):
79
- H = src.shape[0]
80
- W = src.shape[1]
79
+ H = marker.shape[0]
80
+ W = marker.shape[1]
81
81
  n_dirty = dirty_idx.shape[0]
82
82
  changed = np.zeros(n_tiles, dtype=np.bool_)
83
83
 
@@ -102,27 +102,27 @@ def _tiled_sweep_2d(src, dst, mask, offsets, mode_code, cval, erosion, dtype,
102
102
  local_marker = scratch_marker[tid, :th, :tw]
103
103
  local_mask = scratch_mask[tid, :th, :tw]
104
104
 
105
- local_marker[:, :] = src[hh0:hh1, ww0:ww1]
105
+ local_marker[:, :] = marker[hh0:hh1, ww0:ww1]
106
106
  local_mask[:, :] = mask[hh0:hh1, ww0:ww1]
107
107
 
108
108
  _propagate_2d(local_marker, local_mask, offsets,
109
109
  mode_code, cval, erosion, dtype)
110
110
 
111
111
  new_slice = local_marker[h0 - hh0:h1 - hh0, w0 - ww0:w1 - ww0]
112
- old_slice = src[h0:h1, w0:w1]
112
+ old_slice = marker[h0:h1, w0:w1]
113
113
  changed[t] = not np.array_equal(new_slice, old_slice)
114
- dst[h0:h1, w0:w1] = new_slice
114
+ marker[h0:h1, w0:w1] = new_slice
115
115
 
116
116
  return changed
117
117
 
118
118
 
119
119
  @njit(parallel=True, fastmath=True, cache=True)
120
- def _tiled_sweep_2d_batched(src, dst, mask, offsets, mode_code, cval, erosion, dtype,
120
+ def _tiled_sweep_2d_batched(marker, mask, offsets, mode_code, cval, erosion, dtype,
121
121
  tile_h, tile_w, n_tiles_w, n_tiles,
122
122
  dirty_b, dirty_t, scratch_marker, scratch_mask):
123
- B = src.shape[0]
124
- H = src.shape[1]
125
- W = src.shape[2]
123
+ B = marker.shape[0]
124
+ H = marker.shape[1]
125
+ W = marker.shape[2]
126
126
  n_dirty = dirty_b.shape[0]
127
127
  changed = np.zeros((B, n_tiles), dtype=np.bool_)
128
128
 
@@ -148,16 +148,16 @@ def _tiled_sweep_2d_batched(src, dst, mask, offsets, mode_code, cval, erosion, d
148
148
  local_marker = scratch_marker[tid, :th, :tw]
149
149
  local_mask = scratch_mask[tid, :th, :tw]
150
150
 
151
- local_marker[:, :] = src[b, hh0:hh1, ww0:ww1]
151
+ local_marker[:, :] = marker[b, hh0:hh1, ww0:ww1]
152
152
  local_mask[:, :] = mask[b, hh0:hh1, ww0:ww1]
153
153
 
154
154
  _propagate_2d(local_marker, local_mask, offsets,
155
155
  mode_code, cval, erosion, dtype)
156
156
 
157
157
  new_slice = local_marker[h0 - hh0:h1 - hh0, w0 - ww0:w1 - ww0]
158
- old_slice = src[b, h0:h1, w0:w1]
158
+ old_slice = marker[b, h0:h1, w0:w1]
159
159
  changed[b, t] = not np.array_equal(new_slice, old_slice)
160
- dst[b, h0:h1, w0:w1] = new_slice
160
+ marker[b, h0:h1, w0:w1] = new_slice
161
161
 
162
162
  return changed
163
163
 
@@ -166,12 +166,12 @@ def _tiled_sweep_2d_batched(src, dst, mask, offsets, mode_code, cval, erosion, d
166
166
  # 3D sweeps
167
167
  # ----------------------------------------------------------------------
168
168
  @njit(parallel=True, fastmath=True, cache=True)
169
- def _tiled_sweep_3d(src, dst, mask, offsets, mode_code, cval, erosion, dtype,
169
+ def _tiled_sweep_3d(marker, mask, offsets, mode_code, cval, erosion, dtype,
170
170
  tile_d, tile_h, tile_w, n_tiles_h, n_tiles_w, n_tiles,
171
171
  dirty_idx, scratch_marker, scratch_mask):
172
- D = src.shape[0]
173
- H = src.shape[1]
174
- W = src.shape[2]
172
+ D = marker.shape[0]
173
+ H = marker.shape[1]
174
+ W = marker.shape[2]
175
175
  n_dirty = dirty_idx.shape[0]
176
176
  changed = np.zeros(n_tiles, dtype=np.bool_)
177
177
 
@@ -203,28 +203,28 @@ def _tiled_sweep_3d(src, dst, mask, offsets, mode_code, cval, erosion, dtype,
203
203
  local_marker = scratch_marker[tid, :td, :th, :tw]
204
204
  local_mask = scratch_mask[tid, :td, :th, :tw]
205
205
 
206
- local_marker[:, :, :] = src[dd0:dd1, hh0:hh1, ww0:ww1]
206
+ local_marker[:, :, :] = marker[dd0:dd1, hh0:hh1, ww0:ww1]
207
207
  local_mask[:, :, :] = mask[dd0:dd1, hh0:hh1, ww0:ww1]
208
208
 
209
209
  _propagate_3d(local_marker, local_mask, offsets,
210
210
  mode_code, cval, erosion, dtype)
211
211
 
212
212
  new_slice = local_marker[d0 - dd0:d1 - dd0, h0 - hh0:h1 - hh0, w0 - ww0:w1 - ww0]
213
- old_slice = src[d0:d1, h0:h1, w0:w1]
213
+ old_slice = marker[d0:d1, h0:h1, w0:w1]
214
214
  changed[t] = not np.array_equal(new_slice, old_slice)
215
- dst[d0:d1, h0:h1, w0:w1] = new_slice
215
+ marker[d0:d1, h0:h1, w0:w1] = new_slice
216
216
 
217
217
  return changed
218
218
 
219
219
 
220
220
  @njit(parallel=True, fastmath=True, cache=True)
221
- def _tiled_sweep_3d_batched(src, dst, mask, offsets, mode_code, cval, erosion, dtype,
221
+ def _tiled_sweep_3d_batched(marker, mask, offsets, mode_code, cval, erosion, dtype,
222
222
  tile_d, tile_h, tile_w, n_tiles_h, n_tiles_w, n_tiles,
223
223
  dirty_b, dirty_t, scratch_marker, scratch_mask):
224
- B = src.shape[0]
225
- D = src.shape[1]
226
- H = src.shape[2]
227
- W = src.shape[3]
224
+ B = marker.shape[0]
225
+ D = marker.shape[1]
226
+ H = marker.shape[2]
227
+ W = marker.shape[3]
228
228
  n_dirty = dirty_b.shape[0]
229
229
  changed = np.zeros((B, n_tiles), dtype=np.bool_)
230
230
 
@@ -257,16 +257,16 @@ def _tiled_sweep_3d_batched(src, dst, mask, offsets, mode_code, cval, erosion, d
257
257
  local_marker = scratch_marker[tid, :td, :th, :tw]
258
258
  local_mask = scratch_mask[tid, :td, :th, :tw]
259
259
 
260
- local_marker[:, :, :] = src[b, dd0:dd1, hh0:hh1, ww0:ww1]
260
+ local_marker[:, :, :] = marker[b, dd0:dd1, hh0:hh1, ww0:ww1]
261
261
  local_mask[:, :, :] = mask[b, dd0:dd1, hh0:hh1, ww0:ww1]
262
262
 
263
263
  _propagate_3d(local_marker, local_mask, offsets,
264
264
  mode_code, cval, erosion, dtype)
265
265
 
266
266
  new_slice = local_marker[d0 - dd0:d1 - dd0, h0 - hh0:h1 - hh0, w0 - ww0:w1 - ww0]
267
- old_slice = src[b, d0:d1, h0:h1, w0:w1]
267
+ old_slice = marker[b, d0:d1, h0:h1, w0:w1]
268
268
  changed[b, t] = not np.array_equal(new_slice, old_slice)
269
- dst[b, d0:d1, h0:h1, w0:w1] = new_slice
269
+ marker[b, d0:d1, h0:h1, w0:w1] = new_slice
270
270
 
271
271
  return changed
272
272
 
@@ -292,26 +292,20 @@ def _queue_2d_tiled(marker, mask, offsets, mode_code, cval, erosion, dtype):
292
292
  scratch_marker = np.empty((n_threads, max_th, max_tw), dtype=dtype)
293
293
  scratch_mask = np.empty((n_threads, max_th, max_tw), dtype=mask.dtype)
294
294
 
295
- buf = np.empty_like(marker)
296
- src, dst = marker, buf
297
-
298
295
  dirty = np.ones((n_tiles_h, n_tiles_w), dtype=bool)
299
296
 
300
297
  while True:
301
- np.copyto(dst, src)
302
- dirty_idx = np.flatnonzero(dirty.ravel()).astype(np.int64)
298
+ dirty_idx = np.flatnonzero(dirty.ravel())
299
+ # chaotic and potential race condition but so far my testing suggest it will converge to the right solution
303
300
  changed = _tiled_sweep_2d(
304
- src, dst, mask, offsets, mode_code, cval, erosion, dtype,
301
+ marker, mask, offsets, mode_code, cval, erosion, dtype,
305
302
  tile_h, tile_w, n_tiles_w, n_tiles,
306
303
  dirty_idx, scratch_marker, scratch_mask,
307
304
  )
308
- src, dst = dst, src
309
305
  if not changed.any():
310
306
  break
311
307
  dirty = _compute_dirty_2d(changed, n_tiles_h, n_tiles_w)
312
308
 
313
- if src is not marker:
314
- np.copyto(marker, src)
315
309
 
316
310
 
317
311
  def _queue_2d_tiled_batched(marker, mask, offsets, mode_code, cval, erosion, dtype):
@@ -332,35 +326,28 @@ def _queue_2d_tiled_batched(marker, mask, offsets, mode_code, cval, erosion, dty
332
326
  scratch_marker = np.empty((n_threads, max_th, max_tw), dtype=dtype)
333
327
  scratch_mask = np.empty((n_threads, max_th, max_tw), dtype=mask.dtype)
334
328
 
335
- buf = np.empty_like(marker)
336
- src, dst = marker, buf
337
-
338
329
  dirty = np.ones((B, n_tiles_h, n_tiles_w), dtype=bool)
339
330
 
340
331
  while True:
341
- np.copyto(dst, src)
342
- flat = np.flatnonzero(dirty.ravel()).astype(np.int64)
332
+ flat = np.flatnonzero(dirty.ravel())
343
333
  dirty_b = flat // n_tiles
344
334
  dirty_t = flat % n_tiles
345
335
  changed = _tiled_sweep_2d_batched(
346
- src, dst, mask, offsets, mode_code, cval, erosion, dtype,
336
+ marker, mask, offsets, mode_code, cval, erosion, dtype,
347
337
  tile_h, tile_w, n_tiles_w, n_tiles,
348
338
  dirty_b, dirty_t, scratch_marker, scratch_mask,
349
339
  )
350
- src, dst = dst, src
351
340
  if not changed.any():
352
341
  break
353
342
  dirty = _compute_dirty_2d_batched(changed, n_tiles_h, n_tiles_w)
354
343
 
355
- if src is not marker:
356
- np.copyto(marker, src)
357
344
 
358
345
 
359
346
  def _queue_3d_tiled(marker, mask, offsets, mode_code, cval, erosion, dtype):
360
347
  D, H, W = marker.shape
361
348
 
362
349
  n_threads = max(get_num_threads(), 1)
363
- target_tiles = 2 * n_threads
350
+ target_tiles = 4 * n_threads
364
351
  tile_size = int((D * H * W / max(target_tiles, 1)) ** (1.0 / 3.0))
365
352
 
366
353
  tile_d = min(tile_size, D)
@@ -377,26 +364,19 @@ def _queue_3d_tiled(marker, mask, offsets, mode_code, cval, erosion, dtype):
377
364
  scratch_marker = np.empty((n_threads, max_td, max_th, max_tw), dtype=dtype)
378
365
  scratch_mask = np.empty((n_threads, max_td, max_th, max_tw), dtype=mask.dtype)
379
366
 
380
- buf = np.empty_like(marker)
381
- src, dst = marker, buf
382
367
 
383
368
  dirty = np.ones((n_tiles_d, n_tiles_h, n_tiles_w), dtype=bool)
384
-
385
369
  while True:
386
- np.copyto(dst, src)
387
370
  dirty_idx = np.flatnonzero(dirty.ravel())
388
371
  changed = _tiled_sweep_3d(
389
- src, dst, mask, offsets, mode_code, cval, erosion, dtype,
372
+ marker, mask, offsets, mode_code, cval, erosion, dtype,
390
373
  tile_d, tile_h, tile_w, n_tiles_h, n_tiles_w, n_tiles,
391
374
  dirty_idx, scratch_marker, scratch_mask,
392
375
  )
393
- src, dst = dst, src
394
376
  if not changed.any():
395
377
  break
396
378
  dirty = _compute_dirty_3d(changed, n_tiles_d, n_tiles_h, n_tiles_w)
397
379
 
398
- if src is not marker:
399
- np.copyto(marker, src)
400
380
 
401
381
 
402
382
  def _queue_3d_tiled_batched(marker, mask, offsets, mode_code, cval, erosion, dtype):
@@ -420,28 +400,22 @@ def _queue_3d_tiled_batched(marker, mask, offsets, mode_code, cval, erosion, dty
420
400
  scratch_marker = np.empty((n_threads, max_td, max_th, max_tw), dtype=dtype)
421
401
  scratch_mask = np.empty((n_threads, max_td, max_th, max_tw), dtype=mask.dtype)
422
402
 
423
- buf = np.empty_like(marker)
424
- src, dst = marker, buf
425
403
 
426
404
  dirty = np.ones((B, n_tiles_d, n_tiles_h, n_tiles_w), dtype=bool)
427
405
 
428
406
  while True:
429
- np.copyto(dst, src)
430
407
  flat = np.flatnonzero(dirty.ravel())
431
408
  dirty_b = flat // n_tiles
432
409
  dirty_t = flat % n_tiles
433
410
  changed = _tiled_sweep_3d_batched(
434
- src, dst, mask, offsets, mode_code, cval, erosion, dtype,
411
+ marker, mask, offsets, mode_code, cval, erosion, dtype,
435
412
  tile_d, tile_h, tile_w, n_tiles_h, n_tiles_w, n_tiles,
436
413
  dirty_b, dirty_t, scratch_marker, scratch_mask,
437
414
  )
438
- src, dst = dst, src
439
415
  if not changed.any():
440
416
  break
441
417
  dirty = _compute_dirty_3d_batched(changed, n_tiles_d, n_tiles_h, n_tiles_w)
442
418
 
443
- if src is not marker:
444
- np.copyto(marker, src)
445
419
 
446
420
 
447
421
  def _propagate(marker, mask, offsets, mode_code, cval, erosion,
@@ -90,13 +90,32 @@ def erosion(input, size=None, footprint=None, structure=None, iterations=1, mask
90
90
 
91
91
  i = 0
92
92
  changed = True
93
+ src = input
94
+ dst = output
95
+ scratch = None
93
96
  while changed:
94
- changed = _scan_filter(input, output, mask, None, offsets, edge_mode_code, cval, erosion, working_dim, batch)
97
+ changed = _scan_filter(src, dst, mask, None, offsets, edge_mode_code, cval,
98
+ erosion, working_dim, batch)
95
99
  i += 1
96
- if i >= iterations & iterations > 0:
100
+ if iterations > 0 and i >= iterations:
97
101
  break
98
- if ~changed and i < iterations:
102
+ if not changed:
99
103
  print(f'Erosion ending prematurely: image no longer changed after {i} iterations.')
100
104
  break
101
- return output.reshape(original_shape) if output.ndim > working_dim else output
105
+ # Prepare for the next iteration: previous output becomes new input.
106
+ # Cannot write back into `input` (owned by the caller), so once the
107
+ # first iteration has written into `output` we need a scratch buffer
108
+ # to alternate between.
109
+ if dst is output:
110
+ if scratch is None:
111
+ scratch = np.empty_like(output)
112
+ src, dst = output, scratch
113
+ else:
114
+ src, dst = scratch, output
102
115
 
116
+ # Make sure the final result lands in `output` regardless of which buffer
117
+ # the last iteration wrote into.
118
+ if dst is not output:
119
+ np.copyto(output, dst)
120
+
121
+ return output.reshape(original_shape) if output.ndim > working_dim else output
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: numba-morph
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Some image morphology operations optimized with Numba
5
5
  Author-email: Yuyao Huang <yuyaoh6@gmail.com>
6
6
  License-Expression: MIT
@@ -62,7 +62,7 @@ class TestReconstruction3D:
62
62
  @pytest.mark.parametrize("mode", ["erosion", "dilation"])
63
63
  @pytest.mark.parametrize("connectivity", [6, 18, 26])
64
64
  def test_reconstruction_3d(self, connectivity, mode, speed):
65
- original = np.random.randint(low=0, high=255, size=(64, 64, 64), dtype=np.uint8)
65
+ original = np.random.randint(low=0, high=255, size=(128, 128, 256), dtype=np.uint8)
66
66
  seed = np.copy(original)
67
67
  if mode == "erosion":
68
68
  seed[1:-1, 1:-1] = original.max()
File without changes
File without changes
File without changes