//  [python]
def update_state(self, groundtruths, predictions):
    """Update the metrics state with prediction and ground-truth data.

    Args:
      groundtruths: a dictionary of Tensors including the fields below.
        Required fields:
          - source_id: a numpy array of int or string of shape [batch_size].
          - num_detections: a numpy array of int of shape [batch_size].
          - boxes: a numpy array of float of shape [batch_size, K, 4].
          - classes: a numpy array of int of shape [batch_size, K].
          - difficulties: a numpy array of int of shape [batch_size, K].

      predictions: a dictionary of tensors including the fields below.
        Required fields:
          - source_id: a numpy array of int or string of shape [batch_size].
          - image_info: a numpy array of float of shape [batch_size, 4, 2].
          - num_detections: a numpy array of int of shape [batch_size].
          - detection_boxes: a numpy array of float of shape [batch_size, K, 4].
          - detection_classes: a numpy array of int of shape [batch_size, K].
          - detection_scores: a numpy array of float of shape [batch_size, K].
    """
    # Preprocess potentially aggregated tensors.
    for k, v in groundtruths.items():
      if isinstance(v, tuple):
        groundtruths[k] = tf.concat(v, axis=0)
    for k, v in predictions.items():
      if isinstance(v, tuple):
        predictions[k] = tf.concat(v, axis=0)

    # Change cyclists' type id from 3 to 4, where 3 is reserved for sign.
    groundtruth_type = tf.cast(groundtruths['classes'], tf.uint8)
    groundtruth_type = tf.where(
        tf.equal(groundtruth_type, 3),
        tf.ones_like(groundtruth_type) * 4, groundtruth_type)
    prediction_type = tf.cast(predictions['detection_classes'], tf.uint8)
    prediction_type = tf.where(
        tf.equal(prediction_type, 3),
        tf.ones_like(prediction_type) * 4, prediction_type)

    # Rescale the detection boxes back to original scale.
    image_scale = tf.tile(predictions['image_info'][:, 2:3, :], (1, 1, 2))
    prediction_bbox = predictions['detection_boxes'] / image_scale

    batch_size = tf.shape(groundtruths['source_id'])[0]

    for i in tf.range(batch_size):
      frame_groundtruths = {
          'ground_truth_frame_id':
              groundtruths['source_id'][i],
          'ground_truth_bbox':
              box_ops.yxyx_to_cycxhw(
                  tf.cast(groundtruths['boxes'][i], tf.float32)),
          'ground_truth_type':
              groundtruth_type[i],
          'ground_truth_difficulty':
              tf.cast(groundtruths['difficulties'][i], tf.uint8),
      }
      frame_groundtruths = self._remove_padding(
          frame_groundtruths, groundtruths['num_detections'][i])
      frame_predictions = {
          'prediction_frame_id':
              groundtruths['source_id'][i],
          'prediction_bbox':
              box_ops.yxyx_to_cycxhw(
                  tf.cast(prediction_bbox[i], tf.float32)),
          'prediction_type':
              prediction_type[i],
          'prediction_score':
              tf.cast(predictions['detection_scores'][i], tf.float32),
          'prediction_overlap_nlz':
              tf.zeros_like(predictions['detection_scores'][i], dtype=tf.bool)
      }
      frame_predictions = self._remove_padding(frame_predictions,
                                               predictions['num_detections'][i])
      super().update_state(frame_groundtruths, frame_predictions)