mirror of
https://github.com/wassname/scikit-image.git
synced 2026-08-11 11:25:30 +08:00
ENH: Add image viewer based on Qt and Matplotlib
This commit is contained in:
@@ -0,0 +1 @@
|
||||
from viewers import ImageViewer
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
from core import *
|
||||
@@ -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))
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from .core import *
|
||||
@@ -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_())
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -0,0 +1,7 @@
|
||||
from skimage import data
|
||||
from skimage.viewer import ImageViewer
|
||||
|
||||
|
||||
image = data.camera()
|
||||
viewer = ImageViewer(image)
|
||||
viewer.show()
|
||||
Reference in New Issue
Block a user