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\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.