scenariogeneration

repository·main·Indexed 18 days ago

https://github.com/pyoscx/scenariogeneration

A Python library suite for programmatically generating ASAM OpenSCENARIO (.xosc) and OpenDRIVE (.xodr) XML files. It includes the xosc module for dynamic content (supporting versions V1.0.0, V1.1.0, and V1.2.0) and the xodr module for static road networks, lanes, and signs. The package provides tools for automated road network patching, elevation calculation, junction creation via CommonJunctionCreator and DirectJunctionCreator, and integration with esmini for scenario visualization.

Tokens
8.6K
Snippets
21
Records
31
Agent score
64%

What's inside scenariogeneration

  1. Overview of scenariogeneration modules

    main

    The scenariogeneration package is a collection of libraries for generating OpenSCENARIO and OpenDRIVE XML files. It is composed of the following main components:

    • scenario_generator: The core module.
    • xosc: Subpackage for generating OpenSCENARIO files (describing dynamic content like traffic maneuvers and weather).
    • xodr: Subpackage for generating OpenDRIVE files (describing static content like roads, lanes, and signs).

    These modules can be used together to generate interlinked scenarios (OpenSCENARIO based on OpenDRIVE road networks) or used independently for specific file types.

  2. Overview of scenariogeneration modules

    main

    The scenariogeneration package is a Python tool for creating and generating ASAM OpenSCENARIO and OpenDRIVE files. It is organized into two primary modules:

    • xosc: Handles OpenSCENARIO (V1.0.0, V1.1.0, and V1.2.0). It acts as an XML generator that abstracts away complex XML hierarchies, allowing users to interact with a simplified class structure. It can also parse existing .xosc files into Python objects.
    • xodr: Handles OpenDRIVE generation.

    The package also includes ScenarioGenerator for automated generation with parametrization and supports viewing via esmini.

  3. Automatic generation of Story, Act, and ManeuverGroup in xosc

    main

    The xosc module provides a StoryBoard class that simplifies scenario creation by automatically managing top-level XML layers. For simple scenarios, you can add sub-classes directly to the StoryBoard without manually defining the full hierarchy.

    Available methods for adding sub-classes:

    • add_story
    • add_act
    • add_maneuvergroup
    • add_maneuver

    WARNING: Do not use more than one of these calls (except add_story) in a single workflow, as each call will create a new story, which is typically not the desired behavior.

  4. Use ScenarioGenerator for parametrized scenario generation

    main

    The ScenarioGenerator class acts as a glue to parameterize and generate connected OpenSCENARIO (xosc) and OpenDRIVE (xodr) XML files. This is useful for large-scale, parametrized simulations.

    To use it, create a class that inherits from ScenarioGenerator and initialize it. You must implement two key methods:

    1. A road method that returns an xodr.OpenDrive object.
    2. A scenario method that returns a xosc.Scenario object.

    To link the generated road to the scenario, use xosc.RoadNetwork(self.road_file) within your scenario method.

    Finally, call the .generate() method to produce all permutations of the defined parameters.

    class MyScenario(ScenarioGenerator):
        def road(self):
            # Return an xodr.OpenDrive object
            return my_open_drive_object
    
        def scenario(self):
            # Return a xosc.Scenario object
            # Link the road using the generated road file path
            return xosc.Scenario(road_network=xosc.RoadNetwork(self.road_file))
    
    # To generate all permutations:
    my_gen = MyScenario()
    my_gen.generate()
  5. Install scenariogeneration via pip

    main

    Install the scenariogeneration package using pip to begin generating OpenSCENARIO (.xosc) and OpenDRIVE (.xodr) XML files.

    Prerequisites:

    • Python >3.6.9 (Tested).
    • Note: For Python versions <3.7, the order of certain XML elements might not be consistent between generations.
    pip install scenariogeneration
  6. Create direct junctions with DirectJunctionCreator

    main

    The DirectJunctionCreator is used for junctions that do not require the generation of internal junction roads.

    Workflow:

    1. Define Roads: Create your roads as usual.
    2. Link to Junction: Use road.add_successor(xodr.ElementType.junction, junction_id) or road.add_predecessor(xodr.ElementType.junction, junction_id) to connect roads to the junction ID.
    3. Add Connections: Use direct_junction.add_connection(incoming_road=..., linked_road=...). To connect specific lanes, provide incoming_lane_id and linked_lane_id.
    4. Finalize: Add roads and the junction creator to the OpenDrive object and call adjust_roads_and_lanes().
    from scenariogeneration import xodr
    
    junction_id = 100
    direct_junction = xodr.DirectJunctionCreator(junction_id, 'my_direct_junction')
    
    first_road = xodr.create_road([xodr.Line(300)], id=1, left_lanes=3, right_lanes=4)
    continuation_road = xodr.create_road([xodr.Line(300)], id=2, left_lanes=3, right_lanes=3)
    off_ramp = xodr.create_road([xodr.Spiral(-0.00001, -0.02, length=150)], id=3, left_lanes=0, right_lanes=1)
    
    # Connect roads to the junction
    first_road.add_successor(xodr.ElementType.junction, junction_id)
    continuation_road.add_predecessor(xodr.ElementType.junction, junction_id)
    off_ramp.add_predecessor(xodr.ElementType.junction, junction_id)
    
    # Define connections
    direct_junction.add_connection(incoming_road=first_road, linked_road=continuation_road)
    direct_junction.add_connection(incoming_road=first_road, linked_road=off_ramp, incoming_lane_id=-4, linked_lane_id=-1)
    
    # Finalize OpenDrive
    odr = xodr.OpenDrive('myroad')
    odr.add_road(first_road)
    odr.add_road(continuation_road)
    odr.add_road(off_ramp)
    odr.add_junction_creator(direct_junction)
    odr.adjust_roads_and_lanes()
  7. Configure parameter permutations in ScenarioGenerator

    main

    You can control how many scenarios are generated by defining self.parameters in your ScenarioGenerator subclass. There are three ways to define parameters:

    1. Dict containing lists: Generates all permutations of the inputs. Example: {'speed': [10, 20], 'curvature': [0.1, 0.2]} yields 4 scenarios.

    2. List of dicts: Generates exactly one scenario for each dictionary in the list. Example: [{'speed': 10}, {'speed': 20}] yields 2 scenarios.

    3. List of dicts with expand_permutations: Combines fixed sets with a parameter sweep. Example: A list of 2 base dicts combined with an expand_permutations dict containing 2 list items will yield $2 \times 2 = 4$ scenarios.

    # 1. Dict containing lists (Permutations)
    self.parameters = {'road_curvature': [0.001, 0.002], 'speed': [10, 20, 30]}
    
    # 2. List of dicts (Fixed sets)
    self.parameters = [{'road_curvature': 0.001, 'speed': 10}, {'road_curvature': 0.002, 'speed': 20}]
    
    # 3. List of dicts with expand_permutations (Hybrid)
    self.parameters = [
        {'road_curvature': 0.001, 'speed': 10},
        {'road_curvature': 0.002, 'speed': 20}
    ]
    self.expand_permutations = [
        {'initial_distance': [40, 50, 60], 'line_length': [100, 200]}
    ]
  8. Release to PyPI

    main

    To release a new version of scenariogeneration to PyPI, follow these steps:

    1. Update Versioning: Change the version = "..." field under the [project] section in pyproject.toml.
    2. Update Release Notes: Add the new release notes to release_notes.md.
    3. Commit Changes: Use git add -u, git commit -m "prep release", and git push.
    4. Tag the Release: Create a git tag using the format git tag -a "v0.15.X" -m "Version 0.15.X" and push the tags with git push --tags.
    5. Build Artifacts: Remove old dist directories, ensure the build package is installed (pip install build), and run python3 -m build.
    6. Upload: Use twine upload dist/* to upload the artifacts to PyPI.
    # 1. Prepare and push changes
    git add -u
    git commit -m "prep release"
    git push
    
    # 2. Tag and push tags
    git tag -a "v0.15.X" -m "Version 0.15.X"
    git push --tags
    
    # 3. Build and upload
    rm -rf dist
    pip install build
    python3 -m build
    twine upload dist/*
  9. Create common junctions with CommonJunctionCreator

    main

    The CommonJunctionCreator class is used to build standard OpenDRIVE junctions. This is a two-step process: adding roads to the junction and then defining connections between them.

    1. Add Incoming Roads

    Roads must have a predecessor or successor pointing to the junction. You can add them using add_incoming_road_cartesian_geometry or add_incoming_road_circular_geometry.

    • Cartesian Geometry: Uses an x-y-h system where h is the heading of the road into the junction.
    • Circular Geometry: Uses an r-h system where r is the radius from the junction center and h is the heading from the center.

    2. Add Connections

    Use add_connection to link roads.

    • Basic: add_connection(road_one_id=1, road_two_id=2) creates a connecting road with the minimum equal number of lanes between the two roads.
    • Specific Lanes: Use lane_one_id and lane_two_id to connect only specific lanes.

    3. Finalize

    Add the roads to an OpenDrive object and use add_junction_creator followed by adjust_roads_and_lanes().

    from scenariogeneration import xodr
    
    road1 = xodr.create_road(xodr.Line(100), id=1, left_lanes=2, right_lanes=2)
    road2 = xodr.create_road(xodr.Line(100), id=2, left_lanes=1, right_lanes=1)
    road3 = xodr.create_road(xodr.Line(100), id=3, left_lanes=2, right_lanes=2)
    
    junction_creator = xodr.CommonJunctionCreator(id=100, name='my_junction')
    
    # Add roads using Cartesian coordinates
    junction_creator.add_incoming_road_cartesian_geometry(road1, x=0, y=0, heading=0, road_connection='successor')
    junction_creator.add_incoming_road_cartesian_geometry(road2, x=50, y=50, heading=3.1415*3/2, road_connection='predecessor')
    junction_creator.add_incoming_road_cartesian_geometry(road3, x=100, y=0, heading=-3.1415, road_connection='predecessor')
    
    # Define connections
    junction_creator.add_connection(road_one_id=1, road_two_id=3)
    junction_creator.add_connection(road_one_id=1, road_two_id=2, lane_one_id=2, lane_two_id=1)
    junction_creator.add_connection(road_one_id=2, road_two_id=3, lane_one_id=-1, lane_two_id=2)
    
    # Finalize OpenDrive
    odr = xodr.OpenDrive('myroad')
    odr.add_road(road1)
    odr.add_road(road2)
    odr.add_road(road3)
    odr.add_junction_creator(junction_creator)
    odr.adjust_roads_and_lanes()
  10. Update documentation

    main

    To update the project documentation and host it on the pyoscx.github.io repository, follow these steps:

    1. Generate Documentation: Ensure pdoc3 is installed (pip install pdoc3). Navigate to the docu directory and run the generation script:
      cd docu
      ./generate_documentation.sh
    2. Prepare Hosting Repository: Ensure you have cloned the sibling repository https://github.com/pyoscx/pyoscx.github.io.
    3. Sync Files: Copy the newly generated files into the sibling repository:
      cp -rf generated/* ../pyoscx.github.io
    4. Deploy: Navigate to the sibling repository, add the files, commit, and push to trigger the update.
    # 1. Generate
    pip install pdoc3
    cd docu
    ./generate_documentation.sh
    
    # 2. Copy to hosting repo
    cp -rf generated/* ../pyoscx.github.io
    
    # 3. Push to GitHub Pages
    cd ../pyoscx.github.io
    git add .
    git commit -m "Update documentation"
    git push
  11. Use the scenariogeneration package to generate OpenSCENARIO and OpenDRIVE files

    main

    The scenariogeneration package provides a collection of libraries for generating OpenSCENARIO (.xosc) and OpenDRIVE (.xodr) XML files. The package exposes functionality from three main modules:

    • esmini_runner: Tools for running scenarios with esmini.
    • helpers: Utility functions for scenario construction.
    • scenario_generator: Core logic for generating scenario files.

    To use the library, import the desired components directly from the top-level scenariogeneration package.

    import scenariogeneration
    # Access exported classes and functions from esmini_runner, helpers, and scenario_generator
  12. How ScenarioGenerator handles parameter permutations

    main

    The ScenarioGenerator uses two primary logic paths to determine which scenarios to create based on the type of self.parameters provided:

    1. Dictionary of Lists (Sweep): If self.parameters is a dict where values are lists, the generator performs a Cartesian product of all lists. Every possible combination of the provided values is treated as a unique scenario.
    2. List of Dictionaries (Explicit): If self.parameters is a list of dicts, the generator treats each dictionary as a single, specific scenario to be generated. No combinations are performed.

    Additionally, if self.expand_permutations is used, the generator expands the expand_permutations dict into a list of dicts and then combines them with the base self.parameters list using a cross-product logic.