Пример #1
0
def test_oriented_astar(debug=False):
    occupancy_map_data = load_occupancy_map_data('brain', 'big_retail.png')
    occupancy_map = Costmap(occupancy_map_data, 0.03, origin=[0., 0.])

    footprint_points = get_tricky_circular_footprint()
    footprint = CustomFootprint(footprint_points,
                                2. * np.pi / 180.,
                                inflation_scale=2.0)

    angles = get_astar_angles()

    footprint_masks = footprint.get_footprint_masks(occupancy_map.resolution,
                                                    angles=angles)
    outline_coords = footprint.get_outline_coords(occupancy_map.resolution,
                                                  angles=angles)

    start = rc_to_xy([1956, 137, 0], occupancy_map)
    goal = rc_to_xy([841, 3403, 0.], occupancy_map)

    start_time = time.time()
    success, path = oriented_astar(goal=goal,
                                   start=start,
                                   occupancy_map=occupancy_map,
                                   footprint_masks=footprint_masks,
                                   outline_coords=outline_coords,
                                   obstacle_values=[0, 127],
                                   planning_scale=10)
    time_elapsed = time.time() - start_time

    assert success

    if debug:
        visualization_map = occupancy_map.copy()
        visualization_map.data = np.dstack(
            (occupancy_map.data, occupancy_map.data, occupancy_map.data))
        draw_footprint_path(footprint, path, visualization_map, [255, 0, 0],
                            [0, 0, 255])

        if success:
            print("found solution in", time_elapsed, "seconds, with length:",
                  path.shape[0])
        else:
            print("failed to find solution")

        plt.imshow(visualization_map.data, interpolation='nearest')
        plt.show()
Пример #2
0
def test_multithread_astar(debug=False):
    num_instances = 10
    pool = multiprocessing.Pool()

    occupancy_map_data = load_occupancy_map_data('test', 'maze_small.png')
    occupancy_map = Costmap(occupancy_map_data, 0.03, origin=[0., 0.])

    obstacle_values = np.array([Costmap.OCCUPIED, Costmap.UNEXPLORED],
                               dtype=np.uint8)

    start = rc_to_xy(
        np.argwhere(occupancy_map.data == Costmap.FREE)[0, :], occupancy_map)
    goals = rc_to_xy(
        np.argwhere(occupancy_map.data == Costmap.FREE)[-num_instances:, :],
        occupancy_map)

    astar_fn = partial(astar,
                       start=start,
                       occupancy_map=occupancy_map,
                       obstacle_values=obstacle_values)

    start_time = time.time()
    results = pool.map(astar_fn, goals)
    time_elapsed = time.time() - start_time

    if debug:
        print("ran", num_instances, "astar instances, finished in",
              time_elapsed)

    for i, [successful, path] in enumerate(results):
        assert successful
        if debug:
            if successful:
                print("  instance", i, "\b: found solution with length:",
                      path.shape[0])
            else:
                print("  instance", i, "\b: failed to find solution")
Пример #3
0
def test_one_astar(debug=False):
    occupancy_map_data = load_occupancy_map_data('test', 'maze_small.png')
    occupancy_map = Costmap(occupancy_map_data, 0.03, origin=[0., 0.])

    obstacle_values = np.array([Costmap.OCCUPIED, Costmap.UNEXPLORED],
                               dtype=np.uint8)

    start = rc_to_xy(
        np.argwhere(occupancy_map.data == Costmap.FREE)[0, :], occupancy_map)
    goal = rc_to_xy(
        np.argwhere(occupancy_map.data == Costmap.FREE)[-1, :], occupancy_map)

    start_time = time.time()
    success, path = astar(goal=goal,
                          start=start,
                          occupancy_map=occupancy_map,
                          obstacle_values=obstacle_values,
                          allow_diagonal=False)
    time_elapsed = time.time() - start_time

    assert success

    if debug:
        map_solution = np.dstack(
            (occupancy_map.data, occupancy_map.data, occupancy_map.data))
        path_px = xy_to_rc(path, occupancy_map).astype(np.int)
        map_solution[path_px[:, 0], path_px[:, 1]] = [0, 0, 255]

        if success:
            print("found solution in", time_elapsed, "seconds, with length:",
                  path.shape[0])
        else:
            print("failed to find solution")

        plt.imshow(map_solution, interpolation='nearest')
        plt.show()
Пример #4
0
def astar(goal,
          start,
          occupancy_map,
          obstacle_values,
          planning_scale=1,
          delta=0.0,
          epsilon=1.0,
          allow_diagonal=False):
    """
    Wrapper for vanilla a-star c++ planning. given a start and end and a map, give a path.
    :param goal array(3)[float]: [x, y, theta] goal pose of the robot
    :param start array(3)[float]: [x, y, theta] start pose of the robot
    :param occupancy_map Costmap: occupancy map used for planning, data must be compatible with uint8
    :param obstacle_values array(N)[uint8]: an array containing values that the collision checker should deem as an obstacle
                             i.e [127, 0]
    :param planning_scale int: value > 1, to plan on a lower resolution than the original occupancy map resolution,
                           this value is round_int(desired resolution / original resolution)
    :param delta float: distance in pixels to extend the goal region for solution (allow to be within delta away from goal)
                  TODO FIX this to be in meters
    :param epsilon float: weighting for the heuristic in the A* algorithm
    :param allow_diagonal bool: whether to allow diagonal movements
    :return Tuple[bool, array(N, 3)[float]]: whether we were able to successfully plan to the goal node,
                                              and the most promising path to the goal node (solution if obtained)
    """
    start_px = xy_to_rc(start, occupancy_map)
    c_start = np.array(start_px[:2], dtype=np.int32)

    goal_px = xy_to_rc(goal, occupancy_map)
    c_goal = np.array(goal_px[:2], dtype=np.int32)

    c_occupancy_map = occupancy_map.data.astype(np.uint8)

    success, path_px = c_astar(c_start, c_goal, c_occupancy_map,
                               obstacle_values, delta, epsilon, planning_scale,
                               allow_diagonal)

    path = rc_to_xy(path_px, occupancy_map)
    return success, np.vstack(([start], path))
Пример #5
0
def extract_frontiers(occupancy_map,
                      approx=True,
                      approx_iters=2,
                      kernel=cv2.getStructuringElement(cv2.MORPH_CROSS,
                                                       (3, 3)),
                      debug=False):
    """
    Given a map of free/occupied/unexplored pixels, identify the frontiers.
    This method is a quicker method than the brute force approach,
    and scales pretty well with large maps. Using cv2.dilate for 1 pixel on the unexplored space
    and comparing with the free space, we can isolate the frontiers.

    :param occupancy_map Costmap: object corresponding to the map to extract frontiers from
    :param approx bool: does an approximation of the map before extracting frontiers. (dilate erode, get rid of single
                   pixel unexplored areas creating a large number fo frontiers)
    :param approx_iters int: number of iterations for the dilate erode
    :param kernel array(N, N)[float]: the kernel of which to use to extract frontiers / approx map
    :param debug bool: show debug windows?
    :return List[array(N, 2][float]: list of frontiers, each frontier is a set of coordinates
    """
    # todo regional frontiers
    # extract coordinates of occupied, unexplored, and free coordinates
    occupied_coords = np.argwhere(
        occupancy_map.data.astype(np.uint8) == Costmap.OCCUPIED)
    unexplored_coords = np.argwhere(
        occupancy_map.data.astype(np.uint8) == Costmap.UNEXPLORED)
    free_coords = np.argwhere(
        occupancy_map.data.astype(np.uint8) == Costmap.FREE)

    if free_coords.shape[0] == 0 or unexplored_coords.shape[0] == 0:
        return []

    # create a binary mask of unexplored pixels, letting unexplored pixels = 1
    unexplored_mask = np.zeros_like(occupancy_map.data)
    unexplored_mask[unexplored_coords[:, 0], unexplored_coords[:, 1]] = 1

    # dilate using a 3x3 kernel, effectively increasing
    # the size of the unexplored space by one pixel in all directions
    dilated_unexplored_mask = cv2.dilate(unexplored_mask, kernel=kernel)
    dilated_unexplored_mask[occupied_coords[:, 0], occupied_coords[:, 1]] = 1

    # create a binary mask of the free pixels
    free_mask = np.zeros_like(occupancy_map.data)
    free_mask[free_coords[:, 0], free_coords[:, 1]] = 1

    # can isolate the frontiers using the difference between the masks,
    # and looking for contours
    frontier_mask = ((1 - dilated_unexplored_mask) - free_mask)
    if approx:
        frontier_mask = cv2.dilate(frontier_mask,
                                   kernel=kernel,
                                   iterations=approx_iters)
        frontier_mask = cv2.erode(frontier_mask,
                                  kernel=kernel,
                                  iterations=approx_iters)

    # this indexing will work with opencv 2.x 3.x and 4.x
    frontiers_xy_px = cv2.findContours(frontier_mask, cv2.RETR_LIST,
                                       cv2.CHAIN_APPROX_NONE)[-2:][0]
    frontiers = [
        rc_to_xy(np.array(frontier).squeeze(1)[:, ::-1], occupancy_map)
        for frontier in frontiers_xy_px
    ]

    if debug:
        frontier_map = np.repeat([occupancy_map.data], repeats=3,
                                 axis=0).transpose((1, 2, 0))
        # frontiers = [frontiers[rank] for i, rank in enumerate(frontier_ranks)
        #              if i < self.num_frontiers_considered]
        for frontier in frontiers:
            # if frontier.astype(np.int).tolist() in self.frontier_blacklist:
            #     continue
            frontier_px = xy_to_rc(frontier, occupancy_map).astype(np.int)
            frontier_px = frontier_px[which_coords_in_bounds(
                frontier_px, occupancy_map.get_shape())]
            frontier_map[frontier_px[:, 0], frontier_px[:, 1]] = [255, 0, 0]

        plt.imshow(frontier_map, cmap='gray', interpolation='nearest')
        plt.show()

    return frontiers
Пример #6
0
    def check_for_collision(self,
                            state,
                            occupancy_map,
                            unexplored_is_occupied=False,
                            use_python=False,
                            debug=False):
        """
        using the state and the map, check for a collision on the map.
        :param state array(3)[float]: state of the robot [x, y, theta]
        :param occupancy_map Costmap: object to check the footprint against
        :param unexplored_is_occupied bool: whether to treat unexplored on the map as occupied
        :param use_python bool: whether to use python collision checking or c++
        :param debug bool: show debug plots?
        :return bool: True for collision
        """
        # check if we have computed a mask for this resolution, if not compute it
        if occupancy_map.resolution not in list(
                self._rotated_masks_set.keys()):
            self._add_new_masks(occupancy_map.resolution)

        # convert state to pixel coordinates
        state_px = xy_to_rc(pose=state, occupancy_map=occupancy_map)
        position = np.array(state_px[:2]).astype(np.int)

        if debug:
            map_vis = occupancy_map.copy()
            map_vis.data = np.repeat([occupancy_map.data], repeats=3,
                                     axis=0).transpose((1, 2, 0)).copy()
            self.draw(rc_to_xy(state_px, occupancy_map), map_vis,
                      [255, 10, 10])
            occupied_inds = np.argwhere(occupancy_map.data == Costmap.OCCUPIED)
            map_vis.data[occupied_inds[:, 0], occupied_inds[:, 1]] = [0, 0, 0]
            plt.imshow(map_vis.data, interpolation='nearest')
            plt.show()

        # get the mask radius that was saved for this resolution
        mask_radius = self._mask_radius_set[occupancy_map.resolution]

        # compute the closest angle to the current angle in the state, and get that rotated footprint
        closest_angle_ind = np.argmin(np.abs(state_px[2] - self._mask_angles))
        footprint_mask = self._rotated_masks_set[
            occupancy_map.resolution][closest_angle_ind]
        outline_coords = self._rotated_outline_coords_set[
            occupancy_map.resolution][closest_angle_ind]

        if not use_python:
            obstacle_values = [
                Costmap.OCCUPIED, Costmap.UNEXPLORED
            ] if unexplored_is_occupied else [Costmap.OCCUPIED]

            is_colliding = check_for_collision(state,
                                               occupancy_map,
                                               footprint_mask=footprint_mask,
                                               outline_coords=outline_coords,
                                               obstacle_values=obstacle_values)
        else:
            # part of robot off the edge of map, it is a collision, because we assume map is bounded
            if not np.all(
                    which_coords_in_bounds(
                        coords=(outline_coords + state_px[:2]).astype(np.int),
                        map_shape=occupancy_map.get_shape())):
                return True

            # get a small subsection around the state in the map (as big as our mask),
            # and do a masking check with the footprint to see if there is a collision.
            # if part of our subsection is off the map, we need to clip the size
            # to reflect this, in both the subsection and the mask
            min_range = [position[0] - mask_radius, position[1] - mask_radius]
            max_range = [
                position[0] + mask_radius + 1, position[1] + mask_radius + 1
            ]
            clipped_min_range, clipped_max_range = clip_range(
                min_range, max_range, occupancy_map.data.shape)

            min_range_delta = clipped_min_range - np.array(min_range)
            max_range_delta = clipped_max_range - np.array(max_range)

            ego_map = occupancy_map.data[
                clipped_min_range[0]:clipped_max_range[0],
                clipped_min_range[1]:clipped_max_range[1]].copy()

            # todo might be - max_range_delta
            footprint_mask = footprint_mask[
                min_range_delta[0]:footprint_mask.shape[1] +
                max_range_delta[0],
                min_range_delta[1]:footprint_mask.shape[1] +
                max_range_delta[1]]

            # treat unexplored space as obstacle
            if unexplored_is_occupied:
                ego_map[ego_map == Costmap.UNEXPLORED] = Costmap.OCCUPIED

            is_colliding = np.any(footprint_mask == ego_map)

            if debug:
                plt.imshow(footprint_mask - 0.1 * ego_map)
                plt.show()

        return is_colliding
Пример #7
0
def oriented_astar(goal,
                   start,
                   occupancy_map,
                   footprint_masks,
                   outline_coords,
                   obstacle_values,
                   planning_scale=1,
                   delta=0.0,
                   epsilon=1.0,
                   allow_diagonal=True):
    """
        Oriented Astar C++ wrapper for python. Formats input data in required format for c++ function, the calls it,
    returning the path if found.
    :param goal array(3)[float]: [x, y, theta] goal pose of the robot
    :param start array(3)[float]: [x, y, theta] start pose of the robot
    :param occupancy_map Costmap: occupancy map used for planning, data must be compatible with uint8
    :param footprint_masks array(N,M,M)[int]: masks of the footprint rotated at the corresponding angles
                            needed for checking, i.e state[2]. N is 2 * mask_radius + 1,
                            the values are -1 for not footprint, 0 for footprint.
                            N is the dimension across angles, (M, M) is the mask shape
    :param outline_coords array(N, 2)[int]: the coordinates that define the outline of the footprint. N is the number
                           of points that define the outline of the footprint
    :param obstacle_values array(N)[uint8]: an array containing values that the collision checker should deem as an obstacle
                             i.e [127, 0]
    :param planning_scale int: value > 1, to plan on a lower resolution than the original occupancy map resolution,
                           this value is round_int(desired resolution / original resolution)
    :param delta float: distance in pixels to extend the goal region for solution (allow to be within delta away from goal)
                  TODO FIX this to be in meters
    :param epsilon float: weighting for the heuristic in the A* algorithm
    :param allow_diagonal bool: whether to allow diagonal movements
    :return Tuple[bool, array(N, 3)[float]]: whether we were able to successfully plan to the goal,
                                              and the most promising path to the goal node (solution if obtained)
    """
    c_angles = np.array(c_get_astar_angles(), dtype=np.float32)

    start_px = xy_to_rc(start, occupancy_map)
    c_start = np.array(start_px[:2], dtype=np.int32)

    goal_px = xy_to_rc(goal, occupancy_map)
    c_goal = np.array(goal_px[:2], dtype=np.int32)

    c_occupancy_map = occupancy_map.data.astype(np.uint8)

    c_footprint_masks = np.logical_not(np.array(footprint_masks,
                                                dtype=np.bool))
    c_footprint_masks = [
        c_footprint_mask for c_footprint_mask in c_footprint_masks
    ]

    c_outline_coords = np.array(outline_coords, dtype=np.int32)
    c_outline_coords = [
        c_outline_coord for c_outline_coord in c_outline_coords
    ]

    c_obstacle_values = np.array(obstacle_values, dtype=np.uint8)

    success, path_px = c_oriented_astar(c_start, c_goal, c_occupancy_map,
                                        c_footprint_masks, c_angles,
                                        c_outline_coords, c_obstacle_values,
                                        delta, epsilon, planning_scale,
                                        allow_diagonal)

    path = rc_to_xy(path_px, occupancy_map)
    return success, np.vstack(([start], path))
Пример #8
0
def test_custom_footprint(debug=False):
    map_data = load_occupancy_map_data('test', 'three_rooms.png')
    occupancy_map = Costmap(data=map_data, resolution=0.03, origin=[0, 0])

    free_state = rc_to_xy(np.array([110, 220, 0]), occupancy_map)
    colliding_state = rc_to_xy(np.array([90, 215, 0]), occupancy_map)

    footprint_points = get_tricky_oval_footprint()
    footprint = CustomFootprint(footprint_points, 2 * np.pi / 180.)
    assert not footprint.check_for_collision(
        free_state, occupancy_map, debug=debug)
    assert footprint.check_for_collision(colliding_state,
                                         occupancy_map,
                                         debug=debug)
    assert not footprint.check_for_collision(
        free_state, occupancy_map, debug=debug, use_python=True)
    assert footprint.check_for_collision(colliding_state,
                                         occupancy_map,
                                         debug=debug,
                                         use_python=True)

    rotated_colliding_state = rc_to_xy(np.array([110, 220, -np.pi / 2]),
                                       occupancy_map)
    assert footprint.check_for_collision(rotated_colliding_state,
                                         occupancy_map,
                                         debug=debug)
    assert footprint.check_for_collision(rotated_colliding_state,
                                         occupancy_map,
                                         debug=debug,
                                         use_python=True)

    footprint_points = get_tricky_circular_footprint()
    footprint = CustomFootprint(footprint_points, 2 * np.pi / 180.)
    assert not footprint.check_for_collision(
        free_state, occupancy_map, debug=debug)
    assert footprint.check_for_collision(colliding_state,
                                         occupancy_map,
                                         debug=debug)
    assert not footprint.check_for_collision(
        free_state, occupancy_map, debug=debug, use_python=True)
    assert footprint.check_for_collision(colliding_state,
                                         occupancy_map,
                                         debug=debug,
                                         use_python=True)

    rotated_colliding_state = rc_to_xy(np.array([115, 155, np.pi / 4]),
                                       occupancy_map)
    assert footprint.check_for_collision(rotated_colliding_state,
                                         occupancy_map,
                                         debug=debug)
    assert footprint.check_for_collision(rotated_colliding_state,
                                         occupancy_map,
                                         debug=debug,
                                         use_python=True)

    rotated_colliding_state = rc_to_xy(np.array([0, 0, np.pi / 2]),
                                       occupancy_map)
    assert footprint.check_for_collision(rotated_colliding_state,
                                         occupancy_map,
                                         debug=debug)
    assert footprint.check_for_collision(rotated_colliding_state,
                                         occupancy_map,
                                         debug=debug,
                                         use_python=True)