Построение больших массивов в 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_()

он начинает быстро и панорамирование / масштабирование достаточно отзывчивый. Я подчеркну, однако, что работает ли это решение для вас, вероятно, будет зависеть от деталей вашей программы.