第 4 层 · 伪迹剔除层 · 第 20 步 / 共 25 步

步骤 20:事件提取

从 annotations 提取 events 数组

🕐 22 分钟🎯 难度:进阶🔗 核心 API:events_from_annotations⬅️ 前置:步骤 19

1. 目标

从 Raw 对象中提取事件标记(trigger),用于后续 Epochs 创建。事件是 (sample_index, prev_value, trigger_id) 三元组数组。

2. 何时使用 / 何时跳过

  • 必须使用:任何基于事件分段的 ERP 研究
  • 可跳过:连续数据研究(如 resting-state)

3. 前置条件

  • 已执行步骤 19:ICA 已施加(或决定先创建 epochs 后施加)
  • 数据中存在 stim 类型通道或 annotation

4. 核心代码

Python
import mne

# ====== 方式 A:从 annotation 提取事件(推荐) ======
# 适用于 .fif、.vhdr(已含 annotation)格式
events = mne.events_from_annotations(raw, event_id=None)
# 返回: events_array, event_id_dict
events, event_id = events
print(f"事件数: {len(events)}")
print(f"事件 ID 字典: {event_id}")

# ====== 方式 B:从 stim 通道直接检测 ======
# 适用于 .edf、.cnt、原始 .fif 等使用 stim 通道的格式
# 实测 MNE 1.12.1 源码(mne/event.py)L33:
#   output : 'onset' | 'offset' | 'step'
# 默认即 'onset';'onset' 是合法值(不是 'red'/'on'/'off')
events = mne.find_events(
    raw,
    stim_channel='STI001',     # stim 通道名
    output='onset',            # 'onset'(默认)/'offset'/'step'
    consecutive='increasing',  # 默认 'increasing'
    min_duration=0,            # 默认 0
    shortest_event=2,          # 默认 2 样本
    verbose='INFO',
)
print(f"找到 {len(events)} 个事件")
print(f"前 5 个事件: {events[:5]}")

# ====== 方式 C:自定义事件字典映射 ======
# 仅保留你关心的 trigger
event_id = {
    'target': 1,
    'standard': 2,
}
events, _ = mne.events_from_annotations(raw, event_id=event_id)
print(f"过滤后事件数: {len(events)}")

# ====== 验证:每个 ID 的事件数 ======
from collections import Counter
event_counts = Counter(events[:, 2])
print(f"事件计数: {event_counts}")
# 期望:每个 ID 的事件数与实验设计一致

5. 参数详解

events_from_annotations

参数 含义 推荐值
event_id 事件过滤字典 None(保留所有)/ {'cond1': 1, 'cond2': 2}
regexp 正则匹配 默认匹配所有

find_events

参数 含义 推荐值
stim_channel stim 通道 'STI001' / 'STI 014'
output 输出模式 'onset'(默认,实测合法)/ 'offset' / 'step'
consecutive 是否合并相邻 'increasing'(默认);其他有意义取值:True / False
min_duration 最小事件间隔 0(默认;单位:秒)

6. 验证方法

Python
# 验证 1:事件数与实验设计一致
n_target = (events[:, 2] == event_id['target']).sum()
n_standard = (events[:, 2] == event_id['standard']).sum()
print(f"target: {n_target}, standard: {n_standard}")
# 期望:与设计文件一致

# 验证 2:事件间隔合理
event_onsets = events[:, 0]
intervals = np.diff(event_onsets) / raw.info['sfreq']  # 秒
print(f"事件间隔: min={intervals.min():.2f}s, mean={intervals.mean():.2f}s")
# 期望:≥ 实验中两个 trial 的最小间隔

# 验证 3:可视化(在 stim 通道上加标记)
mne.viz.plot_events(events, raw.info['sfreq'])

# 验证 4:触发延迟验证(用 onset 时刻附近)
# 应有事件处出现 ERP 信号

7. 常见错误

错误 原因 解决方案
找到 0 个事件 stim 通道名错误 检查 raw.ch_names
事件数过多(倍数关系) 同时存在 onset + offset 设 consecutive='increasing'(默认)
事件 ID 字典不匹配 文件 trigger ID 与设计文件不一致 用 event_id=None 全部取出后核对
触发延迟 0(很精准)但 ERP 时序错 设备存在硬件延迟 用 mne.event.shift_time_events(tshift, ...) 或调整 tmin
events_from_annotations 返回空 格式不含 annotation(如 EDF) 改用 find_events

8. 官方来源

资源类型 链接
events_from_annotations https://mne.tools/stable/generated/mne.events_from_annotations.html
find_events https://mne.tools/stable/generated/mne.find_events.html
plot_events https://mne.tools/stable/generated/mne.viz.plot_events.html
事件教程 https://mne.tools/stable/auto_tutorials/epochs/plot_10_epochs_overview.html

9. 学术引用

无需单独引用。本步骤是事件相关的标准化操作。