Support inline PNGs of matplotlib plots #381

Closed
wants to merge 2 commits into
from
View
12 IPython/core/usage.py
@@ -471,11 +471,13 @@
Inline matplotlib graphics
==========================
-The IPython console is capable of displaying matplotlib figures inline, in SVG
-format. If started with the ``--pylab inline`` flag, then all figures are
-rendered inline automatically. If started with ``--pylab`` or ``--pylab <your
-backend>``, then a GUI backend will be used, but IPython's ``display()`` and
-``getfigs()`` functions can be used to view plots inline::
+The IPython console is capable of displaying matplotlib figures
+inline, in SVG or PNG format. If started with the ``--pylab
+inline_svg`` or ``--pylab inline_png`` flags, then all figures are
+rendered inline automatically. If started with ``--pylab`` or
+``--pylab <your backend>``, then a GUI backend will be used, but
+IPython's ``display()`` and ``getfigs()`` functions can be used to
+view plots inline::
In [9]: display(*getfigs()) # display all figures inline
View
4 IPython/frontend/qt/console/ipythonqt.py
@@ -156,11 +156,11 @@ def main():
egroup = kgroup.add_mutually_exclusive_group()
egroup.add_argument('--pure', action='store_true', help = \
'use a pure Python kernel instead of an IPython kernel')
- egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
+ egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
const='auto', help = \
"Pre-load matplotlib and numpy for interactive use. If GUI is not \
given, the GUI backend is matplotlib's, otherwise use one of: \
- ['tk', 'gtk', 'qt', 'wx', 'inline'].")
+ ['tk', 'gtk', 'qt', 'wx', 'inline_svg', 'inline_png'].")
wgroup = parser.add_argument_group('widget options')
wgroup.add_argument('--paging', type=str, default='inside',
View
68 IPython/lib/pylabtools.py
@@ -31,7 +31,9 @@
'qt': 'Qt4Agg', # qt3 not supported
'qt4': 'Qt4Agg',
'osx': 'MacOSX',
- 'inline' : 'module://IPython.zmq.pylab.backend_inline'}
+ 'inline' : 'module://IPython.zmq.pylab.backend_inline_svg',
+ 'inline_svg' : 'module://IPython.zmq.pylab.backend_inline_svg',
+ 'inline_png' : 'module://IPython.zmq.pylab.backend_inline_png' }
#-----------------------------------------------------------------------------
# Matplotlib utilities
@@ -75,8 +77,8 @@ def figsize(sizex, sizey):
matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
-def figure_to_svg(fig):
- """Convert a figure to svg for inline display."""
+def print_figure(fig, format):
+ """Convert a figure to a serialized format for inline display."""
# When there's an empty figure, we shouldn't return anything, otherwise we
# get big blank areas in the qt console.
if not fig.axes:
@@ -88,12 +90,14 @@ def figure_to_svg(fig):
fig.set_edgecolor('white')
try:
string_io = StringIO()
- fig.canvas.print_figure(string_io, format='svg')
- svg = string_io.getvalue()
+ fig.canvas.print_figure(string_io, format=format, dpi=72)
+ data = string_io.getvalue()
finally:
fig.set_facecolor(fc)
fig.set_edgecolor(ec)
- return svg
+ if format == 'png':
+ data = data.encode('base64')
+ return data
# We need a little factory function here to create the closure where
@@ -149,7 +153,7 @@ def find_gui_and_backend(gui=None):
Parameters
----------
gui : str
- Can be one of ('tk','gtk','wx','qt','qt4','inline').
+ Can be one of ('tk','gtk','wx','qt','qt4','inline','inline_svg','inline_png').
Returns
-------
@@ -215,19 +219,22 @@ def import_pylab(user_ns, backend, import_all=True, shell=None):
if shell is not None:
exec s in shell.user_ns_hidden
- # If using our svg payload backend, register the post-execution
+ # If using our inline payload backend, register the post-execution
# function that will pick up the results for display. This can only be
# done with access to the real shell object.
- if backend == backends['inline']:
- from IPython.zmq.pylab.backend_inline import flush_svg
+ if backend in (backends['inline_svg'], backends['inline_png']):
from matplotlib import pyplot
- shell.register_post_execute(flush_svg)
+ def flush_image():
+ if pyplot.show._draw_called:
+ pyplot.show()
+ pyplot.show._draw_called = False
+ shell.register_post_execute(flush_image)
# The typical default figure size is too large for inline use,
# so we shrink the figure size to 6x4, and tweak fonts to
# make that fit. This is configurable via Global.pylab_inline_rc,
# or rather it will be once the zmq kernel is hooked up to
# the config system.
-
+
default_rc = {
'figure.figsize': (6.0,4.0),
# 12pt labels get cutoff on 6x4 logplots, so use 10pt.
@@ -238,23 +245,34 @@ def import_pylab(user_ns, backend, import_all=True, shell=None):
rc = getattr(shell.config.Global, 'pylab_inline_rc', default_rc)
pyplot.rcParams.update(rc)
shell.config.Global.pylab_inline_rc = rc
-
+
# Add 'figsize' to pyplot and to the user's namespace
user_ns['figsize'] = pyplot.figsize = figsize
shell.user_ns_hidden['figsize'] = figsize
-
- # The old pastefig function has been replaced by display
- # Always add this svg formatter so display works.
- from IPython.core.display import display, display_svg
- svg_formatter = shell.display_formatter.formatters['image/svg+xml']
- svg_formatter.for_type_by_name(
- 'matplotlib.figure','Figure',figure_to_svg
- )
- # Add display and display_png to the user's namespace
+
+ if backend == backends['inline_png']:
+ # The old pastefig function has been replaced by display
+ # Always add this png formatter so display works.
+ from IPython.core.display import display, display_png
+ png_formatter = shell.display_formatter.formatters['image/png']
+ png_formatter.for_type_by_name(
+ 'matplotlib.figure','Figure',lambda fig: print_figure(fig, 'png')
+ )
+ user_ns['display_png'] = display_png
+ shell.user_ns_hidden['display_png'] = display_png
+ else:
+ # The old pastefig function has been replaced by display
+ # Always add this svg formatter so display works.
+ from IPython.core.display import display, display_svg
+ svg_formatter = shell.display_formatter.formatters['image/svg+xml']
+ svg_formatter.for_type_by_name(
+ 'matplotlib.figure','Figure',lambda fig: print_figure(fig, 'svg')
+ )
+ user_ns['display_svg'] = display_svg
+ shell.user_ns_hidden['display_svg'] = display_svg
+ # Add display to the user's namespace
user_ns['display'] = display
shell.user_ns_hidden['display'] = display
- user_ns['display_svg'] = display_svg
- shell.user_ns_hidden['display_svg'] = display_svg
user_ns['getfigs'] = getfigs
shell.user_ns_hidden['getfigs'] = getfigs
@@ -294,6 +312,6 @@ def pylab_activate(user_ns, gui=None, import_all=True):
print """
Welcome to pylab, a matplotlib-based Python environment [backend: %s].
For more information, type 'help(pylab)'.""" % backend
-
+
return gui
View
6 IPython/zmq/ipkernel.py
@@ -612,11 +612,11 @@ def main():
""" The IPython kernel main entry point.
"""
parser = make_argument_parser()
- parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
+ parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
const='auto', help = \
"Pre-load matplotlib and numpy for interactive use. If GUI is not \
given, the GUI backend is matplotlib's, otherwise use one of: \
-['tk', 'gtk', 'qt', 'wx', 'inline'].")
+['tk', 'gtk', 'qt', 'wx', 'inline_svg', 'inline_png'].")
parser.add_argument('--colors',
type=str, dest='colors',
help="Set the color scheme (NoColor, Linux, and LightBG).",
@@ -629,6 +629,8 @@ def main():
'qt' : QtKernel,
'qt4': QtKernel,
'inline': Kernel,
+ 'inline_svg': Kernel,
+ 'inline_png': Kernel,
'wx' : WxKernel,
'tk' : TkKernel,
'gtk': GTKKernel,
View
70 IPython/zmq/pylab/backend_inline_png.py
@@ -0,0 +1,70 @@
+"""Produce Agg-rendered PNG versions of active plots for display by the rich Qt frontend.
+"""
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+from __future__ import print_function
+
+# Standard library imports
+import sys
+
+# Third-party imports
+import matplotlib
+from matplotlib.backends.backend_agg import new_figure_manager
+from matplotlib._pylab_helpers import Gcf
+
+# Local imports.
+from IPython.core.displaypub import publish_display_data
+from IPython.lib.pylabtools import print_figure
+
+#-----------------------------------------------------------------------------
+# Functions
+#-----------------------------------------------------------------------------
+
+def show(close=True):
+ """Show all figures as PNG payloads sent to the IPython clients.
+
+ Parameters
+ ----------
+ close : bool, optional
+ If true, a ``plt.close('all')`` call is automatically issued after
+ sending all the PNG figures. If this is set, the figures will entirely
+ removed from the internal list of figures.
+ """
+ for figure_manager in Gcf.get_all_fig_managers():
+ send_png_figure(figure_manager.canvas.figure)
+ if close:
+ matplotlib.pyplot.close('all')
+
+
+# This flag will be reset by draw_if_interactive when called
+show._draw_called = False
+
+
+def draw_if_interactive():
+ """
+ Is called after every pylab drawing command
+ """
+ # We simply flag we were called and otherwise do nothing. At the end of
+ # the code execution, a separate call to show_close() will act upon this.
+ show._draw_called = True
+
+
+def send_png_figure(fig):
+ """Draw the current figure and send it as a PNG payload.
+ """
+ # For an empty figure, don't even bother calling print_figure, to avoid
+ # big blank spaces in the qt console
+ if not fig.axes:
+ return
+
+ png = print_figure(fig, 'png')
+ # flush text streams before sending figures, helps a little with output
+ # synchronization in the console (though it's a bandaid, not a real sln)
+ sys.stdout.flush(); sys.stderr.flush()
+ publish_display_data(
+ 'IPython.zmq.pylab.backend_inline.send_png_figure',
+ 'Matplotlib Plot',
+ {'image/png' : png}
+ )
+
View
15 IPython/zmq/pylab/backend_inline.py → IPython/zmq/pylab/backend_inline_svg.py
@@ -15,7 +15,7 @@
# Local imports.
from IPython.core.displaypub import publish_display_data
-from IPython.lib.pylabtools import figure_to_svg
+from IPython.lib.pylabtools import print_figure
#-----------------------------------------------------------------------------
# Functions
@@ -50,17 +50,6 @@ def draw_if_interactive():
show._draw_called = True
-def flush_svg():
- """Call show, close all open figures, sending all SVG images.
-
- This is meant to be called automatically and will call show() if, during
- prior code execution, there had been any calls to draw_if_interactive.
- """
- if show._draw_called:
- show()
- show._draw_called = False
-
-
def send_svg_figure(fig):
"""Draw the current figure and send it as an SVG payload.
"""
@@ -69,7 +58,7 @@ def send_svg_figure(fig):
if not fig.axes:
return
- svg = figure_to_svg(fig)
+ svg = print_figure(fig, 'svg')
# flush text streams before sending figures, helps a little with output
# synchronization in the console (though it's a bandaid, not a real sln)
sys.stdout.flush(); sys.stderr.flush()