12

When I use the plt.specgram from matplotlib by using the following code, the spectrogram generated is correct

import matplotlib.pyplot as plt
from scipy import signal
from scipy.io import wavfile
import numpy as np

sample_rate, samples = wavfile.read('.\\Wav\\test.wav')

Pxx, freqs, bins, im = plt.specgram(samples[:,1], NFFT=1024, Fs=44100, noverlap=900)

spectrogram generated by using matplotlib

However, if I generate the spectrogram by using the example code given by in the scipy page with the following code, I get something like this:

import matplotlib.pyplot as plt
from scipy import signal
from scipy.io import wavfile
import numpy as np

sample_rate, samples = wavfile.read('.\\Wav\\test.wav')

frequencies, times, spectrogram = signal.spectrogram(samples[:,1],sample_rate,nfft=1024,noverlap=900, nperseg=1024)

plt.pcolormesh(times, frequencies, spectrogram)
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [sec]')

enter image description here

To debug what's going on, I tried using the Pxx, freqs, bins, generated by the first method, and then use the second method to plot out the data:

plt.pcolormesh(bins, freqs, Pxx)
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [sec]')

enter image description here

The graph generated is almost the same as the graph generated by the second method. So, it seems there is no problem with the scipy.signal.spectrogram after all. The problem is the way that we plot the graph. I wonder if plt.pcolormesh is the correct way to plot the spectrogram despite the fact that this method is suggested in the scipy document

A similar question has been asked here, but there's no solution to the question yet.

1
  • This is now a year old question, but the scipy documentation page on this is still unchanged. Commented Jun 19, 2019 at 9:46

3 Answers 3

18

The default scaling mode for specgram is 'dB' (from the specgram docs)

scale : [ ‘default’ | ‘linear’ | ‘dB’ ] The scaling of the values in the spec. ‘linear’ is no scaling. ‘dB’ returns the values in dB scale. When mode is ‘psd’, this is dB power (10 * log10). Otherwise this is dB amplitude (20 * log10). ‘default’ is ‘dB’ if mode is ‘psd’ or ‘magnitude’ and ‘linear’ otherwise. This must be ‘linear’ if mode is ‘angle’ or ‘phase’.

mode : [ ‘default’ | ‘psd’ | ‘magnitude’ | ‘angle’ | ‘phase’ ] What sort of spectrum to use. Default is ‘psd’, which takes the power spectral density. ‘complex’ returns the complex-valued frequency spectrum. ‘magnitude’ returns the magnitude spectrum. ‘angle’ returns the phase spectrum without unwrapping. ‘phase’ returns the phase spectrum with unwrapping.

To achieve similar results with pcolormesh you will need to scale the data equivalently.

plt.pcolormesh(times, frequencies, 10*np.log10(spectrogram))

I don't think the pcolormesh example is correct in its scaling. You can clearly see the carrier in the example but the added noise signal is not visible.

Sign up to request clarification or add additional context in comments.

2 Comments

Why are we multiplying by 10 here? I removed it and it still works.
It's just the way we represent power if expressed in decibels. See this article. Normally we use 10 as the scaling factor, but sometimes, when we talk about power transfer (or to be precise, we use quantities that need to be squared in order to obtain power) then we use the scaling factor of 20. In other words -- it works without multiplying by 10 but it does not represent a valid quantity.
1

You should use one of the non-linear colormap in your pcolormesh function.

Try to set norm=matplotlib.colors.LogNorm(vmin=np.amin(spectrogram), vmax=np.amax(spectrogram))

Or norm=matplotlib.colors.PowerNorm(gamma=0.5).

See https://matplotlib.org/stable/tutorials/colors/colormapnorms.html for more info.

Comments

-1

Use this instead:

plt.pcolormesh(times, frequencies, spectrogram, norm = matplotlib.colors.Normalize(0,1))

This would normalize the data before plotting so that you can visualize color properly. The documentation on matplotlib.colors.Colormap says 'Typically Colormap instances are used to convert data values (floats) from the interval [0, 1] to the RGBA color that the respective Colormap represents.' If your values are outside this range it would probably plot it to a dark color (I believe.)

1 Comment

I tried this, but it didn't work, it only plotted a yellow background rather than the purple one that I got initially. Could you post your entire spectrogram generating code?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.