Show More
@@ -0,0 +1,3 b'' | |||||
|
1 | * The InlineBackend.figure_format flag now supports JPEG output if PIL/Pillow is available. | |||
|
2 | * The new ``InlineBackend.quality`` flag is a Integer in the range [10, 100] which controls | |||
|
3 | the quality of figures where higher values give nicer images (currently JPEG only). |
@@ -94,8 +94,10 b' def figsize(sizex, sizey):' | |||||
94 | matplotlib.rcParams['figure.figsize'] = [sizex, sizey] |
|
94 | matplotlib.rcParams['figure.figsize'] = [sizex, sizey] | |
95 |
|
95 | |||
96 |
|
96 | |||
97 | def print_figure(fig, fmt='png'): |
|
97 | def print_figure(fig, fmt='png', quality=90): | |
98 |
"""Convert a figure to svg or p |
|
98 | """Convert a figure to svg, png or jpg for inline display. | |
|
99 | Quality is only relevant for jpg. | |||
|
100 | """ | |||
99 | from matplotlib import rcParams |
|
101 | from matplotlib import rcParams | |
100 | # When there's an empty figure, we shouldn't return anything, otherwise we |
|
102 | # When there's an empty figure, we shouldn't return anything, otherwise we | |
101 | # get big blank areas in the qt console. |
|
103 | # get big blank areas in the qt console. | |
@@ -110,7 +112,7 b" def print_figure(fig, fmt='png'):" | |||||
110 | dpi = dpi * 2 |
|
112 | dpi = dpi * 2 | |
111 | fmt = 'png' |
|
113 | fmt = 'png' | |
112 | fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight', |
|
114 | fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight', | |
113 | facecolor=fc, edgecolor=ec, dpi=dpi) |
|
115 | facecolor=fc, edgecolor=ec, dpi=dpi, quality=quality) | |
114 | data = bytes_io.getvalue() |
|
116 | data = bytes_io.getvalue() | |
115 | return data |
|
117 | return data | |
116 |
|
118 | |||
@@ -163,8 +165,8 b' def mpl_runner(safe_execfile):' | |||||
163 | return mpl_execfile |
|
165 | return mpl_execfile | |
164 |
|
166 | |||
165 |
|
167 | |||
166 | def select_figure_format(shell, fmt): |
|
168 | def select_figure_format(shell, fmt, quality=90): | |
167 | """Select figure format for inline backend, can be 'png', 'retina', or 'svg'. |
|
169 | """Select figure format for inline backend, can be 'png', 'retina', 'jpg', or 'svg'. | |
168 |
|
170 | |||
169 | Using this method ensures only one figure format is active at a time. |
|
171 | Using this method ensures only one figure format is active at a time. | |
170 | """ |
|
172 | """ | |
@@ -173,18 +175,20 b' def select_figure_format(shell, fmt):' | |||||
173 |
|
175 | |||
174 | svg_formatter = shell.display_formatter.formatters['image/svg+xml'] |
|
176 | svg_formatter = shell.display_formatter.formatters['image/svg+xml'] | |
175 | png_formatter = shell.display_formatter.formatters['image/png'] |
|
177 | png_formatter = shell.display_formatter.formatters['image/png'] | |
|
178 | jpg_formatter = shell.display_formatter.formatters['image/jpeg'] | |||
|
179 | ||||
|
180 | [ f.type_printers.pop(Figure, None) for f in {svg_formatter, png_formatter, jpg_formatter} ] | |||
176 |
|
181 | |||
177 | if fmt == 'png': |
|
182 | if fmt == 'png': | |
178 | svg_formatter.pop(Figure, None) |
|
|||
179 | png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png')) |
|
183 | png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png')) | |
180 | elif fmt in ('png2x', 'retina'): |
|
184 | elif fmt in ('png2x', 'retina'): | |
181 | svg_formatter.pop(Figure, None) |
|
|||
182 | png_formatter.for_type(Figure, retina_figure) |
|
185 | png_formatter.for_type(Figure, retina_figure) | |
|
186 | elif fmt in ('jpg', 'jpeg'): | |||
|
187 | jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', quality)) | |||
183 | elif fmt == 'svg': |
|
188 | elif fmt == 'svg': | |
184 | png_formatter.pop(Figure, None) |
|
|||
185 | svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg')) |
|
189 | svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg')) | |
186 | else: |
|
190 | else: | |
187 | raise ValueError("supported formats are: 'png', 'retina', 'svg', not %r" % fmt) |
|
191 | raise ValueError("supported formats are: 'png', 'retina', 'svg', 'jpg', not %r" % fmt) | |
188 |
|
192 | |||
189 | # set the format to be used in the backend() |
|
193 | # set the format to be used in the backend() | |
190 | backend_inline._figure_format = fmt |
|
194 | backend_inline._figure_format = fmt | |
@@ -338,5 +342,5 b' def configure_inline_support(shell, backend):' | |||||
338 | del shell._saved_rcParams |
|
342 | del shell._saved_rcParams | |
339 |
|
343 | |||
340 | # Setup the default figure format |
|
344 | # Setup the default figure format | |
341 | select_figure_format(shell, cfg.figure_format) |
|
345 | select_figure_format(shell, cfg.figure_format, cfg.quality) | |
342 |
|
346 |
@@ -26,6 +26,8 b' import numpy as np' | |||||
26 | from IPython.core.interactiveshell import InteractiveShell |
|
26 | from IPython.core.interactiveshell import InteractiveShell | |
27 | from .. import pylabtools as pt |
|
27 | from .. import pylabtools as pt | |
28 |
|
28 | |||
|
29 | from IPython.testing import decorators as dec | |||
|
30 | ||||
29 | #----------------------------------------------------------------------------- |
|
31 | #----------------------------------------------------------------------------- | |
30 | # Globals and constants |
|
32 | # Globals and constants | |
31 | #----------------------------------------------------------------------------- |
|
33 | #----------------------------------------------------------------------------- | |
@@ -53,6 +55,16 b' def test_figure_to_svg():' | |||||
53 | svg = pt.print_figure(fig, 'svg')[:100].lower() |
|
55 | svg = pt.print_figure(fig, 'svg')[:100].lower() | |
54 | nt.assert_in(b'doctype svg', svg) |
|
56 | nt.assert_in(b'doctype svg', svg) | |
55 |
|
57 | |||
|
58 | @dec.skip_without("PIL.Image") | |||
|
59 | def test_figure_to_jpg(): | |||
|
60 | # simple check for at least jpg-looking output | |||
|
61 | fig = plt.figure() | |||
|
62 | ax = fig.add_subplot(1,1,1) | |||
|
63 | ax.plot([1,2,3]) | |||
|
64 | plt.draw() | |||
|
65 | jpg = pt.print_figure(fig, 'jpg')[:100].lower() | |||
|
66 | assert jpg.startswith(b'\xff\xd8') | |||
|
67 | ||||
56 |
|
68 | |||
57 | def test_import_pylab(): |
|
69 | def test_import_pylab(): | |
58 | ip = get_ipython() |
|
70 | ip = get_ipython() |
@@ -14,13 +14,23 b' This module does not import anything from matplotlib.' | |||||
14 | #----------------------------------------------------------------------------- |
|
14 | #----------------------------------------------------------------------------- | |
15 |
|
15 | |||
16 | from IPython.config.configurable import SingletonConfigurable |
|
16 | from IPython.config.configurable import SingletonConfigurable | |
17 | from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum, Bool |
|
17 | from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum, Bool, Int, TraitError | |
18 | from IPython.utils.warn import warn |
|
18 | from IPython.utils.warn import warn | |
19 |
|
19 | |||
20 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
21 | # Configurable for inline backend options |
|
21 | # Configurable for inline backend options | |
22 | #----------------------------------------------------------------------------- |
|
22 | #----------------------------------------------------------------------------- | |
23 |
|
23 | |||
|
24 | def pil_available(): | |||
|
25 | """Test if PIL/Pillow is available""" | |||
|
26 | out = False | |||
|
27 | try: | |||
|
28 | from PIL import Image | |||
|
29 | out = True | |||
|
30 | except: | |||
|
31 | pass | |||
|
32 | return out | |||
|
33 | ||||
24 | # inherit from InlineBackendConfig for deprecation purposes |
|
34 | # inherit from InlineBackendConfig for deprecation purposes | |
25 | class InlineBackendConfig(SingletonConfigurable): |
|
35 | class InlineBackendConfig(SingletonConfigurable): | |
26 | pass |
|
36 | pass | |
@@ -53,15 +63,28 b' class InlineBackend(InlineBackendConfig):' | |||||
53 | inline backend.""" |
|
63 | inline backend.""" | |
54 | ) |
|
64 | ) | |
55 |
|
65 | |||
56 | figure_format = CaselessStrEnum(['svg', 'png', 'retina'], default_value='png', config=True, |
|
66 | ||
57 | help="The image format for figures with the inline backend.") |
|
67 | figure_format = CaselessStrEnum(['svg', 'png', 'retina', 'jpg'], | |
|
68 | default_value='png', config=True, | |||
|
69 | help="""The image format for figures with the inline | |||
|
70 | backend. JPEG requires the PIL/Pillow library.""") | |||
58 |
|
71 | |||
59 | def _figure_format_changed(self, name, old, new): |
|
72 | def _figure_format_changed(self, name, old, new): | |
60 | from IPython.core.pylabtools import select_figure_format |
|
73 | from IPython.core.pylabtools import select_figure_format | |
|
74 | if new in {"jpg", "jpeg"}: | |||
|
75 | if not pil_available(): | |||
|
76 | raise TraitError("Requires PIL/Pillow for JPG figures") | |||
61 | if self.shell is None: |
|
77 | if self.shell is None: | |
62 | return |
|
78 | return | |
63 | else: |
|
79 | else: | |
64 | select_figure_format(self.shell, new) |
|
80 | select_figure_format(self.shell, new) | |
|
81 | ||||
|
82 | quality = Int(default_value=90, config=True, | |||
|
83 | help="Quality of compression [10-100], currently for lossy JPEG only.") | |||
|
84 | ||||
|
85 | def _quality_changed(self, name, old, new): | |||
|
86 | if new < 10 or new > 100: | |||
|
87 | raise TraitError("figure JPEG quality must be in [10-100] range.") | |||
65 |
|
88 | |||
66 | close_figures = Bool(True, config=True, |
|
89 | close_figures = Bool(True, config=True, | |
67 | help="""Close all figures at the end of each cell. |
|
90 | help="""Close all figures at the end of each cell. |
General Comments 0
You need to be logged in to leave comments.
Login now