Adding a phase and group delay plot to the GUI.
[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.nfftpts = int(10000)
50         self.gui.nfftEdit.setText(Qt.QString("%1").arg(self.nfftpts))
51
52         self.gui.lpfPassBandRippleLabel.setVisible(False)
53         self.gui.lpfPassBandRippleEdit.setVisible(False)
54         self.gui.bpfPassBandRippleLabel.setVisible(False)
55         self.gui.bpfPassBandRippleEdit.setVisible(False)
56         self.gui.hpfPassBandRippleLabel.setVisible(False)
57         self.gui.hpfPassBandRippleEdit.setVisible(False)
58                 
59         # Initialize to LPF
60         self.gui.filterTypeWidget.setCurrentWidget(self.gui.firlpfPage)
61
62         # Set up plot curves
63         self.rcurve = Qwt.QwtPlotCurve("Real")
64         self.rcurve.attach(self.gui.timePlot)
65
66         self.freqcurve = Qwt.QwtPlotCurve("PSD")
67         self.freqcurve.attach(self.gui.freqPlot)
68
69         # Create zoom functionality for the plots
70         self.timeZoomer = Qwt.QwtPlotZoomer(self.gui.timePlot.xBottom,
71                                             self.gui.timePlot.yLeft,
72                                             Qwt.QwtPicker.PointSelection,
73                                             Qwt.QwtPicker.AlwaysOn,
74                                             self.gui.timePlot.canvas())
75
76         self.freqZoomer = Qwt.QwtPlotZoomer(self.gui.freqPlot.xBottom,
77                                             self.gui.freqPlot.yLeft,
78                                             Qwt.QwtPicker.PointSelection,
79                                             Qwt.QwtPicker.AlwaysOn,
80                                             self.gui.freqPlot.canvas())
81
82         self.phaseZoomer = Qwt.QwtPlotZoomer(self.gui.phasePlot.xBottom,
83                                              self.gui.phasePlot.yLeft,
84                                              Qwt.QwtPicker.PointSelection,
85                                              Qwt.QwtPicker.AlwaysOn,
86                                              self.gui.phasePlot.canvas())
87
88         self.groupZoomer = Qwt.QwtPlotZoomer(self.gui.groupPlot.xBottom,
89                                              self.gui.groupPlot.yLeft,
90                                              Qwt.QwtPicker.PointSelection,
91                                              Qwt.QwtPicker.AlwaysOn,
92                                              self.gui.groupPlot.canvas())
93
94         # Set up pen for colors and line width
95         blue = QtGui.qRgb(0x00, 0x00, 0xFF)
96         blueBrush = Qt.QBrush(Qt.QColor(blue))
97         self.freqcurve.setPen(Qt.QPen(blueBrush, 2))
98         self.rcurve.setPen(Qt.QPen(blueBrush, 2))
99
100         self.filterWindows = {"Hamming Window" : gr.firdes.WIN_HAMMING,
101                               "Hann Window" : gr.firdes.WIN_HANN,
102                               "Blackman Window" : gr.firdes.WIN_BLACKMAN,
103                               "Rectangular Window" : gr.firdes.WIN_RECTANGULAR,
104                               "Kaiser Window" : gr.firdes.WIN_KAISER,
105                               "Blackman-harris Window" : gr.firdes.WIN_BLACKMAN_hARRIS}
106
107         self.show()
108
109     def changed_filter_type(self, ftype):
110         strftype = str(ftype.toAscii())
111         if(ftype == "Low Pass"):
112             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firlpfPage)
113         elif(ftype == "Band Pass"):
114             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbpfPage)
115         elif(ftype == "High Pass"):
116             self.gui.filterTypeWidget.setCurrentWidget(self.gui.firhpfPage)
117
118         self.design()
119         
120     def changed_filter_design_type(self, design):
121         if(design == "Equiripple"):
122             self.set_equiripple()
123         else:
124             self.set_windowed()
125             
126         self.design()
127
128     def set_equiripple(self):
129         self.equiripple = True
130         self.gui.lpfPassBandRippleLabel.setVisible(True)
131         self.gui.lpfPassBandRippleEdit.setVisible(True)
132         self.gui.bpfPassBandRippleLabel.setVisible(True)
133         self.gui.bpfPassBandRippleEdit.setVisible(True)
134         self.gui.hpfPassBandRippleLabel.setVisible(True)
135         self.gui.hpfPassBandRippleEdit.setVisible(True)
136         
137     def set_windowed(self):
138         self.equiripple = False
139         self.gui.lpfPassBandRippleLabel.setVisible(False)
140         self.gui.lpfPassBandRippleEdit.setVisible(False)
141         self.gui.bpfPassBandRippleLabel.setVisible(False)
142         self.gui.bpfPassBandRippleEdit.setVisible(False)
143         self.gui.hpfPassBandRippleLabel.setVisible(False)
144         self.gui.hpfPassBandRippleEdit.setVisible(False)
145         
146     def design(self):
147         ret = True
148         fs,r = self.gui.sampleRateEdit.text().toDouble()
149         ret = r and ret
150         gain,r = self.gui.filterGainEdit.text().toDouble()
151         ret = r and ret
152
153         if(ret):
154             winstr = str(self.gui.filterDesignTypeComboBox.currentText().toAscii())
155             ftype = str(self.gui.filterTypeComboBox.currentText().toAscii())
156
157             if(winstr == "Equiripple"):
158                 designer = {"Low Pass" : self.design_opt_lpf,
159                             "Band Pass" : self.design_opt_bpf,
160                             "High Pass" :  self.design_opt_hpf}        
161                 taps,r = designer[ftype](fs, gain)
162
163             else:
164                 designer = {"Low Pass" : self.design_win_lpf,
165                             "Band Pass" : self.design_win_bpf,
166                             "High Pass" :  self.design_win_hpf}        
167                 wintype = self.filterWindows[winstr]
168                 taps,r = designer[ftype](fs, gain, wintype)
169
170             if(r):
171                 self.update_time_curves(taps)
172                 self.update_freq_curves(taps, self.nfftpts)
173         
174
175     # Filter design functions using a window
176     def design_win_lpf(self, fs, gain, wintype):
177         ret = True
178         pb,r = self.gui.endofLpfPassBandEdit.text().toDouble()
179         ret = r and ret
180         sb,r = self.gui.startofLpfStopBandEdit.text().toDouble()
181         ret = r and ret
182         atten,r = self.gui.lpfStopBandAttenEdit.text().toDouble()
183         ret = r and ret
184
185         if(ret):
186             tb = sb - pb
187             
188             taps = gr.firdes.low_pass_2(gain, fs, pb, tb,
189                                         atten, wintype)
190             return (taps, ret)
191         else:
192             return ([], ret)
193     
194     def design_win_bpf(self, fs, gain, wintype):
195         ret = True
196         pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
197         ret = r and ret
198         pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
199         ret = r and ret
200         tb,r  = self.gui.bpfTransitionEdit.text().toDouble()
201         ret = r and ret
202         atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
203         ret = r and ret
204
205         if(r):
206             taps = gr.firdes.band_pass_2(gain, fs, pb1, pb2, tb,
207                                          atten, wintype)
208             return (taps,r)
209         else:
210             return ([],r)
211
212     def design_win_hpf(self, fs, gain, wintype):
213         ret = True
214         sb,r = self.gui.endofHpfStopBandEdit.text().toDouble()
215         ret = r and ret
216         pb,r = self.gui.startofHpfPassBandEdit.text().toDouble()
217         ret = r and ret
218         atten,r = self.gui.hpfStopBandAttenEdit.text().toDouble()
219         ret = r and ret
220
221         if(r):
222             tb = pb - sb
223             taps = gr.firdes.high_pass_2(gain, fs, pb, tb,
224                                          atten, wintype)            
225             return (taps,r)
226         else:
227             return ([],r)
228
229
230
231     # Design Functions for Equiripple Filters
232     def design_opt_lpf(self, fs, gain, wintype=None):
233         ret = True
234         pb,r = self.gui.endofLpfPassBandEdit.text().toDouble()
235         ret = r and ret
236         sb,r = self.gui.startofLpfStopBandEdit.text().toDouble()
237         ret = r and ret
238         atten,r = self.gui.lpfStopBandAttenEdit.text().toDouble()
239         ret = r and ret
240         ripple,r = self.gui.lpfPassBandRippleEdit.text().toDouble()
241         ret = r and ret
242
243         if(ret):
244             taps = blks2.optfir.low_pass(gain, fs, pb, sb,
245                                          ripple, atten)
246             return (taps, ret)
247         else:
248             return ([], ret)
249     
250     def design_opt_bpf(self, fs, gain, wintype=None):
251         ret = True
252         pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
253         ret = r and ret
254         pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
255         ret = r and ret
256         tb,r  = self.gui.bpfTransitionEdit.text().toDouble()
257         ret = r and ret
258         atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
259         ret = r and ret
260         ripple,r = self.gui.bpfPassBandRippleEdit.text().toDouble()
261         ret = r and ret
262
263         if(r):
264             sb1 = pb1 - tb
265             sb2 = pb2 + tb
266             taps = blks2.optfir.band_pass(gain, fs, sb1, pb1, pb2, sb2,
267                                           ripple, atten)
268             return (taps,r)
269         else:
270             return ([],r)
271
272     def design_opt_hpf(self, fs, gain, wintype=None):
273         ret = True
274         sb,r = self.gui.endofHpfStopBandEdit.text().toDouble()
275         ret = r and ret
276         pb,r = self.gui.startofHpfPassBandEdit.text().toDouble()
277         ret = r and ret
278         atten,r = self.gui.hpfStopBandAttenEdit.text().toDouble()
279         ret = r and ret
280         ripple,r = self.gui.hpfPassBandRippleEdit.text().toDouble()
281         ret = r and ret
282
283         if(r):
284             taps = blks2.optfir.high_pass(gain, fs, sb, pb,
285                                           atten, ripple)
286             return (taps,r)
287         else:
288             return ([],r)
289
290     def nfft_edit_changed(self, nfft):
291         infft,r = nfft.toInt()
292         if(r and (infft != self.nfftpts)):
293             self.nfftpts = infft
294             self.update_freq_curves(self.taps, self.nfftpts)
295
296     def tab_changed(self, tab):
297         if(tab == 0):
298             self.update_freq_curves(self.taps, self.nfftpts)
299         if(tab == 1):
300             self.update_time_curves(self.taps)
301         
302     def update_time_curves(self, taps):
303         self.taps = taps
304         ntaps = len(taps)
305         if(ntaps > 0):
306             self.rcurve.setData(scipy.arange(ntaps), taps)
307             
308             # Reset the x-axis to the new time scale
309             ymax = 1.5 * max(taps)
310             ymin = 1.5 * min(taps)
311             self.gui.timePlot.setAxisScale(self.gui.timePlot.xBottom,
312                                            0, ntaps)
313             self.gui.timePlot.setAxisScale(self.gui.timePlot.yLeft,
314                                            ymin, ymax)
315             
316             # Set the zoomer base to unzoom to the new axis
317             self.timeZoomer.setZoomBase()
318             
319             self.gui.timePlot.replot()
320         
321     def update_freq_curves(self, taps, Npts=1000):
322         if(len(taps) > 0):
323             fftpts = fftpack.fft(taps, Npts)
324             freq = scipy.arange(0, Npts)
325             
326             fftdB = 20.0*scipy.log10(abs(fftpts))
327             
328             self.freqcurve.setData(freq, fftdB)
329             
330             # Reset the x-axis to the new time scale
331             ymax = 1.5 * max(fftdB)
332             ymin = 1.5 * min(fftdB)
333             self.gui.freqPlot.setAxisScale(self.gui.freqPlot.xBottom,
334                                            0, Npts/2)
335             self.gui.timePlot.setAxisScale(self.gui.timePlot.yLeft,
336                                            ymin, ymax)
337             
338             # Set the zoomer base to unzoom to the new axis
339             self.freqZoomer.setZoomBase()
340             
341             self.gui.freqPlot.replot()
342
343
344 def setup_options():
345     usage="%prog: [options] (input_filename)"
346     description = ""
347
348     parser = OptionParser(conflict_handler="resolve",
349                           usage=usage, description=description)
350     return parser
351
352 def main(args):
353     parser = setup_options()
354     (options, args) = parser.parse_args ()
355
356     app = Qt.QApplication(args)
357     gplt = gr_plot_filter(app, options)
358     app.exec_()
359
360 if __name__ == '__main__':
361     main(sys.argv)
362