Merge pull request #575 from tonysyu/feature/viewer-linking

Linked image viewers and docked plugins
This commit is contained in:
Josh Warner
2013-06-28 08:32:34 -07:00
14 changed files with 304 additions and 63 deletions
+38 -4
View File
@@ -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.
+5 -4
View File
@@ -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]
+3 -2
View File
@@ -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
+12 -3
View File
@@ -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)