Tkinter GUI 中实时更新的 Blitting - 性能和图像重叠问题

Posted

技术标签:

【中文标题】Tkinter GUI 中实时更新的 Blitting - 性能和图像重叠问题【英文标题】:Blitting for live update in Tkinter GUI - performance and image overlap issues 【发布时间】:2017-06-18 09:50:37 【问题描述】:

我遇到了一些关于对 matplotlib 图进行 blitting 的问题,该图本身嵌入在 Tkinter GUI 中 - 整个程序最终将在 Raspberry Pi 上运行。问题涉及各个层面,这是我的第一个问题,如有不清楚之处,请提前道歉。

简而言之,我正在做的是:我正在开发一个 Tk GUI 以同时读取多个传感器,并且我希望对所述 GUI 上的传感器数据进行一些实时更新. 我想将每个可测量的数量放在一个单独的框架上,这就是为什么我决定为每个传感器设置一个类。其中一个传感器是流量传感器,读取并绘制如下:

import Tkinter as Tk
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime

from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

from Backend import Backend  #self written data acquisition handler  

#global variables
t0 = datetime.now() #start time of program

#Returns time difference in seconds
def time_difference(t1, t2):
    delta = t2-t1
    delta = delta.total_seconds()
    return delta

# Define Class for Flow data display
class FlowFig():
    def __init__(self, master): #master:Parent frame for plot
        #Initialize plot data
        self.t = []
        self.Flow = []

        #Initialize plot for FlowFig
        self.fig = plt.figure(figsize=(4,4))
        self.ax = self.fig.add_subplot(111)
        self.ax.set_title("Flow Control Monitor")
        self.ax.set_xlabel("Time")
        self.ax.set_ylabel("Flow")
        self.ax.axis([0,100,0,5])
        self.line = self.ax.plot(self.t, self.Flow, '-')

        #Set up canvas
        self.canvas = FigureCanvasTkAgg(self.fig, master = master)
        self.canvas.show()
        self.background = self.canvas.copy_from_bbox(self.ax.bbox)
        self.ax.grid(True)

        # Initialize handler for data aqcuisition
        self.Data= Backend() 
        self.Data.initialize()

    #update figure
    def update(self):
        # get new data values
        self.t.append(time_difference(t0, datetime.now()))
        Flow,_,_ = self.Data.get_adc_val(1)
        self.Flow.append(Flow)

        # shorten data vector if too long
        if len(self.t) > 200:
            del self.t[0]
            del self.Flow[0]

        #adjust xlims, add new data to plot
        self.ax.set_xlim([np.min(self.t), np.max(self.t)])
        self.line[0].set_data(self.t, self.Flow) 

        #blit new data into old frame
        self.canvas.restore_region(self.background)
        self.ax.draw_artist(self.line[0])
        self.canvas.blit(self.ax.bbox)
        root.after(25, Flowmonitor.Flowdata.update) #Recursive update

#Flow Frame of GUI
class FlowPage(Tk.Frame):
    def __init__(self, parent, controller):
        Tk.Frame.__init__(self,parent)    
        self.parent = parent    
        self.FlowPlot = FlowFig(self)
        self.FlowPlot.canvas.get_tk_widget().grid(row=0, column=0, rowspan=9, columnspan=9)

# Mainloop
root= Tk.Tk()
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)

Flowmonitor = FlowPage(root, root)
Flowmonitor.grid(row =0, column=0, rowspan =10, columnspan=10)
Flowmonitor.rowconfigure(0, weight=1)
Flowmonitor.columnconfigure(0, weight=1)

root.after(25, Flowmonitor.FlowPlot.update)
root.mainloop()

生成的图像让我感到困扰的是: 当我使用语句copy_from_bbox(self.ax.bbox) 时,我得到一个图表like this 显然,blitted 背景的大小不适合 blitted 的图像。所以,我尝试使用 blit 来代替图形的 bbox (copy_from_bbox(self.fig.bbox)) 并得到 this 这些转变的版本发生在fig.bboxax.bbox 的所有组合中,

所以我的实际问题来了:

    谁能帮我找到上面代码中导致不匹配的错误?我知道这可能是非常简单但微妙的错误。它似乎与this 线程非常相关,但我无法将它们完全粘合在一起,在copy_from_bbox() 的参数中使用bbox.expanded() 并没有太大区别

    .blit().draw() 已经讨论过here。 但由于速度对我的应用程序来说至关重要,我认为我必须使用 blit。重绘绘图给了我 fps=10 的帧速率,而 blitting 的运行速度几乎快了 10 倍。无论如何 - 有没有办法在使用 blit 时更新其中一个轴(例如时间轴)? (这个问题的答案大概和第一个问题密切相关)

    1234563使我的代码更加复杂?运行无限递归循环有什么风险?还是一般应该避免这些?

经过几天来回的blitting,我对关于ax/fig blitting的可能性感到相当困惑,所以任何关于此事的帮助都非常非常感谢^^ 如果您需要有关任何内容的更多信息,请告诉我,我希望我能很好地说明我的问题。

非常感谢您的帮助!

【问题讨论】:

【参考方案1】:

简而言之:解决方案

这是用 Python3 编写的,但在您的 Python2 版本中应该几乎完全相同

import tkinter as Tk
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime

from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

# from Backend import Backend  #self written data acquisition handler  
import random

#global variables
t0 = datetime.now() #start time of program

#Returns time difference in seconds
def time_difference(t1, t2):
    delta = t2-t1
    delta = delta.total_seconds()
    return delta

# Define Class for Flow data display
class FlowFig():
    def __init__(self, master): #master:Parent frame for plot
        #Initialize plot data
        self.t = []
        self.Flow = []

        #Initialize plot for FlowFig
        self.fig = plt.figure(figsize=(4,4))
        self.ax = self.fig.add_subplot(111)
        self.ax.set_title("Flow Control Monitor")
        self.ax.set_xlabel("Time")
        self.ax.set_ylabel("Flow")
        self.ax.axis([0,100,0,5])
        self.line = self.ax.plot(self.t, self.Flow, '-')

        #Set up canvas
        self.canvas = FigureCanvasTkAgg(self.fig, master = master)
        self.canvas.draw()
        self.ax.add_line(self.line[0])
        self.background = self.canvas.copy_from_bbox(self.ax.bbox)
        self.ax.grid(True)

        # # Initialize handler for data aqcuisition
        # self.Data= Backend() 
        # self.Data.initialize()

    #update figure
    def update(self):
        # get new data values
        self.t.append(time_difference(t0, datetime.now()))
        Flow = random.uniform(1, 5)
        self.Flow.append(Flow)

        # shorten data vector if too long
        if len(self.t) > 200:
            del self.t[0]
            del self.Flow[0]

        #adjust xlims, add new data to plot
        self.ax.set_xlim([np.min(self.t), np.max(self.t)])
        self.line[0].set_data(self.t, self.Flow) 

        #blit new data into old frame
        self.canvas.restore_region(self.background)
        self.ax.draw_artist(self.line[0])
        self.canvas.blit(self.ax.bbox)
        self.canvas.flush_events()
        root.after(1,self.update)

#Flow Frame of GUI
class FlowPage(Tk.Frame):
    def __init__(self, parent, controller):
        Tk.Frame.__init__(self,parent)    
        self.parent = parent    
        self.FlowPlot = FlowFig(self)
        self.FlowPlot.canvas.get_tk_widget().grid(row=0, column=0, rowspan=9, columnspan=9)

# Mainloop
root= Tk.Tk()
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)

Flowmonitor = FlowPage(root, root)
Flowmonitor.grid(row =0, column=0, rowspan =10, columnspan=10)
Flowmonitor.rowconfigure(0, weight=1)
Flowmonitor.columnconfigure(0, weight=1)

root.after(25, Flowmonitor.FlowPlot.update)
root.mainloop()

变化

迁移到 python3

我搬家了

import Tkinter as Tk

import tkinter as Tk

模拟您的 adc

我搬家了

from Backend import Backend

import random

因为我无法访问Backend,所以我只使用了一个随机数生成器(显然它不是 ADC 读数的最佳示例,但对于测试人员来说已经足够了)

设置画布

我搬家了

self.canvas = FigureCanvasTkAgg(self.fig, master = master)
self.canvas.show()
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.ax.grid(True)

self.canvas = FigureCanvasTkAgg(self.fig, master = master)
self.canvas.draw()
self.ax.add_line(self.line[0])
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.ax.grid(True)

您必须首先draw() 画布,否则matplotlib 将抛出错误AttributeError: draw_artist can only be used after an initial draw which caches the renderer。 然后,我们现在添加self.line,其中没有任何值。目前它只是画布上的一个空点。

模拟 ADC

发件人:

Flow,_,_ = self.Data.get_adc_val(1)

Flow = random.uniform(1, 5)

显然您可以为此保留自己的代码

循环使用after

您使用after 函数的系统并不完全正确,因为您应该从预先存在的窗口继承Flowmonitor.Flowdata。否则,您将更新根本不存在的值,因此,我将其替换为 self. 函数

root.after(25, Flowmonitor.Flowdata.update) 

self.canvas.flush_events()
root.after(1,self.update)

我降低了after 的值,以表明窗口可以在更快地绘制时继续正确绘制! flush_events() 函数使窗口正确更新并跟踪它正在做的其他事情!

回答问题 3

我会彻底劝阻你不要走threading 路线,因为tkinter 很糟糕。您必须跳过的问题和漏洞的数量非常可怕,而且很多时候,即使使用线程,程序仍然开始感觉很慢并且没有响应。

【讨论】:

以上是关于Tkinter GUI 中实时更新的 Blitting - 性能和图像重叠问题的主要内容,如果未能解决你的问题,请参考以下文章

在外部循环中更新 Tkinter GUI

在 Tkinter GUI 中显示 Python 脚本的输出

如何在使用 .after 时更新 GUI?

如何使用 Tkinter 创建自动更新的 GUI?

在 tkinter GUI 中更新 matplotlib 图

打印天气更新在tkinter Gui界面