nettracer3d 0.2.5__py3-none-any.whl → 0.2.7__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 +153 -0
- nettracer3d/hub_getter.py +1 -1
- nettracer3d/modularity.py +263 -19
- nettracer3d/morphology.py +144 -7
- nettracer3d/nettracer.py +542 -48
- nettracer3d/nettracer_gui.py +1685 -255
- nettracer3d/network_analysis.py +96 -26
- nettracer3d/node_draw.py +3 -1
- nettracer3d/proximity.py +66 -6
- nettracer3d/simple_network.py +82 -72
- {nettracer3d-0.2.5.dist-info → nettracer3d-0.2.7.dist-info}/METADATA +1 -1
- nettracer3d-0.2.7.dist-info/RECORD +18 -0
- nettracer3d-0.2.5.dist-info/RECORD +0 -18
- {nettracer3d-0.2.5.dist-info → nettracer3d-0.2.7.dist-info}/LICENSE +0 -0
- {nettracer3d-0.2.5.dist-info → nettracer3d-0.2.7.dist-info}/WHEEL +0 -0
- {nettracer3d-0.2.5.dist-info → nettracer3d-0.2.7.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -13,11 +13,17 @@ from matplotlib.figure import Figure
|
|
|
13
13
|
import matplotlib.pyplot as plt
|
|
14
14
|
from qtrangeslider import QRangeSlider
|
|
15
15
|
from nettracer3d import nettracer as n3d
|
|
16
|
+
from nettracer3d import smart_dilate as sdl
|
|
17
|
+
from nettracer3d import proximity as pxt
|
|
16
18
|
from matplotlib.colors import LinearSegmentedColormap
|
|
17
19
|
import pandas as pd
|
|
18
20
|
from PyQt6.QtGui import (QFont, QCursor, QColor)
|
|
19
21
|
import tifffile
|
|
20
22
|
import copy
|
|
23
|
+
import multiprocessing as mp
|
|
24
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
25
|
+
from functools import partial
|
|
26
|
+
|
|
21
27
|
|
|
22
28
|
class ImageViewerWindow(QMainWindow):
|
|
23
29
|
def __init__(self):
|
|
@@ -120,6 +126,15 @@ class ImageViewerWindow(QMainWindow):
|
|
|
120
126
|
2: [0,0],
|
|
121
127
|
3: [0,0]
|
|
122
128
|
}
|
|
129
|
+
|
|
130
|
+
self.volume_dict = {
|
|
131
|
+
0: None,
|
|
132
|
+
1: None,
|
|
133
|
+
2: None,
|
|
134
|
+
3: None
|
|
135
|
+
} #For storing thresholding information
|
|
136
|
+
|
|
137
|
+
self.original_shape = None #For undoing resamples
|
|
123
138
|
|
|
124
139
|
# Create control panel
|
|
125
140
|
control_panel = QWidget()
|
|
@@ -379,23 +394,44 @@ class ImageViewerWindow(QMainWindow):
|
|
|
379
394
|
elif self.scroll_direction > 0 and new_value <= self.slice_slider.maximum():
|
|
380
395
|
self.slice_slider.setValue(new_value)
|
|
381
396
|
|
|
382
|
-
|
|
397
|
+
|
|
398
|
+
def create_highlight_overlay(self, node_indices=None, edge_indices=None, overlay1_indices = None, overlay2_indices = None):
|
|
383
399
|
"""
|
|
384
|
-
Create a binary overlay highlighting specific nodes and/or edges using
|
|
400
|
+
Create a binary overlay highlighting specific nodes and/or edges using parallel processing.
|
|
385
401
|
|
|
386
402
|
Args:
|
|
387
403
|
node_indices (list): List of node indices to highlight
|
|
388
404
|
edge_indices (list): List of edge indices to highlight
|
|
389
405
|
"""
|
|
406
|
+
|
|
407
|
+
def process_chunk(chunk_data, indices_to_check):
|
|
408
|
+
"""Process a single chunk of the array to create highlight mask"""
|
|
409
|
+
mask = np.isin(chunk_data, indices_to_check)
|
|
410
|
+
return mask * 255
|
|
411
|
+
|
|
412
|
+
if node_indices is not None:
|
|
413
|
+
if 0 in node_indices:
|
|
414
|
+
node_indices.remove(0)
|
|
415
|
+
if edge_indices is not None:
|
|
416
|
+
if 0 in edge_indices:
|
|
417
|
+
edge_indices.remove(0)
|
|
418
|
+
if overlay1_indices is not None:
|
|
419
|
+
if 0 in overlay1_indices:
|
|
420
|
+
overlay1_indices.remove(0)
|
|
421
|
+
|
|
390
422
|
if node_indices is None:
|
|
391
423
|
node_indices = []
|
|
392
424
|
if edge_indices is None:
|
|
393
425
|
edge_indices = []
|
|
426
|
+
if overlay1_indices is None:
|
|
427
|
+
overlay1_indices = []
|
|
428
|
+
if overlay2_indices is None:
|
|
429
|
+
overlay2_indices = []
|
|
394
430
|
|
|
395
|
-
current_xlim = self.ax.get_xlim() if hasattr(self, 'ax') and self.ax.get_xlim() != (0, 1) else None
|
|
431
|
+
current_xlim = self.ax.get_xlim() if hasattr(self, 'ax') and self.ax.get_xlim() != (0, 1) else None
|
|
396
432
|
current_ylim = self.ax.get_ylim() if hasattr(self, 'ax') and self.ax.get_ylim() != (0, 1) else None
|
|
397
|
-
|
|
398
|
-
if not node_indices and not edge_indices:
|
|
433
|
+
|
|
434
|
+
if not node_indices and not edge_indices and not overlay1_indices and not overlay2_indices:
|
|
399
435
|
self.highlight_overlay = None
|
|
400
436
|
self.highlight_bounds = None
|
|
401
437
|
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
@@ -412,22 +448,62 @@ class ImageViewerWindow(QMainWindow):
|
|
|
412
448
|
# Initialize full-size overlay
|
|
413
449
|
self.highlight_overlay = np.zeros(full_shape, dtype=np.uint8)
|
|
414
450
|
|
|
415
|
-
#
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
451
|
+
# Get number of CPU cores
|
|
452
|
+
num_cores = mp.cpu_count()
|
|
453
|
+
|
|
454
|
+
# Calculate chunk size along y-axis
|
|
455
|
+
chunk_size = full_shape[0] // num_cores
|
|
456
|
+
if chunk_size < 1:
|
|
457
|
+
chunk_size = 1
|
|
458
|
+
|
|
459
|
+
def process_channel(channel_data, indices, array_shape):
|
|
460
|
+
if channel_data is None or not indices:
|
|
461
|
+
return None
|
|
462
|
+
|
|
463
|
+
# Create chunks
|
|
464
|
+
chunks = []
|
|
465
|
+
for i in range(0, array_shape[0], chunk_size):
|
|
466
|
+
end = min(i + chunk_size, array_shape[0])
|
|
467
|
+
chunks.append(channel_data[i:end])
|
|
419
468
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
mask = np.isin(self.channel_data[1], edge_indices)
|
|
423
|
-
self.highlight_overlay[mask] = 255
|
|
469
|
+
# Process chunks in parallel using ThreadPoolExecutor
|
|
470
|
+
process_func = partial(process_chunk, indices_to_check=indices)
|
|
424
471
|
|
|
425
|
-
|
|
472
|
+
with ThreadPoolExecutor(max_workers=num_cores) as executor:
|
|
473
|
+
chunk_results = list(executor.map(process_func, chunks))
|
|
474
|
+
|
|
475
|
+
# Reassemble the chunks
|
|
476
|
+
return np.vstack(chunk_results)
|
|
477
|
+
|
|
478
|
+
# Process nodes and edges in parallel using multiprocessing
|
|
479
|
+
with ThreadPoolExecutor(max_workers=2) as executor:
|
|
480
|
+
future_nodes = executor.submit(process_channel, self.channel_data[0], node_indices, full_shape)
|
|
481
|
+
future_edges = executor.submit(process_channel, self.channel_data[1], edge_indices, full_shape)
|
|
482
|
+
future_overlay1 = executor.submit(process_channel, self.channel_data[2], overlay1_indices, full_shape)
|
|
483
|
+
future_overlay2 = executor.submit(process_channel, self.channel_data[3], overlay2_indices, full_shape)
|
|
484
|
+
|
|
485
|
+
# Get results
|
|
486
|
+
node_overlay = future_nodes.result()
|
|
487
|
+
edge_overlay = future_edges.result()
|
|
488
|
+
overlay1_overlay = future_overlay1.result()
|
|
489
|
+
overlay2_overlay = future_overlay2.result()
|
|
490
|
+
|
|
491
|
+
# Combine results
|
|
492
|
+
if node_overlay is not None:
|
|
493
|
+
self.highlight_overlay = np.maximum(self.highlight_overlay, node_overlay)
|
|
494
|
+
if edge_overlay is not None:
|
|
495
|
+
self.highlight_overlay = np.maximum(self.highlight_overlay, edge_overlay)
|
|
496
|
+
if overlay1_overlay is not None:
|
|
497
|
+
self.highlight_overlay = np.maximum(self.highlight_overlay, overlay1_overlay)
|
|
498
|
+
if overlay2_overlay is not None:
|
|
499
|
+
self.highlight_overlay = np.maximum(self.highlight_overlay, overlay2_overlay)
|
|
500
|
+
|
|
426
501
|
# Update display
|
|
427
502
|
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
428
503
|
|
|
429
504
|
|
|
430
505
|
|
|
506
|
+
|
|
431
507
|
#METHODS RELATED TO RIGHT CLICK:
|
|
432
508
|
|
|
433
509
|
def create_context_menu(self, event):
|
|
@@ -474,6 +550,20 @@ class ImageViewerWindow(QMainWindow):
|
|
|
474
550
|
select_edges = select_all_menu.addAction("Edges")
|
|
475
551
|
context_menu.addMenu(select_all_menu)
|
|
476
552
|
|
|
553
|
+
if len(self.clicked_values['nodes']) > 0 or len(self.clicked_values['edges']) > 0:
|
|
554
|
+
highlight_menu = QMenu("Selection", self)
|
|
555
|
+
if len(self.clicked_values['nodes']) > 1 or len(self.clicked_values['edges']) > 1:
|
|
556
|
+
combine_obj = highlight_menu.addAction("Combine Object Labels")
|
|
557
|
+
combine_obj.triggered.connect(self.handle_combine)
|
|
558
|
+
delete_obj = highlight_menu.addAction("Delete Selection")
|
|
559
|
+
delete_obj.triggered.connect(self.handle_delete)
|
|
560
|
+
if len(self.clicked_values['nodes']) > 1:
|
|
561
|
+
link_nodes = highlight_menu.addAction("Link Nodes")
|
|
562
|
+
link_nodes.triggered.connect(self.handle_link)
|
|
563
|
+
delink_nodes = highlight_menu.addAction("Split Nodes")
|
|
564
|
+
delink_nodes.triggered.connect(self.handle_split)
|
|
565
|
+
context_menu.addMenu(highlight_menu)
|
|
566
|
+
|
|
477
567
|
# Create measure menu
|
|
478
568
|
measure_menu = QMenu("Measure", self)
|
|
479
569
|
|
|
@@ -901,6 +991,186 @@ class ImageViewerWindow(QMainWindow):
|
|
|
901
991
|
except Exception as e:
|
|
902
992
|
print(f"Error: {e}")
|
|
903
993
|
|
|
994
|
+
def handle_combine(self):
|
|
995
|
+
|
|
996
|
+
try:
|
|
997
|
+
|
|
998
|
+
self.clicked_values['nodes'].sort()
|
|
999
|
+
nodes = copy.deepcopy(self.clicked_values['nodes'])
|
|
1000
|
+
self.clicked_values['edges'].sort()
|
|
1001
|
+
edges = copy.deepcopy(self.clicked_values['edges'])
|
|
1002
|
+
|
|
1003
|
+
if len(nodes) > 1:
|
|
1004
|
+
new_nodes = nodes[0]
|
|
1005
|
+
|
|
1006
|
+
mask = np.isin(self.channel_data[0], nodes)
|
|
1007
|
+
my_network.nodes[mask] = new_nodes
|
|
1008
|
+
self.load_channel(0, my_network.nodes, True)
|
|
1009
|
+
self.clicked_values['nodes'] = new_nodes
|
|
1010
|
+
|
|
1011
|
+
if len(edges) > 1:
|
|
1012
|
+
new_edges = edges[0]
|
|
1013
|
+
|
|
1014
|
+
mask = np.isin(self.channel_data[1], edges)
|
|
1015
|
+
my_network.edges[mask] = new_edges
|
|
1016
|
+
self.load_channel(1, my_network.edges, True)
|
|
1017
|
+
self.clicked_values['edges'] = new_edges
|
|
1018
|
+
|
|
1019
|
+
try:
|
|
1020
|
+
|
|
1021
|
+
for i in range(len(my_network.network_lists[0])):
|
|
1022
|
+
if my_network.network_lists[0][i] in nodes and len(nodes) > 1:
|
|
1023
|
+
my_network.network_lists[0][i] = new_nodes
|
|
1024
|
+
if my_network.network_lists[1][i] in nodes and len(nodes) > 1:
|
|
1025
|
+
my_network.network_lists[1][i] = new_nodes
|
|
1026
|
+
if my_network.network_lists[2][i] in edges and len(edges) > 1:
|
|
1027
|
+
my_network.network_lists[2][i] = new_edges
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
my_network.network_lists = my_network.network_lists
|
|
1031
|
+
|
|
1032
|
+
if not hasattr(my_network, 'network_lists') or my_network.network_lists is None:
|
|
1033
|
+
empty_df = pd.DataFrame(columns=['Node 1A', 'Node 1B', 'Edge 1C'])
|
|
1034
|
+
model = PandasModel(empty_df)
|
|
1035
|
+
self.network_table.setModel(model)
|
|
1036
|
+
else:
|
|
1037
|
+
model = PandasModel(my_network.network_lists)
|
|
1038
|
+
self.network_table.setModel(model)
|
|
1039
|
+
# Adjust column widths to content
|
|
1040
|
+
for column in range(model.columnCount(None)):
|
|
1041
|
+
self.network_table.resizeColumnToContents(column)
|
|
1042
|
+
|
|
1043
|
+
except Exception as e:
|
|
1044
|
+
print(f"Error, could not update network: {e}")
|
|
1045
|
+
|
|
1046
|
+
except Exception as e:
|
|
1047
|
+
print(f"An error has occured: {e}")
|
|
1048
|
+
|
|
1049
|
+
def handle_delete(self):
|
|
1050
|
+
|
|
1051
|
+
try:
|
|
1052
|
+
if len(self.clicked_values['nodes']) > 0:
|
|
1053
|
+
self.create_highlight_overlay(node_indices = self.clicked_values['nodes'])
|
|
1054
|
+
mask = self.highlight_overlay == 0
|
|
1055
|
+
my_network.nodes = my_network.nodes * mask
|
|
1056
|
+
self.load_channel(0, my_network.nodes, True)
|
|
1057
|
+
|
|
1058
|
+
for i in range(len(my_network.network_lists[0]) - 1, -1, -1):
|
|
1059
|
+
if my_network.network_lists[0][i] in self.clicked_values['nodes'] or my_network.network_lists[0][i] in self.clicked_values['nodes']:
|
|
1060
|
+
del my_network.network_lists[0][i]
|
|
1061
|
+
del my_network.network_lists[1][i]
|
|
1062
|
+
del my_network.network_lists[2][i]
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
if len(self.clicked_values['edges']) > 0:
|
|
1067
|
+
self.create_highlight_overlay(node_indices = self.clicked_values['edges'])
|
|
1068
|
+
mask = self.highlight_overlay == 0
|
|
1069
|
+
my_network.edges = my_network.edges * mask
|
|
1070
|
+
self.load_channel(1, my_network.edges, True)
|
|
1071
|
+
|
|
1072
|
+
for i in range(len(my_network.network_lists[1]) - 1, -1, -1):
|
|
1073
|
+
if my_network.network_lists[2][i] in self.clicked_values['edges']:
|
|
1074
|
+
del my_network.network_lists[0][i]
|
|
1075
|
+
del my_network.network_lists[1][i]
|
|
1076
|
+
del my_network.network_lists[2][i]
|
|
1077
|
+
|
|
1078
|
+
my_network.network_lists = my_network.network_lists
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
if not hasattr(my_network, 'network_lists') or my_network.network_lists is None:
|
|
1082
|
+
empty_df = pd.DataFrame(columns=['Node 1A', 'Node 1B', 'Edge 1C'])
|
|
1083
|
+
model = PandasModel(empty_df)
|
|
1084
|
+
self.network_table.setModel(model)
|
|
1085
|
+
else:
|
|
1086
|
+
model = PandasModel(my_network.network_lists)
|
|
1087
|
+
self.network_table.setModel(model)
|
|
1088
|
+
# Adjust column widths to content
|
|
1089
|
+
for column in range(model.columnCount(None)):
|
|
1090
|
+
self.network_table.resizeColumnToContents(column)
|
|
1091
|
+
|
|
1092
|
+
self.show_centroid_dialog()
|
|
1093
|
+
except Exception as e:
|
|
1094
|
+
print(f"Error: {e}")
|
|
1095
|
+
|
|
1096
|
+
def handle_link(self):
|
|
1097
|
+
|
|
1098
|
+
try:
|
|
1099
|
+
nodes = self.clicked_values['nodes']
|
|
1100
|
+
from itertools import combinations
|
|
1101
|
+
pairs = list(combinations(nodes, 2))
|
|
1102
|
+
|
|
1103
|
+
# Convert existing connections to a set of tuples for efficient lookup
|
|
1104
|
+
existing_connections = set()
|
|
1105
|
+
for n1, n2 in zip(my_network.network_lists[0], my_network.network_lists[1]):
|
|
1106
|
+
existing_connections.add((n1, n2))
|
|
1107
|
+
existing_connections.add((n2, n1)) # Add reverse pair too
|
|
1108
|
+
|
|
1109
|
+
# Filter out existing connections
|
|
1110
|
+
new_pairs = []
|
|
1111
|
+
for pair in pairs:
|
|
1112
|
+
if pair not in existing_connections:
|
|
1113
|
+
new_pairs.append(pair)
|
|
1114
|
+
|
|
1115
|
+
# Add new connections
|
|
1116
|
+
for pair in new_pairs:
|
|
1117
|
+
my_network.network_lists[0].append(pair[0])
|
|
1118
|
+
my_network.network_lists[1].append(pair[1])
|
|
1119
|
+
my_network.network_lists[2].append(0)
|
|
1120
|
+
|
|
1121
|
+
# Update the table
|
|
1122
|
+
if not hasattr(my_network, 'network_lists') or my_network.network_lists is None:
|
|
1123
|
+
empty_df = pd.DataFrame(columns=['Node 1A', 'Node 1B', 'Edge 1C'])
|
|
1124
|
+
model = PandasModel(empty_df)
|
|
1125
|
+
self.network_table.setModel(model)
|
|
1126
|
+
else:
|
|
1127
|
+
model = PandasModel(my_network.network_lists)
|
|
1128
|
+
self.network_table.setModel(model)
|
|
1129
|
+
# Adjust column widths to content
|
|
1130
|
+
for column in range(model.columnCount(None)):
|
|
1131
|
+
self.network_table.resizeColumnToContents(column)
|
|
1132
|
+
except Exception as e:
|
|
1133
|
+
print(f"An error has occurred: {e}")
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
def handle_split(self):
|
|
1137
|
+
try:
|
|
1138
|
+
nodes = self.clicked_values['nodes']
|
|
1139
|
+
|
|
1140
|
+
from itertools import combinations
|
|
1141
|
+
|
|
1142
|
+
pairs = list(combinations(nodes, 2))
|
|
1143
|
+
|
|
1144
|
+
print(pairs)
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
for i in range(len(my_network.network_lists[0]) - 1, -1, -1):
|
|
1148
|
+
print((my_network.network_lists[0][i], my_network.network_lists[1][i]))
|
|
1149
|
+
if (my_network.network_lists[0][i], my_network.network_lists[1][i]) in pairs or (my_network.network_lists[1][i], my_network.network_lists[0][i]) in pairs:
|
|
1150
|
+
del my_network.network_lists[0][i]
|
|
1151
|
+
del my_network.network_lists[1][i]
|
|
1152
|
+
del my_network.network_lists[2][i]
|
|
1153
|
+
|
|
1154
|
+
my_network.network_lists = my_network.network_lists
|
|
1155
|
+
|
|
1156
|
+
if not hasattr(my_network, 'network_lists') or my_network.network_lists is None:
|
|
1157
|
+
empty_df = pd.DataFrame(columns=['Node 1A', 'Node 1B', 'Edge 1C'])
|
|
1158
|
+
model = PandasModel(empty_df)
|
|
1159
|
+
self.network_table.setModel(model)
|
|
1160
|
+
else:
|
|
1161
|
+
model = PandasModel(my_network.network_lists)
|
|
1162
|
+
self.network_table.setModel(model)
|
|
1163
|
+
# Adjust column widths to content
|
|
1164
|
+
for column in range(model.columnCount(None)):
|
|
1165
|
+
self.network_table.resizeColumnToContents(column)
|
|
1166
|
+
except Exception as e:
|
|
1167
|
+
print(f"An error has occurred: {e}")
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
|
|
904
1174
|
|
|
905
1175
|
def handle_highlight_select(self):
|
|
906
1176
|
|
|
@@ -1264,18 +1534,19 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1264
1534
|
# Get clicked value
|
|
1265
1535
|
x_idx = int(round(event.xdata))
|
|
1266
1536
|
y_idx = int(round(event.ydata))
|
|
1537
|
+
# Check if Ctrl key is pressed (using matplotlib's key_press system)
|
|
1538
|
+
ctrl_pressed = 'ctrl' in event.modifiers # Note: changed from 'control' to 'ctrl'
|
|
1267
1539
|
if self.channel_data[self.active_channel][self.current_slice, y_idx, x_idx] != 0:
|
|
1268
1540
|
clicked_value = self.channel_data[self.active_channel][self.current_slice, y_idx, x_idx]
|
|
1269
1541
|
else:
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1542
|
+
if not ctrl_pressed:
|
|
1543
|
+
self.clicked_values = {
|
|
1544
|
+
'nodes': [],
|
|
1545
|
+
'edges': []
|
|
1546
|
+
}
|
|
1547
|
+
self.create_highlight_overlay()
|
|
1275
1548
|
return
|
|
1276
1549
|
|
|
1277
|
-
# Check if Ctrl key is pressed (using matplotlib's key_press system)
|
|
1278
|
-
ctrl_pressed = 'ctrl' in event.modifiers # Note: changed from 'control' to 'ctrl'
|
|
1279
1550
|
|
|
1280
1551
|
starting_vals = copy.deepcopy(self.clicked_values)
|
|
1281
1552
|
|
|
@@ -1384,6 +1655,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1384
1655
|
load_action.triggered.connect(lambda: self.load_misc('Node Centroids'))
|
|
1385
1656
|
load_action = misc_menu.addAction("Load Edge Centroids")
|
|
1386
1657
|
load_action.triggered.connect(lambda: self.load_misc('Edge Centroids'))
|
|
1658
|
+
load_action = misc_menu.addAction("Merge Nodes")
|
|
1659
|
+
load_action.triggered.connect(lambda: self.load_misc('Merge Nodes'))
|
|
1387
1660
|
|
|
1388
1661
|
|
|
1389
1662
|
# Analysis menu
|
|
@@ -1391,11 +1664,19 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1391
1664
|
network_menu = analysis_menu.addMenu("Network")
|
|
1392
1665
|
netshow_action = network_menu.addAction("Show Network")
|
|
1393
1666
|
netshow_action.triggered.connect(self.show_netshow_dialog)
|
|
1394
|
-
partition_action = network_menu.addAction("Community Partition")
|
|
1667
|
+
partition_action = network_menu.addAction("Community Partition + Community Stats")
|
|
1395
1668
|
partition_action.triggered.connect(self.show_partition_dialog)
|
|
1396
1669
|
stats_menu = analysis_menu.addMenu("Stats")
|
|
1397
1670
|
allstats_action = stats_menu.addAction("Calculate Generic Network Stats")
|
|
1398
1671
|
allstats_action.triggered.connect(self.stats)
|
|
1672
|
+
radial_action = stats_menu.addAction("Radial Distribution Analysis")
|
|
1673
|
+
radial_action.triggered.connect(self.show_radial_dialog)
|
|
1674
|
+
degree_dist_action = stats_menu.addAction("Degree Distribution Analysis")
|
|
1675
|
+
degree_dist_action.triggered.connect(self.show_degree_dist_dialog)
|
|
1676
|
+
neighbor_id_action = stats_menu.addAction("Identity Distribution of Neighbors")
|
|
1677
|
+
neighbor_id_action.triggered.connect(self.show_neighbor_id_dialog)
|
|
1678
|
+
random_action = stats_menu.addAction("Generate Equivalent Random Network")
|
|
1679
|
+
random_action.triggered.connect(self.show_random_dialog)
|
|
1399
1680
|
vol_action = stats_menu.addAction("Calculate Volumes")
|
|
1400
1681
|
vol_action.triggered.connect(self.volumes)
|
|
1401
1682
|
inter_action = stats_menu.addAction("Calculate Node < > Edge Interaction")
|
|
@@ -1403,8 +1684,13 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1403
1684
|
overlay_menu = analysis_menu.addMenu("Data/Overlays")
|
|
1404
1685
|
degree_action = overlay_menu.addAction("Get Degree Information")
|
|
1405
1686
|
degree_action.triggered.connect(self.show_degree_dialog)
|
|
1687
|
+
hub_action = overlay_menu.addAction("Get Hub Information")
|
|
1688
|
+
hub_action.triggered.connect(self.show_hub_dialog)
|
|
1406
1689
|
mother_action = overlay_menu.addAction("Get Mother Nodes")
|
|
1407
1690
|
mother_action.triggered.connect(self.show_mother_dialog)
|
|
1691
|
+
community_code_action = overlay_menu.addAction("Code Communities")
|
|
1692
|
+
community_code_action.triggered.connect(self.show_code_dialog)
|
|
1693
|
+
|
|
1408
1694
|
|
|
1409
1695
|
# Process menu
|
|
1410
1696
|
process_menu = menubar.addMenu("Process")
|
|
@@ -1421,25 +1707,37 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1421
1707
|
resize_action.triggered.connect(self.show_resize_dialog)
|
|
1422
1708
|
dilate_action = image_menu.addAction("Dilate")
|
|
1423
1709
|
dilate_action.triggered.connect(self.show_dilate_dialog)
|
|
1710
|
+
erode_action = image_menu.addAction("Erode")
|
|
1711
|
+
erode_action.triggered.connect(self.show_erode_dialog)
|
|
1712
|
+
hole_action = image_menu.addAction("Fill Holes")
|
|
1713
|
+
hole_action.triggered.connect(self.show_hole_dialog)
|
|
1424
1714
|
binarize_action = image_menu.addAction("Binarize")
|
|
1425
1715
|
binarize_action.triggered.connect(self.show_binarize_dialog)
|
|
1426
1716
|
label_action = image_menu.addAction("Label Objects")
|
|
1427
1717
|
label_action.triggered.connect(self.show_label_dialog)
|
|
1718
|
+
thresh_action = image_menu.addAction("Threshold/Segment")
|
|
1719
|
+
thresh_action.triggered.connect(self.show_thresh_dialog)
|
|
1428
1720
|
mask_action = image_menu.addAction("Mask Channel")
|
|
1429
1721
|
mask_action.triggered.connect(self.show_mask_dialog)
|
|
1430
1722
|
skeletonize_action = image_menu.addAction("Skeletonize")
|
|
1431
1723
|
skeletonize_action.triggered.connect(self.show_skeletonize_dialog)
|
|
1432
1724
|
watershed_action = image_menu.addAction("Watershed")
|
|
1433
1725
|
watershed_action.triggered.connect(self.show_watershed_dialog)
|
|
1726
|
+
z_proj_action = image_menu.addAction("Z Project")
|
|
1727
|
+
z_proj_action.triggered.connect(self.show_z_dialog)
|
|
1434
1728
|
|
|
1435
|
-
|
|
1729
|
+
generate_menu = process_menu.addMenu("Generate")
|
|
1730
|
+
centroid_node_action = generate_menu.addAction("Generate Nodes (From Node Centroids)")
|
|
1436
1731
|
centroid_node_action.triggered.connect(self.show_centroid_node_dialog)
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
gennodes_action = process_menu.addAction("Generate Nodes (From 'Edge' Vertices)")
|
|
1732
|
+
gennodes_action = generate_menu.addAction("Generate Nodes (From 'Edge' Vertices)")
|
|
1440
1733
|
gennodes_action.triggered.connect(self.show_gennodes_dialog)
|
|
1441
|
-
branch_action =
|
|
1734
|
+
branch_action = generate_menu.addAction("Label Branches")
|
|
1442
1735
|
branch_action.triggered.connect(self.show_branch_dialog)
|
|
1736
|
+
genvor_action = generate_menu.addAction("Generate Voronoi Diagram (From Node Centroids) - goes in Overlay2")
|
|
1737
|
+
genvor_action.triggered.connect(self.voronoi)
|
|
1738
|
+
|
|
1739
|
+
modify_action = process_menu.addAction("Modify Network")
|
|
1740
|
+
modify_action.triggered.connect(self.show_modify_dialog)
|
|
1443
1741
|
|
|
1444
1742
|
|
|
1445
1743
|
# Image menu
|
|
@@ -1477,17 +1775,26 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1477
1775
|
|
|
1478
1776
|
def volumes(self):
|
|
1479
1777
|
|
|
1480
|
-
print(self.active_channel)
|
|
1481
1778
|
|
|
1482
1779
|
if self.active_channel == 1:
|
|
1483
1780
|
output = my_network.volumes('edges')
|
|
1484
1781
|
self.format_for_upperright_table(output, metric='Edge ID', value = 'Voxel Volume (Scaled)', title = 'Edge Volumes')
|
|
1782
|
+
self.volume_dict[1] = output
|
|
1485
1783
|
|
|
1486
|
-
|
|
1784
|
+
elif self.active_channel == 0:
|
|
1487
1785
|
output = my_network.volumes('nodes')
|
|
1488
1786
|
self.format_for_upperright_table(output, metric='Node ID', value = 'Voxel Volume (Scaled)', title = 'Node Volumes')
|
|
1787
|
+
self.volume_dict[0] = output
|
|
1489
1788
|
|
|
1789
|
+
elif self.active_channel == 2:
|
|
1790
|
+
output = my_network.volumes('network_overlay')
|
|
1791
|
+
self.format_for_upperright_table(output, metric='Object ID', value = 'Voxel Volume (Scaled)', title = 'Overlay 1 Volumes')
|
|
1792
|
+
self.volume_dict[2] = output
|
|
1490
1793
|
|
|
1794
|
+
elif self.active_channel == 3:
|
|
1795
|
+
output = my_network.volumes('id_overlay')
|
|
1796
|
+
self.format_for_upperright_table(output, metric='Object ID', value = 'Voxel Volume (Scaled)', title = 'Overlay 2 Volumes')
|
|
1797
|
+
self.volume_dict[3] = output
|
|
1491
1798
|
|
|
1492
1799
|
|
|
1493
1800
|
|
|
@@ -1582,6 +1889,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1582
1889
|
dialog = WatershedDialog(self)
|
|
1583
1890
|
dialog.exec()
|
|
1584
1891
|
|
|
1892
|
+
def show_z_dialog(self):
|
|
1893
|
+
"""Show the z-proj dialog."""
|
|
1894
|
+
dialog = ZDialog(self)
|
|
1895
|
+
dialog.exec()
|
|
1896
|
+
|
|
1585
1897
|
def show_calc_all_dialog(self):
|
|
1586
1898
|
"""Show the calculate all parameter dialog."""
|
|
1587
1899
|
dialog = CalcAllDialog(self)
|
|
@@ -1602,11 +1914,26 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1602
1914
|
dialog = DilateDialog(self)
|
|
1603
1915
|
dialog.exec()
|
|
1604
1916
|
|
|
1917
|
+
def show_erode_dialog(self):
|
|
1918
|
+
"""show the erode dialog"""
|
|
1919
|
+
dialog = ErodeDialog(self)
|
|
1920
|
+
dialog.exec()
|
|
1921
|
+
|
|
1922
|
+
def show_hole_dialog(self):
|
|
1923
|
+
"""show the hole dialog"""
|
|
1924
|
+
dialog = HoleDialog(self)
|
|
1925
|
+
dialog.exec()
|
|
1926
|
+
|
|
1605
1927
|
def show_label_dialog(self):
|
|
1606
1928
|
"""Show the label dialog"""
|
|
1607
1929
|
dialog = LabelDialog(self)
|
|
1608
1930
|
dialog.exec()
|
|
1609
1931
|
|
|
1932
|
+
def show_thresh_dialog(self):
|
|
1933
|
+
"""Show threshold dialog"""
|
|
1934
|
+
thresh_window = ThresholdWindow(self)
|
|
1935
|
+
thresh_window.show() # Non-modal window
|
|
1936
|
+
|
|
1610
1937
|
def show_mask_dialog(self):
|
|
1611
1938
|
"""Show the mask dialog"""
|
|
1612
1939
|
dialog = MaskDialog(self)
|
|
@@ -1633,6 +1960,33 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1633
1960
|
dialog = BranchDialog(self)
|
|
1634
1961
|
dialog.exec()
|
|
1635
1962
|
|
|
1963
|
+
def voronoi(self):
|
|
1964
|
+
|
|
1965
|
+
try:
|
|
1966
|
+
|
|
1967
|
+
if my_network.nodes is not None:
|
|
1968
|
+
shape = my_network.nodes.shape
|
|
1969
|
+
else:
|
|
1970
|
+
shape = None
|
|
1971
|
+
|
|
1972
|
+
if my_network.node_centroids is None:
|
|
1973
|
+
self.show_centroid_dialog()
|
|
1974
|
+
if my_network.node_centroids is None:
|
|
1975
|
+
print("Node centroids must be set")
|
|
1976
|
+
return
|
|
1977
|
+
|
|
1978
|
+
array = pxt.create_voronoi_3d_kdtree(my_network.node_centroids, shape)
|
|
1979
|
+
self.load_channel(3, array, True)
|
|
1980
|
+
|
|
1981
|
+
except Exception as e:
|
|
1982
|
+
print(f"Error generating voronoi: {e}")
|
|
1983
|
+
|
|
1984
|
+
|
|
1985
|
+
def show_modify_dialog(self):
|
|
1986
|
+
"""Show the network modify dialog"""
|
|
1987
|
+
dialog = ModifyDialog(self)
|
|
1988
|
+
dialog.exec()
|
|
1989
|
+
|
|
1636
1990
|
|
|
1637
1991
|
def show_binarize_dialog(self):
|
|
1638
1992
|
"""show the binarize dialog"""
|
|
@@ -1696,53 +2050,110 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1696
2050
|
def load_misc(self, sort):
|
|
1697
2051
|
"""Loads various things"""
|
|
1698
2052
|
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
filename, _ = QFileDialog.getOpenFileName(
|
|
1702
|
-
self,
|
|
1703
|
-
f"Load {sort}",
|
|
1704
|
-
"",
|
|
1705
|
-
"Spreadsheets (*.xlsx *.csv)"
|
|
1706
|
-
)
|
|
2053
|
+
if sort != 'Merge Nodes':
|
|
1707
2054
|
|
|
1708
2055
|
try:
|
|
1709
|
-
if sort == 'Node Identities':
|
|
1710
|
-
my_network.load_node_identities(file_path = filename)
|
|
1711
2056
|
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
2057
|
+
filename, _ = QFileDialog.getOpenFileName(
|
|
2058
|
+
self,
|
|
2059
|
+
f"Load {sort}",
|
|
2060
|
+
"",
|
|
2061
|
+
"Spreadsheets (*.xlsx *.csv)"
|
|
2062
|
+
)
|
|
1717
2063
|
|
|
1718
|
-
|
|
1719
|
-
|
|
2064
|
+
try:
|
|
2065
|
+
if sort == 'Node Identities':
|
|
2066
|
+
my_network.load_node_identities(file_path = filename)
|
|
1720
2067
|
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
2068
|
+
if hasattr(my_network, 'node_identities') and my_network.node_identities is not None:
|
|
2069
|
+
try:
|
|
2070
|
+
self.format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
|
|
2071
|
+
except Exception as e:
|
|
2072
|
+
print(f"Error loading node identity table: {e}")
|
|
1726
2073
|
|
|
1727
|
-
|
|
1728
|
-
|
|
2074
|
+
elif sort == 'Node Centroids':
|
|
2075
|
+
my_network.load_node_centroids(file_path = filename)
|
|
1729
2076
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
2077
|
+
if hasattr(my_network, 'node_centroids') and my_network.node_centroids is not None:
|
|
2078
|
+
try:
|
|
2079
|
+
self.format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
2080
|
+
except Exception as e:
|
|
2081
|
+
print(f"Error loading node centroid table: {e}")
|
|
2082
|
+
|
|
2083
|
+
elif sort == 'Edge Centroids':
|
|
2084
|
+
my_network.load_edge_centroids(file_path = filename)
|
|
1735
2085
|
|
|
2086
|
+
if hasattr(my_network, 'edge_centroids') and my_network.edge_centroids is not None:
|
|
2087
|
+
try:
|
|
2088
|
+
self.format_for_upperright_table(my_network.edge_centroids, 'EdgeID', ['Z', 'Y', 'X'], 'Edge Centroids')
|
|
2089
|
+
except Exception as e:
|
|
2090
|
+
print(f"Error loading edge centroid table: {e}")
|
|
2091
|
+
|
|
2092
|
+
|
|
2093
|
+
except Exception as e:
|
|
2094
|
+
print(f"An error has occured: {e}")
|
|
1736
2095
|
|
|
1737
2096
|
except Exception as e:
|
|
1738
|
-
|
|
2097
|
+
QMessageBox.critical(
|
|
2098
|
+
self,
|
|
2099
|
+
"Error Loading",
|
|
2100
|
+
f"Failed to load {sort}: {str(e)}"
|
|
2101
|
+
)
|
|
1739
2102
|
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
2103
|
+
else:
|
|
2104
|
+
try:
|
|
2105
|
+
|
|
2106
|
+
if len(np.unique(my_network.nodes)) < 3:
|
|
2107
|
+
self.show_label_dialog()
|
|
2108
|
+
|
|
2109
|
+
# First ask user what they want to select
|
|
2110
|
+
msg = QMessageBox()
|
|
2111
|
+
msg.setWindowTitle("Selection Type")
|
|
2112
|
+
msg.setText("Would you like to select a TIFF file or a directory?")
|
|
2113
|
+
tiff_button = msg.addButton("TIFF File", QMessageBox.ButtonRole.AcceptRole)
|
|
2114
|
+
dir_button = msg.addButton("Directory", QMessageBox.ButtonRole.AcceptRole)
|
|
2115
|
+
msg.addButton("Cancel", QMessageBox.ButtonRole.RejectRole)
|
|
2116
|
+
|
|
2117
|
+
msg.exec()
|
|
2118
|
+
|
|
2119
|
+
if msg.clickedButton() == tiff_button:
|
|
2120
|
+
# Code for selecting TIFF files
|
|
2121
|
+
filename, _ = QFileDialog.getOpenFileName(
|
|
2122
|
+
self,
|
|
2123
|
+
"Select TIFF file",
|
|
2124
|
+
"",
|
|
2125
|
+
"TIFF files (*.tiff *.tif)"
|
|
2126
|
+
)
|
|
2127
|
+
if filename:
|
|
2128
|
+
selected_path = filename
|
|
2129
|
+
|
|
2130
|
+
elif msg.clickedButton() == dir_button:
|
|
2131
|
+
# Code for selecting directories
|
|
2132
|
+
dialog = QFileDialog(self)
|
|
2133
|
+
dialog.setOption(QFileDialog.Option.DontUseNativeDialog)
|
|
2134
|
+
dialog.setOption(QFileDialog.Option.ReadOnly)
|
|
2135
|
+
dialog.setFileMode(QFileDialog.FileMode.Directory)
|
|
2136
|
+
dialog.setViewMode(QFileDialog.ViewMode.Detail)
|
|
2137
|
+
|
|
2138
|
+
if dialog.exec() == QFileDialog.DialogCode.Accepted:
|
|
2139
|
+
selected_path = dialog.directory().absolutePath()
|
|
2140
|
+
|
|
2141
|
+
my_network.merge_nodes(selected_path)
|
|
2142
|
+
self.load_channel(0, my_network.nodes, True)
|
|
2143
|
+
|
|
2144
|
+
|
|
2145
|
+
if hasattr(my_network, 'node_identities') and my_network.node_identities is not None:
|
|
2146
|
+
try:
|
|
2147
|
+
self.format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
|
|
2148
|
+
except Exception as e:
|
|
2149
|
+
print(f"Error loading node identity table: {e}")
|
|
2150
|
+
|
|
2151
|
+
except Exception as e:
|
|
2152
|
+
QMessageBox.critical(
|
|
2153
|
+
self,
|
|
2154
|
+
"Error Merging",
|
|
2155
|
+
f"Failed to load {sort}: {str(e)}"
|
|
2156
|
+
)
|
|
1746
2157
|
|
|
1747
2158
|
|
|
1748
2159
|
# Modify load_from_network_obj method
|
|
@@ -1870,7 +2281,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1870
2281
|
else:
|
|
1871
2282
|
btn.setStyleSheet("")
|
|
1872
2283
|
|
|
1873
|
-
def load_channel(self, channel_index, channel_data=None, data=False):
|
|
2284
|
+
def load_channel(self, channel_index, channel_data=None, data=False, assign_shape = True):
|
|
1874
2285
|
"""Load a channel and enable active channel selection if needed."""
|
|
1875
2286
|
|
|
1876
2287
|
try:
|
|
@@ -1883,8 +2294,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1883
2294
|
"TIFF Files (*.tif *.tiff)"
|
|
1884
2295
|
)
|
|
1885
2296
|
self.channel_data[channel_index] = tifffile.imread(filename)
|
|
2297
|
+
print(self.channel_data[channel_index].shape)
|
|
1886
2298
|
if len(self.channel_data[channel_index].shape) == 2:
|
|
1887
|
-
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
|
|
2299
|
+
#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
|
+
self.channel_data[channel_index] = np.expand_dims(self.channel_data[channel_index], axis=0)
|
|
1888
2301
|
|
|
1889
2302
|
|
|
1890
2303
|
else:
|
|
@@ -1910,18 +2323,21 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1910
2323
|
self.active_channel_combo.setEnabled(True)
|
|
1911
2324
|
|
|
1912
2325
|
# Update slider range if this is the first channel loaded
|
|
1913
|
-
if
|
|
1914
|
-
self.slice_slider.
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
2326
|
+
if len(self.channel_data[channel_index].shape) == 3:
|
|
2327
|
+
if not self.slice_slider.isEnabled():
|
|
2328
|
+
self.slice_slider.setEnabled(True)
|
|
2329
|
+
self.slice_slider.setMinimum(0)
|
|
2330
|
+
self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
|
|
2331
|
+
self.slice_slider.setValue(0)
|
|
2332
|
+
self.current_slice = 0
|
|
2333
|
+
else:
|
|
2334
|
+
self.slice_slider.setEnabled(True)
|
|
2335
|
+
self.slice_slider.setMinimum(0)
|
|
2336
|
+
self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
|
|
2337
|
+
self.slice_slider.setValue(0)
|
|
2338
|
+
self.current_slice = 0
|
|
1919
2339
|
else:
|
|
1920
|
-
self.slice_slider.setEnabled(
|
|
1921
|
-
self.slice_slider.setMinimum(0)
|
|
1922
|
-
self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
|
|
1923
|
-
self.slice_slider.setValue(0)
|
|
1924
|
-
self.current_slice = 0
|
|
2340
|
+
self.slice_slider.setEnabled(False)
|
|
1925
2341
|
|
|
1926
2342
|
|
|
1927
2343
|
# If this is the first channel loaded, make it active
|
|
@@ -1932,6 +2348,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1932
2348
|
self.channel_buttons[channel_index].click()
|
|
1933
2349
|
self.min_max[channel_index][0] = np.min(self.channel_data[channel_index])
|
|
1934
2350
|
self.min_max[channel_index][1] = np.max(self.channel_data[channel_index])
|
|
2351
|
+
self.volume_dict[channel_index] = None #reset volumes
|
|
2352
|
+
|
|
2353
|
+
if assign_shape: #keep original shape tracked to undo resampling.
|
|
2354
|
+
self.original_shape = self.channel_data[channel_index].shape
|
|
1935
2355
|
|
|
1936
2356
|
self.update_display()
|
|
1937
2357
|
|
|
@@ -2157,117 +2577,132 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2157
2577
|
self.update_display(preserve_zoom = (current_xlim, current_ylim))
|
|
2158
2578
|
|
|
2159
2579
|
def update_display(self, preserve_zoom=None):
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2580
|
+
"""Update the display with currently visible channels and highlight overlay."""
|
|
2581
|
+
self.figure.clear()
|
|
2582
|
+
|
|
2583
|
+
# Create subplot with tight layout and white figure background
|
|
2584
|
+
self.figure.patch.set_facecolor('white')
|
|
2585
|
+
self.ax = self.figure.add_subplot(111)
|
|
2586
|
+
|
|
2587
|
+
# Store current zoom limits if they exist and weren't provided
|
|
2588
|
+
if preserve_zoom is None and hasattr(self, 'ax'):
|
|
2589
|
+
current_xlim = self.ax.get_xlim() if self.ax.get_xlim() != (0, 1) else None
|
|
2590
|
+
current_ylim = self.ax.get_ylim() if self.ax.get_ylim() != (0, 1) else None
|
|
2591
|
+
else:
|
|
2592
|
+
current_xlim, current_ylim = preserve_zoom if preserve_zoom else (None, None)
|
|
2593
|
+
|
|
2594
|
+
# Define base colors for each channel with increased intensity
|
|
2595
|
+
base_colors = self.base_colors
|
|
2596
|
+
# Set only the axes (image area) background to black
|
|
2597
|
+
self.ax.set_facecolor('black')
|
|
2598
|
+
|
|
2599
|
+
# Display each visible channel
|
|
2600
|
+
for channel in range(4):
|
|
2601
|
+
if (self.channel_visible[channel] and
|
|
2602
|
+
self.channel_data[channel] is not None):
|
|
2603
|
+
|
|
2604
|
+
# Check if we're dealing with RGB data
|
|
2605
|
+
is_rgb = len(self.channel_data[channel].shape) == 4 and self.channel_data[channel].shape[-1] == 3
|
|
2606
|
+
|
|
2607
|
+
if len(self.channel_data[channel].shape) == 3 and not is_rgb:
|
|
2608
|
+
current_image = self.channel_data[channel][self.current_slice, :, :]
|
|
2609
|
+
elif is_rgb:
|
|
2610
|
+
current_image = self.channel_data[channel][self.current_slice] # Already has RGB channels
|
|
2611
|
+
else:
|
|
2612
|
+
current_image = self.channel_data[channel]
|
|
2613
|
+
|
|
2614
|
+
if is_rgb:
|
|
2615
|
+
# For RGB images, just display directly without colormap
|
|
2616
|
+
self.ax.imshow(current_image,
|
|
2617
|
+
alpha=0.7)
|
|
2618
|
+
else:
|
|
2619
|
+
# Regular channel processing with colormap
|
|
2620
|
+
# Calculate brightness/contrast limits from entire volume
|
|
2621
|
+
img_min = self.min_max[channel][0]
|
|
2622
|
+
img_max = self.min_max[channel][1]
|
|
2623
|
+
|
|
2624
|
+
# Calculate vmin and vmax, ensuring we don't get a zero range
|
|
2625
|
+
if img_min == img_max:
|
|
2626
|
+
vmin = img_min
|
|
2627
|
+
vmax = img_min + 1
|
|
2628
|
+
else:
|
|
2629
|
+
vmin = img_min + (img_max - img_min) * self.channel_brightness[channel]['min']
|
|
2630
|
+
vmax = img_min + (img_max - img_min) * self.channel_brightness[channel]['max']
|
|
2631
|
+
|
|
2632
|
+
# Normalize the image safely
|
|
2633
|
+
if vmin == vmax:
|
|
2634
|
+
normalized_image = np.zeros_like(current_image)
|
|
2635
|
+
else:
|
|
2636
|
+
normalized_image = np.clip((current_image - vmin) / (vmax - vmin), 0, 1)
|
|
2637
|
+
|
|
2638
|
+
# Create custom colormap with higher intensity
|
|
2639
|
+
color = base_colors[channel]
|
|
2640
|
+
custom_cmap = LinearSegmentedColormap.from_list(
|
|
2641
|
+
f'custom_{channel}',
|
|
2642
|
+
[(0,0,0,0), (*color,1)]
|
|
2643
|
+
)
|
|
2644
|
+
|
|
2645
|
+
# Display the image with slightly higher alpha
|
|
2646
|
+
self.ax.imshow(normalized_image,
|
|
2647
|
+
alpha=0.7,
|
|
2648
|
+
cmap=custom_cmap,
|
|
2649
|
+
vmin=0,
|
|
2650
|
+
vmax=1)
|
|
2651
|
+
|
|
2652
|
+
# Rest of the code remains the same...
|
|
2653
|
+
# Add highlight overlay if it exists
|
|
2654
|
+
if self.highlight_overlay is not None:
|
|
2655
|
+
highlight_slice = self.highlight_overlay[self.current_slice]
|
|
2656
|
+
highlight_cmap = LinearSegmentedColormap.from_list(
|
|
2657
|
+
'highlight',
|
|
2658
|
+
[(0, 0, 0, 0), (1, 1, 0, 1)] # yellow
|
|
2208
2659
|
)
|
|
2660
|
+
self.ax.imshow(highlight_slice,
|
|
2661
|
+
cmap=highlight_cmap,
|
|
2662
|
+
alpha=0.5)
|
|
2663
|
+
|
|
2664
|
+
# Restore zoom limits if they existed
|
|
2665
|
+
if current_xlim is not None and current_ylim is not None:
|
|
2666
|
+
self.ax.set_xlim(current_xlim)
|
|
2667
|
+
self.ax.set_ylim(current_ylim)
|
|
2668
|
+
|
|
2669
|
+
# Style the axes
|
|
2670
|
+
self.ax.set_xlabel('X')
|
|
2671
|
+
self.ax.set_ylabel('Y')
|
|
2672
|
+
self.ax.set_title(f'Slice {self.current_slice}')
|
|
2673
|
+
|
|
2674
|
+
# Make axis labels and ticks black for visibility against white background
|
|
2675
|
+
self.ax.xaxis.label.set_color('black')
|
|
2676
|
+
self.ax.yaxis.label.set_color('black')
|
|
2677
|
+
self.ax.title.set_color('black')
|
|
2678
|
+
self.ax.tick_params(colors='black')
|
|
2679
|
+
for spine in self.ax.spines.values():
|
|
2680
|
+
spine.set_color('black')
|
|
2681
|
+
|
|
2682
|
+
# Adjust the layout to ensure the plot fits well in the figure
|
|
2683
|
+
self.figure.tight_layout()
|
|
2684
|
+
|
|
2685
|
+
# Redraw measurement points and their labels
|
|
2686
|
+
for point in self.measurement_points:
|
|
2687
|
+
x1, y1, z1 = point['point1']
|
|
2688
|
+
x2, y2, z2 = point['point2']
|
|
2689
|
+
pair_idx = point['pair_index']
|
|
2209
2690
|
|
|
2210
|
-
#
|
|
2211
|
-
self.
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
)
|
|
2224
|
-
self.ax.imshow(highlight_slice,
|
|
2225
|
-
cmap=highlight_cmap,
|
|
2226
|
-
alpha=0.5)
|
|
2227
|
-
|
|
2228
|
-
# Restore zoom limits if they existed
|
|
2229
|
-
if current_xlim is not None and current_ylim is not None:
|
|
2230
|
-
self.ax.set_xlim(current_xlim)
|
|
2231
|
-
self.ax.set_ylim(current_ylim)
|
|
2232
|
-
|
|
2233
|
-
# Style the axes
|
|
2234
|
-
self.ax.set_xlabel('X')
|
|
2235
|
-
self.ax.set_ylabel('Y')
|
|
2236
|
-
self.ax.set_title(f'Slice {self.current_slice}')
|
|
2237
|
-
|
|
2238
|
-
# Make axis labels and ticks black for visibility against white background
|
|
2239
|
-
self.ax.xaxis.label.set_color('black')
|
|
2240
|
-
self.ax.yaxis.label.set_color('black')
|
|
2241
|
-
self.ax.title.set_color('black')
|
|
2242
|
-
self.ax.tick_params(colors='black')
|
|
2243
|
-
for spine in self.ax.spines.values():
|
|
2244
|
-
spine.set_color('black')
|
|
2245
|
-
|
|
2246
|
-
# Adjust the layout to ensure the plot fits well in the figure
|
|
2247
|
-
self.figure.tight_layout()
|
|
2248
|
-
|
|
2249
|
-
# Redraw measurement points and their labels
|
|
2250
|
-
for point in self.measurement_points:
|
|
2251
|
-
x1, y1, z1 = point['point1']
|
|
2252
|
-
x2, y2, z2 = point['point2']
|
|
2253
|
-
pair_idx = point['pair_index']
|
|
2254
|
-
|
|
2255
|
-
# Draw points and labels if they're on current slice
|
|
2256
|
-
if z1 == self.current_slice:
|
|
2257
|
-
self.ax.plot(x1, y1, 'yo', markersize=8)
|
|
2258
|
-
self.ax.text(x1, y1+5, str(pair_idx),
|
|
2259
|
-
color='white', ha='center', va='bottom')
|
|
2260
|
-
if z2 == self.current_slice:
|
|
2261
|
-
self.ax.plot(x2, y2, 'yo', markersize=8)
|
|
2262
|
-
self.ax.text(x2, y2+5, str(pair_idx),
|
|
2263
|
-
color='white', ha='center', va='bottom')
|
|
2264
|
-
|
|
2265
|
-
# Draw line if both points are on current slice
|
|
2266
|
-
if z1 == z2 == self.current_slice:
|
|
2267
|
-
self.ax.plot([x1, x2], [y1, y2], 'r--', alpha=0.5)
|
|
2268
|
-
|
|
2691
|
+
# Draw points and labels if they're on current slice
|
|
2692
|
+
if z1 == self.current_slice:
|
|
2693
|
+
self.ax.plot(x1, y1, 'yo', markersize=8)
|
|
2694
|
+
self.ax.text(x1, y1+5, str(pair_idx),
|
|
2695
|
+
color='white', ha='center', va='bottom')
|
|
2696
|
+
if z2 == self.current_slice:
|
|
2697
|
+
self.ax.plot(x2, y2, 'yo', markersize=8)
|
|
2698
|
+
self.ax.text(x2, y2+5, str(pair_idx),
|
|
2699
|
+
color='white', ha='center', va='bottom')
|
|
2700
|
+
|
|
2701
|
+
# Draw line if both points are on current slice
|
|
2702
|
+
if z1 == z2 == self.current_slice:
|
|
2703
|
+
self.ax.plot([x1, x2], [y1, y2], 'r--', alpha=0.5)
|
|
2269
2704
|
|
|
2270
|
-
|
|
2705
|
+
self.canvas.draw()
|
|
2271
2706
|
|
|
2272
2707
|
def show_netshow_dialog(self):
|
|
2273
2708
|
dialog = NetShowDialog(self)
|
|
@@ -2277,6 +2712,23 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2277
2712
|
dialog = PartitionDialog(self)
|
|
2278
2713
|
dialog.exec()
|
|
2279
2714
|
|
|
2715
|
+
def show_radial_dialog(self):
|
|
2716
|
+
dialog = RadialDialog(self)
|
|
2717
|
+
dialog.exec()
|
|
2718
|
+
|
|
2719
|
+
def show_degree_dist_dialog(self):
|
|
2720
|
+
dialog = DegreeDistDialog(self)
|
|
2721
|
+
dialog.exec()
|
|
2722
|
+
|
|
2723
|
+
def show_neighbor_id_dialog(self):
|
|
2724
|
+
dialog = NeighborIdentityDialog(self)
|
|
2725
|
+
dialog.exec()
|
|
2726
|
+
|
|
2727
|
+
def show_random_dialog(self):
|
|
2728
|
+
dialog = RandomDialog(self)
|
|
2729
|
+
dialog.exec()
|
|
2730
|
+
|
|
2731
|
+
|
|
2280
2732
|
def show_interaction_dialog(self):
|
|
2281
2733
|
dialog = InteractionDialog(self)
|
|
2282
2734
|
dialog.exec()
|
|
@@ -2285,10 +2737,19 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2285
2737
|
dialog = DegreeDialog(self)
|
|
2286
2738
|
dialog.exec()
|
|
2287
2739
|
|
|
2740
|
+
|
|
2741
|
+
def show_hub_dialog(self):
|
|
2742
|
+
dialog = HubDialog(self)
|
|
2743
|
+
dialog.exec()
|
|
2744
|
+
|
|
2288
2745
|
def show_mother_dialog(self):
|
|
2289
2746
|
dialog = MotherDialog(self)
|
|
2290
2747
|
dialog.exec()
|
|
2291
2748
|
|
|
2749
|
+
def show_code_dialog(self):
|
|
2750
|
+
dialog = CodeDialog(self)
|
|
2751
|
+
dialog.exec()
|
|
2752
|
+
|
|
2292
2753
|
|
|
2293
2754
|
|
|
2294
2755
|
#TABLE RELATED:
|
|
@@ -3509,6 +3970,9 @@ class NetShowDialog(QDialog):
|
|
|
3509
3970
|
def show_network(self):
|
|
3510
3971
|
# Get parameters and run analysis
|
|
3511
3972
|
geo = self.geo_layout.isChecked()
|
|
3973
|
+
if geo:
|
|
3974
|
+
if my_network.node_centroids is None:
|
|
3975
|
+
self.parent().show_centroid_dialog()
|
|
3512
3976
|
accepted_mode = self.mode_selector.currentIndex() # Convert to 1-based index
|
|
3513
3977
|
# Get directory (None if empty)
|
|
3514
3978
|
directory = self.directory.text() if self.directory.text() else None
|
|
@@ -3533,6 +3997,8 @@ class NetShowDialog(QDialog):
|
|
|
3533
3997
|
self.accept()
|
|
3534
3998
|
except Exception as e:
|
|
3535
3999
|
print(f"Error showing network: {e}")
|
|
4000
|
+
import traceback
|
|
4001
|
+
print(traceback.format_exc())
|
|
3536
4002
|
|
|
3537
4003
|
class PartitionDialog(QDialog):
|
|
3538
4004
|
def __init__(self, parent=None):
|
|
@@ -3555,6 +4021,12 @@ class PartitionDialog(QDialog):
|
|
|
3555
4021
|
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
3556
4022
|
layout.addRow("Execution Mode:", self.mode_selector)
|
|
3557
4023
|
|
|
4024
|
+
# stats checkbox (default True)
|
|
4025
|
+
self.stats = QPushButton("Stats")
|
|
4026
|
+
self.stats.setCheckable(True)
|
|
4027
|
+
self.stats.setChecked(True)
|
|
4028
|
+
layout.addRow("Community Stats:", self.stats)
|
|
4029
|
+
|
|
3558
4030
|
# Add Run button
|
|
3559
4031
|
run_button = QPushButton("Partition")
|
|
3560
4032
|
run_button.clicked.connect(self.partition)
|
|
@@ -3564,18 +4036,189 @@ class PartitionDialog(QDialog):
|
|
|
3564
4036
|
|
|
3565
4037
|
accepted_mode = self.mode_selector.currentIndex()
|
|
3566
4038
|
weighted = self.weighted.isChecked()
|
|
4039
|
+
dostats = self.stats.isChecked()
|
|
4040
|
+
|
|
4041
|
+
my_network.communities = None
|
|
3567
4042
|
|
|
3568
4043
|
try:
|
|
3569
|
-
my_network.community_partition(weighted = weighted, style = accepted_mode)
|
|
4044
|
+
stats = my_network.community_partition(weighted = weighted, style = accepted_mode, dostats = dostats)
|
|
3570
4045
|
print(f"Discovered communities: {my_network.communities}")
|
|
3571
4046
|
|
|
3572
|
-
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID')
|
|
4047
|
+
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID', title = 'Community Partition')
|
|
4048
|
+
|
|
4049
|
+
if len(stats.keys()) > 0:
|
|
4050
|
+
self.parent().format_for_upperright_table(stats, title = 'Community Stats')
|
|
3573
4051
|
|
|
3574
4052
|
self.accept()
|
|
3575
4053
|
|
|
3576
4054
|
except Exception as e:
|
|
3577
4055
|
print(f"Error creating communities: {e}")
|
|
3578
4056
|
|
|
4057
|
+
class RadialDialog(QDialog):
|
|
4058
|
+
|
|
4059
|
+
def __init__(self, parent=None):
|
|
4060
|
+
|
|
4061
|
+
super().__init__(parent)
|
|
4062
|
+
self.setWindowTitle("Radial Parameters")
|
|
4063
|
+
self.setModal(True)
|
|
4064
|
+
|
|
4065
|
+
layout = QFormLayout(self)
|
|
4066
|
+
|
|
4067
|
+
self.distance = QLineEdit("50")
|
|
4068
|
+
layout.addRow("Bucket Distance for Searching For Node Neighbors (automatically scaled by xy and z scales):", self.distance)
|
|
4069
|
+
|
|
4070
|
+
self.directory = QLineEdit("")
|
|
4071
|
+
layout.addRow("Output Directory:", self.directory)
|
|
4072
|
+
|
|
4073
|
+
# Add Run button
|
|
4074
|
+
run_button = QPushButton("Get Radial Distribution")
|
|
4075
|
+
run_button.clicked.connect(self.radial)
|
|
4076
|
+
layout.addWidget(run_button)
|
|
4077
|
+
|
|
4078
|
+
def radial(self):
|
|
4079
|
+
|
|
4080
|
+
distance = float(self.distance.text()) if self.distance.text().strip() else 50
|
|
4081
|
+
|
|
4082
|
+
directory = str(self.distance.text()) if self.directory.text().strip() else None
|
|
4083
|
+
|
|
4084
|
+
if my_network.node_centroids is None:
|
|
4085
|
+
self.parent().show_centroid_dialog()
|
|
4086
|
+
|
|
4087
|
+
radial = my_network.radial_distribution(distance, directory = directory)
|
|
4088
|
+
|
|
4089
|
+
self.parent().format_for_upperright_table(radial, 'Radial Distance From Any Node', 'Average Number of Neighboring Nodes', title = 'Radial Distribution Analysis')
|
|
4090
|
+
|
|
4091
|
+
self.accept()
|
|
4092
|
+
|
|
4093
|
+
class DegreeDistDialog(QDialog):
|
|
4094
|
+
|
|
4095
|
+
def __init__(self, parent=None):
|
|
4096
|
+
|
|
4097
|
+
super().__init__(parent)
|
|
4098
|
+
self.setWindowTitle("Degree Distribution Parameters")
|
|
4099
|
+
self.setModal(True)
|
|
4100
|
+
|
|
4101
|
+
layout = QFormLayout(self)
|
|
4102
|
+
|
|
4103
|
+
self.directory = QLineEdit("")
|
|
4104
|
+
layout.addRow("Output Directory:", self.directory)
|
|
4105
|
+
|
|
4106
|
+
# Add Run button
|
|
4107
|
+
run_button = QPushButton("Get Degree Distribution")
|
|
4108
|
+
run_button.clicked.connect(self.degreedist)
|
|
4109
|
+
layout.addWidget(run_button)
|
|
4110
|
+
|
|
4111
|
+
def degreedist(self):
|
|
4112
|
+
|
|
4113
|
+
try:
|
|
4114
|
+
|
|
4115
|
+
directory = str(self.distance.text()) if self.directory.text().strip() else None
|
|
4116
|
+
|
|
4117
|
+
degrees = my_network.degree_distribution(directory = directory)
|
|
4118
|
+
|
|
4119
|
+
|
|
4120
|
+
self.parent().format_for_upperright_table(degrees, 'Degree (k)', 'Proportion of nodes with degree (p(k))', title = 'Degree Distribution Analysis')
|
|
4121
|
+
|
|
4122
|
+
self.accept()
|
|
4123
|
+
|
|
4124
|
+
except Excpetion as e:
|
|
4125
|
+
print(f"An error occurred: {e}")
|
|
4126
|
+
|
|
4127
|
+
class NeighborIdentityDialog(QDialog):
|
|
4128
|
+
|
|
4129
|
+
def __init__(self, parent=None):
|
|
4130
|
+
|
|
4131
|
+
super().__init__(parent)
|
|
4132
|
+
self.setWindowTitle(f"Neighborhood Identity Distribution Parameters \n(Note - the same node is not included more than once as a neighbor even if it borders multiple nodes of the root ID)")
|
|
4133
|
+
self.setModal(True)
|
|
4134
|
+
|
|
4135
|
+
layout = QFormLayout(self)
|
|
4136
|
+
|
|
4137
|
+
self.root = QComboBox()
|
|
4138
|
+
self.root.addItems(list(set(my_network.node_identities.values())))
|
|
4139
|
+
self.root.setCurrentIndex(0)
|
|
4140
|
+
layout.addRow("Root Identity to Search for Neighbor's IDs (search uses nodes of this ID, finds what IDs they connect to", self.root)
|
|
4141
|
+
|
|
4142
|
+
self.directory = QLineEdit("")
|
|
4143
|
+
layout.addRow("Output Directory:", self.directory)
|
|
4144
|
+
|
|
4145
|
+
self.mode = QComboBox()
|
|
4146
|
+
self.mode.addItems(["From Network - Based on Absolute Connectivity", "Use Labeled Nodes - Based on Neighborhood Densities"])
|
|
4147
|
+
self.mode.setCurrentIndex(0)
|
|
4148
|
+
layout.addRow("Mode", self.mode)
|
|
4149
|
+
|
|
4150
|
+
self.search = QLineEdit("")
|
|
4151
|
+
layout.addRow("Search Radius (Ignore if using network):", self.search)
|
|
4152
|
+
|
|
4153
|
+
# Add Run button
|
|
4154
|
+
run_button = QPushButton("Get Neighborhood Identity Distribution")
|
|
4155
|
+
run_button.clicked.connect(self.neighborids)
|
|
4156
|
+
layout.addWidget(run_button)
|
|
4157
|
+
|
|
4158
|
+
def neighborids(self):
|
|
4159
|
+
|
|
4160
|
+
root = self.root.currentText()
|
|
4161
|
+
|
|
4162
|
+
directory = self.directory.text() if self.directory.text().strip() else None
|
|
4163
|
+
|
|
4164
|
+
mode = self.mode.currentIndex()
|
|
4165
|
+
|
|
4166
|
+
search = float(self.search.text()) if self.search.text().strip() else 0
|
|
4167
|
+
|
|
4168
|
+
|
|
4169
|
+
result, result2, title1, title2 = my_network.neighborhood_identities(root = root, directory = directory, mode = mode, search = search)
|
|
4170
|
+
|
|
4171
|
+
self.parent().format_for_upperright_table(result, 'Node Identity', 'Amount', title = title1)
|
|
4172
|
+
self.parent().format_for_upperright_table(result2, 'Node Identity', 'Proportion', title = title2)
|
|
4173
|
+
|
|
4174
|
+
self.accept()
|
|
4175
|
+
|
|
4176
|
+
|
|
4177
|
+
|
|
4178
|
+
|
|
4179
|
+
|
|
4180
|
+
|
|
4181
|
+
|
|
4182
|
+
|
|
4183
|
+
class RandomDialog(QDialog):
|
|
4184
|
+
|
|
4185
|
+
def __init__(self, parent=None):
|
|
4186
|
+
|
|
4187
|
+
super().__init__(parent)
|
|
4188
|
+
self.setWindowTitle("Degree Distribution Parameters")
|
|
4189
|
+
self.setModal(True)
|
|
4190
|
+
|
|
4191
|
+
layout = QFormLayout(self)
|
|
4192
|
+
|
|
4193
|
+
|
|
4194
|
+
# stats checkbox (default True)
|
|
4195
|
+
self.weighted = QPushButton("weighted")
|
|
4196
|
+
self.weighted.setCheckable(True)
|
|
4197
|
+
self.weighted.setChecked(True)
|
|
4198
|
+
layout.addRow("Allow Random Network to be weighted? (Whether or not edges can be repeatedly assigned between the same set of nodes to increase their weights, or if they must always find a new partner):", self.weighted)
|
|
4199
|
+
|
|
4200
|
+
|
|
4201
|
+
# Add Run button
|
|
4202
|
+
run_button = QPushButton("Get Random Network (Will go in Selection Table)")
|
|
4203
|
+
run_button.clicked.connect(self.random)
|
|
4204
|
+
layout.addWidget(run_button)
|
|
4205
|
+
|
|
4206
|
+
def random(self):
|
|
4207
|
+
|
|
4208
|
+
weighted = self.weighted.isChecked()
|
|
4209
|
+
|
|
4210
|
+
_, df = my_network.assign_random(weighted = weighted)
|
|
4211
|
+
|
|
4212
|
+
# Create new model with filtered DataFrame and update selection table
|
|
4213
|
+
new_model = PandasModel(df)
|
|
4214
|
+
self.parent().selection_table.setModel(new_model)
|
|
4215
|
+
|
|
4216
|
+
# Switch to selection table
|
|
4217
|
+
self.parent().selection_button.click()
|
|
4218
|
+
|
|
4219
|
+
self.accept()
|
|
4220
|
+
|
|
4221
|
+
|
|
3579
4222
|
|
|
3580
4223
|
class InteractionDialog(QDialog):
|
|
3581
4224
|
|
|
@@ -3645,6 +4288,9 @@ class DegreeDialog(QDialog):
|
|
|
3645
4288
|
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
3646
4289
|
layout.addRow("Execution Mode:", self.mode_selector)
|
|
3647
4290
|
|
|
4291
|
+
self.mask_limiter = QLineEdit("1")
|
|
4292
|
+
layout.addRow("Masks smaller high degree proportion of nodes (ignore if only returning degrees)", self.mask_limiter)
|
|
4293
|
+
|
|
3648
4294
|
self.down_factor = QLineEdit("1")
|
|
3649
4295
|
layout.addRow("down_factor (for speeding up overlay generation - ignore if only returning degrees:", self.down_factor)
|
|
3650
4296
|
|
|
@@ -3664,6 +4310,11 @@ class DegreeDialog(QDialog):
|
|
|
3664
4310
|
except ValueError:
|
|
3665
4311
|
down_factor = 1
|
|
3666
4312
|
|
|
4313
|
+
try:
|
|
4314
|
+
mask_limiter = float(self.mask_limiter.text()) if self.mask_limiter.text() else 1
|
|
4315
|
+
except ValueError:
|
|
4316
|
+
mask_limiter = 1
|
|
4317
|
+
|
|
3667
4318
|
if self.parent().active_channel == 1:
|
|
3668
4319
|
active_data = self.parent().channel_data[0]
|
|
3669
4320
|
else:
|
|
@@ -3680,9 +4331,47 @@ class DegreeDialog(QDialog):
|
|
|
3680
4331
|
|
|
3681
4332
|
original_shape = copy.deepcopy(active_data.shape)
|
|
3682
4333
|
|
|
3683
|
-
temp_network = n3d.Network_3D(nodes = active_data, node_centroids = my_network.node_centroids, network = my_network.network, network_lists = my_network.network_lists)
|
|
3684
4334
|
|
|
3685
|
-
|
|
4335
|
+
if mask_limiter < 1 and accepted_mode != 0:
|
|
4336
|
+
|
|
4337
|
+
if len(np.unique(active_data)) < 3:
|
|
4338
|
+
active_data, _ = n3d.label_objects(active_data)
|
|
4339
|
+
|
|
4340
|
+
node_list = list(my_network.network.nodes)
|
|
4341
|
+
node_dict = {}
|
|
4342
|
+
|
|
4343
|
+
for node in node_list:
|
|
4344
|
+
node_dict[node] = (my_network.network.degree(node))
|
|
4345
|
+
|
|
4346
|
+
# Calculate the number of top proportion% entries
|
|
4347
|
+
num_items = len(node_dict)
|
|
4348
|
+
num_top_10_percent = max(1, int(num_items * mask_limiter)) # Ensure at least one item
|
|
4349
|
+
|
|
4350
|
+
# Sort the dictionary by values in descending order and get the top 10%
|
|
4351
|
+
sorted_items = sorted(node_dict.items(), key=lambda item: item[1], reverse=True)
|
|
4352
|
+
top_10_percent_items = sorted_items[:num_top_10_percent]
|
|
4353
|
+
|
|
4354
|
+
# Extract the keys from the top proportion% items
|
|
4355
|
+
top_10_percent_keys = [key for key, value in top_10_percent_items]
|
|
4356
|
+
|
|
4357
|
+
mask = np.isin(active_data, top_10_percent_keys)
|
|
4358
|
+
nodes = mask * active_data
|
|
4359
|
+
new_centroids = {}
|
|
4360
|
+
for node in my_network.node_centroids:
|
|
4361
|
+
if node in top_10_percent_keys:
|
|
4362
|
+
new_centroids[node] = my_network.node_centroids[node]
|
|
4363
|
+
del mask
|
|
4364
|
+
|
|
4365
|
+
temp_network = n3d.Network_3D(nodes = nodes, node_centroids = new_centroids, network = my_network.network, network_lists = my_network.network_lists)
|
|
4366
|
+
|
|
4367
|
+
result, nodes = temp_network.get_degrees(called = True, no_img = accepted_mode, down_factor = down_factor)
|
|
4368
|
+
|
|
4369
|
+
else:
|
|
4370
|
+
temp_network = n3d.Network_3D(nodes = active_data, node_centroids = my_network.node_centroids, network = my_network.network, network_lists = my_network.network_lists)
|
|
4371
|
+
|
|
4372
|
+
result, nodes = temp_network.get_degrees(called = True, no_img = accepted_mode, down_factor = down_factor)
|
|
4373
|
+
|
|
4374
|
+
|
|
3686
4375
|
|
|
3687
4376
|
self.parent().format_for_upperright_table(result, 'Node ID', 'Degree', title = 'Degrees of nodes')
|
|
3688
4377
|
|
|
@@ -3698,9 +4387,75 @@ class DegreeDialog(QDialog):
|
|
|
3698
4387
|
|
|
3699
4388
|
except Exception as e:
|
|
3700
4389
|
|
|
4390
|
+
import traceback
|
|
4391
|
+
print(traceback.format_exc())
|
|
4392
|
+
|
|
3701
4393
|
print(f"Error finding degrees: {e}")
|
|
3702
4394
|
|
|
3703
4395
|
|
|
4396
|
+
class HubDialog(QDialog):
|
|
4397
|
+
|
|
4398
|
+
def __init__(self, parent=None):
|
|
4399
|
+
|
|
4400
|
+
super().__init__(parent)
|
|
4401
|
+
self.setWindowTitle("Hub Parameters")
|
|
4402
|
+
self.setModal(True)
|
|
4403
|
+
|
|
4404
|
+
layout = QFormLayout(self)
|
|
4405
|
+
|
|
4406
|
+
layout.addRow("Note:", QLabel(f"Finds hubs, which are nodes in the network that have the shortest number of steps to the other nodes\nWe can draw optional overlays to Overlay 2 as described below:"))
|
|
4407
|
+
|
|
4408
|
+
# Overlay checkbox (default True)
|
|
4409
|
+
self.overlay = QPushButton("Overlay")
|
|
4410
|
+
self.overlay.setCheckable(True)
|
|
4411
|
+
self.overlay.setChecked(True)
|
|
4412
|
+
layout.addRow("Make Overlay?:", self.overlay)
|
|
4413
|
+
|
|
4414
|
+
|
|
4415
|
+
self.proportion = QLineEdit("0.15")
|
|
4416
|
+
layout.addRow("Proportion of most connected hubs to keep (1 would imply returning entire network)", self.proportion)
|
|
4417
|
+
|
|
4418
|
+
|
|
4419
|
+
# Add Run button
|
|
4420
|
+
run_button = QPushButton("Get hubs")
|
|
4421
|
+
run_button.clicked.connect(self.hubs)
|
|
4422
|
+
layout.addWidget(run_button)
|
|
4423
|
+
|
|
4424
|
+
def hubs(self):
|
|
4425
|
+
|
|
4426
|
+
try:
|
|
4427
|
+
|
|
4428
|
+
try:
|
|
4429
|
+
proportion = float(self.proportion.text()) if self.proportion.text() else 1
|
|
4430
|
+
except ValueError:
|
|
4431
|
+
proportion = 1
|
|
4432
|
+
|
|
4433
|
+
overlay = self.overlay.isChecked()
|
|
4434
|
+
|
|
4435
|
+
result, img = my_network.isolate_hubs(proportion = proportion, retimg = overlay)
|
|
4436
|
+
|
|
4437
|
+
hub_dict = {}
|
|
4438
|
+
|
|
4439
|
+
for node in result:
|
|
4440
|
+
hub_dict[node] = my_network.network.degree(node)
|
|
4441
|
+
|
|
4442
|
+
self.parent().format_for_upperright_table(hub_dict, 'NodeID', 'Degree', title = f'Upper {proportion} Hub Nodes')
|
|
4443
|
+
|
|
4444
|
+
if img is not None:
|
|
4445
|
+
|
|
4446
|
+
self.parent().load_channel(3, channel_data = img, data = True)
|
|
4447
|
+
|
|
4448
|
+
|
|
4449
|
+
self.accept()
|
|
4450
|
+
|
|
4451
|
+
except Exception as e:
|
|
4452
|
+
|
|
4453
|
+
import traceback
|
|
4454
|
+
print(traceback.format_exc())
|
|
4455
|
+
|
|
4456
|
+
print(f"Error finding hubs: {e}")
|
|
4457
|
+
|
|
4458
|
+
|
|
3704
4459
|
|
|
3705
4460
|
class MotherDialog(QDialog):
|
|
3706
4461
|
|
|
@@ -3749,7 +4504,12 @@ class MotherDialog(QDialog):
|
|
|
3749
4504
|
G, result = my_network.isolate_mothers(self, louvain = my_network.communities, ret_nodes = False, called = True)
|
|
3750
4505
|
self.parent().load_channel(2, channel_data = result, data = True)
|
|
3751
4506
|
|
|
3752
|
-
|
|
4507
|
+
degree_dict = {}
|
|
4508
|
+
|
|
4509
|
+
for node in G.nodes():
|
|
4510
|
+
degree_dict[node] = my_network.network.degree(node)
|
|
4511
|
+
|
|
4512
|
+
self.parent().format_for_upperright_table(degree_dict, 'Mother ID', 'Degree', title = 'Mother Nodes')
|
|
3753
4513
|
|
|
3754
4514
|
|
|
3755
4515
|
self.accept()
|
|
@@ -3759,6 +4519,58 @@ class MotherDialog(QDialog):
|
|
|
3759
4519
|
print(f"Error finding mothers: {e}")
|
|
3760
4520
|
|
|
3761
4521
|
|
|
4522
|
+
class CodeDialog(QDialog):
|
|
4523
|
+
|
|
4524
|
+
def __init__(self, parent=None):
|
|
4525
|
+
|
|
4526
|
+
super().__init__(parent)
|
|
4527
|
+
self.setWindowTitle("Community Code Parameters (Will go to Overlay2)")
|
|
4528
|
+
self.setModal(True)
|
|
4529
|
+
|
|
4530
|
+
layout = QFormLayout(self)
|
|
4531
|
+
|
|
4532
|
+
self.down_factor = QLineEdit("")
|
|
4533
|
+
layout.addRow("down_factor (for speeding up overlay generation - optional):", self.down_factor)
|
|
4534
|
+
|
|
4535
|
+
# Add mode selection dropdown
|
|
4536
|
+
self.mode_selector = QComboBox()
|
|
4537
|
+
self.mode_selector.addItems(["Color Coded", "Grayscale Coded"])
|
|
4538
|
+
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
4539
|
+
layout.addRow("Execution Mode:", self.mode_selector)
|
|
4540
|
+
|
|
4541
|
+
|
|
4542
|
+
# Add Run button
|
|
4543
|
+
run_button = QPushButton("Community Code")
|
|
4544
|
+
run_button.clicked.connect(self.code)
|
|
4545
|
+
layout.addWidget(run_button)
|
|
4546
|
+
|
|
4547
|
+
def code(self):
|
|
4548
|
+
|
|
4549
|
+
try:
|
|
4550
|
+
|
|
4551
|
+
mode = self.mode_selector.currentIndex()
|
|
4552
|
+
|
|
4553
|
+
down_factor = float(self.down_factor.text()) if self.down_factor.text().strip() else None
|
|
4554
|
+
|
|
4555
|
+
|
|
4556
|
+
|
|
4557
|
+
if my_network.communities is None:
|
|
4558
|
+
self.parent().show_partition_dialog()
|
|
4559
|
+
if my_network.communities is None:
|
|
4560
|
+
return
|
|
4561
|
+
|
|
4562
|
+
if mode == 0:
|
|
4563
|
+
image = my_network.extract_communities(down_factor = down_factor)
|
|
4564
|
+
elif mode == 1:
|
|
4565
|
+
image = my_network.extract_communities(color_code = False, down_factor = down_factor)
|
|
4566
|
+
|
|
4567
|
+
|
|
4568
|
+
self.parent().load_channel(3, image, True)
|
|
4569
|
+
self.accept()
|
|
4570
|
+
|
|
4571
|
+
except Exception as e:
|
|
4572
|
+
print(f"An error has occurred: {e}")
|
|
4573
|
+
|
|
3762
4574
|
|
|
3763
4575
|
|
|
3764
4576
|
|
|
@@ -3790,7 +4602,11 @@ class ResizeDialog(QDialog):
|
|
|
3790
4602
|
self.cubic.setChecked(False)
|
|
3791
4603
|
layout.addRow("Use cubic algorithm:", self.cubic)
|
|
3792
4604
|
|
|
3793
|
-
|
|
4605
|
+
if self.parent().original_shape is not None:
|
|
4606
|
+
undo_button = QPushButton(f"Resample to original shape: {self.parent().original_shape}")
|
|
4607
|
+
undo_button.clicked.connect(lambda: self.run_resize(undo = True))
|
|
4608
|
+
layout.addRow(undo_button)
|
|
4609
|
+
|
|
3794
4610
|
run_button = QPushButton("Run Resize")
|
|
3795
4611
|
run_button.clicked.connect(self.run_resize)
|
|
3796
4612
|
layout.addRow(run_button)
|
|
@@ -3800,9 +4616,9 @@ class ResizeDialog(QDialog):
|
|
|
3800
4616
|
self.resize.clear()
|
|
3801
4617
|
self.zsize.setText("1")
|
|
3802
4618
|
self.xsize.setText("1")
|
|
3803
|
-
self.ysize.setText("1")
|
|
4619
|
+
self.ysize.setText("1")
|
|
3804
4620
|
|
|
3805
|
-
def run_resize(self):
|
|
4621
|
+
def run_resize(self, undo = False):
|
|
3806
4622
|
try:
|
|
3807
4623
|
# Get parameters
|
|
3808
4624
|
try:
|
|
@@ -3851,16 +4667,31 @@ class ResizeDialog(QDialog):
|
|
|
3851
4667
|
self.parent().slice_slider.setValue(0)
|
|
3852
4668
|
self.parent().current_slice = 0
|
|
3853
4669
|
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
4670
|
+
if not undo:
|
|
4671
|
+
# Process each channel
|
|
4672
|
+
for channel in range(4):
|
|
4673
|
+
if self.parent().channel_data[channel] is not None:
|
|
4674
|
+
resized_data = n3d.resize(self.parent().channel_data[channel], resize, order)
|
|
4675
|
+
self.parent().load_channel(channel, channel_data=resized_data, data=True, assign_shape = False)
|
|
4676
|
+
|
|
4677
|
+
|
|
4678
|
+
# Process highlight overlay if it exists
|
|
4679
|
+
if self.parent().highlight_overlay is not None:
|
|
4680
|
+
self.parent().highlight_overlay = n3d.resize(self.parent().highlight_overlay, resize, order)
|
|
4681
|
+
else:
|
|
4682
|
+
# Process each channel
|
|
4683
|
+
if array_shape == self.parent().original_shape:
|
|
4684
|
+
return
|
|
4685
|
+
for channel in range(4):
|
|
4686
|
+
if self.parent().channel_data[channel] is not None:
|
|
4687
|
+
resized_data = n3d.upsample_with_padding(self.parent().channel_data[channel], original_shape = self.parent().original_shape)
|
|
4688
|
+
self.parent().load_channel(channel, channel_data=resized_data, data=True, assign_shape = False)
|
|
4689
|
+
|
|
4690
|
+
|
|
4691
|
+
# Process highlight overlay if it exists
|
|
4692
|
+
if self.parent().highlight_overlay is not None:
|
|
4693
|
+
self.parent().highlight_overlay = n3d.upsample_with_padding(self.parent().highlight_overlay, original_shape = self.parent().original_shape)
|
|
3859
4694
|
|
|
3860
|
-
|
|
3861
|
-
# Process highlight overlay if it exists
|
|
3862
|
-
if self.parent().highlight_overlay is not None:
|
|
3863
|
-
self.parent().highlight_overlay = n3d.resize(self.parent().highlight_overlay, resize, order)
|
|
3864
4695
|
|
|
3865
4696
|
# Update slider range based on new z-dimension
|
|
3866
4697
|
for channel in self.parent().channel_data:
|
|
@@ -3984,78 +4815,317 @@ class BinarizeDialog(QDialog):
|
|
|
3984
4815
|
f"Error running binarize: {str(e)}"
|
|
3985
4816
|
)
|
|
3986
4817
|
|
|
3987
|
-
except Exception as e:
|
|
3988
|
-
QMessageBox.critical(
|
|
3989
|
-
self,
|
|
3990
|
-
"Error",
|
|
3991
|
-
f"Error running binarize: {str(e)}"
|
|
3992
|
-
)
|
|
4818
|
+
except Exception as e:
|
|
4819
|
+
QMessageBox.critical(
|
|
4820
|
+
self,
|
|
4821
|
+
"Error",
|
|
4822
|
+
f"Error running binarize: {str(e)}"
|
|
4823
|
+
)
|
|
4824
|
+
|
|
4825
|
+
class LabelDialog(QDialog):
|
|
4826
|
+
def __init__(self, parent=None):
|
|
4827
|
+
super().__init__(parent)
|
|
4828
|
+
self.setWindowTitle("Label Active Channel?")
|
|
4829
|
+
self.setModal(True)
|
|
4830
|
+
|
|
4831
|
+
layout = QFormLayout(self)
|
|
4832
|
+
|
|
4833
|
+
# Add Run button
|
|
4834
|
+
run_button = QPushButton("Run Label")
|
|
4835
|
+
run_button.clicked.connect(self.run_label)
|
|
4836
|
+
layout.addRow(run_button)
|
|
4837
|
+
|
|
4838
|
+
def run_label(self):
|
|
4839
|
+
|
|
4840
|
+
try:
|
|
4841
|
+
|
|
4842
|
+
# Get the active channel data from parent
|
|
4843
|
+
active_data = self.parent().channel_data[self.parent().active_channel]
|
|
4844
|
+
if active_data is None:
|
|
4845
|
+
raise ValueError("No active image selected")
|
|
4846
|
+
|
|
4847
|
+
try:
|
|
4848
|
+
# Call watershed method with parameters
|
|
4849
|
+
result, _ = n3d.label_objects(
|
|
4850
|
+
active_data
|
|
4851
|
+
)
|
|
4852
|
+
|
|
4853
|
+
# Update both the display data and the network object
|
|
4854
|
+
self.parent().channel_data[self.parent().active_channel] = result
|
|
4855
|
+
|
|
4856
|
+
|
|
4857
|
+
# Update the corresponding property in my_network
|
|
4858
|
+
setattr(my_network, network_properties[self.parent().active_channel], result)
|
|
4859
|
+
|
|
4860
|
+
self.parent().update_display()
|
|
4861
|
+
self.accept()
|
|
4862
|
+
|
|
4863
|
+
except Exception as e:
|
|
4864
|
+
QMessageBox.critical(
|
|
4865
|
+
self,
|
|
4866
|
+
"Error",
|
|
4867
|
+
f"Error running label: {str(e)}"
|
|
4868
|
+
)
|
|
4869
|
+
|
|
4870
|
+
except Exception as e:
|
|
4871
|
+
QMessageBox.critical(
|
|
4872
|
+
self,
|
|
4873
|
+
"Error",
|
|
4874
|
+
f"Error running label: {str(e)}"
|
|
4875
|
+
)
|
|
4876
|
+
|
|
4877
|
+
class ThresholdWindow(QMainWindow):
|
|
4878
|
+
def __init__(self, parent=None):
|
|
4879
|
+
super().__init__(parent)
|
|
4880
|
+
self.setWindowTitle("Threshold Params (Active Image)")
|
|
4881
|
+
|
|
4882
|
+
# Create central widget and layout
|
|
4883
|
+
central_widget = QWidget()
|
|
4884
|
+
self.setCentralWidget(central_widget)
|
|
4885
|
+
layout = QFormLayout(central_widget)
|
|
4886
|
+
|
|
4887
|
+
self.min = QLineEdit("")
|
|
4888
|
+
layout.addRow("Minimum Value to retain:", self.min)
|
|
4889
|
+
|
|
4890
|
+
# Create widgets
|
|
4891
|
+
self.max = QLineEdit("")
|
|
4892
|
+
layout.addRow("Maximum Value to retain:", self.max)
|
|
4893
|
+
|
|
4894
|
+
# Add mode selection dropdown
|
|
4895
|
+
self.mode_selector = QComboBox()
|
|
4896
|
+
self.mode_selector.addItems(["Using Volumes", "Using Label/Brightness"])
|
|
4897
|
+
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
4898
|
+
layout.addRow("Execution Mode:", self.mode_selector)
|
|
4899
|
+
|
|
4900
|
+
# Add Run button
|
|
4901
|
+
prev_button = QPushButton("Preview")
|
|
4902
|
+
prev_button.clicked.connect(self.run_preview)
|
|
4903
|
+
layout.addRow(prev_button)
|
|
4904
|
+
|
|
4905
|
+
# Add Run button
|
|
4906
|
+
run_button = QPushButton("Apply Threshold")
|
|
4907
|
+
run_button.clicked.connect(self.thresh)
|
|
4908
|
+
layout.addRow(run_button)
|
|
4909
|
+
|
|
4910
|
+
# Set a reasonable default size
|
|
4911
|
+
self.setMinimumWidth(300)
|
|
4912
|
+
|
|
4913
|
+
def run_preview(self):
|
|
4914
|
+
|
|
4915
|
+
def get_valid_float(text, default_value):
|
|
4916
|
+
try:
|
|
4917
|
+
return float(text) if text.strip() else default_value
|
|
4918
|
+
except ValueError:
|
|
4919
|
+
print(f"Invalid input: {text}")
|
|
4920
|
+
return default_value
|
|
4921
|
+
|
|
4922
|
+
try:
|
|
4923
|
+
channel = self.parent().active_channel
|
|
4924
|
+
accepted_mode = self.mode_selector.currentIndex()
|
|
4925
|
+
|
|
4926
|
+
if accepted_mode == 0:
|
|
4927
|
+
if len(np.unique(self.parent().channel_data[self.parent().active_channel])) < 3:
|
|
4928
|
+
self.parent().show_label_dialog()
|
|
4929
|
+
|
|
4930
|
+
if self.parent().volume_dict[channel] is None:
|
|
4931
|
+
self.parent().volumes()
|
|
4932
|
+
|
|
4933
|
+
volumes = self.parent().volume_dict[channel]
|
|
4934
|
+
default_max = max(volumes.values())
|
|
4935
|
+
default_min = min(volumes.values())
|
|
4936
|
+
|
|
4937
|
+
max_val = get_valid_float(self.max.text(), default_max)
|
|
4938
|
+
min_val = get_valid_float(self.min.text(), default_min)
|
|
4939
|
+
|
|
4940
|
+
valid_indices = [item for item in volumes
|
|
4941
|
+
if min_val <= volumes[item] <= max_val]
|
|
4942
|
+
|
|
4943
|
+
elif accepted_mode == 1:
|
|
4944
|
+
channel_data = self.parent().channel_data[self.parent().active_channel]
|
|
4945
|
+
default_max = np.max(channel_data)
|
|
4946
|
+
default_min = np.min(channel_data)
|
|
4947
|
+
|
|
4948
|
+
max_val = int(get_valid_float(self.max.text(), default_max))
|
|
4949
|
+
min_val = int(get_valid_float(self.min.text(), default_min))
|
|
4950
|
+
|
|
4951
|
+
if min_val > max_val:
|
|
4952
|
+
min_val, max_val = max_val, min_val
|
|
4953
|
+
|
|
4954
|
+
valid_indices = list(range(min_val, max_val + 1))
|
|
4955
|
+
|
|
4956
|
+
if channel == 0:
|
|
4957
|
+
self.parent().create_highlight_overlay(node_indices = valid_indices)
|
|
4958
|
+
elif channel == 1:
|
|
4959
|
+
self.parent().create_highlight_overlay(edge_indices = valid_indices)
|
|
4960
|
+
elif channel == 2:
|
|
4961
|
+
self.parent().create_highlight_overlay(overlay1_indices = valid_indices)
|
|
4962
|
+
elif channel == 3:
|
|
4963
|
+
self.parent().create_highlight_overlay(overlay2_indices = valid_indices)
|
|
4964
|
+
|
|
4965
|
+
except Exception as e:
|
|
4966
|
+
print(f"Error showing preview: {e}")
|
|
4967
|
+
|
|
4968
|
+
def thresh(self):
|
|
4969
|
+
try:
|
|
4970
|
+
|
|
4971
|
+
self.run_preview()
|
|
4972
|
+
channel_data = self.parent().channel_data[self.parent().active_channel]
|
|
4973
|
+
mask = self.parent().highlight_overlay > 0
|
|
4974
|
+
channel_data = channel_data * mask
|
|
4975
|
+
self.parent().load_channel(self.parent().active_channel, channel_data, True)
|
|
4976
|
+
self.parent().update_display()
|
|
4977
|
+
self.close()
|
|
4978
|
+
|
|
4979
|
+
except Exception as e:
|
|
4980
|
+
QMessageBox.critical(
|
|
4981
|
+
self,
|
|
4982
|
+
"Error",
|
|
4983
|
+
f"Error running threshold: {str(e)}"
|
|
4984
|
+
)
|
|
4985
|
+
|
|
4986
|
+
|
|
4987
|
+
class SmartDilateDialog(QDialog):
|
|
4988
|
+
def __init__(self, parent, params):
|
|
4989
|
+
super().__init__(parent)
|
|
4990
|
+
self.setWindowTitle("Additional Smart Dilate Parameters")
|
|
4991
|
+
self.setModal(True)
|
|
4992
|
+
|
|
4993
|
+
layout = QFormLayout(self)
|
|
4994
|
+
|
|
4995
|
+
# GPU checkbox (default True)
|
|
4996
|
+
self.GPU = QPushButton("GPU")
|
|
4997
|
+
self.GPU.setCheckable(True)
|
|
4998
|
+
self.GPU.setChecked(True)
|
|
4999
|
+
layout.addRow("Use GPU:", self.GPU)
|
|
5000
|
+
|
|
5001
|
+
self.down_factor = QLineEdit("")
|
|
5002
|
+
layout.addRow("Internal Downsample for GPU (if needed):", self.down_factor)
|
|
5003
|
+
|
|
5004
|
+
self.params = params
|
|
5005
|
+
|
|
5006
|
+
# Add Run button
|
|
5007
|
+
run_button = QPushButton("Dilate")
|
|
5008
|
+
run_button.clicked.connect(self.smart_dilate)
|
|
5009
|
+
layout.addRow(run_button)
|
|
5010
|
+
|
|
5011
|
+
def smart_dilate(self):
|
|
5012
|
+
|
|
5013
|
+
GPU = self.GPU.isChecked()
|
|
5014
|
+
down_factor = float(self.down_factor.text()) if self.down_factor.text().strip() else None
|
|
5015
|
+
active_data, amount, xy_scale, z_scale = self.params
|
|
5016
|
+
|
|
5017
|
+
dilate_xy, dilate_z = n3d.dilation_length_to_pixels(xy_scale, z_scale, amount, amount)
|
|
5018
|
+
|
|
5019
|
+
result = sdl.smart_dilate(active_data, dilate_xy, dilate_z, GPU = GPU, predownsample = down_factor)
|
|
5020
|
+
|
|
5021
|
+
self.parent().load_channel(self.parent().active_channel, result, True)
|
|
5022
|
+
self.accept()
|
|
5023
|
+
|
|
5024
|
+
|
|
5025
|
+
|
|
5026
|
+
class DilateDialog(QDialog):
|
|
5027
|
+
def __init__(self, parent=None):
|
|
5028
|
+
super().__init__(parent)
|
|
5029
|
+
self.setWindowTitle("Dilate Parameters")
|
|
5030
|
+
self.setModal(True)
|
|
5031
|
+
|
|
5032
|
+
layout = QFormLayout(self)
|
|
5033
|
+
|
|
5034
|
+
self.amount = QLineEdit("1")
|
|
5035
|
+
layout.addRow("Dilation Radius:", self.amount)
|
|
5036
|
+
|
|
5037
|
+
if my_network.xy_scale is not None:
|
|
5038
|
+
xy_scale = f"{my_network.xy_scale}"
|
|
5039
|
+
else:
|
|
5040
|
+
xy_scale = "1"
|
|
5041
|
+
|
|
5042
|
+
self.xy_scale = QLineEdit(xy_scale)
|
|
5043
|
+
layout.addRow("xy_scale:", self.xy_scale)
|
|
5044
|
+
|
|
5045
|
+
if my_network.z_scale is not None:
|
|
5046
|
+
z_scale = f"{my_network.z_scale}"
|
|
5047
|
+
else:
|
|
5048
|
+
z_scale = "1"
|
|
5049
|
+
|
|
5050
|
+
self.z_scale = QLineEdit(z_scale)
|
|
5051
|
+
layout.addRow("z_scale:", self.z_scale)
|
|
3993
5052
|
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
self.
|
|
3998
|
-
self.
|
|
3999
|
-
|
|
4000
|
-
layout = QFormLayout(self)
|
|
5053
|
+
# Add mode selection dropdown
|
|
5054
|
+
self.mode_selector = QComboBox()
|
|
5055
|
+
self.mode_selector.addItems(["Binary Dilation", "Preserve Labels (slower)"])
|
|
5056
|
+
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
5057
|
+
layout.addRow("Execution Mode:", self.mode_selector)
|
|
4001
5058
|
|
|
4002
5059
|
# Add Run button
|
|
4003
|
-
run_button = QPushButton("Run
|
|
4004
|
-
run_button.clicked.connect(self.
|
|
5060
|
+
run_button = QPushButton("Run Dilate")
|
|
5061
|
+
run_button.clicked.connect(self.run_dilate)
|
|
4005
5062
|
layout.addRow(run_button)
|
|
4006
5063
|
|
|
4007
|
-
def
|
|
4008
|
-
|
|
5064
|
+
def run_dilate(self):
|
|
4009
5065
|
try:
|
|
4010
5066
|
|
|
5067
|
+
accepted_mode = self.mode_selector.currentIndex()
|
|
5068
|
+
|
|
5069
|
+
# Get amount
|
|
5070
|
+
try:
|
|
5071
|
+
amount = float(self.amount.text()) if self.amount.text() else 1
|
|
5072
|
+
except ValueError:
|
|
5073
|
+
amount = 1
|
|
5074
|
+
|
|
5075
|
+
try:
|
|
5076
|
+
xy_scale = float(self.xy_scale.text()) if self.xy_scale.text() else 1
|
|
5077
|
+
except ValueError:
|
|
5078
|
+
xy_scale = 1
|
|
5079
|
+
|
|
5080
|
+
try:
|
|
5081
|
+
z_scale = float(self.z_scale.text()) if self.z_scale.text() else 1
|
|
5082
|
+
except ValueError:
|
|
5083
|
+
z_scale = 1
|
|
5084
|
+
|
|
4011
5085
|
# Get the active channel data from parent
|
|
4012
5086
|
active_data = self.parent().channel_data[self.parent().active_channel]
|
|
4013
5087
|
if active_data is None:
|
|
4014
5088
|
raise ValueError("No active image selected")
|
|
4015
5089
|
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
# Update both the display data and the network object
|
|
4023
|
-
self.parent().channel_data[self.parent().active_channel] = result
|
|
4024
|
-
|
|
5090
|
+
if accepted_mode == 1:
|
|
5091
|
+
dialog = SmartDilateDialog(self.parent(), [active_data, amount, xy_scale, z_scale])
|
|
5092
|
+
dialog.exec()
|
|
5093
|
+
self.accept()
|
|
5094
|
+
return
|
|
4025
5095
|
|
|
4026
|
-
|
|
4027
|
-
|
|
5096
|
+
# Call dilate method with parameters
|
|
5097
|
+
result = n3d.dilate(
|
|
5098
|
+
active_data,
|
|
5099
|
+
amount,
|
|
5100
|
+
xy_scale = xy_scale,
|
|
5101
|
+
z_scale = z_scale,
|
|
5102
|
+
)
|
|
4028
5103
|
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
except Exception as e:
|
|
4033
|
-
QMessageBox.critical(
|
|
4034
|
-
self,
|
|
4035
|
-
"Error",
|
|
4036
|
-
f"Error running label: {str(e)}"
|
|
4037
|
-
)
|
|
5104
|
+
# Update both the display data and the network object
|
|
5105
|
+
self.parent().load_channel(self.parent().active_channel, result, True)
|
|
4038
5106
|
|
|
5107
|
+
self.parent().update_display()
|
|
5108
|
+
self.accept()
|
|
5109
|
+
|
|
4039
5110
|
except Exception as e:
|
|
5111
|
+
import traceback
|
|
5112
|
+
print(traceback.format_exc())
|
|
4040
5113
|
QMessageBox.critical(
|
|
4041
5114
|
self,
|
|
4042
5115
|
"Error",
|
|
4043
|
-
f"Error running
|
|
5116
|
+
f"Error running dilate: {str(e)}"
|
|
4044
5117
|
)
|
|
4045
5118
|
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
class DilateDialog(QDialog):
|
|
5119
|
+
class ErodeDialog(QDialog):
|
|
4050
5120
|
def __init__(self, parent=None):
|
|
4051
5121
|
super().__init__(parent)
|
|
4052
|
-
self.setWindowTitle("
|
|
5122
|
+
self.setWindowTitle("Erosion Parameters")
|
|
4053
5123
|
self.setModal(True)
|
|
4054
5124
|
|
|
4055
5125
|
layout = QFormLayout(self)
|
|
4056
5126
|
|
|
4057
5127
|
self.amount = QLineEdit("1")
|
|
4058
|
-
layout.addRow("
|
|
5128
|
+
layout.addRow("Erosion Radius:", self.amount)
|
|
4059
5129
|
|
|
4060
5130
|
if my_network.xy_scale is not None:
|
|
4061
5131
|
xy_scale = f"{my_network.xy_scale}"
|
|
@@ -4074,11 +5144,11 @@ class DilateDialog(QDialog):
|
|
|
4074
5144
|
layout.addRow("z_scale:", self.z_scale)
|
|
4075
5145
|
|
|
4076
5146
|
# Add Run button
|
|
4077
|
-
run_button = QPushButton("Run
|
|
4078
|
-
run_button.clicked.connect(self.
|
|
5147
|
+
run_button = QPushButton("Run Erode")
|
|
5148
|
+
run_button.clicked.connect(self.run_erode)
|
|
4079
5149
|
layout.addRow(run_button)
|
|
4080
5150
|
|
|
4081
|
-
def
|
|
5151
|
+
def run_erode(self):
|
|
4082
5152
|
try:
|
|
4083
5153
|
|
|
4084
5154
|
# Get amount
|
|
@@ -4103,7 +5173,7 @@ class DilateDialog(QDialog):
|
|
|
4103
5173
|
raise ValueError("No active image selected")
|
|
4104
5174
|
|
|
4105
5175
|
# Call dilate method with parameters
|
|
4106
|
-
result = n3d.
|
|
5176
|
+
result = n3d.erode(
|
|
4107
5177
|
active_data,
|
|
4108
5178
|
amount,
|
|
4109
5179
|
xy_scale = xy_scale,
|
|
@@ -4124,7 +5194,46 @@ class DilateDialog(QDialog):
|
|
|
4124
5194
|
QMessageBox.critical(
|
|
4125
5195
|
self,
|
|
4126
5196
|
"Error",
|
|
4127
|
-
f"Error running
|
|
5197
|
+
f"Error running erode: {str(e)}"
|
|
5198
|
+
)
|
|
5199
|
+
|
|
5200
|
+
class HoleDialog(QDialog):
|
|
5201
|
+
def __init__(self, parent=None):
|
|
5202
|
+
super().__init__(parent)
|
|
5203
|
+
self.setWindowTitle("Fill Holes? (Active Image)")
|
|
5204
|
+
self.setModal(True)
|
|
5205
|
+
|
|
5206
|
+
layout = QFormLayout(self)
|
|
5207
|
+
|
|
5208
|
+
# Add Run button
|
|
5209
|
+
run_button = QPushButton("Run Fill Holes")
|
|
5210
|
+
run_button.clicked.connect(self.run_holes)
|
|
5211
|
+
layout.addRow(run_button)
|
|
5212
|
+
|
|
5213
|
+
def run_holes(self):
|
|
5214
|
+
try:
|
|
5215
|
+
|
|
5216
|
+
|
|
5217
|
+
# Get the active channel data from parent
|
|
5218
|
+
active_data = self.parent().channel_data[self.parent().active_channel]
|
|
5219
|
+
if active_data is None:
|
|
5220
|
+
raise ValueError("No active image selected")
|
|
5221
|
+
|
|
5222
|
+
# Call dilate method with parameters
|
|
5223
|
+
result = n3d.fill_holes_3d(
|
|
5224
|
+
active_data
|
|
5225
|
+
)
|
|
5226
|
+
|
|
5227
|
+
self.parent().load_channel(self.parent().active_channel, result, True)
|
|
5228
|
+
|
|
5229
|
+
self.parent().update_display()
|
|
5230
|
+
self.accept()
|
|
5231
|
+
|
|
5232
|
+
except Exception as e:
|
|
5233
|
+
QMessageBox.critical(
|
|
5234
|
+
self,
|
|
5235
|
+
"Error",
|
|
5236
|
+
f"Error running fill holes: {str(e)}"
|
|
4128
5237
|
)
|
|
4129
5238
|
|
|
4130
5239
|
class MaskDialog(QDialog):
|
|
@@ -4372,6 +5481,40 @@ class WatershedDialog(QDialog):
|
|
|
4372
5481
|
f"Error running watershed: {str(e)}"
|
|
4373
5482
|
)
|
|
4374
5483
|
|
|
5484
|
+
class ZDialog(QDialog):
|
|
5485
|
+
|
|
5486
|
+
def __init__(self, parent=None):
|
|
5487
|
+
super().__init__(parent)
|
|
5488
|
+
self.setWindowTitle("Z Parameters (Save your network first - this will alter all channels into 2D versions)")
|
|
5489
|
+
self.setModal(True)
|
|
5490
|
+
|
|
5491
|
+
layout = QFormLayout(self)
|
|
5492
|
+
|
|
5493
|
+
# Add mode selection dropdown
|
|
5494
|
+
self.mode_selector = QComboBox()
|
|
5495
|
+
self.mode_selector.addItems(["max", "mean", "min", "sum", "std"])
|
|
5496
|
+
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
5497
|
+
layout.addRow("Execution Mode:", self.mode_selector)
|
|
5498
|
+
|
|
5499
|
+
# Add Run button
|
|
5500
|
+
run_button = QPushButton("Run Z Project")
|
|
5501
|
+
run_button.clicked.connect(self.run_z)
|
|
5502
|
+
layout.addRow(run_button)
|
|
5503
|
+
|
|
5504
|
+
def run_z(self):
|
|
5505
|
+
|
|
5506
|
+
mode = self.mode_selector.currentText()
|
|
5507
|
+
|
|
5508
|
+
for i in range(len(self.parent().channel_data)):
|
|
5509
|
+
try:
|
|
5510
|
+
self.parent().channel_data[i] = n3d.z_project(self.parent().channel_data[i], mode)
|
|
5511
|
+
self.parent().load_channel(i, self.parent().channel_data[i], True)
|
|
5512
|
+
except:
|
|
5513
|
+
pass
|
|
5514
|
+
|
|
5515
|
+
self.accept()
|
|
5516
|
+
|
|
5517
|
+
|
|
4375
5518
|
class CentroidNodeDialog(QDialog):
|
|
4376
5519
|
def __init__(self, parent=None):
|
|
4377
5520
|
super().__init__(parent)
|
|
@@ -4624,6 +5767,275 @@ class BranchDialog(QDialog):
|
|
|
4624
5767
|
|
|
4625
5768
|
|
|
4626
5769
|
|
|
5770
|
+
class IsolateDialog(QDialog):
|
|
5771
|
+
def __init__(self, parent=None):
|
|
5772
|
+
super().__init__(parent)
|
|
5773
|
+
self.setWindowTitle("Select Node types to isolate")
|
|
5774
|
+
self.setModal(True)
|
|
5775
|
+
layout = QFormLayout(self)
|
|
5776
|
+
|
|
5777
|
+
self.combo1 = QComboBox()
|
|
5778
|
+
self.combo1.addItems(list(set(my_network.node_identities.values())))
|
|
5779
|
+
self.combo1.setCurrentIndex(0)
|
|
5780
|
+
layout.addRow("ID 1:", self.combo1)
|
|
5781
|
+
|
|
5782
|
+
self.combo2 = QComboBox()
|
|
5783
|
+
self.combo2.addItems(list(set(my_network.node_identities.values())))
|
|
5784
|
+
self.combo2.setCurrentIndex(1)
|
|
5785
|
+
layout.addRow("ID 2:", self.combo2)
|
|
5786
|
+
|
|
5787
|
+
# Add submit button
|
|
5788
|
+
sub_button = QPushButton("Submit")
|
|
5789
|
+
sub_button.clicked.connect(self.submit_ids)
|
|
5790
|
+
layout.addRow(sub_button)
|
|
5791
|
+
|
|
5792
|
+
def submit_ids(self):
|
|
5793
|
+
try:
|
|
5794
|
+
id1 = self.combo1.currentText()
|
|
5795
|
+
id2 = self.combo2.currentText()
|
|
5796
|
+
if id1 == id2:
|
|
5797
|
+
print("Please select different identities")
|
|
5798
|
+
self.parent().show_isolate_dialog()
|
|
5799
|
+
return
|
|
5800
|
+
else:
|
|
5801
|
+
my_network.isolate_internode_connections(id1, id2)
|
|
5802
|
+
self.accept()
|
|
5803
|
+
except Exception as e:
|
|
5804
|
+
print(f"An error occurred: {e}")
|
|
5805
|
+
|
|
5806
|
+
class AlterDialog(QDialog):
|
|
5807
|
+
def __init__(self, parent=None):
|
|
5808
|
+
super().__init__(parent)
|
|
5809
|
+
self.setWindowTitle("Enter Node/Edge groups to add/remove")
|
|
5810
|
+
self.setModal(True)
|
|
5811
|
+
layout = QFormLayout(self)
|
|
5812
|
+
|
|
5813
|
+
# Node 1
|
|
5814
|
+
self.node1 = QLineEdit()
|
|
5815
|
+
self.node1.setPlaceholderText("Enter integer")
|
|
5816
|
+
layout.addRow("Node1:", self.node1)
|
|
5817
|
+
|
|
5818
|
+
# Node 2
|
|
5819
|
+
self.node2 = QLineEdit()
|
|
5820
|
+
self.node2.setPlaceholderText("Enter integer")
|
|
5821
|
+
layout.addRow("Node2:", self.node2)
|
|
5822
|
+
|
|
5823
|
+
# Edge
|
|
5824
|
+
self.edge = QLineEdit()
|
|
5825
|
+
self.edge.setPlaceholderText("Optional - Enter integer")
|
|
5826
|
+
layout.addRow("Edge:", self.edge)
|
|
5827
|
+
|
|
5828
|
+
# Add add button
|
|
5829
|
+
addbutton = QPushButton("Add pair")
|
|
5830
|
+
addbutton.clicked.connect(self.add)
|
|
5831
|
+
layout.addRow(addbutton)
|
|
5832
|
+
|
|
5833
|
+
# Add remove button
|
|
5834
|
+
removebutton = QPushButton("Remove pair")
|
|
5835
|
+
removebutton.clicked.connect(self.remove)
|
|
5836
|
+
layout.addRow(removebutton)
|
|
5837
|
+
|
|
5838
|
+
def add(self):
|
|
5839
|
+
try:
|
|
5840
|
+
node1 = int(self.node1.text()) if self.node1.text().strip() else None
|
|
5841
|
+
node2 = int(self.node2.text()) if self.node2.text().strip() else None
|
|
5842
|
+
edge = int(self.edge.text()) if self.edge.text().strip() else None
|
|
5843
|
+
|
|
5844
|
+
# Check if we have valid node pairs
|
|
5845
|
+
if node1 is not None and node2 is not None:
|
|
5846
|
+
# Add the node pair and its reverse
|
|
5847
|
+
my_network.network_lists[0].append(node1)
|
|
5848
|
+
my_network.network_lists[1].append(node2)
|
|
5849
|
+
# Add edge value (0 if none provided)
|
|
5850
|
+
my_network.network_lists[2].append(edge if edge is not None else 0)
|
|
5851
|
+
|
|
5852
|
+
# Add reverse pair with same edge value
|
|
5853
|
+
my_network.network_lists[0].append(node2)
|
|
5854
|
+
my_network.network_lists[1].append(node1)
|
|
5855
|
+
my_network.network_lists[2].append(edge if edge is not None else 0)
|
|
5856
|
+
try:
|
|
5857
|
+
if hasattr(my_network, 'network_lists'):
|
|
5858
|
+
model = PandasModel(my_network.network_lists)
|
|
5859
|
+
self.parent().network_table.setModel(model)
|
|
5860
|
+
# Adjust column widths to content
|
|
5861
|
+
for column in range(model.columnCount(None)):
|
|
5862
|
+
self.parent().network_table.resizeColumnToContents(column)
|
|
5863
|
+
except Exception as e:
|
|
5864
|
+
print(f"Error showing network table: {e}")
|
|
5865
|
+
except ValueError:
|
|
5866
|
+
import traceback
|
|
5867
|
+
print(traceback.format_exc())
|
|
5868
|
+
pass # Invalid input - do nothing
|
|
5869
|
+
|
|
5870
|
+
def remove(self):
|
|
5871
|
+
try:
|
|
5872
|
+
node1 = int(self.node1.text()) if self.node1.text().strip() else None
|
|
5873
|
+
node2 = int(self.node2.text()) if self.node2.text().strip() else None
|
|
5874
|
+
edge = int(self.edge.text()) if self.edge.text().strip() else None
|
|
5875
|
+
|
|
5876
|
+
# Check if we have valid node pairs
|
|
5877
|
+
if node1 is not None and node2 is not None:
|
|
5878
|
+
# Create lists for indices to remove
|
|
5879
|
+
indices_to_remove = []
|
|
5880
|
+
|
|
5881
|
+
# Loop through the lists to find matching pairs
|
|
5882
|
+
for i in range(len(my_network.network_lists[0])):
|
|
5883
|
+
forward_match = (my_network.network_lists[0][i] == node1 and
|
|
5884
|
+
my_network.network_lists[1][i] == node2)
|
|
5885
|
+
reverse_match = (my_network.network_lists[0][i] == node2 and
|
|
5886
|
+
my_network.network_lists[1][i] == node1)
|
|
5887
|
+
|
|
5888
|
+
if forward_match or reverse_match:
|
|
5889
|
+
# If edge value specified, only remove if edge matches
|
|
5890
|
+
if edge is not None:
|
|
5891
|
+
if my_network.network_lists[2][i] == edge:
|
|
5892
|
+
indices_to_remove.append(i)
|
|
5893
|
+
else:
|
|
5894
|
+
# If no edge specified, remove all matching pairs
|
|
5895
|
+
indices_to_remove.append(i)
|
|
5896
|
+
|
|
5897
|
+
# Remove elements in reverse order to maintain correct indices
|
|
5898
|
+
for i in sorted(indices_to_remove, reverse=True):
|
|
5899
|
+
my_network.network_lists[0].pop(i)
|
|
5900
|
+
my_network.network_lists[1].pop(i)
|
|
5901
|
+
my_network.network_lists[2].pop(i)
|
|
5902
|
+
|
|
5903
|
+
try:
|
|
5904
|
+
if hasattr(my_network, 'network_lists'):
|
|
5905
|
+
model = PandasModel(my_network.network_lists)
|
|
5906
|
+
self.parent().network_table.setModel(model)
|
|
5907
|
+
# Adjust column widths to content
|
|
5908
|
+
for column in range(model.columnCount(None)):
|
|
5909
|
+
self.parent().network_table.resizeColumnToContents(column)
|
|
5910
|
+
except Exception as e:
|
|
5911
|
+
print(f"Error showing network table: {e}")
|
|
5912
|
+
|
|
5913
|
+
except ValueError:
|
|
5914
|
+
import traceback
|
|
5915
|
+
print(traceback.format_exc())
|
|
5916
|
+
pass # Invalid input - do nothing
|
|
5917
|
+
|
|
5918
|
+
|
|
5919
|
+
class ModifyDialog(QDialog):
|
|
5920
|
+
def __init__(self, parent=None):
|
|
5921
|
+
super().__init__(parent)
|
|
5922
|
+
self.setWindowTitle("Create Nodes from Edge Vertices")
|
|
5923
|
+
self.setModal(True)
|
|
5924
|
+
layout = QFormLayout(self)
|
|
5925
|
+
|
|
5926
|
+
# trunk checkbox (default false)
|
|
5927
|
+
self.trunk = QPushButton("Remove Trunk")
|
|
5928
|
+
self.trunk.setCheckable(True)
|
|
5929
|
+
self.trunk.setChecked(False)
|
|
5930
|
+
layout.addRow("Remove Trunk? (Most connected edge - overrides below):", self.trunk)
|
|
5931
|
+
|
|
5932
|
+
# trunk checkbox (default false)
|
|
5933
|
+
self.trunknode = QPushButton("Trunk -> Node")
|
|
5934
|
+
self.trunknode.setCheckable(True)
|
|
5935
|
+
self.trunknode.setChecked(False)
|
|
5936
|
+
layout.addRow("Convert Trunk to Node? (Most connected edge):", self.trunknode)
|
|
5937
|
+
|
|
5938
|
+
# edgenode checkbox (default false)
|
|
5939
|
+
self.edgenode = QPushButton("Edges -> Nodes")
|
|
5940
|
+
self.edgenode.setCheckable(True)
|
|
5941
|
+
self.edgenode.setChecked(False)
|
|
5942
|
+
layout.addRow("Convert 'Edges (Labeled objects)' to node objects?:", self.edgenode)
|
|
5943
|
+
|
|
5944
|
+
# edgeweight checkbox (default false)
|
|
5945
|
+
self.edgeweight = QPushButton("Remove weights")
|
|
5946
|
+
self.edgeweight.setCheckable(True)
|
|
5947
|
+
self.edgeweight.setChecked(False)
|
|
5948
|
+
layout.addRow("Remove network weights?:", self.edgeweight)
|
|
5949
|
+
|
|
5950
|
+
# prune checkbox (default false)
|
|
5951
|
+
self.prune = QPushButton("Prune Same Type")
|
|
5952
|
+
self.prune.setCheckable(True)
|
|
5953
|
+
self.prune.setChecked(False)
|
|
5954
|
+
layout.addRow("Prune connections between nodes of the same type (if assigned)?:", self.prune)
|
|
5955
|
+
|
|
5956
|
+
# isolate checkbox (default false)
|
|
5957
|
+
self.isolate = QPushButton("Isolate Two Types")
|
|
5958
|
+
self.isolate.setCheckable(True)
|
|
5959
|
+
self.isolate.setChecked(False)
|
|
5960
|
+
layout.addRow("Isolate connections between two specific node types (if assigned)?:", self.isolate)
|
|
5961
|
+
|
|
5962
|
+
#change button
|
|
5963
|
+
change_button = QPushButton("Add/Remove Network Pairs")
|
|
5964
|
+
change_button.clicked.connect(self.show_alter_dialog)
|
|
5965
|
+
layout.addRow(change_button)
|
|
5966
|
+
|
|
5967
|
+
# Add Run button
|
|
5968
|
+
run_button = QPushButton("Make Changes")
|
|
5969
|
+
run_button.clicked.connect(self.run_changes)
|
|
5970
|
+
layout.addRow(run_button)
|
|
5971
|
+
|
|
5972
|
+
def show_isolate_dialog(self):
|
|
5973
|
+
|
|
5974
|
+
dialog = IsolateDialog(self)
|
|
5975
|
+
dialog.exec()
|
|
5976
|
+
|
|
5977
|
+
def show_alter_dialog(self):
|
|
5978
|
+
|
|
5979
|
+
dialog = AlterDialog(self.parent())
|
|
5980
|
+
dialog.exec()
|
|
5981
|
+
|
|
5982
|
+
def run_changes(self):
|
|
5983
|
+
|
|
5984
|
+
try:
|
|
5985
|
+
|
|
5986
|
+
trunk = self.trunk.isChecked()
|
|
5987
|
+
if not trunk:
|
|
5988
|
+
trunknode = self.trunknode.isChecked()
|
|
5989
|
+
else:
|
|
5990
|
+
trunknode = False
|
|
5991
|
+
edgenode = self.edgenode.isChecked()
|
|
5992
|
+
edgeweight = self.edgeweight.isChecked()
|
|
5993
|
+
prune = self.prune.isChecked()
|
|
5994
|
+
isolate = self.isolate.isChecked()
|
|
5995
|
+
|
|
5996
|
+
if isolate and my_network.node_identities is not None:
|
|
5997
|
+
self.show_isolate_dialog()
|
|
5998
|
+
|
|
5999
|
+
if edgeweight:
|
|
6000
|
+
my_network.remove_edge_weights()
|
|
6001
|
+
if prune and my_network.node_identities is not None:
|
|
6002
|
+
my_network.prune_samenode_connections()
|
|
6003
|
+
if trunk:
|
|
6004
|
+
my_network.remove_trunk_post()
|
|
6005
|
+
if trunknode:
|
|
6006
|
+
if my_network.node_centroids is None or my_network.edge_centroids is None:
|
|
6007
|
+
self.parent().show_centroid_dialog()
|
|
6008
|
+
my_network.trunk_to_node()
|
|
6009
|
+
self.parent().load_channel(0, my_network.nodes, True)
|
|
6010
|
+
if edgenode:
|
|
6011
|
+
if my_network.node_centroids is None or my_network.edge_centroids is None:
|
|
6012
|
+
self.parent().show_centroid_dialog()
|
|
6013
|
+
my_network.edge_to_node()
|
|
6014
|
+
self.parent().load_channel(0, my_network.nodes, True)
|
|
6015
|
+
self.parent().load_channel(1, my_network.edges, True)
|
|
6016
|
+
|
|
6017
|
+
try:
|
|
6018
|
+
if hasattr(my_network, 'network_lists'):
|
|
6019
|
+
model = PandasModel(my_network.network_lists)
|
|
6020
|
+
self.parent().network_table.setModel(model)
|
|
6021
|
+
# Adjust column widths to content
|
|
6022
|
+
for column in range(model.columnCount(None)):
|
|
6023
|
+
self.parent().network_table.resizeColumnToContents(column)
|
|
6024
|
+
except Exception as e:
|
|
6025
|
+
print(f"Error showing network table: {e}")
|
|
6026
|
+
|
|
6027
|
+
if hasattr(my_network, 'node_identities') and my_network.node_identities is not None:
|
|
6028
|
+
try:
|
|
6029
|
+
self.parent().format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
|
|
6030
|
+
except Exception as e:
|
|
6031
|
+
print(f"Error loading node identity table: {e}")
|
|
6032
|
+
|
|
6033
|
+
self.parent().update_display()
|
|
6034
|
+
self.accept()
|
|
6035
|
+
|
|
6036
|
+
except Exception as e:
|
|
6037
|
+
print(f"An error occurred: {e}")
|
|
6038
|
+
|
|
4627
6039
|
|
|
4628
6040
|
|
|
4629
6041
|
|
|
@@ -5031,6 +6443,15 @@ class ProxDialog(QDialog):
|
|
|
5031
6443
|
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
5032
6444
|
layout.addRow("Execution Mode:", self.mode_selector)
|
|
5033
6445
|
|
|
6446
|
+
if my_network.node_identities is not None:
|
|
6447
|
+
self.id_selector = QComboBox()
|
|
6448
|
+
# Add all options from id dictionary
|
|
6449
|
+
self.id_selector.addItems(['None'] + list(set(my_network.node_identities.values())))
|
|
6450
|
+
self.id_selector.setCurrentIndex(0) # Default to Mode 1
|
|
6451
|
+
layout.addRow("Create Networks only from a specific node identity?:", self.id_selector)
|
|
6452
|
+
else:
|
|
6453
|
+
self.id_selector = None
|
|
6454
|
+
|
|
5034
6455
|
self.overlays = QPushButton("Overlays")
|
|
5035
6456
|
self.overlays.setCheckable(True)
|
|
5036
6457
|
self.overlays.setChecked(True)
|
|
@@ -5054,6 +6475,15 @@ class ProxDialog(QDialog):
|
|
|
5054
6475
|
|
|
5055
6476
|
mode = self.mode_selector.currentIndex()
|
|
5056
6477
|
|
|
6478
|
+
if self.id_selector is not None and self.id_selector.currentText() != 'None':
|
|
6479
|
+
target = self.id_selector.currentText()
|
|
6480
|
+
targets = []
|
|
6481
|
+
for node in my_network.node_identities:
|
|
6482
|
+
if target == my_network.node_identities[node]:
|
|
6483
|
+
targets.append(int(node))
|
|
6484
|
+
else:
|
|
6485
|
+
targets = None
|
|
6486
|
+
|
|
5057
6487
|
try:
|
|
5058
6488
|
directory = self.directory.text() if self.directory.text() else None
|
|
5059
6489
|
except:
|
|
@@ -5087,7 +6517,7 @@ class ProxDialog(QDialog):
|
|
|
5087
6517
|
my_network.nodes, _ = n3d.label_objects(my_network.nodes)
|
|
5088
6518
|
if my_network.node_centroids is None:
|
|
5089
6519
|
self.parent().show_centroid_dialog()
|
|
5090
|
-
my_network.morph_proximity(search = search)
|
|
6520
|
+
my_network.morph_proximity(search = search, targets = targets)
|
|
5091
6521
|
|
|
5092
6522
|
self.parent().load_channel(0, channel_data = my_network.nodes, data = True)
|
|
5093
6523
|
elif mode == 0:
|
|
@@ -5113,10 +6543,10 @@ class ProxDialog(QDialog):
|
|
|
5113
6543
|
return
|
|
5114
6544
|
|
|
5115
6545
|
if populate:
|
|
5116
|
-
my_network.nodes = my_network.kd_network(distance = search)
|
|
6546
|
+
my_network.nodes = my_network.kd_network(distance = search, targets = targets)
|
|
5117
6547
|
self.parent().load_channel(0, channel_data = my_network.nodes, data = True)
|
|
5118
6548
|
else:
|
|
5119
|
-
my_network.kd_network(distance = search)
|
|
6549
|
+
my_network.kd_network(distance = search, targets = targets)
|
|
5120
6550
|
|
|
5121
6551
|
|
|
5122
6552
|
my_network.dump(directory = directory)
|