nettracer3d 0.5.3__py3-none-any.whl → 0.5.5__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 +42 -0
- nettracer3d/nettracer.py +23 -0
- nettracer3d/nettracer_gui.py +556 -99
- nettracer3d/segmenter.py +577 -80
- {nettracer3d-0.5.3.dist-info → nettracer3d-0.5.5.dist-info}/METADATA +7 -6
- {nettracer3d-0.5.3.dist-info → nettracer3d-0.5.5.dist-info}/RECORD +10 -10
- {nettracer3d-0.5.3.dist-info → nettracer3d-0.5.5.dist-info}/WHEEL +1 -1
- {nettracer3d-0.5.3.dist-info → nettracer3d-0.5.5.dist-info}/LICENSE +0 -0
- {nettracer3d-0.5.3.dist-info → nettracer3d-0.5.5.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.5.3.dist-info → nettracer3d-0.5.5.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -397,6 +397,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
397
397
|
# Initialize highlight overlay
|
|
398
398
|
self.highlight_overlay = None
|
|
399
399
|
self.highlight_bounds = None # Store bounds for positioning
|
|
400
|
+
self.mini_overlay = False # If the program is currently drawing the overlay by frame this will be true
|
|
401
|
+
self.mini_overlay_data = None #Actual data for mini overlay
|
|
402
|
+
self.mini_thresh = (500*500*500) # Array volume to start using mini overlays for
|
|
400
403
|
|
|
401
404
|
def start_left_scroll(self):
|
|
402
405
|
"""Start scrolling left when left arrow is pressed."""
|
|
@@ -443,6 +446,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
443
446
|
edge_indices (list): List of edge indices to highlight
|
|
444
447
|
"""
|
|
445
448
|
|
|
449
|
+
self.mini_overlay = False #If this method is ever being called, it means we are rendering the entire overlay so mini overlay needs to reset.
|
|
450
|
+
self.mini_overlay_data = None
|
|
451
|
+
|
|
446
452
|
def process_chunk(chunk_data, indices_to_check):
|
|
447
453
|
"""Process a single chunk of the array to create highlight mask"""
|
|
448
454
|
mask = np.isin(chunk_data, indices_to_check)
|
|
@@ -463,6 +469,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
463
469
|
if overlay1_indices is not None:
|
|
464
470
|
if 0 in overlay1_indices:
|
|
465
471
|
overlay1_indices.remove(0)
|
|
472
|
+
if overlay2_indices is not None:
|
|
473
|
+
if 0 in overlay2_indices:
|
|
474
|
+
overlay2_indices.remove(0)
|
|
466
475
|
|
|
467
476
|
if node_indices is None:
|
|
468
477
|
node_indices = []
|
|
@@ -555,6 +564,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
555
564
|
|
|
556
565
|
def create_highlight_overlay_slice(self, indices, bounds = False):
|
|
557
566
|
|
|
567
|
+
"""Highlight overlay generation method specific for the segmenter interactive mode"""
|
|
568
|
+
|
|
558
569
|
|
|
559
570
|
def process_chunk_bounds(chunk_data, indices_to_check):
|
|
560
571
|
"""Process a single chunk of the array to create highlight mask"""
|
|
@@ -628,6 +639,110 @@ class ImageViewerWindow(QMainWindow):
|
|
|
628
639
|
# Update display
|
|
629
640
|
self.update_display(preserve_zoom=(current_xlim, current_ylim), called = True)
|
|
630
641
|
|
|
642
|
+
def create_mini_overlay(self, node_indices = None, edge_indices = None):
|
|
643
|
+
|
|
644
|
+
"""
|
|
645
|
+
Create a highlight overlay one slice at a time.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
node_indices (list): List of node indices to highlight
|
|
649
|
+
edge_indices (list): List of edge indices to highlight
|
|
650
|
+
"""
|
|
651
|
+
|
|
652
|
+
def process_chunk(chunk_data, indices_to_check):
|
|
653
|
+
"""Process a single chunk of the array to create highlight mask"""
|
|
654
|
+
mask = np.isin(chunk_data, indices_to_check)
|
|
655
|
+
return mask * 255
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
if node_indices is not None:
|
|
659
|
+
if 0 in node_indices:
|
|
660
|
+
node_indices.remove(0)
|
|
661
|
+
if edge_indices is not None:
|
|
662
|
+
if 0 in edge_indices:
|
|
663
|
+
edge_indices.remove(0)
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
if node_indices is None:
|
|
667
|
+
node_indices = []
|
|
668
|
+
if edge_indices is None:
|
|
669
|
+
edge_indices = []
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
current_xlim = self.ax.get_xlim() if hasattr(self, 'ax') and self.ax.get_xlim() != (0, 1) else None
|
|
673
|
+
current_ylim = self.ax.get_ylim() if hasattr(self, 'ax') and self.ax.get_ylim() != (0, 1) else None
|
|
674
|
+
|
|
675
|
+
if not node_indices and not edge_indices: #Theoretically this can't be called because it uses full highlight overlay method for empty clicks
|
|
676
|
+
self.mini_overlay_data = None
|
|
677
|
+
self.mini_overlay = False
|
|
678
|
+
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
679
|
+
return
|
|
680
|
+
|
|
681
|
+
# Get the shape of the mini array from any existing channel
|
|
682
|
+
for channel in self.channel_data:
|
|
683
|
+
if channel is not None:
|
|
684
|
+
full_shape = channel.shape
|
|
685
|
+
full_shape = (full_shape[1], full_shape[2]) #Just get (Y, X) shape
|
|
686
|
+
break
|
|
687
|
+
else:
|
|
688
|
+
return # No valid channels to get shape from
|
|
689
|
+
|
|
690
|
+
# Initialize full-size overlay
|
|
691
|
+
self.mini_overlay_data = np.zeros(full_shape, dtype=np.uint8)
|
|
692
|
+
|
|
693
|
+
# Get number of CPU cores
|
|
694
|
+
num_cores = mp.cpu_count()
|
|
695
|
+
|
|
696
|
+
# Calculate chunk size along y-axis
|
|
697
|
+
chunk_size = full_shape[0] // num_cores
|
|
698
|
+
if chunk_size < 1:
|
|
699
|
+
chunk_size = 1
|
|
700
|
+
|
|
701
|
+
def process_channel(channel_data, indices, array_shape):
|
|
702
|
+
if channel_data is None or not indices:
|
|
703
|
+
return None
|
|
704
|
+
|
|
705
|
+
# Create chunks
|
|
706
|
+
chunks = []
|
|
707
|
+
for i in range(0, array_shape[0], chunk_size):
|
|
708
|
+
end = min(i + chunk_size, array_shape[0])
|
|
709
|
+
chunks.append(channel_data[i:end, :])
|
|
710
|
+
|
|
711
|
+
# Process chunks in parallel using ThreadPoolExecutor
|
|
712
|
+
process_func = partial(process_chunk, indices_to_check=indices)
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
with ThreadPoolExecutor(max_workers=num_cores) as executor:
|
|
716
|
+
chunk_results = list(executor.map(process_func, chunks))
|
|
717
|
+
|
|
718
|
+
# Reassemble the chunks
|
|
719
|
+
return np.concatenate(chunk_results, axis=0)
|
|
720
|
+
|
|
721
|
+
# Process nodes and edges in parallel using multiprocessing
|
|
722
|
+
with ThreadPoolExecutor(max_workers=num_cores) as executor:
|
|
723
|
+
try:
|
|
724
|
+
slice_node = self.channel_data[0][self.current_slice, :, :] #This is the only major difference to the big highlight... we are only looking at this
|
|
725
|
+
future_nodes = executor.submit(process_channel, slice_node, node_indices, full_shape)
|
|
726
|
+
node_overlay = future_nodes.result()
|
|
727
|
+
except:
|
|
728
|
+
node_overlay = None
|
|
729
|
+
try:
|
|
730
|
+
slice_edge = self.channel_data[1][self.current_slice, :, :]
|
|
731
|
+
future_edges = executor.submit(process_channel, slice_edge, edge_indices, full_shape)
|
|
732
|
+
edge_overlay = future_edges.result()
|
|
733
|
+
except:
|
|
734
|
+
edge_overlay = None
|
|
735
|
+
|
|
736
|
+
# Combine results
|
|
737
|
+
if node_overlay is not None:
|
|
738
|
+
self.mini_overlay_data = np.maximum(self.mini_overlay_data, node_overlay)
|
|
739
|
+
if edge_overlay is not None:
|
|
740
|
+
self.mini_overlay_data = np.maximum(self.mini_overlay_data, edge_overlay)
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
# Update display
|
|
744
|
+
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
745
|
+
|
|
631
746
|
|
|
632
747
|
|
|
633
748
|
|
|
@@ -890,14 +1005,23 @@ class ImageViewerWindow(QMainWindow):
|
|
|
890
1005
|
edge_indices = filtered_df.iloc[:, 2].unique().tolist()
|
|
891
1006
|
self.clicked_values['edges'] = edge_indices
|
|
892
1007
|
|
|
893
|
-
self.
|
|
894
|
-
|
|
895
|
-
edge_indices=self.clicked_values['edges']
|
|
896
|
-
|
|
1008
|
+
if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
|
|
1009
|
+
self.mini_overlay = True
|
|
1010
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
1011
|
+
else:
|
|
1012
|
+
self.create_highlight_overlay(
|
|
1013
|
+
node_indices=self.clicked_values['nodes'],
|
|
1014
|
+
edge_indices=self.clicked_values['edges']
|
|
1015
|
+
)
|
|
897
1016
|
else:
|
|
898
|
-
self.
|
|
899
|
-
|
|
900
|
-
|
|
1017
|
+
if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
|
|
1018
|
+
self.mini_overlay = True
|
|
1019
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
1020
|
+
else:
|
|
1021
|
+
self.create_highlight_overlay(
|
|
1022
|
+
node_indices=self.clicked_values['nodes'],
|
|
1023
|
+
edge_indices = self.clicked_values['edges']
|
|
1024
|
+
)
|
|
901
1025
|
|
|
902
1026
|
|
|
903
1027
|
except Exception as e:
|
|
@@ -972,14 +1096,23 @@ class ImageViewerWindow(QMainWindow):
|
|
|
972
1096
|
if edges:
|
|
973
1097
|
edge_indices = filtered_df.iloc[:, 2].unique().tolist()
|
|
974
1098
|
self.clicked_values['edges'] = edge_indices
|
|
975
|
-
self.
|
|
976
|
-
|
|
977
|
-
edge_indices=
|
|
978
|
-
|
|
1099
|
+
if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
|
|
1100
|
+
self.mini_overlay = True
|
|
1101
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
1102
|
+
else:
|
|
1103
|
+
self.create_highlight_overlay(
|
|
1104
|
+
node_indices=self.clicked_values['nodes'],
|
|
1105
|
+
edge_indices=edge_indices
|
|
1106
|
+
)
|
|
979
1107
|
else:
|
|
980
|
-
self.
|
|
981
|
-
|
|
982
|
-
|
|
1108
|
+
if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
|
|
1109
|
+
self.mini_overlay = True
|
|
1110
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
1111
|
+
else:
|
|
1112
|
+
self.create_highlight_overlay(
|
|
1113
|
+
node_indices = self.clicked_values['nodes'],
|
|
1114
|
+
edge_indices = self.clicked_values['edges']
|
|
1115
|
+
)
|
|
983
1116
|
|
|
984
1117
|
except Exception as e:
|
|
985
1118
|
|
|
@@ -1043,15 +1176,24 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1043
1176
|
if edges:
|
|
1044
1177
|
edge_indices = filtered_df.iloc[:, 2].unique().tolist()
|
|
1045
1178
|
self.clicked_values['edges'] = edge_indices
|
|
1046
|
-
self.
|
|
1047
|
-
|
|
1048
|
-
edge_indices=edge_indices
|
|
1049
|
-
|
|
1179
|
+
if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
|
|
1180
|
+
self.mini_overlay = True
|
|
1181
|
+
self.create_mini_overlay(node_indices = nodes, edge_indices = edge_indices)
|
|
1182
|
+
else:
|
|
1183
|
+
self.create_highlight_overlay(
|
|
1184
|
+
node_indices=nodes,
|
|
1185
|
+
edge_indices=edge_indices
|
|
1186
|
+
)
|
|
1050
1187
|
self.clicked_values['nodes'] = nodes
|
|
1051
1188
|
else:
|
|
1052
|
-
self.
|
|
1053
|
-
|
|
1054
|
-
|
|
1189
|
+
if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
|
|
1190
|
+
self.mini_overlay = True
|
|
1191
|
+
self.create_mini_overlay(node_indices = nodes, edge_indices = self.clicked_values['edges'])
|
|
1192
|
+
else:
|
|
1193
|
+
self.create_highlight_overlay(
|
|
1194
|
+
node_indices = nodes,
|
|
1195
|
+
edge_indices = self.clicked_values['edges']
|
|
1196
|
+
)
|
|
1055
1197
|
self.clicked_values['nodes'] = nodes
|
|
1056
1198
|
|
|
1057
1199
|
except Exception as e:
|
|
@@ -1095,9 +1237,15 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1095
1237
|
|
|
1096
1238
|
print(f"Found {len(filtered_df)} direct connections between nodes of ID {sort} and their neighbors (of any ID)")
|
|
1097
1239
|
|
|
1098
|
-
self.
|
|
1099
|
-
|
|
1100
|
-
)
|
|
1240
|
+
if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
|
|
1241
|
+
self.mini_overlay = True
|
|
1242
|
+
self.create_mini_overlay(node_indices = nodes, edge_indices = self.clicked_values['edges'])
|
|
1243
|
+
else:
|
|
1244
|
+
self.create_highlight_overlay(
|
|
1245
|
+
node_indices = nodes,
|
|
1246
|
+
edge_indices = self.clicked_values['edges']
|
|
1247
|
+
)
|
|
1248
|
+
self.clicked_values['nodes'] = nodes
|
|
1101
1249
|
|
|
1102
1250
|
except Exception as e:
|
|
1103
1251
|
print(f"Error showing identities: {e}")
|
|
@@ -1393,8 +1541,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1393
1541
|
|
|
1394
1542
|
pairs = list(combinations(nodes, 2))
|
|
1395
1543
|
|
|
1396
|
-
print(pairs)
|
|
1397
|
-
|
|
1398
1544
|
|
|
1399
1545
|
for i in range(len(my_network.network_lists[0]) - 1, -1, -1):
|
|
1400
1546
|
print((my_network.network_lists[0][i], my_network.network_lists[1][i]))
|
|
@@ -1891,7 +2037,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1891
2037
|
self.clicked_values['nodes'].extend(selected_values)
|
|
1892
2038
|
# Remove duplicates while preserving order
|
|
1893
2039
|
self.clicked_values['nodes'] = list(dict.fromkeys(self.clicked_values['nodes']))
|
|
1894
|
-
self.
|
|
2040
|
+
if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
|
|
2041
|
+
self.mini_overlay = True
|
|
2042
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
2043
|
+
else:
|
|
2044
|
+
self.create_highlight_overlay(node_indices=self.clicked_values['nodes'])
|
|
1895
2045
|
|
|
1896
2046
|
# Try to highlight the last selected value in tables
|
|
1897
2047
|
if self.clicked_values['nodes']:
|
|
@@ -1903,7 +2053,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1903
2053
|
self.clicked_values['edges'].extend(selected_values)
|
|
1904
2054
|
# Remove duplicates while preserving order
|
|
1905
2055
|
self.clicked_values['edges'] = list(dict.fromkeys(self.clicked_values['edges']))
|
|
1906
|
-
self.
|
|
2056
|
+
if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
|
|
2057
|
+
self.mini_overlay = True
|
|
2058
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
2059
|
+
else:
|
|
2060
|
+
self.create_highlight_overlay(edge_indices=self.clicked_values['edges'])
|
|
1907
2061
|
|
|
1908
2062
|
# Try to highlight the last selected value in tables
|
|
1909
2063
|
if self.clicked_values['edges']:
|
|
@@ -2144,9 +2298,17 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2144
2298
|
|
|
2145
2299
|
# Highlight the clicked element in the image using the stored lists
|
|
2146
2300
|
if self.active_channel == 0 and (starting_vals['nodes']) != (self.clicked_values['nodes']):
|
|
2147
|
-
self.
|
|
2301
|
+
if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
|
|
2302
|
+
self.mini_overlay = True
|
|
2303
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
2304
|
+
else:
|
|
2305
|
+
self.create_highlight_overlay(node_indices=self.clicked_values['nodes'], edge_indices=self.clicked_values['edges'])
|
|
2148
2306
|
elif self.active_channel == 1 and starting_vals['edges'] != self.clicked_values['edges']:
|
|
2149
|
-
self.
|
|
2307
|
+
if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
|
|
2308
|
+
self.mini_overlay = True
|
|
2309
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
2310
|
+
else:
|
|
2311
|
+
self.create_highlight_overlay(node_indices=self.clicked_values['nodes'], edge_indices=self.clicked_values['edges'])
|
|
2150
2312
|
|
|
2151
2313
|
|
|
2152
2314
|
except IndexError:
|
|
@@ -2298,6 +2460,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2298
2460
|
netoverlay_action.triggered.connect(self.show_netoverlay_dialog)
|
|
2299
2461
|
idoverlay_action = overlay_menu.addAction("Create ID Overlay")
|
|
2300
2462
|
idoverlay_action.triggered.connect(self.show_idoverlay_dialog)
|
|
2463
|
+
coloroverlay_action = overlay_menu.addAction("Color Nodes (or Edges)")
|
|
2464
|
+
coloroverlay_action.triggered.connect(self.show_coloroverlay_dialog)
|
|
2301
2465
|
searchoverlay_action = overlay_menu.addAction("Show Search Regions")
|
|
2302
2466
|
searchoverlay_action.triggered.connect(self.show_search_dialog)
|
|
2303
2467
|
shuffle_action = overlay_menu.addAction("Shuffle")
|
|
@@ -2578,6 +2742,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2578
2742
|
dialog = IdOverlayDialog(self)
|
|
2579
2743
|
dialog.exec()
|
|
2580
2744
|
|
|
2745
|
+
def show_coloroverlay_dialog(self):
|
|
2746
|
+
"""show the color overlay dialog"""
|
|
2747
|
+
dialog = ColorOverlayDialog(self)
|
|
2748
|
+
dialog.exec()
|
|
2749
|
+
|
|
2581
2750
|
def show_search_dialog(self):
|
|
2582
2751
|
"""Show the search dialog"""
|
|
2583
2752
|
dialog = SearchOverlayDialog(self)
|
|
@@ -3001,14 +3170,20 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3001
3170
|
else:
|
|
3002
3171
|
self.channel_data[channel_index] = channel_data
|
|
3003
3172
|
|
|
3004
|
-
|
|
3005
|
-
if self.channel_data[channel_index].shape
|
|
3006
|
-
if self.
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3173
|
+
try:
|
|
3174
|
+
if len(self.channel_data[channel_index].shape) == 3: # potentially 2D RGB
|
|
3175
|
+
if self.channel_data[channel_index].shape[-1] in (3, 4): # last dim is 3 or 4
|
|
3176
|
+
if self.confirm_rgb_dialog():
|
|
3177
|
+
# User confirmed it's 2D RGB, expand to 4D
|
|
3178
|
+
self.channel_data[channel_index] = np.expand_dims(self.channel_data[channel_index], axis=0)
|
|
3179
|
+
except:
|
|
3180
|
+
pass
|
|
3181
|
+
|
|
3182
|
+
try:
|
|
3183
|
+
if len(self.channel_data[channel_index].shape) == 4 and (channel_index == 0 or channel_index == 1):
|
|
3184
|
+
self.channel_data[channel_index] = self.reduce_rgb_dimension(self.channel_data[channel_index])
|
|
3185
|
+
except:
|
|
3186
|
+
pass
|
|
3012
3187
|
|
|
3013
3188
|
reset_resize = False
|
|
3014
3189
|
|
|
@@ -3050,27 +3225,30 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3050
3225
|
self.active_channel_combo.setEnabled(True)
|
|
3051
3226
|
|
|
3052
3227
|
# Update slider range if this is the first channel loaded
|
|
3053
|
-
|
|
3054
|
-
if
|
|
3055
|
-
self.slice_slider.
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3228
|
+
try:
|
|
3229
|
+
if len(self.channel_data[channel_index].shape) == 3 or len(self.channel_data[channel_index].shape) == 4:
|
|
3230
|
+
if not self.slice_slider.isEnabled():
|
|
3231
|
+
self.slice_slider.setEnabled(True)
|
|
3232
|
+
self.slice_slider.setMinimum(0)
|
|
3233
|
+
self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
|
|
3234
|
+
if self.slice_slider.value() < self.channel_data[channel_index].shape[0] - 1:
|
|
3235
|
+
self.current_slice = self.slice_slider.value()
|
|
3236
|
+
else:
|
|
3237
|
+
self.slice_slider.setValue(0)
|
|
3238
|
+
self.current_slice = 0
|
|
3060
3239
|
else:
|
|
3061
|
-
self.slice_slider.
|
|
3062
|
-
self.
|
|
3240
|
+
self.slice_slider.setEnabled(True)
|
|
3241
|
+
self.slice_slider.setMinimum(0)
|
|
3242
|
+
self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
|
|
3243
|
+
if self.slice_slider.value() < self.channel_data[channel_index].shape[0] - 1:
|
|
3244
|
+
self.current_slice = self.slice_slider.value()
|
|
3245
|
+
else:
|
|
3246
|
+
self.current_slice = 0
|
|
3247
|
+
self.slice_slider.setValue(0)
|
|
3063
3248
|
else:
|
|
3064
|
-
self.slice_slider.setEnabled(
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
if self.slice_slider.value() < self.channel_data[channel_index].shape[0] - 1:
|
|
3068
|
-
self.current_slice = self.slice_slider.value()
|
|
3069
|
-
else:
|
|
3070
|
-
self.current_slice = 0
|
|
3071
|
-
self.slice_slider.setValue(0)
|
|
3072
|
-
else:
|
|
3073
|
-
self.slice_slider.setEnabled(False)
|
|
3249
|
+
self.slice_slider.setEnabled(False)
|
|
3250
|
+
except:
|
|
3251
|
+
pass
|
|
3074
3252
|
|
|
3075
3253
|
|
|
3076
3254
|
# If this is the first channel loaded, make it active
|
|
@@ -3083,13 +3261,16 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3083
3261
|
self.min_max[channel_index][1] = np.max(self.channel_data[channel_index])
|
|
3084
3262
|
self.volume_dict[channel_index] = None #reset volumes
|
|
3085
3263
|
|
|
3086
|
-
|
|
3087
|
-
if
|
|
3088
|
-
self.original_shape
|
|
3089
|
-
|
|
3090
|
-
self.original_shape
|
|
3091
|
-
|
|
3092
|
-
|
|
3264
|
+
try:
|
|
3265
|
+
if assign_shape: #keep original shape tracked to undo resampling.
|
|
3266
|
+
if self.original_shape is None:
|
|
3267
|
+
self.original_shape = self.channel_data[channel_index].shape
|
|
3268
|
+
elif self.original_shape[0] < self.channel_data[channel_index].shape[0] or self.original_shape[1] < self.channel_data[channel_index].shape[1] or self.original_shape[2] < self.channel_data[channel_index].shape[2]:
|
|
3269
|
+
self.original_shape = self.channel_data[channel_index].shape
|
|
3270
|
+
if len(self.original_shape) == 4:
|
|
3271
|
+
self.original_shape = (self.original_shape[0], self.original_shape[1], self.original_shape[2])
|
|
3272
|
+
except:
|
|
3273
|
+
pass
|
|
3093
3274
|
|
|
3094
3275
|
self.update_display(reset_resize = reset_resize)
|
|
3095
3276
|
|
|
@@ -3262,6 +3443,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3262
3443
|
elif ch_index == 3:
|
|
3263
3444
|
my_network.save_id_overlay(filename=filename)
|
|
3264
3445
|
elif ch_index == 4:
|
|
3446
|
+
if self.mini_overlay == True:
|
|
3447
|
+
self.create_highlight_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
3265
3448
|
if filename == None:
|
|
3266
3449
|
filename = "Highlighted_Element.tif"
|
|
3267
3450
|
tifffile.imwrite(f"{filename}", self.highlight_overlay)
|
|
@@ -3306,7 +3489,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3306
3489
|
if self.pending_slice is not None:
|
|
3307
3490
|
slice_value, view_settings = self.pending_slice
|
|
3308
3491
|
self.current_slice = slice_value
|
|
3492
|
+
if self.mini_overlay == True: #If we are rendering the highlight overlay for selected values one at a time.
|
|
3493
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
3309
3494
|
self.update_display(preserve_zoom=view_settings)
|
|
3495
|
+
if self.machine_window is not None:
|
|
3496
|
+
self.machine_window.poke_segmenter()
|
|
3310
3497
|
self.pending_slice = None
|
|
3311
3498
|
|
|
3312
3499
|
def update_brightness(self, channel_index, values):
|
|
@@ -3442,7 +3629,15 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3442
3629
|
self.create_highlight_overlay_slice(self.targs, bounds = self.bounds)
|
|
3443
3630
|
|
|
3444
3631
|
# Add highlight overlay if it exists
|
|
3445
|
-
if self.
|
|
3632
|
+
if self.mini_overlay and self.highlight and self.machine_window is None:
|
|
3633
|
+
highlight_cmap = LinearSegmentedColormap.from_list(
|
|
3634
|
+
'highlight',
|
|
3635
|
+
[(0, 0, 0, 0), (1, 1, 0, 1)] # yellow
|
|
3636
|
+
)
|
|
3637
|
+
self.ax.imshow(self.mini_overlay_data,
|
|
3638
|
+
cmap=highlight_cmap,
|
|
3639
|
+
alpha=0.5)
|
|
3640
|
+
elif self.highlight_overlay is not None and self.highlight and self.machine_window is None:
|
|
3446
3641
|
highlight_slice = self.highlight_overlay[self.current_slice]
|
|
3447
3642
|
highlight_cmap = LinearSegmentedColormap.from_list(
|
|
3448
3643
|
'highlight',
|
|
@@ -3968,7 +4163,13 @@ class CustomTableView(QTableView):
|
|
|
3968
4163
|
# Navigate to the Z-slice
|
|
3969
4164
|
self.parent.slice_slider.setValue(int(centroid[0]))
|
|
3970
4165
|
print(f"Found node {value} at Z-slice {centroid[0]}")
|
|
3971
|
-
self.parent.
|
|
4166
|
+
if self.parent.channel_data[0].shape[0] * self.parent.channel_data[0].shape[1] * self.parent.channel_data[0].shape[2] > self.parent.mini_thresh:
|
|
4167
|
+
self.parent.mini_overlay = True
|
|
4168
|
+
self.parent.create_mini_overlay(node_indices = [value])
|
|
4169
|
+
else:
|
|
4170
|
+
self.parent.create_highlight_overlay(node_indices=[value])
|
|
4171
|
+
self.parent.clicked_values['nodes'] = []
|
|
4172
|
+
self.parent.clicked_values['edges'] = []
|
|
3972
4173
|
self.parent.clicked_values['nodes'].append(value)
|
|
3973
4174
|
|
|
3974
4175
|
# Highlight the value in both tables if it exists
|
|
@@ -3992,7 +4193,13 @@ class CustomTableView(QTableView):
|
|
|
3992
4193
|
# Navigate to the Z-slice
|
|
3993
4194
|
self.parent.slice_slider.setValue(int(centroid[0]))
|
|
3994
4195
|
print(f"Found edge {value} at Z-slice {centroid[0]}")
|
|
3995
|
-
self.parent.
|
|
4196
|
+
if self.parent.channel_data[1].shape[0] * self.parent.channel_data[1].shape[1] * self.parent.channel_data[1].shape[2] > self.parent.mini_thresh:
|
|
4197
|
+
self.parent.mini_overlay = True
|
|
4198
|
+
self.parent.create_mini_overlay(edge_indices = [value])
|
|
4199
|
+
else:
|
|
4200
|
+
self.parent.create_highlight_overlay(edge_indices=[value])
|
|
4201
|
+
self.parent.clicked_values['nodes'] = []
|
|
4202
|
+
self.parent.clicked_values['edges'] = []
|
|
3996
4203
|
self.parent.clicked_values['edges'].append(value)
|
|
3997
4204
|
|
|
3998
4205
|
# Highlight the value in both tables if it exists
|
|
@@ -4020,12 +4227,24 @@ class CustomTableView(QTableView):
|
|
|
4020
4227
|
self.parent.slice_slider.setValue(int(centroid1[0]))
|
|
4021
4228
|
print(f"Found node pair {value[0]} and {value[1]} at Z-slices {centroid1[0]} and {centroid2[0]}, respectively")
|
|
4022
4229
|
try:
|
|
4023
|
-
self.parent.
|
|
4230
|
+
if self.parent.channel_data[0].shape[0] * self.parent.channel_data[0].shape[1] * self.parent.channel_data[0].shape[2] > self.parent.mini_thresh:
|
|
4231
|
+
self.parent.mini_overlay = True
|
|
4232
|
+
self.parent.create_mini_overlay(node_indices=[int(value[0]), int(value[1])], edge_indices = int(value[2]))
|
|
4233
|
+
else:
|
|
4234
|
+
self.parent.create_highlight_overlay(node_indices=[int(value[0]), int(value[1])], edge_indices = int(value[2]))
|
|
4235
|
+
self.parent.clicked_values['nodes'] = []
|
|
4236
|
+
self.parent.clicked_values['edges'] = []
|
|
4024
4237
|
self.parent.clicked_values['edges'].append(value[2])
|
|
4025
4238
|
self.parent.clicked_values['nodes'].append(value[0])
|
|
4026
4239
|
self.parent.clicked_values['nodes'].append(value[1])
|
|
4027
4240
|
except:
|
|
4028
|
-
self.parent.
|
|
4241
|
+
if self.parent.channel_data[0].shape[0] * self.parent.channel_data[0].shape[1] * self.parent.channel_data[0].shape[2] > self.parent.mini_thresh:
|
|
4242
|
+
self.parent.mini_overlay = True
|
|
4243
|
+
self.parent.create_mini_overlay(node_indices=[int(value[0]), int(value[1])])
|
|
4244
|
+
else:
|
|
4245
|
+
self.parent.create_highlight_overlay(node_indices=[int(value[0]), int(value[1])])
|
|
4246
|
+
self.parent.clicked_values['nodes'] = []
|
|
4247
|
+
self.parent.clicked_values['edges'] = []
|
|
4029
4248
|
self.parent.clicked_values['nodes'].append(value[0])
|
|
4030
4249
|
self.parent.clicked_values['nodes'].append(value[1])
|
|
4031
4250
|
|
|
@@ -4571,6 +4790,8 @@ class Show3dDialog(QDialog):
|
|
|
4571
4790
|
arrays_4d.append(channel)
|
|
4572
4791
|
|
|
4573
4792
|
if self.parent().highlight_overlay is not None:
|
|
4793
|
+
if self.parent().mini_overlay == True:
|
|
4794
|
+
self.parent().create_highlight_overlay(node_indices = self.parent().clicked_values['nodes'], edge_indices = self.parent().clicked_values['edges'])
|
|
4574
4795
|
arrays_3d.append(self.parent().highlight_overlay)
|
|
4575
4796
|
colors.append(color_template[4])
|
|
4576
4797
|
|
|
@@ -4705,6 +4926,45 @@ class IdOverlayDialog(QDialog):
|
|
|
4705
4926
|
|
|
4706
4927
|
self.accept()
|
|
4707
4928
|
|
|
4929
|
+
class ColorOverlayDialog(QDialog):
|
|
4930
|
+
|
|
4931
|
+
def __init__(self, parent=None):
|
|
4932
|
+
|
|
4933
|
+
super().__init__(parent)
|
|
4934
|
+
self.setWindowTitle("Generate Node (or Edge) -> Color Overlay?")
|
|
4935
|
+
self.setModal(True)
|
|
4936
|
+
|
|
4937
|
+
layout = QFormLayout(self)
|
|
4938
|
+
|
|
4939
|
+
self.down_factor = QLineEdit("")
|
|
4940
|
+
layout.addRow("down_factor (for speeding up overlay generation - optional):", self.down_factor)
|
|
4941
|
+
|
|
4942
|
+
# Add Run button
|
|
4943
|
+
run_button = QPushButton("Generate (Will go to Overlay 2)")
|
|
4944
|
+
run_button.clicked.connect(self.coloroverlay)
|
|
4945
|
+
layout.addWidget(run_button)
|
|
4946
|
+
|
|
4947
|
+
def coloroverlay(self):
|
|
4948
|
+
|
|
4949
|
+
down_factor = float(self.down_factor.text()) if self.down_factor.text().strip() else None
|
|
4950
|
+
|
|
4951
|
+
if self.parent().active_channel == 0:
|
|
4952
|
+
mode = 0
|
|
4953
|
+
self.sort = 'Node'
|
|
4954
|
+
else:
|
|
4955
|
+
mode = 1
|
|
4956
|
+
self.sort = 'Edge'
|
|
4957
|
+
|
|
4958
|
+
|
|
4959
|
+
result, legend = my_network.node_to_color(down_factor = down_factor, mode = mode)
|
|
4960
|
+
|
|
4961
|
+
self.parent().format_for_upperright_table(legend, f'{self.sort} Id', f'Encoding Val: {self.sort}', 'Legend')
|
|
4962
|
+
|
|
4963
|
+
|
|
4964
|
+
self.parent().load_channel(3, channel_data = result, data = True)
|
|
4965
|
+
|
|
4966
|
+
self.accept()
|
|
4967
|
+
|
|
4708
4968
|
|
|
4709
4969
|
class ShuffleDialog(QDialog):
|
|
4710
4970
|
|
|
@@ -4745,11 +5005,15 @@ class ShuffleDialog(QDialog):
|
|
|
4745
5005
|
accepted_target = self.target_selector.currentIndex()
|
|
4746
5006
|
|
|
4747
5007
|
if accepted_mode == 4:
|
|
5008
|
+
if self.parent().mini_overlay == True:
|
|
5009
|
+
self.parent().create_highlight_overlay(node_indices = self.parent().clicked_values['nodes'], edge_indices = self.parent().clicked_values['edges'])
|
|
4748
5010
|
active_data = self.parent().highlight_overlay
|
|
4749
5011
|
else:
|
|
4750
5012
|
active_data = self.parent().channel_data[accepted_mode]
|
|
4751
5013
|
|
|
4752
5014
|
if accepted_target == 4:
|
|
5015
|
+
if self.parent().mini_overlay == True:
|
|
5016
|
+
self.parent().create_highlight_overlay(node_indices = self.parent().clicked_values['nodes'], edge_indices = self.parent().clicked_values['edges'])
|
|
4753
5017
|
target_data = self.parent().highlight_overlay
|
|
4754
5018
|
else:
|
|
4755
5019
|
target_data = self.parent().channel_data[accepted_target]
|
|
@@ -5952,7 +6216,18 @@ class MachineWindow(QMainWindow):
|
|
|
5952
6216
|
self.GPU.setChecked(False)
|
|
5953
6217
|
self.GPU.clicked.connect(self.toggle_GPU)
|
|
5954
6218
|
self.use_gpu = False
|
|
6219
|
+
self.two = QPushButton("Train By 2D Slice Patterns (Cheaper - CPU only)")
|
|
6220
|
+
self.two.setCheckable(True)
|
|
6221
|
+
self.two.setChecked(True)
|
|
6222
|
+
self.two.clicked.connect(self.toggle_two)
|
|
6223
|
+
self.use_two = True
|
|
6224
|
+
self.three = QPushButton("Train by 3D Patterns")
|
|
6225
|
+
self.three.setCheckable(True)
|
|
6226
|
+
self.three.setChecked(False)
|
|
6227
|
+
self.three.clicked.connect(self.toggle_three)
|
|
5955
6228
|
processing_layout.addWidget(self.GPU)
|
|
6229
|
+
processing_layout.addWidget(self.two)
|
|
6230
|
+
processing_layout.addWidget(self.three)
|
|
5956
6231
|
processing_group.setLayout(processing_layout)
|
|
5957
6232
|
|
|
5958
6233
|
# Group 3: Training Options
|
|
@@ -5970,6 +6245,7 @@ class MachineWindow(QMainWindow):
|
|
|
5970
6245
|
segmentation_group = QGroupBox("Segmentation")
|
|
5971
6246
|
segmentation_layout = QVBoxLayout()
|
|
5972
6247
|
seg_button = QPushButton("Preview Segment")
|
|
6248
|
+
self.seg_button = seg_button
|
|
5973
6249
|
seg_button.clicked.connect(self.start_segmentation)
|
|
5974
6250
|
full_button = QPushButton("Segment All")
|
|
5975
6251
|
full_button.clicked.connect(self.segment)
|
|
@@ -5987,9 +6263,12 @@ class MachineWindow(QMainWindow):
|
|
|
5987
6263
|
self.setCentralWidget(main_widget)
|
|
5988
6264
|
|
|
5989
6265
|
self.trained = False
|
|
6266
|
+
self.previewing = False
|
|
5990
6267
|
|
|
5991
6268
|
|
|
5992
6269
|
self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=True)
|
|
6270
|
+
self.segmentation_worker = None
|
|
6271
|
+
|
|
5993
6272
|
|
|
5994
6273
|
|
|
5995
6274
|
def toggle_GPU(self):
|
|
@@ -5997,6 +6276,27 @@ class MachineWindow(QMainWindow):
|
|
|
5997
6276
|
|
|
5998
6277
|
self.use_gpu = self.GPU.isChecked()
|
|
5999
6278
|
|
|
6279
|
+
def toggle_two(self):
|
|
6280
|
+
if self.two.isChecked():
|
|
6281
|
+
# If button two is checked, ensure button three is unchecked
|
|
6282
|
+
self.three.setChecked(False)
|
|
6283
|
+
self.use_two = True
|
|
6284
|
+
else:
|
|
6285
|
+
# If button three is checked, ensure button two is unchecked
|
|
6286
|
+
self.three.setChecked(True)
|
|
6287
|
+
self.use_two = False
|
|
6288
|
+
|
|
6289
|
+
def toggle_three(self):
|
|
6290
|
+
if self.three.isChecked():
|
|
6291
|
+
# If button two is checked, ensure button three is unchecked
|
|
6292
|
+
self.two.setChecked(False)
|
|
6293
|
+
self.use_two = False
|
|
6294
|
+
else:
|
|
6295
|
+
# If button three is checked, ensure button two is unchecked
|
|
6296
|
+
self.two.setChecked(True)
|
|
6297
|
+
self.use_two = True
|
|
6298
|
+
|
|
6299
|
+
|
|
6000
6300
|
|
|
6001
6301
|
def toggle_foreground(self):
|
|
6002
6302
|
|
|
@@ -6045,15 +6345,29 @@ class MachineWindow(QMainWindow):
|
|
|
6045
6345
|
self.kill_segmentation()
|
|
6046
6346
|
# Wait a bit for cleanup
|
|
6047
6347
|
time.sleep(0.1)
|
|
6048
|
-
|
|
6049
|
-
|
|
6348
|
+
if not self.use_two:
|
|
6349
|
+
self.previewing = False
|
|
6350
|
+
try:
|
|
6351
|
+
self.segmenter.train_batch(self.parent().channel_data[2], speed = speed, use_gpu = self.use_gpu, use_two = self.use_two)
|
|
6352
|
+
self.trained = True
|
|
6353
|
+
except MemoryError:
|
|
6354
|
+
QMessageBox.critical(
|
|
6355
|
+
self,
|
|
6356
|
+
"Alert",
|
|
6357
|
+
"Out of memory computing feature maps. Note these for 3D require 7x the RAM of the active image (or 9x for the detailed map).\n Please use 2D slice models if you do not have enough RAM."
|
|
6358
|
+
)
|
|
6359
|
+
|
|
6050
6360
|
|
|
6051
6361
|
def start_segmentation(self):
|
|
6052
6362
|
|
|
6053
6363
|
self.kill_segmentation()
|
|
6054
6364
|
time.sleep(0.1)
|
|
6055
6365
|
|
|
6056
|
-
|
|
6366
|
+
if self.use_two:
|
|
6367
|
+
self.previewing = True
|
|
6368
|
+
else:
|
|
6369
|
+
print("Beginning new segmentation...")
|
|
6370
|
+
|
|
6057
6371
|
|
|
6058
6372
|
if self.parent().active_channel == 0:
|
|
6059
6373
|
if self.parent().channel_data[0] is not None:
|
|
@@ -6067,7 +6381,7 @@ class MachineWindow(QMainWindow):
|
|
|
6067
6381
|
if not self.trained:
|
|
6068
6382
|
return
|
|
6069
6383
|
else:
|
|
6070
|
-
self.segmentation_worker = SegmentationWorker(self.parent().highlight_overlay, self.segmenter, self.use_gpu)
|
|
6384
|
+
self.segmentation_worker = SegmentationWorker(self.parent().highlight_overlay, self.segmenter, self.use_gpu, self.use_two, self.previewing, self)
|
|
6071
6385
|
self.segmentation_worker.chunk_processed.connect(self.update_display) # Just update display
|
|
6072
6386
|
self.segmentation_worker.finished.connect(self.segmentation_finished)
|
|
6073
6387
|
current_xlim = self.parent().ax.get_xlim()
|
|
@@ -6095,10 +6409,42 @@ class MachineWindow(QMainWindow):
|
|
|
6095
6409
|
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
6096
6410
|
return msg.exec() == QMessageBox.StandardButton.Yes
|
|
6097
6411
|
|
|
6412
|
+
|
|
6413
|
+
|
|
6414
|
+
def check_for_z_change(self):
|
|
6415
|
+
current_z = self.parent().current_slice
|
|
6416
|
+
if not hasattr(self, '_last_z'):
|
|
6417
|
+
self._last_z = current_z
|
|
6418
|
+
return False
|
|
6419
|
+
|
|
6420
|
+
changed = (self._last_z != current_z)
|
|
6421
|
+
self._last_z = current_z
|
|
6422
|
+
|
|
6423
|
+
if changed and self.previewing and self.segmentation_worker is not None:
|
|
6424
|
+
self.segmentation_worker.stop()
|
|
6425
|
+
time.sleep(0.1)
|
|
6426
|
+
|
|
6427
|
+
# Force regeneration of chunks
|
|
6428
|
+
self.segmenter.realtimechunks = None
|
|
6429
|
+
|
|
6430
|
+
# Restart the worker
|
|
6431
|
+
self.start_segmentation()
|
|
6432
|
+
|
|
6433
|
+
return changed
|
|
6434
|
+
|
|
6098
6435
|
def update_display(self):
|
|
6099
6436
|
if not hasattr(self, '_last_update'):
|
|
6100
6437
|
self._last_update = 0
|
|
6101
6438
|
|
|
6439
|
+
current_z = self.parent().current_slice
|
|
6440
|
+
if not hasattr(self, '_last_z'):
|
|
6441
|
+
self._last_z = current_z
|
|
6442
|
+
|
|
6443
|
+
self._last_z = current_z
|
|
6444
|
+
|
|
6445
|
+
if self.previewing:
|
|
6446
|
+
changed = self.check_for_z_change()
|
|
6447
|
+
|
|
6102
6448
|
current_time = time.time()
|
|
6103
6449
|
if current_time - self._last_update >= 1: # Match worker's interval
|
|
6104
6450
|
try:
|
|
@@ -6116,19 +6462,84 @@ class MachineWindow(QMainWindow):
|
|
|
6116
6462
|
except Exception as e:
|
|
6117
6463
|
print(f"Display update error: {e}")
|
|
6118
6464
|
|
|
6465
|
+
def poke_segmenter(self):
|
|
6466
|
+
if self.use_two and self.previewing:
|
|
6467
|
+
try:
|
|
6468
|
+
# Clear any processing flags in the segmenter
|
|
6469
|
+
if hasattr(self.segmenter, '_currently_processing'):
|
|
6470
|
+
self.segmenter._currently_processing = None
|
|
6471
|
+
|
|
6472
|
+
# Force regenerating the worker
|
|
6473
|
+
if self.segmentation_worker is not None:
|
|
6474
|
+
self.kill_segmentation()
|
|
6475
|
+
|
|
6476
|
+
time.sleep(0.2)
|
|
6477
|
+
self.start_segmentation()
|
|
6478
|
+
|
|
6479
|
+
except Exception as e:
|
|
6480
|
+
print(f"Error in poke_segmenter: {e}")
|
|
6481
|
+
import traceback
|
|
6482
|
+
traceback.print_exc()
|
|
6483
|
+
|
|
6119
6484
|
def segmentation_finished(self):
|
|
6120
|
-
|
|
6485
|
+
if not self.use_two:
|
|
6486
|
+
print("Segmentation completed")
|
|
6487
|
+
|
|
6121
6488
|
current_xlim = self.parent().ax.get_xlim()
|
|
6122
6489
|
current_ylim = self.parent().ax.get_ylim()
|
|
6123
6490
|
self.parent().update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
6491
|
+
|
|
6492
|
+
# Store the current z position before killing the worker
|
|
6493
|
+
current_z = self.parent().current_slice
|
|
6494
|
+
|
|
6495
|
+
# Clean up the worker
|
|
6124
6496
|
self.kill_segmentation()
|
|
6497
|
+
self.segmentation_worker = None
|
|
6125
6498
|
time.sleep(0.1)
|
|
6499
|
+
|
|
6500
|
+
# Auto-restart for 2D preview mode only if certain conditions are met
|
|
6501
|
+
if self.previewing and self.use_two:
|
|
6502
|
+
# Track when this slice was last processed
|
|
6503
|
+
if not hasattr(self, '_processed_slices'):
|
|
6504
|
+
self._processed_slices = {}
|
|
6505
|
+
|
|
6506
|
+
current_time = time.time()
|
|
6507
|
+
|
|
6508
|
+
# Check if we've recently tried to process this slice (to prevent loops)
|
|
6509
|
+
recently_processed = False
|
|
6510
|
+
if current_z in self._processed_slices:
|
|
6511
|
+
time_since_last_attempt = current_time - self._processed_slices[current_z]
|
|
6512
|
+
recently_processed = time_since_last_attempt < 5.0 # 5 second cooldown
|
|
6513
|
+
|
|
6514
|
+
if not recently_processed:
|
|
6515
|
+
self._processed_slices[current_z] = current_time
|
|
6516
|
+
|
|
6517
|
+
# Reset any processing flags in the segmenter
|
|
6518
|
+
if hasattr(self.segmenter, '_currently_processing'):
|
|
6519
|
+
self.segmenter._currently_processing = None
|
|
6520
|
+
|
|
6521
|
+
# Create a new worker after a brief delay
|
|
6522
|
+
QTimer.singleShot(500, self.start_segmentation)
|
|
6523
|
+
|
|
6126
6524
|
|
|
6127
6525
|
|
|
6128
6526
|
def kill_segmentation(self):
|
|
6129
|
-
if hasattr(self, 'segmentation_worker'):
|
|
6527
|
+
if hasattr(self, 'segmentation_worker') and self.segmentation_worker is not None:
|
|
6528
|
+
# Signal the thread to stop
|
|
6130
6529
|
self.segmentation_worker.stop()
|
|
6131
|
-
|
|
6530
|
+
|
|
6531
|
+
# Wait for the thread to finish
|
|
6532
|
+
if self.segmentation_worker.isRunning():
|
|
6533
|
+
self.segmentation_worker.wait(1000) # Wait up to 1 second
|
|
6534
|
+
|
|
6535
|
+
# If thread is still running after timeout, try to force termination
|
|
6536
|
+
if self.segmentation_worker.isRunning():
|
|
6537
|
+
self.segmentation_worker.terminate()
|
|
6538
|
+
self.segmentation_worker.wait() # Wait for it to be terminated
|
|
6539
|
+
|
|
6540
|
+
# Now safe to delete
|
|
6541
|
+
del self.segmentation_worker
|
|
6542
|
+
self.segmentation_worker = None
|
|
6132
6543
|
|
|
6133
6544
|
|
|
6134
6545
|
def segment(self):
|
|
@@ -6141,6 +6552,8 @@ class MachineWindow(QMainWindow):
|
|
|
6141
6552
|
self.kill_segmentation()
|
|
6142
6553
|
time.sleep(0.1)
|
|
6143
6554
|
|
|
6555
|
+
self.previewing = False
|
|
6556
|
+
|
|
6144
6557
|
if self.parent().active_channel == 0:
|
|
6145
6558
|
if self.parent().channel_data[0] is not None:
|
|
6146
6559
|
active_data = self.parent().channel_data[0]
|
|
@@ -6170,20 +6583,26 @@ class MachineWindow(QMainWindow):
|
|
|
6170
6583
|
|
|
6171
6584
|
self.parent().update_display()
|
|
6172
6585
|
|
|
6586
|
+
self.previewing = False
|
|
6587
|
+
|
|
6173
6588
|
print("Finished segmentation moved to Overlay 2. Use File -> Save(As) for disk saving.")
|
|
6174
6589
|
|
|
6175
6590
|
def closeEvent(self, event):
|
|
6176
6591
|
if self.parent().isVisible():
|
|
6177
6592
|
if self.confirm_close_dialog():
|
|
6178
|
-
|
|
6593
|
+
# Clean up resources before closing
|
|
6179
6594
|
if self.brush_button.isChecked():
|
|
6180
6595
|
self.silence_button()
|
|
6181
6596
|
self.toggle_brush_mode()
|
|
6597
|
+
|
|
6182
6598
|
self.parent().pen_button.setEnabled(True)
|
|
6183
6599
|
self.parent().brush_mode = False
|
|
6184
|
-
|
|
6600
|
+
|
|
6601
|
+
# Kill the segmentation thread and wait for it to finish
|
|
6185
6602
|
self.kill_segmentation()
|
|
6186
|
-
time.sleep(0.
|
|
6603
|
+
time.sleep(0.2) # Give additional time for cleanup
|
|
6604
|
+
|
|
6605
|
+
self.parent().machine_window = None
|
|
6187
6606
|
else:
|
|
6188
6607
|
event.ignore()
|
|
6189
6608
|
|
|
@@ -6194,48 +6613,84 @@ class SegmentationWorker(QThread):
|
|
|
6194
6613
|
finished = pyqtSignal()
|
|
6195
6614
|
chunk_processed = pyqtSignal()
|
|
6196
6615
|
|
|
6197
|
-
def __init__(self, highlight_overlay, segmenter, use_gpu):
|
|
6616
|
+
def __init__(self, highlight_overlay, segmenter, use_gpu, use_two, previewing, machine_window):
|
|
6198
6617
|
super().__init__()
|
|
6199
6618
|
self.overlay = highlight_overlay
|
|
6200
6619
|
self.segmenter = segmenter
|
|
6201
6620
|
self.use_gpu = use_gpu
|
|
6621
|
+
self.use_two = use_two
|
|
6622
|
+
self.previewing = previewing
|
|
6623
|
+
self.machine_window = machine_window
|
|
6202
6624
|
self._stop = False
|
|
6203
6625
|
self.update_interval = 1 # Increased to 500ms
|
|
6204
6626
|
self.chunks_since_update = 0
|
|
6205
6627
|
self.chunks_per_update = 5 # Only update every 5 chunks
|
|
6628
|
+
self.poked = False # If it should wake up or not
|
|
6206
6629
|
self.last_update = time.time()
|
|
6207
6630
|
|
|
6208
6631
|
def stop(self):
|
|
6209
6632
|
self._stop = True
|
|
6633
|
+
|
|
6634
|
+
def get_poked(self):
|
|
6635
|
+
self.poked = True
|
|
6210
6636
|
|
|
6211
6637
|
def run(self):
|
|
6212
6638
|
try:
|
|
6213
6639
|
self.overlay.fill(False)
|
|
6214
6640
|
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6641
|
+
# Remember the starting z position
|
|
6642
|
+
self.starting_z = self.segmenter.current_z
|
|
6643
|
+
|
|
6644
|
+
if self.previewing and self.use_two:
|
|
6645
|
+
# Process current z-slice in chunks
|
|
6646
|
+
current_z = self.segmenter.current_z
|
|
6647
|
+
|
|
6648
|
+
# Process the slice with chunked generator
|
|
6649
|
+
for foreground, background in self.segmenter.segment_slice_chunked(current_z):
|
|
6650
|
+
if self._stop:
|
|
6651
|
+
break
|
|
6218
6652
|
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6653
|
+
# Update the overlay
|
|
6654
|
+
for z,y,x in foreground:
|
|
6655
|
+
self.overlay[z,y,x] = 1
|
|
6656
|
+
for z,y,x in background:
|
|
6657
|
+
self.overlay[z,y,x] = 2
|
|
6658
|
+
|
|
6659
|
+
# Signal update after each chunk
|
|
6660
|
+
self.chunks_since_update += 1
|
|
6661
|
+
current_time = time.time()
|
|
6662
|
+
if (self.chunks_since_update >= self.chunks_per_update and
|
|
6663
|
+
current_time - self.last_update >= self.update_interval):
|
|
6664
|
+
self.chunk_processed.emit()
|
|
6665
|
+
self.chunks_since_update = 0
|
|
6666
|
+
self.last_update = current_time
|
|
6667
|
+
|
|
6668
|
+
else:
|
|
6669
|
+
# Original 3D approach
|
|
6670
|
+
for foreground_coords, background_coords in self.segmenter.segment_volume_realtime(gpu=self.use_gpu):
|
|
6671
|
+
if self._stop:
|
|
6672
|
+
break
|
|
6673
|
+
|
|
6674
|
+
for z,y,x in foreground_coords:
|
|
6675
|
+
self.overlay[z,y,x] = 1
|
|
6676
|
+
for z,y,x in background_coords:
|
|
6677
|
+
self.overlay[z,y,x] = 2
|
|
6678
|
+
|
|
6679
|
+
self.chunks_since_update += 1
|
|
6680
|
+
current_time = time.time()
|
|
6681
|
+
if (self.chunks_since_update >= self.chunks_per_update and
|
|
6682
|
+
current_time - self.last_update >= self.update_interval):
|
|
6683
|
+
self.chunk_processed.emit()
|
|
6684
|
+
self.chunks_since_update = 0
|
|
6685
|
+
self.last_update = current_time
|
|
6686
|
+
|
|
6233
6687
|
self.finished.emit()
|
|
6234
6688
|
|
|
6235
6689
|
except Exception as e:
|
|
6236
6690
|
print(f"Error in segmentation: {e}")
|
|
6237
|
-
|
|
6238
|
-
|
|
6691
|
+
import traceback
|
|
6692
|
+
traceback.print_exc()
|
|
6693
|
+
|
|
6239
6694
|
def run_batch(self):
|
|
6240
6695
|
try:
|
|
6241
6696
|
foreground_coords, _ = self.segmenter.segment_volume()
|
|
@@ -6851,6 +7306,8 @@ class MaskDialog(QDialog):
|
|
|
6851
7306
|
output_target = self.output_selector.currentIndex()
|
|
6852
7307
|
|
|
6853
7308
|
if accepted_mode == 4:
|
|
7309
|
+
if self.parent().mini_overlay == True:
|
|
7310
|
+
self.parent().create_highlight_overlay(node_indices = self.parent().clicked_values['nodes'], edge_indices = self.parent().clicked_values['edges'])
|
|
6854
7311
|
active_data = self.parent().highlight_overlay
|
|
6855
7312
|
else:
|
|
6856
7313
|
active_data = self.parent().channel_data[accepted_mode]
|