Tkinter + matplotlib 动画填充内存
Posted
技术标签:
【中文标题】Tkinter + matplotlib 动画填充内存【英文标题】:Tkinter + matplot animation filling memory 【发布时间】:2020-04-19 08:01:29 【问题描述】:我正在尝试为回流炉控制器做简单的 GUI。 GUI 每 1 秒采样一次温度(现在它从随机函数中获取,最终从 arduino 获取)。但是现在每 1 秒内存使用量增加大约 3.5 mb - 我猜这与 matplot 动画有关,并且它只在旧图像之上绘制新图像(第 103-115 行)但是如何解决这个问题? 然后运行大约 385 秒:
C:\Users\veeti\PycharmProjects\Reflow\venv\Scripts\python.exe C:/Users/veeti/PycharmProjects/Reflow/Main.py Tkinter 中的异常 回调 Traceback(最近一次调用最后一次):文件“C:\ Program Files (x86)\python37\lib\tkinter__init__.py",第 1705 行,在 call 中 在 callit 中返回 self.func(*args) 文件“C:\Program Files (x86)\python37\lib\tkinter__init__.py”,第 749 行 func(*args) 文件 "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\backends_backend_tk.py", 第 118 行,在 _on_timer TimerBase._on_timer(self) 文件“C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\backend_bases.py”, 第 1194 行,在 _on_timer ret = func(*args, **kwargs) 文件“C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\animation.py”, 第 1447 行,在 _step still_going = Animation._step(self, *args) 文件“C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\animation.py”, 第 1173 行,在 _step self._draw_next_frame(framedata, self._blit) 文件“C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\animation.py”, 第 1192 行,在 _draw_next_frame self._draw_frame(framedata) 文件 "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\animation.py", 第 1755 行,在_draw_frame 中 self._drawn_artists = self._func(framedata, self._args) 文件“C:/Users/veeti/PycharmProjects/Reflow/Main.py”,第 115 行,动画 canvas.draw() 文件 "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\backends\backend_tkagg.py", 第 9 行,平局 super(FigureCanvasTkAgg, self).draw() 文件 "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\backends\backend_agg.py", 第 386 行,平局 self.renderer = self.get_renderer(cleared=True) 文件 "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\backends\backend_agg.py", 第 399 行,在 get_renderer 中 self.renderer = RendererAgg(w, h, self.figure.dpi) 文件 "C:\Users\veeti\PycharmProjects\Reflow\venv\lib\site-packages\matplotlib\backends\backend_agg.py", 第 86 行,在 init 中 self._renderer = _RendererAgg(int(width), int(height), dpi) MemoryError: In RendererAgg: Out of memory
如果没有第 124 行命令“animate(1)”,动画也不会打开 - 为什么会这样?
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()
提前谢谢
【问题讨论】:
你能提供一个Minimal, Reproducible Example吗? 转载。仅供参考,我找到了this 并尝试了垃圾收集但没有成功。 看起来您正在构建画布并将其一遍又一遍地放置在旧画布之上,而不是更新它。那会导致你的记忆问题。解决方法是更新画布。 【参考方案1】:问题是您一遍又一遍地堆叠画布。我们希望在 animate
函数之外创建画布,而不是像这样重新创建画布并堆叠它们,然后将 draw 留在函数中。
解决方法是改变这个:
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)
到这里:
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()
为了遵循 PEP8 标准,我已经稍微清理了您的代码。见下文:
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()
内存接近动画开始:
记忆 1 分钟后:
如您所见,内存增长已停止,不再超过 3 MB/秒。
至于是否需要使用animate()
来启动动画我不是100% 确定的。我没有使用足够的matplotlib
来了解一切是如何工作的。我什至无法在这里推测,因此必须由其他人来回答。
【讨论】:
以上是关于Tkinter + matplotlib 动画填充内存的主要内容,如果未能解决你的问题,请参考以下文章
使用带有 pylab/matplotlib 嵌入的 Tkinter 播放、暂停、停止功能的彩色绘图动画:无法更新图形/画布?