pylance things
This commit is contained in:
+176
-223
@@ -15,10 +15,9 @@ from typing import Any, cast
|
||||
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 flares import run_inter_group_contrast_analysis, run_inter_group_laterality_analysis, run_inter_group_second_level_analysis
|
||||
from src.shared.flaresbasewidget import InterGroupUIMixin, FlaresBaseWidget
|
||||
from src.shared.shareddata import APP_NAME
|
||||
|
||||
@@ -34,7 +33,7 @@ PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
{
|
||||
"key": "min_subjects",
|
||||
"label": "Minimum number of participants to process",
|
||||
"default": "5",
|
||||
"default": "3",
|
||||
"type": int,
|
||||
},
|
||||
{
|
||||
@@ -50,10 +49,10 @@ PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
"type": str,
|
||||
},
|
||||
{
|
||||
"key": "graph_bounds",
|
||||
"label": "Graph Upper/Lower Limit",
|
||||
"default": "0.0",
|
||||
"type": float,
|
||||
"key": "threshold_topo",
|
||||
"label": "threshold_topo: TBD",
|
||||
"default": False,
|
||||
"type": bool,
|
||||
}
|
||||
],
|
||||
1: [
|
||||
@@ -66,7 +65,7 @@ PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
{
|
||||
"key": "min_subjects",
|
||||
"label": "Minimum number of participants to process",
|
||||
"default": "5",
|
||||
"default": "3",
|
||||
"type": int,
|
||||
},
|
||||
{
|
||||
@@ -104,7 +103,7 @@ PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
{
|
||||
"key": "min_subjects",
|
||||
"label": "Minimum number of participants to process",
|
||||
"default": "5",
|
||||
"default": "3",
|
||||
"type": int,
|
||||
},
|
||||
{
|
||||
@@ -125,31 +124,19 @@ PARAMETERIZED_INDEXES: dict[int, list[dict[str, Any]]] = {
|
||||
"default": [],
|
||||
"type": list,
|
||||
},
|
||||
{
|
||||
"key": "weighted",
|
||||
"label": "Use inverse-variance weighting to minimize noisy channels",
|
||||
"default": True,
|
||||
"type": bool,
|
||||
},
|
||||
{
|
||||
"key": "graph_bounds",
|
||||
"label": "Graph Upper/Lower Limit",
|
||||
"default": "0.0",
|
||||
"type": float,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
||||
DESCRIPTION = """0. ROI vs. Zero (run_roi_second_level_analysis)
|
||||
\nTests whether one ROI's response during one condition reliably differs from zero across subjects - a one-sample t-test on each subject's ROI-averaged theta. A significant result means the region's signal during this condition is consistently non-zero across your sample, not just noise. It does not tell you whether the response is localized/specific to this region, or whether it reflects real neural activity versus systemic physiology (blood pressure, arousal) shared across the whole head during any active task - a single-condition "vs. zero" test can't distinguish those two explanations on its own.
|
||||
\nIf you expected significance here and didn't get it, likely causes include: the sample size is simply small relative to between-subject variability in true response magnitude or HRF shape (individual differences in timing/amplitude inflate the variance a t-test divides by); the ROI's channel composition differs slightly across subjects (missing channels get down-weighted or excluded from the inverse-variance average, diluting a real signal); FDR correction across many ROIs is suppressing a modest true effect that would clear an uncorrected threshold; or the condition itself may not reliably engage this region the way you assumed (worth checking the single-subject/individual-level results for this ROI before concluding the group effect isn't there).
|
||||
\n\n1. Paired ROI Contrast (run_roi_paired_contrast_analysis)
|
||||
\nFor one condition, subtracts each subject's ROI_A response from their ROI_B response, then tests whether that per-subject difference is reliably non-zero. A significant result is a genuine spatial contrast - the two regions respond differently from each other during this specific condition, with shared systemic noise partially cancelling in the subtraction. It says nothing about whether the condition produced meaningful activity at all (only a relative difference between two places), and its power depends entirely on ROI_A and ROI_B varying together across subjects - an assumption that isn't guaranteed.
|
||||
\nIf this test underperforms a plain ROI-vs-zero result, which can occur, the most likely explanation is that ROI_A and ROI_B's noise isn't well-correlated across your subjects. The math is variance(A - B) = variance(A) + variance(B) - 2·covariance(A,B): subtraction only helps when the shared/systemic component is large relative to independent noise in each region. If the two regions are picking up largely independent noise sources (motion artifact affecting one side more, different channel quality, etc.), subtracting adds variance rather than removing it, and can turn a detectable single-ROI effect into an underpowered paired one. Small sample size makes this worse, since the covariance itself is poorly estimated with few subjects.
|
||||
\n\n2. Joint Contrast, ROI-Aggregated (aggregate_channel_contrasts_to_roi + one-sample test)
|
||||
\nUses a contrast fit jointly within each subject's GLM (Condition A minus Condition B, estimated together), then aggregates that per-channel contrast to ROI level using inverse-variance weighting, and tests it against zero across subjects. A significant result means the two conditions produce reliably different responses at this ROI, with systemic noise largely cancelled at the model-fitting stage itself - the most statistically efficient of the three within-group methods, since the correlation between conditions is handled natively rather than inferred afterward. It does not tell you where the difference is localized on its own - for that, compare the sign/pattern across multiple ROIs: opposite signs across regions indicates a real, spatially-specific effect, while the same sign everywhere suggests diffuse/systemic noise rather than localized activity (as seen when comparing a real task-vs-task contrast against a task-vs-inert-marker contrast).
|
||||
\nIf this comes back non-significant despite expecting an effect, first check whether the two conditions are actually similar enough in their neural engagement of this ROI that a small or genuinely near-zero contrast is the correct answer - not every ROI should differentiate every pair of tasks, and a null result here can be the right result. Beyond that: FDR correction across every ROI in your regions file can suppress a real but modest contrast; the inverse-variance weighting can be destabilized if a few channels within the ROI have very noisy or near-zero t-statistics (their standard error estimate becomes huge or unstable); and - as always - small subject counts limit the achievable degrees of freedom regardless of how clean the underlying per-channel estimates are.
|
||||
DESCRIPTION = """0. Raw ROI Comparison (run_inter_group_second_level_analysis)
|
||||
\nCompares one ROI's raw response magnitude between two independent groups (e.g. control vs. target) for a given condition, using Welch's t-test. A significant result means the two populations differ in this ROI's response magnitude for this condition. It does not tell you whether that difference is a real, localized, task-specific effect or a generic between-population difference - different overall vascular reactivity, arousal, or skull/scalp optical properties can produce the exact same statistical signature, and two independently recruited groups (especially patients vs. healthy controls) are considerably more likely to differ this way than two subsets of one study population.
|
||||
\nIf you expected a group difference and didn't find one, the most common cause is within-group heterogeneity swallowing a real between-group difference - a "target" population (e.g. a clinical group) is often more variable than a tightly-screened control group, and that added within-group variance directly weakens a between-group t-test even if the group means truly differ. Small per-group sample sizes compound this. It's also possible the true difference between your groups isn't in raw magnitude at all, but in spatial specificity or task-differentiation - which is exactly why the laterality and contrast-comparison methods exist alongside this one; a null result here doesn't rule those out.
|
||||
\n\n1. Laterality Comparison (run_inter_group_laterality_analysis)
|
||||
\nComputes each subject's own contralateral-minus-ipsilateral laterality index first, then compares those indices between the two groups with Welch's t-test. A significant result means the degree of spatial specificity/lateralization differs between the two populations - a claim about lateralization itself, harder to explain away as a generic population confound since person-level differences in overall reactivity largely cancel before the group comparison happens. It says nothing about overall response magnitude between groups (a group could have identical laterality but very different raw amplitude), and it only uses subjects who have both the contra and ipsi ROI valid, so it can lose subjects the raw-ROI comparison would have kept.
|
||||
\nNon-significance here has two likely sources, and it's worth distinguishing them. First, the same covariance issue from the within-group paired test applies across a whole group: if contra/ipsi responses aren't well-correlated within subjects, the laterality index itself is noisier than either ROI alone, and that added noise now has to clear a between-group test on top of it - a double power cost at small N. Second, and more informative if true: the groups may genuinely have similar lateralization but differ in overall magnitude instead, in which case this test correctly returns null while method 4 (raw comparison) should be the one to look at.
|
||||
\n\n2. Contrast Comparison (run_inter_group_contrast_analysis)
|
||||
\nCompares a jointly-fit task contrast (e.g. Task A minus Task B, estimated together within each subject's GLM), aggregated to ROI level, between two independent groups. A significant result means one group differentiates between the two tasks more or less than the other does, at this specific ROI - with systemic noise cancelled at the model-fitting stage, the same benefit that makes the within-group version of this method the strongest of that trio. As with the within-group version, it does not by itself say where a difference is localized unless you compare sign/pattern across multiple ROIs - opposite-signed group differences across regions point to something spatially specific, same-signed differences everywhere point to a diffuse/non-specific group difference (e.g. one group simply has stronger contrast responses across the whole head).
|
||||
\nIf this comes back non-significant despite an expected group difference, check first whether the underlying single-subject contrast estimates are noisy for either group - small per-group N means the joint contrast's precision depends on the same limited subject count as everything else, and a noisy input propagates all the way through the ROI aggregation. It's also possible for a real, localized sub-regional effect to get washed out by ROI averaging itself: if only part of an ROI's channels actually show the group difference while others don't, the inverse-variance-weighted average can dilute it toward null - in that case, a finer-grained ROI definition (splitting the region further) may recover the effect that a coarser ROI averaged away. Finally, FDR correction across every ROI tested reduces power exactly as it does everywhere else in this framework - a real but modest effect can fail to survive correction even when the raw p-value would have looked convincing on its own.
|
||||
\n\n
|
||||
\nWhy channels needed to be aggregated into ROIs: Testing every channel independently means paying a steep multiple-comparisons tax - with dozens of channels, FDR/Bonferroni correction demands very large effect sizes to call anything significant, and at small subject counts (n=5) essentially nothing survives even when a real, consistent effect exists. Collapsing channels into a handful of anatomically meaningful ROIs cuts the number of independent tests from a minimum of ~40 down to 2-8, which lets a genuinely present effect actually clear correction. It also matches the scientific question better: you have a hypothesis about regions (contralateral motor cortex, prefrontal cortex), not about individual source-detector pairs, so testing at the ROI level is testing the thing you actually believe in, using inverse-variance weighting so noisier channels contribute less to the region's combined estimate rather than diluting it equally.
|
||||
\nWhy some analyses needed contrasts instead of raw values: A single condition's GLM beta is only ever measured relative to the model's implicit intercept, and that intercept absorbs whatever's happening for the rest of the recording - including systemic physiology (blood pressure, arousal, general vascular reactivity) that rises during almost any active task, not just the one you care about. Testing a raw "vs. zero" value can't tell a real, localized neural response apart from that shared full-head noise. A contrast - either a within-subject spatial subtraction (ROI A minus ROI B) or a jointly-fit task contrast (Condition A minus Condition B, estimated together in one GLM) cancels out whatever's common to both halves of the subtraction, leaving something closer to the actual differential signal.
|
||||
@@ -157,6 +144,7 @@ DESCRIPTION = """0. ROI vs. Zero (run_roi_second_level_analysis)
|
||||
|
||||
|
||||
class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
haemo_dict: dict[str | Path, BaseRaw],
|
||||
@@ -178,226 +166,191 @@ class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
self.roi_channel_map_dict = roi_channel_map_dict
|
||||
self.group_dict = group_dict
|
||||
|
||||
self.setup_inter_group_ui(["0 (ROI vs. Zero)", "1 (Paired ROI Contrast)", "2 (Joint Contrast, ROI-Aggregated)"], placeholder_text=DESCRIPTION)
|
||||
|
||||
self.setup_inter_group_ui(["0 (Raw ROI Comparison)", "1 (Laterality Comparison)", "2 (Contrast Comparison)",], placeholder_text=DESCRIPTION)
|
||||
|
||||
|
||||
def process_request(self):
|
||||
request = self.get_common_request_data(PARAMETERIZED_INDEXES, self.df_ind_dict, self.contrast_results_dict)
|
||||
if request is None:
|
||||
return
|
||||
|
||||
(selected_event, selected_file_paths, selected_indexes, raw_params) = request
|
||||
(selected_event, file_paths_a, file_paths_b, _, selected_indexes, raw_params) = request
|
||||
|
||||
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)
|
||||
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 = pd.DataFrame()
|
||||
|
||||
if haemo_obj is None:
|
||||
continue
|
||||
|
||||
if selected_event:
|
||||
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
|
||||
|
||||
|
||||
|
||||
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)
|
||||
|
||||
file_path = selected_file_paths[0]
|
||||
p_haemo = self.haemo_dict.get(file_path)
|
||||
|
||||
# Concatenate individual ROI stats (df_ind) for all chosen subjects
|
||||
df_group = DataFrame()
|
||||
if selected_file_paths:
|
||||
for file_path in selected_file_paths:
|
||||
df = self.df_ind_dict.get(file_path)
|
||||
if df is not None:
|
||||
df_group = pd.concat([df_group, df], ignore_index=True)
|
||||
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)
|
||||
|
||||
# Visualizations
|
||||
for idx in selected_indexes:
|
||||
if idx == 0:
|
||||
params = param_values.get(idx, {})
|
||||
p_threshold = params.get("p_threshold", 0.05)
|
||||
min_subjects = params.get("min_subjects", 5)
|
||||
min_subjects = params.get("min_subjects", 3)
|
||||
correction_method = params.get("correction_method", "fdr_bh")
|
||||
target_chroma = params.get("target_chroma", "hbo")
|
||||
graph_bounds = params.get("graph_bounds", 0.0)
|
||||
|
||||
if correction_method == "None":
|
||||
correction_method = None
|
||||
|
||||
if df_group.empty:
|
||||
print("No ROI data (df_ind) found for selected participants.")
|
||||
continue
|
||||
|
||||
# Filter down to the selected experimental event/condition
|
||||
if selected_event:
|
||||
if 'Condition' in df_group.columns:
|
||||
df_filtered = df_group[df_group['Condition'] == selected_event]
|
||||
else:
|
||||
print("Warning: 'Condition' column not found in ROI data.")
|
||||
df_filtered = df_group
|
||||
else:
|
||||
df_filtered = df_group
|
||||
|
||||
if df_filtered.empty:
|
||||
print(f"No ROI data matches the condition '{selected_event}'.")
|
||||
continue
|
||||
|
||||
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]
|
||||
else:
|
||||
all_cha_filtered = all_cha
|
||||
|
||||
|
||||
run_roi_second_level_analysis(
|
||||
df_roi_all=df_filtered,
|
||||
df_cha_all=all_cha_filtered,
|
||||
raw_haemo=p_haemo,
|
||||
p_threshold=p_threshold,
|
||||
min_subjects=min_subjects,
|
||||
correction_method=correction_method,
|
||||
target_chroma=target_chroma,
|
||||
graph_bounds=graph_bounds if graph_bounds > 0.0 else None,
|
||||
)
|
||||
|
||||
elif idx == 1:
|
||||
params = param_values.get(idx, {})
|
||||
p_threshold = params.get("p_threshold", 0.05)
|
||||
min_subjects = params.get("min_subjects", 5)
|
||||
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()
|
||||
|
||||
if not selected_event:
|
||||
print("Paired ROI contrast requires a specific event/condition "
|
||||
"to be selected - pick one from the Event dropdown first.")
|
||||
continue
|
||||
|
||||
if df_group.empty:
|
||||
print("No ROI data (df_ind) found for selected participants.")
|
||||
continue
|
||||
|
||||
if correction_method == "None":
|
||||
correction_method = None
|
||||
|
||||
if not roi_a or not roi_b:
|
||||
print("Both ROI A and ROI B must be specified.")
|
||||
continue
|
||||
|
||||
print(min_subjects)
|
||||
run_roi_paired_contrast_analysis(
|
||||
df_roi_all=df_group,
|
||||
roi_pairs=(roi_a, roi_b),
|
||||
condition=selected_event,
|
||||
target_chroma=target_chroma,
|
||||
min_subjects=min_subjects,
|
||||
p_threshold=p_threshold,
|
||||
correction_method=correction_method,
|
||||
roi_a_label=roi_a,
|
||||
roi_b_label=roi_b,
|
||||
)
|
||||
|
||||
elif idx == 2:
|
||||
params = param_values.get(idx, {})
|
||||
p_threshold = params.get("p_threshold", 0.05)
|
||||
min_subjects = params.get("min_subjects", 5)
|
||||
correction_method = params.get("correction_method", "fdr_bh")
|
||||
target_chroma = params.get("target_chroma", "hbo")
|
||||
contrast_name = params.get("contrast_name", "")
|
||||
weighted = params.get("weighted", True)
|
||||
graph_bounds = params.get("graph_bounds", 0.0)
|
||||
|
||||
if not selected_event:
|
||||
print("Joint contrast ROI analysis requires a specific contrast "
|
||||
"to be selected from the Event dropdown first.")
|
||||
continue
|
||||
|
||||
if not contrast_name:
|
||||
print("Contrast name must be specified.")
|
||||
continue
|
||||
|
||||
|
||||
all_contrasts: list[DataFrame] = []
|
||||
for fp in selected_file_paths:
|
||||
condition_dfs = self.contrast_results_dict.get(fp)
|
||||
if condition_dfs is None:
|
||||
print(f" [MISSING] '{fp}' not found in contrast_results.")
|
||||
continue
|
||||
if contrast_name in condition_dfs:
|
||||
df = condition_dfs[contrast_name].copy()
|
||||
df["ID"] = fp
|
||||
df["contrast_name"] = contrast_name
|
||||
all_contrasts.append(df)
|
||||
else:
|
||||
print(f" [MISSING CONTRAST] '{contrast_name}' not "
|
||||
f"available for {self.participant_map.get(fp, fp)}.")
|
||||
|
||||
if not all_contrasts:
|
||||
print(f"No contrast data found for '{contrast_name}' "
|
||||
f"across selected participants.")
|
||||
continue
|
||||
|
||||
df_contrasts = pd.concat(all_contrasts, ignore_index=True)
|
||||
threshold_topo = params.get("threshold_topo", False)
|
||||
|
||||
selected_roi_maps = {
|
||||
fp: self.roi_channel_map_dict[fp]
|
||||
for fp in selected_file_paths
|
||||
for fp in (file_paths_a + file_paths_b)
|
||||
if fp in self.roi_channel_map_dict
|
||||
}
|
||||
if not selected_roi_maps:
|
||||
print("No channel-to-ROI mapping available for selected participants.")
|
||||
continue
|
||||
|
||||
try:
|
||||
roi_theta = aggregate_channel_contrasts_to_roi(
|
||||
df_contrasts,
|
||||
roi_channel_maps=selected_roi_maps,
|
||||
weighted=weighted,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to aggregate contrasts to ROI: {e}")
|
||||
continue
|
||||
|
||||
if roi_theta.empty:
|
||||
print("No ROI-level contrast values could be computed "
|
||||
"(check regions.json channel names against this montage).")
|
||||
continue
|
||||
|
||||
# TODO: Come back to this
|
||||
# df_cha_all intentionally omitted (None): the topography
|
||||
# section of run_roi_second_level_analysis expects
|
||||
# single-condition Condition values in df_cha_all, which
|
||||
# doesn't semantically match a contrast name - skip it here
|
||||
# rather than pass mismatched data.
|
||||
run_roi_second_level_analysis(
|
||||
df_roi_all=roi_theta,
|
||||
df_cha_all=None,
|
||||
run_inter_group_second_level_analysis(
|
||||
df_roi_all=df_ind_combined, # Individual stats dataframe
|
||||
file_paths_a=file_paths_a,
|
||||
file_paths_b=file_paths_b,
|
||||
group_a_name=self.group_a_dropdown.currentText(),
|
||||
group_b_name=self.group_b_dropdown.currentText(),
|
||||
df_cha_all=cha_combined,
|
||||
raw_haemo=p_haemo,
|
||||
p_threshold=p_threshold,
|
||||
min_subjects=min_subjects,
|
||||
correction_method=correction_method,
|
||||
target_chroma=target_chroma,
|
||||
graph_bounds=graph_bounds if graph_bounds > 0.0 else None,
|
||||
selected_event=selected_event,
|
||||
roi_channel_maps=selected_roi_maps,
|
||||
threshold_topo=threshold_topo # Shows the raw difference map (Unthresholded)
|
||||
)
|
||||
elif idx == 1:
|
||||
if not selected_event:
|
||||
print("Laterality comparison requires a specific event/condition "
|
||||
"to be selected first.")
|
||||
continue
|
||||
|
||||
params = param_values.get(idx, {})
|
||||
p_threshold = params.get("p_threshold", 0.05)
|
||||
min_subjects = params.get("min_subjects", 3)
|
||||
correction_method = params.get("correction_method", "None")
|
||||
target_chroma = params.get("target_chroma", "hbo")
|
||||
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.")
|
||||
continue
|
||||
|
||||
if correction_method == "None":
|
||||
correction_method = None
|
||||
|
||||
# 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: list[str],
|
||||
dict_source: dict[str, DataFrame]
|
||||
) -> DataFrame:
|
||||
|
||||
valid_dfs = [
|
||||
dict_source[fp] for fp in file_paths
|
||||
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)
|
||||
|
||||
if df_roi_a.empty or df_roi_b.empty:
|
||||
print("No ROI data (df_ind) found for one or both groups.")
|
||||
continue
|
||||
|
||||
run_inter_group_laterality_analysis(
|
||||
df_roi_all_a=df_roi_a,
|
||||
df_roi_all_b=df_roi_b,
|
||||
roi_pairs=(roi_a, roi_b),
|
||||
condition=selected_event,
|
||||
group_a_name=self.group_a_dropdown.currentText(),
|
||||
group_b_name=self.group_b_dropdown.currentText(),
|
||||
target_chroma=target_chroma,
|
||||
min_subjects=min_subjects,
|
||||
p_threshold=p_threshold,
|
||||
correction_method=correction_method,
|
||||
roi_contra_label=roi_a,
|
||||
roi_ipsi_label=roi_b,
|
||||
)
|
||||
|
||||
elif idx == 2:
|
||||
params = param_values.get(idx, {})
|
||||
p_threshold = params.get("p_threshold", 0.05)
|
||||
min_subjects = params.get("min_subjects", 3)
|
||||
correction_method = params.get("correction_method", "fdr_bh")
|
||||
target_chroma = params.get("target_chroma", "hbo")
|
||||
contrast_name = params.get("contrast_name", "")
|
||||
|
||||
if not contrast_name:
|
||||
print("A contrast name must be specified.")
|
||||
continue
|
||||
|
||||
# Build each group's channel-level contrast dataframe
|
||||
# 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: 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:
|
||||
print(f" [MISSING] '{fp}' not found in contrast_results.")
|
||||
continue
|
||||
if name in condition_dfs:
|
||||
df = condition_dfs[name].copy()
|
||||
df["ID"] = fp
|
||||
df["contrast_name"] = name
|
||||
all_rows.append(df)
|
||||
else:
|
||||
print(f" [MISSING CONTRAST] '{name}' not available for '{fp}'.")
|
||||
return pd.concat(all_rows, ignore_index=True) if all_rows else pd.DataFrame()
|
||||
|
||||
df_contrasts_a = _build_group_contrast_df(file_paths_a, self.contrast_results_dict, contrast_name)
|
||||
df_contrasts_b = _build_group_contrast_df(file_paths_b, self.contrast_results_dict, contrast_name)
|
||||
|
||||
if df_contrasts_a.empty or df_contrasts_b.empty:
|
||||
print("No contrast data found for one or both groups.")
|
||||
continue
|
||||
|
||||
roi_maps_a = {
|
||||
fp: self.roi_channel_map_dict[fp]
|
||||
for fp in file_paths_a
|
||||
if fp in self.roi_channel_map_dict
|
||||
}
|
||||
roi_maps_b = {
|
||||
fp: self.roi_channel_map_dict[fp]
|
||||
for fp in file_paths_b
|
||||
if fp in self.roi_channel_map_dict
|
||||
}
|
||||
if not roi_maps_a or not roi_maps_b:
|
||||
print("No channel-to-ROI mapping available for one or both groups.")
|
||||
continue
|
||||
|
||||
run_inter_group_contrast_analysis(
|
||||
df_contrasts_a=df_contrasts_a,
|
||||
df_contrasts_b=df_contrasts_b,
|
||||
contrast_name=contrast_name,
|
||||
roi_channel_maps_a=roi_maps_a,
|
||||
roi_channel_maps_b=roi_maps_b,
|
||||
group_a_name=self.group_a_dropdown.currentText(),
|
||||
group_b_name=self.group_b_dropdown.currentText(),
|
||||
target_chroma=target_chroma,
|
||||
min_subjects=min_subjects,
|
||||
p_threshold=p_threshold,
|
||||
correction_method=correction_method,
|
||||
)
|
||||
|
||||
else:
|
||||
print(f"No method defined for index {idx}")
|
||||
print("no")
|
||||
Reference in New Issue
Block a user