more stats

This commit is contained in:
2026-07-15 22:45:07 -07:00
parent d300dfb7f7
commit 2ff7cda93a
3 changed files with 529 additions and 5 deletions
+188 -3
View File
@@ -9,13 +9,19 @@ License: GPL-3.0
# External library imports
import pandas as pd
from flares import run_roi_second_level_analysis
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 = {
0: [
{
"key": "info",
"label": "Tests whether one ROI's response during one condition reliably differs from zero across subjects.\nIf significant, you can claim: This region's signal during this condition is consistently non-zero across your sample - not just noise.\nIt does NOT say: Whether that 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.",
"default": "Okay.",
"type": str,
},
{
"key": "p_value",
"label": "Significance threshold P-value (e.g. 0.05)",
@@ -29,6 +35,52 @@ PARAMETERIZED_INDEXES = {
"type": float,
}
],
1: [
{
"key": "info",
"label": "For 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.\nIf significant, you can claim: The two regions respond differently from each other during this specific condition - a real spatial contrast, since shared systemic noise partially cancels in the subtraction.\nIt does NOT say: Anything about whether the condition itself produced meaningful activity at all (only a relative difference between two places); and its power depends on the two ROIs' noise being correlated across subjects, which isn't guaranteed.",
"default": "Okay.",
"type": str,
},
{
"key": "roi_a",
"label": "ROI A (e.g. contralateral region name from regions.json)",
"default": "",
"type": str,
},
{
"key": "roi_b",
"label": "ROI B (e.g. ipsilateral region name from regions.json)",
"default": "",
"type": str,
},
{
"key": "p_value",
"label": "Significance threshold P-value (e.g. 0.05)",
"default": "0.05",
"type": float,
},
],
2: [
{
"key": "info",
"label": "Uses 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 and tests it against zero across subjects.\nIf significant, you can claim: 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.\nIt does NOT say: Which region the difference is localized to, unless you compare the sign/pattern across multiple ROIs",
"default": "Okay.",
"type": str,
},
{
"key": "p_value",
"label": "Significance threshold P-value (e.g. 0.05)",
"default": "0.05",
"type": float,
},
{
"key": "graph_bounds",
"label": "Graph Upper/Lower Limit",
"default": "0.0",
"type": float,
},
],
}
@@ -44,7 +96,7 @@ class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
self.contrast_results = contrast_results
self.group = group
self.setup_inter_group_ui(["0 (Significance)",])
self.setup_inter_group_ui(["0 (Significance)", "1 (More significasd)", "2 (moreeee)"])
def process_request(self):
@@ -113,7 +165,18 @@ class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
else:
all_cha_filtered = all_cha
# Call your new custom group ROI method!
# ---------------------------------------------------------------------
# run_roi_second_level_analysis
#
# Tests: is this ROI's activation reliably different from zero, for one
# condition, across subjects? (One-sample t-test per ROI.)
#
# CAUTION: "vs zero" includes systemic/global physiology shared across
# the whole head (blood pressure, arousal, etc.), not just localized
# neural response — a significant result here doesn't by itself prove
# the effect is spatially specific to this ROI.
# ---------------------------------------------------------------------
run_roi_second_level_analysis(
df_roi_all=df_filtered,
df_cha_all=all_cha_filtered,
@@ -126,5 +189,127 @@ class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
roi_config=r"C:\Users\tyler\Desktop\research\flares\regions.json"
)
elif idx == 1:
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
params = param_values.get(idx, {})
roi_a = params.get("roi_a", "").strip()
roi_b = params.get("roi_b", "").strip()
p_val = params.get("p_value", 0.05)
if not roi_a or not roi_b:
print("Both ROI A and ROI B must be specified.")
continue
# ---------------------------------------------------------------------
# run_roi_paired_contrast_analysis
#
# Tests: within one condition, does ROI_A's activation differ from
# ROI_B's, per subject? (Paired one-sample t-test on the per-subject
# difference, e.g. Right_PFC minus Left_PFC for a laterality check.)
#
# Only gains power over testing ROI_A and ROI_B separately if the two
# ROIs' noise is correlated across subjects (shared systemic component
# cancels in the subtraction). If they vary independently, this test
# can be WEAKER than testing either ROI alone — check per-subject
# correlation between ROI_A and ROI_B if this test underperforms.
run_roi_paired_contrast_analysis(
df_roi_all=df_group, # unfiltered — function filters internally
roi_pairs=(roi_a, roi_b),
condition=selected_event,
target_chroma='hbo',
min_subjects=min(5, len(selected_file_paths)),
p_threshold=p_val,
correction_method=None, # single pre-specified contrast
roi_a_label=roi_a,
roi_b_label=roi_b,
)
elif idx == 2:
if not selected_event:
print("Joint contrast ROI analysis requires a specific contrast "
"to be selected from the Event dropdown first.")
continue
# Build the channel-level contrast dataframe for selected
# participants + selected contrast, same pattern used in
# GroupViewerWidget.show_brain_images.
contrast_name = "15.0_vs_2.0" # <-- change this to test other contrasts
print(f"[TEMP HARDCODE] Using contrast '{contrast_name}' "
f"instead of dropdown selection ('{selected_event}') for option 2.")
# Build the channel-level contrast dataframe for selected
# participants + selected contrast, same pattern used in
# GroupViewerWidget.show_brain_images.
all_contrasts = []
for fp in selected_file_paths:
condition_dfs = self.contrast_results.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
# contrast_results dict values don't carry a
# contrast_name column themselves — that's only
# stamped on during CSV export. Add it here since
# aggregate_channel_contrasts_to_roi requires it.
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)
params = param_values.get(idx, {})
p_val = params.get("p_value", 0.05)
graph_bounds = params.get("graph_bounds", 0.0)
try:
roi_theta = aggregate_channel_contrasts_to_roi(
df_contrasts,
roi_json_path=r"C:\Users\tyler\Desktop\research\flares\regions.json",
weighted=True,
)
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
# 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,
raw_haemo=p_haemo,
p_threshold=p_val,
min_subjects=min(5, len(selected_file_paths)),
correction_method='fdr_bh',
target_chroma='hbo',
graph_bounds=graph_bounds if graph_bounds > 0.0 else None,
)
else:
print(f"No method defined for index {idx}")
+2 -2
View File
@@ -38,8 +38,8 @@ class ViewerLauncherWidget(QWidget):
("Inter-Group Functional Connectivity Viewer [BETA]", InterGroupFunctionalConnectivityWidget, [haemo_dict, group_dict, config_dict], True),
("Inter-Group Stats Viewer", InterGroupStatsWidget, [haemo_dict, cha_dict, df_ind_dict, design_matrix_dict, contrast_results_dict, group_dict], True),
("Cross-Group Stats Viewer", CrossGroupStatsWidget, [haemo_dict, cha_dict, df_ind_dict, design_matrix_dict, contrast_results_dict, group_dict], True),
("Inter-Group Brain & Image Viewer", InterGroupBrainImageWidget, [haemo_dict, cha_dict, df_ind_dict, design_matrix_dict, contrast_results_dict, group_dict], True),
("Cross-Group Brain & Image Viewer", CrossGroupBrainImageWidget, [haemo_dict, df_ind_dict, design_matrix_dict, contrast_results_dict, group_dict], True),
("Inter-Group Brain and Image Viewer", InterGroupBrainImageWidget, [haemo_dict, cha_dict, df_ind_dict, design_matrix_dict, contrast_results_dict, group_dict], True),
("Cross-Group Brain and Image Viewer", CrossGroupBrainImageWidget, [haemo_dict, df_ind_dict, design_matrix_dict, contrast_results_dict, group_dict], True),
("Export To CSV Viewer", ExportToCSVWidget, [haemo_dict, cha_dict, df_ind_dict, design_matrix_dict, group_dict, contrast_results_dict], True)
]