from typing import List import numpy as np import numpy.typing as npt import shapely from nuplan.common.actor_state.ego_state import EgoState from nuplan.common.actor_state.state_representation import StateSE2, StateVector2D, TimePoint from nuplan.common.actor_state.vehicle_parameters import VehicleParameters from navsim.planning.simulation.planner.pdm_planner.utils.pdm_enums import BBCoordsIndex, SE2Index, StateIndex from navsim.planning.simulation.planner.pdm_planner.utils.pdm_geometry_utils import translate_lon_and_lat def array_to_state_se2(array: npt.NDArray[np.float64]) -> StateSE2: """ Converts array representation to single StateSE2. :param array: array filled with (x,y,θ) :return: StateSE2 class """ return StateSE2(array[0], array[1], array[2]) # use numpy vectorize function to apply on last dim array_to_state_se2_vectorize = np.vectorize(array_to_state_se2, signature="(n)->()") def array_to_states_se2(array: npt.NDArray[np.float64]) -> npt.NDArray[np.object_]: """ Converts array representation to StateSE2 over last dim. :param array: array filled with (x,y,θ) on last dim :return: array of StateSE2 class """ assert array.shape[-1] == len(SE2Index) return array_to_state_se2_vectorize(array) def state_se2_to_array(state_se2: StateSE2) -> npt.NDArray[np.float64]: """ Converts StateSE2 to array representation. :param state_se2: class containing (x,y,θ) :return: array containing (x,y,θ) """ array = np.zeros(len(SE2Index), dtype=np.float64) array[SE2Index.X] = state_se2.x array[SE2Index.Y] = state_se2.y array[SE2Index.HEADING] = state_se2.heading return array def states_se2_to_array(states_se2: List[StateSE2]) -> npt.NDArray[np.float64]: """ Converts list of StateSE2 object to array representation :param states_se2: list of StateSE2 object's :return: array representation of states """ state_se2_array = np.zeros((len(states_se2), len(SE2Index)), dtype=np.float64) for i, state_se2 in enumerate(states_se2): state_se2_array[i] = state_se2_to_array(state_se2) return state_se2_array def ego_state_to_state_array(ego_state: EgoState) -> npt.NDArray[np.float64]: """ Converts an ego state into an array representation (drops time-stamps and vehicle parameters) :param ego_state: ego state class :return: array containing ego state values """ state_array = np.zeros(StateIndex.size(), dtype=np.float64) state_array[StateIndex.STATE_SE2] = ego_state.rear_axle.serialize() state_array[StateIndex.VELOCITY_2D] = ego_state.dynamic_car_state.rear_axle_velocity_2d.array state_array[StateIndex.ACCELERATION_2D] = ego_state.dynamic_car_state.rear_axle_acceleration_2d.array state_array[StateIndex.STEERING_ANGLE] = ego_state.tire_steering_angle state_array[StateIndex.STEERING_RATE] = ego_state.dynamic_car_state.tire_steering_rate state_array[StateIndex.ANGULAR_VELOCITY] = ego_state.dynamic_car_state.angular_velocity state_array[StateIndex.ANGULAR_ACCELERATION] = ego_state.dynamic_car_state.angular_acceleration return state_array def ego_states_to_state_array(ego_states: List[EgoState]) -> npt.NDArray[np.float64]: """ Converts a list of ego states into an array representation (drops time-stamps and vehicle parameters) :param ego_state: ego state class :return: array containing ego state values """ state_array = np.array( [ego_state_to_state_array(ego_state) for ego_state in ego_states], dtype=np.float64, ) return state_array def state_array_to_ego_state( state_array: npt.NDArray[np.float64], time_point: TimePoint, vehicle_parameters: VehicleParameters, ) -> EgoState: """ Converts array representation of ego state back to ego state class. :param state_array: array representation of ego states :param time_point: time point of state :param vehicle_parameters: vehicle parameter of ego :return: nuPlan's EgoState object """ return EgoState.build_from_rear_axle( rear_axle_pose=StateSE2(*state_array[StateIndex.STATE_SE2]), rear_axle_velocity_2d=StateVector2D(*state_array[StateIndex.VELOCITY_2D]), rear_axle_acceleration_2d=StateVector2D(*state_array[StateIndex.ACCELERATION_2D]), tire_steering_angle=state_array[StateIndex.STEERING_ANGLE], time_point=time_point, vehicle_parameters=vehicle_parameters, is_in_auto_mode=True, angular_vel=state_array[StateIndex.ANGULAR_VELOCITY], angular_accel=state_array[StateIndex.ANGULAR_ACCELERATION], tire_steering_rate=state_array[StateIndex.STEERING_RATE], ) def state_array_to_ego_states( state_array: npt.NDArray[np.float64], time_points: List[TimePoint], vehicle_parameter: VehicleParameters, ) -> List[EgoState]: """ Converts array representation of ego states back to list of ego state class. :param state_array: array representation of ego states :param time_point: list of time point of state array :param vehicle_parameters: vehicle parameter of ego :return: list nuPlan's EgoState object """ ego_states_list: List[EgoState] = [] for i, time_point in enumerate(time_points): state = state_array[i] if i < len(state_array) else state_array[-1] ego_states_list.append(state_array_to_ego_state(state, time_point, vehicle_parameter)) return ego_states_list def state_array_to_coords_array( states: npt.NDArray[np.float64], vehicle_parameters: VehicleParameters, ) -> npt.NDArray[np.float64]: """ Converts multi-dim array representation of ego states to bounding box coordinates :param state_array: array representation of ego states :param vehicle_parameters: vehicle parameter of ego :return: multi-dim array bounding box coordinates """ n_batch, n_time, n_states = states.shape half_length, half_width, rear_axle_to_center = ( vehicle_parameters.half_length, vehicle_parameters.half_width, vehicle_parameters.rear_axle_to_center, ) headings = states[..., StateIndex.HEADING] cos, sin = np.cos(headings), np.sin(headings) # calculate ego center from rear axle rear_axle_to_center_translate = np.stack([rear_axle_to_center * cos, rear_axle_to_center * sin], axis=-1) ego_centers: npt.NDArray[np.float64] = states[..., StateIndex.POINT] + rear_axle_to_center_translate coords_array: npt.NDArray[np.float64] = np.zeros((n_batch, n_time, len(BBCoordsIndex), 2), dtype=np.float64) coords_array[:, :, BBCoordsIndex.CENTER] = ego_centers coords_array[:, :, BBCoordsIndex.FRONT_LEFT] = translate_lon_and_lat(ego_centers, headings, half_length, half_width) coords_array[:, :, BBCoordsIndex.FRONT_RIGHT] = translate_lon_and_lat( ego_centers, headings, half_length, -half_width ) coords_array[:, :, BBCoordsIndex.REAR_LEFT] = translate_lon_and_lat(ego_centers, headings, -half_length, half_width) coords_array[:, :, BBCoordsIndex.REAR_RIGHT] = translate_lon_and_lat( ego_centers, headings, -half_length, -half_width ) return coords_array def coords_array_to_polygon_array( coords: npt.NDArray[np.float64], ) -> npt.NDArray[np.object_]: """ Converts multi-dim array of bounding box coords of to polygons :param coords: bounding box coords (including corners and center) :return: array of shapely's polygons """ # create coords copy and use center point for closed exterior coords_exterior: npt.NDArray[np.float64] = coords.copy() coords_exterior[..., BBCoordsIndex.CENTER, :] = coords_exterior[..., BBCoordsIndex.FRONT_LEFT, :] # load new coordinates into polygon array polygons = shapely.creation.polygons(coords_exterior) return polygons