Python: Debug Views#

This example renders RT2 G-buffer and heat map debug views from the robot-lineup scene. It captures each view before logging all images together to Rerun under their debug-view selector names. Static views are captured first, then the robots move for ten frames to capture targetMotion and targetMotionArrows.

Grid of RT2 depth, position, coordinate, normal, identifier, motion, and wireframe debug views

The RT2 G-buffer debug views rendered from one scene. Each tile is named after the selector authored on its RenderProduct.#

The example creates one RenderProduct per selector. Every RenderProduct requests only LdrColor and HdrColor.

The rendering loop below captures the HdrColor output for each RenderProduct. It converts the display-encoded debug views to 8-bit without tonemapping.

images = {}
for label, selector in DEBUG_VIEWS:
    render_product_path = f"/Render/{selector}"
    print(f"Rendering {label} ({selector})...")

    products = None
    # Each RenderProduct has independent temporal state, so warm it up before capture.
    for _ in range(max(args.warmup_frames, 0) + 1):
        products = renderer.step(
            render_products={render_product_path},
            delta_time=args.step_dt,
            ordinal=ordinal,
        )

    if not products[render_product_path].frames:
        raise RuntimeError(f"{render_product_path} did not produce an image")

    frame = products[render_product_path].frames[0]
    # Debug views already contain display colors; copy them without HDR tonemapping.
    pixels = _map_hdr_debug_view(frame, render_product_path)
    images[selector] = _to_uint8(pixels)
    del frame, products

# Capture motion views after all static views so movement cannot change their images.
motion_selectors = ("targetMotion", "targetMotionArrows")
robot_paths = [
    "/World/GR1T2_fourier_hand_6dof", "/World/spot", "/World/forklift_c", "/World/forklift_b",
    "/World/carter_v1", "/World/jetbot", "/World/kaya",
]
velocities = np.array(
    [(3, 0, 0), (-3, 0, 0), (2, 0, 1), (-2, 0, 1), (3, 0, 1), (-3, 0, 1), (2, 0, 1)],
    dtype=np.float64,
)
with ovstage.PathDictionary(stage) as paths:
    path_list = paths.create_path_list_from_strings(robot_paths)
    with stage.query_from_path_list(path_list) as query:
        attribute = paths.intern_token("omni:xform")
        transforms = np.full((len(robot_paths), 4, 4), np.nan, dtype=np.float64)
        # Preserve orientation and scale, including the centimetre-scaled forklift payload.
        with stage.read_attributes(query, [attribute], ovstage.OrdinalRange.latest(ordinal)) as read:
            while (group := read.fetch_next()) is not None:
                values = np.from_dlpack(group.dlpack(0)).reshape(-1, 4, 4)
                for index in range(group.prim_count):
                    transforms[group.prim_index(index)] = values[group.data_row_index(index)]
                del values
                stage.release_group(group)
        if not np.all(np.isfinite(transforms)):
            raise RuntimeError("Motion views require transforms for all seven lineup robots")

        matrix_dtype = ovstage.numpy_to_dldatatype(transforms.dtype, lanes=16)
        for selector in motion_selectors:
            render_product_path = f"/Render/{selector}"
            # Refresh previous-frame transforms after the other views' independent warmups.
            for _ in range(2):
                products = renderer.step(
                    render_products={render_product_path}, delta_time=args.step_dt, ordinal=ordinal
                )
                del products

            for motion_step in range(10):
                # Alternate small offsets to keep robots in view while providing motion every frame.
                direction = 1.0 if motion_step % 2 == 0 else -1.0
                transforms[:, 3, :3] += direction * velocities * args.step_dt
                ordinal += 1
                tensor = ovstage.make_dltensor(transforms, dtype=matrix_dtype, shape=[len(robot_paths)], ndim=1)
                stage.write_attribute(query, attribute, ordinal=ordinal, tensors=tensor, is_array=False).wait()
                stage.advance_write_floor(ordinal, ovstage.Scope.ALL).wait()
                products = renderer.step(
                    render_products={render_product_path}, delta_time=args.step_dt, ordinal=ordinal
                )
                if motion_step < 9:
                    del products

            if not products[render_product_path].frames:
                raise RuntimeError(f"{render_product_path} did not produce a moving frame")
            frame = products[render_product_path].frames[0]
            pixels = _map_hdr_debug_view(frame, render_product_path)
            display = _to_uint8(pixels)
            changed_pixels = np.count_nonzero(np.any(display != images[selector], axis=-1))
            print(f"Moving {selector}: {changed_pixels} pixels differ from static capture", flush=True)
            images[selector] = display
            del frame, products
    paths.destroy_path_list(path_list)

Run the example interactively, save a Rerun recording, or create PNG output:

cd examples/python/debug-views
uv run main.py
uv run main.py --rrd _output/debug-views.rrd
uv run main.py --no-rr --png _output/debug-views.png

See Outputs for the supported selectors and output-routing behavior.