整理自【脑机接口社区】的几篇文章:
(3)-数据结构Epoch及其创建方法
epochs 定义
从连续的脑电图信号中提取一些特定时间窗口的信号,这些时间窗口可以称作为epochs.
由于EEG是连续收集的,要分析脑电事件相关的电位时,需要将信号"切分"成时间片段,这些时间片段被锁定到某个事件(例如刺激)中的时间片段。
举个例子
假设我们有一个长度为60s的信号x,采样频率为1 Hz.
脑电信号的矩阵表示为1x60矩阵,
如果将信号划分成一些2s的信号,则将有30个peoch(信号中每2s就是一个epoch)
在MNE中,Epoch对象是一种把连续型数据作为时间段集合的表示方法,
形状为(n_events,n_channels,n_times)的数组形式:
创建Epochs对象方式有三种:
(1)通过Raw对象和事件事件点(event times)
(2)通过读取.fif文件数据生成Epoch对象
a. 读取fif文件创建Epoch对象
1)读取fif文件,构建raw对象;
raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
2)创建event对象;
event_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw-eve.fif'
event_id, tmin, tmax = 1, -0.2, 0.5
为什么event id = 1
events = mne.read_events(event_fname)
3)创建epoch对象;
epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
picks=picks, baseline=(None, 0), preload=True,
reject=dict(grad=4000e-13, mag=4e-12, eog=150e-6))
mne.Epochs
b.查看epoch对象
print(epochs.events[:3])
[[27977 0 2]
[28771 0 1]
[29652 0 2]]
print(epochs.event_id)
{'Auditory/Left': 1, 'Auditory/Right': 2}
4)对epoch进行叠加平均得到evoked对象;
evoked = epochs.average()
evoked.plot(time_unit='s')
plt.show()
5)绘制evoked。
(3)通过mne.EpochsArray从头创建Epoch对象
a. 读取fif文件创建Epoch对象
1)读取fif文件,构建raw对象;
raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
2)创建event对象;
event_id, tmin, tmax = 1, -0.2, 0.5
为什么event id = 1
events = mne.read_events(event_fname)
event_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw-eve.fif'
3)创建epoch对象;
epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
picks=picks, baseline=(None, 0), preload=True,
reject=dict(grad=4000e-13, mag=4e-12, eog=150e-6))
mne.Epochs
b.查看epoch对象
print(epochs.events[:3])
[[27977 0 2]
[28771 0 1]
[29652 0 2]]
打印前三个
print(epochs.event_id)
{'Auditory/Left': 1, 'Auditory/Right': 2}
4)对epoch进行叠加平均得到evoked对象;
evoked = epochs.average()
叠加平均
evoked.plot(time_unit='s')
plt.show()
5)绘制evoked。
脑机接口MNE工具箱epoch基础知识点
(3)-数据结构Epoch及其创建方法
epochs 定义
从连续的脑电图信号中提取一些特定时间窗口的信号,这些时间窗口可以称作为epochs.
由于EEG是连续收集的,要分析脑电事件相关的电位时,需要将信号"切分"成时间片段,这些时间片段被锁定到某个事件(例如刺激)中的时间片段。
举个例子
假设我们有一个长度为60s的信号x,采样频率为1 Hz.
脑电信号的矩阵表示为1x60矩阵,
如果将信号划分成一些2s的信号,则将有30个peoch(信号中每2s就是一个epoch)
在MNE中,Epoch对象是一种把连续型数据作为时间段集合的表示方法,
形状为(n_events,n_channels,n_times)的数组形式:
(n_events,n_channels,n_times)
创建Epochs对象方式有三种:
(1)通过Raw对象和事件事件点(event times)
(2)通过读取.fif文件数据生成Epoch对象
a. 读取fif文件创建Epoch对象
1)读取fif文件,构建raw对象;
raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
2)创建event对象;
event_id, tmin, tmax = 1, -0.2, 0.5
为什么event id = 1
events = mne.read_events(event_fname)
event_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw-eve.fif'
3)创建epoch对象;
epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
picks=picks, baseline=(None, 0), preload=True,
reject=dict(grad=4000e-13, mag=4e-12, eog=150e-6))
mne.Epochs
b.查看epoch对象
print(epochs.events[:3])
[[27977 0 2]
[28771 0 1]
[29652 0 2]]
打印前三个
print(epochs.event_id)
{'Auditory/Left': 1, 'Auditory/Right': 2}
4)对epoch进行叠加平均得到evoked对象;
evoked = epochs.average()
叠加平均
evoked.plot(time_unit='s')
plt.show()
5)绘制evoked。
(3)通过mne.EpochsArray从头创建Epoch对象
(14)-Epoch对象中的元数据(metadata)
有时候使用mne的metadata属性来存储相关数据特别有用,metadata使用pandas.DataFrame来封装数据。
其中每一行对应一个epoch,
每一列对应一个epoch的元数据属性。
列必须包含字符串、整数或浮点数。
案例
#导入工具
import mne
import numpy as np
import matplotlib.pyplot as plt
#加载数据
path = mne.datasets.kiloword.data_path() + '/kword_metadata-epo.fif'
从上面打印的信息可以知道该数据集中有960个样本数据。
epochs = mne.read_epochs(path)
epochs.metadata[:10]
# 元数据以panda.DataFrame的形式存储数据
# 获取前10条记录
print(epochs.metadata.head(10))
我们可以使用该元数据属性来选择epoch的子集。
这使用了Pandas中的pandas.DataFrame.query()方法。
任何有效的查询字符串都将起作用。
"""
注意,传统的epoch子选择仍然有效。
传统的选择epoch的MNE方法将取代丰富的元数据查询。
"""
epochs['cent'].average().plot(show=False, time_unit='s')
plt.show()
下面,我们将绘制两个图进行比较:
av1 = epochs['Concreteness < 5 and WordFrequency < 2'].average()
av2 = epochs['Concreteness > 5 and WordFrequency > 2'].average()
joint_kwargs = dict(ts_args=dict(time_unit='s'),
topomap_args=dict(time_unit='s'))
av1.plot_joint(show=False, **joint_kwargs)av2.plot_joint(show=False, **joint_kwargs)
plt.show()
子主题 1
子主题 2
Concreteness < 5 and WordFrequency < 2
words = ['film', 'cent', 'shot', 'cold', 'main']
epochs['WORD in {}'.format(words)].plot_image(show=False)
plt.show()
下面将展示一个更复杂的示例,该示例利用每个epoch的元数据。
我们将在元数据对象中创建一个新列,并使用它生成许多试验子集的平均值。
metadata = epochs.metadatais
_concrete = metadata["Concreteness"] > metadata["Concreteness"].median()
metadata["is_concrete"] = np.where(is_concrete, 'Concrete', 'Abstract')
is_long = metadata["NumberOfLetters"] > 5
metadata["is_long"] = np.where(is_long, 'Long', 'Short')
epochs.metadata = metadata
"""
现在我们可以快速提取(并绘制)数据的子集。
例如,看按单词长度和具体程度划分的单词:
"""
query = "is_long == '{0}' & is_concrete == '{1}'"evokeds = dict()
for concreteness in ("Concrete", "Abstract"):
for length in ("Long", "Short"):
subset = epochs[query.format(length, concreteness)]
evokeds["/".join((concreteness, length))] = list(subset.iter_evoked())
# 为了进行实际的可视化,下面存储了许多共享参数。
style_plot = dict(
colors={"Long": "Crimson", "Short": "Cornflowerblue"},
linestyles={"Concrete": "-", "Abstract": ":"},
split_legend=True,
ci=.68,
show_sensors='lower right',
legend='lower left',
truncate_yaxis="auto",
picks=epochs.ch_names.index("Pz"),)fig, ax = plt.subplots(figsize=(6, 4))
共享参数。
#绘制诱发响应
mne.viz.plot_compare_evokeds(evokeds, axes=ax, **style_plot)
plt.show()
下面比较不同字母长度(字母个数)单词所得到的诱发响应。
比较长度为4、5、6、7或8个字母的单词:
letters = epochs.metadata["NumberOfLetters"].unique().astype(int).astype(str)evokeds = dict()
for n_letters in letters:
evokeds[n_letters] = epochs["NumberOfLetters == " + n_letters].average()
style_plot["colors"] = {n_letters: int(n_letters)
for n_letters in letters}style_plot["cmap"] = ("# of Letters", "viridis_r")
del style_plot['linestyles']
fig, ax = plt.subplots(figsize=(6, 4))
mne.viz.plot_compare_evokeds(evokeds, axes=ax, **style_plot)
plt.show()
最后,对于字母的具体性与连续长度之间的相互作用:
evokeds = dict()
query = "is_concrete == '{0}' & NumberOfLetters == {1}"
for concreteness in ("Concrete", "Abstract"):
for n_letters in letters:
subset = epochs[query.format(concreteness, n_letters)]
evokeds["/".join((concreteness, n_letters))] = subset.average()
style_plot["linestyles"] = {"Concrete": "-", "Abstract": ":"}
fig, ax = plt.subplots(figsize=(6, 4))
mne.viz.plot_compare_evokeds(evokeds, axes=ax, **style_plot)
plt.show()
(15)-Epochs数据可视化
高级matplotlib函数
所有方法均返回matplotlib图形实例的句柄。
mne.Epochs.plot()提供了一个交互式浏览器,
当与关键字block = True结合使用时,允许手动拒绝。
这将阻止脚本执行,直到关闭浏览器窗口。
# 加载工具包
import os.path as op
import mne
import matplotlib.pyplot as plt
# 加载数据
"""加载数据,如果本地无该数据,则从网络中下载"""
data_path = op.join(mne.datasets.sample.data_path(), 'MEG', 'sample')
raw = mne.io.read_raw_fif( op.join(data_path, 'sample_audvis_filt-0-40_raw.fif'), preload=True)
# 设置
event IDevent_id = {'auditory/left': 1, 'auditory/right': 2, 'visual/left': 3,'visual/right': 4, 'smiley': 5, 'button': 32}
events = mne.find_events(raw)
epochs = mne.Epochs(raw, events, event_id=event_id, tmin=-0.2, tmax=.5,
preload=True)
del raw
# 绘制
epochsepochs.plot(block=True)
plt.show()
顶部的数字表示epoch的事件id。
事件id?
底部的数字是各个epoch的运行编号。
因为这里没有进行人为的伪影校正或剔除,所以有一些epoch数据被眨眼和扫视污染。
例如,epoch 1似乎被眨眼污染了(滚动到底部查看EOG通道)。
单击浏览器窗口的顶部可以将这个epoch标记为剔除。
单击时,epoch应该变为红色。
这意味着当浏览器窗口关闭时,它将被删除。
通过将events关键字传递给epochs绘图仪,可以在epoched数据上绘制事件标记。
这些事件被绘制为竖线,它们遵循与mvc.viz.plot_events()相同的着色方案。
由于颜色相同,事件绘图仪也可以作为epochs绘图仪事件的图例。
也可以通过event_colors关键字传递自己的颜色。
颜色
在这里,我们可以绘制看到笑脸和按下按钮之间的反应时间(事件32)。
在传递事件时,默认情况下,底部的epoch编号是关闭的,以避免重叠。
你可以通过设置对话框通过按o键重新打开它。
# 绘制
eventsevents = mne.pick_events(events, include=[5, 32])
mne.viz.plot_events(events)
# 绘制smiley的epochs
epochs['smiley'].plot(events=events)
plt.show()
为了一眼就可以看到所有的epoch,可以使用函数mne.epoch.plot_image()将各个通道绘制为图像。
它可以显示信号在所有时间点上的振幅,加上激活的平均(诱发响应)。
显式地将交互式colorbar设置为on
(默认情况下也是on,以使用除topo绘图以外的colorbar来绘制函数)。
在交互模式下,可以通过鼠标滚动和上下箭头键来缩放和改变颜色图。
也可以用鼠标左右拖动颜色栏。
按下空格键则会重置比例。
epochs.plot_image(278,
cmap='interactive',
sigma=1.,
vmin=-250, vmax=250)
No baseline correction applied
Not setting metadata
0 projection items activated
0 bad epochs dropped
这里还可以通过计算全局字段功率(或其他聚合方法)来概述所有通道。
但是,以这种方式组合多个通道类型(例如MEG和EEG)是不明智的,
因此,如果未指定特定的通道选择,默认情况下plot_image()方法将为每个通道类型生成单独的图形。
epochs.plot_image(combine='gfp', sigma=2., cmap="YlGnBu_r")
为每个通道类型生成单独的图形。
mne.epoch.plot_image()
这里还可以使用一些函数来绘制按通道排列成通道阵列形状的信息。
图像绘制时默认情况下使用自动缩放,但是有噪声的通道和不同的通道类型会导致缩放有点小。
epochs.plot_topo_image(vmin=-250,
vmax=250,
title='ERF images',
sigma=2.,
fig_facecolor='w',
font_color='k')
(16)-脑电数据的Epoching处理
首先,读入原始样本数据:
data_path = mne.datasets.sample.data_path()
fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(fname)
# 设置EEG 平均参考
raw.set_eeg_reference('average', projection=True)
要创建时间锁定的epochs,首先需要一组包含有关时间信息的事件。
这里使用刺激通道来定义事件。
order = np.arange(raw.info['nchan'])
注意底部的STI 014频道。
触发通道用于将所有事件合并到单个通道中。
触发通道
从上图上可以看到在整个记录中有几个振幅不同的脉冲。
这些脉冲对应于在采集过程中呈现给受试者的不同刺激。
脉冲的值为1、2、3、4、5和32。
"""交换两个通道的绘图顺序,以将触发通道显示为第十个通道。"""
order[9] = 312
order[312] = 9
raw.plot(n_channels=10, order=order, block=True)
plt.show()
要从原始数据创建事件列表,MNE中只需调用一个专门用于此目的的函数。
由于事件列表只是一个numpy数组,所以也可以手动创建一个。
如果是从外部源(如单独的事件文件)创建事件,则应注意将事件与原始数据正确对齐。
events = mne.find_events(raw)
print('Found %s events, first five:' % len(events))
print(events[:5])
时间锁定的epochs
"""绘制事件以了解范例为图例指定颜色和event_id字典。"""
event_id = {'Auditory/Left': 1, 'Auditory/Right': 2,
'Visual/Left': 3, 'Visual/Right': 4,
'smiley': 5, 'button': 32}
color = {1: 'green', 2: 'yellow', 3: 'red', 4: 'c', 5: 'black', 32: 'blue'}
mne.viz.plot_events(events, raw.info['sfreq'], raw.first_samp, color=color,
event_id=event_id)
plt.show()
320 events found
Event IDs: [ 1 2 3 4 5 32]
Found 320 events, first five:
[[27977 0 2]
[28345 0 3]
[28771 0 1]
[29219 0 4]
[29652 0 2]]
如上面所示,事件列表包含三列。
第一列对应于样本编号,要将此转换为秒,可以将采样数除以使用的采样频率。
第二列是在转换时保留给触发器通道的旧值,目前没有使用。
第三列是触发ID(脉冲幅度)。
这里说明一下为什么这些样本看起来与绘制的数据不一致。
例如,第一个事件的样本编号为27977,应该转换为大约46.6秒(27977 / 600)。
但是查看脉冲时,可以在3.6秒时看到第一个脉冲。
这是因为Neuromag记录有一个first_samp属性,它表示系统启动和录制开始之间的偏移量。
Neuromag记录数据的first_samp等于25800。
这意味着使用raw.plot看到的第一个样本的样本号25800。
一般来说,在使用时不需要担心这个偏移量,因为它在MNE函数中已经被考虑进去了,不过最好要注意这一点。
为了确认一下,我们将事件与原始数据一起绘制。
raw.plot(events=events, n_channels=10, order=order)
plt.show()
注意垂直线(事件)如何与STI 014上的脉冲很好地对齐。
这些触发器对应于听觉和视觉刺激。
这里定义了阈值来去除噪声。
阈值被定义为epoch时间窗口内的峰到峰的值。
定义为T/m表示梯度计,
T表示磁强计,
V表示EEG和EOG电极。
阈值
epochs = mne.Epochs(raw, events=events,
event_id=event_id, tmin=tmin,
tmax=tmax, baseline=baseline,
reject=reject,
picks=('meg', 'eog'))
baseline = (None, 0.0)
为了得到一些有意义的结果,我们还希望将这些epochs作为基线。
基线化计算基线期间的平均值并相应地调整数据。
epochs构造函数默认使用从tmin到0.0秒的基线周期,作为元组的第一个元素的None指的是时间窗口的开始(本例中为-200 ms)
reject = {'mag': 4e-12, 'eog': 200e-6}
# 这里只包含 MEG 和 EOG
reject 拒绝
tmin, tmax = -0.2, 0.5
event_id = {'Auditory/Left': 1, 'Auditory/Right': 2,
'Visual/Left': 3, 'Visual/Right': 4}
首先,我们为mne.Epochs构造函数定义一些参数,tmin和tmax指的是与事件相关的偏移量,并使用epoch来封装事件前200毫秒到事件后500毫秒的数据。
这里的event_id可以是int、int列表或dict。
使用dict可以将这些id分配给不同的类别。
dict
当使用int或列表时,这个信息就会丢失。
int
289 matching events found
Applying baseline correction (mode: mean)
Not setting metadata
Created an SSP operator (subspace dimension = 3)
4 projection items activated
baseline
SSP
epochs.plot(block=True)
plt.show()
下面就绘制epochs查看结果。
顶部的数字是指ID号,从图中可以看到145个事件中有128个通过了rejection process。
rejection process
epochs.plot_drop_log()
plt.show()
通过绘制drop日志,来查看为什么剔除epoch(一般被伪影等污染的epoch数据需要被剔除)。
picks = mne.pick_types(epochs.info, meg=True, eog=True)
evoked_left = epochs['Auditory/Left'].average(picks=picks)evoked_right = epochs['Auditory/Right'].average(picks=picks)
要获得诱发响应,只需执行epoch.average()。
它默认只包含数据通道。
为了便于举例,我们还使用pick来包含EOG通道。
epochs['Auditory/Left']
注意,这里使用了前斜杠('/')来分隔实验条件的各个因素。
epochs_left = epochs['Left']
# ... or to select a very specific subset.
This is the same as above:
evoked_left_again = epochs['Left/Auditory'].average(picks=picks)
我们可以使用这些“标签”来选择例如所有左侧试验(包括视觉左侧和听觉右侧)。
下面绘制诱发响应
evoked_left.plot(time_unit='s')
evoked_right.plot(time_unit='s')
plt.show()
整理自【脑机接口社区】的几篇文章: