more stats
This commit is contained in:
@@ -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}")
|
||||
@@ -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)
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user