diff --git a/skimage/viewer/plugins/plotplugin.py b/skimage/viewer/plugins/plotplugin.py index d3d4ae63..850d0b71 100644 --- a/skimage/viewer/plugins/plotplugin.py +++ b/skimage/viewer/plugins/plotplugin.py @@ -5,25 +5,11 @@ try: except ImportError: print("Could not import PyQt4 -- skimage.viewer not available.") -try: - import matplotlib.pyplot as plt -except ImportError: - print("Could not import matplotlib -- skimage.viewer not available.") - -from ..utils import MatplotlibCanvas +from ..utils import new_plot from .base import Plugin -class PlotCanvas(MatplotlibCanvas): - """Canvas for displaying images. - - This canvas derives from Matplotlib, and has attributes `fig` and `ax`, - which point to Matplotlib figure and axes. - """ - def __init__(self, parent, height, width, **kwargs): - self.fig, self.ax = plt.subplots(figsize=(height, width), **kwargs) - super(PlotCanvas, self).__init__(parent, self.fig, **kwargs) - self.setMinimumHeight(150) +__all__ = ['PlotPlugin'] class PlotPlugin(Plugin): @@ -45,8 +31,9 @@ class PlotPlugin(Plugin): self.canvas.draw_idle() def add_plot(self, height=4, width=4): - self.canvas = PlotCanvas(self, height, width) - self.fig = self.canvas.fig + self.fig, self.ax = new_plot(figsize=(height, width)) + self.canvas = self.fig.canvas + self.canvas.setMinimumHeight(150) #TODO: Converted color is slightly different than Qt background. qpalette = QtGui.QPalette() qcolor = qpalette.color(QtGui.QPalette.Window) @@ -54,5 +41,4 @@ class PlotPlugin(Plugin): if np.isscalar(bgcolor): bgcolor = str(bgcolor / 255.) self.fig.patch.set_facecolor(bgcolor) - self.ax = self.canvas.ax self.layout.addWidget(self.canvas, self.row, 0) diff --git a/skimage/viewer/utils/core.py b/skimage/viewer/utils/core.py index cf632d5a..9646bbd6 100644 --- a/skimage/viewer/utils/core.py +++ b/skimage/viewer/utils/core.py @@ -3,8 +3,11 @@ import warnings import numpy as np try: - import matplotlib.pyplot as plt + import matplotlib as mpl + from matplotlib.figure import Figure + from matplotlib import _pylab_helpers from matplotlib.colors import LinearSegmentedColormap + from matplotlib.backends.backend_qt4 import FigureManagerQT from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg except ImportError: FigureCanvasQTAgg = object # hack to prevent nosetest and autodoc errors @@ -18,7 +21,7 @@ except ImportError: __all__ = ['init_qtapp', 'start_qtapp', 'RequiredAttr', 'figimage', - 'LinearColormap', 'ClearColormap', 'MatplotlibCanvas'] + 'LinearColormap', 'ClearColormap', 'FigureCanvas', 'new_plot'] QApp = None @@ -55,38 +58,6 @@ class RequiredAttr(object): self.val = val -def figimage(image, scale=1, dpi=None, **kwargs): - """Return figure and axes with figure tightly surrounding image. - - Unlike pyplot.figimage, this actually plots onto an axes object, which - fills the figure. Plotting the image onto an axes allows for subsequent - overlays of axes artists. - - Parameters - ---------- - image : array - image to plot - scale : float - If scale is 1, the figure and axes have the same dimension as the - image. Smaller values of `scale` will shrink the figure. - dpi : int - Dots per inch for figure. If None, use the default rcParam. - """ - dpi = dpi if dpi is not None else plt.rcParams['figure.dpi'] - kwargs.setdefault('interpolation', 'nearest') - kwargs.setdefault('cmap', 'gray') - - h, w, d = np.atleast_3d(image).shape - figsize = np.array((w, h), dtype=float) / dpi * scale - - fig, ax = plt.subplots(figsize=figsize, dpi=dpi) - fig.subplots_adjust(left=0, bottom=0, right=1, top=1) - - ax.set_axis_off() - ax.imshow(image, **kwargs) - return fig, ax - - class LinearColormap(LinearSegmentedColormap): """LinearSegmentedColormap in which color varies smoothly. @@ -124,14 +95,88 @@ class ClearColormap(LinearColormap): LinearColormap.__init__(self, name, cg_speq) -class MatplotlibCanvas(FigureCanvasQTAgg): +class FigureCanvas(FigureCanvasQTAgg): """Canvas for displaying images.""" - def __init__(self, parent, figure, **kwargs): + def __init__(self, figure, **kwargs): self.fig = figure FigureCanvasQTAgg.__init__(self, self.fig) FigureCanvasQTAgg.setSizePolicy(self, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) FigureCanvasQTAgg.updateGeometry(self) - # Note: `setParent` must be called after `FigureCanvasQTAgg.__init__`. - self.setParent(parent) + + #TODO: Consider overriding Matplotlib key-event handling + # def keyPressEvent(self, event): + # # Override key events defined by Matplotlib + # event.ignore() + + # def keyReleaseEvent(self, event): + # # Override key events defined by Matplotlib + # event.ignore() + + +def new_canvas(*args, **kwargs): + """Return a new figure canvas.""" + allnums = _pylab_helpers.Gcf.figs.keys() + num = max(allnums) + 1 if allnums else 1 + + FigureClass = kwargs.pop('FigureClass', Figure) + figure = FigureClass(*args, **kwargs) + canvas = FigureCanvas(figure) + fig_manager = FigureManagerQT(canvas, num) + return fig_manager.canvas + + +def new_plot(parent=None, subplot_kw=None, **fig_kw): + """Return new figure and axes. + + Parameters + ---------- + parent : QtWidget + Qt widget that displays the plot objects. If None, you must manually + call ``canvas.setParent`` and pass the parent widget. + subplot_kw : dict + Keyword arguments passed ``matplotlib.figure.Figure.add_subplot``. + fig_kw : dict + Keyword arguments passed ``matplotlib.figure.Figure``. + """ + if subplot_kw is None: + subplot_kw = {} + canvas = new_canvas(**fig_kw) + canvas.setParent(parent) + + fig = canvas.figure + ax = fig.add_subplot(1, 1, 1, **subplot_kw) + return fig, ax + + +def figimage(image, scale=1, dpi=None, **kwargs): + """Return figure and axes with figure tightly surrounding image. + + Unlike pyplot.figimage, this actually plots onto an axes object, which + fills the figure. Plotting the image onto an axes allows for subsequent + overlays of axes artists. + + Parameters + ---------- + image : array + image to plot + scale : float + If scale is 1, the figure and axes have the same dimension as the + image. Smaller values of `scale` will shrink the figure. + dpi : int + Dots per inch for figure. If None, use the default rcParam. + """ + dpi = dpi if dpi is not None else mpl.rcParams['figure.dpi'] + kwargs.setdefault('interpolation', 'nearest') + kwargs.setdefault('cmap', 'gray') + + h, w, d = np.atleast_3d(image).shape + figsize = np.array((w, h), dtype=float) / dpi * scale + + fig, ax = new_plot(figsize=figsize, dpi=dpi) + fig.subplots_adjust(left=0, bottom=0, right=1, top=1) + + ax.set_axis_off() + ax.imshow(image, **kwargs) + return fig, ax diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index e11967f7..f740eb81 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -16,13 +16,6 @@ from ..widgets import Slider __all__ = ['ImageViewer', 'CollectionViewer'] -class ImageCanvas(utils.MatplotlibCanvas): - """Canvas for displaying images.""" - def __init__(self, parent, image, **kwargs): - self.fig, self.ax = utils.figimage(image, **kwargs) - super(ImageCanvas, self).__init__(parent, self.fig, **kwargs) - - class ImageViewer(QMainWindow): """Viewer for displaying images. @@ -72,9 +65,10 @@ class ImageViewer(QMainWindow): self.main_widget = QtGui.QWidget() self.setCentralWidget(self.main_widget) - self.canvas = ImageCanvas(self.main_widget, image) - self.fig = self.canvas.fig - self.ax = self.canvas.ax + self.fig, self.ax = utils.figimage(image) + self.canvas = self.fig.canvas + self.canvas.setParent(self) + self.ax.autoscale(enable=False) self._image_plot = self.ax.images[0] @@ -274,6 +268,8 @@ class CollectionViewer(ImageViewer): if 48 <= key < 58: index = 0.1 * int(key - 48) * self.num_images self.update_index('', index) - event.accept() + event.accept() + else: + event.ignore() else: event.ignore()