# Improving spectrogram resolution in Python?

I'm using the specgram() function in matplotlib to generate spectrograms of speech wave files in Python, but the output is always of vastly inferior quality to what my normal transcription software, Praat, can generate. For example, the following call:

specgram(
Fs=framerate,
cmap=cm.gray_r,
)


Generates this:

While Praat, working on the same audio sample with the following settings:

• View range: 0-8000Hz
• Window length: 0.005s
• Dynamic range: 70dB
• Time steps: 1000
• Frequency steps: 250
• Window shape: Gaussian

Generates this:

What am I doing wrong? I've tried fiddling with all the specgram() parameters, but nothing seems to improve the resolution. I have virtually no experience with FFTs.

• Could you provide the example of the matplotlib.specgram parameter configurations you have tried? You give a very specific example of the parameters for Praat but don't show the same configuration for matplotlib.specgram? – Christopher Felton Feb 29 '12 at 3:26

Here are the matplotlib.specgram parameters

matplotlib.mlab.specgram(x,
NFFT=256,
Fs=2,
detrend=<function detrend_none at 0x1dd6410>,
window=<function window_hanning at 0x1e0b1b8>,
noverlap=128,
sides='default',
scale_by_freq=None)


The parameters provided in the question description need to be converted to comparable mpl.specgram parameters. The following is an example of the mapping:

View range: 0-8000Hz            Fs=16000
Window length: 0.005s           NFFT = int(Fs*0.005) = 80
noverlap = int(Fs*0.0025) = 40
Dynamic range: 70dB             n/a
Time steps: 1000                n/a
Frequency steps: 250
Window shape: Gaussian          default window is hanning change to gaussian


If you use 8ms you will get a power of 2 FFT (128). The following is the description of the Praat settings from their website

View range (Hz) : the range of frequencies to display. The standard is 0 Hz at the bottom and 5000 Hz at the top. If this maximum frequency is higher than the Nyquist frequency of the Sound (which is half its sampling frequency), some values in the spectrogram will be zero, and the higher frequencies will be drawn in white. You can see this if you record a Sound at 44100 Hz and set the view range from 0 Hz to 25000 Hz.

Window length : the duration of the analysis window. If this is 0.005 seconds (the standard), Praat uses for each frame the part of the sound that lies between 0.0025 seconds before and 0.0025 seconds after the centre of that frame (for Gaussian windows, Praat actually uses a bit more than that). The window length determines the bandwidth of the spectral analysis, i.e. the width of the horizontal line in the spectrogram of a pure sine wave (see below). For a Gaussian window, the -3 dB bandwidth is 2*sqrt(6*ln(2))/(π*Window length), or 1.2982804 / Window length. To get a broad-band' spectrogram (bandwidth 260 Hz), keep the standard window length of 5 ms; to get anarrow-band' spectrogram (bandwidth 43 Hz), set it to 30 ms (0.03 seconds). The other window shapes give slightly different values.

Dynamic range (dB) : All values that are more than Dynamic range dB below the maximum (perhaps after dynamic compression, see Advanced spectrogram settings...) will be drawn in white. Values in-between have appropriate shades of grey. Thus, if the highest peak in the spectrogram has a height of 30 dB/Hz, and the dynamic range is 50 dB (which is the standard value), then values below -20 dB/Hz will be drawn in white, and values between -20 dB/Hz and 30 dB/Hz will be drawn in various shades of grey.

The OP's question might be concerning the contrast difference between the Praat specgram and the mpl (matplotlib) specgram. Praat has a Dynamic Range setting which affects the contrast. The mpl function does not have a similar setting/parameter. The mpl.specgram does return the 2D array of power levels (the spectrogram) the dynamic range could be applied to the return array and re-plotted.

The following is a code snippet to create the plots below. The example is ~1m15s speech with a chirp from 20Hz-8000Hz.

import numpy
import pylab
import wave
import array
pylab.close('all')
w1 = wave.open('example_no_noise.wav')
w2 = wave.open('example_noise.wav')
# hmmm, probably a better way to do this, scipy.io function?
x1 = x1 / (2.**(16-1))  # normalize
x2 = x2 / (2.**(16-1))  # normalize
Fs = 16000.
NFFT = int(Fs*0.005)  # 5ms window
noverlap = int(Fs*0.0025)
pylab.figure(1)
pylab.specgram(x1, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
cmap=pylab.get_cmap('Greys'))
pylab.title('Full 1m15s example min noise')
pylab.figure(2)
pylab.specgram(x2, NFFT=NFFT, Fs=Fs, noverlap=noverlap,
cmap=pylab.get_cmap('Greys'))
pylab.title('Full 1m15s example more noise')
pylab.figure(3); n=2100*176;
pylab.specgram(x2[n:n+256*256], NFFT=NFFT, Fs=Fs, noverlap=noverlap,
cmap=pylab.get_cmap('Greys'))
pylab.title('Full ~4s example min noise')
pylab.figure(4); pylab.plot(x1[n:n+256*256])


• Thinking about this a little more, the Praat "Dynamic Range" parameter might be the main factor for the difference in how the plots look. The Praat "Dynamic Range" might be limiting the range (compressing) so that you get a greater contrast in the plot. BOMK MPL doesn't have a similar feature but one could be added. – Christopher Felton Feb 29 '12 at 4:03

It seems to be a time/frequency resolution problem. Your Praat plot has a worse frequency resolution (you cannot even clearly see the harmonics) and a better time resolution. Try reducing the window size (NFFT) to 16000 x 0.05 = 80 samples. I'd suggest using a bigger power of 2 in pad_to (128 or 256).