From d8bbcd30e3ea4e5482647e4c6899be5230690507 Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Tue, 2 Sep 2025 17:03:42 +0900 Subject: [PATCH 1/6] feat: add support of rendering point cloud coloring with intensity Signed-off-by: ktro2828 --- t4_devkit/viewer/color.py | 42 +++++++++++++++++++++++++++++++------- t4_devkit/viewer/viewer.py | 4 ++-- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/t4_devkit/viewer/color.py b/t4_devkit/viewer/color.py index 6ebbf1b..2455ba5 100644 --- a/t4_devkit/viewer/color.py +++ b/t4_devkit/viewer/color.py @@ -1,31 +1,59 @@ from __future__ import annotations +from enum import Enum, unique from typing import TYPE_CHECKING import matplotlib +import numpy as np if TYPE_CHECKING: - from t4_devkit.typing import ArrayLike, NDArrayF64, ScalarLike + from t4_devkit.dataclass import PointCloudLike + from t4_devkit.typing import ArrayLike, NDArrayF64 + + +@unique +class PointCloudColorMode(str, Enum): + """Color mode of point cloud.""" + + DISTANCE = "distance" + INTENSITY = "intensity" + + +def pointcloud_color( + pointcloud: PointCloudLike, + color_mode: PointCloudColorMode = PointCloudColorMode.INTENSITY, +) -> NDArrayF64: + """Return color map depending on the specified color mode. + + Args: + pointcloud (PointCloudLike): Any inheritance of `PointCloud` class. + color_mode (PointCloudColorMode): Color mode of point cloud. + """ + match color_mode: + case PointCloudColorMode.DISTANCE: + values = np.linalg.norm(pointcloud.points[:3].T, axis=1) + case _: + values = pointcloud.points[3] + + return distance_color(values) def distance_color( - distances: ScalarLike | ArrayLike, + distances: ArrayLike, cmap: str | None = None, - v_min: float = 3.0, - v_max: float = 75.0, ) -> tuple[float, float, float] | NDArrayF64: """Return color map depending on distance values. Args: - distances (ScalarLike | ArrayLike): Array of distances in the shape of (N,). + distances (ArrayLike): Array of distances in the shape of (N,). cmap (str | None, optional): Color map name in matplotlib. If None, `turbo_r` will be used. - v_min (float, optional): Min value to normalize. - v_max (float, optional): Max value to normalize. Returns: Color map in the shape of (N,). If input type is any number, returns a color as `tuple[float, float, float]`. Otherwise, returns colors as `NDArrayF64`. """ color_map = matplotlib.colormaps["turbo_r"] if cmap is None else matplotlib.colormaps[cmap] + v_min = np.min(distances) + v_max = np.max(distances) norm = matplotlib.colors.Normalize(v_min, v_max) return color_map(norm(distances)) diff --git a/t4_devkit/viewer/viewer.py b/t4_devkit/viewer/viewer.py index e2d7e63..008accc 100644 --- a/t4_devkit/viewer/viewer.py +++ b/t4_devkit/viewer/viewer.py @@ -12,7 +12,7 @@ from t4_devkit.lanelet import LaneletParser from t4_devkit.schema import SensorModality -from .color import distance_color +from .color import pointcloud_color from .config import ViewerConfig, format_entity from .geography import calculate_geodetic_point from .lanelet import ( @@ -419,7 +419,7 @@ def render_pointcloud(self, seconds: float, channel: str, pointcloud: PointCloud # TODO(ktro2828): add support of rendering pointcloud on images rr.set_time_seconds(self.config.timeline, seconds) - colors = distance_color(np.linalg.norm(pointcloud.points[:3].T, axis=1)) + colors = pointcloud_color(pointcloud) rr.log( format_entity(self.config.ego_entity, channel), rr.Points3D(pointcloud.points[:3].T, colors=colors), From bb36e1b43bae8ac59eb7007228eee294c727f556 Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Thu, 11 Sep 2025 00:54:16 +0900 Subject: [PATCH 2/6] feat: add support of colorizing points on image by intensity Signed-off-by: ktro2828 --- t4_devkit/helper/rendering.py | 35 +++++++++++++++++++++++++---------- t4_devkit/viewer/color.py | 24 ++++++++++++------------ 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/t4_devkit/helper/rendering.py b/t4_devkit/helper/rendering.py index 6f15bcc..5228d35 100644 --- a/t4_devkit/helper/rendering.py +++ b/t4_devkit/helper/rendering.py @@ -15,7 +15,14 @@ from t4_devkit.common.timestamp import sec2us, us2sec from t4_devkit.dataclass import LidarPointCloud, RadarPointCloud from t4_devkit.schema import SensorModality -from t4_devkit.viewer import RerunViewer, ViewerBuilder, ViewerConfig, distance_color, format_entity +from t4_devkit.viewer import ( + PointCloudColorMode, + RerunViewer, + ViewerBuilder, + ViewerConfig, + format_entity, + normalize_color, +) if TYPE_CHECKING: from t4_devkit import Tier4 @@ -441,7 +448,7 @@ def _render_points_on_single_camera(camera: str) -> None: if max_timestamp_us < sample.timestamp: break - points_on_image, depths, image = self._project_pointcloud( + points_on_image, colors, 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, @@ -453,7 +460,7 @@ def _render_points_on_single_camera(camera: str) -> None: rr.log(format_entity(ViewerConfig.ego_entity, camera), rr.Image(image)) rr.log( format_entity(ViewerConfig.ego_entity, camera, "pointcloud"), - rr.Points2D(positions=points_on_image.T, colors=distance_color(depths)), + rr.Points2D(positions=points_on_image.T, colors=colors), ) current_point_sample_data_token = sample_data.next @@ -471,6 +478,7 @@ def _project_pointcloud( min_dist: float = 1.0, *, ignore_distortion: bool = True, + color_mode: PointCloudColorMode = PointCloudColorMode.INTENSITY, ) -> tuple[NDArrayF64, NDArrayF64, NDArrayU8]: """Project pointcloud on image plane. @@ -479,9 +487,10 @@ def _project_pointcloud( 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. + color_mode (PointCloudColorMode, optional): Color mode for pointcloud. Returns: - Projected points [2, n], their normalized depths [n] and an image. + Projected points [2, n], their normalized features [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) @@ -522,8 +531,6 @@ def _project_pointcloud( 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( @@ -533,16 +540,24 @@ def _project_pointcloud( normalize=True, )[:2] - mask = np.ones(depths.shape[0], dtype=bool) - mask = np.logical_and(mask, depths > min_dist) + match color_mode: + case PointCloudColorMode.DISTANCE: + colors = normalize_color(pointcloud.points[2, :]) + case _: + colors = normalize_color(pointcloud.points[3, :]) + depths = pointcloud.points[2, :] + + mask = np.ones(colors.shape[0], dtype=bool) + mask = np.logical_and(mask, depths > min_dist) # mask by depths + # mask by size of points on image 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] + colors = colors[mask] - return points_on_img, depths, np.array(img, dtype=np.uint8) + return points_on_img, colors, np.array(img, dtype=np.uint8) def _render_annotation3ds( self, diff --git a/t4_devkit/viewer/color.py b/t4_devkit/viewer/color.py index 2455ba5..36e5728 100644 --- a/t4_devkit/viewer/color.py +++ b/t4_devkit/viewer/color.py @@ -11,6 +11,9 @@ from t4_devkit.typing import ArrayLike, NDArrayF64 +__all__ = ["PointCloudColorMode", "pointcloud_color", "normalize_color"] + + @unique class PointCloudColorMode(str, Enum): """Color mode of point cloud.""" @@ -35,25 +38,22 @@ def pointcloud_color( case _: values = pointcloud.points[3] - return distance_color(values) + return normalize_color(values) -def distance_color( - distances: ArrayLike, - cmap: str | None = None, -) -> tuple[float, float, float] | NDArrayF64: - """Return color map depending on distance values. +def normalize_color(values: ArrayLike, cmap: str | None = None, alpha: float = 1.0) -> NDArrayF64: + """Return color map normalizing values. Args: - distances (ArrayLike): Array of distances in the shape of (N,). + values (ArrayLike): Array of values in the shape of (N,). cmap (str | None, optional): Color map name in matplotlib. If None, `turbo_r` will be used. + alpha (float, optional): Alpha value of color map. Returns: - Color map in the shape of (N,). If input type is any number, returns a color as - `tuple[float, float, float]`. Otherwise, returns colors as `NDArrayF64`. + Color map in the shape of (N,). """ color_map = matplotlib.colormaps["turbo_r"] if cmap is None else matplotlib.colormaps[cmap] - v_min = np.min(distances) - v_max = np.max(distances) + v_min = np.min(values) + v_max = np.max(values) norm = matplotlib.colors.Normalize(v_min, v_max) - return color_map(norm(distances)) + return color_map(norm(values), alpha=alpha) From b7cee3985ae6ba650b697592d588344e4e3fc5b5 Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Thu, 2 Oct 2025 18:02:39 +0900 Subject: [PATCH 3/6] feat: set default value of PointCloudColorMode to DISTANCE Signed-off-by: ktro2828 --- t4_devkit/helper/rendering.py | 14 +++++++------- t4_devkit/viewer/color.py | 4 ++-- t4_devkit/viewer/viewer.py | 13 ++++++++++--- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/t4_devkit/helper/rendering.py b/t4_devkit/helper/rendering.py index 5228d35..939db48 100644 --- a/t4_devkit/helper/rendering.py +++ b/t4_devkit/helper/rendering.py @@ -21,7 +21,7 @@ ViewerBuilder, ViewerConfig, format_entity, - normalize_color, + pointcloud_color, ) if TYPE_CHECKING: @@ -339,6 +339,7 @@ def _render_lidar_and_ego( viewer: RerunViewer, first_lidar_tokens: list[str], max_timestamp_us: float, + color_mode: PointCloudColorMode = PointCloudColorMode.DISTANCE, ) -> list[Future]: def _render_single_lidar(first_lidar_token: str) -> None: self._render_sensor_calibration(viewer=viewer, sample_data_token=first_lidar_token) @@ -360,6 +361,7 @@ def _render_single_lidar(first_lidar_token: str) -> None: seconds=us2sec(sample_data.timestamp), channel=sample_data.channel, pointcloud=pointcloud, + color_mode=color_mode, ) current_lidar_token = sample_data.next @@ -430,6 +432,7 @@ def _render_points_on_cameras( *, min_dist: float = 1.0, ignore_distortion: bool = True, + color_mode: PointCloudColorMode = PointCloudColorMode.DISTANCE, ) -> list[Future]: def _render_points_on_single_camera(camera: str) -> None: current_point_sample_data_token = first_point_sample_data_token @@ -453,6 +456,7 @@ def _render_points_on_single_camera(camera: str) -> None: camera_sample_data_token=camera_sample_data_token, min_dist=min_dist, ignore_distortion=ignore_distortion, + color_mode=color_mode, ) rr.set_time_seconds(ViewerConfig.timeline, us2sec(sample.timestamp)) @@ -478,7 +482,7 @@ def _project_pointcloud( min_dist: float = 1.0, *, ignore_distortion: bool = True, - color_mode: PointCloudColorMode = PointCloudColorMode.INTENSITY, + color_mode: PointCloudColorMode = PointCloudColorMode.DISTANCE, ) -> tuple[NDArrayF64, NDArrayF64, NDArrayU8]: """Project pointcloud on image plane. @@ -540,11 +544,7 @@ def _project_pointcloud( normalize=True, )[:2] - match color_mode: - case PointCloudColorMode.DISTANCE: - colors = normalize_color(pointcloud.points[2, :]) - case _: - colors = normalize_color(pointcloud.points[3, :]) + colors = pointcloud_color(pointcloud, color_mode) depths = pointcloud.points[2, :] mask = np.ones(colors.shape[0], dtype=bool) diff --git a/t4_devkit/viewer/color.py b/t4_devkit/viewer/color.py index 36e5728..685cead 100644 --- a/t4_devkit/viewer/color.py +++ b/t4_devkit/viewer/color.py @@ -24,13 +24,13 @@ class PointCloudColorMode(str, Enum): def pointcloud_color( pointcloud: PointCloudLike, - color_mode: PointCloudColorMode = PointCloudColorMode.INTENSITY, + color_mode: PointCloudColorMode = PointCloudColorMode.DISTANCE, ) -> NDArrayF64: """Return color map depending on the specified color mode. Args: pointcloud (PointCloudLike): Any inheritance of `PointCloud` class. - color_mode (PointCloudColorMode): Color mode of point cloud. + color_mode (PointCloudColorMode, optional): Color mode for pointcloud. """ match color_mode: case PointCloudColorMode.DISTANCE: diff --git a/t4_devkit/viewer/viewer.py b/t4_devkit/viewer/viewer.py index 008accc..a1d9a94 100644 --- a/t4_devkit/viewer/viewer.py +++ b/t4_devkit/viewer/viewer.py @@ -12,7 +12,7 @@ from t4_devkit.lanelet import LaneletParser from t4_devkit.schema import SensorModality -from .color import pointcloud_color +from .color import PointCloudColorMode, pointcloud_color from .config import ViewerConfig, format_entity from .geography import calculate_geodetic_point from .lanelet import ( @@ -408,18 +408,25 @@ def render_segmentation2d( ) @_check_spatial3d - def render_pointcloud(self, seconds: float, channel: str, pointcloud: PointCloudLike) -> None: + def render_pointcloud( + self, + seconds: float, + channel: str, + pointcloud: PointCloudLike, + color_mode: PointCloudColorMode = PointCloudColorMode.DISTANCE, + ) -> None: """Render pointcloud. Args: seconds (float): Timestamp in [sec]. channel (str): Name of the pointcloud sensor channel. pointcloud (PointCloudLike): Inherence object of `PointCloud`. + color_mode (PointCloudColorMode, optional): Color mode for pointcloud. """ # TODO(ktro2828): add support of rendering pointcloud on images rr.set_time_seconds(self.config.timeline, seconds) - colors = pointcloud_color(pointcloud) + colors = pointcloud_color(pointcloud, color_mode=color_mode) rr.log( format_entity(self.config.ego_entity, channel), rr.Points3D(pointcloud.points[:3].T, colors=colors), From a1f4bb16f7089ec747ec60b987e4b32d113f6c93 Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Thu, 2 Oct 2025 18:05:06 +0900 Subject: [PATCH 4/6] refactor: modify normalize_color to private function Signed-off-by: ktro2828 --- t4_devkit/viewer/color.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/t4_devkit/viewer/color.py b/t4_devkit/viewer/color.py index 685cead..3d45091 100644 --- a/t4_devkit/viewer/color.py +++ b/t4_devkit/viewer/color.py @@ -11,7 +11,7 @@ from t4_devkit.typing import ArrayLike, NDArrayF64 -__all__ = ["PointCloudColorMode", "pointcloud_color", "normalize_color"] +__all__ = ["PointCloudColorMode", "pointcloud_color"] @unique @@ -38,10 +38,10 @@ def pointcloud_color( case _: values = pointcloud.points[3] - return normalize_color(values) + return _normalize_color(values) -def normalize_color(values: ArrayLike, cmap: str | None = None, alpha: float = 1.0) -> NDArrayF64: +def _normalize_color(values: ArrayLike, cmap: str | None = None, alpha: float = 1.0) -> NDArrayF64: """Return color map normalizing values. Args: From b135bf8cf5787eea1cd6192a0396e20bd1e25518 Mon Sep 17 00:00:00 2001 From: Kotaro Uetake <60615504+ktro2828@users.noreply.github.com> Date: Thu, 2 Oct 2025 18:10:58 +0900 Subject: [PATCH 5/6] Update t4_devkit/viewer/color.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- t4_devkit/viewer/color.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/t4_devkit/viewer/color.py b/t4_devkit/viewer/color.py index 3d45091..ca30954 100644 --- a/t4_devkit/viewer/color.py +++ b/t4_devkit/viewer/color.py @@ -35,8 +35,10 @@ def pointcloud_color( match color_mode: case PointCloudColorMode.DISTANCE: values = np.linalg.norm(pointcloud.points[:3].T, axis=1) - case _: + case PointCloudColorMode.INTENSITY: values = pointcloud.points[3] + case _: + raise ValueError(f"Unsupported color mode: {color_mode}") return _normalize_color(values) From 50cd81a30e03d0e5d783d2c1e17e0049dd684ed2 Mon Sep 17 00:00:00 2001 From: Kotaro Uetake <60615504+ktro2828@users.noreply.github.com> Date: Thu, 2 Oct 2025 18:11:22 +0900 Subject: [PATCH 6/6] Update t4_devkit/helper/rendering.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- t4_devkit/helper/rendering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t4_devkit/helper/rendering.py b/t4_devkit/helper/rendering.py index 939db48..b2df55b 100644 --- a/t4_devkit/helper/rendering.py +++ b/t4_devkit/helper/rendering.py @@ -494,7 +494,7 @@ def _project_pointcloud( color_mode (PointCloudColorMode, optional): Color mode for pointcloud. Returns: - Projected points [2, n], their normalized features [n] and an image. + Projected points [2, n], their color values [n, 3], 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)