Graph figure not displayed when live plotting arduino data with matplotlib - python

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()

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.

Animating plots conditionally in Python

This is my first post here.
I was trying to plot some serial data(temperature), the data printed by my microcontroller in the following format: temperature data is printed every 1 seconds and otherwise some other data is printed(like output from different sensors).
My implementation so far is that in order for Python to know that the data we are sending is the temperature data, I send a unique code like say "kkk1k1" and the temperature data in the next line.
Then I can read the serial port in Python and check whether the serial data is the unique code and when it matches, read the next line and then plot it. Otherwise skip plotting and and keep reading.
When I try this the chart doesn't show anything and it hangs.
Does anyone know how to update my plot only when a certain condition is fulfilled?
Any links to documentation regarding this will be appreciated.
Thank You
Note: This query is resolved. I am writing it here as do not know how to do it otherwise.
This is my implementation, if you find any errors please let me know. Thanks!
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import sys, time, math, serial
xsize=100
#These are used to display current temp, max and min temp
curr_temp = 0.0
ma = 0
mi = 0
def data_gen():
global ma, mi
t = data_gen.t
while True:
#Since lots of different data is being printed from the serial, we
#need our script to different what useful for it
#so that when the temperature data comes, it only plots that.
#Here the scripts continuously check for serial input, in our main
#code, we send a key before sending the temperature data
#Script reads the serial line and matches it with the key. If match
#successful, it immediate read next line and then proceed to plot
#it. Otherwise If match is not successful, it keeps reading and the
#Realtime plot pauses.
strin = ser.readline()
strr = strin.decode()
key = b'10011\r\n'
key = key.decode()
if (strr == key):
#first update t and then get the values.
t+=1
print ('HOORAY')
strin = ser.readline()
print (strin.decode('utf-8'))
val = float(strin)
#here basically we check if the the current temperature is maximum
#or minimum.
#at start, we initialize the max and min values
if t == 5:
ma = val
mi = val
else:
if ma < val:
ma = val
if mi > val:
mi = val
#give the plot functions the following data to plot.
yield t, val, ma, mi
def run(data):
# update the data
t,y, maxx, minn = data
if t>-1:
xdata.append(t)
ydata.append(y)
if t>xsize: # Scroll to the left.
ax.set_xlim(t-xsize, t)
line.set_data(xdata, ydata)
#now update the text_boxes
TEMP1.set_text(y)
TEMPMa.set_text(maxx)
TEMPMi.set_text(minn)
return line
def on_close_figure(event):
sys.exit(0)
# configure the serial port
ser = serial.Serial(
port='COM3', # Change as needed
baudrate=115200,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_TWO,
bytesize=serial.EIGHTBITS,
timeout=1
)
ser.isOpen()
data_gen.t = -1
fig = plt.figure()
fig.canvas.mpl_connect('close_event', on_close_figure)
ax = fig.add_subplot(111)
#Differentiating each line from one another.
line, = ax.plot([], [], lw=2, color="red")
ax.set_ylim(15, 45)
ax.set_xlim(0, xsize)
#Axis lables and text_boxes
plt.xlabel('Time')
plt.ylabel('Measurement Temperature')
fig.text(0.91,0.80, 'TEMP', fontweight = 'bold', size = 10)
fig.text(0.91,0.65, 'MAX', fontweight = 'bold', size = 10)
fig.text(0.91,0.50, 'MIN', fontweight = 'bold', size = 10)
TEMP1 = fig.text(0.91,0.75, curr_temp)
TEMPMa = fig.text(0.91,0.60, curr_temp)
TEMPMi = fig.text(0.91,0.45, curr_temp)
ax.grid()
xdata, ydata = [], []
# Important: Although blit=True makes graphing faster, we need blit=False
#to prevent spurious lines to appear when resizing the stripchart.
ani = animation.FuncAnimation(fig, run, data_gen, blit=False, interval=10,
repeat=False)
plt.show()

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!

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)

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

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

Categories

Resources