This repository contains an epoch-based fNIRS processing pipeline built on MNE-Python and MNE-NIRS. It loads annotated .fif raw files, applies a configurable cleaning pipeline, converts OD → Hb (MBLL), and exports per-channel features, ROI-level features (two methods), and QC logs.
- CF subjects (S1–S24) are read from:
CF_DIR/<Subject>-pr-annotated_raw.fif
- SD subjects (S25–S48) are mapped to source files:
SD_DIR/S<SubjectNum-24>-6pm-annotated_raw.fif
Example:
S26readsSD_DIR/S2-6pm-annotated_raw.fif
The pipeline is applied in the following conceptual order:
- Raw → Optical Density (OD)
- (Optional) Linear detrend on OD
- SCI-based channel dropping (scalp coupling index)
- TDDR (Temporal Derivative Distribution Repair)
- Short-channel regression (SCR) (if short channels exist)
- Band-pass filter (BPF) (default 0.01–0.5 Hz, Butterworth IIR)
- Post-filter QC drop based on range/variance outliers
- (Optional) Power-based BAD annotations and masking
- Keep long channels only (optional)
- Prune & reorder wavelength pairs (ensure valid pairs for MBLL)
- OD → Hemoglobin (HbO/HbR/HbT) using Beer–Lambert (MBLL)
- Baseline subtraction using the middle X minutes of the single “Baseline” annotation
- Parse trials using annotations (Activity–Stim–Trial) and compute features
The script expects trial annotations with names that match:
<something>-<Activity>-<Stimulation>-<T1|T2>
Examples:
S1-pr-TWEO-NG-T1S1-pr-TWEO-G-T2
Baseline annotation
- The baseline segment must include the word
Baseline(case-insensitive). - Baseline subtraction uses the middle
BASELINE_MIDDLE_MINminutes of that segment.
ROIs are defined in the dictionary brain_regions, mapping ROI names to HbO channel labels (e.g., "S1_D1 hbo").
Two ROI feature methods are computed:
- Compute features for each HbO channel in the ROI
- Average feature values across ROI channels
- Output columns are prefixed with
ChWise_
- Average HbO waveforms across ROI channels (per trial)
- Compute features on the ROI-averaged waveform
- Output columns are prefixed with
ROIWise_
A combined file compares both methods per ROI.
features_get_per_channel_then_average.xlsx
One row per (trial × HbO channel) with:- Mean, AUC, AUC_abs, RMS, SD
- PeakSigned, PeakPos, PeakPosWinMean2s, PeakNeg, PeakAbs
- PeakToPeak, PeakWinMean2s
timeseries_get_per_channel_then_average.xlsx
Exported only ifEXPORT_TIMESER = True.
features_channelAverageFirst.xlsx
One row per (trial × ROI), includes:N_ch= number of contributing HbO channels
timeseries_channelAverageFirst.xlsx
Exported only ifEXPORT_TIMESER = True.
roi_features_both_methods.xlsx
One row per (trial × ROI), includes:ChWise_*features +ChWise_N_chROIWise_*features +ROIWise_N_ch
Saved in qc_logs/:
<Subject>_sci.csv— SCI values per OD channel<Subject>_chanvar_qc.csv— post-filter range/variance QC metrics<Subject>_baseline.csv— baseline window details<Subject>_trial_qc.csv— which trials were kept / skipped<Subject>_channel_inclusion_by_trial.csv— kept/dropped channels and reasons
Generated only if DO_PLOTS = True:
Per_Channel_HbO/— 2×2 panel per subject/activity with all HbO channelsperROI_HbO/— 2×2 panel per subject/activity with ROI overlay lines + legend
-
Annotated
.fiffiles- Must contain fNIRS channels with valid wavelength pairing info for MBLL.
- Must contain trial annotations as described above.
-
Template Excel
all_subjects_expected_trials.xlsx- Must include columns:
trialID, Subject, Fatigue, Activity, Stimulation, Trial
Install the main dependencies:
pip install numpy pandas matplotlib openpyxl mne mne-nirs scipyNotes
- The script uses
glue-free pandas Excel export;openpyxlis recommended. - Deprecation warnings for
np.trapzmay appear with newer NumPy; it’s safe but you can replace withnp.trapezoidlater.
Save the notebook cell code into a Python file (e.g., fnirs_epoch_pipeline.py) and run:
python fnirs_epoch_pipeline.pyRun the main cell; outputs will be written to the working directory.
At the top under CONFIG:
CF_DIR,SD_DIR— paths to annotated FIF foldersSUBJECTS_CF,SUBJECTS_SD— which subjects to processTEMPLATE_XLSX— expected-trials template file
Pipeline toggles (common ones):
DO_SCI,DO_TDDR,DO_SCR,DO_BPFKEEP_LONG_ONLYDO_BASELINE_SUBTRACTDO_POSTFILTER_VAR_QCEXPORT_TIMESER(careful: makes very large files)DO_PLOTS
QC parameters:
SCI_THRESHOLDBPF_LF,BPF_HF,IIR_ORDERQC_METRIC,QC_MULTBASELINE_MIDDLE_MIN
Channel inclusion is decided per trial using reasons like:
short(short channel)SCI(SCI below threshold)qcvar(range/var too large)unpaired_wavelength(missing wavelength pair after pruning)not_present_after_pipelinepower_masked_majority(too many NaNs after BAD_power masking)
See: qc_logs/<Subject>_channel_inclusion_by_trial.csv
- SCR requires short channels. If your montage has no short channels (or they were dropped), SCR is skipped.
- Baseline subtraction will be skipped; check your raw annotations.
- Set
EXPORT_TIMESER = Falseunless you really need timeseries exports.
- Safe warning; the script still works. You can replace with
inst.pick(...)later.
MIT