text: # -*- coding: utf-8 -*-
  # File: model_box.py
  
  import numpy as np
  from collections import namedtuple
  import tensorflow as tf
  
  from tensorpack.tfutils.scope_utils import under_name_scope
  from tensorpack.models import layer_register
  
  from config import config
  
  
  @under_name_scope()
  def clip_boxes(boxes, window, name=None):
      """
      Args:
          boxes: nx4, xyxy
          window: [h, w]
      """
      boxes = tf.maximum(boxes, 0.0)
      m = tf.tile(tf.reverse(window, [0]), [2])    # (4,)
      boxes = tf.minimum(boxes, tf.to_float(m), name=name)
      return boxes
  
  
  @under_name_scope()
  def decode_bbox_target(box_predictions, anchors):
      """
      Args:
          box_predictions: (..., 4), logits
          anchors: (..., 4), floatbox. Must have the same shape
  
      Returns:
          box_decoded: (..., 4), float32. With the same shape.
      """
      orig_shape = tf.shape(anchors)
      box_pred_txtytwth = tf.reshape(box_predictions, (-1, 2, 2))
      box_pred_txty, box_pred_twth = tf.split(box_pred_txtytwth, 2, axis=1)
      # each is (...)x1x2
      anchors_x1y1x2y2 = tf.reshape(anchors, (-1, 2, 2))
      anchors_x1y1, anchors_x2y2 = tf.split(anchors_x1y1x2y2, 2, axis=1)
  
      waha = anchors_x2y2 - anchors_x1y1
      xaya = (anchors_x2y2 + anchors_x1y1) * 0.5
  
      clip = np.log(config.PREPROC.MAX_SIZE / 16.)
      wbhb = tf.exp(tf.minimum(box_pred_twth, clip)) * waha
      xbyb = box_pred_txty * waha + xaya
      x1y1 = xbyb - wbhb * 0.5
      x2y2 = xbyb + wbhb * 0.5    # (...)x1x2
      out = tf.concat([x1y1, x2y2], axis=-2)
      return tf.reshape(out, orig_shape)
  
  
  @under_name_scope()
  def encode_bbox_target(boxes, anchors):
      """
      Args:
          boxes: (..., 4), float32
          anchors: (..., 4), float32
  
      Returns:
          box_encoded: (..., 4), float32 with the same shape.
      """
      anchors_x1y1x2y2 = tf.reshape(anchors, (-1, 2, 2))
      anchors_x1y1, anchors_x2y2 = tf.split(anchors_x1y1x2y2, 2, axis=1)
      waha = anchors_x2y2 - anchors_x1y1
      xaya = (anchors_x2y2 + anchors_x1y1) * 0.5
  
      boxes_x1y1x2y2 = tf.reshape(boxes, (-1, 2, 2))
      boxes_x1y1, boxes_x2y2 = tf.split(boxes_x1y1x2y2, 2, axis=1)
      wbhb = boxes_x2y2 - boxes_x1y1
      xbyb = (boxes_x2y2 + boxes_x1y1) * 0.5
  
      # Note that here not all boxes are valid. Some may be zero
      txty = (xbyb - xaya) / waha
      twth = tf.log(wbhb / waha)  # may contain -inf for invalid boxes
      encoded = tf.concat([txty, twth], axis=1)  # (-1x2x2)
      return tf.reshape(encoded, tf.shape(boxes))
  
  @under_name_scope()
  def crop_and_resize(image, boxes, box_ind, crop_size, pad_border=True):
      """
      Aligned version of tf.image.crop_and_resize, following our definition of floating point boxes.
  
      Args:
          image: NCHW
          boxes: nx4, x1y1x2y2
          box_ind: (n,)
          crop_size (int):
      Returns:
          n,C,size,size
      """
      assert isinstance(crop_size, int), crop_size
      boxes = tf.stop_gradient(boxes)
  
      # TF's crop_and_resize produces zeros on border
      if pad_border:
          # this can be quite slow
          image = tf.pad(image, [[0, 0], [0, 0], [1, 1], [1, 1]], mode='SYMMETRIC')
          boxes = boxes + 1
  
      @under_name_scope()
      def transform_fpcoor_for_tf(boxes, image_shape, crop_shape):
          """
          The way tf.image.crop_and_resize works (with normalized box):
          Initial point (the value of output[0]): x0_box * (W_img - 1)
          Spacing: w_box * (W_img - 1) / (W_crop - 1)
          Use the above grid to bilinear sample.
  
          However, what we want is (with fpcoor box):
          Spacing: w_box / W_crop
          Initial point: x0_box + spacing/2 - 0.5
          (-0.5 because bilinear sample (in my definition) assumes floating point coordinate
           (0.0, 0.0) is the same as pixel value (0, 0))
  
          This function transform fpcoor boxes to a format to be used by tf.image.crop_and_resize
  
          Returns:
              y1x1y2x2
          """
          x0, y0, x1, y1 = tf.split(boxes, 4, axis=1)
  
          spacing_w = (x1 - x0) / tf.to_float(crop_shape[1])
          spacing_h = (y1 - y0) / tf.to_float(crop_shape[0])
  
          nx0 = (x0 + spacing_w / 2 - 0.5) / tf.to_float(image_shape[1] - 1)
          ny0 = (y0 + spacing_h / 2 - 0.5) / tf.to_float(image_shape[0] - 1)
  
          nw = spacing_w * tf.to_float(crop_shape[1] - 1) / tf.to_float(image_shape[1] - 1)
          nh = spacing_h * tf.to_float(crop_shape[0] - 1) / tf.to_float(image_shape[0] - 1)
  
          return tf.concat([ny0, nx0, ny0 + nh, nx0 + nw], axis=1)
  
      # Expand bbox to a minium size of 1
      # boxes_x1y1, boxes_x2y2 = tf.split(boxes, 2, axis=1)
      # boxes_wh = boxes_x2y2 - boxes_x1y1
      # boxes_center = tf.reshape((boxes_x2y2 + boxes_x1y1) * 0.5, [-1, 2])
      # boxes_newwh = tf.maximum(boxes_wh, 1.)
      # boxes_x1y1new = boxes_center - boxes_newwh * 0.5
      # boxes_x2y2new = boxes_center + boxes_newwh * 0.5
      # boxes = tf.concat([boxes_x1y1new, boxes_x2y2new], axis=1)
  
      image_shape = tf.shape(image)[2:]
      boxes = transform_fpcoor_for_tf(boxes, image_shape, [crop_size, crop_size])
      image = tf.transpose(image, [0, 2, 3, 1])   # nhwc
      ret = tf.image.crop_and_resize(
          image, boxes, tf.to_int32(box_ind),
          crop_size=[crop_size, crop_size])
      ret = tf.transpose(ret, [0, 3, 1, 2])   # ncss
      return ret
  
  @under_name_scope()
  def roi_align(featuremap, boxes, resolution):
      """
      Args:
          featuremap: 1xCxHxW
          boxes: Nx4 floatbox
          resolution: output spatial resolution
  
      Returns:
          NxCx res x res
      """
      # sample 4 locations per roi bin
      ret = crop_and_resize(
          featuremap, boxes,
          tf.zeros([tf.shape(boxes)[0]], dtype=tf.int32),
          resolution * 2)
      ret = tf.nn.avg_pool(ret, [1, 1, 2, 2], [1, 1, 2, 2], padding='SAME', data_format='NCHW')
      return ret
  @layer_register(log_shape=True)
  def VotePooling(featuremap, boxes, k, poolshape, isCls=True):
      """
      1. split boxes into k^2 region
      2. calculate value of every region. we assume structure of featuremap is [k^2, numClass]
  
      """
  
      ret = crop_and_resize(featuremap, boxes,
                            tf.zeros([tf.shape(boxes)[0]], dtype=tf.int32),
                            k * poolshape)
      # normalize
      ret = tf.transpose(ret, [0, 2, 3, 1])
      scoremap_bins = []
      base = 1 if isCls else 4
      print(ret.shape)
      for k_step in range(k*k):
          offset_x = int((k_step//k)*poolshape)
          offset_y = int((k_step%k)*poolshape)
          channel_indices = np.arange(k_step*config.DATA.NUM_CLASS*base, (k_step+1)*config.DATA.NUM_CLASS*base,
                                      dtype=np.int32)
          # shape B x k*poolshape x k*poolshape x k*k*C*base-> B x poolshape x poolshape x C*base
          cropRegion = tf.image.crop_to_bounding_box(tf.gather(ret, channel_indices, axis=-1), offset_y, offset_x, poolshape, poolshape, )
          # B x poolshape x poolshape x C*base-> B x 1 x 1 x C*base->0
          cropmean = tf.nn.avg_pool(cropRegion, (1, poolshape, poolshape, 1),
                                      (1, poolshape, poolshape, 1), padding='VALID', data_format='NHWC')
          # print(cropmean.shape)
          cropmean = tf.transpose(cropmean, [0, 3, 1, 2])
          cropmean = tf.squeeze(cropmean, axis=-1)
          scoremap_bins.append(cropmean)
      scoremap_bins = tf.concat(scoremap_bins, axis=-1)
      return tf.reduce_sum(scoremap_bins, axis=-1)/(k*k)
  
  class RPNAnchors(namedtuple('_RPNAnchors', ['boxes', 'gt_labels', 'gt_boxes'])):
      """
      boxes (FS x FS x NA x 4): The anchor boxes.
      gt_labels (FS x FS x NA):
      gt_boxes (FS x FS x NA x 4): Groundtruth boxes corresponding to each anchor.
      """
      def encoded_gt_boxes(self):
          return encode_bbox_target(self.gt_boxes, self.boxes)
  
      def decode_logits(self, logits):
          return decode_bbox_target(logits, self.boxes)
  
      @under_name_scope()
      def narrow_to(self, featuremap):
          """
          Slice anchors to the spatial size of this featuremap.
          """
          shape2d = tf.shape(featuremap)[2:]  # h,w
          slice3d = tf.concat([shape2d, [-1]], axis=0)
          slice4d = tf.concat([shape2d, [-1, -1]], axis=0)
          boxes = tf.slice(self.boxes, [0, 0, 0, 0], slice4d)
          gt_labels = tf.slice(self.gt_labels, [0, 0, 0], slice3d)
          gt_boxes = tf.slice(self.gt_boxes, [0, 0, 0, 0], slice4d)
          return RPNAnchors(boxes, gt_labels, gt_boxes)
  
  
  if __name__ == '__main__':
      """
      Demonstrate what's wrong with tf.image.crop_and_resize:
      """
      import tensorflow.contrib.eager as tfe
      tfe.enable_eager_execution()
  
      # want to crop 2x2 out of a 5x5 image, and resize to 4x4
      image = np.arange(25).astype('float32').reshape(5, 5)
      boxes = np.asarray([[1, 1, 3, 3]], dtype='float32')
      target = 4
  
      print(crop_and_resize(
          image[None, None, :, :], boxes, [0], target)[0][0])
      """
      Expected values:
      4.5 5 5.5 6
      7 7.5 8 8.5
      9.5 10 10.5 11
      12 12.5 13 13.5
  
      You cannot easily get the above results with tf.image.crop_and_resize.
      Try out yourself here:
      """
      print(tf.image.crop_and_resize(
          image[None, :, :, None],
          np.asarray([[1, 1, 2, 2]]) / 4.0, [0], [target, target])[0][:, :, 0])
  