utils.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. """Utility functions used in augmentable modules."""
  2. from __future__ import print_function, absolute_import, division
  3. import copy as copylib
  4. import numpy as np
  5. import six.moves as sm
  6. import imgaug as ia
  7. # TODO add tests
  8. def copy_augmentables(augmentables):
  9. if ia.is_np_array(augmentables):
  10. return np.copy(augmentables)
  11. result = []
  12. for augmentable in augmentables:
  13. if ia.is_np_array(augmentable):
  14. result.append(np.copy(augmentable))
  15. else:
  16. result.append(augmentable.deepcopy())
  17. return result
  18. # Added in 0.4.0.
  19. def deepcopy_fast(obj):
  20. if obj is None:
  21. return None
  22. if ia.is_single_number(obj) or ia.is_string(obj):
  23. return obj
  24. if isinstance(obj, list):
  25. return [deepcopy_fast(el) for el in obj]
  26. if isinstance(obj, tuple):
  27. return tuple([deepcopy_fast(el) for el in obj])
  28. if ia.is_np_array(obj):
  29. return np.copy(obj)
  30. if hasattr(obj, "deepcopy"):
  31. return obj.deepcopy()
  32. return copylib.deepcopy(obj)
  33. # TODO integrate into keypoints
  34. def normalize_shape(shape):
  35. """Normalize a shape ``tuple`` or ``array`` to a shape ``tuple``.
  36. Parameters
  37. ----------
  38. shape : tuple of int or ndarray
  39. The input to normalize. May optionally be an array.
  40. Returns
  41. -------
  42. tuple of int
  43. Shape ``tuple``.
  44. """
  45. if isinstance(shape, tuple):
  46. return shape
  47. assert ia.is_np_array(shape), (
  48. "Expected tuple of ints or array, got %s." % (type(shape),))
  49. return shape.shape
  50. def project_coords_(coords, from_shape, to_shape):
  51. """Project coordinates from one image shape to another in-place.
  52. This performs a relative projection, e.g. a point at ``60%`` of the old
  53. image width will be at ``60%`` of the new image width after projection.
  54. Added in 0.4.0.
  55. Parameters
  56. ----------
  57. coords : ndarray or list of tuple of number
  58. Coordinates to project.
  59. Either an ``(N,2)`` numpy array or a ``list`` containing ``(x,y)``
  60. coordinate ``tuple`` s.
  61. from_shape : tuple of int or ndarray
  62. Old image shape.
  63. to_shape : tuple of int or ndarray
  64. New image shape.
  65. Returns
  66. -------
  67. ndarray
  68. Projected coordinates as ``(N,2)`` ``float32`` numpy array.
  69. This function may change the input data in-place.
  70. """
  71. from_shape = normalize_shape(from_shape)
  72. to_shape = normalize_shape(to_shape)
  73. if from_shape[0:2] == to_shape[0:2]:
  74. return coords
  75. from_height, from_width = from_shape[0:2]
  76. to_height, to_width = to_shape[0:2]
  77. no_zeros_in_shapes = (
  78. all([v > 0 for v in [from_height, from_width, to_height, to_width]]))
  79. assert no_zeros_in_shapes, (
  80. "Expected from_shape and to_shape to not contain zeros. Got shapes "
  81. "%s (from_shape) and %s (to_shape)." % (from_shape, to_shape))
  82. coords_proj = coords
  83. if not ia.is_np_array(coords) or coords.dtype.kind != "f":
  84. coords_proj = np.array(coords).astype(np.float32)
  85. coords_proj[:, 0] = (coords_proj[:, 0] / from_width) * to_width
  86. coords_proj[:, 1] = (coords_proj[:, 1] / from_height) * to_height
  87. return coords_proj
  88. def project_coords(coords, from_shape, to_shape):
  89. """Project coordinates from one image shape to another.
  90. This performs a relative projection, e.g. a point at ``60%`` of the old
  91. image width will be at ``60%`` of the new image width after projection.
  92. Parameters
  93. ----------
  94. coords : ndarray or list of tuple of number
  95. Coordinates to project.
  96. Either an ``(N,2)`` numpy array or a ``list`` containing ``(x,y)``
  97. coordinate ``tuple`` s.
  98. from_shape : tuple of int or ndarray
  99. Old image shape.
  100. to_shape : tuple of int or ndarray
  101. New image shape.
  102. Returns
  103. -------
  104. ndarray
  105. Projected coordinates as ``(N,2)`` ``float32`` numpy array.
  106. """
  107. if ia.is_np_array(coords):
  108. coords = np.copy(coords)
  109. return project_coords_(coords, from_shape, to_shape)
  110. # TODO does that include point_b in the result?
  111. def interpolate_point_pair(point_a, point_b, nb_steps):
  112. """Interpolate ``N`` points on a line segment.
  113. Parameters
  114. ----------
  115. point_a : iterable of number
  116. Start point of the line segment, given as ``(x,y)`` coordinates.
  117. point_b : iterable of number
  118. End point of the line segment, given as ``(x,y)`` coordinates.
  119. nb_steps : int
  120. Number of points to interpolate between `point_a` and `point_b`.
  121. Returns
  122. -------
  123. list of tuple of number
  124. The interpolated points.
  125. Does not include `point_a`.
  126. """
  127. if nb_steps < 1:
  128. return []
  129. x1, y1 = point_a
  130. x2, y2 = point_b
  131. vec = np.float32([x2 - x1, y2 - y1])
  132. step_size = vec / (1 + nb_steps)
  133. return [
  134. (x1 + (i + 1) * step_size[0], y1 + (i + 1) * step_size[1])
  135. for i
  136. in sm.xrange(nb_steps)]
  137. def interpolate_points(points, nb_steps, closed=True):
  138. """Interpolate ``N`` on each line segment in a line string.
  139. Parameters
  140. ----------
  141. points : iterable of iterable of number
  142. Points on the line segments, each one given as ``(x,y)`` coordinates.
  143. They are assumed to form one connected line string.
  144. nb_steps : int
  145. Number of points to interpolate on each individual line string.
  146. closed : bool, optional
  147. If ``True`` the output contains the last point in `points`.
  148. Otherwise it does not (but it will contain the interpolated points
  149. leading to the last point).
  150. Returns
  151. -------
  152. list of tuple of number
  153. Coordinates of `points`, with additional `nb_steps` new points
  154. interpolated between each point pair. If `closed` is ``False``,
  155. the last point in `points` is not returned.
  156. """
  157. if len(points) <= 1:
  158. return points
  159. if closed:
  160. points = list(points) + [points[0]]
  161. points_interp = []
  162. for point_a, point_b in zip(points[:-1], points[1:]):
  163. points_interp.extend(
  164. [point_a]
  165. + interpolate_point_pair(point_a, point_b, nb_steps)
  166. )
  167. if not closed:
  168. points_interp.append(points[-1])
  169. # close does not have to be reverted here, as last point is not included
  170. # in the extend()
  171. return points_interp
  172. def interpolate_points_by_max_distance(points, max_distance, closed=True):
  173. """Interpolate points with distance ``d`` on a line string.
  174. For a list of points ``A, B, C``, if the distance between ``A`` and ``B``
  175. is greater than `max_distance`, it will place at least one point between
  176. ``A`` and ``B`` at ``A + max_distance * (B - A)``. Multiple points can
  177. be placed between the two points if they are far enough away from each
  178. other. The process is repeated for ``B`` and ``C``.
  179. Parameters
  180. ----------
  181. points : iterable of iterable of number
  182. Points on the line segments, each one given as ``(x,y)`` coordinates.
  183. They are assumed to form one connected line string.
  184. max_distance : number
  185. Maximum distance between any two points in the result.
  186. closed : bool, optional
  187. If ``True`` the output contains the last point in `points`.
  188. Otherwise it does not (but it will contain the interpolated points
  189. leading to the last point).
  190. Returns
  191. -------
  192. list of tuple of number
  193. Coordinates of `points`, with interpolated points added to the
  194. iterable. If `closed` is ``False``, the last point in `points` is not
  195. returned.
  196. """
  197. assert max_distance > 0, (
  198. "Expected max_distance to have a value >0, got %.8f." % (
  199. max_distance,))
  200. if len(points) <= 1:
  201. return points
  202. if closed:
  203. points = list(points) + [points[0]]
  204. points_interp = []
  205. for point_a, point_b in zip(points[:-1], points[1:]):
  206. dist = np.sqrt(
  207. (point_a[0] - point_b[0]) ** 2
  208. + (point_a[1] - point_b[1]) ** 2)
  209. nb_steps = int((dist / max_distance) - 1)
  210. points_interp.extend(
  211. [point_a]
  212. + interpolate_point_pair(point_a, point_b, nb_steps))
  213. if not closed:
  214. points_interp.append(points[-1])
  215. return points_interp
  216. def convert_cbaois_to_kpsois(cbaois):
  217. """Convert coordinate-based augmentables to KeypointsOnImage instances.
  218. Added in 0.4.0.
  219. Parameters
  220. ----------
  221. cbaois : list of imgaug.augmentables.bbs.BoundingBoxesOnImage or list of imgaug.augmentables.bbs.PolygonsOnImage or list of imgaug.augmentables.bbs.LineStringsOnImage or imgaug.augmentables.bbs.BoundingBoxesOnImage or imgaug.augmentables.bbs.PolygonsOnImage or imgaug.augmentables.bbs.LineStringsOnImage
  222. Coordinate-based augmentables to convert, e.g. bounding boxes.
  223. Returns
  224. -------
  225. list of imgaug.augmentables.kps.KeypointsOnImage or imgaug.augmentables.kps.KeypointsOnImage
  226. ``KeypointsOnImage`` instances containing the coordinates of input
  227. `cbaois`.
  228. """
  229. if not isinstance(cbaois, list):
  230. return cbaois.to_keypoints_on_image()
  231. kpsois = []
  232. for cbaoi in cbaois:
  233. kpsois.append(cbaoi.to_keypoints_on_image())
  234. return kpsois
  235. def invert_convert_cbaois_to_kpsois_(cbaois, kpsois):
  236. """Invert the output of :func:`convert_to_cbaois_to_kpsois` in-place.
  237. This function writes in-place into `cbaois`.
  238. Added in 0.4.0.
  239. Parameters
  240. ----------
  241. cbaois : list of imgaug.augmentables.bbs.BoundingBoxesOnImage or list of imgaug.augmentables.bbs.PolygonsOnImage or list of imgaug.augmentables.bbs.LineStringsOnImage or imgaug.augmentables.bbs.BoundingBoxesOnImage or imgaug.augmentables.bbs.PolygonsOnImage or imgaug.augmentables.bbs.LineStringsOnImage
  242. Original coordinate-based augmentables before they were converted,
  243. i.e. the same inputs as provided to :func:`convert_to_kpsois`.
  244. kpsois : list of imgaug.augmentables.kps.KeypointsOnImages or imgaug.augmentables.kps.KeypointsOnImages
  245. Keypoints to convert back to the types of `cbaois`, i.e. the outputs
  246. of :func:`convert_cbaois_to_kpsois`.
  247. Returns
  248. -------
  249. list of imgaug.augmentables.bbs.BoundingBoxesOnImage or list of imgaug.augmentables.bbs.PolygonsOnImage or list of imgaug.augmentables.bbs.LineStringsOnImage or imgaug.augmentables.bbs.BoundingBoxesOnImage or imgaug.augmentables.bbs.PolygonsOnImage or imgaug.augmentables.bbs.LineStringsOnImage
  250. Parameter `cbaois`, with updated coordinates and shapes derived from
  251. `kpsois`. `cbaois` is modified in-place.
  252. """
  253. if not isinstance(cbaois, list):
  254. assert not isinstance(kpsois, list), (
  255. "Expected non-list for `kpsois` when `cbaois` is non-list. "
  256. "Got type %s." % (type(kpsois.__name__)),)
  257. return cbaois.invert_to_keypoints_on_image_(kpsois)
  258. result = []
  259. for cbaoi, kpsoi in zip(cbaois, kpsois):
  260. cbaoi_recovered = cbaoi.invert_to_keypoints_on_image_(kpsoi)
  261. result.append(cbaoi_recovered)
  262. return result
  263. # Added in 0.4.0.
  264. def _remove_out_of_image_fraction_(cbaoi, fraction):
  265. cbaoi.items = [
  266. item for item in cbaoi.items
  267. if item.compute_out_of_image_fraction(cbaoi.shape) < fraction]
  268. return cbaoi
  269. # Added in 0.4.0.
  270. def _normalize_shift_args(x, y, top=None, right=None, bottom=None, left=None):
  271. """Normalize ``shift()`` arguments to x, y and handle deprecated args."""
  272. if any([v is not None for v in [top, right, bottom, left]]):
  273. ia.warn_deprecated(
  274. "Got one of the arguments `top` (%s), `right` (%s), "
  275. "`bottom` (%s), `left` (%s) in a shift() call. "
  276. "These are deprecated. Use `x` and `y` instead." % (
  277. top, right, bottom, left),
  278. stacklevel=3)
  279. top = top if top is not None else 0
  280. right = right if right is not None else 0
  281. bottom = bottom if bottom is not None else 0
  282. left = left if left is not None else 0
  283. x = x + left - right
  284. y = y + top - bottom
  285. return x, y