##// END OF EJS Templates
Adding support for multiple figure formats in InlineBackend.
Brian E. Granger -
Show More
@@ -1,346 +1,356 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 from __future__ import print_function
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2009 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import sys
24 24 from io import BytesIO
25 25
26 26 from IPython.core.display import _pngxy
27 27 from IPython.utils.decorators import flag_calls
28 from IPython.utils import py3compat
28 29
29 30 # If user specifies a GUI, that dictates the backend, otherwise we read the
30 31 # user's mpl default from the mpl rc structure
31 32 backends = {'tk': 'TkAgg',
32 33 'gtk': 'GTKAgg',
33 34 'gtk3': 'GTK3Agg',
34 35 'wx': 'WXAgg',
35 36 'qt': 'Qt4Agg', # qt3 not supported
36 37 'qt4': 'Qt4Agg',
37 38 'osx': 'MacOSX',
38 39 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
39 40
40 41 # We also need a reverse backends2guis mapping that will properly choose which
41 42 # GUI support to activate based on the desired matplotlib backend. For the
42 43 # most part it's just a reverse of the above dict, but we also need to add a
43 44 # few others that map to the same GUI manually:
44 45 backend2gui = dict(zip(backends.values(), backends.keys()))
45 46 # Our tests expect backend2gui to just return 'qt'
46 47 backend2gui['Qt4Agg'] = 'qt'
47 48 # In the reverse mapping, there are a few extra valid matplotlib backends that
48 49 # map to the same GUI support
49 50 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
50 51 backend2gui['GTK3Cairo'] = 'gtk3'
51 52 backend2gui['WX'] = 'wx'
52 53 backend2gui['CocoaAgg'] = 'osx'
53 54
54 55 #-----------------------------------------------------------------------------
55 56 # Matplotlib utilities
56 57 #-----------------------------------------------------------------------------
57 58
58 59
59 60 def getfigs(*fig_nums):
60 61 """Get a list of matplotlib figures by figure numbers.
61 62
62 63 If no arguments are given, all available figures are returned. If the
63 64 argument list contains references to invalid figures, a warning is printed
64 65 but the function continues pasting further figures.
65 66
66 67 Parameters
67 68 ----------
68 69 figs : tuple
69 70 A tuple of ints giving the figure numbers of the figures to return.
70 71 """
71 72 from matplotlib._pylab_helpers import Gcf
72 73 if not fig_nums:
73 74 fig_managers = Gcf.get_all_fig_managers()
74 75 return [fm.canvas.figure for fm in fig_managers]
75 76 else:
76 77 figs = []
77 78 for num in fig_nums:
78 79 f = Gcf.figs.get(num)
79 80 if f is None:
80 81 print('Warning: figure %s not available.' % num)
81 82 else:
82 83 figs.append(f.canvas.figure)
83 84 return figs
84 85
85 86
86 87 def figsize(sizex, sizey):
87 88 """Set the default figure size to be [sizex, sizey].
88 89
89 90 This is just an easy to remember, convenience wrapper that sets::
90 91
91 92 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
92 93 """
93 94 import matplotlib
94 95 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
95 96
96 97
97 98 def print_figure(fig, fmt='png', quality=90):
98 99 """Convert a figure to svg, png or jpg for inline display.
99 100 Quality is only relevant for jpg.
100 101 """
101 102 from matplotlib import rcParams
102 103 # When there's an empty figure, we shouldn't return anything, otherwise we
103 104 # get big blank areas in the qt console.
104 105 if not fig.axes and not fig.lines:
105 106 return
106 107
107 108 fc = fig.get_facecolor()
108 109 ec = fig.get_edgecolor()
109 110 bytes_io = BytesIO()
110 111 dpi = rcParams['savefig.dpi']
111 112 if fmt == 'retina':
112 113 dpi = dpi * 2
113 114 fmt = 'png'
114 115 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight',
115 116 facecolor=fc, edgecolor=ec, dpi=dpi, quality=quality)
116 117 data = bytes_io.getvalue()
117 118 return data
118 119
119 120 def retina_figure(fig):
120 121 """format a figure as a pixel-doubled (retina) PNG"""
121 122 pngdata = print_figure(fig, fmt='retina')
122 123 w, h = _pngxy(pngdata)
123 124 metadata = dict(width=w//2, height=h//2)
124 125 return pngdata, metadata
125 126
126 127 # We need a little factory function here to create the closure where
127 128 # safe_execfile can live.
128 129 def mpl_runner(safe_execfile):
129 130 """Factory to return a matplotlib-enabled runner for %run.
130 131
131 132 Parameters
132 133 ----------
133 134 safe_execfile : function
134 135 This must be a function with the same interface as the
135 136 :meth:`safe_execfile` method of IPython.
136 137
137 138 Returns
138 139 -------
139 140 A function suitable for use as the ``runner`` argument of the %run magic
140 141 function.
141 142 """
142 143
143 144 def mpl_execfile(fname,*where,**kw):
144 145 """matplotlib-aware wrapper around safe_execfile.
145 146
146 147 Its interface is identical to that of the :func:`execfile` builtin.
147 148
148 149 This is ultimately a call to execfile(), but wrapped in safeties to
149 150 properly handle interactive rendering."""
150 151
151 152 import matplotlib
152 153 import matplotlib.pylab as pylab
153 154
154 155 #print '*** Matplotlib runner ***' # dbg
155 156 # turn off rendering until end of script
156 157 is_interactive = matplotlib.rcParams['interactive']
157 158 matplotlib.interactive(False)
158 159 safe_execfile(fname,*where,**kw)
159 160 matplotlib.interactive(is_interactive)
160 161 # make rendering call now, if the user tried to do it
161 162 if pylab.draw_if_interactive.called:
162 163 pylab.draw()
163 164 pylab.draw_if_interactive.called = False
164 165
165 166 return mpl_execfile
166 167
167 168
168 def select_figure_format(shell, fmt, quality=90):
169 """Select figure format for inline backend, can be 'png', 'retina', 'jpg', or 'svg'.
169 def select_figure_format(shell, formats, quality=90):
170 """Select figure formats for the inline backend.
170 171
171 Using this method ensures only one figure format is active at a time.
172 Parameters
173 ==========
174 shell : InteractiveShell
175 The main IPython instance
176 formats : list
177 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
172 178 """
173 179 from matplotlib.figure import Figure
174 180 from IPython.kernel.zmq.pylab import backend_inline
175 181
176 182 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
177 183 png_formatter = shell.display_formatter.formatters['image/png']
178 184 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
185 pdf_formatter = shell.display_formatter.formatters['application/pdf']
179 186
180 [ f.type_printers.pop(Figure, None) for f in {svg_formatter, png_formatter, jpg_formatter} ]
187 if isinstance(formats, py3compat.string_types):
188 formats = {formats}
181 189
182 if fmt == 'png':
183 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
184 elif fmt in ('png2x', 'retina'):
185 png_formatter.for_type(Figure, retina_figure)
186 elif fmt in ('jpg', 'jpeg'):
187 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', quality))
188 elif fmt == 'svg':
189 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
190 else:
191 raise ValueError("supported formats are: 'png', 'retina', 'svg', 'jpg', not %r" % fmt)
190 [ f.type_printers.pop(Figure, None) for f in {svg_formatter, png_formatter, jpg_formatter} ]
192 191
193 # set the format to be used in the backend()
194 backend_inline._figure_format = fmt
192 for fmt in formats:
193 if fmt == 'png':
194 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
195 elif fmt in ('png2x', 'retina'):
196 png_formatter.for_type(Figure, retina_figure)
197 elif fmt in ('jpg', 'jpeg'):
198 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', quality))
199 elif fmt == 'svg':
200 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
201 elif fmt == 'pdf':
202 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf'))
203 else:
204 raise ValueError("supported formats are: 'png', 'retina', 'svg', 'jpg', 'pdf' not %r" % fmt)
195 205
196 206 #-----------------------------------------------------------------------------
197 207 # Code for initializing matplotlib and importing pylab
198 208 #-----------------------------------------------------------------------------
199 209
200 210
201 211 def find_gui_and_backend(gui=None, gui_select=None):
202 212 """Given a gui string return the gui and mpl backend.
203 213
204 214 Parameters
205 215 ----------
206 216 gui : str
207 217 Can be one of ('tk','gtk','wx','qt','qt4','inline').
208 218 gui_select : str
209 219 Can be one of ('tk','gtk','wx','qt','qt4','inline').
210 220 This is any gui already selected by the shell.
211 221
212 222 Returns
213 223 -------
214 224 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
215 225 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
216 226 """
217 227
218 228 import matplotlib
219 229
220 230 if gui and gui != 'auto':
221 231 # select backend based on requested gui
222 232 backend = backends[gui]
223 233 else:
224 234 # We need to read the backend from the original data structure, *not*
225 235 # from mpl.rcParams, since a prior invocation of %matplotlib may have
226 236 # overwritten that.
227 237 # WARNING: this assumes matplotlib 1.1 or newer!!
228 238 backend = matplotlib.rcParamsOrig['backend']
229 239 # In this case, we need to find what the appropriate gui selection call
230 240 # should be for IPython, so we can activate inputhook accordingly
231 241 gui = backend2gui.get(backend, None)
232 242
233 243 # If we have already had a gui active, we need it and inline are the
234 244 # ones allowed.
235 245 if gui_select and gui != gui_select:
236 246 gui = gui_select
237 247 backend = backends[gui]
238 248
239 249 return gui, backend
240 250
241 251
242 252 def activate_matplotlib(backend):
243 253 """Activate the given backend and set interactive to True."""
244 254
245 255 import matplotlib
246 256 matplotlib.interactive(True)
247 257
248 258 # Matplotlib had a bug where even switch_backend could not force
249 259 # the rcParam to update. This needs to be set *before* the module
250 260 # magic of switch_backend().
251 261 matplotlib.rcParams['backend'] = backend
252 262
253 263 import matplotlib.pyplot
254 264 matplotlib.pyplot.switch_backend(backend)
255 265
256 266 # This must be imported last in the matplotlib series, after
257 267 # backend/interactivity choices have been made
258 268 import matplotlib.pylab as pylab
259 269
260 270 pylab.show._needmain = False
261 271 # We need to detect at runtime whether show() is called by the user.
262 272 # For this, we wrap it into a decorator which adds a 'called' flag.
263 273 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
264 274
265 275
266 276 def import_pylab(user_ns, import_all=True):
267 277 """Populate the namespace with pylab-related values.
268 278
269 279 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
270 280
271 281 Also imports a few names from IPython (figsize, display, getfigs)
272 282
273 283 """
274 284
275 285 # Import numpy as np/pyplot as plt are conventions we're trying to
276 286 # somewhat standardize on. Making them available to users by default
277 287 # will greatly help this.
278 288 s = ("import numpy\n"
279 289 "import matplotlib\n"
280 290 "from matplotlib import pylab, mlab, pyplot\n"
281 291 "np = numpy\n"
282 292 "plt = pyplot\n"
283 293 )
284 294 exec(s, user_ns)
285 295
286 296 if import_all:
287 297 s = ("from matplotlib.pylab import *\n"
288 298 "from numpy import *\n")
289 299 exec(s, user_ns)
290 300
291 301 # IPython symbols to add
292 302 user_ns['figsize'] = figsize
293 303 from IPython.core.display import display
294 304 # Add display and getfigs to the user's namespace
295 305 user_ns['display'] = display
296 306 user_ns['getfigs'] = getfigs
297 307
298 308
299 309 def configure_inline_support(shell, backend):
300 310 """Configure an IPython shell object for matplotlib use.
301 311
302 312 Parameters
303 313 ----------
304 314 shell : InteractiveShell instance
305 315
306 316 backend : matplotlib backend
307 317 """
308 318 # If using our svg payload backend, register the post-execution
309 319 # function that will pick up the results for display. This can only be
310 320 # done with access to the real shell object.
311 321
312 322 # Note: if we can't load the inline backend, then there's no point
313 323 # continuing (such as in terminal-only shells in environments without
314 324 # zeromq available).
315 325 try:
316 326 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
317 327 except ImportError:
318 328 return
319 329 from matplotlib import pyplot
320 330
321 331 cfg = InlineBackend.instance(parent=shell)
322 332 cfg.shell = shell
323 333 if cfg not in shell.configurables:
324 334 shell.configurables.append(cfg)
325 335
326 336 if backend == backends['inline']:
327 337 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
328 338 shell.register_post_execute(flush_figures)
329 339
330 340 # Save rcParams that will be overwrittern
331 341 shell._saved_rcParams = dict()
332 342 for k in cfg.rc:
333 343 shell._saved_rcParams[k] = pyplot.rcParams[k]
334 344 # load inline_rc
335 345 pyplot.rcParams.update(cfg.rc)
336 346 else:
337 347 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
338 348 if flush_figures in shell._post_execute:
339 349 shell._post_execute.pop(flush_figures)
340 350 if hasattr(shell, '_saved_rcParams'):
341 351 pyplot.rcParams.update(shell._saved_rcParams)
342 352 del shell._saved_rcParams
343 353
344 354 # Setup the default figure format
345 select_figure_format(shell, cfg.figure_format, cfg.quality)
355 select_figure_format(shell, cfg.figure_formats, cfg.quality)
346 356
@@ -1,108 +1,114 b''
1 1 """Configurable for configuring the IPython inline backend
2 2
3 3 This module does not import anything from matplotlib.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (C) 2011 The IPython Development Team
7 7 #
8 8 # Distributed under the terms of the BSD License. The full license is in
9 9 # the file COPYING, distributed as part of this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15
16 16 from IPython.config.configurable import SingletonConfigurable
17 from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum, Bool, Int, TraitError
17 from IPython.utils.traitlets import (
18 Dict, Instance, CaselessStrEnum, Set, Bool, Int, TraitError, Unicode
19 )
18 20 from IPython.utils.warn import warn
19 21
20 22 #-----------------------------------------------------------------------------
21 23 # Configurable for inline backend options
22 24 #-----------------------------------------------------------------------------
23 25
24 26 def pil_available():
25 27 """Test if PIL/Pillow is available"""
26 28 out = False
27 29 try:
28 30 from PIL import Image
29 31 out = True
30 32 except:
31 33 pass
32 34 return out
33 35
34 36 # inherit from InlineBackendConfig for deprecation purposes
35 37 class InlineBackendConfig(SingletonConfigurable):
36 38 pass
37 39
38 40 class InlineBackend(InlineBackendConfig):
39 41 """An object to store configuration of the inline backend."""
40 42
41 43 def _config_changed(self, name, old, new):
42 44 # warn on change of renamed config section
43 45 if new.InlineBackendConfig != old.InlineBackendConfig:
44 46 warn("InlineBackendConfig has been renamed to InlineBackend")
45 47 super(InlineBackend, self)._config_changed(name, old, new)
46 48
47 49 # The typical default figure size is too large for inline use,
48 50 # so we shrink the figure size to 6x4, and tweak fonts to
49 51 # make that fit.
50 52 rc = Dict({'figure.figsize': (6.0,4.0),
51 53 # play nicely with white background in the Qt and notebook frontend
52 54 'figure.facecolor': (1,1,1,0),
53 55 'figure.edgecolor': (1,1,1,0),
54 56 # 12pt labels get cutoff on 6x4 logplots, so use 10pt.
55 57 'font.size': 10,
56 58 # 72 dpi matches SVG/qtconsole
57 59 # this only affects PNG export, as SVG has no dpi setting
58 60 'savefig.dpi': 72,
59 61 # 10pt still needs a little more room on the xlabel:
60 62 'figure.subplot.bottom' : .125
61 63 }, config=True,
62 64 help="""Subset of matplotlib rcParams that should be different for the
63 65 inline backend."""
64 66 )
65 67
68 figure_formats = Set({'png'}, config=True,
69 help="""A set of figure formats to enable: 'png',
70 'retina', 'jpeg', 'svg', 'pdf'.""")
66 71
67 figure_format = CaselessStrEnum(['svg', 'png', 'retina', 'jpg'],
68 default_value='png', config=True,
69 help="""The image format for figures with the inline
70 backend. JPEG requires the PIL/Pillow library.""")
71
72 def _figure_format_changed(self, name, old, new):
72 def _figure_formats_changed(self, name, old, new):
73 73 from IPython.core.pylabtools import select_figure_format
74 if new in {"jpg", "jpeg"}:
74 if 'jpg' in new or 'jpeg' in new:
75 75 if not pil_available():
76 76 raise TraitError("Requires PIL/Pillow for JPG figures")
77 77 if self.shell is None:
78 78 return
79 79 else:
80 80 select_figure_format(self.shell, new)
81 81
82 figure_format = Unicode()
83
84 def _figure_format_changed(self, name, old, new):
85 if new:
86 self.figure_formats = {new}
87
82 88 quality = Int(default_value=90, config=True,
83 89 help="Quality of compression [10-100], currently for lossy JPEG only.")
84 90
85 91 def _quality_changed(self, name, old, new):
86 92 if new < 10 or new > 100:
87 93 raise TraitError("figure JPEG quality must be in [10-100] range.")
88 94
89 95 close_figures = Bool(True, config=True,
90 96 help="""Close all figures at the end of each cell.
91 97
92 98 When True, ensures that each cell starts with no active figures, but it
93 99 also means that one must keep track of references in order to edit or
94 100 redraw figures in subsequent cells. This mode is ideal for the notebook,
95 101 where residual plots from other cells might be surprising.
96 102
97 103 When False, one must call figure() to create new figures. This means
98 104 that gcf() and getfigs() can reference figures created in other cells,
99 105 and the active figure can continue to be edited with pylab/pyplot
100 106 methods that reference the current active figure. This mode facilitates
101 107 iterative editing of figures, and behaves most consistently with
102 108 other matplotlib backends, but figure barriers between cells must
103 109 be explicit.
104 110 """)
105 111
106 112 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
107 113
108 114
General Comments 0
You need to be logged in to leave comments. Login now