a b/featurebased-approach/subfunctions/lib/JQRS/jqrs.m
1
function qrs_pos = jqrs(ecg,varargin)
2
% QRS detector based on the P&T method. This is an offline implementation
3
% of the detector.
4
%
5
% inputs
6
%   ecg:            one ecg channel on which to run the detector (required)
7
%                   in [mV]
8
%   varargin
9
%       THRES:      energy threshold of the detector (default: 0.6)
10
%                   [arbitrary units]
11
%       REF_PERIOD: refractory period in sec between two R-peaks (default: 0.250)
12
%                   in [ms]
13
%       fs:         sampling frequency (default: 1KHz) [Hz]
14
%       fid_vec:    if some subsegments should not be used for finding the
15
%                   optimal threshold of the P&Tthen input the indices of
16
%                   the corresponding points here
17
%       SIGN_FORCE: force sign of peaks (positive value/negative value).
18
%                   Particularly usefull if we do window by window detection and want to
19
%                   unsure the sign of the peaks to be the same accross
20
%                   windows (which is necessary to build an FECG template)
21
%       debug:      1: plot to bebug, 0: do not plot
22
%
23
% outputs
24
%   qrs_pos:        indexes of detected peaks (in samples)
25
%   sign:           sign of the peaks (a pos or neg number)
26
%   en_thres:       energy threshold used
27
%
28
%
29
%
30
% Physionet Challenge 2014, version 1.0
31
% Released under the GNU General Public License
32
%
33
% Copyright (C) 2014  Joachim Behar
34
% Oxford university, Intelligent Patient Monitoring Group
35
% joachim.behar@eng.ox.ac.uk
36
%
37
% Last updated : 13-09-2014
38
% - bug on refrac period fixed
39
% - sombrero hat for prefiltering added
40
% - code a bit more tidy
41
% - condition added on flatline detection for overall segment (if flatline
42
% then returns empty matrices rather than some random stuff)
43
%
44
% This program is free software; you can redistribute it and/or modify it
45
% under the terms of the GNU General Public License as published by the
46
% Free Software Foundation; either version 2 of the License, or (at your
47
% option) any later version.
48
% This program is distributed in the hope that it will be useful, but
49
% WITHOUT ANY WARRANTY; without even the implied warranty of
50
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
51
% Public License for more details.
52
53
% == managing inputs
54
if nargin > 8
55
    error('Too many arguments')
56
end
57
58
% optional input
59
optargs = {0.250 0.6 1000 [] false 0};
60
newVals = cellfun(@(x) ~isempty(x), varargin);
61
optargs(newVals) = varargin(newVals);
62
[REF_PERIOD, THRES, fs, fid_vec,SIGN_FORCE,debug] = optargs{:};
63
SEARCH_BACK = 1; % perform search back (FIXME: should be in function param)
64
65
if size(ecg,2) > size(ecg,1); ecg = ecg'; end;
66
67
% == constants
68
NB_SAMP=length(ecg);
69
tm = 1/fs:1/fs:ceil(NB_SAMP/fs);
70
MED_SMOOTH_NB_COEFF = round(fs/100);
71
INT_NB_COEFF = round(0.1*fs); % length is 100 ms
72
MAX_FORCE = []; % if you want to force the energy threshold value (FIXME: should be in function param)
73
NB_SAMP = length(ecg); % number of input samples
74
75
76
% == Bandpass filtering for ECG signalREF_PERIOD
77
% this sombrero hat has shown to give slightly better results than a
78
% standard band-pass filter. Plot the frequency response to convince
79
% yourself of what it does
80
b1 = [-7.757327341237223e-05  -2.357742589814283e-04 -6.689305101192819e-04 -0.001770119249103 ...
81
    -0.004364327211358 -0.010013251577232 -0.021344241245400 -0.042182820580118 -0.077080889653194...
82
    -0.129740392318591 -0.200064921294891 -0.280328573340852 -0.352139052257134 -0.386867664739069 ...
83
    -0.351974030208595 -0.223363323458050 0 0.286427448595213 0.574058766243311 ...
84
    0.788100265785590 0.867325070584078 0.788100265785590 0.574058766243311 0.286427448595213 0 ...
85
    -0.223363323458050 -0.351974030208595 -0.386867664739069 -0.352139052257134...
86
    -0.280328573340852 -0.200064921294891 -0.129740392318591 -0.077080889653194 -0.042182820580118 ...
87
    -0.021344241245400 -0.010013251577232 -0.004364327211358 -0.001770119249103 -6.689305101192819e-04...
88
    -2.357742589814283e-04 -7.757327341237223e-05];
89
90
b1 = resample(b1,fs,250);
91
bpfecg = filtfilt(b1,1,ecg)';
92
93
94
95
% == P&T operations
96
dffecg = diff(bpfecg');  % (4) differentiate (one datum shorter)
97
sqrecg = dffecg.*dffecg; % (5) square ecg
98
intecg = filter(ones(1,INT_NB_COEFF),1,sqrecg); % (6) integrate
99
mdfint = medfilt1(intecg,MED_SMOOTH_NB_COEFF);  % (7) smooth
100
delay  = ceil(INT_NB_COEFF/2);
101
mdfint = circshift(mdfint,-delay); % remove filter delay for scanning back through ECG
102
103
% look for some measure of signal quality with signal fid_vec? (FIXME)
104
if isempty(fid_vec); mdfintFidel = mdfint; else mdfintFidel(fid_vec>2) = 0; end;
105
106
% == P&T threshold
107
if NB_SAMP/fs>90; xs=sort(mdfintFidel(fs:fs*90)); else xs = sort(mdfintFidel(fs:end)); end;
108
109
if isempty(MAX_FORCE)
110
    ind_xs = ceil(0.98*length(xs));
111
    en_thres = xs(ind_xs); % else 98% CI
112
else
113
    en_thres = MAX_FORCE;
114
end
115
116
% build an array of segments to look into
117
poss_reg = mdfint>(THRES*en_thres);
118
119
% in case empty because force threshold and crap in the signal
120
if isempty(poss_reg); poss_reg(10) = 1; end;
121
122
% == P&T QRS detection & search back
123
if SEARCH_BACK
124
    indAboveThreshold = find(poss_reg); % ind of samples above threshold
125
    RRv = diff(tm(indAboveThreshold));  % compute RRv
126
    medRRv = median(RRv(RRv>0.01));
127
    indMissedBeat = find(RRv>1.5*medRRv); % missedSIGN_FORCE a peak?
128
    % find interval onto which a beat might have been missed
129
    indStart = indAboveThreshold(indMissedBeat);
130
    indEnd = indAboveThreshold(indMissedBeat+1);
131
    
132
    for i=1:length(indStart)
133
        % look for a peak on this interval by lowering the energy threshold
134
        poss_reg(indStart(i):indEnd(i)) = mdfint(indStart(i):indEnd(i))>(0.5*THRES*en_thres);
135
    end
136
end
137
138
% find indices into boudaries of each segment
139
left  = find(diff([0 poss_reg'])==1);  % remember to zero pad at start
140
right = find(diff([poss_reg' 0])==-1); % remember to zero pad at end
141
142
% Better align to peaks (independent if max/min)
143
qs = num2cell([left' right'],2);
144
[~,lag]=cellfun(@(x) max(abs(ecg(x(1):x(2)))),qs);
145
qrs_pos = left' + lag -1;
146
d = diff(qrs_pos); % remove doubles
147
qrs_pos([false; d<REF_PERIOD])=[];
148
149
% == plots
150
if debug
151
    figure;
152
    FONTSIZE = 20;
153
    ax(1) = subplot(4,1,1); plot(tm,ecg); hold on;plot(tm,bpfecg,'r')
154
    title('raw ECG (blue) and zero-pahse FIR filtered ECG (red)'); ylabel('ECG');
155
    xlim([0 tm(end)]);  hold off;
156
    ax(2) = subplot(4,1,2); plot(tm(1:length(mdfint)),mdfint);hold on;
157
    plot(tm,max(mdfint)*bpfecg/(2*max(bpfecg)),'r',tm(left),mdfint(left),'og',tm(right),mdfint(right),'om');
158
    title('Integrated ecg with scan boundaries over scaled ECG');
159
    ylabel('Int ECG'); xlim([0 tm(end)]); hold off;
160
    ax(3) = subplot(4,1,3); plot(tm,bpfecg,'r');hold on;
161
    plot(tm(qrs_pos),bpfecg(qrs_pos),'+k');
162
    title('ECG with R-peaks (black) and S-points (green) over ECG')
163
    ylabel('ECG+R+S'); xlim([0 tm(end)]); hold off;
164
   
165
    
166
    %linkaxes(ax,'x');
167
    set(gca,'FontSize',FONTSIZE);
168
    allAxesInFigure = findall(gcf,'type','axes');
169
    set(allAxesInFigure,'fontSize',FONTSIZE);
170
end
171
172
173
% NOTES
174
%   Finding the P&T energy threshold: in order to avoid crash due to local
175
%   huge bumps, threshold is choosen at 98-99% of amplitude distribution.
176
%   first sec removed for choosing the thres because of filter init lag.
177
%
178
%   Search back: look for missed peaks by lowering the threshold in area where the
179
%   RR interval variability (RRv) is higher than 1.5*medianRRv
180
%
181
%   Sign of the QRS (signForce): look for the mean sign of the R-peak over the
182
%   first 30sec when looking for max of abs value. Then look for the
183
%   R-peaks over the whole record that have this given sign. This allows to
184
%   not alternate between positive and negative detections which might
185
%   happen in some occasion depending on the ECG morphology. It is also
186
%   better than forcing to look for a max or min systematically.
187
188