-
Notifications
You must be signed in to change notification settings - Fork 1
/
freqHRV.m
257 lines (223 loc) · 6.77 KB
/
freqHRV.m
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
function output = freqHRV(ibi,VLF,LF,HF,AR_order,window, ...
noverlap,nfft,fs,methods,flagPlot)
%freqHRV - calculates freq domain HRV using FFT, AR, and Lomb-Scargle
%methods
%
%Inputs: ibi = 2Dim array of time (s) and inter-beat interval (s)
% AR_order = order of AR model
% window = # of samples in window
% noverlap = # of samples to overlap
% fs = cubic spline interpolation rate / resample rate (Hz)
% nfft = # of points in the frequency axis
% methods = cell array of strings that defines the methods used to
% calculate freqDomain. The default is all to use
% all three methods.
% methods={'welch','ar','lomb'}
% flagPlot = flag to tell function to plot PSD. 1=plot,
% 0=don't plot, default is 0.
%Outputs: output is a structure containg all HRV. One field for each
% PSD method.
% Output units include:
% peakHF,LF,VLF (Hz)
% aHF,aLF,aVLF (ms^2)
% pHF,pLF,pVLF (%)
% nHF,nLF,nVLF (%)
% PSD (ms^2/Hz)
% F (Hz)
%check input
if nargin<9
error('Not enough input arguments!')
elseif nargin<10
methods={'welch','ar','lomb'};
flagPlot=false;
elseif nargin<11
flagPlot=false;
end
flagWelch=false; flagAR=false; flagLomb=false;
for m=1:length(methods)
if strcmpi(methods{m},'welch')
flagWelch=true;
elseif strcmpi(methods{m},'ar')
flagAR=true;
elseif strcmpi(methods{m},'lomb')
flagLomb=true;
end
end
t=ibi(:,1); %time (s)
y=ibi(:,2); %ibi (s)
y=y.*1000; %convert ibi to ms
%assumes ibi units are seconds
maxF=fs/2;
%prepare y
y=detrend(y,'linear');
y=y-mean(y);
%Welch FFT
if flagWelch
[output.welch.psd,output.welch.f] = ...
calcWelch(t,y,window,noverlap,nfft,fs);
output.welch.hrv = ...
calcAreas(output.welch.f,output.welch.psd,VLF,LF,HF);
else
output.welch=emptyData(nfft,maxF);
end
end
function [PSD,F]=calcWelch(t,y,window,noverlap,nfft,fs)
%calFFT - Calculates the PSD using Welch method.
%
%Inputs:
%Outputs:
%Prepare y
t2 = t(1):1/fs:t(length(t));%time values for interp.
y=interp1(t,y,t2','spline')'; %cubic spline interpolation
y=y-mean(y); %remove mean
%Calculate Welch PSD using hamming windowing
[PSD,F] = pwelch(y,window,noverlap,(nfft*2)-1,fs,'onesided');
end
function output=calcAreas(F,PSD,VLF,LF,HF,flagNorm)
%calcAreas - Calulates areas/energy under the PSD curve within the freq
%bands defined by VLF, LF, and HF. Returns areas/energies as ms^2,
%percentage, and normalized units. Also returns LF/HF ratio.
%
%Inputs:
% PSD: PSD vector
% F: Freq vector
% VLF, LF, HF: array containing VLF, LF, and HF freq limits
% flagNormalize: option to normalize PSD to max(PSD)
%Output:
%
%Usage:
%
% Modified from Gary Clifford's ECG Toolbox: calc_lfhf.m
if nargin<6
flagNorm=false;
end
%normalize PSD if needed
if flagNorm
PSD=PSD/max(PSD);
end
% find the indexes corresponding to the VLF, LF, and HF bands
iVLF= (F>=VLF(1)) & (F<=VLF(2));
iLF = (F>=LF(1)) & (F<=LF(2));
iHF = (F>=HF(1)) & (F<=HF(2));
%Find peaks
%VLF Peak
tmpF=F(iVLF);
tmppsd=PSD(iVLF);
[pks,ipks] = zipeaks(tmppsd);
if ~isempty(pks)
[tmpMax i]=max(pks);
peakVLF=tmpF(ipks(i));
else
[tmpMax i]=max(tmppsd);
peakVLF=tmpF(i);
end
%LF Peak
tmpF=F(iLF);
tmppsd=PSD(iLF);
[pks,ipks] = zipeaks(tmppsd);
if ~isempty(pks)
[tmpMax i]=max(pks);
peakLF=tmpF(ipks(i));
else
[tmpMax i]=max(tmppsd);
peakLF=tmpF(i);
end
%HF Peak
tmpF=F(iHF);
tmppsd=PSD(iHF);
[pks,ipks] = zipeaks(tmppsd);
if ~isempty(pks)
[tmpMax i]=max(pks);
peakHF=tmpF(ipks(i));
else
[tmpMax i]=max(tmppsd);
peakHF=tmpF(i);
end
% calculate raw areas (power under curve), within the freq bands (ms^2)
aVLF=trapz(F(iVLF),PSD(iVLF));
aLF=trapz(F(iLF),PSD(iLF));
aHF=trapz(F(iHF),PSD(iHF));
aTotal=aVLF+aLF+aHF;
%calculate areas relative to the total area (%)
pVLF=(aVLF/aTotal)*100;
pLF=(aLF/aTotal)*100;
pHF=(aHF/aTotal)*100;
%calculate normalized areas (relative to HF+LF, n.u.)
nLF=aLF/(aLF+aHF);
nHF=aHF/(aLF+aHF);
%calculate LF/HF ratio
lfhf =aLF/aHF;
%create output structure
if flagNorm
output.aVLF=round(aVLF*1000)/1000;
output.aLF=round(aLF*1000)/1000;
output.aHF=round(aHF*1000)/1000;
output.aTotal=round(aTotal*1000)/1000;
else
output.aVLF=round(aVLF*100)/100; % round
output.aLF=round(aLF*100)/100;
output.aHF=round(aHF*100)/100;
output.aTotal=round(aTotal*100)/100;
end
output.pVLF=round(pVLF*10)/10;
output.pLF=round(pLF*10)/10;
output.pHF=round(pHF*10)/10;
output.nLF=round(nLF*1000)/1000;
output.nHF=round(nHF*1000)/1000;
output.LFHF=round(lfhf*1000)/1000;
output.peakVLF=round(peakVLF(1)*100)/100;
output.peakLF=round(peakLF(1)*100)/100;
output.peakHF=round(peakHF(1)*100)/100;
end
function output=emptyData(nfft,maxF)
%create output structure of zeros
output.hrv.aVLF=0;
output.hrv.aLF=0;
output.hrv.aHF=0;
output.hrv.aTotal=0;
output.hrv.pVLF=0;
output.hrv.pLF=0;
output.hrv.pHF=0;
output.hrv.nLF=0;
output.hrv.nHF=0;
output.hrv.LFHF=0;
output.hrv.peakVLF=0;
output.hrv.peakLF=0;
output.hrv.peakHF=0;
%PSD with all zeros
deltaF=maxF/nfft;
output.f = linspace(0.0,maxF-deltaF,nfft);
output.psd=zeros(length(output.f),1);
end
function [pks locs]=zipeaks(y)
%zippeaks: finds local maxima of input signal y
%Usage: peak=zipeaks(y);
%Returns 2x(number of maxima) array
%pks = value at maximum
%locs = index value for maximum
%
%Reference: 2009, George Zipfel (Mathworks File Exchange #24797)
%check dimentions
if isempty(y)
Warning('Empty input array')
pks=[]; locs=[];
return
end
[rows cols] = size(y);
if cols==1 && rows>1 %all data in 1st col
y=y';
elseif cols==1 && rows==1
Warning('Short input array')
pks=[]; locs=[];
return
end
%Find locations of local maxima
%yD=1 at maxima, yD=0 otherwise, end point maxima excluded
N=length(y)-2;
yD=[0 (sign(sign(y(2:N+1)-y(3:N+2))-sign(y(1:N)-y(2:N+1))-.1)+1) 0];
%Indices of maxima and corresponding values of y
Y=logical(yD);
I=1:length(Y);
locs=I(Y);
pks=y(Y);
end