small rewrite to impose dry principles
This commit is contained in:
@@ -8,9 +8,9 @@ License: GPL-3.0
|
||||
|
||||
import os
|
||||
|
||||
from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QHBoxLayout, QLabel, QLineEdit, QListView, QMessageBox, QPushButton, QVBoxLayout, QWidget, QFrame, QSpinBox
|
||||
from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListView, QMessageBox, QPushButton, QScrollArea, QVBoxLayout, QWidget, QFrame, QSpinBox
|
||||
from PySide6.QtGui import QStandardItemModel, QStandardItem, QPixmap, QIntValidator, QDoubleValidator
|
||||
from PySide6.QtCore import QEvent, Qt
|
||||
from PySide6.QtCore import QEvent, QSize, Qt
|
||||
|
||||
from src.shared.shareddata import APP_NAME
|
||||
|
||||
@@ -836,9 +836,9 @@ class FlaresBaseWidget(QWidget):
|
||||
# 3. Conditional trigger for event updates
|
||||
# We only update events if we aren't in one of the excluded viewers
|
||||
excluded_viewers = {
|
||||
"ParticipantViewer",
|
||||
"ParticipantImage",
|
||||
"ParticipantFoldChannels",
|
||||
"ExportDataAsCSVViewer",
|
||||
"ExportToCSV",
|
||||
}
|
||||
|
||||
if getattr(self, "caller", None) not in excluded_viewers:
|
||||
@@ -1050,4 +1050,480 @@ class FlaresBaseWidget(QWidget):
|
||||
model.appendRow(item)
|
||||
|
||||
self._connect_select_all_toggle(toggle_ref, model)
|
||||
self.update_participant_dropdown_label(combo=target_combo)
|
||||
self.update_participant_dropdown_label(combo=target_combo)
|
||||
|
||||
|
||||
class CrossGroupUIMixin:
|
||||
|
||||
def setup_cross_group_ui(self, index_texts):
|
||||
|
||||
self.group_to_paths = {}
|
||||
for file_path, group_name in self.group_dict.items():
|
||||
self.group_to_paths.setdefault(group_name, []).append(file_path)
|
||||
|
||||
self.group_names = sorted(self.group_to_paths.keys())
|
||||
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.top_bar = QHBoxLayout()
|
||||
self.main_layout.addLayout(self.top_bar)
|
||||
|
||||
|
||||
self.group_a_dropdown = QComboBox()
|
||||
self.group_a_dropdown.addItem("<None Selected>")
|
||||
self.group_a_dropdown.addItems(self.group_names)
|
||||
self.group_a_dropdown.currentIndexChanged.connect(self._update_group_a_options)
|
||||
|
||||
|
||||
self.group_b_dropdown = QComboBox()
|
||||
self.group_b_dropdown.addItem("<None Selected>")
|
||||
self.group_b_dropdown.addItems(self.group_names)
|
||||
self.group_b_dropdown.currentIndexChanged.connect(self._update_group_b_options)
|
||||
|
||||
|
||||
self.event_dropdown = QComboBox()
|
||||
self.event_dropdown.addItem("<None Selected>")
|
||||
|
||||
|
||||
self.participant_dropdown_a = self._create_multiselect_dropdown([])
|
||||
line_edit = self.participant_dropdown_a.lineEdit()
|
||||
assert line_edit is not None, "Dropdown A must be editable to have a lineEdit"
|
||||
line_edit.setPlaceholderText("Select participants (Group A)")
|
||||
model = self.participant_dropdown_a.model()
|
||||
assert isinstance(model, QStandardItemModel), "Model must be QStandardItemModel"
|
||||
model.itemChanged.connect(self._on_participants_changed)
|
||||
|
||||
|
||||
self.participant_dropdown_b = self._create_multiselect_dropdown([])
|
||||
line_edit = self.participant_dropdown_b.lineEdit()
|
||||
assert line_edit is not None, "Dropdown B must be editable to have a lineEdit"
|
||||
line_edit.setPlaceholderText("Select participants (Group B)")
|
||||
model = self.participant_dropdown_b.model()
|
||||
assert isinstance(model, QStandardItemModel), "Model must be QStandardItemModel"
|
||||
model.itemChanged.connect(self._on_participants_changed)
|
||||
|
||||
|
||||
self.index_texts = index_texts
|
||||
self.image_index_dropdown = self._create_multiselect_dropdown(self.index_texts)
|
||||
self.image_index_dropdown.currentIndexChanged.connect(self.update_image_index_dropdown_label)
|
||||
|
||||
|
||||
self.submit_button = QPushButton("Submit")
|
||||
self.submit_button.clicked.connect(self.proccess_request)
|
||||
|
||||
|
||||
self.top_bar.addWidget(QLabel("Group A:"))
|
||||
self.top_bar.addWidget(self.group_a_dropdown)
|
||||
self.top_bar.addWidget(QLabel("Participants (Group A):"))
|
||||
self.top_bar.addWidget(self.participant_dropdown_a)
|
||||
self.top_bar.addWidget(QLabel("Group B:"))
|
||||
self.top_bar.addWidget(self.group_b_dropdown)
|
||||
self.top_bar.addWidget(QLabel("Participants (Group B):"))
|
||||
self.top_bar.addWidget(self.participant_dropdown_b)
|
||||
self.top_bar.addWidget(QLabel("Event:"))
|
||||
self.top_bar.addWidget(self.event_dropdown)
|
||||
self.top_bar.addWidget(QLabel("Image Indexes:"))
|
||||
self.top_bar.addWidget(self.image_index_dropdown)
|
||||
self.top_bar.addWidget(self.submit_button)
|
||||
|
||||
self.scroll_area = QScrollArea()
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_content = QWidget()
|
||||
self.grid_layout = QGridLayout(self.scroll_content)
|
||||
self.scroll_area.setWidget(self.scroll_content)
|
||||
self.main_layout.addWidget(self.scroll_area)
|
||||
|
||||
self.thumb_size = QSize(280, 180)
|
||||
self.showMaximized()
|
||||
|
||||
def _update_group_b_options(self):
|
||||
"""Triggered when Group B changes: Update Group A to exclude B's choice"""
|
||||
selected_b = self.group_b_dropdown.currentText()
|
||||
|
||||
# Refresh Group A and exclude what was just picked in Group B
|
||||
self._refresh_group_dropdown(self.group_a_dropdown, exclude=selected_b)
|
||||
|
||||
# Update the participants for Group B
|
||||
self.update_participant_list_for_group(selected_b, self.participant_dropdown_b)
|
||||
self._update_event_dropdown()
|
||||
|
||||
def _update_group_a_options(self):
|
||||
"""Triggered when Group A changes: Update Group B to exclude A's choice"""
|
||||
selected_a = self.group_a_dropdown.currentText()
|
||||
|
||||
# Refresh Group B and exclude what was just picked in Group A
|
||||
self._refresh_group_dropdown(self.group_b_dropdown, exclude=selected_a)
|
||||
|
||||
# Update the participants for Group A
|
||||
self.update_participant_list_for_group(selected_a, self.participant_dropdown_a)
|
||||
self._update_event_dropdown()
|
||||
|
||||
def _on_participants_changed(self, item=None):
|
||||
self._update_event_dropdown()
|
||||
|
||||
|
||||
def _refresh_group_dropdown(self, dropdown, exclude):
|
||||
current = dropdown.currentText()
|
||||
dropdown.blockSignals(True)
|
||||
dropdown.clear()
|
||||
dropdown.addItem("<None Selected>")
|
||||
for group in self.group_names:
|
||||
if group != exclude:
|
||||
dropdown.addItem(group)
|
||||
# Restore previous selection if still valid
|
||||
if current != "<None Selected>" and current != exclude and dropdown.findText(current) != -1:
|
||||
dropdown.setCurrentText(current)
|
||||
else:
|
||||
dropdown.setCurrentIndex(0) # Reset to "<None Selected>"
|
||||
dropdown.blockSignals(False)
|
||||
|
||||
|
||||
|
||||
|
||||
def _get_file_paths_from_labels(self, labels, group_name):
|
||||
file_paths = []
|
||||
|
||||
if group_name == self.group_a_dropdown.currentText():
|
||||
participant_map = self.participant_map_a
|
||||
elif group_name == self.group_b_dropdown.currentText():
|
||||
participant_map = self.participant_map_b
|
||||
else:
|
||||
return []
|
||||
|
||||
# Reverse map: display label -> file path
|
||||
reverse_map = {
|
||||
f"{label} ({os.path.basename(fp)})": fp
|
||||
for fp, label in participant_map.items()
|
||||
}
|
||||
|
||||
for label in labels:
|
||||
file_path = reverse_map.get(label)
|
||||
if file_path:
|
||||
file_paths.append(file_path)
|
||||
|
||||
return file_paths
|
||||
|
||||
def get_common_request_data(self, parameterized_indexes):
|
||||
selected_event = self.event_dropdown.currentText()
|
||||
if selected_event == "<None Selected>":
|
||||
selected_event = None
|
||||
|
||||
participants_a = self._get_checked_items(self.participant_dropdown_a)
|
||||
file_paths_a = self._get_file_paths_from_labels(
|
||||
participants_a, self.group_a_dropdown.currentText()
|
||||
)
|
||||
|
||||
participants_b = self._get_checked_items(self.participant_dropdown_b)
|
||||
file_paths_b = self._get_file_paths_from_labels(
|
||||
participants_b, self.group_b_dropdown.currentText()
|
||||
)
|
||||
|
||||
selected_indexes = [
|
||||
int(s.split(" ")[0])
|
||||
for s in self._get_checked_items(self.image_index_dropdown)
|
||||
]
|
||||
|
||||
all_selected_paths = list(set(file_paths_a + file_paths_b))
|
||||
|
||||
if not all_selected_paths:
|
||||
print("No participants selected.")
|
||||
return None
|
||||
|
||||
# Inject full_text
|
||||
for idx, params_list in parameterized_indexes.items():
|
||||
full_text = self.index_texts[idx]
|
||||
for param in params_list:
|
||||
param["full_text"] = full_text
|
||||
|
||||
indexes_needing_params = {
|
||||
idx: parameterized_indexes[idx]
|
||||
for idx in selected_indexes
|
||||
if idx in parameterized_indexes
|
||||
}
|
||||
|
||||
param_values = {}
|
||||
if indexes_needing_params:
|
||||
dialog = ParameterInputDialog(indexes_needing_params, parent=self)
|
||||
if dialog.exec() != QDialog.DialogCode.Accepted:
|
||||
return None
|
||||
|
||||
param_values = dialog.get_values()
|
||||
if param_values is None:
|
||||
return None
|
||||
|
||||
return (
|
||||
selected_event,
|
||||
file_paths_a,
|
||||
file_paths_b,
|
||||
all_selected_paths,
|
||||
selected_indexes,
|
||||
param_values,
|
||||
)
|
||||
|
||||
|
||||
class CSVUIMixin:
|
||||
|
||||
def setup_csv_ui(self, index_texts):
|
||||
|
||||
# Create mappings: file_path -> participant label and dropdown display text
|
||||
self.participant_map = {} # file_path -> "Participant 1"
|
||||
self.participant_dropdown_items = [] # "Participant 1 (filename)"
|
||||
|
||||
for i, file_path in enumerate(self.haemo_dict.keys(), start=1):
|
||||
short_label = f"Participant {i}"
|
||||
display_label = f"{short_label} ({os.path.basename(file_path)})"
|
||||
self.participant_map[file_path] = short_label
|
||||
self.participant_dropdown_items.append(display_label)
|
||||
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.top_bar = QHBoxLayout()
|
||||
self.layout.addLayout(self.top_bar)
|
||||
|
||||
self.participant_dropdown = self._create_multiselect_dropdown(self.participant_dropdown_items)
|
||||
self.participant_dropdown.currentIndexChanged.connect(self.update_participant_dropdown_label)
|
||||
|
||||
self.index_texts = index_texts
|
||||
|
||||
self.image_index_dropdown = self._create_multiselect_dropdown(self.index_texts)
|
||||
self.image_index_dropdown.currentIndexChanged.connect(self.update_image_index_dropdown_label)
|
||||
|
||||
self.submit_button = QPushButton("Submit")
|
||||
self.submit_button.clicked.connect(self.process_request)
|
||||
|
||||
self.top_bar.addWidget(QLabel("Participants:"))
|
||||
self.top_bar.addWidget(self.participant_dropdown)
|
||||
self.top_bar.addWidget(QLabel("Export Type:"))
|
||||
self.top_bar.addWidget(self.image_index_dropdown)
|
||||
self.top_bar.addWidget(self.submit_button)
|
||||
|
||||
self.scroll = QScrollArea()
|
||||
self.scroll.setWidgetResizable(True)
|
||||
self.scroll_content = QWidget()
|
||||
self.grid_layout = QGridLayout(self.scroll_content)
|
||||
self.scroll.setWidget(self.scroll_content)
|
||||
self.layout.addWidget(self.scroll)
|
||||
|
||||
self.thumb_size = QSize(280, 180)
|
||||
self.showMaximized()
|
||||
|
||||
|
||||
class InterGroupUIMixin:
|
||||
def setup_inter_group_ui(self, index_texts):
|
||||
self.show_all_events = True
|
||||
self._updating_checkstates = False
|
||||
|
||||
# Create mappings: file_path -> participant label and dropdown display text
|
||||
self.participant_map = {} # file_path -> "Participant 1"
|
||||
self.participant_dropdown_items = [] # "Participant 1 (filename)"
|
||||
|
||||
for i, file_path in enumerate(self.haemo_dict.keys(), start=1):
|
||||
short_label = f"Participant {i}"
|
||||
display_label = f"{short_label} ({os.path.basename(file_path)})"
|
||||
self.participant_map[file_path] = short_label
|
||||
self.participant_dropdown_items.append(display_label)
|
||||
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.top_bar = QHBoxLayout()
|
||||
self.layout.addLayout(self.top_bar)
|
||||
|
||||
self.group_to_paths = {}
|
||||
for file_path, group_name in self.group.items():
|
||||
self.group_to_paths.setdefault(group_name, []).append(file_path)
|
||||
|
||||
self.group_names = sorted(self.group_to_paths.keys())
|
||||
|
||||
self.group_dropdown = QComboBox()
|
||||
self.group_dropdown.addItem("<None Selected>")
|
||||
self.group_dropdown.addItems(self.group_names)
|
||||
self.group_dropdown.setCurrentIndex(0)
|
||||
self.group_dropdown.currentIndexChanged.connect(self.update_participant_list_for_group)
|
||||
|
||||
self.participant_dropdown = self._create_multiselect_dropdown(self.participant_dropdown_items)
|
||||
self.participant_dropdown.currentIndexChanged.connect(self.update_participant_dropdown_label)
|
||||
self.participant_dropdown.setEnabled(False)
|
||||
|
||||
self.event_dropdown = QComboBox()
|
||||
self.event_dropdown.addItem("<None Selected>")
|
||||
|
||||
self.index_texts = index_texts
|
||||
self.image_index_dropdown = self._create_multiselect_dropdown(self.index_texts)
|
||||
self.image_index_dropdown.currentIndexChanged.connect(self.update_image_index_dropdown_label)
|
||||
|
||||
self.submit_button = QPushButton("Submit")
|
||||
self.submit_button.clicked.connect(self.process_request)
|
||||
|
||||
self.top_bar.addWidget(QLabel("Group:"))
|
||||
self.top_bar.addWidget(self.group_dropdown)
|
||||
self.top_bar.addWidget(QLabel("Participants:"))
|
||||
self.top_bar.addWidget(self.participant_dropdown)
|
||||
self.top_bar.addWidget(QLabel("Event:"))
|
||||
self.top_bar.addWidget(self.event_dropdown)
|
||||
self.top_bar.addWidget(QLabel("Image Indexes:"))
|
||||
self.top_bar.addWidget(self.image_index_dropdown)
|
||||
self.top_bar.addWidget(self.submit_button)
|
||||
|
||||
self.scroll = QScrollArea()
|
||||
self.scroll.setWidgetResizable(True)
|
||||
self.scroll_content = QWidget()
|
||||
self.grid_layout = QGridLayout(self.scroll_content)
|
||||
self.scroll.setWidget(self.scroll_content)
|
||||
self.layout.addWidget(self.scroll)
|
||||
|
||||
self.thumb_size = QSize(280, 180)
|
||||
self.showMaximized()
|
||||
|
||||
def get_common_request_data(self, parameterized_indexes):
|
||||
selected_event = self.event_dropdown.currentText()
|
||||
if selected_event == "<None Selected>":
|
||||
selected_event = None
|
||||
|
||||
selected_display_names = self._get_checked_items(self.participant_dropdown)
|
||||
selected_file_paths = []
|
||||
for display_name in selected_display_names:
|
||||
for fp, short_label in self.participant_map.items():
|
||||
expected_display = f"{short_label} ({os.path.basename(fp)})"
|
||||
if display_name == expected_display:
|
||||
selected_file_paths.append(fp)
|
||||
break
|
||||
|
||||
if selected_event:
|
||||
valid_paths = []
|
||||
for fp in selected_file_paths:
|
||||
raw = self.haemo_dict.get(fp)
|
||||
# Check if this participant actually has the event in their annotations
|
||||
if raw is not None and hasattr(raw, "annotations"):
|
||||
if selected_event in raw.annotations.description:
|
||||
valid_paths.append(fp)
|
||||
|
||||
selected_file_paths = valid_paths
|
||||
|
||||
selected_indexes = [
|
||||
int(s.split(" ")[0]) for s in self._get_checked_items(self.image_index_dropdown)
|
||||
]
|
||||
|
||||
if not selected_file_paths:
|
||||
print("No participants selected.")
|
||||
return
|
||||
|
||||
# Only keep indexes 0 and 1 that need parameters
|
||||
|
||||
|
||||
# Inject full_text from index_texts
|
||||
for idx, params_list in parameterized_indexes.items():
|
||||
full_text = self.index_texts[idx] if idx < len(self.index_texts) else f"{idx} (No label found)"
|
||||
for param_info in params_list:
|
||||
param_info["full_text"] = full_text
|
||||
|
||||
indexes_needing_params = {idx: parameterized_indexes[idx] for idx in selected_indexes if idx in parameterized_indexes}
|
||||
|
||||
param_values = {}
|
||||
if indexes_needing_params:
|
||||
dialog = ParameterInputDialog(indexes_needing_params, parent=self)
|
||||
if dialog.exec_() == QDialog.Accepted:
|
||||
param_values = dialog.get_values()
|
||||
if param_values is None:
|
||||
return
|
||||
else:
|
||||
return
|
||||
|
||||
return (
|
||||
selected_event,
|
||||
selected_file_paths,
|
||||
selected_indexes,
|
||||
param_values,
|
||||
)
|
||||
|
||||
class ParticipantUIMixin:
|
||||
def setup_participant_ui(self, index_texts):
|
||||
# Create mappings: file_path -> participant label and dropdown display text
|
||||
self.participant_map = {} # file_path -> "Participant 1"
|
||||
self.participant_dropdown_items = [] # "Participant 1 (filename)"
|
||||
|
||||
for i, file_path in enumerate(self.haemo_dict.keys(), start=1):
|
||||
short_label = f"Participant {i}"
|
||||
display_label = f"{short_label} ({os.path.basename(file_path)})"
|
||||
self.participant_map[file_path] = short_label
|
||||
self.participant_dropdown_items.append(display_label)
|
||||
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.top_bar = QHBoxLayout()
|
||||
self.layout.addLayout(self.top_bar)
|
||||
|
||||
self.participant_dropdown = self._create_multiselect_dropdown(self.participant_dropdown_items)
|
||||
self.participant_dropdown.currentIndexChanged.connect(self.update_participant_dropdown_label)
|
||||
|
||||
self.event_dropdown = QComboBox()
|
||||
self.event_dropdown.addItem("<None Selected>")
|
||||
|
||||
|
||||
self.index_texts = index_texts
|
||||
|
||||
self.image_index_dropdown = self._create_multiselect_dropdown(self.index_texts)
|
||||
self.image_index_dropdown.currentIndexChanged.connect(self.update_image_index_dropdown_label)
|
||||
|
||||
self.submit_button = QPushButton("Submit")
|
||||
self.submit_button.clicked.connect(self.process_request)
|
||||
|
||||
self.top_bar.addWidget(QLabel("Participants:"))
|
||||
self.top_bar.addWidget(self.participant_dropdown)
|
||||
self.top_bar.addWidget(QLabel("Event:"))
|
||||
self.top_bar.addWidget(self.event_dropdown)
|
||||
self.top_bar.addWidget(QLabel("Image Indexes:"))
|
||||
self.top_bar.addWidget(self.image_index_dropdown)
|
||||
self.top_bar.addWidget(self.submit_button)
|
||||
|
||||
self.scroll = QScrollArea()
|
||||
self.scroll.setWidgetResizable(True)
|
||||
self.scroll_content = QWidget()
|
||||
self.grid_layout = QGridLayout(self.scroll_content)
|
||||
self.scroll.setWidget(self.scroll_content)
|
||||
self.layout.addWidget(self.scroll)
|
||||
|
||||
self.thumb_size = QSize(280, 180)
|
||||
self.showMaximized()
|
||||
|
||||
|
||||
def get_common_request_data(self, parameterized_indexes):
|
||||
selected_event = self.event_dropdown.currentText()
|
||||
if selected_event == "<None Selected>":
|
||||
selected_event = None
|
||||
|
||||
selected_display_names = self._get_checked_items(self.participant_dropdown)
|
||||
selected_file_paths = []
|
||||
for display_name in selected_display_names:
|
||||
for fp, short_label in self.participant_map.items():
|
||||
expected_display = f"{short_label} ({os.path.basename(fp)})"
|
||||
if display_name == expected_display:
|
||||
selected_file_paths.append(fp)
|
||||
break
|
||||
|
||||
selected_indexes = [
|
||||
int(s.split(" ")[0]) for s in self._get_checked_items(self.image_index_dropdown)
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
# Inject full_text from index_texts
|
||||
for idx, params_list in parameterized_indexes.items():
|
||||
full_text = self.index_texts[idx] if idx < len(self.index_texts) else f"{idx} (No label found)"
|
||||
for param_info in params_list:
|
||||
param_info["full_text"] = full_text
|
||||
|
||||
indexes_needing_params = {idx: parameterized_indexes[idx] for idx in selected_indexes if idx in parameterized_indexes}
|
||||
|
||||
param_values = {}
|
||||
if indexes_needing_params:
|
||||
dialog = ParameterInputDialog(indexes_needing_params, parent=self)
|
||||
if dialog.exec_() == QDialog.Accepted:
|
||||
param_values = dialog.get_values()
|
||||
if param_values is None:
|
||||
return
|
||||
else:
|
||||
return
|
||||
|
||||
return (
|
||||
selected_event,
|
||||
selected_file_paths,
|
||||
selected_indexes,
|
||||
param_values,
|
||||
)
|
||||
Reference in New Issue
Block a user