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 = {'p |
|
|
733 |
c.InlineBackend.quality |
|
|
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 : |
|
|
738 |
One or |
|
|
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, |
|
|
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. |
|
|
770 |
|
|
|
771 |
|
|
|
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', |
|
|
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, |
|
|
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 : |
|
|
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 |
|
|
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 |
|
|
|
203 | elif fmt == 'pdf': | |
|
204 |
|
|
|
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. |
|
|
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( |
|
|
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