I am working on a Tkinter project where I am plotting live data from different sensors. I am using Tkinter to create the GUI and matplotlib animation.FuncAnimation to create live plots that update every given time (i.e. 1 second). The code is written in python 3.
This work fine as long as the total number of point is small. As the number of points exceed 300-400 the system starts accumulating delay, slows down and eventually freezes. I.e. if I aim to have a reading every 1 second, in the beginning the system returns a reading every 1 second and few ms. However as the time goes by, it starts increasing the interval with a linear trend (please see image below)
I can reproduce the problem by creating a single plot having on the x-axis the number of iteration (i.e. readings) and on the y-axis the delta time between each iteration and updating the plot every second (even if I use a longer time interval the result is the same).
I have tried to put the animation function in an independent thread, as it was suggested in other posts, but it did not help at all.
If I do not create the plot but I use the generated data (i.e. delta time) to update labels in tkinter I do not have the issue, so it must be related with the creation/update of the plot.
Switching on/off blit does not help, and I would prefer to keep it off anyway.
Please see below a short version of the code to reproduce the error and the output image after 600 iterations.
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends import backend_tkagg as bk
import matplotlib.animation as animation
import numpy as np
import time
import threading
class Application(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
# =============================================================================
# # Test 1 flags initialization
# =============================================================================
self.ani = None
# =============================================================================
# Canvas frame
# =============================================================================
self.fig = plt.figure(figsize=(15,5))
frm = tk.Frame(self)
frm.pack()
self.canvas = bk.FigureCanvasTkAgg(self.fig, master=frm)
self.canvas.get_tk_widget().pack()
# =============================================================================
# # Figure initialization
# =============================================================================
self.ax = self.fig.add_subplot(1,1,1)
self.ax.plot([],[],'-k', label='delta time')
self.ax.legend(loc='upper right')
self.ax.set_xlabel('n of readings [-]')
self.ax.set_ylabel('time difference between readings [s]')
# =============================================================================
# # Start/Quick button
# =============================================================================
frm4 = tk.Frame(self)
frm4.pack(side='top', fill='x')
frm_acquisition = tk.Frame(frm4)
frm_acquisition.pack()
self.button= tk.Button(frm_acquisition, text="Start acquisition", command=lambda: self.on_click(), bg='green')
self.button.grid(column = 0, row=0)
# =============================================================================
# # Methods
# =============================================================================
def on_click(self):
'''the button is a start/stop button '''
if self.ani is None:
self.button.config(text='Quit acquisition', bg='red')
print('acquisition started')
return self.start()
else:
self.ani.event_source.stop()
self.button.config(text='Start acquisition', bg='green')
print('acquisition stopped')
self.ani = None
return
def start(self):
self.starting_time = time.time()
self.time = np.array([])
self.ani = animation.FuncAnimation(self.fig, self.animate_thread, interval =1000, blit=False, cache_frame_data=False) #interval in ms
self.ani._start()
return
# Some post suggested to put animate() in an indipendent thread, but did not solve the problem
def animate_thread(self, *args):
self.w = threading.Thread(target=self.animate)
self.w.daemon = True # Daemonize the thread so it exits when the main program finishes
self.w.start()
return self.w
def animate(self, *args):
self.time = np.append(self.time, time.time()-self.starting_time)
if len(self.time) > 1:
self.ax.scatter(len(self.time),self.time[-1]-self.time[-2], c='k')
# root.update() # another post suggested root.update() but did not help either
return self.ax,
if __name__ == "__main__":
root = tk.Tk()
app=Application(root)
app.pack()
root.mainloop()
Time delay plot:
Most of the examples matplotlib animation suggests creating the plot axes during intialization of animation instead of creating inside animate function, as your above part of code shows.
So a possible way can be create scatter object and hold it in a variable which is intialized once. it actually returns matplotlib collections. Something like this for example,
self.ax = self.fig.add_subplot(1,1,1)
self.ax.plot([],[],'-k', label='delta time')
self.ax.legend(loc='upper right')
self.ax.set_xlabel('n of readings [-]')
self.ax.set_ylabel('time difference between readings [s]')
self.scatplot = self.ax.scatter(x, y, c='k') #here x and y are array data initalized empty.
And in animate function, you can utilize 'self.scatplot' like this below with proper data format and customization, Refer here How to animate a scatter plot and FuncAnimation
def animate(self, *args):
self.time = np.append(self.time, time.time()-self.starting_time)
data = np.stack([len(self.time), self.time[-1]-self.time[-2]])
if len(self.time) > 1:
self.scatplot.set_offsets(data)
return self.scatplot,