coralnet-toolbox 0.0.72__py2.py3-none-any.whl → 0.0.73__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 (35) hide show
  1. coralnet_toolbox/AutoDistill/QtDeployModel.py +23 -12
  2. coralnet_toolbox/Explorer/QtDataItem.py +1 -1
  3. coralnet_toolbox/Explorer/QtExplorer.py +143 -3
  4. coralnet_toolbox/Explorer/QtSettingsWidgets.py +46 -4
  5. coralnet_toolbox/MachineLearning/DeployModel/QtDetect.py +22 -11
  6. coralnet_toolbox/MachineLearning/DeployModel/QtSegment.py +22 -10
  7. coralnet_toolbox/MachineLearning/ExportDataset/QtBase.py +61 -24
  8. coralnet_toolbox/MachineLearning/ExportDataset/QtClassify.py +5 -1
  9. coralnet_toolbox/MachineLearning/ExportDataset/QtDetect.py +19 -6
  10. coralnet_toolbox/MachineLearning/ExportDataset/QtSegment.py +21 -8
  11. coralnet_toolbox/QtAnnotationWindow.py +42 -14
  12. coralnet_toolbox/QtEventFilter.py +8 -2
  13. coralnet_toolbox/QtImageWindow.py +17 -18
  14. coralnet_toolbox/QtLabelWindow.py +1 -1
  15. coralnet_toolbox/QtMainWindow.py +143 -8
  16. coralnet_toolbox/Rasters/QtRaster.py +59 -7
  17. coralnet_toolbox/Rasters/RasterTableModel.py +34 -6
  18. coralnet_toolbox/SAM/QtBatchInference.py +0 -2
  19. coralnet_toolbox/SAM/QtDeployGenerator.py +22 -11
  20. coralnet_toolbox/SeeAnything/QtBatchInference.py +19 -221
  21. coralnet_toolbox/SeeAnything/QtDeployGenerator.py +1016 -0
  22. coralnet_toolbox/SeeAnything/QtDeployPredictor.py +69 -53
  23. coralnet_toolbox/SeeAnything/QtTrainModel.py +115 -45
  24. coralnet_toolbox/SeeAnything/__init__.py +2 -0
  25. coralnet_toolbox/Tools/QtSAMTool.py +150 -7
  26. coralnet_toolbox/Tools/QtSeeAnythingTool.py +220 -55
  27. coralnet_toolbox/Tools/QtSelectSubTool.py +6 -4
  28. coralnet_toolbox/Tools/QtWorkAreaTool.py +25 -13
  29. coralnet_toolbox/__init__.py +1 -1
  30. {coralnet_toolbox-0.0.72.dist-info → coralnet_toolbox-0.0.73.dist-info}/METADATA +1 -1
  31. {coralnet_toolbox-0.0.72.dist-info → coralnet_toolbox-0.0.73.dist-info}/RECORD +35 -34
  32. {coralnet_toolbox-0.0.72.dist-info → coralnet_toolbox-0.0.73.dist-info}/WHEEL +0 -0
  33. {coralnet_toolbox-0.0.72.dist-info → coralnet_toolbox-0.0.73.dist-info}/entry_points.txt +0 -0
  34. {coralnet_toolbox-0.0.72.dist-info → coralnet_toolbox-0.0.73.dist-info}/licenses/LICENSE.txt +0 -0
  35. {coralnet_toolbox-0.0.72.dist-info → coralnet_toolbox-0.0.73.dist-info}/top_level.txt +0 -0
@@ -89,7 +89,7 @@ class RasterTableModel(QAbstractTableModel):
89
89
 
90
90
  elif role == Qt.TextAlignmentRole:
91
91
  return Qt.AlignCenter
92
-
92
+
93
93
  elif role == Qt.FontRole:
94
94
  # Bold the selected raster's text
95
95
  if raster.is_selected:
@@ -106,12 +106,40 @@ class RasterTableModel(QAbstractTableModel):
106
106
 
107
107
  elif role == Qt.ToolTipRole:
108
108
  if index.column() == self.FILENAME_COL:
109
- # Include full path and metadata in tooltip
110
109
  dimensions = raster.metadata.get('dimensions', f"{raster.width}x{raster.height}")
111
- return (f"Path: {path}\n"
112
- f"Dimensions: {dimensions}\n"
113
- f"Has Annotations: {'Yes' if raster.has_annotations else 'No'}\n"
114
- f"Has Predictions: {'Yes' if raster.has_predictions else 'No'}")
110
+
111
+ tooltip_parts = [
112
+ f"<b>Path:</b> {path}",
113
+ f"<b>Dimensions:</b> {dimensions}",
114
+ f"<b>Annotations:</b> {'Yes' if raster.has_annotations else 'No'}",
115
+ f"<b>Predictions:</b> {'Yes' if raster.has_predictions else 'No'}"
116
+ ]
117
+
118
+ if raster.has_work_areas():
119
+ tooltip_parts.append(f"<b>Work Areas:</b> {raster.count_work_items()}")
120
+
121
+ return "<br>".join(tooltip_parts)
122
+
123
+ elif index.column() == self.ANNOTATION_COUNT_COL and raster.annotation_count > 0:
124
+ tooltip_text = f"<b>Total annotations:</b> {raster.annotation_count}"
125
+
126
+ # Add annotation counts per label using a for loop
127
+ if hasattr(raster, 'label_counts') and raster.label_counts:
128
+ label_items = []
129
+ for label, count in raster.label_counts.items():
130
+ label_items.append(f"<li>{label}: {count}</li>")
131
+ label_counts_text = "".join(label_items)
132
+ tooltip_text += f"<br><br><b>Annotations by label:</b><ul>{label_counts_text}</ul>"
133
+
134
+ # Add annotation counts per type using a for loop
135
+ if hasattr(raster, 'annotation_types') and raster.annotation_types:
136
+ type_items = []
137
+ for type_name, count in raster.annotation_types.items():
138
+ type_items.append(f"<li>{type_name}: {count}</li>")
139
+ type_counts_text = "".join(type_items)
140
+ tooltip_text += f"<br><b>Annotations by type:</b><ul>{type_counts_text}</ul>"
141
+
142
+ return tooltip_text
115
143
 
116
144
  return None
117
145
 
@@ -35,8 +35,6 @@ class BatchInferenceDialog(QDialog):
35
35
  self.deploy_model_dialog = None
36
36
  self.loaded_model = None
37
37
 
38
- self.annotations = []
39
- self.prepared_patches = []
40
38
  self.image_paths = []
41
39
 
42
40
  self.layout = QVBoxLayout(self)
@@ -384,18 +384,29 @@ class DeployGeneratorDialog(QDialog):
384
384
 
385
385
  def update_sam_task_state(self):
386
386
  """
387
- Centralized method to check if SAM is loaded and update task and dropdown accordingly.
387
+ Centralized method to check if SAM is loaded and update task accordingly.
388
+ If the user has selected to use SAM, this function ensures the task is set to 'segment'.
389
+ Crucially, it does NOT alter the task if SAM is not selected, respecting the
390
+ user's choice from the 'Task' dropdown.
388
391
  """
389
- sam_active = (
390
- self.sam_dialog is not None and
391
- self.sam_dialog.loaded_model is not None and
392
- self.use_sam_dropdown.currentText() == "True"
393
- )
394
- if sam_active:
395
- self.task = 'segment'
396
- else:
397
- self.task = 'detect'
398
- self.use_sam_dropdown.setCurrentText("False")
392
+ # Check if the user wants to use the SAM model
393
+ if self.use_sam_dropdown.currentText() == "True":
394
+ # SAM is requested. Check if it's actually available.
395
+ sam_is_available = (
396
+ hasattr(self, 'sam_dialog') and
397
+ self.sam_dialog is not None and
398
+ self.sam_dialog.loaded_model is not None
399
+ )
400
+
401
+ if sam_is_available:
402
+ # If SAM is wanted and available, the task must be segmentation.
403
+ self.task = 'segment'
404
+ else:
405
+ # If SAM is wanted but not available, revert the dropdown and do nothing else.
406
+ # The 'is_sam_model_deployed' function already handles showing an error message.
407
+ self.use_sam_dropdown.setCurrentText("False")
408
+
409
+ # If use_sam_dropdown is "False", do nothing. Let self.task be whatever the user set.
399
410
 
400
411
  def load_model(self):
401
412
  """
@@ -40,23 +40,17 @@ class BatchInferenceDialog(QDialog):
40
40
 
41
41
  self.setWindowIcon(get_icon("eye.png"))
42
42
  self.setWindowTitle("Batch Inference")
43
- self.resize(600, 100)
43
+ self.resize(400, 100)
44
44
 
45
- self.deploy_model_dialog = None
45
+ self.deploy_generator_dialog = None
46
46
  self.loaded_model = None
47
47
 
48
- # Reference image and label
49
- self.source_image_path = None
50
- self.source_label = None
51
- # Target images
52
- self.target_images = []
48
+ self.image_paths = []
53
49
 
54
50
  self.layout = QVBoxLayout(self)
55
51
 
56
52
  # Setup the info layout
57
53
  self.setup_info_layout()
58
- # Setup the source layout
59
- self.setup_source_layout()
60
54
  # Setup the image options layout
61
55
  self.setup_options_layout()
62
56
  # Setup the buttons layout
@@ -69,11 +63,8 @@ class BatchInferenceDialog(QDialog):
69
63
  :param event: Show event
70
64
  """
71
65
  super().showEvent(event)
72
- self.deploy_model_dialog = self.main_window.see_anything_deploy_predictor_dialog
73
- self.loaded_model = self.deploy_model_dialog.loaded_model
74
-
75
- # Update the source images (now assuming sources are valid)
76
- self.update_source_images()
66
+ self.deploy_generator_dialog = self.main_window.see_anything_deploy_generator_dialog
67
+ self.loaded_model = self.deploy_generator_dialog.loaded_model
77
68
 
78
69
  def setup_info_layout(self):
79
70
  """
@@ -92,26 +83,6 @@ class BatchInferenceDialog(QDialog):
92
83
  group_box.setLayout(layout)
93
84
  self.layout.addWidget(group_box)
94
85
 
95
- def setup_source_layout(self):
96
- """
97
- Set up the layout with source image and label selection.
98
- Contains dropdown combo boxes for selecting the source image and label.
99
- """
100
- group_box = QGroupBox("Source Selection")
101
- layout = QFormLayout()
102
-
103
- # Create the source image combo box
104
- self.source_image_combo_box = QComboBox()
105
- self.source_image_combo_box.currentIndexChanged.connect(self.update_source_labels)
106
- layout.addRow("Source Image:", self.source_image_combo_box)
107
-
108
- # Create the source label combo box
109
- self.source_label_combo_box = QComboBox()
110
- layout.addRow("Source Label:", self.source_label_combo_box)
111
-
112
- group_box.setLayout(layout)
113
- self.layout.addWidget(group_box)
114
-
115
86
  def setup_options_layout(self):
116
87
  """
117
88
  Set up the layout with image options.
@@ -159,202 +130,37 @@ class BatchInferenceDialog(QDialog):
159
130
 
160
131
  self.layout.addWidget(button_box)
161
132
 
162
- def has_valid_sources(self):
163
- """
164
- Check if there are any valid source images with polygon or rectangle annotations.
165
-
166
- :return: True if valid sources exist, False otherwise
167
- """
168
- # Check if there are any images
169
- if not self.image_window.raster_manager.image_paths:
170
- QMessageBox.information(None,
171
- "No Images",
172
- "No images available for batch inference.")
173
- return False
174
-
175
- # Check for images with valid annotations
176
- for image_path in self.image_window.raster_manager.image_paths:
177
- # Get annotations for this image
178
- annotations = self.annotation_window.get_image_annotations(image_path)
179
-
180
- # Check if there's at least one valid polygon/rectangle annotation
181
- for annotation in annotations:
182
- if isinstance(annotation, PolygonAnnotation) or isinstance(annotation, RectangleAnnotation):
183
- return True
184
-
185
- QMessageBox.information(None,
186
- "No Valid Annotations",
187
- "No images have polygon or rectangle annotations for batch inference.")
188
- return False
189
-
190
- def check_valid_sources(self):
191
- """
192
- Check if there are any valid source images with polygon or rectangle annotations.
193
-
194
- :return: True if valid sources exist, False otherwise
195
- """
196
- # Check if there are any images
197
- if not self.image_window.raster_manager.image_paths:
198
- QMessageBox.information(self,
199
- "No Images",
200
- "No images available for batch inference.")
201
- return False
202
-
203
- # Check for images with valid annotations
204
- for image_path in self.image_window.raster_manager.image_paths:
205
- # Get annotations for this image
206
- annotations = self.annotation_window.get_image_annotations(image_path)
207
-
208
- # Check if there's at least one valid polygon/rectangle annotation
209
- for annotation in annotations:
210
- if isinstance(annotation, PolygonAnnotation) or isinstance(annotation, RectangleAnnotation):
211
- return True
212
-
213
- QMessageBox.information(self,
214
- "No Valid Annotations",
215
- "No images have polygon or rectangle annotations for batch inference.")
216
- return False
217
-
218
- def update_source_images(self):
219
- """
220
- Updates the source image combo box with images that have at least one label
221
- with a valid polygon or rectangle annotation.
222
-
223
- :return: True if valid source images were found, False otherwise
224
- """
225
- self.source_image_combo_box.clear()
226
- valid_images_found = False
227
-
228
- # Get all image paths from the raster_manager
229
- for image_path in self.image_window.raster_manager.image_paths:
230
- # Get annotations for this image
231
- annotations = self.annotation_window.get_image_annotations(image_path)
232
-
233
- # Check if there's at least one valid polygon/rectangle annotation
234
- valid_annotation_found = False
235
- for annotation in annotations:
236
- if isinstance(annotation, PolygonAnnotation) or isinstance(annotation, RectangleAnnotation):
237
- valid_annotation_found = True
238
- break
239
-
240
- if valid_annotation_found:
241
- # Get the basename (filename)
242
- basename = os.path.basename(image_path)
243
- # Add item to combo box with full path as data
244
- self.source_image_combo_box.addItem(basename, image_path)
245
- valid_images_found = True
246
-
247
- if not valid_images_found:
248
- QMessageBox.information(self,
249
- "No Source Images",
250
- "No images available for batch inference.")
251
- # Close the dialog since batch inference can't proceed
252
- QApplication.processEvents() # Process pending events
253
- self.reject()
254
- return False
255
-
256
- # Update the combo box to have the selected image first
257
- if self.annotation_window.current_image_path in self.image_window.raster_manager.image_paths:
258
- self.source_image_combo_box.setCurrentText(os.path.basename(self.annotation_window.current_image_path))
259
-
260
- # Update the source labels given changes in the source images
261
- return self.update_source_labels()
262
-
263
- def update_source_labels(self):
264
- """
265
- Updates the source label combo box with labels that have at least one
266
- polygon or rectangle annotation from the current image.
267
-
268
- :return: True if valid source labels were found, False otherwise
269
- """
270
- self.source_label_combo_box.clear()
271
-
272
- source_image_path = self.source_image_combo_box.currentData()
273
- if not source_image_path:
274
- return False
275
-
276
- # Get annotations for this image
277
- annotations = self.annotation_window.get_image_annotations(source_image_path)
278
-
279
- # Create a dict of labels with valid annotations
280
- valid_labels = {}
281
- for annotation in annotations:
282
- if isinstance(annotation, PolygonAnnotation) or isinstance(annotation, RectangleAnnotation):
283
- valid_labels[annotation.label.short_label_code] = annotation.label
284
-
285
- # Add valid labels to combo box
286
- for label_code, label_obj in valid_labels.items():
287
- self.source_label_combo_box.addItem(label_code, label_obj)
288
-
289
- if not valid_labels:
290
- QMessageBox.information(self,
291
- "No Valid Labels",
292
- "No labels with polygon or rectangle annotations available for batch inference.")
293
- # Close the dialog since batch inference can't proceed
294
- QApplication.processEvents() # Process pending events
295
- self.reject()
296
- return False
297
-
298
- return True
299
-
300
- def get_source_annotations(self):
301
- """Return a list of polygon and rectangle annotations for the
302
- source image belonging to the selected label."""
303
- source_image_path = self.source_image_combo_box.currentData()
304
- source_label = self.source_label_combo_box.currentData()
305
-
306
- # Get annotations for this image
307
- annotations = self.annotation_window.get_image_annotations(source_image_path)
308
-
309
- # Filter annotations by label
310
- source_annotations = []
311
- for annotation in annotations:
312
- if annotation.label.short_label_code == source_label.short_label_code:
313
- source_annotations.append(annotation.cropped_bbox)
314
-
315
- return np.array(source_annotations)
316
-
317
133
  def get_selected_image_paths(self):
318
134
  """
319
135
  Get the selected image paths based on the options.
320
- Excludes the source image path if present.
321
-
136
+
322
137
  :return: List of selected image paths
323
138
  """
324
- # Get the source image path to exclude
325
- source_image_path = self.source_image_combo_box.currentData()
326
-
327
139
  # Current image path showing
328
140
  current_image_path = self.annotation_window.current_image_path
329
141
  if not current_image_path:
330
142
  return []
331
-
143
+
332
144
  # Determine which images to export annotations for
333
145
  if self.apply_filtered_checkbox.isChecked():
334
- selected_paths = self.image_window.table_model.filtered_paths.copy()
146
+ return self.image_window.table_model.filtered_paths
335
147
  elif self.apply_prev_checkbox.isChecked():
336
148
  if current_image_path in self.image_window.table_model.filtered_paths:
337
149
  current_index = self.image_window.table_model.get_row_for_path(current_image_path)
338
- selected_paths = self.image_window.table_model.filtered_paths[:current_index + 1].copy()
150
+ return self.image_window.table_model.filtered_paths[:current_index + 1]
339
151
  else:
340
- selected_paths = [current_image_path]
152
+ return [current_image_path]
341
153
  elif self.apply_next_checkbox.isChecked():
342
154
  if current_image_path in self.image_window.table_model.filtered_paths:
343
155
  current_index = self.image_window.table_model.get_row_for_path(current_image_path)
344
- selected_paths = self.image_window.table_model.filtered_paths[current_index:].copy()
156
+ return self.image_window.table_model.filtered_paths[current_index:]
345
157
  else:
346
- selected_paths = [current_image_path]
158
+ return [current_image_path]
347
159
  elif self.apply_all_checkbox.isChecked():
348
- selected_paths = self.image_window.raster_manager.image_paths.copy()
160
+ return self.image_window.raster_manager.image_paths
349
161
  else:
350
162
  # Only apply to the current image
351
- selected_paths = [current_image_path]
352
-
353
- # Remove the source image path if it's in the selected paths
354
- if source_image_path and source_image_path in selected_paths:
355
- selected_paths.remove(source_image_path)
356
-
357
- return selected_paths
163
+ return [current_image_path]
358
164
 
359
165
  def apply(self):
360
166
  """
@@ -364,14 +170,8 @@ class BatchInferenceDialog(QDialog):
364
170
  QApplication.setOverrideCursor(Qt.WaitCursor)
365
171
 
366
172
  try:
367
- # Get the source image path and label
368
- self.source_image_path = self.source_image_combo_box.currentData()
369
- self.source_label = self.source_label_combo_box.currentData()
370
- # Get the source annotations
371
- self.source_annotations = self.get_source_annotations()
372
173
  # Get the selected image paths
373
- self.target_images = self.get_selected_image_paths()
374
- # Perform batch inference
174
+ self.image_paths = self.get_selected_image_paths()
375
175
  self.batch_inference()
376
176
 
377
177
  except Exception as e:
@@ -385,17 +185,15 @@ class BatchInferenceDialog(QDialog):
385
185
  def batch_inference(self):
386
186
  """
387
187
  Perform batch inference on the selected images.
388
-
389
188
  """
390
189
  # Make predictions on each image's annotations
391
190
  progress_bar = ProgressBar(self, title="Batch Inference")
392
191
  progress_bar.show()
393
- progress_bar.start_progress(len(self.target_images))
192
+ progress_bar.start_progress(len(self.image_paths))
394
193
 
395
194
  if self.loaded_model is not None:
396
- self.deploy_model_dialog.predict_from_annotations(refer_image=self.source_image_path,
397
- refer_label=self.source_label,
398
- refer_annotations=self.source_annotations,
399
- target_images=self.target_images)
195
+ self.deploy_generator_dialog.predict(image_paths=self.image_paths)
196
+
400
197
  progress_bar.stop_progress()
401
198
  progress_bar.close()
199
+