text: # -*- coding: utf-8 -*-
  # @Time : 20-6-4 下午4:19
  # @Author : zhuying
  # @Company : Minivision
  # @File : transform.py
  # @Software : PyCharm
  
  from __future__ import division
  import math
  import random
  from PIL import Image
  try:
      import accimage
  except ImportError:
      accimage = None
  import numpy as np
  import numbers
  import types
  
  from anti_spoofing import functional as F
  
  __all__ = ["Compose", "ToTensor", "ToPILImage", "Normalize", "RandomHorizontalFlip",
             "Lambda", "RandomResizedCrop", "ColorJitter", "RandomRotation"]
  
  
  class Compose(object):
      """Composes several transforms together.
  
      Args:
          transforms (list of ``Transform`` objects): list of transforms to compose.
  
      Example:
          >>> transforms.Compose([
          >>>     transforms.CenterCrop(10),
          >>>     transforms.ToTensor(),
          >>> ])
      """
  
      def __init__(self, transforms):
          self.transforms = transforms
  
      def __call__(self, img):
          for t in self.transforms:
              img = t(img)
          return img
  
  
  class ToTensor(object):
  
      """Convert a ``PIL Image`` or ``numpy.ndarray`` to tensor.
  
      Converts a PIL Image or numpy.ndarray (H x W x C) in the range
      [0, 255] to a torch.FloatTensor of shape (C x H x W) in the range [0.0, 1.0].
      """
  
      def __call__(self, pic):
          """
          Args:
              pic (PIL Image or numpy.ndarray): Image to be converted to tensor.
  
          Returns:
              Tensor: Converted image.
          """
          return F.to_tensor(pic)
  
  
  class Lambda(object):
      """Apply a user-defined lambda as a transform.
  
      Args:
          lambd (function): Lambda/function to be used for transform.
      """
  
      def __init__(self, lambd):
          assert isinstance(lambd, types.LambdaType)
          self.lambd = lambd
  
      def __call__(self, img):
          return self.lambd(img)
  
  
  class ToPILImage(object):
      """Convert a tensor or an ndarray to PIL Image.
  
      Converts a torch.*Tensor of shape C x H x W or a numpy ndarray of shape
      H x W x C to a PIL Image while preserving the value range.
  
      Args:
          mode (`PIL.Image mode`_): color space and pixel depth of input data (optional).
              If ``mode`` is ``None`` (default) there are some assumptions made about the input data:
              1. If the input has 3 channels, the ``mode`` is assumed to be ``RGB``.
              2. If the input has 4 channels, the ``mode`` is assumed to be ``RGBA``.
              3. If the input has 1 channel, the ``mode`` is determined by the data type (i,e,
              ``int``, ``float``, ``short``).
  
      .. _PIL.Image mode: http://pillow.readthedocs.io/en/3.4.x/handbook/concepts.html#modes
      """
      def __init__(self, mode=None):
          self.mode = mode
  
      def __call__(self, pic):
          """
          Args:
              pic (Tensor or numpy.ndarray): Image to be converted to PIL Image.
  
          Returns:
              PIL Image: Image converted to PIL Image.
  
          """
          return F.to_pil_image(pic, self.mode)
  
  
  class Normalize(object):
      """Normalize an tensor image with mean and standard deviation.
      Given mean: ``(M1,...,Mn)`` and std: ``(S1,..,Sn)`` for ``n`` channels, this transform
      will normalize each channel of the input ``torch.*Tensor`` i.e.
      ``input[channel] = (input[channel] - mean[channel]) / std[channel]``
  
      Args:
          mean (sequence): Sequence of means for each channel.
          std (sequence): Sequence of standard deviations for each channel.
      """
  
      def __init__(self, mean, std):
          self.mean = mean
          self.std = std
  
      def __call__(self, tensor):
          """
          Args:
              tensor (Tensor): Tensor image of size (C, H, W) to be normalized.
  
          Returns:
              Tensor: Normalized Tensor image.
          """
          return F.normalize(tensor, self.mean, self.std)
  
  
  class RandomHorizontalFlip(object):
      """Horizontally flip the given PIL Image randomly with a probability of 0.5."""
  
      def __call__(self, img):
          """
          Args:
              img (PIL Image): Image to be flipped.
  
          Returns:
              PIL Image: Randomly flipped image.
          """
          if random.random() < 0.5:
              return F.hflip(img)
          return img
  
  
  class RandomResizedCrop(object):
      """Crop the given PIL Image to random size and aspect ratio.
  
      A crop of random size (default: of 0.08 to 1.0) of the original size and a random
      aspect ratio (default: of 3/4 to 4/3) of the original aspect ratio is made. This crop
      is finally resized to given size.
      This is popularly used to train the Inception networks.
  
      Args:
          size: expected output size of each edge
          scale: range of size of the origin size cropped
          ratio: range of aspect ratio of the origin aspect ratio cropped
          interpolation: Default: PIL.Image.BILINEAR
      """
  
      def __init__(self, size, scale=(0.08, 1.0), ratio=(3. / 4., 4. / 3.), interpolation=Image.BILINEAR):
          if isinstance(size, tuple):
              self.size = size
          else:
              self.size = (size, size)
          self.interpolation = interpolation
          self.scale = scale
          self.ratio = ratio
  
      @staticmethod
      def get_params(img, scale, ratio):
          """Get parameters for ``crop`` for a random sized crop.
  
          Args:
              img (PIL Image): Image to be cropped.
              scale (tuple): range of size of the origin size cropped
              ratio (tuple): range of aspect ratio of the origin aspect ratio cropped
  
          Returns:
              tuple: params (i, j, h, w) to be passed to ``crop`` for a random
                  sized crop.
          """
          for attempt in range(10):
              area = img.size[0] * img.size[1]
              target_area = random.uniform(*scale) * area
              aspect_ratio = random.uniform(*ratio)
  
              w = int(round(math.sqrt(target_area * aspect_ratio)))
              h = int(round(math.sqrt(target_area / aspect_ratio)))
  
              if random.random() < 0.5:
                  w, h = h, w
  
              if w <= img.size[0] and h <= img.size[1]:
                  i = random.randint(0, img.size[1] - h)
                  j = random.randint(0, img.size[0] - w)
                  return i, j, h, w
  
          # Fallback
          w = min(img.size[0], img.size[1])
          i = (img.size[1] - w) // 2
          j = (img.size[0] - w) // 2
          return i, j, w, w
  
      def __call__(self, img):
          """
          Args:
              img (PIL Image): Image to be flipped.
  
          Returns:
              PIL Image: Randomly cropped and resize image.
          """
          i, j, h, w = self.get_params(img, self.scale, self.ratio)
          return F.resized_crop(img, i, j, h, w, self.size, self.interpolation)
  
  
  class ColorJitter(object):
      """Randomly change the brightness, contrast and saturation of an image.
  
      Args:
          brightness (float): How much to jitter brightness. brightness_factor
              is chosen uniformly from [max(0, 1 - brightness), 1 + brightness].
          contrast (float): How much to jitter contrast. contrast_factor
              is chosen uniformly from [max(0, 1 - contrast), 1 + contrast].
          saturation (float): How much to jitter saturation. saturation_factor
              is chosen uniformly from [max(0, 1 - saturation), 1 + saturation].
          hue(float): How much to jitter hue. hue_factor is chosen uniformly from
              [-hue, hue]. Should be >=0 and <= 0.5.
      """
      def __init__(self, brightness=0, contrast=0, saturation=0, hue=0):
          self.brightness = brightness
          self.contrast = contrast
          self.saturation = saturation
          self.hue = hue
  
      @staticmethod
      def get_params(brightness, contrast, saturation, hue):
          """Get a randomized transform to be applied on image.
  
          Arguments are same as that of __init__.
  
          Returns:
              Transform which randomly adjusts brightness, contrast and
              saturation in a random order.
          """
          transforms = []
          if brightness > 0:
              brightness_factor = np.random.uniform(max(0, 1 - brightness), 1 + brightness)
              transforms.append(Lambda(lambda img: F.adjust_brightness(img, brightness_factor)))
  
          if contrast > 0:
              contrast_factor = np.random.uniform(max(0, 1 - contrast), 1 + contrast)
              transforms.append(Lambda(lambda img: F.adjust_contrast(img, contrast_factor)))
  
          if saturation > 0:
              saturation_factor = np.random.uniform(max(0, 1 - saturation), 1 + saturation)
              transforms.append(Lambda(lambda img: F.adjust_saturation(img, saturation_factor)))
  
          if hue > 0:
              hue_factor = np.random.uniform(-hue, hue)
              transforms.append(Lambda(lambda img: F.adjust_hue(img, hue_factor)))
  
          np.random.shuffle(transforms)
          transform = Compose(transforms)
  
          return transform
  
      def __call__(self, img):
          """
          Args:
              img (PIL Image): Input image.
  
          Returns:
              PIL Image: Color jittered image.
          """
          transform = self.get_params(self.brightness, self.contrast,
                                      self.saturation, self.hue)
          return transform(img)
  
  
  class RandomRotation(object):
      """Rotate the image by angle.
  
      Args:
          degrees (sequence or float or int): Range of degrees to select from.
              If degrees is a number instead of sequence like (min, max), the range of degrees
              will be (-degrees, +degrees).
          resample ({PIL.Image.NEAREST, PIL.Image.BILINEAR, PIL.Image.BICUBIC}, optional):
              An optional resampling filter.
              See http://pillow.readthedocs.io/en/3.4.x/handbook/concepts.html#filters
              If omitted, or if the image has mode "1" or "P", it is set to PIL.Image.NEAREST.
          expand (bool, optional): Optional expansion flag.
              If true, expands the output to make it large enough to hold the entire rotated image.
              If false or omitted, make the output image the same size as the input image.
              Note that the expand flag assumes rotation around the center and no translation.
          center (2-tuple, optional): Optional center of rotation.
              Origin is the upper left corner.
              Default is the center of the image.
      """
  
      def __init__(self, degrees, resample=False, expand=False, center=None):
          if isinstance(degrees, numbers.Number):
              if degrees < 0:
                  raise ValueError("If degrees is a single number, it must be positive.")
              self.degrees = (-degrees, degrees)
          else:
              if len(degrees) != 2:
                  raise ValueError("If degrees is a sequence, it must be of len 2.")
              self.degrees = degrees
  
          self.resample = resample
          self.expand = expand
          self.center = center
  
      @staticmethod
      def get_params(degrees):
          """Get parameters for ``rotate`` for a random rotation.
  
          Returns:
              sequence: params to be passed to ``rotate`` for random rotation.
          """
          angle = np.random.uniform(degrees[0], degrees[1])
  
          return angle
  
      def __call__(self, img):
          """
              img (PIL Image): Image to be rotated.
  
          Returns:
              PIL Image: Rotated image.
          """
  
          angle = self.get_params(self.degrees)
  
          return F.rotate(img, angle, self.resample, self.expand, self.center)
  
  
  