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.

Files changed (31) hide show
  1. {nettracer3d-1.0.2/src/nettracer3d.egg-info → nettracer3d-1.1.3}/PKG-INFO +3 -4
  2. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/README.md +2 -3
  3. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/pyproject.toml +2 -2
  4. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/morphology.py +113 -17
  5. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/neighborhoods.py +3 -3
  6. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/nettracer.py +331 -85
  7. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/nettracer_gui.py +1060 -341
  8. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/node_draw.py +38 -59
  9. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/segmenter.py +67 -25
  10. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/segmenter_GPU.py +67 -29
  11. nettracer3d-1.1.3/src/nettracer3d/stats.py +861 -0
  12. {nettracer3d-1.0.2 → nettracer3d-1.1.3/src/nettracer3d.egg-info}/PKG-INFO +3 -4
  13. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d.egg-info/SOURCES.txt +1 -0
  14. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/LICENSE +0 -0
  15. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/setup.cfg +0 -0
  16. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/__init__.py +0 -0
  17. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/cellpose_manager.py +0 -0
  18. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/community_extractor.py +0 -0
  19. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/excelotron.py +0 -0
  20. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/modularity.py +0 -0
  21. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/network_analysis.py +0 -0
  22. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/network_draw.py +0 -0
  23. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/painting.py +0 -0
  24. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/proximity.py +0 -0
  25. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/run.py +0 -0
  26. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/simple_network.py +0 -0
  27. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d/smart_dilate.py +0 -0
  28. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d.egg-info/dependency_links.txt +0 -0
  29. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d.egg-info/entry_points.txt +0 -0
  30. {nettracer3d-1.0.2 → nettracer3d-1.1.3}/src/nettracer3d.egg-info/requires.txt +0 -0
  31. {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.0.2
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.0.2 Updates --
113
+ -- Version 1.1.3 Updates --
114
114
 
115
- * Minor fixes
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.0.2 Updates --
68
+ -- Version 1.1.3 Updates --
69
69
 
70
- * Minor fixes
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.0.2"
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
- args = [edge_array, label_array]
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) # Default to transparent (0,0,0,0)
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():