Spaces:
Sleeping
Sleeping
# Copyright 2023 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. | |
"""The COCO-style evaluator. | |
The following snippet demonstrates the use of interfaces: | |
evaluator = COCOEvaluator(...) | |
for _ in range(num_evals): | |
for _ in range(num_batches_per_eval): | |
predictions, groundtruth = predictor.predict(...) # pop a batch. | |
evaluator.update(predictions, groundtruths) # aggregate internal stats. | |
evaluator.evaluate() # finish one full eval. | |
See also: https://github.com/cocodataset/cocoapi/ | |
""" | |
from __future__ import absolute_import | |
from __future__ import division | |
from __future__ import print_function | |
import atexit | |
import copy | |
import tempfile | |
from absl import logging | |
import numpy as np | |
from pycocotools import cocoeval | |
import six | |
import tensorflow as tf, tf_keras | |
from official.legacy.detection.evaluation import coco_utils | |
from official.legacy.detection.utils import class_utils | |
class OlnCOCOevalWrapper(cocoeval.COCOeval): | |
"""COCOeval wrapper class. | |
Rewritten based on cocoapi: (pycocotools/cocoeval.py) | |
This class wraps COCOEVAL API object, which provides the following additional | |
functionalities: | |
1. summarze 'all', 'seen', and 'novel' split output print-out, e.g., AR at | |
different K proposals, AR and AP resutls for 'seen' and 'novel' class | |
splits. | |
""" | |
def __init__(self, coco_gt, coco_dt, iou_type='box'): | |
super(OlnCOCOevalWrapper, self).__init__( | |
cocoGt=coco_gt, cocoDt=coco_dt, iouType=iou_type) | |
def summarize(self): | |
"""Compute and display summary metrics for evaluation results. | |
Delta to the standard cocoapi function: | |
More Averate Recall metrics are produced with different top-K proposals. | |
Note this functin can *only* be applied on the default parameter | |
setting. | |
Raises: | |
Exception: Please run accumulate() first. | |
""" | |
def _summarize(ap=1, iou_thr=None, area_rng='all', max_dets=100): | |
p = self.params | |
i_str = (' {:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = ' | |
'{:0.3f}') | |
title_str = 'Average Precision' if ap == 1 else 'Average Recall' | |
type_str = '(AP)' if ap == 1 else '(AR)' | |
iou_str = '{:0.2f}:{:0.2f}'.format( | |
p.iouThrs[0], | |
p.iouThrs[-1]) if iou_thr is None else '{:0.2f}'.format(iou_thr) | |
aind = [i for i, a_rng in enumerate(p.areaRngLbl) if a_rng == area_rng] | |
mind = [i for i, m_det in enumerate(p.maxDets) if m_det == max_dets] | |
if ap == 1: | |
# dimension of precision: [TxRxKxAxM] | |
s = self.eval['precision'] | |
# IoU | |
if iou_thr is not None: | |
t = np.where(iou_thr == p.iouThrs)[0] | |
s = s[t] | |
s = s[:, :, :, aind, mind] | |
else: | |
# dimension of recall: [TxKxAxM] | |
s = self.eval['recall'] | |
if iou_thr is not None: | |
t = np.where(iou_thr == p.iouThrs)[0] | |
s = s[t] | |
s = s[:, :, aind, mind] | |
if not (s[s > -1]).any(): | |
mean_s = -1 | |
else: | |
mean_s = np.mean(s[s > -1]) | |
print( | |
i_str.format(title_str, type_str, iou_str, area_rng, max_dets, | |
mean_s)) | |
return mean_s | |
def _summarize_dets(): | |
stats = np.zeros((14,)) | |
stats[0] = _summarize(1) | |
stats[1] = _summarize( | |
1, | |
iou_thr=.5, | |
) | |
stats[2] = _summarize( | |
1, | |
iou_thr=.75, | |
) | |
stats[3] = _summarize( | |
1, | |
area_rng='small', | |
) | |
stats[4] = _summarize( | |
1, | |
area_rng='medium', | |
) | |
stats[5] = _summarize( | |
1, | |
area_rng='large', | |
) | |
stats[6] = _summarize(0, max_dets=self.params.maxDets[0]) # 10 | |
stats[7] = _summarize(0, max_dets=self.params.maxDets[1]) # 20 | |
stats[8] = _summarize(0, max_dets=self.params.maxDets[2]) # 50 | |
stats[9] = _summarize(0, max_dets=self.params.maxDets[3]) # 100 | |
stats[10] = _summarize(0, max_dets=self.params.maxDets[4]) # 200 | |
stats[11] = _summarize(0, area_rng='small', max_dets=10) | |
stats[12] = _summarize(0, area_rng='medium', max_dets=10) | |
stats[13] = _summarize(0, area_rng='large', max_dets=10) | |
return stats | |
if not self.eval: | |
raise Exception('Please run accumulate() first') | |
summarize = _summarize_dets | |
self.stats = summarize() | |
class OlnCOCOevalXclassWrapper(OlnCOCOevalWrapper): | |
"""COCOeval wrapper class. | |
Rewritten based on cocoapi: (pycocotools/cocoeval.py) | |
Delta to the standard cocoapi: | |
Detections that hit the 'seen' class objects are ignored in top-K proposals. | |
This class wraps COCOEVAL API object, which provides the following additional | |
functionalities: | |
1. Include ignore-class split (e.g., 'voc' or 'nonvoc'). | |
2. Do not count (or ignore) box proposals hitting ignore-class when | |
evaluating Average Recall at top-K proposals. | |
""" | |
def __init__(self, coco_gt, coco_dt, iou_type='box'): | |
super(OlnCOCOevalXclassWrapper, self).__init__( | |
coco_gt=coco_gt, coco_dt=coco_dt, iou_type=iou_type) | |
def evaluateImg(self, img_id, cat_id, a_rng, max_det): | |
p = self.params | |
if p.useCats: | |
gt = self._gts[img_id, cat_id] | |
dt = self._dts[img_id, cat_id] | |
else: | |
gt, dt = [], [] | |
for c_id in p.catIds: | |
gt.extend(self._gts[img_id, c_id]) | |
dt.extend(self._dts[img_id, c_id]) | |
if not gt and not dt: | |
return None | |
for g in gt: | |
if g['ignore'] or (g['area'] < a_rng[0] or g['area'] > a_rng[1]): | |
g['_ignore'] = 1 | |
else: | |
g['_ignore'] = 0 | |
# Class manipulation: ignore the 'ignored_split'. | |
if 'ignored_split' in g and g['ignored_split'] == 1: | |
g['_ignore'] = 1 | |
# sort dt highest score first, sort gt ignore last | |
gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort') | |
gt = [gt[i] for i in gtind] | |
dtind = np.argsort([-d['score'] for d in dt], kind='mergesort') | |
dt = [dt[i] for i in dtind[0:max_det]] | |
iscrowd = [int(o['iscrowd']) for o in gt] | |
# load computed ious | |
# ious = self.ious[img_id, cat_id][:, gtind] if len( | |
# self.ious[img_id, cat_id]) > 0 else self.ious[img_id, cat_id] | |
if self.ious[img_id, cat_id].any(): | |
ious = self.ious[img_id, cat_id][:, gtind] | |
else: | |
ious = self.ious[img_id, cat_id] | |
tt = len(p.iouThrs) | |
gg = len(gt) | |
dd = len(dt) | |
gtm = np.zeros((tt, gg)) | |
dtm = np.zeros((tt, dd)) | |
gt_ig = np.array([g['_ignore'] for g in gt]) | |
dt_ig = np.zeros((tt, dd)) | |
# indicator of whether the gt object class is of ignored_split or not. | |
gt_ig_split = np.array([g['ignored_split'] for g in gt]) | |
dt_ig_split = np.zeros((dd)) | |
if ious.any(): | |
for tind, t in enumerate(p.iouThrs): | |
for dind, d in enumerate(dt): | |
# information about best match so far (m=-1 -> unmatched) | |
iou = min([t, 1 - 1e-10]) | |
m = -1 | |
for gind, g in enumerate(gt): | |
# if this gt already matched, and not a crowd, continue | |
if gtm[tind, gind] > 0 and not iscrowd[gind]: | |
continue | |
# if dt matched to reg gt, and on ignore gt, stop | |
if m > -1 and gt_ig[m] == 0 and gt_ig[gind] == 1: | |
break | |
# continue to next gt unless better match made | |
if ious[dind, gind] < iou: | |
continue | |
# if match successful and best so far, store appropriately | |
iou = ious[dind, gind] | |
m = gind | |
# if match made store id of match for both dt and gt | |
if m == -1: | |
continue | |
dt_ig[tind, dind] = gt_ig[m] | |
dtm[tind, dind] = gt[m]['id'] | |
gtm[tind, m] = d['id'] | |
# Activate to ignore the seen-class detections. | |
if tind == 0: # Register just only once: tind > 0 is also fine. | |
dt_ig_split[dind] = gt_ig_split[m] | |
# set unmatched detections outside of area range to ignore | |
a = np.array([d['area'] < a_rng[0] or d['area'] > a_rng[1] for d in dt | |
]).reshape((1, len(dt))) | |
dt_ig = np.logical_or(dt_ig, np.logical_and(dtm == 0, np.repeat(a, tt, 0))) | |
# Activate to ignore the seen-class detections. | |
# Take only eval_split (eg, nonvoc) and ignore seen_split (eg, voc). | |
if dt_ig_split.sum() > 0: | |
dtm = dtm[:, dt_ig_split == 0] | |
dt_ig = dt_ig[:, dt_ig_split == 0] | |
len_dt = min(max_det, len(dt)) | |
dt = [dt[i] for i in range(len_dt) if dt_ig_split[i] == 0] | |
# store results for given image and category | |
return { | |
'image_id': img_id, | |
'category_id': cat_id, | |
'aRng': a_rng, | |
'maxDet': max_det, | |
'dtIds': [d['id'] for d in dt], | |
'gtIds': [g['id'] for g in gt], | |
'dtMatches': dtm, | |
'gtMatches': gtm, | |
'dtScores': [d['score'] for d in dt], | |
'gtIgnore': gt_ig, | |
'dtIgnore': dt_ig, | |
} | |
class MetricWrapper(object): | |
"""Metric Wrapper of the COCO evaluator.""" | |
# This is only a wrapper for COCO metric and works on for numpy array. So it | |
# doesn't inherit from tf_keras.layers.Layer or tf_keras.metrics.Metric. | |
def __init__(self, evaluator): | |
self._evaluator = evaluator | |
def update_state(self, y_true, y_pred): | |
"""Update internal states.""" | |
labels = tf.nest.map_structure(lambda x: x.numpy(), y_true) | |
outputs = tf.nest.map_structure(lambda x: x.numpy(), y_pred) | |
groundtruths = {} | |
predictions = {} | |
for key, val in outputs.items(): | |
if isinstance(val, tuple): | |
val = np.concatenate(val) | |
predictions[key] = val | |
for key, val in labels.items(): | |
if isinstance(val, tuple): | |
val = np.concatenate(val) | |
groundtruths[key] = val | |
self._evaluator.update(predictions, groundtruths) | |
def result(self): | |
return self._evaluator.evaluate() | |
def reset_states(self): | |
return self._evaluator.reset() | |
class COCOEvaluator(object): | |
"""COCO evaluation metric class.""" | |
def __init__(self, annotation_file, include_mask, need_rescale_bboxes=True): | |
"""Constructs COCO evaluation class. | |
The class provides the interface to metrics_fn in TPUEstimator. The | |
_update_op() takes detections from each image and push them to | |
self.detections. The _evaluate() loads a JSON file in COCO annotation format | |
as the groundtruths and runs COCO evaluation. | |
Args: | |
annotation_file: a JSON file that stores annotations of the eval dataset. | |
If `annotation_file` is None, groundtruth annotations will be loaded | |
from the dataloader. | |
include_mask: a boolean to indicate whether or not to include the mask | |
eval. | |
need_rescale_bboxes: If true bboxes in `predictions` will be rescaled back | |
to absolute values (`image_info` is needed in this case). | |
""" | |
if annotation_file: | |
if annotation_file.startswith('gs://'): | |
_, local_val_json = tempfile.mkstemp(suffix='.json') | |
tf.io.gfile.remove(local_val_json) | |
tf.io.gfile.copy(annotation_file, local_val_json) | |
atexit.register(tf.io.gfile.remove, local_val_json) | |
else: | |
local_val_json = annotation_file | |
self._coco_gt = coco_utils.COCOWrapper( | |
eval_type=('mask' if include_mask else 'box'), | |
annotation_file=local_val_json) | |
self._annotation_file = annotation_file | |
self._include_mask = include_mask | |
self._metric_names = [ | |
'AP', 'AP50', 'AP75', 'APs', 'APm', 'APl', 'ARmax1', 'ARmax10', | |
'ARmax100', 'ARs', 'ARm', 'ARl' | |
] | |
self._required_prediction_fields = [ | |
'source_id', 'num_detections', 'detection_classes', 'detection_scores', | |
'detection_boxes' | |
] | |
self._need_rescale_bboxes = need_rescale_bboxes | |
if self._need_rescale_bboxes: | |
self._required_prediction_fields.append('image_info') | |
self._required_groundtruth_fields = [ | |
'source_id', 'height', 'width', 'classes', 'boxes' | |
] | |
if self._include_mask: | |
mask_metric_names = ['mask_' + x for x in self._metric_names] | |
self._metric_names.extend(mask_metric_names) | |
self._required_prediction_fields.extend(['detection_masks']) | |
self._required_groundtruth_fields.extend(['masks']) | |
self.reset() | |
def reset(self): | |
"""Resets internal states for a fresh run.""" | |
self._predictions = {} | |
if not self._annotation_file: | |
self._groundtruths = {} | |
def evaluate(self): | |
"""Evaluates with detections from all images with COCO API. | |
Returns: | |
coco_metric: float numpy array with shape [24] representing the | |
coco-style evaluation metrics (box and mask). | |
""" | |
if not self._annotation_file: | |
logging.info('Thre is no annotation_file in COCOEvaluator.') | |
gt_dataset = coco_utils.convert_groundtruths_to_coco_dataset( | |
self._groundtruths) | |
coco_gt = coco_utils.COCOWrapper( | |
eval_type=('mask' if self._include_mask else 'box'), | |
gt_dataset=gt_dataset) | |
else: | |
logging.info('Using annotation file: %s', self._annotation_file) | |
coco_gt = self._coco_gt | |
coco_predictions = coco_utils.convert_predictions_to_coco_annotations( | |
self._predictions) | |
coco_dt = coco_gt.loadRes(predictions=coco_predictions) | |
image_ids = [ann['image_id'] for ann in coco_predictions] | |
coco_eval = cocoeval.COCOeval(coco_gt, coco_dt, iouType='bbox') | |
coco_eval.params.imgIds = image_ids | |
coco_eval.evaluate() | |
coco_eval.accumulate() | |
coco_eval.summarize() | |
coco_metrics = coco_eval.stats | |
if self._include_mask: | |
mcoco_eval = cocoeval.COCOeval(coco_gt, coco_dt, iouType='segm') | |
mcoco_eval.params.imgIds = image_ids | |
mcoco_eval.evaluate() | |
mcoco_eval.accumulate() | |
mcoco_eval.summarize() | |
mask_coco_metrics = mcoco_eval.stats | |
if self._include_mask: | |
metrics = np.hstack((coco_metrics, mask_coco_metrics)) | |
else: | |
metrics = coco_metrics | |
# Cleans up the internal variables in order for a fresh eval next time. | |
self.reset() | |
metrics_dict = {} | |
for i, name in enumerate(self._metric_names): | |
metrics_dict[name] = metrics[i].astype(np.float32) | |
return metrics_dict | |
def _process_predictions(self, predictions): | |
image_scale = np.tile(predictions['image_info'][:, 2:3, :], (1, 1, 2)) | |
predictions['detection_boxes'] = ( | |
predictions['detection_boxes'].astype(np.float32)) | |
predictions['detection_boxes'] /= image_scale | |
if 'detection_outer_boxes' in predictions: | |
predictions['detection_outer_boxes'] = ( | |
predictions['detection_outer_boxes'].astype(np.float32)) | |
predictions['detection_outer_boxes'] /= image_scale | |
def update(self, predictions, groundtruths=None): | |
"""Update and aggregate detection results and groundtruth data. | |
Args: | |
predictions: a dictionary of numpy arrays including the fields below. See | |
different parsers under `../dataloader` for more details. | |
Required fields: | |
- source_id: a numpy array of int or string of shape [batch_size]. | |
- image_info [if `need_rescale_bboxes` is True]: 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]. | |
Optional fields: | |
- detection_masks: a numpy array of float of shape [batch_size, K, | |
mask_height, mask_width]. | |
groundtruths: a dictionary of numpy arrays including the fields below. See | |
also different parsers under `../dataloader` for more details. | |
Required fields: | |
- source_id: a numpy array of int or string of shape [batch_size]. | |
- height: a numpy array of int of shape [batch_size]. | |
- width: a numpy array of int 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]. | |
Optional fields: | |
- is_crowds: a numpy array of int of shape [batch_size, K]. If the | |
field is absent, it is assumed that this instance is not crowd. | |
- areas: a numy array of float of shape [batch_size, K]. If the field | |
is absent, the area is calculated using either boxes or masks | |
depending on which one is available. | |
- masks: a numpy array of float of shape [batch_size, K, mask_height, | |
mask_width], | |
Raises: | |
ValueError: if the required prediction or groundtruth fields are not | |
present in the incoming `predictions` or `groundtruths`. | |
""" | |
for k in self._required_prediction_fields: | |
if k not in predictions: | |
raise ValueError( | |
'Missing the required key `{}` in predictions!'.format(k)) | |
if self._need_rescale_bboxes: | |
self._process_predictions(predictions) | |
for k, v in six.iteritems(predictions): | |
if k not in self._predictions: | |
self._predictions[k] = [v] | |
else: | |
self._predictions[k].append(v) | |
if not self._annotation_file: | |
assert groundtruths | |
for k in self._required_groundtruth_fields: | |
if k not in groundtruths: | |
raise ValueError( | |
'Missing the required key `{}` in groundtruths!'.format(k)) | |
for k, v in six.iteritems(groundtruths): | |
if k not in self._groundtruths: | |
self._groundtruths[k] = [v] | |
else: | |
self._groundtruths[k].append(v) | |
class OlnXclassEvaluator(COCOEvaluator): | |
"""COCO evaluation metric class.""" | |
def __init__(self, annotation_file, include_mask, need_rescale_bboxes=True, | |
use_category=True, seen_class='all'): | |
"""Constructs COCO evaluation class. | |
The class provides the interface to metrics_fn in TPUEstimator. The | |
_update_op() takes detections from each image and push them to | |
self.detections. The _evaluate() loads a JSON file in COCO annotation format | |
as the groundtruths and runs COCO evaluation. | |
Args: | |
annotation_file: a JSON file that stores annotations of the eval dataset. | |
If `annotation_file` is None, groundtruth annotations will be loaded | |
from the dataloader. | |
include_mask: a boolean to indicate whether or not to include the mask | |
eval. | |
need_rescale_bboxes: If true bboxes in `predictions` will be rescaled back | |
to absolute values (`image_info` is needed in this case). | |
use_category: if `False`, treat all object in all classes in one | |
foreground category. | |
seen_class: 'all' or 'voc' or 'nonvoc' | |
""" | |
super(OlnXclassEvaluator, self).__init__( | |
annotation_file=annotation_file, | |
include_mask=include_mask, | |
need_rescale_bboxes=need_rescale_bboxes) | |
self._use_category = use_category | |
self._seen_class = seen_class | |
self._seen_class_ids = class_utils.coco_split_class_ids(seen_class) | |
self._metric_names = [ | |
'AP', 'AP50', 'AP75', | |
'APs', 'APm', 'APl', | |
'ARmax10', 'ARmax20', 'ARmax50', 'ARmax100', 'ARmax200', | |
'ARmax10s', 'ARmax10m', 'ARmax10l' | |
] | |
if self._seen_class != 'all': | |
self._metric_names.extend([ | |
'AP_seen', 'AP50_seen', 'AP75_seen', | |
'APs_seen', 'APm_seen', 'APl_seen', | |
'ARmax10_seen', 'ARmax20_seen', 'ARmax50_seen', | |
'ARmax100_seen', 'ARmax200_seen', | |
'ARmax10s_seen', 'ARmax10m_seen', 'ARmax10l_seen', | |
'AP_novel', 'AP50_novel', 'AP75_novel', | |
'APs_novel', 'APm_novel', 'APl_novel', | |
'ARmax10_novel', 'ARmax20_novel', 'ARmax50_novel', | |
'ARmax100_novel', 'ARmax200_novel', | |
'ARmax10s_novel', 'ARmax10m_novel', 'ARmax10l_novel', | |
]) | |
if self._include_mask: | |
mask_metric_names = ['mask_' + x for x in self._metric_names] | |
self._metric_names.extend(mask_metric_names) | |
self._required_prediction_fields.extend(['detection_masks']) | |
self._required_groundtruth_fields.extend(['masks']) | |
self.reset() | |
def evaluate(self): | |
"""Evaluates with detections from all images with COCO API. | |
Returns: | |
coco_metric: float numpy array with shape [24] representing the | |
coco-style evaluation metrics (box and mask). | |
""" | |
if not self._annotation_file: | |
logging.info('Thre is no annotation_file in COCOEvaluator.') | |
gt_dataset = coco_utils.convert_groundtruths_to_coco_dataset( | |
self._groundtruths) | |
coco_gt = coco_utils.COCOWrapper( | |
eval_type=('mask' if self._include_mask else 'box'), | |
gt_dataset=gt_dataset) | |
else: | |
logging.info('Using annotation file: %s', self._annotation_file) | |
coco_gt = self._coco_gt | |
coco_predictions = coco_utils.convert_predictions_to_coco_annotations( | |
self._predictions) | |
coco_dt = coco_gt.loadRes(predictions=coco_predictions) | |
image_ids = [ann['image_id'] for ann in coco_predictions] | |
# Class manipulation: 'all' split samples -> ignored_split = 0. | |
for idx, ann in enumerate(coco_gt.dataset['annotations']): | |
coco_gt.dataset['annotations'][idx]['ignored_split'] = 0 | |
coco_eval = cocoeval.OlnCOCOevalXclassWrapper( | |
coco_gt, coco_dt, iou_type='bbox') | |
coco_eval.params.maxDets = [10, 20, 50, 100, 200] | |
coco_eval.params.imgIds = image_ids | |
coco_eval.params.useCats = 0 if not self._use_category else 1 | |
coco_eval.evaluate() | |
coco_eval.accumulate() | |
coco_eval.summarize() | |
coco_metrics = coco_eval.stats | |
if self._include_mask: | |
mcoco_eval = cocoeval.OlnCOCOevalXclassWrapper( | |
coco_gt, coco_dt, iou_type='segm') | |
mcoco_eval.params.maxDets = [10, 20, 50, 100, 200] | |
mcoco_eval.params.imgIds = image_ids | |
mcoco_eval.params.useCats = 0 if not self._use_category else 1 | |
mcoco_eval.evaluate() | |
mcoco_eval.accumulate() | |
mcoco_eval.summarize() | |
mask_coco_metrics = mcoco_eval.stats | |
if self._include_mask: | |
metrics = np.hstack((coco_metrics, mask_coco_metrics)) | |
else: | |
metrics = coco_metrics | |
if self._seen_class != 'all': | |
# for seen class eval, samples of novel_class are ignored. | |
coco_gt_seen = copy.deepcopy(coco_gt) | |
for idx, ann in enumerate(coco_gt.dataset['annotations']): | |
if ann['category_id'] in self._seen_class_ids: | |
coco_gt_seen.dataset['annotations'][idx]['ignored_split'] = 0 | |
else: | |
coco_gt_seen.dataset['annotations'][idx]['ignored_split'] = 1 | |
coco_eval_seen = cocoeval.OlnCOCOevalXclassWrapper( | |
coco_gt_seen, coco_dt, iou_type='bbox') | |
coco_eval_seen.params.maxDets = [10, 20, 50, 100, 200] | |
coco_eval_seen.params.imgIds = image_ids | |
coco_eval_seen.params.useCats = 0 if not self._use_category else 1 | |
coco_eval_seen.evaluate() | |
coco_eval_seen.accumulate() | |
coco_eval_seen.summarize() | |
coco_metrics_seen = coco_eval_seen.stats | |
if self._include_mask: | |
mcoco_eval_seen = cocoeval.OlnCOCOevalXclassWrapper( | |
coco_gt_seen, coco_dt, iou_type='segm') | |
mcoco_eval_seen.params.maxDets = [10, 20, 50, 100, 200] | |
mcoco_eval_seen.params.imgIds = image_ids | |
mcoco_eval_seen.params.useCats = 0 if not self._use_category else 1 | |
mcoco_eval_seen.evaluate() | |
mcoco_eval_seen.accumulate() | |
mcoco_eval_seen.summarize() | |
mask_coco_metrics_seen = mcoco_eval_seen.stats | |
# for novel class eval, samples of seen_class are ignored. | |
coco_gt_novel = copy.deepcopy(coco_gt) | |
for idx, ann in enumerate(coco_gt.dataset['annotations']): | |
if ann['category_id'] in self._seen_class_ids: | |
coco_gt_novel.dataset['annotations'][idx]['ignored_split'] = 1 | |
else: | |
coco_gt_novel.dataset['annotations'][idx]['ignored_split'] = 0 | |
coco_eval_novel = cocoeval.OlnCOCOevalXclassWrapper( | |
coco_gt_novel, coco_dt, iou_type='bbox') | |
coco_eval_novel.params.maxDets = [10, 20, 50, 100, 200] | |
coco_eval_novel.params.imgIds = image_ids | |
coco_eval_novel.params.useCats = 0 if not self._use_category else 1 | |
coco_eval_novel.evaluate() | |
coco_eval_novel.accumulate() | |
coco_eval_novel.summarize() | |
coco_metrics_novel = coco_eval_novel.stats | |
if self._include_mask: | |
mcoco_eval_novel = cocoeval.OlnCOCOevalXclassWrapper( | |
coco_gt_novel, coco_dt, iou_type='segm') | |
mcoco_eval_novel.params.maxDets = [10, 20, 50, 100, 200] | |
mcoco_eval_novel.params.imgIds = image_ids | |
mcoco_eval_novel.params.useCats = 0 if not self._use_category else 1 | |
mcoco_eval_novel.evaluate() | |
mcoco_eval_novel.accumulate() | |
mcoco_eval_novel.summarize() | |
mask_coco_metrics_novel = mcoco_eval_novel.stats | |
# Combine all splits. | |
if self._include_mask: | |
metrics = np.hstack(( | |
coco_metrics, coco_metrics_seen, coco_metrics_novel, | |
mask_coco_metrics, mask_coco_metrics_seen, mask_coco_metrics_novel)) | |
else: | |
metrics = np.hstack(( | |
coco_metrics, coco_metrics_seen, coco_metrics_novel)) | |
# Cleans up the internal variables in order for a fresh eval next time. | |
self.reset() | |
metrics_dict = {} | |
for i, name in enumerate(self._metric_names): | |
metrics_dict[name] = metrics[i].astype(np.float32) | |
return metrics_dict | |
class OlnXdataEvaluator(OlnXclassEvaluator): | |
"""COCO evaluation metric class.""" | |
def __init__(self, annotation_file, include_mask, need_rescale_bboxes=True, | |
use_category=True, seen_class='all'): | |
"""Constructs COCO evaluation class. | |
The class provides the interface to metrics_fn in TPUEstimator. The | |
_update_op() takes detections from each image and push them to | |
self.detections. The _evaluate() loads a JSON file in COCO annotation format | |
as the groundtruths and runs COCO evaluation. | |
Args: | |
annotation_file: a JSON file that stores annotations of the eval dataset. | |
If `annotation_file` is None, groundtruth annotations will be loaded | |
from the dataloader. | |
include_mask: a boolean to indicate whether or not to include the mask | |
eval. | |
need_rescale_bboxes: If true bboxes in `predictions` will be rescaled back | |
to absolute values (`image_info` is needed in this case). | |
use_category: if `False`, treat all object in all classes in one | |
foreground category. | |
seen_class: 'all' or 'voc' or 'nonvoc' | |
""" | |
super(OlnXdataEvaluator, self).__init__( | |
annotation_file=annotation_file, | |
include_mask=include_mask, | |
need_rescale_bboxes=need_rescale_bboxes, | |
use_category=False, | |
seen_class='all') | |
def evaluate(self): | |
"""Evaluates with detections from all images with COCO API. | |
Returns: | |
coco_metric: float numpy array with shape [24] representing the | |
coco-style evaluation metrics (box and mask). | |
""" | |
if not self._annotation_file: | |
logging.info('Thre is no annotation_file in COCOEvaluator.') | |
gt_dataset = coco_utils.convert_groundtruths_to_coco_dataset( | |
self._groundtruths) | |
coco_gt = coco_utils.COCOWrapper( | |
eval_type=('mask' if self._include_mask else 'box'), | |
gt_dataset=gt_dataset) | |
else: | |
logging.info('Using annotation file: %s', self._annotation_file) | |
coco_gt = self._coco_gt | |
coco_predictions = coco_utils.convert_predictions_to_coco_annotations( | |
self._predictions) | |
coco_dt = coco_gt.loadRes(predictions=coco_predictions) | |
image_ids = [ann['image_id'] for ann in coco_predictions] | |
# Class manipulation: 'all' split samples -> ignored_split = 0. | |
for idx, _ in enumerate(coco_gt.dataset['annotations']): | |
coco_gt.dataset['annotations'][idx]['ignored_split'] = 0 | |
coco_eval = cocoeval.OlnCOCOevalWrapper(coco_gt, coco_dt, iou_type='bbox') | |
coco_eval.params.maxDets = [10, 20, 50, 100, 200] | |
coco_eval.params.imgIds = image_ids | |
coco_eval.params.useCats = 0 if not self._use_category else 1 | |
coco_eval.evaluate() | |
coco_eval.accumulate() | |
coco_eval.summarize() | |
coco_metrics = coco_eval.stats | |
if self._include_mask: | |
mcoco_eval = cocoeval.OlnCOCOevalWrapper(coco_gt, coco_dt, | |
iou_type='segm') | |
mcoco_eval.params.maxDets = [10, 20, 50, 100, 200] | |
mcoco_eval.params.imgIds = image_ids | |
mcoco_eval.params.useCats = 0 if not self._use_category else 1 | |
mcoco_eval.evaluate() | |
mcoco_eval.accumulate() | |
mcoco_eval.summarize() | |
mask_coco_metrics = mcoco_eval.stats | |
if self._include_mask: | |
metrics = np.hstack((coco_metrics, mask_coco_metrics)) | |
else: | |
metrics = coco_metrics | |
# Cleans up the internal variables in order for a fresh eval next time. | |
self.reset() | |
metrics_dict = {} | |
for i, name in enumerate(self._metric_names): | |
metrics_dict[name] = metrics[i].astype(np.float32) | |
return metrics_dict | |
class ShapeMaskCOCOEvaluator(COCOEvaluator): | |
"""COCO evaluation metric class for ShapeMask.""" | |
def __init__(self, mask_eval_class, **kwargs): | |
"""Constructs COCO evaluation class. | |
The class provides the interface to metrics_fn in TPUEstimator. The | |
_update_op() takes detections from each image and push them to | |
self.detections. The _evaluate() loads a JSON file in COCO annotation format | |
as the groundtruths and runs COCO evaluation. | |
Args: | |
mask_eval_class: the set of classes for mask evaluation. | |
**kwargs: other keyword arguments passed to the parent class initializer. | |
""" | |
super(ShapeMaskCOCOEvaluator, self).__init__(**kwargs) | |
self._mask_eval_class = mask_eval_class | |
self._eval_categories = class_utils.coco_split_class_ids(mask_eval_class) | |
if mask_eval_class != 'all': | |
self._metric_names = [ | |
x.replace('mask', 'novel_mask') for x in self._metric_names | |
] | |
def evaluate(self): | |
"""Evaluates with detections from all images with COCO API. | |
Returns: | |
coco_metric: float numpy array with shape [24] representing the | |
coco-style evaluation metrics (box and mask). | |
""" | |
if not self._annotation_file: | |
gt_dataset = coco_utils.convert_groundtruths_to_coco_dataset( | |
self._groundtruths) | |
coco_gt = coco_utils.COCOWrapper( | |
eval_type=('mask' if self._include_mask else 'box'), | |
gt_dataset=gt_dataset) | |
else: | |
coco_gt = self._coco_gt | |
coco_predictions = coco_utils.convert_predictions_to_coco_annotations( | |
self._predictions) | |
coco_dt = coco_gt.loadRes(predictions=coco_predictions) | |
image_ids = [ann['image_id'] for ann in coco_predictions] | |
coco_eval = cocoeval.COCOeval(coco_gt, coco_dt, iouType='bbox') | |
coco_eval.params.imgIds = image_ids | |
coco_eval.evaluate() | |
coco_eval.accumulate() | |
coco_eval.summarize() | |
coco_metrics = coco_eval.stats | |
if self._include_mask: | |
mcoco_eval = cocoeval.COCOeval(coco_gt, coco_dt, iouType='segm') | |
mcoco_eval.params.imgIds = image_ids | |
mcoco_eval.evaluate() | |
mcoco_eval.accumulate() | |
mcoco_eval.summarize() | |
if self._mask_eval_class == 'all': | |
metrics = np.hstack((coco_metrics, mcoco_eval.stats)) | |
else: | |
mask_coco_metrics = mcoco_eval.category_stats | |
val_catg_idx = np.isin(mcoco_eval.params.catIds, self._eval_categories) | |
# Gather the valid evaluation of the eval categories. | |
if np.any(val_catg_idx): | |
mean_val_metrics = [] | |
for mid in range(len(self._metric_names) // 2): | |
mean_val_metrics.append( | |
np.nanmean(mask_coco_metrics[mid][val_catg_idx])) | |
mean_val_metrics = np.array(mean_val_metrics) | |
else: | |
mean_val_metrics = np.zeros(len(self._metric_names) // 2) | |
metrics = np.hstack((coco_metrics, mean_val_metrics)) | |
else: | |
metrics = coco_metrics | |
# Cleans up the internal variables in order for a fresh eval next time. | |
self.reset() | |
metrics_dict = {} | |
for i, name in enumerate(self._metric_names): | |
metrics_dict[name] = metrics[i].astype(np.float32) | |
return metrics_dict | |