pylance standardization

This commit is contained in:
2026-07-21 00:54:51 -07:00
parent 2b019c1bc0
commit 8b017005c5
20 changed files with 608 additions and 277 deletions
+187 -88
View File
@@ -8,12 +8,13 @@ License: GPL-3.0
# Built-in imports
import os
from pathlib import Path
import sys
import platform
import threading
import logging
from io import BytesIO
from typing import Any, Optional, cast, Literal, Union
from typing import Any, Optional, Sequence, cast, Literal, Union
from itertools import compress
from copy import deepcopy
from multiprocessing import Queue, Pool
@@ -1765,29 +1766,6 @@ def _check_load_fold(fold_files, atlas):
def fold_channel_specificity_normal(raw, fold_files=None, atlas="Juelich", interpolate=False):
"""Return the landmarks and specificity a channel is sensitive to.
Parameters
""" # noqa: E501
_validate_type(raw, BaseRaw, "raw")
reference_locations = generate_montage_locations()
fold_tbl = _check_load_fold(fold_files, atlas)
chan_spec = list()
for cidx in range(len(raw.ch_names)):
tbl = _source_detector_fold_table(
raw, cidx, reference_locations, fold_tbl, interpolate
)
chan_spec.append(tbl.reset_index(drop=True))
return chan_spec
def resource_path(relative_path):
"""
Get absolute path to resource regardless of running directly or packaged using PyInstaller
@@ -1958,35 +1936,42 @@ def resource_path(relative_path):
def fold_channels(raw: BaseRaw, p_name: str, progress_queue=None) -> dict[str, list[dict[str, Any]]]:
def fold_channels(raw: BaseRaw, p_name: str, atlas: str='Brodmann', progress_queue=None) -> dict[str, list[dict[str, Any]]]:
"""Runs in background process.
Does only heavy math/lookup. Returns data instead of a static image.
"""
if getattr(sys, 'frozen', False):
set_config('MNE_NIRS_FOLD_PATH', resource_path("./mne_data/fOLD/fOLD-public-master/Supplementary"))
fold_dir = resource_path("./mne_data/fOLD/fOLD-public-master/Supplementary")
else:
path = os.path.expanduser("~") + "/mne_data/fOLD/fOLD-public-master/Supplementary"
set_config('MNE_NIRS_FOLD_PATH', resource_path(path))
fold_dir = resource_path(path)
set_config('MNE_NIRS_FOLD_PATH', fold_dir)
hbo_channel_names = cast(list[str], getattr(raw.copy().pick(picks='hbo'), "ch_names"))
# Store clean, picklable data lists instead of complex DataFrames
channel_results = {}
_validate_type(raw, BaseRaw, "raw")
reference_locations = generate_montage_locations()
fold_tbl = _check_load_fold(fold_files=fold_dir, atlas=atlas)
channel_results = {}
step_idx = 0
for channel_name in hbo_channel_names:
channel_data = raw.copy().pick(picks=channel_name)
output = cast(list[DataFrame], fold_channel_specificity_normal(channel_data, interpolate=True, atlas='Brodmann'))
for cidx, channel_name in enumerate(hbo_channel_names):
tbl = _source_detector_fold_table(
raw, cidx, reference_locations, fold_tbl, interpolate=True
)
channel_results[channel_name] = []
for df_data in output:
# Extract just raw primitive types so they transfer over process channels flawlessly
for _, row in df_data.iterrows():
channel_results[channel_name].append({
'Landmark': str(row['Landmark']),
'Specificity': float(row['Specificity'])
})
for _, row in tbl.iterrows():
channel_results[channel_name].append({
'Landmark': str(row['Landmark']),
'Specificity': float(row['Specificity'])
})
step_idx += 1
if progress_queue is not None:
progress_queue.put((p_name, step_idx))
@@ -2180,7 +2165,7 @@ def plot_3d_evoked_array(
return brain
def aggregate_fnirs_group_geometry(raw_list):
def aggregate_fnirs_group_geometry(raw_list: Sequence[BaseRaw | None]) -> BaseRaw:
"""
Averages fNIRS geometry across participants in two tiers:
1. Average by Channel Pairing (S_D).
@@ -2246,7 +2231,15 @@ def aggregate_fnirs_group_geometry(raw_list):
def brain_3d_visualization(raw_haemo, df_cha, selected_event, t_or_theta: Literal['t', 'theta'] = 'theta', show_optodes: Literal['sensors', 'labels', 'none', 'all'] = 'all', show_text: bool = True, brain_bounds: float = 1.0) -> None:
def brain_3d_visualization(
raw_haemo: BaseRaw | None,
df_cha: DataFrame | None,
selected_event: str | None,
t_or_theta: Literal["t", "theta"] = "theta",
show_optodes: Literal["sensors", "labels", "none", "all"] = "all",
show_text: bool = True,
brain_bounds: float | tuple[float, float] | Sequence[float] = 1.0,
) -> None:
clim = dict(kind="value", pos_lims=(0, brain_bounds/2, brain_bounds))
@@ -2582,7 +2575,14 @@ def plot_2d_3d_contrasts_between_groups(
def plot_fir_model_results(df, raw_haemo, dm, selected_event, l_bound, u_bound):
def plot_fir_model_results(
df: DataFrame,
raw_haemo: BaseRaw | None,
dm: DataFrame | None,
selected_event: str | None,
l_bound: float,
u_bound: float,
) -> None:
df["isActivity"] = [f"{selected_event}" in n for n in df["Condition"]]
@@ -2801,11 +2801,19 @@ def load_snirf(file_path: str) -> tuple[BaseRaw, Figure]:
def run_roi_second_level_analysis(df_roi_all, df_cha_all=None, raw_haemo=None,
p_threshold=0.05, min_subjects=5,
correction_method='fdr_bh', target_chroma='hbo',
graph_bounds=None, roi_config=None,
threshold_topo=False): # Added parameter
def run_roi_second_level_analysis(
df_roi_all: DataFrame,
df_cha_all: DataFrame | None = None,
raw_haemo: BaseRaw | None = None,
p_threshold: float = 0.05,
min_subjects: int = 5,
correction_method: str | None = "fdr_bh",
target_chroma: str = "hbo",
graph_bounds: float | None = None,
roi_config: str | Path | None = None,
threshold_topo: bool = False,
) -> DataFrame:
"""
Perform group-level ROI analysis, prints stats to console, plots the ROI bar chart,
and dynamically plots isolated channel-level group topography maps based on a JSON config.
@@ -3064,13 +3072,24 @@ def clean_subject_id(path_or_id):
def run_cross_group_second_level_analysis(df_roi_all, file_paths_a, file_paths_b,
group_a_name="Group A", group_b_name="Group B",
df_cha_all=None, raw_haemo=None,
p_threshold=0.05, min_subjects=3,
correction_method='fdr_bh', target_chroma='hbo',
selected_event=None, graph_bounds=None,
roi_config=None, threshold_topo=False):
def run_cross_group_second_level_analysis(
df_roi_all: DataFrame,
file_paths_a: list[str],
file_paths_b: list[str],
group_a_name: str = "Group A",
group_b_name: str = "Group B",
df_cha_all: DataFrame | None = None,
raw_haemo: Any = None,
p_threshold: float = 0.05,
min_subjects: int = 3,
correction_method: str | None = "fdr_bh",
target_chroma: str = "hbo",
selected_event: str | None = None,
graph_bounds: tuple[float, float] | list[float] | None = None,
roi_config: Path | str | None = None,
threshold_topo: bool = False,
) -> DataFrame:
"""
Perform cross-group independent statistical analyses (Group A vs Group B),
renders a grouped bar chart with significance brackets, and plots a group-contrast topography map.
@@ -3331,11 +3350,21 @@ def run_cross_group_second_level_analysis(df_roi_all, file_paths_a, file_paths_b
def run_cross_group_laterality_analysis(df_roi_all_a, df_roi_all_b, roi_pairs, condition,
group_a_name="Group A", group_b_name="Group B",
target_chroma='hbo', min_subjects=3,
p_threshold=0.05, correction_method=None,
roi_contra_label=None, roi_ipsi_label=None):
def run_cross_group_laterality_analysis(
df_roi_all_a: DataFrame,
df_roi_all_b: DataFrame,
roi_pairs: tuple[str, str] | None,
condition: str | None,
group_a_name: str = "Group A",
group_b_name: str = "Group B",
target_chroma: str = "hbo",
min_subjects: int = 3,
p_threshold: float = 0.05,
correction_method: str | None = None,
roi_contra_label: str | None = None,
roi_ipsi_label: str | None = None,
) -> DataFrame:
"""
Compare LATERALITY between two independent groups of subjects (e.g. a
control group vs. a target group), using Welch's t-test on each
@@ -3582,11 +3611,20 @@ def run_cross_group_laterality_analysis(df_roi_all_a, df_roi_all_b, roi_pairs, c
def run_cross_group_contrast_analysis(df_contrasts_a, df_contrasts_b, contrast_name, roi_json_path,
group_a_name="Group A", group_b_name="Group B",
target_chroma='hbo', min_subjects=3,
p_threshold=0.05, correction_method='fdr_bh',
weighted=True):
def run_cross_group_contrast_analysis(
df_contrasts_a: DataFrame,
df_contrasts_b: DataFrame,
contrast_name: str,
roi_json_path: str | Path | None,
group_a_name: str = "Group A",
group_b_name: str = "Group B",
target_chroma: str = "hbo",
min_subjects: int = 3,
p_threshold: float = 0.05,
correction_method: str = "fdr_bh",
weighted: bool = True,
) -> DataFrame:
"""
Compare a JOINT-FIT TASK CONTRAST (e.g. '2.0_vs_3.0'), aggregated to ROI
level, between two independent groups. This is the cross-group analog
@@ -3665,10 +3703,10 @@ def run_cross_group_contrast_analysis(df_contrasts_a, df_contrasts_b, contrast_n
if df_a_filt.empty:
print(f"[ERROR] Contrast '{contrast_name}' not found anywhere in {group_a_name}'s data.")
return pd.DataFrame()
return DataFrame()
if df_b_filt.empty:
print(f"[ERROR] Contrast '{contrast_name}' not found anywhere in {group_b_name}'s data.")
return pd.DataFrame()
return DataFrame()
roi_a = aggregate_channel_contrasts_to_roi(df_a_filt, roi_json_path, weighted=weighted)
roi_b = aggregate_channel_contrasts_to_roi(df_b_filt, roi_json_path, weighted=weighted)
@@ -3679,7 +3717,7 @@ def run_cross_group_contrast_analysis(df_contrasts_a, df_contrasts_b, contrast_n
if roi_a.empty or roi_b.empty:
print(f"[ERROR] No ROI-aggregated values produced for one or both groups "
f"(check regions.json channel names against this montage).")
return pd.DataFrame()
return DataFrame()
all_rois = sorted(set(roi_a['ROI'].unique()) | set(roi_b['ROI'].unique()))
results = []
@@ -3798,10 +3836,17 @@ def run_cross_group_contrast_analysis(df_contrasts_a, df_contrasts_b, contrast_n
def run_roi_paired_contrast_analysis(df_roi_all, roi_pairs, condition,
target_chroma='hbo', min_subjects=5,
p_threshold=0.05, correction_method=None,
roi_a_label=None, roi_b_label=None):
def run_roi_paired_contrast_analysis(
df_roi_all: DataFrame,
roi_pairs: Sequence[tuple[str, str]] | list[list[str]],
condition: str,
target_chroma: str = 'hbo',
min_subjects: int = 5,
p_threshold: float = 0.05,
correction_method: str | None = None,
roi_a_label: str | None = None,
roi_b_label: str | None = None,
) -> DataFrame:
"""
Paired within-subject ROI contrast (e.g. contralateral minus ipsilateral
motor ROI), as a companion to run_roi_second_level_analysis rather than a
@@ -4012,7 +4057,11 @@ def run_roi_paired_contrast_analysis(df_roi_all, roi_pairs, condition,
def aggregate_channel_contrasts_to_roi(df_contrasts, roi_json_path, weighted=True):
def aggregate_channel_contrasts_to_roi(
df_contrasts: DataFrame,
roi_json_path: str | Path | None,
weighted: bool = True
) -> DataFrame:
"""
Combine already-computed per-channel CONTRAST results (e.g. your
'2.0_vs_3.0' rows from contrasts.csv / contrast_results) into
@@ -5519,13 +5568,13 @@ def process_participant(file_path, progress_callback=None):
if BAD_CHANNELS_HANDLING != "None" and not FOLDING_BYP:
raw, fig_dropped, fig_raw_before, bad_channels = mark_bads(raw, bad_sci, bad_snr, bad_psp, bad_coeff_var, bad_amplitude_range, bad_noise, bad_disp)
if fig_dropped and fig_raw_before is not None:
fig_individual["fig2"] = fig_dropped
fig_individual["fig3"] = fig_raw_before
fig_individual["Bad Channels by Method"] = fig_dropped
fig_individual["Bad Channels Data"] = fig_raw_before
if bad_channels:
if BAD_CHANNELS_HANDLING == "Interpolate":
raw, fig_raw_after, fig_compare = interpolate_fNIRS_bads_weighted_average(raw, max_dist=MAX_DIST, min_neighbors=MIN_NEIGHBORS)
fig_individual["fig4"] = fig_raw_after
fig_individual["Compare"] = fig_compare
fig_individual["Data after Interpolating Bad Channels"] = fig_raw_after
fig_individual["Bad Channels Interpolation Results"] = fig_compare
elif BAD_CHANNELS_HANDLING == "Remove":
raw = remove_bad_channels(raw, bad_channels)
if progress_callback: progress_callback(13)
@@ -5542,7 +5591,7 @@ def process_participant(file_path, progress_callback=None):
if TDDR and not FOLDING_BYP:
raw_od = temporal_derivative_distribution_repair(raw_od)
fig_raw_od_tddr = raw_od.plot(duration=raw.times[-1], n_channels=raw.info['nchan'], title="After TDDR (Motion Correction)", show=False)
fig_individual["TDDR"] = fig_raw_od_tddr
fig_individual["Temporal Derivative Distribution Repair"] = fig_raw_od_tddr
if progress_callback: progress_callback(15)
logger.info("Step 15 Completed.")
@@ -5556,7 +5605,7 @@ def process_participant(file_path, progress_callback=None):
# Step 17: Haemoglobin Concentration
raw_haemo = beer_lambert_law(raw_od, ppf=calculate_dpf(file_path))
fig_raw_haemo_bll = raw_haemo.plot(duration=raw_haemo.times[-1], n_channels=raw_haemo.info['nchan'], title="HbO and HbR Signals", show=False)
fig_individual["BLL"] = fig_raw_haemo_bll
fig_individual["Modified Beer Lambert Law"] = fig_raw_haemo_bll
if progress_callback: progress_callback(17)
logger.info("Step 17 Completed.")
@@ -5564,15 +5613,15 @@ def process_participant(file_path, progress_callback=None):
if ENHANCE_NEGATIVE_CORRELATION and not FOLDING_BYP:
raw_haemo = enhance_negative_correlation(raw_haemo)
fig_raw_haemo_enc = raw_haemo.plot(duration=raw_haemo.times[-1], n_channels=raw_haemo.info['nchan'], title="Enhance Negative Correlation", show=False)
fig_individual["ENC"] = fig_raw_haemo_enc
fig_individual["Enhance Negative Correlation"] = fig_raw_haemo_enc
if progress_callback: progress_callback(18)
logger.info("Step 18 Completed.")
# Step 19: Filter
if FILTER and not FOLDING_BYP:
raw_haemo, fig_filter, fig_raw_haemo_filter = filter_the_data(raw_haemo)
fig_individual["filter1"] = fig_filter
fig_individual["filter2"] = fig_raw_haemo_filter
fig_individual["Filter_1"] = fig_filter
fig_individual["Filter_2"] = fig_raw_haemo_filter
if progress_callback: progress_callback(19)
logger.info("Step 19 Completed.")
@@ -5580,7 +5629,7 @@ def process_participant(file_path, progress_callback=None):
if not FOLDING_BYP:
events, event_dict = events_from_annotations(raw_haemo)
fig_events = plot_events(events, event_id=event_dict, sfreq=raw_haemo.info["sfreq"], show=False)
fig_individual["events"] = fig_events
fig_individual["Events"] = fig_events
if progress_callback: progress_callback(20)
logger.info("Step 20 Completed.")
@@ -5652,7 +5701,11 @@ def sanitize_paths_for_pickle(raw_haemo, epochs):
epochs._raw._filenames = [str(p) for p in epochs._raw._filenames]
def functional_connectivity_spectral_epochs(epochs, n_lines, vmin):
def functional_connectivity_spectral_epochs(
epochs: DataFrame | None,
n_lines: int,
vmin: float,
) -> None:
# will crash without this load
epochs.load_data()
@@ -5691,7 +5744,11 @@ def functional_connectivity_spectral_epochs(epochs, n_lines, vmin):
def functional_connectivity_spectral_time(epochs, n_lines, vmin):
def functional_connectivity_spectral_time(
epochs: DataFrame | None,
n_lines: int,
vmin: float,
) -> None:
# will crash without this load
epochs.load_data()
@@ -5735,7 +5792,12 @@ def functional_connectivity_spectral_time(epochs, n_lines, vmin):
def functional_connectivity_envelope(epochs, n_lines, vmin):
def functional_connectivity_envelope(
epochs: DataFrame | None,
n_lines: int,
vmin: float,
) -> None:
# will crash without this load
epochs.load_data()
@@ -5765,7 +5827,12 @@ def functional_connectivity_envelope(epochs, n_lines, vmin):
)
def functional_connectivity_betas(raw_hbo, n_lines, vmin, event_name=None):
def functional_connectivity_betas(
raw_hbo: BaseRaw,
n_lines: int,
vmin: float,
event_name: str | None = None,
) -> None:
raw_hbo = raw_hbo.copy().pick(picks="hbo")
onsets = raw_hbo.annotations.onset
@@ -5966,7 +6033,15 @@ def get_single_subject_beta_corr(raw_hbo, event_name=None, config=None):
return corr_matrix, raw_hbo.ch_names
def run_group_functional_connectivity(haemo_dict, config_dict, selected_paths, event_name, n_lines, vmin):
def run_group_functional_connectivity(
haemo_dict: dict[str | Path, BaseRaw],
config_dict: dict[str, Any],
selected_paths: list[str],
event_name: str | None,
n_lines: int,
vmin: float,
) -> None:
"""Aggregates multiple participants and triggers the plot."""
all_z_matrices = []
common_names = None
@@ -6067,4 +6142,28 @@ def run_group_functional_connectivity(haemo_dict, config_dict, selected_paths, e
sig_avg_r, common_names, n_lines=n_lines,
title=f"Group Connectivity: {event_name if event_name else 'All Events'}",
vmin=vmin, vmax=1.0, colormap='hot'
)
)
def sparks_csv_export(
haemo_obj: BaseRaw,
save_path: str,
) -> 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)