Skip to content

Commit 8185eb0

Browse files
committed
Extend tests of sample validation in sample consensus models
Special emphasis is placed on tests with collinear inputs.
1 parent 3a4500a commit 8185eb0

3 files changed

Lines changed: 257 additions & 0 deletions

File tree

test/sample_consensus/test_sample_consensus_line_models.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,89 @@ TEST (SampleConsensusModelLine, OnGroundPlane)
149149
EXPECT_NEAR (0, coeff[5], 1e-4);
150150
}
151151

152+
TEST (SampleConsensusModelLine, SampleValidationPointsEqual)
153+
{
154+
PointCloud<PointXYZ> cloud;
155+
cloud.resize (3);
156+
157+
// The "cheat point" makes it possible to find a set of valid samples and
158+
// therefore avoids the log message of an unsuccessful sample validation
159+
// being printed a 1000 times without any chance of success.
160+
// The order is chosen such that with a known, fixed rng-state/-seed all
161+
// validation steps are actually exercised.
162+
const pcl::index_t firstKnownEqualPoint = 0;
163+
const pcl::index_t secondKnownEqualPoint = 1;
164+
const pcl::index_t cheatPointIndex = 2;
165+
166+
cloud[firstKnownEqualPoint].getVector3fMap () << 0.1f, 0.0f, 0.0f;
167+
cloud[secondKnownEqualPoint].getVector3fMap () << 0.1f, 0.0f, 0.0f;
168+
cloud[cheatPointIndex].getVector3fMap () << 0.0f, 0.1f, 0.0f; // <-- cheat point
169+
170+
// Create a shared line model pointer directly and explicitly disable the
171+
// random seed for the reasons mentioned above
172+
SampleConsensusModelLinePtr model (
173+
new SampleConsensusModelLine<PointXYZ> (cloud.makeShared (), /* random = */ false));
174+
175+
// Algorithm tests
176+
pcl::Indices samples;
177+
int iterations = 0;
178+
model->getSamples(iterations, samples);
179+
EXPECT_EQ (samples.size(), 2);
180+
// The "cheat point" has to be part of the sample, otherwise something is wrong.
181+
// The best option would be to assert on stderr output here, but that doesn't
182+
// seem to be that simple.
183+
EXPECT_TRUE (std::find(samples.begin (), samples.end (), cheatPointIndex) != samples.end ());
184+
185+
pcl::Indices forcedSamples = {firstKnownEqualPoint, secondKnownEqualPoint};
186+
Eigen::VectorXf modelCoefficients;
187+
EXPECT_FALSE (model->computeModelCoefficients (forcedSamples, modelCoefficients));
188+
}
189+
190+
TEST (SampleConsensusModelLine, SampleValidationPointsValid)
191+
{
192+
PointCloud<PointXYZ> cloud;
193+
cloud.resize (2);
194+
195+
// These two points only differ in one coordinate so this also acts as a
196+
// regression test for 36c2bd6209f87dc7c6f56e2c0314e19f9cab95ec
197+
cloud[0].getVector3fMap () << 0.0f, 0.0f, 0.0f;
198+
cloud[1].getVector3fMap () << 0.1f, 0.0f, 0.0f;
199+
200+
// Create a shared line model pointer directly
201+
SampleConsensusModelLinePtr model (new SampleConsensusModelLine<PointXYZ> (cloud.makeShared ()));
202+
203+
// Algorithm tests
204+
pcl::Indices samples;
205+
int iterations = 0;
206+
model->getSamples(iterations, samples);
207+
EXPECT_EQ (samples.size(), 2);
208+
209+
pcl::Indices forcedSamples = {0, 1};
210+
Eigen::VectorXf modelCoefficients;
211+
EXPECT_TRUE (model->computeModelCoefficients (forcedSamples, modelCoefficients));
212+
}
213+
214+
TEST (SampleConsensusModelLine, SampleValidationNotEnoughSamples)
215+
{
216+
PointCloud<PointXYZ> cloud;
217+
cloud.resize (1);
218+
219+
cloud[0].getVector3fMap () << 0.1f, 0.0f, 0.0f;
220+
221+
// Create a shared line model pointer directly
222+
SampleConsensusModelLinePtr model (new SampleConsensusModelLine<PointXYZ> (cloud.makeShared ()));
223+
224+
// Algorithm tests
225+
pcl::Indices samples;
226+
int iterations = 0;
227+
model->getSamples(iterations, samples);
228+
EXPECT_EQ (samples.size(), 0);
229+
230+
pcl::Indices forcedSamples = {0,};
231+
Eigen::VectorXf modelCoefficients;
232+
EXPECT_FALSE (model->computeModelCoefficients (forcedSamples, modelCoefficients));
233+
}
234+
152235
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
153236
TEST (SampleConsensusModelParallelLine, RANSAC)
154237
{

test/sample_consensus/test_sample_consensus_plane_models.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,97 @@ TEST (SampleConsensusModelPlane, Base)
134134
ASSERT_EQ (indices_.size (), indices->size ());
135135
}
136136

137+
TEST (SampleConsensusModelPlane, SampleValidationPointsCollinear)
138+
{
139+
PointCloud<PointXYZ> cloud;
140+
cloud.resize (4);
141+
142+
// The "cheat point" makes it possible to find a set of valid samples and
143+
// therefore avoids the log message of an unsuccessful sample validation
144+
// being printed a 1000 times without any chance of success.
145+
// The order is chosen such that with a known, fixed rng-state/-seed all
146+
// validation steps are actually exercised.
147+
const pcl::index_t firstCollinearPointIndex = 0;
148+
const pcl::index_t secondCollinearPointIndex = 1;
149+
const pcl::index_t thirdCollinearPointIndex = 2;
150+
const pcl::index_t cheatPointIndex = 3;
151+
152+
cloud[firstCollinearPointIndex].getVector3fMap () << 0.1f, 0.1f, 0.1f;
153+
cloud[secondCollinearPointIndex].getVector3fMap () << 0.2f, 0.2f, 0.2f;
154+
cloud[thirdCollinearPointIndex].getVector3fMap () << 0.3f, 0.3f, 0.3f;
155+
cloud[cheatPointIndex].getVector3fMap () << 0.0f, 0.1f, 0.0f; // <-- cheat point
156+
157+
// Create a shared line model pointer directly and explicitly disable the
158+
// random seed for the reasons mentioned above
159+
SampleConsensusModelPlanePtr model (
160+
new SampleConsensusModelPlane<PointXYZ> (cloud.makeShared (), /* random = */ false));
161+
162+
// Algorithm tests
163+
pcl::Indices samples;
164+
int iterations = 0;
165+
model->getSamples(iterations, samples);
166+
EXPECT_EQ (samples.size(), 3);
167+
// The "cheat point" has to be part of the sample, otherwise something is wrong.
168+
// The best option would be to assert on stderr output here, but that doesn't
169+
// seem to be that simple.
170+
EXPECT_TRUE (std::find(samples.begin (), samples.end (), cheatPointIndex) != samples.end ());
171+
172+
pcl::Indices forcedSamples = {firstCollinearPointIndex, secondCollinearPointIndex, thirdCollinearPointIndex};
173+
Eigen::VectorXf modelCoefficients;
174+
EXPECT_FALSE (model->computeModelCoefficients (forcedSamples, modelCoefficients));
175+
}
176+
177+
TEST (SampleConsensusModelPlane, SampleValidationPointsValid)
178+
{
179+
PointCloud<PointXYZ> cloud;
180+
cloud.resize (3);
181+
182+
cloud[0].getVector3fMap () << 0.1f, 0.0f, 0.0f;
183+
cloud[1].getVector3fMap () << 0.0f, 0.1f, 0.0f;
184+
cloud[2].getVector3fMap () << 0.0f, 0.0f, 0.1f;
185+
186+
// Create a shared line model pointer directly
187+
SampleConsensusModelPlanePtr model (
188+
new SampleConsensusModelPlane<PointXYZ> (cloud.makeShared ()));
189+
190+
// Algorithm tests
191+
pcl::Indices samples;
192+
int iterations = 0;
193+
model->getSamples(iterations, samples);
194+
EXPECT_EQ (samples.size(), 3);
195+
196+
Eigen::VectorXf modelCoefficients;
197+
EXPECT_TRUE (model->computeModelCoefficients (samples, modelCoefficients));
198+
}
199+
200+
TEST (SampleConsensusModelPlane, SampleValidationNotEnoughSamples)
201+
{
202+
PointCloud<PointXYZ> cloud;
203+
cloud.resize (2);
204+
205+
cloud[0].getVector3fMap () << 0.1f, 0.0f, 0.0f;
206+
cloud[1].getVector3fMap () << 0.0f, 0.1f, 0.0f;
207+
208+
std::vector<pcl::Indices> testIndices = {{}, {0,}, {0, 1}};
209+
210+
for( const auto& indices : testIndices) {
211+
PointCloud<PointXYZ> subCloud {cloud, indices};
212+
213+
// Create a shared line model pointer directly
214+
SampleConsensusModelPlanePtr model (
215+
new SampleConsensusModelPlane<PointXYZ> (subCloud.makeShared ()));
216+
217+
// Algorithm tests
218+
pcl::Indices samples;
219+
int iterations = 0;
220+
model->getSamples(iterations, samples);
221+
EXPECT_EQ (samples.size(), 0);
222+
223+
Eigen::VectorXf modelCoefficients;
224+
EXPECT_FALSE (model->computeModelCoefficients (indices, modelCoefficients));
225+
}
226+
}
227+
137228
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
138229
TEST (SampleConsensusModelPlane, RANSAC)
139230
{

test/sample_consensus/test_sample_consensus_quadric_models.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,89 @@ TEST (SampleConsensusModelCircle2D, RANSAC)
520520
EXPECT_NEAR ( 1, coeff_refined[2], 1e-3);
521521
}
522522

523+
///////////////////////////////////////////////////////////////////////////////
524+
525+
TEST (SampleConsensusModelCircle2D, SampleValidationPointsValid)
526+
{
527+
PointCloud<PointXYZ> cloud;
528+
cloud.resize (3);
529+
530+
cloud[0].getVector3fMap () << 1.0f, 2.0f, 0.0f;
531+
cloud[1].getVector3fMap () << 0.0f, 1.0f, 0.0f;
532+
cloud[2].getVector3fMap () << 1.5f, 0.0f, 0.0f;
533+
534+
// Create a shared line model pointer directly
535+
SampleConsensusModelCircle2DPtr model (
536+
new SampleConsensusModelCircle2D<PointXYZ> (cloud.makeShared ()));
537+
538+
// Algorithm tests
539+
pcl::Indices samples;
540+
int iterations = 0;
541+
model->getSamples(iterations, samples);
542+
EXPECT_EQ (samples.size(), 3);
543+
544+
Eigen::VectorXf modelCoefficients;
545+
EXPECT_TRUE (model->computeModelCoefficients (samples, modelCoefficients));
546+
547+
EXPECT_NEAR (modelCoefficients[0], 1.05f, 1e-3); // center x
548+
EXPECT_NEAR (modelCoefficients[1], 0.95f, 1e-3); // center y
549+
EXPECT_NEAR (modelCoefficients[2], std::sqrt (1.105f), 1e-3); // radius
550+
}
551+
552+
using PointVector = std::vector<PointXYZ>;
553+
class SampleConsensusModelCircle2DCollinearTest
554+
: public testing::TestWithParam<PointVector> {
555+
protected:
556+
void SetUp() override {
557+
pointCloud_ = make_shared<PointCloud<PointXYZ>>();
558+
for(const auto& point : GetParam()) {
559+
pointCloud_->emplace_back(point);
560+
}
561+
}
562+
563+
PointCloud<PointXYZ>::Ptr pointCloud_ = nullptr;
564+
};
565+
566+
// Parametric tests clearly show the input for which they failed and provide
567+
// clearer feedback if something is changed in the future.
568+
TEST_P (SampleConsensusModelCircle2DCollinearTest, SampleValidationPointsInvalid)
569+
{
570+
ASSERT_NE (pointCloud_, nullptr);
571+
572+
SampleConsensusModelCircle2DPtr model (
573+
new SampleConsensusModelCircle2D<PointXYZ> (pointCloud_));
574+
ASSERT_GE (model->getInputCloud ()->size (), model->getSampleSize ());
575+
576+
// Algorithm tests
577+
// (Maybe) TODO: try to implement the "cheat point" trick from
578+
// test_sample_consensus_line_models.cpp and friends here, too.
579+
// pcl::Indices samples;
580+
// int iterations = 0;
581+
// model->getSamples(iterations, samples);
582+
// EXPECT_EQ (samples.size(), 0);
583+
584+
// Explicitly enforce sample order so they can act as designed
585+
pcl::Indices forcedSamples = {0, 1, 2};
586+
Eigen::VectorXf modelCoefficients;
587+
EXPECT_FALSE (model->computeModelCoefficients (forcedSamples, modelCoefficients));
588+
}
589+
590+
INSTANTIATE_TEST_SUITE_P (CollinearInputs, SampleConsensusModelCircle2DCollinearTest,
591+
testing::Values( // Throw in some negative coordinates and "asymmetric" points to cover more cases
592+
PointVector {{ 0.0f, 0.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 2.0f, 0.0f, 0.0f}}, // collinear along x-axis
593+
PointVector {{-1.0f, 0.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 2.0f, 0.0f, 0.0f}},
594+
PointVector {{ 0.0f, -1.0f, 0.0f}, { 0.0f, 1.0f, 0.0f}, { 0.0f, 2.0f, 0.0f}}, // collinear along y-axis
595+
PointVector {{ 0.0f, -1.0f, 0.0f}, { 0.0f, 1.0f, 0.0f}, { 0.0f, 2.0f, 0.0f}},
596+
PointVector {{ 0.0f, 0.0f, 0.0f}, { 1.0f, 1.0f, 0.0f}, { 2.0f, 2.0f, 0.0f}}, // collinear along diagonal
597+
PointVector {{ 0.0f, 0.0f, 0.0f}, {-1.0f, -1.0f, 0.0f}, {-2.0f, -2.0f, 0.0f}},
598+
PointVector {{-3.0f, -6.5f, 0.0f}, {-2.0f, -0.5f, 0.0f}, {-1.5f, 2.5f, 0.0f}}, // other collinear input
599+
PointVector {{ 2.0f, 2.0f, 0.0f}, { 0.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 0.0f}}, // two points equal
600+
PointVector {{ 0.0f, 0.0f, 0.0f}, { 2.0f, 2.0f, 0.0f}, { 0.0f, 0.0f, 0.0f}},
601+
PointVector {{ 0.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 0.0f}, { 2.0f, 2.0f, 0.0f}},
602+
PointVector {{ 1.0f, 1.0f, 0.0f}, { 1.0f, 1.0f, 0.0f}, { 1.0f, 1.0f, 0.0f}} // all points equal
603+
)
604+
);
605+
523606
//////////////////////////////////////////////////////////////////////////////////////////////////
524607
template <typename PointT>
525608
class SampleConsensusModelCircle2DTest : private SampleConsensusModelCircle2D<PointT>

0 commit comments

Comments
 (0)