| | from typing import Union |
| | import numbers |
| | import numpy as np |
| | import scipy.interpolate as si |
| | import scipy.spatial.transform as st |
| |
|
| | def rotation_distance(a: st.Rotation, b: st.Rotation) -> float: |
| | return (b * a.inv()).magnitude() |
| |
|
| | def pose_distance(start_pose, end_pose): |
| | start_pose = np.array(start_pose) |
| | end_pose = np.array(end_pose) |
| | start_pos = start_pose[:3] |
| | end_pos = end_pose[:3] |
| | start_rot = st.Rotation.from_rotvec(start_pose[3:]) |
| | end_rot = st.Rotation.from_rotvec(end_pose[3:]) |
| | pos_dist = np.linalg.norm(end_pos - start_pos) |
| | rot_dist = rotation_distance(start_rot, end_rot) |
| | return pos_dist, rot_dist |
| |
|
| | class PoseTrajectoryInterpolator: |
| | def __init__(self, times: np.ndarray, poses: np.ndarray): |
| | assert len(times) >= 1 |
| | assert len(poses) == len(times) |
| | if not isinstance(times, np.ndarray): |
| | times = np.array(times) |
| | if not isinstance(poses, np.ndarray): |
| | poses = np.array(poses) |
| |
|
| | if len(times) == 1: |
| | |
| | self.single_step = True |
| | self._times = times |
| | self._poses = poses |
| | else: |
| | self.single_step = False |
| | assert np.all(times[1:] >= times[:-1]) |
| |
|
| | pos = poses[:,:3] |
| | rot = st.Rotation.from_rotvec(poses[:,3:]) |
| |
|
| | self.pos_interp = si.interp1d(times, pos, |
| | axis=0, assume_sorted=True) |
| | self.rot_interp = st.Slerp(times, rot) |
| | |
| | @property |
| | def times(self) -> np.ndarray: |
| | if self.single_step: |
| | return self._times |
| | else: |
| | return self.pos_interp.x |
| | |
| | @property |
| | def poses(self) -> np.ndarray: |
| | if self.single_step: |
| | return self._poses |
| | else: |
| | n = len(self.times) |
| | poses = np.zeros((n, 6)) |
| | poses[:,:3] = self.pos_interp.y |
| | poses[:,3:] = self.rot_interp(self.times).as_rotvec() |
| | return poses |
| |
|
| | def trim(self, |
| | start_t: float, end_t: float |
| | ) -> "PoseTrajectoryInterpolator": |
| | assert start_t <= end_t |
| | times = self.times |
| | should_keep = (start_t < times) & (times < end_t) |
| | keep_times = times[should_keep] |
| | all_times = np.concatenate([[start_t], keep_times, [end_t]]) |
| | |
| | all_times = np.unique(all_times) |
| | |
| | all_poses = self(all_times) |
| | return PoseTrajectoryInterpolator(times=all_times, poses=all_poses) |
| | |
| | def drive_to_waypoint(self, |
| | pose, time, curr_time, |
| | max_pos_speed=np.inf, |
| | max_rot_speed=np.inf |
| | ) -> "PoseTrajectoryInterpolator": |
| | assert(max_pos_speed > 0) |
| | assert(max_rot_speed > 0) |
| | time = max(time, curr_time) |
| | |
| | curr_pose = self(curr_time) |
| | pos_dist, rot_dist = pose_distance(curr_pose, pose) |
| | pos_min_duration = pos_dist / max_pos_speed |
| | rot_min_duration = rot_dist / max_rot_speed |
| | duration = time - curr_time |
| | duration = max(duration, max(pos_min_duration, rot_min_duration)) |
| | assert duration >= 0 |
| | last_waypoint_time = curr_time + duration |
| |
|
| | |
| | trimmed_interp = self.trim(curr_time, curr_time) |
| | times = np.append(trimmed_interp.times, [last_waypoint_time], axis=0) |
| | poses = np.append(trimmed_interp.poses, [pose], axis=0) |
| |
|
| | |
| | final_interp = PoseTrajectoryInterpolator(times, poses) |
| | return final_interp |
| |
|
| | def schedule_waypoint(self, |
| | pose, time, |
| | max_pos_speed=np.inf, |
| | max_rot_speed=np.inf, |
| | curr_time=None, |
| | last_waypoint_time=None |
| | ) -> "PoseTrajectoryInterpolator": |
| | assert(max_pos_speed > 0) |
| | assert(max_rot_speed > 0) |
| | if last_waypoint_time is not None: |
| | assert curr_time is not None |
| |
|
| | |
| | start_time = self.times[0] |
| | end_time = self.times[-1] |
| | assert start_time <= end_time |
| |
|
| | if curr_time is not None: |
| | if time <= curr_time: |
| | |
| | |
| | return self |
| | |
| | start_time = max(curr_time, start_time) |
| |
|
| | if last_waypoint_time is not None: |
| | |
| | |
| | if time <= last_waypoint_time: |
| | end_time = curr_time |
| | else: |
| | end_time = max(last_waypoint_time, curr_time) |
| | else: |
| | end_time = curr_time |
| |
|
| | end_time = min(end_time, time) |
| | start_time = min(start_time, end_time) |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | assert start_time <= end_time |
| | assert end_time <= time |
| | if last_waypoint_time is not None: |
| | if time <= last_waypoint_time: |
| | assert end_time == curr_time |
| | else: |
| | assert end_time == max(last_waypoint_time, curr_time) |
| |
|
| | if curr_time is not None: |
| | assert curr_time <= start_time |
| | assert curr_time <= time |
| |
|
| | trimmed_interp = self.trim(start_time, end_time) |
| | |
| | |
| |
|
| | |
| | duration = time - end_time |
| | end_pose = trimmed_interp(end_time) |
| | pos_dist, rot_dist = pose_distance(pose, end_pose) |
| | pos_min_duration = pos_dist / max_pos_speed |
| | rot_min_duration = rot_dist / max_rot_speed |
| | duration = max(duration, max(pos_min_duration, rot_min_duration)) |
| | assert duration >= 0 |
| | last_waypoint_time = end_time + duration |
| |
|
| | |
| | times = np.append(trimmed_interp.times, [last_waypoint_time], axis=0) |
| | poses = np.append(trimmed_interp.poses, [pose], axis=0) |
| |
|
| | |
| | final_interp = PoseTrajectoryInterpolator(times, poses) |
| | return final_interp |
| |
|
| |
|
| | def __call__(self, t: Union[numbers.Number, np.ndarray]) -> np.ndarray: |
| | is_single = False |
| | if isinstance(t, numbers.Number): |
| | is_single = True |
| | t = np.array([t]) |
| | |
| | pose = np.zeros((len(t), 6)) |
| | if self.single_step: |
| | pose[:] = self._poses[0] |
| | else: |
| | start_time = self.times[0] |
| | end_time = self.times[-1] |
| | t = np.clip(t, start_time, end_time) |
| |
|
| | pose = np.zeros((len(t), 6)) |
| | pose[:,:3] = self.pos_interp(t) |
| | pose[:,3:] = self.rot_interp(t).as_rotvec() |
| |
|
| | if is_single: |
| | pose = pose[0] |
| | return pose |
| |
|