Skip to content
Snippets Groups Projects
utils.py 4.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • import numpy as np
    from scipy.interpolate import interp1d
    
    
    def compute_path_from_wp(start_xp, start_yp, step=0.1):
        """
        params:
            start_xp (array-like): 1D array of x-positions
            start_yp (array-like): 1D array of y-positions
    
            step (float): interpolation step size 
    
    
        output:
            ndarray of shape (3,N) representing the  path as x,y,heading
        """
        final_xp = []
        final_yp = []
        delta = step  # [m]
        for idx in range(len(start_xp) - 1):
    
            # find the distance between consecutive waypoints
    
            section_len = np.sum(
                np.sqrt(
                    np.power(np.diff(start_xp[idx : idx + 2]), 2)
                    + np.power(np.diff(start_yp[idx : idx + 2]), 2)
                )
            )
    
    
            # how many interpolated points are needed to reach the next waypoint
            interp_range = np.linspace(0, 1, np.floor(section_len / delta).astype(int))  
    
            # interpolate between waypoints
    
            fx = interp1d(np.linspace(0, 1, 2), start_xp[idx : idx + 2], kind=1)
            fy = interp1d(np.linspace(0, 1, 2), start_yp[idx : idx + 2], kind=1)
    
    
            # append the interpolated points to the final path 
    
            final_xp = np.append(final_xp, fx(interp_range)[1:])
            final_yp = np.append(final_yp, fy(interp_range)[1:])
        dx = np.append(0, np.diff(final_xp))
        dy = np.append(0, np.diff(final_yp))
        theta = np.arctan2(dy, dx)
        return np.vstack((final_xp, final_yp, theta))
    
    
    def get_nn_idx(state, path, visited=[]):
    
        """
        Helper function to find the index of the nearest path point to the current state.
    
    
        The "nearest" point is defined as the point with the smallest Euclidean distance 
        to the current state that has not already been visited.
    
    
        Args:
            state (array-like): Current state [x, y, theta]
    
            path (ndarray): Path points [[x1, y1], [x2, y2], ...]
            visited (array-like): Visited path points [[x1, y1], [x2, y2], ...]
    
    
        Returns:
            int: Index of the nearest path point
        """
    
        # Calculate the Euclidean distance between the current state and all path points
        distances = np.linalg.norm(path[:2] - state[:2].reshape(2, 1), axis=0)
        
        # Set the distance to infinity for visited points
        for point in visited:
            point = np.array(point)
            # print(f"point = {point}")
            # print(f"path[:2] = {path[:2]}")
            # Set the distance to infinity for visited points
            distances = np.where(np.linalg.norm(path[:2] - point.reshape(2, 1), axis=0) < 1e-3, np.inf, distances)
        
    
        return np.argmin(distances)
    
    
    def fix_angle_reference(angle_ref, angle_init):
        """
        Removes jumps greater than 2PI to smooth the heading.
    
        Args:
            angle_ref (array-like): Reference angles
            angle_init (float): Initial angle
    
        Returns:
            array-like: Smoothed reference angles
        """
        diff_angle = angle_ref - angle_init
        diff_angle = np.unwrap(diff_angle)
        return angle_init + diff_angle
    
    
    def get_ref_trajectory(state, path, target_v, T, DT, path_visited_points=[]):
    
        """
        Generates a reference trajectory for the Roomba.
    
        Args:
            state (array-like): Current state [x, y, theta]
            path (ndarray): Path points [x, y, theta] in the global frame
    
            path_visited_points (array-like): Visited path points [[x, y], [x, y], ...]
    
            target_v (float): Desired speed
            T (float): Control horizon duration
            DT (float): Control horizon time-step
    
        Returns:
            ndarray: Reference trajectory [x_k, y_k, theta_k] in the ego frame
        """
        K = int(T / DT)
    
        xref = np.zeros((3, K))  # Reference trajectory for [x, y, theta]
    
    
        # find the nearest path point to the current state
    
        ind = get_nn_idx(state, path, path_visited_points)
    
        path_visited_points.append([path[0, ind], path[1, ind]])
    
        # calculate the cumulative distance along the path
        cdist = np.append([0.0], np.cumsum(np.hypot(np.diff(path[0, :]), np.diff(path[1, :]))))
    
        cdist = np.clip(cdist, cdist[0], cdist[-1])
    
    
        # determine where we want the robot to be at each time step 
    
        start_dist = cdist[ind]
        interp_points = [d * DT * target_v + start_dist for d in range(1, K + 1)]
    
        # interpolate between these points to get the reference trajectory
    
        xref[0, :] = np.interp(interp_points, cdist, path[0, :])
        xref[1, :] = np.interp(interp_points, cdist, path[1, :])
        xref[2, :] = np.interp(interp_points, cdist, path[2, :])
        
        # Transform to ego frame
        dx = xref[0, :] - state[0]
        dy = xref[1, :] - state[1]
        xref[0, :] = dx * np.cos(-state[2]) - dy * np.sin(-state[2])  # X
        xref[1, :] = dy * np.cos(-state[2]) + dx * np.sin(-state[2])  # Y
        xref[2, :] = path[2, ind] - state[2]  # Theta
    
    
        xref[2, :] = (xref[2, :] + np.pi) % (2.0 * np.pi) - np.pi
        xref[2, :] = fix_angle_reference(xref[2, :], xref[2, 0])