Thursday
Sep302010

Using QT Sinks

GNU Radio provides a couple of GUI interfaces using wxPython or QT. This example focuses just on the QT interface, gr-qtgui. Through a few small tweaks to an existing program, we can start to add QT sinks to the flow graph to start to visualize different signals, which can be especially useful when debugging and analyzing signals. The QT framework allows us to go even farther, though, and build entire applications in QT (using PyQT, for example). Also, the QT sink is written in C++ and wrapped into Python with SWIG, and so it should be possible to build C++-only applications using QT, although this hasn't been tested, yet.

In this example, though, we're just going to focus on inserting a QT sink into an existing flow graph to provide the basics of what is required to get a working GUI. We'll start with a simple application that adds two sine waves together with some additive Gaussian noise and outputs to a sink (I'm using a null sink here because we really don't care about the output until we add the GUI).

  1. #!/usr/bin/env python
  2. from gnuradio import gr
  3. class qt_top_block(gr.top_block):
  4.     def __init__(self):
  5.         gr.top_block.__init__(self)
  6.         self.src0 = gr.sig_source_c(Rs, gr.GR_SIN_WAVE, 0.01, 1)
  7.         self.src1 = gr.sig_source_c(Rs, gr.GR_SIN_WAVE, 0.10, 0.1)
  8.         self.noise = gr.noise_source_c(gr.GR_GAUSSIAN, 0.1)
  9.         self.add  = gr.add_cc()
  10.         self.snk  = gr.null_sink(gr.sizeof_gr_complex)
  11.         self.connect(self.src0, (self.add,0))
  12.         self.connect(self.src1, (self.add,1))
  13.         self.connect(self.noise, (self.add,2))
  14.         self.connect(self.add, self.snk)
  15. def main():
  16.     mytb = qt_top_block()
  17.     mytb.start()
  18.     mytb.wait()
  19. if __name__ == "__main__":
  20.     main()

This should look like a regular GNU Radio application right now. What we want to do, though, is add a QT sink. First, there's the boiler-plate stuff we have to insert into the project. We need to associate the class with the global qApp, which is done by adding a line in the constructor like: 

self.qapp = QtGui.QApplication(sys.argv)

Obviously, to access this information, we need to import QtGui and sys into the Python project. In the end, there are a few new modules we have to add. We'll get to those later.

The qApp let's us talk to the QT runtime engine, so we're going to have to make use of this later. For now, that's enough to make the qt_top_block class a QT application. Next, we need to build a QT sink. The prototype for the QT sink constructor looks like:

qtgui.sink_c(int fftsize, int wintype, double fc, double bandwidth, string name, bool plotfreq, bool plotwaterfall, bool plottime, bool plotconst, QWidget *parent);

 In this constructor, we can specify the size of the FFT points (e.g., 1024, 2048, etc.), the window type, which we can get from gr.firdes, and the center frequency and bandwidth to set up the axis. The "name" parameters is the title of the window that will appear in the title bar of the display. The next five arguments are all Boolean values that default to True and are used to determine whether or not to display a particular type of plot. The types of plots, in order, are the FFT, waterfall (spectrogram), 3D waterfall, time, and constellation plots. The final parameter is an optional parent, which defaults to None. When working a QT sink into a larger QT project, you can pass the parent information in this way.

Ok, so now we have a way to create a QT sink object. It acts like any other GNU Radio sink as far as the flow graph is concerned. We just connect a signal to it. Unfortunately, there's a bit of ugliness left to work through in order to expose the QT GUI -- we have to tell it to show itself. This is done by getting a QWidget instance of the object in Python and calling the show() function on this. 

Putting all of this knowledge together leads to the code below (which you can download here: qt_basics.py). There are a few new lines that I will explain afterwards.

  1. #!/usr/bin/env python
  2. from gnuradio import gr
  3. from gnuradio.qtgui import qtgui
  4. from PyQt4 import QtGui
  5. import sys, sip
  6. class qt_top_block(gr.top_block):
  7.     def __init__(self):
  8.         gr.top_block.__init__(self)
  9.         Rs = 1
  10.         fftsize = 2048
  11.         self.qapp = QtGui.QApplication(sys.argv)
  12.         self.src0 = gr.sig_source_c(Rs, gr.GR_SIN_WAVE, 0.01, 1)
  13.         self.src1 = gr.sig_source_c(Rs, gr.GR_SIN_WAVE, 0.10, 0.1)
  14.         self.noise = gr.noise_source_c(gr.GR_GAUSSIAN, 0.1)
  15.         self.add  = gr.add_cc()
  16.         self.thr  = gr.throttle(gr.sizeof_gr_complex, 10e5)
  17.         self.snk  = gr.null_sink(gr.sizeof_gr_complex)
  18.         self.qtsnk  = qtgui.sink_c(fftsize, gr.firdes.WIN_BLACKMAN_hARRIS,
  19.                                    0, Rs,
  20.                                    "Complex Signal Example",
  21.                                    True, True, False, True, False)
  22.         self.connect(self.src0, (self.add,0))
  23.         self.connect(self.src1, (self.add,1))
  24.         self.connect(self.noise, (self.add,2))
  25.         self.connect(self.add, self.snk)
  26.         self.connect(self.add, self.thr, self.qtsnk)
  27.         pyWin = sip.wrapinstance(self.qtsnk.pyqwidget(), QtGui.QWidget)
  28.         pyWin.show()
  29. def main():
  30.     mytb = qt_top_block()
  31.     mytb.start()
  32.     mytb.qapp.exec_()
  33.     mytb.stop()
  34. if __name__ == "__main__":
  35.     main()

Now, instead of just importing the gr namespace, lines 3-5 import the GNU Radio qtgui module, QtGui from PyQT, and sys and sip. In line 11, we get our reference to the qApp, which takes in a set of arguments that we get from sys.argv. We tend to ignore this, though, although advanced users can manipulate the system through this interface. Lines 18 - 21 call the qtgui constructor for a complex sink (sink_c). Following the prototype above, we create a sink where the FFT size is initially 2048, we use a Blackman-harris window, set the center frequency to 0, and the bandwidth is just 1.  We then give the window a title, "Complex Signal Example," and turn on the FFT, waterfall, and time plots.

Line 26 connects the sink to the graph, so the output of the adder will be displayed. Notice that I also put the signal through a throttle. I did this to control the speed of the graph. Without anything external to clock the samples such as an audio device or a USRP, the graph would simply run as fast as possible. The gr_throttle allows us to set a simulated sample rate so that when we graph it, we can actually see what's going on. Try removing the throttle from the graph, but be prepared for the application to take over your computer :)

Lines 27 and 28 are what I meant about getting a QWidget instance and calling the show() function. Using sip, which is a PyQT program for getting QT stuff into Python, allows us to get a Python version of a QWidget so that we can actually call the show() function on a widget. There doesn't seem to be a good way to do this otherwise, so I exposed the pyqwidget function in the qtgui sink classes. The problem comes down to who owns the qApp and the widgets in order to display them. This is uglier than I wanted it, but it's just an odd two lines of code, which was better than some alternatives that were tossed around...

The final change required is to call the qApp's executor function. This is a blocking loop, so we can first start the flow graph as a non-blocking call with mytb.start(). Instead of calling mytb.wait() like we originally did, we now let the QT framework lifecycle take over in line 32 with mytb.qapp.exec_(). Make sure to call mytb.stop() to properly shut down the flow graph and delete the objects in the proper order.

When you run this program, you will see a display that looks like the following. The display starts by showing the Frequency plot (if it's activated, of course).

 

 The next tab shows the waterfall display (or spectrogram) that starts at the bottom of the screen and moves up. This image was captured after a few seconds had passed. I also hit the "Auto Scale" button in order to set the dynamic range of the color bar to best represent the range of the signal (in this case from 0 dB to about -64 dB).

The last tab in this case shows us the time waveform, showing a trace for both the real and imaginary parts of the signal.

 Notice that the display has a few different controls. For one, while we set the initial FFT size to 2048, this can be changed in real-time by clicking the "FFT Size" drop-down box and selecting from a handful of preset sizes (all powers of 2). This will adjust the resolution in the frequency and waterfall plots and will increase the number of samples displayed in the time domain plot.

We can also tell the display to show the frequency domain relative to the RF frequencies. This check box really just uses the fc argument we passed to the constructor to reset the x-axis of the frequency and waterfall plots.

Another drop-down box exposed for us to change in the FFT window. While we initialized with the Blackman-harris window, this gives us the option of whatever windows are available in GNU Radio, which currently includes Hamming, Hann, Blackman, rectangular, Kaiser, and Blackman-harris.

The frequency domain plot has a few of its own interfaces to work with. First, you can set the min and max holds and reset them as you want to. The Average box tells the GUI to average a certain number of plots, which can help smooth out noise. Also, there are a few lines drawn on this display, including a line (green) showing the value of the maximum signal in the current display as well as a line (cyan) of the average noise.

The waterfall plot also has a few things to play with. As I already mentioned, you can auto scale the intensity bar range, or you can manually adjust using the two wheels. The top wheel sets the upper bound of the intensity plot while the bottom wheel sets the lower bound. Furthermore, you can change the color scheme with the "Intensity Display" drop-down box, which includes a "User Defined" selection that lets the user set the upper and lower colors for the plots and then interpolates between them.

One final note about the plots is that each of the figures allows you to position the cursor anywhere in the plot and it will tell you the current values. By left-clicking with the mouse and dragging a box, you can zoom in to any region, too. You can zoom in about 10 times. A right-click will back up a single zoom step.

So that's the basics of how to incorporate a QT gui into a flow graph and how to use the figures.

 

Sunday
Sep122010

Basic Filtering

This example shows the basics of how to use a filter in GNU Radio. First, we have to generate a set of filter taps, which can be a vector of either real complex values. While you are welcome to generate the taps any way you would like, GNU Radio provides a few generating functions to make this easier.

 

Basic Filters

The first example shows the use of the "firdes" utility, which is used to generate windowed FIR filters. Here, I am creating a simple low pass filter (LPF). There are generally two versions of each type of filter. Those with the "_2" extension provide a means of specifying the out-of-band (or stopband) attenuation (in dB). Generally, you want to use this version as it costs you almost nothing but gives you an extra parameter to control.

Example code: filter_basics.py

In this example, we create a low pass filter using the function:

    lpf_taps = gr.firdes.low_pass_2(1, 1, 0.2, 0.1, 60)

We are specifying, in order, the filter gain, sample rate, center of the transition band, the transition band width, and the stopband attenuation (in dB). The center of the transition band specifies the point where the rolloff is 6 dB down from the passband. This value and the transition band are set relative to the sample rate. Since the sample rate here is set to 1.0, we must also specify normalized bandwidths. We can also use a real sample rate here and scale the widths appropriately in terms of Hz.

The filter_basics.py example shows the use of two filters: gr.fir_filter_ccc and gr.fft_filter_ccc. These take the same arguments and both filter a signal with the same filter taps. The difference is in how they do it. The version with "fir_" performs a straight-forward implementation of an FIR filter by performing the convolution in time. The "fft_" version, however, performs the fast-convolution, which means that it takes an FFT and performs the convolution as a multiplication in the frequency domain. The results are then passed through an IFFT to get the samples back into the time domain. 

For small filters, which is somewhere around 30-taps and fewer, the FIR filter version performs faster. However, the fast-convolution takes advantage of faster multiplies and the inexpensive FFT and IFFT operations. For larger filters (greater than 30), these operations perform better than the FIR filter.

The figure below shows the output of filtering a noise signal with both an FIR and FFT version of the same filter. While this does not show off the difference in speed, it shows that the output is the same. The takeaway: use the FFT filter for all but the smallest of filters. Then again, at small filter sizes, the amount of processing time is very small, and so the cost of either filter is probably negligible. I will try to post some data on the filter speeds at some point.

 Windowed and Parks-McClellen Filters

The second example shows the use of a second GNU Radio filter design utility called "optfir." Unlike the firdes, which is a part of the "gr" namespace, the optfir utility comes packaged in the "blks2" namespace. This filter designer uses the Parks-McClellen algorithm for designing filters. Unlike the firdes windowed filters, this utility gives you another degree of freedom when designing filters: the passband ripple (in dB).

You have to watch out for this utility, though, especially if you are comfortable with using firdes. In the firdes utility, you specify the center of the transition band, which is 0.5 in linear amplitude or -6 dB in log power scale. You also specify the transition band, which is the width from the cutoff to the stopband. On the other hand, in the optfir utility, you will specify the end of the passband(s) and start of the stopband(s) (plural if you are designing bandpass filters). The end of the passband is the point when the filter roll-off begins, not the 3-dB or 6-dB point, and the start of the stopband is where the first null occurs. Keep in mind, though, that these values are going to be approximate since the Parks-McClellen algorithm is an optimization technique that attempts to fit a filter to the desired specs.

In the following example, I tried to design a LPF with both the firdes and optfir filters that were approximately the same filter, by which I mean having the same passband and stopband. What is different is that the specified passband ripple from optfir gives me a much lower ripple for the same number of filter taps. If we specified a ripple of, say 0.03 dB, we can reduce the filter size a bit farther. The point here being that for approximately equivalent filters, you are generally going to get a smaller filter using the Parks-McClellen.

Filter example with optfir: filter_optfir.py

Impulse Response of Both Filters 

Zoomed-in Passband Response

Impulse Response Showing Almost Identical Filters

A few good explanations:

Julius O. Smith: 

http://www.dsprelated.com/dspbooks/sasp/FIR_Digital_Filter_Design.html

http://www.dsprelated.com/dspbooks/sasp/Window_Method.html

 

Other:

http://www.dspguru.com/dsp/faqs/fir/design

 

Sunday
Sep122010

Simple Signals

I just wanted to get a simple application programmed up and available for people to see the basics of GNU Radio. In this equivalent of a "hello world," I'm simply creating a couple of signal sources and outputting them to a vector sink. To display the results, I'm using the Python package Matplotlib, which can be easily installed in almost any operating system.

The output is displayed below showing the resulting signal in both time and frequency.

Code: simple.py