Skip to content

第 8 章:应用到你自己的数据

把第 1–7 章的所有内容,应用到你自己的 .edf 记录上。这个 notebook 完全在你自己的电脑上运行 —— 只需要修改下面的 EDF_PATH;这里的任何内容都不会把你的数据上传或发送到任何地方。

你的文件已知的一些特殊之处(下面已经考虑进去了)(来自之前的检查):

  • 32 个 EEG 通道,以 CPz 为参考,命名类似 EEG Fp1-CPz —— 需要先改名,标准 montage 才能套用(第 3 章)。
  • 包含乳突电极 M1/M2
  • 采样率 1000 Hz,时长约 944 秒。
  • raw.annotations 中包含了类似 Impedance ... 的条目 —— 这些是放大器阻抗检查标记,不是任务事件。在切分 epoch 有意义之前,你需要先确认你真正的刺激/反应触发标记长什么样(用新的眼光再检查一遍 raw.annotations,或者向记录这份数据的人询问他们使用的标记方案)。
import mne
import matplotlib.pyplot as plt
import re

%matplotlib inline
mne.set_log_level("WARNING")

1. 加载(第 3 章)

EDF_PATH = r"PUT_YOUR_FILE_PATH_HERE.edf"  # <-- 修改这一行为你的文件路径

raw = mne.io.read_raw_edf(EDF_PATH, preload=True)
raw

2. 检查

print("通道名称:", raw.ch_names)
print("采样率 (Hz):", raw.info["sfreq"])
print("时长 (s):", raw.times[-1])
print("标注:", raw.annotations)

3. 重命名通道并应用 montage(第 3 章)

去掉 EEG 前缀和 -CPz 参考后缀,使通道名称与标准 10-20 命名对应(EEG Fp1-CPzFp1)。这只是标签层面的修正 —— 不会改变底层信号或它的参考方案。

raw.rename_channels(lambda name: re.sub(r"^EEG\s+", "", name).split("-")[0])
print(raw.ch_names)

montage = mne.channels.make_standard_montage("standard_1020")
raw.set_montage(montage, on_missing="warn")

4. 可视化并标记坏通道(第 3 章)

raw.plot(n_channels=20, duration=10, scalings="auto")
plt.show()
raw.info["bads"] = []  # <-- 把你在上面注意到的坏通道名称填在这里

5. 滤波(第 4 章)

LINE_FREQ = 50  # <-- 如果你所在地区市电是 60 Hz,改成 60

raw_filt = raw.copy().filter(l_freq=1.0, h_freq=40.0)
raw_filt.notch_filter(freqs=LINE_FREQ)

raw_filt.compute_psd(picks="eeg").plot()
plt.show()

6. 用 ICA 去除伪迹(第 5 章)

你的文件没有专门的 EOG 通道,所以我们退回到肉眼检查 —— 寻找一个具有强烈、对称、额部地形图的成分(典型的眨眼特征)。

ica = mne.preprocessing.ICA(n_components=15, random_state=97, max_iter="auto")
ica.fit(raw_filt)
ica.plot_components()
plt.show()
ica.plot_sources(raw_filt)
plt.show()
ica.exclude = []  # <-- 填入要去除的成分索引,例如 [0, 3]

raw_clean = raw_filt.copy()
ica.apply(raw_clean)

raw_clean.plot(n_channels=20, duration=10, scalings="auto")
plt.show()

7. 查找事件(第 6 章)

继续之前,先停下来仔细检查这一步。 前面的检查显示,你的 raw.annotations 中包含的是阻抗检查标记,而不是真正的任务事件。运行下面的单元格,看看 event_id 里实际有什么 —— 如果里面只是阻抗/设置相关的标记,那么你真正的触发信号存放在别处(一个状态/触发通道,或者你实验软件生成的一份单独日志文件),你需要先调整这一步,切分 epoch 才有意义。

events, event_id = mne.events_from_annotations(raw_clean)
print("找到的事件编码:", event_id)
print("事件数量:", len(events))

8. 切分 Epoch(第 6 章)

等你知道了真正的事件标签之后,把下面的 event_id 调整为你真正关心的那个(些)条件(例如 event_id={"stimulus/target": 1}),并根据你的实验设计调整 tmin/tmax

epochs = mne.Epochs(
    raw_clean,
    events,
    event_id=event_id,
    tmin=-0.2,
    tmax=0.8,
    baseline=(-0.2, 0.0),
    preload=True,
    reject=None,  # 等你看过典型的幅度大小之后,可以加上峰峰值阈值,例如 dict(eeg=150e-6)
)
epochs

9. 平均成 ERP(第 7 章)

evoked = epochs.average()
evoked.plot()
plt.show()
# 等你有了真实、有意义的条件标签之后,可以这样比较它们:
# evoked_condA = epochs["real_label_a"].average()
# evoked_condB = epochs["real_label_b"].average()
# mne.viz.plot_compare_evokeds({"条件 A": evoked_condA, "条件 B": evoked_condB})

接下来做什么

在运行这个 notebook 的过程中,把你在每一步观察到的情况描述出来(通道名称、事件标签、epoch 数量、图看起来是什么样)—— 只需要文字/打印出来的输出,绝不要把数据文件本身发出来 —— 我们会一起调试、完善。一旦真实的事件接上了,并且开始得到看起来合理的 ERP,接下来自然的步骤是:测量具体的成分峰值(第 7 章的 get_peak()),规范地比较条件,最终再进入正式的统计检验(mne.stats,第 7 章简单提到过)。