Merge pull request #4995 from sgiraudot/PCA-Fix_Segment_3_variant-GF

PCA: Add validity test and fix several bugs
This commit is contained in:
Laurent Rineau 2020-10-09 16:58:32 +02:00
commit fb843d4d56
4 changed files with 389 additions and 9 deletions

View File

@ -144,9 +144,9 @@ assemble_covariance_matrix_3(InputIterator first,
FT x0 = t[0].x();
FT y0 = t[0].y();
FT z0 = t[0].z();
FT delta[9] = {t[0].x(), t[1].x(), t[2].x(),
t[0].y(), t[1].y(), t[2].y(),
t[0].z(), t[1].z(), t[2].z()};
FT delta[9] = {t[1].x()-x0, t[3].x()-x0, t[5].x()-x0,
t[1].y()-y0, t[3].y()-y0, t[5].y()-y0,
t[1].z()-z0, t[3].z()-z0, t[5].z()-z0};
Matrix transformation (delta);
FT volume = t.volume();
@ -548,9 +548,9 @@ assemble_covariance_matrix_3(InputIterator first,
// assemble 2nd order moment about the origin.
Matrix moment;
moment << 1.0, 0.5, 0.0,
0.5, 1.0, 0.0,
0.0, 0.0, 0.0;
moment << 1.0/3.0, 0.5/3.0, 0.0,
0.5/3.0, 1.0/3.0, 0.0,
0.0, 0.0, 0.0;
for(InputIterator it = first;
it != beyond;

View File

@ -171,7 +171,8 @@ linear_least_squares_fitting_2(InputIterator first,
segments.push_back(Segment_2(t[3],t[0]));
}
return linear_least_squares_fitting_2(segments.begin(),segments.end(),line,c,K(),tag,
return linear_least_squares_fitting_2(segments.begin(),segments.end(),line,c,
(Segment_2*)nullptr, K(),tag,
diagonalize_traits);
} // end linear_least_squares_fitting_2 for rectangle set with 1D tag
@ -209,7 +210,8 @@ linear_least_squares_fitting_2(InputIterator first,
points.push_back(Point_2(t[3]));
}
return linear_least_squares_fitting_2(points.begin(),points.end(),line,c,K(),tag,
return linear_least_squares_fitting_2(points.begin(),points.end(),line,c,
(Point_2*)nullptr, K(),tag,
diagonalize_traits);
} // end linear_least_squares_fitting_2 for rectangle set with 0D tag

View File

@ -157,7 +157,7 @@ linear_least_squares_fitting_2(InputIterator first,
points.push_back(s[0]);
points.push_back(s[1]);
}
return linear_least_squares_fitting_2(points.begin(),points.end(),line,c,k,(Point*)nullptr,tag,
return linear_least_squares_fitting_2(points.begin(),points.end(),line,c,(Point*)nullptr,k,tag,
diagonalize_traits);
} // end linear_least_squares_fitting_2 for segment set with 1D tag

View File

@ -0,0 +1,378 @@
/*
This test checks that the outputs of all variants of PCA are
correct.
To do so:
- N Point_3 objects are generated on a random line
- N*N Point_3 objects are generated on a random plane
For each variant of the PCA, we generate objects "centered" on the
reference points:
- Sphere_3 centered on each point with random radius
- Equilateral Triangle_3 centered on each point with random length
- Point_2 using first 2 coordinates of Point_3
- etc.
PCA is then computed for Line_2 in 2D or for Line_3 and Plane_3 in
3D. Then, the mean distance between the original points (or 2D
variants if 2D case) and the estimated support is computed: if it
higher than 0.01% of the diameter of the point cloud, an assertion
is raised.
This only tests an obvious case where the PCA is computed on a set
of exactly aligned objects, but it should help avoiding the
introduction of bugs.
*/
#include <CGAL/Simple_cartesian.h>
#include <CGAL/linear_least_squares_fitting_2.h>
#include <CGAL/linear_least_squares_fitting_3.h>
#include <CGAL/Random.h>
#include <fstream>
typedef CGAL::Simple_cartesian<double> Kernel;
typedef Kernel::Point_3 Point_3;
typedef Kernel::Segment_3 Segment_3;
typedef Kernel::Vector_3 Vector_3;
typedef Kernel::Sphere_3 Sphere_3;
typedef Kernel::Triangle_3 Triangle_3;
typedef Kernel::Tetrahedron_3 Tetrahedron_3;
typedef Kernel::Iso_cuboid_3 Iso_cuboid_3;
typedef Kernel::Plane_3 Plane_3;
typedef Kernel::Line_3 Line_3;
typedef Kernel::Point_2 Point_2;
typedef Kernel::Vector_2 Vector_2;
typedef Kernel::Circle_2 Circle_2;
typedef Kernel::Iso_rectangle_2 Iso_rectangle_2;
typedef Kernel::Segment_2 Segment_2;
typedef Kernel::Triangle_2 Triangle_2;
typedef Kernel::Line_2 Line_2;
CGAL::Random rnd;
/*
Functions used to generate objects "centered" on points (objects
whose centers of mass are on the points).
*/
void generate_objects_centered_on_points (const std::vector<Point_3>& source,
std::vector<Point_3>& target)
{
target.reserve (source.size());
std::copy (source.begin(), source.end(), std::back_inserter (target));
}
void generate_objects_centered_on_points (const std::vector<Point_3>& source,
std::vector<Point_2>& target)
{
target.reserve (source.size());
for (std::size_t i = 0; i < source.size(); ++ i)
target.push_back (Point_2 (source[i].x(), source[i].y()));
}
void generate_objects_centered_on_points (const std::vector<Point_3>& source,
std::vector<Segment_3>& target)
{
Vector_3 diff (rnd.get_double(0, 0.01),
rnd.get_double(0, 0.01),
rnd.get_double(0, 0.01));
target.reserve (source.size());
for (std::size_t i = 0; i < source.size(); ++ i)
target.push_back (Segment_3 (source[i] + diff, source[i] - diff));
}
void generate_objects_centered_on_points (const std::vector<Point_3>& source,
std::vector<Segment_2>& target)
{
Vector_2 diff (rnd.get_double(0, 0.01),
rnd.get_double(0, 0.01));
target.reserve (source.size());
for (std::size_t i = 0; i < source.size(); ++ i)
{
Point_2 p (source[i].x(), source[i].y());
target.push_back (Segment_2 (p + diff, p - diff));
}
}
void generate_objects_centered_on_points (const std::vector<Point_3>& source,
std::vector<Sphere_3>& target)
{
double radius = rnd.get_double(0, 0.01);
target.reserve (source.size());
for (std::size_t i = 0; i < source.size(); ++ i)
target.push_back (Sphere_3 (source[i], radius * radius));
}
void generate_objects_centered_on_points (const std::vector<Point_3>& source,
std::vector<Circle_2>& target)
{
double radius = rnd.get_double(0, 0.01);
target.reserve (source.size());
for (std::size_t i = 0; i < source.size(); ++ i)
{
Point_2 p (source[i].x(), source[i].y());
target.push_back (Circle_2 (p, radius * radius));
}
}
void generate_objects_centered_on_points (const std::vector<Point_3>& source,
std::vector<Triangle_3>& target)
{
// General equilateral triangles
double length = rnd.get_double(0, 0.01);
double height = length * std::sqrt(3.) / 2.;
target.reserve (source.size());
for (std::size_t i = 0; i < source.size(); ++ i)
target.push_back
(Triangle_3 (source[i] + Vector_3 (0, 0, 2. * height / 3.),
source[i] + Vector_3 (0, -length/2., -height/3.),
source[i] + Vector_3 (0, length/2., -height/3.)));
}
void generate_objects_centered_on_points (const std::vector<Point_3>& source,
std::vector<Triangle_2>& target)
{
// General equilateral triangles
double length = rnd.get_double(0, 0.01);
double height = length * std::sqrt(3.) / 2.;
target.reserve (source.size());
for (std::size_t i = 0; i < source.size(); ++ i)
{
Point_2 p (source[i].x(), source[i].y());
target.push_back
(Triangle_2 (p + Vector_2 (0, 2. * height / 3.),
p + Vector_2 (-length/2., -height/3.),
p + Vector_2 (length/2., -height/3.)));
}
}
void generate_objects_centered_on_points (const std::vector<Point_3>& source,
std::vector<Tetrahedron_3>& target)
{
// General regular tetrahedra
double length = rnd.get_double(0, 0.01);
double height = length * std::sqrt(2. / 3.);
double height_tri = length * std::sqrt(3.) / 2.;
target.reserve (source.size());
for (std::size_t i = 0; i < source.size(); ++ i)
target.push_back
(Tetrahedron_3 (source[i] + Vector_3 (0, 0, 3. * height / 4.),
source[i] + Vector_3 (0, 2. * height_tri / 3., -height/4.),
source[i] + Vector_3 (-length/2., -height_tri / 3., -height/4.),
source[i] + Vector_3 (length/2., -height_tri / 3., -height/4.)));
}
void generate_objects_centered_on_points (const std::vector<Point_3>& source,
std::vector<Iso_cuboid_3>& target)
{
Vector_3 diff (rnd.get_double(0, 0.01),
rnd.get_double(0, 0.01),
rnd.get_double(0, 0.01));
target.reserve (source.size());
for (std::size_t i = 0; i < source.size(); ++ i)
target.push_back (Iso_cuboid_3 (source[i] + diff, source[i] - diff));
}
void generate_objects_centered_on_points (const std::vector<Point_3>& source,
std::vector<Iso_rectangle_2>& target)
{
Vector_2 diff (rnd.get_double(0, 0.01),
rnd.get_double(0, 0.01));
target.reserve (source.size());
for (std::size_t i = 0; i < source.size(); ++ i)
{
Point_2 p (source[i].x(), source[i].y());
target.push_back (Iso_rectangle_2 (p + diff, p - diff));
}
}
/*
Compute distances between original points to Line_3/Plane_3/Line_2
*/
double distance (const Point_3& p, const Line_3& l)
{ return std::sqrt (CGAL::squared_distance (p, l)); }
double distance (const Point_3& p, const Plane_3& pl)
{ return std::sqrt (CGAL::squared_distance (p, pl)); }
double distance (const Point_3& p, const Line_2& l)
{ return std::sqrt (CGAL::squared_distance (Point_2 (p.x(), p.y()), l)); }
/*
Test quality of fit and raise assertion if too low.
*/
template <typename Fitted>
void assert_quality (const std::vector<Point_3>& points, const Fitted& fitted)
{
double mean_dist = 0;
for (std::size_t i = 0; i < points.size(); ++ i)
{
double dist = distance (points[i], fitted);
mean_dist += dist;
}
mean_dist /= points.size();
std::cerr << "mean distance = " << mean_dist << std::endl;
CGAL_assertion_code
(double limit = 1e-3 * std::sqrt (CGAL::squared_distance (points.front(), points.back())));
CGAL_assertion (mean_dist < limit);
}
/*
Reference test functions, both in 2D and 3D.
*/
template <typename Fitted, typename Object, int dim>
void test (const std::vector<Object>& objects,
const std::vector<Point_3>& points,
const CGAL::Dimension_tag<2>& /* ambient dimension */)
{
std::cerr << " CGAL::Dimension_tag<" << dim << ">: ";
Fitted fitted;
CGAL::linear_least_squares_fitting_2
(objects.begin(), objects.end(), fitted, CGAL::Dimension_tag<dim>());
assert_quality (points, fitted);
}
template <typename Fitted, typename Object, int dim>
void test (const std::vector<Object>& objects,
const std::vector<Point_3>& points,
const CGAL::Dimension_tag<3>& /* ambient dimension */)
{
std::cerr << " CGAL::Dimension_tag<" << dim << ">: ";
Fitted fitted;
CGAL::linear_least_squares_fitting_3
(objects.begin(), objects.end(), fitted, CGAL::Dimension_tag<dim>());
assert_quality (points, fitted);
}
template <typename Object, typename Fitted, int dim>
void test (const std::vector<Point_3>& points)
{
std::vector<Object> objects;
generate_objects_centered_on_points (points, objects);
test<Fitted, Object, dim>
(objects, points, typename CGAL::Ambient_dimension<Object, Kernel>::type());
}
int main()
{
std::cerr << "Validity test with seed " << rnd.get_seed() << std::endl;
Point_3 origin (rnd.get_double(), rnd.get_double(), rnd.get_double());
Vector_3 base1 (rnd.get_double(), rnd.get_double(), rnd.get_double());
Vector_3 base2 (rnd.get_double(), rnd.get_double(), rnd.get_double());
std::cerr << "Origin = " << origin << std::endl
<< "Base1 = " << base1 << std::endl
<< "Base2 = " << base2 << std::endl;
std::size_t nb_points_on_line = 100;
std::vector<Point_3> points_on_line;
points_on_line.reserve (nb_points_on_line);
for (std::size_t i = 0; i < nb_points_on_line; ++ i)
points_on_line.push_back (Point_3 (origin + double(i) * base1));
std::vector<Point_3> points_on_plane;
points_on_plane.reserve (nb_points_on_line * nb_points_on_line);
for (std::size_t i = 0; i < nb_points_on_line; ++ i)
for (std::size_t j = 0; j < nb_points_on_line; ++ j)
points_on_plane.push_back (Point_3 (origin + double(i) * base1 + double(j) * base2));
std::cerr << std::endl << "=== 2D ===" << std::endl << std::endl;
std::cerr << "[Testing line fitting on Point_2 objects]" << std::endl;
test<Point_2, Line_2, 0> (points_on_line);
std::cerr << "[Testing line fitting on Segment_2 objects]" << std::endl;
test<Segment_2, Line_2, 1> (points_on_line);
test<Segment_2, Line_2, 0> (points_on_line);
std::cerr << "[Testing line fitting on Circle_2 objects]" << std::endl;
test<Circle_2, Line_2, 2> (points_on_line);
test<Circle_2, Line_2, 1> (points_on_line);
std::cerr << "[Testing line fitting on Triangle_2 objects]" << std::endl;
test<Triangle_2, Line_2, 2> (points_on_line);
test<Triangle_2, Line_2, 1> (points_on_line);
test<Triangle_2, Line_2, 0> (points_on_line);
std::cerr << "[Testing line fitting on Iso_rectangle_2 objects]" << std::endl;
test<Iso_rectangle_2, Line_2, 2> (points_on_line);
test<Iso_rectangle_2, Line_2, 1> (points_on_line);
test<Iso_rectangle_2, Line_2, 0> (points_on_line);
std::cerr << std::endl << "=== 3D ===" << std::endl << std::endl;
std::cerr << "[Testing line fitting on Point_3 objects]" << std::endl;
test<Point_3, Line_3, 0> (points_on_line);
std::cerr << "[Testing plane fitting on Point_3 objects]" << std::endl;
test<Point_3, Plane_3, 0> (points_on_plane);
std::cerr << "[Testing line fitting on Segment_3 objects]" << std::endl;
test<Segment_3, Line_3, 1> (points_on_line);
test<Segment_3, Line_3, 0> (points_on_line);
std::cerr << "[Testing plane fitting on Segment_3 objects]" << std::endl;
test<Segment_3, Plane_3, 1> (points_on_plane);
test<Segment_3, Plane_3, 0> (points_on_plane);
std::cerr << "[Testing line fitting on Sphere_3 objects]" << std::endl;
test<Sphere_3, Line_3, 3> (points_on_line);
test<Sphere_3, Line_3, 2> (points_on_line);
std::cerr << "[Testing plane fitting on Sphere_3 objects]" << std::endl;
test<Sphere_3, Plane_3, 3> (points_on_plane);
test<Sphere_3, Plane_3, 2> (points_on_plane);
std::cerr << "[Testing line fitting on Triangle_3 objects]" << std::endl;
test<Triangle_3, Line_3, 2> (points_on_line);
test<Triangle_3, Line_3, 1> (points_on_line);
test<Triangle_3, Line_3, 0> (points_on_line);
std::cerr << "[Testing plane fitting on Triangle_3 objects]" << std::endl;
test<Triangle_3, Plane_3, 2> (points_on_plane);
test<Triangle_3, Plane_3, 1> (points_on_plane);
test<Triangle_3, Plane_3, 0> (points_on_plane);
std::cerr << "[Testing line fitting on Tetrahedron_3 objects]" << std::endl;
test<Tetrahedron_3, Line_3, 3> (points_on_line);
test<Tetrahedron_3, Line_3, 2> (points_on_line);
test<Tetrahedron_3, Line_3, 1> (points_on_line);
test<Tetrahedron_3, Line_3, 0> (points_on_line);
std::cerr << "[Testing plane fitting on Tetrahedron_3 objects]" << std::endl;
test<Tetrahedron_3, Plane_3, 3> (points_on_plane);
test<Tetrahedron_3, Plane_3, 2> (points_on_plane);
test<Tetrahedron_3, Plane_3, 1> (points_on_plane);
test<Tetrahedron_3, Plane_3, 0> (points_on_plane);
std::cerr << "[Testing line fitting on Iso_cuboid_3 objects]" << std::endl;
test<Iso_cuboid_3, Line_3, 3> (points_on_line);
test<Iso_cuboid_3, Line_3, 2> (points_on_line);
test<Iso_cuboid_3, Line_3, 1> (points_on_line);
test<Iso_cuboid_3, Line_3, 0> (points_on_line);
std::cerr << "[Testing plane fitting on Iso_cuboid_3 objects]" << std::endl;
test<Iso_cuboid_3, Plane_3, 3> (points_on_plane);
test<Iso_cuboid_3, Plane_3, 2> (points_on_plane);
test<Iso_cuboid_3, Plane_3, 1> (points_on_plane);
test<Iso_cuboid_3, Plane_3, 0> (points_on_plane);
return EXIT_SUCCESS;
}