pylabtools.py
409 lines
| 13.9 KiB
| text/x-python
|
PythonLexer
Fernando Perez
|
r2363 | # -*- coding: utf-8 -*- | ||
MinRK
|
r18084 | """Pylab (matplotlib) support utilities.""" | ||
Fernando Perez
|
r2363 | |||
MinRK
|
r18084 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
Brian Granger
|
r2498 | |||
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 | ||
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', | ||
'qt4': 'Qt4Agg', | ||||
MinRK
|
r18084 | 'qt5': 'Qt5Agg', | ||
Thomas A Caswell
|
r23858 | 'qt': 'Qt5Agg', | ||
MinRK
|
r3462 | 'osx': 'MacOSX', | ||
MinRK
|
r18091 | 'nbagg': 'nbAgg', | ||
Min RK
|
r20433 | 'notebook': 'nbAgg', | ||
Matteo
|
r22830 | 'agg': 'agg', | ||
Jens Hedegaard Nielsen
|
r23017 | 'inline': 'module://ipykernel.pylab.backend_inline', | ||
'ipympl': 'module://ipympl.backend_nbagg', | ||||
Thomas A Caswell
|
r23857 | 'widget': 'module://ipympl.backend_nbagg', | ||
} | ||||
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' | ||||
Thomas Kluyver
|
r22877 | # And some backends that don't need GUI integration | ||
del backend2gui['nbAgg'] | ||||
del backend2gui['agg'] | ||||
del backend2gui['module://ipykernel.pylab.backend_inline'] | ||||
Fernando Perez
|
r3902 | |||
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 | """ | ||
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 | ||
Hassan Kibirige
|
r22789 | dpi = fig.dpi | ||
MinRK
|
r10802 | if fmt == 'retina': | ||
dpi = dpi * 2 | ||||
MinRK
|
r10803 | fmt = 'png' | ||
MinRK
|
r10802 | |||
MinRK
|
r15342 | # build keyword args | ||
Srinivas Reddy Thatiparthy
|
r23231 | kw = { | ||
"format":fmt, | ||||
"facecolor":fig.get_facecolor(), | ||||
"edgecolor":fig.get_edgecolor(), | ||||
"dpi":dpi, | ||||
"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) | ||
Christopher Roach
|
r21813 | # Make sure that retina_figure acts just like print_figure and returns | ||
# None when the figure is empty. | ||||
if pngdata is None: | ||||
return | ||||
MinRK
|
r10803 | w, h = _pngxy(pngdata) | ||
Srinivas Reddy Thatiparthy
|
r23231 | metadata = {"width": w//2, "height":h//2} | ||
MinRK
|
r10802 | 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 | ||||
Thomas A Caswell
|
r22944 | import matplotlib.pyplot as plt | ||
Brian Granger
|
r3280 | |||
#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 | ||||
Thomas A Caswell
|
r22944 | if plt.draw_if_interactive.called: | ||
plt.draw() | ||||
plt.draw_if_interactive.called = False | ||||
Thomas A Caswell
|
r22945 | # re-draw everything that is stale | ||
try: | ||||
da = plt.draw_all | ||||
except AttributeError: | ||||
pass | ||||
else: | ||||
da() | ||||
Brian Granger
|
r3280 | |||
return mpl_execfile | ||||
Min RK
|
r21962 | def _reshow_nbagg_figure(fig): | ||
"""reshow an nbagg figure""" | ||||
try: | ||||
reshow = fig.canvas.manager.reshow | ||||
except AttributeError: | ||||
raise NotImplementedError() | ||||
else: | ||||
reshow() | ||||
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 | """ | ||
Min RK
|
r21962 | import matplotlib | ||
MinRK
|
r3973 | from matplotlib.figure import Figure | ||
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 | |||
Srinivas Reddy Thatiparthy
|
r23037 | if isinstance(formats, str): | ||
Brian E. Granger
|
r15122 | 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() ] | ||
Jens Hedegaard Nielsen
|
r23018 | mplbackend = matplotlib.get_backend().lower() | ||
if mplbackend == 'nbagg' or mplbackend == 'module://ipympl.backend_nbagg': | ||||
Min RK
|
r21962 | formatter = shell.display_formatter.ipython_display_formatter | ||
formatter.for_type(Figure, _reshow_nbagg_figure) | ||||
MinRK
|
r15342 | 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 | ||
Matteo
|
r22833 | Can be one of ('tk','gtk','wx','qt','qt4','inline','agg'). | ||
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', | ||
Matteo
|
r22833 | 'WXAgg','Qt4Agg','module://ipykernel.pylab.backend_inline','agg'). | ||
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] | ||
Matteo
|
r22830 | if gui == 'agg': | ||
gui = None | ||||
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 | ||||
Thomas A Caswell
|
r22944 | import matplotlib.pyplot as plt | ||
Fernando Perez
|
r2363 | |||
Thomas A Caswell
|
r22944 | plt.show._needmain = False | ||
Brian Granger
|
r2872 | # 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. | ||||
Thomas A Caswell
|
r22944 | plt.draw_if_interactive = flag_calls(plt.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: | ||||
Min RK
|
r21337 | from ipykernel.pylab.backend_inline import InlineBackend | ||
Fernando Perez
|
r5474 | except ImportError: | ||
return | ||||
Min RK
|
r22772 | import matplotlib | ||
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']: | ||
Min RK
|
r21337 | from ipykernel.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 | ||||
Srinivas Reddy Thatiparthy
|
r23231 | shell._saved_rcParams = {} | ||
Ryan May
|
r7940 | for k in cfg.rc: | ||
Min RK
|
r22772 | shell._saved_rcParams[k] = matplotlib.rcParams[k] | ||
Fernando Perez
|
r5474 | # load inline_rc | ||
Min RK
|
r22772 | matplotlib.rcParams.update(cfg.rc) | ||
Jan Schulz
|
r21756 | new_backend_name = "inline" | ||
Ryan May
|
r7940 | else: | ||
Min RK
|
r21337 | from ipykernel.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'): | ||
Min RK
|
r22772 | matplotlib.rcParams.update(shell._saved_rcParams) | ||
Ryan May
|
r7940 | del shell._saved_rcParams | ||
Jan Schulz
|
r21756 | new_backend_name = "other" | ||
# only enable the formats once -> don't change the enabled formats (which the user may | ||||
# has changed) when getting another "%matplotlib inline" call. | ||||
# See https://github.com/ipython/ipykernel/issues/29 | ||||
cur_backend = getattr(configure_inline_support, "current_backend", "unset") | ||||
if new_backend_name != cur_backend: | ||||
# Setup the default figure format | ||||
select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs) | ||||
configure_inline_support.current_backend = new_backend_name | ||||