|
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 |
|