text: # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  # Copyright 2021 Toyota Research Institute.  All rights reserved.
  # Adapted from detectron2:
  #   https://github.com/facebookresearch/detectron2/blob/master/detectron2/data/detection_utils.py
  import numpy as np
  import torch
  
  from detectron2.data import transforms as T
  from detectron2.structures import Boxes, BoxMode, Instances
  
  from tridet.structures.boxes3d import Boxes3D
  
  __all__ = ["transform_instance_annotations", "annotations_to_instances"]
  
  
  def transform_instance_annotations(
      annotation,
      transforms,
      image_size,
  ):
      """Adapted from:
          https://github.com/facebookresearch/detectron2/blob/master/detectron2/data/detection_utils.py#L254
  
      The changes from original:
          - The presence of 2D bounding box (i.e. "bbox" field) is assumed by default in d2; here it's optional.
          - Add optional 3D bounding box support.
          - If the instance mask annotation is in RLE, then it's decoded into polygons, not bitmask, to save memory.
  
      ===============================================================================================================
  
      Apply transforms to box, segmentation and keypoints annotations of a single instance.
  
      It will use `transforms.apply_box` for the box, and
      `transforms.apply_coords` for segmentation polygons & keypoints.
      If you need anything more specially designed for each data structure,
      you'll need to implement your own version of this function or the transforms.
  
      Args:
          annotation (dict): dict of instance annotations for a single instance.
              It will be modified in-place.
          transforms (TransformList or list[Transform]):
          image_size (tuple): the height, width of the transformed image
          keypoint_hflip_indices (ndarray[int]): see `create_keypoint_hflip_indices`.
  
      Returns:
          dict:
              the same input dict with fields "bbox", "segmentation", "keypoints"
              transformed according to `transforms`.
              The "bbox_mode" field will be set to XYXY_ABS.
      """
      if isinstance(transforms, (tuple, list)):
          transforms = T.TransformList(transforms)
      # (dennis.park) Here 2D bounding box is optional.
      if "bbox" in annotation:
          assert "bbox_mode" in annotation, "'bbox' is present, but 'bbox_mode' is not."
          # bbox is 1d (per-instance bounding box)
          bbox = BoxMode.convert(annotation["bbox"], annotation["bbox_mode"], BoxMode.XYXY_ABS)
          bbox = transforms.apply_box(np.array([bbox]))[0]
          # clip transformed bbox to image size
          bbox = bbox.clip(min=0)
          bbox = np.minimum(bbox, list(image_size + image_size)[::-1])
          annotation["bbox"] = bbox
          annotation["bbox_mode"] = BoxMode.XYXY_ABS
  
      # Vertical flipping is not implemented (`flip_transform.py`). TODO: implement if needed.
      if "bbox3d" in annotation:
          bbox3d = np.array(annotation["bbox3d"])
          annotation['bbox3d'] = transforms.apply_box3d(bbox3d)
  
      return annotation
  
  
  def _create_empty_instances(image_size):
      target = Instances(image_size)
  
      target.gt_boxes = Boxes([])
      target.gt_classes = torch.tensor([], dtype=torch.int64)
      target.gt_boxes3d = Boxes3D.from_vectors([], torch.eye(3, dtype=torch.float32))
  
      return target
  
  
  def annotations_to_instances(
      annos,
      image_size,
      intrinsics=None,
  ):
      """
      Create an :class:`Instances` object used by the models,
      from instance annotations in the dataset dict.
  
      Args:
          annos (list[dict]): a list of instance annotations in one image, each
              element for one instance.
          image_size (tuple): height, width
  
      Returns:
          Instances:
              It will contain fields "gt_boxes", "gt_classes",
              "gt_masks", "gt_keypoints", if they can be obtained from `annos`.
              This is the format that builtin models expect.
      """
      if len(annos) == 0:
          return _create_empty_instances(image_size)
  
      boxes = [BoxMode.convert(obj["bbox"], obj["bbox_mode"], BoxMode.XYXY_ABS) for obj in annos]
      target = Instances(image_size)
      target.gt_boxes = Boxes(boxes)
  
      classes = [obj["category_id"] for obj in annos]
      classes = torch.tensor(classes, dtype=torch.int64)
      target.gt_classes = classes
  
      if len(annos) and "bbox3d" in annos[0]:
          assert intrinsics is not None
          target.gt_boxes3d = Boxes3D.from_vectors([anno['bbox3d'] for anno in annos], intrinsics)
          if len(target.gt_boxes3d) != target.gt_boxes.tensor.shape[0]:
              raise ValueError(
                  f"The sizes of `gt_boxes3d` and `gt_boxes` do not match: a={len(target.gt_boxes3d)}, b={target.gt_boxes.tensor.shape[0]}."
              )
  
      return target
  