From a2456daca773e1c1640ccb9a1d6c409d677b4b9e Mon Sep 17 00:00:00 2001 From: tyler Date: Sat, 24 Oct 2026 17:42:54 -0700 Subject: [PATCH] improvements for notifications and prepare for release --- .gitignore | 3 +- changelog.md | 3 + flares.py | 16 +----- main.py | 27 +++++++-- src/shared/inspiration.py | 117 ++++++++++++++++++++++++++++++++++++++ src/shared/shareddata.py | 65 ++++++++++++++++++++- src/window/about.py | 12 +++- 7 files changed, 221 insertions(+), 22 deletions(-) create mode 100644 src/shared/inspiration.py diff --git a/.gitignore b/.gitignore index 44aecc9..c4e4b57 100644 --- a/.gitignore +++ b/.gitignore @@ -184,4 +184,5 @@ flares-* *.flare *.cfg tempCodeRunnerFile.py -*.pkl \ No newline at end of file +*.pkl +*.joblib \ No newline at end of file diff --git a/changelog.md b/changelog.md index f0c3e29..ac27822 100644 --- a/changelog.md +++ b/changelog.md @@ -12,7 +12,10 @@ - Export To CSV Viewer export options now perform the actions they claim rather than having no functionality - Logs on a per participant basis are now available to be viewed from within the application - They can be accessed by right clicking a participant bubble and selecting "Show Logs". Logs will be empty if they have not been processed +- A notification now appears when processing completes even if the application is not focused - Fixed a regression where opening a folder would no longer load all the files in that folder +- Added inspirational quotes to the About window +- Licence is now clickable from the About window - Improved code stability and functionality diff --git a/flares.py b/flares.py index 28cd24a..d308f25 100644 --- a/flares.py +++ b/flares.py @@ -105,7 +105,7 @@ from mne_nirs.statistics._glm_level_first import RegressionResults # type: igno from mne_connectivity.viz import plot_connectivity_circle # type: ignore from mne_connectivity import envelope_correlation, spectral_connectivity_epochs, spectral_connectivity_time # type: ignore -from src.shared.shareddata import PLATFORM_NAME, resource_path, get_app_dir +from src.shared.shareddata import PLATFORM_NAME, resource_path, get_app_dir, METRIC_REGISTRY PRIMARY_COLORS = { @@ -7152,20 +7152,6 @@ def write_qc_excel_summary( wb.save(output_path) -METRIC_REGISTRY = { - 'Peak_Amp': 'Peak_Amp', - 'TTP': 'Time_to_Peak', - 'AUC': 'AUC', - 'Rising_Slope': 'Rising_Slope', - 'Recovery_Slope': 'Recovery_Slope', - 'FWHM': 'FWHM', - 'Onset_Latency': 'Onset_Latency', - 'P2P_Amp': 'Peak_to_Peak_Amp', - 'Signal_Std': 'Signal_Std', - 'RMS': 'RMS' -} - - def plot_and_enqueue_waveform_metrics( roi_curves, fir_delays, diff --git a/main.py b/main.py index 1c0395e..2c493c1 100644 --- a/main.py +++ b/main.py @@ -31,7 +31,7 @@ if sys.platform.startswith("linux"): import psutil from PySide6.QtWidgets import ( - QApplication, QWidget, QMessageBox, QVBoxLayout, QHBoxLayout, QTextEdit, QScrollArea, QComboBox, QGridLayout, QSplitter, QDialogButtonBox, QHeaderView, + QApplication, QStyle, QSystemTrayIcon, QWidget, QMessageBox, QVBoxLayout, QHBoxLayout, QTextEdit, QScrollArea, QComboBox, QGridLayout, QSplitter, QDialogButtonBox, QHeaderView, QPushButton, QMainWindow, QLabel, QLineEdit, QGroupBox, QDialog, QMenu, QSpinBox, QTableWidget, QTableWidgetItem ) from PySide6.QtCore import QEvent, QObject, Signal, Qt, QTimer @@ -738,6 +738,18 @@ class MainApplication(QMainWindow): self.plugin_manager.reload_plugins() + self.tray_icon = QSystemTrayIcon(self) + stock_icon = self.style().standardIcon(QStyle.StandardPixmap.SP_ComputerIcon) # Or use your own QIcon("path_to_logo.png") + self.tray_icon.setIcon(stock_icon) + self.tray_icon.show() + + def bring_to_front(): + self.showNormal() # Restores if minimized + self.raise_() # Brings to top of window stack (macOS/Linux) + self.activateWindow() # Focuses the window (Windows) + + self.tray_icon.messageClicked.connect(bring_to_front) + self.show() # Check if we should pop up the welcome screen @@ -2361,9 +2373,16 @@ class MainApplication(QMainWindow): elapsed_str = self._format_elapsed(msg.get("elapsed", getattr(self, "_last_elapsed", 0))) speedup = msg.get("speedup") speedup_str = f" | Speedup: {speedup:.1f}x" if speedup else "" - self.statusbar.showMessage( - f"Complete: {success_count} succeeded, {fail_count} failed. | Total time: {elapsed_str}{speedup_str}" - ) + status_text = f"Complete: {success_count} succeeded, {fail_count} failed. | Total time: {elapsed_str}{speedup_str}" + self.statusbar.showMessage(status_text) + + if hasattr(self, "tray_icon") and self.tray_icon.isSystemTrayAvailable(): + self.tray_icon.showMessage( + "Process Completed", + status_text, + QSystemTrayIcon.MessageIcon.Information, + 4000 # Duration in milliseconds + ) if success_count > 0: self.is_saved = False diff --git a/src/shared/inspiration.py b/src/shared/inspiration.py new file mode 100644 index 0000000..35aa4be --- /dev/null +++ b/src/shared/inspiration.py @@ -0,0 +1,117 @@ +""" +Filename: inspiration.py +Description: Contains inspiration quotes to be shown at random +Note: Compliant with pylance strict type checking + +Author: Tyler de Zeeuw +License: GPL-3.0 +""" + +# Built-in imports +import random + +FNIRS_QUOTES = [ + "Understanding the brain through light and data.", + "Every signal offers another perspective on brain function.", + "Exploring the relationship between brain activity and behavior.", + "Using light to investigate the complexity of the human brain.", + "Turning neural measurements into meaningful insights.", + "A deeper understanding of the brain begins with better questions.", + "Studying the brain means looking beyond what we can see.", + "Connecting brain activity to the way we think, feel, and act.", + "Even small changes in brain signals can reveal meaningful patterns.", + "Advancing neuroscience through new ways of measuring brain activity.", + "The brain is complex, and every measurement adds another piece.", + "Using fNIRS to explore the connection between the brain and the world around us.", + "Good science begins with curiosity and grows through careful observation.", + "There is still so much to learn about how the brain works.", + "Measuring brain activity is only the beginning; understanding it is the goal.", + "Exploring what brain activity can tell us about the human experience.", + "Every experiment brings a new opportunity to understand the brain.", + "The more we measure, the more questions we learn to ask.", + "Neuroscience advances through careful measurements and thoughtful interpretation.", + "Understanding brain function requires looking at both signals and context.", + "Each dataset offers a different view of the brain in action.", + "Exploring the neural processes behind everyday human experiences.", + "Better measurements can lead to better questions about the brain.", + "The challenge is not just collecting data, but understanding what it means.", + "Brain activity reflects a complex interaction of processes and experiences.", + "Scientific progress begins with observing what we do not yet understand.", + "Each discovery builds on the questions that came before it.", + "There is more to brain activity than a single signal can reveal.", + "New tools create new opportunities to investigate old questions.", + "The goal is to connect what we measure with what we want to understand.", + "Neural signals provide clues to the processes underlying human behavior.", + "A better understanding of brain function starts with better evidence.", + "The value of a measurement lies in what it helps us understand.", + "Every experiment adds another perspective to a complex picture.", + "Scientific discovery often begins with noticing something unexpected.", + "Understanding the brain is an ongoing process of questioning and refinement.", + "The best insights emerge when data meets thoughtful interpretation.", + "Research moves forward when curiosity is matched with rigorous methods.", + "There is always another question worth exploring.", + "Investigating the brain means embracing complexity rather than avoiding it.", + "Progress comes from asking precise questions about complicated systems.", + "The brain does not work in isolation from the person or environment.", + "Understanding behavior requires more than observing behavior alone.", + "The smallest details can sometimes change how we interpret the bigger picture.", + "Every method offers a different window into brain function.", + "Different perspectives help build a more complete understanding of the brain.", + "Good research turns uncertainty into questions that can be investigated.", + "The process of discovery is as important as the result.", + "Meaningful findings begin with reliable data and careful reasoning.", + "The brain is not simple, and our methods should reflect that complexity.", + "Scientific understanding grows when evidence challenges our assumptions.", + "Research is a process of learning what the data can—and cannot—tell us.", + "The most useful findings are those that move understanding forward.", + "Each new method expands the questions we can ask about the brain.", + "The path to understanding is built from observations, questions, and evidence.", + "Neuroscience is about understanding the processes behind the patterns.", + "The aim is not simply to measure brain activity, but to interpret it meaningfully.", + "Better tools help us investigate the questions that matter.", + "Progress in neuroscience depends on both technical precision and human curiosity.", + "The brain is complex, but every careful observation helps clarify a part of it.", + "Understanding brain function means connecting measurements to meaningful outcomes.", + "Research turns curiosity into evidence and evidence into understanding.", + "There is a story behind every pattern, but evidence determines what we can conclude.", + "New insights emerge when we approach familiar questions from different angles.", + "The strongest conclusions begin with careful measurements.", + "Understanding the brain takes more than data; it takes context.", + "The questions we ask shape what we learn from the brain.", + "Good research is not about finding answers quickly, but finding them carefully.", + "Every result contributes to a larger conversation about how the brain works.", + "The human brain remains an evolving question for science.", + "Measuring brain activity gives us another way to investigate human cognition.", + "The connection between brain function and behavior is rarely straightforward.", + "Scientific progress comes from combining new methods with careful interpretation.", + "The more we learn about the brain, the better our questions become.", + "Research is the practice of making uncertainty more understandable.", + "Understanding the brain means being willing to revise what we think we know.", + "Each experiment helps define what we know and what remains uncertain.", + "The most interesting findings often begin with questions that have no easy answers.", + "The purpose of research is to move beyond assumptions toward evidence.", + "A measurement becomes useful when it helps answer a meaningful question.", + "Neuroscience brings together technology, observation, and the study of human experience.", + "fNIRS offers another way to investigate changes in brain hemodynamics.", + "Optical measurements provide a noninvasive approach to studying brain function.", + "Studying brain function outside the lab can reveal aspects of everyday cognition.", + "The flexibility of fNIRS opens new possibilities for human neuroscience research.", + "Brain measurements become more meaningful when considered alongside behavior.", + "fNIRS provides a useful perspective on the relationship between physiology and cognition.", + "Understanding the strengths and limitations of a method is part of good science.", + "The future of neuroscience depends on asking better questions, not just collecting more data.", + "Technology expands what we can measure, while critical thinking guides what we conclude.", + "The goal of neuroscience is not just to observe the brain, but to understand its function.", + "Every research method brings its own perspective, possibilities, and limitations.", + "The most meaningful discoveries connect technical advances to real-world questions.", + "Better ways to study the brain can lead to a better understanding of human experience.", + "Science progresses when we remain curious about what the evidence cannot yet explain.", + "Understanding the brain is a long-term effort built on many small discoveries.", + "Behind every useful finding is a question worth investigating.", + "The value of research lies in what we learn, not just what we measure.", + "Good science makes room for complexity, uncertainty, and new perspectives.", + "The next insight may come from looking at a familiar problem in a different way.", +] + +def get_quote() -> None: + return f"'{random.choice(FNIRS_QUOTES)}'" \ No newline at end of file diff --git a/src/shared/shareddata.py b/src/shared/shareddata.py index dd42f94..a2f91a6 100644 --- a/src/shared/shareddata.py +++ b/src/shared/shareddata.py @@ -8,12 +8,18 @@ License: GPL-3.0 """ # Built-in imports +import json import os +import subprocess import sys import platform +try: + import winreg +except ImportError: + winreg = None -CURRENT_VERSION = "1.7.2" +CURRENT_VERSION = "1.8.0" APP_NAME = "flares" APP_NAME_EXPANDED = "fNIRS Lightweight Analysis, Research, & Evaluation Suite" API_URL = f"https://git.research.dezeeuw.ca/api/v1/repos/tyler/{APP_NAME}/releases" @@ -72,6 +78,63 @@ DATA_SCHEMA = [ ] +METRIC_REGISTRY = { + "Peak_Amp": "Peak_Amp", + "TTP": "Time_to_Peak", + "AUC": "AUC", + "Rising_Slope": "Rising_Slope", + "Recovery_Slope": "Recovery_Slope", + "FWHM": "FWHM", + "Onset_Latency": "Onset_Latency", + "P2P_Amp": "Peak_to_Peak_Amp", + "Signal_Std": "Signal_Std", + "RMS": "RMS", +} + + +def get_cpu_model_name(): + """Detect detailed processor info, collapsing Windows registry whitespace padding.""" + try: + system = platform.system() + + if system == "Windows" and winreg is not None: + with winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, + r"HARDWARE\DESCRIPTION\System\CentralProcessor\0", + ) as key: + raw_cpu_name, _ = winreg.QueryValueEx(key, "ProcessorNameString") + cpu_name = " ".join(raw_cpu_name.split()) + + command = ( + "Get-CimInstance Win32_Processor | " + "Select-Object NumberOfCores, NumberOfLogicalProcessors, MaxClockSpeed | " + "ConvertTo-Json -Compress" + ) + cpu = json.loads(subprocess.check_output( + ["powershell", "-NoProfile", "-Command", command], + text=True, + creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0), + )) + base_ghz = cpu["MaxClockSpeed"] / 1000 + return (f"{cpu_name} @ {base_ghz:.1f}GHz " + f"{cpu['NumberOfCores']}c/{cpu['NumberOfLogicalProcessors']}t") + + if system == "Darwin": + return subprocess.check_output( + ["sysctl", "-n", "machdep.cpu.brand_string"], text=True + ).strip() + + if system == "Linux": + with open("/proc/cpuinfo") as f: + for line in f: + if "model name" in line: + return line.split(":")[1].strip() + except Exception: + pass + + return platform.processor() or "Multi-Core CPU" + + def resource_path(relative_path: str) -> str: """Get absolute path to resource regardless of running directly or packaged using PyInstaller""" base_path = getattr(sys, "_MEIPASS", os.path.abspath(".")) diff --git a/src/window/about.py b/src/window/about.py index 37e435a..d554826 100644 --- a/src/window/about.py +++ b/src/window/about.py @@ -12,6 +12,7 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel from PySide6.QtCore import Qt from src.shared.shareddata import APP_NAME, APP_NAME_EXPANDED, CURRENT_VERSION +from src.shared.inspiration import get_quote class AboutWindow(QWidget): @@ -30,12 +31,21 @@ class AboutWindow(QWidget): layout = QVBoxLayout() label = QLabel(f"About {APP_NAME.upper()}", self) label2 = QLabel(f"{APP_NAME_EXPANDED}", self) - label3 = QLabel(f"{APP_NAME.upper()} is licensed under the GPL-3.0 licence. For more information, visit https://www.gnu.org/licenses/gpl-3.0.en.html", self) + label3 = QLabel( + f'{APP_NAME.upper()} is licensed under the GPL-3.0 licence. ' + 'For more information, visit ' + '' + 'GNU GPL-3.0', + self, + ) + label3.setOpenExternalLinks(True) label4 = QLabel(f"Version v{CURRENT_VERSION}") + label5 = QLabel(f"{get_quote()}", self) layout.addWidget(label) layout.addWidget(label2) layout.addWidget(label3) layout.addWidget(label4) + layout.addWidget(label5) self.setLayout(layout) \ No newline at end of file