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.bbox和ax.bbox,
所以我的实际问题来了:
任何人都可以帮我找到上面代码中导致不匹配的错误吗?我知道这可能是非常简单但微妙的错误。它似乎与这个线程非常相关,但我不能完全将它粘合在一起,bbox.expanded()在参数中使用copy_from_bbox()并没有太大区别
.blit()vs..draw()已经在这里讨论过。但是由于速度对我的应用程序至关重要,所以我想我必须使用 blit。重绘绘图的帧速率为 fps=10,而 blitting 运行速度快了近 10 倍。在任何情况下 - 有没有办法在使用 blit 时更新其中一个轴(例如时间轴)?(这个答案可能与问题1密切相关)
最后,关于我的应用程序的一个相当基本的问题是:由于我的传感器数据目前是在一个无限的递归循环中获取的 - 是否可以并行运行多个这样的循环,或者我应该改为使用线程,从而使我的代码变得更加复杂? 运行无限递归循环的风险是什么?或者一般应该避免这些?
经过几天来回传输后,我对 ax/fig 传输的可能性感到困惑,因此非常感谢有关此事的任何帮助^^ 如果您需要有关任何内容的更多信息,请告诉我,我希望我能很好地说明了我的问题。
非常感谢你的帮助!
这是用 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)
我搬家了
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, ,其中没有任何值。目前它只是画布上的一个空点。
从:
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()函数使窗口正确更新并跟踪它正在执行的其他操作!
我会彻底劝阻你不要走这threading条路,因为tkinter. 您必须跳过的问题和漏洞的数量是可怕的,而且很多时候,即使使用线程,程序仍然开始感觉非常缓慢且无响应。
| 归档时间: |
|
| 查看次数: |
1145 次 |
| 最近记录: |