coralnet-toolbox 0.0.66__py2.py3-none-any.whl → 0.0.68__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.
- coralnet_toolbox/Annotations/QtMultiPolygonAnnotation.py +1 -1
- coralnet_toolbox/Annotations/QtPatchAnnotation.py +1 -1
- coralnet_toolbox/Annotations/QtPolygonAnnotation.py +1 -1
- coralnet_toolbox/Annotations/QtRectangleAnnotation.py +1 -1
- coralnet_toolbox/AutoDistill/QtDeployModel.py +4 -0
- coralnet_toolbox/Explorer/QtDataItem.py +300 -0
- coralnet_toolbox/Explorer/QtExplorer.py +1825 -0
- coralnet_toolbox/Explorer/QtSettingsWidgets.py +494 -0
- coralnet_toolbox/Explorer/__init__.py +7 -0
- coralnet_toolbox/IO/QtImportViscoreAnnotations.py +2 -4
- coralnet_toolbox/IO/QtOpenProject.py +2 -1
- coralnet_toolbox/Icons/magic.png +0 -0
- coralnet_toolbox/MachineLearning/DeployModel/QtDetect.py +4 -0
- coralnet_toolbox/MachineLearning/DeployModel/QtSegment.py +4 -0
- coralnet_toolbox/MachineLearning/TrainModel/QtClassify.py +1 -1
- coralnet_toolbox/QtConfidenceWindow.py +2 -23
- coralnet_toolbox/QtEventFilter.py +18 -7
- coralnet_toolbox/QtLabelWindow.py +35 -8
- coralnet_toolbox/QtMainWindow.py +81 -2
- coralnet_toolbox/QtProgressBar.py +12 -0
- coralnet_toolbox/SAM/QtDeployGenerator.py +4 -0
- coralnet_toolbox/__init__.py +1 -1
- coralnet_toolbox/utilities.py +24 -0
- {coralnet_toolbox-0.0.66.dist-info → coralnet_toolbox-0.0.68.dist-info}/METADATA +12 -6
- {coralnet_toolbox-0.0.66.dist-info → coralnet_toolbox-0.0.68.dist-info}/RECORD +29 -24
- {coralnet_toolbox-0.0.66.dist-info → coralnet_toolbox-0.0.68.dist-info}/WHEEL +0 -0
- {coralnet_toolbox-0.0.66.dist-info → coralnet_toolbox-0.0.68.dist-info}/entry_points.txt +0 -0
- {coralnet_toolbox-0.0.66.dist-info → coralnet_toolbox-0.0.68.dist-info}/licenses/LICENSE.txt +0 -0
- {coralnet_toolbox-0.0.66.dist-info → coralnet_toolbox-0.0.68.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,494 @@
|
|
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 and reset to minimum values
|
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
|
+
self.param1_slider.setValue(self.param1_slider.minimum())
|
466
|
+
self.param1_value_label.setText(str(self.param1_slider.minimum()))
|
467
|
+
|
468
|
+
self.param2_label.setEnabled(False)
|
469
|
+
self.param2_slider.setEnabled(False)
|
470
|
+
self.param2_value_label.setEnabled(False)
|
471
|
+
self.param2_label.setText(" ")
|
472
|
+
self.param2_slider.setValue(self.param2_slider.minimum())
|
473
|
+
self.param2_value_label.setText(str(self.param2_slider.minimum()))
|
474
|
+
|
475
|
+
def get_embedding_parameters(self):
|
476
|
+
"""Returns a dictionary of the current embedding parameters."""
|
477
|
+
params = {
|
478
|
+
'technique': self.embedding_technique_combo.currentText(),
|
479
|
+
}
|
480
|
+
if params['technique'] == 'UMAP':
|
481
|
+
params['n_neighbors'] = self.param1_slider.value()
|
482
|
+
params['min_dist'] = self.param2_slider.value() / 100.0
|
483
|
+
elif params['technique'] == 'TSNE':
|
484
|
+
params['perplexity'] = self.param1_slider.value()
|
485
|
+
params['early_exaggeration'] = self.param2_slider.value() / 10.0
|
486
|
+
return params
|
487
|
+
|
488
|
+
def apply_embedding(self):
|
489
|
+
if self.explorer_window and hasattr(self.explorer_window, 'run_embedding_pipeline'):
|
490
|
+
# Clear all selections before running embedding pipeline
|
491
|
+
if hasattr(self.explorer_window, 'handle_selection_change'):
|
492
|
+
self.explorer_window.handle_selection_change([])
|
493
|
+
|
494
|
+
self.explorer_window.run_embedding_pipeline()
|
@@ -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
|
-
|
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
|
@@ -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 =
|
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.
|
33
|
+
if event.key() == Qt.Key_Alt:
|
34
34
|
self.main_window.switch_back_to_tool()
|
35
35
|
return True
|
36
36
|
|
@@ -72,11 +72,22 @@ class GlobalEventFilter(QObject):
|
|
72
72
|
self.annotation_window.cycle_annotations(1)
|
73
73
|
return True
|
74
74
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
75
|
+
# Delete (backspace or delete key) selected annotations when select tool is active
|
76
|
+
if event.key() == Qt.Key_Delete or event.key() == Qt.Key_Backspace:
|
77
|
+
|
78
|
+
# First, check if the Explorer window exists and is the active window
|
79
|
+
if (self.main_window.explorer_window and
|
80
|
+
self.main_window.explorer_window.isActiveWindow()):
|
81
|
+
|
82
|
+
# If it is, let the Explorer handle its own key press events.
|
83
|
+
# Returning False passes the event along instead of consuming it.
|
84
|
+
return False
|
85
|
+
|
86
|
+
# If Explorer is not active, proceed with the original logic for the main window
|
87
|
+
if self.main_window.select_tool_action.isChecked():
|
88
|
+
if self.annotation_window.selected_annotations:
|
89
|
+
self.annotation_window.delete_selected_annotations()
|
90
|
+
# Consume the event so it doesn't do anything else
|
80
91
|
return True
|
81
92
|
|
82
93
|
# Handle image cycling hotkeys
|
@@ -125,4 +136,4 @@ class GlobalEventFilter(QObject):
|
|
125
136
|
'Are you sure you want to exit?',
|
126
137
|
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
127
138
|
if reply == QMessageBox.Yes:
|
128
|
-
QApplication.quit()
|
139
|
+
QApplication.quit()
|