使用 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】:对于从设备读取的每个数字,您将从头开始重新创建 x
和 y
数组。这意味着,如果您有 1000 个数据点,您将遍历 1000 个元素的列表 1000 次,这相当于一百万步。可以想象,这不能很好地扩展。您的执行时间以n^2
的系数增长,其中n
是您测量的点数。谷歌'computational complex' 或'big O notation' 并阅读它。
追加到 Python 列表(或双端队列)速度很快,所花费的时间不取决于列表中元素的数量(注意,对于追加到 Numpy 数组,不是 )。如果您创建 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
# 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 的实时绘图仍在绘制