Spaces:
Runtime error
Runtime error
File size: 9,475 Bytes
d1843be |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# coding=utf-8
# Copyright 2021 The Deeplab2 Authors.
#
# 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.
"""Implementation of Depth-aware Segmentation and Tracking Quality (DSTQ) metric."""
import collections
from typing import Sequence, List, Tuple
import tensorflow as tf
from deeplab2.evaluation import segmentation_and_tracking_quality as stq
class DSTQuality(stq.STQuality):
"""Metric class for Depth-aware Segmentation and Tracking Quality (DSTQ).
This metric computes STQ and the inlier depth metric (or depth quality (DQ))
under several thresholds. Then it returns the geometric mean of DQ's, AQ and
IoU to get the final DSTQ, i.e.,
DSTQ@{threshold_1} = pow(STQ ** 2 * DQ@{threshold_1}, 1/3)
DSTQ@{threshold_2} = pow(STQ ** 2 * DQ@{threshold_2}, 1/3)
...
DSTQ = pow(STQ ** 2 * DQ, 1/3)
where DQ = pow(prod_i^n(threshold_i), 1/n) for n depth thresholds.
The default choices for depth thresholds are 1.1 and 1.25, i.e.,
max(pred/gt, gt/pred) <= 1.1 and max(pred/gt, gt/pred) <= 1.25.
Commonly used thresholds for the inlier metrics are 1.25, 1.25**2, 1.25**3.
These thresholds are so loose that many methods achieves > 99%.
Therefore, we choose 1.25 and 1.1 to encourage high-precision predictions.
Example usage:
dstq_obj = depth_aware_segmentation_and_tracking_quality.DSTQuality(
num_classes, things_list, ignore_label, max_instances_per_category,
offset, depth_threshold)
dstq.update_state(y_true_1, y_pred_1, d_true_1, d_pred_1)
dstq.update_state(y_true_2, y_pred_2, d_true_2, d_pred_2)
...
result = dstq_obj.result().numpy()
"""
_depth_threshold: Tuple[float, float] = (1.25, 1.1)
_depth_total_counts: collections.OrderedDict
_depth_inlier_counts: List[collections.OrderedDict]
def __init__(self,
num_classes: int,
things_list: Sequence[int],
ignore_label: int,
max_instances_per_category: int,
offset: int,
depth_threshold: Tuple[float] = (1.25, 1.1),
name: str = 'dstq',): # pytype: disable=annotation-type-mismatch
"""Initialization of the DSTQ metric.
Args:
num_classes: Number of classes in the dataset as an integer.
things_list: A sequence of class ids that belong to `things`.
ignore_label: The class id to be ignored in evaluation as an integer or
integer tensor.
max_instances_per_category: The maximum number of instances for each class
as an integer or integer tensor.
offset: The maximum number of unique labels as an integer or integer
tensor.
depth_threshold: A sequence of depth thresholds for the depth quality.
(default: (1.25, 1.1))
name: An optional name. (default: 'dstq')
"""
super().__init__(num_classes, things_list, ignore_label,
max_instances_per_category, offset, name)
if not (isinstance(depth_threshold, tuple) or
isinstance(depth_threshold, list)):
raise TypeError('The type of depth_threshold must be tuple or list.')
if not depth_threshold:
raise ValueError('depth_threshold must be non-empty.')
self._depth_threshold = tuple(depth_threshold)
self._depth_total_counts = collections.OrderedDict()
self._depth_inlier_counts = []
for _ in range(len(self._depth_threshold)):
self._depth_inlier_counts.append(collections.OrderedDict())
def update_state(self,
y_true: tf.Tensor,
y_pred: tf.Tensor,
d_true: tf.Tensor,
d_pred: tf.Tensor,
sequence_id: int = 0):
"""Accumulates the depth-aware segmentation and tracking quality statistics.
Args:
y_true: The ground-truth panoptic label map for a particular video frame
(defined as semantic_map * max_instances_per_category + instance_map).
y_pred: The predicted panoptic label map for a particular video frame
(defined as semantic_map * max_instances_per_category + instance_map).
d_true: The ground-truth depth map for this video frame.
d_pred: The predicted depth map for this video frame.
sequence_id: The optional ID of the sequence the frames belong to. When no
sequence is given, all frames are considered to belong to the same
sequence (default: 0).
"""
super().update_state(y_true, y_pred, sequence_id)
# Valid depth labels contain positive values.
d_valid_mask = d_true > 0
d_valid_total = tf.reduce_sum(tf.cast(d_valid_mask, tf.int32))
# Valid depth prediction is expected to contain positive values.
d_valid_mask = tf.logical_and(d_valid_mask, d_pred > 0)
d_valid_true = tf.boolean_mask(d_true, d_valid_mask)
d_valid_pred = tf.boolean_mask(d_pred, d_valid_mask)
inlier_error = tf.maximum(d_valid_pred / d_valid_true,
d_valid_true / d_valid_pred)
# For each threshold, count the number of inliers.
for threshold_index, threshold in enumerate(self._depth_threshold):
num_inliers = tf.reduce_sum(tf.cast(inlier_error <= threshold, tf.int32))
inlier_counts = self._depth_inlier_counts[threshold_index]
inlier_counts[sequence_id] = (inlier_counts.get(sequence_id, 0) +
int(num_inliers.numpy()))
# Update the total counts of the depth labels.
self._depth_total_counts[sequence_id] = (
self._depth_total_counts.get(sequence_id, 0) +
int(d_valid_total.numpy()))
def result(self):
"""Computes the depth-aware segmentation and tracking quality.
Returns:
A dictionary containing:
- 'STQ': The total STQ score.
- 'AQ': The total association quality (AQ) score.
- 'IoU': The total mean IoU.
- 'STQ_per_seq': A list of the STQ score per sequence.
- 'AQ_per_seq': A list of the AQ score per sequence.
- 'IoU_per_seq': A list of mean IoU per sequence.
- 'Id_per_seq': A list of sequence Ids to map list index to sequence.
- 'Length_per_seq': A list of the length of each sequence.
- 'DSTQ': The total DSTQ score.
- 'DSTQ@thres': The total DSTQ score for threshold thres
- 'DSTQ_per_seq@thres': A list of DSTQ score per sequence for thres.
- 'DQ': The total DQ score.
- 'DQ@thres': The total DQ score for threshold thres.
- 'DQ_per_seq@thres': A list of DQ score per sequence for thres.
"""
# Gather the results for STQ.
stq_results = super().result()
# Collect results for depth quality per sequecne and threshold.
dq_per_seq_at_threshold = {}
dq_at_threshold = {}
for threshold_index, threshold in enumerate(self._depth_threshold):
dq_per_seq_at_threshold[threshold] = [0] * len(self._ground_truth)
total_count = 0
inlier_count = 0
# Follow the order of computing STQ by enumerating _ground_truth.
for index, sequence_id in enumerate(self._ground_truth):
sequence_inlier = self._depth_inlier_counts[threshold_index][
sequence_id]
sequence_total = self._depth_total_counts[sequence_id]
if sequence_total > 0:
dq_per_seq_at_threshold[threshold][
index] = sequence_inlier / sequence_total
total_count += sequence_total
inlier_count += sequence_inlier
if total_count == 0:
dq_at_threshold[threshold] = 0
else:
dq_at_threshold[threshold] = inlier_count / total_count
# Compute DQ as the geometric mean of DQ's at different thresholds.
dq = 1
for _, threshold in enumerate(self._depth_threshold):
dq *= dq_at_threshold[threshold]
dq = dq ** (1 / len(self._depth_threshold))
dq_results = {}
dq_results['DQ'] = dq
for _, threshold in enumerate(self._depth_threshold):
dq_results['DQ@{}'.format(threshold)] = dq_at_threshold[threshold]
dq_results['DQ_per_seq@{}'.format(
threshold)] = dq_per_seq_at_threshold[threshold]
# Combine STQ and DQ to get DSTQ.
dstq_results = {}
dstq_results['DSTQ'] = (stq_results['STQ'] ** 2 * dq) ** (1/3)
for _, threshold in enumerate(self._depth_threshold):
dstq_results['DSTQ@{}'.format(threshold)] = (
stq_results['STQ'] ** 2 * dq_at_threshold[threshold]) ** (1/3)
dstq_results['DSTQ_per_seq@{}'.format(threshold)] = [
(stq_result**2 * dq_result)**(1 / 3) for stq_result, dq_result in zip(
stq_results['STQ_per_seq'], dq_per_seq_at_threshold[threshold])
]
# Merge all the results.
dstq_results.update(stq_results)
dstq_results.update(dq_results)
return dstq_results
def reset_states(self):
"""Resets all states that accumulated data."""
super().reset_states()
self._depth_total_counts = collections.OrderedDict()
self._depth_inlier_counts = []
for _ in range(len(self._depth_threshold)):
self._depth_inlier_counts.append(collections.OrderedDict())
|