11

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.

Raven Cheuk
  • 2,903
  • 4
  • 27
  • 54

3 Answers3

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.

jaket
  • 9,140
  • 2
  • 25
  • 44
  • Why are we multiplying by 10 here? I removed it and it still works. – Ahmad Moussa Jun 19 '19 at 09:46
  • 1
    It's just the way we represent power if expressed in decibels. See [this article](https://en.wikipedia.org/wiki/Decibel#Power_quantities). 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. – LemurPwned Sep 03 '19 at 09:49
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.

Kong Chun Ho
  • 268
  • 4
  • 14
-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.)

  • 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? – Ahmad Moussa Jun 19 '19 at 09:45