Design: CARLA × Autoware integration architecture for closed-loop evaluation #7277
Replies: 9 comments 3 replies
Reference: measured latency of the CARLA client API path (why the Renderer publishes sensor topics directly over DDS)To back the design decision of having the Renderer publish sensor topics directly over DDS — rather than pulling sensor data through the CARLA client API and re-publishing it — we benchmarked the sensor-data latency of the CARLA 0.10.0 client API itself. Setup: CARLA 0.10.0 (UE 5.5, official
For scale: a bare RPC round-trip is ~0.03 ms and Two takeaways relevant to this architecture:
Benchmark script (RPC RTT / |
|
related discussion: https://github.com/orgs/autowarefoundation/discussions/7133 |
|
How simulating sensor kit information to be managed? Is possible to integrate simulating sensor kit information to one place? |
|
@mitsudome-r As far as I understand, this discussion was addressed by the Software WG, and approval was granted on the understanding that the details of the file format would be worked out as development progresses. |
|
autoware-carla-scenario integration wiill be integrated into Autoware soon! |
Proposal: donate the toolchain repository to AWF — and split it in two while doing soAs a follow-up to this design, I would like to donate What the repository contains todayIt is currently a single
Why they should be separated before/at contributionKeeping both in one repository was convenient while bootstrapping, but it is confusing from the outside — the repository name advertises a map converter, while half of it is a closed-loop scenario framework. Concretely:
Proposed contribution shapeDonate as two repositories under
Notes on the mechanics:
Feedback on the proposed boundary (and on which AWF WG should own each repository) is very welcome before I start the split. |
Proposal (1/3): donate
|
| Change | Why we need it |
|---|---|
Handle an omitted <link> in the contact-point check (#5) |
<link> is minOccurs="0" in the schema (OpenDRIVE §10.3, "Isolated roads may omit this element"). The checker calls road.find("link").find(...) unconditionally and aborts with AttributeError on a spec-legal map — which our converter emits. |
Use road_belongs_to_junction() instead of @junction == 1 (#6) |
@junction is -1 outside junctions and the junction id inside one, so comparing it to 1 only skips roads that happen to belong to junction 1. Every other junction road is falsely reported. |
Memoise getpath() (#8) |
All 42 issue-location call sites go through lxml's xmlGetNodePath, which walks the preceding siblings — i.e. the roads — at each level. Any checker reporting per road is therefore quadratic in map size, which is what we hit on city-scale maps. |
| Relax the numpy upper bound (#9) | numpy = "^1.26.0" excludes numpy 2.x, while the converter locks numpy 2.2.6. Same intent as upstream asam-ev/qc-opendrive#138. This is the commit currently pinned by rev. |
Drop an unused numpy.ma.core import (#7) |
Trivial, but it is the base of the stacked PRs above. |
In other words, the fork is not a product fork — it is the set of upstream bugs that a Lanelet2-derived OpenDRIVE map happens to trigger.
Upstream-first, and the fork is meant to shrink
The goal is not to maintain a permanent downstream copy of a well-run ASAM project. The policy I would propose for the AWF-owned fork:
- Everything goes to
asam-evfirst; the fork only carries what is still in review there. mainof the fork stays a fast-forward ofasam-ev/main— patches live in branches that get dropped as upstream merges them.- The converter pins the fork by
revonly while a fix is pending, and moves back to the upstream PyPI release (asam-qc-opendrive>=1.0.0) as soon as it can.
If upstream takes all of the patches, the fork ends up holding nothing and can simply be archived. That is the success case.
Proposed shape
- Create
autowarefoundation/qc-opendriveas a GitHub fork ofasam-ev/qc-opendrive(not a fresh import), so the fork relationship — and therefore one-click PRs to upstream — is preserved. License stays MPL-2.0; no relicensing is involved or needed. - Push the five branches above to it and reopen the PRs there, keeping the review history readable.
- Switch
autoware_lanelet2_to_opendriveto pin the AWF fork at the same revision, so the donated converter has no tier4 remote left. - Ownership: the same WG that ends up owning
autoware_lanelet2_to_opendrive, since the people who review a map-conversion change are the ones who care whether the checker accepts its output.
Feedback welcome — in particular on whether AWF prefers hosting such upstream-tracking forks at all, versus vendoring the patches in the converter until ASAM merges them.
Proposal (2/3): donate
|
| Change | What goes wrong without it |
|---|---|
| Reserve the issue ids already used by a loaded report (#10, merged) | load_from_file() leaves the id counter at 0, so an issue registered on a loaded report gets an id the report already uses. _get_issue() then returns whichever duplicate it finds first, and add_*_location() / add_domain_specific_info() silently mutate the wrong issue. No exception — the written report is just wrong. |
| Look issues up through an index kept by their checker (#11, merged) | _get_issue() scans every issue of the checker, so adding a location to each issue is O(N²). On a city-scale map that is the difference between a report and a hang. |
| Keep a rejected issue out of the report (#7) | register_issue() appends the issue before validating, so an issue the validators reject stays on the checker: it is written to the report, and every later registration on that checker fails with the same exception. |
| Keep a rejected status off the checker (#8) | Same shape in set_checker_status() — pydantic validates after assignment, so e.g. a SKIPPED status on a checker that holds issues sticks, and the report is written with status="skipped" next to <Issue> elements, which the schema forbids. |
| Validate only the issue being registered (#9) | register_issue() re-validates every issue registered so far on each call — O(N²) again. The PR adds a guard test pinning the validators, so the optimisation cannot silently stop being equivalent. |
Two of these are correctness bugs that produce a silently wrong report, and three are the reason a QC run over a large converted map is slow enough to discourage running it at all. Both categories matter for using QC as an acceptance gate in CI rather than as something a human runs occasionally.
Current dependency situation
autoware_lanelet2_to_opendrive resolves asam-qc-baselib 1.1.0 from PyPI today and pins only the qc-opendrive fork by rev. Pinning the fixes above means pointing at the fork as well — which is precisely why it should not be a tier4 repository by then.
Proposed shape
Identical to the previous comment, and for the same reasons:
- Create
autowarefoundation/qc-baselib-pyas a GitHub fork ofasam-ev/qc-baselib-py, MPL-2.0 unchanged, so upstream PRs stay one click away. - Upstream-first:
maintracksasam-ev/main, the fork only carries what is pending review upstream, and patches are dropped as they are merged there. The fork shrinking to nothing is the intended end state. - Same owning WG as
qc-opendriveandautoware_lanelet2_to_opendrive— the three are one toolchain and splitting their review across WGs would only add latency.
If AWF would rather not host upstream-tracking forks at all, the alternative is to vendor these patches inside the converter until ASAM merges them; I think a fork is the cleaner and more honest option, but I am happy to follow whatever the foundation prefers.
Proposal (3/3): support
|
| Library | What it is |
|---|---|
typesafe_carla (MIT, PyPI typesafe-carla 0.8.5) |
A CARLA-API-compatible client that is statically type-checked. Code is written against the same API shape as the carla package (Client / World / Actor / Sensor / command.* …), but it is compiled by Codon and calls LibCarla through a narrow C ABI — CPython and the carla package are not in the path. Passing a Transform where a VehicleControl is expected is a compile error, not a failure 40 s into a scenario run. The surface is generated from binding specs (252 C ABI functions) validated against LibCarla with libclang, so the coverage is mechanical rather than hand-maintained. |
carla_canvas (MIT, PyPI carla-canvas 0.2.1) |
The abstraction of the rendering layer implemented inside typesafe_carla. typesafe_carla has a sensor-source ABI: a plugin can supply the measurements and the ROS 2 output of the sensors it claims, instead of the CARLA server. carla_canvas is that plugin — it hands the claimed sensors to a renderer service over gRPC. Which sensors are swapped and which renderer is used is a configuration file (CARLA_CANVAS_CONFIG); the program is unchanged, listen() / poll() / wait_for_data() / enable_for_ros() keep working, measurements arrive as LibCarla's own Image / LidarMeasurement, and the ROS 2 topics keep CARLA's names and types. See the repositories for the details. |
carla_canvas is exactly the Sensor Simulation Layer boundary drawn in the design above, made concrete: a 3DGS renderer, or a world model such as cosmos3-canvas (NVIDIA Cosmos3-Nano-Sim-Auto generating the ego's cameras and LiDAR closed-loop), becomes a renderer behind the CARLA API — with no change to autoware_carla_interface and no change to the topics Autoware subscribes to.
Why support it in Autoware
- It is an installable dependency. Both libraries are released on PyPI, so a rosdep key resolves them and
autoware_carla_interfacecan finally declare its CARLA client inpackage.xml. Today that package declares no CARLA client at all, and the README asks the user to install a third-party wheel or to add an egg file toPYTHONPATHby hand — a step that cannot be reproduced byrosdep installand that every CI or container image has to special-case. - Type checking where it pays off. The bridge and the scenarios are the parts where an API mistake surfaces late, in the middle of a long simulation run, and often as a confusing CARLA-side error. Catching it at build time is worth more here than in ordinary application code.
- Renderer swapping becomes configuration, not a fork. The alternative to
carla_canvasis patching the sensor path of the bridge per renderer, which is what we would otherwise end up doing for every new sensor-simulation backend.
Constraint: UE5 only, so UE4 needs a fallback
typesafe_carla targets LibCarla of the UE5 line (0.10.0, ue5-dev, ue58-dev). The combination documented for autoware_carla_interface today is still CARLA 0.9.15 (UE4), and that must keep working for everyone using it.
So the integration shape I would propose is:
- Put a thin client abstraction in
autoware_carla_interfacewith two backends:typesafe_carla(+carla_canvaswhen a canvas configuration is given) for UE5-line servers, and the existingcarlaPython package for the UE4 line. - Select the backend explicitly (launch parameter, defaulting to what the connected server reports), and fall back to the existing path when
typesafe_carlais not installed or the server is 0.9.x. No current user loses their setup, and the UE4 code path stays exactly as it is. - Only once that fallback exists does the new client get wired in as the default for the UE5 line.
On upstreaming to CARLA
The long-term goal is not to maintain a parallel client forever. CARLA itself is currently in feature freeze for the UE5.8 release, so proposing this upstream right now is not realistic — nothing would move until the freeze lifts. Once it does, I would like to spend the time to propose integrating these capabilities into CARLA upstream: a typed client surface, and the renderer/sensor-source plugin boundary that carla_canvas relies on. Until then the two libraries live outside CARLA, which is also the honest place for them while the ABI is still moving.
Feedback welcome on where the client abstraction should sit (inside autoware_carla_interface, or as its own package that both the bridge and autoware_carla_scenario use), and on whether AWF would want these two repositories under the foundation as well, or prefer to consume them from PyPI as ordinary upstream dependencies.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
CARLA × Autoware integration design
Sharing a proposal of integration architecture for using CARLA as a closed-loop evaluation / digital-twin backend for Autoware. The design decomposes the integration into three layers around CARLA as the simulation core. (Legend: solid arrows = runtime communication, dashed arrows = file/data handoff.)
1. Sensor Simulation Layer
Generates photorealistic sensor input from the CARLA scene and feeds it to the Autoware under test.
2. Simulation Core
autoware_carla_interfacemediates between Autoware and CARLA: it forwards sensor data (DDS) into Autoware and returns control commands (DDS) back, plus initialization / pose sync.3. Traffic Environment Layer
Reproduces realistic surrounding traffic.
Note 1 — Scenarios as a Python package
Scenarios are authored and managed as a Python package rather than ad-hoc scripts. Benefits:
tier4/autoware_lanelet2_to_opendrive—autoware_carla_scenario.Note 2 — Input asset format: usdz
Scene assets enter the pipeline in a USD-based
usdzarchive (a ZIP package). Representative contents:default.usda— USD root stage (World / Z-up ENU)metadata.yaml— uuid / scene_id / versionscene.json— scene index (coordinate frames, ECEF, map/CARLA references)tileset.json— 3D Tiles (EXT_3dgs_spz)chunks/*.spz— 3DGS body (Gaussians; position/rotation/scale/color(SH)/opacity, Niantic SPZ compression)chunks/*.json.gz— LiDAR sidecar (depth/intensity)entities/<id>/*.spz— dynamic objects as individual 3DGSrig_trajectories.json/sequence_tracks.json— sensor-rig poses / dynamic-actor trajectoriesppisp.json— camera appearance correctionCommon coordinate system: right-handed Z-up ENU, geo-referenced via an ECEF anchor. (Ref:
github.com/autowarefoundation/3dgs_io,scene_usdz.py.)Why this design — AlpaSim alignment. The usdz layout is modeled after AlpaSim, so that we keep compatibility with AlpaSim as far as possible going forward. We are also keeping the ongoing AlpaSim closed-loop integration in mind — it is actively being explored in
autowarefoundation/auto_e2e#140, where an early working prototype of an AutoE2E policy running a closed-loop session in AlpaSim has already been demonstrated (renderer choice and the full RL loop are still under active discussion).Gaussian data format — open standard. The exact data format for the Gaussians packed inside usdz was not clearly defined. To resolve this, we propose packing the 3D GS data based on the emerging open standard from Khronos/OGC that adds 3D Gaussian Splats to the glTF asset standard (announcement), and carrying that standardized 3D GS representation inside the usdz package.
All reactions