使用 PyQtGraph 读取实时数据而不附加数据

Posted

技术标签:

【中文标题】使用 PyQtGraph 读取实时数据而不附加数据【英文标题】:Reading live data using PyQtGraph without appending the data 【发布时间】:2016-09-01 23:15:14 【问题描述】:

我正在尝试使用我开始学习的PyQtgraph 绘制实时数据。我在这里看了很多帖子,比如:

Fast plotting using pyqtgraph

并在互联网上搜索文档。我遇到的问题是绘制数据时绘图非常慢。这是因为,当数据来自串行端口时,我将其附加到一个列表中,然后从该列表中取出并绘制它。因此,即使在我关闭了数据来源的设备后,绘图仍会继续绘制。

这是我的代码:

class LivePlot(QtGui.QApplication):
  def __init__(self, *args, **kwargs):
    super(MyApplication, self).__init__(*args, **kwargs)
    self.t = QTime()
    self.t.start()

    self.data = deque()
    self.plot = self.win.addPlot(title='Timed data') 
    self.curve = self.plot.plot()

    print "Opening port"
    self.raw=serial.Serial("com4",115200)
    print "Port is open"

    self.tmr = QTimer()
    self.tmr.timeout.connect(self.update)
    self.tmr.start(100)

    self.cnt = 0

  def update(self):
    line = self.raw.read()
    ardString = map(ord, line)
    for number in ardString:
        numb = float(number/77.57)
        self.cnt += 1
        x = self.cnt/2

        self.data.append('x': x , 'y': numb)  
        x = [item['x'] for item in self.data]
        y = [item['y'] for item in self.data]
        self.curve.setData(x=x, y=y)

那么,如何在不将数据附加到列表的情况下绘制数据?我是这个图书馆的新手,所以我很困惑。希望你能帮助我。

---- 编辑----

我已经在代码中尝试过这种修改:

class LivePlot(QtGui.QApplication):
  def __init__(self, *args, **kwargs):
    super(MyApplication, self).__init__(*args, **kwargs)
   self.cnt = 0
 #Added this new lists
   self.xData = []
   self.yData = []

  def update(self):
    line = self.raw.read()
    ardString = map(ord, line)
    for number in ardString:
        numb = float(number/77.57)
        self.cnt += 1
        x = self.cnt/2

        self.data.append('x': x , 'y': numb) 
        self.xData.append(x)
        self.yData.append(numb) 

    self.curve.setData(x=self.xData, y=self.yData)

但是我遇到了同样的问题。

【问题讨论】:

【参考方案1】:

正如我在 cmets 中提到的,setData() 重新绘制了整个系列。如果一次只修改/添加一小部分,效率非常低。

我的解决方案是继续为系列创建和添加新的 PlotDataItems(或 BarGraphItems,...),并将 PlotWidget 设置为仅重新绘制添加/删除的项目。

QGraphicsView 的 viewportUpdateMode 的默认设置是 QGraphicsView::MinimalViewportUpdate 但 QGraphicsScene 花费大量时间来确定最小更新。要禁用此设置,请将 PlotWidget 的 viewportUpdateMode() 设置为 QGraphicsView::BoundingRectViewportUpdate 。

plot = pg.PlotWidget()
plot.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)

关闭项目索引对于实时图表等动态场景是有利的。默认情况下,BSP 树用于索引。添加和删​​除项目,以及所有 QGraphicsScene 的定位算法,具有对数时间复杂度。在没有索引的情况下,添加和删除项目以恒定时间运行,项目查找以线性时间运行。因为随着项目的添加和删除,树的深度正在被优化,所以在有很多项目的场景和动态场景中,维护索引可能会超过对数查找的速度优势。

对于动态场景或具有许多动画项目的场景,索引簿记可以超过快速查找速度。

Qt5 QGraphicsScene::itemIndexMethod

plot.scene().setItemIndexMethod(QGraphicsScene.NoIndex)

或者,如果您知道绘图的约束(最大项目数)并将其设置为固定值,您可以预先计算最佳树深度:

# scene - total length of the scene in your units (seconds, ...)
# segment - minimal length of a single segment

depth = math.ceil(math.log(scene / segment, 2) + 1)
plot.scene().setBspTreeDepth(depth)

Qt5 QGraphicsScene::bspTreeDepth

设置 QGraphicsView.DontAdjustForAntialiasing 优化标志并完全关闭抗锯齿也有帮助。设置此标志可加快绘画/重绘。

plot.setAntialiasing(False)
plot.setOptimizationFlag(QGraphicsView.DontAdjustForAntialiasing)

在每个新添加的项目之后,显式处理所有未决的 Qt 事件也可能会有所帮助:

QtGui.QApplication.processEvents()

另一个改进可以通过禁用小部件更新和重新绘制来实现,除非在选定的情况下,例如添加新项目或调整窗口大小时。

这样做的原因是,如果用户在窗口之外单击,则该窗口会在 PlotWidgets 上调用 paint(),这会触发所有 PlotDataItems 的重新绘制。当用户点击回到窗口时也是如此。大多数情况下,这是完全没有必要的,如果场景中有很多 PlotDataItem,它会暂时冻结应用程序。

但是方法 setUpdatesEnabled() 不能使用,因为 setUpdatesEnabled(True) 会自动调用整个小部件上的 update() 触发所有项目上的paint(),这正是我们想要避免的。

isShown = False

def setUpdatesDisabled(self, widget, value):
    try:
        widget.setAttribute(Qt.WA_UpdatesDisabled, value)
        widget.setAttribute(Qt.WA_ForceUpdatesDisabled, value)
    except AttributeError:
        pass
    for child in widget.children():
        self.setUpdatesDisabled(child, value)

def myWorkMethod(self):
    self.setUpdatesDisabled(self.plot, False)

    ...

    QtGui.QApplication.processEvents()
    self.setUpdatesDisabled(self.plot, True)

def changeEvent(self, event):
    if event.type() == QEvent.ActivationChange:
        if self.isShown:
            self.setUpdatesDisabled(self.plot, False)
        else:
            self.isShown = True

def resizeEvent(self, event):
    self.plot.setUpdatesEnabled(True)
  
    

可能更快但不太灵活的解决方案是将其简单地绘制为 QImage 并且根本不使用 PyQtGraph。

【讨论】:

感谢您的全面回答。它提供了很多好主意。就个人而言,我不会禁用小部件更新和自动重绘,因为在某些情况下您无法避免重绘。例如,在调整窗口大小时,就像你说的那样,而且在缩放平移时也是如此。如果应用程序在这些情况下仍然冻结几秒钟,我不认为它是一个完整的解决方案。但也许这只是我。另外,您如何处理添加项目的线段之间的间隙?您是否在边界处绘制重复点?【参考方案2】:

对于从设备读取的每个数字,您将从头开始重新创建 xy 数组。这意味着,如果您有 1000 个数据点,您将遍历 1000 个元素的列表 1000 次,这相当于一百万步。可以想象,这不能很好地扩展。您的执行时间以n^2 的系数增长,其中n 是您测量的点数。谷歌'computational complex''big O notation' 并阅读它。

追加到 Python 列表(或双端队列)速度很快,所花费的时间不取决于列表中元素的数量(注意,对于追加到 Numpy 数组,不是 )。如果您创建 self.xDataself.yData 列表,然后将数据附加到这些列表中,则可以摆脱内部循环:

def update(self):
    line = self.raw.read()
    ardString = map(ord, line)
    for number in ardString:
        numb = float(number/77.57)
        self.cnt += 1
        x = self.cnt/2

        # Appending goes in constant time 
        self.data.append('x': x , 'y': numb)  
        self.xData.append(x)    # self.xData should be a list or deque
        self.yData.append(numb) # self.yData should be a list or deque

    # Drawing the plot is now only performed once per update
    self.curve.setData(x=self.xData, y=self.yData)

此外,在setData 中发生的重绘绘图是一项昂贵的操作,您在读取每个数据点后执行此操作。更好的解决方案是每次更新只需更新一次绘图。请参见上面的示例。这仍然是每秒十次(至少,如果您的绘图可以跟上数据的话)。

最后,与硬件的通信是一个棘手的问题,您的第一次尝试无疑会不稳定并且充满错误。如果您将此作为一个爱好项目,那么这是一个很好的学习机会。如果您是在专业环境中这样做,那么请尝试是否可以找到导师。

【讨论】:

抱歉回复晚了,感谢您的回复。在self.yData.append(x) 行中,应该有“麻木”而不是“x”,对吧?在self.curve.setData(x=x, y=y) 中,我应该用 self.xData 和 self.yDAta 替换 x 和 y 吗? 我已经用你的答案修改了代码,但它仍然不起作用。我的意思是我仍然有同样的问题。也许我做错了什么。希望您能够帮助我。 P.S.:谢谢你的链接,我学到了一些新东西 10 秒后,我在每个列表中有大约 562 个项目。我打印每个列表的长度:len(xData)len(yData),我还使用了 QTime() @mhrvth,我认为不可能。请注意,它不会删除您的整个情节,而是“仅”删除所有数据点。保留轴和标签等内容。禁用自动缩放可以显着提高性能。绘制 lineWidth 为 1 的线也比粗线快得多。 @titusjan 我想唯一的解决方案就是继续为每个新数据点创建和添加新的 PlotDataItems

以上是关于使用 PyQtGraph 读取实时数据而不附加数据的主要内容,如果未能解决你的问题,请参考以下文章

即使设备断开连接,使用 PyQtGraph 的实时绘图仍在绘制

pyqtgraph:如何在 00:00 时间之前不显示时间序列(使用 AxisItem)

如何在 PyQtGraph 的一个图中绘制两个实时数据?

绘制实时传感器数据时,PyQtGraph 停止更新并冻结

使用 pyqtgraph 和线程进行实时绘图

pyqtgraph / PlotCurveItem 的实时可视化瓶颈