Logo Search packages:      
Sourcecode: octave-matcompat version File versions

pwelch.m

## Copyright (C) 1999-2000 Paul Kienzle
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

## usage: [Pxx, w] = pwelch(x,n,Fs,window,overlap,ci,range,units,trend)
##        [Pxx, Pci, w] = pwelch(x,n,Fs,window,overlap,ci,range,units,trend)
##
## Estimate power spectrum of a stationary signal. This chops the signal
## into overlapping slices, windows each slice and applies a Fourier
## transform to determine the frequency components at that slice. The
## magnitudes of these slices are then averaged to produce the estimate Pxx.
## The confidence interval around the estimate is returned in Pci.
##
## x: vector of samples
## n: size of fourier transform window, or [] for default=256
## Fs: sample rate, or [] for default=2 Hz
## window: shape of the fourier transform window, or [] for default=hanning(n)
##    Note: window length can be specified instead, in which case
##    window=hanning(length)
## overlap: overlap with previous window, or [] for default=length(window)/2
## ci: confidence interval, or [] for default=0.95
##    ci must be between 0 and 1
## range: 'whole',  or [] for default='half'
##    show all frequencies, or just half of the frequencies
## units: 'squared', or [] for default='db'
##    show results as magnitude squared or as log magnitude squared
## trend: 'mean', 'linear', or [] for default='none'
##    remove trends from the data slices before computing spectral estimates
##
## Example
##    [b,a] = cheby1(4,3,[0.2, 0.4]);     ## define noise colour
##    pwelch(filter(b,a,randn(2^12,1))); ## estimate noise colour

## TODO: Should be extended to accept a vector of frequencies at which to
## TODO:    evaluate the fourier transform (via filterbank or chirp
## TODO:    z-transform).
## TODO: Compute confidence intervals for cohere and tfe as well
## TODO: What should happen with the final window when it isn't full?
## TODO:    currently I dump it, but I should probably zero pad and add
## TODO:    it in.
## TODO: Consider returning the whole gamit of Pxx, Pyy, Pxy, Cxy, Txy
## TODO:    as well as confidence intervals for each;  if users tend
## TODO:    only to use one of these don't bother, but if they ever need
## TODO:    more than one, then it's free.  Alternatively, break out the
## TODO:    compute engine into a function that the user can call directly.
## TODO: Check if Cxy, Txy are computed frame-by-frame or on the average
## TODO:    of the frames.  SpcTools and I do it on the average, 
## TODO:    wdkirby@ix.netcom.com (1998-04-29 octave-sources) computes 
## TODO:    them frame-by-frame.
function  = pwelch(x, ...)
  ## sort out parameters
  if nargin < 1, 
    usage("[Pxx, w] = pwelch(x,nfft,Fs,window,overlap,pc,range,units,trend)");
  endif
  va_start(); 

  ## Determine if we are called as pwelch, csd, cohere or tfe
  if isstr(x)
    calledby = x;
  else
    calledby = "pwelch";
  endif
  if !isstr(x)
    ftype = 1;
  elseif strcmp(x, 'csd')
    ftype = 2;
  elseif strcmp(x, 'cohere')
    ftype = 3;
  elseif strcmp(x, 'tfe')
    ftype = 4;
  endif

  ## Sort out x and y vectors
  if ftype!=1, 
    x=va_arg(); y=va_arg(); 
    first = 4;
  else
    y=;
    first = 2;
  endif
  if (columns(x) != 1 && rows(x) != 1) || ...
    (!isempty(y) && columns(y) != 1 && rows(y) != 1)
    error ([calledby, " data must be a vector"]);
  end
  if columns(x) != 1, x = x'; end
  if columns(y) != 1, y = y'; end
  if !isempty(y) && rows(x)!=rows(y)
    error ([calledby, " x and y vectors must be the same length"]);
  endif

  ## interpret remaining arguments
  trend=nfft=Fs=window=overlap=usewhole=usedB=;
  ci=0;  ## default no confidence intervals
  pos=0; ## no positional parameters yet interpreted.
  for i=first:nargin
    arg = va_arg();
    if isstr(arg), 
      arg=tolower(arg); 
      if strcmp(arg, 'squared')
            usedB = 0;
      elseif strcmp(arg, 'db')
      usedB = 1;
      elseif strcmp(arg, 'whole')
      usewhole = 1;
      elseif strcmp(arg, 'half')
      usewhole = 0;
      elseif strcmp(arg, 'none')
            trend = -1;
      elseif strcmp(arg, 'mean')
            trend = 0;
      elseif strcmp(arg, 'linear')
            trend = 1;
      else
            error([calledby, " doesn't understand '", arg, "'"]);
      endif
    elseif pos == 0
      nfft = arg;
      pos++;
    elseif pos == 1
      Fs = arg;
      pos++;
    elseif pos == 2
      window = arg;
      pos++;
    elseif pos == 3
      overlap = arg;
      pos++;
    elseif pos == 4
      ci = arg;
      pos++;
    else
      usage(usagestr);
    endif
  endfor

  ## Fill in defaults for arguments that aren't specified
  if isempty(nfft), nfft = min(256, length(x)); endif
  if isempty(Fs), Fs = 2; endif
  if isempty(window), window = hanning(nfft); endif
  if isempty(overlap), overlap = length(window)/2; endif
  if isempty(usewhole), usewhole = !isreal(x)||(!isempty(y)&&!isreal(y)); endif
  if isempty(trend), trend=-1; endif
  if isempty(usedB), usedB=ftype!=3; endif # don't default to db for cohere.
  if isempty(ci), ci=0.95; endif # if ci was unspecified, it would be 0

  ## if only the window length is given, generate hanning window
  if length(window) == 1, window = hanning(window); endif
  if rows(window)==1, window = window.'; endif

  ## compute window offsets
  win_size = length(window);
  if (win_size > nfft)
    nfft = win_size;
    warning (sprintf("%s fft size adjusted to %d", calledby, n));
  end
  step = win_size - overlap;

  ## Determine which correlations to compute
  Pci = Pxx = Pyy = Pxy = ;
  if ftype!=2, Pxx = zeros(nfft,1); endif # Not needed for csd
  if ftype==3, Pyy = zeros(nfft,1); endif # Only needed for cohere
  if ftype!=1, Pxy = zeros(nfft,1); endif # Not needed for pwelch
  if ci>0, Pci = zeros(nfft,1); endif     # confidence intervals?

  ## Average the slices
  offset = 1:step:length(x)-win_size+1;
  N = length(offset);
  for i=1:N
    a=x(offset(i):offset(i)+win_size-1);
    if trend>=0, a=detrend(a,trend); endif
    a=fft(postpad(a.*window, nfft));
    if !isempty(Pxx)
      P = a.*conj(a);
      Pxx=Pxx+P;
      if !isempty(Pci), Pci = Pci + P.^2; endif
    endif
    if !isempty(Pxy)
      b=y(offset(i):offset(i)+win_size-1);
      if trend>=0, b=detrend(b,trend); endif
      b=fft(postpad(b.*window, nfft));
      P = a.*conj(b);
      Pxy=Pxy+P;
      if !isempty(Pci), Pci = Pci + P.*conj(P); endif
    endif
    if !isempty(Pyy), Pyy = Pyy + b.*conj(b); endif
  endfor


  if ftype==1,     # pwelch
    P = Pxx;
  elseif ftype==2, # csd
    P = Pxy; 
  elseif ftype==3, # cohere
    P = Pxy.*conj(Pxy)./Pxx./Pyy;
  else             # tfe
    P = Pxy./Pxx;
  endif

  if ftype<=2      # pwelch and csd need normalization and c.i.
    renorm=1/norm(window)^2;
    if ftype==1, renorm=renorm/Fs; endif # only pwelch normalizes frequency
    ## c.i. = mean +/- dev
    ## dev = z_ci*std/sqrt(n)
    ## std = sqrt((N*sum(P^2)-sum(P)^2)/(N*(N-1)))
    ## z_ci = normal_inv( 1-(1-ci)/2 ) = normal_inv( (1+ci)/2 );
    ## normal_inv(x) = sqrt(2) * erfinv(2*x-1)
    ##    => z_ci = sqrt(2)*erfinv(2*(1+ci)/2-1) = sqrt(2)*erfinv 
    ## combining, gives dev as follows:
    if ci>0
      if N>1
            Pci = sqrt(2*(N*Pci - P.^2)/(N*N*(N-1)))*erfinv(ci);
      else
            Pci = zeros(nfft,1);
      endif
      Pci=Pci*renorm;
      P = P*renorm/N;
      Pci=[P-Pci, P+Pci];
    else
      P = P*renorm/N;
    endif
  endif
    
  if usedB, 
    P = 10.0*log10(P); 
    Pci = 10.0*log10(Pci);
  endif

  ## extract the positive frequency components
  if usewhole,
    ret_n = nfft;
  elseif rem(nfft,2)==1
    ret_n = (nfft+1)/2;
  else
    ret_n = nfft/2;
  end
  P = P(1:ret_n, :);
  if ci>0,  Pci = Pci(1:ret_n, :); endif
  f = *Fs/nfft;

  if nargout==0, 
    unwind_protect
      if Fs==2
            xlabel("Frequency (rad/pi)");
      else
            xlabel("Frequency (Hz)");
      endif
      if ftype==1
            title ("Welch's Spectral Estimate Pxx/Fs");
            ytext="Power Spectral Density";
      elseif ftype==2
            title ("Cross Spectral Estimate Pxy");
            ytext="Cross Spectral Density";
      elseif ftype==3
            title ("Coherence Function Estimate |Pxy|^2/(PxxPyy)");
            ytext="Coherence ";
      else
            title ("Transfer Function Estimate Pxy/Pxx");
            ytext="Transfer";
      endif
      if usedB,
            ylabel(strcat(ytext, " (dB)"));
      else
            ylabel(ytext);
      endif
      grid("on");
      if ci>0
            plot(f, [P, Pci], ";;"); 
      else
            plot(f, P, ";;");
      endif
    unwind_protect_cleanup
      grid("off");
      title("");
      xlabel("");
      ylabel("");
    end_unwind_protect
  endif
  if nargout>=1, vr_val(P); endif
  if nargout>=2 && ci>0, vr_val(Pci); endif
  if nargout>=2 && ci==0, vr_val(f); endif
  if nargout>=3 && ci>0, vr_val(f); endif

endfunction

%!demo
%! Fs=8000;
%! [b,a] = cheby1(4,3,2*[500, 1000]/Fs);    ## define spectral envelope
%! s=0.05*randn(2^11,1);                    ## define noise
%! idx=fix(1:Fs/70:length(s))'; 
%! s(idx)=s(idx)+ones(size(idx));           ## add 70 Hz excitation
%! x=filter(b,a,s);                         ## generate signal
%!
%! figure(1); subplot(221); 
%! text(0,0.9,'basic estimate','Units','Normalized'); 
%! pwelch(x',,Fs); text;   # slip in a test for row vs. column vector
%! subplot(222); 
%! text(0,0.9,'nfft=1024 instead of 256','Units','Normalized'); 
%! pwelch(x,1024); text;
%! subplot(223); 
%! text(0,0.9,'boxcar instead of hanning','Units','Normalized');
%! pwelch(x,[],[],boxcar(256)); text;
%! subplot(224); 
%! text(0,0.9,'no overlap','Units','Normalized'); 
%! pwelch(x,[],[],[],0); text;
%!
%! figure(2); subplot(121);
%! text(0,0.9,'magnitude units, whole range','Units','Normalized'); 
%! pwelch(x,'whole','squared'); text;
%! subplot(122);
%! text(0,0.9,'90% confidence intervals','Units','Normalized'); 
%! pwelch(x,[],[],[],[],0.9); text;
%! oneplot();
%! %----------------------------------------------------------
%! % plots should show a chebyshev bandpass filter shape

Generated by  Doxygen 1.6.0   Back to index