code: # Copyright 2017 The TensorFlow Authors. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  # =============================================================================
  """MobileNet v1.
  
  MobileNet is a general architecture and can be used for multiple use cases.
  Depending on the use case, it can use different input layer size and different
  head (for example: embeddings, localization and classification).
  
  As described in https://arxiv.org/abs/1704.04861.
  
    MobileNets: Efficient Convolutional Neural Networks for
      Mobile Vision Applications
    Andrew G. Howard, Menglong Zhu, Bo Chen, Dmitry Kalenichenko, Weijun Wang,
      Tobias Weyand, Marco Andreetto, Hartwig Adam
  
  100% Mobilenet V1 (base) with input size 224x224:
  
  See mobilenet_v1()
  
  Layer                                                     params           macs
  --------------------------------------------------------------------------------
  MobilenetV1/Conv2d_0/Conv2D:                                 864      10,838,016
  MobilenetV1/Conv2d_1_depthwise/depthwise:                    288       3,612,672
  MobilenetV1/Conv2d_1_pointwise/Conv2D:                     2,048      25,690,112
  MobilenetV1/Conv2d_2_depthwise/depthwise:                    576       1,806,336
  MobilenetV1/Conv2d_2_pointwise/Conv2D:                     8,192      25,690,112
  MobilenetV1/Conv2d_3_depthwise/depthwise:                  1,152       3,612,672
  MobilenetV1/Conv2d_3_pointwise/Conv2D:                    16,384      51,380,224
  MobilenetV1/Conv2d_4_depthwise/depthwise:                  1,152         903,168
  MobilenetV1/Conv2d_4_pointwise/Conv2D:                    32,768      25,690,112
  MobilenetV1/Conv2d_5_depthwise/depthwise:                  2,304       1,806,336
  MobilenetV1/Conv2d_5_pointwise/Conv2D:                    65,536      51,380,224
  MobilenetV1/Conv2d_6_depthwise/depthwise:                  2,304         451,584
  MobilenetV1/Conv2d_6_pointwise/Conv2D:                   131,072      25,690,112
  MobilenetV1/Conv2d_7_depthwise/depthwise:                  4,608         903,168
  MobilenetV1/Conv2d_7_pointwise/Conv2D:                   262,144      51,380,224
  MobilenetV1/Conv2d_8_depthwise/depthwise:                  4,608         903,168
  MobilenetV1/Conv2d_8_pointwise/Conv2D:                   262,144      51,380,224
  MobilenetV1/Conv2d_9_depthwise/depthwise:                  4,608         903,168
  MobilenetV1/Conv2d_9_pointwise/Conv2D:                   262,144      51,380,224
  MobilenetV1/Conv2d_10_depthwise/depthwise:                 4,608         903,168
  MobilenetV1/Conv2d_10_pointwise/Conv2D:                  262,144      51,380,224
  MobilenetV1/Conv2d_11_depthwise/depthwise:                 4,608         903,168
  MobilenetV1/Conv2d_11_pointwise/Conv2D:                  262,144      51,380,224
  MobilenetV1/Conv2d_12_depthwise/depthwise:                 4,608         225,792
  MobilenetV1/Conv2d_12_pointwise/Conv2D:                  524,288      25,690,112
  MobilenetV1/Conv2d_13_depthwise/depthwise:                 9,216         451,584
  MobilenetV1/Conv2d_13_pointwise/Conv2D:                1,048,576      51,380,224
  --------------------------------------------------------------------------------
  Total:                                                 3,185,088     567,716,352
  
  
  75% Mobilenet V1 (base) with input size 128x128:
  
  See mobilenet_v1_075()
  
  Layer                                                     params           macs
  --------------------------------------------------------------------------------
  MobilenetV1/Conv2d_0/Conv2D:                                 648       2,654,208
  MobilenetV1/Conv2d_1_depthwise/depthwise:                    216         884,736
  MobilenetV1/Conv2d_1_pointwise/Conv2D:                     1,152       4,718,592
  MobilenetV1/Conv2d_2_depthwise/depthwise:                    432         442,368
  MobilenetV1/Conv2d_2_pointwise/Conv2D:                     4,608       4,718,592
  MobilenetV1/Conv2d_3_depthwise/depthwise:                    864         884,736
  MobilenetV1/Conv2d_3_pointwise/Conv2D:                     9,216       9,437,184
  MobilenetV1/Conv2d_4_depthwise/depthwise:                    864         221,184
  MobilenetV1/Conv2d_4_pointwise/Conv2D:                    18,432       4,718,592
  MobilenetV1/Conv2d_5_depthwise/depthwise:                  1,728         442,368
  MobilenetV1/Conv2d_5_pointwise/Conv2D:                    36,864       9,437,184
  MobilenetV1/Conv2d_6_depthwise/depthwise:                  1,728         110,592
  MobilenetV1/Conv2d_6_pointwise/Conv2D:                    73,728       4,718,592
  MobilenetV1/Conv2d_7_depthwise/depthwise:                  3,456         221,184
  MobilenetV1/Conv2d_7_pointwise/Conv2D:                   147,456       9,437,184
  MobilenetV1/Conv2d_8_depthwise/depthwise:                  3,456         221,184
  MobilenetV1/Conv2d_8_pointwise/Conv2D:                   147,456       9,437,184
  MobilenetV1/Conv2d_9_depthwise/depthwise:                  3,456         221,184
  MobilenetV1/Conv2d_9_pointwise/Conv2D:                   147,456       9,437,184
  MobilenetV1/Conv2d_10_depthwise/depthwise:                 3,456         221,184
  MobilenetV1/Conv2d_10_pointwise/Conv2D:                  147,456       9,437,184
  MobilenetV1/Conv2d_11_depthwise/depthwise:                 3,456         221,184
  MobilenetV1/Conv2d_11_pointwise/Conv2D:                  147,456       9,437,184
  MobilenetV1/Conv2d_12_depthwise/depthwise:                 3,456          55,296
  MobilenetV1/Conv2d_12_pointwise/Conv2D:                  294,912       4,718,592
  MobilenetV1/Conv2d_13_depthwise/depthwise:                 6,912         110,592
  MobilenetV1/Conv2d_13_pointwise/Conv2D:                  589,824       9,437,184
  --------------------------------------------------------------------------------
  Total:                                                 1,800,144     106,002,432
  
  """
  
  # Tensorflow mandates these.
  from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  
  from collections import namedtuple
  import functools
  
  import tensorflow.compat.v1 as tf
  import tf_slim as slim
  
  # Conv and DepthSepConv namedtuple define layers of the MobileNet architecture
  # Conv defines 3x3 convolution layers
  # DepthSepConv defines 3x3 depthwise convolution followed by 1x1 convolution.
  # stride is the stride of the convolution
  # depth is the number of channels or filters in a layer
  Conv = namedtuple('Conv', ['kernel', 'stride', 'depth'])
  DepthSepConv = namedtuple('DepthSepConv', ['kernel', 'stride', 'depth'])
  
  # MOBILENETV1_CONV_DEFS specifies the MobileNet body
  MOBILENETV1_CONV_DEFS = [
      Conv(kernel=[3, 3], stride=2, depth=32),
      DepthSepConv(kernel=[3, 3], stride=1, depth=64),
      DepthSepConv(kernel=[3, 3], stride=2, depth=128),
      DepthSepConv(kernel=[3, 3], stride=1, depth=128),
      DepthSepConv(kernel=[3, 3], stride=2, depth=256),
      DepthSepConv(kernel=[3, 3], stride=1, depth=256),
      DepthSepConv(kernel=[3, 3], stride=2, depth=512),
      DepthSepConv(kernel=[3, 3], stride=1, depth=512),
      DepthSepConv(kernel=[3, 3], stride=1, depth=512),
      DepthSepConv(kernel=[3, 3], stride=1, depth=512),
      DepthSepConv(kernel=[3, 3], stride=1, depth=512),
      DepthSepConv(kernel=[3, 3], stride=1, depth=512),
      DepthSepConv(kernel=[3, 3], stride=2, depth=1024),
      DepthSepConv(kernel=[3, 3], stride=1, depth=1024)
  ]
  
  
  def _fixed_padding(inputs, kernel_size, rate=1):
    """Pads the input along the spatial dimensions independently of input size.
  
    Pads the input such that if it was used in a convolution with 'VALID' padding,
    the output would have the same dimensions as if the unpadded input was used
    in a convolution with 'SAME' padding.
  
    Args:
      inputs: A tensor of size [batch, height_in, width_in, channels].
      kernel_size: The kernel to be used in the conv2d or max_pool2d operation.
      rate: An integer, rate for atrous convolution.
  
    Returns:
      output: A tensor of size [batch, height_out, width_out, channels] with the
        input, either intact (if kernel_size == 1) or padded (if kernel_size > 1).
    """
    kernel_size_effective = [kernel_size[0] + (kernel_size[0] - 1) * (rate - 1),
                             kernel_size[0] + (kernel_size[0] - 1) * (rate - 1)]
    pad_total = [kernel_size_effective[0] - 1, kernel_size_effective[1] - 1]
    pad_beg = [pad_total[0] // 2, pad_total[1] // 2]
    pad_end = [pad_total[0] - pad_beg[0], pad_total[1] - pad_beg[1]]
    padded_inputs = tf.pad(
        tensor=inputs,
        paddings=[[0, 0], [pad_beg[0], pad_end[0]], [pad_beg[1], pad_end[1]],
                  [0, 0]])
    return padded_inputs
  
  
  def mobilenet_v1_base(inputs,
                        final_endpoint='Conv2d_13_pointwise',
                        min_depth=8,
                        depth_multiplier=1.0,
                        conv_defs=None,
                        output_stride=None,
                        use_explicit_padding=False,
                        scope=None):
    """Mobilenet v1.
  
    Constructs a Mobilenet v1 network from inputs to the given final endpoint.
  
    Args:
      inputs: a tensor of shape [batch_size, height, width, channels].
      final_endpoint: specifies the endpoint to construct the network up to. It
        can be one of ['Conv2d_0', 'Conv2d_1_pointwise', 'Conv2d_2_pointwise',
        'Conv2d_3_pointwise', 'Conv2d_4_pointwise', 'Conv2d_5'_pointwise,
        'Conv2d_6_pointwise', 'Conv2d_7_pointwise', 'Conv2d_8_pointwise',
        'Conv2d_9_pointwise', 'Conv2d_10_pointwise', 'Conv2d_11_pointwise',
        'Conv2d_12_pointwise', 'Conv2d_13_pointwise'].
      min_depth: Minimum depth value (number of channels) for all convolution ops.
        Enforced when depth_multiplier < 1, and not an active constraint when
        depth_multiplier >= 1.
      depth_multiplier: Float multiplier for the depth (number of channels)
        for all convolution ops. The value must be greater than zero. Typical
        usage will be to set this value in (0, 1) to reduce the number of
        parameters or computation cost of the model.
      conv_defs: A list of ConvDef namedtuples specifying the net architecture.
      output_stride: An integer that specifies the requested ratio of input to
        output spatial resolution. If not None, then we invoke atrous convolution
        if necessary to prevent the network from reducing the spatial resolution
        of the activation maps. Allowed values are 8 (accurate fully convolutional
        mode), 16 (fast fully convolutional mode), 32 (classification mode).
      use_explicit_padding: Use 'VALID' padding for convolutions, but prepad
        inputs so that the output dimensions are the same as if 'SAME' padding
        were used.
      scope: Optional variable_scope.
  
    Returns:
      tensor_out: output tensor corresponding to the final_endpoint.
      end_points: a set of activations for external use, for example summaries or
                  losses.
  
    Raises:
      ValueError: if final_endpoint is not set to one of the predefined values,
                  or depth_multiplier <= 0, or the target output_stride is not
                  allowed.
    """
    depth = lambda d: max(int(d * depth_multiplier), min_depth)
    end_points = {}
  
    # Used to find thinned depths for each layer.
    if depth_multiplier <= 0:
      raise ValueError('depth_multiplier is not greater than zero.')
  
    if conv_defs is None:
      conv_defs = MOBILENETV1_CONV_DEFS
  
    if output_stride is not None and output_stride not in [8, 16, 32]:
      raise ValueError('Only allowed output_stride values are 8, 16, 32.')
  
    padding = 'SAME'
    if use_explicit_padding:
      padding = 'VALID'
    with tf.variable_scope(scope, 'MobilenetV1', [inputs]):
      with slim.arg_scope([slim.conv2d, slim.separable_conv2d], padding=padding):
        # The current_stride variable keeps track of the output stride of the
        # activations, i.e., the running product of convolution strides up to the
        # current network layer. This allows us to invoke atrous convolution
        # whenever applying the next convolution would result in the activations
        # having output stride larger than the target output_stride.
        current_stride = 1
  
        # The atrous convolution rate parameter.
        rate = 1
  
        net = inputs
        for i, conv_def in enumerate(conv_defs):
          end_point_base = 'Conv2d_%d' % i
  
          if output_stride is not None and current_stride == output_stride:
            # If we have reached the target output_stride, then we need to employ
            # atrous convolution with stride=1 and multiply the atrous rate by the
            # current unit's stride for use in subsequent layers.
            layer_stride = 1
            layer_rate = rate
            rate *= conv_def.stride
          else:
            layer_stride = conv_def.stride
            layer_rate = 1
            current_stride *= conv_def.stride
  
          if isinstance(conv_def, Conv):
            end_point = end_point_base
            if use_explicit_padding:
              net = _fixed_padding(net, conv_def.kernel)
            net = slim.conv2d(net, depth(conv_def.depth), conv_def.kernel,
                              stride=conv_def.stride,
                              scope=end_point)
            end_points[end_point] = net
            if end_point == final_endpoint:
              return net, end_points
  
          elif isinstance(conv_def, DepthSepConv):
            end_point = end_point_base + '_depthwise'
  
            # By passing filters=None
            # separable_conv2d produces only a depthwise convolution layer
            if use_explicit_padding:
              net = _fixed_padding(net, conv_def.kernel, layer_rate)
            net = slim.separable_conv2d(net, None, conv_def.kernel,
                                        depth_multiplier=1,
                                        stride=layer_stride,
                                        rate=layer_rate,
                                        scope=end_point)
  
            end_points[end_point] = net
            if end_point == final_endpoint:
              return net, end_points
  
            end_point = end_point_base + '_pointwise'
  
            net = slim.conv2d(net, depth(conv_def.depth), [1, 1],
                              stride=1,
                              scope=end_point)
  
            end_points[end_point] = net
            if end_point == final_endpoint:
              return net, end_points
          else:
            raise ValueError('Unknown convolution type %s for layer %d'
                             % (conv_def.ltype, i))
    raise ValueError('Unknown final endpoint %s' % final_endpoint)
  
  
  def mobilenet_v1(inputs,
                   num_classes=1000,
                   dropout_keep_prob=0.999,
                   is_training=True,
                   min_depth=8,
                   depth_multiplier=1.0,
                   conv_defs=None,
                   prediction_fn=slim.softmax,
                   spatial_squeeze=True,
                   reuse=None,
                   scope='MobilenetV1',
                   global_pool=False):
    """Mobilenet v1 model for classification.
  
    Args:
      inputs: a tensor of shape [batch_size, height, width, channels].
      num_classes: number of predicted classes. If 0 or None, the logits layer
        is omitted and the input features to the logits layer (before dropout)
        are returned instead.
      dropout_keep_prob: the percentage of activation values that are retained.
      is_training: whether is training or not.
      min_depth: Minimum depth value (number of channels) for all convolution ops.
        Enforced when depth_multiplier < 1, and not an active constraint when
        depth_multiplier >= 1.
      depth_multiplier: Float multiplier for the depth (number of channels)
        for all convolution ops. The value must be greater than zero. Typical
        usage will be to set this value in (0, 1) to reduce the number of
        parameters or computation cost of the model.
      conv_defs: A list of ConvDef namedtuples specifying the net architecture.
      prediction_fn: a function to get predictions out of logits.
      spatial_squeeze: if True, logits is of shape is [B, C], if false logits is
          of shape [B, 1, 1, C], where B is batch_size and C is number of classes.
      reuse: whether or not the network and its variables should be reused. To be
        able to reuse 'scope' must be given.
      scope: Optional variable_scope.
      global_pool: Optional boolean flag to control the avgpooling before the
        logits layer. If false or unset, pooling is done with a fixed window
        that reduces default-sized inputs to 1x1, while larger inputs lead to
        larger outputs. If true, any input size is pooled down to 1x1.
  
    Returns:
      net: a 2D Tensor with the logits (pre-softmax activations) if num_classes
        is a non-zero integer, or the non-dropped-out input to the logits layer
        if num_classes is 0 or None.
      end_points: a dictionary from components of the network to the corresponding
        activation.
  
    Raises:
      ValueError: Input rank is invalid.
    """
    input_shape = inputs.get_shape().as_list()
    if len(input_shape) != 4:
      raise ValueError('Invalid input tensor rank, expected 4, was: %d' %
                       len(input_shape))
  
    with tf.variable_scope(
        scope, 'MobilenetV1', [inputs], reuse=reuse) as scope:
      with slim.arg_scope([slim.batch_norm, slim.dropout],
                          is_training=is_training):
        net, end_points = mobilenet_v1_base(inputs, scope=scope,
                                            min_depth=min_depth,
                                            depth_multiplier=depth_multiplier,
                                            conv_defs=conv_defs)
        with tf.variable_scope('Logits'):
          if global_pool:
            # Global average pooling.
            net = tf.reduce_mean(
                input_tensor=net, axis=[1, 2], keepdims=True, name='global_pool')
            end_points['global_pool'] = net
          else:
            # Pooling with a fixed kernel size.
            kernel_size = _reduced_kernel_size_for_small_input(net, [7, 7])
            net = slim.avg_pool2d(net, kernel_size, padding='VALID',
                                  scope='AvgPool_1a')
            end_points['AvgPool_1a'] = net
          if not num_classes:
            return net, end_points
          # 1 x 1 x 1024
          net = slim.dropout(net, keep_prob=dropout_keep_prob, scope='Dropout_1b')
          logits = slim.conv2d(net, num_classes, [1, 1], activation_fn=None,
                               normalizer_fn=None, scope='Conv2d_1c_1x1')
          if spatial_squeeze:
            logits = tf.squeeze(logits, [1, 2], name='SpatialSqueeze')
        end_points['Logits'] = logits
        if prediction_fn:
          end_points['Predictions'] = prediction_fn(logits, scope='Predictions')
    return logits, end_points
  
  mobilenet_v1.default_image_size = 224
  
  
  def wrapped_partial(func, *args, **kwargs):
    partial_func = functools.partial(func, *args, **kwargs)
    functools.update_wrapper(partial_func, func)
    return partial_func
  
  
  mobilenet_v1_075 = wrapped_partial(mobilenet_v1, depth_multiplier=0.75)
  mobilenet_v1_050 = wrapped_partial(mobilenet_v1, depth_multiplier=0.50)
  mobilenet_v1_025 = wrapped_partial(mobilenet_v1, depth_multiplier=0.25)
  
  
  def _reduced_kernel_size_for_small_input(input_tensor, kernel_size):
    """Define kernel size which is automatically reduced for small input.
  
    If the shape of the input images is unknown at graph construction time this
    function assumes that the input images are large enough.
  
    Args:
      input_tensor: input tensor of size [batch_size, height, width, channels].
      kernel_size: desired kernel size of length 2: [kernel_height, kernel_width]
  
    Returns:
      a tensor with the kernel size.
    """
    shape = input_tensor.get_shape().as_list()
    if shape[1] is None or shape[2] is None:
      kernel_size_out = kernel_size
    else:
      kernel_size_out = [min(shape[1], kernel_size[0]),
                         min(shape[2], kernel_size[1])]
    return kernel_size_out
  
  
  def mobilenet_v1_arg_scope(
      is_training=True,
      weight_decay=0.00004,
      stddev=0.09,
      regularize_depthwise=False,
      batch_norm_decay=0.9997,
      batch_norm_epsilon=0.001,
      batch_norm_updates_collections=tf.GraphKeys.UPDATE_OPS,
      normalizer_fn=slim.batch_norm):
    """Defines the default MobilenetV1 arg scope.
  
    Args:
      is_training: Whether or not we're training the model. If this is set to
        None, the parameter is not added to the batch_norm arg_scope.
      weight_decay: The weight decay to use for regularizing the model.
      stddev: The standard deviation of the trunctated normal weight initializer.
      regularize_depthwise: Whether or not apply regularization on depthwise.
      batch_norm_decay: Decay for batch norm moving average.
      batch_norm_epsilon: Small float added to variance to avoid dividing by zero
        in batch norm.
      batch_norm_updates_collections: Collection for the update ops for
        batch norm.
      normalizer_fn: Normalization function to apply after convolution.
  
    Returns:
      An `arg_scope` to use for the mobilenet v1 model.
    """
    batch_norm_params = {
        'center': True,
        'scale': True,
        'decay': batch_norm_decay,
        'epsilon': batch_norm_epsilon,
        'updates_collections': batch_norm_updates_collections,
    }
    if is_training is not None:
      batch_norm_params['is_training'] = is_training
  
    # Set weight_decay for weights in Conv and DepthSepConv layers.
    weights_init = tf.truncated_normal_initializer(stddev=stddev)
    regularizer = slim.l2_regularizer(weight_decay)
    if regularize_depthwise:
      depthwise_regularizer = regularizer
    else:
      depthwise_regularizer = None
    with slim.arg_scope([slim.conv2d, slim.separable_conv2d],
                        weights_initializer=weights_init,
                        activation_fn=tf.nn.relu6, normalizer_fn=normalizer_fn):
      with slim.arg_scope([slim.batch_norm], **batch_norm_params):
        with slim.arg_scope([slim.conv2d], weights_regularizer=regularizer):
          with slim.arg_scope([slim.separable_conv2d],
                              weights_regularizer=depthwise_regularizer) as sc:
            return sc
   # Copyright 2017 The TensorFlow Authors. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  # ==============================================================================
  
  """Bounding Box List operations.
  
  Example box operations that are supported:
    * areas: compute bounding box areas
    * iou: pairwise intersection-over-union scores
    * sq_dist: pairwise distances between bounding boxes
  
  Whenever box_list_ops functions output a BoxList, the fields of the incoming
  BoxList are retained unless documented otherwise.
  """
  from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  
  from six.moves import range
  import tensorflow.compat.v1 as tf
  
  from object_detection.core import box_list
  from object_detection.utils import ops
  from object_detection.utils import shape_utils
  
  
  class SortOrder(object):
    """Enum class for sort order.
  
    Attributes:
      ascend: ascend order.
      descend: descend order.
    """
    ascend = 1
    descend = 2
  
  
  def area(boxlist, scope=None):
    """Computes area of boxes.
  
    Args:
      boxlist: BoxList holding N boxes
      scope: name scope.
  
    Returns:
      a tensor with shape [N] representing box areas.
    """
    with tf.name_scope(scope, 'Area'):
      y_min, x_min, y_max, x_max = tf.split(
          value=boxlist.get(), num_or_size_splits=4, axis=1)
      return tf.squeeze((y_max - y_min) * (x_max - x_min), [1])
  
  
  def height_width(boxlist, scope=None):
    """Computes height and width of boxes in boxlist.
  
    Args:
      boxlist: BoxList holding N boxes
      scope: name scope.
  
    Returns:
      Height: A tensor with shape [N] representing box heights.
      Width: A tensor with shape [N] representing box widths.
    """
    with tf.name_scope(scope, 'HeightWidth'):
      y_min, x_min, y_max, x_max = tf.split(
          value=boxlist.get(), num_or_size_splits=4, axis=1)
      return tf.squeeze(y_max - y_min, [1]), tf.squeeze(x_max - x_min, [1])
  
  
  def scale(boxlist, y_scale, x_scale, scope=None):
    """scale box coordinates in x and y dimensions.
  
    Args:
      boxlist: BoxList holding N boxes
      y_scale: (float) scalar tensor
      x_scale: (float) scalar tensor
      scope: name scope.
  
    Returns:
      boxlist: BoxList holding N boxes
    """
    with tf.name_scope(scope, 'Scale'):
      y_scale = tf.cast(y_scale, tf.float32)
      x_scale = tf.cast(x_scale, tf.float32)
      y_min, x_min, y_max, x_max = tf.split(
          value=boxlist.get(), num_or_size_splits=4, axis=1)
      y_min = y_scale * y_min
      y_max = y_scale * y_max
      x_min = x_scale * x_min
      x_max = x_scale * x_max
      scaled_boxlist = box_list.BoxList(
          tf.concat([y_min, x_min, y_max, x_max], 1))
      return _copy_extra_fields(scaled_boxlist, boxlist)
  
  
  def scale_height_width(boxlist, y_scale, x_scale, scope=None):
    """Scale the height and width of boxes, leaving centers unchanged.
  
    Args:
      boxlist: BoxList holding N boxes
      y_scale: (float) scalar tensor
      x_scale: (float) scalar tensor
      scope: name scope.
  
    Returns:
      boxlist: BoxList holding N boxes
    """
    with tf.name_scope(scope, 'ScaleHeightWidth'):
      y_scale = tf.cast(y_scale, tf.float32)
      x_scale = tf.cast(x_scale, tf.float32)
      yc, xc, height_orig, width_orig = boxlist.get_center_coordinates_and_sizes()
      y_min = yc - 0.5 * y_scale * height_orig
      y_max = yc + 0.5 * y_scale * height_orig
      x_min = xc - 0.5 * x_scale * width_orig
      x_max = xc + 0.5 * x_scale * width_orig
      scaled_boxlist = box_list.BoxList(
          tf.stack([y_min, x_min, y_max, x_max], 1))
      return _copy_extra_fields(scaled_boxlist, boxlist)
  
  
  def clip_to_window(boxlist, window, filter_nonoverlapping=True, scope=None):
    """Clip bounding boxes to a window.
  
    This op clips any input bounding boxes (represented by bounding box
    corners) to a window, optionally filtering out boxes that do not
    overlap at all with the window.
  
    Args:
      boxlist: BoxList holding M_in boxes
      window: a tensor of shape [4] representing the [y_min, x_min, y_max, x_max]
        window to which the op should clip boxes.
      filter_nonoverlapping: whether to filter out boxes that do not overlap at
        all with the window.
      scope: name scope.
  
    Returns:
      a BoxList holding M_out boxes where M_out <= M_in
    """
    with tf.name_scope(scope, 'ClipToWindow'):
      y_min, x_min, y_max, x_max = tf.split(
          value=boxlist.get(), num_or_size_splits=4, axis=1)
      win_y_min, win_x_min, win_y_max, win_x_max = tf.unstack(window)
      y_min_clipped = tf.maximum(tf.minimum(y_min, win_y_max), win_y_min)
      y_max_clipped = tf.maximum(tf.minimum(y_max, win_y_max), win_y_min)
      x_min_clipped = tf.maximum(tf.minimum(x_min, win_x_max), win_x_min)
      x_max_clipped = tf.maximum(tf.minimum(x_max, win_x_max), win_x_min)
      clipped = box_list.BoxList(
          tf.concat([y_min_clipped, x_min_clipped, y_max_clipped, x_max_clipped],
                    1))
      clipped = _copy_extra_fields(clipped, boxlist)
      if filter_nonoverlapping:
        areas = area(clipped)
        nonzero_area_indices = tf.cast(
            tf.reshape(tf.where(tf.greater(areas, 0.0)), [-1]), tf.int32)
        clipped = gather(clipped, nonzero_area_indices)
      return clipped
  
  
  def prune_outside_window(boxlist, window, scope=None):
    """Prunes bounding boxes that fall outside a given window.
  
    This function prunes bounding boxes that even partially fall outside the given
    window. See also clip_to_window which only prunes bounding boxes that fall
    completely outside the window, and clips any bounding boxes that partially
    overflow.
  
    Args:
      boxlist: a BoxList holding M_in boxes.
      window: a float tensor of shape [4] representing [ymin, xmin, ymax, xmax]
        of the window
      scope: name scope.
  
    Returns:
      pruned_corners: a tensor with shape [M_out, 4] where M_out <= M_in
      valid_indices: a tensor with shape [M_out] indexing the valid bounding boxes
       in the input tensor.
    """
    with tf.name_scope(scope, 'PruneOutsideWindow'):
      y_min, x_min, y_max, x_max = tf.split(
          value=boxlist.get(), num_or_size_splits=4, axis=1)
      win_y_min, win_x_min, win_y_max, win_x_max = tf.unstack(window)
      coordinate_violations = tf.concat([
          tf.less(y_min, win_y_min), tf.less(x_min, win_x_min),
          tf.greater(y_max, win_y_max), tf.greater(x_max, win_x_max)
      ], 1)
      valid_indices = tf.reshape(
          tf.where(tf.logical_not(tf.reduce_any(coordinate_violations, 1))), [-1])
      return gather(boxlist, valid_indices), valid_indices
  
  
  def prune_completely_outside_window(boxlist, window, scope=None):
    """Prunes bounding boxes that fall completely outside of the given window.
  
    The function clip_to_window prunes bounding boxes that fall
    completely outside the window, but also clips any bounding boxes that
    partially overflow. This function does not clip partially overflowing boxes.
  
    Args:
      boxlist: a BoxList holding M_in boxes.
      window: a float tensor of shape [4] representing [ymin, xmin, ymax, xmax]
        of the window
      scope: name scope.
  
    Returns:
      pruned_boxlist: a new BoxList with all bounding boxes partially or fully in
        the window.
      valid_indices: a tensor with shape [M_out] indexing the valid bounding boxes
       in the input tensor.
    """
    with tf.name_scope(scope, 'PruneCompleteleyOutsideWindow'):
      y_min, x_min, y_max, x_max = tf.split(
          value=boxlist.get(), num_or_size_splits=4, axis=1)
      win_y_min, win_x_min, win_y_max, win_x_max = tf.unstack(window)
      coordinate_violations = tf.concat([
          tf.greater_equal(y_min, win_y_max), tf.greater_equal(x_min, win_x_max),
          tf.less_equal(y_max, win_y_min), tf.less_equal(x_max, win_x_min)
      ], 1)
      valid_indices = tf.reshape(
          tf.where(tf.logical_not(tf.reduce_any(coordinate_violations, 1))), [-1])
      return gather(boxlist, valid_indices), valid_indices
  
  
  def intersection(boxlist1, boxlist2, scope=None):
    """Compute pairwise intersection areas between boxes.
  
    Args:
      boxlist1: BoxList holding N boxes
      boxlist2: BoxList holding M boxes
      scope: name scope.
  
    Returns:
      a tensor with shape [N, M] representing pairwise intersections
    """
    with tf.name_scope(scope, 'Intersection'):
      y_min1, x_min1, y_max1, x_max1 = tf.split(
          value=boxlist1.get(), num_or_size_splits=4, axis=1)
      y_min2, x_min2, y_max2, x_max2 = tf.split(
          value=boxlist2.get(), num_or_size_splits=4, axis=1)
      all_pairs_min_ymax = tf.minimum(y_max1, tf.transpose(y_max2))
      all_pairs_max_ymin = tf.maximum(y_min1, tf.transpose(y_min2))
      intersect_heights = tf.maximum(0.0, all_pairs_min_ymax - all_pairs_max_ymin)
      all_pairs_min_xmax = tf.minimum(x_max1, tf.transpose(x_max2))
      all_pairs_max_xmin = tf.maximum(x_min1, tf.transpose(x_min2))
      intersect_widths = tf.maximum(0.0, all_pairs_min_xmax - all_pairs_max_xmin)
      return intersect_heights * intersect_widths
  
  
  def matched_intersection(boxlist1, boxlist2, scope=None):
    """Compute intersection areas between corresponding boxes in two boxlists.
  
    Args:
      boxlist1: BoxList holding N boxes
      boxlist2: BoxList holding N boxes
      scope: name scope.
  
    Returns:
      a tensor with shape [N] representing pairwise intersections
    """
    with tf.name_scope(scope, 'MatchedIntersection'):
      y_min1, x_min1, y_max1, x_max1 = tf.split(
          value=boxlist1.get(), num_or_size_splits=4, axis=1)
      y_min2, x_min2, y_max2, x_max2 = tf.split(
          value=boxlist2.get(), num_or_size_splits=4, axis=1)
      min_ymax = tf.minimum(y_max1, y_max2)
      max_ymin = tf.maximum(y_min1, y_min2)
      intersect_heights = tf.maximum(0.0, min_ymax - max_ymin)
      min_xmax = tf.minimum(x_max1, x_max2)
      max_xmin = tf.maximum(x_min1, x_min2)
      intersect_widths = tf.maximum(0.0, min_xmax - max_xmin)
      return tf.reshape(intersect_heights * intersect_widths, [-1])
  
  
  def iou(boxlist1, boxlist2, scope=None):
    """Computes pairwise intersection-over-union between box collections.
  
    Args:
      boxlist1: BoxList holding N boxes
      boxlist2: BoxList holding M boxes
      scope: name scope.
  
    Returns:
      a tensor with shape [N, M] representing pairwise iou scores.
    """
    with tf.name_scope(scope, 'IOU'):
      intersections = intersection(boxlist1, boxlist2)
      areas1 = area(boxlist1)
      areas2 = area(boxlist2)
      unions = (
          tf.expand_dims(areas1, 1) + tf.expand_dims(areas2, 0) - intersections)
      return tf.where(
          tf.equal(intersections, 0.0),
          tf.zeros_like(intersections), tf.truediv(intersections, unions))
  
  
  def l1(boxlist1, boxlist2, scope=None):
    """Computes l1 loss (pairwise) between two boxlists.
  
    Args:
      boxlist1: BoxList holding N boxes
      boxlist2: BoxList holding M boxes
      scope: name scope.
  
    Returns:
      a tensor with shape [N, M] representing the pairwise L1 loss.
    """
    with tf.name_scope(scope, 'PairwiseL1'):
      ycenter1, xcenter1, h1, w1 = boxlist1.get_center_coordinates_and_sizes()
      ycenter2, xcenter2, h2, w2 = boxlist2.get_center_coordinates_and_sizes()
      ycenters = tf.abs(tf.expand_dims(ycenter2, axis=0) - tf.expand_dims(
          tf.transpose(ycenter1), axis=1))
      xcenters = tf.abs(tf.expand_dims(xcenter2, axis=0) - tf.expand_dims(
          tf.transpose(xcenter1), axis=1))
      heights = tf.abs(tf.expand_dims(h2, axis=0) - tf.expand_dims(
          tf.transpose(h1), axis=1))
      widths = tf.abs(tf.expand_dims(w2, axis=0) - tf.expand_dims(
          tf.transpose(w1), axis=1))
      return ycenters + xcenters + heights + widths
  
  
  def giou(boxlist1, boxlist2, scope=None):
    """Computes pairwise generalized IOU between two boxlists.
  
    Args:
      boxlist1: BoxList holding N boxes
      boxlist2: BoxList holding M boxes
      scope: name scope.
  
    Returns:
      a tensor with shape [N, M] representing the pairwise GIoU loss.
    """
    with tf.name_scope(scope, 'PairwiseGIoU'):
      n = boxlist1.num_boxes()
      m = boxlist2.num_boxes()
      boxes1 = tf.repeat(boxlist1.get(), repeats=m, axis=0)
      boxes2 = tf.tile(boxlist2.get(), multiples=[n, 1])
      return tf.reshape(ops.giou(boxes1, boxes2), [n, m])
  
  
  def matched_iou(boxlist1, boxlist2, scope=None):
    """Compute intersection-over-union between corresponding boxes in boxlists.
  
    Args:
      boxlist1: BoxList holding N boxes
      boxlist2: BoxList holding N boxes
      scope: name scope.
  
    Returns:
      a tensor with shape [N] representing pairwise iou scores.
    """
    with tf.name_scope(scope, 'MatchedIOU'):
      intersections = matched_intersection(boxlist1, boxlist2)
      areas1 = area(boxlist1)
      areas2 = area(boxlist2)
      unions = areas1 + areas2 - intersections
      return tf.where(
          tf.equal(intersections, 0.0),
          tf.zeros_like(intersections), tf.truediv(intersections, unions))
  
  
  def ioa(boxlist1, boxlist2, scope=None):
    """Computes pairwise intersection-over-area between box collections.
  
    intersection-over-area (IOA) between two boxes box1 and box2 is defined as
    their intersection area over box2's area. Note that ioa is not symmetric,
    that is, ioa(box1, box2) != ioa(box2, box1).
  
    Args:
      boxlist1: BoxList holding N boxes
      boxlist2: BoxList holding M boxes
      scope: name scope.
  
    Returns:
      a tensor with shape [N, M] representing pairwise ioa scores.
    """
    with tf.name_scope(scope, 'IOA'):
      intersections = intersection(boxlist1, boxlist2)
      areas = tf.expand_dims(area(boxlist2), 0)
      return tf.truediv(intersections, areas)
  
  
  def prune_non_overlapping_boxes(
      boxlist1, boxlist2, min_overlap=0.0, scope=None):
    """Prunes the boxes in boxlist1 that overlap less than thresh with boxlist2.
  
    For each box in boxlist1, we want its IOA to be more than minoverlap with
    at least one of the boxes in boxlist2. If it does not, we remove it.
  
    Args:
      boxlist1: BoxList holding N boxes.
      boxlist2: BoxList holding M boxes.
      min_overlap: Minimum required overlap between boxes, to count them as
                  overlapping.
      scope: name scope.
  
    Returns:
      new_boxlist1: A pruned boxlist with size [N', 4].
      keep_inds: A tensor with shape [N'] indexing kept bounding boxes in the
        first input BoxList `boxlist1`.
    """
    with tf.name_scope(scope, 'PruneNonOverlappingBoxes'):
      ioa_ = ioa(boxlist2, boxlist1)  # [M, N] tensor
      ioa_ = tf.reduce_max(ioa_, reduction_indices=[0])  # [N] tensor
      keep_bool = tf.greater_equal(ioa_, tf.constant(min_overlap))
      keep_inds = tf.squeeze(tf.where(keep_bool), axis=[1])
      new_boxlist1 = gather(boxlist1, keep_inds)
      return new_boxlist1, keep_inds
  
  
  def prune_small_boxes(boxlist, min_side, scope=None):
    """Prunes small boxes in the boxlist which have a side smaller than min_side.
  
    Args:
      boxlist: BoxList holding N boxes.
      min_side: Minimum width AND height of box to survive pruning.
      scope: name scope.
  
    Returns:
      A pruned boxlist.
    """
    with tf.name_scope(scope, 'PruneSmallBoxes'):
      height, width = height_width(boxlist)
      is_valid = tf.logical_and(tf.greater_equal(width, min_side),
                                tf.greater_equal(height, min_side))
      return gather(boxlist, tf.reshape(tf.where(is_valid), [-1]))
  
  
  def change_coordinate_frame(boxlist, window, scope=None):
    """Change coordinate frame of the boxlist to be relative to window's frame.
  
    Given a window of the form [ymin, xmin, ymax, xmax],
    changes bounding box coordinates from boxlist to be relative to this window
    (e.g., the min corner maps to (0,0) and the max corner maps to (1,1)).
  
    An example use case is data augmentation: where we are given groundtruth
    boxes (boxlist) and would like to randomly crop the image to some
    window (window). In this case we need to change the coordinate frame of
    each groundtruth box to be relative to this new window.
  
    Args:
      boxlist: A BoxList object holding N boxes.
      window: A rank 1 tensor [4].
      scope: name scope.
  
    Returns:
      Returns a BoxList object with N boxes.
    """
    with tf.name_scope(scope, 'ChangeCoordinateFrame'):
      win_height = window[2] - window[0]
      win_width = window[3] - window[1]
      boxlist_new = scale(box_list.BoxList(
          boxlist.get() - [window[0], window[1], window[0], window[1]]),
                          1.0 / win_height, 1.0 / win_width)
      boxlist_new = _copy_extra_fields(boxlist_new, boxlist)
      return boxlist_new
  
  
  def sq_dist(boxlist1, boxlist2, scope=None):
    """Computes the pairwise squared distances between box corners.
  
    This op treats each box as if it were a point in a 4d Euclidean space and
    computes pairwise squared distances.
  
    Mathematically, we are given two matrices of box coordinates X and Y,
    where X(i,:) is the i'th row of X, containing the 4 numbers defining the
    corners of the i'th box in boxlist1. Similarly Y(j,:) corresponds to
    boxlist2.  We compute
    Z(i,j) = ||X(i,:) - Y(j,:)||^2
           = ||X(i,:)||^2 + ||Y(j,:)||^2 - 2 X(i,:)' * Y(j,:),
  
    Args:
      boxlist1: BoxList holding N boxes
      boxlist2: BoxList holding M boxes
      scope: name scope.
  
    Returns:
      a tensor with shape [N, M] representing pairwise distances
    """
    with tf.name_scope(scope, 'SqDist'):
      sqnorm1 = tf.reduce_sum(tf.square(boxlist1.get()), 1, keep_dims=True)
      sqnorm2 = tf.reduce_sum(tf.square(boxlist2.get()), 1, keep_dims=True)
      innerprod = tf.matmul(boxlist1.get(), boxlist2.get(),
                            transpose_a=False, transpose_b=True)
      return sqnorm1 + tf.transpose(sqnorm2) - 2.0 * innerprod
  
  
  def boolean_mask(boxlist, indicator, fields=None, scope=None,
                   use_static_shapes=False, indicator_sum=None):
    """Select boxes from BoxList according to indicator and return new BoxList.
  
    `boolean_mask` returns the subset of boxes that are marked as "True" by the
    indicator tensor. By default, `boolean_mask` returns boxes corresponding to
    the input index list, as well as all additional fields stored in the boxlist
    (indexing into the first dimension).  However one can optionally only draw
    from a subset of fields.
  
    Args:
      boxlist: BoxList holding N boxes
      indicator: a rank-1 boolean tensor
      fields: (optional) list of fields to also gather from.  If None (default),
        all fields are gathered from.  Pass an empty fields list to only gather
        the box coordinates.
      scope: name scope.
      use_static_shapes: Whether to use an implementation with static shape
        gurantees.
      indicator_sum: An integer containing the sum of `indicator` vector. Only
        required if `use_static_shape` is True.
  
    Returns:
      subboxlist: a BoxList corresponding to the subset of the input BoxList
        specified by indicator
    Raises:
      ValueError: if `indicator` is not a rank-1 boolean tensor.
    """
    with tf.name_scope(scope, 'BooleanMask'):
      if indicator.shape.ndims != 1:
        raise ValueError('indicator should have rank 1')
      if indicator.dtype != tf.bool:
        raise ValueError('indicator should be a boolean tensor')
      if use_static_shapes:
        if not (indicator_sum and isinstance(indicator_sum, int)):
          raise ValueError('`indicator_sum` must be a of type int')
        selected_positions = tf.cast(indicator, dtype=tf.float32)
        indexed_positions = tf.cast(
            tf.multiply(
                tf.cumsum(selected_positions), selected_positions),
            dtype=tf.int32)
        one_hot_selector = tf.one_hot(
            indexed_positions - 1, indicator_sum, dtype=tf.float32)
        sampled_indices = tf.cast(
            tf.tensordot(
                tf.cast(tf.range(tf.shape(indicator)[0]), dtype=tf.float32),
                one_hot_selector,
                axes=[0, 0]),
            dtype=tf.int32)
        return gather(boxlist, sampled_indices, use_static_shapes=True)
      else:
        subboxlist = box_list.BoxList(tf.boolean_mask(boxlist.get(), indicator))
        if fields is None:
          fields = boxlist.get_extra_fields()
        for field in fields:
          if not boxlist.has_field(field):
            raise ValueError('boxlist must contain all specified fields')
          subfieldlist = tf.boolean_mask(boxlist.get_field(field), indicator)
          subboxlist.add_field(field, subfieldlist)
        return subboxlist
  
  
  def gather(boxlist, indices, fields=None, scope=None, use_static_shapes=False):
    """Gather boxes from BoxList according to indices and return new BoxList.
  
    By default, `gather` returns boxes corresponding to the input index list, as
    well as all additional fields stored in the boxlist (indexing into the
    first dimension).  However one can optionally only gather from a
    subset of fields.
  
    Args:
      boxlist: BoxList holding N boxes
      indices: a rank-1 tensor of type int32 / int64
      fields: (optional) list of fields to also gather from.  If None (default),
        all fields are gathered from.  Pass an empty fields list to only gather
        the box coordinates.
      scope: name scope.
      use_static_shapes: Whether to use an implementation with static shape
        gurantees.
  
    Returns:
      subboxlist: a BoxList corresponding to the subset of the input BoxList
      specified by indices
    Raises:
      ValueError: if specified field is not contained in boxlist or if the
        indices are not of type int32
    """
    with tf.name_scope(scope, 'Gather'):
      if len(indices.shape.as_list()) != 1:
        raise ValueError('indices should have rank 1')
      if indices.dtype != tf.int32 and indices.dtype != tf.int64:
        raise ValueError('indices should be an int32 / int64 tensor')
      gather_op = tf.gather
      if use_static_shapes:
        gather_op = ops.matmul_gather_on_zeroth_axis
      subboxlist = box_list.BoxList(gather_op(boxlist.get(), indices))
      if fields is None:
        fields = boxlist.get_extra_fields()
      fields += ['boxes']
      for field in fields:
        if not boxlist.has_field(field):
          raise ValueError('boxlist must contain all specified fields')
        subfieldlist = gather_op(boxlist.get_field(field), indices)
        subboxlist.add_field(field, subfieldlist)
      return subboxlist
  
  
  def concatenate(boxlists, fields=None, scope=None):
    """Concatenate list of BoxLists.
  
    This op concatenates a list of input BoxLists into a larger BoxList.  It also
    handles concatenation of BoxList fields as long as the field tensor shapes
    are equal except for the first dimension.
  
    Args:
      boxlists: list of BoxList objects
      fields: optional list of fields to also concatenate.  By default, all
        fields from the first BoxList in the list are included in the
        concatenation.
      scope: name scope.
  
    Returns:
      a BoxList with number of boxes equal to
        sum([boxlist.num_boxes() for boxlist in BoxList])
    Raises:
      ValueError: if boxlists is invalid (i.e., is not a list, is empty, or
        contains non BoxList objects), or if requested fields are not contained in
        all boxlists
    """
    with tf.name_scope(scope, 'Concatenate'):
      if not isinstance(boxlists, list):
        raise ValueError('boxlists should be a list')
      if not boxlists:
        raise ValueError('boxlists should have nonzero length')
      for boxlist in boxlists:
        if not isinstance(boxlist, box_list.BoxList):
          raise ValueError('all elements of boxlists should be BoxList objects')
      concatenated = box_list.BoxList(
          tf.concat([boxlist.get() for boxlist in boxlists], 0))
      if fields is None:
        fields = boxlists[0].get_extra_fields()
      for field in fields:
        first_field_shape = boxlists[0].get_field(field).get_shape().as_list()
        first_field_shape[0] = -1
        if None in first_field_shape:
          raise ValueError('field %s must have fully defined shape except for the'
                           ' 0th dimension.' % field)
        for boxlist in boxlists:
          if not boxlist.has_field(field):
            raise ValueError('boxlist must contain all requested fields')
          field_shape = boxlist.get_field(field).get_shape().as_list()
          field_shape[0] = -1
          if field_shape != first_field_shape:
            raise ValueError('field %s must have same shape for all boxlists '
                             'except for the 0th dimension.' % field)
        concatenated_field = tf.concat(
            [boxlist.get_field(field) for boxlist in boxlists], 0)
        concatenated.add_field(field, concatenated_field)
      return concatenated
  
  
  def sort_by_field(boxlist, field, order=SortOrder.descend, scope=None):
    """Sort boxes and associated fields according to a scalar field.
  
    A common use case is reordering the boxes according to descending scores.
  
    Args:
      boxlist: BoxList holding N boxes.
      field: A BoxList field for sorting and reordering the BoxList.
      order: (Optional) descend or ascend. Default is descend.
      scope: name scope.
  
    Returns:
      sorted_boxlist: A sorted BoxList with the field in the specified order.
  
    Raises:
      ValueError: if specified field does not exist
      ValueError: if the order is not either descend or ascend
    """
    with tf.name_scope(scope, 'SortByField'):
      if order != SortOrder.descend and order != SortOrder.ascend:
        raise ValueError('Invalid sort order')
  
      field_to_sort = boxlist.get_field(field)
      if len(field_to_sort.shape.as_list()) != 1:
        raise ValueError('Field should have rank 1')
  
      num_boxes = boxlist.num_boxes()
      num_entries = tf.size(field_to_sort)
      length_assert = tf.Assert(
          tf.equal(num_boxes, num_entries),
          ['Incorrect field size: actual vs expected.', num_entries, num_boxes])
  
      with tf.control_dependencies([length_assert]):
        _, sorted_indices = tf.nn.top_k(field_to_sort, num_boxes, sorted=True)
  
      if order == SortOrder.ascend:
        sorted_indices = tf.reverse_v2(sorted_indices, [0])
  
      return gather(boxlist, sorted_indices)
  
  
  def visualize_boxes_in_image(image, boxlist, normalized=False, scope=None):
    """Overlay bounding box list on image.
  
    Currently this visualization plots a 1 pixel thick red bounding box on top
    of the image.  Note that tf.image.draw_bounding_boxes essentially is
    1 indexed.
  
    Args:
      image: an image tensor with shape [height, width, 3]
      boxlist: a BoxList
      normalized: (boolean) specify whether corners are to be interpreted
        as absolute coordinates in image space or normalized with respect to the
        image size.
      scope: name scope.
  
    Returns:
      image_and_boxes: an image tensor with shape [height, width, 3]
    """
    with tf.name_scope(scope, 'VisualizeBoxesInImage'):
      if not normalized:
        height, width, _ = tf.unstack(tf.shape(image))
        boxlist = scale(boxlist,
                        1.0 / tf.cast(height, tf.float32),
                        1.0 / tf.cast(width, tf.float32))
      corners = tf.expand_dims(boxlist.get(), 0)
      image = tf.expand_dims(image, 0)
      return tf.squeeze(tf.image.draw_bounding_boxes(image, corners), [0])
  
  
  def filter_field_value_equals(boxlist, field, value, scope=None):
    """Filter to keep only boxes with field entries equal to the given value.
  
    Args:
      boxlist: BoxList holding N boxes.
      field: field name for filtering.
      value: scalar value.
      scope: name scope.
  
    Returns:
      a BoxList holding M boxes where M <= N
  
    Raises:
      ValueError: if boxlist not a BoxList object or if it does not have
        the specified field.
    """
    with tf.name_scope(scope, 'FilterFieldValueEquals'):
      if not isinstance(boxlist, box_list.BoxList):
        raise ValueError('boxlist must be a BoxList')
      if not boxlist.has_field(field):
        raise ValueError('boxlist must contain the specified field')
      filter_field = boxlist.get_field(field)
      gather_index = tf.reshape(tf.where(tf.equal(filter_field, value)), [-1])
      return gather(boxlist, gather_index)
  
  
  def filter_greater_than(boxlist, thresh, scope=None):
    """Filter to keep only boxes with score exceeding a given threshold.
  
    This op keeps the collection of boxes whose corresponding scores are
    greater than the input threshold.
  
    TODO(jonathanhuang): Change function name to filter_scores_greater_than
  
    Args:
      boxlist: BoxList holding N boxes.  Must contain a 'scores' field
        representing detection scores.
      thresh: scalar threshold
      scope: name scope.
  
    Returns:
      a BoxList holding M boxes where M <= N
  
    Raises:
      ValueError: if boxlist not a BoxList object or if it does not
        have a scores field
    """
    with tf.name_scope(scope, 'FilterGreaterThan'):
      if not isinstance(boxlist, box_list.BoxList):
        raise ValueError('boxlist must be a BoxList')
      if not boxlist.has_field('scores'):
        raise ValueError('input boxlist must have \'scores\' field')
      scores = boxlist.get_field('scores')
      if len(scores.shape.as_list()) > 2:
        raise ValueError('Scores should have rank 1 or 2')
      if len(scores.shape.as_list()) == 2 and scores.shape.as_list()[1] != 1:
        raise ValueError('Scores should have rank 1 or have shape '
                         'consistent with [None, 1]')
      high_score_indices = tf.cast(tf.reshape(
          tf.where(tf.greater(scores, thresh)),
          [-1]), tf.int32)
      return gather(boxlist, high_score_indices)
  
  
  def non_max_suppression(boxlist, thresh, max_output_size, scope=None):
    """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.  Note that this only works for a single class ---
    to apply NMS to multi-class predictions, use MultiClassNonMaxSuppression.
  
    Args:
      boxlist: BoxList holding N boxes.  Must contain a 'scores' field
        representing detection scores.
      thresh: scalar threshold
      max_output_size: maximum number of retained boxes
      scope: name scope.
  
    Returns:
      a BoxList holding M boxes where M <= max_output_size
    Raises:
      ValueError: if thresh is not in [0, 1]
    """
    with tf.name_scope(scope, 'NonMaxSuppression'):
      if not 0 <= thresh <= 1.0:
        raise ValueError('thresh must be between 0 and 1')
      if not isinstance(boxlist, box_list.BoxList):
        raise ValueError('boxlist must be a BoxList')
      if not boxlist.has_field('scores'):
        raise ValueError('input boxlist must have \'scores\' field')
      selected_indices = tf.image.non_max_suppression(
          boxlist.get(), boxlist.get_field('scores'),
          max_output_size, iou_threshold=thresh)
      return gather(boxlist, selected_indices)
  
  
  def _copy_extra_fields(boxlist_to_copy_to, boxlist_to_copy_from):
    """Copies the extra fields of boxlist_to_copy_from to boxlist_to_copy_to.
  
    Args:
      boxlist_to_copy_to: BoxList to which extra fields are copied.
      boxlist_to_copy_from: BoxList from which fields are copied.
  
    Returns:
      boxlist_to_copy_to with extra fields.
    """
    for field in boxlist_to_copy_from.get_extra_fields():
      boxlist_to_copy_to.add_field(field, boxlist_to_copy_from.get_field(field))
    return boxlist_to_copy_to
  
  
  def to_normalized_coordinates(boxlist, height, width,
                                check_range=True, scope=None):
    """Converts absolute box coordinates to normalized coordinates in [0, 1].
  
    Usually one uses the dynamic shape of the image or conv-layer tensor:
      boxlist = box_list_ops.to_normalized_coordinates(boxlist,
                                                       tf.shape(images)[1],
                                                       tf.shape(images)[2]),
  
    This function raises an assertion failed error at graph execution time when
    the maximum coordinate is smaller than 1.01 (which means that coordinates are
    already normalized). The value 1.01 is to deal with small rounding errors.
  
    Args:
      boxlist: BoxList with coordinates in terms of pixel-locations.
      height: Maximum value for height of absolute box coordinates.
      width: Maximum value for width of absolute box coordinates.
      check_range: If True, checks if the coordinates are normalized or not.
      scope: name scope.
  
    Returns:
      boxlist with normalized coordinates in [0, 1].
    """
    with tf.name_scope(scope, 'ToNormalizedCoordinates'):
      height = tf.cast(height, tf.float32)
      width = tf.cast(width, tf.float32)
  
      if check_range:
        max_val = tf.reduce_max(boxlist.get())
        max_assert = tf.Assert(tf.greater(max_val, 1.01),
                               ['max value is lower than 1.01: ', max_val])
        with tf.control_dependencies([max_assert]):
          width = tf.identity(width)
  
      return scale(boxlist, 1 / height, 1 / width)
  
  
  def to_absolute_coordinates(boxlist,
                              height,
                              width,
                              check_range=True,
                              maximum_normalized_coordinate=1.1,
                              scope=None):
    """Converts normalized box coordinates to absolute pixel coordinates.
  
    This function raises an assertion failed error when the maximum box coordinate
    value is larger than maximum_normalized_coordinate (in which case coordinates
    are already absolute).
  
    Args:
      boxlist: BoxList with coordinates in range [0, 1].
      height: Maximum value for height of absolute box coordinates.
      width: Maximum value for width of absolute box coordinates.
      check_range: If True, checks if the coordinates are normalized or not.
      maximum_normalized_coordinate: Maximum coordinate value to be considered
        as normalized, default to 1.1.
      scope: name scope.
  
    Returns:
      boxlist with absolute coordinates in terms of the image size.
  
    """
    with tf.name_scope(scope, 'ToAbsoluteCoordinates'):
      height = tf.cast(height, tf.float32)
      width = tf.cast(width, tf.float32)
  
      # Ensure range of input boxes is correct.
      if check_range:
        box_maximum = tf.reduce_max(boxlist.get())
        max_assert = tf.Assert(
            tf.greater_equal(maximum_normalized_coordinate, box_maximum),
            ['maximum box coordinate value is larger '
             'than %f: ' % maximum_normalized_coordinate, box_maximum])
        with tf.control_dependencies([max_assert]):
          width = tf.identity(width)
  
      return scale(boxlist, height, width)
  
  
  def refine_boxes_multi_class(pool_boxes,
                               num_classes,
                               nms_iou_thresh,
                               nms_max_detections,
                               voting_iou_thresh=0.5):
    """Refines a pool of boxes using non max suppression and box voting.
  
    Box refinement is done independently for each class.
  
    Args:
      pool_boxes: (BoxList) A collection of boxes to be refined. pool_boxes must
        have a rank 1 'scores' field and a rank 1 'classes' field.
      num_classes: (int scalar) Number of classes.
      nms_iou_thresh: (float scalar) iou threshold for non max suppression (NMS).
      nms_max_detections: (int scalar) maximum output size for NMS.
      voting_iou_thresh: (float scalar) iou threshold for box voting.
  
    Returns:
      BoxList of refined boxes.
  
    Raises:
      ValueError: if
        a) nms_iou_thresh or voting_iou_thresh is not in [0, 1].
        b) pool_boxes is not a BoxList.
        c) pool_boxes does not have a scores and classes field.
    """
    if not 0.0 <= nms_iou_thresh <= 1.0:
      raise ValueError('nms_iou_thresh must be between 0 and 1')
    if not 0.0 <= voting_iou_thresh <= 1.0:
      raise ValueError('voting_iou_thresh must be between 0 and 1')
    if not isinstance(pool_boxes, box_list.BoxList):
      raise ValueError('pool_boxes must be a BoxList')
    if not pool_boxes.has_field('scores'):
      raise ValueError('pool_boxes must have a \'scores\' field')
    if not pool_boxes.has_field('classes'):
      raise ValueError('pool_boxes must have a \'classes\' field')
  
    refined_boxes = []
    for i in range(num_classes):
      boxes_class = filter_field_value_equals(pool_boxes, 'classes', i)
      refined_boxes_class = refine_boxes(boxes_class, nms_iou_thresh,
                                         nms_max_detections, voting_iou_thresh)
      refined_boxes.append(refined_boxes_class)
    return sort_by_field(concatenate(refined_boxes), 'scores')
  
  
  def refine_boxes(pool_boxes,
                   nms_iou_thresh,
                   nms_max_detections,
                   voting_iou_thresh=0.5):
    """Refines a pool of boxes using non max suppression and box voting.
  
    Args:
      pool_boxes: (BoxList) A collection of boxes to be refined. pool_boxes must
        have a rank 1 'scores' field.
      nms_iou_thresh: (float scalar) iou threshold for non max suppression (NMS).
      nms_max_detections: (int scalar) maximum output size for NMS.
      voting_iou_thresh: (float scalar) iou threshold for box voting.
  
    Returns:
      BoxList of refined boxes.
  
    Raises:
      ValueError: if
        a) nms_iou_thresh or voting_iou_thresh is not in [0, 1].
        b) pool_boxes is not a BoxList.
        c) pool_boxes does not have a scores field.
    """
    if not 0.0 <= nms_iou_thresh <= 1.0:
      raise ValueError('nms_iou_thresh must be between 0 and 1')
    if not 0.0 <= voting_iou_thresh <= 1.0:
      raise ValueError('voting_iou_thresh must be between 0 and 1')
    if not isinstance(pool_boxes, box_list.BoxList):
      raise ValueError('pool_boxes must be a BoxList')
    if not pool_boxes.has_field('scores'):
      raise ValueError('pool_boxes must have a \'scores\' field')
  
    nms_boxes = non_max_suppression(
        pool_boxes, nms_iou_thresh, nms_max_detections)
    return box_voting(nms_boxes, pool_boxes, voting_iou_thresh)
  
  
  def box_voting(selected_boxes, pool_boxes, iou_thresh=0.5):
    """Performs box voting as described in S. Gidaris and N. Komodakis, ICCV 2015.
  
    Performs box voting as described in 'Object detection via a multi-region &
    semantic segmentation-aware CNN model', Gidaris and Komodakis, ICCV 2015. For
    each box 'B' in selected_boxes, we find the set 'S' of boxes in pool_boxes
    with iou overlap >= iou_thresh. The location of B is set to the weighted
    average location of boxes in S (scores are used for weighting). And the score
    of B is set to the average score of boxes in S.
  
    Args:
      selected_boxes: BoxList containing a subset of boxes in pool_boxes. These
        boxes are usually selected from pool_boxes using non max suppression.
      pool_boxes: BoxList containing a set of (possibly redundant) boxes.
      iou_thresh: (float scalar) iou threshold for matching boxes in
        selected_boxes and pool_boxes.
  
    Returns:
      BoxList containing averaged locations and scores for each box in
      selected_boxes.
  
    Raises:
      ValueError: if
        a) selected_boxes or pool_boxes is not a BoxList.
        b) if iou_thresh is not in [0, 1].
        c) pool_boxes does not have a scores field.
    """
    if not 0.0 <= iou_thresh <= 1.0:
      raise ValueError('iou_thresh must be between 0 and 1')
    if not isinstance(selected_boxes, box_list.BoxList):
      raise ValueError('selected_boxes must be a BoxList')
    if not isinstance(pool_boxes, box_list.BoxList):
      raise ValueError('pool_boxes must be a BoxList')
    if not pool_boxes.has_field('scores'):
      raise ValueError('pool_boxes must have a \'scores\' field')
  
    iou_ = iou(selected_boxes, pool_boxes)
    match_indicator = tf.cast(tf.greater(iou_, iou_thresh), dtype=tf.float32)
    num_matches = tf.reduce_sum(match_indicator, 1)
    # TODO(kbanoop): Handle the case where some boxes in selected_boxes do not
    # match to any boxes in pool_boxes. For such boxes without any matches, we
    # should return the original boxes without voting.
    match_assert = tf.Assert(
        tf.reduce_all(tf.greater(num_matches, 0)),
        ['Each box in selected_boxes must match with at least one box '
         'in pool_boxes.'])
  
    scores = tf.expand_dims(pool_boxes.get_field('scores'), 1)
    scores_assert = tf.Assert(
        tf.reduce_all(tf.greater_equal(scores, 0)),
        ['Scores must be non negative.'])
  
    with tf.control_dependencies([scores_assert, match_assert]):
      sum_scores = tf.matmul(match_indicator, scores)
    averaged_scores = tf.reshape(sum_scores, [-1]) / num_matches
  
    box_locations = tf.matmul(match_indicator,
                              pool_boxes.get() * scores) / sum_scores
    averaged_boxes = box_list.BoxList(box_locations)
    _copy_extra_fields(averaged_boxes, selected_boxes)
    averaged_boxes.add_field('scores', averaged_scores)
    return averaged_boxes
  
  
  def pad_or_clip_box_list(boxlist, num_boxes, scope=None):
    """Pads or clips all fields of a BoxList.
  
    Args:
      boxlist: A BoxList with arbitrary of number of boxes.
      num_boxes: First num_boxes in boxlist are kept.
        The fields are zero-padded if num_boxes is bigger than the
        actual number of boxes.
      scope: name scope.
  
    Returns:
      BoxList with all fields padded or clipped.
    """
    with tf.name_scope(scope, 'PadOrClipBoxList'):
      subboxlist = box_list.BoxList(shape_utils.pad_or_clip_tensor(
          boxlist.get(), num_boxes))
      for field in boxlist.get_extra_fields():
        subfield = shape_utils.pad_or_clip_tensor(
            boxlist.get_field(field), num_boxes)
        subboxlist.add_field(field, subfield)
      return subboxlist
  
  
  def select_random_box(boxlist,
                        default_box=None,
                        seed=None,
                        scope=None):
    """Selects a random bounding box from a `BoxList`.
  
    Args:
      boxlist: A BoxList.
      default_box: A [1, 4] float32 tensor. If no boxes are present in `boxlist`,
        this default box will be returned. If None, will use a default box of
        [[-1., -1., -1., -1.]].
      seed: Random seed.
      scope: Name scope.
  
    Returns:
      bbox: A [1, 4] tensor with a random bounding box.
      valid: A bool tensor indicating whether a valid bounding box is returned
        (True) or whether the default box is returned (False).
    """
    with tf.name_scope(scope, 'SelectRandomBox'):
      bboxes = boxlist.get()
      combined_shape = shape_utils.combined_static_and_dynamic_shape(bboxes)
      number_of_boxes = combined_shape[0]
      default_box = default_box or tf.constant([[-1., -1., -1., -1.]])
  
      def select_box():
        random_index = tf.random_uniform([],
                                         maxval=number_of_boxes,
                                         dtype=tf.int32,
                                         seed=seed)
        return tf.expand_dims(bboxes[random_index], axis=0), tf.constant(True)
  
    return tf.cond(
        tf.greater_equal(number_of_boxes, 1),
        true_fn=select_box,
        false_fn=lambda: (default_box, tf.constant(False)))
  
  
  def get_minimal_coverage_box(boxlist,
                               default_box=None,
                               scope=None):
    """Creates a single bounding box which covers all boxes in the boxlist.
  
    Args:
      boxlist: A Boxlist.
      default_box: A [1, 4] float32 tensor. If no boxes are present in `boxlist`,
        this default box will be returned. If None, will use a default box of
        [[0., 0., 1., 1.]].
      scope: Name scope.
  
    Returns:
      A [1, 4] float32 tensor with a bounding box that tightly covers all the
      boxes in the box list. If the boxlist does not contain any boxes, the
      default box is returned.
    """
    with tf.name_scope(scope, 'CreateCoverageBox'):
      num_boxes = boxlist.num_boxes()
  
      def coverage_box(bboxes):
        y_min, x_min, y_max, x_max = tf.split(
            value=bboxes, num_or_size_splits=4, axis=1)
        y_min_coverage = tf.reduce_min(y_min, axis=0)
        x_min_coverage = tf.reduce_min(x_min, axis=0)
        y_max_coverage = tf.reduce_max(y_max, axis=0)
        x_max_coverage = tf.reduce_max(x_max, axis=0)
        return tf.stack(
            [y_min_coverage, x_min_coverage, y_max_coverage, x_max_coverage],
            axis=1)
  
      default_box = default_box or tf.constant([[0., 0., 1., 1.]])
      return tf.cond(
          tf.greater_equal(num_boxes, 1),
          true_fn=lambda: coverage_box(boxlist.get()),
          false_fn=lambda: default_box)
  
  
  def sample_boxes_by_jittering(boxlist,
                                num_boxes_to_sample,
                                stddev=0.1,
                                scope=None):
    """Samples num_boxes_to_sample boxes by jittering around boxlist boxes.
  
    It is possible that this function might generate boxes with size 0. The larger
    the stddev, this is more probable. For a small stddev of 0.1 this probability
    is very small.
  
    Args:
      boxlist: A boxlist containing N boxes in normalized coordinates.
      num_boxes_to_sample: A positive integer containing the number of boxes to
        sample.
      stddev: Standard deviation. This is used to draw random offsets for the
        box corners from a normal distribution. The offset is multiplied by the
        box size so will be larger in terms of pixels for larger boxes.
      scope: Name scope.
  
    Returns:
      sampled_boxlist: A boxlist containing num_boxes_to_sample boxes in
        normalized coordinates.
    """
    with tf.name_scope(scope, 'SampleBoxesByJittering'):
      num_boxes = boxlist.num_boxes()
      box_indices = tf.random_uniform(
          [num_boxes_to_sample],
          minval=0,
          maxval=num_boxes,
          dtype=tf.int32)
      sampled_boxes = tf.gather(boxlist.get(), box_indices)
      sampled_boxes_height = sampled_boxes[:, 2] - sampled_boxes[:, 0]
      sampled_boxes_width = sampled_boxes[:, 3] - sampled_boxes[:, 1]
      rand_miny_gaussian = tf.random_normal([num_boxes_to_sample], stddev=stddev)
      rand_minx_gaussian = tf.random_normal([num_boxes_to_sample], stddev=stddev)
      rand_maxy_gaussian = tf.random_normal([num_boxes_to_sample], stddev=stddev)
      rand_maxx_gaussian = tf.random_normal([num_boxes_to_sample], stddev=stddev)
      miny = rand_miny_gaussian * sampled_boxes_height + sampled_boxes[:, 0]
      minx = rand_minx_gaussian * sampled_boxes_width + sampled_boxes[:, 1]
      maxy = rand_maxy_gaussian * sampled_boxes_height + sampled_boxes[:, 2]
      maxx = rand_maxx_gaussian * sampled_boxes_width + sampled_boxes[:, 3]
      maxy = tf.maximum(miny, maxy)
      maxx = tf.maximum(minx, maxx)
      sampled_boxes = tf.stack([miny, minx, maxy, maxx], axis=1)
      sampled_boxes = tf.maximum(tf.minimum(sampled_boxes, 1.0), 0.0)
      return box_list.BoxList(sampled_boxes)
  
apis: tensorflow.compat.v1.reduce_mean tensorflow.compat.v1.truncated_normal_initializer tensorflow.compat.v1.variable_scope tensorflow.compat.v1.squeeze tensorflow.compat.v1.pad
apis: tensorflow.compat.v1.zeros_like tensorflow.compat.v1.cumsum tensorflow.compat.v1.control_dependencies tensorflow.compat.v1.expand_dims tensorflow.compat.v1.unstack tensorflow.compat.v1.reduce_sum tensorflow.compat.v1.shape tensorflow.compat.v1.truediv tensorflow.compat.v1.equal tensorflow.compat.v1.reduce_max tensorflow.compat.v1.greater tensorflow.compat.v1.matmul tensorflow.compat.v1.where tensorflow.compat.v1.less_equal tensorflow.compat.v1.constant tensorflow.compat.v1.maximum tensorflow.compat.v1.cast tensorflow.compat.v1.nn.top_k tensorflow.compat.v1.stack tensorflow.compat.v1.reshape tensorflow.compat.v1.transpose tensorflow.compat.v1.reduce_any tensorflow.compat.v1.one_hot tensorflow.compat.v1.minimum tensorflow.compat.v1.concat tensorflow.compat.v1.size tensorflow.compat.v1.random_uniform tensorflow.compat.v1.split tensorflow.compat.v1.reverse_v2 tensorflow.compat.v1.greater_equal tensorflow.compat.v1.reduce_min tensorflow.compat.v1.less tensorflow.compat.v1.identity tensorflow.compat.v1.random_normal tensorflow.compat.v1.squeeze tensorflow.compat.v1.image.draw_bounding_boxes tensorflow.compat.v1.name_scope