Merge pull request #1065 from blink1073/multi_linetool_drawing

Multi linetool drawing
This commit is contained in:
Tony S Yu
2014-09-01 21:06:50 -05:00
13 changed files with 273 additions and 195 deletions
+32 -67
View File
@@ -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))
+30 -29
View File
@@ -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()
+15 -16
View File
@@ -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()
+20 -20
View File
@@ -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)
+2 -1
View File
@@ -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):
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+40 -50
View File
@@ -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()
+2 -2
View File
@@ -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
+5 -3
View File
@@ -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():
+123 -3
View File
@@ -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)