Skip to content

Commit d7ad15d

Browse files
committed
chore: make normals and covs set-able from python
1 parent 1d8cce8 commit d7ad15d

File tree

2 files changed

+70
-13
lines changed

2 files changed

+70
-13
lines changed

src/python/pointcloud.cpp

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,27 @@ void define_pointcloud(py::module& m) {
1717
// PointCloud
1818
py::class_<PointCloud, std::shared_ptr<PointCloud>>(m, "PointCloud") //
1919
.def(
20-
py::init([](const Eigen::MatrixXd& points) {
20+
py::init([](const Eigen::MatrixXd& points, const Eigen::MatrixXd& normals, const std::vector<Eigen::MatrixXd>& covs) {
2121
if (points.cols() != 3 && points.cols() != 4) {
2222
std::cerr << "points must be Nx3 or Nx4" << std::endl;
2323
return std::make_shared<PointCloud>();
2424
}
2525

26+
if (normals.rows() != 0 && normals.cols() != 0 && points.rows() != normals.rows()) {
27+
std::cerr << "The number of points and normals must be equal" << std::endl;
28+
return std::make_shared<PointCloud>();
29+
}
30+
31+
if (normals.rows() != 0 && normals.cols() != 0 && normals.cols() != 3 && normals.cols() != 4) {
32+
std::cerr << "normals must be Nx3 or Nx4" << std::endl;
33+
return std::make_shared<PointCloud>();
34+
}
35+
36+
if (!covs.empty() && points.rows() != covs.size()) {
37+
std::cerr << "points and covs must be same size" << std::endl;
38+
return std::make_shared<PointCloud>();
39+
}
40+
2641
auto pc = std::make_shared<PointCloud>();
2742
pc->resize(points.rows());
2843
if (points.cols() == 3) {
@@ -35,9 +50,26 @@ void define_pointcloud(py::module& m) {
3550
}
3651
}
3752

53+
for (size_t i = 0; i < covs.size(); ++i) {
54+
pc->cov(i).setZero();
55+
pc->cov(i).block<3, 3>(0, 0) = covs[i];
56+
}
57+
58+
if (normals.cols() == 3) {
59+
for (size_t i = 0; i < normals.rows(); i++) {
60+
pc->normal(i) << normals.row(i).transpose(), 1.0;
61+
}
62+
} else {
63+
for (size_t i = 0; i < normals.rows(); i++) {
64+
pc->normal(i) << normals.row(i).transpose();
65+
}
66+
}
67+
3868
return pc;
3969
}),
4070
py::arg("points"),
71+
py::arg("normals"),
72+
py::arg("covs"),
4173
R"""(
4274
PointCloud(points: numpy.ndarray)
4375
@@ -47,6 +79,12 @@ void define_pointcloud(py::module& m) {
4779
----------
4880
points : numpy.ndarray, shape (n, 3) or (n, 4)
4981
The input point cloud.
82+
83+
normals : numpy.ndarray, shape (n, 3) or (n, 4)
84+
The normals corresponding to points. Ignored if empty (0-by-0).
85+
86+
covs : list of numpy.ndarray, each shape (3, 3) or (4, 4)
87+
The input point cloud covariances (only 3x3 upper block is used). Ignored if empty.
5088
)""")
5189
.def("__repr__", [](const PointCloud& points) { return "small_gicp.PointCloud (" + std::to_string(points.size()) + " points)"; })
5290
.def("__len__", [](const PointCloud& points) { return points.size(); })
@@ -156,4 +194,4 @@ void define_pointcloud(py::module& m) {
156194
cov : numpy.ndarray, shape (4, 4)
157195
Covariance matrix.
158196
)pbdoc");
159-
}
197+
}

src/test/python_test.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,32 @@ def test_load_points(load_points):
8282
def test_points(load_points):
8383
_, points_numpy, _ = load_points
8484

85-
points = small_gicp.PointCloud(points_numpy)
85+
empty_normals = numpy.array([])
86+
empty_covs = []
87+
points = small_gicp.PointCloud(points_numpy, empty_normals, empty_covs)
8688
assert points.size() == points_numpy.shape[0]
8789
assert numpy.all(numpy.abs(points.points() - points_numpy) < 1e-6)
8890

89-
points = small_gicp.PointCloud(points_numpy[:, :3])
91+
points = small_gicp.PointCloud(points_numpy[:, :3], empty_normals, empty_covs)
9092
assert points.size() == points_numpy.shape[0]
9193
assert numpy.all(numpy.abs(points.points() - points_numpy) < 1e-6)
9294

9395
for i in range(10):
9496
assert numpy.all(numpy.abs(points.point(i) - points_numpy[i]) < 1e-6)
95-
97+
98+
# Now test setting and getting pre-determined normals and covariances.
99+
N = 10
100+
points_numpy = numpy.cumsum(numpy.tile(numpy.array([1.0, 2.0, 3.0]), (N, 1)), axis=0)
101+
# Like the C++ API, setting non-unit norms and non-positive-definite
102+
# covariances is not enforced, and that's ok.
103+
nonsense_normals = numpy.cumsum(numpy.tile(numpy.array([4.0, 5.0, 6.0]), (N, 1)), axis=0)
104+
nonsense_covs = [numpy.array(list(range(9))).astype(numpy.float64).reshape(3, 3) * i for i in range(N)]
105+
points_with_normals_and_covs = small_gicp.PointCloud(points_numpy, nonsense_normals, nonsense_covs)
106+
107+
assert points_with_normals_and_covs.size() == N
108+
for i in range(points_with_normals_and_covs.size()):
109+
numpy.testing.assert_equal(points_with_normals_and_covs.cov(i)[:3, :3], nonsense_covs[i])
110+
numpy.testing.assert_equal(points_with_normals_and_covs.normal(i)[:3], nonsense_normals[i])
96111

97112
# Downsampling test
98113
def test_downsampling(load_points):
@@ -103,11 +118,13 @@ def test_downsampling(load_points):
103118

104119
downsampled2 = small_gicp.voxelgrid_sampling(points_numpy, 0.25, num_threads=2)
105120
assert abs(1.0 - downsampled.size() / downsampled2.size()) < 0.05
106-
107-
downsampled2 = small_gicp.voxelgrid_sampling(small_gicp.PointCloud(points_numpy), 0.25)
121+
122+
empty_normals = numpy.array([])
123+
empty_covs = []
124+
downsampled2 = small_gicp.voxelgrid_sampling(small_gicp.PointCloud(points_numpy, empty_normals, empty_covs), 0.25)
108125
assert downsampled.size() == downsampled2.size()
109-
110-
downsampled2 = small_gicp.voxelgrid_sampling(small_gicp.PointCloud(points_numpy), 0.25, num_threads=2)
126+
127+
downsampled2 = small_gicp.voxelgrid_sampling(small_gicp.PointCloud(points_numpy, empty_normals, empty_covs), 0.25, num_threads=2)
111128
assert abs(1.0 - downsampled.size() / downsampled2.size()) < 0.05
112129

113130
# Preprocess test
@@ -117,13 +134,15 @@ def test_preprocess(load_points):
117134
downsampled, _ = small_gicp.preprocess_points(points_numpy, downsampling_resolution=0.25)
118135
assert downsampled.size() > 0
119136

137+
empty_normals = numpy.array([])
138+
empty_covs = []
120139
downsampled2, _ = small_gicp.preprocess_points(points_numpy, downsampling_resolution=0.25, num_threads=2)
121140
assert abs(1.0 - downsampled.size() / downsampled2.size()) < 0.05
122-
123-
downsampled2, _ = small_gicp.preprocess_points(small_gicp.PointCloud(points_numpy), downsampling_resolution=0.25)
141+
142+
downsampled2, _ = small_gicp.preprocess_points(small_gicp.PointCloud(points_numpy, empty_normals, empty_covs), downsampling_resolution=0.25)
124143
assert downsampled.size() == downsampled2.size()
125-
126-
downsampled2, _ = small_gicp.preprocess_points(small_gicp.PointCloud(points_numpy), downsampling_resolution=0.25, num_threads=2)
144+
145+
downsampled2, _ = small_gicp.preprocess_points(small_gicp.PointCloud(points_numpy, empty_normals, empty_covs), downsampling_resolution=0.25, num_threads=2)
127146
assert abs(1.0 - downsampled.size() / downsampled2.size()) < 0.05
128147

129148
# Voxelmap test

0 commit comments

Comments
 (0)