Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ A toolkit to load and operate T4 dataset.

### Installation

#### Install via GitHub
#### [For Users] Install via GitHub

Note that the following command installs the latest `main` branch:

Expand All @@ -31,7 +31,7 @@ By specifying `@<TAG_OR_BRANCH>`, you can install the particular version of `t4-
pip install git+https://github.com/tier4/t4-devkit.git@main
```

#### Install from source
#### [For Developers] Install from source

You need to install `uv`. For details, please refer to [OFFICIAL DOCUMENT](https://docs.astral.sh/uv/).

Expand Down
Binary file added docs/assets/render_box2ds.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/render_box3ds.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/render_pointcloud.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/install.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Install via GitHub
## [For Users] Install via GitHub

Note that the following command installs the latest `main` branch:

Expand All @@ -14,7 +14,7 @@ By specifying `@<TAG_OR_BRANCH>`, you can install the particular version of `t4-
pip install git+https://github.com/tier4/t4-devkit.git@main
```

## Install from source
## [For Developers] Install from source

You need to install `uv`. For details, please refer to [OFFICIAL DOCUMENT](https://docs.astral.sh/uv/).

Expand Down
71 changes: 61 additions & 10 deletions docs/tutorials/render.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,37 +60,88 @@ When you specify `save_dir`, viewer will not be spawned on your screen.

## Rendering with `RerunViewer`

### Rendering boxes

If you want to visualize your components, such as boxes that your ML-model estimated, `RerunViewer` allows you to visualize these components.
For details, please refer to the API references.

```python
>>> from t4_devkit.viewer import RerunViewer
# You need to specify `cameras` if you want to 2D spaces
>>> viewer = RerunViewer(app_id, cameras=<CAMERA_NAMES:[str;N]>)
>>> viewer = RerunViewer("foo", cameras=<CAMERA_NAMES:[str;N]>)

# Timestamp in seconds
>>> seconds: int | float = ...
```

### Rendering 3D boxes

```python
# Rendering 3D boxes
>>> from t4_devkit.dataclass import Box3D
>>> box3ds = [Box3D(...)...]
>>> viewer.render_box3ds(seconds, box3ds)
# Rendering 2D boxes
>>> viewer.render_box2ds(seconds, box2ds)
```

It allows you to render boxes by specifying elements of boxes directly.

```python
# Rendering 3D boxes
>>> centers = [[i, i, i] for i in range(10)]
>>> rotations = [[1, 0, 0, 0] for _ in range(10)]
>>> sizes = [[1, 1, 1] for _ in range(10)]
>>> class_ids = [0 for _ in range(10)]
>>> viewer.render_box3ds(seconds, centers, rotations, sizes, class_ids)
```

![Render Box3Ds](../assets/render_box3ds.png)

### Rendering 2D boxes

For 2D spaces, you need to specify camera names in the viewer constructor, and render images by specifying camera names:

```python
# RerunViewer(<APP_ID:str>, cameras=<CAMERA_NAMES:[str;N]>)
>>> viewer = RerunViewer("foo", cameras=["camera1"])

>>> import numpy as np
>>> image = np.zeros((100, 100, 3), dtype=np.uint8)
>>> viewer.render_image(seconds, "camera1", image)
```

```python
# Rendering 2D boxes
>>> viewer.render_box2ds(seconds, rois, class_ids)
>>> from t4_devkit.dataclass import Box2D
>>> box2ds = [Box2D(...)...]
>>> viewer.render_box2ds(seconds, "camera1", box2ds)
```

### Rendering lanelet map
It allows you to render boxes by specifying elements of boxes directly:

![Render Lanelet Map](../assets/render_map.png)
```python
# Rendering 2D boxes
>>> rois = [[0, 0, 10 * i, 10 * i] for i in range(10)]
>>> viewer.render_box2ds(seconds, "camera1", rois, class_ids)
```

You can also render lanelet map by specifying `lanelet_path`:
![Render Box2Ds](../assets/render_box2ds.png)

### Rendering point cloud

```python
from t4_devkit.dataclass import LidarPointCloud
# Point cloud channel name
>>> lidar_channel = "LIDAR_TOP"
# Load point cloud from file
>>> pointcloud = LidarPointCloud.from_file(<PATH_TO_POINTCLOUD.pcd.bin>)
>>> viewer.render_pointcloud(seconds, lidar_channel, pointcloud)
```

![Render Point Cloud](../assets/render_pointcloud.png)

### Rendering lanelet map

```python
# Rendering lanelet map
>>> viewer.render_map(lanelet_path)
>>> viewer.render_map(<PATH_TO_LANELET.osm>)
```

![Render Lanelet Map](../assets/render_map.png)
28 changes: 18 additions & 10 deletions t4_devkit/viewer/record/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
import numpy as np
import rerun as rr
import rerun.components as rrc
from attrs import converters, define, field
from attrs import converters, define, field, validators

Copilot AI Sep 1, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import statement is missing the removal of unused imports. Since concrete types are replaced with generic types, imports like Roi and Vector3 from line 12 may no longer be needed if they're only used for type annotations that are now using *Like types.

Copilot uses AI. Check for mistakes.

from t4_devkit.common.converter import to_quaternion
from t4_devkit.dataclass import Future
from t4_devkit.typing import Roi, Vector3
from t4_devkit.typing.aliases import RoiLike

if TYPE_CHECKING:
from t4_devkit.dataclass import Box2D, Box3D, Future
from t4_devkit.typing import RotationLike, Vector3Like
from t4_devkit.dataclass import Box2D, Box3D
from t4_devkit.typing import RoiLike, RotationLike, Vector3Like


__all__ = ["BatchBox3D", "BatchBox2D"]
Expand All @@ -35,12 +36,16 @@ class Record:
"""Inner class to represent a record of 3D box instance for rendering."""

center: Vector3 = field(converter=Vector3)
rotation: rr.Quaternion
rotation: rr.Quaternion = field(validator=validators.instance_of(rr.Quaternion))
size: Vector3 = field(converter=Vector3)
class_id: int
uuid: int | None = field(default=None)
class_id: int = field(validator=validators.instance_of(int))
uuid: str | None = field(
default=None, validator=validators.optional(validators.instance_of(str))
)
velocity: Vector3 | None = field(default=None, converter=converters.optional(Vector3))
future: Future | None = field(default=None)
future: Future | None = field(
default=None, validator=validators.optional(validators.instance_of(Future))
)

@overload
def append(self, box: Box3D) -> None:
Expand Down Expand Up @@ -105,6 +110,7 @@ def _append_with_elements(
uuid: str | None = None,
future: Future | None = None,
) -> None:
rotation = to_quaternion(rotation)
rotation_xyzw = np.roll(rotation.q, shift=-1)

width, length, height = size
Expand Down Expand Up @@ -203,8 +209,10 @@ class Record:
"""Inner class to represent a record of 2D box instance for rendering."""

roi: Roi = field(converter=Roi)
class_id: int
uuid: str | None = field(default=None)
class_id: int = field(validator=validators.instance_of(int))
uuid: str | None = field(
default=None, validator=validators.optional(validators.instance_of(str))
)

@overload
def append(self, box: Box2D) -> None:
Expand Down
80 changes: 41 additions & 39 deletions t4_devkit/viewer/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
import rerun.blueprint as rrb
from typing_extensions import Self

from t4_devkit.common.converter import to_quaternion

Copilot AI Sep 1, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import of to_quaternion is added but the corresponding removal of unused imports (Quaternion, Roi, Vector3) is missing from line 15. These concrete types should be removed since they're replaced with generic *Like types.

Copilot uses AI. Check for mistakes.
from t4_devkit.common.timestamp import us2sec
from t4_devkit.lanelet import LaneletParser
from t4_devkit.schema import SensorModality
from t4_devkit.typing import Quaternion, Roi, Vector3

from .color import distance_color
from .geography import calculate_geodetic_point
Expand All @@ -27,7 +27,7 @@
if TYPE_CHECKING:
from t4_devkit.dataclass import Box2D, Box3D, Future, PointCloudLike
from t4_devkit.schema import CalibratedSensor, EgoPose, Sensor
from t4_devkit.typing import CamIntrinsicLike, NDArrayU8
from t4_devkit.typing import CamIntrinsicLike, NDArrayU8, RoiLike, RotationLike, Vector3Like

__all__ = ["RerunViewer", "format_entity"]

Expand Down Expand Up @@ -214,23 +214,23 @@ def render_box3ds(self, seconds: float, boxes: Sequence[Box3D]) -> None:
def render_box3ds(
self,
seconds: float,
centers: Sequence[Vector3],
rotations: Sequence[Quaternion],
sizes: Sequence[Vector3],
centers: Sequence[Vector3Like],
rotations: Sequence[RotationLike],
sizes: Sequence[Vector3Like],
class_ids: Sequence[int],
velocities: Sequence[Vector3] | None = None,
velocities: Sequence[Vector3Like] | None = None,
uuids: Sequence[str] | None = None,
futures: Sequence[Future] | None = None,
) -> None:
"""Render 3D boxes with its elements.

Args:
seconds (float): Timestamp in [sec].
centers (Sequence[Vector3]): Sequence of 3D positions in the order of (x, y, z).
rotations (Sequence[Quaternion]): Sequence of quaternions.
sizes (Sequence[Vector3]): Sequence of box sizes in the order of (width, length, height).
centers (Sequence[Vector3Like]): Sequence of 3D positions in the order of (x, y, z).
rotations (Sequence[RotationLike]): Sequence of rotations.
sizes (Sequence[Vector3Like]): Sequence of box sizes in the order of (width, length, height).
class_ids (Sequence[int]): Sequence of class IDs.
velocities (Sequence[Vector3] | None, optional): Sequence of velocities.
velocities (Sequence[Vector3Like] | None, optional): Sequence of velocities.
uuids (Sequence[str] | None, optional): Sequence of unique identifiers.
futures (Sequence[Future] | None, optional): Sequence future trajectories.
"""
Expand Down Expand Up @@ -276,11 +276,11 @@ def _render_box3ds_with_boxes(self, seconds: float, boxes: Sequence[Box3D]) -> N
def _render_box3ds_with_elements(
self,
seconds: float,
centers: Sequence[Vector3],
rotations: Sequence[Quaternion],
sizes: Sequence[Vector3],
centers: Sequence[Vector3Like],
rotations: Sequence[RotationLike],
sizes: Sequence[Vector3Like],
class_ids: Sequence[int],
velocities: Sequence[Vector3] | None = None,
velocities: Sequence[Vector3Like] | None = None,
uuids: Sequence[str] | None | None = None,
futures: Sequence[Future] | None = None,
) -> None:
Expand Down Expand Up @@ -346,7 +346,7 @@ def render_box2ds(
self,
seconds: float,
camera: str,
rois: Sequence[Roi],
rois: Sequence[RoiLike],
class_ids: Sequence[int],
uuids: Sequence[str] | None = None,
) -> None:
Expand All @@ -355,7 +355,7 @@ def render_box2ds(
Args:
seconds (float): Timestamp in [sec].
camera (str): Camera name.
rois (Sequence[Roi]): Sequence of ROIs in the order of (xmin, ymin, xmax, ymax).
rois (Sequence[RoiLike]): 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.
"""
Expand Down Expand Up @@ -391,7 +391,7 @@ def _render_box2ds_with_elements(
self,
seconds: float,
camera: str,
rois: Sequence[Roi],
rois: Sequence[RoiLike],
class_ids: Sequence[int],
uuids: Sequence[str] | None = None,
) -> None:
Expand Down Expand Up @@ -496,18 +496,18 @@ def render_ego(self, ego_pose: EgoPose) -> None:
def render_ego(
self,
seconds: float,
translation: Vector3,
rotation: Quaternion,
geocoordinate: Vector3 | None = None,
translation: Vector3Like,
rotation: RotationLike,
geocoordinate: Vector3Like | None = None,
) -> None:
"""Render an ego pose.

Args:
seconds (float): Timestamp in [sec].
translation (Vector3): 3D position in the map coordinate system
translation (Vector3Like): 3D position in the map coordinate system
, in the order of (x, y, z) in [m].
rotation (Quaternion): Rotation in the map coordinate system.
geocoordinate (Vector3 | None, optional): Coordinates in the WGS 84
rotation (RotationLike): Rotation in the map coordinate system.
geocoordinate (Vector3Like | None, optional): Coordinates in the WGS 84
reference ellipsoid (latitude, longitude, altitude) in degrees and meters.
"""
pass
Expand All @@ -530,18 +530,17 @@ def _render_ego_with_schema(self, ego_pose: EgoPose) -> None:
def _render_ego_without_schema(
self,
seconds: float,
translation: Vector3,
rotation: Quaternion,
geocoordinate: Vector3 | None = None,
translation: Vector3Like,
rotation: RotationLike,
geocoordinate: Vector3Like | None = None,
) -> None:
rr.set_time_seconds(self.timeline, seconds)

rotation_xyzw = np.roll(rotation.q, shift=-1)
rr.log(
self.ego_entity,
rr.Transform3D(
translation=translation,
rotation=rr.Quaternion(xyzw=rotation_xyzw),
rotation=_to_rerun_quaternion(rotation),
relation=rr.TransformRelation.ParentFromChild,
),
)
Expand Down Expand Up @@ -578,17 +577,17 @@ def render_calibration(
self,
channel: str,
modality: str | SensorModality,
translation: Vector3,
rotation: Quaternion,
translation: Vector3Like,
rotation: RotationLike,
camera_intrinsic: CamIntrinsicLike | None = None,
) -> None:
"""Render a sensor calibration.

Args:
channel (str): Name of the sensor channel.
modality (str | SensorModality): Sensor modality.
translation (Vector3): Sensor translation in ego centric coords.
rotation (Quaternion): Sensor rotation in ego centric coords.
translation (Vector3Like): Sensor translation in ego centric coords.
rotation (RotationLike): Sensor rotation in ego centric coords.
camera_intrinsic (CamIntrinsicLike | None, optional): Camera intrinsic matrix.
Defaults to None.
"""
Expand Down Expand Up @@ -618,25 +617,23 @@ def _render_calibration_without_schema(
self,
channel: str,
modality: str | SensorModality,
translation: Vector3,
rotation: Quaternion,
translation: Vector3Like,
rotation: RotationLike,
camera_intrinsic: CamIntrinsicLike | None = None,
) -> None:
"""Render a sensor calibration.

Args:
channel (str): Name of the sensor channel.
modality (str | SensorModality): Sensor modality.
translation (Vector3): Sensor translation in ego centric coords.
rotation (Quaternion): Sensor rotation in ego centric coords.
translation (Vector3Like): Sensor translation in ego centric coords.
rotation (RotationLike): Sensor rotation in ego centric coords.
camera_intrinsic (CamIntrinsicLike | None, optional): Camera intrinsic matrix.
Defaults to None.
"""
rotation_xyzw = np.roll(rotation.q, shift=-1)

rr.log(
format_entity(self.ego_entity, channel),
rr.Transform3D(translation=translation, rotation=rr.Quaternion(xyzw=rotation_xyzw)),
rr.Transform3D(translation=translation, rotation=_to_rerun_quaternion(rotation)),
static=True,
)

Expand All @@ -661,3 +658,8 @@ def render_map(self, filepath: str) -> None:
render_ways(parser, root_entity)

render_geographic_borders(parser, f"{self.geocoordinate_entity}/vector_map")


def _to_rerun_quaternion(rotation: RotationLike) -> rr.Quaternion:
rotation_xyzw = np.roll(to_quaternion(rotation).q, shift=-1)
return rr.Quaternion(xyzw=rotation_xyzw)
Loading