Matplotlib / PyPlot中的快速实时绘图

79
多年来,我一直在努力让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()

在运行代码时应实时更新绘图,并在停止在任何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 = ...这一行上设置断点,则第二个绘图不会出现)。

奇怪的是,实际上起作用的是调用两次show:

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

如果你在任何一行断点上打开,它能提供大约11FPS并保持图表最新。

现在我听说“block”关键字已经被弃用了。而且调用相同的函数两次似乎是一种奇怪的、可能不可移植的方法。

那么,在这个函数中我可以放什么来以合理的帧率绘制图表,而不是一个巨大的笨拙的解决方案,并且最好能够在后端和系统间工作?

  • 我使用的是TkAgg后端,但欢迎在任何后端/系统上提供解决方案。
  • 交互模式“开”不起作用,因为它不会实时更新。只有在Python控制台中等待用户输入时才会更新。
  • 一个博客建议使用以下实现:

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

但至少在我的系统上,这根本没有重新绘制图表。

所以,如果有人有答案,你将直接让我和千千万万人非常高兴。他们的幸福可能会通过他们的朋友和亲戚,以及他们的朋友和亲戚,等等,传递下去,从而您可以潜在地改善数十亿人的生活。

结论

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


在我的系统上,如果我按照博客的建议添加 plt.show(block=False),那么绘图大约会以每秒13帧的速度显示出来。 - DavidG
你尝试过PyQtGraph吗?同时也看看这个问题 - berna1111
2
我有,但是(1)使用不同的库进行实时和静态绘图很麻烦,而且(2)从像这个的帖子中可以看出,matplotlib可以通过一些方法加速实时绘图,但目前似乎还没有标准化的方法。 - Peter
这肯定会改善数十亿人的生活。 - Niko Pasanen
使用 fig.canvas.draw_idle() 而不是 fig.canvas.draw() 对我有用。 - Nirmal
根据这些问题的答案,我最终基于matplotlib制作了一个实时绘图工具。您可以通过pip install artemis-ml获取它,并查看函数dbplot的文档:https://artemis-ml.readthedocs.io/en/latest/plotting.html - Peter
2个回答

99

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

现在,正如许多帖子中建议的那样,比如这里这里,使用blit可能是一种选择。尽管这篇文章提到blit会导致内存泄漏,但我没有观察到这种情况。

我修改了你的代码,并比较了使用和不使用blit时的帧速率。下面的代码给出:

  • 没有使用blit时的帧速率为28 fps
  • 使用blit时为175 fps

代码:

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

更新:
为了更快地绘图,可以考虑使用pyqtgraph
正如pyqtgraph文档所说:"对于绘图而言,pyqtgraph并不像matplotlib那样完整/成熟,但运行速度要快得多。"

我将上述示例转换为pyqtgraph。虽然它看起来有点丑陋,但在我的机器上运行时帧率达到了250fps。

总结一下,

  • matplotlib(不使用blitting):28 fps
  • matplotlib(使用blitting):175 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_())

3
在放上述示例之后,我在想,30帧每秒是否真的是极限。这让我发现了 pyqtgraph。为了判断这些数字,我决定在 pyqtgraph 中实现完全相同的示例...结果获得了很大的提升!与30 fps相比,达到了250 fps!因此,我更新了上面的答案,包括该代码,以防有人感兴趣。 - ImportanceOfBeingErnest
2
@Guimoute 如果背景包含静态内容,就会出现冲突。只要不是这种情况,它就可以正常工作 - 甚至可以简化,只需要恢复一个背景,而不是两个,因为它们彼此重叠。 - ImportanceOfBeingErnest
figure.canvas.draw()figure.draw()有什么区别?我刚发现figure.draw()方法好像很少被人使用。 - nn0p
2
@nn0p,你需要使用渲染器来执行figure.draw(renderer)。为了获取渲染器,你需要一个画布。因此,更简单的方法显然是figure.canvas.draw() - ImportanceOfBeingErnest
@ImportanceOfBeingErnest 我在这里发布了一个相关的问题:https://stackoverflow.com/questions/66615700/render-a-dynamically-changing-numpy-array-realtime-as-a-bitmap-on-screen-at-30hz ... 如果您能提供任何见解,我将不胜感激! - P i
显示剩余6条评论

14
这里是一种实时作图的方法:获取绘图为图像数组,然后将图像绘制到多线程屏幕上。
使用pyformulas屏幕的示例(约30帧/秒):
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()

输入图像描述

声明:我是pyformulas的维护者


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接