[cc7dc8]: / featurebased-approach / subfunctions / morphofeatures.m

Download this file

186 lines (166 with data), 7.1 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
function feats = morphofeatures(signal,fs,qrs,recordName)
% This function obtains morphological features from raw ECG data.
%
% --
% ECG classification from single-lead segments using Deep Convolutional Neural
% Networks and Feature-Based Approaches - December 2017
%
% Released under the GNU General Public License
%
% Copyright (C) 2017 Fernando Andreotti, Oliver Carr
% University of Oxford, Insitute of Biomedical Engineering, CIBIM Lab - Oxford 2017
% fernando.andreotti@eng.ox.ac.uk
%
%
% For more information visit: https://github.com/fernandoandreotti/cinc-challenge2017
%
% Referencing this work
%
% Andreotti, F., Carr, O., Pimentel, M.A.F., Mahdi, A., & De Vos, M. (2017).
% Comparing Feature Based Classifiers and Convolutional Neural Networks to Detect
% Arrhythmia from Short Segments of ECG. In Computing in Cardiology. Rennes (France).
%
% Last updated : December 2017
%
% 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 3 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, see <http://www.gnu.org/licenses/>.
fsnew = 250;
gain = 3000;
T_LENGTH = 200; % about a second
NB_BINS = 250;
Nfeats = 17;
% Bandpass filter for morphological analysis
Fhigh = 1; % highpass frequency [Hz]
Flow = 100; % low pass frequency [Hz]
Nbut = 12; % order of Butterworth filter
d_bp= design(fdesign.bandpass('N,F3dB1,F3dB2',Nbut,Fhigh,Flow,fs),'butter');
[b_bp2,a_bp2] = tf(d_bp);
clear Fhigh Flow Nbut d_bp
signal = filtfilt(b_bp2,a_bp2,signal); % filtering
signal = detrend(signal);
recordName = ['pu' recordName];
% Resampling for ECGPUWAVE
sigres=resample(signal,fsnew,fs);
qrsfinal = round(qrs{end}*fsnew/fs);
% Morphological features
%% Running ECGPUWAVE for normal beats
% == Using average normal beat
try
[M,RES] = stackbeats(sigres,qrsfinal,T_LENGTH,NB_BINS);
template = mean(M,2);
template = resample(template,RES,NB_BINS);
template = detrend(template);
fake_sig = repmat(template,1,20);
smoothvec = [0:0.1:0.9, ones(1,size(fake_sig,1)-20),0.9:-0.1:0]';
fake_sig = bsxfun(@times,fake_sig,smoothvec); % smoothing edges
fake_sig = reshape(fake_sig,[],1);
fake_sig = detrend(fake_sig);
tm = 1/fsnew:1/fsnew:length(fake_sig)/fsnew;
tm = tm'-1/fsnew;
[~,I] = max(abs(fake_sig));
wsign = sign(fake_sig(I)); % looking for signal sign
qrsfake = cumsum([0 repmat(RES,1,19)]) + floor(RES/2);
fake_sig = 2*gain*wsign(1)*fake_sig/max(abs(fake_sig));
wrsamp(tm,fake_sig,recordName,fsnew,gain,'')
wrann(recordName,'qrs',qrsfake-1,repmat('N',length(qrsfake),1));
if isunix
system(['ecgpuwave -r ' recordName '.hea' ' -a ecgpu -i qrs']);
else
ecgpuwave(recordName,'ecgpu',[],[],'qrs'); % important to specify the QRS because it seems that ecgpuwave is crashing sometimes otherwise
end
[allref,alltypes_r] = rdann(recordName,'ecgpu');
fake_sig = fake_sig./gain;
featavgnorm = FECGSYN_QTcalc(alltypes_r,allref,fake_sig,fsnew);
feats = struct2array(featavgnorm);
feats(isnan(feats)) = 0;
catch
warning('ecgpuwave NORMAL average has failed.')
feats = zeros(1,Nfeats);
end
delete([recordName '.*'])
end
function feats = FECGSYN_QTcalc(ann_types,ann_stamp,signal,fs)
temp_types = ann_types; % allows exclusion of unsuitable annotations
temp_stamp = ann_stamp;zeros(1,10);
feats = struct('QRSheight',0,'QRSwidth',0,'QRSpow',0,...
'noPwave',0,'Pheight',0,'Pwidth',0,'Ppow',0,...
'Theight',0,'Twidth',0,'Tpow',0,...
'Theightnorm',0,'Pheightnorm',0,'Prelpow',0,...
'PTrelpow',0,'Trelpow',0,'QTlen',0,'PRlen',0);
%== Treat biphasic T-waves
annstr = strcat({temp_types'});
idxbi=cell2mat(regexp(annstr,'tt')); % biphasic
nonbi2= cell2mat(regexp(annstr,'\)t\(')) +1; % weird t
nonbi3= cell2mat(regexp(annstr,'\)t\)')) +1; % weird t2
if sum(idxbi) > 0 % case biphasic waves occured
posmax = [idxbi' idxbi'+1];
[~,bindx]=max(abs(signal(ann_stamp(posmax))),[],2); % max abs value between tt
clearidx = [idxbi+double(bindx'==1) nonbi2 nonbi3];
%idxbi = idxbi + bindx -1; % new index to consider
temp_types(clearidx) = []; % clearing biphasic cases
temp_stamp(clearidx) = [];
end
clear biphasic teesfollowed nonbi1 nonbi2 nonbi3 bindx clearidx posmax idxbi
%== Remove incomplete beats
comp=cell2mat(regexp(cellstr(temp_types'),'\(p\)\(N\)\(t\)')); % regular
beats = temp_stamp(cell2mat(arrayfun(@(x) x:x+8,comp','uniformoutput',0)));
skipP = false;
if isempty(beats)
comp=cell2mat(regexp(cellstr(temp_types'),'\(t\)\(N\)\(t\)')); % missing P-wave
if isempty(comp), return, end % if ECGPUWAVE does not really work then output zeros
beats = temp_stamp(cell2mat(arrayfun(@(x) x:x+8,comp','uniformoutput',0)));
skipP = true;
feats.noPwave = 1;
else
feats.noPwave = 0;
end
if size(beats,2)==1; beats = beats';end % case single beat is available
Pstart = beats(:,1);
validP = beats(:,2);
Pend = beats(:,3);
Rstart = beats(:,4);
validR = beats(:,5);
Rend = beats(:,6);
Tstart = beats(:,7);
validT = beats(:,8);
Tend = beats(:,9);
%% Calculating features
feats.QRSheight = abs(median(min([signal(Rstart) signal(Rend)],[],2) - signal(validR))); % T-wave height
feats.QRSwidth = median(Rend - Rstart)/fs; % T-wave width
feats.QRSpow = sum(signal(Rstart:Rend).^2);
if ~skipP
feats.Pheight = abs(median(median([signal(Pstart) signal(Pend)],2) - signal(validP))); % P-wave height
feats.Pwidth = median(Pend - Pstart)/fs; % P-wave width
feats.Ppow = sum(signal(Pstart:Pend).^2);
end
feats.Theight = abs(median(median([signal(Tstart) signal(Tend)],2) - signal(validT))); % T-wave height
feats.Twidth = median(Tend - Tstart)/fs; % T-wave width
feats.Tpow = sum(signal(Tstart:Tend).^2);
feats.Theightnorm = feats.Theight/feats.QRSheight;
feats.Trelpow = feats.Tpow/feats.QRSpow;
if ~skipP
feats.Pheightnorm = feats.Pheight/feats.QRSheight;
feats.Prelpow = feats.Ppow/feats.QRSpow;
feats.PTrelpow = feats.Ppow/feats.Tpow;
end
QT_MAX = 0.5*fs; % Maximal QT length (in s) MAY VARY DEPENDING ON APPLICATION!
QT_MIN = 0.1*fs; % Minimal QT length (in s) MAY VARY DEPENDING ON APPLICATION!
[~,dist] = dsearchn(Rstart,Tend); % closest ref for each point in test qrs
feats.QTlen = median(dist(dist<QT_MAX&dist>QT_MIN))/fs;
if ~skipP
PR_MAX = 0.3*fs; % Maximal QT length (in s) MAY VARY DEPENDING ON APPLICATION!
PR_MIN = 0.05*fs; % Minimal QT length (in s) MAY VARY DEPENDING ON APPLICATION!
[~,dist] = dsearchn(validP,validR); % closest ref for each point in test qrs
feats.PRlen = median(dist(dist<PR_MAX&dist>PR_MIN))/fs;
end
end