[原创]基于EDFlib/C++实现脑电数据EDF标准格式读写

2019-04-13 15:01发布

一、关于EDF(European Data Format)格式的介绍

    欧洲数据格式(EDF)是一种用于交换和存储多通道生物和物理信号的简单而灵活的格式。它是由几位在哥本哈根举行的1987年国际睡眠大会上首次会见的欧洲“医疗”工程师开发的。 EDF标志来源于丹麦作家汉斯·克里斯蒂安·安德森的童话故事“公主和豌豆”中的绿豆。在奥尔堡大学已故教授Annelise Rosenfalck的支持下,工程师们启动了由欧洲共同体通过其“Comitéd'ActionConcertée”资助的“睡眠 - 醒觉连续性分析方法学”项目(1989-1992) “(COMAC委员会)生物医学工程。他们希望将睡眠分析算法应用于其他数据并比较分析结果。因此,1990年3月在莱顿的一个早晨,他们就一个非常简单的文件格式达成一致,以交换他们的睡眠记录。这种格式被称为欧洲数据格式。 1990年8月,所有参与实验室都为该项目提供了EDF睡眠记录。       EDF于1992年发表于Electroencephalography and Clinical Neurophysiology 82,第391-393页。 从那时起,EDF成为商业设备和多中心研究项目中EEG和PSG记录的事实标准。     EDF的扩展名为EDF +,于2002年开发出来,与EDF基本兼容:所有现有的EDF阅读器也显示EDF +信号。 但EDF +文件也可能包含中断录制,注释,刺激和事件。 因此,EDF +可存储任何医疗记录,如肌电图,诱发电位,心电图,以及自动和手动分析结果,如δ图,QRS参数和睡眠阶段。 规格比EDF更严格,可以自动定位和校准电极。 EDF +修正了EDF中的一些遗漏问题,例如Y2K问题,小端整数和逗号对点。     EDF +于2003年在Clinical Neurophysiology 114,第1755-1761页发表。 从那以后,数百个EDF +文件和几个EDF +查看器在互联网上出现。 应用主要在临床神经生理学,睡眠和心脏病学。 来自其他专业的正式标准也可以整合到EDF +中。 大多数EDF应用程序已迁移到EDF +。 相关链接:支持EDF数据存取的软/硬件设备公司 EDF存储定义:Full specifications of EDF and EDF+  EEG其他常用格式:.eeg/.cnt/  [其它] 心理学MATLAB初学者教程 -- 脑电数据读取

二、EDF开源阅读器推荐

EDF相关的信号显示软件很多,推荐一款开源软件 EDFbrowser A free, opensource, multiplatform, universal viewer and toolbox intended for, but not limited to, timeseries storage files like EEG, EMG, ECG, BioImpedance, et

三、EDF文件读写开源库EDFlib

支持EDF+,支持c++/python/java/labview https://www.teuniz.net/edflib/index.html  

四、EDFlib使用步骤

1、预备工作

1.1、将“edflib.h”“edflib.c”两个文件添加到工程 1.2、#include “edflib.h” 1.3、c++工程需要参考([填坑]VS环境下C语言与C++混合编译),解决c++与c混合编译的问题  

2、编程示例

2.1、创建一个文件 hdl = edfopen_file_writeonly("test_generator.edf", EDFLIB_FILETYPE_EDFPLUS, chns); //chns通道数 2.2、设置参数  1、设置各通道采样频率    edf_set_samplefrequency(hdl, i, SMP_FREQ) //设置第i个通道 2、设置各通道数字范围(digital)   edf_set_digital_maximum(hdl, i, 32768) //EDF最大支持16位 BDF支持24位 edf_set_digital_minimum(hdl, i, -32768) 3、设置各通道物理范围   edf_set_physical_maximum(hdl, i, 1000.0) edf_set_physical_minimum(hdl, i, 1000.0) 4、设置各通道物理单位   edf_set_physical_dimension(hdl, i, "uV") edf_set_physical_dimension(hdl, 13, "V") 5、设置各通道lable   edf_set_label(hdl, i++, "squarewave") 6、设置被试信息 姓名、性别、生日、编号 7、操作员信息 8、写入设备信息 9、按顺序写入数据 int edfwrite_digital_samples(int handle, int *buf); //按数字量写入 Writes n "raw" digital samples from *buf belonging to one signal where n is the samplefrequency of the signal. The 16 (or 24 in case of BDF) least significant bits of the sample will be written to the file without any conversion. The number of samples written is equal to the samplefrequency of that signal. Size of buf should be equal to or bigger than sizeof(int[samplefrequency]). Call this function for every signal in the file. The order is important! When there are 4 signals in the file, the order of calling this function must be: signal 0, signal 1, signal 2, signal 3, signal 0, signal 1, signal 2, etc. Returns 0 on success, otherwise -按数字量写入 Writes n "raw" digital samples from *buf belonging to one signal where n is the samplefrequency of the signal. The 16 (or 24 in case of BDF) least significant bits of the sample will be written to the file without any conversion. The number of samples written is equal to the samplefrequency of that signal. Size of buf should be equal to or bigger than sizeof(int[samplefrequency]). Call this function for every signal in the file. The order is important! When there are 4 signals in the file, the order of calling this function must be: signal 0, signal 1, signal 2, signal 3, signal 0, signal 1, signal 2, etc. Returns 0 on success, otherwise - int edfwrite_physical_samples(int handle, double *buf); //按物理量写入,内部自动转换为数字量 Writes n physical samples (uV, mA, Ohm) from *buf belonging to one signal where n is the samplefrequency of the signal. The physical samples will be converted to digital samples using the values of physical maximum, physical minimum, digital maximum and digital minimum.按物理量写入,内部自动转换为数字量 Writes n physical samples (uV, mA, Ohm) from *buf belonging to one signal where n is the samplefrequency of the signal. The physical samples will be converted to digital samples using the values of physical maximum, physical minimum, digital maximum and digital minimum. int edf_blockwrite_digital_samples(int handle, int *buf);// 一次写入一秒数据   Writes "raw" digital samples from *buf.   buf must be filled with samples from all signals, starting with n samples of signal 0, n samples of signal 1, n samples of signal 2, etc.   where n is the samplefrequency of the signal.   One block equals one second. 一次写入一秒数据   Writes "raw" digital samples from *buf.   buf must be filled with samples from all signals, starting with n samples of signal 0, n samples of signal 1, n samples of signal 2, etc.   where n is the samplefrequency of the signal.   One block equals one second. 10、加入事件 int edfwrite_annotation_latin1(int handle, long long onset, long long duration, const char *description); Writes an annotation/event to the file. onset is relative to the starttime and startdate of the file. onset and duration are in units of 100 microSeconds! resolution is 0.0001 second! For example: 34.071 seconds must be written as 340710. If duration is unknown or not applicable: set a negative number (-1). description is a null-terminated Latin1-string containing the text that describes the event. This function is optional and can be called only after opening a file in writemode and before closing the file34.071 seconds must be written as 340710. If duration is unknown or not applicable: set a negative number (-1). description is a null-terminated Latin1-string containing the text that describes the event. This function is optional and can be called only after opening a file in writemode and before closing the file 11、关闭文件 edfclose_file(hdl); Closes the file. Returns -1 in case of an error, 0 on success.

 五、实例

/* this program generates an EDFplus or BDFplus testfile with the following signals: signal label/waveform amplitude f sf ------------------------------------------------------ 1 squarewave 100 uV 0.1Hz 200 Hz 2 ramp 100 uV 1 Hz 200 Hz 3 pulse 1 100 uV 1 Hz 200 Hz 4 pulse 2 100 uV 1 Hz 256 Hz 5 pulse 3 100 uV 1 Hz 217 Hz 6 noise 100 uV - Hz 200 Hz 7 sine 1 Hz 100 uV 1 Hz 200 Hz 8 sine 8 Hz + DC 100 uV 8 Hz 200 Hz 9 sine 8.1777 Hz + DC 100 uV 8.25 Hz 200 Hz 10 sine 8.5 Hz 100 uV 8.5Hz 200 Hz 11 sine 15 Hz 100 uV 15 Hz 200 Hz 12 sine 17 Hz 100 uV 17 Hz 200 Hz 13 sine 50 Hz 100 uV 50 Hz 200 Hz 14 DC event 8-bits code 1 V 100 mS/bit 200 Hz */ #define SMP_FREQ 200 #define SMP_FREQ_2 256 #define SMP_FREQ_3 217 #define FILE_DURATION 600 #define M_PI 3.14159265358979323846 // Uncomment the next line to create a BDF+ file instead of EDF+: // #define BDF_FORMAT int main(void) { int i, j, hdl, chns; double buf[1000], q, sine_1, sine_8, sine_81777, sine_85, sine_15, sine_17, sine_50; struct{ long long samples; long long triggers[512]; int index; int code; int bitposition; int smp_in_bit; } dc_event_stat; memset(&dc_event_stat, 0, sizeof(dc_event_stat)); dc_event_stat.code = 0; dc_event_stat.triggers[0] = 1951; for(i=1; i<512; i++) { dc_event_stat.triggers[i] = (i * 1667) + 1951; } chns = 14; #ifdef BDF_FORMAT hdl = edfopen_file_writeonly("test_generator.bdf", EDFLIB_FILETYPE_BDFPLUS, chns); #else hdl = edfopen_file_writeonly("test_generator.edf", EDFLIB_FILETYPE_EDFPLUS, chns); #endif if(hdl<0) { printf("error: edfopen_file_writeonly() "); return(1); } for(i=0; i= 10) { dc_event_stat.smp_in_bit = 0; dc_event_stat.bitposition++; } if(dc_event_stat.bitposition > 10) { dc_event_stat.bitposition = 0; dc_event_stat.smp_in_bit = 0; dc_event_stat.code++; dc_event_stat.code &= 255; if(++dc_event_stat.index >= 512) { dc_event_stat.index = 0; dc_event_stat.code = 0; } } } else { if(dc_event_stat.samples == dc_event_stat.triggers[dc_event_stat.index]) { /* edfwrite_annotation_latin1(hdl, dc_event_stat.samples * 10LL, -1LL, "Trigger"); */ dc_event_stat.bitposition = 1; dc_event_stat.smp_in_bit = 1; buf[i] = 1.0; } else { buf[i] = 0.0; } } dc_event_stat.samples++; } if(edfwrite_physical_samples(hdl, buf)) { printf("error: edfwrite_physical_samples() "); return(1); } } edfwrite_annotation_latin1(hdl, 0LL, -1LL, "Recording starts"); edfwrite_annotation_latin1(hdl, 2980000LL, -1LL, "Test 1"); edfwrite_annotation_latin1(hdl, 2940000LL + (long long)((10000.0 / SMP_FREQ) * (SMP_FREQ - 2)), -1LL, "pulse 1"); edfwrite_annotation_latin1(hdl, 2950000LL + (long long)((10000.0 / SMP_FREQ_2) * (SMP_FREQ_2 - 2)), -1LL, "pulse 2"); edfwrite_annotation_latin1(hdl, 2960000LL + (long long)((10000.0 / SMP_FREQ_3) * (SMP_FREQ_3 - 2)), -1LL, "pulse 3"); edfwrite_annotation_latin1(hdl, FILE_DURATION * 10000LL, -1LL, "Recording ends"); edfclose_file(hdl); return(0); }