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.

Potentially problematic release.


This version of nettracer3d might be problematic. Click here for more details.

@@ -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
- self.parent().update_brightness(channel, values)
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
- try:
9342
- 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.")
9343
- self.parent().load_channel(0, color = True)
9344
- except:
9345
- pass
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.use_two:
9854
- self.previewing = True
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
- if not self.trained:
9867
- return
9868
- else:
9869
- self.segmentation_worker = SegmentationWorker(self.parent().highlight_overlay, self.segmenter, self.use_gpu, self.use_two, self.previewing, self, self.mem_lock)
9870
- self.segmentation_worker.chunk_processed.connect(self.update_display) # Just update display
9871
- current_xlim = self.parent().ax.get_xlim()
9872
- current_ylim = self.parent().ax.get_ylim()
9873
- try:
9874
- x, y = self.parent().get_current_mouse_position()
9875
- except:
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.update_interval = 1 # Increased to 500ms
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