Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Finding minimal jump zero crossings in numpy

For a data analysis task, I want to find zero crossings in a numpy array, coming from a convolution with first a sobel-like kernel, and then a mexican hat kernel. Zero crossings allow me to detect edges in the data.

Unfortunately, the data is somewhat noisy and I only want to find zero crossings with a minimal jump size, 20 in the follwing example:

import numpy as np
arr = np.array([12, 15, 9, 8, -1, 1, -12, -10, 10])

Should result in

>>>array([1, 3, 7])

or

>>>array([3, 7])

Where 3 is the index of -1, just before the middle of the first jump and 7 is the index of -10

I have tried a modification of the following code (source: Efficiently detect sign-changes in python)

zero_crossings = np.where(np.diff(np.sign(np.trunc(arr/10))))[0]

Which correctly ignores small jumps, but puts the zero-crossings at [1,5,7]

What would be an efficient way of doing this?

The definition of minimal jump is not strict, but results should be along the lines of my question.

Edit: For Clarification

arr = np.array([12, 15, 9, 8, -1, 1, -12, -10, 10])
arr_floored = np.trunc(arr/10)
>>>>np.array([10, 10, 0, 0, 0, 0, -10, -10, 10])
sgn = np.sign(arr_floored)
>>>>array([ 1,  1,  0,  0,  0,  0, -1, -1,  1])
dsgn = np.diff(sgn)
>>>>array([ 0, -1,  0,  0,  0, -1,  0,  2])
np.where(dsgn)
>>>>(array([1, 5, 7], dtype=int64),)

Further edgecases:

arr = [10,9,8,7,6,5,4,3,2,1,0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-10]

Should result in

>>> np.array([10])

Also just noticed: The problem might be ill-posed(in a mathematical sense). I will clarify it later today.

like image 821
AlexNe Avatar asked May 23 '19 07:05

AlexNe


People also ask

How do you find the zero crossing points in 1D data?

Find the zero crossing points in 1d data. Find the zero crossing events in a discrete data set. Linear interpolation is used to determine the actual locations of the zero crossing between two data points showing a change in sign. Data point which are zero are counted in as zero crossings if a sign change occurs across them.

How do you count zero crossings in Python?

Another way to count zero crossings and squeeze just a few more milliseconds out of the code is to use nonzero and compute the signs directly. Assuming you have a one-dimensional array of data:

How do you find the zero crossing events in a graph?

Find the zero crossing events in a discrete data set. Linear interpolation is used to determine the actual locations of the zero crossing between two data points showing a change in sign. Data point which are zero are counted in as zero crossings if a sign change occurs across them.

Does NumPy handle zeros in the input array?

As remarked by Jay Borseth the accepted answer does not handle arrays containing 0 correctly. Since a) using numpy.signbit () is a little bit quicker than numpy.sign (), since it's implementation is simpler, I guess and b) it deals correctly with zeros in the input array.


2 Answers

Here's a solution that gives the midpoint of crossings involving a noise threshold to filter potentially multiple fluctuations around zero applied across multiple data points. It give the correct answers for the two examples you supplied. However, I've made a couple of assumptions:

  • You didn't define precisely what range of data points to consider to determine the midpoint of the crossing, but I've used your sample code as a basis - it was detecting crossings where ABS(start | end) >= 10 hence I've used the minimum range where this condition holds.
    NB: This does not detect a transition from +15 to -6.
    EDIT: Actually it's not always the minimum range, but the code should be enough for you to get started and adjust as needed.
  • I've assumed that it is ok to also use pandas (to track the indexes of data points of interest). You could probably avoid pandas if essential.

import numpy as np import pandas as pd arr = np.array([12, 15, 9, 8, -1, 1, -12, -10, 10]) sgn = pd.Series(np.sign(np.trunc(arr/10))) trailingEdge = sgn[sgn!=0].diff() edgeIndex = np.array(trailingEdge[trailingEdge!=0].index) edgeIndex[:-1] + np.diff(edgeIndex) / 2

gives:

array([3., 7.])

and

arr = [10,9,8,7,6,5,4,3,2,1,0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-10]

gives:

array([10.])

like image 133
Mike Avatar answered Oct 23 '22 01:10

Mike


Base case

I guess you want

import numpy as np
x = np.array([10, -50, -30, 50, 10, 3, -200, -12, 123])
indices = np.where(np.logical_and(np.abs(np.diff(x)) >= 20, np.diff(np.sign(x)) != 0))[0]

read as: indices, where ((absolute differences of x) are larger or equal 20) and (the sign flips)

which returns

array([0, 2, 5, 7])

Periodic signal

The usual numpy functions don't cover this case. I would suggest simply adding the first element in the end, via the pad function:

import numpy as np
x = np.array([10, 5, 0, -5, -10])
x = np.pad(x, (0, 1), 'wrap')
indices = np.where(np.logical_and(np.abs(np.diff(x)) >= 20, np.diff(np.sign(x)) != 0))[0]
like image 33
Obay Avatar answered Oct 23 '22 01:10

Obay