From 86b428952dbfecb3623fb3daab8889f1340f1ad9 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 22 Jul 2012 13:24:41 -0400 Subject: [PATCH] ENH: Allow `Plugin.add_widget` to hook into Plugin attributes. The `ptype` parameter of widget can now be set to 'plugin'. When this is the case, the plugin will set a plugin attribute whenever the widget is updated. As an example, this commit adds a ComboBox widget which is hooked into the overlay color of the OverlayPlugin. --- skimage/viewer/plugins/base.py | 7 ++- skimage/viewer/plugins/canny.py | 3 +- skimage/viewer/plugins/overlayplugin.py | 30 ++++++++++-- skimage/viewer/utils/core.py | 7 +-- skimage/viewer/widgets/core.py | 65 ++++++++++++++++++++++++- 5 files changed, 99 insertions(+), 13 deletions(-) diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index 43ad4c0a..17387b8b 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -1,7 +1,6 @@ from PyQt4 import QtGui import matplotlib as mpl -from ..widgets import Slider class Plugin(QtGui.QDialog): @@ -43,6 +42,7 @@ class Plugin(QtGui.QDialog): if image_filter is not None: self.image_filter = image_filter + #TODO: Always passing image as first argument may be bad assumption. self.arguments = [image_viewer.original_image] self.keyword_arguments= {} @@ -86,9 +86,14 @@ class Plugin(QtGui.QDialog): elif widget.ptype == 'arg': self.arguments.append(widget) widget.callback = self.filter_image + elif widget.ptype == 'plugin': + widget.callback = self.update_plugin self.layout.addWidget(widget, self.row, 0) self.row += 1 + def update_plugin(self, name, value): + setattr(self, name, value) + def closeEvent(self, event): """Disconnect all artists and events from ImageViewer. diff --git a/skimage/viewer/plugins/canny.py b/skimage/viewer/plugins/canny.py index 175b749d..97465e6a 100644 --- a/skimage/viewer/plugins/canny.py +++ b/skimage/viewer/plugins/canny.py @@ -1,7 +1,7 @@ from skimage.filter import canny from .overlayplugin import OverlayPlugin -from ..widgets import Slider +from ..widgets import Slider, ComboBox class CannyPlugin(OverlayPlugin): @@ -16,6 +16,7 @@ class CannyPlugin(OverlayPlugin): self.add_widget(Slider('sigma', 0, 5, update_on='release')) self.add_widget(Slider('low threshold', 0, 255, update_on='release')) self.add_widget(Slider('high threshold', 0, 255, update_on='release')) + self.add_widget(ComboBox('color', self.color_names, ptype='plugin')) # Update image overlay to default slider values. self.filter_image() diff --git a/skimage/viewer/plugins/overlayplugin.py b/skimage/viewer/plugins/overlayplugin.py index b55d6587..e9a6a42a 100644 --- a/skimage/viewer/plugins/overlayplugin.py +++ b/skimage/viewer/plugins/overlayplugin.py @@ -1,6 +1,5 @@ -from ..utils import clear_red - from .base import Plugin +from ..utils import ClearColormap class OverlayPlugin(Plugin): @@ -12,12 +11,19 @@ class OverlayPlugin(Plugin): Overlay displayed on top of image. This overlay defaults to a color map with alpha values varying linearly from 0 to 1. """ + colors = {'red': (1, 0, 0), + 'yellow': (1, 1, 0), + 'green': (0, 1, 0), + 'cyan': (0, 1, 1)} def __init__(self, image_viewer, **kwargs): Plugin.__init__(self, image_viewer, **kwargs) - self.overlay_cmap = clear_red self._overlay_plot = None self._overlay = None + self.cmap = None + self.color_names = self.colors.keys() + #TODO: `color` doesn't update GUI widget when set manually. + self.color = 0 @property def overlay(self): @@ -31,7 +37,7 @@ class OverlayPlugin(Plugin): ax.images.remove(self._overlay_plot) self._overlay_plot = None elif self._overlay_plot is None: - self._overlay_plot = ax.imshow(image, cmap=self.overlay_cmap) + self._overlay_plot = ax.imshow(image, cmap=self.cmap) else: self._overlay_plot.set_array(image) self.image_viewer.redraw() @@ -39,3 +45,19 @@ class OverlayPlugin(Plugin): def closeEvent(self, event): self.overlay = None super(OverlayPlugin, self).closeEvent(event) + + @property + def color(self): + return self._color + + @color.setter + def color(self, index): + # Update colormap whenever color is changed. + name = self.color_names[index] + self._color = name + rgb = self.colors[name] + self.cmap = ClearColormap(rgb) + + if self._overlay_plot is not None: + self._overlay_plot.set_cmap(self.cmap) + self.image_viewer.redraw() diff --git a/skimage/viewer/utils/core.py b/skimage/viewer/utils/core.py index f7dc6433..6a29b07c 100644 --- a/skimage/viewer/utils/core.py +++ b/skimage/viewer/utils/core.py @@ -3,7 +3,7 @@ import matplotlib.pyplot as plt from matplotlib.colors import LinearSegmentedColormap -__all__ = ['figimage', 'LinearColormap', 'ClearColormap', 'clear_red'] +__all__ = ['figimage', 'LinearColormap', 'ClearColormap'] def figimage(image, scale=1, dpi=None, **kwargs): @@ -65,13 +65,10 @@ class LinearColormap(LinearSegmentedColormap): class ClearColormap(LinearColormap): """Color map that varies linearly from alpha = 0 to 1 """ - def __init__(self, name, rgb): + def __init__(self, rgb, name='clear_color'): 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)) - diff --git a/skimage/viewer/widgets/core.py b/skimage/viewer/widgets/core.py index 9184ccf6..02d29545 100644 --- a/skimage/viewer/widgets/core.py +++ b/skimage/viewer/widgets/core.py @@ -1,5 +1,21 @@ +""" +Widgets for interacting with ImageViewer. + +These widgets should be added to a Plugin subclass using its `add_widget` +method. The Plugin will delegate action based on the widget's parameter type +specified by its `ptype` attribute, which can be: + + 'arg' : positional argument passed to Plugin's `filter_image` method. + 'kwarg' : keyword argument passed to Plugin's `filter_image` method. + 'plugin' : attribute of Plugin. You'll probably need to make the attribute + a class property that updates the display. + +""" +from PyQt4 import QtGui +from PyQt4 import QtCore from skimage.io._plugins.q_color_mixer import IntelligentSlider + class Slider(IntelligentSlider): """Slider widget. @@ -12,11 +28,56 @@ class Slider(IntelligentSlider): name of the slider. low, high : float Range of slider values. - ptype : {'arg' | 'kwarg' | ...} - Parameter + ptype : {'arg' | 'kwarg' | 'plugin'} + Parameter type. """ def __init__(self, name, low, high, ptype='kwarg', callback=None, **kwargs): self.ptype = ptype kwargs.setdefault('orientation', 'horizontal') scale = (high - low) / 1000.0 super(Slider, self).__init__(name, scale, low, callback, **kwargs) + + +class ComboBox(QtGui.QWidget): + """ComboBox widget for selecting among a list of choices. + + Parameters + ---------- + name : str + Name of slider parameter. If this parameter is passed as a keyword + argument, it must match the name of that keyword argument (spaces are + replaced with underscores). In addition, this name is displayed as the + name of the slider. + items: list + Allowed parameter values. + ptype : {'arg' | 'kwarg' | 'plugin'} + Parameter type. + """ + + def __init__(self, name, items, ptype='kwarg', callback=None): + super(ComboBox, self).__init__() + self.ptype = ptype + self.callback = callback + + self.name = name + self.name_label = QtGui.QLabel() + self.name_label.setText(self.name) + self.name_label.setAlignment(QtCore.Qt.AlignLeft) + + self._combo_box = QtGui.QComboBox() + self._combo_box.addItems(items) + + self.layout = QtGui.QHBoxLayout(self) + self.layout.addWidget(self.name_label) + self.layout.addWidget(self._combo_box, alignment=QtCore.Qt.AlignLeft) + + self._combo_box.currentIndexChanged.connect(self._value_changed) + # self.connect(self._combo_box, + # SIGNAL("currentIndexChanged(int)"), self.updateUi) + + @property + def val(self): + return self._combo_box.value() + + def _value_changed(self, value): + self.callback(self.name, value)