CoolFace
Apppublic

jawahar-konathala/Tryon2

sourceHugging Facecc-by-nc-sa-4.0updated 2y agoView on Hugging Face
0likes
augmentation.py381 linesDownload Raw Back to transforms
1# -*- coding: utf-8 -*-2# Copyright (c) Facebook, Inc. and its affiliates.3 4import inspect5import numpy as np6import pprint7from typing import Any, List, Optional, Tuple, Union8from fvcore.transforms.transform import Transform, TransformList9 10"""11See "Data Augmentation" tutorial for an overview of the system:12https://detectron2.readthedocs.io/tutorials/augmentation.html13"""14 15 16__all__ = [17    "Augmentation",18    "AugmentationList",19    "AugInput",20    "TransformGen",21    "apply_transform_gens",22    "StandardAugInput",23    "apply_augmentations",24]25 26 27def _check_img_dtype(img):28    assert isinstance(img, np.ndarray), "[Augmentation] Needs an numpy array, but got a {}!".format(29        type(img)30    )31    assert not isinstance(img.dtype, np.integer) or (32        img.dtype == np.uint833    ), "[Augmentation] Got image of type {}, use uint8 or floating points instead!".format(34        img.dtype35    )36    assert img.ndim in [2, 3], img.ndim37 38 39def _get_aug_input_args(aug, aug_input) -> List[Any]:40    """41    Get the arguments to be passed to ``aug.get_transform`` from the input ``aug_input``.42    """43    if aug.input_args is None:44        # Decide what attributes are needed automatically45        prms = list(inspect.signature(aug.get_transform).parameters.items())46        # The default behavior is: if there is one parameter, then its "image"47        # (work automatically for majority of use cases, and also avoid BC breaking),48        # Otherwise, use the argument names.49        if len(prms) == 1:50            names = ("image",)51        else:52            names = []53            for name, prm in prms:54                if prm.kind in (55                    inspect.Parameter.VAR_POSITIONAL,56                    inspect.Parameter.VAR_KEYWORD,57                ):58                    raise TypeError(59                        f""" \60The default implementation of `{type(aug)}.__call__` does not allow \61`{type(aug)}.get_transform` to use variable-length arguments (*args, **kwargs)! \62If arguments are unknown, reimplement `__call__` instead. \63"""64                    )65                names.append(name)66        aug.input_args = tuple(names)67 68    args = []69    for f in aug.input_args:70        try:71            args.append(getattr(aug_input, f))72        except AttributeError as e:73            raise AttributeError(74                f"{type(aug)}.get_transform needs input attribute '{f}', "75                f"but it is not an attribute of {type(aug_input)}!"76            ) from e77    return args78 79 80class Augmentation:81    """82    Augmentation defines (often random) policies/strategies to generate :class:`Transform`83    from data. It is often used for pre-processing of input data.84 85    A "policy" that generates a :class:`Transform` may, in the most general case,86    need arbitrary information from input data in order to determine what transforms87    to apply. Therefore, each :class:`Augmentation` instance defines the arguments88    needed by its :meth:`get_transform` method. When called with the positional arguments,89    the :meth:`get_transform` method executes the policy.90 91    Note that :class:`Augmentation` defines the policies to create a :class:`Transform`,92    but not how to execute the actual transform operations to those data.93    Its :meth:`__call__` method will use :meth:`AugInput.transform` to execute the transform.94 95    The returned `Transform` object is meant to describe deterministic transformation, which means96    it can be re-applied on associated data, e.g. the geometry of an image and its segmentation97    masks need to be transformed together.98    (If such re-application is not needed, then determinism is not a crucial requirement.)99    """100 101    input_args: Optional[Tuple[str]] = None102    """103    Stores the attribute names needed by :meth:`get_transform`, e.g.  ``("image", "sem_seg")``.104    By default, it is just a tuple of argument names in :meth:`self.get_transform`, which often only105    contain "image". As long as the argument name convention is followed, there is no need for106    users to touch this attribute.107    """108 109    def _init(self, params=None):110        if params:111            for k, v in params.items():112                if k != "self" and not k.startswith("_"):113                    setattr(self, k, v)114 115    def get_transform(self, *args) -> Transform:116        """117        Execute the policy based on input data, and decide what transform to apply to inputs.118 119        Args:120            args: Any fixed-length positional arguments. By default, the name of the arguments121                should exist in the :class:`AugInput` to be used.122 123        Returns:124            Transform: Returns the deterministic transform to apply to the input.125 126        Examples:127        ::128            class MyAug:129                # if a policy needs to know both image and semantic segmentation130                def get_transform(image, sem_seg) -> T.Transform:131                    pass132            tfm: Transform = MyAug().get_transform(image, sem_seg)133            new_image = tfm.apply_image(image)134 135        Notes:136            Users can freely use arbitrary new argument names in custom137            :meth:`get_transform` method, as long as they are available in the138            input data. In detectron2 we use the following convention:139 140            * image: (H,W) or (H,W,C) ndarray of type uint8 in range [0, 255], or141              floating point in range [0, 1] or [0, 255].142            * boxes: (N,4) ndarray of float32. It represents the instance bounding boxes143              of N instances. Each is in XYXY format in unit of absolute coordinates.144            * sem_seg: (H,W) ndarray of type uint8. Each element is an integer label of pixel.145 146            We do not specify convention for other types and do not include builtin147            :class:`Augmentation` that uses other types in detectron2.148        """149        raise NotImplementedError150 151    def __call__(self, aug_input) -> Transform:152        """153        Augment the given `aug_input` **in-place**, and return the transform that's used.154 155        This method will be called to apply the augmentation. In most augmentation, it156        is enough to use the default implementation, which calls :meth:`get_transform`157        using the inputs. But a subclass can overwrite it to have more complicated logic.158 159        Args:160            aug_input (AugInput): an object that has attributes needed by this augmentation161                (defined by ``self.get_transform``). Its ``transform`` method will be called162                to in-place transform it.163 164        Returns:165            Transform: the transform that is applied on the input.166        """167        args = _get_aug_input_args(self, aug_input)168        tfm = self.get_transform(*args)169        assert isinstance(tfm, (Transform, TransformList)), (170            f"{type(self)}.get_transform must return an instance of Transform! "171            f"Got {type(tfm)} instead."172        )173        aug_input.transform(tfm)174        return tfm175 176    def _rand_range(self, low=1.0, high=None, size=None):177        """178        Uniform float random number between low and high.179        """180        if high is None:181            low, high = 0, low182        if size is None:183            size = []184        return np.random.uniform(low, high, size)185 186    def __repr__(self):187        """188        Produce something like:189        "MyAugmentation(field1={self.field1}, field2={self.field2})"190        """191        try:192            sig = inspect.signature(self.__init__)193            classname = type(self).__name__194            argstr = []195            for name, param in sig.parameters.items():196                assert (197                    param.kind != param.VAR_POSITIONAL and param.kind != param.VAR_KEYWORD198                ), "The default __repr__ doesn't support *args or **kwargs"199                assert hasattr(self, name), (200                    "Attribute {} not found! "201                    "Default __repr__ only works if attributes match the constructor.".format(name)202                )203                attr = getattr(self, name)204                default = param.default205                if default is attr:206                    continue207                attr_str = pprint.pformat(attr)208                if "\n" in attr_str:209                    # don't show it if pformat decides to use >1 lines210                    attr_str = "..."211                argstr.append("{}={}".format(name, attr_str))212            return "{}({})".format(classname, ", ".join(argstr))213        except AssertionError:214            return super().__repr__()215 216    __str__ = __repr__217 218 219class _TransformToAug(Augmentation):220    def __init__(self, tfm: Transform):221        self.tfm = tfm222 223    def get_transform(self, *args):224        return self.tfm225 226    def __repr__(self):227        return repr(self.tfm)228 229    __str__ = __repr__230 231 232def _transform_to_aug(tfm_or_aug):233    """234    Wrap Transform into Augmentation.235    Private, used internally to implement augmentations.236    """237    assert isinstance(tfm_or_aug, (Transform, Augmentation)), tfm_or_aug238    if isinstance(tfm_or_aug, Augmentation):239        return tfm_or_aug240    else:241        return _TransformToAug(tfm_or_aug)242 243 244class AugmentationList(Augmentation):245    """246    Apply a sequence of augmentations.247 248    It has ``__call__`` method to apply the augmentations.249 250    Note that :meth:`get_transform` method is impossible (will throw error if called)251    for :class:`AugmentationList`, because in order to apply a sequence of augmentations,252    the kth augmentation must be applied first, to provide inputs needed by the (k+1)th253    augmentation.254    """255 256    def __init__(self, augs):257        """258        Args:259            augs (list[Augmentation or Transform]):260        """261        super().__init__()262        self.augs = [_transform_to_aug(x) for x in augs]263 264    def __call__(self, aug_input) -> TransformList:265        tfms = []266        for x in self.augs:267            tfm = x(aug_input)268            tfms.append(tfm)269        return TransformList(tfms)270 271    def __repr__(self):272        msgs = [str(x) for x in self.augs]273        return "AugmentationList[{}]".format(", ".join(msgs))274 275    __str__ = __repr__276 277 278class AugInput:279    """280    Input that can be used with :meth:`Augmentation.__call__`.281    This is a standard implementation for the majority of use cases.282    This class provides the standard attributes **"image", "boxes", "sem_seg"**283    defined in :meth:`__init__` and they may be needed by different augmentations.284    Most augmentation policies do not need attributes beyond these three.285 286    After applying augmentations to these attributes (using :meth:`AugInput.transform`),287    the returned transforms can then be used to transform other data structures that users have.288 289    Examples:290    ::291        input = AugInput(image, boxes=boxes)292        tfms = augmentation(input)293        transformed_image = input.image294        transformed_boxes = input.boxes295        transformed_other_data = tfms.apply_other(other_data)296 297    An extended project that works with new data types may implement augmentation policies298    that need other inputs. An algorithm may need to transform inputs in a way different299    from the standard approach defined in this class. In those rare situations, users can300    implement a class similar to this class, that satify the following condition:301 302    * The input must provide access to these data in the form of attribute access303      (``getattr``).  For example, if an :class:`Augmentation` to be applied needs "image"304      and "sem_seg" arguments, its input must have the attribute "image" and "sem_seg".305    * The input must have a ``transform(tfm: Transform) -> None`` method which306      in-place transforms all its attributes.307    """308 309    # TODO maybe should support more builtin data types here310    def __init__(311        self,312        image: np.ndarray,313        *,314        boxes: Optional[np.ndarray] = None,315        sem_seg: Optional[np.ndarray] = None,316    ):317        """318        Args:319            image (ndarray): (H,W) or (H,W,C) ndarray of type uint8 in range [0, 255], or320                floating point in range [0, 1] or [0, 255]. The meaning of C is up321                to users.322            boxes (ndarray or None): Nx4 float32 boxes in XYXY_ABS mode323            sem_seg (ndarray or None): HxW uint8 semantic segmentation mask. Each element324                is an integer label of pixel.325        """326        _check_img_dtype(image)327        self.image = image328        self.boxes = boxes329        self.sem_seg = sem_seg330 331    def transform(self, tfm: Transform) -> None:332        """333        In-place transform all attributes of this class.334 335        By "in-place", it means after calling this method, accessing an attribute such336        as ``self.image`` will return transformed data.337        """338        self.image = tfm.apply_image(self.image)339        if self.boxes is not None:340            self.boxes = tfm.apply_box(self.boxes)341        if self.sem_seg is not None:342            self.sem_seg = tfm.apply_segmentation(self.sem_seg)343 344    def apply_augmentations(345        self, augmentations: List[Union[Augmentation, Transform]]346    ) -> TransformList:347        """348        Equivalent of ``AugmentationList(augmentations)(self)``349        """350        return AugmentationList(augmentations)(self)351 352 353def apply_augmentations(augmentations: List[Union[Transform, Augmentation]], inputs):354    """355    Use ``T.AugmentationList(augmentations)(inputs)`` instead.356    """357    if isinstance(inputs, np.ndarray):358        # handle the common case of image-only Augmentation, also for backward compatibility359        image_only = True360        inputs = AugInput(inputs)361    else:362        image_only = False363    tfms = inputs.apply_augmentations(augmentations)364    return inputs.image if image_only else inputs, tfms365 366 367apply_transform_gens = apply_augmentations368"""369Alias for backward-compatibility.370"""371 372TransformGen = Augmentation373"""374Alias for Augmentation, since it is something that generates :class:`Transform`s375"""376 377StandardAugInput = AugInput378"""379Alias for compatibility. It's not worth the complexity to have two classes.380"""381