test_coco_panoptic_metric.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import os
  2. import os.path as osp
  3. import tempfile
  4. import unittest
  5. from copy import deepcopy
  6. import mmcv
  7. import numpy as np
  8. import torch
  9. from mmengine.fileio import dump
  10. from mmdet.evaluation import INSTANCE_OFFSET, CocoPanopticMetric
  11. try:
  12. import panopticapi
  13. except ImportError:
  14. panopticapi = None
  15. class TestCocoPanopticMetric(unittest.TestCase):
  16. def _create_panoptic_gt_annotations(self, ann_file, seg_map_dir):
  17. categories = [{
  18. 'id': 0,
  19. 'name': 'person',
  20. 'supercategory': 'person',
  21. 'isthing': 1
  22. }, {
  23. 'id': 1,
  24. 'name': 'cat',
  25. 'supercategory': 'cat',
  26. 'isthing': 1
  27. }, {
  28. 'id': 2,
  29. 'name': 'dog',
  30. 'supercategory': 'dog',
  31. 'isthing': 1
  32. }, {
  33. 'id': 3,
  34. 'name': 'wall',
  35. 'supercategory': 'wall',
  36. 'isthing': 0
  37. }]
  38. images = [{
  39. 'id': 0,
  40. 'width': 80,
  41. 'height': 60,
  42. 'file_name': 'fake_name1.jpg',
  43. }]
  44. annotations = [{
  45. 'segments_info': [{
  46. 'id': 1,
  47. 'category_id': 0,
  48. 'area': 400,
  49. 'bbox': [10, 10, 10, 40],
  50. 'iscrowd': 0
  51. }, {
  52. 'id': 2,
  53. 'category_id': 0,
  54. 'area': 400,
  55. 'bbox': [30, 10, 10, 40],
  56. 'iscrowd': 0
  57. }, {
  58. 'id': 3,
  59. 'category_id': 2,
  60. 'iscrowd': 0,
  61. 'bbox': [50, 10, 10, 5],
  62. 'area': 50
  63. }, {
  64. 'id': 4,
  65. 'category_id': 3,
  66. 'iscrowd': 0,
  67. 'bbox': [0, 0, 80, 60],
  68. 'area': 3950
  69. }],
  70. 'file_name':
  71. 'fake_name1.png',
  72. 'image_id':
  73. 0
  74. }]
  75. gt_json = {
  76. 'images': images,
  77. 'annotations': annotations,
  78. 'categories': categories
  79. }
  80. # 4 is the id of the background class annotation.
  81. gt = np.zeros((60, 80), dtype=np.int64) + 4
  82. gt_bboxes = np.array(
  83. [[10, 10, 10, 40], [30, 10, 10, 40], [50, 10, 10, 5]],
  84. dtype=np.int64)
  85. for i in range(3):
  86. x, y, w, h = gt_bboxes[i]
  87. gt[y:y + h, x:x + w] = i + 1 # id starts from 1
  88. rgb_gt_seg_map = np.zeros(gt.shape + (3, ), dtype=np.uint8)
  89. rgb_gt_seg_map[:, :, 2] = gt // (256 * 256)
  90. rgb_gt_seg_map[:, :, 1] = gt % (256 * 256) // 256
  91. rgb_gt_seg_map[:, :, 0] = gt % 256
  92. img_path = osp.join(seg_map_dir, 'fake_name1.png')
  93. mmcv.imwrite(rgb_gt_seg_map[:, :, ::-1], img_path)
  94. dump(gt_json, ann_file)
  95. return gt_json
  96. def _create_panoptic_data_samples(self):
  97. # predictions
  98. # TP for background class, IoU=3576/4324=0.827
  99. # 2 the category id of the background class
  100. pred = np.zeros((60, 80), dtype=np.int64) + 2
  101. pred_bboxes = np.array(
  102. [
  103. [11, 11, 10, 40], # TP IoU=351/449=0.78
  104. [38, 10, 10, 40], # FP
  105. [51, 10, 10, 5] # TP IoU=45/55=0.818
  106. ],
  107. dtype=np.int64)
  108. pred_labels = np.array([0, 0, 1], dtype=np.int64)
  109. for i in range(3):
  110. x, y, w, h = pred_bboxes[i]
  111. pred[y:y + h, x:x + w] = (i + 1) * INSTANCE_OFFSET + pred_labels[i]
  112. data_samples = [{
  113. 'img_id':
  114. 0,
  115. 'ori_shape': (60, 80),
  116. 'img_path':
  117. 'xxx/fake_name1.jpg',
  118. 'segments_info': [{
  119. 'id': 1,
  120. 'category': 0,
  121. 'is_thing': 1
  122. }, {
  123. 'id': 2,
  124. 'category': 0,
  125. 'is_thing': 1
  126. }, {
  127. 'id': 3,
  128. 'category': 1,
  129. 'is_thing': 1
  130. }, {
  131. 'id': 4,
  132. 'category': 2,
  133. 'is_thing': 0
  134. }],
  135. 'seg_map_path':
  136. osp.join(self.gt_seg_dir, 'fake_name1.png'),
  137. 'pred_panoptic_seg': {
  138. 'sem_seg': torch.from_numpy(pred).unsqueeze(0)
  139. },
  140. }]
  141. return data_samples
  142. def setUp(self):
  143. self.tmp_dir = tempfile.TemporaryDirectory()
  144. self.gt_json_path = osp.join(self.tmp_dir.name, 'gt.json')
  145. self.gt_seg_dir = osp.join(self.tmp_dir.name, 'gt_seg')
  146. os.mkdir(self.gt_seg_dir)
  147. self._create_panoptic_gt_annotations(self.gt_json_path,
  148. self.gt_seg_dir)
  149. self.dataset_meta = {
  150. 'classes': ('person', 'dog', 'wall'),
  151. 'thing_classes': ('person', 'dog'),
  152. 'stuff_classes': ('wall', )
  153. }
  154. self.target = {
  155. 'coco_panoptic/PQ': 67.86874803219071,
  156. 'coco_panoptic/SQ': 80.89770126158936,
  157. 'coco_panoptic/RQ': 83.33333333333334,
  158. 'coco_panoptic/PQ_th': 60.45252075318891,
  159. 'coco_panoptic/SQ_th': 79.9959505972869,
  160. 'coco_panoptic/RQ_th': 75.0,
  161. 'coco_panoptic/PQ_st': 82.70120259019427,
  162. 'coco_panoptic/SQ_st': 82.70120259019427,
  163. 'coco_panoptic/RQ_st': 100.0
  164. }
  165. self.data_samples = self._create_panoptic_data_samples()
  166. def tearDown(self):
  167. self.tmp_dir.cleanup()
  168. @unittest.skipIf(panopticapi is not None, 'panopticapi is installed')
  169. def test_init(self):
  170. with self.assertRaises(RuntimeError):
  171. CocoPanopticMetric()
  172. @unittest.skipIf(panopticapi is None, 'panopticapi is not installed')
  173. def test_evaluate_without_json(self):
  174. # with tmpfile, without json
  175. metric = CocoPanopticMetric(
  176. ann_file=None,
  177. seg_prefix=self.gt_seg_dir,
  178. classwise=False,
  179. nproc=1,
  180. outfile_prefix=None)
  181. metric.dataset_meta = self.dataset_meta
  182. metric.process({}, deepcopy(self.data_samples))
  183. eval_results = metric.evaluate(size=1)
  184. self.assertDictEqual(eval_results, self.target)
  185. # without tmpfile and json
  186. outfile_prefix = f'{self.tmp_dir.name}/test'
  187. metric = CocoPanopticMetric(
  188. ann_file=None,
  189. seg_prefix=self.gt_seg_dir,
  190. classwise=False,
  191. nproc=1,
  192. outfile_prefix=outfile_prefix)
  193. metric.dataset_meta = self.dataset_meta
  194. metric.process({}, deepcopy(self.data_samples))
  195. eval_results = metric.evaluate(size=1)
  196. self.assertDictEqual(eval_results, self.target)
  197. @unittest.skipIf(panopticapi is None, 'panopticapi is not installed')
  198. def test_evaluate_with_json(self):
  199. # with tmpfile and json
  200. metric = CocoPanopticMetric(
  201. ann_file=self.gt_json_path,
  202. seg_prefix=self.gt_seg_dir,
  203. classwise=False,
  204. nproc=1,
  205. outfile_prefix=None)
  206. metric.dataset_meta = self.dataset_meta
  207. metric.process({}, deepcopy(self.data_samples))
  208. eval_results = metric.evaluate(size=1)
  209. self.assertDictEqual(eval_results, self.target)
  210. # classwise
  211. metric = CocoPanopticMetric(
  212. ann_file=self.gt_json_path,
  213. seg_prefix=self.gt_seg_dir,
  214. classwise=True,
  215. nproc=1,
  216. outfile_prefix=None)
  217. metric.dataset_meta = self.dataset_meta
  218. metric.process({}, deepcopy(self.data_samples))
  219. eval_results = metric.evaluate(size=1)
  220. self.assertDictEqual(eval_results, self.target)
  221. # without tmpfile, with json
  222. outfile_prefix = f'{self.tmp_dir.name}/test1'
  223. metric = CocoPanopticMetric(
  224. ann_file=self.gt_json_path,
  225. seg_prefix=self.gt_seg_dir,
  226. classwise=False,
  227. nproc=1,
  228. outfile_prefix=outfile_prefix)
  229. metric.dataset_meta = self.dataset_meta
  230. metric.process({}, deepcopy(self.data_samples))
  231. eval_results = metric.evaluate(size=1)
  232. self.assertDictEqual(eval_results, self.target)
  233. @unittest.skipIf(panopticapi is None, 'panopticapi is not installed')
  234. def test_format_only(self):
  235. with self.assertRaises(AssertionError):
  236. metric = CocoPanopticMetric(
  237. ann_file=self.gt_json_path,
  238. seg_prefix=self.gt_seg_dir,
  239. classwise=False,
  240. nproc=1,
  241. format_only=True,
  242. outfile_prefix=None)
  243. outfile_prefix = f'{self.tmp_dir.name}/test'
  244. metric = CocoPanopticMetric(
  245. ann_file=self.gt_json_path,
  246. seg_prefix=self.gt_seg_dir,
  247. classwise=False,
  248. nproc=1,
  249. format_only=True,
  250. outfile_prefix=outfile_prefix)
  251. metric.dataset_meta = self.dataset_meta
  252. metric.process({}, deepcopy(self.data_samples))
  253. eval_results = metric.evaluate(size=1)
  254. self.assertDictEqual(eval_results, dict())
  255. self.assertTrue(osp.exists(f'{self.tmp_dir.name}/test.panoptic'))
  256. self.assertTrue(osp.exists(f'{self.tmp_dir.name}/test.panoptic.json'))