mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-01 22:06:14 +08:00
Merge pull request #575 from tonysyu/feature/viewer-linking
Linked image viewers and docked plugins
This commit is contained in:
@@ -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, Signal
|
||||
from ..utils import RequiredAttr, init_qtapp
|
||||
|
||||
|
||||
@@ -71,14 +74,24 @@ class Plugin(QtGui.QDialog):
|
||||
name = 'Plugin'
|
||||
image_viewer = RequiredAttr("%s is not attached to ImageViewer" % name)
|
||||
|
||||
def __init__(self, image_filter=None, height=0, width=400, useblit=True):
|
||||
# Signals used when viewers are linked to the Plugin output.
|
||||
image_changed = Signal(np.ndarray)
|
||||
_started = Signal(int)
|
||||
|
||||
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'):
|
||||
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)
|
||||
@@ -109,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()
|
||||
@@ -155,12 +168,22 @@ class Plugin(QtGui.QDialog):
|
||||
kwargs = dict([(name, self._get_value(a))
|
||||
for name, a in self.keyword_arguments.items()])
|
||||
filtered = self.image_filter(*arguments, **kwargs)
|
||||
|
||||
self.display_filtered_image(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."""
|
||||
@@ -183,6 +206,17 @@ class Plugin(QtGui.QDialog):
|
||||
"""
|
||||
setattr(self, name, value)
|
||||
|
||||
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()
|
||||
x_hint = size.x() + size.width()
|
||||
self._started.emit(x_hint)
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""On close disconnect all artists and events from ImageViewer.
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -4,6 +4,19 @@ 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
|
||||
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 Signal(*args, **kwargs):
|
||||
pass
|
||||
|
||||
def Slot(*args, **kwargs):
|
||||
pass
|
||||
|
||||
@@ -23,7 +23,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
|
||||
@@ -35,29 +36,53 @@ def init_qtapp():
|
||||
The QApplication needs to be initialized before creating any QWidgets
|
||||
"""
|
||||
global QApp
|
||||
QApp = QtGui.QApplication.instance()
|
||||
if QApp is None:
|
||||
QApp = QtGui.QApplication([])
|
||||
return QApp
|
||||
|
||||
|
||||
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):
|
||||
"""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):
|
||||
@@ -179,3 +204,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))
|
||||
|
||||
@@ -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, Signal
|
||||
|
||||
from skimage import io, img_as_float
|
||||
from skimage.util.dtype import dtype_range
|
||||
@@ -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']
|
||||
@@ -59,6 +60,15 @@ class ImageViewer(QtGui.QMainWindow):
|
||||
>>> # viewer.show()
|
||||
|
||||
"""
|
||||
|
||||
dock_areas = {'top': Qt.TopDockWidgetArea,
|
||||
'bottom': Qt.BottomDockWidgetArea,
|
||||
'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()
|
||||
@@ -66,21 +76,28 @@ 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()
|
||||
self.setCentralWidget(self.main_widget)
|
||||
|
||||
if isinstance(image, Plugin):
|
||||
plugin = image
|
||||
image = plugin.filtered_image
|
||||
plugin.image_changed.connect(self._update_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 +105,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._update_original_image(image)
|
||||
self.plugins = []
|
||||
|
||||
self.layout = QtGui.QVBoxLayout(self.main_widget)
|
||||
@@ -107,16 +122,48 @@ 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)
|
||||
dock = QtGui.QDockWidget()
|
||||
dock.setWidget(plugin)
|
||||
dock.setWindowTitle(plugin.name)
|
||||
self.addDockWidget(dock_location, dock)
|
||||
|
||||
horiz = (self.dock_areas['left'], self.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()
|
||||
if filename is None:
|
||||
return
|
||||
image = io.imread(filename)
|
||||
self._update_original_image(image)
|
||||
|
||||
def _update_original_image(self, image):
|
||||
self.original_image = image # update saved image
|
||||
self.image = image # update displayed image
|
||||
self.image = image.copy() # update displayed image
|
||||
self.original_image_changed.emit(image)
|
||||
|
||||
def save_to_file(self):
|
||||
"""Save current image to file.
|
||||
@@ -149,27 +196,22 @@ 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.geometry()
|
||||
self.move(0, 0)
|
||||
w = size.width()
|
||||
y = 0
|
||||
#TODO: Layout isn't quite correct for multiple plugins (overlaps).
|
||||
def _show(self, x=0):
|
||||
self.move(x, 0)
|
||||
for p in self.plugins:
|
||||
p.move(w, y)
|
||||
y += p.geometry().height()
|
||||
p.show()
|
||||
super(ImageViewer, self).show()
|
||||
self.activateWindow()
|
||||
self.raise_()
|
||||
|
||||
def show(self):
|
||||
def show(self, main_window=True):
|
||||
"""Show ImageViewer and attached plugins.
|
||||
|
||||
This behaves much like `matplotlib.pyplot.show` and `QWidget.show`.
|
||||
"""
|
||||
self.auto_layout()
|
||||
for p in self.plugins:
|
||||
p.show()
|
||||
super(ImageViewer, self).show()
|
||||
utils.start_qtapp()
|
||||
self._show()
|
||||
if main_window:
|
||||
utils.start_qtapp()
|
||||
|
||||
def redraw(self):
|
||||
self.canvas.draw_idle()
|
||||
@@ -181,13 +223,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)
|
||||
|
||||
@@ -248,7 +287,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
|
||||
@@ -296,7 +335,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:
|
||||
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
|
||||
@@ -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:')
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
==================================
|
||||
``CollectionViewer`` with a 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')
|
||||
plugin.name = "Autolevel"
|
||||
|
||||
viewer = CollectionViewer(img_collection)
|
||||
viewer += plugin
|
||||
|
||||
viewer.show()
|
||||
@@ -5,5 +5,5 @@ from skimage import data
|
||||
|
||||
image = data.load('color.png')
|
||||
viewer = ImageViewer(image)
|
||||
viewer += ColorHistogram()
|
||||
viewer += ColorHistogram(dock='right')
|
||||
viewer.show()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
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.name = 'Hough Lines'
|
||||
|
||||
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)
|
||||
hough_viewer += hough_plugin
|
||||
|
||||
# Show viewers displays both viewers since `hough_viewer` is connected to
|
||||
# `canny_viewer` through `canny_plugin`
|
||||
canny_viewer.show()
|
||||
Reference in New Issue
Block a user