diff --git a/.github/workflows/run_build_docs.yml b/.github/workflows/run_build_docs.yml
new file mode 100644
index 00000000..9c66225d
--- /dev/null
+++ b/.github/workflows/run_build_docs.yml
@@ -0,0 +1,56 @@
+# Workflow for building Sphinx docs and deploying to GH Pages
+name: Build Sphinx docs and deploy to GH Pages
+
+on:
+ pull_request:
+ branches: [main]
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: Branch to build docs for
+ required: true
+ default: main
+
+# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+# Allow only one concurrent deployment, cancelling in-progress runs.
+concurrency:
+ group: pages
+ cancel-in-progress: true
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo "The job was automatically triggered by a ${{ github.event_name }} event."
+ - run: echo "This job is now running on a ${{ runner.os }} server."
+ - run: echo "Running on branch ${{ github.ref }} of repository ${{ github.repository }}."
+ - name: Check out repository code.
+ uses: actions/checkout@v3
+ - name: Python environment setup
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.10'
+ - name: Install dependencies.
+ run: |
+ sudo apt-get update
+ sudo apt-get install libsfml-dev
+ git submodule sync
+ git submodule update --init --recursive
+ python -m pip install -U pip poetry
+ poetry install --with=docs
+ - name: Build Sphinx docs
+ run: poetry run sphinx-build -b html ${{ github.workspace }}/docs/source ${{ github.workspace }}/docs/build/html
+ - name: Setup Pages
+ uses: actions/configure-pages@v3
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v1
+ with:
+ path: ./docs/build/html # Upload HTML docs only
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v2
diff --git a/.gitignore b/.gitignore
index 1529576e..a8e131b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,5 +51,8 @@ configs.json
# poetry
poetry.lock
+# Static doc files
+!docs/source/_static/*
+
# wandb
wandb
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d1117cb4..018753c8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -45,6 +45,22 @@ repos:
args: [--autofix, --no-sort]
- id: pretty-format-yaml
args: [--autofix]
+- repo: https://github.com/mwouts/jupytext
+ rev: v1.15.2
+ hooks:
+ - id: jupytext
+ args: [--from, ipynb, --to, md:myst, --sync]
+- repo: https://github.com/nbQA-dev/nbQA
+ rev: 1.7.0
+ hooks:
+ - id: nbqa-pyupgrade
+ args: [--py310-plus]
+ - id: nbqa-black
+ args: [--line-length=120]
+ - id: nbqa-isort
+ args: [--profile=black]
+ - id: nbqa-flake8
+ args: [--max-line-length=120, --extend-ignore=E203]
- repo: local
hooks:
- id: pylint
@@ -52,6 +68,7 @@ repos:
entry: poetry run pylint
language: system
types: [python]
+ exclude: ^docs/.*
- id: poetry-export-requirements
name: poetry-export-requirements
entry: poetry export --without-hashes --with=main,research -f requirements.txt -o requirements.txt
@@ -64,3 +81,9 @@ repos:
language: system
types: [python]
pass_filenames: false
+ - id: poetry-export-requirements-docs
+ name: poetry-export-requirements-docs
+ entry: poetry export --without-hashes --only docs -f requirements.txt -o requirements.docs.txt
+ language: system
+ types: [python]
+ pass_filenames: false
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 00000000..d0c3cbf1
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 00000000..dc1312ab
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.https://www.sphinx-doc.org/
+ exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/source/01_data_structure.ipynb b/docs/source/01_data_structure.ipynb
new file mode 100644
index 00000000..62a7ccca
--- /dev/null
+++ b/docs/source/01_data_structure.ipynb
@@ -0,0 +1,4230 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Data format of a traffic scene\n",
+ "\n",
+ "This notebook dives into the data format used to create simulations in Nocturne.\n",
+ "\n",
+ "_Last update: 10/2023_"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import json\n",
+ "import os\n",
+ "\n",
+ "import matplotlib.pyplot as plt\n",
+ "import pandas as pd\n",
+ "import seaborn as sns\n",
+ "\n",
+ "os.chdir(\"..\")\n",
+ "\n",
+ "cmap = [\"r\", \"g\", \"b\", \"y\", \"c\"]\n",
+ "%config InlineBackend.figure_format = 'svg'\n",
+ "sns.set(\"notebook\", font_scale=1.1, rc={\"figure.figsize\": (8, 3)})\n",
+ "sns.set_style(\"ticks\", rc={\"figure.facecolor\": \"none\", \"axes.facecolor\": \"none\"})"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Traffic scenes are constructed by utilizing the [Waymo Open Motion dataset](https://waymo.com/open/). Though every scene is unique, they all have the same basic data structure. \n",
+ "\n",
+ "To load a traffic scene:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['name', 'objects', 'roads', 'tl_states'])"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Take an example scene\n",
+ "data_path = \"./data/example_scenario.json\"\n",
+ "\n",
+ "with open(data_path) as file:\n",
+ " traffic_scene = json.load(file)\n",
+ "\n",
+ "traffic_scene.keys()"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Global Overview \n",
+ "A traffic scene consists of:\n",
+ "- `name`: the name of the traffic scenario.\n",
+ "- `objects`: the road objects or moving vehicles in the scene.\n",
+ "- `roads`: the road points in the scene, these are all the stationary objects.\n",
+ "- `tl_states`: the states of the traffic lights, which are filtered out for now. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{}"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "traffic_scene[\"tl_states\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'tfrecord-00358-of-01000_65.json'"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "traffic_scene[\"name\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "pd.Series([traffic_scene[\"objects\"][idx][\"type\"] for idx in range(len(traffic_scene[\"objects\"]))]).value_counts().plot(\n",
+ " kind=\"bar\", rot=45, color=cmap\n",
+ ")\n",
+ "plt.title(f'Distribution of road objects in traffic scene. Total # objects: {len(traffic_scene[\"objects\"])}')\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This traffic scenario only contains vehicles and pedestrians, some scenes have cyclists as well."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "pd.Series([traffic_scene[\"roads\"][idx][\"type\"] for idx in range(len(traffic_scene[\"roads\"]))]).value_counts().plot(\n",
+ " kind=\"bar\", rot=45, color=cmap\n",
+ ")\n",
+ "plt.title(f'Distribution of road points in traffic scene. Total # points: {len(traffic_scene[\"roads\"])}')\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### In-Depth: Road Objects\n",
+ "\n",
+ "This is a list of different road objects in the traffic scene. For each road object, we have information about its position, velocity, size, in which direction it's heading, whether it's a valid object, the type, and the final position of the vehicle."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['position', 'width', 'length', 'heading', 'velocity', 'valid', 'goalPosition', 'type'])"
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Take the first object\n",
+ "idx = 0\n",
+ "\n",
+ "# For each object, we have this information:\n",
+ "traffic_scene[\"objects\"][idx].keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[\n",
+ " {\n",
+ " \"x\": 9037.7138671875,\n",
+ " \"y\": -2720.373779296875\n",
+ " },\n",
+ " {\n",
+ " \"x\": 9037.7607421875,\n",
+ " \"y\": -2720.306640625\n",
+ " },\n",
+ " {\n",
+ " \"x\": 9037.822265625,\n",
+ " \"y\": -2720.217529296875\n",
+ " },\n",
+ " {\n",
+ " \"x\": 9037.8916015625,\n",
+ " \"y\": -2720.146240234375\n",
+ " },\n",
+ " {\n",
+ " \"x\": 9037.9482421875,\n",
+ " \"y\": -2720.070068359375\n",
+ " },\n",
+ " {\n",
+ " \"x\": 9038.01953125,\n",
+ " \"y\": -2719.994384765625\n",
+ " },\n",
+ " {\n",
+ " \"x\": 9038.1005859375,\n",
+ " \"y\": -2719.903076171875\n",
+ " },\n",
+ " {\n",
+ " \"x\": 9038.1953125,\n",
+ " \"y\": -2719.830810546875\n",
+ " },\n",
+ " {\n",
+ " \"x\": 9038.279296875,\n",
+ " \"y\": -2719.74462890625\n",
+ " },\n",
+ " {\n",
+ " \"x\": 9038.3564453125,\n",
+ " \"y\": -2719.674560546875\n",
+ " }\n",
+ "]\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Position contains the (x, y) coordinates for the vehicle at every time step\n",
+ "print(json.dumps(traffic_scene[\"objects\"][idx][\"position\"][:10], indent=4))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(0.6877052187919617, 0.6777269244194031)"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Width and length together make the size of the object, and is used to see if there is a collision\n",
+ "traffic_scene[\"objects\"][idx][\"width\"], traffic_scene[\"objects\"][idx][\"length\"]"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "An object's heading refers to the direction it is pointing or moving in. The default coordinate system in Nocturne is right-handed, where the positive x and y axes point to the right and downwards, respectively. In a right-handed coordinate system, 0 degrees is located on the x-axis and the angle increases counter-clockwise.\n",
+ "\n",
+ "Because the scene is created from the viewpoint of an ego driver, there may be instances where the heading of certain vehicles is not available. These cases are represented by the value `-10_000`, to indicate that these steps should be filtered out or are invalid."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Heading is the direction in which the vehicle is pointing\n",
+ "plt.plot(traffic_scene[\"objects\"][idx][\"heading\"])\n",
+ "plt.xlabel(\"Time step\")\n",
+ "plt.ylabel(\"Heading\")\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[\n",
+ " {\n",
+ " \"x\": 0.634765625,\n",
+ " \"y\": 0.72265625\n",
+ " },\n",
+ " {\n",
+ " \"x\": 0.46875,\n",
+ " \"y\": 0.67138671875\n",
+ " },\n",
+ " {\n",
+ " \"x\": 0.615234375,\n",
+ " \"y\": 0.89111328125\n",
+ " },\n",
+ " {\n",
+ " \"x\": 0.693359375,\n",
+ " \"y\": 0.712890625\n",
+ " },\n",
+ " {\n",
+ " \"x\": 0.56640625,\n",
+ " \"y\": 0.76171875\n",
+ " },\n",
+ " {\n",
+ " \"x\": 0.712890625,\n",
+ " \"y\": 0.7568359375\n",
+ " },\n",
+ " {\n",
+ " \"x\": 0.810546875,\n",
+ " \"y\": 0.9130859375\n",
+ " },\n",
+ " {\n",
+ " \"x\": 0.947265625,\n",
+ " \"y\": 0.72265625\n",
+ " },\n",
+ " {\n",
+ " \"x\": 0.83984375,\n",
+ " \"y\": 0.86181640625\n",
+ " },\n",
+ " {\n",
+ " \"x\": 0.771484375,\n",
+ " \"y\": 0.70068359375\n",
+ " }\n",
+ "]\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Velocity shows the velocity in the x- and y- directions\n",
+ "print(json.dumps(traffic_scene[\"objects\"][idx][\"velocity\"][:10], indent=4))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Valid indicates if the state of the vehicle was observed for each timepoint\n",
+ "plt.xlabel(\"Time step\")\n",
+ "plt.ylabel(\"IS VALID\")\n",
+ "plt.plot(traffic_scene[\"objects\"][idx][\"valid\"], \"_\", lw=5)\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'x': 9041.1259765625, 'y': -2716.647216796875}"
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Each object has a goalPosition, an (x, y) position within the scene\n",
+ "traffic_scene[\"objects\"][idx][\"goalPosition\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'pedestrian'"
+ ]
+ },
+ "execution_count": 23,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Finally, we have the type of the vehicle\n",
+ "traffic_scene[\"objects\"][idx][\"type\"]"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### In-Depth: Road Points\n",
+ "\n",
+ "Road points are static objects in the scene."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['geometry', 'type'])"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "traffic_scene[\"roads\"][idx].keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'road_edge'"
+ ]
+ },
+ "execution_count": 25,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# This point represents the edge of a road\n",
+ "traffic_scene[\"roads\"][idx][\"type\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[\n",
+ " {\n",
+ " \"x\": 8922.911733810946,\n",
+ " \"y\": -2849.426741530589\n",
+ " },\n",
+ " {\n",
+ " \"x\": 8923.216436260553,\n",
+ " \"y\": -2849.038518766975\n",
+ " },\n",
+ " {\n",
+ " \"x\": 8923.50673911804,\n",
+ " \"y\": -2848.63941352788\n",
+ " },\n",
+ " {\n",
+ " \"x\": 8923.782254084921,\n",
+ " \"y\": -2848.2299596442986\n",
+ " },\n",
+ " {\n",
+ " \"x\": 8924.042612639492,\n",
+ " \"y\": -2847.8107047886665\n",
+ " },\n",
+ " {\n",
+ " \"x\": 8924.287466537296,\n",
+ " \"y\": -2847.382209743547\n",
+ " },\n",
+ " {\n",
+ " \"x\": 8924.516488266596,\n",
+ " \"y\": -2846.945047650609\n",
+ " },\n",
+ " {\n",
+ " \"x\": 8924.729371495881,\n",
+ " \"y\": -2846.49980324385\n",
+ " },\n",
+ " {\n",
+ " \"x\": 8924.91688626026,\n",
+ " \"y\": -2846.067714357487\n",
+ " },\n",
+ " {\n",
+ " \"x\": 8925.087545312272,\n",
+ " \"y\": -2845.6286986979553\n",
+ " }\n",
+ "]\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Geometry contains the (x, y) position(s) for a road point\n",
+ "# Note that this will be a list for road lanes and edges but a single (x, y) tuple for stop signs and alike\n",
+ "print(json.dumps(traffic_scene[\"roads\"][idx][\"geometry\"][:10], indent=4));"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "nocturne-research",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.12"
+ },
+ "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/docs/source/02_nocturne_concepts.ipynb b/docs/source/02_nocturne_concepts.ipynb
new file mode 100644
index 00000000..a31d262c
--- /dev/null
+++ b/docs/source/02_nocturne_concepts.ipynb
@@ -0,0 +1,786 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Nocturne concepts\n",
+ "\n",
+ "This page introduces the most basic elements of nocturne. You can find further information about these [in Section 3 of the Nocturne paper](https://arxiv.org/abs/2206.09889).\n",
+ "\n",
+ "_Last update: 10/2023_"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "\n",
+ "import numpy as np\n",
+ "\n",
+ "os.chdir(\"..\")\n",
+ "\n",
+ "data_path = \"./data/example_scenario.json\""
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Summary\n",
+ "\n",
+ "- Nocturne simulations are **discretized traffic scenarios**. A scenario is a constructed snapshot of traffic situation at a particular timepoint.\n",
+ "- The state of the vehicle of focus is referred to as the **ego state**. Each vehicle has their **own partial view of the traffic scene**; and a visible state is constructed by parameterizing the view distance, head angle and cone radius of the driver. The action for each vehicle is a `(1, 3)` tuple with the acceleration, steering and head angle of the vehicle. \n",
+ "- The **step method advances the simulation** with a desired step size. By default, the dynamics of vehicles are driven by a kinematic bicycle model. If a vehicle is set to expert-controlled mode, its position, heading, and speed will be updated according to a trajectory recorded from a human driver."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Simulation\n",
+ "\n",
+ "In Nocturne, a simulation discretizes an existing traffic scenario. At the moment, Nocturne supports traffic scenarios from the Waymo Open Dataset, but can be further extended to work with other driving datasets. \n",
+ "\n",
+ "\n",
+ "
\n",
+ "\n",
+ "An example of a set of traffic scenario's in Nocturne. Upon initialization, a start time is chosen. After each iteration we take a step in the simulation, which gets us to the next scenario. This is done until we reach the end of the simulation.
\n",
+ "\n",
+ "\n",
+ "We show an example of this using `example_scenario.json`, where our traffic data is extracted from the Waymo open motion dataset:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from nocturne import Simulation\n",
+ "\n",
+ "scenario_config = {\n",
+ " \"start_time\": 0, # When to start the simulation\n",
+ " \"allow_non_vehicles\": True, # Whether to include cyclists and pedestrians\n",
+ " \"max_visible_road_points\": 10, # Maximum number of road points for a vehicle\n",
+ " \"max_visible_objects\": 10, # Maximum number of road objects for a vehicle\n",
+ " \"max_visible_traffic_lights\": 10, # Maximum number of traffic lights in constructed view\n",
+ " \"max_visible_stop_signs\": 10, # Maximum number of stop signs in constructed view\n",
+ "}\n",
+ "\n",
+ "# Create simulation\n",
+ "sim = Simulation(data_path, scenario_config)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Scenario\n",
+ "\n",
+ "A simulation consists of a set of scenarios. A scenario is a snapshot of the traffic scene at a particular timepoint. \n",
+ "\n",
+ "Here is how to create a scenario object:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Get traffic scenario at timepoint\n",
+ "scenario = sim.getScenario()"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `scenario` objects holds information we are interested in. Here are a couple of examples:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "33"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# The number of road objects in the scene\n",
+ "len(scenario.getObjects())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Total # moving objects: 15\n",
+ "\n",
+ "Object IDs of moving vehicles: \n",
+ " [0, 1, 2, 3, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32] \n"
+ ]
+ }
+ ],
+ "source": [
+ "# The road objects that moved at a particular timepoint\n",
+ "objects_that_moved = scenario.getObjectsThatMoved()\n",
+ "\n",
+ "print(f\"Total # moving objects: {len(objects_that_moved)}\\n\")\n",
+ "print(f\"Object IDs of moving vehicles: \\n {[obj.getID() for obj in objects_that_moved]} \")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "128"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Number of road lines\n",
+ "len(scenario.road_lines())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[,\n",
+ " ,\n",
+ " ,\n",
+ " ,\n",
+ " ]"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "scenario.getVehicles()[:5]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[]"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# No cyclists in this scene\n",
+ "scenario.getCyclists()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Found 2 moving vehicles in scene: [3, 32]\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Select all moving vehicles that move\n",
+ "moving_vehicles = [obj for obj in scenario.getVehicles() if obj in objects_that_moved]\n",
+ "\n",
+ "print(f\"Found {len(moving_vehicles)} moving vehicles in scene: {[vehicle.getID() for vehicle in moving_vehicles]}\")"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Ego state\n",
+ "\n",
+ "The **ego state** is an array with features that describe the current vehicle. This array holds the following information: \n",
+ "- 0: length of ego vehicle\n",
+ "- 1: width of ego vehicle\n",
+ "- 2: speed of ego vehicle\n",
+ "- 3: distance to the goal position of ego vehicle\n",
+ "- 4: angle to the goal (target azimuth) \n",
+ "- 5: desired heading at goal position\n",
+ "- 6: desired speed at goal position\n",
+ "- 7: current acceleration\n",
+ "- 8: current steering position\n",
+ "- 9: current head angle"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Selected vehicle # 3\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "array([ 4.4936213 , 1.9770377 , 0.07662283, 4.24219 , -0.05617166,\n",
+ " -0.05909407, 1.6792779 , 0. , 0. , 0. ],\n",
+ " dtype=float32)"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Select an arbitrary vehicle\n",
+ "ego_vehicle = moving_vehicles[0]\n",
+ "\n",
+ "print(f\"Selected vehicle # {ego_vehicle.getID()}\")\n",
+ "\n",
+ "# Get the state for ego vehicle\n",
+ "scenario.ego_state(ego_vehicle)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Visible state\n",
+ "\n",
+ "We use the ego vehicle state, together with a view distance (how far the vehicle can see) and a view angle to construct the **visible state**. The figure below shows this procedure for a simplified traffic scene. \n",
+ "\n",
+ "Calling `scenario.visible_state()` returns a dictionary with four matrices:\n",
+ "- `stop_signs`: The visible stop signs \n",
+ "- `traffic_lights`: The states for the traffic lights from the perspective of the ego driver(red, yellow, green).\n",
+ "- `road_points`: The observable road points (static elements in the scene).\n",
+ "- `objects`: The observable road objects (vehicles, pedestrians and cyclists).\n",
+ "\n",
+ "\n",
+ "
\n",
+ "\n",
+ "To investigate coordination under partial observability, agents in Nocturne can only see an obstructed view of their environment. In this simplified traffic scene, we construct the state for the red ego driver. Note that Nocturne assumes that stop signs can be viewed, even if they are behind another driver.