Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
|
@ -0,0 +1,3 @@
|
|||
from .linetool import LineTool, ThickLineTool
|
||||
from .recttool import RectangleTool
|
||||
from .painttool import PaintTool
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
135
venv/Lib/site-packages/skimage/viewer/canvastools/base.py
Normal file
135
venv/Lib/site-packages/skimage/viewer/canvastools/base.py
Normal file
|
@ -0,0 +1,135 @@
|
|||
import numpy as np
|
||||
from matplotlib import lines
|
||||
|
||||
__all__ = ['CanvasToolBase', 'ToolHandles']
|
||||
|
||||
|
||||
def _pass(*args):
|
||||
pass
|
||||
|
||||
|
||||
class CanvasToolBase(object):
|
||||
"""Base canvas tool for matplotlib axes.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(self, manager, on_move=None, on_enter=None, on_release=None,
|
||||
useblit=True, ax=None):
|
||||
self.manager = manager
|
||||
self.ax = manager.ax
|
||||
self.artists = []
|
||||
self.active = True
|
||||
|
||||
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 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 hit_test(self, event):
|
||||
return False
|
||||
|
||||
def redraw(self):
|
||||
self.manager.redraw()
|
||||
|
||||
def set_visible(self, val):
|
||||
for artist in self.artists:
|
||||
artist.set_visible(val)
|
||||
|
||||
def on_key_press(self, event):
|
||||
if event.key == 'enter':
|
||||
self.callback_on_enter(self.geometry)
|
||||
self.set_visible(False)
|
||||
self.manager.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.manager.remove_tool(self)
|
||||
|
||||
@property
|
||||
def geometry(self):
|
||||
"""Geometry information that gets passed to callback functions."""
|
||||
return None
|
||||
|
||||
|
||||
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 : dict
|
||||
Additional marker properties. See :class:`matplotlib.lines.Line2D`.
|
||||
"""
|
||||
def __init__(self, ax, x, y, marker='o', marker_props=None):
|
||||
self.ax = ax
|
||||
|
||||
props = dict(marker=marker, markersize=7, mfc='w', ls='none',
|
||||
alpha=0.5, visible=False)
|
||||
props.update(marker_props if marker_props is not None else {})
|
||||
self._markers = lines.Line2D(x, y, animated=True, **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 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)
|
212
venv/Lib/site-packages/skimage/viewer/canvastools/linetool.py
Normal file
212
venv/Lib/site-packages/skimage/viewer/canvastools/linetool.py
Normal file
|
@ -0,0 +1,212 @@
|
|||
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()
|
238
venv/Lib/site-packages/skimage/viewer/canvastools/painttool.py
Normal file
238
venv/Lib/site-packages/skimage/viewer/canvastools/painttool.py
Normal file
|
@ -0,0 +1,238 @@
|
|||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.colors as mcolors
|
||||
LABELS_CMAP = mcolors.ListedColormap(['white', 'red', 'dodgerblue', 'gold',
|
||||
'greenyellow', 'blueviolet'])
|
||||
from ...viewer.canvastools.base import CanvasToolBase
|
||||
|
||||
|
||||
__all__ = ['PaintTool']
|
||||
|
||||
|
||||
class PaintTool(CanvasToolBase):
|
||||
"""Widget for painting on top of a plot.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
manager : Viewer or PlotPlugin.
|
||||
Skimage viewer or plot plugin object.
|
||||
overlay_shape : shape tuple
|
||||
2D shape tuple used to initialize overlay image.
|
||||
radius : int
|
||||
The size of the paint cursor.
|
||||
alpha : float (between [0, 1])
|
||||
Opacity of overlay.
|
||||
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.
|
||||
rect_props : dict
|
||||
Properties for :class:`matplotlib.patches.Rectangle`. This class
|
||||
redefines defaults in :class:`matplotlib.widgets.RectangleSelector`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
overlay : array
|
||||
Overlay of painted labels displayed on top of image.
|
||||
label : int
|
||||
Current paint color.
|
||||
|
||||
Examples
|
||||
----------
|
||||
>>> from skimage.data import camera
|
||||
>>> import matplotlib.pyplot as plt
|
||||
>>> from skimage.viewer.canvastools import PaintTool
|
||||
>>> import numpy as np
|
||||
|
||||
>>> img = camera() #doctest: +SKIP
|
||||
|
||||
>>> ax = plt.subplot(111) #doctest: +SKIP
|
||||
>>> plt.imshow(img, cmap=plt.cm.gray) #doctest: +SKIP
|
||||
>>> p = PaintTool(ax,np.shape(img[:-1]),10,0.2) #doctest: +SKIP
|
||||
>>> plt.show() #doctest: +SKIP
|
||||
|
||||
>>> mask = p.overlay #doctest: +SKIP
|
||||
>>> plt.imshow(mask,cmap=plt.cm.gray) #doctest: +SKIP
|
||||
>>> plt.show() #doctest: +SKIP
|
||||
"""
|
||||
def __init__(self, manager, overlay_shape, radius=5, alpha=0.3,
|
||||
on_move=None, on_release=None, on_enter=None,
|
||||
rect_props=None):
|
||||
super(PaintTool, self).__init__(manager, on_move=on_move,
|
||||
on_enter=on_enter,
|
||||
on_release=on_release)
|
||||
|
||||
props = dict(edgecolor='r', facecolor='0.7', alpha=0.5, animated=True)
|
||||
props.update(rect_props if rect_props is not None else {})
|
||||
|
||||
self.alpha = alpha
|
||||
self.cmap = LABELS_CMAP
|
||||
self._overlay_plot = None
|
||||
self.shape = overlay_shape[:2]
|
||||
|
||||
self._cursor = plt.Rectangle((0, 0), 0, 0, **props)
|
||||
self._cursor.set_visible(False)
|
||||
self.ax.add_patch(self._cursor)
|
||||
|
||||
# `label` and `radius` can only be set after initializing `_cursor`
|
||||
self.label = 1
|
||||
self.radius = radius
|
||||
|
||||
# Note that the order is important: Redraw cursor *after* overlay
|
||||
self.artists = [self._overlay_plot, self._cursor]
|
||||
self.manager.add_tool(self)
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
return self._label
|
||||
|
||||
@label.setter
|
||||
def label(self, value):
|
||||
if value >= self.cmap.N:
|
||||
raise ValueError('Maximum label value = %s' % len(self.cmap - 1))
|
||||
self._label = value
|
||||
self._cursor.set_edgecolor(self.cmap(value))
|
||||
|
||||
@property
|
||||
def radius(self):
|
||||
return self._radius
|
||||
|
||||
@radius.setter
|
||||
def radius(self, r):
|
||||
self._radius = r
|
||||
self._width = 2 * r + 1
|
||||
self._cursor.set_width(self._width)
|
||||
self._cursor.set_height(self._width)
|
||||
self.window = CenteredWindow(r, self._shape)
|
||||
|
||||
@property
|
||||
def overlay(self):
|
||||
return self._overlay
|
||||
|
||||
@overlay.setter
|
||||
def overlay(self, image):
|
||||
self._overlay = image
|
||||
if image is None:
|
||||
self.ax.images.remove(self._overlay_plot)
|
||||
self._overlay_plot = None
|
||||
elif self._overlay_plot is None:
|
||||
props = dict(cmap=self.cmap, alpha=self.alpha,
|
||||
norm=mcolors.NoNorm(vmin=0, vmax=self.cmap.N),
|
||||
animated=True)
|
||||
self._overlay_plot = self.ax.imshow(image, **props)
|
||||
else:
|
||||
self._overlay_plot.set_data(image)
|
||||
self.redraw()
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._shape
|
||||
|
||||
@shape.setter
|
||||
def shape(self, shape):
|
||||
self._shape = shape
|
||||
if not self._overlay_plot is None:
|
||||
self._overlay_plot.set_extent((-0.5, shape[1] + 0.5,
|
||||
shape[0] + 0.5, -0.5))
|
||||
self.radius = self._radius
|
||||
self.overlay = np.zeros(shape, dtype='uint8')
|
||||
|
||||
def on_key_press(self, event):
|
||||
if event.key == 'enter':
|
||||
self.callback_on_enter(self.geometry)
|
||||
self.redraw()
|
||||
|
||||
def on_mouse_press(self, event):
|
||||
if event.button != 1 or not self.ax.in_axes(event):
|
||||
return
|
||||
self.update_cursor(event.xdata, event.ydata)
|
||||
self.update_overlay(event.xdata, event.ydata)
|
||||
|
||||
def on_mouse_release(self, event):
|
||||
if event.button != 1:
|
||||
return
|
||||
self.callback_on_release(self.geometry)
|
||||
|
||||
def on_move(self, event):
|
||||
if not self.ax.in_axes(event):
|
||||
self._cursor.set_visible(False)
|
||||
self.redraw() # make sure cursor is not visible
|
||||
return
|
||||
self._cursor.set_visible(True)
|
||||
|
||||
self.update_cursor(event.xdata, event.ydata)
|
||||
if event.button != 1:
|
||||
self.redraw() # update cursor position
|
||||
return
|
||||
self.update_overlay(event.xdata, event.ydata)
|
||||
self.callback_on_move(self.geometry)
|
||||
|
||||
def update_overlay(self, x, y):
|
||||
overlay = self.overlay
|
||||
overlay[self.window.at(y, x)] = self.label
|
||||
# Note that overlay calls `redraw`
|
||||
self.overlay = overlay
|
||||
|
||||
def update_cursor(self, x, y):
|
||||
x = x - self.radius - 1
|
||||
y = y - self.radius - 1
|
||||
self._cursor.set_xy((x, y))
|
||||
|
||||
@property
|
||||
def geometry(self):
|
||||
return self.overlay
|
||||
|
||||
|
||||
class CenteredWindow(object):
|
||||
"""Window that create slices numpy arrays over 2D windows.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> a = np.arange(16).reshape(4, 4)
|
||||
>>> w = CenteredWindow(1, a.shape)
|
||||
>>> a[w.at(1, 1)]
|
||||
array([[ 0, 1, 2],
|
||||
[ 4, 5, 6],
|
||||
[ 8, 9, 10]])
|
||||
>>> a[w.at(0, 0)]
|
||||
array([[0, 1],
|
||||
[4, 5]])
|
||||
>>> a[w.at(4, 3)]
|
||||
array([[14, 15]])
|
||||
"""
|
||||
def __init__(self, radius, array_shape):
|
||||
self.radius = radius
|
||||
self.array_shape = array_shape
|
||||
|
||||
def at(self, row, col):
|
||||
h, w = self.array_shape
|
||||
r = round(self.radius)
|
||||
# Note: the int() cast is necessary because row and col are np.float64,
|
||||
# which does not get cast by round(), unlike a normal Python float:
|
||||
# >>> round(4.5)
|
||||
# 4
|
||||
# >>> round(np.float64(4.5))
|
||||
# 4.0
|
||||
# >>> int(round(np.float64(4.5)))
|
||||
# 4
|
||||
row, col = int(round(row)), int(round(col))
|
||||
xmin = max(0, col - r)
|
||||
xmax = min(w, col + r + 1)
|
||||
ymin = max(0, row - r)
|
||||
ymax = min(h, row + r + 1)
|
||||
return (slice(ymin, ymax), slice(xmin, xmax))
|
||||
|
||||
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
np.testing.rundocs()
|
||||
from ... import data
|
||||
from ...viewer import ImageViewer
|
||||
|
||||
image = data.camera()
|
||||
|
||||
viewer = ImageViewer(image)
|
||||
paint_tool = PaintTool(viewer, image.shape)
|
||||
viewer.show()
|
245
venv/Lib/site-packages/skimage/viewer/canvastools/recttool.py
Normal file
245
venv/Lib/site-packages/skimage/viewer/canvastools/recttool.py
Normal file
|
@ -0,0 +1,245 @@
|
|||
from matplotlib.widgets import RectangleSelector
|
||||
from ...viewer.canvastools.base import CanvasToolBase
|
||||
from ...viewer.canvastools.base import ToolHandles
|
||||
|
||||
|
||||
__all__ = ['RectangleTool']
|
||||
|
||||
|
||||
class RectangleTool(CanvasToolBase, RectangleSelector):
|
||||
"""Widget for selecting a rectangular region in a plot.
|
||||
|
||||
After making the desired selection, press "Enter" to accept the selection
|
||||
and call the `on_enter` callback function.
|
||||
|
||||
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 rectangle extents 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.
|
||||
rect_props : dict
|
||||
Properties for :class:`matplotlib.patches.Rectangle`. This class
|
||||
redefines defaults in :class:`matplotlib.widgets.RectangleSelector`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
extents : tuple
|
||||
Rectangle extents: (xmin, xmax, ymin, ymax).
|
||||
|
||||
Examples
|
||||
----------
|
||||
>>> from skimage import data
|
||||
>>> from skimage.viewer import ImageViewer
|
||||
>>> from skimage.viewer.canvastools import RectangleTool
|
||||
>>> from skimage.draw import line
|
||||
>>> from skimage.draw import set_color
|
||||
|
||||
>>> viewer = ImageViewer(data.coffee()) # doctest: +SKIP
|
||||
|
||||
>>> def print_the_rect(extents):
|
||||
... global viewer
|
||||
... im = viewer.image
|
||||
... coord = np.int64(extents)
|
||||
... [rr1, cc1] = line(coord[2],coord[0],coord[2],coord[1])
|
||||
... [rr2, cc2] = line(coord[2],coord[1],coord[3],coord[1])
|
||||
... [rr3, cc3] = line(coord[3],coord[1],coord[3],coord[0])
|
||||
... [rr4, cc4] = line(coord[3],coord[0],coord[2],coord[0])
|
||||
... set_color(im, (rr1, cc1), [255, 255, 0])
|
||||
... set_color(im, (rr2, cc2), [0, 255, 255])
|
||||
... set_color(im, (rr3, cc3), [255, 0, 255])
|
||||
... set_color(im, (rr4, cc4), [0, 0, 0])
|
||||
... viewer.image=im
|
||||
|
||||
>>> rect_tool = RectangleTool(viewer, on_enter=print_the_rect) # doctest: +SKIP
|
||||
>>> viewer.show() # doctest: +SKIP
|
||||
"""
|
||||
|
||||
def __init__(self, manager, on_move=None, on_release=None, on_enter=None,
|
||||
maxdist=10, rect_props=None):
|
||||
self._rect = None
|
||||
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, manager.ax, lambda *args: None,
|
||||
rectprops=props)
|
||||
CanvasToolBase.__init__(self, manager, on_move=on_move,
|
||||
on_enter=on_enter, on_release=on_release)
|
||||
|
||||
# Events are handled by the viewer
|
||||
try:
|
||||
self.disconnect_events()
|
||||
except AttributeError:
|
||||
# disconnect the events manually (hack for older mpl versions)
|
||||
[self.canvas.mpl_disconnect(i) for i in range(10)]
|
||||
|
||||
# Alias rectangle attribute, which is initialized in RectangleSelector.
|
||||
self._rect = self.to_draw
|
||||
self._rect.set_animated(True)
|
||||
|
||||
self.maxdist = maxdist
|
||||
self.active_handle = None
|
||||
self._extents_on_press = None
|
||||
|
||||
if on_enter is None:
|
||||
def on_enter(extents):
|
||||
print("(xmin=%.3g, xmax=%.3g, ymin=%.3g, ymax=%.3g)" % extents)
|
||||
self.callback_on_enter = on_enter
|
||||
|
||||
props = dict(mec=props['edgecolor'])
|
||||
self._corner_order = ['NW', 'NE', 'SE', 'SW']
|
||||
xc, yc = self.corners
|
||||
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(self.ax, xe, ye, marker='s',
|
||||
marker_props=props)
|
||||
|
||||
self.artists = [self._rect,
|
||||
self._corner_handles.artist,
|
||||
self._edge_handles.artist]
|
||||
self.manager.add_tool(self)
|
||||
|
||||
@property
|
||||
def _rect_bbox(self):
|
||||
if not self._rect:
|
||||
return 0, 0, 0, 0
|
||||
x0 = self._rect.get_x()
|
||||
y0 = self._rect.get_y()
|
||||
width = self._rect.get_width()
|
||||
height = self._rect.get_height()
|
||||
return x0, y0, width, height
|
||||
|
||||
@property
|
||||
def corners(self):
|
||||
"""Corners of rectangle from lower left, moving clockwise."""
|
||||
x0, y0, width, height = self._rect_bbox
|
||||
xc = x0, x0 + width, x0 + width, x0
|
||||
yc = y0, y0, y0 + height, y0 + height
|
||||
return xc, yc
|
||||
|
||||
@property
|
||||
def edge_centers(self):
|
||||
"""Midpoint of rectangle edges from left, moving clockwise."""
|
||||
x0, y0, width, height = self._rect_bbox
|
||||
w = width / 2.
|
||||
h = height / 2.
|
||||
xe = x0, x0 + w, x0 + width, x0 + w
|
||||
ye = y0 + h, y0, y0 + h, y0 + height
|
||||
return xe, ye
|
||||
|
||||
@property
|
||||
def extents(self):
|
||||
"""Return (xmin, xmax, ymin, ymax)."""
|
||||
x0, y0, width, height = self._rect_bbox
|
||||
xmin, xmax = sorted([x0, x0 + width])
|
||||
ymin, ymax = sorted([y0, y0 + height])
|
||||
return xmin, xmax, ymin, ymax
|
||||
|
||||
@extents.setter
|
||||
def extents(self, extents):
|
||||
x1, x2, y1, y2 = extents
|
||||
xmin, xmax = sorted([x1, x2])
|
||||
ymin, ymax = sorted([y1, y2])
|
||||
# Update displayed rectangle
|
||||
self._rect.set_x(xmin)
|
||||
self._rect.set_y(ymin)
|
||||
self._rect.set_width(xmax - xmin)
|
||||
self._rect.set_height(ymax - ymin)
|
||||
# Update displayed handles
|
||||
self._corner_handles.set_data(*self.corners)
|
||||
self._edge_handles.set_data(*self.edge_centers)
|
||||
|
||||
self.set_visible(True)
|
||||
self.redraw()
|
||||
|
||||
def on_mouse_release(self, event):
|
||||
if event.button != 1:
|
||||
return
|
||||
if not self.ax.in_axes(event):
|
||||
self.eventpress = None
|
||||
return
|
||||
RectangleSelector.release(self, event)
|
||||
self._extents_on_press = None
|
||||
# Undo hiding of rectangle and redraw.
|
||||
self.set_visible(True)
|
||||
self.redraw()
|
||||
self.callback_on_release(self.geometry)
|
||||
|
||||
def on_mouse_press(self, event):
|
||||
if event.button != 1 or not self.ax.in_axes(event):
|
||||
return
|
||||
self._set_active_handle(event)
|
||||
if self.active_handle is None:
|
||||
# Clear previous rectangle before drawing new rectangle.
|
||||
self.set_visible(False)
|
||||
self.redraw()
|
||||
self.set_visible(True)
|
||||
RectangleSelector.press(self, event)
|
||||
|
||||
def _set_active_handle(self, event):
|
||||
"""Set active handle based on the location of the mouse event"""
|
||||
# Note: event.xdata/ydata in data coordinates, event.x/y in pixels
|
||||
c_idx, c_dist = self._corner_handles.closest(event.x, event.y)
|
||||
e_idx, e_dist = self._edge_handles.closest(event.x, event.y)
|
||||
|
||||
# Set active handle as closest handle, if mouse click is close enough.
|
||||
if c_dist > self.maxdist and e_dist > self.maxdist:
|
||||
self.active_handle = None
|
||||
return
|
||||
elif c_dist < e_dist:
|
||||
self.active_handle = self._corner_order[c_idx]
|
||||
else:
|
||||
self.active_handle = self._edge_order[e_idx]
|
||||
|
||||
# Save coordinates of rectangle at the start of handle movement.
|
||||
x1, x2, y1, y2 = self.extents
|
||||
# Switch variables so that only x2 and/or y2 are updated on move.
|
||||
if self.active_handle in ['W', 'SW', 'NW']:
|
||||
x1, x2 = x2, event.xdata
|
||||
if self.active_handle in ['N', 'NW', 'NE']:
|
||||
y1, y2 = y2, event.ydata
|
||||
self._extents_on_press = x1, x2, y1, y2
|
||||
|
||||
def on_move(self, event):
|
||||
if self.eventpress is None or not self.ax.in_axes(event):
|
||||
return
|
||||
|
||||
if self.active_handle is None:
|
||||
# New rectangle
|
||||
x1 = self.eventpress.xdata
|
||||
y1 = self.eventpress.ydata
|
||||
x2, y2 = event.xdata, event.ydata
|
||||
else:
|
||||
x1, x2, y1, y2 = self._extents_on_press
|
||||
if self.active_handle in ['E', 'W'] + self._corner_order:
|
||||
x2 = event.xdata
|
||||
if self.active_handle in ['N', 'S'] + self._corner_order:
|
||||
y2 = event.ydata
|
||||
self.extents = (x1, x2, y1, y2)
|
||||
self.callback_on_move(self.geometry)
|
||||
|
||||
@property
|
||||
def geometry(self):
|
||||
return self.extents
|
||||
|
||||
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
from ...viewer import ImageViewer
|
||||
from ... import data
|
||||
|
||||
viewer = ImageViewer(data.camera())
|
||||
|
||||
rect_tool = RectangleTool(viewer)
|
||||
viewer.show()
|
||||
print("Final selection:")
|
||||
rect_tool.callback_on_enter(rect_tool.extents)
|
Loading…
Add table
Add a link
Reference in a new issue