class WaypointPlanningOperator(erdos.Operator):
    """Computes waypoints the ego vehicle must follow.

    If the operator is running in CARLA challenge mode, then it receives all
    the waypoints from the scenario runner agent (on the global trajectory
    stream). Otherwise, it computes waypoints using the HD Map.

    The planner reduces speed/stops whenever it encounters an obstacle,
    and waits for the obstacle to move. It does not implement an obstacle
    avoidance policy.

    Args:
        can_bus_stream (:py:class:`erdos.ReadStream`): Stream on which can bus
            info is received.
        open_drive_stream (:py:class:`erdos.ReadStream`): Stream on which open
            drive string representations are received. The operator can
            construct HDMaps out of the open drive strings.
        global_trajectory_stream (:py:class:`erdos.ReadStream`): Stream on
            which the scenario runner publishes waypoints.
        waypoints_stream (:py:class:`erdos.WriteStream`): Stream on which the
            operator sends waypoints the ego vehicle must follow.
        flags (absl.flags): Object to be used to access absl flags.
        goal_location (:py:class:`~pylot.utils.Location`): The goal location of
            the ego vehicle.
    """
    def __init__(self,
                 can_bus_stream,
                 open_drive_stream,
                 global_trajectory_stream,
                 obstacles_stream,
                 traffic_lights_stream,
                 waypoints_stream,
                 flags,
                 goal_location=None):
        can_bus_stream.add_callback(self.on_can_bus_update)
        open_drive_stream.add_callback(self.on_opendrive_map)
        global_trajectory_stream.add_callback(self.on_global_trajectory)
        obstacles_stream.add_callback(self.on_obstacles_update)
        traffic_lights_stream.add_callback(self.on_traffic_lights_update)
        erdos.add_watermark_callback(
            [can_bus_stream, obstacles_stream, traffic_lights_stream],
            [waypoints_stream], self.on_watermark)

        self._logger = erdos.utils.setup_logging(self.config.name,
                                                 self.config.log_file_name)
        self._flags = flags
        # Initialize the state of the behaviour planner.
        # XXX(ionel): The behaviour planner is not ready yet.
        self.__initialize_behaviour_planner()
        # We do not know yet the vehicle's location.
        self._vehicle_transform = None
        self._goal_location = goal_location
        self._map = None
        # Deque of waypoints the vehicle must follow. The waypoints are either
        # received on the global trajectory stream when running using the
        # scenario runner, or computed using the Carla global planner when
        # running in stand-alone mode. The waypoints are Pylot transforms.
        self._waypoints = deque()
        self._can_bus_msgs = deque()
        self._obstacles_msgs = deque()
        self._traffic_light_msgs = deque()

    @staticmethod
    def connect(can_bus_stream, open_drive_stream, global_trajectory_stream,
                obstacles_stream, traffic_lights_stream):
        waypoints_stream = erdos.WriteStream()
        return [waypoints_stream]

    def run(self):
        # Run method is invoked after all operators finished initializing,
        # including the CARLA operator, which reloads the world. Thus, if
        # we get the map here we're sure it is up-to-date.

        # We're not running in challenge mode if no track flag is present.
        # Thus, we can directly get the map from the simulator.
        if not hasattr(self._flags, 'track'):
            from pylot.map.hd_map import HDMap
            from pylot.simulation.utils import get_map
            self._map = HDMap(
                get_map(self._flags.carla_host, self._flags.carla_port,
                        self._flags.carla_timeout))
            self._logger.info('Planner running in stand-alone mode')
            # Recompute waypoints every RECOMPUTE_WAYPOINT_EVERY_N_WATERMARKS.
            self._recompute_waypoints = True
        else:
            # Do not recompute waypoints upon each run.
            self._recompute_waypoints = False
        self._watermark_cnt = 0

    def on_opendrive_map(self, msg):
        """Invoked whenever a message is received on the open drive stream.

        Args:
            msg (:py:class:`~erdos.message.Message`): Message that contains
                the open drive string.
        """
        self._logger.debug('@{}: received open drive message'.format(
            msg.timestamp))
        try:
            import carla
        except ImportError:
            raise Exception('Error importing carla.')
        self._logger.info('Initializing HDMap from open drive stream')
        from pylot.map.hd_map import HDMap
        self._map = HDMap(carla.Map('map', msg.data))

    def on_global_trajectory(self, msg):
        """Invoked whenever a message is received on the trajectory stream.

        Args:
            msg (:py:class:`~erdos.message.Message`): Message that contains
                a list of waypoints to the goal location.
        """
        self._logger.debug('@{}: global trajectory has {} waypoints'.format(
            msg.timestamp, len(msg.data)))
        if len(msg.data) > 0:
            # The last waypoint is the goal location.
            self._goal_location = msg.data[-1][0].location
        else:
            # Trajectory does not contain any waypoints. We assume we have
            # arrived at destination.
            self._goal_location = self._vehicle_transform.location
        assert self._goal_location, 'Planner does not have a goal'
        self._waypoints = deque()
        for waypoint_option in msg.data:
            self._waypoints.append(waypoint_option[0])

    def on_can_bus_update(self, msg):
        """Invoked whenever a message is received on the can bus stream.

        Args:
            msg (:py:class:`~erdos.message.Message`): Message that contains
                info about the ego vehicle.
        """
        self._logger.debug('@{}: received can bus message'.format(
            msg.timestamp))
        self._can_bus_msgs.append(msg)

    def on_obstacles_update(self, msg):
        self._logger.debug('@{}: obstacles update'.format(msg.timestamp))
        self._obstacles_msgs.append(msg)

    def on_traffic_lights_update(self, msg):
        self._logger.debug('@{}: traffic lights update'.format(msg.timestamp))
        self._traffic_light_msgs.append(msg)

    @erdos.profile_method()
    def on_watermark(self, timestamp, waypoints_stream):
        self._logger.debug('@{}: received watermark'.format(timestamp))
        self._watermark_cnt += 1
        # Get hero vehicle info.
        can_bus_msg = self._can_bus_msgs.popleft()
        self._vehicle_transform = can_bus_msg.data.transform
        tl_msg = self._traffic_light_msgs.popleft()
        obstacles_msg = self._obstacles_msgs.popleft()

        if (self._recompute_waypoints
                and self._watermark_cnt % RECOMPUTE_WAYPOINT_EVERY_N_WATERMARKS
                == 0):
            self._waypoints = self._map.compute_waypoints(
                self._vehicle_transform.location, self._goal_location)
        self.__remove_completed_waypoints()
        if not self._waypoints or len(self._waypoints) == 0:
            # If waypoints are empty (e.g., reached destination), set waypoint
            # to current vehicle location.
            self._waypoints = deque([self._vehicle_transform])

        wp_vector, wp_angle = \
            pylot.planning.utils.compute_waypoint_vector_and_angle(
                self._vehicle_transform, self._waypoints,
                DEFAULT_TARGET_WAYPOINT)

        speed_factor, _ = pylot.planning.utils.stop_for_agents(
            self._vehicle_transform.location, wp_angle, wp_vector,
            obstacles_msg.obstacles, tl_msg.obstacles, self._flags,
            self._logger, self._map, timestamp)

        target_speed = speed_factor * self._flags.target_speed
        self._logger.debug('@{}: computed speed factor: {}'.format(
            timestamp, speed_factor))
        self._logger.debug('@{}: computed target speed: {}'.format(
            timestamp, target_speed))
        head_waypoints = deque(
            itertools.islice(self._waypoints, 0, DEFAULT_NUM_WAYPOINTS))
        target_speeds = deque(
            [target_speed for _ in range(len(head_waypoints))])
        waypoints_stream.send(
            WaypointsMessage(timestamp, head_waypoints, target_speeds))

    def __remove_completed_waypoints(self):
        """Removes waypoints that the ego vehicle has already completed.

        The method first finds the closest waypoint, removes all waypoints
        that are before the closest waypoint, and finally removes the closest
        waypoint if the ego vehicle is very close to it (i.e., close to
        completion).
        """
        min_dist = 10000000
        min_index = 0
        index = 0
        for waypoint in self._waypoints:
            # XXX(ionel): We only check the first 10 waypoints.
            if index > 10:
                break
            dist = waypoint.location.distance(self._vehicle_transform.location)
            if dist < min_dist:
                min_dist = dist
                min_index = index

        # Remove waypoints that are before the closest waypoint. The ego
        # vehicle already completed them.
        while min_index > 0:
            self._waypoints.popleft()
            min_index -= 1

        # The closest waypoint is almost complete, remove it.
        if min_dist < WAYPOINT_COMPLETION_THRESHOLD:
            self._waypoints.popleft()

    def __initialize_behaviour_planner(self):
        # State the planner is in.
        self._state = BehaviorPlannerState.READY
        # Cost functions. Output between 0 and 1.
        self._cost_functions = [
            pylot.planning.cost_functions.cost_speed,
            pylot.planning.cost_functions.cost_lane_change,
            pylot.planning.cost_functions.cost_inefficiency
        ]
        reach_speed_weight = 10**5
        reach_goal_weight = 10**6
        efficiency_weight = 10**4
        # How important a cost function is.
        self._function_weights = [
            reach_speed_weight, reach_goal_weight, efficiency_weight
        ]

    def __best_transition(self, vehicle_transform, predictions):
        """ Computes most likely state transition from current state."""
        # Get possible next state machine states.
        possible_next_states = self.__successor_states()
        best_next_state = None
        min_state_cost = 10000000
        for state in possible_next_states:
            # Generate trajectory for next state.
            vehicle_info, trajectory_for_state = self.__generate_trajectory(
                state, vehicle_transform, predictions)
            state_cost = 0
            # Compute the cost of the trajectory.
            for i in range(len(self._cost_functions)):
                cost_func = self._cost_functions[i](vehicle_info, predictions,
                                                    trajectory_for_state)
                state_cost += self._function_weights[i] * cost_func
            # Check if it's the best trajectory.
            if best_next_state is None or state_cost < min_state_cost:
                best_next_state = state
                min_state_cost = state_cost
        return best_next_state

    def __generate_trajectory(self, next_state, vehicle_transform,
                              predictions):
        raise NotImplementedError

    def __successor_states(self):
        """ Returns possible state transitions from current state."""
        if self._state == BehaviorPlannerState.READY:
            return [BehaviorPlannerState.READY, BehaviorPlannerState.KEEP_LANE]
        elif self._state == BehaviorPlannerState.KEEP_LANE:
            # 1) keep_lane -> prepare_lane_change_left
            # 2) keep_lane -> prepare_lane_change_right
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.PREPARE_LANE_CHANGE_LEFT,
                BehaviorPlannerState.PREPARE_LANE_CHANGE_RIGHT
            ]
        elif self._state == BehaviorPlannerState.PREPARE_LANE_CHANGE_LEFT:
            # 1) prepare_lane_change_left -> keep_lane
            # 2) prepare_lane_change_left -> lange_change_left
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.PREPARE_LANE_CHANGE_LEFT,
                BehaviorPlannerState.LANE_CHANGE_LEFT
            ]
        elif self._state == BehaviorPlannerState.LANE_CHANGE_LEFT:
            # 1) lange_change_left -> keep_lane
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.LANE_CHANGE_LEFT
            ]
        elif self._state == BehaviorPlannerState.PREPARE_LANE_CHANGE_RIGHT:
            # 1) prepare_lane_change_right -> keep_lane
            # 2) prepare_lane_change_right -> lange_change_right
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.PREPARE_LANE_CHANGE_RIGHT,
                BehaviorPlannerState.LANE_CHANGE_RIGHT
            ]
        elif self._state == BehaviorPlannerState.LANE_CHANGE_RIGHT:
            # 1) lane_change_right -> keep_lane
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.LANE_CHANGE_RIGHT
            ]
        else:
            raise ValueError('Unexpected vehicle state {}'.format(self._state))
class BehaviorPlanningOperator(erdos.Operator):
    """Behavior planning operator.

    Args:
        pose_stream (:py:class:`erdos.ReadStream`): Stream on which pose
            info is received.
        open_drive_stream (:py:class:`erdos.ReadStream`): Stream on which open
            drive string representations are received. The operator can
            construct HDMaps out of the open drive strings.
        route_stream (:py:class:`erdos.ReadStream`): Stream on which the
            scenario runner publishes waypoints.
        trajectory_stream (:py:class:`erdos.WriteStream`): Stream on which the
            operator sends waypoints the ego vehicle must follow.
        flags (absl.flags): Object to be used to access absl flags.
        goal_location (:py:class:`~pylot.utils.Location`): The goal location of
            the ego vehicle.
    """
    def __init__(self,
                 pose_stream,
                 open_drive_stream,
                 route_stream,
                 trajectory_stream,
                 flags,
                 goal_location=None):
        pose_stream.add_callback(self.on_pose_update)
        open_drive_stream.add_callback(self.on_opendrive_map)
        route_stream.add_callback(self.on_route_msg)
        erdos.add_watermark_callback(
            [pose_stream, open_drive_stream, route_stream],
            [trajectory_stream], self.on_watermark)

        self._logger = erdos.utils.setup_logging(self.config.name,
                                                 self.config.log_file_name)
        self._flags = flags
        # Do not set the goal location here so that the behavior planner
        # issues an initial message.
        self._goal_location = None
        # Initialize the state of the behaviour planner.
        self.__initialize_behaviour_planner()
        self._pose_msgs = deque()
        self._ego_info = EgoInfo()
        if goal_location:
            self._route = Waypoints(
                deque([
                    pylot.utils.Transform(goal_location,
                                          pylot.utils.Rotation())
                ]))
        else:
            self._route = None
        self._map = None

    @staticmethod
    def connect(pose_stream, open_drive_stream, route_stream):
        trajectory_stream = erdos.WriteStream()
        return [trajectory_stream]

    def on_opendrive_map(self, msg):
        """Invoked whenever a message is received on the open drive stream.

        Args:
            msg (:py:class:`~erdos.message.Message`): Message that contains
                the open drive string.
        """
        self._logger.debug('@{}: received open drive message'.format(
            msg.timestamp))
        try:
            import carla
        except ImportError:
            raise Exception('Error importing carla.')
        self._logger.info('Initializing HDMap from open drive stream')
        from pylot.map.hd_map import HDMap
        self._map = HDMap(carla.Map('map', msg.data),
                          self.config.log_file_name)

    def on_route_msg(self, msg):
        """Invoked whenever a message is received on the trajectory stream.

        Args:
            msg (:py:class:`~erdos.message.Message`): Message that contains
                a list of waypoints to the goal location.
        """
        self._logger.debug('@{}: global trajectory has {} waypoints'.format(
            msg.timestamp, len(msg.waypoints.waypoints)))
        self._route = msg.waypoints

    def on_pose_update(self, msg):
        """Invoked whenever a message is received on the pose stream.

        Args:
            msg (:py:class:`~erdos.message.Message`): Message that contains
                info about the ego vehicle.
        """
        self._logger.debug('@{}: received pose message'.format(msg.timestamp))
        self._pose_msgs.append(msg)

    def on_watermark(self, timestamp, trajectory_stream):
        self._logger.debug('@{}: received watermark'.format(timestamp))
        pose_msg = self._pose_msgs.popleft()
        ego_transform = pose_msg.data.transform
        self._ego_info.update(self._state, pose_msg)
        old_state = self._state
        self._state = self.__best_state_transition(self._ego_info)
        self._logger.debug('@{}: agent transitioned from {} to {}'.format(
            timestamp, old_state, self._state))
        # Remove the waypoint from the route if we're close to it.
        if (old_state != self._state
                and self._state == BehaviorPlannerState.OVERTAKE):
            self._route.remove_waypoint_if_close(ego_transform.location, 10)
        else:
            if not self._map.is_intersection(ego_transform.location):
                self._route.remove_waypoint_if_close(ego_transform.location, 5)
            else:
                self._route.remove_waypoint_if_close(ego_transform.location,
                                                     3.5)
        new_goal_location = None
        if len(self._route.waypoints) > 1:
            new_goal_location = self._route.waypoints[1].location
        elif len(self._route.waypoints) == 1:
            new_goal_location = self._route.waypoints[0].location
        else:
            new_goal_location = ego_transform.location
        if new_goal_location != self._goal_location:
            self._goal_location = new_goal_location
            if self._map:
                # Use the map to compute more fine-grained waypoints.
                waypoints = self._map.compute_waypoints(
                    ego_transform.location, self._goal_location)
                road_options = deque([
                    pylot.utils.RoadOption.LANE_FOLLOW
                    for _ in range(len(waypoints))
                ])
                waypoints = Waypoints(waypoints, road_options=road_options)
            else:
                # Map is not available, send the route.
                waypoints = self._route
            if not waypoints or waypoints.is_empty():
                # If waypoints are empty (e.g., reached destination), set
                # waypoints to current vehicle location.
                waypoints = Waypoints(
                    deque([ego_transform]),
                    road_options=deque([pylot.utils.RoadOption.LANE_FOLLOW]))
            trajectory_stream.send(
                WaypointsMessage(timestamp, waypoints, self._state))
        elif old_state != self._state:
            # Send the state update.
            trajectory_stream.send(
                WaypointsMessage(timestamp, None, self._state))
        trajectory_stream.send(erdos.WatermarkMessage(timestamp))

    def __initialize_behaviour_planner(self):
        # State the planner is in.
        self._state = BehaviorPlannerState.FOLLOW_WAYPOINTS
        # Cost functions. Output between 0 and 1.
        self._cost_functions = [
            pylot.planning.cost_functions.cost_overtake,
        ]
        # How important a cost function is.
        self._function_weights = [1]

    def __successor_states(self):
        """ Returns possible state transitions from current state."""
        if self._state == BehaviorPlannerState.FOLLOW_WAYPOINTS:
            # Can transition to OVERTAKE if the ego vehicle has been stuck
            # behind an obstacle for a while.
            return [
                BehaviorPlannerState.FOLLOW_WAYPOINTS,
                BehaviorPlannerState.OVERTAKE
            ]
        elif self._state == BehaviorPlannerState.OVERTAKE:
            return [
                BehaviorPlannerState.OVERTAKE,
                BehaviorPlannerState.FOLLOW_WAYPOINTS
            ]
        elif self._state == BehaviorPlannerState.KEEP_LANE:
            # 1) keep_lane -> prepare_lane_change_left
            # 2) keep_lane -> prepare_lane_change_right
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.PREPARE_LANE_CHANGE_LEFT,
                BehaviorPlannerState.PREPARE_LANE_CHANGE_RIGHT
            ]
        elif self._state == BehaviorPlannerState.PREPARE_LANE_CHANGE_LEFT:
            # 1) prepare_lane_change_left -> keep_lane
            # 2) prepare_lane_change_left -> lange_change_left
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.PREPARE_LANE_CHANGE_LEFT,
                BehaviorPlannerState.LANE_CHANGE_LEFT
            ]
        elif self._state == BehaviorPlannerState.LANE_CHANGE_LEFT:
            # 1) lange_change_left -> keep_lane
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.LANE_CHANGE_LEFT
            ]
        elif self._state == BehaviorPlannerState.PREPARE_LANE_CHANGE_RIGHT:
            # 1) prepare_lane_change_right -> keep_lane
            # 2) prepare_lane_change_right -> lange_change_right
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.PREPARE_LANE_CHANGE_RIGHT,
                BehaviorPlannerState.LANE_CHANGE_RIGHT
            ]
        elif self._state == BehaviorPlannerState.LANE_CHANGE_RIGHT:
            # 1) lane_change_right -> keep_lane
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.LANE_CHANGE_RIGHT
            ]
        else:
            raise ValueError('Unexpected vehicle state {}'.format(self._state))

    def __best_state_transition(self, ego_info):
        """ Computes most likely state transition from current state."""
        # Get possible next state machine states.
        possible_next_states = self.__successor_states()
        best_next_state = None
        min_state_cost = np.infty
        for state in possible_next_states:
            state_cost = 0
            # Compute the cost of the trajectory.
            for i in range(len(self._cost_functions)):
                cost_func = self._cost_functions[i](self._state, state,
                                                    ego_info)
                state_cost += self._function_weights[i] * cost_func
            # Check if it's the best trajectory.
            if state_cost < min_state_cost:
                best_next_state = state
                min_state_cost = state_cost
        return best_next_state
示例#3
0
class WaypointPlanningOperator(erdos.Operator):
    """ Planning operator for Carla 0.9.x.

    The operator either receives all the waypoints from the scenario runner
    agent (on the global trajectory stream), or computes waypoints using the
    HD Map.
    """
    def __init__(self,
                 can_bus_stream,
                 open_drive_stream,
                 global_trajectory_stream,
                 waypoints_stream,
                 name,
                 flags,
                 goal_location=None,
                 log_file_name=None,
                 csv_file_name=None):
        can_bus_stream.add_callback(self.on_can_bus_update, [waypoints_stream])
        open_drive_stream.add_callback(self.on_opendrive_map)
        global_trajectory_stream.add_callback(self.on_global_trajectory)

        self._log_file_name = log_file_name
        self._logger = erdos.utils.setup_logging(name, log_file_name)
        self._csv_logger = erdos.utils.setup_csv_logging(
            name + '-csv', csv_file_name)
        self._flags = flags
        # Initialize the state of the behaviour planner.
        # XXX(ionel): The behaviour planner is not ready yet.
        self.__initialize_behaviour_planner()
        # We do not know yet the vehicle's location.
        self._vehicle_transform = None
        # Deque of waypoints the vehicle must follow. The waypoints are either
        # received on the global trajectory stream when running using the
        # scenario runner, or computed using the Carla global planner when
        # running in stand-alone mode. The waypoints are Pylot transforms.
        self._waypoints = collections.deque()
        # The operator picks the wp_num_steer-th waypoint to compute the angle
        # it has to steer by when taking a turn.
        self._wp_num_steer = 9  # use 10th waypoint for steering
        # The operator picks the wp_num_speed-th waypoint to compute the angle
        # it has to steer by when driving straight.
        self._wp_num_speed = 4  # use tth waypoint for speed
        # We're not running in challenge mode if no track flag is present.
        # Thus, we can directly get the map from the simulator.
        if not hasattr(self._flags, 'track'):
            self._map = HDMap(
                get_map(self._flags.carla_host, self._flags.carla_port,
                        self._flags.carla_timeout), log_file_name)
            self._logger.info('Planner running in stand-alone mode')
            assert goal_location, 'Planner has not received a goal location'
            # Transform goal location to carla.Location
            self._goal_location = goal_location
            # Recompute waypoints upon each run.
            self._recompute_waypoints = True
        else:
            # Do not recompute waypoints upon each run.
            self._recompute_waypoints = False
            # TODO(ionel): HACK! In Carla challenge track 1 and 2 the waypoints
            # are coarse grained (30 meters apart). We pick the first waypoint
            # to compute the angles. However, we should instead implement
            # trajectory planning.
            if self._flags.track == 1 or self._flags == 2:
                self._wp_num_steer = 1
                self._wp_num_speed = 1

    @staticmethod
    def connect(can_bus_stream, open_drive_stream, global_trajectory_stream):
        waypoints_stream = erdos.WriteStream()
        return [waypoints_stream]

    def on_opendrive_map(self, msg):
        self._logger.debug('@{}: received open drive message'.format(
            msg.timestamp))
        self._logger.info('Initializing HDMap from open drive stream')
        self._map = HDMap(carla.Map('map', msg.data), self._log_file_name)

    def on_global_trajectory(self, msg):
        self._logger.debug('@{}: global trajectory has {} waypoints'.format(
            msg.timestamp, len(msg.data)))
        if len(msg.data) > 0:
            # The last waypoint is the goal location.
            self._goal_location = msg.data[-1][0].location.as_carla_location()
        else:
            # Trajectory does not contain any waypoints. We assume we have
            # arrived at destionation.
            goal_loc = self._vehicle_transform.location
            self._goal_location = goal_loc.as_carla_location()
        assert self._goal_location, 'Planner does not have a goal'
        self._waypoints = collections.deque()
        for waypoint_option in msg.data:
            self._waypoints.append(waypoint_option[0])

    def on_can_bus_update(self, msg, waypoints_stream):
        self._logger.debug('@{}: received can bus message'.format(
            msg.timestamp))
        self._vehicle_transform = msg.data.transform
        self.__update_waypoints()

        next_waypoint_steer = self._waypoints[min(
            len(self._waypoints) - 1, self._wp_num_steer)]
        next_waypoint_speed = self._waypoints[min(
            len(self._waypoints) - 1, self._wp_num_speed)]

        # Get vectors and angles to corresponding speed and steer waypoints.
        wp_steer_vector, wp_steer_angle = get_waypoint_vector_and_angle(
            next_waypoint_steer, self._vehicle_transform)
        wp_speed_vector, wp_speed_angle = get_waypoint_vector_and_angle(
            next_waypoint_speed, self._vehicle_transform)

        head_waypoints = collections.deque(
            itertools.islice(self._waypoints, 0, DEFAULT_NUM_WAYPOINTS))

        output_msg = WaypointsMessage(msg.timestamp,
                                      waypoints=head_waypoints,
                                      target_speed=self._flags.target_speed,
                                      wp_angle=wp_steer_angle,
                                      wp_vector=wp_steer_vector,
                                      wp_angle_speed=wp_speed_angle)
        waypoints_stream.send(output_msg)

    def __update_waypoints(self):
        """ Updates the waypoints.

        Depending on setup, the method either recomputes the waypoints
        between the ego vehicle and the goal location, or just garbage collects
        waypoints that have already been achieved.

        Returns:
            (wp_steer, wp_speed): The waypoints to be used to compute steer and
            speed angles.
        """
        if self._recompute_waypoints:
            ego_location = self._vehicle_transform.location.as_carla_location()
            self._waypoints = self._map.compute_waypoints(
                ego_location, self._goal_location)
        self.__remove_completed_waypoints()
        if not self._waypoints or len(self._waypoints) == 0:
            # If waypoints are empty (e.g., reached destination), set waypoint
            # to current vehicle location.
            self._waypoints = collections.deque([self._vehicle_transform])

    def __remove_completed_waypoints(self):
        """ Removes waypoints that the ego vehicle has already completed.

        The method first finds the closest waypoint, removes all waypoints
        that are before the closest waypoint, and finally removes the
        closest waypoint if the ego vehicle is very close to it
        (i.e., close to completion)."""
        min_dist = 10000000
        min_index = 0
        index = 0
        for waypoint in self._waypoints:
            # XXX(ionel): We only check the first 10 waypoints.
            if index > 10:
                break
            dist = waypoint.location.distance(self._vehicle_transform.location)
            if dist < min_dist:
                min_dist = dist
                min_index = index

        # Remove waypoints that are before the closest waypoint. The ego
        # vehicle already completed them.
        while min_index > 0:
            self._waypoints.popleft()
            min_index -= 1

        # The closest waypoint is almost complete, remove it.
        if min_dist < WAYPOINT_COMPLETION_THRESHOLD:
            self._waypoints.popleft()

    def __initialize_behaviour_planner(self):
        # State the planner is in.
        self._state = BehaviorPlannerState.READY
        # Cost functions. Output between 0 and 1.
        self._cost_functions = [
            pylot.planning.cost_functions.cost_speed,
            pylot.planning.cost_functions.cost_lane_change,
            pylot.planning.cost_functions.cost_inefficiency
        ]
        reach_speed_weight = 10**5
        reach_goal_weight = 10**6
        efficiency_weight = 10**4
        # How important a cost function is.
        self._function_weights = [
            reach_speed_weight, reach_goal_weight, efficiency_weight
        ]

    def __best_transition(self, vehicle_transform, predictions):
        """ Computes most likely state transition from current state."""
        # Get possible next state machine states.
        possible_next_states = self.__successor_states()
        best_next_state = None
        min_state_cost = 10000000
        for state in possible_next_states:
            # Generate trajectory for next state.
            vehicle_info, trajectory_for_state = self.__generate_trajectory(
                state, vehicle_transform, predictions)
            state_cost = 0
            # Compute the cost of the trajectory.
            for i in range(len(self._cost_functions)):
                cost_func = self._cost_functions[i](vehicle_info, predictions,
                                                    trajectory_for_state)
                state_cost += self._function_weights[i] * cost_func
            # Check if it's the best trajectory.
            if best_next_state is None or state_cost < min_state_cost:
                best_next_state = state
                min_state_cost = state_cost
        return best_next_state

    def __generate_trajectory(self, next_state, vehicle_transform,
                              predictions):
        raise NotImplementedError

    def __successor_states(self):
        """ Returns possible state transitions from current state."""
        if self._state == BehaviorPlannerState.READY:
            return [BehaviorPlannerState.READY, BehaviorPlannerState.KEEP_LANE]
        elif self._state == BehaviorPlannerState.KEEP_LANE:
            # 1) keep_lane -> prepare_lane_change_left
            # 2) keep_lane -> prepare_lane_change_right
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.PREPARE_LANE_CHANGE_LEFT,
                BehaviorPlannerState.PREPARE_LANE_CHANGE_RIGHT
            ]
        elif self._state == BehaviorPlannerState.PREPARE_LANE_CHANGE_LEFT:
            # 1) prepare_lane_change_left -> keep_lane
            # 2) prepare_lane_change_left -> lange_change_left
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.PREPARE_LANE_CHANGE_LEFT,
                BehaviorPlannerState.LANE_CHANGE_LEFT
            ]
        elif self._state == BehaviorPlannerState.LANE_CHANGE_LEFT:
            # 1) lange_change_left -> keep_lane
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.LANE_CHANGE_LEFT
            ]
        elif self._state == BehaviorPlannerState.PREPARE_LANE_CHANGE_RIGHT:
            # 1) prepare_lane_change_right -> keep_lane
            # 2) prepare_lane_change_right -> lange_change_right
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.PREPARE_LANE_CHANGE_RIGHT,
                BehaviorPlannerState.LANE_CHANGE_RIGHT
            ]
        elif self._state == BehaviorPlannerState.LANE_CHANGE_RIGHT:
            # 1) lane_change_right -> keep_lane
            return [
                BehaviorPlannerState.KEEP_LANE,
                BehaviorPlannerState.LANE_CHANGE_RIGHT
            ]
        else:
            raise ValueError('Unexpected vehicle state {}'.format(self._state))
示例#4
0
class RRTStarPlanningOperator(erdos.Operator):
    """RRTStar Planning operator for Carla 0.9.x.

    Args:
        flags: Config flags.
        goal_location: Goal pylot.utils.Location for planner to route to.
    """
    def __init__(self,
                 can_bus_stream,
                 prediction_stream,
                 global_trajectory_stream,
                 open_drive_stream,
                 waypoints_stream,
                 flags,
                 goal_location=None,
                 log_file_name=None,
                 csv_file_name=None):
        can_bus_stream.add_callback(self.on_can_bus_update)
        prediction_stream.add_callback(self.on_prediction_update)
        global_trajectory_stream.add_callback(self.on_global_trajectory)
        open_drive_stream.add_callback(self.on_opendrive_map)
        erdos.add_watermark_callback([can_bus_stream, prediction_stream],
                                     [waypoints_stream], self.on_watermark)
        self._logger = erdos.utils.setup_logging(self.config.name,
                                                 self.config.log_file_name)
        self._flags = flags

        self._vehicle_transform = None
        self._map = None
        self._waypoints = None
        self._prev_waypoints = None
        self._goal_location = goal_location
        self._can_bus_msgs = deque()
        self._prediction_msgs = deque()

    @staticmethod
    def connect(can_bus_stream, prediction_stream, global_trajectory_stream,
                open_drive_stream):
        waypoints_stream = erdos.WriteStream()
        return [waypoints_stream]

    def run(self):
        # Run method is invoked after all operators finished initializing,
        # including the CARLA operator, which reloads the world. Thus, if
        # we get the map here we're sure it is up-to-date.
        if not hasattr(self._flags, 'track'):
            from pylot.map.hd_map import HDMap
            from pylot.simulation.utils import get_map
            self._map = HDMap(
                get_map(self._flags.carla_host, self._flags.carla_port,
                        self._flags.carla_timeout))
            self._logger.info('Planner running in stand-alone mode')

    def on_can_bus_update(self, msg):
        self._logger.debug('@{}: received can bus message'.format(
            msg.timestamp))
        self._can_bus_msgs.append(msg)

    def on_prediction_update(self, msg):
        self._logger.debug('@{}: received prediction message'.format(
            msg.timestamp))
        self._prediction_msgs.append(msg)

    def on_global_trajectory(self, msg):
        """Invoked whenever a message is received on the trajectory stream.
        Args:
            msg (:py:class:`~erdos.message.Message`): Message that contains
                a list of waypoints to the goal location.
        """
        self._logger.debug('@{}: global trajectory has {} waypoints'.format(
            msg.timestamp, len(msg.data)))
        if len(msg.data) > 0:
            # The last waypoint is the goal location.
            self._goal_location = msg.data[-1][0].location
        else:
            # Trajectory does not contain any waypoints. We assume we have
            # arrived at destionation.
            self._goal_location = self._vehicle_transform.location
        assert self._goal_location, 'Planner does not have a goal'
        self._waypoints = deque()
        for waypoint_option in msg.data:
            self._waypoints.append(waypoint_option[0])

    def on_opendrive_map(self, msg):
        """Invoked whenever a message is received on the open drive stream.
        Args:
            msg (:py:class:`~erdos.message.Message`): Message that contains
                the open drive string.
        """
        self._logger.debug('@{}: received open drive message'.format(
            msg.timestamp))
        try:
            import carla
        except ImportError:
            raise Exception('Error importing carla.')
        self._logger.info('Initializing HDMap from open drive stream')
        from pylot.map.hd_map import HDMap
        self._map = HDMap(carla.Map('map', msg.data))

    @erdos.profile_method()
    def on_watermark(self, timestamp, waypoints_stream):
        self._logger.debug('@{}: received watermark'.format(timestamp))

        # get ego info
        can_bus_msg = self._can_bus_msgs.popleft()
        vehicle_transform = can_bus_msg.data.transform
        self._vehicle_transform = vehicle_transform

        # get obstacles
        prediction_msg = self._prediction_msgs.popleft()
        obstacle_list = self._build_obstacle_list(vehicle_transform,
                                                  prediction_msg)

        # update waypoints
        if not self._waypoints:
            # running in CARLA
            if self._map is not None:
                self._waypoints = self._map.compute_waypoints(
                    vehicle_transform.location, self._goal_location)
            else:
                # haven't received waypoints from global trajectory stream
                self._logger.debug("@{}: Sending target speed 0, haven't"
                                   "received global trajectory"
                                   .format(timestamp))
                head_waypoints = deque([vehicle_transform])
                target_speeds = deque([0])
                waypoints_stream.send(
                    WaypointsMessage(timestamp, head_waypoints, target_speeds))
                return

        # run rrt star
        # RRT* does not take into account the driveable region
        # it constructs search space as a top down, minimum bounding rectangle
        # with padding in each dimension
        success, (path_x, path_y) = \
            self._apply_rrt_star(obstacle_list, timestamp)

        speeds = [0]
        if success:
            speeds = [self._flags.target_speed] * len(path_x)
            self._logger.debug("@{}: RRT* Path X: {}".format(
                timestamp,
                path_x.tolist())
            )
            self._logger.debug("@{}: RRT* Path Y: {}".format(
                timestamp,
                path_y.tolist())
            )
            self._logger.debug("@{}: RRT* Speeds: {}".format(
                timestamp,
                [self._flags.target_speed] * len(path_x))
            )

        # construct and send waypoint message
        waypoint_message = self._construct_waypoints(
            timestamp, path_x, path_y, speeds, success
        )
        waypoints_stream.send(waypoint_message)

    def _get_closest_index(self, start):
        min_dist = np.infty
        mindex = 0
        for ind, wp in enumerate(self._waypoints):
            dist = np.linalg.norm([start[0] - wp.location.x,
                                   start[1] - wp.location.y])
            if dist <= min_dist:
                mindex = ind
                min_dist = dist
        return mindex

    def _apply_rrt_star(self, obstacles, timestamp):
        start = [
            self._vehicle_transform.location.x,
            self._vehicle_transform.location.y
        ]

        # find the closest point to current location
        mindex = self._get_closest_index(start)
        end_ind = min(mindex + DEFAULT_TARGET_WAYPOINT, len(self._waypoints) - 1)
        end = [
            self._waypoints[end_ind].location.x,
            self._waypoints[end_ind].location.y
        ]

        # log initial conditions for debugging
        initial_conditions = {
            "start": start,
            "end": end,
            "obstacles": obstacles.tolist(),
            "step_size": STEP_SIZE,
            "max_iterations": MAX_ITERATIONS,
        }
        self._logger.debug("@{}: Initial conditions: {}".format(
            timestamp, initial_conditions))
        return apply_rrt_star(start, end, STEP_SIZE, MAX_ITERATIONS, obstacles)

    def _construct_waypoints(self, timestamp, path_x, path_y, speeds, success):
        """
        Convert the rrt* path into a waypoints message.
        """
        path_transforms = []
        target_speeds = []
        if not success:
            self._logger.error("@{}: RRT* failed. "
                               "Sending emergency stop.".format(timestamp))
            for wp in itertools.islice(self._prev_waypoints, 0,
                                       DEFAULT_NUM_WAYPOINTS):
                path_transforms.append(wp)
                target_speeds.append(0)
        else:
            self._logger.debug("@{}: RRT* succeeded."
                               .format(timestamp))
            for point in zip(path_x, path_y, speeds):
                if self._map is not None:
                    p_loc = self._map.get_closest_lane_waypoint(
                        Location(x=point[0], y=point[1], z=0)).location
                else:
                    # RRT* does not take into account the driveable region
                    # it constructs search space as a top down, minimum bounding rectangle
                    # with padding in each dimension
                    p_loc = Location(x=point[0], y=point[1], z=0)
                path_transforms.append(
                    Transform(
                        location=Location(x=point[0], y=point[1], z=p_loc.z),
                        rotation=Rotation(),
                    ))
                target_speeds.append(point[2])

        waypoints = deque(path_transforms)
        self._prev_waypoints = waypoints
        return WaypointsMessage(timestamp, waypoints, target_speeds)

    @staticmethod
    def _build_obstacle_list(vehicle_transform, prediction_msg):
        """
        Construct an obstacle list of proximal objects given vehicle_transform.
        """
        obstacle_list = []
        # look over all predictions
        for prediction in prediction_msg.predictions:
            # use all prediction times as potential obstacles
            for transform in prediction.trajectory:
                global_obstacle = vehicle_transform * transform
                obstacle_origin = [
                    global_obstacle.location.x, global_obstacle.location.y
                ]
                dist_to_ego = np.linalg.norm([
                    vehicle_transform.location.x - obstacle_origin[0],
                    vehicle_transform.location.y - obstacle_origin[1]
                ])
                # TODO (@fangedward): Fix this hack
                # Prediction also sends a prediction for ego vehicle
                # This will always be the closest to the ego vehicle
                # Filter out until this is removed from prediction
                if dist_to_ego < 2:  # this allows max vel to be 20m/s
                    break
                elif dist_to_ego < DEFAULT_DISTANCE_THRESHOLD:
                    # use 3d bounding boxes if available, otherwise use default
                    if isinstance(prediction.bounding_box, BoundingBox3D):
                        start_location = \
                            prediction.bounding_box.transform.location - \
                            prediction.bounding_box.extent
                        end_location = \
                            prediction.bounding_box.transform.location + \
                            prediction.bounding_box.extent
                        start_transform = global_obstacle.transform_locations(
                            [start_location])
                        end_transform = global_obstacle.transform_locations(
                            [end_location])
                    else:
                        start_transform = [
                            Location(
                                obstacle_origin[0] - DEFAULT_OBSTACLE_SIZE,
                                obstacle_origin[1] - DEFAULT_OBSTACLE_SIZE,
                                0
                            )
                        ]
                        end_transform = [
                            Location(
                                obstacle_origin[0] + DEFAULT_OBSTACLE_SIZE,
                                obstacle_origin[1] + DEFAULT_OBSTACLE_SIZE,
                                0
                            )
                        ]
                    obstacle_list.append([min(start_transform[0].x,
                                              end_transform[0].x),
                                          min(start_transform[0].y,
                                              end_transform[0].y),
                                          max(start_transform[0].x,
                                              end_transform[0].x),
                                          max(start_transform[0].y,
                                              end_transform[0].y)])

        if len(obstacle_list) == 0:
            return np.empty((0, 4))

        return np.array(obstacle_list)
示例#5
0
class FOTPlanningOperator(erdos.Operator):
    """ Frenet Optimal Trajectory (FOT) Planning operator for Carla 0.9.x.

    This planning operator uses a global route and listens for predictions
    to produce a frenet optimal trajectory plan. Details can be found in
    `~pylot.planning.frenet_optimal_trajectory.frenet_optimal_trajectory.py`.

     Args:
        flags(:absl.flags:): Object to be used to access absl flags
        goal_location(:pylot.utils.Location:): Goal location for route planning
    """
    def __init__(self,
                 can_bus_stream,
                 prediction_stream,
                 global_trajectory_stream,
                 open_drive_stream,
                 waypoints_stream,
                 flags,
                 goal_location=None,
                 log_file_name=None,
                 csv_file_name=None):
        can_bus_stream.add_callback(self.on_can_bus_update)
        prediction_stream.add_callback(self.on_prediction_update)
        global_trajectory_stream.add_callback(self.on_global_trajectory)
        open_drive_stream.add_callback(self.on_opendrive_map)
        erdos.add_watermark_callback([can_bus_stream, prediction_stream],
                                     [waypoints_stream], self.on_watermark)
        self._logger = erdos.utils.setup_logging(self.config.name,
                                                 self.config.log_file_name)
        self._flags = flags

        self._vehicle_transform = None
        self._map = None
        self._waypoints = None
        self._prev_waypoints = None
        self._goal_location = goal_location

        self._can_bus_msgs = deque()
        self._prediction_msgs = deque()
        self.s0 = 0

    @staticmethod
    def connect(can_bus_stream, prediction_stream, global_trajectory_stream,
                open_drive_stream):
        waypoints_stream = erdos.WriteStream()
        return [waypoints_stream]

    def run(self):
        # Run method is invoked after all operators finished initializing,
        # including the CARLA operator, which reloads the world. Thus, if
        # we get the map here we're sure it is up-to-date.
        if not hasattr(self._flags, 'track'):
            from pylot.map.hd_map import HDMap
            from pylot.simulation.utils import get_map
            self._map = HDMap(
                get_map(self._flags.carla_host, self._flags.carla_port,
                        self._flags.carla_timeout))
            self._logger.info('Planner running in stand-alone mode')

    def on_can_bus_update(self, msg):
        self._logger.debug('@{}: received can bus message'.format(
            msg.timestamp))
        self._can_bus_msgs.append(msg)

    def on_prediction_update(self, msg):
        self._logger.debug('@{}: received prediction message'.format(
            msg.timestamp))
        self._prediction_msgs.append(msg)

    def on_global_trajectory(self, msg):
        """Invoked whenever a message is received on the trajectory stream.

        Args:
            msg (:py:class:`~erdos.message.Message`): Message that contains
                a list of waypoints to the goal location.
        """
        self._logger.debug('@{}: global trajectory has {} waypoints'.format(
            msg.timestamp, len(msg.data)))
        if len(msg.data) > 0:
            # The last waypoint is the goal location.
            self._goal_location = msg.data[-1][0].location
        else:
            # Trajectory does not contain any waypoints. We assume we have
            # arrived at destionation.
            self._goal_location = self._vehicle_transform.location
        assert self._goal_location, 'Planner does not have a goal'
        self._waypoints = deque()
        for waypoint_option in msg.data:
            self._waypoints.append(waypoint_option[0])

    def on_opendrive_map(self, msg):
        """Invoked whenever a message is received on the open drive stream.

        Args:
            msg (:py:class:`~erdos.message.Message`): Message that contains
                the open drive string.
        """
        self._logger.debug('@{}: received open drive message'.format(
            msg.timestamp))
        try:
            import carla
        except ImportError:
            raise Exception('Error importing carla.')
        self._logger.info('Initializing HDMap from open drive stream')
        from pylot.map.hd_map import HDMap
        self._map = HDMap(carla.Map('map', msg.data))

    @erdos.profile_method()
    def on_watermark(self, timestamp, waypoints_stream):
        self._logger.debug('@{}: received watermark'.format(timestamp))

        # get ego info
        can_bus_msg = self._can_bus_msgs.popleft()
        vehicle_transform = can_bus_msg.data.transform
        self._vehicle_transform = vehicle_transform

        # get obstacles
        prediction_msg = self._prediction_msgs.popleft()
        obstacle_list = self._build_obstacle_list(vehicle_transform,
                                                  prediction_msg)
        # update waypoints
        if not self._waypoints:
            # running in CARLA
            if self._map is not None:
                self._waypoints = self._map.compute_waypoints(
                    vehicle_transform.location, self._goal_location)
            # haven't received waypoints from global trajectory stream
            else:
                self._logger.debug("@{}: Sending target speed 0, haven't"
                                   "received global trajectory"
                                   .format(timestamp))
                head_waypoints = deque([vehicle_transform])
                target_speeds = deque([0])
                waypoints_stream.send(
                    WaypointsMessage(timestamp, head_waypoints, target_speeds))
                return

        # compute optimal frenet trajectory
        path_x, path_y, speeds, params, success, s0 = \
            self._compute_optimal_frenet_trajectory(can_bus_msg, obstacle_list)

        if success:
            self._logger.debug("@{}: Frenet Path X: {}".format(
                timestamp,
                path_x.tolist())
            )
            self._logger.debug("@{}: Frenet Path Y: {}".format(
                timestamp,
                path_y.tolist())
            )
            self._logger.debug("@{}: Frenet Speeds: {}".format(
                timestamp,
                speeds.tolist())
            )

        # construct and send waypoint message
        waypoints_message = self._construct_waypoints(
            timestamp, path_x, path_y, speeds, success
        )
        waypoints_stream.send(waypoints_message)

    def _compute_optimal_frenet_trajectory(self, can_bus_msg, obstacle_list):
        """
        Compute the optimal frenet trajectory, given current environment info.
        """
        # convert waypoints to frenet coordinates
        wx = []
        wy = []
        for wp in itertools.islice(self._waypoints, 0, DEFAULT_NUM_WAYPOINTS):
            wx.append(wp.location.x)
            wy.append(wp.location.y)
        wx = np.array(wx)
        wy = np.array(wy)

        # compute frenet optimal trajectory
        s0, c_speed, c_d, c_d_d, c_d_dd = \
            self._compute_initial_conditions(can_bus_msg, wx, wy)
        self.s0 = s0
        target_speed = (c_speed + self._flags.target_speed) / 2
        path_x, path_y, speeds, params, success = get_fot_frenet_space(
            s0, c_speed, c_d, c_d_d, c_d_dd,
            wx, wy, obstacle_list, target_speed
        )

        # log initial conditions for debugging
        initial_conditions = {
            "s0": s0,
            "c_speed": c_speed,
            "c_d": c_d,
            "c_d_d": c_d_d,
            "c_d_dd": c_d_dd,
            "wx": wx.tolist(),
            "wy": wy.tolist(),
            "obstacle_list": obstacle_list.tolist(),
            "x": can_bus_msg.data.transform.location.x,
            "y": can_bus_msg.data.transform.location.y,
            "vx": can_bus_msg.data.velocity_vector.x,
            "vy": can_bus_msg.data.velocity_vector.y,
        }
        timestamp = can_bus_msg.timestamp
        self._logger.debug("@{}: Initial conditions: {}".format(
            timestamp, initial_conditions))

        return path_x, path_y, speeds, params, success, s0

    def _construct_waypoints(self, timestamp, path_x, path_y, speeds, success):
        """
        Convert the optimal frenet path into a waypoints message.
        """
        path_transforms = []
        target_speeds = []
        if not success:
            self._logger.debug("@{}: Frenet Optimal Trajectory failed. "
                               "Sending emergency stop.".format(timestamp))
            for wp in itertools.islice(self._prev_waypoints, 0,
                                       DEFAULT_NUM_WAYPOINTS):
                path_transforms.append(wp)
                target_speeds.append(0)
        else:
            self._logger.debug("@{}: Frenet Optimal Trajectory succeeded."
                               .format(timestamp))
            for point in zip(path_x, path_y, speeds):
                if self._map is not None:
                    p_loc = self._map.get_closest_lane_waypoint(
                        Location(x=point[0], y=point[1], z=0)).location
                else:
                    p_loc = Location(x=point[0], y=point[1], z=0)
                path_transforms.append(
                    Transform(
                        location=Location(x=point[0], y=point[1], z=p_loc.z),
                        rotation=Rotation(),
                    ))
                target_speeds.append(point[2])

        waypoints = deque(path_transforms)
        self._prev_waypoints = waypoints
        return WaypointsMessage(timestamp, waypoints, target_speeds)

    def _compute_initial_conditions(self, can_bus_msg, wx, wy):
        """
        Convert the initial conditions of vehicle into frenet frame parameters.
        """
        x = can_bus_msg.data.transform.location.x
        y = can_bus_msg.data.transform.location.y
        vx = can_bus_msg.data.velocity_vector.x
        vy = can_bus_msg.data.velocity_vector.y
        return compute_initial_conditions(self.s0, x, y, vx, vy,
                                          can_bus_msg.data.forward_speed, wx,
                                          wy)

    @staticmethod
    def _build_obstacle_list(vehicle_transform, prediction_msg):
        """
        Construct an obstacle list of proximal objects given vehicle_transform.
        """
        obstacle_list = []
        # look over all predictions
        for prediction in prediction_msg.predictions:
            # use all prediction times as potential obstacles
            for transform in prediction.trajectory:
                global_obstacle = vehicle_transform * transform
                obstacle_origin = [
                    global_obstacle.location.x, global_obstacle.location.y
                ]
                dist_to_ego = np.linalg.norm([
                    vehicle_transform.location.x - obstacle_origin[0],
                    vehicle_transform.location.y - obstacle_origin[1]
                ])
                # TODO (@fangedward): Fix this hack
                # Prediction also sends a prediction for ego vehicle
                # This will always be the closest to the ego vehicle
                # Filter out until this is removed from prediction
                if dist_to_ego < 2:  # this allows max vel to be 20m/s
                    break
                elif dist_to_ego < DEFAULT_DISTANCE_THRESHOLD:
                    obstacle_list.append(obstacle_origin)

        if len(obstacle_list) == 0:
            return np.empty((0, 2))
        return np.array(obstacle_list)
示例#6
0
class RRTStarPlanningOperator(erdos.Operator):
    """ RRTStar Planning operator for Carla 0.9.x."""
    def __init__(self,
                 can_bus_stream,
                 prediction_stream,
                 waypoints_stream,
                 name,
                 flags,
                 goal_location,
                 log_file_name=None,
                 csv_file_name=None):
        """
        Initialize the RRT* planner. Setup logger and map attributes.

        Args:
            name: Name of the operator.
            flags: Config flags.
            goal_location: Global goal carla.Location for planner to route to.
        """
        can_bus_stream.add_callback(self.on_can_bus_update)
        prediction_stream.add_callback(self.on_prediction_update)
        erdos.add_watermark_callback([can_bus_stream, prediction_stream],
                                     [waypoints_stream], self.on_watermark)
        self._name = name
        self._logger = erdos.utils.setup_logging(name, log_file_name)
        self._csv_logger = erdos.utils.setup_csv_logging(
            name + '-csv', csv_file_name)
        self._flags = flags

        self._wp_index = 9
        self._waypoints = None
        self._carla_map = get_map(self._flags.carla_host,
                                  self._flags.carla_port,
                                  self._flags.carla_timeout)
        self._hd_map = HDMap(self._carla_map, log_file_name)
        self._goal_location = goal_location

        self._can_bus_msgs = deque()
        self._prediction_msgs = deque()

    @staticmethod
    def connect(can_bus_stream, prediction_stream):
        waypoints_stream = erdos.WriteStream()
        return [waypoints_stream]

    def on_can_bus_update(self, msg):
        self._logger.debug('@{}: received can bus message'.format(
            msg.timestamp))
        self._can_bus_msgs.append(msg)

    def on_prediction_update(self, msg):
        self._logger.debug('@{}: received prediction message'.format(
            msg.timestamp))
        self._prediction_msgs.append(msg)

    def on_watermark(self, timestamp, waypoints_stream):
        self._logger.debug('@{}: received watermark'.format(timestamp))
        # get ego info
        can_bus_msg = self._can_bus_msgs.popleft()
        vehicle_transform = can_bus_msg.data.transform

        # get obstacles
        obstacle_map = self._build_obstacle_map(vehicle_transform)

        # compute goals
        target_location = self._compute_target_location(vehicle_transform)

        # run rrt*
        path, cost = self._run_rrt_star(vehicle_transform, target_location,
                                        obstacle_map)

        # convert to waypoints if path found, else use default waypoints
        if cost is not None:
            path_transforms = []
            for point in path:
                p_loc = self._carla_map.get_waypoint(
                    carla.Location(x=point[0], y=point[1], z=0),
                    project_to_road=True,
                ).transform.location  # project to road to approximate Z
                path_transforms.append(
                    Transform(
                        location=Location(x=point[0], y=point[1], z=p_loc.z),
                        rotation=Rotation(),
                    ))
            waypoints = deque(path_transforms)
            waypoints.extend(
                itertools.islice(self._waypoints, self._wp_index,
                                 len(self._waypoints))
            )  # add the remaining global route for future
        else:
            waypoints = self._waypoints

        # construct waypoints message
        waypoints = collections.deque(
            itertools.islice(waypoints, 0,
                             DEFAULT_NUM_WAYPOINTS))  # only take 50 meters
        next_waypoint = waypoints[self._wp_index]
        wp_steer_speed_vector, wp_steer_speed_angle = \
            get_waypoint_vector_and_angle(
                next_waypoint, vehicle_transform
            )
        output_msg = WaypointsMessage(timestamp,
                                      waypoints=waypoints,
                                      wp_angle=wp_steer_speed_angle,
                                      wp_vector=wp_steer_speed_vector,
                                      wp_angle_speed=wp_steer_speed_angle)

        # send waypoints message
        waypoints_stream.send(output_msg)
        waypoints_stream.send(erdos.WatermarkMessage(timestamp))

    def _build_obstacle_map(self, vehicle_transform):
        """
        Construct an obstacle map given vehicle_transform.

        Args:
            vehicle_transform: pylot.utils.Transform of vehicle from can_bus
                stream

        Returns:
            an obstacle map that maps
                {id_time: (obstacle_origin, obstacle_range)}
            only obstacles within DEFAULT_DISTANCE_THRESHOLD in front of the
            ego vehicle are considered to save computation cost.
        """
        obstacle_map = {}
        prediction_msg = self._prediction_msgs.popleft()
        # look over all predictions
        for prediction in prediction_msg.predictions:
            time = 0
            # use all prediction times as potential obstacles
            for transform in prediction.trajectory:
                if vehicle_transform.location.is_within_distance_ahead(
                        transform.location, DEFAULT_DISTANCE_THRESHOLD):
                    # compute the obstacle origin and range of the obstacle
                    obstacle_origin = ((transform.location.x -
                                        DEFAULT_OBSTACLE_LENGTH / 2,
                                        transform.location.y -
                                        DEFAULT_OBSTACLE_WIDTH / 2),
                                       (DEFAULT_OBSTACLE_LENGTH,
                                        DEFAULT_OBSTACLE_WIDTH))
                    obs_id = str("{}_{}".format(prediction.id, time))
                    obstacle_map[obs_id] = obstacle_origin
                time += 1
        return obstacle_map

    def _compute_target_location(self, vehicle_transform):
        """
        Update the global waypoint route and compute the target location for
        RRT* search to plan for.

        Args:
            vehicle_transform: pylot.utils.Transform of vehicle from can_bus
                stream

        Returns:
            target location
        """
        ego_location = vehicle_transform.location.as_carla_location()
        self._waypoints = self._hd_map.compute_waypoints(
            ego_location, self._goal_location)
        target_waypoint = self._waypoints[self._wp_index]
        target_location = target_waypoint.location
        return target_location

    @staticmethod
    def _run_rrt_star(vehicle_transform, target_location, obstacle_map):
        """
        Run the RRT* algorithm given the vehicle_transform, target_location,
        and obstacle_map.

        Args:
            vehicle_transform: pylot.utils.Transform of vehicle from can_bus
                stream
            target_location: Location target
            obstacle_map: an obstacle map that maps
                {id_time: (obstacle_origin, obstacle_range)}

        Returns:
            np.ndarray, float
            return the path in form [[x0, y0],...] and final cost
            if solution not found, returns the path to the closest point to the
            target space and final cost is none
        """
        starting_state = (vehicle_transform.location.x,
                          vehicle_transform.location.y)
        target_space = ((target_location.x - DEFAULT_TARGET_LENGTH / 2,
                         target_location.y - DEFAULT_TARGET_WIDTH / 2),
                        (DEFAULT_TARGET_LENGTH, DEFAULT_TARGET_WIDTH))
        state_space = start_target_to_space(starting_state, target_space,
                                            DEFAULT_TARGET_LENGTH,
                                            DEFAULT_TARGET_WIDTH)
        path, cost = apply_rrt_star(state_space=state_space,
                                    starting_state=starting_state,
                                    target_space=target_space,
                                    obstacle_map=obstacle_map)
        return path, cost