pointmatcher/PointCloudGenerator.cpp
Go to the documentation of this file.
1 // kate: replace-tabs off; indent-width 4; indent-mode normal
2 // vim: ts=4:sw=4:noexpandtab
3 /*
4 
5 Copyright (c) 2023,
6 Yoshua Nava, ANYbotics AG, Switzerland
7 You can contact the authors at <f dot pomerleau at gmail dot com> and
8 <stephane at magnenat dot net>
9 
10 All rights reserved.
11 
12 Redistribution and use in source and binary forms, with or without
13 modification, are permitted provided that the following conditions are met:
14  * Redistributions of source code must retain the above copyright
15  notice, this list of conditions and the following disclaimer.
16  * Redistributions in binary form must reproduce the above copyright
17  notice, this list of conditions and the following disclaimer in the
18  documentation and/or other materials provided with the distribution.
19  * Neither the name of the <organization> nor the
20  names of its contributors may be used to endorse or promote products
21  derived from this software without specific prior written permission.
22 
23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 DISCLAIMED. IN NO EVENT SHALL ETH-ASL BE LIABLE FOR ANY
27 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
30 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 
34 */
35 
36 #include "PointMatcher.h"
37 
38 #include <memory>
39 #include <random>
40 
41 template<typename T>
43  const Quaternion& rotation)
44 {
46  transformation.translation() = translation;
47  transformation.linear() = rotation.normalized().toRotationMatrix();
48  return transformation;
49 }
50 
51 template<typename T>
53 {
54  using Label = typename DataPoints::Label;
55 
56  // Add features.
57  pointCloud.features = Matrix::Ones(4, numberOfPoints);
58 
59  // Add feature labels.
60  pointCloud.featureLabels.push_back(Label("x", 1));
61  pointCloud.featureLabels.push_back(Label("y", 1));
62  pointCloud.featureLabels.push_back(Label("z", 1));
63  pointCloud.featureLabels.push_back(Label("pad", 1));
64 
65  // Add descriptors.
66  pointCloud.addDescriptor("normals", Matrix::Constant(3, numberOfPoints, 0));
67 }
68 
69 template<typename T>
71  DataPoints& pointCloud)
72 {
73  // Convert (translation, rotation) into a representation
74  const AffineTransform transformation{ buildUpTransformation(translation, rotation) };
75 
76  // Transformation handler;
77  std::shared_ptr<Transformation> transformator(PointMatcher<T>::get().REG(Transformation).create("RigidTransformation"));
78 
79  // Apply transformation.
80  transformator->inPlaceCompute(transformation.matrix(), pointCloud);
81 }
82 
83 template<typename T>
85  const StaticCoordVector& axisAlignedPlaneDimensions)
86 {
87  StaticCoordVector normalVector{ StaticCoordVector::Zero() };
88  for (Index i{ 0 }; i < 3; ++i)
89  {
90  if (axisAlignedPlaneDimensions(i) == 0)
91  {
92  normalVector(i) = 1;
93  }
94  }
95  return normalVector;
96 }
97 
98 // Reference: http://corysimon.github.io/articles/uniformdistn-on-sphere/
99 template<typename T>
101  const ScalarType radius,
102  const Index numberOfPoints,
104  const Quaternion& rotation)
105 {
106  // Create point cloud and add basic structure to fill.
107  DataPoints pointCloud;
108  addEmpty3dPointFields(numberOfPoints, pointCloud);
109 
110  // Create random distribution generators.
111  std::random_device randomDevice;
112  std::mt19937 randomNumberGenerator(randomDevice());
113  std::uniform_real_distribution<ScalarType> uniformDistribution(0.0f, 1.0f);
114 
115  // Sampling in Spherical coordinates.
116  auto normalsView{ pointCloud.getDescriptorViewByName("normals") };
117  for (Index i{ 0 }; i < numberOfPoints; ++i)
118  {
119  // Sample random values of theta and phi.
120  const ScalarType theta{ 2.0f * pi * uniformDistribution(randomNumberGenerator) };
121  const ScalarType phi{ std::acos(1.0f - 2.0f * uniformDistribution(randomNumberGenerator)) };
122 
123  // Pre-compute values, such as sine and cosine of phi and theta.
124  const ScalarType sinPhi{ std::sin(phi) };
125  const ScalarType cosPhi{ std::cos(phi) };
126  const ScalarType sinTheta{ std::sin(theta) };
127  const ScalarType cosTheta{ std::cos(theta) };
128 
129  // Fill features (3D point coordinates)
130  pointCloud.features(0, i) = sinPhi * cosTheta * radius; // x
131  pointCloud.features(1, i) = sinPhi * sinTheta * radius; // y
132  pointCloud.features(2, i) = cosPhi * radius; // z
133 
134  // For a sphere, it's possible to compute outward looking normals by simply normalizing the vector going from its center to the
135  // form's body.
136  const StaticCoordVector normalVector{ pointCloud.features.col(i).head(3).normalized() };
137 
138  // Fill normals.
139  normalsView(0, i) = normalVector(0);
140  normalsView(1, i) = normalVector(1);
141  normalsView(2, i) = normalVector(2);
142  }
143 
144  // Transform point cloud in space.
146 
147  return pointCloud;
148 }
149 
150 // Reference: https://stackoverflow.com/questions/5837572/generate-a-random-point-within-a-circle-uniformly
151 template<typename T>
153  const ScalarType radius,
154  const Index numberOfPoints,
156  const Quaternion& rotation)
157 {
158  // Create point cloud and add basic structure to fill.
159  DataPoints pointCloud;
160  addEmpty3dPointFields(numberOfPoints, pointCloud);
161 
162  // Create random distribution generators.
163  std::random_device randomDevice;
164  std::mt19937 randomNumberGenerator(randomDevice());
165  std::uniform_real_distribution<ScalarType> uniformDistribution(0.0f, 1.0f);
166 
167  // Sampling in Cartesian coordinates.
168  auto normalsView{ pointCloud.getDescriptorViewByName("normals") };
169  for (Index i{ 0 }; i < numberOfPoints; ++i)
170  {
171  // Sample random values of theta and phi.
172  const ScalarType phi{ 2.0f * pi * uniformDistribution(randomNumberGenerator) };
173  const ScalarType radiusSample{ radius * sqrt(uniformDistribution(randomNumberGenerator)) };
174 
175  // Pre-compute values, such as sine and cosine of phi and theta.
176  const ScalarType sinPhi{ std::sin(phi) };
177  const ScalarType cosPhi{ std::cos(phi) };
178 
179  // Fill features (3D point coordinates)
180  pointCloud.features(0, i) = cosPhi * radiusSample; // x
181  pointCloud.features(1, i) = sinPhi * radiusSample; // y
182  pointCloud.features(2, i) = 0; // z
183 
184  // Fill normals.
185  normalsView(0, i) = 0;
186  normalsView(1, i) = 0;
187  normalsView(2, i) = 1;
188  }
189 
190  // We generated the circle on the ground plane, and now we rotate it based on the translation and rotation arguments of this function.
192 
193  return pointCloud;
194 }
195 
196 // Reference: https://stackoverflow.com/questions/2678501/uniform-generation-of-3d-points-on-cylinder-cone
197 template<typename T>
199  const ScalarType radius, const ScalarType height, const Index numberOfPoints, const StaticCoordVector& translation,
200  const Quaternion& rotation)
201 {
202  // Create point cloud.
203  DataPoints pointCloud;
204 
205  // Define number of points per section of the volume.
206  // Cylinder area S = 2 * pi * r * h + 2 * pi * r^2
207  // where r: radius
208  // h: height
209  // The ratio between the caps and the body is given by the ratio between the area of the caps and the total area of the solid.
210  // R = (2 * pi * r^2) / (2 * pi * r * h + 2 * pi * r^2) = r / (h + r)
211  const ScalarType ratioCaps{ radius / (height + radius) };
212  // Round to nearest even number
213  const Index numberOfPointsBothCaps{ static_cast<Index>(std::round(static_cast<ScalarType>(numberOfPoints) * ratioCaps * 0.5f)) * 2u };
214  // Split between the two caps
215  const Index numberOfPointsCap{ numberOfPointsBothCaps / 2u };
216  // Put the rest of the points in the body
217  const Index numberOfPointsBody{ numberOfPoints - numberOfPointsBothCaps };
218 
219  // Create random distribution generators.
220  std::random_device randomDevice;
221  std::mt19937 randomNumberGenerator(randomDevice());
222  std::uniform_real_distribution<ScalarType> uniformDistribution(-1.0f, 1.0f);
223 
224  addEmpty3dPointFields(numberOfPointsBody, pointCloud);
225 
226  // Sampling in Cylindrical coordinates.
227  // This loop builds the Cylinder body.
228  auto normalsView{ pointCloud.getDescriptorViewByName("normals") };
229  for (Index i{ 0 }; i < numberOfPointsBody; ++i)
230  {
231  // Sample random values of theta and phi.
232  const ScalarType phi{ 2.0f * pi * uniformDistribution(randomNumberGenerator) };
233  const ScalarType z{ height * 0.5f * uniformDistribution(randomNumberGenerator) };
234 
235  // Pre-compute values, such as sine and cosine of phi and theta.
236  const ScalarType sinPhi{ std::sin(phi) };
237  const ScalarType cosPhi{ std::cos(phi) };
238 
239  // Fill features (3D point coordinates)
240  pointCloud.features(0, i) = cosPhi * radius; // x
241  pointCloud.features(1, i) = sinPhi * radius; // y
242  pointCloud.features(2, i) = z; // z
243 
244  // For a cylinder, it's possible to compute outward looking normals by simply normalizing the vector going from its center to the
245  // cylinder's body.
246  const StaticCoordVector radiusVector(pointCloud.features(0, i), pointCloud.features(1, i), 0);
247  const StaticCoordVector normalVector{ radiusVector.normalized() };
248 
249  // Fill normals.
250  normalsView(0, i) = normalVector(0);
251  normalsView(1, i) = normalVector(1);
252  normalsView(2, i) = normalVector(2);
253  }
254 
255  // Add top and bottom caps.
256  const Quaternion topCapOrientation{ Quaternion::Identity() };
257  const Quaternion bottomCapOrientation{ 0, 1, 0, 0 }; // Flip 180 degrees around x.
258  const StaticCoordVector topCapTranslation{ 0.0f, 0.0f, height * 0.5f };
259  const StaticCoordVector bottomCapTranslation{ 0.0f, 0.0f, -height * 0.5f };
260  pointCloud.concatenate(generateUniformlySampledCircle(radius, numberOfPointsCap, topCapTranslation, topCapOrientation));
261  pointCloud.concatenate(generateUniformlySampledCircle(radius, numberOfPointsCap, bottomCapTranslation, bottomCapOrientation));
262 
263  // Transform point cloud in space.
265 
266  return pointCloud;
267 }
268 
269 // Reference: https://stackoverflow.com/questions/11815792/generation-of-3d-random-points-on-the-surface-of-a-cube
270 template<typename T>
272  const StaticCoordVector& dimensions,
273  const Index numberOfPoints,
275  const Quaternion& rotation)
276 {
277  // Create point cloud and add basic structure to fill.
278  DataPoints pointCloud;
279  addEmpty3dPointFields(numberOfPoints, pointCloud);
280 
281  // Create random distribution generators.
282  std::random_device randomDevice;
283  std::mt19937 randomNumberGenerator(randomDevice());
284  std::uniform_real_distribution<ScalarType> lengthUniformDistribution(-dimensions(0), dimensions(0));
285  std::uniform_real_distribution<ScalarType> widthUniformDistribution(-dimensions(1), dimensions(1));
286  std::uniform_real_distribution<ScalarType> heightUniformDistribution(-dimensions(2), dimensions(2));
287 
288  const StaticCoordVector normalVector{ computeNormalOfAxisAlignedPlane(dimensions) };
289 
290  // Sampling in Cartesian coordinates.
291  auto normalsView{ pointCloud.getDescriptorViewByName("normals") };
292  for (Index i{ 0 }; i < numberOfPoints; ++i)
293  {
294  // Fill features (3D point coordinates)
295  pointCloud.features(0, i) = lengthUniformDistribution(randomNumberGenerator); // x
296  pointCloud.features(1, i) = widthUniformDistribution(randomNumberGenerator); // y
297  pointCloud.features(2, i) = heightUniformDistribution(randomNumberGenerator); // z
298 
299  // Fill normals.
300  normalsView(0, i) = normalVector(0);
301  normalsView(1, i) = normalVector(1);
302  normalsView(2, i) = normalVector(2);
303  }
304 
305  // We generated the plane on the ground plane, and now we rotate it based on the translation and rotation arguments of this function.
307 
308  return pointCloud;
309 }
310 
311 template<typename T>
313  const ScalarType length, const ScalarType width, const ScalarType height, const Index numberOfPoints,
315 {
316  const Index numberOfFaces{ 6 };
317  const Index numberOfPointsPerFace{ numberOfPoints / numberOfFaces };
318 
319  // Create point cloud and add basic structure to fill.
320  DataPoints pointCloud;
321  addEmpty3dPointFields(0, pointCloud);
322 
323  // Unit vectors for vertices.
324  // TODO(ynava) Evaluate generating a unit-length box and then re-scaling it.
325  const StaticCoordVector positiveXaxisFaceCenter{ length * 0.5f, 0.0f, 0.0f };
326  const StaticCoordVector negativeXaxisFaceCenter{ -length * 0.5f, 0.0f, 0.0f };
327  const StaticCoordVector positiveYaxisFaceCenter{ 0.0f, width * 0.5f, 0.0f };
328  const StaticCoordVector negativeYaxisFaceCenter{ 0.0f, -width * 0.5f, 0.0f };
329  const StaticCoordVector positiveZaxisFaceCenter{ 0.0f, 0.0f, height * 0.5f };
330  const StaticCoordVector negativeZaxisFaceCenter{ 0.0f, 0.0f, -height * 0.5f };
331 
332  // Dimension vectors of each of the box faces.
333  const Quaternion faceOrientation{ Quaternion::Identity() };
334  const StaticCoordVector xyFaceDimensions{ length * 0.5f, width * 0.5f, 0.0f };
335  const StaticCoordVector yzFaceDimensions{ 0.0f, width * 0.5f, height * 0.5f };
336  const StaticCoordVector xzFaceDimensions{ length * 0.5f, 0.0f, height * 0.5f };
337 
338  // Add points from each face to the point cloud.
339  pointCloud.concatenate(
340  generateUniformlySampledPlane(yzFaceDimensions, numberOfPointsPerFace, positiveXaxisFaceCenter, faceOrientation));
341  pointCloud.concatenate(
342  generateUniformlySampledPlane(yzFaceDimensions, numberOfPointsPerFace, negativeXaxisFaceCenter, faceOrientation));
343  pointCloud.concatenate(
344  generateUniformlySampledPlane(xzFaceDimensions, numberOfPointsPerFace, positiveYaxisFaceCenter, faceOrientation));
345  pointCloud.concatenate(
346  generateUniformlySampledPlane(xzFaceDimensions, numberOfPointsPerFace, negativeYaxisFaceCenter, faceOrientation));
347  pointCloud.concatenate(
348  generateUniformlySampledPlane(xyFaceDimensions, numberOfPointsPerFace, positiveZaxisFaceCenter, faceOrientation));
349  const Index missingPointsLastFace{ numberOfPoints - pointCloud.getNbPoints() - numberOfPointsPerFace };
351  xyFaceDimensions, numberOfPointsPerFace + missingPointsLastFace, negativeZaxisFaceCenter, faceOrientation));
352 
353  // Transform point cloud in space.
355 
356  return pointCloud;
357 }
358 
Label
DataPoints::Label Label
Definition: pypoint_matcher_helper.h:17
python::pointmatcher::applyTransformation
void applyTransformation(const StaticCoordVector &translation, const Eigen::Matrix< ScalarType, 4, 1 > &rotation, DataPoints &pointCloud)
Wrapper class that converts general Eigen::Matrix, convertible from numpy array, to Eigen::Quaternion...
Definition: point_cloud_generator.cpp:19
PointMatcher::PointCloudGenerator::addEmpty3dPointFields
static void addEmpty3dPointFields(const Index numberOfPoints, DataPoints &pointCloud)
Adds 3D coordinates and normals fields to a point cloud.
Definition: pointmatcher/PointCloudGenerator.cpp:52
PointMatcher::PointCloudGenerator::Index
typename DataPoints::Index Index
Definition: PointMatcher.h:784
PointMatcher::PointCloudGenerator::generateUniformlySampledCylinder
static DataPoints generateUniformlySampledCylinder(const ScalarType radius, const ScalarType height, const Index numberOfPoints, const StaticCoordVector &translation, const Quaternion &rotation)
Generates a uniformly sampled cylinder (with no filling), with a given number of points and pose.
Definition: pointmatcher/PointCloudGenerator.cpp:198
PointMatcher::PointCloudGenerator::generateUniformlySampledBox
static DataPoints generateUniformlySampledBox(const ScalarType length, const ScalarType width, const ScalarType height, const Index numberOfPoints, const StaticCoordVector &translation, const Quaternion &rotation)
Generates a uniformly sampled box (with no filling), with a given number of points and pose.
Definition: pointmatcher/PointCloudGenerator.cpp:312
PointMatcher::DataPoints::concatenate
void concatenate(const DataPoints &dp)
Add another point cloud after the current one. Faster merge will be achieved if all descriptor and ti...
Definition: pointmatcher/DataPoints.cpp:226
icp.rotation
rotation
Definition: icp.py:74
PointMatcher::PointCloudGenerator::buildUpTransformation
static AffineTransform buildUpTransformation(const StaticCoordVector &translation, const Quaternion &rotation)
Builds a 3D affine transformation with a given translation and rotation.
Definition: pointmatcher/PointCloudGenerator.cpp:42
PointMatcher::ScalarType
T ScalarType
The scalar type.
Definition: PointMatcher.h:159
PointMatcher::DataPoints::addDescriptor
void addDescriptor(const std::string &name, const Matrix &newDescriptor)
Add a descriptor by name, remove first if already exists.
Definition: pointmatcher/DataPoints.cpp:533
PointMatcher
Functions and classes that are dependant on scalar type are defined in this templatized class.
Definition: PointMatcher.h:130
PointMatcher::PointCloudGenerator::StaticCoordVector
Eigen::Matrix< T, kPointDimension, 1 > StaticCoordVector
A vector over ScalarType of kPointDimension.
Definition: PointMatcher.h:782
PointMatcher::DataPoints
A point cloud.
Definition: PointMatcher.h:207
PointMatcher::PointCloudGenerator::AffineTransform
Eigen::Transform< T, kPointDimension, Eigen::Affine > AffineTransform
An affine transform over ScalarType of kPointDimension.
Definition: PointMatcher.h:780
PointMatcher::PointCloudGenerator::computeNormalOfAxisAlignedPlane
static StaticCoordVector computeNormalOfAxisAlignedPlane(const StaticCoordVector &axisAlignedPlaneDimensions)
Computes a normal vector from a vector that contains the dimensions of a 2D shape in at-most 2 direct...
Definition: pointmatcher/PointCloudGenerator.cpp:84
PointMatcher::PointCloudGenerator
Definition: PointMatcher.h:775
icp.translation
translation
Definition: icp.py:73
PointMatcher::Transformation
A function that transforms points and their descriptors given a transformation matrix.
Definition: PointMatcher.h:404
PointMatcher::PointCloudGenerator::generateUniformlySampledPlane
static DataPoints generateUniformlySampledPlane(const StaticCoordVector &dimensions, const Index numberOfPoints, const StaticCoordVector &translation, const Quaternion &rotation)
Generates a uniformly sampled plane, with a given number of points and pose.
Definition: pointmatcher/PointCloudGenerator.cpp:271
PointMatcher::DataPoints::featureLabels
Labels featureLabels
labels of features
Definition: PointMatcher.h:332
PointMatcher::DataPoints::getNbPoints
unsigned getNbPoints() const
Return the number of points contained in the point cloud.
Definition: pointmatcher/DataPoints.cpp:158
PointMatcher::DataPoints::features
Matrix features
features of points in the cloud
Definition: PointMatcher.h:331
PointMatcher::PointCloudGenerator::generateUniformlySampledSphere
static DataPoints generateUniformlySampledSphere(const ScalarType radius, const Index numberOfPoints, const StaticCoordVector &translation, const Quaternion &rotation)
Generates a uniformly sampled sphere (with no filling), with a given number of points and pose.
Definition: pointmatcher/PointCloudGenerator.cpp:100
PointMatcher::PointCloudGenerator::generateUniformlySampledCircle
static DataPoints generateUniformlySampledCircle(const ScalarType radius, const Index numberOfPoints, const StaticCoordVector &translation, const Quaternion &rotation)
Generates a uniformly sampled circle, with a given number of points and pose.
Definition: pointmatcher/PointCloudGenerator.cpp:152
python::pointmatcher::generateUniformlySampledCircle
DataPoints generateUniformlySampledCircle(const ScalarType radius, const DataPoints::Index numberOfPoints, const StaticCoordVector &translation, const Eigen::Matrix< ScalarType, 4, 1 > &rotation)
Wrapper class that converts general Eigen::Matrix, convertible from numpy array, to Eigen::Quaternion...
Definition: point_cloud_generator.cpp:82
python::pointmatcher::generateUniformlySampledPlane
DataPoints generateUniformlySampledPlane(const StaticCoordVector &dimensions, const DataPoints::Index numberOfPoints, const StaticCoordVector &translation, const Eigen::Matrix< ScalarType, 4, 1 > &rotation)
Wrapper class that converts general Eigen::Matrix, convertible from numpy array, to Eigen::Quaternion...
Definition: point_cloud_generator.cpp:51
build_map.transformation
transformation
Definition: build_map.py:37
REG
#define REG(name)
Definition: Registrar.h:209
PointMatcher::PointCloudGenerator::applyTransformation
static void applyTransformation(const StaticCoordVector &translation, const Quaternion &rotation, DataPoints &pointCloud)
Transforms a point cloud by translating and rotating it a given amount.
Definition: pointmatcher/PointCloudGenerator.cpp:70
PointMatcher.h
public interface
PointMatcher::DataPoints::getDescriptorViewByName
ConstView getDescriptorViewByName(const std::string &name) const
Get a const view on a descriptor by name, throw an exception if it does not exist.
Definition: pointmatcher/DataPoints.cpp:555
PointMatcher::Quaternion
Eigen::Quaternion< T > Quaternion
A quaternion over ScalarType.
Definition: PointMatcher.h:165
PointMatcher::DataPoints::Label
The name for a certain number of dim.
Definition: PointMatcher.h:221


libpointmatcher
Author(s):
autogenerated on Mon Jul 1 2024 02:22:43