code: #!/usr/bin/env python3
  # -*- coding: utf-8 -*-
  """
  Created on Mon Apr 29 18:56:23 2019
  
  @author: pengming
  """
  
  # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  import torch
  
  # transpose
  FLIP_LEFT_RIGHT = 0
  FLIP_TOP_BOTTOM = 1
  
  from .bounding_box import BoxList as bbox2
  from maskrcnn_benchmark.data.datasets.DOTA_devkit.dota_utils import dots4ToRec4
  
  
  
  def _dots8ToRec4(poly):
      poly = poly.view(poly.size(0), 4, 2)
      xmin, xmax, ymin, ymax = torch.min(poly[:,0,0], torch.min(poly[:,1,0], torch.min(poly[:,2,0], poly[:,3,0]))), \
                               torch.max(poly[:,0,0], torch.max(poly[:,1,0], torch.max(poly[:,2,0], poly[:,3,0]))), \
                               torch.min(poly[:,0,1], torch.min(poly[:,1,1], torch.min(poly[:,2,1], poly[:,3,1]))), \
                               torch.max(poly[:,0,1], torch.max(poly[:,1,1], torch.max(poly[:,2,1], poly[:,3,1])))
      poly_box = torch.cat([xmin.unsqueeze(1), ymin.unsqueeze(1), xmax.unsqueeze(1) - xmin.unsqueeze(1), ymax.unsqueeze(1) - ymin.unsqueeze(1)], dim=1)
      return poly_box
  
  
  
  class BoxList(object):
      """
      This class represents a set of bounding boxes.
      The bounding boxes are represented as a Nx4 Tensor.
      In order to uniquely determine the bounding boxes with respect
      to an image, we also store the corresponding image dimensions.
      They can contain extra information that is specific to each bounding box, such as
      labels.
      """
  
      def __init__(self, bbox, image_size, mode="xyxy"):
          device = bbox.device if isinstance(bbox, torch.Tensor) else torch.device("cpu")
          bbox = torch.as_tensor(bbox, dtype=torch.float32, device=device)
          if bbox.ndimension() != 2:
              raise ValueError(
                  "bbox should have 2 dimensions, got {}".format(bbox.ndimension())
              )
          if bbox.size(-1) != 8 and bbox.size(-1) != 4:
              raise ValueError(
                  "last dimension of bbox should have a "
                  "size of 10 or 4, got {}".format(bbox.size(-1))
              )
          if mode not in ("xyxy", "xywh", "xxyy", "xxyy_", "xyxy_"):
              raise ValueError("mode should be 'xyxy' ")
  
          self.bbox = bbox
          #self.temp_bbox = self._dots8ToRec4(self.bbox)
          self.size = image_size  # (image_width, image_height)
          self.mode = mode
          self.extra_fields = {}
          
  
      def add_field(self, field, field_data):
          self.extra_fields[field] = field_data
  
      def get_field(self, field):
          return self.extra_fields[field]
  
      def has_field(self, field):
          return field in self.extra_fields
  
      def fields(self):
          return list(self.extra_fields.keys())
  
      def _copy_extra_fields(self, bbox):
          for k, v in bbox.extra_fields.items():
              self.extra_fields[k] = v
              
      def center(self):
          x1, y1, x2, y2, x3, y3, x4, y4 = self._split_into_xyxy()
          minx = torch.stack([x1, x2, x3, x4], dim=1).min(dim=1)[0]
          maxx = torch.stack([x1, x2, x3, x4], dim=1).max(dim=1)[0]
          miny = torch.stack([y1, y2, y3, y4], dim=1).min(dim=1)[0]
          maxy = torch.stack([y1, y2, y3, y4], dim=1).max(dim=1)[0]
          centerx = (minx + maxx)/2
          centery = (miny + maxy)/2
          center = torch.stack([centerx, centery],dim=1)
          return center
  
      def convert(self, mode):
          if mode not in ("xyxy", "xywh", "xxyy", "xyxy_", "xxyy_"):
              raise ValueError("mode should be 'xyxy' or 'xywh'")
  #        if mode == self.mode:
  #            return self
          # we only have two modes, so don't need to check
          # self.mode
          
          #if mode == "xyxy_":
          temp = self.bbox
          tempbbox = _dots8ToRec4(temp)
          return  BoxList(tempbbox, self.size, mode="xyxy_")
  
      def _split_into_xyxy(self):
          if self.mode == "xyxy":
              #xmin, ymin, xmax, ymax = self.bbox.split(1, dim=-1)
              x1, y1, x2, y2, x3, y3, x4, y4 = (self.bbox[:,0], self.bbox[:,1], 
                                               self.bbox[:,2], self.bbox[:,3],
                                               self.bbox[:,4], self.bbox[:,5], 
                                               self.bbox[:,6], self.bbox[:,7])
                                               
              return x1, y1, x2, y2, x3, y3, x4, y4
          else:
              raise RuntimeError("xywh Should not be here")
              
      def _dots8ToRec4_(self, poly):
          poly = poly.view(poly.size(0), 4, 2)
          xmin, xmax, ymin, ymax = torch.min(poly[:,0,0], torch.min(poly[:,1,0], torch.min(poly[:,2,0], poly[:,3,0]))), \
                                   torch.max(poly[:,0,0], torch.max(poly[:,1,0], torch.max(poly[:,2,0], poly[:,3,0]))), \
                                   torch.min(poly[:,0,1], torch.min(poly[:,1,1], torch.min(poly[:,2,1], poly[:,3,1]))), \
                                   torch.max(poly[:,0,1], torch.max(poly[:,1,1], torch.max(poly[:,2,1], poly[:,3,1])))
          poly_box = torch.cat([xmin.unsqueeze(1), xmax.unsqueeze(1), ymin.unsqueeze(1), ymax.unsqueeze(1)], dim=1)
          #poly_box = torch.cat([xmin.unsqueeze(1), ymin.unsqueeze(1), xmax.unsqueeze(1) - xmin.unsqueeze(1), ymax.unsqueeze(1) - ymin.unsqueeze(1)], dim=1)
          bbox = BoxList(poly_box, self.size, mode="xyxy")
          # bbox._copy_extra_fields(self)
          for k, v in self.extra_fields.items():
              if not isinstance(v, torch.Tensor):
                  v = v._dots8ToRec4(poly)
              bbox.add_field(k, v)
  
          return bbox
  
      def resize(self, size, *args, **kwargs):
          """
          Returns a resized copy of this bounding box
  
          :param size: The requested size in pixels, as a 2-tuple:
              (width, height).
          """
  
          ratios = tuple(float(s) / float(s_orig) for s, s_orig in zip(size, self.size))
          if ratios[0] == ratios[1]:
              ratio = ratios[0]
              scaled_box = self.bbox * ratio
              bbox = BoxList(scaled_box, size, mode=self.mode)
              # bbox._copy_extra_fields(self)
              for k, v in self.extra_fields.items():
                  if not isinstance(v, torch.Tensor):
                      v = v.resize(size, *args, **kwargs)
                  bbox.add_field(k, v)
              return bbox
  
          ratio_width, ratio_height = ratios
          x1, y1, x2, y2, x3, y3, x4, y4 = self._split_into_xyxy()
          num_boxs = x4.size(0)
          scaled_x1 = x1 * ratio_width
          scaled_y1 = y1 * ratio_height
          scaled_x2 = x2 * ratio_width
          scaled_y2 = y2 * ratio_height
          scaled_x3 = x3 * ratio_width
          scaled_y3 = y3 * ratio_height
          scaled_x4 = x4 * ratio_width
          scaled_y4 = y4 * ratio_height
  #        scaled_box = torch.cat(
  #            (scaled_xmin, scaled_ymin, scaled_xmax, scaled_ymax), dim=-1
  #        )
  #        x1y1 = torch.cat((scaled_x1.view(x1.size(0),-1), scaled_y1.view(y1.size(0),-1)), dim=1)
  #        x2y2 = torch.cat((scaled_x2.view(x2.size(0),-1), scaled_y2.view(y2.size(0),-1)), dim=1)
  #        x3y3 = torch.cat((scaled_x3.view(x3.size(0),-1), scaled_y3.view(y3.size(0),-1)), dim=1)
  #        x4y4 = torch.cat((scaled_x4.view(x4.size(0),-1), scaled_y4.view(y4.size(0),-1)), dim=1)
  #        
  #        cat_xy = torch.cat((x1y1, x2y2, x3y3, x4y4), dim=-1)
  #        scaled_box = cat_xy.view(cat_xy.size(0), 4, 2)
          scaled_box = torch.cat((scaled_x1.view(num_boxs,-1), 
                                  scaled_y1.view(num_boxs,-1), 
                                  scaled_x2.view(num_boxs,-1), 
                                  scaled_y2.view(num_boxs,-1), 
                                  scaled_x3.view(num_boxs,-1), 
                                  scaled_y3.view(num_boxs,-1), 
                                  scaled_x4.view(num_boxs,-1), 
                                  scaled_y4.view(num_boxs,-1)), dim=1)
          bbox = BoxList(scaled_box, size, mode="xyxy")
          # bbox._copy_extra_fields(self)
          for k, v in self.extra_fields.items():
              if not isinstance(v, torch.Tensor):
                  v = v.resize(size, *args, **kwargs)
              bbox.add_field(k, v)
  
          return bbox
  
      def transpose(self, method):
          """
          Transpose bounding box (flip or rotate in 90 degree steps)
          :param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`,
            :py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`,
            :py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270`,
            :py:attr:`PIL.Image.TRANSPOSE` or :py:attr:`PIL.Image.TRANSVERSE`.
          """
          if method not in (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM):
              raise NotImplementedError(
                  "Only FLIP_LEFT_RIGHT and FLIP_TOP_BOTTOM implemented"
              )
  
          image_width, image_height = self.size
          x1, y1, x2, y2, x3, y3, x4, y4 = self._split_into_xyxy()
          if method == FLIP_LEFT_RIGHT:
              TO_REMOVE = 0
              transposed_x1 = image_width - x1 - TO_REMOVE
              transposed_x2 = image_width - x2 - TO_REMOVE
              transposed_x3 = image_width - x3 - TO_REMOVE
              transposed_x4 = image_width - x4 - TO_REMOVE
              transposed_y1 = y1
              transposed_y2 = y2
              transposed_y3 = y3
              transposed_y4 = y4
          elif method == FLIP_TOP_BOTTOM:
              transposed_x1 = x1
              transposed_x2 = x2
              transposed_x3 = x3
              transposed_x4 = x4
              transposed_y1 = image_height - y1
              transposed_y2 = image_height - y2
              transposed_y3 = image_height - y3
              transposed_y4 = image_height - y4
  
  
          num_boxs = x4.size(0)
  #        transposed_boxes = torch.cat(
  #            (transposed_xmin, transposed_ymin, transposed_xmax, transposed_ymax), dim=-1
  #        )
  #        x1y1 = torch.cat((transposed_x1.view(x1.size(0),-1), transposed_y1.view(y1.size(0),-1)), dim=1)
  #        x2y2 = torch.cat((transposed_x2.view(x2.size(0),-1), transposed_y2.view(y2.size(0),-1)), dim=1)
  #        x3y3 = torch.cat((transposed_x3.view(x3.size(0),-1), transposed_y3.view(y3.size(0),-1)), dim=1)
  #        x4y4 = torch.cat((transposed_x4.view(x4.size(0),-1), transposed_y4.view(y4.size(0),-1)), dim=1)
          
          transposed_boxes = torch.cat((transposed_x1.view(num_boxs,-1), 
                                        transposed_y1.view(num_boxs,-1), 
                                        transposed_x2.view(num_boxs,-1), 
                                        transposed_y2.view(num_boxs,-1), 
                                        transposed_x3.view(num_boxs,-1), 
                                        transposed_y3.view(num_boxs,-1), 
                                        transposed_x4.view(num_boxs,-1), 
                                        transposed_y4.view(num_boxs,-1)), dim=1)
          
  #        cat_xy = torch.cat((x1y1, x2y2, x3y3, x4y4), dim=-1)
          #transposed_boxes = cat_xy.view(cat_xy.size(0), 4, 2)
          bbox = BoxList(transposed_boxes, self.size, mode="xyxy")
          # bbox._copy_extra_fields(self)
          for k, v in self.extra_fields.items():
              if not isinstance(v, torch.Tensor):
                  v = v.transpose(method)
              bbox.add_field(k, v)
          return bbox
  
  #    def crop(self, box):
  #        """
  #        Cropss a rectangular region from this bounding box. The box is a
  #        4-tuple defining the left, upper, right, and lower pixel
  #        coordinate.
  #        """
  #        x1, y1, x2, y2, x3, y3, x4, y4 = self._split_into_xyxy()
  #        w, h = box[2] - box[0], box[3] - box[1]
  #        cropped_xmin = (xmin - box[0]).clamp(min=0, max=w)
  #        cropped_ymin = (ymin - box[1]).clamp(min=0, max=h)
  #        cropped_xmax = (xmax - box[0]).clamp(min=0, max=w)
  #        cropped_ymax = (ymax - box[1]).clamp(min=0, max=h)
  #
  #        # TODO should I filter empty boxes here?
  #        if False:
  #            is_empty = (cropped_xmin == cropped_xmax) | (cropped_ymin == cropped_ymax)
  #
  #        cropped_box = torch.cat(
  #            (cropped_xmin, cropped_ymin, cropped_xmax, cropped_ymax), dim=-1
  #        )
  #        bbox = BoxList(cropped_box, (w, h), mode="xyxy")
  #        # bbox._copy_extra_fields(self)
  #        for k, v in self.extra_fields.items():
  #            if not isinstance(v, torch.Tensor):
  #                v = v.crop(box)
  #            bbox.add_field(k, v)
  #        print("don't use!! crop")
  #        return bbox.convert(self.mode)
  
      # Tensor-like methods
  
      def to(self, device):
          bbox = BoxList(self.bbox.to(device), self.size, self.mode)
          for k, v in self.extra_fields.items():
              if hasattr(v, "to"):
                  v = v.to(device)
              bbox.add_field(k, v)
          return bbox
  
      def __getitem__(self, item):
          bbox = BoxList(self.bbox[item], self.size, self.mode)
          for k, v in self.extra_fields.items():
              bbox.add_field(k, v[item])
          return bbox
  
      def __len__(self):
          return self.bbox.shape[0]
  
      def clip_to_image(self, remove_empty=True):
          TO_REMOVE = 1
          self.bbox[:,0].clamp_(min=0, max=self.size[0] - TO_REMOVE)
          self.bbox[:,1].clamp_(min=0, max=self.size[1] - TO_REMOVE)
          self.bbox[:,2].clamp_(min=0, max=self.size[0] - TO_REMOVE)
          self.bbox[:,3].clamp_(min=0, max=self.size[1] - TO_REMOVE)
          self.bbox[:,4].clamp_(min=0, max=self.size[0] - TO_REMOVE)
          self.bbox[:,5].clamp_(min=0, max=self.size[1] - TO_REMOVE)
          self.bbox[:,6].clamp_(min=0, max=self.size[0] - TO_REMOVE)
          self.bbox[:,7].clamp_(min=0, max=self.size[1] - TO_REMOVE)
  #        if remove_empty:
  #            box = self.bbox
  #            #keep = (box[:, 3] > box[:, 1]) & (box[:, 2] > box[:, 0])
  #            keep = (box[:,2,0] > box[:,0,0]) & (
  #                    box[:,3,1] > box[:,1,1]) & (
  #            return self[keep]
          return self
  
      def area(self):
          box = self.bbox
          if self.mode == "xyxy":
              area = self.extra_fields['areas']
          else:
              raise RuntimeError("Should not be here")
  
          return area
  
      def copy_with_fields(self, fields, skip_missing=False):
          bbox = BoxList(self.bbox, self.size, self.mode)
          if not isinstance(fields, (list, tuple)):
              fields = [fields]
          for field in fields:
              if self.has_field(field):
                  bbox.add_field(field, self.get_field(field))
              elif not skip_missing:
                  raise KeyError("Field '{}' not found in {}".format(field, self))
          return bbox
  
      def __repr__(self):
          s = self.__class__.__name__ + "("
          s += "num_boxes={}, ".format(len(self))
          s += "image_width={}, ".format(self.size[0])
          s += "image_height={}, ".format(self.size[1])
          s += "mode={})".format(self.mode)
          return s
  
  
  if __name__ == "__main__":
      bbox = BoxList([[0, 0, 10, 10], [0, 0, 5, 5]], (10, 10))
      s_bbox = bbox.resize((5, 5))
      print(s_bbox)
      print(s_bbox.bbox)
  
      t_bbox = bbox.transpose(0)
      print(t_bbox)
      print(t_bbox.bbox)
  
apis: torch.min torch.stack torch.as_tensor torch.max torch.device