Matplotlib/PyPlot中的快速实时绘图

Pet*_*ter 34 matplotlib

多年来,我一直在努力在matplotlib中获得高效的实时绘图,直到今天我仍然不满意.

我想要一个redraw_figure更新图形"实时"(如代码运行)的函数,并且如果我在断点处停止,将显示最新的图.

这是一些演示代码:

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()
Run Code Online (Sandbox Code Playgroud)

在运行代码时,绘图应该更新,并且我们应该在之后的任何断点处停止时看到最新数据redraw_figure().问题是如何最好地实施redraw_figure()

在上面的实现(plt.draw(); plt.pause(0.00001))中,它可以工作,但速度非常慢(~3.7FPS)

我可以实现它:

def redraw_figure():
    plt.gcf().canvas.flush_events()
    plt.show(block=False)
Run Code Online (Sandbox Code Playgroud)

并且它运行得更快(~11FPS),但是当您在断点处停止时,情节不是最新的(例如,如果我在线上放置断点t_start = ...,则不会出现第二个图).

奇怪的是,实际上工作的是两次调用节目:

def redraw_figure():
    plt.gcf().canvas.flush_events()
    plt.show(block=False)
    plt.show(block=False)
Run Code Online (Sandbox Code Playgroud)

如果你在任何一条线上中断,那么它可以提供~11FPS,并且可以将图表保持为数据.

现在我听说它已经弃用了"block"关键字.并且两次调用相同的函数似乎是一个奇怪的,可能是非便携式的hack.

那么我可以在这个以合理的帧速率绘制的函数中加入什么,不是一个巨大的kludge,并且最好能在后端和系统中工作?

一些说明:

  • 我在OSX上,并使用TkAgg后端,但欢迎任何后端/系统上的解决方案
  • 交互模式"开启"不起作用,因为它不会实时更新.它只是在解释器等待用户输入时在Python控制台中更新.
  • 一篇博客建议实施:

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

但至少在我的系统上,根本没有重绘图.

所以,如果有人有答案,你会直接让我和其他成千上万的人非常开心.他们的快乐可能会流向他们的朋友和亲戚,他们的朋友和亲戚等等,这样你就有可能改善数十亿人的生活.

结论

ImportanceOfBeingErnest展示了如何使用blit进行更快的绘图,但它并不像在redraw_figure函数中放置不同的东西那么简单(你需要跟踪要重绘的内容).

Imp*_*est 42

首先,问题中发布的代码在我的机器上以7 fps运行,QT4Agg作为后端.

现在,正如在这里这里的许多帖子中所建议的那样,使用blit可能是一种选择.虽然这篇文章提到blit导致强烈的内存泄漏,但我无法观察到.

我稍微修改了你的代码并比较了使用和不使用blit的帧速率.下面的代码给出了

  • 没有blit运行时18 fps
  • 快乐的28帧/秒

码:

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
Run Code Online (Sandbox Code Playgroud)

更新:
为了更快地绘图,可以考虑使用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_())
Run Code Online (Sandbox Code Playgroud)

  • 在将上面的示例放在此处之后,我问自己:〜30 fps是否实际上是极限。这使我遇到了[pyqtgraph](http://www.pyqtgraph.org)。为了判断数字,我决定在pyqtgraph中实现完全相同的示例……事实证明,您可以收获很多!250 fps和30 fps相比!因此,如果有人感兴趣,我还更新了上面的答案以包括该代码。 (2认同)
  • @Guimoute 如果背景包含静态内容,将会发生冲突。只要情况并非如此,它就可以正常工作 - 但甚至可以简化,只需要恢复一个背景,而不是两个背景,因为它们彼此重叠。 (2认同)
  • @nn0p 它将是 [`figure.draw(renderer)`](https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.draw)你需要一个渲染器。为了获得渲染器,您需要一个画布。所以更简单的方法显然是“figure.canvas.draw()”。 (2认同)

Def*_*ure 7

这是进行实时绘图的一种方法:将绘图作为图像数组获取,然后将图像绘制到多线程屏幕上。

使用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()
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

免责声明:我是pyformulas的维护者

  • 我认为将其与matplotlib FuncAnimation进行比较会很有帮助。由于这也会绘制matplotlib图形(`fig.canvas.draw()`),因此它肯定不能更快,对吗? (2认同)