Issue
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.
Solution
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}')
Answered By - Paul Brodersen
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.