在Matplotlib / PyPlot中快速实时绘图
多年来,我一直在努力在matplotlib中获得高效的实时阴谋,直到今天,我仍然不满意。
我想要一个redraw_figure
函数来更新数字“live”(如代码运行),并且如果我停在断点处将显示最新的图。
这里是一些演示代码:
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()
代码运行时,应该更新活动redraw_figure()
,并且在redraw_figure()
之后的任何断点处停止时,应该看到最新的数据。 问题是如何最好地实现redraw_figure()
在上面的实现中( plt.draw(); plt.pause(0.00001)
),它工作,但是很慢(〜3.7FPS)
我可以将其实现为:
def redraw_figure(): plt.gcf().canvas.flush_events() plt.show(block=False)
它运行得更快(〜11FPS),但是当你停在断点处时,绘图并不是最新的(例如,如果我在t_start = ...
行上放置断点,则不会出现第二个绘图)。
奇怪的是,实际工作是两次调用这个节目:
def redraw_figure(): plt.gcf().canvas.flush_events() plt.show(block=False) plt.show(block=False)
这给出了〜11FPS,如果你在任何一个线路上rest的话,它也会保持最新的数据。
现在我听说它说“block”关键字已被弃用。 两次调用相同的函数似乎是一个奇怪的,可能不可移植的黑客无论如何。
那么我可以把这个函数放在合理的帧率上,不是一个巨大的混乱,最好是跨越后端和系统?
一些说明:
- 我在OSX上,并使用
TkAgg
后端,但任何后端/系统上的解决scheme是受欢迎的 - 交互模式“开”将不起作用,因为它不会实时更新。 它只是在解释器等待用户input时在Python控制台中更新。
- 一个博客提出了这个实现:
def redraw_figure(): fig = plt.gcf() fig.canvas.draw() fig.canvas.flush_events()
但至less在我的系统中,根本不会重绘这些情节。
所以,如果有人有答案,你会直接让我和成千上万的人非常高兴。 他们的幸福可能会stream到他们的亲友,朋友和亲戚等等,这样你就有可能改善数十亿人的生活。
结论
ImportanceOfBeingErnest展示了如何使用blit来实现更快速的绘图,但并不像在redraw_figure
函数中添加不同的东西那样简单(你需要跟踪重绘的东西)。
首先,问题中发布的代码在我的机器上以7 fps运行,QT4Agg作为后端。
现在,正如在这里或这里的许多post中所提到的,使用blit
可能是一种select。 虽然这篇文章提到blit会导致强大的内存泄漏,但我无法观察到这一点。
我已经修改了一下你的代码,比较了使用blit和不使用blit的帧速率。 下面的代码给出
- 18 fps运行没有blit
- 28 fps与blit
码:
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: # restore background fig.canvas.restore_region(axbackground) fig.canvas.restore_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
更新:
为了更快的绘图,可以考虑使用pyqtgraph 。
正如pyqtgraph文档所说: “对于绘图而言,pyqtgraph几乎不像matplotlib那样完整/成熟,但运行得更快。”
我把上面的例子移植到了pyqtgraph中。 虽然它看起来很丑,但在我的机器上运行速度为250 fps。
总结起来,
- matplotlib(没有blitting):18 fps
- matplotlib(与blitting):28 fps
- pyqtgraph: 250 fps
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_())