I have made an animated spectrogram this way:
import matplotlib.pyplot as plt from matplotlib import animation import librosa WINDOW = 100_000 JUMP = 1000 INTERVAL = 1 FILENAME = 'sound.wav' sound, rate = librosa.load(FILENAME, sr=None) fig = plt.figure() def animate(i): chunk = sound[i * JUMP: i * JUMP + WINDOW] _, _, _, im = plt.specgram(chunk, Fs=rate) return im, ani = animation.FuncAnimation(fig, animate, interval=INTERVAL, blit=True) plt.ion() plt.show()
It works, but in examples of using FuncAnimation I’ve seen, people don’t call the whole plotting function for every animation frame but update the data directly instead and it feels as if there are probably reasons (performance?) to do this. The examples gave some idea of how to do this for other images (ones where you were basically doing your own math to fill the array that is the image) but with something a little more blackbox-ish like pyplot.specgram my (maybe) hack was to just call the plotting function over and over. My question is can this be done in a way more like the example in the link above on the matplotlib site and, if yes, how?
Advertisement
Answer
The reason you don’t want to repeatedly call the plt.specgram
function inside the animation is indeed performance. After N cycles you have N images in your figure, which makes drawing more and more expensive.
Of course a possible solution is to remove the previous image in each iteration, e.g. by having a handle to it, (im.remove()
) or via the list of images (ax.images[0].remove()
).
However, you are right that the more desireable solution is to not recreate any image at all, but instead only change the image data.
In that case you will want to call matplotlib.mlab.specgram
to obtain the spectrum as numpy array and use the set_array()
method of the image to update the image in the animation.
Note however, that this might require you to update the color limits of the image as well, if different spectra have different minimum or maximum amplitudes.
Because the image shown by plt.specgram
is not directly the spectrogram returned by mlab.specgram
, you may then need to set some parameters manually. Especially, by default, the image is shown on a dB scale.
I think the equivalent to
Fs = rate NFFT = 256 noverlap= 128 spec, freqs, t, im = plt.specgram(sound, Fs=Fs, NFFT=NFFT, noverlap=noverlap)
would be
spec, freqs, t = plt.mlab.specgram(sound, Fs=rate, NFFT=NFFT, noverlap=noverlap) pad_xextent = (NFFT-noverlap) / Fs / 2 xmin, xmax = np.min(t) - pad_xextent, np.max(t) + pad_xextent extent = xmin, xmax, freqs[0], freqs[-1] im = plt.imshow(np.flipud(10. * np.log10(spec)), extent=extent, aspect="auto")