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
85 changes: 84 additions & 1 deletion t4_devkit/helper/rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import numpy as np
import rerun as rr
import yaml
from PIL import Image

from t4_devkit.common.geometry import view_points
from t4_devkit.common.timestamp import sec2us, us2sec
from t4_devkit.dataclass import LidarPointCloud, RadarPointCloud
from t4_devkit.schema import SensorModality
Expand All @@ -28,6 +30,7 @@
Sensor,
SurfaceAnn,
)
from t4_devkit.typing import NDArrayF64, NDArrayU8

__all__ = ["RenderingHelper"]

Expand Down Expand Up @@ -489,7 +492,7 @@ async def render_points_on_camera(
if max_timestamp_us < sample.timestamp:
break

points_on_image, depths, image = self._t4.project_pointcloud(
points_on_image, depths, image = self._project_pointcloud(
point_sample_data_token=current_point_sample_data_token,
camera_sample_data_token=camera_sample_data_token,
min_dist=min_dist,
Expand Down Expand Up @@ -519,6 +522,86 @@ async def render_points_on_camera(
]
)

def _project_pointcloud(
self,
point_sample_data_token: str,
camera_sample_data_token: str,
min_dist: float = 1.0,
*,
ignore_distortion: bool = True,
) -> tuple[NDArrayF64, NDArrayF64, NDArrayU8]:
"""Project pointcloud on image plane.

Args:
point_sample_data_token (str): Sample data token of lidar or radar sensor.
camera_sample_data_token (str): Sample data token of camera.
min_dist (float, optional): Distance from the camera below which points are discarded.
ignore_distortion (bool, optional): Whether to ignore distortion parameters.

Returns:
Projected points [2, n], their normalized depths [n] and an image.
"""
point_sample_data: SampleData = self._t4.get("sample_data", point_sample_data_token)
pc_filepath = osp.join(self._t4.data_root, point_sample_data.filename)
if point_sample_data.modality == SensorModality.LIDAR:
pointcloud = LidarPointCloud.from_file(pc_filepath)
elif point_sample_data.modality == SensorModality.RADAR:
pointcloud = RadarPointCloud.from_file(pc_filepath)
else:
raise ValueError(f"Expected sensor lidar/radar, but got {point_sample_data.modality}")

camera_sample_data: SampleData = self._t4.get("sample_data", camera_sample_data_token)
if camera_sample_data.modality != SensorModality.CAMERA:
f"Expected camera, but got {camera_sample_data.modality}"

img = Image.open(osp.join(self._t4.data_root, camera_sample_data.filename))

# 1. transform the pointcloud to the ego vehicle frame for the timestamp to the sweep.
point_cs_record: CalibratedSensor = self._t4.get(
"calibrated_sensor", point_sample_data.calibrated_sensor_token
)
pointcloud.rotate(point_cs_record.rotation.rotation_matrix)
pointcloud.translate(point_cs_record.translation)

# 2. transform from ego to the global frame.
point_ego_pose: EgoPose = self._t4.get("ego_pose", point_sample_data.ego_pose_token)
pointcloud.rotate(point_ego_pose.rotation.rotation_matrix)
pointcloud.translate(point_ego_pose.translation)

# 3. transform from global into the ego vehicle frame for the timestamp of the image
camera_ego_pose: EgoPose = self._t4.get("ego_pose", camera_sample_data.ego_pose_token)
pointcloud.translate(-camera_ego_pose.translation)
pointcloud.rotate(camera_ego_pose.rotation.rotation_matrix.T)

# 4. transform from ego into the camera
camera_cs_record: CalibratedSensor = self._t4.get(
"calibrated_sensor", camera_sample_data.calibrated_sensor_token
)
pointcloud.translate(-camera_cs_record.translation)
pointcloud.rotate(camera_cs_record.rotation.rotation_matrix.T)

depths = pointcloud.points[2, :]

distortion = None if ignore_distortion else camera_cs_record.camera_distortion

points_on_img = view_points(
points=pointcloud.points[:3, :],
intrinsic=camera_cs_record.camera_intrinsic,
distortion=distortion,
normalize=True,
)[:2]

mask = np.ones(depths.shape[0], dtype=bool)
mask = np.logical_and(mask, depths > min_dist)
mask = np.logical_and(mask, 1 < points_on_img[0])
mask = np.logical_and(mask, points_on_img[0] < img.size[0] - 1)
mask = np.logical_and(mask, 1 < points_on_img[1])
mask = np.logical_and(mask, points_on_img[1] < img.size[1] - 1)
points_on_img = points_on_img[:, mask]
depths = depths[mask]

return points_on_img, depths, np.array(img, dtype=np.uint8)

async def _render_annotation3ds(
self,
viewer: RerunViewer,
Expand Down
87 changes: 2 additions & 85 deletions t4_devkit/tier4.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
from typing import TYPE_CHECKING, Sequence

import numpy as np
from PIL import Image
from pyquaternion import Quaternion

from t4_devkit.common.geometry import is_box_in_image, view_points
from t4_devkit.common.geometry import is_box_in_image
from t4_devkit.dataclass import (
Box2D,
Box3D,
LidarPointCloud,
RadarPointCloud,
SemanticLabel,
Shape,
ShapeType,
Expand All @@ -24,7 +21,7 @@
from t4_devkit.schema import SchemaName, SensorModality, VisibilityLevel, build_schema

if TYPE_CHECKING:
from t4_devkit.typing import CamIntrinsicLike, NDArrayF64, NDArrayU8, Vector3Like
from t4_devkit.typing import CamIntrinsicLike, Vector3Like

from .dataclass import BoxLike
from .schema import (
Expand Down Expand Up @@ -646,86 +643,6 @@ def box_velocity(self, sample_annotation_token: str, max_time_diff: float = 1.5)
else:
return pos_diff / time_diff

def project_pointcloud(
self,
point_sample_data_token: str,
camera_sample_data_token: str,
min_dist: float = 1.0,
*,
ignore_distortion: bool = False,
) -> tuple[NDArrayF64, NDArrayF64, NDArrayU8]:
"""Project pointcloud on image plane.

Args:
point_sample_data_token (str): Sample data token of lidar or radar sensor.
camera_sample_data_token (str): Sample data token of camera.
min_dist (float, optional): Distance from the camera below which points are discarded.
ignore_distortion (bool, optional): Whether to ignore distortion parameters.

Returns:
Projected points [2, n], their normalized depths [n] and an image.
"""
point_sample_data: SampleData = self.get("sample_data", point_sample_data_token)
pc_filepath = osp.join(self.data_root, point_sample_data.filename)
if point_sample_data.modality == SensorModality.LIDAR:
pointcloud = LidarPointCloud.from_file(pc_filepath)
elif point_sample_data.modality == SensorModality.RADAR:
pointcloud = RadarPointCloud.from_file(pc_filepath)
else:
raise ValueError(f"Expected sensor lidar/radar, but got {point_sample_data.modality}")

camera_sample_data: SampleData = self.get("sample_data", camera_sample_data_token)
if camera_sample_data.modality != SensorModality.CAMERA:
f"Expected camera, but got {camera_sample_data.modality}"

img = Image.open(osp.join(self.data_root, camera_sample_data.filename))

# 1. transform the pointcloud to the ego vehicle frame for the timestamp to the sweep.
point_cs_record: CalibratedSensor = self.get(
"calibrated_sensor", point_sample_data.calibrated_sensor_token
)
pointcloud.rotate(point_cs_record.rotation.rotation_matrix)
pointcloud.translate(point_cs_record.translation)

# 2. transform from ego to the global frame.
point_ego_pose: EgoPose = self.get("ego_pose", point_sample_data.ego_pose_token)
pointcloud.rotate(point_ego_pose.rotation.rotation_matrix)
pointcloud.translate(point_ego_pose.translation)

# 3. transform from global into the ego vehicle frame for the timestamp of the image
camera_ego_pose: EgoPose = self.get("ego_pose", camera_sample_data.ego_pose_token)
pointcloud.translate(-camera_ego_pose.translation)
pointcloud.rotate(camera_ego_pose.rotation.rotation_matrix.T)

# 4. transform from ego into the camera
camera_cs_record: CalibratedSensor = self.get(
"calibrated_sensor", camera_sample_data.calibrated_sensor_token
)
pointcloud.translate(-camera_cs_record.translation)
pointcloud.rotate(camera_cs_record.rotation.rotation_matrix.T)

depths = pointcloud.points[2, :]

distortion = None if ignore_distortion else camera_cs_record.camera_distortion

points_on_img = view_points(
points=pointcloud.points[:3, :],
intrinsic=camera_cs_record.camera_intrinsic,
distortion=distortion,
normalize=True,
)[:2]

mask = np.ones(depths.shape[0], dtype=bool)
mask = np.logical_and(mask, depths > min_dist)
mask = np.logical_and(mask, 1 < points_on_img[0])
mask = np.logical_and(mask, points_on_img[0] < img.size[0] - 1)
mask = np.logical_and(mask, 1 < points_on_img[1])
mask = np.logical_and(mask, points_on_img[1] < img.size[1] - 1)
points_on_img = points_on_img[:, mask]
depths = depths[mask]

return points_on_img, depths, np.array(img, dtype=np.uint8)

def render_scene(
self,
scene_token: str,
Expand Down
Loading