0
|
1 ## Copyright (C) 1999-2001 Paul Kienzle |
|
2 ## |
|
3 ## This program is free software; you can redistribute it and/or modify |
|
4 ## it under the terms of the GNU General Public License as published by |
|
5 ## the Free Software Foundation; either version 2 of the License, or |
|
6 ## (at your option) any later version. |
|
7 ## |
|
8 ## This program is distributed in the hope that it will be useful, |
|
9 ## but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 ## GNU General Public License for more details. |
|
12 ## |
|
13 ## You should have received a copy of the GNU General Public License |
|
14 ## along with this program; if not, write to the Free Software |
|
15 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
16 |
|
17 ## usage: [Pxx, w] = pwelch(x,n,Fs,window,overlap,ci,range,units,trend) |
|
18 ## [Pxx, Pci, w] = pwelch(x,n,Fs,window,overlap,ci,range,units,trend) |
|
19 ## |
|
20 ## Estimate power spectrum of a stationary signal. This chops the signal |
|
21 ## into overlapping slices, windows each slice and applies a Fourier |
|
22 ## transform to determine the frequency components at that slice. The |
|
23 ## magnitudes of these slices are then averaged to produce the estimate Pxx. |
|
24 ## The confidence interval around the estimate is returned in Pci. |
|
25 ## |
|
26 ## x: vector of samples |
|
27 ## n: size of fourier transform window, or [] for default=256 |
|
28 ## Fs: sample rate, or [] for default=2 Hz |
|
29 ## window: shape of the fourier transform window, or [] for default=hanning(n) |
|
30 ## Note: window length can be specified instead, in which case |
|
31 ## window=hanning(length) |
|
32 ## overlap: overlap with previous window, or [] for default=length(window)/2 |
|
33 ## ci: confidence interval, or [] for default=0.95 |
|
34 ## ci must be between 0 and 1; if ci is not passed, or if it is |
|
35 ## passed as 0, then no confidence intervals will be computed. |
|
36 ## range: 'whole', or [] for default='half' |
|
37 ## show all frequencies, or just half of the frequencies |
|
38 ## units: 'squared', or [] for default='db' |
|
39 ## show results as magnitude squared or as log magnitude squared |
|
40 ## trend: 'mean', 'linear', or [] for default='none' |
|
41 ## remove trends from the data slices before computing spectral estimates |
|
42 ## |
|
43 ## Example |
|
44 ## [b,a] = cheby1(4,3,[0.2, 0.4]); ## define noise colour |
|
45 ## pwelch(filter(b,a,randn(2^12,1))); ## estimate noise colour |
|
46 |
|
47 ## 2001-04-02 Paul Kienzle |
|
48 ## * return nfft/2+1 elements rather than nfft/2 for even nfft. |
|
49 ## * use more accurate (and faster) computation of confidence intervals |
|
50 |
|
51 ## TODO: Should be extended to accept a vector of frequencies at which to |
|
52 ## TODO: evaluate the fourier transform (via filterbank or chirp |
|
53 ## TODO: z-transform). |
|
54 ## TODO: What should happen with the final window when it isn't full? |
|
55 ## TODO: currently I dump it, but I should probably zero pad and add |
|
56 ## TODO: it in. |
|
57 ## TODO: Consider returning the whole gamit of Pxx, Pyy, Pxy, Cxy, Txy |
|
58 ## TODO: as well as confidence intervals for each; if users tend |
|
59 ## TODO: only to use one of these don't bother, but if they ever need |
|
60 ## TODO: more than one, then it's free. Alternatively, break out the |
|
61 ## TODO: compute engine into a function that the user can call directly. |
|
62 ## TODO: Check if Cxy, Txy are computed frame-by-frame or on the average |
|
63 ## TODO: of the frames. SpcTools and I do it on the average, |
|
64 ## TODO: wdkirby@ix.netcom.com (1998-04-29 octave-sources) computes |
|
65 ## TODO: them frame-by-frame. |
|
66 function [...] = pwelch(x, ...) |
|
67 ## sort out parameters |
|
68 if nargin < 1, |
|
69 usage("[Pxx, w] = pwelch(x,nfft,Fs,window,overlap,pc,range,units,trend)"); |
|
70 endif |
|
71 va_start(); |
|
72 |
|
73 ## Determine if we are called as pwelch, csd, cohere or tfe |
|
74 if isstr(x) |
|
75 calledby = x; |
|
76 else |
|
77 calledby = "pwelch"; |
|
78 endif |
|
79 if !isstr(x) |
|
80 ftype = 1; |
|
81 elseif strcmp(x, 'csd') |
|
82 ftype = 2; |
|
83 elseif strcmp(x, 'cohere') |
|
84 ftype = 3; |
|
85 elseif strcmp(x, 'tfe') |
|
86 ftype = 4; |
|
87 endif |
|
88 |
|
89 ## Sort out x and y vectors |
|
90 if ftype!=1 |
|
91 x=va_arg(); y=va_arg(); |
|
92 first = 4; |
|
93 else |
|
94 y=[]; |
|
95 first = 2; |
|
96 endif |
|
97 if (columns(x) != 1 && rows(x) != 1) || ... |
|
98 (!isempty(y) && columns(y) != 1 && rows(y) != 1) |
|
99 error ([calledby, " data must be a vector"]); |
|
100 end |
|
101 if columns(x) != 1, x = x'; end |
|
102 if columns(y) != 1, y = y'; end |
|
103 if !isempty(y) && rows(x)!=rows(y) |
|
104 error ([calledby, " x and y vectors must be the same length"]); |
|
105 endif |
|
106 |
|
107 ## interpret remaining arguments |
|
108 trend=nfft=Fs=window=overlap=whole=use_dB=[]; |
|
109 ci=-1; ## need to do stupid things with ci |
|
110 pos=0; ## no positional parameters yet interpreted. |
|
111 for i=first:nargin |
|
112 arg = va_arg(); |
|
113 if isstr(arg), |
|
114 arg=tolower(arg); |
|
115 if strcmp(arg, 'squared') |
|
116 use_dB = 0; |
|
117 elseif strcmp(arg, 'db') |
|
118 use_dB = 1; |
|
119 elseif strcmp(arg, 'whole') |
|
120 whole = 1; |
|
121 elseif strcmp(arg, 'half') |
|
122 whole = 0; |
|
123 elseif strcmp(arg, 'none') |
|
124 trend = -1; |
|
125 elseif strcmp(arg, 'mean') |
|
126 trend = 0; |
|
127 elseif strcmp(arg, 'linear') |
|
128 trend = 1; |
|
129 else |
|
130 error([calledby, " doesn't understand '", arg, "'"]); |
|
131 endif |
|
132 elseif pos == 0 |
|
133 nfft = arg; |
|
134 pos++; |
|
135 elseif pos == 1 |
|
136 Fs = arg; |
|
137 pos++; |
|
138 elseif pos == 2 |
|
139 window = arg; |
|
140 pos++; |
|
141 elseif pos == 3 |
|
142 overlap = arg; |
|
143 pos++; |
|
144 elseif pos == 4 |
|
145 ci = arg; |
|
146 pos++; |
|
147 else |
|
148 usage(usagestr); |
|
149 endif |
|
150 endfor |
|
151 |
|
152 ## Fill in defaults for arguments that aren't specified |
|
153 if isempty(nfft), nfft = min(256, length(x)); endif |
|
154 if isempty(Fs), Fs = 2; endif |
|
155 if isempty(window), window = hanning(nfft); endif |
|
156 if isempty(overlap), overlap = length(window)/2; endif |
|
157 if isempty(whole), whole = !isreal(x)||(!isempty(y)&&!isreal(y)); endif |
|
158 if isempty(trend), trend=-1; endif |
|
159 if isempty(use_dB), |
|
160 ## don't default to db for cohere, or for returned values |
|
161 use_dB = (ftype!=3 && nargout == 0); |
|
162 endif |
|
163 if isempty(ci), ci=0.95; endif # if ci was not passed in, it would be 0 |
|
164 |
|
165 ## sort out default confidence intervals |
|
166 if (ci < 0) # ci was not passed in |
|
167 if nargout > 2 |
|
168 ci = 0.95; |
|
169 else |
|
170 ci = 0.0; |
|
171 endif |
|
172 endif |
|
173 if (ftype > 2 && ci > 0) |
|
174 error([ calledby, " can't compute confidence intervals" ]); |
|
175 elseif (ci < 0 || ci > 1) |
|
176 error([ calledby, " confidence interval must be between 0 and 1" ]); |
|
177 endif |
|
178 |
|
179 ## if only the window length is given, generate hanning window |
|
180 if length(window) == 1, window = hanning(window); endif |
|
181 if rows(window)==1, window = window.'; endif |
|
182 |
|
183 ## Normalize the window |
|
184 window = window / norm(window); |
|
185 |
|
186 ## compute window offsets |
|
187 win_size = length(window); |
|
188 if (win_size > nfft) |
|
189 nfft = win_size; |
|
190 warning (sprintf("%s fft size adjusted to %d", calledby, n)); |
|
191 end |
|
192 step = win_size - overlap; |
|
193 |
|
194 ## Determine which correlations to compute |
|
195 Pxx = Pyy = Pxy = []; |
|
196 if ftype!=2, Pxx = zeros(nfft,1); endif # Not needed for csd |
|
197 if ftype==3, Pyy = zeros(nfft,1); endif # Only needed for cohere |
|
198 if ftype!=1, Pxy = zeros(nfft,1); endif # Not needed for psd |
|
199 |
|
200 ## Average the slices |
|
201 offset = 1:step:length(x)-win_size+1; |
|
202 N = length(offset); |
|
203 for i=1:N |
|
204 a = x(offset(i):offset(i)+win_size-1); |
|
205 if trend>=0, a=detrend(a,trend); endif |
|
206 a = fft(postpad(a.*window, nfft)); |
|
207 if !isempty(Pxx), Pxx = Pxx + a.*conj(a); endif |
|
208 if !isempty(Pxy) |
|
209 b = y(offset(i):offset(i)+win_size-1); |
|
210 if trend>=0, b=detrend(b,trend); endif |
|
211 b = fft(postpad(b.*window, nfft)); |
|
212 Pxy = Pxy + a .*conj(b); |
|
213 if !isempty(Pyy), Pyy = Pyy + b.*conj(b); endif |
|
214 endif |
|
215 endfor |
|
216 if (ftype <= 2) |
|
217 ## the factors of N cancel when computing cohere and tfe |
|
218 if !isempty(Pxx), Pxx = Pxx / N; endif |
|
219 if !isempty(Pxy), Pxy = Pxy / N; endif |
|
220 if !isempty(Pyy), Pyy = Pyy / N; endif |
|
221 endif |
|
222 |
|
223 ## Compute confidence intervals |
|
224 if ci > 0, Pci = zeros(nfft,1); endif |
|
225 if (ci > 0 && N > 1) |
|
226 if ftype>2 |
|
227 error([calledby, ": internal error -- shouldn't compute Pci"]); |
|
228 end |
|
229 |
|
230 ## c.i. = mean +/- dev |
|
231 ## dev = z_ci*std/sqrt(n) |
|
232 ## std = sqrt(sumsq(P-mean(P))/(N-1)) |
|
233 ## z_ci = normal_inv( 1-(1-ci)/2 ) = normal_inv( (1+ci)/2 ); |
|
234 ## normal_inv(x) = sqrt(2) * erfinv(2*x-1) |
|
235 ## => z_ci = sqrt(2)*erfinv(2*(1+ci)/2-1) = sqrt(2)*erfinv(ci) |
|
236 for i=1:N |
|
237 a=x(offset(i):offset(i)+win_size-1); |
|
238 if trend>=0, a=detrend(a,trend); endif |
|
239 a=fft(postpad(a.*window, nfft)); |
|
240 if ftype == 1 # psd |
|
241 P = a.*conj(a) - Pxx; |
|
242 Pci = Pci + P.*conj(P); |
|
243 else # csd |
|
244 b=y(offset(i):offset(i)+win_size-1); |
|
245 if trend>=0, b=detrend(b,trend); endif |
|
246 b=fft(postpad(b.*window, nfft)); |
|
247 P = a.*conj(b) - Pxy; |
|
248 Pci = Pci + P.*conj(P); |
|
249 endif |
|
250 endfor |
|
251 |
|
252 Pci = ( erfinv(ci) * sqrt( 2/N/(N-1) ) ) * sqrt ( Pci ); |
|
253 endif |
|
254 |
|
255 switch (ftype) |
|
256 case 1, # psd |
|
257 P = Pxx / Fs; |
|
258 if ci > 0, Pci = Pci / Fs; endif |
|
259 case 2, # csd |
|
260 P = Pxy; |
|
261 case 3, # cohere |
|
262 P = Pxy.*conj(Pxy)./Pxx./Pyy; |
|
263 case 4, # tfe |
|
264 P = Pxy./Pxx; |
|
265 endswitch |
|
266 |
|
267 ## compute confidence intervals |
|
268 if ci > 0, Pci = [ P - Pci, P + Pci ]; endif |
|
269 |
|
270 if use_dB |
|
271 P = 10.0*log10(P); |
|
272 if ci > 0, Pci = 10.0*log10(Pci); endif |
|
273 endif |
|
274 |
|
275 ## extract the positive frequency components |
|
276 if whole |
|
277 ret_n = nfft; |
|
278 elseif rem(nfft,2)==1 |
|
279 ret_n = (nfft+1)/2; |
|
280 else |
|
281 ret_n = nfft/2 + 1; |
|
282 end |
|
283 P = P(1:ret_n, :); |
|
284 if ci > 0, Pci = Pci(1:ret_n, :); endif |
|
285 f = [0:ret_n-1]*Fs/nfft; |
|
286 |
|
287 ## Plot if there is no |
|
288 if nargout==0, |
|
289 unwind_protect |
|
290 if Fs==2 |
|
291 xlabel("Frequency (rad/pi)"); |
|
292 else |
|
293 xlabel("Frequency (Hz)"); |
|
294 endif |
|
295 if ftype==1 |
|
296 title ("Welch's Spectral Estimate Pxx/Fs"); |
|
297 ytext="Power Spectral Density"; |
|
298 elseif ftype==2 |
|
299 title ("Cross Spectral Estimate Pxy"); |
|
300 ytext="Cross Spectral Density"; |
|
301 elseif ftype==3 |
|
302 title ("Coherence Function Estimate |Pxy|^2/(PxxPyy)"); |
|
303 ytext="Coherence "; |
|
304 else |
|
305 title ("Transfer Function Estimate Pxy/Pxx"); |
|
306 ytext="Transfer"; |
|
307 endif |
|
308 if use_dB, |
|
309 ylabel(strcat(ytext, " (dB)")); |
|
310 else |
|
311 ylabel(ytext); |
|
312 endif |
|
313 grid("on"); |
|
314 if ci>0 |
|
315 plot(f, [P, Pci], ";;"); |
|
316 else |
|
317 plot(f, P, ";;"); |
|
318 endif |
|
319 unwind_protect_cleanup |
|
320 grid("off"); |
|
321 title(""); |
|
322 xlabel(""); |
|
323 ylabel(""); |
|
324 end_unwind_protect |
|
325 endif |
|
326 |
|
327 if nargout>=1, vr_val(P); endif |
|
328 if nargout>=2 && ci>0, vr_val(Pci); endif |
|
329 if nargout>=2 && ci==0, vr_val(f); endif |
|
330 if nargout>=3 && ci>0, vr_val(f); endif |
|
331 |
|
332 endfunction |
|
333 |
|
334 %!demo |
|
335 %! Fs=8000; |
|
336 %! [b,a] = cheby1(4,3,2*[500, 1000]/Fs); ## define spectral envelope |
|
337 %! s=0.05*randn(2^11,1); ## define noise |
|
338 %! idx=fix(1:Fs/70:length(s))'; |
|
339 %! s(idx)=s(idx)+ones(size(idx)); ## add 70 Hz excitation |
|
340 %! x=filter(b,a,s); ## generate signal |
|
341 %! |
|
342 %! figure(1); subplot(221); |
|
343 %! text(0,0.9,'basic estimate','Units','Normalized'); |
|
344 %! pwelch(x',[],Fs); text; # slip in a test for row vs. column vector |
|
345 %! subplot(222); |
|
346 %! text(0,0.9,'nfft=1024 instead of 256','Units','Normalized'); |
|
347 %! pwelch(x,1024); text; |
|
348 %! subplot(223); |
|
349 %! text(0,0.9,'boxcar instead of hanning','Units','Normalized'); |
|
350 %! pwelch(x,[],[],boxcar(256)); text; |
|
351 %! subplot(224); |
|
352 %! text(0,0.9,'no overlap','Units','Normalized'); |
|
353 %! pwelch(x,[],[],[],0); text; |
|
354 %! |
|
355 %! figure(2); subplot(121); |
|
356 %! text(0,0.9,'magnitude units, whole range','Units','Normalized'); |
|
357 %! pwelch(x,'whole','squared'); text; |
|
358 %! subplot(122); |
|
359 %! text(0,0.9,'90% confidence intervals','Units','Normalized'); |
|
360 %! pwelch(x,[],[],[],[],0.9); text; |
|
361 %! oneplot(); |
|
362 %! %---------------------------------------------------------- |
|
363 %! % plots should show a chebyshev bandpass filter shape |