How do I plot a spectrogram the same way that pylab's specgram() does?

danmcardle picture danmcardle · Apr 12, 2013 · Viewed 24.4k times · Source

In Pylab, the specgram() function creates a spectrogram for a given list of amplitudes and automatically creates a window for the spectrogram.

I would like to generate the spectrogram (instantaneous power is given by Pxx), modify it by running an edge detector on it, and then plot the result.

(Pxx, freqs, bins, im) = pylab.specgram( self.data, Fs=self.rate, ...... )

The problem is that whenever I try to plot the modified Pxx using imshow or even NonUniformImage, I run into the error message below.

/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/image.py:336: UserWarning: Images are not supported on non-linear axes. warnings.warn("Images are not supported on non-linear axes.")

For example, a part of the code I'm working on right is below.

    # how many instantaneous spectra did we calculate
    (numBins, numSpectra) = Pxx.shape

    # how many seconds in entire audio recording
    numSeconds = float(self.data.size) / self.rate


    ax = fig.add_subplot(212)
    im = NonUniformImage(ax, interpolation='bilinear')

    x = np.arange(0, numSpectra)
    y = np.arange(0, numBins)
    z = Pxx
    im.set_data(x, y, z)
    ax.images.append(im) 
    ax.set_xlim(0, numSpectra)
    ax.set_ylim(0, numBins)
    ax.set_yscale('symlog') # see http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.set_yscale
    ax.set_title('Spectrogram 2')

Actual Question

How do you plot image-like data with a logarithmic y axis with matplotlib/pylab?

Answer

Joe Kington picture Joe Kington · Apr 12, 2013

Use pcolor or pcolormesh. pcolormesh is much faster, but is limited to rectilinear grids, where as pcolor can handle arbitrary shaped cells. specgram uses pcolormesh, if I recall correctly. (It uses imshow.)

As a quick example:

import numpy as np
import matplotlib.pyplot as plt

z = np.random.random((11,11))
x, y = np.mgrid[:11, :11]

fig, ax = plt.subplots()
ax.set_yscale('symlog')
ax.pcolormesh(x, y, z)
plt.show()

enter image description here

The differences you're seeing are due to plotting the "raw" values that specgram returns. What specgram actually plots is a scaled version.

import matplotlib.pyplot as plt
import numpy as np

x = np.cumsum(np.random.random(1000) - 0.5)

fig, (ax1, ax2) = plt.subplots(nrows=2)
data, freqs, bins, im = ax1.specgram(x)
ax1.axis('tight')

# "specgram" actually plots 10 * log10(data)...
ax2.pcolormesh(bins, freqs, 10 * np.log10(data))
ax2.axis('tight')

plt.show()

enter image description here

Notice that when we plot things using pcolormesh, there's no interpolation. (That's part of the point of pcolormesh--it's just vector rectangles instead of an image.)

If you want things on a log scale, you can use pcolormesh with it:

import matplotlib.pyplot as plt
import numpy as np

x = np.cumsum(np.random.random(1000) - 0.5)

fig, (ax1, ax2) = plt.subplots(nrows=2)
data, freqs, bins, im = ax1.specgram(x)
ax1.axis('tight')

# We need to explictly set the linear threshold in this case...
# Ideally you should calculate this from your bin size...
ax2.set_yscale('symlog', linthreshy=0.01)

ax2.pcolormesh(bins, freqs, 10 * np.log10(data))
ax2.axis('tight')

plt.show()

enter image description here