Added design for Guassian filters.
[debian/gnuradio] / gr-utils / src / python / gr_filter_design.py
1 #!/usr/bin/env python
2
3 try:
4     import scipy
5     from scipy import fftpack
6 except ImportError:
7     print "Please install SciPy to run this script (http://www.scipy.org/)"
8     raise SystemExit, 1
9
10 import sys, os
11 from PyQt4 import Qt, QtCore, QtGui
12 import PyQt4.Qwt5 as Qwt
13 from optparse import OptionParser
14 from gnuradio import gr, blks2, eng_notation
15 from scipy import fftpack
16
17 from pyqt_filter import Ui_MainWindow
18 from pyqt_filter_firlpf import Ui_firlpfForm
19 from pyqt_filter_firhpf import Ui_firhpfForm
20
21 class gr_plot_filter(QtGui.QMainWindow):
22     def __init__(self, qapp, options):
23         QtGui.QWidget.__init__(self, None)
24         self.gui = Ui_MainWindow()
25         self.gui.setupUi(self)
26
27         self.connect(self.gui.filterTypeComboBox,
28                      Qt.SIGNAL("currentIndexChanged(const QString&)"),
29                      self.changed_filter_type)
30         self.connect(self.gui.filterDesignTypeComboBox,
31                      Qt.SIGNAL("currentIndexChanged(const QString&)"),
32                      self.changed_filter_design_type)
33
34         self.connect(self.gui.designButton,
35                      Qt.SIGNAL("released()"),
36                      self.design)
37
38         self.connect(self.gui.tabGroup,
39                      Qt.SIGNAL("currentChanged(int)"),
40                      self.tab_changed)
41
42         self.connect(self.gui.nfftEdit,
43                      Qt.SIGNAL("textEdited(QString)"),
44                      self.nfft_edit_changed)
45
46         self.gui.designButton.setShortcut("Return")
47
48         self.taps = []
49         self.fftdB = []
50         self.fftDeg = []
51         self.groupDelay = []
52         self.nfftpts = int(10000)
53         self.gui.nfftEdit.setText(Qt.QString("%1").arg(self.nfftpts))
54
55         self.gui.lpfPassBandRippleLabel.setVisible(False)
56         self.gui.lpfPassBandRippleEdit.setVisible(False)
57         self.gui.bpfPassBandRippleLabel.setVisible(False)
58         self.gui.bpfPassBandRippleEdit.setVisible(False)
59         self.gui.hpfPassBandRippleLabel.setVisible(False)
60         self.gui.hpfPassBandRippleEdit.setVisible(False)
61                 
62         # Initialize to LPF
63         self.gui.filterTypeWidget.setCurrentWidget(self.gui.firlpfPage)
64
65         # Set Axis labels
66         self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.xBottom,
67                                        "Frequency (Hz)")
68         self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.yLeft,
69                                        "Magnitude (dB)")
70         self.gui.timePlot.setAxisTitle(self.gui.timePlot.xBottom,
71                                        "Tap number")
72         self.gui.timePlot.setAxisTitle(self.gui.timePlot.yLeft,
73                                        "Amplitude")
74         self.gui.phasePlot.setAxisTitle(self.gui.phasePlot.xBottom,
75                                         "Frequency (Hz)")
76         self.gui.phasePlot.setAxisTitle(self.gui.phasePlot.yLeft,
77                                         "Phase (Radians)")
78         self.gui.groupPlot.setAxisTitle(self.gui.groupPlot.xBottom,
79                                         "Frequency (Hz)")
80         self.gui.groupPlot.setAxisTitle(self.gui.groupPlot.yLeft,
81                                         "Delay (sec)")
82
83         # Set up plot curves
84         self.rcurve = Qwt.QwtPlotCurve("Real")
85         self.rcurve.attach(self.gui.timePlot)
86         self.icurve = Qwt.QwtPlotCurve("Imag")
87         self.icurve.attach(self.gui.timePlot)
88
89         self.freqcurve = Qwt.QwtPlotCurve("PSD")
90         self.freqcurve.attach(self.gui.freqPlot)
91
92         self.phasecurve = Qwt.QwtPlotCurve("Phase")
93         self.phasecurve.attach(self.gui.phasePlot)
94
95         self.groupcurve = Qwt.QwtPlotCurve("Group Delay")
96         self.groupcurve.attach(self.gui.groupPlot)
97
98         # Create zoom functionality for the plots
99         self.timeZoomer = Qwt.QwtPlotZoomer(self.gui.timePlot.xBottom,
100                                             self.gui.timePlot.yLeft,
101                                             Qwt.QwtPicker.PointSelection,
102                                             Qwt.QwtPicker.AlwaysOn,
103                                             self.gui.timePlot.canvas())
104
105         self.freqZoomer = Qwt.QwtPlotZoomer(self.gui.freqPlot.xBottom,
106                                             self.gui.freqPlot.yLeft,
107                                             Qwt.QwtPicker.PointSelection,
108                                             Qwt.QwtPicker.AlwaysOn,
109                                             self.gui.freqPlot.canvas())
110
111         self.phaseZoomer = Qwt.QwtPlotZoomer(self.gui.phasePlot.xBottom,
112                                              self.gui.phasePlot.yLeft,
113                                              Qwt.QwtPicker.PointSelection,
114                                              Qwt.QwtPicker.AlwaysOn,
115                                              self.gui.phasePlot.canvas())
116
117         self.groupZoomer = Qwt.QwtPlotZoomer(self.gui.groupPlot.xBottom,
118                                              self.gui.groupPlot.yLeft,
119                                              Qwt.QwtPicker.PointSelection,
120                                              Qwt.QwtPicker.AlwaysOn,
121                                              self.gui.groupPlot.canvas())
122
123         # Set up pen for colors and line width
124         blue = QtGui.qRgb(0x00, 0x00, 0xFF)
125         blueBrush = Qt.QBrush(Qt.QColor(blue))
126         self.freqcurve.setPen(Qt.QPen(blueBrush, 2))
127         self.rcurve.setPen(Qt.QPen(blueBrush, 2))
128         self.phasecurve.setPen(Qt.QPen(blueBrush, 2))
129         self.groupcurve.setPen(Qt.QPen(blueBrush, 2))
130         
131         self.filterWindows = {"Hamming Window" : gr.firdes.WIN_HAMMING,
132                               "Hann Window" : gr.firdes.WIN_HANN,
133                               "Blackman Window" : gr.firdes.WIN_BLACKMAN,
134                               "Rectangular Window" : gr.firdes.WIN_RECTANGULAR,
135                               "Kaiser Window" : gr.firdes.WIN_KAISER,
136                               "Blackman-harris Window" : gr.firdes.WIN_BLACKMAN_hARRIS}
137
138         self.show()
139
140     def changed_filter_type(self, ftype):
141         strftype = str(ftype.toAscii())
142         if(ftype == "Low Pass"):
143             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firlpfPage)
144         elif(ftype == "Band Pass"):
145             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbpfPage)
146         elif(ftype == "Complex Band Pass"):
147             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbpfPage)
148         elif(ftype == "Band Notch"):
149             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbnfPage)
150         elif(ftype == "High Pass"):
151             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firhpfPage)
152         elif(ftype == "Root Raised Cosine"):
153             self.gui.filterTypeWidget.setCurrentWidget(self.gui.rrcPage)
154         elif(ftype == "Gaussian"):
155             self.gui.filterTypeWidget.setCurrentWidget(self.gui.gausPage)
156
157         self.design()
158         
159     def changed_filter_design_type(self, design):
160         if(design == "Equiripple"):
161             self.set_equiripple()
162         else:
163             self.set_windowed()
164             
165         self.design()
166
167     def set_equiripple(self):
168         self.equiripple = True
169         self.gui.lpfPassBandRippleLabel.setVisible(True)
170         self.gui.lpfPassBandRippleEdit.setVisible(True)
171         self.gui.bpfPassBandRippleLabel.setVisible(True)
172         self.gui.bpfPassBandRippleEdit.setVisible(True)
173         self.gui.hpfPassBandRippleLabel.setVisible(True)
174         self.gui.hpfPassBandRippleEdit.setVisible(True)
175         
176     def set_windowed(self):
177         self.equiripple = False
178         self.gui.lpfPassBandRippleLabel.setVisible(False)
179         self.gui.lpfPassBandRippleEdit.setVisible(False)
180         self.gui.bpfPassBandRippleLabel.setVisible(False)
181         self.gui.bpfPassBandRippleEdit.setVisible(False)
182         self.gui.hpfPassBandRippleLabel.setVisible(False)
183         self.gui.hpfPassBandRippleEdit.setVisible(False)
184         
185     def design(self):
186         ret = True
187         fs,r = self.gui.sampleRateEdit.text().toDouble()
188         ret = r and ret
189         gain,r = self.gui.filterGainEdit.text().toDouble()
190         ret = r and ret
191
192         if(ret):
193             winstr = str(self.gui.filterDesignTypeComboBox.currentText().toAscii())
194             ftype = str(self.gui.filterTypeComboBox.currentText().toAscii())
195
196             if(winstr == "Equiripple"):
197                 designer = {"Low Pass" : self.design_opt_lpf,
198                             "Band Pass" : self.design_opt_bpf,
199                             "High Pass" :  self.design_opt_hpf}        
200                 taps,r = designer[ftype](fs, gain)
201
202             else:
203                 designer = {"Low Pass" : self.design_win_lpf,
204                             "Band Pass" : self.design_win_bpf,
205                             "Complex Band Pass" : self.design_win_cbpf,
206                             "Band Notch" : self.design_win_bnf,
207                             "High Pass" :  self.design_win_hpf,
208                             "Root Raised Cosine" :  self.design_win_rrc,
209                             "Gaussian" :  self.design_win_gaus}
210                 wintype = self.filterWindows[winstr]
211                 taps,r = designer[ftype](fs, gain, wintype)
212
213             if(r):
214                 self.taps = scipy.array(taps)
215                 self.get_fft(fs, self.taps, self.nfftpts)
216                 self.update_time_curves()
217                 self.update_freq_curves()
218                 self.update_phase_curves()
219                 self.update_group_curves()
220
221     # Filter design functions using a window
222     def design_win_lpf(self, fs, gain, wintype):
223         ret = True
224         pb,r = self.gui.endofLpfPassBandEdit.text().toDouble()
225         ret = r and ret
226         sb,r = self.gui.startofLpfStopBandEdit.text().toDouble()
227         ret = r and ret
228         atten,r = self.gui.lpfStopBandAttenEdit.text().toDouble()
229         ret = r and ret
230
231         if(ret):
232             tb = sb - pb
233             
234             taps = gr.firdes.low_pass_2(gain, fs, pb, tb,
235                                         atten, wintype)
236             return (taps, ret)
237         else:
238             return ([], ret)
239     
240     def design_win_bpf(self, fs, gain, wintype):
241         ret = True
242         pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
243         ret = r and ret
244         pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
245         ret = r and ret
246         tb,r  = self.gui.bpfTransitionEdit.text().toDouble()
247         ret = r and ret
248         atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
249         ret = r and ret
250
251         if(r):
252             taps = gr.firdes.band_pass_2(gain, fs, pb1, pb2, tb,
253                                          atten, wintype)
254             return (taps,r)
255         else:
256             return ([],r)
257
258     def design_win_cbpf(self, fs, gain, wintype):
259         ret = True
260         pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
261         ret = r and ret
262         pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
263         ret = r and ret
264         tb,r  = self.gui.bpfTransitionEdit.text().toDouble()
265         ret = r and ret
266         atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
267         ret = r and ret
268
269         if(r):
270             taps = gr.firdes.complex_band_pass_2(gain, fs, pb1, pb2, tb,
271                                                  atten, wintype)
272             return (taps,r)
273         else:
274             return ([],r)
275
276     def design_win_bnf(self, fs, gain, wintype):
277         ret = True
278         pb1,r = self.gui.startofBnfStopBandEdit.text().toDouble()
279         ret = r and ret
280         pb2,r = self.gui.endofBnfStopBandEdit.text().toDouble()
281         ret = r and ret
282         tb,r  = self.gui.bnfTransitionEdit.text().toDouble()
283         ret = r and ret
284         atten,r = self.gui.bnfStopBandAttenEdit.text().toDouble()
285         ret = r and ret
286
287         if(r):
288             taps = gr.firdes.band_reject_2(gain, fs, pb1, pb2, tb,
289                                            atten, wintype)
290             return (taps,r)
291         else:
292             return ([],r)
293
294     def design_win_hpf(self, fs, gain, wintype):
295         ret = True
296         sb,r = self.gui.endofHpfStopBandEdit.text().toDouble()
297         ret = r and ret
298         pb,r = self.gui.startofHpfPassBandEdit.text().toDouble()
299         ret = r and ret
300         atten,r = self.gui.hpfStopBandAttenEdit.text().toDouble()
301         ret = r and ret
302
303         if(r):
304             tb = pb - sb
305             taps = gr.firdes.high_pass_2(gain, fs, pb, tb,
306                                          atten, wintype)            
307             return (taps,r)
308         else:
309             return ([],r)
310
311     def design_win_rrc(self, fs, gain, wintype):
312         ret = True
313         sr,r = self.gui.rrcSymbolRateEdit.text().toDouble()
314         ret = r and ret
315         alpha,r = self.gui.rrcAlphaEdit.text().toDouble()
316         ret = r and ret
317         ntaps,r = self.gui.rrcNumTapsEdit.text().toInt()
318         ret = r and ret
319
320         if(r):
321             taps = gr.firdes.root_raised_cosine(gain, fs, sr,
322                                                 alpha, ntaps)
323             return (taps,r)
324         else:
325             return ([],r)
326
327     def design_win_gaus(self, fs, gain, wintype):
328         ret = True
329         sr,r = self.gui.gausSymbolRateEdit.text().toDouble()
330         ret = r and ret
331         bt,r = self.gui.gausBTEdit.text().toDouble()
332         ret = r and ret
333         ntaps,r = self.gui.gausNumTapsEdit.text().toInt()
334         ret = r and ret
335
336         if(r):
337             spb = fs / sr
338             taps = gr.firdes.gaussian(gain, spb, bt, ntaps)
339             return (taps,r)
340         else:
341             return ([],r)
342
343     # Design Functions for Equiripple Filters
344     def design_opt_lpf(self, fs, gain, wintype=None):
345         ret = True
346         pb,r = self.gui.endofLpfPassBandEdit.text().toDouble()
347         ret = r and ret
348         sb,r = self.gui.startofLpfStopBandEdit.text().toDouble()
349         ret = r and ret
350         atten,r = self.gui.lpfStopBandAttenEdit.text().toDouble()
351         ret = r and ret
352         ripple,r = self.gui.lpfPassBandRippleEdit.text().toDouble()
353         ret = r and ret
354
355         if(ret):
356             taps = blks2.optfir.low_pass(gain, fs, pb, sb,
357                                          ripple, atten)
358             return (taps, ret)
359         else:
360             return ([], ret)
361     
362     def design_opt_bpf(self, fs, gain, wintype=None):
363         ret = True
364         pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
365         ret = r and ret
366         pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
367         ret = r and ret
368         tb,r  = self.gui.bpfTransitionEdit.text().toDouble()
369         ret = r and ret
370         atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
371         ret = r and ret
372         ripple,r = self.gui.bpfPassBandRippleEdit.text().toDouble()
373         ret = r and ret
374
375         if(r):
376             sb1 = pb1 - tb
377             sb2 = pb2 + tb
378             taps = blks2.optfir.band_pass(gain, fs, sb1, pb1, pb2, sb2,
379                                           ripple, atten)
380             return (taps,r)
381         else:
382             return ([],r)
383
384     def design_opt_hpf(self, fs, gain, wintype=None):
385         ret = True
386         sb,r = self.gui.endofHpfStopBandEdit.text().toDouble()
387         ret = r and ret
388         pb,r = self.gui.startofHpfPassBandEdit.text().toDouble()
389         ret = r and ret
390         atten,r = self.gui.hpfStopBandAttenEdit.text().toDouble()
391         ret = r and ret
392         ripple,r = self.gui.hpfPassBandRippleEdit.text().toDouble()
393         ret = r and ret
394
395         if(r):
396             taps = blks2.optfir.high_pass(gain, fs, sb, pb,
397                                           atten, ripple)
398             return (taps,r)
399         else:
400             return ([],r)
401
402     def nfft_edit_changed(self, nfft):
403         infft,r = nfft.toInt()
404         if(r and (infft != self.nfftpts)):
405             self.nfftpts = infft
406             self.update_freq_curves()
407
408     def tab_changed(self, tab):
409         if(tab == 0):
410             self.update_freq_curves()
411         if(tab == 1):
412             self.update_time_curves()
413         if(tab == 2):
414             self.update_phase_curves()
415         if(tab == 3):
416             self.update_group_curves()
417         
418     def get_fft(self, fs, taps, Npts):
419         Ts = 1.0/fs
420         fftpts = fftpack.fft(taps, Npts)
421         self.freq = scipy.arange(0, fs, 1.0/(Npts*Ts))        
422         self.fftdB = 20.0*scipy.log10(abs(fftpts))
423         self.fftDeg = scipy.unwrap(scipy.angle(fftpts))
424         self.groupDelay = -scipy.diff(self.fftDeg)
425         
426     def update_time_curves(self):
427         ntaps = len(self.taps)
428         if(ntaps > 0):
429             if(type(self.taps[0]) == scipy.complex128):
430                 self.rcurve.setData(scipy.arange(ntaps), self.taps.real)
431                 self.icurve.setData(scipy.arange(ntaps), self.taps.imag)
432             else:
433                 self.rcurve.setData(scipy.arange(ntaps), self.taps)
434
435             # Reset the x-axis to the new time scale
436             ymax = 1.5 * max(self.taps)
437             ymin = 1.5 * min(self.taps)
438             self.gui.timePlot.setAxisScale(self.gui.timePlot.xBottom,
439                                            0, ntaps)
440             self.gui.timePlot.setAxisScale(self.gui.timePlot.yLeft,
441                                            ymin, ymax)
442             
443             # Set the zoomer base to unzoom to the new axis
444             self.timeZoomer.setZoomBase()
445             
446             self.gui.timePlot.replot()
447         
448     def update_freq_curves(self):
449         npts = len(self.fftdB)
450         if(npts > 0):
451             self.freqcurve.setData(self.freq, self.fftdB)
452             
453             # Reset the x-axis to the new time scale
454             ymax = 1.5 * max(self.fftdB[0:npts/2])
455             ymin = 1.1 * min(self.fftdB[0:npts/2])
456             xmax = self.freq[npts/2]
457             xmin = self.freq[0]
458             self.gui.freqPlot.setAxisScale(self.gui.freqPlot.xBottom,
459                                            xmin, xmax)
460             self.gui.freqPlot.setAxisScale(self.gui.freqPlot.yLeft,
461                                            ymin, ymax)
462             
463             # Set the zoomer base to unzoom to the new axis
464             self.freqZoomer.setZoomBase()
465             
466             self.gui.freqPlot.replot()
467
468
469     def update_phase_curves(self):
470         npts = len(self.fftDeg)
471         if(npts > 0):
472             self.phasecurve.setData(self.freq, self.fftDeg)
473             
474             # Reset the x-axis to the new time scale
475             ymax = 1.5 * max(self.fftDeg[0:npts/2])
476             ymin = 1.1 * min(self.fftDeg[0:npts/2])
477             xmax = self.freq[npts/2]
478             xmin = self.freq[0]
479             self.gui.phasePlot.setAxisScale(self.gui.phasePlot.xBottom,
480                                             xmin, xmax)
481             self.gui.phasePlot.setAxisScale(self.gui.phasePlot.yLeft,
482                                             ymin, ymax)
483             
484             # Set the zoomer base to unzoom to the new axis
485             self.phaseZoomer.setZoomBase()
486             
487             self.gui.phasePlot.replot()
488
489     def update_group_curves(self):
490         npts = len(self.groupDelay)
491         if(npts > 0):
492             self.groupcurve.setData(self.freq, self.groupDelay)
493             
494             # Reset the x-axis to the new time scale
495             ymax = 1.5 * max(self.groupDelay[0:npts/2])
496             ymin = 1.1 * min(self.groupDelay[0:npts/2])
497             xmax = self.freq[npts/2]
498             xmin = self.freq[0]
499             self.gui.groupPlot.setAxisScale(self.gui.groupPlot.xBottom,
500                                             xmin, xmax)
501             self.gui.groupPlot.setAxisScale(self.gui.groupPlot.yLeft,
502                                             ymin, ymax)
503             
504             # Set the zoomer base to unzoom to the new axis
505             self.groupZoomer.setZoomBase()
506             
507             self.gui.groupPlot.replot()
508
509
510 def setup_options():
511     usage="%prog: [options] (input_filename)"
512     description = ""
513
514     parser = OptionParser(conflict_handler="resolve",
515                           usage=usage, description=description)
516     return parser
517
518 def main(args):
519     parser = setup_options()
520     (options, args) = parser.parse_args ()
521
522     app = Qt.QApplication(args)
523     gplt = gr_plot_filter(app, options)
524     app.exec_()
525
526 if __name__ == '__main__':
527     main(sys.argv)
528