pylabtools.py
425 lines
| 13.5 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 | ||
Min RK
|
r26812 | from binascii import b2a_base64 | ||
from functools import partial | ||||
martinRenou
|
r26464 | import warnings | ||
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 | ||||
martinRenou
|
r26464 | backends = { | ||
"tk": "TkAgg", | ||||
"gtk": "GTKAgg", | ||||
"gtk3": "GTK3Agg", | ||||
Elliott Sales de Andrade
|
r26726 | "gtk4": "GTK4Agg", | ||
martinRenou
|
r26464 | "wx": "WXAgg", | ||
"qt4": "Qt4Agg", | ||||
"qt5": "Qt5Agg", | ||||
Thomas A Caswell
|
r26683 | "qt6": "QtAgg", | ||
martinRenou
|
r26464 | "qt": "Qt5Agg", | ||
"osx": "MacOSX", | ||||
"nbagg": "nbAgg", | ||||
"notebook": "nbAgg", | ||||
"agg": "agg", | ||||
"svg": "svg", | ||||
"pdf": "pdf", | ||||
"ps": "ps", | ||||
"inline": "module://matplotlib_inline.backend_inline", | ||||
"ipympl": "module://ipympl.backend_nbagg", | ||||
"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())) | ||||
# In the reverse mapping, there are a few extra valid matplotlib backends that | ||||
# map to the same GUI support | ||||
Matthias Bussonnier
|
r26736 | backend2gui["GTK"] = backend2gui["GTKCairo"] = "gtk" | ||
backend2gui["GTK3Cairo"] = "gtk3" | ||||
backend2gui["GTK4Cairo"] = "gtk4" | ||||
backend2gui["WX"] = "wx" | ||||
backend2gui["CocoaAgg"] = "osx" | ||||
Thomas A Caswell
|
r26831 | # There needs to be a hysteresis here as the new QtAgg Matplotlib backend | ||
# supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5, | ||||
# and Qt6. | ||||
backend2gui["QtAgg"] = "qt" | ||||
backend2gui["Qt4Agg"] = "qt" | ||||
backend2gui["Qt5Agg"] = "qt" | ||||
Thomas Kluyver
|
r22877 | # And some backends that don't need GUI integration | ||
martinRenou
|
r26464 | del backend2gui["nbAgg"] | ||
del backend2gui["agg"] | ||||
del backend2gui["svg"] | ||||
del backend2gui["pdf"] | ||||
del backend2gui["ps"] | ||||
del backend2gui["module://matplotlib_inline.backend_inline"] | ||||
Thomas A Caswell
|
r26832 | del backend2gui["module://ipympl.backend_nbagg"] | ||
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] | ||||
Min RK
|
r26812 | def print_figure(fig, fmt="png", bbox_inches="tight", base64=False, **kwargs): | ||
MinRK
|
r16048 | """Print a figure to an image, and return the resulting file data | ||
martinRenou
|
r26464 | |||
MinRK
|
r16048 | Returned data will be bytes unless ``fmt='svg'``, | ||
in which case it will be unicode. | ||||
martinRenou
|
r26464 | |||
MinRK
|
r15394 | Any keyword args are passed to fig.canvas.print_figure, | ||
MinRK
|
r15342 | such as ``quality`` or ``bbox_inches``. | ||
Min RK
|
r26812 | |||
If `base64` is True, return base64-encoded str instead of raw bytes | ||||
for binary-encoded image formats | ||||
.. versionadded: 7.29 | ||||
base64 argument | ||||
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' | ||
martinRenou
|
r26464 | |||
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) | ||||
Thomas A Caswell
|
r25419 | |||
MinRK
|
r15342 | bytes_io = BytesIO() | ||
Thomas A Caswell
|
r25419 | if fig.canvas is None: | ||
from matplotlib.backend_bases import FigureCanvasBase | ||||
FigureCanvasBase(fig) | ||||
MinRK
|
r15342 | fig.canvas.print_figure(bytes_io, **kw) | ||
MinRK
|
r16048 | data = bytes_io.getvalue() | ||
if fmt == 'svg': | ||||
data = data.decode('utf-8') | ||||
Min RK
|
r26812 | elif base64: | ||
data = b2a_base64(data).decode("ascii") | ||||
MinRK
|
r16048 | return data | ||
Thomas A Caswell
|
r25419 | |||
Min RK
|
r26812 | def retina_figure(fig, base64=False, **kwargs): | ||
"""format a figure as a pixel-doubled (retina) PNG | ||||
If `base64` is True, return base64-encoded str instead of raw bytes | ||||
for binary-encoded image formats | ||||
.. versionadded: 7.29 | ||||
base64 argument | ||||
""" | ||||
pngdata = print_figure(fig, fmt="retina", base64=False, **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} | ||
Min RK
|
r26812 | if base64: | ||
pngdata = b2a_base64(pngdata).decode("ascii") | ||||
MinRK
|
r10802 | return pngdata, metadata | ||
Brian Granger
|
r3280 | |||
Min RK
|
r26812 | |||
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. | ||||
""" | ||||
martinRenou
|
r26464 | |||
Brian Granger
|
r3280 | 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 | ||||
Ram Rachum
|
r25833 | except AttributeError as e: | ||
raise NotImplementedError() from e | ||||
Min RK
|
r21962 | 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)) | ||||
martinRenou
|
r26464 | |||
Min RK
|
r26812 | if "png" in formats: | ||
png_formatter.for_type( | ||||
Figure, partial(print_figure, fmt="png", base64=True, **kwargs) | ||||
) | ||||
if "retina" in formats or "png2x" in formats: | ||||
png_formatter.for_type(Figure, partial(retina_figure, base64=True, **kwargs)) | ||||
if "jpg" in formats or "jpeg" in formats: | ||||
jpg_formatter.for_type( | ||||
Figure, partial(print_figure, fmt="jpg", base64=True, **kwargs) | ||||
) | ||||
if "svg" in formats: | ||||
svg_formatter.for_type(Figure, partial(print_figure, fmt="svg", **kwargs)) | ||||
if "pdf" in formats: | ||||
pdf_formatter.for_type( | ||||
Figure, partial(print_figure, fmt="pdf", base64=True, **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', | ||
martinRenou
|
r26464 | 'WXAgg','Qt4Agg','module://matplotlib_inline.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) | ||||
martinRenou
|
r26464 | |||
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 | ||||
Ben Lewis
|
r25195 | # Due to circular imports, pyplot may be only partially initialised | ||
# when this function runs. | ||||
# So avoid needing matplotlib attribute-lookup to access pyplot. | ||||
from matplotlib import pyplot as plt | ||||
Ryan May
|
r7939 | |||
Ben Lewis
|
r25195 | plt.switch_backend(backend) | ||
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. | ||
martinRenou
|
r26464 | |||
MinRK
|
r11323 | Imports matplotlib, pylab, numpy, and everything from pylab and numpy. | ||
martinRenou
|
r26464 | |||
MinRK
|
r11323 | Also imports a few names from IPython (figsize, display, getfigs) | ||
martinRenou
|
r26464 | |||
MinRK
|
r11323 | """ | ||
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) | ||
martinRenou
|
r26464 | |||
MinRK
|
r11328 | if import_all: | ||
s = ("from matplotlib.pylab import *\n" | ||||
"from numpy import *\n") | ||||
Thomas Kluyver
|
r13350 | exec(s, user_ns) | ||
martinRenou
|
r26464 | |||
MinRK
|
r11323 | # IPython symbols to add | ||
user_ns['figsize'] = figsize | ||||
Matthias Bussonnier
|
r25632 | from IPython.display import display | ||
MinRK
|
r11323 | # 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): | ||
martinRenou
|
r26464 | """ | ||
Matthias Bussonnier
|
r26474 | .. deprecated: 7.23 | ||
use `matplotlib_inline.backend_inline.configure_inline_support()` | ||||
martinRenou
|
r26464 | |||
Configure an IPython shell object for matplotlib use. | ||||
Fernando Perez
|
r5469 | |||
Parameters | ||||
---------- | ||||
shell : InteractiveShell instance | ||||
Fernando Perez
|
r5474 | backend : matplotlib backend | ||
Fernando Perez
|
r5469 | """ | ||
martinRenou
|
r26464 | warnings.warn( | ||
Matthias Bussonnier
|
r26474 | "`configure_inline_support` is deprecated since IPython 7.23, directly " | ||
"use `matplotlib_inline.backend_inline.configure_inline_support()`", | ||||
martinRenou
|
r26464 | DeprecationWarning, | ||
stacklevel=2, | ||||
) | ||||
Fernando Perez
|
r5469 | |||
Paul Ivanov
|
r26524 | from matplotlib_inline.backend_inline import ( | ||
configure_inline_support as configure_inline_support_orig, | ||||
) | ||||
Fernando Perez
|
r5474 | |||
martinRenou
|
r26464 | configure_inline_support_orig(shell, backend) | ||