在pyqtgraph中绘制大型数组
问题描述
对于电生理数据分析集,我需要绘制一个大的2D点阵(暗淡约20.000×120)。我过去常常在我的PyQt应用程序中嵌入一个Matplotlib小部件,但是因为绘图花费了相当长的时间,所以我开始寻找其他解决方案。不过,使用pyqtgraph绘制数据所需的时间也比预期的要长得多,可能是因为每次使用lot()函数时都会重新绘制小部件。绘制大型数组的最佳做法是什么?
pyqtgraph示例虽然内容广泛,但对我帮助不大.
import pyqtgraph as pg
view = pg.GraphicsLayoutWidget()
w1 = view.addPlot()
for n in data:
w1.plot(n)
或
w1.plot(data)
最后一个规则生成ValueError:操作数无法与Shape(10)(10,120)一起广播
提前感谢.
解决方案
查看此讨论: https://groups.google.com/forum/?fromgroups#!searchin/pyqtgraph/arraytoqpath/pyqtgraph/CBLmhlKWnfo/jinNoI07OqkJ
Pyqtgraph不是在每次调用lot()之后都重新绘制;它将一直等到控制返回到Qt事件循环后才重新绘制。但是,您的代码可能会通过调用QApplication.processEvents()来强制更频繁地访问事件循环(这可能是间接发生的,例如,如果您有一个进度对话框)。通常,提高性能最重要的规则是:分析代码。如果你可以直接衡量,不要假设什么可能会拖累你。
因为我无法访问您的代码,所以我只能猜测如何改进它,并向您展示性能分析是如何帮助您的。我将从这里的"慢"示例开始,逐步完成一些改进。
1.执行缓慢
import pyqtgraph as pg
import numpy as np
app = pg.mkQApp()
data = np.random.normal(size=(120,20000), scale=0.2) +
np.arange(120)[:,np.newaxis]
view = pg.GraphicsLayoutWidget()
view.show()
w1 = view.addPlot()
now = pg.ptime.time()
for n in data:
w1.plot(n)
print "Plot time: %0.2f sec" % (pg.ptime.time()-now)
app.exec_()
此命令的输出为:
Plot time: 6.10 sec
现在让我们分析一下:
$ python -m cProfile -s cumulative speed_test.py
. . .
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.001 0.001 11.705 11.705 speed_test.py:1(<module>)
120 0.002 0.000 8.973 0.075 PlotItem.py:614(plot)
120 0.011 0.000 8.521 0.071 PlotItem.py:500(addItem)
363/362 0.030 0.000 7.982 0.022 ViewBox.py:559(updateAutoRange)
. . .
我们已经可以看到ViewBox.updateAutoRange占用了很多时间,所以让我们禁用自动范围:
2.快一点
import pyqtgraph as pg
import numpy as np
app = pg.mkQApp()
data = np.random.normal(size=(120,20000), scale=0.2) +
np.arange(120)[:,np.newaxis]
view = pg.GraphicsLayoutWidget()
view.show()
w1 = view.addPlot()
w1.disableAutoRange()
now = pg.ptime.time()
for n in data:
w1.plot(n)
w1.autoRange() # only after plots are added
print "Plot time: %0.2f sec" % (pg.ptime.time()-now)
app.exec_()
..,输出为:
Plot time: 0.68 sec
所以有点快,但是平移/缩放绘图仍然相当慢。如果我在拖动绘图一段时间后查看配置文件,它看起来如下所示:
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.034 0.034 16.627 16.627 speed_test.py:1(<module>)
1 1.575 1.575 11.627 11.627 {built-in method exec_}
20 0.000 0.000 7.426 0.371 GraphicsView.py:147(paintEvent)
20 0.124 0.006 7.425 0.371 {paintEvent}
2145 0.076 0.000 6.996 0.003 PlotCurveItem.py:369(paint)
所以我们看到很多对PlotCurveItem.aint()的调用。如果我们将所有120条绘图线放在一个项目中,以减少绘制调用的数量,会怎么样?
3.快速实施
经过几轮分析后,我想出了这个。它基于使用pg.arrayToQPath,如上面的线程所建议:
import pyqtgraph as pg
import numpy as np
app = pg.mkQApp()
y = np.random.normal(size=(120,20000), scale=0.2) + np.arange(120)[:,np.newaxis]
x = np.empty((120,20000))
x[:] = np.arange(20000)[np.newaxis,:]
view = pg.GraphicsLayoutWidget()
view.show()
w1 = view.addPlot()
class MultiLine(pg.QtGui.QGraphicsPathItem):
def __init__(self, x, y):
"""x and y are 2D arrays of shape (Nplots, Nsamples)"""
connect = np.ones(x.shape, dtype=bool)
connect[:,-1] = 0 # don't draw the segment between each trace
self.path = pg.arrayToQPath(x.flatten(), y.flatten(), connect.flatten())
pg.QtGui.QGraphicsPathItem.__init__(self, self.path)
self.setPen(pg.mkPen('w'))
def shape(self): # override because QGraphicsPathItem.shape is too expensive.
return pg.QtGui.QGraphicsItem.shape(self)
def boundingRect(self):
return self.path.boundingRect()
now = pg.ptime.time()
lines = MultiLine(x, y)
w1.addItem(lines)
print "Plot time: %0.2f sec" % (pg.ptime.time()-now)
app.exec_()
启动速度很快,平移/缩放的响应速度相当快。不过,我要强调的是,此解决方案是否适用于您可能取决于您的程序的详细信息。
相关文章