Skip to content

Commit dd746cb

Browse files
ktro2828Copilot
andauthored
feat: add support of rendering point cloud coloring with intensity (#189)
* feat: add support of rendering point cloud coloring with intensity Signed-off-by: ktro2828 <kotaro.uetake@tier4.jp> * feat: add support of colorizing points on image by intensity Signed-off-by: ktro2828 <kotaro.uetake@tier4.jp> * feat: set default value of PointCloudColorMode to DISTANCE Signed-off-by: ktro2828 <kotaro.uetake@tier4.jp> * refactor: modify normalize_color to private function Signed-off-by: ktro2828 <kotaro.uetake@tier4.jp> * Update t4_devkit/viewer/color.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update t4_devkit/helper/rendering.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Signed-off-by: ktro2828 <kotaro.uetake@tier4.jp> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 1ba491c commit dd746cb

3 files changed

Lines changed: 79 additions & 27 deletions

File tree

t4_devkit/helper/rendering.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@
1515
from t4_devkit.common.timestamp import sec2us, us2sec
1616
from t4_devkit.dataclass import LidarPointCloud, RadarPointCloud
1717
from t4_devkit.schema import SensorModality
18-
from t4_devkit.viewer import RerunViewer, ViewerBuilder, ViewerConfig, distance_color, format_entity
18+
from t4_devkit.viewer import (
19+
PointCloudColorMode,
20+
RerunViewer,
21+
ViewerBuilder,
22+
ViewerConfig,
23+
format_entity,
24+
pointcloud_color,
25+
)
1926

2027
if TYPE_CHECKING:
2128
from t4_devkit import Tier4
@@ -332,6 +339,7 @@ def _render_lidar_and_ego(
332339
viewer: RerunViewer,
333340
first_lidar_tokens: list[str],
334341
max_timestamp_us: float,
342+
color_mode: PointCloudColorMode = PointCloudColorMode.DISTANCE,
335343
) -> list[Future]:
336344
def _render_single_lidar(first_lidar_token: str) -> None:
337345
self._render_sensor_calibration(viewer=viewer, sample_data_token=first_lidar_token)
@@ -353,6 +361,7 @@ def _render_single_lidar(first_lidar_token: str) -> None:
353361
seconds=us2sec(sample_data.timestamp),
354362
channel=sample_data.channel,
355363
pointcloud=pointcloud,
364+
color_mode=color_mode,
356365
)
357366

358367
current_lidar_token = sample_data.next
@@ -423,6 +432,7 @@ def _render_points_on_cameras(
423432
*,
424433
min_dist: float = 1.0,
425434
ignore_distortion: bool = True,
435+
color_mode: PointCloudColorMode = PointCloudColorMode.DISTANCE,
426436
) -> list[Future]:
427437
def _render_points_on_single_camera(camera: str) -> None:
428438
current_point_sample_data_token = first_point_sample_data_token
@@ -441,19 +451,20 @@ def _render_points_on_single_camera(camera: str) -> None:
441451
if max_timestamp_us < sample.timestamp:
442452
break
443453

444-
points_on_image, depths, image = self._project_pointcloud(
454+
points_on_image, colors, image = self._project_pointcloud(
445455
point_sample_data_token=current_point_sample_data_token,
446456
camera_sample_data_token=camera_sample_data_token,
447457
min_dist=min_dist,
448458
ignore_distortion=ignore_distortion,
459+
color_mode=color_mode,
449460
)
450461

451462
rr.set_time_seconds(ViewerConfig.timeline, us2sec(sample.timestamp))
452463

453464
rr.log(format_entity(ViewerConfig.ego_entity, camera), rr.Image(image))
454465
rr.log(
455466
format_entity(ViewerConfig.ego_entity, camera, "pointcloud"),
456-
rr.Points2D(positions=points_on_image.T, colors=distance_color(depths)),
467+
rr.Points2D(positions=points_on_image.T, colors=colors),
457468
)
458469

459470
current_point_sample_data_token = sample_data.next
@@ -471,6 +482,7 @@ def _project_pointcloud(
471482
min_dist: float = 1.0,
472483
*,
473484
ignore_distortion: bool = True,
485+
color_mode: PointCloudColorMode = PointCloudColorMode.DISTANCE,
474486
) -> tuple[NDArrayF64, NDArrayF64, NDArrayU8]:
475487
"""Project pointcloud on image plane.
476488
@@ -479,9 +491,10 @@ def _project_pointcloud(
479491
camera_sample_data_token (str): Sample data token of camera.
480492
min_dist (float, optional): Distance from the camera below which points are discarded.
481493
ignore_distortion (bool, optional): Whether to ignore distortion parameters.
494+
color_mode (PointCloudColorMode, optional): Color mode for pointcloud.
482495
483496
Returns:
484-
Projected points [2, n], their normalized depths [n] and an image.
497+
Projected points [2, n], their color values [n, 3], and an image.
485498
"""
486499
point_sample_data: SampleData = self._t4.get("sample_data", point_sample_data_token)
487500
pc_filepath = osp.join(self._t4.data_root, point_sample_data.filename)
@@ -522,8 +535,6 @@ def _project_pointcloud(
522535
pointcloud.translate(-camera_cs_record.translation)
523536
pointcloud.rotate(camera_cs_record.rotation.rotation_matrix.T)
524537

525-
depths = pointcloud.points[2, :]
526-
527538
distortion = None if ignore_distortion else camera_cs_record.camera_distortion
528539

529540
points_on_img = view_points(
@@ -533,16 +544,20 @@ def _project_pointcloud(
533544
normalize=True,
534545
)[:2]
535546

536-
mask = np.ones(depths.shape[0], dtype=bool)
537-
mask = np.logical_and(mask, depths > min_dist)
547+
colors = pointcloud_color(pointcloud, color_mode)
548+
depths = pointcloud.points[2, :]
549+
550+
mask = np.ones(colors.shape[0], dtype=bool)
551+
mask = np.logical_and(mask, depths > min_dist) # mask by depths
552+
# mask by size of points on image
538553
mask = np.logical_and(mask, 1 < points_on_img[0])
539554
mask = np.logical_and(mask, points_on_img[0] < img.size[0] - 1)
540555
mask = np.logical_and(mask, 1 < points_on_img[1])
541556
mask = np.logical_and(mask, points_on_img[1] < img.size[1] - 1)
542557
points_on_img = points_on_img[:, mask]
543-
depths = depths[mask]
558+
colors = colors[mask]
544559

545-
return points_on_img, depths, np.array(img, dtype=np.uint8)
560+
return points_on_img, colors, np.array(img, dtype=np.uint8)
546561

547562
def _render_annotation3ds(
548563
self,

t4_devkit/viewer/color.py

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,61 @@
11
from __future__ import annotations
22

3+
from enum import Enum, unique
34
from typing import TYPE_CHECKING
45

56
import matplotlib
7+
import numpy as np
68

79
if TYPE_CHECKING:
8-
from t4_devkit.typing import ArrayLike, NDArrayF64, ScalarLike
10+
from t4_devkit.dataclass import PointCloudLike
11+
from t4_devkit.typing import ArrayLike, NDArrayF64
912

1013

11-
def distance_color(
12-
distances: ScalarLike | ArrayLike,
13-
cmap: str | None = None,
14-
v_min: float = 3.0,
15-
v_max: float = 75.0,
16-
) -> tuple[float, float, float] | NDArrayF64:
17-
"""Return color map depending on distance values.
14+
__all__ = ["PointCloudColorMode", "pointcloud_color"]
15+
16+
17+
@unique
18+
class PointCloudColorMode(str, Enum):
19+
"""Color mode of point cloud."""
20+
21+
DISTANCE = "distance"
22+
INTENSITY = "intensity"
23+
24+
25+
def pointcloud_color(
26+
pointcloud: PointCloudLike,
27+
color_mode: PointCloudColorMode = PointCloudColorMode.DISTANCE,
28+
) -> NDArrayF64:
29+
"""Return color map depending on the specified color mode.
30+
31+
Args:
32+
pointcloud (PointCloudLike): Any inheritance of `PointCloud` class.
33+
color_mode (PointCloudColorMode, optional): Color mode for pointcloud.
34+
"""
35+
match color_mode:
36+
case PointCloudColorMode.DISTANCE:
37+
values = np.linalg.norm(pointcloud.points[:3].T, axis=1)
38+
case PointCloudColorMode.INTENSITY:
39+
values = pointcloud.points[3]
40+
case _:
41+
raise ValueError(f"Unsupported color mode: {color_mode}")
42+
43+
return _normalize_color(values)
44+
45+
46+
def _normalize_color(values: ArrayLike, cmap: str | None = None, alpha: float = 1.0) -> NDArrayF64:
47+
"""Return color map normalizing values.
1848
1949
Args:
20-
distances (ScalarLike | ArrayLike): Array of distances in the shape of (N,).
50+
values (ArrayLike): Array of values in the shape of (N,).
2151
cmap (str | None, optional): Color map name in matplotlib. If None, `turbo_r` will be used.
22-
v_min (float, optional): Min value to normalize.
23-
v_max (float, optional): Max value to normalize.
52+
alpha (float, optional): Alpha value of color map.
2453
2554
Returns:
26-
Color map in the shape of (N,). If input type is any number, returns a color as
27-
`tuple[float, float, float]`. Otherwise, returns colors as `NDArrayF64`.
55+
Color map in the shape of (N,).
2856
"""
2957
color_map = matplotlib.colormaps["turbo_r"] if cmap is None else matplotlib.colormaps[cmap]
58+
v_min = np.min(values)
59+
v_max = np.max(values)
3060
norm = matplotlib.colors.Normalize(v_min, v_max)
31-
return color_map(norm(distances))
61+
return color_map(norm(values), alpha=alpha)

t4_devkit/viewer/viewer.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from t4_devkit.lanelet import LaneletParser
1313
from t4_devkit.schema import SensorModality
1414

15-
from .color import distance_color
15+
from .color import PointCloudColorMode, pointcloud_color
1616
from .config import ViewerConfig, format_entity
1717
from .geography import calculate_geodetic_point
1818
from .lanelet import (
@@ -408,18 +408,25 @@ def render_segmentation2d(
408408
)
409409

410410
@_check_spatial3d
411-
def render_pointcloud(self, seconds: float, channel: str, pointcloud: PointCloudLike) -> None:
411+
def render_pointcloud(
412+
self,
413+
seconds: float,
414+
channel: str,
415+
pointcloud: PointCloudLike,
416+
color_mode: PointCloudColorMode = PointCloudColorMode.DISTANCE,
417+
) -> None:
412418
"""Render pointcloud.
413419
414420
Args:
415421
seconds (float): Timestamp in [sec].
416422
channel (str): Name of the pointcloud sensor channel.
417423
pointcloud (PointCloudLike): Inherence object of `PointCloud`.
424+
color_mode (PointCloudColorMode, optional): Color mode for pointcloud.
418425
"""
419426
# TODO(ktro2828): add support of rendering pointcloud on images
420427
rr.set_time_seconds(self.config.timeline, seconds)
421428

422-
colors = distance_color(np.linalg.norm(pointcloud.points[:3].T, axis=1))
429+
colors = pointcloud_color(pointcloud, color_mode=color_mode)
423430
rr.log(
424431
format_entity(self.config.ego_entity, channel),
425432
rr.Points3D(pointcloud.points[:3].T, colors=colors),

0 commit comments

Comments
 (0)