Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding AgentProperties field for DRIVE and INITIALIZE #207

Merged
merged 5 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions invertedai/api/drive.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Image,
InfractionIndicators,
AgentAttributes,
AgentProperties,
TrafficLightStatesDict,
LightRecurrentStates,
LightRecurrentState,
Expand Down Expand Up @@ -53,7 +54,8 @@ class DriveResponse(BaseModel):
def drive(
location: str,
agent_states: List[AgentState],
agent_attributes: List[AgentAttributes],
agent_attributes: Optional[List[AgentAttributes]]=None,
agent_properties: Optional[List[AgentProperties]]=None,
recurrent_states: Optional[List[RecurrentState]] = None,
traffic_lights_states: Optional[TrafficLightStatesDict] = None,
light_recurrent_states: Optional[LightRecurrentStates] = None,
Expand Down Expand Up @@ -83,6 +85,14 @@ def drive(
currently supports 'car' and 'pedestrian'.
waypoint: optional [Point], the target waypoint of the agent.

agent_properties:
Agent properties for all agents, replacing soon to be deprecated `agent_attributes`.
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'.
waypoint: optional [Point], the target waypoint of the agent.
max_speed: optional [float], the desired maximum speed of the agent in m/s.

recurrent_states:
Recurrent states for all agents, obtained from the previous call to
:func:`drive` or :func:`initialize`.
Expand Down Expand Up @@ -157,7 +167,8 @@ def _tolist(input_data: List):
model_inputs = dict(
location=location,
agent_states=[state.tolist() for state in agent_states],
agent_attributes=[state.tolist() for state in agent_attributes],
agent_attributes=[state.tolist() for state in agent_attributes] if agent_attributes is not None else None,
agent_properties=[ap.serialize() for ap in agent_properties] if agent_properties is not None else None,
recurrent_states=[r.packed for r in recurrent_states] if recurrent_states is not None else None,
traffic_lights_states=traffic_lights_states,
light_recurrent_states=[light_recurrent_state.tolist() for light_recurrent_state in light_recurrent_states]
Expand Down Expand Up @@ -218,7 +229,8 @@ def _tolist(input_data: List):
async def async_drive(
location: str,
agent_states: List[AgentState],
agent_attributes: List[AgentAttributes],
agent_attributes: Optional[List[AgentAttributes]]=None,
agent_properties: Optional[List[AgentProperties]]=None,
recurrent_states: Optional[List[RecurrentState]] = None,
traffic_lights_states: Optional[TrafficLightStatesDict] = None,
light_recurrent_states: Optional[LightRecurrentStates] = None,
Expand All @@ -243,7 +255,8 @@ def _tolist(input_data: List):
model_inputs = dict(
location=location,
agent_states=[state.tolist() for state in agent_states],
agent_attributes=[state.tolist() for state in agent_attributes],
agent_attributes=[state.tolist() for state in agent_attributes] if agent_attributes is not None else None,
agent_properties=[ap.serialize() for ap in agent_properties] if agent_properties is not None else None,
recurrent_states=[r.packed for r in recurrent_states] if recurrent_states is not None else None,
traffic_lights_states=traffic_lights_states,
light_recurrent_states=[light_recurrent_state.tolist() for light_recurrent_state in light_recurrent_states]
Expand Down
32 changes: 29 additions & 3 deletions invertedai/api/initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from invertedai.error import TryAgain, InvalidInputType, InvalidInput
from invertedai.api.mock import (
get_mock_agent_attributes,
get_mock_agent_properties,
get_mock_agent_state,
get_mock_recurrent_state,
get_mock_birdview,
Expand All @@ -19,6 +20,7 @@
RecurrentState,
AgentState,
AgentAttributes,
AgentProperties,
TrafficLightStatesDict,
Image,
InfractionIndicators,
Expand All @@ -39,6 +41,7 @@ class InitializeResponse(BaseModel):
agent_attributes: List[
Optional[AgentAttributes]
] #: Static attributes of all initialized agents.
agent_properties: List[AgentProperties] #: Static agent properties of all initialized agents.
birdview: Optional[
Image
] #: If `get_birdview` was set, this contains the resulting image.
Expand All @@ -54,6 +57,7 @@ class InitializeResponse(BaseModel):
def initialize(
location: str,
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]
Expand Down Expand Up @@ -90,6 +94,11 @@ def initialize(
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`.
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.

states_history:
History of pre-defined agent states - the outer list is over time and the inner over agents,
Expand Down Expand Up @@ -130,10 +139,15 @@ def initialize(
"""

if should_use_mock_api():
assert agent_attributes is not None or agent_count is not None
agent_count = agent_count if agent_count is not None else len(agent_attributes)
assert agent_properties is not None or agent_attributes is not None or agent_count is not None
if agent_properties is not None:
agent_count = len(agent_properties)
elif agent_attributes is not None:
agent_count = len(agent_attributes)

agent_properties = [get_mock_agent_properties() for _ in range(agent_count)]
agent_attributes = [get_mock_agent_attributes() for _ in range(agent_count)]
if agent_attributes is None:
agent_attributes = [get_mock_agent_attributes() for _ in range(agent_count)]
agent_states = [get_mock_agent_state() for _ in range(agent_count)]
else:
agent_states = states_history[-1] if states_history is not None else []
Expand All @@ -143,6 +157,7 @@ def initialize(
response = InitializeResponse(
agent_states=agent_states,
agent_attributes=agent_attributes,
agent_properties=agent_properties,
recurrent_states=recurrent_states,
birdview=birdview,
infractions=infractions,
Expand All @@ -161,6 +176,8 @@ def initialize(
agent_attributes=agent_attributes
if agent_attributes is None
else [state.tolist() for state in agent_attributes],
agent_properties=agent_properties if agent_properties is None
else [ap.serialize() if ap else None for ap in agent_properties] ,
traffic_light_state_history=traffic_light_state_history,
get_birdview=get_birdview,
location_of_interest=location_of_interest,
Expand All @@ -179,6 +196,9 @@ def initialize(
],
agent_attributes=[
AgentAttributes.fromlist(attr) for attr in response["agent_attributes"]
] if response["agent_attributes"] is not None else [],
agent_properties=[
AgentProperties.deserialize(ap) for ap in response["agent_properties"]
],
recurrent_states=[
RecurrentState.fromval(r) for r in response["recurrent_states"]
Expand Down Expand Up @@ -214,6 +234,7 @@ def initialize(
async def async_initialize(
location: str,
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]
Expand All @@ -238,6 +259,8 @@ async def async_initialize(
agent_attributes=agent_attributes
if agent_attributes is None
else [state.tolist() for state in agent_attributes],
agent_properties=agent_properties if agent_properties is None
else [ap.serialize() if ap else None for ap in agent_properties] ,
traffic_light_state_history=traffic_light_state_history,
get_birdview=get_birdview,
location_of_interest=location_of_interest,
Expand All @@ -259,6 +282,9 @@ async def async_initialize(
agent_attributes=[
AgentAttributes.fromlist(attr) for attr in response["agent_attributes"]
],
agent_properties=[
AgentProperties.deserialize(ap) for ap in response["agent_properties"]
],
recurrent_states=[
RecurrentState.fromval(r) for r in response["recurrent_states"]
],
Expand Down
6 changes: 6 additions & 0 deletions invertedai/api/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from invertedai.common import (
AgentAttributes,
AgentProperties,
AgentState,
RecurrentState,
TrafficLightStatesDict,
Expand All @@ -23,6 +24,11 @@ def get_mock_agent_attributes() -> AgentAttributes:
return attributes


def get_mock_agent_properties() -> AgentProperties:
properties = AgentProperties(length=5, width=2, rear_axis_offset=1.4)
return properties


def get_mock_agent_state() -> AgentState:
state = AgentState(center=Point(x=0, y=0), orientation=0, speed=0)
return state
Expand Down
29 changes: 29 additions & 0 deletions invertedai/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,36 @@ 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.
We assume every agent is a rectangle obeying a kinematic bicycle model.

See Also
--------
AgentState
"""

length: Optional[float] = None #: Longitudinal extent of the agent, in meters.
width: Optional[float] = None #: Lateral extent of the agent, in meters.
#: Distance from the agent's center to its rear axis in meters. Determines motion constraints.
rear_axis_offset: Optional[float] = None
agent_type: Optional[str] = 'car' #: Valid types are those in `AgentType`, but we use `str` here for extensibility.
waypoint: Optional[Point] = None #: Target waypoint of the agent. If provided the agent will attempt to reach it.
max_speed: Optional[float] = None #: Maximum speed limit of the agent in m/s.

@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'])

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}

class AgentState(BaseModel):
"""
The current or predicted state of a given agent at a given point.
Expand Down
14 changes: 14 additions & 0 deletions invertedai_cpp/examples/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,18 @@ cc_binary(
"@boost//:beast",
"@opencv",
],
)

cc_binary(
name = "max_speed_example",
srcs = ["max_speed_example.cc"],
data = [
"drive_body.json",
"initialize_body_max_speed_car_example.json",
],
deps = [
"//invertedai:api",
"@boost//:beast",
"@opencv",
],
)
29 changes: 29 additions & 0 deletions invertedai_cpp/examples/drive_body_template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"location": "canada:ubc_roundabout",
"agent_states": [
[
33.8,
-26.78,
1.6,
6.31
],
[
4.17,
-31.48,
-0.62,
7.58
]
],
"agent_properties": [
{"length": 4.55, "width": 1.94, "agent_type": "car", "waypoint": [270.61, -159.85], "max_speed": 1},
{"length": 4.55, "width": 1.97, "agent_type": "car", "waypoint": [260, -128]}
],
"recurrent_states": null,
"get_birdview": true,
"get_infractions": true,
"traffic_lights_states": null,
"light_recurrent_states": null,
"random_seed": null,
"rendering_fov": null,
"rendering_center": null
}
14 changes: 14 additions & 0 deletions invertedai_cpp/examples/initialize_body_max_speed_car_example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"location": "foretellix:M80_FTX_de_stuttgart",
"num_agents_to_spawn": 2,
"states_history": [[[373, -234, 2.5, 20], [353, -198, 2.5, 19]]],
"agent_properties": [
{"length": 4.55, "width": 1.94, "rear_axis_offset": 1.7, "agent_type": "car", "waypoint": [270.61, -159.85], "max_speed": 1},
{"length": 4.55, "width": 1.97, "rear_axis_offset": 1.7, "agent_type": "car", "waypoint": [260, -128]}
],
"traffic_light_state_history": null,
"get_birdview": true,
"get_infractions": true,
"random_seed": null,
"location_of_interest": null
}
93 changes: 93 additions & 0 deletions invertedai_cpp/examples/max_speed_example.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>

#include "../invertedai/api.h"
#include "../invertedai/data_utils.h"

using AgentAttributes = invertedai::AgentAttributes;
using AgentProperties = invertedai::AgentProperties;

using Point2d = invertedai::Point2d;


int processScenario(const char *bodyPath, const std::string api_key, int timestep);

int main(int argc, char **argv) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <timestep> <api_key>\n";
return EXIT_FAILURE;
}

int timestep;
try {
timestep = std::stoi(argv[1]);
} catch (const std::exception& e) {
std::cerr << "Invalid timestep argument: " << e.what() << std::endl;
return EXIT_FAILURE;
}

const std::string api_key(argv[2]);
const char* example_body = "examples/initialize_body_max_speed_car_example.json";

try {
if (processScenario(example_body, api_key, timestep) != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}

int processScenario(const char *bodyPath, const std::string api_key, int timestep) {
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
using json = nlohmann::json; // from <json.hpp>

net::io_context ioc;
ssl::context ctx(ssl::context::tlsv12_client);
invertedai::Session session(ioc, ctx);
session.set_api_key(api_key);
session.connect();

invertedai::InitializeRequest init_req(invertedai::read_file(bodyPath));
invertedai::InitializeResponse init_res = invertedai::initialize(init_req, &session);
auto image = cv::imdecode(init_res.birdview(), cv::IMREAD_COLOR);
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);

int frame_width = image.rows;
int frame_height = image.cols;
std::string video_name = "max_speed_example.mp4" ;

cv::VideoWriter video(
video_name,
cv::VideoWriter::fourcc('M', 'J', 'P', 'G'),
10,
cv::Size(frame_width, frame_height)
);

invertedai::DriveRequest drive_req(invertedai::read_file("examples/drive_body_template.json"));
drive_req.set_location(init_req.location());
drive_req.update(init_res);
drive_req.set_rendering_center(std::make_pair(313, -194)); // Render the optional birdview in a reasnable area
drive_req.set_rendering_fov(300);

for (int t = 0; t < timestep; t++) {
invertedai::DriveResponse drive_res = invertedai::drive(drive_req, &session);
auto image = cv::imdecode(drive_res.birdview(), cv::IMREAD_COLOR);
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
video.write(image);
drive_req.update(drive_res);
std::cout << "Remaining iterations: " << timestep - t << std::endl;
}

return EXIT_SUCCESS;
}
Loading
Loading