coralnet-toolbox 0.0.70__py2.py3-none-any.whl → 0.0.72__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/QtRectangleAnnotation.py +31 -2
- coralnet_toolbox/Explorer/QtDataItem.py +52 -20
- coralnet_toolbox/Explorer/QtExplorer.py +536 -293
- coralnet_toolbox/Explorer/QtFeatureStore.py +15 -0
- coralnet_toolbox/Explorer/QtSettingsWidgets.py +3 -3
- coralnet_toolbox/Icons/target.png +0 -0
- coralnet_toolbox/MachineLearning/TrainModel/QtBase.py +57 -76
- coralnet_toolbox/QtAnnotationWindow.py +10 -2
- coralnet_toolbox/QtMainWindow.py +60 -0
- coralnet_toolbox/Tools/QtResizeSubTool.py +6 -1
- coralnet_toolbox/Tools/QtSelectTool.py +48 -6
- coralnet_toolbox/__init__.py +1 -1
- {coralnet_toolbox-0.0.70.dist-info → coralnet_toolbox-0.0.72.dist-info}/METADATA +2 -2
- {coralnet_toolbox-0.0.70.dist-info → coralnet_toolbox-0.0.72.dist-info}/RECORD +18 -17
- {coralnet_toolbox-0.0.70.dist-info → coralnet_toolbox-0.0.72.dist-info}/WHEEL +0 -0
- {coralnet_toolbox-0.0.70.dist-info → coralnet_toolbox-0.0.72.dist-info}/entry_points.txt +0 -0
- {coralnet_toolbox-0.0.70.dist-info → coralnet_toolbox-0.0.72.dist-info}/licenses/LICENSE.txt +0 -0
- {coralnet_toolbox-0.0.70.dist-info → coralnet_toolbox-0.0.72.dist-info}/top_level.txt +0 -0
@@ -149,6 +149,21 @@ class FeatureStore:
|
|
149
149
|
index_path = f"{self.index_path_base}_{model_key}.faiss"
|
150
150
|
print(f"Saving FAISS index for '{model_key}' to {index_path}")
|
151
151
|
faiss.write_index(index_to_save, index_path)
|
152
|
+
|
153
|
+
def remove_features_for_annotation(self, annotation_id):
|
154
|
+
"""
|
155
|
+
Removes an annotation's feature metadata from the SQLite database.
|
156
|
+
This effectively orphans the vector in the FAISS index, invalidating it.
|
157
|
+
"""
|
158
|
+
try:
|
159
|
+
self.cursor.execute(
|
160
|
+
"DELETE FROM features WHERE annotation_id = ?",
|
161
|
+
(annotation_id,)
|
162
|
+
)
|
163
|
+
self.conn.commit()
|
164
|
+
print(f"Invalidated features for annotation_id: {annotation_id}")
|
165
|
+
except sqlite3.Error as e:
|
166
|
+
print(f"Error removing feature for annotation {annotation_id}: {e}")
|
152
167
|
|
153
168
|
def close(self):
|
154
169
|
"""Closes the database connection."""
|
@@ -670,8 +670,8 @@ class EmbeddingSettingsWidget(QGroupBox):
|
|
670
670
|
|
671
671
|
def apply_embedding(self):
|
672
672
|
if self.explorer_window and hasattr(self.explorer_window, 'run_embedding_pipeline'):
|
673
|
-
# Clear all selections before running embedding pipeline
|
674
|
-
if hasattr(self.explorer_window, '
|
675
|
-
self.explorer_window.
|
673
|
+
# Clear all selections before running a new embedding pipeline.
|
674
|
+
if hasattr(self.explorer_window, '_clear_selections'):
|
675
|
+
self.explorer_window._clear_selections()
|
676
676
|
|
677
677
|
self.explorer_window.run_embedding_pipeline()
|
Binary file
|
@@ -660,6 +660,10 @@ class Base(QDialog):
|
|
660
660
|
QMessageBox.warning(self, "Import Warning", "The YAML file appears to be empty or invalid.")
|
661
661
|
return
|
662
662
|
|
663
|
+
# For backward compatibility, check if the old nested 'parameters' key exists.
|
664
|
+
# If not, use the whole data dictionary.
|
665
|
+
params_to_load = data.get('parameters', data)
|
666
|
+
|
663
667
|
# Helper function to infer type from value
|
664
668
|
def infer_type_and_value(value):
|
665
669
|
"""
|
@@ -673,12 +677,9 @@ class Base(QDialog):
|
|
673
677
|
elif isinstance(value, float):
|
674
678
|
return "float", value
|
675
679
|
elif isinstance(value, str):
|
676
|
-
# Check for boolean strings
|
677
680
|
if value.lower() in ['true', 'false']:
|
678
681
|
return "bool", value.lower() == 'true'
|
679
|
-
# Check for numeric strings
|
680
682
|
try:
|
681
|
-
# Try to convert to int first
|
682
683
|
if '.' not in value:
|
683
684
|
return "int", int(value)
|
684
685
|
else:
|
@@ -686,14 +687,13 @@ class Base(QDialog):
|
|
686
687
|
except ValueError:
|
687
688
|
return "string", value
|
688
689
|
else:
|
689
|
-
# For any other type, convert to string
|
690
690
|
return "string", str(value)
|
691
691
|
|
692
692
|
# Clear existing custom parameters before importing
|
693
693
|
while self.custom_params:
|
694
694
|
self.remove_parameter_pair()
|
695
695
|
|
696
|
-
# Map parameters to UI controls
|
696
|
+
# Map standard parameters to their UI controls
|
697
697
|
param_mapping = {
|
698
698
|
'epochs': self.epochs_spinbox,
|
699
699
|
'patience': self.patience_spinbox,
|
@@ -712,37 +712,31 @@ class Base(QDialog):
|
|
712
712
|
}
|
713
713
|
|
714
714
|
# Update UI controls with imported values
|
715
|
-
for param_name, value in
|
715
|
+
for param_name, value in params_to_load.items():
|
716
716
|
param_type, converted_value = infer_type_and_value(value)
|
717
717
|
|
718
718
|
if param_name in param_mapping:
|
719
719
|
widget = param_mapping[param_name]
|
720
720
|
|
721
721
|
if isinstance(widget, QSpinBox):
|
722
|
-
if
|
722
|
+
if isinstance(converted_value, (int, float)):
|
723
723
|
widget.setValue(int(converted_value))
|
724
724
|
elif isinstance(widget, QDoubleSpinBox):
|
725
|
-
if
|
725
|
+
if isinstance(converted_value, (int, float)):
|
726
726
|
widget.setValue(float(converted_value))
|
727
727
|
elif isinstance(widget, QComboBox):
|
728
728
|
if param_name in ['multi_scale', 'save', 'weighted', 'val', 'verbose']:
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
else:
|
733
|
-
# String parameters like optimizer
|
734
|
-
if str(converted_value) in [widget.itemText(i) for i in range(widget.count())]:
|
735
|
-
widget.setCurrentText(str(converted_value))
|
729
|
+
widget.setCurrentText("True" if converted_value else "False")
|
730
|
+
elif str(converted_value) in [widget.itemText(i) for i in range(widget.count())]:
|
731
|
+
widget.setCurrentText(str(converted_value))
|
736
732
|
else:
|
737
|
-
# Add as custom parameter
|
733
|
+
# Add as a custom parameter
|
738
734
|
self.add_parameter_pair()
|
739
|
-
|
740
|
-
param_name_widget, param_value_widget, param_type_widget = param_widgets
|
735
|
+
param_name_widget, param_value_widget, param_type_widget = self.custom_params[-1]
|
741
736
|
|
742
737
|
param_name_widget.setText(param_name)
|
743
738
|
param_type_widget.setCurrentText(param_type)
|
744
739
|
|
745
|
-
# Set value based on type
|
746
740
|
if param_type == "bool":
|
747
741
|
param_value_widget.setText("True" if converted_value else "False")
|
748
742
|
else:
|
@@ -750,14 +744,14 @@ class Base(QDialog):
|
|
750
744
|
|
751
745
|
QMessageBox.information(self,
|
752
746
|
"Import Success",
|
753
|
-
"Parameters successfully imported with automatic type inference")
|
747
|
+
"Parameters successfully imported with automatic type inference.")
|
754
748
|
|
755
749
|
except Exception as e:
|
756
750
|
QMessageBox.critical(self, "Import Error", f"Failed to import parameters: {str(e)}")
|
757
751
|
|
758
752
|
def export_parameters(self):
|
759
753
|
"""
|
760
|
-
Export current parameters to a YAML file
|
754
|
+
Export current parameters to a flat YAML file.
|
761
755
|
"""
|
762
756
|
file_path, _ = QFileDialog.getSaveFileName(self,
|
763
757
|
"Export Parameters to YAML",
|
@@ -767,69 +761,56 @@ class Base(QDialog):
|
|
767
761
|
return
|
768
762
|
|
769
763
|
try:
|
770
|
-
#
|
771
|
-
export_data = {
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
'weighted': ('bool', self.weighted_combo.currentText() == "True"),
|
789
|
-
'val': ('bool', self.val_combo.currentText() == "True"),
|
790
|
-
'verbose': ('bool', self.verbose_combo.currentText() == "True"),
|
791
|
-
'optimizer': ('string', self.optimizer_combo.currentText())
|
792
|
-
}
|
793
|
-
|
794
|
-
# Add standard parameters
|
795
|
-
for param_name, (param_type, value) in standard_params.items():
|
796
|
-
export_data['types'][param_name] = param_type
|
797
|
-
export_data['parameters'][param_name] = value
|
764
|
+
# Use a single flat dictionary for export
|
765
|
+
export_data = {}
|
766
|
+
|
767
|
+
# Standard parameters
|
768
|
+
export_data['epochs'] = self.epochs_spinbox.value()
|
769
|
+
export_data['patience'] = self.patience_spinbox.value()
|
770
|
+
export_data['imgsz'] = self.imgsz_spinbox.value()
|
771
|
+
export_data['batch'] = self.batch_spinbox.value()
|
772
|
+
export_data['workers'] = self.workers_spinbox.value()
|
773
|
+
export_data['save_period'] = self.save_period_spinbox.value()
|
774
|
+
export_data['freeze_layers'] = self.freeze_layers_spinbox.value()
|
775
|
+
export_data['dropout'] = self.dropout_spinbox.value()
|
776
|
+
export_data['multi_scale'] = self.multi_scale_combo.currentText() == "True"
|
777
|
+
export_data['save'] = self.save_combo.currentText() == "True"
|
778
|
+
export_data['weighted'] = self.weighted_combo.currentText() == "True"
|
779
|
+
export_data['val'] = self.val_combo.currentText() == "True"
|
780
|
+
export_data['verbose'] = self.verbose_combo.currentText() == "True"
|
781
|
+
export_data['optimizer'] = self.optimizer_combo.currentText()
|
798
782
|
|
799
783
|
# Custom parameters
|
800
784
|
for param_info in self.custom_params:
|
801
|
-
|
802
|
-
name =
|
803
|
-
|
804
|
-
type_name =
|
785
|
+
param_name_widget, param_value_widget, param_type_widget = param_info
|
786
|
+
name = param_name_widget.text().strip()
|
787
|
+
value_str = param_value_widget.text().strip()
|
788
|
+
type_name = param_type_widget.currentText()
|
805
789
|
|
806
|
-
if name and
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
export_data['parameters'][name] = value
|
825
|
-
|
826
|
-
# Write to YAML file
|
790
|
+
if name and value_str:
|
791
|
+
# Convert value to the correct type before exporting
|
792
|
+
try:
|
793
|
+
if type_name == "bool":
|
794
|
+
value = value_str.lower() == "true"
|
795
|
+
elif type_name == "int":
|
796
|
+
value = int(value_str)
|
797
|
+
elif type_name == "float":
|
798
|
+
value = float(value_str)
|
799
|
+
else: # string type
|
800
|
+
value = value_str
|
801
|
+
export_data[name] = value
|
802
|
+
except ValueError:
|
803
|
+
# If conversion fails, save it as a string
|
804
|
+
print(f"Warning: Could not convert '{value_str}' to {type_name} for parameter '{name}'. Saving as string.")
|
805
|
+
export_data[name] = value_str
|
806
|
+
|
807
|
+
# Write the flat dictionary to the YAML file
|
827
808
|
with open(file_path, 'w') as f:
|
828
|
-
yaml.dump(export_data, f, default_flow_style=False, indent=2)
|
809
|
+
yaml.dump(export_data, f, default_flow_style=False, sort_keys=False, indent=2)
|
829
810
|
|
830
811
|
QMessageBox.information(self,
|
831
812
|
"Export Success",
|
832
|
-
"Parameters successfully exported")
|
813
|
+
"Parameters successfully exported.")
|
833
814
|
|
834
815
|
except Exception as e:
|
835
816
|
QMessageBox.critical(self,
|
@@ -48,6 +48,7 @@ class AnnotationWindow(QGraphicsView):
|
|
48
48
|
annotationSelected = pyqtSignal(int) # Signal to emit when annotation is selected
|
49
49
|
annotationDeleted = pyqtSignal(str) # Signal to emit when annotation is deleted
|
50
50
|
annotationCreated = pyqtSignal(str) # Signal to emit when annotation is created
|
51
|
+
annotationModified = pyqtSignal(str) # Signal to emit when annotation is modified
|
51
52
|
|
52
53
|
def __init__(self, main_window, parent=None):
|
53
54
|
"""Initialize the annotation window with the main window and parent widget."""
|
@@ -374,6 +375,9 @@ class AnnotationWindow(QGraphicsView):
|
|
374
375
|
|
375
376
|
def set_image(self, image_path):
|
376
377
|
"""Set and display an image at the given path."""
|
378
|
+
# Calculate GDIs for Windows if needed
|
379
|
+
self.main_window.check_windows_gdi_count()
|
380
|
+
|
377
381
|
# Clean up
|
378
382
|
self.clear_scene()
|
379
383
|
|
@@ -543,7 +547,7 @@ class AnnotationWindow(QGraphicsView):
|
|
543
547
|
return type(self.selected_annotations[0])
|
544
548
|
return None
|
545
549
|
|
546
|
-
def select_annotation(self, annotation, multi_select=False):
|
550
|
+
def select_annotation(self, annotation, multi_select=False, quiet_mode=False):
|
547
551
|
"""Select an annotation and update the UI accordingly."""
|
548
552
|
# If the annotation is already selected and Ctrl is pressed, unselect it
|
549
553
|
if annotation in self.selected_annotations and multi_select:
|
@@ -569,7 +573,11 @@ class AnnotationWindow(QGraphicsView):
|
|
569
573
|
|
570
574
|
# If this is the only selected annotation, update label window and confidence window
|
571
575
|
if len(self.selected_annotations) == 1:
|
572
|
-
|
576
|
+
|
577
|
+
if not quiet_mode:
|
578
|
+
# Emit the label selected signal, unless in quiet mode.
|
579
|
+
# This is in Explorer to avoid overwriting preview label.
|
580
|
+
self.labelSelected.emit(annotation.label.id)
|
573
581
|
|
574
582
|
# Make sure we have a cropped image
|
575
583
|
if not annotation.cropped_image:
|
coralnet_toolbox/QtMainWindow.py
CHANGED
@@ -4,6 +4,7 @@ warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
4
4
|
|
5
5
|
import os
|
6
6
|
import re
|
7
|
+
import ctypes
|
7
8
|
import requests
|
8
9
|
|
9
10
|
from packaging import version
|
@@ -130,6 +131,9 @@ class MainWindow(QMainWindow):
|
|
130
131
|
|
131
132
|
def __init__(self, __version__):
|
132
133
|
super().__init__()
|
134
|
+
|
135
|
+
# Get the process ID
|
136
|
+
self.pid = os.getpid()
|
133
137
|
|
134
138
|
# Define icons
|
135
139
|
self.coral_icon = get_icon("coral.png")
|
@@ -2329,6 +2333,62 @@ class MainWindow(QMainWindow):
|
|
2329
2333
|
msg.exec_()
|
2330
2334
|
except Exception as e:
|
2331
2335
|
QMessageBox.critical(self, "Critical Error", f"{e}")
|
2336
|
+
|
2337
|
+
def check_windows_gdi_count(self):
|
2338
|
+
"""Calculate and print the number of GDI objects for the current process on Windows."""
|
2339
|
+
# 1. Check if the OS is Windows. If not, return early.
|
2340
|
+
if os.name != 'nt':
|
2341
|
+
return
|
2342
|
+
|
2343
|
+
# Load necessary libraries
|
2344
|
+
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
|
2345
|
+
user32 = ctypes.WinDLL('user32', use_last_error=True)
|
2346
|
+
|
2347
|
+
# Define constants
|
2348
|
+
PROCESS_QUERY_INFORMATION = 0x0400
|
2349
|
+
GR_GDIOBJECTS = 0
|
2350
|
+
|
2351
|
+
process_handle = None
|
2352
|
+
try:
|
2353
|
+
# 2. Get a handle to the process from its PID
|
2354
|
+
process_handle = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, self.pid)
|
2355
|
+
|
2356
|
+
if not process_handle:
|
2357
|
+
error_code = ctypes.get_last_error()
|
2358
|
+
raise RuntimeError(f"Failed to open PID {self.pid}. Error code: {error_code}")
|
2359
|
+
|
2360
|
+
# 3. Use the handle to get the GDI object count
|
2361
|
+
gdi_count = user32.GetGuiResources(process_handle, GR_GDIOBJECTS)
|
2362
|
+
|
2363
|
+
if gdi_count >= 9500: # GDI limit
|
2364
|
+
self.show_gdi_limit_warning()
|
2365
|
+
|
2366
|
+
except Exception as e:
|
2367
|
+
pass
|
2368
|
+
|
2369
|
+
finally:
|
2370
|
+
# 4. CRITICAL: Always close the handle when you're done
|
2371
|
+
if process_handle:
|
2372
|
+
kernel32.CloseHandle(process_handle)
|
2373
|
+
|
2374
|
+
return
|
2375
|
+
|
2376
|
+
def show_gdi_limit_warning(self):
|
2377
|
+
"""
|
2378
|
+
Show a warning dialog if the GDI limit is reached.
|
2379
|
+
"""
|
2380
|
+
try:
|
2381
|
+
self.untoggle_all_tools()
|
2382
|
+
msg = QMessageBox()
|
2383
|
+
msg.setWindowIcon(self.coral_icon)
|
2384
|
+
msg.setWindowTitle("GDI Limit Reached")
|
2385
|
+
msg.setText(
|
2386
|
+
"The GDI limit has been reached! Please immediately save your work, close, and reopen the application!"
|
2387
|
+
)
|
2388
|
+
msg.setStandardButtons(QMessageBox.Ok)
|
2389
|
+
msg.exec_()
|
2390
|
+
except Exception as e:
|
2391
|
+
QMessageBox.critical(self, "Critical Error", f"{e}")
|
2332
2392
|
|
2333
2393
|
def open_snake_game_dialog(self):
|
2334
2394
|
"""
|
@@ -59,9 +59,14 @@ class ResizeSubTool(SubTool):
|
|
59
59
|
def mouseReleaseEvent(self, event):
|
60
60
|
"""Finalize the resize, update related windows, and deactivate."""
|
61
61
|
if self.target_annotation:
|
62
|
+
# Normalize the coordinates after resize is complete
|
63
|
+
if hasattr(self.target_annotation, 'normalize_coordinates'):
|
64
|
+
self.target_annotation.normalize_coordinates()
|
65
|
+
|
62
66
|
self.target_annotation.create_cropped_image(self.annotation_window.rasterio_image)
|
63
67
|
self.parent_tool.main_window.confidence_window.display_cropped_image(self.target_annotation)
|
64
|
-
|
68
|
+
self.annotation_window.annotationModified.emit(self.target_annotation.id) # Emit modified signal
|
69
|
+
|
65
70
|
self.parent_tool.deactivate_subtool()
|
66
71
|
|
67
72
|
# --- Handle Management Logic (moved from original class) ---
|
@@ -49,6 +49,7 @@ class SelectTool(Tool):
|
|
49
49
|
|
50
50
|
# --- State for transient UI (like resize handles) ---
|
51
51
|
self.resize_handles_visible = False
|
52
|
+
self.selection_locked = False
|
52
53
|
|
53
54
|
self._connect_signals()
|
54
55
|
|
@@ -81,16 +82,36 @@ class SelectTool(Tool):
|
|
81
82
|
self.deactivate_subtool()
|
82
83
|
self._hide_resize_handles()
|
83
84
|
self.annotation_window.viewport().setCursor(self.cursor)
|
85
|
+
self.selection_locked = False
|
84
86
|
|
85
87
|
def deactivate(self):
|
86
88
|
self.deactivate_subtool()
|
87
89
|
self._hide_resize_handles()
|
88
90
|
self.annotation_window.viewport().setCursor(self.default_cursor)
|
91
|
+
self.selection_locked = False
|
89
92
|
super().deactivate()
|
90
93
|
|
91
94
|
# --- Event Handlers (Dispatcher Logic) ---
|
92
95
|
|
93
96
|
def mousePressEvent(self, event: QMouseEvent):
|
97
|
+
if self.selection_locked:
|
98
|
+
# If selection is locked, only allow interaction with resize handles.
|
99
|
+
# Check if a handle was clicked to start a resize operation.
|
100
|
+
position = self.annotation_window.mapToScene(event.pos())
|
101
|
+
items = self.annotation_window.scene.items(position)
|
102
|
+
if self.resize_handles_visible:
|
103
|
+
for item in items:
|
104
|
+
if item in self.resize_subtool.resize_handles_items:
|
105
|
+
handle_name = item.data(1)
|
106
|
+
if handle_name and len(self.selected_annotations) == 1:
|
107
|
+
self.set_active_subtool(
|
108
|
+
self.resize_subtool, event,
|
109
|
+
annotation=self.selected_annotations[0],
|
110
|
+
handle_name=handle_name
|
111
|
+
)
|
112
|
+
return # Exit after starting resize
|
113
|
+
return # Otherwise, ignore the click entirely
|
114
|
+
|
94
115
|
# Ignore right mouse button events (used for panning)
|
95
116
|
if event.button() == Qt.RightButton:
|
96
117
|
return
|
@@ -215,14 +236,35 @@ class SelectTool(Tool):
|
|
215
236
|
return self.annotation_window.annotations_dict.get(annotation_id) if annotation_id else None
|
216
237
|
|
217
238
|
def _get_annotation_from_items(self, items, position):
|
218
|
-
"""
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
239
|
+
"""
|
240
|
+
Finds the first valid annotation at a position from a list of items.
|
241
|
+
First prioritizes annotations where the center graphic contains the point,
|
242
|
+
then falls back to any annotation that contains the point.
|
243
|
+
"""
|
244
|
+
# Filter out resize handles
|
245
|
+
valid_items = [item for item in items if item not in self.resize_subtool.resize_handles_items]
|
246
|
+
|
247
|
+
center_threshold = 10.0 # Distance threshold in pixels to consider a click "on center"
|
248
|
+
center_candidates = []
|
249
|
+
general_candidates = []
|
250
|
+
|
251
|
+
# Gather all potential candidates
|
252
|
+
for item in valid_items:
|
223
253
|
annotation = self._get_annotation_from_item(item)
|
224
254
|
if annotation and annotation.contains_point(position):
|
225
|
-
|
255
|
+
# Calculate distance to center
|
256
|
+
center_distance = (position - annotation.center_xy).manhattanLength()
|
257
|
+
if center_distance <= center_threshold:
|
258
|
+
center_candidates.append(annotation)
|
259
|
+
else:
|
260
|
+
general_candidates.append(annotation)
|
261
|
+
|
262
|
+
# Return priority: center candidates first, then general candidates
|
263
|
+
if center_candidates:
|
264
|
+
return center_candidates[0]
|
265
|
+
elif general_candidates:
|
266
|
+
return general_candidates[0]
|
267
|
+
|
226
268
|
return None
|
227
269
|
|
228
270
|
def _handle_annotation_selection(self, position, items, modifiers):
|
coralnet_toolbox/__init__.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: coralnet-toolbox
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.72
|
4
4
|
Summary: Tools for annotating and developing ML models for benthic imagery
|
5
5
|
Author-email: Jordan Pierce <jordan.pierce@noaa.gov>
|
6
6
|
License: MIT License
|
@@ -179,7 +179,7 @@ Enhance your CoralNet experience with these tools:
|
|
179
179
|
- 🚀 Optimize: Productionize models for faster inferencing
|
180
180
|
- ⚙️ Batch Inference: Perform predictions on multiple images, automatically
|
181
181
|
- 🎞️ Video Inference: Perform predictions on a video in real-time, record the output and analytics
|
182
|
-
- 🔮 Explorer: Cluster, view, and re-label annotations using embeddings, mapped from feature-space
|
182
|
+
- 🔮 [Explorer](https://youtu.be/68eZt5l_7nA): Cluster, view, and re-label annotations using embeddings, mapped from feature-space
|
183
183
|
- ↔️ I/O: Import and Export annotations from / to CoralNet, Viscore, and TagLab
|
184
184
|
- Export annotations as [GeoJSONs](https://datatracker.ietf.org/doc/html/rfc7946), segmentation masks
|
185
185
|
- 📸 YOLO: Import and Export YOLO datasets for machine learning
|
@@ -1,20 +1,20 @@
|
|
1
|
-
coralnet_toolbox/QtAnnotationWindow.py,sha256=
|
1
|
+
coralnet_toolbox/QtAnnotationWindow.py,sha256=ZlaYYAMNqu95SJhduQxH1K0YP7mOkMv3BzGQwemfByg,39518
|
2
2
|
coralnet_toolbox/QtConfidenceWindow.py,sha256=L5hR23uW91GpqnsNS9R1XF3zCTe2aU7w0iDoQMV0oyE,16190
|
3
3
|
coralnet_toolbox/QtEventFilter.py,sha256=KKC9de3e66PvGVgiML8P7MZ9-r7vvHidPJJYpcbTwyM,6696
|
4
4
|
coralnet_toolbox/QtImageWindow.py,sha256=vLziMSEWFfVRSBN0nUNkosgk3LiNxZDqPwbinz9ZivQ,49356
|
5
5
|
coralnet_toolbox/QtLabelWindow.py,sha256=-4GCk4pTY9g4ADH1iE__4xwqT-7UR_7VCT8v-bJzerk,50869
|
6
|
-
coralnet_toolbox/QtMainWindow.py,sha256=
|
6
|
+
coralnet_toolbox/QtMainWindow.py,sha256=z_Erak0fJC4x962rsFgK3IwjLQYVUe6rayzQzfJtkhw,114372
|
7
7
|
coralnet_toolbox/QtPatchSampling.py,sha256=Ehj06auBGfQwIruLNYQjF8eFOCpl8G72p42UXXb2mUo,29013
|
8
8
|
coralnet_toolbox/QtProgressBar.py,sha256=pnozUOcVjfO_yTS9z8wOMPcrrrOtG_FeCknTcdI6eyk,6250
|
9
9
|
coralnet_toolbox/QtWorkArea.py,sha256=YXRvHQKpWUtWyv_o9lZ8rmxfm28dUOG9pmMUeimDhQ4,13578
|
10
|
-
coralnet_toolbox/__init__.py,sha256
|
10
|
+
coralnet_toolbox/__init__.py,sha256=-OIPFY1gy8COaSsB29_2bWyVd3ro4BAdKgI2eIvHhPY,207
|
11
11
|
coralnet_toolbox/main.py,sha256=6j2B_1reC_KDmqvq1C0fB-UeSEm8eeJOozp2f4XXMLQ,1573
|
12
12
|
coralnet_toolbox/utilities.py,sha256=eUkxXuWaNFH83LSW-KniwujkXKJ2rK04czx3k3OPiAY,27115
|
13
13
|
coralnet_toolbox/Annotations/QtAnnotation.py,sha256=-3ASbjl1dXw9U731vyCgwyiyZT9zOD5Mvp1jt-7bCnA,29242
|
14
14
|
coralnet_toolbox/Annotations/QtMultiPolygonAnnotation.py,sha256=ErAT31gw-zhEVNxkPRpyB9uw-NSpPh-ShCBxpscXdRw,15579
|
15
15
|
coralnet_toolbox/Annotations/QtPatchAnnotation.py,sha256=67fNnK_-muyhGZdGB0kBDx-JGuflv1TM6q5ikfW_zOk,20076
|
16
16
|
coralnet_toolbox/Annotations/QtPolygonAnnotation.py,sha256=1EkZEJlO4VZ4so01Sat2T8LeO1LNs7HbGJLO-G2_73Q,26886
|
17
|
-
coralnet_toolbox/Annotations/QtRectangleAnnotation.py,sha256=
|
17
|
+
coralnet_toolbox/Annotations/QtRectangleAnnotation.py,sha256=TgeawekA3jNBlCmZBRqXYRHoZqch7pWM-NSBBPG6S60,21404
|
18
18
|
coralnet_toolbox/Annotations/__init__.py,sha256=bpMldC70tT_lzMrOdBNDkEhG9dCX3tXEBd48IrcUg3E,419
|
19
19
|
coralnet_toolbox/AutoDistill/QtBatchInference.py,sha256=k871aW3XRX8kc4BDaS1aipbPh9WOZxgmilF2c4KOdVA,5646
|
20
20
|
coralnet_toolbox/AutoDistill/QtDeployModel.py,sha256=6alhzvA3KYEeLaQj-Qhs9GicjNQyVoQbnvgZ3lxGnCU,25162
|
@@ -35,10 +35,10 @@ coralnet_toolbox/Common/QtUpdateImagePaths.py,sha256=_hJYx6hXdAOfH_m77f75AQduQ0W
|
|
35
35
|
coralnet_toolbox/CoralNet/QtAuthenticate.py,sha256=Y__iY0Kcosz6AOV7dlJBwiB6Hte40wHahHe-OmRngZA,13267
|
36
36
|
coralnet_toolbox/CoralNet/QtDownload.py,sha256=HBb8TpZRIEFirGIaIAV1v8qg3fL4cP6Bf-hUiqXoiLE,48516
|
37
37
|
coralnet_toolbox/CoralNet/__init__.py,sha256=ILkAZh6mlAK1UaCCZjCB9JZxd-oY4cIgfnIC8UgjjIU,188
|
38
|
-
coralnet_toolbox/Explorer/QtDataItem.py,sha256
|
39
|
-
coralnet_toolbox/Explorer/QtExplorer.py,sha256=
|
40
|
-
coralnet_toolbox/Explorer/QtFeatureStore.py,sha256=
|
41
|
-
coralnet_toolbox/Explorer/QtSettingsWidgets.py,sha256=
|
38
|
+
coralnet_toolbox/Explorer/QtDataItem.py,sha256=fNpCHJSxMzHL2XpBXtPwKchSbmY7H0HWzL1Kbs4W1Ts,14920
|
39
|
+
coralnet_toolbox/Explorer/QtExplorer.py,sha256=vAtGTNsEUcy14-_XJuOocH7xdAlesHm5faN9AINaEvg,131591
|
40
|
+
coralnet_toolbox/Explorer/QtFeatureStore.py,sha256=3VwGezs1stmu65Z4ZQpvY27rGEIJq_prERWkFwMATBo,7378
|
41
|
+
coralnet_toolbox/Explorer/QtSettingsWidgets.py,sha256=unm23yP329cVL84aOvy20DBt3YBHVLU85rnfN9VUF8A,27649
|
42
42
|
coralnet_toolbox/Explorer/__init__.py,sha256=wZPhf2oaUUyIQ2WK48Aj-4q1ENIZG2dGl1HF_mjhI6w,116
|
43
43
|
coralnet_toolbox/IO/QtExportAnnotations.py,sha256=xeaS0BukC3cpkBIGT9DXRqHmvHhp-vOU47h6EoANpNg,4474
|
44
44
|
coralnet_toolbox/IO/QtExportCoralNetAnnotations.py,sha256=4royhF63EmeOlSIBX389EUjjvE-SF44_maW6qm52mdA,2778
|
@@ -89,6 +89,7 @@ coralnet_toolbox/Icons/rocket.png,sha256=iMlRGlrNBS_dNBD2XIpN4RSrphCGbw_Ds1AYJ01
|
|
89
89
|
coralnet_toolbox/Icons/select.png,sha256=twnMIO9ylQYjvyGnAR28V6K3ds6xpArZQTrvf0uxS6g,1896
|
90
90
|
coralnet_toolbox/Icons/settings.png,sha256=rklROt3oKrfEk_qwN9J-JwvKok08iOkZy3OD4oNsLJQ,1376
|
91
91
|
coralnet_toolbox/Icons/snake.png,sha256=cwcekSkXwDi_fhtTU48u7FN4bIybbY53cWK0n7-IN9A,2361
|
92
|
+
coralnet_toolbox/Icons/target.png,sha256=jzb-S_sXWT8MfbvefhDNsuTdAZgV2nGf1ieawaCkByM,1702
|
92
93
|
coralnet_toolbox/Icons/tile.png,sha256=WiXKBpWVBfPv7gC8dnkc_gW3wuLQmLUyxYMWEM-G9ZU,382
|
93
94
|
coralnet_toolbox/Icons/transparent.png,sha256=ZkuGkVzh6zLVNau1Wj166-TtUlbCRqJObGt4vxMxnLk,1098
|
94
95
|
coralnet_toolbox/Icons/turtle.png,sha256=55OG5atmEs8nIUiN2B5hW-Jx1fpuY9QI-zolQoUOKWw,1971
|
@@ -147,7 +148,7 @@ coralnet_toolbox/MachineLearning/MergeDatasets/QtClassify.py,sha256=FI4WdxJ4-vtn
|
|
147
148
|
coralnet_toolbox/MachineLearning/MergeDatasets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
148
149
|
coralnet_toolbox/MachineLearning/OptimizeModel/QtBase.py,sha256=06irheL8aKvtwKBQLLJUohvWvrMqKFC-jhEEoVqIYdg,8890
|
149
150
|
coralnet_toolbox/MachineLearning/OptimizeModel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
150
|
-
coralnet_toolbox/MachineLearning/TrainModel/QtBase.py,sha256=
|
151
|
+
coralnet_toolbox/MachineLearning/TrainModel/QtBase.py,sha256=f__pgcG8yyV4IQbICED4q4YWO6GnNtD5b5UGpWuv4c0,36580
|
151
152
|
coralnet_toolbox/MachineLearning/TrainModel/QtClassify.py,sha256=ss5ppGbrpULzUPmeRmfqZjiqZPp7XbdUZ4BzSX0ehu0,3267
|
152
153
|
coralnet_toolbox/MachineLearning/TrainModel/QtDetect.py,sha256=uxopZkrNkl3tImMNSDwC2ENpFAxdG0NLiwRwqNnbep0,4467
|
153
154
|
coralnet_toolbox/MachineLearning/TrainModel/QtSegment.py,sha256=y8bpNS24SQxyg967RSi6TraqHSmlJYj8kbvC_5HMBIM,3597
|
@@ -205,19 +206,19 @@ coralnet_toolbox/Tools/QtPanTool.py,sha256=q0g5Ryse6mIZ_Ss4qJw5NNwgoLuQQBIyQTXNF
|
|
205
206
|
coralnet_toolbox/Tools/QtPatchTool.py,sha256=57vFeR2jQ_VQRlMEIC_mH8NigUqOlVvmhaVkXDvd_Gw,5574
|
206
207
|
coralnet_toolbox/Tools/QtPolygonTool.py,sha256=yxnkwK3rb52pWCq7a3iAABhHUSS_a3vkL7G7Ev0uLDA,9174
|
207
208
|
coralnet_toolbox/Tools/QtRectangleTool.py,sha256=gYOOsn1WRHLG0YzkKmmM7OzLpuLNh8GWIZ4MloXoLDc,7218
|
208
|
-
coralnet_toolbox/Tools/QtResizeSubTool.py,sha256
|
209
|
+
coralnet_toolbox/Tools/QtResizeSubTool.py,sha256=derPy4adRj758-xYtjL-_35yGBOjoSe_DRE48HpdQpA,5836
|
209
210
|
coralnet_toolbox/Tools/QtSAMTool.py,sha256=TQ--xcR76lymFS0YVo5Gi4ay_tIsIEecYpLDMRBPLWQ,26174
|
210
211
|
coralnet_toolbox/Tools/QtSeeAnythingTool.py,sha256=2uxyX_chOIXcmW6oBlb6XlgbRmSwSaQXmkmgOtFrqI4,30606
|
211
212
|
coralnet_toolbox/Tools/QtSelectSubTool.py,sha256=xKtXYCwezLq3YZQLsSTG3mxs_ZRjLiPrYl-0ebgq-GA,3125
|
212
|
-
coralnet_toolbox/Tools/QtSelectTool.py,sha256=
|
213
|
+
coralnet_toolbox/Tools/QtSelectTool.py,sha256=rSzM9s7pMxrLqvcWgIcEnpEQhYHU6TbGUna8ZaamakA,19957
|
213
214
|
coralnet_toolbox/Tools/QtSubTool.py,sha256=H25FoFqywdi6Bl35MfpEXGrr48ZTgdRRvHMxUy1tqN4,1601
|
214
215
|
coralnet_toolbox/Tools/QtTool.py,sha256=2MCjT151gYBN8KbsK0GX4WOrEg1uw3oeSkp7Elw1AUA,2531
|
215
216
|
coralnet_toolbox/Tools/QtWorkAreaTool.py,sha256=-CDrEPenOdSI3sf5wn19Cip4alE1ef7WsRDxQFDkHlc,22162
|
216
217
|
coralnet_toolbox/Tools/QtZoomTool.py,sha256=F9CAoABv1jxcUS7dyIh1FYjgjOXYRI1xtBPNIR1g62o,4041
|
217
218
|
coralnet_toolbox/Tools/__init__.py,sha256=218iQ8IFXIkKXiUDVYtXk9e08UY9-LhHjcryaJAanQ0,797
|
218
|
-
coralnet_toolbox-0.0.
|
219
|
-
coralnet_toolbox-0.0.
|
220
|
-
coralnet_toolbox-0.0.
|
221
|
-
coralnet_toolbox-0.0.
|
222
|
-
coralnet_toolbox-0.0.
|
223
|
-
coralnet_toolbox-0.0.
|
219
|
+
coralnet_toolbox-0.0.72.dist-info/licenses/LICENSE.txt,sha256=AURacZ_G_PZKqqPQ9VB9Sqegblk67RNgWSGAYKwXXMY,521
|
220
|
+
coralnet_toolbox-0.0.72.dist-info/METADATA,sha256=cOB6wtxUBZcPWkW0L9v3LCWIsI_NOaaiKo9RyQJl15M,18152
|
221
|
+
coralnet_toolbox-0.0.72.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
|
222
|
+
coralnet_toolbox-0.0.72.dist-info/entry_points.txt,sha256=oEeMoDlJ_2lq95quOeDHIx9hZpubUlSo80OLtgbcrbM,63
|
223
|
+
coralnet_toolbox-0.0.72.dist-info/top_level.txt,sha256=SMWPh4_9JfB8zVpPOOvjucV2_B_hvWW7bNWmMjG0LsY,17
|
224
|
+
coralnet_toolbox-0.0.72.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{coralnet_toolbox-0.0.70.dist-info → coralnet_toolbox-0.0.72.dist-info}/licenses/LICENSE.txt
RENAMED
File without changes
|
File without changes
|