Visualizing a Point Cloud#

This tutorial uses SimulationManager.visualize_point_cloud() to display a color-coded point cloud in the native DexSim viewer. It is useful for inspecting sampled workspaces, sensor output, and other point-based data in the same coordinate frame as a simulation scene.

The Code#

The tutorial corresponds to visualize_point_cloud.py in scripts/tutorials/sim.

Code for visualize_point_cloud.py
  1# ----------------------------------------------------------------------------
  2# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
  3#
  4# Licensed under the Apache License, Version 2.0 (the "License");
  5# you may not use this file except in compliance with the License.
  6# You may obtain a copy of the License at
  7#
  8#     http://www.apache.org/licenses/LICENSE-2.0
  9#
 10# Unless required by applicable law or agreed to in writing, software
 11# distributed under the License is distributed on an "AS IS" BASIS,
 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13# See the License for the specific language governing permissions and
 14# limitations under the License.
 15# ----------------------------------------------------------------------------
 16
 17"""Visualize a deterministic RGB point cloud with DexSim.
 18
 19Run with::
 20
 21    python scripts/tutorials/sim/visualize_point_cloud.py
 22
 23Render one frame with an offscreen camera, without opening a native window::
 24
 25    python scripts/tutorials/sim/visualize_point_cloud.py --headless
 26
 27The viewer should show red X, green Y, and blue Z point axes. The script uses
 28``uint8`` per-point colors to exercise the color normalization in
 29``SimulationManager.visualize_point_cloud``.
 30"""
 31
 32from __future__ import annotations
 33
 34import argparse
 35import time
 36from pathlib import Path
 37
 38import numpy as np
 39from PIL import Image
 40
 41from embodichain.lab.sim import SimulationManager, SimulationManagerCfg
 42from embodichain.lab.visualization import VisualizationCfg
 43from embodichain.utils import logger
 44from embodichain.utils.math import look_at_to_pose
 45
 46CAMERA_EYE = (2.0, -2.0, 1.5)
 47CAMERA_TARGET = (0.0, 0.0, 0.35)
 48CAMERA_UP = (0.0, 0.0, 1.0)
 49FRAME_WIDTH = 1280
 50FRAME_HEIGHT = 720
 51DEFAULT_OUTPUT_PATH = Path("outputs/point_cloud_visualization.png")
 52
 53
 54def build_demo_point_cloud(
 55    num_points_per_axis: int = 120,
 56) -> tuple[np.ndarray, np.ndarray]:
 57    """Build a color-coded three-axis point cloud for visual inspection.
 58
 59    Args:
 60        num_points_per_axis: Number of points rendered for each axis.
 61
 62    Returns:
 63        Point positions with shape ``(3 * N, 3)`` and ``uint8`` RGB colors
 64        with the same leading dimension.
 65    """
 66    horizontal = np.linspace(-0.75, 0.75, num_points_per_axis, dtype=np.float32)
 67    vertical = np.linspace(0.05, 1.25, num_points_per_axis, dtype=np.float32)
 68    ground_height = np.full_like(horizontal, 0.05)
 69    zeros = np.zeros_like(horizontal)
 70
 71    x_axis = np.column_stack((horizontal, zeros, ground_height))
 72    y_axis = np.column_stack((zeros, horizontal, ground_height))
 73    z_axis = np.column_stack((zeros, zeros, vertical))
 74    points = np.concatenate((x_axis, y_axis, z_axis), axis=0)
 75
 76    red = np.full((num_points_per_axis, 3), (255, 0, 0), dtype=np.uint8)
 77    green = np.full((num_points_per_axis, 3), (0, 255, 0), dtype=np.uint8)
 78    blue = np.full((num_points_per_axis, 3), (0, 0, 255), dtype=np.uint8)
 79    colors = np.concatenate((red, green, blue), axis=0)
 80    return points, colors
 81
 82
 83def build_camera_pose() -> np.ndarray:
 84    """Build the offscreen camera pose for the point-cloud overview."""
 85    pose = look_at_to_pose(CAMERA_EYE, CAMERA_TARGET, CAMERA_UP)[0].cpu().numpy()
 86    # DexSim cameras use the OpenGL camera-axis convention.
 87    pose[:3, 1] = -pose[:3, 1]
 88    pose[:3, 2] = -pose[:3, 2]
 89    return np.asarray(pose, dtype=np.float32)
 90
 91
 92def render_headless_frame(sim: SimulationManager, output_path: Path) -> None:
 93    """Render the point cloud once with an offscreen camera and save a PNG.
 94
 95    Args:
 96        sim: Simulation containing the point cloud to render.
 97        output_path: PNG destination. Its parent directory is created if needed.
 98    """
 99    camera = sim.get_env().create_camera(
100        "point_cloud_tutorial_camera", FRAME_WIDTH, FRAME_HEIGHT
101    )
102    if hasattr(camera, "is_open") and camera.is_open() is False:
103        camera.open_camera()
104
105    camera.set_world_pose(build_camera_pose())
106    camera.render()
107    frame = np.ascontiguousarray(np.asarray(camera.get_rgb_map())[..., :3])
108    if frame.size == 0:
109        raise RuntimeError("The offscreen camera returned an empty RGB frame.")
110
111    output_path.parent.mkdir(parents=True, exist_ok=True)
112    Image.fromarray(frame).save(output_path)
113    logger.log_info(f"Saved offscreen point-cloud frame to {output_path}.")
114
115
116def parse_args() -> argparse.Namespace:
117    """Parse the tutorial's optional offscreen-rendering arguments."""
118    parser = argparse.ArgumentParser(description=__doc__)
119    parser.add_argument(
120        "--headless",
121        action="store_true",
122        help="Render one PNG with an offscreen camera instead of opening the viewer.",
123    )
124    parser.add_argument(
125        "--output",
126        type=Path,
127        default=DEFAULT_OUTPUT_PATH,
128        help=(
129            "PNG path used with --headless "
130            f"(default: {DEFAULT_OUTPUT_PATH.as_posix()})."
131        ),
132    )
133    return parser.parse_args()
134
135
136def main() -> None:
137    """Create the RGB point cloud and display or render it once."""
138    args = parse_args()
139    sim = SimulationManager(
140        SimulationManagerCfg(
141            width=FRAME_WIDTH,
142            height=FRAME_HEIGHT,
143            headless=True,
144            visualization=VisualizationCfg(),
145        )
146    )
147    try:
148        points, colors = build_demo_point_cloud()
149        sim.visualize_point_cloud(
150            points=points,
151            colors=colors,
152            point_size=8.0,
153            name="rgb_point_cloud_axes",
154        )
155        if args.headless:
156            sim.update(step=1)
157            render_headless_frame(sim, args.output)
158            return
159
160        if not sim.open_window():
161            raise RuntimeError("Unable to open the native DexSim viewer.")
162
163        sim.get_world().get_windows().set_look_at(
164            eye=np.array(CAMERA_EYE, dtype=np.float32),
165            look_at=np.array(CAMERA_TARGET, dtype=np.float32),
166            up=np.array(CAMERA_UP, dtype=np.float32),
167        )
168        logger.log_info(
169            "Point-cloud viewer open: red=X, green=Y, blue=Z. Press Ctrl+C to exit."
170        )
171        while True:
172            sim.update(step=1)
173            time.sleep(1.0 / 60.0)
174    except KeyboardInterrupt:
175        logger.log_info("Stopping point-cloud viewer.")
176    finally:
177        sim.destroy(exit_process=False)
178        SimulationManager.flush_cleanup_queue()
179
180
181if __name__ == "__main__":
182    main()

Building a Verifiable Point Cloud#

The example constructs three orthogonal point axes:

  • red points along X;

  • green points along Y;

  • blue points along Z.

Each color is stored as uint8 RGB. The manager accepts either normalized [0, 1] values or [0, 255] values, and normalizes the latter before passing them to DexSim.

def build_demo_point_cloud(
    num_points_per_axis: int = 120,
) -> tuple[np.ndarray, np.ndarray]:
    """Build a color-coded three-axis point cloud for visual inspection.

    Args:
        num_points_per_axis: Number of points rendered for each axis.

    Returns:
        Point positions with shape ``(3 * N, 3)`` and ``uint8`` RGB colors
        with the same leading dimension.
    """
    horizontal = np.linspace(-0.75, 0.75, num_points_per_axis, dtype=np.float32)
    vertical = np.linspace(0.05, 1.25, num_points_per_axis, dtype=np.float32)
    ground_height = np.full_like(horizontal, 0.05)
    zeros = np.zeros_like(horizontal)

    x_axis = np.column_stack((horizontal, zeros, ground_height))
    y_axis = np.column_stack((zeros, horizontal, ground_height))
    z_axis = np.column_stack((zeros, zeros, vertical))
    points = np.concatenate((x_axis, y_axis, z_axis), axis=0)

    red = np.full((num_points_per_axis, 3), (255, 0, 0), dtype=np.uint8)
    green = np.full((num_points_per_axis, 3), (0, 255, 0), dtype=np.uint8)
    blue = np.full((num_points_per_axis, 3), (0, 0, 255), dtype=np.uint8)
    colors = np.concatenate((red, green, blue), axis=0)
    return points, colors

Creating the Native Point Cloud#

Create the simulation headlessly, add the point cloud, then open the native window after the scene is ready. The name identifies the native DexSim object, and point_size is measured in renderer pixels.

sim = SimulationManager(
    SimulationManagerCfg(
        width=FRAME_WIDTH,
        height=FRAME_HEIGHT,
        headless=True,
        visualization=VisualizationCfg(),
    )
)
points, colors = build_demo_point_cloud()

visualize_point_cloud accepts point positions with shape (N, 3) and optional per-point RGB or RGBA colors with shape (N, 3) or (N, 4). When colors are omitted, the manager renders all points in green. RGBA input is accepted for compatibility, but the native manager currently renders RGB colors with opaque alpha.

Running the Tutorial#

Run the tutorial from the repository root:

python scripts/tutorials/sim/visualize_point_cloud.py

The native DexSim window should show a red horizontal X axis, a green horizontal Y axis, and a blue vertical Z axis. This verifies both point placement and per-point color handling. Press Ctrl+C in the terminal to stop the tutorial.

The script explicitly uses destroy(exit_process=False) and then SimulationManager.flush_cleanup_queue() so its simulation resources are released before Python exits.

Headless Rendering#

To save a single frame on a machine without a native display, pass --headless. The tutorial creates an offscreen DexSim camera at the same overview pose as the interactive viewer, renders once, and then exits:

python scripts/tutorials/sim/visualize_point_cloud.py --headless \
    --output outputs/point_cloud_visualization.png

The resulting image preserves the red X, green Y, and blue Z axes, so it is a portable visual check of both point placement and per-point colors.

An offscreen render of red, green, and blue point-cloud axes.

One frame rendered by the tutorial’s offscreen camera.#

Next Steps#