| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970 |
- import numpy as np
- from .._shared._geometry import polygon_clip
- from .._shared.version_requirements import require
- from .._shared.compat import NP_COPY_IF_NEEDED
- from ._draw import (
- _coords_inside_image,
- _line,
- _line_aa,
- _polygon,
- _ellipse_perimeter,
- _circle_perimeter,
- _circle_perimeter_aa,
- _bezier_curve,
- )
- __doctest_requires__ = {("polygon_perimeter", "rectangle_perimeter"): ["matplotlib"]}
- def _ellipse_in_shape(shape, center, radii, rotation=0.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.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 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 as a tuple of size 2. Determines 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.
- The shape might result in negative coordinates and wraparound
- behaviour.
- 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
- --------
- >>> import numpy as np
- >>> from skimage.draw import disk
- >>> shape = (4, 4)
- >>> img = np.zeros(shape, dtype=np.uint8)
- >>> rr, cc = disk((0, 0), 2, shape=shape)
- >>> img[rr, cc] = 1
- >>> img
- array([[1, 1, 0, 0],
- [1, 1, 0, 0],
- [0, 0, 0, 0],
- [0, 0, 0, 0]], dtype=uint8)
- >>> img = np.zeros(shape, dtype=np.uint8)
- >>> # Negative coordinates in rr and cc perform a wraparound
- >>> rr, cc = disk((0, 0), 2, shape=None)
- >>> img[rr, cc] = 1
- >>> img
- array([[1, 1, 0, 1],
- [1, 1, 0, 1],
- [0, 0, 0, 0],
- [1, 1, 0, 1]], dtype=uint8)
- >>> 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)
- @require("matplotlib", ">=3.3")
- 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, C) ndarray
- Image
- coords : tuple of ((K,) ndarray, (K,) ndarray)
- Row and column coordinates of pixels to be colored.
- color : (C,) ndarray
- Color to be assigned to coordinates in the image.
- alpha : scalar or (K,) 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=NP_COPY_IF_NEEDED)
- if image.shape[-1] != color.shape[-1]:
- raise ValueError(
- f'Color shape ({color.shape[0]}) must match last '
- 'image dimension ({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 inside a polygon.
- Parameters
- ----------
- r : (N,) array_like
- Row coordinates of the polygon's vertices.
- c : (N,) array_like
- Column coordinates of the polygon's vertices.
- 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``.
- See Also
- --------
- polygon2mask:
- Create a binary mask from a polygon.
- Notes
- -----
- This function ensures that `rr` and `cc` don't contain negative values.
- Pixels of the polygon that whose coordinates are smaller 0, are not drawn.
- Examples
- --------
- >>> import skimage as ski
- >>> r = np.array([1, 2, 8])
- >>> c = np.array([1, 7, 4])
- >>> rr, cc = ski.draw.polygon(r, c)
- >>> img = np.zeros((10, 10), dtype=int)
- >>> 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]])
- If the image `shape` is defined and vertices / points of the `polygon` are
- outside this coordinate space, only a part (or none at all) of the polygon's
- pixels is returned. Shifting the polygon's vertices by an offset can be used
- to move the polygon around and potentially draw an arbitrary sub-region of
- the polygon.
- >>> offset = (2, -4)
- >>> rr, cc = ski.draw.polygon(r - offset[0], c - offset[1], shape=img.shape)
- >>> img = np.zeros((10, 10), dtype=int)
- >>> img[rr, cc] = 1
- >>> img
- array([[0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
- [0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
- [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
- [0, 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, 0, 0, 0, 0, 0, 0],
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
- """
- 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
- @require("matplotlib", ">=3.3")
- 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, bottom_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)
- top_left = np.round(top_left).astype(int)
- bottom_right = np.round(bottom_right).astype(int)
- if extent is None:
- bottom_right += 1
- return (top_left, bottom_right)
|