14

I am writing a program in Python, using matplotlib to (among other things) run an animation showing a numerical solution to the time-dependent Schrodinger Equation.

Everything is working fine, but once an animation has finished running, I would like the window it was in to close itself. My way of doing this (shown below) works, but exceptions are thrown up which I cant seem to catch. It works fine for what I need it to do, but the error looks very messy.

I have an alternative method which works without throwing an error, but requires the user to manually close the window (unacceptable for my purposes). Can someone please point out what I am doing wrong, or suggest a better option?

A simplified version of the relevant parts of my code follows:

from matplotlib import animation as ani
from matplotlib import pyplot as plt

multiplier = 0
def get_data():         # some dummy data to animate
    x = range(-10, 11)
    global multiplier
    y = [multiplier * i for i in x]
    multiplier += 0.005
    return x, y

class Schrodinger_Solver(object):
    def __init__(self, xlim = (-10, 10), ylim = (-10, 10), num_frames = 200):

        self.num_frames = num_frames
        self.fig = plt.figure()
        self.ax = self.fig.add_subplot(111, xlim = xlim, ylim = ylim)
        self.p_line, = self.ax.plot([], [])

        self.ani = ani.FuncAnimation(self.fig, self.animate_frame,
                                     init_func = self.init_func,
                                     interval = 1, frames = self.num_frames,
                                     repeat = False, blit = True)

        plt.show()

    def animate_frame(self, framenum):
        data = get_data()
        self.p_line.set_data(data[0], data[1])

        if framenum == self.num_frames - 1:
            plt.close()
        # closes the window when the last frame is reached,
        # but exception is thrown. Comment out to avoid the error,
        # but then the window needs manual closing

        return self.p_line,

    def init_func(self):
        self.p_line.set_data([], [])
        return self.p_line,

Schrodinger_Solver()

I am running Python 2.7.2 on windows 7, with matplotlib 1.1.0

Thanks in advance

EDIT: exception and traceback as follows:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 1410, in __call__
    return self.func(*args)
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 495, in callit
    func(*args)
  File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 116, in _on_timer
    TimerBase._on_timer(self)
  File "C:\Python27\lib\site-packages\matplotlib\backend_bases.py", line 1092, in _on_timer
    ret = func(*args, **kwargs)
  File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 315, in _step
    still_going = Animation._step(self, *args)
  File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 177, in _step
    self._draw_next_frame(framedata, self._blit)
  File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 197, in _draw_next_frame
    self._post_draw(framedata, blit)
  File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 220, in _post_draw
    self._blit_draw(self._drawn_artists, self._blit_cache)
  File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 240, in _blit_draw
    ax.figure.canvas.blit(ax.bbox)
  File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 244, in blit
    tkagg.blit(self._tkphoto, self.renderer._renderer, bbox=bbox, colormode=2)
  File "C:\Python27\lib\site-packages\matplotlib\backends\tkagg.py", line 19, in blit
    tk.call("PyAggImagePhoto", photoimage, id(aggimage), colormode, id(bbox_array))
TclError: this isn't a Tk application

Traceback (most recent call last):
  File "C:\Python27\quicktest.py", line 44, in <module>
    Schrodinger_Solver()
  File "C:\Python27\quicktest.py", line 26, in __init__
    plt.show()
  File "C:\Python27\lib\site-packages\matplotlib\pyplot.py", line 139, in show
    _show(*args, **kw)
  File "C:\Python27\lib\site-packages\matplotlib\backend_bases.py", line 109, in __call__
    self.mainloop()
  File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 69, in mainloop
    Tk.mainloop()
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 325, in mainloop
    _default_root.tk.mainloop(n)
AttributeError: 'NoneType' object has no attribute 'tk'

I can catch the second exception, the AttributeError, by a small change:

try: plt.show()
except AttributeError: pass

but the first part, the TclError, remains no matter what I try

1
  • What's the exception? Please also include the traceback! Commented Jul 3, 2013 at 12:15

3 Answers 3

4

I had the same problem a few minutes before... The reason was a very low interval-value in FuncAnimation. Your code tries to update the graphics evere 1 millisecond - quite fast! (1000 fps might not be needed). I tried interval=200 and the error was gone...

HTH

Sign up to request clarification or add additional context in comments.

2 Comments

Why is this a problem, though? And how low of an interval value can I use before an error comes up? It seems strange there's some arbitrary limit that is allowed. I'm trying to plot the result of a live computation that's occurring as fast as possible (manually making sure at least, say, 0.1 seconds elapses before the animate function returns), so I don't want to be inserting artificial delays.
@StanleyBak: you data gathering and processing might be as fast as possible, the presentation of that data as an animation doesn't have to be. The animation will sample the live data, it makes little sense to run higher than 30 or 60 FPS.
1

I am facing the exact same problem, and I managed to solve the issue by creating another Animation class. Essentially, I made two changes:

  1. Write a custom class that overwrites the _stop and _step methods.
  2. Riase an StopIteration error in the update function, instead of using plt.close. The exception will be caught and won't break the script.

Here is the code.

from matplotlib import animation as ani
from matplotlib import pyplot as plt

class FuncAnimationDisposable(ani.FuncAnimation):
    def __init__(self, fig, func, **kwargs):
        super().__init__(fig, func, **kwargs)
        
    def _step(self, *args):
        still_going = ani.Animation._step(self, *args)
        if not still_going and self.repeat:
            super()._init_draw()
            self.frame_seq = self.new_frame_seq()
            self.event_source.interval = self._repeat_delay
            return True
        elif (not still_going) and (not self.repeat):
            plt.close()  # this code stopped the window
            return False
        else:
            self.event_source.interval = self._interval
            return still_going
        
    def _stop(self, *args):
        # On stop we disconnect all of our events.
        if self._blit:
            self._fig.canvas.mpl_disconnect(self._resize_id)
        self._fig.canvas.mpl_disconnect(self._close_id)

        

multiplier = 0
def get_data():         # some dummy data to animate
    x = range(-10, 11)
    global multiplier
    y = [multiplier * i for i in x]
    multiplier += 0.005
    return x, y

class Schrodinger_Solver(object):
    def __init__(self, xlim = (-10, 10), ylim = (-10, 10), num_frames = 200):

        self.num_frames = num_frames
        self.fig = plt.figure()
        self.ax = self.fig.add_subplot(111, xlim = xlim, ylim = ylim)
        self.p_line, = self.ax.plot([], [])

        self.ani = FuncAnimationDisposable(self.fig, self.animate_frame,
                                     init_func = self.init_func,
                                     interval = 1, frames = self.num_frames,
                                     repeat = False, blit = True)

        plt.show()

    def animate_frame(self, framenum):
        data = get_data()
        self.p_line.set_data(data[0], data[1])

        if framenum == self.num_frames - 1:
            raise StopIteration  # instead of plt.close()
            
        return self.p_line,

    def init_func(self):
        self.p_line.set_data([], [])
        return self.p_line,

Schrodinger_Solver()
Schrodinger_Solver()

print(multiplier)

(The code snippet was tested with Python 3.7 and matplotlib 3.4.2.)

Comments

0

try with:

if framenum == self.num_frames - 1:
   exit()

it works for me...

2 Comments

This works by closing the program altogether. The reason I need a way of closing just the window on its own is that I want other things to happen (other animations, calculations, comparisons between previously calculated animations) to occur after the animation window has been closed. Closing the program defeats this purpose. The point of automating the process is that I can leave my computer with a bucketload of calculations to chug through and have it finished by the time i get back. If I have to be there to restart after every animation, then I may as well stick with what I had.
To get an idea of what I want, if you add the following to the bottom of the code, then what I want is for the first animation to run, then close, then the second animation to run: multiplier = 0; Schrodinger_Solver()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.