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 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) 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)