nettracer3d 1.2.4__py3-none-any.whl → 1.2.7__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.
- nettracer3d/branch_stitcher.py +9 -4
- nettracer3d/filaments.py +11 -4
- nettracer3d/modularity.py +15 -6
- nettracer3d/morphology.py +1 -1
- nettracer3d/nettracer.py +53 -155
- nettracer3d/nettracer_gui.py +218 -127
- nettracer3d/network_analysis.py +36 -48
- nettracer3d/network_draw.py +16 -15
- nettracer3d/node_draw.py +4 -4
- nettracer3d/proximity.py +36 -150
- nettracer3d/simple_network.py +28 -9
- nettracer3d/smart_dilate.py +200 -107
- nettracer3d/tutorial.py +32 -65
- {nettracer3d-1.2.4.dist-info → nettracer3d-1.2.7.dist-info}/METADATA +24 -10
- nettracer3d-1.2.7.dist-info/RECORD +29 -0
- {nettracer3d-1.2.4.dist-info → nettracer3d-1.2.7.dist-info}/licenses/LICENSE +2 -2
- nettracer3d-1.2.4.dist-info/RECORD +0 -29
- {nettracer3d-1.2.4.dist-info → nettracer3d-1.2.7.dist-info}/WHEEL +0 -0
- {nettracer3d-1.2.4.dist-info → nettracer3d-1.2.7.dist-info}/entry_points.txt +0 -0
- {nettracer3d-1.2.4.dist-info → nettracer3d-1.2.7.dist-info}/top_level.txt +0 -0
nettracer3d/network_analysis.py
CHANGED
|
@@ -601,42 +601,35 @@ def _find_centroids_old(nodes, node_list = None, down_factor = None):
|
|
|
601
601
|
|
|
602
602
|
|
|
603
603
|
def _find_centroids(nodes, node_list=None, down_factor=None):
|
|
604
|
-
"""
|
|
605
|
-
|
|
604
|
+
"""Parallel version using sum accumulation instead of storing coordinates"""
|
|
606
605
|
|
|
607
|
-
def
|
|
608
|
-
"""
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
indices_dict_chunk = {}
|
|
606
|
+
def compute_sums_in_chunk(chunk, y_offset):
|
|
607
|
+
"""Accumulate sums and counts - much less memory than storing coords"""
|
|
608
|
+
sums_dict = {}
|
|
609
|
+
counts_dict = {}
|
|
612
610
|
|
|
613
|
-
# Get all coordinates where chunk is non-zero
|
|
614
611
|
z_coords, y_coords, x_coords = np.where(chunk != 0)
|
|
615
612
|
|
|
616
613
|
if len(z_coords) == 0:
|
|
617
|
-
return
|
|
614
|
+
return sums_dict, counts_dict
|
|
618
615
|
|
|
619
|
-
# Adjust Y coordinates
|
|
620
616
|
y_coords_adjusted = y_coords + y_offset
|
|
621
|
-
|
|
622
|
-
# Get labels at these coordinates
|
|
623
617
|
labels = chunk[z_coords, y_coords, x_coords]
|
|
624
|
-
|
|
625
|
-
# Group by unique labels
|
|
626
618
|
unique_labels = np.unique(labels)
|
|
627
619
|
|
|
628
620
|
for label in unique_labels:
|
|
629
|
-
if label == 0:
|
|
621
|
+
if label == 0:
|
|
630
622
|
continue
|
|
631
623
|
mask = (labels == label)
|
|
632
|
-
#
|
|
633
|
-
|
|
634
|
-
z_coords[mask],
|
|
635
|
-
y_coords_adjusted[mask],
|
|
636
|
-
x_coords[mask]
|
|
637
|
-
)
|
|
624
|
+
# Just store sums and counts - O(1) memory per label
|
|
625
|
+
sums_dict[label] = np.array([
|
|
626
|
+
z_coords[mask].sum(dtype=np.float64),
|
|
627
|
+
y_coords_adjusted[mask].sum(dtype=np.float64),
|
|
628
|
+
x_coords[mask].sum(dtype=np.float64)
|
|
629
|
+
])
|
|
630
|
+
counts_dict[label] = mask.sum()
|
|
638
631
|
|
|
639
|
-
return
|
|
632
|
+
return sums_dict, counts_dict
|
|
640
633
|
|
|
641
634
|
def chunk_3d_array(array, num_chunks):
|
|
642
635
|
"""Split the 3D array into smaller chunks along the y-axis."""
|
|
@@ -644,49 +637,44 @@ def _find_centroids(nodes, node_list=None, down_factor=None):
|
|
|
644
637
|
return y_slices
|
|
645
638
|
|
|
646
639
|
# Handle input processing
|
|
647
|
-
if isinstance(nodes, str):
|
|
640
|
+
if isinstance(nodes, str):
|
|
648
641
|
nodes = tifffile.imread(nodes)
|
|
649
|
-
if len(np.unique(nodes)) == 2:
|
|
642
|
+
if len(np.unique(nodes)) == 2:
|
|
650
643
|
structure_3d = np.ones((3, 3, 3), dtype=int)
|
|
651
644
|
nodes, num_nodes = ndimage.label(nodes)
|
|
652
645
|
|
|
653
646
|
if down_factor is not None:
|
|
654
647
|
nodes = downsample(nodes, down_factor)
|
|
655
|
-
else:
|
|
656
|
-
down_factor = 1
|
|
657
648
|
|
|
658
|
-
|
|
649
|
+
sums_total = {}
|
|
650
|
+
counts_total = {}
|
|
659
651
|
num_cpus = mp.cpu_count()
|
|
660
652
|
|
|
661
|
-
# Chunk the 3D array along the y-axis
|
|
662
653
|
node_chunks = chunk_3d_array(nodes, num_cpus)
|
|
663
|
-
|
|
664
|
-
# Calculate Y offset for each chunk
|
|
665
654
|
chunk_sizes = [chunk.shape[1] for chunk in node_chunks]
|
|
666
655
|
y_offsets = np.cumsum([0] + chunk_sizes[:-1])
|
|
667
656
|
|
|
668
|
-
# Parallel computation using the optimized single-pass approach
|
|
669
657
|
with ThreadPoolExecutor(max_workers=num_cpus) as executor:
|
|
670
|
-
futures =
|
|
671
|
-
for
|
|
658
|
+
futures = [executor.submit(compute_sums_in_chunk, chunk, y_offset)
|
|
659
|
+
for chunk, y_offset in zip(node_chunks, y_offsets)]
|
|
672
660
|
|
|
673
661
|
for future in as_completed(futures):
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
662
|
+
sums_chunk, counts_chunk = future.result()
|
|
663
|
+
|
|
664
|
+
# Merge is now just addition - O(1) instead of vstack
|
|
665
|
+
for label in sums_chunk:
|
|
666
|
+
if label in sums_total:
|
|
667
|
+
sums_total[label] += sums_chunk[label]
|
|
668
|
+
counts_total[label] += counts_chunk[label]
|
|
679
669
|
else:
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
# Remove background label if it exists
|
|
689
|
-
centroid_dict.pop(0, None)
|
|
670
|
+
sums_total[label] = sums_chunk[label]
|
|
671
|
+
counts_total[label] = counts_chunk[label]
|
|
672
|
+
|
|
673
|
+
# Compute centroids from accumulated sums
|
|
674
|
+
centroid_dict = {
|
|
675
|
+
label: np.round(sums_total[label] / counts_total[label]).astype(int)
|
|
676
|
+
for label in sums_total if label != 0
|
|
677
|
+
}
|
|
690
678
|
|
|
691
679
|
return centroid_dict
|
|
692
680
|
|
nettracer3d/network_draw.py
CHANGED
|
@@ -264,20 +264,20 @@ def draw_network_from_centroids(nodes, network, centroids, twod_bool, directory
|
|
|
264
264
|
|
|
265
265
|
if twod_bool:
|
|
266
266
|
output_stack = output_stack[0,:,:] | output_stack[0,:,:]
|
|
267
|
-
|
|
268
|
-
if directory is None:
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if directory is not None:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
except Exception as e:
|
|
280
|
-
|
|
267
|
+
|
|
268
|
+
#if directory is None:
|
|
269
|
+
# try:
|
|
270
|
+
# tifffile.imwrite("drawn_network.tif", output_stack)
|
|
271
|
+
# except Exception as e:
|
|
272
|
+
# print("Could not save network lattice to active directory")
|
|
273
|
+
# print("Network lattice saved as drawn_network.tif")
|
|
274
|
+
|
|
275
|
+
#if directory is not None:
|
|
276
|
+
# try:
|
|
277
|
+
# tifffile.imwrite(f"{directory}/drawn_network.tif", output_stack)
|
|
278
|
+
# print(f"Network lattice saved to {directory}/drawn_network.tif")
|
|
279
|
+
#except Exception as e:
|
|
280
|
+
# print(f"Could not save network lattice to {directory}")
|
|
281
281
|
|
|
282
282
|
return output_stack
|
|
283
283
|
|
|
@@ -340,6 +340,7 @@ def draw_network_from_centroids_GPU(nodes, network, centroids, twod_bool, direct
|
|
|
340
340
|
|
|
341
341
|
output_stack = cp.asnumpy(output_stack)
|
|
342
342
|
|
|
343
|
+
"""
|
|
343
344
|
if directory is None:
|
|
344
345
|
try:
|
|
345
346
|
tifffile.imwrite("drawn_network.tif", output_stack)
|
|
@@ -353,7 +354,7 @@ def draw_network_from_centroids_GPU(nodes, network, centroids, twod_bool, direct
|
|
|
353
354
|
print(f"Network lattice saved to {directory}/drawn_network.tif")
|
|
354
355
|
except Exception as e:
|
|
355
356
|
print(f"Could not save network lattice to {directory}")
|
|
356
|
-
|
|
357
|
+
"""
|
|
357
358
|
|
|
358
359
|
if __name__ == '__main__':
|
|
359
360
|
|
nettracer3d/node_draw.py
CHANGED
|
@@ -145,10 +145,10 @@ def draw_from_centroids(nodes, num_nodes, centroids, twod_bool, directory=None):
|
|
|
145
145
|
draw_array = draw_array[0,:,:] | draw_array[1,:,:]
|
|
146
146
|
|
|
147
147
|
filename = f'{directory}/labelled_node_indices.tif' if directory else 'labelled_node_indices.tif'
|
|
148
|
-
try:
|
|
149
|
-
|
|
150
|
-
except Exception as e:
|
|
151
|
-
|
|
148
|
+
#try:
|
|
149
|
+
# tifffile.imwrite(filename, draw_array)
|
|
150
|
+
#except Exception as e:
|
|
151
|
+
# print(f"Could not save node indices to {filename}")
|
|
152
152
|
|
|
153
153
|
return draw_array
|
|
154
154
|
|
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:
|
|
226
|
+
if identity_dict[node] == 'Edge':
|
|
227
|
+
node_dict[node] = 30
|
|
228
|
+
else:
|
|
229
|
+
node_dict[node] = 100
|
|
230
|
+
except:
|
|
231
|
+
node_dict[node] = 100
|
|
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:
|