Matplotlib Animation, plotting data from a while True: - python

I'm running a script on a Raspberry Pi, where I pull some data from an accelerometer, and I want to continuously update a plot. I've read around and watched some youtube videos and I've decided for the Matplotlib animation.
Now everything kinda works but the plot doesn't really get drawn before I KeyboardInterrupt the script. And I see why, because the plot function is inside the loop. I've tried moving it out of the loop,but then it doesn't work at all.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
style.use("fivethirtyeight")
import time
import board
import busio
import adafruit_tca9548a
import adafruit_adxl34x
i2c = busio.I2C(board.SCL, board.SDA)
tca = adafruit_tca9548a.TCA9548A(i2c)
ad1 = adafruit_adxl34x.ADXL345(tca[0])
ad2 = adafruit_adxl34x.ADXL345(tca[2])
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
def animate(i):
G = []
while True:
x,y,z = ad2.acceleration
acc = [x,y,z]
acc_abs = [abs(a) for a in acc]
i = acc_abs.index(max(acc_abs))
stor = acc[i]
if stor>0:
stor = stor-10.52
elif stor<0:
stor = stor+10.52
time.sleep(1)
G.append(stor)
ax1.clear()
ax1.plot(G)
ani = animation.FuncAnimation(fig,animate,interval =1000)
plt.show()

Related

Matplotlib not showing librosa specshow in custom class constructor

I have a custom "waveform"-class which I use for tkinter-applications. Due to testing reasons, i would like to see a spectrogram via librosa.display.specshow() without calling any tkinter-app. Sadly, the following code does not produce an output:
from matplotlib.figure import Figure
from matplotlib.pyplot import show
import librosa as lr
import librosa.display as lrd
class waveform():
def __init__(self, fp):
self.sig, self.sr = lr.load(fp, sr=None, res_type="polyphase")
X = lr.stft(self.sig, n_fft=2**13)
Xdb = lr.amplitude_to_db(abs(X))
self.figure = Figure(figsize=(10, 8), dpi=80)
self.ax = self.figure.add_subplot()
lrd.specshow(Xdb, sr=self.sr, x_axis="time", y_axis="log", ax=self.ax, cmap='viridis')
if __name__ == "__main__":
wv = waveform("./noise.wav")
show()
Are calls to matplotlib (which is what specshow is doing in the background) not rendered when inside of a class constructor?
The problem seems to come from the fact that you use matplotlib.figure which is not managed by pyplot.
Changing the import works for me
from matplotlib.pyplot import figure
# instead of from matplotlib.figure import Figure
# ...
class waveform():
# ...
self.figure = figure(figsize=(10, 8), dpi=80)
# (Just replaced Figure by figure)
# The rest is the same
However, I am not sure if it fits with your use case, so probably a good read is: https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure.show

Can I update a bokeh plot without callbacks from the server?

I want Bokeh to update periodically and arbitrarily when the results from a separate algorithm running in python returns results, not based on any input from the Bokeh interface.
I've tried various solutions but they all depend on a callback to a some UI event or a periodic callback as in the code below.
import numpy as np
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource, Plot, LinearAxis, Grid
from bokeh.models.glyphs import MultiLine
from time import sleep
from random import randint
def getData(): # simulate data acquisition
# run slow algorith
sleep(randint(2,7)) #simulate slowness of algorithm
return dict(xs=np.random.rand(50, 2).tolist(), ys=np.random.rand(50, 2).tolist())
# init plot
source = ColumnDataSource(data=getData())
plot = Plot(
title=None, plot_width=600, plot_height=600,
min_border=0, toolbar_location=None)
glyph = MultiLine(xs="xs", ys="ys", line_color="#8073ac", line_width=0.1)
plot.add_glyph(source, glyph)
xaxis = LinearAxis()
plot.add_layout(xaxis, 'below')
yaxis = LinearAxis()
plot.add_layout(yaxis, 'left')
plot.add_layout(Grid(dimension=0, ticker=xaxis.ticker))
plot.add_layout(Grid(dimension=1, ticker=yaxis.ticker))
curdoc().add_root(plot)
# update plot
def update():
bokeh_source = getData()
source.stream(bokeh_source, rollover=50)
curdoc().add_periodic_callback(update, 100)
This does seem to work, but is this the best way to go about things? Rather than having Bokeh try to update every 100 milliseconds can I just push new data to it when it becomes available?
Thanks
You can use zmq and asyncio to do it. Here is the code for the bokeh server, it wait data in an async coroutine:
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, curdoc
from functools import partial
from tornado.ioloop import IOLoop
import zmq.asyncio
doc = curdoc()
context = zmq.asyncio.Context.instance()
socket = context.socket(zmq.SUB)
socket.connect("tcp://127.0.0.1:1234")
socket.setsockopt(zmq.SUBSCRIBE, b"")
def update(new_data):
source.stream(new_data, rollover=50)
async def loop():
while True:
new_data = await socket.recv_pyobj()
doc.add_next_tick_callback(partial(update, new_data))
source = ColumnDataSource(data=dict(x=[0], y=[0]))
plot = figure(height=300)
plot.line(x='x', y='y', source=source)
doc.add_root(plot)
IOLoop.current().spawn_callback(loop)
to send the data just run following code in another python process:
import time
import random
import zmq
context = zmq.Context.instance()
pub_socket = context.socket(zmq.PUB)
pub_socket.bind("tcp://127.0.0.1:1234")
t = 0
y = 0
while True:
time.sleep(1.0)
t += 1
y += random.normalvariate(0, 1)
pub_socket.send_pyobj(dict(x=[t], y=[y]))

python matplotlib backend change after program parameters reading

I wrote a program and would like to give to the user the opportunity to run it either on compute with a graphical environment or not.
Currently through hard coding it I can do either one or the other by changing the matplotlib import at the top of my program file, before importing pyplot.
with graphical environment
import matplotlib
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
...
without graphical environment
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt
...
the remaining code would look something like that:
...
import os, sys, argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--noX", action="store_true", dest="noX")
params = parser.parse_args()
data = [0,1,2,3,4,5]
fig, ax = plt.subplots()
ax.plot(data)
if not params.noX:
plt.show()
plt.savefig("foo.png")
sys.exit(0)
if __name__ == "__main__":
main()
Is it possible to change the backend based on the noX parameter value?
You can set the backend o a condition:
import matplotlib
if not params.noX:
matplotlib.use("agg")
else:
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
If you only plot inside main, move your import inside this function:
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--noX", action="store_true", dest="noX", default=False)
params = parser.parse_args()
import matplotlib
if not params.noX:
matplotlib.use("agg")
else:
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
data = [0,1,2,3,4,5]
fig, ax = plt.subplots()
ax.plot(data)
if not params.noX:
plt.show()
plt.savefig("foo.png")
sys.exit(0)
if __name__ == "__main__":
main()
If you want to do plotting everywhere, use special function to parse the
command line arguments and call it only once.
import argparse
def _parse_cmd_args():
"""Parse command line args.
"""
parser = argparse.ArgumentParser()
parser.add_argument("--noX", action="store_true", dest="noX", default=False)
params = parser.parse_args()
return params
PARAMS = _parse_cmd_args()
# Want to prevent any further call to `_parse_cmd_args()`?
# Un-comment the following line:
# del _parse_cmd_args
import matplotlib
if not PARAMS.noX:
matplotlib.use("agg")
else:
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
def main():
data = [0,1,2,3,4,5]
fig, ax = plt.subplots()
ax.plot(data)
if not PARAMS.noX:
plt.show()
plt.savefig("foo.png")
sys.exit(0)
if __name__ == "__main__":
main()
Did you try? As a general rule you can't change the backend. So you will have to pospone the decision until you know the value of noX. Keep in mind that you can import at any time and any place. It is adviced to import at the beginning of the file but that's not always possible.
Ok so combining the idea of Mike with the global import of plt I come up with something like that to do the trick. Sorry for bothering you, it was actually simpler than first envisaged.
import os, sys, argparse
import matplotlib
matplotlib.use("agg") if "--noX" in sys.argv else matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--noX", action="store_true", dest="noX", default=False)
params = parser.parse_args()
data = [0,1,2,3,4,5]
fig, ax = plt.subplots()
ax.plot(data)
if not params.noX:
plt.show()
plt.savefig("foo.png")
sys.exit(0)
if __name__ == "__main__":
main()

Python real time plotting ROS data

I am trying to plot real time data coming to the computer using python. Data comes in a ROS topic and I use 'rospy' to subscribe to the topic in order to get data.
This is the code I wrote
import rospy
from sensor_msgs.msg import ChannelFloat32
import matplotlib.pyplot as plt
N = 200
i = 0
topic = "chatter"
x = range(N)
lmotor = [0]*N
rmotor = [0]*N
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim([0,N])
ax.set_ylim([-1,1])
line1, = ax.plot(lmotor, 'r-')
line2, = ax.plot(rmotor, 'g')
def plotThrottle(data):
global x, lmotor, rmotor, i
[x[i],lmotor[i],rmotor[i], tmp] = data
line1.set_ydata(lmotor)
line1.set_xdata(x)
line2.set_ydata(rmotor)
line2.set_xdata(x)
fig.canvas.draw()
def callBack(packet):
data = list(packet.values)
plotThrottle(data)
def listner():
rospy.init_node('listener', anonymous=True)
rospy.Subscriber(topic, ChannelFloat32, callBack)
rospy.spin()
if __name__ == '__main__':
listner()
My problem is when I call plotThrottle() with the data I got from rostopic, I get following error.
[ERROR]
[WallTime: 1454388985.317080] bad callback: <function callBack at 0x7f13d98ba6e0>
Traceback (most recent call last):
File "/opt/ros/indigo/lib/python2.7/dist-packages/rospy/topics.py", line 720, in _invoke_callback
cb(msg)
File "dummy2.py", line 41, in callBack
plotThrottle(data)
File "dummy2.py", line 37, in plotThrottle
fig.canvas.draw()
File "/usr/lib/pymodules/python2.7/matplotlib/backends/backend_tkagg.py", line 349, in draw
tkagg.blit(self._tkphoto, self.renderer._renderer, colormode=2)
File "/usr/lib/pymodules/python2.7/matplotlib/backends/tkagg.py", line 13, in blit
tk.call("PyAggImagePhoto", photoimage, id(aggimage), colormode, id(bbox_array))
RuntimeError: main thread is not in main loop
But if I use the same function and pass some data generated within the code (some random data) plot works fine.
I am an absolute beginner to python. I searched about this error and it says that this is because of some threading problem. But I don't understand how to fix this code. I am really grateful if someone can explain the problem and help fix this code.
Here you have two threads running, rospy.spin() and top.mainloop() (from Tkinter, backend of matplotlib in your case).
From this answer:
The problems stem from the fact that the _tkinter module attempts to
gain control of the main thread via a polling technique when
processing calls from other threads.
Your Tkinter code in Thread-1 is trying to peek into the main thread
to find the main loop, and it's not there.
From this answer:
If there is another blocking call that keeps your program running,
there is no need to call rospy.spin(). Unlike in C++ where spin() is
needed to process all the threads, in python all it does is block.
So you can use plt.show(block=True) to keep your program from closing, in that case you will use Tkinter mainloop, redrawing your canvas without problems.
The listener fuction should look like this:
def listener():
rospy.init_node('listener', anonymous=True)
rospy.Subscriber(topic, ChannelFloat32, callBack)
# rospy.spin()
plt.show(block=True)
Anyway this seems a bit a workaround for other alternatives see again this answer or simply use separate node for plotting i.e. ros suggested tools like rqt_graph.
Since this is an old post and still seems to be active in the community, I am going to provide an example, in general, how can we do real-time plotting. Here I used matplotlib FuncAnimation function.
import matplotlib.pyplot as plt
import rospy
import tf
from nav_msgs.msg import Odometry
from tf.transformations import quaternion_matrix
import numpy as np
from matplotlib.animation import FuncAnimation
class Visualiser:
def __init__(self):
self.fig, self.ax = plt.subplots()
self.ln, = plt.plot([], [], 'ro')
self.x_data, self.y_data = [] , []
def plot_init(self):
self.ax.set_xlim(0, 10000)
self.ax.set_ylim(-7, 7)
return self.ln
def getYaw(self, pose):
quaternion = (pose.orientation.x, pose.orientation.y, pose.orientation.z,
pose.orientation.w)
euler = tf.transformations.euler_from_quaternion(quaternion)
yaw = euler[2]
return yaw
def odom_callback(self, msg):
yaw_angle = self.getYaw(msg.pose.pose)
self.y_data.append(yaw_angle)
x_index = len(self.x_data)
self.x_data.append(x_index+1)
def update_plot(self, frame):
self.ln.set_data(self.x_data, self.y_data)
return self.ln
rospy.init_node('lidar_visual_node')
vis = Visualiser()
sub = rospy.Subscriber('/dji_sdk/odometry', Odometry, vis.odom_callback)
ani = FuncAnimation(vis.fig, vis.update_plot, init_func=vis.plot_init)
plt.show(block=True)
Note: change the rospy.Subscriber('/dji_sdk/odometry', Odometry, vis.odom_callback) as you need and do necessary changes accordingly.

How to exit properly a never ending live matplotlibplot

I created a basic 3D animation, showing an object in 3D space, the coordinates are coming from an Arduino.
import serial
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import matplotlib as mpl
def make_the_plot(port1, baud1, duration):
plt.ion()
mpl.rcParams['toolbar'] = 'None'
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
port = port1
baudrate = baud1
ser = serial.Serial(port, baudrate)
realtime=0
while ser and realtime<duration:
raw = (ser.readline())
splitted= raw.split()
realtime=realtime+1
#print (realtime, maxtime)
if 'reading' in splitted:
print("error in sensor reading")
msg = "Error in sensor readng "+raw
return msg
try:
ax.cla()
x=float(splitted[0])
y=float(splitted[1])
z=float(splitted[2])
ax.scatter(x,y,z)
ax.set_xlim([0, 100])
ax.set_ylim([0, 100])
ax.set_zlim([0, 100])
#fig.suptitle("Title centered above all subplots", fontsize=14)
ax.set_xlabel('x [cm]')
ax.set_ylabel('y [cm]')
ax.set_zlabel('z [cm]')
plt.tight_layout()
plt.draw()
plt.pause(0.003)
except:
pass
msg="time's up"
print (msg)
return msg
if __name__ == '__main__':
make_the_plot('COM7',9600,100)
The program works, but I can't close the plot without having issues. Most often after trying to close it the whole plot becomes white, the title changes to "Figure (Not Responding)", and after some seconds a new window with Python not responding message appears. if I call the make_the_plot() function from an other module (from a GUI) it's even worse. How can I modify the code to make closing procure working properly?
I was able to do it using threads.

Categories

Resources