carla-driver-interface
Both ends of NVlabs/alpasim's
egodriver.EgodriverService gRPC contract, light enough for a driving policy to depend on:
- policy side: implement
BaseDriver.drive()and serve it withrun_server()/serving()(orcarla-driver-interface serve --policy route_follower, or anypackage.module:Classthroughload_policy()); session bookkeeping, frame retention, ego history and the rig/local conversion are handled for you; - a CARLA-free runtime for tests:
testing.FakeLoopdrives any policy server over real gRPC on a straight road, rendering each declared pinhole camera (carla-driver-interface demo --driver localhost:50051).
The CARLA runtime lives in
autoware_carla_scenario:
with ego.entity=carla_driver it drives the same policy server inside a scenario. An
upstream alpasim runtime drives it too — nothing about the alpasim wire format is changed,
and carla-driver-interface alpasim packages a policy as alpasim's driver service
(Running a policy in alpasim).
Install
$ pip install carla-driver-interface
Python 3.10–3.14. Dependencies: grpcio, protobuf (4.25+), numpy, scipy, Pillow. No CARLA,
no alpasim distribution: the wire contract is vendored (proto/README.md)
with field numbers and service names unchanged.
Writing a policy
from carla_driver_interface.driver import BaseDriver, DriveContext, DriveResult
from carla_driver_interface.geometry import Pose, Trajectory
from carla_driver_interface.server import run_server
class MyPolicy(BaseDriver):
name = "my_policy"
def drive(self, ctx: DriveContext) -> DriveResult:
frame = ctx.session.latest_frame("camera_front") # .as_array() -> RGB
plan = Trajectory.empty()
for i in range(1, 41): # 4 s ahead, in the rig frame (x forward, y left)
plan.append(
ctx.time_now_us + i * 100_000, Pose.from_xyz_yaw(0.5 * i, 0.0, 0.0, 0.0)
)
return DriveResult(trajectory_in_rig=plan)
run_server(MyPolicy(), port=50051)
Every sensor reaches a policy the same way: as a frame in session.frame_history
(CameraFrame, LidarFrame), announced through on_frame, each with as_array().
Beyond alpasim's cameras, a policy run by autoware_carla_scenario gets LiDAR sweeps (rig
frame) that way and, with map_dir set to its copy of the runtime's driver.map_dir, the
world's map as files (ctx.map) and every traffic light resolved into that map's own
elements (ctx.stop_lines(), carla_driver_interface.hdmap). CARLA ground truth arrives
in ctx.renderer_data (None when an alpasim runtime drives the policy).
A policy can also work the ego's turn indicators: DriveResult(..., turn_indicator=TurnIndicator.ENABLE_LEFT)
(carla_driver_interface.driver.TurnIndicator). The values are Autoware's
TurnIndicatorsCommand ones (DISABLE = 1, ENABLE_LEFT = 2, ENABLE_RIGHT = 3), so a
classifier over Autoware's recorded signal answers TurnIndicator(class_index + 1). The
egodriver contract carries only the plan, so the server also serves
carla_driver.v0.CarlaVehicleService on the same port: after each drive a CARLA runtime
fetches the policy's VehicleCommand with get_vehicle_command. autoware_carla_scenario
lights the ego's indicators with it and holds them until a later step sets them again. The default,
NO_COMMAND, changes nothing -- what a policy that never sets it, or predates it, sends.
An upstream alpasim runtime has nowhere to show them.
A camera's mount is session.cameras[logical_id].rig_to_camera: the pose of its
optical frame in the rig (x right, y down, z along the optical axis), as alpasim's
renderers declare it, whichever runtime drives the policy. geometry.optical_to_body
gives the camera body's pose (x along the optical axis, y left, z up).
Testing a policy without CARLA
$ carla-driver-interface serve --policy route_follower --port 50051 &
$ carla-driver-interface demo --driver localhost:50051 --steps 200
or, in a test, FakeLoop("localhost:50051", [FakeCamera()]) from
carla_driver_interface.testing (a context manager; .run(steps) returns a
LoopResult). The ego follows the returned plan exactly, so what is tested is the policy
and the wire, not a controller.
Running a policy in alpasim
alpasim runs its driver as a container its wizard starts, and its runtime drives it over
the same egodriver.EgodriverService. Three commands take a policy there.
1. Serve in alpasim mode. serve --mode alpasim serves the policy as before, except
for the two opaque bytes fields the contract leaves to each runtime, and what a client
that declares nothing is taken to speak:
--mode carla (default) |
--mode alpasim |
|
|---|---|---|
DriveRequest.renderer_data |
read as CarlaRendererData (ctx.renderer_data, ctx.map, LiDAR) |
never parsed: it is alpasim's renderer's own payload; ctx.renderer_data is None |
DriveResponse.debug_info.unstructured_debug_info |
a CarlaDriveDebugInfo |
the pickled dict alpasim's evaluation unpickles (policy_name, inference_seconds, scalars), as alpasim's own driver answers |
| a client that declares no contract revision (Versioning) | speaks revision 1: its cameras are translated | speaks revision 2, alpasim's own convention |
Everything else (sessions, frames, ego history, the rig/local conversion) is the same, and
so is a policy: BaseDriver does not change. SIGTERM stops the server cleanly in either
mode, so docker stop does too. Serve alpasim in alpasim mode: in carla mode its
cameras would be taken as revision 1 and turned into the wrong frame (the server logs
that it translated them).
2. Build an image.
$ carla-driver-interface alpasim build-image --tag my-policy:0.1 \
--install ./my_policy --policy my_policy.driver:MyPolicy \
--policy-arg cruise_speed_mps=8
installs carla-driver-interface (this version from PyPI, or --carla-driver-interface PATH for a checkout) and every --install requirement (a name, a URL, or a local
project directory or wheel, copied into the build context) with one pip install on
--base-image (default python:3.12-slim; any image with python, pip and bash).
The policy and its --policy-args are baked in as the image's defaults
(CARLA_DRIVER_INTERFACE_POLICY, CARLA_DRIVER_INTERFACE_POLICY_ARGS), its module is
imported at build time (--no-check-policy to skip), and the image serves alpasim mode on
port 50051 when run by itself. --apt adds Debian packages, --no-build writes the
context and its Dockerfile without building, and --docker-arg passes arguments to
docker build.
3. Hand it to alpasim's wizard.
$ carla-driver-interface alpasim wizard-config --image my-policy:0.1 --out ./alpasim-conf
$ cd alpasim && uv run alpasim_wizard deploy=local topology=1gpu \
driver=carla_driver_interface 'hydra.searchpath=[file:///abs/path/to/alpasim-conf]' \
wizard.log_dir=$PWD/run
writes the config group driver=carla_driver_interface (--name to change it): the
image as alpasim's driver service (services.driver, an external image), started with
serve --mode alpasim on the port the wizard assigns, logging to
<log_dir>/driver/. --policy and --policy-arg override the image's defaults,
--volume and --env add mounts (weights, say) and variables, and --camera ID[:WxH][@HZ] replaces alpasim's default cameras. --pinhole ID:HFOV[@X,Y,Z[,ROLL,PITCH,YAW]]
has the renderer render camera ID through an undistorted pinhole model instead
(runtime.extra_cameras), mounted in the rig frame (x forward, y left, z up, from the
ground below the rear axle; m and degrees) and at its --camera resolution; the recorded
vehicle's hood mask is then turned off. With NRE, ID must be one of the scene's cameras.
Everything else -- renderer, physics, controller, scenes, evaluation -- is alpasim's,
unchanged.
For example, VisionPilot (vision_pilot.driver.VisionPilotDriver) on its own camera, a
1920x1280, 50 degree pinhole at 10 Hz:
$ carla-driver-interface alpasim wizard-config --name vision_pilot \
--image vision-pilot-alpasim:0.1 --out ./alpasim-conf \
--volume /path/to/weights:/mnt/weights:ro \
--camera camera_front_wide_120fov:1920x1280@10 \
--pinhole camera_front_wide_120fov:50@2.969,-0.0243,2.116,-0.10,0.11,0.23
Successor to carla-driver-interface 0.1.0
This package replaces the previous carla-driver-interface 0.1.0 of this repository,
which had two halves: a driver built on the alpasim-grpc distribution (Python 3.11–3.12
only), and a CARLA runtime (CarlaRuntime, CarlaWorldAdapter, FakeWorld,
carla-driver-interface run). From 1.0.0:
- the driver half is this package. It was developed as
autoware-carla-egodriver, a workspace member of autoware_carla_scenario, and moves here under the old name so policies and scenario packages version independently; - the runtime half is autoware_carla_scenario's
ego.entity=carla_driver, which owns the CARLA world, the ego and the traffic, and drives the policy over the same contract.testing.FakeLoopreplacesFakeWorldfor CARLA-free tests.
Migrating from 0.1.0
Policies port by changing imports; BaseDriver, DriveContext, DriveResult,
SessionState, CameraFrame, Pose and Trajectory keep their names and fields.
| 0.1.0 | 1.0.0 |
|---|---|
carla_driver_interface.driver.base (BaseDriver, DriveContext, ...) |
carla_driver_interface.driver |
carla_driver_interface.driver.server (run_server, serving, build_server) |
carla_driver_interface.server |
carla_driver_interface.driver.service.CarlaEgodriverServicer |
carla_driver_interface.service.EgodriverServicer |
carla_driver_interface.driver.policies |
carla_driver_interface.policies (plus load_policy) |
carla_driver_interface.grpc_api (messages, stubs) |
carla_driver_interface.protocol |
carla_driver_interface.grpc_api.extension (pack_renderer_data, unpack_debug_info, ...) |
carla_driver_interface.protocol |
carla_driver_interface.grpc_api.API_VERSION_MESSAGE |
carla_driver_interface.protocol.ALPASIM_API_VERSION |
alpasim_grpc.v0.{common,egodriver,sensorsim}_pb2 |
carla_driver_interface.protocol.{common,egodriver,sensorsim,carla_driver}_pb2 |
carla_driver_interface.geometry (Pose, Trajectory), .polyline |
unchanged |
geometry.quat_mul, yaw_to_quat_xyzw, quat_to_yaw, ... |
Pose methods, or scipy.spatial.transform.Rotation |
BaseDriver.on_image(session, frame) |
BaseDriver.on_frame(session, frame) (cameras and LiDAR) |
carla_driver_interface.fakes.FakeWorld |
carla_driver_interface.testing.FakeLoop |
carla_driver_interface.runtime (CarlaRuntime, ...), carla-driver-interface run |
autoware_carla_scenario, ego.entity=carla_driver |
carla_driver_interface.compat, carla-driver-interface compat-report |
removed |
alpasim_grpc.v0.runtime_pb2 (RolloutSpec, SimulationReturn, ...) is not vendored: it
is the runtime's own contract, not the egodriver one. Messages not re-exported by name in
protocol (CarlaDriveSessionInfo, CarlaCompatReport, ...) remain available from
protocol.carla_driver_pb2.
Versioning
carla-driver-interface follows semantic versioning on its own,
independently of autoware_carla_scenario or any policy. Its major version is tied to the
contract revision (carla_driver_interface.contract): what the wire means beyond the
messages alpasim's proto fixes.
| Revision | Releases | AvailableCamera.rig_to_camera |
|---|---|---|
| 1 | 1.x | the camera body in the rig (x along the optical axis, y left, z up) |
| 2 | 2.x | the camera's optical frame in the rig (x right, y down, z along the optical axis), as alpasim declares it |
Each side declares its revision in gRPC metadata (carla-driver-interface-contract): a
server in its answer to get_version, a client with start_session. A side that declares
none is taken to speak revision 1, except an alpasim runtime (--mode alpasim), which
speaks alpasim's own convention, revision 2. A 2.x server translates whatever its client
speaks, and a 2.x client (contract.negotiate, as FakeLoop does) declares its cameras
in whatever its server speaks. A policy always sees revision 2. So releases pair across
processes, where no package range can see both sides:
| Client \ policy server | 1.x | 2.x |
|---|---|---|
| autoware_carla_scenario 4.x (carla-driver-interface 1.x) | yes | yes (translated by the server) |
| a carla-driver-interface 2.x client | yes (translated by the client) | yes |
| an alpasim runtime | no: 1.x reads alpasim's cameras as camera bodies | yes |
Within one environment, depend on a range of one major
(carla-driver-interface>=2.0,<3), not an exact pin.
- patch: fixes that change neither the wire nor the Python API;
- minor: additions — new fields or messages in the
carla_driverextension (old peers ignore them), new Python API; - major: anything that breaks a peer or a policy — a new contract revision, a changed or removed field number, a renamed service, a removed Python API. A new revision comes with the translation from the previous one, so peers of adjacent majors still pair.
The vendored alpasim contract is never edited; following a newer alpasim revision is a minor release when it is wire-compatible and a major one when it is not.
Development
$ uv sync --group dev
$ uv run ruff check . && uv run ruff format --check .
$ uv run mypy src tests scripts
$ uv run pytest -q
The generated protobuf modules in src/carla_driver_interface/_proto are committed;
regenerate them with uv run python scripts/compile_protos.py (on Python 3.10–3.12, see
proto/README.md). --check, also run by the test suite, verifies
they are current.
Releasing
Releases are cut by merging, and go to
PyPI from
.github/workflows/release.yml
through Trusted Publishing — no API token is stored anywhere.
Every PR carries exactly one of the labels bump patch, bump minor or bump major
(the Check Version Bump Label check fails otherwise), chosen by the
versioning rules above. Do not edit version in pyproject.toml by hand.
When the PR is merged to main, the release workflow:
- bumps
versioninpyproject.tomlby the label, re-locks (uv lock) and pushes the commitchore: bump version to X.Y.Ztomain; - tags it
vX.Y.Zand creates the GitHub Release; - builds the sdist and wheel (
uv build), checks them withtwine check --strict, installs the wheel into a clean virtual environment on every supported Python (3.10–3.14) and runscarla-driver-interface --help; - publishes to PyPI.
A merge without a bump label releases nothing.
Re-running a failed release. If a step after the bump failed (the tag, the Release,
PyPI), do not re-run the push run — it would bump again. Run the workflow by hand instead
(Actions → Release → Run workflow, on main) with the version it bumped to, e.g.
1.2.3. It builds from that version's tag, or else from its
chore: bump version to 1.2.3 commit, never from a later main, and reuses an existing
tag or Release; files already on PyPI are skipped.
The successor keeps the predecessor's version line: it starts from 0.1.0, and the PR
that introduces it is merged with bump major, which releases 1.0.0 like any other
release.
Pushing to main. The bump commit and the tag are pushed with GITHUB_TOKEN, which
works while main has no branch protection rules. If main gets a ruleset whose bypass
list does not include github-actions[bot], add a GH_PAT repository secret (a token
that may push to main and create tags); the workflow uses it whenever it exists.
One-time PyPI setup. PyPI needs a (pending) trusted publisher for the project
carla-driver-interface (the distribution name in pyproject.toml) with owner
hakuturu583, repository carla_driver_interface, workflow release.yml and environment
pypi (PyPI → Your account → Publishing), and this repository needs a pypi environment
(Settings → Environments). Until then the publish job fails and the built distributions
remain available as the run's pypi-dist artifact.
License
Apache-2.0. The vendored alpasim protos are Apache-2.0 as well
(proto/LICENSE.alpasim).
Metadata
Release files for carla-driver-interface 2.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| carla_driver_interface-2.1.0.tar.gz | 74.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| carla_driver_interface-2.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 156.7 kB
Release files / carla_driver_interface-2.1.0.tar.gz
| Download URL | carla_driver_interface-2.1.0.tar.gz |
|---|---|
| Size | 74.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7506de3b9954b45c2f4d13ece61d6d0da7637743fa4ea640425aaef7a884c2c0
|
|
BLAKE2b-256 checksum How to use checksums |
af4049345b900703409fa3ca2d4998074f431825936bf1bcad9c590647b657fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / carla_driver_interface-2.1.0-py3-none-any.whl
| Download URL | carla_driver_interface-2.1.0-py3-none-any.whl |
|---|---|
| Size | 82.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
675c681659aec8f6219fa82f90dd1baa2de82dc835bcfb6b78cb1495dff1b626
|
|
BLAKE2b-256 checksum How to use checksums |
cf2118923f5c4b9dab3ad4c60877ee1a9f6985419cb80d9f4ecdfd6f4a5bb742
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logSecurity for carla-driver-interface 2.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for carla_driver_interface-2.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.