Skip to content

Commit

Permalink
Add AlphaElementwise; fix some documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
aleju committed Sep 16, 2017
1 parent 8886d91 commit 442edf9
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 9 deletions.
242 changes: 235 additions & 7 deletions imgaug/augmenters/overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
# TODO tests
class Alpha(Augmenter):
"""
Augmenter to overlay two image sources with each other using an alpha value.
Augmenter to overlay two image sources with each other using an
alpha/transparency value.
The image sources can be imagined as branches.
If a source is not given, it is automatically the same as the input.
Expand All @@ -44,7 +45,7 @@ class Alpha(Augmenter):
Parameters
----------
factor : float or iterable of two floats or StochasticParameter, optional(default=0)
factor : int or float or iterable of two floats or StochasticParameter, optional(default=0)
Weighting of the results of the first branch. Values close to 0 mean
that the results from the second branch (see parameter `second`)
make up most of the final image.
Expand Down Expand Up @@ -91,13 +92,13 @@ class Alpha(Augmenter):
--------
>>> aug = iaa.Alpha(0.5, iaa.Grayscale(1.0))
Converts the image to grayscale and overlays it by 50 percent with the
Converts each image to grayscale and overlays it by 50 percent with the
original image, thereby removing about 50 percent of all color. This
is equivalent to iaa.Grayscale(0.5).
>>> aug = iaa.Alpha((0, 1.0), iaa.Grayscale(1.0))
>>> aug = iaa.Alpha((0.0, 1.0), iaa.Grayscale(1.0))
Converts the image to grayscale and overlays it by a random percentage
Converts each image to grayscale and overlays it by a random percentage
(sampled per image) with the original image, thereby removing a random
percentage of all colors. This is equivalent to iaa.Grayscale((0.0, 1.0)).
Expand All @@ -122,14 +123,19 @@ class Alpha(Augmenter):
from (B). This is equivalent to
`iaa.Sequential([iaa.Multiply(0.8), iaa.Alpha((0.0, 1.0), iaa.Add(10))])`.
>>> aug = iaa.Alpha(iap.Choice([0.25, 0.75]), iaa.MedianBlur((3, 7)))
Applies a random median blur to each image and overlays the result with
the original image by either 25 or 75 percent strength.
"""

def __init__(self, factor=0, first=None, second=None, per_channel=False,
name=None, deterministic=False, random_state=None):
super(Alpha, self).__init__(name=name, deterministic=deterministic, random_state=random_state)

if ia.is_single_float(factor):
assert 0 <= factor <= 1.0, "Expected factor to have range [0, 1.0], got value %.2f." % (factor,)
if ia.is_single_number(factor):
assert 0.0 <= factor <= 1.0, "Expected factor to have range [0, 1.0], got value %.2f." % (factor,)
self.factor = Deterministic(factor)
elif ia.is_iterable(factor):
assert len(factor) == 2, "Expected tuple/list with 2 entries, got %d entries." % (len(factor),)
Expand Down Expand Up @@ -306,3 +312,225 @@ def get_children_lists(self):
if self.second is not None:
result.append(self.second)
return result

class AlphaElementwise(Alpha):
"""
Augmenter to overlay two image sources with each other using pixelwise
alpha values.
This is the same as `Alpha`, except that the transparency factor is
sampled per pixel instead of once per image (or a few times per image, if
per_channel is True).
See `Alpha` for more description.
Parameters
----------
factor : float or iterable of two floats or StochasticParameter, optional(default=0)
Weighting of the results of the first branch. Values close to 0 mean
that the results from the second branch (see parameter `second`)
make up most of the final image.
* If float, then that value will be used for all images.
* If tuple (a, b), then a random value from range a <= x <= b will
be sampled per image.
* If StochasticParameter, then that parameter will be used to
sample a value per image.
first : None or Augmenter or iterable of Augmenter, optional(default=None)
Augmenter(s) that make up the first of the two
branches.
* If None, then the input images will be reused as the output
of the first branch.
* If Augmenter, then that augmenter will be used as the branch.
* If iterable of Augmenter, then that iterable will be converted
into a Sequential and used as the augmenter.
second : None or Augmenter or iterable of Augmenter, optional(default=None)
Augmenter(s) that make up the second of the two
branches.
* If None, then the input images will be reused as the output
of the second branch.
* If Augmenter, then that augmenter will be used as the branch.
* If iterable of Augmenter, then that iterable will be converted
into a Sequential and used as the augmenter.
per_channel : bool or float, optional(default=False)
Whether to use the same factor for all channels (False)
or to sample a new value for each channel (True).
If this value is a float p, then for p percent of all images
`per_channel` will be treated as True, otherwise as False.
name : string, optional(default=None)
See `Augmenter.__init__()`
deterministic : bool, optional(default=False)
See `Augmenter.__init__()`
random_state : int or np.random.RandomState or None, optional(default=None)
See `Augmenter.__init__()`
Examples
--------
>>> aug = iaa.AlphaElementwise(0.5, iaa.Grayscale(1.0))
Converts each image to grayscale and overlays it by 50 percent with the
original image, thereby removing about 50 percent of all color. This
is equivalent to iaa.Grayscale(0.5). This is also equivalent to
iaa.Alpha(0.5, iaa.Grayscale(1.0)), as the transparency factor is the
same for all pixels.
>>> aug = iaa.AlphaElementwise((0, 1.0), iaa.Grayscale(1.0))
Converts each image to grayscale and overlays it by a random percentage
(sampled per pixel) with the original image, thereby removing a random
percentage of all colors per pixel.
>>> aug = iaa.AlphaElementwise((0.0, 1.0), iaa.Affine(rotate=(-20, 20)), per_channel=0.5)
Rotates each image by a random degree from the range [-20, 20]. Then
overlays that new image with the original one by a random factor from the
range [0.0, 1.0], sampled per pixel. In 50 percent of all cases, the
overlay happens channel-wise and the factor is sampled independently per
channel. As a result, e.g. the red channel may look visible rotated (factor
near 1.0), while the green and blue channels may not look rotated (factors
near 0.0). NOTE: It is not recommended to use Alpha with augmenters that
change the positions of pixels if you *also* want to augment keypoints, as
it is unclear which of the two keypoint results (first or second branch)
should be used as the final result.
>>> aug = iaa.AlphaElementwise((0.0, 1.0), first=iaa.Add(10), second=iaa.Multiply(0.8))
(A) Adds 10 to each image and (B) multiplies each image by 0.8. Then per
pixel an overlay factor is sampled from the range [0.0, 1.0]. If it is
close to 1.0, the results from (A) are mostly used, otherwise the ones
from (B). This is equivalent to
`iaa.Sequential([iaa.Multiply(0.8), iaa.AlphaElementwise((0.0, 1.0), iaa.Add(10))])`.
>>> aug = iaa.AlphaElementwise(iap.Choice([0.25, 0.75]), iaa.MedianBlur((3, 7)))
Applies a random median blur to each image and overlays the result with
the original image by either 25 or 75 percent strength (sampled per pixel).
"""

def __init__(self, factor=0, first=None, second=None, per_channel=False,
name=None, deterministic=False, random_state=None):
super(AlphaElementwise, self).__init__(
factor=factor,
first=first,
second=second,
per_channel=per_channel,
name=name,
deterministic=deterministic,
random_state=random_state
)

def _augment_images(self, images, random_state, parents, hooks):
result = images
nb_images = len(images)
seeds = random_state.randint(0, 10**6, (nb_images,))

if hooks.is_propagating(images, augmenter=self, parents=parents, default=True):
if self.first is None:
images_first = images
else:
images_first = self.first.augment_images(
images=images,
parents=parents + [self],
hooks=hooks
)

if self.second is None:
images_second = images
else:
images_second = self.second.augment_images(
images=images,
parents=parents + [self],
hooks=hooks
)
else:
images_first = images
images_second = images

for i in sm.xrange(nb_images):
image = images[i]
h, w, nb_channels = image.shape[0:3]
image_first = images_first[i]
image_second = images_second[i]
rs_image = ia.new_random_state(seeds[i])
per_channel = self.per_channel.draw_sample(random_state=rs_image)
input_dtype = image.dtype
if per_channel == 1:
samples = self.factor.draw_samples((h, w, nb_channels), random_state=rs_image)
assert 0 <= samples.item(0) <= 1.0 # validate only first value
#for c, sample in enumerate(samples):
for c in sm.xrange(nb_channels):
samples_c = samples[..., c]
image[..., c] = samples_c * image_first[..., c] + (1.0 - samples_c) * image_second[..., c]
np.clip(image, 0, 255, out=image)
result[i] = image.astype(input_dtype)
else:
samples = self.factor.draw_samples((h, w), random_state=rs_image)
samples = np.tile(samples[..., np.newaxis], (1, 1, nb_channels))
assert 0.0 <= samples.item(0) <= 1.0

image = samples * image_first + (1.0 - samples) * image_second
np.clip(image, 0, 255, out=image)
result[i] = image.astype(input_dtype)
return result

def _augment_keypoints(self, keypoints_on_images, random_state, parents, hooks):
result = keypoints_on_images
nb_images = len(keypoints_on_images)
seeds = random_state.randint(0, 10**6, (nb_images,))

if hooks.is_propagating(keypoints_on_images, augmenter=self, parents=parents, default=True):
if self.first is None:
kps_ois_first = keypoints_on_images
else:
kps_ois_first = self.first.augment_keypoints(
keypoints_on_images=keypoints_on_images,
parents=parents + [self],
hooks=hooks
)

if self.second is None:
kps_ois_second = keypoints_on_images
else:
kps_ois_second = self.second.augment_keypoints(
keypoints_on_images=keypoints_on_images,
parents=parents + [self],
hooks=hooks
)
else:
kps_ois_first = keypoints_on_images
kps_ois_second = keypoints_on_images

for i in sm.xrange(nb_images):
kps_oi_first = kps_ois_first[i]
kps_oi_second = kps_ois_second[i]
rs_image = ia.new_random_state(seeds[i])
h, w, nb_channels = kps_oi_first.shape[0:3]

# keypoint augmentation also works channel-wise, even though
# keypoints do not have channels, in order to keep the random
# values properly synchronized with the image augmentation
per_channel = self.per_channel.draw_sample(random_state=rs_image)
if per_channel == 1:
samples = self.factor.draw_samples((h, w, nb_channels,), random_state=rs_image)
else:
samples = self.factor.draw_samples((h, w), random_state=rs_image)
assert 0.0 <= samples.item(0) <= 1.0
sample = np.average(samples)

# We cant choose "just a bit" of one keypoint augmentation result
# without messing up the positions (interpolation doesn't make much
# sense here),
# so if the alpha is >= 0.5 (branch A is more visible than
# branch B), the result of branch A, otherwise branch B.
if sample >= 0.5:
result[i] = kps_oi_first
else:
result[i] = kps_oi_second

return result
24 changes: 22 additions & 2 deletions tests/check_visually.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,34 @@ def main():
first=iaa.Add(100),
second=iaa.Dropout(0.5),
per_channel=True,
name="AlphaChannelwise"
name="AlphaPerChannel"
),
iaa.Alpha(
factor=(0.0, 1.0),
first=iaa.Affine(rotate=(-45, 45)),
per_channel=True,
name="AlphaAffine"
)
),
iaa.AlphaElementwise(
factor=(0.0, 1.0),
first=iaa.Add(50),
second=iaa.ContrastNormalization(2.0),
per_channel=False,
name="AlphaElementwise"
),
iaa.AlphaElementwise(
factor=(0.0, 1.0),
first=iaa.Add(50),
second=iaa.ContrastNormalization(2.0),
per_channel=True,
name="AlphaElementwisePerChannel"
),
iaa.AlphaElementwise(
factor=(0.0, 1.0),
first=iaa.Affine(rotate=(-45, 45)),
per_channel=True,
name="AlphaElementwiseAffine"
),
]

augmenters.append(iaa.Sequential([iaa.Sometimes(0.2, aug.copy()) for aug in augmenters], name="Sequential"))
Expand Down

0 comments on commit 442edf9

Please sign in to comment.