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
@@ -87,9 +87,16 @@ class CutSubTool(SubTool):
|
|
87
87
|
self._update_cut_line_path(position)
|
88
88
|
|
89
89
|
def keyPressEvent(self, event):
|
90
|
-
"""Handle key press events for
|
91
|
-
|
90
|
+
"""Handle key press events for cutting operations."""
|
91
|
+
# Check for Ctrl+X to toggle cutting mode off
|
92
|
+
if event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_X:
|
92
93
|
self.parent_tool.deactivate_subtool()
|
94
|
+
return
|
95
|
+
|
96
|
+
# Handle Backspace to clear the current cutting line but stay in cutting mode
|
97
|
+
if event.key() == Qt.Key_Backspace:
|
98
|
+
self._clear_cutting_line()
|
99
|
+
return
|
93
100
|
|
94
101
|
def _start_drawing_cut_line(self, position):
|
95
102
|
"""Start drawing the cut line from the given position."""
|
@@ -115,6 +122,15 @@ class CutSubTool(SubTool):
|
|
115
122
|
path.lineTo(point)
|
116
123
|
self.cutting_path_item.setPath(path)
|
117
124
|
|
125
|
+
def _clear_cutting_line(self):
|
126
|
+
"""Clear the current cutting line but remain in cutting mode."""
|
127
|
+
self.cutting_points = []
|
128
|
+
self.drawing_in_progress = False
|
129
|
+
if self.cutting_path_item:
|
130
|
+
self.annotation_window.scene.removeItem(self.cutting_path_item)
|
131
|
+
self.cutting_path_item = None
|
132
|
+
self.annotation_window.scene.update()
|
133
|
+
|
118
134
|
def _break_apart_multipolygon(self):
|
119
135
|
"""Handle the special case of 'cutting' a MultiPolygonAnnotation."""
|
120
136
|
new_annotations = self.target_annotation.cut()
|
@@ -124,5 +124,22 @@ class ResizeSubTool(SubTool):
|
|
124
124
|
}
|
125
125
|
|
126
126
|
def _get_polygon_handles(self, annotation):
|
127
|
-
"""
|
128
|
-
|
127
|
+
"""
|
128
|
+
Return resize handles for a polygon, including its outer boundary and all holes.
|
129
|
+
Uses the new handle format: 'point_{poly_index}_{vertex_index}'.
|
130
|
+
"""
|
131
|
+
handles = {}
|
132
|
+
|
133
|
+
# 1. Create handles for the outer boundary using the 'outer' keyword.
|
134
|
+
for i, p in enumerate(annotation.points):
|
135
|
+
handle_name = f"point_outer_{i}"
|
136
|
+
handles[handle_name] = QPointF(p.x(), p.y())
|
137
|
+
|
138
|
+
# 2. Create handles for each of the inner holes using their index.
|
139
|
+
if hasattr(annotation, 'holes'):
|
140
|
+
for hole_index, hole in enumerate(annotation.holes):
|
141
|
+
for vertex_index, p in enumerate(hole):
|
142
|
+
handle_name = f"point_{hole_index}_{vertex_index}"
|
143
|
+
handles[handle_name] = QPointF(p.x(), p.y())
|
144
|
+
|
145
|
+
return handles
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import warnings
|
2
2
|
import numpy as np
|
3
3
|
|
4
|
-
from PyQt5.QtCore import Qt, QPointF, QRectF
|
4
|
+
from PyQt5.QtCore import Qt, QPointF, QRectF
|
5
5
|
from PyQt5.QtGui import QMouseEvent, QKeyEvent, QPen, QColor, QBrush, QPainterPath
|
6
6
|
from PyQt5.QtWidgets import QMessageBox, QGraphicsEllipseItem, QGraphicsRectItem, QGraphicsPathItem, QApplication
|
7
7
|
|
@@ -12,6 +12,7 @@ from coralnet_toolbox.QtWorkArea import WorkArea
|
|
12
12
|
|
13
13
|
from coralnet_toolbox.utilities import pixmap_to_numpy
|
14
14
|
from coralnet_toolbox.utilities import simplify_polygon
|
15
|
+
from coralnet_toolbox.utilities import polygonize_mask_with_holes
|
15
16
|
|
16
17
|
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
17
18
|
|
@@ -64,6 +65,11 @@ class SAMTool(Tool):
|
|
64
65
|
|
65
66
|
# Flag to track if we have active prompts
|
66
67
|
self.has_active_prompts = False
|
68
|
+
|
69
|
+
# Add state variables for custom working area creation
|
70
|
+
self.creating_working_area = False
|
71
|
+
self.working_area_start = None
|
72
|
+
self.working_area_temp_graphics = None
|
67
73
|
|
68
74
|
def activate(self):
|
69
75
|
"""
|
@@ -82,6 +88,7 @@ class SAMTool(Tool):
|
|
82
88
|
self.sam_dialog = None
|
83
89
|
self.cancel_working_area()
|
84
90
|
self.has_active_prompts = False
|
91
|
+
self.cancel_working_area_creation()
|
85
92
|
|
86
93
|
def set_working_area(self):
|
87
94
|
"""
|
@@ -141,7 +148,116 @@ class SAMTool(Tool):
|
|
141
148
|
|
142
149
|
self.annotation_window.setCursor(Qt.CrossCursor)
|
143
150
|
self.annotation_window.scene.update()
|
144
|
-
|
151
|
+
|
152
|
+
def set_custom_working_area(self, start_point, end_point):
|
153
|
+
"""
|
154
|
+
Create a working area from custom points selected by the user.
|
155
|
+
|
156
|
+
Args:
|
157
|
+
start_point (QPointF): First corner of the working area
|
158
|
+
end_point (QPointF): Opposite corner of the working area
|
159
|
+
"""
|
160
|
+
self.annotation_window.setCursor(Qt.WaitCursor)
|
161
|
+
|
162
|
+
# Cancel any existing working area
|
163
|
+
self.cancel_working_area()
|
164
|
+
|
165
|
+
# Calculate the rectangle bounds
|
166
|
+
left = max(0, int(min(start_point.x(), end_point.x())))
|
167
|
+
top = max(0, int(min(start_point.y(), end_point.y())))
|
168
|
+
right = min(int(self.annotation_window.pixmap_image.size().width()),
|
169
|
+
int(max(start_point.x(), end_point.x())))
|
170
|
+
bottom = min(int(self.annotation_window.pixmap_image.size().height()),
|
171
|
+
int(max(start_point.y(), end_point.y())))
|
172
|
+
|
173
|
+
# Ensure minimum size (at least 10x10 pixels)
|
174
|
+
if right - left < 10:
|
175
|
+
right = min(left + 10, int(self.annotation_window.pixmap_image.size().width()))
|
176
|
+
if bottom - top < 10:
|
177
|
+
bottom = min(top + 10, int(self.annotation_window.pixmap_image.size().height()))
|
178
|
+
|
179
|
+
# Original image information
|
180
|
+
self.image_path = self.annotation_window.current_image_path
|
181
|
+
self.original_image = pixmap_to_numpy(self.annotation_window.pixmap_image)
|
182
|
+
self.original_width = self.annotation_window.pixmap_image.size().width()
|
183
|
+
self.original_height = self.annotation_window.pixmap_image.size().height()
|
184
|
+
|
185
|
+
# Create the WorkArea instance
|
186
|
+
self.working_area = WorkArea(left, top, right - left, bottom - top, self.image_path)
|
187
|
+
|
188
|
+
# Get the thickness for the working area graphics
|
189
|
+
pen_width = self.graphics_utility.get_workarea_thickness(self.annotation_window)
|
190
|
+
|
191
|
+
# Create and add the working area graphics
|
192
|
+
self.working_area.create_graphics(self.annotation_window.scene, pen_width)
|
193
|
+
self.working_area.set_remove_button_visibility(False)
|
194
|
+
self.working_area.removed.connect(self.on_working_area_removed)
|
195
|
+
|
196
|
+
# Create shadow overlay
|
197
|
+
shadow_brush = QBrush(QColor(0, 0, 0, 150))
|
198
|
+
shadow_path = QPainterPath()
|
199
|
+
shadow_path.addRect(self.annotation_window.scene.sceneRect())
|
200
|
+
shadow_path.addRect(self.working_area.rect)
|
201
|
+
shadow_path = shadow_path.simplified()
|
202
|
+
|
203
|
+
self.shadow_area = QGraphicsPathItem(shadow_path)
|
204
|
+
self.shadow_area.setBrush(shadow_brush)
|
205
|
+
self.shadow_area.setPen(QPen(Qt.NoPen))
|
206
|
+
self.annotation_window.scene.addItem(self.shadow_area)
|
207
|
+
|
208
|
+
# Update the working area image in the SAM model
|
209
|
+
self.image = self.original_image[top:bottom, left:right]
|
210
|
+
self.sam_dialog.set_image(self.image, self.image_path)
|
211
|
+
|
212
|
+
self.annotation_window.setCursor(Qt.CrossCursor)
|
213
|
+
self.annotation_window.scene.update()
|
214
|
+
|
215
|
+
def display_working_area_preview(self, current_pos):
|
216
|
+
"""
|
217
|
+
Display a preview rectangle for the working area being created.
|
218
|
+
|
219
|
+
Args:
|
220
|
+
current_pos (QPointF): Current mouse position
|
221
|
+
"""
|
222
|
+
if not self.working_area_start:
|
223
|
+
return
|
224
|
+
|
225
|
+
# Remove previous preview if it exists
|
226
|
+
if self.working_area_temp_graphics:
|
227
|
+
self.annotation_window.scene.removeItem(self.working_area_temp_graphics)
|
228
|
+
self.working_area_temp_graphics = None
|
229
|
+
|
230
|
+
# Create preview rectangle
|
231
|
+
rect = QRectF(
|
232
|
+
min(self.working_area_start.x(), current_pos.x()),
|
233
|
+
min(self.working_area_start.y(), current_pos.y()),
|
234
|
+
abs(current_pos.x() - self.working_area_start.x()),
|
235
|
+
abs(current_pos.y() - self.working_area_start.y())
|
236
|
+
)
|
237
|
+
|
238
|
+
# Create a dashed blue pen for the working area preview
|
239
|
+
pen = QPen(QColor(0, 120, 215))
|
240
|
+
pen.setStyle(Qt.DashLine)
|
241
|
+
pen.setWidth(2)
|
242
|
+
|
243
|
+
self.working_area_temp_graphics = QGraphicsRectItem(rect)
|
244
|
+
self.working_area_temp_graphics.setPen(pen)
|
245
|
+
self.working_area_temp_graphics.setBrush(QBrush(QColor(0, 120, 215, 30))) # Light blue transparent fill
|
246
|
+
self.annotation_window.scene.addItem(self.working_area_temp_graphics)
|
247
|
+
|
248
|
+
def cancel_working_area_creation(self):
|
249
|
+
"""
|
250
|
+
Cancel the process of creating a working area.
|
251
|
+
"""
|
252
|
+
self.creating_working_area = False
|
253
|
+
self.working_area_start = None
|
254
|
+
|
255
|
+
if self.working_area_temp_graphics:
|
256
|
+
self.annotation_window.scene.removeItem(self.working_area_temp_graphics)
|
257
|
+
self.working_area_temp_graphics = None
|
258
|
+
|
259
|
+
self.annotation_window.scene.update()
|
260
|
+
|
145
261
|
def on_working_area_removed(self, work_area):
|
146
262
|
"""
|
147
263
|
Handle when the work area is removed via its internal mechanism.
|
@@ -254,40 +370,47 @@ class SAMTool(Tool):
|
|
254
370
|
QApplication.restoreOverrideCursor()
|
255
371
|
return
|
256
372
|
|
257
|
-
# Get the
|
373
|
+
# Get the top confidence prediction's mask tensor
|
258
374
|
top1_index = np.argmax(results.boxes.conf)
|
259
|
-
|
375
|
+
mask_tensor = results[top1_index].masks.data
|
260
376
|
|
261
|
-
#
|
262
|
-
|
263
|
-
QApplication.restoreOverrideCursor()
|
264
|
-
return
|
377
|
+
# Check if holes are allowed from the SAM dialog
|
378
|
+
allow_holes = self.sam_dialog.get_allow_holes()
|
265
379
|
|
266
|
-
#
|
267
|
-
|
380
|
+
# Polygonize the mask to get the exterior and holes
|
381
|
+
exterior_coords, holes_coords_list = polygonize_mask_with_holes(mask_tensor)
|
268
382
|
|
269
383
|
# Safety check: need at least 3 points for a valid polygon
|
270
|
-
if len(
|
384
|
+
if len(exterior_coords) < 3:
|
271
385
|
QApplication.restoreOverrideCursor()
|
272
386
|
return
|
273
387
|
|
274
|
-
#
|
388
|
+
# --- Process and Clean the Polygon Points ---
|
275
389
|
working_area_top_left = self.working_area.rect.topLeft()
|
276
|
-
|
277
|
-
point[1] + working_area_top_left.y()) for point in predictions]
|
390
|
+
offset_x, offset_y = working_area_top_left.x(), working_area_top_left.y()
|
278
391
|
|
279
|
-
#
|
280
|
-
|
392
|
+
# Simplify, offset, and convert the exterior points
|
393
|
+
simplified_exterior = simplify_polygon(exterior_coords, 0.1)
|
394
|
+
self.points = [QPointF(p[0] + offset_x, p[1] + offset_y) for p in simplified_exterior]
|
281
395
|
|
282
|
-
#
|
396
|
+
# Simplify, offset, and convert each hole only if allowed
|
397
|
+
final_holes = []
|
398
|
+
if allow_holes:
|
399
|
+
for hole_coords in holes_coords_list:
|
400
|
+
if len(hole_coords) >= 3: # Ensure holes are also valid polygons
|
401
|
+
simplified_hole = simplify_polygon(hole_coords, 0.1)
|
402
|
+
final_holes.append([QPointF(p[0] + offset_x, p[1] + offset_y) for p in simplified_hole])
|
403
|
+
|
404
|
+
# Create the temporary annotation, now with holes (or not)
|
283
405
|
self.temp_annotation = PolygonAnnotation(
|
284
|
-
self.points,
|
285
|
-
|
286
|
-
self.annotation_window.selected_label.
|
287
|
-
self.annotation_window.selected_label.
|
288
|
-
self.annotation_window.
|
289
|
-
self.annotation_window.
|
290
|
-
self.
|
406
|
+
points=self.points,
|
407
|
+
holes=final_holes,
|
408
|
+
short_label_code=self.annotation_window.selected_label.short_label_code,
|
409
|
+
long_label_code=self.annotation_window.selected_label.long_label_code,
|
410
|
+
color=self.annotation_window.selected_label.color,
|
411
|
+
image_path=self.annotation_window.current_image_path,
|
412
|
+
label_id=self.annotation_window.selected_label.id,
|
413
|
+
transparency=self.main_window.label_window.active_label.transparency
|
291
414
|
)
|
292
415
|
|
293
416
|
# Create the graphics item for the temporary annotation
|
@@ -357,11 +480,24 @@ class SAMTool(Tool):
|
|
357
480
|
"A label must be selected before adding an annotation.")
|
358
481
|
return
|
359
482
|
|
360
|
-
if not self.working_area:
|
361
|
-
return
|
362
|
-
|
363
483
|
# Get position in scene coordinates
|
364
484
|
scene_pos = self.annotation_window.mapToScene(event.pos())
|
485
|
+
|
486
|
+
# Handle working area creation mode
|
487
|
+
if not self.working_area and event.button() == Qt.LeftButton:
|
488
|
+
if not self.creating_working_area:
|
489
|
+
# Start working area creation
|
490
|
+
self.creating_working_area = True
|
491
|
+
self.working_area_start = scene_pos
|
492
|
+
return
|
493
|
+
elif self.creating_working_area and self.working_area_start:
|
494
|
+
# Finish working area creation
|
495
|
+
self.set_custom_working_area(self.working_area_start, scene_pos)
|
496
|
+
self.cancel_working_area_creation()
|
497
|
+
return
|
498
|
+
|
499
|
+
if not self.working_area:
|
500
|
+
return
|
365
501
|
|
366
502
|
# Check if position is within working area
|
367
503
|
if not self.working_area.contains_point(scene_pos):
|
@@ -430,12 +566,17 @@ class SAMTool(Tool):
|
|
430
566
|
"""
|
431
567
|
Handle mouse move events.
|
432
568
|
"""
|
433
|
-
if not self.working_area:
|
434
|
-
return
|
435
|
-
|
436
569
|
scene_pos = self.annotation_window.mapToScene(event.pos())
|
437
570
|
self.hover_pos = scene_pos
|
438
571
|
|
572
|
+
# Update working area preview during creation
|
573
|
+
if self.creating_working_area and self.working_area_start:
|
574
|
+
self.display_working_area_preview(scene_pos)
|
575
|
+
return
|
576
|
+
|
577
|
+
if not self.working_area:
|
578
|
+
return
|
579
|
+
|
439
580
|
# Update rectangle during drawing
|
440
581
|
if self.drawing_rectangle and self.start_point:
|
441
582
|
self.end_point = scene_pos
|
@@ -461,6 +602,11 @@ class SAMTool(Tool):
|
|
461
602
|
Handle key press events.
|
462
603
|
"""
|
463
604
|
if event.key() == Qt.Key_Space:
|
605
|
+
# If creating working area, confirm it
|
606
|
+
if self.creating_working_area and self.working_area_start and self.hover_pos:
|
607
|
+
self.set_custom_working_area(self.working_area_start, self.hover_pos)
|
608
|
+
self.cancel_working_area_creation()
|
609
|
+
return
|
464
610
|
|
465
611
|
# If no working area, set it up
|
466
612
|
if not self.working_area:
|
@@ -473,12 +619,13 @@ class SAMTool(Tool):
|
|
473
619
|
# Use existing temporary annotation
|
474
620
|
final_annotation = PolygonAnnotation(
|
475
621
|
self.points,
|
476
|
-
self.
|
477
|
-
self.
|
478
|
-
self.
|
479
|
-
self.
|
480
|
-
self.
|
481
|
-
self.
|
622
|
+
self.temp_annotation.label.short_label_code,
|
623
|
+
self.temp_annotation.label.long_label_code,
|
624
|
+
self.temp_annotation.label.color,
|
625
|
+
self.temp_annotation.image_path,
|
626
|
+
self.temp_annotation.label.id,
|
627
|
+
self.temp_annotation.label.transparency,
|
628
|
+
holes=self.temp_annotation.holes
|
482
629
|
)
|
483
630
|
|
484
631
|
# Copy confidence data
|
@@ -499,7 +646,7 @@ class SAMTool(Tool):
|
|
499
646
|
final_annotation = self.create_annotation(True)
|
500
647
|
if final_annotation:
|
501
648
|
self.annotation_window.add_annotation_from_tool(final_annotation)
|
502
|
-
self.clear_prompt_graphics()
|
649
|
+
self.clear_prompt_graphics()
|
503
650
|
# If no active prompts, cancel the working area
|
504
651
|
else:
|
505
652
|
self.cancel_working_area()
|
@@ -507,6 +654,11 @@ class SAMTool(Tool):
|
|
507
654
|
self.annotation_window.scene.update()
|
508
655
|
|
509
656
|
elif event.key() == Qt.Key_Backspace:
|
657
|
+
# If creating working area, cancel it
|
658
|
+
if self.creating_working_area:
|
659
|
+
self.cancel_working_area_creation()
|
660
|
+
return
|
661
|
+
|
510
662
|
# If drawing rectangle, cancel it
|
511
663
|
if self.drawing_rectangle:
|
512
664
|
self.cancel_rectangle_drawing()
|
@@ -584,24 +736,36 @@ class SAMTool(Tool):
|
|
584
736
|
QApplication.restoreOverrideCursor()
|
585
737
|
return None
|
586
738
|
|
587
|
-
# Get the top confidence prediction
|
739
|
+
# Get the top confidence prediction's mask tensor
|
588
740
|
top1_index = np.argmax(results.boxes.conf)
|
589
|
-
|
741
|
+
mask_tensor = results[top1_index].masks.data
|
742
|
+
|
743
|
+
# Check if holes are allowed from the SAM dialog
|
744
|
+
allow_holes = self.sam_dialog.get_allow_holes()
|
590
745
|
|
591
|
-
#
|
592
|
-
|
746
|
+
# Polygonize the mask using the new method to get the exterior and holes
|
747
|
+
exterior_coords, holes_coords_list = polygonize_mask_with_holes(mask_tensor)
|
748
|
+
|
749
|
+
# Safety check for an empty result
|
750
|
+
if not exterior_coords:
|
593
751
|
QApplication.restoreOverrideCursor()
|
594
752
|
return None
|
595
753
|
|
596
|
-
# Clean
|
597
|
-
predictions = simplify_polygon(predictions, 0.1)
|
598
|
-
|
599
|
-
# Move points back to original image space
|
754
|
+
# --- Process and Clean the Polygon Points ---
|
600
755
|
working_area_top_left = self.working_area.rect.topLeft()
|
601
|
-
|
602
|
-
|
603
|
-
#
|
604
|
-
|
756
|
+
offset_x, offset_y = working_area_top_left.x(), working_area_top_left.y()
|
757
|
+
|
758
|
+
# Simplify, offset, and convert the exterior points
|
759
|
+
simplified_exterior = simplify_polygon(exterior_coords, 0.1)
|
760
|
+
self.points = [QPointF(p[0] + offset_x, p[1] + offset_y) for p in simplified_exterior]
|
761
|
+
|
762
|
+
# Simplify, offset, and convert each hole only if allowed
|
763
|
+
final_holes = []
|
764
|
+
if allow_holes:
|
765
|
+
for hole_coords in holes_coords_list:
|
766
|
+
if len(hole_coords) >= 3:
|
767
|
+
simplified_hole = simplify_polygon(hole_coords, 0.1)
|
768
|
+
final_holes.append([QPointF(p[0] + offset_x, p[1] + offset_y) for p in simplified_hole])
|
605
769
|
|
606
770
|
# Require at least 3 points for valid polygon
|
607
771
|
if len(self.points) < 3:
|
@@ -611,15 +775,16 @@ class SAMTool(Tool):
|
|
611
775
|
# Get confidence score
|
612
776
|
confidence = results.boxes.conf[top1_index].item()
|
613
777
|
|
614
|
-
# Create final annotation
|
778
|
+
# Create final annotation, now passing the holes argument
|
615
779
|
annotation = PolygonAnnotation(
|
616
|
-
self.points,
|
617
|
-
|
618
|
-
self.annotation_window.selected_label.
|
619
|
-
self.annotation_window.selected_label.
|
620
|
-
self.annotation_window.
|
621
|
-
self.annotation_window.
|
622
|
-
self.
|
780
|
+
points=self.points,
|
781
|
+
holes=final_holes,
|
782
|
+
short_label_code=self.annotation_window.selected_label.short_label_code,
|
783
|
+
long_label_code=self.annotation_window.selected_label.long_label_code,
|
784
|
+
color=self.annotation_window.selected_label.color,
|
785
|
+
image_path=self.annotation_window.current_image_path,
|
786
|
+
label_id=self.annotation_window.selected_label.id,
|
787
|
+
transparency=self.main_window.label_window.active_label.transparency
|
623
788
|
)
|
624
789
|
|
625
790
|
# Update confidence
|