|
|
import os |
|
|
import shutil |
|
|
import zipfile |
|
|
from pathlib import Path |
|
|
import gradio as gr |
|
|
import mne |
|
|
|
|
|
|
|
|
from ica_xtra import run_preprocessing_pipeline |
|
|
|
|
|
|
|
|
BUILT_IN_GPSC = Path("ghw280_from_egig.gpsc") |
|
|
if not BUILT_IN_GPSC.is_file(): |
|
|
raise FileNotFoundError(f"Required montage file not found: {BUILT_IN_GPSC}") |
|
|
|
|
|
|
|
|
OUTPUT_DIR = Path("outputs") |
|
|
OUTPUT_DIR.mkdir(exist_ok=True) |
|
|
|
|
|
|
|
|
SUBJECT_ID = "sub" |
|
|
|
|
|
def preprocess_eeg( |
|
|
eeg_file, |
|
|
input_format: str = "fif", |
|
|
apply_highpass: bool = True, |
|
|
apply_lowpass: bool = True, |
|
|
apply_notch: bool = True, |
|
|
line_freq: float = 60.0, |
|
|
pre_ica_mad: float = 3.5, |
|
|
post_ica_mad: float = 5.0, |
|
|
interpolate_before_ica: bool = False, |
|
|
use_artifact_detection: bool = True, |
|
|
): |
|
|
|
|
|
subject_dir = OUTPUT_DIR / SUBJECT_ID |
|
|
if subject_dir.exists(): |
|
|
shutil.rmtree(subject_dir) |
|
|
subject_dir.mkdir(parents=True) |
|
|
(subject_dir / "plots").mkdir(exist_ok=True) |
|
|
|
|
|
uploaded_file = Path(eeg_file.name) |
|
|
|
|
|
if input_format == "mff": |
|
|
if not zipfile.is_zipfile(uploaded_file): |
|
|
raise ValueError("MFF input must be a ZIP file containing the .mff folder.") |
|
|
eeg_path = subject_dir / f"{SUBJECT_ID}.mff" |
|
|
with zipfile.ZipFile(uploaded_file, 'r') as zf: |
|
|
zf.extractall(eeg_path) |
|
|
if not (eeg_path / "info.xml").exists(): |
|
|
raise ValueError("Invalid MFF: missing 'info.xml' in the ZIP.") |
|
|
else: |
|
|
eeg_path = subject_dir / uploaded_file.name |
|
|
shutil.copy(uploaded_file, eeg_path) |
|
|
|
|
|
try: |
|
|
|
|
|
run_preprocessing_pipeline( |
|
|
subject=SUBJECT_ID, |
|
|
input_path=str(eeg_path), |
|
|
gpsc_file=str(BUILT_IN_GPSC), |
|
|
base_output_path=str(OUTPUT_DIR), |
|
|
input_format=input_format, |
|
|
apply_highpass=apply_highpass, |
|
|
apply_lowpass=apply_lowpass, |
|
|
apply_notch=apply_notch, |
|
|
line_freq=line_freq, |
|
|
pre_ica_mad_threshold=pre_ica_mad, |
|
|
post_ica_mad_threshold=post_ica_mad, |
|
|
interpolate_before_ica=interpolate_before_ica, |
|
|
use_artifact_detection_channels=use_artifact_detection, |
|
|
append_subject_to_output=True, |
|
|
plot=True, |
|
|
log_to_file=True, |
|
|
random_state=99, |
|
|
) |
|
|
|
|
|
|
|
|
cleaned_fifs = list(subject_dir.glob("*_eeg_ica_cleaned_raw.fif")) |
|
|
if not cleaned_fifs: |
|
|
raise FileNotFoundError("No cleaned FIF file found after preprocessing.") |
|
|
|
|
|
|
|
|
final_fif = None |
|
|
for f in cleaned_fifs: |
|
|
if "post_ica" in f.name: |
|
|
final_fif = f |
|
|
break |
|
|
if final_fif is None: |
|
|
|
|
|
final_fif = max(cleaned_fifs, key=os.path.getmtime) |
|
|
|
|
|
|
|
|
for f in cleaned_fifs: |
|
|
if f != final_fif: |
|
|
f.unlink() |
|
|
|
|
|
|
|
|
expected_name = subject_dir / f"{SUBJECT_ID}_eeg_ica_cleaned_raw.fif" |
|
|
if final_fif != expected_name: |
|
|
final_fif.rename(expected_name) |
|
|
cleaned_fif = expected_name |
|
|
else: |
|
|
cleaned_fif = final_fif |
|
|
|
|
|
|
|
|
raw = mne.io.read_raw_fif(str(cleaned_fif), preload=False) |
|
|
info_summary = subject_dir / f"{SUBJECT_ID}_info_summary.txt" |
|
|
with open(info_summary, "w") as f: |
|
|
f.write(f"=== EEG Info Summary for {SUBJECT_ID} ===\n") |
|
|
f.write(str(raw.info)) |
|
|
|
|
|
|
|
|
zip_path = OUTPUT_DIR / f"{SUBJECT_ID}_preprocessing_output.zip" |
|
|
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: |
|
|
for root, _, files in os.walk(subject_dir): |
|
|
for file in files: |
|
|
file_path = Path(root) / file |
|
|
arcname = file_path.relative_to(OUTPUT_DIR) |
|
|
zf.write(file_path, arcname) |
|
|
|
|
|
return str(zip_path) |
|
|
|
|
|
except Exception as e: |
|
|
error_log = subject_dir / "error.log" |
|
|
with open(error_log, "w") as f: |
|
|
f.write(f"Preprocessing failed:\n{str(e)}") |
|
|
zip_path = OUTPUT_DIR / f"{SUBJECT_ID}_error.zip" |
|
|
with zipfile.ZipFile(zip_path, "w") as zf: |
|
|
zf.write(error_log, error_log.name) |
|
|
return str(zip_path) |
|
|
|
|
|
|
|
|
with gr.Blocks(theme=gr.themes.Base(), title="EEG Preprocessing") as demo: |
|
|
gr.Markdown("# EEG Preprocessing Pipeline") |
|
|
gr.Markdown("Upload an EEG file (.fif or .mff). Uses built-in `ghw280_from_egig.gpsc` montage.") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
eeg_input = gr.File(label="EEG File (.fif or .mff)") |
|
|
format_dropdown = gr.Dropdown(choices=["fif", "mff"], value="fif", label="Input Format") |
|
|
|
|
|
with gr.Accordion("Advanced Settings", open=False): |
|
|
hp = gr.Checkbox(True, label="Apply Highpass (1.0 Hz)") |
|
|
lp = gr.Checkbox(True, label="Apply Lowpass (100.0 Hz)") |
|
|
notch = gr.Checkbox(True, label="Apply Notch Filter") |
|
|
line_freq = gr.Number(60.0, label="Line Frequency (Hz)") |
|
|
pre_mad = gr.Slider(2.0, 8.0, value=3.5, label="Pre-ICA MAD Threshold") |
|
|
post_mad = gr.Slider(2.0, 8.0, value=5.0, label="Post-ICA MAD Threshold") |
|
|
interp_before = gr.Checkbox(False, label="Interpolate Bad Channels Before ICA") |
|
|
use_artifact = gr.Checkbox(True, label="Use Artifact Detection Channels") |
|
|
|
|
|
run_btn = gr.Button("Run Preprocessing", variant="primary") |
|
|
|
|
|
with gr.Column(): |
|
|
output_file = gr.File(label="Download Preprocessing Results (.zip)") |
|
|
|
|
|
run_btn.click( |
|
|
fn=preprocess_eeg, |
|
|
inputs=[ |
|
|
eeg_input, |
|
|
format_dropdown, |
|
|
hp, |
|
|
lp, |
|
|
notch, |
|
|
line_freq, |
|
|
pre_mad, |
|
|
post_mad, |
|
|
interp_before, |
|
|
use_artifact, |
|
|
], |
|
|
outputs=output_file, |
|
|
) |
|
|
|
|
|
demo.launch() |