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