python scipy.signal.peak_widths --> absolute heigth? (fft -3dB damping)

a 夏天 提交于 2021-02-19 05:29:46

问题


https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.peak_widths.html

I think the linked function can only calculate the peak widths at a relative height. Does anyone know if there is a function that calculates the width at a fixed value (peak_amplitude - x) for all peaks?

Currently I am trying to change the original inner function "_peak_widths". Fail already with the cimport. Understand the source code here only partially. I added in the code where I would make a modification.

 with nogil:
    for p in range(peaks.shape[0]):
        i_min = left_bases[p]
        i_max = right_bases[p]
        peak = peaks[p]
        # Validate bounds and order
        if not 0 <= i_min <= peak <= i_max < x.shape[0]:
            with gil:
                raise ValueError("prominence data is invalid for peak {}"
                                 .format(peak))
        height = width_heights[p] = x[peak] - prominences[p] * rel_height 

CHANGE HERE TO x[peak] - 3

        # Find intersection point on left side
        i = peak
        while i_min < i and height < x[i]:
            i -= 1
        left_ip = <np.float64_t>i
        if x[i] < height:
            # Interpolate if true intersection height is between samples
            left_ip += (height - x[i]) / (x[i + 1] - x[i])

        # Find intersection point on right side
        i = peak
        while i < i_max and height < x[i]:
            i += 1
        right_ip = <np.float64_t>i
        if  x[i] < height:
            # Interpolate if true intersection height is between samples
            right_ip -= (height - x[i]) / (x[i - 1] - x[i])

        widths[p] = right_ip - left_ip
        if widths[p] == 0:
            show_warning = True
        left_ips[p] = left_ip
        right_ips[p] = right_ip

回答1:


In case this is still relevant to you, you can use scipy.signal.peak_widths "as is" to achieve what you want by passing in modified prominence_data. Based on your own answer:

import numpy as np
from scipy.signal import find_peaks, peak_prominences, peak_widths

# Create sample data
x = np.linspace(0, 6 * np.pi, 1000)
x = np.sin(x) + 0.6 * np.sin(2.6 * x)

# Find peaks
peaks, _ = find_peaks(x)
prominences, left_bases, right_bases = peak_prominences(x, peaks)

As stated in peak_widths's documentation the height at which the width is measured is calculated as h_eval = h_peak - prominence * relative_height

We can control the latter two variables through the parameters prominence_data and rel_height. So instead of passing in the calculated prominence which differs for each peak we can create an array where all values are the same and use that to create an absolute height:

# Create constant offset as a replacement for prominences
offset = np.ones_like(prominences)

# Calculate widths at x[peaks] - offset * rel_height
widths, h_eval, left_ips, right_ips = peak_widths(
    x, peaks, 
    rel_height=1,
    prominence_data=(offset, left_bases, right_bases)
)

# Check that h_eval is 1 everywhere
np.testing.assert_equal(x[peaks] - h_eval, 1)

# Visualize result
import matplotlib.pyplot as plt
plt.plot(x)
plt.plot(peaks, x[peaks], "x")
plt.hlines(h_eval, left_ips, right_ips, color="C2")
plt.show()

As you can see the width is evaluated for each peak at the same constant offset of 1. By using the original left_bases and right_bases as provided by peak_prominences we are limiting the maximal measured width (e.g. see peaks at 299 and 533). If you want to remove that limitation you must create these arrays yourself.




回答2:


I just removed the c content. Thats my solution:

def gauss(x, p): # p[0]==mean, p[1]==stdev
    return 1.0/(p[1]*np.sqrt(2*np.pi))*np.exp(-(x-p[0])**2/(2*p[1]**2))

def _peak_widths(x,peaks,prop,val=3):

    i_min = prop['left_bases']
    i_max = prop['right_bases']
    peak = peaks[0]
    # Validate bounds and order
    height = x[peak] - val

    # Find intersection point on left side
    i = peak
    while i_min < i and height < x[i]:
        i -= 1
    left_ip = i
    if x[i] < height:
        # Interpolate if true intersection height is between samples
        left_ip += (height - x[i]) / (x[i + 1] - x[i])

    # Find intersection point on right side
    i = peak
    while i < i_max and height < x[i]:
        i += 1
    right_ip = i
    if  x[i] < height:
        # Interpolate if true intersection height is between samples
        right_ip -= (height - x[i]) / (x[i - 1] - x[i])

    widths = right_ip - left_ip
    left_ips = left_ip
    right_ips = right_ip

    return [height, widths, int(left_ips), int(right_ips)]

if __name__ == '__main__':

    # Create some sample data
    known_param = np.array([2.0, 0.07])
    xmin,xmax = -1.0, 5.0
    N = 1000
    X = np.linspace(xmin,xmax,N)
    Y = gauss(X, known_param)
    fig, ax= plt.subplots()
    ax.plot(X,Y)

    #find peaks
    peaks, prop = signal.find_peaks(Y, prominence = 3.1)
    ax.scatter(X[peaks],Y[peaks], color='r')

    #calculate peak width
    y, widths, x1, x2 = _peak_widths(Y,peaks, prop)

    print(f'width = { X[x1] - X[x2]}')

    l = mlines.Line2D([X[x1],X[x2]], [y,y], color='r')
    ax.add_line(l)
    plt.show()


来源:https://stackoverflow.com/questions/53778703/python-scipy-signal-peak-widths-absolute-heigth-fft-3db-damping

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!