coralnet-toolbox 0.0.72__py2.py3-none-any.whl → 0.0.74__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/QtAnnotation.py +28 -69
- coralnet_toolbox/Annotations/QtMaskAnnotation.py +408 -0
- coralnet_toolbox/Annotations/QtMultiPolygonAnnotation.py +72 -56
- coralnet_toolbox/Annotations/QtPatchAnnotation.py +165 -216
- coralnet_toolbox/Annotations/QtPolygonAnnotation.py +497 -353
- coralnet_toolbox/Annotations/QtRectangleAnnotation.py +126 -116
- coralnet_toolbox/AutoDistill/QtDeployModel.py +23 -12
- coralnet_toolbox/CoralNet/QtDownload.py +2 -1
- coralnet_toolbox/Explorer/QtDataItem.py +1 -1
- coralnet_toolbox/Explorer/QtExplorer.py +159 -17
- coralnet_toolbox/Explorer/QtSettingsWidgets.py +160 -86
- coralnet_toolbox/IO/QtExportTagLabAnnotations.py +30 -10
- coralnet_toolbox/IO/QtImportTagLabAnnotations.py +21 -15
- coralnet_toolbox/IO/QtOpenProject.py +46 -78
- coralnet_toolbox/IO/QtSaveProject.py +18 -43
- coralnet_toolbox/MachineLearning/DeployModel/QtDetect.py +22 -11
- coralnet_toolbox/MachineLearning/DeployModel/QtSegment.py +22 -10
- coralnet_toolbox/MachineLearning/ExportDataset/QtBase.py +61 -24
- coralnet_toolbox/MachineLearning/ExportDataset/QtClassify.py +5 -1
- coralnet_toolbox/MachineLearning/ExportDataset/QtDetect.py +19 -6
- coralnet_toolbox/MachineLearning/ExportDataset/QtSegment.py +21 -8
- coralnet_toolbox/MachineLearning/ImportDataset/QtBase.py +42 -22
- coralnet_toolbox/MachineLearning/VideoInference/QtBase.py +0 -4
- coralnet_toolbox/QtAnnotationWindow.py +42 -14
- coralnet_toolbox/QtEventFilter.py +19 -2
- coralnet_toolbox/QtImageWindow.py +134 -86
- coralnet_toolbox/QtLabelWindow.py +14 -2
- coralnet_toolbox/QtMainWindow.py +122 -9
- coralnet_toolbox/QtProgressBar.py +52 -27
- coralnet_toolbox/Rasters/QtRaster.py +59 -7
- coralnet_toolbox/Rasters/RasterTableModel.py +42 -14
- coralnet_toolbox/SAM/QtBatchInference.py +0 -2
- coralnet_toolbox/SAM/QtDeployGenerator.py +22 -11
- coralnet_toolbox/SAM/QtDeployPredictor.py +10 -0
- coralnet_toolbox/SeeAnything/QtBatchInference.py +19 -221
- coralnet_toolbox/SeeAnything/QtDeployGenerator.py +1634 -0
- coralnet_toolbox/SeeAnything/QtDeployPredictor.py +107 -154
- coralnet_toolbox/SeeAnything/QtTrainModel.py +115 -45
- coralnet_toolbox/SeeAnything/__init__.py +2 -0
- coralnet_toolbox/Tools/QtCutSubTool.py +18 -2
- coralnet_toolbox/Tools/QtResizeSubTool.py +19 -2
- coralnet_toolbox/Tools/QtSAMTool.py +222 -57
- coralnet_toolbox/Tools/QtSeeAnythingTool.py +223 -55
- coralnet_toolbox/Tools/QtSelectSubTool.py +6 -4
- coralnet_toolbox/Tools/QtSelectTool.py +27 -3
- coralnet_toolbox/Tools/QtSubtractSubTool.py +66 -0
- coralnet_toolbox/Tools/QtWorkAreaTool.py +25 -13
- coralnet_toolbox/Tools/__init__.py +2 -0
- coralnet_toolbox/__init__.py +1 -1
- coralnet_toolbox/utilities.py +137 -47
- coralnet_toolbox-0.0.74.dist-info/METADATA +375 -0
- {coralnet_toolbox-0.0.72.dist-info → coralnet_toolbox-0.0.74.dist-info}/RECORD +56 -53
- coralnet_toolbox-0.0.72.dist-info/METADATA +0 -341
- {coralnet_toolbox-0.0.72.dist-info → coralnet_toolbox-0.0.74.dist-info}/WHEEL +0 -0
- {coralnet_toolbox-0.0.72.dist-info → coralnet_toolbox-0.0.74.dist-info}/entry_points.txt +0 -0
- {coralnet_toolbox-0.0.72.dist-info → coralnet_toolbox-0.0.74.dist-info}/licenses/LICENSE.txt +0 -0
- {coralnet_toolbox-0.0.72.dist-info → coralnet_toolbox-0.0.74.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,18 @@
|
|
1
1
|
import warnings
|
2
2
|
|
3
3
|
import os
|
4
|
-
import gc
|
5
4
|
from contextlib import contextmanager
|
6
5
|
|
7
6
|
import rasterio
|
8
7
|
|
9
|
-
from PyQt5.
|
10
|
-
from PyQt5.QtCore import Qt, pyqtSignal, QTimer, QPoint, QThreadPool, QItemSelectionModel
|
8
|
+
from PyQt5.QtCore import Qt, pyqtSignal, QTimer, QPoint, QThreadPool, QItemSelectionModel, QModelIndex
|
11
9
|
from PyQt5.QtWidgets import (QSizePolicy, QMessageBox, QCheckBox, QWidget, QVBoxLayout,
|
12
10
|
QLabel, QComboBox, QHBoxLayout, QTableView, QHeaderView, QApplication,
|
13
|
-
QMenu, QButtonGroup,
|
14
|
-
|
15
|
-
|
16
|
-
from coralnet_toolbox.Rasters import Raster, RasterManager, ImageFilter, RasterTableModel
|
11
|
+
QMenu, QButtonGroup, QGroupBox, QPushButton, QStyle,
|
12
|
+
QFormLayout, QFrame)
|
17
13
|
|
14
|
+
from coralnet_toolbox.Rasters import RasterManager, ImageFilter, RasterTableModel
|
18
15
|
from coralnet_toolbox.QtProgressBar import ProgressBar
|
19
|
-
|
20
16
|
from coralnet_toolbox.Icons import get_icon
|
21
17
|
|
22
18
|
|
@@ -30,11 +26,24 @@ warnings.filterwarnings("ignore", category=rasterio.errors.NotGeoreferencedWarni
|
|
30
26
|
|
31
27
|
|
32
28
|
class NoArrowKeyTableView(QTableView):
|
29
|
+
# Custom signal to be emitted only on a left-click
|
30
|
+
leftClicked = pyqtSignal(QModelIndex)
|
31
|
+
|
33
32
|
def keyPressEvent(self, event):
|
34
33
|
if event.key() in (Qt.Key_Up, Qt.Key_Down):
|
35
34
|
event.ignore()
|
36
35
|
return
|
37
36
|
super().keyPressEvent(event)
|
37
|
+
|
38
|
+
def mousePressEvent(self, event):
|
39
|
+
# On a left mouse press, emit our custom signal
|
40
|
+
if event.button() == Qt.LeftButton:
|
41
|
+
index = self.indexAt(event.pos())
|
42
|
+
if index.isValid():
|
43
|
+
self.leftClicked.emit(index)
|
44
|
+
# Call the base class implementation to handle standard behavior
|
45
|
+
# like row selection and context menu triggers.
|
46
|
+
super().mousePressEvent(event)
|
38
47
|
|
39
48
|
|
40
49
|
class ImageWindow(QWidget):
|
@@ -256,10 +265,11 @@ class ImageWindow(QWidget):
|
|
256
265
|
self.table_model = RasterTableModel(self.raster_manager, self)
|
257
266
|
self.tableView.setModel(self.table_model)
|
258
267
|
|
259
|
-
# Set column widths
|
260
|
-
self.tableView.horizontalHeader().setSectionResizeMode(0, QHeaderView.
|
261
|
-
self.tableView.
|
262
|
-
self.tableView.
|
268
|
+
# Set column widths
|
269
|
+
self.tableView.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents) # Checkmark column
|
270
|
+
self.tableView.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) # Filename column
|
271
|
+
self.tableView.setColumnWidth(2, 120) # Annotation column
|
272
|
+
self.tableView.horizontalHeader().setSectionResizeMode(2, QHeaderView.Fixed)
|
263
273
|
|
264
274
|
# Style the header
|
265
275
|
self.tableView.horizontalHeader().setStyleSheet("""
|
@@ -271,7 +281,7 @@ class ImageWindow(QWidget):
|
|
271
281
|
""")
|
272
282
|
|
273
283
|
# Connect signals for clicking
|
274
|
-
self.tableView.
|
284
|
+
self.tableView.leftClicked.connect(self.on_table_pressed)
|
275
285
|
self.tableView.doubleClicked.connect(self.on_table_double_clicked)
|
276
286
|
|
277
287
|
# Add table view to the layout
|
@@ -413,59 +423,62 @@ class ImageWindow(QWidget):
|
|
413
423
|
#
|
414
424
|
|
415
425
|
def on_table_pressed(self, index):
|
416
|
-
"""Handle a single click on the table view."""
|
426
|
+
"""Handle a single left-click on the table view with complex modifier support."""
|
417
427
|
if not index.isValid():
|
418
428
|
return
|
419
|
-
|
420
|
-
# Get the path at the clicked row
|
429
|
+
|
421
430
|
path = self.table_model.get_path_at_row(index.row())
|
422
431
|
if not path:
|
423
432
|
return
|
424
433
|
|
425
|
-
# Get keyboard modifiers
|
426
434
|
modifiers = QApplication.keyboardModifiers()
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
435
|
+
current_row = index.row()
|
436
|
+
|
437
|
+
# Define conditions for modifiers
|
438
|
+
has_ctrl = bool(modifiers & Qt.ControlModifier)
|
439
|
+
has_shift = bool(modifiers & Qt.ShiftModifier)
|
440
|
+
|
441
|
+
if has_shift:
|
442
|
+
# This block handles both Shift+Click and Ctrl+Shift+Click.
|
443
|
+
# First, determine the paths in the selection range.
|
444
|
+
range_paths = []
|
445
|
+
if self.last_highlighted_row >= 0:
|
446
|
+
start = min(self.last_highlighted_row, current_row)
|
447
|
+
end = max(self.last_highlighted_row, current_row)
|
448
|
+
for r in range(start, end + 1):
|
449
|
+
p = self.table_model.get_path_at_row(r)
|
450
|
+
if p:
|
451
|
+
range_paths.append(p)
|
452
|
+
else:
|
453
|
+
# If there's no anchor, the range is just the clicked item.
|
454
|
+
range_paths.append(path)
|
455
|
+
|
456
|
+
if not has_ctrl:
|
457
|
+
# Case 1: Simple Shift+Click. Clears previous highlights
|
458
|
+
# and selects only the new range.
|
459
|
+
self.table_model.set_highlighted_paths(range_paths)
|
460
|
+
else:
|
461
|
+
# Case 2: Ctrl+Shift+Click. Adds the new range to the
|
462
|
+
# existing highlighted rows without clearing them.
|
463
|
+
for p in range_paths:
|
464
|
+
self.table_model.highlight_path(p, True)
|
465
|
+
|
466
|
+
elif has_ctrl:
|
467
|
+
# Case 3: Ctrl+Click. Toggles a single row's highlight state
|
468
|
+
# and sets it as the new anchor for future shift-clicks.
|
431
469
|
raster = self.raster_manager.get_raster(path)
|
432
470
|
if raster:
|
433
471
|
self.table_model.highlight_path(path, not raster.is_highlighted)
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
elif modifiers & Qt.ShiftModifier:
|
438
|
-
# Shift+Click: Highlight range from last highlighted to current
|
439
|
-
if self.last_highlighted_row >= 0:
|
440
|
-
# Get the current row and last highlighted row
|
441
|
-
current_row = index.row()
|
442
|
-
|
443
|
-
# Calculate range (handle both directions)
|
444
|
-
start_row = min(self.last_highlighted_row, current_row)
|
445
|
-
end_row = max(self.last_highlighted_row, current_row)
|
446
|
-
|
447
|
-
# Highlight the range
|
448
|
-
for row in range(start_row, end_row + 1):
|
449
|
-
path_to_highlight = self.table_model.get_path_at_row(row)
|
450
|
-
if path_to_highlight:
|
451
|
-
self.table_model.highlight_path(path_to_highlight, True)
|
452
|
-
else:
|
453
|
-
# No previous selection, just highlight the current row
|
454
|
-
self.table_model.highlight_path(path, True)
|
455
|
-
|
456
|
-
# Update the last highlighted row
|
457
|
-
self.last_highlighted_row = index.row()
|
458
|
-
|
459
|
-
# Update highlighted count
|
460
|
-
self.update_highlighted_count_label()
|
472
|
+
self.last_highlighted_row = current_row
|
473
|
+
|
461
474
|
else:
|
462
|
-
#
|
463
|
-
|
464
|
-
self.table_model.
|
465
|
-
self.last_highlighted_row =
|
466
|
-
|
467
|
-
|
468
|
-
|
475
|
+
# Case 4: Plain Click. Clears everything and highlights only
|
476
|
+
# the clicked row, setting it as the new anchor.
|
477
|
+
self.table_model.set_highlighted_paths([path])
|
478
|
+
self.last_highlighted_row = current_row
|
479
|
+
|
480
|
+
# Finally, update the count label after any changes.
|
481
|
+
self.update_highlighted_count_label()
|
469
482
|
|
470
483
|
def on_table_double_clicked(self, index):
|
471
484
|
"""Handle double click on table view (selects image and loads it)."""
|
@@ -521,6 +534,24 @@ class ImageWindow(QWidget):
|
|
521
534
|
"""Handler for when an image is loaded."""
|
522
535
|
self.selected_image_path = path
|
523
536
|
|
537
|
+
def on_toggle(self, new_state: bool):
|
538
|
+
"""
|
539
|
+
Sets the checked state for all currently highlighted rows.
|
540
|
+
|
541
|
+
Args:
|
542
|
+
new_state (bool): The new state to set (True for checked, False for unchecked).
|
543
|
+
"""
|
544
|
+
highlighted_paths = self.table_model.get_highlighted_paths()
|
545
|
+
if not highlighted_paths:
|
546
|
+
return
|
547
|
+
|
548
|
+
for path in highlighted_paths:
|
549
|
+
raster = self.raster_manager.get_raster(path)
|
550
|
+
if raster:
|
551
|
+
raster.checkbox_state = new_state
|
552
|
+
# Notify the model to update the view for this specific raster
|
553
|
+
self.table_model.update_raster_data(path)
|
554
|
+
|
524
555
|
#
|
525
556
|
# Public methods
|
526
557
|
#
|
@@ -562,7 +593,7 @@ class ImageWindow(QWidget):
|
|
562
593
|
|
563
594
|
except Exception as e:
|
564
595
|
self.show_error("Image Loading Error",
|
565
|
-
|
596
|
+
f"Error loading image {os.path.basename(image_path)}:\n{str(e)}")
|
566
597
|
return False
|
567
598
|
|
568
599
|
@property
|
@@ -960,28 +991,46 @@ class ImageWindow(QWidget):
|
|
960
991
|
|
961
992
|
def show_context_menu(self, position):
|
962
993
|
"""
|
963
|
-
Show the context menu for the table.
|
994
|
+
Show the context menu for the table, including the toggle check state action.
|
964
995
|
|
965
996
|
Args:
|
966
997
|
position (QPoint): Position to show the menu
|
967
998
|
"""
|
999
|
+
# Get the path corresponding to the right-clicked row
|
1000
|
+
index = self.tableView.indexAt(position)
|
1001
|
+
path_at_cursor = self.table_model.get_path_at_row(index.row()) if index.isValid() else None
|
1002
|
+
|
1003
|
+
# Get the currently highlighted paths from the model
|
968
1004
|
highlighted_paths = self.table_model.get_highlighted_paths()
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
else:
|
979
|
-
# If any highlights, ensure all highlighted rows are used (no change needed)
|
980
|
-
self.table_model.set_highlighted_paths(highlighted_paths)
|
1005
|
+
|
1006
|
+
# If the user right-clicked on a row that wasn't already highlighted,
|
1007
|
+
# then we assume they want to act on this row alone.
|
1008
|
+
if path_at_cursor and path_at_cursor not in highlighted_paths:
|
1009
|
+
self.table_model.set_highlighted_paths([path_at_cursor])
|
1010
|
+
self.last_highlighted_row = index.row()
|
1011
|
+
highlighted_paths = [path_at_cursor]
|
1012
|
+
|
1013
|
+
# If no rows are highlighted, do nothing.
|
981
1014
|
if not highlighted_paths:
|
982
1015
|
return
|
1016
|
+
|
983
1017
|
context_menu = QMenu(self)
|
984
1018
|
count = len(highlighted_paths)
|
1019
|
+
|
1020
|
+
# Add the check/uncheck action
|
1021
|
+
raster_under_cursor = self.raster_manager.get_raster(path_at_cursor)
|
1022
|
+
if raster_under_cursor:
|
1023
|
+
is_checked = raster_under_cursor.checkbox_state
|
1024
|
+
if is_checked:
|
1025
|
+
action_text = f"Uncheck {count} Highlighted Image{'s' if count > 1 else ''}"
|
1026
|
+
else:
|
1027
|
+
action_text = f"Check {count} Highlighted Image{'s' if count > 1 else ''}"
|
1028
|
+
toggle_check_action = context_menu.addAction(action_text)
|
1029
|
+
toggle_check_action.triggered.connect(lambda: self.on_toggle(not is_checked))
|
1030
|
+
|
1031
|
+
context_menu.addSeparator()
|
1032
|
+
|
1033
|
+
# Add existing delete actions
|
985
1034
|
delete_images_action = context_menu.addAction(f"Delete {count} Highlighted Image{'s' if count > 1 else ''}")
|
986
1035
|
delete_images_action.triggered.connect(lambda: self.delete_highlighted_images())
|
987
1036
|
delete_annotations_action = context_menu.addAction(
|
@@ -1182,26 +1231,25 @@ class ImagePreviewTooltip(QFrame):
|
|
1182
1231
|
self.hide()
|
1183
1232
|
|
1184
1233
|
def show_at(self, global_pos):
|
1185
|
-
"""
|
1186
|
-
Position
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
screen_rect = self.screen().geometry()
|
1234
|
+
"""Position and show the tooltip at the specified global position."""
|
1235
|
+
# Position tooltip to bottom-right of cursor
|
1236
|
+
x, y = global_pos.x() + 15, global_pos.y() + 15
|
1237
|
+
|
1238
|
+
# Get the screen that contains the cursor position
|
1239
|
+
screen = QApplication.screenAt(global_pos)
|
1240
|
+
if not screen:
|
1241
|
+
screen = QApplication.primaryScreen()
|
1242
|
+
|
1243
|
+
# Get screen geometry and tooltip size
|
1244
|
+
screen_rect = screen.geometry()
|
1197
1245
|
tooltip_size = self.sizeHint()
|
1198
|
-
|
1199
|
-
# Adjust position
|
1246
|
+
|
1247
|
+
# Adjust position to stay on screen
|
1200
1248
|
if x + tooltip_size.width() > screen_rect.right():
|
1201
|
-
x =
|
1249
|
+
x = global_pos.x() - tooltip_size.width() - 15
|
1202
1250
|
if y + tooltip_size.height() > screen_rect.bottom():
|
1203
|
-
y =
|
1204
|
-
|
1251
|
+
y = global_pos.y() - tooltip_size.height() - 15
|
1252
|
+
|
1205
1253
|
# Set position and show
|
1206
1254
|
self.move(x, y)
|
1207
1255
|
self.show()
|
@@ -280,7 +280,7 @@ class LabelWindow(QWidget):
|
|
280
280
|
|
281
281
|
self.edit_label_button = QPushButton()
|
282
282
|
self.edit_label_button.setIcon(self.main_window.edit_icon)
|
283
|
-
self.edit_label_button.setToolTip("Edit Label")
|
283
|
+
self.edit_label_button.setToolTip("Edit Label / Merge Labels")
|
284
284
|
self.edit_label_button.setFixedSize(self.label_width, self.label_height)
|
285
285
|
self.edit_label_button.setEnabled(False) # Initially disabled
|
286
286
|
self.top_bar.addWidget(self.edit_label_button)
|
@@ -485,7 +485,19 @@ class LabelWindow(QWidget):
|
|
485
485
|
self.scroll_content.setFixedWidth(self.labels_per_row * self.label_width)
|
486
486
|
|
487
487
|
def reorganize_labels(self):
|
488
|
-
"""
|
488
|
+
"""
|
489
|
+
Rearrange labels in the grid layout based on the current order and labels_per_row.
|
490
|
+
"""
|
491
|
+
# First, clear the existing layout to remove any lingering widgets.
|
492
|
+
# This prevents references to deleted widgets from persisting in the layout.
|
493
|
+
while self.grid_layout.count():
|
494
|
+
item = self.grid_layout.takeAt(0)
|
495
|
+
widget = item.widget()
|
496
|
+
if widget:
|
497
|
+
# Setting the parent to None removes the widget from the layout's control.
|
498
|
+
widget.setParent(None)
|
499
|
+
|
500
|
+
# Now, add the current labels from the model back into the clean layout.
|
489
501
|
for i, label in enumerate(self.labels):
|
490
502
|
row = i // self.labels_per_row
|
491
503
|
col = i % self.labels_per_row
|
coralnet_toolbox/QtMainWindow.py
CHANGED
@@ -9,8 +9,8 @@ import requests
|
|
9
9
|
|
10
10
|
from packaging import version
|
11
11
|
|
12
|
-
from PyQt5.QtCore import Qt, pyqtSignal, QEvent, QSize, QPoint
|
13
12
|
from PyQt5.QtGui import QIcon, QMouseEvent
|
13
|
+
from PyQt5.QtCore import Qt, pyqtSignal, QEvent, QSize, QPoint
|
14
14
|
from PyQt5.QtWidgets import (QListWidget, QCheckBox, QFrame, QComboBox)
|
15
15
|
from PyQt5.QtWidgets import (QMainWindow, QApplication, QToolBar, QAction, QSizePolicy,
|
16
16
|
QMessageBox, QWidget, QVBoxLayout, QLabel, QHBoxLayout,
|
@@ -95,6 +95,7 @@ from coralnet_toolbox.SAM import (
|
|
95
95
|
from coralnet_toolbox.SeeAnything import (
|
96
96
|
TrainModelDialog as SeeAnythingTrainModelDialog,
|
97
97
|
DeployPredictorDialog as SeeAnythingDeployPredictorDialog,
|
98
|
+
DeployGeneratorDialog as SeeAnythingDeployGeneratorDialog,
|
98
99
|
BatchInferenceDialog as SeeAnythingBatchInferenceDialog
|
99
100
|
)
|
100
101
|
|
@@ -258,6 +259,7 @@ class MainWindow(QMainWindow):
|
|
258
259
|
# Create dialogs (See Anything)
|
259
260
|
self.see_anything_train_model_dialog = SeeAnythingTrainModelDialog(self)
|
260
261
|
self.see_anything_deploy_predictor_dialog = SeeAnythingDeployPredictorDialog(self)
|
262
|
+
self.see_anything_deploy_generator_dialog = SeeAnythingDeployGeneratorDialog(self)
|
261
263
|
self.see_anything_batch_inference_dialog = SeeAnythingBatchInferenceDialog(self)
|
262
264
|
|
263
265
|
# Create dialogs (AutoDistill)
|
@@ -622,11 +624,17 @@ class MainWindow(QMainWindow):
|
|
622
624
|
# Train Model
|
623
625
|
self.see_anything_train_model_action = QAction("Train Model", self)
|
624
626
|
self.see_anything_train_model_action.triggered.connect(self.open_see_anything_train_model_dialog)
|
625
|
-
self.see_anything_menu.addAction(self.see_anything_train_model_action)
|
626
|
-
# Deploy Model
|
627
|
+
# self.see_anything_menu.addAction(self.see_anything_train_model_action) TODO Doesn't work
|
628
|
+
# Deploy Model submenu
|
629
|
+
self.see_anything_deploy_model_menu = self.see_anything_menu.addMenu("Deploy Model")
|
630
|
+
# Deploy Predictor
|
627
631
|
self.see_anything_deploy_predictor_action = QAction("Deploy Predictor", self)
|
628
632
|
self.see_anything_deploy_predictor_action.triggered.connect(self.open_see_anything_deploy_predictor_dialog)
|
629
|
-
self.
|
633
|
+
self.see_anything_deploy_model_menu.addAction(self.see_anything_deploy_predictor_action)
|
634
|
+
# Deploy Generator
|
635
|
+
self.see_anything_deploy_generator_action = QAction("Deploy Generator", self)
|
636
|
+
self.see_anything_deploy_generator_action.triggered.connect(self.open_see_anything_deploy_generator_dialog)
|
637
|
+
self.see_anything_deploy_model_menu.addAction(self.see_anything_deploy_generator_action)
|
630
638
|
# Batch Inference
|
631
639
|
self.see_anything_batch_inference_action = QAction("Batch Inference", self)
|
632
640
|
self.see_anything_batch_inference_action.triggered.connect(self.open_see_anything_batch_inference_dialog)
|
@@ -674,6 +682,76 @@ class MainWindow(QMainWindow):
|
|
674
682
|
# ----------------------------------------
|
675
683
|
# Create and add the toolbar
|
676
684
|
# ----------------------------------------
|
685
|
+
|
686
|
+
# Define verbose tool descriptions
|
687
|
+
self.tool_descriptions = {
|
688
|
+
"select": ("Select Tool\n\n"
|
689
|
+
"Select, modify, and manage annotations.\n"
|
690
|
+
"• Left-click to select annotations; hold Ctrl+left-click to select multiple.\n"
|
691
|
+
"• Left-click and drag to move selected annotations.\n"
|
692
|
+
"• Ctrl+click and drag to create a selection rectangle.\n"
|
693
|
+
"• Ctrl+Shift to show resize handles for a selected Rectangle and Polygon annotations.\n"
|
694
|
+
"• Ctrl+X to cut a selected annotation along a drawn line.\n"
|
695
|
+
"• Ctrl+C to combine multiple selected annotations.\n"
|
696
|
+
"• Ctrl+Space to confirm selected annotations with top predictions.\n"
|
697
|
+
"• Ctrl+Shift+mouse wheel to adjust polygon complexity.\n"
|
698
|
+
"• Ctrl+Delete to remove selected annotations."),
|
699
|
+
|
700
|
+
"patch": ("Patch Tool\n\n"
|
701
|
+
"Create point (patch) annotations centered at the cursor.\n"
|
702
|
+
"• Left-click to place a patch at the mouse location.\n"
|
703
|
+
"• Hold Ctrl and use the mouse wheel or use the Patch Size box to adjust patch size.\n"
|
704
|
+
"• A semi-transparent preview shows the patch before placing it."),
|
705
|
+
|
706
|
+
"rectangle": ("Rectangle Tool\n\n"
|
707
|
+
"Create rectangular annotations by clicking and dragging.\n"
|
708
|
+
"• Left-click to set the first corner, then move the mouse to size the rectangle.\n"
|
709
|
+
"• Left-click again to place the rectangle.\n"
|
710
|
+
"• Press Backspace to cancel drawing the current rectangle.\n"
|
711
|
+
"• A semi-transparent preview shows the rectangle while drawing."),
|
712
|
+
|
713
|
+
"polygon": ("Polygon Tool\n\n"
|
714
|
+
"Create polygon annotations with multiple vertices.\n"
|
715
|
+
"• Left-click to set the first vertex, then move the mouse to draw the polygon\n"
|
716
|
+
"• Hold Ctrl while left-clicking to draw straight-line segments.\n"
|
717
|
+
"• Left-click again to complete the polygon.\n"
|
718
|
+
"• Press Backspace to cancel the current polygon.\n"
|
719
|
+
"• A semi-transparent preview shows the polygon while drawing."),
|
720
|
+
|
721
|
+
"sam": ("Segment Anything (SAM) Tool\n\n"
|
722
|
+
"Generates AI-powered segmentations.\n"
|
723
|
+
"• Left-click to create a working area, then left-click again to confirm.\n"
|
724
|
+
"\t• Or, press Spacebar to create a working area for the current view.\n"
|
725
|
+
"• Ctrl+Left-click to add positive points (foreground).\n"
|
726
|
+
"• Ctrl+Right-click to add negative points (background).\n"
|
727
|
+
"• Left-click and drag to create a bounding box for prompting.\n"
|
728
|
+
"• Press Spacebar to generate and confirm the segmentation.\n"
|
729
|
+
"• Press Backspace to cancel the current operation.\n"
|
730
|
+
"• Uncertainty can be adjusted in Parameters section.\n"
|
731
|
+
"• A SAM predictor must be deployed first."),
|
732
|
+
|
733
|
+
"see_anything": ("See Anything (YOLOE) Tool\n\n"
|
734
|
+
"Uses YOLOE to detect / segments objects of interest based on visual prompts.\n"
|
735
|
+
"• Left-click to create a working area, then click again to confirm.\n"
|
736
|
+
"\t• Or, press Spacebar to create a working area for the current view.\n"
|
737
|
+
"• Draw rectangles inside the working area to guide detection.\n"
|
738
|
+
"• Press Spacebar to generate detections using drawn rectangles.\n"
|
739
|
+
"• Press Spacebar again to confirm annotations or apply SAM refinement.\n"
|
740
|
+
"• Press Backspace to cancel current operation or clear annotations.\n"
|
741
|
+
"• Uncertainty can be adjusted in Parameters section.\n"
|
742
|
+
"• A See Anything (YOLOE) predictor must be deployed first."),
|
743
|
+
|
744
|
+
"work_area": ("Work Area Tool\n\n"
|
745
|
+
"Defines regions for detection and segmentation models to run predictions on.\n"
|
746
|
+
"• Left-click to create a working area, then left-click again to confirm.\n"
|
747
|
+
"\t• Or, press Spacebar to create a work area from the current view.\n"
|
748
|
+
"• Hold Ctrl+Shift to show delete buttons for existing work areas.\n"
|
749
|
+
"• Press Ctrl+Shift+Backspace to clear all work areas.\n"
|
750
|
+
"• Hold Ctrl+Alt to temporarily view a work area of the current view.\n"
|
751
|
+
"• Work areas can be used with Tile Batch Inference and other batch operations.\n"
|
752
|
+
"• All work areas are automatically saved with the image in a Project (JSON) file.")
|
753
|
+
}
|
754
|
+
|
677
755
|
self.toolbar = QToolBar("Tools", self)
|
678
756
|
self.toolbar.setOrientation(Qt.Vertical)
|
679
757
|
self.toolbar.setFixedWidth(40)
|
@@ -696,6 +774,7 @@ class MainWindow(QMainWindow):
|
|
696
774
|
# Add tools here with icons
|
697
775
|
self.select_tool_action = QAction(self.select_icon, "Select", self)
|
698
776
|
self.select_tool_action.setCheckable(True)
|
777
|
+
self.select_tool_action.setToolTip(self.tool_descriptions["select"])
|
699
778
|
self.select_tool_action.triggered.connect(self.toggle_tool)
|
700
779
|
self.toolbar.addAction(self.select_tool_action)
|
701
780
|
|
@@ -703,16 +782,19 @@ class MainWindow(QMainWindow):
|
|
703
782
|
|
704
783
|
self.patch_tool_action = QAction(self.patch_icon, "Patch", self)
|
705
784
|
self.patch_tool_action.setCheckable(True)
|
785
|
+
self.patch_tool_action.setToolTip(self.tool_descriptions["patch"])
|
706
786
|
self.patch_tool_action.triggered.connect(self.toggle_tool)
|
707
787
|
self.toolbar.addAction(self.patch_tool_action)
|
708
788
|
|
709
789
|
self.rectangle_tool_action = QAction(self.rectangle_icon, "Rectangle", self)
|
710
790
|
self.rectangle_tool_action.setCheckable(True)
|
791
|
+
self.rectangle_tool_action.setToolTip(self.tool_descriptions["rectangle"])
|
711
792
|
self.rectangle_tool_action.triggered.connect(self.toggle_tool)
|
712
793
|
self.toolbar.addAction(self.rectangle_tool_action)
|
713
794
|
|
714
795
|
self.polygon_tool_action = QAction(self.polygon_icon, "Polygon", self)
|
715
796
|
self.polygon_tool_action.setCheckable(True)
|
797
|
+
self.polygon_tool_action.setToolTip(self.tool_descriptions["polygon"])
|
716
798
|
self.polygon_tool_action.triggered.connect(self.toggle_tool)
|
717
799
|
self.toolbar.addAction(self.polygon_tool_action)
|
718
800
|
|
@@ -720,11 +802,13 @@ class MainWindow(QMainWindow):
|
|
720
802
|
|
721
803
|
self.sam_tool_action = QAction(self.sam_icon, "SAM", self)
|
722
804
|
self.sam_tool_action.setCheckable(True)
|
805
|
+
self.sam_tool_action.setToolTip(self.tool_descriptions["sam"])
|
723
806
|
self.sam_tool_action.triggered.connect(self.toggle_tool)
|
724
807
|
self.toolbar.addAction(self.sam_tool_action)
|
725
808
|
|
726
809
|
self.see_anything_tool_action = QAction(self.see_anything_icon, "See Anything (YOLOE)", self)
|
727
810
|
self.see_anything_tool_action.setCheckable(True)
|
811
|
+
self.see_anything_tool_action.setToolTip(self.tool_descriptions["see_anything"])
|
728
812
|
self.see_anything_tool_action.triggered.connect(self.toggle_tool)
|
729
813
|
self.toolbar.addAction(self.see_anything_tool_action)
|
730
814
|
|
@@ -732,6 +816,7 @@ class MainWindow(QMainWindow):
|
|
732
816
|
|
733
817
|
self.work_area_tool_action = QAction(self.workarea_icon, "Work Area", self)
|
734
818
|
self.work_area_tool_action.setCheckable(True)
|
819
|
+
self.work_area_tool_action.setToolTip(self.tool_descriptions["work_area"])
|
735
820
|
self.work_area_tool_action.triggered.connect(self.toggle_tool)
|
736
821
|
self.toolbar.addAction(self.work_area_tool_action)
|
737
822
|
|
@@ -2196,6 +2281,26 @@ class MainWindow(QMainWindow):
|
|
2196
2281
|
self.see_anything_deploy_predictor_dialog.exec_()
|
2197
2282
|
except Exception as e:
|
2198
2283
|
QMessageBox.critical(self, "Critical Error", f"{e}")
|
2284
|
+
|
2285
|
+
def open_see_anything_deploy_generator_dialog(self):
|
2286
|
+
"""Open the See Anything Deploy Generator dialog to deploy a See Anything generator."""
|
2287
|
+
if not self.image_window.raster_manager.image_paths:
|
2288
|
+
QMessageBox.warning(self,
|
2289
|
+
"See Anything (YOLOE)",
|
2290
|
+
"No images are present in the project.")
|
2291
|
+
return
|
2292
|
+
|
2293
|
+
if len(self.label_window.labels) <= 1:
|
2294
|
+
QMessageBox.warning(self,
|
2295
|
+
"See Anything (YOLOE)",
|
2296
|
+
"At least one reference label is required for reference.")
|
2297
|
+
return
|
2298
|
+
|
2299
|
+
try:
|
2300
|
+
self.untoggle_all_tools()
|
2301
|
+
self.see_anything_deploy_generator_dialog.exec_()
|
2302
|
+
except Exception as e:
|
2303
|
+
QMessageBox.critical(self, "Critical Error", f"An error occurred: {e}")
|
2199
2304
|
|
2200
2305
|
def open_see_anything_batch_inference_dialog(self):
|
2201
2306
|
"""Open the See Anything Batch Inference dialog to run batch inference with See Anything."""
|
@@ -2205,16 +2310,22 @@ class MainWindow(QMainWindow):
|
|
2205
2310
|
"No images are present in the project.")
|
2206
2311
|
return
|
2207
2312
|
|
2208
|
-
if not self.
|
2313
|
+
if not self.see_anything_deploy_generator_dialog.loaded_model:
|
2209
2314
|
QMessageBox.warning(self,
|
2210
2315
|
"See Anything (YOLOE) Batch Inference",
|
2211
|
-
"Please deploy a
|
2316
|
+
"Please deploy a generator before running batch inference.")
|
2317
|
+
return
|
2318
|
+
|
2319
|
+
# Check if there are any annotations
|
2320
|
+
if not self.annotation_window.annotations_dict:
|
2321
|
+
QMessageBox.warning(self,
|
2322
|
+
"See Anything (YOLOE)",
|
2323
|
+
"Cannot run See Anything (YOLOE) without reference annotations in the project.")
|
2212
2324
|
return
|
2213
2325
|
|
2214
2326
|
try:
|
2215
2327
|
self.untoggle_all_tools()
|
2216
|
-
|
2217
|
-
self.see_anything_batch_inference_dialog.exec_()
|
2328
|
+
self.see_anything_batch_inference_dialog.exec_()
|
2218
2329
|
except Exception as e:
|
2219
2330
|
QMessageBox.critical(self, "Critical Error", f"{e}")
|
2220
2331
|
|
@@ -2383,7 +2494,9 @@ class MainWindow(QMainWindow):
|
|
2383
2494
|
msg.setWindowIcon(self.coral_icon)
|
2384
2495
|
msg.setWindowTitle("GDI Limit Reached")
|
2385
2496
|
msg.setText(
|
2386
|
-
"The GDI limit
|
2497
|
+
"The GDI limit is getting dangerously close to being reached (this is a known issue). "
|
2498
|
+
"Please immediately save your progress, close, and re-open the application. Failure to do so may "
|
2499
|
+
"result in data loss."
|
2387
2500
|
)
|
2388
2501
|
msg.setStandardButtons(QMessageBox.Ok)
|
2389
2502
|
msg.exec_()
|