/py-findpeaks

Overview of the peaks dectection algorithms available in Python

Primary LanguagePythonMIT LicenseMIT

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 on the subject.

Overview

Algorithm Integration Filters MatLab findpeaks-like?
scipy.signal.find_peaks_cwt Included in Scipy ?
scipy.signal.argrelextrema Included in Scipy 0.11+ Minimum distance
scipy.signal.find_peaks Included in Scipy 1.1+ Amplitude
Threshold
Distance
Prominence
Width
detect_peaks Single file source
Depends on Numpy
Minimum distance
Minimum height
Relative threshold
peakutils.peak.indexes PyPI package PeakUtils
Depends on Scipy
Amplitude threshold
Minimum distance
peakdetect Single file source
Depends on Scipy
Minimum distance
Octave-Forge findpeaks Requires an Octave-Forge distribution
+ PyPI package oct2py
Depends on Scipy
Minimum distance
Minimum height
Minimum peak width
Janko Slavic findpeaks Single function
Depends on Numpy
Minimum distance
Minimum height
Tony Beltramelli detect_peaks Single function
Depends on Numpy
Amplitude threshold
mlpy.findpeaks_dist Included in mlpy
Depends on Scipy and GSL
Minimum distance
mlpy.findpeaks_win Single function
Depends on Scipy and GSL
Sliding window width

How to make your choice?

When you're selecting an algorithm, you might consider:

  • 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.
  • The dependencies. Does it require extra dependency? Does is it easy to make it run on a fresh box?
  • The filtering support. Does the algorithm allows to define multiple filters? Which ones do you need?

scipy.signal.find_peaks_cwt

import numpy as np
vector = [
    0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3,
    1, 20, 7, 3, 0 ]
import scipy.signal
print('Detect peaks without any filters.')
indexes = scipy.signal.find_peaks_cwt(vector, np.arange(1, 4),
    max_distances=np.arange(1, 4)*2)
indexes = np.array(indexes) - 1
print('Peaks are: %s' % (indexes))

Documentation. Sample code.

The first historical peak detection algorithm from the Scipy signal processing package. 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.

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.

scipy.signal.argrelextrema

import numpy as np
vector = [
    0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3,
    1, 20, 7, 3, 0 ]
import scipy.signal
print('Detect peaks with order (distance) filter.')
indexes = scipy.signal.argrelextrema(
    np.array(vector),
    comparator=np.greater,order=2
)
print('Peaks are: %s' % (indexes[0]))

Documentation. Sample code.

New peak detection algorithm from Scipy since version 0.11.0. Its usage is really trivial, but it misses out of the box filtering capacities.

It includes an order parameter that can serve as a kind of minimum distance filter. The filtering behavior is customizable through the comparator parameter, which can make it a good choice for building your own filtering algorithm over it.

See also related functions argrelmin and argrelmax.

scipy.signal.find_peaks

import numpy as np
import scipy.signal
vector = np.array([0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8,
                   13, 8, 10, 3, 1, 20, 7, 3, 0])
print('Detect peaks with minimum height and distance filters.')
indexes, _ = scipy.signal.find_peaks(vector, height=7, distance=2.1)
print('Peaks are: %s' % (indexes))

Documentation. Sample code.

This function was added to SciPy in version 1.1.0 and is comparable to findpeaks provided in Matlab's Signal Processing Toolbox.

scipy.signal.find_peaks searches for peaks (local maxima) based on simple value comparison of neighbouring samples and returns those peaks whose properties match optionally specified conditions (minimum and / or maximum) for their height, prominence, width, threshold and distance to each other.

On the prominence parameter, see this explanation.

detect_peaks from Marcos Duarte

import numpy as np
vector = [
    0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3,
    1, 20, 7, 3, 0 ]
from libs import detect_peaks
print('Detect peaks with minimum height and distance filters.')
indexes = detect_peaks.detect_peaks(vector, mph=7, mpd=2)
print('Peaks are: %s' % (indexes))

Documentation. Source. Sample code.

This algorithm comes from a notebook written by Marcos Duarte and is pretty trivial to use.

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.

peakutils.peak.indexes

import numpy as np
vector = [
    0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3,
    1, 20, 7, 3, 0 ]
import peakutils.peak
print('Detect peaks with minimum height and distance filters.')
indexes = peakutils.peak.indexes(np.array(vector),
    thres=7.0/max(vector), min_dist=2)
print('Peaks are: %s' % (indexes))

Documentation. Package. Sample code.

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.

peakdetect from sixtenbe

import numpy as np
vector = [
    0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3,
    1, 20, 7, 3, 0 ]
from libs import peakdetect
print('Detect peaks with distance filters.')
peaks = peakdetect.peakdetect(np.array(vector), lookahead=2, delta=2)
# peakdetect returns two lists, respectively positive and negative peaks,
# with for each peak a tuple of (indexes, values).
indexes = []
for posOrNegPeaks in peaks:
    for peak in posOrNegPeaks:
        indexes.append(peak[0])
print('Peaks are: %s' % (indexes))

Source and documentation. Sample code.

The algorithm was written by sixtenbe based on the previous work of endolith and Eli Billauer.

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).

Octave-Forge findpeaks

import numpy as np
vector = [
    0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3,
    1, 20, 7, 3, 0 ]
from oct2py import octave
# Load the Octage-Forge signal package.
octave.eval("pkg load signal")
print('Detect peaks with minimum height and distance filters.')
(pks, indexes) = octave.findpeaks(np.array(vector), 'DoubleSided',
    'MinPeakHeight', 6, 'MinPeakDistance', 2, 'MinPeakWidth', 0)
# The results are in a 2D array and in floats: get back to 1D array and convert
# peak indexes to integer. Also this is MatLab-style indexation (one-based),
# so we must substract one to get back to Python indexation (zero-based).
indexes = indexes[0].astype(int) - 1
print('Peaks are: %s' % (indexes))

Documentation. oct2py package. Sample code.

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.

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.

Although the function have an interface close to the MatLab findpeaks, it is harder to have the exact same results that with detect_peaks or peakutils.peak.indexes.

Janko Slavic findpeaks

import numpy as np
vector = [
    0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3,
    1, 20, 7, 3, 0 ]
from libs.findpeaks import findpeaks
indexes = findpeaks(np.array(vector), spacing=2, limit=7)
print('Peaks are: %s' % (indexes))

Documentation. Source. Sample code.

Small and fast peak detection algorithm, with minimum distance and height filtering support. Comes as an handy single function, depending only on Numpy.

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.

Tony Beltramelli detect_peaks

import numpy as np
vector = [
    0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8, 13, 8, 10, 3,
    1, 20, 7, 3, 0 ]
from libs.tony_beltramelli_detect_peaks import detect_peaks
print('Detect peaks with height threshold.')
indexes = detect_peaks(vector, 1.5)
print('Peaks are: %s' % (indexes))

Source and documentation. Sample code.

Straightforward, simple and lightweight peak detection algorithm, with minimum distance filtering support.

No minimum peak height filtering support.

mlpy.findpeaks_dist

import numpy as np
import scipy.signal
vector = [0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8,
                   13, 8, 10, 3, 1, 20, 7, 3, 0]
print('Detect peaks with minimum distance filter.')
indexes = mlpy.findpeaks_dist(vector, mindist=2.1)
print('Peaks are: %s' % (indexes))

Documentation. Sample code.

Find peaks, with a minimum distance filter between peaks. Code written by Davide Albanese.

mlpy.findpeaks_win

import numpy as np
import scipy.signal
vector = [0, 6, 25, 20, 15, 8, 15, 6, 0, 6, 0, -5, -15, -3, 4, 10, 8,
                   13, 8, 10, 3, 1, 20, 7, 3, 0]
print('Detect peaks with sliding window of 5.')
indexes = mlpy.findpeaks_win(vector, span=5)
print('Peaks are: %s' % (indexes))

Documentation. Sample code.

Find peaks, with a sliding window of specified width. Code written by Davide Albanese.


How to find both lows and highs?

Most algorithms detect only local maximas. You may want to detect both minimas and maximas.

One solution is to invert the signal before feeding it to the algorithm for detecting lows, as suggested by @raoofhujairi.

With two runs, you can then get both lows and highs:

See the related sample code using PeakUtils.


How to run the examples?

Install Numpy, Scipy and Matplotlib

You need to have Numpy, Scipy and Matplotlib installed - possibly the latest versions.

To install - and update - them for Python 3:

pip3 install -U numpy scipy matplotlib

(you may need to run the command using sudo for a system-wide install)

Install test sample dependencies

Some examples rely on other packages - like PeakUtils. Install them using Pipenv to run all sample codes:

# Go in tests directory.
cd tests/
# Install dependencies in a virtualenv using Pipenv.
# We install also Matplotlib so we can access it in the virtualenv.
pipenv --site-packages install --skip-lock

Run an example

You can them run any example to see the results.

For e.g. for testing PeakUtils:

pipenv run python3 peakutils_indexes.py

Contribute

Feel free to open a new ticket or submit a PR to improve this overview.

Happy processing!