nettracer3d 0.2.7__tar.gz → 0.2.8__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.2.7/src/nettracer3d.egg-info → nettracer3d-0.2.8}/PKG-INFO +1 -1
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/pyproject.toml +1 -1
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/community_extractor.py +100 -16
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/morphology.py +25 -20
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/nettracer.py +43 -16
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/nettracer_gui.py +241 -31
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/network_analysis.py +59 -4
- {nettracer3d-0.2.7 → nettracer3d-0.2.8/src/nettracer3d.egg-info}/PKG-INFO +1 -1
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/LICENSE +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/README.md +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/setup.cfg +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/__init__.py +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/hub_getter.py +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/modularity.py +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/network_draw.py +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/node_draw.py +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/proximity.py +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/simple_network.py +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d/smart_dilate.py +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d.egg-info/SOURCES.txt +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d.egg-info/dependency_links.txt +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d.egg-info/requires.txt +0 -0
- {nettracer3d-0.2.7 → nettracer3d-0.2.8}/src/nettracer3d.egg-info/top_level.txt +0 -0
|
@@ -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
|
|
@@ -2,7 +2,7 @@ import pandas as pd
|
|
|
2
2
|
import networkx as nx
|
|
3
3
|
import tifffile
|
|
4
4
|
import numpy as np
|
|
5
|
-
from typing import List, Dict, Tuple
|
|
5
|
+
from typing import List, Dict, Tuple, Union, Any
|
|
6
6
|
from collections import defaultdict, Counter
|
|
7
7
|
from networkx.algorithms import community
|
|
8
8
|
from scipy import ndimage
|
|
@@ -648,7 +648,69 @@ def find_hub_nodes(G: nx.Graph, proportion: float = 0.1) -> List:
|
|
|
648
648
|
|
|
649
649
|
return hub_nodes
|
|
650
650
|
|
|
651
|
+
def get_color_name_mapping():
|
|
652
|
+
"""Return a dictionary of common colors and their RGB values."""
|
|
653
|
+
return {
|
|
654
|
+
'red': (255, 0, 0),
|
|
655
|
+
'green': (0, 255, 0),
|
|
656
|
+
'blue': (0, 0, 255),
|
|
657
|
+
'yellow': (255, 255, 0),
|
|
658
|
+
'cyan': (0, 255, 255),
|
|
659
|
+
'magenta': (255, 0, 255),
|
|
660
|
+
'purple': (128, 0, 128),
|
|
661
|
+
'orange': (255, 165, 0),
|
|
662
|
+
'brown': (165, 42, 42),
|
|
663
|
+
'pink': (255, 192, 203),
|
|
664
|
+
'navy': (0, 0, 128),
|
|
665
|
+
'teal': (0, 128, 128),
|
|
666
|
+
'olive': (128, 128, 0),
|
|
667
|
+
'maroon': (128, 0, 0),
|
|
668
|
+
'lime': (50, 205, 50),
|
|
669
|
+
'indigo': (75, 0, 130),
|
|
670
|
+
'violet': (238, 130, 238),
|
|
671
|
+
'coral': (255, 127, 80),
|
|
672
|
+
'turquoise': (64, 224, 208),
|
|
673
|
+
'gold': (255, 215, 0)
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
def rgb_to_color_name(rgb: Tuple[int, int, int]) -> str:
|
|
677
|
+
"""
|
|
678
|
+
Convert an RGB tuple to its nearest color name.
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
rgb: Tuple of (r, g, b) values
|
|
682
|
+
|
|
683
|
+
Returns:
|
|
684
|
+
str: Name of the closest matching color
|
|
685
|
+
"""
|
|
686
|
+
color_map = get_color_name_mapping()
|
|
687
|
+
|
|
688
|
+
# Convert input RGB to numpy array
|
|
689
|
+
rgb_array = np.array(rgb)
|
|
690
|
+
|
|
691
|
+
# Calculate Euclidean distance to all known colors
|
|
692
|
+
min_distance = float('inf')
|
|
693
|
+
closest_color = None
|
|
694
|
+
|
|
695
|
+
for color_name, color_rgb in color_map.items():
|
|
696
|
+
distance = np.sqrt(np.sum((rgb_array - np.array(color_rgb)) ** 2))
|
|
697
|
+
if distance < min_distance:
|
|
698
|
+
min_distance = distance
|
|
699
|
+
closest_color = color_name
|
|
700
|
+
|
|
701
|
+
return closest_color
|
|
651
702
|
|
|
703
|
+
def convert_node_colors_to_names(node_to_color: Dict[int, Tuple[int, int, int]]) -> Dict[int, str]:
|
|
704
|
+
"""
|
|
705
|
+
Convert a dictionary of node-to-RGB mappings to node-to-color-name mappings.
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
node_to_color: Dictionary mapping node IDs to RGB tuples
|
|
709
|
+
|
|
710
|
+
Returns:
|
|
711
|
+
Dictionary mapping node IDs to color names
|
|
712
|
+
"""
|
|
713
|
+
return {node: rgb_to_color_name(color) for node, color in node_to_color.items()}
|
|
652
714
|
|
|
653
715
|
def generate_distinct_colors(n_colors: int) -> List[Tuple[int, int, int]]:
|
|
654
716
|
"""
|
|
@@ -721,33 +783,55 @@ def assign_community_colors(community_dict: Dict[int, int], labeled_array: np.nd
|
|
|
721
783
|
mask = labeled_array == label
|
|
722
784
|
for i in range(3): # RGB channels
|
|
723
785
|
rgb_array[mask, i] = node_to_color[label][i]
|
|
786
|
+
|
|
787
|
+
node_to_color_names = convert_node_colors_to_names(community_to_color)
|
|
788
|
+
|
|
724
789
|
|
|
725
|
-
return rgb_array
|
|
790
|
+
return rgb_array, node_to_color_names
|
|
726
791
|
|
|
727
|
-
def assign_community_grays(community_dict: Dict[int, int], labeled_array: np.ndarray) -> np.ndarray:
|
|
792
|
+
def assign_community_grays(community_dict: Dict[int, Union[int, str, Any]], labeled_array: np.ndarray) -> np.ndarray:
|
|
728
793
|
"""
|
|
729
|
-
Assign
|
|
794
|
+
Assign grayscale values to communities. For numeric communities, uses the community
|
|
795
|
+
number directly. For string/other communities, assigns sequential values.
|
|
730
796
|
|
|
731
797
|
Args:
|
|
732
|
-
community_dict: Dictionary mapping node IDs to community numbers
|
|
798
|
+
community_dict: Dictionary mapping node IDs to community identifiers (numbers or strings)
|
|
733
799
|
labeled_array: 3D numpy array with labels corresponding to node IDs
|
|
734
800
|
|
|
735
801
|
Returns:
|
|
736
|
-
grayscale numpy array
|
|
802
|
+
tuple: (grayscale numpy array, mapping of node IDs to assigned values)
|
|
737
803
|
"""
|
|
738
|
-
#
|
|
739
|
-
|
|
740
|
-
|
|
804
|
+
# Determine if we're dealing with numeric or string communities
|
|
805
|
+
sample_value = next(iter(community_dict.values()))
|
|
806
|
+
is_numeric = isinstance(sample_value, (int, float))
|
|
741
807
|
|
|
742
|
-
|
|
743
|
-
|
|
808
|
+
if is_numeric:
|
|
809
|
+
# For numeric communities, use values directly
|
|
810
|
+
node_to_gray = community_dict
|
|
811
|
+
max_val = max(community_dict.values())
|
|
812
|
+
else:
|
|
813
|
+
# For string/other communities, assign sequential values
|
|
814
|
+
unique_communities = sorted(set(community_dict.values()))
|
|
815
|
+
community_to_value = {comm: i+1 for i, comm in enumerate(unique_communities)}
|
|
816
|
+
node_to_gray = {node: community_to_value[comm] for node, comm in community_dict.items()}
|
|
817
|
+
max_val = len(unique_communities)
|
|
744
818
|
|
|
745
|
-
#
|
|
746
|
-
|
|
747
|
-
|
|
819
|
+
# Choose appropriate dtype based on maximum value
|
|
820
|
+
if max_val <= 255:
|
|
821
|
+
dtype = np.uint8
|
|
822
|
+
elif max_val <= 65535:
|
|
823
|
+
dtype = np.uint16
|
|
824
|
+
else:
|
|
825
|
+
dtype = np.uint32
|
|
748
826
|
|
|
749
827
|
# Create output array
|
|
750
|
-
gray_array = np.zeros_like(labeled_array, dtype=
|
|
828
|
+
gray_array = np.zeros_like(labeled_array, dtype=dtype)
|
|
829
|
+
|
|
830
|
+
# Create mapping of unique communities to their grayscale values
|
|
831
|
+
if is_numeric:
|
|
832
|
+
community_to_gray = {comm: comm for comm in set(community_dict.values())}
|
|
833
|
+
else:
|
|
834
|
+
community_to_gray = {comm: i+1 for i, comm in enumerate(sorted(set(community_dict.values())))}
|
|
751
835
|
|
|
752
836
|
# Use numpy's vectorized operations for faster assignment
|
|
753
837
|
unique_labels = np.unique(labeled_array)
|
|
@@ -755,7 +839,7 @@ def assign_community_grays(community_dict: Dict[int, int], labeled_array: np.nda
|
|
|
755
839
|
if label in node_to_gray:
|
|
756
840
|
gray_array[labeled_array == label] = node_to_gray[label]
|
|
757
841
|
|
|
758
|
-
return gray_array
|
|
842
|
+
return gray_array, community_to_gray
|
|
759
843
|
|
|
760
844
|
|
|
761
845
|
if __name__ == "__main__":
|
|
@@ -270,51 +270,56 @@ def calculate_voxel_volumes(array, xy_scale=1, z_scale=1):
|
|
|
270
270
|
return volumes
|
|
271
271
|
|
|
272
272
|
|
|
273
|
-
def search_neighbor_ids(nodes, targets, id_dict, neighborhood_dict, totals, search, xy_scale, z_scale):
|
|
273
|
+
def search_neighbor_ids(nodes, targets, id_dict, neighborhood_dict, totals, search, xy_scale, z_scale, root):
|
|
274
274
|
|
|
275
|
-
|
|
275
|
+
if 0 in targets:
|
|
276
|
+
targets.remove(0)
|
|
276
277
|
targets = np.isin(nodes, targets)
|
|
277
278
|
targets = nettracer.binarize(targets)
|
|
278
279
|
|
|
279
280
|
dilate_xy, dilate_z = nettracer.dilation_length_to_pixels(xy_scale, z_scale, search, search)
|
|
280
|
-
print(f"Dilation parameters - xy: {dilate_xy}, z: {dilate_z}")
|
|
281
281
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
282
|
+
dilated = nettracer.dilate_3D_recursive(targets, dilate_xy, dilate_xy, dilate_z)
|
|
283
|
+
dilated = dilated - targets #technically we dont need the cores
|
|
284
|
+
search_vol = np.count_nonzero(dilated) * xy_scale * xy_scale * z_scale #need this for density
|
|
285
|
+
targets = dilated != 0
|
|
286
|
+
del dilated
|
|
287
|
+
|
|
285
288
|
|
|
286
289
|
targets = targets * nodes
|
|
287
|
-
print(f"After multiplication with nodes - unique values in targets: {np.unique(targets)}")
|
|
288
290
|
|
|
289
291
|
unique, counts = np.unique(targets, return_counts=True)
|
|
290
292
|
count_dict = dict(zip(unique, counts))
|
|
291
|
-
print(
|
|
293
|
+
print(count_dict)
|
|
292
294
|
|
|
293
295
|
del count_dict[0]
|
|
294
|
-
print(f"count_dict after removing zeros: {count_dict}")
|
|
295
296
|
|
|
296
297
|
unique, counts = np.unique(nodes, return_counts=True)
|
|
297
298
|
total_dict = dict(zip(unique, counts))
|
|
298
|
-
print(
|
|
299
|
-
|
|
299
|
+
print(total_dict)
|
|
300
|
+
|
|
300
301
|
del total_dict[0]
|
|
301
|
-
print(f"total_dict after removing zeros: {total_dict}")
|
|
302
302
|
|
|
303
|
-
print(f"id_dict keys: {list(id_dict.keys())}")
|
|
304
|
-
print(f"Initial neighborhood_dict: {neighborhood_dict}")
|
|
305
|
-
print(f"Initial totals: {totals}")
|
|
306
303
|
|
|
307
304
|
for label in total_dict:
|
|
308
305
|
if label in id_dict:
|
|
309
306
|
if label in count_dict:
|
|
310
307
|
neighborhood_dict[id_dict[label]] += count_dict[label]
|
|
311
|
-
print(f"Updated neighborhood_dict[{id_dict[label]}] with count {count_dict[label]}")
|
|
312
308
|
totals[id_dict[label]] += total_dict[label]
|
|
313
|
-
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
del neighborhood_dict[root] #no good way to get this
|
|
313
|
+
del totals[root] #no good way to get this
|
|
314
|
+
except:
|
|
315
|
+
pass
|
|
314
316
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
317
|
+
volume = nodes.shape[0] * nodes.shape[1] * nodes.shape[2] * xy_scale * xy_scale * z_scale
|
|
318
|
+
densities = {}
|
|
319
|
+
for nodeid, amount in totals.items():
|
|
320
|
+
densities[nodeid] = (neighborhood_dict[nodeid]/search_vol)/(amount/volume)
|
|
321
|
+
|
|
322
|
+
return neighborhood_dict, totals, densities
|
|
318
323
|
|
|
319
324
|
|
|
320
325
|
|
|
@@ -1383,7 +1383,7 @@ def binarize(arrayimage, directory = None):
|
|
|
1383
1383
|
|
|
1384
1384
|
return arrayimage
|
|
1385
1385
|
|
|
1386
|
-
def dilate(arrayimage, amount, xy_scale = 1, z_scale = 1, directory = None, fast_dil = False):
|
|
1386
|
+
def dilate(arrayimage, amount, xy_scale = 1, z_scale = 1, directory = None, fast_dil = False, recursive = False):
|
|
1387
1387
|
"""
|
|
1388
1388
|
Can be used to dilate a binary image in 3D. Dilated output will be saved to the active directory if none is specified. Note that dilation is done with single-instance kernels and not iterations, and therefore
|
|
1389
1389
|
objects will lose their shape somewhat and become cube-ish if the 'amount' param is ever significantly larger than the objects in quesiton.
|
|
@@ -1408,13 +1408,15 @@ def dilate(arrayimage, amount, xy_scale = 1, z_scale = 1, directory = None, fast
|
|
|
1408
1408
|
if len(np.unique(arrayimage)) > 2: #binarize
|
|
1409
1409
|
arrayimage = binarize(arrayimage)
|
|
1410
1410
|
|
|
1411
|
-
if not fast_dil:
|
|
1411
|
+
if not fast_dil and not recursive:
|
|
1412
1412
|
arrayimage = (dilate_3D(arrayimage, dilate_xy, dilate_xy, dilate_z)) * 255
|
|
1413
1413
|
if np.max(arrayimage) == 1:
|
|
1414
1414
|
arrayimage = arrayimage * 255
|
|
1415
|
-
|
|
1416
|
-
else:
|
|
1415
|
+
elif not recursive:
|
|
1417
1416
|
arrayimage = (dilate_3D_old(arrayimage, dilate_xy, dilate_xy, dilate_z)) * 255
|
|
1417
|
+
else:
|
|
1418
|
+
arrayimage = (dilate_3D_recursive(arrayimage, dilate_xy, dilate_xy, dilate_z)) * 255
|
|
1419
|
+
|
|
1418
1420
|
|
|
1419
1421
|
|
|
1420
1422
|
if type(image) == str:
|
|
@@ -1935,6 +1937,9 @@ def encapsulate(parent_dir = None, name = None):
|
|
|
1935
1937
|
|
|
1936
1938
|
return new_folder_path
|
|
1937
1939
|
|
|
1940
|
+
|
|
1941
|
+
|
|
1942
|
+
|
|
1938
1943
|
#THE 3D NETWORK CLASS
|
|
1939
1944
|
|
|
1940
1945
|
class Network_3D:
|
|
@@ -3906,25 +3911,37 @@ class Network_3D:
|
|
|
3906
3911
|
return hubs, hub_img
|
|
3907
3912
|
|
|
3908
3913
|
|
|
3909
|
-
def extract_communities(self, color_code = True, down_factor = None):
|
|
3914
|
+
def extract_communities(self, color_code = True, down_factor = None, identities = False):
|
|
3910
3915
|
|
|
3911
3916
|
if down_factor is not None:
|
|
3912
3917
|
original_shape = self._nodes.shape
|
|
3913
3918
|
temp = downsample(self._nodes, down_factor)
|
|
3914
3919
|
if color_code:
|
|
3915
|
-
|
|
3920
|
+
if not identities:
|
|
3921
|
+
image, output = community_extractor.assign_community_colors(self.communities, temp)
|
|
3922
|
+
else:
|
|
3923
|
+
image, output = community_extractor.assign_community_colors(self.node_identities, temp)
|
|
3916
3924
|
else:
|
|
3917
|
-
|
|
3925
|
+
if not identities:
|
|
3926
|
+
image, output = community_extractor.assign_community_grays(self.communities, temp)
|
|
3927
|
+
else:
|
|
3928
|
+
image, output = community_extractor.assign_community_grays(self.node_identities, temp)
|
|
3918
3929
|
image = upsample_with_padding(image, down_factor, original_shape)
|
|
3919
3930
|
else:
|
|
3920
3931
|
|
|
3921
3932
|
if color_code:
|
|
3922
|
-
|
|
3933
|
+
if not identities:
|
|
3934
|
+
image, output = community_extractor.assign_community_colors(self.communities, self._nodes)
|
|
3935
|
+
else:
|
|
3936
|
+
image, output = community_extractor.assign_community_colors(self.node_identities, self._nodes)
|
|
3923
3937
|
else:
|
|
3924
|
-
|
|
3938
|
+
if not identities:
|
|
3939
|
+
image, output = community_extractor.assign_community_grays(self.communities, self._nodes)
|
|
3940
|
+
else:
|
|
3941
|
+
image, output = community_extractor.assign_community_grays(self.node_identities, self._nodes)
|
|
3925
3942
|
|
|
3926
3943
|
|
|
3927
|
-
return image
|
|
3944
|
+
return image, output
|
|
3928
3945
|
|
|
3929
3946
|
|
|
3930
3947
|
|
|
@@ -4072,6 +4089,14 @@ class Network_3D:
|
|
|
4072
4089
|
except:
|
|
4073
4090
|
stats['degree_assortativity'] = "Failed to compute"
|
|
4074
4091
|
|
|
4092
|
+
try:
|
|
4093
|
+
nodes = np.unique(self._nodes)
|
|
4094
|
+
if nodes[0] == 0:
|
|
4095
|
+
nodes = np.delete(nodes, 0)
|
|
4096
|
+
stats['Unconnected nodes (left out from node image)'] = (len(nodes) - len(G.nodes()))
|
|
4097
|
+
except:
|
|
4098
|
+
stats['Unconnected nodes (left out from node image)'] = "Failed to compute"
|
|
4099
|
+
|
|
4075
4100
|
|
|
4076
4101
|
return stats
|
|
4077
4102
|
|
|
@@ -4114,9 +4139,9 @@ class Network_3D:
|
|
|
4114
4139
|
|
|
4115
4140
|
|
|
4116
4141
|
elif mode == 1: #Search neighborhoods morphologically, obtain densities
|
|
4117
|
-
neighborhood_dict, total_dict = morphology.search_neighbor_ids(self._nodes, targets, node_identities, neighborhood_dict, total_dict, search, self._xy_scale, self._z_scale)
|
|
4118
|
-
title1 = f'Volumetric Neighborhood Distribution of Nodes in image from
|
|
4119
|
-
title2 = f'Density Distribution of Nodes in image from Nodes {root} as a proportion of total node volume of that ID'
|
|
4142
|
+
neighborhood_dict, total_dict, densities = morphology.search_neighbor_ids(self._nodes, targets, node_identities, neighborhood_dict, total_dict, search, self._xy_scale, self._z_scale, root)
|
|
4143
|
+
title1 = f'Volumetric Neighborhood Distribution of Nodes in image that are {search} from nodes: {root}'
|
|
4144
|
+
title2 = f'Density Distribution of Nodes in image that are {search} from Nodes {root} as a proportion of total node volume of that ID'
|
|
4120
4145
|
|
|
4121
4146
|
|
|
4122
4147
|
for identity in neighborhood_dict:
|
|
@@ -4126,11 +4151,13 @@ class Network_3D:
|
|
|
4126
4151
|
|
|
4127
4152
|
network_analysis.create_bar_graph(proportion_dict, title2, "Node Identity", "Proportion", directory=directory)
|
|
4128
4153
|
|
|
4154
|
+
try:
|
|
4155
|
+
network_analysis.create_bar_graph(densities, f'Clustering Factor of Node Identities with {search} from nodes {root}', "Node Identity", "Density Search/Density Total", directory=directory)
|
|
4156
|
+
except:
|
|
4157
|
+
densities = None
|
|
4129
4158
|
|
|
4130
4159
|
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
return neighborhood_dict, proportion_dict, title1, title2
|
|
4160
|
+
return neighborhood_dict, proportion_dict, title1, title2, densities
|
|
4134
4161
|
|
|
4135
4162
|
|
|
4136
4163
|
|
|
@@ -555,6 +555,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
555
555
|
if len(self.clicked_values['nodes']) > 1 or len(self.clicked_values['edges']) > 1:
|
|
556
556
|
combine_obj = highlight_menu.addAction("Combine Object Labels")
|
|
557
557
|
combine_obj.triggered.connect(self.handle_combine)
|
|
558
|
+
split_obj = highlight_menu.addAction("Split Non-Touching Labels")
|
|
559
|
+
split_obj.triggered.connect(self.handle_seperate)
|
|
558
560
|
delete_obj = highlight_menu.addAction("Delete Selection")
|
|
559
561
|
delete_obj.triggered.connect(self.handle_delete)
|
|
560
562
|
if len(self.clicked_values['nodes']) > 1:
|
|
@@ -991,6 +993,58 @@ class ImageViewerWindow(QMainWindow):
|
|
|
991
993
|
except Exception as e:
|
|
992
994
|
print(f"Error: {e}")
|
|
993
995
|
|
|
996
|
+
def handle_info(self, sort = 'node'):
|
|
997
|
+
|
|
998
|
+
try:
|
|
999
|
+
|
|
1000
|
+
info_dict = {}
|
|
1001
|
+
|
|
1002
|
+
if sort == 'node':
|
|
1003
|
+
|
|
1004
|
+
label = self.clicked_values['nodes'][-1]
|
|
1005
|
+
|
|
1006
|
+
info_dict['Label'] = label
|
|
1007
|
+
|
|
1008
|
+
info_dict['Object Class'] = 'Node'
|
|
1009
|
+
|
|
1010
|
+
if my_network.node_identities is not None:
|
|
1011
|
+
info_dict['ID'] = my_network.node_identities[label]
|
|
1012
|
+
|
|
1013
|
+
if my_network.network is not None:
|
|
1014
|
+
info_dict['Degree'] = my_network.network.degree(label)
|
|
1015
|
+
|
|
1016
|
+
if my_network.communities is not None:
|
|
1017
|
+
info_dict['Community'] = my_network.communities[label]
|
|
1018
|
+
|
|
1019
|
+
if my_network.node_centroids is not None:
|
|
1020
|
+
info_dict['Centroid'] = my_network.node_centroids[label]
|
|
1021
|
+
|
|
1022
|
+
if self.volume_dict[0] is not None:
|
|
1023
|
+
info_dict['Volume'] = self.volume_dict[0][label]
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
elif sort == 'edge':
|
|
1027
|
+
|
|
1028
|
+
label = self.clicked_values['edges'][-1]
|
|
1029
|
+
|
|
1030
|
+
info_dict['Label'] = label
|
|
1031
|
+
|
|
1032
|
+
info_dict['Object Class'] = 'Edge'
|
|
1033
|
+
|
|
1034
|
+
if my_network.edge_centroids is not None:
|
|
1035
|
+
info_dict['Centroid'] = my_network.edge_centroids[label]
|
|
1036
|
+
|
|
1037
|
+
if self.volume_dict[1] is not None:
|
|
1038
|
+
info_dict['Volume'] = self.volume_dict[1][label]
|
|
1039
|
+
|
|
1040
|
+
self.format_for_upperright_table(info_dict, title = f'Info on Object')
|
|
1041
|
+
|
|
1042
|
+
except:
|
|
1043
|
+
pass
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
|
|
994
1048
|
def handle_combine(self):
|
|
995
1049
|
|
|
996
1050
|
try:
|
|
@@ -1040,12 +1094,73 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1040
1094
|
for column in range(model.columnCount(None)):
|
|
1041
1095
|
self.network_table.resizeColumnToContents(column)
|
|
1042
1096
|
|
|
1097
|
+
self.highlight_overlay = None
|
|
1098
|
+
self.update_display()
|
|
1099
|
+
|
|
1100
|
+
self.show_centroid_dialog()
|
|
1101
|
+
|
|
1043
1102
|
except Exception as e:
|
|
1044
1103
|
print(f"Error, could not update network: {e}")
|
|
1045
1104
|
|
|
1105
|
+
|
|
1046
1106
|
except Exception as e:
|
|
1047
1107
|
print(f"An error has occured: {e}")
|
|
1048
1108
|
|
|
1109
|
+
def handle_seperate(self):
|
|
1110
|
+
|
|
1111
|
+
try:
|
|
1112
|
+
|
|
1113
|
+
if len(self.clicked_values['nodes']) > 0:
|
|
1114
|
+
self.create_highlight_overlay(node_indices = self.clicked_values['nodes'])
|
|
1115
|
+
max_val = np.max(my_network.nodes)
|
|
1116
|
+
self.highlight_overlay, num = n3d.label_objects(self.highlight_overlay)
|
|
1117
|
+
|
|
1118
|
+
node_bools = self.highlight_overlay != 0
|
|
1119
|
+
new_max = num + max_val
|
|
1120
|
+
self.highlight_overlay = self.highlight_overlay + max_val
|
|
1121
|
+
self.highlight_overlay = self.highlight_overlay * node_bools
|
|
1122
|
+
if new_max < 256:
|
|
1123
|
+
dtype = np.uint8
|
|
1124
|
+
elif new_max < 65536:
|
|
1125
|
+
dtype = np.uint16
|
|
1126
|
+
else:
|
|
1127
|
+
dtype = np.uint32
|
|
1128
|
+
|
|
1129
|
+
self.highlight_overlay = self.highlight_overlay.astype(dtype)
|
|
1130
|
+
my_network.nodes = my_network.nodes + self.highlight_overlay
|
|
1131
|
+
self.load_channel(0, my_network.nodes, True)
|
|
1132
|
+
|
|
1133
|
+
if len(self.clicked_values['edges']) > 0:
|
|
1134
|
+
self.create_highlight_overlay(edge_indices = self.clicked_values['edges'])
|
|
1135
|
+
max_val = np.max(my_network.edges)
|
|
1136
|
+
self.highlight_overlay, num = n3d.label_objects(self.highlight_overlay)
|
|
1137
|
+
node_bools = self.highlight_overlay != 0
|
|
1138
|
+
new_max = num + max_val
|
|
1139
|
+
|
|
1140
|
+
self.highlight_overlay = self.highlight_overlay + max_val
|
|
1141
|
+
self.highlight_overlay = self.highlight_overlay * node_bools
|
|
1142
|
+
if new_max < 256:
|
|
1143
|
+
dtype = np.uint8
|
|
1144
|
+
elif new_max < 65536:
|
|
1145
|
+
dtype = np.uint16
|
|
1146
|
+
else:
|
|
1147
|
+
dtype = np.uint32
|
|
1148
|
+
|
|
1149
|
+
self.highlight_overlay = self.highlight_overlay.astype(dtype)
|
|
1150
|
+
my_network.edges = my_network.edges + self.highlight_overlay
|
|
1151
|
+
self.load_channel(1, my_network.edges, True)
|
|
1152
|
+
self.highlight_overlay = None
|
|
1153
|
+
self.update_display()
|
|
1154
|
+
print("Network is not updated automatically, please recompute if necesarry. Identities are not automatically updated.")
|
|
1155
|
+
self.show_centroid_dialog()
|
|
1156
|
+
|
|
1157
|
+
except Exception as e:
|
|
1158
|
+
print(f"Error seperating: {e}")
|
|
1159
|
+
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
|
|
1049
1164
|
def handle_delete(self):
|
|
1050
1165
|
|
|
1051
1166
|
try:
|
|
@@ -1399,6 +1514,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1399
1514
|
# Try to highlight the last selected value in tables
|
|
1400
1515
|
if self.clicked_values['edges']:
|
|
1401
1516
|
self.highlight_value_in_tables(self.clicked_values['edges'][-1])
|
|
1517
|
+
|
|
1402
1518
|
|
|
1403
1519
|
elif not self.selecting and self.selection_start: # If we had a click but never started selection
|
|
1404
1520
|
# Handle as a normal click
|
|
@@ -1564,6 +1680,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1564
1680
|
self.clicked_values = {'nodes': [clicked_value], 'edges': []}
|
|
1565
1681
|
# Get latest value (or the last remaining one if we just removed an item)
|
|
1566
1682
|
latest_value = self.clicked_values['nodes'][-1] if self.clicked_values['nodes'] else None
|
|
1683
|
+
self.handle_info('node')
|
|
1567
1684
|
elif self.active_channel == 1:
|
|
1568
1685
|
if ctrl_pressed:
|
|
1569
1686
|
if clicked_value in self.clicked_values['edges']:
|
|
@@ -1577,6 +1694,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1577
1694
|
self.clicked_values = {'nodes': [], 'edges': [clicked_value]}
|
|
1578
1695
|
# Get latest value (or the last remaining one if we just removed an item)
|
|
1579
1696
|
latest_value = self.clicked_values['edges'][-1] if self.clicked_values['edges'] else None
|
|
1697
|
+
self.handle_info('edge')
|
|
1698
|
+
|
|
1580
1699
|
|
|
1581
1700
|
# Try to find and highlight the latest value in the current table
|
|
1582
1701
|
try:
|
|
@@ -1689,7 +1808,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1689
1808
|
mother_action = overlay_menu.addAction("Get Mother Nodes")
|
|
1690
1809
|
mother_action.triggered.connect(self.show_mother_dialog)
|
|
1691
1810
|
community_code_action = overlay_menu.addAction("Code Communities")
|
|
1692
|
-
community_code_action.triggered.connect(self.show_code_dialog)
|
|
1811
|
+
community_code_action.triggered.connect(lambda: self.show_code_dialog(sort = 'Community'))
|
|
1812
|
+
id_code_action = overlay_menu.addAction("Code Identities")
|
|
1813
|
+
id_code_action.triggered.connect(lambda: self.show_code_dialog(sort = 'Identity'))
|
|
1693
1814
|
|
|
1694
1815
|
|
|
1695
1816
|
# Process menu
|
|
@@ -2050,6 +2171,37 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2050
2171
|
def load_misc(self, sort):
|
|
2051
2172
|
"""Loads various things"""
|
|
2052
2173
|
|
|
2174
|
+
def uncork(my_dict, trumper = None):
|
|
2175
|
+
|
|
2176
|
+
if trumper is None:
|
|
2177
|
+
for thing in my_dict:
|
|
2178
|
+
val = my_dict[thing]
|
|
2179
|
+
new_val = val[0]
|
|
2180
|
+
for i in range(1, len(val)):
|
|
2181
|
+
try:
|
|
2182
|
+
new_val += f" AND {val[i]}"
|
|
2183
|
+
except:
|
|
2184
|
+
break
|
|
2185
|
+
my_dict[thing] = new_val
|
|
2186
|
+
elif trumper == '-':
|
|
2187
|
+
for key, value in my_dict.items():
|
|
2188
|
+
my_dict[key] = value[0]
|
|
2189
|
+
else:
|
|
2190
|
+
for thing in my_dict:
|
|
2191
|
+
val = my_dict[thing]
|
|
2192
|
+
if trumper in val:
|
|
2193
|
+
my_dict[thing] = trumper
|
|
2194
|
+
else:
|
|
2195
|
+
new_val = val[0]
|
|
2196
|
+
for i in range(1, len(val)):
|
|
2197
|
+
try:
|
|
2198
|
+
new_val += f" AND {val[i]}"
|
|
2199
|
+
except:
|
|
2200
|
+
break
|
|
2201
|
+
my_dict[thing] = new_val
|
|
2202
|
+
|
|
2203
|
+
return my_dict
|
|
2204
|
+
|
|
2053
2205
|
if sort != 'Merge Nodes':
|
|
2054
2206
|
|
|
2055
2207
|
try:
|
|
@@ -2058,13 +2210,32 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2058
2210
|
self,
|
|
2059
2211
|
f"Load {sort}",
|
|
2060
2212
|
"",
|
|
2061
|
-
"Spreadsheets (*.xlsx *.csv)"
|
|
2213
|
+
"Spreadsheets (*.xlsx *.csv *.json)"
|
|
2062
2214
|
)
|
|
2063
2215
|
|
|
2064
2216
|
try:
|
|
2065
2217
|
if sort == 'Node Identities':
|
|
2066
2218
|
my_network.load_node_identities(file_path = filename)
|
|
2067
2219
|
|
|
2220
|
+
first_value = list(my_network.node_identities.values())[0] # Check that there are not multiple IDs
|
|
2221
|
+
if isinstance(first_value, (list, tuple)):
|
|
2222
|
+
trump_value, ok = QInputDialog.getText(
|
|
2223
|
+
self,
|
|
2224
|
+
'Multiple IDs Detected',
|
|
2225
|
+
'The node identities appear to contain multiple ids per node in a list.\n'
|
|
2226
|
+
'If you desire one node ID to trump all others, enter it here.\n'
|
|
2227
|
+
'(Enter "-" to have the first IDs trump all others or press x to skip)'
|
|
2228
|
+
)
|
|
2229
|
+
if not ok or trump_value.strip() == '':
|
|
2230
|
+
trump_value = None
|
|
2231
|
+
elif trump_value.upper() == '-':
|
|
2232
|
+
trump_value = '-'
|
|
2233
|
+
my_network.node_identities = uncork(my_network.node_identities, trump_value)
|
|
2234
|
+
else:
|
|
2235
|
+
trump_value = None
|
|
2236
|
+
my_network.node_identities = uncork(my_network.node_identities, trump_value)
|
|
2237
|
+
|
|
2238
|
+
|
|
2068
2239
|
if hasattr(my_network, 'node_identities') and my_network.node_identities is not None:
|
|
2069
2240
|
try:
|
|
2070
2241
|
self.format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
|
|
@@ -2091,9 +2262,13 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2091
2262
|
|
|
2092
2263
|
|
|
2093
2264
|
except Exception as e:
|
|
2265
|
+
import traceback
|
|
2266
|
+
print(traceback.format_exc())
|
|
2094
2267
|
print(f"An error has occured: {e}")
|
|
2095
2268
|
|
|
2096
2269
|
except Exception as e:
|
|
2270
|
+
import traceback
|
|
2271
|
+
print(traceback.format_exc())
|
|
2097
2272
|
QMessageBox.critical(
|
|
2098
2273
|
self,
|
|
2099
2274
|
"Error Loading",
|
|
@@ -2245,7 +2420,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2245
2420
|
self,
|
|
2246
2421
|
f"Load Network",
|
|
2247
2422
|
"",
|
|
2248
|
-
"Spreadsheets (*.xlsx *.csv)"
|
|
2423
|
+
"Spreadsheets (*.xlsx *.csv *.json)"
|
|
2249
2424
|
)
|
|
2250
2425
|
|
|
2251
2426
|
my_network.load_network(file_path = filename)
|
|
@@ -2294,7 +2469,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2294
2469
|
"TIFF Files (*.tif *.tiff)"
|
|
2295
2470
|
)
|
|
2296
2471
|
self.channel_data[channel_index] = tifffile.imread(filename)
|
|
2297
|
-
print(self.channel_data[channel_index].shape)
|
|
2298
2472
|
if len(self.channel_data[channel_index].shape) == 2:
|
|
2299
2473
|
#self.channel_data[channel_index] = np.stack((self.channel_data[channel_index], self.channel_data[channel_index]), axis = 0) #currently handle 2d arrays by just making them 3d
|
|
2300
2474
|
self.channel_data[channel_index] = np.expand_dims(self.channel_data[channel_index], axis=0)
|
|
@@ -2746,8 +2920,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2746
2920
|
dialog = MotherDialog(self)
|
|
2747
2921
|
dialog.exec()
|
|
2748
2922
|
|
|
2749
|
-
def show_code_dialog(self):
|
|
2750
|
-
dialog = CodeDialog(self)
|
|
2923
|
+
def show_code_dialog(self, sort = 'Community'):
|
|
2924
|
+
dialog = CodeDialog(self, sort = sort)
|
|
2751
2925
|
dialog.exec()
|
|
2752
2926
|
|
|
2753
2927
|
|
|
@@ -4134,16 +4308,19 @@ class NeighborIdentityDialog(QDialog):
|
|
|
4134
4308
|
|
|
4135
4309
|
layout = QFormLayout(self)
|
|
4136
4310
|
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4311
|
+
if my_network.node_identities is not None:
|
|
4312
|
+
self.root = QComboBox()
|
|
4313
|
+
self.root.addItems(list(set(my_network.node_identities.values())))
|
|
4314
|
+
self.root.setCurrentIndex(0)
|
|
4315
|
+
layout.addRow("Root Identity to Search for Neighbor's IDs (search uses nodes of this ID, finds what IDs they connect to", self.root)
|
|
4316
|
+
else:
|
|
4317
|
+
self.root = None
|
|
4141
4318
|
|
|
4142
4319
|
self.directory = QLineEdit("")
|
|
4143
4320
|
layout.addRow("Output Directory:", self.directory)
|
|
4144
4321
|
|
|
4145
4322
|
self.mode = QComboBox()
|
|
4146
|
-
self.mode.addItems(["From Network - Based on Absolute Connectivity", "Use Labeled Nodes - Based on Neighborhood Densities"])
|
|
4323
|
+
self.mode.addItems(["From Network - Based on Absolute Connectivity", "Use Labeled Nodes - Based on Morphological Neighborhood Densities"])
|
|
4147
4324
|
self.mode.setCurrentIndex(0)
|
|
4148
4325
|
layout.addRow("Mode", self.mode)
|
|
4149
4326
|
|
|
@@ -4157,21 +4334,33 @@ class NeighborIdentityDialog(QDialog):
|
|
|
4157
4334
|
|
|
4158
4335
|
def neighborids(self):
|
|
4159
4336
|
|
|
4160
|
-
|
|
4337
|
+
try:
|
|
4161
4338
|
|
|
4162
|
-
|
|
4339
|
+
try:
|
|
4340
|
+
root = self.root.currentText()
|
|
4341
|
+
except:
|
|
4342
|
+
pass
|
|
4163
4343
|
|
|
4164
|
-
|
|
4344
|
+
directory = self.directory.text() if self.directory.text().strip() else None
|
|
4165
4345
|
|
|
4166
|
-
|
|
4346
|
+
mode = self.mode.currentIndex()
|
|
4167
4347
|
|
|
4348
|
+
search = float(self.search.text()) if self.search.text().strip() else 0
|
|
4168
4349
|
|
|
4169
|
-
result, result2, title1, title2 = my_network.neighborhood_identities(root = root, directory = directory, mode = mode, search = search)
|
|
4170
4350
|
|
|
4171
|
-
|
|
4172
|
-
|
|
4351
|
+
result, result2, title1, title2, densities = my_network.neighborhood_identities(root = root, directory = directory, mode = mode, search = search)
|
|
4352
|
+
|
|
4353
|
+
self.parent().format_for_upperright_table(result, 'Node Identity', 'Amount', title = title1)
|
|
4354
|
+
self.parent().format_for_upperright_table(result2, 'Node Identity', 'Proportion', title = title2)
|
|
4355
|
+
|
|
4356
|
+
if mode == 1:
|
|
4357
|
+
|
|
4358
|
+
self.parent().format_for_upperright_table(densities, 'Node Identity', 'Density in search/density total', title = f'Clustering Factor of Node Identities with {search} from nodes {root}')
|
|
4173
4359
|
|
|
4174
|
-
|
|
4360
|
+
|
|
4361
|
+
self.accept()
|
|
4362
|
+
except Exception as e:
|
|
4363
|
+
print(f"Error: {e}")
|
|
4175
4364
|
|
|
4176
4365
|
|
|
4177
4366
|
|
|
@@ -4521,14 +4710,16 @@ class MotherDialog(QDialog):
|
|
|
4521
4710
|
|
|
4522
4711
|
class CodeDialog(QDialog):
|
|
4523
4712
|
|
|
4524
|
-
def __init__(self, parent=None):
|
|
4713
|
+
def __init__(self, parent=None, sort = 'Community'):
|
|
4525
4714
|
|
|
4526
4715
|
super().__init__(parent)
|
|
4527
|
-
self.setWindowTitle("
|
|
4716
|
+
self.setWindowTitle(f"{sort} Code Parameters (Will go to Overlay2)")
|
|
4528
4717
|
self.setModal(True)
|
|
4529
4718
|
|
|
4530
4719
|
layout = QFormLayout(self)
|
|
4531
4720
|
|
|
4721
|
+
self.sort = sort
|
|
4722
|
+
|
|
4532
4723
|
self.down_factor = QLineEdit("")
|
|
4533
4724
|
layout.addRow("down_factor (for speeding up overlay generation - optional):", self.down_factor)
|
|
4534
4725
|
|
|
@@ -4540,7 +4731,7 @@ class CodeDialog(QDialog):
|
|
|
4540
4731
|
|
|
4541
4732
|
|
|
4542
4733
|
# Add Run button
|
|
4543
|
-
run_button = QPushButton("
|
|
4734
|
+
run_button = QPushButton(f"{sort} Code")
|
|
4544
4735
|
run_button.clicked.connect(self.code)
|
|
4545
4736
|
layout.addWidget(run_button)
|
|
4546
4737
|
|
|
@@ -4553,16 +4744,27 @@ class CodeDialog(QDialog):
|
|
|
4553
4744
|
down_factor = float(self.down_factor.text()) if self.down_factor.text().strip() else None
|
|
4554
4745
|
|
|
4555
4746
|
|
|
4556
|
-
|
|
4557
|
-
if my_network.communities is None:
|
|
4558
|
-
self.parent().show_partition_dialog()
|
|
4747
|
+
if self.sort == 'Community':
|
|
4559
4748
|
if my_network.communities is None:
|
|
4560
|
-
|
|
4749
|
+
self.parent().show_partition_dialog()
|
|
4750
|
+
if my_network.communities is None:
|
|
4751
|
+
return
|
|
4752
|
+
elif my_network.node_identities is None:
|
|
4753
|
+
print("Node identities are not set")
|
|
4754
|
+
return
|
|
4561
4755
|
|
|
4562
|
-
if
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4756
|
+
if self.sort == 'Community':
|
|
4757
|
+
if mode == 0:
|
|
4758
|
+
image, output = my_network.extract_communities(down_factor = down_factor)
|
|
4759
|
+
elif mode == 1:
|
|
4760
|
+
image, output = my_network.extract_communities(color_code = False, down_factor = down_factor)
|
|
4761
|
+
else:
|
|
4762
|
+
if mode == 0:
|
|
4763
|
+
image, output = my_network.extract_communities(down_factor = down_factor, identities = True)
|
|
4764
|
+
elif mode == 1:
|
|
4765
|
+
image, output = my_network.extract_communities(color_code = False, down_factor = down_factor, identities = True)
|
|
4766
|
+
|
|
4767
|
+
self.parent().format_for_upperright_table(output, f'{self.sort} Id', f'Encoding Val: {self.sort}', 'Legend')
|
|
4566
4768
|
|
|
4567
4769
|
|
|
4568
4770
|
self.parent().load_channel(3, image, True)
|
|
@@ -4570,6 +4772,8 @@ class CodeDialog(QDialog):
|
|
|
4570
4772
|
|
|
4571
4773
|
except Exception as e:
|
|
4572
4774
|
print(f"An error has occurred: {e}")
|
|
4775
|
+
import traceback
|
|
4776
|
+
print(traceback.format_exc())
|
|
4573
4777
|
|
|
4574
4778
|
|
|
4575
4779
|
|
|
@@ -5052,7 +5256,7 @@ class DilateDialog(QDialog):
|
|
|
5052
5256
|
|
|
5053
5257
|
# Add mode selection dropdown
|
|
5054
5258
|
self.mode_selector = QComboBox()
|
|
5055
|
-
self.mode_selector.addItems(["Binary Dilation", "Preserve Labels (slower)"])
|
|
5259
|
+
self.mode_selector.addItems(["Binary Dilation", "Preserve Labels (slower)", "Recursive Binary Dilation (Use if the dilation radius is much larger than your objects)"])
|
|
5056
5260
|
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
5057
5261
|
layout.addRow("Execution Mode:", self.mode_selector)
|
|
5058
5262
|
|
|
@@ -5093,12 +5297,18 @@ class DilateDialog(QDialog):
|
|
|
5093
5297
|
self.accept()
|
|
5094
5298
|
return
|
|
5095
5299
|
|
|
5300
|
+
if accepted_mode == 2:
|
|
5301
|
+
recursive = True
|
|
5302
|
+
else:
|
|
5303
|
+
recursive = False
|
|
5304
|
+
|
|
5096
5305
|
# Call dilate method with parameters
|
|
5097
5306
|
result = n3d.dilate(
|
|
5098
5307
|
active_data,
|
|
5099
5308
|
amount,
|
|
5100
5309
|
xy_scale = xy_scale,
|
|
5101
5310
|
z_scale = z_scale,
|
|
5311
|
+
recursive = recursive
|
|
5102
5312
|
)
|
|
5103
5313
|
|
|
5104
5314
|
# Update both the display data and the network object
|
|
@@ -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
|
|
@@ -105,6 +106,25 @@ def open_network(excel_file_path):
|
|
|
105
106
|
|
|
106
107
|
def read_excel_to_lists(file_path, sheet_name=0):
|
|
107
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
|
+
|
|
108
128
|
if type(file_path) == str:
|
|
109
129
|
# Check file extension
|
|
110
130
|
if file_path.lower().endswith('.xlsx'):
|
|
@@ -115,8 +135,11 @@ def read_excel_to_lists(file_path, sheet_name=0):
|
|
|
115
135
|
# Read the CSV file into a DataFrame without headers
|
|
116
136
|
df = pd.read_csv(file_path, header=None)
|
|
117
137
|
df = df.drop(0)
|
|
138
|
+
elif file_path.lower().endswith('.json'):
|
|
139
|
+
df = load_json_to_list(file_path)
|
|
140
|
+
return df
|
|
118
141
|
else:
|
|
119
|
-
raise ValueError("File must be either .xlsx or .
|
|
142
|
+
raise ValueError("File must be either .xlsx, .csv, or .json format")
|
|
120
143
|
else:
|
|
121
144
|
df = file_path
|
|
122
145
|
|
|
@@ -134,7 +157,7 @@ def read_excel_to_lists(file_path, sheet_name=0):
|
|
|
134
157
|
try:
|
|
135
158
|
master_list[2].extend(data_lists[i+2])
|
|
136
159
|
except IndexError:
|
|
137
|
-
|
|
160
|
+
master_list[2].extend(0)
|
|
138
161
|
|
|
139
162
|
return master_list
|
|
140
163
|
|
|
@@ -402,13 +425,28 @@ def read_centroids_to_dict(file_path):
|
|
|
402
425
|
Returns:
|
|
403
426
|
dict: Dictionary with first column as keys and next three columns as numpy array values
|
|
404
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
|
|
405
441
|
# Check file extension
|
|
406
442
|
if file_path.lower().endswith('.xlsx'):
|
|
407
443
|
df = pd.read_excel(file_path)
|
|
408
444
|
elif file_path.lower().endswith('.csv'):
|
|
409
445
|
df = pd.read_csv(file_path)
|
|
446
|
+
elif file_path.lower().endswith('.json'):
|
|
447
|
+
df = load_json_to_dict(file_path)
|
|
410
448
|
else:
|
|
411
|
-
raise ValueError("Unsupported file format. Please provide either .xlsx or .
|
|
449
|
+
raise ValueError("Unsupported file format. Please provide either .xlsx, .csv, or .json file")
|
|
412
450
|
|
|
413
451
|
# Initialize an empty dictionary
|
|
414
452
|
data_dict = {}
|
|
@@ -434,13 +472,30 @@ def read_excel_to_singval_dict(file_path):
|
|
|
434
472
|
Returns:
|
|
435
473
|
dict: Dictionary with first column as keys and second column as values
|
|
436
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
|
+
|
|
437
489
|
# Check file extension and read accordingly
|
|
438
490
|
if file_path.lower().endswith('.xlsx'):
|
|
439
491
|
df = pd.read_excel(file_path)
|
|
440
492
|
elif file_path.lower().endswith('.csv'):
|
|
441
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
|
|
442
497
|
else:
|
|
443
|
-
raise ValueError("Unsupported file format. Please provide either .xlsx or .
|
|
498
|
+
raise ValueError("Unsupported file format. Please provide either .xlsx, .csv, or .json file")
|
|
444
499
|
|
|
445
500
|
# Convert the DataFrame to a dictionary
|
|
446
501
|
data_dict = {}
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|