2013-06-14 11 views
9

Per un set di analisi dei dati di elettrofisiologia, è necessario tracciare un grande array 2D (circa 20.000 x 120) di punti. Ho usato per incorporare un widget Matplotlib nella mia applicazione PyQt, ma sono andato alla ricerca di altre soluzioni perché il tracciamento richiedeva abbastanza tempo. Tuttavia, il tracciamento dei dati con pyqtgraph richiede molto più tempo del previsto, probabilmente perché ridisegna il widget ogni volta che si usa la funzione plot().Plottaggio di array di grandi dimensioni in pyqtgraph

Qual è la procedura migliore per tracciare array di grandi dimensioni?

Gli esempi pyqtgraph, anche se vasta, non mi aiuterà molto di più ...

import pyqtgraph as pg 
view = pg.GraphicsLayoutWidget() 
w1 = view.addPlot() 

for n in data: 
    w1.plot(n) 

o

w1.plot(data) 

L'ultima regola genera ValueError: operandi non possono essere trasmessi insieme con forme (10) (10.120)

Grazie in anticipo ....

+0

Quanto tempo consideri "abbastanza lungo"? Traccio facilmente 20.000 x 120 punti in un secondo. Per un'istantanea non è un problema. Per esempio, voglio mostrare un ECG live a 128 derivazioni, non è abbastanza. – Micke

+0

Anche se riduco i dati a 200x120 punti, sono necessari 6 secondi. Usi lo stesso codice? –

+0

Dato che non mi hai detto esattamente quali sono i "dati", non ne sono sicuro. Ho usato una matrice numpy.empty ([120, 20000], dtype = numpy.int16). Posso postare codice stasera. – Micke

risposta

25

Vai a questa discussione: https://groups.google.com/forum/?fromgroups#!searchin/pyqtgraph/arraytoqpath/pyqtgraph/CBLmhlKWnfo/jinNoI07OqkJ

Pyqtgraph non ridisegna dopo ogni chiamata per tracciare(); attenderà fino a quando il controllo ritorna al ciclo di eventi Qt prima di ridisegnare. Tuttavia, è possibile che il codice imponga che il ciclo di eventi venga visitato più frequentemente chiamando QApplication.processEvents() (ciò può accadere indirettamente, ad esempio se si dispone di una finestra di dialogo di avanzamento).

In genere, la regola più importante sul miglioramento delle prestazioni è: profile your code. Non fare supposizioni su cosa potrebbe rallentarti se puoi invece misurarlo direttamente.

Poiché non ho accesso al tuo codice, posso solo immaginare come migliorarlo e mostrarti come la profilatura può aiutare. Ho intenzione di iniziare con l'esempio "lento" qui e lavorare con alcuni miglioramenti.

1. La lenta attuazione

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_() 

L'output di questo è:

Plot time: 6.10 sec 

Ora diamo il profilo it:

$ 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) 
. . . 

Già possiamo vedere che Viewbox. updateAutoRange impiega molto tempo, quindi disabilitiamo l'auto-ranging:

2. Un po 'più veloce

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_() 

..e l'output è:

Plot time: 0.68 sec 

Ecco, questo è un po' più veloce, ma panning/scalare la trama è ancora piuttosto lento.Se guardo il profilo dopo aver trascinato la trama per un po ', sembra che questo:

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) 

Così vediamo un sacco di chiamate a PlotCurveItem.paint(). Cosa succede se mettiamo tutte le 120 linee di trama in un singolo oggetto per ridurre il numero di chiamate di vernice?

3. La rapida implementazione

Dopo un paio di giri di profiling, sono arrivato fino a questo. Si basa sull'utilizzo di pg.arrayToQPath, come suggerito nel thread di cui sopra:

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_() 

Si avvia rapidamente e panning/scala è abbastanza reattivo. Sottolineerò, tuttavia, che se questa soluzione funziona per te probabilmente dipenderà dai dettagli del tuo programma.

+1

Grazie per la tua ampia risposta, implementerò l'ultimo algoritmo nel programma e provalo. Come bonus, ho anche imparato qualcosa sulla creazione di profili :) –

+0

Grazie per questa risposta. Ho cercato di leggere e disegnare più set di dati in modo sequenziale nel più breve tempo possibile. Ho aggiunto un ciclo semplice usando 'lines.setPath' che funziona bene. Tuttavia, non posso implementare correttamente la classe 'RemoteGraphicsView' per velocizzare ulteriormente il tracciamento. Potresti aggiungere un esempio? – bejota