From d9a85a167e9d824937c46a85b332263ae91316c8 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sat, 12 Jul 2014 20:32:20 -0500 Subject: [PATCH 1/9] Move handling of blit behavior to axes object. --- skimage/viewer/canvastools/base.py | 58 +++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index 29e6099d..284e2bd6 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -11,6 +11,31 @@ def _pass(*args): pass +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 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) + + def draw_artists(self): + for artist in self.artists: + self.ax.draw_artist(artist) + + class CanvasToolBase(object): """Base canvas tool for matplotlib axes. @@ -28,26 +53,31 @@ class CanvasToolBase(object): useblit : bool If True, update canvas by blitting, which is much faster than normal redrawing (turn off for debugging purposes). + nograb_draw : bool + If a mouse click is detected, but it does not grab a handle, + redraw the line from scratch. True by default. """ + def __init__(self, ax, on_move=None, on_enter=None, on_release=None, useblit=True): self.ax = ax self.canvas = ax.figure.canvas - self.img_background = None self.cids = [] self._artists = [] self.active = True + self.connect_event('draw_event', self._on_draw_event) if useblit: - self.connect_event('draw_event', self._blit_on_draw_event) + if not hasattr(ax, 'blit_manager'): + ax.blit_manager = BlitManager(ax) + ax.blit_manager.artists.extend(self._artists) + self.useblit = useblit 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 self.callback_on_release = _pass if on_release is None else on_release - self.connect_event('key_press_event', self._on_key_press) - def connect_event(self, event, callback): """Connect callback with an event. @@ -74,9 +104,13 @@ class CanvasToolBase(object): for artist in self._artists: artist.set_visible(val) - def _blit_on_draw_event(self, event=None): - self.img_background = self.canvas.copy_from_bbox(self.ax.bbox) - self._draw_artists() + def _on_draw_event(self, event=None): + if self.useblit: + for artist in self._artists: + if not artist in self.ax.blit_manager.artists: + self.ax.blit_manager.artists.append(artist) + else: + self._draw_artists() def _draw_artists(self): for artist in self._artists: @@ -97,14 +131,12 @@ class CanvasToolBase(object): This method should be called by subclasses when artists are updated. """ - if self.useblit and self.img_background is not None: - self.canvas.restore_region(self.img_background) - self._draw_artists() - self.canvas.blit(self.ax.bbox) + if not self.useblit: + self.canvas.draw() else: - self.canvas.draw_idle() + self.ax.blit_manager.redraw() - def _on_key_press(self, event): + def on_key_press(self, event): if event.key == 'enter': self.callback_on_enter(self.geometry) self.set_visible(False) From 410f3926e4b7fce9645506054b5dfd1668502811 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sat, 12 Jul 2014 20:32:43 -0500 Subject: [PATCH 2/9] Push handling of line tools to axes level for better coordination. --- skimage/viewer/canvastools/linetool.py | 84 +++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index 61d94825..4b53ebad 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -10,6 +10,63 @@ from skimage.viewer.canvastools.base import CanvasToolBase, ToolHandles __all__ = ['LineTool', 'ThickLineTool'] +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.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 on_mouse_press(self, event): + for tool in self.tools: + if not tool.ignore(event) and tool.hit_test(event): + self.active_tool = tool + tool.on_mouse_press(event) + return + if self.active_tool and not self.active_tool.ignore(event): + self.active_tool.on_mouse_press(event) + return + for tool in 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() + if not tool is None and not tool.ignore(event): + tool.on_key_press(event) + + def get_tool(self): + if not self.tools: + return + if self.active_tool is None: + self.active_tool = self.tools[0] + return self.active_tool + + def on_mouse_release(self, event): + tool = self.get_tool() + if not tool is None and not tool.ignore(event): + tool.on_mouse_release(event) + + def on_move(self, event): + tool = self.get_tool() + if not tool is None and not tool.ignore(event): + tool.on_move(event) + + class LineTool(CanvasToolBase): """Widget for line selection in a plot. @@ -35,9 +92,10 @@ class LineTool(CanvasToolBase): End points of line ((x1, y1), (x2, y2)). """ def __init__(self, ax, on_move=None, on_release=None, on_enter=None, - maxdist=10, line_props=None): + maxdist=10, line_props=None, + **kwargs): super(LineTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, - on_release=on_release) + on_release=on_release, **kwargs) props = dict(color='r', linewidth=1, alpha=0.4, solid_capstyle='butt') props.update(line_props if line_props is not None else {}) @@ -56,16 +114,16 @@ class LineTool(CanvasToolBase): self._handles.set_visible(False) self._artists = [self._line, self._handles.artist] + if not hasattr(ax, 'event_manager'): + ax.event_manager = EventManager(ax) + ax.event_manager.attach(self) + if on_enter is None: def on_enter(pts): x, y = np.transpose(pts) print("length = %0.2f" % np.sqrt(np.diff(x)**2 + np.diff(y)**2)) self.callback_on_enter = on_enter - self.connect_event('button_press_event', self.on_mouse_press) - self.connect_event('button_release_event', self.on_mouse_release) - self.connect_event('motion_notify_event', self.on_move) - @property def end_points(self): return self._end_pts.astype(int) @@ -76,19 +134,24 @@ class LineTool(CanvasToolBase): self._line.set_data(np.transpose(pts)) self._handles.set_data(np.transpose(pts)) - self._line.set_linewidth(self.linewidth) self.set_visible(True) self.redraw() - def on_mouse_press(self, event): + def hit_test(self, event): if event.button != 1 or not self.ax.in_axes(event): - return - self.set_visible(True) + return False idx, px_dist = self._handles.closest(event.x, event.y) if px_dist < self.maxdist: self._active_pt = idx + return True else: + self._active_pt = None + return False + + def on_mouse_press(self, event): + self.set_visible(True) + if self._active_pt is None: self._active_pt = 0 x, y = event.xdata, event.ydata self._end_pts = np.array([[x, y], [x, y]]) @@ -98,6 +161,7 @@ class LineTool(CanvasToolBase): return self._active_pt = None self.callback_on_release(self.geometry) + self.redraw() def on_move(self, event): if event.button != 1 or self._active_pt is None: From 1799846be9552c7d13586e5ad4b147a121329a87 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sat, 12 Jul 2014 20:44:21 -0500 Subject: [PATCH 3/9] Remove unused parameter from docstring --- skimage/viewer/canvastools/base.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index 284e2bd6..fce163f3 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -53,9 +53,6 @@ class CanvasToolBase(object): useblit : bool If True, update canvas by blitting, which is much faster than normal redrawing (turn off for debugging purposes). - nograb_draw : bool - If a mouse click is detected, but it does not grab a handle, - redraw the line from scratch. True by default. """ def __init__(self, ax, on_move=None, on_enter=None, on_release=None, From 2d9d8bb94ce8f32ff8d07feab60a509c16d21cdb Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sun, 20 Jul 2014 18:39:49 -0500 Subject: [PATCH 4/9] Refactor blit manager and event manager to viewer --- skimage/viewer/canvastools/base.py | 115 +++++------------------- skimage/viewer/canvastools/linetool.py | 83 +++-------------- skimage/viewer/canvastools/painttool.py | 20 ++--- skimage/viewer/canvastools/recttool.py | 25 +++--- skimage/viewer/viewers/core.py | 102 ++++++++++++++++++++- 5 files changed, 153 insertions(+), 192 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index fce163f3..a1a06360 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -11,38 +11,13 @@ def _pass(*args): pass -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 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) - - def draw_artists(self): - for artist in self.artists: - self.ax.draw_artist(artist) - - class CanvasToolBase(object): """Base canvas tool for matplotlib axes. Parameters ---------- - ax : :class:`matplotlib.axes.Axes` - Matplotlib axes where tool is displayed. + viewer : :class:`skimage.viewer.Viewer` + Skimage viewer 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. @@ -50,45 +25,19 @@ class CanvasToolBase(object): Function called whenever the control handle is released. on_enter : function Function called whenever the "enter" key is pressed. - useblit : bool - If True, update canvas by blitting, which is much faster than normal - redrawing (turn off for debugging purposes). """ - def __init__(self, ax, on_move=None, on_enter=None, on_release=None, + def __init__(self, viewer, on_move=None, on_enter=None, on_release=None, useblit=True): - self.ax = ax - self.canvas = ax.figure.canvas - self.cids = [] - self._artists = [] + self.viewer = viewer + self.ax = viewer.ax + self.artists = [] self.active = True - self.connect_event('draw_event', self._on_draw_event) - if useblit: - if not hasattr(ax, 'blit_manager'): - ax.blit_manager = BlitManager(ax) - ax.blit_manager.artists.extend(self._artists) - - self.useblit = useblit - 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 self.callback_on_release = _pass if on_release is None else on_release - def connect_event(self, event, callback): - """Connect callback with an event. - - This should be used in lieu of `figure.canvas.mpl_connect` since this - function stores call back ids for later clean up. - """ - cid = self.canvas.mpl_connect(event, callback) - self.cids.append(cid) - - def disconnect_events(self): - """Disconnect all events created by this widget.""" - for c in self.cids: - self.canvas.mpl_disconnect(c) - def ignore(self, event): """Return True if event should be ignored. @@ -97,47 +46,30 @@ class CanvasToolBase(object): """ return not self.active + def redraw(self): + self.viewer.redraw() + def set_visible(self, val): for artist in self._artists: artist.set_visible(val) - def _on_draw_event(self, event=None): - if self.useblit: - for artist in self._artists: - if not artist in self.ax.blit_manager.artists: - self.ax.blit_manager.artists.append(artist) - else: - self._draw_artists() - - def _draw_artists(self): - for artist in self._artists: - self.ax.draw_artist(artist) - - def remove(self): - """Remove artists and events from axes. - - Note that the naming here mimics the interface of Matplotlib artists. - """ - #TODO: For some reason, RectangleTool doesn't get properly removed - self.disconnect_events() - for a in self._artists: - a.remove() - - def redraw(self): - """Redraw image and canvas artists. - - This method should be called by subclasses when artists are updated. - """ - if not self.useblit: - self.canvas.draw() - else: - self.ax.blit_manager.redraw() - def on_key_press(self, event): if event.key == 'enter': self.callback_on_enter(self.geometry) self.set_visible(False) - self.redraw() + self.viewer.redraw() + + def on_mouse_press(self, event): + pass + + def on_mouse_release(self, event): + pass + + def on_move(self, event): + pass + + def on_scroll(self, event): + pass @property def geometry(self): @@ -190,9 +122,6 @@ class ToolHandles(object): def set_animated(self, val): self._markers.set_animated(val) - def draw(self): - self.ax.draw_artist(self._markers) - def closest(self, x, y): """Return index and pixel distance to closest index.""" pts = np.transpose((self.x, self.y)) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index 4b53ebad..e1b48c05 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -10,70 +10,13 @@ from skimage.viewer.canvastools.base import CanvasToolBase, ToolHandles __all__ = ['LineTool', 'ThickLineTool'] -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.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 on_mouse_press(self, event): - for tool in self.tools: - if not tool.ignore(event) and tool.hit_test(event): - self.active_tool = tool - tool.on_mouse_press(event) - return - if self.active_tool and not self.active_tool.ignore(event): - self.active_tool.on_mouse_press(event) - return - for tool in 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() - if not tool is None and not tool.ignore(event): - tool.on_key_press(event) - - def get_tool(self): - if not self.tools: - return - if self.active_tool is None: - self.active_tool = self.tools[0] - return self.active_tool - - def on_mouse_release(self, event): - tool = self.get_tool() - if not tool is None and not tool.ignore(event): - tool.on_mouse_release(event) - - def on_move(self, event): - tool = self.get_tool() - if not tool is None and not tool.ignore(event): - tool.on_move(event) - - class LineTool(CanvasToolBase): """Widget for line selection in a plot. Parameters ---------- - ax : :class:`matplotlib.axes.Axes` - Matplotlib axes where tool is displayed. + viewer : :class:`skimage.viewer.Viewer` + Skimage viewer 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. @@ -91,7 +34,7 @@ class LineTool(CanvasToolBase): end_points : 2D array End points of line ((x1, y1), (x2, y2)). """ - def __init__(self, ax, on_move=None, on_release=None, on_enter=None, + def __init__(self, viewer, on_move=None, on_release=None, on_enter=None, maxdist=10, line_props=None, **kwargs): super(LineTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, @@ -108,21 +51,18 @@ class LineTool(CanvasToolBase): self._end_pts = np.transpose([x, y]) self._line = lines.Line2D(x, y, visible=False, animated=True, **props) - ax.add_line(self._line) + self.ax.add_line(self._line) - self._handles = ToolHandles(ax, x, y) + self._handles = ToolHandles(self.ax, x, y) self._handles.set_visible(False) - self._artists = [self._line, self._handles.artist] - - if not hasattr(ax, 'event_manager'): - ax.event_manager = EventManager(ax) - ax.event_manager.attach(self) + self.artists = [self._line, self._handles.artist] if on_enter is None: def on_enter(pts): x, y = np.transpose(pts) print("length = %0.2f" % np.sqrt(np.diff(x)**2 + np.diff(y)**2)) self.callback_on_enter = on_enter + viewer.add_tool(self) @property def end_points(self): @@ -189,8 +129,8 @@ class ThickLineTool(LineTool): Parameters ---------- - ax : :class:`matplotlib.axes.Axes` - Matplotlib axes where tool is displayed. + viewer : :class:`skimage.viewer.Viewer` + Skimage viewer 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. @@ -211,7 +151,7 @@ class ThickLineTool(LineTool): End points of line ((x1, y1), (x2, y2)). """ - def __init__(self, ax, on_move=None, on_enter=None, on_release=None, + def __init__(self, viewer, on_move=None, on_enter=None, on_release=None, on_change=None, maxdist=10, line_props=None): super(ThickLineTool, self).__init__(ax, on_move=on_move, @@ -225,9 +165,6 @@ class ThickLineTool(LineTool): pass self.callback_on_change = on_change - self.connect_event('scroll_event', self.on_scroll) - self.connect_event('key_press_event', self.on_key_press) - def on_scroll(self, event): if not event.inaxes: return diff --git a/skimage/viewer/canvastools/painttool.py b/skimage/viewer/canvastools/painttool.py index 9b94e0a2..8d3c9449 100644 --- a/skimage/viewer/canvastools/painttool.py +++ b/skimage/viewer/canvastools/painttool.py @@ -17,8 +17,8 @@ class PaintTool(CanvasToolBase): Parameters ---------- - ax : :class:`matplotlib.axes.Axes` - Matplotlib axes where tool is displayed. + viewer : :class:`skimage.viewer.Viewer` + Skimage viewer object. overlay_shape : shape tuple 2D shape tuple used to initialize overlay image. alpha : float (between [0, 1]) @@ -41,8 +41,9 @@ class PaintTool(CanvasToolBase): label : int Current paint color. """ - def __init__(self, ax, overlay_shape, radius=5, alpha=0.3, on_move=None, - on_release=None, on_enter=None, rect_props=None): + def __init__(self, viewer, overlay_shape, radius=5, alpha=0.3, + on_move=None, on_release=None, on_enter=None, + rect_props=None): super(PaintTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, on_release=on_release) @@ -63,11 +64,8 @@ class PaintTool(CanvasToolBase): self.radius = radius # Note that the order is important: Redraw cursor *after* overlay - self._artists = [self._overlay_plot, self._cursor] - - self.connect_event('button_press_event', self.on_mouse_press) - self.connect_event('button_release_event', self.on_mouse_release) - self.connect_event('motion_notify_event', self.on_move) + self.artists = [self._overlay_plot, self._cursor] + view.add_tool(self) @property def label(self): @@ -123,7 +121,7 @@ class PaintTool(CanvasToolBase): self.radius = self._radius self.overlay = np.zeros(shape, dtype='uint8') - def _on_key_press(self, event): + def on_key_press(self, event): if event.key == 'enter': self.callback_on_enter(self.geometry) self.redraw() @@ -140,7 +138,7 @@ class PaintTool(CanvasToolBase): self.callback_on_release(self.geometry) def on_move(self, event): - if not self.ax.in_axes(event): + if not self.viewer.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 929fe939..d3724bf9 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -18,8 +18,8 @@ class RectangleTool(CanvasToolBase, RectangleSelector): Parameters ---------- - ax : :class:`matplotlib.axes.Axes` - Matplotlib axes where tool is displayed. + viewer : :class:`skimage.viewer.Viewer` + Skimage viewer object. on_move : function Function called whenever a control handle is moved. This function must accept the rectangle extents as the only argument. @@ -39,7 +39,7 @@ class RectangleTool(CanvasToolBase, RectangleSelector): Rectangle extents: (xmin, xmax, ymin, ymax). """ - def __init__(self, ax, on_move=None, on_release=None, on_enter=None, + def __init__(self, viewer, on_move=None, on_release=None, on_enter=None, maxdist=10, rect_props=None): CanvasToolBase.__init__(self, ax, on_move=on_move, on_enter=on_enter, on_release=on_release) @@ -48,9 +48,9 @@ class RectangleTool(CanvasToolBase, RectangleSelector): props.update(rect_props if rect_props is not None else {}) if props['edgecolor'] is None: props['edgecolor'] = props['facecolor'] - RectangleSelector.__init__(self, ax, lambda *args: None, - rectprops=props, - useblit=self.useblit) + RectangleSelector.__init__(self, self.ax, lambda *args: None, + rectprops=props) + self.disconnect_events() # events are handled by the viewer # Alias rectangle attribute, which is initialized in RectangleSelector. self._rect = self.to_draw self._rect.set_animated(True) @@ -74,9 +74,10 @@ class RectangleTool(CanvasToolBase, RectangleSelector): self._edge_handles = ToolHandles(ax, xe, ye, marker='s', marker_props=props) - self._artists = [self._rect, - self._corner_handles.artist, - self._edge_handles.artist] + self.artists = [self._rect, + self._corner_handles.artist, + self._edge_handles.artist] + viewer.add_tool(self) @property def _rect_bbox(self): @@ -129,7 +130,7 @@ class RectangleTool(CanvasToolBase, RectangleSelector): self.set_visible(True) self.redraw() - def release(self, event): + def on_mouse_release(self, event): if event.button != 1: return if not self.ax.in_axes(event): @@ -142,7 +143,7 @@ class RectangleTool(CanvasToolBase, RectangleSelector): self.redraw() self.callback_on_release(self.geometry) - def press(self, event): + def on_mouse_press(self, event): if event.button != 1 or not self.ax.in_axes(event): return self._set_active_handle(event) @@ -177,7 +178,7 @@ class RectangleTool(CanvasToolBase, RectangleSelector): y1, y2 = y2, event.ydata self._extents_on_press = x1, x2, y1, y2 - def onmove(self, event): + def on_move(self, event): if self.eventpress is None or not self.ax.in_axes(event): return diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 52ac073a..afd3ff2b 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -12,7 +12,6 @@ else: from skimage import io, img_as_float from skimage.util.dtype import dtype_range from skimage.exposure import rescale_intensity -from skimage._shared.testing import doctest_skip_parser import numpy as np from .. import utils from ..widgets import Slider @@ -51,6 +50,88 @@ 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 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) + + 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.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 on_mouse_press(self, event): + for tool in self.tools: + if not tool.ignore(event) and tool.hit_test(event): + self.active_tool = tool + tool.on_mouse_press(event) + return + 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() + if not tool is None and not tool.ignore(event): + tool.on_key_press(event) + + def get_tool(self): + if not self.tools: + return + if self.active_tool is None: + self.active_tool = self.tools[0] + return self.active_tool + + def on_mouse_release(self, event): + tool = self.get_tool() + if not tool is None and not tool.ignore(event): + tool.on_mouse_release(event) + + def on_move(self, event): + tool = self.get_tool() + if not tool is None and not tool.ignore(event): + tool.on_move(event) + + class ImageViewer(QtGui.QMainWindow): """Viewer for displaying images. @@ -91,7 +172,7 @@ class ImageViewer(QtGui.QMainWindow): # Signal that the original image has been changed original_image_changed = Signal(np.ndarray) - def __init__(self, image): + def __init__(self, image, useblit=True): # Start main loop utils.init_qtapp() super(ImageViewer, self).__init__() @@ -125,6 +206,12 @@ class ImageViewer(QtGui.QMainWindow): self.canvas.setParent(self) self.ax.autoscale(enable=False) + self._tools = [] + self.useblit = useblit + if useblit: + self._blit_manager = BlitManager(self.ax) + self._event_manager = EventManager(self.ax) + self._image_plot = self.ax.images[0] self._update_original_image(image) self.plugins = [] @@ -238,7 +325,10 @@ class ImageViewer(QtGui.QMainWindow): return [p.output() for p in self.plugins] def redraw(self): - self.canvas.draw_idle() + if self.useblit: + self._blit_manager.redraw() + else: + self.canvas.draw_idle() @property def image(self): @@ -280,6 +370,12 @@ class ImageViewer(QtGui.QMainWindow): else: self.status_message('') + def add_tool(self, tool): + if self.blit: + self._blit_manager.artists.extend(tool.artists) + self._tools.append(tool) + self._event_manager.attach(tool) + def _format_coord(self, x, y): # callback function to format coordinate display in status bar x = int(x + 0.5) From 1775e959d6bec18bf97ee3e42810e02aa4e2d06a Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sun, 20 Jul 2014 19:08:14 -0500 Subject: [PATCH 5/9] Switch plugins to use new tool api --- skimage/viewer/plugins/color_histogram.py | 3 ++- skimage/viewer/plugins/crop.py | 2 +- skimage/viewer/plugins/labelplugin.py | 2 +- skimage/viewer/plugins/lineprofile.py | 2 +- skimage/viewer/plugins/measure.py | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/skimage/viewer/plugins/color_histogram.py b/skimage/viewer/plugins/color_histogram.py index 4826fc44..99205c41 100644 --- a/skimage/viewer/plugins/color_histogram.py +++ b/skimage/viewer/plugins/color_histogram.py @@ -21,7 +21,8 @@ class ColorHistogram(PlotPlugin): def attach(self, image_viewer): super(ColorHistogram, self).attach(image_viewer) - self.rect_tool = RectangleTool(self.ax, on_release=self.ab_selected) + self.rect_tool = RectangleTool(image_viewer, + on_release=self.ab_selected) self._on_new_image(image_viewer.image) def _on_new_image(self, image): diff --git a/skimage/viewer/plugins/crop.py b/skimage/viewer/plugins/crop.py index 61f61034..29ad5866 100644 --- a/skimage/viewer/plugins/crop.py +++ b/skimage/viewer/plugins/crop.py @@ -18,7 +18,7 @@ class Crop(Plugin): def attach(self, image_viewer): super(Crop, self).attach(image_viewer) - self.rect_tool = RectangleTool(self.image_viewer.ax, + self.rect_tool = RectangleTool(image_viewer, maxdist=self.maxdist, on_enter=self.crop) self.artists.append(self.rect_tool) diff --git a/skimage/viewer/plugins/labelplugin.py b/skimage/viewer/plugins/labelplugin.py index 06ba2dd9..fa2b1940 100644 --- a/skimage/viewer/plugins/labelplugin.py +++ b/skimage/viewer/plugins/labelplugin.py @@ -37,7 +37,7 @@ class LabelPainter(Plugin): super(LabelPainter, self).attach(image_viewer) image = image_viewer.original_image - self.paint_tool = PaintTool(self.image_viewer.ax, image.shape, + self.paint_tool = PaintTool(image_viewer, image.shape, on_enter=self.on_enter) self.paint_tool.radius = self.radius self.paint_tool.label = self._label_widget.index = 1 diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 6e5b2c1f..46a0f1d2 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -59,7 +59,7 @@ class LineProfile(PlotPlugin): x = [w / 3, 2 * w / 3] y = [h / 2] * 2 - self.line_tool = ThickLineTool(self.image_viewer.ax, + self.line_tool = ThickLineTool(self.image_viewer, maxdist=self.maxdist, on_move=self.line_changed, on_change=self.line_changed) diff --git a/skimage/viewer/plugins/measure.py b/skimage/viewer/plugins/measure.py index eeaa13b0..7e29a796 100644 --- a/skimage/viewer/plugins/measure.py +++ b/skimage/viewer/plugins/measure.py @@ -32,7 +32,7 @@ class Measure(Plugin): image = image_viewer.original_image h, w = image.shape - self.line_tool = LineTool(self.image_viewer.ax, + self.line_tool = LineTool(self.image_viewer, maxdist=self.maxdist, on_move=self.line_changed) self.artists.append(self.line_tool) From 778f62de6a76e5eae0b0c78b66ab9567e326412a Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sun, 20 Jul 2014 19:15:40 -0500 Subject: [PATCH 6/9] Updates to get all tools tested and working --- skimage/viewer/canvastools/base.py | 5 ++++- skimage/viewer/canvastools/linetool.py | 15 +++++++-------- skimage/viewer/canvastools/painttool.py | 13 +++++++------ skimage/viewer/canvastools/recttool.py | 15 +++++++-------- skimage/viewer/viewers/core.py | 2 +- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index a1a06360..5412198b 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -46,11 +46,14 @@ class CanvasToolBase(object): """ return not self.active + def hit_test(self, event): + return False + def redraw(self): self.viewer.redraw() def set_visible(self, val): - for artist in self._artists: + for artist in self.artists: artist.set_visible(val) def on_key_press(self, event): diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index e1b48c05..0b6de5a4 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -37,7 +37,8 @@ class LineTool(CanvasToolBase): def __init__(self, viewer, on_move=None, on_release=None, on_enter=None, maxdist=10, line_props=None, **kwargs): - super(LineTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, + super(LineTool, self).__init__(viewer, on_move=on_move, + on_enter=on_enter, on_release=on_release, **kwargs) props = dict(color='r', linewidth=1, alpha=0.4, solid_capstyle='butt') @@ -153,7 +154,7 @@ class ThickLineTool(LineTool): def __init__(self, viewer, on_move=None, on_enter=None, on_release=None, on_change=None, maxdist=10, line_props=None): - super(ThickLineTool, self).__init__(ax, + super(ThickLineTool, self).__init__(viewer, on_move=on_move, on_enter=on_enter, on_release=on_release, @@ -192,16 +193,14 @@ class ThickLineTool(LineTool): if __name__ == '__main__': # pragma: no cover - import matplotlib.pyplot as plt from skimage import data + from skimage.viewer import ImageViewer image = data.camera() - f, ax = plt.subplots() - ax.imshow(image, interpolation='nearest') + viewer = ImageViewer(image) h, w = image.shape - # line_tool = LineTool(ax) - line_tool = ThickLineTool(ax) + line_tool = ThickLineTool(viewer) line_tool.end_points = ([w/3, h/2], [2*w/3, h/2]) - plt.show() + viewer.show() diff --git a/skimage/viewer/canvastools/painttool.py b/skimage/viewer/canvastools/painttool.py index 8d3c9449..953f6ebe 100644 --- a/skimage/viewer/canvastools/painttool.py +++ b/skimage/viewer/canvastools/painttool.py @@ -44,7 +44,8 @@ class PaintTool(CanvasToolBase): def __init__(self, viewer, overlay_shape, radius=5, alpha=0.3, on_move=None, on_release=None, on_enter=None, rect_props=None): - super(PaintTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, + super(PaintTool, self).__init__(viewer, on_move=on_move, + on_enter=on_enter, on_release=on_release) props = dict(edgecolor='r', facecolor='0.7', alpha=0.5, animated=True) @@ -65,7 +66,7 @@ class PaintTool(CanvasToolBase): # Note that the order is important: Redraw cursor *after* overlay self.artists = [self._overlay_plot, self._cursor] - view.add_tool(self) + viewer.add_tool(self) @property def label(self): @@ -201,10 +202,10 @@ class CenteredWindow(object): if __name__ == '__main__': # pragma: no cover np.testing.rundocs() from skimage import data + from skimage.viewer import ImageViewer image = data.camera() - f, ax = plt.subplots() - ax.imshow(image, interpolation='nearest') - paint_tool = PaintTool(ax, image.shape) - plt.show() + viewer = ImageViewer(image) + paint_tool = PaintTool(viewer, image.shape) + viewer.show() diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py index d3724bf9..5d658533 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -41,7 +41,7 @@ class RectangleTool(CanvasToolBase, RectangleSelector): def __init__(self, viewer, on_move=None, on_release=None, on_enter=None, maxdist=10, rect_props=None): - CanvasToolBase.__init__(self, ax, on_move=on_move, + CanvasToolBase.__init__(self, viewer, on_move=on_move, on_enter=on_enter, on_release=on_release) props = dict(edgecolor=None, facecolor='r', alpha=0.15) @@ -67,11 +67,11 @@ class RectangleTool(CanvasToolBase, RectangleSelector): props = dict(mec=props['edgecolor']) self._corner_order = ['NW', 'NE', 'SE', 'SW'] xc, yc = self.corners - self._corner_handles = ToolHandles(ax, xc, yc, marker_props=props) + self._corner_handles = ToolHandles(self.ax, xc, yc, marker_props=props) self._edge_order = ['W', 'N', 'E', 'S'] xe, ye = self.edge_centers - self._edge_handles = ToolHandles(ax, xe, ye, marker='s', + self._edge_handles = ToolHandles(self.ax, xe, ye, marker='s', marker_props=props) self.artists = [self._rect, @@ -202,13 +202,12 @@ class RectangleTool(CanvasToolBase, RectangleSelector): if __name__ == '__main__': # pragma: no cover - import matplotlib.pyplot as plt + from skimage.viewer import ImageViewer from skimage import data - f, ax = plt.subplots() - ax.imshow(data.camera(), interpolation='nearest') + viewer = ImageViewer(data.camera()) - rect_tool = RectangleTool(ax) - plt.show() + rect_tool = RectangleTool(viewer) + viewer.show() print("Final selection:") rect_tool.callback_on_enter(rect_tool.extents) diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index afd3ff2b..6dc4a7a5 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -371,7 +371,7 @@ class ImageViewer(QtGui.QMainWindow): self.status_message('') def add_tool(self, tool): - if self.blit: + if self.useblit: self._blit_manager.artists.extend(tool.artists) self._tools.append(tool) self._event_manager.attach(tool) From 7864adbc0c36f81c614e3a7c11c8b9a553ee2583 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sun, 20 Jul 2014 19:29:39 -0500 Subject: [PATCH 7/9] Make other suggested style changes --- skimage/viewer/viewers/core.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 6dc4a7a5..52e2259f 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -98,8 +98,7 @@ class EventManager(object): for tool in self.tools: if not tool.ignore(event) and tool.hit_test(event): self.active_tool = tool - tool.on_mouse_press(event) - return + break if self.active_tool and not self.active_tool.ignore(event): self.active_tool.on_mouse_press(event) return @@ -110,25 +109,23 @@ class EventManager(object): return def on_key_press(self, event): - tool = self.get_tool() - if not tool is None and not tool.ignore(event): + tool = self._get_tool(event) + if not tool is None: tool.on_key_press(event) - def get_tool(self): - if not self.tools: - return - if self.active_tool is None: - self.active_tool = self.tools[0] + 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() - if not tool is None and not tool.ignore(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() - if not tool is None and not tool.ignore(event): + tool = self._get_tool(event) + if not tool is None: tool.on_move(event) From 17403921101f1fd55e263fd90172a23ef3b1dc18 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 1 Sep 2014 10:28:28 -0500 Subject: [PATCH 8/9] Add on_scroll behavior and allow line to change width. --- skimage/viewer/canvastools/linetool.py | 1 + skimage/viewer/viewers/core.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index 0b6de5a4..6dbda97b 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -75,6 +75,7 @@ class LineTool(CanvasToolBase): self._line.set_data(np.transpose(pts)) self._handles.set_data(np.transpose(pts)) + self._line.set_linewidth(self.linewidth) self.set_visible(True) self.redraw() diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 52e2259f..f1e541a1 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -83,6 +83,7 @@ class EventManager(object): 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 @@ -128,6 +129,11 @@ class EventManager(object): 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(QtGui.QMainWindow): """Viewer for displaying images. From 7ac6323210fbab64a417c1cf0f09ed8e5030f77e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 1 Sep 2014 13:30:07 -0500 Subject: [PATCH 9/9] Fix failing tests --- skimage/viewer/canvastools/base.py | 4 ++ skimage/viewer/tests/test_tools.py | 90 +++++++++++++--------------- skimage/viewer/tests/test_utils.py | 4 +- skimage/viewer/tests/test_widgets.py | 8 ++- skimage/viewer/viewers/core.py | 23 ++++++- 5 files changed, 73 insertions(+), 56 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index 5412198b..197800c0 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -33,6 +33,7 @@ class CanvasToolBase(object): self.ax = viewer.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 @@ -74,6 +75,9 @@ class CanvasToolBase(object): def on_scroll(self, event): pass + def remove(self): + self.viewer.remove_tool(self) + @property def geometry(self): """Geometry information that gets passed to callback functions.""" diff --git a/skimage/viewer/tests/test_tools.py b/skimage/viewer/tests/test_tools.py index 6663c465..8cc6bd0d 100644 --- a/skimage/viewer/tests/test_tools.py +++ b/skimage/viewer/tests/test_tools.py @@ -19,7 +19,7 @@ def get_end_points(image): return np.transpose([x, y]) -def create_mouse_event(ax, button=1, xdata=0, ydata=0, key=None): +def do_event(viewer, etype, button=1, xdata=0, ydata=0, key=None): """ *name* the event name @@ -56,6 +56,7 @@ def create_mouse_event(ax, button=1, xdata=0, ydata=0, key=None): *step* number of scroll steps (positive for 'up', negative for 'down') """ + ax = viewer.ax event = namedtuple('Event', ('name canvas guiEvent x y inaxes xdata ydata ' 'button key step')) @@ -68,7 +69,9 @@ def create_mouse_event(ax, button=1, xdata=0, ydata=0, key=None): event.step = 1 event.guiEvent = None event.name = 'Custom' - return event + + func = getattr(viewer._event_manager, 'on_%s' % etype) + func(event) @skipif(not viewer_available) @@ -76,24 +79,22 @@ def test_line_tool(): img = data.camera() viewer = ImageViewer(img) - tool = LineTool(viewer.ax, maxdist=10) + tool = LineTool(viewer, maxdist=10) tool.end_points = get_end_points(img) assert_equal(tool.end_points, np.array([[170, 256], [341, 256]])) # grab a handle and move it - grab = create_mouse_event(viewer.ax, xdata=170, ydata=256) - tool.on_mouse_press(grab) - move = create_mouse_event(viewer.ax, xdata=180, ydata=260) - tool.on_move(move) - tool.on_mouse_release(move) + do_event(viewer, 'mouse_press', xdata=170, ydata=256) + do_event(viewer, 'move', xdata=180, ydata=260) + do_event(viewer, 'mouse_release') + assert_equal(tool.geometry, np.array([[180, 260], [341, 256]])) # create a new line - new = create_mouse_event(viewer.ax, xdata=10, ydata=10) - tool.on_mouse_press(new) - move = create_mouse_event(viewer.ax, xdata=100, ydata=100) - tool.on_move(move) - tool.on_mouse_release(move) + do_event(viewer, 'mouse_press', xdata=10, ydata=10) + do_event(viewer, 'move', xdata=100, ydata=100) + do_event(viewer, 'mouse_release') + assert_equal(tool.geometry, np.array([[100, 100], [10, 10]])) @@ -102,23 +103,20 @@ def test_thick_line_tool(): img = data.camera() viewer = ImageViewer(img) - tool = ThickLineTool(viewer.ax, maxdist=10) + tool = ThickLineTool(viewer, maxdist=10) tool.end_points = get_end_points(img) - scroll_up = create_mouse_event(viewer.ax, button='up') - tool.on_scroll(scroll_up) + do_event(viewer, 'scroll', button='up') assert_equal(tool.linewidth, 2) - scroll_down = create_mouse_event(viewer.ax, button='down') - tool.on_scroll(scroll_down) + do_event(viewer, 'scroll', button='down') + assert_equal(tool.linewidth, 1) - key_up = create_mouse_event(viewer.ax, key='+') - tool.on_key_press(key_up) + do_event(viewer, 'key_press', key='+') assert_equal(tool.linewidth, 2) - key_down = create_mouse_event(viewer.ax, key='-') - tool.on_key_press(key_down) + do_event(viewer, 'key_press', key='-') assert_equal(tool.linewidth, 1) @@ -127,7 +125,7 @@ def test_rect_tool(): img = data.camera() viewer = ImageViewer(img) - tool = RectangleTool(viewer.ax, maxdist=10) + tool = RectangleTool(viewer, maxdist=10) tool.extents = (100, 150, 100, 150) assert_equal(tool.corners, @@ -138,19 +136,15 @@ def test_rect_tool(): assert_equal(tool.geometry, (100, 150, 100, 150)) # grab a corner and move it - grab = create_mouse_event(viewer.ax, xdata=100, ydata=100) - tool.press(grab) - move = create_mouse_event(viewer.ax, xdata=120, ydata=120) - tool.onmove(move) - tool.release(move) + do_event(viewer, 'mouse_press', xdata=100, ydata=100) + do_event(viewer, 'move', xdata=120, ydata=120) + do_event(viewer, 'mouse_release') assert_equal(tool.geometry, [120, 150, 120, 150]) # create a new line - new = create_mouse_event(viewer.ax, xdata=10, ydata=10) - tool.press(new) - move = create_mouse_event(viewer.ax, xdata=100, ydata=100) - tool.onmove(move) - tool.release(move) + do_event(viewer, 'mouse_press', xdata=10, ydata=10) + do_event(viewer, 'move', xdata=100, ydata=100) + do_event(viewer, 'mouse_release') assert_equal(tool.geometry, [10, 100, 10, 100]) @@ -159,7 +153,7 @@ def test_paint_tool(): img = data.moon() viewer = ImageViewer(img) - tool = PaintTool(viewer.ax, img.shape) + tool = PaintTool(viewer, img.shape) tool.radius = 10 assert_equal(tool.radius, 10) @@ -167,24 +161,21 @@ def test_paint_tool(): assert_equal(tool.label, 2) assert_equal(tool.shape, img.shape) - start = create_mouse_event(viewer.ax, xdata=100, ydata=100) - tool.on_mouse_press(start) - move = create_mouse_event(viewer.ax, xdata=110, ydata=110) - tool.on_move(move) - tool.on_mouse_release(move) + do_event(viewer, 'mouse_press', xdata=100, ydata=100) + do_event(viewer, 'move', xdata=110, ydata=110) + do_event(viewer, 'mouse_release') + assert_equal(tool.overlay[tool.overlay == 2].size, 761) tool.label = 5 - start = create_mouse_event(viewer.ax, xdata=20, ydata=20) - tool.on_mouse_press(start) - move = create_mouse_event(viewer.ax, xdata=40, ydata=40) - tool.on_move(move) - tool.on_mouse_release(move) + do_event(viewer, 'mouse_press', xdata=20, ydata=20) + do_event(viewer, 'move', xdata=40, ydata=40) + do_event(viewer, 'mouse_release') + assert_equal(tool.overlay[tool.overlay == 5].size, 881) assert_equal(tool.overlay[tool.overlay == 2].size, 761) - enter = create_mouse_event(viewer.ax, key='enter') - tool.on_mouse_press(enter) + do_event(viewer, 'key_press', key='enter') tool.overlay = tool.overlay * 0 assert_equal(tool.overlay.sum(), 0) @@ -195,15 +186,14 @@ def test_base_tool(): img = data.moon() viewer = ImageViewer(img) - tool = CanvasToolBase(viewer.ax) + tool = CanvasToolBase(viewer) tool.set_visible(False) tool.set_visible(True) - enter = create_mouse_event(viewer.ax, key='enter') - tool._on_key_press(enter) + do_event(viewer, 'key_press', key='enter') tool.redraw() tool.remove() - tool = CanvasToolBase(viewer.ax, useblit=False) + tool = CanvasToolBase(viewer, useblit=False) tool.redraw() diff --git a/skimage/viewer/tests/test_utils.py b/skimage/viewer/tests/test_utils.py index 3f9ec396..77f30299 100644 --- a/skimage/viewer/tests/test_utils.py +++ b/skimage/viewer/tests/test_utils.py @@ -28,7 +28,7 @@ def test_format_filename(): def test_open_file_dialog(): utils.init_qtapp() timer = QtCore.QTimer() - timer.singleShot(10, QtGui.QApplication.quit) + timer.singleShot(100, lambda: QtGui.QApplication.quit()) filename = dialogs.open_file_dialog() assert filename is None @@ -37,6 +37,6 @@ def test_open_file_dialog(): def test_save_file_dialog(): utils.init_qtapp() timer = QtCore.QTimer() - timer.singleShot(10, QtGui.QApplication.quit) + timer.singleShot(100, lambda: QtGui.QApplication.quit()) filename = dialogs.save_file_dialog() assert filename is None diff --git a/skimage/viewer/tests/test_widgets.py b/skimage/viewer/tests/test_widgets.py index ef3cb83c..ee4c91fb 100644 --- a/skimage/viewer/tests/test_widgets.py +++ b/skimage/viewer/tests/test_widgets.py @@ -77,11 +77,11 @@ def test_save_buttons(): viewer.plugins[0] += sv import tempfile - _, filename = tempfile.mkstemp(suffix='.png') - os.remove(filename) + fid, filename = tempfile.mkstemp(suffix='.png') + os.close(fid) timer = QtCore.QTimer() - timer.singleShot(100, lambda: QtGui.QApplication.quit()) + timer.singleShot(100, QtGui.QApplication.quit) sv.save_to_stack() sv.save_to_file(filename) @@ -92,6 +92,8 @@ def test_save_buttons(): img = io.pop() assert_almost_equal(img, viewer.image) + os.remove(filename) + @skipif(not viewer_available) def test_ok_buttons(): diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index f1e541a1..00902112 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -60,6 +60,14 @@ class BlitManager(object): 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.artist.remove(artist) + def on_draw_event(self, event=None): self.background = self.canvas.copy_from_bbox(self.ax.bbox) self.draw_artists() @@ -95,6 +103,13 @@ class EventManager(object): 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): @@ -375,10 +390,16 @@ class ImageViewer(QtGui.QMainWindow): def add_tool(self, tool): if self.useblit: - self._blit_manager.artists.extend(tool.artists) + self._blit_manager.add_artists(tool.artists) self._tools.append(tool) self._event_manager.attach(tool) + def remove_tool(self, tool): + if self.useblit: + self._blit_manager.remove_artists(tool.artists) + self._tools.remove(tool) + self._event_manager.detach(tool) + def _format_coord(self, x, y): # callback function to format coordinate display in status bar x = int(x + 0.5)