From ac205cd6eb1946b5347ebd8f7a396a70d4f77ff3 Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Fri, 22 Nov 2024 04:15:22 +0900 Subject: [PATCH] refacor: refactor viewer class Signed-off-by: ktro2828 --- t4_devkit/tier4.py | 2 +- t4_devkit/viewer/viewer.py | 97 +++++++++++++++++--------------------- 2 files changed, 45 insertions(+), 54 deletions(-) diff --git a/t4_devkit/tier4.py b/t4_devkit/tier4.py index b0f0608..62c0249 100644 --- a/t4_devkit/tier4.py +++ b/t4_devkit/tier4.py @@ -904,7 +904,7 @@ def _init_viewer( else None ) - viewer = Tier4Viewer(application_id, cameras=cameras, without_3d=not render_3d, spawn=spawn) + viewer = Tier4Viewer(application_id, cameras=cameras, with_3d=render_3d, spawn=spawn) if render_annotation: viewer = viewer.with_labels(self._label2id) diff --git a/t4_devkit/viewer/viewer.py b/t4_devkit/viewer/viewer.py index 29643fb..68e4cd1 100644 --- a/t4_devkit/viewer/viewer.py +++ b/t4_devkit/viewer/viewer.py @@ -71,9 +71,9 @@ class Tier4Viewer: def __init__( self, app_id: str, - cameras: Sequence[str] | None = None, *, - without_3d: bool = False, + cameras: Sequence[str] | None = None, + with_3d: bool = True, spawn: bool = True, ) -> None: """Construct a new object. @@ -82,7 +82,7 @@ def __init__( app_id (str): Application ID. cameras (Sequence[str] | None, optional): Sequence of camera names. If `None`, any 2D spaces will not be visualized. - without_3d (bool, optional): Whether to render objects without the 3D space. + with_3d (bool, optional): Whether to render objects with the 3D space. spawn (bool, optional): Whether to spawn the viewer. Examples: @@ -92,16 +92,18 @@ def __init__( # Rendering 3D space only >>> viewer = Tier4Viewer("myapp") # Rendering 2D space only - >>> viewer = Tier4Viewer("myapp", cameras=["camera0", "camera1"], without_3d=True) + >>> viewer = Tier4Viewer("myapp", cameras=["camera0", "camera1"], with_3d=False) """ self.app_id = app_id - self.without_3d = without_3d self.cameras = cameras + self.with_3d = with_3d + self.with_2d = self.cameras is not None - assert self.cameras is not None or not without_3d + if not self.with_3d and not self.with_2d: + raise ValueError("At least one of 3D or 2D spaces must be rendered.") - view_container = [] - if not without_3d: + view_container: list[rrb.Container | rrb.SpaceView] = [] + if self.with_3d: view_container.extend( [ rrb.Horizontal( @@ -112,7 +114,7 @@ def __init__( ] ) - if self.cameras is not None: + if self.with_2d: camera_space_views = [ rrb.Spatial2DView(name=name, origin=format_entity(self.ego_entity, name)) for name in self.cameras @@ -169,12 +171,17 @@ def save(self, save_dir: str) -> None: rr.save(filepath, default_blueprint=self.blueprint) def render_box3ds(self, seconds: float, boxes: Sequence[Box3D]) -> None: - """Render 3D boxes. + """Render 3D boxes. Note that if the viewer initialized with `with_3d=False`, + no 3D box will be rendered. Args: seconds (float): Timestamp in [sec]. boxes (Sequence[Box3D]): Sequence of `Box3D`s. """ + if not self.with_3d: + warnings.warn("There is no camera space.") + return + rr.set_time_seconds(self.timeline, seconds) box_data: dict[str, BoxData3D] = {} @@ -197,13 +204,14 @@ def render_box3ds(self, seconds: float, boxes: Sequence[Box3D]) -> None: ) def render_box2ds(self, seconds: float, boxes: Sequence[Box2D]) -> None: - """Render 2D boxes. + """Render 2D boxes. Note that if the viewer initialized without `cameras=None`, + no 2D box will be rendered. Args: seconds (float): Timestamp in [sec]. boxes (Sequence[Box2D]): Sequence of `Box2D`s. """ - if self.cameras is None: + if not self.with_2d: warnings.warn("There is no camera space.") return @@ -240,7 +248,7 @@ def render_segmentation2d( class_ids (Sequence[int]): Sequence of label ids. uuids (Sequence[str | None] | None, optional): Sequence of each instance ID. """ - if self.cameras is None or camera not in self.cameras: + if not self.with_2d or camera not in self.cameras: warnings.warn(f"There is no camera space: {camera}") return @@ -265,6 +273,7 @@ def render_pointcloud(self, seconds: float, channel: str, pointcloud: PointCloud channel (str): Name of the pointcloud sensor channel. pointcloud (PointCloudLike): Inherence object of `PointCloud`. """ + # TODO(ktro2828): add support of rendering pointcloud on images rr.set_time_seconds(self.timeline, seconds) colors = distance_color(np.linalg.norm(pointcloud.points[:3].T, axis=1)) @@ -284,7 +293,7 @@ def render_image(self, seconds: float, camera: str, image: str | NDArrayU8) -> N camera (str): Name of the camera channel. image (str | NDArrayU8): Image tensor or path of the image file. """ - if self.cameras is None or camera not in self.cameras: + if not self.with_2d or camera not in self.cameras: warnings.warn(f"There is no camera space: {camera}") return @@ -306,25 +315,13 @@ def _render_ego_with_schema(self, ego_pose: EgoPose) -> None: Args: ego_pose (EgoPose): `EgoPose` object. """ - rr.set_time_seconds(self.timeline, us2sec(ego_pose.timestamp)) - - rotation_xyzw = np.roll(ego_pose.rotation.q, shift=-1) - rr.log( - self.ego_entity, - rr.Transform3D( - translation=ego_pose.translation, - rotation=rr.Quaternion(xyzw=rotation_xyzw), - relation=rr.TransformRelation.ParentFromChild, - ), + self._render_ego_without_schema( + seconds=us2sec(ego_pose.timestamp), + translation=ego_pose.translation, + rotation=ego_pose.rotation, + geocoordinate=ego_pose.geocoordinate, ) - if ego_pose.geocoordinate is not None: - latitude, longitude, _ = ego_pose.geocoordinate - rr.log( - self.geocoordinate_entity, - rr.GeoPoints(lat_lon=(latitude, longitude), radii=rr.Radius.ui_points(10.0)), - ) - @render_ego.register def _render_ego_without_schema( self, @@ -333,6 +330,16 @@ def _render_ego_without_schema( rotation: RotationType, geocoordinate: GeoCoordinateType | None = None, ) -> None: + """Render an ego pose. + + Args: + seconds (float): Timestamp in [sec]. + translation (TranslationType): 3D position in the map coordinate system + , in the order of (x, y, z) in [m]. + rotation (RotationType): Rotation in the map coordinate system. + geocoordinate (GeoCoordinateType | None, optional): Coordinates in the WGS 84 + reference ellipsoid (latitude, longitude, altitude) in degrees and meters. + """ rr.set_time_seconds(self.timeline, seconds) rotation_xyzw = np.roll(rotation.q, shift=-1) @@ -368,24 +375,14 @@ def _render_calibration_with_schema( sensor (Sensor): `Sensor` object. calibration (CalibratedSensor): `CalibratedSensor` object. """ - rotation_xyzw = np.roll(calibration.rotation.q, shift=-1) - - rr.log( - format_entity(self.ego_entity, sensor.channel), - rr.Transform3D( - translation=calibration.translation, - rotation=rr.Quaternion(xyzw=rotation_xyzw), - ), - static=True, + self._render_calibration_without_schema( + channel=sensor.channel, + modality=sensor.modality, + translation=calibration.translation, + rotation=calibration.rotation, + camera_intrinsic=calibration.camera_intrinsic, ) - if sensor.modality == SensorModality.CAMERA: - rr.log( - format_entity(self.ego_entity, sensor.channel), - rr.Pinhole(image_from_camera=calibration.camera_intrinsic), - static=True, - ) - @render_calibration.register def _render_calibration_without_schema( self, @@ -419,9 +416,3 @@ def _render_calibration_without_schema( rr.Pinhole(image_from_camera=camera_intrinsic), static=True, ) - if modality == SensorModality.CAMERA: - rr.log( - format_entity(self.ego_entity, channel), - rr.Pinhole(image_from_camera=camera_intrinsic), - static=True, - )