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.

@@ -601,42 +601,35 @@ def _find_centroids_old(nodes, node_list = None, down_factor = None):
601
601
 
602
602
 
603
603
  def _find_centroids(nodes, node_list=None, down_factor=None):
604
- """Internal use version to get centroids without saving"""
605
-
604
+ """Parallel version using sum accumulation instead of storing coordinates"""
606
605
 
607
- def compute_indices_in_chunk(chunk, y_offset):
608
- """
609
- Alternative approach using np.where for even better performance on sparse arrays.
610
- """
611
- indices_dict_chunk = {}
606
+ def compute_sums_in_chunk(chunk, y_offset):
607
+ """Accumulate sums and counts - much less memory than storing coords"""
608
+ sums_dict = {}
609
+ counts_dict = {}
612
610
 
613
- # Get all coordinates where chunk is non-zero
614
611
  z_coords, y_coords, x_coords = np.where(chunk != 0)
615
612
 
616
613
  if len(z_coords) == 0:
617
- return indices_dict_chunk
614
+ return sums_dict, counts_dict
618
615
 
619
- # Adjust Y coordinates
620
616
  y_coords_adjusted = y_coords + y_offset
621
-
622
- # Get labels at these coordinates
623
617
  labels = chunk[z_coords, y_coords, x_coords]
624
-
625
- # Group by unique labels
626
618
  unique_labels = np.unique(labels)
627
619
 
628
620
  for label in unique_labels:
629
- if label == 0: # Skip background
621
+ if label == 0:
630
622
  continue
631
623
  mask = (labels == label)
632
- # Stack coordinates into the expected format [z, y, x]
633
- indices_dict_chunk[label] = np.column_stack((
634
- z_coords[mask],
635
- y_coords_adjusted[mask],
636
- x_coords[mask]
637
- ))
624
+ # Just store sums and counts - O(1) memory per label
625
+ sums_dict[label] = np.array([
626
+ z_coords[mask].sum(dtype=np.float64),
627
+ y_coords_adjusted[mask].sum(dtype=np.float64),
628
+ x_coords[mask].sum(dtype=np.float64)
629
+ ])
630
+ counts_dict[label] = mask.sum()
638
631
 
639
- return indices_dict_chunk
632
+ return sums_dict, counts_dict
640
633
 
641
634
  def chunk_3d_array(array, num_chunks):
642
635
  """Split the 3D array into smaller chunks along the y-axis."""
@@ -644,49 +637,44 @@ def _find_centroids(nodes, node_list=None, down_factor=None):
644
637
  return y_slices
645
638
 
646
639
  # Handle input processing
647
- if isinstance(nodes, str): # Open into numpy array if filepath
640
+ if isinstance(nodes, str):
648
641
  nodes = tifffile.imread(nodes)
649
- if len(np.unique(nodes)) == 2: # Label if binary
642
+ if len(np.unique(nodes)) == 2:
650
643
  structure_3d = np.ones((3, 3, 3), dtype=int)
651
644
  nodes, num_nodes = ndimage.label(nodes)
652
645
 
653
646
  if down_factor is not None:
654
647
  nodes = downsample(nodes, down_factor)
655
- else:
656
- down_factor = 1
657
648
 
658
- indices_dict = {}
649
+ sums_total = {}
650
+ counts_total = {}
659
651
  num_cpus = mp.cpu_count()
660
652
 
661
- # Chunk the 3D array along the y-axis
662
653
  node_chunks = chunk_3d_array(nodes, num_cpus)
663
-
664
- # Calculate Y offset for each chunk
665
654
  chunk_sizes = [chunk.shape[1] for chunk in node_chunks]
666
655
  y_offsets = np.cumsum([0] + chunk_sizes[:-1])
667
656
 
668
- # Parallel computation using the optimized single-pass approach
669
657
  with ThreadPoolExecutor(max_workers=num_cpus) as executor:
670
- futures = {executor.submit(compute_indices_in_chunk, chunk, y_offset): chunk_id
671
- for chunk_id, (chunk, y_offset) in enumerate(zip(node_chunks, y_offsets))}
658
+ futures = [executor.submit(compute_sums_in_chunk, chunk, y_offset)
659
+ for chunk, y_offset in zip(node_chunks, y_offsets)]
672
660
 
673
661
  for future in as_completed(futures):
674
- indices_chunk = future.result()
675
- # Merge indices for each label
676
- for label, indices in indices_chunk.items():
677
- if label in indices_dict:
678
- indices_dict[label] = np.vstack((indices_dict[label], indices))
662
+ sums_chunk, counts_chunk = future.result()
663
+
664
+ # Merge is now just addition - O(1) instead of vstack
665
+ for label in sums_chunk:
666
+ if label in sums_total:
667
+ sums_total[label] += sums_chunk[label]
668
+ counts_total[label] += counts_chunk[label]
679
669
  else:
680
- indices_dict[label] = indices
681
-
682
- # Compute centroids from collected indices
683
- centroid_dict = {}
684
- for label, indices in indices_dict.items():
685
- centroid = np.round(np.mean(indices, axis=0)).astype(int)
686
- centroid_dict[label] = centroid
670
+ sums_total[label] = sums_chunk[label]
671
+ counts_total[label] = counts_chunk[label]
687
672
 
688
- # Remove background label if it exists
689
- centroid_dict.pop(0, None)
673
+ # Compute centroids from accumulated sums
674
+ centroid_dict = {
675
+ label: np.round(sums_total[label] / counts_total[label]).astype(int)
676
+ for label in sums_total if label != 0
677
+ }
690
678
 
691
679
  return centroid_dict
692
680
 
@@ -935,8 +923,15 @@ def get_distance_list(centroids, network, xy_scale, z_scale):
935
923
  return distance_list
936
924
 
937
925
 
938
- def prune_samenode_connections(networkfile, nodeIDs):
939
- """Even faster numpy-based version for very large datasets"""
926
+ def prune_samenode_connections(networkfile, nodeIDs, target=None):
927
+ """Even faster numpy-based version for very large datasets
928
+
929
+ Args:
930
+ networkfile: Network file path or list of node pairs
931
+ nodeIDs: Node identity mapping (file path or dict)
932
+ target: Optional string. If provided, only prunes pairs where BOTH nodes
933
+ have this specific identity. If None, prunes all same-identity pairs.
934
+ """
940
935
  import numpy as np
941
936
 
942
937
  # Handle nodeIDs input
@@ -965,8 +960,13 @@ def prune_samenode_connections(networkfile, nodeIDs):
965
960
  idsA = np.array([data_dict.get(node) for node in nodesA])
966
961
  idsB = np.array([data_dict.get(node) for node in nodesB])
967
962
 
968
- # Create boolean mask - keep where IDs are different
969
- keep_mask = idsA != idsB
963
+ # Create boolean mask based on target parameter
964
+ if target is None:
965
+ # Original behavior: keep where IDs are different
966
+ keep_mask = idsA != idsB
967
+ else:
968
+ # New behavior: only remove pairs where BOTH nodes have the target identity
969
+ keep_mask = ~((idsA == target) & (idsB == target))
970
970
 
971
971
  # Apply filter
972
972
  filtered_nodesA = nodesA[keep_mask].tolist()
@@ -264,20 +264,20 @@ def draw_network_from_centroids(nodes, network, centroids, twod_bool, directory
264
264
 
265
265
  if twod_bool:
266
266
  output_stack = output_stack[0,:,:] | output_stack[0,:,:]
267
-
268
- if directory is None:
269
- try:
270
- tifffile.imwrite("drawn_network.tif", output_stack)
271
- except Exception as e:
272
- print("Could not save network lattice to active directory")
273
- print("Network lattice saved as drawn_network.tif")
274
-
275
- if directory is not None:
276
- try:
277
- tifffile.imwrite(f"{directory}/drawn_network.tif", output_stack)
278
- print(f"Network lattice saved to {directory}/drawn_network.tif")
279
- except Exception as e:
280
- print(f"Could not save network lattice to {directory}")
267
+
268
+ #if directory is None:
269
+ # try:
270
+ # tifffile.imwrite("drawn_network.tif", output_stack)
271
+ # except Exception as e:
272
+ # print("Could not save network lattice to active directory")
273
+ # print("Network lattice saved as drawn_network.tif")
274
+
275
+ #if directory is not None:
276
+ # try:
277
+ # tifffile.imwrite(f"{directory}/drawn_network.tif", output_stack)
278
+ # print(f"Network lattice saved to {directory}/drawn_network.tif")
279
+ #except Exception as e:
280
+ # print(f"Could not save network lattice to {directory}")
281
281
 
282
282
  return output_stack
283
283
 
@@ -340,6 +340,7 @@ def draw_network_from_centroids_GPU(nodes, network, centroids, twod_bool, direct
340
340
 
341
341
  output_stack = cp.asnumpy(output_stack)
342
342
 
343
+ """
343
344
  if directory is None:
344
345
  try:
345
346
  tifffile.imwrite("drawn_network.tif", output_stack)
@@ -353,7 +354,7 @@ def draw_network_from_centroids_GPU(nodes, network, centroids, twod_bool, direct
353
354
  print(f"Network lattice saved to {directory}/drawn_network.tif")
354
355
  except Exception as e:
355
356
  print(f"Could not save network lattice to {directory}")
356
-
357
+ """
357
358
 
358
359
  if __name__ == '__main__':
359
360