##// 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 To set this in your config files use the following::
730 To set this in your config files use the following::
731
731
732 c.InlineBackend.figure_formats = {'pdf', 'png', 'svg'}
732 c.InlineBackend.figure_formats = {'png', 'jpeg'}
733 c.InlineBackend.quality = 90
733 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
734
734
735 Parameters
735 Parameters
736 ----------
736 ----------
737 *formats : list, tuple
737 *formats : strs
738 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
738 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
739 quality : int
739 **kwargs :
740 A percentage for the quality of JPEG figures. Defaults to 90.
740 Keyword args will be relayed to ``figure.canvas.print_figure``.
741 """
741 """
742 from IPython.core.interactiveshell import InteractiveShell
742 from IPython.core.interactiveshell import InteractiveShell
743 from IPython.core.pylabtools import select_figure_formats
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 shell = InteractiveShell.instance()
750 shell = InteractiveShell.instance()
745 select_figure_formats(shell, formats, quality=90)
751 select_figure_formats(shell, formats, **kw)
746
752
747 @skip_doctest
753 @skip_doctest
748 def set_matplotlib_close(close):
754 def set_matplotlib_close(close=True):
749 """Set whether the inline backend closes all figures automatically or not.
755 """Set whether the inline backend closes all figures automatically or not.
750
756
751 By default, the inline backend used in the IPython Notebook will close all
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 Should all matplotlib figures be automatically closed after each cell is
772 Should all matplotlib figures be automatically closed after each cell is
767 run?
773 run?
768 """
774 """
769 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
775 from IPython.kernel.zmq.pylab.config import InlineBackend
770 ilbe = InlineBackend.instance()
776 cfg = InlineBackend.instance()
771 ilbe.close_figures = close
777 cfg.close_figures = close
772
778
@@ -95,9 +95,11 b' def figsize(sizex, sizey):'
95 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
95 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
96
96
97
97
98 def print_figure(fig, fmt='png', quality=90):
98 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
99 """Convert a figure to svg, png or jpg for inline display.
99 """Print a figure to an image, and return the resulting bytes
100 Quality is only relevant for jpg.
100
101 Any keyword args are passed to fig.canvas.print_figure,
102 such as ``quality`` or ``bbox_inches``.
101 """
103 """
102 from matplotlib import rcParams
104 from matplotlib import rcParams
103 # When there's an empty figure, we shouldn't return anything, otherwise we
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 if not fig.axes and not fig.lines:
107 if not fig.axes and not fig.lines:
106 return
108 return
107
109
108 fc = fig.get_facecolor()
109 ec = fig.get_edgecolor()
110 bytes_io = BytesIO()
111 dpi = rcParams['savefig.dpi']
110 dpi = rcParams['savefig.dpi']
112 if fmt == 'retina':
111 if fmt == 'retina':
113 dpi = dpi * 2
112 dpi = dpi * 2
114 fmt = 'png'
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 """format a figure as a pixel-doubled (retina) PNG"""
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 w, h = _pngxy(pngdata)
133 w, h = _pngxy(pngdata)
124 metadata = dict(width=w//2, height=h//2)
134 metadata = dict(width=w//2, height=h//2)
125 return pngdata, metadata
135 return pngdata, metadata
@@ -166,17 +176,17 b' def mpl_runner(safe_execfile):'
166 return mpl_execfile
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 """Select figure formats for the inline backend.
180 """Select figure formats for the inline backend.
171
181
172 Parameters
182 Parameters
173 ==========
183 ==========
174 shell : InteractiveShell
184 shell : InteractiveShell
175 The main IPython instance.
185 The main IPython instance.
176 formats : list
186 formats : str or set
177 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
187 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
178 quality : int
188 **kwargs : any
179 A percentage for the quality of JPEG figures.
189 Extra keyword arguments to be passed to fig.canvas.print_figure.
180 """
190 """
181 from matplotlib.figure import Figure
191 from matplotlib.figure import Figure
182 from IPython.kernel.zmq.pylab import backend_inline
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 if isinstance(formats, py3compat.string_types):
199 if isinstance(formats, py3compat.string_types):
190 formats = {formats}
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} ]
204 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
193
205
194 for fmt in formats:
206 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
195 if fmt == 'png':
207 bad = formats.difference(supported)
196 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
208 if bad:
197 elif fmt in ('png2x', 'retina'):
209 bs = "%s" % ','.join([repr(f) for f in bad])
198 png_formatter.for_type(Figure, retina_figure)
210 gs = "%s" % ','.join([repr(f) for f in supported])
199 elif fmt in ('jpg', 'jpeg'):
211 raise ValueError("supported formats are: %s not %s" % (gs, bs))
200 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', quality))
212
201 elif fmt == 'svg':
213 if 'png' in formats:
202 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
214 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
203 elif fmt == 'pdf':
215 if 'retina' in formats or 'png2x' in formats:
204 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf'))
216 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
205 else:
217 if 'jpg' in formats or 'jpeg' in formats:
206 raise ValueError("supported formats are: 'png', 'retina', 'svg', 'jpg', 'pdf' not %r" % fmt)
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 # Code for initializing matplotlib and importing pylab
225 # Code for initializing matplotlib and importing pylab
@@ -354,5 +370,5 b' def configure_inline_support(shell, backend):'
354 del shell._saved_rcParams
370 del shell._saved_rcParams
355
371
356 # Setup the default figure format
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 import nose.tools as nt
10 import nose.tools as nt
11
11
12 from IPython.core import display
12 from IPython.core import display
13 from IPython.core.getipython import get_ipython
13 from IPython.utils import path as ipath
14 from IPython.utils import path as ipath
14
15
16 import IPython.testing.decorators as dec
17
15 def test_image_size():
18 def test_image_size():
16 """Simple test for display.Image(args, width=x,height=y)"""
19 """Simple test for display.Image(args, width=x,height=y)"""
17 thisurl = 'http://www.google.fr/images/srpr/logo3w.png'
20 thisurl = 'http://www.google.fr/images/srpr/logo3w.png'
@@ -58,3 +61,58 b' def test_image_filename_defaults():'
58 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
61 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
59 nt.assert_equal('jpeg', img.format)
62 nt.assert_equal('jpeg', img.format)
60 nt.assert_is_none(img._repr_jpeg_())
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 from __future__ import print_function
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 import nose.tools as nt
20 import nose.tools as nt
21
21
22 from matplotlib import pyplot as plt
22 from matplotlib import pyplot as plt
23 import numpy as np
23 import numpy as np
24
24
25 # Our own imports
25 # Our own imports
26 from IPython.core.getipython import get_ipython
26 from IPython.core.interactiveshell import InteractiveShell
27 from IPython.core.interactiveshell import InteractiveShell
28 from IPython.core.display import _PNG, _JPEG
27 from .. import pylabtools as pt
29 from .. import pylabtools as pt
28
30
29 from IPython.testing import decorators as dec
31 from IPython.testing import decorators as dec
@@ -62,12 +64,81 b' def test_figure_to_jpg():'
62 ax = fig.add_subplot(1,1,1)
64 ax = fig.add_subplot(1,1,1)
63 ax.plot([1,2,3])
65 ax.plot([1,2,3])
64 plt.draw()
66 plt.draw()
65 jpg = pt.print_figure(fig, 'jpg')[:100].lower()
67 jpg = pt.print_figure(fig, 'jpg', quality=50)[:100].lower()
66 assert jpg.startswith(b'\xff\xd8')
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 ip = get_ipython()
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 ns = {}
142 ns = {}
72 pt.import_pylab(ns, import_all=False)
143 pt.import_pylab(ns, import_all=False)
73 nt.assert_true('plt' in ns)
144 nt.assert_true('plt' in ns)
@@ -69,15 +69,16 b' class InlineBackend(InlineBackendConfig):'
69 help="""A set of figure formats to enable: 'png',
69 help="""A set of figure formats to enable: 'png',
70 'retina', 'jpeg', 'svg', 'pdf'.""")
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 def _figure_formats_changed(self, name, old, new):
76 def _figure_formats_changed(self, name, old, new):
73 from IPython.core.pylabtools import select_figure_formats
77 from IPython.core.pylabtools import select_figure_formats
74 if 'jpg' in new or 'jpeg' in new:
78 if 'jpg' in new or 'jpeg' in new:
75 if not pil_available():
79 if not pil_available():
76 raise TraitError("Requires PIL/Pillow for JPG figures")
80 raise TraitError("Requires PIL/Pillow for JPG figures")
77 if self.shell is None:
81 self._update_figure_formatters()
78 return
79 else:
80 select_figure_formats(self.shell, new)
81
82
82 figure_format = Unicode(config=True, help="""The figure format to enable (deprecated
83 figure_format = Unicode(config=True, help="""The figure format to enable (deprecated
83 use `figure_formats` instead)""")
84 use `figure_formats` instead)""")
@@ -86,12 +87,13 b' class InlineBackend(InlineBackendConfig):'
86 if new:
87 if new:
87 self.figure_formats = {new}
88 self.figure_formats = {new}
88
89
89 quality = Int(default_value=90, config=True,
90 print_figure_kwargs = Dict({'bbox_inches' : 'tight'}, config=True,
90 help="Quality of compression [10-100], currently for lossy JPEG only.")
91 help="""Extra kwargs to be passed to fig.canvas.print_figure.
91
92
92 def _quality_changed(self, name, old, new):
93 Logical examples include: bbox_inches, quality (for jpeg figures), etc.
93 if new < 10 or new > 100:
94 """
94 raise TraitError("figure JPEG quality must be in [10-100] range.")
95 )
96 _print_figure_kwargs_changed = _update_figure_formatters
95
97
96 close_figures = Bool(True, config=True,
98 close_figures = Bool(True, config=True,
97 help="""Close all figures at the end of each cell.
99 help="""Close all figures at the end of each cell.
@@ -109,7 +111,7 b' class InlineBackend(InlineBackendConfig):'
109 other matplotlib backends, but figure barriers between cells must
111 other matplotlib backends, but figure barriers between cells must
110 be explicit.
112 be explicit.
111 """)
113 """)
112
114
113 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
115 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
114
116
115
117
@@ -231,8 +231,6 b' Other changes'
231 1.12.1.
231 1.12.1.
232
232
233 * The InlineBackend.figure_format flag now supports JPEG output if PIL/Pillow is available.
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 * Input transformers (see :doc:`/config/inputtransforms`) may now raise
235 * Input transformers (see :doc:`/config/inputtransforms`) may now raise
238 :exc:`SyntaxError` if they determine that input is invalid. The input
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