12
\$\begingroup\$

I'm trying to speed up this code that loops over an entire spectrum range. It's using a Raspberry Pi, and a rtl-sdr dongle that covers up to around 1.7 GHz. My goal is to be sweep over that spectrum and gather data from it in a timely manner.

#!bin/usr/python

#gathers from the rtl library needed for this program
from rtlsdr import RtlSdr
import math
from pylab import *
import time
from scipy.fftpack import fft, ifft
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import os
import sys

os.system("sudo rmmod dvb_usb_rtl28xxu rtl2832")

#RtlSdr set up
##sdr = RtlSdr()
##
##
##num_samples = 4096
##
##freq_range_lo = 50e6     #experimental center freq for now
##freq_range_hi = 600e6  #1750e6
##freq_step = 1e6


##sample_rate = 2e6
##
###Set initial parameters for RTL-SDR Dongle
##sdr.sample_rate = sample_rate
##freq = 512e6
##sdr.center_freq = freq
##
##
##sdr.freq_correction = 102
##sdr.gain = 0



class Spec_Scan(object):

    samples = np.array([])
    samples_temp = np.array([])
    fft_data = np.array([])
    fft_data_temp = np.array([])

    app = QtGui.QApplication([])
    win = pg.GraphicsWindow(title="Basic plotting examples")
    win.resize(1000,600)
    win.setWindowTitle('pyqtgraph example: Plotting')


    def __init__(self, lo, hi, freq, step, samps):
        self.freq_range_lo = lo
        self.freq_range_hi = hi
        self.freq_step = step
        self.freq = freq
        self.num_samples = samps
        self.sample_rate = 2e6

        self.sdr = RtlSdr()
        self.sdr_default()


    def sdr_default(self):
        self.sdr.center_freq = self.freq
        self.sdr.freq_correction = 101
        self.sdr.gain = 0
        self.sdr.sample_rate = self.sample_rate



    def pyqt(self):
        global curve, p6
        # Enable antialiasing for prettier plots
        pg.setConfigOptions(antialias=True)

        self.win.nextRow()
        p6 = self.win.addPlot(title="Freq plot")

        self.curve = p6.plot(pen='y', clear=True)
        #p6.setRange(xRange = [0, (self.num_samples/2)*(1700e6/self.freq_step)], yRange = [-100,100])
        p6.setRange(xRange = [0, (self.num_samples/2)-48], yRange = [-100,100])






        print 'here'
        timer = pg.QtCore.QTimer()
        print'should begin update'
        timer.timeout.connect(self.update)
        timer.timeout.emit()
        print 'update should have finished'
        timer.start(2)

        if __name__ == '__main__':


            if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
                QtGui.QApplication.instance().exec_()



    def update(self):
        global curve, p6


        for self.freq in range(int(self.freq_range_lo), int(self.freq_range_hi), int(self.freq_step)):

            self.sdr.center_freq = self.freq
            self.samples = self.sdr.read_samples(self.num_samples)
            self.fft_data_temp = 20*np.log10(np.abs(np.fft.rfft(self.samples)))
            #self.fft_data_temp = self.fft_data_temp[0:2049]
##            self.fft_data_temp = self.fft_data_temp[1:(self.num_samples/2)]
##            self.fft_data_temp = 2*self.fft_data_temp[2::]
            self.fft_data = np.append(self.fft_data, self.fft_data_temp)
            self.fft_data_temp = 0

        #self.curve.setData(self.fft_data[1::], clear=True)
        self.fft_data = 0       

        print 'refresh'





def main():
    num_samples = 4096

    lo = 50e6     #experimental center freq for now
    hi = 1750e6
    step = 1e6
    freq = 425e6

    test = Spec_Scan(lo, hi, freq, step, num_samples)
##    test.pyqt()
    start = time.time()
    test.update()
    end = time.time()
    print str(end-start)


## Start Qt event loop unless running in interactive mode or using pyside.


main()

When the scan itself was timed, the result yielded around 197 seconds which is entirely too long. Is there a way I can speed up the spectrum scan?

\$\endgroup\$
  • \$\begingroup\$ That's quite an interesting application. One question, what's the purpose of your GUI ? From the code it seems that the spectrum isn't updated, making me wonder. \$\endgroup\$ – Loufylouf Jul 9 '15 at 19:57
9
\$\begingroup\$

I'll add my small contribution even though I'm not really a Python developer (hopefully some day). I've taken your update method and tested it on my computer (desktop, i3-4360@ 3.70GHz × 4 ), without the SDR bit. And my intuition was right!

Like in most languages allowing you to do it, don't ever do that:

self.fft_data = np.append(self.fft_data, self.fft_data_temp)

The reason is pretty simple: your array does not have a predefined size, so for each call to append, it has to re-allocate the memory to fit the new size. And that's terribly slow.

Let's talk numbers. Without this line of code, doing the FFT and converting the data to dB takes 300ms on my computer. Adding that line, it almost reaches 9s.

So now that we have found this, what to do about it for your application ? It seems that you would like to display the spectrum from 50MHz to 1750MHz, by decomposing it into signals with a 1MHz bandwidth. And for each slice, you generate 4096 samples. I don't know what kind of display you have, but displaying 4096 * 1700 FFT bins is gonna be difficult (especially in a 1000*600 window). You should restrict the size of the spectrum you want to display at once. And speaking just about bands, displaying the end of the HF band, the VHF band and part of the UHF band is not really gonna be interesting. Mostly because you're gonna have trouble finding an antenna that can cover all these bands simultaneously.

\$\endgroup\$
4
\$\begingroup\$

PEP394

First #!bin/usr/python should be #!/bin/usr/python2. This is as you should state the version of python that it works on.

  • python = Python2 and Python3
  • python2 = Python2
  • python3 = Python3

This is as Arch Linux changed python to python3, not python2.


PEP8

  • Two spaces between module level functions and classes.
  • One space between methods.
  • Classes are CamelCase. Not Capitalized_Words_With_Underscores.
  • Operators should have 1 space on both sides. a = b, 20 * 2.
    The exception to this is when you need to show precedence. 20*2 + 1.
    Personally I think using brackets looks nicer. (20 * 2) + 1
  • curve and p6 should be inside the class. Not global.
  • When = is not an assignment operator, used to pass kwargs, the operator should have no spaces around it. yRange=[-100,100].
  • When putting things in lists add a space after the comma. [-100, 100].
  • if __name__=='__main__' should stop the entire code from running.
    It should do this at module level.
  • Don't assign variables to the class when using a for loop. for self.freq in.
  • Limit lines to 79 characters.
    Comments should be a maximum of 72 however.

Personally, I think you have an excessive amount of calls to self. And reliance on globals. It is faster to access local variables. But not only is it faster it's safer.

It is hard for me to tell what I am allowed to change, as everything uses self.. And so I would change everything a lot, as a college, just to make a point that locals are good.


It would seem that you want to use a list comprehension in update. This is as it is a for loop that only seems to write to the variable self.fft_data.

def update(self):
    self.fft_data = np.array(
        20 * np.log10(np.abs(np.fft.rfft(
          self.sdr.read_samples(self.num_samples))))

        for _ in range(
            int(self.freq_range_lo),
            int(self.freq_range_hi),
            int(self.freq_step))
    )

If the code above returns slices you would want to use something like the following. It is in pure Python. This is as I can't find the documentation for numpy arrays.

def update(self):
    self.fft_data = []
    append_ = self.fft_data.append
    for _ in range(
            int(self.freq_range_lo),
            int(self.freq_range_hi),
            int(self.freq_step)):
        append_(
          20 * np.log10(np.abs(np.fft.rfft(
            self.sdr.read_samples(self.num_samples)))))

These are mostly for alternate viewpoints, as to be honest, I can't tell what you need the function for, other than for these lists. Again I blame the self.s.


Along with all the style changes from PEP8, you should use local variables. I really can't tell what I am allowed to change, and what is imperative to being in self.

\$\endgroup\$
  • \$\begingroup\$ Isn't it usually #!/usr/bin/python? \$\endgroup\$ – 200_success Jul 9 '15 at 20:05
3
\$\begingroup\$

I consider Code-Review as 'just before releasing or deploying the code', huge commented out blocks such as:

#RtlSdr set up
##sdr = RtlSdr()
##
##
##num_samples = 4096
##
##freq_range_lo = 50e6     #experimental center freq for now
##freq_range_hi = 600e6  #1750e6
##freq_step = 1e6


##sample_rate = 2e6
##
###Set initial parameters for RTL-SDR Dongle
##sdr.sample_rate = sample_rate
##freq = 512e6
##sdr.center_freq = freq
##
##
##sdr.freq_correction = 102
##sdr.gain = 0

should be deleted: version-control will take care of them.


You can save space:

samples = np.array([])
samples_temp = np.array([])
fft_data = np.array([])
fft_data_temp = np.array([])

can become:

samples = samples_temp = fft_data = fft_data_temp = np.array([])
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.