text: # --------------------------------------------------------
  # Fully Convolutional Instance-aware Semantic Segmentation
  # Copyright (c) 2016 by Contributors
  # Copyright (c) 2017 Microsoft
  # Licensed under The Apache-2.0 License [see LICENSE for details]
  # Modified by Yuwen Xiong, Haozhi Qi, Guodong Zhang
  # --------------------------------------------------------
  
  import numpy as np
  from lib.bbox.bbox import bbox_overlaps_cython
  
  
  def bbox_overlaps(boxes, query_boxes):
      return bbox_overlaps_cython(boxes, query_boxes)
  
  
  def bbox_overlaps_py(boxes, query_boxes):
      """
      determine overlaps between boxes and query_boxes
      :param boxes: n * 4 bounding boxes
      :param query_boxes: k * 4 bounding boxes
      :return: overlaps: n * k overlaps
      """
      n_ = boxes.shape[0]
      k_ = query_boxes.shape[0]
      overlaps = np.zeros((n_, k_), dtype=np.float)
      for k in range(k_):
          query_box_area = (query_boxes[k, 2] - query_boxes[k, 0] + 1) * (query_boxes[k, 3] - query_boxes[k, 1] + 1)
          for n in range(n_):
              iw = min(boxes[n, 2], query_boxes[k, 2]) - max(boxes[n, 0], query_boxes[k, 0]) + 1
              if iw > 0:
                  ih = min(boxes[n, 3], query_boxes[k, 3]) - max(boxes[n, 1], query_boxes[k, 1]) + 1
                  if ih > 0:
                      box_area = (boxes[n, 2] - boxes[n, 0] + 1) * (boxes[n, 3] - boxes[n, 1] + 1)
                      all_area = float(box_area + query_box_area - iw * ih)
                      overlaps[n, k] = iw * ih / all_area
      return overlaps
  
  
  def clip_boxes(boxes, im_shape):
      """
      Clip boxes to image boundaries.
      :param boxes: [N, 4* num_classes]
      :param im_shape: tuple of 2
      :return: [N, 4* num_classes]
      """
      # x1 >= 0
      boxes[:, 0::4] = np.maximum(np.minimum(boxes[:, 0::4], im_shape[1] - 1), 0)
      # y1 >= 0
      boxes[:, 1::4] = np.maximum(np.minimum(boxes[:, 1::4], im_shape[0] - 1), 0)
      # x2 < im_shape[1]
      boxes[:, 2::4] = np.maximum(np.minimum(boxes[:, 2::4], im_shape[1] - 1), 0)
      # y2 < im_shape[0]
      boxes[:, 3::4] = np.maximum(np.minimum(boxes[:, 3::4], im_shape[0] - 1), 0)
      return boxes
  
  
  def filter_boxes(boxes, min_size):
      """
      filter small boxes.
      :param boxes: [N, 4* num_classes]
      :param min_size:
      :return: keep:
      """
      ws = boxes[:, 2] - boxes[:, 0] + 1
      hs = boxes[:, 3] - boxes[:, 1] + 1
      keep = np.where((ws >= min_size) & (hs >= min_size))[0]
      return keep
  
  
  def remove_repetition(boxes):
      """
      remove repetited boxes
      :param boxes: [N, 4]
      :return: keep:
      """
      _, x1_keep = np.unique(boxes[:, 0], return_index=True)
      _, x2_keep = np.unique(boxes[:, 2], return_index=True)
      _, y1_keep = np.unique(boxes[:, 1], return_index=True)
      _, y2_keep = np.unique(boxes[:, 3], return_index=True)
  
      x_keep = np.union1d(x1_keep, x2_keep)
      y_keep = np.union1d(y1_keep, y2_keep)
      keep = np.union1d(x_keep, y_keep)
      return keep
  
  
  def nonlinear_transform(ex_rois, gt_rois):
      """
      compute bounding box regression targets from ex_rois to gt_rois
      :param ex_rois: [N, 4]
      :param gt_rois: [N, 4]
      :return: [N, 4]
      """
      assert ex_rois.shape[0] == gt_rois.shape[0], 'inconsistent rois number'
  
      ex_widths = ex_rois[:, 2] - ex_rois[:, 0] + 1.0
      ex_heights = ex_rois[:, 3] - ex_rois[:, 1] + 1.0
      ex_ctr_x = ex_rois[:, 0] + 0.5 * (ex_widths - 1.0)
      ex_ctr_y = ex_rois[:, 1] + 0.5 * (ex_heights - 1.0)
  
      gt_widths = gt_rois[:, 2] - gt_rois[:, 0] + 1.0
      gt_heights = gt_rois[:, 3] - gt_rois[:, 1] + 1.0
      gt_ctr_x = gt_rois[:, 0] + 0.5 * (gt_widths - 1.0)
      gt_ctr_y = gt_rois[:, 1] + 0.5 * (gt_heights - 1.0)
  
      targets_dx = (gt_ctr_x - ex_ctr_x) / (ex_widths + 1e-14)
      targets_dy = (gt_ctr_y - ex_ctr_y) / (ex_heights + 1e-14)
      targets_dw = np.log(gt_widths / ex_widths)
      targets_dh = np.log(gt_heights / ex_heights)
  
      targets = np.vstack(
          (targets_dx, targets_dy, targets_dw, targets_dh)).transpose()
      return targets
  
  
  def nonlinear_pred(boxes, box_deltas):
      """
      Transform the set of class-agnostic boxes into class-specific boxes
      by applying the predicted offsets (box_deltas)
      :param boxes: !important [N 4]
      :param box_deltas: [N, 4 * num_classes]
      :return: [N 4 * num_classes]
      """
      if boxes.shape[0] == 0:
          return np.zeros((0, box_deltas.shape[1]))
  
      boxes = boxes.astype(np.float, copy=False)
      widths = boxes[:, 2] - boxes[:, 0] + 1.0
      heights = boxes[:, 3] - boxes[:, 1] + 1.0
      ctr_x = boxes[:, 0] + 0.5 * (widths - 1.0)
      ctr_y = boxes[:, 1] + 0.5 * (heights - 1.0)
  
      dx = box_deltas[:, 0::4]
      dy = box_deltas[:, 1::4]
      dw = box_deltas[:, 2::4]
      dh = box_deltas[:, 3::4]
  
      pred_ctr_x = dx * widths[:, np.newaxis] + ctr_x[:, np.newaxis]
      pred_ctr_y = dy * heights[:, np.newaxis] + ctr_y[:, np.newaxis]
      pred_w = np.exp(dw) * widths[:, np.newaxis]
      pred_h = np.exp(dh) * heights[:, np.newaxis]
  
      pred_boxes = np.zeros(box_deltas.shape)
      # x1
      pred_boxes[:, 0::4] = pred_ctr_x - 0.5 * (pred_w - 1.0)
      # y1
      pred_boxes[:, 1::4] = pred_ctr_y - 0.5 * (pred_h - 1.0)
      # x2
      pred_boxes[:, 2::4] = pred_ctr_x + 0.5 * (pred_w - 1.0)
      # y2
      pred_boxes[:, 3::4] = pred_ctr_y + 0.5 * (pred_h - 1.0)
  
      return pred_boxes
  
  
  def iou_transform(ex_rois, gt_rois):
      """ return bbox targets, IoU loss uses gt_rois as gt """
      assert ex_rois.shape[0] == gt_rois.shape[0], 'inconsistent rois number'
      return gt_rois
  
  
  def iou_pred(boxes, box_deltas):
      """
      Transform the set of class-agnostic boxes into class-specific boxes
      by applying the predicted offsets (box_deltas)
      :param boxes: !important [N 4]
      :param box_deltas: [N, 4 * num_classes]
      :return: [N 4 * num_classes]
      """
      if boxes.shape[0] == 0:
          return np.zeros((0, box_deltas.shape[1]))
  
      boxes = boxes.astype(np.float, copy=False)
      x1 = boxes[:, 0]
      y1 = boxes[:, 1]
      x2 = boxes[:, 2]
      y2 = boxes[:, 3]
  
      dx1 = box_deltas[:, 0::4]
      dy1 = box_deltas[:, 1::4]
      dx2 = box_deltas[:, 2::4]
      dy2 = box_deltas[:, 3::4]
  
      pred_boxes = np.zeros(box_deltas.shape)
      # x1
      pred_boxes[:, 0::4] = dx1 + x1[:, np.newaxis]
      # y1
      pred_boxes[:, 1::4] = dy1 + y1[:, np.newaxis]
      # x2
      pred_boxes[:, 2::4] = dx2 + x2[:, np.newaxis]
      # y2
      pred_boxes[:, 3::4] = dy2 + y2[:, np.newaxis]
  
      return pred_boxes
  
  
  # define bbox_transform and bbox_pred
  bbox_transform = nonlinear_transform
  bbox_pred = nonlinear_pred
  