# betterosi - a python library for reading and writing open-simulation-interface files using betterproto2
[](https://github.com/ika-rwth-aachen/betterosi/blob/master/LICENSE)
[](https://pypi.python.org/pypi/betterosi)
[](https://github.com/ika-rwth-aachen/betterosi/actions)
[](https://pypi.python.org/pypi/betterosi/)
[](https://github.com/ika-rwth-aachen/betterosi/issues)
A python library for reading and writing [ASAM OSI (Open-Simulation-Interace)](https://github.com/OpenSimulationInterface/open-simulation-interface) files (either `.osi` binary traces or [MCAP](https://github.com/foxglove/mcap) files) using [betterproto2](https://github.com/betterproto/python-betterproto2) instead of the default protobuf generated code (better typing and enum support).
- Supports writing and reading either mcap or osi files with `betterosi.Writer` and `betterosi.read`.
- View OSI or MCAP file containing OSI GroundTruth `betterosi-viewer <filepath.mcap / filepath.osi>`(adapted from [esmini](https://github.com/esmini/esmini))
- Convert osi to mcap with `betterosi-to-mcap <filepath to osi>`.
The library uses code from [esmini](https://github.com/esmini/esmini) (`betterosi/viewer.py`) under MPL 2.0 license and the code from [open-simulation-interface](https://github.com/OpenSimulationInterface/open-simulation-interface) to read osi traces (`betterosi/osi3trace.py`).
The library uses code generation of [python-betterproto2-compiler](https://github.com/betterproto/python-betterproto2-compiler) to generate python code from the protobuf definitions of [open-simulation-interface](https://github.com/OpenSimulationInterface/open-simulation-interface).
Since OSI and esmini are under MPL, also this repository is published under MPL-2.0 license.
## Differences to OSI 3.7.0
The proto definitions extend the OSI 3.7.0 definitions in the following ways:
- Add `MapAsamOpenDrive` Message: Packages the XML content of an ASAM OpenDRIVE map in a proto Message
See [omega-prime](https://github.com/ika-rwth-aachen/omega-prime) for details.
## Install
`pip install betterosi`
## Create an OSI or MCAP trace
To create an OSI or MCAP trace, you need to use `betterosi.Writer`. After creating the OSI Message of your desire, just add it to the `Writer` as shown in the examples below for either MCAP traaces or OSI traces.
<!--pytest.mark.skip-->
```python
import betterosi
with betterosi.Writer('test.mcap') as writer:
gt = betterosi.GroundTruth(...)
writer.add(gt)
with betterosi.Writer('test.osi') as writer:
sv = betterosi.SensorView(...)
writer.add(sv)
```
Below a full example is given which creates three files, and MCAP trace and and OSI trace with GroundTruth messages and a MCAP trace of SensorViews. If you use the code, you obviously just need one of the writers.
```python
import betterosi
NANOS_PER_SEC = 1_000_000_000
with betterosi.Writer('test.mcap') as writer_mcap, betterosi.Writer('test.osi') as writer_osi, betterosi.Writer('test_sv.mcap') as writer_sv:
moving_object = betterosi.MovingObject(id=betterosi.Identifier(value=42),
type = betterosi.MovingObjectType.TYPE_UNKNOWN,
base=betterosi.BaseMoving(
dimension= betterosi.Dimension3D(length=5, width=2, height=1),
position = betterosi.Vector3D(x=0, y=0, z=0),
orientation = betterosi.Orientation3D(roll = 0.0, pitch = 0.0, yaw = 0.0),
velocity = betterosi.Vector3D(x=1, y=0, z=0)
))
gt = betterosi.GroundTruth(
version=betterosi.InterfaceVersion(version_major= 3, version_minor=7, version_patch=0),
timestamp=betterosi.Timestamp(seconds=0, nanos=0),
moving_object=[
moving_object
],
host_vehicle_id=betterosi.Identifier(value=0)
)
sv = betterosi.SensorView(
version=betterosi.InterfaceVersion(version_major= 3, version_minor=7, version_patch=0),
timestamp=betterosi.Timestamp(seconds=0, nanos=0),
global_ground_truth=gt,
host_vehicle_id=betterosi.Identifier(value=0)
)
# Generate 1000 OSI messages for a duration of 10 seconds
for i in range(1000):
total_nanos = i*0.01*NANOS_PER_SEC
gt.timestamp.seconds = int(total_nanos // NANOS_PER_SEC)
gt.timestamp.nanos = int(total_nanos % NANOS_PER_SEC)
moving_object.base.position.x += 0.5
sv.timestamp = gt.timestamp
writer_mcap.add(gt)
writer_osi.add(gt)
writer_sv.add(sv)
```
When writing MCAP messages you can specifiy the topic in the writer and the add function by setting the `topic` argument. When reading such files, set the argument `mcap_topic` to the same string.
## Read OSI and MCAP
With `betterosi.read` you can read an mcap or osi trace. `read` returns a generator. With the following code, you can get a list of the GroundTruth messages from a trace, even if the GroundTruth are nested inside SensorViews. It works the same for OSI traces.
```python
import betterosi
ground_truths = list(betterosi.read('test_sv.mcap', return_ground_truth=True))
print([len(ground_truths), ground_truths[0]])
```
Above code prints the following:
<!--pytest-codeblocks:expected-output-->
```
[1000, GroundTruth(version=InterfaceVersion(version_major=3, version_minor=7), timestamp=Timestamp(), host_vehicle_id=Identifier(), moving_object=[MovingObject(id=Identifier(value=42), base=BaseMoving(dimension=Dimension3D(length=5.0, width=2.0, height=1.0), position=Vector3D(x=0.5), orientation=Orientation3D(), velocity=Vector3D(x=1.0)))])]
```
If you want a list of the sensor views directly:
```python
import betterosi
sensor_views = betterosi.read('test_sv.mcap', return_sensor_view=True)
print(next(sensor_views))
```
The above prints:
<!--pytest-codeblocks:expected-output-->
```
SensorView(version=InterfaceVersion(version_major=3, version_minor=7), timestamp=Timestamp(), global_ground_truth=GroundTruth(version=InterfaceVersion(version_major=3, version_minor=7), timestamp=Timestamp(), host_vehicle_id=Identifier(), moving_object=[MovingObject(id=Identifier(value=42), base=BaseMoving(dimension=Dimension3D(length=5.0, width=2.0, height=1.0), position=Vector3D(x=0.5), orientation=Orientation3D(), velocity=Vector3D(x=1.0)))]), host_vehicle_id=Identifier())
```
If you want to read any OSI trace, you just need to give the filename.
```python
import betterosi
any_osi_message = betterosi.read('test.osi')
any_osi_message = betterosi.read('test.mcap')
```
# Generate library code
```
pip install grpcio-tools git+https://github.com/MichaelSchuldes/python-betterproto2-compiler@serialized_descriptors
```
cd into osi-proto and run the following command to generate the code
```
cd osi-proto
mkdir ../betterosi/generated
python -m grpc_tools.protoc -I . --python_betterproto2_out=../betterosi/generated osi_common.proto osi_datarecording.proto osi_detectedlane.proto osi_detectedobject.proto osi_detectedoccupant.proto osi_detectedroadmarking.proto osi_detectedtrafficlight.proto osi_detectedtrafficsign.proto osi_environment.proto osi_featuredata.proto osi_groundtruth.proto osi_hostvehicledata.proto osi_lane.proto osi_logicaldetectiondata.proto osi_logicallane.proto osi_motionrequest.proto osi_object.proto osi_occupant.proto osi_referenceline.proto osi_roadmarking.proto osi_route.proto osi_sensordata.proto osi_sensorspecific.proto osi_sensorview.proto osi_sensorviewconfiguration.proto osi_streamingupdate.proto osi_trafficcommand.proto osi_trafficcommandupdate.proto osi_trafficlight.proto osi_trafficsign.proto osi_trafficupdate.proto osi_version.proto osi_mapasamopendrive.proto
```
# LICENSE and Copyright
This code is published under MPL-2.0 license.
It utilizes and modifies parts of [esmini](https://github.com/esmini/esmini) ([betterosi/viewer.py](betterosi/viewer.py)) under MPL-2.0 and [open-simulation-interface](https://github.com/OpenSimulationInterface/open-simulation-interface) ([osi-proto/*](osi-proto/) and [betterosi/osi3trace.py](betterosi/osi3trace.py)) under MPL-2.0.
# Acknowledgements
This package is developed as part of the [SYNERGIES project](https://synergies-ccam.eu).
<img src="https://raw.githubusercontent.com/ika-rwth-aachen/betterosi/refs/heads/main/synergies.svg"
style="width:2in" />
Funded by the European Union. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or European Climate, Infrastructure and Environment Executive Agency (CINEA). Neither the European Union nor the granting authority can be held responsible for them.
<img src="https://raw.githubusercontent.com/ika-rwth-aachen/betterosi/refs/heads/main/funded_by_eu.svg"
style="width:4in" />
Raw data
{
"_id": null,
"home_page": null,
"name": "betterosi",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "osi, open-simulation-interface, simulation, automated-driving",
"author": null,
"author_email": "ika - RWTH Aachen <michael.schuldes@ika.rwth-aachen.de>",
"download_url": "https://files.pythonhosted.org/packages/ed/66/3c0883215d0d3b93b647d44f3fb24b57362ad40e2316f57d2cf76838cdbe/betterosi-0.3.6.tar.gz",
"platform": null,
"description": "# betterosi - a python library for reading and writing open-simulation-interface files using betterproto2\n\n[](https://github.com/ika-rwth-aachen/betterosi/blob/master/LICENSE) \n[](https://pypi.python.org/pypi/betterosi)\n[](https://github.com/ika-rwth-aachen/betterosi/actions)\n[](https://pypi.python.org/pypi/betterosi/)\n[](https://github.com/ika-rwth-aachen/betterosi/issues)\n\nA python library for reading and writing [ASAM OSI (Open-Simulation-Interace)](https://github.com/OpenSimulationInterface/open-simulation-interface) files (either `.osi` binary traces or [MCAP](https://github.com/foxglove/mcap) files) using [betterproto2](https://github.com/betterproto/python-betterproto2) instead of the default protobuf generated code (better typing and enum support).\n\n- Supports writing and reading either mcap or osi files with `betterosi.Writer` and `betterosi.read`.\n- View OSI or MCAP file containing OSI GroundTruth `betterosi-viewer <filepath.mcap / filepath.osi>`(adapted from [esmini](https://github.com/esmini/esmini))\n- Convert osi to mcap with `betterosi-to-mcap <filepath to osi>`.\n\nThe library uses code from [esmini](https://github.com/esmini/esmini) (`betterosi/viewer.py`) under MPL 2.0 license and the code from [open-simulation-interface](https://github.com/OpenSimulationInterface/open-simulation-interface) to read osi traces (`betterosi/osi3trace.py`).\n\nThe library uses code generation of [python-betterproto2-compiler](https://github.com/betterproto/python-betterproto2-compiler) to generate python code from the protobuf definitions of [open-simulation-interface](https://github.com/OpenSimulationInterface/open-simulation-interface).\n\nSince OSI and esmini are under MPL, also this repository is published under MPL-2.0 license.\n\n## Differences to OSI 3.7.0\nThe proto definitions extend the OSI 3.7.0 definitions in the following ways:\n- Add `MapAsamOpenDrive` Message: Packages the XML content of an ASAM OpenDRIVE map in a proto Message\n\nSee [omega-prime](https://github.com/ika-rwth-aachen/omega-prime) for details.\n\n## Install\n\n`pip install betterosi`\n## Create an OSI or MCAP trace\n\nTo create an OSI or MCAP trace, you need to use `betterosi.Writer`. After creating the OSI Message of your desire, just add it to the `Writer` as shown in the examples below for either MCAP traaces or OSI traces. \n\n\n<!--pytest.mark.skip-->\n```python\nimport betterosi\n\nwith betterosi.Writer('test.mcap') as writer:\n gt = betterosi.GroundTruth(...)\n writer.add(gt)\n\nwith betterosi.Writer('test.osi') as writer:\n sv = betterosi.SensorView(...)\n writer.add(sv)\n```\n\nBelow a full example is given which creates three files, and MCAP trace and and OSI trace with GroundTruth messages and a MCAP trace of SensorViews. If you use the code, you obviously just need one of the writers.\n\n```python\nimport betterosi\nNANOS_PER_SEC = 1_000_000_000\n\n\nwith betterosi.Writer('test.mcap') as writer_mcap, betterosi.Writer('test.osi') as writer_osi, betterosi.Writer('test_sv.mcap') as writer_sv:\n moving_object = betterosi.MovingObject(id=betterosi.Identifier(value=42),\n type = betterosi.MovingObjectType.TYPE_UNKNOWN,\n base=betterosi.BaseMoving(\n dimension= betterosi.Dimension3D(length=5, width=2, height=1),\n position = betterosi.Vector3D(x=0, y=0, z=0),\n orientation = betterosi.Orientation3D(roll = 0.0, pitch = 0.0, yaw = 0.0),\n velocity = betterosi.Vector3D(x=1, y=0, z=0)\n ))\n gt = betterosi.GroundTruth(\n version=betterosi.InterfaceVersion(version_major= 3, version_minor=7, version_patch=0),\n timestamp=betterosi.Timestamp(seconds=0, nanos=0),\n moving_object=[\n moving_object\n ],\n host_vehicle_id=betterosi.Identifier(value=0)\n )\n sv = betterosi.SensorView(\n version=betterosi.InterfaceVersion(version_major= 3, version_minor=7, version_patch=0),\n timestamp=betterosi.Timestamp(seconds=0, nanos=0),\n global_ground_truth=gt,\n host_vehicle_id=betterosi.Identifier(value=0)\n )\n # Generate 1000 OSI messages for a duration of 10 seconds\n for i in range(1000):\n total_nanos = i*0.01*NANOS_PER_SEC\n gt.timestamp.seconds = int(total_nanos // NANOS_PER_SEC)\n gt.timestamp.nanos = int(total_nanos % NANOS_PER_SEC)\n moving_object.base.position.x += 0.5\n sv.timestamp = gt.timestamp\n\n writer_mcap.add(gt)\n writer_osi.add(gt)\n writer_sv.add(sv)\n```\n\nWhen writing MCAP messages you can specifiy the topic in the writer and the add function by setting the `topic` argument. When reading such files, set the argument `mcap_topic` to the same string.\n\n## Read OSI and MCAP\nWith `betterosi.read` you can read an mcap or osi trace. `read` returns a generator. With the following code, you can get a list of the GroundTruth messages from a trace, even if the GroundTruth are nested inside SensorViews. It works the same for OSI traces.\n\n```python\nimport betterosi\nground_truths = list(betterosi.read('test_sv.mcap', return_ground_truth=True))\nprint([len(ground_truths), ground_truths[0]])\n```\nAbove code prints the following:\n<!--pytest-codeblocks:expected-output-->\n```\n[1000, GroundTruth(version=InterfaceVersion(version_major=3, version_minor=7), timestamp=Timestamp(), host_vehicle_id=Identifier(), moving_object=[MovingObject(id=Identifier(value=42), base=BaseMoving(dimension=Dimension3D(length=5.0, width=2.0, height=1.0), position=Vector3D(x=0.5), orientation=Orientation3D(), velocity=Vector3D(x=1.0)))])]\n```\n\nIf you want a list of the sensor views directly:\n\n```python\nimport betterosi\nsensor_views = betterosi.read('test_sv.mcap', return_sensor_view=True)\nprint(next(sensor_views))\n```\nThe above prints:\n<!--pytest-codeblocks:expected-output-->\n```\nSensorView(version=InterfaceVersion(version_major=3, version_minor=7), timestamp=Timestamp(), global_ground_truth=GroundTruth(version=InterfaceVersion(version_major=3, version_minor=7), timestamp=Timestamp(), host_vehicle_id=Identifier(), moving_object=[MovingObject(id=Identifier(value=42), base=BaseMoving(dimension=Dimension3D(length=5.0, width=2.0, height=1.0), position=Vector3D(x=0.5), orientation=Orientation3D(), velocity=Vector3D(x=1.0)))]), host_vehicle_id=Identifier())\n```\nIf you want to read any OSI trace, you just need to give the filename.\n```python\nimport betterosi\nany_osi_message = betterosi.read('test.osi')\nany_osi_message = betterosi.read('test.mcap')\n```\n\n\n# Generate library code\n\n```\npip install grpcio-tools git+https://github.com/MichaelSchuldes/python-betterproto2-compiler@serialized_descriptors\n```\n\ncd into osi-proto and run the following command to generate the code \n\n```\ncd osi-proto\n\nmkdir ../betterosi/generated\n\npython -m grpc_tools.protoc -I . --python_betterproto2_out=../betterosi/generated osi_common.proto osi_datarecording.proto osi_detectedlane.proto osi_detectedobject.proto osi_detectedoccupant.proto osi_detectedroadmarking.proto osi_detectedtrafficlight.proto osi_detectedtrafficsign.proto osi_environment.proto osi_featuredata.proto osi_groundtruth.proto osi_hostvehicledata.proto osi_lane.proto osi_logicaldetectiondata.proto osi_logicallane.proto osi_motionrequest.proto osi_object.proto osi_occupant.proto osi_referenceline.proto osi_roadmarking.proto osi_route.proto osi_sensordata.proto osi_sensorspecific.proto osi_sensorview.proto osi_sensorviewconfiguration.proto osi_streamingupdate.proto osi_trafficcommand.proto osi_trafficcommandupdate.proto osi_trafficlight.proto osi_trafficsign.proto osi_trafficupdate.proto osi_version.proto osi_mapasamopendrive.proto\n```\n\n# LICENSE and Copyright\nThis code is published under MPL-2.0 license. \nIt utilizes and modifies parts of [esmini](https://github.com/esmini/esmini) ([betterosi/viewer.py](betterosi/viewer.py)) under MPL-2.0 and [open-simulation-interface](https://github.com/OpenSimulationInterface/open-simulation-interface) ([osi-proto/*](osi-proto/) and [betterosi/osi3trace.py](betterosi/osi3trace.py)) under MPL-2.0.\n\n# Acknowledgements\n\nThis package is developed as part of the [SYNERGIES project](https://synergies-ccam.eu).\n\n<img src=\"https://raw.githubusercontent.com/ika-rwth-aachen/betterosi/refs/heads/main/synergies.svg\"\nstyle=\"width:2in\" />\n\n\n\nFunded by the European Union. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or European Climate, Infrastructure and Environment Executive Agency (CINEA). Neither the European Union nor the granting authority can be held responsible for them. \n\n<img src=\"https://raw.githubusercontent.com/ika-rwth-aachen/betterosi/refs/heads/main/funded_by_eu.svg\"\nstyle=\"width:4in\" />\n",
"bugtrack_url": null,
"license": "MPL-2.0",
"summary": "betterosi - a python library for reading and writing open-simulation-interface files using betterproto2",
"version": "0.3.6",
"project_urls": {
"Homepage": "https://github.com/ika-rwth-aachen/betterosi",
"Repository": "https://github.com/ika-rwth-aachen/betterosi"
},
"split_keywords": [
"osi",
" open-simulation-interface",
" simulation",
" automated-driving"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "f20a4b7235b1fd19235711061f239047959ba799512ce4b0025743cf6656e6a4",
"md5": "7975c1ac643f50ed252899edbcbcad8a",
"sha256": "2987f531fe4037434137fb4111c03470b2a6dc91b242304b1d41a901b7460d61"
},
"downloads": -1,
"filename": "betterosi-0.3.6-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7975c1ac643f50ed252899edbcbcad8a",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 212535,
"upload_time": "2025-09-11T06:48:58",
"upload_time_iso_8601": "2025-09-11T06:48:58.286340Z",
"url": "https://files.pythonhosted.org/packages/f2/0a/4b7235b1fd19235711061f239047959ba799512ce4b0025743cf6656e6a4/betterosi-0.3.6-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "ed663c0883215d0d3b93b647d44f3fb24b57362ad40e2316f57d2cf76838cdbe",
"md5": "93f93f25796ee213307916b0d3223b96",
"sha256": "85286d8e587eb45738a0d3eb0beae887291dd6283fcb79335e5b585031df1887"
},
"downloads": -1,
"filename": "betterosi-0.3.6.tar.gz",
"has_sig": false,
"md5_digest": "93f93f25796ee213307916b0d3223b96",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 211211,
"upload_time": "2025-09-11T06:48:59",
"upload_time_iso_8601": "2025-09-11T06:48:59.723666Z",
"url": "https://files.pythonhosted.org/packages/ed/66/3c0883215d0d3b93b647d44f3fb24b57362ad40e2316f57d2cf76838cdbe/betterosi-0.3.6.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-09-11 06:48:59",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "ika-rwth-aachen",
"github_project": "betterosi",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "betterproto2",
"specs": [
[
"==",
"0.3.1"
]
]
},
{
"name": "betterproto2-rust-codec",
"specs": [
[
"==",
"0.1.3"
]
]
},
{
"name": "click",
"specs": [
[
"==",
"8.1.8"
]
]
},
{
"name": "colorama",
"specs": [
[
"==",
"0.4.6"
]
]
},
{
"name": "contourpy",
"specs": [
[
"==",
"1.3.1"
]
]
},
{
"name": "cycler",
"specs": [
[
"==",
"0.12.1"
]
]
},
{
"name": "fonttools",
"specs": [
[
"==",
"4.56.0"
]
]
},
{
"name": "grpclib",
"specs": [
[
"==",
"0.4.7"
]
]
},
{
"name": "h2",
"specs": [
[
"==",
"4.2.0"
]
]
},
{
"name": "hpack",
"specs": [
[
"==",
"4.1.0"
]
]
},
{
"name": "hyperframe",
"specs": [
[
"==",
"6.1.0"
]
]
},
{
"name": "kiwisolver",
"specs": [
[
"==",
"1.4.8"
]
]
},
{
"name": "lz4",
"specs": [
[
"==",
"4.4.4"
]
]
},
{
"name": "markdown-it-py",
"specs": [
[
"==",
"3.0.0"
]
]
},
{
"name": "matplotlib",
"specs": [
[
"==",
"3.10.1"
]
]
},
{
"name": "mcap",
"specs": [
[
"==",
"1.2.2"
]
]
},
{
"name": "mcap-protobuf-support",
"specs": [
[
"==",
"0.5.3"
]
]
},
{
"name": "mdurl",
"specs": [
[
"==",
"0.1.2"
]
]
},
{
"name": "multidict",
"specs": [
[
"==",
"6.2.0"
]
]
},
{
"name": "numpy",
"specs": [
[
"==",
"2.2.4"
]
]
},
{
"name": "packaging",
"specs": [
[
"==",
"24.2"
]
]
},
{
"name": "pillow",
"specs": [
[
"==",
"11.1.0"
]
]
},
{
"name": "protobuf",
"specs": [
[
"==",
"6.30.2"
]
]
},
{
"name": "pygments",
"specs": [
[
"==",
"2.19.1"
]
]
},
{
"name": "pyparsing",
"specs": [
[
"==",
"3.2.3"
]
]
},
{
"name": "python-dateutil",
"specs": [
[
"==",
"2.9.0.post0"
]
]
},
{
"name": "rich",
"specs": [
[
"==",
"14.0.0"
]
]
},
{
"name": "shellingham",
"specs": [
[
"==",
"1.5.4"
]
]
},
{
"name": "six",
"specs": [
[
"==",
"1.17.0"
]
]
},
{
"name": "tqdm",
"specs": [
[
"==",
"4.67.1"
]
]
},
{
"name": "typer",
"specs": [
[
"==",
"0.15.2"
]
]
},
{
"name": "typing-extensions",
"specs": [
[
"==",
"4.13.0"
]
]
},
{
"name": "zstandard",
"specs": [
[
"==",
"0.23.0"
]
]
}
],
"lcname": "betterosi"
}