PhysioNet Cardiovascular Signal Toolbox 1.0.0
(10,657 bytes)
function [qrs,jpoints]=wqrsm(data,fs,PWfreq,TmDEF,jflag)
% The WQRS detector rewrited from wqrs.c
% rewrite by Qiao Li, March 6, 2017
% qiaolibme@gmail.com
%
% input:
% data: ECG data, the wqrs.c used the data in raw adus, if the input
% data is in physical units, when the function found the peak-peak
% value < 10, it will multiply the value by WFDB_DEFGAIN (200)
% fs: sampling frequency (default: 125)
% PWfreq: power line (mains) frequency, in Hz (default: 60)
% TmDEF: minimum threshold value (default: 100)
% jflag: annotate J-points (ends of QRS complexes) (default: 0)
% output:
% qrs: QRS fiducial mark in samples
% jpoints:J-points annotation, if jflag==1
%
% Matlab code based on:
% file: wqrs.c Wei Zong 23 October 1998
% Last revised: 9 April 2010 (by G. Moody)
% -----------------------------------------------------------------------------
% wqrs: Single-lead QRS detector based on length transform
% Copyright (C) 1998-2010 Wei Zong
%
% 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.
%
% You should have received a copy of the GNU General Public License along with
% this program; if not, write to the Free Software Foundation, Inc., 59 Temple
% Place - Suite 330, Boston, MA 02111-1307, USA.
%
% You may contact the author by e-mail (wzong@mit.edu) or postal mail
% (MIT Room E25-505, Cambridge, MA 02139, USA). For updates to this software,
% please visit PhysioNet (http://www.physionet.org/).
% ------------------------------------------------------------------------------
%
% This program analyzes an ECG signal, detecting QRS onsets and J-points, using
% a nonlinearly-scaled ECG curve length feature. This version has been optimized
% for ECGs sampled at 125 Hz, but it can analyze ECGs sampled at any frequency
% using on-the-fly resampling provided by the WFDB library.
%
% `wqrs' can process records containing any number of signals, but it uses only
% one signal for QRS detection (signal 0 by default; this can be changed using
% the `-s' option, see below). 'wqrs' has been optimized for adult human ECGs.
% For other ECGs, it may be necessary to experiment with the input sampling
% frequency and the time constants.
% ----------------------------------------------------------------------
% This matlab version has updates to deal with a variable sampling
% frequency but has not been exhanstively tested.
% ----- Add notes here:
%
%
if nargin<5
jflag = 0;
end
if nargin<4
TmDEF = 100;
end
if nargin<3
PWfreq = 60;
end
if nargin<2
fs = 125;
end
clear global wqrsm_Yn wqrsm_Yn1 wqrsm_Yn2 wqrsm_lt_tt wqrsm_lbuf wqrsm_ebuf wqrsm_aet wqrsm_et wqrsm_LPn wqrsm_LP2n wqrsm_lfsc wqrsm_data wqrsm_BUFLN wqrsm_LTwindow
global wqrsm_Yn wqrsm_Yn1 wqrsm_Yn2;
global wqrsm_lt_tt wqrsm_lbuf wqrsm_ebuf;
global wqrsm_aet wqrsm_et;
global wqrsm_LPn wqrsm_LP2n wqrsm_lfsc;
global wqrsm_data;
global wqrsm_BUFLN;
global wqrsm_LTwindow;
wqrsm_BUFLN = 16384; % /* must be a power of 2, see ltsamp() */
EYE_CLS = 0.25; % /* eye-closing period is set to 0.25 sec (250 ms) */
MaxQRSw = 0.13; % /* maximum QRS width (130ms) */
NDP = 2.5; % /* adjust threshold if no QRS found in NDP seconds */
PWFreqDEF = PWfreq; % /* power line (mains) frequency, in Hz (default) */
% TmDEF = 100; % /* minimum threshold value (default) */
WFDB_DEFGAIN = 200.0; % /* default value for gain (adu/physical unit) */
timer_d=0;
gain=WFDB_DEFGAIN;
wqrsm_lfsc = fix(1.25*gain*gain/fs); % /* length function scale constant */
PWFreq = PWFreqDEF; % /* power line (mains) frequency, in Hz */
% test data is physical units (mV) or raw adus units
datatest=data(1:fix(length(data)/fs)*fs);
if length(datatest)>fs
datatest=reshape(datatest,fs,[]);
end
test_ap=median(max(datatest)-min(datatest));
if test_ap<10 % peak-peak < 10 mV, may physical units
data=data*gain;
end
wqrsm_data = data;
wqrsm_lbuf = zeros(wqrsm_BUFLN,1);
wqrsm_ebuf = zeros(wqrsm_BUFLN,1);
wqrsm_ebuf(1:end)=fix(sqrt(wqrsm_lfsc));
wqrsm_lt_tt = 0;
wqrsm_aet = 0;
wqrsm_Yn = 0;
wqrsm_Yn1 = 0;
wqrsm_Yn2 = 0;
qrs = [];
jpoints = [];
Tm = fix(TmDEF / 5.0);
% spm = 60 * fs;
% next_minute = from + spm;
wqrsm_LPn = fix(fs/PWFreq); % /* The LP filter will have a notch at the power line (mains) frequency */
if (wqrsm_LPn > 8)
wqrsm_LPn = 8; % /* avoid filtering too agressively */
end
wqrsm_LP2n = 2 * wqrsm_LPn;
EyeClosing = fix(fs * EYE_CLS); % /* set eye-closing period */
ExpectPeriod = fix(fs * NDP); % /* maximum expected RR interval */
wqrsm_LTwindow = fix(fs * MaxQRSw); % /* length transform window size */
% for i=1:2000
% ltdata(i)=ltsamp(i);
% end
% /* Average the first 8 seconds of the length-transformed samples
% to determine the initial thresholds Ta and T0. The number of samples
% in the average is limited to half of the ltsamp buffer if the sampling
% frequency exceeds about 2 KHz. */
t1 = fs*8;
if t1> fix(wqrsm_BUFLN*0.9)
t1=wqrsm_BUFLN/2;
end
T0=0;
for t=1:t1
T0 = T0 + ltsamp(t);
end
T0 = T0 / t1;
Ta = 3 * T0;
% /* Main loop */
t=1;
learning=1;
while t<length(data)
if (learning==1)
if (t > t1)
learning = 0;
T1 = T0;
t = 1; % /* start over */
else
T1 = 2*T0;
end
end
% /* Compare a length-transformed sample against T1. */
if (ltsamp(t) > T1) % /* found a possible QRS near t */
timer_d = 0; % /* used for counting the time after previous QRS */
maxd = ltsamp(t);
mind = maxd;
for tt = t+1:t + fix(EyeClosing/2)
if (ltsamp(tt) > maxd)
maxd = ltsamp(tt);
end
end
for tt = t-1:-1:t - fix(EyeClosing/2)
if (ltsamp(tt) < mind)
mind = ltsamp(tt);
end
end
if (maxd > mind+10) % /* There is a QRS near tt */
% /* Find the QRS onset (PQ junction) */
onset = fix(maxd/100) + 2;
tpq = t - 5;
for tt = t:-1:t - fix(EyeClosing/2)
if (ltsamp(tt) - ltsamp(tt-1) < onset && ltsamp(tt-1) - ltsamp(tt-2) < onset && ltsamp(tt-2) - ltsamp(tt-3) < onset && ltsamp(tt-3) - ltsamp(tt-4) < onset)
tpq = tt - wqrsm_LP2n; % /* account for phase shift */
break;
end
end
if (learning~=1)
% /* Check that we haven't reached the end of the record. */
if tpq>length(data)
break;
end
% /* Record an annotation at the QRS onset */
qrs = [qrs tpq];
% J-points processing
if (jflag)
tj = t+5;
for tt=t:t + fix(EyeClosing/2)
if ltsamp(tt) > maxd - fix(maxd/10)
tj = tt;
break;
end
end
if tj>length(data)
break;
end
% Record an annotation at the J-point
jpoints = [jpoints tj];
end
end
% /* Adjust thresholds */
Ta = Ta + (maxd - Ta)/10;
T1 = Ta / 3;
% /* Lock out further detections during the eye-closing period */
t = t + EyeClosing;
end
elseif (learning~=1)
% /* Once past the learning period, decrease threshold if no QRS
% was detected recently. */
timer_d = timer_d + 1;
if timer_d > ExpectPeriod && Ta > Tm
Ta = Ta -1;
T1 = Ta / 3;
end
end
t = t+1;
end
end
function lt_data = ltsamp(t)
% /* ltsamp() returns a sample of the length transform of the input at time t.
% Since this program analyzes only one signal, ltsamp() does not have an
% input argument for specifying a signal number; rather, it always filters
% and returns samples from the signal designated by the global variable
% 'sig'. The caller must never "rewind" by more than wqrsm_BUFLN samples (the
% length of ltsamp()'s buffers). */
global wqrsm_Yn wqrsm_Yn1 wqrsm_Yn2;
global wqrsm_lt_tt wqrsm_lbuf wqrsm_ebuf;
global wqrsm_aet wqrsm_et;
global wqrsm_LPn wqrsm_LP2n wqrsm_lfsc;
global wqrsm_data;
global wqrsm_BUFLN;
global wqrsm_LTwindow;
while (t > wqrsm_lt_tt)
wqrsm_Yn2 = wqrsm_Yn1;
wqrsm_Yn1 = wqrsm_Yn;
v0=wqrsm_data(1);
v1=wqrsm_data(1);
v2=wqrsm_data(1);
if wqrsm_lt_tt>0 && wqrsm_lt_tt<=length(wqrsm_data)
v0 = wqrsm_data(wqrsm_lt_tt);
end
if wqrsm_lt_tt-wqrsm_LPn>0 && (wqrsm_lt_tt-wqrsm_LPn)<=length(wqrsm_data)
v1 = wqrsm_data(wqrsm_lt_tt-wqrsm_LPn);
end
if wqrsm_lt_tt-wqrsm_LP2n>0 && (wqrsm_lt_tt-wqrsm_LP2n)<=length(wqrsm_data)
v2 = wqrsm_data(wqrsm_lt_tt-wqrsm_LP2n);
end
if v0~=-32768 && v1~=-32768 && v2~=-32768
wqrsm_Yn = 2*wqrsm_Yn1 - wqrsm_Yn2 + v0 - 2*v1 + v2;
end
dy = fix((wqrsm_Yn - wqrsm_Yn1) / wqrsm_LP2n); % /* lowpass derivative of input */
wqrsm_lt_tt=wqrsm_lt_tt+1;
wqrsm_et = fix(sqrt(wqrsm_lfsc +dy*dy)); % /* length transform */
id = mod(wqrsm_lt_tt,wqrsm_BUFLN);
if id == 0
id = wqrsm_BUFLN;
end
wqrsm_ebuf(id) = wqrsm_et;
id2 = mod(wqrsm_lt_tt-wqrsm_LTwindow,wqrsm_BUFLN);
if id2 == 0
id2 = wqrsm_BUFLN;
end
wqrsm_aet = wqrsm_aet + (wqrsm_et - wqrsm_ebuf(id2));
wqrsm_lbuf(id) = wqrsm_aet;
% /* wqrsm_lbuf contains the average of the length-transformed samples over
% the interval from tt-wqrsm_LTwindow+1 to tt */
end
id3 = mod(t,wqrsm_BUFLN);
if id3 == 0
id3 = wqrsm_BUFLN;
end
lt_data = wqrsm_lbuf(id3);
end