class_docstr: Create final object detection proposals.
  
  RCNNProposals takes the proposals generated by the RPN and the predictions
  of the RCNN (both classification and boundin box adjusting) and generates
  a list of object proposals with assigned class.
  
  In the process it tries to remove duplicated suggestions by applying non
  maximum suppresion (NMS).
  
  We apply NMS because the way object detectors are usually scored is by
  treating duplicated detections (multiple detections that overlap the same
  ground truth value) as false positive. It is resonable to assume that there
  may exist such case that applying NMS is completly unnecesary.
  
  Besides applying NMS it also filters the top N results, both for classes
  and in general. These values are easily modifiable in the configuration
  files.
class_docstr_tok: ["Create","final","object","detection","proposals",".","RCNNProposals","takes","the","proposals","generated","by","the","RPN","and","the","predictions","of","the","RCNN","(","both","classification","and","boundin","box","adjusting",")","and","generates","a","list","of","object","proposals","with","assigned","class",".","In","the","process","it","tries","to","remove","duplicated","suggestions","by","applying","non","maximum","suppresion","(","NMS",")",".","We","apply","NMS","because","the","way","object","detectors","are","usually","scored","is","by","treating","duplicated","detections","(","multiple","detections","that","overlap","the","same","ground","truth","value",")","as","false","positive",".","It","is","resonable","to","assume","that","there","may","exist","such","case","that","applying","NMS","is","completly","unnecesary",".","Besides","applying","NMS","it","also","filters","the","top","N","results",",","both","for","classes","and","in","general",".","These","values","are","easily","modifiable","in","the","configuration","files","."]
docstr: Args:
      proposals: Tensor with the RPN proposals bounding boxes.
          Shape (num_proposals, 4). Where num_proposals is less than
          POST_NMS_TOP_N (We don't know exactly beforehand)
      bbox_pred: Tensor with the RCNN delta predictions for each proposal
          for each class. Shape (num_proposals, 4 * num_classes)
      cls_prob: A softmax probability for each proposal where the idx = 0
          is the background class (which we should ignore).
          Shape (num_proposals, num_classes + 1)
  
  Returns:
      objects:
          Shape (final_num_proposals, 4)
          Where final_num_proposals is unknown before-hand (it depends on
          NMS). The 4-length Tensor for each corresponds to:
          (x_min, y_min, x_max, y_max).
      objects_label:
          Shape (final_num_proposals,)
      objects_label_prob:
          Shape (final_num_proposals,)
docstr_tok: ["Args",":","proposals",":","Tensor","with","the","RPN","proposals","bounding","boxes",".","Shape","(","num_proposals",",","4",")",".","Where","num_proposals","is","less","than","POST_NMS_TOP_N","(","We","do","n't","know","exactly","beforehand",")","bbox_pred",":","Tensor","with","the","RCNN","delta","predictions","for","each","proposal","for","each","class",".","Shape","(","num_proposals",",","4","*","num_classes",")","cls_prob",":","A","softmax","probability","for","each","proposal","where","the","idx","=","0","is","the","background","class","(","which","we","should","ignore",")",".","Shape","(","num_proposals",",","num_classes","+","1",")","Returns",":","objects",":","Shape","(","final_num_proposals",",","4",")","Where","final_num_proposals","is","unknown","before-hand","(","it","depends","on","NMS",")",".","The","4-length","Tensor","for","each","corresponds","to",":","(","x_min",",","y_min",",","x_max",",","y_max",")",".","objects_label",":","Shape","(","final_num_proposals",",",")","objects_label_prob",":","Shape","(","final_num_proposals",",",")"]
code: def _build(self, proposals, bbox_pred, cls_prob, im_shape):
      """
      Args:
          proposals: Tensor with the RPN proposals bounding boxes.
              Shape (num_proposals, 4). Where num_proposals is less than
              POST_NMS_TOP_N (We don't know exactly beforehand)
          bbox_pred: Tensor with the RCNN delta predictions for each proposal
              for each class. Shape (num_proposals, 4 * num_classes)
          cls_prob: A softmax probability for each proposal where the idx = 0
              is the background class (which we should ignore).
              Shape (num_proposals, num_classes + 1)
  
      Returns:
          objects:
              Shape (final_num_proposals, 4)
              Where final_num_proposals is unknown before-hand (it depends on
              NMS). The 4-length Tensor for each corresponds to:
              (x_min, y_min, x_max, y_max).
          objects_label:
              Shape (final_num_proposals,)
          objects_label_prob:
              Shape (final_num_proposals,)
  
      """
      selected_boxes = []
      selected_probs = []
      selected_labels = []
  
      # For each class, take the proposals with the class-specific
      # predictions (class scores and bbox regression) and filter accordingly
      # (valid area, min probability score and NMS).
      for class_id in range(self._num_classes):
          # Apply the class-specific transformations to the proposals to
          # obtain the current class' prediction.
          class_prob = cls_prob[
              :, class_id + 1
          ]  # 0 is background class.
          class_bboxes = bbox_pred[
              :, (4 * class_id) : (4 * class_id + 4)
          ]
          raw_class_objects = decode(
              proposals,
              class_bboxes,
              variances=self._variances,
          )
  
          # Clip bboxes so they don't go out of the image.
          class_objects = clip_boxes(raw_class_objects, im_shape)
  
          # Filter objects based on the min probability threshold and on them
          # having a valid area.
          prob_filter = tf.greater_equal(
              class_prob, self._min_prob_threshold
          )
  
          (x_min, y_min, x_max, y_max) = tf.unstack(
              class_objects, axis=1
          )
          area_filter = tf.greater(
              tf.maximum(x_max - x_min, 0.0)
              * tf.maximum(y_max - y_min, 0.0),
              0.0,
          )
  
          object_filter = tf.logical_and(area_filter, prob_filter)
  
          class_objects = tf.boolean_mask(class_objects, object_filter)
          class_prob = tf.boolean_mask(class_prob, object_filter)
  
          # We have to use the TensorFlow's bounding box convention to use
          # the included function for NMS.
          class_objects_tf = change_order(class_objects)
  
          # Apply class NMS.
          class_selected_idx = tf.image.non_max_suppression(
              class_objects_tf,
              class_prob,
              self._class_max_detections,
              iou_threshold=self._class_nms_threshold,
          )
  
          # Using NMS resulting indices, gather values from Tensors.
          class_objects_tf = tf.gather(
              class_objects_tf, class_selected_idx
          )
          class_prob = tf.gather(class_prob, class_selected_idx)
  
          # Revert to our bbox convention.
          class_objects = change_order(class_objects_tf)
  
          # We append values to a regular list which will later be
          # transformed to a proper Tensor.
          selected_boxes.append(class_objects)
          selected_probs.append(class_prob)
          # In the case of the class_id, since it is a loop on classes, we
          # already have a fixed class_id. We use `tf.tile` to create that
          # Tensor with the total number of indices returned by the NMS.
          selected_labels.append(
              tf.tile([class_id], [tf.shape(class_selected_idx)[0]])
          )
  
      # We use concat (axis=0) to generate a Tensor where the rows are
      # stacked on top of each other
      objects = tf.concat(selected_boxes, axis=0)
      proposal_label = tf.concat(selected_labels, axis=0)
      proposal_label_prob = tf.concat(selected_probs, axis=0)
  
      tf.summary.histogram(
          "proposal_cls_scores", proposal_label_prob, ["rcnn"]
      )
  
      # Get top-k detections of all classes.
      k = tf.minimum(
          self._total_max_detections, tf.shape(proposal_label_prob)[0]
      )
      top_k = tf.nn.top_k(proposal_label_prob, k=k)
      top_k_proposal_label_prob = top_k.values
      top_k_objects = tf.gather(objects, top_k.indices)
      top_k_proposal_label = tf.gather(proposal_label, top_k.indices)
  
      return {
          "objects": top_k_objects,
          "proposal_label": top_k_proposal_label,
          "proposal_label_prob": top_k_proposal_label_prob,
          "selected_boxes": selected_boxes,
          "selected_probs": selected_probs,
          "selected_labels": selected_labels,
      }
  
imports: import sonnet
  import tensorflow
  from luminoth.utils.bbox_transform_tf import decode, clip_boxes, change_order
label_desc: Use image node_id 2 for calling the RCNNProposal obj's underlying member method code with example usage: obj._build(proposals, bbox_pred, cls_prob, im_shape) and returns: dict