88

For years, I've been struggling to get efficient live plotting in matplotlib, and to this day I remain unsatisfied.

I want a redraw_figure function that updates the figure "live" (as the code runs), and will display the latest plots if I stop at a breakpoint.

Here is some demo code:

import numpy as np
import time
from matplotlib import pyplot as plt

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 should update live when the code is run, and we should see the latest data when stopping at any breakpoint after redraw_figure(). The question is how to best implement redraw_figure()

In the implementation above (plt.draw(); plt.pause(0.00001)), it works, but is very slow (~3.7FPS)

I can implement it as:

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

And it runs faster (~11FPS), but plots are not up-to date when you stop at breakpoints (eg if I put a breakpoint on the t_start = ... line, the second plot does not appear).

Strangely enough, what does actually work is calling the show twice:

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

Which gives ~11FPS and does keep plots up-to-data if your break on any line.

Now I've heard it said that the "block" keyword is deprecated. And calling the same function twice seems like a weird, probably-non-portable hack anyway.

So what can I put in this function that will plot at a reasonable frame rate, isn't a giant kludge, and preferably will work across backends and systems?

Some notes:

  • I'm on OSX, and using TkAgg backend, but solutions on any backend/system are welcome
  • Interactive mode "On" will not work, because it does not update live. It just updates when in the Python console when the interpreter waits for user input.
  • A blog suggested the implementation:
def redraw_figure():
    fig = plt.gcf()
    fig.canvas.draw()
    fig.canvas.flush_events()

But at least on my system, that does not redraw the plots at all.

So, if anybody has an answer, you would directly make me and thousands of others very happy. Their happiness would probably trickle through to their friends and relatives, and their friends and relatives, and so on, so that you could potentially improve the lives of billions.

Conclusions

ImportanceOfBeingErnest shows how you can use blit for faster plotting, but it's not as simple as putting something different in the redraw_figure function (you need to keep track of what things to redraw).

6
  • On my system if i add plt.show(block=False) on the suggestion from the blog, then the plots do show up at ~13FPS. Commented Oct 19, 2016 at 11:15
  • Have you tried PyQtGraph? Also take a look on this question. Commented Oct 19, 2016 at 11:20
  • 2
    I have, but (1) it's awkward to use different libraries for live and static plotting, and (2) it seems from posts like this that matplotlib can be made very fast for live plotting, but there just doesn't seem to be a standardized way to do it yet. Commented Oct 19, 2016 at 13:36
  • This surely will improve the lives of billions Commented Jul 21, 2020 at 18:01
  • 1
    Using fig.canvas.draw_idle() instead of fig.canvas.draw() worked for me. Commented Mar 26, 2021 at 10:24

2 Answers 2

110

First of all, the code that is posted in the question runs with 7 fps on my machine, with QT4Agg as backend.

Now, as has been suggested in many posts, like here or here, using blit might be an option. Although this article mentions that blit causes strong memory leakage, I could not observe that.

I have modified your code a bit and compared the frame rate with and without the use of blit. The code below gives

  • 28 fps when run without blit
  • 175 fps with 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

Update:
For faster plotting, one may consider using pyqtgraph.
As the pyqtgraph documentation puts it: "For plotting, pyqtgraph is not nearly as complete/mature as matplotlib, but runs much faster."

I ported the above example to pyqtgraph. And although it looks kind of ugly, it runs with 250 fps on my machine.

Summing that up,

  • matplotlib (without blitting): 28 fps
  • matplotlib (with 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_())
Sign up to request clarification or add additional context in comments.

12 Comments

Thanks! For the record, here are the numbers for your code on my machine (MacBook Air) with TkAgg backend: ||| Without Blit: ~8.0FPS ||| With Blit: ~10.4FPS ||| Without Blit, replacing plt.pause(...) with fig.canvas.flush_events(); plt.show(block=False): ~14.3FPS ||| With Blit, replacing plt.pause(...) with fig.canvas.flush_events(); plt.show(block=False): ~22.8FPS.
After putting above example here, I was asking myself, if the ~30 fps are actually the limit. This made me come across pyqtgraph. For judging upon the numbers I decided to implement the exact same example in pyqtgraph... and it turns out you gain a lot! 250 fps compared to 30 fps! So I updated the answer above to include that code as well, in case someone is interested.
@Guimoute There will be a conflict if the background is to contain something static. As long as that is not the case, it would work fine - but could even be simplified, only one background has to be restored, not both, because they lay on top of each other.
@nn0p It would be figure.draw(renderer) for which you need a renderer. In order to get a renderer you would need a canvas. So the easier method is clearly figure.canvas.draw().
I had to replace QtGui with QtWidgets everywhere in the code to make the pyqtgraph example work.
|
13

Here's one way to do live plotting: get the plot as an image array then draw the image to a multithreaded screen.

Example using a pyformulas screen (~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

Disclaimer: I'm the maintainer of pyformulas

1 Comment

I think it would be helpful to see how this compares to matplotlib FuncAnimation. Since this draws the matplotlib figure (fig.canvas.draw()) as well, it sure can't be faster, can it?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.