|
|
from typing import Any, Dict, List |
|
|
|
|
|
import numpy as np |
|
|
from shapely import affinity |
|
|
from shapely.geometry import Polygon, LineString |
|
|
import matplotlib.pyplot as plt |
|
|
|
|
|
from nuplan.common.maps.abstract_map import AbstractMap, SemanticMapLayer |
|
|
from nuplan.common.actor_state.state_representation import StateSE2 |
|
|
from nuplan.common.actor_state.oriented_box import OrientedBox |
|
|
from nuplan.common.actor_state.vehicle_parameters import get_pacifica_parameters |
|
|
from nuplan.common.actor_state.car_footprint import CarFootprint |
|
|
from nuplan.common.actor_state.tracked_objects_types import TrackedObjectType |
|
|
from nuplan.common.geometry.transform import translate_longitudinally |
|
|
|
|
|
from navsim.common.dataclasses import Frame, Annotations, Trajectory, Lidar |
|
|
from navsim.common.enums import BoundingBoxIndex, LidarIndex |
|
|
from navsim.planning.scenario_builder.navsim_scenario_utils import tracked_object_types |
|
|
from navsim.visualization.lidar import filter_lidar_pc, get_lidar_pc_color |
|
|
from navsim.visualization.config import BEV_PLOT_CONFIG, MAP_LAYER_CONFIG, AGENT_CONFIG, LIDAR_CONFIG |
|
|
|
|
|
|
|
|
def add_configured_bev_on_ax(ax: plt.Axes, map_api: AbstractMap, frame: Frame) -> plt.Axes: |
|
|
""" |
|
|
Adds birds-eye-view visualization optionally with map, annotations, or lidar |
|
|
:param ax: matplotlib ax object |
|
|
:param map_api: nuPlans map interface |
|
|
:param frame: navsim frame dataclass |
|
|
:return: ax with plot |
|
|
""" |
|
|
|
|
|
if "map" in BEV_PLOT_CONFIG["layers"]: |
|
|
add_map_to_bev_ax(ax, map_api, StateSE2(*frame.ego_status.ego_pose)) |
|
|
|
|
|
if "annotations" in BEV_PLOT_CONFIG["layers"]: |
|
|
add_annotations_to_bev_ax(ax, frame.annotations) |
|
|
|
|
|
if "lidar" in BEV_PLOT_CONFIG["layers"]: |
|
|
add_lidar_to_bev_ax(ax, frame.lidar) |
|
|
|
|
|
return ax |
|
|
|
|
|
|
|
|
def add_annotations_to_bev_ax(ax: plt.Axes, annotations: Annotations, add_ego: bool = True) -> plt.Axes: |
|
|
""" |
|
|
Adds birds-eye-view visualization of annotations (ie. bounding boxes) |
|
|
:param ax: matplotlib ax object |
|
|
:param annotations: navsim annotations dataclass |
|
|
:param add_ego: boolean weather to add ego bounding box, defaults to True |
|
|
:return: ax with plot |
|
|
""" |
|
|
|
|
|
for name_value, box_value in zip(annotations.names, annotations.boxes): |
|
|
agent_type = tracked_object_types[name_value] |
|
|
|
|
|
x, y, heading = ( |
|
|
box_value[BoundingBoxIndex.X], |
|
|
box_value[BoundingBoxIndex.Y], |
|
|
box_value[BoundingBoxIndex.HEADING], |
|
|
) |
|
|
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) |
|
|
|
|
|
add_oriented_box_to_bev_ax(ax, agent_box, AGENT_CONFIG[agent_type]) |
|
|
|
|
|
if add_ego: |
|
|
car_footprint = CarFootprint.build_from_rear_axle( |
|
|
rear_axle_pose=StateSE2(0, 0, 0), |
|
|
vehicle_parameters=get_pacifica_parameters(), |
|
|
) |
|
|
add_oriented_box_to_bev_ax( |
|
|
ax, car_footprint.oriented_box, AGENT_CONFIG[TrackedObjectType.EGO], add_heading=False |
|
|
) |
|
|
return ax |
|
|
|
|
|
|
|
|
def add_map_to_bev_ax(ax: plt.Axes, map_api: AbstractMap, origin: StateSE2) -> plt.Axes: |
|
|
""" |
|
|
Adds birds-eye-view visualization of map (ie. polygons / lines) |
|
|
TODO: add more layers for visualizations (or flags in config) |
|
|
:param ax: matplotlib ax object |
|
|
:param map_api: nuPlans map interface |
|
|
:param origin: (x,y,θ) dataclass of global ego frame |
|
|
:return: ax with plot |
|
|
""" |
|
|
|
|
|
|
|
|
polygon_layers: List[SemanticMapLayer] = [ |
|
|
SemanticMapLayer.LANE, |
|
|
SemanticMapLayer.WALKWAYS, |
|
|
SemanticMapLayer.CARPARK_AREA, |
|
|
SemanticMapLayer.INTERSECTION, |
|
|
SemanticMapLayer.STOP_LINE, |
|
|
SemanticMapLayer.CROSSWALK, |
|
|
] |
|
|
|
|
|
|
|
|
polyline_layers: List[SemanticMapLayer] = [ |
|
|
SemanticMapLayer.LANE, |
|
|
SemanticMapLayer.LANE_CONNECTOR, |
|
|
] |
|
|
|
|
|
|
|
|
map_object_dict = map_api.get_proximal_map_objects( |
|
|
point=origin.point, |
|
|
radius=max(BEV_PLOT_CONFIG["figure_margin"]), |
|
|
layers=list(set(polygon_layers + polyline_layers)), |
|
|
) |
|
|
|
|
|
def _geometry_local_coords(geometry: Any, origin: StateSE2) -> Any: |
|
|
"""Helper for transforming shapely geometry in coord-frame""" |
|
|
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 |
|
|
|
|
|
for polygon_layer in polygon_layers: |
|
|
for map_object in map_object_dict[polygon_layer]: |
|
|
polygon: Polygon = _geometry_local_coords(map_object.polygon, origin) |
|
|
add_polygon_to_bev_ax(ax, polygon, MAP_LAYER_CONFIG[polygon_layer]) |
|
|
|
|
|
for polyline_layer in polyline_layers: |
|
|
for map_object in map_object_dict[polyline_layer]: |
|
|
linestring: LineString = _geometry_local_coords(map_object.baseline_path.linestring, origin) |
|
|
add_linestring_to_bev_ax(ax, linestring, MAP_LAYER_CONFIG[SemanticMapLayer.BASELINE_PATHS]) |
|
|
return ax |
|
|
|
|
|
|
|
|
def add_lidar_to_bev_ax(ax: plt.Axes, lidar: Lidar) -> plt.Axes: |
|
|
""" |
|
|
Add lidar point cloud in birds-eye-view |
|
|
:param ax: matplotlib ax object |
|
|
:param lidar: navsim lidar dataclass |
|
|
:return: ax with plot |
|
|
""" |
|
|
|
|
|
lidar_pc = filter_lidar_pc(lidar.lidar_pc) |
|
|
lidar_pc_colors = get_lidar_pc_color(lidar_pc, as_hex=True) |
|
|
ax.scatter( |
|
|
lidar_pc[LidarIndex.Y], |
|
|
lidar_pc[LidarIndex.X], |
|
|
c=lidar_pc_colors, |
|
|
alpha=LIDAR_CONFIG["alpha"], |
|
|
s=LIDAR_CONFIG["size"], |
|
|
zorder=LIDAR_CONFIG["zorder"], |
|
|
) |
|
|
return ax |
|
|
|
|
|
|
|
|
def add_trajectory_to_bev_ax(ax: plt.Axes, trajectory: Trajectory, config: Dict[str, Any]) -> plt.Axes: |
|
|
""" |
|
|
Add trajectory poses as lint to plot |
|
|
:param ax: matplotlib ax object |
|
|
:param trajectory: navsim trajectory dataclass |
|
|
:param config: dictionary with plot parameters |
|
|
:return: ax with plot |
|
|
""" |
|
|
poses = np.concatenate([np.array([[0, 0]]), trajectory.poses[:, :2]]) |
|
|
ax.plot( |
|
|
poses[:, 1], |
|
|
poses[:, 0], |
|
|
color=config["line_color"], |
|
|
alpha=config["line_color_alpha"], |
|
|
linewidth=config["line_width"], |
|
|
linestyle=config["line_style"], |
|
|
marker=config["marker"], |
|
|
markersize=config["marker_size"], |
|
|
markeredgecolor=config["marker_edge_color"], |
|
|
zorder=config["zorder"], |
|
|
) |
|
|
return ax |
|
|
|
|
|
|
|
|
def add_oriented_box_to_bev_ax( |
|
|
ax: plt.Axes, box: OrientedBox, config: Dict[str, Any], add_heading: bool = True |
|
|
) -> plt.Axes: |
|
|
""" |
|
|
Adds birds-eye-view visualization of surrounding bounding boxes |
|
|
:param ax: matplotlib ax object |
|
|
:param box: nuPlan dataclass for 2D bounding boxes |
|
|
:param config: dictionary with plot parameters |
|
|
:param add_heading: whether to add a heading line, defaults to True |
|
|
:return: ax with plot |
|
|
""" |
|
|
|
|
|
box_corners = box.all_corners() |
|
|
corners = [[corner.x, corner.y] for corner in box_corners] |
|
|
corners = np.asarray(corners + [corners[0]]) |
|
|
|
|
|
ax.fill( |
|
|
corners[:, 1], |
|
|
corners[:, 0], |
|
|
color=config["fill_color"], |
|
|
alpha=config["fill_color_alpha"], |
|
|
zorder=config["zorder"], |
|
|
) |
|
|
ax.plot( |
|
|
corners[:, 1], |
|
|
corners[:, 0], |
|
|
color=config["line_color"], |
|
|
alpha=config["line_color_alpha"], |
|
|
linewidth=config["line_width"], |
|
|
linestyle=config["line_style"], |
|
|
zorder=config["zorder"], |
|
|
) |
|
|
|
|
|
if add_heading: |
|
|
future = translate_longitudinally(box.center, distance=box.length / 2 + 1) |
|
|
line = np.array([[box.center.x, box.center.y], [future.x, future.y]]) |
|
|
ax.plot( |
|
|
line[:, 1], |
|
|
line[:, 0], |
|
|
color=config["line_color"], |
|
|
alpha=config["line_color_alpha"], |
|
|
linewidth=config["line_width"], |
|
|
linestyle=config["line_style"], |
|
|
zorder=config["zorder"], |
|
|
) |
|
|
|
|
|
return ax |
|
|
|
|
|
|
|
|
def add_polygon_to_bev_ax(ax: plt.Axes, polygon: Polygon, config: Dict[str, Any]) -> plt.Axes: |
|
|
""" |
|
|
Adds shapely polygon to birds-eye-view visualization |
|
|
:param ax: matplotlib ax object |
|
|
:param polygon: shapely Polygon |
|
|
:param config: dictionary containing plot parameters |
|
|
:return: ax with plot |
|
|
""" |
|
|
|
|
|
def _add_element_helper(element: Polygon): |
|
|
"""Helper to add single polygon to ax""" |
|
|
exterior_x, exterior_y = element.exterior.xy |
|
|
ax.fill( |
|
|
exterior_y, |
|
|
exterior_x, |
|
|
color=config["fill_color"], |
|
|
alpha=config["fill_color_alpha"], |
|
|
zorder=config["zorder"], |
|
|
) |
|
|
ax.plot( |
|
|
exterior_y, |
|
|
exterior_x, |
|
|
color=config["line_color"], |
|
|
alpha=config["line_color_alpha"], |
|
|
linewidth=config["line_width"], |
|
|
linestyle=config["line_style"], |
|
|
zorder=config["zorder"], |
|
|
) |
|
|
for interior in element.interiors: |
|
|
x_interior, y_interior = interior.xy |
|
|
ax.fill( |
|
|
y_interior, |
|
|
x_interior, |
|
|
color=BEV_PLOT_CONFIG["background_color"], |
|
|
zorder=config["zorder"], |
|
|
) |
|
|
ax.plot( |
|
|
y_interior, |
|
|
x_interior, |
|
|
color=config["line_color"], |
|
|
alpha=config["line_color_alpha"], |
|
|
linewidth=config["line_width"], |
|
|
linestyle=config["line_style"], |
|
|
zorder=config["zorder"], |
|
|
) |
|
|
|
|
|
if isinstance(polygon, Polygon): |
|
|
_add_element_helper(polygon) |
|
|
else: |
|
|
|
|
|
for element in polygon: |
|
|
_add_element_helper(element) |
|
|
|
|
|
return ax |
|
|
|
|
|
|
|
|
def add_linestring_to_bev_ax(ax: plt.Axes, linestring: LineString, config: Dict[str, Any]) -> plt.Axes: |
|
|
""" |
|
|
Adds shapely linestring (polyline) to birds-eye-view visualization |
|
|
:param ax: matplotlib ax object |
|
|
:param linestring: shapely LineString |
|
|
:param config: dictionary containing plot parameters |
|
|
:return: ax with plot |
|
|
""" |
|
|
|
|
|
x, y = linestring.xy |
|
|
ax.plot( |
|
|
y, |
|
|
x, |
|
|
color=config["line_color"], |
|
|
alpha=config["line_color_alpha"], |
|
|
linewidth=config["line_width"], |
|
|
linestyle=config["line_style"], |
|
|
zorder=config["zorder"], |
|
|
) |
|
|
|
|
|
return ax |
|
|
|