Renaming Pen tool to polyline tool
This commit is contained in:
@@ -81,14 +81,14 @@ class AnnotationTab(QWidget):
|
||||
|
||||
# Annotation tools section
|
||||
self.annotation_tools = AnnotationToolsWidget(self.db_manager)
|
||||
self.annotation_tools.pen_enabled_changed.connect(
|
||||
self.annotation_canvas.set_pen_enabled
|
||||
self.annotation_tools.polyline_enabled_changed.connect(
|
||||
self.annotation_canvas.set_polyline_enabled
|
||||
)
|
||||
self.annotation_tools.pen_color_changed.connect(
|
||||
self.annotation_canvas.set_pen_color
|
||||
self.annotation_tools.polyline_pen_color_changed.connect(
|
||||
self.annotation_canvas.set_polyline_pen_color
|
||||
)
|
||||
self.annotation_tools.pen_width_changed.connect(
|
||||
self.annotation_canvas.set_pen_width
|
||||
self.annotation_tools.polyline_pen_width_changed.connect(
|
||||
self.annotation_canvas.set_polyline_pen_width
|
||||
)
|
||||
# RDP simplification controls
|
||||
self.annotation_tools.simplify_on_finish_changed.connect(
|
||||
@@ -97,13 +97,12 @@ class AnnotationTab(QWidget):
|
||||
self.annotation_tools.simplify_epsilon_changed.connect(
|
||||
self._on_simplify_epsilon_changed
|
||||
)
|
||||
# Class selection and class-color changes
|
||||
self.annotation_tools.class_selected.connect(self._on_class_selected)
|
||||
self.annotation_tools.class_color_changed.connect(self._on_class_color_changed)
|
||||
self.annotation_tools.clear_annotations_requested.connect(
|
||||
self._on_clear_annotations
|
||||
)
|
||||
self.annotation_tools.process_annotations_requested.connect(
|
||||
self._on_process_annotations
|
||||
)
|
||||
self.right_splitter.addWidget(self.annotation_tools)
|
||||
|
||||
# Image loading section
|
||||
@@ -299,10 +298,37 @@ class AnnotationTab(QWidget):
|
||||
self.annotation_canvas.simplify_epsilon = float(epsilon)
|
||||
logger.debug(f"Annotation simplification epsilon set to {epsilon}")
|
||||
|
||||
def _on_class_selected(self, class_data: dict):
|
||||
"""Handle when an object class is selected."""
|
||||
logger.debug(f"Object class selected: {class_data['class_name']}")
|
||||
# When a class is selected, update which annotations are visible
|
||||
def _on_class_color_changed(self):
|
||||
"""
|
||||
Handle changes to the selected object's class color.
|
||||
|
||||
When the user updates a class color in the tools widget, reload the
|
||||
annotations for the current image so that all polylines are redrawn
|
||||
using the updated per-class colors.
|
||||
"""
|
||||
if not self.current_image_id:
|
||||
return
|
||||
|
||||
logger.debug(
|
||||
f"Class color changed; reloading annotations for image ID {self.current_image_id}"
|
||||
)
|
||||
self._load_annotations_for_current_image()
|
||||
|
||||
def _on_class_selected(self, class_data):
|
||||
"""
|
||||
Handle when an object class is selected or cleared.
|
||||
|
||||
When a specific class is selected, only annotations of that class are drawn.
|
||||
When the selection is cleared (\"-- Select Class --\"), all annotations are shown.
|
||||
"""
|
||||
if class_data:
|
||||
logger.debug(f"Object class selected: {class_data['class_name']}")
|
||||
else:
|
||||
logger.debug(
|
||||
'No class selected ("-- Select Class --"), showing all annotations'
|
||||
)
|
||||
|
||||
# Whenever the selection changes, update which annotations are visible
|
||||
self._redraw_annotations_for_current_filter()
|
||||
|
||||
def _on_clear_annotations(self):
|
||||
@@ -310,28 +336,6 @@ class AnnotationTab(QWidget):
|
||||
self.annotation_canvas.clear_annotations()
|
||||
logger.info("Cleared all annotations")
|
||||
|
||||
def _on_process_annotations(self):
|
||||
"""
|
||||
Legacy hook kept for UI compatibility.
|
||||
|
||||
Annotations are now saved automatically when a stroke is completed,
|
||||
so this handler does not perform any additional database writes.
|
||||
"""
|
||||
if not self.current_image or not self.current_image_id:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"No Image",
|
||||
"Please load an image before working with annotations.",
|
||||
)
|
||||
return
|
||||
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Annotations Already Saved",
|
||||
"Annotations are saved automatically as you draw. "
|
||||
"There is no separate processing step required.",
|
||||
)
|
||||
|
||||
def _load_annotations_for_current_image(self):
|
||||
"""
|
||||
Load all annotations for the current image from the database and
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
Annotation canvas widget for drawing annotations on images.
|
||||
Supports pen tool with color selection for manual annotation.
|
||||
Currently supports polyline drawing tool with color selection for manual annotation.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
@@ -111,11 +111,11 @@ def simplify_polyline(
|
||||
|
||||
class AnnotationCanvasWidget(QWidget):
|
||||
"""
|
||||
Widget for displaying images and drawing annotations with pen tool.
|
||||
Widget for displaying images and drawing annotations with zoom and drawing tools.
|
||||
|
||||
Features:
|
||||
- Display images with zoom functionality
|
||||
- Pen tool for drawing annotations
|
||||
- Polyline tool for drawing annotations
|
||||
- Configurable pen color and width
|
||||
- Mouse-based drawing interface
|
||||
- Zoom in/out with mouse wheel and keyboard
|
||||
@@ -143,9 +143,9 @@ class AnnotationCanvasWidget(QWidget):
|
||||
|
||||
# Drawing state
|
||||
self.is_drawing = False
|
||||
self.pen_enabled = False
|
||||
self.pen_color = QColor(255, 0, 0, 128) # Default red with 50% alpha
|
||||
self.pen_width = 3
|
||||
self.polyline_enabled = False
|
||||
self.polyline_pen_color = QColor(255, 0, 0, 128) # Default red with 50% alpha
|
||||
self.polyline_pen_width = 3
|
||||
|
||||
# Current stroke and stored polylines (in image coordinates, pixel units)
|
||||
self.current_stroke: List[Tuple[float, float]] = []
|
||||
@@ -309,21 +309,21 @@ class AnnotationCanvasWidget(QWidget):
|
||||
"""Update display after drawing."""
|
||||
self._apply_zoom()
|
||||
|
||||
def set_pen_enabled(self, enabled: bool):
|
||||
"""Enable or disable pen tool."""
|
||||
self.pen_enabled = enabled
|
||||
def set_polyline_enabled(self, enabled: bool):
|
||||
"""Enable or disable polyline tool."""
|
||||
self.polyline_enabled = enabled
|
||||
if enabled:
|
||||
self.canvas_label.setCursor(Qt.CrossCursor)
|
||||
else:
|
||||
self.canvas_label.setCursor(Qt.ArrowCursor)
|
||||
|
||||
def set_pen_color(self, color: QColor):
|
||||
"""Set pen color."""
|
||||
self.pen_color = color
|
||||
def set_polyline_pen_color(self, color: QColor):
|
||||
"""Set polyline pen color."""
|
||||
self.polyline_pen_color = color
|
||||
|
||||
def set_pen_width(self, width: int):
|
||||
"""Set pen width."""
|
||||
self.pen_width = max(1, width)
|
||||
def set_polyline_pen_width(self, width: int):
|
||||
"""Set polyline pen width."""
|
||||
self.polyline_pen_width = max(1, width)
|
||||
|
||||
def get_zoom_percentage(self) -> int:
|
||||
"""Get current zoom level as percentage."""
|
||||
@@ -415,8 +415,8 @@ class AnnotationCanvasWidget(QWidget):
|
||||
|
||||
painter = QPainter(self.annotation_pixmap)
|
||||
for polyline, meta in zip(self.polylines, self.stroke_meta):
|
||||
pen_color: QColor = meta.get("color", self.pen_color)
|
||||
width: int = meta.get("width", self.pen_width)
|
||||
pen_color: QColor = meta.get("color", self.polyline_pen_color)
|
||||
width: int = meta.get("width", self.polyline_pen_width)
|
||||
pen = QPen(
|
||||
pen_color,
|
||||
width,
|
||||
@@ -433,7 +433,7 @@ class AnnotationCanvasWidget(QWidget):
|
||||
|
||||
def mousePressEvent(self, event: QMouseEvent):
|
||||
"""Handle mouse press events for drawing."""
|
||||
if not self.pen_enabled or self.annotation_pixmap is None:
|
||||
if not self.polyline_enabled or self.annotation_pixmap is None:
|
||||
super().mousePressEvent(event)
|
||||
return
|
||||
|
||||
@@ -450,7 +450,7 @@ class AnnotationCanvasWidget(QWidget):
|
||||
"""Handle mouse move events for drawing."""
|
||||
if (
|
||||
not self.is_drawing
|
||||
or not self.pen_enabled
|
||||
or not self.polyline_enabled
|
||||
or self.annotation_pixmap is None
|
||||
):
|
||||
super().mouseMoveEvent(event)
|
||||
@@ -472,8 +472,8 @@ class AnnotationCanvasWidget(QWidget):
|
||||
# Draw line from last point to current point for interactive feedback
|
||||
painter = QPainter(self.annotation_pixmap)
|
||||
pen = QPen(
|
||||
self.pen_color,
|
||||
self.pen_width,
|
||||
self.polyline_pen_color,
|
||||
self.polyline_pen_width,
|
||||
Qt.SolidLine,
|
||||
Qt.RoundCap,
|
||||
Qt.RoundJoin,
|
||||
@@ -512,7 +512,9 @@ class AnnotationCanvasWidget(QWidget):
|
||||
|
||||
if len(simplified) >= 2:
|
||||
# Store polyline and redraw all annotations
|
||||
self._add_polyline(simplified, self.pen_color, self.pen_width)
|
||||
self._add_polyline(
|
||||
simplified, self.polyline_pen_color, self.polyline_pen_width
|
||||
)
|
||||
|
||||
# Convert to normalized coordinates for metadata + signal
|
||||
normalized_stroke = [
|
||||
@@ -522,9 +524,9 @@ class AnnotationCanvasWidget(QWidget):
|
||||
self.all_strokes.append(
|
||||
{
|
||||
"points": normalized_stroke,
|
||||
"color": self.pen_color.name(),
|
||||
"alpha": self.pen_color.alpha(),
|
||||
"width": self.pen_width,
|
||||
"color": self.polyline_pen_color.name(),
|
||||
"alpha": self.polyline_pen_color.alpha(),
|
||||
"width": self.polyline_pen_width,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -541,94 +543,6 @@ class AnnotationCanvasWidget(QWidget):
|
||||
"""Get all drawn strokes with metadata."""
|
||||
return self.all_strokes
|
||||
|
||||
# def get_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 = []
|
||||
|
||||
# fig = plt.figure()
|
||||
# ax1 = fig.add_subplot(411)
|
||||
# ax2 = fig.add_subplot(412)
|
||||
# ax3 = fig.add_subplot(413)
|
||||
# ax4 = fig.add_subplot(414)
|
||||
|
||||
# # Get np.arrays from annotation_pixmap accoriding to the color of the stroke
|
||||
# qimage = self.annotation_pixmap.toImage()
|
||||
# arr = np.ndarray(
|
||||
# (qimage.height(), qimage.width(), 4),
|
||||
# buffer=qimage.constBits(),
|
||||
# strides=[qimage.bytesPerLine(), 4, 1],
|
||||
# dtype=np.uint8,
|
||||
# )
|
||||
# print(arr.shape, arr.dtype, arr.min(), arr.max())
|
||||
# arr = np.sum(arr, axis=2)
|
||||
# ax1.imshow(arr)
|
||||
|
||||
# arr_bin = arr > 0
|
||||
# ax2.imshow(arr_bin)
|
||||
|
||||
# arr_bin = binary_fill_holes(arr_bin)
|
||||
# ax3.imshow(arr_bin)
|
||||
|
||||
# labels, _number_of_features = label(
|
||||
# arr_bin,
|
||||
# )
|
||||
|
||||
# ax4.imshow(labels)
|
||||
|
||||
# objects = find_objects(labels)
|
||||
# bounding_boxes = np.array(
|
||||
# [[obj[0].start, obj[0].stop, obj[1].start, obj[1].stop] for obj in objects]
|
||||
# ) / np.array([arr.shape[0], arr.shape[1]])
|
||||
|
||||
# print(objects)
|
||||
# print(bounding_boxes)
|
||||
# print(np.array([arr.shape[0], arr.shape[1]]))
|
||||
|
||||
# polylines = find_contours(arr_bin, 0.5)
|
||||
# for pl in polylines:
|
||||
# ax1.plot(pl[:, 1], pl[:, 0], "k")
|
||||
|
||||
# print(arr.shape, arr.dtype, arr.min(), arr.max())
|
||||
|
||||
# plt.show()
|
||||
|
||||
# return polyline
|
||||
|
||||
def get_annotation_parameters(self) -> Optional[List[Dict[str, Any]]]:
|
||||
"""
|
||||
Get all annotation parameters including bounding box and polyline.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
Annotation tools widget for controlling annotation parameters.
|
||||
Includes pen tool, color picker, class selection, and annotation management.
|
||||
Includes polyline tool, color picker, class selection, and annotation management.
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
@@ -33,29 +33,29 @@ class AnnotationToolsWidget(QWidget):
|
||||
Widget for annotation tool controls.
|
||||
|
||||
Features:
|
||||
- Enable/disable pen tool
|
||||
- Color selection for pen
|
||||
- Enable/disable polyline tool
|
||||
- Color selection for polyline pen
|
||||
- Object class selection
|
||||
- Add new object classes
|
||||
- Pen width control
|
||||
- Clear annotations
|
||||
|
||||
Signals:
|
||||
pen_enabled_changed: Emitted when pen tool is enabled/disabled (bool)
|
||||
pen_color_changed: Emitted when pen color changes (QColor)
|
||||
pen_width_changed: Emitted when pen width changes (int)
|
||||
polyline_enabled_changed: Emitted when polyline tool is enabled/disabled (bool)
|
||||
polyline_pen_color_changed: Emitted when polyline pen color changes (QColor)
|
||||
polyline_pen_width_changed: Emitted when polyline pen width changes (int)
|
||||
class_selected: Emitted when object class is selected (dict)
|
||||
clear_annotations_requested: Emitted when clear button is pressed
|
||||
"""
|
||||
|
||||
pen_enabled_changed = Signal(bool)
|
||||
pen_color_changed = Signal(QColor)
|
||||
pen_width_changed = Signal(int)
|
||||
polyline_enabled_changed = Signal(bool)
|
||||
polyline_pen_color_changed = Signal(QColor)
|
||||
polyline_pen_width_changed = Signal(int)
|
||||
simplify_on_finish_changed = Signal(bool)
|
||||
simplify_epsilon_changed = Signal(float)
|
||||
class_selected = Signal(dict)
|
||||
class_color_changed = Signal()
|
||||
clear_annotations_requested = Signal()
|
||||
process_annotations_requested = Signal()
|
||||
|
||||
def __init__(self, db_manager: DatabaseManager, parent=None):
|
||||
"""
|
||||
@@ -67,7 +67,7 @@ class AnnotationToolsWidget(QWidget):
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.db_manager = db_manager
|
||||
self.pen_enabled = False
|
||||
self.polyline_enabled = False
|
||||
self.current_color = QColor(255, 0, 0, 128) # Red with 50% alpha
|
||||
self.current_class = None
|
||||
|
||||
@@ -78,40 +78,31 @@ class AnnotationToolsWidget(QWidget):
|
||||
"""Setup user interface."""
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Pen Tool Group
|
||||
pen_group = QGroupBox("Pen Tool")
|
||||
pen_layout = QVBoxLayout()
|
||||
# Polyline Tool Group
|
||||
polyline_group = QGroupBox("Polyline Tool")
|
||||
polyline_layout = QVBoxLayout()
|
||||
|
||||
# Enable/Disable pen
|
||||
# Enable/Disable polyline tool
|
||||
button_layout = QHBoxLayout()
|
||||
self.pen_toggle_btn = QPushButton("Enable Pen")
|
||||
self.pen_toggle_btn.setCheckable(True)
|
||||
self.pen_toggle_btn.clicked.connect(self._on_pen_toggle)
|
||||
button_layout.addWidget(self.pen_toggle_btn)
|
||||
pen_layout.addLayout(button_layout)
|
||||
self.polyline_toggle_btn = QPushButton("Start Drawing Polyline")
|
||||
self.polyline_toggle_btn.setCheckable(True)
|
||||
self.polyline_toggle_btn.clicked.connect(self._on_polyline_toggle)
|
||||
button_layout.addWidget(self.polyline_toggle_btn)
|
||||
polyline_layout.addLayout(button_layout)
|
||||
|
||||
# Pen width control
|
||||
# Polyline pen width control
|
||||
width_layout = QHBoxLayout()
|
||||
width_layout.addWidget(QLabel("Pen Width:"))
|
||||
self.pen_width_spin = QSpinBox()
|
||||
self.pen_width_spin.setMinimum(1)
|
||||
self.pen_width_spin.setMaximum(20)
|
||||
self.pen_width_spin.setValue(3)
|
||||
self.pen_width_spin.valueChanged.connect(self._on_pen_width_changed)
|
||||
width_layout.addWidget(self.pen_width_spin)
|
||||
self.polyline_pen_width_spin = QSpinBox()
|
||||
self.polyline_pen_width_spin.setMinimum(1)
|
||||
self.polyline_pen_width_spin.setMaximum(20)
|
||||
self.polyline_pen_width_spin.setValue(3)
|
||||
self.polyline_pen_width_spin.valueChanged.connect(
|
||||
self._on_polyline_pen_width_changed
|
||||
)
|
||||
width_layout.addWidget(self.polyline_pen_width_spin)
|
||||
width_layout.addStretch()
|
||||
pen_layout.addLayout(width_layout)
|
||||
|
||||
# Color selection
|
||||
color_layout = QHBoxLayout()
|
||||
color_layout.addWidget(QLabel("Color:"))
|
||||
self.color_btn = QPushButton()
|
||||
self.color_btn.setFixedSize(40, 30)
|
||||
self.color_btn.clicked.connect(self._on_color_picker)
|
||||
self._update_color_button()
|
||||
color_layout.addWidget(self.color_btn)
|
||||
color_layout.addStretch()
|
||||
pen_layout.addLayout(color_layout)
|
||||
polyline_layout.addLayout(width_layout)
|
||||
|
||||
# Simplification controls (RDP)
|
||||
simplify_layout = QHBoxLayout()
|
||||
@@ -128,10 +119,10 @@ class AnnotationToolsWidget(QWidget):
|
||||
self.eps_spin.valueChanged.connect(self._on_eps_change)
|
||||
simplify_layout.addWidget(self.eps_spin)
|
||||
simplify_layout.addStretch()
|
||||
pen_layout.addLayout(simplify_layout)
|
||||
polyline_layout.addLayout(simplify_layout)
|
||||
|
||||
pen_group.setLayout(pen_layout)
|
||||
layout.addWidget(pen_group)
|
||||
polyline_group.setLayout(polyline_layout)
|
||||
layout.addWidget(polyline_group)
|
||||
|
||||
# Object Class Group
|
||||
class_group = QGroupBox("Object Class")
|
||||
@@ -142,7 +133,7 @@ class AnnotationToolsWidget(QWidget):
|
||||
self.class_combo.currentIndexChanged.connect(self._on_class_selected)
|
||||
class_layout.addWidget(self.class_combo)
|
||||
|
||||
# Add class button
|
||||
# Add / manage classes
|
||||
class_button_layout = QHBoxLayout()
|
||||
self.add_class_btn = QPushButton("Add New Class")
|
||||
self.add_class_btn.clicked.connect(self._on_add_class)
|
||||
@@ -153,6 +144,17 @@ class AnnotationToolsWidget(QWidget):
|
||||
class_button_layout.addWidget(self.refresh_classes_btn)
|
||||
class_layout.addLayout(class_button_layout)
|
||||
|
||||
# Class color (associated with selected object class)
|
||||
color_layout = QHBoxLayout()
|
||||
color_layout.addWidget(QLabel("Class Color:"))
|
||||
self.color_btn = QPushButton()
|
||||
self.color_btn.setFixedSize(40, 30)
|
||||
self.color_btn.clicked.connect(self._on_color_picker)
|
||||
self._update_color_button()
|
||||
color_layout.addWidget(self.color_btn)
|
||||
color_layout.addStretch()
|
||||
class_layout.addLayout(color_layout)
|
||||
|
||||
# Selected class info
|
||||
self.class_info_label = QLabel("No class selected")
|
||||
self.class_info_label.setWordWrap(True)
|
||||
@@ -168,13 +170,6 @@ 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.clear_btn = QPushButton("Clear All Annotations")
|
||||
self.clear_btn.clicked.connect(self._on_clear_annotations)
|
||||
actions_layout.addWidget(self.clear_btn)
|
||||
@@ -206,7 +201,7 @@ class AnnotationToolsWidget(QWidget):
|
||||
|
||||
# Clear and repopulate combo box
|
||||
self.class_combo.clear()
|
||||
self.class_combo.addItem("-- Select Class --", None)
|
||||
self.class_combo.addItem("-- Select Class / Show All --", None)
|
||||
|
||||
for cls in classes:
|
||||
self.class_combo.addItem(cls["class_name"], cls)
|
||||
@@ -219,26 +214,26 @@ class AnnotationToolsWidget(QWidget):
|
||||
self, "Error", f"Failed to load object classes:\n{str(e)}"
|
||||
)
|
||||
|
||||
def _on_pen_toggle(self, checked: bool):
|
||||
"""Handle pen tool enable/disable."""
|
||||
self.pen_enabled = checked
|
||||
def _on_polyline_toggle(self, checked: bool):
|
||||
"""Handle polyline tool enable/disable."""
|
||||
self.polyline_enabled = checked
|
||||
|
||||
if checked:
|
||||
self.pen_toggle_btn.setText("Disable Pen")
|
||||
self.pen_toggle_btn.setStyleSheet(
|
||||
self.polyline_toggle_btn.setText("Start Drawing Polyline")
|
||||
self.polyline_toggle_btn.setStyleSheet(
|
||||
"QPushButton { background-color: #4CAF50; }"
|
||||
)
|
||||
else:
|
||||
self.pen_toggle_btn.setText("Enable Pen")
|
||||
self.pen_toggle_btn.setStyleSheet("")
|
||||
self.polyline_toggle_btn.setText("Stop drawing Polyline")
|
||||
self.polyline_toggle_btn.setStyleSheet("")
|
||||
|
||||
self.pen_enabled_changed.emit(self.pen_enabled)
|
||||
logger.debug(f"Pen tool {'enabled' if checked else 'disabled'}")
|
||||
self.polyline_enabled_changed.emit(self.polyline_enabled)
|
||||
logger.debug(f"Polyline tool {'enabled' if checked else 'disabled'}")
|
||||
|
||||
def _on_pen_width_changed(self, width: int):
|
||||
"""Handle pen width changes."""
|
||||
self.pen_width_changed.emit(width)
|
||||
logger.debug(f"Pen width changed to {width}")
|
||||
def _on_polyline_pen_width_changed(self, width: int):
|
||||
"""Handle polyline pen width changes."""
|
||||
self.polyline_pen_width_changed.emit(width)
|
||||
logger.debug(f"Polyline pen width changed to {width}")
|
||||
|
||||
def _on_simplify_toggle(self, state: int):
|
||||
"""Handle simplify-on-finish checkbox toggle."""
|
||||
@@ -253,24 +248,75 @@ class AnnotationToolsWidget(QWidget):
|
||||
logger.debug(f"Simplification epsilon changed to {epsilon}")
|
||||
|
||||
def _on_color_picker(self):
|
||||
"""Open color picker dialog with alpha support."""
|
||||
"""Open color picker dialog and update the selected object's class color."""
|
||||
if not self.current_class:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"No Class Selected",
|
||||
"Please select an object class before changing its color.",
|
||||
)
|
||||
return
|
||||
|
||||
# Use current class color (without alpha) as the base
|
||||
base_color = QColor(self.current_class.get("color", self.current_color.name()))
|
||||
color = QColorDialog.getColor(
|
||||
self.current_color,
|
||||
base_color,
|
||||
self,
|
||||
"Select Pen Color",
|
||||
QColorDialog.ShowAlphaChannel, # Enable alpha channel selection
|
||||
"Select Class Color",
|
||||
QColorDialog.ShowAlphaChannel, # Allow alpha in UI, but store RGB in DB
|
||||
)
|
||||
|
||||
if color.isValid():
|
||||
self.current_color = color
|
||||
self._update_color_button()
|
||||
self.pen_color_changed.emit(color)
|
||||
logger.debug(
|
||||
f"Pen color changed to {color.name()} with alpha {color.alpha()}"
|
||||
if not color.isValid():
|
||||
return
|
||||
|
||||
# Normalize to opaque RGB for storage
|
||||
new_color = QColor(color)
|
||||
new_color.setAlpha(255)
|
||||
hex_color = new_color.name()
|
||||
|
||||
try:
|
||||
# Update in database
|
||||
self.db_manager.update_object_class(
|
||||
class_id=self.current_class["id"], color=hex_color
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update class color in database: {e}")
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Error",
|
||||
f"Failed to update class color in database:\n{str(e)}",
|
||||
)
|
||||
return
|
||||
|
||||
# Update local class data and combo box item data
|
||||
self.current_class["color"] = hex_color
|
||||
current_index = self.class_combo.currentIndex()
|
||||
if current_index >= 0:
|
||||
self.class_combo.setItemData(current_index, dict(self.current_class))
|
||||
|
||||
# Update info label text
|
||||
info_text = f"Class: {self.current_class['class_name']}\nColor: {hex_color}"
|
||||
if self.current_class.get("description"):
|
||||
info_text += f"\nDescription: {self.current_class['description']}"
|
||||
self.class_info_label.setText(info_text)
|
||||
|
||||
# Use semi-transparent version for polyline pen / button preview
|
||||
class_color = QColor(hex_color)
|
||||
class_color.setAlpha(128)
|
||||
self.current_color = class_color
|
||||
self._update_color_button()
|
||||
self.polyline_pen_color_changed.emit(class_color)
|
||||
|
||||
logger.debug(
|
||||
f"Updated class '{self.current_class['class_name']}' color to "
|
||||
f"{hex_color} (polyline pen alpha={class_color.alpha()})"
|
||||
)
|
||||
|
||||
# Notify listeners (e.g., AnnotationTab) so they can reload/redraw
|
||||
self.class_color_changed.emit()
|
||||
|
||||
def _on_class_selected(self, index: int):
|
||||
"""Handle object class selection."""
|
||||
"""Handle object class selection (including '-- Select Class --')."""
|
||||
class_data = self.class_combo.currentData()
|
||||
|
||||
if class_data:
|
||||
@@ -285,20 +331,23 @@ class AnnotationToolsWidget(QWidget):
|
||||
|
||||
self.class_info_label.setText(info_text)
|
||||
|
||||
# Update pen color to match class color with semi-transparency
|
||||
# Update polyline pen color to match class color with semi-transparency
|
||||
class_color = QColor(class_data["color"])
|
||||
if class_color.isValid():
|
||||
# Add 50% alpha for semi-transparency
|
||||
class_color.setAlpha(128)
|
||||
self.current_color = class_color
|
||||
self._update_color_button()
|
||||
self.pen_color_changed.emit(class_color)
|
||||
self.polyline_pen_color_changed.emit(class_color)
|
||||
|
||||
self.class_selected.emit(class_data)
|
||||
logger.debug(f"Selected class: {class_data['class_name']}")
|
||||
else:
|
||||
# "-- Select Class --" chosen: clear current class and show all annotations
|
||||
self.current_class = None
|
||||
self.class_info_label.setText("No class selected")
|
||||
self.class_selected.emit(None)
|
||||
logger.debug("Class selection cleared: showing annotations for all classes")
|
||||
|
||||
def _on_add_class(self):
|
||||
"""Handle adding a new object class."""
|
||||
@@ -376,31 +425,18 @@ 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 get_current_class(self) -> Optional[Dict]:
|
||||
"""Get currently selected object class."""
|
||||
return self.current_class
|
||||
|
||||
def get_pen_color(self) -> QColor:
|
||||
"""Get current pen color."""
|
||||
def get_polyline_pen_color(self) -> QColor:
|
||||
"""Get current polyline pen color."""
|
||||
return self.current_color
|
||||
|
||||
def get_pen_width(self) -> int:
|
||||
"""Get current pen width."""
|
||||
return self.pen_width_spin.value()
|
||||
def get_polyline_pen_width(self) -> int:
|
||||
"""Get current polyline pen width."""
|
||||
return self.polyline_pen_width_spin.value()
|
||||
|
||||
def is_pen_enabled(self) -> bool:
|
||||
"""Check if pen tool is enabled."""
|
||||
return self.pen_enabled
|
||||
def is_polyline_enabled(self) -> bool:
|
||||
"""Check if polyline tool is enabled."""
|
||||
return self.polyline_enabled
|
||||
|
||||
Reference in New Issue
Block a user