nettracer3d 0.4.2__tar.gz → 0.4.4__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.4.2/src/nettracer3d.egg-info → nettracer3d-0.4.4}/PKG-INFO +3 -9
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/README.md +1 -8
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/pyproject.toml +2 -1
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/morphology.py +2 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/nettracer.py +139 -2
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/nettracer_gui.py +1007 -107
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/proximity.py +3 -1
- nettracer3d-0.4.4/src/nettracer3d/segmenter.py +290 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/smart_dilate.py +44 -3
- {nettracer3d-0.4.2 → nettracer3d-0.4.4/src/nettracer3d.egg-info}/PKG-INFO +3 -9
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d.egg-info/SOURCES.txt +1 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d.egg-info/requires.txt +1 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/LICENSE +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/setup.cfg +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/__init__.py +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/community_extractor.py +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/hub_getter.py +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/modularity.py +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/network_analysis.py +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/network_draw.py +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/node_draw.py +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/run.py +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d/simple_network.py +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d.egg-info/dependency_links.txt +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d.egg-info/entry_points.txt +0 -0
- {nettracer3d-0.4.2 → nettracer3d-0.4.4}/src/nettracer3d.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: nettracer3d
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: Scripts for intializing and analyzing networks from segmentations of three dimensional images.
|
|
5
5
|
Author-email: Liam McLaughlin <boom2449@gmail.com>
|
|
6
6
|
Project-URL: User_Manual, https://drive.google.com/drive/folders/1fTkz3n4LN9_VxKRKC8lVQSlrz_wq0bVn?usp=drive_link
|
|
@@ -23,6 +23,7 @@ Requires-Dist: pandas
|
|
|
23
23
|
Requires-Dist: napari
|
|
24
24
|
Requires-Dist: python-louvain
|
|
25
25
|
Requires-Dist: tifffile
|
|
26
|
+
Requires-Dist: qtrangeslider
|
|
26
27
|
Requires-Dist: PyQt6
|
|
27
28
|
Provides-Extra: cuda11
|
|
28
29
|
Requires-Dist: cupy-cuda11x; extra == "cuda11"
|
|
@@ -31,15 +32,8 @@ Requires-Dist: cupy-cuda12x; extra == "cuda12"
|
|
|
31
32
|
Provides-Extra: cupy
|
|
32
33
|
Requires-Dist: cupy; extra == "cupy"
|
|
33
34
|
|
|
34
|
-
NetTracer3D is a python package developed for both 2D and 3D analysis of microscopic images in the .tif file format. It supports generation of 3D networks showing the relationships between objects (or nodes) in three dimensional space, either based on their own proximity or connectivity via connecting objects such as nerves or blood vessels. In addition to these functionalities are several advanced 3D data processing algorithms, such as labeling of branched structures or abstraction of branched structures into networks. Note that nettracer3d uses segmented data, which can be segmented from other softwares such as ImageJ and imported into NetTracer3D, although it does offer its own segmentation via intensity
|
|
35
|
+
NetTracer3D is a python package developed for both 2D and 3D analysis of microscopic images in the .tif file format. It supports generation of 3D networks showing the relationships between objects (or nodes) in three dimensional space, either based on their own proximity or connectivity via connecting objects such as nerves or blood vessels. In addition to these functionalities are several advanced 3D data processing algorithms, such as labeling of branched structures or abstraction of branched structures into networks. Note that nettracer3d uses segmented data, which can be segmented from other softwares such as ImageJ and imported into NetTracer3D, although it does offer its own segmentation via intensity and volumetric thresholding, or random forest machine learning segmentation. NetTracer3D currently has a fully functional GUI. To use the GUI, after installing the nettracer3d package via pip, enter the command 'nettracer3d' in your command prompt:
|
|
35
36
|
|
|
36
|
-
#Start
|
|
37
|
-
|
|
38
|
-
from nettracer3d import nettracer_gui
|
|
39
|
-
|
|
40
|
-
nettracer_gui.run_gui()
|
|
41
|
-
|
|
42
|
-
#End
|
|
43
37
|
|
|
44
38
|
This gui is built from the PyQt6 package and therefore may not function on dockers or virtual envs that are unable to support PyQt6 displays. More advanced documentation (especially for the GUI) is coming down the line, but for now please see: https://drive.google.com/drive/folders/1fTkz3n4LN9_VxKRKC8lVQSlrz_wq0bVn?usp=drive_link
|
|
45
39
|
for a user manual that provides older documentation.
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
NetTracer3D is a python package developed for both 2D and 3D analysis of microscopic images in the .tif file format. It supports generation of 3D networks showing the relationships between objects (or nodes) in three dimensional space, either based on their own proximity or connectivity via connecting objects such as nerves or blood vessels. In addition to these functionalities are several advanced 3D data processing algorithms, such as labeling of branched structures or abstraction of branched structures into networks. Note that nettracer3d uses segmented data, which can be segmented from other softwares such as ImageJ and imported into NetTracer3D, although it does offer its own segmentation via intensity
|
|
1
|
+
NetTracer3D is a python package developed for both 2D and 3D analysis of microscopic images in the .tif file format. It supports generation of 3D networks showing the relationships between objects (or nodes) in three dimensional space, either based on their own proximity or connectivity via connecting objects such as nerves or blood vessels. In addition to these functionalities are several advanced 3D data processing algorithms, such as labeling of branched structures or abstraction of branched structures into networks. Note that nettracer3d uses segmented data, which can be segmented from other softwares such as ImageJ and imported into NetTracer3D, although it does offer its own segmentation via intensity and volumetric thresholding, or random forest machine learning segmentation. NetTracer3D currently has a fully functional GUI. To use the GUI, after installing the nettracer3d package via pip, enter the command 'nettracer3d' in your command prompt:
|
|
2
2
|
|
|
3
|
-
#Start
|
|
4
|
-
|
|
5
|
-
from nettracer3d import nettracer_gui
|
|
6
|
-
|
|
7
|
-
nettracer_gui.run_gui()
|
|
8
|
-
|
|
9
|
-
#End
|
|
10
3
|
|
|
11
4
|
This gui is built from the PyQt6 package and therefore may not function on dockers or virtual envs that are unable to support PyQt6 displays. More advanced documentation (especially for the GUI) is coming down the line, but for now please see: https://drive.google.com/drive/folders/1fTkz3n4LN9_VxKRKC8lVQSlrz_wq0bVn?usp=drive_link
|
|
12
5
|
for a user manual that provides older documentation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nettracer3d"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.4"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="Liam McLaughlin", email="boom2449@gmail.com" },
|
|
6
6
|
]
|
|
@@ -18,6 +18,7 @@ dependencies = [
|
|
|
18
18
|
"napari",
|
|
19
19
|
"python-louvain",
|
|
20
20
|
"tifffile",
|
|
21
|
+
"qtrangeslider",
|
|
21
22
|
"PyQt6"
|
|
22
23
|
]
|
|
23
24
|
readme = "README.md"
|
|
@@ -101,6 +101,8 @@ def process_label(args):
|
|
|
101
101
|
nodes, edges, label, dilate_xy, dilate_z, array_shape = args
|
|
102
102
|
print(f"Processing node {label}")
|
|
103
103
|
indices = np.argwhere(nodes == label)
|
|
104
|
+
if len(indices) == 0:
|
|
105
|
+
return None, None, None
|
|
104
106
|
z_vals, y_vals, x_vals = get_reslice_indices((indices, dilate_xy, dilate_z, array_shape))
|
|
105
107
|
if z_vals is None: #If get_reslice_indices ran into a ValueError, nothing is returned.
|
|
106
108
|
return None, None, None
|
|
@@ -1003,6 +1003,8 @@ def dilate_3D_recursive(tiff_array, dilated_x, dilated_y, dilated_z, step_size=N
|
|
|
1003
1003
|
max_dilation = max(dilated_x, dilated_y, dilated_z)
|
|
1004
1004
|
if max_dilation < (0.2 * min_dim):
|
|
1005
1005
|
return dilate_3D(tiff_array, dilated_x, dilated_y, dilated_z)
|
|
1006
|
+
elif dilated_x == 1 and dilated_y == 1 and dilated_z == 1: #Also if there is only a single dilation don't do it
|
|
1007
|
+
return dilate_3D(tiff_array, dilated_x, dilated_y, dilated_z)
|
|
1006
1008
|
|
|
1007
1009
|
# Initialize step_size for first call
|
|
1008
1010
|
if step_size is None:
|
|
@@ -1587,6 +1589,51 @@ def label_branches(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol =
|
|
|
1587
1589
|
|
|
1588
1590
|
return array
|
|
1589
1591
|
|
|
1592
|
+
def fix_branches(array, G, communities, fix_val = None):
|
|
1593
|
+
|
|
1594
|
+
def invert_dict(d):
|
|
1595
|
+
inverted = {}
|
|
1596
|
+
for key, value in d.items():
|
|
1597
|
+
inverted.setdefault(value, []).append(key)
|
|
1598
|
+
return inverted
|
|
1599
|
+
|
|
1600
|
+
def get_degree_threshold(community_degrees):
|
|
1601
|
+
degrees = np.array(community_degrees, dtype=float)
|
|
1602
|
+
hist, bins = np.histogram(degrees, bins='auto')
|
|
1603
|
+
peaks, _ = find_peaks(hist)
|
|
1604
|
+
if len(peaks) > 1:
|
|
1605
|
+
# Get bin value after first peak as threshold
|
|
1606
|
+
return bins[peaks[0] + 1]
|
|
1607
|
+
return 4 # Default fallback
|
|
1608
|
+
|
|
1609
|
+
avg_degree = G.number_of_edges() * 2 / G.number_of_nodes()
|
|
1610
|
+
|
|
1611
|
+
targs = []
|
|
1612
|
+
|
|
1613
|
+
inverted = invert_dict(communities)
|
|
1614
|
+
|
|
1615
|
+
community_degrees = {}
|
|
1616
|
+
|
|
1617
|
+
for com in inverted:
|
|
1618
|
+
subgraph = G.subgraph(inverted[com])
|
|
1619
|
+
sub_degree = subgraph.number_of_edges() * 2/ subgraph.number_of_nodes()
|
|
1620
|
+
community_degrees[com] = sub_degree
|
|
1621
|
+
|
|
1622
|
+
|
|
1623
|
+
if fix_val is None:
|
|
1624
|
+
threshold = get_degree_threshold(list(community_degrees.values()))
|
|
1625
|
+
else:
|
|
1626
|
+
threshold = fix_val
|
|
1627
|
+
|
|
1628
|
+
for com in community_degrees:
|
|
1629
|
+
if community_degrees[com] > threshold: #This method of comparison could possibly be more nuanced.
|
|
1630
|
+
targs.append(com)
|
|
1631
|
+
|
|
1632
|
+
|
|
1633
|
+
return targs
|
|
1634
|
+
|
|
1635
|
+
|
|
1636
|
+
|
|
1590
1637
|
def label_vertices(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol = 0, down_factor = 0, directory = None, return_skele = False, order = 0):
|
|
1591
1638
|
"""
|
|
1592
1639
|
Can be used to label vertices (where multiple branches connect) a binary image. Labelled output will be saved to the active directory if none is specified. Note this works better on already thin filaments and may over-divide larger trunkish objects.
|
|
@@ -1789,6 +1836,7 @@ def watershed(image, directory = None, proportion = 0.1, GPU = True, smallest_ra
|
|
|
1789
1836
|
gotoexcept = 1/0
|
|
1790
1837
|
|
|
1791
1838
|
except (cp.cuda.memory.OutOfMemoryError, ZeroDivisionError) as e:
|
|
1839
|
+
|
|
1792
1840
|
if predownsample is None:
|
|
1793
1841
|
down_factor = smart_dilate.catch_memory(e) #Obtain downsample amount based on memory missing
|
|
1794
1842
|
else:
|
|
@@ -1819,8 +1867,12 @@ def watershed(image, directory = None, proportion = 0.1, GPU = True, smallest_ra
|
|
|
1819
1867
|
|
|
1820
1868
|
labels, _ = label_objects(distance)
|
|
1821
1869
|
|
|
1870
|
+
if len(labels.shape) ==2:
|
|
1871
|
+
labels = np.expand_dims(labels, axis = 0)
|
|
1872
|
+
|
|
1822
1873
|
del distance
|
|
1823
1874
|
|
|
1875
|
+
|
|
1824
1876
|
if labels.shape[1] < original_shape[1]: #If downsample was used, upsample output
|
|
1825
1877
|
labels = upsample_with_padding(labels, downsample_needed, original_shape)
|
|
1826
1878
|
labels = labels * old_mask
|
|
@@ -1829,8 +1881,7 @@ def watershed(image, directory = None, proportion = 0.1, GPU = True, smallest_ra
|
|
|
1829
1881
|
labels = smart_dilate.smart_label(image, labels, GPU = GPU, predownsample = predownsample2)
|
|
1830
1882
|
|
|
1831
1883
|
if directory is None:
|
|
1832
|
-
|
|
1833
|
-
print("Watershed saved to 'Watershed_output.tif'")
|
|
1884
|
+
pass
|
|
1834
1885
|
else:
|
|
1835
1886
|
tifffile.imwrite(f"{directory}/Watershed_output.tif", labels)
|
|
1836
1887
|
print(f"Watershed saved to {directory}/'Watershed_output.tif'")
|
|
@@ -3417,6 +3468,92 @@ class Network_3D:
|
|
|
3417
3468
|
self._nodes = self._nodes.astype(np.uint16)
|
|
3418
3469
|
|
|
3419
3470
|
|
|
3471
|
+
def com_to_node(self, targets = None):
|
|
3472
|
+
|
|
3473
|
+
def invert_dict(d):
|
|
3474
|
+
inverted = {}
|
|
3475
|
+
for key, value in d.items():
|
|
3476
|
+
inverted.setdefault(value, []).append(key)
|
|
3477
|
+
return inverted
|
|
3478
|
+
|
|
3479
|
+
def update_array(array_3d, value_dict, targets = None):
|
|
3480
|
+
ref_array = copy.deepcopy(array_3d)
|
|
3481
|
+
if targets is None:
|
|
3482
|
+
for key, value_list in value_dict.items():
|
|
3483
|
+
for value in value_list:
|
|
3484
|
+
array_3d[ref_array == value] = key
|
|
3485
|
+
else:
|
|
3486
|
+
max_val = np.max(array_3d) + 1
|
|
3487
|
+
for key, value_list in value_dict.items():
|
|
3488
|
+
for value in value_list:
|
|
3489
|
+
array_3d[ref_array == value] = max_val
|
|
3490
|
+
max_val += 1
|
|
3491
|
+
|
|
3492
|
+
return array_3d
|
|
3493
|
+
|
|
3494
|
+
if 0 in self.communities.values():
|
|
3495
|
+
self.communities = {k: v + 1 for k, v in self.communities.items()}
|
|
3496
|
+
if targets is not None:
|
|
3497
|
+
for item in targets:
|
|
3498
|
+
item = item + 1
|
|
3499
|
+
|
|
3500
|
+
inverted = invert_dict(self.communities)
|
|
3501
|
+
|
|
3502
|
+
|
|
3503
|
+
if targets is not None:
|
|
3504
|
+
new_inverted = copy.deepcopy(inverted)
|
|
3505
|
+
for com in inverted:
|
|
3506
|
+
if com not in targets:
|
|
3507
|
+
del new_inverted[com]
|
|
3508
|
+
inverted = new_inverted
|
|
3509
|
+
|
|
3510
|
+
|
|
3511
|
+
if self._node_identities is not None:
|
|
3512
|
+
new_identities = {}
|
|
3513
|
+
for com in inverted:
|
|
3514
|
+
new_identities[com] = ""
|
|
3515
|
+
|
|
3516
|
+
list1 = self._network_lists[0] #Get network lists to change
|
|
3517
|
+
list2 = self._network_lists[1]
|
|
3518
|
+
list3 = self._network_lists[2]
|
|
3519
|
+
|
|
3520
|
+
for i in range(len(list1)):
|
|
3521
|
+
list1[i] = self.communities[list1[i]] #Set node at network list spot to its community instead
|
|
3522
|
+
list2[i] = self.communities[list2[i]]
|
|
3523
|
+
if list1[i] == list2[i]: #If the edge corresponding there joins different communities, it will not be set to 0
|
|
3524
|
+
list3[i] = 0
|
|
3525
|
+
|
|
3526
|
+
|
|
3527
|
+
self.network_lists = [list1, list2, list3]
|
|
3528
|
+
|
|
3529
|
+
if self._nodes is not None:
|
|
3530
|
+
self._nodes = update_array(self._nodes, inverted, targets = targets) #Set the array to match the new network
|
|
3531
|
+
|
|
3532
|
+
try:
|
|
3533
|
+
|
|
3534
|
+
if self._node_identities is not None:
|
|
3535
|
+
|
|
3536
|
+
for key, value_list in inverted.items():
|
|
3537
|
+
temp_dict = {}
|
|
3538
|
+
for value in value_list:
|
|
3539
|
+
if self._node_identities[value] in temp_dict:
|
|
3540
|
+
temp_dict[self._node_identities[value]] += 1
|
|
3541
|
+
else:
|
|
3542
|
+
temp_dict[self._node_identities[value]] = 1
|
|
3543
|
+
for id_type, num in temp_dict.items():
|
|
3544
|
+
new_identities[key] += f'ID {id_type}:{num}, '
|
|
3545
|
+
|
|
3546
|
+
self.node_identities = new_identities
|
|
3547
|
+
except:
|
|
3548
|
+
pass
|
|
3549
|
+
|
|
3550
|
+
|
|
3551
|
+
|
|
3552
|
+
|
|
3553
|
+
|
|
3554
|
+
|
|
3555
|
+
|
|
3556
|
+
|
|
3420
3557
|
def trunk_to_node(self):
|
|
3421
3558
|
"""
|
|
3422
3559
|
Converts the edge 'trunk' into a node. In this case, the trunk is the edge that creates the most node-node connections. There may be times when many nodes are connected by a single, expansive edge that obfuscates the rest of the edges. Converting the trunk to a node can better reveal these edges.
|