4 from optparse import OptionParser
5 from gnuradio import gr, blks2, eng_notation
9 from scipy import fftpack
11 print "Please install SciPy to run this script (http://www.scipy.org/)"
15 from PyQt4 import Qt, QtCore, QtGui
17 print "Please install PyQt4 to run this script (http://www.riverbankcomputing.co.uk/software/pyqt/download)"
21 import PyQt4.Qwt5 as Qwt
23 print "Please install PyQwt5 to run this script (http://pyqwt.sourceforge.net/)"
27 from pyqt_filter import Ui_MainWindow
29 print "Could not import from pyqt_filter. Please build with \"pyuic4 pyqt_filter.ui -o pyqt_filter.py\""
33 class gr_plot_filter(QtGui.QMainWindow):
34 def __init__(self, qapp, options):
35 QtGui.QWidget.__init__(self, None)
36 self.gui = Ui_MainWindow()
37 self.gui.setupUi(self)
39 self.connect(self.gui.filterTypeComboBox,
40 Qt.SIGNAL("currentIndexChanged(const QString&)"),
41 self.changed_filter_type)
42 self.connect(self.gui.filterDesignTypeComboBox,
43 Qt.SIGNAL("currentIndexChanged(const QString&)"),
44 self.changed_filter_design_type)
46 self.connect(self.gui.designButton,
47 Qt.SIGNAL("released()"),
50 self.connect(self.gui.tabGroup,
51 Qt.SIGNAL("currentChanged(int)"),
54 self.connect(self.gui.nfftEdit,
55 Qt.SIGNAL("textEdited(QString)"),
56 self.nfft_edit_changed)
58 self.gui.designButton.setShortcut(QtCore.Qt.Key_Return)
64 self.nfftpts = int(10000)
65 self.gui.nfftEdit.setText(Qt.QString("%1").arg(self.nfftpts))
67 self.firFilters = ("Low Pass", "Band Pass", "Complex Band Pass", "Band Notch",
68 "High Pass", "Root Raised Cosine", "Gaussian")
69 self.optFilters = ("Low Pass", "Band Pass", "Complex Band Pass",
70 "Band Notch", "High Pass")
75 self.gui.filterTypeWidget.setCurrentWidget(self.gui.firlpfPage)
78 self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.xBottom,
80 self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.yLeft,
82 self.gui.timePlot.setAxisTitle(self.gui.timePlot.xBottom,
84 self.gui.timePlot.setAxisTitle(self.gui.timePlot.yLeft,
86 self.gui.phasePlot.setAxisTitle(self.gui.phasePlot.xBottom,
88 self.gui.phasePlot.setAxisTitle(self.gui.phasePlot.yLeft,
90 self.gui.groupPlot.setAxisTitle(self.gui.groupPlot.xBottom,
92 self.gui.groupPlot.setAxisTitle(self.gui.groupPlot.yLeft,
96 self.rcurve = Qwt.QwtPlotCurve("Real")
97 self.rcurve.attach(self.gui.timePlot)
98 self.icurve = Qwt.QwtPlotCurve("Imag")
99 self.icurve.attach(self.gui.timePlot)
101 self.freqcurve = Qwt.QwtPlotCurve("PSD")
102 self.freqcurve.attach(self.gui.freqPlot)
104 self.phasecurve = Qwt.QwtPlotCurve("Phase")
105 self.phasecurve.attach(self.gui.phasePlot)
107 self.groupcurve = Qwt.QwtPlotCurve("Group Delay")
108 self.groupcurve.attach(self.gui.groupPlot)
110 # Create zoom functionality for the plots
111 self.timeZoomer = Qwt.QwtPlotZoomer(self.gui.timePlot.xBottom,
112 self.gui.timePlot.yLeft,
113 Qwt.QwtPicker.PointSelection,
114 Qwt.QwtPicker.AlwaysOn,
115 self.gui.timePlot.canvas())
117 self.freqZoomer = Qwt.QwtPlotZoomer(self.gui.freqPlot.xBottom,
118 self.gui.freqPlot.yLeft,
119 Qwt.QwtPicker.PointSelection,
120 Qwt.QwtPicker.AlwaysOn,
121 self.gui.freqPlot.canvas())
123 self.phaseZoomer = Qwt.QwtPlotZoomer(self.gui.phasePlot.xBottom,
124 self.gui.phasePlot.yLeft,
125 Qwt.QwtPicker.PointSelection,
126 Qwt.QwtPicker.AlwaysOn,
127 self.gui.phasePlot.canvas())
129 self.groupZoomer = Qwt.QwtPlotZoomer(self.gui.groupPlot.xBottom,
130 self.gui.groupPlot.yLeft,
131 Qwt.QwtPicker.PointSelection,
132 Qwt.QwtPicker.AlwaysOn,
133 self.gui.groupPlot.canvas())
135 # Set up pen for colors and line width
136 blue = QtGui.qRgb(0x00, 0x00, 0xFF)
137 blueBrush = Qt.QBrush(Qt.QColor(blue))
138 red = QtGui.qRgb(0xFF, 0x00, 0x00)
139 redBrush = Qt.QBrush(Qt.QColor(red))
140 self.freqcurve.setPen(Qt.QPen(blueBrush, 2))
141 self.rcurve.setPen(Qt.QPen(blueBrush, 2))
142 self.icurve.setPen(Qt.QPen(redBrush, 2))
143 self.phasecurve.setPen(Qt.QPen(blueBrush, 2))
144 self.groupcurve.setPen(Qt.QPen(blueBrush, 2))
146 # Set up validators for edit boxes
147 self.intVal = Qt.QIntValidator(None)
148 self.dblVal = Qt.QDoubleValidator(None)
149 self.gui.nfftEdit.setValidator(self.intVal)
150 self.gui.sampleRateEdit.setValidator(self.dblVal)
151 self.gui.filterGainEdit.setValidator(self.dblVal)
152 self.gui.endofLpfPassBandEdit.setValidator(self.dblVal)
153 self.gui.startofLpfStopBandEdit.setValidator(self.dblVal)
154 self.gui.lpfStopBandAttenEdit.setValidator(self.dblVal)
155 self.gui.lpfPassBandRippleEdit.setValidator(self.dblVal)
156 self.gui.startofBpfPassBandEdit.setValidator(self.dblVal)
157 self.gui.endofBpfPassBandEdit.setValidator(self.dblVal)
158 self.gui.bpfTransitionEdit.setValidator(self.dblVal)
159 self.gui.bpfStopBandAttenEdit.setValidator(self.dblVal)
160 self.gui.bpfPassBandRippleEdit.setValidator(self.dblVal)
161 self.gui.startofBnfStopBandEdit.setValidator(self.dblVal)
162 self.gui.endofBnfStopBandEdit.setValidator(self.dblVal)
163 self.gui.bnfTransitionEdit.setValidator(self.dblVal)
164 self.gui.bnfStopBandAttenEdit.setValidator(self.dblVal)
165 self.gui.bnfPassBandRippleEdit.setValidator(self.dblVal)
166 self.gui.endofHpfStopBandEdit.setValidator(self.dblVal)
167 self.gui.startofHpfPassBandEdit.setValidator(self.dblVal)
168 self.gui.hpfStopBandAttenEdit.setValidator(self.dblVal)
169 self.gui.hpfPassBandRippleEdit.setValidator(self.dblVal)
170 self.gui.rrcSymbolRateEdit.setValidator(self.dblVal)
171 self.gui.rrcAlphaEdit.setValidator(self.dblVal)
172 self.gui.rrcNumTapsEdit.setValidator(self.dblVal)
173 self.gui.gausSymbolRateEdit.setValidator(self.dblVal)
174 self.gui.gausBTEdit.setValidator(self.dblVal)
175 self.gui.gausNumTapsEdit.setValidator(self.dblVal)
177 self.gui.nTapsEdit.setText("0")
179 self.filterWindows = {"Hamming Window" : gr.firdes.WIN_HAMMING,
180 "Hann Window" : gr.firdes.WIN_HANN,
181 "Blackman Window" : gr.firdes.WIN_BLACKMAN,
182 "Rectangular Window" : gr.firdes.WIN_RECTANGULAR,
183 "Kaiser Window" : gr.firdes.WIN_KAISER,
184 "Blackman-harris Window" : gr.firdes.WIN_BLACKMAN_hARRIS}
188 def changed_filter_type(self, ftype):
189 strftype = str(ftype.toAscii())
190 if(ftype == "Low Pass"):
191 self.gui.filterTypeWidget.setCurrentWidget(self.gui.firlpfPage)
192 elif(ftype == "Band Pass"):
193 self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbpfPage)
194 elif(ftype == "Complex Band Pass"):
195 self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbpfPage)
196 elif(ftype == "Band Notch"):
197 self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbnfPage)
198 elif(ftype == "High Pass"):
199 self.gui.filterTypeWidget.setCurrentWidget(self.gui.firhpfPage)
200 elif(ftype == "Root Raised Cosine"):
201 self.gui.filterTypeWidget.setCurrentWidget(self.gui.rrcPage)
202 elif(ftype == "Gaussian"):
203 self.gui.filterTypeWidget.setCurrentWidget(self.gui.gausPage)
207 def changed_filter_design_type(self, design):
208 if(design == "Equiripple"):
209 self.set_equiripple()
215 def set_equiripple(self):
216 # Stop sending the signal for this function
217 self.gui.filterTypeComboBox.blockSignals(True)
219 self.equiripple = True
220 self.gui.lpfPassBandRippleLabel.setVisible(True)
221 self.gui.lpfPassBandRippleEdit.setVisible(True)
222 self.gui.bpfPassBandRippleLabel.setVisible(True)
223 self.gui.bpfPassBandRippleEdit.setVisible(True)
224 self.gui.bnfPassBandRippleLabel.setVisible(True)
225 self.gui.bnfPassBandRippleEdit.setVisible(True)
226 self.gui.hpfPassBandRippleLabel.setVisible(True)
227 self.gui.hpfPassBandRippleEdit.setVisible(True)
229 # Save current type and repopulate the combo box for
230 # filters this window type can handle
231 currenttype = self.gui.filterTypeComboBox.currentText()
232 items = self.gui.filterTypeComboBox.count()
233 for i in xrange(items):
234 self.gui.filterTypeComboBox.removeItem(0)
235 self.gui.filterTypeComboBox.addItems(self.optFilters)
237 # If the last filter type was valid for this window type,
238 # go back to it; otherwise, reset
240 index = self.optFilters.index(currenttype)
241 self.gui.filterTypeComboBox.setCurrentIndex(index)
245 # Tell gui its ok to start sending this signal again
246 self.gui.filterTypeComboBox.blockSignals(False)
248 def set_windowed(self):
249 # Stop sending the signal for this function
250 self.gui.filterTypeComboBox.blockSignals(True)
252 self.equiripple = False
253 self.gui.lpfPassBandRippleLabel.setVisible(False)
254 self.gui.lpfPassBandRippleEdit.setVisible(False)
255 self.gui.bpfPassBandRippleLabel.setVisible(False)
256 self.gui.bpfPassBandRippleEdit.setVisible(False)
257 self.gui.bnfPassBandRippleLabel.setVisible(False)
258 self.gui.bnfPassBandRippleEdit.setVisible(False)
259 self.gui.hpfPassBandRippleLabel.setVisible(False)
260 self.gui.hpfPassBandRippleEdit.setVisible(False)
262 # Save current type and repopulate the combo box for
263 # filters this window type can handle
264 currenttype = self.gui.filterTypeComboBox.currentText()
265 items = self.gui.filterTypeComboBox.count()
266 for i in xrange(items):
267 self.gui.filterTypeComboBox.removeItem(0)
268 self.gui.filterTypeComboBox.addItems(self.firFilters)
270 # If the last filter type was valid for this window type,
271 # go back to it; otherwise, reset
273 index = self.optFilters.index(currenttype)
274 self.gui.filterTypeComboBox.setCurrentIndex(index)
278 # Tell gui its ok to start sending this signal again
279 self.gui.filterTypeComboBox.blockSignals(False)
283 fs,r = self.gui.sampleRateEdit.text().toDouble()
285 gain,r = self.gui.filterGainEdit.text().toDouble()
289 winstr = str(self.gui.filterDesignTypeComboBox.currentText().toAscii())
290 ftype = str(self.gui.filterTypeComboBox.currentText().toAscii())
292 if(winstr == "Equiripple"):
293 designer = {"Low Pass" : self.design_opt_lpf,
294 "Band Pass" : self.design_opt_bpf,
295 "Complex Band Pass" : self.design_opt_cbpf,
296 "Band Notch" : self.design_opt_bnf,
297 "High Pass" : self.design_opt_hpf}
298 taps,r = designer[ftype](fs, gain)
301 designer = {"Low Pass" : self.design_win_lpf,
302 "Band Pass" : self.design_win_bpf,
303 "Complex Band Pass" : self.design_win_cbpf,
304 "Band Notch" : self.design_win_bnf,
305 "High Pass" : self.design_win_hpf,
306 "Root Raised Cosine" : self.design_win_rrc,
307 "Gaussian" : self.design_win_gaus}
308 wintype = self.filterWindows[winstr]
309 taps,r = designer[ftype](fs, gain, wintype)
312 self.taps = scipy.array(taps)
313 self.get_fft(fs, self.taps, self.nfftpts)
314 self.update_time_curves()
315 self.update_freq_curves()
316 self.update_phase_curves()
317 self.update_group_curves()
319 self.gui.nTapsEdit.setText(Qt.QString("%1").arg(self.taps.size))
322 # Filter design functions using a window
323 def design_win_lpf(self, fs, gain, wintype):
325 pb,r = self.gui.endofLpfPassBandEdit.text().toDouble()
327 sb,r = self.gui.startofLpfStopBandEdit.text().toDouble()
329 atten,r = self.gui.lpfStopBandAttenEdit.text().toDouble()
335 taps = gr.firdes.low_pass_2(gain, fs, pb, tb,
341 def design_win_bpf(self, fs, gain, wintype):
343 pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
345 pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
347 tb,r = self.gui.bpfTransitionEdit.text().toDouble()
349 atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
353 taps = gr.firdes.band_pass_2(gain, fs, pb1, pb2, tb,
359 def design_win_cbpf(self, fs, gain, wintype):
361 pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
363 pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
365 tb,r = self.gui.bpfTransitionEdit.text().toDouble()
367 atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
371 taps = gr.firdes.complex_band_pass_2(gain, fs, pb1, pb2, tb,
377 def design_win_bnf(self, fs, gain, wintype):
379 pb1,r = self.gui.startofBnfStopBandEdit.text().toDouble()
381 pb2,r = self.gui.endofBnfStopBandEdit.text().toDouble()
383 tb,r = self.gui.bnfTransitionEdit.text().toDouble()
385 atten,r = self.gui.bnfStopBandAttenEdit.text().toDouble()
389 taps = gr.firdes.band_reject_2(gain, fs, pb1, pb2, tb,
395 def design_win_hpf(self, fs, gain, wintype):
397 sb,r = self.gui.endofHpfStopBandEdit.text().toDouble()
399 pb,r = self.gui.startofHpfPassBandEdit.text().toDouble()
401 atten,r = self.gui.hpfStopBandAttenEdit.text().toDouble()
406 taps = gr.firdes.high_pass_2(gain, fs, pb, tb,
412 def design_win_rrc(self, fs, gain, wintype):
414 sr,r = self.gui.rrcSymbolRateEdit.text().toDouble()
416 alpha,r = self.gui.rrcAlphaEdit.text().toDouble()
418 ntaps,r = self.gui.rrcNumTapsEdit.text().toInt()
422 taps = gr.firdes.root_raised_cosine(gain, fs, sr,
428 def design_win_gaus(self, fs, gain, wintype):
430 sr,r = self.gui.gausSymbolRateEdit.text().toDouble()
432 bt,r = self.gui.gausBTEdit.text().toDouble()
434 ntaps,r = self.gui.gausNumTapsEdit.text().toInt()
439 taps = gr.firdes.gaussian(gain, spb, bt, ntaps)
444 # Design Functions for Equiripple Filters
445 def design_opt_lpf(self, fs, gain):
447 pb,r = self.gui.endofLpfPassBandEdit.text().toDouble()
449 sb,r = self.gui.startofLpfStopBandEdit.text().toDouble()
451 atten,r = self.gui.lpfStopBandAttenEdit.text().toDouble()
453 ripple,r = self.gui.lpfPassBandRippleEdit.text().toDouble()
457 taps = blks2.optfir.low_pass(gain, fs, pb, sb,
463 def design_opt_bpf(self, fs, gain):
465 pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
467 pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
469 tb,r = self.gui.bpfTransitionEdit.text().toDouble()
471 atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
473 ripple,r = self.gui.bpfPassBandRippleEdit.text().toDouble()
479 taps = blks2.optfir.band_pass(gain, fs, sb1, pb1, pb2, sb2,
485 def design_opt_cbpf(self, fs, gain):
487 pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble()
489 pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble()
491 tb,r = self.gui.bpfTransitionEdit.text().toDouble()
493 atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble()
495 ripple,r = self.gui.bpfPassBandRippleEdit.text().toDouble()
501 taps = blks2.optfir.complex_band_pass(gain, fs, sb1, pb1, pb2, sb2,
507 def design_opt_bnf(self, fs, gain):
509 sb1,r = self.gui.startofBnfStopBandEdit.text().toDouble()
511 sb2,r = self.gui.endofBnfStopBandEdit.text().toDouble()
513 tb,r = self.gui.bnfTransitionEdit.text().toDouble()
515 atten,r = self.gui.bnfStopBandAttenEdit.text().toDouble()
517 ripple,r = self.gui.bnfPassBandRippleEdit.text().toDouble()
523 taps = blks2.optfir.band_reject(gain, fs, pb1, sb1, sb2, pb2,
529 def design_opt_hpf(self, fs, gain):
531 sb,r = self.gui.endofHpfStopBandEdit.text().toDouble()
533 pb,r = self.gui.startofHpfPassBandEdit.text().toDouble()
535 atten,r = self.gui.hpfStopBandAttenEdit.text().toDouble()
537 ripple,r = self.gui.hpfPassBandRippleEdit.text().toDouble()
541 taps = blks2.optfir.high_pass(gain, fs, sb, pb,
547 def nfft_edit_changed(self, nfft):
548 infft,r = nfft.toInt()
549 if(r and (infft != self.nfftpts)):
551 self.update_freq_curves()
553 def tab_changed(self, tab):
555 self.update_freq_curves()
557 self.update_time_curves()
559 self.update_phase_curves()
561 self.update_group_curves()
563 def get_fft(self, fs, taps, Npts):
565 fftpts = fftpack.fft(taps, Npts)
566 self.freq = scipy.arange(0, fs, 1.0/(Npts*Ts))
567 self.fftdB = 20.0*scipy.log10(abs(fftpts))
568 self.fftDeg = scipy.unwrap(scipy.angle(fftpts))
569 self.groupDelay = -scipy.diff(self.fftDeg)
571 def update_time_curves(self):
572 ntaps = len(self.taps)
574 if(type(self.taps[0]) == scipy.complex128):
575 self.rcurve.setData(scipy.arange(ntaps), self.taps.real)
576 self.icurve.setData(scipy.arange(ntaps), self.taps.imag)
578 self.rcurve.setData(scipy.arange(ntaps), self.taps)
580 # Reset the x-axis to the new time scale
581 ymax = 1.5 * max(self.taps)
582 ymin = 1.5 * min(self.taps)
583 self.gui.timePlot.setAxisScale(self.gui.timePlot.xBottom,
585 self.gui.timePlot.setAxisScale(self.gui.timePlot.yLeft,
588 # Set the zoomer base to unzoom to the new axis
589 self.timeZoomer.setZoomBase()
591 self.gui.timePlot.replot()
593 def update_freq_curves(self):
594 npts = len(self.fftdB)
596 self.freqcurve.setData(self.freq, self.fftdB)
598 # Reset the x-axis to the new time scale
599 ymax = 1.5 * max(self.fftdB[0:npts/2])
600 ymin = 1.1 * min(self.fftdB[0:npts/2])
601 xmax = self.freq[npts/2]
603 self.gui.freqPlot.setAxisScale(self.gui.freqPlot.xBottom,
605 self.gui.freqPlot.setAxisScale(self.gui.freqPlot.yLeft,
608 # Set the zoomer base to unzoom to the new axis
609 self.freqZoomer.setZoomBase()
611 self.gui.freqPlot.replot()
614 def update_phase_curves(self):
615 npts = len(self.fftDeg)
617 self.phasecurve.setData(self.freq, self.fftDeg)
619 # Reset the x-axis to the new time scale
620 ymax = 1.5 * max(self.fftDeg[0:npts/2])
621 ymin = 1.1 * min(self.fftDeg[0:npts/2])
622 xmax = self.freq[npts/2]
624 self.gui.phasePlot.setAxisScale(self.gui.phasePlot.xBottom,
626 self.gui.phasePlot.setAxisScale(self.gui.phasePlot.yLeft,
629 # Set the zoomer base to unzoom to the new axis
630 self.phaseZoomer.setZoomBase()
632 self.gui.phasePlot.replot()
634 def update_group_curves(self):
635 npts = len(self.groupDelay)
637 self.groupcurve.setData(self.freq, self.groupDelay)
639 # Reset the x-axis to the new time scale
640 ymax = 1.5 * max(self.groupDelay[0:npts/2])
641 ymin = 1.1 * min(self.groupDelay[0:npts/2])
642 xmax = self.freq[npts/2]
644 self.gui.groupPlot.setAxisScale(self.gui.groupPlot.xBottom,
646 self.gui.groupPlot.setAxisScale(self.gui.groupPlot.yLeft,
649 # Set the zoomer base to unzoom to the new axis
650 self.groupZoomer.setZoomBase()
652 self.gui.groupPlot.replot()
656 usage="%prog: [options] (input_filename)"
659 parser = OptionParser(conflict_handler="resolve",
660 usage=usage, description=description)
664 parser = setup_options()
665 (options, args) = parser.parse_args ()
667 app = Qt.QApplication(args)
668 gplt = gr_plot_filter(app, options)
671 if __name__ == '__main__':