Fast Live Plotting en Matplotlib / PyPlot

Durante años, he estado luchando para obtener una ttwig viva eficiente en matplotlib, y hasta el día de hoy sigo insatisfecho.

Quiero una función redraw_figure que actualice la figura “en vivo” (como se ejecuta el código) y mostrará las últimas gráficas si me detengo en un punto de interrupción.

Aquí hay un código de demostración:

 import time from matplotlib import pyplot as plt import numpy as np def live_update_demo(): plt.subplot(2, 1, 1) h1 = plt.imshow(np.random.randn(30, 30)) redraw_figure() plt.subplot(2, 1, 2) h2, = plt.plot(np.random.randn(50)) redraw_figure() t_start = time.time() for i in xrange(1000): h1.set_data(np.random.randn(30, 30)) redraw_figure() h2.set_ydata(np.random.randn(50)) redraw_figure() print 'Mean Frame Rate: %.3gFPS' % ((i+1) / (time.time() - t_start)) def redraw_figure(): plt.draw() plt.pause(0.00001) live_update_demo() 

Los gráficos deberían actualizarse en vivo cuando se ejecuta el código, y deberíamos ver los datos más recientes al detenernos en cualquier punto de interrupción después de redraw_figure() . La pregunta es cómo implementar mejor redraw_figure()

En la implementación anterior ( plt.draw(); plt.pause(0.00001) ), funciona, pero es muy lento (~ 3.7FPS)

Puedo implementarlo como:

 def redraw_figure(): plt.gcf().canvas.flush_events() plt.show(block=False) 

Y funciona más rápido (~ 11FPS), pero los gráficos no están actualizados cuando te paras en los puntos de ruptura (por ejemplo, si pongo un punto de interrupción en la línea t_start = ... , el segundo gráfico no aparece).

Curiosamente, lo que realmente funciona es llamar al progtwig dos veces:

 def redraw_figure(): plt.gcf().canvas.flush_events() plt.show(block=False) plt.show(block=False) 

Lo que da ~ 11FPS y mantiene los gráficos actualizados a los datos si se rompe en cualquier línea.

Ahora escuché decir que la palabra clave “bloquear” está en desuso. Y llamar a la misma función dos veces parece ser un truco extraño, probablemente no portable.

Entonces, ¿qué puedo poner en esta función que trazará a una velocidad de fotogtwigs razonable, no es un kludge gigante, y preferiblemente funcionará en backends y sistemas?

Algunas notas:

  • Estoy en OSX y usando el backend de TkAgg , pero las soluciones en cualquier backend / sistema son bienvenidas
  • El modo interactivo “Activado” no funcionará, porque no se actualiza en vivo. Simplemente se actualiza cuando está en la consola de Python cuando el intérprete espera la entrada del usuario.
  • Un blog sugirió la implementación:

def redraw_figure(): fig = plt.gcf() fig.canvas.draw() fig.canvas.flush_events()

Pero al menos en mi sistema, eso no redibuja las ttwigs en absoluto.

Entonces, si alguien tiene una respuesta, nos haría a mí y a miles de personas muy felices. Su felicidad probablemente llegaría hasta sus amigos y parientes, y sus amigos y parientes, y así sucesivamente, de modo que usted podría potencialmente mejorar las vidas de miles de millones.

Conclusiones

ImportanceOfBeingErnest muestra cómo puede usar blit para un trazado más rápido, pero no es tan simple como poner algo diferente en la función redraw_figure (necesita hacer un seguimiento de qué cosas se deben volver a dibujar).

En primer lugar, el código que se publica en la pregunta se ejecuta con 7 fps en mi máquina, con QT4Agg como back-end.

Ahora, como se ha sugerido en muchas publicaciones, como aquí o aquí , usar blit podría ser una opción. Aunque este artículo menciona que blit causa una fuerte pérdida de memoria, no pude observar eso.

He modificado un poco tu código y comparé la velocidad de cuadros con y sin el uso de blit. El código a continuación da

  • 18 fps cuando se ejecuta sin blit
  • 28 fps con blit

Código:

 import time from matplotlib import pyplot as plt import numpy as np def live_update_demo(blit = False): x = np.linspace(0,50., num=100) X,Y = np.meshgrid(x,x) fig = plt.figure() ax1 = fig.add_subplot(2, 1, 1) ax2 = fig.add_subplot(2, 1, 2) fig.canvas.draw() # note that the first draw comes before setting data h1 = ax1.imshow(X, vmin=-1, vmax=1, interpolation="None", cmap="RdBu") h2, = ax2.plot(x, lw=3) text = ax2.text(0.8,1.5, "") ax2.set_ylim([-1,1]) if blit: # cache the background axbackground = fig.canvas.copy_from_bbox(ax1.bbox) ax2background = fig.canvas.copy_from_bbox(ax2.bbox) t_start = time.time() k=0. for i in np.arange(1000): h1.set_data(np.sin(X/3.+k)*np.cos(Y/3.+k)) h2.set_ydata(np.sin(x/3.+k)) tx = 'Mean Frame Rate:\n {fps:.3f}FPS'.format(fps= ((i+1) / (time.time() - t_start)) ) text.set_text(tx) #print tx k+=0.11 if blit: # restre background fig.canvas.restre_region(axbackground) fig.canvas.restre_region(ax2background) # redraw just the points ax1.draw_artist(h1) ax2.draw_artist(h2) # fill in the axes rectangle fig.canvas.blit(ax1.bbox) fig.canvas.blit(ax2.bbox) # in this post http://bastibe.de/2013-05-30-speeding-up-matplotlib.html # it is mentionned that blit causes strong memory leakage. # however, I did not observe that. else: # redraw everything fig.canvas.draw() fig.canvas.flush_events() plt.pause(0.000000000001) #plt.pause calls canvas.draw(), as can be read here: #http://bastibe.de/2013-05-30-speeding-up-matplotlib.html #however with Qt4 (and TkAgg??) this is needed. It seems,using a different backend, #one can avoid plt.pause() and gain even more speed. live_update_demo(True) # 28 fps #live_update_demo(False) # 18 fps 

Actualizar:
Para un trazado más rápido, se puede considerar el uso de pyqtgraph .
Como dice la documentación de pyqtgraph : “Para el trazado, pyqtgraph no es tan completo / maduro como matplotlib, pero funciona mucho más rápido”.

Transmití el ejemplo anterior a pyqtgraph. Y aunque parece algo feo, funciona con 250 fps en mi maschine.

Resumiendo eso,

  • matplotlib (sin blitting): 18 fps
  • matplotlib (con blitting): 28 fps
  • pyqtgraph: 250 fps

código pyqtgraph:

 import sys import time from pyqtgraph.Qt import QtCore, QtGui import numpy as np import pyqtgraph as pg class App(QtGui.QMainWindow): def __init__(self, parent=None): super(App, self).__init__(parent) #### Create Gui Elements ########### self.mainbox = QtGui.QWidget() self.setCentralWidget(self.mainbox) self.mainbox.setLayout(QtGui.QVBoxLayout()) self.canvas = pg.GraphicsLayoutWidget() self.mainbox.layout().addWidget(self.canvas) self.label = QtGui.QLabel() self.mainbox.layout().addWidget(self.label) self.view = self.canvas.addViewBox() self.view.setAspectLocked(True) self.view.setRange(QtCore.QRectF(0,0, 100, 100)) # image plot self.img = pg.ImageItem(border='w') self.view.addItem(self.img) self.canvas.nextRow() # line plot self.otherplot = self.canvas.addPlot() self.h2 = self.otherplot.plot(pen='y') #### Set Data ##################### self.x = np.linspace(0,50., num=100) self.X,self.Y = np.meshgrid(self.x,self.x) self.counter = 0 self.fps = 0. self.lastupdate = time.time() #### Start ##################### self._update() def _update(self): self.data = np.sin(self.X/3.+self.counter/9.)*np.cos(self.Y/3.+self.counter/9.) self.ydata = np.sin(self.x/3.+ self.counter/9.) self.img.setImage(self.data) self.h2.setData(self.ydata) now = time.time() dt = (now-self.lastupdate) if dt <= 0: dt = 0.000000000001 fps2 = 1.0 / dt self.lastupdate = now self.fps = self.fps * 0.9 + fps2 * 0.1 tx = 'Mean Frame Rate: {fps:.3f} FPS'.format(fps=self.fps ) self.label.setText(tx) QtCore.QTimer.singleShot(1, self._update) self.counter += 1 if __name__ == '__main__': app = QtGui.QApplication(sys.argv) thisapp = App() thisapp.show() sys.exit(app.exec_()) 

Aquí hay una forma de hacer un trazado en vivo: obtener el trazado como una matriz de imágenes y luego dibujar la imagen en una pantalla de múltiples hilos.

Ejemplo utilizando una pantalla de pyformulas (~ 30 FPS):

 import pyformulas as pf import matplotlib.pyplot as plt import numpy as np import time fig = plt.figure() screen = pf.screen(title='Plot') start = time.time() for i in range(10000): t = time.time() - start x = np.linspace(t-3, t, 100) y = np.sin(2*np.pi*x) + np.sin(3*np.pi*x) plt.xlim(t-3,t) plt.ylim(-3,3) plt.plot(x, y, c='black') # If we haven't already shown or saved the plot, then we need to draw the figure first... fig.canvas.draw() image = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='') image = image.reshape(fig.canvas.get_width_height()[::-1] + (3,)) screen.update(image) #screen.close() 

enter image description here

Descargo de responsabilidad: soy el mantenedor de las pyformulas