nettracer3d 0.5.4__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/nettracer_gui.py +248 -43
- nettracer3d/segmenter.py +576 -79
- {nettracer3d-0.5.4.dist-info → nettracer3d-0.5.5.dist-info}/METADATA +5 -5
- {nettracer3d-0.5.4.dist-info → nettracer3d-0.5.5.dist-info}/RECORD +8 -8
- {nettracer3d-0.5.4.dist-info → nettracer3d-0.5.5.dist-info}/WHEEL +1 -1
- {nettracer3d-0.5.4.dist-info → nettracer3d-0.5.5.dist-info}/LICENSE +0 -0
- {nettracer3d-0.5.4.dist-info → nettracer3d-0.5.5.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.5.4.dist-info → nettracer3d-0.5.5.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -398,6 +398,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
398
398
|
self.highlight_overlay = None
|
|
399
399
|
self.highlight_bounds = None # Store bounds for positioning
|
|
400
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
|
|
401
402
|
self.mini_thresh = (500*500*500) # Array volume to start using mini overlays for
|
|
402
403
|
|
|
403
404
|
def start_left_scroll(self):
|
|
@@ -446,6 +447,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
446
447
|
"""
|
|
447
448
|
|
|
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
|
|
449
451
|
|
|
450
452
|
def process_chunk(chunk_data, indices_to_check):
|
|
451
453
|
"""Process a single chunk of the array to create highlight mask"""
|
|
@@ -562,6 +564,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
562
564
|
|
|
563
565
|
def create_highlight_overlay_slice(self, indices, bounds = False):
|
|
564
566
|
|
|
567
|
+
"""Highlight overlay generation method specific for the segmenter interactive mode"""
|
|
568
|
+
|
|
565
569
|
|
|
566
570
|
def process_chunk_bounds(chunk_data, indices_to_check):
|
|
567
571
|
"""Process a single chunk of the array to create highlight mask"""
|
|
@@ -638,7 +642,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
638
642
|
def create_mini_overlay(self, node_indices = None, edge_indices = None):
|
|
639
643
|
|
|
640
644
|
"""
|
|
641
|
-
Create a
|
|
645
|
+
Create a highlight overlay one slice at a time.
|
|
642
646
|
|
|
643
647
|
Args:
|
|
644
648
|
node_indices (list): List of node indices to highlight
|
|
@@ -668,28 +672,29 @@ class ImageViewerWindow(QMainWindow):
|
|
|
668
672
|
current_xlim = self.ax.get_xlim() if hasattr(self, 'ax') and self.ax.get_xlim() != (0, 1) else None
|
|
669
673
|
current_ylim = self.ax.get_ylim() if hasattr(self, 'ax') and self.ax.get_ylim() != (0, 1) else None
|
|
670
674
|
|
|
671
|
-
if not node_indices and not edge_indices:
|
|
672
|
-
self.
|
|
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
|
|
673
678
|
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
674
679
|
return
|
|
675
680
|
|
|
676
|
-
# Get the shape of the
|
|
681
|
+
# Get the shape of the mini array from any existing channel
|
|
677
682
|
for channel in self.channel_data:
|
|
678
683
|
if channel is not None:
|
|
679
684
|
full_shape = channel.shape
|
|
685
|
+
full_shape = (full_shape[1], full_shape[2]) #Just get (Y, X) shape
|
|
680
686
|
break
|
|
681
687
|
else:
|
|
682
688
|
return # No valid channels to get shape from
|
|
683
689
|
|
|
684
690
|
# Initialize full-size overlay
|
|
685
|
-
|
|
686
|
-
self.highlight_overlay = np.zeros(full_shape, dtype=np.uint8)
|
|
691
|
+
self.mini_overlay_data = np.zeros(full_shape, dtype=np.uint8)
|
|
687
692
|
|
|
688
693
|
# Get number of CPU cores
|
|
689
694
|
num_cores = mp.cpu_count()
|
|
690
695
|
|
|
691
696
|
# Calculate chunk size along y-axis
|
|
692
|
-
chunk_size = full_shape[
|
|
697
|
+
chunk_size = full_shape[0] // num_cores
|
|
693
698
|
if chunk_size < 1:
|
|
694
699
|
chunk_size = 1
|
|
695
700
|
|
|
@@ -699,8 +704,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
699
704
|
|
|
700
705
|
# Create chunks
|
|
701
706
|
chunks = []
|
|
702
|
-
for i in range(0, array_shape[
|
|
703
|
-
end = min(i + chunk_size, array_shape[
|
|
707
|
+
for i in range(0, array_shape[0], chunk_size):
|
|
708
|
+
end = min(i + chunk_size, array_shape[0])
|
|
704
709
|
chunks.append(channel_data[i:end, :])
|
|
705
710
|
|
|
706
711
|
# Process chunks in parallel using ThreadPoolExecutor
|
|
@@ -729,11 +734,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
729
734
|
edge_overlay = None
|
|
730
735
|
|
|
731
736
|
# Combine results
|
|
732
|
-
self.highlight_overlay[self.current_slice, :, :] = np.zeros_like(self.highlight_overlay[self.current_slice, :, :])
|
|
733
737
|
if node_overlay is not None:
|
|
734
|
-
self.
|
|
738
|
+
self.mini_overlay_data = np.maximum(self.mini_overlay_data, node_overlay)
|
|
735
739
|
if edge_overlay is not None:
|
|
736
|
-
self.
|
|
740
|
+
self.mini_overlay_data = np.maximum(self.mini_overlay_data, edge_overlay)
|
|
737
741
|
|
|
738
742
|
|
|
739
743
|
# Update display
|
|
@@ -3488,6 +3492,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3488
3492
|
if self.mini_overlay == True: #If we are rendering the highlight overlay for selected values one at a time.
|
|
3489
3493
|
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
3490
3494
|
self.update_display(preserve_zoom=view_settings)
|
|
3495
|
+
if self.machine_window is not None:
|
|
3496
|
+
self.machine_window.poke_segmenter()
|
|
3491
3497
|
self.pending_slice = None
|
|
3492
3498
|
|
|
3493
3499
|
def update_brightness(self, channel_index, values):
|
|
@@ -3623,7 +3629,15 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3623
3629
|
self.create_highlight_overlay_slice(self.targs, bounds = self.bounds)
|
|
3624
3630
|
|
|
3625
3631
|
# Add highlight overlay if it exists
|
|
3626
|
-
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:
|
|
3627
3641
|
highlight_slice = self.highlight_overlay[self.current_slice]
|
|
3628
3642
|
highlight_cmap = LinearSegmentedColormap.from_list(
|
|
3629
3643
|
'highlight',
|
|
@@ -6202,7 +6216,18 @@ class MachineWindow(QMainWindow):
|
|
|
6202
6216
|
self.GPU.setChecked(False)
|
|
6203
6217
|
self.GPU.clicked.connect(self.toggle_GPU)
|
|
6204
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)
|
|
6205
6228
|
processing_layout.addWidget(self.GPU)
|
|
6229
|
+
processing_layout.addWidget(self.two)
|
|
6230
|
+
processing_layout.addWidget(self.three)
|
|
6206
6231
|
processing_group.setLayout(processing_layout)
|
|
6207
6232
|
|
|
6208
6233
|
# Group 3: Training Options
|
|
@@ -6220,6 +6245,7 @@ class MachineWindow(QMainWindow):
|
|
|
6220
6245
|
segmentation_group = QGroupBox("Segmentation")
|
|
6221
6246
|
segmentation_layout = QVBoxLayout()
|
|
6222
6247
|
seg_button = QPushButton("Preview Segment")
|
|
6248
|
+
self.seg_button = seg_button
|
|
6223
6249
|
seg_button.clicked.connect(self.start_segmentation)
|
|
6224
6250
|
full_button = QPushButton("Segment All")
|
|
6225
6251
|
full_button.clicked.connect(self.segment)
|
|
@@ -6237,9 +6263,12 @@ class MachineWindow(QMainWindow):
|
|
|
6237
6263
|
self.setCentralWidget(main_widget)
|
|
6238
6264
|
|
|
6239
6265
|
self.trained = False
|
|
6266
|
+
self.previewing = False
|
|
6240
6267
|
|
|
6241
6268
|
|
|
6242
6269
|
self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=True)
|
|
6270
|
+
self.segmentation_worker = None
|
|
6271
|
+
|
|
6243
6272
|
|
|
6244
6273
|
|
|
6245
6274
|
def toggle_GPU(self):
|
|
@@ -6247,6 +6276,27 @@ class MachineWindow(QMainWindow):
|
|
|
6247
6276
|
|
|
6248
6277
|
self.use_gpu = self.GPU.isChecked()
|
|
6249
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
|
+
|
|
6250
6300
|
|
|
6251
6301
|
def toggle_foreground(self):
|
|
6252
6302
|
|
|
@@ -6295,15 +6345,29 @@ class MachineWindow(QMainWindow):
|
|
|
6295
6345
|
self.kill_segmentation()
|
|
6296
6346
|
# Wait a bit for cleanup
|
|
6297
6347
|
time.sleep(0.1)
|
|
6298
|
-
|
|
6299
|
-
|
|
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
|
+
|
|
6300
6360
|
|
|
6301
6361
|
def start_segmentation(self):
|
|
6302
6362
|
|
|
6303
6363
|
self.kill_segmentation()
|
|
6304
6364
|
time.sleep(0.1)
|
|
6305
6365
|
|
|
6306
|
-
|
|
6366
|
+
if self.use_two:
|
|
6367
|
+
self.previewing = True
|
|
6368
|
+
else:
|
|
6369
|
+
print("Beginning new segmentation...")
|
|
6370
|
+
|
|
6307
6371
|
|
|
6308
6372
|
if self.parent().active_channel == 0:
|
|
6309
6373
|
if self.parent().channel_data[0] is not None:
|
|
@@ -6317,7 +6381,7 @@ class MachineWindow(QMainWindow):
|
|
|
6317
6381
|
if not self.trained:
|
|
6318
6382
|
return
|
|
6319
6383
|
else:
|
|
6320
|
-
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)
|
|
6321
6385
|
self.segmentation_worker.chunk_processed.connect(self.update_display) # Just update display
|
|
6322
6386
|
self.segmentation_worker.finished.connect(self.segmentation_finished)
|
|
6323
6387
|
current_xlim = self.parent().ax.get_xlim()
|
|
@@ -6345,10 +6409,42 @@ class MachineWindow(QMainWindow):
|
|
|
6345
6409
|
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
6346
6410
|
return msg.exec() == QMessageBox.StandardButton.Yes
|
|
6347
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
|
+
|
|
6348
6435
|
def update_display(self):
|
|
6349
6436
|
if not hasattr(self, '_last_update'):
|
|
6350
6437
|
self._last_update = 0
|
|
6351
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
|
+
|
|
6352
6448
|
current_time = time.time()
|
|
6353
6449
|
if current_time - self._last_update >= 1: # Match worker's interval
|
|
6354
6450
|
try:
|
|
@@ -6366,19 +6462,84 @@ class MachineWindow(QMainWindow):
|
|
|
6366
6462
|
except Exception as e:
|
|
6367
6463
|
print(f"Display update error: {e}")
|
|
6368
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
|
+
|
|
6369
6484
|
def segmentation_finished(self):
|
|
6370
|
-
|
|
6485
|
+
if not self.use_two:
|
|
6486
|
+
print("Segmentation completed")
|
|
6487
|
+
|
|
6371
6488
|
current_xlim = self.parent().ax.get_xlim()
|
|
6372
6489
|
current_ylim = self.parent().ax.get_ylim()
|
|
6373
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
|
|
6374
6496
|
self.kill_segmentation()
|
|
6497
|
+
self.segmentation_worker = None
|
|
6375
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
|
+
|
|
6376
6524
|
|
|
6377
6525
|
|
|
6378
6526
|
def kill_segmentation(self):
|
|
6379
|
-
if hasattr(self, 'segmentation_worker'):
|
|
6527
|
+
if hasattr(self, 'segmentation_worker') and self.segmentation_worker is not None:
|
|
6528
|
+
# Signal the thread to stop
|
|
6380
6529
|
self.segmentation_worker.stop()
|
|
6381
|
-
|
|
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
|
|
6382
6543
|
|
|
6383
6544
|
|
|
6384
6545
|
def segment(self):
|
|
@@ -6391,6 +6552,8 @@ class MachineWindow(QMainWindow):
|
|
|
6391
6552
|
self.kill_segmentation()
|
|
6392
6553
|
time.sleep(0.1)
|
|
6393
6554
|
|
|
6555
|
+
self.previewing = False
|
|
6556
|
+
|
|
6394
6557
|
if self.parent().active_channel == 0:
|
|
6395
6558
|
if self.parent().channel_data[0] is not None:
|
|
6396
6559
|
active_data = self.parent().channel_data[0]
|
|
@@ -6420,20 +6583,26 @@ class MachineWindow(QMainWindow):
|
|
|
6420
6583
|
|
|
6421
6584
|
self.parent().update_display()
|
|
6422
6585
|
|
|
6586
|
+
self.previewing = False
|
|
6587
|
+
|
|
6423
6588
|
print("Finished segmentation moved to Overlay 2. Use File -> Save(As) for disk saving.")
|
|
6424
6589
|
|
|
6425
6590
|
def closeEvent(self, event):
|
|
6426
6591
|
if self.parent().isVisible():
|
|
6427
6592
|
if self.confirm_close_dialog():
|
|
6428
|
-
|
|
6593
|
+
# Clean up resources before closing
|
|
6429
6594
|
if self.brush_button.isChecked():
|
|
6430
6595
|
self.silence_button()
|
|
6431
6596
|
self.toggle_brush_mode()
|
|
6597
|
+
|
|
6432
6598
|
self.parent().pen_button.setEnabled(True)
|
|
6433
6599
|
self.parent().brush_mode = False
|
|
6434
|
-
|
|
6600
|
+
|
|
6601
|
+
# Kill the segmentation thread and wait for it to finish
|
|
6435
6602
|
self.kill_segmentation()
|
|
6436
|
-
time.sleep(0.
|
|
6603
|
+
time.sleep(0.2) # Give additional time for cleanup
|
|
6604
|
+
|
|
6605
|
+
self.parent().machine_window = None
|
|
6437
6606
|
else:
|
|
6438
6607
|
event.ignore()
|
|
6439
6608
|
|
|
@@ -6444,48 +6613,84 @@ class SegmentationWorker(QThread):
|
|
|
6444
6613
|
finished = pyqtSignal()
|
|
6445
6614
|
chunk_processed = pyqtSignal()
|
|
6446
6615
|
|
|
6447
|
-
def __init__(self, highlight_overlay, segmenter, use_gpu):
|
|
6616
|
+
def __init__(self, highlight_overlay, segmenter, use_gpu, use_two, previewing, machine_window):
|
|
6448
6617
|
super().__init__()
|
|
6449
6618
|
self.overlay = highlight_overlay
|
|
6450
6619
|
self.segmenter = segmenter
|
|
6451
6620
|
self.use_gpu = use_gpu
|
|
6621
|
+
self.use_two = use_two
|
|
6622
|
+
self.previewing = previewing
|
|
6623
|
+
self.machine_window = machine_window
|
|
6452
6624
|
self._stop = False
|
|
6453
6625
|
self.update_interval = 1 # Increased to 500ms
|
|
6454
6626
|
self.chunks_since_update = 0
|
|
6455
6627
|
self.chunks_per_update = 5 # Only update every 5 chunks
|
|
6628
|
+
self.poked = False # If it should wake up or not
|
|
6456
6629
|
self.last_update = time.time()
|
|
6457
6630
|
|
|
6458
6631
|
def stop(self):
|
|
6459
6632
|
self._stop = True
|
|
6633
|
+
|
|
6634
|
+
def get_poked(self):
|
|
6635
|
+
self.poked = True
|
|
6460
6636
|
|
|
6461
6637
|
def run(self):
|
|
6462
6638
|
try:
|
|
6463
6639
|
self.overlay.fill(False)
|
|
6464
6640
|
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
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
|
|
6468
6652
|
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
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
|
+
|
|
6483
6687
|
self.finished.emit()
|
|
6484
6688
|
|
|
6485
6689
|
except Exception as e:
|
|
6486
6690
|
print(f"Error in segmentation: {e}")
|
|
6487
|
-
|
|
6488
|
-
|
|
6691
|
+
import traceback
|
|
6692
|
+
traceback.print_exc()
|
|
6693
|
+
|
|
6489
6694
|
def run_batch(self):
|
|
6490
6695
|
try:
|
|
6491
6696
|
foreground_coords, _ = self.segmenter.segment_volume()
|