"""Utility functions used in augmentable modules."""
from __future__ import print_function, absolute_import, division
import copy as copylib
import numpy as np
import six.moves as sm
import imgaug as ia
# TODO add tests
[docs]def copy_augmentables(augmentables):
if ia.is_np_array(augmentables):
return np.copy(augmentables)
result = []
for augmentable in augmentables:
if ia.is_np_array(augmentable):
result.append(np.copy(augmentable))
else:
result.append(augmentable.deepcopy())
return result
# Added in 0.4.0.
[docs]def deepcopy_fast(obj):
if obj is None:
return None
if ia.is_single_number(obj) or ia.is_string(obj):
return obj
if isinstance(obj, list):
return [deepcopy_fast(el) for el in obj]
if isinstance(obj, tuple):
return tuple([deepcopy_fast(el) for el in obj])
if ia.is_np_array(obj):
return np.copy(obj)
if hasattr(obj, "deepcopy"):
return obj.deepcopy()
return copylib.deepcopy(obj)
# TODO integrate into keypoints
[docs]def normalize_shape(shape):
"""Normalize a shape ``tuple`` or ``array`` to a shape ``tuple``.
Parameters
----------
shape : tuple of int or ndarray
The input to normalize. May optionally be an array.
Returns
-------
tuple of int
Shape ``tuple``.
"""
if isinstance(shape, tuple):
return shape
assert ia.is_np_array(shape), (
"Expected tuple of ints or array, got %s." % (type(shape),))
return shape.shape
[docs]def project_coords_(coords, from_shape, to_shape):
"""Project coordinates from one image shape to another in-place.
This performs a relative projection, e.g. a point at ``60%`` of the old
image width will be at ``60%`` of the new image width after projection.
Added in 0.4.0.
Parameters
----------
coords : ndarray or list of tuple of number
Coordinates to project.
Either an ``(N,2)`` numpy array or a ``list`` containing ``(x,y)``
coordinate ``tuple`` s.
from_shape : tuple of int or ndarray
Old image shape.
to_shape : tuple of int or ndarray
New image shape.
Returns
-------
ndarray
Projected coordinates as ``(N,2)`` ``float32`` numpy array.
This function may change the input data in-place.
"""
from_shape = normalize_shape(from_shape)
to_shape = normalize_shape(to_shape)
if from_shape[0:2] == to_shape[0:2]:
return coords
from_height, from_width = from_shape[0:2]
to_height, to_width = to_shape[0:2]
no_zeros_in_shapes = (
all([v > 0 for v in [from_height, from_width, to_height, to_width]]))
assert no_zeros_in_shapes, (
"Expected from_shape and to_shape to not contain zeros. Got shapes "
"%s (from_shape) and %s (to_shape)." % (from_shape, to_shape))
coords_proj = coords
if not ia.is_np_array(coords) or coords.dtype.kind != "f":
coords_proj = np.array(coords).astype(np.float32)
coords_proj[:, 0] = (coords_proj[:, 0] / from_width) * to_width
coords_proj[:, 1] = (coords_proj[:, 1] / from_height) * to_height
return coords_proj
[docs]def project_coords(coords, from_shape, to_shape):
"""Project coordinates from one image shape to another.
This performs a relative projection, e.g. a point at ``60%`` of the old
image width will be at ``60%`` of the new image width after projection.
Parameters
----------
coords : ndarray or list of tuple of number
Coordinates to project.
Either an ``(N,2)`` numpy array or a ``list`` containing ``(x,y)``
coordinate ``tuple`` s.
from_shape : tuple of int or ndarray
Old image shape.
to_shape : tuple of int or ndarray
New image shape.
Returns
-------
ndarray
Projected coordinates as ``(N,2)`` ``float32`` numpy array.
"""
if ia.is_np_array(coords):
coords = np.copy(coords)
return project_coords_(coords, from_shape, to_shape)
# TODO does that include point_b in the result?
[docs]def interpolate_point_pair(point_a, point_b, nb_steps):
"""Interpolate ``N`` points on a line segment.
Parameters
----------
point_a : iterable of number
Start point of the line segment, given as ``(x,y)`` coordinates.
point_b : iterable of number
End point of the line segment, given as ``(x,y)`` coordinates.
nb_steps : int
Number of points to interpolate between `point_a` and `point_b`.
Returns
-------
list of tuple of number
The interpolated points.
Does not include `point_a`.
"""
if nb_steps < 1:
return []
x1, y1 = point_a
x2, y2 = point_b
vec = np.float32([x2 - x1, y2 - y1])
step_size = vec / (1 + nb_steps)
return [
(x1 + (i + 1) * step_size[0], y1 + (i + 1) * step_size[1])
for i
in sm.xrange(nb_steps)]
[docs]def interpolate_points(points, nb_steps, closed=True):
"""Interpolate ``N`` on each line segment in a line string.
Parameters
----------
points : iterable of iterable of number
Points on the line segments, each one given as ``(x,y)`` coordinates.
They are assumed to form one connected line string.
nb_steps : int
Number of points to interpolate on each individual line string.
closed : bool, optional
If ``True`` the output contains the last point in `points`.
Otherwise it does not (but it will contain the interpolated points
leading to the last point).
Returns
-------
list of tuple of number
Coordinates of `points`, with additional `nb_steps` new points
interpolated between each point pair. If `closed` is ``False``,
the last point in `points` is not returned.
"""
if len(points) <= 1:
return points
if closed:
points = list(points) + [points[0]]
points_interp = []
for point_a, point_b in zip(points[:-1], points[1:]):
points_interp.extend(
[point_a]
+ interpolate_point_pair(point_a, point_b, nb_steps)
)
if not closed:
points_interp.append(points[-1])
# close does not have to be reverted here, as last point is not included
# in the extend()
return points_interp
[docs]def interpolate_points_by_max_distance(points, max_distance, closed=True):
"""Interpolate points with distance ``d`` on a line string.
For a list of points ``A, B, C``, if the distance between ``A`` and ``B``
is greater than `max_distance`, it will place at least one point between
``A`` and ``B`` at ``A + max_distance * (B - A)``. Multiple points can
be placed between the two points if they are far enough away from each
other. The process is repeated for ``B`` and ``C``.
Parameters
----------
points : iterable of iterable of number
Points on the line segments, each one given as ``(x,y)`` coordinates.
They are assumed to form one connected line string.
max_distance : number
Maximum distance between any two points in the result.
closed : bool, optional
If ``True`` the output contains the last point in `points`.
Otherwise it does not (but it will contain the interpolated points
leading to the last point).
Returns
-------
list of tuple of number
Coordinates of `points`, with interpolated points added to the
iterable. If `closed` is ``False``, the last point in `points` is not
returned.
"""
assert max_distance > 0, (
"Expected max_distance to have a value >0, got %.8f." % (
max_distance,))
if len(points) <= 1:
return points
if closed:
points = list(points) + [points[0]]
points_interp = []
for point_a, point_b in zip(points[:-1], points[1:]):
dist = np.sqrt(
(point_a[0] - point_b[0]) ** 2
+ (point_a[1] - point_b[1]) ** 2)
nb_steps = int((dist / max_distance) - 1)
points_interp.extend(
[point_a]
+ interpolate_point_pair(point_a, point_b, nb_steps))
if not closed:
points_interp.append(points[-1])
return points_interp
[docs]def convert_cbaois_to_kpsois(cbaois):
"""Convert coordinate-based augmentables to KeypointsOnImage instances.
Added in 0.4.0.
Parameters
----------
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
Coordinate-based augmentables to convert, e.g. bounding boxes.
Returns
-------
list of imgaug.augmentables.kps.KeypointsOnImage or imgaug.augmentables.kps.KeypointsOnImage
``KeypointsOnImage`` instances containing the coordinates of input
`cbaois`.
"""
if not isinstance(cbaois, list):
return cbaois.to_keypoints_on_image()
kpsois = []
for cbaoi in cbaois:
kpsois.append(cbaoi.to_keypoints_on_image())
return kpsois
[docs]def invert_convert_cbaois_to_kpsois_(cbaois, kpsois):
"""Invert the output of :func:`convert_to_cbaois_to_kpsois` in-place.
This function writes in-place into `cbaois`.
Added in 0.4.0.
Parameters
----------
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
Original coordinate-based augmentables before they were converted,
i.e. the same inputs as provided to :func:`convert_to_kpsois`.
kpsois : list of imgaug.augmentables.kps.KeypointsOnImages or imgaug.augmentables.kps.KeypointsOnImages
Keypoints to convert back to the types of `cbaois`, i.e. the outputs
of :func:`convert_cbaois_to_kpsois`.
Returns
-------
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
Parameter `cbaois`, with updated coordinates and shapes derived from
`kpsois`. `cbaois` is modified in-place.
"""
if not isinstance(cbaois, list):
assert not isinstance(kpsois, list), (
"Expected non-list for `kpsois` when `cbaois` is non-list. "
"Got type %s." % (type(kpsois.__name__)),)
return cbaois.invert_to_keypoints_on_image_(kpsois)
result = []
for cbaoi, kpsoi in zip(cbaois, kpsois):
cbaoi_recovered = cbaoi.invert_to_keypoints_on_image_(kpsoi)
result.append(cbaoi_recovered)
return result
# Added in 0.4.0.
def _remove_out_of_image_fraction_(cbaoi, fraction):
cbaoi.items = [
item for item in cbaoi.items
if item.compute_out_of_image_fraction(cbaoi.shape) < fraction]
return cbaoi
# Added in 0.4.0.
def _normalize_shift_args(x, y, top=None, right=None, bottom=None, left=None):
"""Normalize ``shift()`` arguments to x, y and handle deprecated args."""
if any([v is not None for v in [top, right, bottom, left]]):
ia.warn_deprecated(
"Got one of the arguments `top` (%s), `right` (%s), "
"`bottom` (%s), `left` (%s) in a shift() call. "
"These are deprecated. Use `x` and `y` instead." % (
top, right, bottom, left),
stacklevel=3)
top = top if top is not None else 0
right = right if right is not None else 0
bottom = bottom if bottom is not None else 0
left = left if left is not None else 0
x = x + left - right
y = y + top - bottom
return x, y