Graph plots are slow than the real time values that are received - python

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

Related

Real time data plotting from a high throughput source

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.

Updating matplotlib figures in real time for data acquisition

I want to plot data in matplotlib in real time. I want to open a figure once at the start of the programme, then update the figure when new data is acquired. Despite there being a few similar questions out there, none quite answer my specific question.
I want each set of data points new_data1 and new_data2 to be plotted on the same figure at the end of each while loop i.e. one line after the first while loop, two lines on the same figure after the second while loop etc. Currently they are all plotted together, but only right at the end of the programme, which is no use for real time data acquisition.
import matplotlib.pyplot as plt
import numpy
hl, = plt.plot([], [])
def update_line(hl, new_datax, new_datay):
hl.set_xdata(numpy.append(hl.get_xdata(), new_datax))
hl.set_ydata(numpy.append(hl.get_ydata(), new_datay))
plt.xlim(0, 50)
plt.ylim(0,200)
plt.draw()
x = 1
while x < 5:
new_data1 = []
new_data2 = []
for i in range(500):
new_data1.append(i * x)
new_data2.append(i ** 2 * x)
update_line(hl, new_data1, new_data2)
x += 1
else:
print("DONE")
This programme plots all 5 lines, but at the end of the programme. I want each line to be plotted after one another, after the while loop is completed. I have tried putting in plt.pause(0.001) in the function, but it has not worked.
This programme is different from the one that has been put forward - that programme only plots one graph and does not update with time.
If I correctly understood your specifications, you can modify just a bit your MWE as follows:
import matplotlib.pyplot as plt
import numpy
fig = plt.figure(figsize=(11.69,8.27))
ax = fig.gca()
ax.set_xlim(0, 50)
ax.set_ylim(0,200)
hl, = plt.plot([], [])
def update_line(hl, new_datax, new_datay):
# re initialize line object each time if your real xdata is not contiguous else comment next line
hl, = plt.plot([], [])
hl.set_xdata(numpy.append(hl.get_xdata(), new_datax))
hl.set_ydata(numpy.append(hl.get_ydata(), new_datay))
fig.canvas.draw_idle()
fig.canvas.flush_events()
x = 1
while x < 10:
new_data1 = []
new_data2 = []
for i in range(500):
new_data1.append(i * x)
new_data2.append(i ** 2 * x)
update_line(hl, new_data1, new_data2)
# adjust pause duration here
plt.pause(0.5)
x += 1
else:
print("DONE")
which displays :
Not sure, if I am reading the requirements right but below is a blueprint. Please change it to suit your requirements. You may want to change the function Redraw_Function and edit the frames (keyword parameter, which is np.arange(1,5,1) ) in the FuncAnimation call. Also interval=1000 means 1000 milliseconds of delay.
If you are using Jupyter then comment out the second last line (where it says plt.show()) and uncomment the last line. This will defeat your purpose of real time update but I am sorry I had trouble making it work real time in Jupyter. However if you are using python console or official IDLE please run the code as it is. It should work nicely.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
plot, = plt.plot([],[])
def init_function():
ax.set_xlim(0,50)
ax.set_ylim(0,250)
return plot,
def Redraw_Function(UpdatedVal):
new_x = np.arange(500)*UpdatedVal
new_y = np.arange(500)**2*UpdatedVal
plot.set_data(new_x,new_y)
return plot,
Animated_Figure = FuncAnimation(fig,Redraw_Function,init_func=init_function,frames=np.arange(1,5,1),interval=1000)
plt.show()
# Animated_Figure.save('MyAnimated.gif',writer='imagemagick')
When you run the code, you obtain the below result. I tried to keep very little code but I am sorry, if your requirement was totally different.
Best Wishes,

Python: FuncAnimation doesn't work in "while True" loop

I have a Raspberry Pi connected with an accelerometer. In Python, I'm want to draw a animated graph of X-axis value. It's a realtime graph that shows the change of X-axis value as I move the Pi in my hand. However the graph shows only the initial value.
from mpu6050 import mpu6050
from time import sleep
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
style.use("fivethirtyeight")
sensor = mpu6050(0x68)
print " waiting for the sensor to callibrate..."
sleep(2)
acc = np.empty((0,3), float) # storage for x, y, z axes values of the accelerometer
t = 0
time = np.empty((0,1), int) # time counter(this becomes the x axis of the graph)
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
axis = 0 # accelerometer x axis. y axis = 1, z axis = 2
def animate(i):
ax1.clear()
ax1.plot(time, acc[:,axis])
while True:
accel_data = sensor.get_accel_data()
print("Accelerometer data")
print("x: " + str(accel_data['x']))
print("y: " + str(accel_data['y']))
print("z: " + str(accel_data['z']))
acc = np.append(acc, np.array([[accel_data['x'], accel_data['y'], accel_data['z']]]), axis=0)
# increment time array
time = np.append(time, t)
t = t + 1
print("acc[:,0]:" + str(acc[:,0]))
print("time:" + str(time))
ani = animation.FuncAnimation(fig, animate, interval = 1000)
plt.show()
sleep(2)
However, when I run the script, it only prints the value in the first loop like below. It also shows the graph but it's an empty graph because in the first loop there is only one data point.
waiting for the sensor to callibrate...
Accelerometer data
x: 6.2009822998
y: 3.36864173584
z: 9.27513723145
acc[:,0]: [ 6.2009823]
time: [0]
When I close the graph window, the loop resumes from the second loop and prints the values onwards, but the graph is never shown again.
Although it does not give any Error message, I assume something is wrong with animation.FuncAnimation or the place I should put plt.show() in the while loop.
I'm using Raspberry Pi 3b + with Python 2.7.13. Accelerometer is MPU6050.
It would be great if anybody could tell me how to fix this problem. Thank you!

Python realtime plot with matplotlib

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

Erratic data stream from python sketch

I am trying to output time, pitch, roll and yaw from a quadrotor and create graphs using python. I have two XBee modules sending data from the quadrotor to the computer. The values I receive from the python sketch are very erratic, there seem to be a large number of errors, this seems to be caused by unwanted numbers randomly appearing in the data stream. I cannot seem to solve this, does anyone have any ideas??? Below I've attached a copy of the code.
import serial # import Serial Library
import numpy as np # Import numpy
import matplotlib.pyplot as plt #import matplotlib library
from drawnow import *
time=[]
d0k0=[]
d0k1=[]
d0k2=[]
arduinoData = serial.Serial('COM4', 9600) #Creating our serial object named arduinoData
plt.ion() #Tell matplotlib you want interactive mode to plot live data
cnt=0
ydata = [0] * 50
#ax1 = plt.axes()
#line, = plt.plot(ydata)
def makeFig(): #Create a function that makes our desired plot
plt.ylim(180,-180) #Set y min and max values
plt.title('Stability') #Plot the title
plt.grid(True) #Turn the grid on
plt.ylabel('Kalman estimated angles') #Set ylabels
line.set_xdata(time)
line.set_ydata(d0k0)
#plt.plot(t, d2)
#plt.plot(t, d3)
plt.legend(['roll','pitch','yaw'], loc='upper left')
plt.draw()
while True: # While loop that loops forever
while (arduinoData.inWaiting()==0): #Wait here until there is data
pass #do nothing
data = arduinoData.readline()
data = data.decode("ascii", "ignore")
data = ''.join([c for c in data if c in '1234567890.,'])
data = data.strip()
data = data.split(",")
if len(data) == 4:
print(data)
t = float( data[0])
#print (t)
d1 = float( data[1])
d2 = float( data[2])
d3 = float( data[3])
time.append(t)
d0k0.append(d1)
#d0k1.append(d2)
#d0k2.append(d3)
#drawnow(makeFig)
#plt.pause(0.01)

Categories

Resources