coralnet-toolbox 0.0.74__py2.py3-none-any.whl → 0.0.76__py2.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.
Files changed (49) hide show
  1. coralnet_toolbox/Annotations/QtPolygonAnnotation.py +57 -12
  2. coralnet_toolbox/Annotations/QtRectangleAnnotation.py +44 -14
  3. coralnet_toolbox/Explorer/QtDataItem.py +52 -22
  4. coralnet_toolbox/Explorer/QtExplorer.py +277 -1600
  5. coralnet_toolbox/Explorer/QtSettingsWidgets.py +101 -15
  6. coralnet_toolbox/Explorer/QtViewers.py +1568 -0
  7. coralnet_toolbox/Explorer/transformer_models.py +70 -0
  8. coralnet_toolbox/Explorer/yolo_models.py +112 -0
  9. coralnet_toolbox/IO/QtExportMaskAnnotations.py +538 -403
  10. coralnet_toolbox/Icons/system_monitor.png +0 -0
  11. coralnet_toolbox/MachineLearning/ImportDataset/QtBase.py +239 -147
  12. coralnet_toolbox/MachineLearning/VideoInference/YOLO3D/run.py +102 -16
  13. coralnet_toolbox/QtAnnotationWindow.py +16 -10
  14. coralnet_toolbox/QtEventFilter.py +4 -4
  15. coralnet_toolbox/QtImageWindow.py +3 -7
  16. coralnet_toolbox/QtMainWindow.py +104 -64
  17. coralnet_toolbox/QtProgressBar.py +1 -0
  18. coralnet_toolbox/QtSystemMonitor.py +370 -0
  19. coralnet_toolbox/Rasters/RasterTableModel.py +20 -0
  20. coralnet_toolbox/Results/ConvertResults.py +14 -8
  21. coralnet_toolbox/Results/ResultsProcessor.py +3 -2
  22. coralnet_toolbox/SAM/QtDeployGenerator.py +2 -5
  23. coralnet_toolbox/SAM/QtDeployPredictor.py +11 -3
  24. coralnet_toolbox/SeeAnything/QtDeployGenerator.py +146 -116
  25. coralnet_toolbox/SeeAnything/QtDeployPredictor.py +55 -9
  26. coralnet_toolbox/Tile/QtTileBatchInference.py +4 -4
  27. coralnet_toolbox/Tools/QtPolygonTool.py +42 -3
  28. coralnet_toolbox/Tools/QtRectangleTool.py +30 -0
  29. coralnet_toolbox/Tools/QtSAMTool.py +140 -91
  30. coralnet_toolbox/Transformers/Models/GroundingDINO.py +72 -0
  31. coralnet_toolbox/Transformers/Models/OWLViT.py +72 -0
  32. coralnet_toolbox/Transformers/Models/OmDetTurbo.py +68 -0
  33. coralnet_toolbox/Transformers/Models/QtBase.py +120 -0
  34. coralnet_toolbox/{AutoDistill → Transformers}/Models/__init__.py +1 -1
  35. coralnet_toolbox/{AutoDistill → Transformers}/QtBatchInference.py +15 -15
  36. coralnet_toolbox/{AutoDistill → Transformers}/QtDeployModel.py +18 -16
  37. coralnet_toolbox/{AutoDistill → Transformers}/__init__.py +1 -1
  38. coralnet_toolbox/__init__.py +1 -1
  39. coralnet_toolbox/utilities.py +21 -15
  40. {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.76.dist-info}/METADATA +13 -10
  41. {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.76.dist-info}/RECORD +45 -40
  42. coralnet_toolbox/AutoDistill/Models/GroundingDINO.py +0 -81
  43. coralnet_toolbox/AutoDistill/Models/OWLViT.py +0 -76
  44. coralnet_toolbox/AutoDistill/Models/OmDetTurbo.py +0 -75
  45. coralnet_toolbox/AutoDistill/Models/QtBase.py +0 -112
  46. {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.76.dist-info}/WHEEL +0 -0
  47. {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.76.dist-info}/entry_points.txt +0 -0
  48. {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.76.dist-info}/licenses/LICENSE.txt +0 -0
  49. {coralnet_toolbox-0.0.74.dist-info → coralnet_toolbox-0.0.76.dist-info}/top_level.txt +0 -0
@@ -189,6 +189,21 @@ Examples:
189
189
  help='Filter by specific class IDs (e.g., --classes 0 1 2 for persons, bicycles, cars)'
190
190
  )
191
191
 
192
+ # Frame range parameters
193
+ parser.add_argument(
194
+ '--start_at',
195
+ type=int,
196
+ default=0,
197
+ help='Start processing at this frame number (0-based)'
198
+ )
199
+ parser.add_argument(
200
+ '--end_at',
201
+ type=int,
202
+ default=None,
203
+ help='End processing at this frame number (inclusive, 0-based). If not provided, '
204
+ 'process until the end of the video.'
205
+ )
206
+
192
207
  # Device settings
193
208
  parser.add_argument(
194
209
  '--device',
@@ -222,6 +237,8 @@ Examples:
222
237
  action='store_true',
223
238
  help='Show all visualization windows (result, depth, detection). By default, only the result frame is shown.'
224
239
  )
240
+
241
+
225
242
  return parser.parse_args()
226
243
 
227
244
 
@@ -271,19 +288,25 @@ def main():
271
288
  enable_bev = not args.no_bev
272
289
  enable_display = not args.no_display
273
290
  show_all_frames = args.yes_display
291
+
292
+ # Frame range parameters
293
+ start_frame = args.start_at
294
+ end_frame = args.end_at
295
+
274
296
  # Camera parameters - simplified approach
275
297
  camera_params_file = None # Path to camera parameters file (None to use default parameters)
276
298
  # ===============================================
277
- print(f"\nConfiguration:")
299
+ print("\nConfiguration:")
278
300
  print(f"Input source: {source}")
279
301
  print(f"Output path: {output_path}")
280
302
  if target_size is not None:
281
303
  print(f"Target size: {target_size}px (longest edge)")
282
304
  else:
283
- print(f"Using original resolution (no scaling)")
305
+ print("Using original resolution (no scaling)")
284
306
  print(f"YOLO model: {'Custom path: ' + yolo_model_path if yolo_model_path else 'Size: ' + yolo_model_size}")
285
307
  print(f"Depth model size: {depth_model_size}")
286
308
  print(f"Device: {device}")
309
+ print(f"Frame range: {start_frame} to {end_frame if end_frame is not None else 'end'}")
287
310
  print(f"Tracking: {'enabled' if enable_tracking else 'disabled'}")
288
311
  print(f"Bird's Eye View: {'enabled' if enable_bev else 'disabled'}")
289
312
  print(f"Display: {'enabled' if enable_display else 'disabled'}")
@@ -382,9 +405,52 @@ def main():
382
405
  # Use a scale that works well for the 1-5 meter range
383
406
  bev = BirdEyeView(image_shape=(width, height), scale=100) # Increased scale to spread objects out
384
407
 
385
- # Initialize video writer
386
- fourcc = cv2.VideoWriter_fourcc(*'mp4v')
387
- out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
408
+ # Initialize video writer - use a more reliable codec and check output path
409
+ output_dir = os.path.dirname(output_path)
410
+ if output_dir and not os.path.exists(output_dir):
411
+ os.makedirs(output_dir)
412
+
413
+ # On Windows, try different codec options
414
+ if sys.platform == 'win32':
415
+ try:
416
+ # First try H264 codec
417
+ fourcc = cv2.VideoWriter_fourcc(*'H264')
418
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
419
+
420
+ # Check if writer was successfully initialized
421
+ if not out.isOpened():
422
+ # Fallback to XVID codec
423
+ fourcc = cv2.VideoWriter_fourcc(*'XVID')
424
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
425
+
426
+ if not out.isOpened():
427
+ # Last resort, try MJPG
428
+ fourcc = cv2.VideoWriter_fourcc(*'MJPG')
429
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
430
+
431
+ if not out.isOpened():
432
+ print(f"Warning: Could not create output video with standard codecs. Trying AVI format.")
433
+ # Try changing extension to .avi
434
+ output_path = os.path.splitext(output_path)[0] + '.avi'
435
+ fourcc = cv2.VideoWriter_fourcc(*'XVID')
436
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
437
+ except Exception as e:
438
+ print(f"Error initializing video writer: {e}")
439
+ # Last fallback to MP4V
440
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
441
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
442
+ else:
443
+ # For other platforms, use mp4v codec
444
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
445
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
446
+
447
+ # Check if writer was successfully initialized
448
+ if not out.isOpened():
449
+ print(f"Error: Could not create output video file at {output_path}")
450
+ print("Continuing without saving output video.")
451
+ out = None
452
+ else:
453
+ print(f"Successfully opened output video file: {output_path}")
388
454
 
389
455
  # Initialize variables for FPS calculation
390
456
  frame_count = 0
@@ -394,6 +460,7 @@ def main():
394
460
  print("Starting processing...")
395
461
 
396
462
  # Main loop
463
+ current_frame = 0
397
464
  while True:
398
465
  # Check for key press at the beginning of each loop
399
466
  if enable_display:
@@ -401,11 +468,22 @@ def main():
401
468
  if key == ord('q') or key == 27 or (key & 0xFF) == ord('q') or (key & 0xFF) == 27:
402
469
  print("Exiting program...")
403
470
  break
404
- try: # Read frame
471
+ try:
472
+ # Read frame
405
473
  ret, frame = cap.read()
406
474
  if not ret:
407
475
  break
408
476
 
477
+ # Skip frames before start_frame
478
+ if current_frame < start_frame:
479
+ current_frame += 1
480
+ continue
481
+
482
+ # Stop if we've processed the end_frame
483
+ if end_frame is not None and current_frame > end_frame:
484
+ print(f"Reached end frame {end_frame}, stopping processing.")
485
+ break
486
+
409
487
  # Apply resizing if needed
410
488
  if target_size is not None:
411
489
  frame = cv2.resize(frame, (width, height))
@@ -543,7 +621,7 @@ def main():
543
621
  fps_display = f"FPS: {fps_value:.1f}"
544
622
 
545
623
  # Add FPS and device info to the result frame (top-right corner)
546
- text = f"{fps_display} | Device: {device}"
624
+ text = f"{fps_display} | Device: {device} | Frame: {current_frame}"
547
625
 
548
626
  # Calculate text size for right alignment
549
627
  (text_width, text_height), _ = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2)
@@ -584,8 +662,9 @@ def main():
584
662
  except Exception as e:
585
663
  print(f"Error adding depth map to result: {e}")
586
664
 
587
- # Write frame to output video
588
- out.write(result_frame)
665
+ # Write frame to output video (only if writer is valid)
666
+ if out is not None and out.isOpened():
667
+ out.write(result_frame)
589
668
 
590
669
  # Display frames only if display is enabled
591
670
  if enable_display:
@@ -594,12 +673,15 @@ def main():
594
673
  cv2.imshow("Depth Map", depth_colored)
595
674
  cv2.imshow("Object Detection", detection_frame)
596
675
 
597
- # Check for key press again at the end of the loop
598
- key = cv2.waitKey(1)
599
- if key == ord('q') or key == 27 or (key & 0xFF) == ord('q') or (key & 0xFF) == 27:
600
- print("Exiting program...")
601
- break
602
-
676
+ # Check for key press again at the end of the loop
677
+ key = cv2.waitKey(1)
678
+ if key == ord('q') or key == 27 or (key & 0xFF) == ord('q') or (key & 0xFF) == 27:
679
+ print("Exiting program...")
680
+ break
681
+
682
+ # Increment frame counter
683
+ current_frame += 1
684
+
603
685
  except Exception as e:
604
686
  print(f"Error processing frame: {e}")
605
687
  # Also check for key press during exception handling
@@ -608,12 +690,16 @@ def main():
608
690
  if key == ord('q') or key == 27 or (key & 0xFF) == ord('q') or (key & 0xFF) == 27:
609
691
  print("Exiting program...")
610
692
  break
693
+
694
+ # Still increment frame counter even if there was an error
695
+ current_frame += 1
611
696
  continue
612
697
 
613
698
  # Clean up
614
699
  print("Cleaning up resources...")
615
700
  cap.release()
616
- out.release()
701
+ if out is not None:
702
+ out.release()
617
703
  cv2.destroyAllWindows()
618
704
 
619
705
  print(f"Processing complete. Output saved to {output_path}")
@@ -492,9 +492,6 @@ class AnnotationWindow(QGraphicsView):
492
492
  relative_area = 1.0 # fallback, treat as full image
493
493
 
494
494
  # Step 3: Map ratio to padding factor (smaller annotation = more padding)
495
- # Example: padding_factor = clamp(0.5 * (1/relative_area)**0.5, 0.1, 0.5)
496
- # - For very small annotations, padding approaches 0.5 (50%)
497
- # - For large annotations, padding approaches 0.1 (10%)
498
495
  import math
499
496
  min_padding = 0.1 # 10%
500
497
  max_padding = 0.5 # 50%
@@ -503,23 +500,32 @@ class AnnotationWindow(QGraphicsView):
503
500
  else:
504
501
  padding_factor = min_padding
505
502
 
506
- # Step 4: Apply dynamic padding
507
- padding_x = annotation_rect.width() * padding_factor
508
- padding_y = annotation_rect.height() * padding_factor
503
+ # Step 4: Apply dynamic padding with minimum values to prevent zero width/height
504
+ min_padding_absolute = 1.0 # Minimum padding in pixels
505
+ padding_x = max(annotation_rect.width() * padding_factor, min_padding_absolute)
506
+ padding_y = max(annotation_rect.height() * padding_factor, min_padding_absolute)
509
507
  padded_rect = annotation_rect.adjusted(-padding_x, -padding_y, padding_x, padding_y)
510
508
 
511
509
  # Fit the padded annotation rect in the view
512
510
  self.fitInView(padded_rect, Qt.KeepAspectRatio)
513
511
 
514
- # Update the zoom factor based on the new view transformation
512
+ # Update the zoom factor based on the new view transformation with safety checks
515
513
  view_rect = self.viewport().rect()
516
- zoom_x = view_rect.width() / padded_rect.width()
517
- zoom_y = view_rect.height() / padded_rect.height()
514
+ if padded_rect.width() > 0:
515
+ zoom_x = view_rect.width() / padded_rect.width()
516
+ else:
517
+ zoom_x = 1.0 # Default zoom if width is zero
518
+
519
+ if padded_rect.height() > 0:
520
+ zoom_y = view_rect.height() / padded_rect.height()
521
+ else:
522
+ zoom_y = 1.0 # Default zoom if height is zero
523
+
518
524
  self.zoom_factor = min(zoom_x, zoom_y)
519
525
 
520
526
  # Signal that the view has changed
521
527
  self.viewChanged.emit(*self.get_image_dimensions())
522
-
528
+
523
529
  def cycle_annotations(self, direction):
524
530
  """Cycle through annotations in the specified direction."""
525
531
  # Get the annotations for the current image
@@ -24,7 +24,7 @@ class GlobalEventFilter(QObject):
24
24
  self.segment_deploy_model_dialog = main_window.segment_deploy_model_dialog
25
25
  self.sam_deploy_generator_dialog = main_window.sam_deploy_generator_dialog
26
26
  self.see_anything_deploy_generator_dialog = main_window.see_anything_deploy_generator_dialog
27
- self.auto_distill_deploy_model_dialog = main_window.auto_distill_deploy_model_dialog
27
+ self.transformers_deploy_model_dialog = main_window.transformers_deploy_model_dialog
28
28
 
29
29
  def eventFilter(self, obj, event):
30
30
  # Check for explorer window first - this applies to all event types
@@ -75,10 +75,10 @@ class GlobalEventFilter(QObject):
75
75
  if event.key() == Qt.Key_5:
76
76
  self.see_anything_deploy_generator_dialog.predict()
77
77
  return True
78
-
79
- # Handle hotkey for auto distill prediction
78
+
79
+ # Handle hotkey for transformers prediction
80
80
  if event.key() == Qt.Key_6:
81
- self.auto_distill_deploy_model_dialog.predict()
81
+ self.transformers_deploy_model_dialog.predict()
82
82
  return True
83
83
 
84
84
  # Handle annotation cycling hotkeys
@@ -578,13 +578,9 @@ class ImageWindow(QWidget):
578
578
 
579
579
  # Immediately update filtered paths to include the new image
580
580
  # This ensures the image will be visible in the table right away
581
- if self.table_model.filtered_paths == self.raster_manager.image_paths[:-1]:
582
- # No filters are active, so just add the new path
583
- self.table_model.filtered_paths.append(image_path)
584
- self.table_model.dataChanged.emit(
585
- self.table_model.index(0, 0),
586
- self.table_model.index(len(self.table_model.filtered_paths) - 1,
587
- self.table_model.columnCount() - 1))
581
+ if len(self.table_model.filtered_paths) == len(self.raster_manager.image_paths) - 1:
582
+ # No filters are active, so efficiently add the new path to the model
583
+ self.table_model.add_path(image_path)
588
584
  else:
589
585
  # Filters are active, so run filtering again
590
586
  self.filter_images()
@@ -9,6 +9,9 @@ import requests
9
9
 
10
10
  from packaging import version
11
11
 
12
+ import torch
13
+
14
+ from PyQt5 import sip
12
15
  from PyQt5.QtGui import QIcon, QMouseEvent
13
16
  from PyQt5.QtCore import Qt, pyqtSignal, QEvent, QSize, QPoint
14
17
  from PyQt5.QtWidgets import (QListWidget, QCheckBox, QFrame, QComboBox)
@@ -18,6 +21,7 @@ from PyQt5.QtWidgets import (QMainWindow, QApplication, QToolBar, QAction, QSize
18
21
  QGroupBox)
19
22
 
20
23
  from coralnet_toolbox.QtEventFilter import GlobalEventFilter
24
+
21
25
  from coralnet_toolbox.QtAnnotationWindow import AnnotationWindow
22
26
  from coralnet_toolbox.QtConfidenceWindow import ConfidenceWindow
23
27
  from coralnet_toolbox.QtImageWindow import ImageWindow
@@ -99,9 +103,9 @@ from coralnet_toolbox.SeeAnything import (
99
103
  BatchInferenceDialog as SeeAnythingBatchInferenceDialog
100
104
  )
101
105
 
102
- from coralnet_toolbox.AutoDistill import (
103
- DeployModelDialog as AutoDistillDeployModelDialog,
104
- BatchInferenceDialog as AutoDistillBatchInferenceDialog
106
+ from coralnet_toolbox.Transformers import (
107
+ DeployModelDialog as TransformersDeployModelDialog,
108
+ BatchInferenceDialog as TransformersBatchInferenceDialog
105
109
  )
106
110
 
107
111
  from coralnet_toolbox.CoralNet import (
@@ -114,9 +118,9 @@ from coralnet_toolbox.BreakTime import (
114
118
  BreakoutGame
115
119
  )
116
120
 
117
- from coralnet_toolbox.Icons import get_icon
121
+ from coralnet_toolbox.QtSystemMonitor import SystemMonitor
118
122
 
119
- from coralnet_toolbox.utilities import get_available_device
123
+ from coralnet_toolbox.Icons import get_icon
120
124
 
121
125
 
122
126
  # ----------------------------------------------------------------------------------------------------------------------
@@ -155,6 +159,7 @@ class MainWindow(QMainWindow):
155
159
  self.opaque_icon = get_icon("opaque.png")
156
160
  self.all_icon = get_icon("all.png")
157
161
  self.parameters_icon = get_icon("parameters.png")
162
+ self.system_monitor_icon = get_icon("system_monitor.png")
158
163
  self.add_icon = get_icon("add.png")
159
164
  self.remove_icon = get_icon("remove.png")
160
165
  self.edit_icon = get_icon("edit.png")
@@ -193,6 +198,8 @@ class MainWindow(QMainWindow):
193
198
  self.label_window = LabelWindow(self)
194
199
  self.confidence_window = ConfidenceWindow(self)
195
200
 
201
+ self.system_monitor = None
202
+
196
203
  self.explorer_window = None # Initialized in open_explorer_window
197
204
 
198
205
  # TODO update IO classes to have dialogs
@@ -262,9 +269,9 @@ class MainWindow(QMainWindow):
262
269
  self.see_anything_deploy_generator_dialog = SeeAnythingDeployGeneratorDialog(self)
263
270
  self.see_anything_batch_inference_dialog = SeeAnythingBatchInferenceDialog(self)
264
271
 
265
- # Create dialogs (AutoDistill)
266
- self.auto_distill_deploy_model_dialog = AutoDistillDeployModelDialog(self)
267
- self.auto_distill_batch_inference_dialog = AutoDistillBatchInferenceDialog(self)
272
+ # Create dialogs (Transformers)
273
+ self.transformers_deploy_model_dialog = TransformersDeployModelDialog(self)
274
+ self.transformers_batch_inference_dialog = TransformersBatchInferenceDialog(self)
268
275
 
269
276
  # Create dialogs (Tile)
270
277
  self.classify_tile_dataset_dialog = ClassifyTileDatasetDialog(self)
@@ -601,6 +608,17 @@ class MainWindow(QMainWindow):
601
608
  self.ml_segment_video_inference_action = QAction("Segment", self)
602
609
  self.ml_segment_video_inference_action.triggered.connect(self.open_segment_video_inference_dialog)
603
610
  self.ml_video_inference_menu.addAction(self.ml_segment_video_inference_action)
611
+
612
+ # Transformers menu
613
+ self.transformers_menu = self.menu_bar.addMenu("Transformers")
614
+ # Deploy Model
615
+ self.transformers_deploy_model_action = QAction("Deploy Model", self)
616
+ self.transformers_deploy_model_action.triggered.connect(self.open_transformers_deploy_model_dialog)
617
+ self.transformers_menu.addAction(self.transformers_deploy_model_action)
618
+ # Batch Inference
619
+ self.transformers_batch_inference_action = QAction("Batch Inference", self)
620
+ self.transformers_batch_inference_action.triggered.connect(self.open_transformers_batch_inference_dialog)
621
+ self.transformers_menu.addAction(self.transformers_batch_inference_action)
604
622
 
605
623
  # SAM menu
606
624
  self.sam_menu = self.menu_bar.addMenu("SAM")
@@ -640,17 +658,6 @@ class MainWindow(QMainWindow):
640
658
  self.see_anything_batch_inference_action.triggered.connect(self.open_see_anything_batch_inference_dialog)
641
659
  self.see_anything_menu.addAction(self.see_anything_batch_inference_action)
642
660
 
643
- # Auto Distill menu
644
- self.auto_distill_menu = self.menu_bar.addMenu("AutoDistill")
645
- # Deploy Model
646
- self.auto_distill_deploy_model_action = QAction("Deploy Model", self)
647
- self.auto_distill_deploy_model_action.triggered.connect(self.open_auto_distill_deploy_model_dialog)
648
- self.auto_distill_menu.addAction(self.auto_distill_deploy_model_action)
649
- # Batch Inference
650
- self.auto_distill_batch_inference_action = QAction("Batch Inference", self)
651
- self.auto_distill_batch_inference_action.triggered.connect(self.open_auto_distill_batch_inference_dialog)
652
- self.auto_distill_menu.addAction(self.auto_distill_batch_inference_action)
653
-
654
661
  # Help menu
655
662
  self.help_menu = self.menu_bar.addMenu("Help")
656
663
 
@@ -659,9 +666,13 @@ class MainWindow(QMainWindow):
659
666
  self.check_for_updates_action.triggered.connect(self.open_check_for_updates_dialog)
660
667
  self.help_menu.addAction(self.check_for_updates_action)
661
668
  # Usage
662
- self.usage_action = QAction("Usage", self)
669
+ self.usage_action = QAction("Usage / Hotkeys", self)
663
670
  self.usage_action.triggered.connect(self.open_usage_dialog)
664
671
  self.help_menu.addAction(self.usage_action)
672
+ # System Monitor
673
+ self.system_monitor_action = QAction("System Monitor", self)
674
+ self.system_monitor_action.triggered.connect(self.open_system_monitor_dialog)
675
+ self.help_menu.addAction(self.system_monitor_action)
665
676
  # Issues / Feature Requests
666
677
  self.create_issue_action = QAction("Issues / Feature Requests", self)
667
678
  self.create_issue_action.triggered.connect(self.open_create_new_issue_dialog)
@@ -828,15 +839,15 @@ class MainWindow(QMainWindow):
828
839
  self.toolbar.addWidget(spacer)
829
840
 
830
841
  # Add the device label widget as an action in the toolbar
831
- self.devices = get_available_device()
832
- self.current_device_index = 0
833
- self.device = self.devices[self.current_device_index]
842
+ self.devices = self.get_available_devices()
843
+ # Get the 'best' device available
844
+ self.device = self.devices[-1]
834
845
 
835
846
  if self.device.startswith('cuda'):
836
- if len(self.devices) == 1:
837
- device_icon = self.rabbit_icon
838
- else:
847
+ if len([d for d in self.devices if d.endswith('cuda')]) > 1:
839
848
  device_icon = self.rocket_icon
849
+ else:
850
+ device_icon = self.rabbit_icon
840
851
  device_tooltip = self.device
841
852
  elif self.device == 'mps':
842
853
  device_icon = self.apple_icon
@@ -1028,9 +1039,9 @@ class MainWindow(QMainWindow):
1028
1039
  self.left_layout.addWidget(self.annotation_window, 85)
1029
1040
  self.left_layout.addWidget(self.label_window, 15)
1030
1041
 
1031
- # Add widgets to right layout
1032
- self.right_layout.addWidget(self.image_window, 54)
1033
- self.right_layout.addWidget(self.confidence_window, 46)
1042
+ # Adjust the right layout with new proportions
1043
+ self.right_layout.addWidget(self.image_window, 54) # 54% for image window
1044
+ self.right_layout.addWidget(self.confidence_window, 46) # 46% for confidence window
1034
1045
 
1035
1046
  # Add left and right layouts to main layout
1036
1047
  self.main_layout.addLayout(self.left_layout, 85)
@@ -1054,12 +1065,17 @@ class MainWindow(QMainWindow):
1054
1065
  self.showMaximized()
1055
1066
 
1056
1067
  def closeEvent(self, event):
1057
- """Ensure the explorer window is closed when the main window closes."""
1068
+ """Ensure the explorer window and system monitor are closed when the main window closes."""
1058
1069
  if self.explorer_window:
1059
1070
  # Setting parent to None prevents it from being deleted with main window
1060
1071
  # before it can be properly handled.
1061
1072
  self.explorer_window.setParent(None)
1062
1073
  self.explorer_window.close()
1074
+
1075
+ # Close the system monitor if it exists
1076
+ if self.system_monitor:
1077
+ self.system_monitor.close()
1078
+
1063
1079
  super().closeEvent(event)
1064
1080
 
1065
1081
  def changeEvent(self, event):
@@ -1374,6 +1390,20 @@ class MainWindow(QMainWindow):
1374
1390
  self.sam_tool_action.setChecked(False)
1375
1391
  self.see_anything_tool_action.setChecked(False)
1376
1392
  self.work_area_tool_action.setChecked(False)
1393
+
1394
+ def get_available_devices(self):
1395
+ """
1396
+ Get available devices
1397
+
1398
+ :return:
1399
+ """
1400
+ devices = ['cpu',]
1401
+ if torch.backends.mps.is_available():
1402
+ devices.append('mps')
1403
+ if torch.cuda.is_available():
1404
+ for i in range(torch.cuda.device_count()):
1405
+ devices.append(f'cuda:{i}')
1406
+ return devices
1377
1407
 
1378
1408
  def toggle_device(self):
1379
1409
  dialog = DeviceSelectionDialog(self.devices, self)
@@ -2211,6 +2241,40 @@ class MainWindow(QMainWindow):
2211
2241
  self.segment_video_inference_dialog.exec_()
2212
2242
  except Exception as e:
2213
2243
  QMessageBox.critical(self, "Critical Error", f"{e}")
2244
+
2245
+ def open_transformers_deploy_model_dialog(self):
2246
+ """Open the Transformers Deploy Model dialog to deploy an Transformers model."""
2247
+ if not self.image_window.raster_manager.image_paths:
2248
+ QMessageBox.warning(self,
2249
+ "Transformers Deploy Model",
2250
+ "No images are present in the project.")
2251
+ return
2252
+
2253
+ try:
2254
+ self.untoggle_all_tools()
2255
+ self.transformers_deploy_model_dialog.exec_()
2256
+ except Exception as e:
2257
+ QMessageBox.critical(self, "Critical Error", f"{e}")
2258
+
2259
+ def open_transformers_batch_inference_dialog(self):
2260
+ """Open the Transformers Batch Inference dialog to run batch inference with Transformers."""
2261
+ if not self.image_window.raster_manager.image_paths:
2262
+ QMessageBox.warning(self,
2263
+ "Transformers Batch Inference",
2264
+ "No images are present in the project.")
2265
+ return
2266
+
2267
+ if not self.transformers_deploy_model_dialog.loaded_model:
2268
+ QMessageBox.warning(self,
2269
+ "Transformers Batch Inference",
2270
+ "Please deploy a model before running batch inference.")
2271
+ return
2272
+
2273
+ try:
2274
+ self.untoggle_all_tools()
2275
+ self.transformers_batch_inference_dialog.exec_()
2276
+ except Exception as e:
2277
+ QMessageBox.critical(self, "Critical Error", f"{e}")
2214
2278
 
2215
2279
  def open_sam_deploy_predictor_dialog(self):
2216
2280
  """Open the SAM Deploy Predictor dialog to deploy a SAM predictor."""
@@ -2328,40 +2392,16 @@ class MainWindow(QMainWindow):
2328
2392
  self.see_anything_batch_inference_dialog.exec_()
2329
2393
  except Exception as e:
2330
2394
  QMessageBox.critical(self, "Critical Error", f"{e}")
2331
-
2332
- def open_auto_distill_deploy_model_dialog(self):
2333
- """Open the AutoDistill Deploy Model dialog to deploy an AutoDistill model."""
2334
- if not self.image_window.raster_manager.image_paths:
2335
- QMessageBox.warning(self,
2336
- "AutoDistill Deploy Model",
2337
- "No images are present in the project.")
2338
- return
2339
-
2340
- try:
2341
- self.untoggle_all_tools()
2342
- self.auto_distill_deploy_model_dialog.exec_()
2343
- except Exception as e:
2344
- QMessageBox.critical(self, "Critical Error", f"{e}")
2345
-
2346
- def open_auto_distill_batch_inference_dialog(self):
2347
- """Open the AutoDistill Batch Inference dialog to run batch inference with AutoDistill."""
2348
- if not self.image_window.raster_manager.image_paths:
2349
- QMessageBox.warning(self,
2350
- "AutoDistill Batch Inference",
2351
- "No images are present in the project.")
2352
- return
2353
-
2354
- if not self.auto_distill_deploy_model_dialog.loaded_model:
2355
- QMessageBox.warning(self,
2356
- "AutoDistill Batch Inference",
2357
- "Please deploy a model before running batch inference.")
2358
- return
2359
-
2360
- try:
2361
- self.untoggle_all_tools()
2362
- self.auto_distill_batch_inference_dialog.exec_()
2363
- except Exception as e:
2364
- QMessageBox.critical(self, "Critical Error", f"{e}")
2395
+
2396
+ def open_system_monitor_dialog(self):
2397
+ """Open the system system monitor window."""
2398
+ if self.system_monitor is None or sip.isdeleted(self.system_monitor):
2399
+ self.system_monitor = SystemMonitor()
2400
+
2401
+ # Show the monitor window
2402
+ self.system_monitor.show()
2403
+ self.system_monitor.activateWindow()
2404
+ self.system_monitor.raise_()
2365
2405
 
2366
2406
  def open_usage_dialog(self):
2367
2407
  """Display QMessageBox with link to create new issue on GitHub."""
@@ -39,6 +39,7 @@ class ProgressBar(QDialog):
39
39
 
40
40
  # Create cancel button
41
41
  self.cancel_button = QPushButton("Cancel", self)
42
+ self.cancel_button.setEnabled(False)
42
43
  self.cancel_button.clicked.connect(self.cancel)
43
44
 
44
45
  # Setup layout