| import os |
| import numpy as np |
| from pyquaternion import Quaternion |
| from shapely import affinity, ops |
| from shapely.geometry import LineString, box, MultiPolygon, MultiLineString |
|
|
| from nuplan.common.maps.nuplan_map.map_factory import get_maps_api |
| from nuplan.common.maps.maps_datatypes import SemanticMapLayer |
| from nuplan.common.actor_state.oriented_box import OrientedBox |
| import torch |
| from nuplan.common.actor_state.state_representation import Point2D, StateSE2 |
| from navsim.planning.scenario_builder.navsim_scenario_utils import tracked_object_types |
| import mmdet3d_plugin.datasets.utils.calibration as calib_utils |
| import scipy |
| import cv2 |
|
|
| class DiffusionDriveMap: |
| def __init__( |
| self, |
| config, |
| map_root, |
| map_version='nuplan-maps-v1.0', |
| patch_size=(100, 100), |
| map_classes={ |
| 'centerline': [SemanticMapLayer.LANE, SemanticMapLayer.LANE_CONNECTOR], |
| 'ped_crossing': [SemanticMapLayer.CROSSWALK], |
| 'road_boundary': [SemanticMapLayer.ROADBLOCK, SemanticMapLayer.INTERSECTION], |
| |
| }, |
| need_merged=['road_boundary'],): |
|
|
| self._config = config |
| self.map_classes = map_classes |
| self.patch_size = patch_size |
| self.need_merged = need_merged |
| self.MAP_APIS_DICT = { |
| "us-pa-pittsburgh-hazelwood" : get_maps_api(map_root, map_version, "us-pa-pittsburgh-hazelwood"), |
| "sg-one-north" : get_maps_api(map_root, map_version, "sg-one-north"), |
| "us-ma-boston" : get_maps_api(map_root, map_version, "us-ma-boston"), |
| "us-nv-las-vegas-strip" : get_maps_api(map_root, map_version, "us-nv-las-vegas-strip") |
| } |
|
|
| def compute_bev_semantic_map( |
| self, info, |
| ): |
| """ |
| Creates sematic map in BEV |
| :param annotations: annotation dataclass |
| :param map_api: map interface of nuPlan |
| :param ego_pose: ego pose in global frame |
| :return: 2D torch tensor of semantic labels |
| """ |
| map_location = info['map_location'] |
| |
| map_api = self.MAP_APIS_DICT[map_location] |
| |
| sdc_loc_global, _ = calib_utils.transform_matrix_to_vector( |
| info["ego2global"] |
| ) |
| yaw_global = scipy.spatial.transform.Rotation.from_matrix( |
| info["ego2global"][:3, :3] |
| ).as_euler("xyz", degrees=False)[-1] |
| |
| ego_pose = StateSE2(x=sdc_loc_global[0], y=sdc_loc_global[1],heading=yaw_global) |
|
|
| bev_semantic_map = np.zeros(self._config.bev_semantic_frame, dtype=np.int64) |
|
|
| for label, (entity_type, layers) in self._config.bev_semantic_classes.items(): |
| if entity_type == "polygon": |
| entity_mask = self._compute_map_polygon_mask(map_api, ego_pose, layers) |
| elif entity_type == "linestring": |
| entity_mask = self._compute_map_linestring_mask(map_api, ego_pose, layers) |
| else: |
| entity_mask = self._compute_box_mask(info, layers) |
| bev_semantic_map[entity_mask] = label |
|
|
| return torch.Tensor(bev_semantic_map) |
| |
| def _compute_map_polygon_mask( |
| self, map_api, ego_pose, layers |
| ) : |
| """ |
| Compute binary mask given a map layer class |
| :param map_api: map interface of nuPlan |
| :param ego_pose: ego pose in global frame |
| :param layers: map layers |
| :return: binary mask as numpy array |
| """ |
|
|
| map_object_dict = map_api.get_proximal_map_objects( |
| point=ego_pose.point, radius=self._config.bev_radius, layers=layers |
| ) |
| map_polygon_mask = np.zeros(self._config.bev_semantic_frame[::-1], dtype=np.uint8) |
| for layer in layers: |
| for map_object in map_object_dict[layer]: |
| polygon = self._geometry_local_coords(map_object.polygon, ego_pose) |
| exterior = np.array(polygon.exterior.coords).reshape((-1, 1, 2)) |
| exterior = self._coords_to_pixel(exterior) |
| cv2.fillPoly(map_polygon_mask, [exterior], color=255) |
| |
| map_polygon_mask = np.rot90(map_polygon_mask)[::-1] |
| return map_polygon_mask > 0 |
|
|
| def _compute_map_linestring_mask( |
| self, map_api, ego_pose, layers |
| ): |
| """ |
| Compute binary of linestring given a map layer class |
| :param map_api: map interface of nuPlan |
| :param ego_pose: ego pose in global frame |
| :param layers: map layers |
| :return: binary mask as numpy array |
| """ |
| map_object_dict = map_api.get_proximal_map_objects( |
| point=ego_pose.point, radius=self._config.bev_radius, layers=layers |
| ) |
| map_linestring_mask = np.zeros(self._config.bev_semantic_frame[::-1], dtype=np.uint8) |
| for layer in layers: |
| for map_object in map_object_dict[layer]: |
| linestring: LineString = self._geometry_local_coords(map_object.baseline_path.linestring, ego_pose) |
| points = np.array(linestring.coords).reshape((-1, 1, 2)) |
| points = self._coords_to_pixel(points) |
| cv2.polylines(map_linestring_mask, [points], isClosed=False, color=255, thickness=2) |
| |
| map_linestring_mask = np.rot90(map_linestring_mask)[::-1] |
| return map_linestring_mask > 0 |
|
|
| def _compute_box_mask(self, info, layers): |
| """ |
| Compute binary of bounding boxes in BEV space |
| :param annotations: annotation dataclass |
| :param layers: bounding box labels to include |
| :return: binary mask as numpy array |
| """ |
| box_polygon_mask = np.zeros(self._config.bev_semantic_frame[::-1], dtype=np.uint8) |
| for name_value, box_value in zip(info['anns']["gt_names"], info['anns']["gt_boxes"]): |
| agent_type = tracked_object_types[name_value] |
| if agent_type in layers: |
| |
| x, y, heading = box_value[0], box_value[1], box_value[-1] |
| box_length, box_width, box_height = box_value[3], box_value[4], box_value[5] |
| agent_box = OrientedBox(StateSE2(x, y, heading), box_length, box_width, box_height) |
| exterior = np.array(agent_box.geometry.exterior.coords).reshape((-1, 1, 2)) |
| exterior = self._coords_to_pixel(exterior) |
| cv2.fillPoly(box_polygon_mask, [exterior], color=255) |
| |
| box_polygon_mask = np.rot90(box_polygon_mask)[::-1] |
| return box_polygon_mask > 0 |
|
|
|
|
| @staticmethod |
| def _geometry_local_coords(geometry, origin): |
| """ |
| Transform shapely geometry in local coordinates of origin. |
| :param geometry: shapely geometry |
| :param origin: pose dataclass |
| :return: shapely geometry |
| """ |
|
|
| a = np.cos(origin.heading) |
| b = np.sin(origin.heading) |
| d = -np.sin(origin.heading) |
| e = np.cos(origin.heading) |
| xoff = -origin.x |
| yoff = -origin.y |
|
|
| translated_geometry = affinity.affine_transform(geometry, [1, 0, 0, 1, xoff, yoff]) |
| rotated_geometry = affinity.affine_transform(translated_geometry, [a, b, d, e, 0, 0]) |
|
|
| return rotated_geometry |
|
|
| def _coords_to_pixel(self, coords): |
| """ |
| Transform local coordinates in pixel indices of BEV map |
| :param coords: _description_ |
| :return: _description_ |
| """ |
|
|
| |
| pixel_center = np.array([[0, self._config.bev_pixel_width / 2.0]]) |
| coords_idcs = (coords / self._config.bev_pixel_size) + pixel_center |
|
|
| return coords_idcs.astype(np.int32) |