Getting Started in Python#

ovrtx and ovstage are distributed on PyPI. Use uv to install both packages for the current attached-stage workflow:

uv add ovrtx ovstage

pip also works:

pip install ovrtx ovstage

ovstage is optional when maintaining standalone compatibility code. The renderer-owned scene APIs used by that mode have been deprecated since ovrtx 0.4, and scene ownership will move entirely to ovstage in a future release.

All the examples in the repository contain pyproject.toml files that are tested with uv. Python 3.10-3.13 are supported; on Windows, use Python 3.11 or newer.

If installation fails, first verify that you are using a supported Python version and that your environment can reach PyPI. If you need a specific release artifact, GitHub Releases also contain Python wheels that can be installed explicitly.

ovrtx runtime validation requires an NVIDIA RTX-capable GPU, a supported NVIDIA driver, internet access, and execution outside sandboxed environments. The minimal example downloads scene assets from S3. Supported driver versions are listed in Hardware & Driver Requirements.

To get started, first clone the repository and run the first example with uv:

git clone https://github.com/NVIDIA-Omniverse/ovrtx.git
cd ovrtx/examples/python/minimal
uv run main.py --png

The minimal example shows how to create the renderer, load an OpenUSD scene, and render a single image, copying the results back to the CPU for display.

A successful run writes _output/render.png. The output should match the reference image below.

Minimal example output

The first step from a newly built application will block for 1-2 minutes while shaders are compiled and cached.

ovrtx and ovstage Load Order#

Importing ovrtx and ovstage does not load either OpenUSD-dependent runtime. The minimal example creates ovrtx.Renderer first; renderer creation registers ovrtx’s schema paths and loads the ovrtx runtime/OpenUSD, after which ovstage.Stage loads the ovstage runtime against the same initialized OpenUSD.

If an application must create ovstage.Stage first, call ovrtx.register_schema_paths() before stage construction. That call loads only the ovrtx shared loader and publishes discovery paths; it does not initialize the renderer or OpenUSD. Creating the stage may then load the ovstage runtime/OpenUSD, and a later ovrtx.Renderer uses the paths that were already registered. ovstage.library_version() also creates a temporary native instance, so make the schema call before that probe in a stage-first process.

Minimal Example#


import argparse
import sys
from pathlib import Path

import numpy as np
import ovrtx
import ovstage
from PIL import Image

USD_URL = "https://omniverse-content-production.s3.us-west-2.amazonaws.com/Samples/Robot-OVRTX/robot-ovrtx.usda"


def main():
    parser = argparse.ArgumentParser(description="Minimal ovrtx Python example")
    parser.add_argument("--png", action="store_true", help="Save render to _output/render.png instead of displaying")
    args = parser.parse_args()

    # Create the Renderer and attach the stage that owns scene data.
    print("Creating renderer. The first run of the application will take some time as shaders are compiled and cached...", file=sys.stderr)
    renderer = ovrtx.Renderer()
    stage = ovstage.Stage("ovrtx.example.minimal")
    renderer.attach_ovstage(stage)
    print("Renderer created.", file=sys.stderr)

    print(f"Opening {USD_URL}...", file=sys.stderr)
    ordinal = 1
    ovstage.population.open_usd(stage, USD_URL, ordinal=ordinal)
    stage.advance_write_floor(ordinal, ovstage.Scope.ALL).wait()
    print("USD loaded.", file=sys.stderr)

    # Step the renderer to simulate the Camera at 60Hz
    print("Stepping renderer...", file=sys.stderr)
    products = renderer.step(
        render_products={"/Render/Camera"},
        delta_time=1.0 / 60,
        ordinal=ordinal,
    )
    print("Stepped renderer.", file=sys.stderr)

    # Get the Camera output for the step as a numpy array and display it
    print("Fetching results...", file=sys.stderr)
    for _product_name, product in products.items():
        for frame in product.frames:
            var = frame.render_vars["/Render/Camera/LdrColor"].map(device=ovrtx.Device.CPU)
            view = np.from_dlpack(var)
            pixels = view.copy()
            del view
            var.unmap()
            del var
            img = Image.fromarray(pixels)
            if args.png:
                output_dir = Path("_output")
                output_dir.mkdir(exist_ok=True)
                img.save(output_dir / "render.png")
                print(f"Saved to {output_dir / 'render.png'}", file=sys.stderr)
            else:
                img.show()
    print("Fetched results.", file=sys.stderr)

    del frame, product, products
    renderer.detach_ovstage()
    stage.destroy()
    renderer.destroy()


if __name__ == "__main__":
    main()

The example above is provided as a Python project in the examples/python/minimal directory in the repository.

Reading Simulation Time#

The products returned by step() expose the renderer’s simulation clock in seconds. Use simulation_end_time to timestamp external logs or a HUD after a completed step. You do not need to accumulate delta_time in a separate Python clock.

For example, log the simulation time from the results produced above:

import logging

logging.basicConfig(level=logging.INFO)
logging.info("Simulation time: %.6f s", products.simulation_end_time)

simulation_start_time and simulation_end_time describe the whole step window. Each FrameOutput has its own start_time and end_time for the sensor’s capture window. Use those frame timestamps when synchronizing individual sensor outputs.

With step_async(), read the same timestamps from the results returned by .wait().fetch(). They belong to that step even if later steps have already completed. Calling reset() with time=T sets the next step’s simulation start time to T.

Next Steps#