Adding display for the number of taps in the filter.
[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
19 class gr_plot_filter(QtGui.QMainWindow):
20     def __init__(self, qapp, options):
21         QtGui.QWidget.__init__(self, None)
22         self.gui = Ui_MainWindow()
23         self.gui.setupUi(self)
24
25         self.connect(self.gui.filterTypeComboBox,
26                      Qt.SIGNAL("currentIndexChanged(const QString&)"),
27                      self.changed_filter_type)
28         self.connect(self.gui.filterDesignTypeComboBox,
29                      Qt.SIGNAL("currentIndexChanged(const QString&)"),
30                      self.changed_filter_design_type)
31
32         self.connect(self.gui.designButton,
33                      Qt.SIGNAL("released()"),
34                      self.design)
35
36         self.connect(self.gui.tabGroup,
37                      Qt.SIGNAL("currentChanged(int)"),
38                      self.tab_changed)
39
40         self.connect(self.gui.nfftEdit,
41                      Qt.SIGNAL("textEdited(QString)"),
42                      self.nfft_edit_changed)
43
44         self.gui.designButton.setShortcut(QtCore.Qt.Key_Return)
45
46         self.taps = []
47         self.fftdB = []
48         self.fftDeg = []
49         self.groupDelay = []
50         self.nfftpts = int(10000)
51         self.gui.nfftEdit.setText(Qt.QString("%1").arg(self.nfftpts))
52
53         self.gui.lpfPassBandRippleLabel.setVisible(False)
54         self.gui.lpfPassBandRippleEdit.setVisible(False)
55         self.gui.bpfPassBandRippleLabel.setVisible(False)
56         self.gui.bpfPassBandRippleEdit.setVisible(False)
57         self.gui.bnfPassBandRippleLabel.setVisible(False)
58         self.gui.bnfPassBandRippleEdit.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
132         self.gui.nTapsEdit.setText("0")
133
134         self.filterWindows = {"Hamming Window" : gr.firdes.WIN_HAMMING,
135                               "Hann Window" : gr.firdes.WIN_HANN,
136                               "Blackman Window" : gr.firdes.WIN_BLACKMAN,
137                               "Rectangular Window" : gr.firdes.WIN_RECTANGULAR,
138                               "Kaiser Window" : gr.firdes.WIN_KAISER,
139                               "Blackman-harris Window" : gr.firdes.WIN_BLACKMAN_hARRIS}
140
141         self.show()
142
143     def changed_filter_type(self, ftype):
144         strftype = str(ftype.toAscii())
145         if(ftype == "Low Pass"):
146             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firlpfPage)
147         elif(ftype == "Band Pass"):
148             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbpfPage)
149         elif(ftype == "Complex Band Pass"):
150             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbpfPage)
151         elif(ftype == "Band Notch"):
152             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbnfPage)
153         elif(ftype == "High Pass"):
154             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firhpfPage)
155         elif(ftype == "Root Raised Cosine"):
156             self.gui.filterTypeWidget.setCurrentWidget(self.gui.rrcPage)
157         elif(ftype == "Gaussian"):
158             self.gui.filterTypeWidget.setCurrentWidget(self.gui.gausPage)
159
160         self.design()
161         
162     def changed_filter_design_type(self, design):
163         if(design == "Equiripple"):
164             self.set_equiripple()
165         else:
166             self.set_windowed()
167             
168         self.design()
169
170     def set_equiripple(self):
171         self.equiripple = True
172         self.gui.lpfPassBandRippleLabel.setVisible(True)
173         self.gui.lpfPassBandRippleEdit.setVisible(True)
174         self.gui.bpfPassBandRippleLabel.setVisible(True)
175         self.gui.bpfPassBandRippleEdit.setVisible(True)
176         self.gui.bnfPassBandRippleLabel.setVisible(True)
177         self.gui.bnfPassBandRippleEdit.setVisible(True)
178         self.gui.hpfPassBandRippleLabel.setVisible(True)
179         self.gui.hpfPassBandRippleEdit.setVisible(True)
180         
181     def set_windowed(self):
182         self.equiripple = False
183         self.gui.lpfPassBandRippleLabel.setVisible(False)
184         self.gui.lpfPassBandRippleEdit.setVisible(False)
185         self.gui.bpfPassBandRippleLabel.setVisible(False)
186         self.gui.bpfPassBandRippleEdit.setVisible(False)
187         self.gui.bnfPassBandRippleLabel.setVisible(False)
188         self.gui.bnfPassBandRippleEdit.setVisible(False)
189         self.gui.hpfPassBandRippleLabel.setVisible(False)
190         self.gui.hpfPassBandRippleEdit.setVisible(False)
191         
192     def design(self):
193         ret = True
194         fs,r = self.gui.sampleRateEdit.text().toDouble()
195         ret = r and ret
196         gain,r = self.gui.filterGainEdit.text().toDouble()
197         ret = r and ret
198
199         if(ret):
200             winstr = str(self.gui.filterDesignTypeComboBox.currentText().toAscii())
201             ftype = str(self.gui.filterTypeComboBox.currentText().toAscii())
202
203             if(winstr == "Equiripple"):
204                 designer = {"Low Pass" : self.design_opt_lpf,
205                             "Band Pass" : self.design_opt_bpf,
206                             "Complex Band Pass" : self.design_opt_cbpf,
207                             "Band Notch" : self.design_opt_bnf,
208                             "High Pass" :  self.design_opt_hpf}
209                 taps,r = designer[ftype](fs, gain)
210
211             else:
212                 designer = {"Low Pass" : self.design_win_lpf,
213                             "Band Pass" : self.design_win_bpf,
214                             "Complex Band Pass" : self.design_win_cbpf,
215                             "Band Notch" : self.design_win_bnf,
216                             "High Pass" :  self.design_win_hpf,
217                             "Root Raised Cosine" :  self.design_win_rrc,
218                             "Gaussian" :  self.design_win_gaus}
219                 wintype = self.filterWindows[winstr]
220                 taps,r = designer[ftype](fs, gain, wintype)
221
222             if(r):
223                 self.taps = scipy.array(taps)
224                 self.get_fft(fs, self.taps, self.nfftpts)
225                 self.update_time_curves()
226                 self.update_freq_curves()
227                 self.update_phase_curves()
228                 self.update_group_curves()
229
230                 self.gui.nTapsEdit.setText(Qt.QString("%1").arg(self.taps.size))
231
232
233     # Filter design functions using a window
234     def design_win_lpf(self, fs, gain, wintype):
235         ret = True
236         pb,r = self.gui.endofLpfPassBandEdit.text().toDouble()
237         ret = r and ret
238         sb,r = self.gui.startofLpfStopBandEdit.text().toDouble()
239         ret = r and ret
240         atten,r = self.gui.lpfStopBandAttenEdit.text().toDouble()
241         ret = r and ret
242
243         if(ret):
244             tb = sb - pb
245             
246             taps = gr.firdes.low_pass_2(gain, fs, pb, tb,
247                                         atten, wintype)
248             return (taps, ret)
249         else:
250             return ([], ret)
251     
252     def design_win_bpf(self, fs, gain, wintype):
253         ret = True
254         pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
255         ret = r and ret
256         pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
257         ret = r and ret
258         tb,r  = self.gui.bpfTransitionEdit.text().toDouble()
259         ret = r and ret
260         atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
261         ret = r and ret
262
263         if(r):
264             taps = gr.firdes.band_pass_2(gain, fs, pb1, pb2, tb,
265                                          atten, wintype)
266             return (taps,r)
267         else:
268             return ([],r)
269
270     def design_win_cbpf(self, fs, gain, wintype):
271         ret = True
272         pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
273         ret = r and ret
274         pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
275         ret = r and ret
276         tb,r  = self.gui.bpfTransitionEdit.text().toDouble()
277         ret = r and ret
278         atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
279         ret = r and ret
280
281         if(r):
282             taps = gr.firdes.complex_band_pass_2(gain, fs, pb1, pb2, tb,
283                                                  atten, wintype)
284             return (taps,r)
285         else:
286             return ([],r)
287
288     def design_win_bnf(self, fs, gain, wintype):
289         ret = True
290         pb1,r = self.gui.startofBnfStopBandEdit.text().toDouble()
291         ret = r and ret
292         pb2,r = self.gui.endofBnfStopBandEdit.text().toDouble()
293         ret = r and ret
294         tb,r  = self.gui.bnfTransitionEdit.text().toDouble()
295         ret = r and ret
296         atten,r = self.gui.bnfStopBandAttenEdit.text().toDouble()
297         ret = r and ret
298
299         if(r):
300             taps = gr.firdes.band_reject_2(gain, fs, pb1, pb2, tb,
301                                            atten, wintype)
302             return (taps,r)
303         else:
304             return ([],r)
305
306     def design_win_hpf(self, fs, gain, wintype):
307         ret = True
308         sb,r = self.gui.endofHpfStopBandEdit.text().toDouble()
309         ret = r and ret
310         pb,r = self.gui.startofHpfPassBandEdit.text().toDouble()
311         ret = r and ret
312         atten,r = self.gui.hpfStopBandAttenEdit.text().toDouble()
313         ret = r and ret
314
315         if(r):
316             tb = pb - sb
317             taps = gr.firdes.high_pass_2(gain, fs, pb, tb,
318                                          atten, wintype)            
319             return (taps,r)
320         else:
321             return ([],r)
322
323     def design_win_rrc(self, fs, gain, wintype):
324         ret = True
325         sr,r = self.gui.rrcSymbolRateEdit.text().toDouble()
326         ret = r and ret
327         alpha,r = self.gui.rrcAlphaEdit.text().toDouble()
328         ret = r and ret
329         ntaps,r = self.gui.rrcNumTapsEdit.text().toInt()
330         ret = r and ret
331
332         if(r):
333             taps = gr.firdes.root_raised_cosine(gain, fs, sr,
334                                                 alpha, ntaps)
335             return (taps,r)
336         else:
337             return ([],r)
338
339     def design_win_gaus(self, fs, gain, wintype):
340         ret = True
341         sr,r = self.gui.gausSymbolRateEdit.text().toDouble()
342         ret = r and ret
343         bt,r = self.gui.gausBTEdit.text().toDouble()
344         ret = r and ret
345         ntaps,r = self.gui.gausNumTapsEdit.text().toInt()
346         ret = r and ret
347
348         if(r):
349             spb = fs / sr
350             taps = gr.firdes.gaussian(gain, spb, bt, ntaps)
351             return (taps,r)
352         else:
353             return ([],r)
354
355     # Design Functions for Equiripple Filters
356     def design_opt_lpf(self, fs, gain):
357         ret = True
358         pb,r = self.gui.endofLpfPassBandEdit.text().toDouble()
359         ret = r and ret
360         sb,r = self.gui.startofLpfStopBandEdit.text().toDouble()
361         ret = r and ret
362         atten,r = self.gui.lpfStopBandAttenEdit.text().toDouble()
363         ret = r and ret
364         ripple,r = self.gui.lpfPassBandRippleEdit.text().toDouble()
365         ret = r and ret
366
367         if(ret):
368             taps = blks2.optfir.low_pass(gain, fs, pb, sb,
369                                          ripple, atten)
370             return (taps, ret)
371         else:
372             return ([], ret)
373     
374     def design_opt_bpf(self, fs, gain):
375         ret = True
376         pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
377         ret = r and ret
378         pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
379         ret = r and ret
380         tb,r  = self.gui.bpfTransitionEdit.text().toDouble()
381         ret = r and ret
382         atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
383         ret = r and ret
384         ripple,r = self.gui.bpfPassBandRippleEdit.text().toDouble()
385         ret = r and ret
386
387         if(r):
388             sb1 = pb1 - tb
389             sb2 = pb2 + tb
390             taps = blks2.optfir.band_pass(gain, fs, sb1, pb1, pb2, sb2,
391                                           ripple, atten)
392             return (taps,r)
393         else:
394             return ([],r)
395
396     def design_opt_cbpf(self, fs, gain):
397         ret = True
398         pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
399         ret = r and ret
400         pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
401         ret = r and ret
402         tb,r  = self.gui.bpfTransitionEdit.text().toDouble()
403         ret = r and ret
404         atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
405         ret = r and ret
406         ripple,r = self.gui.bpfPassBandRippleEdit.text().toDouble()
407         ret = r and ret
408
409         if(r):
410             sb1 = pb1 - tb
411             sb2 = pb2 + tb
412             taps = blks2.optfir.complex_band_pass(gain, fs, sb1, pb1, pb2, sb2,
413                                                   ripple, atten)
414             return (taps,r)
415         else:
416             return ([],r)
417
418     def design_opt_bnf(self, fs, gain):
419         ret = True
420         sb1,r = self.gui.startofBnfStopBandEdit.text().toDouble()
421         ret = r and ret
422         sb2,r = self.gui.endofBnfStopBandEdit.text().toDouble()
423         ret = r and ret
424         tb,r  = self.gui.bnfTransitionEdit.text().toDouble()
425         ret = r and ret
426         atten,r = self.gui.bnfStopBandAttenEdit.text().toDouble()
427         ret = r and ret
428         ripple,r = self.gui.bnfPassBandRippleEdit.text().toDouble()
429         ret = r and ret
430
431         if(r):
432             pb1 = sb1 - tb
433             pb2 = sb2 + tb
434             taps = blks2.optfir.band_reject(gain, fs, pb1, sb1, sb2, pb2,
435                                             ripple, atten)
436             return (taps,r)
437         else:
438             return ([],r)
439
440     def design_opt_hpf(self, fs, gain):
441         ret = True
442         sb,r = self.gui.endofHpfStopBandEdit.text().toDouble()
443         ret = r and ret
444         pb,r = self.gui.startofHpfPassBandEdit.text().toDouble()
445         ret = r and ret
446         atten,r = self.gui.hpfStopBandAttenEdit.text().toDouble()
447         ret = r and ret
448         ripple,r = self.gui.hpfPassBandRippleEdit.text().toDouble()
449         ret = r and ret
450
451         if(r):
452             taps = blks2.optfir.high_pass(gain, fs, sb, pb,
453                                           atten, ripple)
454             return (taps,r)
455         else:
456             return ([],r)
457
458     def nfft_edit_changed(self, nfft):
459         infft,r = nfft.toInt()
460         if(r and (infft != self.nfftpts)):
461             self.nfftpts = infft
462             self.update_freq_curves()
463
464     def tab_changed(self, tab):
465         if(tab == 0):
466             self.update_freq_curves()
467         if(tab == 1):
468             self.update_time_curves()
469         if(tab == 2):
470             self.update_phase_curves()
471         if(tab == 3):
472             self.update_group_curves()
473         
474     def get_fft(self, fs, taps, Npts):
475         Ts = 1.0/fs
476         fftpts = fftpack.fft(taps, Npts)
477         self.freq = scipy.arange(0, fs, 1.0/(Npts*Ts))        
478         self.fftdB = 20.0*scipy.log10(abs(fftpts))
479         self.fftDeg = scipy.unwrap(scipy.angle(fftpts))
480         self.groupDelay = -scipy.diff(self.fftDeg)
481         
482     def update_time_curves(self):
483         ntaps = len(self.taps)
484         if(ntaps > 0):
485             if(type(self.taps[0]) == scipy.complex128):
486                 self.rcurve.setData(scipy.arange(ntaps), self.taps.real)
487                 self.icurve.setData(scipy.arange(ntaps), self.taps.imag)
488             else:
489                 self.rcurve.setData(scipy.arange(ntaps), self.taps)
490
491             # Reset the x-axis to the new time scale
492             ymax = 1.5 * max(self.taps)
493             ymin = 1.5 * min(self.taps)
494             self.gui.timePlot.setAxisScale(self.gui.timePlot.xBottom,
495                                            0, ntaps)
496             self.gui.timePlot.setAxisScale(self.gui.timePlot.yLeft,
497                                            ymin, ymax)
498             
499             # Set the zoomer base to unzoom to the new axis
500             self.timeZoomer.setZoomBase()
501             
502             self.gui.timePlot.replot()
503         
504     def update_freq_curves(self):
505         npts = len(self.fftdB)
506         if(npts > 0):
507             self.freqcurve.setData(self.freq, self.fftdB)
508             
509             # Reset the x-axis to the new time scale
510             ymax = 1.5 * max(self.fftdB[0:npts/2])
511             ymin = 1.1 * min(self.fftdB[0:npts/2])
512             xmax = self.freq[npts/2]
513             xmin = self.freq[0]
514             self.gui.freqPlot.setAxisScale(self.gui.freqPlot.xBottom,
515                                            xmin, xmax)
516             self.gui.freqPlot.setAxisScale(self.gui.freqPlot.yLeft,
517                                            ymin, ymax)
518             
519             # Set the zoomer base to unzoom to the new axis
520             self.freqZoomer.setZoomBase()
521             
522             self.gui.freqPlot.replot()
523
524
525     def update_phase_curves(self):
526         npts = len(self.fftDeg)
527         if(npts > 0):
528             self.phasecurve.setData(self.freq, self.fftDeg)
529             
530             # Reset the x-axis to the new time scale
531             ymax = 1.5 * max(self.fftDeg[0:npts/2])
532             ymin = 1.1 * min(self.fftDeg[0:npts/2])
533             xmax = self.freq[npts/2]
534             xmin = self.freq[0]
535             self.gui.phasePlot.setAxisScale(self.gui.phasePlot.xBottom,
536                                             xmin, xmax)
537             self.gui.phasePlot.setAxisScale(self.gui.phasePlot.yLeft,
538                                             ymin, ymax)
539             
540             # Set the zoomer base to unzoom to the new axis
541             self.phaseZoomer.setZoomBase()
542             
543             self.gui.phasePlot.replot()
544
545     def update_group_curves(self):
546         npts = len(self.groupDelay)
547         if(npts > 0):
548             self.groupcurve.setData(self.freq, self.groupDelay)
549             
550             # Reset the x-axis to the new time scale
551             ymax = 1.5 * max(self.groupDelay[0:npts/2])
552             ymin = 1.1 * min(self.groupDelay[0:npts/2])
553             xmax = self.freq[npts/2]
554             xmin = self.freq[0]
555             self.gui.groupPlot.setAxisScale(self.gui.groupPlot.xBottom,
556                                             xmin, xmax)
557             self.gui.groupPlot.setAxisScale(self.gui.groupPlot.yLeft,
558                                             ymin, ymax)
559             
560             # Set the zoomer base to unzoom to the new axis
561             self.groupZoomer.setZoomBase()
562             
563             self.gui.groupPlot.replot()
564
565
566 def setup_options():
567     usage="%prog: [options] (input_filename)"
568     description = ""
569
570     parser = OptionParser(conflict_handler="resolve",
571                           usage=usage, description=description)
572     return parser
573
574 def main(args):
575     parser = setup_options()
576     (options, args) = parser.parse_args ()
577
578     app = Qt.QApplication(args)
579     gplt = gr_plot_filter(app, options)
580     app.exec_()
581
582 if __name__ == '__main__':
583     main(sys.argv)
584