[SciPy-User] how to treat an invalid value, in signal/filter_design.py
R Schumacher
rays at blue-cove.com
Fri Oct 21 15:34:45 EDT 2016
In an attempt to computationally invert the effect of an analog RC
filter on a data set and reconstruct the signal prior to the analog
front end, a co-worker suggested: "Mathematically, you just reverse
the a and b parameters. Then the zeros become the poles, but if the
new poles are not inside the unit circle, the filter is not stable."
So then to "stabilize" the poles' issue seen, I test for the DIV/0
error and set it to 2./N+0.j in scipy/signal/filter_design.py ~ line 244
d = polyval(a[::-1], zm1)
if d[0]==0.0+0.j:
d[0] = 2./N+0.j
h = polyval(b[::-1], zm1) / d
- Question is, is this a mathematically valid treatment?
- Is there a better way to invert a Butterworth filter, or work with
the DIV/0 that occurs without modifying the signal library?
I noted d[0] > 2./N+0.j makes the zero bin result spike low; 2/N
gives a reasonable "extension" of the response curve.
The process in general causes a near-zero offset however, which I
remove with a high pass now; In an full FFT of a ~megasample one can
see that the first 5 bins have run away.
An example attached...
Ray Schumacher
Programmer/Consultant
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.scipy.org/pipermail/scipy-user/attachments/20161021/47c3d980/attachment.html>
-------------- next part --------------
import numpy as np
from scipy.signal import butter, lfilter, freqz, filtfilt, iirdesign, cheby1
from scipy.ndimage.interpolation import zoom
import matplotlib.pyplot as plt
def butter_highpass(cutoff, fs, order=5):
nyq = 0.5 * fs
normal_cutoff = cutoff / nyq
b, a = butter(order, Wn=normal_cutoff, btype='highpass', analog=False)
return b, a
def butter_inv_highpass(cutoff, fs, order=5):
nyq = 0.5 * fs
normal_cutoff = cutoff / nyq
b, a = butter(order, Wn=normal_cutoff, btype='highpass', analog=False)
## swap the components
return a, b
def butter_highpass_filter(data, cutoff, fs, order=5):
b, a = butter_highpass(cutoff, fs, order=order)
y = lfilter(b, a, data)
return y
def butter_inv_highpass_filter(data, cutoff, fs, order=5):
b, a = butter_inv_highpass(cutoff, fs, order=1)
offset = data.mean()
y = lfilter(b, a, data)
## remove new offset
y -= (y.mean() - offset)
## high pass filter 3x
## make a low pass to subtract
hpf = .05
nyq_rate = (fs/2.) / 80.
gstop, gpass = 20, .1
b,a = iirdesign(wp = (hpf/2.)/nyq_rate,
ws = hpf/nyq_rate,
gstop=gstop, gpass=gpass, ftype='cheby1')
for x in range(3):
print 'len y', len(y)
yd = decimate(decimate(decimate(y, 5), 4), 4)
filtered = filtfilt(b, a, yd, method='gust')
y = y - rebin(filtered, len(y))
return y
def decimate(x, q, n=None, axis=-1):
"""
Downsample the signal by using a filter.
By default, an order 8 Chebyshev type I filter is used.
Parameters
----------
x : ndarray
The signal to be downsampled, as an N-dimensional array.
q : int
The downsampling factor.
n : int, optional
The order of the filter (1 less than the length for 'fir').
axis : int, optional
The axis along which to decimate.
This is zero_phase : Prevent phase shift by filtering with ``filtfilt`` instead of ``lfilter``.
Returns
-------
y : ndarray
The down-sampled signal.
"""
if not isinstance(q, int):
raise TypeError("q must be an integer")
if n is None:
n = 8
b, a = cheby1(n, 0.05, 0.8 / q)
y = filtfilt(b, a, x, axis=axis)
sl = [slice(None)] * y.ndim
sl[axis] = slice(None, None, q)
return y[sl]
def rebin(oldData, newLen):
"""
linear transform using scipy interp zoom
Ex: rebin([1,2,3,4,5], [0,0,0,0,0,0,0,0])
scipy.ndimage.interpolation.zoom(input, zoom, output=None, order=3, mode='constant', cval=0.0, prefilter=True)[source]
"""
## there are 1/ratio new bins per old
ratio = newLen / float(len(oldData))
#print 'ratio', ratio, len(newData)
newData = zoom(oldData, ratio,
output=None, order=3, mode='nearest', prefilter=True)
#print 'lens', (len(oldData)) , float(len(newData))
return newData
# Filter requirements.
order = 1
fs = 1024.0 # sample rate, Hz
cutoff = 11.6 # desired cutoff frequency of the filter, Hz
nyquist = fs/2.
# Get the filter coefficients so we can check its frequency response.
b, a = butter_highpass(cutoff, fs, order)
bi, ai = butter_inv_highpass(cutoff, fs, order)
# Plot the frequency response.
plt.subplot(2, 1, 1)
w, h = freqz(b, a, worN=8000)
plt.plot(0.5*fs*w/np.pi, np.abs(h), 'g', label='high pass resp')
wi, hi = freqz(bi, ai, worN=8000)
plt.plot(0.5*fs*wi/np.pi, np.abs(hi), 'r', label='inv. high pass resp')
plt.plot(cutoff, 0.5*np.sqrt(2), 'ko')
plt.axvline(cutoff, color='k')
plt.xlim(0, 0.05*fs)
plt.ylim(0, 5)
plt.title("Lowpass Filter Frequency Response")
plt.xlabel('Frequency [Hz]')
# add the legend in the middle of the plot
leg = plt.legend(fancybox=True)
# set the alpha value of the legend: it will be translucent
leg.get_frame().set_alpha(0.5)
plt.subplots_adjust(hspace=0.35)
plt.grid()
# Demonstrate the use of the filter.
# First make some data to be filtered.
T = 5.0 # seconds
n = int(T * fs) # total number of samples
t = np.linspace(0, T, n, endpoint=False)
# "Noisy" data. We want to recover the 1.2 Hz signal from this.
data = np.sin(1.2*2*np.pi*t)# + 1.5*np.cos(9*2*np.pi*t) + 0.5*np.sin(12.0*2*np.pi*t)
# Filter the data, and plot both the original and filtered signals.
y = butter_highpass_filter(data, cutoff, fs, order)
yi = butter_inv_highpass_filter(y, cutoff, fs, order)
plt.subplot(2, 1, 2)
plt.plot(t, data, 'b-', label='1.2Hz "real" data')
plt.plot(t, y, 'g-', linewidth=2, label='blue box data')
plt.plot(t, yi, 'r--', linewidth=2, label='round-trip data')
plt.xlabel('Time [sec]')
plt.grid()
#plt.legend()
# add the legend in the middle of the plot
leg = plt.legend(fancybox=True)
# set the alpha value of the legend: it will be translucent
leg.get_frame().set_alpha(0.5)
plt.subplots_adjust(hspace=0.35)
plt.show()
-------------- next part --------------
Ray Schumacher
Programmer/Consultant
PO Box 182, Pine Valley, CA 91962
(858)248-7232
http://rjs.org/
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.scipy.org/pipermail/scipy-user/attachments/20161021/47c3d980/attachment-0001.html>
More information about the SciPy-User
mailing list