nettracer3d 0.7.6__py3-none-any.whl → 0.7.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.
Potentially problematic release.
This version of nettracer3d might be problematic. Click here for more details.
- nettracer3d/community_extractor.py +13 -29
- nettracer3d/excelotron.py +1719 -0
- nettracer3d/modularity.py +6 -9
- nettracer3d/neighborhoods.py +354 -0
- nettracer3d/nettracer.py +400 -13
- nettracer3d/nettracer_gui.py +1120 -229
- nettracer3d/proximity.py +89 -9
- nettracer3d/smart_dilate.py +20 -15
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/METADATA +11 -2
- nettracer3d-0.7.8.dist-info/RECORD +23 -0
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/WHEEL +1 -1
- nettracer3d-0.7.6.dist-info/RECORD +0 -21
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer.py
CHANGED
|
@@ -348,6 +348,7 @@ def create_and_save_dataframe(pairwise_connections, excel_filename = None):
|
|
|
348
348
|
|
|
349
349
|
#General supporting methods below:
|
|
350
350
|
|
|
351
|
+
|
|
351
352
|
def invert_array(array):
|
|
352
353
|
"""Internal method used to flip node array indices. 0 becomes 255 and vice versa."""
|
|
353
354
|
inverted_array = np.where(array == 0, 255, 0).astype(np.uint8)
|
|
@@ -658,7 +659,84 @@ def threshold(arr, proportion, custom_rad = None):
|
|
|
658
659
|
|
|
659
660
|
return arr
|
|
660
661
|
|
|
661
|
-
def
|
|
662
|
+
def generate_3d_bounding_box(shape, foreground_value=1, background_value=0):
|
|
663
|
+
"""
|
|
664
|
+
Generate a 3D bounding box array with edges connecting the corners.
|
|
665
|
+
|
|
666
|
+
Parameters:
|
|
667
|
+
-----------
|
|
668
|
+
shape : tuple
|
|
669
|
+
Shape of the array in format (Z, Y, X)
|
|
670
|
+
foreground_value : int or float, default=1
|
|
671
|
+
Value to use for the bounding box edges and corners
|
|
672
|
+
background_value : int or float, default=0
|
|
673
|
+
Value to use for the background
|
|
674
|
+
|
|
675
|
+
Returns:
|
|
676
|
+
--------
|
|
677
|
+
numpy.ndarray
|
|
678
|
+
3D array with bounding box edges
|
|
679
|
+
"""
|
|
680
|
+
if len(shape) > 3:
|
|
681
|
+
shape = (shape[0], shape[1], shape[2])
|
|
682
|
+
|
|
683
|
+
z_size, y_size, x_size = shape
|
|
684
|
+
|
|
685
|
+
# Create empty array filled with background value
|
|
686
|
+
box_array = np.full(shape, background_value, dtype=np.float64)
|
|
687
|
+
|
|
688
|
+
# Define the 8 corners of the 3D box
|
|
689
|
+
corners = [
|
|
690
|
+
(0, 0, 0), # corner 0
|
|
691
|
+
(0, 0, x_size-1), # corner 1
|
|
692
|
+
(0, y_size-1, 0), # corner 2
|
|
693
|
+
(0, y_size-1, x_size-1), # corner 3
|
|
694
|
+
(z_size-1, 0, 0), # corner 4
|
|
695
|
+
(z_size-1, 0, x_size-1), # corner 5
|
|
696
|
+
(z_size-1, y_size-1, 0), # corner 6
|
|
697
|
+
(z_size-1, y_size-1, x_size-1) # corner 7
|
|
698
|
+
]
|
|
699
|
+
|
|
700
|
+
# Set corner values
|
|
701
|
+
for corner in corners:
|
|
702
|
+
box_array[corner] = foreground_value
|
|
703
|
+
|
|
704
|
+
# Define edges connecting adjacent corners
|
|
705
|
+
# Each edge connects two corners that differ by only one coordinate
|
|
706
|
+
edges = [
|
|
707
|
+
# Bottom face edges (z=0)
|
|
708
|
+
(0, 1), (1, 3), (3, 2), (2, 0),
|
|
709
|
+
# Top face edges (z=max)
|
|
710
|
+
(4, 5), (5, 7), (7, 6), (6, 4),
|
|
711
|
+
# Vertical edges connecting bottom to top
|
|
712
|
+
(0, 4), (1, 5), (2, 6), (3, 7)
|
|
713
|
+
]
|
|
714
|
+
|
|
715
|
+
# Draw edges using linspace
|
|
716
|
+
for start_idx, end_idx in edges:
|
|
717
|
+
start_corner = corners[start_idx]
|
|
718
|
+
end_corner = corners[end_idx]
|
|
719
|
+
|
|
720
|
+
# Calculate the maximum distance along any axis to determine number of points
|
|
721
|
+
max_distance = max(
|
|
722
|
+
abs(end_corner[0] - start_corner[0]),
|
|
723
|
+
abs(end_corner[1] - start_corner[1]),
|
|
724
|
+
abs(end_corner[2] - start_corner[2])
|
|
725
|
+
)
|
|
726
|
+
num_points = max_distance + 1
|
|
727
|
+
|
|
728
|
+
# Generate points along the edge using linspace
|
|
729
|
+
z_points = np.linspace(start_corner[0], end_corner[0], num_points, dtype=int)
|
|
730
|
+
y_points = np.linspace(start_corner[1], end_corner[1], num_points, dtype=int)
|
|
731
|
+
x_points = np.linspace(start_corner[2], end_corner[2], num_points, dtype=int)
|
|
732
|
+
|
|
733
|
+
# Set foreground values along the edge
|
|
734
|
+
for z, y, x in zip(z_points, y_points, x_points):
|
|
735
|
+
box_array[int(z), int(y), int(x)] = foreground_value
|
|
736
|
+
|
|
737
|
+
return box_array
|
|
738
|
+
|
|
739
|
+
def show_3d(arrays_3d=None, arrays_4d=None, down_factor=None, order=0, xy_scale=1, z_scale=1, colors=['red', 'green', 'white', 'cyan', 'yellow'], box = False):
|
|
662
740
|
"""
|
|
663
741
|
Show 3d (or 2d) displays of array data using napari.
|
|
664
742
|
Params: arrays - A list of 3d or 2d numpy arrays to display
|
|
@@ -682,6 +760,7 @@ def show_3d(arrays_3d=None, arrays_4d=None, down_factor=None, order=0, xy_scale=
|
|
|
682
760
|
# Add 3D arrays if provided
|
|
683
761
|
if arrays_3d is not None:
|
|
684
762
|
for arr, color in zip(arrays_3d, colors):
|
|
763
|
+
shape = arr.shape
|
|
685
764
|
viewer.add_image(
|
|
686
765
|
arr,
|
|
687
766
|
scale=scale,
|
|
@@ -702,6 +781,8 @@ def show_3d(arrays_3d=None, arrays_4d=None, down_factor=None, order=0, xy_scale=
|
|
|
702
781
|
if arr.shape[3] == 4:
|
|
703
782
|
arr = arr[:, :, :, :3] # Remove alpha
|
|
704
783
|
|
|
784
|
+
shape = arr.shape
|
|
785
|
+
|
|
705
786
|
# Add each color channel separately
|
|
706
787
|
colors = ['red', 'green', 'blue']
|
|
707
788
|
for c in range(3):
|
|
@@ -715,6 +796,19 @@ def show_3d(arrays_3d=None, arrays_4d=None, down_factor=None, order=0, xy_scale=
|
|
|
715
796
|
name=f'Channel_{colors[c]}_{i}'
|
|
716
797
|
)
|
|
717
798
|
|
|
799
|
+
if box:
|
|
800
|
+
viewer.add_image(
|
|
801
|
+
generate_3d_bounding_box(shape),
|
|
802
|
+
scale=scale,
|
|
803
|
+
colormap='white',
|
|
804
|
+
rendering='mip',
|
|
805
|
+
blending='additive',
|
|
806
|
+
opacity=0.5,
|
|
807
|
+
name=f'Bounding Box'
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
|
|
718
812
|
napari.run()
|
|
719
813
|
|
|
720
814
|
def z_project(array3d, method='max'):
|
|
@@ -1673,20 +1767,16 @@ def label_branches(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol =
|
|
|
1673
1767
|
if nodes is not None and down_factor is not None:
|
|
1674
1768
|
array = upsample_with_padding(array, down_factor, arrayshape)
|
|
1675
1769
|
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
1770
|
if nodes is None:
|
|
1679
1771
|
|
|
1680
|
-
array = smart_dilate.smart_label(array, other_array, GPU = GPU)
|
|
1772
|
+
array = smart_dilate.smart_label(array, other_array, GPU = GPU, remove_template = True)
|
|
1681
1773
|
|
|
1682
1774
|
else:
|
|
1683
1775
|
if down_factor is not None:
|
|
1684
|
-
array = smart_dilate.smart_label(bonus_array, array, GPU = GPU, predownsample = down_factor)
|
|
1776
|
+
array = smart_dilate.smart_label(bonus_array, array, GPU = GPU, predownsample = down_factor, remove_template = True)
|
|
1685
1777
|
else:
|
|
1686
1778
|
|
|
1687
|
-
array = smart_dilate.smart_label(bonus_array, array, GPU = GPU)
|
|
1688
|
-
|
|
1689
|
-
|
|
1779
|
+
array = smart_dilate.smart_label(bonus_array, array, GPU = GPU, remove_template = True)
|
|
1690
1780
|
|
|
1691
1781
|
if down_factor is not None and nodes is None:
|
|
1692
1782
|
array = upsample_with_padding(array, down_factor, arrayshape)
|
|
@@ -1705,7 +1795,7 @@ def label_branches(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol =
|
|
|
1705
1795
|
|
|
1706
1796
|
return array
|
|
1707
1797
|
|
|
1708
|
-
def
|
|
1798
|
+
def fix_branches_network(array, G, communities, fix_val = None):
|
|
1709
1799
|
|
|
1710
1800
|
def invert_dict(d):
|
|
1711
1801
|
inverted = {}
|
|
@@ -1748,6 +1838,65 @@ def fix_branches(array, G, communities, fix_val = None):
|
|
|
1748
1838
|
|
|
1749
1839
|
return targs
|
|
1750
1840
|
|
|
1841
|
+
def fix_branches(array, G, max_val):
|
|
1842
|
+
"""
|
|
1843
|
+
Parameters:
|
|
1844
|
+
array: numpy array containing the labeled regions
|
|
1845
|
+
G: Graph representing connectivity relationships
|
|
1846
|
+
max_val: The target value to find neighbors for
|
|
1847
|
+
|
|
1848
|
+
Returns:
|
|
1849
|
+
Modified array with fused regions
|
|
1850
|
+
"""
|
|
1851
|
+
# Get all nodes
|
|
1852
|
+
all_nodes = set(G.nodes())
|
|
1853
|
+
|
|
1854
|
+
# Initially safe nodes are direct neighbors of max_val
|
|
1855
|
+
safe_initial = set(G.neighbors(max_val))
|
|
1856
|
+
|
|
1857
|
+
# Not-safe nodes are all other nodes except max_val
|
|
1858
|
+
not_safe_initial = all_nodes - safe_initial - {max_val}
|
|
1859
|
+
|
|
1860
|
+
# Get adjacency view (much faster for repeated neighbor lookups)
|
|
1861
|
+
adj = G.adj
|
|
1862
|
+
|
|
1863
|
+
# Find all neighbors of not_safe nodes in one pass
|
|
1864
|
+
neighbors_of_not_safe = set()
|
|
1865
|
+
for node in not_safe_initial:
|
|
1866
|
+
neighbors_of_not_safe.update(adj[node])
|
|
1867
|
+
|
|
1868
|
+
# Remove max_val if present
|
|
1869
|
+
neighbors_of_not_safe.discard(max_val)
|
|
1870
|
+
|
|
1871
|
+
# Find safe nodes that should be moved
|
|
1872
|
+
nodes_to_move = safe_initial & neighbors_of_not_safe
|
|
1873
|
+
|
|
1874
|
+
# Update sets
|
|
1875
|
+
not_safe = not_safe_initial | nodes_to_move
|
|
1876
|
+
|
|
1877
|
+
# The rest of the function - FIX STARTS HERE
|
|
1878
|
+
targs = np.array(list(not_safe))
|
|
1879
|
+
|
|
1880
|
+
if len(targs) == 0:
|
|
1881
|
+
return array
|
|
1882
|
+
|
|
1883
|
+
mask = np.isin(array, targs)
|
|
1884
|
+
|
|
1885
|
+
labeled, num_components = label_objects(mask)
|
|
1886
|
+
|
|
1887
|
+
# Get the current maximum label in the array to avoid collisions
|
|
1888
|
+
current_max = np.max(array)
|
|
1889
|
+
|
|
1890
|
+
# Assign new unique labels to each connected component
|
|
1891
|
+
for component_id in range(1, num_components + 1):
|
|
1892
|
+
component_mask = labeled == component_id
|
|
1893
|
+
array[component_mask] = current_max + component_id
|
|
1894
|
+
|
|
1895
|
+
return array
|
|
1896
|
+
|
|
1897
|
+
|
|
1898
|
+
|
|
1899
|
+
|
|
1751
1900
|
|
|
1752
1901
|
|
|
1753
1902
|
def label_vertices(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol = 0, down_factor = 0, directory = None, return_skele = False, order = 0, fastdil = True):
|
|
@@ -3564,7 +3713,7 @@ class Network_3D:
|
|
|
3564
3713
|
|
|
3565
3714
|
#Some methods that may be useful:
|
|
3566
3715
|
|
|
3567
|
-
def community_partition(self, weighted = False, style = 0, dostats = True, seed =
|
|
3716
|
+
def community_partition(self, weighted = False, style = 0, dostats = True, seed = 42):
|
|
3568
3717
|
"""
|
|
3569
3718
|
Sets the communities attribute by splitting the network into communities
|
|
3570
3719
|
"""
|
|
@@ -3682,6 +3831,41 @@ class Network_3D:
|
|
|
3682
3831
|
self._nodes = self._nodes.astype(np.uint16)
|
|
3683
3832
|
|
|
3684
3833
|
|
|
3834
|
+
def com_by_size(self):
|
|
3835
|
+
"""Reassign communities based on size, starting with 1 for largest."""
|
|
3836
|
+
|
|
3837
|
+
from collections import Counter
|
|
3838
|
+
|
|
3839
|
+
# Convert all community values to regular ints (handles numpy scalars)
|
|
3840
|
+
clean_communities = {
|
|
3841
|
+
node: comm.item() if hasattr(comm, 'item') else comm
|
|
3842
|
+
for node, comm in self.communities.items()
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
# Count community sizes and create mapping in one go
|
|
3846
|
+
community_sizes = Counter(clean_communities.values())
|
|
3847
|
+
|
|
3848
|
+
# Create old->new mapping: sort by size (desc), then by community ID for ties
|
|
3849
|
+
old_to_new = {
|
|
3850
|
+
old_comm: new_comm
|
|
3851
|
+
for new_comm, (old_comm, _) in enumerate(
|
|
3852
|
+
sorted(community_sizes.items(), key=lambda x: (-x[1], x[0])),
|
|
3853
|
+
start=1
|
|
3854
|
+
)
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3857
|
+
# Apply mapping
|
|
3858
|
+
self.communities = {
|
|
3859
|
+
node: old_to_new[comm]
|
|
3860
|
+
for node, comm in clean_communities.items()
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3863
|
+
|
|
3864
|
+
|
|
3865
|
+
|
|
3866
|
+
|
|
3867
|
+
|
|
3868
|
+
|
|
3685
3869
|
def com_to_node(self, targets = None):
|
|
3686
3870
|
|
|
3687
3871
|
def invert_dict(d):
|
|
@@ -3979,6 +4163,45 @@ class Network_3D:
|
|
|
3979
4163
|
self.node_centroids = new_centroids
|
|
3980
4164
|
|
|
3981
4165
|
|
|
4166
|
+
def purge_properties(self):
|
|
4167
|
+
|
|
4168
|
+
"""Eliminate nodes from properties that are no longer present in the nodes channel"""
|
|
4169
|
+
|
|
4170
|
+
print("Trimming properties. Note this does not update the network...")
|
|
4171
|
+
|
|
4172
|
+
def filter_dict_by_list(input_dict, filter_list):
|
|
4173
|
+
"""
|
|
4174
|
+
Remove dictionary entries where the key is not in the filter list.
|
|
4175
|
+
|
|
4176
|
+
Args:
|
|
4177
|
+
input_dict (dict): Dictionary with integer values
|
|
4178
|
+
filter_list (list): List of integers to keep
|
|
4179
|
+
|
|
4180
|
+
Returns:
|
|
4181
|
+
dict: New dictionary with only keys that exist in filter_list
|
|
4182
|
+
"""
|
|
4183
|
+
return {key: value for key, value in input_dict.items() if key in filter_list}
|
|
4184
|
+
|
|
4185
|
+
nodes = np.unique(self.nodes)
|
|
4186
|
+
|
|
4187
|
+
if 0 in nodes:
|
|
4188
|
+
np.delete(nodes, 0)
|
|
4189
|
+
|
|
4190
|
+
try:
|
|
4191
|
+
self.node_centroids = filter_dict_by_list(self.node_centroids, nodes)
|
|
4192
|
+
print("Updated centroids")
|
|
4193
|
+
except:
|
|
4194
|
+
pass
|
|
4195
|
+
try:
|
|
4196
|
+
self.communities = filter_dict_by_list(self.communities, nodes)
|
|
4197
|
+
print("Updated communities")
|
|
4198
|
+
except:
|
|
4199
|
+
pass
|
|
4200
|
+
try:
|
|
4201
|
+
self.node_identities = filter_dict_by_list(self.node_identities, nodes)
|
|
4202
|
+
print("Updated identities")
|
|
4203
|
+
except:
|
|
4204
|
+
pass
|
|
3982
4205
|
|
|
3983
4206
|
def remove_trunk_post(self):
|
|
3984
4207
|
"""
|
|
@@ -4523,12 +4746,19 @@ class Network_3D:
|
|
|
4523
4746
|
|
|
4524
4747
|
self.remove_edge_weights()
|
|
4525
4748
|
|
|
4526
|
-
def centroid_array(self):
|
|
4749
|
+
def centroid_array(self, clip = False):
|
|
4527
4750
|
"""Use the centroids to populate a node array"""
|
|
4528
4751
|
|
|
4529
|
-
|
|
4752
|
+
if clip:
|
|
4530
4753
|
|
|
4531
|
-
|
|
4754
|
+
array, centroids = proximity.populate_array(self.node_centroids, clip = True)
|
|
4755
|
+
return array, centroids
|
|
4756
|
+
|
|
4757
|
+
else:
|
|
4758
|
+
|
|
4759
|
+
array = proximity.populate_array(self.node_centroids)
|
|
4760
|
+
|
|
4761
|
+
return array
|
|
4532
4762
|
|
|
4533
4763
|
|
|
4534
4764
|
|
|
@@ -4612,6 +4842,7 @@ class Network_3D:
|
|
|
4612
4842
|
|
|
4613
4843
|
|
|
4614
4844
|
def community_id_info(self):
|
|
4845
|
+
|
|
4615
4846
|
def invert_dict(d):
|
|
4616
4847
|
inverted = {}
|
|
4617
4848
|
for key, value in d.items():
|
|
@@ -4654,7 +4885,99 @@ class Network_3D:
|
|
|
4654
4885
|
|
|
4655
4886
|
return output
|
|
4656
4887
|
|
|
4888
|
+
def community_id_info_per_com(self, umap = False, label = False):
|
|
4657
4889
|
|
|
4890
|
+
def invert_dict(d):
|
|
4891
|
+
inverted = {}
|
|
4892
|
+
for key, value in d.items():
|
|
4893
|
+
inverted.setdefault(value, []).append(key)
|
|
4894
|
+
return inverted
|
|
4895
|
+
|
|
4896
|
+
community_dict = invert_dict(self.communities)
|
|
4897
|
+
summation = 0
|
|
4898
|
+
id_set = set(self.node_identities.values())
|
|
4899
|
+
id_dict = {}
|
|
4900
|
+
for i, iden in enumerate(id_set):
|
|
4901
|
+
id_dict[iden] = i
|
|
4902
|
+
|
|
4903
|
+
output = {}
|
|
4904
|
+
|
|
4905
|
+
for community in community_dict:
|
|
4906
|
+
|
|
4907
|
+
counter = np.zeros(len(id_set))
|
|
4908
|
+
|
|
4909
|
+
nodes = community_dict[community]
|
|
4910
|
+
size = len(nodes)
|
|
4911
|
+
|
|
4912
|
+
# Count identities in this community
|
|
4913
|
+
for node in nodes:
|
|
4914
|
+
counter[id_dict[self.node_identities[node]]] += 1 # Keep them as arrays
|
|
4915
|
+
|
|
4916
|
+
for i in range(len(counter)): # Translate them into proportions out of 1
|
|
4917
|
+
|
|
4918
|
+
counter[i] = counter[i]/size
|
|
4919
|
+
|
|
4920
|
+
output[community] = counter #Assign the finding here
|
|
4921
|
+
|
|
4922
|
+
if umap:
|
|
4923
|
+
from . import neighborhoods
|
|
4924
|
+
neighborhoods.visualize_cluster_composition_umap(output, id_set, label = label)
|
|
4925
|
+
|
|
4926
|
+
return output, id_set
|
|
4927
|
+
|
|
4928
|
+
|
|
4929
|
+
def assign_neighborhoods(self, seed, count, limit = None, prev_coms = None):
|
|
4930
|
+
|
|
4931
|
+
from . import neighborhoods
|
|
4932
|
+
|
|
4933
|
+
def invert_dict(d):
|
|
4934
|
+
inverted = {}
|
|
4935
|
+
for key, value in d.items():
|
|
4936
|
+
inverted.setdefault(value, []).append(key)
|
|
4937
|
+
return inverted
|
|
4938
|
+
|
|
4939
|
+
if prev_coms is not None:
|
|
4940
|
+
self.communities = copy.deepcopy(prev_coms)
|
|
4941
|
+
|
|
4942
|
+
identities, _ = self.community_id_info_per_com()
|
|
4943
|
+
|
|
4944
|
+
if limit is not None:
|
|
4945
|
+
|
|
4946
|
+
coms = invert_dict(self.communities)
|
|
4947
|
+
|
|
4948
|
+
zero_group = {}
|
|
4949
|
+
|
|
4950
|
+
for com, nodes in coms.items():
|
|
4951
|
+
|
|
4952
|
+
if len(nodes) < limit:
|
|
4953
|
+
|
|
4954
|
+
zero_group[com] = 0
|
|
4955
|
+
|
|
4956
|
+
del identities[com]
|
|
4957
|
+
|
|
4958
|
+
|
|
4959
|
+
clusters = neighborhoods.cluster_arrays(identities, count, seed = seed) # dict: {cluster_id: {'keys': [keys], 'arrays': [arrays]}}
|
|
4960
|
+
|
|
4961
|
+
coms = {}
|
|
4962
|
+
|
|
4963
|
+
neighbors = {}
|
|
4964
|
+
|
|
4965
|
+
for i, cluster in enumerate(clusters):
|
|
4966
|
+
|
|
4967
|
+
for com in cluster: # For community ID per list
|
|
4968
|
+
|
|
4969
|
+
coms[com] = i + 1
|
|
4970
|
+
|
|
4971
|
+
if limit is not None:
|
|
4972
|
+
coms.update(zero_group)
|
|
4973
|
+
|
|
4974
|
+
for node, com in self.communities.items():
|
|
4975
|
+
|
|
4976
|
+
self.communities[node] = coms[com]
|
|
4977
|
+
|
|
4978
|
+
identities, id_set = self.community_id_info_per_com()
|
|
4979
|
+
|
|
4980
|
+
neighborhoods.plot_dict_heatmap(identities, id_set)
|
|
4658
4981
|
|
|
4659
4982
|
|
|
4660
4983
|
def kd_network(self, distance = 100, targets = None, make_array = False):
|
|
@@ -4694,6 +5017,70 @@ class Network_3D:
|
|
|
4694
5017
|
|
|
4695
5018
|
return array
|
|
4696
5019
|
|
|
5020
|
+
def community_cells(self, size = 32, xy_scale = 1, z_scale = 1):
|
|
5021
|
+
|
|
5022
|
+
def invert_dict(d):
|
|
5023
|
+
inverted = {}
|
|
5024
|
+
for key, value_list in d.items():
|
|
5025
|
+
for value in value_list:
|
|
5026
|
+
inverted[value] = key
|
|
5027
|
+
return inverted
|
|
5028
|
+
|
|
5029
|
+
size_x = int(size * xy_scale)
|
|
5030
|
+
size_z = int(size * z_scale)
|
|
5031
|
+
|
|
5032
|
+
if size_x == size_z:
|
|
5033
|
+
|
|
5034
|
+
com_dict = proximity.partition_objects_into_cells(self.node_centroids, size_x)
|
|
5035
|
+
|
|
5036
|
+
else:
|
|
5037
|
+
|
|
5038
|
+
com_dict = proximity.partition_objects_into_cells(self.node_centroids, (size_z, size_x, size_x))
|
|
5039
|
+
|
|
5040
|
+
self.communities = invert_dict(com_dict)
|
|
5041
|
+
|
|
5042
|
+
def community_heatmap(self, num_nodes = None, is3d = True):
|
|
5043
|
+
|
|
5044
|
+
import math
|
|
5045
|
+
|
|
5046
|
+
def invert_dict(d):
|
|
5047
|
+
inverted = {}
|
|
5048
|
+
for key, value in d.items():
|
|
5049
|
+
inverted.setdefault(value, []).append(key)
|
|
5050
|
+
return inverted
|
|
5051
|
+
|
|
5052
|
+
if num_nodes == None:
|
|
5053
|
+
|
|
5054
|
+
try:
|
|
5055
|
+
num_nodes = len(self.network.nodes())
|
|
5056
|
+
except:
|
|
5057
|
+
try:
|
|
5058
|
+
num_nodes = len(self.node_centroids.keys())
|
|
5059
|
+
except:
|
|
5060
|
+
try:
|
|
5061
|
+
num_nodes = len(self.node_identities.keys())
|
|
5062
|
+
except:
|
|
5063
|
+
try:
|
|
5064
|
+
unique = np.unique(self.nodes)
|
|
5065
|
+
num_nodes = len(unique)
|
|
5066
|
+
if unique[0] == 0:
|
|
5067
|
+
num_nodes -= 1
|
|
5068
|
+
except:
|
|
5069
|
+
return
|
|
5070
|
+
|
|
5071
|
+
coms = invert_dict(self.communities)
|
|
5072
|
+
|
|
5073
|
+
rand_dens = num_nodes / len(coms.keys())
|
|
5074
|
+
|
|
5075
|
+
heat_dict = {}
|
|
5076
|
+
|
|
5077
|
+
for com, nodes in coms.items():
|
|
5078
|
+
heat_dict[com] = math.log(len(nodes)/rand_dens)
|
|
5079
|
+
|
|
5080
|
+
from . import neighborhoods
|
|
5081
|
+
neighborhoods.create_community_heatmap(heat_dict, self.communities, self.node_centroids, is_3d=is3d)
|
|
5082
|
+
|
|
5083
|
+
return heat_dict
|
|
4697
5084
|
|
|
4698
5085
|
|
|
4699
5086
|
|