-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial path types #120
Initial path types #120
Conversation
I strongly like the name path you use if there is no attached temporal dimension. Going from paths to trajectories; trajs are indexed by time, this could be a function or dict. You might also support this instead of splitting this up into Additional DoFs for grippers -> I also thought about initially supporting this but our Robotiq grippers don't like high frequency commands and I don't see an immediate use-case on our Schunk gripper so I think we can ignore that for now. I like the tuple approach more than the stacked numpy array approach. An alternative might be a list of joint names and a list of joint values. |
The tuple approach is what I've currently implemented in my planner interfaces (see except below). The "downside" of this approach is that to extract that path for only the left or right arm, you need things like this: path_left = [joint_left for joint_left, _ in path]
path_right = [joint_right for _, joint_right in path]
path_left_resampled = resample_path(path_left, n_servos)
path_right_resampled = resample_path(path_right, n_servos)
path_resampled = list(zip(path_left_resampled, path_right_resampled)) class SingleArmMotionPlanner(abc.ABC):
"""Base class that defines an interface for single-arm motion planners. """
@abc.abstractmethod
def plan_to_joint_configuration(
self,
start_configuration: JointConfigurationType,
goal_configuration: JointConfigurationType,
) -> Union[List[JointConfigurationType], None]:
raise NotImplementedError
class DualArmMotionPlanner(abc.ABC):
"""Base class that defines an interface for dual-arm motion planners. """
@abc.abstractmethod
def plan_to_joint_configuration(
self,
left_start_configuration: JointConfigurationType,
right_start_configuration: JointConfigurationType,
left_goal_configuration: Union[JointConfigurationType, None],
right_goal_configuration: Union[JointConfigurationType, None],
) -> Union[List[Tuple[JointConfigurationType, JointConfigurationType]], None]:
raise NotImplementedError |
I agree that the distinction between path and trajectory is important and should be very clear, I'm not convinced of the need for a List and Array variant of all the variables, that seems confusing to me and also harder to maintain? Wrt dual-arms, I would not provide separate types because this feels hard to scale to me (what about grippers etc?). Either we rely on the user knowing the degrees of freedom of its system and interpreting the configurations correctly, or we use some kind of naming? Regarding Path -> Trajectory, I can live with a (named) Tuple for this, though I could also live with a dict from 'pose/config' to timestamp. We should maybe also anticipate the need for specifying velocities/accelerations/torques in trajectories. In general it might not be a bad idea to take a look at the relevant ROS messages? 🙂 e.g. https://docs.ros2.org/foxy/api/trajectory_msgs/index-msg.html |
airo-typing/airo_typing/__init__.py
Outdated
####################### | ||
# Dual-arm path types # | ||
####################### | ||
DualJointConfigurationType = np.ndarray |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need separate Types for dual arm paths? IMO it is user responsability to know the DOFS of the system he/she is controlling. I think this would scale badly to motion planning with grippers (that can have various DOFs?).
Or is this additional code complexity worth the increased usability?
For reference, ROS does it like this:
I don't really understand their docs for ROS also doesn't seem to have a distinction between a |
I've pushed a much-simplified proposal. I've chosen to use 2D arrays for paths for the simple reason that you can have both row-wise access (e.g. to execute a I've also removed the dual arm types as this would indeed scale badly to gripper DoFs, mobile base DoFs, etc. However, we have to consider what the consequences are for the (start_left, start_right, goal_left | None, goal_right | None) -> list[(config_left, config_right) | None Using flat arrays as arguments here would mean we can't use (start_joints, goal_joints, plan_for_left=True, plan_for_right=True) -> 2D-array The |
I've pushed a new iteration of the types with
|
Some proposed changes in commit 3ca5049:
Do we also want to support derivatives of the PoseTrajectory (and how)? @tlpss |
I really like the proposal in Mathieu's last commit, and think we are getting close to a final design. It also matches well with the SingleArmMotionPlanner def plan_to_joint_configuration(self,
start_joints: JointConfigurationType,
goal_joints: JointConfigurationType)
-> SingleArmTrajectoryType | None: DualArmMotionPlanner def plan_to_joint_configuration(self,
start_joints_left: JointConfigurationType,
start_joints_right: JointConfigurationType,
goal_joints_left: Optional[JointConfigurationType],
goal_joints_left: Optional[JointConfigurationType]
) -> DualArmTrajectory | None: Many planners however don't consider time, so this should come this the warning/caveat that these trajectories might not always be safe to directly execute. |
I also like the proposal, though imo the time parameterization is the combination of timestamps, velocities,... So I would not split those. So either we move them to the trajectory classes, in which case the path container is is simply a data type alias:
Or we create a 'container/base class/ whatever' to group all the time information for abstract trajectories and create subclasses for the types we want to consider:
Or we can do the above with composition ofc.
|
In discussion with @Victorlouisdg; we prefer composition above inheritance and we embed the time parametrization explicitly instead of having a separate type. We propose the following (which is a bit the combination of your first and third suggestion Thomas). JointPathType: np.ndarray
@dataclass
class PoseTrajectory:
poses
velocities
angular_velocities
...
@dataclass
class JointTrajectory:
positions: JointPathType
times: TimesType
velocities: Optional[JointPathType]
acc: Optional[JointPathType]
efforts: Optional[JointPathType]
@dataclass
class SingleArmTrajectory:
arm_trajectory: JointTrajectory
gripper_trajectory: Optional[JointTrajectory]
@dataclass
class DualArmTrajectory:
left_arm_trajectory: SingleArmTrajectory
right_arm_trajectory: SingleArmTrajectory
@dataclass
class PoseTrajectory:
poses: PoseTrajectory Usage of DualArmTrajectory is quite convoluted with these names:
VL does not like these big names. |
@adverley one disadvantage now is that the time parameterization can be different for the left and right path, which is something we don't want to support I think? IMO a trajectory can have only 1 time parameterization. We split the path DOFs for readability, not because we want to interpret them as separate trajectories I think? And I tend to agree that accessing the data is convoluted. |
We also want to support force trajectories e.g. for a force controllable gripper. I see only two options:
|
@tlpss If we want to group
@adverley @Victorlouisdg Not sure if this matches your preferences for naming conventions, but we could do something like @dataclass
class JointTrajectory:
positions: JointPathType
times: TimesType
velocities: Optional[JointPathType]
acc: Optional[JointPathType]
efforts: Optional[JointPathType]
@dataclass
class SingleArmTrajectory:
arm: JointTrajectory
gripper: Optional[JointTrajectory]
@dataclass
class DualArmTrajectory:
left: SingleArmTrajectory
right: SingleArmTrajectory because it is implied that the dual_arm_trajectory.left.arm.positions It's terse, but implicit. But: this brings me to another issue: how do you define which arm is left and which is right? ;) Would a 2-tuple be an option? Or do we just leave it to the programmer to define left and right in their setup? |
@m-decoster Ah yes I now realize that I made a weird/wrong assumption. In this case I have to say I like your original proposal most: class JointPathContainer:
positions: Optional[JointPathType] = None
velocities: Optional[JointPathType] = None
accelerations: Optional[JointPathType] = None
efforts: Optional[JointPathType] = None
class SingleArmTrajectory:
path: JointPathContainer
gripper_path: Optional[JointPathContainer]
times: TimesType # time (seconds) from start of trajectory
@dataclass
class DualArmTrajectory:
path_left: JointPathContainer
path_right: JointPathContainer
gripper_left: Optional[JointPathContainer]
gripper_right: Optional[JointPathContainer]
times: TimesType # time (seconds) from start of trajectory
Regarding the Left/right, yeah I think we want to distinguish them somehow. Left/right seems easier to work with than first/second for example, though I can imagine there are cases where you don't have a 'left' and 'right' arm. I don't have a strong opinion on that, and would suggest we just go for something and start integrating using the types. We can make changes along the way if needed? |
LGTM! Maybe with |
I think the current proposal is really great. There's just one detail, do we make I prefer making it required in all |
Would there be a use case for having such a default time parameterization? IF not, I think it would be semantically more consistent to have 'actual' timings for trajectories. |
I've made |
Describe your changes
This PR introduces types that are needed for motion planning.
As an initial suggestion I've decided to treat dual arm configurations as an array
np.hstack((joints_left, joints_right))
. The alternative is treating them as the tuple(joints_left, joints_right)
. Both options have pros and cons we should consider.Additionally, for the paths I've provided both list and array types. We should consider whether both are actually necessary.
Considerations
DualJointConfigurationType
being thehstack
or twoJointConfigurationType
is similar to how Drake combines all state of a plant. E.g. callingsceneGraphCollisionChecker.CheckConfigCollisionFree
expects a (12, ) numpy array for my dual UR5e scenes.joints_left, joints_right = joints_dual[:6], joints_dual[6:] or np.split(joints_dual, 2)
. I'm not sure if this or better or worse than unpacking a tuplejoints_left, joints_right = joints_dual
. The tuple approach could support two arms with different #DoFs.np.diff(path_array, axis=0)
.Checklist