Построение больших массивов в pyqtgraph

Для набора данных анализа электрофизиологии мне нужно построить большой двумерный массив (приблизительно 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)

or

w1.plot(data)

Последнее правило генерирует ValueError: операнды не могут передаваться вместе с фигурами (10) (10 120)

Заранее спасибо....


person jw_p    schedule 14.06.2013    source источник
comment
Сколько времени вы считаете достаточно долгим? Я легко рисую 20 000 x 120 точек за секунду. Для снимка это не проблема. Я, например, хочу показать живую ЭКГ в 128 отведениях, этого недостаточно.   -  person Micke    schedule 14.06.2013
comment
Даже если я уменьшаю данные до 200х120 точек, это занимает 6 секунд. Вы используете один и тот же код?   -  person jw_p    schedule 14.06.2013
comment
Учитывая, что вы не сказали мне, как именно выглядят «данные», я не уверен. Я использовал массив numpy.empty([120, 20000], dtype=numpy.int16). Вечером могу отправить код.   -  person Micke    schedule 14.06.2013


Ответы (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)
. . .

Мы уже видим, что 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.paint(). Что, если мы поместим все 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_()

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

person Luke    schedule 14.06.2013
comment
Спасибо за развернутый ответ, я включу последний алгоритм в программу и попробую. В качестве бонуса я также узнал кое-что о профилировании приложений :) - person jw_p; 15.06.2013
comment
Спасибо за этот ответ. Я пытался последовательно читать и рисовать несколько наборов данных в кратчайшие сроки. Я добавил тривиальный цикл, используя 'lines.setPath', который работает хорошо. Однако я не могу правильно реализовать класс RemoteGraphicsView, чтобы еще больше ускорить построение графика. Не могли бы вы добавить пример? - person skytaker; 06.12.2016