MNE-python-系统性介绍(三)

378 阅读8分钟

从数据中解析事件(Parsing Events From Raw Data)

本次介绍如何从原始记录中读取实验事件,以及如何在MNE Python中的两种不同的事件表示形式,即两种与事件有关的对象(Events Array和Annotations objects)之间进行转换。

在前面的文章中,我们看到了一个从STIM Channel阅读实验事件的例子;在这里,我们将更广泛地讨论EventsAnnotations,提供有关从STIM Channel读取的更详细信息,并给出读取标记文件中的事件或作为嵌入(Embedded)数组包含在数据文件中的事件的示例。使用EventsAnnotations连续数据的教程分别讨论了如何图示、组合、加载、保存和导出事件和注释,后一个教程还介绍了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为单位。
  • 事件描述的限制(Limits on the Description):

    • Events数据结构将what表示为整数Event ID代码。
    • Annotations数据结构将what表示为字符串
  • 事件持续时间如何编码(How Duation IS Encoded):

    • Events中的事件没有duration(虽然可能通过数组内成对的onset/offset events来表示)
    • Annotations对象中必须包含duration(虽然duration参数可能为零当为瞬时事件时)。
  • Internal Representation(大致意思就是计算机内部存储形式)

    • Events存储在一个普通的Numpy Array
    • Annotations存储在在MNE-python中类似于list的数据结构中。

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)

image.png

我们可以看到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_CHANNELMNE_STIM_CHANNEL_1等。如果配置变量也没有找到,将会尝试STI 014STI101这两个通道,这两个通道(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对象包含三个属性:onsetdurationdescription

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
    • 会返回descriptionEvent ID之间的映射。
    • 默认情况是,一个event将会在descriptiononset上建立。这个可以进行修改,通过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 IDDescription之间的具体关系,可以通过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 IDdescription的映射,然后使用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)
    

    输出为: image.png

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.   ]