nettracer3d 0.6.6__tar.gz → 0.6.8__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.
- {nettracer3d-0.6.6/src/nettracer3d.egg-info → nettracer3d-0.6.8}/PKG-INFO +4 -4
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/README.md +3 -3
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/pyproject.toml +1 -1
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/community_extractor.py +0 -269
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/morphology.py +61 -36
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/nettracer.py +261 -238
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/nettracer_gui.py +523 -194
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/proximity.py +8 -7
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/segmenter.py +12 -18
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/smart_dilate.py +156 -82
- {nettracer3d-0.6.6 → nettracer3d-0.6.8/src/nettracer3d.egg-info}/PKG-INFO +4 -4
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/LICENSE +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/setup.cfg +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/__init__.py +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/modularity.py +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/network_analysis.py +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/network_draw.py +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/node_draw.py +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/run.py +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d/simple_network.py +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d.egg-info/SOURCES.txt +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d.egg-info/dependency_links.txt +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d.egg-info/entry_points.txt +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d.egg-info/requires.txt +0 -0
- {nettracer3d-0.6.6 → nettracer3d-0.6.8}/src/nettracer3d.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nettracer3d
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8
|
|
4
4
|
Summary: Scripts for intializing and analyzing networks from segmentations of three dimensional images.
|
|
5
5
|
Author-email: Liam McLaughlin <mclaughlinliam99@gmail.com>
|
|
6
6
|
Project-URL: User_Tutorial, https://www.youtube.com/watch?v=cRatn5VTWDY
|
|
@@ -46,8 +46,8 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
|
|
|
46
46
|
|
|
47
47
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
48
48
|
|
|
49
|
-
-- Version 0.6.
|
|
49
|
+
-- Version 0.6.8 updates --
|
|
50
50
|
|
|
51
|
-
1.
|
|
51
|
+
1. Added new fill-can and 3D-brush functionalities to the brush mode (press f in brush mode to toggle the fill can. Press d while in brush mode to use the 3D painting tools. Standard Mouse wheel scrolling in the 3D painter will change how many frames you paint on - ie. 5 lets you paint 2 above and 2 below).
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
1.5. Added single-use ctrl-z functionality to the fill can only because of how easily it can mess up. (Leaving the fill can mode will garbage collect the backup image though).
|
|
@@ -8,8 +8,8 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
|
|
|
8
8
|
|
|
9
9
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
10
10
|
|
|
11
|
-
-- Version 0.6.
|
|
11
|
+
-- Version 0.6.8 updates --
|
|
12
12
|
|
|
13
|
-
1.
|
|
13
|
+
1. Added new fill-can and 3D-brush functionalities to the brush mode (press f in brush mode to toggle the fill can. Press d while in brush mode to use the 3D painting tools. Standard Mouse wheel scrolling in the 3D painter will change how many frames you paint on - ie. 5 lets you paint 2 above and 2 below).
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
1.5. Added single-use ctrl-z functionality to the fill can only because of how easily it can mess up. (Leaving the fill can mode will garbage collect the backup image though).
|
|
@@ -213,178 +213,6 @@ def open_network(excel_file_path):
|
|
|
213
213
|
return G
|
|
214
214
|
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
def isolate_full_edges(nodes, edges, directory = None):
|
|
218
|
-
"""requires smart_dilate output to function properly"""
|
|
219
|
-
|
|
220
|
-
print("Isolating full edges")
|
|
221
|
-
|
|
222
|
-
if type(nodes) == str:
|
|
223
|
-
nodes = tifffile.imread(nodes)
|
|
224
|
-
|
|
225
|
-
if type(edges) == str:
|
|
226
|
-
edges = tifffile.imread(edges)
|
|
227
|
-
|
|
228
|
-
node_bools = binarize(nodes)
|
|
229
|
-
|
|
230
|
-
del nodes
|
|
231
|
-
|
|
232
|
-
real_edges = node_bools * edges
|
|
233
|
-
|
|
234
|
-
del node_bools
|
|
235
|
-
|
|
236
|
-
# Flatten the 3D array to a 1D array
|
|
237
|
-
real_edges = real_edges.flatten()
|
|
238
|
-
|
|
239
|
-
# Find unique values
|
|
240
|
-
real_edges = np.unique(real_edges)
|
|
241
|
-
|
|
242
|
-
edge_masks = labels_to_boolean(edges, real_edges)
|
|
243
|
-
|
|
244
|
-
del real_edges
|
|
245
|
-
|
|
246
|
-
edge_labels_1 = edge_masks * edges
|
|
247
|
-
|
|
248
|
-
del edge_masks
|
|
249
|
-
|
|
250
|
-
edge_labels_1 = binarize(edge_labels_1)
|
|
251
|
-
|
|
252
|
-
edge_labels_1 = edge_labels_1 * 255
|
|
253
|
-
|
|
254
|
-
if directory is None:
|
|
255
|
-
|
|
256
|
-
tifffile.imsave(f"full_edges_for_component.tif", edge_labels_1)
|
|
257
|
-
print("Full edge labels saved to full_edges_for_component.tif")
|
|
258
|
-
else:
|
|
259
|
-
tifffile.imsave(f"{directory}/full_edges_for_component.tif", edge_labels_1)
|
|
260
|
-
print(f"Full edge labels saved to {directory}/full_edges_for_component.tif")
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def isolate_edges(edges, network, iso_network, netlists = None, directory = None):
|
|
264
|
-
|
|
265
|
-
print("Isolating edges")
|
|
266
|
-
|
|
267
|
-
if netlists is None:
|
|
268
|
-
|
|
269
|
-
master_list = read_excel_to_lists(network)
|
|
270
|
-
else:
|
|
271
|
-
master_list = netlists
|
|
272
|
-
|
|
273
|
-
if directory is None:
|
|
274
|
-
comp_list = read_excel_to_lists(iso_network)
|
|
275
|
-
else:
|
|
276
|
-
comp_list = read_excel_to_lists(f"{directory}/{iso_network}")
|
|
277
|
-
|
|
278
|
-
edge_list = []
|
|
279
|
-
|
|
280
|
-
node_1 = master_list[0]
|
|
281
|
-
|
|
282
|
-
node_2 = master_list[1]
|
|
283
|
-
|
|
284
|
-
edges_list = master_list[2]
|
|
285
|
-
|
|
286
|
-
nodes_1 = comp_list[0]
|
|
287
|
-
|
|
288
|
-
nodes_2 = comp_list[1]
|
|
289
|
-
|
|
290
|
-
compare_list = []
|
|
291
|
-
|
|
292
|
-
iso_list = []
|
|
293
|
-
|
|
294
|
-
output_list = []
|
|
295
|
-
|
|
296
|
-
for i in range(len(nodes_1)):
|
|
297
|
-
item = [nodes_1[i], nodes_2[i]]
|
|
298
|
-
iso_list.append(item)
|
|
299
|
-
|
|
300
|
-
for i in range(len(node_1)):
|
|
301
|
-
|
|
302
|
-
item = [node_1[i], node_2[i]]
|
|
303
|
-
|
|
304
|
-
compare_list.append(item)
|
|
305
|
-
|
|
306
|
-
for i in range(len(iso_list)):
|
|
307
|
-
|
|
308
|
-
for k, item in enumerate(compare_list):
|
|
309
|
-
if item == iso_list[i] or [item[1], item[0]] == iso_list[i]:
|
|
310
|
-
add_item = [nodes_1[i], nodes_2[i], edges_list[k]]
|
|
311
|
-
if add_item in output_list:
|
|
312
|
-
break
|
|
313
|
-
else:
|
|
314
|
-
output_list.append(add_item)
|
|
315
|
-
|
|
316
|
-
edge_list.append(edges_list[k])
|
|
317
|
-
|
|
318
|
-
# Convert to a DataFrame
|
|
319
|
-
edges_df = pd.DataFrame(output_list, columns=["Node A", "Node B", "Edge C"])
|
|
320
|
-
|
|
321
|
-
if directory is None:
|
|
322
|
-
# Save to an Excel file
|
|
323
|
-
edges_df.to_excel(f"{iso_network}", index=False)
|
|
324
|
-
print(f"Isolated network file saved to {iso_network}")
|
|
325
|
-
else:
|
|
326
|
-
edges_df.to_excel(f"{directory}/{iso_network}", index=False)
|
|
327
|
-
print(f"Isolated network file saved to {directory}/{iso_network}")
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
mask2 = labels_to_boolean(edges, edge_list)
|
|
331
|
-
|
|
332
|
-
mask2 = mask2 * edges
|
|
333
|
-
|
|
334
|
-
# Convert boolean values to 0 and 255
|
|
335
|
-
#mask2 = mask2.astype(np.uint8) * 255
|
|
336
|
-
|
|
337
|
-
if directory is None:
|
|
338
|
-
|
|
339
|
-
tifffile.imwrite(f"edges_for_{iso_network}.tif", mask2)
|
|
340
|
-
print(f"Computational edge mask saved to edges_for_{iso_network}.tif")
|
|
341
|
-
else:
|
|
342
|
-
tifffile.imwrite(f"{directory}/edges_for_{iso_network}.tif", mask2)
|
|
343
|
-
print(f"Computational edge mask saved to {directory}/edges_for_{iso_network}.tif")
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return output_list, mask2
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
def isolate_connected_component(nodes, excel, key=None, edge_file = None, netlists = None, search_region = None, directory = None):
|
|
350
|
-
|
|
351
|
-
structure_3d = np.ones((3, 3, 3), dtype=int)
|
|
352
|
-
|
|
353
|
-
if edge_file is not None and type(edge_file) == str:
|
|
354
|
-
edge_file = tifffile.imread(edge_file)
|
|
355
|
-
|
|
356
|
-
if type(nodes) == str:
|
|
357
|
-
nodes = tifffile.imread(nodes)
|
|
358
|
-
if len(np.unique(nodes)) == 2:
|
|
359
|
-
nodes, num_nodes = ndimage.label(nodes, structure=structure_3d)
|
|
360
|
-
|
|
361
|
-
if type(excel) == str:
|
|
362
|
-
G = open_network(excel)
|
|
363
|
-
else:
|
|
364
|
-
G = excel
|
|
365
|
-
|
|
366
|
-
if key is None:
|
|
367
|
-
print("Isolating nodes of largest connected component")
|
|
368
|
-
edges_df, mask, searchers = isolate_largest_connected(nodes, G, search_region = search_region, directory = directory)
|
|
369
|
-
if edge_file is not None:
|
|
370
|
-
output_list, mask2 = isolate_edges(edge_file, excel, 'largest_connected_component.xlsx', netlists = netlists, directory = directory)
|
|
371
|
-
#isolate_full_edges(nodes, edge_file, 'largest_connected_component')
|
|
372
|
-
return output_list, mask, mask2, searchers
|
|
373
|
-
else:
|
|
374
|
-
|
|
375
|
-
return edges_df, mask, searchers
|
|
376
|
-
|
|
377
|
-
else:
|
|
378
|
-
print("Isolating nodes of connected component containing specified key")
|
|
379
|
-
edges_df, mask, searchers = isolate_key_connected(nodes, G, key, search_region = search_region, directory = directory)
|
|
380
|
-
if edge_file is not None:
|
|
381
|
-
output_list, mask2 = isolate_edges(edge_file, excel, f'connected_component_containing_{key}_node.xlsx', netlists = netlists, directory = directory)
|
|
382
|
-
#isolate_full_edges(nodes, edge_file, 'connected_component_containing_specific_node')
|
|
383
|
-
return output_list, mask, mask2, searchers
|
|
384
|
-
else:
|
|
385
|
-
return edges_df, mask, searchers
|
|
386
|
-
|
|
387
|
-
|
|
388
216
|
def _isolate_connected(G, key = None):
|
|
389
217
|
|
|
390
218
|
if key is None:
|
|
@@ -401,103 +229,6 @@ def _isolate_connected(G, key = None):
|
|
|
401
229
|
return G0
|
|
402
230
|
|
|
403
231
|
|
|
404
|
-
|
|
405
|
-
def isolate_largest_connected(masks, G, directory = None, search_region = None):
|
|
406
|
-
# Read the Excel file into a pandas DataFrame
|
|
407
|
-
|
|
408
|
-
# Get a list of connected components
|
|
409
|
-
connected_components = list(nx.connected_components(G))
|
|
410
|
-
Gcc = sorted(nx.connected_components(G), key=len, reverse=True)
|
|
411
|
-
G0 = G.subgraph(Gcc[0])
|
|
412
|
-
|
|
413
|
-
# Extract the edges of the largest connected component
|
|
414
|
-
edges_largest_component = list(G0.edges)
|
|
415
|
-
|
|
416
|
-
# Convert to a DataFrame
|
|
417
|
-
edges_df = pd.DataFrame(edges_largest_component, columns=["Node A", "Node B"])
|
|
418
|
-
|
|
419
|
-
if directory is None:
|
|
420
|
-
# Save to an Excel file
|
|
421
|
-
edges_df.to_excel("largest_connected_component.xlsx", index=False)
|
|
422
|
-
print("Largest component nodes saved to largest_connected_component.xlsx")
|
|
423
|
-
else:
|
|
424
|
-
edges_df.to_excel(f"{directory}/largest_connected_component.xlsx", index=False)
|
|
425
|
-
print(f"Largest component nodes saved to {directory}/largest_connected_component.xlsx")
|
|
426
|
-
|
|
427
|
-
nodes_in_largest_component = list(G0)
|
|
428
|
-
|
|
429
|
-
mask2 = labels_to_boolean(masks, nodes_in_largest_component)
|
|
430
|
-
|
|
431
|
-
mask2 = mask2 * masks
|
|
432
|
-
|
|
433
|
-
if search_region is not None:
|
|
434
|
-
searchers = labels_to_boolean(search_region, nodes_in_largest_component)
|
|
435
|
-
searchers = searchers * search_region
|
|
436
|
-
else:
|
|
437
|
-
searchers = None
|
|
438
|
-
|
|
439
|
-
if directory is None:
|
|
440
|
-
tifffile.imwrite("largest_connected_component.tif", mask2)
|
|
441
|
-
print(f"Largest connected component image saved to largest_connected_component.tif")
|
|
442
|
-
|
|
443
|
-
else:
|
|
444
|
-
tifffile.imwrite(f"{directory}/largest_connected_component.tif", mask2)
|
|
445
|
-
print(f"Largest connected component image saved to {directory}/largest_connected_component.tif")
|
|
446
|
-
|
|
447
|
-
return edges_largest_component, mask2, searchers
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
def isolate_key_connected(masks, G, key, search_region = None, directory = None):
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
# Get the connected component containing the specific node label
|
|
455
|
-
connected_component = nx.node_connected_component(G, key)
|
|
456
|
-
|
|
457
|
-
G0 = G.subgraph(connected_component)
|
|
458
|
-
|
|
459
|
-
# Extract the edges of the largest connected component
|
|
460
|
-
edges_component = list(G0.edges)
|
|
461
|
-
|
|
462
|
-
# Convert to a DataFrame
|
|
463
|
-
edges_df = pd.DataFrame(edges_component, columns=["Node A", "Node B"])
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if directory is None:
|
|
467
|
-
# Save to an Excel file
|
|
468
|
-
edges_df.to_excel(f"connected_component_containing_{key}_node.xlsx", index=False)
|
|
469
|
-
print(f"Nodes containing {key} saved to connected_component_containing_{key}_node.xlsx")
|
|
470
|
-
else:
|
|
471
|
-
edges_df.to_excel(f"{directory}/connected_component_containing_{key}_node.xlsx", index=False)
|
|
472
|
-
print(f"Nodes containing {key} saved to {directory}/connected_component_containing_{key}_node.xlsx")
|
|
473
|
-
|
|
474
|
-
# Convert the set of nodes to a list
|
|
475
|
-
nodes_in_component = list(connected_component)
|
|
476
|
-
|
|
477
|
-
mask2 = labels_to_boolean(masks, nodes_in_component)
|
|
478
|
-
|
|
479
|
-
mask2 = mask2 * masks
|
|
480
|
-
|
|
481
|
-
# Convert boolean values to 0 and 255
|
|
482
|
-
#mask2 = mask2.astype(np.uint8) * 255
|
|
483
|
-
|
|
484
|
-
if search_region is not None:
|
|
485
|
-
searchers = labels_to_boolean(search_region, nodes_in_component)
|
|
486
|
-
searchers = searchers * search_region
|
|
487
|
-
else:
|
|
488
|
-
searchers = None
|
|
489
|
-
|
|
490
|
-
if directory is None:
|
|
491
|
-
tifffile.imwrite(f"connected_component_containing_{key}_node.tif", mask2)
|
|
492
|
-
print(f"Connected component containing node {key} saved to connected_component_containing_{key}_node.tif")
|
|
493
|
-
|
|
494
|
-
else:
|
|
495
|
-
tifffile.imwrite(f"{directory}/connected_component_containing_{key}_node.tif", mask2)
|
|
496
|
-
print(f"Connected component containing node {key} saved to {directory}/connected_component_containing_{key}_node.tif")
|
|
497
|
-
|
|
498
|
-
return nodes_in_component, mask2, searchers
|
|
499
|
-
|
|
500
|
-
|
|
501
232
|
def extract_mothers(nodes, excel_file_path, centroid_dic = None, directory = None, louvain = True, ret_nodes = False, called = False):
|
|
502
233
|
|
|
503
234
|
if type(nodes) == str:
|
|
@@ -65,13 +65,12 @@ def reslice_3d_array(args):
|
|
|
65
65
|
return resliced_array
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
def _get_node_edge_dict(label_array, edge_array, label, dilate_xy, dilate_z, cores = 0):
|
|
68
|
+
def _get_node_edge_dict(label_array, edge_array, label, dilate_xy, dilate_z, cores = 0, search = 0, fastdil = False, xy_scale = 1, z_scale = 1):
|
|
70
69
|
"""Internal method used for the secondary algorithm to find pixel involvement of nodes around an edge."""
|
|
71
70
|
|
|
72
71
|
# Create a boolean mask where elements with the specified label are True
|
|
73
72
|
label_array = label_array == label
|
|
74
|
-
dil_array = nettracer.
|
|
73
|
+
dil_array = nettracer.dilate(label_array, search, xy_scale = xy_scale, z_scale = z_scale, fast_dil = fastdil) #Dilate the label to see where the dilated label overlaps
|
|
75
74
|
|
|
76
75
|
if cores == 0: #For getting the volume of objects. Cores presumes you want the 'core' included in the interaction.
|
|
77
76
|
edge_array = edge_array * dil_array # Filter the edges by the label in question
|
|
@@ -116,7 +115,7 @@ def process_label(args):
|
|
|
116
115
|
|
|
117
116
|
|
|
118
117
|
|
|
119
|
-
def create_node_dictionary(nodes, edges, num_nodes, dilate_xy, dilate_z, cores=0):
|
|
118
|
+
def create_node_dictionary(nodes, edges, num_nodes, dilate_xy, dilate_z, cores=0, search = 0, fastdil = False, xy_scale = 1, z_scale = 1):
|
|
120
119
|
"""Modified to pre-compute all bounding boxes using find_objects"""
|
|
121
120
|
node_dict = {}
|
|
122
121
|
array_shape = nodes.shape
|
|
@@ -136,20 +135,20 @@ def create_node_dictionary(nodes, edges, num_nodes, dilate_xy, dilate_z, cores=0
|
|
|
136
135
|
# Process results in parallel
|
|
137
136
|
for label, sub_nodes, sub_edges in results:
|
|
138
137
|
executor.submit(create_dict_entry, node_dict, label, sub_nodes, sub_edges,
|
|
139
|
-
dilate_xy, dilate_z, cores)
|
|
138
|
+
dilate_xy, dilate_z, cores, search, fastdil, xy_scale, z_scale)
|
|
140
139
|
|
|
141
140
|
return node_dict
|
|
142
141
|
|
|
143
|
-
def create_dict_entry(node_dict, label, sub_nodes, sub_edges, dilate_xy, dilate_z, cores = 0):
|
|
142
|
+
def create_dict_entry(node_dict, label, sub_nodes, sub_edges, dilate_xy, dilate_z, cores = 0, search = 0, fastdil = False, xy_scale = 1, z_scale = 1):
|
|
144
143
|
"""Internal method used for the secondary algorithm to pass around args in parallel."""
|
|
145
144
|
|
|
146
145
|
if label is None:
|
|
147
146
|
pass
|
|
148
147
|
else:
|
|
149
|
-
node_dict[label] = _get_node_edge_dict(sub_nodes, sub_edges, label, dilate_xy, dilate_z, cores = cores)
|
|
148
|
+
node_dict[label] = _get_node_edge_dict(sub_nodes, sub_edges, label, dilate_xy, dilate_z, cores = cores, search = search, fastdil = fastdil, xy_scale = xy_scale, z_scale = z_scale)
|
|
150
149
|
|
|
151
150
|
|
|
152
|
-
def quantify_edge_node(nodes, edges, search = 0, xy_scale = 1, z_scale = 1, cores = 0, resize = None, save = True, skele = False):
|
|
151
|
+
def quantify_edge_node(nodes, edges, search = 0, xy_scale = 1, z_scale = 1, cores = 0, resize = None, save = True, skele = False, fastdil = False):
|
|
153
152
|
|
|
154
153
|
def save_dubval_dict(dict, index_name, val1name, val2name, filename):
|
|
155
154
|
|
|
@@ -189,7 +188,7 @@ def quantify_edge_node(nodes, edges, search = 0, xy_scale = 1, z_scale = 1, core
|
|
|
189
188
|
dilate_xy, dilate_z = 0, 0
|
|
190
189
|
|
|
191
190
|
|
|
192
|
-
edge_quants = create_node_dictionary(nodes, edges, num_nodes, dilate_xy, dilate_z, cores = cores) #Find which edges connect which nodes and put them in a dictionary.
|
|
191
|
+
edge_quants = create_node_dictionary(nodes, edges, num_nodes, dilate_xy, dilate_z, cores = cores, search = search, fastdil = fastdil, xy_scale = xy_scale, z_scale = z_scale) #Find which edges connect which nodes and put them in a dictionary.
|
|
193
192
|
|
|
194
193
|
if save:
|
|
195
194
|
|
|
@@ -235,16 +234,14 @@ def calculate_voxel_volumes(array, xy_scale=1, z_scale=1):
|
|
|
235
234
|
|
|
236
235
|
|
|
237
236
|
|
|
238
|
-
def search_neighbor_ids(nodes, targets, id_dict, neighborhood_dict, totals, search, xy_scale, z_scale, root):
|
|
237
|
+
def search_neighbor_ids(nodes, targets, id_dict, neighborhood_dict, totals, search, xy_scale, z_scale, root, fastdil = False):
|
|
239
238
|
|
|
240
239
|
if 0 in targets:
|
|
241
240
|
targets.remove(0)
|
|
242
241
|
targets = np.isin(nodes, targets)
|
|
243
242
|
targets = nettracer.binarize(targets)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
dilated = nettracer.dilate_3D_recursive(targets, dilate_xy, dilate_xy, dilate_z)
|
|
243
|
+
|
|
244
|
+
dilated = nettracer.dilate(targets, search, xy_scale = xy_scale, z_scale = z_scale, fast_dil = fastdil)
|
|
248
245
|
dilated = dilated - targets #technically we dont need the cores
|
|
249
246
|
search_vol = np.count_nonzero(dilated) * xy_scale * xy_scale * z_scale #need this for density
|
|
250
247
|
targets = dilated != 0
|
|
@@ -255,13 +252,11 @@ def search_neighbor_ids(nodes, targets, id_dict, neighborhood_dict, totals, sear
|
|
|
255
252
|
|
|
256
253
|
unique, counts = np.unique(targets, return_counts=True)
|
|
257
254
|
count_dict = dict(zip(unique, counts))
|
|
258
|
-
print(count_dict)
|
|
259
255
|
|
|
260
256
|
del count_dict[0]
|
|
261
257
|
|
|
262
258
|
unique, counts = np.unique(nodes, return_counts=True)
|
|
263
259
|
total_dict = dict(zip(unique, counts))
|
|
264
|
-
print(total_dict)
|
|
265
260
|
|
|
266
261
|
del total_dict[0]
|
|
267
262
|
|
|
@@ -319,7 +314,7 @@ def get_search_space_dilate(target, centroids, id_dict, search, scaling = 1):
|
|
|
319
314
|
|
|
320
315
|
# Methods pertaining to getting radii:
|
|
321
316
|
|
|
322
|
-
def process_object_cpu(label, objects, labeled_array):
|
|
317
|
+
def process_object_cpu(label, objects, labeled_array, xy_scale = 1, z_scale = 1):
|
|
323
318
|
"""
|
|
324
319
|
Process a single labeled object to estimate its radius (CPU version).
|
|
325
320
|
This function is designed to be called in parallel.
|
|
@@ -357,25 +352,36 @@ def process_object_cpu(label, objects, labeled_array):
|
|
|
357
352
|
|
|
358
353
|
# Create binary mask for this object within the subarray
|
|
359
354
|
mask = (subarray == label)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
# Determine which dimension needs resampling
|
|
358
|
+
if (z_scale > xy_scale) and mask.shape[0] != 1:
|
|
359
|
+
# Z dimension needs to be stretched
|
|
360
|
+
zoom_factor = [z_scale/xy_scale, 1, 1] # Scale factor for [z, y, x]
|
|
361
|
+
cardinal = xy_scale
|
|
362
|
+
elif (xy_scale > z_scale) and mask.shape[0] != 1:
|
|
363
|
+
# XY dimensions need to be stretched
|
|
364
|
+
zoom_factor = [1, xy_scale/z_scale, xy_scale/z_scale] # Scale factor for [z, y, x]
|
|
365
|
+
cardinal = z_scale
|
|
366
|
+
else:
|
|
367
|
+
# Already uniform scaling, no need to resample
|
|
368
|
+
zoom_factor = None
|
|
369
|
+
cardinal = xy_scale
|
|
370
|
+
|
|
371
|
+
# Resample the mask if needed
|
|
372
|
+
if zoom_factor:
|
|
373
|
+
mask = ndimage.zoom(mask, zoom_factor, order=0) # Use order=0 for binary masks
|
|
374
|
+
|
|
360
375
|
|
|
361
376
|
# Compute distance transform on the smaller mask
|
|
362
377
|
dist_transform = compute_distance_transform_distance(mask)
|
|
363
378
|
|
|
364
379
|
# Filter out small values near the edge to focus on more central regions
|
|
365
|
-
radius = np.max(dist_transform)
|
|
380
|
+
radius = np.max(dist_transform) * cardinal
|
|
366
381
|
|
|
367
|
-
|
|
368
|
-
volume = np.sum(mask)
|
|
369
|
-
|
|
370
|
-
# Calculate bounding box dimensions
|
|
371
|
-
x_len = obj_slice[0].stop - obj_slice[0].start
|
|
372
|
-
y_len = obj_slice[1].stop - obj_slice[1].start
|
|
373
|
-
z_len = obj_slice[2].stop - obj_slice[2].start
|
|
374
|
-
dimensions = np.array([x_len, y_len, z_len])
|
|
375
|
-
|
|
376
|
-
return label, radius, volume, dimensions
|
|
382
|
+
return label, radius
|
|
377
383
|
|
|
378
|
-
def estimate_object_radii_cpu(labeled_array, n_jobs=None):
|
|
384
|
+
def estimate_object_radii_cpu(labeled_array, n_jobs=None, xy_scale = 1, z_scale = 1):
|
|
379
385
|
"""
|
|
380
386
|
Estimate the radii of labeled objects in a 3D numpy array using distance transform.
|
|
381
387
|
CPU parallel implementation.
|
|
@@ -399,7 +405,7 @@ def estimate_object_radii_cpu(labeled_array, n_jobs=None):
|
|
|
399
405
|
unique_labels = unique_labels[unique_labels != 0] # Remove background
|
|
400
406
|
|
|
401
407
|
# Create a partial function for parallel processing
|
|
402
|
-
process_func = partial(process_object_cpu, objects=objects, labeled_array=labeled_array)
|
|
408
|
+
process_func = partial(process_object_cpu, objects=objects, labeled_array=labeled_array, xy_scale = xy_scale, z_scale = z_scale)
|
|
403
409
|
|
|
404
410
|
# Process objects in parallel
|
|
405
411
|
results = []
|
|
@@ -414,12 +420,12 @@ def estimate_object_radii_cpu(labeled_array, n_jobs=None):
|
|
|
414
420
|
# Organize results
|
|
415
421
|
radii = {}
|
|
416
422
|
|
|
417
|
-
for label, radius
|
|
423
|
+
for label, radius in results:
|
|
418
424
|
radii[label] = radius
|
|
419
425
|
|
|
420
426
|
return radii
|
|
421
427
|
|
|
422
|
-
def estimate_object_radii_gpu(labeled_array):
|
|
428
|
+
def estimate_object_radii_gpu(labeled_array, xy_scale = 1, z_scale = 1):
|
|
423
429
|
"""
|
|
424
430
|
Estimate the radii of labeled objects in a 3D numpy array using distance transform.
|
|
425
431
|
GPU implementation using CuPy.
|
|
@@ -467,13 +473,32 @@ def estimate_object_radii_gpu(labeled_array):
|
|
|
467
473
|
|
|
468
474
|
# Create binary mask for this object (directly on GPU)
|
|
469
475
|
mask_gpu = (labeled_array_gpu[tuple(padded_slices)] == label)
|
|
476
|
+
|
|
477
|
+
# Determine which dimension needs resampling
|
|
478
|
+
if (z_scale > xy_scale) and mask_gpu.shape[0] != 1:
|
|
479
|
+
# Z dimension needs to be stretched
|
|
480
|
+
zoom_factor = [z_scale/xy_scale, 1, 1] # Scale factor for [z, y, x]
|
|
481
|
+
cardinal = xy_scale
|
|
482
|
+
elif (xy_scale > z_scale) and mask_gpu.shape[0] != 1:
|
|
483
|
+
# XY dimensions need to be stretched
|
|
484
|
+
zoom_factor = [1, xy_scale/z_scale, xy_scale/z_scale] # Scale factor for [z, y, x]
|
|
485
|
+
cardinal = z_scale
|
|
486
|
+
else:
|
|
487
|
+
# Already uniform scaling, no need to resample
|
|
488
|
+
zoom_factor = None
|
|
489
|
+
cardinal = xy_scale
|
|
490
|
+
|
|
491
|
+
# Resample the mask if needed
|
|
492
|
+
if zoom_factor:
|
|
493
|
+
mask_gpu = cpx.zoom(mask_gpu, zoom_factor, order=0) # Use order=0 for binary masks
|
|
470
494
|
|
|
471
495
|
# Compute distance transform on GPU
|
|
472
496
|
dist_transform_gpu = compute_distance_transform_distance_GPU(mask_gpu)
|
|
473
|
-
|
|
474
|
-
radius = float(cp.max(dist_transform_gpu).get())
|
|
475
|
-
|
|
476
|
-
|
|
497
|
+
|
|
498
|
+
radius = float(cp.max(dist_transform_gpu).get()) * cardinal
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
# Store the radius and the scaled radius
|
|
477
502
|
radii[label] = radius
|
|
478
503
|
|
|
479
504
|
# Clean up GPU memory
|