nettracer3d 0.6.5__py3-none-any.whl → 0.6.7__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.
@@ -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:
nettracer3d/morphology.py CHANGED
@@ -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.dilate_3D_recursive(label_array, dilate_xy, dilate_xy, dilate_z) #Dilate the label to see where the dilated label overlaps
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
- dilate_xy, dilate_z = nettracer.dilation_length_to_pixels(xy_scale, z_scale, search, search)
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
- # Calculate basic shape metrics
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, volume, dimensions in results:
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
- # Store the radius
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