2

I have a piece of code that takes real time audio signal from audio jack of my laptop and plots its graph after some basic filtering. The problem I am facing is that the real time plotting is getting slower and slower as the program is running ahead.

Any suggestions to make this plotting faster and proceed at constant rate?? I think animation function will make it faster but was not able to formulate according to my requirement

import pyaudio
import numpy as np
import time
import matplotlib.pyplot as plt
import scipy.io.wavfile
from scipy.signal import butter, lfilter
import wave

plt.rcParams["figure.figsize"] = 8,4

RATE = 44100
CHUNK = int(RATE/2) # RATE / number of updates per second
#Filter co-efficients 
nyq = 0.5 * RATE
low = 3000 / nyq
high = 6000 / nyq
b, a = butter(7, [low, high], btype='band')

#Figure structure
fig, (ax, ax2) =plt.subplots(nrows=2, sharex=True)
x = np.linspace(1, CHUNK, CHUNK)
extent = [x[0] - (x[1] - x[0]) / 2., x[-1] + (x[1] - x[0]) / 2., 0, 1]



def soundplot(stream):
    t1=time.time()
    data = np.array(np.fromstring(stream.read(CHUNK),dtype=np.int32))
    y1 = lfilter(b, a, data)
    ax.imshow(y1[np.newaxis, :], cmap="jet", aspect="auto")
    plt.xlim(extent[0], extent[1])
    plt.ylim(-50000000, 50000000)
    ax2.plot(x, y1)
    plt.pause(0.00001)
    plt.cla()  # which clears data but not axes
    y1 = []
    print(time.time()-t1)
if __name__=="__main__":
    p=pyaudio.PyAudio()
    stream=p.open(format=pyaudio.paInt32,channels=1,rate=RATE,input=True,
                  frames_per_buffer=CHUNK)
    for i in range(RATE):
        soundplot(stream)
    stream.stop_stream()
    stream.close()
    p.terminate()
Laveena
  • 453
  • 1
  • 7
  • 24

1 Answers1

4

This is a little long for a comment, and since you're asking for suggestions I think it's a semi-complete answer. There's more info and examples online about getting realtime plotting with matplotlib, if you need ideas beyond what's here. The library wasn't designed for this, but it's possible.

First step, profile the code. You can do this with

import cProfile
cProfile.run('soundplot(stream)')

That will show where most of the time is being spent. Without doing that, I'll give a few tips, but be aware that profiling may show other causes.

First, you want to eliminate redundant function calls in the function soundplot. Both of the following are unnecessary:

plt.xlim(extent[0], extent[1])
plt.ylim(-50000000, 50000000)

They can be called once in initialization code. imshow updates these automatically, but for speed you shouldn't call that every time. Instead, in some initialization code outside the function use im=imshow(data, ...), where data is the same size as what you'll be plotting (although it may not need to be). Then, in soundplot use im.set_data(y1[np.newaxis, :]). Not having to recreate the image object each iteration will speed things up immensely.

Since the image object remains through each iteration, you'll also need to remove the call to cla(), and replace it with either show() or draw() to have the figure draw the updated image. You can do the same with the line on the second axis, using line.set_ydata(y).

Please post the before and after rate it runs at, and let me know if that helps.

Edit: some quick profiling of similar code suggests a 100-500x speedup, mostly from removing cla().

Also looking at your code, the reason for it slowing down is that cla isn't ever called on the first axis. Eventually there will be hundreds of images drawn on that axis, slowing matplotlib to a crawl.

user2699
  • 2,927
  • 14
  • 31
  • I think this is a nice answer if that question had been asked on [codereview.stackexchange.com](https://codereview.stackexchange.com). – ImportanceOfBeingErnest Feb 09 '18 at 12:14
  • 1
    @user2699 cla() is been called every time the loop is executing, if I remove cla() the plot is not getting updated. I added a ax.clear() in my function soundplot in the starting and it has speedup my code upto half second for per update. I am now trying out your suggestions one by one and will update the results of speedup – Laveena Feb 09 '18 at 13:38
  • Is it axis 1, axis 2, or both that isn't getting updated? – user2699 Feb 09 '18 at 14:02
  • Its the data in y1 array that matters for me and that's not getting updated or plotted if I remove plt.cla().. I hope I answered your question @user2699 – Laveena Feb 09 '18 at 14:18
  • 1
    Yes, but you're using y1 in both subplots. In any case, if it still isn't appearing you can try `gcf().canvas.draw()`. That will tell matplotlib to redraw everything in the figure. – user2699 Feb 09 '18 at 22:33
  • 1
    yes, I did that in my later version of code.. @user2699 Thank You :) I am now calling just the values that has to be plotted in the loop and the structure of plot is defined outside. And I am using `fig.canvas.draw()` and `fig.canvas.flush_events()` instead `plt.pause(0.00001)` because pause internally calls out all these function and if I call them directly I am getting speed up of like 60-65 frames per second. – Laveena Feb 11 '18 at 12:53
  • 1
    @user2699 and also I am now reading audio using `callback` function of pyaudio and than plotting it so the data loss is also very negligible (atleast theoretically and empirically) and processing is faster :) – Laveena Feb 11 '18 at 12:59