From 1feaeacd993cde8bdebd870f7b0d082763683045 Mon Sep 17 00:00:00 2001 From: healthonrails Date: Tue, 17 Dec 2024 15:33:42 -0500 Subject: [PATCH] Add flag handling features to FlagTableWidget and AnnolidWindow - Introduced new signals and in to handle saving flags and selecting rows. - Added functionality to save all flags via the Save button and emit signal. - Implemented row selection handling with signal to emit the selected flag name. - Refactored to connect the new signals to appropriate handlers for flag saving and row selection. - Cleaned up unused code related to flag handling in . - Enhanced table setup and flag row management in , including the ability to add, start, and end flags. - Added input validation for flag names and values in the table, with error handling and notifications. --- annolid/gui/app.py | 20 ++-- annolid/gui/widgets/__init__.py | 1 + annolid/gui/widgets/flags.py | 200 ++++++++++++++++++++++---------- 3 files changed, 145 insertions(+), 76 deletions(-) diff --git a/annolid/gui/app.py b/annolid/gui/app.py index 8abcba8..84fd65e 100644 --- a/annolid/gui/app.py +++ b/annolid/gui/app.py @@ -223,6 +223,8 @@ def __init__(self, self.handle_flag_start_button) self.flag_widget.endButtonClicked.connect( self.handle_flag_end_button) + self.flag_widget.flagsSaved.connect(self.handle_flags_saved) + self.flag_widget.rowSelected.connect(self.handle_row_selected) self.setCentralWidget(scrollArea) @@ -577,6 +579,12 @@ def __init__(self, self.populateModeActions() + def handle_flags_saved(self, flags): + self.loadFlags(flags) + + def handle_row_selected(self, flag_name: str): + self.event_type = flag_name + def _grounding_sam(self): """ Handles the text prompt inputs for grouding DINO SAM. @@ -1388,16 +1396,6 @@ def setDirty(self): title = self.getTitle(clean=False) self.setWindowTitle(title) - # if self.flag_widget: - # for row in range(self.flag_widget._table.rowCount()): - # item = self.flag_widget._table.item(row, 0) - # if not self.filename: - # return - # if item: - # flag_name = item.text() - # if self.flag_widget.flags.get(flag_name) == True: - # self.event_type = flag_name - if self.dirty and self.filename: # Save immediately if auto-save self.saveLabels(self._getLabelFile(self.filename)) @@ -2357,8 +2355,6 @@ def image_to_canvas(self, qimage, filename, frame_number): _state = 'continue' self.loadFlags(flags) - if self.pinned_flags is not None: - self.loadFlags(self.pinned_flags) if self._config["keep_prev"] and self.noShapes(): self.loadShapes(prev_shapes, replace=False) self.setDirty() diff --git a/annolid/gui/widgets/__init__.py b/annolid/gui/widgets/__init__.py index e6ad453..d550de2 100644 --- a/annolid/gui/widgets/__init__.py +++ b/annolid/gui/widgets/__init__.py @@ -6,3 +6,4 @@ from annolid.gui.widgets.progressing_dialog import ProgressingWindow from annolid.gui.widgets.quality_control_dialog import QualityControlDialog from annolid.gui.widgets.about_dialog import SystemInfoDialog +from annolid.gui.widgets.flags import FlagTableWidget \ No newline at end of file diff --git a/annolid/gui/widgets/flags.py b/annolid/gui/widgets/flags.py index 444aae8..bf532f5 100644 --- a/annolid/gui/widgets/flags.py +++ b/annolid/gui/widgets/flags.py @@ -1,41 +1,68 @@ from qtpy import QtCore, QtWidgets from qtpy.QtCore import Qt -import typing -import copy +from typing import Dict class FlagTableWidget(QtWidgets.QWidget): - flagsChanged = QtCore.Signal() - startButtonClicked = QtCore.Signal(str) - endButtonClicked = QtCore.Signal(str) + flagsSaved = QtCore.Signal(dict) # Emit all flags at once + startButtonClicked = QtCore.Signal(str) # Emit when "Start" is clicked + endButtonClicked = QtCore.Signal(str) # Emit when "End" is clicked + rowSelected = QtCore.Signal(str) # Emit flag name when a row is selected + + COLUMN_NAME = 0 + COLUMN_VALUE = 1 def __init__(self, parent=None): super().__init__(parent) + self._flags: Dict[str, bool] = {} - self._table = QtWidgets.QTableWidget() - self._table.setColumnCount(4) + # Table setup + self._table = QtWidgets.QTableWidget(0, 4) self._table.setHorizontalHeaderLabels( - ["Flag Name", "Value", "Start", "End"] - ) + ["Flag Name", "Value", "Start", "End"]) + self._table.horizontalHeader().setStretchLastSection(True) self._table.verticalHeader().setVisible(False) - self._flags: typing.Dict[str, bool] = {} + self._table.setEditTriggers(QtWidgets.QTableWidget.AllEditTriggers) + + # Select entire rows + self._table.setSelectionBehavior( + QtWidgets.QAbstractItemView.SelectRows) + # Connect the row selection + self._table.clicked.connect(self._handle_table_clicked) + + # Buttons Layout + buttons_layout = QtWidgets.QHBoxLayout() + + # Add New Flag Button + self.add_flag_button = QtWidgets.QPushButton("New") + self.add_flag_button.clicked.connect(self.add_row) + buttons_layout.addWidget(self.add_flag_button) + + # Save All Button + self.save_all_button = QtWidgets.QPushButton("Save") + self.save_all_button.clicked.connect(self.save_all_flags) + buttons_layout.addWidget(self.save_all_button) + # Main Layout layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self._table) - self.setLayout(layout) + layout.addLayout(buttons_layout) - @property - def flags(self): - # return a copy to avoid external changes - return copy.deepcopy(self._flags) + def _handle_table_clicked(self, index): + """Handle clicks on table rows and emit the flag name.""" + row = index.row() + item_name_widget = self._table.cellWidget(row, self.COLUMN_NAME) - def set_flags(self, flags): - self._flags = flags + if isinstance(item_name_widget, QtWidgets.QLineEdit): + flag_name = item_name_widget.text().strip() + if flag_name: + self.rowSelected.emit(flag_name) - def loadFlags(self, flags: typing.Dict[str, bool]): + def loadFlags(self, flags: Dict[str, bool]): + """Load initial flags into the table or update existing flags.""" self._flags = flags - row_count = self._table.rowCount() + # Create a dictionary to easily access flags in the table by their name table_flags = {} for row in range(row_count): @@ -48,53 +75,98 @@ def loadFlags(self, flags: typing.Dict[str, bool]): # Update Existing Row row = table_flags[key] item_value = self._table.item(row, 1) - item_value.setText("True" if value else "False") + if item_value: + item_value.setText("True" if value else "False") else: - # Add a new Row - row = self._table.rowCount() - self._table.insertRow(row) - - item_name = QtWidgets.QTableWidgetItem(key) - item_name.setFlags(item_name.flags() & ~Qt.ItemIsEditable) - item_value = QtWidgets.QTableWidgetItem( - "True" if value else "False") - item_value.setFlags(item_value.flags() & ~Qt.ItemIsEditable) - - start_button = QtWidgets.QPushButton("Start") - start_button.setProperty("flag_name", key) - start_button.clicked.connect(self.handle_button_click) - - end_button = QtWidgets.QPushButton("End") - end_button.setProperty("flag_name", key) - end_button.clicked.connect(self.handle_button_click) - - self._table.setItem(row, 0, item_name) - self._table.setItem(row, 1, item_value) - self._table.setCellWidget(row, 2, start_button) - self._table.setCellWidget(row, 3, end_button) + # Add a New Row for the new flag + self.add_row(key, value) + # Resize columns to fit content self._table.resizeColumnsToContents() - def handle_button_click(self): - sender = self.sender() - flag_name = sender.property("flag_name") - if sender.text() == "Start": - self._flags[flag_name] = True - self.startButtonClicked.emit(flag_name) # Emit Start signal - elif sender.text() == "End": - self._flags[flag_name] = False - self.endButtonClicked.emit(flag_name) # Emit End signal - - # Update the table value - rows = self._table.rowCount() - for row in range(rows): - item = self._table.item(row, 0) - if item and item.text() == flag_name: - item_value = self._table.item(row, 1) - if item_value: - item_value.setText( - "True" if self._flags[flag_name] else "False" - ) - break + def add_row(self, name: str = "", value: bool = False): + """Add a new row for editing.""" + row = self._table.rowCount() + self._table.insertRow(row) + + # Ensure name is a string and value is a boolean + name = str(name) if name else "" # Make sure 'name' is a string + value = "True" if value else "False" # Ensure value is "True" or "False" + + # Flag Name + name_editor = QtWidgets.QLineEdit(name) + name_editor.setPlaceholderText("Enter flag name...") + self._table.setCellWidget(row, self.COLUMN_NAME, name_editor) + + # Flag Value + value_editor = QtWidgets.QLineEdit(value) + value_editor.setPlaceholderText("True or False") + self._table.setCellWidget(row, self.COLUMN_VALUE, value_editor) + + # Start Button + start_button = QtWidgets.QPushButton("Start") + start_button.clicked.connect(lambda: self.handle_start_button(row)) + self._table.setCellWidget(row, 2, start_button) + + # End Button + end_button = QtWidgets.QPushButton("End") + end_button.clicked.connect(lambda: self.handle_end_button(row)) + self._table.setCellWidget(row, 3, end_button) + + def handle_start_button(self, row): + """Handle the Start button click.""" + item_name = self._table.cellWidget(row, self.COLUMN_NAME) + if isinstance(item_name, QtWidgets.QLineEdit): + flag_name = item_name.text().strip() + if flag_name: + self.startButtonClicked.emit(flag_name) + + def handle_end_button(self, row): + """Handle the End button click.""" + item_name = self._table.cellWidget(row, self.COLUMN_NAME) + if isinstance(item_name, QtWidgets.QLineEdit): + flag_name = item_name.text().strip() + if flag_name: + self.endButtonClicked.emit(flag_name) + + def save_all_flags(self): + """Collect all flags and emit them.""" + flags = {} + for row in range(self._table.rowCount()): + name_editor = self._table.cellWidget(row, self.COLUMN_NAME) + value_editor = self._table.cellWidget(row, self.COLUMN_VALUE) + + if not isinstance(name_editor, QtWidgets.QLineEdit) or not isinstance(value_editor, QtWidgets.QLineEdit): + continue + + flag_name = name_editor.text().strip() + flag_value = value_editor.text().strip().lower() + + # Validate inputs + if not flag_name: + self.show_error(f"Row {row + 1}: Flag name cannot be empty.") + return + if flag_value not in {"true", "false"}: + self.show_error( + f"Row {row + 1}: Value must be 'True' or 'False'.") + return + if flag_name in flags: + self.show_error(f"Duplicate flag name: {flag_name}") + return + + flags[flag_name] = flag_value == "true" + + # Save the flags and emit the signal + self._flags = flags + self.flagsSaved.emit(self._flags) + QtWidgets.QMessageBox.information( + self, "Success", "All flags have been saved!") - self.flagsChanged.emit() + def show_error(self, message: str): + """Display an error message.""" + QtWidgets.QMessageBox.warning(self, "Error", message) + + @property + def flags(self) -> Dict[str, bool]: + """Return all flags as a dictionary.""" + return self._flags