scikit-image
973 строки · 32.9 Кб
1import numpy as np2
3from .._shared._geometry import polygon_clip4from .._shared.version_requirements import require5from .._shared.compat import NP_COPY_IF_NEEDED6from ._draw import (7_coords_inside_image,8_line,9_line_aa,10_polygon,11_ellipse_perimeter,12_circle_perimeter,13_circle_perimeter_aa,14_bezier_curve,15)
16
17
18def _ellipse_in_shape(shape, center, radii, rotation=0.0):19"""Generate coordinates of points within ellipse bounded by shape.20
21Parameters
22----------
23shape : iterable of ints
24Shape of the input image. Must be at least length 2. Only the first
25two values are used to determine the extent of the input image.
26center : iterable of floats
27(row, column) position of center inside the given shape.
28radii : iterable of floats
29Size of two half axes (for row and column)
30rotation : float, optional
31Rotation of the ellipse defined by the above, in radians
32in range (-PI, PI), in contra clockwise direction,
33with respect to the column-axis.
34
35Returns
36-------
37rows : iterable of ints
38Row coordinates representing values within the ellipse.
39cols : iterable of ints
40Corresponding column coordinates representing values within the ellipse.
41"""
42r_lim, c_lim = np.ogrid[0 : float(shape[0]), 0 : float(shape[1])]43r_org, c_org = center44r_rad, c_rad = radii45rotation %= np.pi46sin_alpha, cos_alpha = np.sin(rotation), np.cos(rotation)47r, c = (r_lim - r_org), (c_lim - c_org)48distances = ((r * cos_alpha + c * sin_alpha) / r_rad) ** 2 + (49(r * sin_alpha - c * cos_alpha) / c_rad50) ** 251return np.nonzero(distances < 1)52
53
54def ellipse(r, c, r_radius, c_radius, shape=None, rotation=0.0):55"""Generate coordinates of pixels within ellipse.56
57Parameters
58----------
59r, c : double
60Centre coordinate of ellipse.
61r_radius, c_radius : double
62Minor and major semi-axes. ``(r/r_radius)**2 + (c/c_radius)**2 = 1``.
63shape : tuple, optional
64Image shape which is used to determine the maximum extent of output pixel
65coordinates. This is useful for ellipses which exceed the image size.
66By default the full extent of the ellipse are used. Must be at least
67length 2. Only the first two values are used to determine the extent.
68rotation : float, optional (default 0.)
69Set the ellipse rotation (rotation) in range (-PI, PI)
70in contra clock wise direction, so PI/2 degree means swap ellipse axis
71
72Returns
73-------
74rr, cc : ndarray of int
75Pixel coordinates of ellipse.
76May be used to directly index into an array, e.g.
77``img[rr, cc] = 1``.
78
79Examples
80--------
81>>> from skimage.draw import ellipse
82>>> img = np.zeros((10, 12), dtype=np.uint8)
83>>> rr, cc = ellipse(5, 6, 3, 5, rotation=np.deg2rad(30))
84>>> img[rr, cc] = 1
85>>> img
86array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
87[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
88[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0],
89[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
90[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
91[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
92[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
93[0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
94[0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
95[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
96
97Notes
98-----
99The ellipse equation::
100
101((x * cos(alpha) + y * sin(alpha)) / x_radius) ** 2 +
102((x * sin(alpha) - y * cos(alpha)) / y_radius) ** 2 = 1
103
104
105Note that the positions of `ellipse` without specified `shape` can have
106also, negative values, as this is correct on the plane. On the other hand
107using these ellipse positions for an image afterwards may lead to appearing
108on the other side of image, because ``image[-1, -1] = image[end-1, end-1]``
109
110>>> rr, cc = ellipse(1, 2, 3, 6)
111>>> img = np.zeros((6, 12), dtype=np.uint8)
112>>> img[rr, cc] = 1
113>>> img
114array([[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
115[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
116[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
117[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1],
118[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
119[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1]], dtype=uint8)
120"""
121
122center = np.array([r, c])123radii = np.array([r_radius, c_radius])124# allow just rotation with in range +/- 180 degree125rotation %= np.pi126
127# compute rotated radii by given rotation128r_radius_rot = abs(r_radius * np.cos(rotation)) + c_radius * np.sin(rotation)129c_radius_rot = r_radius * np.sin(rotation) + abs(c_radius * np.cos(rotation))130# The upper_left and lower_right corners of the smallest rectangle131# containing the ellipse.132radii_rot = np.array([r_radius_rot, c_radius_rot])133upper_left = np.ceil(center - radii_rot).astype(int)134lower_right = np.floor(center + radii_rot).astype(int)135
136if shape is not None:137# Constrain upper_left and lower_right by shape boundary.138upper_left = np.maximum(upper_left, np.array([0, 0]))139lower_right = np.minimum(lower_right, np.array(shape[:2]) - 1)140
141shifted_center = center - upper_left142bounding_shape = lower_right - upper_left + 1143
144rr, cc = _ellipse_in_shape(bounding_shape, shifted_center, radii, rotation)145rr.flags.writeable = True146cc.flags.writeable = True147rr += upper_left[0]148cc += upper_left[1]149return rr, cc150
151
152def disk(center, radius, *, shape=None):153"""Generate coordinates of pixels within circle.154
155Parameters
156----------
157center : tuple
158Center coordinate of disk.
159radius : double
160Radius of disk.
161shape : tuple, optional
162Image shape as a tuple of size 2. Determines the maximum
163extent of output pixel coordinates. This is useful for disks that
164exceed the image size. If None, the full extent of the disk is used.
165The shape might result in negative coordinates and wraparound
166behaviour.
167
168Returns
169-------
170rr, cc : ndarray of int
171Pixel coordinates of disk.
172May be used to directly index into an array, e.g.
173``img[rr, cc] = 1``.
174
175Examples
176--------
177>>> import numpy as np
178>>> from skimage.draw import disk
179>>> shape = (4, 4)
180>>> img = np.zeros(shape, dtype=np.uint8)
181>>> rr, cc = disk((0, 0), 2, shape=shape)
182>>> img[rr, cc] = 1
183>>> img
184array([[1, 1, 0, 0],
185[1, 1, 0, 0],
186[0, 0, 0, 0],
187[0, 0, 0, 0]], dtype=uint8)
188>>> img = np.zeros(shape, dtype=np.uint8)
189>>> # Negative coordinates in rr and cc perform a wraparound
190>>> rr, cc = disk((0, 0), 2, shape=None)
191>>> img[rr, cc] = 1
192>>> img
193array([[1, 1, 0, 1],
194[1, 1, 0, 1],
195[0, 0, 0, 0],
196[1, 1, 0, 1]], dtype=uint8)
197>>> img = np.zeros((10, 10), dtype=np.uint8)
198>>> rr, cc = disk((4, 4), 5)
199>>> img[rr, cc] = 1
200>>> img
201array([[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
202[0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
203[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
204[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
205[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
206[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
207[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
208[0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
209[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
210[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
211"""
212r, c = center213return ellipse(r, c, radius, radius, shape)214
215
216@require("matplotlib", ">=3.3")217def polygon_perimeter(r, c, shape=None, clip=False):218"""Generate polygon perimeter coordinates.219
220Parameters
221----------
222r : (N,) ndarray
223Row coordinates of vertices of polygon.
224c : (N,) ndarray
225Column coordinates of vertices of polygon.
226shape : tuple, optional
227Image shape which is used to determine maximum extents of output pixel
228coordinates. This is useful for polygons that exceed the image size.
229If None, the full extents of the polygon is used. Must be at least
230length 2. Only the first two values are used to determine the extent of
231the input image.
232clip : bool, optional
233Whether to clip the polygon to the provided shape. If this is set
234to True, the drawn figure will always be a closed polygon with all
235edges visible.
236
237Returns
238-------
239rr, cc : ndarray of int
240Pixel coordinates of polygon.
241May be used to directly index into an array, e.g.
242``img[rr, cc] = 1``.
243
244Examples
245--------
246.. testsetup::
247>>> import pytest; _ = pytest.importorskip('matplotlib')
248
249>>> from skimage.draw import polygon_perimeter
250>>> img = np.zeros((10, 10), dtype=np.uint8)
251>>> rr, cc = polygon_perimeter([5, -1, 5, 10],
252... [-1, 5, 11, 5],
253... shape=img.shape, clip=True)
254>>> img[rr, cc] = 1
255>>> img
256array([[0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
257[0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
258[0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
259[0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
260[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
261[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
262[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
263[0, 1, 1, 0, 0, 0, 0, 0, 0, 1],
264[0, 0, 0, 1, 0, 0, 0, 1, 1, 0],
265[0, 0, 0, 0, 1, 1, 1, 0, 0, 0]], dtype=uint8)
266
267"""
268if clip:269if shape is None:270raise ValueError("Must specify clipping shape")271clip_box = np.array([0, 0, shape[0] - 1, shape[1] - 1])272else:273clip_box = np.array([np.min(r), np.min(c), np.max(r), np.max(c)])274
275# Do the clipping irrespective of whether clip is set. This276# ensures that the returned polygon is closed and is an array.277r, c = polygon_clip(r, c, *clip_box)278
279r = np.round(r).astype(int)280c = np.round(c).astype(int)281
282# Construct line segments283rr, cc = [], []284for i in range(len(r) - 1):285line_r, line_c = line(r[i], c[i], r[i + 1], c[i + 1])286rr.extend(line_r)287cc.extend(line_c)288
289rr = np.asarray(rr)290cc = np.asarray(cc)291
292if shape is None:293return rr, cc294else:295return _coords_inside_image(rr, cc, shape)296
297
298def set_color(image, coords, color, alpha=1):299"""Set pixel color in the image at the given coordinates.300
301Note that this function modifies the color of the image in-place.
302Coordinates that exceed the shape of the image will be ignored.
303
304Parameters
305----------
306image : (M, N, C) ndarray
307Image
308coords : tuple of ((K,) ndarray, (K,) ndarray)
309Row and column coordinates of pixels to be colored.
310color : (C,) ndarray
311Color to be assigned to coordinates in the image.
312alpha : scalar or (K,) ndarray
313Alpha values used to blend color with image. 0 is transparent,
3141 is opaque.
315
316Examples
317--------
318>>> from skimage.draw import line, set_color
319>>> img = np.zeros((10, 10), dtype=np.uint8)
320>>> rr, cc = line(1, 1, 20, 20)
321>>> set_color(img, (rr, cc), 1)
322>>> img
323array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
324[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
325[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
326[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
327[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
328[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
329[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
330[0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
331[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
332[0, 0, 0, 0, 0, 0, 0, 0, 0, 1]], dtype=uint8)
333
334"""
335rr, cc = coords336
337if image.ndim == 2:338image = image[..., np.newaxis]339
340color = np.array(color, ndmin=1, copy=NP_COPY_IF_NEEDED)341
342if image.shape[-1] != color.shape[-1]:343raise ValueError(344f'Color shape ({color.shape[0]}) must match last '345'image dimension ({image.shape[-1]}).'346)347
348if np.isscalar(alpha):349# Can be replaced by ``full_like`` when numpy 1.8 becomes350# minimum dependency351alpha = np.ones_like(rr) * alpha352
353rr, cc, alpha = _coords_inside_image(rr, cc, image.shape, val=alpha)354
355alpha = alpha[..., np.newaxis]356
357color = color * alpha358vals = image[rr, cc] * (1 - alpha)359
360image[rr, cc] = vals + color361
362
363def line(r0, c0, r1, c1):364"""Generate line pixel coordinates.365
366Parameters
367----------
368r0, c0 : int
369Starting position (row, column).
370r1, c1 : int
371End position (row, column).
372
373Returns
374-------
375rr, cc : (N,) ndarray of int
376Indices of pixels that belong to the line.
377May be used to directly index into an array, e.g.
378``img[rr, cc] = 1``.
379
380Notes
381-----
382Anti-aliased line generator is available with `line_aa`.
383
384Examples
385--------
386>>> from skimage.draw import line
387>>> img = np.zeros((10, 10), dtype=np.uint8)
388>>> rr, cc = line(1, 1, 8, 8)
389>>> img[rr, cc] = 1
390>>> img
391array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
392[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
393[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
394[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
395[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
396[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
397[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
398[0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
399[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
400[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
401"""
402return _line(r0, c0, r1, c1)403
404
405def line_aa(r0, c0, r1, c1):406"""Generate anti-aliased line pixel coordinates.407
408Parameters
409----------
410r0, c0 : int
411Starting position (row, column).
412r1, c1 : int
413End position (row, column).
414
415Returns
416-------
417rr, cc, val : (N,) ndarray (int, int, float)
418Indices of pixels (`rr`, `cc`) and intensity values (`val`).
419``img[rr, cc] = val``.
420
421References
422----------
423.. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
424http://members.chello.at/easyfilter/Bresenham.pdf
425
426Examples
427--------
428>>> from skimage.draw import line_aa
429>>> img = np.zeros((10, 10), dtype=np.uint8)
430>>> rr, cc, val = line_aa(1, 1, 8, 8)
431>>> img[rr, cc] = val * 255
432>>> img
433array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
434[ 0, 255, 74, 0, 0, 0, 0, 0, 0, 0],
435[ 0, 74, 255, 74, 0, 0, 0, 0, 0, 0],
436[ 0, 0, 74, 255, 74, 0, 0, 0, 0, 0],
437[ 0, 0, 0, 74, 255, 74, 0, 0, 0, 0],
438[ 0, 0, 0, 0, 74, 255, 74, 0, 0, 0],
439[ 0, 0, 0, 0, 0, 74, 255, 74, 0, 0],
440[ 0, 0, 0, 0, 0, 0, 74, 255, 74, 0],
441[ 0, 0, 0, 0, 0, 0, 0, 74, 255, 0],
442[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
443"""
444return _line_aa(r0, c0, r1, c1)445
446
447def polygon(r, c, shape=None):448"""Generate coordinates of pixels inside a polygon.449
450Parameters
451----------
452r : (N,) array_like
453Row coordinates of the polygon's vertices.
454c : (N,) array_like
455Column coordinates of the polygon's vertices.
456shape : tuple, optional
457Image shape which is used to determine the maximum extent of output
458pixel coordinates. This is useful for polygons that exceed the image
459size. If None, the full extent of the polygon is used. Must be at
460least length 2. Only the first two values are used to determine the
461extent of the input image.
462
463Returns
464-------
465rr, cc : ndarray of int
466Pixel coordinates of polygon.
467May be used to directly index into an array, e.g.
468``img[rr, cc] = 1``.
469
470See Also
471--------
472polygon2mask:
473Create a binary mask from a polygon.
474
475Notes
476-----
477This function ensures that `rr` and `cc` don't contain negative values.
478Pixels of the polygon that whose coordinates are smaller 0, are not drawn.
479
480Examples
481--------
482>>> import skimage as ski
483>>> r = np.array([1, 2, 8])
484>>> c = np.array([1, 7, 4])
485>>> rr, cc = ski.draw.polygon(r, c)
486>>> img = np.zeros((10, 10), dtype=int)
487>>> img[rr, cc] = 1
488>>> img
489array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
490[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
491[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
492[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
493[0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
494[0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
495[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
496[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
497[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
498[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
499
500If the image `shape` is defined and vertices / points of the `polygon` are
501outside this coordinate space, only a part (or none at all) of the polygon's
502pixels is returned. Shifting the polygon's vertices by an offset can be used
503to move the polygon around and potentially draw an arbitrary sub-region of
504the polygon.
505
506>>> offset = (2, -4)
507>>> rr, cc = ski.draw.polygon(r - offset[0], c - offset[1], shape=img.shape)
508>>> img = np.zeros((10, 10), dtype=int)
509>>> img[rr, cc] = 1
510>>> img
511array([[0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
512[0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
513[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
514[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
515[0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
516[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
517[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
518[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
519[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
520[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
521"""
522return _polygon(r, c, shape)523
524
525def circle_perimeter(r, c, radius, method='bresenham', shape=None):526"""Generate circle perimeter coordinates.527
528Parameters
529----------
530r, c : int
531Centre coordinate of circle.
532radius : int
533Radius of circle.
534method : {'bresenham', 'andres'}, optional
535bresenham : Bresenham method (default)
536andres : Andres method
537shape : tuple, optional
538Image shape which is used to determine the maximum extent of output
539pixel coordinates. This is useful for circles that exceed the image
540size. If None, the full extent of the circle is used. Must be at least
541length 2. Only the first two values are used to determine the extent of
542the input image.
543
544Returns
545-------
546rr, cc : (N,) ndarray of int
547Bresenham and Andres' method:
548Indices of pixels that belong to the circle perimeter.
549May be used to directly index into an array, e.g.
550``img[rr, cc] = 1``.
551
552Notes
553-----
554Andres method presents the advantage that concentric
555circles create a disc whereas Bresenham can make holes. There
556is also less distortions when Andres circles are rotated.
557Bresenham method is also known as midpoint circle algorithm.
558Anti-aliased circle generator is available with `circle_perimeter_aa`.
559
560References
561----------
562.. [1] J.E. Bresenham, "Algorithm for computer control of a digital
563plotter", IBM Systems journal, 4 (1965) 25-30.
564.. [2] E. Andres, "Discrete circles, rings and spheres", Computers &
565Graphics, 18 (1994) 695-706.
566
567Examples
568--------
569>>> from skimage.draw import circle_perimeter
570>>> img = np.zeros((10, 10), dtype=np.uint8)
571>>> rr, cc = circle_perimeter(4, 4, 3)
572>>> img[rr, cc] = 1
573>>> img
574array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
575[0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
576[0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
577[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
578[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
579[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
580[0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
581[0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
582[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
583[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
584"""
585return _circle_perimeter(r, c, radius, method, shape)586
587
588def circle_perimeter_aa(r, c, radius, shape=None):589"""Generate anti-aliased circle perimeter coordinates.590
591Parameters
592----------
593r, c : int
594Centre coordinate of circle.
595radius : int
596Radius of circle.
597shape : tuple, optional
598Image shape which is used to determine the maximum extent of output
599pixel coordinates. This is useful for circles that exceed the image
600size. If None, the full extent of the circle is used. Must be at least
601length 2. Only the first two values are used to determine the extent of
602the input image.
603
604Returns
605-------
606rr, cc, val : (N,) ndarray (int, int, float)
607Indices of pixels (`rr`, `cc`) and intensity values (`val`).
608``img[rr, cc] = val``.
609
610Notes
611-----
612Wu's method draws anti-aliased circle. This implementation doesn't use
613lookup table optimization.
614
615Use the function ``draw.set_color`` to apply ``circle_perimeter_aa``
616results to color images.
617
618References
619----------
620.. [1] X. Wu, "An efficient antialiasing technique", In ACM SIGGRAPH
621Computer Graphics, 25 (1991) 143-152.
622
623Examples
624--------
625>>> from skimage.draw import circle_perimeter_aa
626>>> img = np.zeros((10, 10), dtype=np.uint8)
627>>> rr, cc, val = circle_perimeter_aa(4, 4, 3)
628>>> img[rr, cc] = val * 255
629>>> img
630array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
631[ 0, 0, 60, 211, 255, 211, 60, 0, 0, 0],
632[ 0, 60, 194, 43, 0, 43, 194, 60, 0, 0],
633[ 0, 211, 43, 0, 0, 0, 43, 211, 0, 0],
634[ 0, 255, 0, 0, 0, 0, 0, 255, 0, 0],
635[ 0, 211, 43, 0, 0, 0, 43, 211, 0, 0],
636[ 0, 60, 194, 43, 0, 43, 194, 60, 0, 0],
637[ 0, 0, 60, 211, 255, 211, 60, 0, 0, 0],
638[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
639[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
640
641>>> from skimage import data, draw
642>>> image = data.chelsea()
643>>> rr, cc, val = draw.circle_perimeter_aa(r=100, c=100, radius=75)
644>>> draw.set_color(image, (rr, cc), [1, 0, 0], alpha=val)
645"""
646return _circle_perimeter_aa(r, c, radius, shape)647
648
649def ellipse_perimeter(r, c, r_radius, c_radius, orientation=0, shape=None):650"""Generate ellipse perimeter coordinates.651
652Parameters
653----------
654r, c : int
655Centre coordinate of ellipse.
656r_radius, c_radius : int
657Minor and major semi-axes. ``(r/r_radius)**2 + (c/c_radius)**2 = 1``.
658orientation : double, optional
659Major axis orientation in clockwise direction as radians.
660shape : tuple, optional
661Image shape which is used to determine the maximum extent of output
662pixel coordinates. This is useful for ellipses that exceed the image
663size. If None, the full extent of the ellipse is used. Must be at
664least length 2. Only the first two values are used to determine the
665extent of the input image.
666
667Returns
668-------
669rr, cc : (N,) ndarray of int
670Indices of pixels that belong to the ellipse perimeter.
671May be used to directly index into an array, e.g.
672``img[rr, cc] = 1``.
673
674References
675----------
676.. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
677http://members.chello.at/easyfilter/Bresenham.pdf
678
679Examples
680--------
681>>> from skimage.draw import ellipse_perimeter
682>>> img = np.zeros((10, 10), dtype=np.uint8)
683>>> rr, cc = ellipse_perimeter(5, 5, 3, 4)
684>>> img[rr, cc] = 1
685>>> img
686array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
687[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
688[0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
689[0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
690[0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
691[0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
692[0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
693[0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
694[0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
695[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
696
697
698Note that the positions of `ellipse` without specified `shape` can have
699also, negative values, as this is correct on the plane. On the other hand
700using these ellipse positions for an image afterwards may lead to appearing
701on the other side of image, because ``image[-1, -1] = image[end-1, end-1]``
702
703>>> rr, cc = ellipse_perimeter(2, 3, 4, 5)
704>>> img = np.zeros((9, 12), dtype=np.uint8)
705>>> img[rr, cc] = 1
706>>> img
707array([[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
708[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
709[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
710[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
711[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
712[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
713[0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
714[0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
715[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]], dtype=uint8)
716"""
717return _ellipse_perimeter(r, c, r_radius, c_radius, orientation, shape)718
719
720def bezier_curve(r0, c0, r1, c1, r2, c2, weight, shape=None):721"""Generate Bezier curve coordinates.722
723Parameters
724----------
725r0, c0 : int
726Coordinates of the first control point.
727r1, c1 : int
728Coordinates of the middle control point.
729r2, c2 : int
730Coordinates of the last control point.
731weight : double
732Middle control point weight, it describes the line tension.
733shape : tuple, optional
734Image shape which is used to determine the maximum extent of output
735pixel coordinates. This is useful for curves that exceed the image
736size. If None, the full extent of the curve is used.
737
738Returns
739-------
740rr, cc : (N,) ndarray of int
741Indices of pixels that belong to the Bezier curve.
742May be used to directly index into an array, e.g.
743``img[rr, cc] = 1``.
744
745Notes
746-----
747The algorithm is the rational quadratic algorithm presented in
748reference [1]_.
749
750References
751----------
752.. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
753http://members.chello.at/easyfilter/Bresenham.pdf
754
755Examples
756--------
757>>> import numpy as np
758>>> from skimage.draw import bezier_curve
759>>> img = np.zeros((10, 10), dtype=np.uint8)
760>>> rr, cc = bezier_curve(1, 5, 5, -2, 8, 8, 2)
761>>> img[rr, cc] = 1
762>>> img
763array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
764[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
765[0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
766[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
767[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
768[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
769[0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
770[0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
771[0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
772[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
773"""
774return _bezier_curve(r0, c0, r1, c1, r2, c2, weight, shape)775
776
777def rectangle(start, end=None, extent=None, shape=None):778"""Generate coordinates of pixels within a rectangle.779
780Parameters
781----------
782start : tuple
783Origin point of the rectangle, e.g., ``([plane,] row, column)``.
784end : tuple
785End point of the rectangle ``([plane,] row, column)``.
786For a 2D matrix, the slice defined by the rectangle is
787``[start:(end+1)]``.
788Either `end` or `extent` must be specified.
789extent : tuple
790The extent (size) of the drawn rectangle. E.g.,
791``([num_planes,] num_rows, num_cols)``.
792Either `end` or `extent` must be specified.
793A negative extent is valid, and will result in a rectangle
794going along the opposite direction. If extent is negative, the
795`start` point is not included.
796shape : tuple, optional
797Image shape used to determine the maximum bounds of the output
798coordinates. This is useful for clipping rectangles that exceed
799the image size. By default, no clipping is done.
800
801Returns
802-------
803coords : array of int, shape (Ndim, Npoints)
804The coordinates of all pixels in the rectangle.
805
806Notes
807-----
808This function can be applied to N-dimensional images, by passing `start` and
809`end` or `extent` as tuples of length N.
810
811Examples
812--------
813>>> import numpy as np
814>>> from skimage.draw import rectangle
815>>> img = np.zeros((5, 5), dtype=np.uint8)
816>>> start = (1, 1)
817>>> extent = (3, 3)
818>>> rr, cc = rectangle(start, extent=extent, shape=img.shape)
819>>> img[rr, cc] = 1
820>>> img
821array([[0, 0, 0, 0, 0],
822[0, 1, 1, 1, 0],
823[0, 1, 1, 1, 0],
824[0, 1, 1, 1, 0],
825[0, 0, 0, 0, 0]], dtype=uint8)
826
827
828>>> img = np.zeros((5, 5), dtype=np.uint8)
829>>> start = (0, 1)
830>>> end = (3, 3)
831>>> rr, cc = rectangle(start, end=end, shape=img.shape)
832>>> img[rr, cc] = 1
833>>> img
834array([[0, 1, 1, 1, 0],
835[0, 1, 1, 1, 0],
836[0, 1, 1, 1, 0],
837[0, 1, 1, 1, 0],
838[0, 0, 0, 0, 0]], dtype=uint8)
839
840>>> import numpy as np
841>>> from skimage.draw import rectangle
842>>> img = np.zeros((6, 6), dtype=np.uint8)
843>>> start = (3, 3)
844>>>
845>>> rr, cc = rectangle(start, extent=(2, 2))
846>>> img[rr, cc] = 1
847>>> rr, cc = rectangle(start, extent=(-2, 2))
848>>> img[rr, cc] = 2
849>>> rr, cc = rectangle(start, extent=(-2, -2))
850>>> img[rr, cc] = 3
851>>> rr, cc = rectangle(start, extent=(2, -2))
852>>> img[rr, cc] = 4
853>>> print(img)
854[[0 0 0 0 0 0]
855[0 3 3 2 2 0]
856[0 3 3 2 2 0]
857[0 4 4 1 1 0]
858[0 4 4 1 1 0]
859[0 0 0 0 0 0]]
860
861"""
862tl, br = _rectangle_slice(start=start, end=end, extent=extent)863
864if shape is not None:865n_dim = len(start)866br = np.minimum(shape[0:n_dim], br)867tl = np.maximum(np.zeros_like(shape[0:n_dim]), tl)868coords = np.meshgrid(*[np.arange(st, en) for st, en in zip(tuple(tl), tuple(br))])869return coords870
871
872@require("matplotlib", ">=3.3")873def rectangle_perimeter(start, end=None, extent=None, shape=None, clip=False):874"""Generate coordinates of pixels that are exactly around a rectangle.875
876Parameters
877----------
878start : tuple
879Origin point of the inner rectangle, e.g., ``(row, column)``.
880end : tuple
881End point of the inner rectangle ``(row, column)``.
882For a 2D matrix, the slice defined by inner the rectangle is
883``[start:(end+1)]``.
884Either `end` or `extent` must be specified.
885extent : tuple
886The extent (size) of the inner rectangle. E.g.,
887``(num_rows, num_cols)``.
888Either `end` or `extent` must be specified.
889Negative extents are permitted. See `rectangle` to better
890understand how they behave.
891shape : tuple, optional
892Image shape used to determine the maximum bounds of the output
893coordinates. This is useful for clipping perimeters that exceed
894the image size. By default, no clipping is done. Must be at least
895length 2. Only the first two values are used to determine the extent of
896the input image.
897clip : bool, optional
898Whether to clip the perimeter to the provided shape. If this is set
899to True, the drawn figure will always be a closed polygon with all
900edges visible.
901
902Returns
903-------
904coords : array of int, shape (2, Npoints)
905The coordinates of all pixels in the rectangle.
906
907Examples
908--------
909.. testsetup::
910>>> import pytest; _ = pytest.importorskip('matplotlib')
911
912>>> import numpy as np
913>>> from skimage.draw import rectangle_perimeter
914>>> img = np.zeros((5, 6), dtype=np.uint8)
915>>> start = (2, 3)
916>>> end = (3, 4)
917>>> rr, cc = rectangle_perimeter(start, end=end, shape=img.shape)
918>>> img[rr, cc] = 1
919>>> img
920array([[0, 0, 0, 0, 0, 0],
921[0, 0, 1, 1, 1, 1],
922[0, 0, 1, 0, 0, 1],
923[0, 0, 1, 0, 0, 1],
924[0, 0, 1, 1, 1, 1]], dtype=uint8)
925
926>>> img = np.zeros((5, 5), dtype=np.uint8)
927>>> r, c = rectangle_perimeter(start, (10, 10), shape=img.shape, clip=True)
928>>> img[r, c] = 1
929>>> img
930array([[0, 0, 0, 0, 0],
931[0, 0, 1, 1, 1],
932[0, 0, 1, 0, 1],
933[0, 0, 1, 0, 1],
934[0, 0, 1, 1, 1]], dtype=uint8)
935
936"""
937top_left, bottom_right = _rectangle_slice(start=start, end=end, extent=extent)938
939top_left -= 1940r = [top_left[0], top_left[0], bottom_right[0], bottom_right[0], top_left[0]]941c = [top_left[1], bottom_right[1], bottom_right[1], top_left[1], top_left[1]]942return polygon_perimeter(r, c, shape=shape, clip=clip)943
944
945def _rectangle_slice(start, end=None, extent=None):946"""Return the slice ``(top_left, bottom_right)`` of the rectangle.947
948Returns
949-------
950(top_left, bottom_right)
951The slice you would need to select the region in the rectangle defined
952by the parameters.
953Select it like:
954
955``rect[top_left[0]:bottom_right[0], top_left[1]:bottom_right[1]]``
956"""
957if end is None and extent is None:958raise ValueError("Either `end` or `extent` must be given.")959if end is not None and extent is not None:960raise ValueError("Cannot provide both `end` and `extent`.")961
962if extent is not None:963end = np.asarray(start) + np.asarray(extent)964top_left = np.minimum(start, end)965bottom_right = np.maximum(start, end)966
967top_left = np.round(top_left).astype(int)968bottom_right = np.round(bottom_right).astype(int)969
970if extent is None:971bottom_right += 1972
973return (top_left, bottom_right)974