Send a figure through a pipe - python

Im trying to make a gui where sensor data will be displayed on real time. My program has 4 processes and a main that gets everything going. 2 processes (lets call them sensor processes) just generate random numbers and send them through a pipe to a third process. This processes makes a matplotlib animation on a figure that then is sent to the last process who shows the figure on a tkinter gui. This is just a test to see if it is posible, the end game is to have a bunch of processes that make other animations using the data from the sensors and send them all to the gui where they can be shown when the user decides so in the gui. The problem i have is that when i execute the code the gui will show an empty figure with no animation. I dont know if this is the correct way to doit or if there is a better one all help is welcomed.
I will add the code bellow:
################### main ######################
import multiprocessing
from multiprocessing import freeze_support
from process_1 import Process_1
from process_2 import Process_2
from process_both import Figure_both
from process_GUI import Figure_distrib
if __name__ == "__main__":
freeze_support()
pipe_end_1, pipe_end_2 = multiprocessing.Pipe()
pipe_end_3, pipe_end_4 = multiprocessing.Pipe()
pipe_end_5, pipe_end_6 = multiprocessing.Pipe()
p1 = Process_1(pipe1=pipe_end_1)
p1.start()
p2 = Process_2(pipe2=pipe_end_3)
p2.start()
fb = Figure_both(pipe1=pipe_end_2, pipe2=pipe_end_4, pipe3 = pipe_end_5)
fb.start()
gui = Figure_distrib(pipe3 = pipe_end_6)
gui.start()
############### process_1 ##################
import multiprocessing
import random
class Process_1(multiprocessing.Process):
def __init__(self, pipe1=None):
multiprocessing.Process.__init__(self)
self.pipe1 = pipe1
def run(self):
while True:
y1 = random.randint(0,50)
self.pipe1.send(y1)
################### process_2 ###########################
import multiprocessing
import random
import time
class Process_2(multiprocessing.Process):
def __init__(self, pipe2=None):
multiprocessing.Process.__init__(self)
self.pipe2 = pipe2
def run(self):
while True:
y2 = random.randint(0,50)
self.pipe2.send(y2)
############## process_both ####################
import multiprocessing
import time
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import tkinter as tk
both = plt.figure()
b1 = both.add_subplot(1, 2, 1)
b2 = both.add_subplot(1, 2, 2)
class Figure_both(multiprocessing.Process):
def __init__(self, pipe1=None, pipe2=None, pipe3=None):
multiprocessing.Process.__init__(self)
self.pipe1 = pipe1
self.pipe2 = pipe2
self.pipe3 = pipe3
def Get_data(self):
if self.pipe1.poll():
y1 = self.pipe1.recv()
if self.pipe2.poll():
y2 = self.pipe2.recv()
values = (y1,y2)
return y1,y2
def animateboth(self,i):
y1, y2 = self.Get_data()
b1.clear()
b1.bar(1, y1, width = 0.4)
b1.hlines(xmin = 0, xmax = 2, y = [5,10,15,20,25,30,35,40,45,50], linestyles= '--', color = 'gray')
b2.clear()
b2.bar(1, y2, width = 0.4)
b2.hlines(xmin = 0, xmax = 2, y = [5,10,15,20,25,30,35,40,45,50], linestyles= '--', color = 'gray')
### this code sends the figure
def run(self):
ani1 = animation.FuncAnimation(both, self.animateboth, interval= 100)
self.pipe3.send(both)
###
### This code shows the figure that should be sent
# def run(self):
# gui = GUI()
#
# ani1 = animation.FuncAnimation(both, self.animateboth, interval=100)
#
# gui.mainloop()
#
#
# class GUI(tk.Tk):
# def __init__(self):
# tk.Tk.__init__(self)
#
# canvas = FigureCanvasTkAgg(both,self)
# canvas.get_tk_widget().pack()
###
############################### process_GUI ################################
import multiprocessing
import time
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import tkinter as tk
import threading
class Figure_distrib(multiprocessing.Process):
def __init__(self, pipe3=None):
multiprocessing.Process.__init__(self)
self.pipe3 = pipe3
def update_fig(self):
both = self.pipe3.recv()
return both
def run(self):
both = self.update_fig()
gui = GUI(both)
gui.mainloop()
class GUI(tk.Tk):
def __init__(self,both):
tk.Tk.__init__(self)
self.both = both
canvas = FigureCanvasTkAgg(self.both,self)
canvas.get_tk_widget().pack()

Related

Unwanted additional/current matplotlib window while embedding in tkinter gui

I'm plotting streamed data with tkinter but my app opens the current plot in an additional window:
My App looks something like this:
import tkinter as tk
from tkinter import ttk
from random import randint
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class App(tk.Tk):
def __init__(self):
super().__init__()
self.button_run = tk.Button(master=self, text='run', bg='grey', command=self.run)
self.button_run.pack()
self.fig = plt.Figure()
self.fig, self.axes_dict = plt.subplot_mosaic([['o', 'o'], ['_', '__']])
self.canvas = FigureCanvasTkAgg(figure=self.fig, master=self)
self.canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
self.canvas.draw_idle()
self.fig.canvas.flush_events()
def run(self):
S = Streamer(parent=self)
S.start()
And I stream data like this:
class Streamer:
def __init__(self, parent):
self.parent = parent
def start(self):
# plot random data
for x in range(6):
self.parent.axes_dict['o'].add_patch(Rectangle((x, randint(x, x*2)), height=0.4, width=0.4))
self.parent.axes_dict['o'].relim()
self.parent.axes_dict['o'].autoscale_view(True, True, True)
self.parent.fig.canvas.draw_idle()
self.parent.fig.canvas.flush_events()
plt.pause(0.4)
Starting the app:
if __name__ == '__main__':
A = App()
A.mainloop()
How can I close this matplotlib window and why does it appear?
You should not call plt.pause() because it will block the event loop of the tkinter.
Do like this using the after().
class Streamer:
...
def start(self):
xs = list(range(6))
def update():
if xs:
x = xs.pop(0)
parent = self.parent
parent.axes_dict['o'].add_patch(Rectangle((x, randint(x, x*2)), height=0.4, width=0.4))
parent.axes_dict['o'].relim()
parent.axes_dict['o'].autoscale_view(True, True, True)
parent.fig.canvas.draw()
parent.after(400, update)
update()
If you do a time consuming work in the update(), you are better to use a worker thread or process.

Tcl_AsyncDelete error when displaying live updating plots

I want to display a plot which updates every second (alongside other stuff) on a Tkinter window. I simply need to get a line from a data matrix and plot it, then go to the next line, and so on.
Since I need a Start/Stop button, I'm using threading.
In order to do so, I followed this post which basically does what I need.
However, after a while Python crashes and Spyder displays this error:
An error occurred while starting the kernel
Tcl_AsyncDelete: async handler deleted by the wrong thread
I tried reading about it but I didn't really find a solution or an explaination to this.
Here's a sample code:
import tkinter as tk
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import time
import threading
continuePlotting = False
line = 0
data = np.random.rand(100, 500)
def change_state():
global continuePlotting
if continuePlotting == True:
continuePlotting = False
else:
continuePlotting = True
def data_points():
global line
global data
l = line % len(data) - 1
r = data[l]
line = line+1
return r
def app():
root = tk.Tk()
root.configure(background='white')
# First Plot
top = tk.Frame(root)
top.pack(fill='both')
fig = Figure()
ax = fig.add_subplot(111)
graph = FigureCanvasTkAgg(fig, master=top)
graph.get_tk_widget().pack(fill='both')
def plotter():
while continuePlotting:
ax.cla()
dpts = data_points()
y = dpts[0:-1]
x = np.linspace(0,len(y),len(y))
ax.plot(x, y)
ax.grid(True)
graph.draw()
time.sleep(1)
def gui_handler():
change_state()
threading.Thread(target=plotter).start()
b = tk.Button(root, text="Start/Stop", command=gui_handler, bg="red", fg="white")
b.pack()
root.mainloop()
if __name__ == '__main__':
app()
Any help would be much appreciated
The basic problem is you are calling Tk functions from a non-GUI thread. Don't do that. Tk is not designed to be called from random threads. The general solution is described as an answer to a question on tkinter thread communication on this site. In short, push your calculated data onto a Queue and raise a Tk event to let the UI thread know that there is more data ready. The event handler can then fetch the new value from the queue and do UI things with it.
Attached is a modified version of your script using this mechanism.
import tkinter as tk
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import time
import threading
from queue import Queue
DATA_READY_EVENT = '<<DataReadyEvent>>'
continuePlotting = False
line = 0
data = np.random.rand(100, 500)
def change_state():
global continuePlotting
if continuePlotting == True:
continuePlotting = False
else:
continuePlotting = True
def data_points():
global line
global data
l = line % len(data) - 1
r = data[l]
line = line+1
return r
def app():
root = tk.Tk()
root.configure(background='white')
queue = Queue()
# First Plot
top = tk.Frame(root)
top.pack(fill='both')
fig = Figure()
ax = fig.add_subplot(111)
graph = FigureCanvasTkAgg(fig, master=top)
graph.get_tk_widget().pack(fill='both')
def plot(ev):
x,y = queue.get()
ax.plot(x, y)
ax.grid(True)
graph.draw()
def plotter():
global continuePlotting
while continuePlotting:
ax.cla()
dpts = data_points()
y = dpts[0:-1]
x = np.linspace(0,len(y),len(y))
queue.put((x,y))
graph.get_tk_widget().event_generate(DATA_READY_EVENT)
time.sleep(1)
def gui_handler():
change_state()
threading.Thread(target=plotter).start()
graph.get_tk_widget().bind(DATA_READY_EVENT, plot)
b = tk.Button(root, text="Start/Stop", command=gui_handler, bg="red", fg="white")
b.pack()
root.mainloop()
if __name__ == '__main__':
app()

How to update full chart after 5 seconds using matplotlib and tkinter?

What this code should do: Draw the data using function get_latest_data(1) with parameter number 1 and after 5 seconds redraw data using function get_latest_data(2) with parameter number 2
What this code do already: Draw the data using function get_latest_data(1) with parameter number 1
This is semi-functional code(works only first chart drawing)
from threading import Thread
from queue import Empty, Queue
import time
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg#, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
class tkChartGUI(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def get_latest_data(self, dataid):
x_array=[]
y_array=[]
if (dataid == 1):
x_array=[1,2,3,4,5,6,7,8,9,10,11,12,13,14];
y_array=[0.5,0.7,0.3,1.0,0.6,0.9,0.5,0.2,0.1,0.5,0.33,0.55,0.3,0.6]
if (dataid == 2):
x_array=[1,2,3,4,5,6,7,8,9,10,11,12,13,14];
y_array=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.1,1.1,1.2,1.3]
return (x_array, y_array)
def initUI(self):
self.parent.title("Simple chart")
self.parent.geometry("800x600+300+100")
result_queue = Queue()
Thread(target=self.get_latest_data, args=[result_queue], daemon=True).start()
x_array, y_array = self.get_latest_data(1)
f = Figure(figsize=(5, 3), dpi=150)
a = f.add_subplot(111)
a.set_xlabel("Values_X")
a.set_ylabel("Values_Y")
a.yaxis.grid(True, which='major')
a.xaxis.grid(True, which='major')
a.plot(x_array, y_array)
canvas = FigureCanvasTkAgg(f, master=self.parent)
canvas.show()
canvas.get_tk_widget().grid(row=0,column=0)
def display_result(a, q):
x_array = []
y_array = []
try:
x_array = q.get(block=False) # get data
y_array = q.get(block=False)
except Empty:
#a.clear()
timeout_millis = round(100 - (5000 * time.time()) % 100)
self.parent.after(timeout_millis, display_result, a, q)
a.plot(x_array, y_array)
canvas.draw()
def get_result(q):
x_array, y_array = self.get_latest_data(2)
q.put(x_array) # put data in FIFO queue x coords array
q.put(y_array) # put data in FIFO queue y coords array
display_result(a, result_queue)
def onExit(self):
self.quit()
def main():
root = tk.Tk()
my_gui = tkChartGUI(root)
root.mainloop()
if __name__ == '__main__':
main()
To run the function get_latest_data() after five secoonds, do the following:
root.after(5000, get_latest_data)
root.after : A tkinter function to execute a function after some time has elapsed.
5000 : 5000 milliseconds, i.e. 5 seconds.
get_latest_data : The function we are calling without the parentheses. To pass it arguments, use lambda, like this:
root.after(5000, lambda: get_latest_data(variable))

Animated plot using matplotlib with gtk

I did the following example code just to test how to integrate an animated matplotlib plot with pygtk. However, I get some unexpected behaviors when I run it.
First, when I run my program and click on the button (referred to as button1 in the code), there is another external blank window which shows up and the animated plot starts only after closing this window.
Secondly, when I click on the button many times, it seems that there is more animations which are created on top of each other (which gives the impression that the animated plot speeds up). I have tried to call animation.FuncAnimation inside a thread (as you can see in the comment at end of the function on_button1_clicked), but the problem still the same.
Thirdly, is it a good practice to call animation.FuncAnimation in a thread to allow the user to use the other functions of the gui ? Or should I rather create a thread inside the method animate (I guess this will create too many threads quickly) ? I am not sure how to proceed.
Here is my code:
import gtk
from random import random
import numpy as np
from multiprocessing.pool import ThreadPool
import matplotlib.pyplot as plt
import matplotlib.animation as animation
#from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
#from matplotlib.backends.backend_gtkcairo import FigureCanvasGTKCairo as FigureCanvas
class HelloWorld:
def __init__(self):
interface = gtk.Builder()
interface.add_from_file('interface.glade')
self.dialog1 = interface.get_object("dialog1")
self.label1 = interface.get_object("label1")
self.entry1 = interface.get_object("entry1")
self.button1 = interface.get_object("button1")
self.hbox1 = interface.get_object("hbox1")
self.fig, self.ax = plt.subplots()
self.X = [random() for x in range(10)]
self.Y = [random() for x in range(10)]
self.line, = self.ax.plot(self.X, self.Y)
self.canvas = FigureCanvas(self.fig)
# self.hbox1.add(self.canvas)
self.hbox1.pack_start(self.canvas)
interface.connect_signals(self)
self.dialog1.show_all()
def gtk_widget_destroy(self, widget):
gtk.main_quit()
def on_button1_clicked(self, widget):
name = self.entry1.get_text()
self.label1.set_text("Hello " + name)
self.ani = animation.FuncAnimation(self.fig, self.animate, np.arange(1, 200), init_func=self.init, interval=25, blit=True)
'''
pool = ThreadPool(processes=1)
async_result = pool.apply_async(animation.FuncAnimation, args=(self.fig, self.animate, np.arange(1, 200)), kwds={'init_func':self.init, 'interval':25, 'blit':True} )
self.ani = async_result.get()
'''
plt.show()
def animate(self, i):
# Read XX and YY from a file or whateve
XX = [random() for x in range(10)] # just as an example
YY = [random() for x in range(10)] # just as an example
self.line.set_xdata( XX )
self.line.set_ydata( YY )
return self.line,
def init(self):
self.line.set_ydata(np.ma.array(self.X, mask=True))
return self.line,
if __name__ == "__main__":
HelloWorld()
gtk.main()

Python script freezes for no apparent reason (afaik)

i'm not particularly new at python but i just thought there might be a reason for this program not working properly. i have written a similar one from which this is derived, and that still works fine. it is a program to graph the average time of a set of ping responses, to see if there is any pattern in the time over the day. the source is as follows
from Tkinter import *
import matplotlib
import time
import os, sys, threading, Queue
matplotlib.use('TkAgg')
from numpy import arange, array, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import Tkinter
import sys
class App(object):
def __init__(self):
self.q = Queue.Queue()
self.q2 = Queue.Queue()
self.avvals=[]
self.addlist=['bbc.co.uk', 'google.co.uk', 'nhgs.co.uk', 'bing.co.uk', 'msn.com']
self.addlistlen = len(self.addlist)
self.root = Tkinter.Tk()
self.root.wm_title("Connection Speed")
self.frame = Tkinter.Frame(self.root)
self.frame.pack(side='top', expand=1, fill='both',)
self.frame2 = Tkinter.Frame(self.frame)
self.frame2.pack(side='bottom', expand=1, fill='both',)
self.f = Figure(figsize=(5,4), dpi=100)
self.a = self.f.add_subplot(111)
self.gframe = Tkinter.Frame(self.frame)
self.gframe.pack(side='top', expand=1, fill='both',)
self.canvas = FigureCanvasTkAgg(self.f, master=self.gframe)
self.canvas.show()
self.canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
self.canvas._tkcanvas.pack(side='top', fill='both', expand=1)
self.lt = threading.Thread(target=self.loop())
def getping(self, add):
pingaling = os.popen("ping -n 2 "+str(add).strip())
sys.stdout.flush()
line = pingaling.read()
if line:
try:
line = line.split('Maximum = ')[1]
time = line.split('ms, Average')[0]
self.q.put(int(time))
except:
self.q.put(None)
def gpthread(self, *a):
t = threading.Thread(target=self.getping, args=a)
t.isDaemon = True
t.start()
def loop(self):
while 1:
for x in self.addlist:
self.gpthread(x)
while self.q.qsize<self.addlistlen:
pass
tl = []
for u in range(self.addlistlen):
temp = self.q.get()
if temp != None:
tl.append(temp)
if len(tl)>0:
self.update(sum(tl)/len(tl))
else:
self.update(None)
def update(self, val):
self.a.clear()
self.avvals.append(val)
self.a.plot(self.avvals, linewidth=0.5, color = 'b')
self.canvas.draw()
a = App()
try:
a.root.mainloop()
except:
a.root.destroy()
i probably dont need the bottom try..except but i put it in to check if it would make a difference.
i haven't had a chance to try it on another computer, but my other scripts are working fine so....
i simply can't comprehend why it freezes, stops responding, and if i exit it by any method i get a error saying
Fatal python error: PyEval NULL tstate
or somthing very similar.
now it doesn't even expand! it just goes straight to not responding!
Change
self.lt = threading.Thread(target=self.loop())
to
self.lt = threading.Thread(target=self.loop)
target=self.loop() calls the loop method before passing the result to threading.Thread.
Passing target=self.loop passes the method object to threading.Thread without calling it. This lets threading.Thread call the method in a new thread.
Here is some code which uses threads to ping some ips, and displays the average ping times in an animated matplotlib bar chart, embedded in a Tkinter window:
import Tkinter
import threading
import subprocess
import Queue
import shlex
import re
import matplotlib.pyplot as plt
import matplotlib.backends.backend_tkagg as tkagg
import atexit
import numpy as np
pingers=[]
def cleanup():
print('terminating ping subprocesses...')
for pinger in pingers:
pinger.proc.terminate()
atexit.register(cleanup)
class Pinger(threading.Thread):
def __init__(self,app,queue):
threading.Thread.__init__(self)
self.app=app
self.queue=queue
def run(self):
# One ping subprocess is started by each Pinger thread.
# The ping subprocess runs indefinitely, terminated by the cleanup function
# which is called by atexit right before the main program terminates.
ip = self.queue.get()
cmd="ping %s" % ip
self.proc = subprocess.Popen(shlex.split(cmd),
stdout=subprocess.PIPE)
for line in iter(self.proc.stdout.readline,''):
match=re.search('time=(.*)\s+ms',line)
if match:
avg=float(match.group(1))
self.app.update(ip,avg)
class App(object):
def __init__(self,master,ips):
self.ips=ips
self.fig = plt.Figure(figsize=(5,4), dpi=100)
self.fig.subplots_adjust(bottom=0.25)
self.ax=self.fig.add_subplot(1,1,1)
self.canvas = tkagg.FigureCanvasTkAgg(self.fig, master=master)
self.canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
self.canvas.show()
N=len(self.ips)
self.idx=dict(zip(self.ips,range(N)))
# I set an initial ping time of 200 just to make the initial bar chart
times=[200]*N
self.rects=self.ax.bar(range(N), times)
self.ax.set_xticks(np.arange(N)+0.8*0.5)
self.ax.set_xticklabels(self.ips, rotation=25)
def update(self,ip,avg):
# This is called by Pinger threads, each time a new ping value is obtained
print(ip,avg)
self.rects[self.idx[ip]].set_height(avg)
self.canvas.draw()
def main():
root = Tkinter.Tk()
root.wm_title("Connection Speed")
ips=['bbc.co.uk', 'google.co.uk', 'nhgs.co.uk', 'bing.co.uk', 'msn.com']
app = App(root,ips)
queue = Queue.Queue()
for ip in ips:
queue.put(ip)
# This starts one Pinger for each ip.
pinger=Pinger(app,queue)
pingers.append(pinger)
pinger.daemon=True
pinger.start()
Tkinter.mainloop()
if __name__=='__main__':
main()

Categories