nettracer3d 0.7.9__tar.gz → 0.8.0__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.
Potentially problematic release.
This version of nettracer3d might be problematic. Click here for more details.
- {nettracer3d-0.7.9/src/nettracer3d.egg-info → nettracer3d-0.8.0}/PKG-INFO +8 -3
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/README.md +8 -3
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/pyproject.toml +1 -1
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/community_extractor.py +17 -26
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/nettracer.py +111 -30
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/nettracer_gui.py +914 -182
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/proximity.py +20 -6
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/segmenter.py +1 -1
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/segmenter_GPU.py +1 -1
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/simple_network.py +43 -25
- {nettracer3d-0.7.9 → nettracer3d-0.8.0/src/nettracer3d.egg-info}/PKG-INFO +8 -3
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/LICENSE +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/setup.cfg +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/__init__.py +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/excelotron.py +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/modularity.py +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/morphology.py +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/neighborhoods.py +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/network_analysis.py +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/network_draw.py +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/node_draw.py +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/run.py +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d/smart_dilate.py +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d.egg-info/SOURCES.txt +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d.egg-info/dependency_links.txt +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d.egg-info/entry_points.txt +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/src/nettracer3d.egg-info/requires.txt +0 -0
- {nettracer3d-0.7.9 → nettracer3d-0.8.0}/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.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Scripts for intializing and analyzing networks from segmentations of three dimensional images.
|
|
5
5
|
Author-email: Liam McLaughlin <liamm@wustl.edu>
|
|
6
6
|
Project-URL: Documentation, https://nettracer3d.readthedocs.io/en/latest/
|
|
@@ -73,6 +73,11 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
|
|
|
73
73
|
|
|
74
74
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
75
75
|
|
|
76
|
-
-- Version 0.
|
|
76
|
+
-- Version 0.8.0 Updates --
|
|
77
77
|
|
|
78
|
-
*
|
|
78
|
+
* Added ability to threshold nodes by degree.
|
|
79
|
+
* Improved image viewer window performance.
|
|
80
|
+
* Bug fixes and a few optimizations.
|
|
81
|
+
* Added ability to 'merge node identities' which just uses the nodes image as a reference for collecting 'identity' information from a group of other images - ie can use with cell nuclei (DAPI) to see what markers from the same imaging session overlap.
|
|
82
|
+
* Added ability to search for specific nodes directly in the nodes image with 'shift + f' or right click.
|
|
83
|
+
|
|
@@ -34,6 +34,11 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
|
|
|
34
34
|
|
|
35
35
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
36
36
|
|
|
37
|
-
-- Version 0.
|
|
38
|
-
|
|
39
|
-
*
|
|
37
|
+
-- Version 0.8.0 Updates --
|
|
38
|
+
|
|
39
|
+
* Added ability to threshold nodes by degree.
|
|
40
|
+
* Improved image viewer window performance.
|
|
41
|
+
* Bug fixes and a few optimizations.
|
|
42
|
+
* Added ability to 'merge node identities' which just uses the nodes image as a reference for collecting 'identity' information from a group of other images - ie can use with cell nuclei (DAPI) to see what markers from the same imaging session overlap.
|
|
43
|
+
* Added ability to search for specific nodes directly in the nodes image with 'shift + f' or right click.
|
|
44
|
+
|
|
@@ -492,48 +492,39 @@ def generate_distinct_colors(n_colors: int) -> List[Tuple[int, int, int]]:
|
|
|
492
492
|
return colors
|
|
493
493
|
|
|
494
494
|
def assign_node_colors(node_list: List[int], labeled_array: np.ndarray) -> Tuple[np.ndarray, Dict[int, str]]:
|
|
495
|
-
"""
|
|
496
|
-
Assign distinct colors to nodes and create an RGBA image.
|
|
497
|
-
|
|
498
|
-
Args:
|
|
499
|
-
node_list: List of node IDs
|
|
500
|
-
labeled_array: 3D numpy array with labels corresponding to node IDs
|
|
495
|
+
"""fast version using lookup table approach."""
|
|
501
496
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
"""
|
|
505
|
-
|
|
506
|
-
# Sort communities by size (descending)
|
|
507
|
-
sorted_nodes= sorted(node_list, reverse=True)
|
|
497
|
+
# Sort nodes by size (descending)
|
|
498
|
+
sorted_nodes = sorted(node_list, reverse=True)
|
|
508
499
|
|
|
509
500
|
# Generate distinct colors
|
|
510
501
|
colors = generate_distinct_colors(len(node_list))
|
|
511
|
-
random.shuffle(colors)
|
|
502
|
+
random.shuffle(colors) # Randomly sorted to make adjacent structures likely stand out
|
|
512
503
|
|
|
513
504
|
# Convert RGB colors to RGBA by adding alpha channel
|
|
514
|
-
colors_rgba = [(r, g, b, 255) for r, g, b in colors]
|
|
505
|
+
colors_rgba = np.array([(r, g, b, 255) for r, g, b in colors], dtype=np.uint8)
|
|
515
506
|
|
|
516
|
-
# Create mapping from
|
|
507
|
+
# Create mapping from node to color
|
|
517
508
|
node_to_color = {node: colors_rgba[i] for i, node in enumerate(sorted_nodes)}
|
|
518
509
|
|
|
519
|
-
# Create
|
|
520
|
-
|
|
510
|
+
# Create lookup table
|
|
511
|
+
max_label = max(max(labeled_array.flat), max(node_list) if node_list else 0)
|
|
512
|
+
color_lut = np.zeros((max_label + 1, 4), dtype=np.uint8) # Transparent by default
|
|
521
513
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
# Convert the RGB portion of community_to_color back to RGB for color naming
|
|
514
|
+
for node_id, color in node_to_color.items():
|
|
515
|
+
color_lut[node_id] = color
|
|
516
|
+
|
|
517
|
+
# Single vectorized operation - eliminates all loops!
|
|
518
|
+
rgba_array = color_lut[labeled_array]
|
|
519
|
+
|
|
520
|
+
# Convert colors for naming
|
|
530
521
|
node_to_color_rgb = {k: tuple(v[:3]) for k, v in node_to_color.items()}
|
|
531
522
|
node_to_color_names = convert_node_colors_to_names(node_to_color_rgb)
|
|
532
523
|
|
|
533
524
|
return rgba_array, node_to_color_names
|
|
534
525
|
|
|
535
526
|
def assign_community_colors(community_dict: Dict[int, int], labeled_array: np.ndarray) -> Tuple[np.ndarray, Dict[int, str]]:
|
|
536
|
-
"""
|
|
527
|
+
"""fast version using lookup table approach."""
|
|
537
528
|
|
|
538
529
|
# Same setup as before
|
|
539
530
|
communities = set(community_dict.values())
|
|
@@ -548,6 +548,7 @@ def remove_branches(skeleton, length):
|
|
|
548
548
|
return image_copy
|
|
549
549
|
|
|
550
550
|
|
|
551
|
+
|
|
551
552
|
def estimate_object_radii(labeled_array, gpu=False, n_jobs=None, xy_scale = 1, z_scale = 1):
|
|
552
553
|
"""
|
|
553
554
|
Estimate the radii of labeled objects in a 3D numpy array.
|
|
@@ -1485,21 +1486,21 @@ def remove_zeros(input_list):
|
|
|
1485
1486
|
|
|
1486
1487
|
|
|
1487
1488
|
def combine_edges(edge_labels_1, edge_labels_2):
|
|
1488
|
-
"""
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1489
|
+
"""
|
|
1490
|
+
let NumPy handle promotion automatically
|
|
1491
|
+
"""
|
|
1492
|
+
# Early exit if no combination needed
|
|
1493
|
+
mask = (edge_labels_1 == 0) & (edge_labels_2 > 0)
|
|
1494
|
+
if not np.any(mask):
|
|
1495
|
+
return edge_labels_1.copy()
|
|
1496
|
+
|
|
1497
|
+
max_val = np.max(edge_labels_1)
|
|
1498
|
+
|
|
1499
|
+
# Let NumPy handle dtype promotion automatically
|
|
1500
|
+
# This will promote to the smallest type that can handle the operation
|
|
1501
|
+
offset_labels = edge_labels_2 + max_val
|
|
1502
|
+
|
|
1503
|
+
return np.where(mask, offset_labels, edge_labels_1)
|
|
1503
1504
|
|
|
1504
1505
|
def combine_nodes(root_nodes, other_nodes, other_ID, identity_dict, root_ID = None):
|
|
1505
1506
|
|
|
@@ -1507,15 +1508,10 @@ def combine_nodes(root_nodes, other_nodes, other_ID, identity_dict, root_ID = No
|
|
|
1507
1508
|
|
|
1508
1509
|
print("Combining node arrays")
|
|
1509
1510
|
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
root_bools = root_nodes == 0 #Get boolean mask where root nodes do not exist.
|
|
1515
|
-
other_bools = other_nodes > 0 #Get boolean mask where other nodes exist.
|
|
1516
|
-
other_nodes = other_nodes + max_val #Add the maximum root node labels to other nodes so the two can be merged without overriding eachother
|
|
1517
|
-
other_nodes = other_nodes * other_bools #Eliminate any indices that should be 0 from other_nodes.
|
|
1518
|
-
other_nodes = other_nodes * root_bools #Eliminate any indices where other nodes overlap root nodes (root node are giving overlap priority)
|
|
1511
|
+
mask = (root_nodes == 0) & (other_nodes > 0)
|
|
1512
|
+
if np.any(mask):
|
|
1513
|
+
max_val = np.max(root_nodes)
|
|
1514
|
+
other_nodes[:] = np.where(mask, other_nodes + max_val, 0)
|
|
1519
1515
|
|
|
1520
1516
|
if root_ID is not None:
|
|
1521
1517
|
rootIDs = list(np.unique(root_nodes)) #Sets up adding these vals to the identitiy dictionary. Gets skipped if this has already been done.
|
|
@@ -1530,6 +1526,11 @@ def combine_nodes(root_nodes, other_nodes, other_ID, identity_dict, root_ID = No
|
|
|
1530
1526
|
|
|
1531
1527
|
if root_ID is not None: #Adds the root vals to the dictionary if it hasn't already
|
|
1532
1528
|
|
|
1529
|
+
if other_ID.endswith('.tiff'):
|
|
1530
|
+
other_ID = other_ID[:-5]
|
|
1531
|
+
elif other_ID.endswith('.tif'):
|
|
1532
|
+
other_ID = other_ID[:-4]
|
|
1533
|
+
|
|
1533
1534
|
for item in rootIDs:
|
|
1534
1535
|
identity_dict[item] = root_ID
|
|
1535
1536
|
|
|
@@ -1538,6 +1539,11 @@ def combine_nodes(root_nodes, other_nodes, other_ID, identity_dict, root_ID = No
|
|
|
1538
1539
|
other_ID = os.path.basename(other_ID)
|
|
1539
1540
|
except:
|
|
1540
1541
|
pass
|
|
1542
|
+
if other_ID.endswith('.tiff'):
|
|
1543
|
+
other_ID = other_ID[:-5]
|
|
1544
|
+
elif other_ID.endswith('.tif'):
|
|
1545
|
+
other_ID = other_ID[:-4]
|
|
1546
|
+
|
|
1541
1547
|
identity_dict[item] = other_ID
|
|
1542
1548
|
|
|
1543
1549
|
nodes = root_nodes + other_nodes #Combine the outer edges with the inner edges modified via the above steps
|
|
@@ -1609,6 +1615,17 @@ def downsample(data, factor, directory=None, order=0):
|
|
|
1609
1615
|
|
|
1610
1616
|
return data
|
|
1611
1617
|
|
|
1618
|
+
|
|
1619
|
+
def otsu_binarize(image_array):
|
|
1620
|
+
|
|
1621
|
+
"""Automated binarize method for seperating the foreground"""
|
|
1622
|
+
|
|
1623
|
+
from skimage.filters import threshold_otsu
|
|
1624
|
+
|
|
1625
|
+
threshold = threshold_otsu(image_array)
|
|
1626
|
+
binary_mask = image_array > threshold
|
|
1627
|
+
return binary_mask
|
|
1628
|
+
|
|
1612
1629
|
def binarize(arrayimage, directory = None):
|
|
1613
1630
|
"""
|
|
1614
1631
|
Can be used to binarize an image. Binary output will be saved to the active directory if none is specified.
|
|
@@ -3428,7 +3445,7 @@ class Network_3D:
|
|
|
3428
3445
|
"""
|
|
3429
3446
|
self._nodes, num_nodes = label_objects(nodes, structure_3d)
|
|
3430
3447
|
|
|
3431
|
-
def merge_nodes(self, addn_nodes_name, label_nodes = True):
|
|
3448
|
+
def merge_nodes(self, addn_nodes_name, label_nodes = True, root_id = "Root_Nodes"):
|
|
3432
3449
|
"""
|
|
3433
3450
|
Merges the self._nodes attribute with alternate labelled node images. The alternate nodes can be inputted as a string for a filepath to a tif,
|
|
3434
3451
|
or as a directory address containing only tif images, which will merge the _nodes attribute with all tifs in the folder. The _node_identities attribute
|
|
@@ -3439,8 +3456,13 @@ class Network_3D:
|
|
|
3439
3456
|
:param label_nodes: (Optional - Val = True; Boolean). Will label all discrete objects in each node file being merged if True. If False, will not label.
|
|
3440
3457
|
"""
|
|
3441
3458
|
|
|
3442
|
-
nodes_name =
|
|
3459
|
+
nodes_name = root_id
|
|
3443
3460
|
|
|
3461
|
+
try:
|
|
3462
|
+
nodes_name = os.path.splitext(os.path.basename(nodes_name))[0]
|
|
3463
|
+
except:
|
|
3464
|
+
pass
|
|
3465
|
+
|
|
3444
3466
|
identity_dict = {} #A dictionary to deliniate the node identities
|
|
3445
3467
|
|
|
3446
3468
|
try: #Try presumes the input is a tif
|
|
@@ -3462,7 +3484,10 @@ class Network_3D:
|
|
|
3462
3484
|
for i, addn_nodes in enumerate(addn_nodes_list):
|
|
3463
3485
|
try:
|
|
3464
3486
|
addn_nodes_ID = addn_nodes
|
|
3465
|
-
|
|
3487
|
+
try:
|
|
3488
|
+
addn_nodes = tifffile.imread(f'{addn_nodes_name}/{addn_nodes}')
|
|
3489
|
+
except:
|
|
3490
|
+
continue
|
|
3466
3491
|
|
|
3467
3492
|
if label_nodes is True:
|
|
3468
3493
|
addn_nodes, num_nodes2 = label_objects(addn_nodes) # Label the node objects. Note this presumes no overlap between node masks.
|
|
@@ -4649,11 +4674,15 @@ class Network_3D:
|
|
|
4649
4674
|
else:
|
|
4650
4675
|
min_coords, max_coords = bounds
|
|
4651
4676
|
|
|
4652
|
-
|
|
4677
|
+
try:
|
|
4678
|
+
dim_list = max_coords - min_coords
|
|
4679
|
+
except:
|
|
4680
|
+
min_coords = np.array([0,0,0])
|
|
4681
|
+
bounds = (min_coords, max_coords)
|
|
4682
|
+
dim_list = max_coords - min_coords
|
|
4653
4683
|
|
|
4654
4684
|
new_list = []
|
|
4655
4685
|
|
|
4656
|
-
|
|
4657
4686
|
if dim == 3:
|
|
4658
4687
|
for centroid in roots:
|
|
4659
4688
|
|
|
@@ -4688,7 +4717,7 @@ class Network_3D:
|
|
|
4688
4717
|
|
|
4689
4718
|
h_vals = proximity.compute_ripleys_h(k_vals, r_vals, dim)
|
|
4690
4719
|
|
|
4691
|
-
proximity.plot_ripley_functions(r_vals, k_vals, h_vals, dim)
|
|
4720
|
+
proximity.plot_ripley_functions(r_vals, k_vals, h_vals, dim, root, targ)
|
|
4692
4721
|
|
|
4693
4722
|
return r_vals, k_vals, h_vals
|
|
4694
4723
|
|
|
@@ -5082,6 +5111,58 @@ class Network_3D:
|
|
|
5082
5111
|
|
|
5083
5112
|
return heat_dict
|
|
5084
5113
|
|
|
5114
|
+
def merge_node_ids(self, path, data):
|
|
5115
|
+
|
|
5116
|
+
if self.node_identities is None: # Prepare modular dict
|
|
5117
|
+
|
|
5118
|
+
self.node_identities = {}
|
|
5119
|
+
|
|
5120
|
+
nodes = list(np.unique(data))
|
|
5121
|
+
if 0 in nodes:
|
|
5122
|
+
del nodes[0]
|
|
5123
|
+
|
|
5124
|
+
for node in nodes:
|
|
5125
|
+
self.node_identities[node] = ''
|
|
5126
|
+
|
|
5127
|
+
img_list = directory_info(path)
|
|
5128
|
+
|
|
5129
|
+
for i, img in enumerate(img_list):
|
|
5130
|
+
mask = tifffile.imread(f'{path}/{img}')
|
|
5131
|
+
|
|
5132
|
+
if len(np.unique(mask)) != 2:
|
|
5133
|
+
|
|
5134
|
+
mask = otsu_binarize(mask)
|
|
5135
|
+
|
|
5136
|
+
nodes = data * mask
|
|
5137
|
+
nodes = list(np.unique(nodes))
|
|
5138
|
+
if 0 in nodes:
|
|
5139
|
+
del nodes[0]
|
|
5140
|
+
|
|
5141
|
+
if img.endswith('.tiff'):
|
|
5142
|
+
base_name = img[:-5]
|
|
5143
|
+
elif img.endswith('.tif'):
|
|
5144
|
+
base_name = img[:-4]
|
|
5145
|
+
else:
|
|
5146
|
+
base_name = img
|
|
5147
|
+
|
|
5148
|
+
for node in self.node_identities.keys():
|
|
5149
|
+
|
|
5150
|
+
try:
|
|
5151
|
+
|
|
5152
|
+
if node in nodes:
|
|
5153
|
+
|
|
5154
|
+
self.node_identities[node] += f" {base_name}+"
|
|
5155
|
+
|
|
5156
|
+
else:
|
|
5157
|
+
|
|
5158
|
+
self.node_identities[node] += f" {base_name}-"
|
|
5159
|
+
|
|
5160
|
+
except:
|
|
5161
|
+
pass
|
|
5162
|
+
|
|
5163
|
+
|
|
5164
|
+
|
|
5165
|
+
|
|
5085
5166
|
|
|
5086
5167
|
|
|
5087
5168
|
if __name__ == "__main__":
|