How to exit properly a never ending live matplotlibplot - python

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.

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

Matplotlib Animation, plotting data from a while True:

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

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

Plotting a cumulative graph in Python

I'm new to the world of python. I'm trying to create a plot showing accumulated GDD (Growing degree days) vs time for selected cities within a given period of time
I wrote a function to accomplish this, but I can't seem to get it to work right. I keep getting with an empty plot in it. Also a dimension error. Anyone know how i can solve these issues.
Any help/suggestions is appreciated!
Here is the code
import sys, argparse, csv
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import datetime as dt
def open_file(file_name, mode):
"""Open a file."""
try:
with open(sys.argv[1], 'r') as the_file:
filename=the_file.read()
except IOError as err:
print("Unable to open the file", file_name, "Ending program.\n", err)
input("\n\nPress the enter key to exit.")
sys.exit()
else:
return filename
"""Cities.txt file as an argument"""
try:
my_file = open_file(sys.argv[1], 'r')
except:
print("GDD needs one argument", "\nEnding program...\n")
input("Press the enter key to exit.")
sys.exit()
extension=".csv"
for line in my_file.splitlines():
file_name=line+extension
print(file_name)
with open('data/'+ file_name, "rU") as files:
val = list(csv.DictReader(files))
l = []
for i in range(0, len(val)):
row = val[i]
if row['Min Temp'] == '' or row['Max Temp'] == '':
pass
else:
GDD = ((float(row['Min Temp']) + float(row['Max Temp']))/2)-10
l.append(GDD)
val[i]['GDD'] = GDD
#print(row['Min Temp'], ' ' , row['Max Temp'], ' ', str(round(row['GDD'], 2)))
#plt.subplot(1,1,1)
#x = np.linspace(1, 12, 365, endpoint=True)
x = np.linspace(1, 12, 365, endpoint=True)
plt.plot(x,GDD, label = file_name.split(',')[0])
plt.gcf().autofmt_xdate()
plt.legend(loc="upper left")
plt.xlabel('Months', color='black')
plt.ylabel('Cumulative GDD (>10°C)')
plt.title('Accumulated Growing Degree Days')
plt.draw()
A simple and fast solution could be to add plt.hold(True) after a plot call.
A more clean solution is to use a figure which can bee seen as window which you draw onto. just like in this example
So you'd define a figure, keep its reference, add axes and perform all action on these axes
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.4, 0.7])
ax.plot(...)

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.

Categories

Resources