release worthy?

This commit is contained in:
2026-01-31 23:42:49 -08:00
parent dd2ac058af
commit f1dd9bd184
4 changed files with 805 additions and 251 deletions

572
main.py
View File

@@ -226,6 +226,44 @@ SECTIONS = [
class SaveProjectThread(QThread):
finished_signal = Signal(str)
error_signal = Signal(str)
def __init__(self, filename, project_data):
super().__init__()
self.filename = filename
self.project_data = project_data
def run(self):
try:
import pickle
with open(self.filename, "wb") as f:
pickle.dump(self.project_data, f)
self.finished_signal.emit(self.filename)
except Exception as e:
self.error_signal.emit(str(e))
class SavingOverlay(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.FramelessWindowHint)
self.setModal(True)
self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
layout = QVBoxLayout()
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
label = QLabel("Saving Project…")
label.setStyleSheet("font-size: 18px; color: white; background-color: rgba(0,0,0,150); padding: 20px; border-radius: 10px;")
layout.addWidget(label)
self.setLayout(layout)
class TerminalWindow(QWidget):
def __init__(self, parent=None):
super().__init__(parent, Qt.WindowType.Window)
@@ -2439,6 +2477,371 @@ class ParticipantBrainViewerWidget(QWidget):
class ParticipantFunctionalConnectivityWidget(QWidget):
def __init__(self, haemo_dict, epochs_dict):
super().__init__()
self.setWindowTitle("FLARES Functional Connectivity Viewer [BETA]")
self.haemo_dict = haemo_dict
self.epochs_dict = epochs_dict
QMessageBox.warning(self, "Warning - FLARES", f"Functional Connectivity is still in development and the results should currently be taken with a grain of salt. "
"By clicking OK, you accept that the images generated may not be factual.")
# 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 = [
"0 (Spectral Connectivity Epochs)",
"1 (Envelope Correlation)",
"2 (Betas)",
"3 (Spectral Connectivity Epochs)",
]
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.show_brain_images)
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 _create_multiselect_dropdown(self, items):
combo = FullClickComboBox()
combo.setView(QListView())
model = QStandardItemModel()
combo.setModel(model)
combo.setEditable(True)
combo.lineEdit().setReadOnly(True)
combo.lineEdit().setPlaceholderText("Select...")
dummy_item = QStandardItem("<None Selected>")
dummy_item.setFlags(Qt.ItemIsEnabled)
model.appendRow(dummy_item)
toggle_item = QStandardItem("Toggle Select All")
toggle_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
toggle_item.setData(Qt.Unchecked, Qt.CheckStateRole)
model.appendRow(toggle_item)
for item in items:
standard_item = QStandardItem(item)
standard_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
standard_item.setData(Qt.Unchecked, Qt.CheckStateRole)
model.appendRow(standard_item)
combo.setInsertPolicy(QComboBox.NoInsert)
def on_view_clicked(index):
item = model.itemFromIndex(index)
if item.isCheckable():
new_state = Qt.Checked if item.checkState() == Qt.Unchecked else Qt.Unchecked
item.setCheckState(new_state)
combo.view().pressed.connect(on_view_clicked)
self._updating_checkstates = False
def on_item_changed(item):
if self._updating_checkstates:
return
self._updating_checkstates = True
normal_items = [model.item(i) for i in range(2, model.rowCount())] # skip dummy and toggle
if item == toggle_item:
all_checked = all(i.checkState() == Qt.Checked for i in normal_items)
if all_checked:
for i in normal_items:
i.setCheckState(Qt.Unchecked)
toggle_item.setCheckState(Qt.Unchecked)
else:
for i in normal_items:
i.setCheckState(Qt.Checked)
toggle_item.setCheckState(Qt.Checked)
elif item == dummy_item:
pass
else:
# When normal items change, update toggle item
all_checked = all(i.checkState() == Qt.Checked for i in normal_items)
toggle_item.setCheckState(Qt.Checked if all_checked else Qt.Unchecked)
# Update label text immediately after change
if combo == self.participant_dropdown:
self.update_participant_dropdown_label()
elif combo == self.image_index_dropdown:
self.update_image_index_dropdown_label()
self._updating_checkstates = False
model.itemChanged.connect(on_item_changed)
combo.setInsertPolicy(QComboBox.NoInsert)
return combo
def _get_checked_items(self, combo):
checked = []
model = combo.model()
for i in range(model.rowCount()):
item = model.item(i)
# Skip dummy and toggle items:
if item.text() in ("<None Selected>", "Toggle Select All"):
continue
if item.checkState() == Qt.Checked:
checked.append(item.text())
return checked
def update_participant_dropdown_label(self):
selected = self._get_checked_items(self.participant_dropdown)
if not selected:
self.participant_dropdown.lineEdit().setText("<None Selected>")
else:
# Extract just "Participant N" from "Participant N (filename)"
selected_short = [s.split(" ")[0] + " " + s.split(" ")[1] for s in selected]
self.participant_dropdown.lineEdit().setText(", ".join(selected_short))
self._update_event_dropdown()
def update_image_index_dropdown_label(self):
selected = self._get_checked_items(self.image_index_dropdown)
if not selected:
self.image_index_dropdown.lineEdit().setText("<None Selected>")
else:
# Only show the index part
index_labels = [s.split(" ")[0] for s in selected]
self.image_index_dropdown.lineEdit().setText(", ".join(index_labels))
def _update_event_dropdown(self):
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 not selected_file_paths:
self.event_dropdown.clear()
self.event_dropdown.addItem("<None Selected>")
return
annotation_sets = []
for file_path in selected_file_paths:
raw = self.haemo_dict.get(file_path)
if raw is None or not hasattr(raw, "annotations"):
continue
annotations = set(raw.annotations.description)
annotation_sets.append(annotations)
if not annotation_sets:
self.event_dropdown.clear()
self.event_dropdown.addItem("<None Selected>")
return
shared_annotations = set.intersection(*annotation_sets)
self.event_dropdown.clear()
self.event_dropdown.addItem("<None Selected>")
for ann in sorted(shared_annotations):
self.event_dropdown.addItem(ann)
def show_brain_images(self):
import flares
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)
]
parameterized_indexes = {
0: [
{
"key": "n_lines",
"label": "<Description>",
"default": "20",
"type": int,
},
{
"key": "vmin",
"label": "<Description>",
"default": "0.9",
"type": float,
},
],
1: [
{
"key": "n_lines",
"label": "<Description>",
"default": "20",
"type": int,
},
{
"key": "vmin",
"label": "<Description>",
"default": "0.9",
"type": float,
},
],
2: [
{
"key": "n_lines",
"label": "<Description>",
"default": "20",
"type": int,
},
{
"key": "vmin",
"label": "<Description>",
"default": "0.9",
"type": float,
},
],
3: [
{
"key": "n_lines",
"label": "<Description>",
"default": "20",
"type": int,
},
{
"key": "vmin",
"label": "<Description>",
"default": "0.9",
"type": float,
},
],
}
# 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
# Pass the necessary arguments to each method
for file_path in selected_file_paths:
haemo_obj = self.haemo_dict.get(file_path)
epochs_obj = self.epochs_dict.get(file_path)
if haemo_obj is None:
raise Exception("How did we get here?")
for idx in selected_indexes:
if idx == 0:
params = param_values.get(idx, {})
n_lines = params.get("n_lines", None)
vmin = params.get("vmin", None)
if n_lines is None or vmin is None:
print(f"Missing parameters for index {idx}, skipping.")
continue
flares.functional_connectivity_spectral_epochs(epochs_obj, n_lines, vmin)
elif idx == 1:
params = param_values.get(idx, {})
n_lines = params.get("n_lines", None)
vmin = params.get("vmin", None)
if n_lines is None or vmin is None:
print(f"Missing parameters for index {idx}, skipping.")
continue
flares.functional_connectivity_envelope(epochs_obj, n_lines, vmin)
elif idx == 2:
params = param_values.get(idx, {})
n_lines = params.get("n_lines", None)
vmin = params.get("vmin", None)
if n_lines is None or vmin is None:
print(f"Missing parameters for index {idx}, skipping.")
continue
flares.functional_connectivity_betas(haemo_obj, n_lines, vmin, selected_event)
elif idx == 3:
params = param_values.get(idx, {})
n_lines = params.get("n_lines", None)
vmin = params.get("vmin", None)
if n_lines is None or vmin is None:
print(f"Missing parameters for index {idx}, skipping.")
continue
flares.functional_connectivity_spectral_time(epochs_obj, n_lines, vmin)
else:
print(f"No method defined for index {idx}")
class MultiProgressDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
@@ -3000,7 +3403,7 @@ class ExportDataAsCSVViewerWidget(QWidget):
# Open save dialog
save_path, _ = QFileDialog.getSaveFileName(
self,
"Save SNIRF File As",
"Save CSV File As",
suggested_name,
"CSV Files (*.csv)"
)
@@ -3017,7 +3420,7 @@ class ExportDataAsCSVViewerWidget(QWidget):
QMessageBox.information(self, "Success", "CSV file has been saved.")
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to update SNIRF file:\n{e}")
QMessageBox.critical(self, "Error", f"Failed to update CSV file:\n{e}")
elif idx == 1:
@@ -3027,7 +3430,7 @@ class ExportDataAsCSVViewerWidget(QWidget):
# Open save dialog
save_path, _ = QFileDialog.getSaveFileName(
self,
"Save SNIRF File As",
"Save CSV File As",
suggested_name,
"CSV Files (*.csv)"
)
@@ -3071,7 +3474,7 @@ class ExportDataAsCSVViewerWidget(QWidget):
win.show()
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to update SNIRF file:\n{e}")
QMessageBox.critical(self, "Error", f"Failed to update CSV file:\n{e}")
else:
@@ -4263,10 +4666,15 @@ class GroupBrainViewerWidget(QWidget):
class ViewerLauncherWidget(QWidget):
def __init__(self, haemo_dict, fig_bytes_dict, cha_dict, contrast_results_dict, df_ind, design_matrix, group):
def __init__(self, haemo_dict, config_dict, fig_bytes_dict, cha_dict, contrast_results_dict, df_ind, design_matrix, epochs_dict):
super().__init__()
self.setWindowTitle("Viewer Launcher")
group_dict = {
file_path: config.get("GROUP", "Unknown") # default if GROUP missing
for file_path, config in config_dict.items()
}
layout = QVBoxLayout(self)
btn1 = QPushButton("Open Participant Viewer")
@@ -4278,19 +4686,23 @@ class ViewerLauncherWidget(QWidget):
btn3 = QPushButton("Open Participant Fold Channels Viewer")
btn3.clicked.connect(lambda: self.open_participant_fold_channels_viewer(haemo_dict, cha_dict))
btn7 = QPushButton("Open Functional Connectivity Viewer [BETA]")
btn7.clicked.connect(lambda: self.open_participant_functional_connectivity_viewer(haemo_dict, epochs_dict))
btn4 = QPushButton("Open Inter-Group Viewer")
btn4.clicked.connect(lambda: self.open_group_viewer(haemo_dict, cha_dict, df_ind, design_matrix, contrast_results_dict, group))
btn4.clicked.connect(lambda: self.open_group_viewer(haemo_dict, cha_dict, df_ind, design_matrix, contrast_results_dict, group_dict))
btn5 = QPushButton("Open Cross Group Brain Viewer")
btn5.clicked.connect(lambda: self.open_group_brain_viewer(haemo_dict, df_ind, design_matrix, group, contrast_results_dict))
btn5.clicked.connect(lambda: self.open_group_brain_viewer(haemo_dict, df_ind, design_matrix, group_dict, contrast_results_dict))
btn6 = QPushButton("Open Export Data As CSV Viewer")
btn6.clicked.connect(lambda: self.open_export_data_as_csv_viewer(haemo_dict, cha_dict, df_ind, design_matrix, group, contrast_results_dict))
btn6.clicked.connect(lambda: self.open_export_data_as_csv_viewer(haemo_dict, cha_dict, df_ind, design_matrix, group_dict, contrast_results_dict))
layout.addWidget(btn1)
layout.addWidget(btn2)
layout.addWidget(btn3)
layout.addWidget(btn7)
layout.addWidget(btn4)
layout.addWidget(btn5)
layout.addWidget(btn6)
@@ -4307,6 +4719,10 @@ class ViewerLauncherWidget(QWidget):
self.participant_fold_channels_viewer = ParticipantFoldChannelsWidget(haemo_dict, cha_dict)
self.participant_fold_channels_viewer.show()
def open_participant_functional_connectivity_viewer(self, haemo_dict, epochs_dict):
self.participant_brain_viewer = ParticipantFunctionalConnectivityWidget(haemo_dict, epochs_dict)
self.participant_brain_viewer.show()
def open_group_viewer(self, haemo_dict, cha_dict, df_ind, design_matrix, contrast_results_dict, group):
self.participant_brain_viewer = GroupViewerWidget(haemo_dict, cha_dict, df_ind, design_matrix, contrast_results_dict, group)
self.participant_brain_viewer.show()
@@ -4343,7 +4759,8 @@ class MainApplication(QMainWindow):
self.section_widget = None
self.first_run = True
self.is_2d_bypass = False
self.incompatible_save_bypass = False
self.files_total = 0 # total number of files to process
self.files_done = set() # set of file paths done (success or fail)
self.files_failed = set() # set of failed file paths
@@ -4593,7 +5010,8 @@ class MainApplication(QMainWindow):
preferences_menu = menu_bar.addMenu("Preferences")
preferences_actions = [
("2D Data Bypass", "Ctrl+B", self.is_2d_bypass_func, resource_path("icons/info_24dp_1F1F1F.svg"))
("2D Data Bypass", "", self.is_2d_bypass_func, resource_path("icons/info_24dp_1F1F1F.svg")),
("Incompatible Save Bypass", "", self.incompatable_save_bypass_func, resource_path("icons/info_24dp_1F1F1F.svg"))
]
for name, shortcut, slot, icon in preferences_actions:
preferences_menu.addAction(make_action(name, shortcut, slot, icon=icon, checkable=True, checked=False))
@@ -4643,15 +5061,13 @@ class MainApplication(QMainWindow):
self.statusBar().clearMessage()
self.raw_haemo_dict = None
self.config_dict = None
self.epochs_dict = None
self.fig_bytes_dict = None
self.cha_dict = None
self.contrast_results_dict = None
self.df_ind_dict = None
self.design_matrix_dict = None
self.age_dict = None
self.gender_dict = None
self.group_dict = None
self.valid_dict = None
# Reset any visible UI elements
@@ -4662,7 +5078,7 @@ class MainApplication(QMainWindow):
def open_launcher_window(self):
self.launcher_window = ViewerLauncherWidget(self.raw_haemo_dict, self.fig_bytes_dict, self.cha_dict, self.contrast_results_dict, self.df_ind_dict, self.design_matrix_dict, self.group_dict)
self.launcher_window = ViewerLauncherWidget(self.raw_haemo_dict, self.config_dict, self.fig_bytes_dict, self.cha_dict, self.contrast_results_dict, self.df_ind_dict, self.design_matrix_dict, self.epochs_dict)
self.launcher_window.show()
@@ -4681,6 +5097,9 @@ class MainApplication(QMainWindow):
def is_2d_bypass_func(self, checked):
self.is_2d_bypass = checked
def incompatable_save_bypass_func(self, checked):
self.incompatible_save_bypass = checked
def about_window(self):
if self.about is None or not self.about.isVisible():
self.about = AboutWindow(self)
@@ -4839,19 +5258,19 @@ class MainApplication(QMainWindow):
for bubble in self.bubble_widgets.values()
}
version = CURRENT_VERSION
project_data = {
"version": version,
"file_list": file_list,
"progress_states": progress_states,
"raw_haemo_dict": self.raw_haemo_dict,
"config_dict": self.config_dict,
"epochs_dict": self.epochs_dict,
"fig_bytes_dict": self.fig_bytes_dict,
"cha_dict": self.cha_dict,
"contrast_results_dict": self.contrast_results_dict,
"df_ind_dict": self.df_ind_dict,
"design_matrix_dict": self.design_matrix_dict,
"age_dict": self.age_dict,
"gender_dict": self.gender_dict,
"group_dict": self.group_dict,
"valid_dict": self.valid_dict,
}
@@ -4866,10 +5285,24 @@ class MainApplication(QMainWindow):
project_data = sanitize(project_data)
with open(filename, "wb") as f:
pickle.dump(project_data, f)
QMessageBox.information(self, "Success", f"Project saved to:\n{filename}")
self.saving_overlay = SavingOverlay(self)
self.saving_overlay.resize(self.size()) # Cover the main window
self.saving_overlay.show()
# Start the background save thread
self.save_thread = SaveProjectThread(filename, project_data)
# When finished, close overlay and show success
self.save_thread.finished_signal.connect(lambda f: (
self.saving_overlay.close(),
QMessageBox.information(self, "Success", f"Project saved to:\n{f}")
))
self.save_thread.error_signal.connect(lambda e: (
self.saving_overlay.close(),
QMessageBox.critical(self, "Error", f"Failed to save project:\n{e}")
))
self.save_thread.start()
except Exception as e:
if not onCrash:
@@ -4888,16 +5321,27 @@ class MainApplication(QMainWindow):
with open(filename, "rb") as f:
data = pickle.load(f)
# Check for saves prior to 1.2.0
if "version" not in data:
print(self.incompatible_save_bypass)
if self.incompatible_save_bypass:
QMessageBox.warning(self, "Warning - FLARES", f"This project was saved in an earlier version of FLARES (<=1.1.7) and is potentially not compatible with this version. "
"You are receiving this warning because you have 'Incompatible Save Bypass' turned on. FLARES will now attempt to load the project. It is strongly "
"recommended to recreate the project file.")
else:
QMessageBox.critical(self, "Error - FLARES", f"This project was saved in an earlier version of FLARES (<=1.1.7) and is potentially not compatible with this version. "
"The file can attempt to be loaded if 'Incompatible Save Bypass' is selected in the 'Preferences' menu.")
return
self.raw_haemo_dict = data.get("raw_haemo_dict", {})
self.config_dict = data.get("config_dict", {})
self.epochs_dict = data.get("epochs_dict", {})
self.fig_bytes_dict = data.get("fig_bytes_dict", {})
self.cha_dict = data.get("cha_dict", {})
self.contrast_results_dict = data.get("contrast_results_dict", {})
self.df_ind_dict = data.get("df_ind_dict", {})
self.design_matrix_dict = data.get("design_matrix_dict", {})
self.age_dict = data.get("age_dict", {})
self.gender_dict = data.get("gender_dict", {})
self.group_dict = data.get("group_dict", {})
self.valid_dict = data.get("valid_dict", {})
project_dir = Path(filename).parent
@@ -4913,7 +5357,27 @@ class MainApplication(QMainWindow):
}
self.show_files_as_bubbles_from_list(file_list, progress_states, filename)
for file_path, config in self.config_dict.items():
# Only store AGE, GENDER, GROUP
self.file_metadata[file_path] = {
key: str(config.get(key, "")) # convert to str for QLineEdit
for key in ["AGE", "GENDER", "GROUP"]
}
if self.config_dict:
first_file = next(iter(self.config_dict.keys()))
self.current_file = first_file
# Update meta fields (AGE/GENDER/GROUP)
for key, field in self.meta_fields.items():
field.setText(self.file_metadata[first_file][key])
self.right_column_widget.show()
# Restore all other constants to the parameter sections
first_config = self.config_dict[first_file]
self.restore_sections_from_config(first_config)
# Re-enable buttons
# self.button1.setVisible(True)
self.button3.setVisible(True)
@@ -4924,6 +5388,54 @@ class MainApplication(QMainWindow):
QMessageBox.critical(self, "Error", f"Failed to load project:\n{e}")
def restore_sections_from_config(self, config):
"""
Fill all ParamSection widgets with values from a participant's config.
"""
for section_widget in self.param_sections:
widgets_dict = getattr(section_widget, 'widgets', None)
if widgets_dict is None:
continue
for name, widget_info in widgets_dict.items():
if name not in config:
continue
value = config[name]
print(f"Restoring {name} = {value}")
widget = widget_info["widget"]
w_type = widget_info.get("type")
# QLineEdit (int, float, str)
if isinstance(widget, QLineEdit):
widget.blockSignals(True)
widget.setText(str(value))
widget.blockSignals(False)
widget.update()
# QComboBox (bool, list)
elif isinstance(widget, QComboBox):
widget.blockSignals(True)
widget.setCurrentText(str(value))
widget.blockSignals(False)
widget.update()
# QSpinBox (range)
elif isinstance(widget, QSpinBox):
widget.blockSignals(True)
try:
widget.setValue(int(value))
except Exception:
pass
widget.blockSignals(False)
widget.update()
# After restoring, make sure dependencies are updated
if hasattr(section_widget, 'update_dependencies'):
section_widget.update_dependencies()
def show_files_as_bubbles(self, folder_paths):
@@ -5366,29 +5878,25 @@ class MainApplication(QMainWindow):
# TODO: Is this check needed? Edit: yes very much so
if getattr(self, 'raw_haemo_dict', None) is None:
self.raw_haemo_dict = {}
self.config_dict = {}
self.epochs_dict = {}
self.fig_bytes_dict = {}
self.cha_dict = {}
self.contrast_results_dict = {}
self.df_ind_dict = {}
self.design_matrix_dict = {}
self.age_dict = {}
self.gender_dict = {}
self.group_dict = {}
self.valid_dict = {}
# Combine all results into the dicts
for file_path, (raw_haemo, epochs, fig_bytes, cha, contrast_results, df_ind, design_matrix, age, gender, group, valid) in results.items():
for file_path, (raw_haemo, config, epochs, fig_bytes, cha, contrast_results, df_ind, design_matrix, valid) in results.items():
self.raw_haemo_dict[file_path] = raw_haemo
self.config_dict[file_path] = config
self.epochs_dict[file_path] = epochs
self.fig_bytes_dict[file_path] = fig_bytes
self.cha_dict[file_path] = cha
self.contrast_results_dict[file_path] = contrast_results
self.df_ind_dict[file_path] = df_ind
self.design_matrix_dict[file_path] = design_matrix
self.age_dict[file_path] = age
self.gender_dict[file_path] = gender
self.group_dict[file_path] = group
self.valid_dict[file_path] = valid
# self.statusbar.showMessage(f"Processing complete! Time elapsed: {elapsed_time:.2f} seconds")