comparison main/signal/pwelch.m @ 0:6b33357c7561 octave-forge

Initial revision
author pkienzle
date Wed, 10 Oct 2001 19:54:49 +0000
parents
children 13d7efaa47b8
comparison
equal deleted inserted replaced
-1:000000000000 0:6b33357c7561
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