From f442fe231a081c3ddc3aec2f97151557db22edcd Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 20 Jun 2015 06:51:39 -0500 Subject: [PATCH] Allow plot plugin to have tools --- skimage/viewer/canvastools/base.py | 19 ++-- skimage/viewer/canvastools/linetool.py | 18 ++-- skimage/viewer/canvastools/painttool.py | 12 +-- skimage/viewer/canvastools/recttool.py | 14 +-- skimage/viewer/plugins/color_histogram.py | 2 +- skimage/viewer/plugins/plotplugin.py | 21 +++++ skimage/viewer/utils/canvas.py | 106 ++++++++++++++++++++++ skimage/viewer/viewers/core.py | 105 +-------------------- 8 files changed, 162 insertions(+), 135 deletions(-) create mode 100644 skimage/viewer/utils/canvas.py diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index 4b1d0c00..9aa391f2 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -13,8 +13,8 @@ class CanvasToolBase(object): Parameters ---------- - viewer : :class:`skimage.viewer.Viewer` - Skimage viewer object. + manager : Viewer or PlotPlugin. + Skimage viewer or plot plugin object. on_move : function Function called whenever a control handle is moved. This function must accept the end points of line as the only argument. @@ -24,13 +24,12 @@ class CanvasToolBase(object): Function called whenever the "enter" key is pressed. """ - def __init__(self, viewer, on_move=None, on_enter=None, on_release=None, - useblit=True): - self.viewer = viewer - self.ax = viewer.ax + def __init__(self, manager, on_move=None, on_enter=None, on_release=None, + useblit=True, ax=None): + self.manager = manager + self.ax = manager.ax self.artists = [] self.active = True - viewer.add_tool(self) self.callback_on_move = _pass if on_move is None else on_move self.callback_on_enter = _pass if on_enter is None else on_enter @@ -48,7 +47,7 @@ class CanvasToolBase(object): return False def redraw(self): - self.viewer.redraw() + self.manager.redraw() def set_visible(self, val): for artist in self.artists: @@ -58,7 +57,7 @@ class CanvasToolBase(object): if event.key == 'enter': self.callback_on_enter(self.geometry) self.set_visible(False) - self.viewer.redraw() + self.manager.redraw() def on_mouse_press(self, event): pass @@ -73,7 +72,7 @@ class CanvasToolBase(object): pass def remove(self): - self.viewer.remove_tool(self) + self.manager.remove_tool(self) @property def geometry(self): diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index 261d5a7e..e131da95 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -11,8 +11,8 @@ class LineTool(CanvasToolBase): Parameters ---------- - viewer : :class:`skimage.viewer.Viewer` - Skimage viewer object. + manager : Viewer or PlotPlugin. + Skimage viewer or plot plugin object. on_move : function Function called whenever a control handle is moved. This function must accept the end points of line as the only argument. @@ -33,10 +33,10 @@ class LineTool(CanvasToolBase): end_points : 2D array End points of line ((x1, y1), (x2, y2)). """ - def __init__(self, viewer, on_move=None, on_release=None, on_enter=None, + def __init__(self, manager, on_move=None, on_release=None, on_enter=None, maxdist=10, line_props=None, handle_props=None, **kwargs): - super(LineTool, self).__init__(viewer, on_move=on_move, + super(LineTool, self).__init__(manager, on_move=on_move, on_enter=on_enter, on_release=on_release, **kwargs) @@ -64,7 +64,7 @@ class LineTool(CanvasToolBase): print("length = %0.2f" % np.sqrt(np.diff(x)**2 + np.diff(y)**2)) self.callback_on_enter = on_enter - viewer.add_tool(self) + self.manager.add_tool(self) @property def end_points(self): @@ -132,8 +132,8 @@ class ThickLineTool(LineTool): Parameters ---------- - viewer : :class:`skimage.viewer.Viewer` - Skimage viewer object. + manager : Viewer or PlotPlugin. + Skimage viewer or plot plugin object. on_move : function Function called whenever a control handle is moved. This function must accept the end points of line as the only argument. @@ -157,9 +157,9 @@ class ThickLineTool(LineTool): End points of line ((x1, y1), (x2, y2)). """ - def __init__(self, viewer, on_move=None, on_enter=None, on_release=None, + def __init__(self, manager, on_move=None, on_enter=None, on_release=None, on_change=None, maxdist=10, line_props=None, handle_props=None): - super(ThickLineTool, self).__init__(viewer, + super(ThickLineTool, self).__init__(manager, on_move=on_move, on_enter=on_enter, on_release=on_release, diff --git a/skimage/viewer/canvastools/painttool.py b/skimage/viewer/canvastools/painttool.py index e8ce8281..f1440f84 100644 --- a/skimage/viewer/canvastools/painttool.py +++ b/skimage/viewer/canvastools/painttool.py @@ -14,8 +14,8 @@ class PaintTool(CanvasToolBase): Parameters ---------- - viewer : :class:`skimage.viewer.Viewer` - Skimage viewer object. + manager : Viewer or PlotPlugin. + Skimage viewer or plot plugin object. overlay_shape : shape tuple 2D shape tuple used to initialize overlay image. alpha : float (between [0, 1]) @@ -38,10 +38,10 @@ class PaintTool(CanvasToolBase): label : int Current paint color. """ - def __init__(self, viewer, overlay_shape, radius=5, alpha=0.3, + def __init__(self, manager, overlay_shape, radius=5, alpha=0.3, on_move=None, on_release=None, on_enter=None, rect_props=None): - super(PaintTool, self).__init__(viewer, on_move=on_move, + super(PaintTool, self).__init__(manager, on_move=on_move, on_enter=on_enter, on_release=on_release) @@ -63,7 +63,7 @@ class PaintTool(CanvasToolBase): # Note that the order is important: Redraw cursor *after* overlay self.artists = [self._overlay_plot, self._cursor] - viewer.add_tool(self) + self.manager.add_tool(self) @property def label(self): @@ -136,7 +136,7 @@ class PaintTool(CanvasToolBase): self.callback_on_release(self.geometry) def on_move(self, event): - if not self.viewer.ax.in_axes(event): + if not self.ax.in_axes(event): self._cursor.set_visible(False) self.redraw() # make sure cursor is not visible return diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py index 8a895adf..56034422 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -14,8 +14,8 @@ class RectangleTool(CanvasToolBase, RectangleSelector): Parameters ---------- - viewer : :class:`skimage.viewer.Viewer` - Skimage viewer object. + manager : Viewer or PlotPlugin. + Skimage viewer or plot plugin object. on_move : function Function called whenever a control handle is moved. This function must accept the rectangle extents as the only argument. @@ -58,20 +58,20 @@ class RectangleTool(CanvasToolBase, RectangleSelector): ... set_color(im, (rr4, cc4), [0, 0, 0]) ... viewer.image=im - >>> rect_tool = RectangleTool(viewer.ax, on_enter=print_the_rect) # doctest: +SKIP + >>> rect_tool = RectangleTool(viewer, on_enter=print_the_rect) # doctest: +SKIP >>> viewer.show() # doctest: +SKIP """ - def __init__(self, viewer, on_move=None, on_release=None, on_enter=None, + def __init__(self, manager, on_move=None, on_release=None, on_enter=None, maxdist=10, rect_props=None): self._rect = None props = dict(edgecolor=None, facecolor='r', alpha=0.15) props.update(rect_props if rect_props is not None else {}) if props['edgecolor'] is None: props['edgecolor'] = props['facecolor'] - RectangleSelector.__init__(self, viewer.ax, lambda *args: None, + RectangleSelector.__init__(self, manager.ax, lambda *args: None, rectprops=props) - CanvasToolBase.__init__(self, viewer, on_move=on_move, + CanvasToolBase.__init__(self, manager, on_move=on_move, on_enter=on_enter, on_release=on_release) # Events are handled by the viewer @@ -107,7 +107,7 @@ class RectangleTool(CanvasToolBase, RectangleSelector): self.artists = [self._rect, self._corner_handles.artist, self._edge_handles.artist] - viewer.add_tool(self) + self.manager.add_tool(self) @property def _rect_bbox(self): diff --git a/skimage/viewer/plugins/color_histogram.py b/skimage/viewer/plugins/color_histogram.py index 09415a83..d0af4e0a 100644 --- a/skimage/viewer/plugins/color_histogram.py +++ b/skimage/viewer/plugins/color_histogram.py @@ -17,7 +17,7 @@ class ColorHistogram(PlotPlugin): def attach(self, image_viewer): super(ColorHistogram, self).attach(image_viewer) - self.rect_tool = RectangleTool(image_viewer, + self.rect_tool = RectangleTool(self, on_release=self.ab_selected) self._on_new_image(image_viewer.image) diff --git a/skimage/viewer/plugins/plotplugin.py b/skimage/viewer/plugins/plotplugin.py index f995efa8..656d95c7 100644 --- a/skimage/viewer/plugins/plotplugin.py +++ b/skimage/viewer/plugins/plotplugin.py @@ -2,6 +2,7 @@ import numpy as np from ..qt import QtGui from ..utils import new_plot +from ..utils.canvas import BlitManager, EventManager from .base import Plugin @@ -23,11 +24,17 @@ class PlotPlugin(Plugin): self._height = height self._width = width + self._blit_manager = None + self._tools = [] + self._event_manager = None def attach(self, image_viewer): super(PlotPlugin, self).attach(image_viewer) # Add plot for displaying intensity profile. self.add_plot() + if image_viewer.useblit: + self._blit_manager = BlitManager(self.ax) + self._event_manager = EventManager(self.ax) def redraw(self): """Redraw plot.""" @@ -51,3 +58,17 @@ class PlotPlugin(Plugin): def _update_original_image(self, image): super(PlotPlugin, self)._update_original_image(image) self.redraw() + + def add_tool(self, tool): + if self._blit_manager: + self._blit_manager.add_artists(tool.artists) + self._tools.append(tool) + self._event_manager.attach(tool) + + def remove_tool(self, tool): + if tool not in self._tools: + return + if self._blit_manager: + self._blit_manager.remove_artists(tool.artists) + self._tools.remove(tool) + self._event_manager.detach(tool) diff --git a/skimage/viewer/utils/canvas.py b/skimage/viewer/utils/canvas.py new file mode 100644 index 00000000..3eecb913 --- /dev/null +++ b/skimage/viewer/utils/canvas.py @@ -0,0 +1,106 @@ + + +class BlitManager(object): + + """Object that manages blits on an axes""" + + def __init__(self, ax): + self.ax = ax + self.canvas = ax.figure.canvas + self.canvas.mpl_connect('draw_event', self.on_draw_event) + self.ax = ax + self.background = None + self.artists = [] + + def add_artists(self, artists): + self.artists.extend(artists) + self.redraw() + + def remove_artists(self, artists): + for artist in artists: + self.artists.remove(artist) + + def on_draw_event(self, event=None): + self.background = self.canvas.copy_from_bbox(self.ax.bbox) + self.draw_artists() + + def redraw(self): + if self.background is not None: + self.canvas.restore_region(self.background) + self.draw_artists() + self.canvas.blit(self.ax.bbox) + else: + self.canvas.draw_idle() + + def draw_artists(self): + for artist in self.artists: + self.ax.draw_artist(artist) + + +class EventManager(object): + + """Object that manages events on a canvas""" + + def __init__(self, ax): + self.canvas = ax.figure.canvas + self.connect_event('button_press_event', self.on_mouse_press) + self.connect_event('key_press_event', self.on_key_press) + self.connect_event('button_release_event', self.on_mouse_release) + self.connect_event('motion_notify_event', self.on_move) + self.connect_event('scroll_event', self.on_scroll) + + self.tools = [] + self.active_tool = None + + def connect_event(self, name, handler): + self.canvas.mpl_connect(name, handler) + + def attach(self, tool): + self.tools.append(tool) + self.active_tool = tool + + def detach(self, tool): + self.tools.remove(tool) + if self.tools: + self.active_tool = self.tools[-1] + else: + self.active_tool = None + + def on_mouse_press(self, event): + for tool in self.tools: + if not tool.ignore(event) and tool.hit_test(event): + self.active_tool = tool + break + if self.active_tool and not self.active_tool.ignore(event): + self.active_tool.on_mouse_press(event) + return + for tool in reversed(self.tools): + if not tool.ignore(event): + self.active_tool = tool + tool.on_mouse_press(event) + return + + def on_key_press(self, event): + tool = self._get_tool(event) + if tool is not None: + tool.on_key_press(event) + + def _get_tool(self, event): + if not self.tools or self.active_tool.ignore(event): + return None + return self.active_tool + + def on_mouse_release(self, event): + tool = self._get_tool(event) + if tool is not None: + tool.on_mouse_release(event) + + def on_move(self, event): + tool = self._get_tool(event) + if tool is not None: + tool.on_move(event) + + def on_scroll(self, event): + tool = self._get_tool(event) + if tool is not None: + tool.on_scroll(event) diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 50a69603..88a1cb58 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -10,6 +10,7 @@ from ..qt import QtWidgets, Qt, Signal from ..widgets import Slider from ..utils import (dialogs, init_qtapp, figimage, start_qtapp, update_axes_image) +from ..utils.canvas import BlitManager, EventManager from ..plugins.base import Plugin @@ -44,108 +45,6 @@ def mpl_image_to_rgba(mpl_image): return img_as_float(image) -class BlitManager(object): - """Object that manages blits on an axes""" - def __init__(self, ax): - self.ax = ax - self.canvas = ax.figure.canvas - self.canvas.mpl_connect('draw_event', self.on_draw_event) - self.ax = ax - self.background = None - self.artists = [] - - def add_artists(self, artists): - self.artists.extend(artists) - self.redraw() - - def remove_artists(self, artists): - for artist in artists: - self.artists.remove(artist) - - def on_draw_event(self, event=None): - self.background = self.canvas.copy_from_bbox(self.ax.bbox) - self.draw_artists() - - def redraw(self): - if self.background is not None: - self.canvas.restore_region(self.background) - self.draw_artists() - self.canvas.blit(self.ax.bbox) - else: - self.canvas.draw_idle() - - def draw_artists(self): - for artist in self.artists: - self.ax.draw_artist(artist) - - -class EventManager(object): - """Object that manages events on a canvas""" - def __init__(self, ax): - self.canvas = ax.figure.canvas - self.connect_event('button_press_event', self.on_mouse_press) - self.connect_event('key_press_event', self.on_key_press) - self.connect_event('button_release_event', self.on_mouse_release) - self.connect_event('motion_notify_event', self.on_move) - self.connect_event('scroll_event', self.on_scroll) - - self.tools = [] - self.active_tool = None - - def connect_event(self, name, handler): - self.canvas.mpl_connect(name, handler) - - def attach(self, tool): - self.tools.append(tool) - self.active_tool = tool - - def detach(self, tool): - self.tools.remove(tool) - if self.tools: - self.active_tool = self.tools[-1] - else: - self.active_tool = None - - def on_mouse_press(self, event): - for tool in self.tools: - if not tool.ignore(event) and tool.hit_test(event): - self.active_tool = tool - break - if self.active_tool and not self.active_tool.ignore(event): - self.active_tool.on_mouse_press(event) - return - for tool in reversed(self.tools): - if not tool.ignore(event): - self.active_tool = tool - tool.on_mouse_press(event) - return - - def on_key_press(self, event): - tool = self._get_tool(event) - if not tool is None: - tool.on_key_press(event) - - def _get_tool(self, event): - if not self.tools or self.active_tool.ignore(event): - return None - return self.active_tool - - def on_mouse_release(self, event): - tool = self._get_tool(event) - if not tool is None: - tool.on_mouse_release(event) - - def on_move(self, event): - tool = self._get_tool(event) - if not tool is None: - tool.on_move(event) - - def on_scroll(self, event): - tool = self._get_tool(event) - if not tool is None: - tool.on_scroll(event) - - class ImageViewer(QtWidgets.QMainWindow): """Viewer for displaying images. @@ -405,6 +304,8 @@ class ImageViewer(QtWidgets.QMainWindow): self._event_manager.attach(tool) def remove_tool(self, tool): + if tool not in self._tools: + return if self.useblit: self._blit_manager.remove_artists(tool.artists) self._tools.remove(tool)