roi from user provided file

This commit is contained in:
2026-09-09 16:26:14 -07:00
parent 5d46d3b3e1
commit cd0b55df34
3 changed files with 84 additions and 30 deletions
+2
View File
@@ -22,6 +22,7 @@
- Added a new parameter to the PSP section on the right side of the screen: PSP_USE_HEART_RATE_BAND. This functions similarly to the existing SCI_USE_HEART_RATE_BAND
- Added description text to the Inter-Group and Intra-Group Brain and Image Viewers, as well as the Functional Connectivity windows to explain what output can be expected
- Added a new Preference option of Theme. Allows from selecting Auto (System default), Light, or Dark. Fixes [Issue 7](https://git.research.dezeeuw.ca/tyler/flares/issues/7)
- Optode updater can now update optode locations in multiple snirf files at once
- Removed image index 1 (Significance) from the Intra-Group Brain and Image Viewer as it is now provided more in depth with the Stats viewers
- Modified the timeout when waiting for the application to close while performing updates down to a reasonable number
- Modified the help messages for parameters in the SCI and PSP areas to better reflect how the parameters are used
@@ -42,6 +43,7 @@
- Fixed an issue where certain parameters would not enable or disable depending on other parameters when they should've
- Fixed an issue where not all widgets would close when attempting to close the application causing the application to crash
- Fixed an issue where events were not created correctly after the data had been resampled by the design matrix
- Fixed an issue where progress bar colors would not reset if the data was reprocessed
# Version 1.6.0
+3 -1
View File
@@ -12,6 +12,7 @@
- Added a new parameter to the PSP section on the right side of the screen: PSP_USE_HEART_RATE_BAND. This functions similarly to the existing SCI_USE_HEART_RATE_BAND
- Added description text to the Inter-Group and Intra-Group Brain and Image Viewers, as well as the Functional Connectivity windows to explain what output can be expected
- Added a new Preference option of Theme. Allows from selecting Auto (System default), Light, or Dark. Fixes [Issue 7](https://git.research.dezeeuw.ca/tyler/flares/issues/7)
- Optode updater can now update optode locations in multiple snirf files at once
- Removed image index 1 (Significance) from the Intra-Group Brain and Image Viewer as it is now provided more in depth with the Stats viewers
- Modified the timeout when waiting for the application to close while performing updates down to a reasonable number
- Modified the help messages for parameters in the SCI and PSP areas to better reflect how the parameters are used
@@ -31,4 +32,5 @@
- Fixed an issue where file associations would refuse to associate on macOS once they have attempted to be associated
- Fixed an issue where certain parameters would not enable or disable depending on other parameters when they should've
- Fixed an issue where not all widgets would close when attempting to close the application causing the application to crash
- Fixed an issue where events were not created correctly after the data had been resampled by the design matrix
- Fixed an issue where events were not created correctly after the data had been resampled by the design matrix
- Fixed an issue where progress bar colors would not reset if the data was reprocessed
+79 -29
View File
@@ -162,8 +162,6 @@ QC_METRIC_LABELS = {
"total_processing_seconds": "Processing Time (s)",
}
ROI_MAP = {} # TODO: Should be grabbed from the json file
DOWNSAMPLE: bool
DOWNSAMPLE_FREQUENCY: int
@@ -6059,21 +6057,22 @@ def process_participant(file_path, file_start, progress_callback=None):
# Step 27.5: Extract FIR Waveform Features & Enqueue Metric Plots
fir_feature_dict = {'features': np.array([]), 'feature_names': [], 'feature_channels': []}
# if HRF_MODEL.lower() == "fir":
# try:
# fir_feature_dict = extract_fir_features_real_data(
# raw=raw_haemo,
# target_condition=None, # e.g., 'reach'
# fir_delays=FIR_DELAYS, # e.g., np.arange(0, 15)
# selected_metrics=tuple(METRIC_REGISTRY.keys()), # e.g., ('Peak_Amp', 'TTP', 'AUC')
# roi_map=ROI_MAP,
# glm_est=glm_est,
# df_design_matrix=df_design_matrix,
# png_queue=png_queue
# )
# logger.info("Step 27.5: FIR features successfully extracted and metric images enqueued.")
# except Exception as e:
# logger.warning(f"Step 27.5 Failed to extract FIR features: {e}")
if HRF_MODEL.lower() == "fir":
try:
fir_feature_dict = extract_fir_features_real_data(
raw=raw_haemo,
target_condition=None, # e.g., 'reach'
fir_delays=FIR_DELAYS, # e.g., np.arange(0, 15)
selected_metrics=tuple(METRIC_REGISTRY.keys()), # e.g., ('Peak_Amp', 'TTP', 'AUC')
roi_map=JSON_LOCATION,
chromophores=('hbo', 'hbr', 'hbt'),
glm_est=glm_est,
df_design_matrix=df_design_matrix,
png_queue=png_queue
)
logger.info("Step 27.5: FIR features successfully extracted and metric images enqueued.")
except Exception as e:
logger.warning(f"Step 27.5 Failed to extract FIR features: {e}")
# Step 28: Finishing Up
@@ -7197,8 +7196,8 @@ def _compute_roi_fir_curves(
raw=None,
target_condition='reach',
fir_delays=np.arange(0, 15),
roi_map=ROI_MAP,
chromophores=('hbr',),
roi_map={},
chromophores=('hbo', 'hbr', 'hbt'),
glm_est=None,
df_design_matrix=None
):
@@ -7207,6 +7206,7 @@ def _compute_roi_fir_curves(
it reuses pre-calculated GLM results directly to avoid duplicate processing.
"""
roi_curves = {}
active_roi_map = normalize_roi_map(roi_map)
# --- SHORT-CIRCUIT: Reuse pre-calculated GLM estimation if available ---
if glm_est is not None and df_design_matrix is not None:
@@ -7225,25 +7225,36 @@ def _compute_roi_fir_curves(
fir_df = glm_df[glm_df[cond_col].astype(str).str.lower().str.contains(target_condition.lower())].copy()
print(f"Matched rows for '{target_condition}': {len(fir_df)}")
active_roi_map = normalize_roi_map(roi_map)
if fir_df.empty:
logger.warning(f"Condition '{target_condition}' not found in precalculated GLM estimates.")
return roi_curves
for chromo in chromophores:
chromo_df = fir_df[fir_df[ch_col].str.lower().str.contains(chromo.lower())] if ch_col in fir_df.columns else fir_df
# 1. Extract base measured chromophores (e.g., hbo, hbr) directly from GLM data
base_chromos = [c.lower() for c in chromophores if c.lower() != 'hbt']
for chromo in base_chromos:
chromo_df = fir_df[fir_df[ch_col].str.lower().str.contains(chromo)] if ch_col in fir_df.columns else fir_df
ch_curves = {}
for ch_name, ch_group in chromo_df.groupby(ch_col):
pair = ch_name.split(' ')[0]
ch_curves[pair] = ch_group['theta'].values if 'theta' in ch_group.columns else ch_group['beta'].values
print("Extracted channel keys:", list(ch_curves.keys())[:5])
for roi_name, channels in roi_map.items():
for roi_name, channels in active_roi_map.items():
matching_curves = [ch_curves[ch] for ch in channels if ch in ch_curves]
if matching_curves:
roi_curves[(chromo, roi_name)] = np.mean(matching_curves, axis=0)
# 2. Derive HbT (HbO + HbR) dynamically if requested
if 'hbt' in [c.lower() for c in chromophores]:
for roi_name in active_roi_map.keys():
hbo_key = ('hbo', roi_name)
hbr_key = ('hbr', roi_name)
if hbo_key in roi_curves and hbr_key in roi_curves:
roi_curves[('hbt', roi_name)] = roi_curves[hbo_key] + roi_curves[hbr_key]
return roi_curves
@@ -7252,7 +7263,8 @@ def extract_fir_features_real_data(
target_condition=None,
fir_delays=np.arange(0, 15),
selected_metrics=('Peak_Amp',),
roi_map=ROI_MAP,
roi_map={},
chromophores=('hbo', 'hbr', 'hbt'),
glm_est=None,
df_design_matrix=None,
png_queue=None
@@ -7285,7 +7297,7 @@ def extract_fir_features_real_data(
target_condition=cond,
fir_delays=fir_delays,
roi_map=roi_map,
chromophores=('hbr',),
chromophores=chromophores,
glm_est=glm_est,
df_design_matrix=df_design_matrix
)
@@ -7300,9 +7312,10 @@ def extract_fir_features_real_data(
for (chromo, roi_name), roi_fir_curve in roi_curves.items():
metrics = compute_waveform_metrics(roi_fir_curve, fir_delays=fir_delays, selected_metrics=selected_metrics)
collapsed_features.extend(metrics)
# Prefix feature names with condition
feature_names.extend([f"{cond}_{roi_name}_{m}" for m in metric_labels])
feature_channels.extend([roi_name] * len(metric_labels))
# Prefix feature names with condition AND chromophore
feature_names.extend([f"{cond}_{chromo.upper()}_{roi_name}_{m}" for m in metric_labels])
feature_channels.extend([f"{roi_name} ({chromo.upper()})"] * len(metric_labels))
# Enqueue plots for this specific condition
plot_and_enqueue_waveform_metrics(
@@ -7366,6 +7379,43 @@ def compute_waveform_metrics(fir_curve, fir_delays, selected_metrics=('Peak_Amp'
return [calculated_metrics[m] for m in selected_metrics]
def normalize_roi_map(roi_map):
"""
Normalizes ROI mapping inputs into a flat {roi_name: [channel_list]} dict.
Accepts:
1. String or Path pointing to a JSON file (JSON_LOCATION).
2. Dict with 'regions_of_interest' list (loaded JSON).
3. Standard flat dict {roi_name: [channels]}.
"""
# 1. If roi_map is a file path string or Path, load JSON from disk
if isinstance(roi_map, (str, Path)):
json_path = Path(roi_map)
if json_path.is_file():
try:
with open(json_path, 'r', encoding='utf-8') as f:
roi_map = json.load(f)
except Exception as e:
logger.error(f"Failed to load ROI JSON file from {json_path}: {e}")
return {}
else:
logger.error(f"ROI JSON file path does not exist: {json_path}")
return {}
# 2. Handle nested JSON structure with 'regions_of_interest'
if isinstance(roi_map, dict) and 'regions_of_interest' in roi_map:
return {
roi['name']: roi['channels']
for roi in roi_map['regions_of_interest']
}
# 3. Fallback for flat dictionary {roi_name: [channels]}
if isinstance(roi_map, dict):
return roi_map
return {}
if __name__ == "__main__":
print("This file has no functionality when not used in tandem with the FLARES application.")