Noninvasive Fetal ECG: The PhysioNet/Computing in Cardiology Challenge 2013 1.0.0
(7,133 bytes)
function FQRSc = smooth_rr(FQRS,rMQRS,ecg,fs,debug)
% look for smoothing RR time series to remove additional FQRS and fill
% missing FQRS. This showed to improve the PCinC scoring results in term
% of F1 measure by >1% on the training set-a.
% This function takes into account a certain number of cases
% where there might be missing or extra QRS and given context and prior
% on physiology decides where to add/remove QRS.
%
% inputs
% FQRS: FQRS time series (required)
% rMQRS: reference MQRS (optional). If rMQRS is inputted then the
% algorithm looks for FQRS that are overlapping with the MQRS. If
% that is the case then the FQRS fiducial is moved to the centre
% between the previous and following FQRS. This is thought to
% be more accurate.
% ecg: residual signal for context (optional). Instead of guessing the
% FQRS location out of complete context, we look at the sign of
% the known FQRS and look for a local max/min around the expected
% position where we should have a FQRS.
% fs: sampling frequency (optional, default: 1kHz)
%
% output
% FQRS: outputed smoothed FQRS time series.
% (empty nargout: in the case no output is specified the results
% are plotted)
%
% IMPORTANT NOTE: this code is designed for foetal ecg which means that the
% constants used to assess whether a FQRS position is missing or extra are
% based on known typical foetal heart rate. If you want to use this code for
% adult ecg then you should change the constants that are listed in the
% code under 'constants' to reflect adult ECG physiology.
%
% FECG extraction toolbox, version 1.0, Sept 2013
% Released under the GNU General Public License
%
% Copyright (C) 2013 Joachim Behar
% Oxford university, Intelligent Patient Monitoring Group - Oxford 2013
% joachim.behar@eng.ox.ac.uk
%
% Last updated : 02-08-2013
%
% This program is free software; you can redistribute it and/or modify it
% under the terms of the GNU General Public License as published by the
% Free Software Foundation; either version 2 of the License, or (at your
% option) any later version.
% This program is distributed in the hope that it will be useful, but
% WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
% Public License for more details.
% == managing inputs
if nargin<1; error('smooth_rr: wrong number of input arguments \n'); end;
if nargin<2; rMQRS=[]; end;
if nargin<3; ecg=[]; end;
if nargin<4; fs=1000; end;
if nargin<5; debug=0; end;
% == constants
WINDOW = 0.020*fs; % window in which looking for local max/min in case of missing QRS
MIN_FRR = 0.35*fs; % minimal foetal RR interval corresponds to 171.4bpm
MAX_FRR = 0.5*fs; % maximal foetal RR interval corresponds to 120bpm
EXTRA_BEAT_THRES = 0.7; % at what threshold do we consider the QRS to be a FP
MISSED_BEAT_THRES = 1.75; % at what threshold do we consider the QRS to be a FN
if ~isempty(ecg); SIGN = sign(median(ecg(FQRS))); end; % sign of the peaks
MAX_NB_ITER = 5000; % this is to avoid infine loops
MQRS_FQRS_TOL = 0.050; % tolerance for assuming MQRS and MQRS overlaps (in sec)
% == core function
try
FQRSc = FQRS;
qrsnb = 2;
compt = 0;
while qrsnb<length(FQRSc)-1 && compt<MAX_NB_ITER
% med RR interval over the past 5 beats
if qrsnb>6
med = median(diff(FQRSc(qrsnb-5:qrsnb)));
else
med = median(diff(FQRSc(qrsnb:qrsnb+5)));
end
% check that the med computed makes sense (i.e. should be between
% 350 and 500ms i.e. 120 and 172bpm) otherwise do nothing
% because prediction might be rubbish
if med>MIN_FRR && med<MAX_FRR
dTplus = FQRSc(qrsnb+1)-FQRSc(qrsnb); % RR interval forward
dTminus = FQRSc(qrsnb)-FQRSc(qrsnb-1); % RR interval backward
if dTplus<EXTRA_BEAT_THRES*med && dTminus<1.2*med
% == case 1: extra beat
FQRSc(qrsnb+1) = []; % remove extra beat
elseif dTplus>MISSED_BEAT_THRES*med && dTminus>0.7*med
% == case 2: missed beat
if ~isempty(ecg)
% if we have context then look for a local max/min
if SIGN>0
[~,mind] = max(ecg(FQRSc(qrsnb)+med-WINDOW:FQRSc(qrsnb)+med+WINDOW));
else
[~,mind] = min(ecg(FQRSc(qrsnb)+med-WINDOW:FQRSc(qrsnb)+med+WINDOW));
end
MissedFQRS = (mind-WINDOW-1)+FQRSc(qrsnb)+med;
else
% otherwise guess location
if dTplus<3*med && dTplus>1.5*med
% if the dTplus makes sense then better to guess in
% the middle of current and next peak
MissedFQRS = round(FQRSc(qrsnb)+(dTplus)/2);
else
% otherwise use the med to predict (less precise)
MissedFQRS = round(FQRSc(qrsnb)+med);
end
end
% insert missed FQRS where it belongs
FQRSc = [FQRSc(1:qrsnb) MissedFQRS FQRSc(qrsnb+1:end)];
qrsnb = qrsnb+1;
else
% == case 3: normal detection
qrsnb = qrsnb+1;
end
else
qrsnb = qrsnb+1;
end
compt = compt+1;
end
% == managing MQRS/FQRS overlap
if ~isempty(rMQRS)
FQRStemp = FQRSc;
[~,d] = dsearchn(rMQRS',FQRStemp');
ind2cor = find(d<MQRS_FQRS_TOL*fs); % indices to correct
for cc=1:length(ind2cor)
% replace all overlapping FQRS/MQRS by the midpoint between
% previous and next FQRS position which is assumed to be a better
% estimate
if ind2cor~=1 & ind2cor~=length(d)
FQRStemp(ind2cor(cc)) = round((FQRStemp(ind2cor(cc)-1)+FQRStemp(ind2cor(cc)+1))/2);
end
end
FQRSc = FQRStemp;
end
catch ME
for enb=1:length(ME.stack); disp(ME.stack(enb)); end;
FQRSc = FQRS;
end
% == plots
if debug || isempty(nargout)
if isempty(ecg)
ax(1)=subplot(211); plot(FQRS/fs,'o','LineWidth',2); hold on, plot(FQRSc/fs,'+r','LineWidth',2);
legend('initial','corrected'); title('FQRS correction'); xlabel('Time [sec]');
else
tm = 1/fs:1/fs:length(ecg)/fs;
ax(1)=subplot(211); plot(tm,ecg);
hold on; plot(tm(FQRS),ecg(FQRS),'o','LineWidth',2); hold on, plot(tm(FQRSc),ecg(FQRSc),'+r','LineWidth',2);
legend('ecg residual','initial','corrected'); title('FQRS correction'); xlabel('Time [sec]');
end
hr = 60./(diff(FQRS)/fs);
hrc = 60./(diff(FQRSc)/fs);
ax(2)=subplot(212); plot(FQRS(1:end-1)/fs,hr,'--o'); hold on, plot(FQRSc(1:end-1)/fs,hrc,'--r+');
legend('HR','HRc'); ylabel('HR [bpm]'); xlabel('Time [sec]');
linkaxes(ax,'x');
set(findall(gcf,'type','text'),'fontSize',14,'fontWeight','bold');
end
end