python - How to get high and low envelope of a signal?

后端 未结 4 912
感情败类
感情败类 2020-12-23 23:30

I have quite a noisy data, and I am trying to work out a high and low envelope to the signal. It is kinda of like this example in MATLAB:

http://uk.mathworks.com/hel

4条回答
  •  轮回少年
    2020-12-23 23:51

    First attempt was to make use of scipy Hilbert transform to determine the amplitude envelope but this didn't work as expected in many cases, mainly reason because, citing from this digital signal processing answer:

    Hilbert envelope, also called Energy-Time Curve (ETC), only works well for narrow-band fluctuations. Producing an analytic signal, of which you later take the absolute value, is a linear operation, so it treats all frequencies of your signal equally. If you give it a pure sine wave, it will indeed return to you a straight line. When you give it white noise however, you will likely get noise back.

    From then, since the other answers were using cubic spline interpolation and did tend to become cumbersome, a bit unstable (spurious oscillations) and time consuming for very long and noisy data arrays, I will contribute here with a simple and numpy efficient version that seems to work pretty fine:

    import numpy as np
    from matplotlib import pyplot as plt
    
    def hl_envelopes_idx(s,dmin=1,dmax=1):
        """
        Input :
        s : 1d-array, data signal from which to extract high and low envelopes
        dmin, dmax : int, size of chunks, use this if size of data is too big
        Output :
        lmin,lmax : high/low enveloppe idx of signal s
        """
    
        # locals min      
        lmin = (np.diff(np.sign(np.diff(s))) > 0).nonzero()[0] + 1 
        # locals max
        lmax = (np.diff(np.sign(np.diff(s))) < 0).nonzero()[0] + 1 
        
        """
        # using the following might help in some case by cutting the signal in "half" along y-axis
        s_mid = np.mean(s) (0 if s centered around x-axis or more generally mean of signal)
        # pre-sorting of locals min based on sign 
        lmin = lmin[s[lmin]s_mid]
        """
    
        # global max of dmax-chunks of locals max 
        lmin = lmin[[i+np.argmin(s[lmin[i:i+dmin]]) for i in range(0,len(lmin),dmin)]]
        # global min of dmin-chunks of locals min 
        lmax = lmax[[i+np.argmax(s[lmax[i:i+dmax]]) for i in range(0,len(lmax),dmax)]]
        
        return lmin,lmax
    

    Example 1: quasi-periodic vibration

    t = np.linspace(0,8*np.pi,5000)
    s = 0.8*np.cos(t)**3 + 0.5*np.sin(np.exp(1)*t)
    high_idx, low_idx = hl_envelopes_idx(s)
    
    # plot
    plt.plot(t,s,label='signal')
    plt.plot(t[high_idx], s[high_idx], 'r', label='low')
    plt.plot(t[low_idx], s[low_idx], 'g', label='high')
    

    Example 2: noisy decaying signal

    t = np.linspace(0,2*np.pi,5000)
    s = 5*np.cos(5*t)*np.exp(-t) + np.random.rand(len(t))
    
    high_idx, low_idx = hl_envelopes_idx(s,dmin=15,dmax=15)
    
    # plot
    plt.plot(t,s,label='signal')
    plt.plot(t[high_idx], s[high_idx], 'r', label='low')
    plt.plot(t[low_idx], s[low_idx], 'g', label='high')
    

    Example 3: nonsymmetric modulated chirp

    A much more complex signal of 18867925 samples (which isn't included here):

提交回复
热议问题