213 lines
6.7 KiB
Python
213 lines
6.7 KiB
Python
|
import numpy as np
|
||
|
from matplotlib import lines
|
||
|
from ...viewer.canvastools.base import CanvasToolBase, ToolHandles
|
||
|
|
||
|
|
||
|
__all__ = ['LineTool', 'ThickLineTool']
|
||
|
|
||
|
|
||
|
class LineTool(CanvasToolBase):
|
||
|
"""Widget for line selection in a plot.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
manager : Viewer or PlotPlugin.
|
||
|
Skimage viewer or plot plugin object.
|
||
|
on_move : function
|
||
|
Function called whenever a control handle is moved.
|
||
|
This function must accept the end points of line as the only argument.
|
||
|
on_release : function
|
||
|
Function called whenever the control handle is released.
|
||
|
on_enter : function
|
||
|
Function called whenever the "enter" key is pressed.
|
||
|
maxdist : float
|
||
|
Maximum pixel distance allowed when selecting control handle.
|
||
|
line_props : dict
|
||
|
Properties for :class:`matplotlib.lines.Line2D`.
|
||
|
handle_props : dict
|
||
|
Marker properties for the handles (also see
|
||
|
:class:`matplotlib.lines.Line2D`).
|
||
|
|
||
|
Attributes
|
||
|
----------
|
||
|
end_points : 2D array
|
||
|
End points of line ((x1, y1), (x2, y2)).
|
||
|
"""
|
||
|
def __init__(self, manager, on_move=None, on_release=None, on_enter=None,
|
||
|
maxdist=10, line_props=None, handle_props=None,
|
||
|
**kwargs):
|
||
|
super(LineTool, self).__init__(manager, 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 {})
|
||
|
self.linewidth = props['linewidth']
|
||
|
self.maxdist = maxdist
|
||
|
self._active_pt = None
|
||
|
|
||
|
x = (0, 0)
|
||
|
y = (0, 0)
|
||
|
self._end_pts = np.transpose([x, y])
|
||
|
|
||
|
self._line = lines.Line2D(x, y, visible=False, animated=True, **props)
|
||
|
self.ax.add_line(self._line)
|
||
|
|
||
|
self._handles = ToolHandles(self.ax, x, y,
|
||
|
marker_props=handle_props)
|
||
|
self._handles.set_visible(False)
|
||
|
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.manager.add_tool(self)
|
||
|
|
||
|
@property
|
||
|
def end_points(self):
|
||
|
return self._end_pts.astype(int)
|
||
|
|
||
|
@end_points.setter
|
||
|
def end_points(self, pts):
|
||
|
self._end_pts = np.asarray(pts)
|
||
|
|
||
|
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 hit_test(self, event):
|
||
|
if event.button != 1 or not self.ax.in_axes(event):
|
||
|
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]])
|
||
|
|
||
|
def on_mouse_release(self, event):
|
||
|
if event.button != 1:
|
||
|
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:
|
||
|
return
|
||
|
if not self.ax.in_axes(event):
|
||
|
return
|
||
|
self.update(event.xdata, event.ydata)
|
||
|
self.callback_on_move(self.geometry)
|
||
|
|
||
|
def update(self, x=None, y=None):
|
||
|
if x is not None:
|
||
|
self._end_pts[self._active_pt, :] = x, y
|
||
|
self.end_points = self._end_pts
|
||
|
|
||
|
@property
|
||
|
def geometry(self):
|
||
|
return self.end_points
|
||
|
|
||
|
|
||
|
class ThickLineTool(LineTool):
|
||
|
"""Widget for line selection in a plot.
|
||
|
|
||
|
The thickness of the line can be varied using the mouse scroll wheel, or
|
||
|
with the '+' and '-' keys.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
manager : Viewer or PlotPlugin.
|
||
|
Skimage viewer or plot plugin object.
|
||
|
on_move : function
|
||
|
Function called whenever a control handle is moved.
|
||
|
This function must accept the end points of line as the only argument.
|
||
|
on_release : function
|
||
|
Function called whenever the control handle is released.
|
||
|
on_enter : function
|
||
|
Function called whenever the "enter" key is pressed.
|
||
|
on_change : function
|
||
|
Function called whenever the line thickness is changed.
|
||
|
maxdist : float
|
||
|
Maximum pixel distance allowed when selecting control handle.
|
||
|
line_props : dict
|
||
|
Properties for :class:`matplotlib.lines.Line2D`.
|
||
|
handle_props : dict
|
||
|
Marker properties for the handles (also see
|
||
|
:class:`matplotlib.lines.Line2D`).
|
||
|
|
||
|
Attributes
|
||
|
----------
|
||
|
end_points : 2D array
|
||
|
End points of line ((x1, y1), (x2, y2)).
|
||
|
"""
|
||
|
|
||
|
def __init__(self, manager, on_move=None, on_enter=None, on_release=None,
|
||
|
on_change=None, maxdist=10, line_props=None, handle_props=None):
|
||
|
super(ThickLineTool, self).__init__(manager,
|
||
|
on_move=on_move,
|
||
|
on_enter=on_enter,
|
||
|
on_release=on_release,
|
||
|
maxdist=maxdist,
|
||
|
line_props=line_props,
|
||
|
handle_props=handle_props)
|
||
|
|
||
|
if on_change is None:
|
||
|
def on_change(*args):
|
||
|
pass
|
||
|
self.callback_on_change = on_change
|
||
|
|
||
|
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):
|
||
|
if event.key == '+':
|
||
|
self._thicken_scan_line()
|
||
|
elif event.key == '-':
|
||
|
self._shrink_scan_line()
|
||
|
|
||
|
def _thicken_scan_line(self):
|
||
|
self.linewidth += 1
|
||
|
self.update()
|
||
|
self.callback_on_change(self.geometry)
|
||
|
|
||
|
def _shrink_scan_line(self):
|
||
|
if self.linewidth > 1:
|
||
|
self.linewidth -= 1
|
||
|
self.update()
|
||
|
self.callback_on_change(self.geometry)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__': # pragma: no cover
|
||
|
from ... import data
|
||
|
from ...viewer import ImageViewer
|
||
|
|
||
|
image = data.camera()
|
||
|
|
||
|
viewer = ImageViewer(image)
|
||
|
h, w = image.shape
|
||
|
|
||
|
line_tool = ThickLineTool(viewer)
|
||
|
line_tool.end_points = ([w/3, h/2], [2*w/3, h/2])
|
||
|
viewer.show()
|