Waveform Database Software Package (WFDB) for MATLAB and Octave 0.10.0
(8,397 bytes)
function y=edr(varargin)
%
% y = edr(data_type,signal,r_peaks,fs,pqoff, jpoff, gain_ecg, channel, show)
%
% ECG-derived Respiratory (EDR) signal computation from given
% single-lead ECG signal based on the signed area under the QRS complex.
%
% Required Parameters:
%
% data_type
% A 1x1 integer specifying the file data_type
% 0 --> if Matlab file
% 1 --> if record in MIT format
%
% signal
% A Nx1 integer array containing the ECG signal in mV (if data_type=0)
% OR a char string containing record name (if data_type=1)
%
% fs
% A 1x1 integer specifying the sampling frequency in hz (for Matlab variables only)
%
% r_peaks
%
% A Mx1 integer array containing locations of r peaks on signal in s
% OR a char string containing the extension of the annotation file
% with r peaks in samples (e.g. "qrs") (if data_type=1)
%
% optional parameters:
%
% gain_ecg
% A 1x1 integer specifying dig_max/phy_max (default=1)
%
% channel
% A 1x1 integer>1 (default=1) indicating ECG channel (if data_type=1)
%
% pqoff
% A 1x1 integer>0 specifying average distance between PQ junction and
% R peak, in samples
%
% jpoff
% A 1x1 integer>0 specifying average distance between R peak and
% J point, in samples
%
% show
% A 1x1 boolean if true, generates a plot of the estimated
% respiration signal (default = 0).
%
%
% output:
%
% y
% A Mx2 integer matrix containing time in seconds and edr
%
% This code was written by Sara Mariani at the Wyss Institute at Harvard
% based on the open-source PhysioNet code edr.c
% (http://www.physionet.org/physiotools/edr/)
% by George Moody
%
% Author: Sara Mariani, 2014
% Last Modified: November 17, 2014
%
% please report bugs/questions at sara.mariani@wyss.harvard.edu
%
% Example - Extract EDR signal from ECG in PhysioNet's Remote server:
% signal='fantasia/f1o02';
% r_peaks='ecg';
% data_type=1;
% channel=2;
% show=1;
% y=edr(data_type,signal,r_peaks,[],[],[],channel,show);
% wfdb2mat('f1o02')
% [~,signal,Fs,~]=rdmat('f1o02m');
% resp=signal(:,1);
% resp=resp-mean(resp);
% resp=resp*200;
% sec=length(resp)/Fs;
% xax=[.25:.25:sec];
% r=interp1(y(:,1), y(:,2), xax, 'spline');
% figure
% plot(xax,r)
% hold on
% plot([1:length(resp)]/Fs,resp,'r')
% legend('edr','respiratory signal')
% xlabel('time (s)')
%
% see also: ecgpuwave, gqrs
%endOfHelp
%Set default pararameter values
inputs={'data_type','signal','r_peaks','fs','pqoff','jpoff', 'gain_ecg', 'channel' ,'show'};
show=0;
Ninputs=length(inputs);
if nargin>Ninputs
error('Too many input arguments');
end
if nargin<3
error('Not enough input arguments');
end
for n=1:nargin
eval([inputs{n} '=varargin{n};']);
end
for n=nargin+1:Ninputs
eval([inputs{n} '=[];']);
end
% check format and obtain all the features I need
if data_type==0 %matlab
if isempty(gain_ecg)
gain_ecg=1;
end
ECGm=signal*gain_ecg;
if isempty(r_peaks)
error('R peaks locations not provided');
else
tqrs=round(r_peaks*fs); %samples where I have the R peak
end
elseif data_type==1 %wfdb record
if isempty(channel)
channel=1;
end
% read the signal
wfdb2mat(signal);
pp=strfind(signal,'/');
if ~isempty(pp)
signal2=signal(pp(end)+1:length(signal));
else signal2=signal;
end
[~,sig,fs]=rdmat([signal2 'm']);
ECGm=sig(:,channel);
if numel(fs)>1
fs=fs(channel);
end
% read the header
siginfo=wfdbdesc(signal);
siginfo=siginfo(:,channel);
gainstring=siginfo.Gain;
sp=strfind(gainstring,' ');
try
gain_ecg=str2num(gainstring(1:sp-1));
catch
gain_ecg=1;
end
if strfind(gainstring(end-1),'m')
gain_ecg=gain_ecg*1000;
end
ECGm=ECGm*gain_ecg;
% read r_peaks if annotation file
if ischar(r_peaks)
[ann,ty]=rdann(signal,r_peaks);
tqrs=ann(ty=='N');
r_peaks=tqrs/fs;
else
tqrs=round(r_peaks*fs); %samples where I have the R peak
end
else error('format data_type must be 0 or 1');
end
% check if signal is upside-down
if mean(ECGm(tqrs))<mean(ECGm)
ECGm=-ECGm;
end
% EDR COMPUTATION
% 1) filter the signal with a moving window of lpflen=25 ms
lpflen=0.025;
lp=round(lpflen*fs);
w=ones(lp+1,1)./(lp+1);
sample=filter(w,1,ECGm);
% correct for the delay of lp/2
sample(1:round(lp/2))=[];
% correct for the initialization
for i=1:round(lp/2)
sample(i)=mean(ECGm(1:i+round(lp/2)));
end
% 2) find the baseline: moving window again of bflen=1 s
bflen=1;
b=round(bflen*fs);
w2=ones(b+1,1)./(b+1);
baseline=filter(w2,1,sample);
% correct for the delay of b/2
baseline(1:round(b/2))=[];
% correct for the initialization
for i=1:round(b/2)
baseline(i)=mean(sample(1:i+round(b/2)));
end
% 3) find average boundaries of QRS interval
if isempty(jpoff)||isempty(pqoff)
[pqoff, jpoff]=boundaries(sample, baseline, tqrs, fs);
end
% now estimate signed area under QRS complex
sb=sample(1:length(baseline))-baseline;
snar=zeros(size(tqrs));
for i=2:length(tqrs)-1
win=sb(tqrs(i)-pqoff:tqrs(i)+jpoff);
snar(i)=sum(win);
end
if tqrs(end)+jpoff>length(sb)
win=sb(tqrs(end)-pqoff:end);
else
win=sb(tqrs(end)-pqoff:tqrs(end)+jpoff);
end
snar(end)=sum(win);
% now start from signed area and estimate edr
xm=0;
xd=0;
xdmax=0;
xc=0;
x=snar;
r=zeros(size(x));
for i=25:length(x)
d=x(i)-xm;
if xc<500
xc=xc+1;
dn=d/xc;
else
dn=d/xc;
if dn>xdmax
dn=xdmax;
elseif dn<-xdmax
dn=-xdmax;
end
end
xm=xm+dn;
xd=xd+abs(dn)-xd/(xc);
if xd<1
xd=1;
end
xdmax=3*xd/(xc);
r(i)=d/xd;
end
y=r*50;
while (max(y)>127 || min(y)<-128)
y(y<-128)=y(y<-128)+255;
y(y>127)=y(y>127)-255;
end
if(show)
scrsz = get(0,'ScreenSize');
figure('Position',...
[0.05*scrsz(3) 0.05*scrsz(4) 0.8*scrsz(3) 0.89*scrsz(4)],...
'Color',[1 1 1]);
ax(1)=subplot(211);
plot([1:length(sample)]/fs,sample);
hold on;
plot([1:length(baseline)]/fs,baseline,'g');
plot((tqrs-pqoff)/fs,mean(ECGm)*ones(size(tqrs)),'*m');
plot((tqrs+jpoff)/fs,mean(ECGm)*ones(size(tqrs)),'*c');
legend('filtered ecg','baseline','window start','window end');
set(gca,'fontsize',18);
xlabel('time (s)','fontsize',18);
ylim([mean(ECGm)-5*std(ECGm) mean(ECGm)+5*std(ECGm)]);
ax(2)=subplot(212);
plot(r_peaks,y,'r');
title('edr','fontsize',18);
set(gca,'fontsize',18);
xlabel('time (s)','fontsize',18);
linkaxes(ax,'x');
end
y=[r_peaks y];
end
%%%% Helper function %%%%%%
function[pqoff, jpoff]=boundaries(sample, baseline, tqrs, fs)
% estimate the noise level
sb=sample(1:length(baseline))-baseline;
nlest=mean(abs(sb));
display(['The estimated noise level is ' num2str(nlest) ' microvolts']);
dlthresh=2*nlest;
dlthmax=1200;
dlthmin=140;
if dlthresh>dlthmax, dlthresh=dlthmax;
elseif dlthresh<dlthmin, dlthresh=dlthmin;
end
% determine if samples are baseline
vwindow=100;
twin1=0.033;
twin2=0.067;
% time of the 51st QRS
last=tqrs(51);
sample2=sample(1:last);
bline=zeros(size(sample2));
% a sample is baseline if I have twin1 or twin2 consecutive samples
% that vary in amplitude by no more than dlthresh
for i=1:length(sample2)-twin1*fs
vmax=sample(i);
vmin=sample(i);
if abs(baseline(i)-vmax)<vwindow, twindow=twin1;
else twindow=twin2;
end
ww=sample(i:i+round(twindow*fs));
if max(ww)-min(ww)<dlthresh
bline(i)=1;
end
end
% for first 50 beats, look for PQ junction and J point
tlim2=0.060;
tlim3=0.100;
PQ=zeros(50,1);
J=zeros(50,1);
for j=1:50
% search to the left
try
w=bline(round(tqrs(j)-tlim2*fs):tqrs(j)-1);
catch
display(j);
w=bline(1:tqrs(j)-1);
end
f=find(w);
if numel(f)>0
PQ(j)=length(w)-max(f)+1;
else
PQ(j)=length(w);
end
% search to the right
w=bline(tqrs(j)+1:round(tqrs(j)+tlim3*fs));
f=find(w);
if numel(f)>0
J(j)=min(f);
else
J(j)=length(w);
end
end
% incremental average
pqoff=PQ(1);
for i=1:length(PQ)
if PQ(i)<pqoff
pqoff=pqoff-1;
elseif PQ(i)>pqoff
pqoff=pqoff+1;
end
end
jpoff=J(1);
for i=1:length(J)
if J(i)<jpoff
jpoff=jpoff-1;
elseif J(i)>jpoff
jpoff=jpoff+1;
end
end
end