【问题标题】:Pyqtgraph class: how to automatically update graph values from data buffer?Pyqtgraph 类:如何从数据缓冲区自动更新图形值?
【发布时间】:2018-11-23 12:37:11
【问题描述】:

我正在使用pyqtgraph 模块制作一个漂亮而简单的实时图表。我想将其作为可以接收数据缓冲区的类/对象,并在更新时重新读取数据缓冲区以绘制图形。我在将数据缓冲区值从类代码外部获取到对象中时遇到了一些问题。

代码如下:

import pyqtgraph as pg
# pip install pyqtgraph


class App(QtGui.QMainWindow):
    def __init__(self, buffer_size, data_buffer, graph_title, parent=None):
        super(App, self).__init__(parent)

        #### Create Gui Elements ###########
        self.mainbox = QtGui.QWidget()
        self.setCentralWidget(self.mainbox)
        self.mainbox.setLayout(QtGui.QVBoxLayout())

        self.canvas = pg.GraphicsLayoutWidget()
        self.mainbox.layout().addWidget(self.canvas)

        self.label = QtGui.QLabel()
        self.mainbox.layout().addWidget(self.label)

        self.view = self.canvas.addViewBox()
        self.view.setAspectLocked(True)
        self.view.setRange(QtCore.QRectF(0,0, 100, 100))

        self.numDstreams = 1
        self.bufferLength = buffer_size
        self.dataBuffer = data_buffer
        self.graphTitle = graph_title
        
        self.otherplot = [[self.canvas.addPlot(row=i,col=0, title=self.graphTitle)] # , repeat line for more
                           for i in range(0,self.numDstreams)]
        self.h2 = [[self.otherplot[i][0].plot(pen='r')] for i in range(0,self.numDstreams)] # , self.otherplot[i][1].plot(pen='g'), self.otherplot[i][2].plot(pen='b')
        self.ydata = [[np.zeros((1,self.bufferLength))] for i in range(0,self.numDstreams)] # ,np.zeros((1,self.bufferLength)),np.zeros((1,self.bufferLength))
        
        for i in range(0,self.numDstreams):
            self.otherplot[i][0].setYRange(min= -100, max= 100) 

        self.counter = 0
        self.fps = 0.
        self.lastupdate = time.time()

        #### Start  #####################
        self._update()

    def _update(self):
    
        
        for i in range(0,self.numDstreams):
            self.ydata[i][0] = np.array(self.dataBuffer)
            
            self.h2[i][0].setData(self.ydata[i][0])
            
        
        now = time.time()
        dt = (now-self.lastupdate)
        if dt <= 0:
            dt = 0.000000000001
        fps2 = 1.0 / dt
        self.lastupdate = now
        self.fps = self.fps * 0.9 + fps2 * 0.1
        tx = 'Mean Frame Rate:  {fps:.3f} FPS'.format(fps=self.fps )
        self.label.setText(tx)
        QtCore.QTimer.singleShot(1, self._update)
        self.counter += 1
        
        
def CreateGraph(buffer_size, data_buffer, graph_title): 
        
    app1 = QtGui.QApplication(sys.argv)
    thisapp1 = App(buffer_size, data_buffer, graph_title)
    thisapp1.show()
    
    sys.exit(app1.exec_())
    return app1
    
if __name__ == "__main__":
    
    test_buffer = np.random.randn(100,)
    
    app = CreateGraph(100, test_buffer, "Activity Score")
    
    while 1:
        test_buffer = np.random.randn(100,)
        app._update()

代码的工作原理是绘制随机数据的初始图。但是,它不会像我想要的那样在循环中更新。当我使用这个对象时,我希望它根据外部变量更新其图形数据缓冲区,正如我正在尝试的那样。相反,它是堆叠的,即它只第一次读取数据。

编辑

明确地说,我期待 test_buffer = np.random.randn(100,) app._update() 在循环中不断更新图表。我需要图表能够实时读取缓冲区变量并绘制新数据。

我该怎么做?

【问题讨论】:

  • 你可以更好地解释自己,你期望test_buffer = np.random.randn(100,) app._update() update the graph?
  • 是的,这就是我希望发生的事情,我会补充问题。
  • 您希望多久更新一次?
  • Pyqt 图形模块非常快,本质上更新循环以每秒数百帧的速度读取,然后重新绘制。所以尽可能快。
  • 好吧,我理解你,但不要指望超过60FPS,你计算的FPS不正确,你只是计算函数执行_update()的时间,不能保证画打印时为 300FPS,Qt 和所有库图表不会在必要时自动更新视图,例如 60FPS 为 16.6ms,假设您以 2ms 的间隔放置数据,那么每 8 个数据中只有 1 个将显示.我可以让它更新,但我表明你的计算是不真实的。

标签: python graph pyqtgraph graph-visualization


【解决方案1】:

在 cmets 中,指出您的计算不正确,因此我将从我的答案中删除 _update() 方法。

言归正传,exec_() 方法创建了一个事件循环,它在概念上为 while True,因此在该行之后不会执行其他代码行,因此您来自 while 1: 的代码永远不会执行。

另一方面,如果我们消除它,我们不能将while 1: 放置在 GUI 线程中,因为它会阻止它并且不允许 GUI 查看各种事件或更新 GUI,例如绘画任务。

此外,如果您使用 test_buffer = np.random.randn(100,) 并不意味着 self.dataBuffer 已更新,则它们不会链接。

解决办法是把while 1:放到一个新线程中,通过信号的方式将数据发送到主线程。

import sys

import threading

import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg


class App(QtGui.QMainWindow):
    def __init__(self, buffer_size=0, data_buffer=[], graph_title="", parent=None):
        super(App, self).__init__(parent)

        #### Create Gui Elements ###########
        self.mainbox = QtGui.QWidget()
        self.setCentralWidget(self.mainbox)
        self.mainbox.setLayout(QtGui.QVBoxLayout())

        self.canvas = pg.GraphicsLayoutWidget()
        self.mainbox.layout().addWidget(self.canvas)

        self.label = QtGui.QLabel()
        self.mainbox.layout().addWidget(self.label)

        self.view = self.canvas.addViewBox()
        self.view.setAspectLocked(True)
        self.view.setRange(QtCore.QRectF(0,0, 100, 100))

        self.numDstreams = 1
        self.bufferLength = buffer_size
        self.graphTitle = graph_title

        self.otherplot = [[self.canvas.addPlot(row=i,col=0, title=self.graphTitle)] # , repeat line for more
                           for i in range(0,self.numDstreams)]
        self.h2 = [[self.otherplot[i][0].plot(pen='r')] for i in range(0,self.numDstreams)] # , self.otherplot[i][1].plot(pen='g'), self.otherplot[i][2].plot(pen='b')
        self.ydata = [[np.zeros((1,self.bufferLength))] for i in range(0,self.numDstreams)] # ,np.zeros((1,self.bufferLength)),np.zeros((1,self.bufferLength))

        for i in range(0,self.numDstreams):
            self.otherplot[i][0].setYRange(min= -100, max= 100) 
        self.update_plot(data_buffer)

    def update_plot(self, data):
        self.dataBuffer = data
        for i in range(0, self.numDstreams):
            self.ydata[i][0] = np.array(self.dataBuffer)
            self.h2[i][0].setData(self.ydata[i][0])


def CreateGraph(graph_title): 
    thisapp1 = App(graph_title=graph_title)
    thisapp1.show()
    return thisapp1

class Helper(QtCore.QObject):
    bufferChanged = QtCore.pyqtSignal(object)

def generate_buffer(helper):
    while 1:
        test_buffer = np.random.randn(100,)
        helper.bufferChanged.emit(test_buffer)
        QtCore.QThread.msleep(1)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)

    graph = CreateGraph("Activity Score")
    helper = Helper()
    threading.Thread(target=generate_buffer, args=(helper, ), daemon=True).start()
    helper.bufferChanged.connect(graph.update_plot)

    if sys.flags.interactive != 1 or not hasattr(QtCore, 'PYQT_VERSION'):
        sys.exit(app.exec_())

【讨论】:

  • 谢谢你的全面回答,我明天去测试。关于计算(我认为您指的是 FPS),我不确定它们是否正确,这是我在其他地方找到的代码,刚刚开始使用。
  • 我试过了,它很实用,但不是我需要的。如何将此类包含在另一个文件中,然后使用其他代码中的变量作为该对象然后读取和绘制的数据缓冲区(不断更新)?
  • @JDS 我不明白,你会明白你的想法非常广泛和抽象,我的解决方案解决了你所展示的,而不是你想要的,不要放在你的问题中。如果你不清楚你的问题,不要指望你没有提到的答案。
猜你喜欢
  • 2018-04-25
  • 1970-01-01
  • 2021-12-22
  • 1970-01-01
  • 1970-01-01
  • 2021-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多