nettracer3d 0.7.6__py3-none-any.whl → 0.7.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nettracer3d/community_extractor.py +13 -29
- nettracer3d/excelotron.py +1719 -0
- nettracer3d/modularity.py +6 -9
- nettracer3d/neighborhoods.py +354 -0
- nettracer3d/nettracer.py +400 -13
- nettracer3d/nettracer_gui.py +1120 -229
- nettracer3d/proximity.py +89 -9
- nettracer3d/smart_dilate.py +20 -15
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/METADATA +11 -2
- nettracer3d-0.7.8.dist-info/RECORD +23 -0
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/WHEEL +1 -1
- nettracer3d-0.7.6.dist-info/RECORD +0 -21
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.7.6.dist-info → nettracer3d-0.7.8.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -5,7 +5,7 @@ from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QG
|
|
|
5
5
|
QFormLayout, QLineEdit, QPushButton, QFileDialog,
|
|
6
6
|
QLabel, QComboBox, QMessageBox, QTableView, QInputDialog,
|
|
7
7
|
QMenu, QTabWidget, QGroupBox)
|
|
8
|
-
from PyQt6.QtCore import (QPoint, Qt, QAbstractTableModel, QTimer, QThread, pyqtSignal)
|
|
8
|
+
from PyQt6.QtCore import (QPoint, Qt, QAbstractTableModel, QTimer, QThread, pyqtSignal, QObject, QCoreApplication)
|
|
9
9
|
import numpy as np
|
|
10
10
|
import time
|
|
11
11
|
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
@@ -26,6 +26,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
26
26
|
from functools import partial
|
|
27
27
|
from nettracer3d import segmenter
|
|
28
28
|
from nettracer3d import segmenter_GPU
|
|
29
|
+
from nettracer3d import excelotron
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
|
|
@@ -204,6 +205,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
204
205
|
self.high_button.setChecked(True)
|
|
205
206
|
buttons_layout.addWidget(self.high_button)
|
|
206
207
|
self.highlight = True
|
|
208
|
+
self.needs_mini = False
|
|
207
209
|
|
|
208
210
|
self.pen_button = QPushButton("🖊️")
|
|
209
211
|
self.pen_button.setCheckable(True)
|
|
@@ -393,11 +395,14 @@ class ImageViewerWindow(QMainWindow):
|
|
|
393
395
|
|
|
394
396
|
#self.canvas.mpl_connect('button_press_event', self.on_mouse_click)
|
|
395
397
|
|
|
396
|
-
# Initialize measurement
|
|
398
|
+
# Initialize measurement tracking
|
|
397
399
|
self.measurement_points = [] # List to store point pairs
|
|
398
|
-
self.
|
|
400
|
+
self.angle_measurements = [] # NEW: List to store angle trios
|
|
401
|
+
self.current_point = None # Store first point of current pair/trio
|
|
402
|
+
self.current_second_point = None # Store second point when building trio
|
|
399
403
|
self.current_pair_index = 0 # Track pair numbering
|
|
400
|
-
|
|
404
|
+
self.current_trio_index = 0 # Track trio numbering
|
|
405
|
+
self.measurement_mode = "distance" # "distance" or "angle" mode
|
|
401
406
|
|
|
402
407
|
# Add these new methods for handling neighbors and components (FOR RIGHT CLICKIGN)
|
|
403
408
|
self.show_neighbors_clicked = None
|
|
@@ -409,6 +414,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
409
414
|
self.mini_overlay = False # If the program is currently drawing the overlay by frame this will be true
|
|
410
415
|
self.mini_overlay_data = None #Actual data for mini overlay
|
|
411
416
|
self.mini_thresh = (500*500*500) # Array volume to start using mini overlays for
|
|
417
|
+
self.shape = None
|
|
418
|
+
|
|
419
|
+
self.excel_manager = ExcelotronManager(self)
|
|
420
|
+
self.excel_manager.data_received.connect(self.handle_excel_data)
|
|
421
|
+
self.prev_coms = None
|
|
412
422
|
|
|
413
423
|
def start_left_scroll(self):
|
|
414
424
|
"""Start scrolling left when left arrow is pressed."""
|
|
@@ -658,6 +668,18 @@ class ImageViewerWindow(QMainWindow):
|
|
|
658
668
|
edge_indices (list): List of edge indices to highlight
|
|
659
669
|
"""
|
|
660
670
|
|
|
671
|
+
if not self.high_button.isChecked():
|
|
672
|
+
|
|
673
|
+
if len(self.clicked_values['edges']) > 0:
|
|
674
|
+
self.format_for_upperright_table(self.clicked_values['edges'], title = 'Selected Edges')
|
|
675
|
+
self.needs_mini = True
|
|
676
|
+
if len(self.clicked_values['nodes']) > 0:
|
|
677
|
+
self.format_for_upperright_table(self.clicked_values['nodes'], title = 'Selected Nodes')
|
|
678
|
+
self.needs_mini = True
|
|
679
|
+
|
|
680
|
+
return
|
|
681
|
+
|
|
682
|
+
|
|
661
683
|
def process_chunk(chunk_data, indices_to_check):
|
|
662
684
|
"""Process a single chunk of the array to create highlight mask"""
|
|
663
685
|
mask = np.isin(chunk_data, indices_to_check)
|
|
@@ -822,21 +844,38 @@ class ImageViewerWindow(QMainWindow):
|
|
|
822
844
|
override_obj.triggered.connect(self.handle_override)
|
|
823
845
|
context_menu.addMenu(highlight_menu)
|
|
824
846
|
|
|
825
|
-
# Create
|
|
826
|
-
measure_menu =
|
|
827
|
-
|
|
847
|
+
# Create measurement submenu
|
|
848
|
+
measure_menu = context_menu.addMenu("Measurements")
|
|
849
|
+
|
|
850
|
+
# Distance measurement options
|
|
851
|
+
distance_menu = measure_menu.addMenu("Distance")
|
|
828
852
|
if self.current_point is None:
|
|
829
|
-
|
|
830
|
-
show_point_menu = measure_menu.addAction("Place Measurement Point")
|
|
853
|
+
show_point_menu = distance_menu.addAction("Place First Point")
|
|
831
854
|
show_point_menu.triggered.connect(
|
|
832
|
-
lambda: self.
|
|
855
|
+
lambda: self.place_distance_point(x_idx, y_idx, self.current_slice))
|
|
833
856
|
else:
|
|
834
|
-
|
|
835
|
-
show_point_menu = measure_menu.addAction("Place Second Point")
|
|
857
|
+
show_point_menu = distance_menu.addAction("Place Second Point")
|
|
836
858
|
show_point_menu.triggered.connect(
|
|
837
|
-
lambda: self.
|
|
838
|
-
|
|
839
|
-
|
|
859
|
+
lambda: self.place_distance_point(x_idx, y_idx, self.current_slice))
|
|
860
|
+
|
|
861
|
+
# Angle measurement options
|
|
862
|
+
angle_menu = measure_menu.addMenu("Angle")
|
|
863
|
+
if self.current_point is None:
|
|
864
|
+
angle_first = angle_menu.addAction("Place First Point (A)")
|
|
865
|
+
angle_first.triggered.connect(
|
|
866
|
+
lambda: self.place_angle_point(x_idx, y_idx, self.current_slice))
|
|
867
|
+
elif self.current_second_point is None:
|
|
868
|
+
angle_second = angle_menu.addAction("Place Second Point (B - Vertex)")
|
|
869
|
+
angle_second.triggered.connect(
|
|
870
|
+
lambda: self.place_angle_point(x_idx, y_idx, self.current_slice))
|
|
871
|
+
else:
|
|
872
|
+
angle_third = angle_menu.addAction("Place Third Point (C)")
|
|
873
|
+
angle_third.triggered.connect(
|
|
874
|
+
lambda: self.place_angle_point(x_idx, y_idx, self.current_slice))
|
|
875
|
+
|
|
876
|
+
show_remove_menu = measure_menu.addAction("Remove All Measurements")
|
|
877
|
+
show_remove_menu.triggered.connect(self.handle_remove_all_measurements)
|
|
878
|
+
|
|
840
879
|
context_menu.addMenu(measure_menu)
|
|
841
880
|
|
|
842
881
|
# Connect actions to callbacks
|
|
@@ -854,7 +893,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
854
893
|
if self.highlight_overlay is not None or self.mini_overlay_data is not None:
|
|
855
894
|
highlight_select = context_menu.addAction("Add highlight in network selection")
|
|
856
895
|
highlight_select.triggered.connect(self.handle_highlight_select)
|
|
857
|
-
show_remove_menu.triggered.connect(self.handle_remove_points)
|
|
858
896
|
|
|
859
897
|
cursor_pos = QCursor.pos()
|
|
860
898
|
context_menu.exec(cursor_pos)
|
|
@@ -863,24 +901,25 @@ class ImageViewerWindow(QMainWindow):
|
|
|
863
901
|
pass
|
|
864
902
|
|
|
865
903
|
|
|
866
|
-
def
|
|
867
|
-
"""Place a measurement point
|
|
904
|
+
def place_distance_point(self, x, y, z):
|
|
905
|
+
"""Place a measurement point for distance measurement."""
|
|
868
906
|
if self.current_point is None:
|
|
869
907
|
# This is the first point
|
|
870
908
|
self.current_point = (x, y, z)
|
|
871
909
|
self.ax.plot(x, y, 'yo', markersize=8)
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
color='white', ha='center', va='bottom')
|
|
910
|
+
self.ax.text(x, y+5, f"D{self.current_pair_index}",
|
|
911
|
+
color='yellow', ha='center', va='bottom')
|
|
875
912
|
self.canvas.draw()
|
|
876
|
-
|
|
913
|
+
self.measurement_mode = "distance"
|
|
877
914
|
else:
|
|
878
915
|
# This is the second point
|
|
879
916
|
x1, y1, z1 = self.current_point
|
|
880
917
|
x2, y2, z2 = x, y, z
|
|
881
918
|
|
|
882
919
|
# Calculate distance
|
|
883
|
-
distance = np.sqrt(((x2-x1)*my_network.xy_scale)**2 +
|
|
920
|
+
distance = np.sqrt(((x2-x1)*my_network.xy_scale)**2 +
|
|
921
|
+
((y2-y1)*my_network.xy_scale)**2 +
|
|
922
|
+
((z2-z1)*my_network.z_scale)**2)
|
|
884
923
|
distance2 = np.sqrt(((x2-x1))**2 + ((y2-y1))**2 + ((z2-z1))**2)
|
|
885
924
|
|
|
886
925
|
# Store the point pair
|
|
@@ -894,9 +933,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
894
933
|
|
|
895
934
|
# Draw second point and line
|
|
896
935
|
self.ax.plot(x2, y2, 'yo', markersize=8)
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
color='white', ha='center', va='bottom')
|
|
936
|
+
self.ax.text(x2, y2+5, f"D{self.current_pair_index}",
|
|
937
|
+
color='yellow', ha='center', va='bottom')
|
|
900
938
|
if z1 == z2: # Only draw line if points are on same slice
|
|
901
939
|
self.ax.plot([x1, x2], [y1, y2], 'r--', alpha=0.5)
|
|
902
940
|
self.canvas.draw()
|
|
@@ -907,46 +945,194 @@ class ImageViewerWindow(QMainWindow):
|
|
|
907
945
|
# Reset for next pair
|
|
908
946
|
self.current_point = None
|
|
909
947
|
self.current_pair_index += 1
|
|
948
|
+
self.measurement_mode = "distance"
|
|
949
|
+
|
|
950
|
+
def place_angle_point(self, x, y, z):
|
|
951
|
+
"""Place a measurement point for angle measurement."""
|
|
952
|
+
if self.current_point is None:
|
|
953
|
+
# First point (A)
|
|
954
|
+
self.current_point = (x, y, z)
|
|
955
|
+
self.ax.plot(x, y, 'go', markersize=8)
|
|
956
|
+
self.ax.text(x, y+5, f"A{self.current_trio_index}",
|
|
957
|
+
color='green', ha='center', va='bottom')
|
|
958
|
+
self.canvas.draw()
|
|
959
|
+
self.measurement_mode = "angle"
|
|
960
|
+
|
|
961
|
+
elif self.current_second_point is None:
|
|
962
|
+
# Second point (B - vertex)
|
|
963
|
+
self.current_second_point = (x, y, z)
|
|
964
|
+
x1, y1, z1 = self.current_point
|
|
965
|
+
|
|
966
|
+
self.ax.plot(x, y, 'go', markersize=8)
|
|
967
|
+
self.ax.text(x, y+5, f"B{self.current_trio_index}",
|
|
968
|
+
color='green', ha='center', va='bottom')
|
|
969
|
+
|
|
970
|
+
# Draw line from A to B
|
|
971
|
+
if z1 == z:
|
|
972
|
+
self.ax.plot([x1, x], [y1, y], 'g--', alpha=0.7)
|
|
973
|
+
self.canvas.draw()
|
|
974
|
+
|
|
975
|
+
else:
|
|
976
|
+
# Third point (C)
|
|
977
|
+
x1, y1, z1 = self.current_point # Point A
|
|
978
|
+
x2, y2, z2 = self.current_second_point # Point B (vertex)
|
|
979
|
+
x3, y3, z3 = x, y, z # Point C
|
|
980
|
+
|
|
981
|
+
# Calculate angles and distances
|
|
982
|
+
angle_data = self.calculate_3d_angle(
|
|
983
|
+
(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)
|
|
984
|
+
)
|
|
985
|
+
|
|
986
|
+
# Store the trio
|
|
987
|
+
self.angle_measurements.append({
|
|
988
|
+
'trio_index': self.current_trio_index,
|
|
989
|
+
'point_a': (x1, y1, z1),
|
|
990
|
+
'point_b': (x2, y2, z2), # vertex
|
|
991
|
+
'point_c': (x3, y3, z3),
|
|
992
|
+
**angle_data
|
|
993
|
+
})
|
|
994
|
+
|
|
995
|
+
# Also add the two distances as separate pairs
|
|
996
|
+
dist_ab = np.sqrt(((x2-x1)*my_network.xy_scale)**2 +
|
|
997
|
+
((y2-y1)*my_network.xy_scale)**2 +
|
|
998
|
+
((z2-z1)*my_network.z_scale)**2)
|
|
999
|
+
dist_bc = np.sqrt(((x3-x2)*my_network.xy_scale)**2 +
|
|
1000
|
+
((y3-y2)*my_network.xy_scale)**2 +
|
|
1001
|
+
((z3-z2)*my_network.z_scale)**2)
|
|
1002
|
+
|
|
1003
|
+
dist_ab_voxel = np.sqrt((x2-x1)**2 + (y2-y1)**2 + (z2-z1)**2)
|
|
1004
|
+
dist_bc_voxel = np.sqrt((x3-x2)**2 + (y3-y2)**2 + (z3-z2)**2)
|
|
1005
|
+
|
|
1006
|
+
self.measurement_points.extend([
|
|
1007
|
+
{
|
|
1008
|
+
'pair_index': f"A{self.current_trio_index}-B{self.current_trio_index}",
|
|
1009
|
+
'point1': (x1, y1, z1),
|
|
1010
|
+
'point2': (x2, y2, z2),
|
|
1011
|
+
'distance': dist_ab,
|
|
1012
|
+
'distance2': dist_ab_voxel
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
'pair_index': f"B{self.current_trio_index}-C{self.current_trio_index}",
|
|
1016
|
+
'point1': (x2, y2, z2),
|
|
1017
|
+
'point2': (x3, y3, z3),
|
|
1018
|
+
'distance': dist_bc,
|
|
1019
|
+
'distance2': dist_bc_voxel
|
|
1020
|
+
}
|
|
1021
|
+
])
|
|
1022
|
+
|
|
1023
|
+
# Draw third point and line
|
|
1024
|
+
self.ax.plot(x3, y3, 'go', markersize=8)
|
|
1025
|
+
self.ax.text(x3, y3+5, f"C{self.current_trio_index}",
|
|
1026
|
+
color='green', ha='center', va='bottom')
|
|
1027
|
+
|
|
1028
|
+
if z2 == z3: # Draw line from B to C if on same slice
|
|
1029
|
+
self.ax.plot([x2, x3], [y2, y3], 'g--', alpha=0.7)
|
|
1030
|
+
self.canvas.draw()
|
|
1031
|
+
|
|
1032
|
+
# Update measurement display
|
|
1033
|
+
self.update_measurement_display()
|
|
1034
|
+
|
|
1035
|
+
# Reset for next trio
|
|
1036
|
+
self.current_point = None
|
|
1037
|
+
self.current_second_point = None
|
|
1038
|
+
self.current_trio_index += 1
|
|
1039
|
+
self.measurement_mode = "angle"
|
|
1040
|
+
|
|
1041
|
+
def calculate_3d_angle(self, point_a, point_b, point_c):
|
|
1042
|
+
"""Calculate 3D angle at vertex B between points A-B-C."""
|
|
1043
|
+
x1, y1, z1 = point_a
|
|
1044
|
+
x2, y2, z2 = point_b # vertex
|
|
1045
|
+
x3, y3, z3 = point_c
|
|
1046
|
+
|
|
1047
|
+
# Apply scaling
|
|
1048
|
+
scaled_a = np.array([x1 * my_network.xy_scale, y1 * my_network.xy_scale, z1 * my_network.z_scale])
|
|
1049
|
+
scaled_b = np.array([x2 * my_network.xy_scale, y2 * my_network.xy_scale, z2 * my_network.z_scale])
|
|
1050
|
+
scaled_c = np.array([x3 * my_network.xy_scale, y3 * my_network.xy_scale, z3 * my_network.z_scale])
|
|
1051
|
+
|
|
1052
|
+
# Create vectors from vertex B
|
|
1053
|
+
vec_ba = scaled_a - scaled_b
|
|
1054
|
+
vec_bc = scaled_c - scaled_b
|
|
1055
|
+
|
|
1056
|
+
# Calculate angle using dot product
|
|
1057
|
+
dot_product = np.dot(vec_ba, vec_bc)
|
|
1058
|
+
magnitude_ba = np.linalg.norm(vec_ba)
|
|
1059
|
+
magnitude_bc = np.linalg.norm(vec_bc)
|
|
1060
|
+
|
|
1061
|
+
# Avoid division by zero
|
|
1062
|
+
if magnitude_ba == 0 or magnitude_bc == 0:
|
|
1063
|
+
return {'angle_degrees': 0}
|
|
1064
|
+
|
|
1065
|
+
cos_angle = dot_product / (magnitude_ba * magnitude_bc)
|
|
1066
|
+
cos_angle = np.clip(cos_angle, -1.0, 1.0) # Handle numerical errors
|
|
1067
|
+
|
|
1068
|
+
angle_radians = np.arccos(cos_angle)
|
|
1069
|
+
angle_degrees = np.degrees(angle_radians)
|
|
1070
|
+
|
|
1071
|
+
return {'angle_degrees': angle_degrees}
|
|
910
1072
|
|
|
911
|
-
def
|
|
912
|
-
"""Remove all measurement points."""
|
|
1073
|
+
def handle_remove_all_measurements(self):
|
|
1074
|
+
"""Remove all measurement points and angles."""
|
|
913
1075
|
self.measurement_points = []
|
|
1076
|
+
self.angle_measurements = []
|
|
914
1077
|
self.current_point = None
|
|
1078
|
+
self.current_second_point = None
|
|
915
1079
|
self.current_pair_index = 0
|
|
1080
|
+
self.current_trio_index = 0
|
|
1081
|
+
self.measurement_mode = "distance"
|
|
916
1082
|
self.update_display()
|
|
917
1083
|
self.update_measurement_display()
|
|
918
1084
|
|
|
919
|
-
# Modify the update_measurement_display method:
|
|
920
1085
|
def update_measurement_display(self):
|
|
921
1086
|
"""Update the measurement information display in the top right widget."""
|
|
1087
|
+
# Distance measurements
|
|
922
1088
|
if not self.measurement_points:
|
|
923
|
-
|
|
924
|
-
df = pd.DataFrame()
|
|
1089
|
+
distance_df = pd.DataFrame()
|
|
925
1090
|
else:
|
|
926
|
-
|
|
927
|
-
data = []
|
|
1091
|
+
distance_data = []
|
|
928
1092
|
for point in self.measurement_points:
|
|
929
1093
|
x1, y1, z1 = point['point1']
|
|
930
1094
|
x2, y2, z2 = point['point2']
|
|
931
|
-
|
|
1095
|
+
distance_data.append({
|
|
932
1096
|
'Pair ID': point['pair_index'],
|
|
933
1097
|
'Point 1 (X,Y,Z)': f"({x1:.1f}, {y1:.1f}, {z1})",
|
|
934
1098
|
'Point 2 (X,Y,Z)': f"({x2:.1f}, {y2:.1f}, {z2})",
|
|
935
1099
|
'Scaled Distance': f"{point['distance']:.2f}",
|
|
936
1100
|
'Voxel Distance': f"{point['distance2']:.2f}"
|
|
937
1101
|
})
|
|
938
|
-
|
|
1102
|
+
distance_df = pd.DataFrame(distance_data)
|
|
939
1103
|
|
|
940
|
-
#
|
|
941
|
-
|
|
942
|
-
|
|
1104
|
+
# Angle measurements
|
|
1105
|
+
if not self.angle_measurements:
|
|
1106
|
+
angle_df = pd.DataFrame()
|
|
1107
|
+
else:
|
|
1108
|
+
angle_data = []
|
|
1109
|
+
for angle in self.angle_measurements:
|
|
1110
|
+
xa, ya, za = angle['point_a']
|
|
1111
|
+
xb, yb, zb = angle['point_b']
|
|
1112
|
+
xc, yc, zc = angle['point_c']
|
|
1113
|
+
angle_data.append({
|
|
1114
|
+
'Trio ID': f"A{angle['trio_index']}-B{angle['trio_index']}-C{angle['trio_index']}",
|
|
1115
|
+
'Point A (X,Y,Z)': f"({xa:.1f}, {ya:.1f}, {za})",
|
|
1116
|
+
'Point B (X,Y,Z)': f"({xb:.1f}, {yb:.1f}, {zb})",
|
|
1117
|
+
'Point C (X,Y,Z)': f"({xc:.1f}, {yc:.1f}, {zc})",
|
|
1118
|
+
'Angle (°)': f"{angle['angle_degrees']:.1f}"
|
|
1119
|
+
})
|
|
1120
|
+
angle_df = pd.DataFrame(angle_data)
|
|
943
1121
|
|
|
944
|
-
#
|
|
945
|
-
|
|
1122
|
+
# Create tables
|
|
1123
|
+
if not distance_df.empty:
|
|
1124
|
+
distance_table = CustomTableView(self)
|
|
1125
|
+
distance_table.setModel(PandasModel(distance_df))
|
|
1126
|
+
self.tabbed_data.add_table("Distance Measurements", distance_table)
|
|
1127
|
+
for column in range(distance_table.model().columnCount(None)):
|
|
1128
|
+
distance_table.resizeColumnToContents(column)
|
|
946
1129
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1130
|
+
if not angle_df.empty:
|
|
1131
|
+
angle_table = CustomTableView(self)
|
|
1132
|
+
angle_table.setModel(PandasModel(angle_df))
|
|
1133
|
+
self.tabbed_data.add_table("Angle Measurements", angle_table)
|
|
1134
|
+
for column in range(angle_table.model().columnCount(None)):
|
|
1135
|
+
angle_table.resizeColumnToContents(column)
|
|
950
1136
|
|
|
951
1137
|
|
|
952
1138
|
def show_network_table(self):
|
|
@@ -1726,6 +1912,12 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1726
1912
|
self.highlight = self.high_button.isChecked()
|
|
1727
1913
|
current_xlim = self.ax.get_xlim() if hasattr(self, 'ax') and self.ax.get_xlim() != (0, 1) else None
|
|
1728
1914
|
current_ylim = self.ax.get_ylim() if hasattr(self, 'ax') and self.ax.get_ylim() != (0, 1) else None
|
|
1915
|
+
|
|
1916
|
+
if self.high_button.isChecked():
|
|
1917
|
+
if self.highlight_overlay is None and ((len(self.clicked_values['nodes']) + len(self.clicked_values['edges'])) > 0):
|
|
1918
|
+
if self.needs_mini:
|
|
1919
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
1920
|
+
self.needs_mini = False
|
|
1729
1921
|
|
|
1730
1922
|
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
1731
1923
|
|
|
@@ -2680,6 +2872,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2680
2872
|
load_action.triggered.connect(lambda checked, ch=i: self.load_channel(ch))
|
|
2681
2873
|
load_action = load_menu.addAction("Load Network")
|
|
2682
2874
|
load_action.triggered.connect(self.load_network)
|
|
2875
|
+
load_action = load_menu.addAction("Load From Excel Helper")
|
|
2876
|
+
load_action.triggered.connect(self.launch_excelotron)
|
|
2683
2877
|
misc_menu = load_menu.addMenu("Load Misc Properties")
|
|
2684
2878
|
load_action = misc_menu.addAction("Load Node IDs")
|
|
2685
2879
|
load_action.triggered.connect(lambda: self.load_misc('Node Identities'))
|
|
@@ -2696,10 +2890,16 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2696
2890
|
network_menu = analysis_menu.addMenu("Network")
|
|
2697
2891
|
netshow_action = network_menu.addAction("Show Network")
|
|
2698
2892
|
netshow_action.triggered.connect(self.show_netshow_dialog)
|
|
2699
|
-
|
|
2893
|
+
report_action = network_menu.addAction("Generic Network Report")
|
|
2894
|
+
report_action.triggered.connect(self.handle_report)
|
|
2895
|
+
partition_action = network_menu.addAction("Community Partition + Generic Community Stats")
|
|
2700
2896
|
partition_action.triggered.connect(self.show_partition_dialog)
|
|
2701
|
-
com_identity_action = network_menu.addAction("Identity Makeup of Network Communities (
|
|
2897
|
+
com_identity_action = network_menu.addAction("Identity Makeup of Network Communities (and UMAP)")
|
|
2702
2898
|
com_identity_action.triggered.connect(self.handle_com_id)
|
|
2899
|
+
com_neighbor_action = network_menu.addAction("Convert Network Communities into Neighborhoods?")
|
|
2900
|
+
com_neighbor_action.triggered.connect(self.handle_com_neighbor)
|
|
2901
|
+
com_cell_action = network_menu.addAction("Create Communities Based on Cuboidal Proximity Cells?")
|
|
2902
|
+
com_cell_action.triggered.connect(self.handle_com_cell)
|
|
2703
2903
|
stats_menu = analysis_menu.addMenu("Stats")
|
|
2704
2904
|
allstats_action = stats_menu.addAction("Calculate Generic Network Stats")
|
|
2705
2905
|
allstats_action.triggered.connect(self.stats)
|
|
@@ -2711,8 +2911,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2711
2911
|
degree_dist_action.triggered.connect(self.show_degree_dist_dialog)
|
|
2712
2912
|
neighbor_id_action = stats_menu.addAction("Identity Distribution of Neighbors")
|
|
2713
2913
|
neighbor_id_action.triggered.connect(self.show_neighbor_id_dialog)
|
|
2714
|
-
ripley_action = stats_menu.addAction("Clustering Analysis")
|
|
2914
|
+
ripley_action = stats_menu.addAction("Ripley Clustering Analysis")
|
|
2715
2915
|
ripley_action.triggered.connect(self.show_ripley_dialog)
|
|
2916
|
+
heatmap_action = stats_menu.addAction("Community Cluster Heatmap")
|
|
2917
|
+
heatmap_action.triggered.connect(self.show_heatmap_dialog)
|
|
2716
2918
|
vol_action = stats_menu.addAction("Calculate Volumes")
|
|
2717
2919
|
vol_action.triggered.connect(self.volumes)
|
|
2718
2920
|
rad_action = stats_menu.addAction("Calculate Radii")
|
|
@@ -2768,6 +2970,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2768
2970
|
thresh_action.triggered.connect(self.show_thresh_dialog)
|
|
2769
2971
|
mask_action = image_menu.addAction("Mask Channel")
|
|
2770
2972
|
mask_action.triggered.connect(self.show_mask_dialog)
|
|
2973
|
+
crop_action = image_menu.addAction("Crop Channels")
|
|
2974
|
+
crop_action.triggered.connect(self.show_crop_dialog)
|
|
2771
2975
|
type_action = image_menu.addAction("Channel dtype")
|
|
2772
2976
|
type_action.triggered.connect(self.show_type_dialog)
|
|
2773
2977
|
skeletonize_action = image_menu.addAction("Skeletonize")
|
|
@@ -3135,6 +3339,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3135
3339
|
dialog = MaskDialog(self)
|
|
3136
3340
|
dialog.exec()
|
|
3137
3341
|
|
|
3342
|
+
def show_crop_dialog(self):
|
|
3343
|
+
"""Show the crop dialog"""
|
|
3344
|
+
dialog = CropDialog(self)
|
|
3345
|
+
dialog.exec()
|
|
3346
|
+
|
|
3138
3347
|
def show_type_dialog(self):
|
|
3139
3348
|
"""Show the type dialog"""
|
|
3140
3349
|
try:
|
|
@@ -3553,6 +3762,103 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3553
3762
|
f"Failed to load network: {str(e)}"
|
|
3554
3763
|
)
|
|
3555
3764
|
|
|
3765
|
+
def launch_excelotron(self):
|
|
3766
|
+
"""Method to launch Excelotron - call this from a button or menu"""
|
|
3767
|
+
self.excel_manager.launch()
|
|
3768
|
+
|
|
3769
|
+
def close_excelotron(self):
|
|
3770
|
+
"""Method to close Excelotron"""
|
|
3771
|
+
self.excel_manager.close()
|
|
3772
|
+
|
|
3773
|
+
def handle_excel_data(self, data_dict, property_name):
|
|
3774
|
+
"""Handle data received from Excelotron"""
|
|
3775
|
+
print(f"Received data for property: {property_name}")
|
|
3776
|
+
print(f"Data keys: {list(data_dict.keys())}")
|
|
3777
|
+
|
|
3778
|
+
if property_name == 'Node Centroids':
|
|
3779
|
+
|
|
3780
|
+
try:
|
|
3781
|
+
|
|
3782
|
+
ys = data_dict['Y']
|
|
3783
|
+
xs = data_dict['X']
|
|
3784
|
+
if 'Numerical IDs' in data_dict:
|
|
3785
|
+
nodes = data_dict['Numerical IDs']
|
|
3786
|
+
else:
|
|
3787
|
+
nodes = np.arange(1, len(ys) + 1)
|
|
3788
|
+
|
|
3789
|
+
|
|
3790
|
+
if 'Z' in data_dict:
|
|
3791
|
+
zs = data_dict['Z']
|
|
3792
|
+
else:
|
|
3793
|
+
zs = np.zeros(len(ys))
|
|
3794
|
+
|
|
3795
|
+
centroids = {}
|
|
3796
|
+
|
|
3797
|
+
for i in range(len(nodes)):
|
|
3798
|
+
|
|
3799
|
+
centroids[nodes[i]] = [int(zs[i]), int(ys[i]), int(xs[i])]
|
|
3800
|
+
|
|
3801
|
+
my_network.node_centroids = centroids
|
|
3802
|
+
|
|
3803
|
+
self.format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
3804
|
+
|
|
3805
|
+
print("Centroids succesfully set")
|
|
3806
|
+
|
|
3807
|
+
except Exception as e:
|
|
3808
|
+
print(f"Error: {e}")
|
|
3809
|
+
|
|
3810
|
+
elif property_name == 'Node Identities':
|
|
3811
|
+
|
|
3812
|
+
try:
|
|
3813
|
+
|
|
3814
|
+
idens = data_dict['Identity Column']
|
|
3815
|
+
|
|
3816
|
+
if 'Numerical IDs' in data_dict:
|
|
3817
|
+
nodes = data_dict['Numerical IDs']
|
|
3818
|
+
else:
|
|
3819
|
+
nodes = np.arange(1, len(idens) + 1)
|
|
3820
|
+
|
|
3821
|
+
identities = {}
|
|
3822
|
+
|
|
3823
|
+
|
|
3824
|
+
for i in range(len(nodes)):
|
|
3825
|
+
|
|
3826
|
+
identities[nodes[i]] = str(idens[i])
|
|
3827
|
+
|
|
3828
|
+
my_network.node_identities = identities
|
|
3829
|
+
|
|
3830
|
+
self.format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', title = 'Node Identities')
|
|
3831
|
+
|
|
3832
|
+
print("Identities succesfully set")
|
|
3833
|
+
|
|
3834
|
+
except Exception as e:
|
|
3835
|
+
print(f"Error: {e}")
|
|
3836
|
+
|
|
3837
|
+
elif property_name == 'Node Communities':
|
|
3838
|
+
|
|
3839
|
+
try:
|
|
3840
|
+
|
|
3841
|
+
coms = data_dict['Community Identifier']
|
|
3842
|
+
|
|
3843
|
+
if 'Numerical IDs' in data_dict:
|
|
3844
|
+
nodes = data_dict['Numerical IDs']
|
|
3845
|
+
else:
|
|
3846
|
+
nodes = np.arange(1, len(coms) + 1)
|
|
3847
|
+
|
|
3848
|
+
communities = {}
|
|
3849
|
+
|
|
3850
|
+
for i in range(len(nodes)):
|
|
3851
|
+
|
|
3852
|
+
communities[nodes[i]] = [str(coms[i])]
|
|
3853
|
+
|
|
3854
|
+
my_network.communities = communities
|
|
3855
|
+
|
|
3856
|
+
self.format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID', title = 'Community Partition')
|
|
3857
|
+
|
|
3858
|
+
print("Communities succesfully set")
|
|
3859
|
+
|
|
3860
|
+
except Exception as e:
|
|
3861
|
+
print(f"Error: {e}")
|
|
3556
3862
|
|
|
3557
3863
|
|
|
3558
3864
|
def set_active_channel(self, index):
|
|
@@ -3783,6 +4089,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3783
4089
|
except:
|
|
3784
4090
|
pass
|
|
3785
4091
|
|
|
4092
|
+
self.shape = self.channel_data[channel_index].shape
|
|
4093
|
+
|
|
3786
4094
|
self.update_display(reset_resize = reset_resize)
|
|
3787
4095
|
|
|
3788
4096
|
|
|
@@ -4278,24 +4586,62 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4278
4586
|
dialog = NetShowDialog(self)
|
|
4279
4587
|
dialog.exec()
|
|
4280
4588
|
|
|
4589
|
+
def handle_report(self):
|
|
4590
|
+
|
|
4591
|
+
def invert_dict(d):
|
|
4592
|
+
inverted = {}
|
|
4593
|
+
for key, value in d.items():
|
|
4594
|
+
inverted.setdefault(value, []).append(key)
|
|
4595
|
+
return inverted
|
|
4596
|
+
|
|
4597
|
+
stats = {}
|
|
4598
|
+
|
|
4599
|
+
try:
|
|
4600
|
+
# Basic graph properties
|
|
4601
|
+
stats['num_nodes'] = my_network.network.number_of_nodes()
|
|
4602
|
+
stats['num_edges'] = my_network.network.number_of_edges()
|
|
4603
|
+
except:
|
|
4604
|
+
pass
|
|
4605
|
+
|
|
4606
|
+
try:
|
|
4607
|
+
idens = invert_dict(my_network.node_identities)
|
|
4608
|
+
|
|
4609
|
+
for iden, nodes in idens.items():
|
|
4610
|
+
stats[f'num_nodes_{iden}'] = len(nodes)
|
|
4611
|
+
except:
|
|
4612
|
+
pass
|
|
4613
|
+
|
|
4614
|
+
try:
|
|
4615
|
+
|
|
4616
|
+
coms = invert_dict(my_network.communities)
|
|
4617
|
+
|
|
4618
|
+
for com, nodes in coms.items():
|
|
4619
|
+
stats[f'num_nodes_community_{com}'] = len(nodes)
|
|
4620
|
+
except:
|
|
4621
|
+
pass
|
|
4622
|
+
|
|
4623
|
+
self.format_for_upperright_table(stats, title = 'Network Report')
|
|
4624
|
+
|
|
4625
|
+
|
|
4626
|
+
|
|
4281
4627
|
def show_partition_dialog(self):
|
|
4282
4628
|
dialog = PartitionDialog(self)
|
|
4283
4629
|
dialog.exec()
|
|
4284
4630
|
|
|
4285
4631
|
def handle_com_id(self):
|
|
4286
|
-
if my_network.node_identities is None:
|
|
4287
|
-
print("Node identities must be set")
|
|
4288
4632
|
|
|
4289
|
-
|
|
4290
|
-
|
|
4633
|
+
dialog = ComIdDialog(self)
|
|
4634
|
+
dialog.exec()
|
|
4291
4635
|
|
|
4292
|
-
|
|
4293
|
-
return
|
|
4636
|
+
def handle_com_neighbor(self):
|
|
4294
4637
|
|
|
4295
|
-
|
|
4638
|
+
dialog = ComNeighborDialog(self)
|
|
4639
|
+
dialog.exec()
|
|
4296
4640
|
|
|
4297
|
-
|
|
4641
|
+
def handle_com_cell(self):
|
|
4298
4642
|
|
|
4643
|
+
dialog = ComCellDialog(self)
|
|
4644
|
+
dialog.exec()
|
|
4299
4645
|
|
|
4300
4646
|
def show_radial_dialog(self):
|
|
4301
4647
|
dialog = RadialDialog(self)
|
|
@@ -4313,6 +4659,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4313
4659
|
dialog = RipleyDialog(self)
|
|
4314
4660
|
dialog.exec()
|
|
4315
4661
|
|
|
4662
|
+
def show_heatmap_dialog(self):
|
|
4663
|
+
dialog = HeatmapDialog(self)
|
|
4664
|
+
dialog.exec()
|
|
4665
|
+
|
|
4316
4666
|
def show_random_dialog(self):
|
|
4317
4667
|
dialog = RandomDialog(self)
|
|
4318
4668
|
dialog.exec()
|
|
@@ -4346,6 +4696,21 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4346
4696
|
dialog = CodeDialog(self, sort = sort)
|
|
4347
4697
|
dialog.exec()
|
|
4348
4698
|
|
|
4699
|
+
def closeEvent(self, event):
|
|
4700
|
+
"""Override closeEvent to close all windows when main window closes"""
|
|
4701
|
+
|
|
4702
|
+
# Close all Qt windows
|
|
4703
|
+
QApplication.closeAllWindows()
|
|
4704
|
+
|
|
4705
|
+
# Close all matplotlib figures
|
|
4706
|
+
plt.close('all')
|
|
4707
|
+
|
|
4708
|
+
# Accept the close event
|
|
4709
|
+
event.accept()
|
|
4710
|
+
|
|
4711
|
+
# Force quit the application
|
|
4712
|
+
QCoreApplication.quit()
|
|
4713
|
+
|
|
4349
4714
|
|
|
4350
4715
|
|
|
4351
4716
|
#TABLE RELATED:
|
|
@@ -5527,8 +5892,7 @@ class ArbitraryDialog(QDialog):
|
|
|
5527
5892
|
|
|
5528
5893
|
except Exception as e:
|
|
5529
5894
|
QMessageBox.critical(self, "Error", f"Error processing selections: {str(e)}")
|
|
5530
|
-
|
|
5531
|
-
print(traceback.format_exc())
|
|
5895
|
+
|
|
5532
5896
|
|
|
5533
5897
|
class Show3dDialog(QDialog):
|
|
5534
5898
|
def __init__(self, parent=None):
|
|
@@ -5542,10 +5906,15 @@ class Show3dDialog(QDialog):
|
|
|
5542
5906
|
layout.addRow("Downsample Factor (Optional to speed up display):", self.downsample)
|
|
5543
5907
|
|
|
5544
5908
|
# Network Overlay checkbox (default True)
|
|
5545
|
-
self.cubic = QPushButton("
|
|
5909
|
+
self.cubic = QPushButton("Cubic")
|
|
5546
5910
|
self.cubic.setCheckable(True)
|
|
5547
5911
|
self.cubic.setChecked(False)
|
|
5548
5912
|
layout.addRow("Use cubic downsample (Slower but preserves shape better potentially)?", self.cubic)
|
|
5913
|
+
|
|
5914
|
+
self.box = QPushButton("Box")
|
|
5915
|
+
self.box.setCheckable(True)
|
|
5916
|
+
self.box.setChecked(False)
|
|
5917
|
+
layout.addRow("Include bounding box?", self.box)
|
|
5549
5918
|
|
|
5550
5919
|
# Add Run button
|
|
5551
5920
|
run_button = QPushButton("Show 3D")
|
|
@@ -5564,6 +5933,7 @@ class Show3dDialog(QDialog):
|
|
|
5564
5933
|
downsample = None
|
|
5565
5934
|
|
|
5566
5935
|
cubic = self.cubic.isChecked()
|
|
5936
|
+
box = self.box.isChecked()
|
|
5567
5937
|
|
|
5568
5938
|
if cubic:
|
|
5569
5939
|
order = 3
|
|
@@ -5596,7 +5966,7 @@ class Show3dDialog(QDialog):
|
|
|
5596
5966
|
arrays_3d.append(self.parent().highlight_overlay)
|
|
5597
5967
|
colors.append(color_template[4])
|
|
5598
5968
|
|
|
5599
|
-
n3d.show_3d(arrays_3d, arrays_4d, down_factor = downsample, order = order, xy_scale = my_network.xy_scale, z_scale = my_network.z_scale, colors = colors)
|
|
5969
|
+
n3d.show_3d(arrays_3d, arrays_4d, down_factor = downsample, order = order, xy_scale = my_network.xy_scale, z_scale = my_network.z_scale, colors = colors, box = box)
|
|
5600
5970
|
|
|
5601
5971
|
self.accept()
|
|
5602
5972
|
|
|
@@ -5972,7 +6342,7 @@ class PartitionDialog(QDialog):
|
|
|
5972
6342
|
dostats = self.stats.isChecked()
|
|
5973
6343
|
|
|
5974
6344
|
try:
|
|
5975
|
-
seed = int(self.seed.text()) if self.seed.text() else
|
|
6345
|
+
seed = int(self.seed.text()) if self.seed.text() else 42
|
|
5976
6346
|
except:
|
|
5977
6347
|
seed = None
|
|
5978
6348
|
|
|
@@ -5993,134 +6363,320 @@ class PartitionDialog(QDialog):
|
|
|
5993
6363
|
except Exception as e:
|
|
5994
6364
|
print(f"Error creating communities: {e}")
|
|
5995
6365
|
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
class RadialDialog(QDialog):
|
|
6366
|
+
class ComIdDialog(QDialog):
|
|
5999
6367
|
|
|
6000
6368
|
def __init__(self, parent=None):
|
|
6001
6369
|
|
|
6002
6370
|
super().__init__(parent)
|
|
6003
|
-
self.setWindowTitle("
|
|
6371
|
+
self.setWindowTitle("Select Mode")
|
|
6004
6372
|
self.setModal(True)
|
|
6005
6373
|
|
|
6006
6374
|
layout = QFormLayout(self)
|
|
6007
6375
|
|
|
6008
|
-
self.
|
|
6009
|
-
|
|
6376
|
+
self.mode = QComboBox()
|
|
6377
|
+
self.mode.addItems(["Average Identities Per Community", "Weighted Average Identity of All Communities", ])
|
|
6378
|
+
self.mode.setCurrentIndex(0)
|
|
6379
|
+
layout.addRow("Mode", self.mode)
|
|
6380
|
+
|
|
6381
|
+
# umap checkbox (default True)
|
|
6382
|
+
self.umap = QPushButton("UMAP")
|
|
6383
|
+
self.umap.setCheckable(True)
|
|
6384
|
+
self.umap.setChecked(True)
|
|
6385
|
+
layout.addRow("Generate UMAP?:", self.umap)
|
|
6386
|
+
|
|
6387
|
+
# weighted checkbox (default True)
|
|
6388
|
+
self.label = QPushButton("Label")
|
|
6389
|
+
self.label.setCheckable(True)
|
|
6390
|
+
self.label.setChecked(False)
|
|
6391
|
+
layout.addRow("If using above - label UMAP points?:", self.label)
|
|
6010
6392
|
|
|
6011
|
-
self.directory = QLineEdit("")
|
|
6012
|
-
layout.addRow("Output Directory:", self.directory)
|
|
6013
6393
|
|
|
6014
6394
|
# Add Run button
|
|
6015
|
-
run_button = QPushButton("Get
|
|
6016
|
-
run_button.clicked.connect(self.
|
|
6395
|
+
run_button = QPushButton("Get Community ID Info")
|
|
6396
|
+
run_button.clicked.connect(self.run)
|
|
6017
6397
|
layout.addWidget(run_button)
|
|
6018
6398
|
|
|
6019
|
-
def
|
|
6399
|
+
def run(self):
|
|
6020
6400
|
|
|
6021
6401
|
try:
|
|
6022
6402
|
|
|
6023
|
-
|
|
6403
|
+
if my_network.node_identities is None:
|
|
6404
|
+
print("Node identities must be set")
|
|
6024
6405
|
|
|
6025
|
-
|
|
6406
|
+
if my_network.communities is None:
|
|
6407
|
+
self.parent().show_partition_dialog()
|
|
6026
6408
|
|
|
6027
|
-
|
|
6028
|
-
|
|
6409
|
+
if my_network.communities is None:
|
|
6410
|
+
return
|
|
6029
6411
|
|
|
6030
|
-
|
|
6412
|
+
mode = self.mode.currentIndex()
|
|
6031
6413
|
|
|
6032
|
-
self.
|
|
6414
|
+
umap = self.umap.isChecked()
|
|
6415
|
+
label = self.label.isChecked()
|
|
6416
|
+
|
|
6417
|
+
if mode == 1:
|
|
6418
|
+
|
|
6419
|
+
info = my_network.community_id_info()
|
|
6420
|
+
|
|
6421
|
+
self.parent().format_for_upperright_table(info, 'Node Identity Type', 'Weighted Proportion in Communities', 'Weighted Average of Community Makeup')
|
|
6422
|
+
|
|
6423
|
+
else:
|
|
6424
|
+
|
|
6425
|
+
info, names = my_network.community_id_info_per_com(umap = umap, label = label)
|
|
6426
|
+
|
|
6427
|
+
self.parent().format_for_upperright_table(info, 'Community', names, 'Average of Community Makeup')
|
|
6033
6428
|
|
|
6034
6429
|
self.accept()
|
|
6035
6430
|
|
|
6036
6431
|
except Exception as e:
|
|
6037
|
-
print(f"An error occurred: {e}")
|
|
6038
6432
|
|
|
6039
|
-
|
|
6433
|
+
print(f"Error: {e}")
|
|
6434
|
+
|
|
6435
|
+
|
|
6436
|
+
|
|
6437
|
+
class ComNeighborDialog(QDialog):
|
|
6040
6438
|
|
|
6041
6439
|
def __init__(self, parent=None):
|
|
6042
6440
|
|
|
6043
6441
|
super().__init__(parent)
|
|
6044
|
-
self.setWindowTitle("
|
|
6442
|
+
self.setWindowTitle("Reassign Communities Based on Identity Similarity?")
|
|
6045
6443
|
self.setModal(True)
|
|
6046
6444
|
|
|
6047
6445
|
layout = QFormLayout(self)
|
|
6048
6446
|
|
|
6049
|
-
self.
|
|
6050
|
-
layout.addRow("
|
|
6447
|
+
self.neighborcount = QLineEdit("5")
|
|
6448
|
+
layout.addRow("Num Neighborhoods:", self.neighborcount)
|
|
6449
|
+
|
|
6450
|
+
self.seed = QLineEdit("")
|
|
6451
|
+
layout.addRow("Clustering Seed:", self.seed)
|
|
6452
|
+
|
|
6453
|
+
self.limit = QLineEdit("")
|
|
6454
|
+
layout.addRow("Min Community Size to be grouped (Smaller communities will be placed in neighborhood 0 - does not apply if empty)", self.limit)
|
|
6051
6455
|
|
|
6052
6456
|
# Add Run button
|
|
6053
|
-
run_button = QPushButton("Get
|
|
6054
|
-
run_button.clicked.connect(self.
|
|
6457
|
+
run_button = QPushButton("Get Communities")
|
|
6458
|
+
run_button.clicked.connect(self.run)
|
|
6055
6459
|
layout.addWidget(run_button)
|
|
6056
6460
|
|
|
6057
|
-
def
|
|
6461
|
+
def run(self):
|
|
6058
6462
|
|
|
6059
6463
|
try:
|
|
6060
6464
|
|
|
6061
|
-
|
|
6465
|
+
if my_network.node_identities is None:
|
|
6466
|
+
print("Node identities must be set")
|
|
6062
6467
|
|
|
6063
|
-
|
|
6468
|
+
if my_network.communities is None:
|
|
6469
|
+
self.parent().show_partition_dialog()
|
|
6470
|
+
|
|
6471
|
+
if my_network.communities is None:
|
|
6472
|
+
return
|
|
6064
6473
|
|
|
6474
|
+
seed = float(self.seed.text()) if self.seed.text().strip() else 42
|
|
6065
6475
|
|
|
6066
|
-
self.
|
|
6476
|
+
limit = int(self.limit.text()) if self.limit.text().strip() else None
|
|
6477
|
+
|
|
6478
|
+
|
|
6479
|
+
neighborcount = int(self.neighborcount.text()) if self.neighborcount.text().strip() else 5
|
|
6480
|
+
|
|
6481
|
+
if self.parent().prev_coms is None:
|
|
6482
|
+
|
|
6483
|
+
self.parent().prev_coms = copy.deepcopy(my_network.communities)
|
|
6484
|
+
my_network.assign_neighborhoods(seed, neighborcount, limit = limit)
|
|
6485
|
+
else:
|
|
6486
|
+
my_network.assign_neighborhoods(seed, neighborcount, limit = limit, prev_coms = self.parent().prev_coms)
|
|
6487
|
+
|
|
6488
|
+
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'NeighborhoodID', title = 'Neighborhood Partition')
|
|
6489
|
+
|
|
6490
|
+
print("Neighborhoods have been assigned to communities based on similarity")
|
|
6067
6491
|
|
|
6068
6492
|
self.accept()
|
|
6069
6493
|
|
|
6070
6494
|
except Exception as e:
|
|
6071
|
-
print(f"An error occurred: {e}")
|
|
6072
6495
|
|
|
6073
|
-
|
|
6496
|
+
print(f"Error assigning neighborhoods: {e}")
|
|
6497
|
+
|
|
6498
|
+
class ComCellDialog(QDialog):
|
|
6074
6499
|
|
|
6075
6500
|
def __init__(self, parent=None):
|
|
6076
6501
|
|
|
6077
6502
|
super().__init__(parent)
|
|
6078
|
-
self.setWindowTitle(
|
|
6503
|
+
self.setWindowTitle("Assign Communities Based on Proximity Within Cuboidal Cells?")
|
|
6079
6504
|
self.setModal(True)
|
|
6080
6505
|
|
|
6081
6506
|
layout = QFormLayout(self)
|
|
6082
6507
|
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
self.root.addItems(list(set(my_network.node_identities.values())))
|
|
6086
|
-
self.root.setCurrentIndex(0)
|
|
6087
|
-
layout.addRow("Root Identity to Search for Neighbor's IDs (search uses nodes of this ID, finds what IDs they connect to", self.root)
|
|
6088
|
-
else:
|
|
6089
|
-
self.root = None
|
|
6090
|
-
|
|
6091
|
-
self.directory = QLineEdit("")
|
|
6092
|
-
layout.addRow("Output Directory:", self.directory)
|
|
6093
|
-
|
|
6094
|
-
self.mode = QComboBox()
|
|
6095
|
-
self.mode.addItems(["From Network - Based on Absolute Connectivity", "Use Labeled Nodes - Based on Morphological Neighborhood Densities"])
|
|
6096
|
-
self.mode.setCurrentIndex(0)
|
|
6097
|
-
layout.addRow("Mode", self.mode)
|
|
6508
|
+
self.size = QLineEdit("")
|
|
6509
|
+
layout.addRow("Cell Size:", self.size)
|
|
6098
6510
|
|
|
6099
|
-
self.
|
|
6100
|
-
layout.addRow("
|
|
6511
|
+
self.xy_scale = QLineEdit(f"{my_network.xy_scale}")
|
|
6512
|
+
layout.addRow("xy scale:", self.xy_scale)
|
|
6101
6513
|
|
|
6102
|
-
self.
|
|
6103
|
-
self.
|
|
6104
|
-
self.fastdil.setChecked(False)
|
|
6105
|
-
layout.addRow("(If not using network) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
6514
|
+
self.z_scale = QLineEdit(f"{my_network.z_scale}")
|
|
6515
|
+
layout.addRow("z scale:", self.z_scale)
|
|
6106
6516
|
|
|
6107
6517
|
# Add Run button
|
|
6108
|
-
run_button = QPushButton("Get
|
|
6109
|
-
run_button.clicked.connect(self.
|
|
6518
|
+
run_button = QPushButton("Get Neighborhoods (Note this overwrites current communities - save your coms first)")
|
|
6519
|
+
run_button.clicked.connect(self.run)
|
|
6110
6520
|
layout.addWidget(run_button)
|
|
6111
6521
|
|
|
6112
|
-
def
|
|
6522
|
+
def run(self):
|
|
6113
6523
|
|
|
6114
6524
|
try:
|
|
6115
6525
|
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
pass
|
|
6526
|
+
size = float(self.size.text()) if self.size.text().strip() else None
|
|
6527
|
+
xy_scale = float(self.xy_scale.text()) if self.xy_scale.text().strip() else 1
|
|
6528
|
+
z_scale = float(self.z_scale.text()) if self.z_scale.text().strip() else 1
|
|
6120
6529
|
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6530
|
+
if size is None:
|
|
6531
|
+
return
|
|
6532
|
+
|
|
6533
|
+
if my_network.node_centroids is None:
|
|
6534
|
+
self.parent().show_centroid_dialog()
|
|
6535
|
+
if my_network.node_centroids is None:
|
|
6536
|
+
return
|
|
6537
|
+
|
|
6538
|
+
my_network.community_cells(size = size, xy_scale = xy_scale, z_scale = z_scale)
|
|
6539
|
+
|
|
6540
|
+
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID')
|
|
6541
|
+
|
|
6542
|
+
self.accept()
|
|
6543
|
+
|
|
6544
|
+
except Exception as e:
|
|
6545
|
+
|
|
6546
|
+
print(f"Error: {e}")
|
|
6547
|
+
|
|
6548
|
+
|
|
6549
|
+
|
|
6550
|
+
|
|
6551
|
+
|
|
6552
|
+
|
|
6553
|
+
|
|
6554
|
+
class RadialDialog(QDialog):
|
|
6555
|
+
|
|
6556
|
+
def __init__(self, parent=None):
|
|
6557
|
+
|
|
6558
|
+
super().__init__(parent)
|
|
6559
|
+
self.setWindowTitle("Radial Parameters")
|
|
6560
|
+
self.setModal(True)
|
|
6561
|
+
|
|
6562
|
+
layout = QFormLayout(self)
|
|
6563
|
+
|
|
6564
|
+
self.distance = QLineEdit("50")
|
|
6565
|
+
layout.addRow("Bucket Distance for Searching For Node Neighbors (automatically scaled by xy and z scales):", self.distance)
|
|
6566
|
+
|
|
6567
|
+
self.directory = QLineEdit("")
|
|
6568
|
+
layout.addRow("Output Directory:", self.directory)
|
|
6569
|
+
|
|
6570
|
+
# Add Run button
|
|
6571
|
+
run_button = QPushButton("Get Radial Distribution")
|
|
6572
|
+
run_button.clicked.connect(self.radial)
|
|
6573
|
+
layout.addWidget(run_button)
|
|
6574
|
+
|
|
6575
|
+
def radial(self):
|
|
6576
|
+
|
|
6577
|
+
try:
|
|
6578
|
+
|
|
6579
|
+
distance = float(self.distance.text()) if self.distance.text().strip() else 50
|
|
6580
|
+
|
|
6581
|
+
directory = str(self.distance.text()) if self.directory.text().strip() else None
|
|
6582
|
+
|
|
6583
|
+
if my_network.node_centroids is None:
|
|
6584
|
+
self.parent().show_centroid_dialog()
|
|
6585
|
+
|
|
6586
|
+
radial = my_network.radial_distribution(distance, directory = directory)
|
|
6587
|
+
|
|
6588
|
+
self.parent().format_for_upperright_table(radial, 'Radial Distance From Any Node', 'Average Number of Neighboring Nodes', title = 'Radial Distribution Analysis')
|
|
6589
|
+
|
|
6590
|
+
self.accept()
|
|
6591
|
+
|
|
6592
|
+
except Exception as e:
|
|
6593
|
+
print(f"An error occurred: {e}")
|
|
6594
|
+
|
|
6595
|
+
class DegreeDistDialog(QDialog):
|
|
6596
|
+
|
|
6597
|
+
def __init__(self, parent=None):
|
|
6598
|
+
|
|
6599
|
+
super().__init__(parent)
|
|
6600
|
+
self.setWindowTitle("Degree Distribution Parameters")
|
|
6601
|
+
self.setModal(True)
|
|
6602
|
+
|
|
6603
|
+
layout = QFormLayout(self)
|
|
6604
|
+
|
|
6605
|
+
self.directory = QLineEdit("")
|
|
6606
|
+
layout.addRow("Output Directory:", self.directory)
|
|
6607
|
+
|
|
6608
|
+
# Add Run button
|
|
6609
|
+
run_button = QPushButton("Get Degree Distribution")
|
|
6610
|
+
run_button.clicked.connect(self.degreedist)
|
|
6611
|
+
layout.addWidget(run_button)
|
|
6612
|
+
|
|
6613
|
+
def degreedist(self):
|
|
6614
|
+
|
|
6615
|
+
try:
|
|
6616
|
+
|
|
6617
|
+
directory = str(self.distance.text()) if self.directory.text().strip() else None
|
|
6618
|
+
|
|
6619
|
+
degrees = my_network.degree_distribution(directory = directory)
|
|
6620
|
+
|
|
6621
|
+
|
|
6622
|
+
self.parent().format_for_upperright_table(degrees, 'Degree (k)', 'Proportion of nodes with degree (p(k))', title = 'Degree Distribution Analysis')
|
|
6623
|
+
|
|
6624
|
+
self.accept()
|
|
6625
|
+
|
|
6626
|
+
except Exception as e:
|
|
6627
|
+
print(f"An error occurred: {e}")
|
|
6628
|
+
|
|
6629
|
+
class NeighborIdentityDialog(QDialog):
|
|
6630
|
+
|
|
6631
|
+
def __init__(self, parent=None):
|
|
6632
|
+
|
|
6633
|
+
super().__init__(parent)
|
|
6634
|
+
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)")
|
|
6635
|
+
self.setModal(True)
|
|
6636
|
+
|
|
6637
|
+
layout = QFormLayout(self)
|
|
6638
|
+
|
|
6639
|
+
if my_network.node_identities is not None:
|
|
6640
|
+
self.root = QComboBox()
|
|
6641
|
+
self.root.addItems(list(set(my_network.node_identities.values())))
|
|
6642
|
+
self.root.setCurrentIndex(0)
|
|
6643
|
+
layout.addRow("Root Identity to Search for Neighbor's IDs (search uses nodes of this ID, finds what IDs they connect to", self.root)
|
|
6644
|
+
else:
|
|
6645
|
+
self.root = None
|
|
6646
|
+
|
|
6647
|
+
self.directory = QLineEdit("")
|
|
6648
|
+
layout.addRow("Output Directory:", self.directory)
|
|
6649
|
+
|
|
6650
|
+
self.mode = QComboBox()
|
|
6651
|
+
self.mode.addItems(["From Network - Based on Absolute Connectivity", "Use Labeled Nodes - Based on Morphological Neighborhood Densities"])
|
|
6652
|
+
self.mode.setCurrentIndex(0)
|
|
6653
|
+
layout.addRow("Mode", self.mode)
|
|
6654
|
+
|
|
6655
|
+
self.search = QLineEdit("")
|
|
6656
|
+
layout.addRow("Search Radius (Ignore if using network):", self.search)
|
|
6657
|
+
|
|
6658
|
+
self.fastdil = QPushButton("Fast Dilate")
|
|
6659
|
+
self.fastdil.setCheckable(True)
|
|
6660
|
+
self.fastdil.setChecked(False)
|
|
6661
|
+
layout.addRow("(If not using network) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
6662
|
+
|
|
6663
|
+
# Add Run button
|
|
6664
|
+
run_button = QPushButton("Get Neighborhood Identity Distribution")
|
|
6665
|
+
run_button.clicked.connect(self.neighborids)
|
|
6666
|
+
layout.addWidget(run_button)
|
|
6667
|
+
|
|
6668
|
+
def neighborids(self):
|
|
6669
|
+
|
|
6670
|
+
try:
|
|
6671
|
+
|
|
6672
|
+
try:
|
|
6673
|
+
root = self.root.currentText()
|
|
6674
|
+
except:
|
|
6675
|
+
pass
|
|
6676
|
+
|
|
6677
|
+
directory = self.directory.text() if self.directory.text().strip() else None
|
|
6678
|
+
|
|
6679
|
+
mode = self.mode.currentIndex()
|
|
6124
6680
|
|
|
6125
6681
|
search = float(self.search.text()) if self.search.text().strip() else 0
|
|
6126
6682
|
|
|
@@ -6260,6 +6816,60 @@ class RipleyDialog(QDialog):
|
|
|
6260
6816
|
print(traceback.format_exc())
|
|
6261
6817
|
print(f"Error: {e}")
|
|
6262
6818
|
|
|
6819
|
+
class HeatmapDialog(QDialog):
|
|
6820
|
+
|
|
6821
|
+
def __init__(self, parent = None):
|
|
6822
|
+
|
|
6823
|
+
super().__init__(parent)
|
|
6824
|
+
self.setWindowTitle("Heatmap Parameters")
|
|
6825
|
+
self.setModal(True)
|
|
6826
|
+
|
|
6827
|
+
layout = QFormLayout(self)
|
|
6828
|
+
|
|
6829
|
+
self.nodecount = QLineEdit("")
|
|
6830
|
+
layout.addRow("(Optional) Total Number of Nodes?:", self.nodecount)
|
|
6831
|
+
|
|
6832
|
+
|
|
6833
|
+
# stats checkbox (default True)
|
|
6834
|
+
self.is3d = QPushButton("3D")
|
|
6835
|
+
self.is3d.setCheckable(True)
|
|
6836
|
+
self.is3d.setChecked(True)
|
|
6837
|
+
layout.addRow("Use 3D Plot (uncheck for 2D)?:", self.is3d)
|
|
6838
|
+
|
|
6839
|
+
|
|
6840
|
+
# Add Run button
|
|
6841
|
+
run_button = QPushButton("Run")
|
|
6842
|
+
run_button.clicked.connect(self.run)
|
|
6843
|
+
layout.addWidget(run_button)
|
|
6844
|
+
|
|
6845
|
+
def run(self):
|
|
6846
|
+
|
|
6847
|
+
nodecount = int(self.nodecount.text()) if self.nodecount.text().strip() else None
|
|
6848
|
+
|
|
6849
|
+
is3d = self.is3d.isChecked()
|
|
6850
|
+
|
|
6851
|
+
|
|
6852
|
+
if my_network.communities is None:
|
|
6853
|
+
if my_network.network is not None:
|
|
6854
|
+
self.parent().show_partition_dialog()
|
|
6855
|
+
else:
|
|
6856
|
+
self.parent().handle_com_cell()
|
|
6857
|
+
if my_network.communities is None:
|
|
6858
|
+
return
|
|
6859
|
+
|
|
6860
|
+
heat_dict = my_network.community_heatmap(num_nodes = nodecount, is3d = is3d)
|
|
6861
|
+
|
|
6862
|
+
self.parent().format_for_upperright_table(heat_dict, metric='Community', value='ln(Predicted Community Nodecount/Actual)', title="Community Heatmap")
|
|
6863
|
+
|
|
6864
|
+
self.accept()
|
|
6865
|
+
|
|
6866
|
+
|
|
6867
|
+
|
|
6868
|
+
|
|
6869
|
+
|
|
6870
|
+
|
|
6871
|
+
|
|
6872
|
+
|
|
6263
6873
|
class RandomDialog(QDialog):
|
|
6264
6874
|
|
|
6265
6875
|
def __init__(self, parent=None):
|
|
@@ -7316,7 +7926,7 @@ class SLabelDialog(QDialog):
|
|
|
7316
7926
|
try:
|
|
7317
7927
|
|
|
7318
7928
|
# Update both the display data and the network object
|
|
7319
|
-
binary_array = sdl.smart_label(binary_array, label_array, directory = None, GPU = GPU, predownsample = down_factor)
|
|
7929
|
+
binary_array = sdl.smart_label(binary_array, label_array, directory = None, GPU = GPU, predownsample = down_factor, remove_template = True)
|
|
7320
7930
|
|
|
7321
7931
|
label_array = sdl.invert_array(label_array)
|
|
7322
7932
|
|
|
@@ -7433,12 +8043,101 @@ class ThresholdDialog(QDialog):
|
|
|
7433
8043
|
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
7434
8044
|
return msg.exec() == QMessageBox.StandardButton.Yes
|
|
7435
8045
|
|
|
8046
|
+
class ExcelotronManager(QObject):
|
|
8047
|
+
# Signal to emit when data is received from Excelotron
|
|
8048
|
+
data_received = pyqtSignal(dict, str) # dictionary, property_name
|
|
8049
|
+
|
|
8050
|
+
def __init__(self, parent=None):
|
|
8051
|
+
super().__init__(parent)
|
|
8052
|
+
self.excelotron_window = None
|
|
8053
|
+
self.last_data = None
|
|
8054
|
+
self.last_property = None
|
|
8055
|
+
|
|
8056
|
+
def launch(self):
|
|
8057
|
+
"""Launch the Excelotron window"""
|
|
8058
|
+
|
|
8059
|
+
if self.excelotron_window is None:
|
|
8060
|
+
ExcelGUIClass = excelotron.main(standalone=False)
|
|
8061
|
+
self.excelotron_window = ExcelGUIClass()
|
|
8062
|
+
self.excelotron_window.data_exported.connect(self._on_data_exported)
|
|
8063
|
+
# Connect to both close event and destroyed signal
|
|
8064
|
+
self.excelotron_window.destroyed.connect(self._on_window_destroyed)
|
|
8065
|
+
self.excelotron_window.closeEvent = self._create_close_handler(self.excelotron_window.closeEvent)
|
|
8066
|
+
self.excelotron_window.show()
|
|
8067
|
+
else:
|
|
8068
|
+
self.excelotron_window.raise_()
|
|
8069
|
+
self.excelotron_window.activateWindow()
|
|
8070
|
+
|
|
8071
|
+
def _create_close_handler(self, original_close_event):
|
|
8072
|
+
"""Create a close event handler that cleans up properly"""
|
|
8073
|
+
def close_handler(event):
|
|
8074
|
+
self._cleanup_window()
|
|
8075
|
+
original_close_event(event)
|
|
8076
|
+
return close_handler
|
|
8077
|
+
|
|
8078
|
+
def close(self):
|
|
8079
|
+
"""Close the Excelotron window"""
|
|
8080
|
+
if self.excelotron_window is not None:
|
|
8081
|
+
self.excelotron_window.close()
|
|
8082
|
+
self._cleanup_window()
|
|
8083
|
+
|
|
8084
|
+
def _cleanup_window(self):
|
|
8085
|
+
"""Properly cleanup the window reference"""
|
|
8086
|
+
if self.excelotron_window is not None:
|
|
8087
|
+
try:
|
|
8088
|
+
# Disconnect all signals to prevent issues
|
|
8089
|
+
self.excelotron_window.data_exported.disconnect()
|
|
8090
|
+
self.excelotron_window.destroyed.disconnect()
|
|
8091
|
+
except:
|
|
8092
|
+
pass # Ignore if already disconnected
|
|
8093
|
+
|
|
8094
|
+
# Schedule for deletion
|
|
8095
|
+
self.excelotron_window.deleteLater()
|
|
8096
|
+
self.excelotron_window = None
|
|
8097
|
+
|
|
8098
|
+
def is_open(self):
|
|
8099
|
+
"""Check if Excelotron window is open"""
|
|
8100
|
+
is_open = self.excelotron_window is not None
|
|
8101
|
+
return is_open
|
|
8102
|
+
|
|
8103
|
+
def _on_data_exported(self, data_dict, property_name):
|
|
8104
|
+
"""Internal slot to handle data from Excelotron"""
|
|
8105
|
+
self.last_data = data_dict
|
|
8106
|
+
self.last_property = property_name
|
|
8107
|
+
# Re-emit the signal for parent to handle
|
|
8108
|
+
self.data_received.emit(data_dict, property_name)
|
|
8109
|
+
|
|
8110
|
+
def _on_window_destroyed(self):
|
|
8111
|
+
"""Handle when the Excelotron window is destroyed/closed"""
|
|
8112
|
+
self.excelotron_window = None
|
|
8113
|
+
|
|
8114
|
+
def get_last_data(self):
|
|
8115
|
+
"""Get the last exported data"""
|
|
8116
|
+
return self.last_data, self.last_property
|
|
7436
8117
|
|
|
7437
8118
|
class MachineWindow(QMainWindow):
|
|
7438
8119
|
|
|
7439
8120
|
def __init__(self, parent=None, GPU = False):
|
|
7440
8121
|
super().__init__(parent)
|
|
7441
8122
|
|
|
8123
|
+
if self.parent().active_channel == 0:
|
|
8124
|
+
if self.parent().channel_data[0] is not None:
|
|
8125
|
+
try:
|
|
8126
|
+
active_data = self.parent().channel_data[0]
|
|
8127
|
+
act_channel = 0
|
|
8128
|
+
except:
|
|
8129
|
+
active_data = self.parent().channel_data[1]
|
|
8130
|
+
act_channel = 1
|
|
8131
|
+
else:
|
|
8132
|
+
active_data = self.parent().channel_data[1]
|
|
8133
|
+
act_channel = 1
|
|
8134
|
+
|
|
8135
|
+
try:
|
|
8136
|
+
array1 = np.zeros_like(active_data).astype(np.uint8)
|
|
8137
|
+
except:
|
|
8138
|
+
print("No data in nodes channel")
|
|
8139
|
+
return
|
|
8140
|
+
|
|
7442
8141
|
self.setWindowTitle("Threshold")
|
|
7443
8142
|
|
|
7444
8143
|
# Create central widget and layout
|
|
@@ -7460,25 +8159,6 @@ class MachineWindow(QMainWindow):
|
|
|
7460
8159
|
|
|
7461
8160
|
self.parent().pen_button.setEnabled(False)
|
|
7462
8161
|
|
|
7463
|
-
|
|
7464
|
-
if self.parent().active_channel == 0:
|
|
7465
|
-
if self.parent().channel_data[0] is not None:
|
|
7466
|
-
try:
|
|
7467
|
-
active_data = self.parent().channel_data[0]
|
|
7468
|
-
act_channel = 0
|
|
7469
|
-
except:
|
|
7470
|
-
active_data = self.parent().channel_data[1]
|
|
7471
|
-
act_channel = 1
|
|
7472
|
-
else:
|
|
7473
|
-
active_data = self.parent().channel_data[1]
|
|
7474
|
-
act_channel = 1
|
|
7475
|
-
|
|
7476
|
-
try:
|
|
7477
|
-
array1 = np.zeros_like(active_data).astype(np.uint8)
|
|
7478
|
-
except:
|
|
7479
|
-
print("No data in nodes channel")
|
|
7480
|
-
return
|
|
7481
|
-
|
|
7482
8162
|
array3 = np.zeros_like(active_data).astype(np.uint8)
|
|
7483
8163
|
self.parent().highlight_overlay = array3 #Clear this out for the segmenter to use
|
|
7484
8164
|
|
|
@@ -8863,6 +9543,91 @@ class MaskDialog(QDialog):
|
|
|
8863
9543
|
except Exception as e:
|
|
8864
9544
|
print(f"Error masking: {e}")
|
|
8865
9545
|
|
|
9546
|
+
class CropDialog(QDialog):
|
|
9547
|
+
|
|
9548
|
+
def __init__(self, parent=None):
|
|
9549
|
+
|
|
9550
|
+
try:
|
|
9551
|
+
|
|
9552
|
+
super().__init__(parent)
|
|
9553
|
+
self.setWindowTitle("Crop Image?")
|
|
9554
|
+
self.setModal(True)
|
|
9555
|
+
|
|
9556
|
+
layout = QFormLayout(self)
|
|
9557
|
+
|
|
9558
|
+
self.xmin = QLineEdit("0")
|
|
9559
|
+
layout.addRow("X Min", self.xmin)
|
|
9560
|
+
|
|
9561
|
+
self.xmax = QLineEdit(f"{self.parent().shape[2]}")
|
|
9562
|
+
layout.addRow("X Max", self.xmax)
|
|
9563
|
+
|
|
9564
|
+
self.ymin = QLineEdit("0")
|
|
9565
|
+
layout.addRow("Y Min", self.ymin)
|
|
9566
|
+
|
|
9567
|
+
self.ymax = QLineEdit(f"{self.parent().shape[1]}")
|
|
9568
|
+
layout.addRow("Y Max", self.ymax)
|
|
9569
|
+
|
|
9570
|
+
self.zmin = QLineEdit("0")
|
|
9571
|
+
layout.addRow("Z Min", self.zmin)
|
|
9572
|
+
|
|
9573
|
+
self.zmax = QLineEdit(f"{self.parent().shape[0]}")
|
|
9574
|
+
layout.addRow("Z Max", self.zmax)
|
|
9575
|
+
|
|
9576
|
+
# Add Run button
|
|
9577
|
+
run_button = QPushButton("Run")
|
|
9578
|
+
run_button.clicked.connect(self.run)
|
|
9579
|
+
layout.addRow(run_button)
|
|
9580
|
+
|
|
9581
|
+
except:
|
|
9582
|
+
pass
|
|
9583
|
+
|
|
9584
|
+
def run(self):
|
|
9585
|
+
|
|
9586
|
+
try:
|
|
9587
|
+
|
|
9588
|
+
xmin = int(self.xmin.text()) if self.xmin.text() else 0
|
|
9589
|
+
ymin = int(self.ymin.text()) if self.ymin.text() else 0
|
|
9590
|
+
zmin = int(self.zmin.text()) if self.zmin.text() else 0
|
|
9591
|
+
xmax = int(self.xmax.text()) if self.xmax.text() else self.parent().shape[2]
|
|
9592
|
+
ymax = int(self.ymax.text()) if self.xmax.text() else self.parent().shape[1]
|
|
9593
|
+
zmax = int(self.zmax.text()) if self.xmax.text() else self.parent().shape[0]
|
|
9594
|
+
|
|
9595
|
+
args = xmin, ymin, zmin, xmax, ymax, zmax
|
|
9596
|
+
|
|
9597
|
+
for i, array in enumerate(self.parent().channel_data):
|
|
9598
|
+
|
|
9599
|
+
if array is None:
|
|
9600
|
+
|
|
9601
|
+
continue
|
|
9602
|
+
|
|
9603
|
+
else:
|
|
9604
|
+
|
|
9605
|
+
array = self.reslice_3d_array(array, args)
|
|
9606
|
+
|
|
9607
|
+
self.parent().load_channel(i, array, data = True)
|
|
9608
|
+
|
|
9609
|
+
self.accept()
|
|
9610
|
+
|
|
9611
|
+
except Exception as e:
|
|
9612
|
+
|
|
9613
|
+
print(f"Error cropping: {e}")
|
|
9614
|
+
|
|
9615
|
+
|
|
9616
|
+
|
|
9617
|
+
|
|
9618
|
+
|
|
9619
|
+
|
|
9620
|
+
|
|
9621
|
+
|
|
9622
|
+
def reslice_3d_array(self, array, args):
|
|
9623
|
+
"""Internal method used for the secondary algorithm to reslice subarrays around nodes."""
|
|
9624
|
+
|
|
9625
|
+
x_start, y_start, z_start, x_end, y_end, z_end = args
|
|
9626
|
+
|
|
9627
|
+
# Reslice the array
|
|
9628
|
+
array = array[z_start:z_end+1, y_start:y_end+1, x_start:x_end+1]
|
|
9629
|
+
|
|
9630
|
+
return array
|
|
8866
9631
|
|
|
8867
9632
|
|
|
8868
9633
|
class TypeDialog(QDialog):
|
|
@@ -9226,6 +9991,12 @@ class CentroidNodeDialog(QDialog):
|
|
|
9226
9991
|
|
|
9227
9992
|
layout = QFormLayout(self)
|
|
9228
9993
|
|
|
9994
|
+
# Add mode selection dropdown
|
|
9995
|
+
self.mode_selector = QComboBox()
|
|
9996
|
+
self.mode_selector.addItems(["Starting at 0", "Starting at Min Centroids (will transpose centroids)"])
|
|
9997
|
+
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
9998
|
+
layout.addRow("Execution Mode:", self.mode_selector)
|
|
9999
|
+
|
|
9229
10000
|
# Add Run button
|
|
9230
10001
|
run_button = QPushButton("Run Node Generation? (Will override current nodes). Note it is presumed your nodes begin at 1, not 0.")
|
|
9231
10002
|
run_button.clicked.connect(self.run_nodes)
|
|
@@ -9255,7 +10026,18 @@ class CentroidNodeDialog(QDialog):
|
|
|
9255
10026
|
)
|
|
9256
10027
|
return
|
|
9257
10028
|
|
|
9258
|
-
|
|
10029
|
+
mode = self.mode_selector.currentIndex()
|
|
10030
|
+
|
|
10031
|
+
if mode == 0:
|
|
10032
|
+
|
|
10033
|
+
my_network.nodes = my_network.centroid_array()
|
|
10034
|
+
|
|
10035
|
+
else:
|
|
10036
|
+
|
|
10037
|
+
my_network.nodes, my_network.centroids = my_network.centroid_array(clip = True)
|
|
10038
|
+
|
|
10039
|
+
self.parent().format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
10040
|
+
|
|
9259
10041
|
|
|
9260
10042
|
self.parent().load_channel(0, channel_data = my_network.nodes, data = True)
|
|
9261
10043
|
|
|
@@ -9338,7 +10120,13 @@ class GenNodesDialog(QDialog):
|
|
|
9338
10120
|
# Auto checkbox
|
|
9339
10121
|
self.auto = QPushButton("Auto")
|
|
9340
10122
|
self.auto.setCheckable(True)
|
|
9341
|
-
|
|
10123
|
+
try:
|
|
10124
|
+
if my_network.edges.shape[0] == 1:
|
|
10125
|
+
self.auto.setChecked(False)
|
|
10126
|
+
else:
|
|
10127
|
+
self.auto.setChecked(True)
|
|
10128
|
+
except:
|
|
10129
|
+
self.auto.setChecked(True)
|
|
9342
10130
|
rec_layout.addWidget(QLabel("Attempt to Auto Correct Skeleton Looping:"), 1, 0)
|
|
9343
10131
|
rec_layout.addWidget(self.auto, 1, 1)
|
|
9344
10132
|
|
|
@@ -9503,10 +10291,10 @@ class BranchDialog(QDialog):
|
|
|
9503
10291
|
correction_layout = QGridLayout()
|
|
9504
10292
|
|
|
9505
10293
|
# Branch Fix checkbox
|
|
9506
|
-
self.fix = QPushButton("Auto-Correct
|
|
10294
|
+
self.fix = QPushButton("Auto-Correct 1")
|
|
9507
10295
|
self.fix.setCheckable(True)
|
|
9508
10296
|
self.fix.setChecked(False)
|
|
9509
|
-
correction_layout.addWidget(QLabel("
|
|
10297
|
+
correction_layout.addWidget(QLabel("Auto-Correct Branches by Collapsing Busy Neighbors: "), 0, 0)
|
|
9510
10298
|
correction_layout.addWidget(self.fix, 0, 1)
|
|
9511
10299
|
|
|
9512
10300
|
# Fix value
|
|
@@ -9518,6 +10306,12 @@ class BranchDialog(QDialog):
|
|
|
9518
10306
|
self.seed = QLineEdit('')
|
|
9519
10307
|
correction_layout.addWidget(QLabel("Random seed for auto correction (int - optional):"), 2, 0)
|
|
9520
10308
|
correction_layout.addWidget(self.seed, 2, 1)
|
|
10309
|
+
|
|
10310
|
+
self.fix2 = QPushButton("Auto-Correct 2")
|
|
10311
|
+
self.fix2.setCheckable(True)
|
|
10312
|
+
self.fix2.setChecked(True)
|
|
10313
|
+
correction_layout.addWidget(QLabel("Auto-Correct Branches by Collapsing Internal Labels: "), 3, 0)
|
|
10314
|
+
correction_layout.addWidget(self.fix2, 3, 1)
|
|
9521
10315
|
|
|
9522
10316
|
correction_group.setLayout(correction_layout)
|
|
9523
10317
|
main_layout.addWidget(correction_group)
|
|
@@ -9587,6 +10381,7 @@ class BranchDialog(QDialog):
|
|
|
9587
10381
|
GPU = self.GPU.isChecked()
|
|
9588
10382
|
cubic = self.cubic.isChecked()
|
|
9589
10383
|
fix = self.fix.isChecked()
|
|
10384
|
+
fix2 = self.fix2.isChecked()
|
|
9590
10385
|
fix_val = float(self.fix_val.text()) if self.fix_val.text() else None
|
|
9591
10386
|
seed = int(self.seed.text()) if self.seed.text() else None
|
|
9592
10387
|
|
|
@@ -9603,6 +10398,25 @@ class BranchDialog(QDialog):
|
|
|
9603
10398
|
|
|
9604
10399
|
output = n3d.label_branches(my_network.edges, nodes = my_network.nodes, bonus_array = original_array, GPU = GPU, down_factor = down_factor, arrayshape = original_shape)
|
|
9605
10400
|
|
|
10401
|
+
if fix2:
|
|
10402
|
+
|
|
10403
|
+
temp_network = n3d.Network_3D(nodes = output)
|
|
10404
|
+
|
|
10405
|
+
max_val = np.max(temp_network.nodes)
|
|
10406
|
+
|
|
10407
|
+
background = temp_network.nodes == 0
|
|
10408
|
+
|
|
10409
|
+
background = background * max_val
|
|
10410
|
+
|
|
10411
|
+
temp_network.nodes = temp_network.nodes + background
|
|
10412
|
+
|
|
10413
|
+
del background
|
|
10414
|
+
|
|
10415
|
+
temp_network.morph_proximity(search = [3,3], fastdil = True) #Detect network of nearby branches
|
|
10416
|
+
|
|
10417
|
+
output = n3d.fix_branches(output, temp_network.network, max_val)
|
|
10418
|
+
|
|
10419
|
+
|
|
9606
10420
|
if fix:
|
|
9607
10421
|
|
|
9608
10422
|
temp_network = n3d.Network_3D(nodes = output)
|
|
@@ -9611,7 +10425,7 @@ class BranchDialog(QDialog):
|
|
|
9611
10425
|
|
|
9612
10426
|
temp_network.community_partition(weighted = False, style = 1, dostats = False, seed = seed) #Find communities with louvain, unweighted params
|
|
9613
10427
|
|
|
9614
|
-
targs = n3d.
|
|
10428
|
+
targs = n3d.fix_branches_network(temp_network.nodes, temp_network.network, temp_network.communities, fix_val)
|
|
9615
10429
|
|
|
9616
10430
|
temp_network.com_to_node(targs)
|
|
9617
10431
|
|
|
@@ -9798,6 +10612,11 @@ class ModifyDialog(QDialog):
|
|
|
9798
10612
|
self.revid.setCheckable(True)
|
|
9799
10613
|
self.revid.setChecked(False)
|
|
9800
10614
|
layout.addRow("Remove Unassigned IDs from Centroid List?:", self.revid)
|
|
10615
|
+
|
|
10616
|
+
self.remove = QPushButton("Remove Missing")
|
|
10617
|
+
self.remove.setCheckable(True)
|
|
10618
|
+
self.remove.setChecked(False)
|
|
10619
|
+
layout.addRow("Remove Any Nodes Not in Nodes Channel From Properties?:", self.remove)
|
|
9801
10620
|
|
|
9802
10621
|
# trunk checkbox (default false)
|
|
9803
10622
|
self.trunk = QPushButton("Remove Trunk")
|
|
@@ -9835,6 +10654,12 @@ class ModifyDialog(QDialog):
|
|
|
9835
10654
|
self.isolate.setChecked(False)
|
|
9836
10655
|
layout.addRow("Isolate connections between two specific node types (if assigned)?:", self.isolate)
|
|
9837
10656
|
|
|
10657
|
+
# isolate checkbox (default false)
|
|
10658
|
+
self.com_sizes = QPushButton("Communities By Size")
|
|
10659
|
+
self.com_sizes.setCheckable(True)
|
|
10660
|
+
self.com_sizes.setChecked(False)
|
|
10661
|
+
layout.addRow("Rearrange Community IDs by size?:", self.com_sizes)
|
|
10662
|
+
|
|
9838
10663
|
# Community collapse checkbox (default False)
|
|
9839
10664
|
self.comcollapse = QPushButton("Communities -> nodes")
|
|
9840
10665
|
self.comcollapse.setCheckable(True)
|
|
@@ -9876,6 +10701,8 @@ class ModifyDialog(QDialog):
|
|
|
9876
10701
|
prune = self.prune.isChecked()
|
|
9877
10702
|
isolate = self.isolate.isChecked()
|
|
9878
10703
|
comcollapse = self.comcollapse.isChecked()
|
|
10704
|
+
remove = self.remove.isChecked()
|
|
10705
|
+
com_size = self.com_sizes.isChecked()
|
|
9879
10706
|
|
|
9880
10707
|
|
|
9881
10708
|
if isolate and my_network.node_identities is not None:
|
|
@@ -9889,6 +10716,21 @@ class ModifyDialog(QDialog):
|
|
|
9889
10716
|
pass
|
|
9890
10717
|
|
|
9891
10718
|
|
|
10719
|
+
if remove:
|
|
10720
|
+
my_network.purge_properties()
|
|
10721
|
+
try:
|
|
10722
|
+
self.parent().format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
10723
|
+
except:
|
|
10724
|
+
pass
|
|
10725
|
+
try:
|
|
10726
|
+
self.parent().format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
|
|
10727
|
+
except:
|
|
10728
|
+
pass
|
|
10729
|
+
try:
|
|
10730
|
+
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'Community', 'Node Communities')
|
|
10731
|
+
except:
|
|
10732
|
+
pass
|
|
10733
|
+
|
|
9892
10734
|
|
|
9893
10735
|
if edgeweight:
|
|
9894
10736
|
my_network.remove_edge_weights()
|
|
@@ -9911,6 +10753,14 @@ class ModifyDialog(QDialog):
|
|
|
9911
10753
|
self.parent().format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
9912
10754
|
except:
|
|
9913
10755
|
pass
|
|
10756
|
+
if com_size:
|
|
10757
|
+
if my_network.communities is None:
|
|
10758
|
+
self.parent().show_partition_dialog()
|
|
10759
|
+
if my_network.communities is None:
|
|
10760
|
+
return
|
|
10761
|
+
my_network.com_by_size()
|
|
10762
|
+
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'Community', 'Node Communities')
|
|
10763
|
+
|
|
9914
10764
|
if comcollapse:
|
|
9915
10765
|
if my_network.communities is None:
|
|
9916
10766
|
self.parent().show_partition_dialog()
|
|
@@ -9940,6 +10790,8 @@ class ModifyDialog(QDialog):
|
|
|
9940
10790
|
self.accept()
|
|
9941
10791
|
|
|
9942
10792
|
except Exception as e:
|
|
10793
|
+
import traceback
|
|
10794
|
+
print(traceback.format_exc())
|
|
9943
10795
|
print(f"An error occurred: {e}")
|
|
9944
10796
|
|
|
9945
10797
|
|
|
@@ -10070,85 +10922,107 @@ class CalcAllDialog(QDialog):
|
|
|
10070
10922
|
prev_fastdil = False
|
|
10071
10923
|
prev_overlays = False
|
|
10072
10924
|
prev_updates = True
|
|
10073
|
-
|
|
10925
|
+
|
|
10074
10926
|
def __init__(self, parent=None):
|
|
10075
10927
|
super().__init__(parent)
|
|
10076
|
-
self.setWindowTitle("Calculate
|
|
10928
|
+
self.setWindowTitle("Calculate Connectivity Network Parameters")
|
|
10077
10929
|
self.setModal(True)
|
|
10078
10930
|
|
|
10079
|
-
|
|
10931
|
+
# Main layout
|
|
10932
|
+
main_layout = QVBoxLayout(self)
|
|
10080
10933
|
|
|
10081
|
-
#
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
layout.addRow("Output Directory:", self.directory)
|
|
10934
|
+
# Important Parameters Group
|
|
10935
|
+
important_group = QGroupBox("Important Parameters")
|
|
10936
|
+
important_layout = QFormLayout(important_group)
|
|
10085
10937
|
|
|
10086
|
-
# Load previous values for all inputs
|
|
10087
10938
|
self.xy_scale = QLineEdit(f'{my_network.xy_scale}')
|
|
10088
|
-
|
|
10939
|
+
important_layout.addRow("xy_scale:", self.xy_scale)
|
|
10089
10940
|
|
|
10090
10941
|
self.z_scale = QLineEdit(f'{my_network.z_scale}')
|
|
10091
|
-
|
|
10092
|
-
|
|
10942
|
+
important_layout.addRow("z_scale:", self.z_scale)
|
|
10943
|
+
|
|
10093
10944
|
self.search = QLineEdit(self.prev_search)
|
|
10094
10945
|
self.search.setPlaceholderText("Leave empty for None")
|
|
10095
|
-
|
|
10096
|
-
|
|
10946
|
+
important_layout.addRow("Node Search (float):", self.search)
|
|
10947
|
+
|
|
10097
10948
|
self.diledge = QLineEdit(self.prev_diledge)
|
|
10098
10949
|
self.diledge.setPlaceholderText("Leave empty for None")
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
self.
|
|
10102
|
-
self.
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10950
|
+
important_layout.addRow("Edge Reconnection Distance (float):", self.diledge)
|
|
10951
|
+
|
|
10952
|
+
self.label_nodes = QPushButton("Label")
|
|
10953
|
+
self.label_nodes.setCheckable(True)
|
|
10954
|
+
self.label_nodes.setChecked(self.prev_label_nodes)
|
|
10955
|
+
important_layout.addRow("Re-Label Nodes (WARNING - OVERRIDES ANY CURRENT LABELS):", self.label_nodes)
|
|
10956
|
+
|
|
10957
|
+
main_layout.addWidget(important_group)
|
|
10958
|
+
|
|
10959
|
+
# Optional Parameters Group
|
|
10960
|
+
optional_group = QGroupBox("Optional Parameters")
|
|
10961
|
+
optional_layout = QFormLayout(optional_group)
|
|
10962
|
+
|
|
10109
10963
|
self.other_nodes = QLineEdit(self.prev_other_nodes)
|
|
10110
10964
|
self.other_nodes.setPlaceholderText("Leave empty for None")
|
|
10111
|
-
|
|
10112
|
-
|
|
10965
|
+
optional_layout.addRow("Filepath or directory containing additional node images:", self.other_nodes)
|
|
10966
|
+
|
|
10113
10967
|
self.remove_trunk = QLineEdit(self.prev_remove_trunk)
|
|
10114
10968
|
self.remove_trunk.setPlaceholderText("Leave empty for 0")
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
# Load previous button states
|
|
10118
|
-
self.gpu = QPushButton("GPU")
|
|
10119
|
-
self.gpu.setCheckable(True)
|
|
10120
|
-
self.gpu.setChecked(self.prev_gpu)
|
|
10121
|
-
layout.addRow("Use GPU:", self.gpu)
|
|
10122
|
-
|
|
10123
|
-
self.label_nodes = QPushButton("Label")
|
|
10124
|
-
self.label_nodes.setCheckable(True)
|
|
10125
|
-
self.label_nodes.setChecked(self.prev_label_nodes)
|
|
10126
|
-
layout.addRow("Re-Label Nodes (WARNING - OVERRIDES ANY CURRENT LABELS):", self.label_nodes)
|
|
10127
|
-
|
|
10969
|
+
optional_layout.addRow("Times to remove edge trunks (int):", self.remove_trunk)
|
|
10970
|
+
|
|
10128
10971
|
self.inners = QPushButton("Inner Edges")
|
|
10129
10972
|
self.inners.setCheckable(True)
|
|
10130
10973
|
self.inners.setChecked(self.prev_inners)
|
|
10131
|
-
|
|
10132
|
-
|
|
10974
|
+
optional_layout.addRow("Use Inner Edges:", self.inners)
|
|
10975
|
+
|
|
10976
|
+
main_layout.addWidget(optional_group)
|
|
10977
|
+
|
|
10978
|
+
# Speed Up Options Group
|
|
10979
|
+
speedup_group = QGroupBox("Speed Up Options")
|
|
10980
|
+
speedup_layout = QFormLayout(speedup_group)
|
|
10981
|
+
|
|
10982
|
+
self.down_factor = QLineEdit(self.prev_down_factor)
|
|
10983
|
+
self.down_factor.setPlaceholderText("Leave empty for None")
|
|
10984
|
+
speedup_layout.addRow("Downsample for Centroids (int):", self.down_factor)
|
|
10985
|
+
|
|
10986
|
+
self.GPU_downsample = QLineEdit(self.prev_GPU_downsample)
|
|
10987
|
+
self.GPU_downsample.setPlaceholderText("Leave empty for None")
|
|
10988
|
+
speedup_layout.addRow("Downsample for Distance Transform (GPU) (int):", self.GPU_downsample)
|
|
10989
|
+
|
|
10990
|
+
self.gpu = QPushButton("GPU")
|
|
10991
|
+
self.gpu.setCheckable(True)
|
|
10992
|
+
self.gpu.setChecked(self.prev_gpu)
|
|
10993
|
+
speedup_layout.addRow("Use GPU:", self.gpu)
|
|
10994
|
+
|
|
10133
10995
|
self.fastdil = QPushButton("Fast Dilate")
|
|
10134
10996
|
self.fastdil.setCheckable(True)
|
|
10135
10997
|
self.fastdil.setChecked(self.prev_fastdil)
|
|
10136
|
-
|
|
10137
|
-
|
|
10998
|
+
speedup_layout.addRow("Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
10999
|
+
|
|
11000
|
+
main_layout.addWidget(speedup_group)
|
|
11001
|
+
|
|
11002
|
+
# Output Options Group
|
|
11003
|
+
output_group = QGroupBox("Output Options")
|
|
11004
|
+
output_layout = QFormLayout(output_group)
|
|
11005
|
+
|
|
11006
|
+
self.directory = QLineEdit(self.prev_directory)
|
|
11007
|
+
self.directory.setPlaceholderText("Will Have to Save Manually If Empty")
|
|
11008
|
+
output_layout.addRow("Output Directory:", self.directory)
|
|
11009
|
+
|
|
10138
11010
|
self.overlays = QPushButton("Overlays")
|
|
10139
11011
|
self.overlays.setCheckable(True)
|
|
10140
11012
|
self.overlays.setChecked(self.prev_overlays)
|
|
10141
|
-
|
|
10142
|
-
|
|
11013
|
+
output_layout.addRow("Generate Overlays:", self.overlays)
|
|
11014
|
+
|
|
10143
11015
|
self.update = QPushButton("Update")
|
|
10144
11016
|
self.update.setCheckable(True)
|
|
10145
11017
|
self.update.setChecked(self.prev_updates)
|
|
10146
|
-
|
|
11018
|
+
output_layout.addRow("Update Node/Edge in NetTracer3D:", self.update)
|
|
11019
|
+
|
|
11020
|
+
main_layout.addWidget(output_group)
|
|
10147
11021
|
|
|
10148
11022
|
# Add Run button
|
|
10149
11023
|
run_button = QPushButton("Run Calculate All")
|
|
10150
11024
|
run_button.clicked.connect(self.run_calc_all)
|
|
10151
|
-
|
|
11025
|
+
main_layout.addWidget(run_button)
|
|
10152
11026
|
|
|
10153
11027
|
def run_calc_all(self):
|
|
10154
11028
|
|
|
@@ -10325,65 +11199,87 @@ class CalcAllDialog(QDialog):
|
|
|
10325
11199
|
f"Error running calculate all: {str(e)}"
|
|
10326
11200
|
)
|
|
10327
11201
|
|
|
10328
|
-
class ProxDialog(QDialog):
|
|
10329
11202
|
|
|
11203
|
+
class ProxDialog(QDialog):
|
|
10330
11204
|
def __init__(self, parent=None):
|
|
10331
11205
|
super().__init__(parent)
|
|
10332
11206
|
self.setWindowTitle("Calculate Proximity Network")
|
|
10333
11207
|
self.setModal(True)
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
11208
|
+
|
|
11209
|
+
# Main layout
|
|
11210
|
+
main_layout = QVBoxLayout(self)
|
|
11211
|
+
|
|
11212
|
+
# Important Parameters Group
|
|
11213
|
+
important_group = QGroupBox("Important Parameters")
|
|
11214
|
+
important_layout = QFormLayout(important_group)
|
|
11215
|
+
|
|
10342
11216
|
self.search = QLineEdit()
|
|
10343
11217
|
self.search.setPlaceholderText("search")
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
# Load previous values for all inputs
|
|
11218
|
+
important_layout.addRow("Search Region Distance? (enter true value corresponding to scaling, ie in microns):", self.search)
|
|
11219
|
+
|
|
10347
11220
|
self.xy_scale = QLineEdit(f"{my_network.xy_scale}")
|
|
10348
|
-
|
|
11221
|
+
important_layout.addRow("xy_scale:", self.xy_scale)
|
|
10349
11222
|
|
|
10350
11223
|
self.z_scale = QLineEdit(f"{my_network.z_scale}")
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
11224
|
+
important_layout.addRow("z_scale:", self.z_scale)
|
|
11225
|
+
|
|
11226
|
+
main_layout.addWidget(important_group)
|
|
11227
|
+
|
|
11228
|
+
# Mode Group
|
|
11229
|
+
mode_group = QGroupBox("Mode")
|
|
11230
|
+
mode_layout = QFormLayout(mode_group)
|
|
11231
|
+
|
|
10354
11232
|
self.mode_selector = QComboBox()
|
|
10355
11233
|
self.mode_selector.addItems(["From Centroids (fast but ignores shape - use for small or spherical objects - search STARTS at centroid)", "From Morphological Shape (slower but preserves shape - use for oddly shaped objects - search STARTS at object border)"])
|
|
10356
11234
|
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
self.fastdil = QPushButton("Fast Dilate")
|
|
10360
|
-
self.fastdil.setCheckable(True)
|
|
10361
|
-
self.fastdil.setChecked(False)
|
|
10362
|
-
layout.addRow("(If using morphological) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
10363
|
-
|
|
11235
|
+
mode_layout.addRow("Execution Mode:", self.mode_selector)
|
|
11236
|
+
|
|
10364
11237
|
if my_network.node_identities is not None:
|
|
10365
11238
|
self.id_selector = QComboBox()
|
|
10366
11239
|
# Add all options from id dictionary
|
|
10367
11240
|
self.id_selector.addItems(['None'] + list(set(my_network.node_identities.values())))
|
|
10368
11241
|
self.id_selector.setCurrentIndex(0) # Default to Mode 1
|
|
10369
|
-
|
|
11242
|
+
mode_layout.addRow("Create Networks only from a specific node identity?:", self.id_selector)
|
|
10370
11243
|
else:
|
|
10371
11244
|
self.id_selector = None
|
|
10372
|
-
|
|
11245
|
+
|
|
11246
|
+
main_layout.addWidget(mode_group)
|
|
11247
|
+
|
|
11248
|
+
# Output Options Group
|
|
11249
|
+
output_group = QGroupBox("Output Options")
|
|
11250
|
+
output_layout = QFormLayout(output_group)
|
|
11251
|
+
|
|
11252
|
+
self.directory = QLineEdit('')
|
|
11253
|
+
self.directory.setPlaceholderText("Leave empty for 'my_network'")
|
|
11254
|
+
output_layout.addRow("Output Directory:", self.directory)
|
|
11255
|
+
|
|
10373
11256
|
self.overlays = QPushButton("Overlays")
|
|
10374
11257
|
self.overlays.setCheckable(True)
|
|
10375
11258
|
self.overlays.setChecked(True)
|
|
10376
|
-
|
|
10377
|
-
|
|
11259
|
+
output_layout.addRow("Generate Overlays:", self.overlays)
|
|
11260
|
+
|
|
10378
11261
|
self.populate = QPushButton("Populate Nodes from Centroids?")
|
|
10379
11262
|
self.populate.setCheckable(True)
|
|
10380
11263
|
self.populate.setChecked(False)
|
|
10381
|
-
|
|
10382
|
-
|
|
11264
|
+
output_layout.addRow("If using centroid search:", self.populate)
|
|
11265
|
+
|
|
11266
|
+
main_layout.addWidget(output_group)
|
|
11267
|
+
|
|
11268
|
+
# Speed Up Options Group
|
|
11269
|
+
speedup_group = QGroupBox("Speed Up Options")
|
|
11270
|
+
speedup_layout = QFormLayout(speedup_group)
|
|
11271
|
+
|
|
11272
|
+
self.fastdil = QPushButton("Fast Dilate")
|
|
11273
|
+
self.fastdil.setCheckable(True)
|
|
11274
|
+
self.fastdil.setChecked(False)
|
|
11275
|
+
speedup_layout.addRow("(If using morphological) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
11276
|
+
|
|
11277
|
+
main_layout.addWidget(speedup_group)
|
|
11278
|
+
|
|
10383
11279
|
# Add Run button
|
|
10384
11280
|
run_button = QPushButton("Run Proximity Network")
|
|
10385
11281
|
run_button.clicked.connect(self.prox)
|
|
10386
|
-
|
|
11282
|
+
main_layout.addWidget(run_button)
|
|
10387
11283
|
|
|
10388
11284
|
def prox(self):
|
|
10389
11285
|
|
|
@@ -10483,14 +11379,9 @@ class ProxDialog(QDialog):
|
|
|
10483
11379
|
my_network.id_overlay = my_network.draw_node_indices(directory=directory)
|
|
10484
11380
|
|
|
10485
11381
|
# Update channel data
|
|
10486
|
-
self.parent().
|
|
10487
|
-
self.parent().
|
|
11382
|
+
self.parent().load_channel(2, channel_data = my_network.network_overlay, data = True)
|
|
11383
|
+
self.parent().load_channel(3, channel_data = my_network.id_overlay, data = True)
|
|
10488
11384
|
|
|
10489
|
-
# Enable the overlay channel buttons
|
|
10490
|
-
self.parent().channel_buttons[2].setEnabled(True)
|
|
10491
|
-
self.parent().channel_buttons[3].setEnabled(True)
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
11385
|
self.parent().update_display()
|
|
10495
11386
|
self.accept()
|
|
10496
11387
|
|