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