miek / inspectrum

Radio signal analyser
GNU General Public License v3.0
2.04k stars 263 forks source link

Improve spectrogram time alignment #227

Closed daniestevez closed 9 months ago

daniestevez commented 9 months ago

Currently, SpectrogramPlot::getLine() uses the sample parameter to determine the index of the first sample that is used to calculate the FFT for a particular spectrogram line. Since the FFT is in some sense an average of fftSize samples, this causes features (such as the start and end of packet bursts) to appear somewhat sooner in the spectrogram compared to their actual locations in the IQ file.

This improves the time alignment of the spectrogram plot by making sample refer to the middle sample of the FFT (so the samples used to compute the FFT start at sample - fftSize / 2.

For the beginning of the file we need to make an exception, because if we try to fetch samples before the beginning of the file, then inputSource->getSamples() returns nullptr. SpectrogramPlot::getLine() handles this gracefully, but an ugly red bar appears at the beginning of the file when the FFT size and zoom are large. To solve this, we cheat and force the FFT to start at the beginning of the file. To be more precise we could pad the beginning with zeros instead.


To test this, I'm using the following script to generate a 100 ms SigMF file at 1 Msps with an AWGN noise floor and a chunk of much stronger AWGN between 10 and 11 ms (which simulates a digital communications packet). A SigMF annotation marks this stronger noise chunk.

#!/usr/bin/env python3

import datetime

import numpy as np
import sigmf
from sigmf import SigMFFile

fs = 1000000
x = np.zeros(int(100e-3 * fs), 'complex64')
a = int(10e-3*fs)
b = int(11e-3*fs)
x[:] = 0.0001 * (np.random.randn(x.size) + 1j * np.random.randn(x.size))
x[a:b] = 0.1 * (np.random.randn(b - a) + 1j * np.random.randn(b - a))
data_file = 'test-file.sigmf-data'
x.tofile(data_file)

meta = SigMFFile(
    data_file=data_file,
    global_info = {
        SigMFFile.DATATYPE_KEY: sigmf.utils.get_data_type_str(x),
        SigMFFile.SAMPLE_RATE_KEY: fs,
        SigMFFile.VERSION_KEY: '1.0.0',
    }
)

f0 = 100_000_000
meta.add_capture(0, metadata={
    SigMFFile.FREQUENCY_KEY: f0,
    SigMFFile.DATETIME_KEY: datetime.datetime.utcnow().isoformat()+'Z',
})

meta.add_annotation(a, b-a, metadata = {
    SigMFFile.FLO_KEY: f0 - fs / 2,
    SigMFFile.FHI_KEY: f0 + fs / 2,
    SigMFFile.COMMENT_KEY: 'signal',
})

meta.tofile('test-file.sigmf-meta')

The main branch shows the following. The packet appears to begin before it should.

inspectrum-main

This PR shows the following. The start and end of the packet "bleed over" outside of the annotation box, but this is inevitable when using FFTs. I think that this is a better representation of the signal.

inspectrum-pr

miek commented 9 months ago

This is great, thanks! I've merged it manually with some whitespace fixes.

jacobagilbert commented 8 months ago

Already merged but wanted to say thanks @daniestevez 🚀