nettracer3d 0.2.6__py3-none-any.whl → 0.2.8__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/community_extractor.py +237 -0
- nettracer3d/hub_getter.py +1 -1
- nettracer3d/modularity.py +263 -19
- nettracer3d/morphology.py +149 -7
- nettracer3d/nettracer.py +573 -52
- nettracer3d/nettracer_gui.py +1850 -210
- nettracer3d/network_analysis.py +155 -30
- nettracer3d/node_draw.py +3 -1
- nettracer3d/proximity.py +66 -6
- nettracer3d/simple_network.py +82 -72
- {nettracer3d-0.2.6.dist-info → nettracer3d-0.2.8.dist-info}/METADATA +1 -1
- nettracer3d-0.2.8.dist-info/RECORD +18 -0
- nettracer3d-0.2.6.dist-info/RECORD +0 -18
- {nettracer3d-0.2.6.dist-info → nettracer3d-0.2.8.dist-info}/LICENSE +0 -0
- {nettracer3d-0.2.6.dist-info → nettracer3d-0.2.8.dist-info}/WHEEL +0 -0
- {nettracer3d-0.2.6.dist-info → nettracer3d-0.2.8.dist-info}/top_level.txt +0 -0
nettracer3d/network_analysis.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
import networkx as nx
|
|
3
|
+
import json
|
|
3
4
|
import tifffile
|
|
4
5
|
import numpy as np
|
|
5
6
|
from networkx.algorithms import community
|
|
@@ -36,6 +37,52 @@ def compute_centroid(binary_stack, label):
|
|
|
36
37
|
|
|
37
38
|
return centroid
|
|
38
39
|
|
|
40
|
+
def create_bar_graph(data_dict, title, x_label, y_label, directory=None):
|
|
41
|
+
"""
|
|
42
|
+
Create a bar graph from a dictionary where keys are bar names and values are heights.
|
|
43
|
+
|
|
44
|
+
Parameters:
|
|
45
|
+
data_dict (dict): Dictionary with bar names as keys and heights as values
|
|
46
|
+
title (str): Title of the graph
|
|
47
|
+
x_label (str): Label for x-axis
|
|
48
|
+
y_label (str): Label for y-axis
|
|
49
|
+
directory (str, optional): Directory path to save the plot. If None, plot is not saved
|
|
50
|
+
"""
|
|
51
|
+
import matplotlib.pyplot as plt
|
|
52
|
+
|
|
53
|
+
# Create figure and axis
|
|
54
|
+
plt.figure(figsize=(10, 6))
|
|
55
|
+
|
|
56
|
+
# Create bars
|
|
57
|
+
plt.bar(list(data_dict.keys()), list(data_dict.values()))
|
|
58
|
+
|
|
59
|
+
# Add labels and title
|
|
60
|
+
plt.title(title)
|
|
61
|
+
plt.xlabel(x_label)
|
|
62
|
+
plt.ylabel(y_label)
|
|
63
|
+
|
|
64
|
+
# Rotate x-axis labels if there are many bars
|
|
65
|
+
plt.xticks(rotation=45, ha='right')
|
|
66
|
+
|
|
67
|
+
# Adjust layout to prevent label cutoff
|
|
68
|
+
plt.tight_layout()
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
|
|
72
|
+
# Save plot if directory is specified
|
|
73
|
+
if directory:
|
|
74
|
+
plt.savefig(f"{directory}/bar_graph.png")
|
|
75
|
+
|
|
76
|
+
except:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
|
|
81
|
+
# Display the plot
|
|
82
|
+
plt.show()
|
|
83
|
+
except:
|
|
84
|
+
pass
|
|
85
|
+
|
|
39
86
|
def open_network(excel_file_path):
|
|
40
87
|
"""opens an unweighted network from the network excel file"""
|
|
41
88
|
|
|
@@ -59,6 +106,25 @@ def open_network(excel_file_path):
|
|
|
59
106
|
|
|
60
107
|
def read_excel_to_lists(file_path, sheet_name=0):
|
|
61
108
|
"""Convert a pd dataframe to lists. Handles both .xlsx and .csv files"""
|
|
109
|
+
def load_json_to_list(filename):
|
|
110
|
+
with open(filename, 'r') as f:
|
|
111
|
+
data = json.load(f)
|
|
112
|
+
|
|
113
|
+
# Convert only numeric strings to integers, leave other strings as is
|
|
114
|
+
converted_data = [[],[],[]]
|
|
115
|
+
for i in data[0]:
|
|
116
|
+
try:
|
|
117
|
+
converted_data[0].append(int(data[0][i]))
|
|
118
|
+
converted_data[1].append(int(data[1][i]))
|
|
119
|
+
try:
|
|
120
|
+
converted_data[2].append(int(data[2][i]))
|
|
121
|
+
except IndexError:
|
|
122
|
+
converted_data[2].append(0)
|
|
123
|
+
except ValueError:
|
|
124
|
+
converted_data[k] = v
|
|
125
|
+
|
|
126
|
+
return converted_data
|
|
127
|
+
|
|
62
128
|
if type(file_path) == str:
|
|
63
129
|
# Check file extension
|
|
64
130
|
if file_path.lower().endswith('.xlsx'):
|
|
@@ -69,8 +135,11 @@ def read_excel_to_lists(file_path, sheet_name=0):
|
|
|
69
135
|
# Read the CSV file into a DataFrame without headers
|
|
70
136
|
df = pd.read_csv(file_path, header=None)
|
|
71
137
|
df = df.drop(0)
|
|
138
|
+
elif file_path.lower().endswith('.json'):
|
|
139
|
+
df = load_json_to_list(file_path)
|
|
140
|
+
return df
|
|
72
141
|
else:
|
|
73
|
-
raise ValueError("File must be either .xlsx or .
|
|
142
|
+
raise ValueError("File must be either .xlsx, .csv, or .json format")
|
|
74
143
|
else:
|
|
75
144
|
df = file_path
|
|
76
145
|
|
|
@@ -88,7 +157,7 @@ def read_excel_to_lists(file_path, sheet_name=0):
|
|
|
88
157
|
try:
|
|
89
158
|
master_list[2].extend(data_lists[i+2])
|
|
90
159
|
except IndexError:
|
|
91
|
-
|
|
160
|
+
master_list[2].extend(0)
|
|
92
161
|
|
|
93
162
|
return master_list
|
|
94
163
|
|
|
@@ -356,13 +425,28 @@ def read_centroids_to_dict(file_path):
|
|
|
356
425
|
Returns:
|
|
357
426
|
dict: Dictionary with first column as keys and next three columns as numpy array values
|
|
358
427
|
"""
|
|
428
|
+
def load_json_to_dict(filename):
|
|
429
|
+
with open(filename, 'r') as f:
|
|
430
|
+
data = json.load(f)
|
|
431
|
+
|
|
432
|
+
# Convert only numeric strings to integers, leave other strings as is
|
|
433
|
+
converted_data = {}
|
|
434
|
+
for k, v in data.items():
|
|
435
|
+
try:
|
|
436
|
+
converted_data[int(k)] = v
|
|
437
|
+
except ValueError:
|
|
438
|
+
converted_data[k] = v
|
|
439
|
+
|
|
440
|
+
return converted_data
|
|
359
441
|
# Check file extension
|
|
360
442
|
if file_path.lower().endswith('.xlsx'):
|
|
361
443
|
df = pd.read_excel(file_path)
|
|
362
444
|
elif file_path.lower().endswith('.csv'):
|
|
363
445
|
df = pd.read_csv(file_path)
|
|
446
|
+
elif file_path.lower().endswith('.json'):
|
|
447
|
+
df = load_json_to_dict(file_path)
|
|
364
448
|
else:
|
|
365
|
-
raise ValueError("Unsupported file format. Please provide either .xlsx or .
|
|
449
|
+
raise ValueError("Unsupported file format. Please provide either .xlsx, .csv, or .json file")
|
|
366
450
|
|
|
367
451
|
# Initialize an empty dictionary
|
|
368
452
|
data_dict = {}
|
|
@@ -388,13 +472,30 @@ def read_excel_to_singval_dict(file_path):
|
|
|
388
472
|
Returns:
|
|
389
473
|
dict: Dictionary with first column as keys and second column as values
|
|
390
474
|
"""
|
|
475
|
+
def load_json_to_dict(filename):
|
|
476
|
+
with open(filename, 'r') as f:
|
|
477
|
+
data = json.load(f)
|
|
478
|
+
|
|
479
|
+
# Convert only numeric strings to integers, leave other strings as is
|
|
480
|
+
converted_data = {}
|
|
481
|
+
for k, v in data.items():
|
|
482
|
+
try:
|
|
483
|
+
converted_data[int(k)] = v
|
|
484
|
+
except ValueError:
|
|
485
|
+
converted_data[k] = v
|
|
486
|
+
|
|
487
|
+
return converted_data
|
|
488
|
+
|
|
391
489
|
# Check file extension and read accordingly
|
|
392
490
|
if file_path.lower().endswith('.xlsx'):
|
|
393
491
|
df = pd.read_excel(file_path)
|
|
394
492
|
elif file_path.lower().endswith('.csv'):
|
|
395
493
|
df = pd.read_csv(file_path)
|
|
494
|
+
elif file_path.lower().endswith('.json'):
|
|
495
|
+
df = load_json_to_dict(file_path)
|
|
496
|
+
return df
|
|
396
497
|
else:
|
|
397
|
-
raise ValueError("Unsupported file format. Please provide either .xlsx or .
|
|
498
|
+
raise ValueError("Unsupported file format. Please provide either .xlsx, .csv, or .json file")
|
|
398
499
|
|
|
399
500
|
# Convert the DataFrame to a dictionary
|
|
400
501
|
data_dict = {}
|
|
@@ -836,7 +937,10 @@ def radial_analysis(nodes, network, rad_dist, xy_scale = None, z_scale = None, c
|
|
|
836
937
|
dist_list = get_distance_list(centroids, network, xy_scale, z_scale)
|
|
837
938
|
x_vals, y_vals = buckets(dist_list, num_objects, rad_dist, directory = directory)
|
|
838
939
|
histogram(x_vals, y_vals, directory = directory)
|
|
839
|
-
|
|
940
|
+
output = {}
|
|
941
|
+
for i in range(len(x_vals)):
|
|
942
|
+
output[y_vals[i]] = x_vals[i]
|
|
943
|
+
return output
|
|
840
944
|
|
|
841
945
|
def buckets(dists, num_objects, rad_dist, directory = None):
|
|
842
946
|
y_vals = []
|
|
@@ -859,13 +963,17 @@ def buckets(dists, num_objects, rad_dist, directory = None):
|
|
|
859
963
|
data = {'Radial Distance From Any Node': y_vals, 'Average Number of Neighboring Nodes': x_vals}
|
|
860
964
|
df = pd.DataFrame(data)
|
|
861
965
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
966
|
+
try:
|
|
967
|
+
|
|
968
|
+
if directory is None:
|
|
969
|
+
# Save the DataFrame to an Excel file
|
|
970
|
+
df.to_excel('radial_distribution.xlsx', index=False)
|
|
971
|
+
print("Radial distribution saved to radial_distribution.xlsx")
|
|
972
|
+
else:
|
|
973
|
+
df.to_excel(f'{directory}/radial_distribution.xlsx', index=False)
|
|
974
|
+
print(f"Radial distribution saved to {directory}/radial_distribution.xlsx")
|
|
975
|
+
except:
|
|
976
|
+
pass
|
|
869
977
|
|
|
870
978
|
return x_vals, y_vals
|
|
871
979
|
|
|
@@ -881,8 +989,14 @@ def histogram(counts, y_vals, directory = None):
|
|
|
881
989
|
plt.xlabel('Distance from any node')
|
|
882
990
|
plt.ylabel('Avg Number of Neigbhoring Vertices')
|
|
883
991
|
|
|
884
|
-
|
|
885
|
-
|
|
992
|
+
try:
|
|
993
|
+
|
|
994
|
+
if directory is not None:
|
|
995
|
+
plt.savefig(f'{directory}/radial_plot.png')
|
|
996
|
+
else:
|
|
997
|
+
plt.savefig('radial_plot.png')
|
|
998
|
+
except:
|
|
999
|
+
pass
|
|
886
1000
|
|
|
887
1001
|
# Show the plot
|
|
888
1002
|
plt.show()
|
|
@@ -1090,8 +1204,8 @@ def edge_to_node(network, node_identities = None):
|
|
|
1090
1204
|
alledges = set(edgesC)
|
|
1091
1205
|
|
|
1092
1206
|
for i in range(len(edgesC)):
|
|
1093
|
-
newpair1 = [nodesA[i], edgesC[i],
|
|
1094
|
-
newpair2 = [edgesC[i], nodesB[i],
|
|
1207
|
+
newpair1 = [nodesA[i], edgesC[i], 0]
|
|
1208
|
+
newpair2 = [edgesC[i], nodesB[i], 0]
|
|
1095
1209
|
new_network.append(newpair1)
|
|
1096
1210
|
new_network.append(newpair2)
|
|
1097
1211
|
|
|
@@ -1140,7 +1254,7 @@ def rand_net_weighted(num_rows, num_nodes, nodes):
|
|
|
1140
1254
|
while random_partner == nodes[i]:
|
|
1141
1255
|
random_partner = random.randint(0, len(nodes)-1)
|
|
1142
1256
|
random_partner = nodes[random_partner]
|
|
1143
|
-
random_pair = [nodes[i], random_partner,
|
|
1257
|
+
random_pair = [nodes[i], random_partner, 0]
|
|
1144
1258
|
random_network.append(random_pair)
|
|
1145
1259
|
k+= 1
|
|
1146
1260
|
if k == num_rows:
|
|
@@ -1152,7 +1266,7 @@ def rand_net_weighted(num_rows, num_nodes, nodes):
|
|
|
1152
1266
|
|
|
1153
1267
|
G, edge_weights = weighted_network(df)
|
|
1154
1268
|
|
|
1155
|
-
return G
|
|
1269
|
+
return G, df
|
|
1156
1270
|
|
|
1157
1271
|
def rand_net(num_rows, num_nodes, nodes):
|
|
1158
1272
|
random_network = []
|
|
@@ -1167,10 +1281,10 @@ def rand_net(num_rows, num_nodes, nodes):
|
|
|
1167
1281
|
|
|
1168
1282
|
# Check if the random partner is different from the current node
|
|
1169
1283
|
# and if the pair is not already in the network
|
|
1170
|
-
if random_partner != nodes[i] and [nodes[i], random_partner,
|
|
1284
|
+
if random_partner != nodes[i] and [nodes[i], random_partner, 0] not in random_network and [random_partner, nodes[i], 0] not in random_network:
|
|
1171
1285
|
break
|
|
1172
1286
|
|
|
1173
|
-
random_pair = [nodes[i], random_partner,
|
|
1287
|
+
random_pair = [nodes[i], random_partner, 0]
|
|
1174
1288
|
random_network.append(random_pair)
|
|
1175
1289
|
k += 1
|
|
1176
1290
|
|
|
@@ -1187,7 +1301,7 @@ def rand_net(num_rows, num_nodes, nodes):
|
|
|
1187
1301
|
|
|
1188
1302
|
G, edge_weights = weighted_network(df)
|
|
1189
1303
|
|
|
1190
|
-
return G
|
|
1304
|
+
return G, df
|
|
1191
1305
|
|
|
1192
1306
|
def generate_random(G, net_lists, weighted = True):
|
|
1193
1307
|
|
|
@@ -1250,13 +1364,17 @@ def degree_distribution(G, directory = None):
|
|
|
1250
1364
|
'Proportion of nodes with degree (p(k))': proportion_list
|
|
1251
1365
|
})
|
|
1252
1366
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1367
|
+
try:
|
|
1368
|
+
|
|
1369
|
+
if directory is None:
|
|
1370
|
+
# Save the DataFrame to an Excel file
|
|
1371
|
+
df.to_excel('degree_dist.xlsx', index=False)
|
|
1372
|
+
print("Degree distribution saved to degree_dist.xlsx")
|
|
1373
|
+
else:
|
|
1374
|
+
df.to_excel(f'{directory}/degree_dist.xlsx', index=False)
|
|
1375
|
+
print(f"Degree distribution saved to {directory}/degree_dist.xlsx")
|
|
1376
|
+
except:
|
|
1377
|
+
pass
|
|
1260
1378
|
|
|
1261
1379
|
|
|
1262
1380
|
power_trendline(degrees, proportion_list, directory = directory)
|
|
@@ -1310,8 +1428,15 @@ def power_trendline(x, y, directory = None):
|
|
|
1310
1428
|
verticalalignment='top'
|
|
1311
1429
|
)
|
|
1312
1430
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1431
|
+
try:
|
|
1432
|
+
|
|
1433
|
+
if directory is not None:
|
|
1434
|
+
plt.savefig(f'{directory}/degree_plot.png')
|
|
1435
|
+
else:
|
|
1436
|
+
plt.savefig('degree_plot.png')
|
|
1437
|
+
except:
|
|
1438
|
+
pass
|
|
1439
|
+
|
|
1315
1440
|
|
|
1316
1441
|
plt.show()
|
|
1317
1442
|
|
nettracer3d/node_draw.py
CHANGED
|
@@ -139,7 +139,9 @@ def degree_draw(degree_dict, centroid_dict, nodes):
|
|
|
139
139
|
draw_array = np.zeros_like(nodes, dtype=np.uint8)
|
|
140
140
|
#font_size = 24
|
|
141
141
|
|
|
142
|
-
for node
|
|
142
|
+
for node in centroid_dict:
|
|
143
|
+
|
|
144
|
+
degree = degree_dict[node]
|
|
143
145
|
z, y, x = centroid_dict[node].astype(int)
|
|
144
146
|
|
|
145
147
|
try:
|
nettracer3d/proximity.py
CHANGED
|
@@ -6,6 +6,7 @@ from scipy.spatial import KDTree
|
|
|
6
6
|
import concurrent.futures
|
|
7
7
|
import multiprocessing as mp
|
|
8
8
|
import pandas as pd
|
|
9
|
+
from typing import Dict, Union, Tuple, List, Optional
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
# Related to morphological border searching:
|
|
@@ -74,7 +75,7 @@ def _get_node_node_dict(label_array, label, dilate_xy, dilate_z):
|
|
|
74
75
|
|
|
75
76
|
# Create a boolean mask where elements with the specified label are True
|
|
76
77
|
binary_array = label_array == label
|
|
77
|
-
binary_array = nettracer.
|
|
78
|
+
binary_array = nettracer.dilate_3D_recursive(binary_array, dilate_xy, dilate_xy, dilate_z) #Dilate the label to see where the dilated label overlaps
|
|
78
79
|
label_array = label_array * binary_array # Filter the labels by the node in question
|
|
79
80
|
label_array = label_array.flatten() # Convert 3d array to 1d array
|
|
80
81
|
label_array = nettracer.remove_zeros(label_array) # Remove zeros
|
|
@@ -95,22 +96,26 @@ def process_label(args):
|
|
|
95
96
|
return label, sub_nodes
|
|
96
97
|
|
|
97
98
|
|
|
98
|
-
def create_node_dictionary(nodes, num_nodes, dilate_xy, dilate_z):
|
|
99
|
+
def create_node_dictionary(nodes, num_nodes, dilate_xy, dilate_z, targets = None):
|
|
99
100
|
"""Internal method used for the secondary algorithm to process nodes in parallel."""
|
|
100
101
|
# Initialize the dictionary to be returned
|
|
101
102
|
node_dict = {}
|
|
102
103
|
|
|
103
104
|
array_shape = nodes.shape
|
|
104
105
|
|
|
106
|
+
|
|
105
107
|
# Use ThreadPoolExecutor for parallel execution
|
|
106
108
|
with ThreadPoolExecutor(max_workers=mp.cpu_count()) as executor:
|
|
107
109
|
# First parallel section to process labels
|
|
108
110
|
# List of arguments for each parallel task
|
|
109
111
|
args_list = [(nodes, i, dilate_xy, dilate_z, array_shape) for i in range(1, num_nodes + 1)]
|
|
110
112
|
|
|
111
|
-
|
|
113
|
+
if targets is not None:
|
|
114
|
+
args_list = [tup for tup in args_list if tup[1] in targets]
|
|
115
|
+
|
|
112
116
|
results = executor.map(process_label, args_list)
|
|
113
117
|
|
|
118
|
+
|
|
114
119
|
# Second parallel section to create dictionary entries
|
|
115
120
|
for label, sub_nodes in results:
|
|
116
121
|
executor.submit(create_dict_entry, node_dict, label, sub_nodes, dilate_xy, dilate_z)
|
|
@@ -182,15 +187,19 @@ def find_neighbors_kdtree(array, radius, targets=None):
|
|
|
182
187
|
if targets is None:
|
|
183
188
|
# Original behavior: find neighbors for all points
|
|
184
189
|
query_points = points
|
|
190
|
+
query_indices = range(len(points)) # Add this line
|
|
185
191
|
else:
|
|
186
192
|
# Find coordinates of target values
|
|
187
193
|
target_points = []
|
|
194
|
+
target_indices = [] # Add this line
|
|
188
195
|
for idx, point in enumerate(points):
|
|
189
196
|
if array[tuple(point)] in targets:
|
|
190
197
|
target_points.append(point)
|
|
198
|
+
target_indices.append(idx) # Add this line
|
|
191
199
|
|
|
192
200
|
# Convert to numpy array for querying
|
|
193
201
|
query_points = np.array(target_points)
|
|
202
|
+
query_indices = target_indices # Add this line
|
|
194
203
|
|
|
195
204
|
# Handle case where no target values were found
|
|
196
205
|
if len(query_points) == 0:
|
|
@@ -203,14 +212,14 @@ def find_neighbors_kdtree(array, radius, targets=None):
|
|
|
203
212
|
output = []
|
|
204
213
|
|
|
205
214
|
# Generate pairs
|
|
206
|
-
for
|
|
215
|
+
for i, neighbors in enumerate(neighbor_indices):
|
|
216
|
+
query_idx = query_indices[i] # Modified this line
|
|
207
217
|
for neighbor_idx in neighbors:
|
|
208
218
|
# Skip self-pairing
|
|
209
219
|
if neighbor_idx != query_idx:
|
|
210
220
|
query_value = array[tuple(points[query_idx])]
|
|
211
221
|
neighbor_value = array[tuple(points[neighbor_idx])]
|
|
212
222
|
output.append([query_value, neighbor_value, 0])
|
|
213
|
-
|
|
214
223
|
|
|
215
224
|
return output
|
|
216
225
|
|
|
@@ -225,4 +234,55 @@ def extract_pairwise_connections(connections):
|
|
|
225
234
|
output.append([list_index_value, number, 0])
|
|
226
235
|
print(f'sublist: {sublist}, adding: {[list_index_value, number, 0]}')
|
|
227
236
|
|
|
228
|
-
return output
|
|
237
|
+
return output
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
#voronois:
|
|
242
|
+
def create_voronoi_3d_kdtree(centroids: Dict[Union[int, str], Union[Tuple[int, int, int], List[int]]],
|
|
243
|
+
shape: Optional[Tuple[int, int, int]] = None) -> np.ndarray:
|
|
244
|
+
"""
|
|
245
|
+
Create a 3D Voronoi diagram using scipy's KDTree for faster computation.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
centroids: Dictionary with labels as keys and (z,y,x) coordinates as values
|
|
249
|
+
shape: Optional tuple of (Z,Y,X) dimensions. If None, calculated from centroids
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
3D numpy array where each cell contains the label of the closest centroid as uint32
|
|
253
|
+
"""
|
|
254
|
+
from scipy.spatial import cKDTree
|
|
255
|
+
|
|
256
|
+
# Convert string labels to integers if necessary
|
|
257
|
+
if any(isinstance(k, str) for k in centroids.keys()):
|
|
258
|
+
label_map = {label: idx for idx, label in enumerate(centroids.keys())}
|
|
259
|
+
centroids = {label_map[k]: v for k, v in centroids.items()}
|
|
260
|
+
|
|
261
|
+
# Convert centroids to array and keep track of labels
|
|
262
|
+
labels = np.array(list(centroids.keys()), dtype=np.uint32)
|
|
263
|
+
centroid_points = np.array([centroids[label] for label in labels])
|
|
264
|
+
|
|
265
|
+
# Calculate shape if not provided
|
|
266
|
+
if shape is None:
|
|
267
|
+
max_coords = centroid_points.max(axis=0)
|
|
268
|
+
shape = tuple(max_coord + 1 for max_coord in max_coords)
|
|
269
|
+
|
|
270
|
+
# Create KD-tree
|
|
271
|
+
tree = cKDTree(centroid_points)
|
|
272
|
+
|
|
273
|
+
# Create coordinate arrays
|
|
274
|
+
coords = np.array(np.meshgrid(
|
|
275
|
+
np.arange(shape[0]),
|
|
276
|
+
np.arange(shape[1]),
|
|
277
|
+
np.arange(shape[2]),
|
|
278
|
+
indexing='ij'
|
|
279
|
+
)).reshape(3, -1).T
|
|
280
|
+
|
|
281
|
+
# Find nearest centroid for each point
|
|
282
|
+
_, indices = tree.query(coords)
|
|
283
|
+
|
|
284
|
+
# Convert indices to labels and ensure uint32 dtype
|
|
285
|
+
label_array = labels[indices].astype(np.uint32)
|
|
286
|
+
|
|
287
|
+
# Reshape to final shape
|
|
288
|
+
return label_array.reshape(shape)
|
nettracer3d/simple_network.py
CHANGED
|
@@ -237,21 +237,6 @@ def show_simple_network(excel_file_path, geometric = False, geo_info = None, dir
|
|
|
237
237
|
# Add edges from the DataFrame
|
|
238
238
|
G.add_edges_from(edges)
|
|
239
239
|
|
|
240
|
-
# Print basic information about the graph
|
|
241
|
-
num_nodes = G.number_of_nodes()
|
|
242
|
-
num_edge = G.number_of_edges()
|
|
243
|
-
|
|
244
|
-
print("Number of nodes:", num_nodes)
|
|
245
|
-
print("Number of edges:", num_edge)
|
|
246
|
-
|
|
247
|
-
# Calculate the average degree connectivity
|
|
248
|
-
average_degree_connectivity = nx.average_degree_connectivity(G)
|
|
249
|
-
print("Average degree connectivity:", average_degree_connectivity)
|
|
250
|
-
|
|
251
|
-
# Calculate the average number of edges attached to a node
|
|
252
|
-
average_edges_per_node = num_nodes/num_edge
|
|
253
|
-
print("Average edges per node:", average_edges_per_node)
|
|
254
|
-
|
|
255
240
|
if geometric:
|
|
256
241
|
for node in list(G.nodes()):
|
|
257
242
|
if node not in geo_info[0]:
|
|
@@ -272,12 +257,10 @@ def show_simple_network(excel_file_path, geometric = False, geo_info = None, dir
|
|
|
272
257
|
plt.show()
|
|
273
258
|
|
|
274
259
|
|
|
275
|
-
def show_identity_network(excel_file_path, node_identities, geometric
|
|
276
|
-
|
|
260
|
+
def show_identity_network(excel_file_path, node_identities, geometric=False, geo_info=None, directory=None):
|
|
277
261
|
if type(node_identities) == str:
|
|
278
262
|
# Read the Excel file into a DataFrame
|
|
279
263
|
df = pd.read_excel(node_identities)
|
|
280
|
-
|
|
281
264
|
# Convert the DataFrame to a dictionary
|
|
282
265
|
identity_dict = pd.Series(df.iloc[:, 1].values, index=df.iloc[:, 0]).to_dict()
|
|
283
266
|
else:
|
|
@@ -289,74 +272,101 @@ def show_identity_network(excel_file_path, node_identities, geometric = False, g
|
|
|
289
272
|
master_list = excel_file_path
|
|
290
273
|
|
|
291
274
|
edges = zip(master_list[0], master_list[1])
|
|
292
|
-
|
|
275
|
+
|
|
293
276
|
# Create a graph
|
|
294
277
|
G = nx.Graph()
|
|
295
|
-
|
|
296
|
-
# Add edges from the lists
|
|
297
278
|
G.add_edges_from(edges)
|
|
298
279
|
|
|
299
|
-
#
|
|
300
|
-
num_nodes = G.number_of_nodes()
|
|
301
|
-
num_edge = G.number_of_edges()
|
|
302
|
-
|
|
303
|
-
print("Number of nodes:", num_nodes)
|
|
304
|
-
print("Number of edges:", num_edge)
|
|
305
|
-
|
|
306
|
-
# Calculate the average number of edges attached to a node
|
|
307
|
-
average_edges_per_node = num_edge / num_nodes
|
|
308
|
-
print("Average edges per node:", average_edges_per_node)
|
|
309
|
-
|
|
310
|
-
# Calculate the average degree connectivity
|
|
311
|
-
average_degree_connectivity = nx.average_degree_connectivity(G)
|
|
312
|
-
print("Average degree connectivity:", average_degree_connectivity)
|
|
313
|
-
|
|
314
|
-
# Create a color map based on the categories in identity_dict
|
|
280
|
+
# Create a more sophisticated color palette using a combination of colormap sequences
|
|
315
281
|
unique_categories = list(set(identity_dict.values()))
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
282
|
+
num_categories = len(unique_categories)
|
|
283
|
+
|
|
284
|
+
# Create a color palette that combines multiple colormaps for more distinct colors
|
|
285
|
+
if num_categories <= 10:
|
|
286
|
+
colors = plt.cm.tab10(np.linspace(0, 1, num_categories))
|
|
287
|
+
elif num_categories <= 20:
|
|
288
|
+
colors1 = plt.cm.tab20(np.linspace(0, 1, min(num_categories, 20)))
|
|
289
|
+
colors = colors1[:num_categories]
|
|
290
|
+
else:
|
|
291
|
+
# For large number of categories, combine multiple distinct colormaps
|
|
292
|
+
colors1 = plt.cm.tab20(np.linspace(0, 1, 20))
|
|
293
|
+
colors2 = plt.cm.Set3(np.linspace(0, 1, 12))
|
|
294
|
+
colors3 = plt.cm.Pastel1(np.linspace(0, 1, 9))
|
|
295
|
+
colors4 = plt.cm.Paired(np.linspace(0, 1, 12))
|
|
296
|
+
|
|
297
|
+
# Combine and take needed number of colors
|
|
298
|
+
all_colors = np.vstack([colors1, colors2, colors3, colors4])
|
|
299
|
+
# Shuffle the colors to ensure adjacent categories have distinct colors
|
|
300
|
+
np.random.seed(42) # For consistency
|
|
301
|
+
np.random.shuffle(all_colors)
|
|
302
|
+
colors = all_colors[:num_categories]
|
|
303
|
+
|
|
304
|
+
color_map = {category: mcolors.to_hex(color[:3])
|
|
305
|
+
for category, color in zip(unique_categories, colors)}
|
|
306
|
+
|
|
307
|
+
# Node size handling
|
|
308
|
+
node_dict = {node: 30 if identity_dict[node] == 'Edge' else 100
|
|
309
|
+
for node in G.nodes()}
|
|
310
|
+
|
|
333
311
|
if geometric:
|
|
312
|
+
# Handle geometric positioning
|
|
334
313
|
for node in list(G.nodes()):
|
|
335
314
|
if node not in geo_info[0]:
|
|
336
315
|
G.remove_node(node)
|
|
337
|
-
print(f"Removing node {node} from network visualization
|
|
338
|
-
|
|
339
|
-
|
|
316
|
+
print(f"Removing node {node} from network visualization "
|
|
317
|
+
f"(no centroid - likely due to downsampling when finding centroids)")
|
|
318
|
+
|
|
319
|
+
pos, z_pos = geometric_positions(geo_info[0], geo_info[1])
|
|
340
320
|
node_sizes_list = [z_pos[node] for node in G.nodes()]
|
|
341
|
-
# Assign a color to each node based on its category
|
|
342
|
-
node_colors = [color_map[identity_dict[node]] for node in G.nodes]
|
|
343
|
-
nx.draw(G, pos, with_labels=True, font_color='black', font_weight='bold', node_size= node_sizes_list, node_color=node_colors, alpha=0.8, font_size = 12)
|
|
344
321
|
else:
|
|
345
|
-
# Assign a color to each node based on its category
|
|
346
|
-
node_colors = [color_map[identity_dict[node]] for node in G.nodes]
|
|
347
322
|
pos = nx.spring_layout(G)
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
# Create custom legend handles
|
|
351
|
-
legend_handles = [Patch(color=color, label=category) for category, color in color_map.items()]
|
|
352
|
-
|
|
353
|
-
# Add legend to the plot
|
|
354
|
-
plt.legend(handles=legend_handles, loc='upper right', title='Categories')
|
|
323
|
+
node_sizes_list = [node_dict[node] for node in G.nodes()]
|
|
355
324
|
|
|
325
|
+
# Create figure with custom size
|
|
326
|
+
plt.figure(figsize=(12, 8))
|
|
327
|
+
|
|
328
|
+
# Create separate axes for the graph and legend
|
|
329
|
+
graph_ax = plt.gca()
|
|
330
|
+
|
|
331
|
+
# Draw the network
|
|
332
|
+
node_colors = [color_map[identity_dict[node]] for node in G.nodes()]
|
|
333
|
+
nx.draw(G, pos, ax=graph_ax, with_labels=True, font_color='black',
|
|
334
|
+
font_weight='bold', node_size=node_sizes_list,
|
|
335
|
+
node_color=node_colors, alpha=0.8, font_size=12)
|
|
336
|
+
|
|
337
|
+
# Create custom legend with multiple columns if needed
|
|
338
|
+
legend_handles = [Patch(color=color, label=category)
|
|
339
|
+
for category, color in color_map.items()]
|
|
340
|
+
|
|
341
|
+
# Adjust number of columns based on number of categories
|
|
342
|
+
if len(unique_categories) > 20:
|
|
343
|
+
ncol = 3
|
|
344
|
+
bbox_to_anchor = (1.2, 1)
|
|
345
|
+
elif len(unique_categories) > 10:
|
|
346
|
+
ncol = 2
|
|
347
|
+
bbox_to_anchor = (1.1, 1)
|
|
348
|
+
else:
|
|
349
|
+
ncol = 1
|
|
350
|
+
bbox_to_anchor = (1.05, 1)
|
|
351
|
+
|
|
352
|
+
# Add legend with adjusted parameters
|
|
353
|
+
legend = plt.legend(handles=legend_handles,
|
|
354
|
+
bbox_to_anchor=bbox_to_anchor,
|
|
355
|
+
loc='upper left',
|
|
356
|
+
title='Categories',
|
|
357
|
+
ncol=ncol,
|
|
358
|
+
fontsize='small',
|
|
359
|
+
title_fontsize='medium')
|
|
360
|
+
|
|
361
|
+
# Adjust layout to prevent legend overlap
|
|
362
|
+
plt.tight_layout()
|
|
363
|
+
|
|
364
|
+
# Save if directory provided
|
|
356
365
|
if directory is not None:
|
|
357
|
-
plt.savefig(f'{directory}/identity_network_plot.png'
|
|
358
|
-
|
|
359
|
-
|
|
366
|
+
plt.savefig(f'{directory}/identity_network_plot.png',
|
|
367
|
+
bbox_inches='tight',
|
|
368
|
+
dpi=300)
|
|
369
|
+
|
|
360
370
|
plt.show()
|
|
361
371
|
|
|
362
372
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: nettracer3d
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Scripts for intializing and analyzing networks from segmentations of three dimensional images.
|
|
5
5
|
Author-email: Liam McLaughlin <boom2449@gmail.com>
|
|
6
6
|
Project-URL: User_Manual, https://drive.google.com/drive/folders/1fTkz3n4LN9_VxKRKC8lVQSlrz_wq0bVn?usp=drive_link
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
nettracer3d/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
nettracer3d/community_extractor.py,sha256=o-W0obPAzCgvPB2SAJ3BhPgykevH6GyD8Y5lcd4t3Vw,29725
|
|
3
|
+
nettracer3d/hub_getter.py,sha256=KiNtxdajLkwB1ftslvrh1FE1Ch9ZCFEmHSEEotwR-To,8298
|
|
4
|
+
nettracer3d/modularity.py,sha256=V1f3s_vGd8EuVz27mzq6ycIGr0BWIpH7c7NU4QjgAHU,30247
|
|
5
|
+
nettracer3d/morphology.py,sha256=wv7v06YUcn5lMyefcc_znQlXF5iDxvUdoc0fXOKlGTw,12982
|
|
6
|
+
nettracer3d/nettracer.py,sha256=X33ehVPkfrQHgh4zUTqWlESsZnzs5kc45__llG3UCRc,204438
|
|
7
|
+
nettracer3d/nettracer_gui.py,sha256=aO8gwEgI0pt9rEu1BGNHW9WpSu8xLJohoP7vZ_3Yjnc,281387
|
|
8
|
+
nettracer3d/network_analysis.py,sha256=Nv47U3VcmiTK8b5oEa7rgnYLL4n1r2dF9XCDiQiza4A,45788
|
|
9
|
+
nettracer3d/network_draw.py,sha256=JWWEX7zT6Y9fcO75TtBwkGpPKFIkvBy8pfyB3YB-H_E,12599
|
|
10
|
+
nettracer3d/node_draw.py,sha256=AL8KfFNYBybOx4q6y2pGsAD4QdMebnS-FGRVTqDa0tA,8234
|
|
11
|
+
nettracer3d/proximity.py,sha256=KYs4QUbt1U79RLzTvt8BmrxeGVaeKOQ2brtzTjjA78c,11011
|
|
12
|
+
nettracer3d/simple_network.py,sha256=fP1gkDdtQcHruEZpUdasKdZeVacoLOxKhR3bY0L1CAQ,15426
|
|
13
|
+
nettracer3d/smart_dilate.py,sha256=howfO6Lw5PxNjkaOBSCjkmf7fyau_-_8iTct2mAuTAQ,22083
|
|
14
|
+
nettracer3d-0.2.8.dist-info/LICENSE,sha256=gM207DhJjWrxLuEWXl0Qz5ISbtWDmADfjHp3yC2XISs,888
|
|
15
|
+
nettracer3d-0.2.8.dist-info/METADATA,sha256=spJkNDOCkTGqbv7GPGZc7pletM3c5hfXRpQ79u0GRXA,2258
|
|
16
|
+
nettracer3d-0.2.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
17
|
+
nettracer3d-0.2.8.dist-info/top_level.txt,sha256=zsYy9rZwirfCEOubolhee4TyzqBAL5gSUeFMzhFTX8c,12
|
|
18
|
+
nettracer3d-0.2.8.dist-info/RECORD,,
|