|
import sys |
|
from numpy import NaN, Inf, arange, isscalar, asarray, array |
|
|
|
def peakdet(v, delta, x = None): |
|
""" |
|
Converted from MATLAB script at http://billauer.co.il/peakdet.html |
|
|
|
Returns two arrays |
|
|
|
function [maxtab, mintab]=peakdet(v, delta, x) |
|
%PEAKDET Detect peaks in a vector |
|
% [MAXTAB, MINTAB] = PEAKDET(V, DELTA) finds the local |
|
% maxima and minima ("peaks") in the vector V. |
|
% MAXTAB and MINTAB consists of two columns. Column 1 |
|
% contains indices in V, and column 2 the found values. |
|
% |
|
% With [MAXTAB, MINTAB] = PEAKDET(V, DELTA, X) the indices |
|
% in MAXTAB and MINTAB are replaced with the corresponding |
|
% X-values. |
|
% |
|
% A point is considered a maximum peak if it has the maximal |
|
% value, and was preceded (to the left) by a value lower by |
|
% DELTA. |
|
|
|
% Eli Billauer, 3.4.05 (Explicitly not copyrighted). |
|
% This function is released to the public domain; Any use is allowed. |
|
|
|
""" |
|
maxtab = [] |
|
mintab = [] |
|
|
|
if x is None: |
|
x = arange(len(v)) |
|
|
|
v = asarray(v) |
|
|
|
if len(v) != len(x): |
|
sys.exit('Input vectors v and x must have same length') |
|
|
|
if not isscalar(delta): |
|
sys.exit('Input argument delta must be a scalar') |
|
|
|
if delta <= 0: |
|
sys.exit('Input argument delta must be positive') |
|
|
|
mn, mx = Inf, -Inf |
|
mnpos, mxpos = NaN, NaN |
|
|
|
lookformax = True |
|
|
|
for i in arange(len(v)): |
|
this = v[i] |
|
if this > mx: |
|
mx = this |
|
mxpos = x[i] |
|
if this < mn: |
|
mn = this |
|
mnpos = x[i] |
|
|
|
if lookformax: |
|
if this < mx-delta: |
|
maxtab.append((mxpos, mx)) |
|
mn = this |
|
mnpos = x[i] |
|
lookformax = False |
|
else: |
|
if this > mn+delta: |
|
mintab.append((mnpos, mn)) |
|
mx = this |
|
mxpos = x[i] |
|
lookformax = True |
|
|
|
return array(maxtab), array(mintab) |
|
|
|
if __name__=="__main__": |
|
from matplotlib.pyplot import plot, scatter, show |
|
series = [0,0,0,2,0,0,0,-2,0,0,0,2,0,0,0,-2,0] |
|
maxtab, mintab = peakdet(series,.3) |
|
plot(series) |
|
scatter(array(maxtab)[:,0], array(maxtab)[:,1], color='blue') |
|
scatter(array(mintab)[:,0], array(mintab)[:,1], color='red') |
|
show() |
What I mean by "some amount of RMS noise tolerable'" is that the function also works when you have a noisy signal, I'm not adding any noisy if that's what you think. The sensitivity of the function can be set with the window variable. For a very noisy signal with maybe a thousand or a few thousand samples per period a window of about 20 should be enough, but I set it quite high to get a good margin and it doesn't effect the final result anyway, as long as it can find the zero-crossings properly. Should probably change the description to "repeatable signals, where some noise is tolerated" or something to that end.
As for performing interpolation I'm a little split about that, theoretically I think it might be a good practice, but experience hasn't always shown this. A colleague had a labView program for analysing waveforms, where he adapted a sinewave to every peak to find the actual peak smoothing away noise, but when we investigated the peaks we observed that it consistently choose a value lower than the actual peak and offset in time.
So the solution we use is to simply find the highest measured peak without trying to do anything smart. Although at another company they have fewer sample per period than we, but they perform an fft of the signal and then padds the fft-array with zero values and calculates the ifft of it to interpolate new values. This way they can have a longer aperture time for each sample increasing their accuracy for each sample and should theoretically retain resolution in time.
I would a define a realistic triangle as the following and by realistic I mean that you might actually encounter it on an actual power grid in a house or a factory: