Reputation: 25
i'm trying to do simple GUI for reflow oven controller. The GUI samples temperature every 1s (now it get it from random function, eventually from arduino). But now every 1s the ram usage increase by aprox 3.5 mb - i guess it something to do with matplot animation, and that its only drawing new images on top of old ones (lines 103- 115) but how to solve this? It runs aprox 385 sec then:
C:\Users\veeti\PycharmProjects\Reflow\venv\Scripts\python.exe C:/Users/veeti/PycharmProjects/Reflow/Main.py Exception in Tkinter callback Traceback (most recent call last): File "C:\Program Files (x86)\python37\lib\tkinter__init__.py", line 1705, in call return self.func(*args) File "C:\Program Files (x86)\python37\lib\tkinter__init__.py", line 749, in callit func(*args) File "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\backends_backend_tk.py", line 118, in _on_timer TimerBase._on_timer(self) File "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\backend_bases.py", line 1194, in _on_timer ret = func(*args, **kwargs) File "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\animation.py", line 1447, in _step still_going = Animation._step(self, *args) File "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\animation.py", line 1173, in _step self._draw_next_frame(framedata, self._blit) File "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\animation.py", line 1192, in _draw_next_frame self._draw_frame(framedata) File "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\animation.py", line 1755, in _draw_frame self._drawn_artists = self._func(framedata, self._args) File "C:/Users/veeti/PycharmProjects/Reflow/Main.py", line 115, in animate canvas.draw() File "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 9, in draw super(FigureCanvasTkAgg, self).draw() File "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\backends\backend_agg.py", line 386, in draw self.renderer = self.get_renderer(cleared=True) File "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\backends\backend_agg.py", line 399, in get_renderer self.renderer = RendererAgg(w, h, self.figure.dpi) File "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\backends\backend_agg.py", line 86, in init self._renderer = _RendererAgg(int(width), int(height), dpi) MemoryError: In RendererAgg: Out of memory
also the animation wont turn on without line 124 command "animate(1)" - why so?
import tkinter
import matplotlib.pyplot as plt
import random
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg)
def exitHandler():
print("at exit handler")
ani.event_source.stop()
root.destroy()
def startButtonClick():
print("start")
def stopButtonClick():
print("stop")
def readTemperature():
return random.randint(0,250)
def updateTimeLabel(time = -99):
timeLabel.config(text= "Time: {}".format (time))
def updateTargetTempLabel(temp = -99) :
targetTempLabel.config(text="Target temp: \n {} ".format (temp))
def updateCurrentTempLabel(temp = -99) :
currentTempLabel.config(text="Current temp: \n {} ".format (temp))
def updateHeaterStatus(temp,target):
if (temp < target):
heaterStatusLabel.config(text="Heater: \n On")
else:
heaterStatusLabel.config(text="Heater: \n Off")
def calculateTarget(time):
global timePoints, tempQuidance, targetTemp
#find current slope and calculate the target temp
for i in range (len(timePoints)):
if (timePoints[i] < time < timePoints[i+1]):
slope = (tempQuidance[i+1] - tempQuidance[i]) / (timePoints[i+1] -timePoints[i])
currentTarget = (time -timePoints[i]) * slope + tempQuidance[i]
return (currentTarget)
def animate(i):
global timePoints,tempQuidance,numberOfPoints,measuredTemp, time
time = time+1
measuredTemp.append(readTemperature())
numberOfPoints.append(len(measuredTemp))
ax.clear()
ax.plot(timePoints,tempQuidance)
ax.plot(numberOfPoints,measuredTemp)
updateTimeLabel(time)
updateTargetTempLabel(calculateTarget(time))
updateCurrentTempLabel(measuredTemp[-1])
updateHeaterStatus(measuredTemp[-1], 100)
canvas = FigureCanvasTkAgg(fig,plotFrame)
canvas.draw()
canvas.get_tk_widget().grid(row=3,column=0,columnspan = 4)
global measuredTemp, numberOfPoints, time, targetTemp
time = 0
measuredTemp=[]
numberOfPoints=[]
targetTemp = 0
#temperature profile
timePoints = [0,300,400,460,500]
tempQuidance =[0,150,150,250,0]
root=tkinter.Tk()
root.title('Reflow oven controller')
root.geometry("1600x800")
controlFrame = tkinter.LabelFrame(root,width = 500, height = 800, borderwidth = 3, padx=5,pady=5)
plotFrame = tkinter.LabelFrame(root,padx=5,pady=5)
controlFrame.grid(row = 0, column = 0 )
controlFrame.grid_propagate(0)
plotFrame.grid(row = 0, column= 1)
timeLabel=tkinter.Label(plotFrame,text = "Time: ")
targetTempLabel = tkinter.Label(plotFrame,text = "Target temp")
currentTempLabel = tkinter.Label(plotFrame,text = "Current temp")
heaterStatusLabel = tkinter.Label(plotFrame,text = "Heater status: \n unknown")
timeLabel.grid(row= 0, column=0)
targetTempLabel.grid(row=0, column= 1)
currentTempLabel.grid(row=0, column= 2)
heaterStatusLabel.grid(row = 0, column = 3)
fig=plt.figure()
ax = fig.add_subplot(1, 1, 1)
startButton = tkinter.Button(controlFrame, text = "Start", command = startButtonClick)
stopButton = tkinter.Button(controlFrame, text="Stop", command = stopButtonClick)
startButton.grid(row = 0,column = 0)
stopButton.grid(row = 0 ,column= 1 )
animate(1)
ani = animation.FuncAnimation(fig, animate, interval=1000) #run animation every 1s, animate func takes care of getting new values and GUI update
root.protocol("WM_DELETE_WINDOW", exitHandler)
root.mainloop()
thx in advance
Upvotes: 0
Views: 413
Reputation: 15236
The issue is that you are stacking canvas over and over again. Instead of recreating the canvas and stacking them like this we want to create the canvas outside of the animate
function and then just leave draw in the function.
The fix is to change this:
fig=plt.figure()
ax = fig.add_subplot(1, 1, 1)
def animate(i):
global timePoints,tempQuidance,numberOfPoints,measuredTemp, time
time = time+1
measuredTemp.append(readTemperature())
numberOfPoints.append(len(measuredTemp))
ax.clear()
ax.plot(timePoints,tempQuidance)
ax.plot(numberOfPoints,measuredTemp)
updateTimeLabel(time)
updateTargetTempLabel(calculateTarget(time))
updateCurrentTempLabel(measuredTemp[-1])
updateHeaterStatus(measuredTemp[-1], 100)
canvas = FigureCanvasTkAgg(fig,plotFrame)
canvas.draw()
canvas.get_tk_widget().grid(row=3,column=0,columnspan = 4)
To this:
fig=plt.figure()
ax = fig.add_subplot(1, 1, 1)
canvas = FigureCanvasTkAgg(fig,plotFrame)
canvas.get_tk_widget().grid(row=3,column=0,columnspan = 4)
def animate(i):
global timePoints,tempQuidance,numberOfPoints,measuredTemp, time
time = time+1
measuredTemp.append(readTemperature())
numberOfPoints.append(len(measuredTemp))
ax.clear()
ax.plot(timePoints,tempQuidance)
ax.plot(numberOfPoints,measuredTemp)
updateTimeLabel(time)
updateTargetTempLabel(calculateTarget(time))
updateCurrentTempLabel(measuredTemp[-1])
updateHeaterStatus(measuredTemp[-1], 100)
canvas.draw()
I have cleaned up your code a bit to follow PEP8 standards. See below:
import tkinter as tk
import matplotlib.pyplot as plt
import random
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
def exit_handler():
print("at exit handler")
ani.event_source.stop()
root.destroy()
def start_button_click():
print("start")
def stop_button_click():
print("stop")
def read_temperature():
return random.randint(0, 250)
time = 0
measured_temp = []
number_of_points = []
targetTemp = 0
time_points = [0, 300, 400, 460, 500]
temp_quidance = [0, 150, 150, 250, 0]
root = tk.Tk()
root.title('Reflow oven controller')
root.geometry("1600x800")
control_frame = tk.LabelFrame(root, width=500, height=800, borderwidth=3, padx=5, pady=5)
plot_frame = tk.LabelFrame(root, padx=5, pady=5)
control_frame.grid(row=0, column=0)
control_frame.grid_propagate(0)
plot_frame.grid(row=0, column=1)
time_label = tk.Label(plot_frame, text="Time: ")
target_temp_label = tk.Label(plot_frame, text="Target temp")
current_temp_label = tk.Label(plot_frame, text="Current temp")
heater_status_label = tk.Label(plot_frame, text="Heater status: \n unknown")
time_label.grid(row=0, column=0)
target_temp_label.grid(row=0, column=1)
current_temp_label.grid(row=0, column=2)
heater_status_label.grid(row=0, column=3)
def update_time_label(time=-99):
time_label.config(text="Time:\n{}".format(time))
def update_target_temp_label(temp=-99):
target_temp_label.config(text="Target temp:\n{}".format(temp))
def update_current_temp_label(temp=-99) :
current_temp_label.config(text="Current temp:\n{}".format(temp))
def update_heater_status(temp, target):
if temp < target:
heater_status_label.config(text="Heater:\nOn")
else:
heater_status_label.config(text="Heater:\nOff")
def calculate_target(time):
for i in range(len(time_points)):
if time_points[i] < time < time_points[i+1]:
slope = (temp_quidance[i+1] - temp_quidance[i]) / (time_points[i+1] - time_points[i])
current_target = (time - time_points[i]) * slope + temp_quidance[i]
return current_target
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
canvas = FigureCanvasTkAgg(fig, plot_frame)
canvas.get_tk_widget().grid(row=3, column=0, columnspan=4)
def animate(_=None):
global time
time = time+1
measured_temp.append(read_temperature())
number_of_points.append(len(measured_temp))
ax.clear()
ax.plot(time_points, temp_quidance)
ax.plot(number_of_points, measured_temp)
update_time_label(time)
update_target_temp_label(calculate_target(time))
update_current_temp_label(measured_temp[-1])
update_heater_status(measured_temp[-1], 100)
canvas.draw()
start_button = tk.Button(control_frame, text="Start", command=start_button_click)
stop_button = tk.Button(control_frame, text="Stop", command=stop_button_click)
start_button.grid(row=0, column=0)
stop_button.grid(row=0, column=1)
animate()
ani = animation.FuncAnimation(fig, animate, interval=1000)
root.protocol("WM_DELETE_WINDOW", exit_handler)
root.mainloop()
Memory close to start of animation:
Memory 1+ min later:
As you can see memory growth has stopped and no longer 3+ MB/sec.
As for the need to use animate()
for the animation to start I am not 100% sure. I have not used matplotlib
enough to know how everything works. I cannot even speculate here so someone else will have to answer that part.
Upvotes: 4