├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── demo ├── mono_det_demo.py ├── multi_modality_demo.py ├── pcd_demo.py └── pcd_seg_demo.py ├── mmdet3d ├── __init__.py ├── apis │ ├── __init__.py │ ├── inference.py │ └── inferencers │ │ ├── __init__.py │ │ ├── base_3d_inferencer.py │ │ ├── lidar_det3d_inferencer.py │ │ ├── lidar_seg3d_inferencer.py │ │ ├── mono_det3d_inferencer.py │ │ └── multi_modality_det3d_inferencer.py ├── datasets │ ├── __init__.py │ ├── convert_utils.py │ ├── dataset_wrappers.py │ ├── det3d_dataset.py │ ├── kitti2d_dataset.py │ ├── kitti_dataset.py │ ├── lyft_dataset.py │ ├── nuscenes_dataset.py │ ├── s3dis_dataset.py │ ├── scannet_dataset.py │ ├── seg3d_dataset.py │ ├── semantickitti_dataset.py │ ├── sunrgbd_dataset.py │ ├── transforms │ │ ├── __init__.py │ │ ├── data_augment_utils.py │ │ ├── dbsampler.py │ │ ├── formating.py │ │ ├── loading.py │ │ ├── test_time_aug.py │ │ └── transforms_3d.py │ ├── utils.py │ └── waymo_dataset.py ├── engine │ ├── __init__.py │ └── hooks │ │ ├── __init__.py │ │ ├── benchmark_hook.py │ │ ├── disable_object_sample_hook.py │ │ └── visualization_hook.py ├── evaluation │ ├── __init__.py │ ├── functional │ │ ├── __init__.py │ │ ├── indoor_eval.py │ │ ├── instance_seg_eval.py │ │ ├── kitti_utils │ │ │ ├── __init__.py │ │ │ ├── eval.py │ │ │ └── rotate_iou.py │ │ ├── lyft_eval.py │ │ ├── panoptic_seg_eval.py │ │ ├── scannet_utils │ │ │ ├── __init__.py │ │ │ ├── evaluate_semantic_instance.py │ │ │ └── util_3d.py │ │ ├── seg_eval.py │ │ └── waymo_utils │ │ │ ├── __init__.py │ │ │ └── prediction_to_waymo.py │ └── metrics │ │ ├── __init__.py │ │ ├── indoor_metric.py │ │ ├── instance_seg_metric.py │ │ ├── kitti_metric.py │ │ ├── lyft_metric.py │ │ ├── nuscenes_metric.py │ │ ├── panoptic_seg_metric.py │ │ ├── seg_metric.py │ │ └── waymo_metric.py ├── models │ ├── __init__.py │ ├── backbones │ │ ├── __init__.py │ │ ├── base_pointnet.py │ │ ├── cylinder3d.py │ │ ├── dgcnn.py │ │ ├── dla.py │ │ ├── mink_resnet.py │ │ ├── minkunet_backbone.py │ │ ├── multi_backbone.py │ │ ├── nostem_regnet.py │ │ ├── pointnet2_sa_msg.py │ │ ├── pointnet2_sa_ssg.py │ │ ├── second.py │ │ └── spvcnn_backone.py │ ├── data_preprocessors │ │ ├── __init__.py │ │ ├── data_preprocessor.py │ │ ├── utils.py │ │ └── voxelize.py │ ├── decode_heads │ │ ├── __init__.py │ │ ├── cylinder3d_head.py │ │ ├── decode_head.py │ │ ├── dgcnn_head.py │ │ ├── minkunet_head.py │ │ ├── paconv_head.py │ │ └── pointnet2_head.py │ ├── dense_heads │ │ ├── __init__.py │ │ ├── anchor3d_head.py │ │ ├── anchor_free_mono3d_head.py │ │ ├── base_3d_dense_head.py │ │ ├── base_conv_bbox_head.py │ │ ├── base_mono3d_dense_head.py │ │ ├── centerpoint_head.py │ │ ├── fcaf3d_head.py │ │ ├── fcos_mono3d_head.py │ │ ├── free_anchor3d_head.py │ │ ├── groupfree3d_head.py │ │ ├── imvoxel_head.py │ │ ├── monoflex_head.py │ │ ├── parta2_rpn_head.py │ │ ├── pgd_head.py │ │ ├── point_rpn_head.py │ │ ├── shape_aware_head.py │ │ ├── smoke_mono3d_head.py │ │ ├── ssd_3d_head.py │ │ ├── train_mixins.py │ │ └── vote_head.py │ ├── detectors │ │ ├── __init__.py │ │ ├── base.py │ │ ├── centerpoint.py │ │ ├── dfm.py │ │ ├── dynamic_voxelnet.py │ │ ├── fcos_mono3d.py │ │ ├── groupfree3dnet.py │ │ ├── h3dnet.py │ │ ├── imvotenet.py │ │ ├── imvoxelnet.py │ │ ├── mink_single_stage.py │ │ ├── multiview_dfm.py │ │ ├── mvx_faster_rcnn.py │ │ ├── mvx_two_stage.py │ │ ├── parta2.py │ │ ├── point_rcnn.py │ │ ├── pv_rcnn.py │ │ ├── sassd.py │ │ ├── single_stage.py │ │ ├── single_stage_mono3d.py │ │ ├── smoke_mono3d.py │ │ ├── ssd3dnet.py │ │ ├── two_stage.py │ │ ├── votenet.py │ │ └── voxelnet.py │ ├── layers │ │ ├── __init__.py │ │ ├── box3d_nms.py │ │ ├── dgcnn_modules │ │ │ ├── __init__.py │ │ │ ├── dgcnn_fa_module.py │ │ │ ├── dgcnn_fp_module.py │ │ │ └── dgcnn_gf_module.py │ │ ├── edge_fusion_module.py │ │ ├── fusion_layers │ │ │ ├── __init__.py │ │ │ ├── coord_transform.py │ │ │ ├── point_fusion.py │ │ │ └── vote_fusion.py │ │ ├── mlp.py │ │ ├── norm.py │ │ ├── paconv │ │ │ ├── __init__.py │ │ │ ├── paconv.py │ │ │ └── utils.py │ │ ├── pointnet_modules │ │ │ ├── __init__.py │ │ │ ├── builder.py │ │ │ ├── paconv_sa_module.py │ │ │ ├── point_fp_module.py │ │ │ ├── point_sa_module.py │ │ │ └── stack_point_sa_module.py │ │ ├── sparse_block.py │ │ ├── spconv │ │ │ ├── __init__.py │ │ │ └── overwrite_spconv │ │ │ │ ├── __init__.py │ │ │ │ └── write_spconv2.py │ │ ├── torchsparse │ │ │ ├── __init__.py │ │ │ └── torchsparse_wrapper.py │ │ ├── torchsparse_block.py │ │ ├── transformer.py │ │ └── vote_module.py │ ├── losses │ │ ├── __init__.py │ │ ├── axis_aligned_iou_loss.py │ │ ├── chamfer_distance.py │ │ ├── lovasz_loss.py │ │ ├── multibin_loss.py │ │ ├── paconv_regularization_loss.py │ │ ├── rotated_iou_loss.py │ │ └── uncertain_smooth_l1_loss.py │ ├── middle_encoders │ │ ├── __init__.py │ │ ├── pillar_scatter.py │ │ ├── sparse_encoder.py │ │ ├── sparse_unet.py │ │ └── voxel_set_abstraction.py │ ├── necks │ │ ├── __init__.py │ │ ├── dla_neck.py │ │ ├── imvoxel_neck.py │ │ ├── pointnet2_fp_neck.py │ │ └── second_fpn.py │ ├── roi_heads │ │ ├── __init__.py │ │ ├── base_3droi_head.py │ │ ├── bbox_heads │ │ │ ├── __init__.py │ │ │ ├── h3d_bbox_head.py │ │ │ ├── parta2_bbox_head.py │ │ │ ├── point_rcnn_bbox_head.py │ │ │ └── pv_rcnn_bbox_head.py │ │ ├── h3d_roi_head.py │ │ ├── mask_heads │ │ │ ├── __init__.py │ │ │ ├── foreground_segmentation_head.py │ │ │ ├── pointwise_semantic_head.py │ │ │ └── primitive_head.py │ │ ├── part_aggregation_roi_head.py │ │ ├── point_rcnn_roi_head.py │ │ ├── pv_rcnn_roi_head.py │ │ └── roi_extractors │ │ │ ├── __init__.py │ │ │ ├── batch_roigridpoint_extractor.py │ │ │ ├── single_roiaware_extractor.py │ │ │ └── single_roipoint_extractor.py │ ├── segmentors │ │ ├── __init__.py │ │ ├── base.py │ │ ├── cylinder3d.py │ │ ├── encoder_decoder.py │ │ └── minkunet.py │ ├── task_modules │ │ ├── __init__.py │ │ ├── anchor │ │ │ ├── __init__.py │ │ │ ├── anchor_3d_generator.py │ │ │ └── builder.py │ │ ├── assigners │ │ │ ├── __init__.py │ │ │ └── max_3d_iou_assigner.py │ │ ├── builder.py │ │ ├── coders │ │ │ ├── __init__.py │ │ │ ├── anchor_free_bbox_coder.py │ │ │ ├── centerpoint_bbox_coders.py │ │ │ ├── delta_xyzwhlr_bbox_coder.py │ │ │ ├── fcos3d_bbox_coder.py │ │ │ ├── groupfree3d_bbox_coder.py │ │ │ ├── monoflex_bbox_coder.py │ │ │ ├── partial_bin_based_bbox_coder.py │ │ │ ├── pgd_bbox_coder.py │ │ │ ├── point_xyzwhlr_bbox_coder.py │ │ │ └── smoke_bbox_coder.py │ │ ├── samplers │ │ │ ├── __init__.py │ │ │ ├── iou_neg_piecewise_sampler.py │ │ │ └── pseudosample.py │ │ └── voxel │ │ │ ├── __init__.py │ │ │ └── voxel_generator.py │ ├── test_time_augs │ │ ├── __init__.py │ │ └── merge_augs.py │ ├── utils │ │ ├── __init__.py │ │ ├── add_prefix.py │ │ ├── clip_sigmoid.py │ │ ├── edge_indices.py │ │ ├── gaussian.py │ │ ├── gen_keypoints.py │ │ └── handle_objs.py │ └── voxel_encoders │ │ ├── __init__.py │ │ ├── pillar_encoder.py │ │ ├── utils.py │ │ └── voxel_encoder.py ├── registry.py ├── structures │ ├── __init__.py │ ├── bbox_3d │ │ ├── __init__.py │ │ ├── base_box3d.py │ │ ├── box_3d_mode.py │ │ ├── cam_box3d.py │ │ ├── coord_3d_mode.py │ │ ├── depth_box3d.py │ │ ├── lidar_box3d.py │ │ └── utils.py │ ├── det3d_data_sample.py │ ├── ops │ │ ├── __init__.py │ │ ├── box_np_ops.py │ │ ├── iou3d_calculator.py │ │ └── transforms.py │ ├── point_data.py │ └── points │ │ ├── __init__.py │ │ ├── base_points.py │ │ ├── cam_points.py │ │ ├── depth_points.py │ │ └── lidar_points.py ├── testing │ ├── __init__.py │ ├── data_utils.py │ └── model_utils.py ├── utils │ ├── __init__.py │ ├── array_converter.py │ ├── collect_env.py │ ├── compat_cfg.py │ ├── misc.py │ ├── setup_env.py │ └── typing_utils.py ├── version.py └── visualization │ ├── __init__.py │ ├── local_visualizer.py │ └── vis_utils.py ├── pic └── backbone.png ├── requirements.txt ├── requirements ├── build.txt ├── docs.txt ├── mminstall.txt ├── optional.txt ├── readthedocs.txt ├── runtime.txt └── tests.txt ├── resources ├── mmdet3d_outdoor_demo.gif ├── nuimages_demo.gif └── open3d_visual.gif ├── setup.cfg ├── setup.py ├── test.py ├── tests ├── test_apis │ └── test_inferencers │ │ ├── test_lidar_det3d_inferencer.py │ │ ├── test_lidar_seg3d_inferencer.py │ │ ├── test_mono_det3d_inferencer.py │ │ └── test_multi_modality_det3d_inferencer.py ├── test_datasets │ ├── test_dataset_wrappers.py │ ├── test_kitti_dataset.py │ ├── test_lyft_dataset.py │ ├── test_nuscenes_dataset.py │ ├── test_s3dis_dataset.py │ ├── test_scannet_dataset.py │ ├── test_semantickitti_dataset.py │ ├── test_sunrgbd_dataset.py │ └── test_transforms │ │ ├── test_formating.py │ │ ├── test_loading.py │ │ ├── test_transforms_3d.py │ │ └── utils.py ├── test_engine │ └── test_hooks │ │ ├── test_disable_object_sample_hook.py │ │ └── test_visualization_hook.py ├── test_evaluation │ ├── test_functional │ │ ├── test_instance_seg_eval.py │ │ ├── test_kitti_eval.py │ │ ├── test_panoptic_seg_eval.py │ │ └── test_seg_eval.py │ └── test_metrics │ │ ├── test_indoor_metric.py │ │ ├── test_instance_seg_metric.py │ │ ├── test_kitti_metric.py │ │ ├── test_panoptic_seg_metric.py │ │ └── test_seg_metric.py ├── test_models │ ├── test_backbones │ │ ├── test_cylinder3d_backbone.py │ │ ├── test_dgcnn.py │ │ ├── test_dla.py │ │ ├── test_mink_resnet.py │ │ ├── test_minkunet_backbone.py │ │ ├── test_multi_backbone.py │ │ ├── test_pointnet2_sa_msg.py │ │ ├── test_pointnet2_sa_ssg.py │ │ └── test_spvcnn_backbone.py │ ├── test_data_preprocessors │ │ └── test_data_preprocessor.py │ ├── test_decode_heads │ │ ├── test_cylinder3d_head.py │ │ ├── test_dgcnn_head.py │ │ ├── test_minkunet_head.py │ │ ├── test_paconv_head.py │ │ └── test_pointnet2_head.py │ ├── test_dense_heads │ │ ├── test_anchor3d_head.py │ │ ├── test_fcaf3d_head.py │ │ ├── test_fcos_mono3d_head.py │ │ ├── test_freeanchors.py │ │ ├── test_imvoxel_head.py │ │ ├── test_monoflex_head.py │ │ ├── test_pgd_head.py │ │ ├── test_smoke_mono3d_head.py │ │ └── test_ssn.py │ ├── test_detectors │ │ ├── test_3dssd.py │ │ ├── test_center_point.py │ │ ├── test_fcaf3d.py │ │ ├── test_groupfree3d.py │ │ ├── test_h3dnet.py │ │ ├── test_imvotenet.py │ │ ├── test_imvoxelnet.py │ │ ├── test_mvxnet.py │ │ ├── test_parta2.py │ │ ├── test_pointrcnn.py │ │ ├── test_pvrcnn.py │ │ ├── test_sassd.py │ │ ├── test_votenet.py │ │ └── test_voxelnet.py │ ├── test_layers │ │ ├── test_box3d_nms.py │ │ ├── test_dgcnn_modules │ │ │ ├── test_dgcnn_fa_module.py │ │ │ ├── test_dgcnn_fp_module.py │ │ │ └── test_dgcnn_gf_module.py │ │ ├── test_fusion_layers │ │ │ ├── test_fusion_coord_trans.py │ │ │ ├── test_point_fusion.py │ │ │ └── test_vote_fusion.py │ │ ├── test_paconv │ │ │ ├── test_paconv_modules.py │ │ │ └── test_paconv_ops.py │ │ ├── test_pointnet_modules │ │ │ ├── test_point_fp_module.py │ │ │ └── test_point_sa_module.py │ │ ├── test_spconv │ │ │ └── test_spconv_module.py │ │ ├── test_torchsparse │ │ │ └── test_torchsparse_module.py │ │ └── test_vote_module.py │ ├── test_losses │ │ ├── test_chamfer_disrance.py │ │ ├── test_multibin_loss.py │ │ ├── test_paconv_regularization_loss.py │ │ ├── test_rotated_iou_loss.py │ │ └── test_uncertain_smooth_l1_loss.py │ ├── test_middle_encoders │ │ ├── test_sparse_encoders.py │ │ └── test_sparse_unet.py │ ├── test_necks │ │ ├── test_dla_neck.py │ │ ├── test_imvoxel_neck.py │ │ ├── test_pointnet2_fp_neck.py │ │ └── test_second_fpn.py │ ├── test_segmentor │ │ └── test_minkunet.py │ ├── test_segmentors │ │ └── test_cylinder3d.py │ ├── test_task_modules │ │ ├── test_anchor │ │ │ └── test_anchor_3d_generator.py │ │ ├── test_coders │ │ │ ├── test_anchor_free_box_coder.py │ │ │ ├── test_centerpoint_bbox_coder.py │ │ │ ├── test_fcos3d_bbox_coder.py │ │ │ ├── test_monoflex_bbox_coder.py │ │ │ ├── test_partial_bin_based_box_coder.py │ │ │ ├── test_pgd_bbox_coder.py │ │ │ ├── test_point_xyzwhlr_bbox_coder.py │ │ │ └── test_smoke_bbox_coder.py │ │ ├── test_samplers │ │ │ └── test_iou_piecewise_sampler.py │ │ └── test_voxel │ │ │ └── test_voxel_generator.py │ ├── test_utils │ │ └── test_utils.py │ └── test_voxel_encoders │ │ ├── test_pillar_encoder.py │ │ └── test_voxel_encoders.py ├── test_samples │ └── parta2_roihead_inputs.npz ├── test_structures │ ├── test_bbox │ │ ├── test_box3d.py │ │ └── test_coord_3d_mode.py │ ├── test_det3d_data_sample.py │ ├── test_ops │ │ └── test_box_np_ops.py │ ├── test_point_data.py │ └── test_points │ │ ├── test_base_points.py │ │ ├── test_cam_points.py │ │ └── test_depth_points.py └── test_utils │ ├── test_compat_cfg.py │ └── test_setup_env.py └── tools ├── analysis_tools ├── analyze_logs.py ├── benchmark.py └── get_flops.py ├── create_data.py ├── create_data.sh ├── dataset_converters ├── create_gt_database.py ├── indoor_converter.py ├── kitti_converter.py ├── kitti_data_utils.py ├── lyft_converter.py ├── lyft_data_fixer.py ├── nuimage_converter.py ├── nuscenes_converter.py ├── s3dis_data_utils.py ├── scannet_data_utils.py ├── semantickitti_converter.py ├── sunrgbd_data_utils.py ├── update_infos_to_v2.py └── waymo_converter.py ├── deployment ├── mmdet3d2torchserve.py ├── mmdet3d_handler.py └── test_torchserver.py ├── dist_test.sh ├── dist_train.sh ├── misc ├── browse_dataset.py ├── fuse_conv_bn.py ├── print_config.py └── visualize_results.py ├── model_converters ├── convert_h3dnet_checkpoints.py ├── convert_votenet_checkpoints.py ├── publish_model.py └── regnet2mmdet.py ├── slurm_test.sh ├── slurm_train.sh ├── test.py ├── train.py ├── update_data_coords.py └── update_data_coords.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.ipynb 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/en/_build/ 69 | docs/zh_cn/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | # cython generated cpp 109 | data 110 | .vscode 111 | .idea 112 | 113 | # custom 114 | *.pkl 115 | *.pkl.json 116 | *.log.json 117 | work_dirs/ 118 | exps/ 119 | *~ 120 | mmdet3d/.mim 121 | 122 | # Pytorch 123 | *.pth 124 | 125 | # demo 126 | *.jpg 127 | *.png 128 | data/s3dis/Stanford3dDataset_v1.2_Aligned_Version/ 129 | data/scannet/scans/ 130 | data/sunrgbd/OFFICIAL_SUNRGBD/ 131 | *.obj 132 | *.ply 133 | 134 | # Waymo evaluation 135 | mmdet3d/evaluation/functional/waymo_utils/compute_detection_metrics_main 136 | mmdet3d/evaluation/functional/waymo_utils/compute_detection_let_metrics_main 137 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/PyCQA/flake8 3 | rev: 5.0.4 4 | hooks: 5 | - id: flake8 6 | - repo: https://github.com/PyCQA/isort 7 | rev: 5.11.5 8 | hooks: 9 | - id: isort 10 | - repo: https://github.com/pre-commit/mirrors-yapf 11 | rev: v0.32.0 12 | hooks: 13 | - id: yapf 14 | - repo: https://github.com/pre-commit/pre-commit-hooks 15 | rev: v4.3.0 16 | hooks: 17 | - id: trailing-whitespace 18 | - id: check-yaml 19 | - id: end-of-file-fixer 20 | - id: requirements-txt-fixer 21 | - id: double-quote-string-fixer 22 | - id: check-merge-conflict 23 | - id: fix-encoding-pragma 24 | args: ["--remove"] 25 | - id: mixed-line-ending 26 | args: ["--fix=lf"] 27 | - repo: https://github.com/codespell-project/codespell 28 | rev: v2.2.1 29 | hooks: 30 | - id: codespell 31 | - repo: https://github.com/executablebooks/mdformat 32 | rev: 0.7.9 33 | hooks: 34 | - id: mdformat 35 | args: [ "--number" ] 36 | additional_dependencies: 37 | - mdformat-openmmlab 38 | - mdformat_frontmatter 39 | - linkify-it-py 40 | - repo: https://github.com/myint/docformatter 41 | rev: v1.3.1 42 | hooks: 43 | - id: docformatter 44 | args: ["--in-place", "--wrap-descriptions", "79"] 45 | - repo: https://github.com/open-mmlab/pre-commit-hooks 46 | rev: v0.2.0 # Use the ref you want to point at 47 | hooks: 48 | - id: check-algo-readme 49 | - id: check-copyright 50 | args: ["mmdet3d"] # replace the dir_to_check with your expected directory to check 51 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | formats: all 4 | 5 | python: 6 | version: 3.7 7 | install: 8 | - requirements: requirements/docs.txt 9 | - requirements: requirements/readthedocs.txt 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include mmdet3d/.mim/model-index.yml 2 | include requirements/*.txt 3 | recursive-include mmdet3d/.mim/ops *.cpp *.cu *.h *.cc 4 | recursive-include mmdet3d/.mim/configs *.py *.yml 5 | recursive-include mmdet3d/.mim/tools *.sh *.py 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FGFusion: Fine-Grained Lidar-Camera Fusion for 3D Object Detection 2 | 3 | ![image](./pic/backbone.png) 4 | 5 | ## Abstract 6 | Lidars and cameras are critical sensors that provide complementary information for 3D detection in autonomous driving. While most prevalent methods progressively downscale the 3D point clouds and camera images and then fuse the high-level features, the downscaled features inevitably lose low-level detailed information. In this paper, we propose Fine-Grained Lidar-Camera Fusion (FGFusion) that make full use of multi-scale features of image and point cloud and fuse them in a fine-grained way. First, we design a dual pathway hierarchy structure to extract both high-level semantic and low-level detailed features of the image. Second, an auxiliary network is introduced to guide point cloud features to better learn the fine-grained spatial information. Finally, we propose multi-scale fusion (MSF) to fuse the last N feature maps of image and point cloud. Extensive experiments on two popular autonomous driving benchmarks, i.e. KITTI and Waymo, demonstrate the effectiveness of our method. 7 | 8 | ## Main Results 9 | 10 | #### KITTI 11 | 12 | | | Easy | Mod. | Hard | 13 | |-----------|-------|-------|-------| 14 | |Car | 92.38 | 84.96 | 83.84 | 15 | |Pedestrian | 72.63 | 65.07 | 59.21 | 16 | |Cyclist | 90.33 | 74.19 | 70.84 | 17 | 18 | #### Waymo 19 | 20 | | | L1 | L2 | 21 | |-----------|-------------|-------------| 22 | |Vehicle | 81.92/81.44 | 73.85/73.34 | 23 | |Pedestrian | 85.73/82.85 | 78.81/76.14 | 24 | 25 | ## Reference 26 | If you find our paper useful, please kindly cite us via: 27 | ``` 28 | @article{yin2023fgfusion, 29 | title={FGFusion: Fine-Grained Lidar-Camera Fusion for 3D Object Detection}, 30 | author={Yin, Zixuan and Sun, Han and Liu, Ningzhong and Zhou, Huiyu and Shen, Jiaquan}, 31 | journal={PRCV}, 32 | year={2023} 33 | } 34 | ``` 35 | 36 | ## Contact 37 | This repository is implemented by Zixuan Yin (yinzixuan@nuaa.edu.cn) 38 | -------------------------------------------------------------------------------- /demo/mono_det_demo.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from argparse import ArgumentParser 3 | 4 | import mmcv 5 | 6 | from mmdet3d.apis import inference_mono_3d_detector, init_model 7 | from mmdet3d.registry import VISUALIZERS 8 | 9 | 10 | def parse_args(): 11 | parser = ArgumentParser() 12 | parser.add_argument('img', help='image file') 13 | parser.add_argument('ann', help='ann file') 14 | parser.add_argument('config', help='Config file') 15 | parser.add_argument('checkpoint', help='Checkpoint file') 16 | parser.add_argument( 17 | '--device', default='cuda:0', help='Device used for inference') 18 | parser.add_argument( 19 | '--cam-type', 20 | type=str, 21 | default='CAM_BACK', 22 | help='choose camera type to inference') 23 | parser.add_argument( 24 | '--score-thr', type=float, default=0.30, help='bbox score threshold') 25 | parser.add_argument( 26 | '--out-dir', type=str, default='demo', help='dir to save results') 27 | parser.add_argument( 28 | '--show', 29 | action='store_true', 30 | help='show online visualization results') 31 | parser.add_argument( 32 | '--snapshot', 33 | action='store_true', 34 | help='whether to save online visualization results') 35 | args = parser.parse_args() 36 | return args 37 | 38 | 39 | def main(args): 40 | # build the model from a config file and a checkpoint file 41 | model = init_model(args.config, args.checkpoint, device=args.device) 42 | 43 | # init visualizer 44 | visualizer = VISUALIZERS.build(model.cfg.visualizer) 45 | visualizer.dataset_meta = model.dataset_meta 46 | 47 | # test a single image 48 | result = inference_mono_3d_detector(model, args.img, args.ann, 49 | args.cam_type) 50 | 51 | img = mmcv.imread(args.img) 52 | img = mmcv.imconvert(img, 'bgr', 'rgb') 53 | 54 | data_input = dict(img=img) 55 | # show the results 56 | visualizer.add_datasample( 57 | 'result', 58 | data_input, 59 | data_sample=result, 60 | draw_gt=False, 61 | show=args.show, 62 | wait_time=0, 63 | out_file=args.out_dir, 64 | pred_score_thr=args.score_thr, 65 | vis_task='mono_det') 66 | 67 | 68 | if __name__ == '__main__': 69 | args = parse_args() 70 | main(args) 71 | -------------------------------------------------------------------------------- /demo/multi_modality_demo.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from argparse import ArgumentParser 3 | 4 | import mmcv 5 | 6 | from mmdet3d.apis import inference_multi_modality_detector, init_model 7 | from mmdet3d.registry import VISUALIZERS 8 | 9 | 10 | def parse_args(): 11 | parser = ArgumentParser() 12 | parser.add_argument('pcd', help='Point cloud file') 13 | parser.add_argument('img', help='image file') 14 | parser.add_argument('ann', help='ann file') 15 | parser.add_argument('config', help='Config file') 16 | parser.add_argument('checkpoint', help='Checkpoint file') 17 | parser.add_argument( 18 | '--device', default='cuda:0', help='Device used for inference') 19 | parser.add_argument( 20 | '--cam-type', 21 | type=str, 22 | default='CAM2', 23 | help='choose camera type to inference') 24 | parser.add_argument( 25 | '--score-thr', type=float, default=0.0, help='bbox score threshold') 26 | parser.add_argument( 27 | '--out-dir', type=str, default='demo', help='dir to save results') 28 | parser.add_argument( 29 | '--show', 30 | action='store_true', 31 | help='show online visualization results') 32 | parser.add_argument( 33 | '--snapshot', 34 | action='store_true', 35 | help='whether to save online visualization results') 36 | args = parser.parse_args() 37 | return args 38 | 39 | 40 | def main(args): 41 | # build the model from a config file and a checkpoint file 42 | model = init_model(args.config, args.checkpoint, device=args.device) 43 | 44 | # init visualizer 45 | visualizer = VISUALIZERS.build(model.cfg.visualizer) 46 | visualizer.dataset_meta = model.dataset_meta 47 | 48 | # test a single image and point cloud sample 49 | result, data = inference_multi_modality_detector(model, args.pcd, args.img, 50 | args.ann, args.cam_type) 51 | points = data['inputs']['points'] 52 | img = mmcv.imread(args.img) 53 | img = mmcv.imconvert(img, 'bgr', 'rgb') 54 | data_input = dict(points=points, img=img) 55 | 56 | # show the results 57 | visualizer.add_datasample( 58 | 'result', 59 | data_input, 60 | data_sample=result, 61 | draw_gt=False, 62 | show=True, 63 | wait_time=0, 64 | out_file=args.out_dir, 65 | pred_score_thr=args.score_thr, 66 | vis_task='multi-modality_det') 67 | 68 | 69 | if __name__ == '__main__': 70 | args = parse_args() 71 | main(args) 72 | -------------------------------------------------------------------------------- /demo/pcd_demo.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from argparse import ArgumentParser 3 | 4 | # from mmdet3d.apis import inference_detector, init_model, show_result_meshlab 5 | from mmdet3d.apis import inference_detector, init_model 6 | from mmdet3d.registry import VISUALIZERS 7 | 8 | 9 | def parse_args(): 10 | parser = ArgumentParser() 11 | parser.add_argument('pcd', help='Point cloud file') 12 | parser.add_argument('config', help='Config file') 13 | parser.add_argument('checkpoint', help='Checkpoint file') 14 | parser.add_argument( 15 | '--device', default='cuda:0', help='Device used for inference') 16 | parser.add_argument( 17 | '--score-thr', type=float, default=0.0, help='bbox score threshold') 18 | parser.add_argument( 19 | '--out-dir', type=str, default='demo', help='dir to save results') 20 | parser.add_argument( 21 | '--show', 22 | action='store_true', 23 | help='show online visualization results') 24 | parser.add_argument( 25 | '--snapshot', 26 | action='store_true', 27 | help='whether to save online visualization results') 28 | args = parser.parse_args() 29 | return args 30 | 31 | 32 | def main(args): 33 | # TODO: Support inference of point cloud numpy file. 34 | # build the model from a config file and a checkpoint file 35 | model = init_model(args.config, args.checkpoint, device=args.device) 36 | 37 | # init visualizer 38 | visualizer = VISUALIZERS.build(model.cfg.visualizer) 39 | visualizer.dataset_meta = model.dataset_meta 40 | 41 | # test a single point cloud sample 42 | result, data = inference_detector(model, args.pcd) 43 | points = data['inputs']['points'] 44 | data_input = dict(points=points) 45 | 46 | 47 | # show the results 48 | visualizer.add_datasample( 49 | 'result', 50 | data_input, 51 | data_sample=result, 52 | draw_gt=False, 53 | draw_pred=False, 54 | show=args.show, 55 | wait_time=0, 56 | # out_file=args.out_dir, 57 | out_file='./', 58 | # pred_score_thr=args.score_thr, 59 | vis_task='lidar_det') 60 | 61 | 62 | if __name__ == '__main__': 63 | args = parse_args() 64 | main(args) 65 | -------------------------------------------------------------------------------- /demo/pcd_seg_demo.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from argparse import ArgumentParser 3 | 4 | from mmdet3d.apis import inference_segmentor, init_model 5 | from mmdet3d.registry import VISUALIZERS 6 | 7 | 8 | def parse_args(): 9 | parser = ArgumentParser() 10 | parser.add_argument('pcd', help='Point cloud file') 11 | parser.add_argument('config', help='Config file') 12 | parser.add_argument('checkpoint', help='Checkpoint file') 13 | parser.add_argument( 14 | '--device', default='cuda:0', help='Device used for inference') 15 | parser.add_argument( 16 | '--out-dir', type=str, default='demo', help='dir to save results') 17 | parser.add_argument( 18 | '--show', 19 | action='store_true', 20 | help='show online visualization results') 21 | parser.add_argument( 22 | '--snapshot', 23 | action='store_true', 24 | help='whether to save online visualization results') 25 | args = parser.parse_args() 26 | return args 27 | 28 | 29 | def main(args): 30 | # build the model from a config file and a checkpoint file 31 | model = init_model(args.config, args.checkpoint, device=args.device) 32 | 33 | # init visualizer 34 | visualizer = VISUALIZERS.build(model.cfg.visualizer) 35 | visualizer.dataset_meta = model.dataset_meta 36 | 37 | # test a single point cloud sample 38 | result, data = inference_segmentor(model, args.pcd) 39 | points = data['inputs']['points'] 40 | data_input = dict(points=points) 41 | # show the results 42 | visualizer.add_datasample( 43 | 'result', 44 | data_input, 45 | data_sample=result, 46 | draw_gt=False, 47 | show=args.show, 48 | wait_time=0, 49 | out_file=args.out_dir, 50 | vis_task='lidar_seg') 51 | 52 | 53 | if __name__ == '__main__': 54 | args = parse_args() 55 | main(args) 56 | -------------------------------------------------------------------------------- /mmdet3d/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import mmcv 3 | import mmdet 4 | import mmengine 5 | from mmengine.utils import digit_version 6 | 7 | from .version import __version__, version_info 8 | 9 | mmcv_minimum_version = '2.0.0rc4' 10 | mmcv_maximum_version = '2.1.0' 11 | mmcv_version = digit_version(mmcv.__version__) 12 | 13 | mmengine_minimum_version = '0.7.1' 14 | mmengine_maximum_version = '1.0.0' 15 | mmengine_version = digit_version(mmengine.__version__) 16 | 17 | mmdet_minimum_version = '3.0.0' 18 | mmdet_maximum_version = '3.1.0' 19 | mmdet_version = digit_version(mmdet.__version__) 20 | 21 | assert (mmcv_version >= digit_version(mmcv_minimum_version) 22 | and mmcv_version < digit_version(mmcv_maximum_version)), \ 23 | f'MMCV=={mmcv.__version__} is used but incompatible. ' \ 24 | f'Please install mmcv>={mmcv_minimum_version}, <{mmcv_maximum_version}.' 25 | 26 | assert (mmengine_version >= digit_version(mmengine_minimum_version) 27 | and mmengine_version < digit_version(mmengine_maximum_version)), \ 28 | f'MMEngine=={mmengine.__version__} is used but incompatible. ' \ 29 | f'Please install mmengine>={mmengine_minimum_version}, ' \ 30 | f'<{mmengine_maximum_version}.' 31 | 32 | assert (mmdet_version >= digit_version(mmdet_minimum_version) 33 | and mmdet_version < digit_version(mmdet_maximum_version)), \ 34 | f'MMDET=={mmdet.__version__} is used but incompatible. ' \ 35 | f'Please install mmdet>={mmdet_minimum_version}, ' \ 36 | f'<{mmdet_maximum_version}.' 37 | 38 | __all__ = ['__version__', 'version_info', 'digit_version'] 39 | -------------------------------------------------------------------------------- /mmdet3d/apis/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .inference import (convert_SyncBN, inference_detector, 3 | inference_mono_3d_detector, 4 | inference_multi_modality_detector, inference_segmentor, 5 | init_model) 6 | from .inferencers import (Base3DInferencer, LidarDet3DInferencer, 7 | LidarSeg3DInferencer, MonoDet3DInferencer, 8 | MultiModalityDet3DInferencer) 9 | 10 | __all__ = [ 11 | 'inference_detector', 'init_model', 'inference_mono_3d_detector', 12 | 'convert_SyncBN', 'inference_multi_modality_detector', 13 | 'inference_segmentor', 'Base3DInferencer', 'MonoDet3DInferencer', 14 | 'LidarDet3DInferencer', 'LidarSeg3DInferencer', 15 | 'MultiModalityDet3DInferencer' 16 | ] 17 | -------------------------------------------------------------------------------- /mmdet3d/apis/inferencers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .base_3d_inferencer import Base3DInferencer 3 | from .lidar_det3d_inferencer import LidarDet3DInferencer 4 | from .lidar_seg3d_inferencer import LidarSeg3DInferencer 5 | from .mono_det3d_inferencer import MonoDet3DInferencer 6 | from .multi_modality_det3d_inferencer import MultiModalityDet3DInferencer 7 | 8 | __all__ = [ 9 | 'Base3DInferencer', 'MonoDet3DInferencer', 'LidarDet3DInferencer', 10 | 'LidarSeg3DInferencer', 'MultiModalityDet3DInferencer' 11 | ] 12 | -------------------------------------------------------------------------------- /mmdet3d/datasets/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .dataset_wrappers import CBGSDataset 3 | from .det3d_dataset import Det3DDataset 4 | from .kitti_dataset import KittiDataset 5 | from .lyft_dataset import LyftDataset 6 | from .nuscenes_dataset import NuScenesDataset 7 | # yapf: enable 8 | from .s3dis_dataset import S3DISDataset, S3DISSegDataset 9 | from .scannet_dataset import (ScanNetDataset, ScanNetInstanceSegDataset, 10 | ScanNetSegDataset) 11 | from .seg3d_dataset import Seg3DDataset 12 | from .semantickitti_dataset import SemanticKittiDataset 13 | from .sunrgbd_dataset import SUNRGBDDataset 14 | # yapf: disable 15 | from .transforms import (AffineResize, BackgroundPointsFilter, GlobalAlignment, 16 | GlobalRotScaleTrans, IndoorPatchPointSample, 17 | IndoorPointSample, LoadAnnotations3D, 18 | LoadPointsFromDict, LoadPointsFromFile, 19 | LoadPointsFromMultiSweeps, NormalizePointsColor, 20 | ObjectNameFilter, ObjectNoise, ObjectRangeFilter, 21 | ObjectSample, PointSample, PointShuffle, 22 | PointsRangeFilter, RandomDropPointsColor, 23 | RandomFlip3D, RandomJitterPoints, RandomResize3D, 24 | RandomShiftScale, Resize3D, VoxelBasedPointSampler) 25 | from .utils import get_loading_pipeline 26 | from .waymo_dataset import WaymoDataset 27 | 28 | __all__ = [ 29 | 'KittiDataset', 'CBGSDataset', 'NuScenesDataset', 'LyftDataset', 30 | 'ObjectSample', 'RandomFlip3D', 'ObjectNoise', 'GlobalRotScaleTrans', 31 | 'PointShuffle', 'ObjectRangeFilter', 'PointsRangeFilter', 32 | 'LoadPointsFromFile', 'S3DISSegDataset', 'S3DISDataset', 33 | 'NormalizePointsColor', 'IndoorPatchPointSample', 'IndoorPointSample', 34 | 'PointSample', 'LoadAnnotations3D', 'GlobalAlignment', 'SUNRGBDDataset', 35 | 'ScanNetDataset', 'ScanNetSegDataset', 'ScanNetInstanceSegDataset', 36 | 'SemanticKittiDataset', 'Det3DDataset', 'Seg3DDataset', 37 | 'LoadPointsFromMultiSweeps', 'WaymoDataset', 'BackgroundPointsFilter', 38 | 'VoxelBasedPointSampler', 'get_loading_pipeline', 'RandomDropPointsColor', 39 | 'RandomJitterPoints', 'ObjectNameFilter', 'AffineResize', 40 | 'RandomShiftScale', 'LoadPointsFromDict', 'Resize3D', 'RandomResize3D', 41 | ] 42 | -------------------------------------------------------------------------------- /mmdet3d/datasets/transforms/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .dbsampler import DataBaseSampler 3 | from .formating import Pack3DDetInputs 4 | from .loading import (LoadAnnotations3D, LoadImageFromFileMono3D, 5 | LoadMultiViewImageFromFiles, LoadPointsFromDict, 6 | LoadPointsFromFile, LoadPointsFromMultiSweeps, 7 | MonoDet3DInferencerLoader, 8 | MultiModalityDet3DInferencerLoader, NormalizePointsColor, 9 | PointSegClassMapping) 10 | from .test_time_aug import MultiScaleFlipAug3D 11 | # yapf: disable 12 | from .transforms_3d import (AffineResize, BackgroundPointsFilter, 13 | GlobalAlignment, GlobalRotScaleTrans, 14 | IndoorPatchPointSample, IndoorPointSample, 15 | LaserMix, MultiViewWrapper, ObjectNameFilter, 16 | ObjectNoise, ObjectRangeFilter, ObjectSample, 17 | PhotoMetricDistortion3D, PointSample, PointShuffle, 18 | PointsRangeFilter, PolarMix, RandomDropPointsColor, 19 | RandomFlip3D, RandomJitterPoints, RandomResize3D, 20 | RandomShiftScale, Resize3D, VoxelBasedPointSampler) 21 | 22 | __all__ = [ 23 | 'ObjectSample', 'RandomFlip3D', 'ObjectNoise', 'GlobalRotScaleTrans', 24 | 'PointShuffle', 'ObjectRangeFilter', 'PointsRangeFilter', 25 | 'Pack3DDetInputs', 'LoadMultiViewImageFromFiles', 'LoadPointsFromFile', 26 | 'DataBaseSampler', 'NormalizePointsColor', 'LoadAnnotations3D', 27 | 'IndoorPointSample', 'PointSample', 'PointSegClassMapping', 28 | 'MultiScaleFlipAug3D', 'LoadPointsFromMultiSweeps', 29 | 'BackgroundPointsFilter', 'VoxelBasedPointSampler', 'GlobalAlignment', 30 | 'IndoorPatchPointSample', 'LoadImageFromFileMono3D', 'ObjectNameFilter', 31 | 'RandomDropPointsColor', 'RandomJitterPoints', 'AffineResize', 32 | 'RandomShiftScale', 'LoadPointsFromDict', 'Resize3D', 'RandomResize3D', 33 | 'MultiViewWrapper', 'PhotoMetricDistortion3D', 'MonoDet3DInferencerLoader', 34 | 'LidarDet3DInferencerLoader', 'PolarMix', 'LaserMix', 35 | 'MultiModalityDet3DInferencerLoader' 36 | ] 37 | -------------------------------------------------------------------------------- /mmdet3d/engine/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .hooks import BenchmarkHook, Det3DVisualizationHook 3 | 4 | __all__ = ['Det3DVisualizationHook', 'BenchmarkHook'] 5 | -------------------------------------------------------------------------------- /mmdet3d/engine/hooks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .benchmark_hook import BenchmarkHook 3 | from .disable_object_sample_hook import DisableObjectSampleHook 4 | from .visualization_hook import Det3DVisualizationHook 5 | 6 | __all__ = [ 7 | 'Det3DVisualizationHook', 'BenchmarkHook', 'DisableObjectSampleHook' 8 | ] 9 | -------------------------------------------------------------------------------- /mmdet3d/engine/hooks/benchmark_hook.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | 3 | from mmengine.hooks import Hook 4 | 5 | from mmdet3d.registry import HOOKS 6 | 7 | 8 | @HOOKS.register_module() 9 | class BenchmarkHook(Hook): 10 | """A hook that logs the training speed of each epch.""" 11 | 12 | priority = 'NORMAL' 13 | 14 | def after_train_epoch(self, runner) -> None: 15 | """We use the average throughput in iterations of the entire training 16 | run and skip the first 50 iterations of each epoch to skip GPU warmup 17 | time. 18 | 19 | Args: 20 | runner (Runner): The runner of the training process. 21 | """ 22 | message_hub = runner.message_hub 23 | max_iter_num = len(runner.train_dataloader) 24 | speed = message_hub.get_scalar('train/time').mean(max_iter_num - 50) 25 | message_hub.update_scalar('train/speed', speed) 26 | runner.logger.info( 27 | f'Training speed of epoch {runner.epoch + 1} is {speed} s/iter') 28 | 29 | def after_train(self, runner) -> None: 30 | """Log average training speed of entire training process. 31 | 32 | Args: 33 | runner (Runner): The runner of the training process. 34 | """ 35 | message_hub = runner.message_hub 36 | avg_speed = message_hub.get_scalar('train/speed').mean() 37 | runner.logger.info('Average training speed of entire training process' 38 | f'is {avg_speed} s/iter') 39 | -------------------------------------------------------------------------------- /mmdet3d/engine/hooks/disable_object_sample_hook.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmengine.hooks import Hook 3 | from mmengine.model import is_model_wrapper 4 | from mmengine.runner import Runner 5 | 6 | from mmdet3d.datasets.transforms import ObjectSample 7 | from mmdet3d.registry import HOOKS 8 | 9 | 10 | @HOOKS.register_module() 11 | class DisableObjectSampleHook(Hook): 12 | """The hook of disabling augmentations during training. 13 | 14 | Args: 15 | disable_after_epoch (int): The number of epochs after which 16 | the ``ObjectSample`` will be closed in the training. 17 | Defaults to 15. 18 | """ 19 | 20 | def __init__(self, disable_after_epoch: int = 15): 21 | self.disable_after_epoch = disable_after_epoch 22 | self._restart_dataloader = False 23 | 24 | def before_train_epoch(self, runner: Runner): 25 | """Close augmentation. 26 | 27 | Args: 28 | runner (Runner): The runner. 29 | """ 30 | epoch = runner.epoch 31 | train_loader = runner.train_dataloader 32 | model = runner.model 33 | # TODO: refactor after mmengine using model wrapper 34 | if is_model_wrapper(model): 35 | model = model.module 36 | if epoch == self.disable_after_epoch: 37 | runner.logger.info('Disable ObjectSample') 38 | for transform in runner.train_dataloader.dataset.pipeline.transforms: # noqa: E501 39 | if isinstance(transform, ObjectSample): 40 | assert hasattr(transform, 'disabled') 41 | transform.disabled = True 42 | # The dataset pipeline cannot be updated when persistent_workers 43 | # is True, so we need to force the dataloader's multi-process 44 | # restart. This is a very hacky approach. 45 | if hasattr(train_loader, 'persistent_workers' 46 | ) and train_loader.persistent_workers is True: 47 | train_loader._DataLoader__initialized = False 48 | train_loader._iterator = None 49 | self._restart_dataloader = True 50 | else: 51 | # Once the restart is complete, we need to restore 52 | # the initialization flag. 53 | if self._restart_dataloader: 54 | train_loader._DataLoader__initialized = True 55 | -------------------------------------------------------------------------------- /mmdet3d/evaluation/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmdet3d.evaluation.functional.kitti_utils import (do_eval, eval_class, 3 | kitti_eval, 4 | kitti_eval_coco_style) 5 | from .functional import (aggregate_predictions, average_precision, 6 | eval_det_cls, eval_map_recall, fast_hist, get_acc, 7 | get_acc_cls, get_classwise_aps, get_single_class_aps, 8 | indoor_eval, instance_seg_eval, load_lyft_gts, 9 | load_lyft_predictions, lyft_eval, panoptic_seg_eval, 10 | per_class_iou, rename_gt, seg_eval) 11 | from .metrics import (IndoorMetric, InstanceSegMetric, KittiMetric, LyftMetric, 12 | NuScenesMetric, PanopticSegMetric, SegMetric, 13 | WaymoMetric) 14 | 15 | __all__ = [ 16 | 'kitti_eval_coco_style', 'kitti_eval', 'indoor_eval', 'lyft_eval', 17 | 'seg_eval', 'instance_seg_eval', 'average_precision', 'eval_det_cls', 18 | 'eval_map_recall', 'indoor_eval', 'aggregate_predictions', 'rename_gt', 19 | 'instance_seg_eval', 'load_lyft_gts', 'load_lyft_predictions', 'lyft_eval', 20 | 'get_classwise_aps', 'get_single_class_aps', 'fast_hist', 'per_class_iou', 21 | 'get_acc', 'get_acc_cls', 'seg_eval', 'KittiMetric', 'NuScenesMetric', 22 | 'IndoorMetric', 'LyftMetric', 'SegMetric', 'InstanceSegMetric', 23 | 'WaymoMetric', 'eval_class', 'do_eval', 'PanopticSegMetric', 24 | 'panoptic_seg_eval' 25 | ] 26 | -------------------------------------------------------------------------------- /mmdet3d/evaluation/functional/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .indoor_eval import (average_precision, eval_det_cls, eval_map_recall, 3 | indoor_eval) 4 | from .instance_seg_eval import (aggregate_predictions, instance_seg_eval, 5 | rename_gt) 6 | from .kitti_utils import do_eval, kitti_eval, kitti_eval_coco_style 7 | from .lyft_eval import (get_classwise_aps, get_single_class_aps, load_lyft_gts, 8 | load_lyft_predictions, lyft_eval) 9 | from .panoptic_seg_eval import panoptic_seg_eval 10 | from .scannet_utils import evaluate_matches, scannet_eval 11 | from .seg_eval import fast_hist, get_acc, get_acc_cls, per_class_iou, seg_eval 12 | 13 | __all__ = [ 14 | 'average_precision', 'eval_det_cls', 'eval_map_recall', 'indoor_eval', 15 | 'aggregate_predictions', 'rename_gt', 'instance_seg_eval', 'load_lyft_gts', 16 | 'load_lyft_predictions', 'lyft_eval', 'get_classwise_aps', 17 | 'get_single_class_aps', 'fast_hist', 'per_class_iou', 'get_acc', 18 | 'get_acc_cls', 'seg_eval', 'kitti_eval', 'kitti_eval_coco_style', 19 | 'scannet_eval', 'evaluate_matches', 'do_eval', 'panoptic_seg_eval' 20 | ] 21 | -------------------------------------------------------------------------------- /mmdet3d/evaluation/functional/kitti_utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .eval import do_eval, eval_class, kitti_eval, kitti_eval_coco_style 3 | 4 | __all__ = ['kitti_eval', 'kitti_eval_coco_style', 'do_eval', 'eval_class'] 5 | -------------------------------------------------------------------------------- /mmdet3d/evaluation/functional/scannet_utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .evaluate_semantic_instance import evaluate_matches, scannet_eval 3 | 4 | __all__ = ['scannet_eval', 'evaluate_matches'] 5 | -------------------------------------------------------------------------------- /mmdet3d/evaluation/functional/waymo_utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | 3 | from .prediction_to_waymo import Prediction2Waymo 4 | 5 | __all__ = ['Prediction2Waymo'] 6 | -------------------------------------------------------------------------------- /mmdet3d/evaluation/metrics/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .indoor_metric import IndoorMetric # noqa: F401,F403 3 | from .instance_seg_metric import InstanceSegMetric # noqa: F401,F403 4 | from .kitti_metric import KittiMetric # noqa: F401,F403 5 | from .lyft_metric import LyftMetric # noqa: F401,F403 6 | from .nuscenes_metric import NuScenesMetric # noqa: F401,F403 7 | from .panoptic_seg_metric import PanopticSegMetric # noqa: F401,F403 8 | from .seg_metric import SegMetric # noqa: F401,F403 9 | from .waymo_metric import WaymoMetric # noqa: F401,F403 10 | 11 | __all__ = [ 12 | 'KittiMetric', 'NuScenesMetric', 'IndoorMetric', 'LyftMetric', 'SegMetric', 13 | 'InstanceSegMetric', 'WaymoMetric', 'PanopticSegMetric' 14 | ] 15 | -------------------------------------------------------------------------------- /mmdet3d/models/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmdet3d.models.layers.fusion_layers import * # noqa: F401,F403 3 | from .backbones import * # noqa: F401,F403 4 | from .data_preprocessors import * # noqa: F401,F403 5 | from .decode_heads import * # noqa: F401,F403 6 | from .dense_heads import * # noqa: F401,F403 7 | from .detectors import * # noqa: F401,F403 8 | from .layers import * # noqa: F401,F403 9 | from .losses import * # noqa: F401,F403 10 | from .middle_encoders import * # noqa: F401,F403 11 | from .necks import * # noqa: F401,F403 12 | from .roi_heads import * # noqa: F401,F403 13 | from .segmentors import * # noqa: F401,F403 14 | from .test_time_augs import * # noqa: F401,F403 15 | from .utils import * # noqa: F401,F403 16 | from .voxel_encoders import * # noqa: F401,F403 17 | -------------------------------------------------------------------------------- /mmdet3d/models/backbones/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmdet.models.backbones import SSDVGG, HRNet, ResNet, ResNetV1d, ResNeXt 3 | 4 | from .cylinder3d import Asymm3DSpconv 5 | from .dgcnn import DGCNNBackbone 6 | from .dla import DLANet 7 | from .mink_resnet import MinkResNet 8 | from .minkunet_backbone import MinkUNetBackbone 9 | from .multi_backbone import MultiBackbone 10 | from .nostem_regnet import NoStemRegNet 11 | from .pointnet2_sa_msg import PointNet2SAMSG 12 | from .pointnet2_sa_ssg import PointNet2SASSG 13 | from .second import SECOND 14 | from .spvcnn_backone import SPVCNNBackbone 15 | 16 | __all__ = [ 17 | 'ResNet', 'ResNetV1d', 'ResNeXt', 'SSDVGG', 'HRNet', 'NoStemRegNet', 18 | 'SECOND', 'DGCNNBackbone', 'PointNet2SASSG', 'PointNet2SAMSG', 19 | 'MultiBackbone', 'DLANet', 'MinkResNet', 'Asymm3DSpconv', 20 | 'MinkUNetBackbone', 'SPVCNNBackbone' 21 | ] 22 | -------------------------------------------------------------------------------- /mmdet3d/models/backbones/base_pointnet.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import warnings 3 | from abc import ABCMeta 4 | 5 | from mmengine.model import BaseModule 6 | 7 | 8 | class BasePointNet(BaseModule, metaclass=ABCMeta): 9 | """Base class for PointNet.""" 10 | 11 | def __init__(self, init_cfg=None, pretrained=None): 12 | super(BasePointNet, self).__init__(init_cfg) 13 | self.fp16_enabled = False 14 | assert not (init_cfg and pretrained), \ 15 | 'init_cfg and pretrained cannot be setting at the same time' 16 | if isinstance(pretrained, str): 17 | warnings.warn('DeprecationWarning: pretrained is a deprecated, ' 18 | 'please use "init_cfg" instead') 19 | self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) 20 | 21 | @staticmethod 22 | def _split_point_feats(points): 23 | """Split coordinates and features of input points. 24 | 25 | Args: 26 | points (torch.Tensor): Point coordinates with features, 27 | with shape (B, N, 3 + input_feature_dim). 28 | 29 | Returns: 30 | torch.Tensor: Coordinates of input points. 31 | torch.Tensor: Features of input points. 32 | """ 33 | xyz = points[..., 0:3].contiguous() 34 | if points.size(-1) > 3: 35 | features = points[..., 3:].transpose(1, 2).contiguous() 36 | else: 37 | features = None 38 | 39 | return xyz, features 40 | -------------------------------------------------------------------------------- /mmdet3d/models/data_preprocessors/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .data_preprocessor import Det3DDataPreprocessor 3 | 4 | __all__ = ['Det3DDataPreprocessor'] 5 | -------------------------------------------------------------------------------- /mmdet3d/models/decode_heads/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .cylinder3d_head import Cylinder3DHead 3 | from .dgcnn_head import DGCNNHead 4 | from .minkunet_head import MinkUNetHead 5 | from .paconv_head import PAConvHead 6 | from .pointnet2_head import PointNet2Head 7 | 8 | __all__ = [ 9 | 'PointNet2Head', 'DGCNNHead', 'PAConvHead', 'Cylinder3DHead', 10 | 'MinkUNetHead' 11 | ] 12 | -------------------------------------------------------------------------------- /mmdet3d/models/decode_heads/dgcnn_head.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from typing import Sequence 3 | 4 | from mmcv.cnn.bricks import ConvModule 5 | from torch import Tensor 6 | 7 | from mmdet3d.models.layers import DGCNNFPModule 8 | from mmdet3d.registry import MODELS 9 | from .decode_head import Base3DDecodeHead 10 | 11 | 12 | @MODELS.register_module() 13 | class DGCNNHead(Base3DDecodeHead): 14 | r"""DGCNN decoder head. 15 | 16 | Decoder head used in `DGCNN `_. 17 | Refer to the 18 | `reimplementation code `_. 19 | 20 | Args: 21 | fp_channels (Sequence[int]): Tuple of mlp channels in feature 22 | propagation (FP) modules. Defaults to (1216, 512). 23 | """ 24 | 25 | def __init__(self, fp_channels: Sequence[int] = (1216, 512), 26 | **kwargs) -> None: 27 | super(DGCNNHead, self).__init__(**kwargs) 28 | 29 | self.FP_module = DGCNNFPModule( 30 | mlp_channels=fp_channels, act_cfg=self.act_cfg) 31 | 32 | # https://github.com/charlesq34/pointnet2/blob/master/models/pointnet2_sem_seg.py#L40 33 | self.pre_seg_conv = ConvModule( 34 | fp_channels[-1], 35 | self.channels, 36 | kernel_size=1, 37 | bias=False, 38 | conv_cfg=self.conv_cfg, 39 | norm_cfg=self.norm_cfg, 40 | act_cfg=self.act_cfg) 41 | 42 | def _extract_input(self, feat_dict: dict) -> Tensor: 43 | """Extract inputs from features dictionary. 44 | 45 | Args: 46 | feat_dict (dict): Feature dict from backbone. 47 | 48 | Returns: 49 | torch.Tensor: Points for decoder. 50 | """ 51 | fa_points = feat_dict['fa_points'] 52 | 53 | return fa_points 54 | 55 | def forward(self, feat_dict: dict) -> Tensor: 56 | """Forward pass. 57 | 58 | Args: 59 | feat_dict (dict): Feature dict from backbone. 60 | 61 | Returns: 62 | Tensor: Segmentation map of shape [B, num_classes, N]. 63 | """ 64 | fa_points = self._extract_input(feat_dict) 65 | 66 | fp_points = self.FP_module(fa_points) 67 | fp_points = fp_points.transpose(1, 2).contiguous() 68 | output = self.pre_seg_conv(fp_points) 69 | output = self.cls_seg(output) 70 | 71 | return output 72 | -------------------------------------------------------------------------------- /mmdet3d/models/dense_heads/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .anchor3d_head import Anchor3DHead 3 | from .anchor_free_mono3d_head import AnchorFreeMono3DHead 4 | from .base_3d_dense_head import Base3DDenseHead 5 | from .base_conv_bbox_head import BaseConvBboxHead 6 | from .base_mono3d_dense_head import BaseMono3DDenseHead 7 | from .centerpoint_head import CenterHead 8 | from .fcaf3d_head import FCAF3DHead 9 | from .fcos_mono3d_head import FCOSMono3DHead 10 | from .free_anchor3d_head import FreeAnchor3DHead 11 | from .groupfree3d_head import GroupFree3DHead 12 | from .imvoxel_head import ImVoxelHead 13 | from .monoflex_head import MonoFlexHead 14 | from .parta2_rpn_head import PartA2RPNHead 15 | from .pgd_head import PGDHead 16 | from .point_rpn_head import PointRPNHead 17 | from .shape_aware_head import ShapeAwareHead 18 | from .smoke_mono3d_head import SMOKEMono3DHead 19 | from .ssd_3d_head import SSD3DHead 20 | from .vote_head import VoteHead 21 | 22 | __all__ = [ 23 | 'Anchor3DHead', 'FreeAnchor3DHead', 'PartA2RPNHead', 'VoteHead', 24 | 'SSD3DHead', 'BaseConvBboxHead', 'CenterHead', 'ShapeAwareHead', 25 | 'BaseMono3DDenseHead', 'AnchorFreeMono3DHead', 'FCOSMono3DHead', 26 | 'GroupFree3DHead', 'PointRPNHead', 'SMOKEMono3DHead', 'PGDHead', 27 | 'MonoFlexHead', 'Base3DDenseHead', 'FCAF3DHead', 'ImVoxelHead' 28 | ] 29 | -------------------------------------------------------------------------------- /mmdet3d/models/detectors/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .base import Base3DDetector 3 | from .centerpoint import CenterPoint 4 | from .dfm import DfM 5 | from .dynamic_voxelnet import DynamicVoxelNet 6 | from .fcos_mono3d import FCOSMono3D 7 | from .groupfree3dnet import GroupFree3DNet 8 | from .h3dnet import H3DNet 9 | from .imvotenet import ImVoteNet 10 | from .imvoxelnet import ImVoxelNet 11 | from .mink_single_stage import MinkSingleStage3DDetector 12 | from .multiview_dfm import MultiViewDfM 13 | from .mvx_faster_rcnn import DynamicMVXFasterRCNN, MVXFasterRCNN 14 | from .mvx_two_stage import MVXTwoStageDetector 15 | from .parta2 import PartA2 16 | from .point_rcnn import PointRCNN 17 | from .pv_rcnn import PointVoxelRCNN 18 | from .sassd import SASSD 19 | from .single_stage_mono3d import SingleStageMono3DDetector 20 | from .smoke_mono3d import SMOKEMono3D 21 | from .ssd3dnet import SSD3DNet 22 | from .votenet import VoteNet 23 | from .voxelnet import VoxelNet 24 | 25 | __all__ = [ 26 | 'Base3DDetector', 'VoxelNet', 'DynamicVoxelNet', 'MVXTwoStageDetector', 27 | 'DynamicMVXFasterRCNN', 'MVXFasterRCNN', 'PartA2', 'VoteNet', 'H3DNet', 28 | 'CenterPoint', 'SSD3DNet', 'ImVoteNet', 'SingleStageMono3DDetector', 29 | 'FCOSMono3D', 'ImVoxelNet', 'GroupFree3DNet', 'PointRCNN', 'SMOKEMono3D', 30 | 'SASSD', 'MinkSingleStage3DDetector', 'MultiViewDfM', 'DfM', 31 | 'PointVoxelRCNN' 32 | ] 33 | -------------------------------------------------------------------------------- /mmdet3d/models/detectors/dynamic_voxelnet.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from typing import Tuple 3 | 4 | from torch import Tensor 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.utils import ConfigType, OptConfigType, OptMultiConfig 8 | from .voxelnet import VoxelNet 9 | 10 | 11 | @MODELS.register_module() 12 | class DynamicVoxelNet(VoxelNet): 13 | r"""VoxelNet using `dynamic voxelization 14 | `_. 15 | """ 16 | 17 | def __init__(self, 18 | voxel_encoder: ConfigType, 19 | middle_encoder: ConfigType, 20 | backbone: ConfigType, 21 | neck: OptConfigType = None, 22 | bbox_head: OptConfigType = None, 23 | train_cfg: OptConfigType = None, 24 | test_cfg: OptConfigType = None, 25 | data_preprocessor: OptConfigType = None, 26 | init_cfg: OptMultiConfig = None) -> None: 27 | super().__init__( 28 | voxel_encoder=voxel_encoder, 29 | middle_encoder=middle_encoder, 30 | backbone=backbone, 31 | neck=neck, 32 | bbox_head=bbox_head, 33 | train_cfg=train_cfg, 34 | test_cfg=test_cfg, 35 | data_preprocessor=data_preprocessor, 36 | init_cfg=init_cfg) 37 | 38 | def extract_feat(self, batch_inputs_dict: dict) -> Tuple[Tensor]: 39 | """Extract features from points.""" 40 | voxel_dict = batch_inputs_dict['voxels'] 41 | voxel_features, feature_coors = self.voxel_encoder( 42 | voxel_dict['voxels'], voxel_dict['coors']) 43 | batch_size = voxel_dict['coors'][-1, 0].item() + 1 44 | x = self.middle_encoder(voxel_features, feature_coors, batch_size) 45 | x = self.backbone(x) 46 | if self.with_neck: 47 | x = self.neck(x) 48 | return x 49 | -------------------------------------------------------------------------------- /mmdet3d/models/detectors/mvx_faster_rcnn.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from typing import Dict, List, Optional, Sequence 3 | 4 | from torch import Tensor 5 | 6 | from mmdet3d.registry import MODELS 7 | from .mvx_two_stage import MVXTwoStageDetector 8 | 9 | 10 | @MODELS.register_module() 11 | class MVXFasterRCNN(MVXTwoStageDetector): 12 | """Multi-modality VoxelNet using Faster R-CNN.""" 13 | 14 | def __init__(self, **kwargs): 15 | super(MVXFasterRCNN, self).__init__(**kwargs) 16 | 17 | 18 | @MODELS.register_module() 19 | class DynamicMVXFasterRCNN(MVXTwoStageDetector): 20 | """Multi-modality VoxelNet using Faster R-CNN and dynamic voxelization.""" 21 | 22 | def __init__(self, **kwargs): 23 | super(DynamicMVXFasterRCNN, self).__init__(**kwargs) 24 | 25 | def extract_pts_feat( 26 | self, 27 | voxel_dict: Dict[str, Tensor], 28 | points: Optional[List[Tensor]] = None, 29 | img_feats: Optional[Sequence[Tensor]] = None, 30 | batch_input_metas: Optional[List[dict]] = None 31 | ) -> Sequence[Tensor]: 32 | """Extract features of points. 33 | 34 | Args: 35 | voxel_dict(Dict[str, Tensor]): Dict of voxelization infos. 36 | points (List[tensor], optional): Point cloud of multiple inputs. 37 | img_feats (list[Tensor], tuple[tensor], optional): Features from 38 | image backbone. 39 | batch_input_metas (list[dict], optional): The meta information 40 | of multiple samples. Defaults to True. 41 | 42 | Returns: 43 | Sequence[tensor]: points features of multiple inputs 44 | from backbone or neck. 45 | """ 46 | if not self.with_pts_bbox: 47 | return None 48 | voxel_features, feature_coors = self.pts_voxel_encoder( 49 | voxel_dict['voxels'], voxel_dict['coors'], points, img_feats, 50 | batch_input_metas) 51 | batch_size = voxel_dict['coors'][-1, 0] + 1 52 | x = self.pts_middle_encoder(voxel_features, feature_coors, batch_size) 53 | x = self.pts_backbone(x) 54 | if self.with_pts_neck: 55 | x = self.pts_neck(x) 56 | return x 57 | -------------------------------------------------------------------------------- /mmdet3d/models/detectors/point_rcnn.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from typing import Dict, Optional 3 | 4 | import torch 5 | 6 | from mmdet3d.registry import MODELS 7 | from .two_stage import TwoStage3DDetector 8 | 9 | 10 | @MODELS.register_module() 11 | class PointRCNN(TwoStage3DDetector): 12 | r"""PointRCNN detector. 13 | 14 | Please refer to the `PointRCNN `_ 15 | 16 | Args: 17 | backbone (dict): Config dict of detector's backbone. 18 | neck (dict, optional): Config dict of neck. Defaults to None. 19 | rpn_head (dict, optional): Config of RPN head. Defaults to None. 20 | roi_head (dict, optional): Config of ROI head. Defaults to None. 21 | train_cfg (dict, optional): Train configs. Defaults to None. 22 | test_cfg (dict, optional): Test configs. Defaults to None. 23 | pretrained (str, optional): Model pretrained path. Defaults to None. 24 | init_cfg (dict, optional): Config of initialization. Defaults to None. 25 | """ 26 | 27 | def __init__(self, 28 | backbone: dict, 29 | neck: Optional[dict] = None, 30 | rpn_head: Optional[dict] = None, 31 | roi_head: Optional[dict] = None, 32 | train_cfg: Optional[dict] = None, 33 | test_cfg: Optional[dict] = None, 34 | init_cfg: Optional[dict] = None, 35 | data_preprocessor: Optional[dict] = None) -> Optional: 36 | super(PointRCNN, self).__init__( 37 | backbone=backbone, 38 | neck=neck, 39 | rpn_head=rpn_head, 40 | roi_head=roi_head, 41 | train_cfg=train_cfg, 42 | test_cfg=test_cfg, 43 | init_cfg=init_cfg, 44 | data_preprocessor=data_preprocessor) 45 | 46 | def extract_feat(self, batch_inputs_dict: Dict) -> Dict: 47 | """Directly extract features from the backbone+neck. 48 | 49 | Args: 50 | batch_inputs_dict (dict): The model input dict which include 51 | 'points', 'imgs' keys. 52 | 53 | - points (list[torch.Tensor]): Point cloud of each sample. 54 | - imgs (torch.Tensor, optional): Image of each sample. 55 | 56 | Returns: 57 | dict: Features from the backbone+neck and raw points. 58 | """ 59 | points = torch.stack(batch_inputs_dict['points']) 60 | x = self.backbone(points) 61 | 62 | if self.with_neck: 63 | x = self.neck(x) 64 | return dict( 65 | fp_features=x['fp_features'].clone(), 66 | fp_points=x['fp_xyz'].clone(), 67 | raw_points=points) 68 | -------------------------------------------------------------------------------- /mmdet3d/models/detectors/smoke_mono3d.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmdet3d.registry import MODELS 3 | from mmdet3d.utils import ConfigType, OptConfigType, OptMultiConfig 4 | from .single_stage_mono3d import SingleStageMono3DDetector 5 | 6 | 7 | @MODELS.register_module() 8 | class SMOKEMono3D(SingleStageMono3DDetector): 9 | r"""SMOKE `_ for monocular 3D object 10 | detection. 11 | 12 | Args: 13 | backbone (:obj:`ConfigDict` or dict): The backbone config. 14 | neck (:obj:`ConfigDict` or dict): The neck config. 15 | bbox_head (:obj:`ConfigDict` or dict): The bbox head config. 16 | train_cfg (:obj:`ConfigDict` or dict, optional): The training config 17 | of FCOS. Defaults to None. 18 | test_cfg (:obj:`ConfigDict` or dict, optional): The testing config 19 | of FCOS. Defaults to None. 20 | data_preprocessor (:obj:`ConfigDict` or dict, optional): Config of 21 | :class:`DetDataPreprocessor` to process the input data. 22 | Defaults to None. 23 | init_cfg (:obj:`ConfigDict` or list[:obj:`ConfigDict`] or dict or 24 | list[dict], optional): Initialization config dict. 25 | Defaults to None. 26 | """ 27 | 28 | def __init__(self, 29 | backbone: ConfigType, 30 | neck: ConfigType, 31 | bbox_head: ConfigType, 32 | train_cfg: OptConfigType = None, 33 | test_cfg: OptConfigType = None, 34 | data_preprocessor: OptConfigType = None, 35 | init_cfg: OptMultiConfig = None) -> None: 36 | super().__init__( 37 | backbone=backbone, 38 | neck=neck, 39 | bbox_head=bbox_head, 40 | train_cfg=train_cfg, 41 | test_cfg=test_cfg, 42 | data_preprocessor=data_preprocessor, 43 | init_cfg=init_cfg) 44 | -------------------------------------------------------------------------------- /mmdet3d/models/detectors/ssd3dnet.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmdet3d.registry import MODELS 3 | from .votenet import VoteNet 4 | 5 | 6 | @MODELS.register_module() 7 | class SSD3DNet(VoteNet): 8 | """3DSSDNet model. 9 | 10 | https://arxiv.org/abs/2002.10187.pdf 11 | """ 12 | 13 | def __init__(self, 14 | backbone, 15 | bbox_head=None, 16 | train_cfg=None, 17 | test_cfg=None, 18 | init_cfg=None, 19 | **kwargs): 20 | super(SSD3DNet, self).__init__( 21 | backbone=backbone, 22 | bbox_head=bbox_head, 23 | train_cfg=train_cfg, 24 | test_cfg=test_cfg, 25 | init_cfg=init_cfg, 26 | **kwargs) 27 | -------------------------------------------------------------------------------- /mmdet3d/models/detectors/voxelnet.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from typing import Tuple 3 | 4 | from torch import Tensor 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.utils import ConfigType, OptConfigType, OptMultiConfig 8 | from .single_stage import SingleStage3DDetector 9 | 10 | 11 | @MODELS.register_module() 12 | class VoxelNet(SingleStage3DDetector): 13 | r"""`VoxelNet `_ for 3D detection.""" 14 | 15 | def __init__(self, 16 | voxel_encoder: ConfigType, 17 | middle_encoder: ConfigType, 18 | backbone: ConfigType, 19 | neck: OptConfigType = None, 20 | bbox_head: OptConfigType = None, 21 | train_cfg: OptConfigType = None, 22 | test_cfg: OptConfigType = None, 23 | data_preprocessor: OptConfigType = None, 24 | init_cfg: OptMultiConfig = None) -> None: 25 | super().__init__( 26 | backbone=backbone, 27 | neck=neck, 28 | bbox_head=bbox_head, 29 | train_cfg=train_cfg, 30 | test_cfg=test_cfg, 31 | data_preprocessor=data_preprocessor, 32 | init_cfg=init_cfg) 33 | self.voxel_encoder = MODELS.build(voxel_encoder) 34 | self.middle_encoder = MODELS.build(middle_encoder) 35 | 36 | def extract_feat(self, batch_inputs_dict: dict) -> Tuple[Tensor]: 37 | """Extract features from points.""" 38 | voxel_dict = batch_inputs_dict['voxels'] 39 | voxel_features = self.voxel_encoder(voxel_dict['voxels'], 40 | voxel_dict['num_points'], 41 | voxel_dict['coors']) 42 | batch_size = voxel_dict['coors'][-1, 0].item() + 1 43 | x = self.middle_encoder(voxel_features, voxel_dict['coors'], 44 | batch_size) 45 | x = self.backbone(x) 46 | if self.with_neck: 47 | x = self.neck(x) 48 | return x 49 | -------------------------------------------------------------------------------- /mmdet3d/models/layers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .box3d_nms import (aligned_3d_nms, box3d_multiclass_nms, circle_nms, 3 | nms_bev, nms_normal_bev) 4 | from .dgcnn_modules import DGCNNFAModule, DGCNNFPModule, DGCNNGFModule 5 | from .edge_fusion_module import EdgeFusionModule 6 | from .fusion_layers import (PointFusion, VoteFusion, apply_3d_transformation, 7 | bbox_2d_transform, coord_2d_transform) 8 | from .mlp import MLP 9 | from .norm import NaiveSyncBatchNorm1d, NaiveSyncBatchNorm2d 10 | from .paconv import PAConv, PAConvCUDA 11 | from .pointnet_modules import (PAConvCUDASAModule, PAConvCUDASAModuleMSG, 12 | PAConvSAModule, PAConvSAModuleMSG, 13 | PointFPModule, PointSAModule, PointSAModuleMSG, 14 | build_sa_module) 15 | from .sparse_block import (SparseBasicBlock, SparseBottleneck, 16 | make_sparse_convmodule) 17 | from .torchsparse_block import TorchSparseConvModule, TorchSparseResidualBlock 18 | from .transformer import GroupFree3DMHA 19 | from .vote_module import VoteModule 20 | 21 | __all__ = [ 22 | 'VoteModule', 'GroupFree3DMHA', 'EdgeFusionModule', 'DGCNNFAModule', 23 | 'DGCNNFPModule', 'DGCNNGFModule', 'NaiveSyncBatchNorm1d', 24 | 'NaiveSyncBatchNorm2d', 'PAConv', 'PAConvCUDA', 'SparseBasicBlock', 25 | 'SparseBottleneck', 'make_sparse_convmodule', 'PointFusion', 'VoteFusion', 26 | 'apply_3d_transformation', 'bbox_2d_transform', 'coord_2d_transform', 27 | 'MLP', 'box3d_multiclass_nms', 'aligned_3d_nms', 'circle_nms', 'nms_bev', 28 | 'nms_normal_bev', 'build_sa_module', 'PointSAModuleMSG', 'PointSAModule', 29 | 'PointFPModule', 'PAConvSAModule', 'PAConvSAModuleMSG', 30 | 'PAConvCUDASAModule', 'PAConvCUDASAModuleMSG', 'TorchSparseConvModule', 31 | 'TorchSparseResidualBlock' 32 | ] 33 | -------------------------------------------------------------------------------- /mmdet3d/models/layers/dgcnn_modules/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .dgcnn_fa_module import DGCNNFAModule 3 | from .dgcnn_fp_module import DGCNNFPModule 4 | from .dgcnn_gf_module import DGCNNGFModule 5 | 6 | __all__ = ['DGCNNFAModule', 'DGCNNFPModule', 'DGCNNGFModule'] 7 | -------------------------------------------------------------------------------- /mmdet3d/models/layers/dgcnn_modules/dgcnn_fp_module.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from typing import List 3 | 4 | from mmcv.cnn import ConvModule 5 | from mmengine.model import BaseModule 6 | from torch import Tensor 7 | from torch import nn as nn 8 | 9 | from mmdet3d.utils import ConfigType, OptMultiConfig 10 | 11 | 12 | class DGCNNFPModule(BaseModule): 13 | """Point feature propagation module used in DGCNN. 14 | 15 | Propagate the features from one set to another. 16 | 17 | Args: 18 | mlp_channels (List[int]): List of mlp channels. 19 | norm_cfg (:obj:`ConfigDict` or dict): Config dict for normalization 20 | layer. Defaults to dict(type='BN1d'). 21 | act_cfg (:obj:`ConfigDict` or dict): Config dict for activation layer. 22 | Defaults to dict(type='ReLU'). 23 | init_cfg (:obj:`ConfigDict` or dict or List[:obj:`Contigdict` or dict], 24 | optional): Initialization config dict. Defaults to None. 25 | """ 26 | 27 | def __init__(self, 28 | mlp_channels: List[int], 29 | norm_cfg: ConfigType = dict(type='BN1d'), 30 | act_cfg: ConfigType = dict(type='ReLU'), 31 | init_cfg: OptMultiConfig = None) -> None: 32 | super(DGCNNFPModule, self).__init__(init_cfg=init_cfg) 33 | self.mlps = nn.Sequential() 34 | for i in range(len(mlp_channels) - 1): 35 | self.mlps.add_module( 36 | f'layer{i}', 37 | ConvModule( 38 | mlp_channels[i], 39 | mlp_channels[i + 1], 40 | kernel_size=(1, ), 41 | stride=(1, ), 42 | conv_cfg=dict(type='Conv1d'), 43 | norm_cfg=norm_cfg, 44 | act_cfg=act_cfg)) 45 | 46 | def forward(self, points: Tensor) -> Tensor: 47 | """Forward. 48 | 49 | Args: 50 | points (Tensor): (B, N, C) Tensor of the input points. 51 | 52 | Returns: 53 | Tensor: (B, N, M) M = mlp[-1]. Tensor of the new points. 54 | """ 55 | 56 | if points is not None: 57 | new_points = points.transpose(1, 2).contiguous() # (B, C, N) 58 | new_points = self.mlps(new_points) 59 | new_points = new_points.transpose(1, 2).contiguous() 60 | else: 61 | new_points = points 62 | 63 | return new_points 64 | -------------------------------------------------------------------------------- /mmdet3d/models/layers/fusion_layers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .coord_transform import (apply_3d_transformation, bbox_2d_transform, 3 | coord_2d_transform) 4 | from .point_fusion import PointFusion 5 | from .vote_fusion import VoteFusion 6 | 7 | __all__ = [ 8 | 'PointFusion', 'VoteFusion', 'apply_3d_transformation', 9 | 'bbox_2d_transform', 'coord_2d_transform' 10 | ] 11 | -------------------------------------------------------------------------------- /mmdet3d/models/layers/mlp.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from typing import Tuple 3 | 4 | from mmcv.cnn import ConvModule 5 | from mmengine.model import BaseModule 6 | from torch import Tensor 7 | from torch import nn as nn 8 | 9 | from mmdet3d.utils import ConfigType, OptMultiConfig 10 | 11 | 12 | class MLP(BaseModule): 13 | """A simple MLP module. 14 | 15 | Pass features (B, C, N) through an MLP. 16 | 17 | Args: 18 | in_channels (int): Number of channels of input features. 19 | Defaults to 18. 20 | conv_channels (Tuple[int]): Out channels of the convolution. 21 | Defaults to (256, 256). 22 | conv_cfg (:obj:`ConfigDict` or dict): Config dict for convolution 23 | layer. Defaults to dict(type='Conv1d'). 24 | norm_cfg (:obj:`ConfigDict` or dict): Config dict for normalization 25 | layer. Defaults to dict(type='BN1d'). 26 | act_cfg (:obj:`ConfigDict` or dict): Config dict for activation layer. 27 | Defaults to dict(type='ReLU'). 28 | init_cfg (:obj:`ConfigDict` or dict or List[:obj:`Contigdict` or dict], 29 | optional): Initialization config dict. Defaults to None. 30 | """ 31 | 32 | def __init__(self, 33 | in_channel: int = 18, 34 | conv_channels: Tuple[int] = (256, 256), 35 | conv_cfg: ConfigType = dict(type='Conv1d'), 36 | norm_cfg: ConfigType = dict(type='BN1d'), 37 | act_cfg: ConfigType = dict(type='ReLU'), 38 | init_cfg: OptMultiConfig = None) -> None: 39 | super(MLP, self).__init__(init_cfg=init_cfg) 40 | self.mlp = nn.Sequential() 41 | prev_channels = in_channel 42 | for i, conv_channel in enumerate(conv_channels): 43 | self.mlp.add_module( 44 | f'layer{i}', 45 | ConvModule( 46 | prev_channels, 47 | conv_channels[i], 48 | 1, 49 | padding=0, 50 | conv_cfg=conv_cfg, 51 | norm_cfg=norm_cfg, 52 | act_cfg=act_cfg, 53 | bias=True, 54 | inplace=True)) 55 | prev_channels = conv_channels[i] 56 | 57 | def forward(self, img_features: Tensor) -> Tensor: 58 | return self.mlp(img_features) 59 | -------------------------------------------------------------------------------- /mmdet3d/models/layers/paconv/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .paconv import PAConv, PAConvCUDA 3 | 4 | __all__ = ['PAConv', 'PAConvCUDA'] 5 | -------------------------------------------------------------------------------- /mmdet3d/models/layers/pointnet_modules/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .builder import build_sa_module 3 | from .paconv_sa_module import (PAConvCUDASAModule, PAConvCUDASAModuleMSG, 4 | PAConvSAModule, PAConvSAModuleMSG) 5 | from .point_fp_module import PointFPModule 6 | from .point_sa_module import PointSAModule, PointSAModuleMSG 7 | from .stack_point_sa_module import StackedSAModuleMSG 8 | 9 | __all__ = [ 10 | 'build_sa_module', 'PointSAModuleMSG', 'PointSAModule', 'PointFPModule', 11 | 'PAConvSAModule', 'PAConvSAModuleMSG', 'PAConvCUDASAModule', 12 | 'PAConvCUDASAModuleMSG', 'StackedSAModuleMSG' 13 | ] 14 | -------------------------------------------------------------------------------- /mmdet3d/models/layers/pointnet_modules/builder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from typing import Union 3 | 4 | from mmengine.registry import Registry 5 | from torch import nn as nn 6 | 7 | SA_MODULES = Registry( 8 | name='point_sa_module', 9 | locations=['mmdet3d.models.layers.pointnet_modules']) 10 | 11 | 12 | def build_sa_module(cfg: Union[dict, None], *args, **kwargs) -> nn.Module: 13 | """Build PointNet2 set abstraction (SA) module. 14 | 15 | Args: 16 | cfg (dict or None): The SA module config, which should contain: 17 | 18 | - type (str): Module type. 19 | - module args: Args needed to instantiate an SA module. 20 | args (argument list): Arguments passed to the `__init__` 21 | method of the corresponding module. 22 | kwargs (keyword arguments): Keyword arguments passed to the `__init__` 23 | method of the corresponding SA module . 24 | 25 | Returns: 26 | nn.Module: Created SA module. 27 | """ 28 | if cfg is None: 29 | cfg_ = dict(type='PointSAModule') 30 | else: 31 | if not isinstance(cfg, dict): 32 | raise TypeError('cfg must be a dict') 33 | if 'type' not in cfg: 34 | raise KeyError('the cfg dict must contain the key "type"') 35 | cfg_ = cfg.copy() 36 | 37 | module_type = cfg_.pop('type') 38 | if module_type not in SA_MODULES: 39 | raise KeyError(f'Unrecognized module type {module_type}') 40 | else: 41 | sa_module = SA_MODULES.get(module_type) 42 | 43 | module = sa_module(*args, **kwargs, **cfg_) 44 | 45 | return module 46 | -------------------------------------------------------------------------------- /mmdet3d/models/layers/spconv/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .overwrite_spconv import register_spconv2 3 | 4 | try: 5 | import spconv 6 | except ImportError: 7 | IS_SPCONV2_AVAILABLE = False 8 | else: 9 | if hasattr(spconv, '__version__') and spconv.__version__ >= '2.0.0': 10 | IS_SPCONV2_AVAILABLE = register_spconv2() 11 | else: 12 | IS_SPCONV2_AVAILABLE = False 13 | 14 | __all__ = ['IS_SPCONV2_AVAILABLE'] 15 | -------------------------------------------------------------------------------- /mmdet3d/models/layers/spconv/overwrite_spconv/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .write_spconv2 import register_spconv2 3 | 4 | __all__ = ['register_spconv2'] 5 | -------------------------------------------------------------------------------- /mmdet3d/models/layers/torchsparse/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .torchsparse_wrapper import register_torchsparse 3 | 4 | try: 5 | import torchsparse # noqa 6 | except ImportError: 7 | IS_TORCHSPARSE_AVAILABLE = False 8 | else: 9 | IS_TORCHSPARSE_AVAILABLE = register_torchsparse() 10 | 11 | __all__ = ['IS_TORCHSPARSE_AVAILABLE'] 12 | -------------------------------------------------------------------------------- /mmdet3d/models/layers/torchsparse/torchsparse_wrapper.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmengine.registry import MODELS 3 | 4 | 5 | def register_torchsparse() -> bool: 6 | """This func registers torchsparse modules.""" 7 | try: 8 | from torchsparse.nn import (BatchNorm, Conv3d, GroupNorm, LeakyReLU, 9 | ReLU) 10 | except ImportError: 11 | return False 12 | else: 13 | MODELS._register_module(Conv3d, 'TorchSparseConv3d') 14 | MODELS._register_module(BatchNorm, 'TorchSparseBatchNorm') 15 | MODELS._register_module(GroupNorm, 'TorchSparseGroupNorm') 16 | MODELS._register_module(ReLU, 'TorchSparseReLU') 17 | MODELS._register_module(LeakyReLU, 'TorchSparseLeakyReLU') 18 | return True 19 | -------------------------------------------------------------------------------- /mmdet3d/models/losses/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmdet.models.losses import FocalLoss, SmoothL1Loss, binary_cross_entropy 3 | 4 | from .axis_aligned_iou_loss import AxisAlignedIoULoss, axis_aligned_iou_loss 5 | from .chamfer_distance import ChamferDistance, chamfer_distance 6 | from .lovasz_loss import LovaszLoss 7 | from .multibin_loss import MultiBinLoss 8 | from .paconv_regularization_loss import PAConvRegularizationLoss 9 | from .rotated_iou_loss import RotatedIoU3DLoss, rotated_iou_3d_loss 10 | from .uncertain_smooth_l1_loss import UncertainL1Loss, UncertainSmoothL1Loss 11 | 12 | __all__ = [ 13 | 'FocalLoss', 'SmoothL1Loss', 'binary_cross_entropy', 'ChamferDistance', 14 | 'chamfer_distance', 'axis_aligned_iou_loss', 'AxisAlignedIoULoss', 15 | 'PAConvRegularizationLoss', 'UncertainL1Loss', 'UncertainSmoothL1Loss', 16 | 'MultiBinLoss', 'RotatedIoU3DLoss', 'rotated_iou_3d_loss', 'LovaszLoss' 17 | ] 18 | -------------------------------------------------------------------------------- /mmdet3d/models/middle_encoders/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .pillar_scatter import PointPillarsScatter 3 | from .sparse_encoder import SparseEncoder, SparseEncoderSASSD 4 | from .sparse_unet import SparseUNet 5 | from .voxel_set_abstraction import VoxelSetAbstraction 6 | 7 | __all__ = [ 8 | 'PointPillarsScatter', 'SparseEncoder', 'SparseEncoderSASSD', 'SparseUNet', 9 | 'VoxelSetAbstraction' 10 | ] 11 | -------------------------------------------------------------------------------- /mmdet3d/models/necks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmdet.models.necks.fpn import FPN 3 | 4 | from .dla_neck import DLANeck 5 | from .imvoxel_neck import IndoorImVoxelNeck, OutdoorImVoxelNeck 6 | from .pointnet2_fp_neck import PointNetFPNeck 7 | from .second_fpn import SECONDFPN 8 | 9 | __all__ = [ 10 | 'FPN', 'SECONDFPN', 'OutdoorImVoxelNeck', 'PointNetFPNeck', 'DLANeck', 11 | 'IndoorImVoxelNeck' 12 | ] 13 | -------------------------------------------------------------------------------- /mmdet3d/models/roi_heads/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .base_3droi_head import Base3DRoIHead 3 | from .bbox_heads import PartA2BboxHead 4 | from .h3d_roi_head import H3DRoIHead 5 | from .mask_heads import PointwiseSemanticHead, PrimitiveHead 6 | from .part_aggregation_roi_head import PartAggregationROIHead 7 | from .point_rcnn_roi_head import PointRCNNRoIHead 8 | from .pv_rcnn_roi_head import PVRCNNRoiHead 9 | from .roi_extractors import Single3DRoIAwareExtractor, SingleRoIExtractor 10 | 11 | __all__ = [ 12 | 'Base3DRoIHead', 'PartAggregationROIHead', 'PointwiseSemanticHead', 13 | 'Single3DRoIAwareExtractor', 'PartA2BboxHead', 'SingleRoIExtractor', 14 | 'H3DRoIHead', 'PrimitiveHead', 'PointRCNNRoIHead', 'PVRCNNRoiHead' 15 | ] 16 | -------------------------------------------------------------------------------- /mmdet3d/models/roi_heads/base_3droi_head.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmdet.models.roi_heads import BaseRoIHead 3 | 4 | from mmdet3d.registry import MODELS, TASK_UTILS 5 | 6 | 7 | class Base3DRoIHead(BaseRoIHead): 8 | """Base class for 3d RoIHeads.""" 9 | 10 | def __init__(self, 11 | bbox_head=None, 12 | bbox_roi_extractor=None, 13 | mask_head=None, 14 | mask_roi_extractor=None, 15 | train_cfg=None, 16 | test_cfg=None, 17 | init_cfg=None): 18 | super(Base3DRoIHead, self).__init__( 19 | bbox_head=bbox_head, 20 | bbox_roi_extractor=bbox_roi_extractor, 21 | mask_head=mask_head, 22 | mask_roi_extractor=mask_roi_extractor, 23 | train_cfg=train_cfg, 24 | test_cfg=test_cfg, 25 | init_cfg=init_cfg) 26 | 27 | def init_bbox_head(self, bbox_roi_extractor: dict, 28 | bbox_head: dict) -> None: 29 | """Initialize box head and box roi extractor. 30 | 31 | Args: 32 | bbox_roi_extractor (dict or ConfigDict): Config of box 33 | roi extractor. 34 | bbox_head (dict or ConfigDict): Config of box in box head. 35 | """ 36 | self.bbox_roi_extractor = MODELS.build(bbox_roi_extractor) 37 | self.bbox_head = MODELS.build(bbox_head) 38 | 39 | def init_assigner_sampler(self): 40 | """Initialize assigner and sampler.""" 41 | self.bbox_assigner = None 42 | self.bbox_sampler = None 43 | if self.train_cfg: 44 | if isinstance(self.train_cfg.assigner, dict): 45 | self.bbox_assigner = TASK_UTILS.build(self.train_cfg.assigner) 46 | elif isinstance(self.train_cfg.assigner, list): 47 | self.bbox_assigner = [ 48 | TASK_UTILS.build(res) for res in self.train_cfg.assigner 49 | ] 50 | self.bbox_sampler = TASK_UTILS.build(self.train_cfg.sampler) 51 | 52 | def init_mask_head(self): 53 | """Initialize mask head, skip since ``PartAggregationROIHead`` does not 54 | have one.""" 55 | pass 56 | -------------------------------------------------------------------------------- /mmdet3d/models/roi_heads/bbox_heads/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmdet.models.roi_heads.bbox_heads import (BBoxHead, ConvFCBBoxHead, 3 | DoubleConvFCBBoxHead, 4 | Shared2FCBBoxHead, 5 | Shared4Conv1FCBBoxHead) 6 | 7 | from .h3d_bbox_head import H3DBboxHead 8 | from .parta2_bbox_head import PartA2BboxHead 9 | from .point_rcnn_bbox_head import PointRCNNBboxHead 10 | from .pv_rcnn_bbox_head import PVRCNNBBoxHead 11 | 12 | __all__ = [ 13 | 'BBoxHead', 'ConvFCBBoxHead', 'Shared2FCBBoxHead', 14 | 'Shared4Conv1FCBBoxHead', 'DoubleConvFCBBoxHead', 'PartA2BboxHead', 15 | 'H3DBboxHead', 'PointRCNNBboxHead', 'PVRCNNBBoxHead' 16 | ] 17 | -------------------------------------------------------------------------------- /mmdet3d/models/roi_heads/mask_heads/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .foreground_segmentation_head import ForegroundSegmentationHead 3 | from .pointwise_semantic_head import PointwiseSemanticHead 4 | from .primitive_head import PrimitiveHead 5 | 6 | __all__ = [ 7 | 'PointwiseSemanticHead', 'PrimitiveHead', 'ForegroundSegmentationHead' 8 | ] 9 | -------------------------------------------------------------------------------- /mmdet3d/models/roi_heads/roi_extractors/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmdet.models.roi_heads.roi_extractors import SingleRoIExtractor 3 | 4 | from .batch_roigridpoint_extractor import Batch3DRoIGridExtractor 5 | from .single_roiaware_extractor import Single3DRoIAwareExtractor 6 | from .single_roipoint_extractor import Single3DRoIPointExtractor 7 | 8 | __all__ = [ 9 | 'SingleRoIExtractor', 'Single3DRoIAwareExtractor', 10 | 'Single3DRoIPointExtractor', 'Batch3DRoIGridExtractor' 11 | ] 12 | -------------------------------------------------------------------------------- /mmdet3d/models/roi_heads/roi_extractors/single_roiaware_extractor.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from typing import Optional 3 | 4 | import torch 5 | import torch.nn as nn 6 | from mmcv import ops 7 | from mmengine.model import BaseModule 8 | from torch import Tensor 9 | 10 | from mmdet3d.registry import MODELS 11 | 12 | 13 | @MODELS.register_module() 14 | class Single3DRoIAwareExtractor(BaseModule): 15 | """Point-wise roi-aware Extractor. 16 | 17 | Extract Point-wise roi features. 18 | 19 | Args: 20 | roi_layer (dict, optional): The config of roi layer. 21 | """ 22 | 23 | def __init__(self, 24 | roi_layer: Optional[dict] = None, 25 | init_cfg: Optional[dict] = None) -> None: 26 | super(Single3DRoIAwareExtractor, self).__init__(init_cfg=init_cfg) 27 | self.roi_layer = self.build_roi_layers(roi_layer) 28 | 29 | def build_roi_layers(self, layer_cfg: dict) -> nn.Module: 30 | """Build roi layers using `layer_cfg`""" 31 | cfg = layer_cfg.copy() 32 | layer_type = cfg.pop('type') 33 | assert hasattr(ops, layer_type) 34 | layer_cls = getattr(ops, layer_type) 35 | roi_layers = layer_cls(**cfg) 36 | return roi_layers 37 | 38 | def forward(self, feats: Tensor, coordinate: Tensor, batch_inds: Tensor, 39 | rois: Tensor) -> Tensor: 40 | """Extract point-wise roi features. 41 | 42 | Args: 43 | feats (torch.FloatTensor): Point-wise features with 44 | shape (batch, npoints, channels) for pooling. 45 | coordinate (torch.FloatTensor): Coordinate of each point. 46 | batch_inds (torch.LongTensor): Indicate the batch of each point. 47 | rois (torch.FloatTensor): Roi boxes with batch indices. 48 | 49 | Returns: 50 | torch.FloatTensor: Pooled features 51 | """ 52 | pooled_roi_feats = [] 53 | for batch_idx in range(int(batch_inds.max()) + 1): 54 | roi_inds = (rois[..., 0].int() == batch_idx) 55 | coors_inds = (batch_inds.int() == batch_idx) 56 | pooled_roi_feat = self.roi_layer(rois[..., 1:][roi_inds], 57 | coordinate[coors_inds], 58 | feats[coors_inds]) 59 | pooled_roi_feats.append(pooled_roi_feat) 60 | pooled_roi_feats = torch.cat(pooled_roi_feats, 0) 61 | return pooled_roi_feats 62 | -------------------------------------------------------------------------------- /mmdet3d/models/roi_heads/roi_extractors/single_roipoint_extractor.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from typing import Optional 3 | 4 | import torch 5 | import torch.nn as nn 6 | from mmcv import ops 7 | from torch import Tensor 8 | 9 | from mmdet3d.registry import MODELS 10 | from mmdet3d.structures.bbox_3d import rotation_3d_in_axis 11 | 12 | 13 | @MODELS.register_module() 14 | class Single3DRoIPointExtractor(nn.Module): 15 | """Point-wise roi-aware Extractor. 16 | 17 | Extract Point-wise roi features. 18 | 19 | Args: 20 | roi_layer (dict, optional): The config of roi layer. 21 | """ 22 | 23 | def __init__(self, roi_layer: Optional[dict] = None) -> None: 24 | super(Single3DRoIPointExtractor, self).__init__() 25 | self.roi_layer = self.build_roi_layers(roi_layer) 26 | 27 | def build_roi_layers(self, layer_cfg: dict) -> nn.Module: 28 | """Build roi layers using `layer_cfg`""" 29 | cfg = layer_cfg.copy() 30 | layer_type = cfg.pop('type') 31 | assert hasattr(ops, layer_type) 32 | layer_cls = getattr(ops, layer_type) 33 | roi_layers = layer_cls(**cfg) 34 | return roi_layers 35 | 36 | def forward(self, feats: Tensor, coordinate: Tensor, batch_inds: Tensor, 37 | rois: Tensor) -> Tensor: 38 | """Extract point-wise roi features. 39 | 40 | Args: 41 | feats (torch.FloatTensor): Point-wise features with 42 | shape (batch, npoints, channels) for pooling. 43 | coordinate (torch.FloatTensor): Coordinate of each point. 44 | batch_inds (torch.LongTensor): Indicate the batch of each point. 45 | rois (torch.FloatTensor): Roi boxes with batch indices. 46 | 47 | Returns: 48 | torch.FloatTensor: Pooled features 49 | """ 50 | rois = rois[..., 1:] 51 | rois = rois.view(batch_inds, -1, rois.shape[-1]) 52 | with torch.no_grad(): 53 | pooled_roi_feat, pooled_empty_flag = self.roi_layer( 54 | coordinate, feats, rois) 55 | 56 | # canonical transformation 57 | roi_center = rois[:, :, 0:3] 58 | pooled_roi_feat[:, :, :, 0:3] -= roi_center.unsqueeze(dim=2) 59 | pooled_roi_feat = pooled_roi_feat.view(-1, 60 | pooled_roi_feat.shape[-2], 61 | pooled_roi_feat.shape[-1]) 62 | pooled_roi_feat[:, :, 0:3] = rotation_3d_in_axis( 63 | pooled_roi_feat[:, :, 0:3], 64 | -(rois.view(-1, rois.shape[-1])[:, 6]), 65 | axis=2) 66 | pooled_roi_feat[pooled_empty_flag.view(-1) > 0] = 0 67 | 68 | return pooled_roi_feat 69 | -------------------------------------------------------------------------------- /mmdet3d/models/segmentors/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .base import Base3DSegmentor 3 | from .cylinder3d import Cylinder3D 4 | from .encoder_decoder import EncoderDecoder3D 5 | from .minkunet import MinkUNet 6 | 7 | __all__ = ['Base3DSegmentor', 'EncoderDecoder3D', 'Cylinder3D', 'MinkUNet'] 8 | -------------------------------------------------------------------------------- /mmdet3d/models/task_modules/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmdet.models.task_modules import AssignResult, BaseAssigner 3 | 4 | from .anchor import (ANCHOR_GENERATORS, PRIOR_GENERATORS, 5 | AlignedAnchor3DRangeGenerator, 6 | AlignedAnchor3DRangeGeneratorPerCls, 7 | Anchor3DRangeGenerator, build_anchor_generator, 8 | build_prior_generator) 9 | from .assigners import Max3DIoUAssigner 10 | from .coders import (AnchorFreeBBoxCoder, CenterPointBBoxCoder, 11 | DeltaXYZWLHRBBoxCoder, FCOS3DBBoxCoder, 12 | GroupFree3DBBoxCoder, MonoFlexCoder, 13 | PartialBinBasedBBoxCoder, PGDBBoxCoder, 14 | PointXYZWHLRBBoxCoder, SMOKECoder) 15 | from .samplers import (BaseSampler, CombinedSampler, 16 | InstanceBalancedPosSampler, IoUBalancedNegSampler, 17 | IoUNegPiecewiseSampler, OHEMSampler, PseudoSampler, 18 | RandomSampler, SamplingResult) 19 | from .voxel import VoxelGenerator 20 | 21 | __all__ = [ 22 | 'BaseAssigner', 'Max3DIoUAssigner', 'AssignResult', 'BaseSampler', 23 | 'PseudoSampler', 'RandomSampler', 'InstanceBalancedPosSampler', 24 | 'IoUBalancedNegSampler', 'CombinedSampler', 'OHEMSampler', 25 | 'SamplingResult', 'IoUNegPiecewiseSampler', 'DeltaXYZWLHRBBoxCoder', 26 | 'PartialBinBasedBBoxCoder', 'CenterPointBBoxCoder', 'AnchorFreeBBoxCoder', 27 | 'GroupFree3DBBoxCoder', 'PointXYZWHLRBBoxCoder', 'FCOS3DBBoxCoder', 28 | 'PGDBBoxCoder', 'SMOKECoder', 'MonoFlexCoder', 'VoxelGenerator', 29 | 'AlignedAnchor3DRangeGenerator', 'Anchor3DRangeGenerator', 30 | 'build_prior_generator', 'AlignedAnchor3DRangeGeneratorPerCls', 31 | 'build_anchor_generator', 'ANCHOR_GENERATORS', 'PRIOR_GENERATORS' 32 | ] 33 | -------------------------------------------------------------------------------- /mmdet3d/models/task_modules/anchor/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .anchor_3d_generator import (AlignedAnchor3DRangeGenerator, 3 | AlignedAnchor3DRangeGeneratorPerCls, 4 | Anchor3DRangeGenerator) 5 | from .builder import (ANCHOR_GENERATORS, PRIOR_GENERATORS, 6 | build_anchor_generator, build_prior_generator) 7 | 8 | __all__ = [ 9 | 'AlignedAnchor3DRangeGenerator', 'Anchor3DRangeGenerator', 10 | 'build_prior_generator', 'AlignedAnchor3DRangeGeneratorPerCls', 11 | 'build_anchor_generator', 'ANCHOR_GENERATORS', 'PRIOR_GENERATORS' 12 | ] 13 | -------------------------------------------------------------------------------- /mmdet3d/models/task_modules/anchor/builder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import warnings 3 | 4 | from mmdet3d.registry import TASK_UTILS 5 | 6 | PRIOR_GENERATORS = TASK_UTILS 7 | 8 | ANCHOR_GENERATORS = TASK_UTILS 9 | 10 | 11 | def build_prior_generator(cfg, default_args=None): 12 | warnings.warn( 13 | '``build_prior_generator`` would be deprecated soon, please use ' 14 | '``mmdet3d.registry.TASK_UTILS.build()`` ') 15 | return TASK_UTILS.build(cfg, default_args=default_args) 16 | 17 | 18 | def build_anchor_generator(cfg, default_args=None): 19 | warnings.warn( 20 | '``build_anchor_generator`` would be deprecated soon, please use ' 21 | '``mmdet3d.registry.TASK_UTILS.build()`` ') 22 | return TASK_UTILS.build(cfg, default_args=default_args) 23 | -------------------------------------------------------------------------------- /mmdet3d/models/task_modules/assigners/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .max_3d_iou_assigner import Max3DIoUAssigner 3 | 4 | __all__ = ['Max3DIoUAssigner'] 5 | -------------------------------------------------------------------------------- /mmdet3d/models/task_modules/builder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import warnings 3 | 4 | from mmdet3d.registry import TASK_UTILS 5 | 6 | BBOX_ASSIGNERS = TASK_UTILS 7 | BBOX_SAMPLERS = TASK_UTILS 8 | BBOX_CODERS = TASK_UTILS 9 | 10 | 11 | def build_assigner(cfg, **default_args): 12 | """Builder of box assigner.""" 13 | warnings.warn('``build_assigner`` would be deprecated soon, please use ' 14 | '``mmdet3d.registry.TASK_UTILS.build()`` ') 15 | return TASK_UTILS.build(cfg, default_args=default_args) 16 | 17 | 18 | def build_sampler(cfg, **default_args): 19 | """Builder of box sampler.""" 20 | warnings.warn('``build_sampler`` would be deprecated soon, please use ' 21 | '``mmdet3d.registry.TASK_UTILS.build()`` ') 22 | return TASK_UTILS.build(cfg, default_args=default_args) 23 | 24 | 25 | def build_bbox_coder(cfg, **default_args): 26 | """Builder of box coder.""" 27 | warnings.warn('``build_bbox_coder`` would be deprecated soon, please use ' 28 | '``mmdet3d.registry.TASK_UTILS.build()`` ') 29 | return TASK_UTILS.build(cfg, default_args=default_args) 30 | -------------------------------------------------------------------------------- /mmdet3d/models/task_modules/coders/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .anchor_free_bbox_coder import AnchorFreeBBoxCoder 3 | from .centerpoint_bbox_coders import CenterPointBBoxCoder 4 | from .delta_xyzwhlr_bbox_coder import DeltaXYZWLHRBBoxCoder 5 | from .fcos3d_bbox_coder import FCOS3DBBoxCoder 6 | from .groupfree3d_bbox_coder import GroupFree3DBBoxCoder 7 | from .monoflex_bbox_coder import MonoFlexCoder 8 | from .partial_bin_based_bbox_coder import PartialBinBasedBBoxCoder 9 | from .pgd_bbox_coder import PGDBBoxCoder 10 | from .point_xyzwhlr_bbox_coder import PointXYZWHLRBBoxCoder 11 | from .smoke_bbox_coder import SMOKECoder 12 | 13 | __all__ = [ 14 | 'DeltaXYZWLHRBBoxCoder', 'PartialBinBasedBBoxCoder', 15 | 'CenterPointBBoxCoder', 'AnchorFreeBBoxCoder', 'GroupFree3DBBoxCoder', 16 | 'PointXYZWHLRBBoxCoder', 'FCOS3DBBoxCoder', 'PGDBBoxCoder', 'SMOKECoder', 17 | 'MonoFlexCoder' 18 | ] 19 | -------------------------------------------------------------------------------- /mmdet3d/models/task_modules/samplers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from mmdet.models.task_modules.samplers import (BaseSampler, CombinedSampler, 3 | InstanceBalancedPosSampler, 4 | IoUBalancedNegSampler, 5 | OHEMSampler, RandomSampler, 6 | SamplingResult) 7 | 8 | from .iou_neg_piecewise_sampler import IoUNegPiecewiseSampler 9 | from .pseudosample import PseudoSampler 10 | 11 | __all__ = [ 12 | 'BaseSampler', 'PseudoSampler', 'RandomSampler', 13 | 'InstanceBalancedPosSampler', 'IoUBalancedNegSampler', 'CombinedSampler', 14 | 'OHEMSampler', 'SamplingResult', 'IoUNegPiecewiseSampler' 15 | ] 16 | -------------------------------------------------------------------------------- /mmdet3d/models/task_modules/samplers/pseudosample.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import torch 3 | from mmdet.models.task_modules import AssignResult 4 | from mmengine.structures import InstanceData 5 | 6 | from mmdet3d.registry import TASK_UTILS 7 | from ..samplers import BaseSampler, SamplingResult 8 | 9 | 10 | @TASK_UTILS.register_module() 11 | class PseudoSampler(BaseSampler): 12 | """A pseudo sampler that does not do sampling actually.""" 13 | 14 | # TODO: This is a temporary pseudo sampler. 15 | 16 | def __init__(self, **kwargs): 17 | pass 18 | 19 | def _sample_pos(self, **kwargs): 20 | """Sample positive samples.""" 21 | raise NotImplementedError 22 | 23 | def _sample_neg(self, **kwargs): 24 | """Sample negative samples.""" 25 | raise NotImplementedError 26 | 27 | def sample(self, assign_result: AssignResult, pred_instances: InstanceData, 28 | gt_instances: InstanceData, *args, **kwargs): 29 | """Directly returns the positive and negative indices of samples. 30 | 31 | Args: 32 | assign_result (:obj:`AssignResult`): Bbox assigning results. 33 | pred_instances (:obj:`InstaceData`): Instances of model 34 | predictions. It includes ``priors``, and the priors can 35 | be anchors, points, or bboxes predicted by the model, 36 | shape(n, 4). 37 | gt_instances (:obj:`InstaceData`): Ground truth of instance 38 | annotations. It usually includes ``bboxes`` and ``labels`` 39 | attributes. 40 | 41 | Returns: 42 | :obj:`SamplingResult`: sampler results 43 | """ 44 | gt_bboxes = gt_instances.bboxes_3d 45 | priors = pred_instances.priors 46 | 47 | pos_inds = torch.nonzero( 48 | assign_result.gt_inds > 0, as_tuple=False).squeeze(-1).unique() 49 | neg_inds = torch.nonzero( 50 | assign_result.gt_inds == 0, as_tuple=False).squeeze(-1).unique() 51 | 52 | gt_flags = priors.new_zeros(priors.shape[0], dtype=torch.uint8) 53 | sampling_result = SamplingResult( 54 | pos_inds=pos_inds, 55 | neg_inds=neg_inds, 56 | priors=priors, 57 | gt_bboxes=gt_bboxes, 58 | assign_result=assign_result, 59 | gt_flags=gt_flags, 60 | avg_factor_with_neg=False) 61 | return sampling_result 62 | -------------------------------------------------------------------------------- /mmdet3d/models/task_modules/voxel/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .voxel_generator import VoxelGenerator 3 | 4 | __all__ = ['VoxelGenerator'] 5 | -------------------------------------------------------------------------------- /mmdet3d/models/test_time_augs/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .merge_augs import merge_aug_bboxes_3d 3 | 4 | __all__ = ['merge_aug_bboxes_3d'] 5 | -------------------------------------------------------------------------------- /mmdet3d/models/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .add_prefix import add_prefix 3 | from .clip_sigmoid import clip_sigmoid 4 | from .edge_indices import get_edge_indices 5 | from .gaussian import (draw_heatmap_gaussian, ellip_gaussian2D, gaussian_2d, 6 | gaussian_radius, get_ellip_gaussian_2D) 7 | from .gen_keypoints import get_keypoints 8 | from .handle_objs import filter_outside_objs, handle_proj_objs 9 | 10 | __all__ = [ 11 | 'clip_sigmoid', 'get_edge_indices', 'filter_outside_objs', 12 | 'handle_proj_objs', 'get_keypoints', 'gaussian_2d', 13 | 'draw_heatmap_gaussian', 'gaussian_radius', 'get_ellip_gaussian_2D', 14 | 'ellip_gaussian2D', 'add_prefix' 15 | ] 16 | -------------------------------------------------------------------------------- /mmdet3d/models/utils/add_prefix.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | def add_prefix(inputs: dict, prefix: str) -> dict: 3 | """Add prefix for dict. 4 | 5 | Args: 6 | inputs (dict): The input dict with str keys. 7 | prefix (str): The prefix to add. 8 | 9 | Returns: 10 | 11 | dict: The dict with keys updated with ``prefix``. 12 | """ 13 | 14 | outputs = dict() 15 | for name, value in inputs.items(): 16 | outputs[f'{prefix}.{name}'] = value 17 | 18 | return outputs 19 | -------------------------------------------------------------------------------- /mmdet3d/models/utils/clip_sigmoid.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import torch 3 | from torch import Tensor 4 | 5 | 6 | def clip_sigmoid(x: Tensor, eps: float = 1e-4) -> Tensor: 7 | """Sigmoid function for input feature. 8 | 9 | Args: 10 | x (Tensor): Input feature map with the shape of [B, N, H, W]. 11 | eps (float): Lower bound of the range to be clamped to. 12 | Defaults to 1e-4. 13 | 14 | Returns: 15 | Tensor: Feature map after sigmoid. 16 | """ 17 | y = torch.clamp(x.sigmoid_(), min=eps, max=1 - eps) 18 | return y 19 | -------------------------------------------------------------------------------- /mmdet3d/models/voxel_encoders/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .pillar_encoder import DynamicPillarFeatureNet, PillarFeatureNet 3 | from .voxel_encoder import (DynamicSimpleVFE, DynamicVFE, HardSimpleVFE, 4 | HardVFE, SegVFE) 5 | 6 | __all__ = [ 7 | 'PillarFeatureNet', 'DynamicPillarFeatureNet', 'HardVFE', 'DynamicVFE', 8 | 'HardSimpleVFE', 'DynamicSimpleVFE', 'SegVFE' 9 | ] 10 | -------------------------------------------------------------------------------- /mmdet3d/structures/bbox_3d/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .base_box3d import BaseInstance3DBoxes 3 | from .box_3d_mode import Box3DMode 4 | from .cam_box3d import CameraInstance3DBoxes 5 | from .coord_3d_mode import Coord3DMode 6 | from .depth_box3d import DepthInstance3DBoxes 7 | from .lidar_box3d import LiDARInstance3DBoxes 8 | from .utils import (get_box_type, get_proj_mat_by_coord_type, limit_period, 9 | mono_cam_box2vis, points_cam2img, points_img2cam, 10 | rotation_3d_in_axis, xywhr2xyxyr) 11 | 12 | __all__ = [ 13 | 'Box3DMode', 'BaseInstance3DBoxes', 'LiDARInstance3DBoxes', 14 | 'CameraInstance3DBoxes', 'DepthInstance3DBoxes', 'xywhr2xyxyr', 15 | 'get_box_type', 'rotation_3d_in_axis', 'limit_period', 'points_cam2img', 16 | 'points_img2cam', 'Coord3DMode', 'mono_cam_box2vis', 17 | 'get_proj_mat_by_coord_type' 18 | ] 19 | -------------------------------------------------------------------------------- /mmdet3d/structures/ops/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | # yapf:disable 3 | from .box_np_ops import (box2d_to_corner_jit, box3d_to_bbox, 4 | box_camera_to_lidar, boxes3d_to_corners3d_lidar, 5 | camera_to_lidar, center_to_corner_box2d, 6 | center_to_corner_box3d, center_to_minmax_2d, 7 | corner_to_standup_nd_jit, corner_to_surfaces_3d, 8 | corner_to_surfaces_3d_jit, corners_nd, 9 | create_anchors_3d_range, depth_to_lidar_points, 10 | depth_to_points, get_frustum, iou_jit, 11 | minmax_to_corner_2d, points_in_convex_polygon_3d_jit, 12 | points_in_convex_polygon_jit, points_in_rbbox, 13 | projection_matrix_to_CRT_kitti, rbbox2d_to_near_bbox, 14 | remove_outside_points, rotation_points_single_angle, 15 | surface_equ_3d) 16 | # yapf:enable 17 | from .iou3d_calculator import (AxisAlignedBboxOverlaps3D, BboxOverlaps3D, 18 | BboxOverlapsNearest3D, 19 | axis_aligned_bbox_overlaps_3d, bbox_overlaps_3d, 20 | bbox_overlaps_nearest_3d) 21 | from .transforms import bbox3d2result, bbox3d2roi, bbox3d_mapping_back 22 | 23 | __all__ = [ 24 | 'box2d_to_corner_jit', 'box3d_to_bbox', 'box_camera_to_lidar', 25 | 'boxes3d_to_corners3d_lidar', 'camera_to_lidar', 'center_to_corner_box2d', 26 | 'center_to_corner_box3d', 'center_to_minmax_2d', 27 | 'corner_to_standup_nd_jit', 'corner_to_surfaces_3d', 28 | 'corner_to_surfaces_3d_jit', 'corners_nd', 'create_anchors_3d_range', 29 | 'depth_to_lidar_points', 'depth_to_points', 'get_frustum', 'iou_jit', 30 | 'minmax_to_corner_2d', 'points_in_convex_polygon_3d_jit', 31 | 'points_in_convex_polygon_jit', 'points_in_rbbox', 32 | 'projection_matrix_to_CRT_kitti', 'rbbox2d_to_near_bbox', 33 | 'remove_outside_points', 'rotation_points_single_angle', 'surface_equ_3d', 34 | 'BboxOverlapsNearest3D', 'BboxOverlaps3D', 'bbox_overlaps_nearest_3d', 35 | 'bbox_overlaps_3d', 'AxisAlignedBboxOverlaps3D', 36 | 'axis_aligned_bbox_overlaps_3d', 'bbox3d_mapping_back', 'bbox3d2roi', 37 | 'bbox3d2result' 38 | ] 39 | -------------------------------------------------------------------------------- /mmdet3d/structures/ops/transforms.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import torch 3 | 4 | 5 | def bbox3d_mapping_back(bboxes, scale_factor, flip_horizontal, flip_vertical): 6 | """Map bboxes from testing scale to original image scale. 7 | 8 | Args: 9 | bboxes (:obj:`BaseInstance3DBoxes`): Boxes to be mapped back. 10 | scale_factor (float): Scale factor. 11 | flip_horizontal (bool): Whether to flip horizontally. 12 | flip_vertical (bool): Whether to flip vertically. 13 | 14 | Returns: 15 | :obj:`BaseInstance3DBoxes`: Boxes mapped back. 16 | """ 17 | new_bboxes = bboxes.clone() 18 | if flip_horizontal: 19 | new_bboxes.flip('horizontal') 20 | if flip_vertical: 21 | new_bboxes.flip('vertical') 22 | new_bboxes.scale(1 / scale_factor) 23 | 24 | return new_bboxes 25 | 26 | 27 | def bbox3d2roi(bbox_list): 28 | """Convert a list of bounding boxes to roi format. 29 | 30 | Args: 31 | bbox_list (list[torch.Tensor]): A list of bounding boxes 32 | corresponding to a batch of images. 33 | 34 | Returns: 35 | torch.Tensor: Region of interests in shape (n, c), where 36 | the channels are in order of [batch_ind, x, y ...]. 37 | """ 38 | rois_list = [] 39 | for img_id, bboxes in enumerate(bbox_list): 40 | if bboxes.size(0) > 0: 41 | img_inds = bboxes.new_full((bboxes.size(0), 1), img_id) 42 | rois = torch.cat([img_inds, bboxes], dim=-1) 43 | else: 44 | rois = torch.zeros_like(bboxes) 45 | rois_list.append(rois) 46 | rois = torch.cat(rois_list, 0) 47 | return rois 48 | 49 | 50 | # TODO delete this 51 | def bbox3d2result(bboxes, scores, labels, attrs=None): 52 | """Convert detection results to a list of numpy arrays. 53 | 54 | Args: 55 | bboxes (torch.Tensor): Bounding boxes with shape (N, 5). 56 | labels (torch.Tensor): Labels with shape (N, ). 57 | scores (torch.Tensor): Scores with shape (N, ). 58 | attrs (torch.Tensor, optional): Attributes with shape (N, ). 59 | Defaults to None. 60 | 61 | Returns: 62 | dict[str, torch.Tensor]: Bounding box results in cpu mode. 63 | 64 | - boxes_3d (torch.Tensor): 3D boxes. 65 | - scores (torch.Tensor): Prediction scores. 66 | - labels_3d (torch.Tensor): Box labels. 67 | - attrs_3d (torch.Tensor, optional): Box attributes. 68 | """ 69 | result_dict = dict( 70 | bboxes_3d=bboxes.to('cpu'), 71 | scores_3d=scores.cpu(), 72 | labels_3d=labels.cpu()) 73 | 74 | if attrs is not None: 75 | result_dict['attr_labels'] = attrs.cpu() 76 | 77 | return result_dict 78 | -------------------------------------------------------------------------------- /mmdet3d/structures/points/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .base_points import BasePoints 3 | from .cam_points import CameraPoints 4 | from .depth_points import DepthPoints 5 | from .lidar_points import LiDARPoints 6 | 7 | __all__ = ['BasePoints', 'CameraPoints', 'DepthPoints', 'LiDARPoints'] 8 | 9 | 10 | def get_points_type(points_type): 11 | """Get the class of points according to coordinate type. 12 | 13 | Args: 14 | points_type (str): The type of points coordinate. 15 | The valid value are "CAMERA", "LIDAR", or "DEPTH". 16 | 17 | Returns: 18 | class: Points type. 19 | """ 20 | if points_type == 'CAMERA': 21 | points_cls = CameraPoints 22 | elif points_type == 'LIDAR': 23 | points_cls = LiDARPoints 24 | elif points_type == 'DEPTH': 25 | points_cls = DepthPoints 26 | else: 27 | raise ValueError('Only "points_type" of "CAMERA", "LIDAR", or "DEPTH"' 28 | f' are supported, got {points_type}') 29 | 30 | return points_cls 31 | -------------------------------------------------------------------------------- /mmdet3d/structures/points/cam_points.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .base_points import BasePoints 3 | 4 | 5 | class CameraPoints(BasePoints): 6 | """Points of instances in CAM coordinates. 7 | 8 | Args: 9 | tensor (torch.Tensor | np.ndarray | list): a N x points_dim matrix. 10 | points_dim (int, optional): Number of the dimension of a point. 11 | Each row is (x, y, z). Defaults to 3. 12 | attribute_dims (dict, optional): Dictionary to indicate the 13 | meaning of extra dimension. Defaults to None. 14 | 15 | Attributes: 16 | tensor (torch.Tensor): Float matrix of N x points_dim. 17 | points_dim (int): Integer indicating the dimension of a point. 18 | Each row is (x, y, z, ...). 19 | attribute_dims (bool): Dictionary to indicate the meaning of extra 20 | dimension. Defaults to None. 21 | rotation_axis (int): Default rotation axis for points rotation. 22 | """ 23 | 24 | def __init__(self, tensor, points_dim=3, attribute_dims=None): 25 | super(CameraPoints, self).__init__( 26 | tensor, points_dim=points_dim, attribute_dims=attribute_dims) 27 | self.rotation_axis = 1 28 | 29 | def flip(self, bev_direction='horizontal'): 30 | """Flip the points along given BEV direction. 31 | 32 | Args: 33 | bev_direction (str): Flip direction (horizontal or vertical). 34 | """ 35 | if bev_direction == 'horizontal': 36 | self.tensor[:, 0] = -self.tensor[:, 0] 37 | elif bev_direction == 'vertical': 38 | self.tensor[:, 2] = -self.tensor[:, 2] 39 | 40 | @property 41 | def bev(self): 42 | """torch.Tensor: BEV of the points in shape (N, 2).""" 43 | return self.tensor[:, [0, 2]] 44 | 45 | def convert_to(self, dst, rt_mat=None): 46 | """Convert self to ``dst`` mode. 47 | 48 | Args: 49 | dst (:obj:`CoordMode`): The target Point mode. 50 | rt_mat (np.ndarray | torch.Tensor, optional): The rotation and 51 | translation matrix between different coordinates. 52 | Defaults to None. 53 | The conversion from `src` coordinates to `dst` coordinates 54 | usually comes along the change of sensors, e.g., from camera 55 | to LiDAR. This requires a transformation matrix. 56 | 57 | Returns: 58 | :obj:`BasePoints`: The converted point of the same type 59 | in the `dst` mode. 60 | """ 61 | from mmdet3d.structures import Coord3DMode 62 | return Coord3DMode.convert_point( 63 | point=self, src=Coord3DMode.CAM, dst=dst, rt_mat=rt_mat) 64 | -------------------------------------------------------------------------------- /mmdet3d/structures/points/depth_points.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .base_points import BasePoints 3 | 4 | 5 | class DepthPoints(BasePoints): 6 | """Points of instances in DEPTH coordinates. 7 | 8 | Args: 9 | tensor (torch.Tensor | np.ndarray | list): a N x points_dim matrix. 10 | points_dim (int, optional): Number of the dimension of a point. 11 | Each row is (x, y, z). Defaults to 3. 12 | attribute_dims (dict, optional): Dictionary to indicate the 13 | meaning of extra dimension. Defaults to None. 14 | 15 | Attributes: 16 | tensor (torch.Tensor): Float matrix of N x points_dim. 17 | points_dim (int): Integer indicating the dimension of a point. 18 | Each row is (x, y, z, ...). 19 | attribute_dims (bool): Dictionary to indicate the meaning of extra 20 | dimension. Defaults to None. 21 | rotation_axis (int): Default rotation axis for points rotation. 22 | """ 23 | 24 | def __init__(self, tensor, points_dim=3, attribute_dims=None): 25 | super(DepthPoints, self).__init__( 26 | tensor, points_dim=points_dim, attribute_dims=attribute_dims) 27 | self.rotation_axis = 2 28 | 29 | def flip(self, bev_direction='horizontal'): 30 | """Flip the points along given BEV direction. 31 | 32 | Args: 33 | bev_direction (str): Flip direction (horizontal or vertical). 34 | """ 35 | if bev_direction == 'horizontal': 36 | self.tensor[:, 0] = -self.tensor[:, 0] 37 | elif bev_direction == 'vertical': 38 | self.tensor[:, 1] = -self.tensor[:, 1] 39 | 40 | def convert_to(self, dst, rt_mat=None): 41 | """Convert self to ``dst`` mode. 42 | 43 | Args: 44 | dst (:obj:`CoordMode`): The target Point mode. 45 | rt_mat (np.ndarray | torch.Tensor, optional): The rotation and 46 | translation matrix between different coordinates. 47 | Defaults to None. 48 | The conversion from `src` coordinates to `dst` coordinates 49 | usually comes along the change of sensors, e.g., from camera 50 | to LiDAR. This requires a transformation matrix. 51 | 52 | Returns: 53 | :obj:`BasePoints`: The converted point of the same type 54 | in the `dst` mode. 55 | """ 56 | from mmdet3d.structures import Coord3DMode 57 | return Coord3DMode.convert_point( 58 | point=self, src=Coord3DMode.DEPTH, dst=dst, rt_mat=rt_mat) 59 | -------------------------------------------------------------------------------- /mmdet3d/structures/points/lidar_points.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .base_points import BasePoints 3 | 4 | 5 | class LiDARPoints(BasePoints): 6 | """Points of instances in LIDAR coordinates. 7 | 8 | Args: 9 | tensor (torch.Tensor | np.ndarray | list): a N x points_dim matrix. 10 | points_dim (int, optional): Number of the dimension of a point. 11 | Each row is (x, y, z). Defaults to 3. 12 | attribute_dims (dict, optional): Dictionary to indicate the 13 | meaning of extra dimension. Defaults to None. 14 | 15 | Attributes: 16 | tensor (torch.Tensor): Float matrix of N x points_dim. 17 | points_dim (int): Integer indicating the dimension of a point. 18 | Each row is (x, y, z, ...). 19 | attribute_dims (bool): Dictionary to indicate the meaning of extra 20 | dimension. Defaults to None. 21 | rotation_axis (int): Default rotation axis for points rotation. 22 | """ 23 | 24 | def __init__(self, tensor, points_dim=3, attribute_dims=None): 25 | super(LiDARPoints, self).__init__( 26 | tensor, points_dim=points_dim, attribute_dims=attribute_dims) 27 | self.rotation_axis = 2 28 | 29 | def flip(self, bev_direction='horizontal'): 30 | """Flip the points along given BEV direction. 31 | 32 | Args: 33 | bev_direction (str): Flip direction (horizontal or vertical). 34 | """ 35 | if bev_direction == 'horizontal': 36 | self.tensor[:, 1] = -self.tensor[:, 1] 37 | elif bev_direction == 'vertical': 38 | self.tensor[:, 0] = -self.tensor[:, 0] 39 | 40 | def convert_to(self, dst, rt_mat=None): 41 | """Convert self to ``dst`` mode. 42 | 43 | Args: 44 | dst (:obj:`CoordMode`): The target Point mode. 45 | rt_mat (np.ndarray | torch.Tensor, optional): The rotation and 46 | translation matrix between different coordinates. 47 | Defaults to None. 48 | The conversion from `src` coordinates to `dst` coordinates 49 | usually comes along the change of sensors, e.g., from camera 50 | to LiDAR. This requires a transformation matrix. 51 | 52 | Returns: 53 | :obj:`BasePoints`: The converted point of the same type 54 | in the `dst` mode. 55 | """ 56 | from mmdet3d.structures import Coord3DMode 57 | return Coord3DMode.convert_point( 58 | point=self, src=Coord3DMode.LIDAR, dst=dst, rt_mat=rt_mat) 59 | -------------------------------------------------------------------------------- /mmdet3d/testing/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .data_utils import (create_data_info_after_loading, 3 | create_dummy_data_info, 4 | create_mono3d_data_info_after_loading) 5 | from .model_utils import (create_detector_inputs, get_detector_cfg, 6 | get_model_cfg, setup_seed) 7 | 8 | __all__ = [ 9 | 'create_dummy_data_info', 'create_data_info_after_loading', 10 | 'create_mono3d_data_info_after_loading', 'create_detector_inputs', 11 | 'get_detector_cfg', 'get_model_cfg', 'setup_seed' 12 | ] 13 | -------------------------------------------------------------------------------- /mmdet3d/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .array_converter import ArrayConverter, array_converter 3 | from .collect_env import collect_env 4 | from .compat_cfg import compat_cfg 5 | from .misc import replace_ceph_backend 6 | from .setup_env import register_all_modules, setup_multi_processes 7 | from .typing_utils import (ConfigType, InstanceList, MultiConfig, 8 | OptConfigType, OptInstanceList, OptMultiConfig, 9 | OptSampleList, OptSamplingResultList) 10 | 11 | __all__ = [ 12 | 'collect_env', 'setup_multi_processes', 'compat_cfg', 13 | 'register_all_modules', 'array_converter', 'ArrayConverter', 'ConfigType', 14 | 'OptConfigType', 'MultiConfig', 'OptMultiConfig', 'InstanceList', 15 | 'OptInstanceList', 'OptSamplingResultList', 'replace_ceph_backend', 16 | 'OptSampleList' 17 | ] 18 | -------------------------------------------------------------------------------- /mmdet3d/utils/collect_env.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import mmdet 3 | from mmengine.utils import get_git_hash 4 | from mmengine.utils.dl_utils import collect_env as collect_base_env 5 | 6 | import mmdet3d 7 | 8 | 9 | def collect_env(): 10 | """Collect the information of the running environments.""" 11 | env_info = collect_base_env() 12 | env_info['MMDetection'] = mmdet.__version__ 13 | env_info['MMDetection3D'] = mmdet3d.__version__ + '+' + get_git_hash()[:7] 14 | from mmdet3d.models.layers.spconv import IS_SPCONV2_AVAILABLE 15 | env_info['spconv2.0'] = IS_SPCONV2_AVAILABLE 16 | 17 | return env_info 18 | 19 | 20 | if __name__ == '__main__': 21 | for name, val in collect_env().items(): 22 | print(f'{name}: {val}') 23 | -------------------------------------------------------------------------------- /mmdet3d/utils/typing_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | """Collecting some commonly used type hint in MMDetection3D.""" 3 | from typing import List, Optional, Union 4 | 5 | from mmdet.models.task_modules.samplers import SamplingResult 6 | from mmengine.config import ConfigDict 7 | from mmengine.structures import InstanceData 8 | 9 | from mmdet3d.structures.det3d_data_sample import Det3DDataSample 10 | 11 | # Type hint of config data 12 | ConfigType = Union[ConfigDict, dict] 13 | OptConfigType = Optional[ConfigType] 14 | 15 | # Type hint of one or more config data 16 | MultiConfig = Union[ConfigType, List[ConfigType]] 17 | OptMultiConfig = Optional[MultiConfig] 18 | 19 | InstanceList = List[InstanceData] 20 | OptInstanceList = Optional[InstanceList] 21 | 22 | SamplingResultList = List[SamplingResult] 23 | 24 | OptSamplingResultList = Optional[SamplingResultList] 25 | SampleList = List[Det3DDataSample] 26 | OptSampleList = Optional[SampleList] 27 | -------------------------------------------------------------------------------- /mmdet3d/version.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Open-MMLab. All rights reserved. 2 | 3 | __version__ = '1.1.0' 4 | short_version = __version__ 5 | 6 | 7 | def parse_version_info(version_str): 8 | """Parse a version string into a tuple. 9 | 10 | Args: 11 | version_str (str): The version string. 12 | 13 | Returns: 14 | tuple[int | str]: The version info, e.g., "1.3.0" is parsed into 15 | (1, 3, 0), and "2.0.0rc4" is parsed into (2, 0, 0, 'rc4'). 16 | """ 17 | version_info = [] 18 | for x in version_str.split('.'): 19 | if x.isdigit(): 20 | version_info.append(int(x)) 21 | elif x.find('rc') != -1: 22 | patch_version = x.split('rc') 23 | version_info.append(int(patch_version[0])) 24 | version_info.append(f'rc{patch_version[1]}') 25 | return tuple(version_info) 26 | 27 | 28 | version_info = parse_version_info(__version__) 29 | -------------------------------------------------------------------------------- /mmdet3d/visualization/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from .local_visualizer import Det3DLocalVisualizer 3 | from .vis_utils import (proj_camera_bbox3d_to_img, proj_depth_bbox3d_to_img, 4 | proj_lidar_bbox3d_to_img, to_depth_mode, write_obj, 5 | write_oriented_bbox) 6 | 7 | __all__ = [ 8 | 'Det3DLocalVisualizer', 'write_obj', 'write_oriented_bbox', 9 | 'to_depth_mode', 'proj_lidar_bbox3d_to_img', 'proj_depth_bbox3d_to_img', 10 | 'proj_camera_bbox3d_to_img' 11 | ] 12 | -------------------------------------------------------------------------------- /pic/backbone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavierGrool/FGFusion/60a0f82ff2d0ba14d0c18b2acb3b83efdd6a8f82/pic/backbone.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements/build.txt 2 | -r requirements/optional.txt 3 | -r requirements/runtime.txt 4 | -r requirements/tests.txt 5 | -------------------------------------------------------------------------------- /requirements/build.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavierGrool/FGFusion/60a0f82ff2d0ba14d0c18b2acb3b83efdd6a8f82/requirements/build.txt -------------------------------------------------------------------------------- /requirements/docs.txt: -------------------------------------------------------------------------------- 1 | docutils==0.16.0 2 | m2r==0.2.1 3 | mistune==0.8.4 4 | myst-parser 5 | -e git+https://github.com/open-mmlab/pytorch_sphinx_theme.git#egg=pytorch_sphinx_theme 6 | sphinx==4.0.2 7 | sphinx-copybutton 8 | sphinx_markdown_tables 9 | -------------------------------------------------------------------------------- /requirements/mminstall.txt: -------------------------------------------------------------------------------- 1 | mmcv>=2.0.0rc4,<2.1.0 2 | mmdet>=3.0.0,<3.1.0 3 | mmengine>=0.7.1,<1.0.0 4 | -------------------------------------------------------------------------------- /requirements/optional.txt: -------------------------------------------------------------------------------- 1 | black==20.8b1 # be compatible with typing-extensions 3.7.4 2 | typing-extensions # required by tensorflow<=2.6 3 | waymo-open-dataset-tf-2-6-0 # requires python>=3.7 4 | -------------------------------------------------------------------------------- /requirements/readthedocs.txt: -------------------------------------------------------------------------------- 1 | mmcv>=2.0.0rc4 2 | mmdet>=3.0.0 3 | mmengine>=0.7.1 4 | torch 5 | torchvision 6 | -------------------------------------------------------------------------------- /requirements/runtime.txt: -------------------------------------------------------------------------------- 1 | lyft_dataset_sdk 2 | networkx>=2.5 3 | numba # you should install numba==0.53.0 if your environment is cuda-9.0 4 | numpy 5 | nuscenes-devkit 6 | open3d 7 | plyfile 8 | scikit-image 9 | # by default we also use tensorboard to log results 10 | tensorboard 11 | trimesh 12 | -------------------------------------------------------------------------------- /requirements/tests.txt: -------------------------------------------------------------------------------- 1 | asynctest 2 | codecov 3 | flake8 4 | interrogate 5 | isort 6 | # Note: used for kwarray.group_items, this may be ported to mmcv in the future. 7 | kwarray 8 | parameterized 9 | pytest 10 | pytest-cov 11 | pytest-runner 12 | ubelt 13 | xdoctest >= 0.10.0 14 | yapf 15 | -------------------------------------------------------------------------------- /resources/mmdet3d_outdoor_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavierGrool/FGFusion/60a0f82ff2d0ba14d0c18b2acb3b83efdd6a8f82/resources/mmdet3d_outdoor_demo.gif -------------------------------------------------------------------------------- /resources/nuimages_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavierGrool/FGFusion/60a0f82ff2d0ba14d0c18b2acb3b83efdd6a8f82/resources/nuimages_demo.gif -------------------------------------------------------------------------------- /resources/open3d_visual.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavierGrool/FGFusion/60a0f82ff2d0ba14d0c18b2acb3b83efdd6a8f82/resources/open3d_visual.gif -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [yapf] 2 | BASED_ON_STYLE = pep8 3 | BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF = true 4 | SPLIT_BEFORE_EXPRESSION_AFTER_OPENING_PAREN = true 5 | 6 | [isort] 7 | line_length = 79 8 | multi_line_output = 0 9 | extra_standard_library = setuptools 10 | known_first_party = mmdet3d 11 | known_third_party = cv2,imageio,indoor3d_util,load_scannet_data,lyft_dataset_sdk,m2r,matplotlib,mmcv,mmdet,mmengine,nuimages,numba,numpy,nuscenes,pandas,plyfile,pycocotools,pyquaternion,pytest,pytorch_sphinx_theme,recommonmark,requests,scannet_utils,scipy,seaborn,shapely,skimage,sphinx,tensorflow,terminaltables,torch,trimesh,ts,waymo_open_dataset 12 | no_lines_before = STDLIB,LOCALFOLDER 13 | default_section = THIRDPARTY 14 | 15 | [codespell] 16 | ignore-words-list = ans,refridgerator,crate,hist,formating,dout,wan,nd,fo,avod,AVOD,warmup 17 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from mmdet3d.apis import inference_detector, init_model 2 | from mmdet3d.registry import VISUALIZERS 3 | 4 | config_file = './checkpoints/second_hv_secfpn_8xb6-80e_kitti-3d-car.py' 5 | checkpoint_file = './checkpoints/hv_second_secfpn_6x8_80e_kitti-3d-car_20200620_230238-393f000c.pth' 6 | 7 | model = init_model(config_file, checkpoint_file, device='cuda:0') 8 | 9 | visualizer = VISUALIZERS.build(model.cfg.visualizer) 10 | visualizer.dataset_meta = model.dataset_meta 11 | 12 | pcd = './demo/data/kitti/000008.bin' 13 | result, data = inference_detector(model, pcd) 14 | points = data['inputs']['points'] 15 | data_input = dict(points=points) 16 | 17 | out_dir = '/home/awesome/yzx/coding/mmdetection3d/mmdetection3d/checkpoints/test.png' 18 | visualizer.add_datasample( 19 | 'result', 20 | data_input, 21 | data_sample=result, 22 | draw_gt=False, 23 | show=False, 24 | wait_time=0, 25 | out_file=out_dir, 26 | vis_task='lidar_det') -------------------------------------------------------------------------------- /tests/test_datasets/test_lyft_dataset.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import numpy as np 3 | from mmcv.transforms.base import BaseTransform 4 | from mmengine.registry import TRANSFORMS 5 | from mmengine.structures import InstanceData 6 | 7 | from mmdet3d.datasets import LyftDataset 8 | from mmdet3d.structures import Det3DDataSample, LiDARInstance3DBoxes 9 | 10 | 11 | def _generate_nus_dataset_config(): 12 | data_root = 'tests/data/lyft' 13 | ann_file = 'lyft_infos.pkl' 14 | classes = [ 15 | 'car', 'truck', 'bus', 'emergency_vehicle', 'other_vehicle', 16 | 'motorcycle', 'bicycle', 'pedestrian', 'animal' 17 | ] 18 | if 'Identity' not in TRANSFORMS: 19 | 20 | @TRANSFORMS.register_module() 21 | class Identity(BaseTransform): 22 | 23 | def transform(self, info): 24 | packed_input = dict(data_samples=Det3DDataSample()) 25 | if 'ann_info' in info: 26 | packed_input[ 27 | 'data_samples'].gt_instances_3d = InstanceData() 28 | packed_input[ 29 | 'data_samples'].gt_instances_3d.labels_3d = info[ 30 | 'ann_info']['gt_labels_3d'] 31 | return packed_input 32 | 33 | pipeline = [ 34 | dict(type='Identity'), 35 | ] 36 | modality = dict(use_lidar=True, use_camera=False) 37 | data_prefix = dict(pts='lidar', img='', sweeps='sweeps/LIDAR_TOP') 38 | return data_root, ann_file, classes, data_prefix, pipeline, modality 39 | 40 | 41 | def test_getitem(): 42 | np.random.seed(0) 43 | data_root, ann_file, classes, data_prefix, pipeline, modality = \ 44 | _generate_nus_dataset_config() 45 | 46 | lyft_dataset = LyftDataset( 47 | data_root, 48 | ann_file, 49 | data_prefix=data_prefix, 50 | pipeline=pipeline, 51 | metainfo=dict(classes=classes), 52 | modality=modality) 53 | 54 | lyft_dataset.prepare_data(0) 55 | input_dict = lyft_dataset.get_data_info(0) 56 | # assert the the path should contains data_prefix and data_root 57 | assert data_prefix['pts'] in input_dict['lidar_points']['lidar_path'] 58 | assert data_root in input_dict['lidar_points']['lidar_path'] 59 | 60 | ann_info = lyft_dataset.parse_ann_info(input_dict) 61 | 62 | # assert the keys in ann_info and the type 63 | assert 'gt_labels_3d' in ann_info 64 | assert ann_info['gt_labels_3d'].dtype == np.int64 65 | assert len(ann_info['gt_labels_3d']) == 3 66 | 67 | assert 'gt_bboxes_3d' in ann_info 68 | assert isinstance(ann_info['gt_bboxes_3d'], LiDARInstance3DBoxes) 69 | 70 | assert len(lyft_dataset.metainfo['classes']) == 9 71 | -------------------------------------------------------------------------------- /tests/test_datasets/test_transforms/test_formating.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import unittest 3 | 4 | import torch 5 | from mmengine.testing import assert_allclose 6 | 7 | from mmdet3d.datasets.transforms.formating import Pack3DDetInputs 8 | from mmdet3d.structures import LiDARInstance3DBoxes 9 | from mmdet3d.testing import create_data_info_after_loading 10 | 11 | 12 | class TestPack3DDetInputs(unittest.TestCase): 13 | 14 | def test_packinputs(self): 15 | ori_data_info = create_data_info_after_loading() 16 | pack_input = Pack3DDetInputs( 17 | keys=['points', 'gt_labels_3d', 'gt_bboxes_3d']) 18 | packed_results = pack_input(ori_data_info) 19 | inputs = packed_results['inputs'] 20 | 21 | # annotations 22 | gt_instances = packed_results['data_samples'].gt_instances_3d 23 | self.assertIn('points', inputs) 24 | self.assertIsInstance(inputs['points'], torch.Tensor) 25 | assert_allclose(inputs['points'].sum(), torch.tensor(13062.6436)) 26 | # assert to_tensor 27 | self.assertIsInstance(inputs['points'], torch.Tensor) 28 | self.assertIn('labels_3d', gt_instances) 29 | assert_allclose(gt_instances.labels_3d, torch.tensor([1])) 30 | # assert to_tensor 31 | self.assertIsInstance(gt_instances.labels_3d, torch.Tensor) 32 | 33 | self.assertIn('bboxes_3d', gt_instances) 34 | self.assertIsInstance(gt_instances.bboxes_3d, LiDARInstance3DBoxes) 35 | assert_allclose(gt_instances.bboxes_3d.tensor.sum(), 36 | torch.tensor(7.2650)) 37 | -------------------------------------------------------------------------------- /tests/test_evaluation/test_functional/test_seg_eval.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import numpy as np 3 | import pytest 4 | import torch 5 | 6 | from mmdet3d.evaluation.functional.seg_eval import seg_eval 7 | 8 | 9 | def test_indoor_eval(): 10 | if not torch.cuda.is_available(): 11 | pytest.skip() 12 | seg_preds = [ 13 | np.array([ 14 | 0, 0, 1, 0, 0, 2, 1, 3, 1, 2, 1, 0, 2, 2, 2, 2, 1, 3, 0, 3, 3, 4, 0 15 | ]) 16 | ] 17 | gt_labels = [ 18 | np.array([ 19 | 0, 0, 0, 4, 0, 0, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4 20 | ]) 21 | ] 22 | 23 | label2cat = { 24 | 0: 'car', 25 | 1: 'bicycle', 26 | 2: 'motorcycle', 27 | 3: 'truck', 28 | 4: 'unlabeled' 29 | } 30 | ret_value = seg_eval(gt_labels, seg_preds, label2cat, ignore_index=4) 31 | 32 | assert np.isclose(ret_value['car'], 0.428571429) 33 | assert np.isclose(ret_value['bicycle'], 0.428571429) 34 | assert np.isclose(ret_value['motorcycle'], 0.6666667) 35 | assert np.isclose(ret_value['truck'], 0.5) 36 | 37 | assert np.isclose(ret_value['acc'], 0.65) 38 | assert np.isclose(ret_value['acc_cls'], 0.65) 39 | assert np.isclose(ret_value['miou'], 0.50595238) 40 | -------------------------------------------------------------------------------- /tests/test_evaluation/test_metrics/test_seg_metric.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import unittest 3 | 4 | import numpy as np 5 | import torch 6 | from mmengine.structures import BaseDataElement 7 | 8 | from mmdet3d.evaluation.metrics import SegMetric 9 | from mmdet3d.structures import Det3DDataSample, PointData 10 | 11 | 12 | class TestSegMetric(unittest.TestCase): 13 | 14 | def _demo_mm_model_output(self): 15 | """Create a superset of inputs needed to run test or train batches.""" 16 | pred_pts_semantic_mask = torch.Tensor([ 17 | 0, 0, 1, 0, 0, 2, 1, 3, 1, 2, 1, 0, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3 18 | ]) 19 | pred_pts_seg_data = dict(pts_semantic_mask=pred_pts_semantic_mask) 20 | data_sample = Det3DDataSample() 21 | data_sample.pred_pts_seg = PointData(**pred_pts_seg_data) 22 | 23 | gt_pts_semantic_mask = np.array([ 24 | 0, 0, 0, 255, 0, 0, 1, 1, 1, 255, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 25 | 3, 255 26 | ]) 27 | ann_info_data = dict(pts_semantic_mask=gt_pts_semantic_mask) 28 | data_sample.eval_ann_info = ann_info_data 29 | 30 | batch_data_samples = [data_sample] 31 | 32 | predictions = [] 33 | for pred in batch_data_samples: 34 | if isinstance(pred, BaseDataElement): 35 | pred = pred.to_dict() 36 | predictions.append(pred) 37 | 38 | return predictions 39 | 40 | def test_evaluate(self): 41 | data_batch = {} 42 | predictions = self._demo_mm_model_output() 43 | label2cat = { 44 | 0: 'car', 45 | 1: 'bicycle', 46 | 2: 'motorcycle', 47 | 3: 'truck', 48 | } 49 | dataset_meta = dict(label2cat=label2cat, ignore_index=255) 50 | seg_metric = SegMetric() 51 | seg_metric.dataset_meta = dataset_meta 52 | seg_metric.process(data_batch, predictions) 53 | res = seg_metric.evaluate(1) 54 | self.assertIsInstance(res, dict) 55 | -------------------------------------------------------------------------------- /tests/test_models/test_backbones/test_cylinder3d_backbone.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | 5 | from mmdet3d.registry import MODELS 6 | 7 | 8 | def test_cylinder3d(): 9 | if not torch.cuda.is_available(): 10 | pytest.skip() 11 | cfg = dict( 12 | type='Asymm3DSpconv', 13 | grid_size=[48, 32, 4], 14 | input_channels=16, 15 | base_channels=32, 16 | norm_cfg=dict(type='BN1d', eps=1e-5, momentum=0.1)) 17 | self = MODELS.build(cfg) 18 | self.cuda() 19 | 20 | batch_size = 1 21 | coorx = torch.randint(0, 48, (50, 1)) 22 | coory = torch.randint(0, 36, (50, 1)) 23 | coorz = torch.randint(0, 4, (50, 1)) 24 | coorbatch = torch.zeros(50, 1) 25 | coors = torch.cat([coorbatch, coorx, coory, coorz], dim=1).cuda() 26 | voxel_features = torch.rand(50, 16).cuda() 27 | 28 | # test forward 29 | feature = self(voxel_features, coors, batch_size) 30 | 31 | assert feature.features.shape == (50, 128) 32 | assert feature.indices.data.shape == (50, 4) 33 | -------------------------------------------------------------------------------- /tests/test_models/test_backbones/test_dgcnn.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import numpy as np 3 | import pytest 4 | import torch 5 | 6 | from mmdet3d.registry import MODELS 7 | 8 | 9 | def test_dgcnn_gf(): 10 | if not torch.cuda.is_available(): 11 | pytest.skip() 12 | 13 | # DGCNNGF used in segmentation 14 | cfg = dict( 15 | type='DGCNNBackbone', 16 | in_channels=6, 17 | num_samples=(20, 20, 20), 18 | knn_modes=['D-KNN', 'F-KNN', 'F-KNN'], 19 | radius=(None, None, None), 20 | gf_channels=((64, 64), (64, 64), (64, )), 21 | fa_channels=(1024, ), 22 | act_cfg=dict(type='ReLU')) 23 | 24 | self = MODELS.build(cfg) 25 | self.cuda() 26 | 27 | xyz = np.fromfile('tests/data/sunrgbd/points/000001.bin', dtype=np.float32) 28 | xyz = torch.from_numpy(xyz).view(1, -1, 6).cuda() # (B, N, 6) 29 | # test forward 30 | ret_dict = self(xyz) 31 | gf_points = ret_dict['gf_points'] 32 | fa_points = ret_dict['fa_points'] 33 | 34 | assert len(gf_points) == 4 35 | assert gf_points[0].shape == torch.Size([1, 100, 6]) 36 | assert gf_points[1].shape == torch.Size([1, 100, 64]) 37 | assert gf_points[2].shape == torch.Size([1, 100, 64]) 38 | assert gf_points[3].shape == torch.Size([1, 100, 64]) 39 | assert fa_points.shape == torch.Size([1, 100, 1216]) 40 | -------------------------------------------------------------------------------- /tests/test_models/test_backbones/test_dla.py: -------------------------------------------------------------------------------- 1 | import torch 2 | 3 | from mmdet3d.registry import MODELS 4 | 5 | 6 | def test_dla_net(): 7 | # test DLANet used in SMOKE 8 | # test list config 9 | cfg = dict( 10 | type='DLANet', 11 | depth=34, 12 | in_channels=3, 13 | norm_cfg=dict(type='GN', num_groups=32)) 14 | 15 | img = torch.randn((4, 3, 32, 32)) 16 | self = MODELS.build(cfg) 17 | self.init_weights() 18 | 19 | results = self(img) 20 | assert len(results) == 6 21 | assert results[0].shape == torch.Size([4, 16, 32, 32]) 22 | assert results[1].shape == torch.Size([4, 32, 16, 16]) 23 | assert results[2].shape == torch.Size([4, 64, 8, 8]) 24 | assert results[3].shape == torch.Size([4, 128, 4, 4]) 25 | assert results[4].shape == torch.Size([4, 256, 2, 2]) 26 | assert results[5].shape == torch.Size([4, 512, 1, 1]) 27 | -------------------------------------------------------------------------------- /tests/test_models/test_backbones/test_mink_resnet.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import numpy as np 3 | import pytest 4 | import torch 5 | 6 | from mmdet3d.registry import MODELS 7 | 8 | 9 | def test_mink_resnet(): 10 | if not torch.cuda.is_available(): 11 | pytest.skip('test requires GPU and torch+cuda') 12 | 13 | try: 14 | import MinkowskiEngine as ME 15 | except ImportError: 16 | pytest.skip('test requires MinkowskiEngine installation') 17 | 18 | coordinates, features = [], [] 19 | np.random.seed(42) 20 | # batch of 2 point clouds 21 | for i in range(2): 22 | c = torch.from_numpy(np.random.rand(500, 3) * 100) 23 | coordinates.append(c.float().cuda()) 24 | f = torch.from_numpy(np.random.rand(500, 3)) 25 | features.append(f.float().cuda()) 26 | tensor_coordinates, tensor_features = ME.utils.sparse_collate( 27 | coordinates, features) 28 | x = ME.SparseTensor( 29 | features=tensor_features, coordinates=tensor_coordinates) 30 | 31 | # MinkResNet34 with 4 outputs 32 | cfg = dict(type='MinkResNet', depth=34, in_channels=3) 33 | self = MODELS.build(cfg).cuda() 34 | self.init_weights() 35 | 36 | y = self(x) 37 | assert len(y) == 4 38 | assert y[0].F.shape == torch.Size([900, 64]) 39 | assert y[0].tensor_stride[0] == 8 40 | assert y[1].F.shape == torch.Size([472, 128]) 41 | assert y[1].tensor_stride[0] == 16 42 | assert y[2].F.shape == torch.Size([105, 256]) 43 | assert y[2].tensor_stride[0] == 32 44 | assert y[3].F.shape == torch.Size([16, 512]) 45 | assert y[3].tensor_stride[0] == 64 46 | 47 | # MinkResNet50 with 2 outputs 48 | cfg = dict( 49 | type='MinkResNet', depth=34, in_channels=3, num_stages=2, pool=False) 50 | self = MODELS.build(cfg).cuda() 51 | self.init_weights() 52 | 53 | y = self(x) 54 | assert len(y) == 2 55 | assert y[0].F.shape == torch.Size([985, 64]) 56 | assert y[0].tensor_stride[0] == 4 57 | assert y[1].F.shape == torch.Size([900, 128]) 58 | assert y[1].tensor_stride[0] == 8 59 | -------------------------------------------------------------------------------- /tests/test_models/test_backbones/test_minkunet_backbone.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | import torch.nn.functional as F 5 | 6 | from mmdet3d.registry import MODELS 7 | 8 | 9 | def test_minkunet_backbone(): 10 | if not torch.cuda.is_available(): 11 | pytest.skip('test requires GPU and torch+cuda') 12 | 13 | try: 14 | import torchsparse # noqa: F401 15 | except ImportError: 16 | pytest.skip('test requires Torchsparse installation') 17 | 18 | coordinates, features = [], [] 19 | for i in range(2): 20 | c = torch.randint(0, 10, (100, 3)).int() 21 | c = F.pad(c, (0, 1), mode='constant', value=i) 22 | coordinates.append(c) 23 | f = torch.rand(100, 4) 24 | features.append(f) 25 | features = torch.cat(features, dim=0).cuda() 26 | coordinates = torch.cat(coordinates, dim=0).cuda() 27 | 28 | cfg = dict(type='MinkUNetBackbone') 29 | self = MODELS.build(cfg).cuda() 30 | self.init_weights() 31 | 32 | y = self(features, coordinates) 33 | assert y.F.shape == torch.Size([200, 96]) 34 | assert y.C.shape == torch.Size([200, 4]) 35 | -------------------------------------------------------------------------------- /tests/test_models/test_backbones/test_spvcnn_backbone.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | import torch.nn.functional as F 5 | 6 | from mmdet3d.registry import MODELS 7 | 8 | 9 | def test_spvcnn_backbone(): 10 | if not torch.cuda.is_available(): 11 | pytest.skip('test requires GPU and torch+cuda') 12 | 13 | try: 14 | import torchsparse # noqa: F401 15 | except ImportError: 16 | pytest.skip('test requires Torchsparse installation') 17 | 18 | coordinates, features = [], [] 19 | for i in range(2): 20 | c = torch.randint(0, 10, (100, 3)).int() 21 | c = F.pad(c, (0, 1), mode='constant', value=i) 22 | coordinates.append(c) 23 | f = torch.rand(100, 4) 24 | features.append(f) 25 | features = torch.cat(features, dim=0).cuda() 26 | coordinates = torch.cat(coordinates, dim=0).cuda() 27 | 28 | cfg = dict(type='SPVCNNBackbone') 29 | self = MODELS.build(cfg).cuda() 30 | self.init_weights() 31 | 32 | y = self(features, coordinates) 33 | assert y.F.shape == torch.Size([200, 96]) 34 | assert y.C.shape == torch.Size([200, 4]) 35 | -------------------------------------------------------------------------------- /tests/test_models/test_decode_heads/test_dgcnn_head.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from unittest import TestCase 3 | 4 | import torch 5 | 6 | from mmdet3d.models.decode_heads import DGCNNHead 7 | from mmdet3d.structures import Det3DDataSample, PointData 8 | 9 | 10 | class TestDGCNNHead(TestCase): 11 | 12 | def test_dgcnn_head_loss(self): 13 | """Tests DGCNN head loss.""" 14 | 15 | dgcnn_head = DGCNNHead( 16 | fp_channels=(1024, 512), 17 | channels=256, 18 | num_classes=13, 19 | dropout_ratio=0.5, 20 | conv_cfg=dict(type='Conv1d'), 21 | norm_cfg=dict(type='BN1d'), 22 | act_cfg=dict(type='LeakyReLU', negative_slope=0.2), 23 | loss_decode=dict( 24 | type='mmdet.CrossEntropyLoss', 25 | use_sigmoid=False, 26 | class_weight=None, 27 | loss_weight=1.0), 28 | ignore_index=13) 29 | 30 | # DGCNN head expects dict format features 31 | fa_points = torch.rand(1, 4096, 1024).float() 32 | feat_dict = dict(fa_points=fa_points) 33 | 34 | # Test forward 35 | seg_logits = dgcnn_head.forward(feat_dict) 36 | 37 | self.assertEqual(seg_logits.shape, torch.Size([1, 13, 4096])) 38 | 39 | # When truth is non-empty then losses 40 | # should be nonzero for random inputs 41 | pts_semantic_mask = torch.randint(0, 13, (4096, )).long() 42 | gt_pts_seg = PointData(pts_semantic_mask=pts_semantic_mask) 43 | 44 | datasample = Det3DDataSample() 45 | datasample.gt_pts_seg = gt_pts_seg 46 | 47 | gt_losses = dgcnn_head.loss_by_feat(seg_logits, [datasample]) 48 | 49 | gt_sem_seg_loss = gt_losses['loss_sem_seg'].item() 50 | 51 | self.assertGreater(gt_sem_seg_loss, 0, 52 | 'semantic seg loss should be positive') 53 | -------------------------------------------------------------------------------- /tests/test_models/test_decode_heads/test_minkunet_head.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from unittest import TestCase 3 | 4 | import pytest 5 | import torch 6 | import torch.nn.functional as F 7 | 8 | from mmdet3d.models.decode_heads import MinkUNetHead 9 | from mmdet3d.structures import Det3DDataSample, PointData 10 | 11 | 12 | class TestMinkUNetHead(TestCase): 13 | 14 | def test_minkunet_head_loss(self): 15 | """Tests PAConv head loss.""" 16 | 17 | try: 18 | import torchsparse 19 | except ImportError: 20 | pytest.skip('test requires Torchsparse installation') 21 | if torch.cuda.is_available(): 22 | minkunet_head = MinkUNetHead(channels=4, num_classes=19) 23 | 24 | minkunet_head.cuda() 25 | coordinates, features = [], [] 26 | for i in range(2): 27 | c = torch.randint(0, 10, (100, 3)).int() 28 | c = F.pad(c, (0, 1), mode='constant', value=i) 29 | coordinates.append(c) 30 | f = torch.rand(100, 4) 31 | features.append(f) 32 | features = torch.cat(features, dim=0).cuda() 33 | coordinates = torch.cat(coordinates, dim=0).cuda() 34 | x = torchsparse.SparseTensor(feats=features, coords=coordinates) 35 | 36 | # Test forward 37 | seg_logits = minkunet_head.forward(x) 38 | 39 | self.assertEqual(seg_logits.shape, torch.Size([200, 19])) 40 | 41 | # When truth is non-empty then losses 42 | # should be nonzero for random inputs 43 | voxel_semantic_mask = torch.randint(0, 19, (100, )).long().cuda() 44 | gt_pts_seg = PointData(voxel_semantic_mask=voxel_semantic_mask) 45 | 46 | datasample = Det3DDataSample() 47 | datasample.gt_pts_seg = gt_pts_seg 48 | 49 | gt_losses = minkunet_head.loss(x, [datasample, datasample], {}) 50 | 51 | gt_sem_seg_loss = gt_losses['loss_sem_seg'].item() 52 | 53 | self.assertGreater(gt_sem_seg_loss, 0, 54 | 'semantic seg loss should be positive') 55 | -------------------------------------------------------------------------------- /tests/test_models/test_dense_heads/test_imvoxel_head.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | from unittest import TestCase 3 | 4 | import pytest 5 | import torch 6 | 7 | from mmdet3d import * # noqa 8 | from mmdet3d.models.dense_heads import ImVoxelHead 9 | from mmdet3d.testing import create_detector_inputs 10 | 11 | 12 | class TestImVoxelHead(TestCase): 13 | 14 | def test_imvoxel_head_loss(self): 15 | """Test imvoxel head loss when truth is empty and non-empty.""" 16 | if not torch.cuda.is_available(): 17 | pytest.skip('test requires GPU and torch+cuda') 18 | 19 | # build head 20 | prior_generator = dict( 21 | type='AlignedAnchor3DRangeGenerator', 22 | ranges=[[-3.2, -0.2, -2.28, 3.2, 6.2, 0.28]], 23 | rotations=[.0]) 24 | imvoxel_head = ImVoxelHead( 25 | n_classes=1, 26 | n_levels=1, 27 | n_channels=32, 28 | n_reg_outs=7, 29 | pts_assign_threshold=27, 30 | pts_center_threshold=18, 31 | prior_generator=prior_generator, 32 | center_loss=dict(type='mmdet.CrossEntropyLoss', use_sigmoid=True), 33 | bbox_loss=dict(type='RotatedIoU3DLoss'), 34 | cls_loss=dict(type='mmdet.FocalLoss'), 35 | ) 36 | imvoxel_head = imvoxel_head.cuda() 37 | 38 | # fake input of head 39 | # (x, valid_preds) 40 | x = [ 41 | torch.randn(1, 32, 10, 10, 4).cuda(), 42 | torch.ones(1, 1, 10, 10, 4).cuda() 43 | ] 44 | 45 | # fake annotation 46 | num_gt_instance = 1 47 | packed_inputs = create_detector_inputs( 48 | with_points=False, 49 | with_img=True, 50 | img_size=(128, 128), 51 | num_gt_instance=num_gt_instance, 52 | with_pts_semantic_mask=False, 53 | with_pts_instance_mask=False) 54 | data_samples = [ 55 | sample.cuda() for sample in packed_inputs['data_samples'] 56 | ] 57 | 58 | losses = imvoxel_head.loss(x, data_samples) 59 | print(losses) 60 | self.assertGreaterEqual(losses['center_loss'], 0) 61 | self.assertGreaterEqual(losses['bbox_loss'], 0) 62 | self.assertGreaterEqual(losses['cls_loss'], 0) 63 | -------------------------------------------------------------------------------- /tests/test_models/test_detectors/test_3dssd.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | from mmengine import DefaultScope 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.testing import (create_detector_inputs, get_detector_cfg, 8 | setup_seed) 9 | 10 | 11 | class Test3DSSD(unittest.TestCase): 12 | 13 | def test_3dssd(self): 14 | import mmdet3d.models 15 | 16 | assert hasattr(mmdet3d.models, 'SSD3DNet') 17 | DefaultScope.get_instance('test_ssd3d', scope_name='mmdet3d') 18 | setup_seed(0) 19 | voxel_net_cfg = get_detector_cfg('3dssd/3dssd_4xb4_kitti-3d-car.py') 20 | model = MODELS.build(voxel_net_cfg) 21 | num_gt_instance = 3 22 | packed_inputs = create_detector_inputs( 23 | num_gt_instance=num_gt_instance, num_classes=1) 24 | 25 | if torch.cuda.is_available(): 26 | model = model.cuda() 27 | # test simple_test 28 | with torch.no_grad(): 29 | data = model.data_preprocessor(packed_inputs, True) 30 | torch.cuda.empty_cache() 31 | results = model.forward(**data, mode='predict') 32 | self.assertEqual(len(results), 1) 33 | self.assertIn('bboxes_3d', results[0].pred_instances_3d) 34 | self.assertIn('scores_3d', results[0].pred_instances_3d) 35 | self.assertIn('labels_3d', results[0].pred_instances_3d) 36 | 37 | losses = model.forward(**data, mode='loss') 38 | 39 | self.assertGreater(losses['centerness_loss'], 0) 40 | -------------------------------------------------------------------------------- /tests/test_models/test_detectors/test_center_point.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | from mmengine import DefaultScope 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.testing import (create_detector_inputs, get_detector_cfg, 8 | setup_seed) 9 | 10 | 11 | class TestCenterPoint(unittest.TestCase): 12 | 13 | def test_center_point(self): 14 | import mmdet3d.models 15 | 16 | assert hasattr(mmdet3d.models, 'CenterPoint') 17 | 18 | setup_seed(0) 19 | DefaultScope.get_instance('test_center_point', scope_name='mmdet3d') 20 | centerpoint_net_cfg = get_detector_cfg( 21 | 'centerpoint/centerpoint_voxel01_second_secfpn_8xb4-cyclic-20e_nus-3d.py' # noqa 22 | ) 23 | model = MODELS.build(centerpoint_net_cfg) 24 | num_gt_instance = 50 25 | packed_inputs = create_detector_inputs( 26 | with_img=True, num_gt_instance=num_gt_instance, points_feat_dim=5) 27 | 28 | for sample_id in range(len(packed_inputs['data_samples'])): 29 | det_sample = packed_inputs['data_samples'][sample_id] 30 | num_instances = len(det_sample.gt_instances_3d.bboxes_3d) 31 | bbox_3d_class = det_sample.gt_instances_3d.bboxes_3d.__class__ 32 | det_sample.gt_instances_3d.bboxes_3d = bbox_3d_class( 33 | torch.rand(num_instances, 9), box_dim=9) 34 | 35 | if torch.cuda.is_available(): 36 | 37 | model = model.cuda() 38 | # test simple_test 39 | 40 | data = model.data_preprocessor(packed_inputs, True) 41 | with torch.no_grad(): 42 | torch.cuda.empty_cache() 43 | losses = model.forward(**data, mode='loss') 44 | assert losses['task0.loss_heatmap'] >= 0 45 | assert losses['task0.loss_bbox'] >= 0 46 | assert losses['task1.loss_heatmap'] >= 0 47 | assert losses['task1.loss_bbox'] >= 0 48 | assert losses['task2.loss_heatmap'] >= 0 49 | assert losses['task2.loss_bbox'] >= 0 50 | assert losses['task3.loss_heatmap'] >= 0 51 | assert losses['task3.loss_bbox'] >= 0 52 | assert losses['task3.loss_bbox'] >= 0 53 | assert losses['task4.loss_bbox'] >= 0 54 | assert losses['task5.loss_heatmap'] >= 0 55 | assert losses['task5.loss_bbox'] >= 0 56 | 57 | with torch.no_grad(): 58 | results = model.forward(**data, mode='predict') 59 | self.assertEqual(len(results), 1) 60 | self.assertIn('bboxes_3d', results[0].pred_instances_3d) 61 | self.assertIn('scores_3d', results[0].pred_instances_3d) 62 | self.assertIn('labels_3d', results[0].pred_instances_3d) 63 | # TODO test_aug_test 64 | -------------------------------------------------------------------------------- /tests/test_models/test_detectors/test_fcaf3d.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | from mmengine import DefaultScope 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.testing import (create_detector_inputs, get_detector_cfg, 8 | setup_seed) 9 | 10 | 11 | class TestFCAF3d(unittest.TestCase): 12 | 13 | def test_fcaf3d(self): 14 | try: 15 | import MinkowskiEngine # noqa: F401 16 | except ImportError: 17 | return 18 | 19 | import mmdet3d.models 20 | assert hasattr(mmdet3d.models, 'MinkSingleStage3DDetector') 21 | DefaultScope.get_instance('test_fcaf3d', scope_name='mmdet3d') 22 | setup_seed(0) 23 | fcaf3d_net_cfg = get_detector_cfg( 24 | 'fcaf3d/fcaf3d_2xb8_scannet-3d-18class.py') 25 | model = MODELS.build(fcaf3d_net_cfg) 26 | num_gt_instance = 3 27 | packed_inputs = create_detector_inputs( 28 | num_gt_instance=num_gt_instance, 29 | num_classes=1, 30 | points_feat_dim=6, 31 | gt_bboxes_dim=6) 32 | 33 | if torch.cuda.is_available(): 34 | model = model.cuda() 35 | with torch.no_grad(): 36 | data = model.data_preprocessor(packed_inputs, False) 37 | torch.cuda.empty_cache() 38 | results = model.forward(**data, mode='predict') 39 | self.assertEqual(len(results), 1) 40 | self.assertIn('bboxes_3d', results[0].pred_instances_3d) 41 | self.assertIn('scores_3d', results[0].pred_instances_3d) 42 | self.assertIn('labels_3d', results[0].pred_instances_3d) 43 | 44 | losses = model.forward(**data, mode='loss') 45 | 46 | self.assertGreater(losses['center_loss'], 0) 47 | self.assertGreater(losses['bbox_loss'], 0) 48 | self.assertGreater(losses['cls_loss'], 0) 49 | -------------------------------------------------------------------------------- /tests/test_models/test_detectors/test_groupfree3d.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | from mmengine import DefaultScope 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.testing import (create_detector_inputs, get_detector_cfg, 8 | setup_seed) 9 | 10 | 11 | class TestGroupfree3d(unittest.TestCase): 12 | 13 | def test_groupfree3d(self): 14 | import mmdet3d.models 15 | 16 | assert hasattr(mmdet3d.models, 'GroupFree3DNet') 17 | DefaultScope.get_instance('test_groupfree3d', scope_name='mmdet3d') 18 | setup_seed(0) 19 | voxel_net_cfg = get_detector_cfg( 20 | 'groupfree3d/groupfree3d_head-L6-O256_4xb8_scannet-seg.py') 21 | model = MODELS.build(voxel_net_cfg) 22 | num_gt_instance = 5 23 | packed_inputs = create_detector_inputs( 24 | num_gt_instance=num_gt_instance, 25 | points_feat_dim=3, 26 | with_pts_semantic_mask=True, 27 | with_pts_instance_mask=True) 28 | 29 | if torch.cuda.is_available(): 30 | model = model.cuda() 31 | # test simple_test 32 | with torch.no_grad(): 33 | data = model.data_preprocessor(packed_inputs, True) 34 | torch.cuda.empty_cache() 35 | results = model.forward(**data, mode='predict') 36 | self.assertEqual(len(results), 1) 37 | self.assertIn('bboxes_3d', results[0].pred_instances_3d) 38 | self.assertIn('scores_3d', results[0].pred_instances_3d) 39 | self.assertIn('labels_3d', results[0].pred_instances_3d) 40 | 41 | # save the memory 42 | with torch.no_grad(): 43 | losses = model.forward(**data, mode='loss') 44 | 45 | self.assertGreater(losses['sampling_objectness_loss'], 0) 46 | self.assertGreater(losses['proposal.objectness_loss'], 0) 47 | self.assertGreater(losses['s0.objectness_loss'], 0) 48 | self.assertGreater(losses['s1.size_res_loss'], 0) 49 | self.assertGreater(losses['s4.size_class_loss'], 0) 50 | -------------------------------------------------------------------------------- /tests/test_models/test_detectors/test_h3dnet.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | from mmengine import DefaultScope 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.testing import (create_detector_inputs, get_detector_cfg, 8 | setup_seed) 9 | 10 | 11 | class TestH3D(unittest.TestCase): 12 | 13 | def test_h3dnet(self): 14 | import mmdet3d.models 15 | 16 | assert hasattr(mmdet3d.models, 'H3DNet') 17 | DefaultScope.get_instance('test_H3DNet', scope_name='mmdet3d') 18 | setup_seed(0) 19 | voxel_net_cfg = get_detector_cfg('h3dnet/h3dnet_8xb3_scannet-seg.py') 20 | model = MODELS.build(voxel_net_cfg) 21 | num_gt_instance = 5 22 | packed_inputs = create_detector_inputs( 23 | num_gt_instance=num_gt_instance, 24 | points_feat_dim=4, 25 | bboxes_3d_type='depth', 26 | with_pts_semantic_mask=True, 27 | with_pts_instance_mask=True) 28 | 29 | if torch.cuda.is_available(): 30 | model = model.cuda() 31 | # test simple_test 32 | with torch.no_grad(): 33 | data = model.data_preprocessor(packed_inputs, True) 34 | results = model.forward(**data, mode='predict') 35 | self.assertEqual(len(results), 1) 36 | self.assertIn('bboxes_3d', results[0].pred_instances_3d) 37 | self.assertIn('scores_3d', results[0].pred_instances_3d) 38 | self.assertIn('labels_3d', results[0].pred_instances_3d) 39 | 40 | # save the memory 41 | with torch.no_grad(): 42 | losses = model.forward(**data, mode='loss') 43 | 44 | self.assertGreater(losses['vote_loss'], 0) 45 | self.assertGreater(losses['objectness_loss'], 0) 46 | self.assertGreater(losses['center_loss'], 0) 47 | -------------------------------------------------------------------------------- /tests/test_models/test_detectors/test_mvxnet.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | from mmengine import DefaultScope 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.testing import (create_detector_inputs, get_detector_cfg, 8 | setup_seed) 9 | 10 | 11 | class TestMVXNet(unittest.TestCase): 12 | 13 | def test_mvxnet(self): 14 | import mmdet3d.models 15 | 16 | assert hasattr(mmdet3d.models, 'DynamicMVXFasterRCNN') 17 | 18 | setup_seed(0) 19 | DefaultScope.get_instance('test_mvxnet', scope_name='mmdet3d') 20 | mvx_net_cfg = get_detector_cfg( 21 | 'mvxnet/mvxnet_fpn_dv_second_secfpn_8xb2-80e_kitti-3d-3class.py' # noqa 22 | ) 23 | model = MODELS.build(mvx_net_cfg) 24 | num_gt_instance = 1 25 | packed_inputs = create_detector_inputs( 26 | with_img=False, num_gt_instance=num_gt_instance, points_feat_dim=4) 27 | 28 | if torch.cuda.is_available(): 29 | 30 | model = model.cuda() 31 | # test simple_test 32 | data = model.data_preprocessor(packed_inputs, True) 33 | # save the memory when do the unitest 34 | with torch.no_grad(): 35 | torch.cuda.empty_cache() 36 | losses = model.forward(**data, mode='loss') 37 | assert losses['loss_cls'][0] >= 0 38 | assert losses['loss_bbox'][0] >= 0 39 | assert losses['loss_dir'][0] >= 0 40 | 41 | with torch.no_grad(): 42 | results = model.forward(**data, mode='predict') 43 | self.assertEqual(len(results), 1) 44 | self.assertIn('bboxes_3d', results[0].pred_instances_3d) 45 | self.assertIn('scores_3d', results[0].pred_instances_3d) 46 | self.assertIn('labels_3d', results[0].pred_instances_3d) 47 | # TODO test_aug_test 48 | -------------------------------------------------------------------------------- /tests/test_models/test_detectors/test_parta2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | from mmengine import DefaultScope 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.testing import (create_detector_inputs, get_detector_cfg, 8 | setup_seed) 9 | 10 | 11 | class TestPartA2(unittest.TestCase): 12 | 13 | def test_parta2(self): 14 | import mmdet3d.models 15 | 16 | assert hasattr(mmdet3d.models, 'PartA2') 17 | DefaultScope.get_instance('test_parta2', scope_name='mmdet3d') 18 | setup_seed(0) 19 | parta2_cfg = get_detector_cfg( 20 | 'parta2/parta2_hv_secfpn_8xb2-cyclic-80e_kitti-3d-3class.py') 21 | model = MODELS.build(parta2_cfg) 22 | num_gt_instance = 2 23 | packed_inputs = create_detector_inputs(num_gt_instance=num_gt_instance) 24 | 25 | # TODO: Support aug data test 26 | # aug_packed_inputs = [ 27 | # create_detector_inputs(num_gt_instance=num_gt_instance), 28 | # create_detector_inputs(num_gt_instance=num_gt_instance + 1) 29 | # ] 30 | # test_aug_test 31 | # metainfo = { 32 | # 'pcd_scale_factor': 1, 33 | # 'pcd_horizontal_flip': 1, 34 | # 'pcd_vertical_flip': 1, 35 | # 'box_type_3d': LiDARInstance3DBoxes 36 | # } 37 | # for item in aug_packed_inputs: 38 | # for batch_id in len(item['data_samples']): 39 | # item['data_samples'][batch_id].set_metainfo(metainfo) 40 | 41 | if torch.cuda.is_available(): 42 | model = model.cuda() 43 | # test simple_test 44 | with torch.no_grad(): 45 | data = model.data_preprocessor(packed_inputs, True) 46 | torch.cuda.empty_cache() 47 | results = model.forward(**data, mode='predict') 48 | self.assertEqual(len(results), 1) 49 | self.assertIn('bboxes_3d', results[0].pred_instances_3d) 50 | self.assertIn('scores_3d', results[0].pred_instances_3d) 51 | self.assertIn('labels_3d', results[0].pred_instances_3d) 52 | 53 | # save the memory 54 | with torch.no_grad(): 55 | losses = model.forward(**data, mode='loss') 56 | torch.cuda.empty_cache() 57 | self.assertGreater(losses['loss_rpn_cls'][0], 0) 58 | self.assertGreaterEqual(losses['loss_rpn_bbox'][0], 0) 59 | self.assertGreater(losses['loss_seg'], 0) 60 | self.assertGreater(losses['loss_part'], 0) 61 | self.assertGreater(losses['loss_cls'], 0) 62 | -------------------------------------------------------------------------------- /tests/test_models/test_detectors/test_pointrcnn.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | from mmengine import DefaultScope 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.testing import (create_detector_inputs, get_detector_cfg, 8 | setup_seed) 9 | 10 | 11 | class TestPointRCNN(unittest.TestCase): 12 | 13 | def test_pointrcnn(self): 14 | import mmdet3d.models 15 | 16 | assert hasattr(mmdet3d.models, 'PointRCNN') 17 | DefaultScope.get_instance('test_pointrcnn', scope_name='mmdet3d') 18 | setup_seed(0) 19 | pointrcnn_cfg = get_detector_cfg( 20 | 'point_rcnn/point-rcnn_8xb2_kitti-3d-3class.py') 21 | model = MODELS.build(pointrcnn_cfg) 22 | num_gt_instance = 2 23 | packed_inputs = create_detector_inputs( 24 | num_points=10101, num_gt_instance=num_gt_instance) 25 | 26 | if torch.cuda.is_available(): 27 | model = model.cuda() 28 | # test simple_test 29 | with torch.no_grad(): 30 | data = model.data_preprocessor(packed_inputs, True) 31 | torch.cuda.empty_cache() 32 | results = model.forward(**data, mode='predict') 33 | self.assertEqual(len(results), 1) 34 | self.assertIn('bboxes_3d', results[0].pred_instances_3d) 35 | self.assertIn('scores_3d', results[0].pred_instances_3d) 36 | self.assertIn('labels_3d', results[0].pred_instances_3d) 37 | 38 | # save the memory 39 | with torch.no_grad(): 40 | losses = model.forward(**data, mode='loss') 41 | torch.cuda.empty_cache() 42 | self.assertGreaterEqual(losses['rpn_bbox_loss'], 0) 43 | self.assertGreaterEqual(losses['rpn_semantic_loss'], 0) 44 | self.assertGreaterEqual(losses['loss_cls'], 0) 45 | self.assertGreaterEqual(losses['loss_bbox'], 0) 46 | self.assertGreaterEqual(losses['loss_corner'], 0) 47 | -------------------------------------------------------------------------------- /tests/test_models/test_detectors/test_pvrcnn.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | from mmengine import DefaultScope 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.testing import (create_detector_inputs, get_detector_cfg, 8 | setup_seed) 9 | 10 | 11 | class TestPVRCNN(unittest.TestCase): 12 | 13 | def test_pvrcnn(self): 14 | import mmdet3d.models 15 | 16 | assert hasattr(mmdet3d.models, 'PointVoxelRCNN') 17 | DefaultScope.get_instance('test_pvrcnn', scope_name='mmdet3d') 18 | setup_seed(0) 19 | pvrcnn_cfg = get_detector_cfg( 20 | 'pv_rcnn/pv_rcnn_8xb2-80e_kitti-3d-3class.py') 21 | model = MODELS.build(pvrcnn_cfg) 22 | num_gt_instance = 2 23 | packed_inputs = create_detector_inputs(num_gt_instance=num_gt_instance) 24 | 25 | # TODO: Support aug data test 26 | # aug_packed_inputs = [ 27 | # create_detector_inputs(num_gt_instance=num_gt_instance), 28 | # create_detector_inputs(num_gt_instance=num_gt_instance + 1) 29 | # ] 30 | # test_aug_test 31 | # metainfo = { 32 | # 'pcd_scale_factor': 1, 33 | # 'pcd_horizontal_flip': 1, 34 | # 'pcd_vertical_flip': 1, 35 | # 'box_type_3d': LiDARInstance3DBoxes 36 | # } 37 | # for item in aug_packed_inputs: 38 | # for batch_id in len(item['data_samples']): 39 | # item['data_samples'][batch_id].set_metainfo(metainfo) 40 | 41 | if torch.cuda.is_available(): 42 | model = model.cuda() 43 | # test simple_test 44 | with torch.no_grad(): 45 | data = model.data_preprocessor(packed_inputs, True) 46 | torch.cuda.empty_cache() 47 | results = model.forward(**data, mode='predict') 48 | self.assertEqual(len(results), 1) 49 | self.assertIn('bboxes_3d', results[0].pred_instances_3d) 50 | self.assertIn('scores_3d', results[0].pred_instances_3d) 51 | self.assertIn('labels_3d', results[0].pred_instances_3d) 52 | 53 | # save the memory 54 | with torch.no_grad(): 55 | losses = model.forward(**data, mode='loss') 56 | torch.cuda.empty_cache() 57 | self.assertGreater(losses['loss_rpn_cls'][0], 0) 58 | self.assertGreaterEqual(losses['loss_rpn_bbox'][0], 0) 59 | self.assertGreaterEqual(losses['loss_rpn_dir'][0], 0) 60 | self.assertGreater(losses['loss_semantic'], 0) 61 | self.assertGreaterEqual(losses['loss_bbox'], 0) 62 | self.assertGreaterEqual(losses['loss_cls'], 0) 63 | self.assertGreaterEqual(losses['loss_corner'], 0) 64 | -------------------------------------------------------------------------------- /tests/test_models/test_detectors/test_sassd.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | from mmengine import DefaultScope 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.testing import (create_detector_inputs, get_detector_cfg, 8 | setup_seed) 9 | 10 | 11 | class TestSDSSD(unittest.TestCase): 12 | 13 | def test_3dssd(self): 14 | import mmdet3d.models 15 | 16 | assert hasattr(mmdet3d.models, 'SASSD') 17 | DefaultScope.get_instance('test_sassd', scope_name='mmdet3d') 18 | setup_seed(0) 19 | voxel_net_cfg = get_detector_cfg( 20 | 'sassd/sassd_8xb6-80e_kitti-3d-3class.py') 21 | model = MODELS.build(voxel_net_cfg) 22 | num_gt_instance = 3 23 | packed_inputs = create_detector_inputs( 24 | num_gt_instance=num_gt_instance, num_classes=1) 25 | 26 | if torch.cuda.is_available(): 27 | model = model.cuda() 28 | # test simple_test 29 | with torch.no_grad(): 30 | data = model.data_preprocessor(packed_inputs, True) 31 | torch.cuda.empty_cache() 32 | results = model.forward(**data, mode='predict') 33 | self.assertEqual(len(results), 1) 34 | self.assertIn('bboxes_3d', results[0].pred_instances_3d) 35 | self.assertIn('scores_3d', results[0].pred_instances_3d) 36 | self.assertIn('labels_3d', results[0].pred_instances_3d) 37 | 38 | losses = model.forward(**data, mode='loss') 39 | self.assertGreaterEqual(losses['loss_dir'][0], 0) 40 | self.assertGreaterEqual(losses['loss_bbox'][0], 0) 41 | self.assertGreaterEqual(losses['loss_cls'][0], 0) 42 | self.assertGreater(losses['aux_loss_cls'][0], 0) 43 | self.assertGreater(losses['aux_loss_reg'][0], 0) 44 | -------------------------------------------------------------------------------- /tests/test_models/test_layers/test_dgcnn_modules/test_dgcnn_fa_module.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | 5 | 6 | def test_dgcnn_fa_module(): 7 | if not torch.cuda.is_available(): 8 | pytest.skip() 9 | from mmdet3d.models.layers import DGCNNFAModule 10 | 11 | self = DGCNNFAModule(mlp_channels=[24, 16]).cuda() 12 | assert self.mlps.layer0.conv.in_channels == 24 13 | assert self.mlps.layer0.conv.out_channels == 16 14 | 15 | points = [torch.rand(1, 200, 12).float().cuda() for _ in range(3)] 16 | 17 | fa_points = self(points) 18 | assert fa_points.shape == torch.Size([1, 200, 40]) 19 | -------------------------------------------------------------------------------- /tests/test_models/test_layers/test_dgcnn_modules/test_dgcnn_fp_module.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import numpy as np 3 | import pytest 4 | import torch 5 | 6 | 7 | def test_dgcnn_fp_module(): 8 | if not torch.cuda.is_available(): 9 | pytest.skip() 10 | from mmdet3d.models.layers import DGCNNFPModule 11 | 12 | self = DGCNNFPModule(mlp_channels=[24, 16]).cuda() 13 | assert self.mlps.layer0.conv.in_channels == 24 14 | assert self.mlps.layer0.conv.out_channels == 16 15 | 16 | xyz = np.fromfile('tests/data/sunrgbd/points/000001.bin', 17 | np.float32).reshape((-1, 6)) 18 | 19 | # (B, N, 3) 20 | xyz = torch.from_numpy(xyz).view(1, -1, 3).cuda() 21 | points = xyz.repeat([1, 1, 8]).cuda() 22 | 23 | fp_points = self(points) 24 | assert fp_points.shape == torch.Size([1, 200, 16]) 25 | -------------------------------------------------------------------------------- /tests/test_models/test_layers/test_dgcnn_modules/test_dgcnn_gf_module.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import numpy as np 3 | import pytest 4 | import torch 5 | 6 | 7 | def test_dgcnn_gf_module(): 8 | if not torch.cuda.is_available(): 9 | pytest.skip() 10 | from mmdet3d.models.layers import DGCNNGFModule 11 | 12 | self = DGCNNGFModule( 13 | mlp_channels=[18, 64, 64], 14 | num_sample=20, 15 | knn_mode='D-KNN', 16 | radius=None, 17 | norm_cfg=dict(type='BN2d'), 18 | act_cfg=dict(type='ReLU'), 19 | pool_mode='max').cuda() 20 | 21 | assert self.mlps[0].layer0.conv.in_channels == 18 22 | assert self.mlps[0].layer0.conv.out_channels == 64 23 | 24 | xyz = np.fromfile('tests/data/sunrgbd/points/000001.bin', np.float32) 25 | 26 | # (B, N, C) 27 | xyz = torch.from_numpy(xyz).view(1, -1, 3).cuda() 28 | points = xyz.repeat([1, 1, 3]) 29 | 30 | # test forward 31 | new_points = self(points) 32 | 33 | assert new_points.shape == torch.Size([1, 200, 64]) 34 | 35 | # test F-KNN mod 36 | self = DGCNNGFModule( 37 | mlp_channels=[6, 64, 64], 38 | num_sample=20, 39 | knn_mode='F-KNN', 40 | radius=None, 41 | norm_cfg=dict(type='BN2d'), 42 | act_cfg=dict(type='ReLU'), 43 | pool_mode='max').cuda() 44 | 45 | # test forward 46 | new_points = self(xyz) 47 | assert new_points.shape == torch.Size([1, 200, 64]) 48 | 49 | # test ball query 50 | self = DGCNNGFModule( 51 | mlp_channels=[6, 64, 64], 52 | num_sample=20, 53 | knn_mode='F-KNN', 54 | radius=0.2, 55 | norm_cfg=dict(type='BN2d'), 56 | act_cfg=dict(type='ReLU'), 57 | pool_mode='max').cuda() 58 | -------------------------------------------------------------------------------- /tests/test_models/test_layers/test_fusion_layers/test_point_fusion.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | """Tests the core function of point fusion. 3 | 4 | CommandLine: 5 | pytest tests/test_models/test_fusion/test_point_fusion.py 6 | """ 7 | 8 | import torch 9 | 10 | from mmdet3d.models.layers.fusion_layers import PointFusion 11 | 12 | 13 | def test_sample_single(): 14 | # this function makes sure the rewriting of 3d coords transformation 15 | # in point fusion does not change the original behaviour 16 | lidar2img = torch.tensor( 17 | [[6.0294e+02, -7.0791e+02, -1.2275e+01, -1.7094e+02], 18 | [1.7678e+02, 8.8088e+00, -7.0794e+02, -1.0257e+02], 19 | [9.9998e-01, -1.5283e-03, -5.2907e-03, -3.2757e-01], 20 | [0.0000e+00, 0.0000e+00, 0.0000e+00, 1.0000e+00]]) 21 | 22 | # all use default 23 | img_meta = { 24 | 'transformation_3d_flow': ['R', 'S', 'T', 'HF'], 25 | 'input_shape': [370, 1224], 26 | 'img_shape': [370, 1224], 27 | 'lidar2img': lidar2img, 28 | } 29 | 30 | # dummy parameters 31 | fuse = PointFusion(1, 1, 1, 1) 32 | img_feat = torch.arange(370 * 1224)[None, ...].view( 33 | 370, 1224)[None, None, ...].float() / (370 * 1224) 34 | pts = torch.tensor([[8.356, -4.312, -0.445], [11.777, -6.724, -0.564], 35 | [6.453, 2.53, -1.612], [6.227, -3.839, -0.563]]) 36 | out = fuse.sample_single(img_feat, pts, img_meta) 37 | 38 | expected_tensor = torch.tensor( 39 | [0.5560822, 0.5476625, 0.9687978, 0.6241757]) 40 | assert torch.allclose(expected_tensor, out, 1e-4) 41 | 42 | pcd_rotation = torch.tensor([[8.660254e-01, 0.5, 0], 43 | [-0.5, 8.660254e-01, 0], [0, 0, 1.0e+00]]) 44 | pcd_scale_factor = 1.111 45 | pcd_trans = torch.tensor([1.0, -1.0, 0.5]) 46 | pts = pts @ pcd_rotation 47 | pts *= pcd_scale_factor 48 | pts += pcd_trans 49 | pts[:, 1] = -pts[:, 1] 50 | 51 | # not use default 52 | img_meta.update({ 53 | 'pcd_scale_factor': pcd_scale_factor, 54 | 'pcd_rotation': pcd_rotation, 55 | 'pcd_trans': pcd_trans, 56 | 'pcd_horizontal_flip': True 57 | }) 58 | out = fuse.sample_single(img_feat, pts, img_meta) 59 | expected_tensor = torch.tensor( 60 | [0.5560822, 0.5476625, 0.9687978, 0.6241757]) 61 | assert torch.allclose(expected_tensor, out, 1e-4) 62 | -------------------------------------------------------------------------------- /tests/test_models/test_layers/test_paconv/test_paconv_ops.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | 5 | from mmdet3d.models.layers import PAConv, PAConvCUDA 6 | 7 | 8 | def test_paconv(): 9 | B = 2 10 | in_channels = 6 11 | out_channels = 12 12 | npoint = 4 13 | K = 3 14 | num_kernels = 4 15 | points_xyz = torch.randn(B, 3, npoint, K) 16 | features = torch.randn(B, in_channels, npoint, K) 17 | 18 | paconv = PAConv(in_channels, out_channels, num_kernels) 19 | assert paconv.weight_bank.shape == torch.Size( 20 | [in_channels * 2, out_channels * num_kernels]) 21 | 22 | with torch.no_grad(): 23 | new_features, _ = paconv((features, points_xyz)) 24 | 25 | assert new_features.shape == torch.Size([B, out_channels, npoint, K]) 26 | 27 | 28 | def test_paconv_cuda(): 29 | if not torch.cuda.is_available(): 30 | pytest.skip() 31 | B = 2 32 | in_channels = 6 33 | out_channels = 12 34 | N = 32 35 | npoint = 4 36 | K = 3 37 | num_kernels = 4 38 | points_xyz = torch.randn(B, 3, npoint, K).float().cuda() 39 | features = torch.randn(B, in_channels, N).float().cuda() 40 | points_idx = torch.randint(0, N, (B, npoint, K)).long().cuda() 41 | 42 | paconv = PAConvCUDA(in_channels, out_channels, num_kernels).cuda() 43 | assert paconv.weight_bank.shape == torch.Size( 44 | [in_channels * 2, out_channels * num_kernels]) 45 | 46 | with torch.no_grad(): 47 | new_features, _, _ = paconv((features, points_xyz, points_idx)) 48 | 49 | assert new_features.shape == torch.Size([B, out_channels, npoint, K]) 50 | -------------------------------------------------------------------------------- /tests/test_models/test_layers/test_pointnet_modules/test_point_fp_module.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import numpy as np 3 | import pytest 4 | import torch 5 | 6 | 7 | def test_pointnet_fp_module(): 8 | if not torch.cuda.is_available(): 9 | pytest.skip() 10 | from mmdet3d.models.layers import PointFPModule 11 | 12 | self = PointFPModule(mlp_channels=[24, 16]).cuda() 13 | assert self.mlps.layer0.conv.in_channels == 24 14 | assert self.mlps.layer0.conv.out_channels == 16 15 | 16 | xyz = np.fromfile('tests/data/sunrgbd/points/000001.bin', 17 | np.float32).reshape((-1, 6)) 18 | 19 | # (B, N, 3) 20 | xyz1 = torch.from_numpy(xyz[0::2, :3]).view(1, -1, 3).cuda() 21 | # (B, C1, N) 22 | features1 = xyz1.repeat([1, 1, 4]).transpose(1, 2).contiguous().cuda() 23 | 24 | # (B, M, 3) 25 | xyz2 = torch.from_numpy(xyz[1::3, :3]).view(1, -1, 3).cuda() 26 | # (B, C2, N) 27 | features2 = xyz2.repeat([1, 1, 4]).transpose(1, 2).contiguous().cuda() 28 | 29 | fp_features = self(xyz1, xyz2, features1, features2) 30 | assert fp_features.shape == torch.Size([1, 16, 50]) 31 | -------------------------------------------------------------------------------- /tests/test_models/test_layers/test_torchsparse/test_torchsparse_module.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | 5 | from mmdet3d.models.layers.torchsparse import IS_TORCHSPARSE_AVAILABLE 6 | 7 | if IS_TORCHSPARSE_AVAILABLE: 8 | from torchsparse import SparseTensor 9 | 10 | from mmdet3d.models.layers.torchsparse_block import ( 11 | TorchSparseConvModule, TorchSparseResidualBlock) 12 | else: 13 | pytest.skip('test requires Torchsparse', allow_module_level=True) 14 | 15 | 16 | def test_TorchsparseConvModule(): 17 | if not torch.cuda.is_available(): 18 | pytest.skip('test requires GPU and torch+cuda') 19 | voxel_features = torch.tensor( 20 | [[6.56126, 0.9648336, -1.7339306, 0.315], 21 | [6.8162713, -2.480431, -1.3616394, 0.36], 22 | [11.643568, -4.744306, -1.3580885, 0.16], 23 | [23.482342, 6.5036807, 0.5806964, 0.35]], 24 | dtype=torch.float32).cuda() # n, point_features 25 | coordinates = torch.tensor( 26 | [[12, 819, 131, 0], [16, 750, 136, 0], [16, 705, 232, 1], 27 | [35, 930, 469, 1]], 28 | dtype=torch.int32).cuda() # n, 4(ind_x, ind_y, ind_z, batch) 29 | 30 | # test 31 | input_sp_tensor = SparseTensor(voxel_features, coordinates) 32 | 33 | self = TorchSparseConvModule(4, 4, kernel_size=2, stride=2).cuda() 34 | 35 | out_features = self(input_sp_tensor) 36 | assert out_features.F.shape == torch.Size([4, 4]) 37 | 38 | 39 | def test_TorchsparseResidualBlock(): 40 | if not torch.cuda.is_available(): 41 | pytest.skip('test requires GPU and torch+cuda') 42 | voxel_features = torch.tensor( 43 | [[6.56126, 0.9648336, -1.7339306, 0.315], 44 | [6.8162713, -2.480431, -1.3616394, 0.36], 45 | [11.643568, -4.744306, -1.3580885, 0.16], 46 | [23.482342, 6.5036807, 0.5806964, 0.35]], 47 | dtype=torch.float32).cuda() # n, point_features 48 | coordinates = torch.tensor( 49 | [[12, 819, 131, 0], [16, 750, 136, 0], [16, 705, 232, 1], 50 | [35, 930, 469, 1]], 51 | dtype=torch.int32).cuda() # n, 4(ind_x, ind_y, ind_z, batch) 52 | 53 | # test 54 | input_sp_tensor = SparseTensor(voxel_features, coordinates) 55 | 56 | sparse_block0 = TorchSparseResidualBlock(4, 16, kernel_size=3).cuda() 57 | 58 | # test forward 59 | out_features = sparse_block0(input_sp_tensor) 60 | assert out_features.F.shape == torch.Size([4, 16]) 61 | -------------------------------------------------------------------------------- /tests/test_models/test_layers/test_vote_module.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import torch 3 | 4 | 5 | def test_vote_module(): 6 | from mmdet3d.models.layers import VoteModule 7 | 8 | vote_loss = dict( 9 | type='ChamferDistance', 10 | mode='l1', 11 | reduction='none', 12 | loss_dst_weight=10.0) 13 | self = VoteModule(vote_per_seed=3, in_channels=8, vote_loss=vote_loss) 14 | 15 | seed_xyz = torch.rand([2, 64, 3], dtype=torch.float32) # (b, npoints, 3) 16 | seed_features = torch.rand( 17 | [2, 8, 64], dtype=torch.float32) # (b, in_channels, npoints) 18 | 19 | # test forward 20 | vote_xyz, vote_features, vote_offset = self(seed_xyz, seed_features) 21 | assert vote_xyz.shape == torch.Size([2, 192, 3]) 22 | assert vote_features.shape == torch.Size([2, 8, 192]) 23 | assert vote_offset.shape == torch.Size([2, 3, 192]) 24 | 25 | # test clip offset and without feature residual 26 | self = VoteModule( 27 | vote_per_seed=1, 28 | in_channels=8, 29 | num_points=32, 30 | with_res_feat=False, 31 | vote_xyz_range=(2.0, 2.0, 2.0)) 32 | 33 | vote_xyz, vote_features, vote_offset = self(seed_xyz, seed_features) 34 | assert vote_xyz.shape == torch.Size([2, 32, 3]) 35 | assert vote_features.shape == torch.Size([2, 8, 32]) 36 | assert vote_offset.shape == torch.Size([2, 3, 32]) 37 | assert torch.allclose(seed_features[..., :32], vote_features) 38 | assert vote_offset.max() <= 2.0 39 | assert vote_offset.min() >= -2.0 40 | -------------------------------------------------------------------------------- /tests/test_models/test_losses/test_multibin_loss.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | 5 | from mmdet3d.registry import MODELS 6 | 7 | 8 | def test_multibin_loss(): 9 | from mmdet3d.models.losses import MultiBinLoss 10 | 11 | # reduction should be in ['none', 'mean', 'sum'] 12 | with pytest.raises(AssertionError): 13 | multibin_loss = MultiBinLoss(reduction='l2') 14 | 15 | pred = torch.tensor([[ 16 | 0.81, 0.32, 0.78, 0.52, 0.24, 0.12, 0.32, 0.11, 1.20, 1.30, 0.20, 0.11, 17 | 0.12, 0.11, 0.23, 0.31 18 | ], 19 | [ 20 | 0.02, 0.19, 0.78, 0.22, 0.31, 0.12, 0.22, 0.11, 21 | 1.20, 1.30, 0.45, 0.51, 0.12, 0.11, 0.13, 0.61 22 | ]]) 23 | target = torch.tensor([[1, 1, 0, 0, 2.14, 3.12, 0.68, -2.15], 24 | [1, 1, 0, 0, 3.12, 3.12, 2.34, 1.23]]) 25 | multibin_loss_cfg = dict( 26 | type='MultiBinLoss', reduction='none', loss_weight=1.0) 27 | multibin_loss = MODELS.build(multibin_loss_cfg) 28 | output_multibin_loss = multibin_loss(pred, target, num_dir_bins=4) 29 | expected_multibin_loss = torch.tensor(2.1120) 30 | assert torch.allclose( 31 | output_multibin_loss, expected_multibin_loss, atol=1e-4) 32 | -------------------------------------------------------------------------------- /tests/test_models/test_losses/test_paconv_regularization_loss.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import random 3 | 4 | import numpy as np 5 | import pytest 6 | import torch 7 | from torch import nn as nn 8 | 9 | 10 | def set_random_seed(seed, deterministic=False): 11 | """Set random seed. 12 | 13 | Args: 14 | seed (int): Seed to be used. 15 | deterministic (bool): Whether to set the deterministic option for 16 | CUDNN backend, i.e., set `torch.backends.cudnn.deterministic` 17 | to True and `torch.backends.cudnn.benchmark` to False. 18 | Default: False. 19 | """ 20 | random.seed(seed) 21 | np.random.seed(seed) 22 | torch.manual_seed(seed) 23 | torch.cuda.manual_seed_all(seed) 24 | if deterministic: 25 | torch.backends.cudnn.deterministic = True 26 | torch.backends.cudnn.benchmark = False 27 | 28 | 29 | def test_paconv_regularization_loss(): 30 | from mmdet3d.models.layers import PAConv, PAConvCUDA 31 | from mmdet3d.models.losses import PAConvRegularizationLoss 32 | 33 | class ToyModel(nn.Module): 34 | 35 | def __init__(self): 36 | super(ToyModel, self).__init__() 37 | 38 | self.paconvs = nn.ModuleList() 39 | self.paconvs.append(PAConv(8, 16, 8)) 40 | self.paconvs.append(PAConv(8, 16, 8, kernel_input='identity')) 41 | self.paconvs.append(PAConvCUDA(8, 16, 8)) 42 | 43 | self.conv1 = nn.Conv1d(3, 8, 1) 44 | 45 | set_random_seed(0, True) 46 | model = ToyModel() 47 | 48 | # reduction should be in ['none', 'mean', 'sum'] 49 | with pytest.raises(AssertionError): 50 | paconv_corr_loss = PAConvRegularizationLoss(reduction='l2') 51 | 52 | paconv_corr_loss = PAConvRegularizationLoss(reduction='mean') 53 | mean_corr_loss = paconv_corr_loss(model.modules()) 54 | assert mean_corr_loss >= 0 55 | assert mean_corr_loss.requires_grad 56 | 57 | sum_corr_loss = paconv_corr_loss(model.modules(), reduction_override='sum') 58 | assert torch.allclose(sum_corr_loss, mean_corr_loss * 3) 59 | 60 | none_corr_loss = paconv_corr_loss( 61 | model.modules(), reduction_override='none') 62 | assert none_corr_loss.shape[0] == 3 63 | assert torch.allclose(none_corr_loss.mean(), mean_corr_loss) 64 | -------------------------------------------------------------------------------- /tests/test_models/test_losses/test_rotated_iou_loss.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | 3 | import numpy as np 4 | import torch 5 | 6 | from mmdet3d.models.losses import RotatedIoU3DLoss 7 | 8 | 9 | def test_rotated_iou_3d_loss(): 10 | 11 | if not torch.cuda.is_available(): 12 | return 13 | 14 | boxes1 = torch.tensor([[.5, .5, .5, 1., 1., 1., .0], 15 | [.5, .5, .5, 1., 1., 1., .0], 16 | [.5, .5, .5, 1., 1., 1., .0], 17 | [.5, .5, .5, 1., 1., 1., .0], 18 | [.5, .5, .5, 1., 1., 1., .0]]).cuda() 19 | boxes2 = torch.tensor([[.5, .5, .5, 1., 1., 1., .0], 20 | [.5, .5, .5, 1., 1., 2., np.pi / 2], 21 | [.5, .5, .5, 1., 1., 1., np.pi / 4], 22 | [1., 1., 1., 1., 1., 1., .0], 23 | [-1.5, -1.5, -1.5, 2.5, 2.5, 2.5, .0]]).cuda() 24 | 25 | expect_ious = 1 - torch.tensor([[1., .5, .7071, 1 / 15, .0]]).cuda() 26 | ious = RotatedIoU3DLoss(reduction='none')(boxes1, boxes2) 27 | assert torch.allclose(ious, expect_ious, atol=1e-4) 28 | -------------------------------------------------------------------------------- /tests/test_models/test_losses/test_uncertain_smooth_l1_loss.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | 5 | from mmdet3d.registry import MODELS 6 | 7 | 8 | def test_uncertain_smooth_l1_loss(): 9 | from mmdet3d.models.losses import UncertainL1Loss, UncertainSmoothL1Loss 10 | 11 | # reduction should be in ['none', 'mean', 'sum'] 12 | with pytest.raises(AssertionError): 13 | uncertain_l1_loss = UncertainL1Loss(reduction='l2') 14 | with pytest.raises(AssertionError): 15 | uncertain_smooth_l1_loss = UncertainSmoothL1Loss(reduction='l2') 16 | 17 | pred = torch.tensor([1.5783, 0.5972, 1.4821, 0.9488]) 18 | target = torch.tensor([1.0813, -0.3466, -1.1404, -0.9665]) 19 | sigma = torch.tensor([-1.0053, 0.4710, -1.7784, -0.8603]) 20 | 21 | # test uncertain l1 loss 22 | uncertain_l1_loss_cfg = dict( 23 | type='UncertainL1Loss', alpha=1.0, reduction='mean', loss_weight=1.0) 24 | uncertain_l1_loss = MODELS.build(uncertain_l1_loss_cfg) 25 | mean_l1_loss = uncertain_l1_loss(pred, target, sigma) 26 | expected_l1_loss = torch.tensor(4.7069) 27 | assert torch.allclose(mean_l1_loss, expected_l1_loss, atol=1e-4) 28 | 29 | # test uncertain smooth l1 loss 30 | uncertain_smooth_l1_loss_cfg = dict( 31 | type='UncertainSmoothL1Loss', 32 | alpha=1.0, 33 | beta=0.5, 34 | reduction='mean', 35 | loss_weight=1.0) 36 | uncertain_smooth_l1_loss = MODELS.build(uncertain_smooth_l1_loss_cfg) 37 | mean_smooth_l1_loss = uncertain_smooth_l1_loss(pred, target, sigma) 38 | expected_smooth_l1_loss = torch.tensor(3.9795) 39 | assert torch.allclose( 40 | mean_smooth_l1_loss, expected_smooth_l1_loss, atol=1e-4) 41 | -------------------------------------------------------------------------------- /tests/test_models/test_middle_encoders/test_sparse_encoders.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | 5 | from mmdet3d.registry import MODELS 6 | 7 | 8 | def test_sparse_encoder(): 9 | if not torch.cuda.is_available(): 10 | pytest.skip('test requires GPU and torch+cuda') 11 | sparse_encoder_cfg = dict( 12 | type='SparseEncoder', 13 | in_channels=5, 14 | sparse_shape=[40, 1024, 1024], 15 | order=('conv', 'norm', 'act'), 16 | encoder_channels=((16, 16, 32), (32, 32, 64), (64, 64, 128), (128, 17 | 128)), 18 | encoder_paddings=((1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 19 | 1)), 20 | block_type='basicblock') 21 | 22 | sparse_encoder = MODELS.build(sparse_encoder_cfg).cuda() 23 | voxel_features = torch.rand([207842, 5]).cuda() 24 | coors = torch.randint(0, 4, [207842, 4]).cuda() 25 | 26 | ret = sparse_encoder(voxel_features, coors, 4) 27 | assert ret.shape == torch.Size([4, 256, 128, 128]) 28 | 29 | 30 | def test_sparse_encoder_for_ssd(): 31 | if not torch.cuda.is_available(): 32 | pytest.skip('test requires GPU and torch+cuda') 33 | sparse_encoder_for_ssd_cfg = dict( 34 | type='SparseEncoderSASSD', 35 | in_channels=5, 36 | sparse_shape=[40, 1024, 1024], 37 | order=('conv', 'norm', 'act'), 38 | encoder_channels=((16, 16, 32), (32, 32, 64), (64, 64, 128), (128, 39 | 128)), 40 | encoder_paddings=((1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 41 | 1)), 42 | block_type='basicblock') 43 | 44 | sparse_encoder = MODELS.build(sparse_encoder_for_ssd_cfg).cuda() 45 | voxel_features = torch.rand([207842, 5]).cuda() 46 | coors = torch.randint(0, 4, [207842, 4]).cuda() 47 | 48 | ret, _ = sparse_encoder(voxel_features, coors, 4, True) 49 | assert ret.shape == torch.Size([4, 256, 128, 128]) 50 | -------------------------------------------------------------------------------- /tests/test_models/test_middle_encoders/test_sparse_unet.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | 5 | from mmdet3d.models.layers import SparseBasicBlock 6 | from mmdet3d.models.layers.spconv import IS_SPCONV2_AVAILABLE 7 | 8 | if IS_SPCONV2_AVAILABLE: 9 | from spconv.pytorch import SparseConv3d, SparseInverseConv3d, SubMConv3d 10 | else: 11 | from mmcv.ops import SparseConv3d, SparseInverseConv3d, SubMConv3d 12 | 13 | 14 | def test_SparseUNet(): 15 | if not torch.cuda.is_available(): 16 | pytest.skip('test requires GPU and torch+cuda') 17 | from mmdet3d.models.middle_encoders.sparse_unet import SparseUNet 18 | self = SparseUNet(in_channels=4, sparse_shape=[41, 1600, 1408]).cuda() 19 | 20 | # test encoder layers 21 | assert len(self.encoder_layers) == 4 22 | assert self.encoder_layers.encoder_layer1[0][0].in_channels == 16 23 | assert self.encoder_layers.encoder_layer1[0][0].out_channels == 16 24 | assert isinstance(self.encoder_layers.encoder_layer1[0][0], SubMConv3d) 25 | assert isinstance(self.encoder_layers.encoder_layer1[0][1], 26 | torch.nn.modules.batchnorm.BatchNorm1d) 27 | assert isinstance(self.encoder_layers.encoder_layer1[0][2], 28 | torch.nn.modules.activation.ReLU) 29 | assert self.encoder_layers.encoder_layer4[0][0].in_channels == 64 30 | assert self.encoder_layers.encoder_layer4[0][0].out_channels == 64 31 | assert isinstance(self.encoder_layers.encoder_layer4[0][0], SparseConv3d) 32 | assert isinstance(self.encoder_layers.encoder_layer4[2][0], SubMConv3d) 33 | 34 | # test decoder layers 35 | assert isinstance(self.lateral_layer1, SparseBasicBlock) 36 | assert isinstance(self.merge_layer1[0], SubMConv3d) 37 | assert isinstance(self.upsample_layer1[0], SubMConv3d) 38 | assert isinstance(self.upsample_layer2[0], SparseInverseConv3d) 39 | 40 | voxel_features = torch.tensor( 41 | [[6.56126, 0.9648336, -1.7339306, 0.315], 42 | [6.8162713, -2.480431, -1.3616394, 0.36], 43 | [11.643568, -4.744306, -1.3580885, 0.16], 44 | [23.482342, 6.5036807, 0.5806964, 0.35]], 45 | dtype=torch.float32).cuda() # n, point_features 46 | coordinates = torch.tensor( 47 | [[0, 12, 819, 131], [0, 16, 750, 136], [1, 16, 705, 232], 48 | [1, 35, 930, 469]], 49 | dtype=torch.int32).cuda() # n, 4(batch, ind_x, ind_y, ind_z) 50 | 51 | unet_ret_dict = self.forward(voxel_features, coordinates, 2) 52 | seg_features = unet_ret_dict['seg_features'] 53 | spatial_features = unet_ret_dict['spatial_features'] 54 | 55 | assert seg_features.shape == torch.Size([4, 16]) 56 | assert spatial_features.shape == torch.Size([2, 256, 200, 176]) 57 | -------------------------------------------------------------------------------- /tests/test_models/test_necks/test_dla_neck.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | 3 | import torch 4 | 5 | from mmdet3d.registry import MODELS 6 | 7 | 8 | def test_dla_neck(): 9 | 10 | s = 32 11 | in_channels = [16, 32, 64, 128, 256, 512] 12 | feat_sizes = [s // 2**i for i in range(6)] # [32, 16, 8, 4, 2, 1] 13 | 14 | if torch.cuda.is_available(): 15 | # Test DLA Neck with DCNv2 on GPU 16 | neck_cfg = dict( 17 | type='DLANeck', 18 | in_channels=[16, 32, 64, 128, 256, 512], 19 | start_level=2, 20 | end_level=5, 21 | norm_cfg=dict(type='GN', num_groups=32)) 22 | neck = MODELS.build(neck_cfg) 23 | neck.init_weights() 24 | neck.cuda() 25 | feats = [ 26 | torch.rand(4, in_channels[i], feat_sizes[i], feat_sizes[i]).cuda() 27 | for i in range(len(in_channels)) 28 | ] 29 | outputs = neck(feats) 30 | assert outputs[0].shape == (4, 64, 8, 8) 31 | else: 32 | # Test DLA Neck without DCNv2 on CPU 33 | neck_cfg = dict( 34 | type='DLANeck', 35 | in_channels=[16, 32, 64, 128, 256, 512], 36 | start_level=2, 37 | end_level=5, 38 | norm_cfg=dict(type='GN', num_groups=32), 39 | use_dcn=False) 40 | neck = MODELS.build(neck_cfg) 41 | neck.init_weights() 42 | feats = [ 43 | torch.rand(4, in_channels[i], feat_sizes[i], feat_sizes[i]) 44 | for i in range(len(in_channels)) 45 | ] 46 | outputs = neck(feats) 47 | assert outputs[0].shape == (4, 64, 8, 8) 48 | -------------------------------------------------------------------------------- /tests/test_models/test_necks/test_imvoxel_neck.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import torch 3 | 4 | from mmdet3d.registry import MODELS 5 | 6 | 7 | def test_imvoxel_neck(): 8 | if not torch.cuda.is_available(): 9 | pytest.skip('test requires GPU and torch+cuda') 10 | 11 | neck_cfg = dict( 12 | type='OutdoorImVoxelNeck', in_channels=64, out_channels=256) 13 | neck = MODELS.build(neck_cfg).cuda() 14 | inputs = torch.rand([1, 64, 216, 248, 12], device='cuda') 15 | outputs = neck(inputs) 16 | assert outputs[0].shape == (1, 256, 248, 216) 17 | -------------------------------------------------------------------------------- /tests/test_models/test_necks/test_pointnet2_fp_neck.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import torch 3 | 4 | from mmdet3d.registry import MODELS 5 | 6 | 7 | def test_pointnet2_fp_neck(): 8 | if not torch.cuda.is_available(): 9 | pytest.skip() 10 | 11 | xyzs = [16384, 4096, 1024, 256, 64] 12 | feat_channels = [1, 96, 256, 512, 1024] 13 | channel_num = 5 14 | 15 | sa_xyz = [torch.rand(3, xyzs[i], 3) for i in range(channel_num)] 16 | sa_features = [ 17 | torch.rand(3, feat_channels[i], xyzs[i]) for i in range(channel_num) 18 | ] 19 | 20 | neck_cfg = dict( 21 | type='PointNetFPNeck', 22 | fp_channels=((1536, 512, 512), (768, 512, 512), (608, 256, 256), 23 | (257, 128, 128))) 24 | 25 | neck = MODELS.build(neck_cfg) 26 | neck.init_weights() 27 | 28 | if torch.cuda.is_available(): 29 | sa_xyz = [x.cuda() for x in sa_xyz] 30 | sa_features = [x.cuda() for x in sa_features] 31 | neck.cuda() 32 | 33 | feats_sa = {'sa_xyz': sa_xyz, 'sa_features': sa_features} 34 | outputs = neck(feats_sa) 35 | assert outputs['fp_xyz'].cpu().numpy().shape == (3, 16384, 3) 36 | assert outputs['fp_features'].detach().cpu().numpy().shape == (3, 128, 37 | 16384) 38 | -------------------------------------------------------------------------------- /tests/test_models/test_necks/test_second_fpn.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | 5 | from mmdet3d.registry import MODELS 6 | 7 | 8 | def test_secfpn(): 9 | neck_cfg = dict( 10 | type='SECONDFPN', 11 | in_channels=[2, 3], 12 | upsample_strides=[1, 2], 13 | out_channels=[4, 6], 14 | ) 15 | neck = MODELS.build(neck_cfg) 16 | assert neck.deblocks[0][0].in_channels == 2 17 | assert neck.deblocks[1][0].in_channels == 3 18 | assert neck.deblocks[0][0].out_channels == 4 19 | assert neck.deblocks[1][0].out_channels == 6 20 | assert neck.deblocks[0][0].stride == (1, 1) 21 | assert neck.deblocks[1][0].stride == (2, 2) 22 | assert neck is not None 23 | 24 | neck_cfg = dict( 25 | type='SECONDFPN', 26 | in_channels=[2, 2], 27 | upsample_strides=[1, 2, 4], 28 | out_channels=[2, 2], 29 | ) 30 | with pytest.raises(AssertionError): 31 | MODELS.build(neck_cfg) 32 | 33 | neck_cfg = dict( 34 | type='SECONDFPN', 35 | in_channels=[2, 2, 4], 36 | upsample_strides=[1, 2, 4], 37 | out_channels=[2, 2], 38 | ) 39 | with pytest.raises(AssertionError): 40 | MODELS.build(neck_cfg) 41 | 42 | 43 | def test_centerpoint_fpn(): 44 | 45 | second_cfg = dict( 46 | type='SECOND', 47 | in_channels=2, 48 | out_channels=[2, 2, 2], 49 | layer_nums=[3, 5, 5], 50 | layer_strides=[2, 2, 2], 51 | norm_cfg=dict(type='BN', eps=1e-3, momentum=0.01), 52 | conv_cfg=dict(type='Conv2d', bias=False)) 53 | 54 | second = MODELS.build(second_cfg) 55 | 56 | # centerpoint usage of fpn 57 | centerpoint_fpn_cfg = dict( 58 | type='SECONDFPN', 59 | in_channels=[2, 2, 2], 60 | out_channels=[2, 2, 2], 61 | upsample_strides=[0.5, 1, 2], 62 | norm_cfg=dict(type='BN', eps=1e-3, momentum=0.01), 63 | upsample_cfg=dict(type='deconv', bias=False), 64 | use_conv_for_no_stride=True) 65 | 66 | # original usage of fpn 67 | fpn_cfg = dict( 68 | type='SECONDFPN', 69 | in_channels=[2, 2, 2], 70 | upsample_strides=[1, 2, 4], 71 | out_channels=[2, 2, 2]) 72 | 73 | second_fpn = MODELS.build(fpn_cfg) 74 | 75 | centerpoint_second_fpn = MODELS.build(centerpoint_fpn_cfg) 76 | 77 | input = torch.rand([2, 2, 32, 32]) 78 | sec_output = second(input) 79 | centerpoint_output = centerpoint_second_fpn(sec_output) 80 | second_output = second_fpn(sec_output) 81 | assert centerpoint_output[0].shape == torch.Size([2, 6, 8, 8]) 82 | assert second_output[0].shape == torch.Size([2, 6, 16, 16]) 83 | -------------------------------------------------------------------------------- /tests/test_models/test_segmentor/test_minkunet.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import unittest 3 | 4 | import pytest 5 | import torch 6 | from mmengine import DefaultScope 7 | 8 | from mmdet3d.registry import MODELS 9 | from mmdet3d.testing import (create_detector_inputs, get_detector_cfg, 10 | setup_seed) 11 | 12 | 13 | class TestMinkUNet(unittest.TestCase): 14 | 15 | def test_minkunet(self): 16 | try: 17 | import torchsparse # noqa 18 | except ImportError: 19 | pytest.skip('test requires Torchsparse installation') 20 | 21 | import mmdet3d.models 22 | 23 | assert hasattr(mmdet3d.models, 'MinkUNet') 24 | DefaultScope.get_instance('test_minkunet', scope_name='mmdet3d') 25 | setup_seed(0) 26 | model_cfg = get_detector_cfg('_base_/models/minkunet.py') 27 | model = MODELS.build(model_cfg) 28 | num_gt_instance = 3 29 | packed_inputs = create_detector_inputs( 30 | num_gt_instance=num_gt_instance, 31 | num_classes=19, 32 | with_pts_semantic_mask=True) 33 | 34 | if torch.cuda.is_available(): 35 | model = model.cuda() 36 | # test simple_test 37 | with torch.no_grad(): 38 | data = model.data_preprocessor(packed_inputs, True) 39 | torch.cuda.empty_cache() 40 | results = model.forward(**data, mode='predict') 41 | self.assertEqual(len(results), 1) 42 | self.assertIn('pts_semantic_mask', results[0].pred_pts_seg) 43 | 44 | losses = model.forward(**data, mode='loss') 45 | 46 | self.assertGreater(losses['loss_sem_seg'], 0) 47 | -------------------------------------------------------------------------------- /tests/test_models/test_segmentors/test_cylinder3d.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import torch 4 | from mmengine import DefaultScope 5 | 6 | from mmdet3d.registry import MODELS 7 | from mmdet3d.testing import (create_detector_inputs, get_detector_cfg, 8 | setup_seed) 9 | 10 | 11 | class TestCylinder3D(unittest.TestCase): 12 | 13 | def test_cylinder3d(self): 14 | import mmdet3d.models 15 | 16 | assert hasattr(mmdet3d.models, 'Cylinder3D') 17 | DefaultScope.get_instance('test_cylinder3d', scope_name='mmdet3d') 18 | setup_seed(0) 19 | cylinder3d_cfg = get_detector_cfg( 20 | 'cylinder3d/cylinder3d_4xb4_3x_semantickitti.py') 21 | cylinder3d_cfg.decode_head['ignore_index'] = 1 22 | model = MODELS.build(cylinder3d_cfg) 23 | num_gt_instance = 3 24 | packed_inputs = create_detector_inputs( 25 | num_gt_instance=num_gt_instance, 26 | num_classes=1, 27 | with_pts_semantic_mask=True) 28 | 29 | if torch.cuda.is_available(): 30 | model = model.cuda() 31 | # test simple_test 32 | with torch.no_grad(): 33 | data = model.data_preprocessor(packed_inputs, True) 34 | torch.cuda.empty_cache() 35 | results = model.forward(**data, mode='predict') 36 | self.assertEqual(len(results), 1) 37 | self.assertIn('pts_semantic_mask', results[0].pred_pts_seg) 38 | 39 | losses = model.forward(**data, mode='loss') 40 | 41 | self.assertGreater(losses['decode.loss_ce'], 0) 42 | self.assertGreater(losses['decode.loss_lovasz'], 0) 43 | -------------------------------------------------------------------------------- /tests/test_models/test_task_modules/test_coders/test_centerpoint_bbox_coder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import torch 3 | 4 | from mmdet3d.registry import TASK_UTILS 5 | 6 | 7 | def test_centerpoint_bbox_coder(): 8 | bbox_coder_cfg = dict( 9 | type='CenterPointBBoxCoder', 10 | post_center_range=[-61.2, -61.2, -10.0, 61.2, 61.2, 10.0], 11 | max_num=500, 12 | score_threshold=0.1, 13 | pc_range=[-51.2, -51.2], 14 | out_size_factor=4, 15 | voxel_size=[0.2, 0.2]) 16 | 17 | bbox_coder = TASK_UTILS.build(bbox_coder_cfg) 18 | 19 | batch_dim = torch.rand([2, 3, 128, 128]) 20 | batch_hei = torch.rand([2, 1, 128, 128]) 21 | batch_hm = torch.rand([2, 2, 128, 128]) 22 | batch_reg = torch.rand([2, 2, 128, 128]) 23 | batch_rotc = torch.rand([2, 1, 128, 128]) 24 | batch_rots = torch.rand([2, 1, 128, 128]) 25 | batch_vel = torch.rand([2, 2, 128, 128]) 26 | 27 | temp = bbox_coder.decode(batch_hm, batch_rots, batch_rotc, batch_hei, 28 | batch_dim, batch_vel, batch_reg, 5) 29 | for i in range(len(temp)): 30 | assert temp[i]['bboxes'].shape == torch.Size([500, 9]) 31 | assert temp[i]['scores'].shape == torch.Size([500]) 32 | assert temp[i]['labels'].shape == torch.Size([500]) 33 | -------------------------------------------------------------------------------- /tests/test_models/test_task_modules/test_coders/test_point_xyzwhlr_bbox_coder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | 3 | import torch 4 | 5 | from mmdet3d.registry import TASK_UTILS 6 | 7 | 8 | def test_point_xyzwhlr_bbox_coder(): 9 | bbox_coder_cfg = dict( 10 | type='PointXYZWHLRBBoxCoder', 11 | use_mean_size=True, 12 | mean_size=[[3.9, 1.6, 1.56], [0.8, 0.6, 1.73], [1.76, 0.6, 1.73]]) 13 | boxcoder = TASK_UTILS.build(bbox_coder_cfg) 14 | 15 | # test encode 16 | gt_bboxes_3d = torch.tensor( 17 | [[13.3329, 2.3514, -0.7004, 1.7508, 0.4702, 1.7909, -3.0522], 18 | [2.2068, -2.6994, -0.3277, 3.8703, 1.6602, 1.6913, -1.9057], 19 | [5.5269, 2.5085, -1.0129, 1.1496, 0.8006, 1.8887, 2.1756]]) 20 | 21 | points = torch.tensor([[13.70, 2.40, 0.12], [3.20, -3.00, 0.2], 22 | [5.70, 2.20, -0.4]]) 23 | 24 | gt_labels_3d = torch.tensor([2, 0, 1]) 25 | 26 | bbox_target = boxcoder.encode(gt_bboxes_3d, points, gt_labels_3d) 27 | expected_bbox_target = torch.tensor([[ 28 | -0.1974, -0.0261, -0.4742, -0.0052, -0.2438, 0.0346, -0.9960, -0.0893 29 | ], [-0.2356, 0.0713, -0.3383, -0.0076, 0.0369, 0.0808, -0.3287, -0.9444 30 | ], [-0.1731, 0.3085, -0.3543, 0.3626, 0.2884, 0.0878, -0.5686, 31 | 0.8226]]) 32 | assert torch.allclose(expected_bbox_target, bbox_target, atol=1e-4) 33 | # test decode 34 | bbox3d_out = boxcoder.decode(bbox_target, points, gt_labels_3d) 35 | assert torch.allclose(bbox3d_out, gt_bboxes_3d, atol=1e-4) 36 | -------------------------------------------------------------------------------- /tests/test_models/test_task_modules/test_coders/test_smoke_bbox_coder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import torch 3 | 4 | from mmdet3d.registry import TASK_UTILS 5 | from mmdet3d.structures import CameraInstance3DBoxes 6 | 7 | 8 | def test_smoke_bbox_coder(): 9 | bbox_coder_cfg = dict( 10 | type='SMOKECoder', 11 | base_depth=(28.01, 16.32), 12 | base_dims=((3.88, 1.63, 1.53), (1.78, 1.70, 0.58), (0.88, 1.73, 0.67)), 13 | code_size=7) 14 | 15 | bbox_coder = TASK_UTILS.build(bbox_coder_cfg) 16 | regression = torch.rand([200, 8]) 17 | points = torch.rand([200, 2]) 18 | labels = torch.ones([2, 100]) 19 | cam2imgs = torch.rand([2, 4, 4]) 20 | trans_mats = torch.rand([2, 3, 3]) 21 | 22 | img_metas = [dict(box_type_3d=CameraInstance3DBoxes) for i in range(2)] 23 | locations, dimensions, orientations = bbox_coder.decode( 24 | regression, points, labels, cam2imgs, trans_mats) 25 | assert locations.shape == torch.Size([200, 3]) 26 | assert dimensions.shape == torch.Size([200, 3]) 27 | assert orientations.shape == torch.Size([200, 1]) 28 | bboxes = bbox_coder.encode(locations, dimensions, orientations, img_metas) 29 | assert bboxes.tensor.shape == torch.Size([200, 7]) 30 | 31 | # specically designed to test orientation decode function's 32 | # special cases. 33 | ori_vector = torch.tensor([[-0.9, -0.01], [-0.9, 0.01]]) 34 | locations = torch.tensor([[15., 2., 1.], [15., 2., -1.]]) 35 | orientations = bbox_coder._decode_orientation(ori_vector, locations) 36 | assert orientations.shape == torch.Size([2, 1]) 37 | -------------------------------------------------------------------------------- /tests/test_models/test_task_modules/test_samplers/test_iou_piecewise_sampler.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | from mmengine.structures import InstanceData 5 | 6 | from mmdet3d.models.task_modules import IoUNegPiecewiseSampler 7 | from mmdet3d.models.task_modules.assigners import Max3DIoUAssigner 8 | 9 | 10 | def test_iou_piecewise_sampler(): 11 | if not torch.cuda.is_available(): 12 | pytest.skip() 13 | assigner = Max3DIoUAssigner( 14 | pos_iou_thr=0.55, 15 | neg_iou_thr=0.55, 16 | min_pos_iou=0.55, 17 | ignore_iof_thr=-1, 18 | iou_calculator=dict(type='BboxOverlaps3D', coordinate='lidar')) 19 | bboxes = torch.tensor( 20 | [[32, 32, 16, 8, 38, 42, -0.3], [32, 32, 16, 8, 38, 42, -0.3], 21 | [32, 32, 16, 8, 38, 42, -0.3], [32, 32, 16, 8, 38, 42, -0.3], 22 | [0, 0, 0, 10, 10, 10, 0.2], [10, 10, 10, 20, 20, 15, 0.6], 23 | [5, 5, 5, 15, 15, 15, 0.7], [5, 5, 5, 15, 15, 15, 0.7], 24 | [5, 5, 5, 15, 15, 15, 0.7], [32, 32, 16, 8, 38, 42, -0.3], 25 | [32, 32, 16, 8, 38, 42, -0.3], [32, 32, 16, 8, 38, 42, -0.3]], 26 | dtype=torch.float32).cuda() 27 | gt_bboxes = torch.tensor( 28 | [[0, 0, 0, 10, 10, 9, 0.2], [5, 10, 10, 20, 20, 15, 0.6]], 29 | dtype=torch.float32).cuda() 30 | gt_labels = torch.tensor([1, 1], dtype=torch.int64).cuda() 31 | gt_instanses = InstanceData() 32 | gt_instanses.bboxes_3d = gt_bboxes 33 | gt_instanses.labels_3d = gt_labels 34 | pred_instaces = InstanceData() 35 | pred_instaces.priors = bboxes 36 | 37 | assign_result = assigner.assign(pred_instaces, gt_instanses) 38 | 39 | sampler = IoUNegPiecewiseSampler( 40 | num=10, 41 | pos_fraction=0.55, 42 | neg_piece_fractions=[0.8, 0.2], 43 | neg_iou_piece_thrs=[0.55, 0.1], 44 | neg_pos_ub=-1, 45 | add_gt_as_proposals=False) 46 | 47 | sample_result = sampler.sample(assign_result, bboxes, gt_bboxes, gt_labels) 48 | 49 | assert sample_result.pos_inds == 4 50 | assert len(sample_result.pos_bboxes) == len(sample_result.pos_inds) 51 | assert len(sample_result.neg_bboxes) == len(sample_result.neg_inds) 52 | -------------------------------------------------------------------------------- /tests/test_models/test_task_modules/test_voxel/test_voxel_generator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import numpy as np 3 | 4 | from mmdet3d.models.task_modules.voxel import VoxelGenerator 5 | 6 | 7 | def test_voxel_generator(): 8 | np.random.seed(0) 9 | voxel_size = [5, 5, 1] 10 | point_cloud_range = [0, 0, 0, 20, 40, 4] 11 | max_num_points = 5 12 | self = VoxelGenerator(voxel_size, point_cloud_range, max_num_points) 13 | points = np.random.uniform(0, 4, (20, 3)) 14 | voxels = self.generate(points) 15 | voxels, coors, num_points_per_voxel = voxels 16 | expected_coors = np.array([[2, 0, 0], [3, 0, 0], [0, 0, 0], [1, 0, 0]]) 17 | expected_num_points_per_voxel = np.array([5, 5, 5, 3]) 18 | assert voxels.shape == (4, 5, 3) 19 | assert np.all(coors == expected_coors) 20 | assert np.all(num_points_per_voxel == expected_num_points_per_voxel) 21 | -------------------------------------------------------------------------------- /tests/test_models/test_voxel_encoders/test_pillar_encoder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | 5 | from mmdet3d.registry import MODELS 6 | 7 | 8 | def test_pillar_feature_net(): 9 | if not torch.cuda.is_available(): 10 | pytest.skip('test requires GPU and torch+cuda') 11 | pillar_feature_net_cfg = dict( 12 | type='PillarFeatureNet', 13 | in_channels=5, 14 | feat_channels=[64], 15 | with_distance=False, 16 | voxel_size=(0.2, 0.2, 8), 17 | point_cloud_range=(-51.2, -51.2, -5.0, 51.2, 51.2, 3.0), 18 | norm_cfg=dict(type='BN1d', eps=1e-3, momentum=0.01)) 19 | pillar_feature_net = MODELS.build(pillar_feature_net_cfg) 20 | 21 | features = torch.rand([97297, 20, 5]) 22 | num_voxels = torch.randint(1, 100, [97297]) 23 | coors = torch.randint(0, 100, [97297, 4]) 24 | 25 | features = pillar_feature_net(features, num_voxels, coors) 26 | assert features.shape == torch.Size([97297, 64]) 27 | -------------------------------------------------------------------------------- /tests/test_models/test_voxel_encoders/test_voxel_encoders.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import pytest 3 | import torch 4 | import torch.nn.functional as F 5 | 6 | from mmdet3d.registry import MODELS 7 | 8 | 9 | def test_hard_simple_VFE(): 10 | if not torch.cuda.is_available(): 11 | pytest.skip('test requires GPU and torch+cuda') 12 | hard_simple_VFE_cfg = dict(type='HardSimpleVFE', num_features=5) 13 | hard_simple_VFE = MODELS.build(hard_simple_VFE_cfg) 14 | features = torch.rand([240000, 10, 5]) 15 | num_voxels = torch.randint(1, 10, [240000]) 16 | 17 | outputs = hard_simple_VFE(features, num_voxels, None) 18 | assert outputs.shape == torch.Size([240000, 5]) 19 | 20 | 21 | def test_seg_VFE(): 22 | if not torch.cuda.is_available(): 23 | pytest.skip('test requires GPU and torch+cuda') 24 | seg_VFE_cfg = dict( 25 | type='SegVFE', 26 | feat_channels=[64, 128, 256, 256], 27 | grid_shape=[480, 360, 32], 28 | with_voxel_center=True, 29 | feat_compression=16, 30 | return_point_feats=True) 31 | seg_VFE = MODELS.build(seg_VFE_cfg) 32 | seg_VFE = seg_VFE.cuda() 33 | features = torch.rand([240000, 6]).cuda() 34 | coors = [] 35 | for i in range(4): 36 | coor = torch.randint(0, 10, (60000, 3)) 37 | coor = F.pad(coor, (1, 0), mode='constant', value=i) 38 | coors.append(coor) 39 | coors = torch.cat(coors, dim=0).cuda() 40 | out_features, out_coors, out_point_features = seg_VFE(features, coors) 41 | assert out_features.shape[0] == out_coors.shape[0] 42 | assert len(out_point_features) == 4 43 | assert out_point_features[0].shape == torch.Size([240000, 64]) 44 | assert out_point_features[1].shape == torch.Size([240000, 128]) 45 | assert out_point_features[2].shape == torch.Size([240000, 256]) 46 | assert out_point_features[3].shape == torch.Size([240000, 256]) 47 | -------------------------------------------------------------------------------- /tests/test_samples/parta2_roihead_inputs.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavierGrool/FGFusion/60a0f82ff2d0ba14d0c18b2acb3b83efdd6a8f82/tests/test_samples/parta2_roihead_inputs.npz -------------------------------------------------------------------------------- /tools/create_data.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | export PYTHONPATH=`pwd`:$PYTHONPATH 5 | 6 | PARTITION=$1 7 | JOB_NAME=$2 8 | DATASET=$3 9 | GPUS=${GPUS:-1} 10 | GPUS_PER_NODE=${GPUS_PER_NODE:-1} 11 | SRUN_ARGS=${SRUN_ARGS:-""} 12 | JOB_NAME=create_data 13 | 14 | srun -p ${PARTITION} \ 15 | --job-name=${JOB_NAME} \ 16 | --gres=gpu:${GPUS_PER_NODE} \ 17 | --ntasks=${GPUS} \ 18 | --ntasks-per-node=${GPUS_PER_NODE} \ 19 | --kill-on-bad-exit=1 \ 20 | ${SRUN_ARGS} \ 21 | python -u tools/create_data.py ${DATASET} \ 22 | --root-path ./data/${DATASET} \ 23 | --out-dir ./data/${DATASET} \ 24 | --extra-tag ${DATASET} 25 | -------------------------------------------------------------------------------- /tools/dataset_converters/lyft_data_fixer.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import argparse 3 | import os 4 | 5 | import numpy as np 6 | 7 | 8 | def fix_lyft(root_folder='./data/lyft', version='v1.01'): 9 | # refer to https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/discussion/110000 # noqa 10 | lidar_path = 'lidar/host-a011_lidar1_1233090652702363606.bin' 11 | root_folder = os.path.join(root_folder, f'{version}-train') 12 | lidar_path = os.path.join(root_folder, lidar_path) 13 | assert os.path.isfile(lidar_path), f'Please download the complete Lyft ' \ 14 | f'dataset and make sure {lidar_path} is present.' 15 | points = np.fromfile(lidar_path, dtype=np.float32, count=-1) 16 | try: 17 | points.reshape([-1, 5]) 18 | print(f'This fix is not required for version {version}.') 19 | except ValueError: 20 | new_points = np.array(list(points) + [100.0, 1.0], dtype='float32') 21 | new_points.tofile(lidar_path) 22 | print(f'Appended 100.0 and 1.0 to the end of {lidar_path}.') 23 | 24 | 25 | parser = argparse.ArgumentParser(description='Lyft dataset fixer arg parser') 26 | parser.add_argument( 27 | '--root-folder', 28 | type=str, 29 | default='./data/lyft', 30 | help='specify the root path of Lyft dataset') 31 | parser.add_argument( 32 | '--version', 33 | type=str, 34 | default='v1.01', 35 | help='specify Lyft dataset version') 36 | args = parser.parse_args() 37 | 38 | if __name__ == '__main__': 39 | fix_lyft(root_folder=args.root_folder, version=args.version) 40 | -------------------------------------------------------------------------------- /tools/deployment/test_torchserver.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | 3 | import numpy as np 4 | import requests 5 | 6 | from mmdet3d.apis import inference_detector, init_model 7 | 8 | 9 | def parse_args(): 10 | parser = ArgumentParser() 11 | parser.add_argument('pcd', help='Point cloud file') 12 | parser.add_argument('config', help='Config file') 13 | parser.add_argument('checkpoint', help='Checkpoint file') 14 | parser.add_argument('model_name', help='The model name in the server') 15 | parser.add_argument( 16 | '--inference-addr', 17 | default='127.0.0.1:8080', 18 | help='Address and port of the inference server') 19 | parser.add_argument( 20 | '--device', default='cuda:0', help='Device used for inference') 21 | parser.add_argument( 22 | '--score-thr', type=float, default=0.5, help='3d bbox score threshold') 23 | args = parser.parse_args() 24 | return args 25 | 26 | 27 | def parse_result(input): 28 | bbox = input[0]['3dbbox'] 29 | result = np.array(bbox) 30 | return result 31 | 32 | 33 | def main(args): 34 | # build the model from a config file and a checkpoint file 35 | model = init_model(args.config, args.checkpoint, device=args.device) 36 | # test a single point cloud file 37 | model_result, _ = inference_detector(model, args.pcd) 38 | # filter the 3d bboxes whose scores > 0.5 39 | if 'pts_bbox' in model_result[0].keys(): 40 | pred_bboxes = model_result[0]['pts_bbox']['boxes_3d'].tensor.numpy() 41 | pred_scores = model_result[0]['pts_bbox']['scores_3d'].numpy() 42 | else: 43 | pred_bboxes = model_result[0]['boxes_3d'].tensor.numpy() 44 | pred_scores = model_result[0]['scores_3d'].numpy() 45 | model_result = pred_bboxes[pred_scores > 0.5] 46 | 47 | url = 'http://' + args.inference_addr + '/predictions/' + args.model_name 48 | with open(args.pcd, 'rb') as points: 49 | response = requests.post(url, points) 50 | server_result = parse_result(response.json()) 51 | assert np.allclose(model_result, server_result) 52 | 53 | 54 | if __name__ == '__main__': 55 | args = parse_args() 56 | main(args) 57 | -------------------------------------------------------------------------------- /tools/dist_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CONFIG=$1 4 | CHECKPOINT=$2 5 | GPUS=$3 6 | NNODES=${NNODES:-1} 7 | NODE_RANK=${NODE_RANK:-0} 8 | PORT=${PORT:-29500} 9 | MASTER_ADDR=${MASTER_ADDR:-"127.0.0.1"} 10 | 11 | PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \ 12 | python -m torch.distributed.launch \ 13 | --nnodes=$NNODES \ 14 | --node_rank=$NODE_RANK \ 15 | --master_addr=$MASTER_ADDR \ 16 | --nproc_per_node=$GPUS \ 17 | --master_port=$PORT \ 18 | $(dirname "$0")/test.py \ 19 | $CONFIG \ 20 | $CHECKPOINT \ 21 | --launcher pytorch \ 22 | ${@:4} 23 | -------------------------------------------------------------------------------- /tools/dist_train.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CONFIG=$1 4 | GPUS=$2 5 | NNODES=${NNODES:-1} 6 | NODE_RANK=${NODE_RANK:-0} 7 | PORT=${PORT:-29500} 8 | MASTER_ADDR=${MASTER_ADDR:-"127.0.0.1"} 9 | 10 | PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \ 11 | python -m torch.distributed.launch \ 12 | --nnodes=$NNODES \ 13 | --node_rank=$NODE_RANK \ 14 | --master_addr=$MASTER_ADDR \ 15 | --nproc_per_node=$GPUS \ 16 | --master_port=$PORT \ 17 | $(dirname "$0")/train.py \ 18 | $CONFIG \ 19 | --launcher pytorch ${@:3} 20 | -------------------------------------------------------------------------------- /tools/misc/fuse_conv_bn.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import argparse 3 | 4 | import torch 5 | from mmengine.runner import save_checkpoint 6 | from torch import nn as nn 7 | 8 | from mmdet3d.apis import init_model 9 | 10 | 11 | def fuse_conv_bn(conv, bn): 12 | """During inference, the functionary of batch norm layers is turned off but 13 | only the mean and var alone channels are used, which exposes the chance to 14 | fuse it with the preceding conv layers to save computations and simplify 15 | network bboxes_3d.""" 16 | conv_w = conv.weight 17 | conv_b = conv.bias if conv.bias is not None else torch.zeros_like( 18 | bn.running_mean) 19 | 20 | factor = bn.weight / torch.sqrt(bn.running_var + bn.eps) 21 | conv.weight = nn.Parameter(conv_w * 22 | factor.reshape([conv.out_channels, 1, 1, 1])) 23 | conv.bias = nn.Parameter((conv_b - bn.running_mean) * factor + bn.bias) 24 | return conv 25 | 26 | 27 | def fuse_module(m): 28 | last_conv = None 29 | last_conv_name = None 30 | 31 | for name, child in m.named_children(): 32 | if isinstance(child, (nn.BatchNorm2d, nn.SyncBatchNorm)): 33 | if last_conv is None: # only fuse BN that is after Conv 34 | continue 35 | fused_conv = fuse_conv_bn(last_conv, child) 36 | m._modules[last_conv_name] = fused_conv 37 | # To reduce changes, set BN as Identity instead of deleting it. 38 | m._modules[name] = nn.Identity() 39 | last_conv = None 40 | elif isinstance(child, nn.Conv2d): 41 | last_conv = child 42 | last_conv_name = name 43 | else: 44 | fuse_module(child) 45 | return m 46 | 47 | 48 | def parse_args(): 49 | parser = argparse.ArgumentParser( 50 | description='fuse Conv and BN layers in a model') 51 | parser.add_argument('config', help='config file path') 52 | parser.add_argument('checkpoint', help='checkpoint file path') 53 | parser.add_argument('out', help='output path of the converted model') 54 | args = parser.parse_args() 55 | return args 56 | 57 | 58 | def main(): 59 | args = parse_args() 60 | # build the model from a config file and a checkpoint file 61 | model = init_model(args.config, args.checkpoint) 62 | # fuse conv and bn layers of the model 63 | fused_model = fuse_module(model) 64 | save_checkpoint(fused_model, args.out) 65 | 66 | 67 | if __name__ == '__main__': 68 | main() 69 | -------------------------------------------------------------------------------- /tools/misc/print_config.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import argparse 3 | 4 | from mmengine import Config, DictAction 5 | 6 | 7 | def parse_args(): 8 | parser = argparse.ArgumentParser(description='Print the whole config') 9 | parser.add_argument('config', help='config file path') 10 | parser.add_argument( 11 | '--options', nargs='+', action=DictAction, help='arguments in dict') 12 | args = parser.parse_args() 13 | 14 | return args 15 | 16 | 17 | def main(): 18 | args = parse_args() 19 | 20 | cfg = Config.fromfile(args.config) 21 | if args.options is not None: 22 | cfg.merge_from_dict(args.options) 23 | print(f'Config:\n{cfg.pretty_text}') 24 | 25 | 26 | if __name__ == '__main__': 27 | main() 28 | -------------------------------------------------------------------------------- /tools/misc/visualize_results.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import argparse 3 | 4 | import mmengine 5 | from mmengine import Config 6 | 7 | from mmdet3d.registry import DATASETS 8 | 9 | 10 | def parse_args(): 11 | parser = argparse.ArgumentParser( 12 | description='MMDet3D visualize the results') 13 | parser.add_argument('config', help='test config file path') 14 | parser.add_argument('--result', help='results file in pickle format') 15 | parser.add_argument( 16 | '--show-dir', help='directory where visualize results will be saved') 17 | args = parser.parse_args() 18 | 19 | return args 20 | 21 | 22 | def main(): 23 | args = parse_args() 24 | 25 | if args.result is not None and \ 26 | not args.result.endswith(('.pkl', '.pickle')): 27 | raise ValueError('The results file must be a pkl file.') 28 | 29 | cfg = Config.fromfile(args.config) 30 | cfg.data.test.test_mode = True 31 | 32 | # build the dataset 33 | dataset = DATASETS.build(cfg.data.test) 34 | results = mmengine.load(args.result) 35 | 36 | if getattr(dataset, 'show', None) is not None: 37 | # data loading pipeline for showing 38 | eval_pipeline = cfg.get('eval_pipeline', {}) 39 | if eval_pipeline: 40 | dataset.show(results, args.show_dir, pipeline=eval_pipeline) 41 | else: 42 | dataset.show(results, args.show_dir) # use default pipeline 43 | else: 44 | raise NotImplementedError( 45 | 'Show is not implemented for dataset {}!'.format( 46 | type(dataset).__name__)) 47 | 48 | 49 | if __name__ == '__main__': 50 | main() 51 | -------------------------------------------------------------------------------- /tools/model_converters/publish_model.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) OpenMMLab. All rights reserved. 2 | import argparse 3 | import subprocess 4 | 5 | import torch 6 | 7 | 8 | def parse_args(): 9 | parser = argparse.ArgumentParser( 10 | description='Process a checkpoint to be published') 11 | parser.add_argument('in_file', help='input checkpoint filename') 12 | parser.add_argument('out_file', help='output checkpoint filename') 13 | args = parser.parse_args() 14 | return args 15 | 16 | 17 | def process_checkpoint(in_file, out_file): 18 | checkpoint = torch.load(in_file, map_location='cpu') 19 | # remove optimizer for smaller file size 20 | if 'optimizer' in checkpoint: 21 | del checkpoint['optimizer'] 22 | # if it is necessary to remove some sensitive data in checkpoint['meta'], 23 | # add the code here. 24 | torch.save(checkpoint, out_file) 25 | sha = subprocess.check_output(['sha256sum', out_file]).decode() 26 | final_file = out_file.rstrip('.pth') + '-{}.pth'.format(sha[:8]) 27 | subprocess.Popen(['mv', out_file, final_file]) 28 | 29 | 30 | def main(): 31 | args = parse_args() 32 | process_checkpoint(args.in_file, args.out_file) 33 | 34 | 35 | if __name__ == '__main__': 36 | main() 37 | -------------------------------------------------------------------------------- /tools/slurm_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | PARTITION=$1 6 | JOB_NAME=$2 7 | CONFIG=$3 8 | CHECKPOINT=$4 9 | GPUS=${GPUS:-8} 10 | GPUS_PER_NODE=${GPUS_PER_NODE:-8} 11 | CPUS_PER_TASK=${CPUS_PER_TASK:-5} 12 | PY_ARGS=${@:5} 13 | SRUN_ARGS=${SRUN_ARGS:-""} 14 | 15 | PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \ 16 | srun -p ${PARTITION} \ 17 | --job-name=${JOB_NAME} \ 18 | --gres=gpu:${GPUS_PER_NODE} \ 19 | --ntasks=${GPUS} \ 20 | --ntasks-per-node=${GPUS_PER_NODE} \ 21 | --cpus-per-task=${CPUS_PER_TASK} \ 22 | --kill-on-bad-exit=1 \ 23 | ${SRUN_ARGS} \ 24 | python -u tools/test.py ${CONFIG} ${CHECKPOINT} --launcher="slurm" ${PY_ARGS} 25 | -------------------------------------------------------------------------------- /tools/slurm_train.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | PARTITION=$1 6 | JOB_NAME=$2 7 | CONFIG=$3 8 | WORK_DIR=$4 9 | GPUS=${GPUS:-8} 10 | GPUS_PER_NODE=${GPUS_PER_NODE:-8} 11 | CPUS_PER_TASK=${CPUS_PER_TASK:-5} 12 | SRUN_ARGS=${SRUN_ARGS:-""} 13 | PY_ARGS=${@:5} 14 | 15 | PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \ 16 | srun -p ${PARTITION} \ 17 | --job-name=${JOB_NAME} \ 18 | --gres=gpu:${GPUS_PER_NODE} \ 19 | --ntasks=${GPUS} \ 20 | --ntasks-per-node=${GPUS_PER_NODE} \ 21 | --cpus-per-task=${CPUS_PER_TASK} \ 22 | --kill-on-bad-exit=1 \ 23 | ${SRUN_ARGS} \ 24 | python -u tools/train.py ${CONFIG} --work-dir=${WORK_DIR} --launcher="slurm" ${PY_ARGS} 25 | -------------------------------------------------------------------------------- /tools/update_data_coords.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | export PYTHONPATH=`pwd`:$PYTHONPATH 5 | 6 | PARTITION=$1 7 | DATASET=$2 8 | GPUS=${GPUS:-1} 9 | GPUS_PER_NODE=${GPUS_PER_NODE:-1} 10 | SRUN_ARGS=${SRUN_ARGS:-""} 11 | JOB_NAME=update_data_coords 12 | 13 | srun -p ${PARTITION} \ 14 | --job-name=${JOB_NAME} \ 15 | --gres=gpu:${GPUS_PER_NODE} \ 16 | --ntasks=${GPUS} \ 17 | --ntasks-per-node=${GPUS_PER_NODE} \ 18 | --kill-on-bad-exit=1 \ 19 | ${SRUN_ARGS} \ 20 | python -u tools/update_data_coords.py ${DATASET} \ 21 | --root-dir ./data/${DATASET} \ 22 | --out-dir ./data/${DATASET} 23 | --------------------------------------------------------------------------------