mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-09 20:30:07 +08:00
ENH: Add rectangle tool and crop plugin example
This commit is contained in:
@@ -1 +1,2 @@
|
||||
from linetool import LineTool, ThickLineTool
|
||||
from recttool import RectangleTool
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
import matplotlib.widgets as mwidgets
|
||||
|
||||
from skimage.viewer.canvastools.base import CanvasToolBase
|
||||
from skimage.viewer.canvastools.base import ToolHandles
|
||||
|
||||
|
||||
__all__ = ['RectangleTool']
|
||||
|
||||
|
||||
class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase):
|
||||
"""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
|
||||
----------
|
||||
ax : :class:`matplotlib.axes.Axes
|
||||
on_enter : function
|
||||
Function accepting rectangle extents as the only argument; called
|
||||
whenever "Enter" key is pressed. If None, print extents of rectangle.
|
||||
maxdist : float
|
||||
Maximum distance in pixels for selection of a control handle
|
||||
(i.e. corner or edge) handle.
|
||||
rectprops : dict
|
||||
Properties for :class:`matplotlib.patches.Rectangle`. This class
|
||||
redefines defaults in :class:`matplotlib.widgets.RectangleSelector`.
|
||||
kwargs : see :class:`matplotlib.widgets.RectangleSelector`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
extents : tuple
|
||||
Rectangle extents: (xmin, xmax, ymin, ymax).
|
||||
"""
|
||||
|
||||
def __init__(self, ax, on_update=None, on_enter=None, rectprops=None,
|
||||
maxdist=10, **kwargs):
|
||||
CanvasToolBase.__init__(self, ax, on_update=on_update,
|
||||
on_enter=on_enter)
|
||||
|
||||
props = dict(edgecolor=None, facecolor='r', alpha=0.15)
|
||||
props.update(rectprops if rectprops is not None else {})
|
||||
if props['edgecolor'] is None:
|
||||
props['edgecolor'] = props['facecolor']
|
||||
mwidgets.RectangleSelector.__init__(self, ax, lambda *args: None,
|
||||
rectprops=props,
|
||||
useblit=self.useblit)
|
||||
# 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.on_enter = on_enter
|
||||
|
||||
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._edge_order = ['W', 'N', 'E', 'S']
|
||||
xe, ye = self.edge_centers
|
||||
self._edge_handles = ToolHandles(ax, xe, ye, marker='s',
|
||||
marker_props=props)
|
||||
|
||||
self._artists = [self._rect,
|
||||
self._corner_handles.artist,
|
||||
self._edge_handles.artist]
|
||||
|
||||
@property
|
||||
def _rect_bbox(self):
|
||||
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
|
||||
|
||||
def release(self, event):
|
||||
mwidgets.RectangleSelector.release(self, event)
|
||||
self._extents_on_press = None
|
||||
# Undo hiding of rectangle and redraw.
|
||||
self.set_visible(True)
|
||||
self.redraw()
|
||||
|
||||
def press(self, event):
|
||||
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)
|
||||
mwidgets.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 onmove(self, event):
|
||||
|
||||
if self.eventpress is None or self.ignore(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
|
||||
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.redraw()
|
||||
|
||||
@property
|
||||
def geometry(self):
|
||||
return self.extents
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import matplotlib.pyplot as plt
|
||||
from skimage import data
|
||||
|
||||
f, ax = plt.subplots()
|
||||
ax.imshow(data.camera(), interpolation='nearest')
|
||||
|
||||
rect_tool = RectangleTool(ax)
|
||||
plt.show()
|
||||
print "Final selection:",
|
||||
rect_tool.on_enter(rect_tool.extents)
|
||||
@@ -0,0 +1,34 @@
|
||||
from .base import Plugin
|
||||
from ..canvastools import RectangleTool
|
||||
from skimage.viewer.widgets.history import SaveButtons
|
||||
|
||||
|
||||
__all__ = ['Crop']
|
||||
|
||||
|
||||
class Crop(Plugin):
|
||||
name = 'Crop'
|
||||
|
||||
def __init__(self, maxdist=10, **kwargs):
|
||||
super(Crop, self).__init__(**kwargs)
|
||||
self.maxdist = maxdist
|
||||
self.add_widget(SaveButtons())
|
||||
print self.help()
|
||||
|
||||
def attach(self, image_viewer):
|
||||
super(Crop, self).attach(image_viewer)
|
||||
|
||||
self.rect_tool = RectangleTool(self.image_viewer.ax,
|
||||
maxdist=self.maxdist,
|
||||
on_enter=self.crop)
|
||||
|
||||
def help(self):
|
||||
helpstr = ("Crop tool",
|
||||
"Select rectangular region and press enter to crop.")
|
||||
return '\n'.join(helpstr)
|
||||
|
||||
def crop(self, extents):
|
||||
xmin, xmax, ymin, ymax = extents
|
||||
image = self.image_viewer.image[ymin:ymax+1, xmin:xmax+1]
|
||||
self.image_viewer.image = image
|
||||
self.image_viewer.ax.relim()
|
||||
@@ -137,10 +137,21 @@ class ImageViewer(QMainWindow):
|
||||
def image(self, image):
|
||||
self._img = image
|
||||
self._image_plot.set_array(image)
|
||||
|
||||
# Adjust size if new image shape doesn't match the original
|
||||
h, w = image.shape
|
||||
# update data coordinates (otherwise pixel coordinates are off)
|
||||
self._image_plot.set_extent((0, w, h, 0))
|
||||
# update display (otherwise image doesn't fill the canvas)
|
||||
self.ax.set_xlim(0, w)
|
||||
self.ax.set_ylim(h, 0)
|
||||
|
||||
# update color range
|
||||
clim = dtype_range[image.dtype.type]
|
||||
if clim[0] < 0 and image.min() >= 0:
|
||||
clim = (0, clim[1])
|
||||
self._image_plot.set_clim(clim)
|
||||
|
||||
self.redraw()
|
||||
|
||||
def reset_image(self):
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from skimage import data
|
||||
from skimage.viewer import ImageViewer
|
||||
from skimage.viewer.plugins.crop import Crop
|
||||
|
||||
|
||||
image = data.camera()
|
||||
viewer = ImageViewer(image)
|
||||
viewer += Crop()
|
||||
viewer.show()
|
||||
Reference in New Issue
Block a user