text: import inspect
  
  import mmcv
  import numpy as np
  from numpy import random
  
  from mmdet.core import PolygonMasks
  from mmdet.core.evaluation.bbox_overlaps import bbox_overlaps
  from ..builder import PIPELINES
  
  try:
      from imagecorruptions import corrupt
  except ImportError:
      corrupt = None
  
  try:
      import albumentations
      from albumentations import Compose
  except ImportError:
      albumentations = None
      Compose = None
  
  
  @PIPELINES.register_module()
  class MixUp(object):
      def __init__(self, p=0.3, lambd=0.5):
          self.lambd = lambd
          self.p = p
          self.img2 = None
          self.boxes2 = None
          self.labels2= None
          self.masks2 = None
          self.semantic_seg2 = None
  
      def __call__(self, results):
          img1, boxes1, labels1, masks1, semantic_seg1 = [
              results[k] for k in ('img', 'gt_bboxes', 'gt_labels','gt_masks','gt_semantic_seg')
          ]
          if random.random() < self.p and self.img2 is not None and img1.shape[1]==self.img2.shape[1]:
              
              height = max(img1.shape[0], self.img2.shape[0])
              width = max(img1.shape[1], self.img2.shape[1])
              mixup_image = np.zeros([height, width, 3], dtype='float32')
              mixup_image[:img1.shape[0], :img1.shape[1], :] = img1.astype('float32') * self.lambd
              mixup_image[:self.img2.shape[0], :self.img2.shape[1], :] += self.img2.astype('float32') * (1. - self.lambd)
              mixup_image = mixup_image.astype('uint8')
              mixup_boxes = np.vstack((boxes1, self.boxes2))
              mixup_label = np.hstack((labels1,self.labels2))
              mixup_masks = np.hstack((masks1, self.masks2)) 
              mixup_semantic_seg = np.zeros([height, width, 1], dtype='float32')
              mixup_semantic_seg[:semantic_seg1.shape[0], :semantic_seg1.shape[1], :] = semantic_seg1.astype('float32') * self.lambd
              mixup_semantic_seg[:self.semantic_seg2.shape[0], :self.semantic_seg2.shape[1], :] += self.semantic_seg2.astype('float32') * (1. - self.lambd)
              mixup_semantic_seg = mixup_semantic_seg.astype('unit8')           
              results['img'] = mixup_image
              results['gt_bboxes'] = mixup_boxes
              results['gt_labels'] = mixup_label
              results['gt_masks'] = mixup_masks
              results['gt_semantic_seg'] = mixup_semantic_seg
          else: 
              pass
          self.img2 = img1
          self.boxes2 = boxes1
          self.labels2 =  labels1
          self.masks2 = masks1
          self.semantic_seg2 = semantic_seg1
          return results
  
  
  @PIPELINES.register_module()
  class Resize(object):
      """Resize images & bbox & mask.
  
      This transform resizes the input image to some scale. Bboxes and masks are
      then resized with the same scale factor. If the input dict contains the key
      "scale", then the scale in the input dict is used, otherwise the specified
      scale in the init method is used.
  
      `img_scale` can either be a tuple (single-scale) or a list of tuple
      (multi-scale). There are 3 multiscale modes:
  
      - ``ratio_range is not None``: randomly sample a ratio from the ratio range
        and multiply it with the image scale.
      - ``ratio_range is None`` and ``multiscale_mode == "range"``: randomly
        sample a scale from the multiscale range.
      - ``ratio_range is None`` and ``multiscale_mode == "value"``: randomly
        sample a scale from multiple scales.
  
      Args:
          img_scale (tuple or list[tuple]): Images scales for resizing.
          multiscale_mode (str): Either "range" or "value".
          ratio_range (tuple[float]): (min_ratio, max_ratio)
          keep_ratio (bool): Whether to keep the aspect ratio when resizing the
              image.
      """
  
      def __init__(self,
                   img_scale=None,
                   multiscale_mode='range',
                   ratio_range=None,
                   keep_ratio=True):
          if img_scale is None:
              self.img_scale = None
          else:
              if isinstance(img_scale, list):
                  self.img_scale = img_scale
              else:
                  self.img_scale = [img_scale]
              assert mmcv.is_list_of(self.img_scale, tuple)
  
          if ratio_range is not None:
              # mode 1: given a scale and a range of image ratio
              assert len(self.img_scale) == 1
          else:
              # mode 2: given multiple scales or a range of scales
              assert multiscale_mode in ['value', 'range']
  
          self.multiscale_mode = multiscale_mode
          self.ratio_range = ratio_range
          self.keep_ratio = keep_ratio
  
      @staticmethod
      def random_select(img_scales):
          assert mmcv.is_list_of(img_scales, tuple)
          scale_idx = np.random.randint(len(img_scales))
          img_scale = img_scales[scale_idx]
          return img_scale, scale_idx
  
      @staticmethod
      def random_sample(img_scales):
          assert mmcv.is_list_of(img_scales, tuple) and len(img_scales) == 2
          img_scale_long = [max(s) for s in img_scales]
          img_scale_short = [min(s) for s in img_scales]
          long_edge = np.random.randint(
              min(img_scale_long),
              max(img_scale_long) + 1)
          short_edge = np.random.randint(
              min(img_scale_short),
              max(img_scale_short) + 1)
          img_scale = (long_edge, short_edge)
          return img_scale, None
  
      @staticmethod
      def random_sample_ratio(img_scale, ratio_range):
          assert isinstance(img_scale, tuple) and len(img_scale) == 2
          min_ratio, max_ratio = ratio_range
          assert min_ratio <= max_ratio
          ratio = np.random.random_sample() * (max_ratio - min_ratio) + min_ratio
          scale = int(img_scale[0] * ratio), int(img_scale[1] * ratio)
          return scale, None
  
      def _random_scale(self, results):
          if self.ratio_range is not None:
              scale, scale_idx = self.random_sample_ratio(
                  self.img_scale[0], self.ratio_range)
          elif len(self.img_scale) == 1:
              scale, scale_idx = self.img_scale[0], 0
          elif self.multiscale_mode == 'range':
              scale, scale_idx = self.random_sample(self.img_scale)
          elif self.multiscale_mode == 'value':
              scale, scale_idx = self.random_select(self.img_scale)
          else:
              raise NotImplementedError
  
          results['scale'] = scale
          results['scale_idx'] = scale_idx
  
      def _resize_img(self, results):
          for key in results.get('img_fields', ['img']):
              if self.keep_ratio:
                  img, scale_factor = mmcv.imrescale(
                      results[key], results['scale'], return_scale=True)
                  # the w_scale and h_scale has minor difference
                  # a real fix should be done in the mmcv.imrescale in the future
                  new_h, new_w = img.shape[:2]
                  h, w = results[key].shape[:2]
                  w_scale = new_w / w
                  h_scale = new_h / h
              else:
                  img, w_scale, h_scale = mmcv.imresize(
                      results[key], results['scale'], return_scale=True)
              results[key] = img
  
              scale_factor = np.array([w_scale, h_scale, w_scale, h_scale],
                                      dtype=np.float32)
              results['img_shape'] = img.shape
              # in case that there is no padding
              results['pad_shape'] = img.shape
              results['scale_factor'] = scale_factor
              results['keep_ratio'] = self.keep_ratio
  
      def _resize_bboxes(self, results):
          img_shape = results['img_shape']
          for key in results.get('bbox_fields', []):
              bboxes = results[key] * results['scale_factor']
              bboxes[:, 0::2] = np.clip(bboxes[:, 0::2], 0, img_shape[1])
              bboxes[:, 1::2] = np.clip(bboxes[:, 1::2], 0, img_shape[0])
              results[key] = bboxes
  
      def _resize_masks(self, results):
          for key in results.get('mask_fields', []):
              if results[key] is None:
                  continue
              if self.keep_ratio:
                  results[key] = results[key].rescale(results['scale'])
              else:
                  results[key] = results[key].resize(results['img_shape'][:2])
  
      def _resize_seg(self, results):
          for key in results.get('seg_fields', []):
              if self.keep_ratio:
                  gt_seg = mmcv.imrescale(
                      results[key], results['scale'], interpolation='nearest')
              else:
                  gt_seg = mmcv.imresize(
                      results[key], results['scale'], interpolation='nearest')
              results['gt_semantic_seg'] = gt_seg
  
      def __call__(self, results):
          if 'scale' not in results:
              self._random_scale(results)
          self._resize_img(results)
          self._resize_bboxes(results)
          self._resize_masks(results)
          self._resize_seg(results)
          return results
  
      def __repr__(self):
          repr_str = self.__class__.__name__
          repr_str += f'(img_scale={self.img_scale}, '
          repr_str += f'multiscale_mode={self.multiscale_mode}, '
          repr_str += f'ratio_range={self.ratio_range}, '
          repr_str += f'keep_ratio={self.keep_ratio})'
          return repr_str
  
  
  @PIPELINES.register_module()
  class RandomFlip(object):
      """Flip the image & bbox & mask.
  
      If the input dict contains the key "flip", then the flag will be used,
      otherwise it will be randomly decided by a ratio specified in the init
      method.
  
      Args:
          flip_ratio (float, optional): The flipping probability.
      """
  
      def __init__(self, flip_ratio=0.5, direction='horizontal'):
          assert flip_ratio >= 0 and flip_ratio <= 1
          assert direction in ['horizontal', 'vertical']
          self.flip_ratio = flip_ratio
          self.direction = direction
  
      def bbox_flip(self, bboxes, img_shape, direction):
          """Flip bboxes horizontally.
  
          Args:
              bboxes(ndarray): shape (..., 4*k)
              img_shape(tuple): (height, width)
          """
          assert bboxes.shape[-1] % 4 == 0
          flipped = bboxes.copy()
          if direction == 'horizontal':
              w = img_shape[1]
              flipped[..., 0::4] = w - bboxes[..., 2::4]
              flipped[..., 2::4] = w - bboxes[..., 0::4]
          elif direction == 'vertical':
              h = img_shape[0]
              flipped[..., 1::4] = h - bboxes[..., 3::4]
              flipped[..., 3::4] = h - bboxes[..., 1::4]
          else:
              raise ValueError(f"Invalid flipping direction '{direction}'")
          return flipped
  
      def __call__(self, results):
          if 'flip' not in results:
              flip = True if np.random.rand() < self.flip_ratio else False
              results['flip'] = flip
          if 'flip_direction' not in results:
              results['flip_direction'] = self.direction
          if results['flip']:
              # flip image
              for key in results.get('img_fields', ['img']):
                  results[key] = mmcv.imflip(
                      results[key], direction=results['flip_direction'])
              # flip bboxes
              for key in results.get('bbox_fields', []):
                  results[key] = self.bbox_flip(results[key],
                                                results['img_shape'],
                                                results['flip_direction'])
              # flip masks
              for key in results.get('mask_fields', []):
                  results[key] = results[key].flip(results['flip_direction'])
  
              # flip segs
              for key in results.get('seg_fields', []):
                  results[key] = mmcv.imflip(
                      results[key], direction=results['flip_direction'])
          return results
  
      def __repr__(self):
          return self.__class__.__name__ + f'(flip_ratio={self.flip_ratio})'
  
  
  @PIPELINES.register_module()
  class Pad(object):
      """Pad the image & mask.
  
      There are two padding modes: (1) pad to a fixed size and (2) pad to the
      minimum size that is divisible by some number.
  
      Args:
          size (tuple, optional): Fixed padding size.
          size_divisor (int, optional): The divisor of padded size.
          pad_val (float, optional): Padding value, 0 by default.
      """
  
      def __init__(self, size=None, size_divisor=None, pad_val=0):
          self.size = size
          self.size_divisor = size_divisor
          self.pad_val = pad_val
          # only one of size and size_divisor should be valid
          assert size is not None or size_divisor is not None
          assert size is None or size_divisor is None
  
      def _pad_img(self, results):
          for key in results.get('img_fields', ['img']):
              if self.size is not None:
                  padded_img = mmcv.impad(results[key], self.size, self.pad_val)
              elif self.size_divisor is not None:
                  padded_img = mmcv.impad_to_multiple(
                      results[key], self.size_divisor, pad_val=self.pad_val)
              results[key] = padded_img
          results['pad_shape'] = padded_img.shape
          results['pad_fixed_size'] = self.size
          results['pad_size_divisor'] = self.size_divisor
  
      def _pad_masks(self, results):
          pad_shape = results['pad_shape'][:2]
          for key in results.get('mask_fields', []):
              results[key] = results[key].pad(pad_shape, pad_val=self.pad_val)
  
      def _pad_seg(self, results):
          for key in results.get('seg_fields', []):
              results[key] = mmcv.impad(results[key], results['pad_shape'][:2])
  
      def __call__(self, results):
          self._pad_img(results)
          self._pad_masks(results)
          self._pad_seg(results)
          return results
  
      def __repr__(self):
          repr_str = self.__class__.__name__
          repr_str += f'(size={self.size}, '
          repr_str += f'size_divisor={self.size_divisor}, '
          repr_str += f'pad_val={self.pad_val})'
          return repr_str
  
  
  @PIPELINES.register_module()
  class Normalize(object):
      """Normalize the image.
  
      Args:
          mean (sequence): Mean values of 3 channels.
          std (sequence): Std values of 3 channels.
          to_rgb (bool): Whether to convert the image from BGR to RGB,
              default is true.
      """
  
      def __init__(self, mean, std, to_rgb=True):
          self.mean = np.array(mean, dtype=np.float32)
          self.std = np.array(std, dtype=np.float32)
          self.to_rgb = to_rgb
  
      def __call__(self, results):
          for key in results.get('img_fields', ['img']):
              results[key] = mmcv.imnormalize(results[key], self.mean, self.std,
                                              self.to_rgb)
          results['img_norm_cfg'] = dict(
              mean=self.mean, std=self.std, to_rgb=self.to_rgb)
          return results
  
      def __repr__(self):
          repr_str = self.__class__.__name__
          repr_str += f'(mean={self.mean}, std={self.std}, to_rgb={self.to_rgb})'
          return repr_str
  
  
  @PIPELINES.register_module()
  class RandomCrop(object):
      """Random crop the image & bboxes & masks.
  
      Args:
          crop_size (tuple): Expected size after cropping, (h, w).
  
      Notes:
          - If the image is smaller than the crop size, return the original image
          - The keys for bboxes, labels and masks must be aligned. That is,
            `gt_bboxes` corresponds to `gt_labels` and `gt_masks`, and
            `gt_bboxes_ignore` corresponds to `gt_labels_ignore` and
            `gt_masks_ignore`.
          - If there are gt bboxes in an image and the cropping area does not
            have intersection with any gt bbox, this image is skipped.
      """
  
      def __init__(self, crop_size):
          assert crop_size[0] > 0 and crop_size[1] > 0
          self.crop_size = crop_size
          # The key correspondence from bboxes to labels and masks.
          self.bbox2label = {
              'gt_bboxes': 'gt_labels',
              'gt_bboxes_ignore': 'gt_labels_ignore'
          }
          self.bbox2mask = {
              'gt_bboxes': 'gt_masks',
              'gt_bboxes_ignore': 'gt_masks_ignore'
          }
  
      def __call__(self, results):
          for key in results.get('img_fields', ['img']):
              img = results[key]
              margin_h = max(img.shape[0] - self.crop_size[0], 0)
              margin_w = max(img.shape[1] - self.crop_size[1], 0)
              offset_h = np.random.randint(0, margin_h + 1)
              offset_w = np.random.randint(0, margin_w + 1)
              crop_y1, crop_y2 = offset_h, offset_h + self.crop_size[0]
              crop_x1, crop_x2 = offset_w, offset_w + self.crop_size[1]
  
              # crop the image
              img = img[crop_y1:crop_y2, crop_x1:crop_x2, ...]
              img_shape = img.shape
              results[key] = img
          results['img_shape'] = img_shape
  
          valid_flag = False
          # crop bboxes accordingly and clip to the image boundary
          for key in results.get('bbox_fields', []):
              # e.g. gt_bboxes and gt_bboxes_ignore
              bbox_offset = np.array([offset_w, offset_h, offset_w, offset_h],
                                     dtype=np.float32)
              bboxes = results[key] - bbox_offset
              bboxes[:, 0::2] = np.clip(bboxes[:, 0::2], 0, img_shape[1])
              bboxes[:, 1::2] = np.clip(bboxes[:, 1::2], 0, img_shape[0])
              valid_inds = (bboxes[:, 2] > bboxes[:, 0]) & (
                  bboxes[:, 3] > bboxes[:, 1])
              # When there is no gt bbox, cropping is conducted.
              # When the crop is valid, cropping is conducted.
              if len(valid_inds) == 0 or valid_inds.any():
                  valid_flag = True
              results[key] = bboxes[valid_inds, :]
              # label fields. e.g. gt_labels and gt_labels_ignore
              label_key = self.bbox2label.get(key)
              if label_key in results:
                  results[label_key] = results[label_key][valid_inds]
  
              # mask fields, e.g. gt_masks and gt_masks_ignore
              mask_key = self.bbox2mask.get(key)
              if mask_key in results:
                  results[mask_key] = results[mask_key][
                      valid_inds.nonzero()[0]].crop(
                          np.asarray([crop_x1, crop_y1, crop_x2, crop_y2]))
  
          # if no gt bbox remains after cropping, just skip this image
          # TODO: check whether we can keep the image regardless of the crop.
          if 'bbox_fields' in results and not valid_flag:
              return None
  
          # crop semantic seg
          for key in results.get('seg_fields', []):
              results[key] = results[key][crop_y1:crop_y2, crop_x1:crop_x2]
  
          return results
  
      def __repr__(self):
          return self.__class__.__name__ + f'(crop_size={self.crop_size})'
  
  
  @PIPELINES.register_module()
  class SegRescale(object):
      """Rescale semantic segmentation maps.
  
      Args:
          scale_factor (float): The scale factor of the final output.
      """
  
      def __init__(self, scale_factor=1):
          self.scale_factor = scale_factor
  
      def __call__(self, results):
          for key in results.get('seg_fields', []):
              if self.scale_factor != 1:
                  results[key] = mmcv.imrescale(
                      results[key], self.scale_factor, interpolation='nearest')
          return results
  
      def __repr__(self):
          return self.__class__.__name__ + f'(scale_factor={self.scale_factor})'
  
  
  @PIPELINES.register_module()
  class PhotoMetricDistortion(object):
      """Apply photometric distortion to image sequentially, every transformation
      is applied with a probability of 0.5. The position of random contrast is in
      second or second to last.
  
      1. random brightness
      2. random contrast (mode 0)
      3. convert color from BGR to HSV
      4. random saturation
      5. random hue
      6. convert color from HSV to BGR
      7. random contrast (mode 1)
      8. randomly swap channels
  
      Args:
          brightness_delta (int): delta of brightness.
          contrast_range (tuple): range of contrast.
          saturation_range (tuple): range of saturation.
          hue_delta (int): delta of hue.
      """
  
      def __init__(self,
                   brightness_delta=32,
                   contrast_range=(0.5, 1.5),
                   saturation_range=(0.5, 1.5),
                   hue_delta=18):
          self.brightness_delta = brightness_delta
          self.contrast_lower, self.contrast_upper = contrast_range
          self.saturation_lower, self.saturation_upper = saturation_range
          self.hue_delta = hue_delta
  
      def __call__(self, results):
          if 'img_fields' in results:
              assert results['img_fields'] == ['img'], \
                  'Only single img_fields is allowed'
          img = results['img']
          assert img.dtype == np.float32, \
              'PhotoMetricDistortion needs the input image of dtype np.float32,'\
              ' please set "to_float32=True" in "LoadImageFromFile" pipeline'
          # random brightness
          if random.randint(2):
              delta = random.uniform(-self.brightness_delta,
                                     self.brightness_delta)
              img += delta
  
          # mode == 0 --> do random contrast first
          # mode == 1 --> do random contrast last
          mode = random.randint(2)
          if mode == 1:
              if random.randint(2):
                  alpha = random.uniform(self.contrast_lower,
                                         self.contrast_upper)
                  img *= alpha
  
          # convert color from BGR to HSV
          img = mmcv.bgr2hsv(img)
  
          # random saturation
          if random.randint(2):
              img[..., 1] *= random.uniform(self.saturation_lower,
                                            self.saturation_upper)
  
          # random hue
          if random.randint(2):
              img[..., 0] += random.uniform(-self.hue_delta, self.hue_delta)
              img[..., 0][img[..., 0] > 360] -= 360
              img[..., 0][img[..., 0] < 0] += 360
  
          # convert color from HSV to BGR
          img = mmcv.hsv2bgr(img)
  
          # random contrast
          if mode == 0:
              if random.randint(2):
                  alpha = random.uniform(self.contrast_lower,
                                         self.contrast_upper)
                  img *= alpha
  
          # randomly swap channels
          if random.randint(2):
              img = img[..., random.permutation(3)]
  
          results['img'] = img
          return results
  
      def __repr__(self):
          repr_str = self.__class__.__name__
          repr_str += f'(\nbrightness_delta={self.brightness_delta},\n'
          repr_str += 'contrast_range='
          repr_str += f'{(self.contrast_lower, self.contrast_upper)},\n'
          repr_str += 'saturation_range='
          repr_str += f'{(self.saturation_lower, self.saturation_upper)},\n'
          repr_str += f'hue_delta={self.hue_delta})'
          return repr_str
  
  
  @PIPELINES.register_module()
  class Expand(object):
      """Random expand the image & bboxes.
  
      Randomly place the original image on a canvas of 'ratio' x original image
      size filled with mean values. The ratio is in the range of ratio_range.
  
      Args:
          mean (tuple): mean value of dataset.
          to_rgb (bool): if need to convert the order of mean to align with RGB.
          ratio_range (tuple): range of expand ratio.
          prob (float): probability of applying this transformation
      """
  
      def __init__(self,
                   mean=(0, 0, 0),
                   to_rgb=True,
                   ratio_range=(1, 4),
                   seg_ignore_label=None,
                   prob=0.5):
          self.to_rgb = to_rgb
          self.ratio_range = ratio_range
          if to_rgb:
              self.mean = mean[::-1]
          else:
              self.mean = mean
          self.min_ratio, self.max_ratio = ratio_range
          self.seg_ignore_label = seg_ignore_label
          self.prob = prob
  
      def __call__(self, results):
          if random.uniform(0, 1) > self.prob:
              return results
  
          if 'img_fields' in results:
              assert results['img_fields'] == ['img'], \
                  'Only single img_fields is allowed'
          img = results['img']
  
          h, w, c = img.shape
          ratio = random.uniform(self.min_ratio, self.max_ratio)
          expand_img = np.full((int(h * ratio), int(w * ratio), c),
                               self.mean,
                               dtype=img.dtype)
          left = int(random.uniform(0, w * ratio - w))
          top = int(random.uniform(0, h * ratio - h))
          expand_img[top:top + h, left:left + w] = img
  
          results['img'] = expand_img
          # expand bboxes
          for key in results.get('bbox_fields', []):
              results[key] = results[key] + np.tile(
                  (left, top), 2).astype(results[key].dtype)
  
          # expand masks
          for key in results.get('mask_fields', []):
              results[key] = results[key].expand(
                  int(h * ratio), int(w * ratio), top, left)
  
          # expand segs
          for key in results.get('seg_fields', []):
              gt_seg = results[key]
              expand_gt_seg = np.full((int(h * ratio), int(w * ratio)),
                                      self.seg_ignore_label,
                                      dtype=gt_seg.dtype)
              expand_gt_seg[top:top + h, left:left + w] = gt_seg
              results[key] = expand_gt_seg
          return results
  
      def __repr__(self):
          repr_str = self.__class__.__name__
          repr_str += f'(mean={self.mean}, to_rgb={self.to_rgb}, '
          repr_str += f'ratio_range={self.ratio_range}, '
          repr_str += f'seg_ignore_label={self.seg_ignore_label})'
          return repr_str
  
  
  @PIPELINES.register_module()
  class MinIoURandomCrop(object):
      """Random crop the image & bboxes, the cropped patches have minimum IoU
      requirement with original image & bboxes, the IoU threshold is randomly
      selected from min_ious.
  
      Args:
          min_ious (tuple): minimum IoU threshold for all intersections with
          bounding boxes
          min_crop_size (float): minimum crop's size (i.e. h,w := a*h, a*w,
          where a >= min_crop_size).
  
      Notes:
          The keys for bboxes, labels and masks should be paired. That is,
          `gt_bboxes` corresponds to `gt_labels` and `gt_masks`, and
          `gt_bboxes_ignore` to `gt_labels_ignore` and `gt_masks_ignore`.
      """
  
      def __init__(self, min_ious=(0.1, 0.3, 0.5, 0.7, 0.9), min_crop_size=0.3):
          # 1: return ori img
          self.min_ious = min_ious
          self.sample_mode = (1, *min_ious, 0)
          self.min_crop_size = min_crop_size
          self.bbox2label = {
              'gt_bboxes': 'gt_labels',
              'gt_bboxes_ignore': 'gt_labels_ignore'
          }
          self.bbox2mask = {
              'gt_bboxes': 'gt_masks',
              'gt_bboxes_ignore': 'gt_masks_ignore'
          }
  
      def __call__(self, results):
          if 'img_fields' in results:
              assert results['img_fields'] == ['img'], \
                  'Only single img_fields is allowed'
          img = results['img']
          assert 'bbox_fields' in results
          boxes = [results[key] for key in results['bbox_fields']]
          boxes = np.concatenate(boxes, 0)
          h, w, c = img.shape
          while True:
              mode = random.choice(self.sample_mode)
              self.mode = mode
              if mode == 1:
                  return results
  
              min_iou = mode
              for i in range(50):
                  new_w = random.uniform(self.min_crop_size * w, w)
                  new_h = random.uniform(self.min_crop_size * h, h)
  
                  # h / w in [0.5, 2]
                  if new_h / new_w < 0.5 or new_h / new_w > 2:
                      continue
  
                  left = random.uniform(w - new_w)
                  top = random.uniform(h - new_h)
  
                  patch = np.array(
                      (int(left), int(top), int(left + new_w), int(top + new_h)))
                  # Line or point crop is not allowed
                  if patch[2] == patch[0] or patch[3] == patch[1]:
                      continue
                  overlaps = bbox_overlaps(
                      patch.reshape(-1, 4), boxes.reshape(-1, 4)).reshape(-1)
                  if len(overlaps) > 0 and overlaps.min() < min_iou:
                      continue
  
                  # center of boxes should inside the crop img
                  # only adjust boxes and instance masks when the gt is not empty
                  if len(overlaps) > 0:
                      # adjust boxes
                      def is_center_of_bboxes_in_patch(boxes, patch):
                          center = (boxes[:, :2] + boxes[:, 2:]) / 2
                          mask = ((center[:, 0] > patch[0]) *
                                  (center[:, 1] > patch[1]) *
                                  (center[:, 0] < patch[2]) *
                                  (center[:, 1] < patch[3]))
                          return mask
  
                      mask = is_center_of_bboxes_in_patch(boxes, patch)
                      if not mask.any():
                          continue
                      for key in results.get('bbox_fields', []):
                          boxes = results[key].copy()
                          mask = is_center_of_bboxes_in_patch(boxes, patch)
                          boxes = boxes[mask]
                          boxes[:, 2:] = boxes[:, 2:].clip(max=patch[2:])
                          boxes[:, :2] = boxes[:, :2].clip(min=patch[:2])
                          boxes -= np.tile(patch[:2], 2)
  
                          results[key] = boxes
                          # labels
                          label_key = self.bbox2label.get(key)
                          if label_key in results:
                              results[label_key] = results[label_key][mask]
  
                          # mask fields
                          mask_key = self.bbox2mask.get(key)
                          if mask_key in results:
                              results[mask_key] = results[mask_key][
                                  mask.nonzero()[0]].crop(patch)
                  # adjust the img no matter whether the gt is empty before crop
                  img = img[patch[1]:patch[3], patch[0]:patch[2]]
                  results['img'] = img
                  results['img_shape'] = img.shape
  
                  # seg fields
                  for key in results.get('seg_fields', []):
                      results[key] = results[key][patch[1]:patch[3],
                                                  patch[0]:patch[2]]
                  return results
  
      def __repr__(self):
          repr_str = self.__class__.__name__
          repr_str += f'(min_ious={self.min_ious}, '
          repr_str += f'min_crop_size={self.min_crop_size})'
          return repr_str
  
  
  @PIPELINES.register_module()
  class Corrupt(object):
  
      def __init__(self, corruption, severity=1):
          self.corruption = corruption
          self.severity = severity
  
      def __call__(self, results):
          if corrupt is None:
              raise RuntimeError('imagecorruptions is not installed')
          if 'img_fields' in results:
              assert results['img_fields'] == ['img'], \
                  'Only single img_fields is allowed'
          results['img'] = corrupt(
              results['img'].astype(np.uint8),
              corruption_name=self.corruption,
              severity=self.severity)
          return results
  
      def __repr__(self):
          repr_str = self.__class__.__name__
          repr_str += f'(corruption={self.corruption}, '
          repr_str += f'severity={self.severity})'
          return repr_str
  
  
  @PIPELINES.register_module()
  class Albu(object):
  
      def __init__(self,
                   transforms,
                   bbox_params=None,
                   keymap=None,
                   update_pad_shape=False,
                   skip_img_without_anno=False):
          """
          Adds custom transformations from Albumentations lib.
          Please, visit `https://albumentations.readthedocs.io`
          to get more information.
  
          transforms (list): list of albu transformations
          bbox_params (dict): bbox_params for albumentation `Compose`
          keymap (dict): contains {'input key':'albumentation-style key'}
          skip_img_without_anno (bool): whether to skip the image
                                        if no ann left after aug
          """
          if Compose is None:
              raise RuntimeError('albumentations is not installed')
  
          self.transforms = transforms
          self.filter_lost_elements = False
          self.update_pad_shape = update_pad_shape
          self.skip_img_without_anno = skip_img_without_anno
  
          # A simple workaround to remove masks without boxes
          if (isinstance(bbox_params, dict) and 'label_fields' in bbox_params
                  and 'filter_lost_elements' in bbox_params):
              self.filter_lost_elements = True
              self.origin_label_fields = bbox_params['label_fields']
              bbox_params['label_fields'] = ['idx_mapper']
              del bbox_params['filter_lost_elements']
  
          self.bbox_params = (
              self.albu_builder(bbox_params) if bbox_params else None)
          self.aug = Compose([self.albu_builder(t) for t in self.transforms],
                             bbox_params=self.bbox_params)
  
          if not keymap:
              self.keymap_to_albu = {
                  'img': 'image',
                  'gt_masks': 'masks',
                  'gt_bboxes': 'bboxes'
              }
          else:
              self.keymap_to_albu = keymap
          self.keymap_back = {v: k for k, v in self.keymap_to_albu.items()}
  
      def albu_builder(self, cfg):
          """Import a module from albumentations.
          Inherits some of `build_from_cfg` logic.
  
          Args:
              cfg (dict): Config dict. It should at least contain the key "type".
          Returns:
              obj: The constructed object.
          """
          assert isinstance(cfg, dict) and 'type' in cfg
          args = cfg.copy()
  
          obj_type = args.pop('type')
          if mmcv.is_str(obj_type):
              if albumentations is None:
                  raise RuntimeError('albumentations is not installed')
              obj_cls = getattr(albumentations, obj_type)
          elif inspect.isclass(obj_type):
              obj_cls = obj_type
          else:
              raise TypeError(
                  f'type must be a str or valid type, but got {type(obj_type)}')
  
          if 'transforms' in args:
              args['transforms'] = [
                  self.albu_builder(transform)
                  for transform in args['transforms']
              ]
  
          return obj_cls(**args)
  
      @staticmethod
      def mapper(d, keymap):
          """
          Dictionary mapper.
          Renames keys according to keymap provided.
  
          Args:
              d (dict): old dict
              keymap (dict): {'old_key':'new_key'}
          Returns:
              dict: new dict.
          """
          updated_dict = {}
          for k, v in zip(d.keys(), d.values()):
              new_k = keymap.get(k, k)
              updated_dict[new_k] = d[k]
          return updated_dict
  
      def __call__(self, results):
          # dict to albumentations format
          results = self.mapper(results, self.keymap_to_albu)
          # TODO: add bbox_fields
          if 'bboxes' in results:
              # to list of boxes
              if isinstance(results['bboxes'], np.ndarray):
                  results['bboxes'] = [x for x in results['bboxes']]
              # add pseudo-field for filtration
              if self.filter_lost_elements:
                  results['idx_mapper'] = np.arange(len(results['bboxes']))
  
          # TODO: Support mask structure in albu
          if 'masks' in results:
              if isinstance(results['masks'], PolygonMasks):
                  raise NotImplementedError(
                      'Albu only supports BitMap masks now')
              ori_masks = results['masks']
              results['masks'] = results['masks'].masks
  
          results = self.aug(**results)
  
          if 'bboxes' in results:
              if isinstance(results['bboxes'], list):
                  results['bboxes'] = np.array(
                      results['bboxes'], dtype=np.float32)
              results['bboxes'] = results['bboxes'].reshape(-1, 4)
  
              # filter label_fields
              if self.filter_lost_elements:
  
                  for label in self.origin_label_fields:
                      results[label] = np.array(
                          [results[label][i] for i in results['idx_mapper']])
                  if 'masks' in results:
                      results['masks'] = np.array(
                          [results['masks'][i] for i in results['idx_mapper']])
                      results['masks'] = ori_masks.__class__(
                          results['masks'], results['image'].shape[0],
                          results['image'].shape[1])
  
                  if (not len(results['idx_mapper'])
                          and self.skip_img_without_anno):
                      return None
  
          if 'gt_labels' in results:
              if isinstance(results['gt_labels'], list):
                  results['gt_labels'] = np.array(results['gt_labels'])
              results['gt_labels'] = results['gt_labels'].astype(np.int64)
  
          # back to the original format
          results = self.mapper(results, self.keymap_back)
  
          # update final shape
          if self.update_pad_shape:
              results['pad_shape'] = results['img'].shape
  
          return results
  
      def __repr__(self):
          repr_str = self.__class__.__name__ + f'(transforms={self.transforms})'
          return repr_str
  