import copy from os.path import dirname, exists, join import pytest import torch def _get_config_directory(): """ Find the predefined detector config directory """ try: # Assume we are running in the source mmdetection repo repo_dpath = dirname(dirname(__file__)) except NameError: # For IPython development when this __file__ is not defined import mmdet repo_dpath = dirname(dirname(mmdet.__file__)) config_dpath = join(repo_dpath, 'configs') if not exists(config_dpath): raise Exception('Cannot find config path') return config_dpath def _get_config_module(fname): """ Load a configuration as a python module """ from mmcv import Config config_dpath = _get_config_directory() config_fpath = join(config_dpath, fname) config_mod = Config.fromfile(config_fpath) return config_mod def _get_head_cfg(fname): """ Grab configs necessary to create a bbox_head. These are deep copied to allow for safe modification of parameters without influencing other tests. """ import mmcv config = _get_config_module(fname) model = copy.deepcopy(config.model) train_cfg = mmcv.Config(copy.deepcopy(config.train_cfg)) test_cfg = mmcv.Config(copy.deepcopy(config.test_cfg)) bbox_head = model.bbox_head bbox_head.update(train_cfg=train_cfg) bbox_head.update(test_cfg=test_cfg) return bbox_head def _get_rpn_head_cfg(fname): """ Grab configs necessary to create a rpn_head. These are deep copied to allow for safe modification of parameters without influencing other tests. """ import mmcv config = _get_config_module(fname) model = copy.deepcopy(config.model) train_cfg = mmcv.Config(copy.deepcopy(config.train_cfg)) test_cfg = mmcv.Config(copy.deepcopy(config.test_cfg)) rpn_head = model.rpn_head rpn_head.update(train_cfg=train_cfg.rpn) rpn_head.update(test_cfg=test_cfg.rpn) return rpn_head, train_cfg.rpn_proposal def test_second_head_loss(): if not torch.cuda.is_available(): pytest.skip('test requires GPU and torch+cuda') bbox_head_cfg = _get_head_cfg( 'kitti/dv_second_secfpn_2x8_cosine_80e_kitti-3d-3class.py') from mmdet3d.models.builder import build_head self = build_head(bbox_head_cfg) self.cuda() assert isinstance(self.conv_cls, torch.nn.modules.conv.Conv2d) assert self.conv_cls.in_channels == 512 assert self.conv_cls.out_channels == 18 assert self.conv_reg.out_channels == 42 assert self.conv_dir_cls.out_channels == 12 # test forward feats = list() feats.append(torch.rand([2, 512, 200, 176], dtype=torch.float32).cuda()) (cls_score, bbox_pred, dir_cls_preds) = self.forward(feats) assert cls_score[0].shape == torch.Size([2, 18, 200, 176]) assert bbox_pred[0].shape == torch.Size([2, 42, 200, 176]) assert dir_cls_preds[0].shape == torch.Size([2, 12, 200, 176]) # test loss gt_bboxes = list( torch.tensor( [[[6.4118, -3.4305, -1.7291, 1.7033, 3.4693, 1.6197, -0.9091]], [[16.9107, 9.7925, -1.9201, 1.6097, 3.2786, 1.5307, -2.4056]]], dtype=torch.float32).cuda()) gt_labels = list(torch.tensor([[0], [1]], dtype=torch.int64).cuda()) input_metas = [{ 'sample_idx': 1234 }, { 'sample_idx': 2345 }] # fake input_metas losses = self.loss(cls_score, bbox_pred, dir_cls_preds, gt_bboxes, gt_labels, input_metas) assert losses['loss_cls_3d'][0] > 0 assert losses['loss_bbox_3d'][0] > 0 assert losses['loss_dir_3d'][0] > 0 # test empty ground truth case gt_bboxes = list(torch.empty((2, 0, 7)).cuda()) gt_labels = list(torch.empty((2, 0)).cuda()) empty_gt_losses = self.loss(cls_score, bbox_pred, dir_cls_preds, gt_bboxes, gt_labels, input_metas) assert empty_gt_losses['loss_cls_3d'][0] > 0 assert empty_gt_losses['loss_bbox_3d'][0] == 0 assert empty_gt_losses['loss_dir_3d'][0] == 0 def test_second_head_getboxes(): if not torch.cuda.is_available(): pytest.skip('test requires GPU and torch+cuda') bbox_head_cfg = _get_head_cfg( 'kitti/dv_second_secfpn_2x8_cosine_80e_kitti-3d-3class.py') from mmdet3d.models.builder import build_head self = build_head(bbox_head_cfg) self.cuda() feats = list() feats.append(torch.rand([2, 512, 200, 176], dtype=torch.float32).cuda()) input_metas = [{ 'sample_idx': 1234 }, { 'sample_idx': 2345 }] # fake input_metas (cls_score, bbox_pred, dir_cls_preds) = self.forward(feats) # test get_boxes cls_score[0] -= 1.5 # too many positive samples may cause cuda oom result_list = self.get_bboxes(cls_score, bbox_pred, dir_cls_preds, input_metas) assert (result_list[0]['scores'] > 0.3).all() def test_parta2_rpnhead_getboxes(): if not torch.cuda.is_available(): pytest.skip('test requires GPU and torch+cuda') rpn_head_cfg, proposal_cfg = _get_rpn_head_cfg( 'kitti/hv_PartA2_secfpn_4x8_cosine_80e_kitti-3d-3class.py') from mmdet3d.models.builder import build_head self = build_head(rpn_head_cfg) self.cuda() feats = list() feats.append(torch.rand([2, 512, 200, 176], dtype=torch.float32).cuda()) input_metas = [{ 'sample_idx': 1234 }, { 'sample_idx': 2345 }] # fake input_metas (cls_score, bbox_pred, dir_cls_preds) = self.forward(feats) # test get_boxes cls_score[0] -= 1.5 # too many positive samples may cause cuda oom result_list = self.get_bboxes(cls_score, bbox_pred, dir_cls_preds, input_metas, proposal_cfg) assert result_list[0]['scores'].shape == torch.Size([512]) assert result_list[0]['label_preds'].shape == torch.Size([512]) assert result_list[0]['cls_preds'].shape == torch.Size([512, 3]) assert result_list[0]['box3d_lidar'].shape == torch.Size([512, 7])