unhardcoding and general cleanup

This commit is contained in:
2026-07-17 00:07:56 -07:00
parent 2ff7cda93a
commit 0680718398
7 changed files with 1614 additions and 555 deletions
+115 -19
View File
@@ -7,6 +7,7 @@ License: GPL-3.0
"""
import os
import json
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
@@ -84,28 +85,53 @@ class ParameterInputDialog(QDialog):
self.params_dict = params_dict
self.inputs = {} # {(idx, param_key): QLineEdit}
layout = QVBoxLayout(self)
main_layout = QVBoxLayout(self)
intro_label = QLabel(
"Some methods require parameters to continue:\n"
"Clicking OK will simply use default values if input is left empty."
)
layout.addWidget(intro_label)
main_layout.addWidget(intro_label)
self.setMinimumWidth(400)
self.scroll = QScrollArea()
self.scroll.setWidgetResizable(True)
self.scroll.setMaximumHeight(800)
self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.scroll_content = QWidget()
self.scroll_layout = QVBoxLayout(self.scroll_content)
self.scroll_layout.setContentsMargins(10, 10, 10, 10)
self.scroll.setWidget(self.scroll_content)
main_layout.addWidget(self.scroll)
for idx, param_list in params_dict.items():
full_text = param_list[0].get('full_text', f"Index [{idx}]")
group_label = QLabel(f"{full_text} requires parameters:")
group_label.setStyleSheet("font-weight: bold; margin-top: 10px;")
layout.addWidget(group_label)
self.scroll_layout.addWidget(group_label)
for param_info in param_list:
label = QLabel(param_info["label"])
layout.addWidget(label)
self.scroll_layout.addWidget(label)
line_edit = QLineEdit(self)
line_edit.setPlaceholderText(str(param_info.get("default", "")))
layout.addWidget(line_edit)
if param_info.get("type") == list:
widget = QComboBox(self)
# Convert options to string just in case they aren't
options = [str(opt) for opt in param_info.get("options", [])]
widget.addItems(options)
# Set default choice if it exists in the options list
default_val = str(param_info.get("default", ""))
if default_val in options:
widget.setCurrentText(default_val)
else:
widget = QLineEdit(self)
widget.setPlaceholderText(str(param_info.get("default", "")))
self.inputs[(idx, param_info["key"])] = line_edit
self.scroll_layout.addWidget(widget)
self.inputs[(idx, param_info["key"])] = widget
# Buttons
btn_layout = QHBoxLayout()
@@ -113,7 +139,7 @@ class ParameterInputDialog(QDialog):
cancel_btn = QPushButton("Cancel", self)
btn_layout.addWidget(ok_btn)
btn_layout.addWidget(cancel_btn)
layout.addLayout(btn_layout)
main_layout.addLayout(btn_layout)
ok_btn.clicked.connect(self.accept)
cancel_btn.clicked.connect(self.reject)
@@ -131,8 +157,11 @@ class ParameterInputDialog(QDialog):
Returns None if validation fails (error dialog shown).
"""
values = {}
for (idx, param_key), line_edit in self.inputs.items():
text = line_edit.text().strip()
for (idx, param_key), widget in self.inputs.items():
if isinstance(widget, QComboBox):
text = widget.currentText().strip()
else:
text = widget.text().strip()
# Find param info dict
param_info = None
@@ -164,14 +193,15 @@ class ParameterInputDialog(QDialog):
val = False
else:
raise ValueError(f"Invalid bool value: {text}")
elif param_type == str:
elif param_type in (str, list):
val = text
else:
val = text # fallback
except (ValueError, TypeError):
type_name = "list option" if param_type == list else param_type.__name__
self._show_error(
f"Invalid input for index {idx} parameter '{param_key}': '{text}'\n"
f"Expected type: {param_type.__name__}"
f"Expected type: {type_name}"
)
return None
@@ -1055,7 +1085,7 @@ class FlaresBaseWidget(QWidget):
class CrossGroupUIMixin:
def setup_cross_group_ui(self, index_texts):
def setup_cross_group_ui(self, index_texts, placeholder_text=""):
self.group_to_paths = {}
for file_path, group_name in self.group_dict.items():
@@ -1130,6 +1160,10 @@ class CrossGroupUIMixin:
self.scroll_content = QWidget()
self.grid_layout = QGridLayout(self.scroll_content)
self.scroll_area.setWidget(self.scroll_content)
self.placeholder_label = QLabel(placeholder_text)
self.grid_layout.addWidget(self.placeholder_label, 0, 0)
self.placeholder_label.setWordWrap(True)
self.placeholder_label.setScaledContents(True)
self.main_layout.addWidget(self.scroll_area)
self.thumb_size = QSize(280, 180)
@@ -1307,7 +1341,7 @@ class CSVUIMixin:
class InterGroupUIMixin:
def setup_inter_group_ui(self, index_texts):
def setup_inter_group_ui(self, index_texts, placeholder_text=""):
self.show_all_events = True
self._updating_checkstates = False
@@ -1366,12 +1400,16 @@ class InterGroupUIMixin:
self.scroll_content = QWidget()
self.grid_layout = QGridLayout(self.scroll_content)
self.scroll.setWidget(self.scroll_content)
self.placeholder_label = QLabel(placeholder_text)
self.grid_layout.addWidget(self.placeholder_label, 0, 0)
self.placeholder_label.setWordWrap(True)
self.placeholder_label.setScaledContents(True)
self.layout.addWidget(self.scroll)
self.thumb_size = QSize(280, 180)
self.showMaximized()
def get_common_request_data(self, parameterized_indexes):
def get_common_request_data(self, parameterized_indexes, json_location=None, contrast_dfs=None):
selected_event = self.event_dropdown.currentText()
if selected_event == "<None Selected>":
selected_event = None
@@ -1403,9 +1441,7 @@ class InterGroupUIMixin:
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():
@@ -1415,6 +1451,66 @@ class InterGroupUIMixin:
indexes_needing_params = {idx: parameterized_indexes[idx] for idx in selected_indexes if idx in parameterized_indexes}
dynamic_rois = []
# 1. Check for the JSON file and parse ROI names
if os.path.exists(json_location):
try:
with open(json_location, 'r', encoding='utf-8') as f:
regions_data = json.load(f)
# Extract "name" from each region under "regions_of_interest"
regions_list = regions_data.get("regions_of_interest", [])
dynamic_rois = [region["name"] for region in regions_list if "name" in region]
except Exception as e:
# Safe log if JSON is corrupted or unreadable
print(f"Error reading ROI configurations from {json_location}: {e}")
# Fallback to prevent UI crashes if JSON file doesn't exist or is empty
if not dynamic_rois:
dynamic_rois = ["Option 1", "Option 2"]
dynamic_contrasts = []
if contrast_dfs:
contrast_set = set()
for fp in selected_file_paths:
# Get the contrasts dictionary associated with this file path
file_contrasts = contrast_dfs.get(fp, {})
for contrast_name in file_contrasts.keys():
# If no event is selected, display all contrasts.
# If an event is selected, only keep contrasts containing the event name as a substring.
if selected_event is None or selected_event in contrast_name:
contrast_set.add(contrast_name)
# Sort them cleanly for the UI
dynamic_contrasts = sorted(list(contrast_set))
# 2. Loop through the active parameters needing input and intercept 'roi_a' and 'roi_b'
for idx, params_list in indexes_needing_params.items():
for param_info in params_list:
if param_info["key"] == "roi_a":
# Inject options list dynamically
param_info["options"] = dynamic_rois
# Default to the very first item
param_info["default"] = dynamic_rois[0] if dynamic_rois else ""
elif param_info["key"] == "roi_b":
# Inject the same options list
param_info["options"] = dynamic_rois
# Default to the first item not taken (index 1), with safety fallbacks
if len(dynamic_rois) > 1:
param_info["default"] = dynamic_rois[1]
elif len(dynamic_rois) == 1:
param_info["default"] = dynamic_rois[0]
else:
param_info["default"] = ""
elif param_info["key"] == "contrast_name":
param_info["options"] = dynamic_contrasts
param_info["default"] = dynamic_contrasts[0] if dynamic_contrasts else ""
param_values = {}
if indexes_needing_params:
dialog = ParameterInputDialog(indexes_needing_params, parent=self)