metrics.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. # Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from __future__ import absolute_import
  15. from __future__ import division
  16. from __future__ import print_function
  17. import copy
  18. import sys
  19. from collections import OrderedDict
  20. import paddle
  21. import numpy as np
  22. from ppdet.metrics.map_utils import prune_zero_padding, DetectionMAP
  23. from .coco_utils import get_infer_results, cocoapi_eval
  24. import paddlex.utils.logging as logging
  25. __all__ = ['Metric', 'VOCMetric', 'COCOMetric']
  26. class Metric(paddle.metric.Metric):
  27. def name(self):
  28. return self.__class__.__name__
  29. def reset(self):
  30. pass
  31. def accumulate(self):
  32. pass
  33. # paddle.metric.Metric defined :metch:`update`, :meth:`accumulate`
  34. # :metch:`reset`, in ppdet, we also need following 2 methods:
  35. # abstract method for logging metric results
  36. def log(self):
  37. pass
  38. # abstract method for getting metric results
  39. def get_results(self):
  40. pass
  41. class VOCMetric(Metric):
  42. def __init__(self,
  43. labels,
  44. coco_gt,
  45. overlap_thresh=0.5,
  46. map_type='11point',
  47. is_bbox_normalized=False,
  48. evaluate_difficult=False,
  49. classwise=False):
  50. self.cid2cname = {i: name for i, name in enumerate(labels)}
  51. self.coco_gt = coco_gt
  52. self.clsid2catid = {
  53. i: cat['id']
  54. for i, cat in enumerate(
  55. self.coco_gt.loadCats(self.coco_gt.getCatIds()))
  56. }
  57. self.overlap_thresh = overlap_thresh
  58. self.map_type = map_type
  59. self.evaluate_difficult = evaluate_difficult
  60. self.detection_map = DetectionMAP(
  61. class_num=len(labels),
  62. overlap_thresh=overlap_thresh,
  63. map_type=map_type,
  64. is_bbox_normalized=is_bbox_normalized,
  65. evaluate_difficult=evaluate_difficult,
  66. catid2name=self.cid2cname,
  67. classwise=classwise)
  68. self.reset()
  69. def reset(self):
  70. self.details = {'gt': copy.deepcopy(self.coco_gt.dataset), 'bbox': []}
  71. self.detection_map.reset()
  72. def update(self, inputs, outputs):
  73. bboxes = outputs['bbox'][:, 2:].numpy()
  74. scores = outputs['bbox'][:, 1].numpy()
  75. labels = outputs['bbox'][:, 0].numpy()
  76. bbox_lengths = outputs['bbox_num'].numpy()
  77. if bboxes.shape == (1, 1) or bboxes is None:
  78. return
  79. gt_boxes = inputs['gt_bbox']
  80. gt_labels = inputs['gt_class']
  81. difficults = inputs['difficult'] if not self.evaluate_difficult \
  82. else None
  83. scale_factor = inputs['scale_factor'].numpy(
  84. ) if 'scale_factor' in inputs else np.ones(
  85. (gt_boxes.shape[0], 2)).astype('float32')
  86. bbox_idx = 0
  87. for i in range(len(gt_boxes)):
  88. gt_box = gt_boxes[i].numpy()
  89. h, w = scale_factor[i]
  90. gt_box = gt_box / np.array([w, h, w, h])
  91. gt_label = gt_labels[i].numpy()
  92. difficult = None if difficults is None \
  93. else difficults[i].numpy()
  94. bbox_num = bbox_lengths[i]
  95. bbox = bboxes[bbox_idx:bbox_idx + bbox_num]
  96. score = scores[bbox_idx:bbox_idx + bbox_num]
  97. label = labels[bbox_idx:bbox_idx + bbox_num]
  98. gt_box, gt_label, difficult = prune_zero_padding(gt_box, gt_label,
  99. difficult)
  100. self.detection_map.update(bbox, score, label, gt_box, gt_label,
  101. difficult)
  102. bbox_idx += bbox_num
  103. for l, s, b in zip(label, score, bbox):
  104. xmin, ymin, xmax, ymax = b.tolist()
  105. w = xmax - xmin
  106. h = ymax - ymin
  107. bbox = [xmin, ymin, w, h]
  108. coco_res = {
  109. 'image_id': int(inputs['im_id']),
  110. 'category_id': self.clsid2catid[int(l)],
  111. 'bbox': bbox,
  112. 'score': float(s)
  113. }
  114. self.details['bbox'].append(coco_res)
  115. def accumulate(self):
  116. logging.info("Accumulating evaluatation results...")
  117. self.detection_map.accumulate()
  118. def log(self):
  119. map_stat = 100. * self.detection_map.get_map()
  120. logging.info("mAP({:.2f}, {}) = {:.2f}%".format(
  121. self.overlap_thresh, self.map_type, map_stat))
  122. def get_results(self):
  123. return {'bbox': [self.detection_map.get_map()]}
  124. def get(self):
  125. map_stat = 100. * self.detection_map.get_map()
  126. stats = {
  127. "mAP({:.2f}, {})".format(self.overlap_thresh, self.map_type):
  128. map_stat
  129. }
  130. return stats
  131. class COCOMetric(Metric):
  132. def __init__(self, coco_gt, **kwargs):
  133. self.clsid2catid = {
  134. i: cat['id']
  135. for i, cat in enumerate(coco_gt.loadCats(coco_gt.getCatIds()))
  136. }
  137. self.coco_gt = coco_gt
  138. self.classwise = kwargs.get('classwise', False)
  139. self.bias = 0
  140. self.reset()
  141. def reset(self):
  142. # only bbox and mask evaluation support currently
  143. self.details = {
  144. 'gt': copy.deepcopy(self.coco_gt.dataset),
  145. 'bbox': [],
  146. 'mask': []
  147. }
  148. self.eval_stats = {}
  149. def update(self, inputs, outputs):
  150. outs = {}
  151. # outputs Tensor -> numpy.ndarray
  152. for k, v in outputs.items():
  153. outs[k] = v.numpy() if isinstance(v, paddle.Tensor) else v
  154. im_id = inputs['im_id']
  155. outs['im_id'] = im_id.numpy() if isinstance(im_id,
  156. paddle.Tensor) else im_id
  157. infer_results = get_infer_results(
  158. outs, self.clsid2catid, bias=self.bias)
  159. self.details['bbox'] += infer_results[
  160. 'bbox'] if 'bbox' in infer_results else []
  161. self.details['mask'] += infer_results[
  162. 'mask'] if 'mask' in infer_results else []
  163. def accumulate(self):
  164. if len(self.details['bbox']) > 0:
  165. bbox_stats = cocoapi_eval(
  166. copy.deepcopy(self.details['bbox']),
  167. 'bbox',
  168. coco_gt=self.coco_gt,
  169. classwise=self.classwise)
  170. self.eval_stats['bbox'] = bbox_stats
  171. sys.stdout.flush()
  172. if len(self.details['mask']) > 0:
  173. seg_stats = cocoapi_eval(
  174. copy.deepcopy(self.details['mask']),
  175. 'segm',
  176. coco_gt=self.coco_gt,
  177. classwise=self.classwise)
  178. self.eval_stats['mask'] = seg_stats
  179. sys.stdout.flush()
  180. def log(self):
  181. pass
  182. def get(self):
  183. if 'mask' in self.eval_stats:
  184. return OrderedDict(
  185. zip(['bbox_mmap', 'segm_mmap'],
  186. [self.eval_stats['bbox'][0], self.eval_stats['mask'][0]]))
  187. else:
  188. return {'bbox_mmap': self.eval_stats['bbox'][0]}