Построение больших массивов в pyqtgraph
для набора анализа данных электрофизиологии мне нужно построить большой 2D-массив (dim приблизительно 20.000 x 120) точек. Я использовал виджет Matplotlib в своем приложении PyQt, но искал другие решения, потому что построение заняло довольно много времени. Тем не менее, построение данных с помощью pyqtgraph также занимает гораздо больше времени, чем ожидалось, вероятно, потому, что он перерисовывает виджет каждый раз при использовании функции plot ().
какова наилучшая практика для построения больших массивы?
примеры pyqtgraph, хотя и обширные, не помогли мне намного дальше...
import pyqtgraph as pg
view = pg.GraphicsLayoutWidget()
w1 = view.addPlot()
for n in data:
w1.plot(n)
или
w1.plot(data)
последнее правило генерирует ValueError: операнды не могут транслироваться вместе с фигурами (10) (10,120)
спасибо заранее....
1 ответов
в этом обсуждении: https://groups.google.com/forum/?fromgroups#!searchin/pyqtgraph/arraytoqpath/pyqtgraph/CBLmhlKWnfo/jinNoI07OqkJ
Pyqtgraph не перерисовывается после каждого вызова plot (); он будет ждать, пока элемент управления не вернется в цикл событий 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)
. . .
мы уже видим это окно просмотра.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.краска.)( Что делать, если мы поместим все 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_()
он начинает быстро и панорамирование / масштабирование достаточно отзывчивый. Я подчеркну, однако, что работает ли это решение для вас, вероятно, будет зависеть от деталей вашей программы.