I want to plot Real time in a way that updates fast.
The data I have:
arrives via serial port at 62.5 Hz
data corresponds to 32 sensors (so plot 32 lines vs time).
32points *62.5Hz = 2000 points/sec
The problem with my current plotting loop is that it runs slower than 62.5[Hz], meaning I miss some data coming in from serial port.
I am looking for any solution to this problem that allows for:
All data from serial port to be saved.
Plots the data (even skipping a few points/using averages/eliminating old points and only keeping the most recent)
Here is my code, I am using random data to simulate the serial port data.
import numpy as np
import time
import matplotlib.pyplot as plt
#extra plot debugging
hz_ = [] #list of speed
time_=[] #list for time vs Hz plot
#store all data generated
store_data = np.zeros((1, 33))
#only data to plot
to_plot = np.zeros((1, 33))
#color each line
colours = [f"C{i}" for i in range (1,33)]
fig,ax = plt.subplots(1,1, figsize=(10,8))
ax.set_xlabel('time(s)')
ax.set_ylabel('y')
ax.set_ylim([0, 300])
ax.set_xlim([0, 200])
start_time = time.time()
for i in range (100):
loop_time = time.time()
#make data with col0=time and col[1:11] = y values
data = np.random.randint(1,255,(1,32)).astype(float) #simulated data, usually comes in at 62.5 [Hz]
data = np.insert(data, 0, time.time()-start_time).reshape(1,33) #adding time for first column
store_data = np.append(store_data, data , axis=0)
to_plot = store_data[-100:,]
for i in range(1, to_plot.shape[1]):
ax.plot(to_plot[:,0], to_plot[:,i],c = colours[i-1], marker=(5, 2), linewidth=0, label=i)
#ax.lines = ax.lines[-33:] #This soluition speeds it up, to clear old code.
fig.canvas.draw()
fig.canvas.flush_events()
Hz = 1/(time.time()-loop_time)
#for time vs Hz plot
hz_.append(Hz)
time_.append( time.time()-start_time)
print(1/(time.time()-loop_time), "Hz - frequncy program loops at")
#extra fig showing how speed drops off vs time
fig,ax = plt.subplots(1,1, figsize=(10,8))
fig.suptitle('Decreasingn Speed vs Time', fontsize=20)
ax.set_xlabel('time(s)')
ax.set_ylabel('Hz')
ax.plot(time_, hz_)
fig.show()
I also tried while using
ax.lines = ax.lines[-33:]
to remove older points, and this speed up the plotting, but still slower than the speed i aquire data.
Any library/solution to make sure I collect all data and plot the general trendlines (so even not all points) is ok. Maybe something that runs acquiring data and plotting in parallel?
You could try to have two separate processes:
one for acquiring and storing the data
one for plotting the data
Below there are two basic scripts to get the idea.
You first run gen.py which starts to generate numbers and save them in a file.
Then, in the same directory, you can run plot.py which will read the last part of the file and will update the a Matplotlib plot.
Here is the gen.py script to generate data:
#!/usr/bin/env python3
import time
import random
LIMIT_TIME = 100 # s
DATA_FILENAME = "data.txt"
def gen_data(filename, limit_time):
start_time = time.time()
elapsed_time = time.time() - start_time
with open(filename, "w") as f:
while elapsed_time < limit_time:
f.write(f"{time.time():30.12f} {random.random():30.12f}\n") # produces 64 bytes
f.flush()
elapsed = time.time() - start_time
gen_data(DATA_FILENAME, LIMIT_TIME)
and here is the plot.py script to plot the data (reworked from this one):
#!/usr/bin/env python3
import io
import time
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.animation
BUFFER_LEN = 64
DATA_FILENAME = "data.txt"
PLOT_LIMIT = 20
ANIM_FILENAME = "video.gif"
fig, ax = plt.subplots(1, 1, figsize=(10,8))
ax.set_title("Plot of random numbers from `gen.py`")
ax.set_xlabel("time / s")
ax.set_ylabel("random number / #")
ax.set_ylim([0, 1])
def get_data(filename, buffer_len, delay=0.0):
with open(filename, "r") as f:
f.seek(0, io.SEEK_END)
data = f.read(buffer_len)
if delay:
time.sleep(delay)
return data
def animate(i, xs, ys, limit=PLOT_LIMIT, verbose=False):
# grab the data
try:
data = get_data(DATA_FILENAME, BUFFER_LEN)
if verbose:
print(data)
x, y = map(float, data.split())
if x > xs[-1]:
# Add x and y to lists
xs.append(x)
ys.append(y)
# Limit x and y lists to 10 items
xs = xs[-limit:]
ys = ys[-limit:]
else:
print(f"W: {time.time()} :: STALE!")
except ValueError:
print(f"W: {time.time()} :: EXCEPTION!")
else:
# Draw x and y lists
ax.clear()
ax.set_ylim([0, 1])
ax.plot(xs, ys)
# save video (only to attach here)
#anim = mpl.animation.FuncAnimation(fig, animate, fargs=([time.time()], [None]), interval=1, frames=3 * PLOT_LIMIT, repeat=False)
#anim.save(ANIM_FILENAME, writer='imagemagick', fps=10)
#print(f"I: Saved to `{ANIM_FILENAME}`")
# show interactively
anim = mpl.animation.FuncAnimation(fig, animate, fargs=([time.time()], [None]), interval=1)
plt.show()
plt.close()
Note that I have also included and commented out the portion of code that I used to generate the animated GIF above.
I believe this should be enough to get you going.
Related
I'm plotting real-time data using parsed data from a file that is being repeatedly opened. I'm deriving and plotting two different values on the same chart. The Y axis scales up and down according the values of each. The "snr" (green) value is plotting fine but the "data_rate"(red) value seems to be static. See screenshot.
import matplotlib.pyplot as plt
import csv
import datetime
from matplotlib.animation import FuncAnimation
import numpy as np
x = []
y = []
z = []
rssi_val = []
def animate(i):
with open('stats.txt', 'r') as searchfile:
time = (searchfile.read(8))
for line in searchfile:
if 'agrCtlRSSI:' in line:
rssi_val = line[16:20]
rssi_val=int(rssi_val)
if 'agrCtlNoise:' in line:
noise_val = (line[16:20])
noise_val=int(noise_val)
if 'maxRate:' in line:
data_rate = (line[16:20])
data_rate=int(data_rate)
snr = ((noise_val - rssi_val) * -1)
#begin test
y.append(snr)
x.append(time)
z.append(data_rate)
#end test
#begin test
plt.cla()
#Verify values are not empty
print("SNR = ", snr)
print("DR = ", data_rate)
plt.plot(snr,label='Signal')
plt.plot(data_rate,label='Data Rate')
plt.legend(loc='upper right')
plt.plot(x,y,z)
ax=plt.gca()
ax.tick_params('x',labelrotation=60)
#end test
ani = FuncAnimation(plt.gcf(), animate, interval=1000)
plt.show()
The simplest way is to call plot twice.
plt.plot(x,y)
plt.plot(x,z)
See "Plotting multiple sets of data" here for more options.
I have a little PyQT4 application which shows plots for a big data set (100k points x 14 channels). I just want to show a period of 128 points and click to show the next period.
My naive approach was to create the figures and plot only a subset of my data on each step in the loop. This leads to a loading time for quite a second and I thought this may be to much for this task.
Is there any way to improve the performance? Did I miss some matplotlib built-in functions to plot only a subset of data? I wouldn't mind a longer loading time at the beginning of the application, so maybe I could plot it all and zoom in?
EDIT: Provided a simple running example
Took 7.39s to plot 8 samples on my machine
import time
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
num_channels = 14
num_samples = 1024
data = np.random.rand(num_channels, num_samples)
figure = plt.figure()
start = 0
period = 128
axes = []
for i in range(num_channels):
axes.append(figure.add_subplot(num_channels, 1, i+1))
end = start+period
x_values = [x for x in range(start, end)]
begin = time.time()
num_plot = 0
for i in range(0, num_samples, period):
num_plot += 1
end = start+period
for i, ax in enumerate(axes):
ax.hold(False)
ax.plot(x_values, data[i][start:end], '-')
ax.set_ylabel(i)
start += period
figure.canvas.draw()
print("Took %.2fs to plot %d samples" % (time.time()-begin, num_plot))
Using the #joe-kington answer from here: How to update a plot in matplotlib improved performance to a decent value.
I now only change the y-values of the line object using set_ydata().
The line object is returned when calling ax.plot() which is only called once.
EDIT: Added a running example:
Took 3.11s to plot 8 samples on my machine
import time
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
num_channels = 14
num_samples = 1024
data = np.random.rand(num_channels, num_samples)
figure = plt.figure()
start = 0
period = 128
axes = []
for i in range(num_channels):
axes.append(figure.add_subplot(num_channels, 1, i+1))
end = start+period
x_values = [x for x in range(start, end)]
lines = []
begin = time.time()
num_plot = 1 # first plot
for i, ax in enumerate(axes):
ax.hold(False)
# save the line object
line, = ax.plot(x_values, data[i][start:end], '-')
lines.append(line)
ax.set_xlim([start,end])
ax.set_ylabel(i)
start += period
for _ in range(period, num_samples, period):
num_plot += 1
end = start + period
for i, line in enumerate(lines):
line.set_ydata(data[i][start:end])
start += period
figure.canvas.draw()
print("Took %.2fs to plot %d samples" % (time.time()-begin, num_plot))
I try to plot several data I receive over the USART serial bus from a microcontroller in python. It would be fine, if I could plot all the data in parallel and in realtime.
When I use a single plot, the data is plotted in realtime but when I use subplots, the data has more and more delay, also if I only plot one subchannel. Has someone any Idea why subplots in python are so much slower?
I measured also the time consumption for the function update(), it seems to be 2ms or less. The Data I receive only every 5ms or more. How can I improve the speed?
Kind regards: Sebastian T.
Here is my code
import serial
import matplotlib.pyplot as plt
import matplotlib.animation as anim
import time
from collections import deque
#SERIAL#######################################################
try:
ser = serial.Serial()
ser.baudrate=115200;
ser.port = 'COM7'
ser.open()
except:
ser.close()
print('Problem occured')
ser.flushInput()
ser.flushOutput()
#PLOT##########################################################
MAX_X = 250 #width of graph
MAX_Y = 70000 #height of graph
# intialize line to horizontal line on 0
line1 = deque([0.0]*MAX_X, maxlen=MAX_X)
line2 = deque([0.0]*MAX_X, maxlen=MAX_X)
line3 = deque([0.0]*MAX_X, maxlen=MAX_X)
line4 = deque([0.0]*MAX_X, maxlen=MAX_X)
line5 = deque([0.0]*MAX_X, maxlen=MAX_X)
plt.close('all')
fig, (ax1,ax2,ax3,ax4) = plt.subplots(4,1)
l1, = ax1.plot([], [])
l2, = ax2.plot([], [])
l3, = ax3.plot([], [])
l4, = ax4.plot([], [])
l=[l1,l2,l3,l4]
for ax in [ax1,ax2,ax3,ax4]:
ax.set_ylim(-(MAX_Y/2),MAX_Y/2)
ax.set_xlim(-(MAX_X/2),MAX_X/2)
ax.grid()
def update(fn, data):
try:
t = time.time()
#prepare Data
data_raw = ser.readline()
data_raw = data_raw.split(',')
data_raw = data_raw[1::2]
#Update Plots
line1.append(int(data_raw[0]))
line2.append(int(data_raw[1]))
line3.append(int(data_raw[2]))
line4.append(int(data_raw[3]))
#Set Data
l[0].set_data(range(-MAX_X/2, MAX_X/2), line1)
l[1].set_data(range(-MAX_X/2, MAX_X/2), line2)
l[2].set_data(range(-MAX_X / 2, MAX_X / 2), line3)
l[3].set_data(range(-MAX_X / 2, MAX_X / 2), line4)
print(time.time() - t)
except:
print('exception')
ser.close()
ani = anim.FuncAnimation(fig,update,fargs=(0,),frames=1, interval=100)
plt.show()
This is a known issue if plotted data points grow in number.
I plotted data from a D/A-converter realtime and had to clear the axis with cla() after around 500 plotted points.
The reason is that calling "def update" all the time completely updates the whole figure containing all plot points...it is clar that this is getting more and more unperformant
Greets Dr Cobra
I am trying to plot real-time lectures from Arduino UNO's analogue input with matplotlib.
My problem: The graph would not be shown. Only when I stop running the code (Ctrl+C), It will show the last values's graph.
when adding "print pData" line to the code in order to check whether the values are properly arriving to the computer, these are correctly displayed (shows the 25 values array each second) on the python terminal.
#!/usr/bin/python
from matplotlib import pyplot
import pyfirmata
from time import sleep
# Associate port and board with pyFirmata
port = '/dev/ttyACM0'
board = pyfirmata.Arduino(port)
# Using iterator thread to avoid buffer overflow
it = pyfirmata.util.Iterator(board)
it.start()
# Assign a role and variable to analog pin 0
a0 = board.get_pin('a:0:i')
pyplot.ion()
pData = [0.0] * 25
fig = pyplot.figure()
pyplot.title('Real-time Potentiometer reading')
ax1 = pyplot.axes()
l1, = pyplot.plot(pData)
pyplot.ylim([0, 1])
while True:
try:
sleep(1)
pData.append(float(a0.read()))
pyplot.ylim([0, 1])
del pData[0]
l1.set_xdata([i for i in xrange(25)])
l1.set_ydata(pData) # update the data
#print pData
pyplot.draw() # update the plot
except KeyboardInterrupt:
board.exit()
break
Here is a mock-up that uses matplotlib.animation for real-time plotting.
from matplotlib import pyplot
import matplotlib.animation as animation
import random
# Generate sample data
class Pin:
def read(self):
return random.random()
a0 = Pin()
n = 25
pData = [None] * n
fig, ax = pyplot.subplots()
pyplot.title('Real-time Potentiometer reading')
l1, = ax.plot(pData)
# Display past sampling times as negative, with 0 meaning "now"
l1.set_xdata(range(-n + 1, 1))
ax.set(ylim=(0, 1), xlim=(-n + 1, 0))
def update(data):
del pData[0]
pData.append(float(a0.read()))
l1.set_ydata(pData) # update the data
return l1,
ani = animation.FuncAnimation(fig, update, interval=1000, blit=True)
try:
pyplot.show()
finally:
pass
#board.exit()
I am trying to get data from arduino and trying to then show the data in real time in python using subplot. The values that are coming from arduino uno board are fast and are displayed in the python console also at the same rate but when I am trying to plot the real time data in the graph it is very slowly plotted. It needs to as fast as the rate of values that are coming from the uno board.
Please help. Here is my code:
import serial
import numpy
import matplotlib.pyplot as plt
from drawnow import *
x = []
y = []
z = []
magnitude = []
arduinoData = serial.Serial('com4', 9600)
plt.ion()
count=0
fig = plt.figure()
def makeFig():
ax1 = fig.add_subplot(4,1,1)
ax1.plot(x, 'ro-', label='X axis')
ax2 = fig.add_subplot(4,1,2)
ax2.plot(y, 'b^-', label='Y axis')
ax3 = fig.add_subplot(4,1,3)
ax3.plot(z, 'gp-', label='Y axis')
ax4 = fig.add_subplot(4,1,4)
ax4.plot(magnitude, 'yo-', label='X axis')
while True:
while (arduinoData.inWaiting()==0):
pass
arduinoString = arduinoData.readline()
dataArray = arduinoString.split(',')
xaxis = float( dataArray[0])
yaxis = float( dataArray[1])
zaxis = float( dataArray[2])
mag =float( dataArray[3])
x.append(xaxis)
y.append(yaxis)
z.append(zaxis)
magnitude.append(mag)
drawnow(makeFig)
count = count + 1
nowThere are some things you must understand before you can find a good solution. How fast does the data arrive from arduino? How fast is the drawnow function? These timings are not under your control, so if the data arrives faster than the plotting routine can execute then the task as you have defined it is impossible. All Python versions have a time module, and the function time.time() returns the current time in seconds. This can be used to measure the speed of the drawnow function. You may need to cache a chunk of data before updating the plot. Updating the plot a few times a second will give the illusion of real time, and that may be good enough.
To see how fast the graph plots, use:
t = time.time()
drawnow()
print(time.time()-t) # time in seconds