Renaming Pen tool to polyline tool
This commit is contained in:
@@ -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