-1

using numpy I have extracted the zero crossings of a signal.

Unfortunately the source of the data is noisy and thus there are multiple zero crossings.

If I filter the data before checking for zero crossings, aspects of the filter (gain-phase margin) will need to be justified while averaging the zero crossing points is slightly easier to justify

[123,125,127,1045,1049,1050,2147,2147,2151,2155]

consider the above list. what would be an appropriate way to create:

[125, 1048, 2149]

The aim is to find the phase shift between two sine waves

Naib
  • 999
  • 7
  • 20
  • It looks like you want to partition the data, and then find the mean of each slice. What rule do you use to partition it? – lvc Oct 02 '15 at 10:54
  • 1
    Can we assume that the list will be in ascending order and the jumps between chunks will be large enough (as in your example)? – Imanol Luengo Oct 02 '15 at 10:56
  • There are near endless possiblities ways for solving your problem and there is no general solution (depends on signal length, SNR, signal form, etc). A simple way would be a nondeterministic FIR-Filter: `y_k=(x_k-1 + x_k + x_k+1)/3` – Peter Schneider Oct 02 '15 at 11:00
  • Is the value 214 a typo? If it isn't then what do the numbers mean? Can you include the direction of the zero crossing in these values? – DisappointedByUnaccountableMod Oct 02 '15 at 11:03
  • Sorry yes the 214 was a typo – Naib Oct 02 '15 at 12:29
  • Yes the numbers will be ascending (indexes basically) and there will be a significant gap between crossing events. – Naib Oct 02 '15 at 12:30

2 Answers2

0

This code takes a simplistic approach of looking for a gap THRESHOLD between the transitions - exceeding this marks the end of a signal transition.

xings = [123,125,127,1045,1049,1050,2147,2147,2151,2155]

THRESHOLD = 100

xlast = -1000000
tot = 0
n = 0
results = []
i = 0
while i < len(xings):
    x = xings[i]
    if x-xlast > THRESHOLD:
        # emit a transition, averaged for the
        if n > 0:
            results.append(tot/n)
        tot = 0
        n = 0
    tot += x
    n += 1
    xlast = x
    i += 1
if n > 0:
    results.append(tot/n)

print results

prints:

[125, 1048, 2150]
  • I accepted this one because while it wasn't how I implemented it, once I read through what you are doing, you are essentially doing the same thing. – Naib Oct 02 '15 at 19:27
  • If you have the raw data there are some nice-looking flitering possibilities e.g. see Savitzky-Golay filter http://stackoverflow.com/questions/20618804/how-to-smooth-a-curve-in-the-right-way and moving average using convolve http://stackoverflow.com/questions/11352047/finding-moving-average-from-data-points-in-python/11352216#11352216 – DisappointedByUnaccountableMod Oct 02 '15 at 21:46
  • yup. The reason I am steering away from filtering in this instance is due to the phaseshift of filtering. the below example of implementation has a 16kHz filter on 10Hz data. Its a 1st order and I could be a 2nd order biquad and lower it BUT the level of filtering to produce non-multiple crossing would result in a large phaseshift. If I can get my head around kalman filtering that would work. A boxcar filter is the worst for this. – Naib Oct 02 '15 at 22:02
  • Interesting filter ... but still has crossings even with smoothing coefficient. This selective avg works – Naib Oct 03 '15 at 10:23
0

I was hoping for a more elegant solution to just iterating over the list of zero crossings, but it seems that is the only solution.

I settled on:

def zero_crossing_avg(data):
    output = []
    running_total = data[0]
    count = 1
    for i in range(1,data.size):
        val = data[i]
        if val - data[i-1] < TOL:
            running_total += val
            count += 1
        else:
            output.append(round(running_total/count))
            running_total = val
            count = 1
    return output

with example code of it in-use:

#!/usr/bin/env python

import numpy as np
from matplotlib import pyplot as plt

dt = 5e-6
TOL = 50


class DCfilt():
    def __init__(self,dt,freq):
        self.alpha = dt/(dt + 1/(2*np.pi*freq))
        self.y = [0,0]
    def step(self,x):
        y = self.y[-1] + self.alpha*(x - self.y[-1])
        self.y[-1] = y
        return y

def zero_crossing_avg(data):
    output = []
    running_total = data[0]
    count = 1
    for i in range(1,data.size):
        val = data[i]
        if val - data[i-1] < TOL:
            running_total += val
            count += 1
        else:
            output.append(round(running_total/count))
            running_total = val
            count = 1
    return output





t = np.arange(0,2,dt)
print(t.size)
rng = (np.random.random_sample(t.size) - 0.5)*0.1
s = 10*np.sin(2*np.pi*t*10  + np.pi/12)+rng
c = 10*np.cos(2*np.pi*t*10)+rng

filt_s = DCfilt(dt,16000)
filt_s.y[-1] =s[0]
filt_c = DCfilt(dt,1600)
filt_c.y[-1] =c[0]

# filter the RAW data first
for i in range(s.size):
    s[i] = filt_s.step(s[i])
    c[i] = filt_c.step(c[i])

# determine the zero crossings
s_z = np.where(np.diff(np.sign(s)))[0]
c_z = np.where(np.diff(np.sign(c)))[0]

sin_zc = zero_crossing_avg( np.where(np.diff(np.sign(s)))[0] )  
cos_zc = zero_crossing_avg( np.where(np.diff(np.sign(c)))[0] )  


HALF_PERIOD = (sin_zc[1] - sin_zc[0])
for i in range([len(sin_zc),len(cos_zc)][len(sin_zc) > len(cos_zc)]):
    delta  = abs(cos_zc[i]-sin_zc[i])
    print(90 - (delta/HALF_PERIOD)*180)


plt.hold(True)
plt.grid(True)

plt.plot(s)
plt.plot(c)
plt.show()

This works well enough.

Naib
  • 999
  • 7
  • 20