Jarenlang heb ik geworsteld om efficiënt live plotten in matplotlib te krijgen, en tot op de dag van vandaag ben ik ontevreden.
Ik wil een functie redraw_figure
die het cijfer “live” bijwerkt (terwijl de code wordt uitgevoerd), en de laatste plots weergeeft als ik stop bij een breekpunt.
Hier is een democode:
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()
Plots moeten live worden bijgewerkt wanneer de code wordt uitgevoerd, en we zouden de laatste gegevens moeten zien wanneer we stoppen bij een onderbrekingspunt na redraw_figure()
. De vraag is hoe je redraw_figure()
het beste kunt implementeren
In de bovenstaande implementatie (plt.draw(); plt.pause(0.00001)
), werkt het, maar is het erg traag (~3.7FPS)
Ik kan het implementeren als:
def redraw_figure():
plt.gcf().canvas.flush_events()
plt.show(block=False)
En het werkt sneller (~11FPS), maar plots zijn niet up-to-date wanneer je stopt bij breekpunten (bijvoorbeeld als ik een breekpunt plaats op de regel t_start = ...
, de tweede plot verschijnt niet).
Vreemd genoeg, wat echt werkt, is de show twee keer bellen:
def redraw_figure():
plt.gcf().canvas.flush_events()
plt.show(block=False)
plt.show(block=False)
Dat geeft ~11FPS en houdt plots up-to-data als je pauze op een regel.
Nu heb ik horen zeggen dat het “block”-sleutelwoord is verouderd. En dezelfde functie twee keer aanroepen lijkt sowieso een rare, waarschijnlijk niet-draagbare hack.
Dus wat kan ik in deze functie plaatsen die met een redelijke framesnelheid kan plotten, geen gigantische kludge is en bij voorkeur over backends en systemen werkt?
Enkele opmerkingen:
- Ik gebruik OSX en gebruik de backend
TkAgg
, maar oplossingen voor elk backend/systeem zijn welkom - Interactieve modus “Aan” werkt niet, omdat deze niet live wordt bijgewerkt. Het wordt alleen bijgewerkt in de Python-console wanneer de interpreter wacht op gebruikersinvoer.
-
Een blogstelde de implementatie voor:
def redraw_figure():
fig = plt.gcf()
fig.canvas.draw()
fig.canvas.flush_events()
Maar op mijn systeem worden de plots helemaal niet opnieuw getekend.
Dus, als iemand een antwoord heeft, zou je mij en duizenden anderen direct heel blij maken. Hun geluk zou waarschijnlijk doorsijpelen naar hun vrienden en familieleden, en hun vrienden en familieleden, enzovoort, zodat je potentieel het leven van miljarden zou kunnen verbeteren.
Conclusies
ImportanceOfBeingErnest laat zien hoe je blit kunt gebruiken om sneller te plotten, maar het is niet zo eenvoudig als iets anders in de functie redraw_figure
te zetten (je moet bijhouden welke dingen je opnieuw moet tekenen).
Antwoord 1, autoriteit 100%
Allereerst draait de code die in de vraag is gepost met 7 fps op mijn machine, met QT4Agg als backend.
Nu, zoals in veel berichten is gesuggereerd, zoals hierof hier, kan het gebruik van blit
een optie zijn. Hoewel dit artikelvermeldt dat blit sterke geheugenlekkage veroorzaakt, zou ik let daar niet op.
Ik heb je code een beetje aangepast en de framesnelheid vergeleken met en zonder het gebruik van blit. De onderstaande code geeft
- 28 fps wanneer uitgevoerd zonder blit
- 175 fps met blit
Code:
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)
img = ax1.imshow(X, vmin=-1, vmax=1, interpolation="None", cmap="RdBu")
line, = ax2.plot([], lw=3)
text = ax2.text(0.8,0.5, "")
ax2.set_xlim(x.min(), x.max())
ax2.set_ylim([-1.1, 1.1])
fig.canvas.draw() # note that the first draw comes before setting data
if blit:
# cache the background
axbackground = fig.canvas.copy_from_bbox(ax1.bbox)
ax2background = fig.canvas.copy_from_bbox(ax2.bbox)
plt.show(block=False)
t_start = time.time()
k=0.
for i in np.arange(1000):
img.set_data(np.sin(X/3.+k)*np.cos(Y/3.+k))
line.set_data(x, 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:
# restore background
fig.canvas.restore_region(axbackground)
fig.canvas.restore_region(ax2background)
# redraw just the points
ax1.draw_artist(img)
ax2.draw_artist(line)
ax2.draw_artist(text)
# 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()
#alternatively you could use
#plt.pause(0.000000000001)
# however plt.pause calls canvas.draw(), as can be read here:
#http://bastibe.de/2013-05-30-speeding-up-matplotlib.html
live_update_demo(True) # 175 fps
#live_update_demo(False) # 28 fps
Bijwerken:
Voor sneller plotten kan men overwegen om pyqtgraphte gebruiken.
Zoals de pyqtgraph-documentatiehet stelt: “Voor plotten is pyqtgraph lang niet zo compleet/volwassen als matplotlib, maar werkt veel sneller.”
Ik heb het bovenstaande voorbeeld overgezet naar pyqtgraph. En hoewel het er een beetje lelijk uitziet, draait het met 250 fps op mijn machine.
Dat samenvattend,
- matplotlib (zonder blitting): 28 fps
- matplotlib (met blitting): 175 fps
- pyqtgraph: 250 fps
pyqtgraph-code:
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_())
Antwoord 2, autoriteit 13%
Hier is een manier om live plotten te doen: maak de plot als een afbeeldingsarray en teken de afbeelding vervolgens naar een multithreaded scherm.
Voorbeeld met een pyformula-scherm (~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()
Disclaimer: ik ben de beheerder van pyformula’s