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/network_analysis.py
CHANGED
|
@@ -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
|
-
"""
|
|
605
|
-
|
|
604
|
+
"""Parallel version using sum accumulation instead of storing coordinates"""
|
|
606
605
|
|
|
607
|
-
def
|
|
608
|
-
"""
|
|
609
|
-
|
|
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
|
|
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:
|
|
621
|
+
if label == 0:
|
|
630
622
|
continue
|
|
631
623
|
mask = (labels == label)
|
|
632
|
-
#
|
|
633
|
-
|
|
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
|
|
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):
|
|
640
|
+
if isinstance(nodes, str):
|
|
648
641
|
nodes = tifffile.imread(nodes)
|
|
649
|
-
if len(np.unique(nodes)) == 2:
|
|
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
|
-
|
|
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 =
|
|
671
|
-
for
|
|
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
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
689
|
-
centroid_dict
|
|
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
|
|
969
|
-
|
|
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()
|
nettracer3d/network_draw.py
CHANGED
|
@@ -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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if directory is not None:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
except Exception as e:
|
|
280
|
-
|
|
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
|
|