├── tests ├── libs │ ├── __init__.py │ ├── tony_beltramelli_detect_peaks.py │ ├── findpeaks.py │ ├── detect_peaks.py │ └── peakdetect.py ├── requirements.txt ├── Pipfile ├── scipy_find_peaks_cwt.py ├── detect_peaks.py ├── tony_beltramelli_detect_peaks.py ├── janko_slavic_findpeaks.py ├── peakutils_indexes.py ├── scipy_argrelextrema.py ├── peakdetect.py ├── octave_findpeaks.py ├── lows_and_highs.py ├── Pipfile.lock └── vector.py ├── images ├── detect_peaks.png ├── matlab_findpeaks.png ├── octave_findpeaks.png ├── peakutils_indexes.png ├── scipy_argrelextrema.png ├── scipy_find_peaks_cwt.png ├── sixtenbe_peakdetect.png ├── janko_slavic_findpeaks.png ├── tony_beltramelli_detect_peaks.png └── highs_and_lows_peakutils_indexes.png ├── .editorconfig ├── .gitignore ├── LICENSE └── README.md /tests/libs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | oct2py==3.1.0 2 | PeakUtils==1.1.0 3 | -------------------------------------------------------------------------------- /images/detect_peaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/py-findpeaks/master/images/detect_peaks.png -------------------------------------------------------------------------------- /images/matlab_findpeaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/py-findpeaks/master/images/matlab_findpeaks.png -------------------------------------------------------------------------------- /images/octave_findpeaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/py-findpeaks/master/images/octave_findpeaks.png -------------------------------------------------------------------------------- /images/peakutils_indexes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/py-findpeaks/master/images/peakutils_indexes.png -------------------------------------------------------------------------------- /images/scipy_argrelextrema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/py-findpeaks/master/images/scipy_argrelextrema.png -------------------------------------------------------------------------------- /images/scipy_find_peaks_cwt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/py-findpeaks/master/images/scipy_find_peaks_cwt.png -------------------------------------------------------------------------------- /images/sixtenbe_peakdetect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/py-findpeaks/master/images/sixtenbe_peakdetect.png -------------------------------------------------------------------------------- /images/janko_slavic_findpeaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/py-findpeaks/master/images/janko_slavic_findpeaks.png -------------------------------------------------------------------------------- /images/tony_beltramelli_detect_peaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/py-findpeaks/master/images/tony_beltramelli_detect_peaks.png -------------------------------------------------------------------------------- /images/highs_and_lows_peakutils_indexes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/py-findpeaks/master/images/highs_and_lows_peakutils_indexes.png -------------------------------------------------------------------------------- /tests/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | url = "https://pypi.python.org/simple" 4 | verify_ssl = true 5 | name = "pypi" 6 | 7 | 8 | [packages] 9 | 10 | "oct2py" = "==3.1.0" 11 | peakutils = "*" 12 | matplotlib = "*" 13 | 14 | 15 | [dev-packages] 16 | 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | charset = utf-8 10 | indent_style = space 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /tests/scipy_find_peaks_cwt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import numpy as np 4 | from vector import vector, plot_peaks 5 | import scipy.signal 6 | 7 | print('Detect peaks without any filters.') 8 | indexes = scipy.signal.find_peaks_cwt( 9 | vector, 10 | np.arange(1, 4), 11 | max_distances=np.arange(1, 4)*2 12 | ) 13 | indexes = np.array(indexes) - 1 14 | print('Peaks are: %s' % (indexes)) 15 | plot_peaks( 16 | np.array(vector), 17 | np.array(indexes), 18 | algorithm='scipy.signal.find_peaks_cwt' 19 | ) 20 | -------------------------------------------------------------------------------- /tests/detect_peaks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import numpy as np 4 | from vector import vector, plot_peaks 5 | from libs import detect_peaks 6 | 7 | print('Detect peaks without any filters.') 8 | indexes = detect_peaks.detect_peaks(vector) 9 | print('Peaks are: %s' % (indexes)) 10 | plot_peaks( 11 | np.array(vector), 12 | indexes, 13 | algorithm='detect_peaks from Marcos Duarte' 14 | ) 15 | 16 | print('Detect peaks with minimum height and distance filters.') 17 | indexes = detect_peaks.detect_peaks(vector, mph=7, mpd=2) 18 | print('Peaks are: %s' % (indexes)) 19 | plot_peaks( 20 | np.array(vector), 21 | indexes, mph=7, mpd=2, 22 | algorithm='detect_peaks from Marcos Duarte' 23 | ) 24 | -------------------------------------------------------------------------------- /tests/tony_beltramelli_detect_peaks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import numpy as np 4 | from vector import vector, plot_peaks 5 | from libs.tony_beltramelli_detect_peaks import detect_peaks 6 | 7 | print('Detect peaks without any filters.') 8 | indexes = detect_peaks(vector) 9 | print('Peaks are: %s' % (indexes)) 10 | plot_peaks( 11 | np.array(vector), 12 | np.array(indexes), 13 | algorithm='detect_peaks from Tony Beltramelli' 14 | ) 15 | 16 | print('Detect peaks with height threshold.') 17 | indexes = detect_peaks(vector, 1.5) 18 | print('Peaks are: %s' % (indexes)) 19 | plot_peaks( 20 | np.array(vector), 21 | np.array(indexes), mph=1.5, 22 | algorithm='detect_peaks from Tony Beltramelli' 23 | ) 24 | -------------------------------------------------------------------------------- /tests/janko_slavic_findpeaks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import numpy as np 4 | from vector import vector, plot_peaks 5 | from libs.findpeaks import findpeaks 6 | 7 | print('Detect peaks without any filters.') 8 | indexes = findpeaks(np.array(vector), spacing=1) 9 | print('Peaks are: %s' % (indexes)) 10 | plot_peaks( 11 | np.array(vector), 12 | np.array(indexes), 13 | algorithm='findpeaks from Janko Slavic' 14 | ) 15 | 16 | print('Detect peaks with distance and height filters.') 17 | indexes = findpeaks(np.array(vector), spacing=2, limit=7) 18 | print('Peaks are: %s' % (indexes)) 19 | plot_peaks( 20 | np.array(vector), 21 | np.array(indexes), mph=7, mpd=2, 22 | algorithm='findpeaks from Janko Slavic' 23 | ) 24 | -------------------------------------------------------------------------------- /tests/peakutils_indexes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import numpy as np 4 | from vector import vector, plot_peaks 5 | import peakutils.peak 6 | 7 | print('Detect peaks without any filters.') 8 | indexes = peakutils.peak.indexes(np.array(vector), thres=0, min_dist=0) 9 | print('Peaks are: %s' % (indexes)) 10 | plot_peaks( 11 | np.array(vector), 12 | indexes, 13 | algorithm='peakutils.peak.indexes' 14 | ) 15 | 16 | print('Detect peaks with minimum height and distance filters.') 17 | indexes = peakutils.peak.indexes( 18 | np.array(vector), 19 | thres=7.0/max(vector), min_dist=2 20 | ) 21 | print('Peaks are: %s' % (indexes)) 22 | plot_peaks( 23 | np.array(vector), 24 | indexes, 25 | mph=7, mpd=2, algorithm='peakutils.peak.indexes' 26 | ) 27 | -------------------------------------------------------------------------------- /tests/libs/tony_beltramelli_detect_peaks.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Tony Beltramelli - 07/11/2015' 2 | #!/usr/bin/env python 3 | # -*- coding: utf-8 -*- 4 | import numpy as np 5 | from math import sqrt 6 | 7 | def detect_peaks(signal, threshold=0.5): 8 | """ Performs peak detection on three steps: root mean square, peak to 9 | average ratios and first order logic. 10 | threshold used to discard peaks too small """ 11 | # compute root mean square 12 | root_mean_square = sqrt(np.sum(np.square(signal) / len(signal))) 13 | # compute peak to average ratios 14 | ratios = np.array([pow(x / root_mean_square, 2) for x in signal]) 15 | # apply first order logic 16 | peaks = (ratios > np.roll(ratios, 1)) & (ratios > np.roll(ratios, -1)) & (ratios > threshold) 17 | # optional: return peak indices 18 | peak_indexes = [] 19 | for i in range(0, len(peaks)): 20 | if peaks[i]: 21 | peak_indexes.append(i) 22 | return peak_indexes 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | venv/ -------------------------------------------------------------------------------- /tests/scipy_argrelextrema.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import numpy as np 4 | from vector import vector, plot_peaks 5 | import scipy.signal 6 | 7 | print('Detect peaks without any filters (maxima).') 8 | indexes = scipy.signal.argrelextrema( 9 | np.array(vector), 10 | comparator=np.greater 11 | ) 12 | print('Peaks are: %s' % (indexes[0])) 13 | plot_peaks( 14 | np.array(vector), 15 | indexes[0], 16 | algorithm='scipy.signal.argrelmax' 17 | ) 18 | 19 | print('Detect peaks without any filters (minima).') 20 | indexes = scipy.signal.argrelextrema( 21 | np.array(vector), 22 | comparator=np.less 23 | ) 24 | print('Peaks are: %s' % (indexes[0])) 25 | plot_peaks( 26 | np.array(vector), 27 | indexes[0], 28 | algorithm='scipy.signal.argrelmax' 29 | ) 30 | 31 | print('Detect peaks with order (distance) filter.') 32 | indexes = scipy.signal.argrelextrema( 33 | np.array(vector), 34 | comparator=np.greater, 35 | order=2 36 | ) 37 | print('Peaks are: %s' % (indexes[0])) 38 | plot_peaks( 39 | np.array(vector), 40 | indexes[0], 41 | mpd=2, algorithm='scipy.signal.argrelmax' 42 | ) 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Yoan Tournade 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/peakdetect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import numpy as np 4 | from vector import vector, plot_peaks 5 | from libs import peakdetect 6 | 7 | print('Detect peaks without any filters.') 8 | peaks = peakdetect.peakdetect(np.array(vector), lookahead=2) 9 | # peakdetect returns two lists, respectively positive and negative peaks, 10 | # with for each peak a tuple of (indexes, values). 11 | indexes = [] 12 | for posOrNegPeaks in peaks: 13 | for peak in posOrNegPeaks: 14 | indexes.append(peak[0]) 15 | print('Peaks are: %s' % (indexes)) 16 | plot_peaks( 17 | np.array(vector), 18 | np.array(indexes), 19 | algorithm='peakdetect from sixtenbe' 20 | ) 21 | 22 | print('Detect peaks with distance filters.') 23 | peaks = peakdetect.peakdetect(np.array(vector), lookahead=2, delta=2) 24 | # peakdetect returns two lists, respectively positive and negative peaks, 25 | # with for each peak a tuple of (indexes, values). 26 | indexes = [] 27 | for posOrNegPeaks in peaks: 28 | for peak in posOrNegPeaks: 29 | indexes.append(peak[0]) 30 | print('Peaks are: %s' % (indexes)) 31 | plot_peaks( 32 | np.array(vector), 33 | np.array(indexes), 34 | mph=None, mpd=2, algorithm='peakdetect from sixtenbe' 35 | ) 36 | -------------------------------------------------------------------------------- /tests/octave_findpeaks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import numpy as np 4 | from vector import vector, plot_peaks 5 | from oct2py import octave 6 | 7 | # Load the Octage-Forge signal package. 8 | octave.eval("pkg load signal") 9 | 10 | print('Detect peaks without any filters.') 11 | (_, indexes) = octave.findpeaks( 12 | np.array(vector), 13 | 'DoubleSided', 'MinPeakHeight', 0, 'MinPeakDistance', 0, 'MinPeakWidth', 0 14 | ) 15 | # The results are in a 2D array and in floats: get back to 1D array and convert 16 | # peak indexes to integer. Also this is MatLab-style indexation (one-based), 17 | # so we must substract one to get back to Python indexation (zero-based). 18 | indexes = indexes[0].astype(int) - 1 19 | print('Peaks are: %s' % (indexes)) 20 | plot_peaks( 21 | np.array(vector), 22 | indexes, 23 | algorithm='Octave-Forge findpeaks' 24 | ) 25 | 26 | print('Detect peaks with minimum height and distance filters.') 27 | (pks, indexes) = octave.findpeaks( 28 | np.array(vector), 29 | 'DoubleSided', 'MinPeakHeight', 6, 'MinPeakDistance', 2, 'MinPeakWidth', 0 30 | ) 31 | # The results are in a 2D array and in floats: get back to 1D array and convert 32 | # peak indexes to integer. Also this is MatLab-style indexation (one-based), 33 | # so we must substract one to get back to Python indexation (zero-based). 34 | indexes = indexes[0].astype(int) - 1 35 | print('Peaks are: %s' % (indexes)) 36 | plot_peaks( 37 | np.array(vector), 38 | indexes, 39 | mph=6, mpd=2, algorithm='Octave-Forge findpeaks' 40 | ) 41 | -------------------------------------------------------------------------------- /tests/libs/findpeaks.py: -------------------------------------------------------------------------------- 1 | """ Searches for peaks in data 2 | History: 3 | -nov 2015: Janko Slavic, update 4 | -mar 2013: janko.slavic@fs.uni-lj.si 5 | """ 6 | 7 | import numpy as np 8 | 9 | 10 | def findpeaks(data, spacing=1, limit=None): 11 | """Finds peaks in `data` which are of `spacing` width and >=`limit`. 12 | :param data: values 13 | :param spacing: minimum spacing to the next peak (should be 1 or more) 14 | :param limit: peaks should have value greater or equal 15 | :return: 16 | """ 17 | len = data.size 18 | x = np.zeros(len+2*spacing) 19 | x[:spacing] = data[0]-1.e-6 20 | x[-spacing:] = data[-1]-1.e-6 21 | x[spacing:spacing+len] = data 22 | peak_candidate = np.zeros(len) 23 | peak_candidate[:] = True 24 | for s in range(spacing): 25 | start = spacing - s - 1 26 | h_b = x[start : start + len] # before 27 | start = spacing 28 | h_c = x[start : start + len] # central 29 | start = spacing + s + 1 30 | h_a = x[start : start + len] # after 31 | peak_candidate = np.logical_and(peak_candidate, np.logical_and(h_c > h_b, h_c > h_a)) 32 | 33 | ind = np.argwhere(peak_candidate) 34 | ind = ind.reshape(ind.size) 35 | if limit is not None: 36 | ind = ind[data[ind] > limit] 37 | return ind 38 | 39 | 40 | if __name__ == '__main__': 41 | import matplotlib.pyplot as plt 42 | 43 | n = 80 44 | m = 20 45 | limit = 0 46 | spacing = 3 47 | t = np.linspace(0., 1, n) 48 | x = np.zeros(n) 49 | np.random.seed(0) 50 | phase = 2 * np.pi * np.random.random(m) 51 | for i in range(m): 52 | x += np.sin(phase[i] + 2 * np.pi * t * i) 53 | 54 | peaks = findpeaks(x, spacing=spacing, limit=limit) 55 | plt.plot(t, x) 56 | plt.axhline(limit, color='r') 57 | plt.plot(t[peaks], x[peaks], 'ro') 58 | plt.title('Peaks: minimum value {limit}, minimum spacing {spacing} points'.format(**{'limit': limit, 'spacing': spacing})) 59 | plt.show() 60 | -------------------------------------------------------------------------------- /tests/lows_and_highs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import numpy as np 4 | from vector import cb, plot_peaks 5 | import peakutils.peak 6 | 7 | def plot_peaks_lows_highs(x, highs, lows, algorithm=None, mph=None, mpd=None): 8 | """Plot results of the peak dectection.""" 9 | try: 10 | import matplotlib.pyplot as plt 11 | except ImportError: 12 | print('matplotlib is not available.') 13 | return 14 | _, ax = plt.subplots(1, 1, figsize=(8, 4)) 15 | ax.plot(x, 'b', lw=1) 16 | if highs.size: 17 | label = 'high peak' 18 | label = label + 's' if highs.size > 1 else label 19 | ax.plot(highs, x[highs], '+', mfc=None, mec='r', mew=2, ms=8, 20 | label='%d %s' % (highs.size, label)) 21 | ax.legend(loc='best', framealpha=.5, numpoints=1) 22 | if lows.size: 23 | label = 'low peak' 24 | label = label + 's' if lows.size > 1 else label 25 | ax.plot(lows, x[lows], '+', mfc=None, mec='g', mew=2, ms=8, 26 | label='%d %s' % (lows.size, label)) 27 | ax.legend(loc='best', framealpha=.5, numpoints=1) 28 | ax.set_xlim(-.02*x.size, x.size*1.02-1) 29 | ymin, ymax = x[np.isfinite(x)].min(), x[np.isfinite(x)].max() 30 | yrange = ymax - ymin if ymax > ymin else 1 31 | ax.set_ylim(ymin - 0.1*yrange, ymax + 0.1*yrange) 32 | ax.set_xlabel('Data #', fontsize=14) 33 | ax.set_ylabel('Amplitude', fontsize=14) 34 | ax.set_title('%s (mph=%s, mpd=%s)' % (algorithm, mph, mpd)) 35 | plt.show() 36 | 37 | threshold = 0.02 38 | min_dist = 150 39 | 40 | print('Detect high peaks with minimum height and distance filters.') 41 | highs = peakutils.peak.indexes( 42 | np.array(cb), 43 | thres=threshold/max(cb), min_dist=min_dist 44 | ) 45 | print('High peaks are: %s' % (highs)) 46 | 47 | print('Detect low peaks with minimum height and distance filters.') 48 | # Invert the signal. 49 | cbInverted = cb * -1 50 | lows = peakutils.peak.indexes( 51 | np.array(cbInverted), 52 | thres=threshold/max(cbInverted), min_dist=min_dist 53 | ) 54 | print('Low peaks are: %s' % (lows)) 55 | 56 | plot_peaks_lows_highs( 57 | np.array(cb), 58 | highs, 59 | lows, 60 | mph=threshold, mpd=min_dist, algorithm='peakutils.peak.indexes' 61 | ) 62 | -------------------------------------------------------------------------------- /tests/libs/detect_peaks.py: -------------------------------------------------------------------------------- 1 | """Detect peaks in data based on their amplitude and other features.""" 2 | 3 | from __future__ import division, print_function 4 | import numpy as np 5 | 6 | __author__ = "Marcos Duarte, https://github.com/demotu/BMC" 7 | __version__ = "1.0.4" 8 | __license__ = "MIT" 9 | 10 | def detect_peaks(x, mph=None, mpd=1, threshold=0, edge='rising', 11 | kpsh=False, valley=False, show=False, ax=None): 12 | 13 | """Detect peaks in data based on their amplitude and other features. 14 | 15 | Parameters 16 | ---------- 17 | x : 1D array_like 18 | data. 19 | mph : {None, number}, optional (default = None) 20 | detect peaks that are greater than minimum peak height. 21 | mpd : positive integer, optional (default = 1) 22 | detect peaks that are at least separated by minimum peak distance (in 23 | number of data). 24 | threshold : positive number, optional (default = 0) 25 | detect peaks (valleys) that are greater (smaller) than `threshold` 26 | in relation to their immediate neighbors. 27 | edge : {None, 'rising', 'falling', 'both'}, optional (default = 'rising') 28 | for a flat peak, keep only the rising edge ('rising'), only the 29 | falling edge ('falling'), both edges ('both'), or don't detect a 30 | flat peak (None). 31 | kpsh : bool, optional (default = False) 32 | keep peaks with same height even if they are closer than `mpd`. 33 | valley : bool, optional (default = False) 34 | if True (1), detect valleys (local minima) instead of peaks. 35 | show : bool, optional (default = False) 36 | if True (1), plot data in matplotlib figure. 37 | ax : a matplotlib.axes.Axes instance, optional (default = None). 38 | 39 | Returns 40 | ------- 41 | ind : 1D array_like 42 | indeces of the peaks in `x`. 43 | 44 | Notes 45 | ----- 46 | The detection of valleys instead of peaks is performed internally by simply 47 | negating the data: `ind_valleys = detect_peaks(-x)` 48 | 49 | The function can handle NaN's 50 | 51 | See this IPython Notebook [1]_. 52 | 53 | References 54 | ---------- 55 | .. [1] http://nbviewer.ipython.org/github/demotu/BMC/blob/master/notebooks/DetectPeaks.ipynb 56 | 57 | Examples 58 | -------- 59 | >>> from detect_peaks import detect_peaks 60 | >>> x = np.random.randn(100) 61 | >>> x[60:81] = np.nan 62 | >>> # detect all peaks and plot data 63 | >>> ind = detect_peaks(x, show=True) 64 | >>> print(ind) 65 | 66 | >>> x = np.sin(2*np.pi*5*np.linspace(0, 1, 200)) + np.random.randn(200)/5 67 | >>> # set minimum peak height = 0 and minimum peak distance = 20 68 | >>> detect_peaks(x, mph=0, mpd=20, show=True) 69 | 70 | >>> x = [0, 1, 0, 2, 0, 3, 0, 2, 0, 1, 0] 71 | >>> # set minimum peak distance = 2 72 | >>> detect_peaks(x, mpd=2, show=True) 73 | 74 | >>> x = np.sin(2*np.pi*5*np.linspace(0, 1, 200)) + np.random.randn(200)/5 75 | >>> # detection of valleys instead of peaks 76 | >>> detect_peaks(x, mph=0, mpd=20, valley=True, show=True) 77 | 78 | >>> x = [0, 1, 1, 0, 1, 1, 0] 79 | >>> # detect both edges 80 | >>> detect_peaks(x, edge='both', show=True) 81 | 82 | >>> x = [-2, 1, -2, 2, 1, 1, 3, 0] 83 | >>> # set threshold = 2 84 | >>> detect_peaks(x, threshold = 2, show=True) 85 | """ 86 | 87 | x = np.atleast_1d(x).astype('float64') 88 | if x.size < 3: 89 | return np.array([], dtype=int) 90 | if valley: 91 | x = -x 92 | # find indexes of all peaks 93 | dx = x[1:] - x[:-1] 94 | # handle NaN's 95 | indnan = np.where(np.isnan(x))[0] 96 | if indnan.size: 97 | x[indnan] = np.inf 98 | dx[np.where(np.isnan(dx))[0]] = np.inf 99 | ine, ire, ife = np.array([[], [], []], dtype=int) 100 | if not edge: 101 | ine = np.where((np.hstack((dx, 0)) < 0) & (np.hstack((0, dx)) > 0))[0] 102 | else: 103 | if edge.lower() in ['rising', 'both']: 104 | ire = np.where((np.hstack((dx, 0)) <= 0) & (np.hstack((0, dx)) > 0))[0] 105 | if edge.lower() in ['falling', 'both']: 106 | ife = np.where((np.hstack((dx, 0)) < 0) & (np.hstack((0, dx)) >= 0))[0] 107 | ind = np.unique(np.hstack((ine, ire, ife))) 108 | # handle NaN's 109 | if ind.size and indnan.size: 110 | # NaN's and values close to NaN's cannot be peaks 111 | ind = ind[np.in1d(ind, np.unique(np.hstack((indnan, indnan-1, indnan+1))), invert=True)] 112 | # first and last values of x cannot be peaks 113 | if ind.size and ind[0] == 0: 114 | ind = ind[1:] 115 | if ind.size and ind[-1] == x.size-1: 116 | ind = ind[:-1] 117 | # remove peaks < minimum peak height 118 | if ind.size and mph is not None: 119 | ind = ind[x[ind] >= mph] 120 | # remove peaks - neighbors < threshold 121 | if ind.size and threshold > 0: 122 | dx = np.min(np.vstack([x[ind]-x[ind-1], x[ind]-x[ind+1]]), axis=0) 123 | ind = np.delete(ind, np.where(dx < threshold)[0]) 124 | # detect small peaks closer than minimum peak distance 125 | if ind.size and mpd > 1: 126 | ind = ind[np.argsort(x[ind])][::-1] # sort ind by peak height 127 | idel = np.zeros(ind.size, dtype=bool) 128 | for i in range(ind.size): 129 | if not idel[i]: 130 | # keep peaks with the same height if kpsh is True 131 | idel = idel | (ind >= ind[i] - mpd) & (ind <= ind[i] + mpd) \ 132 | & (x[ind[i]] > x[ind] if kpsh else True) 133 | idel[i] = 0 # Keep current peak 134 | # remove the small peaks and sort back the indexes by their occurrence 135 | ind = np.sort(ind[~idel]) 136 | 137 | if show: 138 | if indnan.size: 139 | x[indnan] = np.nan 140 | if valley: 141 | x = -x 142 | _plot(x, mph, mpd, threshold, edge, valley, ax, ind) 143 | 144 | return ind 145 | 146 | def _plot(x, mph, mpd, threshold, edge, valley, ax, ind): 147 | """Plot results of the detect_peaks function, see its help.""" 148 | try: 149 | import matplotlib.pyplot as plt 150 | except ImportError: 151 | print('matplotlib is not available.') 152 | else: 153 | if ax is None: 154 | _, ax = plt.subplots(1, 1, figsize=(8, 4)) 155 | 156 | ax.plot(x, 'b', lw=1) 157 | if ind.size: 158 | label = 'valley' if valley else 'peak' 159 | label = label + 's' if ind.size > 1 else label 160 | ax.plot(ind, x[ind], '+', mfc=None, mec='r', mew=2, ms=8, 161 | label='%d %s' % (ind.size, label)) 162 | ax.legend(loc='best', framealpha=.5, numpoints=1) 163 | ax.set_xlim(-.02*x.size, x.size*1.02-1) 164 | ymin, ymax = x[np.isfinite(x)].min(), x[np.isfinite(x)].max() 165 | yrange = ymax - ymin if ymax > ymin else 1 166 | ax.set_ylim(ymin - 0.1*yrange, ymax + 0.1*yrange) 167 | ax.set_xlabel('Data #', fontsize=14) 168 | ax.set_ylabel('Amplitude', fontsize=14) 169 | mode = 'Valley detection' if valley else 'Peak detection' 170 | ax.set_title("%s (mph=%s, mpd=%d, threshold=%s, edge='%s')" 171 | % (mode, str(mph), mpd, str(threshold), edge)) 172 | # plt.grid() 173 | plt.show() -------------------------------------------------------------------------------- /tests/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "84f380b7aa7237330ad758022c5975bd065d7961dcbf93ef778600c386863f5b" 5 | }, 6 | "host-environment-markers": { 7 | "implementation_name": "cpython", 8 | "implementation_version": "3.6.3", 9 | "os_name": "posix", 10 | "platform_machine": "x86_64", 11 | "platform_python_implementation": "CPython", 12 | "platform_release": "4.13.0-32-generic", 13 | "platform_system": "Linux", 14 | "platform_version": "#35-Ubuntu SMP Thu Jan 25 09:13:46 UTC 2018", 15 | "python_full_version": "3.6.3", 16 | "python_version": "3.6", 17 | "sys_platform": "linux" 18 | }, 19 | "pipfile-spec": 6, 20 | "requires": {}, 21 | "sources": [ 22 | { 23 | "name": "pypi", 24 | "url": "https://pypi.python.org/simple", 25 | "verify_ssl": true 26 | } 27 | ] 28 | }, 29 | "default": { 30 | "cycler": { 31 | "hashes": [ 32 | "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d", 33 | "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8" 34 | ], 35 | "version": "==0.10.0" 36 | }, 37 | "matplotlib": { 38 | "hashes": [ 39 | "sha256:31662334a4485455167072f80c57d2d2ef9ada4b93197b4851ceacee7269cb17", 40 | "sha256:0c4a0cb10972b18049acde76e4611bcda0fa288a199a5f2c3c6f21ab208010cf", 41 | "sha256:09ac1bde82673bfb4f1cb9ebd7fe258b29400f1be8914cb0891eca62a99cf7e6", 42 | "sha256:295d2b135cb4d546fbb4b4d0b8d62d2132436adcf86221ece1d0bd4a3522128c", 43 | "sha256:710bfe01633a4b734742163187bae99399de9f2c46cb6b40bd79b51467f7ba36", 44 | "sha256:ae091625121ff5bf31515332ee604c90b55f9e6f6a02ac7160e5199f0422a211", 45 | "sha256:fe8d2e29753fbbc1923e18e80487f09fdc01f270ff44da7156f147a3075876c0", 46 | "sha256:9a87c1c8f195f0908b599268332608e5024b34cdd07375c68c15ac24b2a93a4e", 47 | "sha256:ad987a871682d34d44a941cc47bf12f60e7866be7133498c432fb3d6fa3be651", 48 | "sha256:59736af4bed61af336da60b8e97b700e695ffae9af08941c83e37ab0de3f151a", 49 | "sha256:932ca62fb7c4edc377d6f0078995a1c004b7e4e9982a025e7022eb5eb1fc33ba", 50 | "sha256:fb96735f76d5975e09eedc3502e9ff63dbe0c04aef312d0d6048a97f7993e667", 51 | "sha256:2cc12ea94d80990e454a67ce829dc084bbdbfd0c95ed1f527dbd64688f184acd", 52 | "sha256:97b9b56bbfce43deefbc2a089fa4afa8887e84041f17b090ac3d9f90a9e8e745", 53 | "sha256:2452beef35840bdd9c3f613fc112ce6eb449cabca2bcb8a3f0d7eedd8e11d85d", 54 | "sha256:adde3c9eb2145e5597a593877aea456e0bfe37f46cb3534caacce61b603c451a", 55 | "sha256:fe5f8487aa872164a80b0c491f1110d4e2125391933caf17cae0a86df7950a72", 56 | "sha256:725a3f12739d133adfa381e1b33bd70c6f64db453bfc536e148824816e568894" 57 | ], 58 | "version": "==2.1.2" 59 | }, 60 | "numpy": { 61 | "hashes": [ 62 | "sha256:428cd3c0b197cf857671353d8c85833193921af9fafcc169a1f29c7185833d50", 63 | "sha256:a476e437d73e5754aa66e1e75840d0163119c3911b7361f4cd06985212a3c3fb", 64 | "sha256:289ff717138cd9aa133adcbd3c3e284458b9c8230db4d42b39083a3407370317", 65 | "sha256:c5eccb4bf96dbb2436c61bb3c2658139e779679b6ae0d04c5e268e6608b58053", 66 | "sha256:75471acf298d455b035226cc609a92aee42c4bb6aa71def85f77fa2c2b646b61", 67 | "sha256:5c54fb98ecf42da59ed93736d1c071842482b18657eb16ba6e466bd873e1b923", 68 | "sha256:9ddf384ac3aacb72e122a8207775cc29727cbd9c531ee1a4b95754f24f42f7f3", 69 | "sha256:781d3197da49c421a07f250750de70a52c42af08ca02a2f7bdb571c0625ae7eb", 70 | "sha256:93b26d6c06a22e64d56aaca32aaaffd27a4143db0ac2f21a048f0b571f2bfc55", 71 | "sha256:b2547f57d05ba59df4289493254f29f4c9082d255f1f97b7e286f40f453e33a1", 72 | "sha256:eef6af1c752eef538a96018ef9bdf8e37bbf28aab50a1436501a4aa47a6467df", 73 | "sha256:ff8a4b2c3ac831964f529a2da506c28d002562b230261ae5c16885f5f53d2e75", 74 | "sha256:194074058c22a4066e1b6a4ea432486ee468d24ab16f13630c1030409e6b8666", 75 | "sha256:4e13f1a848fde960dea33702770265837c72b796a6a3eaac7528cfe75ddefadd", 76 | "sha256:91101216d72749df63968d86611b549438fb18af2c63849c01f9a897516133c7", 77 | "sha256:97507349abb7d1f6b76b877258defe8720833881dc7e7fd052bac90c88587387", 78 | "sha256:1479b46b6040b5c689831496354c8859c456b152d37315673a0c18720b41223b", 79 | "sha256:98b1ac79c160e36093d7914244e40ee1e7164223e795aa2c71dcce367554e646", 80 | "sha256:24bbec9a199f938eab75de8390f410969bc33c218e5430fa1ae9401b00865255", 81 | "sha256:7880f412543e96548374a4bb1d75e4cdb8cad80f3a101ed0f8d0e0428f719c1c", 82 | "sha256:6112f152b76a28c450bbf665da11757078a724a90330112f5b7ea2d6b6cefd67", 83 | "sha256:7c5276763646480143d5f3a6c2acb2885460c765051a1baf4d5070f63d05010f", 84 | "sha256:3de643935b212307b420248018323a44ec51987a336d1d747c1322afc3c099fb" 85 | ], 86 | "version": "==1.14.0" 87 | }, 88 | "oct2py": { 89 | "hashes": [ 90 | "sha256:878d8e28a52fffd6cc03b47062c136355d3ea6153db7003faa316926c1d54c83", 91 | "sha256:08973a30acc8a6e984c305ed157c6aa5dfa8b5f30d051bef3264a2fbaa01e7b9", 92 | "sha256:090a4f4fc3229b1e78142e9cf054cc7314b5e9c9f8869a64f29f1d3241153949" 93 | ], 94 | "version": "==3.1.0" 95 | }, 96 | "peakutils": { 97 | "hashes": [ 98 | "sha256:b846bf44ec2246ffd6ce735a7a89beb3c230b5f6bb9f988a37cd5c569d5a116a" 99 | ], 100 | "version": "==1.1.0" 101 | }, 102 | "pyparsing": { 103 | "hashes": [ 104 | "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010", 105 | "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04", 106 | "sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e", 107 | "sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07", 108 | "sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5", 109 | "sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18", 110 | "sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58" 111 | ], 112 | "version": "==2.2.0" 113 | }, 114 | "python-dateutil": { 115 | "hashes": [ 116 | "sha256:95511bae634d69bc7329ba55e646499a842bc4ec342ad54a8cdb65645a0aad3c", 117 | "sha256:891c38b2a02f5bb1be3e4793866c8df49c7d19baabf9c1bad62547e0b4866aca" 118 | ], 119 | "version": "==2.6.1" 120 | }, 121 | "pytz": { 122 | "hashes": [ 123 | "sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe", 124 | "sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda", 125 | "sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9", 126 | "sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f", 127 | "sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd", 128 | "sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5", 129 | "sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d", 130 | "sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef", 131 | "sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0" 132 | ], 133 | "version": "==2018.3" 134 | }, 135 | "scipy": { 136 | "hashes": [ 137 | "sha256:70e6fc3f2f52c9152f05e27eb9bd8543cb862cacb71f8521a571e4ffb837f450", 138 | "sha256:08041e5336fcd57defcc78650b44b3df652eff3e3a801638d894e50494fb630d", 139 | "sha256:ff8b6637d8d2c074ed67f3d57513e62f94747c6f1210f43e60ad3d8e93a424e4", 140 | "sha256:5964dba6a3c0be226d44d2520de8fb4ba1501768bad57eec687d36d3f53b6254", 141 | "sha256:bf36f3485e7b7291c36330a93bbfd4f5e8db23bbe4ea46c37b2839fef463f4e2", 142 | "sha256:e3a5673c105eab802fdecb77f102d877352e201df9328698a265b7f57546b34b", 143 | "sha256:cd23894e1cc6eaa00e6807b6b12e4ca66d5ff092986c9c3eb01e97f24e2d6462", 144 | "sha256:23a7238279ae94e088396b8b05a9795ef598dc79c5cd1adb91ad1ff87c7514fd", 145 | "sha256:3b66d5e40152175bca75cbbfd1eb5c108c50de9ae5625923f1c4f8f51cbe2dea", 146 | "sha256:fa17be6c66985931d3a391f61a6ba97c902585cf26020aa3eb24604115732d22", 147 | "sha256:d84df0bc86bbdd49f0a6b6bad5cd62ccb02a3bfe546bf79263de44ae081bcd7b", 148 | "sha256:912499ddb521b7ac6287ac4ccf5f296a83d38996c2d04f43c9e62a91f7b420aa", 149 | "sha256:889602ead28054a15e8c26e1a6b8420d5a4fa777cfeb3ec98cfa52b9f317d153", 150 | "sha256:5774adb6047983489bc81edaa72cd132e665e5680f0b2cf8ea28cd3b99e65d39", 151 | "sha256:01c7040a83eb4e020ab729488637dcadef54cb728b035b76668ab92a72515d60", 152 | "sha256:046705c604c6f1d63cad3e89677c0618b7abb40ed09a4c241c671a2d8e5128a9", 153 | "sha256:1f58fbd59e8d9652759df0d137832ff2a325ed708c173cba20c86589d811c210", 154 | "sha256:424500b2fe573d30de6dea927076c01acaadb3efb3d1f40340e8cc37151ccf27", 155 | "sha256:97123a25216616723083942eb595f47fee18da6b637a88b803de5f078009003c", 156 | "sha256:a79b99b8b5af9a63312bd053bbb7bdb7710e6bbb9cc81617f9f6b9b1e49c72f8", 157 | "sha256:9bd193686fd837472bdb6425486cb234ed0a4db76b930c141cc8d095ab213c8d", 158 | "sha256:a9e479648aab5f36330da94f351ebbfe79acb4e6f5e6ac6aeddc9291eb096839", 159 | "sha256:87ea1f11a0e9ec08c264dc64551d501fa307289460705f6fccd84cbfc7926d10" 160 | ], 161 | "version": "==1.0.0" 162 | }, 163 | "six": { 164 | "hashes": [ 165 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", 166 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" 167 | ], 168 | "version": "==1.11.0" 169 | } 170 | }, 171 | "develop": {} 172 | } 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is an overview of all the ready-to-use algorithms I've found to perform peak detection in Python. I've also written [a blog post](https://blog.ytotech.com/2015/11/01/findpeaks-in-python/) on the subject. 2 | 3 | ## Overview 4 | 5 | | Algorithm | Integration | Filters | MatLab `findpeaks`-like? | 6 | |-----------| ----------- | ------- | :----------------------: | 7 | | [scipy.signal.find_peaks_cwt](#scipysignalfind_peaks_cwt) | Included in Scipy | ? | ✘ | 8 | | [scipy.signal.argrelextrema](#scipysignalargrelextrema) | Included in Scipy 0.11+ | Minimum distance | ✘ | 9 | | [detect_peaks](#detect_peaks-from-marcos-duarte) | Single file source
Depends on Numpy | Minimum distance
Minimum height
Relative threshold | ✔ | 10 | | [peakutils.peak.indexes](#peakutilspeakindexes) | PyPI package PeakUtils
Depends on Scipy | Amplitude threshold
Minimum distance | ✔ | 11 | | [peakdetect](#peakdetect-from-sixtenbe) | Single file source
Depends on Scipy | Minimum distance | ✘ | 12 | | [Octave-Forge findpeaks](#octave-forge-findpeaks) | Requires an Octave-Forge distribution
+ PyPI package oct2py
Depends on Scipy | Minimum distance
Minimum height
Minimum peak width | ✘ | 13 | | [Janko Slavic findpeaks](#janko-slavic-findpeaks) | Single function
Depends on Numpy | Minimum distance
Minimum height | ✘ | 14 | | [Tony Beltramelli detect_peaks](#tony-beltramelli-detect_peaks) | Single function
Depends on Numpy | Amplitude threshold | ✘ | 15 | 16 | ## How to make your choice? 17 | 18 | When you're selecting an algorithm, you might consider: 19 | 20 | * **The function interface.** You may want the function to work natively with Numpy arrays or may search something similar to other platform algorithms, like the MatLab [`findpeaks`](http://fr.mathworks.com/help/signal/ref/findpeaks.html). 21 | * **The dependencies.** Does it require extra dependency? Does is it easy to make it run on a fresh box? 22 | * **The filtering support**. Does the algorithm allows to define multiple filters? Which ones do you need? 23 | 24 | -------------------------------- 25 | 26 | ## scipy.signal.find_peaks_cwt 27 | 28 | ![](/images/scipy_find_peaks_cwt.png?raw=true "scipy.signal.find_peaks_cwt") 29 | 30 | ```python 31 | import numpy as np 32 | vector = [ 33 | 0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3, 34 | 1, 20, 7, 3, 0 ] 35 | import scipy.signal 36 | print('Detect peaks without any filters.') 37 | indexes = scipy.signal.find_peaks_cwt(vector, np.arange(1, 4), 38 | max_distances=np.arange(1, 4)*2) 39 | indexes = np.array(indexes) - 1 40 | print('Peaks are: %s' % (indexes)) 41 | ``` 42 | 43 | [Documentation](http://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.find_peaks_cwt.html). 44 | [Sample code](/tests/scipy_find_peaks_cwt.py). 45 | 46 | The first historical peak detection algorithm from the Scipy signal processing package. 47 | Its name appears to make it an obvious choice (when you already work with Scipy), but it may actually not be, as it uses a wavelet convolution approach. 48 | 49 | This function requires to understand wavelets to be properly used. This is less trivial and direct than other algorithms. However the wavelet approach can make it a good choice on noisy data. 50 | 51 | ## scipy.signal.argrelextrema 52 | 53 | ![](/images/scipy_argrelextrema.png?raw=true "scipy.signal.argrelextrema") 54 | 55 | ```python 56 | import numpy as np 57 | vector = [ 58 | 0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3, 59 | 1, 20, 7, 3, 0 ] 60 | import scipy.signal 61 | print('Detect peaks with order (distance) filter.') 62 | indexes = scipy.signal.argrelextrema( 63 | np.array(vector), 64 | comparator=np.greater,order=2 65 | ) 66 | print('Peaks are: %s' % (indexes[0])) 67 | ``` 68 | 69 | [Documentation](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.argrelextrema.html). 70 | [Sample code](/tests/scipy_argrelextrema.py). 71 | 72 | New peak detection algorithm from Scipy since version 0.11.0. Its usage is really trivial, 73 | but it misses out of the box filtering capacities. 74 | 75 | It includes an `order` parameter that can serve as a kind of minimum distance filter. 76 | The filtering behavior is customizable through the `comparator` parameter, which 77 | can make it a good choice for building your own filtering algorithm over it. 78 | 79 | See also related functions [argrelmin](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.argrelmin.html) and [argrelmax](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.argrelmax.html). 80 | 81 | ## detect_peaks from Marcos Duarte 82 | 83 | ![](/images/detect_peaks.png?raw=true "detect_peaks from Marcos Duarte") 84 | 85 | ```python 86 | import numpy as np 87 | vector = [ 88 | 0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3, 89 | 1, 20, 7, 3, 0 ] 90 | from libs import detect_peaks 91 | print('Detect peaks with minimum height and distance filters.') 92 | indexes = detect_peaks.detect_peaks(vector, mph=7, mpd=2) 93 | print('Peaks are: %s' % (indexes)) 94 | ``` 95 | 96 | [Documentation](http://nbviewer.ipython.org/github/demotu/BMC/blob/master/notebooks/DetectPeaks.ipynb). 97 | [Source](/tests/libs/detect_peaks.py). 98 | [Sample code](/tests/detect_peaks.py). 99 | 100 | This algorithm comes from a notebook written by Marcos Duarte and is pretty trivial to use. 101 | 102 | The function has an interface very similar and consistent results with the MatLab Signal Processing Toolbox `findpeaks`, yet with less complete filtering and tuning support. 103 | 104 | ## peakutils.peak.indexes 105 | 106 | ![](/images/peakutils_indexes.png?raw=true "peakutils.peak.indexes") 107 | 108 | ```python 109 | import numpy as np 110 | vector = [ 111 | 0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3, 112 | 1, 20, 7, 3, 0 ] 113 | import peakutils.peak 114 | print('Detect peaks with minimum height and distance filters.') 115 | indexes = peakutils.peak.indexes(np.array(vector), 116 | thres=7.0/max(vector), min_dist=2) 117 | print('Peaks are: %s' % (indexes)) 118 | ``` 119 | 120 | [Documentation](http://pythonhosted.org/PeakUtils/reference.html#peakutils.peak.indexes). 121 | [Package](https://bitbucket.org/lucashnegri/peakutils). 122 | [Sample code](/tests/peakutils_indexes.py). 123 | 124 | This algorithm can be used as an equivalent of the MatLab `findpeaks` and will give easily give consistent results if you only need minimal distance and height filtering. 125 | 126 | ## peakdetect from sixtenbe 127 | 128 | ![](/images/sixtenbe_peakdetect.png?raw=true "peakdetect from sixtenbe") 129 | 130 | ```python 131 | import numpy as np 132 | vector = [ 133 | 0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3, 134 | 1, 20, 7, 3, 0 ] 135 | from libs import peakdetect 136 | print('Detect peaks with distance filters.') 137 | peaks = peakdetect.peakdetect(np.array(vector), lookahead=2, delta=2) 138 | # peakdetect returns two lists, respectively positive and negative peaks, 139 | # with for each peak a tuple of (indexes, values). 140 | indexes = [] 141 | for posOrNegPeaks in peaks: 142 | for peak in posOrNegPeaks: 143 | indexes.append(peak[0]) 144 | print('Peaks are: %s' % (indexes)) 145 | ``` 146 | 147 | [Source and documentation](https://gist.github.com/sixtenbe/1178136). 148 | [Sample code](/tests/peakdetect.py). 149 | 150 | The algorithm was written by sixtenbe based on the previous work of [endolith](https://gist.github.com/endolith/250860) and [Eli Billauer](http://billauer.co.il/peakdet.html). 151 | 152 | Easy to setup as it comes in a single source file, but the lookahead parameter make it hard to use on low-sampled signals or short samples. May miss filtering capacities (only minimum peak distance with the delta parameter). 153 | 154 | ## Octave-Forge findpeaks 155 | 156 | ![](/images/octave_findpeaks.png?raw=true "Octave-Forge findpeaks") 157 | 158 | ```python 159 | import numpy as np 160 | vector = [ 161 | 0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3, 162 | 1, 20, 7, 3, 0 ] 163 | from oct2py import octave 164 | # Load the Octage-Forge signal package. 165 | octave.eval("pkg load signal") 166 | print('Detect peaks with minimum height and distance filters.') 167 | (pks, indexes) = octave.findpeaks(np.array(vector), 'DoubleSided', 168 | 'MinPeakHeight', 6, 'MinPeakDistance', 2, 'MinPeakWidth', 0) 169 | # The results are in a 2D array and in floats: get back to 1D array and convert 170 | # peak indexes to integer. Also this is MatLab-style indexation (one-based), 171 | # so we must substract one to get back to Python indexation (zero-based). 172 | indexes = indexes[0].astype(int) - 1 173 | print('Peaks are: %s' % (indexes)) 174 | ``` 175 | 176 | [Documentation](http://octave.sourceforge.net/signal/function/findpeaks.html). 177 | [oct2py package](https://github.com/blink1073/oct2py). 178 | [Sample code](/tests/octave_findpeaks.py). 179 | 180 | Use `findpeaks` from the Octave-Forge signal package through the oct2py bridge. This algorithm allows to make a double sided detection, which means it will detect both local maxima and minima in a single run. 181 | 182 | Requires a rather complicated and not very efficient setup to be called from Python code. Of course, you will need an up-to-date distribution of Octave, with the signal package installed from Octave-Forge. 183 | 184 | Although the function have an interface close to the MatLab `findpeaks`, it is harder to have the exact same results that with [detect_peaks](#detect_peaks-from-marcos-duarte) or [peakutils.peak.indexes](#peakutilspeakindexes). 185 | 186 | ## Janko Slavic findpeaks 187 | 188 | ![](/images/janko_slavic_findpeaks.png?raw=true "Janko Slavic findpeaks") 189 | 190 | ```python 191 | import numpy as np 192 | vector = [ 193 | 0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3, 194 | 1, 20, 7, 3, 0 ] 195 | from libs.findpeaks import findpeaks 196 | indexes = findpeaks(np.array(vector), spacing=, limit=7) 197 | print('Peaks are: %s' % (indexes)) 198 | ``` 199 | 200 | [Documentation](https://github.com/jankoslavic/py-tools/blob/master/findpeaks/Findpeaks%20example.ipynb). 201 | [Source](https://github.com/jankoslavic/py-tools/blob/master/findpeaks/findpeaks.py). 202 | [Sample code](/tests/janko_slavic_findpeaks.py). 203 | 204 | Small and fast peak detection algorithm, with minimum distance and height filtering support. Comes as an handy single function, depending only on Numpy. 205 | 206 | Contrary to the MatLab `findpeaks`-like distance filters, the Janko Slavic `findpeaks` `spacing` param requires that all points within the specified width to be lower than the peak. If you work on very low sampled signal, the minimum distance filter may miss fine granularity tuning. 207 | 208 | ## Tony Beltramelli detect_peaks 209 | 210 | ![](/images/tony_beltramelli_detect_peaks.png?raw=true "Lightweight standalone peaks") 211 | 212 | ```python 213 | import numpy as np 214 | vector = [ 215 | 0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3, 216 | 1, 20, 7, 3, 0 ] 217 | from libs.tony_beltramelli_detect_peaks import detect_peaks 218 | print('Detect peaks with height threshold.') 219 | indexes = detect_peaks(vector, 1.5) 220 | print('Peaks are: %s' % (indexes)) 221 | ``` 222 | 223 | [Source and documentation](/tests/libs/tony_beltramelli_detect_peaks.py). 224 | [Sample code](/tests/tony_beltramelli_detect_peaks.py). 225 | 226 | Straightforward, simple and lightweight peak detection algorithm, with minimum distance filtering support. 227 | 228 | No minimum peak height filtering support. 229 | 230 | ---------------------------------- 231 | 232 | # How to find both lows and highs? 233 | 234 | Most algorithms detect only local _maximas_. You may want to detect both _minimas_ and _maximas_. 235 | 236 | One solution is to invert the signal before feeding it to the algorithm for detecting lows, as suggested by [@raoofhujairi](https://github.com/MonsieurV/py-findpeaks/issues/3). 237 | 238 | With two runs, you can then get both lows and highs: 239 | 240 | ![](/images/highs_and_lows_peakutils_indexes.png?raw=true "High and low peaks with PeakUtils Indexes") 241 | 242 | See the related [sample code](/tests/lows_and_highs.py) using PeakUtils. 243 | 244 | ---------------------------------- 245 | 246 | # How to run the examples? 247 | 248 | ## Install Numpy, Scipy and Matplotlib 249 | 250 | You need to have Numpy, Scipy and Matplotlib installed - possibly the latest versions. 251 | 252 | To install - and update - them for Python 3: 253 | 254 | ```sh 255 | pip3 install -U numpy scipy matplotlib 256 | ``` 257 | 258 | (you may need to run the command using `sudo` for a system-wide install) 259 | 260 | ## Install test sample dependencies 261 | 262 | Some examples rely on other packages - like [PeakUtils](https://pypi.python.org/pypi/PeakUtils). 263 | Install them using Pipenv to run all sample codes: 264 | 265 | ```sh 266 | # Go in tests directory. 267 | cd tests/ 268 | # Install dependencies in a virtualenv using Pipenv. 269 | # We install also Matplotlib so we can access it in the virtualenv. 270 | # If you do not want that, you can do a system-wide install using: 271 | # pip install -r requirements.txt 272 | pipenv install 273 | ``` 274 | 275 | ## Run an example 276 | 277 | You can them run any example to see the results. 278 | 279 | For e.g. for testing PeakUtils: 280 | 281 | ```sh 282 | pipenv run python3 peakutils_indexes.py 283 | ``` 284 | 285 | # Contribute 286 | 287 | Feel free to [open a new ticket](https://github.com/MonsieurV/py-findpeaks/issues/new) or submit a PR to improve this overview. 288 | 289 | Happy processing! 290 | -------------------------------------------------------------------------------- /tests/vector.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | vector = [ 4 | 0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3, 5 | 1, 20, 7, 3, 0 6 | ] 7 | 8 | # This is an extract from Laurent Garnier Cryspy Bacon track. 9 | # https://www.youtube.com/watch?v=6SFD7fz8fWc 10 | # Converted from MP3 to WAV, then imported as a Numpy array. 11 | # No Copyright infringement intended. For educational purpose only. 12 | cb = np.array([-0.010406, -0.0080872, -0.0066528, -0.0074158, -0.0072937, -0.0063782, -0.005249, -0.0038147, -0.0021973, -0.0011902, -0.0014954, -0.00036621, 0.00030518, -0.00027466, -0.00073242, 0.0014648, 0.0025635, 0.0019531, 0.0033875, 0.0037842, 0.0047913, 0.0059509, 0.0049133, 0.0059814, 0.007019, 0.0062256, 0.0069885, 0.0074463, 0.007782, 0.0086975, 0.0089722, 0.0097656, 0.0083313, 0.0087585, 0.01001, 0.010345, 0.011139, 0.010345, 0.01123, 0.012482, 0.012329, 0.012573, 0.011505, 0.010773, 0.011017, 0.011658, 0.011353, 0.010498, 0.010803, 0.010773, 0.011932, 0.013184, 0.013428, 0.012665, 0.012451, 0.011993, 0.010559, 0.01004, 0.0097351, 0.0098267, 0.0099792, 0.0098267, 0.0092468, 0.0089111, 0.0088501, 0.0090027, 0.0088196, 0.0079346, 0.0068054, 0.006897, 0.0077209, 0.0073853, 0.0069275, 0.0046692, 0.0021362, 0.0013733, 0.00021362, -0.0024414, -0.0045776, -0.0067444, -0.0083313, -0.0088806, -0.011688, -0.012207, -0.012756, -0.01532, -0.014984, -0.017212, -0.020844, -0.022247, -0.022614, -0.023773, -0.02356, -0.024323, -0.025177, -0.024353, -0.02594, -0.026794, -0.02829, -0.029236, -0.029388, -0.030304, -0.030914, -0.032593, -0.032867, -0.03363, -0.032867, -0.03009, -0.032501, -0.035034, -0.035339, -0.035492, -0.035614, -0.037018, -0.037476, -0.039001, -0.039886, -0.038818, -0.039215, -0.039917, -0.041107, -0.041931, -0.042206, -0.042419, -0.043762, -0.043427, -0.041992, -0.043915, -0.043854, -0.045929, -0.047607, -0.046265, -0.045807, -0.045135, -0.045715, -0.045898, -0.04538, -0.043579, -0.043518, -0.04425, -0.043823, -0.044617, -0.045227, -0.044159, -0.042877, -0.044708, -0.043732, -0.043152, -0.043182, -0.041473, -0.041718, -0.04303, -0.043121, -0.042236, -0.043335, -0.043121, -0.041321, -0.042328, -0.043274, -0.040955, -0.040375, -0.041687, -0.039063, -0.038177, -0.039215, -0.037354, -0.037079, -0.036835, -0.035797, -0.035492, -0.034241, -0.032776, -0.033295, -0.033112, -0.03186, -0.031891, -0.032074, -0.030182, -0.030365, -0.03125, -0.030334, -0.029877, -0.028687, -0.028381, -0.027588, -0.028503, -0.029114, -0.02832, -0.025635, -0.025085, -0.024231, -0.022339, -0.022858, -0.021942, -0.020447, -0.020386, -0.02066, -0.018402, -0.018372, -0.018219, -0.017731, -0.017395, -0.016602, -0.016296, -0.013702, -0.011688, -0.013153, -0.013123, -0.012512, -0.012787, -0.012634, -0.011993, -0.011963, -0.01062, -0.011017, -0.010803, -0.0097351, -0.0089722, -0.0089111, -0.0083923, -0.0074463, -0.0071411, -0.0071716, -0.0061951, -0.0055237, -0.0044556, -0.0046387, -0.0054016, -0.0055847, -0.0041809, -0.0029297, -0.0036011, -0.0030823, -0.0018311, -0.0011902, -6.1035e-05, -0.00030518, -0.0015564, -0.00094604, -0.0013428, -0.0018921, 0.00061035, 0.0024414, 0.0014648, 0.0015564, 0.0018616, 0.0016479, 0.0013428, 0.0015564, 0.0027466, 0.0022583, 0.0012817, 0.0015259, 0.0019226, 0.0027161, 0.0020752, 0.0013123, 0.0024109, 0.0028076, 0.0030212, 0.0030823, 0.0047607, 0.0055847, 0.0053101, 0.0045776, 0.0049438, 0.004425, 0.0010376, 0.0007019, 0.0029297, 0.0048828, 0.0036316, 0.0038147, 0.0045776, 0.0040283, 0.0048828, 0.0049133, 0.0067444, 0.0082092, 0.0082703, 0.010193, 0.010712, 0.012054, 0.013947, 0.015137, 0.017639, 0.019623, 0.02124, 0.023193, 0.026093, 0.028076, 0.028931, 0.030914, 0.031769, 0.032043, 0.032654, 0.033417, 0.034149, 0.034393, 0.034607, 0.034058, 0.035187, 0.035828, 0.037506, 0.039093, 0.040253, 0.042145, 0.043671, 0.045227, 0.045959, 0.047882, 0.04892, 0.049591, 0.052002, 0.055756, 0.058777, 0.059937, 0.061096, 0.062927, 0.065491, 0.066772, 0.068268, 0.068604, 0.068634, 0.07016, 0.071167, 0.070251, 0.069885, 0.071136, 0.07135, 0.072266, 0.073151, 0.073334, 0.073425, 0.071747, 0.070984, 0.07193, 0.071533, 0.071167, 0.072021, 0.071503, 0.068817, 0.068359, 0.069733, 0.068085, 0.066589, 0.066071, 0.066956, 0.066101, 0.063812, 0.063385, 0.062012, 0.060638, 0.058289, 0.055267, 0.054596, 0.053864, 0.052582, 0.051025, 0.05014, 0.04953, 0.047638, 0.045624, 0.044189, 0.042297, 0.039856, 0.038788, 0.038391, 0.036591, 0.034302, 0.033295, 0.031647, 0.029755, 0.028168, 0.02475, 0.022064, 0.020294, 0.019135, 0.016174, 0.012665, 0.011322, 0.0095215, 0.0067139, 0.0045471, 0.0029907, 0.00082397, -0.0013428, -0.0024109, -0.0053101, -0.0079956, -0.0096436, -0.01178, -0.013489, -0.015472, -0.018616, -0.020874, -0.023468, -0.02533, -0.027496, -0.029907, -0.032318, -0.034546, -0.036865, -0.039093, -0.040558, -0.042206, -0.04541, -0.047058, -0.048401, -0.051819, -0.054321, -0.055756, -0.057037, -0.059296, -0.061798, -0.063446, -0.065002, -0.067261, -0.069366, -0.070251, -0.071686, -0.073792, -0.075745, -0.076263, -0.078278, -0.080475, -0.081329, -0.081665, -0.083313, -0.08548, -0.086395, -0.088562, -0.090332, -0.091766, -0.092163, -0.093475, -0.095306, -0.096436, -0.09726, -0.096588, -0.097717, -0.098785, -0.099579, -0.10199, -0.10263, -0.10388, -0.10428, -0.10468, -0.1051, -0.10605, -0.10608, -0.10651, -0.10818, -0.10764, -0.10779, -0.10791, -0.10818, -0.10815, -0.10928, -0.10934, -0.10931, -0.11139, -0.10992, -0.10907, -0.11057, -0.10889, -0.11023, -0.11087, -0.10855, -0.1091, -0.1098, -0.10883, -0.10818, -0.10812, -0.10806, -0.10699, -0.10785, -0.10791, -0.10602, -0.10562, -0.10541, -0.10449, -0.10373, -0.10345, -0.10266, -0.10187, -0.10178, -0.10056, -0.10068, -0.099396, -0.098297, -0.097961, -0.096375, -0.096649, -0.095795, -0.094055, -0.094025, -0.092468, -0.092682, -0.091919, -0.08963, -0.088043, -0.086761, -0.08609, -0.084045, -0.082764, -0.082001, -0.081207, -0.081116, -0.078522, -0.077118, -0.077515, -0.075958, -0.074158, -0.07254, -0.070892, -0.070923, -0.069794, -0.067902, -0.067108, -0.066589, -0.066132, -0.066406, -0.066895, -0.065613, -0.065613, -0.065521, -0.065613, -0.06604, -0.065338, -0.064392, -0.063324, -0.062714, -0.063263, -0.062653, -0.061432, -0.060486, -0.059357, -0.057953, -0.056854, -0.056274, -0.056061, -0.055542, -0.054352, -0.05249, -0.051025, -0.049652, -0.048828, -0.047974, -0.046722, -0.046234, -0.044769, -0.043427, -0.041962, -0.042023, -0.040619, -0.037781, -0.037262, -0.036163, -0.03476, -0.03363, -0.032104, -0.031281, -0.0289, -0.02652, -0.026123, -0.024719, -0.023132, -0.023041, -0.021667, -0.018921, -0.018738, -0.01767, -0.016205, -0.014496, -0.012421, -0.011261, -0.0098267, -0.0087585, -0.0072937, -0.005249, -0.004425, -0.0028992, -0.00039673, 0.00085449, 0.0021667, 0.0040588, 0.003479, 0.006012, 0.0083923, 0.0086975, 0.010529, 0.011627, 0.013214, 0.015778, 0.017151, 0.018219, 0.019562, 0.02066, 0.0224, 0.023621, 0.024994, 0.027679, 0.028015, 0.030762, 0.031921, 0.032104, 0.034943, 0.036652, 0.037323, 0.038879, 0.040985, 0.042236, 0.042603, 0.044556, 0.046295, 0.047852, 0.048981, 0.049683, 0.051575, 0.053467, 0.054291, 0.055908, 0.05777, 0.058105, 0.060059, 0.062042, 0.062225, 0.064117, 0.065247, 0.064636, 0.067505, 0.069641, 0.069794, 0.071808, 0.073303, 0.073486, 0.074219, 0.075684, 0.077454, 0.078247, 0.079102, 0.080688, 0.081268, 0.08197, 0.083191, 0.084137, 0.082764, 0.084656, 0.086945, 0.087555, 0.08905, 0.088715, 0.089325, 0.092133, 0.092682, 0.094177, 0.093689, 0.093903, 0.096863, 0.098083, 0.09848, 0.097839, 0.098755, 0.099487, 0.098999, 0.10013, 0.10031, 0.10095, 0.10153, 0.10245, 0.10449, 0.10391, 0.10379, 0.10464, 0.10391, 0.1051, 0.10434, 0.10483, 0.10681, 0.10614, 0.10635, 0.10785, 0.10611, 0.10648, 0.10797, 0.10745, 0.10773, 0.10849, 0.10742, 0.10825, 0.10892, 0.10809, 0.11011, 0.10889, 0.10843, 0.10977, 0.10785, 0.10971, 0.1091, 0.10828, 0.10883, 0.10739, 0.10925, 0.10855, 0.108, 0.10849, 0.1073, 0.10727, 0.108, 0.10825, 0.10675, 0.10645, 0.10611, 0.10611, 0.10645, 0.1069, 0.10535, 0.10513, 0.10629, 0.10498, 0.10361, 0.10431, 0.10345, 0.10391, 0.1037, 0.10138, 0.10034, 0.10114, 0.10034, 0.099731, 0.099915, 0.10138, 0.10388, 0.10309, 0.10327, 0.10562, 0.10501, 0.10538, 0.10504, 0.10327, 0.10193, 0.10101, 0.10019, 0.098541, 0.097015, 0.096741, 0.097412, 0.095856, 0.093719, 0.092987, 0.093536, 0.092651, 0.090973, 0.090607, 0.088867, 0.087738, 0.087189, 0.086609, 0.085205, 0.084412, 0.082336, 0.082306, 0.081421, 0.079926, 0.080231, 0.078827, 0.076904, 0.076385, 0.075806, 0.073822, 0.074188, 0.072205, 0.071106, 0.070923, 0.068695, 0.06839, 0.066437, 0.065704, 0.064209, 0.060883, 0.057861, 0.054565, 0.052673, 0.05014, 0.048065, 0.046448, 0.043793, 0.041382, 0.039154, 0.036987, 0.034912, 0.033539, 0.030121, 0.026031, 0.022949, 0.018707, 0.015442, 0.01355, 0.0094299, 0.0065918, 0.0042419, 0.00082397, -0.00054932, -0.0040283, -0.0057373, -0.0078735, -0.010895, -0.01181, -0.01358, -0.01593, -0.018799, -0.020905, -0.021576, -0.022278, -0.022766, -0.024933, -0.027313, -0.027557, -0.03006, -0.030945, -0.032074, -0.034271, -0.034851, -0.036255, -0.038696, -0.03949, -0.039856, -0.041443, -0.041809, -0.042877, -0.043579, -0.043427, -0.045593, -0.04538, -0.045013, -0.047852, -0.048004, -0.049225, -0.050293, -0.048218, -0.048706, -0.049194, -0.049988, -0.052368, -0.050995, -0.051819, -0.051971, -0.050812, -0.051971, -0.052185, -0.051575, -0.050842, -0.052429, -0.051392, -0.050537, -0.051056, -0.050568, -0.050964, -0.050446, -0.049164, -0.049072, -0.049438, -0.048798, -0.048309, -0.048157, -0.048462, -0.046509, -0.046753, -0.046265, -0.04422, -0.045135, -0.043213, -0.04248, -0.043549, -0.041443, -0.039093, -0.039856, -0.039276, -0.037659, -0.036469, -0.034607, -0.035431, -0.034485, -0.032928, -0.032867, -0.031158, -0.030212, -0.029327, -0.027893, -0.0271, -0.025269, -0.024048, -0.024597, -0.022736, -0.021393, -0.020844, -0.019775, -0.018982, -0.017029, -0.016663, -0.015961, -0.015289, -0.014526, -0.012146, -0.011658, -0.011108, -0.0084534, -0.0082092, -0.0079651, -0.006012, -0.0049133, -0.0043945, -0.0046692, -0.00354, -0.0016479, 0.00033569, 0.0018921, 0.0016479, 0.002594, 0.0039978, 0.0029602, 0.0047913, 0.0067444, 0.0061035, 0.0078125, 0.0082703, 0.0077515, 0.01001, 0.010162, 0.011658, 0.012482, 0.01236, 0.013336, 0.014374, 0.015137, 0.015442, 0.015564, 0.0177, 0.018494, 0.017914, 0.017242, 0.01828, 0.019043, 0.019043, 0.020203, 0.020477, 0.020905, 0.020599, 0.021057, 0.022583, 0.0224, 0.02298, 0.024017, 0.023743, 0.023376, 0.023071, 0.024231, 0.023987, 0.023895, 0.02356, 0.024811, 0.024658, 0.023163, 0.024567, 0.024872, 0.022797, 0.022949, 0.024445, 0.023041, 0.02121, 0.021393, 0.019531, 0.016388, 0.016663, 0.015137, 0.012268, 0.011414, 0.0098572, 0.0092468, 0.0080566, 0.0061646, 0.0057373, 0.0042725, 0.0044556, 0.0042419, 0.0021057, 0.0014954, 0.0010681, 0.0005188, -9.1553e-05, -0.00048828, -0.0016785, -0.0027466, -0.0034485, -0.0049133, -0.0047302, -0.0044556, -0.0058289, -0.0059509, -0.007782, -0.007843, -0.0082397, -0.01004, -0.010559, -0.010895, -0.011139, -0.01181, -0.012268, -0.011871, -0.013702, -0.015045, -0.01474, -0.014465, -0.013977, -0.014954, -0.01712, -0.017426, -0.017151, -0.01767, -0.017517, -0.019135, -0.020203, -0.019012, -0.020111, -0.021576, -0.020966, -0.02121, -0.021942, -0.021973, -0.022339, -0.022644, -0.023102, -0.023468, -0.023376, -0.022766, -0.023224, -0.024628, -0.024231, -0.024811, -0.0242, -0.024445, -0.025879, -0.025391, -0.024872, -0.025146, -0.024414, -0.024445, -0.024109, -0.023682, -0.024719, -0.024261, -0.023895, -0.0242, -0.023071, -0.023651, -0.023102, -0.023804, -0.024841, -0.024017, -0.024292, -0.023773, -0.023621, -0.023254, -0.022888, -0.023529, -0.022247, -0.020905, -0.020447, -0.019653, -0.021545, -0.022125, -0.019928, -0.019867, -0.020905, -0.020233, -0.019379, -0.0177, -0.018738, -0.018433, -0.01767, -0.019073, -0.017914, -0.01712, -0.017487, -0.017853, -0.015686, -0.01535, -0.015839, -0.015015, -0.01474, -0.013611, -0.013641, -0.013428, -0.012573, -0.011353, -0.011414, -0.011536, -0.011536, -0.010651, -0.011108, -0.011566, -0.010406, -0.010223, -0.0092163, -0.008728, -0.0094604, -0.0085449, -0.0080566, -0.008606, -0.0081787, -0.0058899, -0.0056763, -0.0065918, -0.0081177, -0.0084229, -0.0050354, -0.0049438, -0.0061035, -0.0050354, -0.0057983, -0.0050964, -0.0036621, -0.0050964, -0.0041809, -0.0035706, -0.0024719, -0.0023499, -0.0029297, -0.0012207, -0.00177, -0.0018616, -0.0018311, -0.0016174, -0.0028076, -0.0033264, -0.0017395, -0.00088501, -0.0018005, -0.0022278, -0.0028076, -0.0011292, -0.00057983, -0.00054932, -9.1553e-05, -0.0016479, -0.00076294, -0.00088501, -0.00079346, -0.0005188, -0.00076294, 0.0014954, 0.00354, 0.004364, 0.0039063, 0.0058289, 0.0075073, 0.0081787, 0.0096741, 0.010132, 0.010712, 0.0112, 0.011932, 0.012634, 0.013184, 0.013977, 0.014038, 0.014221, 0.014343, 0.014221, 0.014923, 0.015747, 0.014557, 0.014771, 0.015259, 0.014923, 0.015533, 0.014496, 0.014557, 0.015961, 0.016846, 0.018158, 0.020294, 0.020416, 0.021606, 0.023132, 0.024536, 0.024811, 0.025055, 0.024902, 0.024658, 0.025696, 0.025909, 0.027985, 0.027313, 0.026917, 0.028442, 0.026611, 0.026093, 0.02536, 0.025787, 0.026672, 0.025116, 0.027771, 0.028503, 0.028137, 0.030396, 0.032074, 0.032471, 0.032745, 0.03418, 0.033966, 0.03363, 0.034943, 0.035278, 0.034943, 0.034058, 0.033844, 0.034943, 0.034698, 0.035309, 0.036041, 0.037048, 0.037048, 0.039124, 0.041382, 0.040649, 0.041168, 0.042755, 0.042328, 0.04184, 0.042572, 0.042786, 0.042297, 0.043915, 0.043762, 0.041565, 0.040314, 0.039948, 0.039246, 0.039154, 0.039734, 0.039185, 0.03772, 0.03714, 0.036652, 0.034607, 0.034729, 0.033997, 0.032349, 0.031891, 0.031219, 0.030029, 0.029266, 0.027985, 0.026276, 0.026459, 0.024048, 0.022095, 0.02124, 0.019592, 0.018707, 0.018066, 0.016144, 0.013855, 0.013702, 0.011871, 0.010132, 0.0090027, 0.0054626, 0.0048218, 0.0042725, 0.00097656, -0.00064087, -0.00088501, -0.0022278, -0.0045776, -0.0063477, -0.0081177, -0.010376, -0.010742, -0.012543, -0.014374, -0.015167, -0.018311, -0.019775, -0.022552, -0.024353, -0.026123, -0.028259, -0.029633, -0.032257, -0.032867, -0.036133, -0.03775, -0.038544, -0.04129, -0.042023, -0.044739, -0.046204, -0.04895, -0.051331, -0.051331, -0.054291, -0.056244, -0.05722, -0.060577, -0.062866, -0.064606, -0.06662, -0.067505, -0.069366, -0.070709, -0.073242, -0.074768, -0.076141, -0.07782, -0.078613, -0.081116, -0.082214, -0.082947, -0.085693, -0.087189, -0.089203, -0.090118, -0.091614, -0.093414, -0.095276, -0.096436, -0.097198, -0.098541, -0.10123, -0.10251, -0.10187, -0.10471, -0.10605, -0.1062, -0.10715, -0.10855, -0.10928, -0.11014, -0.11145, -0.11142, -0.11316, -0.11493, -0.11487, -0.11566, -0.11688, -0.11761, -0.11795, -0.11975, -0.12, -0.12051, -0.12097, -0.12125, -0.12152, -0.12265, -0.12372, -0.12289, -0.1221, -0.12338, -0.12427, -0.12363, -0.12448, -0.12527, -0.12494, -0.12567, -0.12537, -0.12589, -0.12463, -0.12509, -0.12604, -0.1243, -0.12341, -0.12442, -0.12567, -0.12482, -0.1246, -0.12393, -0.12292, -0.12372, -0.12228, -0.12112, -0.12219, -0.12183, -0.1218, -0.12134, -0.11926, -0.1199, -0.11911, -0.11765, -0.11777, -0.11752, -0.11636, -0.11533, -0.11359, -0.11295, -0.11313, -0.11234, -0.11169, -0.11035, -0.10797, -0.10794, -0.10785, -0.10687, -0.1055, -0.10342, -0.10236, -0.10245, -0.10104, -0.099854, -0.098877, -0.097046, -0.096497, -0.0961, -0.094025, -0.093475, -0.093628, -0.092529, -0.093506, -0.092712, -0.092255, -0.092773, -0.09201, -0.092621, -0.090912, -0.089447, -0.09024, -0.089355, -0.088531, -0.088135, -0.08728, -0.086639, -0.08609, -0.084351, -0.083557, -0.081573, -0.080048, -0.078583, -0.07605, -0.075562, -0.074707, -0.073944, -0.073242, -0.071716, -0.07074, -0.068756, -0.067841, -0.0672, -0.065155, -0.064941, -0.062714, -0.060913, -0.059174, -0.058716, -0.059052, -0.056335, -0.054901, -0.052856, -0.050812, -0.050171, -0.047913, -0.047119, -0.044617, -0.041962, -0.041046, -0.039764, -0.038147, -0.036865, -0.035889, -0.033691, -0.032715, -0.031281, -0.029327, -0.027924, -0.025482, -0.023956, -0.022247, -0.02121, -0.019958, -0.017944, -0.016785, -0.014221, -0.012482, -0.010895, -0.0085754, -0.0087891, -0.0077209, -0.0039978, -0.0027161, -0.0014038, 0.00042725, 0.0031433, 0.0047302, 0.005127, 0.0076904, 0.0096436, 0.010559, 0.012817, 0.014404, 0.015778, 0.017059, 0.018982, 0.02066, 0.022583, 0.024109, 0.024536, 0.027832, 0.02951, 0.029633, 0.032623, 0.033783, 0.033661, 0.037842, 0.039185, 0.040283, 0.043304, 0.044556, 0.046082, 0.046875, 0.048004, 0.049103, 0.051422, 0.054352, 0.055176, 0.055786, 0.057373, 0.058868, 0.060089, 0.060394, 0.061157, 0.06366, 0.06488, 0.066376, 0.068665, 0.070435, 0.070251, 0.071289, 0.073425, 0.073853, 0.075592, 0.076019, 0.076874, 0.078613, 0.080475, 0.082275, 0.082794, 0.084076, 0.085938, 0.086975, 0.087067, 0.088867, 0.09082, 0.089722, 0.091064, 0.093109, 0.092987, 0.094818, 0.095184, 0.095428, 0.097107, 0.096466, 0.098846, 0.099243, 0.099182, 0.10031, 0.10065, 0.10214, 0.10172, 0.10266, 0.10455, 0.10489, 0.1055, 0.10663, 0.10776, 0.10687, 0.10703, 0.10773, 0.10806, 0.11014, 0.11078, 0.11047, 0.11151, 0.1106, 0.10968, 0.11038, 0.11154, 0.11108, 0.11145, 0.11203, 0.11224, 0.11282, 0.1123, 0.11334, 0.11395, 0.11298, 0.11423, 0.11288, 0.11276, 0.11365, 0.11441, 0.11356, 0.11383, 0.11404, 0.11356, 0.11276, 0.11246, 0.11328, 0.11356, 0.11304, 0.11301, 0.11386, 0.11288, 0.11121, 0.11115, 0.10867, 0.10626, 0.10596, 0.10428, 0.10419, 0.10321, 0.10269, 0.10278, 0.10001, 0.10013, 0.098175, 0.097229, 0.097412, 0.097382, 0.09787, 0.098633, 0.099731, 0.09903, 0.098389, 0.10001, 0.099823, 0.10043, 0.1001, 0.098602, 0.099365, 0.098389, 0.097809, 0.098297, 0.098297, 0.099701, 0.098053, 0.097076, 0.097137, 0.096893, 0.096741, 0.096161, 0.095795, 0.095245, 0.095154, 0.094208, 0.094116, 0.093475, 0.092621, 0.092163, 0.089935, 0.089172, 0.089386, 0.088104, 0.087646, 0.0867, 0.0867, 0.086365, 0.084381, 0.085144, 0.084564, 0.083099, 0.082947, 0.083221, 0.083435, 0.081543, 0.08017, 0.079681, 0.078766, 0.078064, 0.076538, 0.074127, 0.071228, 0.068848, 0.066589, 0.064178, 0.062714, 0.062164, 0.058868, 0.05719, 0.056061, 0.052979, 0.052063, 0.050354, 0.049561, 0.047424, 0.04538, 0.044952, 0.042847, 0.041748, 0.040222, 0.039307, 0.039032, 0.035828, 0.032593, 0.029358, 0.026306, 0.024567, 0.022247, 0.019104, 0.01535, 0.013306, 0.012634, 0.011536, 0.0093079, 0.007019, 0.005249, 0.0040588, 0.0019836, 0.0005188, -0.0013733, -0.0030212, -0.0044556, -0.0060425, -0.0075684, -0.0093079, -0.0098877, -0.010132, -0.013489, -0.015015, -0.014679, -0.016785, -0.018921, -0.019043, -0.020996, -0.021759, -0.020905, -0.023224, -0.024292, -0.024231, -0.0271, -0.027435, -0.027832, -0.029144, -0.030304, -0.030884, -0.029602, -0.031067, -0.031189, -0.031952, -0.032776, -0.031433, -0.033356, -0.033569, -0.034271, -0.035645, -0.034668, -0.03598, -0.03656, -0.037506, -0.038635, -0.037415, -0.037598, -0.037201, -0.037781, -0.037903, -0.037781, -0.037231, -0.036865, -0.037933, -0.037811, -0.037354, -0.037689, -0.037628, -0.037537, -0.036346, -0.035095, -0.035645, -0.035339, -0.03418, -0.035004, -0.035034, -0.03363, -0.034607, -0.033203, -0.031982, -0.032562, -0.031769, -0.031677, -0.030212, -0.030304, -0.031219, -0.029022, -0.028656, -0.027832, -0.027069, -0.026917, -0.025787, -0.024689, -0.024231, -0.022675, -0.022827, -0.021942, -0.022034, -0.021301, -0.019165, -0.018829, -0.017914, -0.01825, -0.017181, -0.015381, -0.015503, -0.015106, -0.014038, -0.013153, -0.010254, -0.010162, -0.011627, -0.0098267, -0.010162, -0.0078125, -0.007019, -0.0068359, -0.0053406, -0.0048218, -0.0030518, -0.0027771, -0.0021057, -0.00021362, -0.00021362, -0.00076294, 0.0019531, 0.0019836, 0.00177, 0.0033264, 0.0032959, 0.0048828, 0.0069885, 0.0080872, 0.0078735, 0.0075073, 0.0090637, 0.010071, 0.010406, 0.010284, 0.01004, 0.011414, 0.011902, 0.011597, 0.012024, 0.012878, 0.014832, 0.014099, 0.015106, 0.015961, 0.016235, 0.018463, 0.017578, 0.016479, 0.018707, 0.019104, 0.018555, 0.01944, 0.020935, 0.022003, 0.021149, 0.019806, 0.018921, 0.016998, 0.015839, 0.014954, 0.014313, 0.014221, 0.012177, 0.012207, 0.011841, 0.010834, 0.011292, 0.0094604, 0.0093384, 0.0097351, 0.0076599, 0.0083313, 0.0083008, 0.0072327, 0.0069275, 0.0072632, 0.0068054, 0.0062561, 0.005188, 0.0052795, 0.006012, 0.0059814, 0.006134, 0.0043335, 0.0035706, 0.003418, 0.0029297, 0.0039978, 0.0027771, 0.0015564, 0.00048828, 0.00045776, 0.0013733, -0.0011902, -0.0018616, -0.0013428, -0.0014954, -0.00097656, -0.0016785, -0.00082397, -0.0015869, -0.0014648, -0.00177, -0.0037537, -0.0044556, -0.0042419, -0.0046692, -0.0052185, -0.0059509, -0.0070496, -0.0059814, -0.0058289, -0.0074768, -0.0075378, -0.0072632, -0.0072327, -0.0076294, -0.008606, -0.0090027, -0.0092163, -0.0088806, -0.0094604, -0.0097961, -0.01001, -0.0095215, -0.0089111, -0.010223, -0.011047, -0.010406, -0.011505, -0.012817, -0.011597, -0.010895, -0.010986, -0.012207, -0.012543, -0.013794, -0.012451, -0.012268, -0.013977, -0.01239, -0.012695, -0.01355, -0.012299, -0.012543, -0.013824, -0.013702, -0.012573, -0.012665, -0.013702, -0.013214, -0.011993, -0.012665, -0.012878, -0.010956, -0.011414, -0.011963, -0.013214, -0.012726, -0.012024, -0.014191, -0.013245, -0.012909, -0.012573, -0.012238, -0.012726, -0.010284, -0.011169, -0.011536, -0.010956, -0.011902, -0.0112, -0.010651, -0.010254, -0.0099792, -0.010559, -0.0096741, -0.0099182, -0.010925, -0.0112, -0.010834, -0.010406, -0.012451, -0.0098877, -0.0094604, -0.010101, -0.0081482, -0.0074768, -0.0076599, -0.0090637]) 13 | 14 | 15 | def plot_peaks(x, indexes, algorithm=None, mph=None, mpd=None): 16 | """Plot results of the peak dectection.""" 17 | try: 18 | import matplotlib.pyplot as plt 19 | except ImportError: 20 | print('matplotlib is not available.') 21 | return 22 | _, ax = plt.subplots(1, 1, figsize=(8, 4)) 23 | ax.plot(x, 'b', lw=1) 24 | if indexes.size: 25 | label = 'peak' 26 | label = label + 's' if indexes.size > 1 else label 27 | ax.plot(indexes, x[indexes], '+', mfc=None, mec='r', mew=2, ms=8, 28 | label='%d %s' % (indexes.size, label)) 29 | ax.legend(loc='best', framealpha=.5, numpoints=1) 30 | ax.set_xlim(-.02*x.size, x.size*1.02-1) 31 | ymin, ymax = x[np.isfinite(x)].min(), x[np.isfinite(x)].max() 32 | yrange = ymax - ymin if ymax > ymin else 1 33 | ax.set_ylim(ymin - 0.1*yrange, ymax + 0.1*yrange) 34 | ax.set_xlabel('Data #', fontsize=14) 35 | ax.set_ylabel('Amplitude', fontsize=14) 36 | ax.set_title('%s (mph=%s, mpd=%s)' % (algorithm, mph, mpd)) 37 | plt.show() 38 | -------------------------------------------------------------------------------- /tests/libs/peakdetect.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from math import pi, log 3 | import pylab 4 | from scipy import fft, ifft 5 | from scipy.optimize import curve_fit 6 | 7 | i = 10000 8 | x = np.linspace(0, 3.5 * pi, i) 9 | y = (0.3*np.sin(x) + np.sin(1.3 * x) + 0.9 * np.sin(4.2 * x) + 0.06 * 10 | np.random.randn(i)) 11 | 12 | 13 | def _datacheck_peakdetect(x_axis, y_axis): 14 | if x_axis is None: 15 | x_axis = range(len(y_axis)) 16 | 17 | if len(y_axis) != len(x_axis): 18 | raise (ValueError, 19 | 'Input vectors y_axis and x_axis must have same length') 20 | 21 | #needs to be a numpy array 22 | y_axis = np.array(y_axis) 23 | x_axis = np.array(x_axis) 24 | return x_axis, y_axis 25 | 26 | def _peakdetect_parabole_fitter(raw_peaks, x_axis, y_axis, points): 27 | """ 28 | Performs the actual parabole fitting for the peakdetect_parabole function. 29 | 30 | keyword arguments: 31 | raw_peaks -- A list of either the maximium or the minimum peaks, as given 32 | by the peakdetect_zero_crossing function, with index used as x-axis 33 | x_axis -- A numpy list of all the x values 34 | y_axis -- A numpy list of all the y values 35 | points -- How many points around the peak should be used during curve 36 | fitting, must be odd. 37 | 38 | return -- A list giving all the peaks and the fitted waveform, format: 39 | [[x, y, [fitted_x, fitted_y]]] 40 | 41 | """ 42 | func = lambda x, k, tau, m: k * ((x - tau) ** 2) + m 43 | fitted_peaks = [] 44 | for peak in raw_peaks: 45 | index = peak[0] 46 | x_data = x_axis[index - points // 2: index + points // 2 + 1] 47 | y_data = y_axis[index - points // 2: index + points // 2 + 1] 48 | # get a first approximation of tau (peak position in time) 49 | tau = x_axis[index] 50 | # get a first approximation of peak amplitude 51 | m = peak[1] 52 | 53 | # build list of approximations 54 | # k = -m as first approximation? 55 | p0 = (-m, tau, m) 56 | popt, pcov = curve_fit(func, x_data, y_data, p0) 57 | # retrieve tau and m i.e x and y value of peak 58 | x, y = popt[1:3] 59 | 60 | # create a high resolution data set for the fitted waveform 61 | x2 = np.linspace(x_data[0], x_data[-1], points * 10) 62 | y2 = func(x2, *popt) 63 | 64 | fitted_peaks.append([x, y, [x2, y2]]) 65 | 66 | return fitted_peaks 67 | 68 | 69 | def peakdetect(y_axis, x_axis = None, lookahead = 300, delta=0): 70 | """ 71 | Converted from/based on a MATLAB script at: 72 | http://billauer.co.il/peakdet.html 73 | 74 | function for detecting local maximas and minmias in a signal. 75 | Discovers peaks by searching for values which are surrounded by lower 76 | or larger values for maximas and minimas respectively 77 | 78 | keyword arguments: 79 | y_axis -- A list containg the signal over which to find peaks 80 | x_axis -- (optional) A x-axis whose values correspond to the y_axis list 81 | and is used in the return to specify the postion of the peaks. If 82 | omitted an index of the y_axis is used. (default: None) 83 | lookahead -- (optional) distance to look ahead from a peak candidate to 84 | determine if it is the actual peak (default: 200) 85 | '(sample / period) / f' where '4 >= f >= 1.25' might be a good value 86 | delta -- (optional) this specifies a minimum difference between a peak and 87 | the following points, before a peak may be considered a peak. Useful 88 | to hinder the function from picking up false peaks towards to end of 89 | the signal. To work well delta should be set to delta >= RMSnoise * 5. 90 | (default: 0) 91 | delta function causes a 20% decrease in speed, when omitted 92 | Correctly used it can double the speed of the function 93 | 94 | return -- two lists [max_peaks, min_peaks] containing the positive and 95 | negative peaks respectively. Each cell of the lists contains a tupple 96 | of: (position, peak_value) 97 | to get the average peak value do: np.mean(max_peaks, 0)[1] on the 98 | results to unpack one of the lists into x, y coordinates do: 99 | x, y = zip(*tab) 100 | """ 101 | max_peaks = [] 102 | min_peaks = [] 103 | dump = [] #Used to pop the first hit which almost always is false 104 | 105 | # check input data 106 | x_axis, y_axis = _datacheck_peakdetect(x_axis, y_axis) 107 | # store data length for later use 108 | length = len(y_axis) 109 | 110 | 111 | #perform some checks 112 | if lookahead < 1: 113 | raise ValueError, "Lookahead must be '1' or above in value" 114 | if not (np.isscalar(delta) and delta >= 0): 115 | raise ValueError, "delta must be a positive number" 116 | 117 | #maxima and minima candidates are temporarily stored in 118 | #mx and mn respectively 119 | mn, mx = np.Inf, -np.Inf 120 | 121 | #Only detect peak if there is 'lookahead' amount of points after it 122 | for index, (x, y) in enumerate(zip(x_axis[:-lookahead], 123 | y_axis[:-lookahead])): 124 | if y > mx: 125 | mx = y 126 | mxpos = x 127 | if y < mn: 128 | mn = y 129 | mnpos = x 130 | 131 | ####look for max#### 132 | if y < mx-delta and mx != np.Inf: 133 | #Maxima peak candidate found 134 | #look ahead in signal to ensure that this is a peak and not jitter 135 | if y_axis[index:index+lookahead].max() < mx: 136 | max_peaks.append([mxpos, mx]) 137 | dump.append(True) 138 | #set algorithm to only find minima now 139 | mx = np.Inf 140 | mn = np.Inf 141 | if index+lookahead >= length: 142 | #end is within lookahead no more peaks can be found 143 | break 144 | continue 145 | #else: #slows shit down this does 146 | # mx = ahead 147 | # mxpos = x_axis[np.where(y_axis[index:index+lookahead]==mx)] 148 | 149 | ####look for min#### 150 | if y > mn+delta and mn != -np.Inf: 151 | #Minima peak candidate found 152 | #look ahead in signal to ensure that this is a peak and not jitter 153 | if y_axis[index:index+lookahead].min() > mn: 154 | min_peaks.append([mnpos, mn]) 155 | dump.append(False) 156 | #set algorithm to only find maxima now 157 | mn = -np.Inf 158 | mx = -np.Inf 159 | if index+lookahead >= length: 160 | #end is within lookahead no more peaks can be found 161 | break 162 | #else: #slows shit down this does 163 | # mn = ahead 164 | # mnpos = x_axis[np.where(y_axis[index:index+lookahead]==mn)] 165 | 166 | 167 | #Remove the false hit on the first value of the y_axis 168 | try: 169 | if dump[0]: 170 | max_peaks.pop(0) 171 | else: 172 | min_peaks.pop(0) 173 | del dump 174 | except IndexError: 175 | #no peaks were found, should the function return empty lists? 176 | pass 177 | 178 | return [max_peaks, min_peaks] 179 | 180 | 181 | def peakdetect_fft(y_axis, x_axis, pad_len = 5): 182 | """ 183 | Performs a FFT calculation on the data and zero-pads the results to 184 | increase the time domain resolution after performing the inverse fft and 185 | send the data to the 'peakdetect' function for peak 186 | detection. 187 | 188 | Omitting the x_axis is forbidden as it would make the resulting x_axis 189 | value silly if it was returned as the index 50.234 or similar. 190 | 191 | Will find at least 1 less peak then the 'peakdetect_zero_crossing' 192 | function, but should result in a more precise value of the peak as 193 | resolution has been increased. Some peaks are lost in an attempt to 194 | minimize spectral leakage by calculating the fft between two zero 195 | crossings for n amount of signal periods. 196 | 197 | The biggest time eater in this function is the ifft and thereafter it's 198 | the 'peakdetect' function which takes only half the time of the ifft. 199 | Speed improvementd could include to check if 2**n points could be used for 200 | fft and ifft or change the 'peakdetect' to the 'peakdetect_zero_crossing', 201 | which is maybe 10 times faster than 'peakdetct'. The pro of 'peakdetect' 202 | is that it resutls in one less lost peak. It should also be noted that the 203 | time used by the ifft function can change greatly depending on the input. 204 | 205 | keyword arguments: 206 | y_axis -- A list containg the signal over which to find peaks 207 | x_axis -- A x-axis whose values correspond to the y_axis list and is used 208 | in the return to specify the postion of the peaks. 209 | pad_len -- (optional) By how many times the time resolution should be 210 | increased by, e.g. 1 doubles the resolution. The amount is rounded up 211 | to the nearest 2 ** n amount (default: 5) 212 | 213 | return -- two lists [max_peaks, min_peaks] containing the positive and 214 | negative peaks respectively. Each cell of the lists contains a tupple 215 | of: (position, peak_value) 216 | to get the average peak value do: np.mean(max_peaks, 0)[1] on the 217 | results to unpack one of the lists into x, y coordinates do: 218 | x, y = zip(*tab) 219 | """ 220 | # check input data 221 | x_axis, y_axis = _datacheck_peakdetect(x_axis, y_axis) 222 | zero_indices = zero_crossings(y_axis, window = 11) 223 | #select a n amount of periods 224 | last_indice = - 1 - (1 - len(zero_indices) & 1) 225 | # Calculate the fft between the first and last zero crossing 226 | # this method could be ignored if the begining and the end of the signal 227 | # are discardable as any errors induced from not using whole periods 228 | # should mainly manifest in the beginning and the end of the signal, but 229 | # not in the rest of the signal 230 | fft_data = fft(y_axis[zero_indices[0]:zero_indices[last_indice]]) 231 | padd = lambda x, c: x[:len(x) // 2] + [0] * c + x[len(x) // 2:] 232 | n = lambda x: int(log(x)/log(2)) + 1 233 | # padds to 2**n amount of samples 234 | fft_padded = padd(list(fft_data), 2 ** 235 | n(len(fft_data) * pad_len) - len(fft_data)) 236 | 237 | # There is amplitude decrease directly proportional to the sample increase 238 | sf = len(fft_padded) / float(len(fft_data)) 239 | # There might be a leakage giving the result an imaginary component 240 | # Return only the real component 241 | y_axis_ifft = ifft(fft_padded).real * sf #(pad_len + 1) 242 | x_axis_ifft = np.linspace( 243 | x_axis[zero_indices[0]], x_axis[zero_indices[last_indice]], 244 | len(y_axis_ifft)) 245 | # get the peaks to the interpolated waveform 246 | max_peaks, min_peaks = peakdetect(y_axis_ifft, x_axis_ifft, 500, 247 | delta = abs(np.diff(y_axis).max() * 2)) 248 | #max_peaks, min_peaks = peakdetect_zero_crossing(y_axis_ifft, x_axis_ifft) 249 | 250 | # store one 20th of a period as waveform data 251 | data_len = int(np.diff(zero_indices).mean()) / 10 252 | data_len += 1 - data_len & 1 253 | 254 | 255 | fitted_wave = [] 256 | for peaks in [max_peaks, min_peaks]: 257 | peak_fit_tmp = [] 258 | index = 0 259 | for peak in peaks: 260 | index = np.where(x_axis_ifft[index:]==peak[0])[0][0] + index 261 | x_fit_lim = x_axis_ifft[index - data_len // 2: 262 | index + data_len // 2 + 1] 263 | y_fit_lim = y_axis_ifft[index - data_len // 2: 264 | index + data_len // 2 + 1] 265 | 266 | peak_fit_tmp.append([x_fit_lim, y_fit_lim]) 267 | fitted_wave.append(peak_fit_tmp) 268 | 269 | #pylab.plot(range(len(fft_data)), fft_data) 270 | #pylab.show() 271 | 272 | pylab.plot(x_axis, y_axis) 273 | pylab.hold(True) 274 | pylab.plot(x_axis_ifft, y_axis_ifft) 275 | #for max_p in max_peaks: 276 | # pylab.plot(max_p[0], max_p[1], 'xr') 277 | pylab.show() 278 | return [max_peaks, min_peaks] 279 | 280 | 281 | def peakdetect_parabole(y_axis, x_axis, points = 9): 282 | """ 283 | Function for detecting local maximas and minmias in a signal. 284 | Discovers peaks by fitting the model function: y = k (x - tau) ** 2 + m 285 | to the peaks. The amount of points used in the fitting is set by the 286 | points argument. 287 | 288 | Omitting the x_axis is forbidden as it would make the resulting x_axis 289 | value silly if it was returned as index 50.234 or similar. 290 | 291 | will find the same amount of peaks as the 'peakdetect_zero_crossing' 292 | function, but might result in a more precise value of the peak. 293 | 294 | keyword arguments: 295 | y_axis -- A list containg the signal over which to find peaks 296 | x_axis -- A x-axis whose values correspond to the y_axis list and is used 297 | in the return to specify the postion of the peaks. 298 | points -- (optional) How many points around the peak should be used during 299 | curve fitting, must be odd (default: 9) 300 | 301 | return -- two lists [max_peaks, min_peaks] containing the positive and 302 | negative peaks respectively. Each cell of the lists contains a list 303 | of: (position, peak_value) 304 | to get the average peak value do: np.mean(max_peaks, 0)[1] on the 305 | results to unpack one of the lists into x, y coordinates do: 306 | x, y = zip(*max_peaks) 307 | """ 308 | # check input data 309 | x_axis, y_axis = _datacheck_peakdetect(x_axis, y_axis) 310 | # make the points argument odd 311 | points += 1 - points % 2 312 | #points += 1 - int(points) & 1 slower when int conversion needed 313 | 314 | # get raw peaks 315 | max_raw, min_raw = peakdetect_zero_crossing(y_axis) 316 | 317 | # define output variable 318 | max_peaks = [] 319 | min_peaks = [] 320 | 321 | max_ = _peakdetect_parabole_fitter(max_raw, x_axis, y_axis, points) 322 | min_ = _peakdetect_parabole_fitter(min_raw, x_axis, y_axis, points) 323 | 324 | max_peaks = map(lambda x: [x[0], x[1]], max_) 325 | max_fitted = map(lambda x: x[-1], max_) 326 | min_peaks = map(lambda x: [x[0], x[1]], min_) 327 | min_fitted = map(lambda x: x[-1], min_) 328 | 329 | 330 | #pylab.plot(x_axis, y_axis) 331 | #pylab.hold(True) 332 | #for max_p, max_f in zip(max_peaks, max_fitted): 333 | # pylab.plot(max_p[0], max_p[1], 'x') 334 | # pylab.plot(max_f[0], max_f[1], 'o', markersize = 2) 335 | #for min_p, min_f in zip(min_peaks, min_fitted): 336 | # pylab.plot(min_p[0], min_p[1], 'x') 337 | # pylab.plot(min_f[0], min_f[1], 'o', markersize = 2) 338 | #pylab.show() 339 | 340 | return [max_peaks, min_peaks] 341 | 342 | 343 | def peakdetect_sine(y_axis, x_axis, points = 9, lock_frequency = False): 344 | """ 345 | Function for detecting local maximas and minmias in a signal. 346 | Discovers peaks by fitting the model function: 347 | y = A * sin(2 * pi * f * x - tau) to the peaks. The amount of points used 348 | in the fitting is set by the points argument. 349 | 350 | Omitting the x_axis is forbidden as it would make the resulting x_axis 351 | value silly if it was returned as index 50.234 or similar. 352 | 353 | will find the same amount of peaks as the 'peakdetect_zero_crossing' 354 | function, but might result in a more precise value of the peak. 355 | 356 | The function might have some problems if the sine wave has a 357 | non-negligible total angle i.e. a k*x component, as this messes with the 358 | internal offset calculation of the peaks, might be fixed by fitting a 359 | k * x + m function to the peaks for offset calculation. 360 | 361 | keyword arguments: 362 | y_axis -- A list containg the signal over which to find peaks 363 | x_axis -- A x-axis whose values correspond to the y_axis list and is used 364 | in the return to specify the postion of the peaks. 365 | points -- (optional) How many points around the peak should be used during 366 | curve fitting, must be odd (default: 9) 367 | lock_frequency -- (optional) Specifies if the frequency argument of the 368 | model function should be locked to the value calculated from the raw 369 | peaks or if optimization process may tinker with it. (default: False) 370 | 371 | return -- two lists [max_peaks, min_peaks] containing the positive and 372 | negative peaks respectively. Each cell of the lists contains a tupple 373 | of: (position, peak_value) 374 | to get the average peak value do: np.mean(max_peaks, 0)[1] on the 375 | results to unpack one of the lists into x, y coordinates do: 376 | x, y = zip(*tab) 377 | """ 378 | # check input data 379 | x_axis, y_axis = _datacheck_peakdetect(x_axis, y_axis) 380 | # make the points argument odd 381 | points += 1 - points % 2 382 | #points += 1 - int(points) & 1 slower when int conversion needed 383 | 384 | # get raw peaks 385 | max_raw, min_raw = peakdetect_zero_crossing(y_axis) 386 | 387 | # define output variable 388 | max_peaks = [] 389 | min_peaks = [] 390 | 391 | # get global offset 392 | offset = np.mean([np.mean(max_raw, 0)[1], np.mean(min_raw, 0)[1]]) 393 | # fitting a k * x + m function to the peaks might be better 394 | #offset_func = lambda x, k, m: k * x + m 395 | 396 | # calculate an approximate frequenzy of the signal 397 | Hz = [] 398 | for raw in [max_raw, min_raw]: 399 | if len(raw) > 1: 400 | peak_pos = [x_axis[index] for index in zip(*raw)[0]] 401 | Hz.append(np.mean(np.diff(peak_pos))) 402 | Hz = 1 / np.mean(Hz) 403 | 404 | # model function 405 | # if cosine is used then tau could equal the x position of the peak 406 | # if sine were to be used then tau would be the first zero crossing 407 | if lock_frequency: 408 | func = lambda x, A, tau: A * np.sin(2 * pi * Hz * (x - tau) + pi / 2) 409 | else: 410 | func = lambda x, A, Hz, tau: A * np.sin(2 * pi * Hz * (x - tau) + 411 | pi / 2) 412 | #func = lambda x, A, Hz, tau: A * np.cos(2 * pi * Hz * (x - tau)) 413 | 414 | 415 | #get peaks 416 | fitted_peaks = [] 417 | for raw_peaks in [max_raw, min_raw]: 418 | peak_data = [] 419 | for peak in raw_peaks: 420 | index = peak[0] 421 | x_data = x_axis[index - points // 2: index + points // 2 + 1] 422 | y_data = y_axis[index - points // 2: index + points // 2 + 1] 423 | # get a first approximation of tau (peak position in time) 424 | tau = x_axis[index] 425 | # get a first approximation of peak amplitude 426 | A = peak[1] 427 | 428 | # build list of approximations 429 | if lock_frequency: 430 | p0 = (A, tau) 431 | else: 432 | p0 = (A, Hz, tau) 433 | 434 | # subtract offset from waveshape 435 | y_data -= offset 436 | popt, pcov = curve_fit(func, x_data, y_data, p0) 437 | # retrieve tau and A i.e x and y value of peak 438 | x = popt[-1] 439 | y = popt[0] 440 | 441 | # create a high resolution data set for the fitted waveform 442 | x2 = np.linspace(x_data[0], x_data[-1], points * 10) 443 | y2 = func(x2, *popt) 444 | 445 | # add the offset to the results 446 | y += offset 447 | y2 += offset 448 | y_data += offset 449 | 450 | peak_data.append([x, y, [x2, y2]]) 451 | 452 | fitted_peaks.append(peak_data) 453 | 454 | # structure date for output 455 | max_peaks = map(lambda x: [x[0], x[1]], fitted_peaks[0]) 456 | max_fitted = map(lambda x: x[-1], fitted_peaks[0]) 457 | min_peaks = map(lambda x: [x[0], x[1]], fitted_peaks[1]) 458 | min_fitted = map(lambda x: x[-1], fitted_peaks[1]) 459 | 460 | 461 | #pylab.plot(x_axis, y_axis) 462 | #pylab.hold(True) 463 | #for max_p, max_f in zip(max_peaks, max_fitted): 464 | # pylab.plot(max_p[0], max_p[1], 'x') 465 | # pylab.plot(max_f[0], max_f[1], 'o', markersize = 2) 466 | #for min_p, min_f in zip(min_peaks, min_fitted): 467 | # pylab.plot(min_p[0], min_p[1], 'x') 468 | # pylab.plot(min_f[0], min_f[1], 'o', markersize = 2) 469 | #pylab.show() 470 | 471 | return [max_peaks, min_peaks] 472 | 473 | 474 | def peakdetect_sine_locked(y_axis, x_axis, points = 9): 475 | """ 476 | Convinience function for calling the 'peakdetect_sine' function with 477 | the lock_frequency argument as True. 478 | 479 | keyword arguments: 480 | y_axis -- A list containg the signal over which to find peaks 481 | x_axis -- A x-axis whose values correspond to the y_axis list and is used 482 | in the return to specify the postion of the peaks. 483 | points -- (optional) How many points around the peak should be used during 484 | curve fitting, must be odd (default: 9) 485 | 486 | return -- see 'peakdetect_sine' 487 | """ 488 | return peakdetect_sine(y_axis, x_axis, points, True) 489 | 490 | 491 | def peakdetect_zero_crossing(y_axis, x_axis = None, window = 11): 492 | """ 493 | Function for detecting local maximas and minmias in a signal. 494 | Discovers peaks by dividing the signal into bins and retrieving the 495 | maximum and minimum value of each the even and odd bins respectively. 496 | Division into bins is performed by smoothing the curve and finding the 497 | zero crossings. 498 | 499 | Suitable for repeatable signals, where some noise is tolerated. Excecutes 500 | faster than 'peakdetect', although this function will break if the offset 501 | of the signal is too large. It should also be noted that the first and 502 | last peak will probably not be found, as this function only can find peaks 503 | between the first and last zero crossing. 504 | 505 | keyword arguments: 506 | y_axis -- A list containg the signal over which to find peaks 507 | x_axis -- (optional) A x-axis whose values correspond to the y_axis list 508 | and is used in the return to specify the postion of the peaks. If 509 | omitted an index of the y_axis is used. (default: None) 510 | window -- the dimension of the smoothing window; should be an odd integer 511 | (default: 11) 512 | 513 | return -- two lists [max_peaks, min_peaks] containing the positive and 514 | negative peaks respectively. Each cell of the lists contains a tupple 515 | of: (position, peak_value) 516 | to get the average peak value do: np.mean(max_peaks, 0)[1] on the 517 | results to unpack one of the lists into x, y coordinates do: 518 | x, y = zip(*tab) 519 | """ 520 | # check input data 521 | x_axis, y_axis = _datacheck_peakdetect(x_axis, y_axis) 522 | 523 | zero_indices = zero_crossings(y_axis, window = window) 524 | period_lengths = np.diff(zero_indices) 525 | 526 | bins_y = [y_axis[index:index + diff] for index, diff in 527 | zip(zero_indices, period_lengths)] 528 | bins_x = [x_axis[index:index + diff] for index, diff in 529 | zip(zero_indices, period_lengths)] 530 | 531 | even_bins_y = bins_y[::2] 532 | odd_bins_y = bins_y[1::2] 533 | even_bins_x = bins_x[::2] 534 | odd_bins_x = bins_x[1::2] 535 | hi_peaks_x = [] 536 | lo_peaks_x = [] 537 | 538 | #check if even bin contains maxima 539 | if abs(even_bins_y[0].max()) > abs(even_bins_y[0].min()): 540 | hi_peaks = [bin.max() for bin in even_bins_y] 541 | lo_peaks = [bin.min() for bin in odd_bins_y] 542 | # get x values for peak 543 | for bin_x, bin_y, peak in zip(even_bins_x, even_bins_y, hi_peaks): 544 | hi_peaks_x.append(bin_x[np.where(bin_y==peak)[0][0]]) 545 | for bin_x, bin_y, peak in zip(odd_bins_x, odd_bins_y, lo_peaks): 546 | lo_peaks_x.append(bin_x[np.where(bin_y==peak)[0][0]]) 547 | else: 548 | hi_peaks = [bin.max() for bin in odd_bins_y] 549 | lo_peaks = [bin.min() for bin in even_bins_y] 550 | # get x values for peak 551 | for bin_x, bin_y, peak in zip(odd_bins_x, odd_bins_y, hi_peaks): 552 | hi_peaks_x.append(bin_x[np.where(bin_y==peak)[0][0]]) 553 | for bin_x, bin_y, peak in zip(even_bins_x, even_bins_y, lo_peaks): 554 | lo_peaks_x.append(bin_x[np.where(bin_y==peak)[0][0]]) 555 | 556 | max_peaks = [[x, y] for x,y in zip(hi_peaks_x, hi_peaks)] 557 | min_peaks = [[x, y] for x,y in zip(lo_peaks_x, lo_peaks)] 558 | 559 | return [max_peaks, min_peaks] 560 | 561 | 562 | def _smooth(x, window_len=11, window='hanning'): 563 | """ 564 | smooth the data using a window of the requested size. 565 | 566 | This method is based on the convolution of a scaled window on the signal. 567 | The signal is prepared by introducing reflected copies of the signal 568 | (with the window size) in both ends so that transient parts are minimized 569 | in the begining and end part of the output signal. 570 | 571 | input: 572 | x: the input signal 573 | window_len: the dimension of the smoothing window; should be an odd 574 | integer 575 | window: the type of window from 'flat', 'hanning', 'hamming', 576 | 'bartlett', 'blackman' 577 | flat window will produce a moving average smoothing. 578 | output: 579 | the smoothed signal 580 | 581 | example: 582 | t = linspace(-2,2,0.1) 583 | x = sin(t)+randn(len(t))*0.1 584 | y = _smooth(x) 585 | 586 | see also: 587 | 588 | numpy.hanning, numpy.hamming, numpy.bartlett, numpy.blackman, 589 | numpy.convolve, scipy.signal.lfilter 590 | 591 | TODO: the window parameter could be the window itself if a list instead of 592 | a string 593 | """ 594 | if x.ndim != 1: 595 | raise ValueError, "smooth only accepts 1 dimension arrays." 596 | 597 | if x.size < window_len: 598 | raise ValueError, "Input vector needs to be bigger than window size." 599 | 600 | if window_len<3: 601 | return x 602 | 603 | if not window in ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']: 604 | raise(ValueError, 605 | "Window is not one of '{0}', '{1}', '{2}', '{3}', '{4}'".format( 606 | *('flat', 'hanning', 'hamming', 'bartlett', 'blackman'))) 607 | 608 | s = np.r_[x[window_len-1:0:-1], x, x[-1:-window_len:-1]] 609 | #print(len(s)) 610 | if window == 'flat': #moving average 611 | w = np.ones(window_len,'d') 612 | else: 613 | w = eval('np.' + window + '(window_len)') 614 | 615 | y = np.convolve(w / w.sum(), s, mode = 'valid') 616 | return y 617 | 618 | 619 | def zero_crossings(y_axis, window = 11): 620 | """ 621 | Algorithm to find zero crossings. Smoothens the curve and finds the 622 | zero-crossings by looking for a sign change. 623 | 624 | 625 | keyword arguments: 626 | y_axis -- A list containg the signal over which to find zero-crossings 627 | window -- the dimension of the smoothing window; should be an odd integer 628 | (default: 11) 629 | 630 | return -- the index for each zero-crossing 631 | """ 632 | # smooth the curve 633 | length = len(y_axis) 634 | x_axis = np.asarray(range(length), int) 635 | 636 | # discard tail of smoothed signal 637 | y_axis = _smooth(y_axis, window)[:length] 638 | zero_crossings = np.where(np.diff(np.sign(y_axis)))[0] 639 | indices = [x_axis[index] for index in zero_crossings] 640 | 641 | # check if zero-crossings are valid 642 | diff = np.diff(indices) 643 | if diff.std() / diff.mean() > 0.2: 644 | print diff.std() / diff.mean() 645 | print np.diff(indices) 646 | raise(ValueError, 647 | "False zero-crossings found, indicates problem {0} or {1}".format( 648 | "with smoothing window", "problem with offset")) 649 | # check if any zero crossings were found 650 | if len(zero_crossings) < 1: 651 | raise(ValueError, "No zero crossings found") 652 | 653 | return indices 654 | # used this to test the fft function's sensitivity to spectral leakage 655 | #return indices + np.asarray(30 * np.random.randn(len(indices)), int) 656 | 657 | ############################Frequency calculation############################# 658 | # diff = np.diff(indices) 659 | # time_p_period = diff.mean() 660 | # 661 | # if diff.std() / time_p_period > 0.1: 662 | # raise ValueError, 663 | # "smoothing window too small, false zero-crossing found" 664 | # 665 | # #return frequency 666 | # return 1.0 / time_p_period 667 | ############################################################################## 668 | 669 | 670 | 671 | 672 | 673 | def _test_zero(): 674 | _max, _min = peakdetect_zero_crossing(y,x) 675 | def _test(): 676 | _max, _min = peakdetect(y,x, delta=0.30) 677 | 678 | 679 | def _test_graph(): 680 | i = 10000 681 | x = np.linspace(0,3.7*pi,i) 682 | y = (0.3*np.sin(x) + np.sin(1.3 * x) + 0.9 * np.sin(4.2 * x) + 0.06 * 683 | np.random.randn(i)) 684 | y *= -1 685 | x = range(i) 686 | 687 | _max, _min = peakdetect(y,x,750, 0.30) 688 | xm = [p[0] for p in _max] 689 | ym = [p[1] for p in _max] 690 | xn = [p[0] for p in _min] 691 | yn = [p[1] for p in _min] 692 | 693 | plot = pylab.plot(x,y) 694 | pylab.hold(True) 695 | pylab.plot(xm, ym, 'r+') 696 | pylab.plot(xn, yn, 'g+') 697 | 698 | _max, _min = peak_det_bad.peakdetect(y, 0.7, x) 699 | xm = [p[0] for p in _max] 700 | ym = [p[1] for p in _max] 701 | xn = [p[0] for p in _min] 702 | yn = [p[1] for p in _min] 703 | pylab.plot(xm, ym, 'y*') 704 | pylab.plot(xn, yn, 'k*') 705 | pylab.show() 706 | 707 | 708 | 709 | if __name__ == "__main__": 710 | from math import pi 711 | import pylab 712 | 713 | i = 10000 714 | x = np.linspace(0,3.7*pi,i) 715 | y = (0.3*np.sin(x) + np.sin(1.3 * x) + 0.9 * np.sin(4.2 * x) + 0.06 * 716 | np.random.randn(i)) 717 | y *= -1 718 | 719 | _max, _min = peakdetect(y, x, 750, 0.30) 720 | xm = [p[0] for p in _max] 721 | ym = [p[1] for p in _max] 722 | xn = [p[0] for p in _min] 723 | yn = [p[1] for p in _min] 724 | 725 | plot = pylab.plot(x, y) 726 | pylab.hold(True) 727 | pylab.plot(xm, ym, 'r+') 728 | pylab.plot(xn, yn, 'g+') 729 | 730 | 731 | pylab.show() 732 | --------------------------------------------------------------------------------