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.