##// END OF EJS Templates
Fix critical bug with pylab support inadvertently introduced in #648....
Fernando Perez -
Show More
@@ -1,325 +1,330 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Pylab (matplotlib) support utilities.
3 3
4 4 Authors
5 5 -------
6 6
7 7 * Fernando Perez.
8 8 * Brian Granger
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2009-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 import sys
23 23 from io import BytesIO
24 24
25 25 from IPython.utils.decorators import flag_calls
26 26
27 27 # If user specifies a GUI, that dictates the backend, otherwise we read the
28 28 # user's mpl default from the mpl rc structure
29 29 backends = {'tk': 'TkAgg',
30 30 'gtk': 'GTKAgg',
31 31 'wx': 'WXAgg',
32 32 'qt': 'Qt4Agg', # qt3 not supported
33 33 'qt4': 'Qt4Agg',
34 34 'osx': 'MacOSX',
35 35 'inline' : 'module://IPython.zmq.pylab.backend_inline'}
36 36
37 37 # We also need a reverse backends2guis mapping that will properly choose which
38 38 # GUI support to activate based on the desired matplotlib backend. For the
39 39 # most part it's just a reverse of the above dict, but we also need to add a
40 40 # few others that map to the same GUI manually:
41 41 backend2gui = dict(zip(backends.values(), backends.keys()))
42 42 # In the reverse mapping, there are a few extra valid matplotlib backends that
43 43 # map to the same GUI support
44 44 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
45 45 backend2gui['WX'] = 'wx'
46 46 backend2gui['CocoaAgg'] = 'osx'
47 47
48 48 #-----------------------------------------------------------------------------
49 49 # Matplotlib utilities
50 50 #-----------------------------------------------------------------------------
51 51
52 52
53 53 def getfigs(*fig_nums):
54 54 """Get a list of matplotlib figures by figure numbers.
55 55
56 56 If no arguments are given, all available figures are returned. If the
57 57 argument list contains references to invalid figures, a warning is printed
58 58 but the function continues pasting further figures.
59 59
60 60 Parameters
61 61 ----------
62 62 figs : tuple
63 63 A tuple of ints giving the figure numbers of the figures to return.
64 64 """
65 65 from matplotlib._pylab_helpers import Gcf
66 66 if not fig_nums:
67 67 fig_managers = Gcf.get_all_fig_managers()
68 68 return [fm.canvas.figure for fm in fig_managers]
69 69 else:
70 70 figs = []
71 71 for num in fig_nums:
72 72 f = Gcf.figs.get(num)
73 73 if f is None:
74 74 print('Warning: figure %s not available.' % num)
75 75 else:
76 76 figs.append(f.canvas.figure)
77 77 return figs
78 78
79 79
80 80 def figsize(sizex, sizey):
81 81 """Set the default figure size to be [sizex, sizey].
82 82
83 83 This is just an easy to remember, convenience wrapper that sets::
84 84
85 85 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
86 86 """
87 87 import matplotlib
88 88 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
89 89
90 90
91 91 def print_figure(fig, fmt='png'):
92 92 """Convert a figure to svg or png for inline display."""
93 93 # When there's an empty figure, we shouldn't return anything, otherwise we
94 94 # get big blank areas in the qt console.
95 95 if not fig.axes:
96 96 return
97 97
98 98 fc = fig.get_facecolor()
99 99 ec = fig.get_edgecolor()
100 100 fig.set_facecolor('white')
101 101 fig.set_edgecolor('white')
102 102 try:
103 103 bytes_io = BytesIO()
104 104 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight')
105 105 data = bytes_io.getvalue()
106 106 finally:
107 107 fig.set_facecolor(fc)
108 108 fig.set_edgecolor(ec)
109 109 return data
110 110
111 111
112 112 # We need a little factory function here to create the closure where
113 113 # safe_execfile can live.
114 114 def mpl_runner(safe_execfile):
115 115 """Factory to return a matplotlib-enabled runner for %run.
116 116
117 117 Parameters
118 118 ----------
119 119 safe_execfile : function
120 120 This must be a function with the same interface as the
121 121 :meth:`safe_execfile` method of IPython.
122 122
123 123 Returns
124 124 -------
125 125 A function suitable for use as the ``runner`` argument of the %run magic
126 126 function.
127 127 """
128 128
129 129 def mpl_execfile(fname,*where,**kw):
130 130 """matplotlib-aware wrapper around safe_execfile.
131 131
132 132 Its interface is identical to that of the :func:`execfile` builtin.
133 133
134 134 This is ultimately a call to execfile(), but wrapped in safeties to
135 135 properly handle interactive rendering."""
136 136
137 137 import matplotlib
138 138 import matplotlib.pylab as pylab
139 139
140 140 #print '*** Matplotlib runner ***' # dbg
141 141 # turn off rendering until end of script
142 142 is_interactive = matplotlib.rcParams['interactive']
143 143 matplotlib.interactive(False)
144 144 safe_execfile(fname,*where,**kw)
145 145 matplotlib.interactive(is_interactive)
146 146 # make rendering call now, if the user tried to do it
147 147 if pylab.draw_if_interactive.called:
148 148 pylab.draw()
149 149 pylab.draw_if_interactive.called = False
150 150
151 151 return mpl_execfile
152 152
153 153
154 154 def select_figure_format(shell, fmt):
155 155 """Select figure format for inline backend, either 'png' or 'svg'.
156 156
157 157 Using this method ensures only one figure format is active at a time.
158 158 """
159 159 from matplotlib.figure import Figure
160 160 from IPython.zmq.pylab import backend_inline
161 161
162 162 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
163 163 png_formatter = shell.display_formatter.formatters['image/png']
164 164
165 165 if fmt=='png':
166 166 svg_formatter.type_printers.pop(Figure, None)
167 167 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
168 168 elif fmt=='svg':
169 169 png_formatter.type_printers.pop(Figure, None)
170 170 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
171 171 else:
172 172 raise ValueError("supported formats are: 'png', 'svg', not %r"%fmt)
173 173
174 174 # set the format to be used in the backend()
175 175 backend_inline._figure_format = fmt
176 176
177 177 #-----------------------------------------------------------------------------
178 178 # Code for initializing matplotlib and importing pylab
179 179 #-----------------------------------------------------------------------------
180 180
181 181
182 182 def find_gui_and_backend(gui=None):
183 183 """Given a gui string return the gui and mpl backend.
184 184
185 185 Parameters
186 186 ----------
187 187 gui : str
188 188 Can be one of ('tk','gtk','wx','qt','qt4','inline').
189 189
190 190 Returns
191 191 -------
192 192 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
193 193 'WXAgg','Qt4Agg','module://IPython.zmq.pylab.backend_inline').
194 194 """
195 195
196 196 import matplotlib
197 197
198 198 if gui and gui != 'auto':
199 199 # select backend based on requested gui
200 200 backend = backends[gui]
201 201 else:
202 202 backend = matplotlib.rcParams['backend']
203 203 # In this case, we need to find what the appropriate gui selection call
204 204 # should be for IPython, so we can activate inputhook accordingly
205 205 gui = backend2gui.get(backend, None)
206 206 return gui, backend
207 207
208 208
209 209 def activate_matplotlib(backend):
210 210 """Activate the given backend and set interactive to True."""
211 211
212 212 import matplotlib
213 213 if backend.startswith('module://'):
214 214 # Work around bug in matplotlib: matplotlib.use converts the
215 215 # backend_id to lowercase even if a module name is specified!
216 216 matplotlib.rcParams['backend'] = backend
217 217 else:
218 218 matplotlib.use(backend)
219 219 matplotlib.interactive(True)
220 220
221 221 # This must be imported last in the matplotlib series, after
222 222 # backend/interactivity choices have been made
223 223 import matplotlib.pylab as pylab
224 224
225 225 # XXX For now leave this commented out, but depending on discussions with
226 226 # mpl-dev, we may be able to allow interactive switching...
227 227 #import matplotlib.pyplot
228 228 #matplotlib.pyplot.switch_backend(backend)
229 229
230 230 pylab.show._needmain = False
231 231 # We need to detect at runtime whether show() is called by the user.
232 232 # For this, we wrap it into a decorator which adds a 'called' flag.
233 233 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
234 234
235 235 def import_pylab(user_ns, backend, import_all=True, shell=None):
236 236 """Import the standard pylab symbols into user_ns."""
237 237
238 238 # Import numpy as np/pyplot as plt are conventions we're trying to
239 239 # somewhat standardize on. Making them available to users by default
240 240 # will greatly help this.
241 241 s = ("import numpy\n"
242 242 "import matplotlib\n"
243 243 "from matplotlib import pylab, mlab, pyplot\n"
244 244 "np = numpy\n"
245 245 "plt = pyplot\n"
246 246 )
247 247 exec s in user_ns
248 248
249 249 if shell is not None:
250 exec s in shell.user_ns_hidden
250 # All local executions are done in a fresh namespace and we then update
251 # the set of 'hidden' keys so these variables don't show up in %who
252 # (which is meant to show only what the user has manually defined).
253 ns = {}
254 exec s in ns
251 255 # If using our svg payload backend, register the post-execution
252 256 # function that will pick up the results for display. This can only be
253 257 # done with access to the real shell object.
254 258 #
255 259 from IPython.zmq.pylab.backend_inline import InlineBackend
256 260
257 261 cfg = InlineBackend.instance(config=shell.config)
258 262 cfg.shell = shell
259 263 if cfg not in shell.configurables:
260 264 shell.configurables.append(cfg)
261 265
262 266 if backend == backends['inline']:
263 267 from IPython.zmq.pylab.backend_inline import flush_figures
264 268 from matplotlib import pyplot
265 269 shell.register_post_execute(flush_figures)
266 270 # load inline_rc
267 271 pyplot.rcParams.update(cfg.rc)
268 272
269 273 # Add 'figsize' to pyplot and to the user's namespace
270 274 user_ns['figsize'] = pyplot.figsize = figsize
271 shell.user_ns_hidden['figsize'] = figsize
275 ns['figsize'] = figsize
272 276
273 277 # Setup the default figure format
274 278 fmt = cfg.figure_format
275 279 select_figure_format(shell, fmt)
276 280
277 281 # The old pastefig function has been replaced by display
278 282 from IPython.core.display import display
279 283 # Add display and display_png to the user's namespace
280 user_ns['display'] = display
281 shell.user_ns_hidden['display'] = display
282 user_ns['getfigs'] = getfigs
283 shell.user_ns_hidden['getfigs'] = getfigs
284 ns['display'] = user_ns['display'] = display
285 ns['getfigs'] = user_ns['getfigs'] = getfigs
284 286
285 287 if import_all:
286 288 s = ("from matplotlib.pylab import *\n"
287 289 "from numpy import *\n")
288 290 exec s in user_ns
289 291 if shell is not None:
290 exec s in shell.user_ns_hidden
292 exec s in ns
293
294 # Update the set of hidden variables with anything we've done here.
295 shell.user_ns_hidden.update(ns)
291 296
292 297
293 298 def pylab_activate(user_ns, gui=None, import_all=True, shell=None):
294 299 """Activate pylab mode in the user's namespace.
295 300
296 301 Loads and initializes numpy, matplotlib and friends for interactive use.
297 302
298 303 Parameters
299 304 ----------
300 305 user_ns : dict
301 306 Namespace where the imports will occur.
302 307
303 308 gui : optional, string
304 309 A valid gui name following the conventions of the %gui magic.
305 310
306 311 import_all : optional, boolean
307 312 If true, an 'import *' is done from numpy and pylab.
308 313
309 314 Returns
310 315 -------
311 316 The actual gui used (if not given as input, it was obtained from matplotlib
312 317 itself, and will be needed next to configure IPython's gui integration.
313 318 """
314 319 gui, backend = find_gui_and_backend(gui)
315 320 activate_matplotlib(backend)
316 321 import_pylab(user_ns, backend, import_all, shell)
317 322
318 323 print """
319 324 Welcome to pylab, a matplotlib-based Python environment [backend: %s].
320 325 For more information, type 'help(pylab)'.""" % backend
321 326 # flush stdout, just to be safe
322 327 sys.stdout.flush()
323 328
324 329 return gui
325 330
@@ -1,54 +1,62 b''
1 1 """Tests for pylab tools module.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (c) 2011, the IPython Development Team.
5 5 #
6 6 # Distributed under the terms of the Modified BSD License.
7 7 #
8 8 # The full license is in the file COPYING.txt, distributed with this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14 from __future__ import print_function
15 15
16 16 # Stdlib imports
17 17
18 18 # Third-party imports
19 19 import matplotlib; matplotlib.use('Agg')
20 20 import nose.tools as nt
21 21
22 22 from matplotlib import pyplot as plt
23 import numpy as np
23 24
24 25 # Our own imports
25 26 from IPython.testing import decorators as dec
26 27 from .. import pylabtools as pt
27 28
28 29 #-----------------------------------------------------------------------------
29 30 # Globals and constants
30 31 #-----------------------------------------------------------------------------
31 32
32 33 #-----------------------------------------------------------------------------
33 34 # Local utilities
34 35 #-----------------------------------------------------------------------------
35 36
36 37 #-----------------------------------------------------------------------------
37 38 # Classes and functions
38 39 #-----------------------------------------------------------------------------
39 40
40 41 @dec.parametric
41 42 def test_figure_to_svg():
42 43 # simple empty-figure test
43 44 fig = plt.figure()
44 45 yield nt.assert_equal(pt.print_figure(fig, 'svg'), None)
45 46
46 47 plt.close('all')
47 48
48 49 # simple check for at least svg-looking output
49 50 fig = plt.figure()
50 51 ax = fig.add_subplot(1,1,1)
51 52 ax.plot([1,2,3])
52 53 plt.draw()
53 54 svg = pt.print_figure(fig, 'svg')[:100].lower()
54 55 yield nt.assert_true('doctype svg' in svg)
56
57
58 def test_import_pylab():
59 ip = get_ipython()
60 pt.import_pylab(ip.user_ns, 'inline', import_all=False, shell=ip)
61 nt.assert_true('plt' in ip.user_ns)
62 nt.assert_equal(ip.user_ns['np'], np)
General Comments 0
You need to be logged in to leave comments. Login now