text: from __future__ import print_function
  
  import os
  import numpy as np
  import torch
  
  import random
  import math
  
  class RandomErasing(object):
      '''
      Class that performs Random Erasing in Random Erasing Data Augmentation by Zhong et al. 
      -------------------------------------------------------------------------------------
      probability: The probability that the operation will be performed.
      sl: min erasing area
      sh: max erasing area
      r1: min aspect ratio
      mean: erasing value
      -------------------------------------------------------------------------------------
      '''
      def __init__(self, probability = 0.5, sl = 0.02, sh = 0.4, r1 = 0.3, mean=[0.4914, 0.4822, 0.4465]):
          self.probability = probability
          self.mean = mean
          self.sl = sl
          self.sh = sh
          self.r1 = r1
         
      def __call__(self, img):
  
          if random.uniform(0, 1) > self.probability:
              return img
  
          for attempt in range(100):
              area = img.size()[1] * img.size()[2]
         
              target_area = random.uniform(self.sl, self.sh) * area
              aspect_ratio = random.uniform(self.r1, 1/self.r1)
  
              h = int(round(math.sqrt(target_area * aspect_ratio)))
              w = int(round(math.sqrt(target_area / aspect_ratio)))
  
              if w < img.size()[2] and h < img.size()[1]:
                  x1 = random.randint(0, img.size()[1] - h)
                  y1 = random.randint(0, img.size()[2] - w)
                  if img.size()[0] == 3:
                      img[0, x1:x1+h, y1:y1+w] = self.mean[0]
                      img[1, x1:x1+h, y1:y1+w] = self.mean[1]
                      img[2, x1:x1+h, y1:y1+w] = self.mean[2]
                  else:
                      img[0, x1:x1+h, y1:y1+w] = self.mean[0]
                  return img
  
          return img
  
  def mixup_data(x, y, gpu, mixup_prob=0.5, alpha=1.0):
      '''Returns mixed inputs, pairs of targets, and lambda'''
      if random.uniform(0, 1) < mixup_prob:
          lam = np.random.beta(alpha, alpha)
      else:
          lam = 1
  
      batch_size = x.size()[0]
      index = torch.randperm(batch_size).cuda(gpu)
  
      mixed_x = lam * x + (1 - lam) * x[index, :]
      y_a, y_b = y, y[index]
      return mixed_x, y_a, y_b, lam
  
  
  def mixup_criterion(criterion, pred, y_a, y_b, lam):
      return lam * criterion(pred, y_a) + (1 - lam) * criterion(pred, y_b)
  '''
  class Cutout(object):
      def __init__(self, n_holes=1, length=112):
          self.n_holes = n_holes
          self.length = length
  
      def __call__(self, img):
          """ cutout_image """
          h, w = img.shape[:2]
          mask = np.ones((h, w), np.float32)
  
          for n in range(self.n_holes):
              y = np.random.randint(h)
              x = np.random.randint(w)
  
              y1 = np.clip(y - self.length // 2, 0, h)
              y2 = np.clip(y + self.length // 2, 0, h)
              x1 = np.clip(x - self.length // 2, 0, w)
              x2 = np.clip(x + self.length // 2, 0, w)
  
              img[y1:y2, x1:x2] = 0
          return img
  '''
  class Cutout(object):
      def __init__(self, p=0.5, scale=(0.02, 0.4), ratio=(0.4, 1 / 0.4),
                   value=(0, 255), pixel_level=False, inplace=False):
  
          if (scale[0] > scale[1]) or (ratio[0] > ratio[1]):
              warnings.warn("range should be of kind (min, max)")
          if scale[0] < 0 or scale[1] > 1:
              raise ValueError("range of scale should be between 0 and 1")
          if p < 0 or p > 1:
              raise ValueError(
                  "range of random erasing probability should be between 0 and 1")
          self.p = p
          self.scale = scale
          self.ratio = ratio
          self.value = value
          self.pixel_level = pixel_level
          self.inplace = inplace
  
      @staticmethod
      def get_params(img, scale, ratio):
  
          if type(img) == np.ndarray:
              img_h, img_w, img_c = img.shape
          else: 
              img_h, img_w = img.size
              img_c = len(img.getbands())
  
          s = random.uniform(*scale)
          # if you img_h != img_w you may need this.
          # r_1 = max(r_1, (img_h*s)/img_w)
          # r_2 = min(r_2, img_h / (img_w*s))
          r = random.uniform(*ratio)
          s = s * img_h * img_w
          w = int(math.sqrt(s / r))
          h = int(math.sqrt(s * r))
          left = random.randint(0, img_w - w)
          top = random.randint(0, img_h - h)
  
          return left, top, h, w, img_c
  
      def __call__(self, img):
          if random.random() < self.p:
              left, top, h, w, ch = self.get_params(img, self.scale, self.ratio)
  
              if self.pixel_level:
                  c = np.random.randint(*self.value, size=(h, w, ch), dtype='uint8')
              else:
                  c = random.randint(*self.value)
   
              if self.pixel_level:
                  c = PIL.Image.fromarray(c)
              img.paste(c, (left, top, left + w, top + h))
              return img
          return img
  
  def rand_bbox(size, lam):
      W = size[2]
      H = size[3]
      cut_rat = np.sqrt(1. - lam)
      cut_w = np.int(W * cut_rat)
      cut_h = np.int(H * cut_rat)
  
      # uniform
      cx = np.random.randint(W)
      cy = np.random.randint(H)
  
      bbx1 = np.clip(cx - cut_w // 2, 0, W)
      bby1 = np.clip(cy - cut_h // 2, 0, H)
      bbx2 = np.clip(cx + cut_w // 2, 0, W)
      bby2 = np.clip(cy + cut_h // 2, 0, H)
  
      return bbx1, bby1, bbx2, bby2
  
  def cutmix_data(input, target, gpu, cutmix_prob=0.5, alpha=1.0):
      if random.uniform(0, 1) < cutmix_prob:
          lam = np.random.beta(alpha, alpha)
      else:
          lam = 1
      rand_index = torch.randperm(input.size()[0]).cuda(gpu)
      target_a = target
      target_b = target[rand_index]
      bbx1, bby1, bbx2, bby2 = rand_bbox(input.size(), lam)
      input[:, :, bbx1:bbx2, bby1:bby2] = input[rand_index, :, bbx1:bbx2, bby1:bby2]
      # adjust lambda to exactly match pixel ratio
      lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (input.size()[-1] * input.size()[-2]))
      return input, target_a, target_b, lam
  