Skip to main content

Finite Impulse Response Filters

  • Chapter
  • First Online:
Book cover Python for Signal Processing
  • 11k Accesses

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.

This is a preview of subscription content, log in via an institution to check access.

Access this chapter

Chapter
USD 29.95
Price excludes VAT (USA)
  • Available as PDF
  • Read on any device
  • Instant download
  • Own it forever
eBook
USD 84.99
Price excludes VAT (USA)
  • Available as EPUB and PDF
  • Read on any device
  • Instant download
  • Own it forever
Softcover Book
USD 109.99
Price excludes VAT (USA)
  • Compact, lightweight edition
  • Dispatched in 3 to 5 business days
  • Free shipping worldwide - see info
Hardcover Book
USD 159.99
Price excludes VAT (USA)
  • Durable hardcover edition
  • Dispatched in 3 to 5 business days
  • Free shipping worldwide - see info

Tax calculation will be finalised at checkout

Purchases are for personal use only

Institutional subscriptions

Author information

Authors and Affiliations

Authors

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

Reprints 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)

Publish with us

Policies and ethics