Search code examples
pythonnumpynumpy-ufunc

numpy: accumulate 'greater' operation


I'm trying to write a function that would detect all rising edges - indexes in a vector where value exceeds certain threshold. Something similar is described here: Python rising/falling edge oscilloscope-like trigger, but I want to add hysteresis, so that trigger won't fire unless the value goes below another limit.

I came up the following code:

import numpy as np

arr = np.linspace(-10, 10, 60)
sample_values = np.sin(arr) + 0.6 * np.sin(arr*3)

above_trigger = sample_values > 0.6
below_deadband = sample_values < 0.0
combined = 1 * above_trigger - 1 * below_deadband

Now in the combined array there is 1 where the original value was above upper limit, -1 where the value was below lower limit and 0 where the value was in between:

>>> combined
array([ 1,  1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  0,  1,  1,  1,  0,  0,
        1,  1,  1,  0, -1, -1, -1, -1, -1, -1, -1, -1, -1,  0,  1,  1,  1,
        0,  0,  1,  1,  1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  1,  1,
        1,  0,  0,  1,  1,  1,  0, -1, -1])

My idea was to use some clever function that would process this vector sequentially and replace all sequences of zeros with whatever non-zero value was preceding them. Then the problem would boil down to simply finding where the value changes from -1 to 1.

I though that greater operation would fulfill this purpose if used correctly: -1 encoded as True and 1 as False:

  • (True ("-1") > -1) -> True ("-1")
  • (True ("-1") > 1) -> False ("1")
  • (True ("-1") > 0) -> True ("-1")
  • (False ("1") > -1) -> True ("-1")
  • (False ("1") > 1) -> False ("1")
  • (False ("1") > 0) -> False ("1")

But the results are not what I expect:

>>> 1 - 2 * np.greater.accumulate(combined)
array([-1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1])

It seems that the greater function doesn't correctly compare booleans with numeric values in this scenario, even though it works fine when used on scalars or pair-wise:

>>> np.greater(False, -1)
True
>>> np.greater.outer(False, combined)
array([False, False,  True,  True,  True,  True,  True,  True,  True,
        True,  True, False, False, False, False, False, False, False,
       False, False, False,  True,  True,  True,  True,  True,  True,
        True,  True,  True, False, False, False, False, False, False,
       False, False, False,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True, False, False, False, False, False,
       False, False, False, False,  True,  True])

Is this expected behavior? Am I doing something wrong here, is there any way around this?

Alternatively, maybe you could suggest another approach to this problem?

Thank you.


Solution

  • I am not sure what the issue with np.greater.accumulate is (it does not seem to behave as advertised indeed), but the following should work:

    import numpy as np
    import numpy as np
    
    arr = np.linspace(-10, 10, 60)
    sample_values = np.sin(arr) + 0.6 * np.sin(arr*3)
    
    above_trigger = sample_values > 0.6
    below_deadband = sample_values < 0.0
    combined = 1 * above_trigger - 1 * below_deadband
    
    mask = combined != 0
    idx = np.where(mask,np.arange(len(mask)),0)
    idx = np.maximum.accumulate(idx)
    result = combined[idx]
    
    print(f"combined:\n {combined}\n")
    print(f"result:\n {result}")
    

    It gives:

    combined:
     [ 1  1 -1 -1 -1 -1 -1 -1 -1 -1 -1  0  1  1  1  0  0  1  1  1  0 -1 -1 -1
     -1 -1 -1 -1 -1 -1  0  1  1  1  0  0  1  1  1 -1 -1 -1 -1 -1 -1 -1 -1 -1
     -1  1  1  1  0  0  1  1  1  0 -1 -1]
    
    result:
     [ 1  1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1  1  1  1  1  1  1  1  1  1 -1 -1 -1
     -1 -1 -1 -1 -1 -1 -1  1  1  1  1  1  1  1  1 -1 -1 -1 -1 -1 -1 -1 -1 -1
     -1  1  1  1  1  1  1  1  1  1 -1 -1]
    

    Then the indices where values jumps from -1 to 1 can be obtained as follows:

    np.nonzero(result[1:] > result[:-1])[0] + 1
    

    It gives:

    array([12, 31, 49])