text: # --------------------------------------------------------
  # Deformable Convolutional Networks
  # Copyright (c) 2017 Microsoft
  # Licensed under The MIT License [see LICENSE for details]
  # Modified by Yuwen Xiong
  # --------------------------------------------------------
  # Based on:
  # MX-RCNN
  # Copyright (c) 2016 by Contributors
  # Licence under The Apache 2.0 License
  # https://github.com/ijkguo/mx-rcnn/
  # --------------------------------------------------------
  
  """
  Proposal Target Operator selects foreground and background roi and assigns label, bbox_transform to them.
  """
  
  import mxnet as mx
  import numpy as np
  import pickle
  
  
  DEBUG = False
  
  
  class ProposalTargetOperator(mx.operator.CustomOp):
      def __init__(self, num_classes, batch_images, batch_rois, cfg, fg_fraction):
          super(ProposalTargetOperator, self).__init__()
          self._num_classes = num_classes
          self._batch_images = batch_images
          self._batch_rois = batch_rois
          self._cfg = cfg
          self._fg_fraction = fg_fraction
  
          if DEBUG:
              self._count = 0
              self._fg_num = 0
              self._bg_num = 0
  
      def forward(self, is_train, req, in_data, out_data, aux):
          assert self._batch_rois == -1 or self._batch_rois % self._batch_images == 0, \
              'batchimages {} must devide batch_rois {}'.format(self._batch_images, self._batch_rois)
          all_rois = in_data[0].asnumpy()
          gt_boxes = in_data[1].asnumpy()
  
          if self._batch_rois == -1:
              rois_per_image = all_rois.shape[0] + gt_boxes.shape[0]
              fg_rois_per_image = rois_per_image
          else:
              rois_per_image = self._batch_rois / self._batch_images
              fg_rois_per_image = np.round(self._fg_fraction * rois_per_image).astype(int)
  
  
          # Include ground-truth boxes in the set of candidate rois
          zeros = np.zeros((gt_boxes.shape[0], 1), dtype=gt_boxes.dtype)
          all_rois = np.vstack((all_rois, np.hstack((zeros, gt_boxes[:, :-1]))))
          # Sanity check: single batch only
          assert np.all(all_rois[:, 0] == 0), 'Only single item batches are supported'
  
          rois, labels, bbox_targets, bbox_weights = \
              sample_rois(all_rois, fg_rois_per_image, rois_per_image, self._num_classes, self._cfg, gt_boxes=gt_boxes)
  
          if DEBUG:
              print("labels=", labels)
              print('num fg: {}'.format((labels > 0).sum()))
              print('num bg: {}'.format((labels == 0).sum()))
              self._count += 1
              self._fg_num += (labels > 0).sum()
              self._bg_num += (labels == 0).sum()
              print("self._count=", self._count)
              print('num fg avg: {}'.format(self._fg_num / self._count))
              print('num bg avg: {}'.format(self._bg_num / self._count))
              print('ratio: {:.3f}'.format(float(self._fg_num) / float(self._bg_num)))
  
          for ind, val in enumerate([rois, labels, bbox_targets, bbox_weights]):
              self.assign(out_data[ind], req[ind], val)
  
      def backward(self, req, out_grad, in_data, out_data, in_grad, aux):
          self.assign(in_grad[0], req[0], 0)
          self.assign(in_grad[1], req[1], 0)
  
  
  @mx.operator.register('proposal_target')
  class ProposalTargetProp(mx.operator.CustomOpProp):
      def __init__(self, num_classes, batch_images, batch_rois, cfg, fg_fraction='0.25'):
          super(ProposalTargetProp, self).__init__(need_top_grad=False)
          self._num_classes = int(num_classes)
          self._batch_images = int(batch_images)
          self._batch_rois = int(batch_rois)
          self._cfg = pickle.loads(cfg.encode(),encoding='latin1')
          self._fg_fraction = float(fg_fraction)
  
      def list_arguments(self):
          return ['rois', 'gt_boxes']
  
      def list_outputs(self):
          return ['rois_output', 'label', 'bbox_target', 'bbox_weight']
  
      def infer_shape(self, in_shape):
          rpn_rois_shape = in_shape[0]
          gt_boxes_shape = in_shape[1]
  
          rois = rpn_rois_shape[0] + gt_boxes_shape[0] if self._batch_rois == -1 else self._batch_rois
  
          output_rois_shape = (rois, 5)
          label_shape = (rois, )
          bbox_target_shape = (rois, self._num_classes * 4)
          bbox_weight_shape = (rois, self._num_classes * 4)
  
          return [rpn_rois_shape, gt_boxes_shape], \
                 [output_rois_shape, label_shape, bbox_target_shape, bbox_weight_shape]
  
      def create_operator(self, ctx, shapes, dtypes):
          return ProposalTargetOperator(self._num_classes, self._batch_images, self._batch_rois, self._cfg, self._fg_fraction)
  
      def declare_backward_dependency(self, out_grad, in_data, out_data):
          return []
  
  def sample_rois(rois, fg_rois_per_image, rois_per_image, num_classes, cfg,
                  labels=None, overlaps=None, bbox_targets=None, gt_boxes=None):
      """
      generate random sample of ROIs comprising foreground and background examples
      :param rois: all_rois [n, 4]; e2e: [n, 5] with batch_index
      :param fg_rois_per_image: foreground roi number
      :param rois_per_image: total roi number
      :param num_classes: number of classes
      :param labels: maybe precomputed
      :param overlaps: maybe precomputed (max_overlaps)
      :param bbox_targets: maybe precomputed
      :param gt_boxes: optional for e2e [n, 5] (x1, y1, x2, y2, cls)
      :return: (labels, rois, bbox_targets, bbox_weights)
      """
      if labels is None:
          overlaps = bbox_overlaps(rois[:, 1:].astype(np.float), gt_boxes[:, :4].astype(np.float))
          gt_assignment = overlaps.argmax(axis=1)
          overlaps = overlaps.max(axis=1)
          labels = gt_boxes[gt_assignment, 4]
  
      # foreground RoI with FG_THRESH overlap
      fg_indexes = np.where(overlaps >= cfg.TRAIN.FG_THRESH)[0]
      # guard against the case when an image has fewer than fg_rois_per_image foreground RoIs
      fg_rois_per_this_image = np.minimum(fg_rois_per_image, fg_indexes.size)
      # Sample foreground regions without replacement
      if len(fg_indexes) > fg_rois_per_this_image:
          fg_indexes = npr.choice(fg_indexes, size=fg_rois_per_this_image, replace=False)
  
      # Select background RoIs as those within [BG_THRESH_LO, BG_THRESH_HI)
      bg_indexes = np.where((overlaps < cfg.TRAIN.BG_THRESH_HI) & (overlaps >= cfg.TRAIN.BG_THRESH_LO))[0]
      # Compute number of background RoIs to take from this image (guarding against there being fewer than desired)
      bg_rois_per_this_image = rois_per_image - fg_rois_per_this_image
      bg_rois_per_this_image = np.minimum(bg_rois_per_this_image, bg_indexes.size)
      # Sample foreground regions without replacement
      if len(bg_indexes) > bg_rois_per_this_image:
          bg_indexes = npr.choice(bg_indexes, size=bg_rois_per_this_image, replace=False)
  
      # indexes selected
      keep_indexes = np.append(fg_indexes, bg_indexes)
  
      # pad more to ensure a fixed minibatch size
      while keep_indexes.shape[0] < rois_per_image:
          gap = np.minimum(len(rois), rois_per_image - keep_indexes.shape[0])
          gap_indexes = npr.choice(range(len(rois)), size=gap, replace=False)
          keep_indexes = np.append(keep_indexes, gap_indexes)
  
      # select labels
      labels = labels[keep_indexes]
      # set labels of bg_rois to be 0
      labels[fg_rois_per_this_image:] = 0
      rois = rois[keep_indexes]
  
      # load or compute bbox_target
      if bbox_targets is not None:
          bbox_target_data = bbox_targets[keep_indexes, :]
      else:
          targets = bbox_transform(rois[:, 1:], gt_boxes[gt_assignment[keep_indexes], :4])
          if cfg.TRAIN.BBOX_NORMALIZATION_PRECOMPUTED:
              targets = ((targets - np.array(cfg.TRAIN.BBOX_MEANS))
                         / np.array(cfg.TRAIN.BBOX_STDS))
          bbox_target_data = np.hstack((labels[:, np.newaxis], targets))
  
      bbox_targets, bbox_weights = \
          expand_bbox_regression_targets(bbox_target_data, num_classes, cfg)
  
      return rois, labels, bbox_targets, bbox_weights
  