4
\$\begingroup\$

Using python 3.6. I have written this code (below) to apply hysteresis to a signal (a numpy array).

My aim was to create a function that didn't require any loop to remember the previous state of the signal. For that purpose I re-used code from this post.

Here is an example output: enter image description here

And here is the code that generated it.

import matplotlib.pyplot as plt
import numpy as np


def ups_downs(y):
    """Detect when the direction of y crosses 0
    """

    try:
        summed = np.cumsum(y)
        d = np.sign(np.diff(summed))
        ud = np.flatnonzero(d)
        uds = d[ud]
        change = ud[np.r_[True, uds[1:] != uds[:-1]]]

        out = np.zeros(len(summed), dtype=int)

        out[1:][change] = d[change]
    except IndexError:
        out = y
    return out


def hysteresis(signal, top_band, bottom_band, normal_below=False):
    """Return a signal that has had hysteresis applied. A value of 1
    in the returned result indicated the signal has crossed both bands
    and has returned to its default position (either above or below
    both top and bottom bands).

    Args:
        signal: A 1D numpy array to apply hysteresis to
        top_band: A 1D numpy array. When the signal evaluates above this band
            the signal is considered 'above'
        bottom_band: A 1D numpy array. When the signal evaluates below this band
          the signal is considered 'below
        normal_below: When `True` indicates that the signal is normally 'below'
          the bottom band. Default = `False` indicating the signal is normally 'above'
          the top band.
    """
    result = np.zeros(len(signal), int)
    i, ii = {False: (1, -1), True: (-1, 1)}[normal_below]
    result[signal > top_band] = i
    result[signal < bottom_band] = ii
    result = ups_downs(result)
    result[result == -1] = 0
    zero_out = {False: np.argmax(signal < bottom_band),
                True: np.argmax(signal > top_band)}[normal_below]
    result[:zero_out] = 0
    return result


top_band = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
                     17, 18, 19])
bottom_band = np.array([-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
                        15, 16, 17])
signal = np.array([1, 2, 3, 4, 1, 2, 7, 8, 9, 6, 7, 12, 11, 14, 11, 12, 15,
                   14, 20, 16])

result = hysteresis(signal, top_band, bottom_band, normal_below=True)

x = np.arange(signal.shape[0])
plt.plot(x, top_band, label='top_band')
plt.plot(x, bottom_band, label='bottom_band')
plt.plot(x, signal, label='signal')
plt.plot(x, result, label='result')
plt.legend()
plt.show()

As stated earlier the ups_downs function is re-purposed from this post. So i'm really interested if there is a simpler way to achieve the same result with out having to use it. Although the concept is simple to understand, I found it challenging to create the hysteresis function, particularly how to initialize the output. I decided to zero it out until the signal has made a whole round trip.

The function takes 4 parameters. The signal, top_band, bottom_band and most confusingly normal_below. normal_below indicates that the signal is normally 'below' or 'above' both bands. And that upon returning to this state the returned result should indicate this.

How could it be improved?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.