Commit 53435c62 authored by Yezhen Cong's avatar Yezhen Cong Committed by Tai-Wang
Browse files

[Refactor] Refactor code structure and docstrings (#803)

* refactor points_in_boxes

* Merge same functions of three boxes

* More docstring fixes and unify x/y/z size

* Add "optional" and fix "Default"

* Add "optional" and fix "Default"

* Add "optional" and fix "Default"

* Add "optional" and fix "Default"

* Add "optional" and fix "Default"

* Remove None in function param type

* Fix unittest

* Add comments for NMS functions

* Merge methods of Points

* Add unittest

* Add optional and default value

* Fix box conversion and add unittest

* Fix comments

* Add unit test

* Indent

* Fix CI

* Remove useless \\

* Remove useless \\

* Remove useless \\

* Remove useless \\

* Remove useless \\

* Add unit test for box bev

* More unit tests and refine docstrings in box_np_ops

* Fix comment

* Add deprecation warning
parent 4f36084f
......@@ -110,8 +110,8 @@ def export(mesh_file,
instance_ids[verts] = object_id
if object_id not in object_id_to_label_id:
object_id_to_label_id[object_id] = label_ids[verts][0]
# bbox format is [x, y, z, dx, dy, dz, label_id]
# [x, y, z] is gravity center of bbox, [dx, dy, dz] is axis-aligned
# bbox format is [x, y, z, x_size, y_size, z_size, label_id]
# [x, y, z] is gravity center of bbox, [x_size, y_size, z_size] is axis-aligned
# [label_id] is semantic label id in 'nyu40id' standard
# Note: since 3D bbox is axis-aligned, the yaw is 0.
unaligned_bboxes = extract_bbox(mesh_vertices, object_id_to_segs,
......
......@@ -460,15 +460,17 @@ def show_result_meshlab(data,
data (dict): Contain data from pipeline.
result (dict): Predicted result from model.
out_dir (str): Directory to save visualized result.
score_thr (float): Minimum score of bboxes to be shown. Default: 0.0
show (bool): Visualize the results online. Defaults to False.
snapshot (bool): Whether to save the online results. Defaults to False.
task (str): Distinguish which task result to visualize. Currently we
support 3D detection, multi-modality detection and 3D segmentation.
Defaults to 'det'.
palette (list[list[int]]] | np.ndarray | None): The palette of
segmentation map. If None is given, random palette will be
generated. Defaults to None.
score_thr (float, optional): Minimum score of bboxes to be shown.
Default: 0.0
show (bool, optional): Visualize the results online. Defaults to False.
snapshot (bool, optional): Whether to save the online results.
Defaults to False.
task (str, optional): Distinguish which task result to visualize.
Currently we support 3D detection, multi-modality detection and
3D segmentation. Defaults to 'det'.
palette (list[list[int]]] | np.ndarray, optional): The palette
of segmentation map. If None is given, random palette will be
generated. Defaults to None.
"""
assert task in ['det', 'multi_modality-det', 'seg', 'mono-det'], \
f'unsupported visualization task {task}'
......
......@@ -22,9 +22,9 @@ def single_gpu_test(model,
Args:
model (nn.Module): Model to be tested.
data_loader (nn.Dataloader): Pytorch data loader.
show (bool): Whether to save viualization results.
show (bool, optional): Whether to save viualization results.
Default: True.
out_dir (str): The path to save visualization results.
out_dir (str, optional): The path to save visualization results.
Default: None.
Returns:
......
......@@ -19,15 +19,21 @@ class Anchor3DRangeGenerator(object):
ranges (list[list[float]]): Ranges of different anchors.
The ranges are the same across different feature levels. But may
vary for different anchor sizes if size_per_range is True.
sizes (list[list[float]]): 3D sizes of anchors.
scales (list[int]): Scales of anchors in different feature levels.
rotations (list[float]): Rotations of anchors in a feature grid.
custom_values (tuple[float]): Customized values of that anchor. For
example, in nuScenes the anchors have velocities.
reshape_out (bool): Whether to reshape the output into (N x 4).
size_per_range: Whether to use separate ranges for different sizes.
If size_per_range is True, the ranges should have the same length
as the sizes, if not, it will be duplicated.
sizes (list[list[float]], optional): 3D sizes of anchors.
Defaults to [[3.9, 1.6, 1.56]].
scales (list[int], optional): Scales of anchors in different feature
levels. Defaults to [1].
rotations (list[float], optional): Rotations of anchors in a feature
grid. Defaults to [0, 1.5707963].
custom_values (tuple[float], optional): Customized values of that
anchor. For example, in nuScenes the anchors have velocities.
Defaults to ().
reshape_out (bool, optional): Whether to reshape the output into
(N x 4). Defaults to True.
size_per_range (bool, optional): Whether to use separate ranges for
different sizes. If size_per_range is True, the ranges should have
the same length as the sizes, if not, it will be duplicated.
Defaults to True.
"""
def __init__(self,
......@@ -86,13 +92,14 @@ class Anchor3DRangeGenerator(object):
Args:
featmap_sizes (list[tuple]): List of feature map sizes in
multiple feature levels.
device (str): Device where the anchors will be put on.
device (str, optional): Device where the anchors will be put on.
Defaults to 'cuda'.
Returns:
list[torch.Tensor]: Anchors in multiple feature levels. \
The sizes of each tensor should be [N, 4], where \
N = width * height * num_base_anchors, width and height \
are the sizes of the corresponding feature lavel, \
list[torch.Tensor]: Anchors in multiple feature levels.
The sizes of each tensor should be [N, 4], where
N = width * height * num_base_anchors, width and height
are the sizes of the corresponding feature lavel,
num_base_anchors is the number of anchors for that level.
"""
assert self.num_levels == len(featmap_sizes)
......@@ -161,14 +168,18 @@ class Anchor3DRangeGenerator(object):
shape [6]. The order is consistent with that of anchors, i.e.,
(x_min, y_min, z_min, x_max, y_max, z_max).
scale (float | int, optional): The scale factor of anchors.
sizes (list[list] | np.ndarray | torch.Tensor): Anchor size with
shape [N, 3], in order of x, y, z.
rotations (list[float] | np.ndarray | torch.Tensor): Rotations of
anchors in a single feature grid.
Defaults to 1.
sizes (list[list] | np.ndarray | torch.Tensor, optional):
Anchor size with shape [N, 3], in order of x, y, z.
Defaults to [[3.9, 1.6, 1.56]].
rotations (list[float] | np.ndarray | torch.Tensor, optional):
Rotations of anchors in a single feature grid.
Defaults to [0, 1.5707963].
device (str): Devices that the anchors will be put on.
Defaults to 'cuda'.
Returns:
torch.Tensor: Anchors with shape \
torch.Tensor: Anchors with shape
[*feature_size, num_sizes, num_rots, 7].
"""
if len(feature_size) == 2:
......@@ -231,10 +242,10 @@ class AlignedAnchor3DRangeGenerator(Anchor3DRangeGenerator):
up corner to distribute anchors.
Args:
anchor_corner (bool): Whether to align with the corner of the voxel
grid. By default it is False and the anchor's center will be
anchor_corner (bool, optional): Whether to align with the corner of the
voxel grid. By default it is False and the anchor's center will be
the same as the corresponding voxel's center, which is also the
center of the corresponding greature grid.
center of the corresponding greature grid. Defaults to False.
"""
def __init__(self, align_corner=False, **kwargs):
......@@ -256,15 +267,18 @@ class AlignedAnchor3DRangeGenerator(Anchor3DRangeGenerator):
anchor_range (torch.Tensor | list[float]): Range of anchors with
shape [6]. The order is consistent with that of anchors, i.e.,
(x_min, y_min, z_min, x_max, y_max, z_max).
scale (float | int, optional): The scale factor of anchors.
sizes (list[list] | np.ndarray | torch.Tensor): Anchor size with
shape [N, 3], in order of x, y, z.
rotations (list[float] | np.ndarray | torch.Tensor): Rotations of
anchors in a single feature grid.
device (str): Devices that the anchors will be put on.
scale (float | int): The scale factor of anchors.
sizes (list[list] | np.ndarray | torch.Tensor, optional):
Anchor size with shape [N, 3], in order of x, y, z.
Defaults to [[3.9, 1.6, 1.56]].
rotations (list[float] | np.ndarray | torch.Tensor, optional):
Rotations of anchors in a single feature grid.
Defaults to [0, 1.5707963].
device (str, optional): Devices that the anchors will be put on.
Defaults to 'cuda'.
Returns:
torch.Tensor: Anchors with shape \
torch.Tensor: Anchors with shape
[*feature_size, num_sizes, num_rots, 7].
"""
if len(feature_size) == 2:
......@@ -334,7 +348,7 @@ class AlignedAnchor3DRangeGeneratorPerCls(AlignedAnchor3DRangeGenerator):
Note that feature maps of different classes may be different.
Args:
kwargs (dict): Arguments are the same as those in \
kwargs (dict): Arguments are the same as those in
:class:`AlignedAnchor3DRangeGenerator`.
"""
......@@ -347,15 +361,16 @@ class AlignedAnchor3DRangeGeneratorPerCls(AlignedAnchor3DRangeGenerator):
"""Generate grid anchors in multiple feature levels.
Args:
featmap_sizes (list[tuple]): List of feature map sizes for \
featmap_sizes (list[tuple]): List of feature map sizes for
different classes in a single feature level.
device (str): Device where the anchors will be put on.
device (str, optional): Device where the anchors will be put on.
Defaults to 'cuda'.
Returns:
list[list[torch.Tensor]]: Anchors in multiple feature levels. \
Note that in this anchor generator, we currently only \
support single feature level. The sizes of each tensor \
should be [num_sizes/ranges*num_rots*featmap_size, \
list[list[torch.Tensor]]: Anchors in multiple feature levels.
Note that in this anchor generator, we currently only
support single feature level. The sizes of each tensor
should be [num_sizes/ranges*num_rots*featmap_size,
box_code_size].
"""
multi_level_anchors = []
......@@ -371,7 +386,7 @@ class AlignedAnchor3DRangeGeneratorPerCls(AlignedAnchor3DRangeGenerator):
This function is usually called by method ``self.grid_anchors``.
Args:
featmap_sizes (list[tuple]): List of feature map sizes for \
featmap_sizes (list[tuple]): List of feature map sizes for
different classes in a single feature level.
scale (float): Scale factor of the anchors in the current level.
device (str, optional): Device the tensor will be put on.
......
# Copyright (c) OpenMMLab. All rights reserved.
# TODO: clean the functions in this file and move the APIs into box structures
# in the future
# NOTICE: All functions in this file are valid for LiDAR or depth boxes only
# if we use default parameters.
import numba
import numpy as np
......@@ -47,13 +50,13 @@ def box_camera_to_lidar(data, r_rect, velo2cam):
np.ndarray, shape=[N, 3]: Boxes in lidar coordinate.
"""
xyz = data[:, 0:3]
dx, dy, dz = data[:, 3:4], data[:, 4:5], data[:, 5:6]
x_size, y_size, z_size = data[:, 3:4], data[:, 4:5], data[:, 5:6]
r = data[:, 6:7]
xyz_lidar = camera_to_lidar(xyz, r_rect, velo2cam)
# yaw and dims also needs to be converted
r_new = -r - np.pi / 2
r_new = limit_period(r_new, period=np.pi * 2)
return np.concatenate([xyz_lidar, dx, dz, dy, r_new], axis=1)
return np.concatenate([xyz_lidar, x_size, z_size, y_size, r_new], axis=1)
def corners_nd(dims, origin=0.5):
......@@ -92,7 +95,7 @@ def corners_nd(dims, origin=0.5):
def center_to_corner_box2d(centers, dims, angles=None, origin=0.5):
"""Convert kitti locations, dimensions and angles to corners.
format: center(xy), dims(xy), angles(clockwise when positive)
format: center(xy), dims(xy), angles(counterclockwise when positive)
Args:
centers (np.ndarray): Locations in kitti label file with shape (N, 2).
......@@ -187,7 +190,7 @@ def center_to_corner_box3d(centers,
np.ndarray: Corners with the shape of (N, 8, 3).
"""
# 'length' in kitti format is in x axis.
# yzx(hwl)(kitti label file)<->xyz(lhw)(camera)<->z(-x)(-y)(wlh)(lidar)
# yzx(hwl)(kitti label file)<->xyz(lhw)(camera)<->z(-x)(-y)(lwh)(lidar)
# center in kitti format is [0.5, 1.0, 0.5] in xyz.
corners = corners_nd(dims, origin=origin)
# corners: [N, 8, 3]
......@@ -348,7 +351,10 @@ def corner_to_surfaces_3d(corners):
def points_in_rbbox(points, rbbox, z_axis=2, origin=(0.5, 0.5, 0)):
"""Check points in rotated bbox and return indicces.
"""Check points in rotated bbox and return indices.
Note:
This function is for counterclockwise boxes.
Args:
points (np.ndarray, shape=[N, 3+dim]): Points to query.
......@@ -404,7 +410,7 @@ def create_anchors_3d_range(feature_size,
rotations (list[float] | np.ndarray | torch.Tensor, optional):
Rotations of anchors in a single feature grid.
Defaults to (0, np.pi / 2).
dtype (type, optional): Data type. Default to np.float32.
dtype (type, optional): Data type. Defaults to np.float32.
Returns:
np.ndarray: Range based anchors with shape of
......@@ -478,6 +484,9 @@ def iou_jit(boxes, query_boxes, mode='iou', eps=0.0):
"""Calculate box iou. Note that jit version runs ~10x faster than the
box_overlaps function in mmdet3d.core.evaluation.
Note:
This function is for counterclockwise boxes.
Args:
boxes (np.ndarray): Input bounding boxes with shape of (N, 4).
query_boxes (np.ndarray): Query boxes with shape of (K, 4).
......@@ -515,7 +524,10 @@ def iou_jit(boxes, query_boxes, mode='iou', eps=0.0):
def projection_matrix_to_CRT_kitti(proj):
"""Split projection matrix of kitti.
"""Split projection matrix of KITTI.
Note:
This function is for KITTI only.
P = C @ [R|T]
C is upper triangular matrix, so we need to inverse CR and use QR
......@@ -541,6 +553,9 @@ def projection_matrix_to_CRT_kitti(proj):
def remove_outside_points(points, rect, Trv2c, P2, image_shape):
"""Remove points which are outside of image.
Note:
This function is for KITTI only.
Args:
points (np.ndarray, shape=[N, 3+dims]): Total points.
rect (np.ndarray, shape=[4, 4]): Matrix to project points in
......@@ -691,7 +706,7 @@ def points_in_convex_polygon_3d_jit(points,
@numba.jit
def points_in_convex_polygon_jit(points, polygon, clockwise=True):
def points_in_convex_polygon_jit(points, polygon, clockwise=False):
"""Check points is in 2d convex polygons. True when point in polygon.
Args:
......@@ -747,10 +762,13 @@ def boxes3d_to_corners3d_lidar(boxes3d, bottom_center=True):
|/ |/
2 -------- 1
Note:
This function is for LiDAR boxes only.
Args:
boxes3d (np.ndarray): Boxes with shape of (N, 7)
[x, y, z, dx, dy, dz, ry] in LiDAR coords, see the definition of
ry in KITTI dataset.
[x, y, z, x_size, y_size, z_size, ry] in LiDAR coords,
see the definition of ry in KITTI dataset.
bottom_center (bool, optional): Whether z is on the bottom center
of object. Defaults to True.
......@@ -758,25 +776,25 @@ def boxes3d_to_corners3d_lidar(boxes3d, bottom_center=True):
np.ndarray: Box corners with the shape of [N, 8, 3].
"""
boxes_num = boxes3d.shape[0]
dx, dy, dz = boxes3d[:, 3], boxes3d[:, 4], boxes3d[:, 5]
x_size, y_size, z_size = boxes3d[:, 3], boxes3d[:, 4], boxes3d[:, 5]
x_corners = np.array([
dx / 2., -dx / 2., -dx / 2., dx / 2., dx / 2., -dx / 2., -dx / 2.,
dx / 2.
x_size / 2., -x_size / 2., -x_size / 2., x_size / 2., x_size / 2.,
-x_size / 2., -x_size / 2., x_size / 2.
],
dtype=np.float32).T
y_corners = np.array([
-dy / 2., -dy / 2., dy / 2., dy / 2., -dy / 2., -dy / 2., dy / 2.,
dy / 2.
-y_size / 2., -y_size / 2., y_size / 2., y_size / 2., -y_size / 2.,
-y_size / 2., y_size / 2., y_size / 2.
],
dtype=np.float32).T
if bottom_center:
z_corners = np.zeros((boxes_num, 8), dtype=np.float32)
z_corners[:, 4:8] = dz.reshape(boxes_num, 1).repeat(
z_corners[:, 4:8] = z_size.reshape(boxes_num, 1).repeat(
4, axis=1) # (N, 8)
else:
z_corners = np.array([
-dz / 2., -dz / 2., -dz / 2., -dz / 2., dz / 2., dz / 2., dz / 2.,
dz / 2.
-z_size / 2., -z_size / 2., -z_size / 2., -z_size / 2.,
z_size / 2., z_size / 2., z_size / 2., z_size / 2.
],
dtype=np.float32).T
......
......@@ -25,7 +25,7 @@ class AnchorFreeBBoxCoder(PartialBinBasedBBoxCoder):
"""Encode ground truth to prediction targets.
Args:
gt_bboxes_3d (BaseInstance3DBoxes): Ground truth bboxes \
gt_bboxes_3d (BaseInstance3DBoxes): Ground truth bboxes
with shape (n, 7).
gt_labels_3d (torch.Tensor): Ground truth classes.
......
......@@ -13,12 +13,12 @@ class CenterPointBBoxCoder(BaseBBoxCoder):
pc_range (list[float]): Range of point cloud.
out_size_factor (int): Downsample factor of the model.
voxel_size (list[float]): Size of voxel.
post_center_range (list[float]): Limit of the center.
post_center_range (list[float], optional): Limit of the center.
Default: None.
max_num (int): Max number to be kept. Default: 100.
score_threshold (float): Threshold to filter boxes based on score.
Default: None.
code_size (int): Code size of bboxes. Default: 9
max_num (int, optional): Max number to be kept. Default: 100.
score_threshold (float, optional): Threshold to filter boxes
based on score. Default: None.
code_size (int, optional): Code size of bboxes. Default: 9
"""
def __init__(self,
......@@ -45,7 +45,8 @@ class CenterPointBBoxCoder(BaseBBoxCoder):
feats (torch.Tensor): Features to be transposed and gathered
with the shape of [B, 2, W, H].
inds (torch.Tensor): Indexes with the shape of [B, N].
feat_masks (torch.Tensor): Mask of the feats. Default: None.
feat_masks (torch.Tensor, optional): Mask of the feats.
Default: None.
Returns:
torch.Tensor: Gathered feats.
......@@ -64,7 +65,7 @@ class CenterPointBBoxCoder(BaseBBoxCoder):
Args:
scores (torch.Tensor): scores with the shape of [B, N, W, H].
K (int): Number to be kept. Defaults to 80.
K (int, optional): Number to be kept. Defaults to 80.
Returns:
tuple[torch.Tensor]
......@@ -135,9 +136,9 @@ class CenterPointBBoxCoder(BaseBBoxCoder):
dim (torch.Tensor): Dim of the boxes with the shape of
[B, 1, W, H].
vel (torch.Tensor): Velocity with the shape of [B, 1, W, H].
reg (torch.Tensor): Regression value of the boxes in 2D with
the shape of [B, 2, W, H]. Default: None.
task_id (int): Index of task. Default: -1.
reg (torch.Tensor, optional): Regression value of the boxes in
2D with the shape of [B, 2, W, H]. Default: None.
task_id (int, optional): Index of task. Default: -1.
Returns:
list[dict]: Decoded boxes.
......
......@@ -19,9 +19,9 @@ class DeltaXYZWLHRBBoxCoder(BaseBBoxCoder):
@staticmethod
def encode(src_boxes, dst_boxes):
"""Get box regression transformation deltas (dx, dy, dz, dw, dh, dl,
dr, dv*) that can be used to transform the `src_boxes` into the
`target_boxes`.
"""Get box regression transformation deltas (dx, dy, dz, dx_size,
dy_size, dz_size, dr, dv*) that can be used to transform the
`src_boxes` into the `target_boxes`.
Args:
src_boxes (torch.Tensor): source boxes, e.g., object proposals.
......@@ -56,13 +56,13 @@ class DeltaXYZWLHRBBoxCoder(BaseBBoxCoder):
@staticmethod
def decode(anchors, deltas):
"""Apply transformation `deltas` (dx, dy, dz, dw, dh, dl, dr, dv*) to
`boxes`.
"""Apply transformation `deltas` (dx, dy, dz, dx_size, dy_size,
dz_size, dr, dv*) to `boxes`.
Args:
anchors (torch.Tensor): Parameters of anchors with shape (N, 7).
deltas (torch.Tensor): Encoded boxes with shape
(N, 7+n) [x, y, z, w, l, h, r, velo*].
(N, 7+n) [x, y, z, x_size, y_size, z_size, r, velo*].
Returns:
torch.Tensor: Decoded boxes.
......
......@@ -14,9 +14,10 @@ class GroupFree3DBBoxCoder(PartialBinBasedBBoxCoder):
num_dir_bins (int): Number of bins to encode direction angle.
num_sizes (int): Number of size clusters.
mean_sizes (list[list[int]]): Mean size of bboxes in each class.
with_rot (bool): Whether the bbox is with rotation. Defaults to True.
size_cls_agnostic (bool): Whether the predicted size is class-agnostic.
with_rot (bool, optional): Whether the bbox is with rotation.
Defaults to True.
size_cls_agnostic (bool, optional): Whether the predicted size is
class-agnostic. Defaults to True.
"""
def __init__(self,
......@@ -36,7 +37,7 @@ class GroupFree3DBBoxCoder(PartialBinBasedBBoxCoder):
"""Encode ground truth to prediction targets.
Args:
gt_bboxes_3d (BaseInstance3DBoxes): Ground truth bboxes \
gt_bboxes_3d (BaseInstance3DBoxes): Ground truth bboxes
with shape (n, 7).
gt_labels_3d (torch.Tensor): Ground truth classes.
......@@ -76,7 +77,7 @@ class GroupFree3DBBoxCoder(PartialBinBasedBBoxCoder):
- size_class: predicted bbox size class.
- size_res: predicted bbox size residual.
- size: predicted class-agnostic bbox size
prefix (str): Decode predictions with specific prefix.
prefix (str, optional): Decode predictions with specific prefix.
Defaults to ''.
Returns:
......@@ -122,7 +123,7 @@ class GroupFree3DBBoxCoder(PartialBinBasedBBoxCoder):
cls_preds (torch.Tensor): Class predicted features to split.
reg_preds (torch.Tensor): Regression predicted features to split.
base_xyz (torch.Tensor): Coordinates of points.
prefix (str): Decode predictions with specific prefix.
prefix (str, optional): Decode predictions with specific prefix.
Defaults to ''.
Returns:
......
......@@ -29,7 +29,7 @@ class PartialBinBasedBBoxCoder(BaseBBoxCoder):
"""Encode ground truth to prediction targets.
Args:
gt_bboxes_3d (BaseInstance3DBoxes): Ground truth bboxes \
gt_bboxes_3d (BaseInstance3DBoxes): Ground truth bboxes
with shape (n, 7).
gt_labels_3d (torch.Tensor): Ground truth classes.
......
......@@ -32,16 +32,16 @@ class BboxOverlapsNearest3D(object):
Args:
bboxes1 (torch.Tensor): shape (N, 7+N)
[x, y, z, dx, dy, dz, ry, v].
[x, y, z, x_size, y_size, z_size, ry, v].
bboxes2 (torch.Tensor): shape (M, 7+N)
[x, y, z, dx, dy, dz, ry, v].
[x, y, z, x_size, y_size, z_size, ry, v].
mode (str): "iou" (intersection over union) or iof
(intersection over foreground).
is_aligned (bool): Whether the calculation is aligned.
Return:
torch.Tensor: If ``is_aligned`` is ``True``, return ious between \
bboxes1 and bboxes2 with shape (M, N). If ``is_aligned`` is \
torch.Tensor: If ``is_aligned`` is ``True``, return ious between
bboxes1 and bboxes2 with shape (M, N). If ``is_aligned`` is
``False``, return shape is M.
"""
return bbox_overlaps_nearest_3d(bboxes1, bboxes2, mode, is_aligned,
......@@ -76,13 +76,15 @@ class BboxOverlaps3D(object):
calculate the actual 3D IoUs of boxes.
Args:
bboxes1 (torch.Tensor): shape (N, 7+C) [x, y, z, dx, dy, dz, ry].
bboxes2 (torch.Tensor): shape (M, 7+C) [x, y, z, dx, dy, dz, ry].
bboxes1 (torch.Tensor): with shape (N, 7+C),
(x, y, z, x_size, y_size, z_size, ry, v*).
bboxes2 (torch.Tensor): with shape (M, 7+C),
(x, y, z, x_size, y_size, z_size, ry, v*).
mode (str): "iou" (intersection over union) or
iof (intersection over foreground).
Return:
torch.Tensor: Bbox overlaps results of bboxes1 and bboxes2 \
torch.Tensor: Bbox overlaps results of bboxes1 and bboxes2
with shape (M, N) (aligned mode is not supported currently).
"""
return bbox_overlaps_3d(bboxes1, bboxes2, mode, self.coordinate)
......@@ -112,15 +114,17 @@ def bbox_overlaps_nearest_3d(bboxes1,
aligned pair of bboxes1 and bboxes2.
Args:
bboxes1 (torch.Tensor): shape (N, 7+C) [x, y, z, dx, dy, dz, ry, v].
bboxes2 (torch.Tensor): shape (M, 7+C) [x, y, z, dx, dy, dz, ry, v].
bboxes1 (torch.Tensor): with shape (N, 7+C),
(x, y, z, x_size, y_size, z_size, ry, v*).
bboxes2 (torch.Tensor): with shape (M, 7+C),
(x, y, z, x_size, y_size, z_size, ry, v*).
mode (str): "iou" (intersection over union) or iof
(intersection over foreground).
is_aligned (bool): Whether the calculation is aligned
Return:
torch.Tensor: If ``is_aligned`` is ``True``, return ious between \
bboxes1 and bboxes2 with shape (M, N). If ``is_aligned`` is \
torch.Tensor: If ``is_aligned`` is ``True``, return ious between
bboxes1 and bboxes2 with shape (M, N). If ``is_aligned`` is
``False``, return shape is M.
"""
assert bboxes1.size(-1) == bboxes2.size(-1) >= 7
......@@ -150,14 +154,16 @@ def bbox_overlaps_3d(bboxes1, bboxes2, mode='iou', coordinate='camera'):
calculate the actual IoUs of boxes.
Args:
bboxes1 (torch.Tensor): shape (N, 7+C) [x, y, z, dx, dy, dz, ry].
bboxes2 (torch.Tensor): shape (M, 7+C) [x, y, z, dx, dy, dz, ry].
bboxes1 (torch.Tensor): with shape (N, 7+C),
(x, y, z, x_size, y_size, z_size, ry, v*).
bboxes2 (torch.Tensor): with shape (M, 7+C),
(x, y, z, x_size, y_size, z_size, ry, v*).
mode (str): "iou" (intersection over union) or
iof (intersection over foreground).
coordinate (str): 'camera' or 'lidar' coordinate system.
Return:
torch.Tensor: Bbox overlaps results of bboxes1 and bboxes2 \
torch.Tensor: Bbox overlaps results of bboxes1 and bboxes2
with shape (M, N) (aligned mode is not supported currently).
"""
assert bboxes1.size(-1) == bboxes2.size(-1) >= 7
......@@ -187,7 +193,7 @@ class AxisAlignedBboxOverlaps3D(object):
mode (str): "iou" (intersection over union) or "giou" (generalized
intersection over union).
is_aligned (bool, optional): If True, then m and n must be equal.
Default False.
Defaults to False.
Returns:
Tensor: shape (m, n) if ``is_aligned`` is False else shape (m,)
"""
......@@ -221,9 +227,9 @@ def axis_aligned_bbox_overlaps_3d(bboxes1,
mode (str): "iou" (intersection over union) or "giou" (generalized
intersection over union).
is_aligned (bool, optional): If True, then m and n must be equal.
Default False.
Defaults to False.
eps (float, optional): A value added to the denominator for numerical
stability. Default 1e-6.
stability. Defaults to 1e-6.
Returns:
Tensor: shape (m, n) if ``is_aligned`` is False else shape (m,)
......
......@@ -139,7 +139,7 @@ class IoUNegPiecewiseSampler(RandomSampler):
assign_result (:obj:`AssignResult`): Bbox assigning results.
bboxes (torch.Tensor): Boxes to be sampled from.
gt_bboxes (torch.Tensor): Ground truth bboxes.
gt_labels (torch.Tensor, optional): Class labels of ground truth \
gt_labels (torch.Tensor, optional): Class labels of ground truth
bboxes.
Returns:
......
# Copyright (c) OpenMMLab. All rights reserved.
import numpy as np
import torch
import warnings
from abc import abstractmethod
from mmdet3d.ops import points_in_boxes_batch, points_in_boxes_gpu
from mmdet3d.ops import points_in_boxes_all, points_in_boxes_part
from mmdet3d.ops.iou3d import iou3d_cuda
from .utils import limit_period, xywhr2xyxyr
......@@ -19,12 +20,12 @@ class BaseInstance3DBoxes(object):
tensor (torch.Tensor | np.ndarray | list): a N x box_dim matrix.
box_dim (int): Number of the dimension of a box.
Each row is (x, y, z, x_size, y_size, z_size, yaw).
Default to 7.
Defaults to 7.
with_yaw (bool): Whether the box is with yaw rotation.
If False, the value of yaw will be set to 0 as minmax boxes.
Default to True.
origin (tuple[float]): The relative position of origin in the box.
Default to (0.5, 0.5, 0). This will guide the box be converted to
Defaults to True.
origin (tuple[float], optional): Relative position of the box origin.
Defaults to (0.5, 0.5, 0). This will guide the box be converted to
(0.5, 0.5, 0) mode.
Attributes:
......@@ -73,27 +74,29 @@ class BaseInstance3DBoxes(object):
@property
def dims(self):
"""torch.Tensor: Corners of each box with size (N, 8, 3)."""
"""torch.Tensor: Size dimensions of each box in shape (N, 3)."""
return self.tensor[:, 3:6]
@property
def yaw(self):
"""torch.Tensor: A vector with yaw of each box."""
"""torch.Tensor: A vector with yaw of each box in shape (N, )."""
return self.tensor[:, 6]
@property
def height(self):
"""torch.Tensor: A vector with height of each box."""
"""torch.Tensor: A vector with height of each box in shape (N, )."""
return self.tensor[:, 5]
@property
def top_height(self):
"""torch.Tensor: A vector with the top height of each box."""
"""torch.Tensor:
A vector with the top height of each box in shape (N, )."""
return self.bottom_height + self.height
@property
def bottom_height(self):
"""torch.Tensor: A vector with bottom's height of each box."""
"""torch.Tensor:
A vector with bottom's height of each box in shape (N, )."""
return self.tensor[:, 2]
@property
......@@ -101,35 +104,84 @@ class BaseInstance3DBoxes(object):
"""Calculate the center of all the boxes.
Note:
In the MMDetection3D's convention, the bottom center is
In MMDetection3D's convention, the bottom center is
usually taken as the default center.
The relative position of the centers in different kinds of
boxes are different, e.g., the relative center of a boxes is
(0.5, 1.0, 0.5) in camera and (0.5, 0.5, 0) in lidar.
It is recommended to use ``bottom_center`` or ``gravity_center``
for more clear usage.
for clearer usage.
Returns:
torch.Tensor: A tensor with center of each box.
torch.Tensor: A tensor with center of each box in shape (N, 3).
"""
return self.bottom_center
@property
def bottom_center(self):
"""torch.Tensor: A tensor with center of each box."""
"""torch.Tensor: A tensor with center of each box in shape (N, 3)."""
return self.tensor[:, :3]
@property
def gravity_center(self):
"""torch.Tensor: A tensor with center of each box."""
"""torch.Tensor: A tensor with center of each box in shape (N, 3)."""
pass
@property
def corners(self):
"""torch.Tensor: a tensor with 8 corners of each box."""
"""torch.Tensor:
a tensor with 8 corners of each box in shape (N, 8, 3)."""
pass
@property
def bev(self):
"""torch.Tensor: 2D BEV box of each box with rotation
in XYWHR format, in shape (N, 5)."""
return self.tensor[:, [0, 1, 3, 4, 6]]
@property
def nearest_bev(self):
"""torch.Tensor: A tensor of 2D BEV box of each box
without rotation."""
# Obtain BEV boxes with rotation in XYWHR format
bev_rotated_boxes = self.bev
# convert the rotation to a valid range
rotations = bev_rotated_boxes[:, -1]
normed_rotations = torch.abs(limit_period(rotations, 0.5, np.pi))
# find the center of boxes
conditions = (normed_rotations > np.pi / 4)[..., None]
bboxes_xywh = torch.where(conditions, bev_rotated_boxes[:,
[0, 1, 3, 2]],
bev_rotated_boxes[:, :4])
centers = bboxes_xywh[:, :2]
dims = bboxes_xywh[:, 2:]
bev_boxes = torch.cat([centers - dims / 2, centers + dims / 2], dim=-1)
return bev_boxes
def in_range_bev(self, box_range):
"""Check whether the boxes are in the given range.
Args:
box_range (list | torch.Tensor): the range of box
(x_min, y_min, x_max, y_max)
Note:
The original implementation of SECOND checks whether boxes in
a range by checking whether the points are in a convex
polygon, we reduce the burden for simpler cases.
Returns:
torch.Tensor: Whether each box is inside the reference range.
"""
in_range_flags = ((self.bev[:, 0] > box_range[0])
& (self.bev[:, 1] > box_range[1])
& (self.bev[:, 0] < box_range[2])
& (self.bev[:, 1] < box_range[3]))
return in_range_flags
@abstractmethod
def rotate(self, angle, points=None):
"""Rotate boxes with points (optional) with the given angle or rotation
......@@ -138,21 +190,28 @@ class BaseInstance3DBoxes(object):
Args:
angle (float | torch.Tensor | np.ndarray):
Rotation angle or rotation matrix.
points (torch.Tensor, numpy.ndarray, :obj:`BasePoints`, optional):
points (torch.Tensor | numpy.ndarray |
:obj:`BasePoints`, optional):
Points to rotate. Defaults to None.
"""
pass
@abstractmethod
def flip(self, bev_direction='horizontal'):
"""Flip the boxes in BEV along given BEV direction."""
"""Flip the boxes in BEV along given BEV direction.
Args:
bev_direction (str, optional): Direction by which to flip.
Can be chosen from 'horizontal' and 'vertical'.
Defaults to 'horizontal'.
"""
pass
def translate(self, trans_vector):
"""Translate boxes with the given translation vector.
Args:
trans_vector (torch.Tensor): Translation vector of size 1x3.
trans_vector (torch.Tensor): Translation vector of size (1, 3).
"""
if not isinstance(trans_vector, torch.Tensor):
trans_vector = self.tensor.new_tensor(trans_vector)
......@@ -182,28 +241,15 @@ class BaseInstance3DBoxes(object):
& (self.tensor[:, 2] < box_range[5]))
return in_range_flags
@abstractmethod
def in_range_bev(self, box_range):
"""Check whether the boxes are in the given range.
Args:
box_range (list | torch.Tensor): The range of box
in order of (x_min, y_min, x_max, y_max).
Returns:
torch.Tensor: Indicating whether each box is inside
the reference range.
"""
pass
@abstractmethod
def convert_to(self, dst, rt_mat=None):
"""Convert self to ``dst`` mode.
Args:
dst (:obj:`Box3DMode`): The target Box mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
......@@ -221,25 +267,26 @@ class BaseInstance3DBoxes(object):
scale_factors (float): Scale factors to scale the boxes.
"""
self.tensor[:, :6] *= scale_factor
self.tensor[:, 7:] *= scale_factor
self.tensor[:, 7:] *= scale_factor # velocity
def limit_yaw(self, offset=0.5, period=np.pi):
"""Limit the yaw to a given period and offset.
Args:
offset (float): The offset of the yaw.
period (float): The expected period.
offset (float, optional): The offset of the yaw. Defaults to 0.5.
period (float, optional): The expected period. Defaults to np.pi.
"""
self.tensor[:, 6] = limit_period(self.tensor[:, 6], offset, period)
def nonempty(self, threshold: float = 0.0):
def nonempty(self, threshold=0.0):
"""Find boxes that are non-empty.
A box is considered empty,
if either of its side is no larger than threshold.
Args:
threshold (float): The threshold of minimal sizes.
threshold (float, optional): The threshold of minimal sizes.
Defaults to 0.0.
Returns:
torch.Tensor: A binary vector which represents whether each
......@@ -364,7 +411,7 @@ class BaseInstance3DBoxes(object):
Args:
boxes1 (:obj:`BaseInstance3DBoxes`): Boxes 1 contain N boxes.
boxes2 (:obj:`BaseInstance3DBoxes`): Boxes 2 contain M boxes.
mode (str, optional): Mode of iou calculation. Defaults to 'iou'.
mode (str, optional): Mode of IoU calculation. Defaults to 'iou'.
Returns:
torch.Tensor: Calculated iou of boxes.
......@@ -461,34 +508,49 @@ class BaseInstance3DBoxes(object):
return original_type(
new_tensor, box_dim=self.box_dim, with_yaw=self.with_yaw)
def points_in_boxes(self, points, boxes_override=None):
"""Find the box which the points are in.
def points_in_boxes_part(self, points, boxes_override=None):
"""Find the box in which each point is.
Args:
points (torch.Tensor): Points in shape (N, 3).
points (torch.Tensor): Points in shape (1, M, 3) or (M, 3),
3 dimensions are (x, y, z) in LiDAR or depth coordinate.
boxes_override (torch.Tensor, optional): Boxes to override
`self.tensor`. Defaults to None.
Returns:
torch.Tensor: The index of box where each point are in.
torch.Tensor: The index of the first box that each point
is in, in shape (M, ). Default value is -1
(if the point is not enclosed by any box).
Note:
If a point is enclosed by multiple boxes, the index of the
first box will be returned.
"""
if boxes_override is not None:
boxes = boxes_override
else:
boxes = self.tensor
box_idx = points_in_boxes_gpu(
points.unsqueeze(0),
boxes.unsqueeze(0).to(points.device)).squeeze(0)
if points.dim() == 2:
points = points.unsqueeze(0)
box_idx = points_in_boxes_part(points,
boxes.unsqueeze(0).to(
points.device)).squeeze(0)
return box_idx
def points_in_boxes_batch(self, points, boxes_override=None):
"""Find points that are in boxes (CUDA).
def points_in_boxes_all(self, points, boxes_override=None):
"""Find all boxes in which each point is.
Args:
points (torch.Tensor): Points in shape [1, M, 3] or [M, 3],
3 dimensions are [x, y, z] in LiDAR coordinate.
points (torch.Tensor): Points in shape (1, M, 3) or (M, 3),
3 dimensions are (x, y, z) in LiDAR or depth coordinate.
boxes_override (torch.Tensor, optional): Boxes to override
`self.tensor`. Defaults to None.
Returns:
torch.Tensor: The index of boxes each point lies in with shape
of (B, M, T).
torch.Tensor: A tensor indicating whether a point is in a box,
in shape (M, T). T is the number of boxes. Denote this
tensor as A, if the m^th point is in the t^th box, then
`A[m, t] == 1`, elsewise `A[m, t] == 0`.
"""
if boxes_override is not None:
boxes = boxes_override
......@@ -502,6 +564,18 @@ class BaseInstance3DBoxes(object):
assert points_clone.dim() == 3 and points_clone.shape[0] == 1
boxes = boxes.to(points_clone.device).unsqueeze(0)
box_idxs_of_pts = points_in_boxes_batch(points_clone, boxes)
box_idxs_of_pts = points_in_boxes_all(points_clone, boxes)
return box_idxs_of_pts.squeeze(0)
def points_in_boxes(self, points, boxes_override=None):
warnings.warn('DeprecationWarning: points_in_boxes is a '
'deprecated method, please consider using '
'points_in_boxes_part.')
return self.points_in_boxes_part(points, boxes_override)
def points_in_boxes_batch(self, points, boxes_override=None):
warnings.warn('DeprecationWarning: points_in_boxes_batch is a '
'deprecated method, please consider using '
'points_in_boxes_all.')
return self.points_in_boxes_all(points, boxes_override)
......@@ -71,12 +71,13 @@ class Box3DMode(IntEnum):
Can be a k-tuple, k-list or an Nxk array/tensor, where k = 7.
src (:obj:`Box3DMode`): The src Box mode.
dst (:obj:`Box3DMode`): The target Box mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
with_yaw (bool): If `box` is an instance of
with_yaw (bool, optional): If `box` is an instance of
:obj:`BaseInstance3DBoxes`, whether or not it has a yaw angle.
Defaults to True.
......@@ -128,13 +129,13 @@ class Box3DMode(IntEnum):
yaw = limit_period(yaw, period=np.pi * 2)
elif src == Box3DMode.DEPTH and dst == Box3DMode.CAM:
if rt_mat is None:
rt_mat = arr.new_tensor([[1, 0, 0], [0, 0, 1], [0, -1, 0]])
rt_mat = arr.new_tensor([[1, 0, 0], [0, 0, -1], [0, 1, 0]])
xyz_size = torch.cat([x_size, z_size, y_size], dim=-1)
if with_yaw:
yaw = -yaw
elif src == Box3DMode.CAM and dst == Box3DMode.DEPTH:
if rt_mat is None:
rt_mat = arr.new_tensor([[1, 0, 0], [0, 0, -1], [0, 1, 0]])
rt_mat = arr.new_tensor([[1, 0, 0], [0, 0, 1], [0, -1, 0]])
xyz_size = torch.cat([x_size, z_size, y_size], dim=-1)
if with_yaw:
yaw = -yaw
......
......@@ -4,7 +4,7 @@ import torch
from ...points import BasePoints
from .base_box3d import BaseInstance3DBoxes
from .utils import limit_period, rotation_3d_in_axis
from .utils import rotation_3d_in_axis
class CameraInstance3DBoxes(BaseInstance3DBoxes):
......@@ -28,15 +28,12 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
The yaw is 0 at the positive direction of x axis, and decreases from
the positive direction of x to the positive direction of z.
A refactor is ongoing to make the three coordinate systems
easier to understand and convert between each other.
Attributes:
tensor (torch.Tensor): Float matrix of N x box_dim.
box_dim (int): Integer indicates the dimension of a box
tensor (torch.Tensor): Float matrix in shape (N, box_dim).
box_dim (int): Integer indicating the dimension of a box
Each row is (x, y, z, x_size, y_size, z_size, yaw, ...).
with_yaw (bool): If True, the value of yaw will be set to 0 as minmax
boxes.
with_yaw (bool): If True, the value of yaw will be set to 0 as
axis-aligned boxes tightly enclosing the original boxes.
"""
YAW_AXIS = 1
......@@ -77,23 +74,25 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
@property
def height(self):
"""torch.Tensor: A vector with height of each box."""
"""torch.Tensor: A vector with height of each box in shape (N, )."""
return self.tensor[:, 4]
@property
def top_height(self):
"""torch.Tensor: A vector with the top height of each box."""
"""torch.Tensor:
A vector with the top height of each box in shape (N, )."""
# the positive direction is down rather than up
return self.bottom_height - self.height
@property
def bottom_height(self):
"""torch.Tensor: A vector with bottom's height of each box."""
"""torch.Tensor:
A vector with bottom's height of each box in shape (N, )."""
return self.tensor[:, 1]
@property
def gravity_center(self):
"""torch.Tensor: A tensor with center of each box."""
"""torch.Tensor: A tensor with center of each box in shape (N, 3)."""
bottom_center = self.bottom_center
gravity_center = torch.zeros_like(bottom_center)
gravity_center[:, [0, 2]] = bottom_center[:, [0, 2]]
......@@ -148,8 +147,8 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
@property
def bev(self):
"""torch.Tensor: A n x 5 tensor of 2D BEV box of each box
with rotation in XYWHR format."""
"""torch.Tensor: 2D BEV box of each box with rotation
in XYWHR format, in shape (N, 5)."""
bev = self.tensor[:, [0, 2, 3, 5, 6]].clone()
# positive direction of the gravity axis
# in cam coord system points to the earth
......@@ -157,27 +156,6 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
bev[:, -1] = -bev[:, -1]
return bev
@property
def nearest_bev(self):
"""torch.Tensor: A tensor of 2D BEV box of each box
without rotation."""
# Obtain BEV boxes with rotation in XZWHR format
bev_rotated_boxes = self.bev
# convert the rotation to a valid range
rotations = bev_rotated_boxes[:, -1]
normed_rotations = torch.abs(limit_period(rotations, 0.5, np.pi))
# find the center of boxes
conditions = (normed_rotations > np.pi / 4)[..., None]
bboxes_xywh = torch.where(conditions, bev_rotated_boxes[:,
[0, 1, 3, 2]],
bev_rotated_boxes[:, :4])
centers = bboxes_xywh[:, :2]
dims = bboxes_xywh[:, 2:]
bev_boxes = torch.cat([centers - dims / 2, centers + dims / 2], dim=-1)
return bev_boxes
def rotate(self, angle, points=None):
"""Rotate boxes with points (optional) with the given angle or rotation
matrix.
......@@ -185,7 +163,7 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
Args:
angle (float | torch.Tensor | np.ndarray):
Rotation angle or rotation matrix.
points (torch.Tensor, numpy.ndarray, :obj:`BasePoints`, optional):
points (torch.Tensor | np.ndarray | :obj:`BasePoints`, optional):
Points to rotate. Defaults to None.
Returns:
......@@ -237,7 +215,7 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
Args:
bev_direction (str): Flip direction (horizontal or vertical).
points (torch.Tensor, numpy.ndarray, :obj:`BasePoints`, None):
points (torch.Tensor | np.ndarray | :obj:`BasePoints`, optional):
Points to flip. Defaults to None.
Returns:
......@@ -264,28 +242,6 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
points.flip(bev_direction)
return points
def in_range_bev(self, box_range):
"""Check whether the boxes are in the given range.
Args:
box_range (list | torch.Tensor): The range of box
(x_min, z_min, x_max, z_max).
Note:
The original implementation of SECOND checks whether boxes in
a range by checking whether the points are in a convex
polygon, we reduce the burden for simpler cases.
Returns:
torch.Tensor: Indicating whether each box is inside
the reference range.
"""
in_range_flags = ((self.tensor[:, 0] > box_range[0])
& (self.tensor[:, 2] > box_range[1])
& (self.tensor[:, 0] < box_range[2])
& (self.tensor[:, 2] < box_range[3]))
return in_range_flags
@classmethod
def height_overlaps(cls, boxes1, boxes2, mode='iou'):
"""Calculate height overlaps of two boxes.
......@@ -322,8 +278,9 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
Args:
dst (:obj:`Box3DMode`): The target Box mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from ``src`` coordinates to ``dst`` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
......@@ -336,43 +293,55 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
return Box3DMode.convert(
box=self, src=Box3DMode.CAM, dst=dst, rt_mat=rt_mat)
def points_in_boxes(self, points):
"""Find the box which the points are in.
def points_in_boxes_part(self, points, boxes_override=None):
"""Find the box in which each point is.
Args:
points (torch.Tensor): Points in shape (N, 3).
points (torch.Tensor): Points in shape (1, M, 3) or (M, 3),
3 dimensions are (x, y, z) in LiDAR or depth coordinate.
boxes_override (torch.Tensor, optional): Boxes to override
`self.tensor `. Defaults to None.
Returns:
torch.Tensor: The index of box where each point are in.
torch.Tensor: The index of the box in which
each point is, in shape (M, ). Default value is -1
(if the point is not enclosed by any box).
"""
from .coord_3d_mode import Coord3DMode
points_lidar = Coord3DMode.convert(points, Coord3DMode.CAM,
Coord3DMode.LIDAR)
boxes_lidar = Coord3DMode.convert(self.tensor, Coord3DMode.CAM,
Coord3DMode.LIDAR)
if boxes_override is not None:
boxes_lidar = boxes_override
else:
boxes_lidar = Coord3DMode.convert(self.tensor, Coord3DMode.CAM,
Coord3DMode.LIDAR)
box_idx = super().points_in_boxes(self, points_lidar, boxes_lidar)
box_idx = super().points_in_boxes_part(points_lidar, boxes_lidar)
return box_idx
def points_in_boxes_batch(self, points):
"""Find points that are in boxes (CUDA).
def points_in_boxes_all(self, points, boxes_override=None):
"""Find all boxes in which each point is.
Args:
points (torch.Tensor): Points in shape [1, M, 3] or [M, 3],
3 dimensions are [x, y, z] in LiDAR coordinate.
points (torch.Tensor): Points in shape (1, M, 3) or (M, 3),
3 dimensions are (x, y, z) in LiDAR or depth coordinate.
boxes_override (torch.Tensor, optional): Boxes to override
`self.tensor `. Defaults to None.
Returns:
torch.Tensor: The index of boxes each point lies in with shape
of (B, M, T).
torch.Tensor: The index of all boxes in which each point is,
in shape (B, M, T).
"""
from .coord_3d_mode import Coord3DMode
points_lidar = Coord3DMode.convert(points, Coord3DMode.CAM,
Coord3DMode.LIDAR)
boxes_lidar = Coord3DMode.convert(self.tensor, Coord3DMode.CAM,
Coord3DMode.LIDAR)
if boxes_override is not None:
boxes_lidar = boxes_override
else:
boxes_lidar = Coord3DMode.convert(self.tensor, Coord3DMode.CAM,
Coord3DMode.LIDAR)
box_idx = super().points_in_boxes_batch(self, points_lidar,
boxes_lidar)
box_idx = super().points_in_boxes_all(points_lidar, boxes_lidar)
return box_idx
......@@ -70,8 +70,9 @@ class Coord3DMode(IntEnum):
Can be a k-tuple, k-list or an Nxk array/tensor, where k = 7.
src (:obj:`Box3DMode` | :obj:`Coord3DMode`): The source mode.
dst (:obj:`Box3DMode` | :obj:`Coord3DMode`): The target mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
......@@ -113,8 +114,9 @@ class Coord3DMode(IntEnum):
Can be a k-tuple, k-list or an Nxk array/tensor, where k = 7.
src (:obj:`Box3DMode`): The src Box mode.
dst (:obj:`Box3DMode`): The target Box mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
......@@ -139,8 +141,9 @@ class Coord3DMode(IntEnum):
Can be a k-tuple, k-list or an Nxk array/tensor.
src (:obj:`CoordMode`): The src Point mode.
dst (:obj:`CoordMode`): The target Point mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
......
......@@ -4,7 +4,7 @@ import torch
from mmdet3d.core.points import BasePoints
from .base_box3d import BaseInstance3DBoxes
from .utils import limit_period, rotation_3d_in_axis
from .utils import rotation_3d_in_axis
class DepthInstance3DBoxes(BaseInstance3DBoxes):
......@@ -41,7 +41,7 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
@property
def gravity_center(self):
"""torch.Tensor: A tensor with center of each box."""
"""torch.Tensor: A tensor with center of each box in shape (N, 3)."""
bottom_center = self.bottom_center
gravity_center = torch.zeros_like(bottom_center)
gravity_center[:, :2] = bottom_center[:, :2]
......@@ -71,8 +71,6 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
(x0, y0, z0) + ----------- + --------> right x
(x1, y0, z0)
"""
# TODO: rotation_3d_in_axis function do not support
# empty tensor currently.
assert len(self.tensor) != 0
dims = self.dims
corners_norm = torch.from_numpy(
......@@ -90,33 +88,6 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
corners += self.tensor[:, :3].view(-1, 1, 3)
return corners
@property
def bev(self):
"""torch.Tensor: A n x 5 tensor of 2D BEV box of each box
in XYWHR format."""
return self.tensor[:, [0, 1, 3, 4, 6]]
@property
def nearest_bev(self):
"""torch.Tensor: A tensor of 2D BEV box of each box
without rotation."""
# Obtain BEV boxes with rotation in XYWHR format
bev_rotated_boxes = self.bev
# convert the rotation to a valid range
rotations = bev_rotated_boxes[:, -1]
normed_rotations = torch.abs(limit_period(rotations, 0.5, np.pi))
# find the center of boxes
conditions = (normed_rotations > np.pi / 4)[..., None]
bboxes_xywh = torch.where(conditions, bev_rotated_boxes[:,
[0, 1, 3, 2]],
bev_rotated_boxes[:, :4])
centers = bboxes_xywh[:, :2]
dims = bboxes_xywh[:, 2:]
bev_boxes = torch.cat([centers - dims / 2, centers + dims / 2], dim=-1)
return bev_boxes
def rotate(self, angle, points=None):
"""Rotate boxes with points (optional) with the given angle or rotation
matrix.
......@@ -124,7 +95,7 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
Args:
angle (float | torch.Tensor | np.ndarray):
Rotation angle or rotation matrix.
points (torch.Tensor, numpy.ndarray, :obj:`BasePoints`, optional):
points (torch.Tensor | np.ndarray | :obj:`BasePoints`, optional):
Points to rotate. Defaults to None.
Returns:
......@@ -154,6 +125,8 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
if self.with_yaw:
self.tensor[:, 6] += angle
else:
# for axis-aligned boxes, we take the new
# enclosing axis-aligned boxes after rotation
corners_rot = self.corners @ rot_mat_T
new_x_size = corners_rot[..., 0].max(
dim=1, keepdim=True)[0] - corners_rot[..., 0].min(
......@@ -181,8 +154,9 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
In Depth coordinates, it flips x (horizontal) or y (vertical) axis.
Args:
bev_direction (str): Flip direction (horizontal or vertical).
points (torch.Tensor, numpy.ndarray, :obj:`BasePoints`, None):
bev_direction (str, optional): Flip direction
(horizontal or vertical). Defaults to 'horizontal'.
points (torch.Tensor | np.ndarray | :obj:`BasePoints`, optional):
Points to flip. Defaults to None.
Returns:
......@@ -209,35 +183,14 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
points.flip(bev_direction)
return points
def in_range_bev(self, box_range):
"""Check whether the boxes are in the given range.
Args:
box_range (list | torch.Tensor): The range of box
(x_min, y_min, x_max, y_max).
Note:
In the original implementation of SECOND, checking whether
a box in the range checks whether the points are in a convex
polygon, we try to reduce the burdun for simpler cases.
Returns:
torch.Tensor: Indicating whether each box is inside
the reference range.
"""
in_range_flags = ((self.tensor[:, 0] > box_range[0])
& (self.tensor[:, 1] > box_range[1])
& (self.tensor[:, 0] < box_range[2])
& (self.tensor[:, 1] < box_range[3]))
return in_range_flags
def convert_to(self, dst, rt_mat=None):
"""Convert self to ``dst`` mode.
Args:
dst (:obj:`Box3DMode`): The target Box mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from ``src`` coordinates to ``dst`` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
......@@ -257,7 +210,7 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
extra_width (float | torch.Tensor): Extra width to enlarge the box.
Returns:
:obj:`LiDARInstance3DBoxes`: Enlarged boxes.
:obj:`DepthInstance3DBoxes`: Enlarged boxes.
"""
enlarged_boxes = self.tensor.clone()
enlarged_boxes[:, 3:6] += extra_width * 2
......
......@@ -4,7 +4,7 @@ import torch
from mmdet3d.core.points import BasePoints
from .base_box3d import BaseInstance3DBoxes
from .utils import limit_period, rotation_3d_in_axis
from .utils import rotation_3d_in_axis
class LiDARInstance3DBoxes(BaseInstance3DBoxes):
......@@ -39,7 +39,7 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
@property
def gravity_center(self):
"""torch.Tensor: A tensor with center of each box."""
"""torch.Tensor: A tensor with center of each box in shape (N, 3)."""
bottom_center = self.bottom_center
gravity_center = torch.zeros_like(bottom_center)
gravity_center[:, :2] = bottom_center[:, :2]
......@@ -88,33 +88,6 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
corners += self.tensor[:, :3].view(-1, 1, 3)
return corners
@property
def bev(self):
"""torch.Tensor: 2D BEV box of each box with rotation
in XYWHR format."""
return self.tensor[:, [0, 1, 3, 4, 6]]
@property
def nearest_bev(self):
"""torch.Tensor: A tensor of 2D BEV box of each box
without rotation."""
# Obtain BEV boxes with rotation in XYWHR format
bev_rotated_boxes = self.bev
# convert the rotation to a valid range
rotations = bev_rotated_boxes[:, -1]
normed_rotations = torch.abs(limit_period(rotations, 0.5, np.pi))
# find the center of boxes
conditions = (normed_rotations > np.pi / 4)[..., None]
bboxes_xywh = torch.where(conditions, bev_rotated_boxes[:,
[0, 1, 3, 2]],
bev_rotated_boxes[:, :4])
centers = bboxes_xywh[:, :2]
dims = bboxes_xywh[:, 2:]
bev_boxes = torch.cat([centers - dims / 2, centers + dims / 2], dim=-1)
return bev_boxes
def rotate(self, angle, points=None):
"""Rotate boxes with points (optional) with the given angle or rotation
matrix.
......@@ -122,7 +95,7 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
Args:
angles (float | torch.Tensor | np.ndarray):
Rotation angle or rotation matrix.
points (torch.Tensor, numpy.ndarray, :obj:`BasePoints`, optional):
points (torch.Tensor | np.ndarray | :obj:`BasePoints`, optional):
Points to rotate. Defaults to None.
Returns:
......@@ -174,7 +147,7 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
Args:
bev_direction (str): Flip direction (horizontal or vertical).
points (torch.Tensor, numpy.ndarray, :obj:`BasePoints`, None):
points (torch.Tensor | np.ndarray | :obj:`BasePoints`, optional):
Points to flip. Defaults to None.
Returns:
......@@ -201,34 +174,14 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
points.flip(bev_direction)
return points
def in_range_bev(self, box_range):
"""Check whether the boxes are in the given range.
Args:
box_range (list | torch.Tensor): the range of box
(x_min, y_min, x_max, y_max)
Note:
The original implementation of SECOND checks whether boxes in
a range by checking whether the points are in a convex
polygon, we reduce the burden for simpler cases.
Returns:
torch.Tensor: Whether each box is inside the reference range.
"""
in_range_flags = ((self.tensor[:, 0] > box_range[0])
& (self.tensor[:, 1] > box_range[1])
& (self.tensor[:, 0] < box_range[2])
& (self.tensor[:, 1] < box_range[3]))
return in_range_flags
def convert_to(self, dst, rt_mat=None):
"""Convert self to ``dst`` mode.
Args:
dst (:obj:`Box3DMode`): the target Box mode
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from ``src`` coordinates to ``dst`` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
......
......@@ -51,10 +51,10 @@ def bbox3d2result(bboxes, scores, labels, attrs=None):
"""Convert detection results to a list of numpy arrays.
Args:
bboxes (torch.Tensor): Bounding boxes with shape of (n, 5).
labels (torch.Tensor): Labels with shape of (n, ).
scores (torch.Tensor): Scores with shape of (n, ).
attrs (torch.Tensor, optional): Attributes with shape of (n, ).
bboxes (torch.Tensor): Bounding boxes with shape (N, 5).
labels (torch.Tensor): Labels with shape (N, ).
scores (torch.Tensor): Scores with shape (N, ).
attrs (torch.Tensor, optional): Attributes with shape (N, ).
Defaults to None.
Returns:
......
......@@ -9,9 +9,9 @@ def average_precision(recalls, precisions, mode='area'):
"""Calculate average precision (for single or multiple scales).
Args:
recalls (np.ndarray): Recalls with shape of (num_scales, num_dets) \
recalls (np.ndarray): Recalls with shape of (num_scales, num_dets)
or (num_dets, ).
precisions (np.ndarray): Precisions with shape of \
precisions (np.ndarray): Precisions with shape of
(num_scales, num_dets) or (num_dets, ).
mode (str): 'area' or '11points', 'area' means calculating the area
under precision-recall curve, '11points' means calculating
......@@ -58,13 +58,13 @@ def eval_det_cls(pred, gt, iou_thr=None):
single class.
Args:
pred (dict): Predictions mapping from image id to bounding boxes \
pred (dict): Predictions mapping from image id to bounding boxes
and scores.
gt (dict): Ground truths mapping from image id to bounding boxes.
iou_thr (list[float]): A list of iou thresholds.
Return:
tuple (np.ndarray, np.ndarray, float): Recalls, precisions and \
tuple (np.ndarray, np.ndarray, float): Recalls, precisions and
average precision.
"""
......@@ -170,10 +170,9 @@ def eval_map_recall(pred, gt, ovthresh=None):
Args:
pred (dict): Information of detection results,
which maps class_id and predictions.
gt (dict): Information of ground truths, which maps class_id and \
gt (dict): Information of ground truths, which maps class_id and
ground truths.
ovthresh (list[float]): iou threshold.
Default: None.
ovthresh (list[float], optional): iou threshold. Default: None.
Return:
tuple[dict]: dict results of recall, AP, and precision for all classes.
......@@ -218,12 +217,12 @@ def indoor_eval(gt_annos,
includes the following keys
- labels_3d (torch.Tensor): Labels of boxes.
- boxes_3d (:obj:`BaseInstance3DBoxes`): \
- boxes_3d (:obj:`BaseInstance3DBoxes`):
3D bounding boxes in Depth coordinate.
- scores_3d (torch.Tensor): Scores of boxes.
metric (list[float]): IoU thresholds for computing average precisions.
label2cat (dict): Map from label to category.
logger (logging.Logger | str | None): The way to print the mAP
logger (logging.Logger | str, optional): The way to print the mAP
summary. See `mmdet.utils.print_log()` for details. Default: None.
Return:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment