前文提到了数据清洗,经过操作发现这里面细节还真多,现在来分享一下心路历程。

我的思路是根据标签进行清洗,所以第一步就是先搞清有什么标签:

%% ====================== 批量统计所有注释(时间分布) ======================
% 基于 128 Hz 采样率,将样本位置转换为时间(秒)
% 输出每个记录的前 10 个注释作为示例,并保存完整结果

clear; clc;

%% 参数设置
dataFolder = 'D:\ECG-Tran\mit-bih-normal-sinus-rhythm-database'; 
recordNames = {
    '16265', '16272', '16273', '16420', '16483', '16539', ...
    '16773', '16786', '16795', '17052', '17453', '18177', ...
    '18184', '19088', '19090', '19093', '19140', '19830'
};
nRecords = length(recordNames);
Fs = 128;   % 固定采样率(已知该数据库为 128 Hz)

% 用于保存所有记录的注释统计结果
all_stats = cell(nRecords, 1);

fprintf('开始批量统计 %d 个记录的注释...\n\n', nRecords);

%% 主循环:依次处理每个记录
for i = 1:nRecords
    recName = recordNames{i};
    fprintf('========== 记录 %d/%d : %s ==========\n', i, nRecords, recName);
    
    % 切换到数据文件夹
    oldFolder = cd(dataFolder);
    
    % 读取注释(样本位置 ann 和类型代码 atype)
    try
        [ann, atype] = rdann(recName, 'atr');
    catch ME
        fprintf('错误:无法读取注释文件 %s.atr\n', recName);
        cd(oldFolder);
        continue;
    end
    
    % 切回原目录
    cd(oldFolder);
    
    nAnnot = length(ann);
    fprintf('注释总数: %d\n', nAnnot);
    
    % 统计各类型代码的出现次数
    unique_types = unique(atype);
    fprintf('类型代码统计 (代码 : 次数):\n');
    for j = 1:length(unique_types)
        code = unique_types(j);
        count = sum(atype == code);
        fprintf('  %d : %d\n', code, count);
    end
    
    % 将样本位置转换为时间(秒): 假设 ann 为 1-based
    time_sec = (ann - 1) / Fs;

end


fprintf('\n批量统计完成。\n');

经过统计发现共有70、78、83、86、124、126六种,对应的ASCII为:F、N、S、V、|、~。依次是心室律与正常心律的融合、正常节拍、室上性早搏、室性早搏、孤立的QRS状伪影、信号质量的变化[1]。

表1 注释统计结果

编号 注释总数  70 74 78  83 86 124 126
16265 100955 6 0 100216 5 16 259 453
16272 97146 0 0 87757 1 0 7265 2123
16273 90097 0 0 89840 5 0 52 200
16420 102436 0 0 102061 4 2 73 296
16483 104561 0 0 104330 4 0 96 131
16539 108674 0 0 108265 17 0 47 345
16773 112897 0 0 81962 27 0 30782 126
16786 101739 2 0 101605 5 3 40 84
16795 87678 0 0 86872 0 0 686 120
17052 88002 0 0 87354 2 0 296 350
17453 101173 0 0 100655 3 0 108 407
18177 117004 0 0 115908 3 0 826 267
18184 102672 0 0 102313 0 0 173 186
19088 117880 0 0 97957 2 2 19186 733
19090 81953 0 2 81382 7 0 281 281
19093 83670 0 0 75100 3 3 8424 140
19140 96992 0 0 96596 0 0 244 152
19830 111263 0 0 109329 3 0 1230 701
总计 1806792 8 2 1729502 91 26 70068 7095

非正常的可视化:

126是信号质量变化,是我关注的一点,我对比发现:并不一定是两条导联均出现质量变化。

还有就是,它在“反复横跳”,如第一个,它的变化标签相对于正常心拍是很少的。

在124比较多的16773上,他甚至在一个时间戳上同时出现:

可视化一下前5s和前20s:

直观感觉,我并未发现有什么问题,当然我并不具备专业的医学背景,无法看懂心电图的细节问题。

同时查阅了一下相关的文献,没看到有研究对其进行清洗,大家普遍认为该数据集质量很高,是一个有效的参考数据集。所以我暂时也不大量清洗了,只针对上次发现的开头的问题进行了处理:
 

function [sig, Fs, tm]  = readdata(recordName, dataFolder)
% 读取心电图信号,根据注释自动裁切,并进行去趋势和带通滤波
% 输入:
%   recordName - 记录名称 (如 '16265')
%   dataFolder - 数据文件夹路径
% 输出:
%   sig - 处理后的双导联信号 [nSamples × 2]
%   Fs  - 采样率 (Hz)

    oldFolder = cd(dataFolder);
    cleanup = onCleanup(@() cd(oldFolder));

    % 读取完整信号和注释
    [sig_full, Fs, tm] = rdsamp(recordName);
    [ann, atype] = rdann(recordName, 'atr');

    % 找到所有类型78的注释位置(样本索引)
    idx78 = find(atype == 78);
    if length(idx78) < 2
        error('记录 %s 中类型78的注释不足2个,无法裁切', recordName);
    end

    % 统计从开始到第二个78之间出现的124数量
    second78_pos = ann(idx78(2));
    idx124 = find(atype == 124);
    count124_before_second = sum(ann(idx124) < second78_pos);

    % 决定取第几个78
    if count124_before_second >= 5 && length(idx78) >= 10
        start_idx = 12;   % 取第10个78
        fprintf('记录 %s:前段出现 %d 个124,从第%d个78开始裁切\n', recordName, count124_before_second,start_idx);
    else
        start_idx = 6;    % 取第5个78
        fprintf('记录 %s:前段出现 %d 个124,从第%d个78开始裁切\n', recordName, count124_before_second,start_idx);
    end

    startSample = ann(idx78(start_idx));
    if startSample > size(sig_full, 1)
        error('起始样本 %d 超出信号长度 %d', startSample, size(sig_full,1));
    end

    % 裁切信号
    sig_cropped = sig_full(startSample:end, :);
    fprintf('裁切后剩余 %d 个样本\n', size(sig_cropped,1));

    % ---- 去趋势和带通滤波 ----
    % 对每个导联独立处理
    sig = zeros(size(sig_cropped));
    for lead = 1:2
        % 去线性趋势
        signal_detrend = detrend(sig_cropped(:, lead));
        % 带通滤波器设计 (0.5 - 40 Hz)
        nyquist = Fs / 2;
        [b, a] = butter(4, [0.5/nyquist, 40/nyquist], 'bandpass');
        % 零相位滤波
        sig(:, lead) = filtfilt(b, a, signal_detrend);
    end
    fprintf('已完成去趋势和带通滤波 (0.5-40 Hz)\n');
end

[1]PhysioBank Annotations

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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐