Bugs in close('all') with the TkAgg backend

Hi all,

we've just run into a nasty problem with the TkAgg backend if close('all') is called. In our setup, we use ipython+pylab with TkAgg because we also need MayaVi to be active, and the GTK/WX backends block Tk windows. But if a call is made to close('all'), matplotlib closes not only all of its own windows, but also it destroys the MayaVi window in some very nasty way. The VTK wrapper complains loudly about improper deletions, and afterwards running any mayavi code is impossible (Tcl/Tk errors come from inside python itself).

The problem is that matplotlib should not be touching any windows that don't belong to it. I quickly wrote the following wrapper code to use here to work around this bug:

try:
     mm.all_figures
except AttributeError:
     mm.all_figures = []

def figure(num=1):
     """Wrapper around mm.figure which updates a global list of held figures."""
     mm.figure(num)
     mm.all_figures.append(num)

def close_all():
     """Close all open figures managed by our figure() wrapper."""

     print 'Closing figures:',mm.all_figures
     map(mm.close,mm.all_figures)
     mm.all_figures = []

but it would be nice to see the TkAgg backend do the right thing.

I should also mention that when I first wrote my wrapper code, I used in figure() the following:

mm.all_figures.append(mm.figure(num))

to append the actual figure handles. This didn't work because the close() call to a handle seems to be also broken. I worked around this other bug by using figure numbers instead of handles.

While we're at it, I think it would be nice to extend the close() syntax to allow a sequence of integers or figure handles to be passed to it, so that one could simply say

close([1,3,5,21,101])

to only close those windows, or the same with their respective figure handles (nicely stored by ipython in the _NN variables).

Regards,

f

Fernando Perez wrote:

Hi all,

we've just run into a nasty problem with the TkAgg backend if close('all') is called. In our setup, we use ipython+pylab with TkAgg because we also need MayaVi to be active, and the GTK/WX backends block Tk windows. But if a call is made to close('all'), matplotlib closes not only all of its own windows, but also it destroys the MayaVi window in some very nasty way. The VTK wrapper complains loudly about improper deletions, and afterwards running any mayavi code is impossible (Tcl/Tk errors come from inside python itself).

The problem is that matplotlib should not be touching any windows that don't belong to it. I quickly wrote the following wrapper code to use here to work around this bug:

OK, a bit more info. It turns out that the crash happens whenever the _first_ figure window is deleted with a close(fignum) command. So in order to really block the problem, I had to create a dummy 'sentinel' window, numbered -666, for which close() is never called. It's OK to close this window via the window manager by clicking on its close button, but pylab.close() must NEVER be called on it. The current code looks like this:

# Temporary hack around a matplotlib figure closing bug
import matplotlib.pylab as mm

try:
     mm.all_figures
except AttributeError:
     mm.all_figures = []

# hack: sentinel to prevent pylab from destroying tk windows. NEVER make a -666 figure!
mm.figure(-666)

def figure(num=1,*args,**kw):
     """Wrapper around mm.figure which updates a global list of held figures."""
     if num == -666:
         raise ValueError,'-666 is an internal sentinel, do not use for your figures'
     mm.figure(num,*args,**kw)
     mm.all_figures.append(num)

def close(*args):
     """Close all open figures managed by our figure() wrapper."""

     if len(args)==1 and args[0]=='all':
         print 'Closing figures:',mm.all_figures
         map(mm.close,mm.all_figures)
         mm.all_figures = []
     else:
         mm.close(*args)

Regards,

f

Fernando Perez wrote:

OK, a bit more info. It turns out that the crash happens whenever the _first_ figure window is deleted with a close(fignum) command. So in order to really block the problem, I had to create a dummy 'sentinel' window, numbered -666, for which close() is never called. It's OK to close this window via the window manager by clicking on its close button, but pylab.close() must NEVER be called on it. The current code looks like this:

More info, sorry about the noise. It's NOT OK to close the sentinel in any way whatsoever, even via the window manager. If this window is closed, through any mechanism, Tk/VTK is hosed.

This is what you get on screen if you close the very first Tk figure window, once MayaVi has run and made a figure:

In [8]: Generic Warning: In
/usr/local/installers/src/vtk/VTK/Rendering/vtkTkRenderWidget.cxx, line 633
A TkRenderWidget is being destroyed before it associated vtkRenderWindow is
destroyed. This is very bad and usually due to the order in which objects are
being destroyed. Always destroy the vtkRenderWindow before destroying the user
interface components.

Any attempt to use mayavi afterwards produces this traceback:

···

---------------------------------------------------------------------------
_tkinter.TclError Traceback (most recent call
last)

/home/sandberg/WavePropagation3D/Codes/Test/wave1DTEST.py
     269 mm.title('Non-projected D2 wrt prolates')
     270
--> 271 imv.surf(range(nnod),range(nnod),255*improc.mat2gray(D2p))
     272
     273

/usr/lib/python2.3/site-packages/mayavi/tools/imv.py in surf(x, y, z, warp,
scale, viewer, f_args, f_keyw)
     267 # do the mayavi stuff.
     268 if not viewer:
--> 269 v = mayavi.mayavi()
     270 else:
     271 v = viewer

/usr/lib/python2.3/site-packages/mayavi/Main.py in mayavi(geometry)
    1826 t = Tkinter.Toplevel (r)
    1827 t.withdraw ()
-> 1828 app = MayaViTkGUI (t, geometry)
    1829 return app
    1830

/usr/lib/python2.3/site-packages/mayavi/Main.py in __init__(self, master,
geometry)
     918 self.renwin_frame = Tkinter.Frame (master_f)
     919 self.renwin_frame.pack (side='left', fill='both', expand=1)
--> 920 self.renwin = Misc.RenderWindow.RenderWindow (self.renwin_frame)
     921 self.renwin.Render ()
     922

/usr/lib/python2.3/site-packages/mayavi/Misc/RenderWindow.py in __init__(self,
master)
      86 else:
      87 tkw = vtkRenderWidget.vtkTkRenderWidget (self.frame,
width=600,
---> 88 height=505)
      89 self.tkwidget = tkw
      90 self.tkwidget.pack (expand='true',fill='both')

/usr/local/lib/python2.3/site-packages/vtk_python/vtk/tk/vtkTkRenderWidget.py in
__init__(self, master, cnf, **kw)
     104
     105 kw['rw'] = renderWindow.GetAddressAsString("vtkRenderWindow")
--> 106 Tkinter.Widget.__init__(self, master, 'vtkTkRenderWidget', cnf,
kw)
     107
     108 self._CurrentRenderer = None

/usr/src/build/475206-i386/install/usr/lib/python2.3/lib-tk/Tkinter.py in
__init__(self, master, widgetName, cnf, kw, extra)
    1833 classes.append((k, cnf[k]))
    1834 del cnf[k]
-> 1835 self.tk.call(
    1836 (widgetName, self._w) + extra + self._options(cnf))
    1837 for k, v in classes:

TclError: invalid command name "vtkTkRenderWidget"

More interestingly, this leaves python in some very strange state. If you close ipyhton, instead of a system prompt you get the '>>>' python prompt, but you can't execute _anything_ there. Even a simple '1+1' fails, all you can do is quit. So basically the closing of that first matplotlib figure window is destroying enough in the python internals to render it completely unusable.

Regards,

f