Skip to content
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

Add forward kinematics package to labs #415

Merged
merged 12 commits into from
Mar 10, 2023
Merged

Conversation

fantaosha
Copy link
Contributor

Motivation and Context

How Has This Been Tested

Types of changes

  • Docs change / refactoring / dependency upgrade
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have completed my CLA (see CONTRIBUTING)
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@fantaosha fantaosha added the enhancement New feature or request label Jan 2, 2023
@fantaosha fantaosha self-assigned this Jan 2, 2023
@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jan 2, 2023
Base automatically changed from taoshaf.functional.se3.log to taoshaf.functional.se3.left_project January 12, 2023 20:11
Base automatically changed from taoshaf.functional.se3.left_project to taoshaf.functional.se3.left_act January 12, 2023 20:11
Base automatically changed from taoshaf.functional.se3.left_act to taoshaf.functional.lift_and_project January 12, 2023 20:12
Base automatically changed from taoshaf.functional.lift_and_project to taoshaf.functional.se3.compose January 12, 2023 20:13
Base automatically changed from taoshaf.functional.se3.compose to taoshaf.functional.hat_and_vee January 12, 2023 20:13
Base automatically changed from taoshaf.functional.hat_and_vee to taoshaf.inverse January 12, 2023 20:14
Base automatically changed from taoshaf.inverse to taoshaf.functional.se3.adjoint January 12, 2023 20:14
Base automatically changed from taoshaf.functional.se3.adjoint to taoshaf.functional.se3.rand January 12, 2023 20:15
Base automatically changed from taoshaf.functional.se3.rand to taoshaf.functional.so3.compose January 12, 2023 20:43
@luisenp luisenp force-pushed the taoshaf.functional.so3.compose branch from f0f12de to 04fa10a Compare January 20, 2023 18:20
Base automatically changed from taoshaf.functional.so3.compose to main January 20, 2023 22:45
Copy link
Contributor

@luisenp luisenp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Requesting some small changes that should be easy to address.


if dtype is not None and origin.dtype != dtype:
warnings.warn(
f"tensor.dtype {origin.dtype} does not match given dtype {dtype}, "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe clearer to write "The origin's dtype ({origin.dtype}) does not match the desired dtype. Origin's dtype will take precendence".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored.

angle_axis = angle_axis.view(-1, 1)

if angle_axis.ndim != 2 or angle_axis.shape != (3, 1):
raise ValueError("The angle axis must be a 3-D vector.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ndim != 2 seems redundant since you are checking the exact angle_axis shape. Also, in the message "3-D vector" seems ambiguous (e.g., a user wouldn't know if (1, 3), (3,) are also valid shapes).

Maybe better to check that angle_axis.numel() == 3, and if that's the case you can just do self._axis[3:] = angle_axis.view(-1). In this case the current error message seems appropriate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

numel() is used now and the error message is "The revolute axis must have 3 elements.".

def _rotation_impl(self, angle: torch.Tensor) -> torch.Tensor:
if angle.ndim == 2 and angle.shape[1] == 1:
angle = angle.view(-1)
if angle.ndim != 1:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible that people could pass ndim > 2 by mistake. I think we can do the following:

angle = angle.squeeze()
if angle.ndim >= 2:
  raise ValueError("The joint angle must be a vector.")
angle = angle.view(-1)  # in case the squeeze collapsed it to shape `[]`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

def axis(self) -> torch.Tensor:
return self._axis

def set_parent(self, parent: int):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python properties provide a mechanism for setting properties so that one can do x.parent = the_parent instead of x.set_parent(the_parent). You can do:

@parent.setter
def parent(self, parent: int):
    ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the setters have been removed for encapsulation.

return 1

@abc.abstractmethod
def _rotation_impl(self, angle: torch.Tensor) -> torch.Tensor:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_rotation_impl implies that there is another rotation() method that's public and that works something like the following:

def rotation(self, angle: torch.Tensor) -> torch.Tensor:
    # common initialization such as checking angle shape
    pre_out = self._rotation_impl(angle)
    # post process pre_out
    return processed_out

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If just a private/internal implementation is needed then def _rotation(...) is sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rotation() and translation() are defined.

self._axis[4] = 1

def _rotation_impl(self, angle: torch.Tensor) -> torch.Tensor:
if angle.ndim == 2 and angle.shape[1] == 1:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you implement rotation() in the super class as mentioned above, all this angle checking code can go there and you don't need to reimplement this check in all subclasses.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored.

from typing import Optional
import torch

from theseus.geometry.functional import so3, se3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on offline discussion we'll move the files to /theseus/labs/kinematics/...

Also the import needs updating.

from theseus.geometry.functional import so3, se3


class Joint(abc.ABC):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have any unit tests accompanying these or will that come in later PRs that add other data structures.

Comment on lines 36 to 118
origin = torch.zeros(1, 3, 4, dtype=dtype)
origin[:, 0, 0] = 1
origin[:, 1, 1] = 1
origin[:, 2, 2] = 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a identity SE3 helper function and then call that here?

Copy link
Contributor

@luisenp luisenp Jan 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this suggestion. Maybe origin = se3.identity()? (this looks like an identity element for SE3, is this correct?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will go back to this once identity() is added to the Lie group library.

pass

@abc.abstractmethod
def relative_pose(self, angle: torch.Tensor) -> torch.Tensor:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment about this, for instance relative to what?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am guessing that as joints are basically pose transforms between links, relative_pose refers to the pose of the child link relative to the parent link.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a function name like compute_joint_transform or parent_to_child_transform would be more descriptive?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming your interpretation is correct, I like the parent_to_chid_transform name. A shorter version of it would be nice, but nothing comes to mind right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe relative_transformation?

def dof(self) -> int:
return 1

@abc.abstractmethod
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have abstract methods then you want to derive from abc as well
class _RevoluteJointImpl(Joint, abc.ABC):

return rot


class RevoluteJointZ(_RevoluteJointImpl):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if the following alternative is better/worse in any way: only one RevoluteJoint class that has a full, x, y, or z type as a property (this would get rid of the Impl intermediate as well).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this crossed my mind as well. I think I'm OK with either.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am okay with either as well. We should consider tradeoffs like ease of use and potential overheads.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if the following alternative is better/worse in any way: only one RevoluteJoint class that has a full, x, y, or z type as a property (this would get rid of the Impl intermediate as well).

I agree with the simpler implementation as well. If the need to have an abstract interface and separate implementations for each class arises, we can always easily refactor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now there are two classes for RevoluteJoint, i.e., RevoluteJointFull and RevoluteJointXYZ

Copy link
Member

@mhmukadam mhmukadam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good so far. A few comments for us to hash out.

def set_parent(self, parent: int):
self._parent = parent

def set_child(self, child: int):
Copy link
Contributor

@luisenp luisenp Jan 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On further thought, I think a lot of these properties (maybe all) can be removed and replaced with public attributes. Using a property is more appropriate if we want to hide some extra computation under the hood (e.g., checking for correct inputs).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the setters have been removed.

return rot


class RevoluteJointZ(_RevoluteJointImpl):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if the following alternative is better/worse in any way: only one RevoluteJoint class that has a full, x, y, or z type as a property (this would get rid of the Impl intermediate as well).

I agree with the simpler implementation as well. If the need to have an abstract interface and separate implementations for each class arises, we can always easily refactor.

Comment on lines 68 to 164
@property
def axis(self) -> torch.Tensor:
return self._axis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only revolute joints have axes, so I don't think a general Joint abstract class should have this as an attribute.

pass

@abc.abstractmethod
def relative_pose(self, angle: torch.Tensor) -> torch.Tensor:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am guessing that as joints are basically pose transforms between links, relative_pose refers to the pose of the child link relative to the parent link.

Comment on lines 82 to 84
@abc.abstractmethod
def relative_pose(self, angle: torch.Tensor) -> torch.Tensor:
pass
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Why output an se3 vector instead of an SE3 object?
  • Should each joint also have its own Jacobian method?
  • Different types of joints have different parametrizations. A revolute joint requires a single angle value, but that is not the case for other types of joints. Since this is a general abstract Joint class, maybe this function should have a more general input, such as a parameter vector of length self.dof?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first question, it might be because this is based on the initial version of the new Lie groups library, which was all regular tensor to regular tensors. This will probably change with the upcoming class-based API (see #450 and #452, for a quick peek).

Agree on the third point, I think this would be preferable.

@fantaosha any thoughts on the second question? (or anything I missed above)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For any types of joints, the input tensor shape should be batch_size x dof.

pass

@abc.abstractmethod
def relative_pose(self, angle: torch.Tensor) -> torch.Tensor:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a function name like compute_joint_transform or parent_to_child_transform would be more descriptive?

@luisenp luisenp changed the title Add revolute joint Add forward kinematics package to labs Mar 7, 2023
fantaosha and others added 10 commits March 8, 2023 10:22
* add prismatic joint

* refactor prismatic joint

* Add robot model (#417)

* add link

* add id to joint

* add id to link

* add robot model

* add name to robot

* backup

* refactor joint

* refactor link

* add robot model

* robot model works

* a minior refactor of joint

* add ancestor to links

* update dof computation

* add forward kinematics function

* Add differentiable forward kinematics (#420)

* fixed a bug in FixedJoint

* add angle_ids

* add jacobians to FK

* forward kinematics works

* rename FK

* add tests for differentiable kinematics

* rename tests

* simplify the computation of origin

* add jacobian tests for forward kinematics

* fixed a bug for vectorize=True in SE3

* improve the efficiency of forward kinematics

* add tests for backward propagation

* update lie group load paths

* Add device to robot model (#422)

* add device to robot model

* Refactor Joint.dof as a property (#423)

* refactor joint dof

* add virtual end-effector for tests

* seems to work after name changes

* rename parent->parent_link and child->child_link

* rename angle_ids -> ancestor_active_joint_ids

* refactor tests with torch.testing.assert_close

* small refactoring of the code

* add Robot.get_links()

* remove some setters for link and joint

* made some functions private for encapsulation

* consolidate if block and classmethod->staticmethod

* refactor set_robot_from_urdf_file

* rename ancestor_active_joint_ids -> ancestor_non_fixed_joint_ids

* id=-1 -> id=None

* refactor the code for simplicity

* move link into joint
@fantaosha fantaosha force-pushed the taoshaf.robot.revolute_joint branch from 22ff507 to 93670ae Compare March 8, 2023 18:36
@fantaosha fantaosha merged commit cd2fc2c into main Mar 10, 2023
@fantaosha fantaosha deleted the taoshaf.robot.revolute_joint branch March 10, 2023 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants