pylabtools.py
382 lines
| 12.9 KiB
| text/x-python
|
PythonLexer
Fernando Perez
|
r2363 | # -*- coding: utf-8 -*- | ||
"""Pylab (matplotlib) support utilities. | ||||
Authors | ||||
------- | ||||
Brian Granger
|
r2918 | |||
* Fernando Perez. | ||||
* Brian Granger | ||||
Fernando Perez
|
r2363 | """ | ||
Thomas Kluyver
|
r13348 | from __future__ import print_function | ||
Fernando Perez
|
r2363 | |||
#----------------------------------------------------------------------------- | ||||
MinRK
|
r10803 | # Copyright (C) 2009 The IPython Development Team | ||
Fernando Perez
|
r2363 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r2498 | |||
Fernando Perez
|
r2363 | #----------------------------------------------------------------------------- | ||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r2498 | |||
MinRK
|
r5361 | import sys | ||
Grahame Bowland
|
r4773 | from io import BytesIO | ||
Brian Granger
|
r3280 | |||
MinRK
|
r10803 | from IPython.core.display import _pngxy | ||
Brian Granger
|
r2498 | from IPython.utils.decorators import flag_calls | ||
Brian E. Granger
|
r15122 | from IPython.utils import py3compat | ||
Fernando Perez
|
r2363 | |||
Fernando Perez
|
r2987 | # If user specifies a GUI, that dictates the backend, otherwise we read the | ||
# user's mpl default from the mpl rc structure | ||||
backends = {'tk': 'TkAgg', | ||||
'gtk': 'GTKAgg', | ||||
Michael Droettboom
|
r13754 | 'gtk3': 'GTK3Agg', | ||
Fernando Perez
|
r2987 | 'wx': 'WXAgg', | ||
'qt': 'Qt4Agg', # qt3 not supported | ||||
'qt4': 'Qt4Agg', | ||||
MinRK
|
r3462 | 'osx': 'MacOSX', | ||
MinRK
|
r9372 | 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'} | ||
Fernando Perez
|
r2987 | |||
Fernando Perez
|
r3902 | # We also need a reverse backends2guis mapping that will properly choose which | ||
# GUI support to activate based on the desired matplotlib backend. For the | ||||
# most part it's just a reverse of the above dict, but we also need to add a | ||||
# few others that map to the same GUI manually: | ||||
backend2gui = dict(zip(backends.values(), backends.keys())) | ||||
Paul Ivanov
|
r11975 | # Our tests expect backend2gui to just return 'qt' | ||
backend2gui['Qt4Agg'] = 'qt' | ||||
Fernando Perez
|
r3902 | # In the reverse mapping, there are a few extra valid matplotlib backends that | ||
# map to the same GUI support | ||||
backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk' | ||||
Michael Droettboom
|
r13754 | backend2gui['GTK3Cairo'] = 'gtk3' | ||
Fernando Perez
|
r3902 | backend2gui['WX'] = 'wx' | ||
backend2gui['CocoaAgg'] = 'osx' | ||||
Fernando Perez
|
r2363 | #----------------------------------------------------------------------------- | ||
Brian Granger
|
r3280 | # Matplotlib utilities | ||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r3288 | |||
def getfigs(*fig_nums): | ||||
"""Get a list of matplotlib figures by figure numbers. | ||||
If no arguments are given, all available figures are returned. If the | ||||
argument list contains references to invalid figures, a warning is printed | ||||
but the function continues pasting further figures. | ||||
Parameters | ||||
---------- | ||||
figs : tuple | ||||
A tuple of ints giving the figure numbers of the figures to return. | ||||
""" | ||||
from matplotlib._pylab_helpers import Gcf | ||||
if not fig_nums: | ||||
fig_managers = Gcf.get_all_fig_managers() | ||||
return [fm.canvas.figure for fm in fig_managers] | ||||
else: | ||||
figs = [] | ||||
for num in fig_nums: | ||||
f = Gcf.figs.get(num) | ||||
if f is None: | ||||
Thomas Kluyver
|
r13386 | print('Warning: figure %s not available.' % num) | ||
Brian Granger
|
r3878 | else: | ||
figs.append(f.canvas.figure) | ||||
Brian Granger
|
r3288 | return figs | ||
Brian Granger
|
r3280 | def figsize(sizex, sizey): | ||
"""Set the default figure size to be [sizex, sizey]. | ||||
This is just an easy to remember, convenience wrapper that sets:: | ||||
matplotlib.rcParams['figure.figsize'] = [sizex, sizey] | ||||
""" | ||||
import matplotlib | ||||
matplotlib.rcParams['figure.figsize'] = [sizex, sizey] | ||||
MinRK
|
r15394 | def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs): | ||
MinRK
|
r16048 | """Print a figure to an image, and return the resulting file data | ||
Returned data will be bytes unless ``fmt='svg'``, | ||||
in which case it will be unicode. | ||||
MinRK
|
r15342 | |||
MinRK
|
r15394 | Any keyword args are passed to fig.canvas.print_figure, | ||
MinRK
|
r15342 | such as ``quality`` or ``bbox_inches``. | ||
Daniel B. Vasquez
|
r14773 | """ | ||
MinRK
|
r10802 | from matplotlib import rcParams | ||
Fernando Perez
|
r3731 | # When there's an empty figure, we shouldn't return anything, otherwise we | ||
# get big blank areas in the qt console. | ||||
Fernando Perez
|
r5732 | if not fig.axes and not fig.lines: | ||
Fernando Perez
|
r3731 | return | ||
MinRK
|
r10802 | dpi = rcParams['savefig.dpi'] | ||
if fmt == 'retina': | ||||
dpi = dpi * 2 | ||||
MinRK
|
r10803 | fmt = 'png' | ||
MinRK
|
r10802 | |||
MinRK
|
r15342 | # build keyword args | ||
kw = dict( | ||||
format=fmt, | ||||
fc=fig.get_facecolor(), | ||||
ec=fig.get_edgecolor(), | ||||
dpi=dpi, | ||||
MinRK
|
r15394 | bbox_inches=bbox_inches, | ||
MinRK
|
r15342 | ) | ||
# **kwargs get higher priority | ||||
kw.update(kwargs) | ||||
bytes_io = BytesIO() | ||||
fig.canvas.print_figure(bytes_io, **kw) | ||||
MinRK
|
r16048 | data = bytes_io.getvalue() | ||
if fmt == 'svg': | ||||
data = data.decode('utf-8') | ||||
return data | ||||
MinRK
|
r15342 | |||
def retina_figure(fig, **kwargs): | ||||
MinRK
|
r10802 | """format a figure as a pixel-doubled (retina) PNG""" | ||
MinRK
|
r15342 | pngdata = print_figure(fig, fmt='retina', **kwargs) | ||
MinRK
|
r10803 | w, h = _pngxy(pngdata) | ||
MinRK
|
r10802 | metadata = dict(width=w//2, height=h//2) | ||
return pngdata, metadata | ||||
Brian Granger
|
r3280 | |||
# We need a little factory function here to create the closure where | ||||
# safe_execfile can live. | ||||
def mpl_runner(safe_execfile): | ||||
"""Factory to return a matplotlib-enabled runner for %run. | ||||
Parameters | ||||
---------- | ||||
safe_execfile : function | ||||
This must be a function with the same interface as the | ||||
:meth:`safe_execfile` method of IPython. | ||||
Returns | ||||
------- | ||||
A function suitable for use as the ``runner`` argument of the %run magic | ||||
function. | ||||
""" | ||||
def mpl_execfile(fname,*where,**kw): | ||||
"""matplotlib-aware wrapper around safe_execfile. | ||||
Its interface is identical to that of the :func:`execfile` builtin. | ||||
This is ultimately a call to execfile(), but wrapped in safeties to | ||||
properly handle interactive rendering.""" | ||||
import matplotlib | ||||
import matplotlib.pylab as pylab | ||||
#print '*** Matplotlib runner ***' # dbg | ||||
# turn off rendering until end of script | ||||
is_interactive = matplotlib.rcParams['interactive'] | ||||
matplotlib.interactive(False) | ||||
safe_execfile(fname,*where,**kw) | ||||
matplotlib.interactive(is_interactive) | ||||
# make rendering call now, if the user tried to do it | ||||
if pylab.draw_if_interactive.called: | ||||
pylab.draw() | ||||
pylab.draw_if_interactive.called = False | ||||
return mpl_execfile | ||||
MinRK
|
r15342 | def select_figure_formats(shell, formats, **kwargs): | ||
Brian E. Granger
|
r15122 | """Select figure formats for the inline backend. | ||
MinRK
|
r3973 | |||
Brian E. Granger
|
r15122 | Parameters | ||
========== | ||||
shell : InteractiveShell | ||||
Brian E. Granger
|
r15125 | The main IPython instance. | ||
MinRK
|
r15342 | formats : str or set | ||
Brian E. Granger
|
r15122 | One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'. | ||
MinRK
|
r15342 | **kwargs : any | ||
Extra keyword arguments to be passed to fig.canvas.print_figure. | ||||
MinRK
|
r3973 | """ | ||
from matplotlib.figure import Figure | ||||
MinRK
|
r9372 | from IPython.kernel.zmq.pylab import backend_inline | ||
MinRK
|
r3973 | |||
svg_formatter = shell.display_formatter.formatters['image/svg+xml'] | ||||
png_formatter = shell.display_formatter.formatters['image/png'] | ||||
Daniel B. Vasquez
|
r14770 | jpg_formatter = shell.display_formatter.formatters['image/jpeg'] | ||
Brian E. Granger
|
r15122 | pdf_formatter = shell.display_formatter.formatters['application/pdf'] | ||
MinRK
|
r3973 | |||
Brian E. Granger
|
r15122 | if isinstance(formats, py3compat.string_types): | ||
formats = {formats} | ||||
MinRK
|
r15342 | # cast in case of list / tuple | ||
formats = set(formats) | ||||
Daniel B. Vasquez
|
r14772 | |||
MinRK
|
r15342 | [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ] | ||
supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'} | ||||
bad = formats.difference(supported) | ||||
if bad: | ||||
MinRK
|
r15397 | bs = "%s" % ','.join([repr(f) for f in bad]) | ||
gs = "%s" % ','.join([repr(f) for f in supported]) | ||||
raise ValueError("supported formats are: %s not %s" % (gs, bs)) | ||||
MinRK
|
r15342 | |||
if 'png' in formats: | ||||
png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs)) | ||||
if 'retina' in formats or 'png2x' in formats: | ||||
png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs)) | ||||
if 'jpg' in formats or 'jpeg' in formats: | ||||
jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs)) | ||||
if 'svg' in formats: | ||||
svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs)) | ||||
if 'pdf' in formats: | ||||
pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs)) | ||||
MinRK
|
r3973 | |||
Brian Granger
|
r3280 | #----------------------------------------------------------------------------- | ||
# Code for initializing matplotlib and importing pylab | ||||
Fernando Perez
|
r2363 | #----------------------------------------------------------------------------- | ||
Ryan May
|
r7965 | def find_gui_and_backend(gui=None, gui_select=None): | ||
Brian Granger
|
r2868 | """Given a gui string return the gui and mpl backend. | ||
Fernando Perez
|
r2363 | |||
Parameters | ||||
---------- | ||||
Brian Granger
|
r2868 | gui : str | ||
Fernando Perez
|
r2991 | Can be one of ('tk','gtk','wx','qt','qt4','inline'). | ||
Ryan May
|
r7965 | gui_select : str | ||
Can be one of ('tk','gtk','wx','qt','qt4','inline'). | ||||
This is any gui already selected by the shell. | ||||
Fernando Perez
|
r2363 | |||
Returns | ||||
------- | ||||
Brian Granger
|
r2868 | A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg', | ||
MinRK
|
r9372 | 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline'). | ||
Fernando Perez
|
r2363 | """ | ||
import matplotlib | ||||
MinRK
|
r5162 | if gui and gui != 'auto': | ||
Fernando Perez
|
r2363 | # select backend based on requested gui | ||
Fernando Perez
|
r2987 | backend = backends[gui] | ||
Fernando Perez
|
r2363 | else: | ||
Fernando Perez
|
r12593 | # We need to read the backend from the original data structure, *not* | ||
# from mpl.rcParams, since a prior invocation of %matplotlib may have | ||||
# overwritten that. | ||||
Fernando Perez
|
r12639 | # WARNING: this assumes matplotlib 1.1 or newer!! | ||
Fernando Perez
|
r12593 | backend = matplotlib.rcParamsOrig['backend'] | ||
Fernando Perez
|
r2363 | # In this case, we need to find what the appropriate gui selection call | ||
# should be for IPython, so we can activate inputhook accordingly | ||||
Fernando Perez
|
r3902 | gui = backend2gui.get(backend, None) | ||
Ryan May
|
r7965 | |||
# If we have already had a gui active, we need it and inline are the | ||||
# ones allowed. | ||||
if gui_select and gui != gui_select: | ||||
gui = gui_select | ||||
backend = backends[gui] | ||||
Brian Granger
|
r2868 | return gui, backend | ||
Fernando Perez
|
r2363 | |||
Brian Granger
|
r2868 | |||
def activate_matplotlib(backend): | ||||
"""Activate the given backend and set interactive to True.""" | ||||
import matplotlib | ||||
matplotlib.interactive(True) | ||||
MinRK
|
r11328 | |||
Ryan May
|
r7939 | # Matplotlib had a bug where even switch_backend could not force | ||
# the rcParam to update. This needs to be set *before* the module | ||||
# magic of switch_backend(). | ||||
matplotlib.rcParams['backend'] = backend | ||||
import matplotlib.pyplot | ||||
matplotlib.pyplot.switch_backend(backend) | ||||
Brian Granger
|
r2872 | # This must be imported last in the matplotlib series, after | ||
# backend/interactivity choices have been made | ||||
Fernando Perez
|
r2363 | import matplotlib.pylab as pylab | ||
Brian Granger
|
r2872 | pylab.show._needmain = False | ||
# We need to detect at runtime whether show() is called by the user. | ||||
# For this, we wrap it into a decorator which adds a 'called' flag. | ||||
pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive) | ||||
Fernando Perez
|
r2363 | |||
Fernando Perez
|
r5469 | |||
def import_pylab(user_ns, import_all=True): | ||||
MinRK
|
r11323 | """Populate the namespace with pylab-related values. | ||
Imports matplotlib, pylab, numpy, and everything from pylab and numpy. | ||||
Also imports a few names from IPython (figsize, display, getfigs) | ||||
""" | ||||
Fernando Perez
|
r2363 | |||
# Import numpy as np/pyplot as plt are conventions we're trying to | ||||
# somewhat standardize on. Making them available to users by default | ||||
Fernando Perez
|
r3195 | # will greatly help this. | ||
s = ("import numpy\n" | ||||
Fernando Perez
|
r2363 | "import matplotlib\n" | ||
"from matplotlib import pylab, mlab, pyplot\n" | ||||
"np = numpy\n" | ||||
"plt = pyplot\n" | ||||
Fernando Perez
|
r3195 | ) | ||
Thomas Kluyver
|
r13350 | exec(s, user_ns) | ||
MinRK
|
r11323 | |||
MinRK
|
r11328 | if import_all: | ||
s = ("from matplotlib.pylab import *\n" | ||||
"from numpy import *\n") | ||||
Thomas Kluyver
|
r13350 | exec(s, user_ns) | ||
MinRK
|
r11323 | |||
# IPython symbols to add | ||||
user_ns['figsize'] = figsize | ||||
from IPython.core.display import display | ||||
# Add display and getfigs to the user's namespace | ||||
user_ns['display'] = display | ||||
user_ns['getfigs'] = getfigs | ||||
Fernando Perez
|
r5468 | |||
Fernando Perez
|
r5469 | |||
MinRK
|
r11328 | def configure_inline_support(shell, backend): | ||
Fernando Perez
|
r5469 | """Configure an IPython shell object for matplotlib use. | ||
Parameters | ||||
---------- | ||||
shell : InteractiveShell instance | ||||
Fernando Perez
|
r5474 | backend : matplotlib backend | ||
Fernando Perez
|
r5469 | """ | ||
# If using our svg 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. | ||||
Fernando Perez
|
r5474 | # Note: if we can't load the inline backend, then there's no point | ||
# continuing (such as in terminal-only shells in environments without | ||||
# zeromq available). | ||||
try: | ||||
MinRK
|
r9372 | from IPython.kernel.zmq.pylab.backend_inline import InlineBackend | ||
Fernando Perez
|
r5474 | except ImportError: | ||
return | ||||
Ryan May
|
r7940 | from matplotlib import pyplot | ||
Fernando Perez
|
r5474 | |||
MinRK
|
r11064 | cfg = InlineBackend.instance(parent=shell) | ||
Fernando Perez
|
r5469 | cfg.shell = shell | ||
if cfg not in shell.configurables: | ||||
shell.configurables.append(cfg) | ||||
Fernando Perez
|
r5474 | if backend == backends['inline']: | ||
MinRK
|
r9372 | from IPython.kernel.zmq.pylab.backend_inline import flush_figures | ||
Thomas Kluyver
|
r15614 | shell.events.register('post_execute', flush_figures) | ||
Ryan May
|
r7940 | |||
# Save rcParams that will be overwrittern | ||||
shell._saved_rcParams = dict() | ||||
for k in cfg.rc: | ||||
shell._saved_rcParams[k] = pyplot.rcParams[k] | ||||
Fernando Perez
|
r5474 | # load inline_rc | ||
pyplot.rcParams.update(cfg.rc) | ||||
Ryan May
|
r7940 | else: | ||
MinRK
|
r9372 | from IPython.kernel.zmq.pylab.backend_inline import flush_figures | ||
Thomas Kluyver
|
r15614 | try: | ||
shell.events.unregister('post_execute', flush_figures) | ||||
except ValueError: | ||||
pass | ||||
Ryan May
|
r7940 | if hasattr(shell, '_saved_rcParams'): | ||
pyplot.rcParams.update(shell._saved_rcParams) | ||||
del shell._saved_rcParams | ||||
Fernando Perez
|
r5469 | |||
# Setup the default figure format | ||||
MinRK
|
r15342 | select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs) | ||
Brian Granger
|
r2868 | |||