Skip to content

First steps

This tutorial connects to the server, moves the spectator to W 125th St and Lenox Ave in Harlem, steps the world in synchronous mode, and renders one view at four times of day and in rain.

python PythonAPI/examples/tutorials/first_steps.py

Connect

client = valdrada.Client(a.host, a.port)       # retries until the server accepts the connection
client.set_timeout(120.0)
world = client.get_world()                       # waits until the simulator has booted

Client retries until the server accepts the connection, and get_world() waits until the simulator has booted, which takes a minute or two on the first start while the shaders compile. set_timeout bounds how long each call waits for its answer.

Synchronous stepping

original = world.get_settings()
world.apply_settings(valdrada.WorldSettings(synchronous_mode=True, fixed_delta_seconds=0.05))

In synchronous mode, the default, the simulation advances by fixed_delta_seconds on each world.tick() and waits otherwise, so a script can take as long as it needs between steps. tick() returns the new frame number once every listening sensor has delivered its data for that step. The script restores the original settings before it exits.

The spectator and streaming

here = m.geolocation_to_location(*LENOX_125)
world.get_spectator().set_transform(Transform(here + Location(0, 0, 30)))
print("loaded:", world.wait_until_loaded())
junction = m.get_junctions(center=here, radius=40)[0]

The city streams in around the spectator, and renders and map queries see only what has loaded. wait_until_loaded renders with time frozen until the tiles and buildings around the spectator are in. get_junctions then returns the intersection from the loaded street network, with its location, its arms and whether it is signalized.

A camera, the time of day and the weather

camera = world.spawn_actor(bp, Transform(junction.location + Location(-22, -20, 1.8), Rotation(pitch=2, yaw=45)))
latest = {}
camera.listen(lambda image: latest.update(image=image))   # runs once per tick, before tick() returns

A camera spawned without a parent keeps its world pose. This one stands at eye height on the south-west corner of the intersection and looks up Lenox Ave with a horizontal field of view of 80°. listen registers a callback that receives the camera's image on every step.

world.set_weather takes a time of day (day, golden, dusk or night) and an amount of rain between 0 and 1; the presets of WeatherParameters combine the two. After each change the script runs 60 steps (3 s of simulated time) so that the lighting and the renderer's temporal history settle, then saves the last image. Rain wets the streets gradually, and they dry the same way once it stops, so the script restores the weather it found and runs another 200 steps before it exits; the next script then starts on dry streets.

The view up Lenox Ave in daylight
WeatherParameters.Day.
The view up Lenox Ave at golden hour
WeatherParameters.Golden.
The view up Lenox Ave at dusk
WeatherParameters.Dusk.
The view up Lenox Ave at night
WeatherParameters.Night.
The view up Lenox Ave in rain
WeatherParameters.RainyDay: daylight with rain 0.8.

The script prints:

server 0.1.0, 1280x720, ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Laptop GPU (0x00002520) Direct3D11 vs_5_0 ps_5_0, D3D11)
loaded: {'loaded': True, 'seconds': 57.8, 'tiles': 26, 'macro': 84}
Junction(2160_-2741, Location(x=2167.500, y=2740.000, z=3.385), signalized=True, arms=8) arm headings: [-120, -119, -29, 62, 151]
day     frame 60, sim time   3.00 s -> _out/first_steps/day.png
golden  frame 120, sim time   6.00 s -> _out/first_steps/golden.png
dusk    frame 180, sim time   9.00 s -> _out/first_steps/dusk.png
night   frame 240, sim time  12.00 s -> _out/first_steps/night.png
rain    frame 300, sim time  15.00 s -> _out/first_steps/rain.png

The complete script

"""First steps: connect to the server, place the spectator, step the world in synchronous mode, and change the time
of day and the weather. Saves one RGB image per condition at W 125th St and Lenox Ave.

    python first_steps.py [--host 127.0.0.1] [--port 2000] [--out _out/first_steps]
"""
import argparse
import os
import sys

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))   # run from a source checkout
import valdrada  # noqa: E402
from valdrada import Location, Rotation, Transform, WeatherParameters  # noqa: E402

LENOX_125 = (40.80776, -73.94549)          # W 125th St & Lenox Ave, Harlem


def main():
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("--host", default="127.0.0.1")
    ap.add_argument("--port", type=int, default=2000)
    ap.add_argument("--out", default="_out/first_steps")
    a = ap.parse_args()

    client = valdrada.Client(a.host, a.port)       # retries until the server accepts the connection
    client.set_timeout(120.0)
    world = client.get_world()                       # waits until the simulator has booted
    info = client.get_server_info()
    print(f"server {client.get_server_version()}, {info['resolution'][0]}x{info['resolution'][1]}, {info['gpu']}")

    # synchronous mode: the world advances one fixed step per world.tick() and waits in between
    original, original_weather = world.get_settings(), world.get_weather()
    world.apply_settings(valdrada.WorldSettings(synchronous_mode=True, fixed_delta_seconds=0.05))

    # the city streams in around the spectator: move it to the intersection and wait for the tiles
    m = world.get_map()
    here = m.geolocation_to_location(*LENOX_125)
    world.get_spectator().set_transform(Transform(here + Location(0, 0, 30)))
    print("loaded:", world.wait_until_loaded())
    junction = m.get_junctions(center=here, radius=40)[0]
    print(junction, "arm headings:", sorted({round(arm.heading) for arm in junction.arms}))

    # an RGB camera at eye height on the south-west corner, looking up Lenox Ave
    bp = world.get_blueprint_library().find("sensor.camera.rgb")
    bp.set_attribute("fov", 80)
    camera = world.spawn_actor(bp, Transform(junction.location + Location(-22, -20, 1.8), Rotation(pitch=2, yaw=45)))
    latest = {}
    camera.listen(lambda image: latest.update(image=image))   # runs once per tick, before tick() returns

    conditions = [("day", WeatherParameters.Day), ("golden", WeatherParameters.Golden),
                  ("dusk", WeatherParameters.Dusk), ("night", WeatherParameters.Night),
                  ("rain", WeatherParameters.RainyDay)]
    try:
        for name, weather in conditions:
            world.set_weather(weather)
            for _ in range(60):                     # 3 s of simulated time: lighting and image history settle
                frame = world.tick()
            path = latest["image"].save_to_disk(f"{a.out}/{name}.png")
            print(f"{name:7s} frame {frame}, sim time {world.get_snapshot().timestamp:6.2f} s -> {path}")
        # rain leaves the streets wet, and they dry over a few seconds of simulated time: dry them for the next script
        camera.stop()
        world.set_weather(original_weather)
        for _ in range(200):
            world.tick()
    finally:
        camera.destroy()
        world.set_weather(original_weather)
        world.apply_settings(original)


if __name__ == "__main__":
    main()