nettracer3d 0.4.4__py3-none-any.whl → 0.4.6__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.py CHANGED
@@ -858,10 +858,26 @@ def hash_inners(search_region, inner_edges, GPU = True):
858
858
 
859
859
  return inner_edges
860
860
 
861
+
862
+ def dilate_2D(array, search, scaling = 1):
863
+
864
+ inv = array < 1
865
+
866
+ inv = smart_dilate.compute_distance_transform_distance(inv)
867
+
868
+ inv = inv * scaling
869
+
870
+ inv = inv <= search
871
+
872
+ return inv
873
+
874
+
861
875
  def dilate_3D(tiff_array, dilated_x, dilated_y, dilated_z):
862
876
  """Internal method to dilate an array in 3D. Dilation this way is much faster than using a distance transform although the latter is theoretically more accurate.
863
877
  Arguments are an array, and the desired pixel dilation amounts in X, Y, Z."""
864
878
 
879
+ if tiff_array.shape[0] == 1:
880
+ return dilate_2D(tiff_array, ((dilated_x - 1) / 2))
865
881
 
866
882
  if dilated_x == 3 and dilated_y == 3 and dilated_z == 3:
867
883
 
@@ -996,6 +1012,8 @@ def dilate_3D_recursive(tiff_array, dilated_x, dilated_y, dilated_z, step_size=N
996
1012
 
997
1013
  Each dilation parameter represents (n-1)/2 steps outward from the object.
998
1014
  """
1015
+ if tiff_array.shape[0] == 1:
1016
+ return dilate_2D(tiff_array, ((dilated_x - 1) / 2))
999
1017
  # Calculate the smallest dimension of the array
1000
1018
  min_dim = min(tiff_array.shape)
1001
1019
 
@@ -2414,6 +2432,7 @@ class Network_3D:
2414
2432
  Can be called on a Network_3D object to save the node centroids properties to hard mem as a .xlsx file. It will save to the active directory if none is specified.
2415
2433
  :param directory: (Optional - Val = None; String). The path to an indended directory to save the centroids to.
2416
2434
  """
2435
+
2417
2436
  if self._node_centroids is not None:
2418
2437
  if directory is None:
2419
2438
  network_analysis._save_centroid_dictionary(self._node_centroids, 'node_centroids.xlsx')
@@ -2424,7 +2443,14 @@ class Network_3D:
2424
2443
  print(f"Centroids saved to {directory}/node_centroids.xlsx")
2425
2444
 
2426
2445
  if self._node_centroids is None:
2427
- print("Node centroids attribute is empty, did not save...")
2446
+ if directory is None:
2447
+ network_analysis._save_centroid_dictionary({}, 'node_centroids.xlsx')
2448
+ print("Centroids saved to node_centroids.xlsx")
2449
+
2450
+ if directory is not None:
2451
+ network_analysis._save_centroid_dictionary({}, f'{directory}/node_centroids.xlsx')
2452
+ print(f"Centroids saved to {directory}/node_centroids.xlsx")
2453
+
2428
2454
 
2429
2455
  def save_edge_centroids(self, directory = None):
2430
2456
  """
@@ -2442,6 +2468,13 @@ class Network_3D:
2442
2468
 
2443
2469
  if self._edge_centroids is None:
2444
2470
  print("Edge centroids attribute is empty, did not save...")
2471
+ if directory is None:
2472
+ network_analysis._save_centroid_dictionary({}, 'edge_centroids.xlsx', index = 'Edge ID')
2473
+ print("Centroids saved to edge_centroids.xlsx")
2474
+
2475
+ if directory is not None:
2476
+ network_analysis._save_centroid_dictionary({}, f'{directory}/edge_centroids.xlsx', index = 'Edge ID')
2477
+ print(f"Centroids saved to {directory}/edge_centroids.xlsx")
2445
2478
 
2446
2479
  def save_search_region(self, directory = None):
2447
2480
  """
@@ -2494,7 +2527,13 @@ class Network_3D:
2494
2527
  print(f"Node identities saved to {directory}/node_identities.xlsx")
2495
2528
 
2496
2529
  if self._node_identities is None:
2497
- print("Node identities attribute is empty...")
2530
+ if directory is None:
2531
+ network_analysis.save_singval_dict({}, 'NodeID', 'Identity', 'node_identities.xlsx')
2532
+ print("Node identities saved to node_identities.xlsx")
2533
+
2534
+ if directory is not None:
2535
+ network_analysis.save_singval_dict({}, 'NodeID', 'Identity', f'{directory}/node_identities.xlsx')
2536
+ print(f"Node identities saved to {directory}/node_identities.xlsx")
2498
2537
 
2499
2538
  def save_communities(self, directory = None):
2500
2539
  """
@@ -2511,8 +2550,13 @@ class Network_3D:
2511
2550
  print(f"Communities saved to {directory}/node_communities.xlsx")
2512
2551
 
2513
2552
  if self._communities is None:
2514
- print("Communities attribute is empty...")
2553
+ if directory is None:
2554
+ network_analysis.save_singval_dict({}, 'NodeID', 'Community', 'node_communities.xlsx')
2555
+ print("Communities saved to node_communities.xlsx")
2515
2556
 
2557
+ if directory is not None:
2558
+ network_analysis.save_singval_dict({}, 'NodeID', 'Community', f'{directory}/node_communities.xlsx')
2559
+ print(f"Communities saved to {directory}/node_communities.xlsx")
2516
2560
 
2517
2561
  def save_network_overlay(self, directory = None, filename = None):
2518
2562
 
@@ -5,7 +5,7 @@ from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
5
5
  QFormLayout, QLineEdit, QPushButton, QFileDialog,
6
6
  QLabel, QComboBox, QMessageBox, QTableView, QInputDialog,
7
7
  QMenu, QTabWidget)
8
- from PyQt6.QtCore import (QPoint, Qt, QAbstractTableModel, QTimer)
8
+ from PyQt6.QtCore import (QPoint, Qt, QAbstractTableModel, QTimer, QThread, pyqtSignal)
9
9
  import numpy as np
10
10
  import time
11
11
  from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
@@ -13,8 +13,8 @@ from matplotlib.figure import Figure
13
13
  import matplotlib.pyplot as plt
14
14
  from qtrangeslider import QRangeSlider
15
15
  from nettracer3d import nettracer as n3d
16
- from nettracer3d import smart_dilate as sdl
17
16
  from nettracer3d import proximity as pxt
17
+ 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
@@ -27,6 +27,7 @@ from functools import partial
27
27
  from nettracer3d import segmenter
28
28
 
29
29
 
30
+
30
31
  class ImageViewerWindow(QMainWindow):
31
32
  def __init__(self):
32
33
  super().__init__()
@@ -178,7 +179,7 @@ class ImageViewerWindow(QMainWindow):
178
179
  self.zoom_button.setCheckable(True)
179
180
  self.zoom_button.setFixedSize(40, 40)
180
181
  self.zoom_button.clicked.connect(self.toggle_zoom_mode)
181
- control_layout.addWidget(self.zoom_button)
182
+ buttons_layout.addWidget(self.zoom_button)
182
183
  self.resizing = False
183
184
 
184
185
  self.pan_button = QPushButton("✋")
@@ -187,6 +188,14 @@ class ImageViewerWindow(QMainWindow):
187
188
  self.pan_button.clicked.connect(self.toggle_pan_mode)
188
189
  buttons_layout.addWidget(self.pan_button)
189
190
 
191
+ self.high_button = QPushButton("👁️")
192
+ self.high_button.setCheckable(True)
193
+ self.high_button.setFixedSize(40, 40)
194
+ self.high_button.clicked.connect(self.toggle_highlight)
195
+ self.high_button.setChecked(True)
196
+ buttons_layout.addWidget(self.high_button)
197
+ self.highlight = True
198
+
190
199
  control_layout.addWidget(buttons_widget)
191
200
 
192
201
  self.preview = False #Whether in preview mode or not
@@ -1429,6 +1438,12 @@ class ImageViewerWindow(QMainWindow):
1429
1438
 
1430
1439
 
1431
1440
 
1441
+ def toggle_highlight(self):
1442
+ self.highlight = self.high_button.isChecked()
1443
+ current_xlim = self.ax.get_xlim() if hasattr(self, 'ax') and self.ax.get_xlim() != (0, 1) else None
1444
+ current_ylim = self.ax.get_ylim() if hasattr(self, 'ax') and self.ax.get_ylim() != (0, 1) else None
1445
+
1446
+ self.update_display(preserve_zoom=(current_xlim, current_ylim))
1432
1447
 
1433
1448
 
1434
1449
  def toggle_zoom_mode(self):
@@ -1512,6 +1527,8 @@ class ImageViewerWindow(QMainWindow):
1512
1527
  if self.machine_window is not None:
1513
1528
  if event.key() == Qt.Key_A:
1514
1529
  self.machine_window.switch_foreground()
1530
+ if event.key() == Qt.Key_X:
1531
+ self.high_button.click()
1515
1532
 
1516
1533
 
1517
1534
  def update_brush_cursor(self):
@@ -1649,11 +1666,7 @@ class ImageViewerWindow(QMainWindow):
1649
1666
  x, y = int(event.xdata), int(event.ydata)
1650
1667
  self.last_paint_pos = (x, y)
1651
1668
 
1652
- if self.foreground:
1653
- channel = 2
1654
- else:
1655
- channel = 3
1656
-
1669
+ channel = 2
1657
1670
 
1658
1671
  # Paint at initial position
1659
1672
  self.paint_at_position(x, y, self.erase, channel)
@@ -1692,8 +1705,10 @@ class ImageViewerWindow(QMainWindow):
1692
1705
 
1693
1706
  if erase:
1694
1707
  val = 0
1708
+ elif self.foreground:
1709
+ val = 1
1695
1710
  else:
1696
- val = 255
1711
+ val = 2
1697
1712
 
1698
1713
  height, width = self.channel_data[channel][self.current_slice].shape
1699
1714
  radius = self.brush_size // 2
@@ -1774,10 +1789,7 @@ class ImageViewerWindow(QMainWindow):
1774
1789
 
1775
1790
  x, y = int(event.xdata), int(event.ydata)
1776
1791
 
1777
- if self.foreground:
1778
- channel = 2
1779
- else:
1780
- channel = 3
1792
+ channel = 2
1781
1793
 
1782
1794
 
1783
1795
  if self.channel_data[2] is not None:
@@ -3313,26 +3325,41 @@ class ImageViewerWindow(QMainWindow):
3313
3325
  else:
3314
3326
  normalized_image = np.clip((current_image - vmin) / (vmax - vmin), 0, 1)
3315
3327
 
3316
- # Create custom colormap with higher intensity
3317
- color = base_colors[channel]
3318
- custom_cmap = LinearSegmentedColormap.from_list(
3319
- f'custom_{channel}',
3320
- [(0,0,0,0), (*color,1)]
3321
- )
3322
-
3323
- # Display the image with slightly higher alpha
3324
- self.ax.imshow(normalized_image,
3325
- alpha=0.7,
3326
- cmap=custom_cmap,
3327
- vmin=0,
3328
- vmax=1,
3329
- extent=(-0.5, min_width-0.5, min_height-0.5, -0.5))
3328
+ if channel == 2 and self.machine_window is not None:
3329
+ custom_cmap = LinearSegmentedColormap.from_list(
3330
+ f'custom_{channel}',
3331
+ [(0, 0, 0, 0), # transparent for 0
3332
+ (0.5, 1, 0.5, 1), # light green for 1
3333
+ (1, 0.5, 0.5, 1)] # light red for 2
3334
+ )
3335
+ self.ax.imshow(current_image,
3336
+ cmap=custom_cmap,
3337
+ vmin=0,
3338
+ vmax=2,
3339
+ alpha=0.7,
3340
+ interpolation='nearest',
3341
+ extent=(-0.5, min_width-0.5, min_height-0.5, -0.5))
3342
+ else:
3343
+ # Create custom colormap with higher intensity
3344
+ color = base_colors[channel]
3345
+ custom_cmap = LinearSegmentedColormap.from_list(
3346
+ f'custom_{channel}',
3347
+ [(0,0,0,0), (*color,1)]
3348
+ )
3349
+
3350
+ # Display the image with slightly higher alpha
3351
+ self.ax.imshow(normalized_image,
3352
+ alpha=0.7,
3353
+ cmap=custom_cmap,
3354
+ vmin=0,
3355
+ vmax=1,
3356
+ extent=(-0.5, min_width-0.5, min_height-0.5, -0.5))
3330
3357
 
3331
3358
  if self.preview and not called:
3332
3359
  self.create_highlight_overlay_slice(self.targs, bounds = self.bounds)
3333
3360
 
3334
3361
  # Add highlight overlay if it exists
3335
- if self.highlight_overlay is not None:
3362
+ if self.highlight_overlay is not None and self.highlight and self.machine_window is None:
3336
3363
  highlight_slice = self.highlight_overlay[self.current_slice]
3337
3364
  highlight_cmap = LinearSegmentedColormap.from_list(
3338
3365
  'highlight',
@@ -3341,6 +3368,19 @@ class ImageViewerWindow(QMainWindow):
3341
3368
  self.ax.imshow(highlight_slice,
3342
3369
  cmap=highlight_cmap,
3343
3370
  alpha=0.5)
3371
+ elif self.highlight_overlay is not None and self.highlight:
3372
+ highlight_slice = self.highlight_overlay[self.current_slice]
3373
+ highlight_cmap = LinearSegmentedColormap.from_list(
3374
+ 'highlight',
3375
+ [(0, 0, 0, 0), # transparent for 0
3376
+ (1, 1, 0, 1), # bright yellow for 1
3377
+ (0, 0.7, 1, 1)] # cool blue for 2
3378
+ )
3379
+ self.ax.imshow(highlight_slice,
3380
+ cmap=highlight_cmap,
3381
+ vmin=0,
3382
+ vmax=2, # Important: set vmax to 2 to accommodate both values
3383
+ alpha=0.5)
3344
3384
 
3345
3385
 
3346
3386
 
@@ -5781,13 +5821,13 @@ class MachineWindow(QMainWindow):
5781
5821
  layout.addLayout(form_layout)
5782
5822
 
5783
5823
 
5784
-
5785
-
5786
5824
  if self.parent().active_channel == 0:
5787
5825
  if self.parent().channel_data[0] is not None:
5788
5826
  active_data = self.parent().channel_data[0]
5827
+ act_channel = 0
5789
5828
  else:
5790
5829
  active_data = self.parent().channel_data[1]
5830
+ act_channel = 1
5791
5831
 
5792
5832
 
5793
5833
  array1 = np.zeros_like(active_data)
@@ -5795,12 +5835,18 @@ class MachineWindow(QMainWindow):
5795
5835
  array3 = np.zeros_like(active_data)
5796
5836
  self.parent().highlight_overlay = array3 #Clear this out for the segmenter to use
5797
5837
 
5798
- self.parent().load_channel(2, array1, True) #Temp for debugging
5799
- self.parent().load_channel(3, array2, True) #Temp for debugging
5838
+ self.parent().load_channel(2, array1, True)
5839
+ # Enable the channel button
5840
+ # Not exactly sure why we need all this but the channel buttons weren't loading like they normally do when load_channel() is called:
5841
+ if not self.parent().channel_buttons[2].isEnabled():
5842
+ self.parent().channel_buttons[2].setEnabled(True)
5843
+ self.parent().channel_buttons[2].click()
5844
+ self.parent().delete_buttons[2].setEnabled(True)
5800
5845
 
5846
+ self.parent().base_colors[act_channel] = self.parent().color_dictionary['WHITE']
5801
5847
  self.parent().base_colors[2] = self.parent().color_dictionary['LIGHT_GREEN']
5802
- self.parent().base_colors[3] = self.parent().color_dictionary['LIGHT_RED']
5803
5848
 
5849
+ self.parent().update_display()
5804
5850
 
5805
5851
  # Set a reasonable default size
5806
5852
  self.setMinimumWidth(400)
@@ -5830,20 +5876,39 @@ class MachineWindow(QMainWindow):
5830
5876
  self.back_button.clicked.connect(self.toggle_background)
5831
5877
  form_layout.addWidget(self.back_button)
5832
5878
 
5833
- train_button = QPushButton("Train Model")
5834
- train_button.clicked.connect(self.train_model)
5879
+ self.GPU = QPushButton("GPU")
5880
+ self.GPU.setCheckable(True)
5881
+ self.GPU.setChecked(False)
5882
+ self.GPU.clicked.connect(self.toggle_GPU)
5883
+ form_layout.addWidget(self.GPU)
5884
+ self.use_gpu = False
5885
+
5886
+ train_button = QPushButton("Train Quick Model")
5887
+ train_button.clicked.connect(lambda: self.train_model(speed = True))
5835
5888
  form_layout.addRow(train_button)
5836
5889
 
5837
- seg_button = QPushButton("Segment")
5838
- seg_button.clicked.connect(self.segment)
5890
+ train_button = QPushButton("Train More Detailed Model")
5891
+ train_button.clicked.connect(lambda: self.train_model(speed = False))
5892
+ form_layout.addRow(train_button)
5893
+
5894
+ seg_button = QPushButton("Preview Segment")
5895
+ seg_button.clicked.connect(self.start_segmentation)
5839
5896
  form_layout.addRow(seg_button)
5840
5897
 
5898
+ full_button = QPushButton("Segment All")
5899
+ full_button.clicked.connect(self.segment)
5900
+ form_layout.addRow(full_button)
5901
+
5841
5902
  self.trained = False
5842
5903
 
5843
5904
 
5844
5905
  self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=True)
5845
5906
 
5846
5907
 
5908
+ def toggle_GPU(self):
5909
+
5910
+
5911
+ self.use_gpu = self.GPU.isChecked()
5847
5912
 
5848
5913
 
5849
5914
  def toggle_foreground(self):
@@ -5869,7 +5934,6 @@ class MachineWindow(QMainWindow):
5869
5934
  self.fore_button.setChecked(True)
5870
5935
 
5871
5936
 
5872
-
5873
5937
  def toggle_brush_mode(self):
5874
5938
  """Toggle brush mode on/off"""
5875
5939
  self.parent().brush_mode = self.brush_button.isChecked()
@@ -5889,34 +5953,164 @@ class MachineWindow(QMainWindow):
5889
5953
 
5890
5954
  self.brush_button.click()
5891
5955
 
5892
- def train_model(self):
5956
+ def train_model(self, speed = True):
5893
5957
 
5894
- self.segmenter.train_batch(self.parent().channel_data[2], self.parent().channel_data[3])
5958
+ self.kill_segmentation()
5959
+ # Wait a bit for cleanup
5960
+ time.sleep(0.1)
5961
+ self.segmenter.train_batch(self.parent().channel_data[2], speed = speed, use_gpu = self.use_gpu)
5895
5962
  self.trained = True
5896
5963
 
5964
+ def start_segmentation(self):
5965
+
5966
+ self.kill_segmentation()
5967
+ print("Beginning new segmentation...")
5968
+
5969
+ if not self.trained:
5970
+ return
5971
+ else:
5972
+ self.segmentation_worker = SegmentationWorker(self.parent().highlight_overlay, self.segmenter, self.use_gpu)
5973
+ self.segmentation_worker.chunk_processed.connect(self.update_display) # Just update display
5974
+ self.segmentation_worker.finished.connect(self.segmentation_finished)
5975
+ current_xlim = self.parent().ax.get_xlim()
5976
+ current_ylim = self.parent().ax.get_ylim()
5977
+ self.segmenter.update_position(self.parent().current_slice, int((current_ylim[0] - current_ylim[1])/2), int((current_xlim[1] - current_xlim[0])/2))
5978
+ self.segmentation_worker.start()
5979
+
5980
+
5981
+
5982
+ def update_display(self):
5983
+ if not hasattr(self, '_last_update'):
5984
+ self._last_update = 0
5985
+
5986
+ current_time = time.time()
5987
+ if current_time - self._last_update >= 1: # Match worker's interval
5988
+ try:
5989
+ # Store current view state
5990
+ current_xlim = self.parent().ax.get_xlim()
5991
+ current_ylim = self.parent().ax.get_ylim()
5992
+
5993
+ self.segmenter.update_position(self.parent().current_slice, int((current_ylim[0] - current_ylim[1])/2), int((current_xlim[1] - current_xlim[0])/2))
5994
+ if not self.parent().painting:
5995
+ # Only update if view limits are valid
5996
+ self.parent().update_display(preserve_zoom=(current_xlim, current_ylim))
5997
+
5998
+
5999
+ self._last_update = current_time
6000
+ except Exception as e:
6001
+ print(f"Display update error: {e}")
6002
+
6003
+ def segmentation_finished(self):
6004
+ print("Segmentation completed")
6005
+ current_xlim = self.parent().ax.get_xlim()
6006
+ current_ylim = self.parent().ax.get_ylim()
6007
+ self.parent().update_display(preserve_zoom=(current_xlim, current_ylim))
6008
+ self.kill_segmentation()
6009
+
6010
+ def kill_segmentation(self):
6011
+ if hasattr(self, 'segmentation_worker'):
6012
+ self.segmentation_worker.stop()
6013
+ del self.segmentation_worker # Clean up reference
6014
+
6015
+
5897
6016
  def segment(self):
5898
6017
 
5899
6018
  if not self.trained:
5900
6019
  return
5901
6020
  else:
5902
- foreground_coords, background_coords = self.segmenter.segment_volume()
6021
+ print("Segmenting entire volume with model...")
6022
+ foreground_coords, background_coords = self.segmenter.segment_volume(gpu = self.use_gpu)
5903
6023
 
5904
6024
  # Clean up when done
5905
6025
  self.segmenter.cleanup()
5906
6026
 
5907
6027
  for z,y,x in foreground_coords:
5908
- self.parent().highlight_overlay[z,y,x] = True
6028
+ self.parent().highlight_overlay[z,y,x] = 255
6029
+
6030
+ self.parent().load_channel(3, self.parent().highlight_overlay, True)
6031
+
6032
+ # Not exactly sure why we need all this but the channel buttons weren't loading like they normally do when load_channel() is called:
6033
+ self.parent().channel_buttons[3].setEnabled(True)
6034
+ self.parent().channel_buttons[3].click()
6035
+ self.parent().delete_buttons[3].setEnabled(True)
6036
+
6037
+ self.parent().highlight_overlay = None
5909
6038
 
5910
6039
  self.parent().update_display()
5911
6040
 
6041
+ print("Finished segmentation moved to Overlay 2. Use File -> Save(As) for disk saving.")
6042
+
5912
6043
  def closeEvent(self, event):
5913
6044
  if self.brush_button.isChecked():
5914
6045
  self.silence_button()
5915
6046
  self.toggle_brush_mode()
5916
6047
  self.parent().brush_mode = False
5917
6048
  self.parent().machine_window = None
6049
+ self.kill_segmentation()
6050
+
5918
6051
 
5919
6052
 
6053
+ class SegmentationWorker(QThread):
6054
+ finished = pyqtSignal()
6055
+ chunk_processed = pyqtSignal()
6056
+
6057
+ def __init__(self, highlight_overlay, segmenter, use_gpu):
6058
+ super().__init__()
6059
+ self.overlay = highlight_overlay
6060
+ self.segmenter = segmenter
6061
+ self.use_gpu = use_gpu
6062
+ self._stop = False
6063
+ self.update_interval = 1 # Increased to 500ms
6064
+ self.chunks_since_update = 0
6065
+ self.chunks_per_update = 5 # Only update every 5 chunks
6066
+ self.last_update = time.time()
6067
+
6068
+ def stop(self):
6069
+ self._stop = True
6070
+
6071
+ def run(self):
6072
+ try:
6073
+ self.overlay.fill(False)
6074
+
6075
+ for foreground_coords, background_coords in self.segmenter.segment_volume_realtime(gpu = self.use_gpu):
6076
+ if self._stop:
6077
+ break
6078
+
6079
+ for z,y,x in foreground_coords:
6080
+ self.overlay[z,y,x] = 1
6081
+ for z,y,x in background_coords:
6082
+ self.overlay[z,y,x] = 2
6083
+
6084
+ # Update only after several chunks AND minimum time interval
6085
+ self.chunks_since_update += 1
6086
+ current_time = time.time()
6087
+ if (self.chunks_since_update >= self.chunks_per_update and
6088
+ current_time - self.last_update >= self.update_interval):
6089
+ self.chunk_processed.emit()
6090
+ self.chunks_since_update = 0
6091
+ self.last_update = current_time
6092
+
6093
+ self.finished.emit()
6094
+
6095
+ except Exception as e:
6096
+ print(f"Error in segmentation: {e}")
6097
+ raise
6098
+
6099
+ def run_batch(self):
6100
+ try:
6101
+ foreground_coords, _ = self.segmenter.segment_volume()
6102
+
6103
+ # Modify the array directly
6104
+ self.overlay.fill(False)
6105
+ for z,y,x in foreground_coords:
6106
+ self.overlay[z,y,x] = True
6107
+
6108
+ self.finished.emit()
6109
+
6110
+ except Exception as e:
6111
+ print(f"Error in segmentation: {e}")
6112
+ raise
6113
+
5920
6114
 
5921
6115
 
5922
6116