mirror of
https://github.com/wassname/scikit-image.git
synced 2026-06-30 03:13:20 +08:00
Merge pull request #1065 from blink1073/multi_linetool_drawing
Multi linetool drawing
This commit is contained in:
@@ -16,8 +16,8 @@ class CanvasToolBase(object):
|
||||
|
||||
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.
|
||||
@@ -25,43 +25,20 @@ 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,
|
||||
useblit=True):
|
||||
self.ax = ax
|
||||
self.canvas = ax.figure.canvas
|
||||
self.img_background = None
|
||||
self.cids = []
|
||||
self._artists = []
|
||||
self.active = True
|
||||
|
||||
if useblit:
|
||||
self.connect_event('draw_event', self._blit_on_draw_event)
|
||||
self.useblit = useblit
|
||||
def __init__(self, viewer, on_move=None, on_enter=None, on_release=None,
|
||||
useblit=True):
|
||||
self.viewer = viewer
|
||||
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
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -70,45 +47,36 @@ class CanvasToolBase(object):
|
||||
"""
|
||||
return not self.active
|
||||
|
||||
def set_visible(self, val):
|
||||
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 _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 hit_test(self, event):
|
||||
return False
|
||||
|
||||
def redraw(self):
|
||||
"""Redraw image and canvas artists.
|
||||
self.viewer.redraw()
|
||||
|
||||
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)
|
||||
else:
|
||||
self.canvas.draw_idle()
|
||||
def set_visible(self, val):
|
||||
for artist in self.artists:
|
||||
artist.set_visible(val)
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
def remove(self):
|
||||
self.viewer.remove_tool(self)
|
||||
|
||||
@property
|
||||
def geometry(self):
|
||||
@@ -161,9 +129,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))
|
||||
|
||||
@@ -15,8 +15,8 @@ class LineTool(CanvasToolBase):
|
||||
|
||||
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.
|
||||
@@ -34,10 +34,12 @@ 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,
|
||||
maxdist=10, line_props=None):
|
||||
super(LineTool, self).__init__(ax, on_move=on_move, on_enter=on_enter,
|
||||
on_release=on_release)
|
||||
def __init__(self, viewer, on_move=None, on_release=None, on_enter=None,
|
||||
maxdist=10, line_props=None,
|
||||
**kwargs):
|
||||
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')
|
||||
props.update(line_props if line_props is not None else {})
|
||||
@@ -50,21 +52,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]
|
||||
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
|
||||
|
||||
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)
|
||||
viewer.add_tool(self)
|
||||
|
||||
@property
|
||||
def end_points(self):
|
||||
@@ -81,14 +80,20 @@ class LineTool(CanvasToolBase):
|
||||
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 +103,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:
|
||||
@@ -125,8 +131,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.
|
||||
@@ -147,9 +153,9 @@ 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,
|
||||
super(ThickLineTool, self).__init__(viewer,
|
||||
on_move=on_move,
|
||||
on_enter=on_enter,
|
||||
on_release=on_release,
|
||||
@@ -161,9 +167,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
|
||||
@@ -191,16 +194,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()
|
||||
|
||||
@@ -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,9 +41,11 @@ 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):
|
||||
super(PaintTool, self).__init__(ax, on_move=on_move, on_enter=on_enter,
|
||||
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__(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)
|
||||
@@ -63,11 +65,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]
|
||||
viewer.add_tool(self)
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
@@ -123,7 +122,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 +139,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
|
||||
@@ -203,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()
|
||||
|
||||
@@ -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,18 +39,18 @@ 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,
|
||||
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)
|
||||
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)
|
||||
@@ -67,16 +67,17 @@ 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,
|
||||
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
|
||||
|
||||
@@ -201,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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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,106 @@ 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.artist.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)
|
||||
|
||||
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(QtGui.QMainWindow):
|
||||
"""Viewer for displaying images.
|
||||
|
||||
@@ -91,7 +190,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 +224,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 +343,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 +388,18 @@ class ImageViewer(QtGui.QMainWindow):
|
||||
else:
|
||||
self.status_message('')
|
||||
|
||||
def add_tool(self, tool):
|
||||
if self.useblit:
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user