Switch to unified view

a b/featurebased-approach/subfunctions/beat_class.m
1
function [type,beats,beatsuct] = beat_class(ecg,qrs,T_LENGTH)
2
% this function is used to contruct a template ecg based on the location of
3
% the R-peaks. A series of peaks that match with each other are stacked to
4
% build a template. This template can then be used for ecg morphological
5
% analysis or further processing. Note that the qrs location inputed must
6
% be as precise as possible. This approach for building the template ECG
7
% seems to be the best of the alternatives that were tested and leaves the
8
% freedom of having more than one mode (i.e. multiple ECG template can be
9
% built if there are different cycle morphology such as PVC)  but it is not
10
% particularly fast.
11
% The procedure for building the template is:
12
% 1. create average wrapped template
13
% 2. identify different modes present
14
%
15
% inputs
16
%   ecg:            the ecg channel(s)
17
%   qrs:            qrs location [number of samples]
18
%   T_LENGTH        template length
19
% 
20
% --
21
% ECG classification from single-lead segments using Deep Convolutional Neural 
22
% Networks and Feature-Based Approaches - December 2017
23
% 
24
% Released under the GNU General Public License
25
%
26
% Copyright (C) 2017  Fernando Andreotti, Oliver Carr
27
% University of Oxford, Insitute of Biomedical Engineering, CIBIM Lab - Oxford 2017
28
% fernando.andreotti@eng.ox.ac.uk
29
%
30
% 
31
% For more information visit: https://github.com/fernandoandreotti/cinc-challenge2017
32
% 
33
% Referencing this work
34
%
35
% Andreotti, F., Carr, O., Pimentel, M.A.F., Mahdi, A., & De Vos, M. (2017). 
36
% Comparing Feature Based Classifiers and Convolutional Neural Networks to Detect 
37
% Arrhythmia from Short Segments of ECG. In Computing in Cardiology. Rennes (France).
38
%
39
% Last updated : December 2017
40
% 
41
% This program is free software: you can redistribute it and/or modify
42
% it under the terms of the GNU General Public License as published by
43
% the Free Software Foundation, either version 3 of the License, or
44
% (at your option) any later version.
45
% 
46
% This program is distributed in the hope that it will be useful,
47
% but WITHOUT ANY WARRANTY; without even the implied warranty of
48
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
49
% GNU General Public License for more details.
50
% 
51
% You should have received a copy of the GNU General Public License
52
% along with this program.  If not, see <http://www.gnu.org/licenses/>.
53
54
55
% == manage inputs
56
if nargin<2; error('ecg_template_build: wrong number of input arguments \n'); end;
57
58
if size(ecg,1)>size(ecg,2), ecg = ecg';end
59
60
% == constants
61
% qrs(1) = []; qrs(end) = [];                                % remove extremity peaks
62
extremities = (qrs <= 2*T_LENGTH | qrs >= length(ecg)-2*T_LENGTH);        % test if there are peaks on the border that may lead to error
63
qrs = round(qrs(~extremities)); % remove extremity peaks
64
65
%% Phase wrap to get all within 2*pi
66
NB_BINS = 250; % number of bins for wrapping
67
[M,RES] = stackbeats(ecg,qrs,T_LENGTH,NB_BINS);
68
    
69
70
% beatsuct = median(M')';
71
% dem = round((length(beatsuct)-2*T_LENGTH-1)/2); 
72
% beats{1} =  beatsuct(dem+1:end-dem);
73
% type = ones(length(qrs),1);
74
75
%% Clustering
76
Nclus1 = 5;
77
Nclus2 = 2;
78
M = bsxfun(@times,M,hamming(size(M,1)));  % do it twice, it's the
79
M = bsxfun(@times,M,hamming(size(M,1)));  % magic sauce
80
M = bsxfun(@times,M,hamming(size(M,1)));  % magic sauce
81
M = M';
82
% [~, clusters, ~] = fkmeans(M',Nclus);
83
rng(1);
84
[~,clusters,~] = kmeans(M,Nclus1); % may need to provide centroid..
85
[~,clusters,~] = kmeans(clusters,Nclus2); % may need to provide centroid..
86
clusters = clusters';
87
88
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
89
% TODO: allow more than two clusters!!!!!!!!!!!!!!!!!!!!
90
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
91
92
corel = corrcoef(clusters(50:200,1),clusters(50:200,2));
93
94
95
%% In case beats are correlated to each other
96
if corel(1,2) > 0.7 
97
    type = ones(size(qrs));
98
    beats{1} = resample(mean([clusters(:,1), clusters(:,2)],2),RES,NB_BINS);    
99
    dem = (length(beats{1})-2*T_LENGTH-1)/2;   
100
    beatsuct = beats;
101
    beats = cellfun(@(x) x(dem+1:end-dem),beats,'UniformOutput',0);
102
    return;
103
end
104
105
%% In case one cluster has low amplitude
106
107
idx = var(clusters)<0.1*var(ecg); % remove clusters with variance less than 10% of signal
108
if any(idx)
109
   type = ones(size(qrs));
110
   beats{1} = resample(clusters(:,~idx),RES,NB_BINS);
111
   dem = round((length(beats{1})-2*T_LENGTH-1)/2);      
112
   beatsuct = beats;
113
   beats = cellfun(@(x) x(dem+1:end-dem),beats,'UniformOutput',0);
114
   return; 
115
end
116
117
118
%% Classifying beats
119
120
beats{1} = resample(clusters(:,1),RES,NB_BINS);
121
beats{2} = resample(clusters(:,2),RES,NB_BINS);
122
beatsuct = beats;
123
dem = (length(beats{1})-2*T_LENGTH-1)/2;
124
beats = cellfun(@(x) x(dem+1:end-dem),beats,'UniformOutput',0);
125
    
126
for i = 1:length(qrs)
127
    cor = corrcoef(ecg(qrs(i)-T_LENGTH:qrs(i)+T_LENGTH),beats{1});
128
    corel(1,i) = cor(1,2);
129
    cor = corrcoef(ecg(qrs(i)-T_LENGTH:qrs(i)+T_LENGTH),beats{2});
130
    corel(2,i) = cor(1,2);
131
end
132
[~,type]=max(corel);
133
134
if sum(type==2)>sum(type==1)
135
    type(type==2) = 3;
136
    type(type==1) = 2;
137
    type(type==3) = 2;
138
end
139
140
if all(type==2)
141
    type = ones(size(type));
142
end
143
type = type';
144
    
145
end
146
147