Skip to content

Commit

Permalink
970 imgaug workaround mock augmentations (#978)
Browse files Browse the repository at this point in the history
* Mock ImageAugmentation if imgaug not installed
- this is a workaround for issue 970
- #970
- it is meant to be temporary until we refactor
  the image augmentations to not use imgaug

* Cleanup linting issues

* version="4.3.4"
  • Loading branch information
Ezward authored Jan 14, 2022
1 parent 3c03a54 commit a3df79a
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 110 deletions.
241 changes: 132 additions & 109 deletions donkeycar/pipeline/augmentations.py
Original file line number Diff line number Diff line change
@@ -1,121 +1,144 @@
import cv2
import numpy as np
import logging
import imgaug.augmenters as iaa
from donkeycar.config import Config


logger = logging.getLogger(__name__)

#
# HACK: workaround for imgaug bug; mock our implementation
# TODO: remove this when https://github.com/autorope/donkeycar/issues/970
# is addressed.
#
try:
import imgaug.augmenters as iaa

class Augmentations(object):
"""
Some ready to use image augumentations.
"""

@classmethod
def crop(cls, left, right, top, bottom, keep_size=False):
class Augmentations(object):
"""
The image augumentation sequence.
Crops based on a region of interest among other things.
left, right, top & bottom are the number of pixels to crop.
Some ready to use image augumentations.
"""
augmentation = iaa.Crop(px=(top, right, bottom, left),
keep_size=keep_size)
return augmentation

@classmethod
def trapezoidal_mask(cls, lower_left, lower_right, upper_left, upper_right,
min_y, max_y):
"""
Uses a binary mask to generate a trapezoidal region of interest.
Especially useful in filtering out uninteresting features from an
input image.
"""
def _transform_images(images, random_state, parents, hooks):
# Transform a batch of images
transformed = []
mask = None
for image in images:
if mask is None:
mask = np.zeros(image.shape, dtype=np.int32)
# # # # # # # # # # # # #
# ul ur min_y
#
#
#
# ll lr max_y
points = [
[upper_left, min_y],
[upper_right, min_y],
[lower_right, max_y],
[lower_left, max_y]
]
cv2.fillConvexPoly(mask, np.array(points, dtype=np.int32),
[255, 255, 255])
mask = np.asarray(mask, dtype='bool')

masked = np.multiply(image, mask)
transformed.append(masked)

return transformed

def _transform_keypoints(keypoints_on_images, random_state,
parents, hooks):
# No-op
return keypoints_on_images

augmentation = iaa.Lambda(func_images=_transform_images,
func_keypoints=_transform_keypoints)
return augmentation


class ImageAugmentation:
def __init__(self, cfg, key):
aug_list = getattr(cfg, key, [])
augmentations = [ImageAugmentation.create(a, cfg) for a in aug_list]
self.augmentations = iaa.Sequential(augmentations)

@classmethod
def create(cls, aug_type: str, config: Config) -> iaa.meta.Augmenter:
""" Augmentation factory. Cropping and trapezoidal mask are
transformations which should be applied in training, validation and
inference. Multiply, Blur and similar are augmentations which should
be used only in training. """

if aug_type == 'CROP':
logger.info(f'Creating augmentation {aug_type} with ROI_CROP '
f'L: {config.ROI_CROP_LEFT}, '
f'R: {config.ROI_CROP_RIGHT}, '
f'B: {config.ROI_CROP_BOTTOM}, '
f'T: {config.ROI_CROP_TOP}')

return Augmentations.crop(left=config.ROI_CROP_LEFT,
right=config.ROI_CROP_RIGHT,
bottom=config.ROI_CROP_BOTTOM,
top=config.ROI_CROP_TOP,
keep_size=True)
elif aug_type == 'TRAPEZE':
logger.info(f'Creating augmentation {aug_type}')
return Augmentations.trapezoidal_mask(
lower_left=config.ROI_TRAPEZE_LL,
lower_right=config.ROI_TRAPEZE_LR,
upper_left=config.ROI_TRAPEZE_UL,
upper_right=config.ROI_TRAPEZE_UR,
min_y=config.ROI_TRAPEZE_MIN_Y,
max_y=config.ROI_TRAPEZE_MAX_Y)

elif aug_type == 'MULTIPLY':
interval = getattr(config, 'AUG_MULTIPLY_RANGE', (0.5, 3.0))
logger.info(f'Creating augmentation {aug_type} {interval}')
return iaa.Multiply(interval)

elif aug_type == 'BLUR':
interval = getattr(config, 'AUG_BLUR_RANGE', (0.0, 3.0))
logger.info(f'Creating augmentation {aug_type} {interval}')
return iaa.GaussianBlur(sigma=interval)

# Parts interface
def run(self, img_arr):
aug_img_arr = self.augmentations.augment_image(img_arr)
return aug_img_arr
@classmethod
def crop(cls, left, right, top, bottom, keep_size=False):
"""
The image augumentation sequence.
Crops based on a region of interest among other things.
left, right, top & bottom are the number of pixels to crop.
"""
augmentation = iaa.Crop(px=(top, right, bottom, left),
keep_size=keep_size)
return augmentation

@classmethod
def trapezoidal_mask(cls, lower_left, lower_right, upper_left,
upper_right, min_y, max_y):
"""
Uses a binary mask to generate a trapezoidal region of interest.
Especially useful in filtering out uninteresting features from an
input image.
"""
def _transform_images(images, random_state, parents, hooks):
# Transform a batch of images
transformed = []
mask = None
for image in images:
if mask is None:
mask = np.zeros(image.shape, dtype=np.int32)
# # # # # # # # # # # # #
# ul ur min_y
#
#
#
# ll lr max_y
points = [
[upper_left, min_y],
[upper_right, min_y],
[lower_right, max_y],
[lower_left, max_y]
]
cv2.fillConvexPoly(mask,
np.array(points, dtype=np.int32),
[255, 255, 255])
mask = np.asarray(mask, dtype='bool')

masked = np.multiply(image, mask)
transformed.append(masked)

return transformed

def _transform_keypoints(keypoints_on_images, random_state,
parents, hooks):
# No-op
return keypoints_on_images

augmentation = iaa.Lambda(func_images=_transform_images,
func_keypoints=_transform_keypoints)
return augmentation

class ImageAugmentation:
def __init__(self, cfg, key):
aug_list = getattr(cfg, key, [])
augmentations = \
[ImageAugmentation.create(a, cfg) for a in aug_list]
self.augmentations = iaa.Sequential(augmentations)

@classmethod
def create(cls, aug_type: str, config: Config) -> iaa.meta.Augmenter:
""" Augmenatition factory. Cropping and trapezoidal mask are
transfomations which should be applied in training, validation
and inference. Multiply, Blur and similar are augmentations
which should be used only in training. """

if aug_type == 'CROP':
logger.info(f'Creating augmentation {aug_type} with ROI_CROP '
f'L: {config.ROI_CROP_LEFT}, '
f'R: {config.ROI_CROP_RIGHT}, '
f'B: {config.ROI_CROP_BOTTOM}, '
f'T: {config.ROI_CROP_TOP}')

return Augmentations.crop(left=config.ROI_CROP_LEFT,
right=config.ROI_CROP_RIGHT,
bottom=config.ROI_CROP_BOTTOM,
top=config.ROI_CROP_TOP,
keep_size=True)
elif aug_type == 'TRAPEZE':
logger.info(f'Creating augmentation {aug_type}')
return Augmentations.trapezoidal_mask(
lower_left=config.ROI_TRAPEZE_LL,
lower_right=config.ROI_TRAPEZE_LR,
upper_left=config.ROI_TRAPEZE_UL,
upper_right=config.ROI_TRAPEZE_UR,
min_y=config.ROI_TRAPEZE_MIN_Y,
max_y=config.ROI_TRAPEZE_MAX_Y)

elif aug_type == 'MULTIPLY':
interval = getattr(config, 'AUG_MULTIPLY_RANGE', (0.5, 1.5))
logger.info(f'Creating augmentation {aug_type} {interval}')
return iaa.Multiply(interval)

elif aug_type == 'BLUR':
interval = getattr(config, 'AUG_BLUR_RANGE', (0.0, 3.0))
logger.info(f'Creating augmentation {aug_type} {interval}')
return iaa.GaussianBlur(sigma=interval)

# Parts interface
def run(self, img_arr):
aug_img_arr = self.augmentations.augment_image(img_arr)
return aug_img_arr

except ImportError:

#
# mock implementation
#
class ImageAugmentation:
def __init__(self, cfg, key):
aug_list = getattr(cfg, key, [])
for aug in aug_list:
logger.warn(
'Augmentation library could not load. '
f'Augmentation {aug} will be ignored')

def run(self, img_arr):
return img_arr
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def package_files(directory, strip_leading):
long_description = fh.read()

setup(name='donkeycar',
version="4.3.3",
version="4.3.4",
long_description=long_description,
description='Self driving library for python.',
url='https://github.com/autorope/donkeycar',
Expand Down

0 comments on commit a3df79a

Please sign in to comment.