nettracer3d 1.2.5__py3-none-any.whl → 1.3.1__py3-none-any.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 nettracer3d might be problematic. Click here for more details.
- nettracer3d/branch_stitcher.py +251 -143
- nettracer3d/filaments.py +11 -4
- nettracer3d/modularity.py +15 -6
- nettracer3d/morphology.py +1 -1
- nettracer3d/nettracer.py +258 -187
- nettracer3d/nettracer_gui.py +2194 -2154
- nettracer3d/network_analysis.py +51 -51
- nettracer3d/network_draw.py +16 -15
- nettracer3d/network_graph_widget.py +2066 -0
- nettracer3d/node_draw.py +4 -4
- nettracer3d/painting.py +158 -298
- nettracer3d/proximity.py +36 -150
- nettracer3d/simple_network.py +28 -9
- nettracer3d/smart_dilate.py +212 -107
- nettracer3d/tutorial.py +68 -66
- {nettracer3d-1.2.5.dist-info → nettracer3d-1.3.1.dist-info}/METADATA +62 -16
- nettracer3d-1.3.1.dist-info/RECORD +30 -0
- nettracer3d-1.2.5.dist-info/RECORD +0 -29
- {nettracer3d-1.2.5.dist-info → nettracer3d-1.3.1.dist-info}/WHEEL +0 -0
- {nettracer3d-1.2.5.dist-info → nettracer3d-1.3.1.dist-info}/entry_points.txt +0 -0
- {nettracer3d-1.2.5.dist-info → nettracer3d-1.3.1.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-1.2.5.dist-info → nettracer3d-1.3.1.dist-info}/top_level.txt +0 -0
nettracer3d/smart_dilate.py
CHANGED
|
@@ -3,12 +3,19 @@ import numpy as np
|
|
|
3
3
|
from scipy.ndimage import binary_dilation, distance_transform_edt
|
|
4
4
|
from scipy.ndimage import gaussian_filter
|
|
5
5
|
from scipy import ndimage
|
|
6
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
6
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed, ProcessPoolExecutor
|
|
7
|
+
from skimage.segmentation import watershed
|
|
7
8
|
import cv2
|
|
8
9
|
import os
|
|
10
|
+
try:
|
|
11
|
+
import edt
|
|
12
|
+
print("Parallel search functions enabled")
|
|
13
|
+
except:
|
|
14
|
+
print("Some parallel search functions disabled (requires edt package), will fall back to single-threaded")
|
|
9
15
|
import math
|
|
10
16
|
import re
|
|
11
17
|
from . import nettracer
|
|
18
|
+
from multiprocessing import shared_memory
|
|
12
19
|
import multiprocessing as mp
|
|
13
20
|
try:
|
|
14
21
|
import cupy as cp
|
|
@@ -177,6 +184,7 @@ def dilate_3D_old(tiff_array, dilated_x=3, dilated_y=3, dilated_z=3):
|
|
|
177
184
|
|
|
178
185
|
return dilated_array.astype(np.uint8)
|
|
179
186
|
|
|
187
|
+
|
|
180
188
|
def dilate_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0, GPU = False):
|
|
181
189
|
"""
|
|
182
190
|
Dilate a 3D array using distance transform method. Dt dilation produces perfect results but only works in euclidean geometry and lags in big arrays.
|
|
@@ -286,74 +294,34 @@ def process_chunk(start_idx, end_idx, nodes, ring_mask, nearest_label_indices):
|
|
|
286
294
|
|
|
287
295
|
return dilated_nodes_with_labels_chunk
|
|
288
296
|
|
|
289
|
-
def smart_dilate(nodes, dilate_xy, dilate_z, directory = None, GPU = True, fast_dil = True, predownsample = None, use_dt_dil_amount = None, xy_scale = 1, z_scale = 1):
|
|
297
|
+
def smart_dilate(nodes, dilate_xy = 0, dilate_z = 0, directory = None, GPU = True, fast_dil = True, predownsample = None, use_dt_dil_amount = None, xy_scale = 1, z_scale = 1):
|
|
290
298
|
|
|
291
|
-
original_shape = nodes.shape
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
#Dilate the binarized array
|
|
295
299
|
if fast_dil:
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
300
|
+
try:
|
|
301
|
+
import edt
|
|
302
|
+
dilated = nettracer.dilate_3D_dt(nodes, use_dt_dil_amount, xy_scale, z_scale, fast_dil = True)
|
|
303
|
+
return smart_label_watershed(dilated, nodes, directory = None, remove_template = False)
|
|
304
|
+
except:
|
|
305
|
+
print("edt package not found. Please use 'pip install edt' if you would like to enable parallel searching.")
|
|
306
|
+
return smart_dilate_short(nodes, use_dt_dil_amount, directory, xy_scale, z_scale)
|
|
299
307
|
else:
|
|
300
|
-
|
|
301
|
-
binary_nodes = binarize(nodes)
|
|
308
|
+
return smart_dilate_short(nodes, use_dt_dil_amount, directory, xy_scale, z_scale)
|
|
302
309
|
|
|
303
|
-
# Step 3: Isolate the ring (binary dilated mask minus original binary mask)
|
|
304
|
-
ring_mask = dilated_binary_nodes & invert_array(binary_nodes)
|
|
305
310
|
|
|
306
|
-
del binary_nodes
|
|
307
311
|
|
|
308
|
-
print("Preforming distance transform for smart search... this step may take some time if computed on CPU...")
|
|
309
312
|
|
|
310
|
-
if fast_dil:
|
|
311
313
|
|
|
312
|
-
|
|
314
|
+
def smart_dilate_short(nodes, amount = None, directory = None, xy_scale = 1, z_scale = 1):
|
|
313
315
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
try:
|
|
318
|
-
|
|
319
|
-
if predownsample is None:
|
|
320
|
-
|
|
321
|
-
# Step 4: Find the nearest label for each voxel in the ring
|
|
322
|
-
nearest_label_indices = compute_distance_transform_GPU(invert_array(nodes))
|
|
323
|
-
|
|
324
|
-
else:
|
|
325
|
-
gotoexcept = 1/0
|
|
326
|
-
|
|
327
|
-
except (cp.cuda.memory.OutOfMemoryError, ZeroDivisionError) as e:
|
|
328
|
-
if predownsample is None:
|
|
329
|
-
down_factor = catch_memory(e) #Obtain downsample amount based on memory missing
|
|
330
|
-
else:
|
|
331
|
-
down_factor = (predownsample)**3
|
|
332
|
-
|
|
333
|
-
while True:
|
|
334
|
-
downsample_needed = down_factor**(1./3.)
|
|
335
|
-
small_nodes = nettracer.downsample(nodes, downsample_needed) #Apply downsample
|
|
336
|
-
try:
|
|
337
|
-
nearest_label_indices = compute_distance_transform_GPU(invert_array(small_nodes)) #Retry dt on downsample
|
|
338
|
-
print(f"Using {down_factor} downsample ({downsample_needed} in each dim - Largest possible with this GPU unless user specified downsample)")
|
|
339
|
-
break
|
|
340
|
-
except cp.cuda.memory.OutOfMemoryError:
|
|
341
|
-
down_factor += 1
|
|
342
|
-
binary_nodes = binarize(small_nodes) #Recompute variables for downsample
|
|
343
|
-
dilated_mask = dilated_binary_nodes #Need this for later to stamp out the correct output
|
|
344
|
-
dilated_binary_nodes = dilate_3D(binary_nodes, 2 + round_to_odd(dilate_xy/downsample_needed), 2 + round_to_odd(dilate_xy/downsample_needed), 2 + round_to_odd(dilate_z/downsample_needed)) #Mod dilation to recompute variables for downsample while also over dilatiing
|
|
345
|
-
|
|
346
|
-
ring_mask = dilated_binary_nodes & invert_array(binary_nodes)
|
|
347
|
-
nodes = small_nodes
|
|
348
|
-
del small_nodes
|
|
349
|
-
else:
|
|
350
|
-
goto_except = 1/0
|
|
351
|
-
except Exception as e:
|
|
352
|
-
print("GPU dt failed or did not detect GPU (cupy must be installed with a CUDA toolkit setup...). Computing CPU distance transform instead.")
|
|
353
|
-
if GPU:
|
|
354
|
-
print(f"Error message: {str(e)}")
|
|
355
|
-
nearest_label_indices = compute_distance_transform(invert_array(nodes))
|
|
316
|
+
original_shape = nodes.shape
|
|
317
|
+
|
|
318
|
+
print("Performing distance transform for smart search...")
|
|
356
319
|
|
|
320
|
+
dilated_binary_nodes, nearest_label_indices, nodes = dilate_3D_dt(nodes, amount, xy_scaling = xy_scale, z_scaling = z_scale)
|
|
321
|
+
binary_nodes = binarize(nodes)
|
|
322
|
+
ring_mask = dilated_binary_nodes & (~binary_nodes)
|
|
323
|
+
del dilated_binary_nodes
|
|
324
|
+
del binary_nodes
|
|
357
325
|
|
|
358
326
|
# Step 5: Process in parallel chunks using ThreadPoolExecutor
|
|
359
327
|
num_cores = mp.cpu_count() # Use all available CPU cores
|
|
@@ -370,11 +338,6 @@ def smart_dilate(nodes, dilate_xy, dilate_z, directory = None, GPU = True, fast_
|
|
|
370
338
|
# Combine results from chunks
|
|
371
339
|
dilated_nodes_with_labels = np.concatenate(results, axis=1)
|
|
372
340
|
|
|
373
|
-
|
|
374
|
-
if (dilated_nodes_with_labels.shape[1] < original_shape[1]) and fast_dil: #If downsample was used, upsample output
|
|
375
|
-
dilated_nodes_with_labels = nettracer.upsample_with_padding(dilated_nodes_with_labels, downsample_needed, original_shape)
|
|
376
|
-
dilated_nodes_with_labels = dilated_nodes_with_labels * dilated_mask
|
|
377
|
-
|
|
378
341
|
if directory is not None:
|
|
379
342
|
try:
|
|
380
343
|
tifffile.imwrite(f"{directory}/search_region.tif", dilated_nodes_with_labels)
|
|
@@ -393,7 +356,63 @@ def round_to_odd(number):
|
|
|
393
356
|
rounded -= 1
|
|
394
357
|
return rounded
|
|
395
358
|
|
|
396
|
-
def
|
|
359
|
+
def smart_label_watershed(binary_array, label_array, directory = None, remove_template = False):
|
|
360
|
+
"""
|
|
361
|
+
Watershed-based version - much lower memory footprint
|
|
362
|
+
"""
|
|
363
|
+
original_shape = binary_array.shape
|
|
364
|
+
|
|
365
|
+
if type(binary_array) == str or type(label_array) == str:
|
|
366
|
+
string_bool = True
|
|
367
|
+
else:
|
|
368
|
+
string_bool = None
|
|
369
|
+
if type(binary_array) == str:
|
|
370
|
+
binary_array = tifffile.imread(binary_array)
|
|
371
|
+
if type(label_array) == str:
|
|
372
|
+
label_array = tifffile.imread(label_array)
|
|
373
|
+
|
|
374
|
+
# Binarize
|
|
375
|
+
binary_array = binarize(binary_array)
|
|
376
|
+
|
|
377
|
+
print("Performing watershed label propagation...")
|
|
378
|
+
|
|
379
|
+
# Watershed approach: propagate existing labels into the dilated region
|
|
380
|
+
# The labels themselves are the "markers" (seeds)
|
|
381
|
+
# We use the binary mask to define where labels can spread
|
|
382
|
+
|
|
383
|
+
# Simple elevation map: distance from edges (lower = closer to labeled regions)
|
|
384
|
+
# This makes watershed flow from labeled regions outward
|
|
385
|
+
elevation = binary_array.astype(np.float32)
|
|
386
|
+
|
|
387
|
+
# Apply watershed - labels propagate into binary_array region
|
|
388
|
+
dilated_nodes_with_labels = watershed(
|
|
389
|
+
elevation, # Elevation map (flat works fine)
|
|
390
|
+
markers=label_array, # Seed labels
|
|
391
|
+
mask=binary_array, # Where to propagate
|
|
392
|
+
compactness=0 # Pure distance-based (not shape-based)
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
if remove_template:
|
|
396
|
+
dilated_nodes_with_labels *= binary_array
|
|
397
|
+
|
|
398
|
+
if string_bool:
|
|
399
|
+
if directory is not None:
|
|
400
|
+
try:
|
|
401
|
+
tifffile.imwrite(f"{directory}/smart_labelled_array.tif", dilated_nodes_with_labels)
|
|
402
|
+
except Exception as e:
|
|
403
|
+
print(f"Could not save search region file to {directory}")
|
|
404
|
+
else:
|
|
405
|
+
try:
|
|
406
|
+
tifffile.imwrite("smart_labelled_array.tif", dilated_nodes_with_labels)
|
|
407
|
+
except Exception as e:
|
|
408
|
+
print(f"Could not save search region file to active directory")
|
|
409
|
+
|
|
410
|
+
return dilated_nodes_with_labels
|
|
411
|
+
|
|
412
|
+
def smart_label(binary_array, label_array, directory = None, GPU = True, predownsample = None, remove_template = False, mode = 0):
|
|
413
|
+
|
|
414
|
+
if mode == 1:
|
|
415
|
+
return smart_label_watershed(binary_array, label_array, directory, remove_template)
|
|
397
416
|
|
|
398
417
|
original_shape = binary_array.shape
|
|
399
418
|
|
|
@@ -406,79 +425,93 @@ def smart_label(binary_array, label_array, directory = None, GPU = True, predown
|
|
|
406
425
|
if type(label_array) == str:
|
|
407
426
|
label_array = tifffile.imread(label_array)
|
|
408
427
|
|
|
409
|
-
#
|
|
410
|
-
binary_core = binarize(label_array)
|
|
428
|
+
# Binarize inputs
|
|
411
429
|
binary_array = binarize(binary_array)
|
|
430
|
+
|
|
431
|
+
print("Performing distance transform for smart label...")
|
|
412
432
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
433
|
+
downsample_needed = None # Track if we downsampled
|
|
434
|
+
|
|
417
435
|
try:
|
|
418
|
-
|
|
419
436
|
if GPU == True and cp.cuda.runtime.getDeviceCount() > 0:
|
|
420
437
|
print("GPU detected. Using CuPy for distance transform.")
|
|
421
438
|
|
|
422
439
|
try:
|
|
423
|
-
|
|
424
440
|
if predownsample is None:
|
|
425
|
-
|
|
426
|
-
|
|
441
|
+
# Compute binary_core only when needed
|
|
442
|
+
binary_core = binarize(label_array)
|
|
427
443
|
nearest_label_indices = compute_distance_transform_GPU(invert_array(binary_core))
|
|
428
|
-
|
|
444
|
+
del binary_core # Free immediately after use
|
|
429
445
|
else:
|
|
430
|
-
|
|
446
|
+
raise ZeroDivisionError # Force downsample path
|
|
431
447
|
|
|
432
448
|
except (cp.cuda.memory.OutOfMemoryError, ZeroDivisionError) as e:
|
|
433
449
|
if predownsample is None:
|
|
434
|
-
down_factor = catch_memory(e)
|
|
450
|
+
down_factor = catch_memory(e)
|
|
435
451
|
else:
|
|
436
452
|
down_factor = (predownsample)**3
|
|
437
453
|
|
|
438
454
|
while True:
|
|
439
455
|
downsample_needed = down_factor**(1./3.)
|
|
440
|
-
small_array = nettracer.downsample(label_array, downsample_needed)
|
|
456
|
+
small_array = nettracer.downsample(label_array, downsample_needed)
|
|
441
457
|
try:
|
|
442
|
-
|
|
443
|
-
|
|
458
|
+
binary_core = binarize(small_array)
|
|
459
|
+
nearest_label_indices = compute_distance_transform_GPU(invert_array(binary_core))
|
|
460
|
+
print(f"Using {down_factor} downsample ({downsample_needed} in each dim)")
|
|
461
|
+
del small_array # Don't need small_array anymore
|
|
444
462
|
break
|
|
445
463
|
except cp.cuda.memory.OutOfMemoryError:
|
|
464
|
+
del small_array, binary_core # Clean up before retry
|
|
446
465
|
down_factor += 1
|
|
447
|
-
|
|
448
|
-
label_array
|
|
466
|
+
|
|
467
|
+
# Update label_array for later use
|
|
468
|
+
label_array = nettracer.downsample(label_array, downsample_needed)
|
|
449
469
|
binary_small = nettracer.downsample(binary_array, downsample_needed)
|
|
450
470
|
binary_small = nettracer.dilate_3D_old(binary_small)
|
|
451
471
|
ring_mask = binary_small & invert_array(binary_core)
|
|
452
|
-
|
|
472
|
+
del binary_small, binary_core # Free after creating ring_mask
|
|
453
473
|
else:
|
|
454
|
-
|
|
474
|
+
raise Exception("GPU not available")
|
|
475
|
+
|
|
455
476
|
except Exception as e:
|
|
456
477
|
if GPU:
|
|
457
|
-
print("GPU dt failed or did not detect GPU
|
|
478
|
+
print("GPU dt failed or did not detect GPU. Computing CPU distance transform instead.")
|
|
458
479
|
print(f"Error message: {str(e)}")
|
|
459
480
|
import traceback
|
|
460
481
|
print(traceback.format_exc())
|
|
461
|
-
|
|
482
|
+
binary_core = binarize(label_array)
|
|
483
|
+
nearest_label_indices = compute_distance_transform(invert_array(binary_core))
|
|
484
|
+
del binary_core
|
|
462
485
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
486
|
+
# Compute ring_mask only if not already computed in downsample path
|
|
487
|
+
if 'ring_mask' not in locals():
|
|
488
|
+
binary_core = binarize(label_array)
|
|
489
|
+
ring_mask = binary_array & invert_array(binary_core)
|
|
490
|
+
del binary_core
|
|
468
491
|
|
|
492
|
+
# Step 5: Process in parallel chunks
|
|
493
|
+
num_cores = mp.cpu_count()
|
|
494
|
+
chunk_size = label_array.shape[1] // num_cores
|
|
469
495
|
|
|
470
496
|
with ThreadPoolExecutor(max_workers=num_cores) as executor:
|
|
471
|
-
args_list = [(i * chunk_size, (i + 1) * chunk_size if i != num_cores - 1 else label_array.shape[1],
|
|
497
|
+
args_list = [(i * chunk_size, (i + 1) * chunk_size if i != num_cores - 1 else label_array.shape[1],
|
|
498
|
+
label_array, ring_mask, nearest_label_indices) for i in range(num_cores)]
|
|
472
499
|
results = list(executor.map(lambda args: process_chunk(*args), args_list))
|
|
473
500
|
|
|
474
|
-
#
|
|
501
|
+
# Free large arrays no longer needed
|
|
502
|
+
del label_array, ring_mask, nearest_label_indices
|
|
503
|
+
|
|
504
|
+
# Combine results
|
|
475
505
|
dilated_nodes_with_labels = np.concatenate(results, axis=1)
|
|
506
|
+
del results # Free the list of chunks
|
|
476
507
|
|
|
477
|
-
if
|
|
508
|
+
if downsample_needed is not None: # If downsample was used
|
|
478
509
|
dilated_nodes_with_labels = nettracer.upsample_with_padding(dilated_nodes_with_labels, downsample_needed, original_shape)
|
|
479
|
-
dilated_nodes_with_labels
|
|
510
|
+
dilated_nodes_with_labels *= binary_array # In-place multiply if possible
|
|
480
511
|
elif remove_template:
|
|
481
|
-
dilated_nodes_with_labels
|
|
512
|
+
dilated_nodes_with_labels *= binary_array # In-place multiply if possible
|
|
513
|
+
|
|
514
|
+
del binary_array # Done with this
|
|
482
515
|
|
|
483
516
|
if string_bool:
|
|
484
517
|
if directory is not None:
|
|
@@ -492,7 +525,6 @@ def smart_label(binary_array, label_array, directory = None, GPU = True, predown
|
|
|
492
525
|
except Exception as e:
|
|
493
526
|
print(f"Could not save search region file to active directory")
|
|
494
527
|
|
|
495
|
-
|
|
496
528
|
return dilated_nodes_with_labels
|
|
497
529
|
|
|
498
530
|
def smart_label_single(binary_array, label_array):
|
|
@@ -613,24 +645,97 @@ def compute_distance_transform_distance_GPU(nodes, sampling = [1, 1, 1]):
|
|
|
613
645
|
return distance
|
|
614
646
|
|
|
615
647
|
|
|
616
|
-
def
|
|
617
|
-
|
|
618
|
-
#
|
|
648
|
+
def _run_edt_in_process_shm(input_shm_name, output_shm_name, shape, dtype_str, sampling_tuple):
|
|
649
|
+
"""Helper function to run edt in a separate process using shared memory."""
|
|
650
|
+
import edt # Import here to ensure it's available in child process
|
|
651
|
+
|
|
652
|
+
input_shm = shared_memory.SharedMemory(name=input_shm_name)
|
|
653
|
+
output_shm = shared_memory.SharedMemory(name=output_shm_name)
|
|
654
|
+
|
|
655
|
+
try:
|
|
656
|
+
nodes_arr = np.ndarray(shape, dtype=dtype_str, buffer=input_shm.buf)
|
|
657
|
+
|
|
658
|
+
n_cores = mp.cpu_count()
|
|
659
|
+
result = edt.edt(
|
|
660
|
+
nodes_arr.astype(bool),
|
|
661
|
+
anisotropy=sampling_tuple,
|
|
662
|
+
parallel=n_cores
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
result_array = np.ndarray(result.shape, dtype=result.dtype, buffer=output_shm.buf)
|
|
666
|
+
np.copyto(result_array, result)
|
|
667
|
+
|
|
668
|
+
return result.shape, str(result.dtype)
|
|
669
|
+
finally:
|
|
670
|
+
input_shm.close()
|
|
671
|
+
output_shm.close()
|
|
619
672
|
|
|
673
|
+
def compute_distance_transform_distance(nodes, sampling=[1, 1, 1], fast_dil=False):
|
|
674
|
+
"""
|
|
675
|
+
Compute distance transform with automatic parallelization when available.
|
|
676
|
+
|
|
677
|
+
Args:
|
|
678
|
+
nodes: Binary array (True/1 for objects)
|
|
679
|
+
sampling: Voxel spacing [z, y, x] for anisotropic data
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
Distance transform array
|
|
683
|
+
"""
|
|
620
684
|
is_pseudo_3d = nodes.shape[0] == 1
|
|
685
|
+
|
|
621
686
|
if is_pseudo_3d:
|
|
622
|
-
nodes = np.squeeze(nodes)
|
|
687
|
+
nodes = np.squeeze(nodes)
|
|
623
688
|
sampling = [sampling[1], sampling[2]]
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
689
|
+
|
|
690
|
+
if fast_dil:
|
|
691
|
+
try:
|
|
692
|
+
# Use shared memory for all array sizes
|
|
693
|
+
input_shm = shared_memory.SharedMemory(create=True, size=nodes.nbytes)
|
|
694
|
+
output_size = nodes.size * np.dtype(np.float64).itemsize
|
|
695
|
+
output_shm = shared_memory.SharedMemory(create=True, size=output_size)
|
|
696
|
+
|
|
697
|
+
try:
|
|
698
|
+
shm_array = np.ndarray(nodes.shape, dtype=nodes.dtype, buffer=input_shm.buf)
|
|
699
|
+
np.copyto(shm_array, nodes)
|
|
700
|
+
|
|
701
|
+
with ProcessPoolExecutor(max_workers=1) as executor:
|
|
702
|
+
future = executor.submit(
|
|
703
|
+
_run_edt_in_process_shm,
|
|
704
|
+
input_shm.name,
|
|
705
|
+
output_shm.name,
|
|
706
|
+
nodes.shape,
|
|
707
|
+
str(nodes.dtype),
|
|
708
|
+
tuple(sampling)
|
|
709
|
+
)
|
|
710
|
+
result_shape, result_dtype = future.result()
|
|
711
|
+
|
|
712
|
+
distance = np.ndarray(result_shape, dtype=result_dtype, buffer=output_shm.buf).copy()
|
|
713
|
+
|
|
714
|
+
finally:
|
|
715
|
+
input_shm.close()
|
|
716
|
+
input_shm.unlink()
|
|
717
|
+
output_shm.close()
|
|
718
|
+
output_shm.unlink()
|
|
719
|
+
|
|
720
|
+
except Exception as e:
|
|
721
|
+
print(f"Parallel distance transform failed ({e}), falling back to scipy")
|
|
722
|
+
try:
|
|
723
|
+
import edt
|
|
724
|
+
import traceback
|
|
725
|
+
traceback.print_exc() # See the full error
|
|
726
|
+
except:
|
|
727
|
+
print("edt package not found. Please use 'pip install edt' if you would like to enable parallel searching.")
|
|
728
|
+
distance = distance_transform_edt(nodes, sampling=sampling)
|
|
729
|
+
else:
|
|
730
|
+
distance = distance_transform_edt(nodes, sampling=sampling)
|
|
731
|
+
|
|
627
732
|
if is_pseudo_3d:
|
|
628
|
-
distance = np.expand_dims(distance, axis
|
|
733
|
+
distance = np.expand_dims(distance, axis=0)
|
|
734
|
+
|
|
629
735
|
return distance
|
|
630
736
|
|
|
631
737
|
|
|
632
738
|
|
|
633
|
-
|
|
634
739
|
def gaussian(search_region, GPU = True):
|
|
635
740
|
try:
|
|
636
741
|
if GPU == True and cp.cuda.runtime.getDeviceCount() > 0:
|