pylabtools.py
385 lines
| 13.2 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 | """ | ||
#----------------------------------------------------------------------------- | ||||
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 | ||
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', | ||||
'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())) | ||||
# In the reverse mapping, there are a few extra valid matplotlib backends that | ||||
# map to the same GUI support | ||||
backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk' | ||||
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: | ||||
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
|
r3973 | def print_figure(fig, fmt='png'): | ||
"""Convert a figure to svg or png for inline display.""" | ||||
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 | ||
Brian Granger
|
r3280 | fc = fig.get_facecolor() | ||
ec = fig.get_edgecolor() | ||||
Takafumi Arakaki
|
r7798 | bytes_io = BytesIO() | ||
MinRK
|
r10802 | dpi = rcParams['savefig.dpi'] | ||
if fmt == 'retina': | ||||
dpi = dpi * 2 | ||||
MinRK
|
r10803 | fmt = 'png' | ||
Takafumi Arakaki
|
r7798 | fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight', | ||
MinRK
|
r10802 | facecolor=fc, edgecolor=ec, dpi=dpi) | ||
Takafumi Arakaki
|
r7798 | data = bytes_io.getvalue() | ||
MinRK
|
r3973 | return data | ||
MinRK
|
r10802 | |||
def retina_figure(fig): | ||||
"""format a figure as a pixel-doubled (retina) PNG""" | ||||
pngdata = print_figure(fig, fmt='retina') | ||||
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
|
r3973 | def select_figure_format(shell, fmt): | ||
MinRK
|
r10802 | """Select figure format for inline backend, can be 'png', 'retina', or 'svg'. | ||
MinRK
|
r3973 | |||
Using this method ensures only one figure format is active at a time. | ||||
""" | ||||
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'] | ||||
MinRK
|
r10802 | if fmt == 'png': | ||
MinRK
|
r3973 | svg_formatter.type_printers.pop(Figure, None) | ||
png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png')) | ||||
MinRK
|
r10802 | elif fmt in ('png2x', 'retina'): | ||
svg_formatter.type_printers.pop(Figure, None) | ||||
png_formatter.for_type(Figure, retina_figure) | ||||
elif fmt == 'svg': | ||||
MinRK
|
r3973 | png_formatter.type_printers.pop(Figure, None) | ||
svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg')) | ||||
else: | ||||
MinRK
|
r10802 | raise ValueError("supported formats are: 'png', 'retina', 'svg', not %r" % fmt) | ||
MinRK
|
r3973 | |||
# set the format to be used in the backend() | ||||
backend_inline._figure_format = fmt | ||||
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: | ||
backend = matplotlib.rcParams['backend'] | ||||
# 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) | ||||
Brian Granger
|
r2872 | |||
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): | ||||
Brian Granger
|
r2868 | """Import the standard pylab symbols into user_ns.""" | ||
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 | ) | ||
exec s in user_ns | ||||
Fernando Perez
|
r2363 | |||
if import_all: | ||||
Fernando Perez
|
r3195 | s = ("from matplotlib.pylab import *\n" | ||
"from numpy import *\n") | ||||
exec s in user_ns | ||||
Fernando Perez
|
r5468 | |||
Fernando Perez
|
r5469 | |||
Fernando Perez
|
r5474 | def configure_inline_support(shell, backend, user_ns=None): | ||
Fernando Perez
|
r5469 | """Configure an IPython shell object for matplotlib use. | ||
Parameters | ||||
---------- | ||||
shell : InteractiveShell instance | ||||
Fernando Perez
|
r5474 | backend : matplotlib backend | ||
Fernando Perez
|
r5469 | user_ns : dict | ||
A namespace where all configured variables will be placed. If not given, | ||||
the `user_ns` attribute of the shell object is used. | ||||
""" | ||||
# 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 | |||
user_ns = shell.user_ns if user_ns is None else user_ns | ||||
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 | ||
Fernando Perez
|
r5474 | shell.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) | ||||
# Add 'figsize' to pyplot and to the user's namespace | ||||
user_ns['figsize'] = pyplot.figsize = figsize | ||||
Ryan May
|
r7940 | else: | ||
MinRK
|
r9372 | from IPython.kernel.zmq.pylab.backend_inline import flush_figures | ||
Ryan May
|
r7940 | if flush_figures in shell._post_execute: | ||
shell._post_execute.pop(flush_figures) | ||||
if hasattr(shell, '_saved_rcParams'): | ||||
pyplot.rcParams.update(shell._saved_rcParams) | ||||
del shell._saved_rcParams | ||||
Fernando Perez
|
r5469 | |||
# Setup the default figure format | ||||
fmt = cfg.figure_format | ||||
select_figure_format(shell, fmt) | ||||
# The old pastefig function has been replaced by display | ||||
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
|
r2363 | |||
Brian Granger
|
r2868 | |||
Aaron Meurer
|
r8229 | def pylab_activate(user_ns, gui=None, import_all=True, shell=None, welcome_message=False): | ||
Brian Granger
|
r2868 | """Activate pylab mode in the user's namespace. | ||
Loads and initializes numpy, matplotlib and friends for interactive use. | ||||
Parameters | ||||
---------- | ||||
user_ns : dict | ||||
Namespace where the imports will occur. | ||||
gui : optional, string | ||||
A valid gui name following the conventions of the %gui magic. | ||||
import_all : optional, boolean | ||||
If true, an 'import *' is done from numpy and pylab. | ||||
Aaron Meurer
|
r8229 | welcome_message : optional, boolean | ||
If true, print a welcome message about pylab, which includes the backend | ||||
being used. | ||||
Brian Granger
|
r2868 | Returns | ||
------- | ||||
The actual gui used (if not given as input, it was obtained from matplotlib | ||||
itself, and will be needed next to configure IPython's gui integration. | ||||
""" | ||||
Ryan May
|
r7965 | pylab_gui_select = shell.pylab_gui_select if shell is not None else None | ||
# Try to find the appropriate gui and backend for the settings | ||||
gui, backend = find_gui_and_backend(gui, pylab_gui_select) | ||||
if shell is not None and gui != 'inline': | ||||
# If we have our first gui selection, store it | ||||
if pylab_gui_select is None: | ||||
shell.pylab_gui_select = gui | ||||
# Otherwise if they are different | ||||
elif gui != pylab_gui_select: | ||||
print ('Warning: Cannot change to a different GUI toolkit: %s.' | ||||
' Using %s instead.' % (gui, pylab_gui_select)) | ||||
gui, backend = find_gui_and_backend(pylab_gui_select) | ||||
Brian Granger
|
r2868 | activate_matplotlib(backend) | ||
Fernando Perez
|
r5469 | import_pylab(user_ns, import_all) | ||
Fernando Perez
|
r5475 | if shell is not None: | ||
configure_inline_support(shell, backend, user_ns) | ||||
Aaron Meurer
|
r8229 | if welcome_message: | ||
print """ | ||||
Fernando Perez
|
r2364 | Welcome to pylab, a matplotlib-based Python environment [backend: %s]. | ||
Fernando Perez
|
r2363 | For more information, type 'help(pylab)'.""" % backend | ||
Aaron Meurer
|
r8229 | # flush stdout, just to be safe | ||
sys.stdout.flush() | ||||
Fernando Perez
|
r2363 | |||
Aaron Meurer
|
r8229 | return gui | ||