Home > home > fbarrett > svn > jlmt > utils > find_peaks.m

find_peaks

PURPOSE ^

Finds the peak locations in a signal

SYNOPSIS ^

function varargout = find_peaks(varargin)

DESCRIPTION ^

 Finds the peak locations in a signal

 peakIdxs = find_peaks(inSig,params)

 Accepts an input signal and identifies the peak index locations.
 A threshold parameter (param.thresh) should be passed in to
 indicate the threshold under which peaks should be ignored.
 The threshold is useful if working with a signal that is
 noisy. The threshold should be chosen sufficiently above the
 noise floor. The signal may optionally be low pass filtered
 before performing peak detection. A window parameter
 (params.peak_height_window) may also be passed in, to indicate the size
 of a sliding window of samples within which to only accept the highest
 peak. For instance, for a peak window of 1 second, and given a signal
 with many peaks that occur within a few tenths of a second of each other,
 only the highest peak within a given 1 second window will be retained,
 and all other peaks within that window will be discarded (equivalent to
 taking the highest peak within +/- 0.5 s of any given peak).

 INPUTS
  params.perform: (optional) A cell array of strings of processes to perform. 
                   'lowPass' - perform a low pass filtering before peak detection.
                   'peak_heights' - returns peak height values in second output argument
                   'peak_widths'  - returns peak widths in third output argument
                   'peak_areas'   - returns peak areas in fourth output argument
  params.thresh:  (optional) Value between 0 and 1. Fraction of maximum
                  amplitude to use as a threshold below which peak
                  detecting will not be performed. This may be useful if
                  your signal has many small peaks with low amplitudes.

  params.Fs:      Sampling rate of signal. Required if performing low pass
                  filtering, or if specifying a peak window.
  params.lpFreq:  Low pass cutoff frequency. Only needed if performing low pass
                  filtering. Requires params.Fs.
 
  params.peak_height_thresh: (optional) peaks with individual heights less
                  than peak_height_thresh will not be retained. This
                  differs from params.thresh, in that params.thresh is a
                  global amplitude threshold, and peak_height_thresh is a
                  local peak height threshold.
 
  params.peak_height_window: (optional) sliding window in which only the
                  highest peak is retained. for each peak found, if there
                  are other peaks within +/- peak_height_window seconds of
                  that peak, all but the highest peak will be discarded.
  
  params.peak_heights, params.peak_widths, and params.peak_areas
  are parameter substructures that are passed to peak_heights,
  peak_widths, and peak_areas. If they are not specified, empty
  parameter structures are used.

 OUTPUTS
  existance of particular outputs necessarily depend upon params.perform,
  but the indices of outputs when they exist are such:
       varargout{1} - peak indexes
       varargout{2} - peak heights
       varargout{3} - base indexes
       varargout{4} - peak widths
       varargout{5} - peak areas
 
 Copyright (c) 2007-2012 The Regents of the University of California
 All Rights Reserved

 Author:
 Stefan Tomic 5/07
 06/17/09 - Stefan Tomic: 
            - added handling of xVals in find_peaks
            - modified input argument formatting to accept
              param/value pairs with compatibility to old function calls                          
            - now returns peak heights, peak widths, and peak
              areas as additional output arguments
 06/18/09 - Fred Barrett
            - added peak_height_window and peak_height_thresh
 06/21/09 - Fred Barrett
            - documnted outputs
            - added "base indexes" to varargout, thus shifting peak widths
            and peak areas (formerly varargout{3:4}) to varargout{4:5}
 10/28/09 - Stefan Tomic:
             - fixed handling of flat peaks (peaks that have consecutive 
               indices with the same Y values). find_peaks now returns 
               the middle index instead of the left-most index. In the 
               case of an even number of indices, it returns the left 
               of the two middle indices.
             - Fixed a bug that was causing find_peaks to return a peak
               index at the end of the signal when the end of the signal
               was flat (e.g. silence at the end of an audio signal).

CROSS-REFERENCE INFORMATION ^

This function calls: This function is called by:

SOURCE CODE ^

0001 function varargout = find_peaks(varargin)
0002 % Finds the peak locations in a signal
0003 %
0004 % peakIdxs = find_peaks(inSig,params)
0005 %
0006 % Accepts an input signal and identifies the peak index locations.
0007 % A threshold parameter (param.thresh) should be passed in to
0008 % indicate the threshold under which peaks should be ignored.
0009 % The threshold is useful if working with a signal that is
0010 % noisy. The threshold should be chosen sufficiently above the
0011 % noise floor. The signal may optionally be low pass filtered
0012 % before performing peak detection. A window parameter
0013 % (params.peak_height_window) may also be passed in, to indicate the size
0014 % of a sliding window of samples within which to only accept the highest
0015 % peak. For instance, for a peak window of 1 second, and given a signal
0016 % with many peaks that occur within a few tenths of a second of each other,
0017 % only the highest peak within a given 1 second window will be retained,
0018 % and all other peaks within that window will be discarded (equivalent to
0019 % taking the highest peak within +/- 0.5 s of any given peak).
0020 %
0021 % INPUTS
0022 %  params.perform: (optional) A cell array of strings of processes to perform.
0023 %                   'lowPass' - perform a low pass filtering before peak detection.
0024 %                   'peak_heights' - returns peak height values in second output argument
0025 %                   'peak_widths'  - returns peak widths in third output argument
0026 %                   'peak_areas'   - returns peak areas in fourth output argument
0027 %  params.thresh:  (optional) Value between 0 and 1. Fraction of maximum
0028 %                  amplitude to use as a threshold below which peak
0029 %                  detecting will not be performed. This may be useful if
0030 %                  your signal has many small peaks with low amplitudes.
0031 %
0032 %  params.Fs:      Sampling rate of signal. Required if performing low pass
0033 %                  filtering, or if specifying a peak window.
0034 %  params.lpFreq:  Low pass cutoff frequency. Only needed if performing low pass
0035 %                  filtering. Requires params.Fs.
0036 %
0037 %  params.peak_height_thresh: (optional) peaks with individual heights less
0038 %                  than peak_height_thresh will not be retained. This
0039 %                  differs from params.thresh, in that params.thresh is a
0040 %                  global amplitude threshold, and peak_height_thresh is a
0041 %                  local peak height threshold.
0042 %
0043 %  params.peak_height_window: (optional) sliding window in which only the
0044 %                  highest peak is retained. for each peak found, if there
0045 %                  are other peaks within +/- peak_height_window seconds of
0046 %                  that peak, all but the highest peak will be discarded.
0047 %
0048 %  params.peak_heights, params.peak_widths, and params.peak_areas
0049 %  are parameter substructures that are passed to peak_heights,
0050 %  peak_widths, and peak_areas. If they are not specified, empty
0051 %  parameter structures are used.
0052 %
0053 % OUTPUTS
0054 %  existance of particular outputs necessarily depend upon params.perform,
0055 %  but the indices of outputs when they exist are such:
0056 %       varargout{1} - peak indexes
0057 %       varargout{2} - peak heights
0058 %       varargout{3} - base indexes
0059 %       varargout{4} - peak widths
0060 %       varargout{5} - peak areas
0061 %
0062 % Copyright (c) 2007-2012 The Regents of the University of California
0063 % All Rights Reserved
0064 %
0065 % Author:
0066 % Stefan Tomic 5/07
0067 % 06/17/09 - Stefan Tomic:
0068 %            - added handling of xVals in find_peaks
0069 %            - modified input argument formatting to accept
0070 %              param/value pairs with compatibility to old function calls
0071 %            - now returns peak heights, peak widths, and peak
0072 %              areas as additional output arguments
0073 % 06/18/09 - Fred Barrett
0074 %            - added peak_height_window and peak_height_thresh
0075 % 06/21/09 - Fred Barrett
0076 %            - documnted outputs
0077 %            - added "base indexes" to varargout, thus shifting peak widths
0078 %            and peak areas (formerly varargout{3:4}) to varargout{4:5}
0079 % 10/28/09 - Stefan Tomic:
0080 %             - fixed handling of flat peaks (peaks that have consecutive
0081 %               indices with the same Y values). find_peaks now returns
0082 %               the middle index instead of the left-most index. In the
0083 %               case of an even number of indices, it returns the left
0084 %               of the two middle indices.
0085 %             - Fixed a bug that was causing find_peaks to return a peak
0086 %               index at the end of the signal when the end of the signal
0087 %               was flat (e.g. silence at the end of an audio signal).
0088 
0089 %this section handles compatibility with the old way of calling
0090 %find_peaks, which just had two input arguments (inData,params)
0091 if(~ischar(varargin{1}))
0092   inSig = varargin{1};
0093   if(nargin > 1)
0094     params = varargin{2};
0095   end
0096 
0097   %set varargin to new input argument formatting, so that we can
0098   %forward the arguments to peak_heights, peak_widths, and peak_areas
0099   varargin = {'signal',inSig,'params',params};
0100   
0101 else
0102 
0103   %this is the new way of calling find_peaks, with tag/value pairs.
0104   %the main reason for going this direction is that peak_widths,
0105   %peak_heights and peak_areas use tag value pairs and it is easy to just forward
0106   %the arguments.
0107   for iarg = 1:2:nargin
0108     switch varargin{iarg}
0109      case 'signal'
0110       inSig = varargin{iarg+1};
0111      case 'params'
0112       params = varargin{iarg+1};
0113      case 'xVals'
0114       xVals = varargin{iarg+1};
0115      case 'perform'
0116       perform = varargin{iarg+1};
0117     end
0118   end
0119 end
0120 
0121 try
0122   perform;
0123 catch
0124   perform = {'peak_heights','peak_widths','peak_areas'};
0125 end
0126 
0127 try
0128   xVals;
0129 catch
0130   %find_peaks didn't receive xVals, so peak indices will be returned
0131   xVals = [1:length(inSig)]';
0132 end
0133 
0134 %make sure that we are dealing with a column vector
0135 if(size(inSig,1) > 1 && size(inSig,2) > 1)
0136   error('Input signal must be a vector');
0137 elseif(size(inSig,1) > 1 && size(inSig,2) == 1)
0138   inSig = inSig;
0139 else
0140   inSig = inSig';
0141 end
0142 
0143 if(~isfield(params,'thresh'))
0144   params.thresh = 0;
0145 end
0146 
0147 if(isfield(params,'perform') && ismember('lowPass',params.perform))
0148   Fs = params.Fs;
0149   lpFreq = params.lpFreq;
0150   W = lpFreq/Fs*2;
0151   [B,A] = butter(12,W,'low');
0152   inSig = filtfilt(B,A,inSig);
0153 end
0154 
0155 %the 0 at the beginning takes care of the diff offset and
0156 %solves for possible peaks at the very beginning of the signal. The
0157 %-1 value at the end takes care of possible peaks at the end of the
0158 %signal.
0159 diffSig = diff(inSig,1);
0160 sigIsFlat = (diffSig == 0);
0161 sigNotFlatIdxs = [1 ; find(~sigIsFlat) + 1];
0162 sigIsFlatIdxs = find(sigIsFlat) + 1;
0163 sigNotFlat = inSig(sigNotFlatIdxs);
0164 diffSigNotFlat = diff(sigNotFlat,1);
0165 
0166 peakRefMask = [0 ; (diffSigNotFlat(1:end-1) > 0 & diffSigNotFlat(2:end) < 0)];
0167 
0168 peakRefs = find(peakRefMask);
0169 
0170 leftPeakIdxs = sigNotFlatIdxs(peakRefs);
0171 
0172 %deal with edge conditions
0173 if(diffSig(1) < 0)
0174   leftPeakIdxs = [1 ; leftPeakIdxs];
0175 end
0176 
0177 if(diffSig(end) > 0)
0178   leftPeakIdxs  = [leftPeakIdxs ; length(inSig)];
0179 end
0180 
0181 possiblePeakMask = zeros(size(inSig));
0182 %check if any detected possible peaks are on flat locations
0183 %detected points will be on left side of flat regions, so
0184 %we need to check flatness of right side
0185 nLeftPeaks = length(leftPeakIdxs);
0186 for iLeftPeak = 1:nLeftPeaks
0187   thisPeakIdx = leftPeakIdxs(iLeftPeak);
0188   %if the next not-flat index is not adjacent to detected
0189   %then we are on a flat location
0190   nextIdx = sigNotFlatIdxs(find(sigNotFlatIdxs > thisPeakIdx,1,'first'));
0191   if(isempty(nextIdx))
0192     thisPeakAdjIdxs = thisPeakIdx;
0193   else
0194     thisPeakAdjIdxs = [thisPeakIdx:nextIdx-1];
0195   end
0196   
0197   minAdjIdx = min(thisPeakAdjIdxs);
0198   maxAdjIdx = max(thisPeakAdjIdxs);
0199   
0200   middleIdx = floor((maxAdjIdx - minAdjIdx)/2) + minAdjIdx;
0201  
0202   possiblePeakMask(middleIdx) = 1;
0203 end
0204 
0205 %only return portions of the signal that are above the threshold.
0206 threshVal = min(inSig) + (max(inSig) - min(inSig))*params.thresh;
0207 peakIdxs = find((inSig >= threshVal) & possiblePeakMask);
0208 
0209 try params.peak_heights; catch params.peak_heights = []; end
0210 [pheights,bidxs] = peak_heights('signal',inSig,'params',...
0211     params.peak_heights,'peakIdxs',peakIdxs,'xVals',xVals);
0212 
0213 if isfield(params,'peak_height_thresh')
0214   rmidxs = pheights < params.peak_height_thresh;
0215   peakIdxs(rmidxs) = [];
0216   pheights(rmidxs) = [];
0217   bidxs(rmidxs) = [];
0218 end
0219 
0220 % remove peaks within +/- peak_window seconds of each peak
0221 if isfield(params,'peak_height_window')
0222   if isfield(params,'Fs')
0223     for ip=length(peakIdxs):-1:1
0224 
0225       if ip > length(peakIdxs), continue, end
0226       % get peak window
0227       win_start=peakIdxs(ip)-round(params.Fs*params.peak_height_window);
0228       win_end  =peakIdxs(ip)+round(params.Fs*params.peak_height_window);
0229       if win_start < 0, win_start = 0; end
0230       if win_end > length(inSig), win_end = length(inSig); end
0231       window = (win_start:win_end);
0232 
0233       % get peaks within window
0234       widxs = find(ismember(peakIdxs,window));
0235       wheights = pheights(widxs);
0236 
0237       % don't keep the max height widxs
0238       maxidx = find(wheights==max(wheights));
0239       if length(maxidx) > 1, maxidx = maxidx(1); end
0240       widxs(maxidx) = [];
0241 
0242       % remove widxs from peakIdxs
0243       if ~isempty(widxs) && ~isempty(peakIdxs)
0244         peakIdxs(widxs) = [];
0245         pheights(widxs) = [];
0246         bidxs(widxs) = [];
0247       end
0248     end
0249   else
0250     warning('peak_height_window not applied: no sampling rate specified');
0251   end
0252 end
0253 
0254 varargout{1} = xVals(peakIdxs);
0255 
0256 if(ismember('peak_heights',perform))
0257   varargout{2} = pheights;
0258   varargout{3} = bidxs;
0259 end
0260 
0261 if(ismember('peak_widths',perform))
0262   try params.peak_widths; catch params.peak_widths = []; end;
0263   pwidths = peak_widths('signal',inSig,'params',params.peak_widths,'peakIdxs',peakIdxs,'xVals',xVals);
0264   %peak widths returns the borders, so calculate actual widths here
0265   varargout{4} = pwidths(2:2:end) - pwidths(1:2:end);
0266 end
0267 
0268 if(ismember('peak_areas',perform))
0269   try params.peak_areas; catch params.peak_areas = []; end;
0270   varargout{5} = peak_areas('signal',inSig,'params',params.peak_areas,'peakIdxs',peakIdxs,'xVals',xVals);
0271 end
0272 
0273 return
0274 
0275

Generated on Mon 18-Mar-2013 10:43:33 by m2html © 2003