nettracer3d 0.9.4__py3-none-any.whl → 0.9.6__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 +88 -25
- nettracer3d/neighborhoods.py +17 -213
- nettracer3d/nettracer.py +221 -228
- nettracer3d/nettracer_gui.py +812 -285
- nettracer3d/proximity.py +91 -1
- {nettracer3d-0.9.4.dist-info → nettracer3d-0.9.6.dist-info}/METADATA +7 -3
- {nettracer3d-0.9.4.dist-info → nettracer3d-0.9.6.dist-info}/RECORD +11 -11
- {nettracer3d-0.9.4.dist-info → nettracer3d-0.9.6.dist-info}/WHEEL +0 -0
- {nettracer3d-0.9.4.dist-info → nettracer3d-0.9.6.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.9.4.dist-info → nettracer3d-0.9.6.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.9.4.dist-info → nettracer3d-0.9.6.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer.py
CHANGED
|
@@ -1267,31 +1267,6 @@ def dilate_2D(array, search, scaling = 1):
|
|
|
1267
1267
|
|
|
1268
1268
|
return inv
|
|
1269
1269
|
|
|
1270
|
-
def erode_2D(array, search, scaling=1):
|
|
1271
|
-
"""
|
|
1272
|
-
Erode a 2D array using distance transform method.
|
|
1273
|
-
|
|
1274
|
-
Parameters:
|
|
1275
|
-
array -- Input 2D binary array
|
|
1276
|
-
search -- Distance within which to erode
|
|
1277
|
-
scaling -- Scaling factor (default: 1)
|
|
1278
|
-
|
|
1279
|
-
Returns:
|
|
1280
|
-
Eroded 2D array
|
|
1281
|
-
"""
|
|
1282
|
-
# For erosion, we work directly with the foreground
|
|
1283
|
-
# No need to invert the array
|
|
1284
|
-
|
|
1285
|
-
# Compute distance transform on the foreground
|
|
1286
|
-
dt = smart_dilate.compute_distance_transform_distance(array)
|
|
1287
|
-
|
|
1288
|
-
# Apply scaling
|
|
1289
|
-
dt = dt * scaling
|
|
1290
|
-
|
|
1291
|
-
# Threshold to keep only points that are at least 'search' distance from the boundary
|
|
1292
|
-
eroded = dt >= search
|
|
1293
|
-
|
|
1294
|
-
return eroded
|
|
1295
1270
|
|
|
1296
1271
|
def dilate_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0):
|
|
1297
1272
|
"""
|
|
@@ -1353,7 +1328,42 @@ def dilate_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0):
|
|
|
1353
1328
|
|
|
1354
1329
|
return inv.astype(np.uint8)
|
|
1355
1330
|
|
|
1356
|
-
def
|
|
1331
|
+
def erode_2D(array, search, scaling=1, preserve_labels = False):
|
|
1332
|
+
"""
|
|
1333
|
+
Erode a 2D array using distance transform method.
|
|
1334
|
+
|
|
1335
|
+
Parameters:
|
|
1336
|
+
array -- Input 2D binary array
|
|
1337
|
+
search -- Distance within which to erode
|
|
1338
|
+
scaling -- Scaling factor (default: 1)
|
|
1339
|
+
|
|
1340
|
+
Returns:
|
|
1341
|
+
Eroded 2D array
|
|
1342
|
+
"""
|
|
1343
|
+
# For erosion, we work directly with the foreground
|
|
1344
|
+
# No need to invert the array
|
|
1345
|
+
|
|
1346
|
+
if preserve_labels:
|
|
1347
|
+
from skimage.segmentation import find_boundaries
|
|
1348
|
+
borders = find_boundaries(array, mode='thick')
|
|
1349
|
+
mask = array * invert_array(borders)
|
|
1350
|
+
mask = smart_dilate.compute_distance_transform_distance(mask)
|
|
1351
|
+
mask = mask * scaling
|
|
1352
|
+
mask = mask >= search
|
|
1353
|
+
array = mask * array
|
|
1354
|
+
else:
|
|
1355
|
+
# Compute distance transform on the foreground
|
|
1356
|
+
dt = smart_dilate.compute_distance_transform_distance(array)
|
|
1357
|
+
|
|
1358
|
+
# Apply scaling
|
|
1359
|
+
dt = dt * scaling
|
|
1360
|
+
|
|
1361
|
+
# Threshold to keep only points that are at least 'search' distance from the boundary
|
|
1362
|
+
array = dt > search
|
|
1363
|
+
|
|
1364
|
+
return array
|
|
1365
|
+
|
|
1366
|
+
def erode_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0, preserve_labels = False):
|
|
1357
1367
|
"""
|
|
1358
1368
|
Erode a 3D array using distance transform method. DT erosion produces perfect results
|
|
1359
1369
|
with Euclidean geometry, but may be slower for large arrays.
|
|
@@ -1371,43 +1381,24 @@ def erode_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0):
|
|
|
1371
1381
|
|
|
1372
1382
|
if array.shape[0] == 1:
|
|
1373
1383
|
# Handle 2D case
|
|
1374
|
-
return erode_2D(array, search_distance, scaling=xy_scaling)
|
|
1384
|
+
return erode_2D(array, search_distance, scaling=xy_scaling, preserve_labels = True)
|
|
1375
1385
|
|
|
1376
|
-
# For erosion, we work directly with the foreground (no inversion needed)
|
|
1377
|
-
|
|
1378
|
-
"""
|
|
1379
|
-
# Determine which dimension needs resampling
|
|
1380
|
-
if (z_scaling > xy_scaling):
|
|
1381
|
-
# Z dimension needs to be stretched
|
|
1382
|
-
zoom_factor = [z_scaling/xy_scaling, 1, 1] # Scale factor for [z, y, x]
|
|
1383
|
-
rev_factor = [xy_scaling/z_scaling, 1, 1]
|
|
1384
|
-
cardinal = xy_scaling
|
|
1385
|
-
elif (xy_scaling > z_scaling):
|
|
1386
|
-
# XY dimensions need to be stretched
|
|
1387
|
-
zoom_factor = [1, xy_scaling/z_scaling, xy_scaling/z_scaling] # Scale factor for [z, y, x]
|
|
1388
|
-
rev_factor = [1, z_scaling/xy_scaling, z_scaling/xy_scaling] # Scale factor for [z, y, x]
|
|
1389
|
-
cardinal = z_scaling
|
|
1390
|
-
else:
|
|
1391
|
-
# Already uniform scaling, no need to resample
|
|
1392
|
-
zoom_factor = None
|
|
1393
|
-
rev_factor = None
|
|
1394
|
-
cardinal = xy_scaling
|
|
1395
|
-
|
|
1396
|
-
# Resample the mask if needed
|
|
1397
|
-
if zoom_factor:
|
|
1398
|
-
array = ndimage.zoom(array, zoom_factor, order=0) # Use order=0 for binary masks
|
|
1399
|
-
"""
|
|
1400
|
-
|
|
1401
|
-
print("Computing a distance transform for a perfect erosion...")
|
|
1402
1386
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1387
|
+
if preserve_labels:
|
|
1388
|
+
|
|
1389
|
+
|
|
1390
|
+
from skimage.segmentation import find_boundaries
|
|
1391
|
+
|
|
1392
|
+
borders = find_boundaries(array, mode='thick')
|
|
1393
|
+
mask = array * invert_array(borders)
|
|
1394
|
+
mask = smart_dilate.compute_distance_transform_distance(mask, sampling = [z_scaling, xy_scaling, xy_scaling])
|
|
1395
|
+
mask = mask >= search_distance
|
|
1396
|
+
array = mask * array
|
|
1397
|
+
else:
|
|
1398
|
+
array = smart_dilate.compute_distance_transform_distance(array, sampling = [z_scaling, xy_scaling, xy_scaling])
|
|
1399
|
+
# Threshold the distance transform to get eroded result
|
|
1400
|
+
# For erosion, we keep only the points that are at least search_distance from the boundary
|
|
1401
|
+
array = array > search_distance
|
|
1411
1402
|
|
|
1412
1403
|
# Resample back to original dimensions if needed
|
|
1413
1404
|
#if rev_factor:
|
|
@@ -1574,7 +1565,7 @@ def dilate_3D_old(tiff_array, dilated_x=3, dilated_y=3, dilated_z=3):
|
|
|
1574
1565
|
|
|
1575
1566
|
|
|
1576
1567
|
def erode_3D(tiff_array, eroded_x, eroded_y, eroded_z):
|
|
1577
|
-
"""Internal method to erode an array in 3D. Erosion this way is
|
|
1568
|
+
"""Internal method to erode an array in 3D. Erosion this way is faster than using a distance transform although the latter is theoretically more accurate.
|
|
1578
1569
|
Arguments are an array, and the desired pixel erosion amounts in X, Y, Z."""
|
|
1579
1570
|
|
|
1580
1571
|
if tiff_array.shape[0] == 1:
|
|
@@ -1744,54 +1735,6 @@ def combine_edges(edge_labels_1, edge_labels_2):
|
|
|
1744
1735
|
|
|
1745
1736
|
return np.where(mask, offset_labels, edge_labels_1)
|
|
1746
1737
|
|
|
1747
|
-
def combine_nodes(root_nodes, other_nodes, other_ID, identity_dict, root_ID = None):
|
|
1748
|
-
|
|
1749
|
-
"""Internal method to merge two labelled node arrays into one"""
|
|
1750
|
-
|
|
1751
|
-
print("Combining node arrays")
|
|
1752
|
-
|
|
1753
|
-
mask = (root_nodes == 0) & (other_nodes > 0)
|
|
1754
|
-
if np.any(mask):
|
|
1755
|
-
max_val = np.max(root_nodes)
|
|
1756
|
-
other_nodes[:] = np.where(mask, other_nodes + max_val, 0)
|
|
1757
|
-
|
|
1758
|
-
if root_ID is not None:
|
|
1759
|
-
rootIDs = list(np.unique(root_nodes)) #Sets up adding these vals to the identitiy dictionary. Gets skipped if this has already been done.
|
|
1760
|
-
|
|
1761
|
-
if rootIDs[0] == 0: #np unique can include 0 which we don't want.
|
|
1762
|
-
del rootIDs[0]
|
|
1763
|
-
|
|
1764
|
-
otherIDs = list(np.unique(other_nodes)) #Sets up adding other vals to the identity dictionary.
|
|
1765
|
-
|
|
1766
|
-
if otherIDs[0] == 0:
|
|
1767
|
-
del otherIDs[0]
|
|
1768
|
-
|
|
1769
|
-
if root_ID is not None: #Adds the root vals to the dictionary if it hasn't already
|
|
1770
|
-
|
|
1771
|
-
if other_ID.endswith('.tiff'):
|
|
1772
|
-
other_ID = other_ID[:-5]
|
|
1773
|
-
elif other_ID.endswith('.tif'):
|
|
1774
|
-
other_ID = other_ID[:-4]
|
|
1775
|
-
|
|
1776
|
-
for item in rootIDs:
|
|
1777
|
-
identity_dict[item] = root_ID
|
|
1778
|
-
|
|
1779
|
-
for item in otherIDs: #Always adds the other vals to the dictionary
|
|
1780
|
-
try:
|
|
1781
|
-
other_ID = os.path.basename(other_ID)
|
|
1782
|
-
except:
|
|
1783
|
-
pass
|
|
1784
|
-
if other_ID.endswith('.tiff'):
|
|
1785
|
-
other_ID = other_ID[:-5]
|
|
1786
|
-
elif other_ID.endswith('.tif'):
|
|
1787
|
-
other_ID = other_ID[:-4]
|
|
1788
|
-
|
|
1789
|
-
identity_dict[item] = other_ID
|
|
1790
|
-
|
|
1791
|
-
nodes = root_nodes + other_nodes #Combine the outer edges with the inner edges modified via the above steps
|
|
1792
|
-
|
|
1793
|
-
return nodes, identity_dict
|
|
1794
|
-
|
|
1795
1738
|
def directory_info(directory = None):
|
|
1796
1739
|
"""Internal method to get the files in a directory, optionally the current directory if nothing passed"""
|
|
1797
1740
|
|
|
@@ -2124,15 +2067,15 @@ def dilate(arrayimage, amount, xy_scale = 1, z_scale = 1, directory = None, fast
|
|
|
2124
2067
|
|
|
2125
2068
|
return arrayimage
|
|
2126
2069
|
|
|
2127
|
-
def erode(arrayimage, amount, xy_scale = 1, z_scale = 1, mode = 0):
|
|
2128
|
-
if len(np.unique(arrayimage)) > 2: #binarize
|
|
2070
|
+
def erode(arrayimage, amount, xy_scale = 1, z_scale = 1, mode = 0, preserve_labels = False):
|
|
2071
|
+
if not preserve_labels and len(np.unique(arrayimage)) > 2: #binarize
|
|
2129
2072
|
arrayimage = binarize(arrayimage)
|
|
2130
2073
|
erode_xy, erode_z = dilation_length_to_pixels(xy_scale, z_scale, amount, amount)
|
|
2131
2074
|
|
|
2132
2075
|
if mode == 0:
|
|
2133
2076
|
arrayimage = (erode_3D(arrayimage, erode_xy, erode_xy, erode_z)) * 255
|
|
2134
2077
|
else:
|
|
2135
|
-
arrayimage = erode_3D_dt(arrayimage, amount, xy_scaling=xy_scale, z_scaling=z_scale)
|
|
2078
|
+
arrayimage = erode_3D_dt(arrayimage, amount, xy_scaling=xy_scale, z_scaling=z_scale, preserve_labels = preserve_labels)
|
|
2136
2079
|
|
|
2137
2080
|
if np.max(arrayimage) == 1:
|
|
2138
2081
|
arrayimage = arrayimage * 255
|
|
@@ -3847,7 +3790,7 @@ class Network_3D:
|
|
|
3847
3790
|
|
|
3848
3791
|
self._search_region = self._nodes
|
|
3849
3792
|
|
|
3850
|
-
def calculate_edges(self, binary_edges, diledge = None, inners = True,
|
|
3793
|
+
def calculate_edges(self, binary_edges, diledge = None, inners = True, search = None, remove_edgetrunk = 0, GPU = True, fast_dil = False, skeletonized = False):
|
|
3851
3794
|
"""
|
|
3852
3795
|
Method to calculate the edges that are used to directly connect nodes. May be done with or without the search region, however using search_region is recommended.
|
|
3853
3796
|
The search_region property must be set to use the search region, otherwise the nodes property must be set. Sets the edges property
|
|
@@ -3856,7 +3799,6 @@ class Network_3D:
|
|
|
3856
3799
|
so some amount of dilation is recommended if there are any, but not so much to create overconnectivity. This is a value that needs to be tuned by the user.
|
|
3857
3800
|
:param inners: (Optional - Val = True; boolean). Will use inner edges if True, will not if False. Inner edges are parts of the edge mask that exist within search regions. If search regions overlap,
|
|
3858
3801
|
any edges that exist within the overlap will only assert connectivity if 'inners' is True.
|
|
3859
|
-
:param hash_inner_edges: (Optional - Val = True; boolean). If False, all search regions that contain an edge object connecting multiple nodes will be assigned as connected.
|
|
3860
3802
|
If True, an extra processing step is used to sort the correct connectivity amongst these search_regions. Can only be computed when search_regions property is set.
|
|
3861
3803
|
:param search: (Optional - Val = None; int). Amount for nodes to search for connections, assuming the search_regions are not being used. Assigning a value to this param will utilize the secondary algorithm and not the search_regions.
|
|
3862
3804
|
:param remove_edgetrunk: (Optional - Val = 0; int). Amount of times to remove the 'Trunk' from the edges. A trunk in this case is the largest (by vol) edge object remaining after nodes have broken up the edges.
|
|
@@ -3909,11 +3851,7 @@ class Network_3D:
|
|
|
3909
3851
|
labelled_edges, num_edge = label_objects(outer_edges)
|
|
3910
3852
|
|
|
3911
3853
|
if inners:
|
|
3912
|
-
|
|
3913
|
-
if search is None and hash_inner_edges is True:
|
|
3914
|
-
inner_edges = hash_inners(self._search_region, binary_edges, GPU = GPU)
|
|
3915
|
-
else:
|
|
3916
|
-
inner_edges = establish_inner_edges(search_region, binary_edges)
|
|
3854
|
+
inner_edges = hash_inners(self._search_region, binary_edges, GPU = GPU)
|
|
3917
3855
|
|
|
3918
3856
|
del binary_edges
|
|
3919
3857
|
|
|
@@ -3939,7 +3877,58 @@ class Network_3D:
|
|
|
3939
3877
|
"""
|
|
3940
3878
|
self._nodes, num_nodes = label_objects(nodes, structure_3d)
|
|
3941
3879
|
|
|
3942
|
-
def
|
|
3880
|
+
def combine_nodes(self, root_nodes, other_nodes, other_ID, identity_dict, root_ID = None, centroids = False):
|
|
3881
|
+
|
|
3882
|
+
"""Internal method to merge two labelled node arrays into one"""
|
|
3883
|
+
|
|
3884
|
+
print("Combining node arrays")
|
|
3885
|
+
|
|
3886
|
+
mask = (root_nodes == 0) & (other_nodes > 0)
|
|
3887
|
+
if np.any(mask):
|
|
3888
|
+
max_val = np.max(root_nodes)
|
|
3889
|
+
other_nodes[:] = np.where(mask, other_nodes + max_val, 0)
|
|
3890
|
+
if centroids:
|
|
3891
|
+
new_dict = network_analysis._find_centroids(other_nodes)
|
|
3892
|
+
self.node_centroids.update(new_dict)
|
|
3893
|
+
|
|
3894
|
+
if root_ID is not None:
|
|
3895
|
+
rootIDs = list(np.unique(root_nodes)) #Sets up adding these vals to the identitiy dictionary. Gets skipped if this has already been done.
|
|
3896
|
+
|
|
3897
|
+
if rootIDs[0] == 0: #np unique can include 0 which we don't want.
|
|
3898
|
+
del rootIDs[0]
|
|
3899
|
+
|
|
3900
|
+
otherIDs = list(np.unique(other_nodes)) #Sets up adding other vals to the identity dictionary.
|
|
3901
|
+
|
|
3902
|
+
if otherIDs[0] == 0:
|
|
3903
|
+
del otherIDs[0]
|
|
3904
|
+
|
|
3905
|
+
if root_ID is not None: #Adds the root vals to the dictionary if it hasn't already
|
|
3906
|
+
|
|
3907
|
+
if other_ID.endswith('.tiff'):
|
|
3908
|
+
other_ID = other_ID[:-5]
|
|
3909
|
+
elif other_ID.endswith('.tif'):
|
|
3910
|
+
other_ID = other_ID[:-4]
|
|
3911
|
+
|
|
3912
|
+
for item in rootIDs:
|
|
3913
|
+
identity_dict[item] = root_ID
|
|
3914
|
+
|
|
3915
|
+
for item in otherIDs: #Always adds the other vals to the dictionary
|
|
3916
|
+
try:
|
|
3917
|
+
other_ID = os.path.basename(other_ID)
|
|
3918
|
+
except:
|
|
3919
|
+
pass
|
|
3920
|
+
if other_ID.endswith('.tiff'):
|
|
3921
|
+
other_ID = other_ID[:-5]
|
|
3922
|
+
elif other_ID.endswith('.tif'):
|
|
3923
|
+
other_ID = other_ID[:-4]
|
|
3924
|
+
|
|
3925
|
+
identity_dict[item] = other_ID
|
|
3926
|
+
|
|
3927
|
+
nodes = root_nodes + other_nodes #Combine the outer edges with the inner edges modified via the above steps
|
|
3928
|
+
|
|
3929
|
+
return nodes, identity_dict
|
|
3930
|
+
|
|
3931
|
+
def merge_nodes(self, addn_nodes_name, label_nodes = True, root_id = "Root_Nodes", centroids = False):
|
|
3943
3932
|
"""
|
|
3944
3933
|
Merges the self._nodes attribute with alternate labelled node images. The alternate nodes can be inputted as a string for a filepath to a tif,
|
|
3945
3934
|
or as a directory address containing only tif images, which will merge the _nodes attribute with all tifs in the folder. The _node_identities attribute
|
|
@@ -3959,16 +3948,20 @@ class Network_3D:
|
|
|
3959
3948
|
|
|
3960
3949
|
identity_dict = {} #A dictionary to deliniate the node identities
|
|
3961
3950
|
|
|
3951
|
+
if centroids:
|
|
3952
|
+
self.node_centroids = network_analysis._find_centroids(self._nodes)
|
|
3953
|
+
|
|
3954
|
+
|
|
3962
3955
|
try: #Try presumes the input is a tif
|
|
3963
3956
|
addn_nodes = tifffile.imread(addn_nodes_name) #If not this will fail and activate the except block
|
|
3964
3957
|
|
|
3965
3958
|
if label_nodes is True:
|
|
3966
3959
|
addn_nodes, num_nodes2 = label_objects(addn_nodes) # Label the node objects. Note this presumes no overlap between node masks.
|
|
3967
|
-
node_labels, identity_dict = combine_nodes(self._nodes, addn_nodes, addn_nodes_name, identity_dict, nodes_name) #This method stacks labelled arrays
|
|
3960
|
+
node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_name, identity_dict, nodes_name, centroids = centroids) #This method stacks labelled arrays
|
|
3968
3961
|
num_nodes = np.max(node_labels)
|
|
3969
3962
|
|
|
3970
3963
|
else: #If nodes already labelled
|
|
3971
|
-
node_labels, identity_dict = combine_nodes(self._nodes, addn_nodes, addn_nodes_name, identity_dict, nodes_name)
|
|
3964
|
+
node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_name, identity_dict, nodes_name, centroids = centroids)
|
|
3972
3965
|
num_nodes = int(np.max(node_labels))
|
|
3973
3966
|
|
|
3974
3967
|
except: #Exception presumes the input is a directory containing multiple tifs, to allow multi-node stackage.
|
|
@@ -3986,16 +3979,15 @@ class Network_3D:
|
|
|
3986
3979
|
if label_nodes is True:
|
|
3987
3980
|
addn_nodes, num_nodes2 = label_objects(addn_nodes) # Label the node objects. Note this presumes no overlap between node masks.
|
|
3988
3981
|
if i == 0:
|
|
3989
|
-
node_labels, identity_dict = combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name)
|
|
3990
|
-
|
|
3982
|
+
node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name, centroids = centroids)
|
|
3991
3983
|
else:
|
|
3992
|
-
node_labels, identity_dict = combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict)
|
|
3984
|
+
node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids)
|
|
3993
3985
|
|
|
3994
3986
|
else:
|
|
3995
3987
|
if i == 0:
|
|
3996
|
-
node_labels, identity_dict = combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name)
|
|
3988
|
+
node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name, centroids = centroids)
|
|
3997
3989
|
else:
|
|
3998
|
-
node_labels, identity_dict = combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict)
|
|
3990
|
+
node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids)
|
|
3999
3991
|
except Exception as e:
|
|
4000
3992
|
print("Could not open additional nodes, verify they are being inputted correctly...")
|
|
4001
3993
|
|
|
@@ -4045,7 +4037,7 @@ class Network_3D:
|
|
|
4045
4037
|
self._network_lists = network_analysis.read_excel_to_lists(df)
|
|
4046
4038
|
self._network, net_weights = network_analysis.weighted_network(df)
|
|
4047
4039
|
|
|
4048
|
-
def calculate_all(self, nodes, edges, xy_scale = 1, z_scale = 1, down_factor = None, search = None, diledge = None, inners = True,
|
|
4040
|
+
def calculate_all(self, nodes, edges, xy_scale = 1, z_scale = 1, down_factor = None, search = None, diledge = None, inners = True, remove_trunk = 0, ignore_search_region = False, other_nodes = None, label_nodes = True, directory = None, GPU = True, fast_dil = True, skeletonize = False, GPU_downsample = None):
|
|
4049
4041
|
"""
|
|
4050
4042
|
Method to calculate and save to mem all properties of a Network_3D object. In general, after initializing a Network_3D object, this method should be called on the node and edge masks that will be used to calculate the network.
|
|
4051
4043
|
:param nodes: (Mandatory; String or ndarray). Filepath to segmented nodes mask or a numpy array containing the same.
|
|
@@ -4058,7 +4050,6 @@ class Network_3D:
|
|
|
4058
4050
|
so some amount of dilation is recommended if there are any, but not so much to create overconnectivity. This is a value that needs to be tuned by the user.
|
|
4059
4051
|
:param inners: (Optional - Val = True; boolean). Will use inner edges if True, will not if False. Inner edges are parts of the edge mask that exist within search regions. If search regions overlap,
|
|
4060
4052
|
any edges that exist within the overlap will only assert connectivity if 'inners' is True.
|
|
4061
|
-
:param hash_inners: (Optional - Val = True; boolean). If False, all search regions that contain an edge object connecting multiple nodes will be assigned as connected.
|
|
4062
4053
|
If True, an extra processing step is used to sort the correct connectivity amongst these search_regions. Can only be computed when search_regions property is set.
|
|
4063
4054
|
:param remove_trunk: (Optional - Val = 0; int). Amount of times to remove the 'Trunk' from the edges. A trunk in this case is the largest (by vol) edge object remaining after nodes have broken up the edges.
|
|
4064
4055
|
Any 'Trunks' removed will be absent for connection calculations.
|
|
@@ -4120,7 +4111,7 @@ class Network_3D:
|
|
|
4120
4111
|
except:
|
|
4121
4112
|
pass
|
|
4122
4113
|
|
|
4123
|
-
self.calculate_edges(edges, diledge = diledge, inners = inners,
|
|
4114
|
+
self.calculate_edges(edges, diledge = diledge, inners = inners, search = search, remove_edgetrunk = remove_trunk, GPU = GPU, fast_dil = fast_dil, skeletonized = skeletonize) #Will have to be moved out if the second method becomes more directly implemented
|
|
4124
4115
|
else:
|
|
4125
4116
|
self._edges, _ = label_objects(edges)
|
|
4126
4117
|
|
|
@@ -5104,13 +5095,16 @@ class Network_3D:
|
|
|
5104
5095
|
|
|
5105
5096
|
|
|
5106
5097
|
for node in G.nodes():
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5098
|
+
try:
|
|
5099
|
+
nodeid = node_identities[node]
|
|
5100
|
+
neighbors = list(G.neighbors(node))
|
|
5101
|
+
for subnode in neighbors:
|
|
5102
|
+
subnodeid = node_identities[subnode]
|
|
5103
|
+
if subnodeid == root:
|
|
5104
|
+
neighborhood_dict[nodeid] += 1
|
|
5105
|
+
break
|
|
5106
|
+
except:
|
|
5107
|
+
pass
|
|
5114
5108
|
|
|
5115
5109
|
title1 = f'Neighborhood Distribution of Nodes in Network from Nodes: {root}'
|
|
5116
5110
|
title2 = f'Neighborhood Distribution of Nodes in Network from Nodes {root} as a proportion of total nodes of that ID'
|
|
@@ -5219,34 +5213,22 @@ class Network_3D:
|
|
|
5219
5213
|
bounds = (min_coords, max_coords)
|
|
5220
5214
|
dim_list = max_coords - min_coords
|
|
5221
5215
|
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
"""
|
|
5236
|
-
|
|
5237
|
-
for centroid in roots:
|
|
5238
|
-
|
|
5239
|
-
if ((centroid[2] - min_coords[0]) > dim_list[0] * factor) and ((max_coords[0] - centroid[2]) > dim_list[0] * factor) and ((centroid[1] - min_coords[1]) > dim_list[1] * factor) and ((max_coords[1] - centroid[1]) > dim_list[1] * factor) and ((centroid[0] - min_coords[2]) > dim_list[2] * factor) and ((max_coords[2] - centroid[0]) > dim_list[2] * factor):
|
|
5216
|
+
for centroid in roots:
|
|
5217
|
+
# Assuming centroid is [z, y, x] based on your indexing
|
|
5218
|
+
z, y, x = centroid[0], centroid[1], centroid[2]
|
|
5219
|
+
|
|
5220
|
+
# Check x-dimension
|
|
5221
|
+
x_ok = (x - min_coords[0]) > dim_list[0] * factor and (max_coords[0] - x) > dim_list[0] * factor
|
|
5222
|
+
# Check y-dimension
|
|
5223
|
+
y_ok = (y - min_coords[1]) > dim_list[1] * factor and (max_coords[1] - y) > dim_list[1] * factor
|
|
5224
|
+
|
|
5225
|
+
if dim == 3: # 3D case
|
|
5226
|
+
# Check z-dimension
|
|
5227
|
+
z_ok = (z - min_coords[2]) > dim_list[2] * factor and (max_coords[2] - z) > dim_list[2] * factor
|
|
5228
|
+
if x_ok and y_ok and z_ok:
|
|
5240
5229
|
new_list.append(centroid)
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
#if ((centroid[2] - min_coords[0]) > dim_list[0] * factor) and ((max_coords[0] - centroid[2]) > dim_list[0] * factor) and ((centroid[1] - min_coords[1]) > dim_list[1] * factor) and ((max_coords[1] - centroid[1]) > dim_list[1] * factor) and ((centroid[0] - min_coords[2]) > dim_list[2] * factor) and ((max_coords[2] - centroid[0]) > dim_list[2] * factor):
|
|
5244
|
-
#new_list.append(centroid)
|
|
5245
|
-
#print(f"dim_list: {dim_list}, centroid: {centroid}, min_coords: {min_coords}, max_coords: {max_coords}")
|
|
5246
|
-
else:
|
|
5247
|
-
for centroid in roots:
|
|
5248
|
-
|
|
5249
|
-
if ((centroid[2] - min_coords[0]) > dim_list[0] * factor) and ((max_coords[0] - centroid[2]) > dim_list[0] * factor) and ((centroid[1] - min_coords[1]) > dim_list[1] * factor) and ((max_coords[1] - centroid[1]) > dim_list[1] * factor):
|
|
5230
|
+
else: # 2D case
|
|
5231
|
+
if x_ok and y_ok:
|
|
5250
5232
|
new_list.append(centroid)
|
|
5251
5233
|
|
|
5252
5234
|
else:
|
|
@@ -5299,8 +5281,6 @@ class Network_3D:
|
|
|
5299
5281
|
print(f"Utilizing {len(roots)} root points. Note that low n values are unstable.")
|
|
5300
5282
|
is_subset = True
|
|
5301
5283
|
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
5284
|
roots = proximity.convert_centroids_to_array(roots, xy_scale = self.xy_scale, z_scale = self.z_scale)
|
|
5305
5285
|
|
|
5306
5286
|
n_subset = len(targs)
|
|
@@ -5531,55 +5511,42 @@ class Network_3D:
|
|
|
5531
5511
|
neighborhoods.visualize_cluster_composition_umap(self.node_centroids, None, id_dictionary = self.node_identities, graph_label = "Node ID", title = 'UMAP Visualization of Node Centroids')
|
|
5532
5512
|
|
|
5533
5513
|
|
|
5534
|
-
|
|
5535
|
-
def identity_umap(self):
|
|
5514
|
+
def identity_umap(self, data):
|
|
5536
5515
|
|
|
5537
5516
|
try:
|
|
5538
5517
|
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
template = np.zeros(len(id_set))
|
|
5518
|
+
neighbor_classes = {}
|
|
5519
|
+
import random
|
|
5542
5520
|
|
|
5543
|
-
|
|
5544
|
-
for i, iden in enumerate(id_set):
|
|
5545
|
-
id_dict[iden] = i
|
|
5521
|
+
umap_dict = copy.deepcopy(data)
|
|
5546
5522
|
|
|
5547
|
-
|
|
5523
|
+
for item in data.keys():
|
|
5524
|
+
if item in self.node_identities:
|
|
5525
|
+
try:
|
|
5526
|
+
parse = ast.literal_eval(self.node_identities[item])
|
|
5527
|
+
neighbor_classes[item] = random.choice(parse)
|
|
5528
|
+
except:
|
|
5529
|
+
neighbor_classes[item] = self.node_identities[item]
|
|
5548
5530
|
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
try:
|
|
5552
|
-
idens = ast.literal_eval(self.node_identities[node])
|
|
5553
|
-
for iden in idens:
|
|
5554
|
-
index = id_dict[iden]
|
|
5555
|
-
ref = umap_dict[node]
|
|
5556
|
-
ref[index] = 1
|
|
5557
|
-
umap_dict[node] = ref
|
|
5558
|
-
except:
|
|
5559
|
-
index = id_dict[self.node_identities[node]]
|
|
5560
|
-
ref = umap_dict[node]
|
|
5561
|
-
ref[index] = 1
|
|
5562
|
-
umap_dict[node] = ref
|
|
5531
|
+
else:
|
|
5532
|
+
del umap_dict[item]
|
|
5563
5533
|
|
|
5564
|
-
|
|
5565
|
-
import random
|
|
5534
|
+
from scipy.stats import zscore
|
|
5566
5535
|
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
neighbor_classes[node] = random.choice(idens)
|
|
5571
|
-
except:
|
|
5572
|
-
neighbor_classes[node] = iden
|
|
5536
|
+
# Z-score normalize each marker (column)
|
|
5537
|
+
for key in umap_dict:
|
|
5538
|
+
umap_dict[key] = zscore(umap_dict[key])
|
|
5573
5539
|
|
|
5574
5540
|
|
|
5575
5541
|
from . import neighborhoods
|
|
5576
5542
|
|
|
5577
|
-
neighborhoods.visualize_cluster_composition_umap(umap_dict, None, id_dictionary = neighbor_classes, graph_label = "Node ID", title = 'UMAP Visualization of Node Identities
|
|
5543
|
+
neighborhoods.visualize_cluster_composition_umap(umap_dict, None, id_dictionary = neighbor_classes, graph_label = "Node ID", title = 'UMAP Visualization of Node Identities by Z-Score')
|
|
5578
5544
|
|
|
5579
5545
|
except Exception as e:
|
|
5546
|
+
import traceback
|
|
5547
|
+
print(traceback.format_exc())
|
|
5580
5548
|
print(f"Error: {e}")
|
|
5581
5549
|
|
|
5582
|
-
|
|
5583
5550
|
def community_id_info_per_com(self, umap = False, label = 0, limit = 0, proportional = False, neighbors = None):
|
|
5584
5551
|
|
|
5585
5552
|
community_dict = invert_dict(self.communities)
|
|
@@ -5608,7 +5575,10 @@ class Network_3D:
|
|
|
5608
5575
|
for iden in idens:
|
|
5609
5576
|
counter[id_dict[iden]] += 1
|
|
5610
5577
|
except:
|
|
5611
|
-
|
|
5578
|
+
try:
|
|
5579
|
+
counter[id_dict[self.node_identities[node]]] += 1 # Keep them as arrays
|
|
5580
|
+
except:
|
|
5581
|
+
pass
|
|
5612
5582
|
|
|
5613
5583
|
for i in range(len(counter)): # Translate them into proportions out of 1
|
|
5614
5584
|
|
|
@@ -5645,7 +5615,10 @@ class Network_3D:
|
|
|
5645
5615
|
for iden in idents:
|
|
5646
5616
|
iden_tracker[iden] += 1
|
|
5647
5617
|
except:
|
|
5648
|
-
|
|
5618
|
+
try:
|
|
5619
|
+
iden_tracker[self.node_identities[node]] += 1
|
|
5620
|
+
except:
|
|
5621
|
+
pass
|
|
5649
5622
|
|
|
5650
5623
|
i = 0
|
|
5651
5624
|
|
|
@@ -5915,6 +5888,23 @@ class Network_3D:
|
|
|
5915
5888
|
overlay = neighborhoods.create_community_heatmap(heat_dict, self.communities, self.node_centroids, shape = shape, is_3d=is3d, labeled_array = self.nodes)
|
|
5916
5889
|
return heat_dict, overlay
|
|
5917
5890
|
|
|
5891
|
+
def get_merge_node_dictionaries(self, path, data):
|
|
5892
|
+
|
|
5893
|
+
img_list = directory_info(path)
|
|
5894
|
+
id_dicts = []
|
|
5895
|
+
num_nodes = np.max(data)
|
|
5896
|
+
|
|
5897
|
+
for i, img in enumerate(img_list):
|
|
5898
|
+
if img.endswith('.tiff') or img.endswith('.tif'):
|
|
5899
|
+
print(f"Processing image {img}")
|
|
5900
|
+
mask = tifffile.imread(f'{path}/{img}')
|
|
5901
|
+
if len(mask.shape) == 2:
|
|
5902
|
+
mask = np.expand_dims(mask, axis = 0)
|
|
5903
|
+
|
|
5904
|
+
id_dict = proximity.create_node_dictionary_id(data, mask, num_nodes)
|
|
5905
|
+
id_dicts.append(id_dict)
|
|
5906
|
+
|
|
5907
|
+
return id_dicts
|
|
5918
5908
|
|
|
5919
5909
|
def merge_node_ids(self, path, data, include = True):
|
|
5920
5910
|
|
|
@@ -5940,46 +5930,49 @@ class Network_3D:
|
|
|
5940
5930
|
img_list = directory_info(path)
|
|
5941
5931
|
|
|
5942
5932
|
for i, img in enumerate(img_list):
|
|
5943
|
-
mask = tifffile.imread(f'{path}/{img}')
|
|
5944
5933
|
|
|
5945
|
-
if
|
|
5934
|
+
if img.endswith('.tiff') or img.endswith('.tif'):
|
|
5946
5935
|
|
|
5947
|
-
mask =
|
|
5948
|
-
else:
|
|
5949
|
-
mask = mask != 0
|
|
5936
|
+
mask = tifffile.imread(f'{path}/{img}')
|
|
5950
5937
|
|
|
5951
|
-
|
|
5952
|
-
nodes = np.unique(nodes)
|
|
5953
|
-
nodes = nodes.tolist()
|
|
5954
|
-
if 0 in nodes:
|
|
5955
|
-
del nodes[0]
|
|
5938
|
+
if len(np.unique(mask)) != 2:
|
|
5956
5939
|
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5940
|
+
mask = otsu_binarize(mask)
|
|
5941
|
+
else:
|
|
5942
|
+
mask = mask != 0
|
|
5943
|
+
|
|
5944
|
+
nodes = data * mask
|
|
5945
|
+
nodes = np.unique(nodes)
|
|
5946
|
+
nodes = nodes.tolist()
|
|
5947
|
+
if 0 in nodes:
|
|
5948
|
+
del nodes[0]
|
|
5949
|
+
|
|
5950
|
+
if img.endswith('.tiff'):
|
|
5951
|
+
base_name = img[:-5]
|
|
5952
|
+
elif img.endswith('.tif'):
|
|
5953
|
+
base_name = img[:-4]
|
|
5954
|
+
else:
|
|
5955
|
+
base_name = img
|
|
5963
5956
|
|
|
5964
|
-
|
|
5957
|
+
assigned = {}
|
|
5965
5958
|
|
|
5966
5959
|
|
|
5967
|
-
|
|
5960
|
+
for node in self.node_identities.keys():
|
|
5968
5961
|
|
|
5969
|
-
|
|
5962
|
+
try:
|
|
5970
5963
|
|
|
5971
|
-
|
|
5964
|
+
if int(node) in nodes:
|
|
5972
5965
|
|
|
5973
|
-
|
|
5966
|
+
self.node_identities[node].append(f'{base_name}+')
|
|
5974
5967
|
|
|
5975
|
-
|
|
5968
|
+
elif include:
|
|
5976
5969
|
|
|
5977
|
-
|
|
5970
|
+
self.node_identities[node].append(f'{base_name}-')
|
|
5978
5971
|
|
|
5979
|
-
|
|
5980
|
-
|
|
5972
|
+
except:
|
|
5973
|
+
pass
|
|
5981
5974
|
|
|
5982
|
-
|
|
5975
|
+
modify_dict = copy.deepcopy(self.node_identities)
|
|
5983
5976
|
|
|
5984
5977
|
for node, iden in self.node_identities.items():
|
|
5985
5978
|
|