mostly finish rewrite

This commit is contained in:
2026-06-28 10:10:27 -07:00
parent f10eb64332
commit ddb30d98f2
18 changed files with 2116 additions and 2350 deletions
+8
View File
@@ -1,3 +1,11 @@
"""
Filename: about.py
Description: About window for FLARES
Author: Tyler de Zeeuw
License: GPL-3.0
"""
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel
from PySide6.QtCore import Qt
+8
View File
@@ -1,3 +1,11 @@
"""
Filename: terminal.py
Description: Terminal window for FLARES
Author: Tyler de Zeeuw
License: GPL-3.0
"""
from PySide6.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QLineEdit
from PySide6.QtCore import Qt
+10 -4
View File
@@ -1,3 +1,11 @@
"""
Filename: updateevents.py
Description: Methods to update snirf events for FLARES
Author: Tyler de Zeeuw
License: GPL-3.0
"""
import os
import json
from enum import Enum, auto
@@ -26,7 +34,7 @@ class UpdateEventsWindow(QWidget):
self.mode = mode
self.caller = caller or self.__class__.__name__
self.setWindowTitle("Update event markers")
self.setWindowTitle(f"Update event markers - {APP_NAME.upper()}")
self.resize(760, 200)
print("INIT MODE:", mode)
@@ -844,6 +852,4 @@ class UpdateEventsBlazesWindow(QWidget):
QMessageBox.information(self, "Success",
f"Aligned {len(onsets)} events.\n(Filtered out {skipped_count} short events)")
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to update SNIRF file:\n{e}")
QMessageBox.critical(self, "Error", f"Failed to update SNIRF file:\n{e}")
+12 -3
View File
@@ -1,3 +1,11 @@
"""
Filename: updateoptodes.py
Description: Methods to update optode locations for FLARES
Author: Tyler de Zeeuw
License: GPL-3.0
"""
import os
from pathlib import Path
@@ -9,6 +17,7 @@ from PySide6.QtCore import Qt
from mne.io import read_raw_snirf
from mne_nirs.io import write_raw_snirf
from mne.channels import make_dig_montage
from src.shared.shareddata import APP_NAME
@@ -17,7 +26,7 @@ class UpdateOptodesWindow(QWidget):
def __init__(self, parent=None):
super().__init__(parent, Qt.WindowType.Window)
self.setWindowTitle("Update optode positions")
self.setWindowTitle(f"Update optode positions - {APP_NAME.upper()}")
self.resize(760, 200)
self.label_file_a = QLabel("SNIRF file:")
@@ -136,7 +145,7 @@ class UpdateOptodesWindow(QWidget):
def show_help_popup(self, text):
msg = QMessageBox(self)
msg.setWindowTitle(f"Parameter Info - {APP_NAME.upper}")
msg.setWindowTitle(f"Parameter Info - {APP_NAME.upper()}")
msg.setText(text)
msg.exec()
@@ -284,4 +293,4 @@ class UpdateOptodesWindow(QWidget):
# Read the SNIRF file, set the montage, and write it back
raw = read_raw_snirf(file_a, preload=True)
raw.set_montage(initial_montage)
write_raw_snirf(raw, save_path)
write_raw_snirf(raw, save_path)
+10 -2
View File
@@ -1,7 +1,15 @@
"""
Filename: userguide.py
Description: User guide for FLARES
Author: Tyler de Zeeuw
License: GPL-3.0
"""
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel
from PySide6.QtCore import Qt
from src.shared.shareddata import APP_NAME, PIPELINE_STAGES
from src.shared.shareddata import APP_NAME, PIPELINE_STAGES, WIKI_URL
class UserGuideWindow(QWidget):
@@ -22,7 +30,7 @@ class UserGuideWindow(QWidget):
label2_text = "\n".join(f"Stage {idx + 1}: {name}" for idx, name in enumerate(PIPELINE_STAGES)) + "\n"
label2 = QLabel(label2_text, self)
label3 = QLabel(f"For more information, visit the Git wiki page <a href='https://git.research.dezeeuw.ca/tyler/{APP_NAME}/wiki'>here</a>.", self)
label3 = QLabel(f"For more information, visit the Git wiki page <a href='{WIKI_URL}'>here</a>.", self)
label3.setTextFormat(Qt.TextFormat.RichText)
label3.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction)
label3.setOpenExternalLinks(True)
+122
View File
@@ -0,0 +1,122 @@
"""
Filename: viewerlauncher.py
Description: Analysis options launcher for FLARES
Author: Tyler de Zeeuw
License: GPL-3.0
"""
from PySide6.QtWidgets import QPushButton, QWidget, QVBoxLayout
from PySide6.QtCore import QTimer
from src.analysis.exportcsv import ExportDataAsCSVViewerWidget
from src.analysis.group import GroupViewerWidget
from src.analysis.groupbrain import GroupBrainViewerWidget
from src.analysis.groupfunctionalconnectivity import GroupFunctionalConnectivityWidget
from src.analysis.participant import ParticipantViewerWidget
from src.analysis.participantbrain import ParticipantBrainViewerWidget
from src.analysis.participantfoldchannels import ParticipantFoldChannelsWidget
from src.analysis.participantfunctionalconnectivity import ParticipantFunctionalConnectivityWidget
from src.shared.shareddata import APP_NAME
class ViewerLauncherWidget(QWidget):
def __init__(self, haemo_dict, config_dict, fig_bytes_dict, cha_dict, contrast_results_dict, df_ind, design_matrix, epochs_dict, folding_bypass):
super().__init__()
self.setWindowTitle(f"Viewer Launcher - {APP_NAME.upper()}")
group_dict = {
file_path: config.get("GROUP", "Unknown")
for file_path, config in config_dict.items()
}
def launch(func, btn, *args):
func(*args)
self._trigger_success(btn)
layout = QVBoxLayout(self)
btn1 = QPushButton("Open Participant Viewer")
btn1.clicked.connect(lambda: launch(self.open_participant_viewer, btn1, haemo_dict, fig_bytes_dict))
btn1.setEnabled(not folding_bypass)
btn2 = QPushButton("Open Participant Brain Viewer")
btn2.clicked.connect(lambda: launch(self.open_participant_brain_viewer, btn2, haemo_dict, cha_dict))
btn2.setEnabled(not folding_bypass)
btn3 = QPushButton("Open Participant Fold Channels Viewer")
btn3.clicked.connect(lambda: launch(self.open_participant_fold_channels_viewer, btn3, haemo_dict, cha_dict))
btn7 = QPushButton("Open Functional Connectivity Viewer [BETA]")
btn7.clicked.connect(lambda: launch(self.open_participant_functional_connectivity_viewer, btn7, haemo_dict, epochs_dict))
btn7.setEnabled(not folding_bypass)
btn8 = QPushButton("Open Group Functional Connectivity Viewer [BETA]")
btn8.clicked.connect(lambda: launch(self.open_group_functional_connectivity_viewer, btn8, haemo_dict, group_dict, config_dict))
btn8.setEnabled(not folding_bypass)
btn4 = QPushButton("Open Inter-Group Viewer")
btn4.clicked.connect(lambda: launch(self.open_group_viewer, btn4, haemo_dict, cha_dict, df_ind, design_matrix, contrast_results_dict, group_dict))
btn4.setEnabled(not folding_bypass)
btn5 = QPushButton("Open Cross Group Brain Viewer")
btn5.clicked.connect(lambda: launch(self.open_group_brain_viewer, btn5, haemo_dict, df_ind, design_matrix, group_dict, contrast_results_dict))
btn5.setEnabled(not folding_bypass)
btn6 = QPushButton("Open Export Data As CSV Viewer")
btn6.clicked.connect(lambda: launch(self.open_export_data_as_csv_viewer, btn6, haemo_dict, cha_dict, df_ind, design_matrix, group_dict, contrast_results_dict))
btn6.setEnabled(not folding_bypass)
layout.addWidget(btn1)
layout.addWidget(btn2)
layout.addWidget(btn3)
layout.addWidget(btn7)
layout.addWidget(btn8)
layout.addWidget(btn4)
layout.addWidget(btn5)
layout.addWidget(btn6)
def open_participant_viewer(self, haemo_dict, fig_bytes_dict):
self.participant_viewer = ParticipantViewerWidget(haemo_dict, fig_bytes_dict)
self.participant_viewer.show()
def open_participant_brain_viewer(self, haemo_dict, cha_dict):
self.participant_brain_viewer = ParticipantBrainViewerWidget(haemo_dict, cha_dict)
self.participant_brain_viewer.show()
def open_participant_fold_channels_viewer(self, haemo_dict, cha_dict):
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_functional_connectivity_viewer(self, haemo_dict, group, config_dict):
self.participant_brain_viewer = GroupFunctionalConnectivityWidget(haemo_dict, group, config_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()
def open_group_brain_viewer(self, haemo_dict, df_ind, design_matrix, group, contrast_results_dict):
self.participant_brain_viewer = GroupBrainViewerWidget(haemo_dict, df_ind, design_matrix, group, contrast_results_dict)
self.participant_brain_viewer.show()
def open_export_data_as_csv_viewer(self, haemo_dict, cha_dict, df_ind, design_matrix, group, contrast_results_dict):
self.export_data_as_csv_viewer = ExportDataAsCSVViewerWidget(haemo_dict, cha_dict, df_ind, design_matrix, group, contrast_results_dict)
self.export_data_as_csv_viewer.show()
def _trigger_success(self, button):
"""Temporarily adds a green checkmark to the button text."""
original_text = button.text()
button.setText(f"{original_text}")
button.setStyleSheet("color: green; font-weight: bold;")
# Revert after 1 second
QTimer.singleShot(1000, lambda: self._revert_button(button, original_text))
def _revert_button(self, button, original_text):
button.setText(original_text)
button.setStyleSheet("")
+18 -12
View File
@@ -1,9 +1,17 @@
"""
Filename: welcome.py
Description: Welcome dialog for FLARES
Author: Tyler de Zeeuw
License: GPL-3.0
"""
from PySide6.QtWidgets import QTextBrowser, QVBoxLayout, QLabel, QDialog, QHBoxLayout, QPushButton
from PySide6.QtGui import QDesktopServices, QIcon
from PySide6.QtCore import QUrl
from PySide6.QtNetwork import QNetworkAccessManager, QNetworkRequest
from src.shared.shareddata import APP_NAME, CURRENT_VERSION, resource_path
from src.shared.shareddata import APP_NAME, CURRENT_VERSION, CHANGELOG_URL, resource_path
class WelcomeDialog(QDialog):
@@ -13,13 +21,13 @@ class WelcomeDialog(QDialog):
self.setMinimumSize(550, 450)
self.resize(800, 500)
# Main Layout
layout = QVBoxLayout(self)
# Header Layout (Logo + App Name)
header_layout = QHBoxLayout()
logo_label = QLabel(self)
logo_label.setPixmap(QIcon(resource_path("icons/main.ico")).pixmap(48, 48)) # Fits cleanly in a header
# NOTE: might not work on mac and need the icns file
logo_label.setPixmap(QIcon(resource_path("icons/main.ico")).pixmap(48, 48))
if direct:
title_label = QLabel(f"<h2>{APP_NAME.upper()} has been sucessfully updated to version {CURRENT_VERSION}!</h2>", self)
else:
@@ -30,19 +38,19 @@ class WelcomeDialog(QDialog):
header_layout.addStretch()
layout.addLayout(header_layout)
# Text Browser Area (Automatically converts Markdown syntax into clean formatted UI text)
self.text_browser = QTextBrowser(self)
self.text_browser.setHtml("<p style='color: gray;'>Loading latest updates from server...</p>")
self.text_browser.setOpenLinks(False) # Don't open links inside the viewer
# Ensure links open in the default web browser and not in this window
self.text_browser.setOpenLinks(False)
self.text_browser.anchorClicked.connect(QDesktopServices.openUrl)
layout.addWidget(self.text_browser)
# Footer Controls Layout
footer_layout = QHBoxLayout()
ok_button = QPushButton("OK", self)
ok_button.setDefault(True)
ok_button.clicked.connect(self.accept) # Closes the dialog with a success signal
ok_button.clicked.connect(self.accept)
footer_layout.addStretch()
footer_layout.addWidget(ok_button)
@@ -51,18 +59,16 @@ class WelcomeDialog(QDialog):
# Fetch markdown from the web asynchronously
self.network_manager = QNetworkAccessManager(self)
self.network_manager.finished.connect(self._on_download_complete)
md_url = "https://git.research.dezeeuw.ca/tyler/flares/raw/branch/main/changelog_major.md"
self.network_manager.get(QNetworkRequest(QUrl(md_url)))
self.network_manager.get(QNetworkRequest(QUrl(CHANGELOG_URL)))
def _on_download_complete(self, reply):
"""Processes the downloaded markdown and drops it into the view frame."""
if reply.error() == reply.NetworkError.NoError:
raw_bytes = reply.readAll()
# Convert raw bytes to standard text string
markdown_text = str(raw_bytes, encoding='utf-8')
# Qt's QTextBrowser natively renders markdown arrays beautifully!
self.text_browser.setMarkdown(markdown_text)
else:
self.text_browser.setHtml(