fix to stats when no json file is defined
This commit is contained in:
+5
-1
@@ -1,8 +1,12 @@
|
||||
# Verison 1.5.3
|
||||
# Verison 1.6.0
|
||||
|
||||
- This is potentially a save-changing release due to adding more data into the save file. Please update your project files to ensure compatibility
|
||||
- It is still possible to load older saves by enabling 'Incompatible Save Bypass' from the Preferences menu, but your mileage may vary
|
||||
- Optimized calculations being performed when calculating the heart rate to speed up step 5 by up to ~35% on a per-file basis
|
||||
- Optimized calculations being performed when running the General Linear Model to speed up step 5 by ~35% on a per-file basis
|
||||
- Fixed an issue where participants could be skipped when processing multiple particants at one which could prevent overall processing from completing
|
||||
- Fixed the two Group Stats Viewer windows crashing the application once opened when JSON_LOCATION was not set
|
||||
- The Group Stats Viewer windows will now properly load the Right/Left or Front/Back fallback ROIs if JSON_LOCATION is not set
|
||||
|
||||
|
||||
# Version 1.5.2
|
||||
|
||||
@@ -2650,7 +2650,6 @@ def run_roi_second_level_analysis(
|
||||
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:
|
||||
|
||||
@@ -2805,35 +2804,13 @@ def run_roi_second_level_analysis(
|
||||
con_model_df = statsmodels_to_results(con_model, order=raw_picked.ch_names)
|
||||
|
||||
# --- DYNAMIC ROI PARSING ---
|
||||
roi_mapping = {}
|
||||
if roi_config is not None:
|
||||
raw_json = None
|
||||
if isinstance(roi_config, str) and os.path.exists(roi_config):
|
||||
with open(roi_config, 'r') as f:
|
||||
raw_json = json.load(f)
|
||||
elif isinstance(roi_config, dict):
|
||||
raw_json = roi_config
|
||||
|
||||
if raw_json:
|
||||
if "regions_of_interest" in raw_json:
|
||||
for roi_item in raw_json["regions_of_interest"]:
|
||||
roi_name = roi_item.get("name")
|
||||
channels = roi_item.get("channels", [])
|
||||
if roi_name and channels:
|
||||
roi_mapping[roi_name] = channels
|
||||
else:
|
||||
roi_mapping = raw_json
|
||||
|
||||
if roi_mapping:
|
||||
ch_to_roi = {}
|
||||
for roi_name, channels in roi_mapping.items():
|
||||
for ch in channels:
|
||||
ch_to_roi[ch] = roi_name
|
||||
ch_to_roi[ch.split()[0]] = roi_name
|
||||
|
||||
con_summary['ROI'] = con_summary[ch_col].apply(
|
||||
lambda x: ch_to_roi.get(x, ch_to_roi.get(x.split()[0], None) if isinstance(x, str) else None)
|
||||
)
|
||||
if 'ROI' not in con_summary.columns or con_summary['ROI'].dropna().empty:
|
||||
if df_roi_all is not None and 'ROI' in df_roi_all.columns and ch_col in df_roi_all.columns:
|
||||
# Create channel -> ROI mapping from df_roi_all
|
||||
ch_to_roi = df_roi_all.dropna(subset=['ROI', ch_col]).set_index(ch_col)['ROI'].to_dict()
|
||||
con_summary['ROI'] = con_summary[ch_col].apply(
|
||||
lambda x: ch_to_roi.get(x, ch_to_roi.get(x.split()[0], None) if isinstance(x, str) else None)
|
||||
)
|
||||
|
||||
unique_rois = []
|
||||
if 'ROI' in con_summary.columns:
|
||||
@@ -2925,7 +2902,7 @@ def run_cross_group_second_level_analysis(
|
||||
target_chroma: str = "hbo",
|
||||
selected_event: str | None = None,
|
||||
graph_bounds: tuple[float, float] | list[float] | None = None,
|
||||
roi_config: Path | str | None = None,
|
||||
roi_channel_maps: dict[str, dict[str, str]] | None = None,
|
||||
threshold_topo: bool = False,
|
||||
) -> DataFrame:
|
||||
|
||||
@@ -3129,21 +3106,13 @@ def run_cross_group_second_level_analysis(
|
||||
con_model_df = pd.DataFrame(contrast_data)
|
||||
|
||||
# --- DYNAMIC ROI PARSING ---
|
||||
roi_mapping = {}
|
||||
if roi_config is not None and os.path.exists(roi_config):
|
||||
with open(roi_config, 'r') as f:
|
||||
raw_json = json.load(f)
|
||||
if "regions_of_interest" in raw_json:
|
||||
for roi_item in raw_json["regions_of_interest"]:
|
||||
roi_mapping[roi_item.get("name")] = roi_item.get("channels", [])
|
||||
if roi_channel_maps:
|
||||
def _lookup_roi(row):
|
||||
m = roi_channel_maps.get(row['clean_ID'], {})
|
||||
ch = row[ch_col]
|
||||
return m.get(ch, m.get(ch.split()[0]) if isinstance(ch, str) else None)
|
||||
|
||||
if roi_mapping:
|
||||
ch_to_roi = {}
|
||||
for roi_name, channels in roi_mapping.items():
|
||||
for ch in channels:
|
||||
ch_to_roi[ch] = roi_name
|
||||
ch_to_roi[ch.split()[0]] = roi_name
|
||||
con_summary['ROI'] = con_summary[ch_col].apply(lambda x: ch_to_roi.get(x, ch_to_roi.get(x.split()[0], None) if isinstance(x, str) else None))
|
||||
con_summary['ROI'] = con_summary.apply(_lookup_roi, axis=1)
|
||||
|
||||
unique_rois = [r for r in con_summary['ROI'].dropna().unique() if r != ""] if 'ROI' in con_summary.columns else ['All_Channels']
|
||||
|
||||
@@ -3454,7 +3423,8 @@ def run_cross_group_contrast_analysis(
|
||||
df_contrasts_a: DataFrame,
|
||||
df_contrasts_b: DataFrame,
|
||||
contrast_name: str,
|
||||
roi_json_path: str | Path | None,
|
||||
roi_channel_maps_a: dict[str, dict[str, str]],
|
||||
roi_channel_maps_b: dict[str, dict[str, str]],
|
||||
group_a_name: str = "Group A",
|
||||
group_b_name: str = "Group B",
|
||||
target_chroma: str = "hbo",
|
||||
@@ -3547,8 +3517,8 @@ def run_cross_group_contrast_analysis(
|
||||
print(f"[ERROR] Contrast '{contrast_name}' not found anywhere in {group_b_name}'s data.")
|
||||
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)
|
||||
roi_a = aggregate_channel_contrasts_to_roi(df_a_filt, roi_channel_maps_a, weighted=weighted)
|
||||
roi_b = aggregate_channel_contrasts_to_roi(df_b_filt, roi_channel_maps_b, weighted=weighted)
|
||||
|
||||
roi_a = roi_a[roi_a['Chroma'] == target_chroma]
|
||||
roi_b = roi_b[roi_b['Chroma'] == target_chroma]
|
||||
@@ -3897,7 +3867,7 @@ def run_roi_paired_contrast_analysis(
|
||||
|
||||
def aggregate_channel_contrasts_to_roi(
|
||||
df_contrasts: DataFrame,
|
||||
roi_json_path: str | Path | None,
|
||||
roi_channel_maps: dict[str, dict[str, str]],
|
||||
weighted: bool = True
|
||||
) -> DataFrame:
|
||||
"""
|
||||
@@ -3924,11 +3894,14 @@ def aggregate_channel_contrasts_to_roi(
|
||||
['ch_name', 'effect', 'stat', 'Chroma', 'contrast_name', 'ID']
|
||||
`stat` must be the t-statistic (ContrastType == 't'), since standard
|
||||
error is recovered as effect / stat.
|
||||
roi_json_path : str
|
||||
Path to the same regions.json used elsewhere in the pipeline, with
|
||||
the structure: {"regions_of_interest": [{"name": ..., "channels": [...]}]}
|
||||
`channels` entries should be bare source-detector names (e.g. "S1_D1"),
|
||||
matching the convention already used for the GLM-level ROI loading.
|
||||
roi_channel_maps : dict[str, dict[str, str]]
|
||||
Per-subject channel-to-ROI mapping, keyed by subject ID (the same
|
||||
ID values used in df_contrasts['ID']), e.g.
|
||||
{"sub-01": {"S1_D1 hbo": "Left", "S1_D1 hbr": "Left", ...}, ...}.
|
||||
This is the actual mapping generate_roi_results used for that
|
||||
subject (whichever tier produced it — JSON, geometric split, or
|
||||
per-channel fallback) — not re-derived here, so ROI assignments
|
||||
stay consistent with df_ind_dict for the same subject.
|
||||
weighted : bool, default True
|
||||
If True, combine channels within an ROI using inverse-variance
|
||||
weighting (weight = 1 / se^2), matching MNE-NIRS's own default
|
||||
@@ -3948,36 +3921,19 @@ def aggregate_channel_contrasts_to_roi(
|
||||
if not all(col in df_contrasts.columns for col in required_cols):
|
||||
raise ValueError(f"Input contrast DataFrame must include: {required_cols}")
|
||||
|
||||
# --- Load ROI definitions and build a channel-base -> ROI lookup ---
|
||||
# Channel base names (e.g. "S1_D1") map to both hbo/hbr rows via the
|
||||
# ch_name column ("S1_D1 hbo" / "S1_D1 hbr"), so we key on the base name.
|
||||
with open(roi_json_path, 'r') as f:
|
||||
roi_data = json.load(f)
|
||||
|
||||
ch_base_to_roi = {}
|
||||
for region in roi_data.get("regions_of_interest", []):
|
||||
roi_name = region["name"]
|
||||
for ch_base in region["channels"]:
|
||||
if ch_base in ch_base_to_roi:
|
||||
logger.warning(
|
||||
f"Channel '{ch_base}' assigned to multiple ROIs "
|
||||
f"('{ch_base_to_roi[ch_base]}' and '{roi_name}') — "
|
||||
f"using '{roi_name}' (last one wins)."
|
||||
)
|
||||
ch_base_to_roi[ch_base] = roi_name
|
||||
|
||||
df = df_contrasts.copy()
|
||||
df['ch_base'] = df['ch_name'].str.split().str[0] # "S1_D1 hbo" -> "S1_D1"
|
||||
df['ROI'] = df['ch_base'].map(ch_base_to_roi)
|
||||
df['ch_base'] = df['ch_name'].str.split().str[0]
|
||||
|
||||
n_unassigned = df['ROI'].isna().sum()
|
||||
if n_unassigned:
|
||||
logger.warning(
|
||||
f"{n_unassigned} channel-rows did not match any ROI in "
|
||||
f"'{roi_json_path}' and will be excluded."
|
||||
)
|
||||
def lookup(row):
|
||||
m = roi_channel_maps.get(row['ID'], {})
|
||||
return m.get(row['ch_name'], m.get(row['ch_base']))
|
||||
|
||||
df['ROI'] = df.apply(lookup, axis=1)
|
||||
df = df.dropna(subset=['ROI'])
|
||||
|
||||
if df.empty:
|
||||
raise ValueError("No channel contrasts matched any subject's ROI mapping.")
|
||||
|
||||
# Recover standard error from the t-statistic: t = effect / se -> se = effect / t
|
||||
with np.errstate(divide='ignore', invalid='ignore'):
|
||||
df['se'] = df['effect'] / df['stat']
|
||||
@@ -5030,6 +4986,12 @@ def generate_roi_results(raw_haemo, df_design_matrix, glm_est, file_path, json_l
|
||||
subject_id = sub['ID'].iloc[0] if 'ID' in sub.columns else ''
|
||||
n_conditions = sub['Condition'].nunique()
|
||||
|
||||
roi_channel_map = {
|
||||
raw_haemo.ch_names[idx]: roi_name
|
||||
for roi_name, indices in rois_formatted.items()
|
||||
for idx in indices
|
||||
}
|
||||
|
||||
sns.set_theme(style="whitegrid")
|
||||
fig, ax = plt.subplots(figsize=(max(6, 1.5 * sub['ROI'].nunique()), 5))
|
||||
|
||||
@@ -5048,7 +5010,7 @@ def generate_roi_results(raw_haemo, df_design_matrix, glm_est, file_path, json_l
|
||||
plt.tight_layout()
|
||||
plt.close(fig)
|
||||
|
||||
return df_roi, fig
|
||||
return df_roi, roi_channel_map, fig
|
||||
|
||||
|
||||
|
||||
@@ -5495,7 +5457,7 @@ def process_participant(file_path, file_start, progress_callback=None):
|
||||
step_start = lap(step_start, timings, "Step 25")
|
||||
|
||||
# Step 26: Generate Region of Interest Results
|
||||
df_roi, fig_roi = generate_roi_results(raw_haemo, df_design_matrix, glm_est, file_path, json_location=JSON_LOCATION)
|
||||
df_roi, roi_channel_map, fig_roi = generate_roi_results(raw_haemo, df_design_matrix, glm_est, file_path, json_location=JSON_LOCATION)
|
||||
_enqueue("Region of Interest", fig_roi, png_queue)
|
||||
if progress_callback: progress_callback(26)
|
||||
logger.info("26")
|
||||
@@ -5523,7 +5485,7 @@ def process_participant(file_path, file_start, progress_callback=None):
|
||||
logger.info(f" {name:<25} {elapsed:7.3f}s")
|
||||
|
||||
logger.info(f"Total processing time: {sum(timings.values()):.3f}s")
|
||||
return raw_haemo, epochs, df_cha, df_roi, df_design_matrix, config_dict, fig_bytes_dict, contrast_results_dict, True
|
||||
return raw_haemo, epochs, df_cha, df_roi, df_design_matrix, config_dict, fig_bytes_dict, contrast_results_dict, roi_channel_map, True
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -329,6 +329,7 @@ DATA_SCHEMA = [
|
||||
{"key": "config_dict", "help": "Dict[file_path, dict]: Processing configuration parameters"},
|
||||
{"key": "fig_bytes_dict", "help": "Dict[file_path, dict]: Serialized figure data"},
|
||||
{"key": "contrast_results_dict", "help": "Dict[file_path, dict]: Calculated contrast statistical results"},
|
||||
{"key": "roi_channel_map_dict", "help": "Dict[file_path, dict]: Calculated contrast statistical results"},
|
||||
{"key": "valid_dict", "help": "Dict[file_path, bool]: Boolean validity status per file"}
|
||||
]
|
||||
|
||||
@@ -677,7 +678,6 @@ class MainApplication(QMainWindow):
|
||||
self.analysis_clearing_bypass = False
|
||||
self.folding_bypass = False
|
||||
self.advanced_parameters = False
|
||||
self.json_location = ""
|
||||
|
||||
|
||||
# Initialization to ensure that saving can occur
|
||||
@@ -1268,8 +1268,8 @@ class MainApplication(QMainWindow):
|
||||
data_map["config_dict"],
|
||||
data_map["fig_bytes_dict"],
|
||||
data_map["contrast_results_dict"],
|
||||
data_map["roi_channel_map_dict"],
|
||||
self.folding_bypass,
|
||||
self.json_location
|
||||
]
|
||||
|
||||
self.launcher_window = ViewerLauncherWidget(*args)
|
||||
@@ -2349,8 +2349,6 @@ class MainApplication(QMainWindow):
|
||||
if self.folding_bypass:
|
||||
all_params['FOLDING_BYP'] = True
|
||||
|
||||
self.json_location = all_params['JSON_LOCATION']
|
||||
|
||||
collected_data = {
|
||||
"SNIRF_FILES": snirf_files,
|
||||
"PARAMS": all_params, # add this line
|
||||
|
||||
@@ -152,8 +152,8 @@ class CrossGroupStatsWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
df_ind_dict: dict[str, DataFrame],
|
||||
design_matrix_dict: dict[str, DataFrame],
|
||||
contrast_results_dict: dict[str, dict[str, Any]],
|
||||
roi_channel_map_dict: dict[str, dict[str, str]],
|
||||
group_dict: dict[str, str],
|
||||
json_location: str | Path
|
||||
) -> None:
|
||||
|
||||
super().__init__("CrossGroupStats")
|
||||
@@ -163,14 +163,14 @@ 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.json_location = json_location
|
||||
self.roi_channel_map_dict = roi_channel_map_dict
|
||||
self.group_dict = group_dict
|
||||
|
||||
self.setup_cross_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.json_location, self.contrast_results_dict)
|
||||
request = self.get_common_request_data(PARAMETERIZED_INDEXES, self.df_ind_dict, self.contrast_results_dict)
|
||||
if request is None:
|
||||
return
|
||||
|
||||
@@ -200,6 +200,12 @@ class CrossGroupStatsWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
target_chroma = params.get("target_chroma", "hbo")
|
||||
threshold_topo = params.get("threshold_topo", False)
|
||||
|
||||
selected_roi_maps = {
|
||||
fp: self.roi_channel_map_dict[fp]
|
||||
for fp in (file_paths_a + file_paths_b)
|
||||
if fp in self.roi_channel_map_dict
|
||||
}
|
||||
|
||||
run_cross_group_second_level_analysis(
|
||||
df_roi_all=df_ind_combined, # Individual stats dataframe
|
||||
file_paths_a=file_paths_a,
|
||||
@@ -213,7 +219,7 @@ class CrossGroupStatsWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
correction_method=correction_method,
|
||||
target_chroma=target_chroma,
|
||||
selected_event=selected_event,
|
||||
roi_config=self.json_location,
|
||||
roi_channel_maps=selected_roi_maps,
|
||||
threshold_topo=threshold_topo # Shows the raw difference map (Unthresholded)
|
||||
)
|
||||
elif idx == 1:
|
||||
@@ -318,11 +324,26 @@ class CrossGroupStatsWidget(CrossGroupUIMixin, FlaresBaseWidget):
|
||||
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_cross_group_contrast_analysis(
|
||||
df_contrasts_a=df_contrasts_a,
|
||||
df_contrasts_b=df_contrasts_b,
|
||||
contrast_name=contrast_name,
|
||||
roi_json_path=self.json_location,
|
||||
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,
|
||||
|
||||
@@ -165,8 +165,8 @@ class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
df_ind_dict: dict[str, DataFrame],
|
||||
design_matrix_dict: dict[str, DataFrame],
|
||||
contrast_results_dict: dict[str, dict[str, Any]],
|
||||
roi_channel_map_dict: dict[str, dict[str, str]],
|
||||
group_dict: dict[str, str],
|
||||
json_location: str | Path
|
||||
) -> None:
|
||||
|
||||
super().__init__("InterGroupStats")
|
||||
@@ -176,14 +176,14 @@ class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
self.df_ind_dict = df_ind_dict
|
||||
self.design_matrix_dict = design_matrix_dict
|
||||
self.contrast_results_dict = contrast_results_dict
|
||||
self.roi_channel_map_dict = roi_channel_map_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_dict)
|
||||
request = self.get_common_request_data(PARAMETERIZED_INDEXES, self.df_ind_dict, self.contrast_results_dict)
|
||||
if request is None:
|
||||
return
|
||||
|
||||
@@ -276,7 +276,6 @@ class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
correction_method=correction_method,
|
||||
target_chroma=target_chroma,
|
||||
graph_bounds=graph_bounds if graph_bounds > 0.0 else None,
|
||||
roi_config=self.json_location
|
||||
)
|
||||
|
||||
elif idx == 1:
|
||||
@@ -359,10 +358,19 @@ class InterGroupStatsWidget(InterGroupUIMixin, FlaresBaseWidget):
|
||||
|
||||
df_contrasts = pd.concat(all_contrasts, ignore_index=True)
|
||||
|
||||
selected_roi_maps = {
|
||||
fp: self.roi_channel_map_dict[fp]
|
||||
for fp in selected_file_paths
|
||||
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_json_path=self.json_location,
|
||||
roi_channel_maps=selected_roi_maps,
|
||||
weighted=weighted,
|
||||
)
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import json
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Sequence, Any
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListView, QMessageBox, QPushButton, QScrollArea, QVBoxLayout, QWidget, QFrame, QSpinBox
|
||||
from PySide6.QtGui import QStandardItemModel, QStandardItem, QPixmap, QIntValidator, QDoubleValidator
|
||||
from PySide6.QtCore import QEvent, QSize, Qt
|
||||
@@ -1380,7 +1382,7 @@ class CrossGroupUIMixin:
|
||||
def get_common_request_data(
|
||||
self,
|
||||
parameterized_indexes: dict[int, list[dict[str, Any]]],
|
||||
json_location: str | Path | None = None,
|
||||
df_ind_dict: dict[str, DataFrame] | None = None,
|
||||
contrast_dfs: dict[str, dict[str, Any]] | None = None,
|
||||
) -> tuple[str | None, list[str], list[str], list[str], list[int], dict[str, Any]] | None:
|
||||
|
||||
@@ -1423,19 +1425,15 @@ class CrossGroupUIMixin:
|
||||
|
||||
dynamic_rois = []
|
||||
|
||||
# 1. Check for the JSON file and parse ROI names
|
||||
if os.path.exists(json_location):
|
||||
try:
|
||||
with open(json_location, 'r', encoding='utf-8') as f:
|
||||
regions_data = json.load(f)
|
||||
if df_ind_dict:
|
||||
roi_set = set()
|
||||
for fp in all_selected_paths:
|
||||
df_roi = df_ind_dict.get(fp)
|
||||
if isinstance(df_roi, pd.DataFrame) and "ROI" in df_roi.columns:
|
||||
roi_set.update(df_roi["ROI"].dropna().unique())
|
||||
|
||||
# Extract "name" from each region under "regions_of_interest"
|
||||
regions_list = regions_data.get("regions_of_interest", [])
|
||||
dynamic_rois = [region["name"] for region in regions_list if "name" in region]
|
||||
|
||||
except Exception as e:
|
||||
# Safe log if JSON is corrupted or unreadable
|
||||
print(f"Error reading ROI configurations from {json_location}: {e}")
|
||||
if roi_set:
|
||||
dynamic_rois = sorted(list(roi_set))
|
||||
|
||||
# Fallback to prevent UI crashes if JSON file doesn't exist or is empty
|
||||
if not dynamic_rois:
|
||||
@@ -1580,7 +1578,7 @@ class InterGroupUIMixin:
|
||||
self.layout.addLayout(self.top_bar)
|
||||
|
||||
self.group_to_paths = {}
|
||||
for file_path, group_name in self.group.items():
|
||||
for file_path, group_name in self.group_dict.items():
|
||||
self.group_to_paths.setdefault(group_name, []).append(file_path)
|
||||
|
||||
self.group_names = sorted(self.group_to_paths.keys())
|
||||
@@ -1632,7 +1630,7 @@ class InterGroupUIMixin:
|
||||
def get_common_request_data(
|
||||
self,
|
||||
parameterized_indexes: dict[int, list[dict[str, Any]]],
|
||||
json_location: str | Path | None = None,
|
||||
df_ind_dict: dict[str, DataFrame] | None = None,
|
||||
contrast_dfs: dict[str, dict[str, Any]] | None = None,
|
||||
) -> tuple[str | None, list[str], list[int], dict[str, Any]] | None:
|
||||
|
||||
@@ -1679,19 +1677,15 @@ class InterGroupUIMixin:
|
||||
|
||||
dynamic_rois = []
|
||||
|
||||
# 1. Check for the JSON file and parse ROI names
|
||||
if json_location is not None and os.path.exists(json_location):
|
||||
try:
|
||||
with open(json_location, 'r', encoding='utf-8') as f:
|
||||
regions_data = json.load(f)
|
||||
if df_ind_dict:
|
||||
roi_set = set()
|
||||
for fp in selected_file_paths:
|
||||
df_roi = df_ind_dict.get(fp)
|
||||
if isinstance(df_roi, pd.DataFrame) and "ROI" in df_roi.columns:
|
||||
roi_set.update(df_roi["ROI"].dropna().unique())
|
||||
|
||||
# Extract "name" from each region under "regions_of_interest"
|
||||
regions_list = regions_data.get("regions_of_interest", [])
|
||||
dynamic_rois = [region["name"] for region in regions_list if "name" in region]
|
||||
|
||||
except Exception as e:
|
||||
# Safe log if JSON is corrupted or unreadable
|
||||
print(f"Error reading ROI configurations from {json_location}: {e}")
|
||||
if roi_set:
|
||||
dynamic_rois = sorted(list(roi_set))
|
||||
|
||||
# Fallback to prevent UI crashes if JSON file doesn't exist or is empty
|
||||
if not dynamic_rois:
|
||||
|
||||
@@ -24,7 +24,7 @@ from src.shared.shareddata import APP_NAME
|
||||
|
||||
|
||||
class ViewerLauncherWidget(QWidget):
|
||||
def __init__(self, haemo_dict, epochs_dict, cha_dict, df_ind_dict, design_matrix_dict, config_dict, fig_bytes_dict, contrast_results_dict, folding_bypass, json_location):
|
||||
def __init__(self, haemo_dict, epochs_dict, cha_dict, df_ind_dict, design_matrix_dict, config_dict, fig_bytes_dict, contrast_results_dict, roi_channel_map_dict, folding_bypass):
|
||||
super().__init__()
|
||||
self.setWindowTitle(f"Viewer Launcher - {APP_NAME.upper()}")
|
||||
|
||||
@@ -36,8 +36,8 @@ class ViewerLauncherWidget(QWidget):
|
||||
("Participant Fold Channels Viewer", ParticipantFoldChannelsWidget, [haemo_dict, cha_dict], False),
|
||||
("Participant Functional Connectivity Viewer [BETA]", ParticipantFunctionalConnectivityWidget, [haemo_dict, epochs_dict], True),
|
||||
("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, json_location], True),
|
||||
("Cross-Group Stats Viewer", CrossGroupStatsWidget, [haemo_dict, cha_dict, df_ind_dict, design_matrix_dict, contrast_results_dict, group_dict, json_location], True),
|
||||
("Inter-Group Stats Viewer", InterGroupStatsWidget, [haemo_dict, cha_dict, df_ind_dict, design_matrix_dict, contrast_results_dict, roi_channel_map_dict, group_dict], True),
|
||||
("Cross-Group Stats Viewer", CrossGroupStatsWidget, [haemo_dict, cha_dict, df_ind_dict, design_matrix_dict, contrast_results_dict, roi_channel_map_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, contrast_results_dict, group_dict, config_dict], True)
|
||||
|
||||
Reference in New Issue
Block a user