Created
October 28, 2022 05:48
-
-
Save alexbodn/16e238a92c40c52d319c1fe35f364088 to your computer and use it in GitHub Desktop.
find a list of peaks and valleys in xy points sequence
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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