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

JRM*_*ler 8 python user-interface tkinter matplotlib blit

我有一些关于 blitting matplotlib 图的问题,它本身嵌入在 Tkinter GUI 中 - 整个程序最终将在 Raspberry Pi 上运行。问题涉及多个层面,这是我的第一个问题,如有不明白之处,请提前见谅。

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

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()
Run Code Online (Sandbox Code Playgroud)

使我对生成的图像感到困扰的是:当我使用语句时,copy_from_bbox(self.ax.bbox)我得到了这样的图形 显然,blitted 背景的大小不适合 blitted 的图像。所以,我试图做块图中的BBOX,而不是(copy_from_bbox(self.fig.bbox)),并得到了这个 发生与所有组合这些变化的版本fig.bboxax.bbox

所以我的实际问题来了:

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

  2. .blit()vs..draw()已经在这里讨论。但是由于速度对我的应用程序至关重要,所以我想我必须使用 blit。重绘绘图的帧速率为 fps=10,而 blitting 运行速度快了近 10 倍。在任何情况下 - 有没有办法在使用 blit 时更新其中一个轴(例如时间轴)?(这个答案可能与问题1密切相关)

  3. 最后,关于我的应用程序的一个相当基本的问题是:由于我的传感器数据目前是在一个无限的递归循环中获取的 - 是否可以并行运行多个这样的循环,或者我应该改为使用线程,从而使我的代码变得更加复杂? 运行无限递归循环的风险是什么?或者一般应该避免这些?

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

非常感谢你的帮助!

wow*_*cha 2

简而言之:解决方案

这是用 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()
Run Code Online (Sandbox Code Playgroud)

变化

迁移到 python3

我搬家了

import Tkinter as Tk
Run Code Online (Sandbox Code Playgroud)

import tkinter as Tk
Run Code Online (Sandbox Code Playgroud)

模拟你的adc价值观

我搬家了

from Backend import Backend
Run Code Online (Sandbox Code Playgroud)

import random
Run Code Online (Sandbox Code Playgroud)

因为我无权访问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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

你必须首先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)
Run Code Online (Sandbox Code Playgroud)

Flow = random.uniform(1, 5)
Run Code Online (Sandbox Code Playgroud)

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

循环使用after

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

root.after(25, Flowmonitor.Flowdata.update) 
Run Code Online (Sandbox Code Playgroud)

self.canvas.flush_events()
root.after(1,self.update)
Run Code Online (Sandbox Code Playgroud)

我减小了该after值,以表明窗口在速度更快时可以继续正确绘制!这flush_events()函数使窗口正确更新并跟踪它正在执行的其他操作!

回答问题3

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