unit testing and others
This commit is contained in:
@@ -87,6 +87,24 @@ PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
}
|
||||
|
||||
|
||||
DESCRIPTION = """0. Spectral Coherence (functional_connectivity_spectral_epochs)
|
||||
\nTests for frequency-domain phase synchronization between channel pairs in a specific oscillatory band (0.04-0.2 Hz) across epoched data using multitaper spectral estimation. A significant connection means two brain regions share consistent, synchronized oscillatory phase dynamics across epochs, reflecting steady-state functional coupling. It does not tell you when during the epoch the interaction occurred (as time is integrated out), nor does it guarantee the interaction is neural, as shared systemic vascular oscillations or motion artifacts can drive spurious high coherence across distant sensors.
|
||||
\nIf connectivity appears lower or sparser than expected, common causes include: non-stationarity within the epoch (phase relationships that shift rapidly over time cancel out when averaged across the whole window); trial-to-trial timing jitter; or applying overly stringent edge thresholding or FDR correction across all unique channel pairs.
|
||||
|
||||
\n1. Envelope Correlation (functional_connectivity_envelope)
|
||||
\nExtracts the Hilbert amplitude envelope from bandpass-filtered signals (0.04-0.2 Hz) to measure slow amplitude power correlations across time within epoched data. A significant result indicates that the overall energy profiles or activation magnitudes of two regions co-vary over time, independent of sub-second phase locking. It says nothing about fast phase interactions or exact event-locked timing, and its power is heavily degraded if epoch lengths are too short (under ~10-15s) to capture multiple complete cycles of low-frequency hemodynamic fluctuations.
|
||||
\nIf this method underperforms compared to phase-based coherence, the most likely explanation is that your trial window is too brief for robust envelope extraction, or that the functional coupling between regions is purely phase-locked rather than power-coupled. Additionally, uncorrected global motion or systemic arterial pressure shifts can globally inflate envelope correlations across the whole head.
|
||||
|
||||
\n2. Time-Resolved Spectral Coherence (functional_connectivity_spectral_time)
|
||||
\nUses continuous Morlet wavelet time-frequency decomposition across multiple frequencies (0.04-0.2 Hz) to track how spectral coherence between channel pairs dynamically evolves over the duration of a trial. A significant result pinpoints the exact temporal window within a trial where functional coupling emerges or dissolves (e.g., during stimulus encoding vs. motor execution). It demands precise, jitter-free stimulus onset triggers and carries high computational complexity; it is also susceptible to wavelet edge artifacts at the start and end of epoch windows.
|
||||
\nIf expected temporal connectivity changes fail to emerge, check whether trial-to-trial onset latency variability across subjects is smearing the time-resolved average, or if the chosen wavelet cycle parameter (n_cycles) is oversmoothing short-lived, transient phase-coupling events.
|
||||
|
||||
\n3. Beta-Series Correlation (functional_connectivity_betas)
|
||||
\nFits a General Linear Model (GLM) using a flexible Finite Impulse Response (FIR) basis set to estimate trial-by-trial activation magnitudes (betas), optionally applies Global Signal Regression (GSR) to strip head-wide systemic noise, and correlates those beta series across events with FDR (q < alpha) and effect-size thresholding. A significant connection means that when Region A responds more strongly on a given trial, Region B also responds more strongly, isolating true task-evoked co-activation from background resting-state noise. It requires at least 4 (ideally 15+) repeated trials per condition to establish degrees of freedom for the correlation t-test, and relies heavily on a correctly specified trial annotation structure.
|
||||
\nIf this test returns no significant edges, the primary culprit is typically insufficient trial count (leading to severely underpowered degrees of freedom), poorly separated trials that induce severe multicollinearity in the FIR design matrix, or applying GSR when the underlying neural effect itself is diffuse, causing true network correlations to be over-regressed.
|
||||
"""
|
||||
|
||||
|
||||
class ParticipantFunctionalConnectivityWidget(ParticipantUIMixin, FlaresBaseWidget):
|
||||
def __init__(
|
||||
self,
|
||||
@@ -102,7 +120,7 @@ class ParticipantFunctionalConnectivityWidget(ParticipantUIMixin, FlaresBaseWidg
|
||||
QMessageBox.warning(self, f"Warning - {APP_NAME.upper()}", 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.")
|
||||
|
||||
self.setup_participant_ui(["0 (Spectral Connectivity Epochs)", "1 (Envelope Correlation)", "2 (Betas)", "3 (Spectral Connectivity Epochs)",])
|
||||
self.setup_participant_ui(["0 (Spectral Connectivity Epochs)", "1 (Envelope Correlation)", "2 (Betas)", "3 (Spectral Connectivity Epochs)"], placeholder_text=DESCRIPTION)
|
||||
|
||||
|
||||
def process_request(self):
|
||||
|
||||
@@ -1877,7 +1877,8 @@ class InterGroupUIMixin:
|
||||
class ParticipantUIMixin:
|
||||
def setup_participant_ui(
|
||||
self,
|
||||
index_texts: Sequence[str]
|
||||
index_texts: Sequence[str],
|
||||
placeholder_text: str = ""
|
||||
) -> None:
|
||||
|
||||
# Create mappings: file_path -> participant label and dropdown display text
|
||||
@@ -1890,9 +1891,9 @@ class ParticipantUIMixin:
|
||||
self.participant_map[file_path] = short_label
|
||||
self.participant_dropdown_items.append(display_label)
|
||||
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.top_bar = QHBoxLayout()
|
||||
self.layout.addLayout(self.top_bar)
|
||||
self.main_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)
|
||||
@@ -1917,12 +1918,16 @@ class ParticipantUIMixin:
|
||||
self.top_bar.addWidget(self.image_index_dropdown)
|
||||
self.top_bar.addWidget(self.submit_button)
|
||||
|
||||
self.scroll = QScrollArea()
|
||||
self.scroll.setWidgetResizable(True)
|
||||
self.scroll_area = QScrollArea()
|
||||
self.scroll_area.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.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)
|
||||
self.showMaximized()
|
||||
|
||||
+49
-3
@@ -8,11 +8,13 @@ License: GPL-3.0
|
||||
"""
|
||||
|
||||
# Built-in imports
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
|
||||
# External library imports
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QLineEdit
|
||||
from PySide6.QtCore import Qt, QThread, Signal
|
||||
from PySide6.QtCore import QProcess, Qt, QThread, Signal
|
||||
|
||||
from file_ext_registration import register_file_association, is_windows_admin
|
||||
from src.shared.shareddata import API_URL, API_URL_SECONDARY, APP_NAME, CURRENT_VERSION, PLATFORM_NAME
|
||||
@@ -55,6 +57,8 @@ class TerminalWindow(QWidget):
|
||||
layout.addWidget(self.input_line)
|
||||
self.setLayout(layout)
|
||||
|
||||
self._process: QProcess | None = None
|
||||
|
||||
self.commands: dict[str, Callable[..., Any]] = {
|
||||
"hello": self.cmd_hello,
|
||||
"help": self.cmd_help,
|
||||
@@ -62,6 +66,7 @@ class TerminalWindow(QWidget):
|
||||
"about": self.cmd_about,
|
||||
"assoc": self.cmd_assoc,
|
||||
"update": self.cmd_update,
|
||||
"utest": self.cmd_utest,
|
||||
}
|
||||
|
||||
self._pending_assoc_confirmation: bool = False
|
||||
@@ -107,7 +112,8 @@ class TerminalWindow(QWidget):
|
||||
return "Hello from the terminal!"
|
||||
|
||||
def cmd_help(self, *args: Any) -> str:
|
||||
return f"Available commands: {', '.join(self.commands.keys())}"
|
||||
available_cmds = [cmd for cmd in self.commands.keys() if cmd != "utest"]
|
||||
return f"Available commands: {', '.join(available_cmds)}"
|
||||
|
||||
def cmd_version(self, *args: Any) -> str:
|
||||
return f"{APP_NAME.upper()} is running version {CURRENT_VERSION}."
|
||||
@@ -163,4 +169,44 @@ class TerminalWindow(QWidget):
|
||||
|
||||
def _on_assoc_result(self, ok: bool, msg: str) -> None:
|
||||
self.output_area.append(msg)
|
||||
self._assoc_worker = None
|
||||
self._assoc_worker = None
|
||||
|
||||
def cmd_utest(self, *args: Any) -> str | None:
|
||||
"""Executes a specific pre-defined python script non-blockingly."""
|
||||
if self._process and self._process.state() != QProcess.ProcessState.NotRunning:
|
||||
return "[Error] A process is already running."
|
||||
|
||||
target_script = Path("main_unit_tests.py")
|
||||
|
||||
if not target_script.exists():
|
||||
return f"[Error] Target script not found at: {target_script}"
|
||||
|
||||
self._process = QProcess(self)
|
||||
|
||||
# Stream stdout and stderr live to output_area
|
||||
self._process.readyReadStandardOutput.connect(self._handle_stdout)
|
||||
self._process.readyReadStandardError.connect(self._handle_stderr)
|
||||
self._process.finished.connect(self._handle_process_finished)
|
||||
|
||||
# Use current Python interpreter executable. Works when packaged?
|
||||
python_executable = sys.executable
|
||||
|
||||
self.output_area.append(f"Starting {target_script.name}...")
|
||||
self._process.start(python_executable, [str(target_script)])
|
||||
return None
|
||||
|
||||
def _handle_stdout(self) -> None:
|
||||
if self._process:
|
||||
data = self._process.readAllStandardOutput().data().decode("utf-8")
|
||||
if data.strip():
|
||||
self.output_area.append(data.strip())
|
||||
|
||||
def _handle_stderr(self) -> None:
|
||||
if self._process:
|
||||
data = self._process.readAllStandardError().data().decode("utf-8")
|
||||
if data.strip():
|
||||
self.output_area.append(f"[Error] {data.strip()}")
|
||||
|
||||
def _handle_process_finished(self, exit_code: int, exit_status: QProcess.ExitStatus) -> None:
|
||||
self.output_area.append(f"Process finished with code {exit_code}.")
|
||||
self._process = None
|
||||
Reference in New Issue
Block a user