text: # -*- coding: utf-8 -*-
  # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
  # File: transform.py
  
  import numpy as np
  import torch
  import torch.nn.functional as F
  from fvcore.transforms.transform import (
      CropTransform,
      HFlipTransform,
      NoOpTransform,
      Transform,
      TransformList,
  )
  from PIL import Image
  import cv2
  
  try:
      import cv2  # noqa
  except ImportError:
      # OpenCV is an optional dependency at the moment
      pass
  
  __all__ = ["ExtentTransform", "ResizeTransform", "RotationTransform"]
  
  
  class ExtentTransform(Transform):
      """
      Extracts a subregion from the source image and scales it to the output size.
  
      The fill color is used to map pixels from the source rect that fall outside
      the source image.
  
      See: https://pillow.readthedocs.io/en/latest/PIL.html#PIL.ImageTransform.ExtentTransform
      """
  
      def __init__(self, src_rect, output_size, interp=Image.LINEAR, fill=0):
          """
          Args:
              src_rect (x0, y0, x1, y1): src coordinates
              output_size (h, w): dst image size
              interp: PIL interpolation methods
              fill: Fill color used when src_rect extends outside image
          """
          super().__init__()
          self._set_attributes(locals())
  
      def apply_image(self, img, interp=None):
          h, w = self.output_size
          ret = Image.fromarray(img).transform(
              size=(w, h),
              method=Image.EXTENT,
              data=self.src_rect,
              resample=interp if interp else self.interp,
              fill=self.fill,
          )
          return np.asarray(ret)
  
      def apply_coords(self, coords):
          # Transform image center from source coordinates into output coordinates
          # and then map the new origin to the corner of the output image.
          h, w = self.output_size
          x0, y0, x1, y1 = self.src_rect
          new_coords = coords.astype(np.float32)
          new_coords[:, 0] -= 0.5 * (x0 + x1)
          new_coords[:, 1] -= 0.5 * (y0 + y1)
          new_coords[:, 0] *= w / (x1 - x0)
          new_coords[:, 1] *= h / (y1 - y0)
          new_coords[:, 0] += 0.5 * w
          new_coords[:, 1] += 0.5 * h
          return new_coords
  
      def apply_segmentation(self, segmentation):
          segmentation = self.apply_image(segmentation, interp=Image.NEAREST)
          return segmentation
  
  
  class ResizeTransform(Transform):
      """
      Resize the image to a target size.
      """
  
      def __init__(self, h, w, new_h, new_w, interp=None):
          """
          Args:
              h, w (int): original image size
              new_h, new_w (int): new image size
              interp: PIL interpolation methods, defaults to bilinear.
          """
          # TODO decide on PIL vs opencv
          super().__init__()
          if interp is None:
              interp = Image.BILINEAR
          self._set_attributes(locals())
  
      def apply_image(self, img, interp=None):
          assert img.shape[:2] == (self.h, self.w)
          #pil_image = Image.fromarray(img)
          interp_method = interp if interp is not None else self.interp
          #pil_image = pil_image.resize((self.new_w, self.new_h), interp_method)
          ret = cv2.resize(img, dsize=(self.new_w, self.new_h), interpolation=cv2.INTER_LINEAR)
          #ret = np.asarray(pil_image)
          return ret
  
      def apply_coords(self, coords):
          coords[:, 0] = coords[:, 0] * (self.new_w * 1.0 / self.w)
          coords[:, 1] = coords[:, 1] * (self.new_h * 1.0 / self.h)
          return coords
  
      def apply_segmentation(self, segmentation):
          segmentation = self.apply_image(segmentation, interp=Image.NEAREST)
          return segmentation
  
      def inverse(self):
          return ResizeTransform(self.new_h, self.new_w, self.h, self.w, self.interp)
  
  
  class RotationTransform(Transform):
      """
      This method returns a copy of this image, rotated the given
      number of degrees counter clockwise around its center.
      """
  
      def __init__(self, h, w, angle, expand=True, center=None, interp=None):
          """
          Args:
              h, w (int): original image size
              angle (float): degrees for rotation
              expand (bool): choose if the image should be resized to fit the whole
                  rotated image (default), or simply cropped
              center (tuple (width, height)): coordinates of the rotation center
                  if left to None, the center will be fit to the center of each image
                  center has no effect if expand=True because it only affects shifting
              interp: cv2 interpolation method, default cv2.INTER_LINEAR
          """
          super().__init__()
          image_center = np.array((w / 2, h / 2))
          if center is None:
              center = image_center
          if interp is None:
              interp = cv2.INTER_LINEAR
          abs_cos, abs_sin = (abs(np.cos(np.deg2rad(angle))), abs(np.sin(np.deg2rad(angle))))
          if expand:
              # find the new width and height bounds
              bound_w, bound_h = np.rint(
                  [h * abs_sin + w * abs_cos, h * abs_cos + w * abs_sin]
              ).astype(int)
          else:
              bound_w, bound_h = w, h
  
          self._set_attributes(locals())
          self.rm_coords = self.create_rotation_matrix()
          # Needed because of this problem https://github.com/opencv/opencv/issues/11784
          self.rm_image = self.create_rotation_matrix(offset=-0.5)
  
      def apply_image(self, img, interp=None):
          """
          img should be a numpy array, formatted as Height * Width * Nchannels
          """
          if len(img) == 0 or self.angle % 360 == 0:
              return img
          assert img.shape[:2] == (self.h, self.w)
          interp = interp if interp is not None else self.interp
          return cv2.warpAffine(img, self.rm_image, (self.bound_w, self.bound_h), flags=interp)
  
      def apply_coords(self, coords):
          """
          coords should be a N * 2 array-like, containing N couples of (x, y) points
          """
          coords = np.asarray(coords, dtype=float)
          if len(coords) == 0 or self.angle % 360 == 0:
              return coords
          return cv2.transform(coords[:, np.newaxis, :], self.rm_coords)[:, 0, :]
  
      def apply_segmentation(self, segmentation):
          segmentation = self.apply_image(segmentation, interp=cv2.INTER_NEAREST)
          return segmentation
  
      def create_rotation_matrix(self, offset=0):
          center = (self.center[0] + offset, self.center[1] + offset)
          rm = cv2.getRotationMatrix2D(tuple(center), self.angle, 1)
          if self.expand:
              # Find the coordinates of the center of rotation in the new image
              # The only point for which we know the future coordinates is the center of the image
              rot_im_center = cv2.transform(self.image_center[None, None, :] + offset, rm)[0, 0, :]
              new_center = np.array([self.bound_w / 2, self.bound_h / 2]) + offset - rot_im_center
              # shift the rotation center to the new coordinates
              rm[:, 2] += new_center
          return rm
  
      def inverse(self):
          """
          The inverse is to rotate it back with expand, and crop to get the original shape.
          """
          if not self.expand:  # Not possible to inverse if a part of the image is lost
              raise NotImplementedError()
          rotation = RotationTransform(
              self.bound_h, self.bound_w, -self.angle, True, None, self.interp
          )
          crop = CropTransform(
              (rotation.bound_w - self.w) // 2, (rotation.bound_h - self.h) // 2, self.w, self.h
          )
          return TransformList([rotation, crop])
  
  
  def HFlip_rotated_box(transform, rotated_boxes):
      """
      Apply the horizontal flip transform on rotated boxes.
  
      Args:
          rotated_boxes (ndarray): Nx5 floating point array of
              (x_center, y_center, width, height, angle_degrees) format
              in absolute coordinates.
      """
      # Transform x_center
      rotated_boxes[:, 0] = transform.width - rotated_boxes[:, 0]
      # Transform angle
      rotated_boxes[:, 4] = -rotated_boxes[:, 4]
      return rotated_boxes
  
  
  def Resize_rotated_box(transform, rotated_boxes):
      """
      Apply the resizing transform on rotated boxes. For details of how these (approximation)
      formulas are derived, please refer to :meth:`RotatedBoxes.scale`.
  
      Args:
          rotated_boxes (ndarray): Nx5 floating point array of
              (x_center, y_center, width, height, angle_degrees) format
              in absolute coordinates.
      """
      scale_factor_x = transform.new_w * 1.0 / transform.w
      scale_factor_y = transform.new_h * 1.0 / transform.h
      rotated_boxes[:, 0] *= scale_factor_x
      rotated_boxes[:, 1] *= scale_factor_y
      theta = rotated_boxes[:, 4] * np.pi / 180.0
      c = np.cos(theta)
      s = np.sin(theta)
      rotated_boxes[:, 2] *= np.sqrt(np.square(scale_factor_x * c) + np.square(scale_factor_y * s))
      rotated_boxes[:, 3] *= np.sqrt(np.square(scale_factor_x * s) + np.square(scale_factor_y * c))
      rotated_boxes[:, 4] = np.arctan2(scale_factor_x * s, scale_factor_y * c) * 180 / np.pi
  
      return rotated_boxes
  
  
  HFlipTransform.register_type("rotated_box", HFlip_rotated_box)
  ResizeTransform.register_type("rotated_box", Resize_rotated_box)
  
  # not necessary any more with latest fvcore
  NoOpTransform.register_type("rotated_box", lambda t, x: x)
  