Skip to content

Commit 4c3f232

Browse files
authored
refactor: move project_pointcloud(...) to RenderingHelper (#145)
Signed-off-by: ktro2828 <kotaro.uetake@tier4.jp>
1 parent b885c1d commit 4c3f232

2 files changed

Lines changed: 86 additions & 86 deletions

File tree

t4_devkit/helper/rendering.py

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import numpy as np
99
import rerun as rr
1010
import yaml
11+
from PIL import Image
1112

13+
from t4_devkit.common.geometry import view_points
1214
from t4_devkit.common.timestamp import sec2us, us2sec
1315
from t4_devkit.dataclass import LidarPointCloud, RadarPointCloud
1416
from t4_devkit.schema import SensorModality
@@ -28,6 +30,7 @@
2830
Sensor,
2931
SurfaceAnn,
3032
)
33+
from t4_devkit.typing import NDArrayF64, NDArrayU8
3134

3235
__all__ = ["RenderingHelper"]
3336

@@ -489,7 +492,7 @@ async def render_points_on_camera(
489492
if max_timestamp_us < sample.timestamp:
490493
break
491494

492-
points_on_image, depths, image = self._t4.project_pointcloud(
495+
points_on_image, depths, image = self._project_pointcloud(
493496
point_sample_data_token=current_point_sample_data_token,
494497
camera_sample_data_token=camera_sample_data_token,
495498
min_dist=min_dist,
@@ -519,6 +522,86 @@ async def render_points_on_camera(
519522
]
520523
)
521524

525+
def _project_pointcloud(
526+
self,
527+
point_sample_data_token: str,
528+
camera_sample_data_token: str,
529+
min_dist: float = 1.0,
530+
*,
531+
ignore_distortion: bool = True,
532+
) -> tuple[NDArrayF64, NDArrayF64, NDArrayU8]:
533+
"""Project pointcloud on image plane.
534+
535+
Args:
536+
point_sample_data_token (str): Sample data token of lidar or radar sensor.
537+
camera_sample_data_token (str): Sample data token of camera.
538+
min_dist (float, optional): Distance from the camera below which points are discarded.
539+
ignore_distortion (bool, optional): Whether to ignore distortion parameters.
540+
541+
Returns:
542+
Projected points [2, n], their normalized depths [n] and an image.
543+
"""
544+
point_sample_data: SampleData = self._t4.get("sample_data", point_sample_data_token)
545+
pc_filepath = osp.join(self._t4.data_root, point_sample_data.filename)
546+
if point_sample_data.modality == SensorModality.LIDAR:
547+
pointcloud = LidarPointCloud.from_file(pc_filepath)
548+
elif point_sample_data.modality == SensorModality.RADAR:
549+
pointcloud = RadarPointCloud.from_file(pc_filepath)
550+
else:
551+
raise ValueError(f"Expected sensor lidar/radar, but got {point_sample_data.modality}")
552+
553+
camera_sample_data: SampleData = self._t4.get("sample_data", camera_sample_data_token)
554+
if camera_sample_data.modality != SensorModality.CAMERA:
555+
f"Expected camera, but got {camera_sample_data.modality}"
556+
557+
img = Image.open(osp.join(self._t4.data_root, camera_sample_data.filename))
558+
559+
# 1. transform the pointcloud to the ego vehicle frame for the timestamp to the sweep.
560+
point_cs_record: CalibratedSensor = self._t4.get(
561+
"calibrated_sensor", point_sample_data.calibrated_sensor_token
562+
)
563+
pointcloud.rotate(point_cs_record.rotation.rotation_matrix)
564+
pointcloud.translate(point_cs_record.translation)
565+
566+
# 2. transform from ego to the global frame.
567+
point_ego_pose: EgoPose = self._t4.get("ego_pose", point_sample_data.ego_pose_token)
568+
pointcloud.rotate(point_ego_pose.rotation.rotation_matrix)
569+
pointcloud.translate(point_ego_pose.translation)
570+
571+
# 3. transform from global into the ego vehicle frame for the timestamp of the image
572+
camera_ego_pose: EgoPose = self._t4.get("ego_pose", camera_sample_data.ego_pose_token)
573+
pointcloud.translate(-camera_ego_pose.translation)
574+
pointcloud.rotate(camera_ego_pose.rotation.rotation_matrix.T)
575+
576+
# 4. transform from ego into the camera
577+
camera_cs_record: CalibratedSensor = self._t4.get(
578+
"calibrated_sensor", camera_sample_data.calibrated_sensor_token
579+
)
580+
pointcloud.translate(-camera_cs_record.translation)
581+
pointcloud.rotate(camera_cs_record.rotation.rotation_matrix.T)
582+
583+
depths = pointcloud.points[2, :]
584+
585+
distortion = None if ignore_distortion else camera_cs_record.camera_distortion
586+
587+
points_on_img = view_points(
588+
points=pointcloud.points[:3, :],
589+
intrinsic=camera_cs_record.camera_intrinsic,
590+
distortion=distortion,
591+
normalize=True,
592+
)[:2]
593+
594+
mask = np.ones(depths.shape[0], dtype=bool)
595+
mask = np.logical_and(mask, depths > min_dist)
596+
mask = np.logical_and(mask, 1 < points_on_img[0])
597+
mask = np.logical_and(mask, points_on_img[0] < img.size[0] - 1)
598+
mask = np.logical_and(mask, 1 < points_on_img[1])
599+
mask = np.logical_and(mask, points_on_img[1] < img.size[1] - 1)
600+
points_on_img = points_on_img[:, mask]
601+
depths = depths[mask]
602+
603+
return points_on_img, depths, np.array(img, dtype=np.uint8)
604+
522605
async def _render_annotation3ds(
523606
self,
524607
viewer: RerunViewer,

t4_devkit/tier4.py

Lines changed: 2 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,12 @@
77
from typing import TYPE_CHECKING, Sequence
88

99
import numpy as np
10-
from PIL import Image
1110
from pyquaternion import Quaternion
1211

13-
from t4_devkit.common.geometry import is_box_in_image, view_points
12+
from t4_devkit.common.geometry import is_box_in_image
1413
from t4_devkit.dataclass import (
1514
Box2D,
1615
Box3D,
17-
LidarPointCloud,
18-
RadarPointCloud,
1916
SemanticLabel,
2017
Shape,
2118
ShapeType,
@@ -24,7 +21,7 @@
2421
from t4_devkit.schema import SchemaName, SensorModality, VisibilityLevel, build_schema
2522

2623
if TYPE_CHECKING:
27-
from t4_devkit.typing import CamIntrinsicLike, NDArrayF64, NDArrayU8, Vector3Like
24+
from t4_devkit.typing import CamIntrinsicLike, Vector3Like
2825

2926
from .dataclass import BoxLike
3027
from .schema import (
@@ -646,86 +643,6 @@ def box_velocity(self, sample_annotation_token: str, max_time_diff: float = 1.5)
646643
else:
647644
return pos_diff / time_diff
648645

649-
def project_pointcloud(
650-
self,
651-
point_sample_data_token: str,
652-
camera_sample_data_token: str,
653-
min_dist: float = 1.0,
654-
*,
655-
ignore_distortion: bool = False,
656-
) -> tuple[NDArrayF64, NDArrayF64, NDArrayU8]:
657-
"""Project pointcloud on image plane.
658-
659-
Args:
660-
point_sample_data_token (str): Sample data token of lidar or radar sensor.
661-
camera_sample_data_token (str): Sample data token of camera.
662-
min_dist (float, optional): Distance from the camera below which points are discarded.
663-
ignore_distortion (bool, optional): Whether to ignore distortion parameters.
664-
665-
Returns:
666-
Projected points [2, n], their normalized depths [n] and an image.
667-
"""
668-
point_sample_data: SampleData = self.get("sample_data", point_sample_data_token)
669-
pc_filepath = osp.join(self.data_root, point_sample_data.filename)
670-
if point_sample_data.modality == SensorModality.LIDAR:
671-
pointcloud = LidarPointCloud.from_file(pc_filepath)
672-
elif point_sample_data.modality == SensorModality.RADAR:
673-
pointcloud = RadarPointCloud.from_file(pc_filepath)
674-
else:
675-
raise ValueError(f"Expected sensor lidar/radar, but got {point_sample_data.modality}")
676-
677-
camera_sample_data: SampleData = self.get("sample_data", camera_sample_data_token)
678-
if camera_sample_data.modality != SensorModality.CAMERA:
679-
f"Expected camera, but got {camera_sample_data.modality}"
680-
681-
img = Image.open(osp.join(self.data_root, camera_sample_data.filename))
682-
683-
# 1. transform the pointcloud to the ego vehicle frame for the timestamp to the sweep.
684-
point_cs_record: CalibratedSensor = self.get(
685-
"calibrated_sensor", point_sample_data.calibrated_sensor_token
686-
)
687-
pointcloud.rotate(point_cs_record.rotation.rotation_matrix)
688-
pointcloud.translate(point_cs_record.translation)
689-
690-
# 2. transform from ego to the global frame.
691-
point_ego_pose: EgoPose = self.get("ego_pose", point_sample_data.ego_pose_token)
692-
pointcloud.rotate(point_ego_pose.rotation.rotation_matrix)
693-
pointcloud.translate(point_ego_pose.translation)
694-
695-
# 3. transform from global into the ego vehicle frame for the timestamp of the image
696-
camera_ego_pose: EgoPose = self.get("ego_pose", camera_sample_data.ego_pose_token)
697-
pointcloud.translate(-camera_ego_pose.translation)
698-
pointcloud.rotate(camera_ego_pose.rotation.rotation_matrix.T)
699-
700-
# 4. transform from ego into the camera
701-
camera_cs_record: CalibratedSensor = self.get(
702-
"calibrated_sensor", camera_sample_data.calibrated_sensor_token
703-
)
704-
pointcloud.translate(-camera_cs_record.translation)
705-
pointcloud.rotate(camera_cs_record.rotation.rotation_matrix.T)
706-
707-
depths = pointcloud.points[2, :]
708-
709-
distortion = None if ignore_distortion else camera_cs_record.camera_distortion
710-
711-
points_on_img = view_points(
712-
points=pointcloud.points[:3, :],
713-
intrinsic=camera_cs_record.camera_intrinsic,
714-
distortion=distortion,
715-
normalize=True,
716-
)[:2]
717-
718-
mask = np.ones(depths.shape[0], dtype=bool)
719-
mask = np.logical_and(mask, depths > min_dist)
720-
mask = np.logical_and(mask, 1 < points_on_img[0])
721-
mask = np.logical_and(mask, points_on_img[0] < img.size[0] - 1)
722-
mask = np.logical_and(mask, 1 < points_on_img[1])
723-
mask = np.logical_and(mask, points_on_img[1] < img.size[1] - 1)
724-
points_on_img = points_on_img[:, mask]
725-
depths = depths[mask]
726-
727-
return points_on_img, depths, np.array(img, dtype=np.uint8)
728-
729646
def render_scene(
730647
self,
731648
scene_token: str,

0 commit comments

Comments
 (0)