Example #1
0
    def measure(self, state, debug=False):
        """
        given the current robot pose, return the lidar data
        :param state array(3)[float]: [x, y, theta (radians)] pose of the robot
        :param debug bool: show debug plot?
        :return Tuple[array(N)[float], array(N)[float]]: [lidar angles radians, lidar ranges meters] if ray did not hit obstacle, value is np.nan
        """
        assert self._map is not None \
            and "Please set the map using set_map() before calling measure, this will initialize lidar as well."

        pose = state.copy()
        pose_px = xy_to_rc(pose, self._map)

        ranges = np.zeros_like(self._ray_angles)
        for i, ego_ray in enumerate(self._ego_rays):
            rotation_matrix = get_rotation_matrix_2d(-state[2])
            rotated_ray = np.rint(ego_ray.dot(rotation_matrix))
            ray = (pose_px[:2] + rotated_ray).astype(np.int)
            ray = ray[which_coords_in_bounds(ray, self._map.get_shape())]
            occupied_ind = np.argwhere(
                self._map.data[ray[:, 0], ray[:, 1]] == Costmap.OCCUPIED)
            if occupied_ind.shape[0]:
                ranges[i] = np.linalg.norm(pose_px[:2] - ray[int(
                    occupied_ind[0])]) * self._map.resolution
            else:
                ranges[i] = np.nan

        if debug:
            points = scan_to_points(self._ray_angles + state[2],
                                    ranges) + pose_px[:2]
            plt.plot(points[:, 0], points[:, 1])
            plt.show()

        return self._ray_angles, ranges
Example #2
0
def draw_scan_ranges(visualization_map, state, scan_angles, scan_ranges, color):
    """
    Draw the occupied points of a lidar scan onto the map
    :param visualization_map Costmap: costmap to draw on
    :param state array(3)[float]: pose of the robot [x, y, theta]
    :param scan_angles array(N)[float]: angles of the lidar scan
    :param scan_ranges array(N)[float]: ranges of the lidar scan
    :param color Union[int, array(3)[uint8]]: depending on the shape of visualization_map.data,
              a valid value for the color to draw the path
    """
    occupied_coords = scan_to_points(scan_angles + state[2], scan_ranges) + state[:2]
    occupied_coords = xy_to_rc(occupied_coords, visualization_map).astype(np.int)
    occupied_coords = occupied_coords[which_coords_in_bounds(occupied_coords, visualization_map.get_shape())]
    visualization_map.data[occupied_coords[:, 0], occupied_coords[:, 1]] = color
def run_frontier_exploration(map_filename,
                             params_filename,
                             start_state,
                             sensor_range,
                             map_resolution,
                             completion_percentage,
                             render=True,
                             render_mode='exp',
                             render_interval=1,
                             render_size_scale=1.7,
                             completion_check_interval=1,
                             render_wait_for_key=True,
                             max_exploration_iterations=None):
    """
    Interface for running frontier exploration on the grid world environment that is initialized via map_filename.. etc
    :param map_filename str: path of the map to load into the grid world environment, needs to be a uint8 png with
                         values 127 for unexplored, 255 for free, 0 for occupied.
    :param params_filename str: path of the params file for setting up the frontier agent etc.
                            See exploration/params/ for examples
    :param start_state array(3)[float]: starting state of the robot in the map (in meters) [x, y, theta], if None the starting
                        state is random
    :param sensor_range float: range of the sensor (lidar) in meters
    :param map_resolution float: resolution of the map desired
    :param completion_percentage float: decimal of the completion percentage desired i.e (.97), how much of the ground
                                  truth environment to explore, note that 1.0 is not always reachable because of
                                  footprint.
    :param render bool: whether or not to visualize
    :param render_mode str: specify rendering function (bc_exploration or bc_gym_planning_env)
    :param render_interval int: visualize every render_interval iterations
    :param render_size_scale Tuple(int): (h, w), the size of the render window in pixels
    :param completion_check_interval int: check for exploration completion every completion_check_interval iterations
    :param render_wait_for_key bool: if render is enabled, if render_wait_for_key is True then the exploration algorithm
                                will wait for key press to start exploration. Timing is not affected.
    :param max_exploration_iterations int: number of exploration cycles to run before exiting
    :return Costmap: occupancy_map, final map from exploration), percentage explored, time taken to explore
    """

    # some parameters
    frontier_agent = create_frontier_agent_from_params(params_filename)
    footprint = frontier_agent.get_footprint()

    # pick a sensor
    sensor = Lidar(sensor_range=sensor_range,
                   angular_range=250 * np.pi / 180,
                   angular_resolution=1.0 * np.pi / 180,
                   map_resolution=map_resolution)

    # setup grid world environment
    env = GridWorld(map_filename=map_filename,
                    map_resolution=map_resolution,
                    sensor=sensor,
                    footprint=footprint,
                    start_state=start_state)

    # setup corresponding gym environment
    env_instance = RandomAisleTurnEnv()

    render_size = (np.array(env.get_map_shape()[::-1]) *
                   render_size_scale).astype(np.int)

    # setup log-odds mapper, we assume the measurements are very accurate,
    # thus one scan should be enough to fill the map
    padding = 1.
    map_shape = np.array(env.get_map_shape()) + int(
        2. * padding // map_resolution)
    initial_map = Costmap(
        data=Costmap.UNEXPLORED * np.ones(map_shape, dtype=np.uint8),
        resolution=env.get_map_resolution(),
        origin=[-padding - env.start_state[0], -padding - env.start_state[1]])

    clearing_footprint_points = footprint.get_clearing_points(map_resolution)
    clearing_footprint_coords = xy_to_rc(clearing_footprint_points,
                                         initial_map).astype(np.int)
    initial_map.data[clearing_footprint_coords[:, 0],
                     clearing_footprint_coords[:, 1]] = Costmap.FREE

    mapper = LogOddsMapper(initial_map=initial_map,
                           sensor_range=sensor.get_sensor_range(),
                           measurement_certainty=0.8,
                           max_log_odd=8,
                           min_log_odd=-8,
                           threshold_occupied=.5,
                           threshold_free=.5)

    # reset the environment to the start state, map the first observations
    pose, [scan_angles, scan_ranges] = env.reset()

    occupancy_map = mapper.update(state=pose,
                                  scan_angles=scan_angles,
                                  scan_ranges=scan_ranges)

    if render:
        if render_mode == 'exp':
            visualize(occupancy_map=occupancy_map,
                      state=pose,
                      footprint=footprint,
                      start_state=start_state,
                      scan_angles=scan_angles,
                      scan_ranges=scan_ranges,
                      path=[],
                      render_size=render_size,
                      frontiers=[],
                      wait_key=0 if render_wait_for_key else 1)
        elif render_mode == 'gym':
            visualize_in_gym(gym_env=env_instance,
                             exp_map=occupancy_map,
                             path=[],
                             pose=pose)
        else:
            raise NameError(
                "Invalid rendering method.\nPlease choose one of \"exp\" or \"gym\" methods."
            )

    iteration = 0
    is_last_plan = False
    was_successful = True
    percentage_explored = 0.0
    while True:
        if iteration % completion_check_interval == 0:
            percentage_explored = env.compare_maps(occupancy_map)
            if percentage_explored >= completion_percentage:
                is_last_plan = True

        if max_exploration_iterations is not None and iteration > max_exploration_iterations:
            was_successful = False
            is_last_plan = True

        # using the current map, make an action plan
        path = frontier_agent.plan(state=pose,
                                   occupancy_map=occupancy_map,
                                   is_last_plan=is_last_plan)

        # if we get empty lists for policy/path, that means that the agent was
        # not able to find a path/frontier to plan to.
        if not path.shape[0]:
            print(
                "No more frontiers! Either the map is 100% explored, or bad start state, or there is a bug!"
            )
            break

        # if we have a policy, follow it until the end. update the map sparsely (speeds it up)
        for j, desired_state in enumerate(path):
            if footprint.check_for_collision(desired_state,
                                             occupancy_map,
                                             unexplored_is_occupied=True):
                footprint_coords = footprint.get_ego_points(
                    desired_state[2], map_resolution) + desired_state[:2]
                footprint_coords = xy_to_rc(footprint_coords,
                                            occupancy_map).astype(np.int)
                footprint_coords = footprint_coords[which_coords_in_bounds(
                    footprint_coords, occupancy_map.get_shape())]
                occupancy_map.data[footprint_coords[:, 0],
                                   footprint_coords[:, 1]] = Costmap.FREE

            pose, [scan_angles, scan_ranges] = env.step(desired_state)
            occupancy_map = mapper.update(state=pose,
                                          scan_angles=scan_angles,
                                          scan_ranges=scan_ranges)

            # put the current laserscan on the map before planning
            occupied_coords = scan_to_points(scan_angles + pose[2],
                                             scan_ranges) + pose[:2]
            occupied_coords = xy_to_rc(occupied_coords,
                                       occupancy_map).astype(np.int)
            occupied_coords = occupied_coords[which_coords_in_bounds(
                occupied_coords, occupancy_map.get_shape())]
            occupancy_map.data[occupied_coords[:, 0],
                               occupied_coords[:, 1]] = Costmap.OCCUPIED

            # shows a live visualization of the exploration process if render is set to true
            if render and j % render_interval == 0:
                if render_mode == 'exp':
                    visualize(occupancy_map=occupancy_map,
                              state=pose,
                              footprint=footprint,
                              start_state=start_state,
                              scan_angles=scan_angles,
                              scan_ranges=scan_ranges,
                              path=path,
                              render_size=render_size,
                              frontiers=frontier_agent.get_frontiers(
                                  compute=True, occupancy_map=occupancy_map),
                              wait_key=1,
                              path_idx=j)
                elif render_mode == 'gym':
                    visualize_in_gym(gym_env=env_instance,
                                     exp_map=occupancy_map,
                                     path=path,
                                     pose=pose)
                else:
                    raise NameError(
                        "Invalid rendering method.\nPlease choose one of \"exp\" or \"gym\" methods."
                    )

        if is_last_plan:
            break

        iteration += 1

    if render:
        cv2.waitKey(0)

    return occupancy_map, percentage_explored, iteration, was_successful
    def _scan_to_occupied_free_coords(self,
                                      state,
                                      angles,
                                      ranges,
                                      debug=False):
        """
        converts laser scan to occupied and free coordinates for mapping
        :param state array(3)[float]: current state of the robot
        :param angles array(N)[float]: lidar angles of the robot
        :param ranges array(N)[float]: lidar ranges corresponding to the angles
        :param debug bool: show debug?
        :return Tuple[array(N,2)[int], array(N,2)[int]]: occupied coordinates, and free coordinates (in row column)
        """

        new_angles = wrap_angles(angles.copy() + state[2])
        state_px = xy_to_rc(state, self._map)
        position_px = state_px[:2].astype(np.int)

        with np.errstate(invalid='ignore'):
            free_inds = np.logical_or(np.isnan(ranges),
                                      ranges >= self.sensor_range)
            occ_inds = np.logical_not(free_inds)

        ranges = ranges.copy()
        ranges[free_inds] = self.sensor_range

        occupied_coords = scan_to_points(new_angles[occ_inds],
                                         ranges[occ_inds]) + state[:2]
        occupied_coords = xy_to_rc(occupied_coords, self._map).astype(np.int)

        free_endcoords = scan_to_points(new_angles[free_inds],
                                        ranges[free_inds]) + state[:2]
        free_endcoords = xy_to_rc(free_endcoords, self._map).astype(np.int)

        if debug:
            occupied_coords_vis = occupied_coords[which_coords_in_bounds(
                occupied_coords, self._map.get_shape())]
            free_coords_vis = free_endcoords[which_coords_in_bounds(
                free_endcoords, self._map.get_shape())]
            map_vis = np.repeat([self._map.data], repeats=3, axis=0).transpose(
                (1, 2, 0)).copy()
            map_vis[occupied_coords_vis[:, 0],
                    occupied_coords_vis[:, 1]] = [255, 0, 0]
            map_vis[free_coords_vis[:, 0], free_coords_vis[:, 1]] = [0, 255, 0]
            plt.imshow(map_vis)
            plt.show()

        free_coords = np.array([position_px])
        for i in range(occupied_coords.shape[0]):
            bresenham_line = bresenham2d_with_intensities(
                position_px, occupied_coords[i, :], self._map.data.T)[:-1]
            free_coords = np.vstack((free_coords, bresenham_line[:, :2]))

        for i in range(free_endcoords.shape[0]):
            bresenham_line = bresenham2d_with_intensities(
                position_px, free_endcoords[i, :], self._map.data.T)
            free_coords = np.vstack((free_coords, bresenham_line[:, :2]))

        free_coords = free_coords.astype(np.int)

        occupied_coords = occupied_coords[which_coords_in_bounds(
            occupied_coords, self._map.get_shape())]
        free_coords = free_coords[which_coords_in_bounds(
            free_coords, self._map.get_shape())]

        if debug:
            map_vis = np.repeat([self._map.copy()], repeats=3,
                                axis=0).transpose((1, 2, 0))

            map_vis[occupied_coords[:, 0], occupied_coords[:,
                                                           1]] = [10, 10, 127]
            map_vis[free_coords[:, 0], free_coords[:, 1]] = [127, 10, 127]
            map_vis[int(state[0]), int(state[1])] = [127, 122, 10]

            plt.imshow(map_vis, interpolation='nearest')
            plt.show()

        return occupied_coords.astype(np.int), free_coords.astype(np.int)