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