Updating annotations

This commit is contained in:
2025-12-08 23:59:44 +02:00
parent fc22479621
commit 710b684456
3 changed files with 257 additions and 0 deletions

View File

@@ -369,6 +369,102 @@ class AnnotationCanvasWidget(QWidget):
"""Get all drawn strokes with metadata."""
return self.all_strokes
def compute_annotation_bounds(self) -> Optional[Tuple[float, float, float, float]]:
"""
Compute bounding box that encompasses all annotation strokes.
Returns:
Tuple of (x_min, y_min, x_max, y_max) in normalized coordinates (0-1),
or None if no annotations exist.
"""
if not self.all_strokes:
return None
# Find min/max across all strokes
all_x = []
all_y = []
for stroke in self.all_strokes:
for x, y in stroke["points"]:
all_x.append(x)
all_y.append(y)
if not all_x:
return None
x_min = min(all_x)
y_min = min(all_y)
x_max = max(all_x)
y_max = max(all_y)
return (x_min, y_min, x_max, y_max)
def get_annotation_polyline(self) -> List[List[float]]:
"""
Get polyline coordinates representing all annotation strokes.
Returns:
List of [x, y] coordinate pairs in normalized coordinates (0-1).
"""
polyline = []
for stroke in self.all_strokes:
polyline.extend(stroke["points"])
return polyline
def draw_saved_polyline(
self, polyline: List[List[float]], color: str, width: int = 3
):
"""
Draw a polyline from database coordinates onto the annotation canvas.
Args:
polyline: List of [x, y] coordinate pairs in normalized coordinates (0-1)
color: Color hex string (e.g., '#FF0000')
width: Line width in pixels
"""
if not self.annotation_pixmap or not self.original_pixmap:
logger.warning("Cannot draw polyline: no image loaded")
return
if len(polyline) < 2:
logger.warning("Polyline has less than 2 points, cannot draw")
return
# Convert normalized coordinates to image coordinates
img_coords = []
for x_norm, y_norm in polyline:
x = int(x_norm * self.original_pixmap.width())
y = int(y_norm * self.original_pixmap.height())
img_coords.append((x, y))
# Draw polyline on annotation pixmap
painter = QPainter(self.annotation_pixmap)
pen_color = QColor(color)
pen_color.setAlpha(128) # Add semi-transparency
pen = QPen(pen_color, width, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
painter.setPen(pen)
# Draw lines between consecutive points
for i in range(len(img_coords) - 1):
x1, y1 = img_coords[i]
x2, y2 = img_coords[i + 1]
painter.drawLine(x1, y1, x2, y2)
painter.end()
# Store in all_strokes for consistency
self.all_strokes.append(
{"points": polyline, "color": color, "alpha": 128, "width": width}
)
# Update display
self._update_display()
logger.debug(
f"Drew saved polyline with {len(polyline)} points in color {color}"
)
def keyPressEvent(self, event: QKeyEvent):
"""Handle keyboard events for zooming."""
if event.key() in (Qt.Key_Plus, Qt.Key_Equal):

View File

@@ -51,6 +51,8 @@ class AnnotationToolsWidget(QWidget):
pen_width_changed = Signal(int)
class_selected = Signal(dict)
clear_annotations_requested = Signal()
process_annotations_requested = Signal()
show_annotations_requested = Signal()
def __init__(self, db_manager: DatabaseManager, parent=None):
"""
@@ -146,6 +148,20 @@ class AnnotationToolsWidget(QWidget):
actions_group = QGroupBox("Actions")
actions_layout = QVBoxLayout()
self.process_btn = QPushButton("Process Annotations")
self.process_btn.clicked.connect(self._on_process_annotations)
self.process_btn.setStyleSheet(
"QPushButton { background-color: #2196F3; color: white; font-weight: bold; }"
)
actions_layout.addWidget(self.process_btn)
self.show_btn = QPushButton("Show Saved Annotations")
self.show_btn.clicked.connect(self._on_show_annotations)
self.show_btn.setStyleSheet(
"QPushButton { background-color: #4CAF50; color: white; }"
)
actions_layout.addWidget(self.show_btn)
self.clear_btn = QPushButton("Clear All Annotations")
self.clear_btn.clicked.connect(self._on_clear_annotations)
actions_layout.addWidget(self.clear_btn)
@@ -335,6 +351,24 @@ class AnnotationToolsWidget(QWidget):
self.clear_annotations_requested.emit()
logger.debug("Clear annotations requested")
def _on_process_annotations(self):
"""Handle process annotations button."""
if not self.current_class:
QMessageBox.warning(
self,
"No Class Selected",
"Please select an object class before processing annotations.",
)
return
self.process_annotations_requested.emit()
logger.debug("Process annotations requested")
def _on_show_annotations(self):
"""Handle show annotations button."""
self.show_annotations_requested.emit()
logger.debug("Show annotations requested")
def get_current_class(self) -> Optional[Dict]:
"""Get currently selected object class."""
return self.current_class