Skip to content

第 6 章:事件与 Epoching

有了干净、已滤波的连续数据,现在我们可以把它切成短小的试次片段(Epochs)—— 一切关于 ERP 的分析都建立在这上面。本章讲解如何查找事件,以及如何正确构建 Epochs

import mne
import matplotlib.pyplot as plt
from pathlib import Path

mne.set_log_level("WARNING")

sample_folder = Path(mne.datasets.sample.data_path()) / "MEG" / "sample"
raw = mne.io.read_raw_fif(sample_folder / "sample_audvis_raw.fif", preload=True)

# 在丢弃刺激通道之前先查找事件 -- 我们需要它来找出触发点
events = mne.find_events(raw, stim_channel="STI 014")

raw.pick(["eeg", "eog"])
raw.filter(l_freq=1.0, h_freq=40.0)
print("找到的事件数量:", len(events))
print(events[:10])
找到的事件数量: 320
[[27977     0     2]
 [28345     0     3]
 [28771     0     1]
 [29219     0     4]
 [29652     0     2]
 [30025     0     3]
 [30450     0     1]
 [30839     0     4]
 [31240     0     2]
 [31665     0     3]]

两种查找事件的方式

正如第 3 章提到的,事件会出现在两个地方之一:

# 方法一:专门的刺激/触发通道(本数据集)
events = mne.find_events(raw, stim_channel="STI 014")

# 方法二:附加在文件上的标注(annotations,你自己的 .edf 记录)
events, event_id = mne.events_from_annotations(raw)

不管用哪种方法,最终得到的都是一个 events 数组:每行代表一个事件,包含 [采样点编号, 0, 事件编码] 三列。event_code(事件编码)只是一个整数 —— 我们通过一个 event_id 字典,赋予它可读的含义。

# 本数据集的事件编码,MNE 文档中已有记录:
event_id = {
    "auditory/left": 1,
    "auditory/right": 2,
    "visual/left": 3,
    "visual/right": 4,
    "smiley": 5,
    "button": 32,
}

fig = mne.viz.plot_events(events, sfreq=raw.info["sfreq"], event_id=event_id)
plt.show()

png

注意像 "auditory/left" 这样的名字里有一个 /。MNE 会把它当作层级结构来处理:之后,epochs["auditory"] 会同时选中 auditory/leftauditory/right,而 epochs["auditory/left"] 则只选中那一个。这纯粹是你在构建 event_id 时自己选择的命名约定 —— 可以用它来把相关的条件归为一组。

创建 Epochs

mne.Epochs 会在每个事件周围切出一个固定的时间窗口。关键参数:

  • tmintmax:相对事件的窗口边界,单位是秒(例如 -0.20.5 表示事件前 200 ms 到事件后 500 ms)。
  • baseline:一个时间窗口(通常是事件前的时段),它的均值会从每个通道中减去 —— 用来修正任意的电压偏移,使各个 epoch 之间可以比较。(-0.2, 0.0) 是常见的选择。
  • reject:一个字典,给出各通道类型的峰峰值幅度阈值;任何超过阈值的 epoch 都会被自动丢弃 —— 这是对付 ICA 没能清理干净的伪迹的一道简单但有效的第二道防线。
  • preload:含义和 Raw 中一样 —— 立即加载数据。
epochs = mne.Epochs(
    raw,
    events,
    event_id=event_id,
    tmin=-0.2,
    tmax=0.5,
    baseline=(-0.2, 0.0),
    preload=True,
    reject=dict(eeg=150e-6, eog=250e-6),
)
epochs
General
MNE object type Epochs
Measurement date 2002-12-03 at 19:01:10 UTC
Participant Unknown
Experimenter MEG
Acquisition
Total number of events 300
Events counts auditory/left: 63
auditory/right: 69
button: 16
smiley: 15
visual/left: 72
visual/right: 65
Time range -0.200 – 0.499 s
Baseline -0.200 – 0.000 s
Sampling frequency 600.61 Hz
Time points 421
Metadata No metadata set
Channels
EEG and
EOG
Head & sensor digitization 146 points
Filters
Highpass 1.00 Hz
Lowpass 40.00 Hz

检查哪些 epoch 被丢弃了

reject 阈值会自动丢弃受污染的 epoch。务必检查丢弃了多少、以及为什么 —— 如果丢弃的比例过大,说明你的阈值设置得太严格了(或者说明上游的伪迹清理步骤还需要改进)。

print(epochs.drop_log_stats(), "% 的 epoch 被丢弃")
epochs.plot_drop_log()
plt.show()
6.25 % 的 epoch 被丢弃

png

选择与浏览 epoch

用条件名称(也就是 event_id 中的标签)来索引,并像浏览 raw.plot() 一样,逐个浏览单个试次。

print("所有听觉试次(左 + 右):", len(epochs["auditory"]))
print("仅 auditory/left:", len(epochs["auditory/left"]))

epochs.plot(n_epochs=10, scalings="auto")
plt.show()
所有听觉试次(左 + 右): 132
仅 auditory/left: 63

png

保存 epochs(可选)

对一个满意的 Epochs 对象,你可以把它保存下来,下次就不用重新跑一遍整个流程:

epochs.save("my-epo.fif", overwrite=True)
epochs = mne.read_epochs("my-epo.fif")
-epo.fif 这种命名约定是 MNE 的硬性要求,不只是建议。)

小结

  • 事件来自刺激通道(mne.find_events)或标注(mne.events_from_annotations)。
  • event_id 把可读的条件名称(可以用 / 表示层级)映射到整数编码。
  • mne.Epochs(raw, events, event_id, tmin, tmax, baseline, reject) 切分并清理试次片段。
  • 应用拒绝阈值之后,务必检查 epochs.plot_drop_log()

下一章:第 7 章 —— ERP 分析 —— 把这些 epoch 平均成 ERP 本身。