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

Pose3 improvements #1148

Merged
merged 5 commits into from
Mar 27, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion gtsam/geometry/Pose3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,14 @@ Point3 Pose3::transformFrom(const Point3& point, OptionalJacobian<3, 6> Hself,
return R_ * point + t_;
}

Matrix Pose3::transformFrom(const Matrix& points) const {
if (points.rows() != 3) {
throw std::invalid_argument("Pose3:transformFrom expects 3*N matrix.");
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR, Frank. I personally would find it more intuitive to have (N,3) as input, rather than (3,N).

Copy link
Member Author

Choose a reason for hiding this comment

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

I respect that, and realize many will feel that way as it's the numpy way. I don't understand why. Points are column vectors in all texts I know of, and replicating columns is much more natural, e.g., to multiply with matrices as the code above shows. It is also used the convention elsewhere in GTSAM (unless it slipped in somewhere, in which case I'd like to deprecate it).

}
const Matrix3 R = R_.matrix();
return (R * points).colwise() + t_; // Eigen broadcasting!
}

/* ************************************************************************* */
Point3 Pose3::transformTo(const Point3& point, OptionalJacobian<3, 6> Hself,
OptionalJacobian<3, 3> Hpoint) const {
Expand All @@ -374,6 +382,14 @@ Point3 Pose3::transformTo(const Point3& point, OptionalJacobian<3, 6> Hself,
return q;
}

Matrix Pose3::transformTo(const Matrix& points) const {
if (points.rows() != 3) {
throw std::invalid_argument("Pose3:transformTo expects 3*N matrix.");
}
const Matrix3 Rt = R_.transpose();
return Rt * (points.colwise() - t_); // Eigen broadcasting!
}

/* ************************************************************************* */
double Pose3::range(const Point3& point, OptionalJacobian<1, 6> Hself,
OptionalJacobian<1, 3> Hpoint) const {
Expand Down Expand Up @@ -431,7 +447,7 @@ Unit3 Pose3::bearing(const Pose3& pose, OptionalJacobian<2, 6> Hself,
boost::optional<Pose3> Pose3::Align(const Point3Pairs &abPointPairs) {
const size_t n = abPointPairs.size();
if (n < 3) {
return boost::none; // we need at least three pairs
return boost::none; // we need at least three pairs
}

// calculate centroids
Expand All @@ -451,6 +467,18 @@ boost::optional<Pose3> Pose3::Align(const Point3Pairs &abPointPairs) {
return Pose3(aRb, aTb);
}

boost::optional<Pose3> Pose3::Align(const Matrix& a, const Matrix& b) {
if (a.rows() != 3 || b.rows() != 3 || a.cols() != b.cols()) {
throw std::invalid_argument(
"Pose3:Align expects 3*N matrices of equal shape.");
}
Point3Pairs abPointPairs;
for (size_t j=0; j < a.cols(); j++) {
abPointPairs.emplace_back(a.col(j), b.col(j));
}
return Pose3::Align(abPointPairs);
}

boost::optional<Pose3> align(const Point3Pairs &baPointPairs) {
Point3Pairs abPointPairs;
for (const Point3Pair &baPair : baPointPairs) {
Expand Down
17 changes: 17 additions & 0 deletions gtsam/geometry/Pose3.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ class GTSAM_EXPORT Pose3: public LieGroup<Pose3, 6> {
*/
static boost::optional<Pose3> Align(const std::vector<Point3Pair>& abPointPairs);

// Version of Pose3::Align that takes 2 matrices.
static boost::optional<Pose3> Align(const Matrix& a, const Matrix& b);

/// @}
/// @name Testable
/// @{
Expand Down Expand Up @@ -249,6 +252,13 @@ class GTSAM_EXPORT Pose3: public LieGroup<Pose3, 6> {
Point3 transformFrom(const Point3& point, OptionalJacobian<3, 6> Hself =
boost::none, OptionalJacobian<3, 3> Hpoint = boost::none) const;

/**
* @brief transform many points in Pose coordinates and transform to world.
* @param points 3*N matrix in Pose coordinates
* @return points in world coordinates, as 3*N Matrix
*/
Matrix transformFrom(const Matrix& points) const;

/** syntactic sugar for transformFrom */
inline Point3 operator*(const Point3& point) const {
return transformFrom(point);
Expand All @@ -264,6 +274,13 @@ class GTSAM_EXPORT Pose3: public LieGroup<Pose3, 6> {
Point3 transformTo(const Point3& point, OptionalJacobian<3, 6> Hself =
boost::none, OptionalJacobian<3, 3> Hpoint = boost::none) const;

/**
* @brief transform many points in world coordinates and transform to Pose.
* @param points 3*N matrix in world coordinates
* @return points in Pose coordinates, as 3*N Matrix
*/
Matrix transformTo(const Matrix& points) const;

/// @}
/// @name Standard Interface
/// @{
Expand Down
7 changes: 7 additions & 0 deletions gtsam/geometry/geometry.i
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,9 @@ class Pose3 {
Pose3(const gtsam::Pose2& pose2);
Pose3(Matrix mat);

static boost::optional<gtsam::Pose3> Align(const gtsam::Point3Pairs& abPointPairs);
static boost::optional<gtsam::Pose3> Align(const gtsam::Matrix& a, const gtsam::Matrix& b);

// Testable
void print(string s = "") const;
bool equals(const gtsam::Pose3& pose, double tol) const;
Expand Down Expand Up @@ -470,6 +473,10 @@ class Pose3 {
gtsam::Point3 transformFrom(const gtsam::Point3& point) const;
gtsam::Point3 transformTo(const gtsam::Point3& point) const;

// Matrix versions
Matrix transformFrom(const Matrix& points) const;
Matrix transformTo(const Matrix& points) const;

// Standard Interface
gtsam::Rot3 rotation() const;
gtsam::Point3 translation() const;
Expand Down
47 changes: 43 additions & 4 deletions python/gtsam/tests/test_Pose3.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import numpy as np

import gtsam
from gtsam import Point3, Pose3, Rot3
from gtsam import Point3, Pose3, Rot3, Point3Pairs
from gtsam.utils.test_case import GtsamTestCase


Expand All @@ -30,13 +30,34 @@ def test_between(self):
actual = T2.between(T3)
self.gtsamAssertEquals(actual, expected, 1e-6)

def test_transform_to(self):
def test_transformTo(self):
"""Test transformTo method."""
transform = Pose3(Rot3.Rodrigues(0, 0, -1.570796), Point3(2, 4, 0))
actual = transform.transformTo(Point3(3, 2, 10))
pose = Pose3(Rot3.Rodrigues(0, 0, -math.pi/2), Point3(2, 4, 0))
actual = pose.transformTo(Point3(3, 2, 10))
expected = Point3(2, 1, 10)
self.gtsamAssertEquals(actual, expected, 1e-6)

# multi-point version
points = np.stack([Point3(3, 2, 10), Point3(3, 2, 10)]).T
dellaert marked this conversation as resolved.
Show resolved Hide resolved
actual_array = pose.transformTo(points)
self.assertEqual(actual_array.shape, (3, 2))
expected_array = np.stack([expected, expected]).T
np.testing.assert_allclose(actual_array, expected_array, atol=1e-6)

def test_transformFrom(self):
"""Test transformTo method."""
dellaert marked this conversation as resolved.
Show resolved Hide resolved
pose = Pose3(Rot3.Rodrigues(0, 0, -math.pi/2), Point3(2, 4, 0))
actual = pose.transformFrom(Point3(2, 1, 10))
expected = Point3(3, 2, 10)
self.gtsamAssertEquals(actual, expected, 1e-6)

# multi-point version
points = np.stack([Point3(2, 1, 10), Point3(2, 1, 10)]).T
actual_array = pose.transformFrom(points)
self.assertEqual(actual_array.shape, (3, 2))
expected_array = np.stack([expected, expected]).T
np.testing.assert_allclose(actual_array, expected_array, atol=1e-6)

def test_range(self):
"""Test range method."""
l1 = Point3(1, 0, 0)
Expand Down Expand Up @@ -81,6 +102,24 @@ def test_serialization(self):
actual.deserialize(serialized)
self.gtsamAssertEquals(expected, actual, 1e-10)

def test_align_squares(self):
"""Test if Align method can align 2 squares."""
square = np.array([[0,0,0],[0,1,0],[1,1,0],[1,0,0]], float).T
sTt = Pose3(Rot3.Rodrigues(0, 0, -math.pi), Point3(2, 4, 0))
transformed = sTt.transformTo(square)

st_pairs = Point3Pairs()
for j in range(4):
st_pairs.append((square[:,j], transformed[:,j]))

# Recover the transformation sTt
estimated_sTt = Pose3.Align(st_pairs)
self.gtsamAssertEquals(estimated_sTt, sTt, 1e-10)

# Matrix version
estimated_sTt = Pose3.Align(square, transformed)
self.gtsamAssertEquals(estimated_sTt, sTt, 1e-10)


if __name__ == "__main__":
unittest.main()