Skip to content

Instantly share code, notes, and snippets.

@alexbodn
Created October 28, 2022 05:48
Show Gist options
  • Save alexbodn/16e238a92c40c52d319c1fe35f364088 to your computer and use it in GitHub Desktop.
Save alexbodn/16e238a92c40c52d319c1fe35f364088 to your computer and use it in GitHub Desktop.
find a list of peaks and valleys in xy points sequence
import numbers
import math
from collections.abc import Iterable
def peakdet(v, delta, x=None, xinit=1, original=False):
"""
this code was kindly provided by it's original developer.
# Eli Billauer, 3.4.05 (Explicitly not copyrighted).
my humble additions were aimed to catch more peaks and values,
in the following 2 cases:
* record the first extreme when it is a minimum.
since lookformax is initially true, the original misses this case.
* record the last extreme. the original missed it,
as being last is not being followed,
especially not by a direction breaking value.
my aim is to lessen the memory commitment, and provide iterables as input.
the original functionality may be obtained by using the 'original' parameter.
this is the original doc:
#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.
"""
def infinite_sequence(init):
num = init
while True:
yield num
num += 1
maxtab = []
mintab = []
if not isinstance(v, Iterable):
raise Exception('values should be iterable')
if not x:
x = infinite_sequence(xinit)
elif isinstance(x, Iterable):
x = iter(x)
else:
raise Exception('if given, x values should be iterable')
if not (isinstance(delta, numbers.Number) and delta > 0):
raise Exception('Input argument DELTA must be a positive number')
mn = math.inf; mx = -math.inf;
mnpos = math.nan; mxpos = math.nan;
lookformax = 1
firstmn = True
lastmn = None
lastmx = None
for this in v:
thispos = next(x)
if this > mx:
mx = this; mxpos = thispos;
if mx >= mn+delta and not original:
if firstmn:
# as lookformax is initially true,
# the min that precedes the first max
# will be ignored
mintab.append((mnpos, mn));
firstmn = False
lastmn = None
lastmx = (mxpos, mx)
if this < mn:
mn = this; mnpos = thispos;
if mn <= mx+delta:
lastmn = (mnpos, mn)
if lookformax:
if this < mx-delta:
maxtab.append((mxpos, mx));
mn = this; mnpos = thispos;
lookformax = 0;
lastmx = None
else:
if this > mn+delta:
mintab.append((mnpos, mn));
firstmn = False
mx = this; mxpos = thispos;
lookformax = 1;
lastmn = None
# if there was no change >= delta after the last mn/mx, that
# are here only if they moved by delta from the point before them
if not original:
if lastmx:
maxtab.append(lastmx);
if lastmn:
mintab.append(lastmn);
return maxtab, mintab
import matplotlib.pyplot as plt
from operator import itemgetter
def split_tab(tab):
x, y = list(), list()
for row in tab:
x.append(row[0])
y.append(row[1])
return x, y
def show(inputs, delta, original=False, scale=1, subplt=None):
whole = False
if subplt is None:
subplt = plt
whole = True
inputs = [(c, value * scale) for c, value in enumerate(inputs, start=1)]
x, v = split_tab(inputs)
maxtab, mintab = peakdet(v, delta, x, original=original)
print('inputs:', inputs)
print('maxtab:', maxtab)
print('mintab:', mintab)
subplt.plot(x, v, 'b-', label='inputs')
if whole:
subplt.xticks(x, x)
subplt.yticks(v, v)
else:
subplt.set_xticks(x, x, minor=False)
subplt.set_yticks(v, v, minor=False)
x, v = split_tab(sorted(mintab + maxtab, key=itemgetter(0)))
subplt.plot(x, v, 'co-.', label='evt')
x, v = split_tab(maxtab)
subplt.plot(x, v, 'g+', label='peaks')
x, v = split_tab(mintab)
subplt.plot(x, v, 'r+', label='valleys')
subplt.plot([], [], 'yo', label='delta=%g' % delta)
if original:
subplt.plot([], [], 'yo', label='original')
if whole:
subplt.title('peaks & valleys')
subplt.ylabel('value')
subplt.xlabel('index')
else:
subplt.set_title('peaks & valleys')
subplt.set_ylabel('value')
subplt.set_xlabel('index')
subplt.grid()
subplt.legend(loc='best')
def show_compare(inputs, delta):
fig, axs = plt.subplots(2, 2)
show(inputs, delta, original=False, scale=1, subplt=axs[0, 0])
show(inputs, delta, original=False, scale=-1, subplt=axs[0, 1])
show(inputs, delta, original=True, scale=1, subplt=axs[1, 0])
show(inputs, delta, original=True, scale=-1, subplt=axs[1, 1])
# Hide x labels and tick labels for top plots and y ticks for right plots.
for ax in axs.flat:
ax.label_outer()
def main():
inputs = [
2.0, 1.0, 2.0, 3.0, 2.0, 1.0, 0.0, 2.0, 4.0, 6.0, 8.0,
12.0, 11.0, 10.0, 11.0, 9.0, 7.0, 5.0, 3.0, 2.0, 1.0
]
delta = 1.5
#show(inputs, delta)
show_compare(inputs, delta)
plt.show()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment