draw.py 33 KB

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