ECG 数据库介绍

现有心电数据集

一、四大数据库概述

目前国际上最重要的,具有权威性的心电数据库有四个:
美国麻省理工学院与Beth Israel医院联合建立的MIT-BIH心电数据库;
美国心脏学会的AHA心律失常心电数据库;
欧盟的CSE心电数据库;
欧盟ST-T心电数据库。
除此之外国际上被广泛认可的还有Sudden Cardiac DeathHolter Database,PTB Diagnostic ECG Database,PAF Prediction ChallengeDatabase等心电数据库。

1、美国的MIT-BIH心电数据库

MIT-BIH Arrhythmia Database
诊断: Arrhythmia(心律失常)

采样率:360 Hz

分辨率:11 bit

导联:2

每段数据持续时间:30+ min

储存格式:Format 212

23条记录源自住院病人,25条记录源自罕见但是临床上很重要。48条记录每一条记录都超过了30min。
106号 异位节拍更加突出
114号 记录被逆转
102,104,107,217这些记录源自佩戴有起搏器的患者,由于起搏器节律与窦性节律接近,因此会出现许多起搏器融合的情况。但是官网表示,即使有一些肌肉噪声,这些信号的质量依然很好。
108——The lower channel exhibits considerable noise and baseline shifts.有相当大的噪声和基线漂移
111——偶然的肌肉噪声和基线漂移,但是大体上信号质量极好
113——The variation in the rate of normal sinus rhythm is possibly due to a wandering atrial pacemaker.窦性心律正常率的变化可能是由于心房起搏器的漂移。
114——PVC是统一的
118119——PVC是多样的
122——The lower channel has low-amplitude high-frequency noise throughout.低振幅高频率噪声
200——上通道偶尔有高频噪声,下通道有严重的噪声和伪影
203——There are QRS morphology changes in the upper channel due to axis shifts. There is considerable noise in both channels, including muscle artifact and baseline shifts. This is a very difficult record, even for humans!由于轴移位,QRS发生改变,两个通道都有相当大的噪声,包括肌肉和基线漂移。
207——This is an extremely difficult record. … The record ends during the episode of SVTA.在最长的心室扑动发作后出现室性心律。记录在 SVTA 的情节期间结束。
214——有两次假振幅降低和一次磁带打滑
215——有<1s的两次磁带打滑
219——Following some conversions from atrial fibrillation to normal sinus rhythm are pauses up to 3 seconds in duration.房颤转正常之后,暂停时长有3s。
222——两个通道都含有高频噪声和伪影
228——胶带有三次短暂滑动,最长2.2s

做去噪算法常用的几条记录:
100/101/103/105/106/115/215
MIT-BIH ST Change Database

诊断: Recorded during exercise stress tests and which exhibit transient ST depression(在运动压力测试期间记录,并显示暂时性ST凹陷)

采样率:360 Hz

分辨率:12 bit

导联:2

每段数据持续时间:varying lengths

储存格式:Format 212
MIT-BIH Atrial Fibrillation Database
诊断: Atrial fibrillation (mostly paroxysmal)(阵发房颤)

采样率:250 Hz

分辨率:12 bit

导联:2

每段数据持续时间:10 h

储存格式:Format 212

网址:http://ecg.mit.edu/

MIT-BIH Noise Stress Test Database
12条半小时的ECG记录,3条半小时的加入了典型噪声的记录。
其中118,119是两条干净的记录。通过加入校准的噪声量制作噪声记录。信噪比见下表。

原始的记录是无噪声的,新纪录的注释也源自原始的数据。

2、AHA心律失常心电数据库

由美国国家心肺及血液研究院资助的美国心脏协会(American HeartAssociation,AHA)开发了AHA心律失常心电数据库,该数据库的开发目的是评价室性心律不齐探测器的检测效果。

诊断:

No ventricular ectopy (records 1001 through 1010)

Isolated unifocal PVCs (records 2001 through 2010)

Isolated multifocal PVCs (records 3001 through 3010)

Ventricular bi- and trigeminy (records 4001 through 4010)

R-on-T PVCs (records 5001 through 5010)

Ventricular couplets (records 6001 through 6010)

Ventricular tachycardia (records 7001 through 7010)

Ventricular flutter/fibrillation (records 8001 through 8010)

采样率:250 Hz

分辨率:12 bit

网址:https://www.ecri.org/Products/Pages/AHA_ECG_DVD.aspx
3、欧盟CSE数据库

欧盟的CSE(Common Standards for Electrocardiography,心电图通用标准)心电数据库包含1000例短时间的心电记录,采用12或15导联,主要开发目的是用于评价心电图自动分析仪的性能。

e-mail: [email protected]

4、欧盟ST-T数据库

欧盟的ST-T数据库是由欧洲心脏病学会(European Society ofCardiology)开发的,用于评价ST段和T波检测算法性能的数据库。

诊断: 每个受试者都被诊断或怀疑有心肌缺血。建立了额外的选择标准,以便在数据库中获得代表性的 ECG 异常选择,包括由高血压、心室运动障碍和药物影响等疾病导致的基线 ST 段位移。

采样率:250 Hz

分辨率:12 bit

导联:2

每段数据持续时间:2 h

储存格式:Format 212

网址:http://www.escardio.org/Pages/index.aspx

心脏性猝死动态心电数据库
据估计,在世界范围内,每年有400000人,还有上百万的儿童猝死,所以PhysioNet举行了心脏性猝死的数据库建设,支持和刺激这一重要领域的电生理研究。

诊断: 18 patients with underlying sinus rhythm (4 with intermittent pacing), 1 who was continuously paced, and 4 with atrial fibrillation. All patients had a sustained ventricular tachyarrhythmia, and most had an actual cardiac arrest.

采样率:250 Hz

分辨率:12 bit

导联:2

每段数据持续时间:30 min

储存格式:Format 212

网址:http://physionet.org/physiobank/database/sddb/

PTB 心电诊断数据库:

德国国家计量署提供的数字化心电数据库,其目的在于算法标准的研究与教学。数据来自柏林的本杰明富兰克林医学大学的心脏内科。

采样率:1000Hz

分辨率:16 bit (± 16.384 mV)

导联:16(14 通道心电信号,1通道呼吸,1通道电压)

每段数据持续时间:varying lengths(大多2 min)

储存格式:Format 16

网址:https://archive.physionet.org/cgi-bin/atm/ATM

PAF 预测挑战数据库
The PAF Prediction Challenge Database来自2001年针对自动预测阵发性心房纤颤/颤振(predicting paroxysmal atrial fibrillation , PAF)的开放性竞赛。竞赛的意义是刺激并促进美国在这个重大临床问题上的探索和培养友好竞争和广泛合作的环境。

诊断: paroxysmal atrial fibrillation

采样率:128Hz

分辨率:16 bit

导联:2

每段数据持续时间:5 min / 30 min

储存格式:Format 16

网址:http://physionet.org/challenge/2001/
以上数据库数据共分两种保存格式,即WFDB signal files Format 212和WFDB signal files Format 16

MIT数据集&&PTB数据集

WFDB读取心电数据(针对MIT-BIH)

.hea文件格式

100 2 360 650000
100.dat 212 200 11 1024 995 -22131 0 MLII
100.dat 212 200 11 1024 1011 20052 0 V5
# 69 M 1085 1629 x1
# Aldomet, Inderal

导联分布情况
ECG 数据库介绍_第1张图片
表示含义

100(文件名) 2(通道数/导联数 MLII和V5两路导联信号组成) 360(采样率是360Hz) 650000(每路信号长度为650000个采样点)
100.dat(信号存储在100.dat文件中) 
212(信号以212格式存储,针对两个信号的数据库记录,每三个字节(24bit=6个16进制的数字)存储两个数据(两路信号分别占一个)) 
200(表示每个信号的增益都是每200ADC units/mV) 
11(ADC的分辨率是11位) 
1024(ADC的零值为1024) 
995/1011(不同信号的第一采样点的值) 
-22131/20052(65万个采样点的校验数) 
0(表示输入和输出可以以任意尺寸的块来执行) 
MLII/V5(信号描述字段,表示信号采自那个导联)

    # 读取心电信号文件
    # sampfrom: 设置读取心电信号的 起始位置,sampfrom=0表示从0开始读取,默认从0开始
    # sampto:设置读取心电信号的 结束位置,sampto = 1500表示从1500出结束,默认读到文件末尾
    # channel_names:设置设置读取心电信号名字,必须是列表,channel_names=['MLII']表示读取MLII导联线
    # channels:设置读取第几个心电信号,必须是列表,channels=[0, 3]表示读取第0和第3个信号,注意信号数不确定
    record = wfdb.rdrecord('../ecg_data/102', sampfrom=0, sampto = 1500) # 读取所有通道信号
    # record = wfdb.rdrecord('../ecg_data/203', sampfrom=0, sampto = 1500,channel_names=['MLII']) # 仅仅读取“MLII”信号
    record = wfdb.rdrecord('../ecg_data/101', sampfrom=0, sampto=3500, channels=[0]) # 仅仅读取第0个信号(MLII)
    print(type(record)) # 查看record类型
    print(dir(record)) # 查看类中的方法和属性
    print(record.p_signal) # 获得心电导联线信号,本文获得是MLII和V1信号数据
    print(record.n_sig) # 查看导联线条数
    print(record.sig_name) # 查看信号名称(列表),本文导联线名称['MLII', 'V1']
    print(record.fs) # 查看采用率
    
    # 读取注解文件
    # sampfrom: 设置读取心电信号的 起始位置,sampfrom=0表示从0开始读取,默认从0开始
    # sampto:设置读取心电信号的 结束位置,sampto = 1500表示从1500出结束,默认读到文件末尾
    print(type(annotation)) # 查看annotation类型
    print(dir(annotation))# 查看类中的方法和属性
    print(annotation.sample) # 标注每一个心拍的R波的尖锋位置,与心电信号对应
    # annotation.symbol  #标注每一个心拍的类型N,L,R等等
    print(annotation.ann_len) # 被标注的数量
    print(annotation.record_name) # 被标注的文件名
    print(wfdb.show_ann_labels()) # 查看心拍的类型

数据集处理

MIT数据集V5导联(并非常用的MLII)

"""
    本程序用于重新预处理raw ecg以获取更短的seg和更精确的label描述
"""
import pandas as pd
# from config import slice_window, slice_stride, RpreDistance, RpostDisance
import numpy as np
import os
import wfdb
import pickle
import gzip
import re
#当前路径下所有子目录
''':type
file_path = '../records100/'
pathname
filename = []
for root, dirs, files in os.walk(file_path):
    for file in files:
        if file.endswith(".dat"):
            # print(os.path.join(root, file))
            filename.append(file.split(".")[0])
            # print(file)
print(filename)

'''
file_path = '../records100/'
pathname=[]
filename = []
for root, dirs, files in os.walk(file_path):
    for file in files:
        if file.endswith(".dat"):
            # print(os.path.join(root, file))
            # pathname.append(root)
            filename.append(os.path.join(root, file))
            # print(file)
            # pathname.append(filename.split(".")[0])
# print(filename)
number03 = []
for i in filename:
    number03.append(i.split(".dat")[0])
# print(number03)
# all_files =os.listdir(file_path)
# print(all_files)
# filenames = []
# for dirs in os.walk(file_path):
#     for file in dirs:
#         if file.endswith(".dat"):
#             filenames.append(file)


    # print(dirs)

# print(filenames)
numberSet = ['100', '101', '103', '105', '106', '108', '109', '111', '112', '113', '114', '115', '116',
             '117', '118', '119', '121', '122', '123', '124', '200', '201', '202', '203', '205', '207',
             '208', '209', '210', '212', '213', '214', '215', '219', '220', '221', '222', '223', '228',
             '230', '231', '232', '233', '234']
numberSet1 = ['123']
numset=[]

# 读取心电数据和对应标签
def getDataSet(number_str):
    # ecgClassSet = ['N', 'A', 'V', 'L', 'R']

    # 读取心电数据记录
    print("正在读取 " + number_str + " 号心电数据...")
    # record = wfdb.rdrecord('C:/Users/MeetT/Desktop/ECG_codes/MIT-BIH/' + number_str, channel_names=['MLII'])


    record = wfdb.rdrecord( number_str, channel_names=['V5'])

    # data = record.p_signal.flatten()  # (650000,)
    data = record.p_signal
    # print(data)

    # rdata = denoise(data=data)

    # 获取心电数据记录中R波的位置和对应的标签
    # annotation = wfdb.rdann('C:/Users/MeetT/Desktop/ECG_codes/MIT-BIH/' + number_str, 'atr')
    # annotation = wfdb.rdann(number_str,'dat')
    # Rlocation = annotation.sample  # (xx,)
    # Rclass = annotation.symbol  # (xx,)
    # print(annotation.symbol)
    #
    # return data, Rlocation, Rclass
    return data

def prepare_df_02(save_path, slice_window=512, slice_stride=512, RpreDistance=115, RpostDisance=144):
    # 115=0.32s,144=0.4s
    # 加载数据集并进行预处理,与old_prepare_df的区别在于采用了新的标签设置方式
    """
    标签设置方式:
    1.截取片段
    2.if seg_label has 'A' ==> label = 'A'
    3.else:
    4.    if d_t <= d_Rpre and d_b <= d_Rpost ==> label='N'
    5.    else:
    6.        截取两侧相邻label
    7.        if 左N右N ==> label = 'N'
    8.        else if 左N右A ==> 向后取一个拍,从当前位置到R_point+d_Rpost , label = 'A'
    9.        else:
    10.           向前取到R_point+d_Rpre, label = 'A'
    """
    slices_list = []
    l = []
    for n_str in number03:
        labels_list = []

        # print(tmp_annot)  ['+', 'L', 'L', 'L', 'L', 'L' 所有记录的第一个标签是+, 对应节律改变,应该属于一种异常,如果必要时把前后5个心拍给删掉
        tmp_data = getDataSet(n_str)  # ndarray,ndarray,list
        slices_list.append(tmp_data) #
        # str(slices_list)
        print("slices_list",slices_list)
        print(len(slices_list))
        np.savetxt('ptb_II.csv',slices_list.reshape(1,-1),delimiter=',')
        #可根据需求取消注释
        # if not os.path.exists(save_path):
        #     os.makedirs(save_path)
        #     np.savetxt( 'ptb_II.csv',slices_list,delimiter=',' )
        #
        #     slices_list = []
        # else:
        #     np.savetxt('ptb_II.csv',slices_list,delimiter=',')
        #     print(slices_list)
        #     slices_list = []
        # for i in slices_list.split(' '):
        #     l.append(i)
        # print(l)
        # print(len(l))
        # print(type(slices_list))
        # print(len(slices_list))
    print(len(slices_list))
        # if len(slices_list)%1 == 0:
        #     if not os.path.exists(save_path):
        #         os.makedirs(save_path)
        #         np.savetxt( 'ptb_II_142.csv',slices_list,delimiter=' ' )
        #
        #         slices_list = []
        #     else:
        #         np.savetxt('ptb_II_142.csv',slices_list,delimiter=' ')
        #         print(slices_list)
        #         slices_list = []

        # print('DataFrame of ' + slices_list + ' patients  is saved.')
        # 写成一个迭代器,保存每个片段在源数据中所在位置的首索引
        # slices_head_index_generator = (x for x in range(0, len(tmp_data), slice_stride)
        #                                if x + slice_window <= len(tmp_data))
        # # 直接二元化处理标签,读取数据及标签直接判定,不再保存片段内的原始标签
        # # labels = [0 if x == 'N' else 1 for x in tmp_annot]
        #
        # for per_slice_head_index in slices_head_index_generator:
        #     # 截取片段:
        #     # 如果最后无法截取完整的心拍就跳过
        #     if per_slice_head_index+slice_window > len(tmp_data):
        #         print(n_str+' has stoped in advance.')
        #         break  # 快要到头了,不再迭代

            # 寻找片段在标签时间中经过排序后在tmp_atrtime(R峰对应的点的位置,有序,不是time)中对应位置的索引
            # first_R_index_in_atrtime = np.searchsorted(tmp_atrtime, per_slice_head_index, side='left')  # 第一个R峰的位置
            # last_R_index_in_Ratrtime = np.searchsorted(tmp_atrtime, per_slice_head_index + slice_window-1, side='right')  # 最后一个R峰再加一个R峰的位置
            #
            # # 考虑到一种情况,就是first_R_index_in_atrtime=0,last_R_index_in_Ratrtime=len(tmp_atrtime),代表离首尾太近取不到完整的seg:跳过本次循环
            # if first_R_index_in_atrtime == 0 or last_R_index_in_Ratrtime >= len(tmp_atrtime):  # 因为atrtime最后一个index是len()-1
            #     continue
            #
            # per_slice = tmp_data[per_slice_head_index:per_slice_head_index + slice_window]
            # # 进入标签设置算法
            # slice_labels = labels[first_R_index_in_atrtime:last_R_index_in_Ratrtime]
            # if 1 in slice_labels:
            #     per_label = 1
            # else:
            #     d_left = tmp_atrtime[first_R_index_in_atrtime] - per_slice_head_index + 1 # RpreDistance >=
            #     # d_left:首元素距离片段内第一个R峰的位置;d_right:末尾元素距离片段内最后一个R峰的位置
            #     d_right = per_slice_head_index + slice_window - 1 - tmp_atrtime[last_R_index_in_Ratrtime-1]  # RpostDisance <=
            #     if d_left <= RpreDistance and d_right <= RpostDisance:
            #         per_label = 0
            #     else:
            #         NN_L_LABEL_NORMAL = labels[first_R_index_in_atrtime - 1] == 0  # 片段外最近的左边的标签
            #         NN_R_LABEL_NORMAL = labels[last_R_index_in_Ratrtime] == 0
            #         if NN_L_LABEL_NORMAL and NN_R_LABEL_NORMAL:
            #             per_label = 0
            #         elif NN_L_LABEL_NORMAL and not NN_R_LABEL_NORMAL:
            #             # 向后取到NN_R_point+R_POST
            #             # 只计算要多余截取的片段至:tmp_atrtime[last_R_index_in_Ratrtime]+R_post 这一点,往前倒推slice_window个
            #             new_slice_end = tmp_atrtime[last_R_index_in_Ratrtime] + RpostDisance + 1  # 加1防止切片取不到
            #             per_slice = tmp_data[new_slice_end - slice_window:new_slice_end]  # 因为切片上限取不到
            #             # 因为d_right已经求出来了,所以这里可以怎么搞?--不搞了,懒得算
            #             per_label = 1
            #         else:
            #             # 向前取到NN_R_point+R_PRE
            #             # 计算要向前多余截取的片段至:tmp_atrtime[first_R_index_in_atrtime-1]-R_pre-1 这一点
            #             new_slice_start = tmp_atrtime[first_R_index_in_atrtime - 1] - RpreDistance
            #             per_slice = tmp_data[new_slice_start:new_slice_start + slice_window]
            #             per_label = 1
            #
            # slices_list.append(per_slice)
            # labels_list.append(per_label)
        # 利用pandas追加写的特点将数据逐条保存至csv; 选择csv的理由是比较简单好操作,很熟悉,而且能打开
        # 为了避免频繁的读写操作,一个病人的所有数据一起写入,包括id,per_slice,per_label; 用存储空间换io次数
        # 生成dataframe保存至csv文件内
        # keep_head = True if n_str == '100' else False
        # df1 = pd.DataFrame({'id': np.array([int(n_str) for _ in range(len(slices_list))])}).astype('category')
        # # df2涉及多个值,需要zip
        # df2 = pd.DataFrame(np.array(slices_list))
        # df3 = pd.DataFrame({'labels': labels_list}).astype('category')  #
        # dfA = pd.concat([df1, df2, df3], axis=1)  # 这是对于一个病人数据的处理

        # 保存dfA
        # if not os.path.exists(save_path):
        #     os.makedirs(save_path)
        #     dfA.to_csv(save_path + 'df.csv', index=False, header=keep_head, mode='w')
        # else:
        #     dfA.to_csv(save_path + 'df.csv', index=False, header=keep_head, mode='a')
        #
        # print('DataFrame of ' + n_str + ' patients  is saved.')



def save_pkl(filename, data, compress=True):
    """ Save dictionary in a pickle file. """
    if compress:
        with gzip.open(filename, 'wb') as fh:
            pickle.dump(data, fh, protocol=4)
    else:
        with open(filename, 'wb') as fh:
            pickle.dump(data, fh, protocol=4)


def save_patients_pkl(csv_path, s_window):
    """

    :param csv_path: 路径
    :param slice_window: 窗口,用于提取样本
    :return: 针对每个病人保存一条pkl,不再划分训练和测试集
    """
    df = pd.read_csv(csv_path + 'df.csv')
    print(df.head(5))
    ids = df['id'].values
    slice_window=512
    data = df.iloc[:, 1:slice_window + 1].values
    # print(data[0].dtype)  float64
    labels = df['labels'].values
    assert ids.shape[0] == data.shape[0] == labels.shape[0], 'Length of data and labels must be the same'

    del df
    for n in numberSet:
        index = np.where(ids == int(n))
        # print(index[0]) index是包含一个列表作为元素的元组
        n_data = data[index[0]]
        n_labels = np.expand_dims(np.array(labels)[index[0]], axis=1)  # 加入numpy数组转化是因为单个元素数组作为标量进行索引不行
        n_set = np.concatenate([n_data, n_labels], axis=1)

        save_pkl(csv_path + n + '.pkl', n_set, compress=True)
        print('patient ' + n + ' data have been saved!')

    print('things have been rearranged.')


slice_window=512
slice_stride=512
RpreDistance=115
RpostDisance=144
# data_path = './data/win{}strd{}Rpre{}Rpo{}/'.format(slice_window,slice_stride,RpreDistance,RpostDisance)
data_path = './data03/'


# data_path = 'intra-patient ECG anomaly detection/DATA/win{}strd{}Rpre{}Rpo{}/'.format(slice_window,
#                                                                                       slice_stride,
#                                                                                       RpreDistance,
#                                                                                       RpostDisance)
# np.set_printoptions(linewidth=10000)
prepare_df_02(data_path)
# res = pd.read_csv('../data02/df02_v5.csv')
# print(res)
# save_patients_pkl(data_path,slice_window)

ECG 数据库介绍_第2张图片
部分转载:https://blog.csdn.net/HJ33_/article/details/120405786
担心原文链接失效,特备份
仅供学习交流

你可能感兴趣的:(实验,深度学习,pytorch)