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/proximity.py
CHANGED
|
@@ -86,7 +86,7 @@ def _get_node_node_dict(label_array, label, dilate_xy, dilate_z, fastdil = False
|
|
|
86
86
|
def process_label(args):
|
|
87
87
|
"""Modified to use pre-computed bounding boxes instead of argwhere"""
|
|
88
88
|
nodes, label, dilate_xy, dilate_z, array_shape, bounding_boxes = args
|
|
89
|
-
print(f"Processing node {label}")
|
|
89
|
+
#print(f"Processing node {label}")
|
|
90
90
|
|
|
91
91
|
# Get the pre-computed bounding box for this label
|
|
92
92
|
slice_obj = bounding_boxes[int(label)-1] # -1 because label numbers start at 1
|
|
@@ -213,83 +213,9 @@ def populate_array(centroids, clip=False, shape = None):
|
|
|
213
213
|
else:
|
|
214
214
|
return array
|
|
215
215
|
|
|
216
|
-
def
|
|
217
|
-
"""Process a chunk of neighbor indices for centroids mode"""
|
|
218
|
-
chunk_data, idx_to_node, query_indices, tree, points, max_neighbors = args
|
|
219
|
-
output = []
|
|
220
|
-
|
|
221
|
-
for i, neighbors in chunk_data:
|
|
222
|
-
query_idx = query_indices[i]
|
|
223
|
-
query_value = idx_to_node[query_idx]
|
|
224
|
-
query_point = points[query_idx]
|
|
225
|
-
|
|
226
|
-
# Filter out self-reference
|
|
227
|
-
filtered_neighbors = [n for n in neighbors if n != query_idx]
|
|
228
|
-
|
|
229
|
-
# If max_neighbors is specified and we have more neighbors than allowed
|
|
230
|
-
if max_neighbors is not None and len(filtered_neighbors) > max_neighbors:
|
|
231
|
-
# Use KDTree to get distances efficiently - query for more than we need
|
|
232
|
-
# to ensure we get the exact closest ones
|
|
233
|
-
k = min(len(filtered_neighbors), max_neighbors + 1) # +1 in case query point is included
|
|
234
|
-
distances, indices = tree.query(query_point, k=k)
|
|
235
|
-
|
|
236
|
-
# Filter out self and limit to max_neighbors
|
|
237
|
-
selected_neighbors = []
|
|
238
|
-
for dist, idx in zip(distances, indices):
|
|
239
|
-
if idx != query_idx and idx in filtered_neighbors:
|
|
240
|
-
selected_neighbors.append(idx)
|
|
241
|
-
if len(selected_neighbors) >= max_neighbors:
|
|
242
|
-
break
|
|
243
|
-
|
|
244
|
-
filtered_neighbors = selected_neighbors
|
|
245
|
-
|
|
246
|
-
# Add all selected neighbors to output
|
|
247
|
-
for neighbor_idx in filtered_neighbors:
|
|
248
|
-
neighbor_value = idx_to_node[neighbor_idx]
|
|
249
|
-
output.append([query_value, neighbor_value, 0])
|
|
250
|
-
|
|
251
|
-
return output
|
|
252
|
-
|
|
253
|
-
def _process_chunk_array(args):
|
|
254
|
-
"""Process a chunk of neighbor indices for array mode"""
|
|
255
|
-
chunk_data, array, point_tuples, query_indices, tree, points, max_neighbors = args
|
|
256
|
-
output = []
|
|
257
|
-
|
|
258
|
-
for i, neighbors in chunk_data:
|
|
259
|
-
query_idx = query_indices[i]
|
|
260
|
-
query_value = array[point_tuples[query_idx]]
|
|
261
|
-
query_point = points[query_idx]
|
|
262
|
-
|
|
263
|
-
# Filter out self-reference
|
|
264
|
-
filtered_neighbors = [n for n in neighbors if n != query_idx]
|
|
265
|
-
|
|
266
|
-
# If max_neighbors is specified and we have more neighbors than allowed
|
|
267
|
-
if max_neighbors is not None and len(filtered_neighbors) > max_neighbors:
|
|
268
|
-
# Use KDTree to get distances efficiently - query for more than we need
|
|
269
|
-
# to ensure we get the exact closest ones
|
|
270
|
-
k = min(len(filtered_neighbors), max_neighbors + 1) # +1 in case query point is included
|
|
271
|
-
distances, indices = tree.query(query_point, k=k)
|
|
272
|
-
|
|
273
|
-
# Filter out self and limit to max_neighbors
|
|
274
|
-
selected_neighbors = []
|
|
275
|
-
for dist, idx in zip(distances, indices):
|
|
276
|
-
if idx != query_idx and idx in filtered_neighbors:
|
|
277
|
-
selected_neighbors.append(idx)
|
|
278
|
-
if len(selected_neighbors) >= max_neighbors:
|
|
279
|
-
break
|
|
280
|
-
|
|
281
|
-
filtered_neighbors = selected_neighbors
|
|
282
|
-
|
|
283
|
-
# Add all selected neighbors to output
|
|
284
|
-
for neighbor_idx in filtered_neighbors:
|
|
285
|
-
neighbor_value = array[point_tuples[neighbor_idx]]
|
|
286
|
-
output.append([query_value, neighbor_value, 0])
|
|
287
|
-
|
|
288
|
-
return output
|
|
289
|
-
|
|
290
|
-
def find_neighbors_kdtree(radius, centroids=None, array=None, targets=None, n_jobs=None, chunk_size=None, max_neighbors=None):
|
|
216
|
+
def find_neighbors_kdtree(radius, centroids=None, array=None, targets=None, max_neighbors=None):
|
|
291
217
|
"""
|
|
292
|
-
Find neighbors using KDTree
|
|
218
|
+
Find neighbors using KDTree.
|
|
293
219
|
|
|
294
220
|
Parameters:
|
|
295
221
|
-----------
|
|
@@ -301,10 +227,6 @@ def find_neighbors_kdtree(radius, centroids=None, array=None, targets=None, n_jo
|
|
|
301
227
|
Array to search for nonzero points
|
|
302
228
|
targets : list, optional
|
|
303
229
|
Specific targets to query for neighbors
|
|
304
|
-
n_jobs : int, optional
|
|
305
|
-
Number of parallel jobs. If None, uses cpu_count(). Set to 1 to disable parallelization.
|
|
306
|
-
chunk_size : int, optional
|
|
307
|
-
Size of chunks for parallel processing. If None, auto-calculated based on data size.
|
|
308
230
|
max_neighbors : int, optional
|
|
309
231
|
Maximum number of nearest neighbors to return per query point within the radius.
|
|
310
232
|
If None, returns all neighbors within radius (original behavior).
|
|
@@ -378,81 +300,45 @@ def find_neighbors_kdtree(radius, centroids=None, array=None, targets=None, n_jo
|
|
|
378
300
|
|
|
379
301
|
print("Sorting Through Output...")
|
|
380
302
|
|
|
381
|
-
#
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
#
|
|
395
|
-
|
|
303
|
+
# Sequential processing
|
|
304
|
+
output = []
|
|
305
|
+
for i, neighbors in enumerate(neighbor_indices):
|
|
306
|
+
query_idx = query_indices[i]
|
|
307
|
+
query_point = points[query_idx]
|
|
308
|
+
|
|
309
|
+
# Filter out self-reference
|
|
310
|
+
filtered_neighbors = [n for n in neighbors if n != query_idx]
|
|
311
|
+
|
|
312
|
+
# If max_neighbors is specified and we have more neighbors than allowed
|
|
313
|
+
if max_neighbors is not None and len(filtered_neighbors) > max_neighbors:
|
|
314
|
+
# Use KDTree to get distances efficiently - query for more than we need
|
|
315
|
+
# to ensure we get the exact closest ones
|
|
316
|
+
k = min(len(filtered_neighbors), max_neighbors + 1) # +1 in case query point is included
|
|
317
|
+
distances, indices = tree.query(query_point, k=k)
|
|
396
318
|
|
|
397
|
-
#
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
# Filter out self and limit to max_neighbors
|
|
405
|
-
selected_neighbors = []
|
|
406
|
-
for dist, idx in zip(distances, indices):
|
|
407
|
-
if idx != query_idx and idx in filtered_neighbors:
|
|
408
|
-
selected_neighbors.append(idx)
|
|
409
|
-
if len(selected_neighbors) >= max_neighbors:
|
|
410
|
-
break
|
|
411
|
-
|
|
412
|
-
filtered_neighbors = selected_neighbors
|
|
319
|
+
# Filter out self and limit to max_neighbors
|
|
320
|
+
selected_neighbors = []
|
|
321
|
+
for dist, idx in zip(distances, indices):
|
|
322
|
+
if idx != query_idx and idx in filtered_neighbors:
|
|
323
|
+
selected_neighbors.append(idx)
|
|
324
|
+
if len(selected_neighbors) >= max_neighbors:
|
|
325
|
+
break
|
|
413
326
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
for neighbor_idx in filtered_neighbors:
|
|
418
|
-
neighbor_value = idx_to_node[neighbor_idx]
|
|
419
|
-
output.append([query_value, neighbor_value, 0])
|
|
420
|
-
else:
|
|
421
|
-
query_value = array[point_tuples[query_idx]]
|
|
422
|
-
for neighbor_idx in filtered_neighbors:
|
|
423
|
-
neighbor_value = array[point_tuples[neighbor_idx]]
|
|
424
|
-
output.append([query_value, neighbor_value, 0])
|
|
425
|
-
return output
|
|
426
|
-
|
|
427
|
-
# Parallel processing
|
|
428
|
-
if chunk_size is None:
|
|
429
|
-
# Auto-calculate chunk size: aim for ~4x more chunks than processes
|
|
430
|
-
chunk_size = max(1, len(neighbor_indices) // (n_jobs * 4))
|
|
431
|
-
|
|
432
|
-
# Create chunks of (index, neighbors) pairs
|
|
433
|
-
chunks = []
|
|
434
|
-
for i in range(0, len(neighbor_indices), chunk_size):
|
|
435
|
-
chunk = [(j, neighbor_indices[j]) for j in range(i, min(i + chunk_size, len(neighbor_indices)))]
|
|
436
|
-
chunks.append(chunk)
|
|
437
|
-
|
|
438
|
-
# Process chunks in parallel
|
|
439
|
-
with Pool(processes=n_jobs) as pool:
|
|
327
|
+
filtered_neighbors = selected_neighbors
|
|
328
|
+
|
|
329
|
+
# Process the selected neighbors
|
|
440
330
|
if centroids:
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
331
|
+
query_value = idx_to_node[query_idx]
|
|
332
|
+
for neighbor_idx in filtered_neighbors:
|
|
333
|
+
neighbor_value = idx_to_node[neighbor_idx]
|
|
334
|
+
output.append([query_value, neighbor_value, 0])
|
|
444
335
|
else:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
# Flatten results
|
|
450
|
-
output = []
|
|
451
|
-
for chunk_result in chunk_results:
|
|
452
|
-
output.extend(chunk_result)
|
|
336
|
+
query_value = array[point_tuples[query_idx]]
|
|
337
|
+
for neighbor_idx in filtered_neighbors:
|
|
338
|
+
neighbor_value = array[point_tuples[neighbor_idx]]
|
|
339
|
+
output.append([query_value, neighbor_value, 0])
|
|
453
340
|
|
|
454
341
|
print("Organizing Network...")
|
|
455
|
-
|
|
456
342
|
|
|
457
343
|
return output
|
|
458
344
|
|
nettracer3d/simple_network.py
CHANGED
|
@@ -119,7 +119,7 @@ def geometric_positions(centroids, shape):
|
|
|
119
119
|
return xy_pos, z_pos
|
|
120
120
|
|
|
121
121
|
|
|
122
|
-
def show_simple_network(excel_file_path, geometric = False, geo_info = None, directory = None):
|
|
122
|
+
def show_simple_network(excel_file_path, geometric = False, geo_info = None, directory = None, show_labels = True):
|
|
123
123
|
|
|
124
124
|
if type(excel_file_path) == str:
|
|
125
125
|
master_list = read_excel_to_lists(excel_file_path)
|
|
@@ -142,11 +142,11 @@ def show_simple_network(excel_file_path, geometric = False, geo_info = None, dir
|
|
|
142
142
|
|
|
143
143
|
pos, z_pos = geometric_positions(geo_info[0], geo_info[1])
|
|
144
144
|
node_sizes_list = [z_pos[node] for node in G.nodes()]
|
|
145
|
-
nx.draw(G, pos, with_labels=
|
|
145
|
+
nx.draw(G, pos, with_labels=show_labels, font_color='black', font_weight='bold', node_size= node_sizes_list, alpha=0.8, font_size = 12)
|
|
146
146
|
else:
|
|
147
147
|
# Visualize the graph with different edge colors for each community
|
|
148
148
|
pos = nx.spring_layout(G, iterations = 15)
|
|
149
|
-
nx.draw(G, pos, with_labels=
|
|
149
|
+
nx.draw(G, pos, with_labels=show_labels, font_color='red', font_weight='bold', node_size=10)
|
|
150
150
|
|
|
151
151
|
if directory is not None:
|
|
152
152
|
plt.savefig(f'{directory}/network_plot.png')
|
|
@@ -154,7 +154,7 @@ def show_simple_network(excel_file_path, geometric = False, geo_info = None, dir
|
|
|
154
154
|
plt.show()
|
|
155
155
|
|
|
156
156
|
|
|
157
|
-
def show_identity_network(excel_file_path, node_identities, geometric=False, geo_info=None, directory=None):
|
|
157
|
+
def show_identity_network(excel_file_path, node_identities, geometric=False, geo_info=None, directory=None, show_labels = True):
|
|
158
158
|
if type(node_identities) == str:
|
|
159
159
|
# Read the Excel file into a DataFrame
|
|
160
160
|
df = pd.read_excel(node_identities)
|
|
@@ -220,9 +220,16 @@ def show_identity_network(excel_file_path, node_identities, geometric=False, geo
|
|
|
220
220
|
color_map = dict(zip(unique_categories, colors))
|
|
221
221
|
|
|
222
222
|
# Node size handling
|
|
223
|
-
node_dict = {
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
node_dict = {}
|
|
224
|
+
for node in G.nodes():
|
|
225
|
+
try: #Perhaps remove this
|
|
226
|
+
if identity_dict[node] == 'Edge':
|
|
227
|
+
node_dict[node] = 10
|
|
228
|
+
else:
|
|
229
|
+
node_dict[node] = 10
|
|
230
|
+
except:
|
|
231
|
+
node_dict[node] = 10
|
|
232
|
+
|
|
226
233
|
if geometric:
|
|
227
234
|
# Handle geometric positioning
|
|
228
235
|
for node in list(G.nodes()):
|
|
@@ -244,14 +251,26 @@ def show_identity_network(excel_file_path, node_identities, geometric=False, geo
|
|
|
244
251
|
graph_ax = plt.gca()
|
|
245
252
|
|
|
246
253
|
# Draw the network with enhanced font styling
|
|
247
|
-
|
|
248
|
-
|
|
254
|
+
misc = False
|
|
255
|
+
node_colors = []
|
|
256
|
+
for node in G.nodes():
|
|
257
|
+
try:
|
|
258
|
+
node_colors.append(color_map[identity_dict[node]])
|
|
259
|
+
except:
|
|
260
|
+
misc = True
|
|
261
|
+
node_colors.append((1, 1, 1))
|
|
262
|
+
|
|
263
|
+
#node_colors = [color_map[identity_dict[node]] for node in G.nodes()]
|
|
264
|
+
nx.draw(G, pos, ax=graph_ax, with_labels=show_labels, font_color='black',
|
|
249
265
|
font_weight='bold', node_size=node_sizes_list,
|
|
250
266
|
node_color=node_colors, alpha=0.8, font_size=11, font_family='sans-serif')
|
|
251
267
|
|
|
252
268
|
# Create custom legend with multiple columns if needed
|
|
253
269
|
legend_handles = [Patch(color=color, label=category)
|
|
254
270
|
for category, color in color_map.items()]
|
|
271
|
+
|
|
272
|
+
if misc:
|
|
273
|
+
legend_handles.append(Patch(color = (1, 1, 1,), label = 'Unassigned'))
|
|
255
274
|
|
|
256
275
|
# Adjust number of columns based on number of categories
|
|
257
276
|
if len(unique_categories) > 20:
|