# How to Configure Custom Camera Paths in YAML for the Offline Renderer Flythrough

> Configure custom camera paths in YAML for offline renderer flythroughs. Learn to define camera poses with position, orientation, and FOV in your lingbot-map benchmark YAML.

- Repository: [Robbyant/lingbot-map](https://github.com/Robbyant/lingbot-map)
- Tags: how-to-guide
- Published: 2026-07-04

---

**The offline renderer in the lingbot-map repository creates fly-through animations by reading a `camera_path` list from the `evaluation.traj` section of your benchmark YAML configuration, where each entry defines a camera pose through position, orientation, and optional timestamp or FOV values.**

The lingbot-map repository provides an offline renderer that generates smooth fly-through visualizations of mapped environments. When you need to showcase specific viewpoints or create presentation-quality camera movements, you can override the default dataset trajectory by defining **custom camera paths** directly in your benchmark configuration files.

## Understanding the Camera Path Configuration

The renderer initializes its trajectory by inspecting the `evaluation.traj` block in your YAML configuration. When the parser encounters a `camera_path` key, it bypasses the default dataset poses and instead builds the animation sequence from your manually specified list.

According to the source code in [`benchmark/benchmark/io/trajectory.py`](https://github.com/Robbyant/lingbot-map/blob/main/benchmark/benchmark/io/trajectory.py), the parser expects `camera_path` to contain a list of pose dictionaries. The rendering loop in [`lingbot_map/vis/viser_wrapper.py`](https://github.com/Robbyant/lingbot-map/blob/main/lingbot_map/vis/viser_wrapper.py) then consumes this data to drive the visualization, while [`lingbot_map/vis/utils.py`](https://github.com/Robbyant/lingbot-map/blob/main/lingbot_map/vis/utils.py) handles the conversion of YAML specifications into the transformation matrices required by the graphics pipeline.

## Required YAML Fields for Camera Poses

Every entry in the `camera_path` list must define the camera's spatial properties using specific data types. The renderer validates these fields before constructing the interpolation spline.

### Position and Orientation

The two mandatory fields define where the camera is and which direction it faces:

- **`position`**: A list of three floats representing world-space XYZ coordinates (e.g., `[1.2, 0.5, 2.0]`)
- **`orientation`**: A list of four floats representing a quaternion in `(w, x, y, z)` format (e.g., `[0.9239, 0.0, 0.3827, 0.0]` for a 45-degree yaw)

### Optional FOV and Timestamp Parameters

You can fine-tune the visualization with two additional fields:

- **`fov`**: A float specifying the horizontal field-of-view in degrees. If omitted, the renderer uses the default intrinsics from the dataset configuration.
- **`timestamp`**: A float indicating the exact time in seconds when the camera should reach this pose. When omitted, the renderer assumes poses are equally spaced at one-second intervals.

## Step-by-Step Configuration Guide

To implement a custom camera trajectory:

1. Open your benchmark configuration file (commonly located at [`benchmark/configs/oxford.yaml`](https://github.com/Robbyant/lingbot-map/blob/main/benchmark/configs/oxford.yaml) or similar paths).
2. Locate the `evaluation` section and ensure `traj` is enabled with `enable: true` and `vis: true`.
3. Add the `camera_path` key under `evaluation.traj` and populate it with your desired pose list.
4. Save the file and execute the offline renderer (e.g., `python benchmark/run.py --config benchmark/configs/oxford.yaml`).

The renderer will automatically interpolate between your specified points, creating a smooth spline that the fly-through visualizer follows.

## Complete YAML Configuration Examples

Below are production-ready configurations demonstrating both timestamped and evenly-spaced camera paths.

### Example with Explicit Timestamps

Use this format when you need precise control over timing and velocity:

```yaml

# benchmark/configs/oxford.yaml

workspace: /path/to/workspace/oxford

datasets:
  - oxford

methods:
  - lingbot_map

evaluation:
  traj:
    enable: true
    vis: true
    camera_path:
      - position: [0.0, 0.0, 0.0]
        orientation: [1.0, 0.0, 0.0, 0.0]   # Identity quaternion

        fov: 60.0
        timestamp: 0.0
      - position: [1.2, 0.5, 2.0]
        orientation: [0.9239, 0.0, 0.3827, 0.0]   # 45° yaw

        fov: 60.0
        timestamp: 2.0
      - position: [2.5, -0.3, 3.1]
        orientation: [0.7071, 0.0, 0.7071, 0.0]   # 90° yaw

        fov: 60.0
        timestamp: 4.5

```

The renderer interpolates from the origin to the second pose over two seconds, then takes an additional 2.5 seconds to reach the final position.

### Example with Equally-Spaced Sampling

Omit timestamps for simpler scenarios where uniform timing is acceptable:

```yaml
evaluation:
  traj:
    enable: true
    vis: true
    camera_path:
      - position: [0.0, 0.0, 0.0]
        orientation: [1.0, 0.0, 0.0, 0.0]
      - position: [1.0, 0.2, 1.5]
        orientation: [0.9659, 0.0, 0.2588, 0.0]
      - position: [2.0, -0.1, 2.8]
        orientation: [0.8660, 0.0, 0.5, 0.0]

```

The system automatically assigns timestamps of `0.0`, `1.0`, and `2.0` seconds to these three poses, creating a constant-speed fly-through.

## How the Renderer Processes Camera Paths

When the offline renderer initializes, the trajectory parser in [`benchmark/benchmark/io/trajectory.py`](https://github.com/Robbyant/lingbot-map/blob/main/benchmark/benchmark/io/trajectory.py) validates the YAML structure and instantiates a `CameraTrajectory` object containing your custom path. If the `camera_path` key is absent, the system falls back to the original trajectory data stored in the dataset files.

The visualization engine in [`lingbot_map/vis/viser_wrapper.py`](https://github.com/Robbyant/lingbot-map/blob/main/lingbot_map/vis/viser_wrapper.py) queries this trajectory object during each frame of the render loop. Using helper functions from [`lingbot_map/vis/utils.py`](https://github.com/Robbyant/lingbot-map/blob/main/lingbot_map/vis/utils.py), it converts the quaternion and position data into view matrices, then interpolates between keyframes using either your explicit timestamps or the default uniform spacing.

## Summary

- The **offline renderer** reads camera paths from the `evaluation.traj.camera_path` key in benchmark YAML files.
- Each pose requires a **position** (3 floats) and **orientation** quaternion (4 floats in `w, x, y, z` order).
- Optional **FOV** and **timestamp** fields allow fine-tuning of the camera lens and animation timing.
- The parser in [`benchmark/benchmark/io/trajectory.py`](https://github.com/Robbyant/lingbot-map/blob/main/benchmark/benchmark/io/trajectory.py) creates the trajectory object, while [`lingbot_map/vis/viser_wrapper.py`](https://github.com/Robbyant/lingbot-map/blob/main/lingbot_map/vis/viser_wrapper.py) executes the rendering loop.
- If no `camera_path` is specified, the renderer defaults to the dataset's original camera poses.

## Frequently Asked Questions

### What happens if I specify a camera path but omit the orientation field?

The renderer will fail to initialize the trajectory. According to the validation logic in [`benchmark/benchmark/io/trajectory.py`](https://github.com/Robbyant/lingbot-map/blob/main/benchmark/benchmark/io/trajectory.py), both `position` and `orientation` are mandatory fields for every entry in the `camera_path` list. You must provide a valid quaternion even if you only want positional movement.

### Can I mix timestamps and non-timestamped poses in the same camera path?

No, the renderer expects a consistent timing strategy for the entire sequence. If any pose contains a `timestamp` field, the system assumes all poses use explicit timing. Mixing the two approaches will result in interpolation errors or unexpected animation speeds.

### How do I calculate quaternion values for specific rotation angles?

You can convert Euler angles (roll, pitch, yaw) to the required `(w, x, y, z)` quaternion format using standard conversion formulas or ROS/SciPy utilities. For example, a 90-degree rotation around the Y-axis (yaw) converts to `[0.7071, 0.0, 0.7071, 0.0]` in the YAML configuration.

### Where does the renderer look if I don't provide a custom camera path?

When the `camera_path` key is absent from `evaluation.traj`, the offline renderer in [`lingbot_map/vis/viser_wrapper.py`](https://github.com/Robbyant/lingbot-map/blob/main/lingbot_map/vis/viser_wrapper.py) automatically falls back to the camera poses stored in the original dataset trajectory files, using the same loading mechanism as the standard evaluation pipeline.