##// END OF EJS Templates
Backport PR #9299: FIX: account for dpi == 'figure'...
Min RK -
r22155:1c5d7488 4.0.x
parent child Browse files
Show More
@@ -1,369 +1,371 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Pylab (matplotlib) support utilities."""
3 3 from __future__ import print_function
4 4
5 5 # Copyright (c) IPython Development Team.
6 6 # Distributed under the terms of the Modified BSD License.
7 7
8 8 from io import BytesIO
9 9
10 10 from IPython.core.display import _pngxy
11 11 from IPython.utils.decorators import flag_calls
12 12 from IPython.utils import py3compat
13 13
14 14 # If user specifies a GUI, that dictates the backend, otherwise we read the
15 15 # user's mpl default from the mpl rc structure
16 16 backends = {'tk': 'TkAgg',
17 17 'gtk': 'GTKAgg',
18 18 'gtk3': 'GTK3Agg',
19 19 'wx': 'WXAgg',
20 20 'qt': 'Qt4Agg', # qt3 not supported
21 21 'qt4': 'Qt4Agg',
22 22 'qt5': 'Qt5Agg',
23 23 'osx': 'MacOSX',
24 24 'nbagg': 'nbAgg',
25 25 'notebook': 'nbAgg',
26 26 'inline' : 'module://ipykernel.pylab.backend_inline'}
27 27
28 28 # We also need a reverse backends2guis mapping that will properly choose which
29 29 # GUI support to activate based on the desired matplotlib backend. For the
30 30 # most part it's just a reverse of the above dict, but we also need to add a
31 31 # few others that map to the same GUI manually:
32 32 backend2gui = dict(zip(backends.values(), backends.keys()))
33 33 # Our tests expect backend2gui to just return 'qt'
34 34 backend2gui['Qt4Agg'] = 'qt'
35 35 # In the reverse mapping, there are a few extra valid matplotlib backends that
36 36 # map to the same GUI support
37 37 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
38 38 backend2gui['GTK3Cairo'] = 'gtk3'
39 39 backend2gui['WX'] = 'wx'
40 40 backend2gui['CocoaAgg'] = 'osx'
41 41
42 42 #-----------------------------------------------------------------------------
43 43 # Matplotlib utilities
44 44 #-----------------------------------------------------------------------------
45 45
46 46
47 47 def getfigs(*fig_nums):
48 48 """Get a list of matplotlib figures by figure numbers.
49 49
50 50 If no arguments are given, all available figures are returned. If the
51 51 argument list contains references to invalid figures, a warning is printed
52 52 but the function continues pasting further figures.
53 53
54 54 Parameters
55 55 ----------
56 56 figs : tuple
57 57 A tuple of ints giving the figure numbers of the figures to return.
58 58 """
59 59 from matplotlib._pylab_helpers import Gcf
60 60 if not fig_nums:
61 61 fig_managers = Gcf.get_all_fig_managers()
62 62 return [fm.canvas.figure for fm in fig_managers]
63 63 else:
64 64 figs = []
65 65 for num in fig_nums:
66 66 f = Gcf.figs.get(num)
67 67 if f is None:
68 68 print('Warning: figure %s not available.' % num)
69 69 else:
70 70 figs.append(f.canvas.figure)
71 71 return figs
72 72
73 73
74 74 def figsize(sizex, sizey):
75 75 """Set the default figure size to be [sizex, sizey].
76 76
77 77 This is just an easy to remember, convenience wrapper that sets::
78 78
79 79 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
80 80 """
81 81 import matplotlib
82 82 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
83 83
84 84
85 85 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
86 86 """Print a figure to an image, and return the resulting file data
87 87
88 88 Returned data will be bytes unless ``fmt='svg'``,
89 89 in which case it will be unicode.
90 90
91 91 Any keyword args are passed to fig.canvas.print_figure,
92 92 such as ``quality`` or ``bbox_inches``.
93 93 """
94 94 from matplotlib import rcParams
95 95 # When there's an empty figure, we shouldn't return anything, otherwise we
96 96 # get big blank areas in the qt console.
97 97 if not fig.axes and not fig.lines:
98 98 return
99 99
100 100 dpi = rcParams['savefig.dpi']
101 if dpi == 'figure':
102 dpi = fig.dpi
101 103 if fmt == 'retina':
102 104 dpi = dpi * 2
103 105 fmt = 'png'
104 106
105 107 # build keyword args
106 108 kw = dict(
107 109 format=fmt,
108 110 facecolor=fig.get_facecolor(),
109 111 edgecolor=fig.get_edgecolor(),
110 112 dpi=dpi,
111 113 bbox_inches=bbox_inches,
112 114 )
113 115 # **kwargs get higher priority
114 116 kw.update(kwargs)
115 117
116 118 bytes_io = BytesIO()
117 119 fig.canvas.print_figure(bytes_io, **kw)
118 120 data = bytes_io.getvalue()
119 121 if fmt == 'svg':
120 122 data = data.decode('utf-8')
121 123 return data
122 124
123 125 def retina_figure(fig, **kwargs):
124 126 """format a figure as a pixel-doubled (retina) PNG"""
125 127 pngdata = print_figure(fig, fmt='retina', **kwargs)
126 128 w, h = _pngxy(pngdata)
127 129 metadata = dict(width=w//2, height=h//2)
128 130 return pngdata, metadata
129 131
130 132 # We need a little factory function here to create the closure where
131 133 # safe_execfile can live.
132 134 def mpl_runner(safe_execfile):
133 135 """Factory to return a matplotlib-enabled runner for %run.
134 136
135 137 Parameters
136 138 ----------
137 139 safe_execfile : function
138 140 This must be a function with the same interface as the
139 141 :meth:`safe_execfile` method of IPython.
140 142
141 143 Returns
142 144 -------
143 145 A function suitable for use as the ``runner`` argument of the %run magic
144 146 function.
145 147 """
146 148
147 149 def mpl_execfile(fname,*where,**kw):
148 150 """matplotlib-aware wrapper around safe_execfile.
149 151
150 152 Its interface is identical to that of the :func:`execfile` builtin.
151 153
152 154 This is ultimately a call to execfile(), but wrapped in safeties to
153 155 properly handle interactive rendering."""
154 156
155 157 import matplotlib
156 158 import matplotlib.pylab as pylab
157 159
158 160 #print '*** Matplotlib runner ***' # dbg
159 161 # turn off rendering until end of script
160 162 is_interactive = matplotlib.rcParams['interactive']
161 163 matplotlib.interactive(False)
162 164 safe_execfile(fname,*where,**kw)
163 165 matplotlib.interactive(is_interactive)
164 166 # make rendering call now, if the user tried to do it
165 167 if pylab.draw_if_interactive.called:
166 168 pylab.draw()
167 169 pylab.draw_if_interactive.called = False
168 170
169 171 return mpl_execfile
170 172
171 173
172 174 def select_figure_formats(shell, formats, **kwargs):
173 175 """Select figure formats for the inline backend.
174 176
175 177 Parameters
176 178 ==========
177 179 shell : InteractiveShell
178 180 The main IPython instance.
179 181 formats : str or set
180 182 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
181 183 **kwargs : any
182 184 Extra keyword arguments to be passed to fig.canvas.print_figure.
183 185 """
184 186 from matplotlib.figure import Figure
185 187 from ipykernel.pylab import backend_inline
186 188
187 189 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
188 190 png_formatter = shell.display_formatter.formatters['image/png']
189 191 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
190 192 pdf_formatter = shell.display_formatter.formatters['application/pdf']
191 193
192 194 if isinstance(formats, py3compat.string_types):
193 195 formats = {formats}
194 196 # cast in case of list / tuple
195 197 formats = set(formats)
196 198
197 199 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
198 200
199 201 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
200 202 bad = formats.difference(supported)
201 203 if bad:
202 204 bs = "%s" % ','.join([repr(f) for f in bad])
203 205 gs = "%s" % ','.join([repr(f) for f in supported])
204 206 raise ValueError("supported formats are: %s not %s" % (gs, bs))
205 207
206 208 if 'png' in formats:
207 209 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
208 210 if 'retina' in formats or 'png2x' in formats:
209 211 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
210 212 if 'jpg' in formats or 'jpeg' in formats:
211 213 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
212 214 if 'svg' in formats:
213 215 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
214 216 if 'pdf' in formats:
215 217 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
216 218
217 219 #-----------------------------------------------------------------------------
218 220 # Code for initializing matplotlib and importing pylab
219 221 #-----------------------------------------------------------------------------
220 222
221 223
222 224 def find_gui_and_backend(gui=None, gui_select=None):
223 225 """Given a gui string return the gui and mpl backend.
224 226
225 227 Parameters
226 228 ----------
227 229 gui : str
228 230 Can be one of ('tk','gtk','wx','qt','qt4','inline').
229 231 gui_select : str
230 232 Can be one of ('tk','gtk','wx','qt','qt4','inline').
231 233 This is any gui already selected by the shell.
232 234
233 235 Returns
234 236 -------
235 237 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
236 238 'WXAgg','Qt4Agg','module://ipykernel.pylab.backend_inline').
237 239 """
238 240
239 241 import matplotlib
240 242
241 243 if gui and gui != 'auto':
242 244 # select backend based on requested gui
243 245 backend = backends[gui]
244 246 else:
245 247 # We need to read the backend from the original data structure, *not*
246 248 # from mpl.rcParams, since a prior invocation of %matplotlib may have
247 249 # overwritten that.
248 250 # WARNING: this assumes matplotlib 1.1 or newer!!
249 251 backend = matplotlib.rcParamsOrig['backend']
250 252 # In this case, we need to find what the appropriate gui selection call
251 253 # should be for IPython, so we can activate inputhook accordingly
252 254 gui = backend2gui.get(backend, None)
253 255
254 256 # If we have already had a gui active, we need it and inline are the
255 257 # ones allowed.
256 258 if gui_select and gui != gui_select:
257 259 gui = gui_select
258 260 backend = backends[gui]
259 261
260 262 return gui, backend
261 263
262 264
263 265 def activate_matplotlib(backend):
264 266 """Activate the given backend and set interactive to True."""
265 267
266 268 import matplotlib
267 269 matplotlib.interactive(True)
268 270
269 271 # Matplotlib had a bug where even switch_backend could not force
270 272 # the rcParam to update. This needs to be set *before* the module
271 273 # magic of switch_backend().
272 274 matplotlib.rcParams['backend'] = backend
273 275
274 276 import matplotlib.pyplot
275 277 matplotlib.pyplot.switch_backend(backend)
276 278
277 279 # This must be imported last in the matplotlib series, after
278 280 # backend/interactivity choices have been made
279 281 import matplotlib.pylab as pylab
280 282
281 283 pylab.show._needmain = False
282 284 # We need to detect at runtime whether show() is called by the user.
283 285 # For this, we wrap it into a decorator which adds a 'called' flag.
284 286 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
285 287
286 288
287 289 def import_pylab(user_ns, import_all=True):
288 290 """Populate the namespace with pylab-related values.
289 291
290 292 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
291 293
292 294 Also imports a few names from IPython (figsize, display, getfigs)
293 295
294 296 """
295 297
296 298 # Import numpy as np/pyplot as plt are conventions we're trying to
297 299 # somewhat standardize on. Making them available to users by default
298 300 # will greatly help this.
299 301 s = ("import numpy\n"
300 302 "import matplotlib\n"
301 303 "from matplotlib import pylab, mlab, pyplot\n"
302 304 "np = numpy\n"
303 305 "plt = pyplot\n"
304 306 )
305 307 exec(s, user_ns)
306 308
307 309 if import_all:
308 310 s = ("from matplotlib.pylab import *\n"
309 311 "from numpy import *\n")
310 312 exec(s, user_ns)
311 313
312 314 # IPython symbols to add
313 315 user_ns['figsize'] = figsize
314 316 from IPython.core.display import display
315 317 # Add display and getfigs to the user's namespace
316 318 user_ns['display'] = display
317 319 user_ns['getfigs'] = getfigs
318 320
319 321
320 322 def configure_inline_support(shell, backend):
321 323 """Configure an IPython shell object for matplotlib use.
322 324
323 325 Parameters
324 326 ----------
325 327 shell : InteractiveShell instance
326 328
327 329 backend : matplotlib backend
328 330 """
329 331 # If using our svg payload backend, register the post-execution
330 332 # function that will pick up the results for display. This can only be
331 333 # done with access to the real shell object.
332 334
333 335 # Note: if we can't load the inline backend, then there's no point
334 336 # continuing (such as in terminal-only shells in environments without
335 337 # zeromq available).
336 338 try:
337 339 from ipykernel.pylab.backend_inline import InlineBackend
338 340 except ImportError:
339 341 return
340 342 from matplotlib import pyplot
341 343
342 344 cfg = InlineBackend.instance(parent=shell)
343 345 cfg.shell = shell
344 346 if cfg not in shell.configurables:
345 347 shell.configurables.append(cfg)
346 348
347 349 if backend == backends['inline']:
348 350 from ipykernel.pylab.backend_inline import flush_figures
349 351 shell.events.register('post_execute', flush_figures)
350 352
351 353 # Save rcParams that will be overwrittern
352 354 shell._saved_rcParams = dict()
353 355 for k in cfg.rc:
354 356 shell._saved_rcParams[k] = pyplot.rcParams[k]
355 357 # load inline_rc
356 358 pyplot.rcParams.update(cfg.rc)
357 359 else:
358 360 from ipykernel.pylab.backend_inline import flush_figures
359 361 try:
360 362 shell.events.unregister('post_execute', flush_figures)
361 363 except ValueError:
362 364 pass
363 365 if hasattr(shell, '_saved_rcParams'):
364 366 pyplot.rcParams.update(shell._saved_rcParams)
365 367 del shell._saved_rcParams
366 368
367 369 # Setup the default figure format
368 370 select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
369 371
General Comments 0
You need to be logged in to leave comments. Login now