7c744f2c6991740f8d185c384f957b66f7dc676a
[debian/gnuradio] / gr-utils / src / python / gr_plot_qt.py
1 #!/usr/bin/env python
2
3 try:
4     import scipy
5     from scipy import fftpack
6 except ImportError:
7     print "Please install SciPy to run this script (http://www.scipy.org/)"
8     raise SystemExit, 1
9
10 import sys, os
11 from PyQt4 import Qt, QtCore, QtGui
12 import PyQt4.Qwt5 as Qwt
13 from matplotlib import mlab
14 from optparse import OptionParser
15 from gnuradio import eng_notation
16
17 from pyqt_plot import Ui_MainWindow
18
19 class gr_plot_qt(QtGui.QMainWindow):
20     def __init__(self, qapp, filename, options, parent=None):
21         QtGui.QWidget.__init__(self, parent)
22         self.gui = Ui_MainWindow()
23         self.gui.setupUi(self)
24                        
25         self.block_length = options.block_length
26         self.start = options.start
27         self.sample_rate = options.sample_rate
28         self.psdfftsize = options.psd_size
29         self.specfftsize = options.spec_size
30         self.winfunc = scipy.blackman
31         self.sizeof_data = 8
32         self.datatype = scipy.complex64
33         self.iq = list()
34         self.time = list()
35         
36         # Set up basic plot attributes
37         self.gui.timePlot.setAxisTitle(self.gui.timePlot.xBottom, "Time (sec)")
38         self.gui.timePlot.setAxisTitle(self.gui.timePlot.yLeft, "Amplitude (V)")
39         self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.xBottom, "Frequency (Hz)")
40         self.gui.freqPlot.setAxisTitle(self.gui.freqPlot.yLeft, "Magnitude (dB)")
41
42         # Set up FFT size combo box
43         self.gui.fftComboBox.addItems(["128", "256", "512", "1024", "2048",
44                                        "4096", "8192", "16384", "32768"])
45         pos = self.gui.fftComboBox.findText(Qt.QString("%1").arg(self.psdfftsize))
46         self.gui.fftComboBox.setCurrentIndex(pos)
47         self.connect(self.gui.fftComboBox,
48                      Qt.SIGNAL("activated (const QString&)"),
49                      self.fftComboBoxEdit)
50
51         # Set up color scheme box
52         self.color_modes = {"Black on White" : self.color_black_on_white,
53                             "White on Black" : self.color_white_on_black,
54                             "Blue on Black"  : self.color_blue_on_black,
55                             "Green on Black" : self.color_green_on_black}
56         self.gui.colorComboBox.addItems(self.color_modes.keys())
57         pos = self.gui.colorComboBox.findText("Blue on Black")
58         self.gui.colorComboBox.setCurrentIndex(pos)
59         self.connect(self.gui.colorComboBox,
60                      Qt.SIGNAL("activated (const QString&)"),
61                      self.colorComboBoxEdit)
62         
63         
64         # Create zoom functionality for the plots
65         self.timeZoomer = Qwt.QwtPlotZoomer(self.gui.timePlot.xBottom,
66                                             self.gui.timePlot.yLeft,
67                                             Qwt.QwtPicker.PointSelection,
68                                             Qwt.QwtPicker.AlwaysOn,
69                                             self.gui.timePlot.canvas())
70
71         self.freqZoomer = Qwt.QwtPlotZoomer(self.gui.freqPlot.xBottom,
72                                             self.gui.freqPlot.yLeft,
73                                             Qwt.QwtPicker.PointSelection,
74                                             Qwt.QwtPicker.AlwaysOn,
75                                             self.gui.freqPlot.canvas())
76
77         self.picker = Qwt.QwtPlotPicker(self.gui.timePlot.xBottom,
78                                         self.gui.timePlot.yLeft,
79                                         Qwt.QwtPicker.PointSelection,
80                                         Qwt.QwtPlotPicker.CrossRubberBand,
81                                         Qwt.QwtPicker.AlwaysOn,
82                                         self.gui.timePlot.canvas())
83         self.picker.connect(self.picker,
84                             Qt.SIGNAL('selected(const QwtDoublePoint&)'),
85                             self.clickMe)
86
87         # Set up action when tab is changed
88         self.connect(self.gui.tabGroup,
89                      Qt.SIGNAL("currentChanged (int)"),
90                      self.tabChanged)
91
92         # Add a legend to the Time plot
93         legend_real = Qwt.QwtLegend()
94         self.gui.timePlot.insertLegend(legend_real)
95
96         # Set up slider
97         self.gui.plotHBar.setSingleStep(1)
98         self.gui.plotHBar.setPageStep(self.block_length)
99         self.gui.plotHBar.setMinimum(0)
100         self.gui.plotHBar.setMaximum(self.block_length)
101         self.connect(self.gui.plotHBar,
102                      Qt.SIGNAL("valueChanged(int)"),
103                      self.sliderMoved)
104
105         # Connect Open action to Open Dialog box
106         self.connect(self.gui.action_open,
107                      Qt.SIGNAL("activated()"),
108                      self.open_file)
109         
110         # Set up file position boxes to update current figure
111         self.connect(self.gui.filePosStartLineEdit,
112                      Qt.SIGNAL("editingFinished()"),
113                      self.file_position_changed)
114         self.connect(self.gui.filePosStopLineEdit,
115                      Qt.SIGNAL("editingFinished()"),
116                      self.file_position_changed)
117         self.connect(self.gui.filePosLengthLineEdit,
118                      Qt.SIGNAL("editingFinished()"),
119                      self.file_length_changed)
120
121         self.connect(self.gui.fileTimeStartLineEdit,
122                      Qt.SIGNAL("editingFinished()"),
123                      self.file_time_changed)
124         self.connect(self.gui.fileTimeStopLineEdit,
125                      Qt.SIGNAL("editingFinished()"),
126                      self.file_time_changed)
127         self.connect(self.gui.fileTimeLengthLineEdit,
128                      Qt.SIGNAL("editingFinished()"),
129                      self.file_time_length_changed)
130
131         self.rcurve = Qwt.QwtPlotCurve("Real")
132         self.icurve = Qwt.QwtPlotCurve("Imaginary")
133
134         self.icurve.attach(self.gui.timePlot)
135         self.rcurve.attach(self.gui.timePlot)
136
137         self.psdcurve = Qwt.QwtPlotCurve("PSD")
138         self.psdcurve.attach(self.gui.freqPlot)
139
140         # Set up initial color scheme
141         self.color_modes["Blue on Black"]()
142
143         self.set_sample_rate(self.sample_rate)
144         self.connect(self.gui.sampleRateLineEdit,
145                      Qt.SIGNAL("editingFinished()"),
146                      self.sample_rate_changed)
147
148         if(filename is not None):
149             self.initialize(filename)
150
151         self.show()
152
153     def open_file(self):
154         filename = Qt.QFileDialog.getOpenFileName(self, "Open", ".")
155         print filename
156         self.initialize(filename)
157
158     def initialize(self, filename):
159         self.hfile = open(filename, "r")
160
161         self.setWindowTitle(("GNU Radio File Plot Utility: %s" % filename))
162         
163         self.cur_start = 0
164         self.cur_stop = self.block_length
165
166         self.init_data_input()
167         self.get_data(self.cur_start, self.cur_stop)
168         self.get_psd()
169         self.gui.plotHBar.setSliderPosition(0)
170         self.gui.plotHBar.setMaximum(self.signal_size)
171
172         self.update_time_curves()
173         self.update_psd_curves()
174
175     def init_data_input(self):
176         self.hfile.seek(0, os.SEEK_END)
177         self.signal_size = self.hfile.tell()/self.sizeof_data
178         print "Sizeof File: ", self.signal_size
179         self.hfile.seek(0, os.SEEK_SET)
180         
181     def get_data(self, start, end):
182         if(end > start):
183             self.hfile.seek(start*self.sizeof_data, os.SEEK_SET)
184             self.position = start
185             iq = scipy.fromfile(self.hfile, dtype=self.datatype,
186                                 count=end-start)
187             if(len(iq) < (end-start)):
188                 print "End of File"
189             else:
190                 tstep = 1.0 / self.sample_rate
191                 self.iq = iq
192                 self.time = [tstep*(self.position + i) for i in xrange(len(self.iq))]
193
194             self.set_file_pos_box(start, end)
195         else:
196             # Do we want to do anything about this?
197             pass
198
199     def get_psd(self):
200         winpoints = self.winfunc(self.psdfftsize)
201         iq_psd, freq = mlab.psd(self.iq, Fs=self.sample_rate,
202                                 NFFT=self.psdfftsize,
203                                 noverlap=self.psdfftsize/4.0,
204                                 window=winpoints,
205                                 scale_by_freq=False)
206         self.iq_psd = 10.0*scipy.log10(abs(fftpack.fftshift(iq_psd)))
207         self.freq = freq - self.sample_rate/2.0
208
209
210     def clickMe(self, qPoint):
211         print qPoint.x()
212
213     def fftComboBoxEdit(self, fftSize):
214         self.psdfftsize = fftSize.toInt()[0]
215         self.get_psd()
216         self.update_psd_curves()
217         
218     def colorComboBoxEdit(self, colorSelection):
219         colorstr = str(colorSelection.toAscii())
220         color_func = self.color_modes[colorstr]
221         color_func()
222
223     def sliderMoved(self, value):
224         pos_start = value
225         pos_end = value + self.gui.plotHBar.pageStep()
226
227         self.get_data(pos_start, pos_end)
228         self.get_psd()
229         self.update_time_curves()
230         self.update_psd_curves()
231
232     def set_sample_rate(self, sr):
233         self.sample_rate = sr
234         srstr = eng_notation.num_to_str(self.sample_rate)
235         self.gui.sampleRateLineEdit.setText(Qt.QString("%1").arg(srstr))
236
237     def sample_rate_changed(self):
238         srstr = self.gui.sampleRateLineEdit.text().toAscii()
239         self.sample_rate = eng_notation.str_to_num(srstr)
240         self.set_file_pos_box(self.cur_start, self.cur_stop)
241         self.get_data(self.cur_start, self.cur_stop)
242         self.get_psd()
243         self.update_time_curves()
244         self.update_psd_curves()
245
246     def set_file_pos_box(self, start, end):
247         tstart = start / self.sample_rate
248         tend = end / self.sample_rate
249
250         self.gui.filePosStartLineEdit.setText(Qt.QString("%1").arg(start))
251         self.gui.filePosStopLineEdit.setText(Qt.QString("%1").arg(end))
252         self.gui.filePosLengthLineEdit.setText(Qt.QString("%1").arg(end-start))
253
254         self.gui.fileTimeStartLineEdit.setText(Qt.QString("%1").arg(tstart))
255         self.gui.fileTimeStopLineEdit.setText(Qt.QString("%1").arg(tend))
256         self.gui.fileTimeLengthLineEdit.setText(Qt.QString("%1").arg(tend-tstart))
257
258     def file_position_changed(self):
259         start  = self.gui.filePosStartLineEdit.text().toInt()
260         end    = self.gui.filePosStopLineEdit.text().toInt()
261         if((start[1] == True) and (end[1] == True)):
262             self.cur_start = start[0]
263             self.cur_stop = end[0]
264
265             tstart = self.cur_start / self.sample_rate
266             tend = self.cur_stop / self.sample_rate
267             self.gui.fileTimeStartLineEdit.setText(Qt.QString("%1").arg(tstart))
268             self.gui.fileTimeStopLineEdit.setText(Qt.QString("%1").arg(tend))
269             
270             self.get_data(self.cur_start, self.cur_stop)
271
272             self.update_time_curves()
273             self.update_psd_curves()
274
275         # If there's a non-digit character, reset box
276         else:
277             self.set_file_pos_box(self.cur_start, self.cur_stop)
278
279     def file_time_changed(self):
280         tstart = self.gui.fileTimeStartLineEdit.text().toDouble()
281         tstop  = self.gui.fileTimeStopLineEdit.text().toDouble()
282         if((tstart[1] == True) and (tstop[1] == True)):
283             self.cur_start = int(tstart[0] * self.sample_rate)
284             self.cur_stop = int(tstop[0] * self.sample_rate)
285             self.get_data(self.cur_start, self.cur_stop)
286
287             self.gui.filePosStartLineEdit.setText(Qt.QString("%1").arg(self.cur_start))
288             self.gui.filePosStopLineEdit.setText(Qt.QString("%1").arg(self.cur_stop))
289
290             self.update_time_curves()
291             self.update_psd_curves()
292         # If there's a non-digit character, reset box
293         else:
294             self.set_file_pos_box(self.cur_start, self.cur_stop)
295
296     def file_length_changed(self):
297         start = self.gui.filePosStartLineEdit.text().toInt()
298         length = self.gui.filePosLengthLineEdit.text().toInt()
299         if((start[1] == True) and (length[1] == True)):
300             self.cur_start = start[0]
301             self.block_length = length[0]
302             self.cur_stop = self.cur_start + self.block_length
303
304             tstart = self.cur_start / self.sample_rate
305             tend = self.cur_stop / self.sample_rate
306             tlen = self.block_length / self.sample_rate
307             self.gui.fileTimeStartLineEdit.setText(Qt.QString("%1").arg(tstart))
308             self.gui.fileTimeStopLineEdit.setText(Qt.QString("%1").arg(tend))
309             self.gui.fileTimeLengthLineEdit.setText(Qt.QString("%1").arg(tlen))
310
311             self.gui.plotHBar.setPageStep(self.block_length)
312
313             self.get_data(self.cur_start, self.cur_stop)
314             self.get_psd()
315
316             self.update_time_curves()
317             self.update_psd_curves()
318         # If there's a non-digit character, reset box
319         else:
320             self.set_file_pos_box(self.cur_start, self.cur_stop)
321
322     def file_time_length_changed(self):
323         tstart = self.gui.fileTimeStartLineEdit.text().toDouble()
324         tlength = self.gui.fileTimeLengthLineEdit.text().toDouble()
325         if((tstart[1] == True) and (tlength[1] == True)):
326             self.cur_start = int(tstart[0] * self.sample_rate)
327             self.block_length = int(tlength[0] * self.sample_rate)
328             self.cur_stop = self.cur_start + self.block_length
329
330             tstart = self.cur_start / self.sample_rate
331             tend = self.cur_stop / self.sample_rate
332             tlen = self.block_length / self.sample_rate
333             self.gui.fileTimeStartLineEdit.setText(Qt.QString("%1").arg(tstart))
334             self.gui.fileTimeStopLineEdit.setText(Qt.QString("%1").arg(tend))
335             self.gui.fileTimeLengthLineEdit.setText(Qt.QString("%1").arg(tlen))
336
337             self.get_data(self.cur_start, self.cur_stop)
338             self.get_psd()
339
340             self.update_time_curves()
341             self.update_psd_curves()
342         # If there's a non-digit character, reset box
343         else:
344             self.set_file_pos_box(self.cur_start, self.cur_stop)
345
346
347     def update_time_curves(self):
348         self.icurve.setData(self.time, self.iq.imag)
349         self.rcurve.setData(self.time, self.iq.real)
350
351         # Reset the x-axis to the new time scale
352         iqmax = 1.5 * max(max(self.iq.real), max(self.iq.imag))
353         iqmin = 1.5 * min(min(self.iq.real), min(self.iq.imag))
354         self.gui.timePlot.setAxisScale(self.gui.timePlot.xBottom,
355                                        min(self.time),
356                                        max(self.time))
357         self.gui.timePlot.setAxisScale(self.gui.timePlot.yLeft,
358                                        iqmin,
359                                        iqmax)
360
361         # Set the zoomer base to unzoom to the new axis
362         self.timeZoomer.setZoomBase()
363     
364         self.gui.timePlot.replot()
365         
366     def update_psd_curves(self):
367         self.psdcurve.setData(self.freq, self.iq_psd)
368
369         self.gui.freqPlot.setAxisScale(self.gui.freqPlot.xBottom,
370                                        min(self.freq),
371                                        max(self.freq))
372
373         # Set the zoomer base to unzoom to the new axis
374         self.freqZoomer.setZoomBase()
375
376         self.gui.freqPlot.replot()
377
378     def tabChanged(self, index):
379         self.gui.timePlot.replot()
380         self.gui.freqPlot.replot()
381
382     def color_black_on_white(self):
383         blue = QtGui.qRgb(0x00, 0x00, 0xFF)
384         red = QtGui.qRgb(0xFF, 0x00, 0x00)
385
386         blackBrush = Qt.QBrush(Qt.QColor("black"))
387         blueBrush = Qt.QBrush(Qt.QColor(blue))
388         redBrush = Qt.QBrush(Qt.QColor(red))
389
390         self.gui.timePlot.setCanvasBackground(Qt.QColor("white"))
391         self.gui.freqPlot.setCanvasBackground(Qt.QColor("white"))
392         self.picker.setTrackerPen(Qt.QPen(blackBrush, 2))
393         self.timeZoomer.setTrackerPen(Qt.QPen(blackBrush, 2))
394         self.timeZoomer.setRubberBandPen(Qt.QPen(blackBrush, 2))
395         self.freqZoomer.setTrackerPen(Qt.QPen(blackBrush, 2))
396         self.freqZoomer.setRubberBandPen(Qt.QPen(blackBrush, 2))
397         self.psdcurve.setPen(Qt.QPen(blueBrush, 1))
398         self.rcurve.setPen(Qt.QPen(blueBrush, 2))
399         self.icurve.setPen(Qt.QPen(redBrush, 2))
400
401         self.gui.timePlot.replot()
402         self.gui.freqPlot.replot()
403
404     def color_white_on_black(self):
405         white = QtGui.qRgb(0xFF, 0xFF, 0xFF)
406         red = QtGui.qRgb(0xFF, 0x00, 0x00)
407
408         whiteBrush = Qt.QBrush(Qt.QColor("white"))
409         whiteBrush = Qt.QBrush(Qt.QColor(white))
410         redBrush = Qt.QBrush(Qt.QColor(red))
411         
412         self.gui.timePlot.setCanvasBackground(QtGui.QColor("black"))
413         self.gui.freqPlot.setCanvasBackground(QtGui.QColor("black"))
414         self.picker.setTrackerPen(Qt.QPen(whiteBrush, 2))
415         self.timeZoomer.setTrackerPen(Qt.QPen(whiteBrush, 2))
416         self.timeZoomer.setRubberBandPen(Qt.QPen(whiteBrush, 2))
417         self.freqZoomer.setTrackerPen(Qt.QPen(whiteBrush, 2))
418         self.freqZoomer.setRubberBandPen(Qt.QPen(whiteBrush, 2))
419         self.psdcurve.setPen(Qt.QPen(whiteBrush, 1))
420         self.rcurve.setPen(Qt.QPen(whiteBrush, 2))
421         self.icurve.setPen(Qt.QPen(redBrush, 2))
422
423         self.gui.timePlot.replot()
424         self.gui.freqPlot.replot()
425
426
427     def color_green_on_black(self):
428         green = QtGui.qRgb(0x00, 0xFF, 0x00)
429         red = QtGui.qRgb(0xFF, 0x00, 0x50)
430
431         whiteBrush = Qt.QBrush(Qt.QColor("white"))
432         greenBrush = Qt.QBrush(Qt.QColor(green))
433         redBrush = Qt.QBrush(Qt.QColor(red))
434         
435         self.gui.timePlot.setCanvasBackground(QtGui.QColor("black"))
436         self.gui.freqPlot.setCanvasBackground(QtGui.QColor("black"))
437         self.picker.setTrackerPen(Qt.QPen(whiteBrush, 2))
438         self.timeZoomer.setTrackerPen(Qt.QPen(whiteBrush, 2))
439         self.timeZoomer.setRubberBandPen(Qt.QPen(whiteBrush, 2))
440         self.freqZoomer.setTrackerPen(Qt.QPen(whiteBrush, 2))
441         self.freqZoomer.setRubberBandPen(Qt.QPen(whiteBrush, 2))
442         self.psdcurve.setPen(Qt.QPen(greenBrush, 1))
443         self.rcurve.setPen(Qt.QPen(greenBrush, 2))
444         self.icurve.setPen(Qt.QPen(redBrush, 2))
445
446         self.gui.timePlot.replot()
447         self.gui.freqPlot.replot()
448
449     def color_blue_on_black(self):
450         blue = QtGui.qRgb(0x00, 0x00, 0xFF)
451         red = QtGui.qRgb(0xFF, 0x00, 0x00)
452
453         whiteBrush = Qt.QBrush(Qt.QColor("white"))
454         blueBrush = Qt.QBrush(Qt.QColor(blue))
455         redBrush = Qt.QBrush(Qt.QColor(red))
456         
457         self.gui.timePlot.setCanvasBackground(QtGui.QColor("black"))
458         self.gui.freqPlot.setCanvasBackground(QtGui.QColor("black"))
459         self.picker.setTrackerPen(Qt.QPen(whiteBrush, 2))
460         self.timeZoomer.setTrackerPen(Qt.QPen(whiteBrush, 2))
461         self.timeZoomer.setRubberBandPen(Qt.QPen(whiteBrush, 2))
462         self.freqZoomer.setTrackerPen(Qt.QPen(whiteBrush, 2))
463         self.freqZoomer.setRubberBandPen(Qt.QPen(whiteBrush, 2))
464         self.psdcurve.setPen(Qt.QPen(blueBrush, 1))
465         self.rcurve.setPen(Qt.QPen(blueBrush, 2))
466         self.icurve.setPen(Qt.QPen(redBrush, 2))
467
468         self.gui.timePlot.replot()
469         self.gui.freqPlot.replot()
470
471 def setup_options():
472     usage="%prog: [options] (input_filename)"
473     description = ""
474
475     parser = OptionParser(conflict_handler="resolve", usage=usage, description=description)
476     parser.add_option("-B", "--block-length", type="int", default=8192,
477                       help="Specify the block size [default=%default]")
478     parser.add_option("-s", "--start", type="int", default=0,
479                       help="Specify where to start in the file [default=%default]")
480     parser.add_option("-R", "--sample-rate", type="float", default=1.0,
481                       help="Set the sampler rate of the data [default=%default]")
482     parser.add_option("", "--psd-size", type="int", default=2048,
483                       help="Set the size of the PSD FFT [default=%default]")
484     parser.add_option("", "--spec-size", type="int", default=256,
485                       help="Set the size of the spectrogram FFT [default=%default]")
486
487     return parser
488
489 def main(args):
490     parser = setup_options()
491     (options, args) = parser.parse_args ()
492
493     if(len(args) == 1):
494         filename = args[0]
495     else:
496         filename = None
497
498     app = Qt.QApplication(args)
499     gplt = gr_plot_qt(app, filename, options)
500     app.exec_()
501
502 if __name__ == '__main__':
503     main(sys.argv)
504