|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| """Code to compute segmentation in a "streaming" pattern in Tensorflow.
|
|
|
| These aggregate the metric over examples of the evaluation set. Each example is
|
| assumed to be fed in in a stream, and the metric implementation accumulates
|
| across them.
|
| """
|
|
|
| from __future__ import absolute_import
|
| from __future__ import division
|
| from __future__ import print_function
|
|
|
| import tensorflow as tf
|
|
|
| from deeplab.evaluation import panoptic_quality
|
| from deeplab.evaluation import parsing_covering
|
|
|
| _EPSILON = 1e-10
|
|
|
|
|
| def _realdiv_maybe_zero(x, y):
|
| """Support tf.realdiv(x, y) where y may contain zeros."""
|
| return tf.where(tf.less(y, _EPSILON), tf.zeros_like(x), tf.realdiv(x, y))
|
|
|
|
|
| def _running_total(value, shape, name=None):
|
| """Maintains a running total of tensor `value` between calls."""
|
| with tf.variable_scope(name, 'running_total', [value]):
|
| total_var = tf.get_variable(
|
| 'total',
|
| shape,
|
| value.dtype,
|
| initializer=tf.zeros_initializer(),
|
| trainable=False,
|
| collections=[
|
| tf.GraphKeys.LOCAL_VARIABLES, tf.GraphKeys.METRIC_VARIABLES
|
| ])
|
| updated_total = tf.assign_add(total_var, value, use_locking=True)
|
|
|
| return total_var, updated_total
|
|
|
|
|
| def _panoptic_quality_helper(
|
| groundtruth_category_array, groundtruth_instance_array,
|
| predicted_category_array, predicted_instance_array, num_classes,
|
| max_instances_per_category, ignored_label, offset):
|
| """Helper function to compute panoptic quality."""
|
| pq = panoptic_quality.PanopticQuality(num_classes, ignored_label,
|
| max_instances_per_category, offset)
|
| pq.compare_and_accumulate(groundtruth_category_array,
|
| groundtruth_instance_array,
|
| predicted_category_array, predicted_instance_array)
|
| return pq.iou_per_class, pq.tp_per_class, pq.fn_per_class, pq.fp_per_class
|
|
|
|
|
| def streaming_panoptic_quality(groundtruth_categories,
|
| groundtruth_instances,
|
| predicted_categories,
|
| predicted_instances,
|
| num_classes,
|
| max_instances_per_category,
|
| ignored_label,
|
| offset,
|
| name=None):
|
| """Aggregates the panoptic metric across calls with different input tensors.
|
|
|
| See tf.metrics.* functions for comparable functionality and usage.
|
|
|
| Args:
|
| groundtruth_categories: A 2D uint16 tensor of groundtruth category labels.
|
| groundtruth_instances: A 2D uint16 tensor of groundtruth instance labels.
|
| predicted_categories: A 2D uint16 tensor of predicted category labels.
|
| predicted_instances: A 2D uint16 tensor of predicted instance labels.
|
| num_classes: Number of classes in the dataset as an integer.
|
| max_instances_per_category: The maximum number of instances for each class
|
| as an integer or integer tensor.
|
| ignored_label: The class id to be ignored in evaluation as an integer or
|
| integer tensor.
|
| offset: The maximum number of unique labels as an integer or integer tensor.
|
| name: An optional variable_scope name.
|
|
|
| Returns:
|
| qualities: A tensor of shape `[6, num_classes]`, where (1) panoptic quality,
|
| (2) segmentation quality, (3) recognition quality, (4) total_tp,
|
| (5) total_fn and (6) total_fp are saved in the respective rows.
|
| update_ops: List of operations that update the running overall panoptic
|
| quality.
|
|
|
| Raises:
|
| RuntimeError: If eager execution is enabled.
|
| """
|
| if tf.executing_eagerly():
|
| raise RuntimeError('Cannot aggregate when eager execution is enabled.')
|
|
|
| input_args = [
|
| tf.convert_to_tensor(groundtruth_categories, tf.uint16),
|
| tf.convert_to_tensor(groundtruth_instances, tf.uint16),
|
| tf.convert_to_tensor(predicted_categories, tf.uint16),
|
| tf.convert_to_tensor(predicted_instances, tf.uint16),
|
| tf.convert_to_tensor(num_classes, tf.int32),
|
| tf.convert_to_tensor(max_instances_per_category, tf.int32),
|
| tf.convert_to_tensor(ignored_label, tf.int32),
|
| tf.convert_to_tensor(offset, tf.int32),
|
| ]
|
| return_types = [
|
| tf.float64,
|
| tf.float64,
|
| tf.float64,
|
| tf.float64,
|
| ]
|
| with tf.variable_scope(name, 'streaming_panoptic_quality', input_args):
|
| panoptic_results = tf.py_func(
|
| _panoptic_quality_helper, input_args, return_types, stateful=False)
|
| iou, tp, fn, fp = tuple(panoptic_results)
|
|
|
| total_iou, updated_iou = _running_total(
|
| iou, [num_classes], name='iou_total')
|
| total_tp, updated_tp = _running_total(tp, [num_classes], name='tp_total')
|
| total_fn, updated_fn = _running_total(fn, [num_classes], name='fn_total')
|
| total_fp, updated_fp = _running_total(fp, [num_classes], name='fp_total')
|
| update_ops = [updated_iou, updated_tp, updated_fn, updated_fp]
|
|
|
| sq = _realdiv_maybe_zero(total_iou, total_tp)
|
| rq = _realdiv_maybe_zero(total_tp,
|
| total_tp + 0.5 * total_fn + 0.5 * total_fp)
|
| pq = tf.multiply(sq, rq)
|
| qualities = tf.stack([pq, sq, rq, total_tp, total_fn, total_fp], axis=0)
|
| return qualities, update_ops
|
|
|
|
|
| def _parsing_covering_helper(
|
| groundtruth_category_array, groundtruth_instance_array,
|
| predicted_category_array, predicted_instance_array, num_classes,
|
| max_instances_per_category, ignored_label, offset, normalize_by_image_size):
|
| """Helper function to compute parsing covering."""
|
| pc = parsing_covering.ParsingCovering(num_classes, ignored_label,
|
| max_instances_per_category, offset,
|
| normalize_by_image_size)
|
| pc.compare_and_accumulate(groundtruth_category_array,
|
| groundtruth_instance_array,
|
| predicted_category_array, predicted_instance_array)
|
| return pc.weighted_iou_per_class, pc.gt_area_per_class
|
|
|
|
|
| def streaming_parsing_covering(groundtruth_categories,
|
| groundtruth_instances,
|
| predicted_categories,
|
| predicted_instances,
|
| num_classes,
|
| max_instances_per_category,
|
| ignored_label,
|
| offset,
|
| normalize_by_image_size=True,
|
| name=None):
|
| """Aggregates the covering across calls with different input tensors.
|
|
|
| See tf.metrics.* functions for comparable functionality and usage.
|
|
|
| Args:
|
| groundtruth_categories: A 2D uint16 tensor of groundtruth category labels.
|
| groundtruth_instances: A 2D uint16 tensor of groundtruth instance labels.
|
| predicted_categories: A 2D uint16 tensor of predicted category labels.
|
| predicted_instances: A 2D uint16 tensor of predicted instance labels.
|
| num_classes: Number of classes in the dataset as an integer.
|
| max_instances_per_category: The maximum number of instances for each class
|
| as an integer or integer tensor.
|
| ignored_label: The class id to be ignored in evaluation as an integer or
|
| integer tensor.
|
| offset: The maximum number of unique labels as an integer or integer tensor.
|
| normalize_by_image_size: Whether to normalize groundtruth region areas by
|
| image size. If True, groundtruth instance areas and weighted IoUs will be
|
| divided by the size of the corresponding image before accumulated across
|
| the dataset.
|
| name: An optional variable_scope name.
|
|
|
| Returns:
|
| coverings: A tensor of shape `[3, num_classes]`, where (1) per class
|
| coverings, (2) per class sum of weighted IoUs, and (3) per class sum of
|
| groundtruth region areas are saved in the perspective rows.
|
| update_ops: List of operations that update the running overall parsing
|
| covering.
|
|
|
| Raises:
|
| RuntimeError: If eager execution is enabled.
|
| """
|
| if tf.executing_eagerly():
|
| raise RuntimeError('Cannot aggregate when eager execution is enabled.')
|
|
|
| input_args = [
|
| tf.convert_to_tensor(groundtruth_categories, tf.uint16),
|
| tf.convert_to_tensor(groundtruth_instances, tf.uint16),
|
| tf.convert_to_tensor(predicted_categories, tf.uint16),
|
| tf.convert_to_tensor(predicted_instances, tf.uint16),
|
| tf.convert_to_tensor(num_classes, tf.int32),
|
| tf.convert_to_tensor(max_instances_per_category, tf.int32),
|
| tf.convert_to_tensor(ignored_label, tf.int32),
|
| tf.convert_to_tensor(offset, tf.int32),
|
| tf.convert_to_tensor(normalize_by_image_size, tf.bool),
|
| ]
|
| return_types = [
|
| tf.float64,
|
| tf.float64,
|
| ]
|
| with tf.variable_scope(name, 'streaming_parsing_covering', input_args):
|
| covering_results = tf.py_func(
|
| _parsing_covering_helper, input_args, return_types, stateful=False)
|
| weighted_iou_per_class, gt_area_per_class = tuple(covering_results)
|
|
|
| total_weighted_iou_per_class, updated_weighted_iou_per_class = (
|
| _running_total(
|
| weighted_iou_per_class, [num_classes],
|
| name='weighted_iou_per_class_total'))
|
| total_gt_area_per_class, updated_gt_area_per_class = _running_total(
|
| gt_area_per_class, [num_classes], name='gt_area_per_class_total')
|
|
|
| covering_per_class = _realdiv_maybe_zero(total_weighted_iou_per_class,
|
| total_gt_area_per_class)
|
| coverings = tf.stack([
|
| covering_per_class,
|
| total_weighted_iou_per_class,
|
| total_gt_area_per_class,
|
| ],
|
| axis=0)
|
| update_ops = [updated_weighted_iou_per_class, updated_gt_area_per_class]
|
|
|
| return coverings, update_ops
|
|
|