Skip to content

OpenScenarioXML Export#421

Open
Eric-Vin wants to merge 16 commits into
mainfrom
OpenScenarioXML
Open

OpenScenarioXML Export#421
Eric-Vin wants to merge 16 commits into
mainfrom
OpenScenarioXML

Conversation

@Eric-Vin

@Eric-Vin Eric-Vin commented Nov 18, 2025

Copy link
Copy Markdown
Collaborator

Description

Added experimental functionality to export the result of a Scenic simulation to OpenScenarioXML.

Issue Link

#334 #32

Checklist

  • I have tested the changes locally via pytest and/or other means
  • I have added or updated relevant documentation
  • I have autoformatted the code with black and isort
  • I have added test cases (if applicable)

Additional Notes

N/A

@Eric-Vin Eric-Vin marked this pull request as draft November 18, 2025 23:11
@codecov

codecov Bot commented Nov 25, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 17.56757% with 61 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.97%. Comparing base (4be87bd) to head (96b3ee1).

Files with missing lines Patch % Lines
src/scenic/core/serialization.py 6.15% 61 Missing ⚠️

❌ Your patch check has failed because the patch coverage (17.56%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #421      +/-   ##
==========================================
- Coverage   89.85%   87.97%   -1.89%     
==========================================
  Files          48       48              
  Lines       13346    13419      +73     
==========================================
- Hits        11992    11805     -187     
- Misses       1354     1614     +260     
Files with missing lines Coverage Δ
src/scenic/core/simulators.py 83.76% <100.00%> (-12.08%) ⬇️
src/scenic/core/serialization.py 34.79% <6.15%> (-56.55%) ⬇️

... and 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Eric-Vin Eric-Vin linked an issue Feb 21, 2026 that may be closed by this pull request
@Eric-Vin Eric-Vin changed the title [WIP] OpenScenarioXML Export OpenScenarioXML Export May 29, 2026
@Eric-Vin Eric-Vin marked this pull request as ready for review May 29, 2026 03:39
@Eric-Vin Eric-Vin requested a review from dfremont May 29, 2026 03:39
@Eric-Vin

Eric-Vin commented May 29, 2026

Copy link
Copy Markdown
Collaborator Author

An example scenario of a pedestrian crossing the road, occluded by a car, simulated in Metadrive and then exported to XOSC and replayed in ESMINI.
Metadrive GIF:
pedestrianJaywalking_12

ESMINI Replay MP4:
https://github.com/user-attachments/assets/e7032e35-11f1-49e3-8634-6703ead6e9ba

Comment thread docs/api.rst
OpenScenarioXML Export
----------------------

Scenic provides experimental support for exporting completed simulations via `toOpenScenario`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function lives in an internal module whose documentation says "The functions in this module usually do not need to be used directly.", so I think it's confusing to link to it here. How about making a method Scenario.toOpenScenario parallel to Scenario.simulationToBytes?

Comment on lines +401 to +403
simulationResult,
scenario,
scene,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless there's a particular reason to separate these, I think it would be simpler to just take a Simulation as input (which has a reference to the underlying Scene) and have this be a method on Scenario.

"No `mapPath` provided and scenario does not have a `map` parameter defined."
)
mapPath = (
mapPath if mapPath is not None else os.path.abspath(scenario.params["map"])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can mapPath not be None here?

# Create entitities
entities = xosc.Entities()
xosc_objects = {}
for obj_i, obj in enumerate(scene.objects):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably this will break for dynamically-created objects. If XOSC doesn't support them we could raise an exception if they are present in the simulation, but at some point we should try to handle the case where all the objects are created dynamically in the first timestep.

entities = xosc.Entities()
xosc_objects = {}
for obj_i, obj in enumerate(scene.objects):
if hasattr(obj, "isVehicle") and obj.isVehicle:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if hasattr(obj, "isVehicle") and obj.isVehicle:
if getattr(obj, "isVehicle", False):

xosc_objects = {}
for obj_i, obj in enumerate(scene.objects):
if hasattr(obj, "isVehicle") and obj.isVehicle:
obj_name = obj.name if hasattr(obj, "name") else f"Vehicle{obj_i}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
obj_name = obj.name if hasattr(obj, "name") else f"Vehicle{obj_i}"
obj_name = getattr(obj, "name", f"Vehicle{obj_i}")

Comment on lines +485 to +486
elif hasattr(obj, "isPedestrian") and obj.isPedestrian:
obj_name = obj.name if hasattr(obj, "name") else f"Pedestrian{obj_i}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can simplify as above.

)
xosc_obj = xosc.Pedestrian(
name=obj_name,
mass=obj.mass,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is mass required for pedestrians but not for cars?

"""
return tuple(obj.position for obj in self.objects)

class SimulationState(tuple):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should update the docstring now that the positions and orientations attributes are available. While we're at it we should probably discourage overriding this method, except possibly by adding extra attributes to SimulationState.

obj, states.positions[obj_i], states.orientations[obj_i].yaw
)
)
action_times.append(simulationResult.timestep * t)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SimulationResult doesn't have a timestep attribute, so I don't understand how your test can be passing.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see, it's actually a Simulation object. Better change the name of the parameter!

times the length of the vehicle.
maxSteeringAngle: The maximum steering angle of the vehicle. The full steering range would be
two times this value, going from (-maxSteeringAngle, maxSteeringAngle). Default value
30 degrees.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean 35 degrees, as in the code?

wheelDiameter: The diameter of the *entire* wheel (including the tire). Default value is 0.7 meters.
trackWidth: Distance between the vehicle's wheels when pointed straight ahead. Default value
is 0.85 times the width of the vehicle.
groundClearance: Default value is half the wheel diameter.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't actually say what this means.

toOpenScenario,
)
from scenic.core.simulators import DivergenceError, DummySimulator
from tests.simulators.metadrive.test_metadrive import getMetadriveSimulator

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the Newtonian simulator instead? I think this would be the first core test which uses a non-built-in simulator, and we should be able to do this test without the optional MetaDrive dependency.

facing 90 deg relative to parkedCar,
with behavior CrossRoad()

terminate after 30 seconds

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make the simulation much shorter, just a few steps, so that the test runs faster. I also don't think there's any particular need to use a complex scenario here, and the test would be easier to read if the scenario just had the minimum stuff necessary (say a couple cars, one with FollowLaneBehavior, plus a pedestrian with an initial velocity).

@dfremont dfremont left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, just one issue (how to handle/ban dynamically spawned objects) and a bunch of minor comments above.


for obj, xosc_obj in xosc_objects.items():
obj_init_action = xosc.TeleportAction(
pos_to_WorldPosition(obj, obj.position, obj.yaw)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should reference obj.heading instead of obj.yaw.

for t, states in enumerate(simulationResult.trajectory):
action_positions.append(
pos_to_WorldPosition(
obj, states.positions[obj_i], states.orientations[obj_i].yaw

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should reference states.orientations[obj_i].heading instead of states.orientations[obj_i].yaw

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Conversion between scenic and openscenario

2 participants