Renaming Pen tool to polyline tool

This commit is contained in:
2025-12-09 23:38:23 +02:00
parent dad5c2bf74
commit c3d44ac945
3 changed files with 200 additions and 246 deletions

View File

@@ -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