Abstract
Filtering means preserving certain favored signal frequencies without distorting them while simultaneously suppressing others. Although there are many, many approaches to digital filtering, we focus on Finite Impulse Response (FIR) filters. As the name suggests, these filters have no feedback loops, which means that they stop producing output when the input runs out. These are very popular in practice, with blazing-fast on-chip implementations, and easy-to-understand design specifications. This section introduces the main concepts of FIR filter design.
Keywords
These keywords were added by machine and not by the authors. This process is experimental and the keywords may be updated as the learning algorithm improves.
This is a preview of subscription content, log in via an institution.
Buying options
Tax calculation will be finalised at checkout
Purchases are for personal use only
Learn about institutional subscriptionsAuthor information
Authors and Affiliations
Appendix
Appendix
Listing 5.1: Listing for Fig. 5.2. The signal.filter function implements Eq. 5.1.
1 from scipy import signal
2
3 Ns=30 # length of input sequence
4 n= arange(Ns) # sample index
5 x = cos(arange(Ns)*pi/2.)
6 y= signal.lfilter([1/2.,1/2.],1,x)
7
8 fig,ax = subplots(1,1)
9 fig.set_size_inches(12,5)
10
11 ax.stem(n,x,label=’input’,basefmt=’b-’)
12 ax.plot(n,x,’:’)
13 ax.stem(n[1:],y[:-1],markerfmt=’ro’,linefmt=’r-’,label=’output’)
14 ax.plot(n[1:],y[:-1],’r:’)
15 ax.set_ylim(ymin=-1.1,ymax=1.1)
16 ax.set_xlabel("n",fontsize=22)
17 ax.legend(loc=0,fontsize=18)
18 ax.set_xticks(n)
19 ax.set_xlim(xmin=-1.1,xmax=20)
20 ax.set_ylabel("Amplitude",fontsize=22);
Listing 5.2: Listing for Fig. 5.3. The signal.freqz function computes the filter’s magnitude (|H(ω)|) and phase response given the filter coefficents.
1 from matplotlib import gridspec
2
3 fig=figure()
4 #fig.set_size_inches((8,5))
5
6 gs = gridspec.GridSpec(2,2)
7 # add vertical and horizontal space
8 gs.update(wspace=0.5, hspace=0.5)
9
10 ax = fig.add_subplot(subplot(gs[0,0]))
11
12 ma_length = 8 # moving average filter length
13 w,h=signal.freqz(ones(ma_length)/ma_length,1)
14 ax.plot(w,20*log10(abs(h)))
15 ax.set_ylabel(r"$ 20 \log_{10}|H(\omega)| $",fontsize=18)
16 ax.set_xlabel(r"$\omega$",fontsize=18)
17 ax.vlines(pi/3,-25,0,linestyles=’:’,color=’r’,lw=3.)
18 ax.set_ylim(ymin=-25)
19
20 ax = fig.add_subplot(subplot(gs[0,1]))
21 ax.plot(w,angle(h,deg=True))
22 ax.set_xlabel(r’$\omega$’,fontsize=18)
23 ax.set_ylabel(r"$\phi $ (deg)",fontsize=16)
24 ax.set_xlim(xmax = pi)
25 ax.set_ylim(ymin=-180,ymax=180)
26 ax.vlines(pi/3,-180,180,linestyles=’:’,color=’r’,lw=3.)
27 ax = fig.add_subplot(subplot(gs[1,:]))
28 Ns=30
29 n= arange(Ns)
30 x = cos(arange(Ns)*pi/3.)
31 y= signal.lfilter(ones(ma_length)/ma_length,1,x)
32
33 ax.stem(n,x,label=’input’,basefmt=’b-’)
34 ax.plot(n,x,’:’)
35 ax.stem(n[ma_length-1:],y[:-ma_length+1],
36 markerfmt=’ro’,
37 linefmt=’r-’,
38 label=’output’)
39 ax.plot(n[ma_length-1:],y[:-ma_length+1],’r:’)
40 ax.set_xlim(xmin=-1.1)
41 ax.set_ylim(ymin=-1.1,ymax=1.1)
42 ax.set_xlabel("n",fontsize=18)
43 ax.set_xticks(n)
44 ax.legend(loc=0)
45 ax.set_ylabel("Amplitude",fontsize=18);
Listing 5.3: Listing corresponding to Fig. 5.1. The angle function returns the angle of the complex number. The deg=True option returns degrees instead of radians.
1 from scipy import signal
2
3 fig, axs = subplots(2,1,sharex=True)
4 subplots_adjust(hspace = .2)
5 fig.set_size_inches((5,5))
6
7 ax=axs[0]
8 w,h=signal.freqz([1/2., 1/2.],1) # Compute impulse response
9 ax.plot(w,20*log10(abs(h)))
10 ax.set_ylabel(r"$20 \log_{10} |H(\omega)| $",fontsize=18)
11 ax.grid()
12
13 ax=axs[1]
14 ax.plot(w,angle(h,deg=True))
15 ax.set_xlabel(r’$\omega$’,fontsize=18)
16 ax.set_ylabel(r"$\phi $ (deg)",fontsize=18)
17 ax.set_xlim(xmax = pi)
18 ax.grid()
Listing 5.4: Listing corresponding to Fig. 5.4. The fftshift function reorganizes the DFT so that the frequencies are centered on zero (\(f \in [-f_{s}/2,f_{s}/2]\)) instead of on f s ∕2 (f ∈ [0,f s ]).
1 from scipy import signal
2 from numpy import fft
3
4 wc = pi/4
5 M=20
6 N = 512 # DFT size
7 n = arange(-M,M)
8 h = wc/pi * sinc(wc*(n)/pi) # see definition of np.sinc()
9
10 w,Hh = signal.freqz(h,1,whole=True, worN=N) # get entire frequency domain
11 wx = fft.fftfreq(len(w)) # shift to center for plotting
12
13 fig,axs = subplots(3,1)
14 fig.set_size_inches((8,8))
15 subplots_adjust(hspace=0.3)
16 ax=axs[0]
17 ax.stem(n+M,h,basefmt=’b-’)
18 ax.set_xlabel("$n$",fontsize=22)
19 ax.set_ylabel("$h_n$",fontsize=22)
20
21 ax=axs[1]
22 ax.plot(w-pi,abs(fft.fftshift(Hh)))
23 ax.axis(xmax=pi/2,xmin=-pi/2)
24 ax.vlines([-wc,wc],0,1.2,color=’g’,lw=2.,linestyle=’--’,)
25 ax.hlines(1,-pi,pi,color=’g’,lw=2.,linestyle=’--’,)
26 ax.set_xlabel(r"$\omega$",fontsize=22)
27 ax.set_ylabel(r"$|H(\omega)| $",fontsize=22)
28
29 ax=axs[2]
30 ax.plot(w-pi,20*log10(abs(fft.fftshift(Hh))))
31 ax.axis(ymin=-40,xmax=pi/2,xmin=-pi/2)
32 ax.vlines([-wc,wc],10,-40,color=’g’,lw=2.,linestyle=’--’,)
33 ax.hlines(0,-pi,pi,color=’g’,lw=2.,linestyle=’--’,)
34 ax.set_xlabel(r"$\omega$",fontsize=22)
35 ax.set_ylabel(r"$20\log_{10}|H(\omega)| $",fontsize=18)
Listing 5.5: Listing corresponding to Fig. 5.5 that shows the Gibbs phenomenon at the edge of the filter’s passband. The set_y function moves the title a bit up so the fonts can be easily seen.
1 fig,ax = subplots()
2 fig.set_size_inches(6,3)
3
4 k=arange(M)
5 omega = linspace(0,pi,100)
6
7 ax.plot(omega,(sin(k*omega[:,None]+k*wc)
8 -sin(k*omega[:,None]-k*wc)).sum(axis=1))
9 ax.set_ylabel(r"$Y_{re}(\omega)$",fontsize=18)
10 ax.grid()
11 t=ax.set_title(r"\(\omega_c = \pi/4\)",fontsize=22)
12 t.set_y(1.03) # scoot title up a bit
13 ax.set_xlabel(r"$\omega$",fontsize=22)
14 # setup xticks and labels for LaTeX
15 ax.set_xticks([0, pi/4,pi/2.,3*pi/4, pi,])
16 ax.set_xticklabels([’$0$’,r’\(\frac{\pi}{4}\)’,r’\(\frac{\pi}{2}\)’,
17 r’\(\frac{3\pi}{4}\)’, r’$\pi$’],fontsize=18)
18 ax.set_xlim(xmax=pi)
19 ax.annotate("Gibbs phenomenon",xy=(pi/4,10),fontsize=14,
20 xytext=(20,0),
21 textcoords=’offset points’,
22 arrowprops={’facecolor’:’b’,’arrowstyle’:’->’})
Listing 5.6: Listing corresponding to Fig. 5.6. The fftfreq function generates the DFT sample frequencies.
1 wc = pi/4
2
3 M=20
4
5 N = 512 # DFT size
6 n = arange(-M,M)
7 win = signal.hamming(len(n))
8 h = wc/pi * sinc(wc*(n)/pi)*win # see definition of np.sinc()
9
10 w,Hh = signal.freqz(h,1,whole=True, worN=N) # get entire frequency domain
11 wx = fft.fftfreq(len(w)) # shift to center for plotting
12
13 fig,axs = subplots(3,1)
14 fig.set_size_inches((8,8))
15 subplots_adjust(hspace=0.3)
16
17 ax=axs[0]
18 ax.stem(n+M,h,basefmt=’b-’)
19 ax.set_xlabel("$n$",fontsize=24)
20 ax.set_ylabel("$h_n$",fontsize=24)
21 ax=axs[1]
22 ax.plot(w-pi,abs(fft.fftshift(Hh)))
23 ax.axis(xmax=pi/2,xmin=-pi/2)
24 ax.vlines([-wc,wc],0,1.2,color=’g’,lw=2.,linestyle=’--’,)
25 ax.hlines(1,-pi,pi,color=’g’,lw=2.,linestyle=’--’,)
26 ax.set_xlabel(r"$\omega$",fontsize=22)
27 ax.set_ylabel(r"$|H(\omega)| $",fontsize=22)
28
29 ax=axs[2]
30 ax.plot(w-pi,20*log10(abs(fft.fftshift(Hh))))
31 ax.axis(ymin=-80,xmax=pi/2,xmin=-pi/2)
32 ax.vlines([-wc,wc],10,-80,color=’g’,lw=2.,linestyle=’--’,)
33 ax.hlines(0,-pi,pi,color=’g’,lw=2.,linestyle=’--’,)
34 ax.set_xlabel(r"$\omega$",fontsize=22)
35 ax.set_ylabel(r"$20\log_{10}|H(\omega)| $",fontsize=18)
Listing 5.7: Listing showing the filter specification for our example.
1 Ns =300 # number of samples
2 N = 1024 # DFT size
3
4 fs = 1e3 # sample rate in Hz
5 fpass = 100 # in Hz
6 fstop = 150 # in Hz
7 delta = 60 # in dB, desired attenuation in stopband
Listing 5.8: Listing corresponding to Fig. 5.7. The bbox puts the text in the foreground of a colored box.
1 from matplotlib.patches import Rectangle
2
3 M,beta= signal.fir_filter_design.kaiserord(delta, (fstop-fpass)/(fs/2.))
4
5 hn = signal.firwin(M,(fstop+fpass)/2.,window=(’kaiser’,beta),nyq=fs/2.)
6 w,H = signal.freqz(hn) # frequency response
7
8 fig,ax = subplots()
9 fig.set_size_inches((8,3))
10
11 ax.plot(w/pi*fs/2.,20*log10(abs(H)))
12 ax.set_xlabel("Frequency (Hz)",fontsize=16)
13 ax.set_ylabel(r"$20\log_{10} |H(f)| $",fontsize=22)
14 ymin,ymax = -80,5
15 ax.axis(ymin = ymin,ymax=ymax)
16 ax.add_patch(Rectangle((0,ymin),width=fpass,
17 height=ymax-ymin,
18 color=’g’,alpha=0.3))
19 ax.add_patch(Rectangle((fpass,ymin),width=fstop-fpass,
20 height=ymax-ymin,
21 color=’r’,alpha=0.3))
22 ax.add_patch(Rectangle((fstop,ymin),width=fs/2-fstop,
23 height=ymax-ymin,
24 color=’y’,alpha=0.3))
25 ax.set_title("Number of taps=%d"%M)
26 ax.text(10,-15,’passband’,fontsize=14,bbox=dict(color=’white’))
27 ax.text(200,-15,’stopband’,fontsize=16,bbox=dict(color=’white’))
28 ax.grid()
Listing 5.9: Listing corresponding to Fig. 5.8.
1 from numpy import fft
2
3 t = arange(0,Ns)/fs
4 x = cos(2*pi*30*t)+cos(2*pi*200*t)
5 X = fft.fft(x,N)
6
7 y=signal.lfilter(hn,1,x)
8 Y = fft.fft(y,N)
9
10 fig,ax = subplots()
11 fig.set_size_inches((10,4))
12 ax.plot(arange(N)/N*fs,20*log10(abs(X)),’r-’,label=’input’)
13 ax.plot(arange(N)/N*fs,20*log10(abs(Y)),’g-’,label=’output’)
14 ax.set_xlim(xmax = fs/2)
15 ax.set_ylim(ymin=-20)
16 ax.set_ylabel(r’dB’,fontsize=22)
17 ax.set_xlabel("Frequency (Hz)",fontsize=18)
18 ax.grid()
19 ax.annotate(’attenuated in\nstopband’,fontsize=16,xy=(200,32),
20 xytext=(50,3),textcoords=’offset points’,
21 arrowprops=dict(arrowstyle=’->’,lw=3),
22 )
23 ax.legend(loc=0,fontsize=16);
Listing 5.10: Listing corresponding to Fig. 5.9. The remez function computes the optimal filter coefficients for the Parks-McClellan method. The numtaps argument which is M in our notation, the bands argument is a sequence of passband/stopband edges in normalized frequency (i.e. scaled by f s ∕2), the desired argument is a Numpy array (or other array-like iterable) that is half the length of the bands argument and contains the desired gain in each of the specified bands. The next argument is the optional weight which is array-like, half the length of the bands argument, and provides the relative weighting for the passband/stopband. The Hz argument (default=1) is the sampling frequency in the same units as the bands. The next optional argument is the type of filter response in each of the bands (i.e. bandpass, hilbert). The default is bandpass filter. The remaining arguments of the function call have to do with the internal operation of the iterative algorithm.
1 from matplotlib.patches import Rectangle
2 from scipy import signal
3
4 fs = 1e3 # sample rate in Hz
5 M = 20
6 fpass = 100 # in Hz
7 fstop = 150 # in Hz
8
9 hn = signal.remez(M,
10 array([0, fpass, fstop, fs])/2., # scaled passband, and stop
11 band [1,0], # low pass filter
12 Hz = fs, # sampling frequency
13 )
14
15 w,H=signal.freqz(hn,1) # frequency response
16
17 def apply_plot_overlay():
18 ’convenience function to illustrate stop/passband in frequency response
19 plot’
20 ax.plot(w/pi*(fs/2),20*log10(abs(H)),label=’Filter response’)
21 ax.set_ylim(ymax=5)
22 ax.vlines(100,*ax.get_ylim(),color=’r’)
23 ax.vlines(150,*ax.get_ylim(),color=’r’)
24 ax.set_ylim(ymin=-40)
25 ax.set_xlabel("Frequency (Hz)",fontsize=18)
26 ax.set_ylabel(r"$20\log_{10}|H(f)|$",fontsize=22)
27 ax.add_patch(Rectangle((0,-40),width=fpass,height=45,color=’g’,alpha=0.3))
28 ax.add_patch(Rectangle((fpass,-40),width=fstop-fpass,height=45,color=’r’,
29 alpha=0.3))
30 ax.add_patch(Rectangle((fstop,-40),width=fs/2-fstop,height=45,color=’y’,
31 alpha=0.3))
32 ax.text(10,-5,’passband’,fontsize=14,bbox=dict(color=’white’))
33 ax.text(200,-5,’stopband’,fontsize=16,bbox=dict(color=’white’))
34 ax.grid()
35
36 fig,ax = subplots()
37 fig.set_size_inches((7,3))
38 apply_plot_overlay()
39 ax.set_title(’%d-tap Parks-McClellan Filter’%M)
Listing 5.11: Listing corresponding to Fig. 5.10.
1 M = 40 # double filter length
2 hn = signal.remez(M,
3 array([0, fpass, fstop, fs])/2., # scaled passband, and stop
4 band [1,0], # low pass filter
5 Hz = fs, # sampling frequency
6 )
7
8 w,H=signal.freqz(hn,1) # frequency response
9 fig,ax = subplots()
10 fig.set_size_inches((7,3))
11 apply_plot_overlay()
12 ax.set_title(’%d-tap Parks-McClellan Filter’%M)
Listing 5.12: Listing corresponding to Fig. 5.11. By using the weight option in the remez function, we influenced the algorithm to penalize errors in the passband more than errors in the stopband.
1 hn = signal.remez(M,
2 array([0, fpass, fstop, fs])/2., # scaled passband, and stop
3 band [1,0], # low pass filter
4 weight=[100,1], # passband 100 times more important than
5 stopband Hz = fs, # sampling frequency
6 )
7
8 w,H=signal.freqz(hn,1) # frequency response
9 fig,ax = subplots()
10 fig.set_size_inches((7,3))
11 apply_plot_overlay()
12 ax.set_title(’Weighted %d-tap Parks-McClellan Filter’%M)
1 Ns =300 # number of samples
2 N = 1024 # DFT size
3 t = arange(0,Ns)/fs
4
5 x = cos(2*pi*30*t)+cos(2*pi*200*t)
6 #x = x*signal.hamming(Ns) # try windowing also!
7 X = fft.fft(x,N)
8
9 y=signal.lfilter(hn,1,x)
10 Y = fft.fft(y,N)
11
12 fig,ax = subplots()
13 fig.set_size_inches((10,4))
14 apply_plot_overlay()
15 ax.set_ylim(ymin=-30,ymax=7)
16 ax.legend(loc=’upper left’,fontsize=16)
Listing 5.13: Listing corresponding to Fig. 5.12.
1
2 ax2 = ax.twinx()
3 ax2.plot(arange(N)/N*fs,20*log10(abs(X)),’r-’,label=’filter input’)
4 ax2.plot(arange(N)/N*fs,20*log10(abs(Y)),’g-’,label=’filter output’)
5 #ax2.plot(arange(N)/N*fs,20*log10(abs(X)*abs(H)),’g:’,lw=2.,label=’YY’)
6 ax2.set_xlim(xmax = fs/2)
7 ax2.set_ylim(ymin=-20)
8 ax2.set_ylabel(r’$20\log|Y(f)|$’,fontsize=22)
9 ax2.legend(loc=0,fontsize=16);
10
11 fig,ax = subplots()
12 fig.set_size_inches((10,4))
13 ax.plot(arange(N)/N*fs,20*log10(abs(X)),’r-’,label=’input’)
14 ax.plot(arange(N)/N*fs,20*log10(abs(Y)),’g-’,label=’output’)
15 ax.set_xlim(xmax = fs/2)
16 ax.set_ylim(ymin=-20)
17 ax.set_ylabel(’dB’,fontsize=22)
18 ax.set_xlabel("Frequency (Hz)",fontsize=18)
19 ax.grid()
20 ax.annotate(’stopband\nattenuation’,fontsize=16,xy=(200,32),
21 xytext=(50,3),textcoords=’offset points’,
22 arrowprops=dict(arrowstyle=’->’,lw=3),
23 )
24 ax.legend(loc=0,fontsize=16);
Listing 5.14: Listing corresponding to Fig. 5.13.
1 x_pass = cos(2*pi*30*t) # passband signal
2 x_stop = cos(2*pi*200*t) # stopband signal
3 x = x_pass + x_stop
4 y=signal.lfilter(hn,1,x)
5
6 fig,axs = subplots(3,1,sharey=True,sharex=True)
7 fig.set_size_inches((10,5))
8
9 ax=axs[0]
10 ax.plot(t,x_pass,label=’passband signal’,color=’k’)
11 ax.plot(t,x_stop,label=’stopband signal’)
12 ax.legend(loc=0,fontsize=16)
13
14 ax=axs[1]
15 ax.plot(t,x,label=’filter input=passband + stopband signals’,color=’r’)
16 ax.legend(loc=0,fontsize=16)
17
18 ax=axs[2]
19 ax.plot(t,x_pass,label=’passband signal’,color=’k’)
20 ax.plot(t,y,label=’filter output’,color=’g’)
21 ax.set_xlabel("Time (sec)",fontsize=18)
22 ax.legend(loc=0,fontsize=16);
Rights and permissions
Copyright information
© 2014 Springer International Publishing Switzerland
About this chapter
Cite this chapter
Unpingco, J. (2014). Finite Impulse Response Filters. In: Python for Signal Processing. Springer, Cham. https://doi.org/10.1007/978-3-319-01342-8_5
Download citation
DOI: https://doi.org/10.1007/978-3-319-01342-8_5
Published:
Publisher Name: Springer, Cham
Print ISBN: 978-3-319-01341-1
Online ISBN: 978-3-319-01342-8
eBook Packages: EngineeringEngineering (R0)