From: Fernando P. <Fer...@co...> - 2005-01-31 22:25:48
|
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 |
From: Fernando P. <Fer...@co...> - 2005-01-31 23:10:47
|
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 |
From: Fernando P. <Fer...@co...> - 2005-01-31 23:22:25
|
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 |
From: John H. <jdh...@ac...> - 2005-01-31 23:30:41
|
>>>>> "Fernando" == Fernando Perez <Fer...@co...> writes: Fernando> 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: Fernando> More info, sorry about the noise. It's NOT OK to close Fernando> the sentinel in any way whatsoever, even via the window Fernando> manager. If this window is closed, through any Fernando> mechanism, Tk/VTK is hosed. Just a guess, The problem may be arising when the backend tries to quit when the total figure count reaches zero. The relevant backend_tkagg section is def destroy(self, *args): if Gcf.get_num_fig_managers()==0 and not matplotlib.is_interactive(): if self.window is not None: self.window.quit() if self.window is not None: #print 'calling window destroy' self.window.destroy() self.window = None Try playing with this function and see if you can deduce where the problem is. JDH |
From: Fernando P. <Fer...@co...> - 2005-02-01 00:14:35
|
John Hunter wrote: > Just a guess, > > The problem may be arising when the backend tries to quit when the > total figure count reaches zero. The relevant backend_tkagg section > is > > def destroy(self, *args): > if Gcf.get_num_fig_managers()==0 and not matplotlib.is_interactive(): > if self.window is not None: > self.window.quit() > if self.window is not None: > #print 'calling window destroy' > self.window.destroy() > self.window = None > > Try playing with this function and see if you can deduce where the > problem is. Sorry, but no luck. I tried a few simple things and made no progress, and I really can't spend more time on this right now. But I did find something bizarre. Consider: planck[pylab]> cat tkbug.py from matplotlib import pylab pylab.plot(range(10)) pylab.show() # Now I try to run this with plain python, no ipython in sight: planck[pylab]> python tkbug.py >>> The '>>>' prompt came in after I closed the plot window, and this is exactly the same thing I see with ipython: I get tossed into a naked python prompt, which is half-broken. With ipython, you get all sorts of weird errors related to ipython having been torn down already. With plain python, the only obvious sign of trouble is that readline is broken (^]]A instead of up-arrow, etc.) So something fishy is going on there, but I really don't have time to track it down. For now, I'll live with my hackish 'window sentinel' approach, ugly as it may be. Cheers, f |
From: John H. <jdh...@ac...> - 2005-02-01 02:46:52
|
>>>>> "Fernando" == Fernando Perez <Fer...@co...> writes: Fernando> planck[pylab]> cat tkbug.py from matplotlib import pylab Fernando> pylab.plot(range(10)) pylab.show() Fernando> # Now I try to run this with plain python, no ipython in Fernando> sight: This is the result of adding if rcParams['tk.pythoninspect']: os.environ['PYTHONINSPECT'] = '1' to tkagg. Comment out the pythoninspect line (or set the rc param accordingly) and see if makes a difference. The pythoninspect thing appears to be required to make idle work in interactive mode, though it was introduced for other reasons I won't go into now. The rc param is only in CVS -- you may just want to comment out the whole bit and see if it helps. Let me know... JDH |
From: Fernando P. <Fer...@co...> - 2005-02-01 05:48:23
|
John Hunter wrote: >>>>>>"Fernando" == Fernando Perez <Fer...@co...> writes: > > Fernando> planck[pylab]> cat tkbug.py from matplotlib import pylab > > Fernando> pylab.plot(range(10)) pylab.show() > > Fernando> # Now I try to run this with plain python, no ipython in > Fernando> sight: > > This is the result of adding > > if rcParams['tk.pythoninspect']: > os.environ['PYTHONINSPECT'] = '1' > > to tkagg. Comment out the pythoninspect line (or set the rc param > accordingly) and see if makes a difference. The pythoninspect thing > appears to be required to make idle work in interactive mode, though > it was introduced for other reasons I won't go into now. OK, my report from this afternoon was written in the office, where I have mpl 0.71. I'm now home, and my laptop runs 0.70. Here, I don't see the problem at all. I can't test here the big code where I saw the massive mayavi breakdown, because that was written by my officemate. But at least I can confirm that 0.70, whose show() looks like: def show(): """ Show all the figures and enter the gtk mainloop This should be the last line of your script """ for manager in Gcf.get_all_fig_managers(): manager.show() import matplotlib matplotlib.interactive(True) #os.environ['PYTHONINSPECT'] = '1' if show._needmain: Tk.mainloop() show._needmain = False does not show the spurious prompt thingie (note the PYTHONINSPECT thingie is commented out). I'll try to make this change to 0.71 and test the larger code with the mayavi problems, but it may be a few days before I can do that. HTH, f |
From: Fernando P. <Fer...@co...> - 2005-02-01 21:42:28
|
John Hunter wrote: >>>>>>"Fernando" == Fernando Perez <Fer...@co...> writes: > > Fernando> planck[pylab]> cat tkbug.py from matplotlib import pylab > > Fernando> pylab.plot(range(10)) pylab.show() > > Fernando> # Now I try to run this with plain python, no ipython in > Fernando> sight: > > This is the result of adding > > if rcParams['tk.pythoninspect']: > os.environ['PYTHONINSPECT'] = '1' > > to tkagg. Comment out the pythoninspect line (or set the rc param > accordingly) and see if makes a difference. The pythoninspect thing > appears to be required to make idle work in interactive mode, though > it was introduced for other reasons I won't go into now. OK, to summarize things with respect to this bug. Indeed, commenting out the pythoninspect line solves the spurious prompt problem. But it does nothing to the close('all') bug. I managed to find a small test case to replicate the bug: planck[pylab]> pylab In [1]: cat tkbug.py # This script crashes vtk, when run in an ipython -pylab session, with TkAgg # as the default backend. # The key is that the pylab.plot() call is made BEFORE the imv.surf call. If # I call imv.surf() first, it works fine. Something in matplotlib is # destroying windows it shouldn't. from matplotlib import pylab from mayavi.tools import imv x = y = pylab.arange(256) z = pylab.rand(256,256) pylab.plot(range(10)) pylab.show() imv.surf(x,y,z) print "*** I'm about to close figure 1, this will crash VTK!!! *** \n" pylab.close(1) ########################## EOF In [2]: run tkbug *** I'm about to close figure 1, this will crash VTK!!! *** 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. Somehow, it seems that the Tk figure manager is messing with windows it shouldn't touch. On a related note, I've been playing with some things in matplotlib which require looping through figure lists, making new figures with guaranteed new numbers, etc. I'd like to propose a change. I'd like figure() to allow calling wit None as the num argument. If num is None, it would produce a guaranteed new figure window, with a number equal to the currently highest + 1. This would make it easy, amongst other things, to write code which displays arrays with a proper aspect ratio by wrapping imshow() or figimage(). I can currently make such a guaranteed new number by using the following as a patch to pylab.py: if num==0: error_msg('Figure number can not be 0.\n' + \ 'Hey, give me a break, this is matlab(TM) compatability') # NEW CODE if num is None: allnums = [f.num for f in _pylab_helpers.Gcf.get_all_fig_managers()] if allnums: num = max(allnums) + 1 else: num = 1 # /NEW CODE I also think the default value for num should be None instead of 1. This would allow you to make the following simple, clean use for building new plots: planck[~]> pylab In [1]: plot(range(10)) Out[1]: [<matplotlib.lines.Line2D instance at 0x412ccd8c>] In [2]: figure() Out[2]: <matplotlib.figure.Figure instance at 0x412ccd2c> In [3]: plot(range(20)) Out[3]: [<matplotlib.lines.Line2D instance at 0x410d320c>] In [4]: figure() Out[4]: <matplotlib.figure.Figure instance at 0x410d32ac> In [5]: plot(range(30)) Out[5]: [<matplotlib.lines.Line2D instance at 0x4115b02c>] without never having to worry about manually managing figure number lists (unless you explicitly want to, which you still can). Finally, is this really a close bug? In [20]: fig=plot(range(10)) In [21]: close(fig) ERROR: Unrecognized argument type to close It sounds to me from reading the docstring like this should work, but maybe I just misunderstood things... Regards, f |
From: John H. <jdh...@ac...> - 2005-02-01 23:44:18
|
>>>>> "Fernando" == Fernando Perez <Fer...@co...> writes: Fernando> Finally, is this really a close bug? Fernando> In [20]: fig=plot(range(10)) Fernando> In [21]: close(fig) ERROR: Unrecognized argument type to Fernando> close No, its a Fernando bug :-) plot returns a list of Line2D instances. Perhaps you mean fig = figure() plot(range(10)) close(fig) It's OK, you can stop hitting yourself on the head now. Actually, the error message would have been more helpful if it reported the type, which it will do in the next release. JDH |
From: Fernando P. <Fer...@co...> - 2005-02-01 23:49:30
|
John Hunter wrote: >>>>>>"Fernando" == Fernando Perez <Fer...@co...> writes: > > > Fernando> Finally, is this really a close bug? > > Fernando> In [20]: fig=plot(range(10)) > > Fernando> In [21]: close(fig) ERROR: Unrecognized argument type to > Fernando> close > > No, its a Fernando bug :-) > > plot returns a list of Line2D instances. Perhaps you mean > > fig = figure() > plot(range(10)) > close(fig) No, it's a John bug :) I also had tried that, and this is what I get with TkAgg: In [4]: fig=figure() In [5]: plot(range(10)) Out[5]: [<matplotlib.lines.Line2D instance at 0x4110494c>] In [6]: close(fig) --------------------------------------------------------------------------- exceptions.AttributeError Traceback (most recent call last) /home/fperez/code/python/pylab/<console> /usr/lib/python2.3/site-packages/matplotlib/pylab.py in close(*args) 611 elif isinstance(arg, Figure): 612 for manager in _pylab_helpers.Gcf.get_all_fig_managers(): --> 613 if manager.figure==arg: 614 _pylab_helpers.Gcf.destroy(manager.num) 615 else: AttributeError: FigureManagerTkAgg instance has no attribute 'figure' Which is why I thought it might be the other way around. So this may be a backend-specifig bug. But still a bug :) Cheers, f |