##// END OF EJS Templates
Backport PR #13162: print_figure return base64 str instead of bytes
Matthias Bussonnier -
Show More
@@ -1,391 +1,417 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Pylab (matplotlib) support utilities."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from io import BytesIO
8 from binascii import b2a_base64
9 from functools import partial
8 10 import warnings
9 11
10 12 from IPython.core.display import _pngxy
11 13 from IPython.utils.decorators import flag_calls
12 14
13 15 # If user specifies a GUI, that dictates the backend, otherwise we read the
14 16 # user's mpl default from the mpl rc structure
15 17 backends = {
16 18 "tk": "TkAgg",
17 19 "gtk": "GTKAgg",
18 20 "gtk3": "GTK3Agg",
19 21 "gtk4": "GTK4Agg",
20 22 "wx": "WXAgg",
21 23 "qt4": "Qt4Agg",
22 24 "qt5": "Qt5Agg",
23 25 "qt6": "QtAgg",
24 26 "qt": "Qt5Agg",
25 27 "osx": "MacOSX",
26 28 "nbagg": "nbAgg",
27 29 "notebook": "nbAgg",
28 30 "agg": "agg",
29 31 "svg": "svg",
30 32 "pdf": "pdf",
31 33 "ps": "ps",
32 34 "inline": "module://matplotlib_inline.backend_inline",
33 35 "ipympl": "module://ipympl.backend_nbagg",
34 36 "widget": "module://ipympl.backend_nbagg",
35 37 }
36 38
37 39 # We also need a reverse backends2guis mapping that will properly choose which
38 40 # GUI support to activate based on the desired matplotlib backend. For the
39 41 # most part it's just a reverse of the above dict, but we also need to add a
40 42 # few others that map to the same GUI manually:
41 43 backend2gui = dict(zip(backends.values(), backends.keys()))
42 44 # Our tests expect backend2gui to just return 'qt'
43 45 backend2gui['Qt4Agg'] = 'qt'
44 46 # In the reverse mapping, there are a few extra valid matplotlib backends that
45 47 # map to the same GUI support
46 48 backend2gui["GTK"] = backend2gui["GTKCairo"] = "gtk"
47 49 backend2gui["GTK3Cairo"] = "gtk3"
48 50 backend2gui["GTK4Cairo"] = "gtk4"
49 51 backend2gui["WX"] = "wx"
50 52 backend2gui["CocoaAgg"] = "osx"
51 53 # And some backends that don't need GUI integration
52 54 del backend2gui["nbAgg"]
53 55 del backend2gui["agg"]
54 56 del backend2gui["svg"]
55 57 del backend2gui["pdf"]
56 58 del backend2gui["ps"]
57 59 del backend2gui["module://matplotlib_inline.backend_inline"]
58 60
59 61 #-----------------------------------------------------------------------------
60 62 # Matplotlib utilities
61 63 #-----------------------------------------------------------------------------
62 64
63 65
64 66 def getfigs(*fig_nums):
65 67 """Get a list of matplotlib figures by figure numbers.
66 68
67 69 If no arguments are given, all available figures are returned. If the
68 70 argument list contains references to invalid figures, a warning is printed
69 71 but the function continues pasting further figures.
70 72
71 73 Parameters
72 74 ----------
73 75 figs : tuple
74 76 A tuple of ints giving the figure numbers of the figures to return.
75 77 """
76 78 from matplotlib._pylab_helpers import Gcf
77 79 if not fig_nums:
78 80 fig_managers = Gcf.get_all_fig_managers()
79 81 return [fm.canvas.figure for fm in fig_managers]
80 82 else:
81 83 figs = []
82 84 for num in fig_nums:
83 85 f = Gcf.figs.get(num)
84 86 if f is None:
85 87 print('Warning: figure %s not available.' % num)
86 88 else:
87 89 figs.append(f.canvas.figure)
88 90 return figs
89 91
90 92
91 93 def figsize(sizex, sizey):
92 94 """Set the default figure size to be [sizex, sizey].
93 95
94 96 This is just an easy to remember, convenience wrapper that sets::
95 97
96 98 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
97 99 """
98 100 import matplotlib
99 101 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
100 102
101 103
102 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
104 def print_figure(fig, fmt="png", bbox_inches="tight", base64=False, **kwargs):
103 105 """Print a figure to an image, and return the resulting file data
104 106
105 107 Returned data will be bytes unless ``fmt='svg'``,
106 108 in which case it will be unicode.
107 109
108 110 Any keyword args are passed to fig.canvas.print_figure,
109 111 such as ``quality`` or ``bbox_inches``.
112
113 If `base64` is True, return base64-encoded str instead of raw bytes
114 for binary-encoded image formats
115
116 .. versionadded: 7.29
117 base64 argument
110 118 """
111 119 # When there's an empty figure, we shouldn't return anything, otherwise we
112 120 # get big blank areas in the qt console.
113 121 if not fig.axes and not fig.lines:
114 122 return
115 123
116 124 dpi = fig.dpi
117 125 if fmt == 'retina':
118 126 dpi = dpi * 2
119 127 fmt = 'png'
120 128
121 129 # build keyword args
122 130 kw = {
123 131 "format":fmt,
124 132 "facecolor":fig.get_facecolor(),
125 133 "edgecolor":fig.get_edgecolor(),
126 134 "dpi":dpi,
127 135 "bbox_inches":bbox_inches,
128 136 }
129 137 # **kwargs get higher priority
130 138 kw.update(kwargs)
131 139
132 140 bytes_io = BytesIO()
133 141 if fig.canvas is None:
134 142 from matplotlib.backend_bases import FigureCanvasBase
135 143 FigureCanvasBase(fig)
136 144
137 145 fig.canvas.print_figure(bytes_io, **kw)
138 146 data = bytes_io.getvalue()
139 147 if fmt == 'svg':
140 148 data = data.decode('utf-8')
149 elif base64:
150 data = b2a_base64(data).decode("ascii")
141 151 return data
142 152
143 def retina_figure(fig, **kwargs):
144 """format a figure as a pixel-doubled (retina) PNG"""
145 pngdata = print_figure(fig, fmt='retina', **kwargs)
153 def retina_figure(fig, base64=False, **kwargs):
154 """format a figure as a pixel-doubled (retina) PNG
155
156 If `base64` is True, return base64-encoded str instead of raw bytes
157 for binary-encoded image formats
158
159 .. versionadded: 7.29
160 base64 argument
161 """
162 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
146 163 # Make sure that retina_figure acts just like print_figure and returns
147 164 # None when the figure is empty.
148 165 if pngdata is None:
149 166 return
150 167 w, h = _pngxy(pngdata)
151 168 metadata = {"width": w//2, "height":h//2}
169 if base64:
170 pngdata = b2a_base64(pngdata).decode("ascii")
152 171 return pngdata, metadata
153 172
173
154 174 # We need a little factory function here to create the closure where
155 175 # safe_execfile can live.
156 176 def mpl_runner(safe_execfile):
157 177 """Factory to return a matplotlib-enabled runner for %run.
158 178
159 179 Parameters
160 180 ----------
161 181 safe_execfile : function
162 182 This must be a function with the same interface as the
163 183 :meth:`safe_execfile` method of IPython.
164 184
165 185 Returns
166 186 -------
167 187 A function suitable for use as the ``runner`` argument of the %run magic
168 188 function.
169 189 """
170 190
171 191 def mpl_execfile(fname,*where,**kw):
172 192 """matplotlib-aware wrapper around safe_execfile.
173 193
174 194 Its interface is identical to that of the :func:`execfile` builtin.
175 195
176 196 This is ultimately a call to execfile(), but wrapped in safeties to
177 197 properly handle interactive rendering."""
178 198
179 199 import matplotlib
180 200 import matplotlib.pyplot as plt
181 201
182 202 #print '*** Matplotlib runner ***' # dbg
183 203 # turn off rendering until end of script
184 204 is_interactive = matplotlib.rcParams['interactive']
185 205 matplotlib.interactive(False)
186 206 safe_execfile(fname,*where,**kw)
187 207 matplotlib.interactive(is_interactive)
188 208 # make rendering call now, if the user tried to do it
189 209 if plt.draw_if_interactive.called:
190 210 plt.draw()
191 211 plt.draw_if_interactive.called = False
192 212
193 213 # re-draw everything that is stale
194 214 try:
195 215 da = plt.draw_all
196 216 except AttributeError:
197 217 pass
198 218 else:
199 219 da()
200 220
201 221 return mpl_execfile
202 222
203 223
204 224 def _reshow_nbagg_figure(fig):
205 225 """reshow an nbagg figure"""
206 226 try:
207 227 reshow = fig.canvas.manager.reshow
208 228 except AttributeError:
209 229 raise NotImplementedError()
210 230 else:
211 231 reshow()
212 232
213 233
214 234 def select_figure_formats(shell, formats, **kwargs):
215 235 """Select figure formats for the inline backend.
216 236
217 237 Parameters
218 238 ==========
219 239 shell : InteractiveShell
220 240 The main IPython instance.
221 241 formats : str or set
222 242 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
223 243 **kwargs : any
224 244 Extra keyword arguments to be passed to fig.canvas.print_figure.
225 245 """
226 246 import matplotlib
227 247 from matplotlib.figure import Figure
228 248
229 249 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
230 250 png_formatter = shell.display_formatter.formatters['image/png']
231 251 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
232 252 pdf_formatter = shell.display_formatter.formatters['application/pdf']
233 253
234 254 if isinstance(formats, str):
235 255 formats = {formats}
236 256 # cast in case of list / tuple
237 257 formats = set(formats)
238 258
239 259 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
240 260 mplbackend = matplotlib.get_backend().lower()
241 261 if mplbackend == 'nbagg' or mplbackend == 'module://ipympl.backend_nbagg':
242 262 formatter = shell.display_formatter.ipython_display_formatter
243 263 formatter.for_type(Figure, _reshow_nbagg_figure)
244 264
245 265 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
246 266 bad = formats.difference(supported)
247 267 if bad:
248 268 bs = "%s" % ','.join([repr(f) for f in bad])
249 269 gs = "%s" % ','.join([repr(f) for f in supported])
250 270 raise ValueError("supported formats are: %s not %s" % (gs, bs))
251 271
252 if 'png' in formats:
253 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
254 if 'retina' in formats or 'png2x' in formats:
255 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
256 if 'jpg' in formats or 'jpeg' in formats:
257 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
258 if 'svg' in formats:
259 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
260 if 'pdf' in formats:
261 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
272 if "png" in formats:
273 png_formatter.for_type(
274 Figure, partial(print_figure, fmt="png", base64=True, **kwargs)
275 )
276 if "retina" in formats or "png2x" in formats:
277 png_formatter.for_type(Figure, partial(retina_figure, base64=True, **kwargs))
278 if "jpg" in formats or "jpeg" in formats:
279 jpg_formatter.for_type(
280 Figure, partial(print_figure, fmt="jpg", base64=True, **kwargs)
281 )
282 if "svg" in formats:
283 svg_formatter.for_type(Figure, partial(print_figure, fmt="svg", **kwargs))
284 if "pdf" in formats:
285 pdf_formatter.for_type(
286 Figure, partial(print_figure, fmt="pdf", base64=True, **kwargs)
287 )
262 288
263 289 #-----------------------------------------------------------------------------
264 290 # Code for initializing matplotlib and importing pylab
265 291 #-----------------------------------------------------------------------------
266 292
267 293
268 294 def find_gui_and_backend(gui=None, gui_select=None):
269 295 """Given a gui string return the gui and mpl backend.
270 296
271 297 Parameters
272 298 ----------
273 299 gui : str
274 300 Can be one of ('tk','gtk','wx','qt','qt4','inline','agg').
275 301 gui_select : str
276 302 Can be one of ('tk','gtk','wx','qt','qt4','inline').
277 303 This is any gui already selected by the shell.
278 304
279 305 Returns
280 306 -------
281 307 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
282 308 'WXAgg','Qt4Agg','module://matplotlib_inline.backend_inline','agg').
283 309 """
284 310
285 311 import matplotlib
286 312
287 313 if gui and gui != 'auto':
288 314 # select backend based on requested gui
289 315 backend = backends[gui]
290 316 if gui == 'agg':
291 317 gui = None
292 318 else:
293 319 # We need to read the backend from the original data structure, *not*
294 320 # from mpl.rcParams, since a prior invocation of %matplotlib may have
295 321 # overwritten that.
296 322 # WARNING: this assumes matplotlib 1.1 or newer!!
297 323 backend = matplotlib.rcParamsOrig['backend']
298 324 # In this case, we need to find what the appropriate gui selection call
299 325 # should be for IPython, so we can activate inputhook accordingly
300 326 gui = backend2gui.get(backend, None)
301 327
302 328 # If we have already had a gui active, we need it and inline are the
303 329 # ones allowed.
304 330 if gui_select and gui != gui_select:
305 331 gui = gui_select
306 332 backend = backends[gui]
307 333
308 334 return gui, backend
309 335
310 336
311 337 def activate_matplotlib(backend):
312 338 """Activate the given backend and set interactive to True."""
313 339
314 340 import matplotlib
315 341 matplotlib.interactive(True)
316 342
317 343 # Matplotlib had a bug where even switch_backend could not force
318 344 # the rcParam to update. This needs to be set *before* the module
319 345 # magic of switch_backend().
320 346 matplotlib.rcParams['backend'] = backend
321 347
322 348 # Due to circular imports, pyplot may be only partially initialised
323 349 # when this function runs.
324 350 # So avoid needing matplotlib attribute-lookup to access pyplot.
325 351 from matplotlib import pyplot as plt
326 352
327 353 plt.switch_backend(backend)
328 354
329 355 plt.show._needmain = False
330 356 # We need to detect at runtime whether show() is called by the user.
331 357 # For this, we wrap it into a decorator which adds a 'called' flag.
332 358 plt.draw_if_interactive = flag_calls(plt.draw_if_interactive)
333 359
334 360
335 361 def import_pylab(user_ns, import_all=True):
336 362 """Populate the namespace with pylab-related values.
337 363
338 364 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
339 365
340 366 Also imports a few names from IPython (figsize, display, getfigs)
341 367
342 368 """
343 369
344 370 # Import numpy as np/pyplot as plt are conventions we're trying to
345 371 # somewhat standardize on. Making them available to users by default
346 372 # will greatly help this.
347 373 s = ("import numpy\n"
348 374 "import matplotlib\n"
349 375 "from matplotlib import pylab, mlab, pyplot\n"
350 376 "np = numpy\n"
351 377 "plt = pyplot\n"
352 378 )
353 379 exec(s, user_ns)
354 380
355 381 if import_all:
356 382 s = ("from matplotlib.pylab import *\n"
357 383 "from numpy import *\n")
358 384 exec(s, user_ns)
359 385
360 386 # IPython symbols to add
361 387 user_ns['figsize'] = figsize
362 388 from IPython.core.display import display
363 389 # Add display and getfigs to the user's namespace
364 390 user_ns['display'] = display
365 391 user_ns['getfigs'] = getfigs
366 392
367 393
368 394 def configure_inline_support(shell, backend):
369 395 """
370 396 .. deprecated: 7.23
371 397
372 398 use `matplotlib_inline.backend_inline.configure_inline_support()`
373 399
374 400 Configure an IPython shell object for matplotlib use.
375 401
376 402 Parameters
377 403 ----------
378 404 shell : InteractiveShell instance
379 405
380 406 backend : matplotlib backend
381 407 """
382 408 warnings.warn(
383 409 "`configure_inline_support` is deprecated since IPython 7.23, directly "
384 410 "use `matplotlib_inline.backend_inline.configure_inline_support()`",
385 411 DeprecationWarning,
386 412 stacklevel=2,
387 413 )
388 414
389 415 from matplotlib_inline.backend_inline import configure_inline_support as configure_inline_support_orig
390 416
391 417 configure_inline_support_orig(shell, backend)
@@ -1,459 +1,461 b''
1 1 # Copyright (c) IPython Development Team.
2 2 # Distributed under the terms of the Modified BSD License.
3 3
4 4 import json
5 5 import os
6 6 import warnings
7 7
8 8 from unittest import mock
9 9
10 10 import nose.tools as nt
11 11
12 12 from IPython.core import display
13 13 from IPython.core.getipython import get_ipython
14 14 from IPython.utils.io import capture_output
15 15 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
16 16 from IPython import paths as ipath
17 17 from IPython.testing.tools import AssertNotPrints
18 18
19 19 import IPython.testing.decorators as dec
20 20
21 21 def test_image_size():
22 22 """Simple test for display.Image(args, width=x,height=y)"""
23 23 thisurl = 'http://www.google.fr/images/srpr/logo3w.png'
24 24 img = display.Image(url=thisurl, width=200, height=200)
25 25 nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
26 26 img = display.Image(url=thisurl, metadata={'width':200, 'height':200})
27 27 nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
28 28 img = display.Image(url=thisurl, width=200)
29 29 nt.assert_equal(u'<img src="%s" width="200"/>' % (thisurl), img._repr_html_())
30 30 img = display.Image(url=thisurl)
31 31 nt.assert_equal(u'<img src="%s"/>' % (thisurl), img._repr_html_())
32 32 img = display.Image(url=thisurl, unconfined=True)
33 33 nt.assert_equal(u'<img src="%s" class="unconfined"/>' % (thisurl), img._repr_html_())
34 34
35 35
36 36 def test_image_mimes():
37 37 fmt = get_ipython().display_formatter.format
38 38 for format in display.Image._ACCEPTABLE_EMBEDDINGS:
39 39 mime = display.Image._MIMETYPES[format]
40 40 img = display.Image(b'garbage', format=format)
41 41 data, metadata = fmt(img)
42 42 nt.assert_equal(sorted(data), sorted([mime, 'text/plain']))
43 43
44 44
45 45 def test_geojson():
46 46
47 47 gj = display.GeoJSON(data={
48 48 "type": "Feature",
49 49 "geometry": {
50 50 "type": "Point",
51 51 "coordinates": [-81.327, 296.038]
52 52 },
53 53 "properties": {
54 54 "name": "Inca City"
55 55 }
56 56 },
57 57 url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
58 58 layer_options={
59 59 "basemap_id": "celestia_mars-shaded-16k_global",
60 60 "attribution": "Celestia/praesepe",
61 61 "minZoom": 0,
62 62 "maxZoom": 18,
63 63 })
64 64 nt.assert_equal(u'<IPython.core.display.GeoJSON object>', str(gj))
65 65
66 66 def test_retina_png():
67 67 here = os.path.dirname(__file__)
68 68 img = display.Image(os.path.join(here, "2x2.png"), retina=True)
69 69 nt.assert_equal(img.height, 1)
70 70 nt.assert_equal(img.width, 1)
71 71 data, md = img._repr_png_()
72 72 nt.assert_equal(md['width'], 1)
73 73 nt.assert_equal(md['height'], 1)
74 74
75 75 def test_embed_svg_url():
76 76 import gzip
77 77 from io import BytesIO
78 78 svg_data = b'<svg><circle x="0" y="0" r="1"/></svg>'
79 79 url = 'http://test.com/circle.svg'
80 80
81 81 gzip_svg = BytesIO()
82 82 with gzip.open(gzip_svg, 'wb') as fp:
83 83 fp.write(svg_data)
84 84 gzip_svg = gzip_svg.getvalue()
85 85
86 86 def mocked_urlopen(*args, **kwargs):
87 87 class MockResponse:
88 88 def __init__(self, svg):
89 89 self._svg_data = svg
90 90 self.headers = {'content-type': 'image/svg+xml'}
91 91
92 92 def read(self):
93 93 return self._svg_data
94 94
95 95 if args[0] == url:
96 96 return MockResponse(svg_data)
97 97 elif args[0] == url + 'z':
98 98 ret= MockResponse(gzip_svg)
99 99 ret.headers['content-encoding']= 'gzip'
100 100 return ret
101 101 return MockResponse(None)
102 102
103 103 with mock.patch('urllib.request.urlopen', side_effect=mocked_urlopen):
104 104 svg = display.SVG(url=url)
105 105 nt.assert_true(svg._repr_svg_().startswith('<svg'))
106 106 svg = display.SVG(url=url + 'z')
107 107 nt.assert_true(svg._repr_svg_().startswith('<svg'))
108 108
109 109 def test_retina_jpeg():
110 110 here = os.path.dirname(__file__)
111 111 img = display.Image(os.path.join(here, "2x2.jpg"), retina=True)
112 112 nt.assert_equal(img.height, 1)
113 113 nt.assert_equal(img.width, 1)
114 114 data, md = img._repr_jpeg_()
115 115 nt.assert_equal(md['width'], 1)
116 116 nt.assert_equal(md['height'], 1)
117 117
118 118 def test_base64image():
119 119 display.Image("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AAAAACAAHiIbwzAAAAAElFTkSuQmCC")
120 120
121 121 def test_image_filename_defaults():
122 122 '''test format constraint, and validity of jpeg and png'''
123 123 tpath = ipath.get_ipython_package_dir()
124 124 nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat.zip'),
125 125 embed=True)
126 126 nt.assert_raises(ValueError, display.Image)
127 127 nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True)
128 128 # check boths paths to allow packages to test at build and install time
129 129 imgfile = os.path.join(tpath, 'core/tests/2x2.png')
130 130 img = display.Image(filename=imgfile)
131 131 nt.assert_equal('png', img.format)
132 132 nt.assert_is_not_none(img._repr_png_())
133 133 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
134 134 nt.assert_equal('jpeg', img.format)
135 135 nt.assert_is_none(img._repr_jpeg_())
136 136
137 137 def _get_inline_config():
138 138 from matplotlib_inline.config import InlineBackend
139 139 return InlineBackend.instance()
140 140
141 141
142 142 @dec.skip_without("ipykernel")
143 143 @dec.skip_without("matplotlib")
144 144 def test_set_matplotlib_close():
145 145 cfg = _get_inline_config()
146 146 cfg.close_figures = False
147 147 display.set_matplotlib_close()
148 148 assert cfg.close_figures
149 149 display.set_matplotlib_close(False)
150 150 assert not cfg.close_figures
151 151
152 152 _fmt_mime_map = {
153 153 'png': 'image/png',
154 154 'jpeg': 'image/jpeg',
155 155 'pdf': 'application/pdf',
156 156 'retina': 'image/png',
157 157 'svg': 'image/svg+xml',
158 158 }
159 159
160 160 @dec.skip_without('matplotlib')
161 161 def test_set_matplotlib_formats():
162 162 from matplotlib.figure import Figure
163 163 formatters = get_ipython().display_formatter.formatters
164 164 for formats in [
165 165 ('png',),
166 166 ('pdf', 'svg'),
167 167 ('jpeg', 'retina', 'png'),
168 168 (),
169 169 ]:
170 170 active_mimes = {_fmt_mime_map[fmt] for fmt in formats}
171 171 display.set_matplotlib_formats(*formats)
172 172 for mime, f in formatters.items():
173 173 if mime in active_mimes:
174 174 nt.assert_in(Figure, f)
175 175 else:
176 176 nt.assert_not_in(Figure, f)
177 177
178 178
179 179 @dec.skip_without("ipykernel")
180 180 @dec.skip_without("matplotlib")
181 181 def test_set_matplotlib_formats_kwargs():
182 182 from matplotlib.figure import Figure
183 183 ip = get_ipython()
184 184 cfg = _get_inline_config()
185 185 cfg.print_figure_kwargs.update(dict(foo='bar'))
186 186 kwargs = dict(dpi=150)
187 187 display.set_matplotlib_formats('png', **kwargs)
188 188 formatter = ip.display_formatter.formatters['image/png']
189 189 f = formatter.lookup_by_type(Figure)
190 cell = f.__closure__[0].cell_contents
190 formatter_kwargs = f.keywords
191 191 expected = kwargs
192 expected["base64"] = True
193 expected["fmt"] = "png"
192 194 expected.update(cfg.print_figure_kwargs)
193 nt.assert_equal(cell, expected)
195 nt.assert_equal(formatter_kwargs, expected)
194 196
195 197 def test_display_available():
196 198 """
197 199 Test that display is available without import
198 200
199 201 We don't really care if it's in builtin or anything else, but it should
200 202 always be available.
201 203 """
202 204 ip = get_ipython()
203 205 with AssertNotPrints('NameError'):
204 206 ip.run_cell('display')
205 207 try:
206 208 ip.run_cell('del display')
207 209 except NameError:
208 210 pass # it's ok, it might be in builtins
209 211 # even if deleted it should be back
210 212 with AssertNotPrints('NameError'):
211 213 ip.run_cell('display')
212 214
213 215 def test_textdisplayobj_pretty_repr():
214 216 p = display.Pretty("This is a simple test")
215 217 nt.assert_equal(repr(p), '<IPython.core.display.Pretty object>')
216 218 nt.assert_equal(p.data, 'This is a simple test')
217 219
218 220 p._show_mem_addr = True
219 221 nt.assert_equal(repr(p), object.__repr__(p))
220 222
221 223 def test_displayobject_repr():
222 224 h = display.HTML('<br />')
223 225 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
224 226 h._show_mem_addr = True
225 227 nt.assert_equal(repr(h), object.__repr__(h))
226 228 h._show_mem_addr = False
227 229 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
228 230
229 231 j = display.Javascript('')
230 232 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
231 233 j._show_mem_addr = True
232 234 nt.assert_equal(repr(j), object.__repr__(j))
233 235 j._show_mem_addr = False
234 236 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
235 237
236 238 @mock.patch('warnings.warn')
237 239 def test_encourage_iframe_over_html(m_warn):
238 240 display.HTML()
239 241 m_warn.assert_not_called()
240 242
241 243 display.HTML('<br />')
242 244 m_warn.assert_not_called()
243 245
244 246 display.HTML('<html><p>Lots of content here</p><iframe src="http://a.com"></iframe>')
245 247 m_warn.assert_not_called()
246 248
247 249 display.HTML('<iframe src="http://a.com"></iframe>')
248 250 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
249 251
250 252 m_warn.reset_mock()
251 253 display.HTML('<IFRAME SRC="http://a.com"></IFRAME>')
252 254 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
253 255
254 256 def test_progress():
255 257 p = display.ProgressBar(10)
256 258 nt.assert_in('0/10',repr(p))
257 259 p.html_width = '100%'
258 260 p.progress = 5
259 261 nt.assert_equal(p._repr_html_(), "<progress style='width:100%' max='10' value='5'></progress>")
260 262
261 263 def test_progress_iter():
262 264 with capture_output(display=False) as captured:
263 265 for i in display.ProgressBar(5):
264 266 out = captured.stdout
265 267 nt.assert_in('{0}/5'.format(i), out)
266 268 out = captured.stdout
267 269 nt.assert_in('5/5', out)
268 270
269 271 def test_json():
270 272 d = {'a': 5}
271 273 lis = [d]
272 274 metadata = [
273 275 {'expanded': False, 'root': 'root'},
274 276 {'expanded': True, 'root': 'root'},
275 277 {'expanded': False, 'root': 'custom'},
276 278 {'expanded': True, 'root': 'custom'},
277 279 ]
278 280 json_objs = [
279 281 display.JSON(d),
280 282 display.JSON(d, expanded=True),
281 283 display.JSON(d, root='custom'),
282 284 display.JSON(d, expanded=True, root='custom'),
283 285 ]
284 286 for j, md in zip(json_objs, metadata):
285 287 nt.assert_equal(j._repr_json_(), (d, md))
286 288
287 289 with warnings.catch_warnings(record=True) as w:
288 290 warnings.simplefilter("always")
289 291 j = display.JSON(json.dumps(d))
290 292 nt.assert_equal(len(w), 1)
291 293 nt.assert_equal(j._repr_json_(), (d, metadata[0]))
292 294
293 295 json_objs = [
294 296 display.JSON(lis),
295 297 display.JSON(lis, expanded=True),
296 298 display.JSON(lis, root='custom'),
297 299 display.JSON(lis, expanded=True, root='custom'),
298 300 ]
299 301 for j, md in zip(json_objs, metadata):
300 302 nt.assert_equal(j._repr_json_(), (lis, md))
301 303
302 304 with warnings.catch_warnings(record=True) as w:
303 305 warnings.simplefilter("always")
304 306 j = display.JSON(json.dumps(lis))
305 307 nt.assert_equal(len(w), 1)
306 308 nt.assert_equal(j._repr_json_(), (lis, metadata[0]))
307 309
308 310 def test_video_embedding():
309 311 """use a tempfile, with dummy-data, to ensure that video embedding doesn't crash"""
310 312 v = display.Video("http://ignored")
311 313 assert not v.embed
312 314 html = v._repr_html_()
313 315 nt.assert_not_in('src="data:', html)
314 316 nt.assert_in('src="http://ignored"', html)
315 317
316 318 with nt.assert_raises(ValueError):
317 319 v = display.Video(b'abc')
318 320
319 321 with NamedFileInTemporaryDirectory('test.mp4') as f:
320 322 f.write(b'abc')
321 323 f.close()
322 324
323 325 v = display.Video(f.name)
324 326 assert not v.embed
325 327 html = v._repr_html_()
326 328 nt.assert_not_in('src="data:', html)
327 329
328 330 v = display.Video(f.name, embed=True)
329 331 html = v._repr_html_()
330 332 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
331 333
332 334 v = display.Video(f.name, embed=True, mimetype='video/other')
333 335 html = v._repr_html_()
334 336 nt.assert_in('src="data:video/other;base64,YWJj"',html)
335 337
336 338 v = display.Video(b'abc', embed=True, mimetype='video/mp4')
337 339 html = v._repr_html_()
338 340 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
339 341
340 342 v = display.Video(u'YWJj', embed=True, mimetype='video/xyz')
341 343 html = v._repr_html_()
342 344 nt.assert_in('src="data:video/xyz;base64,YWJj"',html)
343 345
344 346 def test_html_metadata():
345 347 s = "<h1>Test</h1>"
346 348 h = display.HTML(s, metadata={"isolated": True})
347 349 nt.assert_equal(h._repr_html_(), (s, {"isolated": True}))
348 350
349 351 def test_display_id():
350 352 ip = get_ipython()
351 353 with mock.patch.object(ip.display_pub, 'publish') as pub:
352 354 handle = display.display('x')
353 355 nt.assert_is(handle, None)
354 356 handle = display.display('y', display_id='secret')
355 357 nt.assert_is_instance(handle, display.DisplayHandle)
356 358 handle2 = display.display('z', display_id=True)
357 359 nt.assert_is_instance(handle2, display.DisplayHandle)
358 360 nt.assert_not_equal(handle.display_id, handle2.display_id)
359 361
360 362 nt.assert_equal(pub.call_count, 3)
361 363 args, kwargs = pub.call_args_list[0]
362 364 nt.assert_equal(args, ())
363 365 nt.assert_equal(kwargs, {
364 366 'data': {
365 367 'text/plain': repr('x')
366 368 },
367 369 'metadata': {},
368 370 })
369 371 args, kwargs = pub.call_args_list[1]
370 372 nt.assert_equal(args, ())
371 373 nt.assert_equal(kwargs, {
372 374 'data': {
373 375 'text/plain': repr('y')
374 376 },
375 377 'metadata': {},
376 378 'transient': {
377 379 'display_id': handle.display_id,
378 380 },
379 381 })
380 382 args, kwargs = pub.call_args_list[2]
381 383 nt.assert_equal(args, ())
382 384 nt.assert_equal(kwargs, {
383 385 'data': {
384 386 'text/plain': repr('z')
385 387 },
386 388 'metadata': {},
387 389 'transient': {
388 390 'display_id': handle2.display_id,
389 391 },
390 392 })
391 393
392 394
393 395 def test_update_display():
394 396 ip = get_ipython()
395 397 with mock.patch.object(ip.display_pub, 'publish') as pub:
396 398 with nt.assert_raises(TypeError):
397 399 display.update_display('x')
398 400 display.update_display('x', display_id='1')
399 401 display.update_display('y', display_id='2')
400 402 args, kwargs = pub.call_args_list[0]
401 403 nt.assert_equal(args, ())
402 404 nt.assert_equal(kwargs, {
403 405 'data': {
404 406 'text/plain': repr('x')
405 407 },
406 408 'metadata': {},
407 409 'transient': {
408 410 'display_id': '1',
409 411 },
410 412 'update': True,
411 413 })
412 414 args, kwargs = pub.call_args_list[1]
413 415 nt.assert_equal(args, ())
414 416 nt.assert_equal(kwargs, {
415 417 'data': {
416 418 'text/plain': repr('y')
417 419 },
418 420 'metadata': {},
419 421 'transient': {
420 422 'display_id': '2',
421 423 },
422 424 'update': True,
423 425 })
424 426
425 427
426 428 def test_display_handle():
427 429 ip = get_ipython()
428 430 handle = display.DisplayHandle()
429 431 nt.assert_is_instance(handle.display_id, str)
430 432 handle = display.DisplayHandle('my-id')
431 433 nt.assert_equal(handle.display_id, 'my-id')
432 434 with mock.patch.object(ip.display_pub, 'publish') as pub:
433 435 handle.display('x')
434 436 handle.update('y')
435 437
436 438 args, kwargs = pub.call_args_list[0]
437 439 nt.assert_equal(args, ())
438 440 nt.assert_equal(kwargs, {
439 441 'data': {
440 442 'text/plain': repr('x')
441 443 },
442 444 'metadata': {},
443 445 'transient': {
444 446 'display_id': handle.display_id,
445 447 }
446 448 })
447 449 args, kwargs = pub.call_args_list[1]
448 450 nt.assert_equal(args, ())
449 451 nt.assert_equal(kwargs, {
450 452 'data': {
451 453 'text/plain': repr('y')
452 454 },
453 455 'metadata': {},
454 456 'transient': {
455 457 'display_id': handle.display_id,
456 458 },
457 459 'update': True,
458 460 })
459 461
@@ -1,259 +1,265 b''
1 1 """Tests for pylab tools module.
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 from io import UnsupportedOperation, BytesIO
8 from binascii import a2b_base64
9 from io import BytesIO
9 10
10 11 import matplotlib
11 12 matplotlib.use('Agg')
12 13 from matplotlib.figure import Figure
13 14
14 15 from nose import SkipTest
15 16 import nose.tools as nt
16 17
17 18 from matplotlib import pyplot as plt
18 19 import matplotlib_inline
19 20 import numpy as np
20 21
21 22 from IPython.core.getipython import get_ipython
22 23 from IPython.core.interactiveshell import InteractiveShell
23 24 from IPython.core.display import _PNG, _JPEG
24 25 from .. import pylabtools as pt
25 26
26 27 from IPython.testing import decorators as dec
27 28
28 29
29 30 def test_figure_to_svg():
30 31 # simple empty-figure test
31 32 fig = plt.figure()
32 33 nt.assert_equal(pt.print_figure(fig, 'svg'), None)
33 34
34 35 plt.close('all')
35 36
36 37 # simple check for at least svg-looking output
37 38 fig = plt.figure()
38 39 ax = fig.add_subplot(1,1,1)
39 40 ax.plot([1,2,3])
40 41 plt.draw()
41 42 svg = pt.print_figure(fig, 'svg')[:100].lower()
42 43 nt.assert_in(u'doctype svg', svg)
43 44
44 45 def _check_pil_jpeg_bytes():
45 46 """Skip if PIL can't write JPEGs to BytesIO objects"""
46 47 # PIL's JPEG plugin can't write to BytesIO objects
47 48 # Pillow fixes this
48 49 from PIL import Image
49 50 buf = BytesIO()
50 51 img = Image.new("RGB", (4,4))
51 52 try:
52 53 img.save(buf, 'jpeg')
53 54 except Exception as e:
54 55 ename = e.__class__.__name__
55 56 raise SkipTest("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e))
56 57
57 58 @dec.skip_without("PIL.Image")
58 59 def test_figure_to_jpeg():
59 60 _check_pil_jpeg_bytes()
60 61 # simple check for at least jpeg-looking output
61 62 fig = plt.figure()
62 63 ax = fig.add_subplot(1,1,1)
63 64 ax.plot([1,2,3])
64 65 plt.draw()
65 66 jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower()
66 67 assert jpeg.startswith(_JPEG)
67 68
68 69 def test_retina_figure():
69 70 # simple empty-figure test
70 71 fig = plt.figure()
71 72 nt.assert_equal(pt.retina_figure(fig), None)
72 73 plt.close('all')
73 74
74 75 fig = plt.figure()
75 76 ax = fig.add_subplot(1,1,1)
76 77 ax.plot([1,2,3])
77 78 plt.draw()
78 79 png, md = pt.retina_figure(fig)
79 80 assert png.startswith(_PNG)
80 81 nt.assert_in('width', md)
81 82 nt.assert_in('height', md)
82 83
83 84 _fmt_mime_map = {
84 85 'png': 'image/png',
85 86 'jpeg': 'image/jpeg',
86 87 'pdf': 'application/pdf',
87 88 'retina': 'image/png',
88 89 'svg': 'image/svg+xml',
89 90 }
90 91
91 92 def test_select_figure_formats_str():
92 93 ip = get_ipython()
93 94 for fmt, active_mime in _fmt_mime_map.items():
94 95 pt.select_figure_formats(ip, fmt)
95 96 for mime, f in ip.display_formatter.formatters.items():
96 97 if mime == active_mime:
97 98 nt.assert_in(Figure, f)
98 99 else:
99 100 nt.assert_not_in(Figure, f)
100 101
101 102 def test_select_figure_formats_kwargs():
102 103 ip = get_ipython()
103 104 kwargs = dict(quality=10, bbox_inches='tight')
104 105 pt.select_figure_formats(ip, 'png', **kwargs)
105 106 formatter = ip.display_formatter.formatters['image/png']
106 107 f = formatter.lookup_by_type(Figure)
107 cell = f.__closure__[0].cell_contents
108 nt.assert_equal(cell, kwargs)
108 cell = f.keywords
109 expected = kwargs
110 expected["base64"] = True
111 expected["fmt"] = "png"
112 assert cell == expected
109 113
110 114 # check that the formatter doesn't raise
111 115 fig = plt.figure()
112 116 ax = fig.add_subplot(1,1,1)
113 117 ax.plot([1,2,3])
114 118 plt.draw()
115 119 formatter.enabled = True
116 120 png = formatter(fig)
117 assert png.startswith(_PNG)
121 assert isinstance(png, str)
122 png_bytes = a2b_base64(png)
123 assert png_bytes.startswith(_PNG)
118 124
119 125 def test_select_figure_formats_set():
120 126 ip = get_ipython()
121 127 for fmts in [
122 128 {'png', 'svg'},
123 129 ['png'],
124 130 ('jpeg', 'pdf', 'retina'),
125 131 {'svg'},
126 132 ]:
127 133 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
128 134 pt.select_figure_formats(ip, fmts)
129 135 for mime, f in ip.display_formatter.formatters.items():
130 136 if mime in active_mimes:
131 137 nt.assert_in(Figure, f)
132 138 else:
133 139 nt.assert_not_in(Figure, f)
134 140
135 141 def test_select_figure_formats_bad():
136 142 ip = get_ipython()
137 143 with nt.assert_raises(ValueError):
138 144 pt.select_figure_formats(ip, 'foo')
139 145 with nt.assert_raises(ValueError):
140 146 pt.select_figure_formats(ip, {'png', 'foo'})
141 147 with nt.assert_raises(ValueError):
142 148 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
143 149
144 150 def test_import_pylab():
145 151 ns = {}
146 152 pt.import_pylab(ns, import_all=False)
147 153 nt.assert_true('plt' in ns)
148 154 nt.assert_equal(ns['np'], np)
149 155
150 156 class TestPylabSwitch(object):
151 157 class Shell(InteractiveShell):
152 158 def enable_gui(self, gui):
153 159 pass
154 160
155 161 def setup(self):
156 162 import matplotlib
157 163 def act_mpl(backend):
158 164 matplotlib.rcParams['backend'] = backend
159 165
160 166 # Save rcParams since they get modified
161 167 self._saved_rcParams = matplotlib.rcParams
162 168 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
163 169 matplotlib.rcParams = dict(backend='Qt4Agg')
164 170 matplotlib.rcParamsOrig = dict(backend='Qt4Agg')
165 171
166 172 # Mock out functions
167 173 self._save_am = pt.activate_matplotlib
168 174 pt.activate_matplotlib = act_mpl
169 175 self._save_ip = pt.import_pylab
170 176 pt.import_pylab = lambda *a,**kw:None
171 177 self._save_cis = matplotlib_inline.backend_inline.configure_inline_support
172 178 matplotlib_inline.backend_inline.configure_inline_support = (
173 179 lambda *a, **kw: None
174 180 )
175 181
176 182 def teardown(self):
177 183 pt.activate_matplotlib = self._save_am
178 184 pt.import_pylab = self._save_ip
179 185 matplotlib_inline.backend_inline.configure_inline_support = self._save_cis
180 186 import matplotlib
181 187 matplotlib.rcParams = self._saved_rcParams
182 188 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
183 189
184 190 def test_qt(self):
185 191 s = self.Shell()
186 192 gui, backend = s.enable_matplotlib(None)
187 193 nt.assert_equal(gui, 'qt')
188 194 nt.assert_equal(s.pylab_gui_select, 'qt')
189 195
190 196 gui, backend = s.enable_matplotlib('inline')
191 197 nt.assert_equal(gui, 'inline')
192 198 nt.assert_equal(s.pylab_gui_select, 'qt')
193 199
194 200 gui, backend = s.enable_matplotlib('qt')
195 201 nt.assert_equal(gui, 'qt')
196 202 nt.assert_equal(s.pylab_gui_select, 'qt')
197 203
198 204 gui, backend = s.enable_matplotlib('inline')
199 205 nt.assert_equal(gui, 'inline')
200 206 nt.assert_equal(s.pylab_gui_select, 'qt')
201 207
202 208 gui, backend = s.enable_matplotlib()
203 209 nt.assert_equal(gui, 'qt')
204 210 nt.assert_equal(s.pylab_gui_select, 'qt')
205 211
206 212 def test_inline(self):
207 213 s = self.Shell()
208 214 gui, backend = s.enable_matplotlib('inline')
209 215 nt.assert_equal(gui, 'inline')
210 216 nt.assert_equal(s.pylab_gui_select, None)
211 217
212 218 gui, backend = s.enable_matplotlib('inline')
213 219 nt.assert_equal(gui, 'inline')
214 220 nt.assert_equal(s.pylab_gui_select, None)
215 221
216 222 gui, backend = s.enable_matplotlib('qt')
217 223 nt.assert_equal(gui, 'qt')
218 224 nt.assert_equal(s.pylab_gui_select, 'qt')
219 225
220 226 def test_inline_twice(self):
221 227 "Using '%matplotlib inline' twice should not reset formatters"
222 228
223 229 ip = self.Shell()
224 230 gui, backend = ip.enable_matplotlib('inline')
225 231 nt.assert_equal(gui, 'inline')
226 232
227 233 fmts = {'png'}
228 234 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
229 235 pt.select_figure_formats(ip, fmts)
230 236
231 237 gui, backend = ip.enable_matplotlib('inline')
232 238 nt.assert_equal(gui, 'inline')
233 239
234 240 for mime, f in ip.display_formatter.formatters.items():
235 241 if mime in active_mimes:
236 242 nt.assert_in(Figure, f)
237 243 else:
238 244 nt.assert_not_in(Figure, f)
239 245
240 246 def test_qt_gtk(self):
241 247 s = self.Shell()
242 248 gui, backend = s.enable_matplotlib('qt')
243 249 nt.assert_equal(gui, 'qt')
244 250 nt.assert_equal(s.pylab_gui_select, 'qt')
245 251
246 252 gui, backend = s.enable_matplotlib('gtk')
247 253 nt.assert_equal(gui, 'qt')
248 254 nt.assert_equal(s.pylab_gui_select, 'qt')
249 255
250 256
251 257 def test_no_gui_backends():
252 258 for k in ['agg', 'svg', 'pdf', 'ps']:
253 259 assert k not in pt.backend2gui
254 260
255 261
256 262 def test_figure_no_canvas():
257 263 fig = Figure()
258 264 fig.canvas = None
259 265 pt.print_figure(fig)
General Comments 0
You need to be logged in to leave comments. Login now