kps.py 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487
  1. """Classes to represent keypoints, i.e. points given as xy-coordinates."""
  2. from __future__ import print_function, division, absolute_import
  3. import numpy as np
  4. import scipy.spatial.distance
  5. import six.moves as sm
  6. from .. import imgaug as ia
  7. from .base import IAugmentable
  8. from .utils import (normalize_shape, project_coords,
  9. _remove_out_of_image_fraction_)
  10. def compute_geometric_median(points=None, eps=1e-5, X=None):
  11. """Estimate the geometric median of points in 2D.
  12. Code from https://stackoverflow.com/a/30305181
  13. Parameters
  14. ----------
  15. points : (N,2) ndarray
  16. Points in 2D. Second axis must be given in xy-form.
  17. eps : float, optional
  18. Distance threshold when to return the median.
  19. X : None or (N,2) ndarray, optional
  20. Deprecated.
  21. Returns
  22. -------
  23. (2,) ndarray
  24. Geometric median as xy-coordinate.
  25. """
  26. # pylint: disable=invalid-name
  27. if X is not None:
  28. assert points is None
  29. ia.warn_deprecated("Using 'X' is deprecated, use 'points' instead.")
  30. points = X
  31. y = np.mean(points, 0)
  32. while True:
  33. dist = scipy.spatial.distance.cdist(points, [y])
  34. nonzeros = (dist != 0)[:, 0]
  35. dist_inv = 1 / dist[nonzeros]
  36. dist_inv_sum = np.sum(dist_inv)
  37. dist_inv_norm = dist_inv / dist_inv_sum
  38. T = np.sum(dist_inv_norm * points[nonzeros], 0)
  39. num_zeros = len(points) - np.sum(nonzeros)
  40. if num_zeros == 0:
  41. y1 = T
  42. elif num_zeros == len(points):
  43. return y
  44. else:
  45. R = (T - y) * dist_inv_sum
  46. r = np.linalg.norm(R)
  47. rinv = 0 if r == 0 else num_zeros/r
  48. y1 = max(0, 1-rinv)*T + min(1, rinv)*y
  49. if scipy.spatial.distance.euclidean(y, y1) < eps:
  50. return y1
  51. y = y1
  52. class Keypoint(object):
  53. """A single keypoint (aka landmark) on an image.
  54. Parameters
  55. ----------
  56. x : number
  57. Coordinate of the keypoint on the x axis.
  58. y : number
  59. Coordinate of the keypoint on the y axis.
  60. """
  61. def __init__(self, x, y):
  62. self.x = x
  63. self.y = y
  64. @property
  65. def coords(self):
  66. """Get the xy-coordinates as an ``(N,2)`` ndarray.
  67. Added in 0.4.0.
  68. Returns
  69. -------
  70. ndarray
  71. An ``(N, 2)`` ``float32`` ndarray with ``N=1`` containing the
  72. coordinates of this keypoints.
  73. """
  74. arr = np.empty((1, 2), dtype=np.float32)
  75. arr[0, :] = [self.x, self.y]
  76. return arr
  77. @property
  78. def x_int(self):
  79. """Get the keypoint's x-coordinate, rounded to the closest integer.
  80. Returns
  81. -------
  82. result : int
  83. Keypoint's x-coordinate, rounded to the closest integer.
  84. """
  85. return int(np.round(self.x))
  86. @property
  87. def y_int(self):
  88. """Get the keypoint's y-coordinate, rounded to the closest integer.
  89. Returns
  90. -------
  91. result : int
  92. Keypoint's y-coordinate, rounded to the closest integer.
  93. """
  94. return int(np.round(self.y))
  95. @property
  96. def xy(self):
  97. """Get the keypoint's x- and y-coordinate as a single array.
  98. Added in 0.4.0.
  99. Returns
  100. -------
  101. ndarray
  102. A ``(2,)`` ``ndarray`` denoting the xy-coordinate pair.
  103. """
  104. return self.coords[0, :]
  105. @property
  106. def xy_int(self):
  107. """Get the keypoint's xy-coord, rounded to closest integer.
  108. Added in 0.4.0.
  109. Returns
  110. -------
  111. ndarray
  112. A ``(2,)`` ``ndarray`` denoting the xy-coordinate pair.
  113. """
  114. return np.round(self.xy).astype(np.int32)
  115. def project_(self, from_shape, to_shape):
  116. """Project in-place the keypoint onto a new position on a new image.
  117. E.g. if the keypoint is on its original image
  118. at ``x=(10 of 100 pixels)`` and ``y=(20 of 100 pixels)`` and is
  119. projected onto a new image with size ``(width=200, height=200)``, its
  120. new position will be ``(20, 40)``.
  121. This is intended for cases where the original image is resized.
  122. It cannot be used for more complex changes (e.g. padding, cropping).
  123. Added in 0.4.0.
  124. Parameters
  125. ----------
  126. from_shape : tuple of int
  127. Shape of the original image. (Before resize.)
  128. to_shape : tuple of int
  129. Shape of the new image. (After resize.)
  130. Returns
  131. -------
  132. imgaug.augmentables.kps.Keypoint
  133. Keypoint object with new coordinates.
  134. The instance of the keypoint may have been modified in-place.
  135. """
  136. xy_proj = project_coords([(self.x, self.y)], from_shape, to_shape)
  137. self.x, self.y = xy_proj[0]
  138. return self
  139. def project(self, from_shape, to_shape):
  140. """Project the keypoint onto a new position on a new image.
  141. E.g. if the keypoint is on its original image
  142. at ``x=(10 of 100 pixels)`` and ``y=(20 of 100 pixels)`` and is
  143. projected onto a new image with size ``(width=200, height=200)``, its
  144. new position will be ``(20, 40)``.
  145. This is intended for cases where the original image is resized.
  146. It cannot be used for more complex changes (e.g. padding, cropping).
  147. Parameters
  148. ----------
  149. from_shape : tuple of int
  150. Shape of the original image. (Before resize.)
  151. to_shape : tuple of int
  152. Shape of the new image. (After resize.)
  153. Returns
  154. -------
  155. imgaug.augmentables.kps.Keypoint
  156. Keypoint object with new coordinates.
  157. """
  158. return self.deepcopy().project_(from_shape, to_shape)
  159. def is_out_of_image(self, image):
  160. """Estimate whether this point is outside of the given image plane.
  161. Added in 0.4.0.
  162. Parameters
  163. ----------
  164. image : (H,W,...) ndarray or tuple of int
  165. Image dimensions to use.
  166. If an ``ndarray``, its shape will be used.
  167. If a ``tuple``, it is assumed to represent the image shape
  168. and must contain at least two integers.
  169. Returns
  170. -------
  171. bool
  172. ``True`` is the point is inside the image plane, ``False``
  173. otherwise.
  174. """
  175. shape = normalize_shape(image)
  176. height, width = shape[0:2]
  177. y_inside = (0 <= self.y < height)
  178. x_inside = (0 <= self.x < width)
  179. return not y_inside or not x_inside
  180. def compute_out_of_image_fraction(self, image):
  181. """Compute fraction of the keypoint that is out of the image plane.
  182. The fraction is always either ``1.0`` (point is outside of the image
  183. plane) or ``0.0`` (point is inside the image plane). This method
  184. exists for consistency with other augmentables, e.g. bounding boxes.
  185. Added in 0.4.0.
  186. Parameters
  187. ----------
  188. image : (H,W,...) ndarray or tuple of int
  189. Image dimensions to use.
  190. If an ``ndarray``, its shape will be used.
  191. If a ``tuple``, it is assumed to represent the image shape
  192. and must contain at least two integers.
  193. Returns
  194. -------
  195. float
  196. Either ``1.0`` (point is outside of the image plane) or
  197. ``0.0`` (point is inside of it).
  198. """
  199. return float(self.is_out_of_image(image))
  200. def shift_(self, x=0, y=0):
  201. """Move the keypoint around on an image in-place.
  202. Added in 0.4.0.
  203. Parameters
  204. ----------
  205. x : number, optional
  206. Move by this value on the x axis.
  207. y : number, optional
  208. Move by this value on the y axis.
  209. Returns
  210. -------
  211. imgaug.augmentables.kps.Keypoint
  212. Keypoint object with new coordinates.
  213. The instance of the keypoint may have been modified in-place.
  214. """
  215. self.x += x
  216. self.y += y
  217. return self
  218. def shift(self, x=0, y=0):
  219. """Move the keypoint around on an image.
  220. Parameters
  221. ----------
  222. x : number, optional
  223. Move by this value on the x axis.
  224. y : number, optional
  225. Move by this value on the y axis.
  226. Returns
  227. -------
  228. imgaug.augmentables.kps.Keypoint
  229. Keypoint object with new coordinates.
  230. """
  231. return self.deepcopy().shift_(x, y)
  232. def draw_on_image(self, image, color=(0, 255, 0), alpha=1.0, size=3,
  233. copy=True, raise_if_out_of_image=False):
  234. """Draw the keypoint onto a given image.
  235. The keypoint is drawn as a square.
  236. Parameters
  237. ----------
  238. image : (H,W,3) ndarray
  239. The image onto which to draw the keypoint.
  240. color : int or list of int or tuple of int or (3,) ndarray, optional
  241. The RGB color of the keypoint.
  242. If a single ``int`` ``C``, then that is equivalent to ``(C,C,C)``.
  243. alpha : float, optional
  244. The opacity of the drawn keypoint, where ``1.0`` denotes a fully
  245. visible keypoint and ``0.0`` an invisible one.
  246. size : int, optional
  247. The size of the keypoint. If set to ``S``, each square will have
  248. size ``S x S``.
  249. copy : bool, optional
  250. Whether to copy the image before drawing the keypoint.
  251. raise_if_out_of_image : bool, optional
  252. Whether to raise an exception if the keypoint is outside of the
  253. image.
  254. Returns
  255. -------
  256. image : (H,W,3) ndarray
  257. Image with drawn keypoint.
  258. """
  259. # pylint: disable=redefined-outer-name
  260. if copy:
  261. image = np.copy(image)
  262. if image.ndim == 2:
  263. assert ia.is_single_number(color), (
  264. "Got a 2D image. Expected then 'color' to be a single number, "
  265. "but got %s." % (str(color),))
  266. elif image.ndim == 3 and ia.is_single_number(color):
  267. color = [color] * image.shape[-1]
  268. input_dtype = image.dtype
  269. alpha_color = color
  270. if alpha < 0.01:
  271. # keypoint invisible, nothing to do
  272. return image
  273. if alpha > 0.99:
  274. alpha = 1
  275. else:
  276. image = image.astype(np.float32, copy=False)
  277. alpha_color = alpha * np.array(color)
  278. height, width = image.shape[0:2]
  279. y, x = self.y_int, self.x_int
  280. x1 = max(x - size//2, 0)
  281. x2 = min(x + 1 + size//2, width)
  282. y1 = max(y - size//2, 0)
  283. y2 = min(y + 1 + size//2, height)
  284. x1_clipped, x2_clipped = np.clip([x1, x2], 0, width)
  285. y1_clipped, y2_clipped = np.clip([y1, y2], 0, height)
  286. x1_clipped_ooi = (x1_clipped < 0 or x1_clipped >= width)
  287. x2_clipped_ooi = (x2_clipped < 0 or x2_clipped >= width+1)
  288. y1_clipped_ooi = (y1_clipped < 0 or y1_clipped >= height)
  289. y2_clipped_ooi = (y2_clipped < 0 or y2_clipped >= height+1)
  290. x_ooi = (x1_clipped_ooi and x2_clipped_ooi)
  291. y_ooi = (y1_clipped_ooi and y2_clipped_ooi)
  292. x_zero_size = (x2_clipped - x1_clipped) < 1 # min size is 1px
  293. y_zero_size = (y2_clipped - y1_clipped) < 1
  294. if not x_ooi and not y_ooi and not x_zero_size and not y_zero_size:
  295. if alpha == 1:
  296. image[y1_clipped:y2_clipped, x1_clipped:x2_clipped] = color
  297. else:
  298. image[y1_clipped:y2_clipped, x1_clipped:x2_clipped] = (
  299. (1 - alpha)
  300. * image[y1_clipped:y2_clipped, x1_clipped:x2_clipped]
  301. + alpha_color
  302. )
  303. else:
  304. if raise_if_out_of_image:
  305. raise Exception(
  306. "Cannot draw keypoint x=%.8f, y=%.8f on image with "
  307. "shape %s." % (y, x, image.shape))
  308. if image.dtype.name != input_dtype.name:
  309. if input_dtype.name == "uint8":
  310. image = np.clip(image, 0, 255, out=image)
  311. image = image.astype(input_dtype, copy=False)
  312. return image
  313. def generate_similar_points_manhattan(self, nb_steps, step_size,
  314. return_array=False):
  315. """Generate nearby points based on manhattan distance.
  316. To generate the first neighbouring points, a distance of ``S`` (step
  317. size) is moved from the center point (this keypoint) to the top,
  318. right, bottom and left, resulting in four new points. From these new
  319. points, the pattern is repeated. Overlapping points are ignored.
  320. The resulting points have a shape similar to a square rotated
  321. by ``45`` degrees.
  322. Parameters
  323. ----------
  324. nb_steps : int
  325. The number of steps to move from the center point.
  326. ``nb_steps=1`` results in a total of ``5`` output points (one
  327. center point + four neighbours).
  328. step_size : number
  329. The step size to move from every point to its neighbours.
  330. return_array : bool, optional
  331. Whether to return the generated points as a list of
  332. :class:`Keypoint` or an array of shape ``(N,2)``, where ``N`` is
  333. the number of generated points and the second axis contains the
  334. x-/y-coordinates.
  335. Returns
  336. -------
  337. list of imgaug.augmentables.kps.Keypoint or (N,2) ndarray
  338. If `return_array` was ``False``, then a list of :class:`Keypoint`.
  339. Otherwise a numpy array of shape ``(N,2)``, where ``N`` is the
  340. number of generated points and the second axis contains
  341. the x-/y-coordinates. The center keypoint (the one on which this
  342. function was called) is always included.
  343. """
  344. # TODO add test
  345. # Points generates in manhattan style with S steps have a shape
  346. # similar to a 45deg rotated square. The center line with the origin
  347. # point has S+1+S = 1+2*S points (S to the left, S to the right).
  348. # The lines above contain (S+1+S)-2 + (S+1+S)-2-2 + ... + 1 points.
  349. # E.g. for S=2 it would be 3+1=4 and for S=3 it would be 5+3+1=9.
  350. # Same for the lines below the center. Hence the total number of
  351. # points is S+1+S + 2*(S^2).
  352. nb_points = nb_steps + 1 + nb_steps + 2*(nb_steps**2)
  353. points = np.zeros((nb_points, 2), dtype=np.float32)
  354. # we start at the bottom-most line and move towards the top-most line
  355. yy = np.linspace(
  356. self.y - nb_steps * step_size,
  357. self.y + nb_steps * step_size,
  358. nb_steps + 1 + nb_steps)
  359. # bottom-most line contains only one point
  360. width = 1
  361. nth_point = 0
  362. for i_y, y in enumerate(yy):
  363. if width == 1:
  364. xx = [self.x]
  365. else:
  366. xx = np.linspace(
  367. self.x - (width-1)//2 * step_size,
  368. self.x + (width-1)//2 * step_size,
  369. width)
  370. for x in xx:
  371. points[nth_point] = [x, y]
  372. nth_point += 1
  373. if i_y < nb_steps:
  374. width += 2
  375. else:
  376. width -= 2
  377. if return_array:
  378. return points
  379. return [self.deepcopy(x=point[0], y=point[1]) for point in points]
  380. def coords_almost_equals(self, other, max_distance=1e-4):
  381. """Estimate if this and another KP have almost identical coordinates.
  382. Added in 0.4.0.
  383. Parameters
  384. ----------
  385. other : imgaug.augmentables.kps.Keypoint or iterable
  386. The other keypoint with which to compare this one.
  387. If this is an ``iterable``, it is assumed to contain the
  388. xy-coordinates of a keypoint.
  389. max_distance : number, optional
  390. The maximum euclidean distance between a this keypoint and the
  391. other one. If the distance is exceeded, the two keypoints are not
  392. viewed as equal.
  393. Returns
  394. -------
  395. bool
  396. Whether the two keypoints have almost identical coordinates.
  397. """
  398. if ia.is_np_array(other):
  399. # we use flat here in case other is (N,2) instead of (4,)
  400. coords_b = other.flat
  401. elif ia.is_iterable(other):
  402. coords_b = list(ia.flatten(other))
  403. else:
  404. assert isinstance(other, Keypoint), (
  405. "Expected 'other' to be an iterable containing one "
  406. "(x,y)-coordinate pair or a Keypoint. "
  407. "Got type %s." % (type(other),))
  408. coords_b = other.coords.flat
  409. coords_a = self.coords
  410. return np.allclose(coords_a.flat, coords_b, atol=max_distance, rtol=0)
  411. def almost_equals(self, other, max_distance=1e-4):
  412. """Compare this and another KP's coordinates.
  413. .. note::
  414. This method is currently identical to ``coords_almost_equals``.
  415. It exists for consistency with ``BoundingBox`` and ``Polygons``.
  416. Added in 0.4.0.
  417. Parameters
  418. ----------
  419. other : imgaug.augmentables.kps.Keypoint or iterable
  420. The other object to compare against. Expected to be a
  421. ``Keypoint``.
  422. max_distance : number, optional
  423. See
  424. :func:`~imgaug.augmentables.kps.Keypoint.coords_almost_equals`.
  425. Returns
  426. -------
  427. bool
  428. ``True`` if the coordinates are almost equal. Otherwise ``False``.
  429. """
  430. return self.coords_almost_equals(other, max_distance=max_distance)
  431. def copy(self, x=None, y=None):
  432. """Create a shallow copy of the keypoint instance.
  433. Parameters
  434. ----------
  435. x : None or number, optional
  436. Coordinate of the keypoint on the x axis.
  437. If ``None``, the instance's value will be copied.
  438. y : None or number, optional
  439. Coordinate of the keypoint on the y axis.
  440. If ``None``, the instance's value will be copied.
  441. Returns
  442. -------
  443. imgaug.augmentables.kps.Keypoint
  444. Shallow copy.
  445. """
  446. return self.deepcopy(x=x, y=y)
  447. def deepcopy(self, x=None, y=None):
  448. """Create a deep copy of the keypoint instance.
  449. Parameters
  450. ----------
  451. x : None or number, optional
  452. Coordinate of the keypoint on the x axis.
  453. If ``None``, the instance's value will be copied.
  454. y : None or number, optional
  455. Coordinate of the keypoint on the y axis.
  456. If ``None``, the instance's value will be copied.
  457. Returns
  458. -------
  459. imgaug.augmentables.kps.Keypoint
  460. Deep copy.
  461. """
  462. x = self.x if x is None else x
  463. y = self.y if y is None else y
  464. return Keypoint(x=x, y=y)
  465. def __repr__(self):
  466. return self.__str__()
  467. def __str__(self):
  468. return "Keypoint(x=%.8f, y=%.8f)" % (self.x, self.y)
  469. class KeypointsOnImage(IAugmentable):
  470. """Container for all keypoints on a single image.
  471. Parameters
  472. ----------
  473. keypoints : list of imgaug.augmentables.kps.Keypoint
  474. List of keypoints on the image.
  475. shape : tuple of int or ndarray
  476. The shape of the image on which the objects are placed.
  477. Either an image with shape ``(H,W,[C])`` or a ``tuple`` denoting
  478. such an image shape.
  479. Examples
  480. --------
  481. >>> import numpy as np
  482. >>> from imgaug.augmentables.kps import Keypoint, KeypointsOnImage
  483. >>>
  484. >>> image = np.zeros((70, 70))
  485. >>> kps = [Keypoint(x=10, y=20), Keypoint(x=34, y=60)]
  486. >>> kps_oi = KeypointsOnImage(kps, shape=image.shape)
  487. """
  488. def __init__(self, keypoints, shape):
  489. self.keypoints = keypoints
  490. self.shape = normalize_shape(shape)
  491. @property
  492. def items(self):
  493. """Get the keypoints in this container.
  494. Added in 0.4.0.
  495. Returns
  496. -------
  497. list of Keypoint
  498. Keypoints within this container.
  499. """
  500. return self.keypoints
  501. @items.setter
  502. def items(self, value):
  503. """Set the keypoints in this container.
  504. Added in 0.4.0.
  505. Parameters
  506. ----------
  507. value : list of Keypoint
  508. Keypoints within this container.
  509. """
  510. self.keypoints = value
  511. @property
  512. def height(self):
  513. """Get the image height.
  514. Returns
  515. -------
  516. int
  517. Image height.
  518. """
  519. return self.shape[0]
  520. @property
  521. def width(self):
  522. """Get the image width.
  523. Returns
  524. -------
  525. int
  526. Image width.
  527. """
  528. return self.shape[1]
  529. @property
  530. def empty(self):
  531. """Determine whether this object contains zero keypoints.
  532. Returns
  533. -------
  534. bool
  535. ``True`` if this object contains zero keypoints.
  536. """
  537. return len(self.keypoints) == 0
  538. def on_(self, image):
  539. """Project all keypoints from one image shape to a new one in-place.
  540. Added in 0.4.0.
  541. Parameters
  542. ----------
  543. image : ndarray or tuple of int
  544. New image onto which the keypoints are to be projected.
  545. May also simply be that new image's shape tuple.
  546. Returns
  547. -------
  548. imgaug.augmentables.kps.KeypointsOnImage
  549. Object containing all projected keypoints.
  550. The object may have been modified in-place.
  551. """
  552. # pylint: disable=invalid-name
  553. on_shape = normalize_shape(image)
  554. if on_shape[0:2] == self.shape[0:2]:
  555. self.shape = on_shape # channels may differ
  556. return self
  557. for i, kp in enumerate(self.keypoints):
  558. self.keypoints[i] = kp.project_(self.shape, on_shape)
  559. self.shape = on_shape
  560. return self
  561. def on(self, image):
  562. """Project all keypoints from one image shape to a new one.
  563. Parameters
  564. ----------
  565. image : ndarray or tuple of int
  566. New image onto which the keypoints are to be projected.
  567. May also simply be that new image's shape tuple.
  568. Returns
  569. -------
  570. imgaug.augmentables.kps.KeypointsOnImage
  571. Object containing all projected keypoints.
  572. """
  573. # pylint: disable=invalid-name
  574. return self.deepcopy().on_(image)
  575. def draw_on_image(self, image, color=(0, 255, 0), alpha=1.0, size=3,
  576. copy=True, raise_if_out_of_image=False):
  577. """Draw all keypoints onto a given image.
  578. Each keypoint is drawn as a square of provided color and size.
  579. Parameters
  580. ----------
  581. image : (H,W,3) ndarray
  582. The image onto which to draw the keypoints.
  583. This image should usually have the same shape as
  584. set in ``KeypointsOnImage.shape``.
  585. color : int or list of int or tuple of int or (3,) ndarray, optional
  586. The RGB color of all keypoints.
  587. If a single ``int`` ``C``, then that is equivalent to ``(C,C,C)``.
  588. alpha : float, optional
  589. The opacity of the drawn keypoint, where ``1.0`` denotes a fully
  590. visible keypoint and ``0.0`` an invisible one.
  591. size : int, optional
  592. The size of each point. If set to ``C``, each square will have
  593. size ``C x C``.
  594. copy : bool, optional
  595. Whether to copy the image before drawing the points.
  596. raise_if_out_of_image : bool, optional
  597. Whether to raise an exception if any keypoint is outside of the
  598. image.
  599. Returns
  600. -------
  601. (H,W,3) ndarray
  602. Image with drawn keypoints.
  603. """
  604. # pylint: disable=redefined-outer-name
  605. image = np.copy(image) if copy else image
  606. for keypoint in self.keypoints:
  607. image = keypoint.draw_on_image(
  608. image, color=color, alpha=alpha, size=size, copy=False,
  609. raise_if_out_of_image=raise_if_out_of_image)
  610. return image
  611. def remove_out_of_image_fraction_(self, fraction):
  612. """Remove all KPs with an OOI fraction of at least `fraction` in-place.
  613. 'OOI' is the abbreviation for 'out of image'.
  614. This method exists for consistency with other augmentables, e.g.
  615. bounding boxes.
  616. Added in 0.4.0.
  617. Parameters
  618. ----------
  619. fraction : number
  620. Minimum out of image fraction that a keypoint has to have in
  621. order to be removed. Note that any keypoint can only have a
  622. fraction of either ``1.0`` (is outside) or ``0.0`` (is inside).
  623. Set this to ``0.0+eps`` to remove all points that are outside of
  624. the image. Setting this to ``0.0`` will remove all points.
  625. Returns
  626. -------
  627. imgaug.augmentables.kps.KeypointsOnImage
  628. Reduced set of keypoints, with those thathad an out of image
  629. fraction greater or equal the given one removed.
  630. The object may have been modified in-place.
  631. """
  632. return _remove_out_of_image_fraction_(self, fraction)
  633. def remove_out_of_image_fraction(self, fraction):
  634. """Remove all KPs with an out of image fraction of at least `fraction`.
  635. This method exists for consistency with other augmentables, e.g.
  636. bounding boxes.
  637. Added in 0.4.0.
  638. Parameters
  639. ----------
  640. fraction : number
  641. Minimum out of image fraction that a keypoint has to have in
  642. order to be removed. Note that any keypoint can only have a
  643. fraction of either ``1.0`` (is outside) or ``0.0`` (is inside).
  644. Set this to ``0.0+eps`` to remove all points that are outside of
  645. the image. Setting this to ``0.0`` will remove all points.
  646. Returns
  647. -------
  648. imgaug.augmentables.kps.KeypointsOnImage
  649. Reduced set of keypoints, with those thathad an out of image
  650. fraction greater or equal the given one removed.
  651. """
  652. return self.deepcopy().remove_out_of_image_fraction_(fraction)
  653. def clip_out_of_image_(self):
  654. """Remove all KPs that are outside of the image plane.
  655. This method exists for consistency with other augmentables, e.g.
  656. bounding boxes.
  657. Added in 0.4.0.
  658. Returns
  659. -------
  660. imgaug.augmentables.kps.KeypointsOnImage
  661. Keypoints that are inside the image plane.
  662. The object may have been modified in-place.
  663. """
  664. # we could use anything >0 here as the fraction
  665. return self.remove_out_of_image_fraction_(0.5)
  666. def clip_out_of_image(self):
  667. """Remove all KPs that are outside of the image plane.
  668. This method exists for consistency with other augmentables, e.g.
  669. bounding boxes.
  670. Added in 0.4.0.
  671. Returns
  672. -------
  673. imgaug.augmentables.kps.KeypointsOnImage
  674. Keypoints that are inside the image plane.
  675. """
  676. return self.deepcopy().clip_out_of_image_()
  677. def shift_(self, x=0, y=0):
  678. """Move the keypoints on the x/y-axis in-place.
  679. Added in 0.4.0.
  680. Parameters
  681. ----------
  682. x : number, optional
  683. Move each keypoint by this value on the x axis.
  684. y : number, optional
  685. Move each keypoint by this value on the y axis.
  686. Returns
  687. -------
  688. imgaug.augmentables.kps.KeypointsOnImage
  689. Keypoints after moving them.
  690. The object and its items may have been modified in-place.
  691. """
  692. for i, keypoint in enumerate(self.keypoints):
  693. self.keypoints[i] = keypoint.shift_(x=x, y=y)
  694. return self
  695. def shift(self, x=0, y=0):
  696. """Move the keypoints on the x/y-axis.
  697. Parameters
  698. ----------
  699. x : number, optional
  700. Move each keypoint by this value on the x axis.
  701. y : number, optional
  702. Move each keypoint by this value on the y axis.
  703. Returns
  704. -------
  705. imgaug.augmentables.kps.KeypointsOnImage
  706. Keypoints after moving them.
  707. """
  708. return self.deepcopy().shift_(x=x, y=y)
  709. @ia.deprecated(alt_func="KeypointsOnImage.to_xy_array()")
  710. def get_coords_array(self):
  711. """Convert all keypoint coordinates to an array of shape ``(N,2)``.
  712. Returns
  713. -------
  714. (N, 2) ndarray
  715. Array containing the coordinates of all keypoints.
  716. ``N`` denotes the number of keypoints. The second axis denotes
  717. the x/y-coordinates.
  718. """
  719. return self.to_xy_array()
  720. def to_xy_array(self):
  721. """Convert all keypoint coordinates to an array of shape ``(N,2)``.
  722. Returns
  723. -------
  724. (N, 2) ndarray
  725. Array containing the coordinates of all keypoints.
  726. ``N`` denotes the number of keypoints. The second axis denotes
  727. the x/y-coordinates.
  728. """
  729. result = np.zeros((len(self.keypoints), 2), dtype=np.float32)
  730. for i, keypoint in enumerate(self.keypoints):
  731. result[i, 0] = keypoint.x
  732. result[i, 1] = keypoint.y
  733. return result
  734. @staticmethod
  735. @ia.deprecated(alt_func="KeypointsOnImage.from_xy_array()")
  736. def from_coords_array(coords, shape):
  737. """Convert an ``(N,2)`` array to a ``KeypointsOnImage`` object.
  738. Parameters
  739. ----------
  740. coords : (N, 2) ndarray
  741. Coordinates of ``N`` keypoints on an image, given as a ``(N,2)``
  742. array of xy-coordinates.
  743. shape : tuple
  744. The shape of the image on which the keypoints are placed.
  745. Returns
  746. -------
  747. imgaug.augmentables.kps.KeypointsOnImage
  748. :class:`KeypointsOnImage` object containing the array's keypoints.
  749. """
  750. return KeypointsOnImage.from_xy_array(coords, shape)
  751. @classmethod
  752. def from_xy_array(cls, xy, shape):
  753. """Convert an ``(N,2)`` array to a ``KeypointsOnImage`` object.
  754. Parameters
  755. ----------
  756. xy : (N, 2) ndarray or iterable of iterable of number
  757. Coordinates of ``N`` keypoints on an image, given as a ``(N,2)``
  758. array of xy-coordinates.
  759. shape : tuple of int or ndarray
  760. The shape of the image on which the keypoints are placed.
  761. Returns
  762. -------
  763. imgaug.augmentables.kps.KeypointsOnImage
  764. :class:`KeypointsOnImage` object containing the array's keypoints.
  765. """
  766. xy = np.array(xy, dtype=np.float32)
  767. # note that np.array([]) is (0,), not (0, 2)
  768. if xy.shape[0] == 0: # pylint: disable=unsubscriptable-object
  769. return KeypointsOnImage([], shape)
  770. assert xy.ndim == 2 and xy.shape[-1] == 2, ( # pylint: disable=unsubscriptable-object
  771. "Expected input array to have shape (N,2), "
  772. "got shape %s." % (xy.shape,))
  773. keypoints = [Keypoint(x=coord[0], y=coord[1]) for coord in xy]
  774. return KeypointsOnImage(keypoints, shape)
  775. def fill_from_xy_array_(self, xy):
  776. """Modify the keypoint coordinates of this instance in-place.
  777. .. note::
  778. This currently expects that `xy` contains exactly as many
  779. coordinates as there are keypoints in this instance. Otherwise,
  780. an ``AssertionError`` will be raised.
  781. Added in 0.4.0.
  782. Parameters
  783. ----------
  784. xy : (N, 2) ndarray or iterable of iterable of number
  785. Coordinates of ``N`` keypoints on an image, given as a ``(N,2)``
  786. array of xy-coordinates. ``N`` must match the number of keypoints
  787. in this instance.
  788. Returns
  789. -------
  790. KeypointsOnImage
  791. This instance itself, with updated keypoint coordinates.
  792. Note that the instance was modified in-place.
  793. """
  794. xy = np.array(xy, dtype=np.float32)
  795. # note that np.array([]) is (0,), not (0, 2)
  796. assert xy.shape[0] == 0 or (xy.ndim == 2 and xy.shape[-1] == 2), ( # pylint: disable=unsubscriptable-object
  797. "Expected input array to have shape (N,2), "
  798. "got shape %s." % (xy.shape,))
  799. assert len(xy) == len(self.keypoints), (
  800. "Expected to receive as many keypoint coordinates as there are "
  801. "currently keypoints in this instance. Got %d, expected %d." % (
  802. len(xy), len(self.keypoints)))
  803. for kp, (x, y) in zip(self.keypoints, xy):
  804. kp.x = x
  805. kp.y = y
  806. return self
  807. # TODO add to_gaussian_heatmaps(), from_gaussian_heatmaps()
  808. def to_keypoint_image(self, size=1):
  809. """Create an ``(H,W,N)`` image with keypoint coordinates set to ``255``.
  810. This method generates a new ``uint8`` array of shape ``(H,W,N)``,
  811. where ``H`` is the ``.shape`` height, ``W`` the ``.shape`` width and
  812. ``N`` is the number of keypoints. The array is filled with zeros.
  813. The coordinate of the ``n``-th keypoint is set to ``255`` in the
  814. ``n``-th channel.
  815. This function can be used as a helper when augmenting keypoints with
  816. a method that only supports the augmentation of images.
  817. Parameters
  818. -------
  819. size : int
  820. Size of each (squared) point.
  821. Returns
  822. -------
  823. (H,W,N) ndarray
  824. Image in which the keypoints are marked. ``H`` is the height,
  825. defined in ``KeypointsOnImage.shape[0]`` (analogous ``W``).
  826. ``N`` is the number of keypoints.
  827. """
  828. height, width = self.shape[0:2]
  829. image = np.zeros((height, width, len(self.keypoints)), dtype=np.uint8)
  830. assert size % 2 != 0, (
  831. "Expected 'size' to have an odd value, got %d instead." % (size,))
  832. sizeh = max(0, (size-1)//2)
  833. for i, keypoint in enumerate(self.keypoints):
  834. # TODO for float values spread activation over several cells
  835. # here and do voting at the end
  836. y = keypoint.y_int
  837. x = keypoint.x_int
  838. x1 = np.clip(x - sizeh, 0, width-1)
  839. x2 = np.clip(x + sizeh + 1, 0, width)
  840. y1 = np.clip(y - sizeh, 0, height-1)
  841. y2 = np.clip(y + sizeh + 1, 0, height)
  842. if x1 < x2 and y1 < y2:
  843. image[y1:y2, x1:x2, i] = 128
  844. if 0 <= y < height and 0 <= x < width:
  845. image[y, x, i] = 255
  846. return image
  847. @staticmethod
  848. def from_keypoint_image(image, if_not_found_coords={"x": -1, "y": -1},
  849. threshold=1, nb_channels=None):
  850. """Convert ``to_keypoint_image()`` outputs to ``KeypointsOnImage``.
  851. This is the inverse of :func:`KeypointsOnImage.to_keypoint_image`.
  852. Parameters
  853. ----------
  854. image : (H,W,N) ndarray
  855. The keypoints image. N is the number of keypoints.
  856. if_not_found_coords : tuple or list or dict or None, optional
  857. Coordinates to use for keypoints that cannot be found in `image`.
  858. * If this is a ``list``/``tuple``, it must contain two ``int``
  859. values.
  860. * If it is a ``dict``, it must contain the keys ``x`` and
  861. ``y`` with each containing one ``int`` value.
  862. * If this is ``None``, then the keypoint will not be added to the
  863. final :class:`KeypointsOnImage` object.
  864. threshold : int, optional
  865. The search for keypoints works by searching for the argmax in
  866. each channel. This parameters contains the minimum value that
  867. the max must have in order to be viewed as a keypoint.
  868. nb_channels : None or int, optional
  869. Number of channels of the image on which the keypoints are placed.
  870. Some keypoint augmenters require that information.
  871. If set to ``None``, the keypoint's shape will be set
  872. to ``(height, width)``, otherwise ``(height, width, nb_channels)``.
  873. Returns
  874. -------
  875. imgaug.augmentables.kps.KeypointsOnImage
  876. The extracted keypoints.
  877. """
  878. # pylint: disable=dangerous-default-value
  879. assert image.ndim == 3, (
  880. "Expected 'image' to have three dimensions, "
  881. "got %d with shape %s instead." % (image.ndim, image.shape))
  882. height, width, nb_keypoints = image.shape
  883. drop_if_not_found = False
  884. if if_not_found_coords is None:
  885. drop_if_not_found = True
  886. if_not_found_x = -1
  887. if_not_found_y = -1
  888. elif isinstance(if_not_found_coords, (tuple, list)):
  889. assert len(if_not_found_coords) == 2, (
  890. "Expected tuple 'if_not_found_coords' to contain exactly two "
  891. "values, got %d values." % (len(if_not_found_coords),))
  892. if_not_found_x = if_not_found_coords[0]
  893. if_not_found_y = if_not_found_coords[1]
  894. elif isinstance(if_not_found_coords, dict):
  895. if_not_found_x = if_not_found_coords["x"]
  896. if_not_found_y = if_not_found_coords["y"]
  897. else:
  898. raise Exception(
  899. "Expected if_not_found_coords to be None or tuple or list "
  900. "or dict, got %s." % (type(if_not_found_coords),))
  901. keypoints = []
  902. for i in sm.xrange(nb_keypoints):
  903. maxidx_flat = np.argmax(image[..., i])
  904. maxidx_ndim = np.unravel_index(maxidx_flat, (height, width))
  905. found = (image[maxidx_ndim[0], maxidx_ndim[1], i] >= threshold)
  906. if found:
  907. x = maxidx_ndim[1] + 0.5
  908. y = maxidx_ndim[0] + 0.5
  909. keypoints.append(Keypoint(x=x, y=y))
  910. else:
  911. if drop_if_not_found:
  912. # dont add the keypoint to the result list, i.e. drop it
  913. pass
  914. else:
  915. keypoints.append(Keypoint(x=if_not_found_x,
  916. y=if_not_found_y))
  917. out_shape = (height, width)
  918. if nb_channels is not None:
  919. out_shape += (nb_channels,)
  920. return KeypointsOnImage(keypoints, shape=out_shape)
  921. def to_distance_maps(self, inverted=False):
  922. """Generate a ``(H,W,N)`` array of distance maps for ``N`` keypoints.
  923. The ``n``-th distance map contains at every location ``(y, x)`` the
  924. euclidean distance to the ``n``-th keypoint.
  925. This function can be used as a helper when augmenting keypoints with a
  926. method that only supports the augmentation of images.
  927. Parameters
  928. -------
  929. inverted : bool, optional
  930. If ``True``, inverted distance maps are returned where each
  931. distance value d is replaced by ``d/(d+1)``, i.e. the distance
  932. maps have values in the range ``(0.0, 1.0]`` with ``1.0`` denoting
  933. exactly the position of the respective keypoint.
  934. Returns
  935. -------
  936. (H,W,N) ndarray
  937. A ``float32`` array containing ``N`` distance maps for ``N``
  938. keypoints. Each location ``(y, x, n)`` in the array denotes the
  939. euclidean distance at ``(y, x)`` to the ``n``-th keypoint.
  940. If `inverted` is ``True``, the distance ``d`` is replaced
  941. by ``d/(d+1)``. The height and width of the array match the
  942. height and width in ``KeypointsOnImage.shape``.
  943. """
  944. height, width = self.shape[0:2]
  945. distance_maps = np.zeros((height, width, len(self.keypoints)),
  946. dtype=np.float32)
  947. yy = np.arange(0, height)
  948. xx = np.arange(0, width)
  949. grid_xx, grid_yy = np.meshgrid(xx, yy)
  950. for i, keypoint in enumerate(self.keypoints):
  951. y, x = keypoint.y, keypoint.x
  952. distance_maps[:, :, i] = (grid_xx - x) ** 2 + (grid_yy - y) ** 2
  953. distance_maps = np.sqrt(distance_maps)
  954. if inverted:
  955. return 1/(distance_maps+1)
  956. return distance_maps
  957. # TODO add option to if_not_found_coords to reuse old keypoint coords
  958. @staticmethod
  959. def from_distance_maps(distance_maps, inverted=False,
  960. if_not_found_coords={"x": -1, "y": -1},
  961. threshold=None, nb_channels=None):
  962. """Convert outputs of ``to_distance_maps()`` to ``KeypointsOnImage``.
  963. This is the inverse of :func:`KeypointsOnImage.to_distance_maps`.
  964. Parameters
  965. ----------
  966. distance_maps : (H,W,N) ndarray
  967. The distance maps. ``N`` is the number of keypoints.
  968. inverted : bool, optional
  969. Whether the given distance maps were generated in inverted mode
  970. (i.e. :func:`KeypointsOnImage.to_distance_maps` was called with
  971. ``inverted=True``) or in non-inverted mode.
  972. if_not_found_coords : tuple or list or dict or None, optional
  973. Coordinates to use for keypoints that cannot be found
  974. in `distance_maps`.
  975. * If this is a ``list``/``tuple``, it must contain two ``int``
  976. values.
  977. * If it is a ``dict``, it must contain the keys ``x`` and
  978. ``y`` with each containing one ``int`` value.
  979. * If this is ``None``, then the keypoint will not be added to the
  980. final :class:`KeypointsOnImage` object.
  981. threshold : float, optional
  982. The search for keypoints works by searching for the
  983. argmin (non-inverted) or argmax (inverted) in each channel. This
  984. parameters contains the maximum (non-inverted) or
  985. minimum (inverted) value to accept in order to view a hit as a
  986. keypoint. Use ``None`` to use no min/max.
  987. nb_channels : None or int, optional
  988. Number of channels of the image on which the keypoints are placed.
  989. Some keypoint augmenters require that information.
  990. If set to ``None``, the keypoint's shape will be set
  991. to ``(height, width)``, otherwise ``(height, width, nb_channels)``.
  992. Returns
  993. -------
  994. imgaug.augmentables.kps.KeypointsOnImage
  995. The extracted keypoints.
  996. """
  997. # pylint: disable=dangerous-default-value
  998. assert distance_maps.ndim == 3, (
  999. "Expected three-dimensional input, got %d dimensions and "
  1000. "shape %s." % (distance_maps.ndim, distance_maps.shape))
  1001. height, width, nb_keypoints = distance_maps.shape
  1002. drop_if_not_found = False
  1003. if if_not_found_coords is None:
  1004. drop_if_not_found = True
  1005. if_not_found_x = -1
  1006. if_not_found_y = -1
  1007. elif isinstance(if_not_found_coords, (tuple, list)):
  1008. assert len(if_not_found_coords) == 2, (
  1009. "Expected tuple/list 'if_not_found_coords' to contain "
  1010. "exactly two entries, got %d." % (len(if_not_found_coords),))
  1011. if_not_found_x = if_not_found_coords[0]
  1012. if_not_found_y = if_not_found_coords[1]
  1013. elif isinstance(if_not_found_coords, dict):
  1014. if_not_found_x = if_not_found_coords["x"]
  1015. if_not_found_y = if_not_found_coords["y"]
  1016. else:
  1017. raise Exception(
  1018. "Expected if_not_found_coords to be None or tuple or list or "
  1019. "dict, got %s." % (type(if_not_found_coords),))
  1020. keypoints = []
  1021. for i in sm.xrange(nb_keypoints):
  1022. # TODO introduce voting here among all distance values that have
  1023. # min/max values
  1024. if inverted:
  1025. hitidx_flat = np.argmax(distance_maps[..., i])
  1026. else:
  1027. hitidx_flat = np.argmin(distance_maps[..., i])
  1028. hitidx_ndim = np.unravel_index(hitidx_flat, (height, width))
  1029. if not inverted and threshold is not None:
  1030. found = (distance_maps[hitidx_ndim[0], hitidx_ndim[1], i]
  1031. < threshold)
  1032. elif inverted and threshold is not None:
  1033. found = (distance_maps[hitidx_ndim[0], hitidx_ndim[1], i]
  1034. >= threshold)
  1035. else:
  1036. found = True
  1037. if found:
  1038. keypoints.append(Keypoint(x=hitidx_ndim[1], y=hitidx_ndim[0]))
  1039. else:
  1040. if drop_if_not_found:
  1041. # dont add the keypoint to the result list, i.e. drop it
  1042. pass
  1043. else:
  1044. keypoints.append(Keypoint(x=if_not_found_x,
  1045. y=if_not_found_y))
  1046. out_shape = (height, width)
  1047. if nb_channels is not None:
  1048. out_shape += (nb_channels,)
  1049. return KeypointsOnImage(keypoints, shape=out_shape)
  1050. # TODO add to_keypoints_on_image_() and call that wherever possible
  1051. def to_keypoints_on_image(self):
  1052. """Convert the keypoints to one ``KeypointsOnImage`` instance.
  1053. This method exists for consistency with ``BoundingBoxesOnImage``,
  1054. ``PolygonsOnImage`` and ``LineStringsOnImage``.
  1055. Added in 0.4.0.
  1056. Returns
  1057. -------
  1058. imgaug.augmentables.kps.KeypointsOnImage
  1059. Copy of this keypoints instance.
  1060. """
  1061. return self.deepcopy()
  1062. def invert_to_keypoints_on_image_(self, kpsoi):
  1063. """Invert the output of ``to_keypoints_on_image()`` in-place.
  1064. This function writes in-place into this ``KeypointsOnImage``
  1065. instance.
  1066. Added in 0.4.0.
  1067. Parameters
  1068. ----------
  1069. kpsoi : imgaug.augmentables.kps.KeypointsOnImages
  1070. Keypoints to copy data from, i.e. the outputs of
  1071. ``to_keypoints_on_image()``.
  1072. Returns
  1073. -------
  1074. KeypointsOnImage
  1075. Keypoints container with updated coordinates.
  1076. Note that the instance is also updated in-place.
  1077. """
  1078. nb_points_exp = len(self.keypoints)
  1079. assert len(kpsoi.keypoints) == nb_points_exp, (
  1080. "Expected %d coordinates, got %d." % (
  1081. nb_points_exp, len(kpsoi.keypoints)))
  1082. for kp_target, kp_source in zip(self.keypoints, kpsoi.keypoints):
  1083. kp_target.x = kp_source.x
  1084. kp_target.y = kp_source.y
  1085. self.shape = kpsoi.shape
  1086. return self
  1087. def copy(self, keypoints=None, shape=None):
  1088. """Create a shallow copy of the ``KeypointsOnImage`` object.
  1089. Parameters
  1090. ----------
  1091. keypoints : None or list of imgaug.Keypoint, optional
  1092. List of keypoints on the image.
  1093. If ``None``, the instance's keypoints will be copied.
  1094. shape : tuple of int, optional
  1095. The shape of the image on which the keypoints are placed.
  1096. If ``None``, the instance's shape will be copied.
  1097. Returns
  1098. -------
  1099. imgaug.augmentables.kps.KeypointsOnImage
  1100. Shallow copy.
  1101. """
  1102. if keypoints is None:
  1103. keypoints = self.keypoints[:]
  1104. if shape is None:
  1105. # use tuple() here in case the shape was provided as a list
  1106. shape = tuple(self.shape)
  1107. return KeypointsOnImage(keypoints, shape)
  1108. def deepcopy(self, keypoints=None, shape=None):
  1109. """Create a deep copy of the ``KeypointsOnImage`` object.
  1110. Parameters
  1111. ----------
  1112. keypoints : None or list of imgaug.Keypoint, optional
  1113. List of keypoints on the image.
  1114. If ``None``, the instance's keypoints will be copied.
  1115. shape : tuple of int, optional
  1116. The shape of the image on which the keypoints are placed.
  1117. If ``None``, the instance's shape will be copied.
  1118. Returns
  1119. -------
  1120. imgaug.augmentables.kps.KeypointsOnImage
  1121. Deep copy.
  1122. """
  1123. # Manual copy is far faster than deepcopy, so use manual copy here.
  1124. if keypoints is None:
  1125. keypoints = [kp.deepcopy() for kp in self.keypoints]
  1126. if shape is None:
  1127. # use tuple() here in case the shape was provided as a list
  1128. shape = tuple(self.shape)
  1129. return KeypointsOnImage(keypoints, shape)
  1130. def __getitem__(self, indices):
  1131. """Get the keypoint(s) with given indices.
  1132. Added in 0.4.0.
  1133. Returns
  1134. -------
  1135. list of imgaug.augmentables.kps.Keypoint
  1136. Keypoint(s) with given indices.
  1137. """
  1138. return self.keypoints[indices]
  1139. def __iter__(self):
  1140. """Iterate over the keypoints in this container.
  1141. Added in 0.4.0.
  1142. Yields
  1143. ------
  1144. Keypoint
  1145. A keypoint in this container.
  1146. The order is identical to the order in the keypoint list
  1147. provided upon class initialization.
  1148. """
  1149. return iter(self.items)
  1150. def __len__(self):
  1151. """Get the number of items in this instance.
  1152. Added in 0.4.0.
  1153. Returns
  1154. -------
  1155. int
  1156. Number of items in this instance.
  1157. """
  1158. return len(self.items)
  1159. def __repr__(self):
  1160. return self.__str__()
  1161. def __str__(self):
  1162. return "KeypointsOnImage(%s, shape=%s)" % (
  1163. str(self.keypoints), self.shape)