0

I'm able to capture signals from a RTL-SDR by using the following:

from rtlsdr import *

i = 0
signals = []
sdr = RtlSdr()

sdr.sample_rate = 2.8e6     
sdr.center_freq = 434.42e6
sdr.gain = 25

while True:
    samples = sdr.read_samples(1024*1024)
    decibel = 10*log10(var(samples))
    if decibel >= -10:
        signals.append(samples)
        i += 1
    if i == 2:
        break

If I plot the signals using Matplotlib and Seaborn, they look something like this: enter image description here

Now, what I would need is to get the coordinates of all peaks above a certain power level, e.g., -20.

I found a rather promising listing of the various Python options. However, all those examples use a simple Numpy array which the different algorithms can easily work with.

This was the best attempt (because my guess is that I get a complex signal from the RTL-SDR and have to "transform" it to an array with real values?):

import numpy as np
import peakutils.peak

real_sig = np.real(signals[0])
indexes = peakutils.peak.indexes(real_sig, thres=-20.0/max(real_sig), min_dist=1)
print("Peaks are: %s" % (indexes))

With these few lines added to the script above I do get some output, but, first, there are way too many values for the just five peaks above power level -20. And, second, the values don't make much sense in the given context.

enter image description here

So, what do I need to change to get meaningful results like "Peak 1 is at 433.22 MHz"?

Ideally, I should get coordinates like Peak 1: X = 433.22, Y = -18.0, but I guess I can figure that out myself once I know how to get the correct X-values.

Drise
  • 4,310
  • 5
  • 41
  • 66
ci7i2en4
  • 834
  • 1
  • 13
  • 27
  • 1
    I don't know the **peakutils** library, but something appears strange in your code : the `signals` array (and thus `real_sig` also) is not expressed in decibels, while you use a decibel threshold in `peakutils.peak.indexes`is. Shouldn't you convert the data first ? – sciroccorics Mar 19 '18 at 09:06
  • I should add that I'm looking for ANY solution to get the proper peak coordinates for my signals. While I would prefer the algorithm from the peakutils library, it doesn't necessarily have to be that one ... – ci7i2en4 Mar 19 '18 at 10:33
  • I guess you didn't understand my first comment. Here is the docstring explaining the role of the `thres` argument : _"Threshold for detecting a peak/valley. The absolute value of the intensity must be above this value"_ So obviously the data and the threshold need to be expressed in the same unit, which is not the case in your code. – sciroccorics Mar 19 '18 at 10:58
  • I understood your comment exactly like that but I have no idea what the complex data of the signal looks like and what is included (actually, I thought it would include power level and frequency of any point of the signal within the specified frequency range). Or how I can transform my input (signals[0]) to anything that meets the requirements of the peak function ... ??? – ci7i2en4 Mar 19 '18 at 11:23

4 Answers4

1

You are missing several steps.

You first need to: Pick a segment of complex IQ data from the RTL-SDR of length N (you may need to convert the raw IQ samples from unsigned 8-bit to signed floating point), window it (von Hann or Hamming, etc.), convert it to the frequency domain via an FFT of length N, convert the FFT result to log magnitude, and label the FFT log magnitude result bins (e.g. array elements) by frequency, which will be roughly

frequency(i) = sdr.center_freq + i * sdr.sample_rate / N

for bins 0 to N/2

frequency(i) = sdr.center_freq - (N - i) * sdr.sample_rate / N

for bins N/2 to N-1

Then you can search along that log magnitude array for peaks, and apply the frequency label to the peaks found.

Added: You can't get frequency domain information (as in frequency peaks) directly from an RTL-SDR. The peaks you want are not there. An RTL-SDR outputs raw complex/IQ time domain samples, not frequency domain data. So first you need to look up, study, and understand the difference between the two. Then you might understand why an FFT (or DFT, or Goertzels, or wavelets, etc.) is needed to do the conversion.

hotpaw2
  • 70,107
  • 14
  • 90
  • 153
  • Honestly, I don't understand at all what you want me to do ... length N: isn't that my sdr.sample_rate? I have already tried: real_sig = np.fft.fft(signals[0], n=sdr.sample_rate) decibel = 10*log10(var(real_sig)) plt.plot(decibel) But that doesn't work as the length has to be an integer value. And even if I change it to an int value, I get an empty plot. – ci7i2en4 Mar 19 '18 at 18:13
  • I found this, which sounds similar to what you wrote: https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.hamming.html But I don't understand what that does, why those steps are necessary, and what the values mean (or how they where chosen). That's what I would need somebody to explain to me. – ci7i2en4 Mar 19 '18 at 18:25
  • Way too broad a question. Break what you want/need down into several smaller questions. Otherwise the answer is a book chapter (does not fit here!). I just gave you part of the outline. – hotpaw2 Mar 19 '18 at 19:13
  • N is not the sample rate! N needs to be chosen based on the time-frequency resolution desired. – hotpaw2 Mar 19 '18 at 19:14
  • The first thing I would need to understand is why I do have to do something with the signal I receive from the RTL-SDR at all. Why can't I just use the received signal as input for peakutils.peak.indexes() (or any other library/method) and get the correct five y-coordinates of the peaks? The second thing that I absolutely don't understand is why this strange FFT plot should be of any use to me? How am I supposed to detect the peaks in the original signal (see the PSD plot) in this FFT plot? – ci7i2en4 Mar 19 '18 at 19:21
  • See additions to my answer. – hotpaw2 Mar 19 '18 at 21:03
1

Something similar to:

get your signals array of y value relative power.

sort([x for x in signals > -20])
sort[:i]

for the i fist peaks.

For frequency range:

frequency_peaks = [ frequencyspectrum[a] for a in signals[:i]]

But really you should be using Fourrier transform and binning (@hotpaw2's answer): enter image description here

numpy.fft will do the trick:

https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.fft.html

Also see:

https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem

for background theory.

Benedict K.
  • 836
  • 2
  • 8
  • 22
  • Thank you, you seem to be confirming what hotpaw2 is already trying to tell me. And I have already attempted to use np.fft on my signal (please see my second posting), without getting to the point where I could retrieve peak coordinates. I'm still stuck at understanding what use that FFT plot should be to me and how I can get to retrieving peak coordinates from there ... – ci7i2en4 Mar 19 '18 at 19:38
0

I have now tried the following code (inspired by the answer from hotpaw2 and by what I found on Google):

from numpy.fft import fft, fftshift
window = np.hamming(sdr.sample_rate/1e6+1)

plt.figure()
A = fft(window, 2048) / 25.5 # what do these values mean?
mag = np.abs(fftshift(A))
freq = np.linspace(sdr.center_freq/1e6-(sdr.sample_rate/1e6)/2, sdr.center_freq/1e6+(sdr.sample_rate/1e6)/2, len(A))
response = 20 * np.log10(mag)
#response = np.clip(response, -100, 100)
plt.plot(freq, response)
plt.title("Frequency response of Hamming window")
plt.ylabel("Magnitude [dB]")
plt.xlabel("Normalized frequency [cycles per sample]")
plt.axis("tight")
plt.show()

Source: https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.hamming.html

This results in the following (absolutely useless) signal plot: enter image description here I need a plot like the PSD in my original posting, in which I can then detect the peaks.

Can someone, please, explain to my why I'm supposed to do all this Hamming/FFT stuff?

All I want is a representation of my signal (as received by the RTL-SDR) that the peakutils.peak.indexes() method accepts and returns the correct peaks from.

ci7i2en4
  • 834
  • 1
  • 13
  • 27
  • 1
    Why you need to do all this fft stuff? Short answer, the signal you're getting from the SDR is time domain (signal amplitude), and you need to convert it to the frequency domain (PSD), which is typically done with the FFT. The example plot you showed is probably doing that somewhere in the call. Long answer, take a class on signal processing / communications systems. – user2699 Mar 19 '18 at 19:52
  • Okay, that only leaves me with the part where I have to figure out how to properly use the FFT on my signal so that in the end I have the correct input for peakutils.peak.indexes(). I'll will try that myself now ... We'll see if I can manage that. – ci7i2en4 Mar 19 '18 at 20:00
  • Scipy has some functions that can give better results, and there's good examples included. https://docs.scipy.org/doc/scipy-0.14.0/reference/signal.html#spectral-analysis – user2699 Mar 19 '18 at 20:20
  • 1
    Thanks. While it's clear to my now that (and why) I have to convert one representation of my signal to another, I'm not quite sure yet what this Hamming window is and how it is related to the FFT. But before I continue programming, I'm gonna do some research on the theoretical concepts first ... – ci7i2en4 Mar 19 '18 at 20:43
0

I believe I'm starting to understand what all of you were trying to tell me ...

The goal is still to reproduce a chart like the following (which was created using Matplotlib's plt.psd() method): enter image description here Now, I have been able to come up with three different code segments, each of which got me pretty close, but none is perfect yet:

# Scipy version 1
from scipy.signal import welch
sample_freq, power = welch(signal[0], sdr.sample_rate, window="hamming")
plt.figure(figsize=(9.84, 3.94))
plt.semilogy(sample_freq, power)
plt.xlabel("Frequency (MHz)")
plt.ylabel("Relative power (dB)")
plt.show()

The above one produces the following plot: enter image description here While the figure doesn't look too bad, I have absolutely no idea why a part of the center peak is missing and where that strange line that connects both ends of the figure comes from. Also, I couldn't figure out how to display the proper values for power level and frequencies.

My next attempt:

# Scipy version 2
from scipy.fftpack import fft, fftfreq
window = np.hamming(len(signal[0]))
sig_fft = fft(signal[0])
power = 20*np.log10(np.abs(sig_fft)*window)
sample_freq = fftfreq(signal[0].size, sdr.sample_rate/signal[0].size)
plt.figure(figsize=(9.84, 3.94))
plt.plot(sample_freq, power)
plt.xlabel("Frequency (MHz)")
plt.ylabel("Relative power (dB)")
plt.show()

This gets me the following result: enter image description here Clearly, I'm on the right track again but I have no idea how to apply the Hamming window in that version of the code (obviously, I did it the wrong way). And like in the previous attempt I couldn't figure out how to display the correct frequencies in the x-axis.

My last attempt uses Numpy instead of Scipy:

# Numpy version
window = np.hamming(len(signal[0]))
sig_fft = np.fft.fft(signal[0])
sample_freq = np.fft.fftfreq(signal[0].size, sdr.sample_rate/signal[0].size)
power = 20*np.log10(np.abs(sig_fft))
plt.figure(figsize=(9.84, 3.94))
plt.plot(sample_freq, power)
plt.xlabel("Frequency (MHz)")
plt.ylabel("Relative power (dB)")
plt.show()

And the result is: enter image description here I'd say this one probably comes closest to what I want (the Scipy version 2 would look the same without the wrongly applied hamming window), if it weren't for all that noise. Again, no idea how to apply the Hamming window to get rid of the noise.

My questions are:

  • How do I apply the Hamming window in the second and third code segment?
  • How do I get the proper frequencies (values from 434.42-1.4 to 434.42+1.4) on the x-axis?
  • Why is the signal displayed on a significantly higher power level in all three plots than it is in the original plot (created by plt.psd())?
ci7i2en4
  • 834
  • 1
  • 13
  • 27
  • You probably need to match all the parameters to those used by `plt.psd` (which shouldn't be too difficult with a quick look at the documentation. Worst case you may need to look over the source for axes, but it's still python code). In any case, the answers section isn't the place for this. Edit the original question, or post a new one. – user2699 Mar 22 '18 at 00:50