ENH: Add image viewer based on Qt and Matplotlib

This commit is contained in:
Tony S Yu
2012-07-20 14:03:47 -05:00
parent 81764f693b
commit c27119b0cd
10 changed files with 281 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
from viewers import ImageViewer
View File
+70
View File
@@ -0,0 +1,70 @@
from PyQt4 import QtGui
from skimage.io._plugins.q_color_mixer import IntelligentSlider
class Plugin(QtGui.QDialog):
"""Base class for widgets that interact with the axes.
Parameters
----------
image_viewer : ImageViewer instance.
Window containing image used in measurement/manipulation.
useblit : bool
If True, use blitting to speed up animation. Only available on some
backends. If None, set to True when using Agg backend, otherwise False.
figure : :class:`~matplotlib.figure.Figure`
If None, create a figure with a single axes.
no_toolbar : bool
If True, figure created by plugin has no toolbar. This has no effect
on figures passed into `Plugin`.
Attributes
----------
viewer : ImageViewer
Window containing image used in measurement.
image : array
Image used in measurement/manipulation.
overlay : array
Image used in measurement/manipulation.
"""
def __init__(self, callback, parent=None, height=100, width=400):
self._viewer = parent
QtGui.QDialog.__init__(self, parent)
self.setWindowTitle('Image Plugin')
self.layout = QtGui.QGridLayout(self)
self.resize(width, height)
self.row = 0
self.callback = callback
self.arguments = [parent.original_image]
self.keyword_arguments= {}
self.overlay = self._viewer.overlay
self.image = self._viewer.image
def caller(self, *args):
arguments = [self._get_value(a) for a in self.arguments]
kwargs = dict([(name, self._get_value(a))
for name, a in self.keyword_arguments.iteritems()])
self.callback(*arguments, **kwargs)
def _get_value(self, param):
if hasattr(param, 'val'):
return param.val()
else:
return param
def add_argument(self, name, low, high, callback):
name, slider = self.add_slider(name, low, high, callback)
self.arguments[name] = slider
def add_keyword_argument(self, name, low, high, callback):
name, slider = self.add_slider(name, low, high, callback)
self.keyword_arguments[name] = slider
def add_slider(self, name, low, high, callback):
slider = IntelligentSlider(name, low, high, callback,
orientation='horizontal')
self.layout.addWidget(slider, self.row, 0)
self.row += 1
return name.replace(' ', '_'), slider
+20
View File
@@ -0,0 +1,20 @@
from .base import Plugin
from skimage.filter import canny
class CannyPlugin(Plugin):
def __init__(self, parent, *args, **kwargs):
height = kwargs.get('height', 100)
width = kwargs.get('width', 400)
super(CannyPlugin, self).__init__(self.callback, parent=parent,
width=width, height=height)
self.add_keyword_argument('sigma', 0.005, 0, self.caller)
self.add_keyword_argument('low_threshold', 0.255, 0, self.caller)
self.add_keyword_argument('high_threshold', 0.255, 0, self.caller)
# Call callback so that image is updated to slider values.
self.caller()
def callback(self, *args, **kwargs):
image = canny(*args, **kwargs)
self._viewer.overlay = image
+1
View File
@@ -0,0 +1 @@
from core import *
+75
View File
@@ -0,0 +1,75 @@
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
__all__ = ['figimage', 'LinearColormap', 'ClearColormap', 'clear_red']
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.
This class is a simplification of LinearSegmentedColormap, which doesn't
support jumps in color intensities.
Parameters
----------
name : str
Name of colormap.
segmented_data : dict
Dictionary of 'red', 'green', 'blue', and (optionally) 'alpha' values.
Each color key contains a list of `x`, `y` tuples. `x` must increase
monotonically from 0 to 1 and corresponds to input values for a mappable
object (e.g. an image). `y` corresponds to the color intensity.
"""
def __init__(self, name, segmented_data, **kwargs):
segmented_data = dict((key, [(x, y, y) for x, y in value])
for key, value in segmented_data.iteritems())
LinearSegmentedColormap.__init__(self, name, segmented_data, **kwargs)
class ClearColormap(LinearColormap):
def __init__(self, name, rgb):
r, g, b = rgb
cg_speq = {'blue': [(0.0, b), (1.0, b)],
'green': [(0.0, g), (1.0, g)],
'red': [(0.0, r), (1.0, r)],
'alpha': [(0.0, 0.0), (1.0, 1.0)]}
LinearColormap.__init__(self, name, cg_speq)
clear_red = ClearColormap('clear_red', (0.7, 0, 0))
+1
View File
@@ -0,0 +1 @@
from .core import *
+96
View File
@@ -0,0 +1,96 @@
import sys
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from skimage.viewer.utils import figimage, clear_red
qApp = None
class ImageCanvas(FigureCanvasQTAgg):
"""Canvas for displaying images.
This canvas derives from Matplotlib, so your normal
"""
def __init__(self, parent, image, **kwargs):
self.fig, self.ax = figimage(image, **kwargs)
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)
class ImageViewer(QtGui.QMainWindow):
def __init__(self, image):
# Start main loop
global qApp
if qApp is None:
qApp = QtGui.QApplication(sys.argv)
super(ImageViewer, self).__init__()
#TODO: Add ImageViewer to skimage.io window manager
self.overlay_cmap = clear_red
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setWindowTitle("Image Viewer")
self.file_menu = QtGui.QMenu('&File', self)
self.file_menu.addAction('&Quit', self.close,
QtCore.Qt.CTRL + QtCore.Qt.Key_Q)
self.menuBar().addMenu(self.file_menu)
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.layout = QtGui.QVBoxLayout(self.main_widget)
self.layout.addWidget(self.canvas)
#TODO: Add coordinate display
# self.statusBar().showMessage("coordinates")
self.original_image = image
self.image = image
self._overlay = None
@property
def image(self):
return self._img
@image.setter
def image(self, image):
self._img = image
self.ax.images[0].set_array(image)
self.canvas.draw_idle()
@property
def overlay(self):
return self._overlay
@overlay.setter
def overlay(self, image):
self._overlay = image
if len(self.ax.images) == 1:
self.ax.imshow(image, cmap=self.overlay_cmap)
else:
self.ax.images[1].set_array(image)
self.canvas.draw_idle()
def closeEvent(self, ce):
self.close()
def show(self):
super(ImageViewer, self).show()
sys.exit(qApp.exec_())
+10
View File
@@ -0,0 +1,10 @@
from skimage import data
from skimage.viewer import ImageViewer
from skimage.viewer.plugins.canny import CannyPlugin
image = data.camera()
viewer = ImageViewer(image)
p = CannyPlugin(viewer)
p.show()
viewer.show()
+7
View File
@@ -0,0 +1,7 @@
from skimage import data
from skimage.viewer import ImageViewer
image = data.camera()
viewer = ImageViewer(image)
viewer.show()