coralnet-toolbox 0.0.66__py2.py3-none-any.whl → 0.0.67__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 (31) hide show
  1. coralnet_toolbox/Annotations/QtMultiPolygonAnnotation.py +1 -1
  2. coralnet_toolbox/Annotations/QtPatchAnnotation.py +1 -1
  3. coralnet_toolbox/Annotations/QtPolygonAnnotation.py +1 -1
  4. coralnet_toolbox/Annotations/QtRectangleAnnotation.py +1 -1
  5. coralnet_toolbox/AutoDistill/QtDeployModel.py +4 -0
  6. coralnet_toolbox/Explorer/QtAnnotationDataItem.py +97 -0
  7. coralnet_toolbox/Explorer/QtAnnotationImageWidget.py +183 -0
  8. coralnet_toolbox/Explorer/QtEmbeddingPointItem.py +30 -0
  9. coralnet_toolbox/Explorer/QtExplorer.py +2067 -0
  10. coralnet_toolbox/Explorer/QtSettingsWidgets.py +490 -0
  11. coralnet_toolbox/Explorer/__init__.py +7 -0
  12. coralnet_toolbox/IO/QtImportViscoreAnnotations.py +2 -4
  13. coralnet_toolbox/IO/QtOpenProject.py +2 -1
  14. coralnet_toolbox/Icons/magic.png +0 -0
  15. coralnet_toolbox/MachineLearning/DeployModel/QtDetect.py +4 -0
  16. coralnet_toolbox/MachineLearning/DeployModel/QtSegment.py +4 -0
  17. coralnet_toolbox/MachineLearning/TrainModel/QtClassify.py +1 -1
  18. coralnet_toolbox/QtConfidenceWindow.py +2 -23
  19. coralnet_toolbox/QtEventFilter.py +2 -2
  20. coralnet_toolbox/QtLabelWindow.py +23 -8
  21. coralnet_toolbox/QtMainWindow.py +81 -2
  22. coralnet_toolbox/QtProgressBar.py +12 -0
  23. coralnet_toolbox/SAM/QtDeployGenerator.py +4 -0
  24. coralnet_toolbox/__init__.py +1 -1
  25. coralnet_toolbox/utilities.py +24 -0
  26. {coralnet_toolbox-0.0.66.dist-info → coralnet_toolbox-0.0.67.dist-info}/METADATA +2 -1
  27. {coralnet_toolbox-0.0.66.dist-info → coralnet_toolbox-0.0.67.dist-info}/RECORD +31 -24
  28. {coralnet_toolbox-0.0.66.dist-info → coralnet_toolbox-0.0.67.dist-info}/WHEEL +0 -0
  29. {coralnet_toolbox-0.0.66.dist-info → coralnet_toolbox-0.0.67.dist-info}/entry_points.txt +0 -0
  30. {coralnet_toolbox-0.0.66.dist-info → coralnet_toolbox-0.0.67.dist-info}/licenses/LICENSE.txt +0 -0
  31. {coralnet_toolbox-0.0.66.dist-info → coralnet_toolbox-0.0.67.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,490 @@
1
+ import os
2
+ import warnings
3
+
4
+ from PyQt5.QtCore import Qt
5
+ from PyQt5.QtWidgets import (QVBoxLayout, QHBoxLayout, QPushButton, QComboBox, QLabel,
6
+ QWidget, QGroupBox, QSlider, QListWidget, QTabWidget,
7
+ QLineEdit, QFileDialog, QFormLayout)
8
+
9
+ from coralnet_toolbox.MachineLearning.Community.cfg import get_available_configs
10
+
11
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
12
+
13
+
14
+ # ----------------------------------------------------------------------------------------------------------------------
15
+ # Widgets
16
+ # ----------------------------------------------------------------------------------------------------------------------
17
+
18
+
19
+ class AnnotationSettingsWidget(QGroupBox):
20
+ """Widget for filtering annotations by image, type, and label in a multi-column layout."""
21
+
22
+ def __init__(self, main_window, parent=None):
23
+ super(AnnotationSettingsWidget, self).__init__("Annotation Settings", parent)
24
+ self.main_window = main_window
25
+ self.explorer_window = parent # Store reference to ExplorerWindow
26
+ self.setup_ui()
27
+
28
+ def setup_ui(self):
29
+ # The main layout is vertical, to hold the top columns, the stretch, and the bottom buttons
30
+ layout = QVBoxLayout(self)
31
+
32
+ # A horizontal layout to contain the filter columns
33
+ conditions_layout = QHBoxLayout()
34
+
35
+ # Images column
36
+ images_column = QVBoxLayout()
37
+ images_label = QLabel("Images:")
38
+ images_label.setStyleSheet("font-weight: bold;")
39
+ images_column.addWidget(images_label)
40
+
41
+ self.images_list = QListWidget()
42
+ self.images_list.setSelectionMode(QListWidget.MultiSelection)
43
+ self.images_list.setMaximumHeight(50)
44
+
45
+ if hasattr(self.main_window, 'image_window') and hasattr(self.main_window.image_window, 'raster_manager'):
46
+ for path in self.main_window.image_window.raster_manager.image_paths:
47
+ self.images_list.addItem(os.path.basename(path))
48
+
49
+ images_column.addWidget(self.images_list)
50
+
51
+ images_buttons_layout = QHBoxLayout()
52
+ self.images_select_all_btn = QPushButton("Select All")
53
+ self.images_select_all_btn.clicked.connect(self.select_all_images)
54
+ images_buttons_layout.addWidget(self.images_select_all_btn)
55
+
56
+ self.images_deselect_all_btn = QPushButton("Deselect All")
57
+ self.images_deselect_all_btn.clicked.connect(self.deselect_all_images)
58
+ images_buttons_layout.addWidget(self.images_deselect_all_btn)
59
+ images_column.addLayout(images_buttons_layout)
60
+
61
+ conditions_layout.addLayout(images_column)
62
+
63
+ # Annotation Type column
64
+ type_column = QVBoxLayout()
65
+ type_label = QLabel("Annotation Type:")
66
+ type_label.setStyleSheet("font-weight: bold;")
67
+ type_column.addWidget(type_label)
68
+
69
+ self.annotation_type_list = QListWidget()
70
+ self.annotation_type_list.setSelectionMode(QListWidget.MultiSelection)
71
+ self.annotation_type_list.setMaximumHeight(50)
72
+ self.annotation_type_list.addItems(["PatchAnnotation",
73
+ "RectangleAnnotation",
74
+ "PolygonAnnotation",
75
+ "MultiPolygonAnnotation"])
76
+
77
+ type_column.addWidget(self.annotation_type_list)
78
+
79
+ type_buttons_layout = QHBoxLayout()
80
+ self.type_select_all_btn = QPushButton("Select All")
81
+ self.type_select_all_btn.clicked.connect(self.select_all_annotation_types)
82
+ type_buttons_layout.addWidget(self.type_select_all_btn)
83
+
84
+ self.type_deselect_all_btn = QPushButton("Deselect All")
85
+ self.type_deselect_all_btn.clicked.connect(self.deselect_all_annotation_types)
86
+ type_buttons_layout.addWidget(self.type_deselect_all_btn)
87
+ type_column.addLayout(type_buttons_layout)
88
+
89
+ conditions_layout.addLayout(type_column)
90
+
91
+ # Label column
92
+ label_column = QVBoxLayout()
93
+ label_label = QLabel("Label:")
94
+ label_label.setStyleSheet("font-weight: bold;")
95
+ label_column.addWidget(label_label)
96
+
97
+ self.label_list = QListWidget()
98
+ self.label_list.setSelectionMode(QListWidget.MultiSelection)
99
+ self.label_list.setMaximumHeight(50)
100
+
101
+ if hasattr(self.main_window, 'label_window') and hasattr(self.main_window.label_window, 'labels'):
102
+ for label in self.main_window.label_window.labels:
103
+ self.label_list.addItem(label.short_label_code)
104
+
105
+ label_column.addWidget(self.label_list)
106
+
107
+ label_buttons_layout = QHBoxLayout()
108
+ self.label_select_all_btn = QPushButton("Select All")
109
+ self.label_select_all_btn.clicked.connect(self.select_all_labels)
110
+ label_buttons_layout.addWidget(self.label_select_all_btn)
111
+
112
+ self.label_deselect_all_btn = QPushButton("Deselect All")
113
+ self.label_deselect_all_btn.clicked.connect(self.deselect_all_labels)
114
+ label_buttons_layout.addWidget(self.label_deselect_all_btn)
115
+ label_column.addLayout(label_buttons_layout)
116
+
117
+ conditions_layout.addLayout(label_column)
118
+
119
+ # Add the horizontal layout of columns to the main vertical layout
120
+ layout.addLayout(conditions_layout)
121
+
122
+ # Add a stretch item to push the columns to the top
123
+ layout.addStretch(1)
124
+
125
+ # Bottom buttons layout with Apply and Clear buttons on the right
126
+ bottom_layout = QHBoxLayout()
127
+ bottom_layout.addStretch() # Push buttons to the right
128
+
129
+ self.apply_button = QPushButton("Apply")
130
+ self.apply_button.clicked.connect(self.apply_conditions)
131
+ bottom_layout.addWidget(self.apply_button)
132
+
133
+ self.clear_button = QPushButton("Clear")
134
+ self.clear_button.clicked.connect(self.clear_all_conditions)
135
+ bottom_layout.addWidget(self.clear_button)
136
+
137
+ # Add the bottom buttons layout to the main layout, keeping it at the bottom
138
+ layout.addLayout(bottom_layout)
139
+
140
+ # Set defaults
141
+ self.set_defaults()
142
+
143
+ def select_all_images(self):
144
+ """Select all items in the images list."""
145
+ self.images_list.selectAll()
146
+
147
+ def deselect_all_images(self):
148
+ """Deselect all items in the images list."""
149
+ self.images_list.clearSelection()
150
+
151
+ def select_all_annotation_types(self):
152
+ """Select all items in the annotation types list."""
153
+ self.annotation_type_list.selectAll()
154
+
155
+ def deselect_all_annotation_types(self):
156
+ """Deselect all items in the annotation types list."""
157
+ self.annotation_type_list.clearSelection()
158
+
159
+ def select_all_labels(self):
160
+ """Select all items in the labels list."""
161
+ self.label_list.selectAll()
162
+
163
+ def deselect_all_labels(self):
164
+ """Deselect all items in the labels list."""
165
+ self.label_list.clearSelection()
166
+
167
+ def set_defaults(self):
168
+ """Set default selections."""
169
+ self.set_default_to_current_image()
170
+ self.select_all_annotation_types()
171
+ self.select_all_labels()
172
+
173
+ def set_default_to_current_image(self):
174
+ """Set the current image as the default selection."""
175
+ if hasattr(self.main_window, 'annotation_window'):
176
+ current_image_path = self.main_window.annotation_window.current_image_path
177
+ if current_image_path:
178
+ current_image_name = os.path.basename(current_image_path)
179
+ items = self.images_list.findItems(current_image_name, Qt.MatchExactly)
180
+ if items:
181
+ items[0].setSelected(True)
182
+ return
183
+ self.select_all_images()
184
+
185
+ def clear_all_conditions(self):
186
+ """Reset all conditions to their defaults."""
187
+ self.images_list.clearSelection()
188
+ self.annotation_type_list.clearSelection()
189
+ self.label_list.clearSelection()
190
+ self.set_defaults()
191
+ if self.explorer_window and hasattr(self.explorer_window, 'refresh_filters'):
192
+ self.explorer_window.refresh_filters()
193
+
194
+ def apply_conditions(self):
195
+ """Apply the current filter conditions."""
196
+ if self.explorer_window and hasattr(self.explorer_window, 'refresh_filters'):
197
+ self.explorer_window.refresh_filters()
198
+
199
+ def get_selected_images(self):
200
+ """Get selected image names."""
201
+ selected_items = self.images_list.selectedItems()
202
+ if not selected_items:
203
+ return []
204
+ return [item.text() for item in selected_items]
205
+
206
+ def get_selected_annotation_types(self):
207
+ """Get selected annotation types."""
208
+ selected_items = self.annotation_type_list.selectedItems()
209
+ if not selected_items:
210
+ return []
211
+ return [item.text() for item in selected_items]
212
+
213
+ def get_selected_labels(self):
214
+ """Get selected labels."""
215
+ selected_items = self.label_list.selectedItems()
216
+ if not selected_items:
217
+ return []
218
+ return [item.text() for item in selected_items]
219
+
220
+
221
+ class ModelSettingsWidget(QGroupBox):
222
+ """Widget containing model selection with tabs for different model sources."""
223
+
224
+ def __init__(self, main_window, parent=None):
225
+ super(ModelSettingsWidget, self).__init__("Model Settings", parent)
226
+ self.main_window = main_window
227
+ self.explorer_window = parent
228
+ self.setup_ui()
229
+
230
+ def setup_ui(self):
231
+ """Set up the UI with a tabbed interface for model selection."""
232
+ main_layout = QVBoxLayout(self)
233
+
234
+ # --- Tabbed Interface for Model Selection ---
235
+ self.tabs = QTabWidget()
236
+
237
+ # Tab 1: Select Model
238
+ model_select_tab = QWidget()
239
+ model_select_layout = QFormLayout(model_select_tab)
240
+ model_select_layout.setContentsMargins(5, 10, 5, 5) # Add some top margin
241
+
242
+ self.model_combo = QComboBox()
243
+ self.model_combo.addItems(["Color Features"])
244
+ self.model_combo.insertSeparator(1) # Add a separator
245
+
246
+ standard_models = ['yolov8n-cls.pt',
247
+ 'yolov8s-cls.pt',
248
+ 'yolov8m-cls.pt',
249
+ 'yolov8l-cls.pt',
250
+ 'yolov8x-cls.pt',
251
+ 'yolo11n-cls.pt',
252
+ 'yolo11s-cls.pt',
253
+ 'yolo11m-cls.pt',
254
+ 'yolo11l-cls.pt',
255
+ 'yolo11x-cls.pt',
256
+ 'yolo12n-cls.pt',
257
+ 'yolo12s-cls.pt',
258
+ 'yolo12m-cls.pt',
259
+ 'yolo12l-cls.pt',
260
+ 'yolo12x-cls.pt']
261
+
262
+ self.model_combo.addItems(standard_models)
263
+
264
+ community_configs = get_available_configs(task='classify')
265
+ if community_configs:
266
+ self.model_combo.insertSeparator(len(standard_models) + 2)
267
+ self.model_combo.addItems(list(community_configs.keys()))
268
+
269
+ self.model_combo.setCurrentText('Color Features')
270
+ # Connect selection change to update feature mode field state
271
+ self.model_combo.currentTextChanged.connect(self._update_feature_mode_state)
272
+
273
+ model_select_layout.addRow("Model:", self.model_combo)
274
+
275
+ self.tabs.addTab(model_select_tab, "Select Model")
276
+
277
+ # Tab 2: Existing Model from File
278
+ model_existing_tab = QWidget()
279
+ model_existing_layout = QFormLayout(model_existing_tab)
280
+ model_existing_layout.setContentsMargins(5, 10, 5, 5)
281
+
282
+ self.model_path_edit = QLineEdit()
283
+ self.model_path_edit.setPlaceholderText("Path to a existing .pt model file...")
284
+ browse_button = QPushButton("Browse...")
285
+ browse_button.clicked.connect(self.browse_for_model)
286
+
287
+ path_layout = QHBoxLayout()
288
+ path_layout.addWidget(self.model_path_edit)
289
+ path_layout.addWidget(browse_button)
290
+ model_existing_layout.addRow("Model Path:", path_layout)
291
+
292
+ self.tabs.addTab(model_existing_tab, "Use Existing Model")
293
+
294
+ main_layout.addWidget(self.tabs)
295
+
296
+ # Connect tab change to update feature mode state
297
+ self.tabs.currentChanged.connect(self._update_feature_mode_state)
298
+
299
+ # Add feature extraction mode selection outside of tabs
300
+ feature_mode_layout = QFormLayout()
301
+ self.feature_mode_combo = QComboBox()
302
+ self.feature_mode_combo.addItems(["Predictions", "Embed Features"])
303
+ feature_mode_layout.addRow("Feature Mode:", self.feature_mode_combo)
304
+ main_layout.addLayout(feature_mode_layout)
305
+
306
+ # Initialize the feature mode state based on current selection
307
+ self._update_feature_mode_state()
308
+
309
+ def browse_for_model(self):
310
+ """Open a file dialog to browse for model files."""
311
+ options = QFileDialog.Options()
312
+ file_path, _ = QFileDialog.getOpenFileName(
313
+ self,
314
+ "Select Model File",
315
+ "",
316
+ "PyTorch Models (*.pt);;All Files (*)",
317
+ options=options
318
+ )
319
+ if file_path:
320
+ self.model_path_edit.setText(file_path)
321
+
322
+ def _update_feature_mode_state(self, *args):
323
+ """Update the enabled state of the feature mode field based on the current model selection."""
324
+ current_tab_index = self.tabs.currentIndex()
325
+ is_color_features = False
326
+
327
+ if current_tab_index == 0:
328
+ # Select Model tab - check if Color Features is selected
329
+ current_model = self.model_combo.currentText()
330
+ is_color_features = current_model == "Color Features"
331
+ elif current_tab_index == 1:
332
+ # Use Existing Model tab - feature mode should always be enabled
333
+ is_color_features = False
334
+
335
+ # Enable feature mode only if not Color Features
336
+ self.feature_mode_combo.setEnabled(not is_color_features)
337
+
338
+ # Update the tooltip based on state
339
+ if is_color_features:
340
+ self.feature_mode_combo.setToolTip("Feature Mode is not available for Color Features")
341
+ else:
342
+ self.feature_mode_combo.setToolTip("Select the feature extraction mode")
343
+
344
+ def get_selected_model(self):
345
+ """Get the currently selected model name/path and feature mode."""
346
+ current_tab_index = self.tabs.currentIndex()
347
+
348
+ # Get model name/path and feature mode based on the active tab
349
+ if current_tab_index == 0:
350
+ model_name = self.model_combo.currentText()
351
+ elif current_tab_index == 1:
352
+ model_name = self.model_path_edit.text()
353
+ else:
354
+ return "", None
355
+
356
+ feature_mode = self.feature_mode_combo.currentText() if self.feature_mode_combo.isEnabled() else "N/A"
357
+ return model_name, feature_mode
358
+
359
+
360
+ class EmbeddingSettingsWidget(QGroupBox):
361
+ """Widget containing settings with tabs for models and embedding."""
362
+
363
+ def __init__(self, main_window, parent=None):
364
+ super(EmbeddingSettingsWidget, self).__init__("Embedding Settings", parent)
365
+ self.main_window = main_window
366
+ self.explorer_window = parent
367
+
368
+ self.setup_ui()
369
+
370
+ # Initial call to set the sliders correctly for the default technique
371
+ self._update_parameter_sliders()
372
+
373
+ def setup_ui(self):
374
+ """Set up the UI with embedding technique parameters."""
375
+ main_layout = QVBoxLayout(self)
376
+ main_layout.setContentsMargins(5, 10, 5, 5)
377
+
378
+ # Form layout for embedding settings
379
+ settings_layout = QFormLayout()
380
+
381
+ self.embedding_technique_combo = QComboBox()
382
+ self.embedding_technique_combo.addItems(["PCA", "TSNE", "UMAP"])
383
+ self.embedding_technique_combo.currentTextChanged.connect(self._update_parameter_sliders)
384
+ settings_layout.addRow("Technique:", self.embedding_technique_combo)
385
+
386
+ # Slider 1
387
+ self.param1_label = QLabel("Parameter 1:")
388
+ param1_layout = QHBoxLayout()
389
+ self.param1_slider = QSlider(Qt.Horizontal)
390
+ self.param1_value_label = QLabel("0")
391
+ self.param1_value_label.setMinimumWidth(25)
392
+ param1_layout.addWidget(self.param1_slider)
393
+ param1_layout.addWidget(self.param1_value_label)
394
+ settings_layout.addRow(self.param1_label, param1_layout)
395
+ self.param1_slider.valueChanged.connect(lambda v: self.param1_value_label.setText(str(v)))
396
+
397
+ # Slider 2
398
+ self.param2_label = QLabel("Parameter 2:")
399
+ param2_layout = QHBoxLayout()
400
+ self.param2_slider = QSlider(Qt.Horizontal)
401
+ self.param2_value_label = QLabel("0.0")
402
+ self.param2_value_label.setMinimumWidth(35) # Increased width for larger numbers
403
+ param2_layout.addWidget(self.param2_slider)
404
+ param2_layout.addWidget(self.param2_value_label)
405
+ settings_layout.addRow(self.param2_label, param2_layout)
406
+
407
+ self.apply_embedding_button = QPushButton("Apply Embedding")
408
+ self.apply_embedding_button.clicked.connect(self.apply_embedding)
409
+ settings_layout.addRow("", self.apply_embedding_button)
410
+
411
+ main_layout.addLayout(settings_layout)
412
+
413
+ def _update_parameter_sliders(self):
414
+ """Enable, disable, and configure sliders based on the selected technique."""
415
+ technique = self.embedding_technique_combo.currentText()
416
+
417
+ # Disconnect any existing connections to prevent conflicts
418
+ try:
419
+ self.param2_slider.valueChanged.disconnect()
420
+ except TypeError:
421
+ pass # No connection existed
422
+
423
+ if technique == "UMAP":
424
+ # Enable Row 1 for n_neighbors
425
+ self.param1_label.setEnabled(True)
426
+ self.param1_slider.setEnabled(True)
427
+ self.param1_value_label.setEnabled(True)
428
+ self.param1_label.setText("n_neighbors:")
429
+ self.param1_slider.setRange(2, 150)
430
+ self.param1_slider.setValue(15)
431
+
432
+ # Enable Row 2 for min_dist
433
+ self.param2_label.setEnabled(True)
434
+ self.param2_slider.setEnabled(True)
435
+ self.param2_value_label.setEnabled(True)
436
+ self.param2_label.setText("min_dist:")
437
+ self.param2_slider.setRange(0, 99)
438
+ self.param2_slider.setValue(10)
439
+ self.param2_slider.valueChanged.connect(lambda v: self.param2_value_label.setText(f"{v/100.0:.2f}"))
440
+
441
+ elif technique == "TSNE":
442
+ # Enable Row 1 for Perplexity
443
+ self.param1_label.setEnabled(True)
444
+ self.param1_slider.setEnabled(True)
445
+ self.param1_value_label.setEnabled(True)
446
+ self.param1_label.setText("Perplexity:")
447
+ self.param1_slider.setRange(5, 50)
448
+ self.param1_slider.setValue(30)
449
+
450
+ # --- MODIFIED: Enable Row 2 for Early Exaggeration ---
451
+ self.param2_label.setEnabled(True)
452
+ self.param2_slider.setEnabled(True)
453
+ self.param2_value_label.setEnabled(True)
454
+ self.param2_label.setText("Exaggeration:")
455
+ self.param2_slider.setRange(50, 600) # Represents 5.0 to 60.0
456
+ self.param2_slider.setValue(120) # Represents 12.0
457
+ self.param2_slider.valueChanged.connect(lambda v: self.param2_value_label.setText(f"{v/10.0:.1f}"))
458
+
459
+ elif technique == "PCA":
460
+ # Disable both rows for PCA
461
+ self.param1_label.setEnabled(False)
462
+ self.param1_slider.setEnabled(False)
463
+ self.param1_value_label.setEnabled(False)
464
+ self.param1_label.setText(" ")
465
+
466
+ self.param2_label.setEnabled(False)
467
+ self.param2_slider.setEnabled(False)
468
+ self.param2_value_label.setEnabled(False)
469
+ self.param2_label.setText(" ")
470
+
471
+ def get_embedding_parameters(self):
472
+ """Returns a dictionary of the current embedding parameters."""
473
+ params = {
474
+ 'technique': self.embedding_technique_combo.currentText(),
475
+ }
476
+ if params['technique'] == 'UMAP':
477
+ params['n_neighbors'] = self.param1_slider.value()
478
+ params['min_dist'] = self.param2_slider.value() / 100.0
479
+ elif params['technique'] == 'TSNE':
480
+ params['perplexity'] = self.param1_slider.value()
481
+ params['early_exaggeration'] = self.param2_slider.value() / 10.0
482
+ return params
483
+
484
+ def apply_embedding(self):
485
+ if self.explorer_window and hasattr(self.explorer_window, 'run_embedding_pipeline'):
486
+ # Clear all selections before running embedding pipeline
487
+ if hasattr(self.explorer_window, 'handle_selection_change'):
488
+ self.explorer_window.handle_selection_change([])
489
+
490
+ self.explorer_window.run_embedding_pipeline()
@@ -0,0 +1,7 @@
1
+ # coralnet_toolbox/Explorer/__init__.py
2
+
3
+ from .QtExplorer import ExplorerWindow
4
+
5
+ __all__ = [
6
+ 'ExplorerWindow',
7
+ ]
@@ -262,10 +262,8 @@ class ImportViscoreAnnotations(QDialog):
262
262
  # Get the label information
263
263
  short_code = str(label_code)
264
264
  # Create the label if it does not exist
265
- label = self.label_window.add_label_if_not_exists(short_code,
266
- long_code=None,
267
- color=None,
268
- label_id=None)
265
+ label = self.label_window.add_label_if_not_exists(short_code)
266
+
269
267
  progress_bar.update_progress()
270
268
 
271
269
  # Import annotations
@@ -365,8 +365,9 @@ class OpenProject(QDialog):
365
365
  # Update the image window's image annotations
366
366
  self.image_window.update_image_annotations(image_path)
367
367
 
368
- # Load the annotations for current image
368
+ # Load the annotations for current image and update counts
369
369
  self.annotation_window.load_annotations()
370
+ self.label_window.update_annotation_count()
370
371
 
371
372
  except Exception as e:
372
373
  QMessageBox.warning(self.annotation_window,
Binary file
@@ -284,6 +284,10 @@ class Detect(Base):
284
284
  self.update_sam_task_state()
285
285
  if self.task != 'segment':
286
286
  return results_list
287
+
288
+ if not self.sam_dialog or self.use_sam_dropdown.currentText() == "False":
289
+ # If SAM is not deployed or not selected, return the results as is
290
+ return results_list
287
291
 
288
292
  if self.sam_dialog.loaded_model is None:
289
293
  # If SAM is not loaded, ensure we do not use it accidentally
@@ -283,6 +283,10 @@ class Segment(Base):
283
283
  """Apply SAM to the results if needed."""
284
284
  # Check if SAM model is deployed and loaded
285
285
  self.update_sam_task_state()
286
+
287
+ if not self.sam_dialog or self.use_sam_dropdown.currentText() == "False":
288
+ # If SAM is not deployed or not selected, return the results as is
289
+ return results_list
286
290
 
287
291
  if self.sam_dialog.loaded_model is None:
288
292
  # If SAM is not loaded, ensure we do not use it accidentally
@@ -66,7 +66,7 @@ class Classify(Base):
66
66
  'yolo11s-cls.pt',
67
67
  'yolo11m-cls.pt',
68
68
  'yolo11l-cls.pt',
69
- 'yolo11x-cls.pt'
69
+ 'yolo11x-cls.pt',
70
70
  'yolo12n-cls.pt',
71
71
  'yolo12s-cls.pt',
72
72
  'yolo12m-cls.pt',
@@ -6,6 +6,7 @@ from PyQt5.QtWidgets import (QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout
6
6
  QLabel, QHBoxLayout, QFrame, QGroupBox, QPushButton)
7
7
 
8
8
  from coralnet_toolbox.Icons import get_icon
9
+ from coralnet_toolbox.utilities import scale_pixmap
9
10
 
10
11
  warnings.filterwarnings("ignore", category=DeprecationWarning)
11
12
 
@@ -253,28 +254,6 @@ class ConfidenceWindow(QWidget):
253
254
  if self.annotation and updated_annotation.id == self.annotation.id:
254
255
  self.refresh_display()
255
256
 
256
- def scale_pixmap(self, pixmap):
257
- """Scale pixmap and graphic if they exceed max dimension while preserving aspect ratio"""
258
- width = pixmap.width()
259
- height = pixmap.height()
260
-
261
- # Check if scaling is needed
262
- if width <= self.max_graphic_size and height <= self.max_graphic_size:
263
- return pixmap
264
-
265
- # Calculate scale factor based on largest dimension
266
- scale = self.max_graphic_size / max(width, height)
267
-
268
- # Scale pixmap
269
- scaled_pixmap = pixmap.scaled(
270
- int(width * scale),
271
- int(height * scale),
272
- Qt.KeepAspectRatio,
273
- Qt.SmoothTransformation
274
- )
275
-
276
- return scaled_pixmap
277
-
278
257
  def display_cropped_image(self, annotation):
279
258
  """Display the cropped image and update the bar chart."""
280
259
  try:
@@ -282,7 +261,7 @@ class ConfidenceWindow(QWidget):
282
261
  self.update_annotation(annotation)
283
262
  if self.annotation.cropped_image:
284
263
  # Get the cropped image graphic
285
- cropped_image_graphic = self.scale_pixmap(annotation.get_cropped_image_graphic())
264
+ cropped_image_graphic = scale_pixmap(annotation.get_cropped_image_graphic(), self.max_graphic_size)
286
265
  # Add the scaled annotation graphic (as pixmap)
287
266
  self.scene.addPixmap(cropped_image_graphic)
288
267
  # Add the border color with increased width
@@ -30,7 +30,7 @@ class GlobalEventFilter(QObject):
30
30
  if event.modifiers() & Qt.ControlModifier and not (event.modifiers() & Qt.ShiftModifier):
31
31
 
32
32
  # Handle Tab key for switching between Select and Annotation tools
33
- if event.key() == Qt.Key_Tab:
33
+ if event.key() == Qt.Key_Alt:
34
34
  self.main_window.switch_back_to_tool()
35
35
  return True
36
36
 
@@ -125,4 +125,4 @@ class GlobalEventFilter(QObject):
125
125
  'Are you sure you want to exit?',
126
126
  QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
127
127
  if reply == QMessageBox.Yes:
128
- QApplication.quit()
128
+ QApplication.quit()
@@ -394,12 +394,27 @@ class LabelWindow(QWidget):
394
394
  def update_annotation_count(self):
395
395
  """Update the annotation count display with current selection and total count."""
396
396
  annotations = self.annotation_window.get_image_annotations()
397
- selected_count = len(self.annotation_window.selected_annotations)
398
-
399
- if selected_count == 0:
397
+
398
+ # Check if we're in Explorer mode and get Explorer selections
399
+ explorer_selected_count = 0
400
+ if (hasattr(self.main_window, 'explorer_window') and
401
+ self.main_window.explorer_window and
402
+ hasattr(self.main_window.explorer_window, 'annotation_viewer')):
403
+ explorer_selected_count = len(self.main_window.explorer_window.annotation_viewer.selected_widgets)
404
+
405
+ # Get annotation window selections
406
+ annotation_window_selected_count = len(self.annotation_window.selected_annotations)
407
+
408
+ # Prioritize Explorer selections if Explorer is open
409
+ if explorer_selected_count > 0:
410
+ if explorer_selected_count == 1:
411
+ text = f"Annotation: 1"
412
+ else:
413
+ text = f"Annotations: {explorer_selected_count}"
414
+ elif annotation_window_selected_count == 0:
400
415
  text = f"Annotations: {len(annotations)}"
401
- elif selected_count > 1:
402
- text = f"Annotations: {selected_count}"
416
+ elif annotation_window_selected_count > 1:
417
+ text = f"Annotations: {annotation_window_selected_count}"
403
418
  else:
404
419
  try:
405
420
  selected_annotation = self.annotation_window.selected_annotations[0]
@@ -808,6 +823,8 @@ class LabelWindow(QWidget):
808
823
  current_image_path = self.annotation_window.current_image_path
809
824
  if current_image_path:
810
825
  self.annotation_window.set_image(current_image_path)
826
+ # Update annotation count after merge
827
+ self.update_annotation_count()
811
828
 
812
829
  def delete_label(self, label):
813
830
  """Delete the specified label and its associated annotations after confirmation."""
@@ -1180,6 +1197,4 @@ class EditLabelDialog(QDialog):
1180
1197
  new_long=new_long,
1181
1198
  new_color=new_color
1182
1199
  )
1183
- self.accept()
1184
-
1185
-
1200
+ self.accept()