ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MIT-BIH心电数据库读取指南:.hea/.dat/.atr格式全解析

MIT-BIH心电数据库读取指南:.hea/.dat/.atr格式全解析 做心电信号分析的人十有八九绕不开MIT-BIH这个数据库。它是心电领域最经典的公开数据集也是心律失常检测算法绕不开的评测基准。很多人第一次接触它时第一反应都是数据从哪下载怎么读取那些.hea、.dat、.atr文件到底是干什么的这篇文章就把这些文件从头到尾拆一遍顺便给出完整的读取过程和避坑经验。不管你是要做信号处理、训练深度学习模型还是做心率变异性分析读完都能直接上手。MIT-BIH心律失常数据库由美国麻省理工学院与Beth Israel医院合作建立目前挂在PhysioNet平台上公开下载。整套数据包含48条时长约30分钟的双通道动态心电图记录采样率360Hz总标注心拍超过11万个。它之所以被广泛使用是因为每一条记录都带有人工专家逐心拍的标注——你既可以从里面提取训练样本也可以把它当作测试算法的“标准答案”。这篇文章的目标读者很明确刚接触MIT-BIH、搞不清文件格式、读取时反复报错的人。1. 项目概述这套数据到底解决什么问题1.1 什么是MIT-BIH为什么它是心电分析的“默认答案”MIT-BIH并不是一个简单的波形文件集合而是一套经过精心设计、带标注的临床数据库。它收录了48条记录每一条都来自一位真实的受试者覆盖了正常窦性心律、室性早搏、房性早搏、束支传导阻滞、起搏心律等多种心律失常类型。受试者的年龄分布很广男性和女性都有这使得数据具备不错的多样性做出来的算法不会只对某类人群有效。为什么说它是“默认答案”因为心电算法要评估好坏必须有一个大家都能跑、指标可横向对比的数据集。MIT-BIH刚好满足这个条件数据公开、标注精准、格式固定、文档齐全。到今天为止大量论文里的心律失常分类、QRS波检测、心率变异性分析仍然是在MIT-BIH上完成的。哪怕你做的是自己的临床数据也常需要先用MIT-BIH验证一下方法的可靠性再迁移到自己的场景里。1.2 一句话看懂数据存储逻辑记录、导联、采样率从存储逻辑上看MIT-BIH非常好理解。每一条记录比如“100号记录”对应一位受试者半小时的动态心电图。记录里有两个导联的信号绝大多数记录是MLII和V1少数是MLII和V5等其他导联组合。两个导联是同时采集的采样率固定为360Hz每个导联采样30分钟算下来大约65万个采样点。打开下载好的文件夹你会看到每个记录名都对应好几个文件核心就是三个.hea头文件、.dat数据文件、.atr注释文件。我习惯用一个类比来说明它们的关系.hea是档案袋上的标签写清楚这份档案的基本信息.dat是里面那张原始记录纸波形全在上面.atr是红笔写的批注标记了每个心拍的位置和类型。三者配合才是一份完整可用的心电图数据。2. 各文件作用全拆解.hea/.dat/.atr一个都不能少2.1 .hea头文件全记录的“说明书”与“地图”.hea文件是纯文本格式用记事本就能打开。以100号记录为例开头大概是这样的100 2 360 650000 100.dat 212 200 11 1024 995 0 MLII 100.dat 212 200 11 1024 1004 0 V5 # 69 M 1085 1629第一行的三个数字分别是记录名、导联数、采样率、采样点数。100 2 360 650000的意思就是这条记录叫100有两个导联采样率360Hz总共65万个采样点。采样点数不需要死记.hea里写多少就是多少读取数据时最好以它为准来截断文件避免把多出来的字节也读进来。第二行和第三行描述两个导联的细节这行信息的价值经常被忽略。我们来逐段拆解100.dat信号存储在哪个数据文件里。212数据编码格式MIT-BIH用的是“212”格式后面讲.dat时细说。200ADC增益单位是ADC单位/mV意思是1mV的心电信号会被量化为200个ADC单位。11ADC分辨率11位所以数字量范围大致在0到2047之间。1024基线值即信号为0mV时对应的数字量。995第一个样本的值可以用来校验读取是否正确。0校验和用于检查文件是否损坏。MLII、V5导联名称。手动读取.dat的时候最关键的参数就是基线和增益。因为文件里存的不是以毫伏为单位的物理值而是一个整数。要把整数变成真正的电压值公式是物理值(mV) (数字量 - 基线) / 增益比如数字量是1024基线也是1024那电压就是0mV数字量是1224电压就是(1224-1024)/2001.0mV。这个公式看起来简单但如果读数据时忘记减基线波形整体就会“飘”得很高或很低后面无论做滤波还是做心拍检测结果都会受影响。2.2 .dat数据文件按3字节打包的二进制心电图.dat是二进制文件不能直接用文本编辑器打开。MIT-BIH采用了一种叫“format 212”的编码方式名字里的212其实来自“12位采样、两个通道、3字节存储”的缩写。为什么要这么设计早期存储和传输资源都很紧张如果每个12位样本用一个16位整数存会浪费4位空间如果直接按每个样本3字节存又会把两倍数据量写在盘上。212格式巧妙地用3个字节存下两个通道的各一个样本正好每个通道占12位。具体规则是每3个字节为一组存放两个样本。假设三个字节是b0、b1、b2那么通道1的样本值 (b0 4) | (b1 4)通道2的样本值 ((b1 0x0F) 8) | b2用大白话说第一个字节的8位加上第二个字节的高4位拼出通道1的12位值第二个字节的低4位加上第三个字节的8位拼出通道2的12位值。位运算看起来略繁琐但这是理解.dat文件的关键。如果你用现成的wfdb库根本不需要自己写这些但如果你想排查奇怪的问题或者做自定义工具就必须懂这个底层格式。细心的朋友会发现这样读出来的数值是“数字量”范围大概在0到2047之间距离真正的心电信号还有一步。所以读取.dat之后一定要记得做基线校正和单位换算physical_signal (digital_signal - baseline) / gain做完这步你看到的波形幅值就基本落在-5mV到5mV之间这才符合真实心电信号的物理范围。2.3 .atr注释文件逐心拍标注的二进制协议如果说.dat是“波形原始记录”那.atr就是“专家批注”。文件里记录了每一个被人工识别出的心拍位置以及这个心拍的类型。MIT-BIH的标注非常细致常用符号有N正常心搏、L左束支传导阻滞、R右束支传导阻滞、A房性早搏、V室性早搏、F融合心搏、J交界性早搏等。做心律失常分类任务时人们通常会把它们归并为五大类N、S、V、F、Q分别对应正常、室上性、室性、融合和未知/不可分类。.atr也是二进制格式结构比.dat复杂一些。每个注释记录通常会包含一个标志字节、两个字节的时间增量、一个字节的类型码可能还有一些辅助字节。时间增量的单位是“采样点”不是“秒”。比如一个注释说某个心拍在“第1024个采样点”那在360Hz的采样率下对应的时刻就是1024/360≈2.84秒。如果想手动解析.atr工作量不大但很琐碎要处理边缘情况比如符号为0的伪注释、节奏类型注释等。我的建议是日常使用直接用wfdb库的rdann函数它会帮你把类型码映射成可读的符号把采样点索引也解析好。但你仍然需要理解一个核心概念——annotation.sample给出的是第几个采样点不是秒也不是某个心拍在整个记录里的“第几拍”。所有后续对齐操作都要基于采样点索引来做。3. 数据读取实操从手动解析到一行代码3.1 手动读取.dat搞懂底层字节流先别急着装库我建议至少手动读一次.dat这样以后遇到问题心里有底。下面是一段可以直接运行的Python代码用numpy读一个记录的前N个样本import numpy as np def read_mit_bih_dat(record_name, num_samples3600): 手动读取MIT-BIH的.dat文件format 212 返回两个导联的数字量shape(num_samples, 2) with open(record_name .dat, rb) as f: # 每3个字节存两个12位样本 raw f.read((num_samples * 3) // 2) byte_arr np.frombuffer(raw, dtypenp.uint8).reshape(-1, 3) ch1 (byte_arr[:, 0].astype(np.int16) 4) | (byte_arr[:, 1].astype(np.int16) 4) ch2 ((byte_arr[:, 1].astype(np.int16) 0x0F) 8) | byte_arr[:, 2].astype(np.int16) return np.column_stack([ch1, ch2])注意我这里的读取长度先按num_samples * 3 // 2换算成字节数因为3个字节对应2个样本。如果你想读整条记录直接用.hea文件里的采样点数来计算字节数更稳妥。读完数字量后再配合基线1024和增益200做物理单位换算就能得到类似这样的输出第0个样本: 995 - 物理值 -0.145 mV 第1个样本: 1005 - 物理值 -0.095 mV 第2个样本: 1011 - 物理值 -0.065 mV看到这些数值在0附近缓慢波动说明读取基本正确。如果出来几百几千的数值或者直接乱码大概率是字节拼接方向搞反了。3.2 处理注释文件理解时间增量注释文件的解析虽然琐碎但理解一下时间增量的原理很有必要。每个注释记录的大致结构是先出现一个0x00标志字节然后是两个字节的时间增量16位整数单位是采样点再往后是一个类型字节某些类型后面还会带辅助数据。实际解析时你会碰到几个麻烦点注释流里有“伪注释”它们的类型码是0不表示真实心拍主要用于帮助定位处理时要跳过。节奏类型比如心动过速开始、结束也混在注释流里后缀可能是“”、“x”等不是所有符号都对应一个心拍。文件末尾可能有填充字节不能硬当成注释来解。所以手动解析.atr可以当作一次练习但生产环境里真的没必要。下面这段只展示核心逻辑方便你理解采样点索引是怎么来的def read_ann_basic(atr_path): samples [] symbols [] with open(atr_path, rb) as f: data f.read() i 0 time_counter 0 while i len(data): if data[i] 0x00: # 时间增量占2字节 delta int.from_bytes(data[i1:i3], little) ann_type data[i3] i 4 time_counter delta # 这里省略辅助字节处理完整实现需参考WFDB格式文档 if ann_type not in [0, 1]: samples.append(time_counter) symbols.append(chr(ann_type) if ann_type 128 else ?) else: i 1 return samples, symbols这段代码只是为了让你感受注释文件的解析逻辑不能直接用在实际项目中因为辅助字节和特殊时间标志都被跳过了。遇到复杂的注释记录老老实实用成熟的库解析别自己造轮子。3.3 用wfdb库一行读取傻瓜式落地手动解析可以让你明白原理但日常开发我更推荐用wfdb这个Python库。它是PhysioNet官方维护的WFDB软件包的Python绑定读取MIT-BIH就是几行代码的事pip install wfdb numpy matplotlibimport wfdb record wfdb.rdrecord(100, sampto3600, physicalTrue) annotation wfdb.rdann(100, atr, sampto3600) # record.p_signal 保存物理单位信号shape(3600, 2) # annotation.sample 保存心拍位置索引 # annotation.symbol 保存心拍类型符号 print(record.p_signal[:5]) print(annotation.sample[:10]) print(annotation.symbol[:10]) wfdb.plot_wfdb(recordrecord, annotationannotation)rdrecord负责读.hea和.datrdann负责读.atr。你不需要自己处理字节拼接、基线、增益库内部全部搞定。渲染出来的图会同时显示两个导联的波形和每个心拍的标注符号立刻能看出读取是否正确。实测下来wfdb库对MIT-BIH的兼容性很好几百行数据的读取也就是毫秒级。如果你是从网上下载的.mat版本即信号被保存为MATLAB格式也不要慌。wfdb库同样支持读取只是底层逻辑稍有不同核心API差别不大。但如果你要做标准的MIT-BIH流程建议还是用原生的.hea/.dat/.atr三件套。4. 读取后的验证与预处理要点4.1 怎么确认读取结果没出错读取代码跑通了不代表数据没问题。我见过太多人读出来的“心电信号”幅值稳定在500以上波形直奔顶格还拿着一堆错误数据去训练模型。所以验证这一步不能省。最直接的验证方法有三个。第一打印信号的统计信息正常心电信号在0mV附近波动标准差一般在0.1到0.5之间第二画出前几秒的波形肉眼找一下QRS波群正常波形里应该能明显看到P波、QRS波、T波的形态第三把.atr里标注的心拍位置叠到波形上如果标注的点正好落在R波峰值附近说明读取和标注都对齐了。wfdb.plot_wfdb可以帮你一次性完成第二、第三步。如果你读出来的波形“糊成一片”或者标注和波峰对不上先回去看基线、增益、字节拼接这几项90%的问题都出在这几个地方。4.2 标签对齐、重采样与心拍切分很多任务需要把原始信号处理成固定长度的样本比如“以R峰为中心向前取0.2秒向后取0.4秒”。MIT-BIH的采样率是360Hz所以0.2秒就是72个采样点0.4秒就是144个采样点。切分时要注意注释里的采样点索引是原始的整数索引如果你做了重采样或滤波原始索引可能不再适用于新信号。如果必须重采样比如要统一到250Hz我建议先用插值把信号变到新采样率同时把标注的采样点也按比例缩放并四舍五入取整。这里的“按比例缩放”指的是乘以新采样率与旧采样率的比值不是简单加减。标签一偏移后面训练出来的模型再准也是错的这个问题很容易被忽略。预处理环节还有一个常见操作是滤波。MIT-BIH原始信号里有基线漂移和工频干扰做心拍检测前建议先用带通滤波器过滤掉0.5Hz以下和50Hz以上的成分。滤波时要注意滤波器是有群延迟的如果做的是零相位滤波比如scipy.signal.filtfilt信号延迟会被补偿标签位置基本不需要调整如果用了普通IIR滤波相位延迟会导致R峰位置偏移标签也要相应微调。4.3 训练集/测试集划分患者级分割的必要性MIT-BIH的48条记录来自47位受试者其中有两份记录201和202来自同一位受试者。做心律失常分类时如果把同一位受试者的记录同时放进训练集和测试集模型等于提前见过了这个人的心电形态测试指标会虚高。这种“信息泄露”在跨患者评估中特别致命。合理的做法是按患者划分数据训练集和测试集不要包含同一位受试者的任何记录。很多论文采用的做法是取一部分记录做训练另一部分记录做测试比如用前40条记录训练后8条记录测试但前提仍然是要确认这些记录确实来自不同受试者。所以在实验设计阶段建议先查一下官方受试者列表把同一受试者的记录标记出来再决定怎么划分。这个细节直接决定你论文结果的可信度。5. 常见问题与排查技巧实录5.1 信号变成乱码或幅值异常这是读取MIT-BIH时遇上最多的问题症状是画出来的波形完全不像是心电图或者数值非常大。核心原因有三类字节拼接方向错误。format 212的位运算规则如果写反读出来的数字会完全错位波形像打乱的马赛克。没有做基线校正。数字量直接当物理值用波形整体上移几百个单位看起来像一条粗大的直线在顶部。增益参数用错。MIT-BIH的增益通常是200如果你用了别的数值幅值会被等比放大或缩小。排查方法也很简单打印前10个数字量先看是不是在0到2047之间再看计算后的物理值是不是在正负5mV量级。两步能排除掉绝大多数问题。5.2 注释解析失败或标签漂移.atr解析最容易踩的坑是符号处理。注释里那些“”和“x”符号其实不表示普通心拍前者通常代表起搏心拍后者代表伪迹或质量差的片段。如果做分类任务时把它们也当成普通标签数据集就会被污染。标签漂移则多见于重采样之后。原始标注索引是360Hz下的整数值重采样后如果你只是粗暴地round(downsample_ratio * sample_index)某些边缘位置的索引会偏移一两个点导致切出来的样本窗口错位。我的习惯是重采样后再验证一次R峰对齐效果抽查十几个心拍看波峰是否仍在窗口中心附近。5.3 高频踩坑清单速查下面这张表是实际使用中最常见的几个问题和对应解法建议收藏备用。症状可能原因处理方式文件找不到或读取失败路径大小写错误、目录不对使用绝对路径核对记录名如“100”是否带前缀读取出的信号长度不对没有按.hea文件中采样点数截断读取时限定sampto或按头部声明手动截断波形幅值异常大未减去基线或未除以增益使用(数字量-基线)/增益换算物理值波形有断崖式跳变字节拼接时位运算顺序错误核对format 212的拼接规则注释看起来对不上波形重采样后标签索引未调整按采样率比例重新计算索引并四舍五入分类时只用一个导联数据忽略了“.hea里其实有两个导联”根据任务需求选择channels[0]或[1]除了上面这些还有一个容易被忽略的小问题如果你把.dat整个文件读进内存它会占掉不少空间因为650000个样本乘以2个导联转成float64后大约是10MB左右。听起来不大但如果你同时读48条记录内存也是几十MB起步。读取后及时转成float32或者按需分段读取实验会流畅很多。6. 从读取到建模MIT-BIH还能这么用6.1 心拍级分类的标准流程读通MIT-BIH之后下一步通常就是做心拍级分类。最常见的流程是先用wfdb.rdrecord读信号用wfdb.rdann读标注接着做带通滤波去噪然后在每个标注位置附近切出固定长度的心拍窗口最后把标注符号映射成类别标签输入分类模型。窗口长度需要根据任务调整太短可能漏掉P波或T波太长又会带入无关噪声。我的经验是取R峰前0.25秒到R峰后0.4秒既能看到完整的QRS波群又能包含一部分ST段和T波对判别室性早搏和正常心拍很有帮助。样本切好后要检查一下各类别的样本数量分布。MIT-BIH中正常心拍远多于室性早搏和房性早搏不做类平衡处理的话模型会严重偏向多数类导致少数类几乎识别不出来。6.2 RR间期、心率变异性等扩展特征标注文件的价值不止用来生成分类标签它本身就是宝贵的特征来源。通过annotation.sample可以轻松算出相邻心拍之间的间隔也就是RR间期。RR间期序列反映的是心率变化趋势对检测房颤、早搏等异常非常有用。用简单的numpy代码就能计算import numpy as np rr_intervals np.diff(annotation.sample) / record.fs # 单位秒 mean_hr 60.0 / np.mean(rr_intervals)有了RR间期序列还可以继续统计标准差、RMSSD等时域指标或者做频域分析。很多心率变异性研究就是在这种基础上开展的。也就是说一个.atr文件不光是“答案”同时也是特征工程的一座富矿。6.3 把自己的数据对齐成MIT-BIH格式最后再分享一个很实用的技巧WFDB工具链不仅支持读取也支持写入。也就是说你可以把自己采集的心电数据转换成MIT-BIH的.hea/.dat/.atr格式这样就能直接复用整个WFDB生态里的工具和脚本。在wfdb库里写入一条记录大致是这样import wfdb import numpy as np # signal shape(n_samples, n_channels)单位mV signal np.random.randn(3600, 2).astype(np.float64) wfdb.wrsamp( record_namemy_record, fs360, units[mV, mV], sig_name[MLII, V5], p_signalsignal, fmt[212, 212], adc_gain[200, 200], baseline[1024, 1024], )写出来的文件会自动生成.hea和.dat如果你还想写.atr就需要构造标注数组再调用对应的写注释接口。这个过程稍微复杂一些但能帮你建立起一套“私有数据也能用公开工具处理”的流程对于团队协作和实验复现都很有价值。我在实际使用中的最大感触是MIT-BIH的读取难点从来不在代码本身而在对文件格式底层逻辑的理解。先花半小时把.hea里的参数、.dat的位运算、.atr的含义搞明白后面所有实验都会顺畅很多。就算你决定用wfdb库偷懒也别完全放弃这些底层知识否则遇到数据异常时你会连从哪里排查看起都不知道。另一个小技巧是读好一份数据后立刻保存成.npy或.npz缓存后续实验反复读取会快得多尤其是做深度学习时数据的I/O时间经常被严重低估。
返回列表