This is most likely related to the comparator function and the waveform.
Long story short:
from scipy.signal import argrelextrema
from matplotlib import pyplot as plt
from scipy import linspace,pi,cos
#Time vector
t = linspace(0,4,4*1000); #4 seconds at Fs=1000Hz
#Phase vector
p = 2.0 * pi * t;
#Signal
S = cos(4.0*p); #A 4Hz sinusoid
# Have a look at it, focus on where the maxima and minima occur.
plt.plot(S);plt.show()
#Let's find some peaks
Pmin = argrelextrema(S, lambda x,y:x<y)
Pmax = argrelextrema(S, lambda x,y:x>y)
Pmin
identifies all peaks: [500, 1500, 2499, 3499]
Pmax
has skipped some : [1000,2999]
Try it again, by going for Pmax2 = argrelextrema(S, lambda x,y:x>=y)
. Pmax2
now is [0,1000,1999,2000,2999,3999]
Now, peak detection is relatively straightforward. The mathematical way to think about it is to get the first derivative and look at where it drops to zero. Every zero of the first derivative is either a local maximum or local minimum. To decide if you are at a peak or a trough, you look at the value of the second derivative at that point. The second derivative of your signal, is the derivative of the first derivative. If that is positive, your candidate point is a local minimum, if it is negative, your candidate point is a local maximum.
That's fine, but costly and there are alternative ways to achieve it. For example, a peak could be defined as the pattern increase, decrease
of signal values and a trough could be defined as the pattern decrease, increase
of signal values. This is straightforward to understand but in reality, extrema might be "wide". In other words, the pattern of values now is increase, same, same, same, same, same, same, same, decrease
. Where is the peak there? (or, conversely, the trough).
You might say, "That's not a problem, the peak is in the middle of the wide top" and that would be a reasonable assumption. In fact, you could achieve this by first "blurring" your data (taking the average) in such a way as to reduce the "wideness" and now you are back in a case of "increase, decrease".
This is achieved in argrelextrema
by modifying the order
parameter.
So, having said this, why does argrelextrema
fails to identify those peaks in the extremely simple case of a cos
?
If you take a close look at what the cos
is doing, you will observe that:
- When the peak is identified:
...,0.99993952, 0.99998889, 0.99999877, 0.99996914...
- Did you spot the pattern? (
increase,decrease
)
- When the peak is NOT identified:
..., 0.99995556, 0.99999506, 0.99999506, 0.99995556,...
- Did you spot the pattern? (
increase,same,decrease
)
So, by modifying the comparator function, you enable argrelextrema
to widen its search for what an extrema looks like.
Try introducing the <=
, >=
comparator function.
Hope this helps.