From 2bd5482c2b4ab8c4ccc06bebbd7ed15252ae2f27 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Wed, 4 Dec 2024 15:05:27 -0800 Subject: [PATCH 01/13] Cleaned up formatting and imports. --- invertedai/utils.py | 89 ++++++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 25 deletions(-) diff --git a/invertedai/utils.py b/invertedai/utils.py index e7b3ff6..e167cd7 100644 --- a/invertedai/utils.py +++ b/invertedai/utils.py @@ -1,19 +1,16 @@ import json import os import re -import numpy as np import csv import math import logging import random import time - +import numpy as np from typing import Dict, Optional, List, Tuple, Union, Any -from tqdm.contrib import tmap -from itertools import product from copy import deepcopy -from pydantic import validate_call, validate_arguments, BaseModel, ConfigDict +from pydantic import validate_call, validate_arguments import requests from requests import Response @@ -31,15 +28,17 @@ import invertedai.api import invertedai.api.config from invertedai import error -from invertedai.common import AgentState, AgentAttributes, AgentProperties, StaticMapActor,\ - TrafficLightState, TrafficLightStatesDict, Point, RecurrentState,\ - LightRecurrentState from invertedai.future import to_thread from invertedai.error import InvertedAIError -from invertedai.api.initialize import InitializeResponse -from invertedai.api.drive import DriveResponse -from invertedai.api.location import LocationResponse - +from invertedai.common import ( + AgentState, + AgentAttributes, + AgentProperties, + RecurrentState, + StaticMapActor, + TrafficLightState, + TrafficLightStatesDict +) H_SCALE = 10 text_x_offset = 0 @@ -59,7 +58,6 @@ 500: "The server encountered an unexpected issue. We're working to resolve this. Please try again later.", } - class Session: def __init__(self): self.session = requests.Session() @@ -160,7 +158,11 @@ def should_log(self, retry_count): def base_url(self, value): self._base_url = value - def _verify_api_key(self, api_token: str, verifying_url: str): + def _verify_api_key( + self, + api_token: str, + verifying_url: str + ): """ Verifies the API key by making a request to the verifying URL. @@ -229,18 +231,28 @@ def add_apikey( request_url = url self.base_url = self._verify_api_key(api_token, request_url) - def use_mock_api(self, use_mock: bool = True) -> None: + def use_mock_api( + self, + use_mock: bool = True + ) -> None: invertedai.api.config.mock_api = use_mock if use_mock: iai.logger.warning( "Using mock Inverted AI API - predictions will be trivial" ) - async def async_request(self, *args, **kwargs): + async def async_request( + self, + *args, + **kwargs + ): return await to_thread(self.request, *args, **kwargs) def request( - self, model: str, params: Optional[dict] = None, data: Optional[dict] = None + self, + model: str, + params: Optional[dict] = None, + data: Optional[dict] = None ): method, relative_path = iai.model_resources[model] response = self._request( @@ -369,7 +381,14 @@ def _get_base_url(self) -> str: # TODO: Add endpoint option and versioning to base_url return base_url - def _handle_error_response(self, rbody, rcode, resp, rheaders, stream_error=False): + def _handle_error_response( + self, + rbody, + rcode, + resp, + rheaders, + stream_error=False + ): try: error_data = resp["error"] except (KeyError, TypeError): @@ -427,7 +446,10 @@ def _handle_error_response(self, rbody, rcode, resp, rheaders, stream_error=Fals error_data.get("message"), rbody, rcode, resp, rheaders ) - def _interpret_response_line(self, result): + def _interpret_response_line( + self, + result + ): rbody = result.content rcode = result.status_code rheaders = result.headers @@ -482,7 +504,9 @@ def get_default_agent_properties( return agent_attributes_list @validate_call -def convert_attributes_to_properties(attributes: AgentAttributes) -> AgentProperties: +def convert_attributes_to_properties( + attributes: AgentAttributes +) -> AgentProperties: """ Convert deprecated AgentAttributes data type to AgentProperties. """ @@ -593,10 +617,16 @@ def iai_conditional_initialize( class APITokenAuth(AuthBase): - def __init__(self, api_token): + def __init__( + self, + api_token + ): self.api_token = api_token - def __call__(self, r): + def __call__( + self, + r + ): r.headers["x-api-key"] = self.api_token r.headers["api-key"] = self.api_token return r @@ -649,11 +679,17 @@ def __init__(self): self.int_slider.observe(self.update, "value") self.children = [controls, output] - def update(self, change): + def update( + self, + change + ): self.im.set_data(self.buffer[self.int_slider.value]) self.fig.canvas.draw() - def add_frame(self, frame): + def add_frame( + self, + frame + ): self.buffer.append(frame) self.int_slider.max += 1 self.play.max += 1 @@ -690,7 +726,10 @@ def __init__( self.addHandler(file_handler) @staticmethod - def logfmt(message, **params): + def logfmt( + message, + **params + ): props = dict(message=message, **params) def fmt(key, val): From cdec6475fa4918ef178cd1c479518168d2fb703a Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Thu, 5 Dec 2024 13:20:11 -0800 Subject: [PATCH 02/13] Updated large initialize helper functions to account for pedestrians and updated area width and height. --- examples/large_map_example.py | 7 ++-- examples/scenario_log_example.py | 3 +- invertedai/api/drive.py | 4 +- invertedai/api/initialize.py | 35 ++++++++-------- invertedai/large/initialize.py | 70 +++++++++++++++++++++++--------- invertedai/utils.py | 8 +--- 6 files changed, 76 insertions(+), 51 deletions(-) diff --git a/examples/large_map_example.py b/examples/large_map_example.py index 250c3d0..05f8bf8 100644 --- a/examples/large_map_example.py +++ b/examples/large_map_example.py @@ -1,7 +1,6 @@ import invertedai as iai from invertedai.large.common import Region -from invertedai.common import AgentAttributes -from invertedai.utils import get_default_agent_properties +from invertedai.common import AgentAttributes, AgentType import argparse from tqdm import tqdm @@ -30,8 +29,8 @@ def main(args): print(f"Begin initialization.") regions = iai.get_regions_default( location = args.location, - total_num_agents = args.num_agents, - area_shape = (int(args.width/2),int(args.height/2)), + agent_count_dict = {AgentType.car: args.num_agents}, + area_shape = (int(args.width),int(args.height)), map_center = map_center, ) diff --git a/examples/scenario_log_example.py b/examples/scenario_log_example.py index 1b13637..b9c8d36 100644 --- a/examples/scenario_log_example.py +++ b/examples/scenario_log_example.py @@ -1,5 +1,6 @@ import invertedai as iai from invertedai.utils import get_default_agent_properties +from invertedai.common import AgentType import os from random import randint @@ -18,7 +19,7 @@ # initialize the simulation by spawning NPCs response = iai.initialize( location=LOCATION, # select one of available locations - agent_properties=get_default_agent_properties({"car":5}), # number of NPCs to spawn + agent_properties=get_default_agent_properties({AgentType.car:5}), # number of NPCs to spawn ) agent_properties = response.agent_properties # get dimension and other attributes of NPCs diff --git a/invertedai/api/drive.py b/invertedai/api/drive.py index 2cb8889..b9a902f 100644 --- a/invertedai/api/drive.py +++ b/invertedai/api/drive.py @@ -57,6 +57,8 @@ def drive( api_model_version: Optional[str] = None ) -> DriveResponse: """ + Update the state of all given agents forward one time step. Agents are identified by their list index. + Parameters ---------- location: @@ -69,7 +71,7 @@ def drive( speed: [float] in m/s. agent_attributes: - Static attributes of all agents. + Deprecated. Static attributes of all agents. List of agent attributes. Each agent requires, length: [float] width: [float] and rear_axis_offset: [float] all in meters. agent_type: [str], currently supports 'car' and 'pedestrian'. diff --git a/invertedai/api/initialize.py b/invertedai/api/initialize.py index 7dc10b0..4c66119 100644 --- a/invertedai/api/initialize.py +++ b/invertedai/api/initialize.py @@ -17,15 +17,15 @@ get_mock_light_recurrent_states ) from invertedai.common import ( - RecurrentState, - AgentState, AgentAttributes, AgentProperties, - TrafficLightStatesDict, + AgentState, Image, InfractionIndicators, - LightRecurrentStates, LightRecurrentState, + LightRecurrentStates, + RecurrentState, + TrafficLightStatesDict ) @@ -33,7 +33,6 @@ class InitializeResponse(BaseModel): """ Response returned from an API call to :func:`iai.initialize`. """ - agent_states: List[AgentState] #: Initial states of all initialized agents. recurrent_states: List[Optional[RecurrentState]] #: To pass to :func:`iai.drive` at the first time step. agent_attributes: List[Optional[AgentAttributes]] #: Static attributes of all initialized agents. @@ -61,18 +60,16 @@ def initialize( ) -> InitializeResponse: """ Initializes a simulation in a given location, using a combination of **user-defined** and **sampled** agents. - **User-defined** agents are placed in a scene first, after which a number of agents are sampled conditionally - inferred from the `agent_count` argument. - If **user-defined** agents are desired, `states_history` must contain a list of `AgentState's` of all **user-defined** - agents per historical time step. - Any **user-defined** agent must have a corresponding fully specified static `AgentAttribute` in `agent_attributes`. - Any **sampled** agents not specified in `agent_attributes` will be generated with default static attribute values however **sampled** - agents may be defined by specifying `agent_type` only. - Agents are identified by their list index, so ensure the indices of each agent match in `states_history` and - `agent_attributes` when applicable. - If traffic lights are present in the scene, their states history can be specified with a list of `TrafficLightStatesDict`, each represent light states for one timestep, - with the last element representing the current time step. It is legal to omit the traffic light state specification, - and the scene will be initialized with a light state configuration consistent with agent states. + The `agent_properties` parameter is used to determine the agents that are placed into the given `location`. + **User-defined** agents are placed into a scene first, after which a number of agents are sampled conditionally. + Any **user-defined** agent must have a corresponding fully specified static `AgentProperties` object in `agent_properties`. + Furthermore for all **user-defined** agents, `states_history` must contain a list of `AgentState's` of all **user-defined** agents per historical time step. + Per desired **sampled** agent, an `AgentProperties` object must be provided at the end of `agent_properties` with only its `agent_type` specified. + Agents are identified by their list index, so ensure the indices of each agent match in `states_history` and`agent_properties` when applicable. + The `agent_attributes` and `agent_count` parameters are deprecated. + If traffic lights are present in the scene, their states history can be specified with a list of `TrafficLightStatesDict`, each + represent light states for one timestep, with the last element representing the current time step. It is legal to omit the traffic + light state specification, and the scene will be initialized with a light state configuration consistent with agent states. Every simulation must start with a call to this function in order to obtain correct recurrent states for :func:`drive`. Parameters @@ -81,11 +78,11 @@ def initialize( Location name in IAI format. agent_attributes: - Static attributes for all agents. + Deprecated. Static attributes for all agents. The pre-defined agents should be specified first, followed by the sampled agents. The optional waypoint passed will be ignored for Initialize. agent_properties: - Agent properties for all agents, replacing soon to be deprecated `agent_attributes`. + Agent properties for all agents, replacing the deprecated `agent_attributes`. The pre-defined agents should be specified first, followed by the sampled agents. The optional waypoint passed will be ignored for Initialize. max_speed: optional [float], the desired maximum speed of the agent in m/s. diff --git a/invertedai/large/initialize.py b/invertedai/large/initialize.py index 52d79d1..5620ebc 100644 --- a/invertedai/large/initialize.py +++ b/invertedai/large/initialize.py @@ -1,19 +1,26 @@ import time -from pydantic import BaseModel, validate_call -from typing import Union, List, Optional, Tuple -from itertools import product -from tqdm.contrib import tenumerate import numpy as np + from random import choices, seed, randint from math import sqrt from copy import deepcopy +from pydantic import BaseModel, validate_call +from typing import Union, List, Optional, Tuple, Dict +from itertools import product +from tqdm.contrib import tenumerate import invertedai as iai from invertedai.large.common import Region, REGION_MAX_SIZE from invertedai.api.initialize import InitializeResponse -from invertedai.common import TrafficLightStatesDict, Point, AgentProperties, AgentState from invertedai.utils import get_default_agent_properties from invertedai.error import InvertedAIError +from invertedai.common import ( + AgentProperties, + AgentState, + AgentType, + Point, + TrafficLightStatesDict +) AGENT_SCOPE_FOV_BUFFER = 60 ATTEMPT_PER_NUM_REGIONS = 15 @@ -22,6 +29,7 @@ def get_regions_default( location: str, total_num_agents: Optional[int] = None, + agent_count_dict: Optional[Dict[AgentType,int]] = None, area_shape: Optional[Tuple[float,float]] = None, map_center: Optional[Tuple[float,float]] = (0.0,0.0), random_seed: Optional[int] = None, @@ -37,7 +45,10 @@ def get_regions_default( Location name in IAI format. total_num_agents: - The total number of agents to initialize across all regions. + Deprecated. The total number of agents to initialize across all regions. + + agent_count_dict: + The number of agents to place within the regions per specified agent type. area_shape: Contains the [width, height] of the rectangular area to be broken into smaller regions. If @@ -56,6 +67,12 @@ def get_regions_default( A flag to control whether a command line progress bar is displayed for convenience. """ + if agent_count_dict is None: + if total_num_agents is None: + raise InvertedAIError(message=f"Error: Must specify a number of agents within the regions.") + else: + agent_count_dict = {AgentType.car: total_num_agents} + if area_shape is None: location_response = iai.location_info( location = location, @@ -76,13 +93,10 @@ def get_regions_default( map_center = map_center ) - if total_num_agents is None: - total_num_agents = 10*len(regions) - new_regions = iai.get_number_of_agents_per_region_by_drivable_area( location = location, regions = regions, - total_num_agents = total_num_agents, + agent_count_dict = agent_count_dict, random_seed = random_seed, display_progress_bar = display_progress_bar ) @@ -116,11 +130,14 @@ def get_regions_in_grid( stride: How far apart the centers of the 100x100m regions should be. Some overlap is recommended for best results and if no argument is provided, a value of 50 is used. - """ + + width_half = width/2 + height_half = height/2 + def check_valid_center(center): - return (map_center[0] - width) < center[0] < (map_center[0] + width) and \ - (map_center[1] - height) < center[1] < (map_center[1] + height) + return (map_center[0] - width_half) < center[0] < (map_center[0] + width_half) and \ + (map_center[1] - height_half) < center[1] < (map_center[1] + height_half) def get_neighbors(center): return [ @@ -151,14 +168,15 @@ def get_neighbors(center): def get_number_of_agents_per_region_by_drivable_area( location: str, regions: List[Region], - total_num_agents: Optional[int] = 10, + total_num_agents: Optional[int] = None, + agent_count_dict: Optional[Dict[AgentType,int]] = None, random_seed: Optional[int] = None, display_progress_bar: Optional[bool] = True ) -> List[Region]: """ This function takes in a list of regions, calculates the driveable area for each of them using :func:`location_info`, then creates a new Region object with copied location and shape data and - inserts a number of default AgentProperties objects into it porportional to its drivable surface + inserts a number of car agents to be **sampled** into it porportional to its drivable surface area relative to the other regions. Regions with no or a relatively small amount of drivable surfaces will be removed. If a region is at its capacity (e.g. due to pre-existing agents), no more agents will be added to it. @@ -173,7 +191,10 @@ def get_number_of_agents_per_region_by_drivable_area( agents to initialize is calculated. total_num_agents: - The total number of agents to initialize throughout all the regions. + Deprecated. The total number of agents to initialize across all regions. + + agent_count_dict: + The number of agents to place within the regions per specified agent type. random_seed: Controls the stochastic aspects of assigning agents to regions for reproducibility. If this @@ -181,9 +202,18 @@ def get_number_of_agents_per_region_by_drivable_area( display_progress_bar: A flag to control whether a command line progress bar is displayed for convenience. - """ + if agent_count_dict is None: + if total_num_agents is None: + raise InvertedAIError(message=f"Error: Must specify a number of agents within the regions.") + else: + agent_count_dict = {AgentType.car: total_num_agents} + + agent_list_types = [] + for agent_type, num_agents in agent_count_dict.items(): + agent_list_types = agent_list_types + [agent_type]*num_agents + new_regions = [Region.copy(region) for region in regions] region_road_area = [] total_drivable_area_ratio = 0 @@ -220,7 +250,7 @@ def get_number_of_agents_per_region_by_drivable_area( all_region_weights = [0]*len(new_regions) for i, drivable_ratio in enumerate(region_road_area): all_region_weights[i] = drivable_ratio/total_drivable_area_ratio - random_indexes = choices(list(range(len(new_regions))), weights=all_region_weights, k=total_num_agents) + random_indexes = choices(list(range(len(new_regions))), weights=all_region_weights, k=len(agent_list_types)) number_sampled_agents = {} for ind in random_indexes: @@ -229,9 +259,9 @@ def get_number_of_agents_per_region_by_drivable_area( else: number_sampled_agents[ind] += 1 - for ind in random_indexes: + for agent_id, ind in enumerate(random_indexes): if len(new_regions[ind].agent_properties) < number_sampled_agents[ind]: - new_regions[ind].agent_properties = new_regions[ind].agent_properties + get_default_agent_properties({"car":1}) + new_regions[ind].agent_properties = new_regions[ind].agent_properties + get_default_agent_properties({agent_list_types[agent_id]:1}) filtered_regions = [] for region in new_regions: diff --git a/invertedai/utils.py b/invertedai/utils.py index f85af34..ae117d1 100644 --- a/invertedai/utils.py +++ b/invertedai/utils.py @@ -34,6 +34,7 @@ AgentState, AgentAttributes, AgentProperties, + AgentType, RecurrentState, StaticMapActor, TrafficLightState, @@ -481,14 +482,9 @@ def _interpret_response_line( return data -@validate_call -def interpolation_manager( -): - pass - @validate_call def get_default_agent_properties( - agent_count_dict: Dict[str,int], + agent_count_dict: Dict[AgentType,int], use_agent_properties: Optional[bool] = True ) -> List[Union[AgentAttributes,AgentProperties]]: """ From 63ecd8f381eb9ec10655751f8a356e73d3842c37 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Thu, 5 Dec 2024 13:55:04 -0800 Subject: [PATCH 03/13] Changed width, height back to current framing as half the total region to not break backwards compatibility. --- examples/large_map_example.py | 2 +- invertedai/large/initialize.py | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/examples/large_map_example.py b/examples/large_map_example.py index 05f8bf8..f65d388 100644 --- a/examples/large_map_example.py +++ b/examples/large_map_example.py @@ -30,7 +30,7 @@ def main(args): regions = iai.get_regions_default( location = args.location, agent_count_dict = {AgentType.car: args.num_agents}, - area_shape = (int(args.width),int(args.height)), + area_shape = (int(args.width/2),int(args.height/2)), map_center = map_center, ) diff --git a/invertedai/large/initialize.py b/invertedai/large/initialize.py index 5620ebc..f06e5e4 100644 --- a/invertedai/large/initialize.py +++ b/invertedai/large/initialize.py @@ -51,9 +51,9 @@ def get_regions_default( The number of agents to place within the regions per specified agent type. area_shape: - Contains the [width, height] of the rectangular area to be broken into smaller regions. If - this argument is not provided, a bounding box around the location polygon from :func:`location_info` - will be used. + Contains the [width, height] to either side of the center of the rectangular area to be broken into + smaller regions (i.e. half of full width and height of the region). If this argument is not provided, + a bounding box around the location polygon from :func:`location_info` will be used. map_center: The coordinates of the center of the rectangular area to be broken into smaller regions. If @@ -118,10 +118,10 @@ def get_regions_in_grid( Arguments ---------- width: - The horizontal size of the area which will be broken into smaller regions. + Half of the horizontal size of the area which will be broken into smaller regions. height: - The vertical size of the area which will be broken into smaller regions. + Half of the vertical size of the area which will be broken into smaller regions. map_center: The coordinates of the center of the rectangular area to be broken into smaller regions. If @@ -132,12 +132,9 @@ def get_regions_in_grid( best results and if no argument is provided, a value of 50 is used. """ - width_half = width/2 - height_half = height/2 - def check_valid_center(center): - return (map_center[0] - width_half) < center[0] < (map_center[0] + width_half) and \ - (map_center[1] - height_half) < center[1] < (map_center[1] + height_half) + return (map_center[0] - width) < center[0] < (map_center[0] + width) and \ + (map_center[1] - height) < center[1] < (map_center[1] + height) def get_neighbors(center): return [ From ea50f92aec95335a009dc2f8237650b128a86ec4 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Thu, 5 Dec 2024 14:04:06 -0800 Subject: [PATCH 04/13] Added documentation for large simulation. --- docs/source/pythonapi/index.md | 2 ++ docs/source/pythonapi/sdk-large-drive.md | 8 ++++++++ docs/source/pythonapi/sdk-large-initialize.md | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 docs/source/pythonapi/sdk-large-drive.md create mode 100644 docs/source/pythonapi/sdk-large-initialize.md diff --git a/docs/source/pythonapi/index.md b/docs/source/pythonapi/index.md index 2c95d32..31eaa53 100644 --- a/docs/source/pythonapi/index.md +++ b/docs/source/pythonapi/index.md @@ -12,6 +12,8 @@ sdk-drive sdk-initialize sdk-light sdk-location-info +sdk-large-drive +sdk-large-initialize sdk-common sdk-simulation sdk-env-var diff --git a/docs/source/pythonapi/sdk-large-drive.md b/docs/source/pythonapi/sdk-large-drive.md new file mode 100644 index 0000000..ed330b1 --- /dev/null +++ b/docs/source/pythonapi/sdk-large-drive.md @@ -0,0 +1,8 @@ +# LARGE_DRIVE + + +```{eval-rst} +.. autofunction:: invertedai.large.large_drive +``` + + diff --git a/docs/source/pythonapi/sdk-large-initialize.md b/docs/source/pythonapi/sdk-large-initialize.md new file mode 100644 index 0000000..27b8d39 --- /dev/null +++ b/docs/source/pythonapi/sdk-large-initialize.md @@ -0,0 +1,19 @@ +# LARGE_INITIALIZE + + +```{eval-rst} +.. autofunction:: invertedai.large.large_initialize +``` +--- +```{eval-rst} +.. autofunction:: invertedai.large.get_regions_default +``` +--- +```{eval-rst} +.. autofunction:: invertedai.large.get_regions_in_grid +``` +--- +```{eval-rst} +.. autofunction:: invertedai.large.get_number_of_agents_per_region_by_drivable_area +``` + From 504dd1abeeb13e9e5f6e8c32134beb191438b517 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Thu, 5 Dec 2024 14:30:52 -0800 Subject: [PATCH 05/13] Cleaned up example python scripts. --- examples/large_map_example.py | 5 +++-- examples/minimal_example.py | 16 +++++++++------- examples/scenario_log_example.py | 3 ++- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/examples/large_map_example.py b/examples/large_map_example.py index f65d388..274f960 100644 --- a/examples/large_map_example.py +++ b/examples/large_map_example.py @@ -3,10 +3,11 @@ from invertedai.common import AgentAttributes, AgentType import argparse -from tqdm import tqdm import matplotlib.pyplot as plt -import time import random +import time + +from tqdm import tqdm def main(args): if args.model_version_drive == "None": diff --git a/examples/minimal_example.py b/examples/minimal_example.py index a76cc59..08c6779 100644 --- a/examples/minimal_example.py +++ b/examples/minimal_example.py @@ -1,6 +1,8 @@ -import numpy as np -import matplotlib.pyplot as plt import invertedai as iai +from invertedai.utils import get_default_agent_properties +from invertedai.common import AgentType + +import matplotlib.pyplot as plt location = "canada:drake_street_and_pacific_blvd" # select one of available locations @@ -14,9 +16,9 @@ # initialize the simulation by spawning NPCs response = iai.initialize( location=location, # select one of available locations - agent_count=10, # number of NPCs to spawn + agent_properties=get_default_agent_properties({AgentType.car:10}), # number of NPCs to spawn ) -agent_attributes = response.agent_attributes # get dimension and other attributes of NPCs +agent_properties = response.agent_properties # get dimension and other attributes of NPCs rendered_static_map = location_info_response.birdview_image.decode() scene_plotter = iai.utils.ScenePlotter( @@ -26,8 +28,8 @@ location_info_response.static_actors ) scene_plotter.initialize_recording( - response.agent_states, - agent_attributes, + agent_states=response.agent_states, + agent_properties=agent_properties, ) print("Begin stepping through simulation.") @@ -36,7 +38,7 @@ # query the API for subsequent NPC predictions response = iai.drive( location=location, - agent_attributes=agent_attributes, + agent_properties=agent_properties, agent_states=response.agent_states, recurrent_states=response.recurrent_states, light_recurrent_states=response.light_recurrent_states, diff --git a/examples/scenario_log_example.py b/examples/scenario_log_example.py index b9c8d36..f28dabf 100644 --- a/examples/scenario_log_example.py +++ b/examples/scenario_log_example.py @@ -3,9 +3,10 @@ from invertedai.common import AgentType import os -from random import randint import matplotlib.pyplot as plt +from random import randint + LOCATION = "canada:drake_street_and_pacific_blvd" # select one of available locations SIMULATION_LENGTH = 100 SIMULATION_LENGTH_EXTEND = 100 From a7073b713a4b533ebdc4f02da49f687d8ba4cb75 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Thu, 5 Dec 2024 14:55:36 -0800 Subject: [PATCH 06/13] Small cleanup to cosimulation and README examples. --- README.md | 29 ++++++++++++++---------- examples/cosimulation_minimal_example.py | 23 +++++++++++-------- invertedai/cosimulation.py | 12 +++++----- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index b1a4e47..f75fe43 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,11 @@ so you can also download it and build locally. ## Minimal example ``` python -import numpy as np -import matplotlib.pyplot as plt import invertedai as iai +from invertedai.utils import get_default_agent_properties +from invertedai.common import AgentType + +import matplotlib.pyplot as plt location = "canada:drake_street_and_pacific_blvd" # select one of available locations @@ -60,9 +62,9 @@ location_info_response = iai.location_info(location=location) # initialize the simulation by spawning NPCs response = iai.initialize( location=location, # select one of available locations - agent_count=10, # number of NPCs to spawn + agent_properties=get_default_agent_properties({AgentType.car:10}), # number of NPCs to spawn ) -agent_attributes = response.agent_attributes # get dimension and other attributes of NPCs +agent_properties = response.agent_properties # get dimension and other attributes of NPCs rendered_static_map = location_info_response.birdview_image.decode() scene_plotter = iai.utils.ScenePlotter( @@ -72,8 +74,8 @@ scene_plotter = iai.utils.ScenePlotter( location_info_response.static_actors ) scene_plotter.initialize_recording( - response.agent_states, - agent_attributes, + agent_states=response.agent_states, + agent_properties=agent_properties, ) print("Begin stepping through simulation.") @@ -82,7 +84,7 @@ for _ in range(100): # how many simulation steps to execute (10 steps is 1 seco # query the API for subsequent NPC predictions response = iai.drive( location=location, - agent_attributes=agent_attributes, + agent_properties=agent_properties, agent_states=response.agent_states, recurrent_states=response.recurrent_states, light_recurrent_states=response.light_recurrent_states, @@ -112,9 +114,12 @@ Conceptually, the API is used to establish synchronous co-simulation between you your machine and the NPC engine running on Inverted AI servers. The basic integration in Python looks like this. ```python +import invertedai as iai +from invertedai.utils import get_default_agent_properties +from invertedai.common import AgentType + from typing import List import numpy as np -import invertedai as iai import matplotlib.pyplot as plt iai.add_apikey('') # specify your key here or through the IAI_API_KEY variable @@ -150,10 +155,10 @@ class LocalSimulator: return self.ego_state print("Begin initialization.") -location = 'iai:ubc_roundabout' +location = "canada:drake_street_and_pacific_blvd" iai_simulation = iai.BasicCosimulation( # instantiate a stateful wrapper for Inverted AI API location=location, # select one of available locations - agent_count=5, # how many vehicles in total to use in the simulation + agent_properties=get_default_agent_properties({AgentType.car:5}), # how many vehicles in total to use in the simulation ego_agent_mask=[True, False, False, False, False], # first vehicle is ego, rest are NPCs get_birdview=False, # provides simple visualization - don't use in production traffic_lights=True, # gets the traffic light states and used for initialization and steping the simulation @@ -169,7 +174,7 @@ scene_plotter = iai.utils.ScenePlotter( ) scene_plotter.initialize_recording( agent_states=iai_simulation.agent_states, - agent_attributes=iai_simulation.agent_attributes, + agent_properties=iai_simulation.agent_properties, ) print("Begin stepping through simulation.") @@ -182,7 +187,7 @@ for _ in range(100): # how many simulation steps to execute (10 steps is 1 seco # execute predictions in your simulator, using your actions for the ego vehicle updated_ego_agent_state = local_simulation.step(predicted_npc_behavior) # save the visualization with ScenePlotter - scene_plotter.record_step(iai_simulation.agent_states) + scene_plotter.record_step(iai_simulation.agent_states,iai_simulation.light_states) print("Simulation finished, save visualization.") # save the visualization to disk diff --git a/examples/cosimulation_minimal_example.py b/examples/cosimulation_minimal_example.py index e31ee7f..6032e22 100644 --- a/examples/cosimulation_minimal_example.py +++ b/examples/cosimulation_minimal_example.py @@ -1,6 +1,9 @@ +import invertedai as iai +from invertedai.utils import get_default_agent_properties +from invertedai.common import AgentType + from typing import List import numpy as np -import invertedai as iai import matplotlib.pyplot as plt iai.add_apikey('') # specify your key here or through the IAI_API_KEY variable @@ -36,10 +39,10 @@ def step(self, predicted_npc_states): return self.ego_state print("Begin initialization.") -location = 'iai:ubc_roundabout' +location = "canada:drake_street_and_pacific_blvd" iai_simulation = iai.BasicCosimulation( # instantiate a stateful wrapper for Inverted AI API location=location, # select one of available locations - agent_count=5, # how many vehicles in total to use in the simulation + agent_properties=get_default_agent_properties({AgentType.car:5}), # how many vehicles in total to use in the simulation ego_agent_mask=[True, False, False, False, False], # first vehicle is ego, rest are NPCs get_birdview=False, # provides simple visualization - don't use in production traffic_lights=True, # gets the traffic light states and used for initialization and steping the simulation @@ -47,13 +50,15 @@ def step(self, predicted_npc_states): location_info_response = iai.location_info(location=location) rendered_static_map = location_info_response.birdview_image.decode() -scene_plotter = iai.utils.ScenePlotter(rendered_static_map, - location_info_response.map_fov, - (location_info_response.map_center.x, location_info_response.map_center.y), - location_info_response.static_actors) +scene_plotter = iai.utils.ScenePlotter( + rendered_static_map, + location_info_response.map_fov, + (location_info_response.map_center.x, location_info_response.map_center.y), + location_info_response.static_actors +) scene_plotter.initialize_recording( agent_states=iai_simulation.agent_states, - agent_attributes=iai_simulation.agent_attributes, + agent_properties=iai_simulation.agent_properties, ) print("Begin stepping through simulation.") @@ -66,7 +71,7 @@ def step(self, predicted_npc_states): # execute predictions in your simulator, using your actions for the ego vehicle updated_ego_agent_state = local_simulation.step(predicted_npc_behavior) # save the visualization with ScenePlotter - scene_plotter.record_step(iai_simulation.agent_states) + scene_plotter.record_step(iai_simulation.agent_states,iai_simulation.light_states) print("Simulation finished, save visualization.") # save the visualization to disk diff --git a/invertedai/cosimulation.py b/invertedai/cosimulation.py index d4df66c..d889d3e 100644 --- a/invertedai/cosimulation.py +++ b/invertedai/cosimulation.py @@ -75,9 +75,9 @@ def __init__( else: self._infractions = None self._agent_count = len( - response.agent_attributes + response.agent_properties ) # initialize may produce different agent count - self._agent_attributes = response.agent_attributes + self._agent_properties = response.agent_properties self._agent_states = response.agent_states self._recurrent_states = response.recurrent_states self._monitor_infractions = monitor_infractions @@ -117,11 +117,11 @@ def agent_states(self) -> List[AgentState]: return self._agent_states @property - def agent_attributes(self) -> List[AgentAttributes]: + def agent_properties(self) -> List[AgentAttributes]: """ The attributes (length, width, rear_axis_offset) for all agents, including ego. """ - return self._agent_attributes + return self._agent_properties @property def ego_agent_mask(self) -> List[bool]: @@ -150,7 +150,7 @@ def ego_attributes(self): Returns the attributes of ego agents in order. The NPC agents are excluded. """ - return [attr for attr, s in zip(self._agent_attributes, self._ego_agent_mask) if s] + return [attr for attr, s in zip(self._agent_properties, self._ego_agent_mask) if s] @property def infractions(self) -> Optional[List[InfractionIndicators]]: @@ -200,7 +200,7 @@ def step(self, current_ego_agent_states: List[AgentState]) -> None: response = drive( location=self.location, - agent_attributes=self._agent_attributes, + agent_properties=self._agent_properties, agent_states=self.agent_states, recurrent_states=self._recurrent_states, get_infractions=self._monitor_infractions, From 82f0a569d5cbbcbbce07688245181f418f399087 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Thu, 5 Dec 2024 15:14:24 -0800 Subject: [PATCH 07/13] Changed some minor visual formatting for readability. --- invertedai/api/drive.py | 4 ++-- invertedai/common.py | 43 +++++++++++++++++++++++++++------- invertedai/cosimulation.py | 28 ++++++++++++++++------ invertedai/large/_quadtree.py | 2 ++ invertedai/large/drive.py | 1 + invertedai/large/initialize.py | 8 +++++++ invertedai/logs/logger.py | 9 ++++--- invertedai/utils.py | 4 ++++ 8 files changed, 79 insertions(+), 20 deletions(-) diff --git a/invertedai/api/drive.py b/invertedai/api/drive.py index b9a902f..278c8c4 100644 --- a/invertedai/api/drive.py +++ b/invertedai/api/drive.py @@ -1,17 +1,17 @@ import time +import asyncio from typing import List, Optional, Tuple from pydantic import BaseModel, validate_call -import asyncio import invertedai as iai from invertedai.api.config import TIMEOUT, should_use_mock_api +from invertedai.error import APIConnectionError, InvalidInput from invertedai.api.mock import ( mock_update_agent_state, get_mock_birdview, get_mock_infractions, get_mock_light_recurrent_states ) -from invertedai.error import APIConnectionError, InvalidInput from invertedai.common import ( AgentState, RecurrentState, diff --git a/invertedai/common.py b/invertedai/common.py index f2780cf..d02107b 100644 --- a/invertedai/common.py +++ b/invertedai/common.py @@ -13,7 +13,6 @@ RECURRENT_SIZE = 152 TrafficLightId = int - class RecurrentState(BaseModel): """ Recurrent state used in :func:`iai.drive`. @@ -51,6 +50,7 @@ class Origin(Point): pass + class LocationMap(BaseModel): """ Serializable representation of a Lanelet2 map and the corresponding origin. @@ -184,7 +184,13 @@ def fromlist(cls, l): else: agent_type, = l assert type(waypoint) is list if waypoint is not None else True, "waypoint must be a list of two floats" - return cls(length=length, width=width, rear_axis_offset=rear_axis_offset, agent_type=agent_type, waypoint=Point(x=waypoint[0], y=waypoint[1]) if waypoint is not None else None) + return cls( + length=length, + width=width, + rear_axis_offset=rear_axis_offset, + agent_type=agent_type, + waypoint=Point(x=waypoint[0], y=waypoint[1]) if waypoint is not None else None + ) def tolist(self): """ @@ -204,6 +210,7 @@ def tolist(self): attr_list.append([self.waypoint.x, self.waypoint.y]) return attr_list + class AgentProperties(BaseModel): """ Static attributes of the agent, which don't change over the course of a simulation. @@ -224,15 +231,27 @@ class AgentProperties(BaseModel): @classmethod def deserialize(cls, val): - return cls(length=val['length'], width=val['width'], rear_axis_offset=val['rear_axis_offset'], agent_type=val['agent_type'], - waypoint=Point(x=val['waypoint'][0], y=val['waypoint'][1]) if val['waypoint'] else None, max_speed=val['max_speed']) + return cls( + length=val['length'], + width=val['width'], + rear_axis_offset=val['rear_axis_offset'], + agent_type=val['agent_type'], + waypoint=Point(x=val['waypoint'][0], y=val['waypoint'][1]) if val['waypoint'] else None, max_speed=val['max_speed'] + ) def serialize(self): """ Convert AgentProperties to a valid request format in json """ - return {"length": self.length, "width": self.width, "rear_axis_offset": self.rear_axis_offset, "agent_type": self.agent_type, - "waypoint": [self.waypoint.x, self.waypoint.y] if self.waypoint else None, "max_speed": self.max_speed} + return { + "length": self.length, + "width": self.width, + "rear_axis_offset": self.rear_axis_offset, + "agent_type": self.agent_type, + "waypoint": [self.waypoint.x, self.waypoint.y] if self.waypoint else None, + "max_speed": self.max_speed + } + class AgentState(BaseModel): """ @@ -260,7 +279,11 @@ def fromlist(cls, l): Build AgentState from a list with this order: [x, y, orientation, speed] """ x, y, psi, v = l - return cls(center=Point(x=x, y=y), orientation=psi, speed=v) + return cls( + center=Point(x=x, y=y), + orientation=psi, + speed=v + ) class InfractionIndicators(BaseModel): @@ -275,7 +298,11 @@ class InfractionIndicators(BaseModel): @classmethod def fromlist(cls, l): collisions, offroad, wrong_way = l - return cls(collisions=collisions, offroad=offroad, wrong_way=wrong_way) + return cls( + collisions=collisions, + offroad=offroad, + wrong_way=wrong_way + ) class StaticMapActor(BaseModel): diff --git a/invertedai/cosimulation.py b/invertedai/cosimulation.py index d889d3e..dc1a29d 100644 --- a/invertedai/cosimulation.py +++ b/invertedai/cosimulation.py @@ -1,16 +1,30 @@ -from typing import List, Tuple, Optional, Union import random +import numpy as np +import asyncio from collections import deque from queue import Queue from itertools import product -import numpy as np -import asyncio +from typing import List, Tuple, Optional, Union from itertools import chain import invertedai as iai -from invertedai import drive, initialize, location_info, light, async_drive, async_initialize -from invertedai.common import (AgentState, InfractionIndicators, Image, - TrafficLightStatesDict, AgentAttributes, RecurrentState, Point) +from invertedai import ( + async_drive, + async_initialize + drive, + initialize, + light, + location_info +) +from invertedai.common import ( + AgentProperties, + AgentState, + Image, + InfractionIndicators, + Point, + RecurrentState, + TrafficLightStatesDict +) class BasicCosimulation: @@ -117,7 +131,7 @@ def agent_states(self) -> List[AgentState]: return self._agent_states @property - def agent_properties(self) -> List[AgentAttributes]: + def agent_properties(self) -> List[AgentProperties]: """ The attributes (length, width, rear_axis_offset) for all agents, including ego. """ diff --git a/invertedai/large/_quadtree.py b/invertedai/large/_quadtree.py index c773277..ab6dc38 100644 --- a/invertedai/large/_quadtree.py +++ b/invertedai/large/_quadtree.py @@ -9,6 +9,7 @@ BUFFER_FOV = 35 QUADTREE_SIZE_BUFFER = 1 + class QuadTreeAgentInfo(BaseModel): """ All information relevant to a single agent. @@ -33,6 +34,7 @@ def fromlist(cls, l): agent_state, agent_properties, recurrent_state, agent_id = l return cls(agent_state=agent_state, agent_properties=agent_properties, recurrent_state=recurrent_state, agent_id=agent_id) + class QuadTree: def __init__( self, diff --git a/invertedai/large/drive.py b/invertedai/large/drive.py index db957b3..b568335 100644 --- a/invertedai/large/drive.py +++ b/invertedai/large/drive.py @@ -17,6 +17,7 @@ async def async_drive_all(async_input_params): all_responses = await asyncio.gather(*[iai.async_drive(**input_params) for input_params in async_input_params]) return all_responses + @validate_call def large_drive( location: str, diff --git a/invertedai/large/initialize.py b/invertedai/large/initialize.py index f06e5e4..05071bb 100644 --- a/invertedai/large/initialize.py +++ b/invertedai/large/initialize.py @@ -25,6 +25,7 @@ AGENT_SCOPE_FOV_BUFFER = 60 ATTEMPT_PER_NUM_REGIONS = 15 + @validate_call def get_regions_default( location: str, @@ -103,6 +104,7 @@ def get_regions_default( return new_regions + @validate_call def get_regions_in_grid( width: float, @@ -161,6 +163,7 @@ def get_neighbors(center): return regions + @validate_call def get_number_of_agents_per_region_by_drivable_area( location: str, @@ -267,6 +270,7 @@ def get_number_of_agents_per_region_by_drivable_area( return filtered_regions + @validate_call def _insert_agents_into_nearest_regions( regions: List[Region], @@ -336,6 +340,7 @@ def _insert_agents_into_nearest_regions( return regions, region_map + def _consolidate_all_responses( all_responses: List[InitializeResponse], region_map: Optional[List[Tuple[int,int]]] = None, @@ -388,6 +393,7 @@ def _consolidate_all_responses( return response + def _get_all_existing_agents_from_regions( regions: List[Region], exclude_index: Optional[int] = None, @@ -407,6 +413,7 @@ def _get_all_existing_agents_from_regions( return agent_states, agent_properties + def _initialize_regions( location: str, regions: List[Region], @@ -536,6 +543,7 @@ def inside_fov(center: Point, agent_scope_fov: float, point: Point) -> bool: return regions, all_responses + @validate_call def large_initialize( location: str, diff --git a/invertedai/logs/logger.py b/invertedai/logs/logger.py index 9159cf2..3ce496b 100644 --- a/invertedai/logs/logger.py +++ b/invertedai/logs/logger.py @@ -6,6 +6,9 @@ from invertedai import location_info from invertedai.utils import ScenePlotter +from invertedai.api.location import LocationResponse +from invertedai.api.initialize import InitializeResponse +from invertedai.api.drive import DriveResponse from invertedai.common import ( AgentAttributes, AgentProperties, @@ -16,9 +19,7 @@ RecurrentState, TrafficLightStatesDict ) -from invertedai.api.location import LocationResponse -from invertedai.api.initialize import InitializeResponse -from invertedai.api.drive import DriveResponse + class ScenarioLog(BaseModel): """ @@ -155,6 +156,7 @@ def initialize(self): def drive(self): pass + class LogWriter(LogBase): """ A class for conveniently writing a log to a JSON log format. @@ -367,6 +369,7 @@ def drive( self.simulation_length += 1 + class LogReader(LogBase): """ A class for conveniently reading in a log file then rendering it and/or plugging it into a simulation. Once the log is read, it is diff --git a/invertedai/utils.py b/invertedai/utils.py index ae117d1..c5e6438 100644 --- a/invertedai/utils.py +++ b/invertedai/utils.py @@ -59,6 +59,7 @@ 500: "The server encountered an unexpected issue. We're working to resolve this. Please try again later.", } + class Session: def __init__(self,debug_logger=None): self.session = requests.Session() @@ -482,6 +483,7 @@ def _interpret_response_line( return data + @validate_call def get_default_agent_properties( agent_count_dict: Dict[AgentType,int], @@ -508,6 +510,7 @@ def get_default_agent_properties( return agent_attributes_list + @validate_call def convert_attributes_to_properties( attributes: AgentAttributes @@ -526,6 +529,7 @@ def convert_attributes_to_properties( return properties + @validate_call def iai_conditional_initialize( location: str, From a9aba812329ed337c3abffe7040f3b137aa45c27 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Thu, 5 Dec 2024 15:17:55 -0800 Subject: [PATCH 08/13] Fixed import issue. --- invertedai/cosimulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invertedai/cosimulation.py b/invertedai/cosimulation.py index dc1a29d..6d2b4ee 100644 --- a/invertedai/cosimulation.py +++ b/invertedai/cosimulation.py @@ -10,7 +10,7 @@ import invertedai as iai from invertedai import ( async_drive, - async_initialize + async_initialize, drive, initialize, light, From 1b4a58363785b1113658b0782b1af1cb97edaafe Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Thu, 5 Dec 2024 15:51:15 -0800 Subject: [PATCH 09/13] Added warnings regarding usage of agent attributes. --- examples/cosimulation_minimal_example.py | 1 - invertedai/api/drive.py | 4 ++++ invertedai/api/initialize.py | 6 +++++- invertedai/common.py | 2 +- invertedai/cosimulation.py | 10 +++++----- invertedai/large/drive.py | 8 +++++++- invertedai/large/initialize.py | 4 ++-- invertedai/utils.py | 22 ++++++++++++---------- 8 files changed, 36 insertions(+), 21 deletions(-) diff --git a/examples/cosimulation_minimal_example.py b/examples/cosimulation_minimal_example.py index 6032e22..2cccc80 100644 --- a/examples/cosimulation_minimal_example.py +++ b/examples/cosimulation_minimal_example.py @@ -8,7 +8,6 @@ iai.add_apikey('') # specify your key here or through the IAI_API_KEY variable - class LocalSimulator: """ Mock up of a local simulator, where you control the ego vehicle. This example only supports single ego vehicle. diff --git a/invertedai/api/drive.py b/invertedai/api/drive.py index 278c8c4..7b233e2 100644 --- a/invertedai/api/drive.py +++ b/invertedai/api/drive.py @@ -1,5 +1,6 @@ import time import asyncio +import warnings from typing import List, Optional, Tuple from pydantic import BaseModel, validate_call @@ -149,6 +150,9 @@ def drive( ) return response + if agent_attributes is not None: + warnings.warn('Warning: agent_attributes is deprecated. Please use agent_properties.') + def _tolist(input_data: List): if not isinstance(input_data, list): return input_data.tolist() diff --git a/invertedai/api/initialize.py b/invertedai/api/initialize.py index 4c66119..ef6623b 100644 --- a/invertedai/api/initialize.py +++ b/invertedai/api/initialize.py @@ -1,7 +1,8 @@ import time +import asyncio +import warnings from pydantic import BaseModel, validate_call from typing import List, Optional, Dict, Tuple -import asyncio import invertedai as iai from invertedai.api.config import TIMEOUT, should_use_mock_api @@ -154,6 +155,9 @@ def initialize( ) return response + if agent_attributes is not None: + warnings.warn('Warning: agent_attributes is deprecated. Please use agent_properties.') + model_inputs = dict( location=location, num_agents_to_spawn=agent_count, diff --git a/invertedai/common.py b/invertedai/common.py index d02107b..9274d18 100644 --- a/invertedai/common.py +++ b/invertedai/common.py @@ -259,7 +259,7 @@ class AgentState(BaseModel): See Also -------- - AgentAttributes + AgentProperties """ center: Point #: The center point of the agent's bounding box. diff --git a/invertedai/cosimulation.py b/invertedai/cosimulation.py index 6d2b4ee..eae1d86 100644 --- a/invertedai/cosimulation.py +++ b/invertedai/cosimulation.py @@ -32,7 +32,7 @@ class BasicCosimulation: Stateful wrapper around the Inverted AI API to simplify co-simulation. All arguments to :func:`initialize` can be passed to the constructor here and a sufficient combination of them must be passed as required by :func:`initialize`. - This wrapper caches static agent attributes and propagates the recurrent state, + This wrapper caches static agent properties and propagates the recurrent state, so that only states of ego agents and NPCs need to be exchanged with it to perform co-simulation. Typically, each time step requires a single call to :func:`self.npc_states` and a single call to :func:`self.step`. @@ -133,7 +133,7 @@ def agent_states(self) -> List[AgentState]: @property def agent_properties(self) -> List[AgentProperties]: """ - The attributes (length, width, rear_axis_offset) for all agents, including ego. + The properties (length, width, rear_axis_offset, max_speed) for all agents, including ego. """ return self._agent_properties @@ -159,12 +159,12 @@ def ego_states(self): return [d for d, s in zip(self._agent_states, self._ego_agent_mask) if s] @property - def ego_attributes(self): + def ego_properties(self): """ - Returns the attributes of ego agents in order. + Returns the properties of ego agents in order. The NPC agents are excluded. """ - return [attr for attr, s in zip(self._agent_properties, self._ego_agent_mask) if s] + return [prop for prop, s in zip(self._agent_properties, self._ego_agent_mask) if s] @property def infractions(self) -> Optional[List[InfractionIndicators]]: diff --git a/invertedai/large/drive.py b/invertedai/large/drive.py index b568335..659dc20 100644 --- a/invertedai/large/drive.py +++ b/invertedai/large/drive.py @@ -1,7 +1,8 @@ +import asyncio +import warnings from typing import Tuple, Optional, List, Union from pydantic import BaseModel, validate_call from math import ceil -import asyncio import invertedai as iai from invertedai.large.common import Region @@ -97,13 +98,18 @@ def large_drive( # Convert any AgentAttributes to AgentProperties for backwards compatibility agent_properties_new = [] + is_using_attributes = False for properties in agent_properties: properties_new = properties if isinstance(properties,AgentAttributes): properties_new = convert_attributes_to_properties(properties) + is_using_attributes = True agent_properties_new.append(properties_new) agent_properties = agent_properties_new + if is_using_attributes: + warnings.warn('Warning: AgentAttributes data type is deprecated. Please use AgentProperties.') + # Generate quadtree agent_x = [agent.center.x for agent in agent_states] agent_y = [agent.center.y for agent in agent_states] diff --git a/invertedai/large/initialize.py b/invertedai/large/initialize.py index 05071bb..eb58589 100644 --- a/invertedai/large/initialize.py +++ b/invertedai/large/initialize.py @@ -516,7 +516,7 @@ def inside_fov(center: Point, agent_scope_fov: float, point: Point) -> bool: if response is not None: # Filter out conditional agents from other regions infractions = [] - for j, (state, attrs, r_state) in enumerate(zip( + for j, (state, props, r_state) in enumerate(zip( response.agent_states[num_out_of_region_conditional_agents:], response.agent_properties[num_out_of_region_conditional_agents:], response.recurrent_states[num_out_of_region_conditional_agents:] @@ -525,7 +525,7 @@ def inside_fov(center: Point, agent_scope_fov: float, point: Point) -> bool: if not inside_fov(center=region_center, agent_scope_fov=region_size, point=state.center): continue - regions[i].insert_all_agent_details(state,attrs,r_state) + regions[i].insert_all_agent_details(state,props,r_state) if get_infractions: infractions.append(response.infractions[num_out_of_region_conditional_agents:][j]) diff --git a/invertedai/utils.py b/invertedai/utils.py index c5e6438..cdb7b3e 100644 --- a/invertedai/utils.py +++ b/invertedai/utils.py @@ -536,7 +536,7 @@ def iai_conditional_initialize( agent_type_count: Dict[str,int], location_of_interest: Tuple[float] = (0,0), recurrent_states: Optional[List[RecurrentState]] = None, - agent_attributes: Optional[List[AgentAttributes]] = None, + agent_properties: Optional[List[AgentProperties]] = None, states_history: Optional[List[List[AgentState]]] = None, traffic_light_state_history: Optional[List[TrafficLightStatesDict]] = None, get_birdview: Optional[bool] = False, @@ -568,11 +568,11 @@ def iai_conditional_initialize( :func:`initialize` """ - conditional_agent_attributes = [] + conditional_agent_properties = [] conditional_agent_states_indexes = [] conditional_recurrent_states = [] outside_agent_states = [] - outside_agent_attributes = [] + outside_agent_properties = [] outside_recurrent_states = [] current_agent_states = states_history[-1] @@ -582,10 +582,10 @@ def iai_conditional_initialize( dist = math.dist(location_of_interest, (agent_state.center.x, agent_state.center.y)) if dist < AGENT_SCOPE_FOV: conditional_agent_states_indexes.append(i) - conditional_agent_attributes.append(agent_attributes[i]) + conditional_agent_properties.append(agent_properties[i]) conditional_recurrent_states.append(recurrent_states[i]) - conditional_agent_type = agent_attributes[i].agent_type + conditional_agent_type = agent_properties[i].agent_type if conditional_agent_type in conditional_agent_type_count: conditional_agent_type_count[conditional_agent_type] -= 1 if conditional_agent_type_count[conditional_agent_type] <= 0: @@ -593,14 +593,14 @@ def iai_conditional_initialize( else: outside_agent_states.append(agent_state) - outside_agent_attributes.append(agent_attributes[i]) + outside_agent_properties.append(agent_properties[i]) outside_recurrent_states.append(recurrent_states[i]) if not conditional_agent_type_count: #The dictionary is empty. iai.logger.warning("Agent count requirement already satisfied, no new agents initialized.") - padded_agent_attributes = get_default_agent_attributes(conditional_agent_type_count) - conditional_agent_attributes.extend(padded_agent_attributes) + padded_agent_properties = get_default_agent_properties(conditional_agent_type_count) + conditional_agent_properties.extend(padded_agent_properties) conditional_agent_states = [[]*len(conditional_agent_states_indexes)] for ts in range(len(conditional_agent_states)): @@ -609,7 +609,7 @@ def iai_conditional_initialize( response = invertedai.api.initialize( location = location, - agent_attributes = conditional_agent_attributes, + agent_properties = conditional_agent_properties, states_history = conditional_agent_states, location_of_interest = location_of_interest, traffic_light_state_history = traffic_light_state_history, @@ -618,7 +618,7 @@ def iai_conditional_initialize( random_seed = random_seed, api_model_version = api_model_version ) - response.agent_attributes = response.agent_attributes + outside_agent_attributes + response.agent_properties = response.agent_properties + outside_agent_properties response.agent_states = response.agent_states + outside_agent_states response.recurrent_states = response.recurrent_states + outside_recurrent_states @@ -900,6 +900,7 @@ def initialize_recording( if agent_attributes is not None: self.agent_properties = [convert_attributes_to_properties(attr) for attr in agent_attributes] + warnings.warn('Warning: agent_attributes is deprecated. Please use agent_properties.') else: self.agent_properties = agent_properties @@ -999,6 +1000,7 @@ def plot_scene( if agent_attributes is not None: agent_properties = [convert_attributes_to_properties(attr) for attr in agent_attributes] + warnings.warn('Warning: agent_attributes is deprecated. Please use agent_properties.') self.initialize_recording( agent_states=agent_states, From d74c6aa644789d8faed3cc8eda044a0fffc88a46 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Thu, 5 Dec 2024 16:28:32 -0800 Subject: [PATCH 10/13] Updated warnings for clarity and only display them once per program to avoid spamming messages. --- invertedai/__init__.py | 3 +++ invertedai/api/drive.py | 2 +- invertedai/api/initialize.py | 2 +- invertedai/large/drive.py | 2 +- invertedai/utils.py | 6 ++++-- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/invertedai/__init__.py b/invertedai/__init__.py index 29c97c7..e5ba4d8 100644 --- a/invertedai/__init__.py +++ b/invertedai/__init__.py @@ -1,4 +1,5 @@ import os +import warnings import importlib.metadata __version__ = importlib.metadata.version("invertedai") from distutils.util import strtobool @@ -20,6 +21,8 @@ from invertedai.logs.logger import LogWriter, LogReader from invertedai.logs.debug_logger import DebugLogger +warnings.filterwarnings(action="once",message=".*agent_attributes.*") + dev = strtobool(os.environ.get("IAI_DEV", "false")) if dev: dev_url = os.environ.get("IAI_DEV_URL", "http://localhost:8000") diff --git a/invertedai/api/drive.py b/invertedai/api/drive.py index 7b233e2..e83c2c6 100644 --- a/invertedai/api/drive.py +++ b/invertedai/api/drive.py @@ -151,7 +151,7 @@ def drive( return response if agent_attributes is not None: - warnings.warn('Warning: agent_attributes is deprecated. Please use agent_properties.') + warnings.warn('agent_attributes is deprecated. Please use agent_properties.',category=DeprecationWarning) def _tolist(input_data: List): if not isinstance(input_data, list): diff --git a/invertedai/api/initialize.py b/invertedai/api/initialize.py index ef6623b..0c6e69c 100644 --- a/invertedai/api/initialize.py +++ b/invertedai/api/initialize.py @@ -156,7 +156,7 @@ def initialize( return response if agent_attributes is not None: - warnings.warn('Warning: agent_attributes is deprecated. Please use agent_properties.') + warnings.warn('agent_attributes is deprecated. Please use agent_properties.',category=DeprecationWarning) model_inputs = dict( location=location, diff --git a/invertedai/large/drive.py b/invertedai/large/drive.py index 659dc20..e98c620 100644 --- a/invertedai/large/drive.py +++ b/invertedai/large/drive.py @@ -108,7 +108,7 @@ def large_drive( agent_properties = agent_properties_new if is_using_attributes: - warnings.warn('Warning: AgentAttributes data type is deprecated. Please use AgentProperties.') + warnings.warn('agent_attributes is deprecated. Please use agent_properties.',category=DeprecationWarning) # Generate quadtree agent_x = [agent.center.x for agent in agent_states] diff --git a/invertedai/utils.py b/invertedai/utils.py index cdb7b3e..a5b90db 100644 --- a/invertedai/utils.py +++ b/invertedai/utils.py @@ -7,6 +7,7 @@ import random import time import numpy as np +import warnings from typing import Dict, Optional, List, Tuple, Union, Any from copy import deepcopy @@ -898,9 +899,10 @@ def initialize_recording( assert (agent_attributes is not None) ^ (agent_properties is not None), \ "Either agent_attributes or agent_properties is populated. Populating both or neither field is invalid." + warnings.simplefilter("once") if agent_attributes is not None: self.agent_properties = [convert_attributes_to_properties(attr) for attr in agent_attributes] - warnings.warn('Warning: agent_attributes is deprecated. Please use agent_properties.') + warnings.warn('Warning: agent_attributes is deprecated. Please use agent_properties.', category=DeprecationWarning) else: self.agent_properties = agent_properties @@ -1000,7 +1002,7 @@ def plot_scene( if agent_attributes is not None: agent_properties = [convert_attributes_to_properties(attr) for attr in agent_attributes] - warnings.warn('Warning: agent_attributes is deprecated. Please use agent_properties.') + warnings.warn('agent_attributes is deprecated. Please use agent_properties.',category=DeprecationWarning) self.initialize_recording( agent_states=agent_states, From 6fa0d8c68de6a91d985cbba06aa52151df57dc40 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Thu, 5 Dec 2024 17:00:44 -0800 Subject: [PATCH 11/13] Fixed missed warning message update. --- invertedai/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/invertedai/utils.py b/invertedai/utils.py index a5b90db..f5cdc6b 100644 --- a/invertedai/utils.py +++ b/invertedai/utils.py @@ -899,10 +899,9 @@ def initialize_recording( assert (agent_attributes is not None) ^ (agent_properties is not None), \ "Either agent_attributes or agent_properties is populated. Populating both or neither field is invalid." - warnings.simplefilter("once") if agent_attributes is not None: self.agent_properties = [convert_attributes_to_properties(attr) for attr in agent_attributes] - warnings.warn('Warning: agent_attributes is deprecated. Please use agent_properties.', category=DeprecationWarning) + warnings.warn('agent_attributes is deprecated. Please use agent_properties.',category=DeprecationWarning) else: self.agent_properties = agent_properties From 837ad94bd742df4aec679f0e62345ae600c88b1f Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Fri, 6 Dec 2024 13:13:03 -0800 Subject: [PATCH 12/13] Fixed docustring build and edited the descriptions. --- invertedai/large/drive.py | 4 ++-- invertedai/large/initialize.py | 36 +++++++++++++--------------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/invertedai/large/drive.py b/invertedai/large/drive.py index e98c620..0bbe4bb 100644 --- a/invertedai/large/drive.py +++ b/invertedai/large/drive.py @@ -72,8 +72,8 @@ def large_drive( single_call_agent_limit: The number of agents allowed in a region before it must subdivide. Currently this value represents the capacity of a quadtree leaf node that will subdivide if the number of vehicles - in the region passes this threshold. In any case, this will be limited to the maximum currently - supported by :func:`drive`. + in the region, plus relevant neighbouring regions, passes this threshold. In any case, this + will be limited to the maximum currently supported by :func:`drive`. async_api_calls: A flag to control whether to use asynchronous DRIVE calls. diff --git a/invertedai/large/initialize.py b/invertedai/large/initialize.py index eb58589..ce828be 100644 --- a/invertedai/large/initialize.py +++ b/invertedai/large/initialize.py @@ -68,9 +68,10 @@ def get_regions_default( A flag to control whether a command line progress bar is displayed for convenience. """ + if agent_count_dict is None: if total_num_agents is None: - raise InvertedAIError(message=f"Error: Must specify a number of agents within the regions.") + raise InvertedAIError(message=f"Must specify a number of agents within the regions.") else: agent_count_dict = {AgentType.car: total_num_agents} @@ -86,7 +87,7 @@ def get_regions_default( width = max(polygon_x) - min(polygon_x) height = max(polygon_y) - min(polygon_y) - area_shape = (width,height) + area_shape = (width/2,height/2) regions = iai.get_regions_in_grid( width = area_shape[0], @@ -174,12 +175,11 @@ def get_number_of_agents_per_region_by_drivable_area( display_progress_bar: Optional[bool] = True ) -> List[Region]: """ - This function takes in a list of regions, calculates the driveable area for each of them using + Takes a list of regions, calculates the driveable area for each of them using output from :func:`location_info`, then creates a new Region object with copied location and shape data and - inserts a number of car agents to be **sampled** into it porportional to its drivable surface + inserts a number of car agents to be **sampled** into it proportional to its drivable surface area relative to the other regions. Regions with no or a relatively small amount of drivable - surfaces will be removed. If a region is at its capacity (e.g. due to pre-existing agents), no - more agents will be added to it. + surface will be removed. Arguments ---------- @@ -187,8 +187,7 @@ def get_number_of_agents_per_region_by_drivable_area( Location name in IAI format. regions: - A list of empty Regions (i.e. no pre-existing agent information) for which the number of - agents to initialize is calculated. + A list of Regions which may or may not contain pre-existing agents. total_num_agents: Deprecated. The total number of agents to initialize across all regions. @@ -206,7 +205,7 @@ def get_number_of_agents_per_region_by_drivable_area( if agent_count_dict is None: if total_num_agents is None: - raise InvertedAIError(message=f"Error: Must specify a number of agents within the regions.") + raise InvertedAIError(message=f"Must specify a number of agents within the regions.") else: agent_count_dict = {AgentType.car: total_num_agents} @@ -252,16 +251,8 @@ def get_number_of_agents_per_region_by_drivable_area( all_region_weights[i] = drivable_ratio/total_drivable_area_ratio random_indexes = choices(list(range(len(new_regions))), weights=all_region_weights, k=len(agent_list_types)) - number_sampled_agents = {} - for ind in random_indexes: - if ind not in number_sampled_agents: - number_sampled_agents[ind] = 1 - else: - number_sampled_agents[ind] += 1 - for agent_id, ind in enumerate(random_indexes): - if len(new_regions[ind].agent_properties) < number_sampled_agents[ind]: - new_regions[ind].agent_properties = new_regions[ind].agent_properties + get_default_agent_properties({agent_list_types[agent_id]:1}) + new_regions[ind].agent_properties = new_regions[ind].agent_properties + get_default_agent_properties({agent_list_types[agent_id]:1}) filtered_regions = [] for region in new_regions: @@ -305,6 +296,7 @@ def _insert_agents_into_nearest_regions( Whether to map the region in which agents of the same index have been placed. Returns a list of the same size as the agent_properties parameter. """ + num_agent_states = len(agent_states) num_regions = len(regions) assert num_regions > 0, "Invalid parameter: number of regions must be greater than zero." @@ -563,10 +555,10 @@ def large_initialize( response is combined into a single response which is returned. While looping over all regions, if there are agents in other regions that are near enough to the region of interest, they will be passed as conditional to :func:`initialize`. :func:`initialize` - will not be called if no agent_states or agent_properties are specified in the region. + will not be called if no agents are specified in the region. A boolean flag can be used to control failure behaviour if :func:`initialize` is unable - to produce viable vehicle placements if the initialization should continue or raise an - exception. + to produce viable vehicle placements, specifically whether the initialization should + continue initializing other regions or raise an exception. As well, predefined agents may be passed to this function in 2 different ways. If the index of the predefined agents must be preserved, pass these agents' data into the agent_properties @@ -574,7 +566,7 @@ def large_initialize( agent properties defined as well but an agent is permitted to be defined by its properties only and :func:`initialize` will fill in the state information. If the index of the predefined agents does not matter, they may be placed directly into the region objects or passed into the parameters - mentioned previously, but make sure to avoid adding these agents twice. + mentioned previously, but make sure to avoid adding these agents more than once. Arguments ---------- From 50fe453f6b4675611d583b12f929ba2a9de422f6 Mon Sep 17 00:00:00 2001 From: KieranRatcliffeInvertedAI Date: Fri, 6 Dec 2024 13:39:08 -0800 Subject: [PATCH 13/13] Minor edits to existing documentation. --- docs/source/pythonapi/sdk-drive.md | 3 ++- docs/source/pythonapi/sdk-light.md | 4 ++-- docs/source/userguide.md | 30 +++++++++++++++--------------- invertedai/large/__init__.py | 2 ++ 4 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 invertedai/large/__init__.py diff --git a/docs/source/pythonapi/sdk-drive.md b/docs/source/pythonapi/sdk-drive.md index da6e48f..65118d3 100644 --- a/docs/source/pythonapi/sdk-drive.md +++ b/docs/source/pythonapi/sdk-drive.md @@ -4,7 +4,6 @@ ```{eval-rst} .. autofunction:: invertedai.api.drive ``` - --- ```{eval-rst} .. autoclass:: invertedai.api.DriveResponse @@ -12,3 +11,5 @@ :undoc-members: :exclude-members: model_config, model_fields ``` + + diff --git a/docs/source/pythonapi/sdk-light.md b/docs/source/pythonapi/sdk-light.md index a40b94e..29c3677 100644 --- a/docs/source/pythonapi/sdk-light.md +++ b/docs/source/pythonapi/sdk-light.md @@ -4,8 +4,6 @@ ```{eval-rst} .. autofunction:: invertedai.api.light ``` - - --- ```{eval-rst} .. autoclass:: invertedai.api.LightResponse @@ -13,3 +11,5 @@ :undoc-members: :exclude-members: model_config, model_fields ``` + + diff --git a/docs/source/userguide.md b/docs/source/userguide.md index 1cd61d2..ec1c0cc 100644 --- a/docs/source/userguide.md +++ b/docs/source/userguide.md @@ -24,8 +24,8 @@ bare-bones access mode that offers maximum flexibility to deploy in any environm For convenience, we also provide a {ref}`Python SDK`, freely available on PyPI with minimal dependencies, which provides an abstraction layer on top of the REST API. Recently, we also released {ref}`C++ SDK` and in the future we intend to release similar libraries for other languages. ## Maps and geofencing -The API operates on a pre-defined collection of maps and currently there is no programmatic way to add additional -locations. For each location there is a map, represented internally in the +The API operates on a pre-defined collection of maps and currently a programmatic way to add additional locations is in development. +For each location there is a map, represented internally in the [Lanelet2](https://github.com/fzi-forschungszentrum-informatik/Lanelet2) format, which specifies lanelets, traffic lights, and a selection of static traffic signs (along with their relationship to specific lanelets). Each map comes with a canonical Euclidean coordinate frame in meters, which for OSM files is obtained by applying a @@ -43,8 +43,8 @@ access, LOCATION_INFO provides all the relevant information. Please contact us w locations. ## Agent types and representations -At the moment the API only supports vehicles, but future releases will also support pedestrians, bicycles, etc.. We -assume that each vehicle is a rigid rectangle with a fixed length and width. The motion of each vehicle is constrained +At the moment the API only supports vehicles and pedestrians, but future releases will also support more agents and vulnerable road users. +We assume that each vehicle is a rigid rectangle with a fixed length and width. The motion of each vehicle is constrained by the kinematic bicycle model, which further requires specifying the rear axis offset, that is the distance between the center of the vehicle and its rear axis. Front axis offset is not relevant, because it can not be fit from observational data, so we omit it. The three static agent attributes are: length, width, and rear offset. @@ -65,26 +65,26 @@ Each traffic light can be green, yellow, or red at any given point. Traffic light IDs are fixed and can be derived from the map, but for convenience we also provide traffic light IDs and the corresponding locations in LOCATION_INFO. For maps with traffic lights, on a call to INITIALIZE, the server generates a realistic configuration of all traffic lights, -and returns the associated light states via 'light_recurrent_states'. On each call to DRIVE, traffic lights' states can be automatically managed by the server with 'light_recurrent_states'. +and returns the associated light states via 'light_recurrent_states'. On each call to {ref}`DRIVE`, traffic lights' states can be automatically managed by the server with 'light_recurrent_states'. There is also the option to manually set light states with 'traffic_lights_states', but once this path is taken, -it is on the client to continually provide 'traffic_lights_states' on all calls to DRIVE. +it is on the client to continually provide 'traffic_lights_states' on all calls to {ref}`DRIVE`. ## Handling agents and NPCs In the API, there is no distinction between agents, controlled by you, and NPCs, controlled by us, so we refer to them -collectively as agents. In any simulation there can be zero or more characters of either kind. When calling DRIVE, the +collectively as agents. In any simulation there can be zero or more characters of either kind. When calling {ref}`DRIVE`, the client needs to list all agents in simulation and we predict the next states for all of them. It is up to the client to decide which of those agents are NPCs and use the corresponding predictions in the local simulator. However, it is important to specify all agents when calling the API, since otherwise NPCs will not be able to react to omitted agents. -Due to the recurrent nature of ITRA, we generally recommend that the customer is consistent about this choice throughout +Due to the recurrent nature of ITRA, we generally recommend that the user is consistent about this choice throughout the simulation - predictions for agents whose state is updated differently from ITRA predictions may not be as good as when ITRA fully controls them. ## Consistent simulation with a stateless API -The API is stateless, so each call to DRIVE requires specifying both the static attributes and the dynamic state of each +The API is stateless, so each call to {ref}`DRIVE` requires specifying both the static attributes and the dynamic state of each agent. However, ITRA is a recurrent model that uses the simulation’s history to make predictions, which we facilitate through the stateless API by passing around a recurrent state, which is a vector with unspecified semantics from the -client’s perspective. Each call to DRIVE returns a new recurrent state for each agent, which must be passed for this -agent to DRIVE on the subsequent call. Providing an incorrect recurrent state may silently lead to deteriorating +client’s perspective. Each call to {ref}`DRIVE` returns a new recurrent state for each agent, which must be passed for this +agent to {ref}`DRIVE` on the subsequent call. Providing an incorrect recurrent state may silently lead to deteriorating performance, and in order to obtain valid values for the initial recurrent state, the simulation must always start with INITIALIZE. To initialize the simulation to a specific state, you can provide a sequence of historical states for all agents that will be used to construct the matching recurrent state. For best performance, at least 10 time steps should @@ -96,18 +96,18 @@ Python library that handles this internally. In the simple case there is a fixed number of agents present throughout the entire simulation. However, it is also possible to dynamically introduce and remove agents, which is typically done when they enter and exit the supported area. Removing agents is easy, all it takes is removing the information for a given agent from the lists of agent -attributes, agent states, and recurrent states. For convenience, DRIVE returns a boolean vector indicating which agents +attributes, agent states, and recurrent states. For convenience, {ref}`DRIVE` returns a boolean vector indicating which agents are within the supported area after the predicted step. Introducing agents into a running simulation is more complicated, due to the requirement to construct their recurrent state. When predictions for the new agents are not going to be consumed, its state can simply be appended to the relevant lists, with the recurrent state set to zeros. To obtain good predictions for such an agent, another call to INITIALIZE needs to be made, providing the recent history of all agents, including the new agent. This correctly -initializes the recurrent state and DRIVE can be called from that point on normally. For best performance, each agent +initializes the recurrent state and {ref}`DRIVE` can be called from that point on normally. For best performance, each agent should initially be controlled by the client for at least 10 time steps before being handed off to ITRA as an NPC by calling INITIALIZE. ## Reproducibility and control over predictions -INITIALIZE and DRIVE optionally accept a random seed, which controls their stochastic behavior. With the same seed and +INITIALIZE and {ref}`DRIVE` optionally accept a random seed, which controls their stochastic behavior. With the same seed and the same inputs, the outputs will be approximately the same with high accuracy. Other than for the random seed, there is currently no mechanism to influence the behavior of predicted agents, such as by directing them to certain exits or setting their speed, but such mechanisms will be included in future releases. @@ -119,6 +119,6 @@ formats, including checking lengths of lists and bounds for numeric values, and performed on the client side before paid API calls. All those features are only available in the Python library and not in the REST API. To enable the mock API, just set the environment variable `IAI_MOCK_API` to true according to {ref}`Environment Variables`. -For further debugging and visualization, both INITIALIZE and DRIVE optionally return a rendered birdview image showing +For further debugging and visualization, both INITIALIZE and {ref}`DRIVE` optionally return a rendered birdview image showing the simulation state after the call to them. This significantly increases the payload size and latency, so it should not be done in real integrations. diff --git a/invertedai/large/__init__.py b/invertedai/large/__init__.py new file mode 100644 index 0000000..0de16ed --- /dev/null +++ b/invertedai/large/__init__.py @@ -0,0 +1,2 @@ +from invertedai.large.drive import large_drive +from invertedai.large.initialize import large_initialize, get_regions_default, get_regions_in_grid, get_number_of_agents_per_region_by_drivable_area