From 1ca78cb621cbd60f25c5ce4373fa727114a86cee Mon Sep 17 00:00:00 2001 From: Tom Rondeau Date: Sun, 1 Apr 2012 17:16:41 -0400 Subject: build: removed .py extensions from installed Python plot programs. The fft and psd bases have been moved into the Python packages directory and new wrappers for data-type independent programs have been created. --- gr-utils/src/python/CMakeLists.txt | 30 +- gr-utils/src/python/README.plot | 95 +++- gr-utils/src/python/gr_filter_design | 874 ++++++++++++++++++++++++++++++++ gr-utils/src/python/gr_filter_design.py | 874 -------------------------------- gr-utils/src/python/gr_plot_char | 58 +++ gr-utils/src/python/gr_plot_char.py | 58 --- gr-utils/src/python/gr_plot_const | 250 +++++++++ gr-utils/src/python/gr_plot_const.py | 250 --------- gr-utils/src/python/gr_plot_fft | 42 ++ gr-utils/src/python/gr_plot_fft.py | 248 --------- gr-utils/src/python/gr_plot_fft_c | 46 ++ gr-utils/src/python/gr_plot_fft_c.py | 44 -- gr-utils/src/python/gr_plot_fft_f | 46 ++ gr-utils/src/python/gr_plot_fft_f.py | 44 -- gr-utils/src/python/gr_plot_float | 58 +++ gr-utils/src/python/gr_plot_float.py | 58 --- gr-utils/src/python/gr_plot_int | 58 +++ gr-utils/src/python/gr_plot_int.py | 58 --- gr-utils/src/python/gr_plot_iq | 178 +++++++ gr-utils/src/python/gr_plot_iq.py | 178 ------- gr-utils/src/python/gr_plot_psd | 42 ++ gr-utils/src/python/gr_plot_psd.py | 291 ----------- gr-utils/src/python/gr_plot_psd_c | 47 ++ gr-utils/src/python/gr_plot_psd_c.py | 47 -- gr-utils/src/python/gr_plot_psd_f | 47 ++ gr-utils/src/python/gr_plot_psd_f.py | 47 -- gr-utils/src/python/gr_plot_qt | 729 ++++++++++++++++++++++++++ gr-utils/src/python/gr_plot_qt.py | 729 -------------------------- gr-utils/src/python/gr_plot_short | 58 +++ gr-utils/src/python/gr_plot_short.py | 58 --- gr-utils/src/python/plot_fft_base.py | 249 +++++++++ gr-utils/src/python/plot_psd_base.py | 292 +++++++++++ 32 files changed, 3166 insertions(+), 3017 deletions(-) create mode 100755 gr-utils/src/python/gr_filter_design delete mode 100755 gr-utils/src/python/gr_filter_design.py create mode 100755 gr-utils/src/python/gr_plot_char delete mode 100755 gr-utils/src/python/gr_plot_char.py create mode 100755 gr-utils/src/python/gr_plot_const delete mode 100755 gr-utils/src/python/gr_plot_const.py create mode 100644 gr-utils/src/python/gr_plot_fft delete mode 100755 gr-utils/src/python/gr_plot_fft.py create mode 100755 gr-utils/src/python/gr_plot_fft_c delete mode 100755 gr-utils/src/python/gr_plot_fft_c.py create mode 100755 gr-utils/src/python/gr_plot_fft_f delete mode 100755 gr-utils/src/python/gr_plot_fft_f.py create mode 100755 gr-utils/src/python/gr_plot_float delete mode 100755 gr-utils/src/python/gr_plot_float.py create mode 100755 gr-utils/src/python/gr_plot_int delete mode 100755 gr-utils/src/python/gr_plot_int.py create mode 100755 gr-utils/src/python/gr_plot_iq delete mode 100755 gr-utils/src/python/gr_plot_iq.py create mode 100644 gr-utils/src/python/gr_plot_psd delete mode 100755 gr-utils/src/python/gr_plot_psd.py create mode 100755 gr-utils/src/python/gr_plot_psd_c delete mode 100755 gr-utils/src/python/gr_plot_psd_c.py create mode 100755 gr-utils/src/python/gr_plot_psd_f delete mode 100755 gr-utils/src/python/gr_plot_psd_f.py create mode 100755 gr-utils/src/python/gr_plot_qt delete mode 100755 gr-utils/src/python/gr_plot_qt.py create mode 100755 gr-utils/src/python/gr_plot_short delete mode 100755 gr-utils/src/python/gr_plot_short.py create mode 100755 gr-utils/src/python/plot_fft_base.py create mode 100755 gr-utils/src/python/plot_psd_base.py diff --git a/gr-utils/src/python/CMakeLists.txt b/gr-utils/src/python/CMakeLists.txt index fba0d57a5..90caeb234 100644 --- a/gr-utils/src/python/CMakeLists.txt +++ b/gr-utils/src/python/CMakeLists.txt @@ -25,6 +25,8 @@ include(GrPython) GR_PYTHON_INSTALL( FILES plot_data.py + plot_fft_base.py + plot_psd_base.py pyqt_plot.py pyqt_filter.py DESTINATION ${GR_PYTHON_DIR}/gnuradio @@ -34,20 +36,20 @@ GR_PYTHON_INSTALL( GR_PYTHON_INSTALL( PROGRAMS create-gnuradio-out-of-tree-project - gr_plot_char.py - gr_plot_const.py - gr_plot_fft.py - gr_plot_fft_c.py - gr_plot_fft_f.py - gr_plot_psd.py - gr_plot_psd_c.py - gr_plot_psd_f.py - gr_plot_float.py - gr_plot_int.py - gr_plot_iq.py - gr_plot_short.py - gr_plot_qt.py - gr_filter_design.py + gr_plot_char + gr_plot_const + gr_plot_fft + gr_plot_fft_c + gr_plot_fft_f + gr_plot_psd + gr_plot_psd_c + gr_plot_psd_f + gr_plot_float + gr_plot_int + gr_plot_iq + gr_plot_short + gr_plot_qt + gr_filter_design DESTINATION ${GR_RUNTIME_DIR} COMPONENT "utils" ) diff --git a/gr-utils/src/python/README.plot b/gr-utils/src/python/README.plot index 0c4657ba9..60f14c669 100644 --- a/gr-utils/src/python/README.plot +++ b/gr-utils/src/python/README.plot @@ -1,40 +1,97 @@ -* gr_plot_*.py: -These are a collection of Python scripts to enable viewing and analysis of files produced by GNU Radio flow graphs. Most of them work off complex data produced by digital waveforms. +* gr_plot_*: +These are a collection of Python scripts to enable viewing and +analysis of files produced by GNU Radio flow graphs. Most of them work +off complex data produced by digital waveforms. -** gr_plot_float.py: -Takes a GNU Radio floating point binary file and displays the samples versus time. You can set the block size to specify how many points to read in at a time and the start position in the file. +** gr_plot_float: +Takes a GNU Radio floating point binary file and displays the samples +versus time. You can set the block size to specify how many points to +read in at a time and the start position in the file. -By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples. +By default, the system assumes a sample rate of 1, so in time, each +sample is plotted versus the sample number. To set a true time axis, +set the sample rate (-R or --sample-rate) to the sample rate used when +capturing the samples. -** gr_plot_iq.py: -Takes a GNU Radio complex binary file and displays the I&Q data versus time. You can set the block size to specify how many points to read in at a time and the start position in the file. +** gr_plot_iq: +Takes a GNU Radio complex binary file and displays the I&Q data versus +time. You can set the block size to specify how many points to read in +at a time and the start position in the file. -By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples. +By default, the system assumes a sample rate of 1, so in time, each +sample is plotted versus the sample number. To set a true time axis, +set the sample rate (-R or --sample-rate) to the sample rate used when +capturing the samples. -** gr_plot_const.py: -Takes a GNU Radio complex binary file and displays the I&Q data versus time and the constellation plot (I vs. Q). You can set the block size to specify how many points to read in at a time and the start position in the file. +** gr_plot_const: +Takes a GNU Radio complex binary file and displays the I&Q data versus +time and the constellation plot (I vs. Q). You can set the block size +to specify how many points to read in at a time and the start position +in the file. -By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples. +By default, the system assumes a sample rate of 1, so in time, each +sample is plotted versus the sample number. To set a true time axis, +set the sample rate (-R or --sample-rate) to the sample rate used when +capturing the samples. -** gr_plot_fft_c.py: -Takes a GNU Radio complex binary file and displays the I&Q data versus time as well as the frequency domain (FFT) plot. The y-axis values are plotted assuming volts as the amplitude of the I&Q streams and converted into dBm in the frequency domain (the 1/N power adjustment out of the FFT is performed internally). +** gr_plot_fft_c: +Takes a GNU Radio complex binary file and displays the I&Q data versus +time as well as the frequency domain (FFT) plot. The y-axis values are +plotted assuming volts as the amplitude of the I&Q streams and +converted into dBm in the frequency domain (the 1/N power adjustment +out of the FFT is performed internally). -The script plots a certain block of data at a time, specified on the command line as -B or --block. This value defaults to 1000. The start position in the file can be set by specifying -s or --start and defaults to 0 (the start of the file). +The script plots a certain block of data at a time, specified on the +command line as -B or --block. This value defaults to 1000. The start +position in the file can be set by specifying -s or --start and +defaults to 0 (the start of the file). -By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time and frequency axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples. +By default, the system assumes a sample rate of 1, so in time, each +sample is plotted versus the sample number. To set a true time and +frequency axis, set the sample rate (-R or --sample-rate) to the +sample rate used when capturing the samples. -** gr_plot_fft_f.py: -Takes a GNU Radio floating point binary file and displays the samples versus time as well as the frequency domain (FFT) plot. The y-axis values are plotted assuming volts as the amplitude of the I&Q streams and converted into dBm in the frequency domain (the 1/N power adjustment out of the FFT is performed internally). +** gr_plot_fft_f: +Takes a GNU Radio floating point binary file and displays the samples +versus time as well as the frequency domain (FFT) plot. The y-axis +values are plotted assuming volts as the amplitude of the I&Q streams +and converted into dBm in the frequency domain (the 1/N power +adjustment out of the FFT is performed internally). -The script plots a certain block of data at a time, specified on the command line as -B or --block. This value defaults to 1000. The start position in the file can be set by specifying -s or --start and defaults to 0 (the start of the file). +The script plots a certain block of data at a time, specified on the +command line as -B or --block. This value defaults to 1000. The start +position in the file can be set by specifying -s or --start and +defaults to 0 (the start of the file). -By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time and frequency axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples. +By default, the system assumes a sample rate of 1, so in time, each +sample is plotted versus the sample number. To set a true time and +frequency axis, set the sample rate (-R or --sample-rate) to the +sample rate used when capturing the samples. + + +** gr_plot_fft: +Takes a GNU Radio binary file (the datatype is specified using the -d +option and defaults to complex64) and displays the samples versus time +as well as the frequency domain (FFT) plot. The y-axis values are +plotted assuming volts as the amplitude of the I&Q streams and +converted into dBm in the frequency domain (the 1/N power adjustment +out of the FFT is performed internally). + +The script plots a certain block of data at a time, specified on the +command line as -B or --block. This value defaults to 1000. The start +position in the file can be set by specifying -s or --start and +defaults to 0 (the start of the file). + +By default, the system assumes a sample rate of 1, so in time, each +sample is plotted versus the sample number. To set a true time and +frequency axis, set the sample rate (-R or --sample-rate) to the +sample rate used when capturing the samples. diff --git a/gr-utils/src/python/gr_filter_design b/gr-utils/src/python/gr_filter_design new file mode 100755 index 000000000..e8703db4f --- /dev/null +++ b/gr-utils/src/python/gr_filter_design @@ -0,0 +1,874 @@ +#!/usr/bin/env python + +import sys, os, re, csv +from optparse import OptionParser +from gnuradio import gr, blks2, eng_notation + +try: + import scipy + from scipy import fftpack +except ImportError: + print "Please install SciPy to run this script (http://www.scipy.org/)" + raise SystemExit, 1 + +try: + from PyQt4 import Qt, QtCore, QtGui +except ImportError: + print "Please install PyQt4 to run this script (http://www.riverbankcomputing.co.uk/software/pyqt/download)" + raise SystemExit, 1 + +try: + import PyQt4.Qwt5 as Qwt +except ImportError: + print "Please install PyQwt5 to run this script (http://pyqwt.sourceforge.net/)" + raise SystemExit, 1 + +try: + from gnuradio.pyqt_filter import Ui_MainWindow +except ImportError: + print "Could not import from pyqt_filter. Please build with \"pyuic4 pyqt_filter.ui -o pyqt_filter.py\"" + raise SystemExit, 1 + + +class gr_plot_filter(QtGui.QMainWindow): + def __init__(self, qapp, options): + QtGui.QWidget.__init__(self, None) + self.gui = Ui_MainWindow() + self.gui.setupUi(self) + + self.connect(self.gui.action_save, + Qt.SIGNAL("activated()"), + self.action_save_dialog) + self.connect(self.gui.action_open, + Qt.SIGNAL("activated()"), + self.action_open_dialog) + + + self.connect(self.gui.filterTypeComboBox, + Qt.SIGNAL("currentIndexChanged(const QString&)"), + self.changed_filter_type) + self.connect(self.gui.filterDesignTypeComboBox, + Qt.SIGNAL("currentIndexChanged(const QString&)"), + self.changed_filter_design_type) + + self.connect(self.gui.designButton, + Qt.SIGNAL("released()"), + self.design) + + self.connect(self.gui.tabGroup, + Qt.SIGNAL("currentChanged(int)"), + self.tab_changed) + + self.connect(self.gui.nfftEdit, + Qt.SIGNAL("textEdited(QString)"), + self.nfft_edit_changed) + + self.gui.designButton.setShortcut(QtCore.Qt.Key_Return) + + self.taps = [] + self.fftdB = [] + self.fftDeg = [] + self.groupDelay = [] + self.nfftpts = int(10000) + self.gui.nfftEdit.setText(Qt.QString("%1").arg(self.nfftpts)) + + self.firFilters = ("Low Pass", "Band Pass", "Complex Band Pass", "Band Notch", + "High Pass", "Root Raised Cosine", "Gaussian") + self.optFilters = ("Low Pass", "Band Pass", "Complex Band Pass", + "Band Notch", "High Pass") + + self.set_windowed() + + # Initialize to LPF + self.gui.filterTypeWidget.setCurrentWidget(self.gui.firlpfPage) + + # Set Axis labels + self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.xBottom, + "Frequency (Hz)") + self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.yLeft, + "Magnitude (dB)") + self.gui.timePlot.setAxisTitle(self.gui.timePlot.xBottom, + "Tap number") + self.gui.timePlot.setAxisTitle(self.gui.timePlot.yLeft, + "Amplitude") + self.gui.phasePlot.setAxisTitle(self.gui.phasePlot.xBottom, + "Frequency (Hz)") + self.gui.phasePlot.setAxisTitle(self.gui.phasePlot.yLeft, + "Phase (Radians)") + self.gui.groupPlot.setAxisTitle(self.gui.groupPlot.xBottom, + "Frequency (Hz)") + self.gui.groupPlot.setAxisTitle(self.gui.groupPlot.yLeft, + "Delay (sec)") + + # Set up plot curves + self.rcurve = Qwt.QwtPlotCurve("Real") + self.rcurve.attach(self.gui.timePlot) + self.icurve = Qwt.QwtPlotCurve("Imag") + self.icurve.attach(self.gui.timePlot) + + self.freqcurve = Qwt.QwtPlotCurve("PSD") + self.freqcurve.attach(self.gui.freqPlot) + + self.phasecurve = Qwt.QwtPlotCurve("Phase") + self.phasecurve.attach(self.gui.phasePlot) + + self.groupcurve = Qwt.QwtPlotCurve("Group Delay") + self.groupcurve.attach(self.gui.groupPlot) + + # Create zoom functionality for the plots + self.timeZoomer = Qwt.QwtPlotZoomer(self.gui.timePlot.xBottom, + self.gui.timePlot.yLeft, + Qwt.QwtPicker.PointSelection, + Qwt.QwtPicker.AlwaysOn, + self.gui.timePlot.canvas()) + + self.freqZoomer = Qwt.QwtPlotZoomer(self.gui.freqPlot.xBottom, + self.gui.freqPlot.yLeft, + Qwt.QwtPicker.PointSelection, + Qwt.QwtPicker.AlwaysOn, + self.gui.freqPlot.canvas()) + + self.phaseZoomer = Qwt.QwtPlotZoomer(self.gui.phasePlot.xBottom, + self.gui.phasePlot.yLeft, + Qwt.QwtPicker.PointSelection, + Qwt.QwtPicker.AlwaysOn, + self.gui.phasePlot.canvas()) + + self.groupZoomer = Qwt.QwtPlotZoomer(self.gui.groupPlot.xBottom, + self.gui.groupPlot.yLeft, + Qwt.QwtPicker.PointSelection, + Qwt.QwtPicker.AlwaysOn, + self.gui.groupPlot.canvas()) + + # Set up pen for colors and line width + blue = QtGui.qRgb(0x00, 0x00, 0xFF) + blueBrush = Qt.QBrush(Qt.QColor(blue)) + red = QtGui.qRgb(0xFF, 0x00, 0x00) + redBrush = Qt.QBrush(Qt.QColor(red)) + self.freqcurve.setPen(Qt.QPen(blueBrush, 2)) + self.rcurve.setPen(Qt.QPen(blueBrush, 2)) + self.icurve.setPen(Qt.QPen(redBrush, 2)) + self.phasecurve.setPen(Qt.QPen(blueBrush, 2)) + self.groupcurve.setPen(Qt.QPen(blueBrush, 2)) + + # Set up validators for edit boxes + self.intVal = Qt.QIntValidator(None) + self.dblVal = Qt.QDoubleValidator(None) + self.gui.nfftEdit.setValidator(self.intVal) + self.gui.sampleRateEdit.setValidator(self.dblVal) + self.gui.filterGainEdit.setValidator(self.dblVal) + self.gui.endofLpfPassBandEdit.setValidator(self.dblVal) + self.gui.startofLpfStopBandEdit.setValidator(self.dblVal) + self.gui.lpfStopBandAttenEdit.setValidator(self.dblVal) + self.gui.lpfPassBandRippleEdit.setValidator(self.dblVal) + self.gui.startofBpfPassBandEdit.setValidator(self.dblVal) + self.gui.endofBpfPassBandEdit.setValidator(self.dblVal) + self.gui.bpfTransitionEdit.setValidator(self.dblVal) + self.gui.bpfStopBandAttenEdit.setValidator(self.dblVal) + self.gui.bpfPassBandRippleEdit.setValidator(self.dblVal) + self.gui.startofBnfStopBandEdit.setValidator(self.dblVal) + self.gui.endofBnfStopBandEdit.setValidator(self.dblVal) + self.gui.bnfTransitionEdit.setValidator(self.dblVal) + self.gui.bnfStopBandAttenEdit.setValidator(self.dblVal) + self.gui.bnfPassBandRippleEdit.setValidator(self.dblVal) + self.gui.endofHpfStopBandEdit.setValidator(self.dblVal) + self.gui.startofHpfPassBandEdit.setValidator(self.dblVal) + self.gui.hpfStopBandAttenEdit.setValidator(self.dblVal) + self.gui.hpfPassBandRippleEdit.setValidator(self.dblVal) + self.gui.rrcSymbolRateEdit.setValidator(self.dblVal) + self.gui.rrcAlphaEdit.setValidator(self.dblVal) + self.gui.rrcNumTapsEdit.setValidator(self.dblVal) + self.gui.gausSymbolRateEdit.setValidator(self.dblVal) + self.gui.gausBTEdit.setValidator(self.dblVal) + self.gui.gausNumTapsEdit.setValidator(self.dblVal) + + self.gui.nTapsEdit.setText("0") + + self.filterWindows = {"Hamming Window" : gr.firdes.WIN_HAMMING, + "Hann Window" : gr.firdes.WIN_HANN, + "Blackman Window" : gr.firdes.WIN_BLACKMAN, + "Rectangular Window" : gr.firdes.WIN_RECTANGULAR, + "Kaiser Window" : gr.firdes.WIN_KAISER, + "Blackman-harris Window" : gr.firdes.WIN_BLACKMAN_hARRIS} + self.EQUIRIPPLE_FILT = 6 # const for equiripple filter window types + self.show() + + def changed_filter_type(self, ftype): + strftype = str(ftype.toAscii()) + if(ftype == "Low Pass"): + self.gui.filterTypeWidget.setCurrentWidget(self.gui.firlpfPage) + elif(ftype == "Band Pass"): + self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbpfPage) + elif(ftype == "Complex Band Pass"): + self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbpfPage) + elif(ftype == "Band Notch"): + self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbnfPage) + elif(ftype == "High Pass"): + self.gui.filterTypeWidget.setCurrentWidget(self.gui.firhpfPage) + elif(ftype == "Root Raised Cosine"): + self.gui.filterTypeWidget.setCurrentWidget(self.gui.rrcPage) + elif(ftype == "Gaussian"): + self.gui.filterTypeWidget.setCurrentWidget(self.gui.gausPage) + + self.design() + + def changed_filter_design_type(self, design): + if(design == "Equiripple"): + self.set_equiripple() + else: + self.set_windowed() + + self.design() + + def set_equiripple(self): + # Stop sending the signal for this function + self.gui.filterTypeComboBox.blockSignals(True) + + self.equiripple = True + self.gui.lpfPassBandRippleLabel.setVisible(True) + self.gui.lpfPassBandRippleEdit.setVisible(True) + self.gui.bpfPassBandRippleLabel.setVisible(True) + self.gui.bpfPassBandRippleEdit.setVisible(True) + self.gui.bnfPassBandRippleLabel.setVisible(True) + self.gui.bnfPassBandRippleEdit.setVisible(True) + self.gui.hpfPassBandRippleLabel.setVisible(True) + self.gui.hpfPassBandRippleEdit.setVisible(True) + + # Save current type and repopulate the combo box for + # filters this window type can handle + currenttype = self.gui.filterTypeComboBox.currentText() + items = self.gui.filterTypeComboBox.count() + for i in xrange(items): + self.gui.filterTypeComboBox.removeItem(0) + self.gui.filterTypeComboBox.addItems(self.optFilters) + + # If the last filter type was valid for this window type, + # go back to it; otherwise, reset + try: + index = self.optFilters.index(currenttype) + self.gui.filterTypeComboBox.setCurrentIndex(index) + except ValueError: + pass + + # Tell gui its ok to start sending this signal again + self.gui.filterTypeComboBox.blockSignals(False) + + def set_windowed(self): + # Stop sending the signal for this function + self.gui.filterTypeComboBox.blockSignals(True) + + self.equiripple = False + self.gui.lpfPassBandRippleLabel.setVisible(False) + self.gui.lpfPassBandRippleEdit.setVisible(False) + self.gui.bpfPassBandRippleLabel.setVisible(False) + self.gui.bpfPassBandRippleEdit.setVisible(False) + self.gui.bnfPassBandRippleLabel.setVisible(False) + self.gui.bnfPassBandRippleEdit.setVisible(False) + self.gui.hpfPassBandRippleLabel.setVisible(False) + self.gui.hpfPassBandRippleEdit.setVisible(False) + + # Save current type and repopulate the combo box for + # filters this window type can handle + currenttype = self.gui.filterTypeComboBox.currentText() + items = self.gui.filterTypeComboBox.count() + for i in xrange(items): + self.gui.filterTypeComboBox.removeItem(0) + self.gui.filterTypeComboBox.addItems(self.firFilters) + + # If the last filter type was valid for this window type, + # go back to it; otherwise, reset + try: + index = self.optFilters.index(currenttype) + self.gui.filterTypeComboBox.setCurrentIndex(index) + except ValueError: + pass + + # Tell gui its ok to start sending this signal again + self.gui.filterTypeComboBox.blockSignals(False) + + def design(self): + ret = True + fs,r = self.gui.sampleRateEdit.text().toDouble() + ret = r and ret + gain,r = self.gui.filterGainEdit.text().toDouble() + ret = r and ret + + if(ret): + winstr = str(self.gui.filterDesignTypeComboBox.currentText().toAscii()) + ftype = str(self.gui.filterTypeComboBox.currentText().toAscii()) + + if(winstr == "Equiripple"): + designer = {"Low Pass" : self.design_opt_lpf, + "Band Pass" : self.design_opt_bpf, + "Complex Band Pass" : self.design_opt_cbpf, + "Band Notch" : self.design_opt_bnf, + "High Pass" : self.design_opt_hpf} + taps,params,r = designer[ftype](fs, gain) + + else: + designer = {"Low Pass" : self.design_win_lpf, + "Band Pass" : self.design_win_bpf, + "Complex Band Pass" : self.design_win_cbpf, + "Band Notch" : self.design_win_bnf, + "High Pass" : self.design_win_hpf, + "Root Raised Cosine" : self.design_win_rrc, + "Gaussian" : self.design_win_gaus} + wintype = self.filterWindows[winstr] + taps,params,r = designer[ftype](fs, gain, wintype) + + if(r): + self.draw_plots(taps, params) + + + # Filter design functions using a window + def design_win_lpf(self, fs, gain, wintype): + ret = True + pb,r = self.gui.endofLpfPassBandEdit.text().toDouble() + ret = r and ret + sb,r = self.gui.startofLpfStopBandEdit.text().toDouble() + ret = r and ret + atten,r = self.gui.lpfStopBandAttenEdit.text().toDouble() + ret = r and ret + + if(ret): + tb = sb - pb + + taps = gr.firdes.low_pass_2(gain, fs, pb, tb, + atten, wintype) + params = {"fs": fs, "gain": gain, "wintype": wintype, + "filttype": "lpf", "pbend": pb, "sbstart": sb, + "atten": atten, "ntaps": len(taps)} + return (taps, params, ret) + else: + return ([], [], ret) + + def design_win_bpf(self, fs, gain, wintype): + ret = True + pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble() + ret = r and ret + pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble() + ret = r and ret + tb,r = self.gui.bpfTransitionEdit.text().toDouble() + ret = r and ret + atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble() + ret = r and ret + + if(r): + taps = gr.firdes.band_pass_2(gain, fs, pb1, pb2, tb, + atten, wintype) + params = {"fs": fs, "gain": gain, "wintype": wintype, + "filttype": "bpf", "pbstart": pb1, "pbend": pb2, + "tb": tb, "atten": atten, "ntaps": len(taps)} + return (taps,params,r) + else: + return ([],[],r) + + def design_win_cbpf(self, fs, gain, wintype): + ret = True + pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble() + ret = r and ret + pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble() + ret = r and ret + tb,r = self.gui.bpfTransitionEdit.text().toDouble() + ret = r and ret + atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble() + ret = r and ret + + if(r): + taps = gr.firdes.complex_band_pass_2(gain, fs, pb1, pb2, tb, + atten, wintype) + params = {"fs": fs, "gain": gain, "wintype": wintype, + "filttype": "cbpf", "pbstart": pb1, "pbend": pb2, + "tb": tb, "atten": atten, "ntaps": len(taps)} + return (taps,params,r) + else: + return ([],[],r) + + def design_win_bnf(self, fs, gain, wintype): + ret = True + pb1,r = self.gui.startofBnfStopBandEdit.text().toDouble() + ret = r and ret + pb2,r = self.gui.endofBnfStopBandEdit.text().toDouble() + ret = r and ret + tb,r = self.gui.bnfTransitionEdit.text().toDouble() + ret = r and ret + atten,r = self.gui.bnfStopBandAttenEdit.text().toDouble() + ret = r and ret + + if(r): + taps = gr.firdes.band_reject_2(gain, fs, pb1, pb2, tb, + atten, wintype) + params = {"fs": fs, "gain": gain, "wintype": wintype, + "filttype": "bnf", "sbstart": pb1, "sbend": pb2, + "tb": tb, "atten": atten, "ntaps": len(taps)} + return (taps,params,r) + else: + return ([],[],r) + + def design_win_hpf(self, fs, gain, wintype): + ret = True + sb,r = self.gui.endofHpfStopBandEdit.text().toDouble() + ret = r and ret + pb,r = self.gui.startofHpfPassBandEdit.text().toDouble() + ret = r and ret + atten,r = self.gui.hpfStopBandAttenEdit.text().toDouble() + ret = r and ret + + if(r): + tb = pb - sb + taps = gr.firdes.high_pass_2(gain, fs, pb, tb, + atten, wintype) + params = {"fs": fs, "gain": gain, "wintype": wintype, + "filttype": "hpf", "sbend": sb, "pbstart": pb, + "atten": atten, "ntaps": len(taps)} + return (taps,params,r) + else: + return ([],[],r) + + def design_win_rrc(self, fs, gain, wintype): + ret = True + sr,r = self.gui.rrcSymbolRateEdit.text().toDouble() + ret = r and ret + alpha,r = self.gui.rrcAlphaEdit.text().toDouble() + ret = r and ret + ntaps,r = self.gui.rrcNumTapsEdit.text().toInt() + ret = r and ret + + if(r): + taps = gr.firdes.root_raised_cosine(gain, fs, sr, + alpha, ntaps) + params = {"fs": fs, "gain": gain, "wintype": wintype, + "filttype": "rrc", "srate": sr, "alpha": alpha, + "ntaps": ntaps} + return (taps,params,r) + else: + return ([],[],r) + + def design_win_gaus(self, fs, gain, wintype): + ret = True + sr,r = self.gui.gausSymbolRateEdit.text().toDouble() + ret = r and ret + bt,r = self.gui.gausBTEdit.text().toDouble() + ret = r and ret + ntaps,r = self.gui.gausNumTapsEdit.text().toInt() + ret = r and ret + + if(r): + spb = fs / sr + taps = gr.firdes.gaussian(gain, spb, bt, ntaps) + params = {"fs": fs, "gain": gain, "wintype": wintype, + "filttype": "gaus", "srate": sr, "bt": bt, + "ntaps": ntaps} + return (taps,params,r) + else: + return ([],[],r) + + # Design Functions for Equiripple Filters + def design_opt_lpf(self, fs, gain): + ret = True + pb,r = self.gui.endofLpfPassBandEdit.text().toDouble() + ret = r and ret + sb,r = self.gui.startofLpfStopBandEdit.text().toDouble() + ret = r and ret + atten,r = self.gui.lpfStopBandAttenEdit.text().toDouble() + ret = r and ret + ripple,r = self.gui.lpfPassBandRippleEdit.text().toDouble() + ret = r and ret + + if(ret): + try: + taps = blks2.optfir.low_pass(gain, fs, pb, sb, + ripple, atten) + except RuntimeError, e: + reply = QtGui.QMessageBox.information(self, "Filter did not converge", + e.args[0], "&Ok") + return ([],[],False) + else: + params = {"fs": fs, "gain": gain, "wintype": self.EQUIRIPPLE_FILT, + "filttype": "lpf", "pbend": pb, "sbstart": sb, + "atten": atten, "ripple": ripple, "ntaps": len(taps)} + return (taps, params, ret) + else: + return ([], [], ret) + + def design_opt_bpf(self, fs, gain): + ret = True + pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble() + ret = r and ret + pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble() + ret = r and ret + tb,r = self.gui.bpfTransitionEdit.text().toDouble() + ret = r and ret + atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble() + ret = r and ret + ripple,r = self.gui.bpfPassBandRippleEdit.text().toDouble() + ret = r and ret + + if(r): + sb1 = pb1 - tb + sb2 = pb2 + tb + try: + taps = blks2.optfir.band_pass(gain, fs, sb1, pb1, pb2, sb2, + ripple, atten) + except RuntimeError, e: + reply = QtGui.QMessageBox.information(self, "Filter did not converge", + e.args[0], "&Ok") + return ([],[],False) + + else: + params = {"fs": fs, "gain": gain, "wintype": self.EQUIRIPPLE_FILT, + "filttype": "bpf", "pbstart": pb1, "pbend": pb2, + "tb": tb, "atten": atten, "ripple": ripple, + "ntaps": len(taps)} + return (taps,params,r) + else: + return ([],[],r) + + def design_opt_cbpf(self, fs, gain): + ret = True + pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble() + ret = r and ret + pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble() + ret = r and ret + tb,r = self.gui.bpfTransitionEdit.text().toDouble() + ret = r and ret + atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble() + ret = r and ret + ripple,r = self.gui.bpfPassBandRippleEdit.text().toDouble() + ret = r and ret + + if(r): + sb1 = pb1 - tb + sb2 = pb2 + tb + try: + taps = blks2.optfir.complex_band_pass(gain, fs, sb1, pb1, pb2, sb2, + ripple, atten) + except RuntimeError, e: + reply = QtGui.QMessageBox.information(self, "Filter did not converge", + e.args[0], "&Ok") + return ([],[],False) + else: + params = {"fs": fs, "gain": gain, "wintype": self.EQUIRIPPLE_FILT, + "filttype": "cbpf", "pbstart": pb1, "pbend": pb2, + "tb": tb, "atten": atten, "ripple": ripple, + "ntaps": len(taps)} + return (taps,params,r) + else: + return ([],[],r) + + def design_opt_bnf(self, fs, gain): + ret = True + sb1,r = self.gui.startofBnfStopBandEdit.text().toDouble() + ret = r and ret + sb2,r = self.gui.endofBnfStopBandEdit.text().toDouble() + ret = r and ret + tb,r = self.gui.bnfTransitionEdit.text().toDouble() + ret = r and ret + atten,r = self.gui.bnfStopBandAttenEdit.text().toDouble() + ret = r and ret + ripple,r = self.gui.bnfPassBandRippleEdit.text().toDouble() + ret = r and ret + + if(r): + pb1 = sb1 - tb + pb2 = sb2 + tb + try: + taps = blks2.optfir.band_reject(gain, fs, pb1, sb1, sb2, pb2, + ripple, atten) + except RuntimeError, e: + reply = QtGui.QMessageBox.information(self, "Filter did not converge", + e.args[0], "&Ok") + return ([],[],False) + else: + params = {"fs": fs, "gain": gain, "wintype": self.EQUIRIPPLE_FILT, + "filttype": "bnf", "sbstart": pb1, "sbend": pb2, + "tb": tb, "atten": atten, "ripple": ripple, + "ntaps": len(taps)} + return (taps,params,r) + else: + return ([],[],r) + + def design_opt_hpf(self, fs, gain): + ret = True + sb,r = self.gui.endofHpfStopBandEdit.text().toDouble() + ret = r and ret + pb,r = self.gui.startofHpfPassBandEdit.text().toDouble() + ret = r and ret + atten,r = self.gui.hpfStopBandAttenEdit.text().toDouble() + ret = r and ret + ripple,r = self.gui.hpfPassBandRippleEdit.text().toDouble() + ret = r and ret + + if(r): + try: + taps = blks2.optfir.high_pass(gain, fs, sb, pb, + atten, ripple) + except RuntimeError, e: + reply = QtGui.QMessageBox.information(self, "Filter did not converge", + e.args[0], "&Ok") + return ([],[],False) + else: + params = {"fs": fs, "gain": gain, "wintype": self.EQUIRIPPLE_FILT, + "filttype": "hpf", "sbend": sb, "pbstart": pb, + "atten": atten, "ripple": ripple, + "ntaps": len(taps)} + return (taps,params,r) + else: + return ([],[],r) + + def nfft_edit_changed(self, nfft): + infft,r = nfft.toInt() + if(r and (infft != self.nfftpts)): + self.nfftpts = infft + self.update_freq_curves() + + def tab_changed(self, tab): + if(tab == 0): + self.update_freq_curves() + if(tab == 1): + self.update_time_curves() + if(tab == 2): + self.update_phase_curves() + if(tab == 3): + self.update_group_curves() + + def get_fft(self, fs, taps, Npts): + Ts = 1.0/fs + fftpts = fftpack.fft(taps, Npts) + self.freq = scipy.arange(0, fs, 1.0/(Npts*Ts)) + self.fftdB = 20.0*scipy.log10(abs(fftpts)) + self.fftDeg = scipy.unwrap(scipy.angle(fftpts)) + self.groupDelay = -scipy.diff(self.fftDeg) + + def update_time_curves(self): + ntaps = len(self.taps) + if(ntaps > 0): + if(type(self.taps[0]) == scipy.complex128): + self.rcurve.setData(scipy.arange(ntaps), self.taps.real) + self.icurve.setData(scipy.arange(ntaps), self.taps.imag) + else: + self.rcurve.setData(scipy.arange(ntaps), self.taps) + + # Reset the x-axis to the new time scale + ymax = 1.5 * max(self.taps) + ymin = 1.5 * min(self.taps) + self.gui.timePlot.setAxisScale(self.gui.timePlot.xBottom, + 0, ntaps) + self.gui.timePlot.setAxisScale(self.gui.timePlot.yLeft, + ymin, ymax) + + # Set the zoomer base to unzoom to the new axis + self.timeZoomer.setZoomBase() + + self.gui.timePlot.replot() + + def update_freq_curves(self): + npts = len(self.fftdB) + if(npts > 0): + self.freqcurve.setData(self.freq, self.fftdB) + + # Reset the x-axis to the new time scale + ymax = 1.5 * max(self.fftdB[0:npts/2]) + ymin = 1.1 * min(self.fftdB[0:npts/2]) + xmax = self.freq[npts/2] + xmin = self.freq[0] + self.gui.freqPlot.setAxisScale(self.gui.freqPlot.xBottom, + xmin, xmax) + self.gui.freqPlot.setAxisScale(self.gui.freqPlot.yLeft, + ymin, ymax) + + # Set the zoomer base to unzoom to the new axis + self.freqZoomer.setZoomBase() + + self.gui.freqPlot.replot() + + + def update_phase_curves(self): + npts = len(self.fftDeg) + if(npts > 0): + self.phasecurve.setData(self.freq, self.fftDeg) + + # Reset the x-axis to the new time scale + ymax = 1.5 * max(self.fftDeg[0:npts/2]) + ymin = 1.1 * min(self.fftDeg[0:npts/2]) + xmax = self.freq[npts/2] + xmin = self.freq[0] + self.gui.phasePlot.setAxisScale(self.gui.phasePlot.xBottom, + xmin, xmax) + self.gui.phasePlot.setAxisScale(self.gui.phasePlot.yLeft, + ymin, ymax) + + # Set the zoomer base to unzoom to the new axis + self.phaseZoomer.setZoomBase() + + self.gui.phasePlot.replot() + + def update_group_curves(self): + npts = len(self.groupDelay) + if(npts > 0): + self.groupcurve.setData(self.freq, self.groupDelay) + + # Reset the x-axis to the new time scale + ymax = 1.5 * max(self.groupDelay[0:npts/2]) + ymin = 1.1 * min(self.groupDelay[0:npts/2]) + xmax = self.freq[npts/2] + xmin = self.freq[0] + self.gui.groupPlot.setAxisScale(self.gui.groupPlot.xBottom, + xmin, xmax) + self.gui.groupPlot.setAxisScale(self.gui.groupPlot.yLeft, + ymin, ymax) + + # Set the zoomer base to unzoom to the new axis + self.groupZoomer.setZoomBase() + + self.gui.groupPlot.replot() + + def action_save_dialog(self): + filename = QtGui.QFileDialog.getSaveFileName(self, "Save CSV Filter File", ".", "") + try: + handle = open(filename, "wb") + except IOError: + reply = QtGui.QMessageBox.information(self, 'File Name', + ("Could not save to file: %s" % filename), + "&Ok") + return + + csvhandle = csv.writer(handle, delimiter=",") + for k in self.params.keys(): + csvhandle.writerow([k, self.params[k]]) + csvhandle.writerow(["taps",] + self.taps.tolist()) + handle.close() + + def action_open_dialog(self): + filename = QtGui.QFileDialog.getOpenFileName(self, "Open CSV Filter File", ".", "") + if(len(filename) == 0): + return + + try: + handle = open(filename, "rb") + except IOError: + reply = QtGui.QMessageBox.information(self, 'File Name', + ("Could not open file: %s" % filename), + "&Ok") + return + + csvhandle = csv.reader(handle, delimiter=",") + taps = [] + params = {} + for row in csvhandle: + if(row[0] != "taps"): + testcpx = re.findall("[+-]?\d+\.*\d*[Ee]?[-+]?\d+j", row[1]) + if(len(testcpx) > 0): # it's a complex + params[row[0]] = complex(row[1]) + else: # assume it's a float + try: # if it's not a float, its a string + params[row[0]] = float(row[1]) + except ValueError: + params[row[0]] = row[1] + else: + testcpx = re.findall("[+-]?\d+\.*\d*[Ee]?[-+]?\d+j", row[1]) + if(len(testcpx) > 0): # it's a complex + taps = [complex(r) for r in row[1:]] + else: + taps = [float(r) for r in row[1:]] + handle.close() + self.draw_plots(taps, params) + + self.gui.sampleRateEdit.setText(Qt.QString("%1").arg(params["fs"])) + self.gui.filterGainEdit.setText(Qt.QString("%1").arg(params["gain"])) + + # Set up GUI parameters for each filter type + if(params["filttype"] == "lpf"): + self.gui.filterTypeComboBox.setCurrentIndex(0) + self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) + + self.gui.endofLpfPassBandEdit.setText(Qt.QString("%1").arg(params["pbend"])) + self.gui.startofLpfStopBandEdit.setText(Qt.QString("%1").arg(params["sbstart"])) + self.gui.lpfStopBandAttenEdit.setText(Qt.QString("%1").arg(params["atten"])) + if(params["wintype"] == self.EQUIRIPPLE_FILT): + self.gui.lpfPassBandRippleEdit.setText(Qt.QString("%1").arg(params["ripple"])) + elif(params["filttype"] == "bpf"): + self.gui.filterTypeComboBox.setCurrentIndex(1) + self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) + + self.gui.startofBpfPassBandEdit.setText(Qt.QString("%1").arg(params["pbstart"])) + self.gui.endofBpfPassBandEdit.setText(Qt.QString("%1").arg(params["pbend"])) + self.gui.bpfTransitionEdit.setText(Qt.QString("%1").arg(params["tb"])) + self.gui.bpfStopBandAttenEdit.setText(Qt.QString("%1").arg(params["atten"])) + if(params["wintype"] == self.EQUIRIPPLE_FILT): + self.gui.bpfPassBandRippleEdit.setText(Qt.QString("%1").arg(params["ripple"])) + elif(params["filttype"] == "cbpf"): + self.gui.filterTypeComboBox.setCurrentIndex(2) + self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) + + self.gui.startofBpfPassBandEdit.setText(Qt.QString("%1").arg(params["pbstart"])) + self.gui.endofBpfPassBandEdit.setText(Qt.QString("%1").arg(params["pbend"])) + self.gui.bpfTransitionEdit.setText(Qt.QString("%1").arg(params["tb"])) + self.gui.bpfStopBandAttenEdit.setText(Qt.QString("%1").arg(params["atten"])) + if(params["wintype"] == self.EQUIRIPPLE_FILT): + self.gui.bpfPassBandRippleEdit.setText(Qt.QString("%1").arg(params["ripple"])) + elif(params["filttype"] == "bnf"): + self.gui.filterTypeComboBox.setCurrentIndex(3) + self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) + + self.gui.startofBnfStopBandEdit.setText(Qt.QString("%1").arg(params["sbstart"])) + self.gui.endofBnfStopBandEdit.setText(Qt.QString("%1").arg(params["sbend"])) + self.gui.bnfTransitionEdit.setText(Qt.QString("%1").arg(params["tb"])) + self.gui.bnfStopBandAttenEdit.setText(Qt.QString("%1").arg(params["atten"])) + if(params["wintype"] == self.EQUIRIPPLE_FILT): + self.gui.bnfPassBandRippleEdit.setText(Qt.QString("%1").arg(params["ripple"])) + elif(params["filttype"] == "hpf"): + self.gui.filterTypeComboBox.setCurrentIndex(4) + self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) + + self.gui.endofHpfStopBandEdit.setText(Qt.QString("%1").arg(params["sbend"])) + self.gui.startofHpfPassBandEdit.setText(Qt.QString("%1").arg(params["pbstart"])) + self.gui.hpfStopBandAttenEdit.setText(Qt.QString("%1").arg(params["atten"])) + if(params["wintype"] == self.EQUIRIPPLE_FILT): + self.gui.hpfPassBandRippleEdit.setText(Qt.QString("%1").arg(params["ripple"])) + elif(params["filttype"] == "rrc"): + self.gui.filterTypeComboBox.setCurrentIndex(5) + self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) + + self.gui.rrcSymbolRateEdit.setText(Qt.QString("%1").arg(params["srate"])) + self.gui.rrcAlphaEdit.setText(Qt.QString("%1").arg(params["alpha"])) + self.gui.rrcNumTapsEdit.setText(Qt.QString("%1").arg(params["ntaps"])) + elif(params["filttype"] == "gaus"): + self.gui.filterTypeComboBox.setCurrentIndex(6) + self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) + + self.gui.gausSymbolRateEdit.setText(Qt.QString("%1").arg(params["srate"])) + self.gui.gausBTEdit.setText(Qt.QString("%1").arg(params["bt"])) + self.gui.gausNumTapsEdit.setText(Qt.QString("%1").arg(params["ntaps"])) + + def draw_plots(self, taps, params): + self.params = params + self.taps = scipy.array(taps) + self.get_fft(self.params["fs"], self.taps, self.nfftpts) + self.update_time_curves() + self.update_freq_curves() + self.update_phase_curves() + self.update_group_curves() + + self.gui.nTapsEdit.setText(Qt.QString("%1").arg(self.taps.size)) + + +def setup_options(): + usage="%prog: [options] (input_filename)" + description = "" + + parser = OptionParser(conflict_handler="resolve", + usage=usage, description=description) + return parser + +def main(args): + parser = setup_options() + (options, args) = parser.parse_args () + + app = Qt.QApplication(args) + gplt = gr_plot_filter(app, options) + app.exec_() + +if __name__ == '__main__': + main(sys.argv) + diff --git a/gr-utils/src/python/gr_filter_design.py b/gr-utils/src/python/gr_filter_design.py deleted file mode 100755 index e8703db4f..000000000 --- a/gr-utils/src/python/gr_filter_design.py +++ /dev/null @@ -1,874 +0,0 @@ -#!/usr/bin/env python - -import sys, os, re, csv -from optparse import OptionParser -from gnuradio import gr, blks2, eng_notation - -try: - import scipy - from scipy import fftpack -except ImportError: - print "Please install SciPy to run this script (http://www.scipy.org/)" - raise SystemExit, 1 - -try: - from PyQt4 import Qt, QtCore, QtGui -except ImportError: - print "Please install PyQt4 to run this script (http://www.riverbankcomputing.co.uk/software/pyqt/download)" - raise SystemExit, 1 - -try: - import PyQt4.Qwt5 as Qwt -except ImportError: - print "Please install PyQwt5 to run this script (http://pyqwt.sourceforge.net/)" - raise SystemExit, 1 - -try: - from gnuradio.pyqt_filter import Ui_MainWindow -except ImportError: - print "Could not import from pyqt_filter. Please build with \"pyuic4 pyqt_filter.ui -o pyqt_filter.py\"" - raise SystemExit, 1 - - -class gr_plot_filter(QtGui.QMainWindow): - def __init__(self, qapp, options): - QtGui.QWidget.__init__(self, None) - self.gui = Ui_MainWindow() - self.gui.setupUi(self) - - self.connect(self.gui.action_save, - Qt.SIGNAL("activated()"), - self.action_save_dialog) - self.connect(self.gui.action_open, - Qt.SIGNAL("activated()"), - self.action_open_dialog) - - - self.connect(self.gui.filterTypeComboBox, - Qt.SIGNAL("currentIndexChanged(const QString&)"), - self.changed_filter_type) - self.connect(self.gui.filterDesignTypeComboBox, - Qt.SIGNAL("currentIndexChanged(const QString&)"), - self.changed_filter_design_type) - - self.connect(self.gui.designButton, - Qt.SIGNAL("released()"), - self.design) - - self.connect(self.gui.tabGroup, - Qt.SIGNAL("currentChanged(int)"), - self.tab_changed) - - self.connect(self.gui.nfftEdit, - Qt.SIGNAL("textEdited(QString)"), - self.nfft_edit_changed) - - self.gui.designButton.setShortcut(QtCore.Qt.Key_Return) - - self.taps = [] - self.fftdB = [] - self.fftDeg = [] - self.groupDelay = [] - self.nfftpts = int(10000) - self.gui.nfftEdit.setText(Qt.QString("%1").arg(self.nfftpts)) - - self.firFilters = ("Low Pass", "Band Pass", "Complex Band Pass", "Band Notch", - "High Pass", "Root Raised Cosine", "Gaussian") - self.optFilters = ("Low Pass", "Band Pass", "Complex Band Pass", - "Band Notch", "High Pass") - - self.set_windowed() - - # Initialize to LPF - self.gui.filterTypeWidget.setCurrentWidget(self.gui.firlpfPage) - - # Set Axis labels - self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.xBottom, - "Frequency (Hz)") - self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.yLeft, - "Magnitude (dB)") - self.gui.timePlot.setAxisTitle(self.gui.timePlot.xBottom, - "Tap number") - self.gui.timePlot.setAxisTitle(self.gui.timePlot.yLeft, - "Amplitude") - self.gui.phasePlot.setAxisTitle(self.gui.phasePlot.xBottom, - "Frequency (Hz)") - self.gui.phasePlot.setAxisTitle(self.gui.phasePlot.yLeft, - "Phase (Radians)") - self.gui.groupPlot.setAxisTitle(self.gui.groupPlot.xBottom, - "Frequency (Hz)") - self.gui.groupPlot.setAxisTitle(self.gui.groupPlot.yLeft, - "Delay (sec)") - - # Set up plot curves - self.rcurve = Qwt.QwtPlotCurve("Real") - self.rcurve.attach(self.gui.timePlot) - self.icurve = Qwt.QwtPlotCurve("Imag") - self.icurve.attach(self.gui.timePlot) - - self.freqcurve = Qwt.QwtPlotCurve("PSD") - self.freqcurve.attach(self.gui.freqPlot) - - self.phasecurve = Qwt.QwtPlotCurve("Phase") - self.phasecurve.attach(self.gui.phasePlot) - - self.groupcurve = Qwt.QwtPlotCurve("Group Delay") - self.groupcurve.attach(self.gui.groupPlot) - - # Create zoom functionality for the plots - self.timeZoomer = Qwt.QwtPlotZoomer(self.gui.timePlot.xBottom, - self.gui.timePlot.yLeft, - Qwt.QwtPicker.PointSelection, - Qwt.QwtPicker.AlwaysOn, - self.gui.timePlot.canvas()) - - self.freqZoomer = Qwt.QwtPlotZoomer(self.gui.freqPlot.xBottom, - self.gui.freqPlot.yLeft, - Qwt.QwtPicker.PointSelection, - Qwt.QwtPicker.AlwaysOn, - self.gui.freqPlot.canvas()) - - self.phaseZoomer = Qwt.QwtPlotZoomer(self.gui.phasePlot.xBottom, - self.gui.phasePlot.yLeft, - Qwt.QwtPicker.PointSelection, - Qwt.QwtPicker.AlwaysOn, - self.gui.phasePlot.canvas()) - - self.groupZoomer = Qwt.QwtPlotZoomer(self.gui.groupPlot.xBottom, - self.gui.groupPlot.yLeft, - Qwt.QwtPicker.PointSelection, - Qwt.QwtPicker.AlwaysOn, - self.gui.groupPlot.canvas()) - - # Set up pen for colors and line width - blue = QtGui.qRgb(0x00, 0x00, 0xFF) - blueBrush = Qt.QBrush(Qt.QColor(blue)) - red = QtGui.qRgb(0xFF, 0x00, 0x00) - redBrush = Qt.QBrush(Qt.QColor(red)) - self.freqcurve.setPen(Qt.QPen(blueBrush, 2)) - self.rcurve.setPen(Qt.QPen(blueBrush, 2)) - self.icurve.setPen(Qt.QPen(redBrush, 2)) - self.phasecurve.setPen(Qt.QPen(blueBrush, 2)) - self.groupcurve.setPen(Qt.QPen(blueBrush, 2)) - - # Set up validators for edit boxes - self.intVal = Qt.QIntValidator(None) - self.dblVal = Qt.QDoubleValidator(None) - self.gui.nfftEdit.setValidator(self.intVal) - self.gui.sampleRateEdit.setValidator(self.dblVal) - self.gui.filterGainEdit.setValidator(self.dblVal) - self.gui.endofLpfPassBandEdit.setValidator(self.dblVal) - self.gui.startofLpfStopBandEdit.setValidator(self.dblVal) - self.gui.lpfStopBandAttenEdit.setValidator(self.dblVal) - self.gui.lpfPassBandRippleEdit.setValidator(self.dblVal) - self.gui.startofBpfPassBandEdit.setValidator(self.dblVal) - self.gui.endofBpfPassBandEdit.setValidator(self.dblVal) - self.gui.bpfTransitionEdit.setValidator(self.dblVal) - self.gui.bpfStopBandAttenEdit.setValidator(self.dblVal) - self.gui.bpfPassBandRippleEdit.setValidator(self.dblVal) - self.gui.startofBnfStopBandEdit.setValidator(self.dblVal) - self.gui.endofBnfStopBandEdit.setValidator(self.dblVal) - self.gui.bnfTransitionEdit.setValidator(self.dblVal) - self.gui.bnfStopBandAttenEdit.setValidator(self.dblVal) - self.gui.bnfPassBandRippleEdit.setValidator(self.dblVal) - self.gui.endofHpfStopBandEdit.setValidator(self.dblVal) - self.gui.startofHpfPassBandEdit.setValidator(self.dblVal) - self.gui.hpfStopBandAttenEdit.setValidator(self.dblVal) - self.gui.hpfPassBandRippleEdit.setValidator(self.dblVal) - self.gui.rrcSymbolRateEdit.setValidator(self.dblVal) - self.gui.rrcAlphaEdit.setValidator(self.dblVal) - self.gui.rrcNumTapsEdit.setValidator(self.dblVal) - self.gui.gausSymbolRateEdit.setValidator(self.dblVal) - self.gui.gausBTEdit.setValidator(self.dblVal) - self.gui.gausNumTapsEdit.setValidator(self.dblVal) - - self.gui.nTapsEdit.setText("0") - - self.filterWindows = {"Hamming Window" : gr.firdes.WIN_HAMMING, - "Hann Window" : gr.firdes.WIN_HANN, - "Blackman Window" : gr.firdes.WIN_BLACKMAN, - "Rectangular Window" : gr.firdes.WIN_RECTANGULAR, - "Kaiser Window" : gr.firdes.WIN_KAISER, - "Blackman-harris Window" : gr.firdes.WIN_BLACKMAN_hARRIS} - self.EQUIRIPPLE_FILT = 6 # const for equiripple filter window types - self.show() - - def changed_filter_type(self, ftype): - strftype = str(ftype.toAscii()) - if(ftype == "Low Pass"): - self.gui.filterTypeWidget.setCurrentWidget(self.gui.firlpfPage) - elif(ftype == "Band Pass"): - self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbpfPage) - elif(ftype == "Complex Band Pass"): - self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbpfPage) - elif(ftype == "Band Notch"): - self.gui.filterTypeWidget.setCurrentWidget(self.gui.firbnfPage) - elif(ftype == "High Pass"): - self.gui.filterTypeWidget.setCurrentWidget(self.gui.firhpfPage) - elif(ftype == "Root Raised Cosine"): - self.gui.filterTypeWidget.setCurrentWidget(self.gui.rrcPage) - elif(ftype == "Gaussian"): - self.gui.filterTypeWidget.setCurrentWidget(self.gui.gausPage) - - self.design() - - def changed_filter_design_type(self, design): - if(design == "Equiripple"): - self.set_equiripple() - else: - self.set_windowed() - - self.design() - - def set_equiripple(self): - # Stop sending the signal for this function - self.gui.filterTypeComboBox.blockSignals(True) - - self.equiripple = True - self.gui.lpfPassBandRippleLabel.setVisible(True) - self.gui.lpfPassBandRippleEdit.setVisible(True) - self.gui.bpfPassBandRippleLabel.setVisible(True) - self.gui.bpfPassBandRippleEdit.setVisible(True) - self.gui.bnfPassBandRippleLabel.setVisible(True) - self.gui.bnfPassBandRippleEdit.setVisible(True) - self.gui.hpfPassBandRippleLabel.setVisible(True) - self.gui.hpfPassBandRippleEdit.setVisible(True) - - # Save current type and repopulate the combo box for - # filters this window type can handle - currenttype = self.gui.filterTypeComboBox.currentText() - items = self.gui.filterTypeComboBox.count() - for i in xrange(items): - self.gui.filterTypeComboBox.removeItem(0) - self.gui.filterTypeComboBox.addItems(self.optFilters) - - # If the last filter type was valid for this window type, - # go back to it; otherwise, reset - try: - index = self.optFilters.index(currenttype) - self.gui.filterTypeComboBox.setCurrentIndex(index) - except ValueError: - pass - - # Tell gui its ok to start sending this signal again - self.gui.filterTypeComboBox.blockSignals(False) - - def set_windowed(self): - # Stop sending the signal for this function - self.gui.filterTypeComboBox.blockSignals(True) - - self.equiripple = False - self.gui.lpfPassBandRippleLabel.setVisible(False) - self.gui.lpfPassBandRippleEdit.setVisible(False) - self.gui.bpfPassBandRippleLabel.setVisible(False) - self.gui.bpfPassBandRippleEdit.setVisible(False) - self.gui.bnfPassBandRippleLabel.setVisible(False) - self.gui.bnfPassBandRippleEdit.setVisible(False) - self.gui.hpfPassBandRippleLabel.setVisible(False) - self.gui.hpfPassBandRippleEdit.setVisible(False) - - # Save current type and repopulate the combo box for - # filters this window type can handle - currenttype = self.gui.filterTypeComboBox.currentText() - items = self.gui.filterTypeComboBox.count() - for i in xrange(items): - self.gui.filterTypeComboBox.removeItem(0) - self.gui.filterTypeComboBox.addItems(self.firFilters) - - # If the last filter type was valid for this window type, - # go back to it; otherwise, reset - try: - index = self.optFilters.index(currenttype) - self.gui.filterTypeComboBox.setCurrentIndex(index) - except ValueError: - pass - - # Tell gui its ok to start sending this signal again - self.gui.filterTypeComboBox.blockSignals(False) - - def design(self): - ret = True - fs,r = self.gui.sampleRateEdit.text().toDouble() - ret = r and ret - gain,r = self.gui.filterGainEdit.text().toDouble() - ret = r and ret - - if(ret): - winstr = str(self.gui.filterDesignTypeComboBox.currentText().toAscii()) - ftype = str(self.gui.filterTypeComboBox.currentText().toAscii()) - - if(winstr == "Equiripple"): - designer = {"Low Pass" : self.design_opt_lpf, - "Band Pass" : self.design_opt_bpf, - "Complex Band Pass" : self.design_opt_cbpf, - "Band Notch" : self.design_opt_bnf, - "High Pass" : self.design_opt_hpf} - taps,params,r = designer[ftype](fs, gain) - - else: - designer = {"Low Pass" : self.design_win_lpf, - "Band Pass" : self.design_win_bpf, - "Complex Band Pass" : self.design_win_cbpf, - "Band Notch" : self.design_win_bnf, - "High Pass" : self.design_win_hpf, - "Root Raised Cosine" : self.design_win_rrc, - "Gaussian" : self.design_win_gaus} - wintype = self.filterWindows[winstr] - taps,params,r = designer[ftype](fs, gain, wintype) - - if(r): - self.draw_plots(taps, params) - - - # Filter design functions using a window - def design_win_lpf(self, fs, gain, wintype): - ret = True - pb,r = self.gui.endofLpfPassBandEdit.text().toDouble() - ret = r and ret - sb,r = self.gui.startofLpfStopBandEdit.text().toDouble() - ret = r and ret - atten,r = self.gui.lpfStopBandAttenEdit.text().toDouble() - ret = r and ret - - if(ret): - tb = sb - pb - - taps = gr.firdes.low_pass_2(gain, fs, pb, tb, - atten, wintype) - params = {"fs": fs, "gain": gain, "wintype": wintype, - "filttype": "lpf", "pbend": pb, "sbstart": sb, - "atten": atten, "ntaps": len(taps)} - return (taps, params, ret) - else: - return ([], [], ret) - - def design_win_bpf(self, fs, gain, wintype): - ret = True - pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble() - ret = r and ret - pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble() - ret = r and ret - tb,r = self.gui.bpfTransitionEdit.text().toDouble() - ret = r and ret - atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble() - ret = r and ret - - if(r): - taps = gr.firdes.band_pass_2(gain, fs, pb1, pb2, tb, - atten, wintype) - params = {"fs": fs, "gain": gain, "wintype": wintype, - "filttype": "bpf", "pbstart": pb1, "pbend": pb2, - "tb": tb, "atten": atten, "ntaps": len(taps)} - return (taps,params,r) - else: - return ([],[],r) - - def design_win_cbpf(self, fs, gain, wintype): - ret = True - pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble() - ret = r and ret - pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble() - ret = r and ret - tb,r = self.gui.bpfTransitionEdit.text().toDouble() - ret = r and ret - atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble() - ret = r and ret - - if(r): - taps = gr.firdes.complex_band_pass_2(gain, fs, pb1, pb2, tb, - atten, wintype) - params = {"fs": fs, "gain": gain, "wintype": wintype, - "filttype": "cbpf", "pbstart": pb1, "pbend": pb2, - "tb": tb, "atten": atten, "ntaps": len(taps)} - return (taps,params,r) - else: - return ([],[],r) - - def design_win_bnf(self, fs, gain, wintype): - ret = True - pb1,r = self.gui.startofBnfStopBandEdit.text().toDouble() - ret = r and ret - pb2,r = self.gui.endofBnfStopBandEdit.text().toDouble() - ret = r and ret - tb,r = self.gui.bnfTransitionEdit.text().toDouble() - ret = r and ret - atten,r = self.gui.bnfStopBandAttenEdit.text().toDouble() - ret = r and ret - - if(r): - taps = gr.firdes.band_reject_2(gain, fs, pb1, pb2, tb, - atten, wintype) - params = {"fs": fs, "gain": gain, "wintype": wintype, - "filttype": "bnf", "sbstart": pb1, "sbend": pb2, - "tb": tb, "atten": atten, "ntaps": len(taps)} - return (taps,params,r) - else: - return ([],[],r) - - def design_win_hpf(self, fs, gain, wintype): - ret = True - sb,r = self.gui.endofHpfStopBandEdit.text().toDouble() - ret = r and ret - pb,r = self.gui.startofHpfPassBandEdit.text().toDouble() - ret = r and ret - atten,r = self.gui.hpfStopBandAttenEdit.text().toDouble() - ret = r and ret - - if(r): - tb = pb - sb - taps = gr.firdes.high_pass_2(gain, fs, pb, tb, - atten, wintype) - params = {"fs": fs, "gain": gain, "wintype": wintype, - "filttype": "hpf", "sbend": sb, "pbstart": pb, - "atten": atten, "ntaps": len(taps)} - return (taps,params,r) - else: - return ([],[],r) - - def design_win_rrc(self, fs, gain, wintype): - ret = True - sr,r = self.gui.rrcSymbolRateEdit.text().toDouble() - ret = r and ret - alpha,r = self.gui.rrcAlphaEdit.text().toDouble() - ret = r and ret - ntaps,r = self.gui.rrcNumTapsEdit.text().toInt() - ret = r and ret - - if(r): - taps = gr.firdes.root_raised_cosine(gain, fs, sr, - alpha, ntaps) - params = {"fs": fs, "gain": gain, "wintype": wintype, - "filttype": "rrc", "srate": sr, "alpha": alpha, - "ntaps": ntaps} - return (taps,params,r) - else: - return ([],[],r) - - def design_win_gaus(self, fs, gain, wintype): - ret = True - sr,r = self.gui.gausSymbolRateEdit.text().toDouble() - ret = r and ret - bt,r = self.gui.gausBTEdit.text().toDouble() - ret = r and ret - ntaps,r = self.gui.gausNumTapsEdit.text().toInt() - ret = r and ret - - if(r): - spb = fs / sr - taps = gr.firdes.gaussian(gain, spb, bt, ntaps) - params = {"fs": fs, "gain": gain, "wintype": wintype, - "filttype": "gaus", "srate": sr, "bt": bt, - "ntaps": ntaps} - return (taps,params,r) - else: - return ([],[],r) - - # Design Functions for Equiripple Filters - def design_opt_lpf(self, fs, gain): - ret = True - pb,r = self.gui.endofLpfPassBandEdit.text().toDouble() - ret = r and ret - sb,r = self.gui.startofLpfStopBandEdit.text().toDouble() - ret = r and ret - atten,r = self.gui.lpfStopBandAttenEdit.text().toDouble() - ret = r and ret - ripple,r = self.gui.lpfPassBandRippleEdit.text().toDouble() - ret = r and ret - - if(ret): - try: - taps = blks2.optfir.low_pass(gain, fs, pb, sb, - ripple, atten) - except RuntimeError, e: - reply = QtGui.QMessageBox.information(self, "Filter did not converge", - e.args[0], "&Ok") - return ([],[],False) - else: - params = {"fs": fs, "gain": gain, "wintype": self.EQUIRIPPLE_FILT, - "filttype": "lpf", "pbend": pb, "sbstart": sb, - "atten": atten, "ripple": ripple, "ntaps": len(taps)} - return (taps, params, ret) - else: - return ([], [], ret) - - def design_opt_bpf(self, fs, gain): - ret = True - pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble() - ret = r and ret - pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble() - ret = r and ret - tb,r = self.gui.bpfTransitionEdit.text().toDouble() - ret = r and ret - atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble() - ret = r and ret - ripple,r = self.gui.bpfPassBandRippleEdit.text().toDouble() - ret = r and ret - - if(r): - sb1 = pb1 - tb - sb2 = pb2 + tb - try: - taps = blks2.optfir.band_pass(gain, fs, sb1, pb1, pb2, sb2, - ripple, atten) - except RuntimeError, e: - reply = QtGui.QMessageBox.information(self, "Filter did not converge", - e.args[0], "&Ok") - return ([],[],False) - - else: - params = {"fs": fs, "gain": gain, "wintype": self.EQUIRIPPLE_FILT, - "filttype": "bpf", "pbstart": pb1, "pbend": pb2, - "tb": tb, "atten": atten, "ripple": ripple, - "ntaps": len(taps)} - return (taps,params,r) - else: - return ([],[],r) - - def design_opt_cbpf(self, fs, gain): - ret = True - pb1,r = self.gui.startofBpfPassBandEdit.text().toDouble() - ret = r and ret - pb2,r = self.gui.endofBpfPassBandEdit.text().toDouble() - ret = r and ret - tb,r = self.gui.bpfTransitionEdit.text().toDouble() - ret = r and ret - atten,r = self.gui.bpfStopBandAttenEdit.text().toDouble() - ret = r and ret - ripple,r = self.gui.bpfPassBandRippleEdit.text().toDouble() - ret = r and ret - - if(r): - sb1 = pb1 - tb - sb2 = pb2 + tb - try: - taps = blks2.optfir.complex_band_pass(gain, fs, sb1, pb1, pb2, sb2, - ripple, atten) - except RuntimeError, e: - reply = QtGui.QMessageBox.information(self, "Filter did not converge", - e.args[0], "&Ok") - return ([],[],False) - else: - params = {"fs": fs, "gain": gain, "wintype": self.EQUIRIPPLE_FILT, - "filttype": "cbpf", "pbstart": pb1, "pbend": pb2, - "tb": tb, "atten": atten, "ripple": ripple, - "ntaps": len(taps)} - return (taps,params,r) - else: - return ([],[],r) - - def design_opt_bnf(self, fs, gain): - ret = True - sb1,r = self.gui.startofBnfStopBandEdit.text().toDouble() - ret = r and ret - sb2,r = self.gui.endofBnfStopBandEdit.text().toDouble() - ret = r and ret - tb,r = self.gui.bnfTransitionEdit.text().toDouble() - ret = r and ret - atten,r = self.gui.bnfStopBandAttenEdit.text().toDouble() - ret = r and ret - ripple,r = self.gui.bnfPassBandRippleEdit.text().toDouble() - ret = r and ret - - if(r): - pb1 = sb1 - tb - pb2 = sb2 + tb - try: - taps = blks2.optfir.band_reject(gain, fs, pb1, sb1, sb2, pb2, - ripple, atten) - except RuntimeError, e: - reply = QtGui.QMessageBox.information(self, "Filter did not converge", - e.args[0], "&Ok") - return ([],[],False) - else: - params = {"fs": fs, "gain": gain, "wintype": self.EQUIRIPPLE_FILT, - "filttype": "bnf", "sbstart": pb1, "sbend": pb2, - "tb": tb, "atten": atten, "ripple": ripple, - "ntaps": len(taps)} - return (taps,params,r) - else: - return ([],[],r) - - def design_opt_hpf(self, fs, gain): - ret = True - sb,r = self.gui.endofHpfStopBandEdit.text().toDouble() - ret = r and ret - pb,r = self.gui.startofHpfPassBandEdit.text().toDouble() - ret = r and ret - atten,r = self.gui.hpfStopBandAttenEdit.text().toDouble() - ret = r and ret - ripple,r = self.gui.hpfPassBandRippleEdit.text().toDouble() - ret = r and ret - - if(r): - try: - taps = blks2.optfir.high_pass(gain, fs, sb, pb, - atten, ripple) - except RuntimeError, e: - reply = QtGui.QMessageBox.information(self, "Filter did not converge", - e.args[0], "&Ok") - return ([],[],False) - else: - params = {"fs": fs, "gain": gain, "wintype": self.EQUIRIPPLE_FILT, - "filttype": "hpf", "sbend": sb, "pbstart": pb, - "atten": atten, "ripple": ripple, - "ntaps": len(taps)} - return (taps,params,r) - else: - return ([],[],r) - - def nfft_edit_changed(self, nfft): - infft,r = nfft.toInt() - if(r and (infft != self.nfftpts)): - self.nfftpts = infft - self.update_freq_curves() - - def tab_changed(self, tab): - if(tab == 0): - self.update_freq_curves() - if(tab == 1): - self.update_time_curves() - if(tab == 2): - self.update_phase_curves() - if(tab == 3): - self.update_group_curves() - - def get_fft(self, fs, taps, Npts): - Ts = 1.0/fs - fftpts = fftpack.fft(taps, Npts) - self.freq = scipy.arange(0, fs, 1.0/(Npts*Ts)) - self.fftdB = 20.0*scipy.log10(abs(fftpts)) - self.fftDeg = scipy.unwrap(scipy.angle(fftpts)) - self.groupDelay = -scipy.diff(self.fftDeg) - - def update_time_curves(self): - ntaps = len(self.taps) - if(ntaps > 0): - if(type(self.taps[0]) == scipy.complex128): - self.rcurve.setData(scipy.arange(ntaps), self.taps.real) - self.icurve.setData(scipy.arange(ntaps), self.taps.imag) - else: - self.rcurve.setData(scipy.arange(ntaps), self.taps) - - # Reset the x-axis to the new time scale - ymax = 1.5 * max(self.taps) - ymin = 1.5 * min(self.taps) - self.gui.timePlot.setAxisScale(self.gui.timePlot.xBottom, - 0, ntaps) - self.gui.timePlot.setAxisScale(self.gui.timePlot.yLeft, - ymin, ymax) - - # Set the zoomer base to unzoom to the new axis - self.timeZoomer.setZoomBase() - - self.gui.timePlot.replot() - - def update_freq_curves(self): - npts = len(self.fftdB) - if(npts > 0): - self.freqcurve.setData(self.freq, self.fftdB) - - # Reset the x-axis to the new time scale - ymax = 1.5 * max(self.fftdB[0:npts/2]) - ymin = 1.1 * min(self.fftdB[0:npts/2]) - xmax = self.freq[npts/2] - xmin = self.freq[0] - self.gui.freqPlot.setAxisScale(self.gui.freqPlot.xBottom, - xmin, xmax) - self.gui.freqPlot.setAxisScale(self.gui.freqPlot.yLeft, - ymin, ymax) - - # Set the zoomer base to unzoom to the new axis - self.freqZoomer.setZoomBase() - - self.gui.freqPlot.replot() - - - def update_phase_curves(self): - npts = len(self.fftDeg) - if(npts > 0): - self.phasecurve.setData(self.freq, self.fftDeg) - - # Reset the x-axis to the new time scale - ymax = 1.5 * max(self.fftDeg[0:npts/2]) - ymin = 1.1 * min(self.fftDeg[0:npts/2]) - xmax = self.freq[npts/2] - xmin = self.freq[0] - self.gui.phasePlot.setAxisScale(self.gui.phasePlot.xBottom, - xmin, xmax) - self.gui.phasePlot.setAxisScale(self.gui.phasePlot.yLeft, - ymin, ymax) - - # Set the zoomer base to unzoom to the new axis - self.phaseZoomer.setZoomBase() - - self.gui.phasePlot.replot() - - def update_group_curves(self): - npts = len(self.groupDelay) - if(npts > 0): - self.groupcurve.setData(self.freq, self.groupDelay) - - # Reset the x-axis to the new time scale - ymax = 1.5 * max(self.groupDelay[0:npts/2]) - ymin = 1.1 * min(self.groupDelay[0:npts/2]) - xmax = self.freq[npts/2] - xmin = self.freq[0] - self.gui.groupPlot.setAxisScale(self.gui.groupPlot.xBottom, - xmin, xmax) - self.gui.groupPlot.setAxisScale(self.gui.groupPlot.yLeft, - ymin, ymax) - - # Set the zoomer base to unzoom to the new axis - self.groupZoomer.setZoomBase() - - self.gui.groupPlot.replot() - - def action_save_dialog(self): - filename = QtGui.QFileDialog.getSaveFileName(self, "Save CSV Filter File", ".", "") - try: - handle = open(filename, "wb") - except IOError: - reply = QtGui.QMessageBox.information(self, 'File Name', - ("Could not save to file: %s" % filename), - "&Ok") - return - - csvhandle = csv.writer(handle, delimiter=",") - for k in self.params.keys(): - csvhandle.writerow([k, self.params[k]]) - csvhandle.writerow(["taps",] + self.taps.tolist()) - handle.close() - - def action_open_dialog(self): - filename = QtGui.QFileDialog.getOpenFileName(self, "Open CSV Filter File", ".", "") - if(len(filename) == 0): - return - - try: - handle = open(filename, "rb") - except IOError: - reply = QtGui.QMessageBox.information(self, 'File Name', - ("Could not open file: %s" % filename), - "&Ok") - return - - csvhandle = csv.reader(handle, delimiter=",") - taps = [] - params = {} - for row in csvhandle: - if(row[0] != "taps"): - testcpx = re.findall("[+-]?\d+\.*\d*[Ee]?[-+]?\d+j", row[1]) - if(len(testcpx) > 0): # it's a complex - params[row[0]] = complex(row[1]) - else: # assume it's a float - try: # if it's not a float, its a string - params[row[0]] = float(row[1]) - except ValueError: - params[row[0]] = row[1] - else: - testcpx = re.findall("[+-]?\d+\.*\d*[Ee]?[-+]?\d+j", row[1]) - if(len(testcpx) > 0): # it's a complex - taps = [complex(r) for r in row[1:]] - else: - taps = [float(r) for r in row[1:]] - handle.close() - self.draw_plots(taps, params) - - self.gui.sampleRateEdit.setText(Qt.QString("%1").arg(params["fs"])) - self.gui.filterGainEdit.setText(Qt.QString("%1").arg(params["gain"])) - - # Set up GUI parameters for each filter type - if(params["filttype"] == "lpf"): - self.gui.filterTypeComboBox.setCurrentIndex(0) - self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) - - self.gui.endofLpfPassBandEdit.setText(Qt.QString("%1").arg(params["pbend"])) - self.gui.startofLpfStopBandEdit.setText(Qt.QString("%1").arg(params["sbstart"])) - self.gui.lpfStopBandAttenEdit.setText(Qt.QString("%1").arg(params["atten"])) - if(params["wintype"] == self.EQUIRIPPLE_FILT): - self.gui.lpfPassBandRippleEdit.setText(Qt.QString("%1").arg(params["ripple"])) - elif(params["filttype"] == "bpf"): - self.gui.filterTypeComboBox.setCurrentIndex(1) - self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) - - self.gui.startofBpfPassBandEdit.setText(Qt.QString("%1").arg(params["pbstart"])) - self.gui.endofBpfPassBandEdit.setText(Qt.QString("%1").arg(params["pbend"])) - self.gui.bpfTransitionEdit.setText(Qt.QString("%1").arg(params["tb"])) - self.gui.bpfStopBandAttenEdit.setText(Qt.QString("%1").arg(params["atten"])) - if(params["wintype"] == self.EQUIRIPPLE_FILT): - self.gui.bpfPassBandRippleEdit.setText(Qt.QString("%1").arg(params["ripple"])) - elif(params["filttype"] == "cbpf"): - self.gui.filterTypeComboBox.setCurrentIndex(2) - self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) - - self.gui.startofBpfPassBandEdit.setText(Qt.QString("%1").arg(params["pbstart"])) - self.gui.endofBpfPassBandEdit.setText(Qt.QString("%1").arg(params["pbend"])) - self.gui.bpfTransitionEdit.setText(Qt.QString("%1").arg(params["tb"])) - self.gui.bpfStopBandAttenEdit.setText(Qt.QString("%1").arg(params["atten"])) - if(params["wintype"] == self.EQUIRIPPLE_FILT): - self.gui.bpfPassBandRippleEdit.setText(Qt.QString("%1").arg(params["ripple"])) - elif(params["filttype"] == "bnf"): - self.gui.filterTypeComboBox.setCurrentIndex(3) - self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) - - self.gui.startofBnfStopBandEdit.setText(Qt.QString("%1").arg(params["sbstart"])) - self.gui.endofBnfStopBandEdit.setText(Qt.QString("%1").arg(params["sbend"])) - self.gui.bnfTransitionEdit.setText(Qt.QString("%1").arg(params["tb"])) - self.gui.bnfStopBandAttenEdit.setText(Qt.QString("%1").arg(params["atten"])) - if(params["wintype"] == self.EQUIRIPPLE_FILT): - self.gui.bnfPassBandRippleEdit.setText(Qt.QString("%1").arg(params["ripple"])) - elif(params["filttype"] == "hpf"): - self.gui.filterTypeComboBox.setCurrentIndex(4) - self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) - - self.gui.endofHpfStopBandEdit.setText(Qt.QString("%1").arg(params["sbend"])) - self.gui.startofHpfPassBandEdit.setText(Qt.QString("%1").arg(params["pbstart"])) - self.gui.hpfStopBandAttenEdit.setText(Qt.QString("%1").arg(params["atten"])) - if(params["wintype"] == self.EQUIRIPPLE_FILT): - self.gui.hpfPassBandRippleEdit.setText(Qt.QString("%1").arg(params["ripple"])) - elif(params["filttype"] == "rrc"): - self.gui.filterTypeComboBox.setCurrentIndex(5) - self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) - - self.gui.rrcSymbolRateEdit.setText(Qt.QString("%1").arg(params["srate"])) - self.gui.rrcAlphaEdit.setText(Qt.QString("%1").arg(params["alpha"])) - self.gui.rrcNumTapsEdit.setText(Qt.QString("%1").arg(params["ntaps"])) - elif(params["filttype"] == "gaus"): - self.gui.filterTypeComboBox.setCurrentIndex(6) - self.gui.filterDesignTypeComboBox.setCurrentIndex(int(params["wintype"])) - - self.gui.gausSymbolRateEdit.setText(Qt.QString("%1").arg(params["srate"])) - self.gui.gausBTEdit.setText(Qt.QString("%1").arg(params["bt"])) - self.gui.gausNumTapsEdit.setText(Qt.QString("%1").arg(params["ntaps"])) - - def draw_plots(self, taps, params): - self.params = params - self.taps = scipy.array(taps) - self.get_fft(self.params["fs"], self.taps, self.nfftpts) - self.update_time_curves() - self.update_freq_curves() - self.update_phase_curves() - self.update_group_curves() - - self.gui.nTapsEdit.setText(Qt.QString("%1").arg(self.taps.size)) - - -def setup_options(): - usage="%prog: [options] (input_filename)" - description = "" - - parser = OptionParser(conflict_handler="resolve", - usage=usage, description=description) - return parser - -def main(args): - parser = setup_options() - (options, args) = parser.parse_args () - - app = Qt.QApplication(args) - gplt = gr_plot_filter(app, options) - app.exec_() - -if __name__ == '__main__': - main(sys.argv) - diff --git a/gr-utils/src/python/gr_plot_char b/gr-utils/src/python/gr_plot_char new file mode 100755 index 000000000..87a323c9c --- /dev/null +++ b/gr-utils/src/python/gr_plot_char @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# +# Copyright 2007,2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +try: + import scipy +except ImportError: + print "Please install SciPy to run this script (http://www.scipy.org/)" + raise SystemExit, 1 + +from optparse import OptionParser +from gnuradio.plot_data import plot_data + +def main(): + usage="%prog: [options] input_filenames" + description = "Takes a GNU Radio byte/char binary file and displays the samples versus time. You can set the block size to specify how many points to read in at a time and the start position in the file. By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." + + parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) + parser.add_option("-B", "--block", type="int", default=1000, + help="Specify the block size [default=%default]") + parser.add_option("-s", "--start", type="int", default=0, + help="Specify where to start in the file [default=%default]") + parser.add_option("-R", "--sample-rate", type="float", default=1.0, + help="Set the sampler rate of the data [default=%default]") + + (options, args) = parser.parse_args () + if len(args) < 1: + parser.print_help() + raise SystemExit, 1 + filenames = args + + datatype=scipy.int8 + dc = plot_data(datatype, filenames, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + diff --git a/gr-utils/src/python/gr_plot_char.py b/gr-utils/src/python/gr_plot_char.py deleted file mode 100755 index 87a323c9c..000000000 --- a/gr-utils/src/python/gr_plot_char.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2007,2008 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -try: - import scipy -except ImportError: - print "Please install SciPy to run this script (http://www.scipy.org/)" - raise SystemExit, 1 - -from optparse import OptionParser -from gnuradio.plot_data import plot_data - -def main(): - usage="%prog: [options] input_filenames" - description = "Takes a GNU Radio byte/char binary file and displays the samples versus time. You can set the block size to specify how many points to read in at a time and the start position in the file. By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." - - parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) - parser.add_option("-B", "--block", type="int", default=1000, - help="Specify the block size [default=%default]") - parser.add_option("-s", "--start", type="int", default=0, - help="Specify where to start in the file [default=%default]") - parser.add_option("-R", "--sample-rate", type="float", default=1.0, - help="Set the sampler rate of the data [default=%default]") - - (options, args) = parser.parse_args () - if len(args) < 1: - parser.print_help() - raise SystemExit, 1 - filenames = args - - datatype=scipy.int8 - dc = plot_data(datatype, filenames, options) - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - pass - diff --git a/gr-utils/src/python/gr_plot_const b/gr-utils/src/python/gr_plot_const new file mode 100755 index 000000000..8873e5b7e --- /dev/null +++ b/gr-utils/src/python/gr_plot_const @@ -0,0 +1,250 @@ +#!/usr/bin/env python +# +# Copyright 2007,2008,2011 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +try: + import scipy +except ImportError: + print "Please install SciPy to run this script (http://www.scipy.org/)" + raise SystemExit, 1 + +try: + from pylab import * + from matplotlib.font_manager import fontManager, FontProperties +except ImportError: + print "Please install Matplotlib to run this script (http://matplotlib.sourceforge.net/)" + raise SystemExit, 1 + +from optparse import OptionParser + +class draw_constellation: + def __init__(self, filename, options): + self.hfile = open(filename, "r") + self.block_length = options.block + self.start = options.start + self.sample_rate = options.sample_rate + + self.datatype = scipy.complex64 + self.sizeof_data = self.datatype().nbytes # number of bytes per sample in file + + self.axis_font_size = 16 + self.label_font_size = 18 + self.title_font_size = 20 + + # Setup PLOT + self.fig = figure(1, figsize=(16, 9), facecolor='w') + rcParams['xtick.labelsize'] = self.axis_font_size + rcParams['ytick.labelsize'] = self.axis_font_size + + self.text_file = figtext(0.10, 0.95, ("File: %s" % filename), weight="heavy", size=16) + self.text_file_pos = figtext(0.10, 0.90, "File Position: ", weight="heavy", size=16) + self.text_block = figtext(0.40, 0.90, ("Block Size: %d" % self.block_length), + weight="heavy", size=16) + self.text_sr = figtext(0.60, 0.90, ("Sample Rate: %.2f" % self.sample_rate), + weight="heavy", size=16) + self.make_plots() + + self.button_left_axes = self.fig.add_axes([0.45, 0.01, 0.05, 0.05], frameon=True) + self.button_left = Button(self.button_left_axes, "<") + self.button_left_callback = self.button_left.on_clicked(self.button_left_click) + + self.button_right_axes = self.fig.add_axes([0.50, 0.01, 0.05, 0.05], frameon=True) + self.button_right = Button(self.button_right_axes, ">") + self.button_right_callback = self.button_right.on_clicked(self.button_right_click) + + self.xlim = self.sp_iq.get_xlim() + + self.manager = get_current_fig_manager() + connect('draw_event', self.zoom) + connect('key_press_event', self.click) + connect('button_press_event', self.mouse_button_callback) + show() + + def get_data(self): + self.text_file_pos.set_text("File Position: %d" % (self.hfile.tell()//self.sizeof_data)) + try: + iq = scipy.fromfile(self.hfile, dtype=self.datatype, count=self.block_length) + except MemoryError: + print "End of File" + else: + # retesting length here as newer version of scipy does not throw a MemoryError, just + # returns a zero-length array + if(len(iq) > 0): + self.reals = scipy.array([r.real for r in iq]) + self.imags = scipy.array([i.imag for i in iq]) + + self.time = scipy.array([i*(1/self.sample_rate) for i in range(len(self.reals))]) + return True + else: + print "End of File" + return False + + def make_plots(self): + # if specified on the command-line, set file pointer + self.hfile.seek(self.sizeof_data*self.start, 1) + + r = self.get_data() + + # Subplot for real and imaginary parts of signal + self.sp_iq = self.fig.add_subplot(2,1,1, position=[0.075, 0.2, 0.4, 0.6]) + self.sp_iq.set_title(("I&Q"), fontsize=self.title_font_size, fontweight="bold") + self.sp_iq.set_xlabel("Time (s)", fontsize=self.label_font_size, fontweight="bold") + self.sp_iq.set_ylabel("Amplitude (V)", fontsize=self.label_font_size, fontweight="bold") + self.plot_iq = self.sp_iq.plot(self.time, self.reals, 'bo-', self.time, self.imags, 'ro-') + + # Subplot for constellation plot + self.sp_const = self.fig.add_subplot(2,2,1, position=[0.575, 0.2, 0.4, 0.6]) + self.sp_const.set_title(("Constellation"), fontsize=self.title_font_size, fontweight="bold") + self.sp_const.set_xlabel("Inphase", fontsize=self.label_font_size, fontweight="bold") + self.sp_const.set_ylabel("Qaudrature", fontsize=self.label_font_size, fontweight="bold") + self.plot_const = self.sp_const.plot(self.reals, self.imags, 'bo') + + # Add plots to mark current location of point between time and constellation plots + self.indx = 0 + self.plot_iq += self.sp_iq.plot([self.time[self.indx],], [self.reals[self.indx],], 'mo', ms=8) + self.plot_iq += self.sp_iq.plot([self.time[self.indx],], [self.imags[self.indx],], 'mo', ms=8) + self.plot_const += self.sp_const.plot([self.reals[self.indx],], [self.imags[self.indx],], 'mo', ms=12) + + # Adjust axis + self.sp_iq.axis([self.time.min(), self.time.max(), + 1.5*min([self.reals.min(), self.imags.min()]), + 1.5*max([self.reals.max(), self.imags.max()])]) + self.sp_const.axis([-2, 2, -2, 2]) + + draw() + + def update_plots(self): + self.plot_iq[0].set_data([self.time, self.reals]) + self.plot_iq[1].set_data([self.time, self.imags]) + self.sp_iq.axis([self.time.min(), self.time.max(), + 1.5*min([self.reals.min(), self.imags.min()]), + 1.5*max([self.reals.max(), self.imags.max()])]) + + self.plot_const[0].set_data([self.reals, self.imags]) + self.sp_const.axis([-2, 2, -2, 2]) + draw() + + def zoom(self, event): + newxlim = scipy.array(self.sp_iq.get_xlim()) + curxlim = scipy.array(self.xlim) + if(newxlim[0] != curxlim[0] or newxlim[1] != curxlim[1]): + self.xlim = newxlim + r = self.reals[int(ceil(self.xlim[0])) : int(ceil(self.xlim[1]))] + i = self.imags[int(ceil(self.xlim[0])) : int(ceil(self.xlim[1]))] + + self.plot_const[0].set_data(r, i) + self.sp_const.axis([-2, 2, -2, 2]) + self.manager.canvas.draw() + draw() + + def click(self, event): + forward_valid_keys = [" ", "down", "right"] + backward_valid_keys = ["up", "left"] + trace_forward_valid_keys = [">",] + trace_backward_valid_keys = ["<",] + + if(find(event.key, forward_valid_keys)): + self.step_forward() + + elif(find(event.key, backward_valid_keys)): + self.step_backward() + + elif(find(event.key, trace_forward_valid_keys)): + self.indx = min(self.indx+1, len(self.time)-1) + self.set_trace(self.indx) + + elif(find(event.key, trace_backward_valid_keys)): + self.indx = max(0, self.indx-1) + self.set_trace(self.indx) + + def button_left_click(self, event): + self.step_backward() + + def button_right_click(self, event): + self.step_forward() + + def step_forward(self): + r = self.get_data() + if(r): + self.update_plots() + + def step_backward(self): + # Step back in file position + if(self.hfile.tell() >= 2*self.sizeof_data*self.block_length ): + self.hfile.seek(-2*self.sizeof_data*self.block_length, 1) + else: + self.hfile.seek(-self.hfile.tell(),1) + r = self.get_data() + if(r): + self.update_plots() + + + def mouse_button_callback(self, event): + x, y = event.xdata, event.ydata + + if x is not None and y is not None: + if(event.inaxes == self.sp_iq): + self.indx = searchsorted(self.time, [x]) + self.set_trace(self.indx) + + + def set_trace(self, indx): + self.plot_iq[2].set_data(self.time[indx], self.reals[indx]) + self.plot_iq[3].set_data(self.time[indx], self.imags[indx]) + self.plot_const[1].set_data(self.reals[indx], self.imags[indx]) + draw() + + +def find(item_in, list_search): + try: + return list_search.index(item_in) != None + except ValueError: + return False + + +def main(): + usage="%prog: [options] input_filename" + description = "Takes a GNU Radio complex binary file and displays the I&Q data versus time and the constellation plot (I vs. Q). You can set the block size to specify how many points to read in at a time and the start position in the file. By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." + + parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) + parser.add_option("-B", "--block", type="int", default=1000, + help="Specify the block size [default=%default]") + parser.add_option("-s", "--start", type="int", default=0, + help="Specify where to start in the file [default=%default]") + parser.add_option("-R", "--sample-rate", type="float", default=1.0, + help="Set the sampler rate of the data [default=%default]") + + (options, args) = parser.parse_args () + if len(args) != 1: + parser.print_help() + raise SystemExit, 1 + filename = args[0] + + dc = draw_constellation(filename, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + + + diff --git a/gr-utils/src/python/gr_plot_const.py b/gr-utils/src/python/gr_plot_const.py deleted file mode 100755 index 8873e5b7e..000000000 --- a/gr-utils/src/python/gr_plot_const.py +++ /dev/null @@ -1,250 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2007,2008,2011 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -try: - import scipy -except ImportError: - print "Please install SciPy to run this script (http://www.scipy.org/)" - raise SystemExit, 1 - -try: - from pylab import * - from matplotlib.font_manager import fontManager, FontProperties -except ImportError: - print "Please install Matplotlib to run this script (http://matplotlib.sourceforge.net/)" - raise SystemExit, 1 - -from optparse import OptionParser - -class draw_constellation: - def __init__(self, filename, options): - self.hfile = open(filename, "r") - self.block_length = options.block - self.start = options.start - self.sample_rate = options.sample_rate - - self.datatype = scipy.complex64 - self.sizeof_data = self.datatype().nbytes # number of bytes per sample in file - - self.axis_font_size = 16 - self.label_font_size = 18 - self.title_font_size = 20 - - # Setup PLOT - self.fig = figure(1, figsize=(16, 9), facecolor='w') - rcParams['xtick.labelsize'] = self.axis_font_size - rcParams['ytick.labelsize'] = self.axis_font_size - - self.text_file = figtext(0.10, 0.95, ("File: %s" % filename), weight="heavy", size=16) - self.text_file_pos = figtext(0.10, 0.90, "File Position: ", weight="heavy", size=16) - self.text_block = figtext(0.40, 0.90, ("Block Size: %d" % self.block_length), - weight="heavy", size=16) - self.text_sr = figtext(0.60, 0.90, ("Sample Rate: %.2f" % self.sample_rate), - weight="heavy", size=16) - self.make_plots() - - self.button_left_axes = self.fig.add_axes([0.45, 0.01, 0.05, 0.05], frameon=True) - self.button_left = Button(self.button_left_axes, "<") - self.button_left_callback = self.button_left.on_clicked(self.button_left_click) - - self.button_right_axes = self.fig.add_axes([0.50, 0.01, 0.05, 0.05], frameon=True) - self.button_right = Button(self.button_right_axes, ">") - self.button_right_callback = self.button_right.on_clicked(self.button_right_click) - - self.xlim = self.sp_iq.get_xlim() - - self.manager = get_current_fig_manager() - connect('draw_event', self.zoom) - connect('key_press_event', self.click) - connect('button_press_event', self.mouse_button_callback) - show() - - def get_data(self): - self.text_file_pos.set_text("File Position: %d" % (self.hfile.tell()//self.sizeof_data)) - try: - iq = scipy.fromfile(self.hfile, dtype=self.datatype, count=self.block_length) - except MemoryError: - print "End of File" - else: - # retesting length here as newer version of scipy does not throw a MemoryError, just - # returns a zero-length array - if(len(iq) > 0): - self.reals = scipy.array([r.real for r in iq]) - self.imags = scipy.array([i.imag for i in iq]) - - self.time = scipy.array([i*(1/self.sample_rate) for i in range(len(self.reals))]) - return True - else: - print "End of File" - return False - - def make_plots(self): - # if specified on the command-line, set file pointer - self.hfile.seek(self.sizeof_data*self.start, 1) - - r = self.get_data() - - # Subplot for real and imaginary parts of signal - self.sp_iq = self.fig.add_subplot(2,1,1, position=[0.075, 0.2, 0.4, 0.6]) - self.sp_iq.set_title(("I&Q"), fontsize=self.title_font_size, fontweight="bold") - self.sp_iq.set_xlabel("Time (s)", fontsize=self.label_font_size, fontweight="bold") - self.sp_iq.set_ylabel("Amplitude (V)", fontsize=self.label_font_size, fontweight="bold") - self.plot_iq = self.sp_iq.plot(self.time, self.reals, 'bo-', self.time, self.imags, 'ro-') - - # Subplot for constellation plot - self.sp_const = self.fig.add_subplot(2,2,1, position=[0.575, 0.2, 0.4, 0.6]) - self.sp_const.set_title(("Constellation"), fontsize=self.title_font_size, fontweight="bold") - self.sp_const.set_xlabel("Inphase", fontsize=self.label_font_size, fontweight="bold") - self.sp_const.set_ylabel("Qaudrature", fontsize=self.label_font_size, fontweight="bold") - self.plot_const = self.sp_const.plot(self.reals, self.imags, 'bo') - - # Add plots to mark current location of point between time and constellation plots - self.indx = 0 - self.plot_iq += self.sp_iq.plot([self.time[self.indx],], [self.reals[self.indx],], 'mo', ms=8) - self.plot_iq += self.sp_iq.plot([self.time[self.indx],], [self.imags[self.indx],], 'mo', ms=8) - self.plot_const += self.sp_const.plot([self.reals[self.indx],], [self.imags[self.indx],], 'mo', ms=12) - - # Adjust axis - self.sp_iq.axis([self.time.min(), self.time.max(), - 1.5*min([self.reals.min(), self.imags.min()]), - 1.5*max([self.reals.max(), self.imags.max()])]) - self.sp_const.axis([-2, 2, -2, 2]) - - draw() - - def update_plots(self): - self.plot_iq[0].set_data([self.time, self.reals]) - self.plot_iq[1].set_data([self.time, self.imags]) - self.sp_iq.axis([self.time.min(), self.time.max(), - 1.5*min([self.reals.min(), self.imags.min()]), - 1.5*max([self.reals.max(), self.imags.max()])]) - - self.plot_const[0].set_data([self.reals, self.imags]) - self.sp_const.axis([-2, 2, -2, 2]) - draw() - - def zoom(self, event): - newxlim = scipy.array(self.sp_iq.get_xlim()) - curxlim = scipy.array(self.xlim) - if(newxlim[0] != curxlim[0] or newxlim[1] != curxlim[1]): - self.xlim = newxlim - r = self.reals[int(ceil(self.xlim[0])) : int(ceil(self.xlim[1]))] - i = self.imags[int(ceil(self.xlim[0])) : int(ceil(self.xlim[1]))] - - self.plot_const[0].set_data(r, i) - self.sp_const.axis([-2, 2, -2, 2]) - self.manager.canvas.draw() - draw() - - def click(self, event): - forward_valid_keys = [" ", "down", "right"] - backward_valid_keys = ["up", "left"] - trace_forward_valid_keys = [">",] - trace_backward_valid_keys = ["<",] - - if(find(event.key, forward_valid_keys)): - self.step_forward() - - elif(find(event.key, backward_valid_keys)): - self.step_backward() - - elif(find(event.key, trace_forward_valid_keys)): - self.indx = min(self.indx+1, len(self.time)-1) - self.set_trace(self.indx) - - elif(find(event.key, trace_backward_valid_keys)): - self.indx = max(0, self.indx-1) - self.set_trace(self.indx) - - def button_left_click(self, event): - self.step_backward() - - def button_right_click(self, event): - self.step_forward() - - def step_forward(self): - r = self.get_data() - if(r): - self.update_plots() - - def step_backward(self): - # Step back in file position - if(self.hfile.tell() >= 2*self.sizeof_data*self.block_length ): - self.hfile.seek(-2*self.sizeof_data*self.block_length, 1) - else: - self.hfile.seek(-self.hfile.tell(),1) - r = self.get_data() - if(r): - self.update_plots() - - - def mouse_button_callback(self, event): - x, y = event.xdata, event.ydata - - if x is not None and y is not None: - if(event.inaxes == self.sp_iq): - self.indx = searchsorted(self.time, [x]) - self.set_trace(self.indx) - - - def set_trace(self, indx): - self.plot_iq[2].set_data(self.time[indx], self.reals[indx]) - self.plot_iq[3].set_data(self.time[indx], self.imags[indx]) - self.plot_const[1].set_data(self.reals[indx], self.imags[indx]) - draw() - - -def find(item_in, list_search): - try: - return list_search.index(item_in) != None - except ValueError: - return False - - -def main(): - usage="%prog: [options] input_filename" - description = "Takes a GNU Radio complex binary file and displays the I&Q data versus time and the constellation plot (I vs. Q). You can set the block size to specify how many points to read in at a time and the start position in the file. By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." - - parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) - parser.add_option("-B", "--block", type="int", default=1000, - help="Specify the block size [default=%default]") - parser.add_option("-s", "--start", type="int", default=0, - help="Specify where to start in the file [default=%default]") - parser.add_option("-R", "--sample-rate", type="float", default=1.0, - help="Set the sampler rate of the data [default=%default]") - - (options, args) = parser.parse_args () - if len(args) != 1: - parser.print_help() - raise SystemExit, 1 - filename = args[0] - - dc = draw_constellation(filename, options) - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - pass - - - diff --git a/gr-utils/src/python/gr_plot_fft b/gr-utils/src/python/gr_plot_fft new file mode 100644 index 000000000..6859ea471 --- /dev/null +++ b/gr-utils/src/python/gr_plot_fft @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# +# Copyright 2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio.plot_fft_base import plot_fft_base + +# This is a wrapper program for plot_fft_base. It handles any data +# type and defaults to complex64. + +def main(): + parser = plot_fft_base.setup_options() + (options, args) = parser.parse_args () + if len(args) != 1: + parser.print_help() + raise SystemExit, 1 + filename = args[0] + + dc = plot_fft_base(options.data_type, filename, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass diff --git a/gr-utils/src/python/gr_plot_fft.py b/gr-utils/src/python/gr_plot_fft.py deleted file mode 100755 index ba3901e03..000000000 --- a/gr-utils/src/python/gr_plot_fft.py +++ /dev/null @@ -1,248 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2007,2008,2011 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -try: - import scipy - from scipy import fftpack -except ImportError: - print "Please install SciPy to run this script (http://www.scipy.org/)" - raise SystemExit, 1 - -try: - from pylab import * -except ImportError: - print "Please install Matplotlib to run this script (http://matplotlib.sourceforge.net/)" - raise SystemExit, 1 - -from optparse import OptionParser - -class gr_plot_fft: - def __init__(self, datatype, filename, options): - self.hfile = open(filename, "r") - self.block_length = options.block - self.start = options.start - self.sample_rate = options.sample_rate - - self.datatype = getattr(scipy, datatype) - self.sizeof_data = self.datatype().nbytes # number of bytes per sample in file - - self.axis_font_size = 16 - self.label_font_size = 18 - self.title_font_size = 20 - self.text_size = 22 - - # Setup PLOT - self.fig = figure(1, figsize=(16, 12), facecolor='w') - rcParams['xtick.labelsize'] = self.axis_font_size - rcParams['ytick.labelsize'] = self.axis_font_size - - self.text_file = figtext(0.10, 0.94, ("File: %s" % filename), weight="heavy", size=self.text_size) - self.text_file_pos = figtext(0.10, 0.88, "File Position: ", weight="heavy", size=self.text_size) - self.text_block = figtext(0.35, 0.88, ("Block Size: %d" % self.block_length), - weight="heavy", size=self.text_size) - self.text_sr = figtext(0.60, 0.88, ("Sample Rate: %.2f" % self.sample_rate), - weight="heavy", size=self.text_size) - self.make_plots() - - self.button_left_axes = self.fig.add_axes([0.45, 0.01, 0.05, 0.05], frameon=True) - self.button_left = Button(self.button_left_axes, "<") - self.button_left_callback = self.button_left.on_clicked(self.button_left_click) - - self.button_right_axes = self.fig.add_axes([0.50, 0.01, 0.05, 0.05], frameon=True) - self.button_right = Button(self.button_right_axes, ">") - self.button_right_callback = self.button_right.on_clicked(self.button_right_click) - - self.xlim = self.sp_iq.get_xlim() - - self.manager = get_current_fig_manager() - connect('draw_event', self.zoom) - connect('key_press_event', self.click) - show() - - def get_data(self): - self.position = self.hfile.tell()/self.sizeof_data - self.text_file_pos.set_text("File Position: %d" % (self.position)) - try: - self.iq = scipy.fromfile(self.hfile, dtype=self.datatype, count=self.block_length) - except MemoryError: - print "End of File" - else: - self.iq_fft = self.dofft(self.iq) - - tstep = 1.0 / self.sample_rate - #self.time = scipy.array([tstep*(self.position + i) for i in xrange(len(self.iq))]) - self.time = scipy.array([tstep*(i) for i in xrange(len(self.iq))]) - - self.freq = self.calc_freq(self.time, self.sample_rate) - - def dofft(self, iq): - N = len(iq) - iq_fft = fftpack.fftshift(scipy.fft(iq)) # fft and shift axis - iq_fft = 20*scipy.log10(abs((iq_fft+1e-15)/N)) # convert to decibels, adjust power - # adding 1e-15 (-300 dB) to protect against value errors if an item in iq_fft is 0 - return iq_fft - - def calc_freq(self, time, sample_rate): - N = len(time) - Fs = 1.0 / (time.max() - time.min()) - Fn = 0.5 * sample_rate - freq = scipy.array([-Fn + i*Fs for i in xrange(N)]) - return freq - - def make_plots(self): - # if specified on the command-line, set file pointer - self.hfile.seek(self.sizeof_data*self.start, 1) - - # Subplot for real and imaginary parts of signal - self.sp_iq = self.fig.add_subplot(2,2,1, position=[0.075, 0.2, 0.4, 0.6]) - self.sp_iq.set_title(("I&Q"), fontsize=self.title_font_size, fontweight="bold") - self.sp_iq.set_xlabel("Time (s)", fontsize=self.label_font_size, fontweight="bold") - self.sp_iq.set_ylabel("Amplitude (V)", fontsize=self.label_font_size, fontweight="bold") - - # Subplot for FFT plot - self.sp_fft = self.fig.add_subplot(2,2,2, position=[0.575, 0.2, 0.4, 0.6]) - self.sp_fft.set_title(("FFT"), fontsize=self.title_font_size, fontweight="bold") - self.sp_fft.set_xlabel("Frequency (Hz)", fontsize=self.label_font_size, fontweight="bold") - self.sp_fft.set_ylabel("Power Spectrum (dBm)", fontsize=self.label_font_size, fontweight="bold") - - self.get_data() - - self.plot_iq = self.sp_iq.plot([], 'bo-') # make plot for reals - self.plot_iq += self.sp_iq.plot([], 'ro-') # make plot for imags - self.draw_time() # draw the plot - - self.plot_fft = self.sp_fft.plot([], 'bo-') # make plot for FFT - self.draw_fft() # draw the plot - - draw() - - def draw_time(self): - reals = self.iq.real - imags = self.iq.imag - self.plot_iq[0].set_data([self.time, reals]) - self.plot_iq[1].set_data([self.time, imags]) - self.sp_iq.set_xlim(self.time.min(), self.time.max()) - self.sp_iq.set_ylim([1.5*min([reals.min(), imags.min()]), - 1.5*max([reals.max(), imags.max()])]) - - def draw_fft(self): - self.plot_fft[0].set_data([self.freq, self.iq_fft]) - self.sp_fft.set_xlim(self.freq.min(), self.freq.max()) - self.sp_fft.set_ylim([self.iq_fft.min()-10, self.iq_fft.max()+10]) - - def update_plots(self): - self.draw_time() - self.draw_fft() - - self.xlim = self.sp_iq.get_xlim() - draw() - - def zoom(self, event): - newxlim = scipy.array(self.sp_iq.get_xlim()) - curxlim = scipy.array(self.xlim) - if(newxlim[0] != curxlim[0] or newxlim[1] != curxlim[1]): - self.xlim = newxlim - #xmin = max(0, int(ceil(self.sample_rate*(self.xlim[0] - self.position)))) - #xmax = min(int(ceil(self.sample_rate*(self.xlim[1] - self.position))), len(self.iq)) - xmin = max(0, int(ceil(self.sample_rate*(self.xlim[0])))) - xmax = min(int(ceil(self.sample_rate*(self.xlim[1]))), len(self.iq)) - - iq = self.iq[xmin : xmax] - time = self.time[xmin : xmax] - - iq_fft = self.dofft(iq) - freq = self.calc_freq(time, self.sample_rate) - - self.plot_fft[0].set_data(freq, iq_fft) - self.sp_fft.axis([freq.min(), freq.max(), - iq_fft.min()-10, iq_fft.max()+10]) - - draw() - - def click(self, event): - forward_valid_keys = [" ", "down", "right"] - backward_valid_keys = ["up", "left"] - - if(find(event.key, forward_valid_keys)): - self.step_forward() - - elif(find(event.key, backward_valid_keys)): - self.step_backward() - - def button_left_click(self, event): - self.step_backward() - - def button_right_click(self, event): - self.step_forward() - - def step_forward(self): - self.get_data() - self.update_plots() - - def step_backward(self): - # Step back in file position - if(self.hfile.tell() >= 2*self.sizeof_data*self.block_length ): - self.hfile.seek(-2*self.sizeof_data*self.block_length, 1) - else: - self.hfile.seek(-self.hfile.tell(),1) - self.get_data() - self.update_plots() - -def find(item_in, list_search): - try: - return list_search.index(item_in) != None - except ValueError: - return False - -def setup_options(): - usage="%prog: [options] input_filename" - description = "Takes a GNU Radio complex binary file and displays the I&Q data versus time as well as the frequency domain (FFT) plot. The y-axis values are plotted assuming volts as the amplitude of the I&Q streams and converted into dBm in the frequency domain (the 1/N power adjustment out of the FFT is performed internally). The script plots a certain block of data at a time, specified on the command line as -B or --block. This value defaults to 1000. The start position in the file can be set by specifying -s or --start and defaults to 0 (the start of the file). By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time and frequency axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." - - parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) - parser.add_option("-d", "--data-type", type="string", default="complex64", - help="Specify the data type (complex64, float32, (u)int32, (u)int16, (u)int8) [default=%default]") - parser.add_option("-B", "--block", type="int", default=1000, - help="Specify the block size [default=%default]") - parser.add_option("-s", "--start", type="int", default=0, - help="Specify where to start in the file [default=%default]") - parser.add_option("-R", "--sample-rate", type="float", default=1.0, - help="Set the sampler rate of the data [default=%default]") - return parser - -def main(): - parser = setup_options() - (options, args) = parser.parse_args () - if len(args) != 1: - parser.print_help() - raise SystemExit, 1 - filename = args[0] - - dc = gr_plot_fft(options.data_type, filename, options) - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - pass - - - diff --git a/gr-utils/src/python/gr_plot_fft_c b/gr-utils/src/python/gr_plot_fft_c new file mode 100755 index 000000000..e4ee9c5a9 --- /dev/null +++ b/gr-utils/src/python/gr_plot_fft_c @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# +# Copyright 2007,2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio.plot_fft_base import plot_fft_base + +# This is a wrapper program for plot_fft_base specifically for complex data + +def main(): + parser = plot_fft_base.setup_options() + parser.remove_option("--data-type") + + (options, args) = parser.parse_args () + if len(args) != 1: + parser.print_help() + raise SystemExit, 1 + filename = args[0] + + dc = plot_fft_base("complex64", filename, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + + + diff --git a/gr-utils/src/python/gr_plot_fft_c.py b/gr-utils/src/python/gr_plot_fft_c.py deleted file mode 100755 index de59b36c9..000000000 --- a/gr-utils/src/python/gr_plot_fft_c.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2007,2008 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -import gr_plot_fft - -def main(): - parser = gr_plot_fft.setup_options() - parser.remove_option("--data-type") - - (options, args) = parser.parse_args () - if len(args) != 1: - parser.print_help() - raise SystemExit, 1 - filename = args[0] - - dc = gr_plot_fft.gr_plot_fft("complex64", filename, options) - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - pass - - - diff --git a/gr-utils/src/python/gr_plot_fft_f b/gr-utils/src/python/gr_plot_fft_f new file mode 100755 index 000000000..66c75a8ff --- /dev/null +++ b/gr-utils/src/python/gr_plot_fft_f @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# +# Copyright 2007,2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio.plot_fft_base import plot_fft_base + +# This is a wrapper program for plot_fft_base specifically for float data + +def main(): + parser = plot_fft_base.setup_options() + parser.remove_option("--data-type") + + (options, args) = parser.parse_args () + if len(args) != 1: + parser.print_help() + raise SystemExit, 1 + filename = args[0] + + dc = plot_fft_base("float32", filename, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + + + diff --git a/gr-utils/src/python/gr_plot_fft_f.py b/gr-utils/src/python/gr_plot_fft_f.py deleted file mode 100755 index f50358f01..000000000 --- a/gr-utils/src/python/gr_plot_fft_f.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2007,2008 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -import gr_plot_fft - -def main(): - parser = gr_plot_fft.setup_options() - parser.remove_option("--data-type") - - (options, args) = parser.parse_args () - if len(args) != 1: - parser.print_help() - raise SystemExit, 1 - filename = args[0] - - dc = gr_plot_fft.gr_plot_fft("float32", filename, options) - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - pass - - - diff --git a/gr-utils/src/python/gr_plot_float b/gr-utils/src/python/gr_plot_float new file mode 100755 index 000000000..e5c22a315 --- /dev/null +++ b/gr-utils/src/python/gr_plot_float @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# +# Copyright 2007,2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +try: + import scipy +except ImportError: + print "Please install SciPy to run this script (http://www.scipy.org/)" + raise SystemExit, 1 + +from optparse import OptionParser +from gnuradio.plot_data import plot_data + +def main(): + usage="%prog: [options] input_filenames" + description = "Takes a GNU Radio floating point binary file and displays the samples versus time. You can set the block size to specify how many points to read in at a time and the start position in the file. By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." + + parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) + parser.add_option("-B", "--block", type="int", default=1000, + help="Specify the block size [default=%default]") + parser.add_option("-s", "--start", type="int", default=0, + help="Specify where to start in the file [default=%default]") + parser.add_option("-R", "--sample-rate", type="float", default=1.0, + help="Set the sampler rate of the data [default=%default]") + + (options, args) = parser.parse_args () + if len(args) < 1: + parser.print_help() + raise SystemExit, 1 + filenames = args + + datatype=scipy.float32 + dc = plot_data(datatype, filenames, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + diff --git a/gr-utils/src/python/gr_plot_float.py b/gr-utils/src/python/gr_plot_float.py deleted file mode 100755 index e5c22a315..000000000 --- a/gr-utils/src/python/gr_plot_float.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2007,2008 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -try: - import scipy -except ImportError: - print "Please install SciPy to run this script (http://www.scipy.org/)" - raise SystemExit, 1 - -from optparse import OptionParser -from gnuradio.plot_data import plot_data - -def main(): - usage="%prog: [options] input_filenames" - description = "Takes a GNU Radio floating point binary file and displays the samples versus time. You can set the block size to specify how many points to read in at a time and the start position in the file. By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." - - parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) - parser.add_option("-B", "--block", type="int", default=1000, - help="Specify the block size [default=%default]") - parser.add_option("-s", "--start", type="int", default=0, - help="Specify where to start in the file [default=%default]") - parser.add_option("-R", "--sample-rate", type="float", default=1.0, - help="Set the sampler rate of the data [default=%default]") - - (options, args) = parser.parse_args () - if len(args) < 1: - parser.print_help() - raise SystemExit, 1 - filenames = args - - datatype=scipy.float32 - dc = plot_data(datatype, filenames, options) - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - pass - diff --git a/gr-utils/src/python/gr_plot_int b/gr-utils/src/python/gr_plot_int new file mode 100755 index 000000000..b44d4360a --- /dev/null +++ b/gr-utils/src/python/gr_plot_int @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# +# Copyright 2007,2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +try: + import scipy +except ImportError: + print "Please install SciPy to run this script (http://www.scipy.org/)" + raise SystemExit, 1 + +from optparse import OptionParser +from gnuradio.plot_data import plot_data + +def main(): + usage="%prog: [options] input_filenames" + description = "Takes a GNU Radio integer binary file and displays the samples versus time. You can set the block size to specify how many points to read in at a time and the start position in the file. By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." + + parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) + parser.add_option("-B", "--block", type="int", default=1000, + help="Specify the block size [default=%default]") + parser.add_option("-s", "--start", type="int", default=0, + help="Specify where to start in the file [default=%default]") + parser.add_option("-R", "--sample-rate", type="float", default=1.0, + help="Set the sampler rate of the data [default=%default]") + + (options, args) = parser.parse_args () + if len(args) < 1: + parser.print_help() + raise SystemExit, 1 + filenames = args + + datatype=scipy.int32 + dc = plot_data(datatype, filenames, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + diff --git a/gr-utils/src/python/gr_plot_int.py b/gr-utils/src/python/gr_plot_int.py deleted file mode 100755 index b44d4360a..000000000 --- a/gr-utils/src/python/gr_plot_int.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2007,2008 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -try: - import scipy -except ImportError: - print "Please install SciPy to run this script (http://www.scipy.org/)" - raise SystemExit, 1 - -from optparse import OptionParser -from gnuradio.plot_data import plot_data - -def main(): - usage="%prog: [options] input_filenames" - description = "Takes a GNU Radio integer binary file and displays the samples versus time. You can set the block size to specify how many points to read in at a time and the start position in the file. By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." - - parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) - parser.add_option("-B", "--block", type="int", default=1000, - help="Specify the block size [default=%default]") - parser.add_option("-s", "--start", type="int", default=0, - help="Specify where to start in the file [default=%default]") - parser.add_option("-R", "--sample-rate", type="float", default=1.0, - help="Set the sampler rate of the data [default=%default]") - - (options, args) = parser.parse_args () - if len(args) < 1: - parser.print_help() - raise SystemExit, 1 - filenames = args - - datatype=scipy.int32 - dc = plot_data(datatype, filenames, options) - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - pass - diff --git a/gr-utils/src/python/gr_plot_iq b/gr-utils/src/python/gr_plot_iq new file mode 100755 index 000000000..316e60a75 --- /dev/null +++ b/gr-utils/src/python/gr_plot_iq @@ -0,0 +1,178 @@ +#!/usr/bin/env python +# +# Copyright 2007,2008,2011 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +try: + import scipy +except ImportError: + print "Please install SciPy to run this script (http://www.scipy.org/)" + raise SystemExit, 1 + +try: + from pylab import * +except ImportError: + print "Please install Matplotlib to run this script (http://matplotlib.sourceforge.net/)" + raise SystemExit, 1 + +from optparse import OptionParser + +class draw_iq: + def __init__(self, filename, options): + self.hfile = open(filename, "r") + self.block_length = options.block + self.start = options.start + self.sample_rate = options.sample_rate + + self.datatype = scipy.complex64 + self.sizeof_data = self.datatype().nbytes # number of bytes per sample in file + + self.axis_font_size = 16 + self.label_font_size = 18 + self.title_font_size = 20 + self.text_size = 22 + + # Setup PLOT + self.fig = figure(1, figsize=(16, 9), facecolor='w') + rcParams['xtick.labelsize'] = self.axis_font_size + rcParams['ytick.labelsize'] = self.axis_font_size + + self.text_file = figtext(0.10, 0.94, ("File: %s" % filename), weight="heavy", size=self.text_size) + self.text_file_pos = figtext(0.10, 0.88, "File Position: ", weight="heavy", size=self.text_size) + self.text_block = figtext(0.40, 0.88, ("Block Size: %d" % self.block_length), + weight="heavy", size=self.text_size) + self.text_sr = figtext(0.60, 0.88, ("Sample Rate: %.2f" % self.sample_rate), + weight="heavy", size=self.text_size) + self.make_plots() + + self.button_left_axes = self.fig.add_axes([0.45, 0.01, 0.05, 0.05], frameon=True) + self.button_left = Button(self.button_left_axes, "<") + self.button_left_callback = self.button_left.on_clicked(self.button_left_click) + + self.button_right_axes = self.fig.add_axes([0.50, 0.01, 0.05, 0.05], frameon=True) + self.button_right = Button(self.button_right_axes, ">") + self.button_right_callback = self.button_right.on_clicked(self.button_right_click) + + self.xlim = self.sp_iq.get_xlim() + + self.manager = get_current_fig_manager() + connect('key_press_event', self.click) + show() + + def get_data(self): + self.text_file_pos.set_text("File Position: %d" % (self.hfile.tell()//self.sizeof_data)) + try: + self.iq = scipy.fromfile(self.hfile, dtype=self.datatype, count=self.block_length) + except MemoryError: + print "End of File" + else: + self.reals = scipy.array([r.real for r in self.iq]) + self.imags = scipy.array([i.imag for i in self.iq]) + self.time = scipy.array([i*(1/self.sample_rate) for i in range(len(self.reals))]) + + def make_plots(self): + # if specified on the command-line, set file pointer + self.hfile.seek(self.sizeof_data*self.start, 1) + + self.get_data() + + # Subplot for real and imaginary parts of signal + self.sp_iq = self.fig.add_subplot(2,1,1, position=[0.075, 0.14, 0.85, 0.67]) + self.sp_iq.set_title(("I&Q"), fontsize=self.title_font_size, fontweight="bold") + self.sp_iq.set_xlabel("Time (s)", fontsize=self.label_font_size, fontweight="bold") + self.sp_iq.set_ylabel("Amplitude (V)", fontsize=self.label_font_size, fontweight="bold") + self.plot_iq = plot(self.time, self.reals, 'bo-', self.time, self.imags, 'ro-') + self.sp_iq.set_ylim([1.5*min([self.reals.min(), self.imags.min()]), + 1.5*max([self.reals.max(), self.imags.max()])]) + self.sp_iq.set_xlim(self.time.min(), self.time.max()) + draw() + + def update_plots(self): + self.plot_iq[0].set_data([self.time, self.reals]) + self.plot_iq[1].set_data([self.time, self.imags]) + self.sp_iq.set_ylim([1.5*min([self.reals.min(), self.imags.min()]), + 1.5*max([self.reals.max(), self.imags.max()])]) + self.sp_iq.set_xlim(self.time.min(), self.time.max()) + draw() + + def click(self, event): + forward_valid_keys = [" ", "down", "right"] + backward_valid_keys = ["up", "left"] + + if(find(event.key, forward_valid_keys)): + self.step_forward() + + elif(find(event.key, backward_valid_keys)): + self.step_backward() + + def button_left_click(self, event): + self.step_backward() + + def button_right_click(self, event): + self.step_forward() + + def step_forward(self): + self.get_data() + self.update_plots() + + def step_backward(self): + # Step back in file position + if(self.hfile.tell() >= 2*self.sizeof_data*self.block_length ): + self.hfile.seek(-2*self.sizeof_data*self.block_length, 1) + else: + self.hfile.seek(-self.hfile.tell(),1) + self.get_data() + self.update_plots() + + +def find(item_in, list_search): + try: + return list_search.index(item_in) != None + except ValueError: + return False + +def main(): + usage="%prog: [options] input_filename" + description = "Takes a GNU Radio complex binary file and displays the I&Q data versus time. You can set the block size to specify how many points to read in at a time and the start position in the file. By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." + + parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) + parser.add_option("-B", "--block", type="int", default=1000, + help="Specify the block size [default=%default]") + parser.add_option("-s", "--start", type="int", default=0, + help="Specify where to start in the file [default=%default]") + parser.add_option("-R", "--sample-rate", type="float", default=1.0, + help="Set the sampler rate of the data [default=%default]") + + (options, args) = parser.parse_args () + if len(args) != 1: + parser.print_help() + raise SystemExit, 1 + filename = args[0] + + dc = draw_iq(filename, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + + + diff --git a/gr-utils/src/python/gr_plot_iq.py b/gr-utils/src/python/gr_plot_iq.py deleted file mode 100755 index 316e60a75..000000000 --- a/gr-utils/src/python/gr_plot_iq.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2007,2008,2011 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -try: - import scipy -except ImportError: - print "Please install SciPy to run this script (http://www.scipy.org/)" - raise SystemExit, 1 - -try: - from pylab import * -except ImportError: - print "Please install Matplotlib to run this script (http://matplotlib.sourceforge.net/)" - raise SystemExit, 1 - -from optparse import OptionParser - -class draw_iq: - def __init__(self, filename, options): - self.hfile = open(filename, "r") - self.block_length = options.block - self.start = options.start - self.sample_rate = options.sample_rate - - self.datatype = scipy.complex64 - self.sizeof_data = self.datatype().nbytes # number of bytes per sample in file - - self.axis_font_size = 16 - self.label_font_size = 18 - self.title_font_size = 20 - self.text_size = 22 - - # Setup PLOT - self.fig = figure(1, figsize=(16, 9), facecolor='w') - rcParams['xtick.labelsize'] = self.axis_font_size - rcParams['ytick.labelsize'] = self.axis_font_size - - self.text_file = figtext(0.10, 0.94, ("File: %s" % filename), weight="heavy", size=self.text_size) - self.text_file_pos = figtext(0.10, 0.88, "File Position: ", weight="heavy", size=self.text_size) - self.text_block = figtext(0.40, 0.88, ("Block Size: %d" % self.block_length), - weight="heavy", size=self.text_size) - self.text_sr = figtext(0.60, 0.88, ("Sample Rate: %.2f" % self.sample_rate), - weight="heavy", size=self.text_size) - self.make_plots() - - self.button_left_axes = self.fig.add_axes([0.45, 0.01, 0.05, 0.05], frameon=True) - self.button_left = Button(self.button_left_axes, "<") - self.button_left_callback = self.button_left.on_clicked(self.button_left_click) - - self.button_right_axes = self.fig.add_axes([0.50, 0.01, 0.05, 0.05], frameon=True) - self.button_right = Button(self.button_right_axes, ">") - self.button_right_callback = self.button_right.on_clicked(self.button_right_click) - - self.xlim = self.sp_iq.get_xlim() - - self.manager = get_current_fig_manager() - connect('key_press_event', self.click) - show() - - def get_data(self): - self.text_file_pos.set_text("File Position: %d" % (self.hfile.tell()//self.sizeof_data)) - try: - self.iq = scipy.fromfile(self.hfile, dtype=self.datatype, count=self.block_length) - except MemoryError: - print "End of File" - else: - self.reals = scipy.array([r.real for r in self.iq]) - self.imags = scipy.array([i.imag for i in self.iq]) - self.time = scipy.array([i*(1/self.sample_rate) for i in range(len(self.reals))]) - - def make_plots(self): - # if specified on the command-line, set file pointer - self.hfile.seek(self.sizeof_data*self.start, 1) - - self.get_data() - - # Subplot for real and imaginary parts of signal - self.sp_iq = self.fig.add_subplot(2,1,1, position=[0.075, 0.14, 0.85, 0.67]) - self.sp_iq.set_title(("I&Q"), fontsize=self.title_font_size, fontweight="bold") - self.sp_iq.set_xlabel("Time (s)", fontsize=self.label_font_size, fontweight="bold") - self.sp_iq.set_ylabel("Amplitude (V)", fontsize=self.label_font_size, fontweight="bold") - self.plot_iq = plot(self.time, self.reals, 'bo-', self.time, self.imags, 'ro-') - self.sp_iq.set_ylim([1.5*min([self.reals.min(), self.imags.min()]), - 1.5*max([self.reals.max(), self.imags.max()])]) - self.sp_iq.set_xlim(self.time.min(), self.time.max()) - draw() - - def update_plots(self): - self.plot_iq[0].set_data([self.time, self.reals]) - self.plot_iq[1].set_data([self.time, self.imags]) - self.sp_iq.set_ylim([1.5*min([self.reals.min(), self.imags.min()]), - 1.5*max([self.reals.max(), self.imags.max()])]) - self.sp_iq.set_xlim(self.time.min(), self.time.max()) - draw() - - def click(self, event): - forward_valid_keys = [" ", "down", "right"] - backward_valid_keys = ["up", "left"] - - if(find(event.key, forward_valid_keys)): - self.step_forward() - - elif(find(event.key, backward_valid_keys)): - self.step_backward() - - def button_left_click(self, event): - self.step_backward() - - def button_right_click(self, event): - self.step_forward() - - def step_forward(self): - self.get_data() - self.update_plots() - - def step_backward(self): - # Step back in file position - if(self.hfile.tell() >= 2*self.sizeof_data*self.block_length ): - self.hfile.seek(-2*self.sizeof_data*self.block_length, 1) - else: - self.hfile.seek(-self.hfile.tell(),1) - self.get_data() - self.update_plots() - - -def find(item_in, list_search): - try: - return list_search.index(item_in) != None - except ValueError: - return False - -def main(): - usage="%prog: [options] input_filename" - description = "Takes a GNU Radio complex binary file and displays the I&Q data versus time. You can set the block size to specify how many points to read in at a time and the start position in the file. By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." - - parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) - parser.add_option("-B", "--block", type="int", default=1000, - help="Specify the block size [default=%default]") - parser.add_option("-s", "--start", type="int", default=0, - help="Specify where to start in the file [default=%default]") - parser.add_option("-R", "--sample-rate", type="float", default=1.0, - help="Set the sampler rate of the data [default=%default]") - - (options, args) = parser.parse_args () - if len(args) != 1: - parser.print_help() - raise SystemExit, 1 - filename = args[0] - - dc = draw_iq(filename, options) - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - pass - - - diff --git a/gr-utils/src/python/gr_plot_psd b/gr-utils/src/python/gr_plot_psd new file mode 100644 index 000000000..658e7b297 --- /dev/null +++ b/gr-utils/src/python/gr_plot_psd @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# +# Copyright 2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio.plot_psd_base import plot_psd_base + +# This is a wrapper program for plot_psd_base. It handles any data +# type and defaults to complex64. + +def main(): + parser = plot_psd_base.setup_options() + (options, args) = parser.parse_args () + if len(args) != 1: + parser.print_help() + raise SystemExit, 1 + filename = args[0] + + dc = plot_psd_base(options.data_type, filename, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass diff --git a/gr-utils/src/python/gr_plot_psd.py b/gr-utils/src/python/gr_plot_psd.py deleted file mode 100755 index 3dab0535a..000000000 --- a/gr-utils/src/python/gr_plot_psd.py +++ /dev/null @@ -1,291 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2007,2008,2010,2011 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -try: - import scipy - from scipy import fftpack -except ImportError: - print "Please install SciPy to run this script (http://www.scipy.org/)" - raise SystemExit, 1 - -try: - from pylab import * -except ImportError: - print "Please install Matplotlib to run this script (http://matplotlib.sourceforge.net/)" - raise SystemExit, 1 - -from optparse import OptionParser -from scipy import log10 -from gnuradio.eng_option import eng_option - -class gr_plot_psd: - def __init__(self, datatype, filename, options): - self.hfile = open(filename, "r") - self.block_length = options.block - self.start = options.start - self.sample_rate = options.sample_rate - self.psdfftsize = options.psd_size - self.specfftsize = options.spec_size - - self.dospec = options.enable_spec # if we want to plot the spectrogram - - self.datatype = getattr(scipy, datatype) #scipy.complex64 - self.sizeof_data = self.datatype().nbytes # number of bytes per sample in file - - self.axis_font_size = 16 - self.label_font_size = 18 - self.title_font_size = 20 - self.text_size = 22 - - # Setup PLOT - self.fig = figure(1, figsize=(16, 12), facecolor='w') - rcParams['xtick.labelsize'] = self.axis_font_size - rcParams['ytick.labelsize'] = self.axis_font_size - - self.text_file = figtext(0.10, 0.95, ("File: %s" % filename), - weight="heavy", size=self.text_size) - self.text_file_pos = figtext(0.10, 0.92, "File Position: ", - weight="heavy", size=self.text_size) - self.text_block = figtext(0.35, 0.92, ("Block Size: %d" % self.block_length), - weight="heavy", size=self.text_size) - self.text_sr = figtext(0.60, 0.915, ("Sample Rate: %.2f" % self.sample_rate), - weight="heavy", size=self.text_size) - self.make_plots() - - self.button_left_axes = self.fig.add_axes([0.45, 0.01, 0.05, 0.05], frameon=True) - self.button_left = Button(self.button_left_axes, "<") - self.button_left_callback = self.button_left.on_clicked(self.button_left_click) - - self.button_right_axes = self.fig.add_axes([0.50, 0.01, 0.05, 0.05], frameon=True) - self.button_right = Button(self.button_right_axes, ">") - self.button_right_callback = self.button_right.on_clicked(self.button_right_click) - - self.xlim = scipy.array(self.sp_iq.get_xlim()) - - self.manager = get_current_fig_manager() - connect('draw_event', self.zoom) - connect('key_press_event', self.click) - show() - - def get_data(self): - self.position = self.hfile.tell()/self.sizeof_data - self.text_file_pos.set_text("File Position: %d" % self.position) - try: - self.iq = scipy.fromfile(self.hfile, dtype=self.datatype, count=self.block_length) - except MemoryError: - print "End of File" - return False - else: - # retesting length here as newer version of scipy does not throw a MemoryError, just - # returns a zero-length array - if(len(self.iq) > 0): - tstep = 1.0 / self.sample_rate - #self.time = scipy.array([tstep*(self.position + i) for i in xrange(len(self.iq))]) - self.time = scipy.array([tstep*(i) for i in xrange(len(self.iq))]) - - self.iq_psd, self.freq = self.dopsd(self.iq) - return True - else: - print "End of File" - return False - - def dopsd(self, iq): - ''' Need to do this here and plot later so we can do the fftshift ''' - overlap = self.psdfftsize/4 - winfunc = scipy.blackman - psd,freq = mlab.psd(iq, self.psdfftsize, self.sample_rate, - window = lambda d: d*winfunc(self.psdfftsize), - noverlap = overlap) - psd = 10.0*log10(abs(psd)) - return (psd, freq) - - def make_plots(self): - # if specified on the command-line, set file pointer - self.hfile.seek(self.sizeof_data*self.start, 1) - - iqdims = [[0.075, 0.2, 0.4, 0.6], [0.075, 0.55, 0.4, 0.3]] - psddims = [[0.575, 0.2, 0.4, 0.6], [0.575, 0.55, 0.4, 0.3]] - specdims = [0.2, 0.125, 0.6, 0.3] - - # Subplot for real and imaginary parts of signal - self.sp_iq = self.fig.add_subplot(2,2,1, position=iqdims[self.dospec]) - self.sp_iq.set_title(("I&Q"), fontsize=self.title_font_size, fontweight="bold") - self.sp_iq.set_xlabel("Time (s)", fontsize=self.label_font_size, fontweight="bold") - self.sp_iq.set_ylabel("Amplitude (V)", fontsize=self.label_font_size, fontweight="bold") - - # Subplot for PSD plot - self.sp_psd = self.fig.add_subplot(2,2,2, position=psddims[self.dospec]) - self.sp_psd.set_title(("PSD"), fontsize=self.title_font_size, fontweight="bold") - self.sp_psd.set_xlabel("Frequency (Hz)", fontsize=self.label_font_size, fontweight="bold") - self.sp_psd.set_ylabel("Power Spectrum (dBm)", fontsize=self.label_font_size, fontweight="bold") - - r = self.get_data() - - self.plot_iq = self.sp_iq.plot([], 'bo-') # make plot for reals - self.plot_iq += self.sp_iq.plot([], 'ro-') # make plot for imags - self.draw_time(self.time, self.iq) # draw the plot - - self.plot_psd = self.sp_psd.plot([], 'b') # make plot for PSD - self.draw_psd(self.freq, self.iq_psd) # draw the plot - - - if self.dospec: - # Subplot for spectrogram plot - self.sp_spec = self.fig.add_subplot(2,2,3, position=specdims) - self.sp_spec.set_title(("Spectrogram"), fontsize=self.title_font_size, fontweight="bold") - self.sp_spec.set_xlabel("Time (s)", fontsize=self.label_font_size, fontweight="bold") - self.sp_spec.set_ylabel("Frequency (Hz)", fontsize=self.label_font_size, fontweight="bold") - - self.draw_spec(self.time, self.iq) - - draw() - - def draw_time(self, t, iq): - reals = iq.real - imags = iq.imag - self.plot_iq[0].set_data([t, reals]) - self.plot_iq[1].set_data([t, imags]) - self.sp_iq.set_xlim(t.min(), t.max()) - self.sp_iq.set_ylim([1.5*min([reals.min(), imags.min()]), - 1.5*max([reals.max(), imags.max()])]) - - def draw_psd(self, f, p): - self.plot_psd[0].set_data([f, p]) - self.sp_psd.set_ylim([p.min()-10, p.max()+10]) - self.sp_psd.set_xlim([f.min(), f.max()]) - - def draw_spec(self, t, s): - overlap = self.specfftsize/4 - winfunc = scipy.blackman - self.sp_spec.clear() - self.sp_spec.specgram(s, self.specfftsize, self.sample_rate, - window = lambda d: d*winfunc(self.specfftsize), - noverlap = overlap, xextent=[t.min(), t.max()]) - - def update_plots(self): - self.draw_time(self.time, self.iq) - self.draw_psd(self.freq, self.iq_psd) - - if self.dospec: - self.draw_spec(self.time, self.iq) - - self.xlim = scipy.array(self.sp_iq.get_xlim()) # so zoom doesn't get called - - draw() - - def zoom(self, event): - newxlim = scipy.array(self.sp_iq.get_xlim()) - curxlim = scipy.array(self.xlim) - if(newxlim[0] != curxlim[0] or newxlim[1] != curxlim[1]): - #xmin = max(0, int(ceil(self.sample_rate*(newxlim[0] - self.position)))) - #xmax = min(int(ceil(self.sample_rate*(newxlim[1] - self.position))), len(self.iq)) - xmin = max(0, int(ceil(self.sample_rate*(newxlim[0])))) - xmax = min(int(ceil(self.sample_rate*(newxlim[1]))), len(self.iq)) - - iq = scipy.array(self.iq[xmin : xmax]) - time = scipy.array(self.time[xmin : xmax]) - - iq_psd, freq = self.dopsd(iq) - - self.draw_psd(freq, iq_psd) - self.xlim = scipy.array(self.sp_iq.get_xlim()) - - draw() - - def click(self, event): - forward_valid_keys = [" ", "down", "right"] - backward_valid_keys = ["up", "left"] - - if(find(event.key, forward_valid_keys)): - self.step_forward() - - elif(find(event.key, backward_valid_keys)): - self.step_backward() - - def button_left_click(self, event): - self.step_backward() - - def button_right_click(self, event): - self.step_forward() - - def step_forward(self): - r = self.get_data() - if(r): - self.update_plots() - - def step_backward(self): - # Step back in file position - if(self.hfile.tell() >= 2*self.sizeof_data*self.block_length ): - self.hfile.seek(-2*self.sizeof_data*self.block_length, 1) - else: - self.hfile.seek(-self.hfile.tell(),1) - r = self.get_data() - if(r): - self.update_plots() - -def find(item_in, list_search): - try: - return list_search.index(item_in) != None - except ValueError: - return False - -def setup_options(): - usage="%prog: [options] input_filename" - description = "Takes a GNU Radio binary file (with specified data type using --data-type) and displays the I&Q data versus time as well as the power spectral density (PSD) plot. The y-axis values are plotted assuming volts as the amplitude of the I&Q streams and converted into dBm in the frequency domain (the 1/N power adjustment out of the FFT is performed internally). The script plots a certain block of data at a time, specified on the command line as -B or --block. The start position in the file can be set by specifying -s or --start and defaults to 0 (the start of the file). By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time and frequency axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples. Finally, the size of the FFT to use for the PSD and spectrogram plots can be set independently with --psd-size and --spec-size, respectively. The spectrogram plot does not display by default and is turned on with -S or --enable-spec." - - parser = OptionParser(option_class=eng_option, conflict_handler="resolve", - usage=usage, description=description) - parser.add_option("-d", "--data-type", type="string", default="complex64", - help="Specify the data type (complex64, float32, (u)int32, (u)int16, (u)int8) [default=%default]") - parser.add_option("-B", "--block", type="int", default=8192, - help="Specify the block size [default=%default]") - parser.add_option("-s", "--start", type="int", default=0, - help="Specify where to start in the file [default=%default]") - parser.add_option("-R", "--sample-rate", type="eng_float", default=1.0, - help="Set the sampler rate of the data [default=%default]") - parser.add_option("", "--psd-size", type="int", default=1024, - help="Set the size of the PSD FFT [default=%default]") - parser.add_option("", "--spec-size", type="int", default=256, - help="Set the size of the spectrogram FFT [default=%default]") - parser.add_option("-S", "--enable-spec", action="store_true", default=False, - help="Turn on plotting the spectrogram [default=%default]") - - return parser - -def main(): - parser = setup_options() - (options, args) = parser.parse_args () - if len(args) != 1: - parser.print_help() - raise SystemExit, 1 - filename = args[0] - - dc = gr_plot_psd(options.data_type, filename, options) - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - pass - - - diff --git a/gr-utils/src/python/gr_plot_psd_c b/gr-utils/src/python/gr_plot_psd_c new file mode 100755 index 000000000..192f263ec --- /dev/null +++ b/gr-utils/src/python/gr_plot_psd_c @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from optparse import OptionParser +from gnuradio.plot_psd_base import plot_psd_base + +# This is a wrapper program for plot_psd_base specifically for complex data + +def main(): + parser = plot_psd_base.setup_options() + parser.remove_option("--data-type") + + (options, args) = parser.parse_args () + if len(args) != 1: + parser.print_help() + raise SystemExit, 1 + filename = args[0] + + dc = plot_psd_base("complex64", filename, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + + + diff --git a/gr-utils/src/python/gr_plot_psd_c.py b/gr-utils/src/python/gr_plot_psd_c.py deleted file mode 100755 index 1edc04c2b..000000000 --- a/gr-utils/src/python/gr_plot_psd_c.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2008 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -from optparse import OptionParser -import gr_plot_psd - -# This is a wrapper program for gr_plot_psd specifically for complex data - -def main(): - parser = gr_plot_psd.setup_options() - parser.remove_option("--data-type") - - (options, args) = parser.parse_args () - if len(args) != 1: - parser.print_help() - raise SystemExit, 1 - filename = args[0] - - dc = gr_plot_psd.gr_plot_psd("complex64", filename, options) - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - pass - - - diff --git a/gr-utils/src/python/gr_plot_psd_f b/gr-utils/src/python/gr_plot_psd_f new file mode 100755 index 000000000..fab312645 --- /dev/null +++ b/gr-utils/src/python/gr_plot_psd_f @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from optparse import OptionParser +from gnuradio.plot_psd_base import plot_psd_base + +# This is a wrapper program for gr_plot_psd specifically for floating point data + +def main(): + parser = plot_psd_base.setup_options() + parser.remove_option("--data-type") + + (options, args) = parser.parse_args () + if len(args) != 1: + parser.print_help() + raise SystemExit, 1 + filename = args[0] + + dc = plot_psd_base("float32", filename, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + + + diff --git a/gr-utils/src/python/gr_plot_psd_f.py b/gr-utils/src/python/gr_plot_psd_f.py deleted file mode 100755 index bad6788c3..000000000 --- a/gr-utils/src/python/gr_plot_psd_f.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2008 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -from optparse import OptionParser -import gr_plot_psd - -# This is a wrapper program for gr_plot_psd specifically for floating point data - -def main(): - parser = gr_plot_psd.setup_options() - parser.remove_option("--data-type") - - (options, args) = parser.parse_args () - if len(args) != 1: - parser.print_help() - raise SystemExit, 1 - filename = args[0] - - dc = gr_plot_psd.gr_plot_psd("float32", filename, options) - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - pass - - - diff --git a/gr-utils/src/python/gr_plot_qt b/gr-utils/src/python/gr_plot_qt new file mode 100755 index 000000000..937d0014b --- /dev/null +++ b/gr-utils/src/python/gr_plot_qt @@ -0,0 +1,729 @@ +#!/usr/bin/env python + +try: + import scipy + from scipy import fftpack +except ImportError: + print "Please install SciPy to run this script (http://www.scipy.org/)" + raise SystemExit, 1 + +try: + from matplotlib import mlab +except ImportError: + print "Please install Matplotlib to run this script (http://matplotlib.sourceforge.net)" + raise SystemExit, 1 + +try: + from PyQt4 import Qt, QtCore, QtGui +except ImportError: + print "Please install PyQt4 to run this script (http://www.riverbankcomputing.co.uk/software/pyqt/download)" + raise SystemExit, 1 + +try: + import PyQt4.Qwt5 as Qwt +except ImportError: + print "Please install PyQwt5 to run this script (http://pyqwt.sourceforge.net/)" + raise SystemExit, 1 + +try: + # FIXME: reenable this before committing + #from gnuradio.pyqt_plot import Ui_MainWindow + from gnuradio.pyqt_plot import Ui_MainWindow +except ImportError: + print "Could not import from pyqt_plot. Please build with \"pyuic4 pyqt_plot.ui -o pyqt_plot.py\"" + raise SystemExit, 1 + +import sys, os +from optparse import OptionParser +from gnuradio import eng_notation + + +class SpectrogramData(Qwt.QwtRasterData): + + def __init__(self, f, t): + Qwt.QwtArrayData.__init__(self, Qt.QRectF(0, 0, 0, 0)) + self.sp = scipy.array([[0], [0]]) + + def set_data(self, xfreq, ytime, data): + self.sp = data + self.freq = xfreq + self.time = ytime + boundingBox = Qt.QRectF(self.freq.min(), self.time.min(), + self.freq.max() - self.freq.min(), + self.time.max() - self.time.min()) + self.setBoundingRect(boundingBox) + + def rasterHint(self, rect): + return Qt.QSize(self.sp.shape[0], self.sp.shape[1]) + + def copy(self): + return self + + def range(self): + + return Qwt.QwtDoubleInterval(self.sp.min(), self.sp.max()) + + def value(self, x, y): + try: + f = int(self.freq.searchsorted(x)) + t = int(self.time.searchsorted(y)) + return self.sp[f][t-1] + except AttributeError: # if no file loaded yet + return 0 + + +class gr_plot_qt(QtGui.QMainWindow): + def __init__(self, qapp, filename, options, parent=None): + QtGui.QWidget.__init__(self, parent) + self.gui = Ui_MainWindow() + self.gui.setupUi(self) + + self.filename = None + self.block_length = options.block_length + self.start = options.start + self.sample_rate = options.sample_rate + self.psdfftsize = options.psd_size + self.specfftsize = options.spec_size + self.winfunc = scipy.blackman + self.sizeof_data = 8 + self.datatype = scipy.complex64 + self.pen_width = 1 + self.iq = list() + self.time = list() + + # Set up basic plot attributes + self.gui.timePlot.setAxisTitle(self.gui.timePlot.xBottom, "Time (sec)") + self.gui.timePlot.setAxisTitle(self.gui.timePlot.yLeft, "Amplitude (V)") + self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.xBottom, "Frequency (Hz)") + self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.yLeft, "Magnitude (dB)") + self.gui.specPlot.setAxisTitle(self.gui.specPlot.xBottom, "Frequency (Hz)") + self.gui.specPlot.setAxisTitle(self.gui.specPlot.yLeft, "Time (sec)") + + # Set up FFT size combo box + self.fftsizes = ["128", "256", "512", "1024", "2048", + "4096", "8192", "16384", "32768"] + self.gui.psdFFTComboBox.addItems(self.fftsizes) + self.gui.specFFTComboBox.addItems(self.fftsizes) + pos = self.gui.psdFFTComboBox.findText(Qt.QString("%1").arg(self.psdfftsize)) + self.gui.psdFFTComboBox.setCurrentIndex(pos) + pos = self.gui.specFFTComboBox.findText(Qt.QString("%1").arg(self.specfftsize)) + self.gui.specFFTComboBox.setCurrentIndex(pos) + + self.connect(self.gui.psdFFTComboBox, + Qt.SIGNAL("activated (const QString&)"), + self.psdFFTComboBoxEdit) + self.connect(self.gui.specFFTComboBox, + Qt.SIGNAL("activated (const QString&)"), + self.specFFTComboBoxEdit) + + # Set up color scheme box + self.color_modes = {"Black on White" : self.color_black_on_white, + "White on Black" : self.color_white_on_black, + "Blue on Black" : self.color_blue_on_black, + "Green on Black" : self.color_green_on_black} + self.gui.colorComboBox.addItems(self.color_modes.keys()) + pos = self.gui.colorComboBox.findText("Blue on Black") + self.gui.colorComboBox.setCurrentIndex(pos) + self.connect(self.gui.colorComboBox, + Qt.SIGNAL("activated (const QString&)"), + self.colorComboBoxEdit) + + + # Set up line style combo box + self.line_styles = {"None" : Qwt.QwtSymbol.NoSymbol, + "Circle" : Qwt.QwtSymbol.Ellipse, + "Diamond" : Qwt.QwtSymbol.Rect, + "Triangle" : Qwt.QwtSymbol.Triangle} + self.gui.lineStyleComboBox.addItems(self.line_styles.keys()) + pos = self.gui.lineStyleComboBox.findText("None") + self.gui.lineStyleComboBox.setCurrentIndex(pos) + self.connect(self.gui.lineStyleComboBox, + Qt.SIGNAL("activated (const QString&)"), + self.lineStyleComboBoxEdit) + + # Create zoom functionality for the plots + self.timeZoomer = Qwt.QwtPlotZoomer(self.gui.timePlot.xBottom, + self.gui.timePlot.yLeft, + Qwt.QwtPicker.PointSelection, + Qwt.QwtPicker.AlwaysOn, + self.gui.timePlot.canvas()) + + self.freqZoomer = Qwt.QwtPlotZoomer(self.gui.freqPlot.xBottom, + self.gui.freqPlot.yLeft, + Qwt.QwtPicker.PointSelection, + Qwt.QwtPicker.AlwaysOn, + self.gui.freqPlot.canvas()) + + self.specZoomer = Qwt.QwtPlotZoomer(self.gui.specPlot.xBottom, + self.gui.specPlot.yLeft, + Qwt.QwtPicker.PointSelection, + Qwt.QwtPicker.AlwaysOn, + self.gui.specPlot.canvas()) + + # Set up action when tab is changed + self.connect(self.gui.tabGroup, + Qt.SIGNAL("currentChanged (int)"), + self.tabChanged) + + # Add a legend to the Time plot + legend_real = Qwt.QwtLegend() + self.gui.timePlot.insertLegend(legend_real) + + # Set up slider + self.gui.plotHBar.setSingleStep(1) + self.gui.plotHBar.setPageStep(self.block_length) + self.gui.plotHBar.setMinimum(0) + self.gui.plotHBar.setMaximum(self.block_length) + self.connect(self.gui.plotHBar, + Qt.SIGNAL("valueChanged(int)"), + self.sliderMoved) + + # Connect Open action to Open Dialog box + self.connect(self.gui.action_open, + Qt.SIGNAL("activated()"), + self.open_file) + + # Connect Reload action to reload the file + self.connect(self.gui.action_reload, + Qt.SIGNAL("activated()"), + self.reload_file) + self.gui.action_reload.setShortcut(QtGui.QApplication.translate("MainWindow", "Ctrl+R", + None, QtGui.QApplication.UnicodeUTF8)) + + # Set up file position boxes to update current figure + self.connect(self.gui.filePosStartLineEdit, + Qt.SIGNAL("editingFinished()"), + self.file_position_changed) + self.connect(self.gui.filePosStopLineEdit, + Qt.SIGNAL("editingFinished()"), + self.file_position_changed) + self.connect(self.gui.filePosLengthLineEdit, + Qt.SIGNAL("editingFinished()"), + self.file_length_changed) + + self.connect(self.gui.fileTimeStartLineEdit, + Qt.SIGNAL("editingFinished()"), + self.file_time_changed) + self.connect(self.gui.fileTimeStopLineEdit, + Qt.SIGNAL("editingFinished()"), + self.file_time_changed) + self.connect(self.gui.fileTimeLengthLineEdit, + Qt.SIGNAL("editingFinished()"), + self.file_time_length_changed) + + stylestr = str(self.gui.lineStyleComboBox.currentText().toAscii()) + style = self.line_styles[stylestr] + + self.rcurve = Qwt.QwtPlotCurve("Real") + self.icurve = Qwt.QwtPlotCurve("Imaginary") + self.rsym = Qwt.QwtSymbol() + self.rsym.setStyle(style) + self.rsym.setSize(10) + self.isym = Qwt.QwtSymbol() + self.isym.setStyle(style) + self.isym.setSize(10) + self.rcurve.setSymbol(self.rsym) + self.icurve.setSymbol(self.isym) + + + self.icurve.attach(self.gui.timePlot) + self.rcurve.attach(self.gui.timePlot) + + self.psdcurve = Qwt.QwtPlotCurve("PSD") + self.psdcurve.attach(self.gui.freqPlot) + + # Set up specTab plot as a spectrogram + self.specdata = SpectrogramData(range(0, 10), range(0, 10)) + + colorMap = Qwt.QwtLinearColorMap(Qt.Qt.darkCyan, Qt.Qt.red) + colorMap.addColorStop(0.1, Qt.Qt.cyan) + colorMap.addColorStop(0.6, Qt.Qt.green) + colorMap.addColorStop(0.95, Qt.Qt.yellow) + + self.spec = Qwt.QwtPlotSpectrogram() + self.spec.setColorMap(colorMap) + self.spec.attach(self.gui.specPlot) + self.spec.setDisplayMode(Qwt.QwtPlotSpectrogram.ImageMode, True) + self.spec.setData(self.specdata) + + self.rightAxis = self.gui.specPlot.axisWidget(Qwt.QwtPlot.yRight) + self.rightAxis.setTitle("Magnitude (dBm)") + self.rightAxis.setColorBarEnabled(True) + self.rightAxis.setColorMap(self.spec.data().range(), + self.spec.colorMap()) + self.gui.specPlot.enableAxis(Qwt.QwtPlot.yRight) + + # Set up initial color scheme + self.color_modes["Blue on Black"]() + + # When line width spin box changes, update the pen size + self.connect(self.gui.lineWidthSpinBox, + Qt.SIGNAL("valueChanged(int)"), + self.change_pen_width) + self.gui.lineWidthSpinBox.setRange(1, 10) + + # When style size spin box changes, update the pen size + self.connect(self.gui.styleSizeSpinBox, + Qt.SIGNAL("valueChanged(int)"), + self.change_style_size) + self.gui.styleSizeSpinBox.setRange(1, 20) + self.gui.styleSizeSpinBox.setValue(5) + + + # Connect a signal for when the sample rate changes + self.set_sample_rate(self.sample_rate) + self.connect(self.gui.sampleRateLineEdit, + Qt.SIGNAL("editingFinished()"), + self.sample_rate_changed) + + if(filename is not None): + self.initialize(filename) + + self.show() + + def open_file(self): + filename = Qt.QFileDialog.getOpenFileName(self, "Open", ".") + if(filename != ""): + #print filename + self.initialize(filename) + + def reload_file(self): + if(self.filename): + self.initialize(self.filename) + + def initialize(self, filename): + self.filename = filename + self.hfile = open(filename, "r") + + self.setWindowTitle(("GNU Radio File Plot Utility: %s" % filename)) + + self.gui.filePosStartLineEdit.setText("0") + self.gui.filePosStopLineEdit.setText("0") + self.gui.fileTimeStartLineEdit.setText("0") + self.gui.fileTimeStopLineEdit.setText("0") + + self.cur_start = 0 + self.cur_stop = self.block_length + + self.init_data_input() + self.get_data(self.cur_start, self.cur_stop) + self.get_psd() + self.get_specgram() + self.gui.plotHBar.setSliderPosition(0) + self.gui.plotHBar.setMaximum(self.signal_size-self.block_length) + + + self.update_time_curves() + self.update_psd_curves() + self.update_specgram_curves() + + def init_data_input(self): + self.hfile.seek(0, os.SEEK_END) + self.signal_size = self.hfile.tell()/self.sizeof_data + #print "Sizeof File: ", self.signal_size + self.hfile.seek(0, os.SEEK_SET) + + def get_data(self, start, end): + if(end > start): + self.hfile.seek(start*self.sizeof_data, os.SEEK_SET) + self.position = start + try: + iq = scipy.fromfile(self.hfile, dtype=self.datatype, + count=end-start) + + if(len(iq) < (end-start)): + end = start + len(iq) + self.gui.filePosLengthLineEdit.setText(Qt.QString("%1").arg(len(iq))) + self.file_length_changed() + + tstep = 1.0 / self.sample_rate + self.iq = iq + self.time = [tstep*(self.position + i) for i in xrange(len(self.iq))] + + self.set_file_pos_box(start, end) + except MemoryError: + pass + else: + # Do we want to do anything about this? + pass + + def get_psd(self): + winpoints = self.winfunc(self.psdfftsize) + iq_psd, freq = mlab.psd(self.iq, Fs=self.sample_rate, + NFFT=self.psdfftsize, + noverlap=self.psdfftsize/4.0, + window=winpoints, + scale_by_freq=False) + + self.iq_psd = 10.0*scipy.log10(abs(fftpack.fftshift(iq_psd))) + self.freq = freq - self.sample_rate/2.0 + + def get_specgram(self): + winpoints = self.winfunc(self.specfftsize) + iq_spec, f, t = mlab.specgram(self.iq, Fs=self.sample_rate, + NFFT=self.specfftsize, + noverlap=self.specfftsize/4.0, + window=winpoints, + scale_by_freq=False) + + self.iq_spec = 10.0*scipy.log10(abs(iq_spec)) + self.spec_f = f + self.spec_t = t + + def psdFFTComboBoxEdit(self, fftSize): + self.psdfftsize = fftSize.toInt()[0] + self.get_psd() + self.update_psd_curves() + + def specFFTComboBoxEdit(self, fftSize): + self.specfftsize = fftSize.toInt()[0] + self.get_specgram() + self.update_specgram_curves() + + def colorComboBoxEdit(self, colorSelection): + colorstr = str(colorSelection.toAscii()) + color_func = self.color_modes[colorstr] + color_func() + + def lineStyleComboBoxEdit(self, styleSelection): + stylestr = str(styleSelection.toAscii()) + self.rsym.setStyle(self.line_styles[stylestr]) + self.isym.setStyle(self.line_styles[stylestr]) + self.rcurve.setSymbol(self.rsym) + self.icurve.setSymbol(self.isym) + self.gui.timePlot.replot() + + def sliderMoved(self, value): + pos_start = value + pos_end = value + self.gui.plotHBar.pageStep() + + self.get_data(pos_start, pos_end) + self.get_psd() + self.get_specgram() + self.update_time_curves() + self.update_psd_curves() + self.update_specgram_curves() + + def set_sample_rate(self, sr): + self.sample_rate = sr + srstr = eng_notation.num_to_str(self.sample_rate) + self.gui.sampleRateLineEdit.setText(Qt.QString("%1").arg(srstr)) + + def sample_rate_changed(self): + srstr = self.gui.sampleRateLineEdit.text().toAscii() + self.sample_rate = eng_notation.str_to_num(srstr) + self.set_file_pos_box(self.cur_start, self.cur_stop) + self.get_data(self.cur_start, self.cur_stop) + self.get_psd() + self.get_specgram() + self.update_time_curves() + self.update_psd_curves() + self.update_specgram_curves() + + def set_file_pos_box(self, start, end): + tstart = start / self.sample_rate + tend = end / self.sample_rate + + self.gui.filePosStartLineEdit.setText(Qt.QString("%1").arg(start)) + self.gui.filePosStopLineEdit.setText(Qt.QString("%1").arg(end)) + self.gui.filePosLengthLineEdit.setText(Qt.QString("%1").arg(end-start)) + + self.gui.fileTimeStartLineEdit.setText(Qt.QString("%1").arg(tstart)) + self.gui.fileTimeStopLineEdit.setText(Qt.QString("%1").arg(tend)) + self.gui.fileTimeLengthLineEdit.setText(Qt.QString("%1").arg(tend-tstart)) + + def file_position_changed(self): + start = self.gui.filePosStartLineEdit.text().toInt() + end = self.gui.filePosStopLineEdit.text().toInt() + if((start[1] == True) and (end[1] == True)): + self.cur_start = start[0] + self.cur_stop = end[0] + + tstart = self.cur_start / self.sample_rate + tend = self.cur_stop / self.sample_rate + self.gui.fileTimeStartLineEdit.setText(Qt.QString("%1").arg(tstart)) + self.gui.fileTimeStopLineEdit.setText(Qt.QString("%1").arg(tend)) + + self.get_data(self.cur_start, self.cur_stop) + + self.update_time_curves() + self.update_psd_curves() + self.update_specgram_curves() + + # If there's a non-digit character, reset box + else: + try: + self.set_file_pos_box(self.cur_start, self.cur_stop) + except AttributeError: + pass + + + def file_time_changed(self): + tstart = self.gui.fileTimeStartLineEdit.text().toDouble() + tstop = self.gui.fileTimeStopLineEdit.text().toDouble() + if((tstart[1] == True) and (tstop[1] == True)): + self.cur_start = int(tstart[0] * self.sample_rate) + self.cur_stop = int(tstop[0] * self.sample_rate) + self.get_data(self.cur_start, self.cur_stop) + + self.gui.filePosStartLineEdit.setText(Qt.QString("%1").arg(self.cur_start)) + self.gui.filePosStopLineEdit.setText(Qt.QString("%1").arg(self.cur_stop)) + + self.update_time_curves() + self.update_psd_curves() + self.update_specgram_curves() + # If there's a non-digit character, reset box + else: + self.set_file_pos_box(self.cur_start, self.cur_stop) + + def file_length_changed(self): + start = self.gui.filePosStartLineEdit.text().toInt() + length = self.gui.filePosLengthLineEdit.text().toInt() + + if((start[1] == True) and (length[1] == True)): + self.cur_start = start[0] + self.block_length = length[0] + self.cur_stop = self.cur_start + self.block_length + + tstart = self.cur_start / self.sample_rate + tend = self.cur_stop / self.sample_rate + tlen = self.block_length / self.sample_rate + self.gui.fileTimeStartLineEdit.setText(Qt.QString("%1").arg(tstart)) + self.gui.fileTimeStopLineEdit.setText(Qt.QString("%1").arg(tend)) + self.gui.fileTimeLengthLineEdit.setText(Qt.QString("%1").arg(tlen)) + + self.gui.plotHBar.setPageStep(self.block_length) + + self.get_data(self.cur_start, self.cur_stop) + self.get_psd() + self.get_specgram() + + self.update_time_curves() + self.update_psd_curves() + self.update_specgram_curves() + # If there's a non-digit character, reset box + else: + self.set_file_pos_box(self.cur_start, self.cur_stop) + + def file_time_length_changed(self): + tstart = self.gui.fileTimeStartLineEdit.text().toDouble() + tlength = self.gui.fileTimeLengthLineEdit.text().toDouble() + if((tstart[1] == True) and (tlength[1] == True)): + self.cur_start = int(tstart[0] * self.sample_rate) + self.block_length = int(tlength[0] * self.sample_rate) + self.cur_stop = self.cur_start + self.block_length + + tstart = self.cur_start / self.sample_rate + tend = self.cur_stop / self.sample_rate + tlen = self.block_length / self.sample_rate + self.gui.fileTimeStartLineEdit.setText(Qt.QString("%1").arg(tstart)) + self.gui.fileTimeStopLineEdit.setText(Qt.QString("%1").arg(tend)) + self.gui.fileTimeLengthLineEdit.setText(Qt.QString("%1").arg(tlen)) + + self.get_data(self.cur_start, self.cur_stop) + self.get_psd() + self.get_specgram() + + self.update_time_curves() + self.update_psd_curves() + self.update_specgram_curves() + # If there's a non-digit character, reset box + else: + self.set_file_pos_box(self.cur_start, self.cur_stop) + + + def update_time_curves(self): + self.icurve.setData(self.time, self.iq.imag) + self.rcurve.setData(self.time, self.iq.real) + + # Reset the x-axis to the new time scale + iqmax = 1.5 * max(max(self.iq.real), max(self.iq.imag)) + iqmin = 1.5 * min(min(self.iq.real), min(self.iq.imag)) + self.gui.timePlot.setAxisScale(self.gui.timePlot.xBottom, + min(self.time), + max(self.time)) + self.gui.timePlot.setAxisScale(self.gui.timePlot.yLeft, + iqmin, + iqmax) + + # Set the zoomer base to unzoom to the new axis + self.timeZoomer.setZoomBase() + + self.gui.timePlot.replot() + + def update_psd_curves(self): + self.psdcurve.setData(self.freq, self.iq_psd) + + self.gui.freqPlot.setAxisScale(self.gui.freqPlot.xBottom, + min(self.freq), + max(self.freq)) + + # Set the zoomer base to unzoom to the new axis + self.freqZoomer.setZoomBase() + + self.gui.freqPlot.replot() + + def update_specgram_curves(self): + # We don't have to reset the data for the speccurve here + # since this is taken care of in the SpectrogramData class + self.specdata.set_data(self.spec_f, self.spec_t, self.iq_spec) + + # Set the color map based on the new data + self.rightAxis.setColorMap(self.spec.data().range(), + self.spec.colorMap()) + + # Set the new axis base; include right axis for the intenisty color bar + self.gui.specPlot.setAxisScale(self.gui.specPlot.xBottom, + min(self.spec_f), + max(self.spec_f)) + self.gui.specPlot.setAxisScale(self.gui.specPlot.yLeft, + min(self.spec_t), + max(self.spec_t)) + self.gui.specPlot.setAxisScale(self.gui.specPlot.yRight, + self.iq_spec.min(), + self.iq_spec.max()) + + # Set the zoomer base to unzoom to the new axis + self.specZoomer.setZoomBase() + + self.gui.specPlot.replot() + + def tabChanged(self, index): + self.gui.timePlot.replot() + self.gui.freqPlot.replot() + self.gui.specPlot.replot() + + def change_pen_width(self, width): + self.pen_width = width + colormode = str(self.gui.colorComboBox.currentText().toAscii()) + color_func = self.color_modes[colormode]() + + def change_style_size(self, size): + self.rsym.setSize(size) + self.isym.setSize(size) + self.rcurve.setSymbol(self.rsym) + self.icurve.setSymbol(self.isym) + self.gui.timePlot.replot() + + def color_black_on_white(self): + blue = QtGui.qRgb(0x00, 0x00, 0xFF) + red = QtGui.qRgb(0xFF, 0x00, 0x00) + + blackPen = Qt.QPen(Qt.QBrush(Qt.QColor("black")), self.pen_width) + bluePen = Qt.QPen(Qt.QBrush(Qt.QColor(blue)), self.pen_width) + redPen = Qt.QPen(Qt.QBrush(Qt.QColor(red)), self.pen_width) + + self.gui.timePlot.setCanvasBackground(Qt.QColor("white")) + self.gui.freqPlot.setCanvasBackground(Qt.QColor("white")) + self.timeZoomer.setTrackerPen(blackPen) + self.timeZoomer.setRubberBandPen(blackPen) + self.freqZoomer.setTrackerPen(blackPen) + self.freqZoomer.setRubberBandPen(blackPen) + self.psdcurve.setPen(bluePen) + self.rcurve.setPen(bluePen) + self.icurve.setPen(redPen) + + self.rsym.setPen(bluePen) + self.isym.setPen(redPen) + + self.gui.timePlot.replot() + self.gui.freqPlot.replot() + + def color_white_on_black(self): + white = QtGui.qRgb(0xFF, 0xFF, 0xFF) + red = QtGui.qRgb(0xFF, 0x00, 0x00) + + whiteBrush = Qt.QBrush(Qt.QColor("white")) + whiteBrush = Qt.QBrush(Qt.QColor(white)) + redBrush = Qt.QBrush(Qt.QColor(red)) + + self.gui.timePlot.setCanvasBackground(QtGui.QColor("black")) + self.gui.freqPlot.setCanvasBackground(QtGui.QColor("black")) + self.timeZoomer.setTrackerPen(Qt.QPen(whiteBrush, self.pen_width)) + self.timeZoomer.setRubberBandPen(Qt.QPen(whiteBrush, self.pen_width)) + self.freqZoomer.setTrackerPen(Qt.QPen(whiteBrush, self.pen_width)) + self.freqZoomer.setRubberBandPen(Qt.QPen(whiteBrush, self.pen_width)) + self.psdcurve.setPen(Qt.QPen(whiteBrush, self.pen_width)) + self.rcurve.setPen(Qt.QPen(whiteBrush, self.pen_width)) + self.icurve.setPen(Qt.QPen(redBrush, self.pen_width)) + + self.gui.timePlot.replot() + self.gui.freqPlot.replot() + + + def color_green_on_black(self): + green = QtGui.qRgb(0x00, 0xFF, 0x00) + red = QtGui.qRgb(0xFF, 0x00, 0x50) + + whiteBrush = Qt.QBrush(Qt.QColor("white")) + greenBrush = Qt.QBrush(Qt.QColor(green)) + redBrush = Qt.QBrush(Qt.QColor(red)) + + self.gui.timePlot.setCanvasBackground(QtGui.QColor("black")) + self.gui.freqPlot.setCanvasBackground(QtGui.QColor("black")) + self.timeZoomer.setTrackerPen(Qt.QPen(whiteBrush, self.pen_width)) + self.timeZoomer.setRubberBandPen(Qt.QPen(whiteBrush, self.pen_width)) + self.freqZoomer.setTrackerPen(Qt.QPen(whiteBrush, self.pen_width)) + self.freqZoomer.setRubberBandPen(Qt.QPen(whiteBrush, self.pen_width)) + self.psdcurve.setPen(Qt.QPen(greenBrush, self.pen_width)) + self.rcurve.setPen(Qt.QPen(greenBrush, self.pen_width)) + self.icurve.setPen(Qt.QPen(redBrush, self.pen_width)) + + self.gui.timePlot.replot() + self.gui.freqPlot.replot() + + def color_blue_on_black(self): + blue = QtGui.qRgb(0x00, 0x00, 0xFF) + red = QtGui.qRgb(0xFF, 0x00, 0x00) + + whiteBrush = Qt.QBrush(Qt.QColor("white")) + blueBrush = Qt.QBrush(Qt.QColor(blue)) + redBrush = Qt.QBrush(Qt.QColor(red)) + + self.gui.timePlot.setCanvasBackground(QtGui.QColor("black")) + self.gui.freqPlot.setCanvasBackground(QtGui.QColor("black")) + self.timeZoomer.setTrackerPen(Qt.QPen(whiteBrush, self.pen_width)) + self.timeZoomer.setRubberBandPen(Qt.QPen(whiteBrush, self.pen_width)) + self.freqZoomer.setTrackerPen(Qt.QPen(whiteBrush, self.pen_width)) + self.freqZoomer.setRubberBandPen(Qt.QPen(whiteBrush, self.pen_width)) + self.psdcurve.setPen(Qt.QPen(blueBrush, self.pen_width)) + self.rcurve.setPen(Qt.QPen(blueBrush, self.pen_width)) + self.icurve.setPen(Qt.QPen(redBrush, self.pen_width)) + + self.gui.timePlot.replot() + self.gui.freqPlot.replot() + +def setup_options(): + usage="%prog: [options] (input_filename)" + description = "" + + parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) + parser.add_option("-B", "--block-length", type="int", default=8192, + help="Specify the block size [default=%default]") + parser.add_option("-s", "--start", type="int", default=0, + help="Specify where to start in the file [default=%default]") + parser.add_option("-R", "--sample-rate", type="float", default=1.0, + help="Set the sampler rate of the data [default=%default]") + parser.add_option("", "--psd-size", type="int", default=2048, + help="Set the size of the PSD FFT [default=%default]") + parser.add_option("", "--spec-size", type="int", default=2048, + help="Set the size of the spectrogram FFT [default=%default]") + + return parser + +def main(args): + parser = setup_options() + (options, args) = parser.parse_args () + + if(len(args) == 1): + filename = args[0] + else: + filename = None + + app = Qt.QApplication(args) + gplt = gr_plot_qt(app, filename, options) + app.exec_() + +if __name__ == '__main__': + main(sys.argv) + diff --git a/gr-utils/src/python/gr_plot_qt.py b/gr-utils/src/python/gr_plot_qt.py deleted file mode 100755 index f3dc472f5..000000000 --- a/gr-utils/src/python/gr_plot_qt.py +++ /dev/null @@ -1,729 +0,0 @@ -#!/usr/bin/env python - -try: - import scipy - from scipy import fftpack -except ImportError: - print "Please install SciPy to run this script (http://www.scipy.org/)" - raise SystemExit, 1 - -try: - from matplotlib import mlab -except ImportError: - print "Please install Matplotlib to run this script (http://matplotlib.sourceforge.net)" - raise SystemExit, 1 - -try: - from PyQt4 import Qt, QtCore, QtGui -except ImportError: - print "Please install PyQt4 to run this script (http://www.riverbankcomputing.co.uk/software/pyqt/download)" - raise SystemExit, 1 - -try: - import PyQt4.Qwt5 as Qwt -except ImportError: - print "Please install PyQwt5 to run this script (http://pyqwt.sourceforge.net/)" - raise SystemExit, 1 - -try: - # FIXME: reenable this before committing - #from gnuradio.pyqt_plot import Ui_MainWindow - from pyqt_plot import Ui_MainWindow -except ImportError: - print "Could not import from pyqt_plot. Please build with \"pyuic4 pyqt_plot.ui -o pyqt_plot.py\"" - raise SystemExit, 1 - -import sys, os -from optparse import OptionParser -from gnuradio import eng_notation - - -class SpectrogramData(Qwt.QwtRasterData): - - def __init__(self, f, t): - Qwt.QwtArrayData.__init__(self, Qt.QRectF(0, 0, 0, 0)) - self.sp = scipy.array([[0], [0]]) - - def set_data(self, xfreq, ytime, data): - self.sp = data - self.freq = xfreq - self.time = ytime - boundingBox = Qt.QRectF(self.freq.min(), self.time.min(), - self.freq.max() - self.freq.min(), - self.time.max() - self.time.min()) - self.setBoundingRect(boundingBox) - - def rasterHint(self, rect): - return Qt.QSize(self.sp.shape[0], self.sp.shape[1]) - - def copy(self): - return self - - def range(self): - - return Qwt.QwtDoubleInterval(self.sp.min(), self.sp.max()) - - def value(self, x, y): - try: - f = int(self.freq.searchsorted(x)) - t = int(self.time.searchsorted(y)) - return self.sp[f][t-1] - except AttributeError: # if no file loaded yet - return 0 - - -class gr_plot_qt(QtGui.QMainWindow): - def __init__(self, qapp, filename, options, parent=None): - QtGui.QWidget.__init__(self, parent) - self.gui = Ui_MainWindow() - self.gui.setupUi(self) - - self.filename = None - self.block_length = options.block_length - self.start = options.start - self.sample_rate = options.sample_rate - self.psdfftsize = options.psd_size - self.specfftsize = options.spec_size - self.winfunc = scipy.blackman - self.sizeof_data = 8 - self.datatype = scipy.complex64 - self.pen_width = 1 - self.iq = list() - self.time = list() - - # Set up basic plot attributes - self.gui.timePlot.setAxisTitle(self.gui.timePlot.xBottom, "Time (sec)") - self.gui.timePlot.setAxisTitle(self.gui.timePlot.yLeft, "Amplitude (V)") - self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.xBottom, "Frequency (Hz)") - self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.yLeft, "Magnitude (dB)") - self.gui.specPlot.setAxisTitle(self.gui.specPlot.xBottom, "Frequency (Hz)") - self.gui.specPlot.setAxisTitle(self.gui.specPlot.yLeft, "Time (sec)") - - # Set up FFT size combo box - self.fftsizes = ["128", "256", "512", "1024", "2048", - "4096", "8192", "16384", "32768"] - self.gui.psdFFTComboBox.addItems(self.fftsizes) - self.gui.specFFTComboBox.addItems(self.fftsizes) - pos = self.gui.psdFFTComboBox.findText(Qt.QString("%1").arg(self.psdfftsize)) - self.gui.psdFFTComboBox.setCurrentIndex(pos) - pos = self.gui.specFFTComboBox.findText(Qt.QString("%1").arg(self.specfftsize)) - self.gui.specFFTComboBox.setCurrentIndex(pos) - - self.connect(self.gui.psdFFTComboBox, - Qt.SIGNAL("activated (const QString&)"), - self.psdFFTComboBoxEdit) - self.connect(self.gui.specFFTComboBox, - Qt.SIGNAL("activated (const QString&)"), - self.specFFTComboBoxEdit) - - # Set up color scheme box - self.color_modes = {"Black on White" : self.color_black_on_white, - "White on Black" : self.color_white_on_black, - "Blue on Black" : self.color_blue_on_black, - "Green on Black" : self.color_green_on_black} - self.gui.colorComboBox.addItems(self.color_modes.keys()) - pos = self.gui.colorComboBox.findText("Blue on Black") - self.gui.colorComboBox.setCurrentIndex(pos) - self.connect(self.gui.colorComboBox, - Qt.SIGNAL("activated (const QString&)"), - self.colorComboBoxEdit) - - - # Set up line style combo box - self.line_styles = {"None" : Qwt.QwtSymbol.NoSymbol, - "Circle" : Qwt.QwtSymbol.Ellipse, - "Diamond" : Qwt.QwtSymbol.Rect, - "Triangle" : Qwt.QwtSymbol.Triangle} - self.gui.lineStyleComboBox.addItems(self.line_styles.keys()) - pos = self.gui.lineStyleComboBox.findText("None") - self.gui.lineStyleComboBox.setCurrentIndex(pos) - self.connect(self.gui.lineStyleComboBox, - Qt.SIGNAL("activated (const QString&)"), - self.lineStyleComboBoxEdit) - - # Create zoom functionality for the plots - self.timeZoomer = Qwt.QwtPlotZoomer(self.gui.timePlot.xBottom, - self.gui.timePlot.yLeft, - Qwt.QwtPicker.PointSelection, - Qwt.QwtPicker.AlwaysOn, - self.gui.timePlot.canvas()) - - self.freqZoomer = Qwt.QwtPlotZoomer(self.gui.freqPlot.xBottom, - self.gui.freqPlot.yLeft, - Qwt.QwtPicker.PointSelection, - Qwt.QwtPicker.AlwaysOn, - self.gui.freqPlot.canvas()) - - self.specZoomer = Qwt.QwtPlotZoomer(self.gui.specPlot.xBottom, - self.gui.specPlot.yLeft, - Qwt.QwtPicker.PointSelection, - Qwt.QwtPicker.AlwaysOn, - self.gui.specPlot.canvas()) - - # Set up action when tab is changed - self.connect(self.gui.tabGroup, - Qt.SIGNAL("currentChanged (int)"), - self.tabChanged) - - # Add a legend to the Time plot - legend_real = Qwt.QwtLegend() - self.gui.timePlot.insertLegend(legend_real) - - # Set up slider - self.gui.plotHBar.setSingleStep(1) - self.gui.plotHBar.setPageStep(self.block_length) - self.gui.plotHBar.setMinimum(0) - self.gui.plotHBar.setMaximum(self.block_length) - self.connect(self.gui.plotHBar, - Qt.SIGNAL("valueChanged(int)"), - self.sliderMoved) - - # Connect Open action to Open Dialog box - self.connect(self.gui.action_open, - Qt.SIGNAL("activated()"), - self.open_file) - - # Connect Reload action to reload the file - self.connect(self.gui.action_reload, - Qt.SIGNAL("activated()"), - self.reload_file) - self.gui.action_reload.setShortcut(QtGui.QApplication.translate("MainWindow", "Ctrl+R", - None, QtGui.QApplication.UnicodeUTF8)) - - # Set up file position boxes to update current figure - self.connect(self.gui.filePosStartLineEdit, - Qt.SIGNAL("editingFinished()"), - self.file_position_changed) - self.connect(self.gui.filePosStopLineEdit, - Qt.SIGNAL("editingFinished()"), - self.file_position_changed) - self.connect(self.gui.filePosLengthLineEdit, - Qt.SIGNAL("editingFinished()"), - self.file_length_changed) - - self.connect(self.gui.fileTimeStartLineEdit, - Qt.SIGNAL("editingFinished()"), - self.file_time_changed) - self.connect(self.gui.fileTimeStopLineEdit, - Qt.SIGNAL("editingFinished()"), - self.file_time_changed) - self.connect(self.gui.fileTimeLengthLineEdit, - Qt.SIGNAL("editingFinished()"), - self.file_time_length_changed) - - stylestr = str(self.gui.lineStyleComboBox.currentText().toAscii()) - style = self.line_styles[stylestr] - - self.rcurve = Qwt.QwtPlotCurve("Real") - self.icurve = Qwt.QwtPlotCurve("Imaginary") - self.rsym = Qwt.QwtSymbol() - self.rsym.setStyle(style) - self.rsym.setSize(10) - self.isym = Qwt.QwtSymbol() - self.isym.setStyle(style) - self.isym.setSize(10) - self.rcurve.setSymbol(self.rsym) - self.icurve.setSymbol(self.isym) - - - self.icurve.attach(self.gui.timePlot) - self.rcurve.attach(self.gui.timePlot) - - self.psdcurve = Qwt.QwtPlotCurve("PSD") - self.psdcurve.attach(self.gui.freqPlot) - - # Set up specTab plot as a spectrogram - self.specdata = SpectrogramData(range(0, 10), range(0, 10)) - - colorMap = Qwt.QwtLinearColorMap(Qt.Qt.darkCyan, Qt.Qt.red) - colorMap.addColorStop(0.1, Qt.Qt.cyan) - colorMap.addColorStop(0.6, Qt.Qt.green) - colorMap.addColorStop(0.95, Qt.Qt.yellow) - - self.spec = Qwt.QwtPlotSpectrogram() - self.spec.setColorMap(colorMap) - self.spec.attach(self.gui.specPlot) - self.spec.setDisplayMode(Qwt.QwtPlotSpectrogram.ImageMode, True) - self.spec.setData(self.specdata) - - self.rightAxis = self.gui.specPlot.axisWidget(Qwt.QwtPlot.yRight) - self.rightAxis.setTitle("Magnitude (dBm)") - self.rightAxis.setColorBarEnabled(True) - self.rightAxis.setColorMap(self.spec.data().range(), - self.spec.colorMap()) - self.gui.specPlot.enableAxis(Qwt.QwtPlot.yRight) - - # Set up initial color scheme - self.color_modes["Blue on Black"]() - - # When line width spin box changes, update the pen size - self.connect(self.gui.lineWidthSpinBox, - Qt.SIGNAL("valueChanged(int)"), - self.change_pen_width) - self.gui.lineWidthSpinBox.setRange(1, 10) - - # When style size spin box changes, update the pen size - self.connect(self.gui.styleSizeSpinBox, - Qt.SIGNAL("valueChanged(int)"), - self.change_style_size) - self.gui.styleSizeSpinBox.setRange(1, 20) - self.gui.styleSizeSpinBox.setValue(5) - - - # Connect a signal for when the sample rate changes - self.set_sample_rate(self.sample_rate) - self.connect(self.gui.sampleRateLineEdit, - Qt.SIGNAL("editingFinished()"), - self.sample_rate_changed) - - if(filename is not None): - self.initialize(filename) - - self.show() - - def open_file(self): - filename = Qt.QFileDialog.getOpenFileName(self, "Open", ".") - if(filename != ""): - #print filename - self.initialize(filename) - - def reload_file(self): - if(self.filename): - self.initialize(self.filename) - - def initialize(self, filename): - self.filename = filename - self.hfile = open(filename, "r") - - self.setWindowTitle(("GNU Radio File Plot Utility: %s" % filename)) - - self.gui.filePosStartLineEdit.setText("0") - self.gui.filePosStopLineEdit.setText("0") - self.gui.fileTimeStartLineEdit.setText("0") - self.gui.fileTimeStopLineEdit.setText("0") - - self.cur_start = 0 - self.cur_stop = self.block_length - - self.init_data_input() - self.get_data(self.cur_start, self.cur_stop) - self.get_psd() - self.get_specgram() - self.gui.plotHBar.setSliderPosition(0) - self.gui.plotHBar.setMaximum(self.signal_size-self.block_length) - - - self.update_time_curves() - self.update_psd_curves() - self.update_specgram_curves() - - def init_data_input(self): - self.hfile.seek(0, os.SEEK_END) - self.signal_size = self.hfile.tell()/self.sizeof_data - #print "Sizeof File: ", self.signal_size - self.hfile.seek(0, os.SEEK_SET) - - def get_data(self, start, end): - if(end > start): - self.hfile.seek(start*self.sizeof_data, os.SEEK_SET) - self.position = start - try: - iq = scipy.fromfile(self.hfile, dtype=self.datatype, - count=end-start) - - if(len(iq) < (end-start)): - end = start + len(iq) - self.gui.filePosLengthLineEdit.setText(Qt.QString("%1").arg(len(iq))) - self.file_length_changed() - - tstep = 1.0 / self.sample_rate - self.iq = iq - self.time = [tstep*(self.position + i) for i in xrange(len(self.iq))] - - self.set_file_pos_box(start, end) - except MemoryError: - pass - else: - # Do we want to do anything about this? - pass - - def get_psd(self): - winpoints = self.winfunc(self.psdfftsize) - iq_psd, freq = mlab.psd(self.iq, Fs=self.sample_rate, - NFFT=self.psdfftsize, - noverlap=self.psdfftsize/4.0, - window=winpoints, - scale_by_freq=False) - - self.iq_psd = 10.0*scipy.log10(abs(fftpack.fftshift(iq_psd))) - self.freq = freq - self.sample_rate/2.0 - - def get_specgram(self): - winpoints = self.winfunc(self.specfftsize) - iq_spec, f, t = mlab.specgram(self.iq, Fs=self.sample_rate, - NFFT=self.specfftsize, - noverlap=self.specfftsize/4.0, - window=winpoints, - scale_by_freq=False) - - self.iq_spec = 10.0*scipy.log10(abs(iq_spec)) - self.spec_f = f - self.spec_t = t - - def psdFFTComboBoxEdit(self, fftSize): - self.psdfftsize = fftSize.toInt()[0] - self.get_psd() - self.update_psd_curves() - - def specFFTComboBoxEdit(self, fftSize): - self.specfftsize = fftSize.toInt()[0] - self.get_specgram() - self.update_specgram_curves() - - def colorComboBoxEdit(self, colorSelection): - colorstr = str(colorSelection.toAscii()) - color_func = self.color_modes[colorstr] - color_func() - - def lineStyleComboBoxEdit(self, styleSelection): - stylestr = str(styleSelection.toAscii()) - self.rsym.setStyle(self.line_styles[stylestr]) - self.isym.setStyle(self.line_styles[stylestr]) - self.rcurve.setSymbol(self.rsym) - self.icurve.setSymbol(self.isym) - self.gui.timePlot.replot() - - def sliderMoved(self, value): - pos_start = value - pos_end = value + self.gui.plotHBar.pageStep() - - self.get_data(pos_start, pos_end) - self.get_psd() - self.get_specgram() - self.update_time_curves() - self.update_psd_curves() - self.update_specgram_curves() - - def set_sample_rate(self, sr): - self.sample_rate = sr - srstr = eng_notation.num_to_str(self.sample_rate) - self.gui.sampleRateLineEdit.setText(Qt.QString("%1").arg(srstr)) - - def sample_rate_changed(self): - srstr = self.gui.sampleRateLineEdit.text().toAscii() - self.sample_rate = eng_notation.str_to_num(srstr) - self.set_file_pos_box(self.cur_start, self.cur_stop) - self.get_data(self.cur_start, self.cur_stop) - self.get_psd() - self.get_specgram() - self.update_time_curves() - self.update_psd_curves() - self.update_specgram_curves() - - def set_file_pos_box(self, start, end): - tstart = start / self.sample_rate - tend = end / self.sample_rate - - self.gui.filePosStartLineEdit.setText(Qt.QString("%1").arg(start)) - self.gui.filePosStopLineEdit.setText(Qt.QString("%1").arg(end)) - self.gui.filePosLengthLineEdit.setText(Qt.QString("%1").arg(end-start)) - - self.gui.fileTimeStartLineEdit.setText(Qt.QString("%1").arg(tstart)) - self.gui.fileTimeStopLineEdit.setText(Qt.QString("%1").arg(tend)) - self.gui.fileTimeLengthLineEdit.setText(Qt.QString("%1").arg(tend-tstart)) - - def file_position_changed(self): - start = self.gui.filePosStartLineEdit.text().toInt() - end = self.gui.filePosStopLineEdit.text().toInt() - if((start[1] == True) and (end[1] == True)): - self.cur_start = start[0] - self.cur_stop = end[0] - - tstart = self.cur_start / self.sample_rate - tend = self.cur_stop / self.sample_rate - self.gui.fileTimeStartLineEdit.setText(Qt.QString("%1").arg(tstart)) - self.gui.fileTimeStopLineEdit.setText(Qt.QString("%1").arg(tend)) - - self.get_data(self.cur_start, self.cur_stop) - - self.update_time_curves() - self.update_psd_curves() - self.update_specgram_curves() - - # If there's a non-digit character, reset box - else: - try: - self.set_file_pos_box(self.cur_start, self.cur_stop) - except AttributeError: - pass - - - def file_time_changed(self): - tstart = self.gui.fileTimeStartLineEdit.text().toDouble() - tstop = self.gui.fileTimeStopLineEdit.text().toDouble() - if((tstart[1] == True) and (tstop[1] == True)): - self.cur_start = int(tstart[0] * self.sample_rate) - self.cur_stop = int(tstop[0] * self.sample_rate) - self.get_data(self.cur_start, self.cur_stop) - - self.gui.filePosStartLineEdit.setText(Qt.QString("%1").arg(self.cur_start)) - self.gui.filePosStopLineEdit.setText(Qt.QString("%1").arg(self.cur_stop)) - - self.update_time_curves() - self.update_psd_curves() - self.update_specgram_curves() - # If there's a non-digit character, reset box - else: - self.set_file_pos_box(self.cur_start, self.cur_stop) - - def file_length_changed(self): - start = self.gui.filePosStartLineEdit.text().toInt() - length = self.gui.filePosLengthLineEdit.text().toInt() - - if((start[1] == True) and (length[1] == True)): - self.cur_start = start[0] - self.block_length = length[0] - self.cur_stop = self.cur_start + self.block_length - - tstart = self.cur_start / self.sample_rate - tend = self.cur_stop / self.sample_rate - tlen = self.block_length / self.sample_rate - self.gui.fileTimeStartLineEdit.setText(Qt.QString("%1").arg(tstart)) - self.gui.fileTimeStopLineEdit.setText(Qt.QString("%1").arg(tend)) - self.gui.fileTimeLengthLineEdit.setText(Qt.QString("%1").arg(tlen)) - - self.gui.plotHBar.setPageStep(self.block_length) - - self.get_data(self.cur_start, self.cur_stop) - self.get_psd() - self.get_specgram() - - self.update_time_curves() - self.update_psd_curves() - self.update_specgram_curves() - # If there's a non-digit character, reset box - else: - self.set_file_pos_box(self.cur_start, self.cur_stop) - - def file_time_length_changed(self): - tstart = self.gui.fileTimeStartLineEdit.text().toDouble() - tlength = self.gui.fileTimeLengthLineEdit.text().toDouble() - if((tstart[1] == True) and (tlength[1] == True)): - self.cur_start = int(tstart[0] * self.sample_rate) - self.block_length = int(tlength[0] * self.sample_rate) - self.cur_stop = self.cur_start + self.block_length - - tstart = self.cur_start / self.sample_rate - tend = self.cur_stop / self.sample_rate - tlen = self.block_length / self.sample_rate - self.gui.fileTimeStartLineEdit.setText(Qt.QString("%1").arg(tstart)) - self.gui.fileTimeStopLineEdit.setText(Qt.QString("%1").arg(tend)) - self.gui.fileTimeLengthLineEdit.setText(Qt.QString("%1").arg(tlen)) - - self.get_data(self.cur_start, self.cur_stop) - self.get_psd() - self.get_specgram() - - self.update_time_curves() - self.update_psd_curves() - self.update_specgram_curves() - # If there's a non-digit character, reset box - else: - self.set_file_pos_box(self.cur_start, self.cur_stop) - - - def update_time_curves(self): - self.icurve.setData(self.time, self.iq.imag) - self.rcurve.setData(self.time, self.iq.real) - - # Reset the x-axis to the new time scale - iqmax = 1.5 * max(max(self.iq.real), max(self.iq.imag)) - iqmin = 1.5 * min(min(self.iq.real), min(self.iq.imag)) - self.gui.timePlot.setAxisScale(self.gui.timePlot.xBottom, - min(self.time), - max(self.time)) - self.gui.timePlot.setAxisScale(self.gui.timePlot.yLeft, - iqmin, - iqmax) - - # Set the zoomer base to unzoom to the new axis - self.timeZoomer.setZoomBase() - - self.gui.timePlot.replot() - - def update_psd_curves(self): - self.psdcurve.setData(self.freq, self.iq_psd) - - self.gui.freqPlot.setAxisScale(self.gui.freqPlot.xBottom, - min(self.freq), - max(self.freq)) - - # Set the zoomer base to unzoom to the new axis - self.freqZoomer.setZoomBase() - - self.gui.freqPlot.replot() - - def update_specgram_curves(self): - # We don't have to reset the data for the speccurve here - # since this is taken care of in the SpectrogramData class - self.specdata.set_data(self.spec_f, self.spec_t, self.iq_spec) - - # Set the color map based on the new data - self.rightAxis.setColorMap(self.spec.data().range(), - self.spec.colorMap()) - - # Set the new axis base; include right axis for the intenisty color bar - self.gui.specPlot.setAxisScale(self.gui.specPlot.xBottom, - min(self.spec_f), - max(self.spec_f)) - self.gui.specPlot.setAxisScale(self.gui.specPlot.yLeft, - min(self.spec_t), - max(self.spec_t)) - self.gui.specPlot.setAxisScale(self.gui.specPlot.yRight, - self.iq_spec.min(), - self.iq_spec.max()) - - # Set the zoomer base to unzoom to the new axis - self.specZoomer.setZoomBase() - - self.gui.specPlot.replot() - - def tabChanged(self, index): - self.gui.timePlot.replot() - self.gui.freqPlot.replot() - self.gui.specPlot.replot() - - def change_pen_width(self, width): - self.pen_width = width - colormode = str(self.gui.colorComboBox.currentText().toAscii()) - color_func = self.color_modes[colormode]() - - def change_style_size(self, size): - self.rsym.setSize(size) - self.isym.setSize(size) - self.rcurve.setSymbol(self.rsym) - self.icurve.setSymbol(self.isym) - self.gui.timePlot.replot() - - def color_black_on_white(self): - blue = QtGui.qRgb(0x00, 0x00, 0xFF) - red = QtGui.qRgb(0xFF, 0x00, 0x00) - - blackPen = Qt.QPen(Qt.QBrush(Qt.QColor("black")), self.pen_width) - bluePen = Qt.QPen(Qt.QBrush(Qt.QColor(blue)), self.pen_width) - redPen = Qt.QPen(Qt.QBrush(Qt.QColor(red)), self.pen_width) - - self.gui.timePlot.setCanvasBackground(Qt.QColor("white")) - self.gui.freqPlot.setCanvasBackground(Qt.QColor("white")) - self.timeZoomer.setTrackerPen(blackPen) - self.timeZoomer.setRubberBandPen(blackPen) - self.freqZoomer.setTrackerPen(blackPen) - self.freqZoomer.setRubberBandPen(blackPen) - self.psdcurve.setPen(bluePen) - self.rcurve.setPen(bluePen) - self.icurve.setPen(redPen) - - self.rsym.setPen(bluePen) - self.isym.setPen(redPen) - - self.gui.timePlot.replot() - self.gui.freqPlot.replot() - - def color_white_on_black(self): - white = QtGui.qRgb(0xFF, 0xFF, 0xFF) - red = QtGui.qRgb(0xFF, 0x00, 0x00) - - whiteBrush = Qt.QBrush(Qt.QColor("white")) - whiteBrush = Qt.QBrush(Qt.QColor(white)) - redBrush = Qt.QBrush(Qt.QColor(red)) - - self.gui.timePlot.setCanvasBackground(QtGui.QColor("black")) - self.gui.freqPlot.setCanvasBackground(QtGui.QColor("black")) - self.timeZoomer.setTrackerPen(Qt.QPen(whiteBrush, self.pen_width)) - self.timeZoomer.setRubberBandPen(Qt.QPen(whiteBrush, self.pen_width)) - self.freqZoomer.setTrackerPen(Qt.QPen(whiteBrush, self.pen_width)) - self.freqZoomer.setRubberBandPen(Qt.QPen(whiteBrush, self.pen_width)) - self.psdcurve.setPen(Qt.QPen(whiteBrush, self.pen_width)) - self.rcurve.setPen(Qt.QPen(whiteBrush, self.pen_width)) - self.icurve.setPen(Qt.QPen(redBrush, self.pen_width)) - - self.gui.timePlot.replot() - self.gui.freqPlot.replot() - - - def color_green_on_black(self): - green = QtGui.qRgb(0x00, 0xFF, 0x00) - red = QtGui.qRgb(0xFF, 0x00, 0x50) - - whiteBrush = Qt.QBrush(Qt.QColor("white")) - greenBrush = Qt.QBrush(Qt.QColor(green)) - redBrush = Qt.QBrush(Qt.QColor(red)) - - self.gui.timePlot.setCanvasBackground(QtGui.QColor("black")) - self.gui.freqPlot.setCanvasBackground(QtGui.QColor("black")) - self.timeZoomer.setTrackerPen(Qt.QPen(whiteBrush, self.pen_width)) - self.timeZoomer.setRubberBandPen(Qt.QPen(whiteBrush, self.pen_width)) - self.freqZoomer.setTrackerPen(Qt.QPen(whiteBrush, self.pen_width)) - self.freqZoomer.setRubberBandPen(Qt.QPen(whiteBrush, self.pen_width)) - self.psdcurve.setPen(Qt.QPen(greenBrush, self.pen_width)) - self.rcurve.setPen(Qt.QPen(greenBrush, self.pen_width)) - self.icurve.setPen(Qt.QPen(redBrush, self.pen_width)) - - self.gui.timePlot.replot() - self.gui.freqPlot.replot() - - def color_blue_on_black(self): - blue = QtGui.qRgb(0x00, 0x00, 0xFF) - red = QtGui.qRgb(0xFF, 0x00, 0x00) - - whiteBrush = Qt.QBrush(Qt.QColor("white")) - blueBrush = Qt.QBrush(Qt.QColor(blue)) - redBrush = Qt.QBrush(Qt.QColor(red)) - - self.gui.timePlot.setCanvasBackground(QtGui.QColor("black")) - self.gui.freqPlot.setCanvasBackground(QtGui.QColor("black")) - self.timeZoomer.setTrackerPen(Qt.QPen(whiteBrush, self.pen_width)) - self.timeZoomer.setRubberBandPen(Qt.QPen(whiteBrush, self.pen_width)) - self.freqZoomer.setTrackerPen(Qt.QPen(whiteBrush, self.pen_width)) - self.freqZoomer.setRubberBandPen(Qt.QPen(whiteBrush, self.pen_width)) - self.psdcurve.setPen(Qt.QPen(blueBrush, self.pen_width)) - self.rcurve.setPen(Qt.QPen(blueBrush, self.pen_width)) - self.icurve.setPen(Qt.QPen(redBrush, self.pen_width)) - - self.gui.timePlot.replot() - self.gui.freqPlot.replot() - -def setup_options(): - usage="%prog: [options] (input_filename)" - description = "" - - parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) - parser.add_option("-B", "--block-length", type="int", default=8192, - help="Specify the block size [default=%default]") - parser.add_option("-s", "--start", type="int", default=0, - help="Specify where to start in the file [default=%default]") - parser.add_option("-R", "--sample-rate", type="float", default=1.0, - help="Set the sampler rate of the data [default=%default]") - parser.add_option("", "--psd-size", type="int", default=2048, - help="Set the size of the PSD FFT [default=%default]") - parser.add_option("", "--spec-size", type="int", default=2048, - help="Set the size of the spectrogram FFT [default=%default]") - - return parser - -def main(args): - parser = setup_options() - (options, args) = parser.parse_args () - - if(len(args) == 1): - filename = args[0] - else: - filename = None - - app = Qt.QApplication(args) - gplt = gr_plot_qt(app, filename, options) - app.exec_() - -if __name__ == '__main__': - main(sys.argv) - diff --git a/gr-utils/src/python/gr_plot_short b/gr-utils/src/python/gr_plot_short new file mode 100755 index 000000000..3466e0b7d --- /dev/null +++ b/gr-utils/src/python/gr_plot_short @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# +# Copyright 2007,2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +try: + import scipy +except ImportError: + print "Please install SciPy to run this script (http://www.scipy.org/)" + raise SystemExit, 1 + +from optparse import OptionParser +from gnuradio.plot_data import plot_data + +def main(): + usage="%prog: [options] input_filenames" + description = "Takes a GNU Radio short integer binary file and displays the samples versus time. You can set the block size to specify how many points to read in at a time and the start position in the file. By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." + + parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) + parser.add_option("-B", "--block", type="int", default=1000, + help="Specify the block size [default=%default]") + parser.add_option("-s", "--start", type="int", default=0, + help="Specify where to start in the file [default=%default]") + parser.add_option("-R", "--sample-rate", type="float", default=1.0, + help="Set the sampler rate of the data [default=%default]") + + (options, args) = parser.parse_args () + if len(args) < 1: + parser.print_help() + raise SystemExit, 1 + filenames = args + + datatype=scipy.int16 + dc = plot_data(datatype, filenames, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + diff --git a/gr-utils/src/python/gr_plot_short.py b/gr-utils/src/python/gr_plot_short.py deleted file mode 100755 index 3466e0b7d..000000000 --- a/gr-utils/src/python/gr_plot_short.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2007,2008 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -try: - import scipy -except ImportError: - print "Please install SciPy to run this script (http://www.scipy.org/)" - raise SystemExit, 1 - -from optparse import OptionParser -from gnuradio.plot_data import plot_data - -def main(): - usage="%prog: [options] input_filenames" - description = "Takes a GNU Radio short integer binary file and displays the samples versus time. You can set the block size to specify how many points to read in at a time and the start position in the file. By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." - - parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) - parser.add_option("-B", "--block", type="int", default=1000, - help="Specify the block size [default=%default]") - parser.add_option("-s", "--start", type="int", default=0, - help="Specify where to start in the file [default=%default]") - parser.add_option("-R", "--sample-rate", type="float", default=1.0, - help="Set the sampler rate of the data [default=%default]") - - (options, args) = parser.parse_args () - if len(args) < 1: - parser.print_help() - raise SystemExit, 1 - filenames = args - - datatype=scipy.int16 - dc = plot_data(datatype, filenames, options) - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - pass - diff --git a/gr-utils/src/python/plot_fft_base.py b/gr-utils/src/python/plot_fft_base.py new file mode 100755 index 000000000..0b5df504b --- /dev/null +++ b/gr-utils/src/python/plot_fft_base.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python +# +# Copyright 2007,2008,2011 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +try: + import scipy + from scipy import fftpack +except ImportError: + print "Please install SciPy to run this script (http://www.scipy.org/)" + raise SystemExit, 1 + +try: + from pylab import * +except ImportError: + print "Please install Matplotlib to run this script (http://matplotlib.sourceforge.net/)" + raise SystemExit, 1 + +from optparse import OptionParser + +class plot_fft_base: + def __init__(self, datatype, filename, options): + self.hfile = open(filename, "r") + self.block_length = options.block + self.start = options.start + self.sample_rate = options.sample_rate + + self.datatype = getattr(scipy, datatype) + self.sizeof_data = self.datatype().nbytes # number of bytes per sample in file + + self.axis_font_size = 16 + self.label_font_size = 18 + self.title_font_size = 20 + self.text_size = 22 + + # Setup PLOT + self.fig = figure(1, figsize=(16, 12), facecolor='w') + rcParams['xtick.labelsize'] = self.axis_font_size + rcParams['ytick.labelsize'] = self.axis_font_size + + self.text_file = figtext(0.10, 0.94, ("File: %s" % filename), weight="heavy", size=self.text_size) + self.text_file_pos = figtext(0.10, 0.88, "File Position: ", weight="heavy", size=self.text_size) + self.text_block = figtext(0.35, 0.88, ("Block Size: %d" % self.block_length), + weight="heavy", size=self.text_size) + self.text_sr = figtext(0.60, 0.88, ("Sample Rate: %.2f" % self.sample_rate), + weight="heavy", size=self.text_size) + self.make_plots() + + self.button_left_axes = self.fig.add_axes([0.45, 0.01, 0.05, 0.05], frameon=True) + self.button_left = Button(self.button_left_axes, "<") + self.button_left_callback = self.button_left.on_clicked(self.button_left_click) + + self.button_right_axes = self.fig.add_axes([0.50, 0.01, 0.05, 0.05], frameon=True) + self.button_right = Button(self.button_right_axes, ">") + self.button_right_callback = self.button_right.on_clicked(self.button_right_click) + + self.xlim = self.sp_iq.get_xlim() + + self.manager = get_current_fig_manager() + connect('draw_event', self.zoom) + connect('key_press_event', self.click) + show() + + def get_data(self): + self.position = self.hfile.tell()/self.sizeof_data + self.text_file_pos.set_text("File Position: %d" % (self.position)) + try: + self.iq = scipy.fromfile(self.hfile, dtype=self.datatype, count=self.block_length) + except MemoryError: + print "End of File" + else: + self.iq_fft = self.dofft(self.iq) + + tstep = 1.0 / self.sample_rate + #self.time = scipy.array([tstep*(self.position + i) for i in xrange(len(self.iq))]) + self.time = scipy.array([tstep*(i) for i in xrange(len(self.iq))]) + + self.freq = self.calc_freq(self.time, self.sample_rate) + + def dofft(self, iq): + N = len(iq) + iq_fft = scipy.fftpack.fftshift(scipy.fft(iq)) # fft and shift axis + iq_fft = 20*scipy.log10(abs((iq_fft+1e-15)/N)) # convert to decibels, adjust power + # adding 1e-15 (-300 dB) to protect against value errors if an item in iq_fft is 0 + return iq_fft + + def calc_freq(self, time, sample_rate): + N = len(time) + Fs = 1.0 / (time.max() - time.min()) + Fn = 0.5 * sample_rate + freq = scipy.array([-Fn + i*Fs for i in xrange(N)]) + return freq + + def make_plots(self): + # if specified on the command-line, set file pointer + self.hfile.seek(self.sizeof_data*self.start, 1) + + # Subplot for real and imaginary parts of signal + self.sp_iq = self.fig.add_subplot(2,2,1, position=[0.075, 0.2, 0.4, 0.6]) + self.sp_iq.set_title(("I&Q"), fontsize=self.title_font_size, fontweight="bold") + self.sp_iq.set_xlabel("Time (s)", fontsize=self.label_font_size, fontweight="bold") + self.sp_iq.set_ylabel("Amplitude (V)", fontsize=self.label_font_size, fontweight="bold") + + # Subplot for FFT plot + self.sp_fft = self.fig.add_subplot(2,2,2, position=[0.575, 0.2, 0.4, 0.6]) + self.sp_fft.set_title(("FFT"), fontsize=self.title_font_size, fontweight="bold") + self.sp_fft.set_xlabel("Frequency (Hz)", fontsize=self.label_font_size, fontweight="bold") + self.sp_fft.set_ylabel("Power Spectrum (dBm)", fontsize=self.label_font_size, fontweight="bold") + + self.get_data() + + self.plot_iq = self.sp_iq.plot([], 'bo-') # make plot for reals + self.plot_iq += self.sp_iq.plot([], 'ro-') # make plot for imags + self.draw_time() # draw the plot + + self.plot_fft = self.sp_fft.plot([], 'bo-') # make plot for FFT + self.draw_fft() # draw the plot + + draw() + + def draw_time(self): + reals = self.iq.real + imags = self.iq.imag + self.plot_iq[0].set_data([self.time, reals]) + self.plot_iq[1].set_data([self.time, imags]) + self.sp_iq.set_xlim(self.time.min(), self.time.max()) + self.sp_iq.set_ylim([1.5*min([reals.min(), imags.min()]), + 1.5*max([reals.max(), imags.max()])]) + + def draw_fft(self): + self.plot_fft[0].set_data([self.freq, self.iq_fft]) + self.sp_fft.set_xlim(self.freq.min(), self.freq.max()) + self.sp_fft.set_ylim([self.iq_fft.min()-10, self.iq_fft.max()+10]) + + def update_plots(self): + self.draw_time() + self.draw_fft() + + self.xlim = self.sp_iq.get_xlim() + draw() + + def zoom(self, event): + newxlim = scipy.array(self.sp_iq.get_xlim()) + curxlim = scipy.array(self.xlim) + if(newxlim[0] != curxlim[0] or newxlim[1] != curxlim[1]): + self.xlim = newxlim + #xmin = max(0, int(ceil(self.sample_rate*(self.xlim[0] - self.position)))) + #xmax = min(int(ceil(self.sample_rate*(self.xlim[1] - self.position))), len(self.iq)) + xmin = max(0, int(ceil(self.sample_rate*(self.xlim[0])))) + xmax = min(int(ceil(self.sample_rate*(self.xlim[1]))), len(self.iq)) + + iq = self.iq[xmin : xmax] + time = self.time[xmin : xmax] + + iq_fft = self.dofft(iq) + freq = self.calc_freq(time, self.sample_rate) + + self.plot_fft[0].set_data(freq, iq_fft) + self.sp_fft.axis([freq.min(), freq.max(), + iq_fft.min()-10, iq_fft.max()+10]) + + draw() + + def click(self, event): + forward_valid_keys = [" ", "down", "right"] + backward_valid_keys = ["up", "left"] + + if(find(event.key, forward_valid_keys)): + self.step_forward() + + elif(find(event.key, backward_valid_keys)): + self.step_backward() + + def button_left_click(self, event): + self.step_backward() + + def button_right_click(self, event): + self.step_forward() + + def step_forward(self): + self.get_data() + self.update_plots() + + def step_backward(self): + # Step back in file position + if(self.hfile.tell() >= 2*self.sizeof_data*self.block_length ): + self.hfile.seek(-2*self.sizeof_data*self.block_length, 1) + else: + self.hfile.seek(-self.hfile.tell(),1) + self.get_data() + self.update_plots() + + @staticmethod + def setup_options(): + usage="%prog: [options] input_filename" + description = "Takes a GNU Radio complex binary file and displays the I&Q data versus time as well as the frequency domain (FFT) plot. The y-axis values are plotted assuming volts as the amplitude of the I&Q streams and converted into dBm in the frequency domain (the 1/N power adjustment out of the FFT is performed internally). The script plots a certain block of data at a time, specified on the command line as -B or --block. This value defaults to 1000. The start position in the file can be set by specifying -s or --start and defaults to 0 (the start of the file). By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time and frequency axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples." + + parser = OptionParser(conflict_handler="resolve", usage=usage, description=description) + parser.add_option("-d", "--data-type", type="string", default="complex64", + help="Specify the data type (complex64, float32, (u)int32, (u)int16, (u)int8) [default=%default]") + parser.add_option("-B", "--block", type="int", default=1000, + help="Specify the block size [default=%default]") + parser.add_option("-s", "--start", type="int", default=0, + help="Specify where to start in the file [default=%default]") + parser.add_option("-R", "--sample-rate", type="float", default=1.0, + help="Set the sampler rate of the data [default=%default]") + return parser + +def find(item_in, list_search): + try: + return list_search.index(item_in) != None + except ValueError: + return False + +def main(): + parser = plot_fft_base.setup_options() + (options, args) = parser.parse_args () + if len(args) != 1: + parser.print_help() + raise SystemExit, 1 + filename = args[0] + + dc = plot_fft_base(options.data_type, filename, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + + + diff --git a/gr-utils/src/python/plot_psd_base.py b/gr-utils/src/python/plot_psd_base.py new file mode 100755 index 000000000..78eed5f71 --- /dev/null +++ b/gr-utils/src/python/plot_psd_base.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python +# +# Copyright 2007,2008,2010,2011 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +try: + import scipy + from scipy import fftpack +except ImportError: + print "Please install SciPy to run this script (http://www.scipy.org/)" + raise SystemExit, 1 + +try: + from pylab import * +except ImportError: + print "Please install Matplotlib to run this script (http://matplotlib.sourceforge.net/)" + raise SystemExit, 1 + +from optparse import OptionParser +from scipy import log10 +from gnuradio.eng_option import eng_option + +class plot_psd_base: + def __init__(self, datatype, filename, options): + self.hfile = open(filename, "r") + self.block_length = options.block + self.start = options.start + self.sample_rate = options.sample_rate + self.psdfftsize = options.psd_size + self.specfftsize = options.spec_size + + self.dospec = options.enable_spec # if we want to plot the spectrogram + + self.datatype = getattr(scipy, datatype) #scipy.complex64 + self.sizeof_data = self.datatype().nbytes # number of bytes per sample in file + + self.axis_font_size = 16 + self.label_font_size = 18 + self.title_font_size = 20 + self.text_size = 22 + + # Setup PLOT + self.fig = figure(1, figsize=(16, 12), facecolor='w') + rcParams['xtick.labelsize'] = self.axis_font_size + rcParams['ytick.labelsize'] = self.axis_font_size + + self.text_file = figtext(0.10, 0.95, ("File: %s" % filename), + weight="heavy", size=self.text_size) + self.text_file_pos = figtext(0.10, 0.92, "File Position: ", + weight="heavy", size=self.text_size) + self.text_block = figtext(0.35, 0.92, ("Block Size: %d" % self.block_length), + weight="heavy", size=self.text_size) + self.text_sr = figtext(0.60, 0.915, ("Sample Rate: %.2f" % self.sample_rate), + weight="heavy", size=self.text_size) + self.make_plots() + + self.button_left_axes = self.fig.add_axes([0.45, 0.01, 0.05, 0.05], frameon=True) + self.button_left = Button(self.button_left_axes, "<") + self.button_left_callback = self.button_left.on_clicked(self.button_left_click) + + self.button_right_axes = self.fig.add_axes([0.50, 0.01, 0.05, 0.05], frameon=True) + self.button_right = Button(self.button_right_axes, ">") + self.button_right_callback = self.button_right.on_clicked(self.button_right_click) + + self.xlim = scipy.array(self.sp_iq.get_xlim()) + + self.manager = get_current_fig_manager() + connect('draw_event', self.zoom) + connect('key_press_event', self.click) + show() + + def get_data(self): + self.position = self.hfile.tell()/self.sizeof_data + self.text_file_pos.set_text("File Position: %d" % self.position) + try: + self.iq = scipy.fromfile(self.hfile, dtype=self.datatype, count=self.block_length) + except MemoryError: + print "End of File" + return False + else: + # retesting length here as newer version of scipy does not throw a MemoryError, just + # returns a zero-length array + if(len(self.iq) > 0): + tstep = 1.0 / self.sample_rate + #self.time = scipy.array([tstep*(self.position + i) for i in xrange(len(self.iq))]) + self.time = scipy.array([tstep*(i) for i in xrange(len(self.iq))]) + + self.iq_psd, self.freq = self.dopsd(self.iq) + return True + else: + print "End of File" + return False + + def dopsd(self, iq): + ''' Need to do this here and plot later so we can do the fftshift ''' + overlap = self.psdfftsize/4 + winfunc = scipy.blackman + psd,freq = mlab.psd(iq, self.psdfftsize, self.sample_rate, + window = lambda d: d*winfunc(self.psdfftsize), + noverlap = overlap) + psd = 10.0*log10(abs(psd)) + return (psd, freq) + + def make_plots(self): + # if specified on the command-line, set file pointer + self.hfile.seek(self.sizeof_data*self.start, 1) + + iqdims = [[0.075, 0.2, 0.4, 0.6], [0.075, 0.55, 0.4, 0.3]] + psddims = [[0.575, 0.2, 0.4, 0.6], [0.575, 0.55, 0.4, 0.3]] + specdims = [0.2, 0.125, 0.6, 0.3] + + # Subplot for real and imaginary parts of signal + self.sp_iq = self.fig.add_subplot(2,2,1, position=iqdims[self.dospec]) + self.sp_iq.set_title(("I&Q"), fontsize=self.title_font_size, fontweight="bold") + self.sp_iq.set_xlabel("Time (s)", fontsize=self.label_font_size, fontweight="bold") + self.sp_iq.set_ylabel("Amplitude (V)", fontsize=self.label_font_size, fontweight="bold") + + # Subplot for PSD plot + self.sp_psd = self.fig.add_subplot(2,2,2, position=psddims[self.dospec]) + self.sp_psd.set_title(("PSD"), fontsize=self.title_font_size, fontweight="bold") + self.sp_psd.set_xlabel("Frequency (Hz)", fontsize=self.label_font_size, fontweight="bold") + self.sp_psd.set_ylabel("Power Spectrum (dBm)", fontsize=self.label_font_size, fontweight="bold") + + r = self.get_data() + + self.plot_iq = self.sp_iq.plot([], 'bo-') # make plot for reals + self.plot_iq += self.sp_iq.plot([], 'ro-') # make plot for imags + self.draw_time(self.time, self.iq) # draw the plot + + self.plot_psd = self.sp_psd.plot([], 'b') # make plot for PSD + self.draw_psd(self.freq, self.iq_psd) # draw the plot + + + if self.dospec: + # Subplot for spectrogram plot + self.sp_spec = self.fig.add_subplot(2,2,3, position=specdims) + self.sp_spec.set_title(("Spectrogram"), fontsize=self.title_font_size, fontweight="bold") + self.sp_spec.set_xlabel("Time (s)", fontsize=self.label_font_size, fontweight="bold") + self.sp_spec.set_ylabel("Frequency (Hz)", fontsize=self.label_font_size, fontweight="bold") + + self.draw_spec(self.time, self.iq) + + draw() + + def draw_time(self, t, iq): + reals = iq.real + imags = iq.imag + self.plot_iq[0].set_data([t, reals]) + self.plot_iq[1].set_data([t, imags]) + self.sp_iq.set_xlim(t.min(), t.max()) + self.sp_iq.set_ylim([1.5*min([reals.min(), imags.min()]), + 1.5*max([reals.max(), imags.max()])]) + + def draw_psd(self, f, p): + self.plot_psd[0].set_data([f, p]) + self.sp_psd.set_ylim([p.min()-10, p.max()+10]) + self.sp_psd.set_xlim([f.min(), f.max()]) + + def draw_spec(self, t, s): + overlap = self.specfftsize/4 + winfunc = scipy.blackman + self.sp_spec.clear() + self.sp_spec.specgram(s, self.specfftsize, self.sample_rate, + window = lambda d: d*winfunc(self.specfftsize), + noverlap = overlap, xextent=[t.min(), t.max()]) + + def update_plots(self): + self.draw_time(self.time, self.iq) + self.draw_psd(self.freq, self.iq_psd) + + if self.dospec: + self.draw_spec(self.time, self.iq) + + self.xlim = scipy.array(self.sp_iq.get_xlim()) # so zoom doesn't get called + + draw() + + def zoom(self, event): + newxlim = scipy.array(self.sp_iq.get_xlim()) + curxlim = scipy.array(self.xlim) + if(newxlim[0] != curxlim[0] or newxlim[1] != curxlim[1]): + #xmin = max(0, int(ceil(self.sample_rate*(newxlim[0] - self.position)))) + #xmax = min(int(ceil(self.sample_rate*(newxlim[1] - self.position))), len(self.iq)) + xmin = max(0, int(ceil(self.sample_rate*(newxlim[0])))) + xmax = min(int(ceil(self.sample_rate*(newxlim[1]))), len(self.iq)) + + iq = scipy.array(self.iq[xmin : xmax]) + time = scipy.array(self.time[xmin : xmax]) + + iq_psd, freq = self.dopsd(iq) + + self.draw_psd(freq, iq_psd) + self.xlim = scipy.array(self.sp_iq.get_xlim()) + + draw() + + def click(self, event): + forward_valid_keys = [" ", "down", "right"] + backward_valid_keys = ["up", "left"] + + if(find(event.key, forward_valid_keys)): + self.step_forward() + + elif(find(event.key, backward_valid_keys)): + self.step_backward() + + def button_left_click(self, event): + self.step_backward() + + def button_right_click(self, event): + self.step_forward() + + def step_forward(self): + r = self.get_data() + if(r): + self.update_plots() + + def step_backward(self): + # Step back in file position + if(self.hfile.tell() >= 2*self.sizeof_data*self.block_length ): + self.hfile.seek(-2*self.sizeof_data*self.block_length, 1) + else: + self.hfile.seek(-self.hfile.tell(),1) + r = self.get_data() + if(r): + self.update_plots() + + @staticmethod + def setup_options(): + usage="%prog: [options] input_filename" + description = "Takes a GNU Radio binary file (with specified data type using --data-type) and displays the I&Q data versus time as well as the power spectral density (PSD) plot. The y-axis values are plotted assuming volts as the amplitude of the I&Q streams and converted into dBm in the frequency domain (the 1/N power adjustment out of the FFT is performed internally). The script plots a certain block of data at a time, specified on the command line as -B or --block. The start position in the file can be set by specifying -s or --start and defaults to 0 (the start of the file). By default, the system assumes a sample rate of 1, so in time, each sample is plotted versus the sample number. To set a true time and frequency axis, set the sample rate (-R or --sample-rate) to the sample rate used when capturing the samples. Finally, the size of the FFT to use for the PSD and spectrogram plots can be set independently with --psd-size and --spec-size, respectively. The spectrogram plot does not display by default and is turned on with -S or --enable-spec." + + parser = OptionParser(option_class=eng_option, conflict_handler="resolve", + usage=usage, description=description) + parser.add_option("-d", "--data-type", type="string", default="complex64", + help="Specify the data type (complex64, float32, (u)int32, (u)int16, (u)int8) [default=%default]") + parser.add_option("-B", "--block", type="int", default=8192, + help="Specify the block size [default=%default]") + parser.add_option("-s", "--start", type="int", default=0, + help="Specify where to start in the file [default=%default]") + parser.add_option("-R", "--sample-rate", type="eng_float", default=1.0, + help="Set the sampler rate of the data [default=%default]") + parser.add_option("", "--psd-size", type="int", default=1024, + help="Set the size of the PSD FFT [default=%default]") + parser.add_option("", "--spec-size", type="int", default=256, + help="Set the size of the spectrogram FFT [default=%default]") + parser.add_option("-S", "--enable-spec", action="store_true", default=False, + help="Turn on plotting the spectrogram [default=%default]") + + return parser + +def find(item_in, list_search): + try: + return list_search.index(item_in) != None + except ValueError: + return False + +def main(): + parser = plot_psd_base.setup_options() + (options, args) = parser.parse_args () + if len(args) != 1: + parser.print_help() + raise SystemExit, 1 + filename = args[0] + + dc = plot_psd_base(options.data_type, filename, options) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + + + -- cgit