pylance standardization
This commit is contained in:
@@ -1,20 +1,28 @@
|
||||
"""
|
||||
Filename: crossgroupbrainimage.py
|
||||
Description: Logic for the Cross-Group Brain & Image analysis window
|
||||
Note: Compliant with pylance strict type checking
|
||||
|
||||
Author: Tyler de Zeeuw
|
||||
License: GPL-3.0
|
||||
"""
|
||||
|
||||
# Built-in Imports
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
# External library imports
|
||||
from mne.io.base import BaseRaw
|
||||
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
|
||||
from flares import aggregate_fnirs_group_geometry, plot_2d_3d_contrasts_between_groups
|
||||
from src.shared.flaresbasewidget import CrossGroupUIMixin, FlaresBaseWidget
|
||||
from src.shared.shareddata import APP_NAME
|
||||
|
||||
|
||||
PARAMETERIZED_INDEXES = {
|
||||
PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
0: [
|
||||
{
|
||||
"key": "show_optodes",
|
||||
@@ -52,7 +60,15 @@ PARAMETERIZED_INDEXES = {
|
||||
|
||||
|
||||
class CrossGroupBrainImageWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
def __init__(self, haemo_dict, df_ind_dict, design_matrix_dict, contrast_results_dict, group_dict):
|
||||
def __init__(
|
||||
self,
|
||||
haemo_dict: dict[str | Path, BaseRaw],
|
||||
df_ind_dict: dict[str, DataFrame],
|
||||
design_matrix_dict: dict[str, DataFrame],
|
||||
contrast_results_dict: dict[str, dict[str, Any]],
|
||||
group_dict: dict[str, str],
|
||||
) -> None:
|
||||
|
||||
super().__init__("CrossGroupBrainImage")
|
||||
self.setWindowTitle(f"Cross-Group Brain & Image Viewer - {APP_NAME.upper()}")
|
||||
self.haemo_dict = haemo_dict
|
||||
@@ -70,8 +86,9 @@ class CrossGroupBrainImageWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
if request is None:
|
||||
return
|
||||
|
||||
(selected_event, file_paths_a, file_paths_b, all_selected_paths, selected_indexes, param_values,) = request
|
||||
(selected_event, file_paths_a, file_paths_b, all_selected_paths, selected_indexes, raw_params) = request
|
||||
|
||||
param_values = cast(dict[int | str, dict[str, Any]], raw_params)
|
||||
|
||||
# Build group-level contrast DataFrames
|
||||
def concat_group_contrasts(file_paths: list[str], event: str | None) -> pd.DataFrame:
|
||||
@@ -102,8 +119,11 @@ class CrossGroupBrainImageWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
|
||||
if len(all_raw_objs) > 1:
|
||||
processed_raw = aggregate_fnirs_group_geometry(all_raw_objs)
|
||||
elif len(all_raw_objs) == 1 and all_raw_objs[0] is not None:
|
||||
processed_raw = all_raw_objs[0].copy()
|
||||
processed_raw.pick(picks="hbo") # type: ignore
|
||||
else:
|
||||
processed_raw = all_raw_objs[0].copy().pick(picks="hbo")
|
||||
processed_raw = None
|
||||
|
||||
# Visualizations
|
||||
for idx in selected_indexes:
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
"""
|
||||
Filename: crossgroupstats.py
|
||||
Description: Cross-Group stats analysis window
|
||||
Note: Compliant with pylance strict type checking
|
||||
|
||||
Author: Tyler de Zeeuw
|
||||
License: GPL-3.0
|
||||
"""
|
||||
|
||||
# Built-in imports
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
# External library imports
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
|
||||
from mne.io.base import BaseRaw
|
||||
|
||||
from flares import run_cross_group_contrast_analysis, run_cross_group_laterality_analysis, run_cross_group_second_level_analysis
|
||||
from src.shared.flaresbasewidget import CrossGroupUIMixin, FlaresBaseWidget
|
||||
from src.shared.shareddata import APP_NAME
|
||||
|
||||
|
||||
PARAMETERIZED_INDEXES = {
|
||||
PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
0: [
|
||||
{
|
||||
"key": "p_threshold",
|
||||
@@ -136,7 +144,18 @@ DESCRIPTION = """0. Raw ROI Comparison (run_cross_group_second_level_analysis)
|
||||
|
||||
|
||||
class CrossGroupStatsWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
def __init__(self, haemo_dict, cha_dict, df_ind_dict, design_matrix_dict, contrast_results_dict, group_dict, json_location):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
haemo_dict: dict[str | Path, BaseRaw],
|
||||
cha_dict: dict[str, DataFrame],
|
||||
df_ind_dict: dict[str, DataFrame],
|
||||
design_matrix_dict: dict[str, DataFrame],
|
||||
contrast_results_dict: dict[str, dict[str, Any]],
|
||||
group_dict: dict[str, str],
|
||||
json_location: str | Path
|
||||
) -> None:
|
||||
|
||||
super().__init__("CrossGroupStats")
|
||||
self.setWindowTitle(f"Cross-Group Stats Viewer - {APP_NAME.upper()}")
|
||||
self.haemo_dict = haemo_dict
|
||||
@@ -144,7 +163,7 @@ class CrossGroupStatsWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
self.df_ind_dict = df_ind_dict
|
||||
self.design_matrix_dict = design_matrix_dict
|
||||
self.contrast_results_dict = contrast_results_dict
|
||||
self.group_dict = group_dict
|
||||
# self.group_dict = group_dict
|
||||
self.json_location = json_location
|
||||
|
||||
self.setup_cross_group_ui(["0 (Raw ROI Comparison)", "1 (Laterality Comparison)", "2 (Contrast Comparison)",], placeholder_text=DESCRIPTION)
|
||||
@@ -155,23 +174,18 @@ class CrossGroupStatsWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
if request is None:
|
||||
return
|
||||
|
||||
(selected_event, file_paths_a, file_paths_b, all_selected_paths, selected_indexes, param_values,) = request
|
||||
(selected_event, file_paths_a, file_paths_b, _, selected_indexes, raw_params) = request
|
||||
|
||||
if isinstance(self.df_ind_dict, dict):
|
||||
# Filter out empty entries and concatenate
|
||||
valid_dfs = [df for df in self.df_ind_dict.values() if isinstance(df, pd.DataFrame) and not df.empty]
|
||||
if valid_dfs:
|
||||
df_ind_combined = pd.concat(valid_dfs, ignore_index=True)
|
||||
else:
|
||||
df_ind_combined = pd.DataFrame()
|
||||
param_values = cast(dict[int | str, dict[str, Any]], raw_params)
|
||||
|
||||
valid_dfs = [df for df in self.df_ind_dict.values() if not df.empty]
|
||||
if valid_dfs:
|
||||
df_ind_combined = pd.concat(valid_dfs, ignore_index=True)
|
||||
else:
|
||||
df_ind_combined = self.df_ind_dict
|
||||
|
||||
if isinstance(self.cha_dict, dict):
|
||||
valid_chas = [df for df in self.cha_dict.values() if isinstance(df, pd.DataFrame) and not df.empty]
|
||||
cha_combined = pd.concat(valid_chas, ignore_index=True) if valid_chas else pd.DataFrame()
|
||||
else:
|
||||
cha_combined = self.cha_dict
|
||||
df_ind_combined = pd.DataFrame()
|
||||
|
||||
valid_chas = [df for df in self.cha_dict.values() if not df.empty]
|
||||
cha_combined = pd.concat(valid_chas, ignore_index=True) if valid_chas else pd.DataFrame()
|
||||
|
||||
sample_path = file_paths_a[0]
|
||||
p_haemo = self.haemo_dict.get(sample_path)
|
||||
@@ -213,8 +227,8 @@ class CrossGroupStatsWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
min_subjects = params.get("min_subjects", 3)
|
||||
correction_method = params.get("correction_method", "None")
|
||||
target_chroma = params.get("target_chroma", "hbo")
|
||||
roi_a = params.get("roi_a", "").strip()
|
||||
roi_b = params.get("roi_b", "").strip()
|
||||
roi_a: str = params.get("roi_a", "").strip()
|
||||
roi_b: str = params.get("roi_b", "").strip()
|
||||
|
||||
if not roi_a or not roi_b:
|
||||
print("Both a contralateral and ipsilateral ROI name must be specified.")
|
||||
@@ -225,14 +239,19 @@ class CrossGroupStatsWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
|
||||
# Build each group's dataframe directly from the dict using
|
||||
# the file-path lists as keys - no ID cleaning/matching needed.
|
||||
def _build_group_df(file_paths, dict_source):
|
||||
def _build_group_df(
|
||||
file_paths: list[str],
|
||||
dict_source: dict[str, DataFrame]
|
||||
) -> DataFrame:
|
||||
|
||||
valid_dfs = [
|
||||
dict_source[fp] for fp in file_paths
|
||||
if fp in dict_source and isinstance(dict_source[fp], pd.DataFrame)
|
||||
and not dict_source[fp].empty
|
||||
if fp in dict_source and not dict_source[fp].empty
|
||||
]
|
||||
|
||||
return pd.concat(valid_dfs, ignore_index=True) if valid_dfs else pd.DataFrame()
|
||||
|
||||
|
||||
df_roi_a = _build_group_df(file_paths_a, self.df_ind_dict)
|
||||
df_roi_b = _build_group_df(file_paths_b, self.df_ind_dict)
|
||||
|
||||
@@ -271,8 +290,13 @@ class CrossGroupStatsWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
# directly from contrast_results_dict, keyed by file path -
|
||||
# same dict-key approach as the laterality patch, avoids
|
||||
# any ID-string matching.
|
||||
def _build_group_contrast_df(file_paths, contrast_dict, name):
|
||||
all_rows = []
|
||||
def _build_group_contrast_df(
|
||||
file_paths: list[str],
|
||||
contrast_dict: dict[str, dict[str, pd.DataFrame]],
|
||||
name: str,
|
||||
) -> pd.DataFrame:
|
||||
|
||||
all_rows: list[DataFrame] = []
|
||||
for fp in file_paths:
|
||||
condition_dfs = contrast_dict.get(fp)
|
||||
if condition_dfs is None:
|
||||
|
||||
+26
-33
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Filename: exporttocsv.py
|
||||
Description: Logic for the Export To CSV analysis window
|
||||
Note: Compliant with pylance strict type checking
|
||||
|
||||
Author: Tyler de Zeeuw
|
||||
License: GPL-3.0
|
||||
@@ -8,39 +9,51 @@ License: GPL-3.0
|
||||
|
||||
# Built-in imports
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
# External library imports
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
|
||||
from mne.io.base import BaseRaw
|
||||
|
||||
from PySide6.QtWidgets import QFileDialog, QMessageBox
|
||||
|
||||
from flares import sparks_csv_export
|
||||
from src.shared.flaresbasewidget import CSVUIMixin, FlaresBaseWidget
|
||||
from src.shared.shareddata import APP_NAME
|
||||
|
||||
|
||||
class ExportToCSVWidget(CSVUIMixin, FlaresBaseWidget):
|
||||
def __init__(self, haemo_dict, cha_dict, df_ind, design_matrix, group, contrast_results_dict):
|
||||
def __init__(
|
||||
self,
|
||||
haemo_dict: dict[str | Path, BaseRaw],
|
||||
cha_dict: dict[str, DataFrame],
|
||||
df_ind_dict: dict[str, DataFrame],
|
||||
design_matrix_dict: dict[str, DataFrame],
|
||||
contrast_results_dict: dict[str, dict[str, Any]],
|
||||
group_dict: dict[str, str],
|
||||
) -> None:
|
||||
|
||||
super().__init__("ExportToCSV")
|
||||
self.setWindowTitle(f"Export To CSV Viewer - {APP_NAME.upper()}")
|
||||
self.haemo_dict = haemo_dict
|
||||
self.cha_dict = cha_dict
|
||||
self.df_ind = df_ind
|
||||
self.design_matrix = design_matrix
|
||||
self.group = group
|
||||
self.contrast_results_dict = contrast_results_dict
|
||||
# self.df_ind = df_ind_dict
|
||||
# self.design_matrix = design_matrix_dict
|
||||
# self.contrast_results_dict = contrast_results_dict
|
||||
# self.group = group_dict
|
||||
|
||||
self.setup_csv_ui(["0 (Export Data to CSV)", "1 (CSV for SPARKS)",])
|
||||
|
||||
|
||||
def process_request(self):
|
||||
# TODO: Move this into flares for the call?
|
||||
selected_display_names = self._get_checked_items(self.participant_dropdown)
|
||||
selected_file_paths = []
|
||||
selected_file_paths: list[str] = []
|
||||
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:
|
||||
if display_name == expected_display:
|
||||
selected_file_paths.append(fp)
|
||||
break
|
||||
|
||||
@@ -52,7 +65,6 @@ class ExportToCSVWidget(CSVUIMixin, FlaresBaseWidget):
|
||||
QMessageBox.warning(self, "Selection Missing", "Please select at least one participant and one export type.")
|
||||
return
|
||||
|
||||
# 2. ASK ONCE: Select Output Directory
|
||||
output_dir = QFileDialog.getExistingDirectory(self, "Select Output Folder for CSV Exports")
|
||||
|
||||
if not output_dir:
|
||||
@@ -78,29 +90,11 @@ class ExportToCSVWidget(CSVUIMixin, FlaresBaseWidget):
|
||||
cha.to_csv(save_path)
|
||||
success_count += 1
|
||||
|
||||
|
||||
elif idx == 1:
|
||||
# SPARKS Export
|
||||
save_path = os.path.join(output_dir, f"{base_filename}_sparks.csv")
|
||||
if haemo_obj is not None:
|
||||
raw = haemo_obj
|
||||
data, times = raw.get_data(return_times=True)
|
||||
ann_col = np.full(times.shape, "", dtype=object)
|
||||
|
||||
if raw.annotations is not None and len(raw.annotations) > 0:
|
||||
for onset, duration, desc in zip(
|
||||
raw.annotations.onset,
|
||||
raw.annotations.duration,
|
||||
raw.annotations.description
|
||||
):
|
||||
mask = (times >= onset) & (times < onset + duration)
|
||||
ann_col[mask] = desc
|
||||
|
||||
df = pd.DataFrame(data.T, columns=raw.ch_names)
|
||||
df.insert(0, "annotation", ann_col)
|
||||
df.insert(0, "time", times)
|
||||
df.to_csv(save_path, index=False)
|
||||
success_count += 1
|
||||
sparks_csv_export(haemo_obj, save_path)
|
||||
success_count += 1
|
||||
|
||||
else:
|
||||
print(f"No method defined for index {idx}")
|
||||
@@ -119,5 +113,4 @@ class ExportToCSVWidget(CSVUIMixin, FlaresBaseWidget):
|
||||
# mode=EventUpdateMode.WRITE_JSON,
|
||||
# caller="Video Alignment Tool"
|
||||
# )
|
||||
# win.show()
|
||||
|
||||
# win.show()
|
||||
@@ -1,20 +1,29 @@
|
||||
"""
|
||||
Filename: intergroupbrainimage.py
|
||||
Description: Logic for the Inter-Group Brain & Image analysis window
|
||||
Note: Compliant with pylance strict type checking
|
||||
|
||||
Author: Tyler de Zeeuw
|
||||
License: GPL-3.0
|
||||
"""
|
||||
|
||||
# Built-in Imports
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
# External library imports
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
|
||||
from mne import Annotations
|
||||
from mne.io.base import BaseRaw
|
||||
|
||||
from flares import aggregate_fnirs_group_geometry, plot_fir_model_results, brain_3d_visualization
|
||||
from src.shared.flaresbasewidget import InterGroupUIMixin, FlaresBaseWidget
|
||||
from src.shared.shareddata import APP_NAME
|
||||
from mne.io import BaseRaw
|
||||
|
||||
|
||||
PARAMETERIZED_INDEXES = {
|
||||
PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
0: [
|
||||
{
|
||||
"key": "lower_bound",
|
||||
@@ -74,15 +83,24 @@ PARAMETERIZED_INDEXES = {
|
||||
|
||||
|
||||
class InterGroupBrainImageWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
def __init__(self, haemo_dict, cha, df_ind, design_matrix, contrast_results, group):
|
||||
def __init__(
|
||||
self,
|
||||
haemo_dict: dict[str | Path, BaseRaw],
|
||||
cha_dict: dict[str, DataFrame],
|
||||
df_ind_dict: dict[str, DataFrame],
|
||||
design_matrix_dict: dict[str, DataFrame],
|
||||
contrast_results_dict: dict[str, dict[str, Any]],
|
||||
group_dict: dict[str, str]
|
||||
) -> None:
|
||||
|
||||
super().__init__("InterGroupBrainImage")
|
||||
self.setWindowTitle(f"Inter-Group Brain & Image Viewer - {APP_NAME.upper()}")
|
||||
self.haemo_dict = haemo_dict
|
||||
self.cha = cha
|
||||
self.df_ind = df_ind
|
||||
self.design_matrix = design_matrix
|
||||
self.contrast_results = contrast_results
|
||||
self.group = group
|
||||
self.cha_dict = cha_dict
|
||||
self.df_ind_dict = df_ind_dict
|
||||
self.design_matrix_dict = design_matrix_dict
|
||||
self.contrast_results_dict = contrast_results_dict
|
||||
# self.group_dict = group_dict
|
||||
|
||||
self.setup_inter_group_ui(["0 (GLM Results)", "1 (Significance)", "2 (Brain Activity Visualization)",])
|
||||
|
||||
@@ -92,35 +110,45 @@ class InterGroupBrainImageWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
if request is None:
|
||||
return
|
||||
|
||||
(selected_event, selected_file_paths, selected_indexes, param_values,) = request
|
||||
(selected_event, selected_file_paths, selected_indexes, raw_params) = request
|
||||
|
||||
param_values = cast(dict[int | str, dict[str, Any]], raw_params)
|
||||
|
||||
all_cha = pd.DataFrame()
|
||||
for file_path in selected_file_paths:
|
||||
haemo_obj = self.haemo_dict.get(file_path)
|
||||
|
||||
if haemo_obj is None:
|
||||
continue
|
||||
|
||||
if selected_event:
|
||||
participant_events = set(haemo_obj.annotations.description)
|
||||
raw_annotations = getattr(haemo_obj, "annotations", None)
|
||||
|
||||
if raw_annotations is not None:
|
||||
annotations = cast(Annotations, raw_annotations)
|
||||
descriptions = cast(list[str], list(annotations.description))
|
||||
participant_events: set[str] = set(descriptions)
|
||||
else:
|
||||
participant_events: set[str] = set()
|
||||
|
||||
if selected_event not in participant_events:
|
||||
print(f"Skipping {self.participant_map[file_path]}: Event '{selected_event}' not found.")
|
||||
continue
|
||||
|
||||
if haemo_obj is None:
|
||||
continue
|
||||
|
||||
cha_df = self.cha.get(file_path)
|
||||
cha_df = self.cha_dict.get(file_path)
|
||||
if cha_df is not None:
|
||||
all_cha = pd.concat([all_cha, cha_df], ignore_index=True)
|
||||
|
||||
# Pass the necessary arguments to each method
|
||||
file_path = selected_file_paths[0]
|
||||
p_haemo = self.haemo_dict.get(file_path)
|
||||
p_design_matrix = self.design_matrix.get(file_path)
|
||||
p_design_matrix = self.design_matrix_dict.get(file_path)
|
||||
|
||||
df_group = pd.DataFrame()
|
||||
|
||||
if selected_file_paths:
|
||||
for file_path in selected_file_paths:
|
||||
df = self.df_ind.get(file_path)
|
||||
df = self.df_ind_dict.get(file_path)
|
||||
if df is not None:
|
||||
df_group = pd.concat([df_group, df], ignore_index=True)
|
||||
|
||||
@@ -147,9 +175,9 @@ class InterGroupBrainImageWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
print(f"Missing parameters for index {idx}, skipping.")
|
||||
continue
|
||||
|
||||
all_contrasts = []
|
||||
all_contrasts: list[DataFrame] = []
|
||||
for fp in selected_file_paths:
|
||||
condition_dfs = self.contrast_results.get(fp, {})
|
||||
condition_dfs = self.contrast_results_dict.get(fp, {})
|
||||
if selected_event in condition_dfs:
|
||||
df = condition_dfs[selected_event].copy()
|
||||
df["ID"] = fp
|
||||
@@ -159,7 +187,8 @@ class InterGroupBrainImageWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
print("No contrast data found for selected participants and event.")
|
||||
return
|
||||
|
||||
df_contrasts = pd.concat(all_contrasts, ignore_index=True)
|
||||
# TODO: look at intergroupstats and figure out what to do
|
||||
_ = pd.concat(all_contrasts, ignore_index=True)
|
||||
#flares.run_second_level_analysis(df_contrasts, p_haemo, p_val, graph_bounds)
|
||||
|
||||
elif idx == 2:
|
||||
@@ -173,13 +202,15 @@ class InterGroupBrainImageWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
print(f"Missing parameters for index {idx}, skipping.")
|
||||
continue
|
||||
|
||||
raw_list = [self.haemo_dict.get(fp) for fp in selected_file_paths]
|
||||
all_raw_objs = [self.haemo_dict.get(fp) for fp in selected_file_paths if self.haemo_dict.get(fp)]
|
||||
|
||||
if len(selected_file_paths) > 1:
|
||||
print(f"Aggregating geometry for {len(selected_file_paths)} participants...")
|
||||
processed_raw = aggregate_fnirs_group_geometry(raw_list)
|
||||
if len(all_raw_objs) > 1:
|
||||
processed_raw = aggregate_fnirs_group_geometry(all_raw_objs)
|
||||
elif len(all_raw_objs) == 1 and all_raw_objs[0] is not None:
|
||||
processed_raw = all_raw_objs[0].copy()
|
||||
processed_raw.pick(picks="hbo") # type: ignore
|
||||
else:
|
||||
processed_raw = raw_list[0].copy().pick(picks="hbo")
|
||||
processed_raw = None
|
||||
|
||||
brain_3d_visualization(processed_raw, all_cha, selected_event, t_or_theta=t_or_theta, show_optodes=show_optodes, show_text=show_text, brain_bounds=brain_bounds)
|
||||
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
"""
|
||||
Filename: intergroupfunctionalconnectivity.py
|
||||
Description: Logic for the Inter-Group Functional Connectivity analysis window
|
||||
Note: Compliant with pylance strict type checking
|
||||
|
||||
Author: Tyler de Zeeuw
|
||||
License: GPL-3.0
|
||||
"""
|
||||
|
||||
# Built-in imports
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
# External library imports
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
from mne.io.base import BaseRaw
|
||||
|
||||
from flares import run_group_functional_connectivity
|
||||
from src.shared.flaresbasewidget import InterGroupUIMixin, FlaresBaseWidget
|
||||
from src.shared.shareddata import APP_NAME
|
||||
|
||||
|
||||
PARAMETERIZED_INDEXES = {
|
||||
PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
0: [
|
||||
{
|
||||
"key": "n_lines",
|
||||
@@ -34,11 +41,17 @@ PARAMETERIZED_INDEXES = {
|
||||
|
||||
|
||||
class InterGroupFunctionalConnectivityWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
def __init__(self, haemo_dict, group, config_dict):
|
||||
def __init__(
|
||||
self,
|
||||
haemo_dict: dict[str | Path, BaseRaw],
|
||||
group_dict: dict[str, str],
|
||||
config_dict: dict[str, str]
|
||||
) -> None:
|
||||
|
||||
super().__init__("InterGroupFunctionalConnectivity")
|
||||
self.setWindowTitle(f"Inter-Group Functional Connectivity Viewer [BETA] - {APP_NAME.upper()}")
|
||||
self.haemo_dict = haemo_dict
|
||||
self.group = group
|
||||
#self.group_dict = group_dict
|
||||
self.config_dict = config_dict
|
||||
|
||||
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. "
|
||||
@@ -52,7 +65,9 @@ class InterGroupFunctionalConnectivityWidget(InterGroupUIMixin, FlaresBaseWidget
|
||||
if request is None:
|
||||
return
|
||||
|
||||
(selected_event, selected_file_paths, selected_indexes, param_values,) = request
|
||||
(selected_event, selected_file_paths, selected_indexes, raw_params) = request
|
||||
|
||||
param_values = cast(dict[int | str, dict[str, Any]], raw_params)
|
||||
|
||||
for idx in selected_indexes:
|
||||
if idx == 0:
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
"""
|
||||
Filename: intergroupstats.py
|
||||
Description: Logic for the Inter-Group Stats analysis window
|
||||
Note: Compliant with pylance strict type checking
|
||||
|
||||
Author: Tyler de Zeeuw
|
||||
License: GPL-3.0
|
||||
"""
|
||||
|
||||
# Built-in imports
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
# External library imports
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
|
||||
from mne import Annotations
|
||||
from mne.io.base import BaseRaw
|
||||
|
||||
from flares import run_roi_paired_contrast_analysis, run_roi_second_level_analysis, aggregate_channel_contrasts_to_roi
|
||||
from src.shared.flaresbasewidget import InterGroupUIMixin, FlaresBaseWidget
|
||||
from src.shared.shareddata import APP_NAME
|
||||
|
||||
|
||||
PARAMETERIZED_INDEXES = {
|
||||
PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
0: [
|
||||
{
|
||||
"key": "p_threshold",
|
||||
@@ -148,40 +157,64 @@ DESCRIPTION = """0. ROI vs. Zero (run_roi_second_level_analysis)
|
||||
|
||||
|
||||
class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
def __init__(self, haemo_dict, cha, df_ind, design_matrix, contrast_results, group, json_location):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
haemo_dict: dict[str | Path, BaseRaw],
|
||||
cha_dict: dict[str, DataFrame],
|
||||
df_ind_dict: dict[str, DataFrame],
|
||||
design_matrix_dict: dict[str, DataFrame],
|
||||
contrast_results_dict: dict[str, dict[str, Any]],
|
||||
group_dict: dict[str, str],
|
||||
json_location: str | Path
|
||||
) -> None:
|
||||
|
||||
super().__init__("InterGroupStats")
|
||||
self.setWindowTitle(f"Inter-Group Stats Viewer - {APP_NAME.upper()}")
|
||||
self.haemo_dict = haemo_dict
|
||||
self.cha = cha
|
||||
self.df_ind = df_ind
|
||||
self.design_matrix = design_matrix
|
||||
self.contrast_results = contrast_results
|
||||
self.group = group
|
||||
self.cha_dict = cha_dict
|
||||
self.df_ind_dict = df_ind_dict
|
||||
self.design_matrix_dict = design_matrix_dict
|
||||
self.contrast_results_dict = contrast_results_dict
|
||||
self.group_dict = group_dict
|
||||
self.json_location = json_location
|
||||
|
||||
self.setup_inter_group_ui(["0 (ROI vs. Zero)", "1 (Paired ROI Contrast)", "2 (Joint Contrast, ROI-Aggregated)"], placeholder_text=DESCRIPTION)
|
||||
|
||||
|
||||
def process_request(self):
|
||||
request = self.get_common_request_data(PARAMETERIZED_INDEXES, self.json_location, self.contrast_results)
|
||||
request = self.get_common_request_data(PARAMETERIZED_INDEXES, self.json_location, self.contrast_results_dict)
|
||||
if request is None:
|
||||
return
|
||||
|
||||
(selected_event, selected_file_paths, selected_indexes, param_values,) = request
|
||||
(selected_event, selected_file_paths, selected_indexes, raw_params) = request
|
||||
|
||||
all_cha = pd.DataFrame()
|
||||
param_values = cast(dict[int | str, dict[str, Any]], raw_params)
|
||||
|
||||
all_cha = DataFrame()
|
||||
for file_path in selected_file_paths:
|
||||
haemo_obj = self.haemo_dict.get(file_path)
|
||||
|
||||
if haemo_obj is None:
|
||||
continue
|
||||
|
||||
if selected_event:
|
||||
participant_events = set(haemo_obj.annotations.description)
|
||||
raw_annotations = getattr(haemo_obj, "annotations", None)
|
||||
|
||||
if raw_annotations is not None:
|
||||
annotations = cast(Annotations, raw_annotations)
|
||||
descriptions = cast(list[str], list(annotations.description))
|
||||
participant_events: set[str] = set(descriptions)
|
||||
else:
|
||||
participant_events: set[str] = set()
|
||||
|
||||
if selected_event not in participant_events:
|
||||
print(f"Skipping {self.participant_map[file_path]}: Event '{selected_event}' not found.")
|
||||
continue
|
||||
|
||||
if haemo_obj is None:
|
||||
continue
|
||||
|
||||
|
||||
cha_df = self.cha.get(file_path)
|
||||
cha_df = self.cha_dict.get(file_path)
|
||||
if cha_df is not None:
|
||||
all_cha = pd.concat([all_cha, cha_df], ignore_index=True)
|
||||
|
||||
@@ -189,10 +222,10 @@ class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
p_haemo = self.haemo_dict.get(file_path)
|
||||
|
||||
# Concatenate individual ROI stats (df_ind) for all chosen subjects
|
||||
df_group = pd.DataFrame()
|
||||
df_group = DataFrame()
|
||||
if selected_file_paths:
|
||||
for file_path in selected_file_paths:
|
||||
df = self.df_ind.get(file_path)
|
||||
df = self.df_ind_dict.get(file_path)
|
||||
if df is not None:
|
||||
df_group = pd.concat([df_group, df], ignore_index=True)
|
||||
|
||||
@@ -226,7 +259,7 @@ class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
print(f"No ROI data matches the condition '{selected_event}'.")
|
||||
continue
|
||||
|
||||
all_cha_filtered = pd.DataFrame()
|
||||
all_cha_filtered = DataFrame()
|
||||
if not all_cha.empty:
|
||||
if selected_event and 'Condition' in all_cha.columns:
|
||||
all_cha_filtered = all_cha[all_cha['Condition'] == selected_event]
|
||||
@@ -304,9 +337,9 @@ class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
continue
|
||||
|
||||
|
||||
all_contrasts = []
|
||||
all_contrasts: list[DataFrame] = []
|
||||
for fp in selected_file_paths:
|
||||
condition_dfs = self.contrast_results.get(fp)
|
||||
condition_dfs = self.contrast_results_dict.get(fp)
|
||||
if condition_dfs is None:
|
||||
print(f" [MISSING] '{fp}' not found in contrast_results.")
|
||||
continue
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
"""
|
||||
Filename: participantbrain.py
|
||||
Description: Logic for the Participant Brain analysis window
|
||||
Note: Compliant with pylance strict type checking
|
||||
|
||||
Author: Tyler de Zeeuw
|
||||
License: GPL-3.0
|
||||
"""
|
||||
|
||||
# Built-in imports
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
# External library imports
|
||||
from mne import Annotations
|
||||
from pandas import DataFrame
|
||||
|
||||
from mne.io.base import BaseRaw
|
||||
|
||||
from flares import brain_3d_visualization, brain_landmarks_3d
|
||||
from src.shared.flaresbasewidget import ParticipantUIMixin, FlaresBaseWidget
|
||||
from src.shared.shareddata import APP_NAME
|
||||
|
||||
|
||||
PARAMETERIZED_INDEXES = {
|
||||
PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
0: [
|
||||
{
|
||||
"key": "show_optodes",
|
||||
@@ -57,7 +67,12 @@ PARAMETERIZED_INDEXES = {
|
||||
|
||||
|
||||
class ParticipantBrainViewerWidget(ParticipantUIMixin, FlaresBaseWidget):
|
||||
def __init__(self, haemo_dict, cha_dict):
|
||||
def __init__(
|
||||
self,
|
||||
haemo_dict: dict[str | Path, BaseRaw],
|
||||
cha_dict: dict[str, DataFrame],
|
||||
) -> None:
|
||||
|
||||
super().__init__("ParticipantBrain")
|
||||
self.setWindowTitle(f"Participant Brain Viewer - {APP_NAME.upper()}")
|
||||
self.haemo_dict = haemo_dict
|
||||
@@ -72,21 +87,31 @@ class ParticipantBrainViewerWidget(ParticipantUIMixin, FlaresBaseWidget):
|
||||
if request is None:
|
||||
return
|
||||
|
||||
(selected_event, selected_file_paths, selected_indexes, param_values,) = request
|
||||
(selected_event, selected_file_paths, selected_indexes, raw_params) = request
|
||||
|
||||
param_values = cast(dict[int | str, dict[str, Any]], raw_params)
|
||||
|
||||
# Pass the necessary arguments to each method
|
||||
for file_path in selected_file_paths:
|
||||
haemo_obj = self.haemo_dict.get(file_path)
|
||||
|
||||
if haemo_obj is None:
|
||||
continue
|
||||
|
||||
if selected_event:
|
||||
participant_events = set(haemo_obj.annotations.description)
|
||||
raw_annotations = getattr(haemo_obj, "annotations", None)
|
||||
|
||||
if raw_annotations is not None:
|
||||
annotations = cast(Annotations, raw_annotations)
|
||||
descriptions = cast(list[str], list(annotations.description))
|
||||
participant_events: set[str] = set(descriptions)
|
||||
else:
|
||||
participant_events: set[str] = set()
|
||||
|
||||
if selected_event not in participant_events:
|
||||
print(f"Skipping {self.participant_map[file_path]}: Event '{selected_event}' not found.")
|
||||
continue
|
||||
|
||||
if haemo_obj is None:
|
||||
raise Exception("How did we get here?")
|
||||
|
||||
cha = self.cha_dict.get(file_path)
|
||||
|
||||
for idx in selected_indexes:
|
||||
|
||||
@@ -71,7 +71,7 @@ def single_participant_worker(file_path, raw_data, result_queue, progress_queue)
|
||||
try:
|
||||
from flares import fold_channels
|
||||
# Perform the heavy fold_channels logic
|
||||
channel_results = fold_channels(raw_data, p_name, progress_queue)
|
||||
channel_results = fold_channels(raw=raw_data, p_name=p_name, progress_queue=progress_queue)
|
||||
|
||||
# Hand back results and signal completion
|
||||
result_queue.put({file_path: channel_results})
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
"""
|
||||
Filename: participantfunctionalconnectivity.py
|
||||
Description: Logic for the Participant Functional Connectivity analysis window
|
||||
Note: Compliant with pylance strict type checking
|
||||
|
||||
Author: Tyler de Zeeuw
|
||||
License: GPL-3.0
|
||||
"""
|
||||
|
||||
# Built-in Imports
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
# External library imports
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
from pandas import DataFrame
|
||||
|
||||
from mne import Annotations
|
||||
from mne.io.base import BaseRaw
|
||||
|
||||
from flares import functional_connectivity_betas, functional_connectivity_envelope, functional_connectivity_spectral_epochs, functional_connectivity_spectral_time
|
||||
from src.shared.flaresbasewidget import ParticipantUIMixin, FlaresBaseWidget
|
||||
from src.shared.shareddata import APP_NAME
|
||||
|
||||
|
||||
PARAMETERIZED_INDEXES = {
|
||||
PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
0: [
|
||||
{
|
||||
"key": "n_lines",
|
||||
@@ -79,7 +89,12 @@ PARAMETERIZED_INDEXES = {
|
||||
|
||||
|
||||
class ParticipantFunctionalConnectivityWidget(ParticipantUIMixin, FlaresBaseWidget):
|
||||
def __init__(self, haemo_dict, epochs_dict):
|
||||
def __init__(
|
||||
self,
|
||||
haemo_dict: dict[str | Path, BaseRaw],
|
||||
epochs_dict: dict[str, DataFrame],
|
||||
) -> None:
|
||||
|
||||
super().__init__("ParticipantFunctionalConnectivity")
|
||||
self.setWindowTitle(f"Participant Functional Connectivity Viewer [BETA] - {APP_NAME.upper()}")
|
||||
self.haemo_dict = haemo_dict
|
||||
@@ -97,22 +112,32 @@ class ParticipantFunctionalConnectivityWidget(ParticipantUIMixin, FlaresBaseWidg
|
||||
if request is None:
|
||||
return
|
||||
|
||||
(selected_event, selected_file_paths, selected_indexes, param_values,) = request
|
||||
(selected_event, selected_file_paths, selected_indexes, raw_params) = request
|
||||
|
||||
param_values = cast(dict[int | str, dict[str, Any]], raw_params)
|
||||
|
||||
# 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:
|
||||
continue
|
||||
|
||||
if selected_event:
|
||||
participant_events = set(haemo_obj.annotations.description)
|
||||
raw_annotations = getattr(haemo_obj, "annotations", None)
|
||||
|
||||
if raw_annotations is not None:
|
||||
annotations = cast(Annotations, raw_annotations)
|
||||
descriptions = cast(list[str], list(annotations.description))
|
||||
participant_events: set[str] = set(descriptions)
|
||||
else:
|
||||
participant_events: set[str] = set()
|
||||
|
||||
if selected_event not in participant_events:
|
||||
print(f"Skipping {self.participant_map[file_path]}: Event '{selected_event}' not found.")
|
||||
continue
|
||||
|
||||
if haemo_obj is None:
|
||||
raise Exception("How did we get here?")
|
||||
|
||||
|
||||
for idx in selected_indexes:
|
||||
if idx == 0:
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
"""
|
||||
Filename: participantimage.py
|
||||
Description: Logic for the Participant Image analysis window
|
||||
Note: Compliant with pylance strict type checking
|
||||
|
||||
Author: Tyler de Zeeuw
|
||||
License: GPL-3.0
|
||||
"""
|
||||
|
||||
# Built-in Imports
|
||||
import os
|
||||
import os.path as op
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# External library imports
|
||||
from mne.io.base import BaseRaw
|
||||
|
||||
from PySide6.QtWidgets import QGridLayout, QHBoxLayout, QMessageBox, QPushButton, QScrollArea, QWidget, QVBoxLayout, QLabel
|
||||
from PySide6.QtCore import Qt, QSize
|
||||
from PySide6.QtGui import QPixmap
|
||||
@@ -21,7 +24,13 @@ from src.shared.shareddata import APP_NAME
|
||||
|
||||
|
||||
class ParticipantImageViewerWidget(FlaresBaseWidget):
|
||||
def __init__(self, haemo_dict, fig_bytes_dict):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
haemo_dict: dict[str, BaseRaw],
|
||||
fig_bytes_dict: dict[str, dict[str, bytes]]
|
||||
) -> None:
|
||||
|
||||
super().__init__("ParticipantImage")
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
|
||||
self.setWindowTitle(f"Participant Image Viewer - {APP_NAME.upper()}")
|
||||
@@ -29,12 +38,12 @@ class ParticipantImageViewerWidget(FlaresBaseWidget):
|
||||
self.fig_bytes_dict = fig_bytes_dict
|
||||
|
||||
# Create mappings: file_path -> participant label and dropdown display text
|
||||
self.participant_map = {} # file_path -> "Participant 1"
|
||||
self.participant_dropdown_items = [] # "Participant 1 (filename)"
|
||||
self.participant_map: dict[str, str] = {}
|
||||
self.participant_dropdown_items: list[str] = []
|
||||
|
||||
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)})"
|
||||
display_label = f"{short_label} ({op.basename(file_path)})"
|
||||
self.participant_map[file_path] = short_label
|
||||
self.participant_dropdown_items.append(display_label)
|
||||
|
||||
@@ -87,23 +96,24 @@ class ParticipantImageViewerWidget(FlaresBaseWidget):
|
||||
|
||||
selected_display_names = self._get_checked_items(self.participant_dropdown)
|
||||
# Map from display names back to file paths
|
||||
selected_file_paths = []
|
||||
selected_file_paths: list[str] = []
|
||||
for display_name in selected_display_names:
|
||||
# Find file_path by matching display name
|
||||
for fp, short_label in self.participant_map.items():
|
||||
expected_display = f"{short_label} ({os.path.basename(fp)})"
|
||||
expected_display = f"{short_label} ({Path(fp).name})"
|
||||
if display_name == expected_display:
|
||||
selected_file_paths.append(fp)
|
||||
selected_file_paths.append(str(fp))
|
||||
break
|
||||
|
||||
selected_labels = self._get_checked_items(self.image_index_dropdown)
|
||||
|
||||
row, col = 0, 0
|
||||
for file_path in selected_file_paths:
|
||||
fig_list = self.fig_bytes_dict.get(file_path, [])
|
||||
participant_label = self.participant_map[file_path]
|
||||
fig_map: dict[str, bytes] = self.fig_bytes_dict.get(file_path, {})
|
||||
participant_label: str = self.participant_map.get(file_path, "Unknown")
|
||||
|
||||
for label in selected_labels:
|
||||
fig_bytes = fig_list.get(label)
|
||||
fig_bytes: bytes | None = fig_map.get(label)
|
||||
if not fig_bytes:
|
||||
continue
|
||||
|
||||
@@ -149,7 +159,7 @@ class ParticipantImageViewerWidget(FlaresBaseWidget):
|
||||
for display_name in selected_display_names:
|
||||
# Match display name to file path
|
||||
for file_path, short_label in self.participant_map.items():
|
||||
expected_display = f"{short_label} ({os.path.basename(file_path)})"
|
||||
expected_display = f"{short_label} ({op.basename(file_path)})"
|
||||
if display_name == expected_display:
|
||||
fig_dict = self.fig_bytes_dict.get(file_path, {})
|
||||
for label in selected_image_labels:
|
||||
@@ -157,7 +167,7 @@ class ParticipantImageViewerWidget(FlaresBaseWidget):
|
||||
continue
|
||||
fig_bytes = fig_dict[label]
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"{os.path.basename(file_path)}_{label}_{timestamp}.png"
|
||||
filename = f"{op.basename(file_path)}_{label}_{timestamp}.png"
|
||||
output_path = save_dir / filename
|
||||
with open(output_path, "wb") as f:
|
||||
f.write(fig_bytes)
|
||||
|
||||
Reference in New Issue
Block a user