def concatenate(box_mask_lists, fields=None):
    """Concatenate list of box_mask_lists.

  This op concatenates a list of input box_mask_lists into a larger
  box_mask_list.  It also
  handles concatenation of box_mask_list fields as long as the field tensor
  shapes are equal except for the first dimension.

  Args:
    box_mask_lists: list of np_box_mask_list.BoxMaskList objects
    fields: optional list of fields to also concatenate.  By default, all
      fields from the first BoxMaskList in the list are included in the
      concatenation.

  Returns:
    a box_mask_list with number of boxes equal to
      sum([box_mask_list.num_boxes() for box_mask_list in box_mask_list])
  Raises:
    ValueError: if box_mask_lists is invalid (i.e., is not a list, is empty, or
      contains non box_mask_list objects), or if requested fields are not
      contained in all box_mask_lists
  """
    if fields is not None:
        if 'masks' not in fields:
            fields.append('masks')
    return box_list_to_box_mask_list(
        np_box_list_ops.concatenate(boxlists=box_mask_lists, fields=fields))
 def test_concatenate(self):
     boxlist1 = np_box_list.BoxList(
         np.array([[0.25, 0.25, 0.75, 0.75], [0.0, 0.0, 0.5, 0.75]],
                  dtype=np.float32))
     boxlist2 = np_box_list.BoxList(
         np.array([[0.5, 0.25, 1.0, 1.0], [0.0, 0.0, 1.0, 1.0]],
                  dtype=np.float32))
     boxlists = [boxlist1, boxlist2]
     boxlist_concatenated = np_box_list_ops.concatenate(boxlists)
     boxlist_concatenated_expected = np_box_list.BoxList(
         np.array([[0.25, 0.25, 0.75, 0.75], [0.0, 0.0, 0.5, 0.75],
                   [0.5, 0.25, 1.0, 1.0], [0.0, 0.0, 1.0, 1.0]],
                  dtype=np.float32))
     self.assertAllClose(boxlist_concatenated_expected.get(),
                         boxlist_concatenated.get())
def multi_class_non_max_suppression(box_mask_list, score_thresh, iou_thresh,
                                    max_output_size):
    """Multi-class version of non maximum suppression.

  This op greedily selects a subset of detection bounding boxes, pruning
  away boxes that have high IOU (intersection over union) overlap (> thresh)
  with already selected boxes.  It operates independently for each class for
  which scores are provided (via the scores field of the input box_list),
  pruning boxes with score less than a provided threshold prior to
  applying NMS.

  Args:
    box_mask_list: np_box_mask_list.BoxMaskList holding N boxes.  Must contain a
      'scores' field representing detection scores.  This scores field is a
      tensor that can be 1 dimensional (in the case of a single class) or
      2-dimensional, in which case we assume that it takes the
      shape [num_boxes, num_classes]. We further assume that this rank is known
      statically and that scores.shape[1] is also known (i.e., the number of
      classes is fixed and known at graph construction time).
    score_thresh: scalar threshold for score (low scoring boxes are removed).
    iou_thresh: scalar threshold for IOU (boxes that that high IOU overlap
      with previously selected boxes are removed).
    max_output_size: maximum number of retained boxes per class.

  Returns:
    a box_mask_list holding M boxes with a rank-1 scores field representing
      corresponding scores for each box with scores sorted in decreasing order
      and a rank-1 classes field representing a class label for each box.
  Raises:
    ValueError: if iou_thresh is not in [0, 1] or if input box_mask_list does
      not have a valid scores field.
  """
    if not 0 <= iou_thresh <= 1.0:
        raise ValueError('thresh must be between 0 and 1')
    if not isinstance(box_mask_list, np_box_mask_list.BoxMaskList):
        raise ValueError('box_mask_list must be a box_mask_list')
    if not box_mask_list.has_field('scores'):
        raise ValueError('input box_mask_list must have \'scores\' field')
    scores = box_mask_list.get_field('scores')
    if len(scores.shape) == 1:
        scores = np.reshape(scores, [-1, 1])
    elif len(scores.shape) == 2:
        if scores.shape[1] is None:
            raise ValueError(
                'scores field must have statically defined second '
                'dimension')
    else:
        raise ValueError('scores field must be of rank 1 or 2')

    num_boxes = box_mask_list.num_boxes()
    num_scores = scores.shape[0]
    num_classes = scores.shape[1]

    if num_boxes != num_scores:
        raise ValueError('Incorrect scores field length: actual vs expected.')

    selected_boxes_list = []
    for class_idx in range(num_classes):
        box_mask_list_and_class_scores = np_box_mask_list.BoxMaskList(
            box_data=box_mask_list.get(), mask_data=box_mask_list.get_masks())
        class_scores = np.reshape(scores[0:num_scores, class_idx], [-1])
        box_mask_list_and_class_scores.add_field('scores', class_scores)
        box_mask_list_filt = filter_scores_greater_than(
            box_mask_list_and_class_scores, score_thresh)
        nms_result = non_max_suppression(box_mask_list_filt,
                                         max_output_size=max_output_size,
                                         iou_threshold=iou_thresh,
                                         score_threshold=score_thresh)
        nms_result.add_field(
            'classes',
            np.zeros_like(nms_result.get_field('scores')) + class_idx)
        selected_boxes_list.append(nms_result)
    selected_boxes = np_box_list_ops.concatenate(selected_boxes_list)
    sorted_boxes = np_box_list_ops.sort_by_field(selected_boxes, 'scores')
    return box_list_to_box_mask_list(boxlist=sorted_boxes)