From 53fc2032cda1b45d7165f68f451aae860beeed4c Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Fri, 29 Nov 2024 21:35:23 +0900 Subject: [PATCH 1/3] feat: add support of rendering boxes with its elements Signed-off-by: ktro2828 --- t4_devkit/viewer/rendering_data/box.py | 56 +++++++++++++++++-- t4_devkit/viewer/viewer.py | 77 +++++++++++++++++++++++--- 2 files changed, 120 insertions(+), 13 deletions(-) diff --git a/t4_devkit/viewer/rendering_data/box.py b/t4_devkit/viewer/rendering_data/box.py index 981cdec..49bf475 100644 --- a/t4_devkit/viewer/rendering_data/box.py +++ b/t4_devkit/viewer/rendering_data/box.py @@ -1,13 +1,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from functools import singledispatchmethod import numpy as np import rerun as rr -if TYPE_CHECKING: - from t4_devkit.dataclass import Box2D, Box3D - from t4_devkit.typing import RoiType, SizeType, TranslationType, VelocityType +from t4_devkit.dataclass import Box2D, Box3D +from t4_devkit.typing import RoiType, RotationType, SizeType, TranslationType, VelocityType __all__ = ["BoxData3D", "BoxData2D"] @@ -30,7 +29,12 @@ def __init__(self, label2id: dict[str, int] | None = None) -> None: self._label2id: dict[str, int] = {} if label2id is None else label2id - def append(self, box: Box3D) -> None: + @singledispatchmethod + def append(self, *args, **kwargs) -> None: + raise TypeError("Unexpected parameter types.") + + @append.register + def append_with_box(self, box: Box3D) -> None: """Append a 3D box data. Args: @@ -55,6 +59,32 @@ def append(self, box: Box3D) -> None: if box.velocity is not None: self._velocities.append(box.velocity) + @append.register + def append_with_elements( + self, + center: TranslationType, + rotation: RotationType, + size: SizeType, + class_id: int, + uuid: str | None = None, + velocity: VelocityType | None = None, + ) -> None: + self._centers.append(center) + + rotation_xyzw = np.roll(rotation.q, shift=-1) + self._rotations.append(rr.Quaternion(xyzw=rotation_xyzw)) + + width, length, height = size + self._sizes.append((length, width, height)) + + self._class_ids.append(class_id) + + if uuid is not None: + self._uuids.append(uuid) + + if velocity is not None: + self._velocities.append(velocity) + def as_boxes3d(self) -> rr.Boxes3D: """Return 3D boxes data as a `rr.Boxes3D`. @@ -98,7 +128,12 @@ def __init__(self, label2id: dict[str, int] | None = None) -> None: self._label2id: dict[str, int] = {} if label2id is None else label2id - def append(self, box: Box2D) -> None: + @singledispatchmethod + def append(self, *args, **kwargs) -> None: + raise TypeError("Unexpected parameter types.") + + @append.register + def append_with_box(self, box: Box2D) -> None: """Append a 2D box data. Args: @@ -114,6 +149,15 @@ def append(self, box: Box2D) -> None: if box.uuid is not None: self._uuids.append(box.uuid) + @append.register + def append_with_elements(self, roi: RoiType, class_id: int, uuid: str | None = None) -> None: + self._rois.append(roi) + + self._class_ids.append(class_id) + + if uuid is not None: + self._uuids.append(uuid) + def as_boxes2d(self) -> rr.Boxes2D: """Return 2D boxes data as a `rr.Boxes2D`. diff --git a/t4_devkit/viewer/viewer.py b/t4_devkit/viewer/viewer.py index f7101d6..cc00205 100644 --- a/t4_devkit/viewer/viewer.py +++ b/t4_devkit/viewer/viewer.py @@ -3,7 +3,7 @@ import os.path as osp import warnings from functools import singledispatchmethod -from typing import TYPE_CHECKING, Sequence +from typing import Sequence import numpy as np import rerun as rr @@ -11,22 +11,22 @@ from typing_extensions import Self from t4_devkit.common.timestamp import us2sec +from t4_devkit.dataclass import Box2D, Box3D, PointCloudLike from t4_devkit.schema import CalibratedSensor, EgoPose, Sensor, SensorModality from t4_devkit.typing import ( CamIntrinsicType, GeoCoordinateType, NDArrayU8, + RoiType, RotationType, + SizeType, TranslationType, + VelocityType, ) from .color import distance_color from .rendering_data import BoxData2D, BoxData3D, SegmentationData2D -if TYPE_CHECKING: - from t4_devkit.dataclass import Box2D, Box3D, PointCloudLike - - __all__ = ["Tier4Viewer", "format_entity"] @@ -172,7 +172,12 @@ def save(self, save_dir: str) -> None: filepath = osp.join(save_dir, f"{self.app_id}.rrd") rr.save(filepath, default_blueprint=self.blueprint) - def render_box3ds(self, seconds: float, boxes: Sequence[Box3D]) -> None: + @singledispatchmethod + def render_box3ds(self, *args, **kwargs) -> None: + raise TypeError("Unexpected parameter types.") + + @render_box3ds.register + def render_box3ds_with_boxes(self, seconds: float, boxes: Sequence[Box3D]) -> None: """Render 3D boxes. Note that if the viewer initialized with `with_3d=False`, no 3D box will be rendered. @@ -205,7 +210,42 @@ def render_box3ds(self, seconds: float, boxes: Sequence[Box3D]) -> None: data.as_arrows3d(), ) - def render_box2ds(self, seconds: float, boxes: Sequence[Box2D]) -> None: + @render_box3ds.register + def render_box3ds_with_elements( + self, + seconds: float, + centers: Sequence[TranslationType], + rotations: Sequence[RotationType], + sizes: Sequence[SizeType], + class_ids: Sequence[int], + uuids: Sequence[str] | None | None = None, + velocities: Sequence[VelocityType] | None = None, + ) -> None: + if uuids is None: + uuids = [None] * len(centers) + + if velocities is None: + velocities = [None] * len(centers) + + box_data = BoxData3D(label2id=self.label2id) + for center, rotation, size, class_id, uuid, velocity in zip( + centers, rotations, sizes, class_ids, uuids, velocities, strict=True + ): + box_data.append(center, rotation, size, class_id, uuid, velocity) + + rr.set_time_seconds(self.timeline, seconds) + + rr.log(format_entity(self.ego_entity, "box"), box_data.as_boxes3d()) + + if velocities is not None: + rr.log(format_entity(self.ego_entity, "velocity"), box_data.as_arrows3d()) + + @singledispatchmethod + def render_box2ds(self, *args, **kwargs) -> None: + raise TypeError("Unexpected parameter types.") + + @render_box2ds.register + def render_box2ds_with_boxes(self, seconds: float, boxes: Sequence[Box2D]) -> None: """Render 2D boxes. Note that if the viewer initialized without `cameras=None`, no 2D box will be rendered. @@ -232,6 +272,29 @@ def render_box2ds(self, seconds: float, boxes: Sequence[Box2D]) -> None: data.as_boxes2d(), ) + @render_box2ds.register + def render_box2ds_with_elements( + self, + seconds: float, + camera: str, + rois: Sequence[RoiType], + class_ids: Sequence[int], + uuids: Sequence[str] | None = None, + ) -> None: + if not self.with_2d: + warnings.warn("There is no camera space.") + return + + if uuids is None: + uuids = [None] * len(rois) + + box_data = BoxData2D(label2id=self.label2id) + for roi, class_id, uuid in zip(rois, class_ids, uuids, strict=True): + box_data.append(roi, class_id, uuid) + + rr.set_time_seconds(self.timeline, seconds) + rr.log(format_entity(self.ego_entity, camera, "box"), box_data.as_boxes2d()) + def render_segmentation2d( self, seconds: float, From 27ad19c2aaf506695cc0c05b42efb97bc2704ddf Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Sat, 30 Nov 2024 09:42:47 +0900 Subject: [PATCH 2/3] refactor: apply overload instead of singledispatchmethod Signed-off-by: ktro2828 --- t4_devkit/viewer/rendering_data/box.py | 98 +++++++---- t4_devkit/viewer/viewer.py | 220 ++++++++++++++++++------- tests/conftest.py | 6 + tests/viewer/conftest.py | 6 +- tests/viewer/test_viewer.py | 52 ++++-- 5 files changed, 282 insertions(+), 100 deletions(-) diff --git a/t4_devkit/viewer/rendering_data/box.py b/t4_devkit/viewer/rendering_data/box.py index 49bf475..7f004ad 100644 --- a/t4_devkit/viewer/rendering_data/box.py +++ b/t4_devkit/viewer/rendering_data/box.py @@ -1,12 +1,13 @@ from __future__ import annotations -from functools import singledispatchmethod +from typing import TYPE_CHECKING, overload import numpy as np import rerun as rr -from t4_devkit.dataclass import Box2D, Box3D -from t4_devkit.typing import RoiType, RotationType, SizeType, TranslationType, VelocityType +if TYPE_CHECKING: + from t4_devkit.dataclass import Box2D, Box3D + from t4_devkit.typing import RoiType, RotationType, SizeType, TranslationType, VelocityType __all__ = ["BoxData3D", "BoxData2D"] @@ -29,17 +30,44 @@ def __init__(self, label2id: dict[str, int] | None = None) -> None: self._label2id: dict[str, int] = {} if label2id is None else label2id - @singledispatchmethod - def append(self, *args, **kwargs) -> None: - raise TypeError("Unexpected parameter types.") - - @append.register - def append_with_box(self, box: Box3D) -> None: - """Append a 3D box data. + @overload + def append(self, box: Box3D) -> None: + """Append a 3D box data with a Box3D object. Args: box (Box3D): `Box3D` object. """ + pass + + @overload + def append( + self, + center: TranslationType, + rotation: RotationType, + size: SizeType, + class_id: int, + uuid: str | None = None, + velocity: VelocityType | None = None, + ) -> None: + """Append a 3D box data with its elements. + + Args: + center (TranslationType): 3D position in the order of (x, y, z). + rotation (RotationType): Quaternion. + size (SizeType): Box size in the order of (width, height, length). + class_id (int): Class ID. + velocity (VelocityType | None, optional): Box velocity. Defaults to None. + uuid (str | None, optional): Unique identifier. + """ + pass + + def append(self, *args, **kwargs) -> None: + if len(args) + len(kwargs) == 1: + self._append_with_box(*args, **kwargs) + else: + self._append_with_elements(*args, **kwargs) + + def _append_with_box(self, box: Box3D) -> None: self._centers.append(box.position) rotation_xyzw = np.roll(box.rotation.q, shift=-1) @@ -53,21 +81,20 @@ def append_with_box(self, box: Box3D) -> None: self._class_ids.append(self._label2id[box.semantic_label.name]) - if box.uuid is not None: - self._uuids.append(box.uuid[:6]) - if box.velocity is not None: self._velocities.append(box.velocity) - @append.register - def append_with_elements( + if box.uuid is not None: + self._uuids.append(box.uuid[:6]) + + def _append_with_elements( self, center: TranslationType, rotation: RotationType, size: SizeType, class_id: int, - uuid: str | None = None, velocity: VelocityType | None = None, + uuid: str | None = None, ) -> None: self._centers.append(center) @@ -79,12 +106,12 @@ def append_with_elements( self._class_ids.append(class_id) - if uuid is not None: - self._uuids.append(uuid) - if velocity is not None: self._velocities.append(velocity) + if uuid is not None: + self._uuids.append(uuid) + def as_boxes3d(self) -> rr.Boxes3D: """Return 3D boxes data as a `rr.Boxes3D`. @@ -128,17 +155,33 @@ def __init__(self, label2id: dict[str, int] | None = None) -> None: self._label2id: dict[str, int] = {} if label2id is None else label2id - @singledispatchmethod - def append(self, *args, **kwargs) -> None: - raise TypeError("Unexpected parameter types.") - - @append.register - def append_with_box(self, box: Box2D) -> None: - """Append a 2D box data. + @overload + def append(self, box: Box2D) -> None: + """Append a 2D box data with a `Box2D` object. Args: box (Box2D): `Box2D` object. """ + pass + + @overload + def append(self, roi: RoiType, class_id: int, uuid: str | None = None) -> None: + """Append a 2D box data with its elements. + + Args: + roi (RoiType): ROI in the order of (xmin, ymin, xmax, ymax). + class_id (int): Class ID. + uuid (str | None, optional): Unique identifier. + """ + pass + + def append(self, *args, **kwargs) -> None: + if len(args) + len(kwargs) == 1: + self._append_with_box(*args, **kwargs) + else: + self._append_with_elements(*args, **kwargs) + + def _append_with_box(self, box: Box2D) -> None: self._rois.append(box.roi.roi) if box.semantic_label.name not in self._label2id: @@ -149,8 +192,7 @@ def append_with_box(self, box: Box2D) -> None: if box.uuid is not None: self._uuids.append(box.uuid) - @append.register - def append_with_elements(self, roi: RoiType, class_id: int, uuid: str | None = None) -> None: + def _append_with_elements(self, roi: RoiType, class_id: int, uuid: str | None = None) -> None: self._rois.append(roi) self._class_ids.append(class_id) diff --git a/t4_devkit/viewer/viewer.py b/t4_devkit/viewer/viewer.py index cc00205..f165e61 100644 --- a/t4_devkit/viewer/viewer.py +++ b/t4_devkit/viewer/viewer.py @@ -2,8 +2,7 @@ import os.path as osp import warnings -from functools import singledispatchmethod -from typing import Sequence +from typing import TYPE_CHECKING, Sequence, overload import numpy as np import rerun as rr @@ -11,22 +10,25 @@ from typing_extensions import Self from t4_devkit.common.timestamp import us2sec -from t4_devkit.dataclass import Box2D, Box3D, PointCloudLike -from t4_devkit.schema import CalibratedSensor, EgoPose, Sensor, SensorModality -from t4_devkit.typing import ( - CamIntrinsicType, - GeoCoordinateType, - NDArrayU8, - RoiType, - RotationType, - SizeType, - TranslationType, - VelocityType, -) +from t4_devkit.schema import SensorModality from .color import distance_color from .rendering_data import BoxData2D, BoxData3D, SegmentationData2D +if TYPE_CHECKING: + from t4_devkit.dataclass import Box2D, Box3D, PointCloudLike + from t4_devkit.schema import CalibratedSensor, EgoPose, Sensor + from t4_devkit.typing import ( + CamIntrinsicType, + GeoCoordinateType, + NDArrayU8, + RoiType, + RotationType, + SizeType, + TranslationType, + VelocityType, + ) + __all__ = ["Tier4Viewer", "format_entity"] @@ -172,12 +174,8 @@ def save(self, save_dir: str) -> None: filepath = osp.join(save_dir, f"{self.app_id}.rrd") rr.save(filepath, default_blueprint=self.blueprint) - @singledispatchmethod - def render_box3ds(self, *args, **kwargs) -> None: - raise TypeError("Unexpected parameter types.") - - @render_box3ds.register - def render_box3ds_with_boxes(self, seconds: float, boxes: Sequence[Box3D]) -> None: + @overload + def render_box3ds(self, seconds: float, boxes: Sequence[Box3D]) -> None: """Render 3D boxes. Note that if the viewer initialized with `with_3d=False`, no 3D box will be rendered. @@ -185,6 +183,40 @@ def render_box3ds_with_boxes(self, seconds: float, boxes: Sequence[Box3D]) -> No seconds (float): Timestamp in [sec]. boxes (Sequence[Box3D]): Sequence of `Box3D`s. """ + pass + + @overload + def render_box3ds( + self, + seconds: float, + centers: Sequence[TranslationType], + rotations: Sequence[RotationType], + sizes: Sequence[SizeType], + class_ids: Sequence[int], + velocities: Sequence[VelocityType] | None = None, + uuids: Sequence[str] | None | None = None, + ) -> None: + """Render 3D boxes with its elements. + + Args: + seconds (float): Timestamp in [sec]. + centers (Sequence[TranslationType]): Sequence of 3D positions in the order of (x, y, z). + rotations (Sequence[RotationType]): Sequence of quaternions. + sizes (Sequence[SizeType]): Sequence of box sizes in the order of (width, length, height). + class_ids (Sequence[int]): Sequence of class IDs. + velocities (Sequence[VelocityType] | None, optional): Sequence of velocities. + uuids (Sequence[str] | None | None, optional): Sequence of unique identifiers. + """ + pass + + def render_box3ds(self, *args, **kwargs) -> None: + """Render 3D boxes.""" + if len(args) + len(kwargs) == 2: + self._render_box3ds_with_boxes(*args, **kwargs) + else: + self._render_box3ds_with_elements(*args, **kwargs) + + def _render_box3ds_with_boxes(self, seconds: float, boxes: Sequence[Box3D]) -> None: if not self.with_3d: warnings.warn("There is no camera space.") return @@ -210,16 +242,15 @@ def render_box3ds_with_boxes(self, seconds: float, boxes: Sequence[Box3D]) -> No data.as_arrows3d(), ) - @render_box3ds.register - def render_box3ds_with_elements( + def _render_box3ds_with_elements( self, seconds: float, centers: Sequence[TranslationType], rotations: Sequence[RotationType], sizes: Sequence[SizeType], class_ids: Sequence[int], - uuids: Sequence[str] | None | None = None, velocities: Sequence[VelocityType] | None = None, + uuids: Sequence[str] | None | None = None, ) -> None: if uuids is None: uuids = [None] * len(centers) @@ -228,10 +259,17 @@ def render_box3ds_with_elements( velocities = [None] * len(centers) box_data = BoxData3D(label2id=self.label2id) - for center, rotation, size, class_id, uuid, velocity in zip( - centers, rotations, sizes, class_ids, uuids, velocities, strict=True + for center, rotation, size, class_id, velocity, uuid in zip( + centers, rotations, sizes, class_ids, velocities, uuids, strict=True ): - box_data.append(center, rotation, size, class_id, uuid, velocity) + box_data.append( + center=center, + rotation=rotation, + size=size, + class_id=class_id, + velocity=velocity, + uuid=uuid, + ) rr.set_time_seconds(self.timeline, seconds) @@ -240,12 +278,8 @@ def render_box3ds_with_elements( if velocities is not None: rr.log(format_entity(self.ego_entity, "velocity"), box_data.as_arrows3d()) - @singledispatchmethod - def render_box2ds(self, *args, **kwargs) -> None: - raise TypeError("Unexpected parameter types.") - - @render_box2ds.register - def render_box2ds_with_boxes(self, seconds: float, boxes: Sequence[Box2D]) -> None: + @overload + def render_box2ds(self, seconds: float, boxes: Sequence[Box2D]) -> None: """Render 2D boxes. Note that if the viewer initialized without `cameras=None`, no 2D box will be rendered. @@ -253,6 +287,36 @@ def render_box2ds_with_boxes(self, seconds: float, boxes: Sequence[Box2D]) -> No seconds (float): Timestamp in [sec]. boxes (Sequence[Box2D]): Sequence of `Box2D`s. """ + pass + + @overload + def render_box2ds( + self, + seconds: float, + camera: str, + rois: Sequence[RoiType], + class_ids: Sequence[int], + uuids: Sequence[str] | None = None, + ) -> None: + """Render 2D boxes with its elements. + + Args: + seconds (float): Timestamp in [sec]. + camera (str): Camera name. + rois (Sequence[RoiType]): Sequence of ROIs in the order of (xmin, ymin, xmax, ymax). + class_ids (Sequence[int]): Sequence of class IDs. + uuids (Sequence[str] | None, optional): Sequence of unique identifiers. + """ + pass + + def render_box2ds(self, *args, **kwargs) -> None: + """Render 2D boxes.""" + if len(args) + len(kwargs) == 2: + self._render_box2ds_with_boxes(*args, **kwargs) + else: + self._render_box2ds_with_elements(*args, **kwargs) + + def _render_box2ds_with_boxes(self, seconds: float, boxes: Sequence[Box2D]) -> None: if not self.with_2d: warnings.warn("There is no camera space.") return @@ -272,8 +336,7 @@ def render_box2ds_with_boxes(self, seconds: float, boxes: Sequence[Box2D]) -> No data.as_boxes2d(), ) - @render_box2ds.register - def render_box2ds_with_elements( + def _render_box2ds_with_elements( self, seconds: float, camera: str, @@ -290,7 +353,7 @@ def render_box2ds_with_elements( box_data = BoxData2D(label2id=self.label2id) for roi, class_id, uuid in zip(rois, class_ids, uuids, strict=True): - box_data.append(roi, class_id, uuid) + box_data.append(roi=roi, class_id=class_id, uuid=uuid) rr.set_time_seconds(self.timeline, seconds) rr.log(format_entity(self.ego_entity, camera, "box"), box_data.as_boxes2d()) @@ -369,26 +432,17 @@ def render_image(self, seconds: float, camera: str, image: str | NDArrayU8) -> N else: rr.log(format_entity(self.ego_entity, camera), rr.Image(image)) - @singledispatchmethod - def render_ego(self, *args, **kwargs) -> None: - raise TypeError("Unexpected parameter types.") - - @render_ego.register - def _render_ego_with_schema(self, ego_pose: EgoPose) -> None: + @overload + def render_ego(self, ego_pose: EgoPose) -> None: """Render an ego pose. Args: ego_pose (EgoPose): `EgoPose` object. """ - self._render_ego_without_schema( - seconds=us2sec(ego_pose.timestamp), - translation=ego_pose.translation, - rotation=ego_pose.rotation, - geocoordinate=ego_pose.geocoordinate, - ) + pass - @render_ego.register - def _render_ego_without_schema( + @overload + def render_ego( self, seconds: float, translation: TranslationType, @@ -405,6 +459,30 @@ def _render_ego_without_schema( geocoordinate (GeoCoordinateType | None, optional): Coordinates in the WGS 84 reference ellipsoid (latitude, longitude, altitude) in degrees and meters. """ + pass + + def render_ego(self, *args, **kwargs) -> None: + """Render an ego pose.""" + if len(args) + len(kwargs) == 1: + self._render_ego_with_schema(*args, **kwargs) + else: + self._render_ego_without_schema(*args, **kwargs) + + def _render_ego_with_schema(self, ego_pose: EgoPose) -> None: + self._render_ego_without_schema( + seconds=us2sec(ego_pose.timestamp), + translation=ego_pose.translation, + rotation=ego_pose.rotation, + geocoordinate=ego_pose.geocoordinate, + ) + + def _render_ego_without_schema( + self, + seconds: float, + translation: TranslationType, + rotation: RotationType, + geocoordinate: GeoCoordinateType | None = None, + ) -> None: rr.set_time_seconds(self.timeline, seconds) rotation_xyzw = np.roll(rotation.q, shift=-1) @@ -424,12 +502,8 @@ def _render_ego_without_schema( rr.GeoPoints(lat_lon=(latitude, longitude)), ) - @singledispatchmethod - def render_calibration(self, *args, **kwargs) -> None: - raise TypeError("Unexpected parameter types.") - - @render_calibration.register - def _render_calibration_with_schema( + @overload + def render_calibration( self, sensor: Sensor, calibration: CalibratedSensor, @@ -440,6 +514,41 @@ def _render_calibration_with_schema( sensor (Sensor): `Sensor` object. calibration (CalibratedSensor): `CalibratedSensor` object. """ + pass + + @overload + def render_calibration( + self, + channel: str, + modality: str | SensorModality, + translation: TranslationType, + rotation: RotationType, + camera_intrinsic: CamIntrinsicType | None = None, + ) -> None: + """Render a sensor calibration. + + Args: + channel (str): Name of the sensor channel. + modality (str | SensorModality): Sensor modality. + translation (TranslationType): Sensor translation in ego centric coords. + rotation (RotationType): Sensor rotation in ego centric coords. + camera_intrinsic (CamIntrinsicType | None, optional): Camera intrinsic matrix. + Defaults to None. + """ + pass + + def render_calibration(self, *args, **kwargs) -> None: + """Render a sensor calibration.""" + if len(args) + len(kwargs) == 2: + self._render_calibration_with_schema(*args, **kwargs) + else: + self._render_calibration_without_schema(*args, **kwargs) + + def _render_calibration_with_schema( + self, + sensor: Sensor, + calibration: CalibratedSensor, + ) -> None: self._render_calibration_without_schema( channel=sensor.channel, modality=sensor.modality, @@ -448,7 +557,6 @@ def _render_calibration_with_schema( camera_intrinsic=calibration.camera_intrinsic, ) - @render_calibration.register def _render_calibration_without_schema( self, channel: str, diff --git a/tests/conftest.py b/tests/conftest.py index 6ed3cff..0c3d90a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,6 +12,11 @@ ) +@pytest.fixture(scope="module") +def label2id() -> dict[str, int]: + return {"car": 0, "bicycle": 1, "pedestrian": 2} + + @pytest.fixture(scope="module") def dummy_box3d() -> Box3D: """Return a dummy 3D box. @@ -156,3 +161,4 @@ def dummy_tf_buffer() -> TransformBuffer: ) return tf_buffer + return tf_buffer diff --git a/tests/viewer/conftest.py b/tests/viewer/conftest.py index 6d124ca..3318157 100644 --- a/tests/viewer/conftest.py +++ b/tests/viewer/conftest.py @@ -4,10 +4,12 @@ @pytest.fixture(scope="module") -def dummy_viewer() -> Tier4Viewer: +def dummy_viewer(label2id) -> Tier4Viewer: """Return a dummy viewer. Returns: `Tier4Viewer` without spawning. """ - return Tier4Viewer("test_viewer", cameras=["camera"], spawn=False) + return Tier4Viewer("test_viewer", cameras=["camera"], spawn=False).with_labels( + label2id=label2id + ) diff --git a/tests/viewer/test_viewer.py b/tests/viewer/test_viewer.py index cd85c0f..b949e0d 100644 --- a/tests/viewer/test_viewer.py +++ b/tests/viewer/test_viewer.py @@ -27,6 +27,23 @@ def test_render_box3ds(dummy_viewer, dummy_box3ds) -> None: dummy_viewer.render_box3ds(seconds, dummy_box3ds) + centers = [box.position for box in dummy_box3ds] + rotations = [box.rotation for box in dummy_box3ds] + sizes = [box.size for box in dummy_box3ds] + class_ids = [dummy_viewer.label2id[box.semantic_label.name] for box in dummy_box3ds] + velocities = [box.velocity for box in dummy_box3ds] + uuids = [box.uuid for box in dummy_box3ds] + + dummy_viewer.render_box3ds( + seconds, + centers=centers, + rotations=rotations, + sizes=sizes, + class_ids=class_ids, + velocities=velocities, + uuids=uuids, + ) + def test_render_box2ds(dummy_viewer, dummy_box2ds) -> None: """Test rendering 2D boxes with `Tier4Viewer`. @@ -39,6 +56,13 @@ def test_render_box2ds(dummy_viewer, dummy_box2ds) -> None: dummy_viewer.render_box2ds(seconds, dummy_box2ds) + camera = "camera" + rois = [box.roi.roi for box in dummy_box2ds] + class_ids = [dummy_viewer.label2id[box.semantic_label.name] for box in dummy_box2ds] + uuids = [box.uuid for box in dummy_box2ds] + + dummy_viewer.render_box2ds(seconds, camera=camera, rois=rois, class_ids=class_ids, uuids=uuids) + def test_render_pointcloud(dummy_viewer) -> None: """Test rendering pointcloud with `Tier4Viewer`. @@ -61,15 +85,15 @@ def test_render_ego(dummy_viewer) -> None: seconds = 1.0 # [sec] # without `EgoPose` - ego_translation = [1, 0, 0] - ego_rotation = Quaternion([0, 0, 0, 1]) - dummy_viewer.render_ego(seconds, ego_translation, ego_rotation) + translation = [1, 0, 0] + rotation = Quaternion([0, 0, 0, 1]) + dummy_viewer.render_ego(seconds, translation=translation, rotation=rotation) # with `EgoPose` ego_pose = EgoPose( token="ego", - translation=ego_translation, - rotation=ego_rotation, + translation=translation, + rotation=rotation, timestamp=1e6, ) dummy_viewer.render_ego(ego_pose) @@ -84,19 +108,19 @@ def test_render_calibration(dummy_viewer) -> None: # without `Sensor` and `CalibratedSensor` channel = "camera" modality = "camera" - camera_translation = [1, 0, 0] - camera_rotation = Quaternion([0, 0, 0, 1]) + translation = [1, 0, 0] + rotation = Quaternion([0, 0, 0, 1]) camera_intrinsic = [ [1000.0, 0.0, 100.0], [0.0, 1000.0, 100.0], [0.0, 0.0, 1.0], ] dummy_viewer.render_calibration( - channel, - modality, - camera_translation, - camera_rotation, - camera_intrinsic, + channel=channel, + modality=modality, + translation=translation, + rotation=rotation, + camera_intrinsic=camera_intrinsic, ) # with `Sensor` and `CalibratedSensor` @@ -104,8 +128,8 @@ def test_render_calibration(dummy_viewer) -> None: calibration = CalibratedSensor( token="sensor_calibration", sensor_token="sensor", - translation=camera_translation, - rotation=camera_rotation, + translation=translation, + rotation=rotation, camera_intrinsic=camera_intrinsic, camera_distortion=[0, 0, 0, 0, 0], ) From b6becaec2d5f4d8f8c8de87281c7ce725e819237 Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Sat, 30 Nov 2024 10:45:08 +0900 Subject: [PATCH 3/3] docs: update documents Signed-off-by: ktro2828 --- docs/tutorials/render.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/tutorials/render.md b/docs/tutorials/render.md index e3a92b7..52820fc 100644 --- a/docs/tutorials/render.md +++ b/docs/tutorials/render.md @@ -67,3 +67,12 @@ For details, please refer to the API references. # Rendering 2D boxes >>> viewer.render_box2ds(seconds, box2ds) ``` + +It allows us to render boxes by specifying elements of boxes directly. + +```python +# Rendering 3D boxes +>>> viewer.render_box3ds(seconds, centers, rotations, sizes, class_ids) +# Rendering 2D boxes +>>> viewer.render_box2ds(seconds, rois, class_ids) +```