Skip to content

Commit

Permalink
Add flag handling features to FlagTableWidget and AnnolidWindow
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
healthonrails committed Dec 17, 2024
1 parent d4272e4 commit 1feaeac
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 76 deletions.
20 changes: 8 additions & 12 deletions annolid/gui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions annolid/gui/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
200 changes: 136 additions & 64 deletions annolid/gui/widgets/flags.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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

0 comments on commit 1feaeac

Please sign in to comment.