Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
mmdetection3d
Commits
a5d463d7
Commit
a5d463d7
authored
Jul 06, 2022
by
VVsssssk
Committed by
ChaimZhu
Jul 20, 2022
Browse files
[Refactor]Refactor lyft
parent
ed115937
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
273 additions
and
544 deletions
+273
-544
configs/_base_/datasets/lyft-3d.py
configs/_base_/datasets/lyft-3d.py
+53
-41
configs/pointpillars/hv_pointpillars_secfpn_sbn-all_2x8_2x_lyft-3d.py
...tpillars/hv_pointpillars_secfpn_sbn-all_2x8_2x_lyft-3d.py
+6
-0
mmdet3d/datasets/lyft_dataset.py
mmdet3d/datasets/lyft_dataset.py
+36
-503
tests/data/lyft/lyft_infos.pkl
tests/data/lyft/lyft_infos.pkl
+0
-0
tests/test_data/test_datasets/test_lyft_dataset.py
tests/test_data/test_datasets/test_lyft_dataset.py
+72
-0
tools/data_converter/update_infos_to_v2.py
tools/data_converter/update_infos_to_v2.py
+106
-0
No files found.
configs/_base_/datasets/lyft-3d.py
View file @
a5d463d7
...
@@ -10,12 +10,8 @@ dataset_type = 'LyftDataset'
...
@@ -10,12 +10,8 @@ dataset_type = 'LyftDataset'
data_root
=
'data/lyft/'
data_root
=
'data/lyft/'
# Input modality for Lyft dataset, this is consistent with the submission
# Input modality for Lyft dataset, this is consistent with the submission
# format which requires the information in input_modality.
# format which requires the information in input_modality.
input_modality
=
dict
(
input_modality
=
dict
(
use_lidar
=
True
,
use_camera
=
False
)
use_lidar
=
True
,
data_prefix
=
dict
(
pts
=
'samples/LIDAR_TOP'
,
img
=
''
)
use_camera
=
False
,
use_radar
=
False
,
use_map
=
False
,
use_external
=
False
)
file_client_args
=
dict
(
backend
=
'disk'
)
file_client_args
=
dict
(
backend
=
'disk'
)
# Uncomment the following if use ceph or other file clients.
# Uncomment the following if use ceph or other file clients.
# See https://mmcv.readthedocs.io/en/latest/api.html#mmcv.fileio.FileClient
# See https://mmcv.readthedocs.io/en/latest/api.html#mmcv.fileio.FileClient
...
@@ -47,8 +43,9 @@ train_pipeline = [
...
@@ -47,8 +43,9 @@ train_pipeline = [
dict
(
type
=
'PointsRangeFilter'
,
point_cloud_range
=
point_cloud_range
),
dict
(
type
=
'PointsRangeFilter'
,
point_cloud_range
=
point_cloud_range
),
dict
(
type
=
'ObjectRangeFilter'
,
point_cloud_range
=
point_cloud_range
),
dict
(
type
=
'ObjectRangeFilter'
,
point_cloud_range
=
point_cloud_range
),
dict
(
type
=
'PointShuffle'
),
dict
(
type
=
'PointShuffle'
),
dict
(
type
=
'DefaultFormatBundle3D'
,
class_names
=
class_names
),
dict
(
dict
(
type
=
'Collect3D'
,
keys
=
[
'points'
,
'gt_bboxes_3d'
,
'gt_labels_3d'
])
type
=
'Pack3DDetInputs'
,
keys
=
[
'points'
,
'gt_bboxes_3d'
,
'gt_labels_3d'
])
]
]
test_pipeline
=
[
test_pipeline
=
[
dict
(
dict
(
...
@@ -74,13 +71,9 @@ test_pipeline = [
...
@@ -74,13 +71,9 @@ test_pipeline = [
translation_std
=
[
0
,
0
,
0
]),
translation_std
=
[
0
,
0
,
0
]),
dict
(
type
=
'RandomFlip3D'
),
dict
(
type
=
'RandomFlip3D'
),
dict
(
dict
(
type
=
'PointsRangeFilter'
,
point_cloud_range
=
point_cloud_range
),
type
=
'PointsRangeFilter'
,
point_cloud_range
=
point_cloud_range
)
dict
(
]),
type
=
'DefaultFormatBundle3D'
,
dict
(
type
=
'Pack3DDetInputs'
,
keys
=
[
'points'
])
class_names
=
class_names
,
with_label
=
False
),
dict
(
type
=
'Collect3D'
,
keys
=
[
'points'
])
])
]
]
# construct a pipeline for data and gt loading in show function
# construct a pipeline for data and gt loading in show function
# please keep its loading function consistent with test_pipeline (e.g. client)
# please keep its loading function consistent with test_pipeline (e.g. client)
...
@@ -95,42 +88,61 @@ eval_pipeline = [
...
@@ -95,42 +88,61 @@ eval_pipeline = [
type
=
'LoadPointsFromMultiSweeps'
,
type
=
'LoadPointsFromMultiSweeps'
,
sweeps_num
=
10
,
sweeps_num
=
10
,
file_client_args
=
file_client_args
),
file_client_args
=
file_client_args
),
dict
(
dict
(
type
=
'Pack3DDetInputs'
,
keys
=
[
'points'
])
type
=
'DefaultFormatBundle3D'
,
class_names
=
class_names
,
with_label
=
False
),
dict
(
type
=
'Collect3D'
,
keys
=
[
'points'
])
]
]
train_dataloader
=
dict
(
data
=
dict
(
batch_size
=
2
,
samples_per_gpu
=
2
,
num_workers
=
2
,
workers_per_gpu
=
2
,
persistent_workers
=
True
,
train
=
dict
(
sampler
=
dict
(
type
=
'DefaultSampler'
,
shuffle
=
True
),
dataset
=
dict
(
type
=
dataset_type
,
type
=
dataset_type
,
data_root
=
data_root
,
data_root
=
data_root
,
ann_file
=
data_root
+
'lyft_infos_train.pkl'
,
ann_file
=
'lyft_infos_train.pkl'
,
pipeline
=
train_pipeline
,
pipeline
=
train_pipeline
,
classes
=
class_names
,
metainfo
=
dict
(
CLASSES
=
class_names
)
,
modality
=
input_modality
,
modality
=
input_modality
,
test_mode
=
False
),
data_prefix
=
data_prefix
,
val
=
dict
(
test_mode
=
False
,
box_type_3d
=
'LiDAR'
))
test_dataloader
=
dict
(
batch_size
=
1
,
num_workers
=
1
,
persistent_workers
=
True
,
drop_last
=
False
,
sampler
=
dict
(
type
=
'DefaultSampler'
,
shuffle
=
False
),
dataset
=
dict
(
type
=
dataset_type
,
type
=
dataset_type
,
data_root
=
data_root
,
data_root
=
data_root
,
ann_file
=
data_root
+
'lyft_infos_val.pkl'
,
ann_file
=
'lyft_infos_val.pkl'
,
pipeline
=
test_pipeline
,
pipeline
=
test_pipeline
,
classes
=
class_names
,
metainfo
=
dict
(
CLASSES
=
class_names
)
,
modality
=
input_modality
,
modality
=
input_modality
,
test_mode
=
True
),
data_prefix
=
data_prefix
,
test
=
dict
(
test_mode
=
True
,
box_type_3d
=
'LiDAR'
))
val_dataloader
=
dict
(
batch_size
=
1
,
num_workers
=
1
,
persistent_workers
=
True
,
drop_last
=
False
,
sampler
=
dict
(
type
=
'DefaultSampler'
,
shuffle
=
False
),
dataset
=
dict
(
type
=
dataset_type
,
type
=
dataset_type
,
data_root
=
data_root
,
data_root
=
data_root
,
ann_file
=
data_root
+
'lyft_infos_
test
.pkl'
,
ann_file
=
'lyft_infos_
val
.pkl'
,
pipeline
=
test_pipeline
,
pipeline
=
test_pipeline
,
classes
=
class_names
,
metainfo
=
dict
(
CLASSES
=
class_names
)
,
modality
=
input_modality
,
modality
=
input_modality
,
test_mode
=
True
))
test_mode
=
True
,
# For Lyft dataset, we usually evaluate the model at the end of training.
data_prefix
=
data_prefix
,
# Since the models are trained by 24 epochs by default, we set evaluation
box_type_3d
=
'LiDAR'
))
# interval to be 24. Please change the interval accordingly if you do not
# use a default schedule.
val_evaluator
=
dict
(
evaluation
=
dict
(
interval
=
24
,
pipeline
=
eval_pipeline
)
type
=
'LyftMetric'
,
ann_file
=
data_root
+
'lyft_infos_val.pkl'
,
metric
=
'bbox'
)
test_evaluator
=
dict
(
type
=
'LyftMetric'
,
ann_file
=
data_root
+
'lyft_infos_val.pkl'
,
metric
=
'bbox'
)
configs/pointpillars/hv_pointpillars_secfpn_sbn-all_2x8_2x_lyft-3d.py
View file @
a5d463d7
...
@@ -41,3 +41,9 @@ model = dict(
...
@@ -41,3 +41,9 @@ model = dict(
],
],
rotations
=
[
0
,
1.57
],
rotations
=
[
0
,
1.57
],
reshape_out
=
True
)))
reshape_out
=
True
)))
# For Lyft dataset, we usually evaluate the model at the end of training.
# Since the models are trained by 24 epochs by default, we set evaluation
# interval to be 24. Please change the interval accordingly if you do not
# use a default schedule.
train_cfg
=
dict
(
val_interval
=
24
)
mmdet3d/datasets/lyft_dataset.py
View file @
a5d463d7
# Copyright (c) OpenMMLab. All rights reserved.
# Copyright (c) OpenMMLab. All rights reserved.
import
os
from
typing
import
Dict
,
List
import
tempfile
from
os
import
path
as
osp
import
mmcv
import
numpy
as
np
import
numpy
as
np
import
pandas
as
pd
from
lyft_dataset_sdk.lyftdataset
import
LyftDataset
as
Lyft
from
lyft_dataset_sdk.utils.data_classes
import
Box
as
LyftBox
from
pyquaternion
import
Quaternion
from
mmdet3d.core.evaluation.lyft_eval
import
lyft_eval
from
mmdet3d.registry
import
DATASETS
from
mmdet3d.registry
import
DATASETS
from
..core
import
show_result
from
..core.bbox
import
LiDARInstance3DBoxes
from
..core.bbox
import
Box3DMode
,
Coord3DMode
,
LiDARInstance3DBoxes
from
.det3d_dataset
import
Det3DDataset
from
.det3d_dataset
import
Det3DDataset
from
.pipelines
import
Compose
@
DATASETS
.
register_module
()
@
DATASETS
.
register_module
()
...
@@ -29,17 +19,13 @@ class LyftDataset(Det3DDataset):
...
@@ -29,17 +19,13 @@ class LyftDataset(Det3DDataset):
for data downloading.
for data downloading.
Args:
Args:
data_root (str): Path of dataset root.
ann_file (str): Path of annotation file.
ann_file (str): Path of annotation file.
pipeline (list[dict], optional): Pipeline used for data processing.
pipeline (list[dict], optional): Pipeline used for data processing.
Defaults to None.
Defaults to None.
data_root (str): Path of dataset root.
classes (tuple[str], optional): Classes used in the dataset.
Defaults to None.
load_interval (int, optional): Interval of loading the dataset. It is
used to uniformly sample the dataset. Defaults to 1.
modality (dict, optional): Modality to specify the sensor data used
modality (dict, optional): Modality to specify the sensor data used
as input. Defaults to None.
as input. Defaults to None.
box_type_3d (str
, optional
): Type of 3D box of this dataset.
box_type_3d (str): Type of 3D box of this dataset.
Based on the `box_type_3d`, the dataset will encapsulate the box
Based on the `box_type_3d`, the dataset will encapsulate the box
to its original format then converted them to `box_type_3d`.
to its original format then converted them to `box_type_3d`.
Defaults to 'LiDAR' in this dataset. Available options includes
Defaults to 'LiDAR' in this dataset. Available options includes
...
@@ -47,174 +33,62 @@ class LyftDataset(Det3DDataset):
...
@@ -47,174 +33,62 @@ class LyftDataset(Det3DDataset):
- 'LiDAR': Box in LiDAR coordinates.
- 'LiDAR': Box in LiDAR coordinates.
- 'Depth': Box in depth coordinates, usually for indoor dataset.
- 'Depth': Box in depth coordinates, usually for indoor dataset.
- 'Camera': Box in camera coordinates.
- 'Camera': Box in camera coordinates.
filter_empty_gt (bool
, optional
): Whether to filter empty GT.
filter_empty_gt (bool): Whether to filter empty GT.
Defaults to True.
Defaults to True.
test_mode (bool
, optional
): Whether the dataset is in test mode.
test_mode (bool): Whether the dataset is in test mode.
Defaults to False.
Defaults to False.
"""
# noqa: E501
"""
NameMapping
=
{
'bicycle'
:
'bicycle'
,
METAINFO
=
{
'bus'
:
'bus'
,
'CLASSES'
:
'car'
:
'car'
,
(
'car'
,
'truck'
,
'bus'
,
'emergency_vehicle'
,
'other_vehicle'
,
'emergency_vehicle'
:
'emergency_vehicle'
,
'motorcycle'
:
'motorcycle'
,
'other_vehicle'
:
'other_vehicle'
,
'pedestrian'
:
'pedestrian'
,
'truck'
:
'truck'
,
'animal'
:
'animal'
}
DefaultAttribute
=
{
'car'
:
'is_stationary'
,
'truck'
:
'is_stationary'
,
'bus'
:
'is_stationary'
,
'emergency_vehicle'
:
'is_stationary'
,
'other_vehicle'
:
'is_stationary'
,
'motorcycle'
:
'is_stationary'
,
'bicycle'
:
'is_stationary'
,
'pedestrian'
:
'is_stationary'
,
'animal'
:
'is_stationary'
}
CLASSES
=
(
'car'
,
'truck'
,
'bus'
,
'emergency_vehicle'
,
'other_vehicle'
,
'motorcycle'
,
'bicycle'
,
'pedestrian'
,
'animal'
)
'motorcycle'
,
'bicycle'
,
'pedestrian'
,
'animal'
)
}
def
__init__
(
self
,
def
__init__
(
self
,
ann_file
,
data_root
:
str
,
pipeline
=
None
,
ann_file
:
str
,
data_root
=
None
,
pipeline
:
List
[
dict
]
=
None
,
classes
=
None
,
modality
:
Dict
=
dict
(
use_camera
=
False
,
use_lidar
=
True
),
load_interval
=
1
,
box_type_3d
:
str
=
'LiDAR'
,
modality
=
None
,
filter_empty_gt
:
bool
=
True
,
box_type_3d
=
'LiDAR'
,
test_mode
:
bool
=
False
,
filter_empty_gt
=
True
,
test_mode
=
False
,
**
kwargs
):
**
kwargs
):
self
.
load_interval
=
load_interval
assert
box_type_3d
.
lower
()
in
[
'lidar'
]
super
().
__init__
(
super
().
__init__
(
data_root
=
data_root
,
data_root
=
data_root
,
ann_file
=
ann_file
,
ann_file
=
ann_file
,
pipeline
=
pipeline
,
pipeline
=
pipeline
,
classes
=
classes
,
modality
=
modality
,
modality
=
modality
,
box_type_3d
=
box_type_3d
,
box_type_3d
=
box_type_3d
,
filter_empty_gt
=
filter_empty_gt
,
filter_empty_gt
=
filter_empty_gt
,
test_mode
=
test_mode
,
test_mode
=
test_mode
,
**
kwargs
)
**
kwargs
)
if
self
.
modality
is
None
:
def
parse_ann_info
(
self
,
info
:
dict
)
->
dict
:
self
.
modality
=
dict
(
use_camera
=
False
,
use_lidar
=
True
,
use_radar
=
False
,
use_map
=
False
,
use_external
=
False
,
)
def
load_annotations
(
self
,
ann_file
):
"""Load annotations from ann_file.
Args:
ann_file (str): Path of the annotation file.
Returns:
list[dict]: List of annotations sorted by timestamps.
"""
# loading data from a file-like object needs file format
data
=
mmcv
.
load
(
ann_file
,
file_format
=
'pkl'
)
data_infos
=
list
(
sorted
(
data
[
'infos'
],
key
=
lambda
e
:
e
[
'timestamp'
]))
data_infos
=
data_infos
[::
self
.
load_interval
]
self
.
metadata
=
data
[
'metadata'
]
self
.
version
=
self
.
metadata
[
'version'
]
return
data_infos
def
get_data_info
(
self
,
index
):
"""Get data info according to the given index.
Args:
index (int): Index of the sample data to get.
Returns:
dict: Data information that will be passed to the data
preprocessing pipelines. It includes the following keys:
- sample_idx (str): sample index
- pts_filename (str): filename of point clouds
- sweeps (list[dict]): infos of sweeps
- timestamp (float): sample timestamp
- img_filename (str, optional): image filename
- lidar2img (list[np.ndarray], optional): transformations
from lidar to different cameras
- ann_info (dict): annotation info
"""
info
=
self
.
data_infos
[
index
]
# standard protocol modified from SECOND.Pytorch
input_dict
=
dict
(
sample_idx
=
info
[
'token'
],
pts_filename
=
info
[
'lidar_path'
],
sweeps
=
info
[
'sweeps'
],
timestamp
=
info
[
'timestamp'
]
/
1e6
,
)
if
self
.
modality
[
'use_camera'
]:
image_paths
=
[]
lidar2img_rts
=
[]
for
cam_type
,
cam_info
in
info
[
'cams'
].
items
():
image_paths
.
append
(
cam_info
[
'data_path'
])
# obtain lidar to image transformation matrix
lidar2cam_r
=
np
.
linalg
.
inv
(
cam_info
[
'sensor2lidar_rotation'
])
lidar2cam_t
=
cam_info
[
'sensor2lidar_translation'
]
@
lidar2cam_r
.
T
lidar2cam_rt
=
np
.
eye
(
4
)
lidar2cam_rt
[:
3
,
:
3
]
=
lidar2cam_r
.
T
lidar2cam_rt
[
3
,
:
3
]
=
-
lidar2cam_t
intrinsic
=
cam_info
[
'cam_intrinsic'
]
viewpad
=
np
.
eye
(
4
)
viewpad
[:
intrinsic
.
shape
[
0
],
:
intrinsic
.
shape
[
1
]]
=
intrinsic
lidar2img_rt
=
(
viewpad
@
lidar2cam_rt
.
T
)
lidar2img_rts
.
append
(
lidar2img_rt
)
input_dict
.
update
(
dict
(
img_filename
=
image_paths
,
lidar2img
=
lidar2img_rts
,
))
if
not
self
.
test_mode
:
annos
=
self
.
get_ann_info
(
index
)
input_dict
[
'ann_info'
]
=
annos
return
input_dict
def
get_ann_info
(
self
,
index
):
"""Get annotation info according to the given index.
"""Get annotation info according to the given index.
Args:
Args:
in
dex (int): Index of the annotation data to get
.
in
fo (dict): Data information of single data sample
.
Returns:
Returns:
dict:
A
nnotation information consists of the following keys:
dict:
a
nnotation information consists of the following keys:
- gt_bboxes_3d (:obj:`LiDARInstance3DBoxes`):
- gt_bboxes_3d (:obj:`LiDARInstance3DBoxes`):
3D ground truth bboxes.
3D ground truth bboxes.
- gt_labels_3d (np.ndarray): Labels of ground truths.
- gt_labels_3d (np.ndarray): Labels of 3D ground truths.
- gt_names (list[str]): Class names of ground truths.
"""
"""
info
=
self
.
data_infos
[
index
]
ann_info
=
super
().
parse_ann_info
(
info
)
gt_bboxes_3d
=
info
[
'gt_boxes'
]
if
ann_info
is
None
:
gt_names_3d
=
info
[
'gt_names'
]
# empty instance
gt_labels_3d
=
[]
anns_results
=
dict
()
for
cat
in
gt_names_3d
:
anns_results
[
'gt_bboxes_3d'
]
=
np
.
zeros
((
0
,
7
),
dtype
=
np
.
float32
)
if
cat
in
self
.
CLASSES
:
anns_results
[
'gt_labels_3d'
]
=
np
.
zeros
(
0
,
dtype
=
np
.
int64
)
gt_labels_3d
.
append
(
self
.
CLASSES
.
index
(
cat
))
return
anns_results
else
:
gt_bboxes_3d
=
ann_info
[
'gt_bboxes_3d'
]
gt_labels_3d
.
append
(
-
1
)
gt_labels_3d
=
ann_info
[
'gt_labels_3d'
]
gt_labels_3d
=
np
.
array
(
gt_labels_3d
)
if
'gt_shape'
in
info
:
gt_shape
=
info
[
'gt_shape'
]
gt_bboxes_3d
=
np
.
concatenate
([
gt_bboxes_3d
,
gt_shape
],
axis
=-
1
)
# the
lyft
box center is [0.5, 0.5, 0.5], we change it to be
# the
nuscenes
box center is [0.5, 0.5, 0.5], we change it to be
# the same as KITTI (0.5, 0.5, 0)
# the same as KITTI (0.5, 0.5, 0)
gt_bboxes_3d
=
LiDARInstance3DBoxes
(
gt_bboxes_3d
=
LiDARInstance3DBoxes
(
gt_bboxes_3d
,
gt_bboxes_3d
,
...
@@ -222,346 +96,5 @@ class LyftDataset(Det3DDataset):
...
@@ -222,346 +96,5 @@ class LyftDataset(Det3DDataset):
origin
=
(
0.5
,
0.5
,
0.5
)).
convert_to
(
self
.
box_mode_3d
)
origin
=
(
0.5
,
0.5
,
0.5
)).
convert_to
(
self
.
box_mode_3d
)
anns_results
=
dict
(
anns_results
=
dict
(
gt_bboxes_3d
=
gt_bboxes_3d
,
gt_bboxes_3d
=
gt_bboxes_3d
,
gt_labels_3d
=
gt_labels_3d
)
gt_labels_3d
=
gt_labels_3d
,
)
return
anns_results
return
anns_results
def
_format_bbox
(
self
,
results
,
jsonfile_prefix
=
None
):
"""Convert the results to the standard format.
Args:
results (list[dict]): Testing results of the dataset.
jsonfile_prefix (str): The prefix of the output jsonfile.
You can specify the output directory/filename by
modifying the jsonfile_prefix. Default: None.
Returns:
str: Path of the output json file.
"""
lyft_annos
=
{}
mapped_class_names
=
self
.
CLASSES
print
(
'Start to convert detection format...'
)
for
sample_id
,
det
in
enumerate
(
mmcv
.
track_iter_progress
(
results
)):
annos
=
[]
boxes
=
output_to_lyft_box
(
det
)
sample_token
=
self
.
data_infos
[
sample_id
][
'token'
]
boxes
=
lidar_lyft_box_to_global
(
self
.
data_infos
[
sample_id
],
boxes
)
for
i
,
box
in
enumerate
(
boxes
):
name
=
mapped_class_names
[
box
.
label
]
lyft_anno
=
dict
(
sample_token
=
sample_token
,
translation
=
box
.
center
.
tolist
(),
size
=
box
.
wlh
.
tolist
(),
rotation
=
box
.
orientation
.
elements
.
tolist
(),
name
=
name
,
score
=
box
.
score
)
annos
.
append
(
lyft_anno
)
lyft_annos
[
sample_token
]
=
annos
lyft_submissions
=
{
'meta'
:
self
.
modality
,
'results'
:
lyft_annos
,
}
mmcv
.
mkdir_or_exist
(
jsonfile_prefix
)
res_path
=
osp
.
join
(
jsonfile_prefix
,
'results_lyft.json'
)
print
(
'Results writes to'
,
res_path
)
mmcv
.
dump
(
lyft_submissions
,
res_path
)
return
res_path
def
_evaluate_single
(
self
,
result_path
,
logger
=
None
,
metric
=
'bbox'
,
result_name
=
'pts_bbox'
):
"""Evaluation for a single model in Lyft protocol.
Args:
result_path (str): Path of the result file.
logger (logging.Logger | str, optional): Logger used for printing
related information during evaluation. Default: None.
metric (str, optional): Metric name used for evaluation.
Default: 'bbox'.
result_name (str, optional): Result name in the metric prefix.
Default: 'pts_bbox'.
Returns:
dict: Dictionary of evaluation details.
"""
output_dir
=
osp
.
join
(
*
osp
.
split
(
result_path
)[:
-
1
])
lyft
=
Lyft
(
data_path
=
osp
.
join
(
self
.
data_root
,
self
.
version
),
json_path
=
osp
.
join
(
self
.
data_root
,
self
.
version
,
self
.
version
),
verbose
=
True
)
eval_set_map
=
{
'v1.01-train'
:
'val'
,
}
metrics
=
lyft_eval
(
lyft
,
self
.
data_root
,
result_path
,
eval_set_map
[
self
.
version
],
output_dir
,
logger
)
# record metrics
detail
=
dict
()
metric_prefix
=
f
'
{
result_name
}
_Lyft'
for
i
,
name
in
enumerate
(
metrics
[
'class_names'
]):
AP
=
float
(
metrics
[
'mAPs_cate'
][
i
])
detail
[
f
'
{
metric_prefix
}
/
{
name
}
_AP'
]
=
AP
detail
[
f
'
{
metric_prefix
}
/mAP'
]
=
metrics
[
'Final mAP'
]
return
detail
def
format_results
(
self
,
results
,
jsonfile_prefix
=
None
,
csv_savepath
=
None
):
"""Format the results to json (standard format for COCO evaluation).
Args:
results (list[dict]): Testing results of the dataset.
jsonfile_prefix (str): The prefix of json files. It includes
the file path and the prefix of filename, e.g., "a/b/prefix".
If not specified, a temp file will be created. Default: None.
csv_savepath (str): The path for saving csv files.
It includes the file path and the csv filename,
e.g., "a/b/filename.csv". If not specified,
the result will not be converted to csv file.
Returns:
tuple: Returns (result_files, tmp_dir), where `result_files` is a
dict containing the json filepaths, `tmp_dir` is the temporal
directory created for saving json files when
`jsonfile_prefix` is not specified.
"""
assert
isinstance
(
results
,
list
),
'results must be a list'
assert
len
(
results
)
==
len
(
self
),
(
'The length of results is not equal to the dataset len: {} != {}'
.
format
(
len
(
results
),
len
(
self
)))
if
jsonfile_prefix
is
None
:
tmp_dir
=
tempfile
.
TemporaryDirectory
()
jsonfile_prefix
=
osp
.
join
(
tmp_dir
.
name
,
'results'
)
else
:
tmp_dir
=
None
# currently the output prediction results could be in two formats
# 1. list of dict('boxes_3d': ..., 'scores_3d': ..., 'labels_3d': ...)
# 2. list of dict('pts_bbox' or 'img_bbox':
# dict('boxes_3d': ..., 'scores_3d': ..., 'labels_3d': ...))
# this is a workaround to enable evaluation of both formats on Lyft
# refer to https://github.com/open-mmlab/mmdetection3d/issues/449
if
not
(
'pts_bbox'
in
results
[
0
]
or
'img_bbox'
in
results
[
0
]):
result_files
=
self
.
_format_bbox
(
results
,
jsonfile_prefix
)
else
:
# should take the inner dict out of 'pts_bbox' or 'img_bbox' dict
result_files
=
dict
()
for
name
in
results
[
0
]:
print
(
f
'
\n
Formating bboxes of
{
name
}
'
)
results_
=
[
out
[
name
]
for
out
in
results
]
tmp_file_
=
osp
.
join
(
jsonfile_prefix
,
name
)
result_files
.
update
(
{
name
:
self
.
_format_bbox
(
results_
,
tmp_file_
)})
if
csv_savepath
is
not
None
:
self
.
json2csv
(
result_files
[
'pts_bbox'
],
csv_savepath
)
return
result_files
,
tmp_dir
def
evaluate
(
self
,
results
,
metric
=
'bbox'
,
logger
=
None
,
jsonfile_prefix
=
None
,
csv_savepath
=
None
,
result_names
=
[
'pts_bbox'
],
show
=
False
,
out_dir
=
None
,
pipeline
=
None
):
"""Evaluation in Lyft protocol.
Args:
results (list[dict]): Testing results of the dataset.
metric (str | list[str], optional): Metrics to be evaluated.
Default: 'bbox'.
logger (logging.Logger | str, optional): Logger used for printing
related information during evaluation. Default: None.
jsonfile_prefix (str, optional): The prefix of json files including
the file path and the prefix of filename, e.g., "a/b/prefix".
If not specified, a temp file will be created. Default: None.
csv_savepath (str, optional): The path for saving csv files.
It includes the file path and the csv filename,
e.g., "a/b/filename.csv". If not specified,
the result will not be converted to csv file.
result_names (list[str], optional): Result names in the
metric prefix. Default: ['pts_bbox'].
show (bool, optional): Whether to visualize.
Default: False.
out_dir (str, optional): Path to save the visualization results.
Default: None.
pipeline (list[dict], optional): raw data loading for showing.
Default: None.
Returns:
dict[str, float]: Evaluation results.
"""
result_files
,
tmp_dir
=
self
.
format_results
(
results
,
jsonfile_prefix
,
csv_savepath
)
if
isinstance
(
result_files
,
dict
):
results_dict
=
dict
()
for
name
in
result_names
:
print
(
f
'Evaluating bboxes of
{
name
}
'
)
ret_dict
=
self
.
_evaluate_single
(
result_files
[
name
])
results_dict
.
update
(
ret_dict
)
elif
isinstance
(
result_files
,
str
):
results_dict
=
self
.
_evaluate_single
(
result_files
)
if
tmp_dir
is
not
None
:
tmp_dir
.
cleanup
()
if
show
or
out_dir
:
self
.
show
(
results
,
out_dir
,
show
=
show
,
pipeline
=
pipeline
)
return
results_dict
def
_build_default_pipeline
(
self
):
"""Build the default pipeline for this dataset."""
pipeline
=
[
dict
(
type
=
'LoadPointsFromFile'
,
coord_type
=
'LIDAR'
,
load_dim
=
5
,
use_dim
=
5
,
file_client_args
=
dict
(
backend
=
'disk'
)),
dict
(
type
=
'LoadPointsFromMultiSweeps'
,
sweeps_num
=
10
,
file_client_args
=
dict
(
backend
=
'disk'
)),
dict
(
type
=
'DefaultFormatBundle3D'
,
class_names
=
self
.
CLASSES
,
with_label
=
False
),
dict
(
type
=
'Collect3D'
,
keys
=
[
'points'
])
]
return
Compose
(
pipeline
)
def
show
(
self
,
results
,
out_dir
,
show
=
False
,
pipeline
=
None
):
"""Results visualization.
Args:
results (list[dict]): List of bounding boxes results.
out_dir (str): Output directory of visualization result.
show (bool): Whether to visualize the results online.
Default: False.
pipeline (list[dict], optional): raw data loading for showing.
Default: None.
"""
assert
out_dir
is
not
None
,
'Expect out_dir, got none.'
pipeline
=
self
.
_get_pipeline
(
pipeline
)
for
i
,
result
in
enumerate
(
results
):
if
'pts_bbox'
in
result
.
keys
():
result
=
result
[
'pts_bbox'
]
data_info
=
self
.
data_infos
[
i
]
pts_path
=
data_info
[
'lidar_path'
]
file_name
=
osp
.
split
(
pts_path
)[
-
1
].
split
(
'.'
)[
0
]
points
=
self
.
_extract_data
(
i
,
pipeline
,
'points'
).
numpy
()
points
=
Coord3DMode
.
convert_point
(
points
,
Coord3DMode
.
LIDAR
,
Coord3DMode
.
DEPTH
)
inds
=
result
[
'scores_3d'
]
>
0.1
gt_bboxes
=
self
.
get_ann_info
(
i
)[
'gt_bboxes_3d'
].
tensor
.
numpy
()
show_gt_bboxes
=
Box3DMode
.
convert
(
gt_bboxes
,
Box3DMode
.
LIDAR
,
Box3DMode
.
DEPTH
)
pred_bboxes
=
result
[
'boxes_3d'
][
inds
].
tensor
.
numpy
()
show_pred_bboxes
=
Box3DMode
.
convert
(
pred_bboxes
,
Box3DMode
.
LIDAR
,
Box3DMode
.
DEPTH
)
show_result
(
points
,
show_gt_bboxes
,
show_pred_bboxes
,
out_dir
,
file_name
,
show
)
def
json2csv
(
self
,
json_path
,
csv_savepath
):
"""Convert the json file to csv format for submission.
Args:
json_path (str): Path of the result json file.
csv_savepath (str): Path to save the csv file.
"""
results
=
mmcv
.
load
(
json_path
)[
'results'
]
sample_list_path
=
osp
.
join
(
self
.
data_root
,
'sample_submission.csv'
)
data
=
pd
.
read_csv
(
sample_list_path
)
Id_list
=
list
(
data
[
'Id'
])
pred_list
=
list
(
data
[
'PredictionString'
])
cnt
=
0
print
(
'Converting the json to csv...'
)
for
token
in
results
.
keys
():
cnt
+=
1
predictions
=
results
[
token
]
prediction_str
=
''
for
i
in
range
(
len
(
predictions
)):
prediction_str
+=
\
str
(
predictions
[
i
][
'score'
])
+
' '
+
\
str
(
predictions
[
i
][
'translation'
][
0
])
+
' '
+
\
str
(
predictions
[
i
][
'translation'
][
1
])
+
' '
+
\
str
(
predictions
[
i
][
'translation'
][
2
])
+
' '
+
\
str
(
predictions
[
i
][
'size'
][
0
])
+
' '
+
\
str
(
predictions
[
i
][
'size'
][
1
])
+
' '
+
\
str
(
predictions
[
i
][
'size'
][
2
])
+
' '
+
\
str
(
Quaternion
(
list
(
predictions
[
i
][
'rotation'
]))
.
yaw_pitch_roll
[
0
])
+
' '
+
\
predictions
[
i
][
'name'
]
+
' '
prediction_str
=
prediction_str
[:
-
1
]
idx
=
Id_list
.
index
(
token
)
pred_list
[
idx
]
=
prediction_str
df
=
pd
.
DataFrame
({
'Id'
:
Id_list
,
'PredictionString'
:
pred_list
})
mmcv
.
mkdir_or_exist
(
os
.
path
.
dirname
(
csv_savepath
))
df
.
to_csv
(
csv_savepath
,
index
=
False
)
def
output_to_lyft_box
(
detection
):
"""Convert the output to the box class in the Lyft.
Args:
detection (dict): Detection results.
Returns:
list[:obj:`LyftBox`]: List of standard LyftBoxes.
"""
box3d
=
detection
[
'boxes_3d'
]
scores
=
detection
[
'scores_3d'
].
numpy
()
labels
=
detection
[
'labels_3d'
].
numpy
()
box_gravity_center
=
box3d
.
gravity_center
.
numpy
()
box_dims
=
box3d
.
dims
.
numpy
()
box_yaw
=
box3d
.
yaw
.
numpy
()
# our LiDAR coordinate system -> Lyft box coordinate system
lyft_box_dims
=
box_dims
[:,
[
1
,
0
,
2
]]
box_list
=
[]
for
i
in
range
(
len
(
box3d
)):
quat
=
Quaternion
(
axis
=
[
0
,
0
,
1
],
radians
=
box_yaw
[
i
])
box
=
LyftBox
(
box_gravity_center
[
i
],
lyft_box_dims
[
i
],
quat
,
label
=
labels
[
i
],
score
=
scores
[
i
])
box_list
.
append
(
box
)
return
box_list
def
lidar_lyft_box_to_global
(
info
,
boxes
):
"""Convert the box from ego to global coordinate.
Args:
info (dict): Info for a specific sample data, including the
calibration information.
boxes (list[:obj:`LyftBox`]): List of predicted LyftBoxes.
Returns:
list: List of standard LyftBoxes in the global
coordinate.
"""
box_list
=
[]
for
box
in
boxes
:
# Move box to ego vehicle coord system
box
.
rotate
(
Quaternion
(
info
[
'lidar2ego_rotation'
]))
box
.
translate
(
np
.
array
(
info
[
'lidar2ego_translation'
]))
# Move box to global coord system
box
.
rotate
(
Quaternion
(
info
[
'ego2global_rotation'
]))
box
.
translate
(
np
.
array
(
info
[
'ego2global_translation'
]))
box_list
.
append
(
box
)
return
box_list
tests/data/lyft/lyft_infos.pkl
View file @
a5d463d7
No preview for this file type
tests/test_data/test_datasets/test_lyft_dataset.py
0 → 100644
View file @
a5d463d7
# Copyright (c) OpenMMLab. All rights reserved.
import
numpy
as
np
from
mmcv.transforms.base
import
BaseTransform
from
mmengine.data
import
InstanceData
from
mmengine.registry
import
TRANSFORMS
from
mmdet3d.core.bbox
import
LiDARInstance3DBoxes
from
mmdet3d.core.data_structures
import
Det3DDataSample
from
mmdet3d.datasets
import
LyftDataset
def
_generate_nus_dataset_config
():
data_root
=
'tests/data/lyft'
ann_file
=
'lyft_infos.pkl'
classes
=
[
'car'
,
'truck'
,
'bus'
,
'emergency_vehicle'
,
'other_vehicle'
,
'motorcycle'
,
'bicycle'
,
'pedestrian'
,
'animal'
]
if
'Identity'
not
in
TRANSFORMS
:
@
TRANSFORMS
.
register_module
()
class
Identity
(
BaseTransform
):
def
transform
(
self
,
info
):
packed_input
=
dict
(
data_sample
=
Det3DDataSample
())
if
'ann_info'
in
info
:
packed_input
[
'data_sample'
].
gt_instances_3d
=
InstanceData
(
)
packed_input
[
'data_sample'
].
gt_instances_3d
.
labels_3d
=
info
[
'ann_info'
][
'gt_labels_3d'
]
return
packed_input
pipeline
=
[
dict
(
type
=
'Identity'
),
]
modality
=
dict
(
use_lidar
=
True
,
use_camera
=
False
)
data_prefix
=
dict
(
pts
=
'lidar'
,
img
=
''
)
return
data_root
,
ann_file
,
classes
,
data_prefix
,
pipeline
,
modality
def
test_getitem
():
np
.
random
.
seed
(
0
)
data_root
,
ann_file
,
classes
,
data_prefix
,
pipeline
,
modality
=
\
_generate_nus_dataset_config
()
lyft_dataset
=
LyftDataset
(
data_root
,
ann_file
,
data_prefix
=
data_prefix
,
pipeline
=
pipeline
,
metainfo
=
dict
(
CLASSES
=
classes
),
modality
=
modality
)
lyft_dataset
.
prepare_data
(
0
)
input_dict
=
lyft_dataset
.
get_data_info
(
0
)
# assert the the path should contains data_prefix and data_root
assert
input_dict
[
'lidar_points'
][
'lidar_path'
]
==
'tests/data/lyft/lidar/host-a017_lidar1_'
\
'1236118886901125926.bin'
ann_info
=
lyft_dataset
.
parse_ann_info
(
input_dict
)
# assert the keys in ann_info and the type
assert
'gt_labels_3d'
in
ann_info
assert
ann_info
[
'gt_labels_3d'
].
dtype
==
np
.
int64
assert
len
(
ann_info
[
'gt_labels_3d'
])
==
3
assert
'gt_bboxes_3d'
in
ann_info
assert
isinstance
(
ann_info
[
'gt_bboxes_3d'
],
LiDARInstance3DBoxes
)
assert
len
(
lyft_dataset
.
metainfo
[
'CLASSES'
])
==
9
tools/data_converter/update_infos_to_v2.py
View file @
a5d463d7
...
@@ -561,6 +561,110 @@ def update_sunrgbd_infos(pkl_path, out_dir):
...
@@ -561,6 +561,110 @@ def update_sunrgbd_infos(pkl_path, out_dir):
mmcv
.
dump
(
converted_data_info
,
out_path
,
'pkl'
)
mmcv
.
dump
(
converted_data_info
,
out_path
,
'pkl'
)
def
update_lyft_infos
(
pkl_path
,
out_dir
):
print
(
f
'
{
pkl_path
}
will be modified.'
)
if
out_dir
in
pkl_path
:
print
(
f
'Warning, you may overwriting '
f
'the original data
{
pkl_path
}
.'
)
print
(
f
'Reading from input file:
{
pkl_path
}
.'
)
data_list
=
mmcv
.
load
(
pkl_path
)
METAINFO
=
{
'CLASSES'
:
(
'car'
,
'truck'
,
'bus'
,
'emergency_vehicle'
,
'other_vehicle'
,
'motorcycle'
,
'bicycle'
,
'pedestrian'
,
'animal'
),
'DATASET'
:
'Nuscenes'
,
'version'
:
data_list
[
'metadata'
][
'version'
]
}
print
(
'Start updating:'
)
converted_list
=
[]
for
i
,
ori_info_dict
in
enumerate
(
mmcv
.
track_iter_progress
(
data_list
[
'infos'
])):
temp_data_info
=
get_empty_standard_data_info
()
temp_data_info
[
'sample_idx'
]
=
i
temp_data_info
[
'token'
]
=
ori_info_dict
[
'token'
]
temp_data_info
[
'ego2global'
]
=
convert_quaternion_to_matrix
(
ori_info_dict
[
'ego2global_rotation'
],
ori_info_dict
[
'ego2global_translation'
])
temp_data_info
[
'lidar_points'
][
'lidar_path'
]
=
ori_info_dict
[
'lidar_path'
].
split
(
'/'
)[
-
1
]
temp_data_info
[
'lidar_points'
][
'lidar2ego'
]
=
convert_quaternion_to_matrix
(
ori_info_dict
[
'lidar2ego_rotation'
],
ori_info_dict
[
'lidar2ego_translation'
])
# bc-breaking: Timestamp has divided 1e6 in pkl infos.
temp_data_info
[
'timestamp'
]
=
ori_info_dict
[
'timestamp'
]
/
1e6
for
ori_sweep
in
ori_info_dict
[
'sweeps'
]:
temp_lidar_sweep
=
get_single_lidar_sweep
()
temp_lidar_sweep
[
'lidar_points'
][
'lidar2ego'
]
=
convert_quaternion_to_matrix
(
ori_sweep
[
'sensor2ego_rotation'
],
ori_sweep
[
'sensor2ego_translation'
])
temp_lidar_sweep
[
'ego2global'
]
=
convert_quaternion_to_matrix
(
ori_sweep
[
'ego2global_rotation'
],
ori_sweep
[
'ego2global_translation'
])
lidar2sensor
=
np
.
eye
(
4
)
lidar2sensor
[:
3
,
:
3
]
=
ori_sweep
[
'sensor2lidar_rotation'
].
T
lidar2sensor
[:
3
,
3
]
=
-
ori_sweep
[
'sensor2lidar_translation'
]
temp_lidar_sweep
[
'lidar_points'
][
'lidar2sensor'
]
=
lidar2sensor
.
astype
(
np
.
float32
).
tolist
()
# bc-breaking: Timestamp has divided 1e6 in pkl infos.
temp_lidar_sweep
[
'timestamp'
]
=
ori_sweep
[
'timestamp'
]
/
1e6
temp_lidar_sweep
[
'lidar_points'
][
'lidar_path'
]
=
ori_sweep
[
'data_path'
]
temp_lidar_sweep
[
'sample_data_token'
]
=
ori_sweep
[
'sample_data_token'
]
temp_data_info
[
'lidar_sweeps'
].
append
(
temp_lidar_sweep
)
temp_data_info
[
'images'
]
=
{}
for
cam
in
ori_info_dict
[
'cams'
]:
empty_img_info
=
get_empty_img_info
()
empty_img_info
[
'img_path'
]
=
ori_info_dict
[
'cams'
][
cam
][
'data_path'
].
split
(
'/'
)[
-
1
]
empty_img_info
[
'cam2img'
]
=
ori_info_dict
[
'cams'
][
cam
][
'cam_intrinsic'
].
tolist
()
empty_img_info
[
'sample_data_token'
]
=
ori_info_dict
[
'cams'
][
cam
][
'sample_data_token'
]
empty_img_info
[
'timestamp'
]
=
ori_info_dict
[
'cams'
][
cam
][
'timestamp'
]
/
1e6
empty_img_info
[
'cam2ego'
]
=
convert_quaternion_to_matrix
(
ori_info_dict
[
'cams'
][
cam
][
'sensor2ego_rotation'
],
ori_info_dict
[
'cams'
][
cam
][
'sensor2ego_translation'
])
lidar2sensor
=
np
.
eye
(
4
)
lidar2sensor
[:
3
,
:
3
]
=
ori_info_dict
[
'cams'
][
cam
][
'sensor2lidar_rotation'
].
T
lidar2sensor
[:
3
,
3
]
=
-
ori_info_dict
[
'cams'
][
cam
][
'sensor2lidar_translation'
]
empty_img_info
[
'lidar2cam'
]
=
lidar2sensor
.
astype
(
np
.
float32
).
tolist
()
temp_data_info
[
'images'
][
cam
]
=
empty_img_info
num_instances
=
ori_info_dict
[
'gt_boxes'
].
shape
[
0
]
ignore_class_name
=
set
()
for
i
in
range
(
num_instances
):
empty_instance
=
get_empty_instance
()
empty_instance
[
'bbox_3d'
]
=
ori_info_dict
[
'gt_boxes'
][
i
,
:].
tolist
()
if
ori_info_dict
[
'gt_names'
][
i
]
in
METAINFO
[
'CLASSES'
]:
empty_instance
[
'bbox_label'
]
=
METAINFO
[
'CLASSES'
].
index
(
ori_info_dict
[
'gt_names'
][
i
])
else
:
ignore_class_name
.
add
(
ori_info_dict
[
'gt_names'
][
i
])
empty_instance
[
'bbox_label'
]
=
-
1
empty_instance
[
'bbox_label_3d'
]
=
copy
.
deepcopy
(
empty_instance
[
'bbox_label'
])
empty_instance
=
clear_instance_unused_keys
(
empty_instance
)
temp_data_info
[
'instances'
].
append
(
empty_instance
)
temp_data_info
,
_
=
clear_data_info_unused_keys
(
temp_data_info
)
converted_list
.
append
(
temp_data_info
)
pkl_name
=
pkl_path
.
split
(
'/'
)[
-
1
]
out_path
=
osp
.
join
(
out_dir
,
pkl_name
)
print
(
f
'Writing to output file:
{
out_path
}
.'
)
print
(
f
'ignore classes:
{
ignore_class_name
}
'
)
converted_data_info
=
dict
(
metainfo
=
METAINFO
,
data_list
=
converted_list
)
mmcv
.
dump
(
converted_data_info
,
out_path
,
'pkl'
)
def
parse_args
():
def
parse_args
():
parser
=
argparse
.
ArgumentParser
(
description
=
'Arg parser for data coords '
parser
=
argparse
.
ArgumentParser
(
description
=
'Arg parser for data coords '
'update due to coords sys refactor.'
)
'update due to coords sys refactor.'
)
...
@@ -592,6 +696,8 @@ def main():
...
@@ -592,6 +696,8 @@ def main():
update_scannet_infos
(
pkl_path
=
args
.
pkl
,
out_dir
=
args
.
out_dir
)
update_scannet_infos
(
pkl_path
=
args
.
pkl
,
out_dir
=
args
.
out_dir
)
elif
args
.
dataset
.
lower
()
==
'sunrgbd'
:
elif
args
.
dataset
.
lower
()
==
'sunrgbd'
:
update_sunrgbd_infos
(
pkl_path
=
args
.
pkl
,
out_dir
=
args
.
out_dir
)
update_sunrgbd_infos
(
pkl_path
=
args
.
pkl
,
out_dir
=
args
.
out_dir
)
elif
args
.
dataset
.
lower
()
==
'lyft'
:
update_lyft_infos
(
pkl_path
=
args
.
pkl
,
out_dir
=
args
.
out_dir
)
elif
args
.
dataset
.
lower
()
==
'nuscenes'
:
elif
args
.
dataset
.
lower
()
==
'nuscenes'
:
update_nuscenes_infos
(
pkl_path
=
args
.
pkl
,
out_dir
=
args
.
out_dir
)
update_nuscenes_infos
(
pkl_path
=
args
.
pkl
,
out_dir
=
args
.
out_dir
)
else
:
else
:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment