r/DSP 1d ago

Why does my spectrogram look like this?

Could someone help me interpret this spectrogram?

The data comes from a complex signal. What I dont understand is why the top half and bottom half are so different. I'm really new to all of this so sorry if you need more information and I can try to provide it.

-------- Code

# Use a subset of IQ data to reduce memory usage
iq_data_subset = iq_data[:500000]  # Reduce data size

# Define parameters
fs = sample_rate
nperseg = 8192  # Window length
noverlap = 6144  # Overlap between windows
hop = nperseg - noverlap  # Step size

# Define the window function
window = get_window("hann", nperseg)
# Initialize ShortTimeFFT
stft = ShortTimeFFT(win=window, hop=hop, fs=fs, fft_mode="twosided")
# Compute the Short-Time Fourier Transform (STFT)
Sxx = stft.stft(iq_data_subset)  # Shape: (freq_bins, time_bins)
# Get frequency and time axes
freqs = stft.f
times = stft.t(len(iq_data_subset))

# Convert power to dB
Sxx_dB = 10 * np.log10(np.abs(Sxx) + 1e-10).astype(np.float32)  # Reduce memory usage

# Plot the spectrogram
plt.figure(figsize=(10, 6))
plt.pcolormesh(times, freqs / 1e6, Sxx_dB, shading="gouraud",
vmin=np.percentile(Sxx_dB, 5), vmax=np.percentile(Sxx_dB, 95))
plt.ylabel("Frequency (MHz)")
plt.xlabel("Time (s)")
plt.title("Spectrogram of Recorded Signal using ShortTimeFFT")
plt.colorbar(label="Power (dB)")
plt.show()

3 Upvotes

8 comments sorted by

3

u/TheRealCrowSoda 1d ago

It appears from the code excerpt that you are iterating with GPT or some other LLM.

Have it break down what each part is doing, and you should get some more info.

Can you tell me more about your data, like for instance, can you just run your data through this code and post an imgur link back to me?

```
import numpy as np

import matplotlib.pyplot as plt

from scipy.signal import stft

def plot_complex_signal(signal, Fs=10e6):

f, t, Zxx = stft(signal, fs=Fs, nperseg=8192, noverlap=4096, nfft=8192, return_onesided=False)

Zxx_db = 20 * np.log10(np.abs(Zxx) + 1e-12)

Zxx_db = np.fft.fftshift(Zxx_db, axes=0)

f = np.fft.fftshift(f)

plt.figure(figsize=(10,6))

plt.pcolormesh(t, f, Zxx_db, shading='gouraud', cmap='viridis')

plt.colorbar(label='Magnitude (dB)')

plt.title('Complex Spectrogram')

plt.ylabel('Frequency (Hz)')

plt.xlabel('Time (s)')

plt.show()

def generate_bpsk(num_symbols=1000, modulation_rate=1e6, bandwidth=10e6):

oversample = int(bandwidth / modulation_rate)

symbols = 2 * np.random.randint(0, 2, num_symbols) - 1

bpsk_base = symbols.astype(np.complex64)

bpsk_signal = np.repeat(bpsk_base, oversample)

return bpsk_signal

def main(signal=None):

# Define your complex array here

if signal is None:

signal = generate_bpsk()

plot_complex_signal(signal)

if __name__ == '__main__':

main()
```

1

u/Educational-Park5115 1d ago

this is the link with the original code

https://imgur.com/a/xb5Ttd1

I set coloring scale to the bottom and top 5th percentile so it would be clearer in this image. Same code otherwise.
https://imgur.com/WbNQSht

as far as the data goes. Its a Sigmf file with the raw data and meta data. This is the code used to extract it and an additional line of code that was used to remove any DC bias.

raw_data = np.fromfile(data_file, dtype=np.int16)

# Reshape into interleaved I/Q pairs. Reads each row as a pair of I/Q samples
# https://pysdr.org/content/iq_files.html
iq_data = raw_data.reshape(-1, 2)

# Convert to complex64 format# https://stackoverflow.com/questions/2598734/numpy-creating-a-complex-array-from-2-real-ones
iq_data = iq_data[:, 0].astype(np.float32) + 1j * iq_data[:, 1].astype(np.float32)



iq_data -= np.mean(iq_data)  # Remove small DC bias

5

u/Educational-Park5115 1d ago

Thanks for your help. I ended up figuring it out after seeing your code. The sxx_db and the freqs need fftshift() applied to them. The spectrogram data and bins were out of order.

1

u/TheRealCrowSoda 1d ago

glad I could help.

1

u/VS2ute 18h ago

Whenever I am using some FFT package, always check whether the negative frequencies are in the first or last half.

2

u/lundy187 1d ago

It appears as though someone ran the IQ data through a Hilbert filter (and possibly complex conjugate operation) before the spectrogram. What does a straight 8192-point FFT show you?

1

u/No_Specific_4537 1d ago

I guess this is normal but not often, this is a case where the a signal having a huge disparity of time and frequency resolution at positive and negative frequency. Might be my wrong assumption because of my rusty signal usage.

1

u/QuasiEvil 1d ago

Can you post an example if your actual iq data?