Spaces:
Runtime error
Runtime error
# Copyright (c) OpenMMLab. All rights reserved. | |
import inspect | |
from copy import deepcopy | |
from math import ceil | |
from numbers import Number | |
from typing import List, Optional, Sequence, Tuple, Union | |
import mmcv | |
import numpy as np | |
from mmcv.transforms import BaseTransform, Compose, RandomChoice | |
from mmcv.transforms.utils import cache_randomness | |
from mmengine.utils import is_list_of, is_seq_of | |
from PIL import Image, ImageFilter | |
from mmpretrain.registry import TRANSFORMS | |
def merge_hparams(policy: dict, hparams: dict) -> dict: | |
"""Merge hyperparameters into policy config. | |
Only merge partial hyperparameters required of the policy. | |
Args: | |
policy (dict): Original policy config dict. | |
hparams (dict): Hyperparameters need to be merged. | |
Returns: | |
dict: Policy config dict after adding ``hparams``. | |
""" | |
policy = deepcopy(policy) | |
op = TRANSFORMS.get(policy['type']) | |
assert op is not None, f'Invalid policy type "{policy["type"]}".' | |
op_args = inspect.getfullargspec(op.__init__).args | |
for key, value in hparams.items(): | |
if key in op_args and key not in policy: | |
policy[key] = value | |
return policy | |
class AutoAugment(RandomChoice): | |
"""Auto augmentation. | |
This data augmentation is proposed in `AutoAugment: Learning Augmentation | |
Policies from Data <https://arxiv.org/abs/1805.09501>`_. | |
Args: | |
policies (str | list[list[dict]]): The policies of auto augmentation. | |
If string, use preset policies collection like "imagenet". If list, | |
Each item is a sub policies, composed by several augmentation | |
policy dicts. When AutoAugment is called, a random sub policies in | |
``policies`` will be selected to augment images. | |
hparams (dict): Configs of hyperparameters. Hyperparameters will be | |
used in policies that require these arguments if these arguments | |
are not set in policy dicts. Defaults to ``dict(pad_val=128)``. | |
.. admonition:: Available preset policies | |
- ``"imagenet"``: Policy for ImageNet, come from | |
`DeepVoltaire/AutoAugment`_ | |
.. _DeepVoltaire/AutoAugment: https://github.com/DeepVoltaire/AutoAugment | |
""" | |
def __init__(self, | |
policies: Union[str, List[List[dict]]], | |
hparams: dict = dict(pad_val=128)): | |
if isinstance(policies, str): | |
assert policies in AUTOAUG_POLICIES, 'Invalid policies, ' \ | |
f'please choose from {list(AUTOAUG_POLICIES.keys())}.' | |
policies = AUTOAUG_POLICIES[policies] | |
self.hparams = hparams | |
self.policies = [[merge_hparams(t, hparams) for t in sub] | |
for sub in policies] | |
transforms = [[TRANSFORMS.build(t) for t in sub] for sub in policies] | |
super().__init__(transforms=transforms) | |
def __repr__(self) -> str: | |
policies_str = '' | |
for sub in self.policies: | |
policies_str += '\n ' + ', \t'.join([t['type'] for t in sub]) | |
repr_str = self.__class__.__name__ | |
repr_str += f'(policies:{policies_str}\n)' | |
return repr_str | |
class RandAugment(BaseTransform): | |
r"""Random augmentation. | |
This data augmentation is proposed in `RandAugment: Practical automated | |
data augmentation with a reduced search space | |
<https://arxiv.org/abs/1909.13719>`_. | |
Args: | |
policies (str | list[dict]): The policies of random augmentation. | |
If string, use preset policies collection like "timm_increasing". | |
If list, each item is one specific augmentation policy dict. | |
The policy dict shall should have these keys: | |
- ``type`` (str), The type of augmentation. | |
- ``magnitude_range`` (Sequence[number], optional): For those | |
augmentation have magnitude, you need to specify the magnitude | |
level mapping range. For example, assume ``total_level`` is 10, | |
``magnitude_level=3`` specify magnitude is 3 if | |
``magnitude_range=(0, 10)`` while specify magnitude is 7 if | |
``magnitude_range=(10, 0)``. | |
- other keyword arguments of the augmentation. | |
num_policies (int): Number of policies to select from policies each | |
time. | |
magnitude_level (int | float): Magnitude level for all the augmentation | |
selected. | |
magnitude_std (Number | str): Deviation of magnitude noise applied. | |
- If positive number, the magnitude obeys normal distribution | |
:math:`\mathcal{N}(magnitude_level, magnitude_std)`. | |
- If 0 or negative number, magnitude remains unchanged. | |
- If str "inf", the magnitude obeys uniform distribution | |
:math:`Uniform(min, magnitude)`. | |
total_level (int | float): Total level for the magnitude. Defaults to | |
10. | |
hparams (dict): Configs of hyperparameters. Hyperparameters will be | |
used in policies that require these arguments if these arguments | |
are not set in policy dicts. Defaults to ``dict(pad_val=128)``. | |
.. admonition:: Available preset policies | |
- ``"timm_increasing"``: The ``_RAND_INCREASING_TRANSFORMS`` policy | |
from `timm`_ | |
.. _timm: https://github.com/rwightman/pytorch-image-models | |
Examples: | |
To use "timm-increasing" policies collection, select two policies every | |
time, and magnitude_level of every policy is 6 (total is 10 by default) | |
>>> import numpy as np | |
>>> from mmpretrain.datasets import RandAugment | |
>>> transform = RandAugment( | |
... policies='timm_increasing', | |
... num_policies=2, | |
... magnitude_level=6, | |
... ) | |
>>> data = {'img': np.random.randint(0, 256, (224, 224, 3))} | |
>>> results = transform(data) | |
>>> print(results['img'].shape) | |
(224, 224, 3) | |
If you want the ``magnitude_level`` randomly changes every time, you | |
can use ``magnitude_std`` to specify the random distribution. For | |
example, a normal distribution :math:`\mathcal{N}(6, 0.5)`. | |
>>> transform = RandAugment( | |
... policies='timm_increasing', | |
... num_policies=2, | |
... magnitude_level=6, | |
... magnitude_std=0.5, | |
... ) | |
You can also use your own policies: | |
>>> policies = [ | |
... dict(type='AutoContrast'), | |
... dict(type='Rotate', magnitude_range=(0, 30)), | |
... dict(type='ColorTransform', magnitude_range=(0, 0.9)), | |
... ] | |
>>> transform = RandAugment( | |
... policies=policies, | |
... num_policies=2, | |
... magnitude_level=6 | |
... ) | |
Note: | |
``magnitude_std`` will introduce some randomness to policy, modified by | |
https://github.com/rwightman/pytorch-image-models. | |
When magnitude_std=0, we calculate the magnitude as follows: | |
.. math:: | |
\text{magnitude} = \frac{\text{magnitude_level}} | |
{\text{totallevel}} \times (\text{val2} - \text{val1}) | |
+ \text{val1} | |
""" | |
def __init__(self, | |
policies: Union[str, List[dict]], | |
num_policies: int, | |
magnitude_level: int, | |
magnitude_std: Union[Number, str] = 0., | |
total_level: int = 10, | |
hparams: dict = dict(pad_val=128)): | |
if isinstance(policies, str): | |
assert policies in RANDAUG_POLICIES, 'Invalid policies, ' \ | |
f'please choose from {list(RANDAUG_POLICIES.keys())}.' | |
policies = RANDAUG_POLICIES[policies] | |
assert is_list_of(policies, dict), 'policies must be a list of dict.' | |
assert isinstance(magnitude_std, (Number, str)), \ | |
'`magnitude_std` must be of number or str type, ' \ | |
f'got {type(magnitude_std)} instead.' | |
if isinstance(magnitude_std, str): | |
assert magnitude_std == 'inf', \ | |
'`magnitude_std` must be of number or "inf", ' \ | |
f'got "{magnitude_std}" instead.' | |
assert num_policies > 0, 'num_policies must be greater than 0.' | |
assert magnitude_level >= 0, 'magnitude_level must be no less than 0.' | |
assert total_level > 0, 'total_level must be greater than 0.' | |
self.num_policies = num_policies | |
self.magnitude_level = magnitude_level | |
self.magnitude_std = magnitude_std | |
self.total_level = total_level | |
self.hparams = hparams | |
self.policies = [] | |
self.transforms = [] | |
randaug_cfg = dict( | |
magnitude_level=magnitude_level, | |
total_level=total_level, | |
magnitude_std=magnitude_std) | |
for policy in policies: | |
self._check_policy(policy) | |
policy = merge_hparams(policy, hparams) | |
policy.pop('magnitude_key', None) # For backward compatibility | |
if 'magnitude_range' in policy: | |
policy.update(randaug_cfg) | |
self.policies.append(policy) | |
self.transforms.append(TRANSFORMS.build(policy)) | |
def __iter__(self): | |
"""Iterate all transforms.""" | |
return iter(self.transforms) | |
def _check_policy(self, policy): | |
"""Check whether the sub-policy dict is available.""" | |
assert isinstance(policy, dict) and 'type' in policy, \ | |
'Each policy must be a dict with key "type".' | |
type_name = policy['type'] | |
if 'magnitude_range' in policy: | |
magnitude_range = policy['magnitude_range'] | |
assert is_seq_of(magnitude_range, Number), \ | |
f'`magnitude_range` of RandAugment policy {type_name} ' \ | |
'should be a sequence with two numbers.' | |
def random_policy_indices(self) -> np.ndarray: | |
"""Return the random chosen transform indices.""" | |
indices = np.arange(len(self.policies)) | |
return np.random.choice(indices, size=self.num_policies).tolist() | |
def transform(self, results: dict) -> Optional[dict]: | |
"""Randomly choose a sub-policy to apply.""" | |
chosen_policies = [ | |
self.transforms[i] for i in self.random_policy_indices() | |
] | |
sub_pipeline = Compose(chosen_policies) | |
return sub_pipeline(results) | |
def __repr__(self) -> str: | |
policies_str = '' | |
for policy in self.policies: | |
policies_str += '\n ' + f'{policy["type"]}' | |
if 'magnitude_range' in policy: | |
val1, val2 = policy['magnitude_range'] | |
policies_str += f' ({val1}, {val2})' | |
repr_str = self.__class__.__name__ | |
repr_str += f'(num_policies={self.num_policies}, ' | |
repr_str += f'magnitude_level={self.magnitude_level}, ' | |
repr_str += f'total_level={self.total_level}, ' | |
repr_str += f'policies:{policies_str}\n)' | |
return repr_str | |
class BaseAugTransform(BaseTransform): | |
r"""The base class of augmentation transform for RandAugment. | |
This class provides several common attributions and methods to support the | |
magnitude level mapping and magnitude level randomness in | |
:class:`RandAugment`. | |
Args: | |
magnitude_level (int | float): Magnitude level. | |
magnitude_range (Sequence[number], optional): For augmentation have | |
magnitude argument, maybe "magnitude", "angle" or other, you can | |
specify the magnitude level mapping range to generate the magnitude | |
argument. For example, assume ``total_level`` is 10, | |
``magnitude_level=3`` specify magnitude is 3 if | |
``magnitude_range=(0, 10)`` while specify magnitude is 7 if | |
``magnitude_range=(10, 0)``. Defaults to None. | |
magnitude_std (Number | str): Deviation of magnitude noise applied. | |
- If positive number, the magnitude obeys normal distribution | |
:math:`\mathcal{N}(magnitude, magnitude_std)`. | |
- If 0 or negative number, magnitude remains unchanged. | |
- If str "inf", the magnitude obeys uniform distribution | |
:math:`Uniform(min, magnitude)`. | |
Defaults to 0. | |
total_level (int | float): Total level for the magnitude. Defaults to | |
10. | |
prob (float): The probability for performing transformation therefore | |
should be in range [0, 1]. Defaults to 0.5. | |
random_negative_prob (float): The probability that turns the magnitude | |
negative, which should be in range [0,1]. Defaults to 0. | |
""" | |
def __init__(self, | |
magnitude_level: int = 10, | |
magnitude_range: Tuple[float, float] = None, | |
magnitude_std: Union[str, float] = 0., | |
total_level: int = 10, | |
prob: float = 0.5, | |
random_negative_prob: float = 0.5): | |
self.magnitude_level = magnitude_level | |
self.magnitude_range = magnitude_range | |
self.magnitude_std = magnitude_std | |
self.total_level = total_level | |
self.prob = prob | |
self.random_negative_prob = random_negative_prob | |
def random_disable(self): | |
"""Randomly disable the transform.""" | |
return np.random.rand() > self.prob | |
def random_magnitude(self): | |
"""Randomly generate magnitude.""" | |
magnitude = self.magnitude_level | |
# if magnitude_std is positive number or 'inf', move | |
# magnitude_value randomly. | |
if self.magnitude_std == 'inf': | |
magnitude = np.random.uniform(0, magnitude) | |
elif self.magnitude_std > 0: | |
magnitude = np.random.normal(magnitude, self.magnitude_std) | |
magnitude = np.clip(magnitude, 0, self.total_level) | |
val1, val2 = self.magnitude_range | |
magnitude = (magnitude / self.total_level) * (val2 - val1) + val1 | |
return magnitude | |
def random_negative(self, value): | |
"""Randomly negative the value.""" | |
if np.random.rand() < self.random_negative_prob: | |
return -value | |
else: | |
return value | |
def extra_repr(self): | |
"""Extra repr string when auto-generating magnitude is enabled.""" | |
if self.magnitude_range is not None: | |
repr_str = f', magnitude_level={self.magnitude_level}, ' | |
repr_str += f'magnitude_range={self.magnitude_range}, ' | |
repr_str += f'magnitude_std={self.magnitude_std}, ' | |
repr_str += f'total_level={self.total_level}, ' | |
return repr_str | |
else: | |
return '' | |
class Shear(BaseAugTransform): | |
"""Shear images. | |
Args: | |
magnitude (int | float | None): The magnitude used for shear. If None, | |
generate from ``magnitude_range``, see :class:`BaseAugTransform`. | |
Defaults to None. | |
pad_val (int, Sequence[int]): Pixel pad_val value for constant fill. | |
If a sequence of length 3, it is used to pad_val R, G, B channels | |
respectively. Defaults to 128. | |
prob (float): The probability for performing shear therefore should be | |
in range [0, 1]. Defaults to 0.5. | |
direction (str): The shearing direction. Options are 'horizontal' and | |
'vertical'. Defaults to 'horizontal'. | |
random_negative_prob (float): The probability that turns the magnitude | |
negative, which should be in range [0,1]. Defaults to 0.5. | |
interpolation (str): Interpolation method. Options are 'nearest', | |
'bilinear', 'bicubic', 'area', 'lanczos'. Defaults to 'bicubic'. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, | |
magnitude: Union[int, float, None] = None, | |
pad_val: Union[int, Sequence[int]] = 128, | |
prob: float = 0.5, | |
direction: str = 'horizontal', | |
random_negative_prob: float = 0.5, | |
interpolation: str = 'bicubic', | |
**kwargs): | |
super().__init__( | |
prob=prob, random_negative_prob=random_negative_prob, **kwargs) | |
assert (magnitude is None) ^ (self.magnitude_range is None), \ | |
'Please specify only one of `magnitude` and `magnitude_range`.' | |
self.magnitude = magnitude | |
if isinstance(pad_val, Sequence): | |
self.pad_val = tuple(pad_val) | |
else: | |
self.pad_val = pad_val | |
assert direction in ('horizontal', 'vertical'), 'direction must be ' \ | |
f'either "horizontal" or "vertical", got "{direction}" instead.' | |
self.direction = direction | |
self.interpolation = interpolation | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
if self.magnitude is not None: | |
magnitude = self.random_negative(self.magnitude) | |
else: | |
magnitude = self.random_negative(self.random_magnitude()) | |
img = results['img'] | |
img_sheared = mmcv.imshear( | |
img, | |
magnitude, | |
direction=self.direction, | |
border_value=self.pad_val, | |
interpolation=self.interpolation) | |
results['img'] = img_sheared.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(magnitude={self.magnitude}, ' | |
repr_str += f'pad_val={self.pad_val}, ' | |
repr_str += f'prob={self.prob}, ' | |
repr_str += f'direction={self.direction}, ' | |
repr_str += f'random_negative_prob={self.random_negative_prob}, ' | |
repr_str += f'interpolation={self.interpolation}{self.extra_repr()})' | |
return repr_str | |
class Translate(BaseAugTransform): | |
"""Translate images. | |
Args: | |
magnitude (int | float | None): The magnitude used for translate. Note | |
that the offset is calculated by magnitude * size in the | |
corresponding direction. With a magnitude of 1, the whole image | |
will be moved out of the range. If None, generate from | |
``magnitude_range``, see :class:`BaseAugTransform`. | |
pad_val (int, Sequence[int]): Pixel pad_val value for constant fill. | |
If a sequence of length 3, it is used to pad_val R, G, B channels | |
respectively. Defaults to 128. | |
prob (float): The probability for performing translate therefore should | |
be in range [0, 1]. Defaults to 0.5. | |
direction (str): The translating direction. Options are 'horizontal' | |
and 'vertical'. Defaults to 'horizontal'. | |
random_negative_prob (float): The probability that turns the magnitude | |
negative, which should be in range [0,1]. Defaults to 0.5. | |
interpolation (str): Interpolation method. Options are 'nearest', | |
'bilinear', 'bicubic', 'area', 'lanczos'. Defaults to 'nearest'. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, | |
magnitude: Union[int, float, None] = None, | |
pad_val: Union[int, Sequence[int]] = 128, | |
prob: float = 0.5, | |
direction: str = 'horizontal', | |
random_negative_prob: float = 0.5, | |
interpolation: str = 'nearest', | |
**kwargs): | |
super().__init__( | |
prob=prob, random_negative_prob=random_negative_prob, **kwargs) | |
assert (magnitude is None) ^ (self.magnitude_range is None), \ | |
'Please specify only one of `magnitude` and `magnitude_range`.' | |
self.magnitude = magnitude | |
if isinstance(pad_val, Sequence): | |
self.pad_val = tuple(pad_val) | |
else: | |
self.pad_val = pad_val | |
assert direction in ('horizontal', 'vertical'), 'direction must be ' \ | |
f'either "horizontal" or "vertical", got "{direction}" instead.' | |
self.direction = direction | |
self.interpolation = interpolation | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
if self.magnitude is not None: | |
magnitude = self.random_negative(self.magnitude) | |
else: | |
magnitude = self.random_negative(self.random_magnitude()) | |
img = results['img'] | |
height, width = img.shape[:2] | |
if self.direction == 'horizontal': | |
offset = magnitude * width | |
else: | |
offset = magnitude * height | |
img_translated = mmcv.imtranslate( | |
img, | |
offset, | |
direction=self.direction, | |
border_value=self.pad_val, | |
interpolation=self.interpolation) | |
results['img'] = img_translated.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(magnitude={self.magnitude}, ' | |
repr_str += f'pad_val={self.pad_val}, ' | |
repr_str += f'prob={self.prob}, ' | |
repr_str += f'direction={self.direction}, ' | |
repr_str += f'random_negative_prob={self.random_negative_prob}, ' | |
repr_str += f'interpolation={self.interpolation}{self.extra_repr()})' | |
return repr_str | |
class Rotate(BaseAugTransform): | |
"""Rotate images. | |
Args: | |
angle (float, optional): The angle used for rotate. Positive values | |
stand for clockwise rotation. If None, generate from | |
``magnitude_range``, see :class:`BaseAugTransform`. | |
Defaults to None. | |
center (tuple[float], optional): Center point (w, h) of the rotation in | |
the source image. If None, the center of the image will be used. | |
Defaults to None. | |
scale (float): Isotropic scale factor. Defaults to 1.0. | |
pad_val (int, Sequence[int]): Pixel pad_val value for constant fill. | |
If a sequence of length 3, it is used to pad_val R, G, B channels | |
respectively. Defaults to 128. | |
prob (float): The probability for performing rotate therefore should be | |
in range [0, 1]. Defaults to 0.5. | |
random_negative_prob (float): The probability that turns the angle | |
negative, which should be in range [0,1]. Defaults to 0.5. | |
interpolation (str): Interpolation method. Options are 'nearest', | |
'bilinear', 'bicubic', 'area', 'lanczos'. Defaults to 'nearest'. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, | |
angle: Optional[float] = None, | |
center: Optional[Tuple[float]] = None, | |
scale: float = 1.0, | |
pad_val: Union[int, Sequence[int]] = 128, | |
prob: float = 0.5, | |
random_negative_prob: float = 0.5, | |
interpolation: str = 'nearest', | |
**kwargs): | |
super().__init__( | |
prob=prob, random_negative_prob=random_negative_prob, **kwargs) | |
assert (angle is None) ^ (self.magnitude_range is None), \ | |
'Please specify only one of `angle` and `magnitude_range`.' | |
self.angle = angle | |
self.center = center | |
self.scale = scale | |
if isinstance(pad_val, Sequence): | |
self.pad_val = tuple(pad_val) | |
else: | |
self.pad_val = pad_val | |
self.interpolation = interpolation | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
if self.angle is not None: | |
angle = self.random_negative(self.angle) | |
else: | |
angle = self.random_negative(self.random_magnitude()) | |
img = results['img'] | |
img_rotated = mmcv.imrotate( | |
img, | |
angle, | |
center=self.center, | |
scale=self.scale, | |
border_value=self.pad_val, | |
interpolation=self.interpolation) | |
results['img'] = img_rotated.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(angle={self.angle}, ' | |
repr_str += f'center={self.center}, ' | |
repr_str += f'scale={self.scale}, ' | |
repr_str += f'pad_val={self.pad_val}, ' | |
repr_str += f'prob={self.prob}, ' | |
repr_str += f'random_negative_prob={self.random_negative_prob}, ' | |
repr_str += f'interpolation={self.interpolation}{self.extra_repr()})' | |
return repr_str | |
class AutoContrast(BaseAugTransform): | |
"""Auto adjust image contrast. | |
Args: | |
prob (float): The probability for performing auto contrast | |
therefore should be in range [0, 1]. Defaults to 0.5. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, prob: float = 0.5, **kwargs): | |
super().__init__(prob=prob, **kwargs) | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
img = results['img'] | |
img_contrasted = mmcv.auto_contrast(img) | |
results['img'] = img_contrasted.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(prob={self.prob})' | |
return repr_str | |
class Invert(BaseAugTransform): | |
"""Invert images. | |
Args: | |
prob (float): The probability for performing invert therefore should | |
be in range [0, 1]. Defaults to 0.5. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, prob: float = 0.5, **kwargs): | |
super().__init__(prob=prob, **kwargs) | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
img = results['img'] | |
img_inverted = mmcv.iminvert(img) | |
results['img'] = img_inverted.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(prob={self.prob})' | |
return repr_str | |
class Equalize(BaseAugTransform): | |
"""Equalize the image histogram. | |
Args: | |
prob (float): The probability for performing equalize therefore should | |
be in range [0, 1]. Defaults to 0.5. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, prob: float = 0.5, **kwargs): | |
super().__init__(prob=prob, **kwargs) | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
img = results['img'] | |
img_equalized = mmcv.imequalize(img) | |
results['img'] = img_equalized.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(prob={self.prob})' | |
return repr_str | |
class Solarize(BaseAugTransform): | |
"""Solarize images (invert all pixel values above a threshold). | |
Args: | |
thr (int | float | None): The threshold above which the pixels value | |
will be inverted. If None, generate from ``magnitude_range``, | |
see :class:`BaseAugTransform`. Defaults to None. | |
prob (float): The probability for solarizing therefore should be in | |
range [0, 1]. Defaults to 0.5. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, | |
thr: Union[int, float, None] = None, | |
prob: float = 0.5, | |
**kwargs): | |
super().__init__(prob=prob, random_negative_prob=0., **kwargs) | |
assert (thr is None) ^ (self.magnitude_range is None), \ | |
'Please specify only one of `thr` and `magnitude_range`.' | |
self.thr = thr | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
if self.thr is not None: | |
thr = self.thr | |
else: | |
thr = self.random_magnitude() | |
img = results['img'] | |
img_solarized = mmcv.solarize(img, thr=thr) | |
results['img'] = img_solarized.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(thr={self.thr}, ' | |
repr_str += f'prob={self.prob}{self.extra_repr()}))' | |
return repr_str | |
class SolarizeAdd(BaseAugTransform): | |
"""SolarizeAdd images (add a certain value to pixels below a threshold). | |
Args: | |
magnitude (int | float | None): The value to be added to pixels below | |
the thr. If None, generate from ``magnitude_range``, see | |
:class:`BaseAugTransform`. Defaults to None. | |
thr (int | float): The threshold below which the pixels value will be | |
adjusted. | |
prob (float): The probability for solarizing therefore should be in | |
range [0, 1]. Defaults to 0.5. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, | |
magnitude: Union[int, float, None] = None, | |
thr: Union[int, float] = 128, | |
prob: float = 0.5, | |
**kwargs): | |
super().__init__(prob=prob, random_negative_prob=0., **kwargs) | |
assert (magnitude is None) ^ (self.magnitude_range is None), \ | |
'Please specify only one of `magnitude` and `magnitude_range`.' | |
self.magnitude = magnitude | |
assert isinstance(thr, (int, float)), 'The thr type must '\ | |
f'be int or float, but got {type(thr)} instead.' | |
self.thr = thr | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
if self.magnitude is not None: | |
magnitude = self.magnitude | |
else: | |
magnitude = self.random_magnitude() | |
img = results['img'] | |
img_solarized = np.where(img < self.thr, | |
np.minimum(img + magnitude, 255), img) | |
results['img'] = img_solarized.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(magnitude={self.magnitude}, ' | |
repr_str += f'thr={self.thr}, ' | |
repr_str += f'prob={self.prob}{self.extra_repr()})' | |
return repr_str | |
class Posterize(BaseAugTransform): | |
"""Posterize images (reduce the number of bits for each color channel). | |
Args: | |
bits (int, optional): Number of bits for each pixel in the output img, | |
which should be less or equal to 8. If None, generate from | |
``magnitude_range``, see :class:`BaseAugTransform`. | |
Defaults to None. | |
prob (float): The probability for posterizing therefore should be in | |
range [0, 1]. Defaults to 0.5. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, | |
bits: Optional[int] = None, | |
prob: float = 0.5, | |
**kwargs): | |
super().__init__(prob=prob, random_negative_prob=0., **kwargs) | |
assert (bits is None) ^ (self.magnitude_range is None), \ | |
'Please specify only one of `bits` and `magnitude_range`.' | |
if bits is not None: | |
assert bits <= 8, \ | |
f'The bits must be less than 8, got {bits} instead.' | |
self.bits = bits | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
if self.bits is not None: | |
bits = self.bits | |
else: | |
bits = self.random_magnitude() | |
# To align timm version, we need to round up to integer here. | |
bits = ceil(bits) | |
img = results['img'] | |
img_posterized = mmcv.posterize(img, bits=bits) | |
results['img'] = img_posterized.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(bits={self.bits}, ' | |
repr_str += f'prob={self.prob}{self.extra_repr()})' | |
return repr_str | |
class Contrast(BaseAugTransform): | |
"""Adjust images contrast. | |
Args: | |
magnitude (int | float | None): The magnitude used for adjusting | |
contrast. A positive magnitude would enhance the contrast and | |
a negative magnitude would make the image grayer. A magnitude=0 | |
gives the origin img. If None, generate from ``magnitude_range``, | |
see :class:`BaseAugTransform`. Defaults to None. | |
prob (float): The probability for performing contrast adjusting | |
therefore should be in range [0, 1]. Defaults to 0.5. | |
random_negative_prob (float): The probability that turns the magnitude | |
negative, which should be in range [0,1]. Defaults to 0.5. | |
""" | |
def __init__(self, | |
magnitude: Union[int, float, None] = None, | |
prob: float = 0.5, | |
random_negative_prob: float = 0.5, | |
**kwargs): | |
super().__init__( | |
prob=prob, random_negative_prob=random_negative_prob, **kwargs) | |
assert (magnitude is None) ^ (self.magnitude_range is None), \ | |
'Please specify only one of `magnitude` and `magnitude_range`.' | |
self.magnitude = magnitude | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
if self.magnitude is not None: | |
magnitude = self.random_negative(self.magnitude) | |
else: | |
magnitude = self.random_negative(self.random_magnitude()) | |
img = results['img'] | |
img_contrasted = mmcv.adjust_contrast(img, factor=1 + magnitude) | |
results['img'] = img_contrasted.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(magnitude={self.magnitude}, ' | |
repr_str += f'prob={self.prob}, ' | |
repr_str += f'random_negative_prob={self.random_negative_prob}' | |
repr_str += f'{self.extra_repr()})' | |
return repr_str | |
class ColorTransform(BaseAugTransform): | |
"""Adjust images color balance. | |
Args: | |
magnitude (int | float | None): The magnitude used for color transform. | |
A positive magnitude would enhance the color and a negative | |
magnitude would make the image grayer. A magnitude=0 gives the | |
origin img. If None, generate from ``magnitude_range``, see | |
:class:`BaseAugTransform`. Defaults to None. | |
prob (float): The probability for performing ColorTransform therefore | |
should be in range [0, 1]. Defaults to 0.5. | |
random_negative_prob (float): The probability that turns the magnitude | |
negative, which should be in range [0,1]. Defaults to 0.5. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, | |
magnitude: Union[int, float, None] = None, | |
prob: float = 0.5, | |
random_negative_prob: float = 0.5, | |
**kwargs): | |
super().__init__( | |
prob=prob, random_negative_prob=random_negative_prob, **kwargs) | |
assert (magnitude is None) ^ (self.magnitude_range is None), \ | |
'Please specify only one of `magnitude` and `magnitude_range`.' | |
self.magnitude = magnitude | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
if self.magnitude is not None: | |
magnitude = self.random_negative(self.magnitude) | |
else: | |
magnitude = self.random_negative(self.random_magnitude()) | |
img = results['img'] | |
img_color_adjusted = mmcv.adjust_color(img, alpha=1 + magnitude) | |
results['img'] = img_color_adjusted.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(magnitude={self.magnitude}, ' | |
repr_str += f'prob={self.prob}, ' | |
repr_str += f'random_negative_prob={self.random_negative_prob}' | |
repr_str += f'{self.extra_repr()})' | |
return repr_str | |
class Brightness(BaseAugTransform): | |
"""Adjust images brightness. | |
Args: | |
magnitude (int | float | None): The magnitude used for adjusting | |
brightness. A positive magnitude would enhance the brightness and a | |
negative magnitude would make the image darker. A magnitude=0 gives | |
the origin img. If None, generate from ``magnitude_range``, see | |
:class:`BaseAugTransform`. Defaults to None. | |
prob (float): The probability for performing brightness adjusting | |
therefore should be in range [0, 1]. Defaults to 0.5. | |
random_negative_prob (float): The probability that turns the magnitude | |
negative, which should be in range [0,1]. Defaults to 0.5. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, | |
magnitude: Union[int, float, None] = None, | |
prob: float = 0.5, | |
random_negative_prob: float = 0.5, | |
**kwargs): | |
super().__init__( | |
prob=prob, random_negative_prob=random_negative_prob, **kwargs) | |
assert (magnitude is None) ^ (self.magnitude_range is None), \ | |
'Please specify only one of `magnitude` and `magnitude_range`.' | |
self.magnitude = magnitude | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
if self.magnitude is not None: | |
magnitude = self.random_negative(self.magnitude) | |
else: | |
magnitude = self.random_negative(self.random_magnitude()) | |
img = results['img'] | |
img_brightened = mmcv.adjust_brightness(img, factor=1 + magnitude) | |
results['img'] = img_brightened.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(magnitude={self.magnitude}, ' | |
repr_str += f'prob={self.prob}, ' | |
repr_str += f'random_negative_prob={self.random_negative_prob}' | |
repr_str += f'{self.extra_repr()})' | |
return repr_str | |
class Sharpness(BaseAugTransform): | |
"""Adjust images sharpness. | |
Args: | |
magnitude (int | float | None): The magnitude used for adjusting | |
sharpness. A positive magnitude would enhance the sharpness and a | |
negative magnitude would make the image bulr. A magnitude=0 gives | |
the origin img. If None, generate from ``magnitude_range``, see | |
:class:`BaseAugTransform`. Defaults to None. | |
prob (float): The probability for performing sharpness adjusting | |
therefore should be in range [0, 1]. Defaults to 0.5. | |
random_negative_prob (float): The probability that turns the magnitude | |
negative, which should be in range [0,1]. Defaults to 0.5. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, | |
magnitude: Union[int, float, None] = None, | |
prob: float = 0.5, | |
random_negative_prob: float = 0.5, | |
**kwargs): | |
super().__init__( | |
prob=prob, random_negative_prob=random_negative_prob, **kwargs) | |
assert (magnitude is None) ^ (self.magnitude_range is None), \ | |
'Please specify only one of `magnitude` and `magnitude_range`.' | |
self.magnitude = magnitude | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
if self.magnitude is not None: | |
magnitude = self.random_negative(self.magnitude) | |
else: | |
magnitude = self.random_negative(self.random_magnitude()) | |
img = results['img'] | |
img_sharpened = mmcv.adjust_sharpness(img, factor=1 + magnitude) | |
results['img'] = img_sharpened.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(magnitude={self.magnitude}, ' | |
repr_str += f'prob={self.prob}, ' | |
repr_str += f'random_negative_prob={self.random_negative_prob}' | |
repr_str += f'{self.extra_repr()})' | |
return repr_str | |
class Cutout(BaseAugTransform): | |
"""Cutout images. | |
Args: | |
shape (int | tuple(int) | None): Expected cutout shape (h, w). | |
If given as a single value, the value will be used for both h and | |
w. If None, generate from ``magnitude_range``, see | |
:class:`BaseAugTransform`. Defaults to None. | |
pad_val (int, Sequence[int]): Pixel pad_val value for constant fill. | |
If it is a sequence, it must have the same length with the image | |
channels. Defaults to 128. | |
prob (float): The probability for performing cutout therefore should | |
be in range [0, 1]. Defaults to 0.5. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, | |
shape: Union[int, Tuple[int], None] = None, | |
pad_val: Union[int, Sequence[int]] = 128, | |
prob: float = 0.5, | |
**kwargs): | |
super().__init__(prob=prob, random_negative_prob=0., **kwargs) | |
assert (shape is None) ^ (self.magnitude_range is None), \ | |
'Please specify only one of `shape` and `magnitude_range`.' | |
self.shape = shape | |
if isinstance(pad_val, Sequence): | |
self.pad_val = tuple(pad_val) | |
else: | |
self.pad_val = pad_val | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
if self.shape is not None: | |
shape = self.shape | |
else: | |
shape = int(self.random_magnitude()) | |
img = results['img'] | |
img_cutout = mmcv.cutout(img, shape, pad_val=self.pad_val) | |
results['img'] = img_cutout.astype(img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(shape={self.shape}, ' | |
repr_str += f'pad_val={self.pad_val}, ' | |
repr_str += f'prob={self.prob}{self.extra_repr()})' | |
return repr_str | |
class GaussianBlur(BaseAugTransform): | |
"""Gaussian blur images. | |
Args: | |
radius (int, float, optional): The blur radius. If None, generate from | |
``magnitude_range``, see :class:`BaseAugTransform`. | |
Defaults to None. | |
prob (float): The probability for posterizing therefore should be in | |
range [0, 1]. Defaults to 0.5. | |
**kwargs: Other keyword arguments of :class:`BaseAugTransform`. | |
""" | |
def __init__(self, | |
radius: Union[int, float, None] = None, | |
prob: float = 0.5, | |
**kwargs): | |
super().__init__(prob=prob, random_negative_prob=0., **kwargs) | |
assert (radius is None) ^ (self.magnitude_range is None), \ | |
'Please specify only one of `radius` and `magnitude_range`.' | |
self.radius = radius | |
def transform(self, results): | |
"""Apply transform to results.""" | |
if self.random_disable(): | |
return results | |
if self.radius is not None: | |
radius = self.radius | |
else: | |
radius = self.random_magnitude() | |
img = results['img'] | |
pil_img = Image.fromarray(img) | |
pil_img.filter(ImageFilter.GaussianBlur(radius=radius)) | |
results['img'] = np.array(pil_img, dtype=img.dtype) | |
return results | |
def __repr__(self): | |
repr_str = self.__class__.__name__ | |
repr_str += f'(radius={self.radius}, ' | |
repr_str += f'prob={self.prob}{self.extra_repr()})' | |
return repr_str | |
# yapf: disable | |
# flake8: noqa | |
AUTOAUG_POLICIES = { | |
# Policy for ImageNet, refers to | |
# https://github.com/DeepVoltaire/AutoAugment/blame/master/autoaugment.py | |
'imagenet': [ | |
[dict(type='Posterize', bits=4, prob=0.4), dict(type='Rotate', angle=30., prob=0.6)], | |
[dict(type='Solarize', thr=256 / 9 * 4, prob=0.6), dict(type='AutoContrast', prob=0.6)], | |
[dict(type='Equalize', prob=0.8), dict(type='Equalize', prob=0.6)], | |
[dict(type='Posterize', bits=5, prob=0.6), dict(type='Posterize', bits=5, prob=0.6)], | |
[dict(type='Equalize', prob=0.4), dict(type='Solarize', thr=256 / 9 * 5, prob=0.2)], | |
[dict(type='Equalize', prob=0.4), dict(type='Rotate', angle=30 / 9 * 8, prob=0.8)], | |
[dict(type='Solarize', thr=256 / 9 * 6, prob=0.6), dict(type='Equalize', prob=0.6)], | |
[dict(type='Posterize', bits=6, prob=0.8), dict(type='Equalize', prob=1.)], | |
[dict(type='Rotate', angle=10., prob=0.2), dict(type='Solarize', thr=256 / 9, prob=0.6)], | |
[dict(type='Equalize', prob=0.6), dict(type='Posterize', bits=5, prob=0.4)], | |
[dict(type='Rotate', angle=30 / 9 * 8, prob=0.8), dict(type='ColorTransform', magnitude=0., prob=0.4)], | |
[dict(type='Rotate', angle=30., prob=0.4), dict(type='Equalize', prob=0.6)], | |
[dict(type='Equalize', prob=0.0), dict(type='Equalize', prob=0.8)], | |
[dict(type='Invert', prob=0.6), dict(type='Equalize', prob=1.)], | |
[dict(type='ColorTransform', magnitude=0.4, prob=0.6), dict(type='Contrast', magnitude=0.8, prob=1.)], | |
[dict(type='Rotate', angle=30 / 9 * 8, prob=0.8), dict(type='ColorTransform', magnitude=0.2, prob=1.)], | |
[dict(type='ColorTransform', magnitude=0.8, prob=0.8), dict(type='Solarize', thr=256 / 9 * 2, prob=0.8)], | |
[dict(type='Sharpness', magnitude=0.7, prob=0.4), dict(type='Invert', prob=0.6)], | |
[dict(type='Shear', magnitude=0.3 / 9 * 5, prob=0.6, direction='horizontal'), dict(type='Equalize', prob=1.)], | |
[dict(type='ColorTransform', magnitude=0., prob=0.4), dict(type='Equalize', prob=0.6)], | |
[dict(type='Equalize', prob=0.4), dict(type='Solarize', thr=256 / 9 * 5, prob=0.2)], | |
[dict(type='Solarize', thr=256 / 9 * 4, prob=0.6), dict(type='AutoContrast', prob=0.6)], | |
[dict(type='Invert', prob=0.6), dict(type='Equalize', prob=1.)], | |
[dict(type='ColorTransform', magnitude=0.4, prob=0.6), dict(type='Contrast', magnitude=0.8, prob=1.)], | |
[dict(type='Equalize', prob=0.8), dict(type='Equalize', prob=0.6)], | |
], | |
} | |
RANDAUG_POLICIES = { | |
# Refers to `_RAND_INCREASING_TRANSFORMS` in pytorch-image-models | |
'timm_increasing': [ | |
dict(type='AutoContrast'), | |
dict(type='Equalize'), | |
dict(type='Invert'), | |
dict(type='Rotate', magnitude_range=(0, 30)), | |
dict(type='Posterize', magnitude_range=(4, 0)), | |
dict(type='Solarize', magnitude_range=(256, 0)), | |
dict(type='SolarizeAdd', magnitude_range=(0, 110)), | |
dict(type='ColorTransform', magnitude_range=(0, 0.9)), | |
dict(type='Contrast', magnitude_range=(0, 0.9)), | |
dict(type='Brightness', magnitude_range=(0, 0.9)), | |
dict(type='Sharpness', magnitude_range=(0, 0.9)), | |
dict(type='Shear', magnitude_range=(0, 0.3), direction='horizontal'), | |
dict(type='Shear', magnitude_range=(0, 0.3), direction='vertical'), | |
dict(type='Translate', magnitude_range=(0, 0.45), direction='horizontal'), | |
dict(type='Translate', magnitude_range=(0, 0.45), direction='vertical'), | |
], | |
'simple_increasing': [ | |
dict(type='AutoContrast'), | |
dict(type='Equalize'), | |
dict(type='Rotate', magnitude_range=(0, 30)), | |
dict(type='Shear', magnitude_range=(0, 0.3), direction='horizontal'), | |
dict(type='Shear', magnitude_range=(0, 0.3), direction='vertical'), | |
], | |
} | |