# -*- coding: utf-8 -*-
# Copyright (C) 2015-2019 by Brendt Wohlberg <brendt@ieee.org>
# All rights reserved. BSD 3-clause License.
# This file is part of the SPORCO package. Details of the copyright
# and user license can be found in the 'LICENSE.txt' file distributed
# with the package.
"""Plotting/visualisation functions"""
from __future__ import absolute_import, division, print_function
from builtins import range
import sys
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.pyplot import figure, subplot, subplots, gcf, gca, savefig
from mpl_toolkits.axes_grid1 import make_axes_locatable
from mpl_toolkits.mplot3d import Axes3D
try:
import mpldatacursor as mpldc
except ImportError:
have_mpldc = False
else:
have_mpldc = True
__author__ = """Brendt Wohlberg <brendt@ieee.org>"""
__all__ = ['plot', 'surf', 'contour', 'imview', 'close',
'set_ipython_plot_backend', 'set_notebook_plot_backend',
'config_notebook_plotting']
def attach_keypress(fig, scaling=1.1):
"""
Attach a key press event handler that configures keys for closing a
figure and changing the figure size. Keys 'e' and 'c' respectively
expand and contract the figure, and key 'q' closes it.
**Note:** Resizing may not function correctly with all matplotlib
backends (a
`bug <https://github.com/matplotlib/matplotlib/issues/10083>`__
has been reported).
Parameters
----------
fig : :class:`matplotlib.figure.Figure` object
Figure to which event handling is to be attached
scaling : float, optional (default 1.1)
Scaling factor for figure size changes
Returns
-------
press : function
Key press event handler function
"""
def press(event):
if event.key == 'q':
plt.close(fig)
elif event.key == 'e':
fig.set_size_inches(scaling * fig.get_size_inches(), forward=True)
elif event.key == 'c':
fig.set_size_inches(fig.get_size_inches() / scaling, forward=True)
# Avoid multiple event handlers attached to the same figure
if not hasattr(fig, '_sporco_keypress_cid'):
cid = fig.canvas.mpl_connect('key_press_event', press)
fig._sporco_keypress_cid = cid
return press
def attach_zoom(ax, scaling=2.0):
"""
Attach an event handler that supports zooming within a plot using
the mouse scroll wheel.
Parameters
----------
ax : :class:`matplotlib.axes.Axes` object
Axes to which event handling is to be attached
scaling : float, optional (default 2.0)
Scaling factor for zooming in and out
Returns
-------
zoom : function
Mouse scroll wheel event handler function
"""
# See https://stackoverflow.com/questions/11551049
def zoom(event):
# Get the current x and y limits
cur_xlim = ax.get_xlim()
cur_ylim = ax.get_ylim()
# Get event location
xdata = event.xdata
ydata = event.ydata
# Return if cursor is not over valid region of plot
if xdata is None or ydata is None:
return
if event.button == 'up':
# Deal with zoom in
scale_factor = 1.0 / scaling
elif event.button == 'down':
# Deal with zoom out
scale_factor = scaling
# Get distance from the cursor to the edge of the figure frame
x_left = xdata - cur_xlim[0]
x_right = cur_xlim[1] - xdata
y_top = ydata - cur_ylim[0]
y_bottom = cur_ylim[1] - ydata
# Calculate new x and y limits
new_xlim = (xdata - x_left * scale_factor,
xdata + x_right * scale_factor)
new_ylim = (ydata - y_top * scale_factor,
ydata + y_bottom * scale_factor)
# Ensure that x limit range is no larger than that of the reference
if np.diff(new_xlim) > np.diff(zoom.xlim_ref):
new_xlim *= np.diff(zoom.xlim_ref) / np.diff(new_xlim)
# Ensure that lower x limit is not less than that of the reference
if new_xlim[0] < zoom.xlim_ref[0]:
new_xlim += np.array(zoom.xlim_ref[0] - new_xlim[0])
# Ensure that upper x limit is not greater than that of the reference
if new_xlim[1] > zoom.xlim_ref[1]:
new_xlim -= np.array(new_xlim[1] - zoom.xlim_ref[1])
# Ensure that ylim tuple has the smallest value first
if zoom.ylim_ref[1] < zoom.ylim_ref[0]:
ylim_ref = zoom.ylim_ref[::-1]
new_ylim = new_ylim[::-1]
else:
ylim_ref = zoom.ylim_ref
# Ensure that y limit range is no larger than that of the reference
if np.diff(new_ylim) > np.diff(ylim_ref):
new_ylim *= np.diff(ylim_ref) / np.diff(new_ylim)
# Ensure that lower y limit is not less than that of the reference
if new_ylim[0] < ylim_ref[0]:
new_ylim += np.array(ylim_ref[0] - new_ylim[0])
# Ensure that upper y limit is not greater than that of the reference
if new_ylim[1] > ylim_ref[1]:
new_ylim -= np.array(new_ylim[1] - ylim_ref[1])
# Return the ylim tuple to its original order
if zoom.ylim_ref[1] < zoom.ylim_ref[0]:
new_ylim = new_ylim[::-1]
# Set new x and y limits
ax.set_xlim(new_xlim)
ax.set_ylim(new_ylim)
# Force redraw
ax.figure.canvas.draw()
# Record reference x and y limits prior to any zooming
zoom.xlim_ref = ax.get_xlim()
zoom.ylim_ref = ax.get_ylim()
# Get figure for specified axes and attach the event handler
fig = ax.get_figure()
fig.canvas.mpl_connect('scroll_event', zoom)
return zoom
[docs]
def plot(y, x=None, ptyp='plot', xlbl=None, ylbl=None, title=None,
lgnd=None, lglc=None, **kwargs):
"""
Plot points or lines in 2D. If a figure object is specified then the
plot is drawn in that figure, and ``fig.show()`` is not called. The
figure is closed on key entry 'q'.
Parameters
----------
y : array_like
1d or 2d array of data to plot. If a 2d array, each column is
plotted as a separate curve.
x : array_like, optional (default None)
Values for x-axis of the plot
ptyp : string, optional (default 'plot')
Plot type specification (options are 'plot', 'semilogx',
'semilogy', and 'loglog')
xlbl : string, optional (default None)
Label for x-axis
ylbl : string, optional (default None)
Label for y-axis
title : string, optional (default None)
Figure title
lgnd : list of strings, optional (default None)
List of legend string
lglc : string, optional (default None)
Legend location string
**kwargs : :class:`matplotlib.lines.Line2D` properties or figure \
properties, optional
Keyword arguments specifying :class:`matplotlib.lines.Line2D`
properties, e.g. ``lw=2.0`` sets a line width of 2, or properties
of the figure and axes. If not specified, the defaults for line
width (``lw``) and marker size (``ms``) are 1.5 and 6.0
respectively. The valid figure and axes keyword arguments are
listed below:
.. |mplfg| replace:: :class:`matplotlib.figure.Figure` object
.. |mplax| replace:: :class:`matplotlib.axes.Axes` object
.. rst-class:: kwargs
===== ==================== ======================================
kwarg Accepts Description
===== ==================== ======================================
fgsz tuple (width,height) Specify figure dimensions in inches
fgnm integer Figure number of figure
fig |mplfg| Draw in specified figure instead of
creating one
ax |mplax| Plot in specified axes instead of
current axes of figure
===== ==================== ======================================
Returns
-------
fig : :class:`matplotlib.figure.Figure` object
Figure object for this figure
ax : :class:`matplotlib.axes.Axes` object
Axes object for this plot
"""
# Extract kwargs entries that are not related to line properties
fgsz = kwargs.pop('fgsz', None)
fgnm = kwargs.pop('fgnm', None)
fig = kwargs.pop('fig', None)
ax = kwargs.pop('ax', None)
figp = fig
if fig is None:
fig = plt.figure(num=fgnm, figsize=fgsz)
fig.clf()
ax = fig.gca()
elif ax is None:
ax = fig.gca()
# Set defaults for line width and marker size
if 'lw' not in kwargs and 'linewidth' not in kwargs:
kwargs['lw'] = 1.5
if 'ms' not in kwargs and 'markersize' not in kwargs:
kwargs['ms'] = 6.0
if ptyp not in ('plot', 'semilogx', 'semilogy', 'loglog'):
raise ValueError("Invalid plot type '%s'" % ptyp)
pltmth = getattr(ax, ptyp)
if x is None:
pltln = pltmth(y, **kwargs)
else:
pltln = pltmth(x, y, **kwargs)
ax.fmt_xdata = lambda x: "{: .2f}".format(x)
ax.fmt_ydata = lambda x: "{: .2f}".format(x)
if title is not None:
ax.set_title(title)
if xlbl is not None:
ax.set_xlabel(xlbl)
if ylbl is not None:
ax.set_ylabel(ylbl)
if lgnd is not None:
ax.legend(lgnd, loc=lglc)
attach_keypress(fig)
attach_zoom(ax)
if have_mpldc:
mpldc.datacursor(pltln)
if figp is None:
fig.show()
return fig, ax
[docs]
def surf(z, x=None, y=None, elev=None, azim=None, xlbl=None, ylbl=None,
zlbl=None, title=None, lblpad=8.0, alpha=1.0, cntr=None,
cmap=None, fgsz=None, fgnm=None, fig=None, ax=None):
"""
Plot a 2D surface in 3D. If a figure object is specified then the
surface is drawn in that figure, and ``fig.show()`` is not called.
The figure is closed on key entry 'q'.
Parameters
----------
z : array_like
2d array of data to plot
x : array_like, optional (default None)
Values for x-axis of the plot
y : array_like, optional (default None)
Values for y-axis of the plot
elev : float
Elevation angle (in degrees) in the z plane
azim : foat
Azimuth angle (in degrees) in the x,y plane
xlbl : string, optional (default None)
Label for x-axis
ylbl : string, optional (default None)
Label for y-axis
zlbl : string, optional (default None)
Label for z-axis
title : string, optional (default None)
Figure title
lblpad : float, optional (default 8.0)
Label padding
alpha : float between 0.0 and 1.0, optional (default 1.0)
Transparency
cntr : int or sequence of ints, optional (default None)
If not None, plot contours of the surface on the lower end of
the z-axis. An int specifies the number of contours to plot, and
a sequence specifies the specific contour levels to plot.
cmap : :class:`matplotlib.colors.Colormap` object, optional (default None)
Colour map for surface. If none specifed, defaults to cm.YlOrRd
fgsz : tuple (width,height), optional (default None)
Specify figure dimensions in inches
fgnm : integer, optional (default None)
Figure number of figure
fig : :class:`matplotlib.figure.Figure` object, optional (default None)
Draw in specified figure instead of creating one
ax : :class:`matplotlib.axes.Axes` object, optional (default None)
Plot in specified axes instead of creating one
Returns
-------
fig : :class:`matplotlib.figure.Figure` object
Figure object for this figure
ax : :class:`matplotlib.axes.Axes` object
Axes object for this plot
"""
figp = fig
if fig is None:
fig = plt.figure(num=fgnm, figsize=fgsz)
fig.clf()
ax = plt.axes(projection='3d')
else:
if ax is None:
ax = plt.axes(projection='3d')
else:
# See https://stackoverflow.com/a/43563804
# https://stackoverflow.com/a/35221116
if ax.name != '3d':
ax.remove()
ax = fig.add_subplot(ax.get_subplotspec(), projection='3d')
if elev is not None or azim is not None:
ax.view_init(elev=elev, azim=azim)
if cmap is None:
cmap = cm.YlOrRd
if x is None:
x = range(z.shape[1])
if y is None:
y = range(z.shape[0])
xg, yg = np.meshgrid(x, y)
ax.plot_surface(xg, yg, z, rstride=1, cstride=1, alpha=alpha, cmap=cmap)
if cntr is not None:
offset = np.around(z.min() - 0.2 * (z.max() - z.min()), 3)
ax.contour(xg, yg, z, cntr, cmap=cmap, linewidths=2,
linestyles="solid", offset=offset)
ax.set_zlim(offset, ax.get_zlim()[1])
ax.fmt_xdata = lambda x: "{: .2f}".format(x)
ax.fmt_ydata = lambda x: "{: .2f}".format(x)
ax.fmt_zdata = lambda x: "{: .2f}".format(x)
if title is not None:
ax.set_title(title)
if xlbl is not None:
ax.set_xlabel(xlbl, labelpad=lblpad)
if ylbl is not None:
ax.set_ylabel(ylbl, labelpad=lblpad)
if zlbl is not None:
ax.set_zlabel(zlbl, labelpad=lblpad)
attach_keypress(fig)
if figp is None:
fig.show()
return fig, ax
[docs]
def contour(z, x=None, y=None, v=5, xlog=False, ylog=False, xlbl=None,
ylbl=None, title=None, cfmt=None, cfntsz=10, lfntsz=None,
alpha=1.0, cmap=None, vmin=None, vmax=None, fgsz=None, fgnm=None,
fig=None, ax=None):
"""
Contour plot of a 2D surface. If a figure object is specified then the
plot is drawn in that figure, and ``fig.show()`` is not called. The
figure is closed on key entry 'q'.
Parameters
----------
z : array_like
2d array of data to plot
x : array_like, optional (default None)
Values for x-axis of the plot
y : array_like, optional (default None)
Values for y-axis of the plot
v : int or sequence of floats, optional (default 5)
An int specifies the number of contours to plot, and a sequence
specifies the specific contour levels to plot.
xlog : boolean, optional (default False)
Set x-axis to log scale
ylog : boolean, optional (default False)
Set y-axis to log scale
xlbl : string, optional (default None)
Label for x-axis
ylbl : string, optional (default None)
Label for y-axis
title : string, optional (default None)
Figure title
cfmt : string, optional (default None)
Format string for contour labels.
cfntsz : int or None, optional (default 10)
Contour label font size. No contour labels are displayed if
set to 0 or None.
lfntsz : int, optional (default None)
Axis label font size. The default font size is used if set to None.
alpha : float, optional (default 1.0)
Underlying image display alpha value
cmap : :class:`matplotlib.colors.Colormap`, optional (default None)
Colour map for surface. If none specifed, defaults to cm.YlOrRd
vmin, vmax : float, optional (default None)
Set upper and lower bounds for the colour map (see the corresponding
parameters of :meth:`matplotlib.axes.Axes.imshow`)
fgsz : tuple (width,height), optional (default None)
Specify figure dimensions in inches
fgnm : integer, optional (default None)
Figure number of figure
fig : :class:`matplotlib.figure.Figure` object, optional (default None)
Draw in specified figure instead of creating one
ax : :class:`matplotlib.axes.Axes` object, optional (default None)
Plot in specified axes instead of current axes of figure
Returns
-------
fig : :class:`matplotlib.figure.Figure` object
Figure object for this figure
ax : :class:`matplotlib.axes.Axes` object
Axes object for this plot
"""
figp = fig
if fig is None:
fig = plt.figure(num=fgnm, figsize=fgsz)
fig.clf()
ax = fig.gca()
elif ax is None:
ax = fig.gca()
if xlog:
ax.set_xscale('log')
if ylog:
ax.set_yscale('log')
if cmap is None:
cmap = cm.YlOrRd
if x is None:
x = np.arange(z.shape[1])
else:
x = np.array(x)
if y is None:
y = np.arange(z.shape[0])
else:
y = np.array(y)
xg, yg = np.meshgrid(x, y)
cntr = ax.contour(xg, yg, z, v, colors='black')
kwargs = {}
if cfntsz is not None and cfntsz > 0:
kwargs['fontsize'] = cfntsz
if cfmt is not None:
kwargs['fmt'] = cfmt
if kwargs:
plt.clabel(cntr, inline=True, **kwargs)
pc = ax.pcolormesh(xg, yg, z, cmap=cmap, vmin=vmin, vmax=vmax, alpha=alpha,
shading='gouraud', clim=(vmin, vmax))
if xlog:
ax.fmt_xdata = lambda x: "{: .2e}".format(x)
else:
ax.fmt_xdata = lambda x: "{: .2f}".format(x)
if ylog:
ax.fmt_ydata = lambda x: "{: .2e}".format(x)
else:
ax.fmt_ydata = lambda x: "{: .2f}".format(x)
if title is not None:
ax.set_title(title)
if xlbl is not None:
ax.set_xlabel(xlbl, fontsize=lfntsz)
if ylbl is not None:
ax.set_ylabel(ylbl, fontsize=lfntsz)
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.2)
plt.colorbar(pc, ax=ax, cax=cax)
attach_keypress(fig)
attach_zoom(ax)
if have_mpldc:
mpldc.datacursor()
if figp is None:
fig.show()
return fig, ax
[docs]
def imview(img, title=None, copy=True, fltscl=False, intrp='nearest',
norm=None, cbar=False, cmap=None, fgsz=None, fgnm=None,
fig=None, ax=None):
"""
Display an image. Pixel values are displayed when the pointer is over
valid image data. If a figure object is specified then the image is
drawn in that figure, and ``fig.show()`` is not called. The figure is
closed on key entry 'q'.
Parameters
----------
img : array_like, shape (Nr, Nc) or (Nr, Nc, 3) or (Nr, Nc, 4)
Image to display
title : string, optional (default None)
Figure title
copy : boolean, optional (default True)
If True, create a copy of input `img` as a reference for displayed
pixel values, ensuring that displayed values do not change when the
array changes in the calling scope. Set this flag to False if the
overhead of an additional copy of the input image is not acceptable.
fltscl : boolean, optional (default False)
If True, rescale and shift floating point arrays to [0,1]
intrp : string, optional (default 'nearest')
Specify type of interpolation used to display image (see
``interpolation`` parameter of :meth:`matplotlib.axes.Axes.imshow`)
norm : :class:`matplotlib.colors.Normalize` object, optional (default None)
Specify the :class:`matplotlib.colors.Normalize` instance used to
scale pixel values for input to the colour map
cbar : boolean, optional (default False)
Flag indicating whether to display colorbar
cmap : :class:`matplotlib.colors.Colormap`, optional (default None)
Colour map for image. If none specifed, defaults to cm.Greys_r
for monochrome image
fgsz : tuple (width,height), optional (default None)
Specify figure dimensions in inches
fgnm : integer, optional (default None)
Figure number of figure
fig : :class:`matplotlib.figure.Figure` object, optional (default None)
Draw in specified figure instead of creating one
ax : :class:`matplotlib.axes.Axes` object, optional (default None)
Plot in specified axes instead of current axes of figure
Returns
-------
fig : :class:`matplotlib.figure.Figure` object
Figure object for this figure
ax : :class:`matplotlib.axes.Axes` object
Axes object for this plot
"""
if img.ndim > 2 and img.shape[2] != 3:
raise ValueError('Argument img must be an Nr x Nc array or an '
'Nr x Nc x 3 array')
figp = fig
if fig is None:
fig = plt.figure(num=fgnm, figsize=fgsz)
fig.clf()
ax = fig.gca()
elif ax is None:
ax = fig.gca()
# Deal with removal of 'box-forced' adjustable in Matplotlib 2.2.0
mplv = matplotlib.__version__.split('.')
if int(mplv[0]) > 2 or (int(mplv[0]) == 2 and int(mplv[1]) >= 2):
try:
ax.set_adjustable('box')
except Exception:
ax.set_adjustable('datalim')
else:
ax.set_adjustable('box-forced')
imgd = img.copy()
if copy:
# Keep a separate copy of the input image so that the original
# pixel values can be display rather than the scaled pixel
# values that are actually plotted.
img = img.copy()
if cmap is None and img.ndim == 2:
cmap = cm.Greys_r
if np.issubdtype(img.dtype, np.floating):
if fltscl:
imgd -= imgd.min()
imgd /= imgd.max()
if img.ndim > 2:
imgd = np.clip(imgd, 0.0, 1.0)
elif img.dtype == np.uint16:
imgd = np.float16(imgd) / np.iinfo(np.uint16).max
elif img.dtype == np.int16:
imgd = np.float16(imgd) - imgd.min()
imgd /= imgd.max()
if norm is None:
im = ax.imshow(imgd, cmap=cmap, interpolation=intrp, vmin=imgd.min(),
vmax=imgd.max())
else:
im = ax.imshow(imgd, cmap=cmap, interpolation=intrp, norm=norm)
ax.set_yticklabels([])
ax.set_xticklabels([])
if title is not None:
ax.set_title(title)
if cbar or cbar is None:
orient = 'vertical' if img.shape[0] >= img.shape[1] else 'horizontal'
pos = 'right' if orient == 'vertical' else 'bottom'
divider = make_axes_locatable(ax)
cax = divider.append_axes(pos, size="5%", pad=0.2)
if cbar is None:
# See http://chris35wills.github.io/matplotlib_axis
if hasattr(cax, 'set_facecolor'):
cax.set_facecolor('none')
else:
cax.set_axis_bgcolor('none')
for axis in ['top', 'bottom', 'left', 'right']:
cax.spines[axis].set_linewidth(0)
cax.set_xticks([])
cax.set_yticks([])
else:
plt.colorbar(im, ax=ax, cax=cax, orientation=orient)
def format_coord(x, y):
nr, nc = imgd.shape[0:2]
col = int(x + 0.5)
row = int(y + 0.5)
if col >= 0 and col < nc and row >= 0 and row < nr:
z = img[row, col]
if imgd.ndim == 2:
return 'x=%6.2f, y=%6.2f, z=%.2f' % (x, y, z)
else:
return 'x=%6.2f, y=%6.2f, z=(%.2f,%.2f,%.2f)' % \
sum(((x,), (y,), tuple(z)), ())
else:
return 'x=%.2f, y=%.2f' % (x, y)
ax.format_coord = format_coord
if fig.canvas.toolbar is not None:
# See https://stackoverflow.com/a/47086132
def mouse_move(self, event):
if event.inaxes and event.inaxes.get_navigate():
s = event.inaxes.format_coord(event.xdata, event.ydata)
self.set_message(s)
def mouse_move_patch(arg):
return mouse_move(fig.canvas.toolbar, arg)
fig.canvas.toolbar._idDrag = fig.canvas.mpl_connect(
'motion_notify_event', mouse_move_patch)
attach_keypress(fig)
attach_zoom(ax)
if have_mpldc:
mpldc.datacursor(display='single')
if figp is None:
fig.show()
return fig, ax
[docs]
def close(fig=None):
"""
Close figure(s). If a figure object reference or figure number is
provided, close the specified figure, otherwise close all figures.
Parameters
----------
fig : :class:`matplotlib.figure.Figure` object or integer,\
optional (default None)
Figure object or number of figure to close
"""
if fig is None:
plt.close('all')
else:
plt.close(fig)
[docs]
def set_ipython_plot_backend(backend='qt'):
"""
Set matplotlib backend within an ipython shell. Ths function has the
same effect as the line magic ``%matplotlib [backend]`` but is called
as a function and includes a check to determine whether the code is
running in an ipython shell, so that it can safely be used within a
normal python script since it has no effect when not running in an
ipython shell.
Parameters
----------
backend : string, optional (default 'qt')
Name of backend to be passed to the ``%matplotlib`` line magic
command
"""
from sporco.util import in_ipython
if in_ipython():
# See https://stackoverflow.com/questions/35595766
get_ipython().run_line_magic('matplotlib', backend)
[docs]
def set_notebook_plot_backend(backend='inline'):
"""
Set matplotlib backend within a Jupyter Notebook shell. Ths function
has the same effect as the line magic ``%matplotlib [backend]`` but is
called as a function and includes a check to determine whether the code
is running in a notebook shell, so that it can safely be used within a
normal python script since it has no effect when not running in a
notebook shell.
Parameters
----------
backend : string, optional (default 'inline')
Name of backend to be passed to the ``%matplotlib`` line magic
command
"""
from sporco.util import in_notebook
if in_notebook():
# See https://stackoverflow.com/questions/35595766
get_ipython().run_line_magic('matplotlib', backend)
[docs]
def config_notebook_plotting():
"""
Configure plotting functions for inline plotting within a Jupyter
Notebook shell. This function has no effect when not within a
notebook shell, and may therefore be used within a normal python
script.
"""
# Check whether running within a notebook shell and have
# not already monkey patched the plot function
from sporco.util import in_notebook
module = sys.modules[__name__]
if in_notebook() and module.plot.__name__ == 'plot':
# Set inline backend (i.e. %matplotlib inline) if in a notebook shell
set_notebook_plot_backend()
# Replace plot function with a wrapper function that discards
# its return value (within a notebook with inline plotting, plots
# are duplicated if the return value from the original function is
# not assigned to a variable)
plot_original = module.plot
def plot_wrap(*args, **kwargs):
plot_original(*args, **kwargs)
module.plot = plot_wrap
# Replace surf function with a wrapper function that discards
# its return value (see comment for plot function)
surf_original = module.surf
def surf_wrap(*args, **kwargs):
surf_original(*args, **kwargs)
module.surf = surf_wrap
# Replace contour function with a wrapper function that discards
# its return value (see comment for plot function)
contour_original = module.contour
def contour_wrap(*args, **kwargs):
contour_original(*args, **kwargs)
module.contour = contour_wrap
# Replace imview function with a wrapper function that discards
# its return value (see comment for plot function)
imview_original = module.imview
def imview_wrap(*args, **kwargs):
imview_original(*args, **kwargs)
module.imview = imview_wrap
# Disable figure show method (results in a warning if used within
# a notebook with inline plotting)
import matplotlib.figure
def show_disable(self):
pass
matplotlib.figure.Figure.show = show_disable