nettracer3d 0.8.8__py3-none-any.whl → 0.8.9__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/nettracer_gui.py +155 -35
- nettracer3d/segmenter.py +294 -186
- nettracer3d/segmenter_GPU.py +274 -158
- {nettracer3d-0.8.8.dist-info → nettracer3d-0.8.9.dist-info}/METADATA +2 -2
- {nettracer3d-0.8.8.dist-info → nettracer3d-0.8.9.dist-info}/RECORD +9 -9
- {nettracer3d-0.8.8.dist-info → nettracer3d-0.8.9.dist-info}/WHEEL +0 -0
- {nettracer3d-0.8.8.dist-info → nettracer3d-0.8.9.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.8.8.dist-info → nettracer3d-0.8.9.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.8.8.dist-info → nettracer3d-0.8.9.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -3161,6 +3161,18 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3161
3161
|
# End this stroke but keep session active for continuous painting
|
|
3162
3162
|
self.painting = False
|
|
3163
3163
|
|
|
3164
|
+
if self.erase:
|
|
3165
|
+
if (hasattr(self, 'virtual_draw_operations') and self.virtual_draw_operations) or \
|
|
3166
|
+
(hasattr(self, 'virtual_erase_operations') and self.virtual_erase_operations) or \
|
|
3167
|
+
(hasattr(self, 'current_operation') and self.current_operation):
|
|
3168
|
+
# Finish current operation first
|
|
3169
|
+
if hasattr(self, 'current_operation') and self.current_operation:
|
|
3170
|
+
self.pm.finish_current_virtual_operation()
|
|
3171
|
+
# Now convert to real data
|
|
3172
|
+
self.pm.convert_virtual_strokes_to_data()
|
|
3173
|
+
current_xlim = self.ax.get_xlim()
|
|
3174
|
+
current_ylim = self.ax.get_ylim()
|
|
3175
|
+
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
3164
3176
|
|
|
3165
3177
|
if self.resume:
|
|
3166
3178
|
self.machine_window.segmentation_worker.resume()
|
|
@@ -4779,7 +4791,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4779
4791
|
try:
|
|
4780
4792
|
#if len(self.channel_data[channel_index].shape) == 4:
|
|
4781
4793
|
if 1 in self.channel_data[channel_index].shape:
|
|
4782
|
-
print("Removing singleton dimension (I am assuming this is a channel dimension?)")
|
|
4794
|
+
#print("Removing singleton dimension (I am assuming this is a channel dimension?)")
|
|
4783
4795
|
self.channel_data[channel_index] = np.squeeze(self.channel_data[channel_index])
|
|
4784
4796
|
except:
|
|
4785
4797
|
pass
|
|
@@ -4907,6 +4919,14 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4907
4919
|
self.original_ylim, self.original_xlim = (self.shape[1] + 0.5, -0.5), (-0.5, self.shape[2] - 0.5)
|
|
4908
4920
|
#print(self.original_xlim)
|
|
4909
4921
|
|
|
4922
|
+
self.completed_paint_strokes = [] #Reset pending paint operations
|
|
4923
|
+
self.current_stroke_points = []
|
|
4924
|
+
self.current_stroke_type = None
|
|
4925
|
+
self.virtual_draw_operations = []
|
|
4926
|
+
self.virtual_erase_operations = []
|
|
4927
|
+
self.current_operation = []
|
|
4928
|
+
self.current_operation_type = None
|
|
4929
|
+
|
|
4910
4930
|
if not end_paint:
|
|
4911
4931
|
|
|
4912
4932
|
self.update_display(reset_resize = reset_resize, preserve_zoom = preserve_zoom)
|
|
@@ -5143,8 +5163,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
5143
5163
|
if self.mini_overlay == True: #If we are rendering the highlight overlay for selected values one at a time.
|
|
5144
5164
|
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
5145
5165
|
self.update_display(preserve_zoom=view_settings)
|
|
5146
|
-
if self.machine_window is not None:
|
|
5147
|
-
self.machine_window.poke_segmenter()
|
|
5166
|
+
#if self.machine_window is not None:
|
|
5167
|
+
#self.machine_window.poke_segmenter()
|
|
5148
5168
|
self.pending_slice = None
|
|
5149
5169
|
|
|
5150
5170
|
def update_brightness(self, channel_index, values):
|
|
@@ -6478,6 +6498,13 @@ class BrightnessContrastDialog(QDialog):
|
|
|
6478
6498
|
channel_layout.addWidget(slider_container)
|
|
6479
6499
|
layout.addWidget(channel_widget)
|
|
6480
6500
|
|
|
6501
|
+
#debouncing
|
|
6502
|
+
self.debounce_timer = QTimer()
|
|
6503
|
+
self.debounce_timer.setSingleShot(True)
|
|
6504
|
+
self.debounce_timer.timeout.connect(self._apply_pending_updates)
|
|
6505
|
+
self.pending_updates = {}
|
|
6506
|
+
self.debounce_delay = 300 # 300ms delay
|
|
6507
|
+
|
|
6481
6508
|
# Connect signals
|
|
6482
6509
|
slider.valueChanged.connect(lambda values, ch=i: self.on_slider_change(ch, values))
|
|
6483
6510
|
min_input.editingFinished.connect(lambda ch=i: self.on_min_input_change(ch))
|
|
@@ -6488,7 +6515,18 @@ class BrightnessContrastDialog(QDialog):
|
|
|
6488
6515
|
min_val, max_val = values
|
|
6489
6516
|
self.min_inputs[channel].setText(str(min_val))
|
|
6490
6517
|
self.max_inputs[channel].setText(str(max_val))
|
|
6491
|
-
|
|
6518
|
+
|
|
6519
|
+
# Store the pending update
|
|
6520
|
+
self.pending_updates[channel] = values
|
|
6521
|
+
|
|
6522
|
+
# Restart the debounce timer
|
|
6523
|
+
self.debounce_timer.start(self.debounce_delay)
|
|
6524
|
+
|
|
6525
|
+
def _apply_pending_updates(self):
|
|
6526
|
+
"""Apply all pending brightness updates"""
|
|
6527
|
+
for channel, values in self.pending_updates.items():
|
|
6528
|
+
self.parent().update_brightness(channel, values)
|
|
6529
|
+
self.pending_updates.clear()
|
|
6492
6530
|
|
|
6493
6531
|
def on_min_input_change(self, channel):
|
|
6494
6532
|
"""Handle changes to minimum value input"""
|
|
@@ -9338,11 +9376,13 @@ class ThresholdDialog(QDialog):
|
|
|
9338
9376
|
|
|
9339
9377
|
def start_ml(self, GPU = False):
|
|
9340
9378
|
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9379
|
+
if self.parent().channel_data[0] is None:
|
|
9380
|
+
|
|
9381
|
+
try:
|
|
9382
|
+
print("Please select image to load into nodes channel for segmentation or press X if you already have the one you want. Note that this load may permit a color image in the nodes channel for segmentation purposes only, which is otherwise not allowed.")
|
|
9383
|
+
self.parent().load_channel(0, color = True)
|
|
9384
|
+
except:
|
|
9385
|
+
pass
|
|
9346
9386
|
|
|
9347
9387
|
|
|
9348
9388
|
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:
|
|
@@ -9589,14 +9629,8 @@ class MachineWindow(QMainWindow):
|
|
|
9589
9629
|
train_quick.clicked.connect(lambda: self.train_model(speed=True))
|
|
9590
9630
|
train_detailed = QPushButton("Train Detailed Model (For Morphology)")
|
|
9591
9631
|
train_detailed.clicked.connect(lambda: self.train_model(speed=False))
|
|
9592
|
-
save = QPushButton("Save Model")
|
|
9593
|
-
save.clicked.connect(self.save_model)
|
|
9594
|
-
load = QPushButton("Load Model")
|
|
9595
|
-
load.clicked.connect(self.load_model)
|
|
9596
9632
|
training_layout.addWidget(train_quick)
|
|
9597
9633
|
training_layout.addWidget(train_detailed)
|
|
9598
|
-
training_layout.addWidget(save)
|
|
9599
|
-
training_layout.addWidget(load)
|
|
9600
9634
|
training_group.setLayout(training_layout)
|
|
9601
9635
|
|
|
9602
9636
|
# Group 4: Segmentation Options
|
|
@@ -9621,12 +9655,27 @@ class MachineWindow(QMainWindow):
|
|
|
9621
9655
|
segmentation_layout.addWidget(full_button)
|
|
9622
9656
|
segmentation_group.setLayout(segmentation_layout)
|
|
9623
9657
|
|
|
9658
|
+
# Group 5: Loading Options
|
|
9659
|
+
loading_group = QGroupBox("Saving/Loading")
|
|
9660
|
+
loading_layout = QHBoxLayout()
|
|
9661
|
+
self.save = QPushButton("Save Model")
|
|
9662
|
+
self.save.clicked.connect(self.save_model)
|
|
9663
|
+
self.load = QPushButton("Load Model")
|
|
9664
|
+
self.load.clicked.connect(self.load_model)
|
|
9665
|
+
load_nodes = QPushButton("Load Image (For Seg - Supports Color Images)")
|
|
9666
|
+
load_nodes.clicked.connect(self.load_nodes)
|
|
9667
|
+
loading_layout.addWidget(self.save)
|
|
9668
|
+
loading_layout.addWidget(self.load)
|
|
9669
|
+
loading_layout.addWidget(load_nodes)
|
|
9670
|
+
loading_group.setLayout(loading_layout)
|
|
9671
|
+
|
|
9624
9672
|
# Add all groups to main layout
|
|
9625
9673
|
main_layout.addWidget(drawing_group)
|
|
9626
9674
|
if not GPU:
|
|
9627
9675
|
main_layout.addWidget(processing_group)
|
|
9628
9676
|
main_layout.addWidget(training_group)
|
|
9629
9677
|
main_layout.addWidget(segmentation_group)
|
|
9678
|
+
main_layout.addWidget(loading_group)
|
|
9630
9679
|
|
|
9631
9680
|
# Set the main widget as the central widget
|
|
9632
9681
|
self.setCentralWidget(main_widget)
|
|
@@ -9649,6 +9698,79 @@ class MachineWindow(QMainWindow):
|
|
|
9649
9698
|
except:
|
|
9650
9699
|
return
|
|
9651
9700
|
|
|
9701
|
+
def load_nodes(self):
|
|
9702
|
+
|
|
9703
|
+
def confirm_machine_dialog():
|
|
9704
|
+
"""Shows a dialog asking user to confirm if they want to start the segmenter"""
|
|
9705
|
+
msg = QMessageBox()
|
|
9706
|
+
msg.setIcon(QMessageBox.Icon.Question)
|
|
9707
|
+
msg.setText("Alert")
|
|
9708
|
+
msg.setInformativeText("Use of this feature will require use of both overlay channels and the highlight overlay. Please save any data and return, or proceed if you do not need those overlays")
|
|
9709
|
+
msg.setWindowTitle("Proceed?")
|
|
9710
|
+
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
9711
|
+
return msg.exec() == QMessageBox.StandardButton.Yes
|
|
9712
|
+
|
|
9713
|
+
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:
|
|
9714
|
+
if confirm_machine_dialog():
|
|
9715
|
+
pass
|
|
9716
|
+
else:
|
|
9717
|
+
return
|
|
9718
|
+
|
|
9719
|
+
try:
|
|
9720
|
+
|
|
9721
|
+
try:
|
|
9722
|
+
print("Please select image to load into nodes channel for segmentation or press X if you already have the one you want. Note that this load may permit a color image in the nodes channel for segmentation purposes only, which is otherwise not allowed.")
|
|
9723
|
+
self.parent().reset(nodes = True, edges = True, search_region = True, network_overlay = True, id_overlay = True)
|
|
9724
|
+
self.parent().highlight_overlay = None
|
|
9725
|
+
self.parent().load_channel(0, color = True)
|
|
9726
|
+
if self.parent().active_channel == 0:
|
|
9727
|
+
if self.parent().channel_data[0] is not None:
|
|
9728
|
+
try:
|
|
9729
|
+
active_data = self.parent().channel_data[0]
|
|
9730
|
+
act_channel = 0
|
|
9731
|
+
except:
|
|
9732
|
+
active_data = self.parent().channel_data[1]
|
|
9733
|
+
act_channel = 1
|
|
9734
|
+
import traceback
|
|
9735
|
+
traceback.print_exc()
|
|
9736
|
+
else:
|
|
9737
|
+
active_data = self.parent().channel_data[1]
|
|
9738
|
+
act_channel = 1
|
|
9739
|
+
|
|
9740
|
+
try:
|
|
9741
|
+
if len(active_data.shape) == 3:
|
|
9742
|
+
array1 = np.zeros_like(active_data).astype(np.uint8)
|
|
9743
|
+
elif len(active_data.shape) == 4:
|
|
9744
|
+
array1 = np.zeros_like(active_data)[:,:,:,0].astype(np.uint8)
|
|
9745
|
+
except:
|
|
9746
|
+
print("No data in nodes channel")
|
|
9747
|
+
import traceback
|
|
9748
|
+
traceback.print_exc()
|
|
9749
|
+
return
|
|
9750
|
+
array3 = np.zeros_like(array1).astype(np.uint8)
|
|
9751
|
+
self.parent().highlight_overlay = array3 #Clear this out for the segmenter to use
|
|
9752
|
+
|
|
9753
|
+
self.parent().load_channel(2, array1, True)
|
|
9754
|
+
self.trained = False
|
|
9755
|
+
self.previewing = False
|
|
9756
|
+
|
|
9757
|
+
self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=False)
|
|
9758
|
+
|
|
9759
|
+
self.segmentation_worker = None
|
|
9760
|
+
|
|
9761
|
+
self.fore_button.click()
|
|
9762
|
+
self.fore_button.click()
|
|
9763
|
+
|
|
9764
|
+
self.num_chunks = 0
|
|
9765
|
+
self.parent().update_display()
|
|
9766
|
+
except:
|
|
9767
|
+
import traceback
|
|
9768
|
+
traceback.print_exc()
|
|
9769
|
+
pass
|
|
9770
|
+
|
|
9771
|
+
except:
|
|
9772
|
+
pass
|
|
9773
|
+
|
|
9652
9774
|
def toggle_segment(self):
|
|
9653
9775
|
|
|
9654
9776
|
if self.segmentation_worker is not None:
|
|
@@ -9850,10 +9972,8 @@ class MachineWindow(QMainWindow):
|
|
|
9850
9972
|
self.kill_segmentation()
|
|
9851
9973
|
time.sleep(0.1)
|
|
9852
9974
|
|
|
9853
|
-
if self.
|
|
9854
|
-
|
|
9855
|
-
else:
|
|
9856
|
-
print("Beginning new segmentation...")
|
|
9975
|
+
if not self.trained:
|
|
9976
|
+
return
|
|
9857
9977
|
|
|
9858
9978
|
if self.parent().channel_data[2] is not None:
|
|
9859
9979
|
active_data = self.parent().channel_data[2]
|
|
@@ -9863,19 +9983,16 @@ class MachineWindow(QMainWindow):
|
|
|
9863
9983
|
array3 = np.zeros_like(active_data).astype(np.uint8)
|
|
9864
9984
|
self.parent().highlight_overlay = array3 #Clear this out for the segmenter to use
|
|
9865
9985
|
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
x, y = 0, 0
|
|
9877
|
-
self.segmenter.update_position(self.parent().current_slice, x, y)
|
|
9878
|
-
self.segmentation_worker.start()
|
|
9986
|
+
self.segmentation_worker = SegmentationWorker(self.parent().highlight_overlay, self.segmenter, self.use_gpu, self.use_two, self.previewing, self, self.mem_lock)
|
|
9987
|
+
self.segmentation_worker.chunk_processed.connect(self.update_display) # Just update display
|
|
9988
|
+
current_xlim = self.parent().ax.get_xlim()
|
|
9989
|
+
current_ylim = self.parent().ax.get_ylim()
|
|
9990
|
+
try:
|
|
9991
|
+
x, y = self.parent().get_current_mouse_position()
|
|
9992
|
+
except:
|
|
9993
|
+
x, y = 0, 0
|
|
9994
|
+
self.segmenter.update_position(self.parent().current_slice, x, y)
|
|
9995
|
+
self.segmentation_worker.start()
|
|
9879
9996
|
|
|
9880
9997
|
def confirm_seg_dialog(self):
|
|
9881
9998
|
"""Shows a dialog asking user to confirm segment all"""
|
|
@@ -10086,7 +10203,10 @@ class SegmentationWorker(QThread):
|
|
|
10086
10203
|
self.mem_lock = mem_lock
|
|
10087
10204
|
self._stop = False
|
|
10088
10205
|
self._paused = False # Add pause flag
|
|
10089
|
-
self.
|
|
10206
|
+
if self.machine_window.parent().shape[1] * self.machine_window.parent().shape[2] > 3000 * 3000: #arbitrary throttle for large arrays.
|
|
10207
|
+
self.update_interval = 10
|
|
10208
|
+
else:
|
|
10209
|
+
self.update_interval = 1 # Increased to 1s
|
|
10090
10210
|
self.chunks_since_update = 0
|
|
10091
10211
|
self.chunks_per_update = 5 # Only update every 5 chunks
|
|
10092
10212
|
self.poked = False # If it should wake up or not
|
|
@@ -10138,8 +10258,8 @@ class SegmentationWorker(QThread):
|
|
|
10138
10258
|
current_time = time.time()
|
|
10139
10259
|
if (self.chunks_since_update >= self.chunks_per_update and
|
|
10140
10260
|
current_time - self.last_update >= self.update_interval):
|
|
10141
|
-
if self.machine_window.parent().shape[1] * self.machine_window.parent().shape[2] > 3000 * 3000: #arbitrary throttle for large arrays.
|
|
10142
|
-
self.msleep(3000)
|
|
10261
|
+
#if self.machine_window.parent().shape[1] * self.machine_window.parent().shape[2] > 3000 * 3000: #arbitrary throttle for large arrays.
|
|
10262
|
+
#self.msleep(3000)
|
|
10143
10263
|
self.chunk_processed.emit()
|
|
10144
10264
|
self.chunks_since_update = 0
|
|
10145
10265
|
self.last_update = current_time
|