Home > other >  How to improve performance of plot redraw in a loop?
How to improve performance of plot redraw in a loop?

Time:11-02

I am reading accelerometer sensor data from a serial port and trying to plot it real time using matplot library. I wrote the following code that does what I want but it is very slow. This code takes 5-6 seconds to plot 100 samples but I need to get it done in less than 1 second.

import matplotlib.pyplot as plt
import numpy as np

x = np.zeros((1000,))
data = np.zeros((1000,))

plt.ion()
fig, ax = plt.subplots()
ax.set_ylim(-2, 2)
ax.set_xlim(0, 1000)
l, = ax.plot(data)

import serial
ser = serial.Serial('COM3',9600)
index = 0
while True:
    b = ser.readline().decode("utf-8") # reading samples
    temp = b[:-1].split()
    print(temp[0], temp[1])
    x[index] = index
    data[index] = temp[0]
    print(index, temp[0])
    l.set_xdata(x[0:index])
    l.set_ydata(data[0:index])
    index  = 1
    fig.canvas.draw()
    fig.canvas.flush_events()

How can I improve the performance of this code?

I need it fast enough so I can see a filtered(smoothed) version of data on top of raw data from multiple sensors in real time.


Here is a screenshot of profiler as the program was terminated prematurely.

enter image description here

CodePudding user response:

This seems to be a limit on Matplotlib's performance. It is not made for real time high speed plotting. You should look into pyqtgraph. There are examples you can modify.

It uses OpenGL for plotting and can go up to a couple hundreds points per second for a simple line plot.

CodePudding user response:

I still think you probably don't need to plot every data point individually, but even just adapting the blitting example I linked in the comments, I get 155 fps.

import time
import matplotlib.pyplot as plt
import numpy as np

x = np.zeros((1000,))
data = np.zeros((1000,))

fig, ax = plt.subplots()
ax.set_ylim(-2, 2)
ax.set_xlim(0, 1000)

# animated=True tells matplotlib to only draw the artist when we
# explicitly request it
l, = ax.plot(data, animated=True)

# make sure the window is raised, but the script keeps going
plt.show(block=False)

# stop to admire our empty window axes and ensure it is rendered at
# least once.
#
# We need to fully draw the figure at its final size on the screen
# before we continue on so that :
#  a) we have the correctly sized and drawn background to grab
#  b) we have a cached renderer so that ``ax.draw_artist`` works
# so we spin the event loop to let the backend process any pending operations
plt.pause(0.1)

# get copy of entire figure (everything inside fig.bbox) sans animated artist
bg = fig.canvas.copy_from_bbox(fig.bbox)
# draw the animated artist, this uses a cached renderer
ax.draw_artist(l)
# show the result to the screen, this pushes the updated RGBA buffer from the
# renderer to the GUI framework so you can see it
fig.canvas.blit(fig.bbox)

N = 1000
tic = time.time()
index = 0
for ii in range(N):
    # reset the background back in the canvas state, screen unchanged
    fig.canvas.restore_region(bg)

    # update the artist, neither the canvas state nor the screen have changed
    # b = ser.readline().decode("utf-8") # reading samples
    b = f'{2 * np.random.rand()} abcd'
    temp = b[:-1].split()
    x[index] = index
    data[index] = temp[0]
    l.set_xdata(x[0:index])
    l.set_ydata(data[0:index])
    index  = 1

    # re-render the artist, updating the canvas state, but not the screen
    ax.draw_artist(l)
    # copy the image to the GUI state, but screen might not be changed yet
    fig.canvas.blit(fig.bbox)
    # flush any pending GUI events, re-painting the screen if needed
    fig.canvas.flush_events()
    # you can put a pause in if you want to slow things down
    # plt.pause(.1)

toc = time.time()
fps = N / (toc - tic)
print(f'fps = {fps}')
  • Related