##// END OF EJS Templates
Merge pull request #5110 from minrk/bbox_inches...
Brian E. Granger -
r15412:3d365194 merge
parent child Browse files
Show More
@@ -0,0 +1,3 b''
1 * added ``InlineBackend.print_figure_kwargs`` to allow passing keyword arguments
2 to matplotlib's ``Canvas.print_figure``. This can be used to change the value of
3 ``bbox_inches``, which is 'tight' by default, or set the quality of JPEG figures.
@@ -729,23 +729,29 b' def set_matplotlib_formats(*formats, **kwargs):'
729 729
730 730 To set this in your config files use the following::
731 731
732 c.InlineBackend.figure_formats = {'pdf', 'png', 'svg'}
733 c.InlineBackend.quality = 90
732 c.InlineBackend.figure_formats = {'png', 'jpeg'}
733 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
734 734
735 735 Parameters
736 736 ----------
737 *formats : list, tuple
738 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
739 quality : int
740 A percentage for the quality of JPEG figures. Defaults to 90.
737 *formats : strs
738 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
739 **kwargs :
740 Keyword args will be relayed to ``figure.canvas.print_figure``.
741 741 """
742 742 from IPython.core.interactiveshell import InteractiveShell
743 743 from IPython.core.pylabtools import select_figure_formats
744 from IPython.kernel.zmq.pylab.config import InlineBackend
745 # build kwargs, starting with InlineBackend config
746 kw = {}
747 cfg = InlineBackend.instance()
748 kw.update(cfg.print_figure_kwargs)
749 kw.update(**kwargs)
744 750 shell = InteractiveShell.instance()
745 select_figure_formats(shell, formats, quality=90)
751 select_figure_formats(shell, formats, **kw)
746 752
747 753 @skip_doctest
748 def set_matplotlib_close(close):
754 def set_matplotlib_close(close=True):
749 755 """Set whether the inline backend closes all figures automatically or not.
750 756
751 757 By default, the inline backend used in the IPython Notebook will close all
@@ -766,7 +772,7 b' def set_matplotlib_close(close):'
766 772 Should all matplotlib figures be automatically closed after each cell is
767 773 run?
768 774 """
769 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
770 ilbe = InlineBackend.instance()
771 ilbe.close_figures = close
775 from IPython.kernel.zmq.pylab.config import InlineBackend
776 cfg = InlineBackend.instance()
777 cfg.close_figures = close
772 778
@@ -95,9 +95,11 b' def figsize(sizex, sizey):'
95 95 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
96 96
97 97
98 def print_figure(fig, fmt='png', quality=90):
99 """Convert a figure to svg, png or jpg for inline display.
100 Quality is only relevant for jpg.
98 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
99 """Print a figure to an image, and return the resulting bytes
100
101 Any keyword args are passed to fig.canvas.print_figure,
102 such as ``quality`` or ``bbox_inches``.
101 103 """
102 104 from matplotlib import rcParams
103 105 # When there's an empty figure, we shouldn't return anything, otherwise we
@@ -105,21 +107,29 b" def print_figure(fig, fmt='png', quality=90):"
105 107 if not fig.axes and not fig.lines:
106 108 return
107 109
108 fc = fig.get_facecolor()
109 ec = fig.get_edgecolor()
110 bytes_io = BytesIO()
111 110 dpi = rcParams['savefig.dpi']
112 111 if fmt == 'retina':
113 112 dpi = dpi * 2
114 113 fmt = 'png'
115 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight',
116 facecolor=fc, edgecolor=ec, dpi=dpi, quality=quality)
117 data = bytes_io.getvalue()
118 return data
119 114
120 def retina_figure(fig):
115 # build keyword args
116 kw = dict(
117 format=fmt,
118 fc=fig.get_facecolor(),
119 ec=fig.get_edgecolor(),
120 dpi=dpi,
121 bbox_inches=bbox_inches,
122 )
123 # **kwargs get higher priority
124 kw.update(kwargs)
125
126 bytes_io = BytesIO()
127 fig.canvas.print_figure(bytes_io, **kw)
128 return bytes_io.getvalue()
129
130 def retina_figure(fig, **kwargs):
121 131 """format a figure as a pixel-doubled (retina) PNG"""
122 pngdata = print_figure(fig, fmt='retina')
132 pngdata = print_figure(fig, fmt='retina', **kwargs)
123 133 w, h = _pngxy(pngdata)
124 134 metadata = dict(width=w//2, height=h//2)
125 135 return pngdata, metadata
@@ -166,17 +176,17 b' def mpl_runner(safe_execfile):'
166 176 return mpl_execfile
167 177
168 178
169 def select_figure_formats(shell, formats, quality=90):
179 def select_figure_formats(shell, formats, **kwargs):
170 180 """Select figure formats for the inline backend.
171 181
172 182 Parameters
173 183 ==========
174 184 shell : InteractiveShell
175 185 The main IPython instance.
176 formats : list
186 formats : str or set
177 187 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
178 quality : int
179 A percentage for the quality of JPEG figures.
188 **kwargs : any
189 Extra keyword arguments to be passed to fig.canvas.print_figure.
180 190 """
181 191 from matplotlib.figure import Figure
182 192 from IPython.kernel.zmq.pylab import backend_inline
@@ -188,22 +198,28 b' def select_figure_formats(shell, formats, quality=90):'
188 198
189 199 if isinstance(formats, py3compat.string_types):
190 200 formats = {formats}
201 # cast in case of list / tuple
202 formats = set(formats)
191 203
192 [ f.type_printers.pop(Figure, None) for f in {svg_formatter, png_formatter, jpg_formatter} ]
193
194 for fmt in formats:
195 if fmt == 'png':
196 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
197 elif fmt in ('png2x', 'retina'):
198 png_formatter.for_type(Figure, retina_figure)
199 elif fmt in ('jpg', 'jpeg'):
200 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', quality))
201 elif fmt == 'svg':
202 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
203 elif fmt == 'pdf':
204 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf'))
205 else:
206 raise ValueError("supported formats are: 'png', 'retina', 'svg', 'jpg', 'pdf' not %r" % fmt)
204 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
205
206 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
207 bad = formats.difference(supported)
208 if bad:
209 bs = "%s" % ','.join([repr(f) for f in bad])
210 gs = "%s" % ','.join([repr(f) for f in supported])
211 raise ValueError("supported formats are: %s not %s" % (gs, bs))
212
213 if 'png' in formats:
214 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
215 if 'retina' in formats or 'png2x' in formats:
216 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
217 if 'jpg' in formats or 'jpeg' in formats:
218 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
219 if 'svg' in formats:
220 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
221 if 'pdf' in formats:
222 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
207 223
208 224 #-----------------------------------------------------------------------------
209 225 # Code for initializing matplotlib and importing pylab
@@ -354,5 +370,5 b' def configure_inline_support(shell, backend):'
354 370 del shell._saved_rcParams
355 371
356 372 # Setup the default figure format
357 select_figure_formats(shell, cfg.figure_formats, cfg.quality)
373 select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
358 374
@@ -10,8 +10,11 b' import os'
10 10 import nose.tools as nt
11 11
12 12 from IPython.core import display
13 from IPython.core.getipython import get_ipython
13 14 from IPython.utils import path as ipath
14 15
16 import IPython.testing.decorators as dec
17
15 18 def test_image_size():
16 19 """Simple test for display.Image(args, width=x,height=y)"""
17 20 thisurl = 'http://www.google.fr/images/srpr/logo3w.png'
@@ -58,3 +61,58 b' def test_image_filename_defaults():'
58 61 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
59 62 nt.assert_equal('jpeg', img.format)
60 63 nt.assert_is_none(img._repr_jpeg_())
64
65 def _get_inline_config():
66 from IPython.kernel.zmq.pylab.config import InlineBackend
67 return InlineBackend.instance()
68
69 @dec.skip_without('matplotlib')
70 def test_set_matplotlib_close():
71 cfg = _get_inline_config()
72 cfg.close_figures = False
73 display.set_matplotlib_close()
74 assert cfg.close_figures
75 display.set_matplotlib_close(False)
76 assert not cfg.close_figures
77
78 _fmt_mime_map = {
79 'png': 'image/png',
80 'jpeg': 'image/jpeg',
81 'pdf': 'application/pdf',
82 'retina': 'image/png',
83 'svg': 'image/svg+xml',
84 }
85
86 @dec.skip_without('matplotlib')
87 def test_set_matplotlib_formats():
88 from matplotlib.figure import Figure
89 formatters = get_ipython().display_formatter.formatters
90 for formats in [
91 ('png',),
92 ('pdf', 'svg'),
93 ('jpeg', 'retina', 'png'),
94 (),
95 ]:
96 active_mimes = {_fmt_mime_map[fmt] for fmt in formats}
97 display.set_matplotlib_formats(*formats)
98 for mime, f in formatters.items():
99 if mime in active_mimes:
100 nt.assert_in(Figure, f)
101 else:
102 nt.assert_not_in(Figure, f)
103
104 @dec.skip_without('matplotlib')
105 def test_set_matplotlib_formats_kwargs():
106 from matplotlib.figure import Figure
107 ip = get_ipython()
108 cfg = _get_inline_config()
109 cfg.print_figure_kwargs.update(dict(foo='bar'))
110 kwargs = dict(quality=10)
111 display.set_matplotlib_formats('png', **kwargs)
112 formatter = ip.display_formatter.formatters['image/png']
113 f = formatter.lookup_by_type(Figure)
114 cell = f.__closure__[0].cell_contents
115 expected = kwargs
116 expected.update(cfg.print_figure_kwargs)
117 nt.assert_equal(cell, expected)
118
@@ -13,17 +13,19 b''
13 13 #-----------------------------------------------------------------------------
14 14 from __future__ import print_function
15 15
16 # Stdlib imports
16 import matplotlib
17 matplotlib.use('Agg')
18 from matplotlib.figure import Figure
17 19
18 # Third-party imports
19 import matplotlib; matplotlib.use('Agg')
20 20 import nose.tools as nt
21 21
22 22 from matplotlib import pyplot as plt
23 23 import numpy as np
24 24
25 25 # Our own imports
26 from IPython.core.getipython import get_ipython
26 27 from IPython.core.interactiveshell import InteractiveShell
28 from IPython.core.display import _PNG, _JPEG
27 29 from .. import pylabtools as pt
28 30
29 31 from IPython.testing import decorators as dec
@@ -62,12 +64,81 b' def test_figure_to_jpg():'
62 64 ax = fig.add_subplot(1,1,1)
63 65 ax.plot([1,2,3])
64 66 plt.draw()
65 jpg = pt.print_figure(fig, 'jpg')[:100].lower()
66 assert jpg.startswith(b'\xff\xd8')
67 jpg = pt.print_figure(fig, 'jpg', quality=50)[:100].lower()
68 assert jpg.startswith(_JPEG)
67 69
70 def test_retina_figure():
71 fig = plt.figure()
72 ax = fig.add_subplot(1,1,1)
73 ax.plot([1,2,3])
74 plt.draw()
75 png, md = pt.retina_figure(fig)
76 assert png.startswith(_PNG)
77 nt.assert_in('width', md)
78 nt.assert_in('height', md)
79
80 _fmt_mime_map = {
81 'png': 'image/png',
82 'jpeg': 'image/jpeg',
83 'pdf': 'application/pdf',
84 'retina': 'image/png',
85 'svg': 'image/svg+xml',
86 }
87
88 def test_select_figure_formats_str():
89 ip = get_ipython()
90 for fmt, active_mime in _fmt_mime_map.items():
91 pt.select_figure_formats(ip, fmt)
92 for mime, f in ip.display_formatter.formatters.items():
93 if mime == active_mime:
94 nt.assert_in(Figure, f)
95 else:
96 nt.assert_not_in(Figure, f)
97
98 def test_select_figure_formats_kwargs():
99 ip = get_ipython()
100 kwargs = dict(quality=10, bbox_inches='tight')
101 pt.select_figure_formats(ip, 'png', **kwargs)
102 formatter = ip.display_formatter.formatters['image/png']
103 f = formatter.lookup_by_type(Figure)
104 cell = f.__closure__[0].cell_contents
105 nt.assert_equal(cell, kwargs)
106
107 # check that the formatter doesn't raise
108 fig = plt.figure()
109 ax = fig.add_subplot(1,1,1)
110 ax.plot([1,2,3])
111 plt.draw()
112 formatter.enabled = True
113 png = formatter(fig)
114 assert png.startswith(_PNG)
68 115
69 def test_import_pylab():
116 def test_select_figure_formats_set():
70 117 ip = get_ipython()
118 for fmts in [
119 {'png', 'svg'},
120 ['png'],
121 ('jpeg', 'pdf', 'retina'),
122 {'svg'},
123 ]:
124 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
125 pt.select_figure_formats(ip, fmts)
126 for mime, f in ip.display_formatter.formatters.items():
127 if mime in active_mimes:
128 nt.assert_in(Figure, f)
129 else:
130 nt.assert_not_in(Figure, f)
131
132 def test_select_figure_formats_bad():
133 ip = get_ipython()
134 with nt.assert_raises(ValueError):
135 pt.select_figure_formats(ip, 'foo')
136 with nt.assert_raises(ValueError):
137 pt.select_figure_formats(ip, {'png', 'foo'})
138 with nt.assert_raises(ValueError):
139 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
140
141 def test_import_pylab():
71 142 ns = {}
72 143 pt.import_pylab(ns, import_all=False)
73 144 nt.assert_true('plt' in ns)
@@ -69,15 +69,16 b' class InlineBackend(InlineBackendConfig):'
69 69 help="""A set of figure formats to enable: 'png',
70 70 'retina', 'jpeg', 'svg', 'pdf'.""")
71 71
72 def _update_figure_formatters(self):
73 if self.shell is not None:
74 select_figure_formats(self.shell, self.figure_formats, **self.print_figure_kwargs)
75
72 76 def _figure_formats_changed(self, name, old, new):
73 77 from IPython.core.pylabtools import select_figure_formats
74 78 if 'jpg' in new or 'jpeg' in new:
75 79 if not pil_available():
76 80 raise TraitError("Requires PIL/Pillow for JPG figures")
77 if self.shell is None:
78 return
79 else:
80 select_figure_formats(self.shell, new)
81 self._update_figure_formatters()
81 82
82 83 figure_format = Unicode(config=True, help="""The figure format to enable (deprecated
83 84 use `figure_formats` instead)""")
@@ -86,12 +87,13 b' class InlineBackend(InlineBackendConfig):'
86 87 if new:
87 88 self.figure_formats = {new}
88 89
89 quality = Int(default_value=90, config=True,
90 help="Quality of compression [10-100], currently for lossy JPEG only.")
91
92 def _quality_changed(self, name, old, new):
93 if new < 10 or new > 100:
94 raise TraitError("figure JPEG quality must be in [10-100] range.")
90 print_figure_kwargs = Dict({'bbox_inches' : 'tight'}, config=True,
91 help="""Extra kwargs to be passed to fig.canvas.print_figure.
92
93 Logical examples include: bbox_inches, quality (for jpeg figures), etc.
94 """
95 )
96 _print_figure_kwargs_changed = _update_figure_formatters
95 97
96 98 close_figures = Bool(True, config=True,
97 99 help="""Close all figures at the end of each cell.
@@ -109,7 +111,7 b' class InlineBackend(InlineBackendConfig):'
109 111 other matplotlib backends, but figure barriers between cells must
110 112 be explicit.
111 113 """)
112
114
113 115 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
114 116
115 117
@@ -231,8 +231,6 b' Other changes'
231 231 1.12.1.
232 232
233 233 * The InlineBackend.figure_format flag now supports JPEG output if PIL/Pillow is available.
234 * The new ``InlineBackend.quality`` flag is a Integer in the range [10, 100] which controls
235 the quality of figures where higher values give nicer images (currently JPEG only).
236 234
237 235 * Input transformers (see :doc:`/config/inputtransforms`) may now raise
238 236 :exc:`SyntaxError` if they determine that input is invalid. The input
General Comments 0
You need to be logged in to leave comments. Login now