簡體   English   中英

Tkinter GUI 中實時更新的 Blitting - 性能和圖像重疊問題

[英]Blitting for live update in Tkinter GUI - performance and image overlap issues

我有一些關於 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()

使我對生成的圖像感到困擾的是:當我使用語句copy_from_bbox(self.ax.bbox)我得到了這樣的圖形 顯然,blitted 背景的大小不適合 blitted 的圖像。 所以,我試圖做塊圖中的BBOX代替( copy_from_bbox(self.fig.bbox)並得到了這個發生與所有組合這些變化的版本fig.bboxax.bbox

所以我的實際問題來了:

  1. 任何人都可以幫我找到上面代碼中導致不匹配的錯誤嗎? 我知道這可能是非常簡單但微妙的錯誤。 它似乎與線程非常相關,但我無法將其粘合在一起,在copy_from_bbox()的參數中使用bbox.expanded() copy_from_bbox()並沒有太大區別

  2. .blit().draw()已經在這里討論 但是由於速度對我的應用程序至關重要,所以我想我必須使用 blit。 重繪繪圖的幀速率為 fps=10,而 blitting 運行速度快了近 10 倍。 在任何情況下 - 有沒有辦法在使用 blit 時更新其中一個軸(例如時間軸)? (這個答案可能與問題1密切相關)

  3. 最后,關於我的應用程序的一個相當基本的問題是:由於我的傳感器數據目前是在一個無限的遞歸循環中獲取的 - 是否可以並行運行多個這樣的循環,或者我應該改用線程,使我的代碼變得更加復雜? 運行無限遞歸循環的風險是什么? 或者一般應該避免這些?

經過幾天的 blitting 來回,我對 ax/fig blitting 的可能性感到很困惑,因此非常感謝有關此事的任何幫助^^ 如果您需要有關任何內容的更多信息,請告訴我,我希望我能很好地說明了我的問題。

非常感謝你的幫助!

我知道這是一個老問題,但我看到它從未解決過,所以我想我會展示解決方案:

簡而言之:解決方案

這是用 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很糟糕。 你必須跳過的問題和漏洞的數量是可怕的,而且很多時候,即使使用線程,程序仍然開始感覺很慢和反應遲鈍。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM