从数据中解析事件(Parsing Events From Raw Data)
本次介绍如何从原始记录中读取实验事件,以及如何在MNE Python中的两种不同的事件表示形式,即两种与事件有关的对象(Events Array和Annotations objects)之间进行转换。
在前面的文章中,我们看到了一个从STIM Channel
阅读实验事件的例子;在这里,我们将更广泛地讨论Events
和Annotations
,提供有关从STIM Channel
读取的更详细信息,并给出读取标记文件中的事件或作为嵌入(Embedded)数组包含在数据文件中的事件的示例。使用Events
和Annotations
连续数据的教程分别讨论了如何图示、组合、加载、保存和导出事件和注释,后一个教程还介绍了Raw
对象的交互式注释。
首先,我们将加载所需的Python模块,并加载与前面文章中使用的相同的示例数据,但为了节省内存,我们将原始对象裁剪到60秒后再加载到RAM中:
import os
import numpy as np
import mne
sample_data_folder = mne.datasets.sample.data_path()
sample_data_raw_file = os.path.join(sample_data_folder, 'MEG', 'sample',
'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(sample_data_raw_file)
raw.crop(tmax=60).load_data()
Events和Annotations数据结构
一般来说Events和Annotations都有相同的目的:它们提供在记录EEG/MEG期间的时间和在那些时间所发生的事情的描述的映射关系。它们把when和what联系在一起。两个数据结构的主要区别是:
-
单位(Units):
- Events以
样本
为单位即samples
为单位。 - Annotations以
秒
即second
为单位。
- Events以
-
事件描述的限制(Limits on the Description):
- Events数据结构将what表示为整数
Event ID
代码。 - Annotations数据结构将what表示为
字符串
。
- Events数据结构将what表示为整数
-
事件持续时间如何编码(How Duation IS Encoded):
- Events中的事件没有
duration
(虽然可能通过数组内成对的onset/offset events
来表示) - Annotations对象中必须包含
duration
(虽然duration参数可能为零当为瞬时事件时)。
- Events中的事件没有
-
Internal Representation(大致意思就是计算机内部存储形式)
- Events存储在一个普通的
Numpy Array
。 - Annotations存储在在MNE-python中
类似于list
的数据结构中。
- Events存储在一个普通的
What Is STIM Channel(Stimulus channel)?
Stim Channel是不接收来自EEG、MEG或其他传感器信号的通道。相反,STIM通道记录的电压(通常是从实验控制计算机发送的固定大小的短矩形直流脉冲)与实验事件是锁时的
,例如刺激开始或受试者按下按钮的反应(这些脉冲有时被称为TTL脉冲、事件脉冲、触发信号或只是“触发器”)。在其他情况下,这些脉冲可能不会严格锁定在实验事件上,而是可能发生在两次试验之间,以指示即将在即将到来的试验中发生的刺激类型(或实验条件)。
直流脉冲可以全部在一个STIM通道上(在这种情况下,不同的实验事件或试验类型被编码为不同的电压幅值),或者它们可以分布在多个通道上,在这种情况下,脉冲发生的通道可以用于编码不同的事件或条件。即使在具有多个STIM通道的系统上,通常也会有一个通道记录其他STIM通道的加权和,以这样的方式,该通道上的电压水平可以明确地解码为特定事件类型。在较旧的Neuromag系统(例如用于记录样本数据的系统)上,这种“总和通道”通常是STI 014;在较新的系统上,它更常见。我们之前的文章中就用到了STI 014
。
raw.copy().pick_types(meg=False, stim=True).plot(start=3, duration=6)
我们可以看到STI 014(总和通道)包含不同量级(Magnitude)的脉冲,而其他通道上的脉冲具有一致的量级。您还可以看到,每当其他一个STIM通道上有一个脉冲时,STI 014上就会有一个相应的脉冲。
Converting A STIM Channel signal to an Event Array
如果数据在STIM通道上记录了事件,则可以使用find_events将其转换为event array。每个脉冲的onset(或offset,因为有时候脉冲不与事件锁时,而是在两次试验之间,所以此时事件的开始就要加上offset)的样本数被记录为事件时间,脉冲大小被转换为整数,这些样本数对加上整数码被存储在NumPy数组中(通常称为“事件数组”或“事件”)。在最简单的形式中,函数只需要原始对象和从中读取事件的通道名称:
events = mne.find_events(raw, stim_channel='STI 014')
print(events[:5]) # show the first 5
上述代码输出为:
86 events found
Event IDs: [ 1 2 3 4 5 32]
[[27977 0 2]
[28345 0 3]
[28771 0 1]
[29219 0 4]
[29652 0 2]]
如果我们没有提供STIM Channel的名字,find_events
将首先寻找MNE-python的配置变量MNE_STIM_CHANNEL
、MNE_STIM_CHANNEL_1
等。如果配置变量也没有找到,将会尝试STI 014
和STI101
这两个通道,这两个通道(followed by the first channel with type “STIM” present in raw.ch_names
)。如果我们经常使用来自多个不同MEG系统、具有不同STIM通道名称的数据,设置MNE_STIM_CHANNEL
配置变量可能不是很有用,但对于数据都来自单个系统的研究人员来说,只需配置一次该变量,然后就可以省时省力了。
Reading Embedded Events As Annotations
一些EEG/MEG系统生成文件,其中事件存储在单独的数据阵列中,而不是作为一个或多个STIM Channel上的脉冲。例如,EEGLAB格式将事件存储.set
文件中的数组集合。读取这些文件时,MNE Python会自动将存储的事件转换为Annotations
对象,并将其存储为Raw
对象的Annotations
属性:
testing_data_folder = mne.datasets.testing.data_path()
eeglab_raw_file = os.path.join(testing_data_folder, 'EEGLAB', 'test_raw.set')
eeglab_raw = mne.io.read_raw_eeglab(eeglab_raw_file)
print(eeglab_raw.annotations)
上述代码的输出为:
Reading /home/circleci/mne_data/MNE-testing-data/EEGLAB/test_raw.fdt
<Annotations | 154 segments: rt (74), square (80)>
Annotation对象包含三个属性:onset
、duration
和description
。
print(len(eeglab_raw.annotations))
print(set(eeglab_raw.annotations.duration))
print(set(eeglab_raw.annotations.description))
print(eeglab_raw.annotations.onset[0])
上述代码的输出为:
154
{0.0}
{'rt', 'square'}
1.000068
- 我们可以看到在这个EEGLab的.set文件中存储了154个事件
- 每个事件的持续时间都为0
- 有两种不同类型的事件
- 第一个事件的开始时间在记录开始后的约1秒时间
包括如何给
Raw
对象添加Annotation等更多详细细节在后面具体章节中讲解
Converting Between Events arrays and Annotations object
一旦将实验事件读入MNE Python(作为Events Array或Annotation Object),就可以根据需要轻松地在这两种格式之间进行转换。这样做可能是因为,例如,需要一个Event Array
进行Continuous Data Epoching
,或者是因为您希望利用某些函数的"Annotation-Aware"功能,如果数据与某些注释重叠,这些功能会自动忽略数据的范围(不太懂)。
-
Annotation Object
->Events Array
,使用mne.events_from_annotations()
在包含annotations
属性的Raw
对象上。- 此函数为
raw.annotations.description
中每个不重复的元素分配一个Integer Event ID
。 - 会返回
description
与Event ID
之间的映射。 - 默认情况是,一个
event
将会在description
的onset
上建立。这个可以进行修改,通过events_from_annotations
的参数chunk_duration
。可以在每个annotation
内创建等间隔的事件。
events_from_annot, event_dict = mne.events_from_annotations(eeglab_raw) print(event_dict) print(events_from_annot[:5])
上述代码的输出为:
Used Annotations descriptions: ['rt', 'square'] {'rt': 1, 'square': 2} [[128 0 2] [217 0 2] [267 0 1] [602 0 2] [659 0 1]]
如果要控制
Event ID
和Description
之间的具体关系,可以通过events_from_annotations
的参数event_id
。custom_mapping = {'rt': 77, 'square': 42} (events_from_annot, event_dict) = mne.events_from_annotations(eeglab_raw, event_id=custom_mapping) print(event_dict) print(events_from_annot[:5])
上述代码的输出为:
Used Annotations descriptions: ['rt', 'square'] {'rt': 77, 'square': 42} [[128 0 42] [217 0 42] [267 0 77] [602 0 42] [659 0 77]]
- 此函数为
-
Events Array
->Annotation Object
,我们可以创建一个Event ID
到description
的映射,然后使用annotations_from_events
来进行转换,然后调用set_annotations
来在Raw
对象上加annotations
。mapping = {1: 'auditory/left', 2: 'auditory/right', 3: 'visual/left', 4: 'visual/right', 5: 'smiley', 32: 'buttonpress'} annot_from_events = mne.annotations_from_events( events=events, event_desc=mapping, sfreq=raw.info['sfreq'], orig_time=raw.info['meas_date']) raw.set_annotations(annot_from_events)
- 由于样本数据记录在Neuromag系统上(样本编号在采集系统启动时开始,而不是记录启动时开始),因此我们还需要传入
orig_time
参数,以便onsets与记录开始时正确对齐。 现在,在打印原始数据时,注释将自动显示,并将根据其标签值进行颜色编码:
raw.plot(start=5, duration=5)
输出为:
- 由于样本数据记录在Neuromag系统上(样本编号在采集系统启动时开始,而不是记录启动时开始),因此我们还需要传入
Making multiple events per annotation
正如前面所说的,可以使用events_from_annotation
的参数chunk_duration
来进行等时间间隔的事件划分。
例如,假设在Raw
对象上有一个annotation
,它表示了被试者什么时候进入REM sleep状态。我们想要对这些数据进行静息状态分析。因此我们可以在每个REM sleep状态内创建一个Events Array
,数组中都是等时间间隔的事件,然后再进一步利用这些时间来创建epoch
以进一步分析。
# create the REM annotations
rem_annot = mne.Annotations(onset=[5, 41],
duration=[16, 11],
description=['REM'] * 2)
raw.set_annotations(rem_annot)
(rem_events,
rem_event_dict) = mne.events_from_annotations(raw, chunk_duration=1.5)
print(np.round((rem_events[:, 0] - raw.first_samp) / raw.info['sfreq'], 3))
现在我们可以检查我们的事件是否确实落在5-21秒和41-52秒的范围内,并且相隔约1.5秒(由于采样频率会有一些抖动)。以下是四舍五入到最接近毫秒的事件时间:
[ 5. 6.5 8. 9.5 11. 12.501 14.001 15.501 16.999 18.499
41. 42.5 44. 45.5 47. 48.5 50. ]