2

I am having a problem getting matplotlib to work well with interactive plotting... what I see is that after displaying a few frames of my simulated data matplotlib hangs-and doesn't display any more.

Basically I've been playing around a bit with science simulations - and would like to be able to plot my results as they are being made - rather than at the end - using pylab.show().

I found a cookbook example from a while back that seems to do what I would want - in simple terms (although obv. the data is different). The cookbook is here...http://www.scipy.org/Cookbook/Matplotlib/Animations#head-2f6224cc0c133b6e35c95f4b74b1b6fc7d3edca4

I have searched around a little and I know that some people had these problems before - Matplotlib animation either freezes after a few frames or just doesn't work but it seems at the time there were no good solutions. I was wondering if someone has since found a good solution here.

I have tried a few 'backends' on matplotlib....TkAgg seems to work for a few frames.... qt4agg doesn't show the frames. I haven't yet got GTK to install properly.

I am running the most recent pythonxy(2.7.3).

Anyone have any advice?

import matplotlib
matplotlib.use('TkAgg') # 'Normal' Interactive backend. - works for several frames
#matplotlib.use('qt4agg') # 'QT' Interactive backend. - doesn't seem to work at all
#matplotlib.use('GTKAgg') # 'GTK' backend - can't seem to get this to work.... -

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

plt.ion()

tstart = time.time()                     # for profiling
x = np.arange(0,2*np.pi,0.01)            # x-array
line, = plt.plot(x,np.sin(x))

#plt.ioff()

for i in np.arange(1,200):

    line.set_ydata(np.sin(x+i/10.0))  # update the data
    line.axes.set_title('frame number {0}'.format(i))

    plt.draw()                         # redraw the canvas

print 'FPS:' , 200/(time.time()-tstart)

EDIT:

edited code - to get rid of some style issues brought up.

Community
  • 1
  • 1
JPH
  • 1,224
  • 2
  • 14
  • 21
  • Why are you calling `ioff`? (There's no obvious reason to.) Do you still get the same behavior if you don't call `ioff`? I can't reproduce your problems, for whatever it's worth, but I'd hazard a guess that it's because you're calling `ioff` and then later drawing the plot. – Joe Kington Nov 25 '12 at 22:58
  • Thanks for the reply.... I guess the reason I was calling `ioff` was that as far as i'm aware in interactive mode when you change something on the plot it forces a redraw. what i'm trying to do here is minimise the number of redraws - so that my simulations don't get slowed down too much. In any case - removing it doesn't help - me at least - interesting that you are able to run it ok. Could I ask what python version etc. you are using? – JPH Nov 25 '12 at 23:53
  • 1
    Python 2.7 with matplotlib 1.1 on linux. For what it's worth, there's no difference in performance by calling `ioff`, in this case (and no extra draw calls inside your loop with `ion`). However, `ioff` may (i.e. is allowed to) cause the backend's main loop to stop (or it may not, the exact behavior is backend-dependent). That's why I was guessing the problem was due to `ioff`, at any rate. What happens if you use the `matplotlib.animations` module instead? http://matplotlib.org/api/animation_api.html (In this case `FuncAnimation` would be easiest.) – Joe Kington Nov 25 '12 at 23:59
  • 1
    Also, it really is best to avoid `from pylab import *` unless you're using things from a shell, but that's a purely stylistic issue, and won't affect your problem, in this case. At any rate, use `matplotlib.pyplot` instead (the convention is `import matplotlib.pyplot as plt` to avoid excessively wordy code). – Joe Kington Nov 26 '12 at 00:01
  • Thanks... I'm on python 2.7.3 and mpl 1.1.1- but windows 7. I take your point on the stylistic stuff - I normally avoid *s but i was mangling an example I got off t'interweb so some bad stuff crept in. Is there a reason for using pyplot - rather than pylab? – JPH Nov 26 '12 at 01:05
  • 1
    As for the matplotlib.animations stuff.... I had a quick look at that before - but I'm not quite sure it's what I want. Basically I may be wrong - but it looks like it expects you cot create a function that is called repeatedly by matplotlib - updating the data shown each time. What I want is the other way around - here I have just used 'sin' as a dummy - but really in my code I am doing numerical integrals...which take quite a long time + other logical stuff that changes the analysis slightly. I want my code to be calling matplot lib - not the other way around. – JPH Nov 26 '12 at 01:10
  • 1
    As far as the `pyplot` vs `pylab` part, `pyplot` is just the core of matplotlib, whereas `pylab` is `matplotlib`, `numpy`, and `matplotlib.mlab` all rolled into one. It's a gigantic namespace, and it's really better to distinguish where things are coming from. Of course, it's all mostly stylistic, but it makes it considerably easier to find the proper venue for help, if nothing else. – Joe Kington Nov 26 '12 at 01:26
  • On the animation part, give me a bit and let me cobble together an exmaple that might help. (You're right, you don't want `FuncAnimation` if you want to call the update, but not too hard to do the other way around.) I'm shooting in the dark, as I'm not seeing the same problem on linux, but I can make a few guesses, at any rate. – Joe Kington Nov 26 '12 at 01:29
  • Actually, if what you have in your new version isn't working correctly, it's a bug. My exmaple was going to just be a more complex version of what you have... Since you mention pythonxy, are you by chance running it from Spyder? If so, it might be a problem with the way spyder handles threading with matplotlib... Do you get the same problem if you run it directly? (e.g. open up a `cmd` window and run `python filename.py`) – Joe Kington Nov 26 '12 at 02:23
  • 1
    Thanks again - I'm afraid i generally do use spyder to develop on - but in this case it's definitely NOT spyder that is causing problems - I get the same effect running from cmd, and also from IPython. I suspect therefore it may be something to do with python\matplotlib on windows - since it works for you on linux. I've had a bash at doing something using wx - which seems on a basic level to work - I guess I'll see how well it works within my code. – JPH Nov 26 '12 at 03:16

1 Answers1

2

Ok... So I have mangled together something that may sort of work for me....

Basically it is something like a watered down gui - but i'm hoping that it is a class i can import and basically forget about the details of (here's hoping).

I should say though - this is my first attempt at threading OR guis in python - so this code comes with a health warning.

** I'm not going to mark the question as answered though - because i'm sure someone more experienced will have a better solution.

'''

JP

Attempt to get multiple updating of matplotlibs working.
Uses WX to create an 'almost' gui with a mpl in the middle of it.
Data can be queued to this object - or you can directly plot to it.

Probably will have some limitations atm
- only really thinking about 2d plots for now -
but presumably can work around this for other implimentations.
- the working code seems to need to be put into another thread.
Tried to put the wx mainloop into another thread,
but it seemed unhappy. :(



Classes of Interest :
    GraphData - A silly class that holds data to be plotted.
    PlotFigure - Class of wx frame type.
        Holds a mpl figure in it + queue to queue data to.
        The frame will plot the data when it refreshes it's canvas

    ThreadSimulation - This is not to do with the plotting
                        it is a test program.


Modified version of:

Copyright (C) 2003-2005 Jeremy O'Donoghue and others

License: This work is licensed under the PSF. A copy should be included
with this source code, and is also available at
http://www.python.org/psf/license.html

'''
import threading
import collections
import time

import numpy as np

import matplotlib
matplotlib.use('WXAgg')



from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.backends.backend_wx import NavigationToolbar2Wx

from matplotlib.figure import Figure

import wx







class GraphData(object):
    '''
        A silly class that holds data to be plotted.
    '''
    def __init__(self, xdatainit, ydatainit):

        self.xdata = xdatainit
        self.ydata = ydatainit

class PlotFigure(wx.Frame):

    def __init__(self ):
        '''
            Initialises the frame.
        '''
        wx.Frame.__init__(self, None, -1, "Test embedded wxFigure")

        self.timerid = wx.NewId()

        self.fig = Figure((5,4), 75)
        self.canvas = FigureCanvasWxAgg(self, -1, self.fig)
        self.toolbar = NavigationToolbar2Wx(self.canvas)
        self.toolbar.Realize()

        # On Windows, default frame size behaviour is incorrect
        # you don't need this under Linux
        tw, th = self.toolbar.GetSizeTuple()
        fw, fh = self.canvas.GetSizeTuple()
        self.toolbar.SetSize(wx.Size(fw, th))

        # Now put all into a sizer
        sizer = wx.BoxSizer(wx.VERTICAL)
        # This way of adding to sizer allows resizing
        sizer.Add(self.canvas, 1, wx.LEFT|wx.TOP|wx.GROW)
        # Best to allow the toolbar to resize!
        sizer.Add(self.toolbar, 0, wx.GROW)
        self.SetSizer(sizer)
        self.Fit()
        wx.EVT_TIMER(self, self.timerid, self.onTimer)

        self.dataqueue = collections.deque()

        # Add an axes and a line to the figure.
        self.axes = self.fig.add_subplot(111)
        self.line, = self.axes.plot([],[])

    def GetToolBar(self):
        '''
            returns default toolbar.
        '''
        return self.toolbar

    def onTimer(self, evt):
        '''
            Every timer period this is called.

            Want to redraw the canvas.
        '''
        #print "onTimer"
        if len(self.dataqueue) > 0 :
            data = self.dataqueue.pop()

            x = data.xdata
            y = data.ydata

            xmax = max(x)
            xmin = min(x)

            ymin = round(min(y), 0) - 1
            ymax = round(max(y), 0) + 1

            self.axes.set_xbound(lower=xmin, upper=xmax)
            self.axes.set_ybound(lower=ymin, upper=ymax)

            self.line.set_xdata(x)
            self.line.set_ydata(y)

        # Redraws the canvas - does this even if the data isn't updated...
        self.canvas.draw()


    def onEraseBackground(self, evt):
        '''
        this is supposed to prevent redraw flicker on some X servers...
        '''
        pass


class ThreadSimulation(threading.Thread):
    '''
    Simulation Thread - produces data to be displayed in the other thread.
    '''

    def __init__(self,  nsimloops, datastep, pltframe, slowloop = 0):
        threading.Thread.__init__(self)

        self.nsimloops = nsimloops
        self.datastep = datastep
        self.pltframe = pltframe
        self.slowloop=slowloop

    def run(self):
        '''
        This is the simulation function.
        '''
        nsimloops = self.nsimloops
        datastep = self.datastep
        pltframe = self.pltframe

        print 'Sim Thread: Starting.'
        tstart = time.time()               # for profiling

        # Define Data to share between threads.
        x  = np.arange(0,2*np.pi,datastep)            # x-array
        y  = np.sin(x )

        # Queues up the data and removes previous versions.
        pltframe.dataqueue.append(GraphData(x,y))
        for i in range(len(pltframe.dataqueue)-1):
            pltframe.dataqueue.popleft()
        pltframe.dataqueue

        for i in np.arange(1, nsimloops):


            x = x + datastep
            y = np.sin(x)

            # Queues up the data and removes previous versions.
            pltframe.dataqueue.append(GraphData(x,y))
            for i in range(len(pltframe.dataqueue)-1):
                pltframe.dataqueue.popleft()
            #pltframe.dataqueue

            if self.slowloop > 0 :
                time.sleep(self.slowloop)



        tstop= time.time()
        print 'Sim Thread: Complete.'
        print 'Av Loop Time:' , (tstop-tstart)/ nsimloops

if __name__ == '__main__':


    # Create the wx application.
    app = wx.PySimpleApp()

    # Create a frame with a plot inside it.
    pltframe = PlotFigure()
    pltframe1 = PlotFigure()

    # Initialise the timer - wxPython requires this to be connected to
    # the receiving event handler

    t = wx.Timer(pltframe, pltframe.timerid)
    t.Start(100)

    pltframe.Show()
    pltframe1.Show()

    npoints = 100
    nsimloops = 20000
    datastep = 2 * np.pi/ npoints
    slowloop = .1

    #Define and start application thread
    thrd = ThreadSimulation(nsimloops, datastep, pltframe,slowloop)
    thrd.setDaemon(True)
    thrd.start()

    pltframe1.axes.plot(np.random.rand(10),np.random.rand(10))

    app.MainLoop()
JPH
  • 1,224
  • 2
  • 14
  • 21
  • Ok so I marked it answered - since someone mentioned that lots of my questions hadn't been marked answered and made it less likely that I would get help in the future.... what a tangled web we weave! – JPH Mar 03 '13 at 18:04