mirror of
https://github.com/wassname/scikit-image.git
synced 2026-08-12 12:30:16 +08:00
ENH: Add canvastools subpckages with base class and line tools
This commit is contained in:
@@ -0,0 +1 @@
|
||||
from line_tool import LineTool, ThickLineTool
|
||||
@@ -0,0 +1,109 @@
|
||||
import numpy as np
|
||||
import matplotlib as mpl
|
||||
from matplotlib import lines
|
||||
|
||||
|
||||
__all__ = ['CanvasToolBase', 'ToolHandles']
|
||||
|
||||
|
||||
class CanvasToolBase(object):
|
||||
"""Base canvas tool for matplotlib axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
"""
|
||||
def __init__(self, ax, useblit=None):
|
||||
self.ax = ax
|
||||
self.canvas = ax.figure.canvas
|
||||
self.cids = []
|
||||
self._artists = []
|
||||
self.active = True
|
||||
|
||||
if useblit is None:
|
||||
useblit = True if mpl.backends.backend.endswith('Agg') else False
|
||||
self.useblit = useblit
|
||||
if useblit:
|
||||
bbox = self.ax.bbox
|
||||
self.img_background = self.canvas.copy_from_bbox(bbox)
|
||||
|
||||
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.
|
||||
|
||||
This method (or a version of it) should be called at the beginning
|
||||
of any event callback.
|
||||
"""
|
||||
return not self.active
|
||||
|
||||
def set_visible(self, val):
|
||||
for a in self._artists:
|
||||
a.set_visible(val)
|
||||
|
||||
|
||||
class ToolHandles(object):
|
||||
"""Control handles for canvas tools.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ax : :class:`matplotlib.axes.Axes`
|
||||
Matplotlib axes where tool handles are displayed.
|
||||
x, y : 1D arrays
|
||||
Coordinates of control handles.
|
||||
marker : str
|
||||
Shape of marker used to display handle. See `matplotlib.pyplot.plot`.
|
||||
marker_props : see :class:`matplotlib.lines.Line2D`.
|
||||
"""
|
||||
def __init__(self, ax, x, y, marker='o', markerprops=None):
|
||||
self.ax = ax
|
||||
|
||||
props = dict(mfc='w', ls='none', alpha=0.5, visible=False)
|
||||
props.update(markerprops if markerprops is not None else {})
|
||||
self._markers = lines.Line2D(x, y, marker=marker, **props)
|
||||
self.ax.add_line(self._markers)
|
||||
self.artist = self._markers
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
return self._markers.get_xdata()
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
return self._markers.get_ydata()
|
||||
|
||||
def set_data(self, pts, y=None):
|
||||
"""Set x and y positions of handles"""
|
||||
if y is not None:
|
||||
x = pts
|
||||
pts = np.array([x, y])
|
||||
self._markers.set_data(pts)
|
||||
|
||||
def set_visible(self, val):
|
||||
self._markers.set_visible(val)
|
||||
|
||||
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))
|
||||
# Transform data coordinates to pixel coordinates.
|
||||
pts = self.ax.transData.transform(pts)
|
||||
diff = pts - ((x, y))
|
||||
dist = np.sqrt(np.sum(diff**2, axis=1))
|
||||
return np.argmin(dist), np.min(dist)
|
||||
@@ -0,0 +1,169 @@
|
||||
import numpy as np
|
||||
from matplotlib import lines
|
||||
|
||||
from base import CanvasToolBase, ToolHandles
|
||||
|
||||
|
||||
__all__ = ['LineTool', 'ThickLineTool']
|
||||
|
||||
|
||||
class LineTool(CanvasToolBase):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
on_update : function
|
||||
Function accepting end points of line as the only argument.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
end_pts : 2D array
|
||||
End points of line ((x1, y1), (x2, y2)).
|
||||
"""
|
||||
def __init__(self, ax, x, y, on_update=None, on_enter=None, maxdist=10,
|
||||
lineprops=None):
|
||||
super(LineTool, self).__init__(ax)
|
||||
|
||||
#TODO: Figure out how to cleanly restore image background for useblit
|
||||
self.useblit = False
|
||||
|
||||
props = dict(color='r', linewidth=1, alpha=0.4, solid_capstyle='butt')
|
||||
props.update(lineprops if lineprops is not None else {})
|
||||
self.linewidth = props['linewidth']
|
||||
self.maxdist = maxdist
|
||||
self._active_pt = None
|
||||
|
||||
if on_update is None:
|
||||
on_update = lambda *args: None
|
||||
self.on_update = on_update
|
||||
|
||||
if on_enter is None:
|
||||
on_enter = lambda *args: None
|
||||
self.on_enter = on_enter
|
||||
|
||||
self._init_end_pts = np.transpose([x, y])
|
||||
self.end_pts = self._init_end_pts.copy()
|
||||
|
||||
self._line = lines.Line2D(x, y, **props)
|
||||
ax.add_line(self._line)
|
||||
|
||||
self._handles = ToolHandles(ax, x, y)
|
||||
self._handles.set_visible(True)
|
||||
self._artists = [self._line, self._handles.artist]
|
||||
|
||||
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)
|
||||
|
||||
def on_mouse_press(self, event):
|
||||
if event.button != 1:
|
||||
return
|
||||
if event.inaxes == None:
|
||||
return
|
||||
idx, px_dist = self._handles.closest(event.x, event.y)
|
||||
if px_dist < self.maxdist:
|
||||
self._active_pt = idx
|
||||
|
||||
def on_mouse_release(self, event):
|
||||
if event.button != 1:
|
||||
return
|
||||
self._active_pt = None
|
||||
|
||||
def on_move(self, event):
|
||||
if event.button != 1:
|
||||
return
|
||||
if self._active_pt is None:
|
||||
return
|
||||
if not self.ax.in_axes(event):
|
||||
return
|
||||
x, y = event.xdata, event.ydata
|
||||
self.update(x, y)
|
||||
|
||||
def on_key_press(self, event):
|
||||
if event.key == 'enter':
|
||||
self.on_enter(self.end_pts)
|
||||
self.set_visible(False)
|
||||
self.redraw()
|
||||
|
||||
def reset(self):
|
||||
self.end_pts = self._init_end_pts.copy()
|
||||
self._line.set_data(np.transpose(self.end_pts))
|
||||
self._handles.set_data(np.transpose(self.end_pts))
|
||||
self.update(None, None)
|
||||
|
||||
def update(self, x, y):
|
||||
if x is not None:
|
||||
self.end_pts[self._active_pt, :] = x, y
|
||||
self._line.set_data(np.transpose(self.end_pts))
|
||||
self._handles.set_data(np.transpose(self.end_pts))
|
||||
self._line.set_linewidth(self.linewidth)
|
||||
|
||||
self.ax.relim()
|
||||
self.redraw()
|
||||
|
||||
self.on_update(self.end_pts)
|
||||
|
||||
def redraw(self):
|
||||
if self.useblit:
|
||||
# self.canvas.restore_region(self.img_background)
|
||||
self.ax.draw_artist(self._line)
|
||||
self.canvas.blit(self.ax.bbox)
|
||||
else:
|
||||
self.canvas.draw_idle()
|
||||
|
||||
|
||||
class ThickLineTool(LineTool):
|
||||
|
||||
def __init__(self, ax, x, y, on_update=None, on_enter=None, maxdist=10,
|
||||
lineprops=None):
|
||||
super(ThickLineTool, self).__init__(ax, x, y, on_update=on_update,
|
||||
on_enter=on_enter, maxdist=maxdist,
|
||||
lineprops=lineprops)
|
||||
|
||||
self.connect_event('key_press_event', self.on_key_press)
|
||||
self.connect_event('scroll_event', self.on_scroll)
|
||||
|
||||
def on_scroll(self, event):
|
||||
if not event.inaxes:
|
||||
return
|
||||
if event.button == 'up':
|
||||
self._thicken_scan_line()
|
||||
elif event.button == 'down':
|
||||
self._shrink_scan_line()
|
||||
|
||||
def on_key_press(self, event):
|
||||
super(ThickLineTool, self).on_key_press(event)
|
||||
|
||||
if event.key == '+':
|
||||
self._thicken_scan_line()
|
||||
elif event.key == '-':
|
||||
self._shrink_scan_line()
|
||||
elif event.key == 'r':
|
||||
self.reset()
|
||||
|
||||
def _thicken_scan_line(self):
|
||||
self.linewidth += 1
|
||||
self.update(None, None)
|
||||
|
||||
def _shrink_scan_line(self):
|
||||
if self.linewidth > 1:
|
||||
self.linewidth -= 1
|
||||
self.update(None, None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import matplotlib.pyplot as plt
|
||||
from skimage import data
|
||||
|
||||
image = data.camera()
|
||||
|
||||
f, ax = plt.subplots()
|
||||
ax.imshow(image, interpolation='nearest')
|
||||
h, w = image.shape
|
||||
|
||||
def printer(pts):
|
||||
x, y = np.transpose(pts)
|
||||
print "length = %0.2f" % np.sqrt(np.diff(x)**2 + np.diff(y)**2)
|
||||
|
||||
# line_tool = LineTool(ax, [w/3, 2*w/3], [h/2, h/2])
|
||||
line_tool = ThickLineTool(ax, [w/3, 2*w/3], [h/2, h/2], on_enter=printer)
|
||||
plt.show()
|
||||
Reference in New Issue
Block a user