text: '''
  Proposal Layer
  '''
  import numpy as np
  
  import torch
  import torch.utils.data
  from torch.autograd import Variable
  
  from lib.nms.nms_wrapper import nms
  
  
  def apply_box_deltas(boxes, deltas):
      """Applies the given deltas to the given boxes.
      boxes: [N, 4] where each row is y1, x1, y2, x2
      deltas: [N, 4] where each row is [dy, dx, log(dh), log(dw)]
      """
      # Convert to y, x, h, w
      height = boxes[:, 2] - boxes[:, 0]
      width = boxes[:, 3] - boxes[:, 1]
      center_y = boxes[:, 0] + 0.5 * height
      center_x = boxes[:, 1] + 0.5 * width
      # Apply deltas
      center_y += deltas[:, 0] * height
      center_x += deltas[:, 1] * width
      height *= torch.exp(deltas[:, 2])
      width *= torch.exp(deltas[:, 3])
      # Convert back to y1, x1, y2, x2
      y1 = center_y - 0.5 * height
      x1 = center_x - 0.5 * width
      y2 = y1 + height
      x2 = x1 + width
      result = torch.stack([y1, x1, y2, x2], dim=1)
      return result
  
  def clip_boxes(boxes, window):
      """
      boxes: [N, 4] each col is y1, x1, y2, x2
      window: [4] in the form y1, x1, y2, x2
      """
      boxes = torch.stack( \
          [boxes[:, 0].clamp(float(window[0]), float(window[2])),
           boxes[:, 1].clamp(float(window[1]), float(window[3])),
           boxes[:, 2].clamp(float(window[0]), float(window[2])),
           boxes[:, 3].clamp(float(window[1]), float(window[3]))], 1)
      return boxes
  
  def proposal_layer(inputs, proposal_count, nms_threshold, anchors, config=None):
      """Receives anchor scores and selects a subset to pass as proposals
      to the second stage. Filtering is done based on anchor scores and
      non-max suppression to remove overlaps. It also applies bounding
      box refinment detals to anchors.
  
      Inputs:
          rpn_probs: [batch, anchors, (bg prob, fg prob)]
          rpn_bbox: [batch, anchors, (dy, dx, log(dh), log(dw))]
  
      Returns:
          Proposals in normalized coordinates [batch, rois, (y1, x1, y2, x2)]
      """
  
      # Currently only supports batchsize 1
      inputs[0] = inputs[0].squeeze(0)
      inputs[1] = inputs[1].squeeze(0)
  
      # Box Scores. Use the foreground class confidence. [Batch, num_rois, 1]
      scores = inputs[0][:, 1]
  
      # Box deltas [batch, num_rois, 4]
      deltas = inputs[1]
      std_dev = Variable(torch.from_numpy(np.reshape(config.RPN.BBOX_STD_DEV, [1, 4])).float(), requires_grad=False)
      if config.GPU_COUNT:
          std_dev = std_dev.cuda()
      deltas = deltas * std_dev
  
      # Improve performance by trimming to top anchors by score
      # and doing the rest on the smaller subset.
      pre_nms_limit = min(6000, anchors.size()[0])
      scores, order = scores.sort(descending=True)
      order = order[:pre_nms_limit]
      scores = scores[:pre_nms_limit]
      deltas = deltas[order.data, :] # TODO: Support batch size > 1 ff.
      anchors = anchors[order.data, :]
  
      # Apply deltas to anchors to get refined anchors.
      # [batch, N, (y1, x1, y2, x2)]
      boxes = apply_box_deltas(anchors, deltas)
  
      # Clip to image boundaries. [batch, N, (y1, x1, y2, x2)]
      height, width = config.TRAIN.IMAGE_SHAPE[:2]
      window = np.array([0, 0, height, width]).astype(np.float32)
      boxes = clip_boxes(boxes, window)
  
      # Filter out small boxes
      # According to Xinlei Chen's paper, this reduces detection accuracy
      # for small objects, so we're skipping it.
  
      # Non-max suppression
      keep = nms(torch.cat((boxes, scores.unsqueeze(1)), 1).data, nms_threshold)
      keep = keep[:proposal_count]
      boxes = boxes[keep, :]
  
      # Normalize dimensions to range of 0 to 1.
      norm = Variable(torch.from_numpy(np.array([height, width, height, width])).float(), requires_grad=False)
      if config.GPU_COUNT:
          norm = norm.cuda()
      normalized_boxes = boxes / norm
  
      # Add back batch dimension
      normalized_boxes = normalized_boxes.unsqueeze(0)
  
      return normalized_boxes
  
  