nettracer3d 0.0.1__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.
- nettracer3d-0.0.1/LICENSE +7 -0
- nettracer3d-0.0.1/PKG-INFO +37 -0
- nettracer3d-0.0.1/README.md +8 -0
- nettracer3d-0.0.1/pyproject.toml +35 -0
- nettracer3d-0.0.1/setup.cfg +4 -0
- nettracer3d-0.0.1/src/nettracer3d/community_extractor.py +595 -0
- nettracer3d-0.0.1/src/nettracer3d/hub_getter.py +248 -0
- nettracer3d-0.0.1/src/nettracer3d/modularity.py +385 -0
- nettracer3d-0.0.1/src/nettracer3d/nettracer.py +3566 -0
- nettracer3d-0.0.1/src/nettracer3d/network_analysis.py +1145 -0
- nettracer3d-0.0.1/src/nettracer3d/network_draw.py +295 -0
- nettracer3d-0.0.1/src/nettracer3d/node_draw.py +252 -0
- nettracer3d-0.0.1/src/nettracer3d/simple_network.py +399 -0
- nettracer3d-0.0.1/src/nettracer3d/smart_dilate.py +521 -0
- nettracer3d-0.0.1/src/nettracer3d.egg-info/PKG-INFO +37 -0
- nettracer3d-0.0.1/src/nettracer3d.egg-info/SOURCES.txt +17 -0
- nettracer3d-0.0.1/src/nettracer3d.egg-info/dependency_links.txt +1 -0
- nettracer3d-0.0.1/src/nettracer3d.egg-info/requires.txt +18 -0
- nettracer3d-0.0.1/src/nettracer3d.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
NetTracer3D is freely available for academic and nonprofit use and can be obtained from Liam McLaughlin (boom2449@gmail.com) OR at pip (pip install nettracer3d), provided that the following citation is included in any abstract, paper, or presentation utilizing NetTracer3D.
|
|
2
|
+
|
|
3
|
+
Three Dimensional Multiscalar Neurovascular Nephron Connectivity Map of the Human Kidney Across the Lifespan
|
|
4
|
+
Liam McLaughlin, Bo Zhang, Siddharth Sharma, Amanda L. Knoten, Madhurima Kaushal, Jeffrey M. Purkerson, Heidy Huyck, Gloria S. Pryhuber, Joseph P. Gaut, Sanjay Jain
|
|
5
|
+
bioRxiv 2024.07.29.605633; doi: https://doi.org/10.1101/2024.07.29.605633
|
|
6
|
+
|
|
7
|
+
Commercial use is available for a fee. Copyright © is held by Washington University. Please direct all commercial requests for licensing, information, and limited evaluation copies to Washington University's Office of Technology Management at OTM@wustl.edu.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: nettracer3d
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Scripts for intialzing and analyzing networks from segmentations of three dimensional images.
|
|
5
|
+
Author-email: Liam McLaughlin <boom2449@gmail.com>
|
|
6
|
+
Project-URL: User_Manual, https://drive.google.com/drive/folders/1fTkz3n4LN9_VxKRKC8lVQSlrz_wq0bVn?usp=drive_link
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: Other/Proprietary License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: scipy
|
|
15
|
+
Requires-Dist: scikit-image
|
|
16
|
+
Requires-Dist: Pillow
|
|
17
|
+
Requires-Dist: matplotlib
|
|
18
|
+
Requires-Dist: networkx
|
|
19
|
+
Requires-Dist: opencv-python
|
|
20
|
+
Requires-Dist: openpyxl
|
|
21
|
+
Requires-Dist: pandas
|
|
22
|
+
Requires-Dist: plotly
|
|
23
|
+
Requires-Dist: python-louvain
|
|
24
|
+
Requires-Dist: tifffile
|
|
25
|
+
Provides-Extra: cuda11
|
|
26
|
+
Requires-Dist: cupy-cuda11x; extra == "cuda11"
|
|
27
|
+
Provides-Extra: cuda12
|
|
28
|
+
Requires-Dist: cupy-cuda12x; extra == "cuda12"
|
|
29
|
+
|
|
30
|
+
NetTracer3D is a python package developed for 3D analysis of microscopic images in the .tif file format, such as lightsheet images, although it can also be used for 2D analysis if the user converts the 2D .tif file into a 3D stack first by appending the image together twice. NetTracer3D does not function on images directly, but rather on binary/labelled segmentations of image signal, and general requires one segmentation to serve as objects in a network (nodes) and one segmentation to serve as a connective media (edges), although it is possible to make networks from only edges.
|
|
31
|
+
|
|
32
|
+
Please see: https://drive.google.com/drive/folders/1fTkz3n4LN9_VxKRKC8lVQSlrz_wq0bVn?usp=drive_link
|
|
33
|
+
for a user manual that provides documentation and detailed information.
|
|
34
|
+
|
|
35
|
+
NetTracer3D is free to use/fork for academic/nonprofit use so long as citation is provided, and is available for commercial use at a fee (see license file for information).
|
|
36
|
+
|
|
37
|
+
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
NetTracer3D is a python package developed for 3D analysis of microscopic images in the .tif file format, such as lightsheet images, although it can also be used for 2D analysis if the user converts the 2D .tif file into a 3D stack first by appending the image together twice. NetTracer3D does not function on images directly, but rather on binary/labelled segmentations of image signal, and general requires one segmentation to serve as objects in a network (nodes) and one segmentation to serve as a connective media (edges), although it is possible to make networks from only edges.
|
|
2
|
+
|
|
3
|
+
Please see: https://drive.google.com/drive/folders/1fTkz3n4LN9_VxKRKC8lVQSlrz_wq0bVn?usp=drive_link
|
|
4
|
+
for a user manual that provides documentation and detailed information.
|
|
5
|
+
|
|
6
|
+
NetTracer3D is free to use/fork for academic/nonprofit use so long as citation is provided, and is available for commercial use at a fee (see license file for information).
|
|
7
|
+
|
|
8
|
+
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "nettracer3d"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name="Liam McLaughlin", email="boom2449@gmail.com" },
|
|
6
|
+
]
|
|
7
|
+
description = "Scripts for intialzing and analyzing networks from segmentations of three dimensional images."
|
|
8
|
+
dependencies = [
|
|
9
|
+
"numpy",
|
|
10
|
+
"scipy",
|
|
11
|
+
"scikit-image",
|
|
12
|
+
"Pillow",
|
|
13
|
+
"matplotlib",
|
|
14
|
+
"networkx",
|
|
15
|
+
"opencv-python",
|
|
16
|
+
"openpyxl",
|
|
17
|
+
"pandas",
|
|
18
|
+
"plotly",
|
|
19
|
+
"python-louvain",
|
|
20
|
+
"tifffile",
|
|
21
|
+
]
|
|
22
|
+
readme = "README.md"
|
|
23
|
+
requires-python = ">=3.8"
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"License :: Other/Proprietary License",
|
|
27
|
+
"Operating System :: OS Independent",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
CUDA11 = ["cupy-cuda11x"]
|
|
32
|
+
CUDA12 = ["cupy-cuda12x"]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
User_Manual = "https://drive.google.com/drive/folders/1fTkz3n4LN9_VxKRKC8lVQSlrz_wq0bVn?usp=drive_link"
|
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import networkx as nx
|
|
3
|
+
import tifffile
|
|
4
|
+
import numpy as np
|
|
5
|
+
from networkx.algorithms import community
|
|
6
|
+
from scipy import ndimage
|
|
7
|
+
from scipy.ndimage import zoom
|
|
8
|
+
from networkx.algorithms import community
|
|
9
|
+
from community import community_louvain
|
|
10
|
+
import node_draw
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def binarize(image):
|
|
14
|
+
"""Convert an array from numerical values to boolean mask"""
|
|
15
|
+
image = image != 0
|
|
16
|
+
|
|
17
|
+
image = image.astype(np.uint8)
|
|
18
|
+
|
|
19
|
+
return image
|
|
20
|
+
|
|
21
|
+
def upsample_with_padding(data, factor, original_shape):
|
|
22
|
+
# Upsample the input binary array while adding padding to match the original shape
|
|
23
|
+
|
|
24
|
+
# Get the dimensions of the original and upsampled arrays
|
|
25
|
+
original_shape = np.array(original_shape)
|
|
26
|
+
binary_array = zoom(data, factor, order=0)
|
|
27
|
+
upsampled_shape = np.array(binary_array.shape)
|
|
28
|
+
|
|
29
|
+
# Calculate the positive differences in dimensions
|
|
30
|
+
difference_dims = original_shape - upsampled_shape
|
|
31
|
+
|
|
32
|
+
# Calculate the padding amounts for each dimension
|
|
33
|
+
padding_dims = np.maximum(difference_dims, 0)
|
|
34
|
+
padding_before = padding_dims // 2
|
|
35
|
+
padding_after = padding_dims - padding_before
|
|
36
|
+
|
|
37
|
+
# Pad the binary array along each dimension
|
|
38
|
+
padded_array = np.pad(binary_array, [(padding_before[0], padding_after[0]),
|
|
39
|
+
(padding_before[1], padding_after[1]),
|
|
40
|
+
(padding_before[2], padding_after[2])], mode='constant', constant_values=0)
|
|
41
|
+
|
|
42
|
+
# Calculate the subtraction amounts for each dimension
|
|
43
|
+
sub_dims = np.maximum(-difference_dims, 0)
|
|
44
|
+
sub_before = sub_dims // 2
|
|
45
|
+
sub_after = sub_dims - sub_before
|
|
46
|
+
|
|
47
|
+
# Remove planes from the beginning and end
|
|
48
|
+
if sub_dims[0] == 0:
|
|
49
|
+
trimmed_planes = padded_array
|
|
50
|
+
else:
|
|
51
|
+
trimmed_planes = padded_array[sub_before[0]:-sub_after[0], :, :]
|
|
52
|
+
|
|
53
|
+
# Remove rows from the beginning and end
|
|
54
|
+
if sub_dims[1] == 0:
|
|
55
|
+
trimmed_rows = trimmed_planes
|
|
56
|
+
else:
|
|
57
|
+
trimmed_rows = trimmed_planes[:, sub_before[1]:-sub_after[1], :]
|
|
58
|
+
|
|
59
|
+
# Remove columns from the beginning and end
|
|
60
|
+
if sub_dims[2] == 0:
|
|
61
|
+
trimmed_array = trimmed_rows
|
|
62
|
+
else:
|
|
63
|
+
trimmed_array = trimmed_rows[:, :, sub_before[2]:-sub_after[2]]
|
|
64
|
+
|
|
65
|
+
return trimmed_array
|
|
66
|
+
|
|
67
|
+
def weighted_network(excel_file_path):
|
|
68
|
+
"""creates a network where the edges have weights proportional to the number of connections they make between the same structure"""
|
|
69
|
+
# Read the Excel file into a pandas DataFrame
|
|
70
|
+
master_list = read_excel_to_lists(excel_file_path)
|
|
71
|
+
|
|
72
|
+
# Create a graph
|
|
73
|
+
G = nx.Graph()
|
|
74
|
+
|
|
75
|
+
# Create a dictionary to store edge weights based on node pairs
|
|
76
|
+
edge_weights = {}
|
|
77
|
+
|
|
78
|
+
nodes_a = master_list[0]
|
|
79
|
+
nodes_b = master_list[1]
|
|
80
|
+
|
|
81
|
+
# Iterate over the DataFrame rows and update edge weights
|
|
82
|
+
for i in range(len(nodes_a)):
|
|
83
|
+
node1, node2 = nodes_a[i], nodes_b[i]
|
|
84
|
+
edge = (node1, node2) if node1 < node2 else (node2, node1) # Ensure consistent order
|
|
85
|
+
edge_weights[edge] = edge_weights.get(edge, 0) + 1
|
|
86
|
+
|
|
87
|
+
# Add edges to the graph with weights
|
|
88
|
+
for edge, weight in edge_weights.items():
|
|
89
|
+
G.add_edge(edge[0], edge[1], weight=weight)
|
|
90
|
+
|
|
91
|
+
return G, edge_weights
|
|
92
|
+
|
|
93
|
+
def compute_centroid(binary_stack, label):
|
|
94
|
+
"""
|
|
95
|
+
Finds centroid of labelled object in array
|
|
96
|
+
"""
|
|
97
|
+
indices = np.argwhere(binary_stack == label)
|
|
98
|
+
centroid = np.round(np.mean(indices, axis=0)).astype(int)
|
|
99
|
+
|
|
100
|
+
return centroid
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_border_nodes(partition, G):
|
|
105
|
+
# Find nodes that border nodes in other communities
|
|
106
|
+
border_nodes = set()
|
|
107
|
+
for edge in G.edges():
|
|
108
|
+
if partition[edge[0]] != partition[edge[1]]:
|
|
109
|
+
border_nodes.add(edge[0])
|
|
110
|
+
border_nodes.add(edge[1])
|
|
111
|
+
|
|
112
|
+
return border_nodes
|
|
113
|
+
|
|
114
|
+
def downsample(data, factor):
|
|
115
|
+
# Downsample the input data by a specified factor
|
|
116
|
+
return zoom(data, 1/factor, order=0)
|
|
117
|
+
|
|
118
|
+
def labels_to_boolean(label_array, labels_list):
|
|
119
|
+
# Use np.isin to create a boolean array with a single operation
|
|
120
|
+
boolean_array = np.isin(label_array, labels_list)
|
|
121
|
+
|
|
122
|
+
return boolean_array
|
|
123
|
+
|
|
124
|
+
def read_excel_to_lists(file_path, sheet_name=0):
|
|
125
|
+
"""Convert a pd dataframe to lists"""
|
|
126
|
+
# Read the Excel file into a DataFrame without headers
|
|
127
|
+
df = pd.read_excel(file_path, header=None, sheet_name=sheet_name)
|
|
128
|
+
|
|
129
|
+
df = df.drop(0)
|
|
130
|
+
|
|
131
|
+
# Initialize an empty list to store the lists of values
|
|
132
|
+
data_lists = []
|
|
133
|
+
|
|
134
|
+
# Iterate over each column in the DataFrame
|
|
135
|
+
for column_name, column_data in df.items():
|
|
136
|
+
# Convert the column values to a list and append to the data_lists
|
|
137
|
+
data_lists.append(column_data.tolist())
|
|
138
|
+
|
|
139
|
+
master_list = [[], [], []]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
for i in range(0, len(data_lists), 3):
|
|
143
|
+
|
|
144
|
+
master_list[0].extend(data_lists[i])
|
|
145
|
+
master_list[1].extend(data_lists[i+1])
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
master_list[2].extend(data_lists[i+2])
|
|
149
|
+
except IndexError:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
return master_list
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def open_network(excel_file_path):
|
|
156
|
+
"""opens an unweighted network from the network excel file"""
|
|
157
|
+
|
|
158
|
+
# Read the Excel file into a pandas DataFrame
|
|
159
|
+
master_list = read_excel_to_lists(excel_file_path)
|
|
160
|
+
|
|
161
|
+
# Create a graph
|
|
162
|
+
G = nx.Graph()
|
|
163
|
+
|
|
164
|
+
nodes_a = master_list[0]
|
|
165
|
+
nodes_b = master_list[1]
|
|
166
|
+
|
|
167
|
+
# Add edges to the graph
|
|
168
|
+
for i in range(len(nodes_a)):
|
|
169
|
+
G.add_edge(nodes_a[i], nodes_b[i])
|
|
170
|
+
|
|
171
|
+
return G
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def isolate_full_edges(nodes, edges, directory = None):
|
|
176
|
+
"""requires smart_dilate output to function properly"""
|
|
177
|
+
|
|
178
|
+
print("Isolating full edges")
|
|
179
|
+
|
|
180
|
+
if type(nodes) == str:
|
|
181
|
+
nodes = tifffile.imread(nodes)
|
|
182
|
+
|
|
183
|
+
if type(edges) == str:
|
|
184
|
+
edges = tifffile.imread(edges)
|
|
185
|
+
|
|
186
|
+
node_bools = binarize(nodes)
|
|
187
|
+
|
|
188
|
+
del nodes
|
|
189
|
+
|
|
190
|
+
real_edges = node_bools * edges
|
|
191
|
+
|
|
192
|
+
del node_bools
|
|
193
|
+
|
|
194
|
+
# Flatten the 3D array to a 1D array
|
|
195
|
+
real_edges = real_edges.flatten()
|
|
196
|
+
|
|
197
|
+
# Find unique values
|
|
198
|
+
real_edges = np.unique(real_edges)
|
|
199
|
+
|
|
200
|
+
edge_masks = labels_to_boolean(edges, real_edges)
|
|
201
|
+
|
|
202
|
+
del real_edges
|
|
203
|
+
|
|
204
|
+
edge_labels_1 = edge_masks * edges
|
|
205
|
+
|
|
206
|
+
del edge_masks
|
|
207
|
+
|
|
208
|
+
edge_labels_1 = binarize(edge_labels_1)
|
|
209
|
+
|
|
210
|
+
edge_labels_1 = edge_labels_1 * 255
|
|
211
|
+
|
|
212
|
+
if directory is None:
|
|
213
|
+
|
|
214
|
+
tifffile.imsave(f"full_edges_for_component.tif", edge_labels_1)
|
|
215
|
+
print("Full edge labels saved to full_edges_for_component.tif")
|
|
216
|
+
else:
|
|
217
|
+
tifffile.imsave(f"{directory}/full_edges_for_component.tif", edge_labels_1)
|
|
218
|
+
print(f"Full edge labels saved to {directory}/full_edges_for_component.tif")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def isolate_edges(edges, network, iso_network, netlists = None, directory = None):
|
|
222
|
+
|
|
223
|
+
print("Isolating edges")
|
|
224
|
+
|
|
225
|
+
if netlists is None:
|
|
226
|
+
|
|
227
|
+
master_list = read_excel_to_lists(network)
|
|
228
|
+
else:
|
|
229
|
+
master_list = netlists
|
|
230
|
+
|
|
231
|
+
if directory is None:
|
|
232
|
+
comp_list = read_excel_to_lists(iso_network)
|
|
233
|
+
else:
|
|
234
|
+
comp_list = read_excel_to_lists(f"{directory}/{iso_network}")
|
|
235
|
+
|
|
236
|
+
edge_list = []
|
|
237
|
+
|
|
238
|
+
node_1 = master_list[0]
|
|
239
|
+
|
|
240
|
+
node_2 = master_list[1]
|
|
241
|
+
|
|
242
|
+
edges_list = master_list[2]
|
|
243
|
+
|
|
244
|
+
nodes_1 = comp_list[0]
|
|
245
|
+
|
|
246
|
+
nodes_2 = comp_list[1]
|
|
247
|
+
|
|
248
|
+
compare_list = []
|
|
249
|
+
|
|
250
|
+
iso_list = []
|
|
251
|
+
|
|
252
|
+
output_list = []
|
|
253
|
+
|
|
254
|
+
for i in range(len(nodes_1)):
|
|
255
|
+
item = [nodes_1[i], nodes_2[i]]
|
|
256
|
+
iso_list.append(item)
|
|
257
|
+
|
|
258
|
+
for i in range(len(node_1)):
|
|
259
|
+
|
|
260
|
+
item = [node_1[i], node_2[i]]
|
|
261
|
+
|
|
262
|
+
compare_list.append(item)
|
|
263
|
+
|
|
264
|
+
for i in range(len(iso_list)):
|
|
265
|
+
|
|
266
|
+
for k, item in enumerate(compare_list):
|
|
267
|
+
if item == iso_list[i] or [item[1], item[0]] == iso_list[i]:
|
|
268
|
+
add_item = [nodes_1[i], nodes_2[i], edges_list[k]]
|
|
269
|
+
if add_item in output_list:
|
|
270
|
+
break
|
|
271
|
+
else:
|
|
272
|
+
output_list.append(add_item)
|
|
273
|
+
|
|
274
|
+
edge_list.append(edges_list[k])
|
|
275
|
+
|
|
276
|
+
# Convert to a DataFrame
|
|
277
|
+
edges_df = pd.DataFrame(output_list, columns=["Node A", "Node B", "Edge C"])
|
|
278
|
+
|
|
279
|
+
if directory is None:
|
|
280
|
+
# Save to an Excel file
|
|
281
|
+
edges_df.to_excel(f"{iso_network}", index=False)
|
|
282
|
+
print(f"Isolated network file saved to {iso_network}")
|
|
283
|
+
else:
|
|
284
|
+
edges_df.to_excel(f"{directory}/{iso_network}", index=False)
|
|
285
|
+
print(f"Isolated network file saved to {directory}/{iso_network}")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
mask2 = labels_to_boolean(edges, edge_list)
|
|
289
|
+
|
|
290
|
+
mask2 = mask2 * edges
|
|
291
|
+
|
|
292
|
+
# Convert boolean values to 0 and 255
|
|
293
|
+
#mask2 = mask2.astype(np.uint8) * 255
|
|
294
|
+
|
|
295
|
+
if directory is None:
|
|
296
|
+
|
|
297
|
+
tifffile.imwrite(f"edges_for_{iso_network}.tif", mask2)
|
|
298
|
+
print(f"Computational edge mask saved to edges_for_{iso_network}.tif")
|
|
299
|
+
else:
|
|
300
|
+
tifffile.imwrite(f"{directory}/edges_for_{iso_network}.tif", mask2)
|
|
301
|
+
print(f"Computational edge mask saved to {directory}/edges_for_{iso_network}.tif")
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
return output_list, mask2
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def isolate_connected_component(nodes, excel, key=None, edge_file = None, netlists = None, search_region = None, directory = None):
|
|
308
|
+
|
|
309
|
+
structure_3d = np.ones((3, 3, 3), dtype=int)
|
|
310
|
+
|
|
311
|
+
if edge_file is not None and type(edge_file) == str:
|
|
312
|
+
edge_file = tifffile.imread(edge_file)
|
|
313
|
+
|
|
314
|
+
if type(nodes) == str:
|
|
315
|
+
nodes = tifffile.imread(nodes)
|
|
316
|
+
if len(np.unique(nodes)) == 2:
|
|
317
|
+
nodes, num_nodes = ndimage.label(nodes, structure=structure_3d)
|
|
318
|
+
|
|
319
|
+
if type(excel) == str:
|
|
320
|
+
G = open_network(excel)
|
|
321
|
+
else:
|
|
322
|
+
G = excel
|
|
323
|
+
|
|
324
|
+
if key is None:
|
|
325
|
+
print("Isolating nodes of largest connected component")
|
|
326
|
+
edges_df, mask, searchers = isolate_largest_connected(nodes, G, search_region = search_region, directory = directory)
|
|
327
|
+
if edge_file is not None:
|
|
328
|
+
output_list, mask2 = isolate_edges(edge_file, excel, 'largest_connected_component.xlsx', netlists = netlists, directory = directory)
|
|
329
|
+
#isolate_full_edges(nodes, edge_file, 'largest_connected_component')
|
|
330
|
+
return output_list, mask, mask2, searchers
|
|
331
|
+
else:
|
|
332
|
+
|
|
333
|
+
return edges_df, mask, searchers
|
|
334
|
+
|
|
335
|
+
else:
|
|
336
|
+
print("Isolating nodes of connected component containing specified key")
|
|
337
|
+
edges_df, mask, searchers = isolate_key_connected(nodes, G, key, search_region = search_region, directory = directory)
|
|
338
|
+
if edge_file is not None:
|
|
339
|
+
output_list, mask2 = isolate_edges(edge_file, excel, f'connected_component_containing_{key}_node.xlsx', netlists = netlists, directory = directory)
|
|
340
|
+
#isolate_full_edges(nodes, edge_file, 'connected_component_containing_specific_node')
|
|
341
|
+
return output_list, mask, mask2, searchers
|
|
342
|
+
else:
|
|
343
|
+
return edges_df, mask, searchers
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _isolate_connected(G, key = None):
|
|
347
|
+
|
|
348
|
+
if key is None:
|
|
349
|
+
connected_components = list(nx.connected_components(G))
|
|
350
|
+
Gcc = sorted(nx.connected_components(G), key=len, reverse=True)
|
|
351
|
+
G0 = G.subgraph(Gcc[0])
|
|
352
|
+
return G0
|
|
353
|
+
|
|
354
|
+
else:
|
|
355
|
+
# Get the connected component containing the specific node label
|
|
356
|
+
connected_component = nx.node_connected_component(G, key)
|
|
357
|
+
|
|
358
|
+
G0 = G.subgraph(connected_component)
|
|
359
|
+
return G0
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def isolate_largest_connected(masks, G, directory = None, search_region = None):
|
|
364
|
+
# Read the Excel file into a pandas DataFrame
|
|
365
|
+
|
|
366
|
+
# Get a list of connected components
|
|
367
|
+
connected_components = list(nx.connected_components(G))
|
|
368
|
+
Gcc = sorted(nx.connected_components(G), key=len, reverse=True)
|
|
369
|
+
G0 = G.subgraph(Gcc[0])
|
|
370
|
+
|
|
371
|
+
# Extract the edges of the largest connected component
|
|
372
|
+
edges_largest_component = list(G0.edges)
|
|
373
|
+
|
|
374
|
+
# Convert to a DataFrame
|
|
375
|
+
edges_df = pd.DataFrame(edges_largest_component, columns=["Node A", "Node B"])
|
|
376
|
+
|
|
377
|
+
if directory is None:
|
|
378
|
+
# Save to an Excel file
|
|
379
|
+
edges_df.to_excel("largest_connected_component.xlsx", index=False)
|
|
380
|
+
print("Largest component nodes saved to largest_connected_component.xlsx")
|
|
381
|
+
else:
|
|
382
|
+
edges_df.to_excel(f"{directory}/largest_connected_component.xlsx", index=False)
|
|
383
|
+
print(f"Largest component nodes saved to {directory}/largest_connected_component.xlsx")
|
|
384
|
+
|
|
385
|
+
nodes_in_largest_component = list(G0)
|
|
386
|
+
|
|
387
|
+
mask2 = labels_to_boolean(masks, nodes_in_largest_component)
|
|
388
|
+
|
|
389
|
+
mask2 = mask2 * masks
|
|
390
|
+
|
|
391
|
+
if search_region is not None:
|
|
392
|
+
searchers = labels_to_boolean(search_region, nodes_in_largest_component)
|
|
393
|
+
searchers = searchers * search_region
|
|
394
|
+
else:
|
|
395
|
+
searchers = None
|
|
396
|
+
|
|
397
|
+
if directory is None:
|
|
398
|
+
tifffile.imwrite("largest_connected_component.tif", mask2)
|
|
399
|
+
print(f"Largest connected component image saved to largest_connected_component.tif")
|
|
400
|
+
|
|
401
|
+
else:
|
|
402
|
+
tifffile.imwrite(f"{directory}/largest_connected_component.tif", mask2)
|
|
403
|
+
print(f"Largest connected component image saved to {directory}/largest_connected_component.tif")
|
|
404
|
+
|
|
405
|
+
return edges_largest_component, mask2, searchers
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def isolate_key_connected(masks, G, key, search_region = None, directory = None):
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
# Get the connected component containing the specific node label
|
|
413
|
+
connected_component = nx.node_connected_component(G, key)
|
|
414
|
+
|
|
415
|
+
G0 = G.subgraph(connected_component)
|
|
416
|
+
|
|
417
|
+
# Extract the edges of the largest connected component
|
|
418
|
+
edges_component = list(G0.edges)
|
|
419
|
+
|
|
420
|
+
# Convert to a DataFrame
|
|
421
|
+
edges_df = pd.DataFrame(edges_component, columns=["Node A", "Node B"])
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
if directory is None:
|
|
425
|
+
# Save to an Excel file
|
|
426
|
+
edges_df.to_excel(f"connected_component_containing_{key}_node.xlsx", index=False)
|
|
427
|
+
print(f"Nodes containing {key} saved to connected_component_containing_{key}_node.xlsx")
|
|
428
|
+
else:
|
|
429
|
+
edges_df.to_excel(f"{directory}/connected_component_containing_{key}_node.xlsx", index=False)
|
|
430
|
+
print(f"Nodes containing {key} saved to {directory}/connected_component_containing_{key}_node.xlsx")
|
|
431
|
+
|
|
432
|
+
# Convert the set of nodes to a list
|
|
433
|
+
nodes_in_component = list(connected_component)
|
|
434
|
+
|
|
435
|
+
mask2 = labels_to_boolean(masks, nodes_in_component)
|
|
436
|
+
|
|
437
|
+
mask2 = mask2 * masks
|
|
438
|
+
|
|
439
|
+
# Convert boolean values to 0 and 255
|
|
440
|
+
#mask2 = mask2.astype(np.uint8) * 255
|
|
441
|
+
|
|
442
|
+
if search_region is not None:
|
|
443
|
+
searchers = labels_to_boolean(search_region, nodes_in_component)
|
|
444
|
+
searchers = searchers * search_region
|
|
445
|
+
else:
|
|
446
|
+
searchers = None
|
|
447
|
+
|
|
448
|
+
if directory is None:
|
|
449
|
+
tifffile.imwrite(f"connected_component_containing_{key}_node.tif", mask2)
|
|
450
|
+
print(f"Connected component containing node {key} saved to connected_component_containing_{key}_node.tif")
|
|
451
|
+
|
|
452
|
+
else:
|
|
453
|
+
tifffile.imwrite(f"{directory}/connected_component_containing_{key}_node.tif", mask2)
|
|
454
|
+
print(f"Connected component containing node {key} saved to {directory}/connected_component_containing_{key}_node.tif")
|
|
455
|
+
|
|
456
|
+
return nodes_in_component, mask2, searchers
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def extract_mothers(nodes, excel_file_path, centroid_dic = None, directory = None, louvain = True):
|
|
460
|
+
|
|
461
|
+
if type(nodes) == str:
|
|
462
|
+
nodes = tifffile.imread(nodes)
|
|
463
|
+
|
|
464
|
+
if np.unique(nodes) < 3:
|
|
465
|
+
structure_3d = np.ones((3, 3, 3), dtype=int)
|
|
466
|
+
nodes, num_nodes = ndimage.label(nodes, structure=structure_3d)
|
|
467
|
+
|
|
468
|
+
if type(excel_file_path) == str:
|
|
469
|
+
G, edge_weights = weighted_network(excel_file_path)
|
|
470
|
+
else:
|
|
471
|
+
G = excel_file_path
|
|
472
|
+
|
|
473
|
+
if louvain:
|
|
474
|
+
# Apply the Louvain algorithm for community detection
|
|
475
|
+
partition = community_louvain.best_partition(G)
|
|
476
|
+
else:
|
|
477
|
+
some_communities = list(nx.community.label_propagation_communities(G))
|
|
478
|
+
partition = {}
|
|
479
|
+
for i, community in enumerate(some_communities):
|
|
480
|
+
for node in community:
|
|
481
|
+
partition[node] = i + 1
|
|
482
|
+
|
|
483
|
+
my_nodes = get_border_nodes(partition, G)
|
|
484
|
+
|
|
485
|
+
mother_nodes = list(my_nodes)
|
|
486
|
+
|
|
487
|
+
if centroid_dic is None:
|
|
488
|
+
for item in nodes.shape:
|
|
489
|
+
if item < 5:
|
|
490
|
+
down_factor = 1
|
|
491
|
+
break
|
|
492
|
+
else:
|
|
493
|
+
down_factor = 5
|
|
494
|
+
|
|
495
|
+
smalls2 = downsample(nodes, down_factor)
|
|
496
|
+
|
|
497
|
+
centroid_dic = {}
|
|
498
|
+
|
|
499
|
+
for item in mother_nodes:
|
|
500
|
+
centroid = compute_centroid(smalls2, item)
|
|
501
|
+
centroid_dic[item] = centroid
|
|
502
|
+
|
|
503
|
+
mother_dict = {}
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
for node in mother_nodes:
|
|
507
|
+
mother_dict[node] = G.degree(node)
|
|
508
|
+
|
|
509
|
+
#mask2 = labels_to_boolean(nodes, mother_nodes)
|
|
510
|
+
|
|
511
|
+
smalls = labels_to_boolean(nodes, mother_nodes)
|
|
512
|
+
|
|
513
|
+
# Convert boolean values to 0 and 255
|
|
514
|
+
mask = smalls * nodes
|
|
515
|
+
|
|
516
|
+
labels = node_draw.degree_draw(mother_dict, centroid_dic, smalls)
|
|
517
|
+
|
|
518
|
+
# Convert dictionary to DataFrame with keys as index and values as a column
|
|
519
|
+
df = pd.DataFrame.from_dict(mother_dict, orient='index', columns=['Degree'])
|
|
520
|
+
|
|
521
|
+
# Rename the index to 'Node ID'
|
|
522
|
+
df.index.name = 'Node ID'
|
|
523
|
+
|
|
524
|
+
if directory is None:
|
|
525
|
+
|
|
526
|
+
# Save DataFrame to Excel file
|
|
527
|
+
df.to_excel('mothers.xlsx', engine='openpyxl')
|
|
528
|
+
print("Mother list saved to mothers.xlsx")
|
|
529
|
+
else:
|
|
530
|
+
df.to_excel(f'{directory}/mothers.xlsx', engine='openpyxl')
|
|
531
|
+
print(f"Mother list saved to {directory}/mothers.xlsx")
|
|
532
|
+
|
|
533
|
+
if directory is None:
|
|
534
|
+
|
|
535
|
+
tifffile.imwrite("mother_nodes.tif", mask)
|
|
536
|
+
print("Mother nodes saved to mother_nodes.tif")
|
|
537
|
+
tifffile.imwrite("mother_degree_labels.tif", labels)
|
|
538
|
+
print(f"Mother degree labels saved to mother_degree_labels.tif")
|
|
539
|
+
|
|
540
|
+
else:
|
|
541
|
+
tifffile.imwrite(f"{directory}/mother_nodes.tif", mask)
|
|
542
|
+
print(f"Mother nodes saved to {directory}/mother_nodes.tif")
|
|
543
|
+
tifffile.imwrite(f"{directory}/mother_degree_labels.tif", labels)
|
|
544
|
+
print(f"Mother degree labels saved to {directory}/mother_degree_labels.tif")
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
smalls = node_draw.degree_infect(mother_dict, mask)
|
|
548
|
+
|
|
549
|
+
if directory is None:
|
|
550
|
+
|
|
551
|
+
tifffile.imwrite("mother_degree_labels_grayscale.tif", smalls)
|
|
552
|
+
print("Mother graycale degree labels saved to mother_degree_labels_grayscale.tif")
|
|
553
|
+
|
|
554
|
+
else:
|
|
555
|
+
tifffile.imwrite(f"{directory}/mother_degree_labels_grayscale.tif", smalls)
|
|
556
|
+
print(f"Mother graycale degree labels saved to {directory}/mother_degree_labels_grayscale.tif")
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
return mother_nodes
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
if __name__ == "__main__":
|
|
567
|
+
|
|
568
|
+
# Read the Excel file into a pandas DataFrame
|
|
569
|
+
excel_file_path = input("Excel file?: ")
|
|
570
|
+
masks = input("watershedded, dilated glom mask?: ")
|
|
571
|
+
masks = tifffile.imread(masks)
|
|
572
|
+
masks = masks.astype(np.uint16)
|
|
573
|
+
|
|
574
|
+
G = open_network(excel_file_path)
|
|
575
|
+
|
|
576
|
+
# Get a list of connected components
|
|
577
|
+
connected_components = list(nx.connected_components(G))
|
|
578
|
+
|
|
579
|
+
largest_component = max(connected_components, key=len)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
# Choose a specific connected component (let's say, the first one)
|
|
583
|
+
#selected_component = connected_components[0]
|
|
584
|
+
|
|
585
|
+
# Convert the set of nodes to a list
|
|
586
|
+
#nodes_in_component = list(selected_component)
|
|
587
|
+
|
|
588
|
+
nodes_in_largest_component = list(largest_component)
|
|
589
|
+
|
|
590
|
+
mask2 = labels_to_boolean(masks, nodes_in_largest_component)
|
|
591
|
+
|
|
592
|
+
# Convert boolean values to 0 and 255
|
|
593
|
+
mask2 = mask2.astype(np.uint8) * 255
|
|
594
|
+
|
|
595
|
+
tifffile.imwrite("isolated_community.tif", mask2)
|