From 124e38751ca15137b1d896e3ab0d9bc646e945f8 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 28 May 2013 23:34:52 -0500 Subject: [PATCH 01/24] Fix `RequiredAttrs` definition. The example in python's descriptor tutorial creates a singleton so multiple, instances share the same attribute. This update fixes the issue based on [1]. [1] http://stackoverflow.com/questions/8718052/where-does-a-python-descriptors-state-go --- skimage/viewer/utils/core.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/skimage/viewer/utils/core.py b/skimage/viewer/utils/core.py index 0dcd4176..7ea28fab 100644 --- a/skimage/viewer/utils/core.py +++ b/skimage/viewer/utils/core.py @@ -42,17 +42,22 @@ def start_qtapp(): class RequiredAttr(object): """A class attribute that must be set before use.""" - def __init__(self, msg): + instances = dict() + + def __init__(self, msg='Required attribute not set', init_val=None): + self.instances[self, None] = init_val self.msg = msg - self.val = None def __get__(self, obj, objtype): - if self.val is None: + value = self.instances[self, obj] + if value is None: + # Should raise an error but that causes issues with the buildbot. warnings.warn(self.msg) - return self.val + self.__set__(obj, self.init_val) + return value - def __set__(self, obj, val): - self.val = val + def __set__(self, obj, value): + self.instances[self, obj] = value class LinearColormap(LinearSegmentedColormap): From e7ca4b6138443c0f52bda5485c24ed0355e36bbf Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 28 May 2013 23:51:53 -0500 Subject: [PATCH 02/24] Fix parameter name in docstring --- skimage/viewer/widgets/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/widgets/core.py b/skimage/viewer/widgets/core.py index ad69bfcf..0000bebb 100644 --- a/skimage/viewer/widgets/core.py +++ b/skimage/viewer/widgets/core.py @@ -81,7 +81,7 @@ class Slider(BaseWidget): Range of slider values. value : float Default slider value. If None, use midpoint between `low` and `high`. - value : {'float' | 'int'} + value_type : {'float' | 'int'} Numeric type of slider value. ptype : {'arg' | 'kwarg' | 'plugin'} Parameter type. From bd860b7720560f9642cc95200b3158a4c70783c3 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 28 May 2013 23:52:10 -0500 Subject: [PATCH 03/24] Add infrastructure for conneting plugin output to a viewer. --- skimage/viewer/plugins/base.py | 21 +++++++++++++++++++-- skimage/viewer/viewers/core.py | 33 +++++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index aa6b585a..ab18d257 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -1,9 +1,12 @@ """ Base class for Plugins that interact with ImageViewer. """ -from ..qt import QtGui -from ..qt.QtCore import Qt +from warnings import warn +import numpy as np + +from ..qt import QtGui +from ..qt.QtCore import Qt, pyqtSignal from ..utils import RequiredAttr, init_qtapp @@ -71,6 +74,10 @@ class Plugin(QtGui.QDialog): name = 'Plugin' image_viewer = RequiredAttr("%s is not attached to ImageViewer" % name) + # Signals used when viewers are linked to the Plugin output. + image_updated = pyqtSignal(np.ndarray) + _started = pyqtSignal() + def __init__(self, image_filter=None, height=0, width=400, useblit=True): init_qtapp() super(Plugin, self).__init__() @@ -79,6 +86,9 @@ class Plugin(QtGui.QDialog): # If subclass defines `image_filter` method ignore input. if not hasattr(self, 'image_filter'): self.image_filter = image_filter + elif image_filter is not None: + warn("If the Plugin class defines an `image_filter` method, " + "then the `image_filter` argument is ignored.") self.setWindowTitle(self.name) self.layout = QtGui.QGridLayout(self) @@ -155,7 +165,9 @@ class Plugin(QtGui.QDialog): kwargs = dict([(name, self._get_value(a)) for name, a in self.keyword_arguments.iteritems()]) filtered = self.image_filter(*arguments, **kwargs) + self.display_filtered_image(filtered) + self.image_updated.emit(filtered) def _get_value(self, param): # If param is a widget, return its `val` attribute. @@ -183,6 +195,11 @@ class Plugin(QtGui.QDialog): """ setattr(self, name, value) + def show(self, main_window=True): + """Show plugin.""" + super(Plugin, self).show() + self._started.emit() + def closeEvent(self, event): """On close disconnect all artists and events from ImageViewer. diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 9d6261c6..209e6b42 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -11,6 +11,7 @@ import numpy as np from .. import utils from ..widgets import Slider from ..utils import dialogs +from ..plugins.base import Plugin __all__ = ['ImageViewer', 'CollectionViewer'] @@ -81,6 +82,13 @@ class ImageViewer(QtGui.QMainWindow): self.main_widget = QtGui.QWidget() self.setCentralWidget(self.main_widget) + if isinstance(image, Plugin): + plugin = image + image = plugin.filtered_image + plugin.image_updated.connect(self._new_original_image) + # When plugin is started, start + plugin._started.connect(self._show) + self.fig, self.ax = utils.figimage(image) self.canvas = self.fig.canvas self.canvas.setParent(self) @@ -88,9 +96,7 @@ class ImageViewer(QtGui.QMainWindow): self.ax.autoscale(enable=False) self._image_plot = self.ax.images[0] - - self.original_image = image - self.image = image.copy() + self._new_original_image(image) self.plugins = [] self.layout = QtGui.QVBoxLayout(self.main_widget) @@ -115,8 +121,11 @@ class ImageViewer(QtGui.QMainWindow): if filename is None: return image = io.imread(filename) + self._new_original_image(image) + + def _new_original_image(self, image): self.original_image = image # update saved image - self.image = image # update displayed image + self.image = image.copy() # update displayed image def save_to_file(self): """Save current image to file. @@ -160,16 +169,20 @@ class ImageViewer(QtGui.QMainWindow): p.move(w, y) y += p.geometry().height() - def show(self): - """Show ImageViewer and attached plugins. - - This behaves much like `matplotlib.pyplot.show` and `QWidget.show`. - """ + def _show(self): self.auto_layout() for p in self.plugins: p.show() super(ImageViewer, self).show() - utils.start_qtapp() + + def show(self, main_window=True): + """Show ImageViewer and attached plugins. + + This behaves much like `matplotlib.pyplot.show` and `QWidget.show`. + """ + self._show() + if main_window: + utils.start_qtapp() def redraw(self): self.canvas.draw_idle() From 177a1fdb394eee4a41a3667b0f138a1f2d8b59ca Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 28 May 2013 23:52:48 -0500 Subject: [PATCH 04/24] Add example of connected viewers/plugins --- .../plugins/probabilistic_hough.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 viewer_examples/plugins/probabilistic_hough.py diff --git a/viewer_examples/plugins/probabilistic_hough.py b/viewer_examples/plugins/probabilistic_hough.py new file mode 100644 index 00000000..027136f9 --- /dev/null +++ b/viewer_examples/plugins/probabilistic_hough.py @@ -0,0 +1,45 @@ +import numpy as np + +from skimage import data +from skimage import draw +from skimage.transform import probabilistic_hough_line + +from skimage.viewer import ImageViewer +from skimage.viewer.widgets import Slider +from skimage.viewer.plugins.overlayplugin import OverlayPlugin +from skimage.viewer.plugins.canny import CannyPlugin + + +def line_image(shape, lines): + image = np.zeros(shape, dtype=bool) + for end_points in lines: + # hough lines returns (x, y) points, draw.line wants (row, columns) + end_points = np.asarray(end_points)[:, ::-1] + image[draw.line(*np.ravel(end_points))] = 1 + return image + + +def hough_lines(image, *args, **kwargs): + # Set threshold to 0.5 since we're working with a binary image (from canny) + lines = probabilistic_hough_line(image, threshold=0.5, *args, **kwargs) + image = line_image(image.shape, lines) + return image + + +image = data.camera() +canny_viewer = ImageViewer(image) +canny_plugin = CannyPlugin() +canny_viewer += canny_plugin + +hough_plugin = OverlayPlugin(image_filter=hough_lines) + +hough_plugin += Slider('line length', 0, 100, update_on='release') +hough_plugin += Slider('line gap', 0, 20, update_on='release') + +# Passing a plugin to a viewer connects the output of the plugin to the viewer. +hough_viewer = ImageViewer(canny_plugin) +hough_viewer += hough_plugin + +# Show viewers displays both viewers since `hough_viewer` is connected to +# `canny_viewer` through `canny_plugin` +canny_viewer.show() From 07630a93e6216d63915d4d61c842d7fd2e0433a3 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 30 May 2013 22:21:21 -0500 Subject: [PATCH 05/24] Dock plugins to image viewer. --- skimage/viewer/plugins/base.py | 5 +- skimage/viewer/viewers/core.py | 57 +++++++++++++++++----- viewer_examples/plugins/color_histogram.py | 2 +- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index ab18d257..d4873377 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -78,10 +78,13 @@ class Plugin(QtGui.QDialog): image_updated = pyqtSignal(np.ndarray) _started = pyqtSignal() - def __init__(self, image_filter=None, height=0, width=400, useblit=True): + def __init__(self, image_filter=None, height=0, width=400, useblit=True, + dock='bottom'): init_qtapp() super(Plugin, self).__init__() + self.dock = dock + self.image_viewer = None # If subclass defines `image_filter` method ignore input. if not hasattr(self, 'image_filter'): diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 209e6b42..f49d4b1a 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -2,7 +2,7 @@ ImageViewer class for viewing and interacting with images. """ from ..qt import QtGui -from ..qt import QtCore +from ..qt.QtCore import Qt from skimage import io, img_as_float from skimage.util.dtype import dtype_range @@ -17,6 +17,12 @@ from ..plugins.base import Plugin __all__ = ['ImageViewer', 'CollectionViewer'] +dock_areas = {'top': Qt.TopDockWidgetArea, + 'bottom': Qt.BottomDockWidgetArea, + 'left': Qt.LeftDockWidgetArea, + 'right': Qt.RightDockWidgetArea} + + def mpl_image_to_rgba(mpl_image): """Return RGB image from the given matplotlib image object. @@ -67,16 +73,16 @@ class ImageViewer(QtGui.QMainWindow): #TODO: Add ImageViewer to skimage.io window manager - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle("Image Viewer") self.file_menu = QtGui.QMenu('&File', self) self.file_menu.addAction('Open file', self.open_file, - QtCore.Qt.CTRL + QtCore.Qt.Key_O) + Qt.CTRL + Qt.Key_O) self.file_menu.addAction('Save to file', self.save_to_file, - QtCore.Qt.CTRL + QtCore.Qt.Key_S) + Qt.CTRL + Qt.Key_S) self.file_menu.addAction('Quit', self.close, - QtCore.Qt.CTRL + QtCore.Qt.Key_Q) + Qt.CTRL + Qt.Key_Q) self.menuBar().addMenu(self.file_menu) self.main_widget = QtGui.QWidget() @@ -113,8 +119,33 @@ class ImageViewer(QtGui.QMainWindow): def __add__(self, plugin): """Add plugin to ImageViewer""" plugin.attach(self) + if plugin.dock: + location = dock_areas[plugin.dock] + dock_location = Qt.DockWidgetArea(location) + dock = QtGui.QDockWidget() + dock.setWidget(plugin) + self.addDockWidget(dock_location, dock) + + horiz = (dock_areas['left'], dock_areas['right']) + dimension = 'width' if location in horiz else 'height' + self._add_widget_size(plugin, dimension=dimension) + return self + def _add_widget_size(self, widget, dimension='width'): + widget_size = widget.sizeHint() + viewer_size = self.frameGeometry() + + dx = dy = 0 + if dimension == 'width': + dx = widget_size.width() + elif dimension == 'height': + dy = widget_size.height() + + w = viewer_size.width() + h = viewer_size.height() + self.resize(w + dx, h + dy) + def open_file(self): """Open image file and display in viewer.""" filename = dialogs.open_file_dialog() @@ -160,14 +191,16 @@ class ImageViewer(QtGui.QMainWindow): def auto_layout(self): """Move viewer to top-left and align plugin on right edge of viewer.""" - size = self.geometry() + size = self.frameGeometry() self.move(0, 0) - w = size.width() - y = 0 - #TODO: Layout isn't quite correct for multiple plugins (overlaps). - for p in self.plugins: - p.move(w, y) - y += p.geometry().height() + # w = size.width() + # h = size.height() + # x = 0 + # y = h + # #TODO: Layout isn't quite correct for multiple plugins (overlaps). + # for p in self.plugins: + # p.move(x, y) + # y += p.frameGeometry().height() def _show(self): self.auto_layout() diff --git a/viewer_examples/plugins/color_histogram.py b/viewer_examples/plugins/color_histogram.py index 0b654b3e..6b091d69 100644 --- a/viewer_examples/plugins/color_histogram.py +++ b/viewer_examples/plugins/color_histogram.py @@ -5,5 +5,5 @@ from skimage import data image = data.load('color.png') viewer = ImageViewer(image) -viewer += ColorHistogram() +viewer += ColorHistogram(dock='right') viewer.show() From e373e13f03350ccce888d95a9b686cd651050f5a Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 30 May 2013 22:21:55 -0500 Subject: [PATCH 06/24] Fix sizing of PlotPlugin --- skimage/viewer/plugins/plotplugin.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/skimage/viewer/plugins/plotplugin.py b/skimage/viewer/plugins/plotplugin.py index c120756c..0ce5df73 100644 --- a/skimage/viewer/plugins/plotplugin.py +++ b/skimage/viewer/plugins/plotplugin.py @@ -17,6 +17,13 @@ class PlotPlugin(Plugin): See base Plugin class for additional details. """ + def __init__(self, image_filter=None, height=150, width=400, **kwargs): + super(PlotPlugin, self).__init__(image_filter=image_filter, + height=height, width=width, **kwargs) + + self._height = height + self._width = width + def attach(self, image_viewer): super(PlotPlugin, self).attach(image_viewer) # Add plot for displaying intensity profile. @@ -26,10 +33,12 @@ class PlotPlugin(Plugin): """Redraw plot.""" self.canvas.draw_idle() - def add_plot(self, height=4, width=4): - self.fig, self.ax = new_plot(figsize=(height, width)) + def add_plot(self): + self.fig, self.ax = new_plot() + self.fig.set_figwidth(self._width / float(self.fig.dpi)) + self.fig.set_figheight(self._height / float(self.fig.dpi)) + 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) From 16b6411059fcb1c92b20db51c500ea698052d8da Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 30 May 2013 22:25:02 -0500 Subject: [PATCH 07/24] Display title in docked plugins --- skimage/viewer/viewers/core.py | 1 + viewer_examples/plugins/probabilistic_hough.py | 1 + 2 files changed, 2 insertions(+) diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index f49d4b1a..b4ad4c4d 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -124,6 +124,7 @@ class ImageViewer(QtGui.QMainWindow): dock_location = Qt.DockWidgetArea(location) dock = QtGui.QDockWidget() dock.setWidget(plugin) + dock.setWindowTitle(plugin.name) self.addDockWidget(dock_location, dock) horiz = (dock_areas['left'], dock_areas['right']) diff --git a/viewer_examples/plugins/probabilistic_hough.py b/viewer_examples/plugins/probabilistic_hough.py index 027136f9..5564ba9f 100644 --- a/viewer_examples/plugins/probabilistic_hough.py +++ b/viewer_examples/plugins/probabilistic_hough.py @@ -32,6 +32,7 @@ canny_plugin = CannyPlugin() canny_viewer += canny_plugin hough_plugin = OverlayPlugin(image_filter=hough_lines) +hough_plugin.name = 'Hough Lines' hough_plugin += Slider('line length', 0, 100, update_on='release') hough_plugin += Slider('line gap', 0, 20, update_on='release') From 55386ec785f960bc49778e5c45ca830398efe736 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 30 May 2013 22:37:36 -0500 Subject: [PATCH 08/24] Add smart window-layout for multi-viewer display --- skimage/viewer/plugins/base.py | 6 ++++-- skimage/viewer/viewers/core.py | 17 ++--------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index d4873377..85d75b95 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -76,7 +76,7 @@ class Plugin(QtGui.QDialog): # Signals used when viewers are linked to the Plugin output. image_updated = pyqtSignal(np.ndarray) - _started = pyqtSignal() + _started = pyqtSignal(int) def __init__(self, image_filter=None, height=0, width=400, useblit=True, dock='bottom'): @@ -201,7 +201,9 @@ class Plugin(QtGui.QDialog): def show(self, main_window=True): """Show plugin.""" super(Plugin, self).show() - self._started.emit() + size = self.frameGeometry() + x_hint = size.x() + size.width() + self._started.emit(x_hint) def closeEvent(self, event): """On close disconnect all artists and events from ImageViewer. diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index b4ad4c4d..d60e5693 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -190,21 +190,8 @@ class ImageViewer(QtGui.QMainWindow): def closeEvent(self, event): self.close() - def auto_layout(self): - """Move viewer to top-left and align plugin on right edge of viewer.""" - size = self.frameGeometry() - self.move(0, 0) - # w = size.width() - # h = size.height() - # x = 0 - # y = h - # #TODO: Layout isn't quite correct for multiple plugins (overlaps). - # for p in self.plugins: - # p.move(x, y) - # y += p.frameGeometry().height() - - def _show(self): - self.auto_layout() + def _show(self, x=0): + self.move(x, 0) for p in self.plugins: p.show() super(ImageViewer, self).show() From 3e3ead7d9eb05c2fd713d83510cf08b46cf21f15 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 30 May 2013 23:14:30 -0500 Subject: [PATCH 09/24] Add mock pyqtSignal to try to get Travis to build --- skimage/viewer/qt/QtCore.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/viewer/qt/QtCore.py b/skimage/viewer/qt/QtCore.py index 897b8760..e0def041 100644 --- a/skimage/viewer/qt/QtCore.py +++ b/skimage/viewer/qt/QtCore.py @@ -7,3 +7,4 @@ elif qt_api == 'pyqt': else: # Mock objects Qt = None + pyqtSignal = None From 86c2c1a37c18e260bee8e399f67b997b53acedad Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 30 May 2013 23:38:26 -0500 Subject: [PATCH 10/24] Second attempt to get Travis to pass --- skimage/viewer/qt/QtCore.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skimage/viewer/qt/QtCore.py b/skimage/viewer/qt/QtCore.py index e0def041..e0aa923a 100644 --- a/skimage/viewer/qt/QtCore.py +++ b/skimage/viewer/qt/QtCore.py @@ -7,4 +7,5 @@ elif qt_api == 'pyqt': else: # Mock objects Qt = None - pyqtSignal = None + def pyqtSignal(*args, **kwargs): + pass From 9393eac1b0a4808391de4a792cb68252fb50329d Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Fri, 31 May 2013 00:06:59 -0500 Subject: [PATCH 11/24] Third attempt at getting Travis to build --- skimage/viewer/qt/QtCore.py | 2 -- skimage/viewer/viewers/core.py | 16 ++++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/skimage/viewer/qt/QtCore.py b/skimage/viewer/qt/QtCore.py index e0aa923a..897b8760 100644 --- a/skimage/viewer/qt/QtCore.py +++ b/skimage/viewer/qt/QtCore.py @@ -7,5 +7,3 @@ elif qt_api == 'pyqt': else: # Mock objects Qt = None - def pyqtSignal(*args, **kwargs): - pass diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index d60e5693..91362f47 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -17,12 +17,6 @@ from ..plugins.base import Plugin __all__ = ['ImageViewer', 'CollectionViewer'] -dock_areas = {'top': Qt.TopDockWidgetArea, - 'bottom': Qt.BottomDockWidgetArea, - 'left': Qt.LeftDockWidgetArea, - 'right': Qt.RightDockWidgetArea} - - def mpl_image_to_rgba(mpl_image): """Return RGB image from the given matplotlib image object. @@ -66,6 +60,12 @@ class ImageViewer(QtGui.QMainWindow): >>> # viewer.show() """ + + dock_areas = {'top': Qt.TopDockWidgetArea, + 'bottom': Qt.BottomDockWidgetArea, + 'left': Qt.LeftDockWidgetArea, + 'right': Qt.RightDockWidgetArea} + def __init__(self, image): # Start main loop utils.init_qtapp() @@ -120,14 +120,14 @@ class ImageViewer(QtGui.QMainWindow): """Add plugin to ImageViewer""" plugin.attach(self) if plugin.dock: - location = dock_areas[plugin.dock] + location = self.dock_areas[plugin.dock] dock_location = Qt.DockWidgetArea(location) dock = QtGui.QDockWidget() dock.setWidget(plugin) dock.setWindowTitle(plugin.name) self.addDockWidget(dock_location, dock) - horiz = (dock_areas['left'], dock_areas['right']) + horiz = (self.dock_areas['left'], self.dock_areas['right']) dimension = 'width' if location in horiz else 'height' self._add_widget_size(plugin, dimension=dimension) From 87afe3e175f06cba74d27c2be300b382ebdb6d04 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Fri, 31 May 2013 00:14:25 -0500 Subject: [PATCH 12/24] Accidental deletion --- skimage/viewer/qt/QtCore.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/skimage/viewer/qt/QtCore.py b/skimage/viewer/qt/QtCore.py index 897b8760..e0aa923a 100644 --- a/skimage/viewer/qt/QtCore.py +++ b/skimage/viewer/qt/QtCore.py @@ -7,3 +7,5 @@ elif qt_api == 'pyqt': else: # Mock objects Qt = None + def pyqtSignal(*args, **kwargs): + pass From 484e5693b2f3e0bc8c238cd64afeaad17bfa6673 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Fri, 31 May 2013 08:15:26 -0500 Subject: [PATCH 13/24] Add attributes to Mock object to fix Travis build --- skimage/viewer/qt/QtCore.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/skimage/viewer/qt/QtCore.py b/skimage/viewer/qt/QtCore.py index e0aa923a..60c5566e 100644 --- a/skimage/viewer/qt/QtCore.py +++ b/skimage/viewer/qt/QtCore.py @@ -5,7 +5,12 @@ if qt_api == 'pyside': elif qt_api == 'pyqt': from PyQt4.QtCore import * else: - # Mock objects - Qt = None + # Mock objects for buildbot (which doesn't have Qt, but imports viewer). + class Qt(object): + TopDockWidgetArea = None + BottomDockWidgetArea = None + LeftDockWidgetArea = None + RightDockWidgetArea = None + def pyqtSignal(*args, **kwargs): pass From 2ca77c42bed2a059aea5e04c207c48c2568b4fc0 Mon Sep 17 00:00:00 2001 From: tonysyu Date: Wed, 5 Jun 2013 11:22:24 -0500 Subject: [PATCH 14/24] Fix PySide compatibility for signal defs --- skimage/viewer/plugins/base.py | 6 +++--- skimage/viewer/qt/QtCore.py | 8 +++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index 85d75b95..f5fbf935 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -6,7 +6,7 @@ from warnings import warn import numpy as np from ..qt import QtGui -from ..qt.QtCore import Qt, pyqtSignal +from ..qt.QtCore import Qt, Signal from ..utils import RequiredAttr, init_qtapp @@ -75,8 +75,8 @@ class Plugin(QtGui.QDialog): image_viewer = RequiredAttr("%s is not attached to ImageViewer" % name) # Signals used when viewers are linked to the Plugin output. - image_updated = pyqtSignal(np.ndarray) - _started = pyqtSignal(int) + image_updated = Signal(np.ndarray) + _started = Signal(int) def __init__(self, image_filter=None, height=0, width=400, useblit=True, dock='bottom'): diff --git a/skimage/viewer/qt/QtCore.py b/skimage/viewer/qt/QtCore.py index 60c5566e..19b92a53 100644 --- a/skimage/viewer/qt/QtCore.py +++ b/skimage/viewer/qt/QtCore.py @@ -4,6 +4,9 @@ if qt_api == 'pyside': from PySide.QtCore import * elif qt_api == 'pyqt': from PyQt4.QtCore import * + # Use pyside names for signals and slots + Signal = pyqtSignal + Slot = pyqtSlot else: # Mock objects for buildbot (which doesn't have Qt, but imports viewer). class Qt(object): @@ -12,5 +15,8 @@ else: LeftDockWidgetArea = None RightDockWidgetArea = None - def pyqtSignal(*args, **kwargs): + def Signal(*args, **kwargs): + pass + + def Slot(*args, **kwargs): pass From cc2f1854b5d1c39ec6b899d902dcf8a762bd814f Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 8 Jun 2013 19:06:23 -0500 Subject: [PATCH 15/24] Fix plugin interaction with CollectionViewer * Signal updates to original image when image changed in CollectionViewer. * Update plugin arguments for the filter. * Also fixes image updates when opening a new image from the file menu. --- skimage/viewer/plugins/base.py | 16 +++++++++++++--- skimage/viewer/viewers/core.py | 18 ++++++++++++------ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index f5fbf935..1d655d61 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -75,7 +75,7 @@ class Plugin(QtGui.QDialog): image_viewer = RequiredAttr("%s is not attached to ImageViewer" % name) # Signals used when viewers are linked to the Plugin output. - image_updated = Signal(np.ndarray) + image_changed = Signal(np.ndarray) _started = Signal(int) def __init__(self, image_filter=None, height=0, width=400, useblit=True, @@ -122,7 +122,7 @@ class Plugin(QtGui.QDialog): self.image_viewer = image_viewer self.image_viewer.plugins.append(self) #TODO: Always passing image as first argument may be bad assumption. - self.arguments.append(self.image_viewer.original_image) + self.arguments = [self.image_viewer.original_image] # Call filter so that filtered image matches widget values self.filter_image() @@ -170,12 +170,20 @@ class Plugin(QtGui.QDialog): filtered = self.image_filter(*arguments, **kwargs) self.display_filtered_image(filtered) - self.image_updated.emit(filtered) + self.image_changed.emit(filtered) def _get_value(self, param): # If param is a widget, return its `val` attribute. return param if not hasattr(param, 'val') else param.val + def _update_original_image(self, image): + """Update the original image argument passed to the filter function. + + This method is called by the viewer when the original image is updated. + """ + self.arguments[0] = image + self.filter_image() + @property def filtered_image(self): """Return filtered image.""" @@ -201,6 +209,8 @@ class Plugin(QtGui.QDialog): def show(self, main_window=True): """Show plugin.""" super(Plugin, self).show() + + # Emit signal with x-hint so new windows can be displayed w/o overlap. size = self.frameGeometry() x_hint = size.x() + size.width() self._started.emit(x_hint) diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 91362f47..a054da13 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -2,7 +2,7 @@ ImageViewer class for viewing and interacting with images. """ from ..qt import QtGui -from ..qt.QtCore import Qt +from ..qt.QtCore import Qt, Signal from skimage import io, img_as_float from skimage.util.dtype import dtype_range @@ -66,6 +66,9 @@ class ImageViewer(QtGui.QMainWindow): 'left': Qt.LeftDockWidgetArea, 'right': Qt.RightDockWidgetArea} + # Signal that the original image has been changed + original_image_changed = Signal(np.ndarray) + def __init__(self, image): # Start main loop utils.init_qtapp() @@ -91,7 +94,7 @@ class ImageViewer(QtGui.QMainWindow): if isinstance(image, Plugin): plugin = image image = plugin.filtered_image - plugin.image_updated.connect(self._new_original_image) + plugin.image_changed.connect(self._update_original_image) # When plugin is started, start plugin._started.connect(self._show) @@ -102,7 +105,7 @@ class ImageViewer(QtGui.QMainWindow): self.ax.autoscale(enable=False) self._image_plot = self.ax.images[0] - self._new_original_image(image) + self._update_original_image(image) self.plugins = [] self.layout = QtGui.QVBoxLayout(self.main_widget) @@ -119,6 +122,8 @@ class ImageViewer(QtGui.QMainWindow): def __add__(self, plugin): """Add plugin to ImageViewer""" plugin.attach(self) + self.original_image_changed.connect(plugin._update_original_image) + if plugin.dock: location = self.dock_areas[plugin.dock] dock_location = Qt.DockWidgetArea(location) @@ -153,11 +158,12 @@ class ImageViewer(QtGui.QMainWindow): if filename is None: return image = io.imread(filename) - self._new_original_image(image) + self._update_original_image(image) - def _new_original_image(self, image): + def _update_original_image(self, image): self.original_image = image # update saved image self.image = image.copy() # update displayed image + self.original_image_changed.emit(image) def save_to_file(self): """Save current image to file. @@ -330,7 +336,7 @@ class CollectionViewer(ImageViewer): This method can be overridden or extended in subclasses and plugins to react to image changes. """ - self.image = image + self._update_original_image(image) def keyPressEvent(self, event): if type(event) == QtGui.QKeyEvent: From a6293fd84b1b393f5a2ed00f07131dc13371554b Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 8 Jun 2013 19:11:33 -0500 Subject: [PATCH 16/24] Add example of connecting plugins to CollectionViewer --- viewer_examples/plugins/collection_plugin.py | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 viewer_examples/plugins/collection_plugin.py diff --git a/viewer_examples/plugins/collection_plugin.py b/viewer_examples/plugins/collection_plugin.py new file mode 100644 index 00000000..f29b0132 --- /dev/null +++ b/viewer_examples/plugins/collection_plugin.py @@ -0,0 +1,33 @@ +""" +================= +Collection plugin +================= + +Demo of a CollectionViewer for viewing collections of images with the +`autolevel` rank filter connected as a plugin. + +""" +from skimage import data +from skimage.filter import rank +from skimage.morphology import disk + +from skimage.viewer import CollectionViewer +from skimage.viewer.widgets import Slider +from skimage.viewer.plugins.base import Plugin + + +# Wrap autolevel function to make the disk size a filter argument. +def autolevel(image, disk_size): + return rank.autolevel(image, disk(disk_size)) + + +img_collection = [data.camera(), data.coins(), data.text()] + +plugin = Plugin(image_filter=autolevel) +plugin += Slider('disk_size', 2, 8, value_type='int', update_on='release') +plugin.name = "Autolevel" + +viewer = CollectionViewer(img_collection) +viewer += plugin + +viewer.show() From afd1b1b835ff09e716643f5608103c61a3251104 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 8 Jun 2013 19:24:50 -0500 Subject: [PATCH 17/24] Fix display of overlay plugin when original image is updated --- skimage/viewer/plugins/overlayplugin.py | 5 +++-- skimage/viewer/utils/core.py | 22 ++++++++++++++++++- skimage/viewer/viewers/core.py | 7 ++---- viewer_examples/plugins/collection_overlay.py | 21 ++++++++++++++++++ viewer_examples/plugins/collection_plugin.py | 6 ++--- 5 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 viewer_examples/plugins/collection_overlay.py diff --git a/skimage/viewer/plugins/overlayplugin.py b/skimage/viewer/plugins/overlayplugin.py index 278f072c..25ea3b00 100644 --- a/skimage/viewer/plugins/overlayplugin.py +++ b/skimage/viewer/plugins/overlayplugin.py @@ -2,7 +2,7 @@ from warnings import warn from skimage.util.dtype import dtype_range from .base import Plugin -from ..utils import ClearColormap +from ..utils import ClearColormap, update_axes_image __all__ = ['OverlayPlugin'] @@ -66,7 +66,8 @@ class OverlayPlugin(Plugin): self._overlay_plot = ax.imshow(image, cmap=self.cmap, vmin=vmin, vmax=vmax) else: - self._overlay_plot.set_array(image) + update_axes_image(self._overlay_plot, image) + self.image_viewer.redraw() @property diff --git a/skimage/viewer/utils/core.py b/skimage/viewer/utils/core.py index 7ea28fab..826147eb 100644 --- a/skimage/viewer/utils/core.py +++ b/skimage/viewer/utils/core.py @@ -18,7 +18,8 @@ from ..qt import QtGui __all__ = ['init_qtapp', 'start_qtapp', 'RequiredAttr', 'figimage', - 'LinearColormap', 'ClearColormap', 'FigureCanvas', 'new_plot'] + 'LinearColormap', 'ClearColormap', 'FigureCanvas', 'new_plot', + 'update_axes_image'] QApp = None @@ -179,3 +180,22 @@ def figimage(image, scale=1, dpi=None, **kwargs): ax.set_axis_off() ax.imshow(image, **kwargs) return fig, ax + + +def update_axes_image(image_axes, image): + """Update the image displayed by an image plot. + + This sets the image plot's array and updates its shape appropriately + + Parameters + ---------- + image_axes : `matplotlib.image.AxesImage` + Image axes to update. + image : array + Image array. + """ + image_axes.set_array(image) + + # Adjust size if new image shape doesn't match the original + h, w = image.shape[:2] + image_axes.set_extent((0, w, h, 0)) diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index a054da13..874dc6ba 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -221,13 +221,10 @@ class ImageViewer(QtGui.QMainWindow): @image.setter def image(self, image): self._img = image - self._image_plot.set_array(image) + utils.update_axes_image(self._image_plot, image) - # Adjust size if new image shape doesn't match the original - h, w = image.shape[:2] - # update data coordinates (otherwise pixel coordinates are off) - self._image_plot.set_extent((0, w, h, 0)) # update display (otherwise image doesn't fill the canvas) + h, w = image.shape[:2] self.ax.set_xlim(0, w) self.ax.set_ylim(h, 0) diff --git a/viewer_examples/plugins/collection_overlay.py b/viewer_examples/plugins/collection_overlay.py new file mode 100644 index 00000000..4c7002f2 --- /dev/null +++ b/viewer_examples/plugins/collection_overlay.py @@ -0,0 +1,21 @@ +""" +============================================== +``CollectionViewer`` with an ``OverlayPlugin`` +============================================== + +Demo of a CollectionViewer for viewing collections of images with an +overlay plugin. + +""" +from skimage import data + +from skimage.viewer import CollectionViewer +from skimage.viewer.plugins.canny import CannyPlugin + + +img_collection = [data.camera(), data.coins(), data.text()] + +viewer = CollectionViewer(img_collection) +viewer += CannyPlugin() + +viewer.show() diff --git a/viewer_examples/plugins/collection_plugin.py b/viewer_examples/plugins/collection_plugin.py index f29b0132..80c7ff45 100644 --- a/viewer_examples/plugins/collection_plugin.py +++ b/viewer_examples/plugins/collection_plugin.py @@ -1,7 +1,7 @@ """ -================= -Collection plugin -================= +================================== +``CollectionViewer`` with a plugin +================================== Demo of a CollectionViewer for viewing collections of images with the `autolevel` rank filter connected as a plugin. From 0a8ff1b3641a5cb24ce5885c2b86c8eb667fa9ad Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 9 Jun 2013 05:38:08 -0500 Subject: [PATCH 18/24] Fix docstring for CollectionViewer slider. --- skimage/viewer/viewers/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 874dc6ba..3a5f8ad3 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -285,7 +285,7 @@ class CollectionViewer(ImageViewer): ---------- image_collection : list of images List of images to be displayed. - update_on : {'on_slide' | 'on_release'} + update_on : {'move' | 'release'} Control whether image is updated on slide or release of the image slider. Using 'on_release' will give smoother behavior when displaying large images or when writing a plugin/subclass that requires heavy From c4299c46374995f1dbe31faa4b2c1b175a880829 Mon Sep 17 00:00:00 2001 From: tonysyu Date: Tue, 25 Jun 2013 14:37:01 -0500 Subject: [PATCH 19/24] Fix execution in IPython with qt backend. New QApplication and event-loop implementation stolen shamelessly from IPython. Strangely, running the viewer at the IPython prompt will open an orphan Matplotlib figure window, but running a script using `%run` does not. Only tested on PySide (not PyQt4). --- skimage/viewer/utils/core.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/skimage/viewer/utils/core.py b/skimage/viewer/utils/core.py index 826147eb..4ec3e1cf 100644 --- a/skimage/viewer/utils/core.py +++ b/skimage/viewer/utils/core.py @@ -22,22 +22,37 @@ __all__ = ['init_qtapp', 'start_qtapp', 'RequiredAttr', 'figimage', 'update_axes_image'] -QApp = None - - def init_qtapp(): """Initialize QAppliction. The QApplication needs to be initialized before creating any QWidgets """ - global QApp - if QApp is None: - QApp = QtGui.QApplication([]) + app = QtGui.QApplication.instance() + if app is None: + app = QtGui.QApplication([]) + return app -def start_qtapp(): +def is_event_loop_running(app=None): + """Return True if event loop is running.""" + if app is None: + app = init_qtapp() + if hasattr(app, '_in_event_loop'): + return app._in_event_loop + else: + return False + + +def start_qtapp(app=None): """Start Qt mainloop""" - QApp.exec_() + if app is None: + app = init_qtapp() + if not is_event_loop_running(app): + app._in_event_loop = True + app.exec_() + app._in_event_loop = False + else: + app._in_event_loop = True class RequiredAttr(object): From e305677de59872674b0b7bb45e81844e1910e2ea Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 25 Jun 2013 23:15:42 -0500 Subject: [PATCH 20/24] Fix PyQt4 segfault caused by PySide fix. I guess PySide saves the QApplication internally, while PyQt4 doesn't. Saving the QApplication as a global prevents it from getting garbage collected. Saving the QApplication as an instance variable in the ImageViewer also works, but that might prevent the ImageViewer from getting garbage collected in an interactive session. (weakref doesn't seem to work here.) --- skimage/viewer/utils/core.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/skimage/viewer/utils/core.py b/skimage/viewer/utils/core.py index 4ec3e1cf..3ac0eef3 100644 --- a/skimage/viewer/utils/core.py +++ b/skimage/viewer/utils/core.py @@ -22,15 +22,19 @@ __all__ = ['init_qtapp', 'start_qtapp', 'RequiredAttr', 'figimage', 'update_axes_image'] +global QApp + + def init_qtapp(): """Initialize QAppliction. The QApplication needs to be initialized before creating any QWidgets """ - app = QtGui.QApplication.instance() - if app is None: - app = QtGui.QApplication([]) - return app + global QApp + QApp = QtGui.QApplication.instance() + if QApp is None: + QApp = QtGui.QApplication([]) + return QApp def is_event_loop_running(app=None): From dae0156230807f265fe9b8773f1ae9b5fd3f7a1b Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 25 Jun 2013 23:20:12 -0500 Subject: [PATCH 21/24] Make histogram threshold adjustable --- skimage/viewer/plugins/color_histogram.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/skimage/viewer/plugins/color_histogram.py b/skimage/viewer/plugins/color_histogram.py index 166b2805..39a75004 100644 --- a/skimage/viewer/plugins/color_histogram.py +++ b/skimage/viewer/plugins/color_histogram.py @@ -10,8 +10,9 @@ from ..canvastools import RectangleTool class ColorHistogram(PlotPlugin): name = 'Color Histogram' - def __init__(self, **kwargs): + def __init__(self, max_pct=0.99, **kwargs): super(ColorHistogram, self).__init__(height=400, **kwargs) + self.max_pct = max_pct print(self.help()) @@ -30,7 +31,7 @@ class ColorHistogram(PlotPlugin): normed=True) # Clip bin heights that dominate a-b histogram - max_val = pct_total_area(hist, percentile=99) + max_val = pct_total_area(hist, percentile=self.max_pct) hist = exposure.rescale_intensity(hist, in_range=(0, max_val)) self.ax.imshow(hist, extent=ab_extents, cmap=plt.cm.gray) @@ -55,12 +56,12 @@ class ColorHistogram(PlotPlugin): self.image_viewer.image = color.lab2rgb(lab_masked) -def pct_total_area(image, percentile=80): +def pct_total_area(image, percentile=0.80): """Return threshold value based on percentage of total area. The specified percent of pixels less than the given intensity threshold. """ - idx = int((image.size - 1) * percentile / 100.0) + idx = int((image.size - 1) * percentile) sorted_pixels = np.sort(image.flat) return sorted_pixels[idx] From cae693cb699b46d0554f50cf051d09cb7201ce15 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 25 Jun 2013 23:24:06 -0500 Subject: [PATCH 22/24] Change QApp default to previous behavior. --- skimage/viewer/utils/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/utils/core.py b/skimage/viewer/utils/core.py index 3ac0eef3..0a053784 100644 --- a/skimage/viewer/utils/core.py +++ b/skimage/viewer/utils/core.py @@ -22,7 +22,7 @@ __all__ = ['init_qtapp', 'start_qtapp', 'RequiredAttr', 'figimage', 'update_axes_image'] -global QApp +QApp = None def init_qtapp(): From ed7c75d4c6d3e687bdf05ca38c044d29e4fd65d5 Mon Sep 17 00:00:00 2001 From: tonysyu Date: Wed, 26 Jun 2013 11:00:24 -0500 Subject: [PATCH 23/24] Raise ImageViewer to front on start. Currently only works for main window, does not work for linked viewers. --- skimage/viewer/plugins/base.py | 2 ++ skimage/viewer/viewers/core.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index 1d655d61..c84245d2 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -209,6 +209,8 @@ class Plugin(QtGui.QDialog): def show(self, main_window=True): """Show plugin.""" super(Plugin, self).show() + self.activateWindow() + self.raise_() # Emit signal with x-hint so new windows can be displayed w/o overlap. size = self.frameGeometry() diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 3a5f8ad3..c3ffeb1e 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -201,6 +201,8 @@ class ImageViewer(QtGui.QMainWindow): for p in self.plugins: p.show() super(ImageViewer, self).show() + self.activateWindow() + self.raise_() def show(self, main_window=True): """Show ImageViewer and attached plugins. From c826935d9e3cb7c31a80f5d0d763452016cbd732 Mon Sep 17 00:00:00 2001 From: tonysyu Date: Wed, 26 Jun 2013 11:23:12 -0500 Subject: [PATCH 24/24] Change default Slider update_on value to 'release' Image filtering is usually slow, so updating on move was usually a bad idea. --- skimage/viewer/widgets/core.py | 4 ++-- viewer_examples/plugins/canny_simple.py | 6 +++--- viewer_examples/plugins/collection_plugin.py | 2 +- viewer_examples/plugins/median_filter.py | 2 +- viewer_examples/plugins/probabilistic_hough.py | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/skimage/viewer/widgets/core.py b/skimage/viewer/widgets/core.py index 0000bebb..b9714d38 100644 --- a/skimage/viewer/widgets/core.py +++ b/skimage/viewer/widgets/core.py @@ -90,12 +90,12 @@ class Slider(BaseWidget): is typically set when the widget is added to a plugin. orientation : {'horizontal' | 'vertical'} Slider orientation. - update_on : {'move' | 'release'} + update_on : {'release' | 'move'} Control when callback function is called: on slider move or release. """ def __init__(self, name, low=0.0, high=1.0, value=None, value_type='float', ptype='kwarg', callback=None, max_edit_width=60, - orientation='horizontal', update_on='move'): + orientation='horizontal', update_on='release'): super(Slider, self).__init__(name, ptype, callback) if value is None: diff --git a/viewer_examples/plugins/canny_simple.py b/viewer_examples/plugins/canny_simple.py index 912401e1..c26ca08d 100644 --- a/viewer_examples/plugins/canny_simple.py +++ b/viewer_examples/plugins/canny_simple.py @@ -12,9 +12,9 @@ image = data.camera() # You can create a UI for a filter just by passing a filter function... plugin = OverlayPlugin(image_filter=canny) # ... and adding widgets to adjust parameter values. -plugin += Slider('sigma', 0, 5, update_on='release') -plugin += Slider('low threshold', 0, 255, update_on='release') -plugin += Slider('high threshold', 0, 255, update_on='release') +plugin += Slider('sigma', 0, 5) +plugin += Slider('low threshold', 0, 255) +plugin += Slider('high threshold', 0, 255) # ... and we can also add buttons to save the overlay: plugin += SaveButtons(name='Save overlay to:') diff --git a/viewer_examples/plugins/collection_plugin.py b/viewer_examples/plugins/collection_plugin.py index 80c7ff45..65ff1f21 100644 --- a/viewer_examples/plugins/collection_plugin.py +++ b/viewer_examples/plugins/collection_plugin.py @@ -24,7 +24,7 @@ def autolevel(image, disk_size): img_collection = [data.camera(), data.coins(), data.text()] plugin = Plugin(image_filter=autolevel) -plugin += Slider('disk_size', 2, 8, value_type='int', update_on='release') +plugin += Slider('disk_size', 2, 8, value_type='int') plugin.name = "Autolevel" viewer = CollectionViewer(img_collection) diff --git a/viewer_examples/plugins/median_filter.py b/viewer_examples/plugins/median_filter.py index a20ad8f3..36593c3d 100644 --- a/viewer_examples/plugins/median_filter.py +++ b/viewer_examples/plugins/median_filter.py @@ -10,7 +10,7 @@ image = data.coins() viewer = ImageViewer(image) plugin = Plugin(image_filter=median_filter) -plugin += Slider('radius', 2, 10, value_type='int', update_on='release') +plugin += Slider('radius', 2, 10, value_type='int') plugin += SaveButtons() plugin += OKCancelButtons() diff --git a/viewer_examples/plugins/probabilistic_hough.py b/viewer_examples/plugins/probabilistic_hough.py index 5564ba9f..98052f87 100644 --- a/viewer_examples/plugins/probabilistic_hough.py +++ b/viewer_examples/plugins/probabilistic_hough.py @@ -34,8 +34,8 @@ canny_viewer += canny_plugin hough_plugin = OverlayPlugin(image_filter=hough_lines) hough_plugin.name = 'Hough Lines' -hough_plugin += Slider('line length', 0, 100, update_on='release') -hough_plugin += Slider('line gap', 0, 20, update_on='release') +hough_plugin += Slider('line length', 0, 100) +hough_plugin += Slider('line gap', 0, 20) # Passing a plugin to a viewer connects the output of the plugin to the viewer. hough_viewer = ImageViewer(canny_plugin)