nettracer3d 1.0.2__tar.gz → 1.1.3__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-1.0.2/src/nettracer3d.egg-info → nettracer3d-1.1.3}/PKG-INFO +3 -4
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/README.md +2 -3
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/pyproject.toml +2 -2
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/morphology.py +113 -17
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/neighborhoods.py +3 -3
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/nettracer.py +331 -85
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/nettracer_gui.py +1060 -341
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/node_draw.py +38 -59
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/segmenter.py +67 -25
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/segmenter_GPU.py +67 -29
- nettracer3d-1.1.3/src/nettracer3d/stats.py +861 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3/src/nettracer3d.egg-info}/PKG-INFO +3 -4
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d.egg-info/SOURCES.txt +1 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/LICENSE +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/setup.cfg +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/__init__.py +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/cellpose_manager.py +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/community_extractor.py +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/excelotron.py +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/modularity.py +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/network_analysis.py +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/network_draw.py +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/painting.py +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/proximity.py +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/run.py +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/simple_network.py +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/smart_dilate.py +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d.egg-info/dependency_links.txt +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d.egg-info/entry_points.txt +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d.egg-info/requires.txt +0 -0
- {nettracer3d-1.0.2 → nettracer3d-1.1.3}/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: 1.
|
|
3
|
+
Version: 1.1.3
|
|
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/
|
|
@@ -110,7 +110,6 @@ McLaughlin, L., Zhang, B., Sharma, S. et al. Three dimensional multiscalar neuro
|
|
|
110
110
|
|
|
111
111
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
112
112
|
|
|
113
|
-
-- Version 1.
|
|
113
|
+
-- Version 1.1.3 Updates --
|
|
114
114
|
|
|
115
|
-
*
|
|
116
|
-
* Added ability to generate violin plots using the table generated from merging node identities, showing the relative expression of markers for multiple channels for the nodes belonging to some channel or community/neighborhood
|
|
115
|
+
* Some minor text adjustments
|
|
@@ -65,7 +65,6 @@ McLaughlin, L., Zhang, B., Sharma, S. et al. Three dimensional multiscalar neuro
|
|
|
65
65
|
|
|
66
66
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
67
67
|
|
|
68
|
-
-- Version 1.
|
|
68
|
+
-- Version 1.1.3 Updates --
|
|
69
69
|
|
|
70
|
-
*
|
|
71
|
-
* Added ability to generate violin plots using the table generated from merging node identities, showing the relative expression of markers for multiple channels for the nodes belonging to some channel or community/neighborhood
|
|
70
|
+
* Some minor text adjustments
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nettracer3d"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.1.3"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="Liam McLaughlin", email="liamm@wustl.edu" },
|
|
6
6
|
]
|
|
@@ -37,7 +37,7 @@ classifiers = [
|
|
|
37
37
|
# GPU options (choose one)
|
|
38
38
|
CUDA11 = ["cupy-cuda11x"]
|
|
39
39
|
CUDA12 = ["cupy-cuda12x"]
|
|
40
|
-
cupy = ["cupy"]
|
|
40
|
+
cupy = ["cupy"]
|
|
41
41
|
|
|
42
42
|
# Features
|
|
43
43
|
cellpose = ["cellpose[GUI]"]
|
|
@@ -65,7 +65,7 @@ def reslice_3d_array(args):
|
|
|
65
65
|
return resliced_array
|
|
66
66
|
|
|
67
67
|
|
|
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):
|
|
68
|
+
def _get_node_edge_dict(label_array, edge_array, label, dilate_xy, dilate_z, cores = 0, search = 0, fastdil = False, length = False, xy_scale = 1, z_scale = 1):
|
|
69
69
|
"""Internal method used for the secondary algorithm to find pixel involvement of nodes around an edge."""
|
|
70
70
|
|
|
71
71
|
# Create a boolean mask where elements with the specified label are True
|
|
@@ -74,24 +74,25 @@ def _get_node_edge_dict(label_array, edge_array, label, dilate_xy, dilate_z, cor
|
|
|
74
74
|
|
|
75
75
|
if cores == 0: #For getting the volume of objects. Cores presumes you want the 'core' included in the interaction.
|
|
76
76
|
edge_array = edge_array * dil_array # Filter the edges by the label in question
|
|
77
|
-
label_array = np.count_nonzero(dil_array)
|
|
78
|
-
edge_array = np.count_nonzero(edge_array) # For getting the interacting skeleton
|
|
79
|
-
|
|
80
77
|
elif cores == 1: #Cores being 1 presumes you do not want to 'core' included in the interaction
|
|
81
78
|
label_array = dil_array - label_array
|
|
82
79
|
edge_array = edge_array * label_array
|
|
83
|
-
label_array = np.count_nonzero(label_array)
|
|
84
|
-
edge_array = np.count_nonzero(edge_array) # For getting the interacting skeleton
|
|
85
|
-
|
|
86
80
|
elif cores == 2: #Presumes you want skeleton within the core but to only 'count' the stuff around the core for volumes... because of imaging artifacts, perhaps
|
|
87
81
|
edge_array = edge_array * dil_array
|
|
88
82
|
label_array = dil_array - label_array
|
|
89
|
-
label_array = np.count_nonzero(label_array)
|
|
90
|
-
edge_array = np.count_nonzero(edge_array) # For getting the interacting skeleton
|
|
91
83
|
|
|
84
|
+
label_count = np.count_nonzero(label_array) * xy_scale * xy_scale * z_scale
|
|
92
85
|
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
if not length:
|
|
87
|
+
edge_count = np.count_nonzero(edge_array) * xy_scale * xy_scale * z_scale # For getting the interacting skeleton
|
|
88
|
+
else:
|
|
89
|
+
edge_count = calculate_skeleton_lengths(
|
|
90
|
+
edge_array,
|
|
91
|
+
xy_scale=xy_scale,
|
|
92
|
+
z_scale=z_scale
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
args = [edge_count, label_count]
|
|
95
96
|
|
|
96
97
|
return args
|
|
97
98
|
|
|
@@ -115,7 +116,7 @@ def process_label(args):
|
|
|
115
116
|
|
|
116
117
|
|
|
117
118
|
|
|
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):
|
|
119
|
+
def create_node_dictionary(nodes, edges, num_nodes, dilate_xy, dilate_z, cores=0, search = 0, fastdil = False, length = False, xy_scale = 1, z_scale = 1):
|
|
119
120
|
"""Modified to pre-compute all bounding boxes using find_objects"""
|
|
120
121
|
node_dict = {}
|
|
121
122
|
array_shape = nodes.shape
|
|
@@ -135,20 +136,20 @@ def create_node_dictionary(nodes, edges, num_nodes, dilate_xy, dilate_z, cores=0
|
|
|
135
136
|
# Process results in parallel
|
|
136
137
|
for label, sub_nodes, sub_edges in results:
|
|
137
138
|
executor.submit(create_dict_entry, node_dict, label, sub_nodes, sub_edges,
|
|
138
|
-
dilate_xy, dilate_z, cores, search, fastdil, xy_scale, z_scale)
|
|
139
|
+
dilate_xy, dilate_z, cores, search, fastdil, length, xy_scale, z_scale)
|
|
139
140
|
|
|
140
141
|
return node_dict
|
|
141
142
|
|
|
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):
|
|
143
|
+
def create_dict_entry(node_dict, label, sub_nodes, sub_edges, dilate_xy, dilate_z, cores = 0, search = 0, fastdil = False, length = False, xy_scale = 1, z_scale = 1):
|
|
143
144
|
"""Internal method used for the secondary algorithm to pass around args in parallel."""
|
|
144
145
|
|
|
145
146
|
if label is None:
|
|
146
147
|
pass
|
|
147
148
|
else:
|
|
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)
|
|
149
|
+
node_dict[label] = _get_node_edge_dict(sub_nodes, sub_edges, label, dilate_xy, dilate_z, cores = cores, search = search, fastdil = fastdil, length = length, xy_scale = xy_scale, z_scale = z_scale)
|
|
149
150
|
|
|
150
151
|
|
|
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):
|
|
152
|
+
def quantify_edge_node(nodes, edges, search = 0, xy_scale = 1, z_scale = 1, cores = 0, resize = None, save = True, skele = False, length = False, auto = True, fastdil = False):
|
|
152
153
|
|
|
153
154
|
def save_dubval_dict(dict, index_name, val1name, val2name, filename):
|
|
154
155
|
|
|
@@ -168,6 +169,9 @@ def quantify_edge_node(nodes, edges, search = 0, xy_scale = 1, z_scale = 1, core
|
|
|
168
169
|
edges = tifffile.imread(edges)
|
|
169
170
|
|
|
170
171
|
if skele:
|
|
172
|
+
if auto:
|
|
173
|
+
edges = nettracer.skeletonize(edges)
|
|
174
|
+
edges = nettracer.fill_holes_3d(edges)
|
|
171
175
|
edges = nettracer.skeletonize(edges)
|
|
172
176
|
else:
|
|
173
177
|
edges = nettracer.binarize(edges)
|
|
@@ -188,7 +192,7 @@ def quantify_edge_node(nodes, edges, search = 0, xy_scale = 1, z_scale = 1, core
|
|
|
188
192
|
dilate_xy, dilate_z = 0, 0
|
|
189
193
|
|
|
190
194
|
|
|
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.
|
|
195
|
+
edge_quants = create_node_dictionary(nodes, edges, num_nodes, dilate_xy, dilate_z, cores = cores, search = search, fastdil = fastdil, length = length, xy_scale = xy_scale, z_scale = z_scale) #Find which edges connect which nodes and put them in a dictionary.
|
|
192
196
|
|
|
193
197
|
if save:
|
|
194
198
|
|
|
@@ -199,6 +203,98 @@ def quantify_edge_node(nodes, edges, search = 0, xy_scale = 1, z_scale = 1, core
|
|
|
199
203
|
return edge_quants
|
|
200
204
|
|
|
201
205
|
|
|
206
|
+
# Helper methods for counting the lens of skeletons:
|
|
207
|
+
|
|
208
|
+
def calculate_skeleton_lengths(skeleton_binary, xy_scale=1.0, z_scale=1.0, skeleton_coords = None):
|
|
209
|
+
"""
|
|
210
|
+
Calculate total length of all skeletons in a 3D binary image.
|
|
211
|
+
|
|
212
|
+
skeleton_binary: 3D boolean array where True = skeleton voxel
|
|
213
|
+
xy_scale, z_scale: physical units per voxel
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
if skeleton_coords is None:
|
|
217
|
+
# Find all skeleton voxels
|
|
218
|
+
skeleton_coords = np.argwhere(skeleton_binary)
|
|
219
|
+
shape = skeleton_binary.shape
|
|
220
|
+
else:
|
|
221
|
+
shape = skeleton_binary #Very professional stuff
|
|
222
|
+
|
|
223
|
+
if len(skeleton_coords) == 0:
|
|
224
|
+
return 0.0
|
|
225
|
+
|
|
226
|
+
# Create a mapping from coordinates to indices for fast lookup
|
|
227
|
+
coord_to_idx = {tuple(coord): idx for idx, coord in enumerate(skeleton_coords)}
|
|
228
|
+
|
|
229
|
+
# Build adjacency graph
|
|
230
|
+
adjacency_list = build_adjacency_graph(skeleton_coords, coord_to_idx, shape)
|
|
231
|
+
|
|
232
|
+
# Calculate lengths using scaled distances
|
|
233
|
+
total_length = calculate_graph_length(skeleton_coords, adjacency_list, xy_scale, z_scale)
|
|
234
|
+
|
|
235
|
+
return total_length
|
|
236
|
+
|
|
237
|
+
def build_adjacency_graph(skeleton_coords, coord_to_idx, shape):
|
|
238
|
+
"""Build adjacency list for skeleton voxels using 26-connectivity."""
|
|
239
|
+
adjacency_list = [[] for _ in range(len(skeleton_coords))]
|
|
240
|
+
|
|
241
|
+
# 26-connectivity offsets (all combinations of -1,0,1 except 0,0,0)
|
|
242
|
+
offsets = []
|
|
243
|
+
for dz in [-1, 0, 1]:
|
|
244
|
+
for dy in [-1, 0, 1]:
|
|
245
|
+
for dx in [-1, 0, 1]:
|
|
246
|
+
if not (dx == 0 and dy == 0 and dz == 0):
|
|
247
|
+
offsets.append((dz, dy, dx))
|
|
248
|
+
|
|
249
|
+
for idx, coord in enumerate(skeleton_coords):
|
|
250
|
+
z, y, x = coord
|
|
251
|
+
|
|
252
|
+
# Check all 26 neighbors
|
|
253
|
+
for dz, dy, dx in offsets:
|
|
254
|
+
nz, ny, nx = z + dz, y + dy, x + dx
|
|
255
|
+
|
|
256
|
+
# Check bounds
|
|
257
|
+
if (0 <= nz < shape[0] and
|
|
258
|
+
0 <= ny < shape[1] and
|
|
259
|
+
0 <= nx < shape[2]):
|
|
260
|
+
|
|
261
|
+
neighbor_coord = (nz, ny, nx)
|
|
262
|
+
if neighbor_coord in coord_to_idx:
|
|
263
|
+
neighbor_idx = coord_to_idx[neighbor_coord]
|
|
264
|
+
adjacency_list[idx].append(neighbor_idx)
|
|
265
|
+
|
|
266
|
+
return adjacency_list
|
|
267
|
+
|
|
268
|
+
def calculate_graph_length(skeleton_coords, adjacency_list, xy_scale, z_scale):
|
|
269
|
+
"""Calculate total length by summing distances between adjacent voxels."""
|
|
270
|
+
total_length = 0.0
|
|
271
|
+
processed_edges = set()
|
|
272
|
+
|
|
273
|
+
for idx, neighbors in enumerate(adjacency_list):
|
|
274
|
+
coord = skeleton_coords[idx]
|
|
275
|
+
|
|
276
|
+
for neighbor_idx in neighbors:
|
|
277
|
+
# Avoid double-counting edges
|
|
278
|
+
edge = tuple(sorted([idx, neighbor_idx]))
|
|
279
|
+
if edge in processed_edges:
|
|
280
|
+
continue
|
|
281
|
+
processed_edges.add(edge)
|
|
282
|
+
|
|
283
|
+
neighbor_coord = skeleton_coords[neighbor_idx]
|
|
284
|
+
|
|
285
|
+
# Calculate scaled distance
|
|
286
|
+
dz = (coord[0] - neighbor_coord[0]) * z_scale
|
|
287
|
+
dy = (coord[1] - neighbor_coord[1]) * xy_scale
|
|
288
|
+
dx = (coord[2] - neighbor_coord[2]) * xy_scale
|
|
289
|
+
|
|
290
|
+
distance = np.sqrt(dx*dx + dy*dy + dz*dz)
|
|
291
|
+
total_length += distance
|
|
292
|
+
|
|
293
|
+
return total_length
|
|
294
|
+
|
|
295
|
+
# End helper methods
|
|
296
|
+
|
|
297
|
+
|
|
202
298
|
|
|
203
299
|
def calculate_voxel_volumes(array, xy_scale=1, z_scale=1):
|
|
204
300
|
"""
|
|
@@ -793,7 +793,7 @@ def create_community_heatmap(community_intensity, node_community, node_centroids
|
|
|
793
793
|
return np.array([r, g, b], dtype=np.uint8)
|
|
794
794
|
|
|
795
795
|
# Create lookup table for RGB colors
|
|
796
|
-
max_label = max(max(labeled_array.flat), max(node_to_community_intensity.keys()) if node_to_community_intensity else 0)
|
|
796
|
+
max_label = int(max(max(labeled_array.flat), max(node_to_community_intensity.keys()) if node_to_community_intensity else 0))
|
|
797
797
|
color_lut = np.zeros((max_label + 1, 3), dtype=np.uint8) # Default to black (0,0,0)
|
|
798
798
|
|
|
799
799
|
# Fill lookup table with RGB colors based on community intensity
|
|
@@ -1036,8 +1036,8 @@ def create_node_heatmap(node_intensity, node_centroids, shape=None, is_3d=True,
|
|
|
1036
1036
|
|
|
1037
1037
|
# Modified usage in your main function:
|
|
1038
1038
|
# Create lookup table for RGBA colors (note the 4 channels now)
|
|
1039
|
-
max_label = max(max(labeled_array.flat), max(node_to_intensity.keys()) if node_to_intensity else 0)
|
|
1040
|
-
color_lut = np.zeros((max_label + 1, 4), dtype=np.uint8)
|
|
1039
|
+
max_label = int(max(max(labeled_array.flat), max(node_to_intensity.keys()) if node_to_intensity else 0))
|
|
1040
|
+
color_lut = np.zeros((max_label + 1, 4), dtype=np.uint8)
|
|
1041
1041
|
|
|
1042
1042
|
# Fill lookup table with RGBA colors based on intensity
|
|
1043
1043
|
for node_id, intensity in node_to_intensity.items():
|