番外(开源心电图数据库处理)--mit-bih-normal-sinus-rhythm-database的使用教程

This database includes 18 long-term ECG recordings of subjects referred to the Arrhythmia Laboratory at Boston's Beth Israel Hospital (now the Beth Israel Deaconess Medical Center). Subjects included in this database were found to have had no significant arrhythmias; they include 5 men, aged 26 to 45, and 13 women, aged 20 to 50.1

***本文不介绍数据集内容,仅分享如何读取到心电图***


第一关:批量读取数据集的名字

SHA256SUMS.txt文件中记录了各个文件的命名,可直观的看出,命名方式并非顺序,因此如果需要读取所有文件需要知道各自的文件名,所以可以手写记录,或者根据这份代码自动读取:

Matlab 复制代码
% 文件路径
filePath = 'D:\ECG-Tran\mit-bih-normal-sinus-rhythm-database\SHA256SUMS.txt';

% 读取所有行
fid = fopen(filePath, 'r');
lines = textscan(fid, '%s', 'Delimiter', '\n', 'Whitespace', '');
fclose(fid);
lines = lines{1};

% 提取每行末尾的文件名
fileNames = {};
for i = 1:length(lines)
    token = regexp(lines{i}, '\S+$', 'match', 'once');
    if ~isempty(token)
        fileNames{end+1} = token;
    end
end

% 提取扩展名(包括短横线,如 '.hea-'),无扩展名的留空
extensions = cellfun(@(f) regexp(f, '\.[^.]*$', 'match', 'once'), fileNames, 'UniformOutput', false);

% 将空后缀替换为 'no_extension'
emptyIdx = cellfun(@isempty, extensions);
extensions(emptyIdx) = {'no_extension'};



% 获取唯一扩展名及其对应的合法字段名
uniqueExt = unique(extensions);
fieldNames = cellfun(@ext2fieldname, uniqueExt, 'UniformOutput', false);

% 分组并排序
sortedGroups = struct();
extOriginalMap = struct();  % 存储每个字段名对应的原始扩展名(用于显示)
for i = 1:length(uniqueExt)
    ext = uniqueExt{i};
    fieldName = fieldNames{i};
    idx = strcmp(extensions, ext);
    groupFiles = fileNames(idx);
    
    % 排序:优先按文件名开头的数字升序,否则按字母序
    nums = cellfun(@(f) regexp(f, '^\d+', 'match', 'once'), groupFiles, 'UniformOutput', false);
    hasNum = ~cellfun(@isempty, nums);
    if all(hasNum)
        numVals = cellfun(@str2double, nums);
        [~, sortIdx] = sort(numVals);
    else
        [~, sortIdx] = sort(groupFiles);
    end
    sortedGroups.(fieldName) = groupFiles(sortIdx);
    extOriginalMap.(fieldName) = ext;  % 保存原始扩展名
    % 保存为 MAT 文件
    save('sorted_filenames_by_ext.mat', 'sortedGroups', 'extOriginalMap');
end

% 显示结果(使用原始扩展名展示)
extFieldNames = fieldnames(sortedGroups);
for i = 1:length(extFieldNames)
    fieldName = extFieldNames{i};
    originalExt = extOriginalMap.(fieldName);
    if strcmp(originalExt, 'no_extension')
        displayExt = '无扩展名';
    else
        displayExt = originalExt;
    end
    fprintf('后缀: %s, 共 %d 个文件\n', displayExt, length(sortedGroups.(fieldName)));
    disp(sortedGroups.(fieldName)');
end

% 将扩展名转换为合法的结构体字段名(例如 '.atr' -> 'atr', '.hea-' -> 'hea_')
function validName = ext2fieldname(ext)
    if strcmp(ext, 'no_extension')
        validName = 'no_extension';
        return;
    end
    % 去掉开头的点号
    if startsWith(ext, '.')
        ext = ext(2:end);
    end
    % 将非字母数字下划线替换为下划线(例如 '-' -> '_')
    validName = regexprep(ext, '[^a-zA-Z0-9_]', '_');
end

或者直接复制粘贴需要的:

Matlab 复制代码
后缀: .atr, 共 18 个文件
    {'16265.atr'}
    {'16272.atr'}
    {'16273.atr'}
    {'16420.atr'}
    {'16483.atr'}
    {'16539.atr'}
    {'16773.atr'}
    {'16786.atr'}
    {'16795.atr'}
    {'17052.atr'}
    {'17453.atr'}
    {'18177.atr'}
    {'18184.atr'}
    {'19088.atr'}
    {'19090.atr'}
    {'19093.atr'}
    {'19140.atr'}
    {'19830.atr'}

后缀: .atr-, 共 3 个文件
    {'16273.atr-'}
    {'16539.atr-'}
    {'16773.atr-'}

后缀: .dat, 共 18 个文件
    {'16265.dat'}
    {'16272.dat'}
    {'16273.dat'}
    {'16420.dat'}
    {'16483.dat'}
    {'16539.dat'}
    {'16773.dat'}
    {'16786.dat'}
    {'16795.dat'}
    {'17052.dat'}
    {'17453.dat'}
    {'18177.dat'}
    {'18184.dat'}
    {'19088.dat'}
    {'19090.dat'}
    {'19093.dat'}
    {'19140.dat'}
    {'19830.dat'}

后缀: .hea, 共 18 个文件
    {'16265.hea'}
    {'16272.hea'}
    {'16273.hea'}
    {'16420.hea'}
    {'16483.hea'}
    {'16539.hea'}
    {'16773.hea'}
    {'16786.hea'}
    {'16795.hea'}
    {'17052.hea'}
    {'17453.hea'}
    {'18177.hea'}
    {'18184.hea'}
    {'19088.hea'}
    {'19090.hea'}
    {'19093.hea'}
    {'19140.hea'}
    {'19830.hea'}

后缀: .hea-, 共 18 个文件
    {'16265.hea-'}
    {'16272.hea-'}
    {'16273.hea-'}
    {'16420.hea-'}
    {'16483.hea-'}
    {'16539.hea-'}
    {'16773.hea-'}
    {'16786.hea-'}
    {'16795.hea-'}
    {'17052.hea-'}
    {'17453.hea-'}
    {'18177.hea-'}
    {'18184.hea-'}
    {'19088.hea-'}
    {'19090.hea-'}
    {'19093.hea-'}
    {'19140.hea-'}
    {'19830.hea-'}

后缀: .hea--, 共 1 个文件
    {'16273.hea--'}

后缀: .xws, 共 18 个文件
    {'16265.xws'}
    {'16272.xws'}
    {'16273.xws'}
    {'16420.xws'}
    {'16483.xws'}
    {'16539.xws'}
    {'16773.xws'}
    {'16786.xws'}
    {'16795.xws'}
    {'17052.xws'}
    {'17453.xws'}
    {'18177.xws'}
    {'18184.xws'}
    {'19088.xws'}
    {'19090.xws'}
    {'19093.xws'}
    {'19140.xws'}
    {'19830.xws'}

后缀: 无扩展名, 共 2 个文件
    {'ANNOTATORS'}
    {'RECORDS'   }

第二关:数据格式(需要下载配置WFDB 工具箱)

心电图信号由一个文本头文件(.hea)、一个二进制文件(.dat)和一个二进制注释文件(.atr)描述2。

其中头文件有个细节:有的后缀有-,有的是--。通过代码验证:

Matlab 复制代码
%% 直接显示原始 .hea 文件内容
dataFolder = 'D:\ECG-Tran\mit-bih-normal-sinus-rhythm-database';
recordName = '16273';
heaFile = fullfile(dataFolder, [recordName '.hea']);

% 打开文件
fid = fopen(heaFile, 'r');
if fid == -1
    error('无法打开文件: %s', heaFile);
end

% 逐行读取并打印原始文本
while true
    line = fgetl(fid);
    if ~ischar(line)   % 当 line == -1 时结束循环
        break;
    end
    fprintf('%s\n', line);
end

fclose(fid);

%依次输出--,-,没有杠***

>> untitled
16273 2 128 11354112 10:45:00
16273.dat 212 0 12 0 -61 -28135 0
16273.dat 212 0 12 0 -33 -342 0
# 28 F
>> untitled
16273 2 128 11354112 08:00:00
16273.dat 212 0 12 0 -61 -28135 0
16273.dat 212 0 12 0 -33 -342 0
# 28 F
>> untitled
16273 2 128 11354112  8:00:00
16273.dat 212 0 12 0 -61 -28135 0 ECG1
16273.dat 212 0 12 0 -33 -342 0 ECG2
# 28 F

首先他肯定是来源与同一人:28岁男性

16273是标识号,2导联,128hz,总样本数,起始时间

212是MIT格式(两个12-Bit) 12是采样位数,ECG1是信号描述。

具体为什么有多个头文件,我也不是很了解,有需要的可以查阅相关资料,知道上述信息咱们就可以进行实验了。

注释文件:我最初以为他是标注了波形位置,但可视化发现似乎"歪了",所以打印一下看看注释的内容:

Matlab 复制代码
dataFolder = 'D:\ECG-Tran\mit-bih-normal-sinus-rhythm-database';
recordName = '16265';

% 切换目录
oldFolder = cd(dataFolder);

% 读取注释
[ann, atype] = rdann(recordName, 'atr');

% 切回原目录
cd(oldFolder);

% 输出前20个原始值
fprintf('样本位置 (ann) 和 类型代码 (atype) 的前20个原始值:\n');
for i = 1:min(20, length(ann))
    fprintf('%d\t%d\n', ann(i), atype(i));
end

输出的都是:78,这对应了**ASCII字符 'N',**表示正常搏动。

Matlab 复制代码
%% 读取并可视化双导联 ECG 及注释(MIT-BIH NSRDB)
dataFolder = 'D:\ECG-Tran\mit-bih-normal-sinus-rhythm-database';
recordName = '16265';
heaFile = fullfile(dataFolder, [recordName '.hea']);
% 打开文件
fid = fopen(heaFile, 'r');
if fid == -1
    error('无法打开文件: %s', heaFile);
end

% 逐行读取并打印原始文本
while true
    line = fgetl(fid);
    if ~ischar(line)   % 当 line == -1 时结束循环
        break;
    end
    fprintf('%s\n', line);
end
% 切换目录
oldFolder = cd(dataFolder);

% 读取所有信号
[sig, Fs, tm] = rdsamp(recordName);   % sig: [11730944 x 2]

% 读取注释
[ann, atype] = rdann(recordName, 'atr');

% 切回原目录
cd(oldFolder);

% 基本参数
nSamples = size(sig, 1);
nLeads = size(sig, 2);
fprintf('信号尺寸: %d 样本 × %d 导联\n', nSamples, nLeads);
fprintf('采样率: %.1f Hz\n', Fs);
fprintf('注释数量: %d\n', length(ann));

% 注释类型映射(常用符号,可根据 atype 数值查表)
annSym = cell(size(atype));


%% 选择显示的时间段(秒),例如前 10 秒
tStart = 0;      % 起始时间(秒)
tEnd = 10;       % 结束时间(秒)

idxStart = max(1, round(tStart * Fs) + 1);
idxEnd = min(nSamples, round(tEnd * Fs));
idxRange = idxStart:idxEnd;
tRange = tm(idxRange);
sigRange = sig(idxRange, :);

% 筛选该时间段内的注释
annMask = (ann >= idxStart) & (ann <= idxEnd);
annIdx = ann(annMask);
annType = annSym(annMask);
annValues = sig(annIdx, :);   % 每个注释对应的信号值(两个导联)

%% 绘图:双导联上下排列,标注注释
figure('Name', sprintf('%s - 双导联 ECG', recordName), 'Position', [100,100,1400,600]);

% 第一导联(子图1)
subplot(2,1,1);
plot(tRange, sigRange(:,1), 'b');
hold on;
% 标注注释点
scatter(tRange(annIdx - idxStart + 1), annValues(:,1), 30, 'r', 'filled', 'MarkerEdgeColor', 'k');
xlabel('时间 (秒)'); ylabel('幅度 (mV)');
title(sprintf('Lead 1 (ECG1) - 注释数量: %d', sum(annMask)));
grid on;
legend('ECG', '注释位置', 'Location', 'best');
hold off;

% 第二导联(子图2)
subplot(2,1,2);
plot(tRange, sigRange(:,2), 'g');
hold on;
scatter(tRange(annIdx - idxStart + 1), annValues(:,2), 30, 'r', 'filled', 'MarkerEdgeColor', 'k');
xlabel('时间 (秒)'); ylabel('幅度 (mV)');
title(sprintf('Lead 2 (ECG2) - 注释数量: %d', sum(annMask)));
grid on;
legend('ECG', '注释位置', 'Location', 'best');
hold off;

sgtitle(sprintf('MIT-BIH Normal Sinus Rhythm - 记录 %s (%.1f - %.1f 秒)', recordName, tStart, tEnd));

后续处理中发现,部分数据并非是直接记录的,最开始应该是有一部分设备未连接:

19088、19090、19093、19140、19830均出现了这种情况,这个时候我们看一下注释,标注为128,我并未查到其具体对应的意思。但其有一个转折:

根据这个正常心搏的标记,我们就可以做出正确的判断,应该从这之后开始选取信号。后续也可以通过这个进行数据清洗。

*****************END*******************

参考:

1Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark RG, Mietus JE, Moody GB, Peng C-K, Stanley HE. PhysioBank, PhysioToolkit, and PhysioNet: Components of a New Research Resource for Complex Physiologic Signals. Circulation 101 (23):e215-e220 Circulation Electronic Pages; [http://circ.ahajournals.org/content/101/23/e215.full](http://circ.ahajournals.org/content/101/23/e215.full "http://circ.ahajournals.org/content/101/23/e215.full"); 2000 (June 13).

2Z. F. M. Apandi, R. Ikeura and S. Hayakawa, "Arrhythmia Detection Using MIT-BIH Dataset: A Review," 2018 International Conference on Computational Approach in Smart Systems Design and Applications (ICASSDA), Kuching, Malaysia, 2018, pp. 1-5, doi: 10.1109/ICASSDA.2018.8477620.

相关推荐
学习FPGA的电气小兴兴16 小时前
基于FPGA的CORDIC旋转模式实现sin和cos运算_采用EGo1板卡上板验证【FPGA数字信号处理第5讲】
fpga开发·信号处理·数字信号处理·verilog·cordic·ego1·fpga设计入门与提高
weixin_307779138 天前
C++代码实现MATLAB中的ode23tb函数功能
开发语言·c++·算法·matlab
菜鸟~noob2338 天前
【太空电子战】从CCS到Meadowlands:反通信系统的二十年演进史【含matlab代码】
开发语言·matlab
菜鸟~noob2339 天前
【太空电子战】Meadowlands作战验收:美国太空军可逆干扰能力的里程碑【含matlab代码】
matlab·电子战
菜鸟~noob23310 天前
【Bonjour】华为Peerium架构深度解析:从冯·诺依曼到百万处理器“一台计算机”,附MATLAB性能仿真【含matlab代码,可直接运行】
开发语言·matlab·华为·peerium
民乐团扒谱机10 天前
【手把手通信仿真】FSK与GFSK调制原理对比及MATLAB全实现,附频谱波形分析
matlab·蓝牙·微电子·通信技术·通信原理·gfsk·fsk
ι:10 天前
单相桥式全控整流仿真从零复现教学
windows·学习·matlab·simulink
菜鸟~noob23311 天前
【电子战】第14篇:TOA 定位——圆交汇与到达时间【含matlab代码】
开发语言·matlab
简简单单做算法11 天前
基于灰狼算法和贝叶斯联合优化的BiLSTM(GWO-BO-BiLSTM)一维时间序列预测算法matlab仿真
matlab·一维时间序列预测·gwo-bo-bilstm
Evand J11 天前
【MATLAB例程|图像滤波降噪13】局部噪声方差自适应滤波(LNR)图像降噪与效果展示。附完整例程的下载链接
图像处理·计算机视觉·matlab·滤波·代码·降噪