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))
Related
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()
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()
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()
I am trying to build a simple time series viewer which will have a next button to skip to the next time series and a play button which will iterate throughout the time series dataset. The code is surprisingly super slow (1 frame per second), even if I use set_ydata. Is this as good as python get? When I press the play button the program freezes after 5-6 slides.
Here is the code:
from tkinter import Tk, Button
import numpy
import matplotlib.pyplot as pyplot
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
import scipy
from scipy import io
class Viewer:
_rawData = None
_featureMatrix = None
_root = None
_t = None
_index = 0
_T = None
_N = None
_plot = None
_nextButton = None
_figure = None
_axes = None
_canvas = None
_toolbar = None
def __init__(self, rawData, featureMatrix = []):
# keep local copy of data
self._rawData = rawData
self._featureMatrix = featureMatrix
self._T = rawData.shape[1]
self._N = rawData.shape[0]
self._t = numpy.arange(self._T)
# GUI SETUP
self._root = Tk()
self._nextButton = Button(self._root, text="next", command = self.next)
self._nextButton.grid(row=0, column=0)
self._playButton = Button(self._root, text="play", command = self.play)
self._playButton.grid(row=1, column=0)
# init figure
self._figure = pyplot.figure()
self._axes = self._figure.add_subplot(111)
self._canvas = FigureCanvasTkAgg(self._figure, master=self._root)
self._toolbar = NavigationToolbar2TkAgg(self._canvas, self._root)
self._canvas.get_tk_widget().grid(row=0,column=1)
self._toolbar.grid(row=1,column=1)
# draw first time series
self.draw_ts()
self._root.mainloop()
def next(self):
self._index = self._index + 1 % self._N
self.draw_ts()
def play(self):
for i in range(self._N): self.next()
def draw_ts(self):
pyplot.clf() # clear figure
#if self._plot is None:
self._plot, = pyplot.plot(self._t, self._rawData[self._index, :]) # the annoying comma is to output an object and not a list of objects
#else:
# self._plot.set_ydata(self._rawData[self._index, :])
pyplot.title("time series index # %d / %d" % (self._index, self._N))
#self._axes.relim()
#self._axes.autoscale_view(True,True,True)
self._canvas.draw()
if __name__ == "__main__":
x = numpy.random.random([1000, 100])
iv = Viewer(x)
oops, I think it was pyplot.clf() which caused the long delay. Now it works faster (still a bit slow ~ 7 Hz). If I turn on play, it again freezes after few slides. I think it because it tries to draw before finishing the previous drawing or something like that.
I'm running a Tkinter script that updates a plot every 5 seconds. It calls the function that plots it every 5 seconds. After not that long python starts using a lot of memory, I checked in task manager. The memory usage keeps increasing really fast. It starts a new file every 24 hours so there is a limit to the number of lines in the file.
The file starts empty.
I tried increasing the 5s time span but it does the same thing. Maybe a little slower,
also tried tried plotting every 3 rows or so but the same thing happened again.
Any idea what is causing such high memory usage and how to fix?
Thanks!
data = np.genfromtxt(filename)
time_data = data[:,0]
room_temp_data_celsius = data[:,1]
rad_temp_data_celsius = data[:,2]
fan_state_data = data[:,3]
threshold_data = data[:,4]
hysteresis_data = data[:,5]
threshold_up = [] #empty array
threshold_down = []#empty array
for i in range(0,len(threshold_data)):
threshold_up.append(threshold_data[i]+hysteresis_data[i])
threshold_down.append(threshold_data[i]-hysteresis_data[i])
# Time formatting
dts = map(datetime.datetime.fromtimestamp, time_data)
fds = matplotlib.dates.date2num(dts)
hfmt = matplotlib.dates.DateFormatter('%H:%M')
# Temperature conversion
room_temp_data_fahrenheit = map(celsius_to_fahrenheit, room_temp_data_celsius)
rad_temp_data_fahrenheit = map(celsius_to_fahrenheit, rad_temp_data_celsius)
threshold_data_fahrenheit = map(celsius_to_fahrenheit, threshold_data)
threshold_up_fahrenheit = map(celsius_to_fahrenheit, threshold_up)
threshold_down_fahrenheit = map(celsius_to_fahrenheit, threshold_down)
f = plt.figure()
a = f.add_subplot(111)
a.plot(fds,room_temp_data_fahrenheit, fds, rad_temp_data_fahrenheit, 'r')
a.plot(fds,fan_state_data*(max(rad_temp_data_fahrenheit)+4),'g_')
a.plot(fds, threshold_up_fahrenheit, 'y--')
a.plot(fds, threshold_down_fahrenheit, 'y--')
plt.xlabel('Time (min)')
plt.ylabel('Temperature '+unichr(176)+'F')
plt.legend(["Room Temperature","Radiator","Fan State","Threshold Region"], loc="upper center", ncol=2)
plt.ylim([min(room_temp_data_fahrenheit)-5, max(rad_temp_data_fahrenheit)+5])
plt.grid()
a.xaxis.set_major_formatter(hfmt)
data_graph = FigureCanvasTkAgg(f, master=root)
data_graph.show()
data_graph.get_tk_widget().grid(row=6,column=0, columnspan=3)
root.after(WAIT_TIME, control)
It's not clear to me from your code how your plots are changing with time. So I don't have any specific suggestion for your existing code. However, here is a basic example of how to embed an animated matplotlib figure in a Tkinter app. Once you grok how it works, you should be able to adapt it to your situation.
import matplotlib.pyplot as plt
import numpy as np
import Tkinter as tk
import matplotlib.figure as mplfig
import matplotlib.backends.backend_tkagg as tkagg
pi = np.pi
sin = np.sin
class App(object):
def __init__(self, master):
self.master = master
self.fig = mplfig.Figure(figsize = (5, 4), dpi = 100)
self.ax = self.fig.add_subplot(111)
self.canvas = canvas = tkagg.FigureCanvasTkAgg(self.fig, master)
canvas.get_tk_widget().pack(side = tk.TOP, fill = tk.BOTH, expand = 1)
self.toolbar = toolbar = tkagg.NavigationToolbar2TkAgg(canvas, master)
toolbar.update()
self.update = self.animate().next
master.after(10, self.update)
canvas.show()
def animate(self):
x = np.linspace(0, 6*pi, 100)
y = sin(x)
line1, = self.ax.plot(x, y, 'r-')
phase = 0
while True:
phase += 0.1
line1.set_ydata(sin(x + phase))
newx = x+phase
line1.set_xdata(newx)
self.ax.set_xlim(newx.min(), newx.max())
self.ax.relim()
self.ax.autoscale_view(True, True, True)
self.fig.canvas.draw()
self.master.after(10, self.update)
yield
def main():
root = tk.Tk()
app = App(root)
tk.mainloop()
if __name__ == '__main__':
main()
The main idea here is that plt.plot should only be called once. It returns a Line2D object, line1. You can then manipulate the plot by calling line1.set_xdata and/or line1.set_ydata. This "technique" for animation comes from the Matplotlib Cookbook.
Technical note:
The generator function, animate was used here to allow the state of the plot to be saved and updated without having to save state information in instance attributes. Note that it is the generator function's next method (not the generator self.animate) which is being called repeatedly:
self.update = self.animate().next
master.after(10, self.update)
So we are advancing the plot frame-by-frame by calling the generator, self.animate()'s, next method.