From a2400c3e6e4f60f47d53cdd01e5e9afcfe9a72aa Mon Sep 17 00:00:00 2001 From: John Lambert Date: Wed, 23 Jun 2021 21:41:16 -0400 Subject: [PATCH 01/11] remove quaternion dependency --- argoverse/data_loading/object_label_record.py | 2 +- argoverse/evaluation/competition_util.py | 21 +++++++++---------- argoverse/utils/transform.py | 14 +++++++++++++ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/argoverse/data_loading/object_label_record.py b/argoverse/data_loading/object_label_record.py index 9446f4db..335d65be 100644 --- a/argoverse/data_loading/object_label_record.py +++ b/argoverse/data_loading/object_label_record.py @@ -52,7 +52,7 @@ def __init__( """Create an ObjectLabelRecord. Args: - quaternion: Numpy vector representing quaternion, box/cuboid orientation + quaternion: Numpy vector representing quaternion (qw,qx,qy,qz), box/cuboid orientation translation: Numpy vector representing translation, center of box given as x, y, z. length: object length. width: object width. diff --git a/argoverse/evaluation/competition_util.py b/argoverse/evaluation/competition_util.py index 23c64f9d..54619d51 100644 --- a/argoverse/evaluation/competition_util.py +++ b/argoverse/evaluation/competition_util.py @@ -10,7 +10,6 @@ import h5py import numpy as np -import quaternion from scipy.spatial import ConvexHull from shapely.geometry import Polygon from sklearn.cluster.dbscan_ import DBSCAN @@ -18,6 +17,7 @@ from argoverse.data_loading.argoverse_tracking_loader import ArgoverseTrackingLoader from argoverse.data_loading.object_label_record import ObjectLabelRecord from argoverse.utils.se3 import SE3 +from argoverse.utils.transform import rotmat2quat TYPE_LIST = Union[List[np.ndarray], np.ndarray] @@ -169,6 +169,7 @@ def dist(p1: Tuple[float, float], p2: Tuple[float, float]) -> float: def poly_to_label(poly: Polygon, category: str = "VEHICLE", track_id: str = "") -> ObjectLabelRecord: + """ """ # poly in polygon format bbox = poly.minimum_rotated_rectangle @@ -197,18 +198,20 @@ def poly_to_label(poly: Polygon, category: str = "VEHICLE", track_id: str = "") # translation = center center = np.array([bbox.centroid.xy[0][0], bbox.centroid.xy[1][0], min(z) + height / 2]) + c = np.cos(angle) + s = np.sin(angle) R = np.array( [ - [np.cos(angle), -np.sin(angle), 0], - [np.sin(angle), np.cos(angle), 0], + [c, -s, 0], + [s, c, 0], [0, 0, 1], ] ) - q = quaternion.from_rotation_matrix(R) + q = rotmat2quat(R) return ObjectLabelRecord( - quaternion=quaternion.as_float_array(q), + quaternion=q, translation=center, length=length, width=width, @@ -266,18 +269,14 @@ def save_label(argoverse_data: ArgoverseTrackingLoader, labels: List[ObjectLabel timestamp = argoverse_data.lidar_timestamp_list[idx] for label in labels: + (qw, qx, qy, qz) = label.quaternion json_data = { "center": { "x": label.translation[0], "y": label.translation[1], "z": label.translation[2], }, - "rotation": { - "x": label.quaternion[0], - "y": label.quaternion[1], - "z": label.quaternion[2], - "w": label.quaternion[3], - }, + "rotation": {"x": qx, "y": qy, "z": qz, "w": qw}, "length": label.length, "width": label.width, "height": label.height, diff --git a/argoverse/utils/transform.py b/argoverse/utils/transform.py index 7193bda7..723f57a5 100644 --- a/argoverse/utils/transform.py +++ b/argoverse/utils/transform.py @@ -16,6 +16,13 @@ logger = logging.getLogger(__name__) +def rotmat2quat(R: np.ndarray) -> np.ndarray: + """Convert a rotation-matrix to a quaternion in Argo's scalar-first notation (w, x, y, z).""" + quat_xyzw = Rotation.from_matrix(R).as_quat() + quat_wxyz = quat_scipy2argo(quat_xyzw) + return quat_wxyz + + def quat2rotmat(q: np.ndarray) -> np.ndarray: """Normalizes a quaternion to unit-length, then converts it into a rotation matrix. @@ -48,6 +55,13 @@ def quat_argo2scipy(q: np.ndarray) -> np.ndarray: return q_scipy +def quat_scipy2argo(q: np.ndarray) -> np.ndarray: + """Re-order Scipy's scalar-last [x,y,z,w] quaternion order to Argoverse's scalar-first [w,x,y,z].""" + x, y, z, w = q + q_argo = np.array([w, x, y, z]) + return q_argo + + def quat_argo2scipy_vectorized(q: np.ndarray) -> np.ndarray: """Re-order Argoverse's scalar-first [w,x,y,z] quaternion order to Scipy's scalar-last [x,y,z,w]""" return q[..., [1, 2, 3, 0]] From 1207856f3f395dda2a4d8d17bf3c1bbce40e18d9 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Sat, 26 Jun 2021 14:58:04 -0400 Subject: [PATCH 02/11] improve docstrings for ObjectLabelRecord --- argoverse/data_loading/object_label_record.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/argoverse/data_loading/object_label_record.py b/argoverse/data_loading/object_label_record.py index 335d65be..8c15a8d8 100644 --- a/argoverse/data_loading/object_label_record.py +++ b/argoverse/data_loading/object_label_record.py @@ -37,6 +37,12 @@ class ObjectLabelRecord: + """Parameterizes an object via a 3d bounding box and the object's pose within the egovehicle's frame. + + We refer to the object's pose as `egovehicle_SE3_object` and is parameterized by (R,t), where R is + a quaternion in scalar-first order. + """ + def __init__( self, quaternion: np.ndarray, @@ -72,7 +78,7 @@ def __init__( self.score = score def as_2d_bbox(self) -> np.ndarray: - """Construct a 2D bounding box from this label. + """Convert the object cuboid to a 2D bounding box, with vertices inside the egovehicle's frame. Length is x, width is y, and z is height @@ -97,10 +103,7 @@ def as_2d_bbox(self) -> np.ndarray: return bbox_in_egovehicle_frame def as_3d_bbox(self) -> np.ndarray: - r"""Calculate the 8 bounding box corners. - - Args: - None + r"""Calculate the 8 bounding box corners (returned as points inside the egovehicle's frame). Returns: Numpy array of shape (8,3) @@ -147,7 +150,7 @@ def render_clip_frustum_cv2( ) -> np.ndarray: r"""We bring the 3D points into each camera, and do the clipping there. - Renders box using OpenCV2. Roughly based on + Renders box using OpenCV2. Edge coloring and vertex ordering is roughly based on https://github.com/nutonomy/nuscenes-devkit/blob/master/python-sdk/nuscenes_utils/data_classes.py :: From 1d0703182a4a9e39f213f79e5c12b46119cc2a93 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Sat, 26 Jun 2021 15:20:01 -0400 Subject: [PATCH 03/11] add yaw-> quaternion conversion, and unit tests --- argoverse/utils/transform.py | 13 ++++++++ tests/test_transform.py | 64 ++++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/argoverse/utils/transform.py b/argoverse/utils/transform.py index 723f57a5..d2d22c35 100644 --- a/argoverse/utils/transform.py +++ b/argoverse/utils/transform.py @@ -16,6 +16,19 @@ logger = logging.getLogger(__name__) +def yaw_to_quaternion3d(yaw: float) -> np.ndarray: + """Convert a rotation angle in the xy plane (i.e. about the z axis) to a quaternion. + + Args: + yaw: angle to rotate about the z-axis, representing an Euler angle, in radians + + Returns: + array w/ quaternion coefficients (qw,qx,qy,qz) in scalar-first order, per Argoverse convention. + """ + qx, qy, qz, qw = Rotation.from_euler(seq="z", angles=yaw, degrees=False).as_quat() + return np.array([qw, qx, qy, qz]) + + def rotmat2quat(R: np.ndarray) -> np.ndarray: """Convert a rotation-matrix to a quaternion in Argo's scalar-first notation (w, x, y, z).""" quat_xyzw = Rotation.from_matrix(R).as_quat() diff --git a/tests/test_transform.py b/tests/test_transform.py index c895f046..821c01a1 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -8,7 +8,7 @@ import numpy as np import pytest -from argoverse.utils.transform import quat2rotmat +from argoverse.utils.transform import quat2rotmat, rotmat2quat, yaw_to_quaternion3d EPSILON = 1e-10 @@ -148,7 +148,7 @@ def test_quat2rotmat_5() -> None: assert np.allclose(R_gt, R) -def test_invalid_quaternion_zero_norm(): +def test_invalid_quaternion_zero_norm() -> None: """Ensure that passing a zero-norm quaternion raises an error, as normalization would divide by 0.""" q = np.array([0.0, 0.0, 0.0, 0.0]) @@ -156,7 +156,7 @@ def test_invalid_quaternion_zero_norm(): quat2rotmat(q) -def test_quaternion_renormalized(): +def test_quaternion_renormalized() -> None: """Make sure that a quaternion is correctly re-normalized. Normalized and unnormalized quaternion variants should generate the same 3d rotation matrix. @@ -168,3 +168,61 @@ def test_quaternion_renormalized(): R2 = quat2rotmat(q2) assert np.allclose(R1, R2) + + +def test_rotmat2quat() -> None: + """Ensure `rotmat2quat()` correctly converts rotation matrices to scalar-first quaternions.""" + num_trials = 1000 + for trial in range(num_trials): + + # generate random rotation matrices by sampling quaternion elements from normal distribution + # https://en.wikipedia.org/wiki/Rotation_matrix#Uniform_random_rotation_matrices + + q = np.random.randn(4) + q /= np.linalg.norm(q) + + R = quat2rotmat(q) + q_ = rotmat2quat(R) + + # Note: A unit quaternion multiplied by 1 or -1 represents the same 3d rotation + # https://math.stackexchange.com/questions/2016282/negative-quaternion + # https://math.stackexchange.com/questions/1790521/unit-quaternion-multiplied-by-1 + assert np.allclose(q, q_) or np.allclose(-q, q_) + + +def test_yaw_to_quaternion3d() -> None: + """Ensure yaw_to_quaternion3d() outputs correct values. + + Compare `yaw_to_quaternion3d()` output (which relies upon Scipy) to manual + computation of the 3d rotation matrix, followed by a rotation matrix -> quaternion conversion. + """ + + def rot3d_z(angle_rad: float) -> np.ndarray: + """Generate 3d rotation matrix about the z-axis, from a rotation angle in radians.""" + c = np.cos(angle_rad) + s = np.sin(angle_rad) + R = np.array( + [ + [c, -s, 0], + [s, c, 0], + [0, 0, 1], + ] + ) + return R + + num_trials = 1000 + angle_samples_rad = np.random.rand(num_trials) * 2 * np.pi + for angle_rad in angle_samples_rad: + + R = rot3d_z(angle_rad) + q = rotmat2quat(R) + + q_ = yaw_to_quaternion3d(angle_rad) + + assert isinstance(q_, np.ndarray) + assert q_.size == 4 + + # Note: A unit quaternion multiplied by 1 or -1 represents the same 3d rotation + # https://math.stackexchange.com/questions/2016282/negative-quaternion + # https://math.stackexchange.com/questions/1790521/unit-quaternion-multiplied-by-1 + assert np.allclose(q, q_) or np.allclose(q, -q_) From 555fc1e5de481fda92cadc14c5b8e444a75bfeb1 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Sat, 26 Jun 2021 15:20:45 -0400 Subject: [PATCH 04/11] clean up function, and make more modular --- argoverse/evaluation/competition_util.py | 36 ++++++++++-------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/argoverse/evaluation/competition_util.py b/argoverse/evaluation/competition_util.py index 54619d51..71df1639 100644 --- a/argoverse/evaluation/competition_util.py +++ b/argoverse/evaluation/competition_util.py @@ -17,7 +17,7 @@ from argoverse.data_loading.argoverse_tracking_loader import ArgoverseTrackingLoader from argoverse.data_loading.object_label_record import ObjectLabelRecord from argoverse.utils.se3 import SE3 -from argoverse.utils.transform import rotmat2quat +from argoverse.utils.transform import yaw_to_quaternion3d TYPE_LIST = Union[List[np.ndarray], np.ndarray] @@ -101,7 +101,6 @@ def generate_tracking_zip(input_path: str, output_path: str, filename: str = "ar filename: to be used as the name of the file """ - if not os.path.exists(output_path): os.makedirs(output_path) dirpath = tempfile.mkdtemp() @@ -122,8 +121,7 @@ def generate_tracking_zip(input_path: str, output_path: str, filename: str = "ar def get_polygon_from_points(points: np.ndarray) -> Polygon: - """ - function to generate (convex hull) shapely polygon from set of points + """Convert a point set to a Shapely polygon representing its convex hull. Args: points: list of 2d coordinate points @@ -169,9 +167,16 @@ def dist(p1: Tuple[float, float], p2: Tuple[float, float]) -> float: def poly_to_label(poly: Polygon, category: str = "VEHICLE", track_id: str = "") -> ObjectLabelRecord: - """ """ - # poly in polygon format + """Convert a Shapely Polygon to a 3d cuboid by estimating the minimum-bounding rectangle. + Args: + poly: Shapely polygon object representing a convex hull of an object + category: + track_id + + Returns: + object + """ bbox = poly.minimum_rotated_rectangle x = bbox.exterior.xy[0] @@ -191,25 +196,14 @@ def poly_to_label(poly: Polygon, category: str = "VEHICLE", track_id: str = "") else: unit_v = unit_vector((x[0], y[0]), (x[1], y[1])) - angle = math.atan2(unit_v[1], unit_v[0]) + angle_rad = math.atan2(unit_v[1], unit_v[0]) + q = yaw_to_quaternion3d(angle_rad) height = max(z) - min(z) # translation = center center = np.array([bbox.centroid.xy[0][0], bbox.centroid.xy[1][0], min(z) + height / 2]) - c = np.cos(angle) - s = np.sin(angle) - R = np.array( - [ - [c, -s, 0], - [s, c, 0], - [0, 0, 1], - ] - ) - - q = rotmat2quat(R) - return ObjectLabelRecord( quaternion=q, translation=center, @@ -223,7 +217,7 @@ def poly_to_label(poly: Polygon, category: str = "VEHICLE", track_id: str = "") def get_objects(clustering: DBSCAN, pts: np.ndarray, category: str = "VEHICLE") -> List[Tuple[np.ndarray, uuid.UUID]]: - + """ """ core_samples_mask = np.zeros_like(clustering.labels_, dtype=bool) core_samples_mask[clustering.core_sample_indices_] = True labels = clustering.labels_ @@ -269,7 +263,7 @@ def save_label(argoverse_data: ArgoverseTrackingLoader, labels: List[ObjectLabel timestamp = argoverse_data.lidar_timestamp_list[idx] for label in labels: - (qw, qx, qy, qz) = label.quaternion + qw, qx, qy, qz = label.quaternion json_data = { "center": { "x": label.translation[0], From 7979fa0b621368209cffc61df4851adac7c9eefa Mon Sep 17 00:00:00 2001 From: John Lambert Date: Sat, 26 Jun 2021 15:36:21 -0400 Subject: [PATCH 05/11] add unit test for competition util --- argoverse/evaluation/competition_util.py | 6 +++--- tests/test_competition_util.py | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 tests/test_competition_util.py diff --git a/argoverse/evaluation/competition_util.py b/argoverse/evaluation/competition_util.py index 71df1639..b71bf6c0 100644 --- a/argoverse/evaluation/competition_util.py +++ b/argoverse/evaluation/competition_util.py @@ -171,11 +171,11 @@ def poly_to_label(poly: Polygon, category: str = "VEHICLE", track_id: str = "") Args: poly: Shapely polygon object representing a convex hull of an object - category: - track_id + category: object category to which object belongs, e.g. VEHICLE, PEDESTRIAN, etc + track_id: unique identifier Returns: - object + object representing a 3d cuboid """ bbox = poly.minimum_rotated_rectangle diff --git a/tests/test_competition_util.py b/tests/test_competition_util.py new file mode 100644 index 00000000..d3f8019b --- /dev/null +++ b/tests/test_competition_util.py @@ -0,0 +1,20 @@ +import numpy as np + +from argoverse.evaluation.competition_label import get_polygon_from_points, poly_to_label + + +def test_get_polygon_from_points() -> None: + """ """ + points = np.array([[], [], [], []]) + poly = get_polygon_from_points(points) + + assert True + + +def test_poly_to_label() -> None: + """ """ + poly = get_polygon_from_points(points) + + object_rec = poly_to_label(poly, category="VEHICLE", track_id="123") + + assert True From 49c98a1de56763d9f3085e9108a59a9f784805fa Mon Sep 17 00:00:00 2001 From: John Lambert Date: Sat, 26 Jun 2021 18:45:04 -0400 Subject: [PATCH 06/11] try to simplify code --- argoverse/evaluation/competition_util.py | 60 ++++++++++-------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/argoverse/evaluation/competition_util.py b/argoverse/evaluation/competition_util.py index b71bf6c0..3df15ffd 100644 --- a/argoverse/evaluation/competition_util.py +++ b/argoverse/evaluation/competition_util.py @@ -12,7 +12,7 @@ import numpy as np from scipy.spatial import ConvexHull from shapely.geometry import Polygon -from sklearn.cluster.dbscan_ import DBSCAN +from sklearn.cluster import DBSCAN from argoverse.data_loading.argoverse_tracking_loader import ArgoverseTrackingLoader from argoverse.data_loading.object_label_record import ObjectLabelRecord @@ -121,10 +121,10 @@ def generate_tracking_zip(input_path: str, output_path: str, filename: str = "ar def get_polygon_from_points(points: np.ndarray) -> Polygon: - """Convert a point set to a Shapely polygon representing its convex hull. + """Convert a 3d point set to a Shapely polygon representing its convex hull. Args: - points: list of 2d coordinate points + points: list of 3d coordinate points Returns: polygon: shapely polygon representing the results @@ -134,13 +134,9 @@ def get_polygon_from_points(points: np.ndarray) -> Polygon: poly = [] - for simplex in hull.simplices: - poly.append([points[simplex, 0][0], points[simplex, 1][0], points[simplex, 2][0]]) - poly.append([points[simplex, 0][1], points[simplex, 1][1], points[simplex, 2][1]]) - - # plt.plot(points[simplex, 0], points[simplex, 1], 'k-') - - return Polygon(poly) + # `simplices` contains indices of points forming the simplical facets of the convex hull. + poly_pts = hull.points[np.unique(hull.simplices)] + return Polygon(poly_pts) def get_rotated_bbox_from_points(points: np.ndarray) -> Polygon: @@ -156,16 +152,6 @@ def get_rotated_bbox_from_points(points: np.ndarray) -> Polygon: return get_polygon_from_points(points).minimum_rotated_rectangle -def unit_vector(pt0: Tuple[float, float], pt1: Tuple[float, float]) -> Tuple[float, float]: - # returns an unit vector that points in the direction of pt0 to pt1 - dis_0_to_1 = math.sqrt((pt0[0] - pt1[0]) ** 2 + (pt0[1] - pt1[1]) ** 2) - return (pt1[0] - pt0[0]) / dis_0_to_1, (pt1[1] - pt0[1]) / dis_0_to_1 - - -def dist(p1: Tuple[float, float], p2: Tuple[float, float]) -> float: - return math.sqrt(((p1[0] - p2[0]) ** 2) + ((p1[1] - p2[1]) ** 2)) - - def poly_to_label(poly: Polygon, category: str = "VEHICLE", track_id: str = "") -> ObjectLabelRecord: """Convert a Shapely Polygon to a 3d cuboid by estimating the minimum-bounding rectangle. @@ -178,31 +164,37 @@ def poly_to_label(poly: Polygon, category: str = "VEHICLE", track_id: str = "") object representing a 3d cuboid """ bbox = poly.minimum_rotated_rectangle + centroid = bbox.centroid.coords[0] - x = bbox.exterior.xy[0] - y = bbox.exterior.xy[1] - z = np.array([z for _, _, z in poly.exterior.coords]) + # exterior consists of of x and y values for bbox vertices [0,1,2,3,0], i.e. the first vertex is repeated as last + x = np.array(bbox.exterior.xy[0]).reshape(5, 1) + y = np.array(bbox.exterior.xy[1]).reshape(5, 1) + + v0, v1, v2, v3, _ = np.hstack([x, y]) - # z = poly.exterior.xy[2] + z = np.array([z for _, _, z in poly.exterior.coords]) + height = max(z) - min(z) - d1 = dist((x[0], y[0]), (x[1], y[1])) - d2 = dist((x[1], y[1]), (x[2], y[2])) + d1 = np.linalg.norm(v0 - v1) + d2 = np.linalg.norm(v1 - v2) + # assign orientation so that the rectangle's longest side represents the object's length width = min(d1, d2) length = max(d1, d2) - if max(d1, d2) == d2: - unit_v = unit_vector((x[1], y[1]), (x[2], y[2])) + if d2 == length: + # vector points from v1 -> v2 + v = v2 - v1 else: - unit_v = unit_vector((x[0], y[0]), (x[1], y[1])) + # vector points from v0 -> v1 + v = v0 - v1 - angle_rad = math.atan2(unit_v[1], unit_v[0]) + # vector need not be unit length + angle_rad = np.arctan2(v[1], v[0]) q = yaw_to_quaternion3d(angle_rad) - height = max(z) - min(z) - - # translation = center - center = np.array([bbox.centroid.xy[0][0], bbox.centroid.xy[1][0], min(z) + height / 2]) + # location of object in egovehicle coordinates + center = np.array([centroid[0], centroid[1], min(z) + height / 2]) return ObjectLabelRecord( quaternion=q, From 240ae4e3289f7d41646b3b20c01e0c1061ab7693 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Sat, 26 Jun 2021 18:45:20 -0400 Subject: [PATCH 07/11] add missing unit tests for competition util --- tests/test_competition_util.py | 98 +++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 7 deletions(-) diff --git a/tests/test_competition_util.py b/tests/test_competition_util.py index d3f8019b..1d9063f9 100644 --- a/tests/test_competition_util.py +++ b/tests/test_competition_util.py @@ -1,20 +1,104 @@ import numpy as np -from argoverse.evaluation.competition_label import get_polygon_from_points, poly_to_label +from argoverse.evaluation.competition_util import get_polygon_from_points, poly_to_label def test_get_polygon_from_points() -> None: - """ """ - points = np.array([[], [], [], []]) + """Ensure polygon contains only points within the convex hull. + + Point set shape looks like: + .__. + | | + | | + .__. + """ + # z values between -1 and 2 + # Note: point 1 should be missing in convex hull + points = np.array( + [ + # at upper level + [1, 0, 2], + [3, 3, 2], + [2, 3, 2], # interior as linear combination of points 1 and 3 + [1, 3, 2], + [3, 1, 2], + # now, at lower level + [1, 0, -1], + [3, 3, -1], + [2, 3, -1], # interior as linear combination of points 1 and 3 + [1, 3, -1], + [3, 1, -1], + ] + ) poly = get_polygon_from_points(points) - assert True + # note: first point is repeated as last point + expected_exterior_coords = [ + (1.0, 0.0, 2.0), + (3.0, 3.0, 2.0), + (1.0, 3.0, 2.0), + (3.0, 1.0, 2.0), + (1.0, 0.0, -1.0), + (3.0, 3.0, -1.0), + (1.0, 3.0, -1.0), + (3.0, 1.0, -1.0), + (1.0, 0.0, 2.0), + ] + + assert list(poly.exterior.coords) == expected_exterior_coords def test_poly_to_label() -> None: - """ """ + """Make sure we can recover a cuboid, from a point set. + + Shape should resemble a slanted bounding box, 2 * sqrt(2) in width, and 3 * sqrt(2) in length + . + / \\ + ./ \\ + \\ \\ + \\ / + \\ / + . + """ + # fmt: off + points = np.array( + [ + [4, 6, -1], + [4, 4, 2], + [7, 5, 1], + [7, 3, 0.5], + [6, 4, 0], + [6, 2, 0], + [7, 5, 0], + [5, 7, -1], + [8, 4, 0] + ] + ) + # fmt: on poly = get_polygon_from_points(points) - object_rec = poly_to_label(poly, category="VEHICLE", track_id="123") - assert True + bbox_verts_2d = object_rec.as_2d_bbox() + + # fmt: off + expected_bbox_verts_2d = np.array( + [ + [8, 4, 2], + [6, 2, 2], + [5, 7, 2], + [3, 5, 2] + ] + ) + # fmt: on + assert np.allclose(expected_bbox_verts_2d, bbox_verts_2d) + + expected_length = np.sqrt(2) * 3 + expected_width = np.sqrt(2) * 2 + expected_height = 3.0 + + assert np.isclose(object_rec.length, expected_length) + assert np.isclose(object_rec.width, expected_width) + assert np.isclose(object_rec.height, expected_height) + + assert object_rec.label_class == "VEHICLE" + assert object_rec.track_id == "123" From 724d41bdb2b22f4f0ffe7248910b0908c5546df3 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Sat, 26 Jun 2021 19:02:54 -0400 Subject: [PATCH 08/11] remove unused variable --- argoverse/evaluation/competition_util.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/argoverse/evaluation/competition_util.py b/argoverse/evaluation/competition_util.py index 3df15ffd..223986c0 100644 --- a/argoverse/evaluation/competition_util.py +++ b/argoverse/evaluation/competition_util.py @@ -127,13 +127,11 @@ def get_polygon_from_points(points: np.ndarray) -> Polygon: points: list of 3d coordinate points Returns: - polygon: shapely polygon representing the results + polygon: shapely Polygon representing the points along the convex hull's boundary """ points = points hull = ConvexHull(points) - poly = [] - # `simplices` contains indices of points forming the simplical facets of the convex hull. poly_pts = hull.points[np.unique(hull.simplices)] return Polygon(poly_pts) From fd31957a501278dba0f79a4fe4ae7d3c50fd6cc3 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Sat, 26 Jun 2021 19:07:04 -0400 Subject: [PATCH 09/11] remove unused var --- argoverse/evaluation/competition_util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/argoverse/evaluation/competition_util.py b/argoverse/evaluation/competition_util.py index 223986c0..37249bfb 100644 --- a/argoverse/evaluation/competition_util.py +++ b/argoverse/evaluation/competition_util.py @@ -129,7 +129,6 @@ def get_polygon_from_points(points: np.ndarray) -> Polygon: Returns: polygon: shapely Polygon representing the points along the convex hull's boundary """ - points = points hull = ConvexHull(points) # `simplices` contains indices of points forming the simplical facets of the convex hull. From 3cbffb226d9a7a42d005a4fd78e642da85d2aa85 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Mon, 28 Jun 2021 10:12:41 -0400 Subject: [PATCH 10/11] improve docstrings --- argoverse/data_loading/object_label_record.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/argoverse/data_loading/object_label_record.py b/argoverse/data_loading/object_label_record.py index 8c15a8d8..7d89a7a3 100644 --- a/argoverse/data_loading/object_label_record.py +++ b/argoverse/data_loading/object_label_record.py @@ -37,7 +37,7 @@ class ObjectLabelRecord: - """Parameterizes an object via a 3d bounding box and the object's pose within the egovehicle's frame. + """Parameterizes an object via a 3d bounding box and its pose within the egovehicle's reference frame. We refer to the object's pose as `egovehicle_SE3_object` and is parameterized by (R,t), where R is a quaternion in scalar-first order. @@ -78,7 +78,7 @@ def __init__( self.score = score def as_2d_bbox(self) -> np.ndarray: - """Convert the object cuboid to a 2D bounding box, with vertices inside the egovehicle's frame. + """Convert the object cuboid to a 2D bounding box, with vertices provided in the egovehicle's reference frame. Length is x, width is y, and z is height From 6851762a90a44bbd3b246e7dee4b38c416ab0820 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Mon, 28 Jun 2021 12:30:45 -0400 Subject: [PATCH 11/11] Add missing period in docstring --- argoverse/data_loading/object_label_record.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/argoverse/data_loading/object_label_record.py b/argoverse/data_loading/object_label_record.py index 7d89a7a3..c11df8d5 100644 --- a/argoverse/data_loading/object_label_record.py +++ b/argoverse/data_loading/object_label_record.py @@ -58,7 +58,7 @@ def __init__( """Create an ObjectLabelRecord. Args: - quaternion: Numpy vector representing quaternion (qw,qx,qy,qz), box/cuboid orientation + quaternion: Numpy vector representing quaternion (qw,qx,qy,qz), box/cuboid orientation. translation: Numpy vector representing translation, center of box given as x, y, z. length: object length. width: object width.