nettracer3d 0.6.6__py3-none-any.whl → 0.6.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 +0 -269
- nettracer3d/morphology.py +61 -36
- nettracer3d/nettracer.py +261 -238
- nettracer3d/nettracer_gui.py +523 -194
- nettracer3d/proximity.py +8 -7
- nettracer3d/segmenter.py +12 -18
- nettracer3d/smart_dilate.py +156 -82
- {nettracer3d-0.6.6.dist-info → nettracer3d-0.6.8.dist-info}/METADATA +4 -4
- nettracer3d-0.6.8.dist-info/RECORD +20 -0
- nettracer3d-0.6.6.dist-info/RECORD +0 -20
- {nettracer3d-0.6.6.dist-info → nettracer3d-0.6.8.dist-info}/WHEEL +0 -0
- {nettracer3d-0.6.6.dist-info → nettracer3d-0.6.8.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.6.6.dist-info → nettracer3d-0.6.8.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.6.6.dist-info → nettracer3d-0.6.8.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -18,13 +18,14 @@ from nettracer3d import smart_dilate as sdl
|
|
|
18
18
|
from matplotlib.colors import LinearSegmentedColormap
|
|
19
19
|
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
20
20
|
import pandas as pd
|
|
21
|
-
from PyQt6.QtGui import (QFont, QCursor, QColor, QPixmap, QPainter, QPen)
|
|
21
|
+
from PyQt6.QtGui import (QFont, QCursor, QColor, QPixmap, QFontMetrics, QPainter, QPen)
|
|
22
22
|
import tifffile
|
|
23
23
|
import copy
|
|
24
24
|
import multiprocessing as mp
|
|
25
25
|
from concurrent.futures import ThreadPoolExecutor
|
|
26
26
|
from functools import partial
|
|
27
27
|
from nettracer3d import segmenter
|
|
28
|
+
#from nettracer3d import segmenter_GPU
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
|
|
@@ -117,6 +118,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
117
118
|
|
|
118
119
|
#For ML segmenting mode
|
|
119
120
|
self.brush_mode = False
|
|
121
|
+
self.can = False
|
|
122
|
+
self.threed = False
|
|
123
|
+
self.threedthresh = 5
|
|
120
124
|
self.painting = False
|
|
121
125
|
self.foreground = True
|
|
122
126
|
self.machine_window = None
|
|
@@ -147,6 +151,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
147
151
|
3: None
|
|
148
152
|
} #For storing thresholding information
|
|
149
153
|
|
|
154
|
+
self.radii_dict = {
|
|
155
|
+
0: None,
|
|
156
|
+
1: None
|
|
157
|
+
}
|
|
158
|
+
|
|
150
159
|
self.original_shape = None #For undoing resamples
|
|
151
160
|
|
|
152
161
|
# Create control panel
|
|
@@ -1312,7 +1321,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1312
1321
|
info_dict['Centroid'] = my_network.node_centroids[label]
|
|
1313
1322
|
|
|
1314
1323
|
if self.volume_dict[0] is not None:
|
|
1315
|
-
info_dict['Volume'] = self.volume_dict[0][label]
|
|
1324
|
+
info_dict['Volume (Scaled)'] = self.volume_dict[0][label]
|
|
1325
|
+
|
|
1326
|
+
if self.radii_dict[0] is not None:
|
|
1327
|
+
info_dict['Max Radius (Scaled)'] = self.radii_dict[0][label]
|
|
1316
1328
|
|
|
1317
1329
|
|
|
1318
1330
|
elif sort == 'edge':
|
|
@@ -1327,7 +1339,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1327
1339
|
info_dict['Centroid'] = my_network.edge_centroids[label]
|
|
1328
1340
|
|
|
1329
1341
|
if self.volume_dict[1] is not None:
|
|
1330
|
-
info_dict['Volume'] = self.volume_dict[1][label]
|
|
1342
|
+
info_dict['Volume (Scaled)'] = self.volume_dict[1][label]
|
|
1343
|
+
|
|
1344
|
+
if self.radii_dict[1] is not None:
|
|
1345
|
+
info_dict['~Radius (Scaled)'] = self.radii_dict[1][label]
|
|
1331
1346
|
|
|
1332
1347
|
self.format_for_upperright_table(info_dict, title = f'Info on Object')
|
|
1333
1348
|
|
|
@@ -1700,6 +1715,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1700
1715
|
self.pen_button.setChecked(False)
|
|
1701
1716
|
self.pan_mode = False
|
|
1702
1717
|
self.brush_mode = False
|
|
1718
|
+
self.can = False
|
|
1719
|
+
self.threed = False
|
|
1720
|
+
self.last_change = None
|
|
1703
1721
|
if self.machine_window is not None:
|
|
1704
1722
|
self.machine_window.silence_button()
|
|
1705
1723
|
self.canvas.setCursor(Qt.CursorShape.CrossCursor)
|
|
@@ -1717,6 +1735,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1717
1735
|
self.zoom_button.setChecked(False)
|
|
1718
1736
|
self.pen_button.setChecked(False)
|
|
1719
1737
|
self.zoom_mode = False
|
|
1738
|
+
self.can = False
|
|
1739
|
+
self.threed = False
|
|
1740
|
+
self.last_change = None
|
|
1720
1741
|
self.brush_mode = False
|
|
1721
1742
|
if self.machine_window is not None:
|
|
1722
1743
|
self.machine_window.silence_button()
|
|
@@ -1737,14 +1758,69 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1737
1758
|
self.zoom_mode = False
|
|
1738
1759
|
self.update_brush_cursor()
|
|
1739
1760
|
else:
|
|
1761
|
+
self.last_change = None
|
|
1762
|
+
self.can = False
|
|
1763
|
+
self.threed = False
|
|
1740
1764
|
self.canvas.setCursor(Qt.CursorShape.ArrowCursor)
|
|
1741
1765
|
|
|
1766
|
+
def toggle_can(self):
|
|
1767
|
+
|
|
1768
|
+
if not self.can:
|
|
1769
|
+
self.can = True
|
|
1770
|
+
self.update_brush_cursor()
|
|
1771
|
+
else:
|
|
1772
|
+
self.can = False
|
|
1773
|
+
self.last_change = None
|
|
1774
|
+
self.update_brush_cursor()
|
|
1775
|
+
|
|
1776
|
+
def toggle_threed(self):
|
|
1777
|
+
|
|
1778
|
+
if not self.threed:
|
|
1779
|
+
self.threed = True
|
|
1780
|
+
self.threedthresh = 5
|
|
1781
|
+
self.update_brush_cursor()
|
|
1782
|
+
else:
|
|
1783
|
+
self.threed = False
|
|
1784
|
+
self.update_brush_cursor()
|
|
1785
|
+
|
|
1742
1786
|
|
|
1743
1787
|
def on_mpl_scroll(self, event):
|
|
1744
1788
|
"""Handle matplotlib canvas scroll events"""
|
|
1745
1789
|
#Wheel events
|
|
1746
1790
|
if self.brush_mode and event.inaxes == self.ax:
|
|
1791
|
+
|
|
1792
|
+
# Get modifiers
|
|
1793
|
+
modifiers = event.guiEvent.modifiers()
|
|
1794
|
+
ctrl_pressed = bool(modifiers & Qt.ControlModifier)
|
|
1795
|
+
shift_pressed = bool(modifiers & Qt.ShiftModifier)
|
|
1796
|
+
alt_pressed = bool(modifiers & Qt.AltModifier)
|
|
1797
|
+
|
|
1798
|
+
# Check if threed is enabled and ONLY if no specific modifiers are pressed
|
|
1799
|
+
if self.threed and not ctrl_pressed and not shift_pressed and not alt_pressed:
|
|
1800
|
+
import math
|
|
1801
|
+
step = 1 if event.button == 'up' else -1
|
|
1802
|
+
self.threedthresh += step
|
|
1803
|
+
|
|
1804
|
+
# Round to appropriate odd integer based on scroll direction
|
|
1805
|
+
if event.button == 'up':
|
|
1806
|
+
# Round up to nearest odd
|
|
1807
|
+
self.threedthresh = math.ceil(self.threedthresh)
|
|
1808
|
+
if self.threedthresh % 2 == 0:
|
|
1809
|
+
self.threedthresh += 1
|
|
1810
|
+
else: # event.button == 'down'
|
|
1811
|
+
# Round down to nearest odd, but not below 1
|
|
1812
|
+
self.threedthresh = math.floor(self.threedthresh)
|
|
1813
|
+
if self.threedthresh % 2 == 0:
|
|
1814
|
+
self.threedthresh -= 1
|
|
1815
|
+
# Ensure not below minimum value of 1
|
|
1816
|
+
self.threedthresh = max(1, self.threedthresh)
|
|
1817
|
+
|
|
1818
|
+
# Update the brush cursor to show the new threshold
|
|
1819
|
+
self.update_brush_cursor()
|
|
1820
|
+
return
|
|
1821
|
+
|
|
1747
1822
|
# Check if Ctrl is pressed
|
|
1823
|
+
|
|
1748
1824
|
if event.guiEvent.modifiers() & Qt.ShiftModifier:
|
|
1749
1825
|
pass
|
|
1750
1826
|
|
|
@@ -1781,6 +1857,15 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1781
1857
|
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
1782
1858
|
|
|
1783
1859
|
def keyPressEvent(self, event):
|
|
1860
|
+
|
|
1861
|
+
if event.key() == Qt.Key_Z and event.modifiers() & Qt.ControlModifier:
|
|
1862
|
+
try:
|
|
1863
|
+
self.load_channel(self.last_change[1], self.last_change[0], True)
|
|
1864
|
+
except:
|
|
1865
|
+
pass
|
|
1866
|
+
|
|
1867
|
+
return # Return to prevent triggering the regular Z key action below
|
|
1868
|
+
|
|
1784
1869
|
if event.key() == Qt.Key_Z:
|
|
1785
1870
|
self.zoom_button.click()
|
|
1786
1871
|
if self.machine_window is not None:
|
|
@@ -1788,28 +1873,65 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1788
1873
|
self.machine_window.switch_foreground()
|
|
1789
1874
|
if event.key() == Qt.Key_X:
|
|
1790
1875
|
self.high_button.click()
|
|
1876
|
+
if self.brush_mode and self.machine_window is None:
|
|
1877
|
+
if event.key() == Qt.Key_F:
|
|
1878
|
+
self.toggle_can()
|
|
1879
|
+
elif event.key() == Qt.Key_D:
|
|
1880
|
+
self.toggle_threed()
|
|
1791
1881
|
|
|
1792
1882
|
|
|
1793
1883
|
def update_brush_cursor(self):
|
|
1794
1884
|
"""Update the cursor to show brush size"""
|
|
1795
1885
|
if not self.brush_mode:
|
|
1796
1886
|
return
|
|
1797
|
-
|
|
1798
|
-
#
|
|
1799
|
-
|
|
1800
|
-
|
|
1887
|
+
|
|
1888
|
+
# Get font metrics first to determine text size
|
|
1889
|
+
font = QFont()
|
|
1890
|
+
font.setPointSize(14)
|
|
1891
|
+
font_metrics = QFontMetrics(font)
|
|
1892
|
+
thresh_text = str(self.threedthresh)
|
|
1893
|
+
text_rect = font_metrics.boundingRect(thresh_text)
|
|
1894
|
+
|
|
1895
|
+
# Create a pixmap for the cursor - ensure it's large enough for text
|
|
1896
|
+
brush_size = self.brush_size * 2 + 2 # Add padding for border
|
|
1897
|
+
extra_width = max(0, text_rect.width() + 4 - brush_size) # Extra width for text if needed
|
|
1898
|
+
extra_height = max(0, text_rect.height() + 4 - brush_size) # Extra height for text if needed
|
|
1899
|
+
|
|
1900
|
+
# Make sure pixmap is large enough for both brush and text
|
|
1901
|
+
total_width = brush_size + extra_width
|
|
1902
|
+
total_height = brush_size + extra_height
|
|
1903
|
+
pixmap = QPixmap(total_width, total_height)
|
|
1801
1904
|
pixmap.fill(Qt.transparent)
|
|
1802
1905
|
|
|
1803
1906
|
# Create painter for the pixmap
|
|
1804
1907
|
painter = QPainter(pixmap)
|
|
1805
1908
|
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
1806
1909
|
|
|
1910
|
+
# Calculate center offset for brush ellipse to accommodate text
|
|
1911
|
+
x_offset = extra_width // 2
|
|
1912
|
+
y_offset = extra_height // 2
|
|
1913
|
+
|
|
1807
1914
|
# Draw circle
|
|
1808
|
-
|
|
1915
|
+
if not self.threed:
|
|
1916
|
+
pen = QPen(Qt.white)
|
|
1917
|
+
else:
|
|
1918
|
+
pen = QPen(Qt.red)
|
|
1809
1919
|
pen.setWidth(1)
|
|
1810
1920
|
painter.setPen(pen)
|
|
1811
1921
|
painter.setBrush(Qt.transparent)
|
|
1812
|
-
|
|
1922
|
+
if not self.can:
|
|
1923
|
+
painter.drawEllipse(1 + x_offset, 1 + y_offset, brush_size-2, brush_size-2)
|
|
1924
|
+
|
|
1925
|
+
# Draw threshold number when threed is True and can is False
|
|
1926
|
+
if self.threed:
|
|
1927
|
+
# Set text properties
|
|
1928
|
+
painter.setFont(font)
|
|
1929
|
+
painter.setPen(QPen(Qt.white)) # White text for visibility
|
|
1930
|
+
|
|
1931
|
+
# Draw the text
|
|
1932
|
+
painter.drawText(2, font_metrics.ascent() + 2, thresh_text)
|
|
1933
|
+
else:
|
|
1934
|
+
painter.drawRect(1 + x_offset, 1 + y_offset, 8, 8)
|
|
1813
1935
|
|
|
1814
1936
|
# Create cursor from pixmap
|
|
1815
1937
|
cursor = QCursor(pixmap)
|
|
@@ -1917,13 +2039,20 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1917
2039
|
|
|
1918
2040
|
if event.button == 1 or event.button == 3:
|
|
1919
2041
|
|
|
2042
|
+
x, y = int(event.xdata), int(event.ydata)
|
|
2043
|
+
|
|
2044
|
+
|
|
2045
|
+
if event.button == 1 and self.can:
|
|
2046
|
+
self.handle_can(x, y)
|
|
2047
|
+
return
|
|
2048
|
+
|
|
2049
|
+
|
|
1920
2050
|
if event.button == 3:
|
|
1921
2051
|
self.erase = True
|
|
1922
2052
|
else:
|
|
1923
2053
|
self.erase = False
|
|
1924
2054
|
|
|
1925
2055
|
self.painting = True
|
|
1926
|
-
x, y = int(event.xdata), int(event.ydata)
|
|
1927
2056
|
self.last_paint_pos = (x, y)
|
|
1928
2057
|
|
|
1929
2058
|
if self.pen_button.isChecked():
|
|
@@ -1983,7 +2112,77 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1983
2112
|
for x in range(max(0, center_x - radius), min(width, center_x + radius + 1)):
|
|
1984
2113
|
# Check if point is within circular brush area
|
|
1985
2114
|
if (x - center_x) ** 2 + (y - center_y) ** 2 <= radius ** 2:
|
|
1986
|
-
|
|
2115
|
+
|
|
2116
|
+
if self.threed and self.threedthresh > 1:
|
|
2117
|
+
amount = (self.threedthresh - 1) / 2
|
|
2118
|
+
low = max(0, self.current_slice - amount)
|
|
2119
|
+
high = min(self.channel_data[channel].shape[0] - 1, self.current_slice + amount)
|
|
2120
|
+
|
|
2121
|
+
for i in range(int(low), int(high + 1)):
|
|
2122
|
+
self.channel_data[channel][i][y, x] = val
|
|
2123
|
+
else:
|
|
2124
|
+
self.channel_data[channel][self.current_slice][y, x] = val
|
|
2125
|
+
|
|
2126
|
+
def handle_can(self, x, y):
|
|
2127
|
+
|
|
2128
|
+
|
|
2129
|
+
if self.threed:
|
|
2130
|
+
ref = copy.deepcopy(self.channel_data[self.active_channel])
|
|
2131
|
+
the_slice = self.channel_data[self.active_channel]
|
|
2132
|
+
|
|
2133
|
+
# First invert the boolean array
|
|
2134
|
+
inv = n3d.invert_boolean(the_slice)
|
|
2135
|
+
|
|
2136
|
+
# Label the connected components in the inverted array
|
|
2137
|
+
labeled_array, num_features = n3d.label_objects(inv)
|
|
2138
|
+
|
|
2139
|
+
# Get the target label at the clicked point
|
|
2140
|
+
target_label = labeled_array[self.current_slice][y][x]
|
|
2141
|
+
|
|
2142
|
+
# Only fill if we clicked on a valid region (target_label > 0)
|
|
2143
|
+
if target_label > 0:
|
|
2144
|
+
# Create a mask of the connected component we clicked on
|
|
2145
|
+
fill_mask = (labeled_array == target_label) * 255
|
|
2146
|
+
|
|
2147
|
+
self.last_change = [ref, self.active_channel]
|
|
2148
|
+
|
|
2149
|
+
# Add this mask to the original slice
|
|
2150
|
+
the_slice = the_slice | fill_mask # Use logical OR to add the filled region
|
|
2151
|
+
|
|
2152
|
+
# Update the channel data
|
|
2153
|
+
self.load_channel(self.active_channel, the_slice, True)
|
|
2154
|
+
else:
|
|
2155
|
+
|
|
2156
|
+
ref = copy.deepcopy(self.channel_data[self.active_channel])
|
|
2157
|
+
|
|
2158
|
+
the_slice = self.channel_data[self.active_channel][self.current_slice]
|
|
2159
|
+
|
|
2160
|
+
# First invert the boolean array
|
|
2161
|
+
inv = n3d.invert_boolean(the_slice)
|
|
2162
|
+
|
|
2163
|
+
# Label the connected components in the inverted array
|
|
2164
|
+
labeled_array, num_features = n3d.label_objects(inv)
|
|
2165
|
+
|
|
2166
|
+
# Get the target label at the clicked point
|
|
2167
|
+
target_label = labeled_array[y][x]
|
|
2168
|
+
|
|
2169
|
+
# Only fill if we clicked on a valid region (target_label > 0)
|
|
2170
|
+
if target_label > 0:
|
|
2171
|
+
# Create a mask of the connected component we clicked on
|
|
2172
|
+
fill_mask = (labeled_array == target_label) * 255
|
|
2173
|
+
|
|
2174
|
+
self.last_change = [ref, self.active_channel]
|
|
2175
|
+
|
|
2176
|
+
# Add this mask to the original slice
|
|
2177
|
+
the_slice = the_slice | fill_mask # Use logical OR to add the filled region
|
|
2178
|
+
|
|
2179
|
+
# Update the channel data
|
|
2180
|
+
self.channel_data[self.active_channel][self.current_slice] = the_slice
|
|
2181
|
+
self.load_channel(self.active_channel, self.channel_data[self.active_channel], True)
|
|
2182
|
+
|
|
2183
|
+
|
|
2184
|
+
|
|
2185
|
+
|
|
1987
2186
|
|
|
1988
2187
|
def on_mouse_move(self, event):
|
|
1989
2188
|
"""Handle mouse movement events."""
|
|
@@ -2070,6 +2269,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2070
2269
|
points = self.get_line_points(last_x, last_y, x, y)
|
|
2071
2270
|
|
|
2072
2271
|
# Paint at each point along the line
|
|
2272
|
+
|
|
2073
2273
|
for px, py in points:
|
|
2074
2274
|
if 0 <= px < width and 0 <= py < height:
|
|
2075
2275
|
self.paint_at_position(px, py, self.erase, channel)
|
|
@@ -2485,7 +2685,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2485
2685
|
# Process menu
|
|
2486
2686
|
process_menu = menubar.addMenu("Process")
|
|
2487
2687
|
calculate_menu = process_menu.addMenu("Calculate")
|
|
2488
|
-
calc_all_action = calculate_menu.addAction("Calculate
|
|
2688
|
+
calc_all_action = calculate_menu.addAction("Calculate Connectivity Network (Find Node-Edge-Node Network)")
|
|
2489
2689
|
calc_all_action.triggered.connect(self.show_calc_all_dialog)
|
|
2490
2690
|
calc_prox_action = calculate_menu.addAction("Calculate Proximity Network (connect nodes by distance)")
|
|
2491
2691
|
calc_prox_action.triggered.connect(self.show_calc_prox_dialog)
|
|
@@ -2551,8 +2751,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2551
2751
|
idoverlay_action.triggered.connect(self.show_idoverlay_dialog)
|
|
2552
2752
|
coloroverlay_action = overlay_menu.addAction("Color Nodes (or Edges)")
|
|
2553
2753
|
coloroverlay_action.triggered.connect(self.show_coloroverlay_dialog)
|
|
2554
|
-
searchoverlay_action = overlay_menu.addAction("Show Search Regions")
|
|
2555
|
-
searchoverlay_action.triggered.connect(self.show_search_dialog)
|
|
2754
|
+
#searchoverlay_action = overlay_menu.addAction("Show Search Regions")
|
|
2755
|
+
#searchoverlay_action.triggered.connect(self.show_search_dialog)
|
|
2556
2756
|
shuffle_action = overlay_menu.addAction("Shuffle")
|
|
2557
2757
|
shuffle_action.triggered.connect(self.show_shuffle_dialog)
|
|
2558
2758
|
arbitrary_action = image_menu.addAction("Select Objects")
|
|
@@ -2887,6 +3087,20 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2887
3087
|
elif trumper == '-':
|
|
2888
3088
|
for key, value in my_dict.items():
|
|
2889
3089
|
my_dict[key] = value[0]
|
|
3090
|
+
elif trumper == '/':
|
|
3091
|
+
new_dict = {}
|
|
3092
|
+
max_val = max(my_dict.keys()) + 1
|
|
3093
|
+
for key, value in my_dict.items():
|
|
3094
|
+
new_dict[key] = f'{value[0]}'
|
|
3095
|
+
if len(value) > 1:
|
|
3096
|
+
for i in range(1, len(value)):
|
|
3097
|
+
new_dict[max_val] = f'{value[i]}'
|
|
3098
|
+
try:
|
|
3099
|
+
my_network.node_centroids[max_val] = my_network.node_centroids[key]
|
|
3100
|
+
except:
|
|
3101
|
+
pass
|
|
3102
|
+
max_val += 1
|
|
3103
|
+
return new_dict
|
|
2890
3104
|
else:
|
|
2891
3105
|
for thing in my_dict:
|
|
2892
3106
|
val = my_dict[thing]
|
|
@@ -2925,12 +3139,16 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2925
3139
|
'Multiple IDs Detected',
|
|
2926
3140
|
'The node identities appear to contain multiple ids per node in a list.\n'
|
|
2927
3141
|
'If you desire one node ID to trump all others, enter it here.\n'
|
|
2928
|
-
'(Enter "-" to have the first IDs trump all others
|
|
3142
|
+
'(Enter "-" to have the first IDs trump all others)\n'
|
|
3143
|
+
'(Enter "/" to have multi-ID nodes be split into many nodes sharing a centroid)\n'
|
|
3144
|
+
'(Close this window to continue with multi-ID nodes)'
|
|
2929
3145
|
)
|
|
2930
3146
|
if not ok or trump_value.strip() == '':
|
|
2931
3147
|
trump_value = None
|
|
2932
3148
|
elif trump_value.upper() == '-':
|
|
2933
3149
|
trump_value = '-'
|
|
3150
|
+
elif trump_value.upper() == "/":
|
|
3151
|
+
trump_value = '/'
|
|
2934
3152
|
my_network.node_identities = uncork(my_network.node_identities, trump_value)
|
|
2935
3153
|
else:
|
|
2936
3154
|
trump_value = None
|
|
@@ -3038,76 +3256,78 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3038
3256
|
"",
|
|
3039
3257
|
QFileDialog.Option.ShowDirsOnly
|
|
3040
3258
|
)
|
|
3041
|
-
self.reset(nodes = True, network = True, xy_scale = 1, z_scale = 1, edges = True, search_region = True, network_overlay = True, id_overlay = True)
|
|
3042
3259
|
|
|
3260
|
+
if directory != "":
|
|
3043
3261
|
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
# Load image channels
|
|
3047
|
-
try:
|
|
3048
|
-
self.load_channel(0, my_network.nodes, True)
|
|
3049
|
-
except Exception as e:
|
|
3050
|
-
print(e)
|
|
3051
|
-
try:
|
|
3052
|
-
self.load_channel(1, my_network.edges, True)
|
|
3053
|
-
except Exception as e:
|
|
3054
|
-
print(e)
|
|
3055
|
-
try:
|
|
3056
|
-
self.load_channel(2, my_network.network_overlay, True)
|
|
3057
|
-
except Exception as e:
|
|
3058
|
-
print(e)
|
|
3059
|
-
try:
|
|
3060
|
-
self.load_channel(3, my_network.id_overlay, True)
|
|
3061
|
-
except Exception as e:
|
|
3062
|
-
print(e)
|
|
3063
|
-
|
|
3064
|
-
# Update slider range based on new data
|
|
3065
|
-
for channel in self.channel_data:
|
|
3066
|
-
if channel is not None:
|
|
3067
|
-
self.slice_slider.setEnabled(True)
|
|
3068
|
-
self.slice_slider.setMinimum(0)
|
|
3069
|
-
self.slice_slider.setMaximum(channel.shape[0] - 1)
|
|
3070
|
-
self.slice_slider.setValue(0)
|
|
3071
|
-
self.current_slice = 0
|
|
3072
|
-
break
|
|
3262
|
+
self.reset(network = True, xy_scale = 1, z_scale = 1, edges = True, network_overlay = True, id_overlay = True)
|
|
3073
3263
|
|
|
3074
|
-
|
|
3075
|
-
# Create empty DataFrame for network table if network_lists is None
|
|
3076
|
-
if not hasattr(my_network, 'network_lists') or my_network.network_lists is None:
|
|
3077
|
-
empty_df = pd.DataFrame(columns=['Node 1A', 'Node 1B', 'Edge 1C'])
|
|
3078
|
-
model = PandasModel(empty_df)
|
|
3079
|
-
self.network_table.setModel(model)
|
|
3080
|
-
else:
|
|
3081
|
-
model = PandasModel(my_network.network_lists)
|
|
3082
|
-
self.network_table.setModel(model)
|
|
3083
|
-
# Adjust column widths to content
|
|
3084
|
-
for column in range(model.columnCount(None)):
|
|
3085
|
-
self.network_table.resizeColumnToContents(column)
|
|
3264
|
+
my_network.assemble(directory)
|
|
3086
3265
|
|
|
3087
|
-
|
|
3266
|
+
# Load image channels
|
|
3088
3267
|
try:
|
|
3089
|
-
self.
|
|
3268
|
+
self.load_channel(0, my_network.nodes, True)
|
|
3090
3269
|
except Exception as e:
|
|
3091
|
-
print(
|
|
3092
|
-
|
|
3093
|
-
if hasattr(my_network, 'edge_centroids') and my_network.edge_centroids is not None:
|
|
3270
|
+
print(e)
|
|
3094
3271
|
try:
|
|
3095
|
-
self.
|
|
3272
|
+
self.load_channel(1, my_network.edges, True)
|
|
3096
3273
|
except Exception as e:
|
|
3097
|
-
print(
|
|
3098
|
-
|
|
3099
|
-
if hasattr(my_network, 'node_identities') and my_network.node_identities is not None:
|
|
3274
|
+
print(e)
|
|
3100
3275
|
try:
|
|
3101
|
-
self.
|
|
3276
|
+
self.load_channel(2, my_network.network_overlay, True)
|
|
3102
3277
|
except Exception as e:
|
|
3103
|
-
print(
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
if hasattr(my_network, 'communities') and my_network.communities is not None:
|
|
3278
|
+
print(e)
|
|
3107
3279
|
try:
|
|
3108
|
-
self.
|
|
3280
|
+
self.load_channel(3, my_network.id_overlay, True)
|
|
3109
3281
|
except Exception as e:
|
|
3110
|
-
print(
|
|
3282
|
+
print(e)
|
|
3283
|
+
|
|
3284
|
+
# Update slider range based on new data
|
|
3285
|
+
for channel in self.channel_data:
|
|
3286
|
+
if channel is not None:
|
|
3287
|
+
self.slice_slider.setEnabled(True)
|
|
3288
|
+
self.slice_slider.setMinimum(0)
|
|
3289
|
+
self.slice_slider.setMaximum(channel.shape[0] - 1)
|
|
3290
|
+
self.slice_slider.setValue(0)
|
|
3291
|
+
self.current_slice = 0
|
|
3292
|
+
break
|
|
3293
|
+
|
|
3294
|
+
# Display network_lists in the network table
|
|
3295
|
+
# Create empty DataFrame for network table if network_lists is None
|
|
3296
|
+
if not hasattr(my_network, 'network_lists') or my_network.network_lists is None:
|
|
3297
|
+
empty_df = pd.DataFrame(columns=['Node 1A', 'Node 1B', 'Edge 1C'])
|
|
3298
|
+
model = PandasModel(empty_df)
|
|
3299
|
+
self.network_table.setModel(model)
|
|
3300
|
+
else:
|
|
3301
|
+
model = PandasModel(my_network.network_lists)
|
|
3302
|
+
self.network_table.setModel(model)
|
|
3303
|
+
# Adjust column widths to content
|
|
3304
|
+
for column in range(model.columnCount(None)):
|
|
3305
|
+
self.network_table.resizeColumnToContents(column)
|
|
3306
|
+
|
|
3307
|
+
if hasattr(my_network, 'node_centroids') and my_network.node_centroids is not None:
|
|
3308
|
+
try:
|
|
3309
|
+
self.format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
3310
|
+
except Exception as e:
|
|
3311
|
+
print(f"Error loading node centroid table: {e}")
|
|
3312
|
+
|
|
3313
|
+
if hasattr(my_network, 'edge_centroids') and my_network.edge_centroids is not None:
|
|
3314
|
+
try:
|
|
3315
|
+
self.format_for_upperright_table(my_network.edge_centroids, 'EdgeID', ['Z', 'Y', 'X'], 'Edge Centroids')
|
|
3316
|
+
except Exception as e:
|
|
3317
|
+
print(f"Error loading edge centroid table: {e}")
|
|
3318
|
+
|
|
3319
|
+
if hasattr(my_network, 'node_identities') and my_network.node_identities is not None:
|
|
3320
|
+
try:
|
|
3321
|
+
self.format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
|
|
3322
|
+
except Exception as e:
|
|
3323
|
+
print(f"Error loading node identity table: {e}")
|
|
3324
|
+
|
|
3325
|
+
|
|
3326
|
+
if hasattr(my_network, 'communities') and my_network.communities is not None:
|
|
3327
|
+
try:
|
|
3328
|
+
self.format_for_upperright_table(my_network.communities, 'NodeID', 'Community', 'Node Communities')
|
|
3329
|
+
except Exception as e:
|
|
3330
|
+
print(f"Error loading node community table: {e}")
|
|
3111
3331
|
|
|
3112
3332
|
except Exception as e:
|
|
3113
3333
|
QMessageBox.critical(
|
|
@@ -3471,9 +3691,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3471
3691
|
|
|
3472
3692
|
if edges:
|
|
3473
3693
|
self.delete_channel(1, False)
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3694
|
+
try:
|
|
3695
|
+
if search_region:
|
|
3696
|
+
my_network.search_region = None
|
|
3697
|
+
except:
|
|
3698
|
+
pass
|
|
3477
3699
|
|
|
3478
3700
|
if network_overlay:
|
|
3479
3701
|
self.delete_channel(2, False)
|
|
@@ -3483,8 +3705,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3483
3705
|
|
|
3484
3706
|
|
|
3485
3707
|
|
|
3486
|
-
def save_network_3d(self, asbool
|
|
3487
|
-
|
|
3708
|
+
def save_network_3d(self, asbool=True):
|
|
3488
3709
|
try:
|
|
3489
3710
|
if asbool: # Save As
|
|
3490
3711
|
# First let user select parent directory
|
|
@@ -3494,25 +3715,28 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3494
3715
|
"",
|
|
3495
3716
|
QFileDialog.Option.ShowDirsOnly
|
|
3496
3717
|
)
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3718
|
+
if not parent_dir: # If user canceled the directory selection
|
|
3719
|
+
return # Exit the method early
|
|
3720
|
+
|
|
3721
|
+
# Prompt user for new folder name
|
|
3722
|
+
new_folder_name, ok = QInputDialog.getText(
|
|
3723
|
+
self,
|
|
3724
|
+
"New Folder",
|
|
3725
|
+
"Enter name for new output folder:"
|
|
3726
|
+
)
|
|
3505
3727
|
|
|
3728
|
+
# Check if user canceled the folder name dialog
|
|
3729
|
+
if not ok or not new_folder_name:
|
|
3730
|
+
return # Exit the method early
|
|
3731
|
+
|
|
3506
3732
|
else: # Save
|
|
3507
3733
|
parent_dir = None # Let the backend handle default save location
|
|
3508
3734
|
|
|
3509
3735
|
# Call appropriate save method
|
|
3510
|
-
if
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
my_network.dump(name = 'my_network')
|
|
3515
|
-
|
|
3736
|
+
if asbool:
|
|
3737
|
+
my_network.dump(parent_dir=parent_dir, name=new_folder_name)
|
|
3738
|
+
else:
|
|
3739
|
+
my_network.dump(name='my_network')
|
|
3516
3740
|
|
|
3517
3741
|
except Exception as e:
|
|
3518
3742
|
QMessageBox.critical(
|
|
@@ -3689,6 +3913,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3689
3913
|
else:
|
|
3690
3914
|
# Regular channel processing with colormap
|
|
3691
3915
|
# Calculate brightness/contrast limits from entire volume
|
|
3916
|
+
if self.min_max[channel][0] == None:
|
|
3917
|
+
self.min_max[channel][0] = np.min(channel)
|
|
3918
|
+
if self.min_max[channel][1] == None:
|
|
3919
|
+
self.min_max[channel][1] = np.max(channel)
|
|
3920
|
+
|
|
3692
3921
|
img_min = self.min_max[channel][0]
|
|
3693
3922
|
img_max = self.min_max[channel][1]
|
|
3694
3923
|
|
|
@@ -3699,6 +3928,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3699
3928
|
else:
|
|
3700
3929
|
vmin = img_min + (img_max - img_min) * self.channel_brightness[channel]['min']
|
|
3701
3930
|
vmax = img_min + (img_max - img_min) * self.channel_brightness[channel]['max']
|
|
3931
|
+
|
|
3932
|
+
|
|
3933
|
+
|
|
3702
3934
|
|
|
3703
3935
|
# Normalize the image safely
|
|
3704
3936
|
if vmin == vmax:
|
|
@@ -3827,7 +4059,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3827
4059
|
self.canvas.draw()
|
|
3828
4060
|
|
|
3829
4061
|
except:
|
|
3830
|
-
|
|
4062
|
+
import traceback
|
|
4063
|
+
print(traceback.format_exc())
|
|
3831
4064
|
|
|
3832
4065
|
def update_display_slice(self, channel, preserve_zoom=None):
|
|
3833
4066
|
"""Ultra minimal update that only changes the paint channel's data"""
|
|
@@ -4675,10 +4908,10 @@ class PropertiesDialog(QDialog):
|
|
|
4675
4908
|
self.id_overlay.setChecked(self.check_checked(my_network.id_overlay))
|
|
4676
4909
|
layout.addRow("Overlay 2 Status", self.id_overlay)
|
|
4677
4910
|
|
|
4678
|
-
self.search_region = QPushButton("search region")
|
|
4679
|
-
self.search_region.setCheckable(True)
|
|
4680
|
-
self.search_region.setChecked(self.check_checked(my_network.search_region))
|
|
4681
|
-
layout.addRow("Node Search Region Status", self.search_region)
|
|
4911
|
+
#self.search_region = QPushButton("search region")
|
|
4912
|
+
#self.search_region.setCheckable(True)
|
|
4913
|
+
#self.search_region.setChecked(self.check_checked(my_network.search_region))
|
|
4914
|
+
#layout.addRow("Node Search Region Status", self.search_region)
|
|
4682
4915
|
|
|
4683
4916
|
self.network = QPushButton("Network")
|
|
4684
4917
|
self.network.setCheckable(True)
|
|
@@ -4717,10 +4950,10 @@ class PropertiesDialog(QDialog):
|
|
|
4717
4950
|
edges = not self.edges.isChecked()
|
|
4718
4951
|
network_overlay = not self.network_overlay.isChecked()
|
|
4719
4952
|
id_overlay = not self.id_overlay.isChecked()
|
|
4720
|
-
search_region = not self.search_region.isChecked()
|
|
4953
|
+
#search_region = not self.search_region.isChecked()
|
|
4721
4954
|
network = not self.network.isChecked()
|
|
4722
4955
|
|
|
4723
|
-
self.parent().reset(nodes = nodes, edges = edges, network_overlay = network_overlay, id_overlay = id_overlay,
|
|
4956
|
+
self.parent().reset(nodes = nodes, edges = edges, network_overlay = network_overlay, id_overlay = id_overlay, network = network, xy_scale = xy_scale, z_scale = z_scale)
|
|
4724
4957
|
|
|
4725
4958
|
self.accept()
|
|
4726
4959
|
|
|
@@ -5670,6 +5903,11 @@ class NeighborIdentityDialog(QDialog):
|
|
|
5670
5903
|
self.search = QLineEdit("")
|
|
5671
5904
|
layout.addRow("Search Radius (Ignore if using network):", self.search)
|
|
5672
5905
|
|
|
5906
|
+
self.fastdil = QPushButton("Fast Dilate")
|
|
5907
|
+
self.fastdil.setCheckable(True)
|
|
5908
|
+
self.fastdil.setChecked(False)
|
|
5909
|
+
layout.addRow("(If not using network) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
5910
|
+
|
|
5673
5911
|
# Add Run button
|
|
5674
5912
|
run_button = QPushButton("Get Neighborhood Identity Distribution")
|
|
5675
5913
|
run_button.clicked.connect(self.neighborids)
|
|
@@ -5690,8 +5928,10 @@ class NeighborIdentityDialog(QDialog):
|
|
|
5690
5928
|
|
|
5691
5929
|
search = float(self.search.text()) if self.search.text().strip() else 0
|
|
5692
5930
|
|
|
5931
|
+
fastdil = self.fastdil.isChecked()
|
|
5932
|
+
|
|
5693
5933
|
|
|
5694
|
-
result, result2, title1, title2, densities = my_network.neighborhood_identities(root = root, directory = directory, mode = mode, search = search)
|
|
5934
|
+
result, result2, title1, title2, densities = my_network.neighborhood_identities(root = root, directory = directory, mode = mode, search = search, fastdil = fastdil)
|
|
5695
5935
|
|
|
5696
5936
|
self.parent().format_for_upperright_table(result, 'Node Identity', 'Amount', title = title1)
|
|
5697
5937
|
self.parent().format_for_upperright_table(result2, 'Node Identity', 'Proportion', title = title2)
|
|
@@ -5761,10 +6001,10 @@ class RadDialog(QDialog):
|
|
|
5761
6001
|
layout = QFormLayout(self)
|
|
5762
6002
|
|
|
5763
6003
|
# GPU checkbox (default False)
|
|
5764
|
-
self.GPU = QPushButton("GPU")
|
|
5765
|
-
self.GPU.setCheckable(True)
|
|
5766
|
-
self.GPU.setChecked(False)
|
|
5767
|
-
layout.addRow("Use GPU:", self.GPU)
|
|
6004
|
+
#self.GPU = QPushButton("GPU")
|
|
6005
|
+
#self.GPU.setCheckable(True)
|
|
6006
|
+
#self.GPU.setChecked(False)
|
|
6007
|
+
#layout.addRow("Use GPU:", self.GPU)
|
|
5768
6008
|
|
|
5769
6009
|
|
|
5770
6010
|
# Add Run button
|
|
@@ -5775,17 +6015,18 @@ class RadDialog(QDialog):
|
|
|
5775
6015
|
def rads(self):
|
|
5776
6016
|
|
|
5777
6017
|
try:
|
|
5778
|
-
GPU = self.GPU.isChecked()
|
|
6018
|
+
#GPU = self.GPU.isChecked() # <- I can never get these to be faster than parallel CPU *shrugs*
|
|
5779
6019
|
|
|
5780
6020
|
active_data = self.parent().channel_data[self.parent().active_channel]
|
|
5781
6021
|
|
|
5782
|
-
radii = n3d.estimate_object_radii(active_data, gpu=
|
|
6022
|
+
radii = n3d.estimate_object_radii(active_data, gpu=False, xy_scale = my_network.xy_scale, z_scale = my_network.z_scale)
|
|
5783
6023
|
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
6024
|
+
if self.parent().active_channel == 0:
|
|
6025
|
+
self.parent().radii_dict[0] = radii
|
|
6026
|
+
elif self.parent().active_channel == 1:
|
|
6027
|
+
self.parent().radii_dict[1] = radii
|
|
5787
6028
|
|
|
5788
|
-
self.parent().format_for_upperright_table(radii, title = '
|
|
6029
|
+
self.parent().format_for_upperright_table(radii, title = 'Largest Radii of Objects', metric='ObjectID', value='Largest Radius (Scaled)')
|
|
5789
6030
|
|
|
5790
6031
|
self.accept()
|
|
5791
6032
|
|
|
@@ -5818,6 +6059,11 @@ class InteractionDialog(QDialog):
|
|
|
5818
6059
|
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
5819
6060
|
layout.addRow("Execution Mode:", self.mode_selector)
|
|
5820
6061
|
|
|
6062
|
+
self.fastdil = QPushButton("Fast Dilate")
|
|
6063
|
+
self.fastdil.setCheckable(True)
|
|
6064
|
+
self.fastdil.setChecked(False)
|
|
6065
|
+
layout.addRow("(If not using network) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
6066
|
+
|
|
5821
6067
|
# Add Run button
|
|
5822
6068
|
run_button = QPushButton("Calculate")
|
|
5823
6069
|
run_button.clicked.connect(self.interaction)
|
|
@@ -5833,8 +6079,11 @@ class InteractionDialog(QDialog):
|
|
|
5833
6079
|
node_search = float(self.node_search.text()) if self.node_search.text() else 0
|
|
5834
6080
|
except ValueError:
|
|
5835
6081
|
node_search = 0
|
|
6082
|
+
|
|
6083
|
+
|
|
6084
|
+
fastdil = self.fastdil.isChecked()
|
|
5836
6085
|
|
|
5837
|
-
result = my_network.interactions(search = node_search, cores = accepted_mode)
|
|
6086
|
+
result = my_network.interactions(search = node_search, cores = accepted_mode, fastdil = fastdil)
|
|
5838
6087
|
|
|
5839
6088
|
self.parent().format_for_upperright_table(result, 'Node ID', ['Volume of Nearby Edge (Scaled)', 'Volume of Search Region'], title = 'Node/Edge Interactions')
|
|
5840
6089
|
|
|
@@ -5842,6 +6091,9 @@ class InteractionDialog(QDialog):
|
|
|
5842
6091
|
|
|
5843
6092
|
except Exception as e:
|
|
5844
6093
|
|
|
6094
|
+
import traceback
|
|
6095
|
+
print(traceback.format_exc())
|
|
6096
|
+
|
|
5845
6097
|
print(f"Error finding interactions: {e}")
|
|
5846
6098
|
|
|
5847
6099
|
|
|
@@ -6198,6 +6450,15 @@ class ResizeDialog(QDialog):
|
|
|
6198
6450
|
undo_button.clicked.connect(lambda: self.run_resize(undo = True))
|
|
6199
6451
|
layout.addRow(undo_button)
|
|
6200
6452
|
|
|
6453
|
+
if my_network.xy_scale != my_network.z_scale:
|
|
6454
|
+
norm_button_upsize = QPushButton(f"Normalize Scaling with Upsample")
|
|
6455
|
+
norm_button_upsize.clicked.connect(lambda: self.run_resize(upsize = True, special = True))
|
|
6456
|
+
layout.addRow(norm_button_upsize)
|
|
6457
|
+
|
|
6458
|
+
norm_button_downsize = QPushButton("Normalize Scaling with Downsample")
|
|
6459
|
+
norm_button_downsize.clicked.connect(lambda: self.run_resize(upsize = False, special = True))
|
|
6460
|
+
layout.addRow(norm_button_downsize)
|
|
6461
|
+
|
|
6201
6462
|
run_button = QPushButton("Run Resize")
|
|
6202
6463
|
run_button.clicked.connect(self.run_resize)
|
|
6203
6464
|
layout.addRow(run_button)
|
|
@@ -6209,7 +6470,7 @@ class ResizeDialog(QDialog):
|
|
|
6209
6470
|
self.xsize.setText("1")
|
|
6210
6471
|
self.ysize.setText("1")
|
|
6211
6472
|
|
|
6212
|
-
def run_resize(self, undo = False):
|
|
6473
|
+
def run_resize(self, undo = False, upsize = True, special = False):
|
|
6213
6474
|
try:
|
|
6214
6475
|
self.parent().resizing = True
|
|
6215
6476
|
# Get parameters
|
|
@@ -6224,7 +6485,27 @@ class ResizeDialog(QDialog):
|
|
|
6224
6485
|
return
|
|
6225
6486
|
|
|
6226
6487
|
resize = resize if resize is not None else (zsize, ysize, xsize)
|
|
6227
|
-
|
|
6488
|
+
|
|
6489
|
+
if special:
|
|
6490
|
+
if upsize:
|
|
6491
|
+
if (my_network.z_scale > my_network.xy_scale):
|
|
6492
|
+
# Z dimension needs to be stretched
|
|
6493
|
+
resize = [my_network.z_scale/my_network.xy_scale, 1, 1] # Scale factor for [z, y, x]
|
|
6494
|
+
cardinal = my_network.xy_scale
|
|
6495
|
+
elif (my_network.xy_scale > my_network.z_scale):
|
|
6496
|
+
# XY dimensions need to be stretched
|
|
6497
|
+
resize = [1, my_network.xy_scale/my_network.z_scale, my_network.xy_scale/my_network.z_scale] # Scale factor for [z, y, x]
|
|
6498
|
+
cardinal = my_network.z_scale
|
|
6499
|
+
else:
|
|
6500
|
+
if (my_network.z_scale > my_network.xy_scale):
|
|
6501
|
+
# XY dimension needs to be shrunk
|
|
6502
|
+
resize = [1, my_network.xy_scale/my_network.z_scale, my_network.xy_scale/my_network.z_scale] # Scale factor for [z, y, x]
|
|
6503
|
+
cardinal = my_network.z_scale
|
|
6504
|
+
elif (my_network.xy_scale > my_network.z_scale):
|
|
6505
|
+
# Z dimensions need to be shrunk
|
|
6506
|
+
resize = [my_network.z_scale/my_network.xy_scale, 1, 1] # Scale factor for [z, y, x]
|
|
6507
|
+
cardinal = my_network.xy_scale
|
|
6508
|
+
|
|
6228
6509
|
# Get the shape from whichever array exists
|
|
6229
6510
|
array_shape = None
|
|
6230
6511
|
if my_network.nodes is not None:
|
|
@@ -6306,14 +6587,18 @@ class ResizeDialog(QDialog):
|
|
|
6306
6587
|
self.parent().slice_slider.setMaximum(channel.shape[0] - 1)
|
|
6307
6588
|
break
|
|
6308
6589
|
|
|
6309
|
-
if
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6590
|
+
if not special:
|
|
6591
|
+
if isinstance(resize, (int, float)):
|
|
6592
|
+
my_network.xy_scale = my_network.xy_scale/resize
|
|
6593
|
+
my_network.z_scale = my_network.z_scale/resize
|
|
6594
|
+
print("xy_scales and z_scales have been adjusted per resample. Check image -> properties to manually reset them to 1 if desired.")
|
|
6595
|
+
else:
|
|
6596
|
+
my_network.xy_scale = my_network.xy_scale/resize[1]
|
|
6597
|
+
my_network.z_scale = my_network.z_scale/resize[0]
|
|
6598
|
+
print("xy_scales and z_scales have been adjusted per resample. Check image -> properties to manually reset them to 1 if desired. Note that xy_scale will not correspond if you made your XY plane a non-square.")
|
|
6313
6599
|
else:
|
|
6314
|
-
my_network.xy_scale =
|
|
6315
|
-
my_network.z_scale =
|
|
6316
|
-
print("xy_scales and z_scales have been adjusted per resample. Check image -> properties to manually reset them to 1 if desired. Note that xy_scale will not correspond if you made your XY plane a non-square.")
|
|
6600
|
+
my_network.xy_scale = cardinal
|
|
6601
|
+
my_network.z_scale = cardinal
|
|
6317
6602
|
|
|
6318
6603
|
try:
|
|
6319
6604
|
if my_network.node_centroids is not None:
|
|
@@ -6587,9 +6872,14 @@ class ThresholdDialog(QDialog):
|
|
|
6587
6872
|
|
|
6588
6873
|
# Add ML button
|
|
6589
6874
|
ML = QPushButton("Machine Learning")
|
|
6590
|
-
ML.clicked.connect(self.start_ml)
|
|
6875
|
+
ML.clicked.connect(lambda: self.start_ml(GPU = False))
|
|
6591
6876
|
layout.addRow(ML)
|
|
6592
6877
|
|
|
6878
|
+
# Add ML button
|
|
6879
|
+
#ML2 = QPushButton("Machine Learning (GPU)")
|
|
6880
|
+
#ML2.clicked.connect(lambda: self.start_ml(GPU = True))
|
|
6881
|
+
#layout.addRow(ML2)
|
|
6882
|
+
|
|
6593
6883
|
|
|
6594
6884
|
def thresh_mode(self):
|
|
6595
6885
|
|
|
@@ -6614,7 +6904,7 @@ class ThresholdDialog(QDialog):
|
|
|
6614
6904
|
except:
|
|
6615
6905
|
pass
|
|
6616
6906
|
|
|
6617
|
-
def start_ml(self):
|
|
6907
|
+
def start_ml(self, GPU = False):
|
|
6618
6908
|
|
|
6619
6909
|
|
|
6620
6910
|
if self.parent().channel_data[2] is not None or self.parent().channel_data[3] is not None or self.parent().highlight_overlay is not None:
|
|
@@ -6630,10 +6920,16 @@ class ThresholdDialog(QDialog):
|
|
|
6630
6920
|
)
|
|
6631
6921
|
return
|
|
6632
6922
|
|
|
6923
|
+
try:
|
|
6924
|
+
import cupy as cp
|
|
6925
|
+
except:
|
|
6926
|
+
print("Cupy import failed, using CPU version")
|
|
6927
|
+
GPU = False
|
|
6928
|
+
|
|
6633
6929
|
if self.parent().mini_overlay_data is not None:
|
|
6634
6930
|
self.parent().mini_overlay_data = None
|
|
6635
6931
|
|
|
6636
|
-
self.parent().machine_window = MachineWindow(self.parent())
|
|
6932
|
+
self.parent().machine_window = MachineWindow(self.parent(), GPU = GPU)
|
|
6637
6933
|
self.parent().machine_window.show() # Non-modal window
|
|
6638
6934
|
self.accept()
|
|
6639
6935
|
|
|
@@ -6650,7 +6946,7 @@ class ThresholdDialog(QDialog):
|
|
|
6650
6946
|
|
|
6651
6947
|
class MachineWindow(QMainWindow):
|
|
6652
6948
|
|
|
6653
|
-
def __init__(self, parent=None):
|
|
6949
|
+
def __init__(self, parent=None, GPU = False):
|
|
6654
6950
|
super().__init__(parent)
|
|
6655
6951
|
|
|
6656
6952
|
self.setWindowTitle("Threshold")
|
|
@@ -6668,6 +6964,9 @@ class MachineWindow(QMainWindow):
|
|
|
6668
6964
|
|
|
6669
6965
|
if self.parent().pen_button.isChecked(): #Disable the pen mode if the user is in it because the segmenter pen forks it
|
|
6670
6966
|
self.parent().pen_button.click()
|
|
6967
|
+
self.parent().threed = False
|
|
6968
|
+
self.parent().can = False
|
|
6969
|
+
self.parent().last_change = None
|
|
6671
6970
|
|
|
6672
6971
|
self.parent().pen_button.setEnabled(False)
|
|
6673
6972
|
|
|
@@ -6743,11 +7042,8 @@ class MachineWindow(QMainWindow):
|
|
|
6743
7042
|
# Group 2: Processing Options (GPU)
|
|
6744
7043
|
processing_group = QGroupBox("Processing Options")
|
|
6745
7044
|
processing_layout = QHBoxLayout()
|
|
6746
|
-
|
|
6747
|
-
self.GPU
|
|
6748
|
-
self.GPU.setChecked(False)
|
|
6749
|
-
self.GPU.clicked.connect(self.toggle_GPU)
|
|
6750
|
-
self.use_gpu = False
|
|
7045
|
+
|
|
7046
|
+
self.use_gpu = GPU
|
|
6751
7047
|
self.two = QPushButton("Train By 2D Slice Patterns")
|
|
6752
7048
|
self.two.setCheckable(True)
|
|
6753
7049
|
self.two.setChecked(False)
|
|
@@ -6796,7 +7092,8 @@ class MachineWindow(QMainWindow):
|
|
|
6796
7092
|
|
|
6797
7093
|
# Add all groups to main layout
|
|
6798
7094
|
main_layout.addWidget(drawing_group)
|
|
6799
|
-
|
|
7095
|
+
if not GPU:
|
|
7096
|
+
main_layout.addWidget(processing_group)
|
|
6800
7097
|
main_layout.addWidget(training_group)
|
|
6801
7098
|
main_layout.addWidget(segmentation_group)
|
|
6802
7099
|
|
|
@@ -6806,10 +7103,16 @@ class MachineWindow(QMainWindow):
|
|
|
6806
7103
|
self.trained = False
|
|
6807
7104
|
self.previewing = False
|
|
6808
7105
|
|
|
7106
|
+
if not GPU:
|
|
7107
|
+
self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=False)
|
|
7108
|
+
else:
|
|
7109
|
+
self.segmenter = segmenter_GPU.InteractiveSegmenter(active_data)
|
|
6809
7110
|
|
|
6810
|
-
self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=True)
|
|
6811
7111
|
self.segmentation_worker = None
|
|
6812
7112
|
|
|
7113
|
+
self.fore_button.click()
|
|
7114
|
+
self.fore_button.click()
|
|
7115
|
+
|
|
6813
7116
|
def toggle_lock(self):
|
|
6814
7117
|
|
|
6815
7118
|
self.mem_lock = self.lock_button.isChecked()
|
|
@@ -6835,12 +7138,6 @@ class MachineWindow(QMainWindow):
|
|
|
6835
7138
|
pass
|
|
6836
7139
|
|
|
6837
7140
|
|
|
6838
|
-
|
|
6839
|
-
def toggle_GPU(self):
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
self.use_gpu = self.GPU.isChecked()
|
|
6843
|
-
|
|
6844
7141
|
def toggle_two(self):
|
|
6845
7142
|
if self.two.isChecked():
|
|
6846
7143
|
# If button two is checked, ensure button three is unchecked
|
|
@@ -6896,6 +7193,8 @@ class MachineWindow(QMainWindow):
|
|
|
6896
7193
|
self.parent().zoom_mode = False
|
|
6897
7194
|
self.parent().update_brush_cursor()
|
|
6898
7195
|
else:
|
|
7196
|
+
self.threed = False
|
|
7197
|
+
self.can = False
|
|
6899
7198
|
self.parent().zoom_button.click()
|
|
6900
7199
|
|
|
6901
7200
|
def silence_button(self):
|
|
@@ -6918,6 +7217,8 @@ class MachineWindow(QMainWindow):
|
|
|
6918
7217
|
self.trained = True
|
|
6919
7218
|
except Exception as e:
|
|
6920
7219
|
print("Error training. Perhaps you forgot both foreground and background markers? I need both!")
|
|
7220
|
+
import traceback
|
|
7221
|
+
traceback.print_exc()
|
|
6921
7222
|
except MemoryError:
|
|
6922
7223
|
QMessageBox.critical(
|
|
6923
7224
|
self,
|
|
@@ -7580,6 +7881,12 @@ class SmartDilateDialog(QDialog):
|
|
|
7580
7881
|
self.GPU.setChecked(False)
|
|
7581
7882
|
layout.addRow("Use GPU:", self.GPU)
|
|
7582
7883
|
|
|
7884
|
+
# dt checkbox (default False)
|
|
7885
|
+
self.predt = QPushButton("Pre-DT")
|
|
7886
|
+
self.predt.setCheckable(True)
|
|
7887
|
+
self.predt.setChecked(False)
|
|
7888
|
+
layout.addRow("Use Distance Transform for Predilation (Better at Large Dilations):", self.predt)
|
|
7889
|
+
|
|
7583
7890
|
self.down_factor = QLineEdit("")
|
|
7584
7891
|
layout.addRow("Internal Downsample for GPU (if needed):", self.down_factor)
|
|
7585
7892
|
|
|
@@ -7594,11 +7901,12 @@ class SmartDilateDialog(QDialog):
|
|
|
7594
7901
|
|
|
7595
7902
|
GPU = self.GPU.isChecked()
|
|
7596
7903
|
down_factor = float(self.down_factor.text()) if self.down_factor.text().strip() else None
|
|
7904
|
+
predt = not self.predt.isChecked()
|
|
7597
7905
|
active_data, amount, xy_scale, z_scale = self.params
|
|
7598
7906
|
|
|
7599
7907
|
dilate_xy, dilate_z = n3d.dilation_length_to_pixels(xy_scale, z_scale, amount, amount)
|
|
7600
7908
|
|
|
7601
|
-
result = sdl.smart_dilate(active_data, dilate_xy, dilate_z, GPU = GPU, predownsample = down_factor)
|
|
7909
|
+
result = sdl.smart_dilate(active_data, dilate_xy, dilate_z, GPU = GPU, predownsample = down_factor, fast_dil = predt, use_dt_dil_amount = amount, xy_scale = xy_scale, z_scale = z_scale)
|
|
7602
7910
|
|
|
7603
7911
|
self.parent().load_channel(self.parent().active_channel, result, True)
|
|
7604
7912
|
self.accept()
|
|
@@ -7634,7 +7942,7 @@ class DilateDialog(QDialog):
|
|
|
7634
7942
|
|
|
7635
7943
|
# Add mode selection dropdown
|
|
7636
7944
|
self.mode_selector = QComboBox()
|
|
7637
|
-
self.mode_selector.addItems(["Binary
|
|
7945
|
+
self.mode_selector.addItems(["Pseudo3D Binary Kernels (For Fast, small dilations)", "Preserve Labels (slower)", "Distance Transform-Based (Slower but more accurate at larger dilations)"])
|
|
7638
7946
|
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
7639
7947
|
layout.addRow("Execution Mode:", self.mode_selector)
|
|
7640
7948
|
|
|
@@ -7684,18 +7992,17 @@ class DilateDialog(QDialog):
|
|
|
7684
7992
|
return
|
|
7685
7993
|
|
|
7686
7994
|
if accepted_mode == 2:
|
|
7687
|
-
|
|
7995
|
+
result = n3d.dilate_3D_dt(active_data, amount, xy_scaling = xy_scale, z_scaling = z_scale)
|
|
7688
7996
|
else:
|
|
7689
|
-
recursive = False
|
|
7690
7997
|
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7998
|
+
# Call dilate method with parameters
|
|
7999
|
+
result = n3d.dilate(
|
|
8000
|
+
active_data,
|
|
8001
|
+
amount,
|
|
8002
|
+
xy_scale = xy_scale,
|
|
8003
|
+
z_scale = z_scale)
|
|
8004
|
+
|
|
8005
|
+
result = result * 255
|
|
7699
8006
|
|
|
7700
8007
|
# Update both the display data and the network object
|
|
7701
8008
|
self.parent().load_channel(self.parent().active_channel, result, True)
|
|
@@ -7739,6 +8046,12 @@ class ErodeDialog(QDialog):
|
|
|
7739
8046
|
self.z_scale = QLineEdit(z_scale)
|
|
7740
8047
|
layout.addRow("z_scale:", self.z_scale)
|
|
7741
8048
|
|
|
8049
|
+
# Add mode selection dropdown
|
|
8050
|
+
self.mode_selector = QComboBox()
|
|
8051
|
+
self.mode_selector.addItems(["Pseudo3D Binary Kernels (For Fast, small erosions)", "Distance Transform-Based (Slower but more accurate at larger dilations)"])
|
|
8052
|
+
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
8053
|
+
layout.addRow("Execution Mode:", self.mode_selector)
|
|
8054
|
+
|
|
7742
8055
|
# Add Run button
|
|
7743
8056
|
run_button = QPushButton("Run Erode")
|
|
7744
8057
|
run_button.clicked.connect(self.run_erode)
|
|
@@ -7769,6 +8082,8 @@ class ErodeDialog(QDialog):
|
|
|
7769
8082
|
z_scale = float(self.z_scale.text()) if self.z_scale.text() else 1
|
|
7770
8083
|
except ValueError:
|
|
7771
8084
|
z_scale = 1
|
|
8085
|
+
|
|
8086
|
+
mode = self.mode_selector.currentIndex()
|
|
7772
8087
|
|
|
7773
8088
|
# Get the active channel data from parent
|
|
7774
8089
|
active_data = self.parent().channel_data[self.parent().active_channel]
|
|
@@ -7781,6 +8096,7 @@ class ErodeDialog(QDialog):
|
|
|
7781
8096
|
amount,
|
|
7782
8097
|
xy_scale = xy_scale,
|
|
7783
8098
|
z_scale = z_scale,
|
|
8099
|
+
mode = mode
|
|
7784
8100
|
)
|
|
7785
8101
|
|
|
7786
8102
|
|
|
@@ -8182,8 +8498,7 @@ class WatershedDialog(QDialog):
|
|
|
8182
8498
|
self.accept()
|
|
8183
8499
|
|
|
8184
8500
|
except Exception as e:
|
|
8185
|
-
|
|
8186
|
-
print(traceback.format_exc())
|
|
8501
|
+
|
|
8187
8502
|
QMessageBox.critical(
|
|
8188
8503
|
self,
|
|
8189
8504
|
"Error",
|
|
@@ -8343,20 +8658,9 @@ class GenNodesDialog(QDialog):
|
|
|
8343
8658
|
layout = QFormLayout(self)
|
|
8344
8659
|
self.called = called
|
|
8345
8660
|
|
|
8346
|
-
self.
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
self.comp_dil = QLineEdit("0")
|
|
8350
|
-
layout.addRow("Voxel distance to merge nearby nodes (Int - compensates for multi-branch identification along thick branch regions):", self.comp_dil)
|
|
8351
|
-
|
|
8352
|
-
self.max_vol = QLineEdit("0")
|
|
8353
|
-
layout.addRow("Maximum Voxel Volume of Vertices to Retain (int - Compensates for skeleton looping - occurs before any node merging - the smallest objects are always 27 voxels):", self.max_vol)
|
|
8354
|
-
|
|
8355
|
-
# auto checkbox (default True)
|
|
8356
|
-
self.auto = QPushButton("Auto")
|
|
8357
|
-
self.auto.setCheckable(True)
|
|
8358
|
-
self.auto.setChecked(False)
|
|
8359
|
-
layout.addRow("Attempt to Auto Correct Skeleton Looping:", self.auto)
|
|
8661
|
+
self.directory = QLineEdit()
|
|
8662
|
+
self.directory.setPlaceholderText("Leave empty to save in active dir")
|
|
8663
|
+
layout.addRow("Output Directory:", self.directory)
|
|
8360
8664
|
|
|
8361
8665
|
if not down_factor:
|
|
8362
8666
|
down_factor = None
|
|
@@ -8371,16 +8675,33 @@ class GenNodesDialog(QDialog):
|
|
|
8371
8675
|
self.down_factor = down_factor[0]
|
|
8372
8676
|
self.cubic = down_factor[1]
|
|
8373
8677
|
|
|
8374
|
-
self.
|
|
8375
|
-
|
|
8376
|
-
|
|
8678
|
+
self.branch_removal = QLineEdit("0")
|
|
8679
|
+
layout.addRow("Skeleton Voxel Branch Length to Remove (int) (Compensates for spines off medial axis):", self.branch_removal)
|
|
8680
|
+
|
|
8681
|
+
self.max_vol = QLineEdit("0")
|
|
8682
|
+
layout.addRow("Maximum Voxel Volume of Vertices to Retain (int - Compensates for skeleton looping - occurs before any node merging - the smallest objects are always 27 voxels):", self.max_vol)
|
|
8683
|
+
|
|
8684
|
+
self.comp_dil = QLineEdit("0")
|
|
8685
|
+
layout.addRow("Voxel distance to merge nearby nodes (Int - compensates for multi-branch identification along thick branch regions):", self.comp_dil)
|
|
8686
|
+
|
|
8687
|
+
self.fast_dil = QPushButton("Fast-Dil")
|
|
8688
|
+
self.fast_dil.setCheckable(True)
|
|
8689
|
+
self.fast_dil.setChecked(True)
|
|
8690
|
+
layout.addRow("(If using above) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fast_dil)
|
|
8691
|
+
|
|
8692
|
+
# auto checkbox (default True)
|
|
8693
|
+
self.auto = QPushButton("Auto")
|
|
8694
|
+
self.auto.setCheckable(True)
|
|
8695
|
+
self.auto.setChecked(True)
|
|
8696
|
+
layout.addRow("Attempt to Auto Correct Skeleton Looping:", self.auto)
|
|
8697
|
+
|
|
8377
8698
|
|
|
8378
8699
|
# retain checkbox (default True)
|
|
8379
8700
|
if not called:
|
|
8380
8701
|
self.retain = QPushButton("Retain")
|
|
8381
8702
|
self.retain.setCheckable(True)
|
|
8382
8703
|
self.retain.setChecked(True)
|
|
8383
|
-
layout.addRow("Retain Original Edges? (Will be moved to overlay 2):", self.retain)
|
|
8704
|
+
#layout.addRow("Retain Original Edges? (Will be moved to overlay 2):", self.retain)
|
|
8384
8705
|
else:
|
|
8385
8706
|
self.retain = False
|
|
8386
8707
|
|
|
@@ -8436,6 +8757,8 @@ class GenNodesDialog(QDialog):
|
|
|
8436
8757
|
|
|
8437
8758
|
auto = self.auto.isChecked()
|
|
8438
8759
|
|
|
8760
|
+
fastdil = self.fast_dil.isChecked()
|
|
8761
|
+
|
|
8439
8762
|
|
|
8440
8763
|
if auto:
|
|
8441
8764
|
my_network.edges = n3d.skeletonize(my_network.edges)
|
|
@@ -8449,7 +8772,8 @@ class GenNodesDialog(QDialog):
|
|
|
8449
8772
|
comp_dil=comp_dil,
|
|
8450
8773
|
down_factor=down_factor,
|
|
8451
8774
|
order = order,
|
|
8452
|
-
return_skele = True
|
|
8775
|
+
return_skele = True,
|
|
8776
|
+
fastdil = fastdil
|
|
8453
8777
|
|
|
8454
8778
|
)
|
|
8455
8779
|
|
|
@@ -8476,7 +8800,7 @@ class GenNodesDialog(QDialog):
|
|
|
8476
8800
|
|
|
8477
8801
|
self.parent().load_channel(0, channel_data = result, data = True)
|
|
8478
8802
|
|
|
8479
|
-
if retain:
|
|
8803
|
+
if retain and self.called:
|
|
8480
8804
|
self.parent().load_channel(3, channel_data = my_network.edges, data = True)
|
|
8481
8805
|
|
|
8482
8806
|
|
|
@@ -8487,6 +8811,9 @@ class GenNodesDialog(QDialog):
|
|
|
8487
8811
|
|
|
8488
8812
|
except Exception as e:
|
|
8489
8813
|
|
|
8814
|
+
import traceback
|
|
8815
|
+
print(traceback.format_exc())
|
|
8816
|
+
|
|
8490
8817
|
|
|
8491
8818
|
QMessageBox.critical(
|
|
8492
8819
|
self,
|
|
@@ -8581,7 +8908,7 @@ class BranchDialog(QDialog):
|
|
|
8581
8908
|
|
|
8582
8909
|
temp_network = n3d.Network_3D(nodes = output)
|
|
8583
8910
|
|
|
8584
|
-
temp_network.morph_proximity(search =
|
|
8911
|
+
temp_network.morph_proximity(search = [3,3], fastdil = True) #Detect network of nearby branches
|
|
8585
8912
|
|
|
8586
8913
|
temp_network.community_partition(weighted = False, style = 1, dostats = False) #Find communities with louvain, unweighted params
|
|
8587
8914
|
|
|
@@ -9013,8 +9340,6 @@ class CentroidDialog(QDialog):
|
|
|
9013
9340
|
class CalcAllDialog(QDialog):
|
|
9014
9341
|
# Class variables to store previous settings
|
|
9015
9342
|
prev_directory = ""
|
|
9016
|
-
prev_xy_scale = "1"
|
|
9017
|
-
prev_z_scale = "1"
|
|
9018
9343
|
prev_search = ""
|
|
9019
9344
|
prev_diledge = ""
|
|
9020
9345
|
prev_down_factor = ""
|
|
@@ -9024,7 +9349,7 @@ class CalcAllDialog(QDialog):
|
|
|
9024
9349
|
prev_gpu = True
|
|
9025
9350
|
prev_label_nodes = True
|
|
9026
9351
|
prev_inners = True
|
|
9027
|
-
|
|
9352
|
+
prev_fastdil = False
|
|
9028
9353
|
prev_overlays = False
|
|
9029
9354
|
prev_updates = True
|
|
9030
9355
|
|
|
@@ -9041,10 +9366,10 @@ class CalcAllDialog(QDialog):
|
|
|
9041
9366
|
layout.addRow("Output Directory:", self.directory)
|
|
9042
9367
|
|
|
9043
9368
|
# Load previous values for all inputs
|
|
9044
|
-
self.xy_scale = QLineEdit(
|
|
9369
|
+
self.xy_scale = QLineEdit(f'{my_network.xy_scale}')
|
|
9045
9370
|
layout.addRow("xy_scale:", self.xy_scale)
|
|
9046
9371
|
|
|
9047
|
-
self.z_scale = QLineEdit(
|
|
9372
|
+
self.z_scale = QLineEdit(f'{my_network.z_scale}')
|
|
9048
9373
|
layout.addRow("z_scale:", self.z_scale)
|
|
9049
9374
|
|
|
9050
9375
|
self.search = QLineEdit(self.prev_search)
|
|
@@ -9087,10 +9412,10 @@ class CalcAllDialog(QDialog):
|
|
|
9087
9412
|
self.inners.setChecked(self.prev_inners)
|
|
9088
9413
|
layout.addRow("Use Inner Edges:", self.inners)
|
|
9089
9414
|
|
|
9090
|
-
self.
|
|
9091
|
-
self.
|
|
9092
|
-
self.
|
|
9093
|
-
layout.addRow("
|
|
9415
|
+
self.fastdil = QPushButton("Fast Dilate")
|
|
9416
|
+
self.fastdil.setCheckable(True)
|
|
9417
|
+
self.fastdil.setChecked(self.prev_fastdil)
|
|
9418
|
+
layout.addRow("Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
9094
9419
|
|
|
9095
9420
|
self.overlays = QPushButton("Overlays")
|
|
9096
9421
|
self.overlays.setCheckable(True)
|
|
@@ -9161,7 +9486,7 @@ class CalcAllDialog(QDialog):
|
|
|
9161
9486
|
gpu = self.gpu.isChecked()
|
|
9162
9487
|
label_nodes = self.label_nodes.isChecked()
|
|
9163
9488
|
inners = self.inners.isChecked()
|
|
9164
|
-
|
|
9489
|
+
fastdil = self.fastdil.isChecked()
|
|
9165
9490
|
overlays = self.overlays.isChecked()
|
|
9166
9491
|
update = self.update.isChecked()
|
|
9167
9492
|
|
|
@@ -9184,13 +9509,11 @@ class CalcAllDialog(QDialog):
|
|
|
9184
9509
|
GPU=gpu,
|
|
9185
9510
|
label_nodes=label_nodes,
|
|
9186
9511
|
inners=inners,
|
|
9187
|
-
|
|
9512
|
+
fast_dil=fastdil
|
|
9188
9513
|
)
|
|
9189
9514
|
|
|
9190
9515
|
# Store current values as previous values
|
|
9191
9516
|
CalcAllDialog.prev_directory = self.directory.text()
|
|
9192
|
-
CalcAllDialog.prev_xy_scale = self.xy_scale.text()
|
|
9193
|
-
CalcAllDialog.prev_z_scale = self.z_scale.text()
|
|
9194
9517
|
CalcAllDialog.prev_search = self.search.text()
|
|
9195
9518
|
CalcAllDialog.prev_diledge = self.diledge.text()
|
|
9196
9519
|
CalcAllDialog.prev_down_factor = self.down_factor.text()
|
|
@@ -9200,22 +9523,22 @@ class CalcAllDialog(QDialog):
|
|
|
9200
9523
|
CalcAllDialog.prev_gpu = self.gpu.isChecked()
|
|
9201
9524
|
CalcAllDialog.prev_label_nodes = self.label_nodes.isChecked()
|
|
9202
9525
|
CalcAllDialog.prev_inners = self.inners.isChecked()
|
|
9203
|
-
CalcAllDialog.
|
|
9526
|
+
CalcAllDialog.prev_fastdil = self.fastdil.isChecked()
|
|
9204
9527
|
CalcAllDialog.prev_overlays = self.overlays.isChecked()
|
|
9205
9528
|
CalcAllDialog.prev_updates = self.update.isChecked()
|
|
9206
9529
|
|
|
9207
9530
|
|
|
9208
9531
|
# Update both the display data and the network object
|
|
9209
9532
|
if update:
|
|
9210
|
-
self.parent().
|
|
9211
|
-
self.parent().
|
|
9533
|
+
self.parent().load_channel(0, my_network.nodes, True)
|
|
9534
|
+
self.parent().load_channel(1, my_network.edges, True)
|
|
9212
9535
|
else:
|
|
9213
9536
|
my_network.nodes = temp_nodes.copy()
|
|
9214
9537
|
del temp_nodes
|
|
9215
9538
|
my_network.edges = temp_edges.copy()
|
|
9216
9539
|
del temp_edges
|
|
9217
|
-
self.parent().
|
|
9218
|
-
self.parent().
|
|
9540
|
+
self.parent().load_channel(0, my_network.nodes, True)
|
|
9541
|
+
self.parent().load_channel(1, my_network.edges, True)
|
|
9219
9542
|
|
|
9220
9543
|
|
|
9221
9544
|
# Then handle overlays
|
|
@@ -9228,8 +9551,8 @@ class CalcAllDialog(QDialog):
|
|
|
9228
9551
|
my_network.id_overlay = my_network.draw_node_indices(directory=directory)
|
|
9229
9552
|
|
|
9230
9553
|
# Update channel data
|
|
9231
|
-
self.parent().
|
|
9232
|
-
self.parent().
|
|
9554
|
+
self.parent().load_channel(2, my_network.network_overlay, True)
|
|
9555
|
+
self.parent().load_channel(3, my_network.id_overlay, True)
|
|
9233
9556
|
|
|
9234
9557
|
# Enable the overlay channel buttons
|
|
9235
9558
|
self.parent().channel_buttons[2].setEnabled(True)
|
|
@@ -9315,6 +9638,11 @@ class ProxDialog(QDialog):
|
|
|
9315
9638
|
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
9316
9639
|
layout.addRow("Execution Mode:", self.mode_selector)
|
|
9317
9640
|
|
|
9641
|
+
self.fastdil = QPushButton("Fast Dilate")
|
|
9642
|
+
self.fastdil.setCheckable(True)
|
|
9643
|
+
self.fastdil.setChecked(False)
|
|
9644
|
+
layout.addRow("(If using morphological) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
|
|
9645
|
+
|
|
9318
9646
|
if my_network.node_identities is not None:
|
|
9319
9647
|
self.id_selector = QComboBox()
|
|
9320
9648
|
# Add all options from id dictionary
|
|
@@ -9378,7 +9706,8 @@ class ProxDialog(QDialog):
|
|
|
9378
9706
|
except ValueError:
|
|
9379
9707
|
search = None
|
|
9380
9708
|
|
|
9381
|
-
overlays = self.overlays.isChecked()
|
|
9709
|
+
overlays = self.overlays.isChecked()
|
|
9710
|
+
fastdil = self.fastdil.isChecked()
|
|
9382
9711
|
|
|
9383
9712
|
my_network.xy_scale = xy_scale
|
|
9384
9713
|
my_network.z_scale = z_scale
|
|
@@ -9389,7 +9718,7 @@ class ProxDialog(QDialog):
|
|
|
9389
9718
|
my_network.nodes, _ = n3d.label_objects(my_network.nodes)
|
|
9390
9719
|
if my_network.node_centroids is None:
|
|
9391
9720
|
self.parent().show_centroid_dialog()
|
|
9392
|
-
my_network.morph_proximity(search = search, targets = targets)
|
|
9721
|
+
my_network.morph_proximity(search = search, targets = targets, fastdil = fastdil)
|
|
9393
9722
|
|
|
9394
9723
|
self.parent().load_channel(0, channel_data = my_network.nodes, data = True)
|
|
9395
9724
|
elif mode == 0:
|