Demonstration of FIR filtering using scipy.signal. Part of a lecture given in ELEG 306 Digital Signal Processing, Oct 6, 2014.
Charles Boncelet, boncelet@udel.edu
%pylab inline
import scipy.signal as sig
x = randn(1200)
plot(x)
#sig.firwin?
h = sig.firwin(31,0.1)
plot(h)
The connected dots plot above isn't as informative as the stem plot below:
stem(range(len(h)),h)
Below we plot the magnitude response of the filter versus frequency (only versus positive frequencies--the negative half is the mirror image).
Notice, this is a linear phase filter whose sidelobes are about 120 dB below the passband. Try building an analog filter like this!
w, v = sig.freqz(h)
plot(w,20*log(abs(v)))
y=convolve(x,h)
plot(y)
Below, we tighten the filter to have a passband of 0.01\(\pi\) wide (ok, as well as we can do with 51 taps--more taps results in a response closer to ideal at the expense of more delay and more computation).
h2 = sig.firwin(51,0.01)
stem(range(len(h2)),h2)
Notice how the signal is much smoother now. This is clearer in the scatter plot below.
One easily missed point: The y-axis autoscales, resulting in plots of about the same "visual magnitude". But look carefully: the original plot of x has a scale from -4 to 4 while this one goes from about -0.4 to 0.4. The signal has gotten much smaller. Why? Because the filter has removed about 99% of the energy!
y2 = convolve(x,h2)
plot(y2)
Show the dots. Notice how close consecutive dots are to each other. Also, notice how the dots have "momentum". This is a characteristic of a low pass signal like this.
scatter(range(len(y2)),y2,s=2)
Notice how the bandpass filter's impulse response matches the intended signal. It oscillates slowly between positive and negative values, much like a sinusoid of frequency 0.15\(\pi\) does.
hbp = sig.firwin(51, [0.1,0.2], pass_zero=False)
stem(range(len(hbp)),hbp)
w, v = sig.freqz(hbp)
plot(w, 20*log(abs(v)))
ybp = convolve(x,hbp)
plot(ybp)
stem(range(len(ybp)),ybp,s=1)
This plot is a total mess. It has too many points and the dots are too large. Let's fix both:
segment = ybp[100:200]
stem(range(len(segment)),segment,s=2)
Now we can see what happened. The signal looks like a sinusoid, oscillating slowly between positive and negative values. It isn't a perfect sinusoid for two reasons: 1) the signal is random (we starting with white Gaussian noise) and 2) the output has components at all frequencies within (and near) the passband.
hhp = sig.firwin(51, 0.9, pass_zero=False)
w, v = sig.freqz(hhp)
plot(w, 20*log(abs(v)))
yhp = convolve(x,hhp)
plot(yhp)
plot(yhp[500:600])
stem(yhp[500:600])
Let's do some pole-zero plots. Since the filters have no poles, we only have to worry about the zeros.
z, p, k = sig.tf2zpk(h,1)
z
theta = linspace(-pi,pi,201)
plot(cos(theta),sin(theta))
fig = scatter(real(z),imag(z))
axes().set_aspect('equal')
z, p, k = sig.tf2zpk(hbp,[1])
theta = linspace(-pi,pi,201)
plot(cos(theta),sin(theta))
fig = scatter(real(z),imag(z))
axes().set_aspect('equal')
Remember, zeros can be outside the unit circle without affecting stability.