949 lines
32 KiB
Python
949 lines
32 KiB
Python
import warnings
|
|
import numpy as np
|
|
|
|
from .._shared._geometry import polygon_clip
|
|
from ._draw import (_coords_inside_image, _line, _line_aa,
|
|
_polygon, _ellipse_perimeter,
|
|
_circle_perimeter, _circle_perimeter_aa,
|
|
_bezier_curve)
|
|
|
|
|
|
def _ellipse_in_shape(shape, center, radii, rotation=0.):
|
|
"""Generate coordinates of points within ellipse bounded by shape.
|
|
|
|
Parameters
|
|
----------
|
|
shape : iterable of ints
|
|
Shape of the input image. Must be at least length 2. Only the first
|
|
two values are used to determine the extent of the input image.
|
|
center : iterable of floats
|
|
(row, column) position of center inside the given shape.
|
|
radii : iterable of floats
|
|
Size of two half axes (for row and column)
|
|
rotation : float, optional
|
|
Rotation of the ellipse defined by the above, in radians
|
|
in range (-PI, PI), in contra clockwise direction,
|
|
with respect to the column-axis.
|
|
|
|
Returns
|
|
-------
|
|
rows : iterable of ints
|
|
Row coordinates representing values within the ellipse.
|
|
cols : iterable of ints
|
|
Corresponding column coordinates representing values within the ellipse.
|
|
"""
|
|
r_lim, c_lim = np.ogrid[0:float(shape[0]), 0:float(shape[1])]
|
|
r_org, c_org = center
|
|
r_rad, c_rad = radii
|
|
rotation %= np.pi
|
|
sin_alpha, cos_alpha = np.sin(rotation), np.cos(rotation)
|
|
r, c = (r_lim - r_org), (c_lim - c_org)
|
|
distances = ((r * cos_alpha + c * sin_alpha) / r_rad) ** 2 \
|
|
+ ((r * sin_alpha - c * cos_alpha) / c_rad) ** 2
|
|
return np.nonzero(distances < 1)
|
|
|
|
|
|
def ellipse(r, c, r_radius, c_radius, shape=None, rotation=0.):
|
|
"""Generate coordinates of pixels within ellipse.
|
|
|
|
Parameters
|
|
----------
|
|
r, c : double
|
|
Centre coordinate of ellipse.
|
|
r_radius, c_radius : double
|
|
Minor and major semi-axes. ``(r/r_radius)**2 + (c/c_radius)**2 = 1``.
|
|
shape : tuple, optional
|
|
Image shape which is used to determine the maximum extent of output pixel
|
|
coordinates. This is useful for ellipses which exceed the image size.
|
|
By default the full extent of the ellipse are used. Must be at least
|
|
length 2. Only the first two values are used to determine the extent.
|
|
rotation : float, optional (default 0.)
|
|
Set the ellipse rotation (rotation) in range (-PI, PI)
|
|
in contra clock wise direction, so PI/2 degree means swap ellipse axis
|
|
|
|
Returns
|
|
-------
|
|
rr, cc : ndarray of int
|
|
Pixel coordinates of ellipse.
|
|
May be used to directly index into an array, e.g.
|
|
``img[rr, cc] = 1``.
|
|
|
|
Examples
|
|
--------
|
|
>>> from skimage.draw import ellipse
|
|
>>> img = np.zeros((10, 12), dtype=np.uint8)
|
|
>>> rr, cc = ellipse(5, 6, 3, 5, rotation=np.deg2rad(30))
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0],
|
|
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
|
|
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
|
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
|
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
|
[0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
|
[0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
|
|
|
Notes
|
|
-----
|
|
The ellipse equation::
|
|
|
|
((x * cos(alpha) + y * sin(alpha)) / x_radius) ** 2 +
|
|
((x * sin(alpha) - y * cos(alpha)) / y_radius) ** 2 = 1
|
|
|
|
|
|
Note that the positions of `ellipse` without specified `shape` can have
|
|
also, negative values, as this is correct on the plane. On the other hand
|
|
using these ellipse positions for an image afterwards may lead to appearing
|
|
on the other side of image, because ``image[-1, -1] = image[end-1, end-1]``
|
|
|
|
>>> rr, cc = ellipse(1, 2, 3, 6)
|
|
>>> img = np.zeros((6, 12), dtype=np.uint8)
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
|
|
[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
|
|
[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
|
|
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1]], dtype=uint8)
|
|
"""
|
|
|
|
center = np.array([r, c])
|
|
radii = np.array([r_radius, c_radius])
|
|
# allow just rotation with in range +/- 180 degree
|
|
rotation %= np.pi
|
|
|
|
# compute rotated radii by given rotation
|
|
r_radius_rot = abs(r_radius * np.cos(rotation)) \
|
|
+ c_radius * np.sin(rotation)
|
|
c_radius_rot = r_radius * np.sin(rotation) \
|
|
+ abs(c_radius * np.cos(rotation))
|
|
# The upper_left and lower_right corners of the smallest rectangle
|
|
# containing the ellipse.
|
|
radii_rot = np.array([r_radius_rot, c_radius_rot])
|
|
upper_left = np.ceil(center - radii_rot).astype(int)
|
|
lower_right = np.floor(center + radii_rot).astype(int)
|
|
|
|
if shape is not None:
|
|
# Constrain upper_left and lower_right by shape boundary.
|
|
upper_left = np.maximum(upper_left, np.array([0, 0]))
|
|
lower_right = np.minimum(lower_right, np.array(shape[:2]) - 1)
|
|
|
|
shifted_center = center - upper_left
|
|
bounding_shape = lower_right - upper_left + 1
|
|
|
|
rr, cc = _ellipse_in_shape(bounding_shape, shifted_center, radii, rotation)
|
|
rr.flags.writeable = True
|
|
cc.flags.writeable = True
|
|
rr += upper_left[0]
|
|
cc += upper_left[1]
|
|
return rr, cc
|
|
|
|
|
|
def circle(r, c, radius, shape=None):
|
|
"""Generate coordinates of pixels within circle.
|
|
|
|
Parameters
|
|
----------
|
|
r, c : double
|
|
Center coordinate of disk.
|
|
radius : double
|
|
Radius of disk.
|
|
shape : tuple, optional
|
|
Image shape which is used to determine the maximum extent of output
|
|
pixel coordinates. This is useful for disks that exceed the image
|
|
size. If None, the full extent of the disk is used. Must be at least
|
|
length 2. Only the first two values are used to determine the extent of
|
|
the input image.
|
|
|
|
Returns
|
|
-------
|
|
rr, cc : ndarray of int
|
|
Pixel coordinates of disk.
|
|
May be used to directly index into an array, e.g.
|
|
``img[rr, cc] = 1``.
|
|
|
|
Warns
|
|
-----
|
|
Deprecated:
|
|
.. versionadded:: 0.17
|
|
|
|
This function is deprecated and will be removed in scikit-image 0.19.
|
|
Please use the function named ``disk`` instead.
|
|
"""
|
|
warnings.warn("`draw.circle` is deprecated in favor of `draw.disk`."
|
|
"`draw.circle` will be removed in version 0.19",
|
|
FutureWarning, stacklevel=2)
|
|
return disk((r, c), radius, shape=shape)
|
|
|
|
|
|
def disk(center, radius, *, shape=None):
|
|
"""Generate coordinates of pixels within circle.
|
|
|
|
Parameters
|
|
----------
|
|
center : tuple
|
|
Center coordinate of disk.
|
|
radius : double
|
|
Radius of disk.
|
|
shape : tuple, optional
|
|
Image shape which is used to determine the maximum extent of output
|
|
pixel coordinates. This is useful for disks that exceed the image
|
|
size. If None, the full extent of the disk is used. Must be at least
|
|
length 2. Only the first two values are used to determine the extent of
|
|
the input image.
|
|
|
|
Returns
|
|
-------
|
|
rr, cc : ndarray of int
|
|
Pixel coordinates of disk.
|
|
May be used to directly index into an array, e.g.
|
|
``img[rr, cc] = 1``.
|
|
|
|
Examples
|
|
--------
|
|
>>> from skimage.draw import disk
|
|
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
|
>>> rr, cc = disk((4, 4), 5)
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
|
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
|
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
|
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
|
"""
|
|
r, c = center
|
|
return ellipse(r, c, radius, radius, shape)
|
|
|
|
|
|
def polygon_perimeter(r, c, shape=None, clip=False):
|
|
"""Generate polygon perimeter coordinates.
|
|
|
|
Parameters
|
|
----------
|
|
r : (N,) ndarray
|
|
Row coordinates of vertices of polygon.
|
|
c : (N,) ndarray
|
|
Column coordinates of vertices of polygon.
|
|
shape : tuple, optional
|
|
Image shape which is used to determine maximum extents of output pixel
|
|
coordinates. This is useful for polygons that exceed the image size.
|
|
If None, the full extents of the polygon is used. Must be at least
|
|
length 2. Only the first two values are used to determine the extent of
|
|
the input image.
|
|
clip : bool, optional
|
|
Whether to clip the polygon to the provided shape. If this is set
|
|
to True, the drawn figure will always be a closed polygon with all
|
|
edges visible.
|
|
|
|
Returns
|
|
-------
|
|
rr, cc : ndarray of int
|
|
Pixel coordinates of polygon.
|
|
May be used to directly index into an array, e.g.
|
|
``img[rr, cc] = 1``.
|
|
|
|
Examples
|
|
--------
|
|
>>> from skimage.draw import polygon_perimeter
|
|
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
|
>>> rr, cc = polygon_perimeter([5, -1, 5, 10],
|
|
... [-1, 5, 11, 5],
|
|
... shape=img.shape, clip=True)
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
|
|
[0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
|
|
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
|
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
[0, 1, 1, 0, 0, 0, 0, 0, 0, 1],
|
|
[0, 0, 0, 1, 0, 0, 0, 1, 1, 0],
|
|
[0, 0, 0, 0, 1, 1, 1, 0, 0, 0]], dtype=uint8)
|
|
|
|
"""
|
|
if clip:
|
|
if shape is None:
|
|
raise ValueError("Must specify clipping shape")
|
|
clip_box = np.array([0, 0, shape[0] - 1, shape[1] - 1])
|
|
else:
|
|
clip_box = np.array([np.min(r), np.min(c),
|
|
np.max(r), np.max(c)])
|
|
|
|
# Do the clipping irrespective of whether clip is set. This
|
|
# ensures that the returned polygon is closed and is an array.
|
|
r, c = polygon_clip(r, c, *clip_box)
|
|
|
|
r = np.round(r).astype(int)
|
|
c = np.round(c).astype(int)
|
|
|
|
# Construct line segments
|
|
rr, cc = [], []
|
|
for i in range(len(r) - 1):
|
|
line_r, line_c = line(r[i], c[i], r[i + 1], c[i + 1])
|
|
rr.extend(line_r)
|
|
cc.extend(line_c)
|
|
|
|
rr = np.asarray(rr)
|
|
cc = np.asarray(cc)
|
|
|
|
if shape is None:
|
|
return rr, cc
|
|
else:
|
|
return _coords_inside_image(rr, cc, shape)
|
|
|
|
|
|
def set_color(image, coords, color, alpha=1):
|
|
"""Set pixel color in the image at the given coordinates.
|
|
|
|
Note that this function modifies the color of the image in-place.
|
|
Coordinates that exceed the shape of the image will be ignored.
|
|
|
|
Parameters
|
|
----------
|
|
image : (M, N, D) ndarray
|
|
Image
|
|
coords : tuple of ((P,) ndarray, (P,) ndarray)
|
|
Row and column coordinates of pixels to be colored.
|
|
color : (D,) ndarray
|
|
Color to be assigned to coordinates in the image.
|
|
alpha : scalar or (N,) ndarray
|
|
Alpha values used to blend color with image. 0 is transparent,
|
|
1 is opaque.
|
|
|
|
Examples
|
|
--------
|
|
>>> from skimage.draw import line, set_color
|
|
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
|
>>> rr, cc = line(1, 1, 20, 20)
|
|
>>> set_color(img, (rr, cc), 1)
|
|
>>> img
|
|
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1]], dtype=uint8)
|
|
|
|
"""
|
|
rr, cc = coords
|
|
|
|
if image.ndim == 2:
|
|
image = image[..., np.newaxis]
|
|
|
|
color = np.array(color, ndmin=1, copy=False)
|
|
|
|
if image.shape[-1] != color.shape[-1]:
|
|
raise ValueError('Color shape ({}) must match last '
|
|
'image dimension ({}).'.format(color.shape[0],
|
|
image.shape[-1]))
|
|
|
|
if np.isscalar(alpha):
|
|
# Can be replaced by ``full_like`` when numpy 1.8 becomes
|
|
# minimum dependency
|
|
alpha = np.ones_like(rr) * alpha
|
|
|
|
rr, cc, alpha = _coords_inside_image(rr, cc, image.shape, val=alpha)
|
|
|
|
alpha = alpha[..., np.newaxis]
|
|
|
|
color = color * alpha
|
|
vals = image[rr, cc] * (1 - alpha)
|
|
|
|
image[rr, cc] = vals + color
|
|
|
|
|
|
def line(r0, c0, r1, c1):
|
|
"""Generate line pixel coordinates.
|
|
|
|
Parameters
|
|
----------
|
|
r0, c0 : int
|
|
Starting position (row, column).
|
|
r1, c1 : int
|
|
End position (row, column).
|
|
|
|
Returns
|
|
-------
|
|
rr, cc : (N,) ndarray of int
|
|
Indices of pixels that belong to the line.
|
|
May be used to directly index into an array, e.g.
|
|
``img[rr, cc] = 1``.
|
|
|
|
Notes
|
|
-----
|
|
Anti-aliased line generator is available with `line_aa`.
|
|
|
|
Examples
|
|
--------
|
|
>>> from skimage.draw import line
|
|
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
|
>>> rr, cc = line(1, 1, 8, 8)
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
|
"""
|
|
return _line(r0, c0, r1, c1)
|
|
|
|
|
|
def line_aa(r0, c0, r1, c1):
|
|
"""Generate anti-aliased line pixel coordinates.
|
|
|
|
Parameters
|
|
----------
|
|
r0, c0 : int
|
|
Starting position (row, column).
|
|
r1, c1 : int
|
|
End position (row, column).
|
|
|
|
Returns
|
|
-------
|
|
rr, cc, val : (N,) ndarray (int, int, float)
|
|
Indices of pixels (`rr`, `cc`) and intensity values (`val`).
|
|
``img[rr, cc] = val``.
|
|
|
|
References
|
|
----------
|
|
.. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
|
|
http://members.chello.at/easyfilter/Bresenham.pdf
|
|
|
|
Examples
|
|
--------
|
|
>>> from skimage.draw import line_aa
|
|
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
|
>>> rr, cc, val = line_aa(1, 1, 8, 8)
|
|
>>> img[rr, cc] = val * 255
|
|
>>> img
|
|
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[ 0, 255, 74, 0, 0, 0, 0, 0, 0, 0],
|
|
[ 0, 74, 255, 74, 0, 0, 0, 0, 0, 0],
|
|
[ 0, 0, 74, 255, 74, 0, 0, 0, 0, 0],
|
|
[ 0, 0, 0, 74, 255, 74, 0, 0, 0, 0],
|
|
[ 0, 0, 0, 0, 74, 255, 74, 0, 0, 0],
|
|
[ 0, 0, 0, 0, 0, 74, 255, 74, 0, 0],
|
|
[ 0, 0, 0, 0, 0, 0, 74, 255, 74, 0],
|
|
[ 0, 0, 0, 0, 0, 0, 0, 74, 255, 0],
|
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
|
"""
|
|
return _line_aa(r0, c0, r1, c1)
|
|
|
|
|
|
def polygon(r, c, shape=None):
|
|
"""Generate coordinates of pixels within polygon.
|
|
|
|
Parameters
|
|
----------
|
|
r : (N,) ndarray
|
|
Row coordinates of vertices of polygon.
|
|
c : (N,) ndarray
|
|
Column coordinates of vertices of polygon.
|
|
shape : tuple, optional
|
|
Image shape which is used to determine the maximum extent of output
|
|
pixel coordinates. This is useful for polygons that exceed the image
|
|
size. If None, the full extent of the polygon is used. Must be at
|
|
least length 2. Only the first two values are used to determine the
|
|
extent of the input image.
|
|
|
|
Returns
|
|
-------
|
|
rr, cc : ndarray of int
|
|
Pixel coordinates of polygon.
|
|
May be used to directly index into an array, e.g.
|
|
``img[rr, cc] = 1``.
|
|
|
|
Examples
|
|
--------
|
|
>>> from skimage.draw import polygon
|
|
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
|
>>> r = np.array([1, 2, 8])
|
|
>>> c = np.array([1, 7, 4])
|
|
>>> rr, cc = polygon(r, c)
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
|
|
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
|
[0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
|
|
[0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
|
|
|
"""
|
|
return _polygon(r, c, shape)
|
|
|
|
|
|
def circle_perimeter(r, c, radius, method='bresenham', shape=None):
|
|
"""Generate circle perimeter coordinates.
|
|
|
|
Parameters
|
|
----------
|
|
r, c : int
|
|
Centre coordinate of circle.
|
|
radius : int
|
|
Radius of circle.
|
|
method : {'bresenham', 'andres'}, optional
|
|
bresenham : Bresenham method (default)
|
|
andres : Andres method
|
|
shape : tuple, optional
|
|
Image shape which is used to determine the maximum extent of output
|
|
pixel coordinates. This is useful for circles that exceed the image
|
|
size. If None, the full extent of the circle is used. Must be at least
|
|
length 2. Only the first two values are used to determine the extent of
|
|
the input image.
|
|
|
|
Returns
|
|
-------
|
|
rr, cc : (N,) ndarray of int
|
|
Bresenham and Andres' method:
|
|
Indices of pixels that belong to the circle perimeter.
|
|
May be used to directly index into an array, e.g.
|
|
``img[rr, cc] = 1``.
|
|
|
|
Notes
|
|
-----
|
|
Andres method presents the advantage that concentric
|
|
circles create a disc whereas Bresenham can make holes. There
|
|
is also less distortions when Andres circles are rotated.
|
|
Bresenham method is also known as midpoint circle algorithm.
|
|
Anti-aliased circle generator is available with `circle_perimeter_aa`.
|
|
|
|
References
|
|
----------
|
|
.. [1] J.E. Bresenham, "Algorithm for computer control of a digital
|
|
plotter", IBM Systems journal, 4 (1965) 25-30.
|
|
.. [2] E. Andres, "Discrete circles, rings and spheres", Computers &
|
|
Graphics, 18 (1994) 695-706.
|
|
|
|
Examples
|
|
--------
|
|
>>> from skimage.draw import circle_perimeter
|
|
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
|
>>> rr, cc = circle_perimeter(4, 4, 3)
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
|
|
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
|
|
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
|
|
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
|
|
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
|
|
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
|
|
[0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
|
"""
|
|
return _circle_perimeter(r, c, radius, method, shape)
|
|
|
|
|
|
def circle_perimeter_aa(r, c, radius, shape=None):
|
|
"""Generate anti-aliased circle perimeter coordinates.
|
|
|
|
Parameters
|
|
----------
|
|
r, c : int
|
|
Centre coordinate of circle.
|
|
radius : int
|
|
Radius of circle.
|
|
shape : tuple, optional
|
|
Image shape which is used to determine the maximum extent of output
|
|
pixel coordinates. This is useful for circles that exceed the image
|
|
size. If None, the full extent of the circle is used. Must be at least
|
|
length 2. Only the first two values are used to determine the extent of
|
|
the input image.
|
|
|
|
Returns
|
|
-------
|
|
rr, cc, val : (N,) ndarray (int, int, float)
|
|
Indices of pixels (`rr`, `cc`) and intensity values (`val`).
|
|
``img[rr, cc] = val``.
|
|
|
|
Notes
|
|
-----
|
|
Wu's method draws anti-aliased circle. This implementation doesn't use
|
|
lookup table optimization.
|
|
|
|
Use the function ``draw.set_color`` to apply ``circle_perimeter_aa``
|
|
results to color images.
|
|
|
|
References
|
|
----------
|
|
.. [1] X. Wu, "An efficient antialiasing technique", In ACM SIGGRAPH
|
|
Computer Graphics, 25 (1991) 143-152.
|
|
|
|
Examples
|
|
--------
|
|
>>> from skimage.draw import circle_perimeter_aa
|
|
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
|
>>> rr, cc, val = circle_perimeter_aa(4, 4, 3)
|
|
>>> img[rr, cc] = val * 255
|
|
>>> img
|
|
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[ 0, 0, 60, 211, 255, 211, 60, 0, 0, 0],
|
|
[ 0, 60, 194, 43, 0, 43, 194, 60, 0, 0],
|
|
[ 0, 211, 43, 0, 0, 0, 43, 211, 0, 0],
|
|
[ 0, 255, 0, 0, 0, 0, 0, 255, 0, 0],
|
|
[ 0, 211, 43, 0, 0, 0, 43, 211, 0, 0],
|
|
[ 0, 60, 194, 43, 0, 43, 194, 60, 0, 0],
|
|
[ 0, 0, 60, 211, 255, 211, 60, 0, 0, 0],
|
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
|
|
|
>>> from skimage import data, draw
|
|
>>> image = data.chelsea()
|
|
>>> rr, cc, val = draw.circle_perimeter_aa(r=100, c=100, radius=75)
|
|
>>> draw.set_color(image, (rr, cc), [1, 0, 0], alpha=val)
|
|
"""
|
|
return _circle_perimeter_aa(r, c, radius, shape)
|
|
|
|
|
|
def ellipse_perimeter(r, c, r_radius, c_radius, orientation=0, shape=None):
|
|
"""Generate ellipse perimeter coordinates.
|
|
|
|
Parameters
|
|
----------
|
|
r, c : int
|
|
Centre coordinate of ellipse.
|
|
r_radius, c_radius : int
|
|
Minor and major semi-axes. ``(r/r_radius)**2 + (c/c_radius)**2 = 1``.
|
|
orientation : double, optional
|
|
Major axis orientation in clockwise direction as radians.
|
|
shape : tuple, optional
|
|
Image shape which is used to determine the maximum extent of output
|
|
pixel coordinates. This is useful for ellipses that exceed the image
|
|
size. If None, the full extent of the ellipse is used. Must be at
|
|
least length 2. Only the first two values are used to determine the
|
|
extent of the input image.
|
|
|
|
Returns
|
|
-------
|
|
rr, cc : (N,) ndarray of int
|
|
Indices of pixels that belong to the ellipse perimeter.
|
|
May be used to directly index into an array, e.g.
|
|
``img[rr, cc] = 1``.
|
|
|
|
References
|
|
----------
|
|
.. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
|
|
http://members.chello.at/easyfilter/Bresenham.pdf
|
|
|
|
Examples
|
|
--------
|
|
>>> from skimage.draw import ellipse_perimeter
|
|
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
|
>>> rr, cc = ellipse_perimeter(5, 5, 3, 4)
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
|
|
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
|
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
|
|
[0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
|
|
|
|
|
Note that the positions of `ellipse` without specified `shape` can have
|
|
also, negative values, as this is correct on the plane. On the other hand
|
|
using these ellipse positions for an image afterwards may lead to appearing
|
|
on the other side of image, because ``image[-1, -1] = image[end-1, end-1]``
|
|
|
|
>>> rr, cc = ellipse_perimeter(2, 3, 4, 5)
|
|
>>> img = np.zeros((9, 12), dtype=np.uint8)
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
|
|
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
|
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
|
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
|
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]], dtype=uint8)
|
|
"""
|
|
return _ellipse_perimeter(r, c, r_radius, c_radius, orientation, shape)
|
|
|
|
|
|
def bezier_curve(r0, c0, r1, c1, r2, c2, weight, shape=None):
|
|
"""Generate Bezier curve coordinates.
|
|
|
|
Parameters
|
|
----------
|
|
r0, c0 : int
|
|
Coordinates of the first control point.
|
|
r1, c1 : int
|
|
Coordinates of the middle control point.
|
|
r2, c2 : int
|
|
Coordinates of the last control point.
|
|
weight : double
|
|
Middle control point weight, it describes the line tension.
|
|
shape : tuple, optional
|
|
Image shape which is used to determine the maximum extent of output
|
|
pixel coordinates. This is useful for curves that exceed the image
|
|
size. If None, the full extent of the curve is used.
|
|
|
|
Returns
|
|
-------
|
|
rr, cc : (N,) ndarray of int
|
|
Indices of pixels that belong to the Bezier curve.
|
|
May be used to directly index into an array, e.g.
|
|
``img[rr, cc] = 1``.
|
|
|
|
Notes
|
|
-----
|
|
The algorithm is the rational quadratic algorithm presented in
|
|
reference [1]_.
|
|
|
|
References
|
|
----------
|
|
.. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
|
|
http://members.chello.at/easyfilter/Bresenham.pdf
|
|
|
|
Examples
|
|
--------
|
|
>>> import numpy as np
|
|
>>> from skimage.draw import bezier_curve
|
|
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
|
>>> rr, cc = bezier_curve(1, 5, 5, -2, 8, 8, 2)
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
|
[0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
|
|
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
|
"""
|
|
return _bezier_curve(r0, c0, r1, c1, r2, c2, weight, shape)
|
|
|
|
|
|
def rectangle(start, end=None, extent=None, shape=None):
|
|
"""Generate coordinates of pixels within a rectangle.
|
|
|
|
Parameters
|
|
----------
|
|
start : tuple
|
|
Origin point of the rectangle, e.g., ``([plane,] row, column)``.
|
|
end : tuple
|
|
End point of the rectangle ``([plane,] row, column)``.
|
|
For a 2D matrix, the slice defined by the rectangle is
|
|
``[start:(end+1)]``.
|
|
Either `end` or `extent` must be specified.
|
|
extent : tuple
|
|
The extent (size) of the drawn rectangle. E.g.,
|
|
``([num_planes,] num_rows, num_cols)``.
|
|
Either `end` or `extent` must be specified.
|
|
A negative extent is valid, and will result in a rectangle
|
|
going along the opposite direction. If extent is negative, the
|
|
`start` point is not included.
|
|
shape : tuple, optional
|
|
Image shape used to determine the maximum bounds of the output
|
|
coordinates. This is useful for clipping rectangles that exceed
|
|
the image size. By default, no clipping is done.
|
|
|
|
Returns
|
|
-------
|
|
coords : array of int, shape (Ndim, Npoints)
|
|
The coordinates of all pixels in the rectangle.
|
|
|
|
Notes
|
|
-----
|
|
This function can be applied to N-dimensional images, by passing `start` and
|
|
`end` or `extent` as tuples of length N.
|
|
|
|
Examples
|
|
--------
|
|
>>> import numpy as np
|
|
>>> from skimage.draw import rectangle
|
|
>>> img = np.zeros((5, 5), dtype=np.uint8)
|
|
>>> start = (1, 1)
|
|
>>> extent = (3, 3)
|
|
>>> rr, cc = rectangle(start, extent=extent, shape=img.shape)
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[0, 0, 0, 0, 0],
|
|
[0, 1, 1, 1, 0],
|
|
[0, 1, 1, 1, 0],
|
|
[0, 1, 1, 1, 0],
|
|
[0, 0, 0, 0, 0]], dtype=uint8)
|
|
|
|
|
|
>>> img = np.zeros((5, 5), dtype=np.uint8)
|
|
>>> start = (0, 1)
|
|
>>> end = (3, 3)
|
|
>>> rr, cc = rectangle(start, end=end, shape=img.shape)
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[0, 1, 1, 1, 0],
|
|
[0, 1, 1, 1, 0],
|
|
[0, 1, 1, 1, 0],
|
|
[0, 1, 1, 1, 0],
|
|
[0, 0, 0, 0, 0]], dtype=uint8)
|
|
|
|
>>> import numpy as np
|
|
>>> from skimage.draw import rectangle
|
|
>>> img = np.zeros((6, 6), dtype=np.uint8)
|
|
>>> start = (3, 3)
|
|
>>>
|
|
>>> rr, cc = rectangle(start, extent=(2, 2))
|
|
>>> img[rr, cc] = 1
|
|
>>> rr, cc = rectangle(start, extent=(-2, 2))
|
|
>>> img[rr, cc] = 2
|
|
>>> rr, cc = rectangle(start, extent=(-2, -2))
|
|
>>> img[rr, cc] = 3
|
|
>>> rr, cc = rectangle(start, extent=(2, -2))
|
|
>>> img[rr, cc] = 4
|
|
>>> print(img)
|
|
[[0 0 0 0 0 0]
|
|
[0 3 3 2 2 0]
|
|
[0 3 3 2 2 0]
|
|
[0 4 4 1 1 0]
|
|
[0 4 4 1 1 0]
|
|
[0 0 0 0 0 0]]
|
|
|
|
"""
|
|
tl, br = _rectangle_slice(start=start, end=end, extent=extent)
|
|
|
|
if shape is not None:
|
|
n_dim = len(start)
|
|
br = np.minimum(shape[0:n_dim], br)
|
|
tl = np.maximum(np.zeros_like(shape[0:n_dim]), tl)
|
|
coords = np.meshgrid(*[np.arange(st, en) for st, en in zip(tuple(tl),
|
|
tuple(br))])
|
|
return coords
|
|
|
|
|
|
def rectangle_perimeter(start, end=None, extent=None, shape=None, clip=False):
|
|
"""Generate coordinates of pixels that are exactly around a rectangle.
|
|
|
|
Parameters
|
|
----------
|
|
start : tuple
|
|
Origin point of the inner rectangle, e.g., ``(row, column)``.
|
|
end : tuple
|
|
End point of the inner rectangle ``(row, column)``.
|
|
For a 2D matrix, the slice defined by inner the rectangle is
|
|
``[start:(end+1)]``.
|
|
Either `end` or `extent` must be specified.
|
|
extent : tuple
|
|
The extent (size) of the inner rectangle. E.g.,
|
|
``(num_rows, num_cols)``.
|
|
Either `end` or `extent` must be specified.
|
|
Negative extents are permitted. See `rectangle` to better
|
|
understand how they behave.
|
|
shape : tuple, optional
|
|
Image shape used to determine the maximum bounds of the output
|
|
coordinates. This is useful for clipping perimeters that exceed
|
|
the image size. By default, no clipping is done. Must be at least
|
|
length 2. Only the first two values are used to determine the extent of
|
|
the input image.
|
|
clip : bool, optional
|
|
Whether to clip the perimeter to the provided shape. If this is set
|
|
to True, the drawn figure will always be a closed polygon with all
|
|
edges visible.
|
|
|
|
Returns
|
|
-------
|
|
coords : array of int, shape (2, Npoints)
|
|
The coordinates of all pixels in the rectangle.
|
|
|
|
Examples
|
|
--------
|
|
>>> import numpy as np
|
|
>>> from skimage.draw import rectangle_perimeter
|
|
>>> img = np.zeros((5, 6), dtype=np.uint8)
|
|
>>> start = (2, 3)
|
|
>>> end = (3, 4)
|
|
>>> rr, cc = rectangle_perimeter(start, end=end, shape=img.shape)
|
|
>>> img[rr, cc] = 1
|
|
>>> img
|
|
array([[0, 0, 0, 0, 0, 0],
|
|
[0, 0, 1, 1, 1, 1],
|
|
[0, 0, 1, 0, 0, 1],
|
|
[0, 0, 1, 0, 0, 1],
|
|
[0, 0, 1, 1, 1, 1]], dtype=uint8)
|
|
|
|
>>> img = np.zeros((5, 5), dtype=np.uint8)
|
|
>>> r, c = rectangle_perimeter(start, (10, 10), shape=img.shape, clip=True)
|
|
>>> img[r, c] = 1
|
|
>>> img
|
|
array([[0, 0, 0, 0, 0],
|
|
[0, 0, 1, 1, 1],
|
|
[0, 0, 1, 0, 1],
|
|
[0, 0, 1, 0, 1],
|
|
[0, 0, 1, 1, 1]], dtype=uint8)
|
|
|
|
"""
|
|
top_left, bottom_right = _rectangle_slice(start=start,
|
|
end=end,
|
|
extent=extent)
|
|
|
|
top_left -= 1
|
|
r = [top_left[0], top_left[0], bottom_right[0], bottom_right[0],
|
|
top_left[0]]
|
|
c = [top_left[1], bottom_right[1], bottom_right[1], top_left[1],
|
|
top_left[1]]
|
|
return polygon_perimeter(r, c, shape=shape, clip=clip)
|
|
|
|
|
|
def _rectangle_slice(start, end=None, extent=None):
|
|
"""Return the slice ``(top_left, bottom_right)`` of the rectangle.
|
|
|
|
Returns
|
|
=======
|
|
(top_left, bottomm_right)
|
|
The slice you would need to select the region in the rectangle defined
|
|
by the parameters.
|
|
Select it like:
|
|
|
|
``rect[top_left[0]:bottom_right[0], top_left[1]:bottom_right[1]]``
|
|
"""
|
|
if end is None and extent is None:
|
|
raise ValueError("Either `end` or `extent` must be given.")
|
|
if end is not None and extent is not None:
|
|
raise ValueError("Cannot provide both `end` and `extent`.")
|
|
|
|
if extent is not None:
|
|
end = np.asarray(start) + np.asarray(extent)
|
|
top_left = np.minimum(start, end)
|
|
bottom_right = np.maximum(start, end)
|
|
|
|
if extent is None:
|
|
bottom_right += 1
|
|
|
|
return (top_left, bottom_right)
|