diff --git a/henry_experiments/pretrained_checkpoints/gc_pgp_checkpoint.ckpt b/experiments/pretrained_checkpoints/gc_pgp_checkpoint.ckpt similarity index 100% rename from henry_experiments/pretrained_checkpoints/gc_pgp_checkpoint.ckpt rename to experiments/pretrained_checkpoints/gc_pgp_checkpoint.ckpt diff --git a/henry_experiments/pretrained_checkpoints/pdm_offset_checkpoint.ckpt b/experiments/pretrained_checkpoints/pdm_offset_checkpoint.ckpt similarity index 100% rename from henry_experiments/pretrained_checkpoints/pdm_offset_checkpoint.ckpt rename to experiments/pretrained_checkpoints/pdm_offset_checkpoint.ckpt diff --git a/henry_experiments/pretrained_checkpoints/urbandriver_checkpoint.ckpt b/experiments/pretrained_checkpoints/urbandriver_checkpoint.ckpt similarity index 100% rename from henry_experiments/pretrained_checkpoints/urbandriver_checkpoint.ckpt rename to experiments/pretrained_checkpoints/urbandriver_checkpoint.ckpt diff --git a/henry_experiments/relavance_construction.ipynb b/experiments/relavance_construction.ipynb similarity index 100% rename from henry_experiments/relavance_construction.ipynb rename to experiments/relavance_construction.ipynb diff --git a/henry_experiments/run_sim_closed_loop.ipynb b/experiments/run_sim_closed_loop.ipynb similarity index 100% rename from henry_experiments/run_sim_closed_loop.ipynb rename to experiments/run_sim_closed_loop.ipynb diff --git a/henry_experiments/run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.14.22.55.23/hparams.yaml b/experiments/run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.14.22.55.23/hparams.yaml similarity index 100% rename from henry_experiments/run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.14.22.55.23/hparams.yaml rename to experiments/run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.14.22.55.23/hparams.yaml diff --git a/henry_experiments/run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.15.01.28.02/hparams.yaml b/experiments/run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.15.01.28.02/hparams.yaml similarity index 100% rename from henry_experiments/run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.15.01.28.02/hparams.yaml rename to experiments/run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.15.01.28.02/hparams.yaml diff --git a/experiments/test_notebook.ipynb b/experiments/test_notebook.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..33cfffacf3c5efe3c7a2472c4d336291b7d28ba9 --- /dev/null +++ b/experiments/test_notebook.ipynb @@ -0,0 +1,389 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "34460db1", + "metadata": {}, + "source": [ + "# Creating a new planner in nuPlan <a name=\"introduction\"></a>\n" + ] + }, + { + "cell_type": "markdown", + "id": "f0189156", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "373ffd1c", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_5670/4095267831.py:5: DeprecationWarning: Importing display from IPython.core.display is deprecated since IPython 7.14, please import from IPython display\n", + " from IPython.core.display import display, HTML\n" + ] + } + ], + "source": [ + "# Useful imports\n", + "import os\n", + "import hydra\n", + "import nest_asyncio\n", + "from IPython.core.display import display, HTML\n", + "from bokeh.io import output_notebook\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "13c00121", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<div class=\"bk-root\">\n", + " <a href=\"https://bokeh.org\" target=\"_blank\" class=\"bk-logo bk-logo-small bk-logo-notebook\"></a>\n", + " <span id=\"1001\">Loading BokehJS ...</span>\n", + " </div>\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n\n if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n root._bokeh_onload_callbacks = [];\n root._bokeh_is_loading = undefined;\n }\n\nconst JS_MIME_TYPE = 'application/javascript';\n const HTML_MIME_TYPE = 'text/html';\n const EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n const CLASS_NAME = 'output_bokeh rendered_html';\n\n /**\n * Render data to the DOM node\n */\n function render(props, node) {\n const script = document.createElement(\"script\");\n node.appendChild(script);\n }\n\n /**\n * Handle when an output is cleared or removed\n */\n function handleClearOutput(event, handle) {\n const cell = handle.cell;\n\n const id = cell.output_area._bokeh_element_id;\n const server_id = cell.output_area._bokeh_server_id;\n // Clean up Bokeh references\n if (id != null && id in Bokeh.index) {\n Bokeh.index[id].model.document.clear();\n delete Bokeh.index[id];\n }\n\n if (server_id !== undefined) {\n // Clean up Bokeh references\n const cmd_clean = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n cell.notebook.kernel.execute(cmd_clean, {\n iopub: {\n output: function(msg) {\n const id = msg.content.text.trim();\n if (id in Bokeh.index) {\n Bokeh.index[id].model.document.clear();\n delete Bokeh.index[id];\n }\n }\n }\n });\n // Destroy server and session\n const cmd_destroy = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n cell.notebook.kernel.execute(cmd_destroy);\n }\n }\n\n /**\n * Handle when a new output is added\n */\n function handleAddOutput(event, handle) {\n const output_area = handle.output_area;\n const output = handle.output;\n\n // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n if ((output.output_type != \"display_data\") || (!Object.prototype.hasOwnProperty.call(output.data, EXEC_MIME_TYPE))) {\n return\n }\n\n const toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n\n if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n // store reference to embed id on output_area\n output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n }\n if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n const bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n const script_attrs = bk_div.children[0].attributes;\n for (let i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n }\n\n function register_renderer(events, OutputArea) {\n\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n const toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n const props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[toinsert.length - 1]);\n element.append(toinsert);\n return toinsert\n }\n\n /* Handle when an output is cleared or removed */\n events.on('clear_output.CodeCell', handleClearOutput);\n events.on('delete.Cell', handleClearOutput);\n\n /* Handle when a new output is added */\n events.on('output_added.OutputArea', handleAddOutput);\n\n /**\n * Register the mime type and append_mime function with output_area\n */\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n /* Is output safe? */\n safe: true,\n /* Index of renderer in `output_area.display_order` */\n index: 0\n });\n }\n\n // register the mime type if in Jupyter Notebook environment and previously unregistered\n if (root.Jupyter !== undefined) {\n const events = require('base/js/events');\n const OutputArea = require('notebook/js/outputarea').OutputArea;\n\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n }\n if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n const NB_LOAD_WARNING = {'data': {'text/html':\n \"<div style='background-color: #fdd'>\\n\"+\n \"<p>\\n\"+\n \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n \"</p>\\n\"+\n \"<ul>\\n\"+\n \"<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\\n\"+\n \"<li>use INLINE resources instead, as so:</li>\\n\"+\n \"</ul>\\n\"+\n \"<code>\\n\"+\n \"from bokeh.resources import INLINE\\n\"+\n \"output_notebook(resources=INLINE)\\n\"+\n \"</code>\\n\"+\n \"</div>\"}};\n\n function display_loaded() {\n const el = document.getElementById(\"1001\");\n if (el != null) {\n el.textContent = \"BokehJS is loading...\";\n }\n if (root.Bokeh !== undefined) {\n if (el != null) {\n el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(display_loaded, 100)\n }\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n\n root._bokeh_onload_callbacks.push(callback);\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls == null || js_urls.length === 0) {\n run_callbacks();\n return null;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n root._bokeh_is_loading = css_urls.length + js_urls.length;\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n\n function on_error(url) {\n console.error(\"failed to load \" + url);\n }\n\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error.bind(null, url);\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n }\n\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error.bind(null, url);\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.3.min.js\"];\n const css_urls = [];\n\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {\n }\n ];\n\n function run_inline_js() {\n if (root.Bokeh !== undefined || force === true) {\n for (let i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\nif (force === true) {\n display_loaded();\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n } else if (force !== true) {\n const cell = $(document.getElementById(\"1001\")).parents('.cell').data().cell;\n cell.output_area.append_execute_result(NB_LOAD_WARNING)\n }\n }\n\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n run_inline_js();\n } else {\n load_libs(css_urls, js_urls, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n}(window));", + "application/vnd.bokehjs_load.v0+json": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "<style>.output_result { max-width:100% !important; }</style>" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "<style>.container { width:100% !important; }</style>" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "nest_asyncio.apply()\n", + "output_notebook()\n", + "display(HTML(\"<style>.output_result { max-width:100% !important; }</style>\"))\n", + "display(HTML(\"<style>.container { width:100% !important; }</style>\"))" + ] + }, + { + "cell_type": "markdown", + "id": "128b0d15", + "metadata": {}, + "source": [ + "# Simulating the planner <a name=\"simulation\"></a>" + ] + }, + { + "cell_type": "markdown", + "id": "db337ceb", + "metadata": {}, + "source": [ + "## Prepare the simulation config" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "11b08c6d", + "metadata": {}, + "outputs": [], + "source": [ + "from tutorials.utils.tutorial_utils import construct_simulation_hydra_paths\n", + "\n", + "# Location of paths with all simulation configs\n", + "BASE_CONFIG_PATH = os.path.join(os.getenv('NUPLAN_TUTORIAL_PATH', ''), '../nuplan/planning/script')\n", + "simulation_hydra_paths = construct_simulation_hydra_paths(BASE_CONFIG_PATH)\n", + "\n", + "# Create a temporary directory to store the simulation artifacts\n", + "\n", + "DATASET_PARAMS = [\n", + " 'scenario_builder=nuplan_mini', # use nuplan mini database (2.5h of 8 autolabeled logs in Las Vegas)\n", + " 'scenario_filter=one_continuous_log', # simulate only one log\n", + " \"scenario_filter.log_names=['2021.06.14.16.48.02_veh-12_04057_04438']\",\n", + " 'scenario_filter.limit_total_scenarios=1', # use 1 total scenarios\n", + "]\n", + "ckpt_dir = '/home/sacardoz/checkpoints/pdm_offset_checkpoint.ckpt'\n", + "#'/home/sacardoz/checkpoints/urbandriver_checkpoint.ckpt'\n", + "#\"/home/sacardoz/tutorial_vector_framework/training_simple_vector_experiment/train_default_simple_vector/2023.11.23.09.55.21/best_model/epoch.ckpt\"\n", + "#\"/home/sacardoz/training_raster_experiment/train_default_raster/2023.11.23.07.36.36/best_model/epoch.ckpt\"\n", + "# Initialize configuration management system\n", + "hydra.core.global_hydra.GlobalHydra.instance().clear() # reinitialize hydra if already initialized\n", + "hydra.initialize(config_path=simulation_hydra_paths.config_path)\n", + "\n", + "# Compose the configuration\n", + "cfg = hydra.compose(config_name=simulation_hydra_paths.config_name, overrides=[\n", + " '+simulation=closed_loop_reactive_agents',\n", + " #'model=pgm_hybrid_model',\n", + " 'planner=pdm_hybrid_planner',\n", + " f\"planner.pdm_hybrid_planner.checkpoint_path={ckpt_dir}\" ,\n", + " #'planner.ml_planner.model_config=${model}',\n", + " #f'planner.ml_planner.checkpoint_path={ckpt_dir}',\n", + " #f'observation=idm_agents_observation',\n", + " #'observation.model_config=${model}',\n", + " #f'observation.checkpoint_path={ckpt_dir}',\n", + " 'worker=sequential',\n", + " '+occlusion=true',\n", + " \"hydra.searchpath=[pkg://tuplan_garage.planning.script.config.common, pkg://tuplan_garage.planning.script.config.simulation, pkg://nuplan.planning.script.config.common, pkg://nuplan.planning.script.experiments]\",\n", + " *DATASET_PARAMS,\n", + "])" + ] + }, + { + "cell_type": "markdown", + "id": "88231b74", + "metadata": {}, + "source": [ + "## Launch simulation (within the notebook)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "161cc166", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:nuplan.planning.script.utils:Setting default NUPLAN_DATA_ROOT: /home/sacardoz/nuplan/dataset\n", + "INFO:nuplan.planning.script.utils:Setting default NUPLAN_EXP_ROOT: /home/sacardoz/nuplan/exp\n", + "Global seed set to 0\n", + "INFO:nuplan.planning.script.builders.main_callback_builder:Building MultiMainCallback...\n", + "INFO:nuplan.planning.script.builders.main_callback_builder:Building MultiMainCallback: 4...DONE!\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2023-11-28 11:02:20,269 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/worker_pool_builder.py:19} Building WorkerPool...\n", + "2023-11-28 11:02:20,270 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/utils/multithreading/worker_pool.py:101} Worker: Sequential\n", + "2023-11-28 11:02:20,270 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/utils/multithreading/worker_pool.py:102} Number of nodes: 1\n", + "Number of CPUs per node: 1\n", + "Number of GPUs per node: 0\n", + "Number of threads across all nodes: 1\n", + "2023-11-28 11:02:20,270 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/worker_pool_builder.py:27} Building WorkerPool...DONE!\n", + "2023-11-28 11:02:20,270 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:32} Building experiment folders...\n", + "2023-11-28 11:02:20,270 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:35} \n", + "\n", + "\tFolder where all results are stored: /home/sacardoz/nuplan/exp/exp/simulation/closed_loop_reactive_agents/2023.11.28.11.02.19\n", + "\n", + "2023-11-28 11:02:20,271 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/folder_builder.py:70} Building experiment folders...DONE!\n", + "2023-11-28 11:02:20,272 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_callback_builder.py:52} Building AbstractCallback...\n", + "2023-11-28 11:02:20,272 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_callback_builder.py:68} Building AbstractCallback: 0...DONE!\n", + "2023-11-28 11:02:20,272 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:49} Building simulations...\n", + "2023-11-28 11:02:20,272 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:55} Extracting scenarios...\n", + "2023-11-28 11:02:20,272 INFO {/home/sacardoz/nuplan-devkit/nuplan/common/utils/distributed_scenario_filter.py:83} Building Scenarios in mode DistributedMode.SINGLE_NODE\n", + "2023-11-28 11:02:20,272 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/scenario_building_builder.py:18} Building AbstractScenarioBuilder...\n", + "2023-11-28 11:02:20,284 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/scenario_building_builder.py:21} Building AbstractScenarioBuilder...DONE!\n", + "2023-11-28 11:02:20,284 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/scenario_filter_builder.py:35} Building ScenarioFilter...\n", + "2023-11-28 11:02:20,285 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/scenario_filter_builder.py:44} Building ScenarioFilter...DONE!\n", + "2023-11-28 11:02:20,302 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:76} Building metric engines...\n", + "2023-11-28 11:02:20,327 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:78} Building metric engines...DONE\n", + "2023-11-28 11:02:20,327 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:82} Building simulations from 1 scenarios...\n", + "2023-11-28 11:02:20,786 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/script/builders/simulation_builder.py:142} Building simulations...DONE!\n" + ] + } + ], + "source": [ + "from nuplan.planning.script.run_simulation import build_simulation_runners\n", + "from nuplan.common.actor_state.tracked_objects_types import AGENT_TYPES, STATIC_OBJECT_TYPES, TrackedObjectType\n", + "\n", + "# Run the simulation loop (real-time visualization not yet supported, see next section for visualization)\n", + "runners, common_builder, cfg = build_simulation_runners(cfg)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "223284d4", + "metadata": {}, + "outputs": [], + "source": [ + "runner = runners[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "90b79421", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + } + ], + "source": [ + "runner.simulation.callback.on_simulation_start(runner.simulation.setup)\n", + "\n", + "# Initialize all simulations\n", + "runner._initialize()\n", + "\n", + "while runner.simulation.is_simulation_running():\n", + " # Execute specific callback\n", + " runner.simulation.callback.on_step_start(runner.simulation.setup, runner.planner)\n", + "\n", + " # Perform step\n", + " planner_input = runner._simulation.get_planner_input()\n", + "\n", + " # Execute specific callback\n", + " runner._simulation.callback.on_planner_start(runner.simulation.setup, runner.planner)\n", + "\n", + " # Plan path based on all planner's inputs\n", + " trajectory = runner.planner.compute_trajectory(planner_input)\n", + "\n", + " # Propagate simulation based on planner trajectory\n", + " runner._simulation.callback.on_planner_end(runner.simulation.setup, runner.planner, trajectory)\n", + "\n", + " iteration = runner.simulation._time_controller.get_iteration()\n", + "\n", + " runner.simulation.propagate(trajectory)\n", + "\n", + " # Execute specific callback\n", + " runner.simulation.callback.on_step_end(runner.simulation.setup, runner.planner, runner.simulation.history.last())\n", + "\n", + "runner.simulation.callback.on_simulation_end(runner.simulation.setup, runner.planner, runner.simulation.history)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e6c22f5f", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:bokeh.server.server:Starting Bokeh server version 2.4.3 (running on Tornado 6.3.3)\n", + "WARNING:bokeh.server.util:Host wildcard '*' will allow connections originating from multiple (or possibly all) hostnames or IPs. Use non-wildcard values to restrict access explicitly\n", + "INFO:bokeh.server.tornado:User authentication hooks NOT provided (default user enabled)\n" + ] + }, + { + "data": { + "application/vnd.bokehjs_exec.v0+json": "", + "text/html": [ + "<script id=\"1003\">\n", + " (function() {\n", + " const xhr = new XMLHttpRequest()\n", + " xhr.responseType = 'blob';\n", + " xhr.open('GET', \"http://localhost:8888/autoload.js?bokeh-autoload-element=1003&bokeh-absolute-url=http://localhost:8888&resources=none\", true);\n", + " xhr.onload = function (event) {\n", + " const script = document.createElement('script');\n", + " const src = URL.createObjectURL(event.target.response);\n", + " script.src = src;\n", + " document.body.appendChild(script);\n", + " };\n", + " xhr.send();\n", + " })();\n", + "</script>" + ] + }, + "metadata": { + "application/vnd.bokehjs_exec.v0+json": { + "server_id": "09119f1b4c254247b9a8a2c9feb5a3bb" + } + }, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2023-11-28 11:03:23,128 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140} Could not open Parquet input source '<Buffer>': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n", + "2023-11-28 11:03:23,129 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140} Could not open Parquet input source '<Buffer>': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n", + "2023-11-28 11:03:23,129 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140} Error creating dataset. Could not read schema from 'pretrained_checkpoints/gc_pgp_checkpoint.ckpt'. Is this a 'parquet' file?: Could not open Parquet input source 'pretrained_checkpoints/gc_pgp_checkpoint.ckpt': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n", + "2023-11-28 11:03:23,129 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140} Could not open Parquet input source '<Buffer>': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n", + "2023-11-28 11:03:23,130 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/experiment_file_data.py:140} Error creating dataset. Could not read schema from 'run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.14.22.55.23/hparams.yaml'. Is this a 'parquet' file?: Could not open Parquet input source 'run_sim_closed_loop/training_raster_experiment/train_default_raster/2023.11.14.22.55.23/hparams.yaml': Parquet magic bytes not found in footer. Either the file is corrupted or this is not a parquet file.\n", + "2023-11-28 11:03:23,132 INFO {/home/sacardoz/nuplan-devkit/nuplan/planning/nuboard/base/simulation_tile.py:172} Minimum frame time=0.017 s\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Rendering a scenario: 100%|██████████| 1/1 [00:00<00:00, 7.03it/s]\n", + "WARNING:bokeh.core.validation.check:W-1000 (MISSING_RENDERERS): Plot has no renderers: Figure(id='1005', ...)\n", + "INFO:tornado.access:200 GET /autoload.js?bokeh-autoload-element=1003&bokeh-absolute-url=http://localhost:8888&resources=none (::1) 959.86ms\n", + "INFO:bokeh.server.views.ws:WebSocket connection opened\n", + "INFO:tornado.access:101 GET /ws?id=7cadc218-0d10-4ad2-90c5-86e1be250248&origin=da2f890a-eb18-4637-9199-dd0f06169aef&swVersion=4&extensionId=&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app&purpose=notebookRenderer (::1) 0.62ms\n", + "INFO:bokeh.server.views.ws:ServerConnection created\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2023-11-28 11:03:24,076 INFO {/home/sacardoz/miniconda3/envs/nuplan/lib/python3.9/site-packages/tornado/web.py:2344} 200 GET /autoload.js?bokeh-autoload-element=1003&bokeh-absolute-url=http://localhost:8888&resources=none (::1) 959.86ms\n", + "2023-11-28 11:03:24,083 INFO {/home/sacardoz/nuplan-devkit/tutorials/utils/tutorial_utils.py:267} Done rendering!\n", + "2023-11-28 11:03:24,084 INFO {/home/sacardoz/miniconda3/envs/nuplan/lib/python3.9/site-packages/tornado/web.py:2344} 101 GET /ws?id=7cadc218-0d10-4ad2-90c5-86e1be250248&origin=da2f890a-eb18-4637-9199-dd0f06169aef&swVersion=4&extensionId=&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app&purpose=notebookRenderer (::1) 0.62ms\n" + ] + } + ], + "source": [ + "from tutorials.utils.tutorial_utils import visualize_history\n", + "visualize_history(runner.simulation._history, runner.scenario, bokeh_port=8888)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "nuplan", + "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.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/nuplan/planning/nuboard/base/plot_data.py b/nuplan/planning/nuboard/base/plot_data.py index d74f8c66115878f2514b983721fc75add09c9e7c..b61a533e014c74dbdfff16a1a99fbc2a5873f49f 100644 --- a/nuplan/planning/nuboard/base/plot_data.py +++ b/nuplan/planning/nuboard/base/plot_data.py @@ -41,6 +41,7 @@ class BokehAgentStates(NamedTuple): velocity_ys: List[float] # [m/s], A list of velocity in y (body frame). speeds: List[float] # [m/s], A list of speed. headings: List[float] # [m], a list of headings + occluded: List[bool] # A list of occlusion flags @dataclass(frozen=True) @@ -451,12 +452,14 @@ class AgentStatePlot(BaseScenarioPlot): for category, data_source in data_sources.items(): plot = self.plots.get(category, None) data = dict(data_source.data) + agent_color = simulation_tile_agent_style.get(category) + data['occ_color'] = ['#000000' if occ else agent_color["fill_color"] for occ in data['occluded']] + if plot is None: - agent_color = simulation_tile_agent_style.get(category) self.plots[category] = main_figure.multi_polygons( xs="xs", ys="ys", - fill_color=agent_color["fill_color"], + fill_color={"field": "occ_color"}, fill_alpha=agent_color["fill_alpha"], line_color=agent_color["line_color"], line_width=agent_color["line_width"], @@ -473,6 +476,7 @@ class AgentStatePlot(BaseScenarioPlot): ("heading [rad]", "@headings{0.2f}"), ("type", "@agent_type"), ("track token", "@track_token"), + ("occluded", "@occluded"), ], ) main_figure.add_tools(agent_hover) @@ -499,6 +503,7 @@ class AgentStatePlot(BaseScenarioPlot): tracked_objects = sample.observation.tracked_objects frame_dict = {} + time_us = sample.iteration.time_us for tracked_object_type_name, tracked_object_type in tracked_object_types.items(): corner_xs = [] corner_ys = [] @@ -511,6 +516,7 @@ class AgentStatePlot(BaseScenarioPlot): velocity_ys = [] speeds = [] headings = [] + occluded = [] for tracked_object in tracked_objects.get_tracked_objects_of_type(tracked_object_type): agent_corners = tracked_object.box.all_corners() @@ -530,6 +536,11 @@ class AgentStatePlot(BaseScenarioPlot): track_ids.append(self._get_track_id(tracked_object.track_token)) track_tokens.append(tracked_object.track_token) + if history.occlusion_masks is not None: + occluded.append(tracked_object.track_token not in history.occlusion_masks[time_us]) + else: + occluded.append(False) + agent_states = BokehAgentStates( xs=corner_xs, ys=corner_ys, @@ -542,6 +553,7 @@ class AgentStatePlot(BaseScenarioPlot): velocity_ys=velocity_ys, speeds=speeds, headings=headings, + occluded=occluded, ) frame_dict[tracked_object_type_name] = ColumnDataSource(agent_states._asdict()) diff --git a/nuplan/planning/script/builders/occlusion_manager_builder.py b/nuplan/planning/script/builders/occlusion_manager_builder.py new file mode 100644 index 0000000000000000000000000000000000000000..2b75cc59d7d4c7b922ced2a140268924e04f6d21 --- /dev/null +++ b/nuplan/planning/script/builders/occlusion_manager_builder.py @@ -0,0 +1,18 @@ + +from omegaconf import DictConfig + +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.simulation.occlusion.abstract_occlusion_manager import AbstractOcclusionManager +from nuplan.planning.simulation.occlusion.range_occlusion_manager import RangeOcclusionManager + +def build_occlusion_manager(occlusion_cfg: DictConfig, scenario: AbstractScenario) -> AbstractOcclusionManager: + """ + Instantiate occlusion_manager + :param occlusion_cfg: config of a occlusion_manager + :param scenario: scenario + :return occlusion_cfg + """ + # Placeholder + occlusion_manager: AbstractOcclusionManager = RangeOcclusionManager(scenario) + + return occlusion_manager diff --git a/nuplan/planning/script/builders/simulation_builder.py b/nuplan/planning/script/builders/simulation_builder.py index 1d35ab7af41cb7c488053277a72ac661b8a47cd7..52abd74a11c3fc7fc3104946adc9b33a3da52f44 100644 --- a/nuplan/planning/script/builders/simulation_builder.py +++ b/nuplan/planning/script/builders/simulation_builder.py @@ -9,6 +9,7 @@ from nuplan.common.utils.distributed_scenario_filter import DistributedMode, Dis from nuplan.planning.scenario_builder.nuplan_db.nuplan_scenario_builder import NuPlanScenarioBuilder from nuplan.planning.script.builders.metric_builder import build_metrics_engines from nuplan.planning.script.builders.observation_builder import build_observations +from nuplan.planning.script.builders.occlusion_manager_builder import build_occlusion_manager from nuplan.planning.script.builders.planner_builder import build_planners from nuplan.planning.script.builders.utils.utils_type import is_target_type from nuplan.planning.simulation.callback.abstract_callback import AbstractCallback @@ -16,6 +17,7 @@ from nuplan.planning.simulation.callback.metric_callback import MetricCallback from nuplan.planning.simulation.callback.multi_callback import MultiCallback from nuplan.planning.simulation.controller.abstract_controller import AbstractEgoController from nuplan.planning.simulation.observation.abstract_observation import AbstractObservation +from nuplan.planning.simulation.occlusion.abstract_occlusion_manager import AbstractOcclusionManager from nuplan.planning.simulation.planner.abstract_planner import AbstractPlanner from nuplan.planning.simulation.runner.simulations_runner import SimulationRunner from nuplan.planning.simulation.simulation import Simulation @@ -103,6 +105,12 @@ def build_simulations( # Perception observations: AbstractObservation = build_observations(cfg.observation, scenario=scenario) + # Occlusions + if 'occlusion' in cfg.keys() and cfg.occlusion: + occlusion_manager: AbstractOcclusionManager = build_occlusion_manager(cfg.occlusion, scenario=scenario) + else: + occlusion_manager = None + # Metric Engine metric_engine = metric_engines_map.get(scenario.scenario_type, None) if metric_engine is not None: @@ -120,6 +128,7 @@ def build_simulations( time_controller=simulation_time_controller, observations=observations, ego_controller=ego_controller, + occlusion_manager=occlusion_manager, scenario=scenario, ) diff --git a/nuplan/planning/script/run_simulation.py b/nuplan/planning/script/run_simulation.py index d97f24611440b72d59a5cf7db563118f45c9b6ab..9e3e5cff00c21868b1afcc9dc35d52371cce640f 100644 --- a/nuplan/planning/script/run_simulation.py +++ b/nuplan/planning/script/run_simulation.py @@ -34,6 +34,42 @@ if os.path.basename(CONFIG_PATH) != 'simulation': CONFIG_NAME = 'default_simulation' +def build_simulation_runners(cfg: DictConfig, planners: Optional[Union[AbstractPlanner, List[AbstractPlanner]]] = None): + # Fix random seed + pl.seed_everything(cfg.seed, workers=True) + + profiler_name = 'building_simulation' + common_builder = set_up_common_builder(cfg=cfg, profiler_name=profiler_name) + + # Build simulation callbacks + callbacks_worker_pool = build_callbacks_worker(cfg) + callbacks = build_simulation_callbacks(cfg=cfg, output_dir=common_builder.output_dir, worker=callbacks_worker_pool) + + # Remove planner from config to make sure run_simulation does not receive multiple planner specifications. + if planners and 'planner' in cfg.keys(): + logger.info('Using pre-instantiated planner. Ignoring planner in config') + OmegaConf.set_struct(cfg, False) + cfg.pop('planner') + OmegaConf.set_struct(cfg, True) + + # Construct simulations + if isinstance(planners, AbstractPlanner): + planners = [planners] + + runners = build_simulations( + cfg=cfg, + callbacks=callbacks, + worker=common_builder.worker, + pre_built_planners=planners, + callbacks_worker=callbacks_worker_pool, + ) + + if common_builder.profiler: + # Stop simulation construction profiling + common_builder.profiler.save_profiler(profiler_name) + + return runners, common_builder, cfg + def run_simulation(cfg: DictConfig, planners: Optional[Union[AbstractPlanner, List[AbstractPlanner]]] = None) -> None: """ Execute all available challenges simultaneously on the same scenario. Helper function for main to allow planner to diff --git a/nuplan/planning/simulation/history/simulation_history.py b/nuplan/planning/simulation/history/simulation_history.py index 65bd30a98c8a0b52e23d11b0e9c4e1ddda0db54f..d2b7042de5b7e0085303990ae03ab4005f3160f8 100644 --- a/nuplan/planning/simulation/history/simulation_history.py +++ b/nuplan/planning/simulation/history/simulation_history.py @@ -41,6 +41,8 @@ class SimulationHistory: """ self.map_api: AbstractMap = map_api self.mission_goal = mission_goal + # NOTE: This is just for visualization code, not used during simulation + self.occlusion_masks = None self.data: List[SimulationHistorySample] = data if data is not None else list() diff --git a/nuplan/planning/simulation/occlusion/abstract_occlusion_manager.py b/nuplan/planning/simulation/occlusion/abstract_occlusion_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..654c0af62185ff9a145783ba46160c8ecd393abd --- /dev/null +++ b/nuplan/planning/simulation/occlusion/abstract_occlusion_manager.py @@ -0,0 +1,74 @@ +from abc import ABCMeta, abstractmethod +from collections import deque +from typing import Tuple + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.tracked_objects import TrackedObjects + +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.simulation.history.simulation_history_buffer import SimulationHistoryBuffer +from nuplan.planning.simulation.observation.observation_type import DetectionsTracks, Observation + + +class AbstractOcclusionManager(metaclass=ABCMeta): + """ + Interface for a generic occlusion manager. + """ + + def __init__( + self, + scenario: AbstractScenario + ): + self._visible_agent_cache = {} + self.scenario = scenario + + def reset(self) -> None: + """ + Resets occlusion manager cache. + """ + self._visible_agent_cache = {} + + def occlude_input(self, input_buffer: SimulationHistoryBuffer) -> SimulationHistoryBuffer: + """ + Occludes SimulationHistoryBuffer input. Loops through each timestep defined by time_us, + checks to see if timestep is already contained in _visible_agent_cache and computes + occlusions if not, and occludes timestep using cached results. Repacks output in + SimulationHistoryBuffer. + """ + + ego_state_buffer = input_buffer.ego_state_buffer + observations_buffer = input_buffer.observation_buffer + sample_interval = input_buffer.sample_interval + + for ego_state, observations in zip(ego_state_buffer, observations_buffer): + if ego_state.time_us not in self._visible_agent_cache: + self._visible_agent_cache[ego_state.time_us] = self._compute_visible_agents(ego_state, observations) + + output_buffer = SimulationHistoryBuffer(ego_state_buffer, \ + deque([self._mask_input(ego_state.time_us, observations) for ego_state, observations in zip(ego_state_buffer, observations_buffer)]), \ + sample_interval) + + return output_buffer + + @abstractmethod + def _compute_visible_agents(self, ego_state: EgoState, observations: DetectionsTracks) -> set: + """ + Returns set of track tokens that represents the observations visible to the ego + at this time step. + """ + pass + + def _mask_input(self, time_us: int, observations: DetectionsTracks) -> DetectionsTracks: + """ + Occludes observations at timestep time_us based on cached occlusions. + """ + + assert time_us in self._visible_agent_cache, "Attempted to mask non-cached timestep!" + assert isinstance(observations, DetectionsTracks), "Occlusions only support DetectionsTracks." + + mask = self._visible_agent_cache[time_us] + tracks = observations.tracked_objects.tracked_objects + + visible_tracks = [track for track in tracks if track.metadata.track_token in mask] + + return DetectionsTracks(tracked_objects=TrackedObjects(visible_tracks)) diff --git a/nuplan/planning/simulation/occlusion/range_occlusion_manager.py b/nuplan/planning/simulation/occlusion/range_occlusion_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..9d513fb42fb818206b339a25739788a14b1276bf --- /dev/null +++ b/nuplan/planning/simulation/occlusion/range_occlusion_manager.py @@ -0,0 +1,38 @@ +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.simulation.observation.observation_type import DetectionsTracks +from nuplan.planning.simulation.occlusion.abstract_occlusion_manager import AbstractOcclusionManager + + +class RangeOcclusionManager(AbstractOcclusionManager): + """ + Range occlusion manager. Occludes all objects outside of a given + range of the ego. + """ + + def __init__( + self, + scenario: AbstractScenario, + range_threshold: float = 25 + ): + super().__init__(scenario) + self.range_threshold = range_threshold + + def _compute_visible_agents(self, ego_state: EgoState, observations: DetectionsTracks) -> set: + """ + Returns set of track tokens that represents the observations visible to the ego + at this time step. + """ + + # Visible track token set + not_occluded = set() + + # Loop through observations and check if it's closer to the ego then range_threshold, + # add to output set if so. + for track in observations.tracked_objects.tracked_objects: + if ((ego_state.center.x - track.center.x) ** 2 + \ + (ego_state.center.y - track.center.y) ** 2) ** 0.5 <= self.range_threshold: + not_occluded.add(track.metadata.track_token) + + return not_occluded + \ No newline at end of file diff --git a/nuplan/planning/simulation/simulation.py b/nuplan/planning/simulation/simulation.py index 243d8d345c42c86e1f8be18497185b26329c0c55..dda8c01b87aecbdb09bb6e3f193a04742a46b0cd 100644 --- a/nuplan/planning/simulation/simulation.py +++ b/nuplan/planning/simulation/simulation.py @@ -45,6 +45,7 @@ class Simulation: self._time_controller = simulation_setup.time_controller self._ego_controller = simulation_setup.ego_controller self._observations = simulation_setup.observations + self._occlusion_manager = simulation_setup.occlusion_manager self._scenario = simulation_setup.scenario self._callback = MultiCallback([]) if callback is None else callback @@ -136,8 +137,13 @@ class Simulation: # Extract traffic light status data traffic_light_data = list(self._scenario.get_traffic_light_status_at_iteration(iteration.index)) + + history_input = self._history_buffer + if self._occlusion_manager is not None: + history_input = self._occlusion_manager.occlude_input(history_input) + logger.debug(f"Executing {iteration.index}!") - return PlannerInput(iteration=iteration, history=self._history_buffer, traffic_light_data=traffic_light_data) + return PlannerInput(iteration=iteration, history=history_input, traffic_light_data=traffic_light_data) def propagate(self, trajectory: AbstractTrajectory) -> None: """ @@ -162,6 +168,9 @@ class Simulation: self._history.add_sample( SimulationHistorySample(iteration, ego_state, trajectory, observation, traffic_light_status) ) + + if self._occlusion_manager: + self._history.occlusion_masks = self._occlusion_manager._visible_agent_cache # Propagate state to next iteration next_iteration = self._time_controller.next_iteration() diff --git a/nuplan/planning/simulation/simulation_setup.py b/nuplan/planning/simulation/simulation_setup.py index 34da9d3627e2b41735a6fed6e3767def17b02b41..03b3d39eefef9ce0131d6719efb46f909d2f416d 100644 --- a/nuplan/planning/simulation/simulation_setup.py +++ b/nuplan/planning/simulation/simulation_setup.py @@ -3,6 +3,7 @@ from dataclasses import dataclass from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario from nuplan.planning.simulation.controller.abstract_controller import AbstractEgoController from nuplan.planning.simulation.observation.abstract_observation import AbstractObservation +from nuplan.planning.simulation.occlusion.abstract_occlusion_manager import AbstractOcclusionManager from nuplan.planning.simulation.planner.abstract_planner import AbstractPlanner from nuplan.planning.simulation.simulation_time_controller.abstract_simulation_time_controller import ( AbstractSimulationTimeController, @@ -16,6 +17,7 @@ class SimulationSetup: time_controller: AbstractSimulationTimeController observations: AbstractObservation ego_controller: AbstractEgoController + occlusion_manager: AbstractOcclusionManager scenario: AbstractScenario def __post_init__(self) -> None: @@ -30,7 +32,7 @@ class SimulationSetup: assert isinstance( self.ego_controller, AbstractEgoController ), 'Error: ego_controller must inherit from AbstractEgoController!' - + def reset(self) -> None: """ Reset all simulation controllers @@ -39,6 +41,9 @@ class SimulationSetup: self.ego_controller.reset() self.time_controller.reset() + if self.occlusion_manager: + self.occlusion_manager.reset() + def validate_planner_setup(setup: SimulationSetup, planner: AbstractPlanner) -> None: """ diff --git a/tutorials/utils/tutorial_utils.py b/tutorials/utils/tutorial_utils.py index 714b7cd2589252bcead07f7284e0c966be4f65f3..2406af9943d3a0b26e7233ca7de359168f377ae0 100644 --- a/tutorials/utils/tutorial_utils.py +++ b/tutorials/utils/tutorial_utils.py @@ -213,6 +213,23 @@ def serialize_scenario( return simulation_history + +def visualize_history( + simulation_history: SimulationHistory, scenario: NuPlanScenario, save_dir: str = '/tmp/scenario_visualization/', bokeh_port: int = 8899 +) -> None: + """ + Visualize a scenario in Bokeh. + :param scenario: Scenario object to be visualized. + :param save_dir: Dir to save serialization and visualization artifacts. + :param bokeh_port: Port that the server bokeh starts to render the generate the visualization will run on. + """ + map_factory = NuPlanMapFactory(get_maps_db(map_root=scenario.map_root, map_version=scenario.map_version)) + + simulation_scenario_key = save_scenes_to_dir( + scenario=scenario, save_dir=save_dir, simulation_history=simulation_history + ) + visualize_scenarios([simulation_scenario_key], map_factory, Path(save_dir), bokeh_port=bokeh_port) + def visualize_scenario( scenario: NuPlanScenario, save_dir: str = '/tmp/scenario_visualization/', bokeh_port: int = 8899 ) -> None: