TutorialsQTGUI: Difference between revisions

From GNU Radio
Jump to navigation Jump to search
No edit summary
m (update to version 3.8.0.0)
Line 37: Line 37:
If you have successfully installed the gr-qtgui module with GNU Radio, then all of the prerequisites should be installed. Just in case, the Python modules you will need to run a QT GUI application are:
If you have successfully installed the gr-qtgui module with GNU Radio, then all of the prerequisites should be installed. Just in case, the Python modules you will need to run a QT GUI application are:


* PyQt4<br />
* PyQt5<br />
* SIP
* SIP


Line 48: Line 48:
Here is the full code:
Here is the full code:


<pre>#!/usr/bin/env python
<pre>#!/usr/bin/env python3
 
from PyQt5 import Qt
from gnuradio import gr
from gnuradio import gr
from gnuradio import qtgui
from gnuradio import qtgui
Line 55: Line 55:
from gnuradio import blocks
from gnuradio import blocks
from gnuradio.filter import firdes
from gnuradio.filter import firdes
from PyQt4 import QtGui
import sys, sip
import sys, sip


Line 64: Line 62:


         # Make a local QtApp so we can start it from our side
         # Make a local QtApp so we can start it from our side
         self.qapp = QtGui.QApplication(sys.argv)
         self.qapp = Qt.QApplication(sys.argv)


         samp_rate = 1e6
         samp_rate = 1e6
Line 79: Line 77:
             0, #fc
             0, #fc
             samp_rate, #bw
             samp_rate, #bw
             &quot;&quot;, #name
             "", #name
             True, #plotfreq
             True, #plotfreq
             True, #plotwaterfall
             True, #plotwaterfall
Line 91: Line 89:


         # Tell the sink we want it displayed
         # Tell the sink we want it displayed
         self.pyobj = sip.wrapinstance(self.snk.pyqwidget(), QtGui.QWidget)
         self.pyobj = sip.wrapinstance(self.snk.pyqwidget(), Qt.QWidget)
         self.pyobj.show()
         self.pyobj.show()


Line 99: Line 97:
     tb.qapp.exec_()
     tb.qapp.exec_()


if __name__ == &quot;__main__&quot;:
if __name__ == "__main__":
     try:
     try:
         main()
         main()
     except KeyboardInterrupt:
     except KeyboardInterrupt:
         pass</pre>
         pass</pre>
Lines 1 - 7 just set up the environment and modules, including the PyQT modules we require. Lines 9 - 11 simply set up the &quot;my_tb&quot; class as a GNU Radio top block class and initialize it.
Lines 2 - 8 just set up the environment and modules, including the PyQT modules we require. Lines 10 - 12 simply set up the &quot;my_tb&quot; class as a GNU Radio top block class and initialize it.


Line 14 is the first deviation from normal GNU Radio flow graphs. This line gets a reference to the qApp, which is QT global application element. Right now, it is enough to know that we require it, but we won't do anything with it until later.
Line 15 is the first deviation from normal GNU Radio flow graphs. This line gets a reference to the qApp, which is QT global application element. Right now, it is enough to know that we require it, but we won't do anything with it until later.


Line 16 just sets a variable for the FFT size. Lines 18 - 22 build the blocks of the flow graph, including the qtgui block. In this flow graph, we create a sine wave and add noise to it. The noise is to make sure the signal is constantly changing in the display as a simple sine wave looks like nothing changes between frames. There is a threshold block so the GUI is not trying to run at full speed, too. Finally, the sink is a complex qtgui sink with just the first two arguments given. As can be seen in the class constructor listed above, all of the other arguments have defaults, so we will use these for now.
Line 18 just sets a variable for the FFT size. Lines 20 - 25 build the blocks of the flow graph, including the qtgui block. In this flow graph, we create a sine wave and add noise to it. The noise is to make sure the signal is constantly changing in the display as a simple sine wave looks like nothing changes between frames. There is a threshold block so the GUI is not trying to run at full speed, too. Finally, the sink is a complex qtgui sink with just the first two arguments given. As can be seen in the class constructor listed above, all of the other arguments have defaults, so we will use these for now.


Lines 24 - 26 just connect all of the blocks. Like any other block, the qtgui sink is added as just another connection.
Lines 37 - 39 just connect all of the blocks. Like any other block, the qtgui sink is added as just another connection.


Lines 29 and 30 are specific to making the qtgui blocks visible. We have to use the &quot;show&quot; operation on the qtgui block, but first, we have to convert it to a Python object. Line 29 does the conversion by using the SIP wrapper interface to go from a PyObject in C++ to a native Python object as a QWidget. The &quot;pyqwidget&quot; method of the qtgui sink passes the proper object pointer back to allow this handling. Now, the Python object has the &quot;show()&quot; method required to display the sink that is called in Line 30. Without this, the code would run, but we would see nothing displayed.
Lines 42 and 43 are specific to making the qtgui blocks visible. We have to use the &quot;show&quot; operation on the qtgui block, but first, we have to convert it to a Python object. Line 42 does the conversion by using the SIP wrapper interface to go from a PyObject in C++ to a native Python object as a QWidget. The &quot;pyqwidget&quot; method of the qtgui sink passes the proper object pointer back to allow this handling. Now, the Python object has the &quot;show()&quot; method required to display the sink that is called in Line 43. Without this, the code would run, but we would see nothing displayed.


The final bit of tickery that deviates from a normal flow graph is how we tell the class to run. Line 33 instantiates an object of our &quot;my_tb&quot; class. Instead of calling &quot;run()&quot; on this object, though, we only call &quot;start()&quot; in line 34, which is non-blocking (run() performs both a start() and wait() call). We then tell the qApp that we got a reference to in Line 10 to execute with the &quot;exec_()&quot; method on line 35. This starts the QT runtime engine to handle the display and controls of the QT widgets. Now, when we run the program, we should see a QT application with a widget displaying the frequency domain. [SHOWPCITURE]
The final bit of tickery that deviates from a normal flow graph is how we tell the class to run. Line 46 instantiates an object of our &quot;my_tb&quot; class. Instead of calling &quot;run()&quot; on this object, though, we only call &quot;start()&quot; in line 47, which is non-blocking (run() performs both a start() and wait() call). We then tell the qApp that we got a reference to in Line 15 to execute with the &quot;exec_()&quot; method on line 48. This starts the QT runtime engine to handle the display and controls of the QT widgets. Now, when we run the program, we should see a QT application with a widget displaying the frequency domain. [SHOWPCITURE]


The big take-aways message here is the relatively minor changes required to make a qtgui application from a flow graph.
The big take-aways message here is the relatively minor changes required to make a qtgui application from a flow graph.

Revision as of 20:30, 27 November 2019

Tutorial - Using the QT GUI Blocks in GNU Radio

Basics

This document demonstrates how to use the QT GUI features in GNU Radio. There is one main QT interface defined that comes as either a complex sink or floating point sink, they are called:

  • complex sink: qtgui.sink_c
  • float sink: qtgui.sink_f

As this shows, the sinks are located in the module gnuradio.qtgui and is imported into Python with:

from gnuradio import qtgui

Both the complex and floating point versions of the sink take the same arguments using the following class constructor:

 
qtgui_make_sink_X (int fftsize, int wintype, double fc=0, 
                   double bandwidth=1.0, const std::string &name="Spectrum Display",
                   bool plotfreq=true, bool plotwaterfall=true, bool plotwaterfall3d=true,
                   bool plottime=true, bool plotconst=true, bool use_openGL=true,
                   QWidget *parent=NULL)

The following describes the arguments meanings more:

  • fftsize: initial FFT size
  • wintype: initial FFT window type. These can be taken from gr.firdes as WIN_BLACKMAN, WIN_BLACKMAN_HARRIS, WIN_HAMMING, WIN_HANN, WIN_KAISER, WIN_RECTANGULAR WIN_BLACKMAN_HARRIS is recommended if you don't have specific windowing needs
  • fc: center frequency for the x-axis display
  • bandwidth: sets the x-axis range around fc
  • name: The title of the GUI object in the title bar
  • plotfreq: Display frequency window?
  • plotwaterfall: Display waterfall window?
  • plotwaterfall3d: Display 3D waterfall window?
  • plottime: Display time window?
  • plotconst: Display constellation window?
  • parent: a parent widget for this object to be put into as a child

Prerequisites

If you have successfully installed the gr-qtgui module with GNU Radio, then all of the prerequisites should be installed. Just in case, the Python modules you will need to run a QT GUI application are:

  • PyQt5
  • SIP

It is also recommended that you get PyQWT5 to be able to build more extensive and nicer looking GUIs.

Example 1: Seeing the GUI

The first step is to be able to create a qtgui sink and display it on screen. This is both the simplest and most useful for debugging. By understanding how this example works, you can go and easily add a GUI to any existing GNU Radio application to see the signals at any point in the flow graph.

Here is the full code:

#!/usr/bin/env python3
from PyQt5 import Qt
from gnuradio import gr
from gnuradio import qtgui
from gnuradio import analog
from gnuradio import blocks
from gnuradio.filter import firdes
import sys, sip

class my_tb(gr.top_block):
    def __init__(self):
        gr.top_block.__init__(self)

        # Make a local QtApp so we can start it from our side
        self.qapp = Qt.QApplication(sys.argv)

        samp_rate = 1e6
        fftsize = 2048

        self.src = analog.sig_source_c(samp_rate, analog.GR_SIN_WAVE, 0.1, 1, 0)
        self.nse = analog.noise_source_c(analog.GR_GAUSSIAN, 0.1)
        self.add = blocks.add_cc()
        self.thr = blocks.throttle(gr.sizeof_gr_complex, samp_rate, True)

        self.snk = qtgui.sink_c(
            fftsize, #fftsize
            firdes.WIN_BLACKMAN_hARRIS, #wintype
            0, #fc
            samp_rate, #bw
            "", #name
            True, #plotfreq
            True, #plotwaterfall
            True, #plottime
            True, #plotconst
        )

        self.connect(self.src, (self.add, 0))
        self.connect(self.nse, (self.add, 1))
        self.connect(self.add, self.thr, self.snk)

        # Tell the sink we want it displayed
        self.pyobj = sip.wrapinstance(self.snk.pyqwidget(), Qt.QWidget)
        self.pyobj.show()

def main():
    tb = my_tb()
    tb.start()
    tb.qapp.exec_()

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        pass

Lines 2 - 8 just set up the environment and modules, including the PyQT modules we require. Lines 10 - 12 simply set up the "my_tb" class as a GNU Radio top block class and initialize it.

Line 15 is the first deviation from normal GNU Radio flow graphs. This line gets a reference to the qApp, which is QT global application element. Right now, it is enough to know that we require it, but we won't do anything with it until later.

Line 18 just sets a variable for the FFT size. Lines 20 - 25 build the blocks of the flow graph, including the qtgui block. In this flow graph, we create a sine wave and add noise to it. The noise is to make sure the signal is constantly changing in the display as a simple sine wave looks like nothing changes between frames. There is a threshold block so the GUI is not trying to run at full speed, too. Finally, the sink is a complex qtgui sink with just the first two arguments given. As can be seen in the class constructor listed above, all of the other arguments have defaults, so we will use these for now.

Lines 37 - 39 just connect all of the blocks. Like any other block, the qtgui sink is added as just another connection.

Lines 42 and 43 are specific to making the qtgui blocks visible. We have to use the "show" operation on the qtgui block, but first, we have to convert it to a Python object. Line 42 does the conversion by using the SIP wrapper interface to go from a PyObject in C++ to a native Python object as a QWidget. The "pyqwidget" method of the qtgui sink passes the proper object pointer back to allow this handling. Now, the Python object has the "show()" method required to display the sink that is called in Line 43. Without this, the code would run, but we would see nothing displayed.

The final bit of tickery that deviates from a normal flow graph is how we tell the class to run. Line 46 instantiates an object of our "my_tb" class. Instead of calling "run()" on this object, though, we only call "start()" in line 47, which is non-blocking (run() performs both a start() and wait() call). We then tell the qApp that we got a reference to in Line 15 to execute with the "exec_()" method on line 48. This starts the QT runtime engine to handle the display and controls of the QT widgets. Now, when we run the program, we should see a QT application with a widget displaying the frequency domain. [SHOWPCITURE]

The big take-aways message here is the relatively minor changes required to make a qtgui application from a flow graph.

  • Get a reference to the qApp pointer
  • Build and connect the qtgui sink
  • Use sip to get a wrapped instance in Python of the block and call "show()" on it
  • Use the "start()" method of the top block class followed by a call to "exec_()" on the qApp to run the QT application