123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- # Copyright (c) OpenMMLab. All rights reserved.
- import copy
- import os.path as osp
- import tempfile
- from collections import defaultdict
- from unittest import TestCase
- import numpy as np
- from mmengine.fileio import load
- from mmengine.structures import InstanceData
- from xtcocotools.coco import COCO
- from mmpose.datasets.datasets.utils import parse_pose_metainfo
- from mmpose.evaluation.metrics import KeypointPartitionMetric
- class TestKeypointPartitionMetricWrappingCocoMetric(TestCase):
- def setUp(self):
- """Setup some variables which are used in every test method.
- TestCase calls functions in this order: setUp() -> testMethod() ->
- tearDown() -> cleanUp()
- """
- self.tmp_dir = tempfile.TemporaryDirectory()
- self.ann_file_coco = \
- 'tests/data/coco/test_keypoint_partition_metric.json'
- meta_info_coco = dict(
- from_file='configs/_base_/datasets/coco_wholebody.py')
- self.dataset_meta_coco = parse_pose_metainfo(meta_info_coco)
- self.coco = COCO(self.ann_file_coco)
- self.dataset_meta_coco['CLASSES'] = self.coco.loadCats(
- self.coco.getCatIds())
- self.topdown_data_coco = self._convert_ann_to_topdown_batch_data(
- self.ann_file_coco)
- assert len(self.topdown_data_coco) == 14
- self.bottomup_data_coco = self._convert_ann_to_bottomup_batch_data(
- self.ann_file_coco)
- assert len(self.bottomup_data_coco) == 4
- """
- The target results were obtained from CocoWholebodyMetric with
- score_mode='bbox' and nms_mode='none'. We cannot compare other
- combinations of score_mode and nms_mode because CocoWholebodyMetric
- calculates scores and nms using all keypoints while
- KeypointPartitionMetric calculates scores and nms part by part.
- As long as this case is tested correct, the other cases should be
- correct.
- """
- self.target_bbox_none = {
- 'body/coco/AP': 0.749,
- 'body/coco/AR': 0.800,
- 'foot/coco/AP': 0.840,
- 'foot/coco/AR': 0.850,
- 'face/coco/AP': 0.051,
- 'face/coco/AR': 0.050,
- 'left_hand/coco/AP': 0.283,
- 'left_hand/coco/AR': 0.300,
- 'right_hand/coco/AP': 0.383,
- 'right_hand/coco/AR': 0.380,
- 'all/coco/AP': 0.284,
- 'all/coco/AR': 0.450,
- }
- def _convert_ann_to_topdown_batch_data(self, ann_file):
- """Convert annotations to topdown-style batch data."""
- topdown_data = []
- db = load(ann_file)
- imgid2info = dict()
- for img in db['images']:
- imgid2info[img['id']] = img
- for ann in db['annotations']:
- w, h = ann['bbox'][2], ann['bbox'][3]
- bboxes = np.array(ann['bbox'], dtype=np.float32).reshape(-1, 4)
- bbox_scales = np.array([w * 1.25, h * 1.25]).reshape(-1, 2)
- _keypoints = np.array(ann['keypoints']).reshape((1, -1, 3))
- gt_instances = {
- 'bbox_scales': bbox_scales,
- 'bbox_scores': np.ones((1, ), dtype=np.float32),
- 'bboxes': bboxes,
- 'keypoints': _keypoints[..., :2],
- 'keypoints_visible': _keypoints[..., 2:3]
- }
- # fake predictions
- keypoints = np.zeros_like(_keypoints)
- keypoints[..., 0] = _keypoints[..., 0] * 0.99
- keypoints[..., 1] = _keypoints[..., 1] * 1.02
- keypoints[..., 2] = _keypoints[..., 2] * 0.8
- pred_instances = {
- 'keypoints': keypoints[..., :2],
- 'keypoint_scores': keypoints[..., -1],
- }
- data = {'inputs': None}
- data_sample = {
- 'id': ann['id'],
- 'img_id': ann['image_id'],
- 'category_id': ann.get('category_id', 1),
- 'gt_instances': gt_instances,
- 'pred_instances': pred_instances,
- # dummy image_shape for testing
- 'ori_shape': [640, 480],
- # store the raw annotation info to test without ann_file
- 'raw_ann_info': copy.deepcopy(ann),
- }
- # add crowd_index to data_sample if it is present in the image_info
- if 'crowdIndex' in imgid2info[ann['image_id']]:
- data_sample['crowd_index'] = imgid2info[
- ann['image_id']]['crowdIndex']
- # batch size = 1
- data_batch = [data]
- data_samples = [data_sample]
- topdown_data.append((data_batch, data_samples))
- return topdown_data
- def _convert_ann_to_bottomup_batch_data(self, ann_file):
- """Convert annotations to bottomup-style batch data."""
- img2ann = defaultdict(list)
- db = load(ann_file)
- for ann in db['annotations']:
- img2ann[ann['image_id']].append(ann)
- bottomup_data = []
- for img_id, anns in img2ann.items():
- _keypoints = np.array([ann['keypoints'] for ann in anns]).reshape(
- (len(anns), -1, 3))
- gt_instances = {
- 'bbox_scores': np.ones((len(anns)), dtype=np.float32),
- 'keypoints': _keypoints[..., :2],
- 'keypoints_visible': _keypoints[..., 2:3]
- }
- # fake predictions
- keypoints = np.zeros_like(_keypoints)
- keypoints[..., 0] = _keypoints[..., 0] * 0.99
- keypoints[..., 1] = _keypoints[..., 1] * 1.02
- keypoints[..., 2] = _keypoints[..., 2] * 0.8
- pred_instances = {
- 'keypoints': keypoints[..., :2],
- 'keypoint_scores': keypoints[..., -1],
- }
- data = {'inputs': None}
- data_sample = {
- 'id': [ann['id'] for ann in anns],
- 'img_id': img_id,
- 'gt_instances': gt_instances,
- 'pred_instances': pred_instances,
- # dummy image_shape for testing
- 'ori_shape': [640, 480],
- 'raw_ann_info': copy.deepcopy(anns),
- }
- # batch size = 1
- data_batch = [data]
- data_samples = [data_sample]
- bottomup_data.append((data_batch, data_samples))
- return bottomup_data
- def _assert_outfiles(self, prefix):
- for part in ['body', 'foot', 'face', 'left_hand', 'right_hand', 'all']:
- self.assertTrue(
- osp.isfile(
- osp.join(self.tmp_dir.name,
- f'{prefix}.{part}.keypoints.json')))
- def tearDown(self):
- self.tmp_dir.cleanup()
- def test_init(self):
- """test metric init method."""
- # test wrong metric type
- with self.assertRaisesRegex(
- ValueError, 'Metrics supported by KeypointPartitionMetric'):
- _ = KeypointPartitionMetric(
- metric=dict(type='Metric'), partitions=dict(all=range(133)))
- # test ann_file arg warning
- with self.assertWarnsRegex(UserWarning,
- 'does not support the ann_file argument'):
- _ = KeypointPartitionMetric(
- metric=dict(type='CocoMetric', ann_file=''),
- partitions=dict(all=range(133)))
- # test score_mode arg warning
- with self.assertWarnsRegex(UserWarning, "if score_mode is not 'bbox'"):
- _ = KeypointPartitionMetric(
- metric=dict(type='CocoMetric'),
- partitions=dict(all=range(133)))
- # test nms arg warning
- with self.assertWarnsRegex(UserWarning, 'oks_nms and soft_oks_nms'):
- _ = KeypointPartitionMetric(
- metric=dict(type='CocoMetric'),
- partitions=dict(all=range(133)))
- # test partitions
- with self.assertRaisesRegex(AssertionError, 'at least one partition'):
- _ = KeypointPartitionMetric(
- metric=dict(type='CocoMetric'), partitions=dict())
- with self.assertRaisesRegex(AssertionError, 'should be a sequence'):
- _ = KeypointPartitionMetric(
- metric=dict(type='CocoMetric'), partitions=dict(all={}))
- with self.assertRaisesRegex(AssertionError, 'at least one element'):
- _ = KeypointPartitionMetric(
- metric=dict(type='CocoMetric'), partitions=dict(all=[]))
- def test_bottomup_evaluate(self):
- """test bottomup-style COCO metric evaluation."""
- # case1: score_mode='bbox', nms_mode='none'
- metric = KeypointPartitionMetric(
- metric=dict(
- type='CocoMetric',
- outfile_prefix=f'{self.tmp_dir.name}/test_bottomup',
- score_mode='bbox',
- nms_mode='none'),
- partitions=dict(
- body=range(17),
- foot=range(17, 23),
- face=range(23, 91),
- left_hand=range(91, 112),
- right_hand=range(112, 133),
- all=range(133)))
- metric.dataset_meta = self.dataset_meta_coco
- # process samples
- for data_batch, data_samples in self.bottomup_data_coco:
- metric.process(data_batch, data_samples)
- eval_results = metric.evaluate(size=len(self.bottomup_data_coco))
- for key in self.target_bbox_none.keys():
- self.assertAlmostEqual(
- eval_results[key], self.target_bbox_none[key], places=3)
- self._assert_outfiles('test_bottomup')
- def test_topdown_evaluate(self):
- """test topdown-style COCO metric evaluation."""
- # case 1: score_mode='bbox', nms_mode='none'
- metric = KeypointPartitionMetric(
- metric=dict(
- type='CocoMetric',
- outfile_prefix=f'{self.tmp_dir.name}/test_topdown1',
- score_mode='bbox',
- nms_mode='none'),
- partitions=dict(
- body=range(17),
- foot=range(17, 23),
- face=range(23, 91),
- left_hand=range(91, 112),
- right_hand=range(112, 133),
- all=range(133)))
- metric.dataset_meta = self.dataset_meta_coco
- # process samples
- for data_batch, data_samples in self.topdown_data_coco:
- metric.process(data_batch, data_samples)
- eval_results = metric.evaluate(size=len(self.topdown_data_coco))
- for key in self.target_bbox_none.keys():
- self.assertAlmostEqual(
- eval_results[key], self.target_bbox_none[key], places=3)
- self._assert_outfiles('test_topdown1')
- class TestKeypointPartitionMetricWrappingPCKAccuracy(TestCase):
- def setUp(self):
- """Setup some variables which are used in every test method.
- TestCase calls functions in this order: setUp() -> testMethod() ->
- tearDown() -> cleanUp()
- """
- self.batch_size = 8
- num_keypoints = 24
- self.data_batch = []
- self.data_samples = []
- for i in range(self.batch_size):
- gt_instances = InstanceData()
- keypoints = np.zeros((1, num_keypoints, 2))
- for j in range(num_keypoints):
- keypoints[0, j] = [0.5 * i * j, 0.5 * i * j]
- gt_instances.keypoints = keypoints
- gt_instances.keypoints_visible = np.ones(
- (1, num_keypoints, 1)).astype(bool)
- gt_instances.keypoints_visible[0, (2 * i) % 8, 0] = False
- gt_instances.bboxes = np.array([[0.1, 0.2, 0.3, 0.4]]) * 20 * i
- gt_instances.head_size = np.array([[0.1]]) * 10 * i
- pred_instances = InstanceData()
- # fake predictions
- _keypoints = np.zeros_like(keypoints)
- _keypoints[0, :, 0] = keypoints[0, :, 0] * 0.95
- _keypoints[0, :, 1] = keypoints[0, :, 1] * 1.05
- pred_instances.keypoints = _keypoints
- data = {'inputs': None}
- data_sample = {
- 'gt_instances': gt_instances.to_dict(),
- 'pred_instances': pred_instances.to_dict(),
- }
- self.data_batch.append(data)
- self.data_samples.append(data_sample)
- def test_init(self):
- # test norm_item arg warning
- with self.assertWarnsRegex(UserWarning,
- 'norm_item torso is used in JhmdbDataset'):
- _ = KeypointPartitionMetric(
- metric=dict(
- type='PCKAccuracy', thr=0.05, norm_item=['bbox', 'torso']),
- partitions=dict(all=range(133)))
- def test_evaluate(self):
- """test PCK accuracy evaluation metric."""
- # test normalized by 'bbox'
- pck_metric = KeypointPartitionMetric(
- metric=dict(type='PCKAccuracy', thr=0.5, norm_item='bbox'),
- partitions=dict(
- p1=range(10),
- p2=range(10, 24),
- all=range(24),
- ))
- pck_metric.process(self.data_batch, self.data_samples)
- pck = pck_metric.evaluate(self.batch_size)
- target = {'p1/PCK': 1.0, 'p2/PCK': 1.0, 'all/PCK': 1.0}
- self.assertDictEqual(pck, target)
- # test normalized by 'head_size'
- pckh_metric = KeypointPartitionMetric(
- metric=dict(type='PCKAccuracy', thr=0.3, norm_item='head'),
- partitions=dict(
- p1=range(10),
- p2=range(10, 24),
- all=range(24),
- ))
- pckh_metric.process(self.data_batch, self.data_samples)
- pckh = pckh_metric.evaluate(self.batch_size)
- target = {'p1/PCKh': 0.9, 'p2/PCKh': 0.0, 'all/PCKh': 0.375}
- self.assertDictEqual(pckh, target)
- # test normalized by 'torso_size'
- tpck_metric = KeypointPartitionMetric(
- metric=dict(
- type='PCKAccuracy', thr=0.05, norm_item=['bbox', 'torso']),
- partitions=dict(
- p1=range(10),
- p2=range(10, 24),
- all=range(24),
- ))
- tpck_metric.process(self.data_batch, self.data_samples)
- tpck = tpck_metric.evaluate(self.batch_size)
- self.assertIsInstance(tpck, dict)
- target = {
- 'p1/PCK': 0.6,
- 'p1/tPCK': 0.11428571428571428,
- 'p2/PCK': 0.0,
- 'p2/tPCK': 0.0,
- 'all/PCK': 0.25,
- 'all/tPCK': 0.047619047619047616
- }
- self.assertDictEqual(tpck, target)
- class TestKeypointPartitionMetricWrappingAUCandEPE(TestCase):
- def setUp(self):
- """Setup some variables which are used in every test method.
- TestCase calls functions in this order: setUp() -> testMethod() ->
- tearDown() -> cleanUp()
- """
- output = np.zeros((1, 5, 2))
- target = np.zeros((1, 5, 2))
- # first channel
- output[0, 0] = [10, 4]
- target[0, 0] = [10, 0]
- # second channel
- output[0, 1] = [10, 18]
- target[0, 1] = [10, 10]
- # third channel
- output[0, 2] = [0, 0]
- target[0, 2] = [0, -1]
- # fourth channel
- output[0, 3] = [40, 40]
- target[0, 3] = [30, 30]
- # fifth channel
- output[0, 4] = [20, 10]
- target[0, 4] = [0, 10]
- gt_instances = InstanceData()
- gt_instances.keypoints = target
- gt_instances.keypoints_visible = np.array(
- [[True, True, False, True, True]])
- pred_instances = InstanceData()
- pred_instances.keypoints = output
- data = {'inputs': None}
- data_sample = {
- 'gt_instances': gt_instances.to_dict(),
- 'pred_instances': pred_instances.to_dict()
- }
- self.data_batch = [data]
- self.data_samples = [data_sample]
- def test_auc_evaluate(self):
- """test AUC evaluation metric."""
- auc_metric = KeypointPartitionMetric(
- metric=dict(type='AUC', norm_factor=20, num_thrs=4),
- partitions=dict(
- p1=range(3),
- p2=range(3, 5),
- all=range(5),
- ))
- auc_metric.process(self.data_batch, self.data_samples)
- auc = auc_metric.evaluate(1)
- target = {'p1/AUC': 0.625, 'p2/AUC': 0.125, 'all/AUC': 0.375}
- self.assertDictEqual(auc, target)
- def test_epe_evaluate(self):
- """test EPE evaluation metric."""
- epe_metric = KeypointPartitionMetric(
- metric=dict(type='EPE', ),
- partitions=dict(
- p1=range(3),
- p2=range(3, 5),
- all=range(5),
- ))
- epe_metric.process(self.data_batch, self.data_samples)
- epe = epe_metric.evaluate(1)
- target = {
- 'p1/EPE': 6.0,
- 'p2/EPE': 17.071067810058594,
- 'all/EPE': 11.535533905029297
- }
- self.assertDictEqual(epe, target)
- class TestKeypointPartitionMetricWrappingNME(TestCase):
- def setUp(self):
- """Setup some variables which are used in every test method.
- TestCase calls functions in this order: setUp() -> testMethod() ->
- tearDown() -> cleanUp()
- """
- self.batch_size = 4
- num_keypoints = 19
- self.data_batch = []
- self.data_samples = []
- for i in range(self.batch_size):
- gt_instances = InstanceData()
- keypoints = np.zeros((1, num_keypoints, 2))
- for j in range(num_keypoints):
- keypoints[0, j] = [0.5 * i * j, 0.5 * i * j]
- gt_instances.keypoints = keypoints
- gt_instances.keypoints_visible = np.ones(
- (1, num_keypoints, 1)).astype(bool)
- gt_instances.keypoints_visible[0, (2 * i) % self.batch_size,
- 0] = False
- gt_instances['box_size'] = np.array([[0.1]]) * 10 * i
- pred_instances = InstanceData()
- # fake predictions
- _keypoints = np.zeros_like(keypoints)
- _keypoints[0, :, 0] = keypoints[0, :, 0] * 0.95
- _keypoints[0, :, 1] = keypoints[0, :, 1] * 1.05
- pred_instances.keypoints = _keypoints
- data = {'inputs': None}
- data_sample = {
- 'gt_instances': gt_instances.to_dict(),
- 'pred_instances': pred_instances.to_dict(),
- }
- self.data_batch.append(data)
- self.data_samples.append(data_sample)
- def test_init(self):
- # test norm_mode arg missing
- with self.assertRaisesRegex(AssertionError, 'Missing norm_mode'):
- _ = KeypointPartitionMetric(
- metric=dict(type='NME', ), partitions=dict(all=range(133)))
- # test norm_mode = keypoint_distance
- with self.assertRaisesRegex(ValueError,
- "NME norm_mode 'keypoint_distance'"):
- _ = KeypointPartitionMetric(
- metric=dict(type='NME', norm_mode='keypoint_distance'),
- partitions=dict(all=range(133)))
- def test_nme_evaluate(self):
- """test NME evaluation metric."""
- # test when norm_mode = 'use_norm_item'
- # test norm_item = 'box_size' like in `AFLWDataset`
- nme_metric = KeypointPartitionMetric(
- metric=dict(
- type='NME', norm_mode='use_norm_item', norm_item='box_size'),
- partitions=dict(
- p1=range(10),
- p2=range(10, 19),
- all=range(19),
- ))
- nme_metric.process(self.data_batch, self.data_samples)
- nme = nme_metric.evaluate(4)
- target = {
- 'p1/NME': 0.1715388651247378,
- 'p2/NME': 0.4949747721354167,
- 'all/NME': 0.333256827460395
- }
- self.assertDictEqual(nme, target)
|