Merge pull request #367 from sgiraudot/Point_set_processing-hierarchical_clustering-sgiraudot

New point set processing algorithm: Hierarchical clustering
This commit is contained in:
Laurent Rineau 2015-10-14 10:54:37 +02:00
commit 18cac48bae
14 changed files with 757 additions and 67 deletions

View File

@ -1550,6 +1550,15 @@ ABSTRACT = {We present the first complete, exact and efficient C++ implementatio
} }
@inproceedings{cgal:pgk-esops-02,
title={Efficient simplification of point-sampled surfaces},
author={Pauly, Mark and Gross, Markus and Kobbelt, Leif P},
booktitle={Proceedings of the conference on Visualization'02},
pages={163--170},
year={2002},
organization={IEEE Computer Society}
}
@article{ cgal:pp-cdmsc-93, @article{ cgal:pp-cdmsc-93,
author = "U. Pinkall and K. Polthier", author = "U. Pinkall and K. Polthier",
title = "Computing discrete minimal surfaces and their conjugates", title = "Computing discrete minimal surfaces and their conjugates",

View File

@ -154,6 +154,13 @@ and <code>src/</code> directories).
or <code>CGAL::Parallel_tag</code> when calling one of these functions.</li> or <code>CGAL::Parallel_tag</code> when calling one of these functions.</li>
<li> <code>CGAL::Parallel_tag</code> can no longer be used in <li> <code>CGAL::Parallel_tag</code> can no longer be used in
Point Set Processing algorithms if TBB is not available.</li> Point Set Processing algorithms if TBB is not available.</li>
<li>
Add a new simplification algorithm based on hierarchical
clustering: <code>CGAL::hierarchy_simplify_point_set()</code>. It
allows either to uniformly simplify the point set or to
automatically adapt the local density of points to the local
variation of the input computed by principal component analysis.
</li>
</ul> </ul>
<h3>Surface Mesh Parameterization</h3> <h3>Surface Mesh Parameterization</h3>
<ul> <ul>

View File

@ -30,6 +30,7 @@
- `CGAL::remove_outliers()` - `CGAL::remove_outliers()`
- `CGAL::grid_simplify_point_set()` - `CGAL::grid_simplify_point_set()`
- `CGAL::random_simplify_point_set()` - `CGAL::random_simplify_point_set()`
- `CGAL::hierarchy_simplify_point_set()`
- `CGAL::wlop_simplify_and_regularize_point_set()` - `CGAL::wlop_simplify_and_regularize_point_set()`
- `CGAL::jet_smooth_point_set()` - `CGAL::jet_smooth_point_set()`
- `CGAL::bilateral_smooth_point_set()` - `CGAL::bilateral_smooth_point_set()`

View File

@ -138,7 +138,7 @@ functions in this component.)
\section Point_set_processing_3Simplification Simplification \section Point_set_processing_3Simplification Simplification
Three simplification functions are devised to reduce an input point set. Four simplification functions are devised to reduce an input point set.
Function `random_simplify_point_set()` randomly deletes a Function `random_simplify_point_set()` randomly deletes a
user-specified fraction of points from the input point set. This user-specified fraction of points from the input point set. This
@ -150,6 +150,12 @@ points sharing the same cell of the grid by picking as representant
one arbitrarily chosen point. This algorithm is slower than one arbitrarily chosen point. This algorithm is slower than
`random_simplify_point_set()`. `random_simplify_point_set()`.
Function `hierarchy_simplify_point_set()` provides an adaptive
simplification of the point set through local clusters
\cgalCite{cgal:pgk-esops-02}. The size of the clusters is either
directly selected by the user or it automatically adapts to the local
variation of the point set.
Function `wlop_simplify_and_regularize_point_set()` not only simplifies, Function `wlop_simplify_and_regularize_point_set()` not only simplifies,
but also regularizes downsampled points. This is an implementation of but also regularizes downsampled points. This is an implementation of
the Weighted Locally Optimal Projection (WLOP) algorithm \cgalCite{wlop-2009}. the Weighted Locally Optimal Projection (WLOP) algorithm \cgalCite{wlop-2009}.
@ -164,6 +170,40 @@ The following example reads a point set and simplifies it by clustering.
Point set simplification through grid-based clustering. Removed points are depicted in red. Notice how low-density areas (in green) are not simplified. Point set simplification through grid-based clustering. Removed points are depicted in red. Notice how low-density areas (in green) are not simplified.
\cgalFigureEnd \cgalFigureEnd
\subsection Point_set_processing_3Example_9 Hierarchy Simplification Example
The following example reads a point set and produces a set of clusters.
\cgalExample{Point_set_processing_3/hierarchy_simplification_example.cpp}
\subsubsection Point_set_processing_3Hierarchy_simplification_parameter_size Parameter: size
The hierarchy simplification algorithm recursively split the point set
in two until each cluster's size is less than the parameter `size`.
\cgalFigureBegin{Point_set_processing_3figHierarchy_simplification_size, hierarchical_clustering_size.jpg}
Input point set and hierarchy simplification with different `size`
parameter: \f$10\f$, \f$100\f$ and \f$1000\f$. In the 3 cases,
`var_max`\f$=1/3\f$. \cgalFigureEnd
\subsubsection Point_set_processing_3Hierarchy_simplification_parameter_var_max Parameter: var_max
In addition to the size parameter, a variation parameter allows to
increase simplification in monotoneous regions. For each cluster, a
surface variation measure is computed using the sorted eigenvalues of
the covariance matrix: \f[ \sigma(p) = \frac{\lambda_0}{\lambda_0 +
\lambda_1 + \lambda_2}. \f]
This function goes from \f$0\f$ if the cluster is coplanar to
\f$1/3\f$ if it is fully isotropic. If a cluster's variation is above
`var_max`, it is splitted. If `var_max` is equal to \f$1/3\f$, this
parameter has no effect and the clustering is regular on the whole
point set.
\cgalFigureBegin{Point_set_processing_3figHierarchical_clustering_var_max, hierarchical_clustering_var_max.jpg}
Input point set and hierarchy simplification with different `var_max`
parameter: \f$0.00001\f$, \f$0.001\f$ and \f$0.1\f$. In the 3 cases,
`size`\f$=1000\f$. \cgalFigureEnd
\subsection Point_set_processing_3Example_4 WLOP Simplification Example \subsection Point_set_processing_3Example_4 WLOP Simplification Example
The following example reads a point set, simplifies and regularizes it by WLOP. The following example reads a point set, simplifies and regularizes it by WLOP.
@ -199,6 +239,7 @@ for more details. We provide below a speed-up chart generated using the parallel
Parallel WLOP speed-up, compared to the sequential version of the algorithm. Parallel WLOP speed-up, compared to the sequential version of the algorithm.
\cgalFigureEnd \cgalFigureEnd
\section Point_set_processing_3Smoothing Smoothing \section Point_set_processing_3Smoothing Smoothing
Two smoothing functions are devised to smooth an input point set. Two smoothing functions are devised to smooth an input point set.

View File

@ -4,6 +4,7 @@
\example Point_set_processing_3/remove_outliers_example.cpp \example Point_set_processing_3/remove_outliers_example.cpp
\example Point_set_processing_3/grid_simplification_example.cpp \example Point_set_processing_3/grid_simplification_example.cpp
\example Point_set_processing_3/grid_simplify_indices.cpp \example Point_set_processing_3/grid_simplify_indices.cpp
\example Point_set_processing_3/hierarchy_simplification_example.cpp
\example Point_set_processing_3/jet_smoothing_example.cpp \example Point_set_processing_3/jet_smoothing_example.cpp
\example Point_set_processing_3/normals_example.cpp \example Point_set_processing_3/normals_example.cpp
\example Point_set_processing_3/wlop_simplify_and_regularize_point_set_example.cpp \example Point_set_processing_3/wlop_simplify_and_regularize_point_set_example.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -57,6 +57,7 @@ if ( CGAL_FOUND )
create_single_source_cgal_program( "bilateral_smooth_point_set_example.cpp" ) create_single_source_cgal_program( "bilateral_smooth_point_set_example.cpp" )
create_single_source_cgal_program( "grid_simplification_example.cpp" ) create_single_source_cgal_program( "grid_simplification_example.cpp" )
create_single_source_cgal_program( "grid_simplify_indices.cpp" ) create_single_source_cgal_program( "grid_simplify_indices.cpp" )
create_single_source_cgal_program( "hierarchy_simplification_example.cpp" )
create_single_source_cgal_program( "normals_example.cpp" ) create_single_source_cgal_program( "normals_example.cpp" )
create_single_source_cgal_program( "property_map.cpp" ) create_single_source_cgal_program( "property_map.cpp" )
create_single_source_cgal_program( "random_simplification_example.cpp" ) create_single_source_cgal_program( "random_simplification_example.cpp" )

View File

@ -0,0 +1,48 @@
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/hierarchy_simplify_point_set.h>
#include <CGAL/IO/read_xyz_points.h>
#include <CGAL/IO/write_xyz_points.h>
#include <CGAL/Timer.h>
#include <CGAL/Memory_sizer.h>
#include <vector>
#include <fstream>
// types
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::Point_3 Point;
int main(int argc, char*argv[])
{
// Reads a .xyz point set file in points[].
std::vector<Point> points;
const char* fname = (argc>1)?argv[1]:"data/oni.xyz";
std::ifstream stream(fname);
if (!stream ||
!CGAL::read_xyz_points(stream, std::back_inserter(points)))
{
std::cerr << "Error: cannot read file " << fname << std::endl;
return EXIT_FAILURE;
}
std::cout << "Read " << points.size () << " point(s)" << std::endl;
CGAL::Timer task_timer; task_timer.start();
// simplification by clustering using erase-remove idiom
points.erase (CGAL::hierarchy_simplify_point_set (points.begin (), points.end (),
100, // Max cluster size
0.01), // Max surface variation
points.end ());
std::size_t memory = CGAL::Memory_sizer().virtual_size();
std::cout << points.size () << " point(s) kept, computed in "
<< task_timer.time() << " seconds, "
<< (memory>>20) << " Mib allocated." << std::endl;
std::ofstream f ("out.xyz");
CGAL::write_xyz_points (f, points.begin (), points.end ());
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,380 @@
// Copyright (c) 2012 INRIA Sophia-Antipolis (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
// You can redistribute it and/or modify it under the terms of the GNU
// General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// Licensees holding a valid commercial license may use this file in
// accordance with the commercial license agreement provided with the software.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//
// $URL:
// $Id:
//
// Author(s) : Simon Giraudot, Pierre Alliez
#ifndef HIERARCHY_SIMPLIFY_POINT_SET_H
#define HIERARCHY_SIMPLIFY_POINT_SET_H
#include <cmath>
#include <stack>
#include <CGAL/property_map.h>
#include <CGAL/basic.h>
#include <CGAL/Dimension.h>
#include <CGAL/Object.h>
#include <CGAL/centroid.h>
#include <CGAL/point_set_processing_assertions.h>
#include <CGAL/Default_diagonalize_traits.h>
#include <CGAL/PCA_util.h>
namespace CGAL {
namespace internal {
template < typename InputIterator,
typename PointPMap,
typename K >
typename K::Point_3
hsps_centroid(InputIterator begin,
InputIterator end,
PointPMap& point_pmap,
const K&)
{
typedef typename K::Point_3 Point;
typedef typename K::FT FT;
CGAL_precondition(begin != end);
FT x = (FT)0., y = (FT)0., z = (FT)0.;
unsigned int nb_pts = 0;
while(begin != end)
{
#ifdef CGAL_USE_PROPERTY_MAPS_API_V1
const Point& point = get(point_pmap, begin);
#else
const Point& point = get(point_pmap, *begin);
#endif
x += point.x (); y += point.y (); z += point.z ();
++ nb_pts;
++ begin;
}
return Point (x/nb_pts, y/nb_pts, z/nb_pts);
}
template < typename Input_type,
typename PointPMap,
typename K >
void
hsc_terminate_cluster (std::list<Input_type>& cluster,
std::list<Input_type>& points_to_keep,
std::list<Input_type>& points_to_remove,
PointPMap& point_pmap,
const typename K::Point_3& centroid,
const K&)
{
typedef typename std::list<Input_type>::iterator Iterator;
typedef typename K::FT FT;
typedef typename K::Point_3 Point;
FT dist_min = (std::numeric_limits<FT>::max)();
typename std::list<Input_type>::iterator point_min;
for (Iterator it = cluster.begin (); it != cluster.end (); ++ it)
{
#ifdef CGAL_USE_PROPERTY_MAPS_API_V1
const Point& point = get(point_pmap, it);
#else
const Point& point = get(point_pmap, *it);
#endif
FT dist = CGAL::squared_distance (point, centroid);
if (dist < dist_min)
{
dist_min = dist;
point_min = it;
}
}
points_to_keep.splice (points_to_keep.end (), cluster, point_min);
points_to_remove.splice (points_to_remove.end (), cluster, cluster.begin (), cluster.end ());
}
} // namespace internal
/// \ingroup PkgPointSetProcessing
/// Recursively split the point set in smaller clusters until the
/// clusters have less than `size` elements or until their variation
/// factor is below `var_max`.
///
/// This method modifies the order of input points so as to pack all remaining points first,
/// and returns an iterator over the first point to remove (see erase-remove idiom).
/// For this reason it should not be called on sorted containers.
///
/// \pre `0 < var_max < 1/3`
/// \pre `size > 0`
///
/// @tparam ForwardIterator iterator over input points.
/// @tparam PointPMap is a model of `ReadablePropertyMap` with value type `Point_3<Kernel>`.
/// It can be omitted if the value type of `ForwardIterator` is convertible to `Point_3<Kernel>`.
/// @tparam DiagonalizeTraits is a model of `DiagonalizeTraits`. It
/// can be omitted: if Eigen 3 (or greater) is available and
/// `CGAL_EIGEN3_ENABLED` is defined then an overload using
/// `Eigen_diagonalize_traits` is provided. Otherwise, the internal
/// implementation `Internal_diagonalize_traits` is used.
/// @tparam Kernel Geometric traits class.
/// It can be omitted and deduced automatically from the value type of `PointPMap`.
///
/// @return iterator over the first point to remove.
// This variant requires all parameters.
template <typename ForwardIterator,
typename PointPMap,
typename DiagonalizeTraits,
typename Kernel>
ForwardIterator hierarchy_simplify_point_set (ForwardIterator begin,
ForwardIterator end,
PointPMap point_pmap,
const unsigned int size,
const double var_max,
const DiagonalizeTraits&,
const Kernel&)
{
typedef typename std::iterator_traits<ForwardIterator>::value_type Input_type;
typedef typename Kernel::FT FT;
typedef typename Kernel::Point_3 Point;
typedef typename Kernel::Vector_3 Vector;
// We define a cluster as a point set + its centroid (useful for
// faster computations of centroids - to be implemented)
typedef std::pair< std::list<Input_type>, Point > cluster;
std::list<cluster> clusters_stack;
typedef typename std::list<cluster>::iterator cluster_iterator;
CGAL_precondition (begin != end);
CGAL_point_set_processing_precondition (size > 0);
CGAL_point_set_processing_precondition (var_max > 0.0);
// The first cluster is the whole input point set
clusters_stack.push_front (cluster (std::list<Input_type>(), Point (0., 0., 0.)));
std::copy (begin, end, std::back_inserter (clusters_stack.front ().first));
clusters_stack.front ().second = internal::hsps_centroid (clusters_stack.front ().first.begin (),
clusters_stack.front ().first.end (),
point_pmap, Kernel());
std::list<Input_type> points_to_keep;
std::list<Input_type> points_to_remove;
while (!(clusters_stack.empty ()))
{
cluster_iterator current_cluster = clusters_stack.begin ();
// If the cluster only has 1 element, we add it to the list of
// output points
if (current_cluster->first.size () == 1)
{
points_to_keep.splice (points_to_keep.end (), current_cluster->first,
current_cluster->first.begin ());
clusters_stack.pop_front ();
continue;
}
// Compute the covariance matrix of the set
cpp11::array<FT, 6> covariance = {{ 0., 0., 0., 0., 0., 0. }};
for (typename std::list<Input_type>::iterator it = current_cluster->first.begin ();
it != current_cluster->first.end (); ++ it)
{
#ifdef CGAL_USE_PROPERTY_MAPS_API_V1
const Point& point = get(point_pmap, it);
#else
const Point& point = get(point_pmap, *it);
#endif
Vector d = point - current_cluster->second;
covariance[0] += d.x () * d.x ();
covariance[1] += d.x () * d.y ();
covariance[2] += d.x () * d.z ();
covariance[3] += d.y () * d.y ();
covariance[4] += d.y () * d.z ();
covariance[5] += d.z () * d.z ();
}
cpp11::array<FT, 3> eigenvalues = {{ 0., 0., 0. }};
cpp11::array<FT, 9> eigenvectors = {{ 0., 0., 0.,
0., 0., 0.,
0., 0., 0. }};
// Linear algebra = get eigenvalues and eigenvectors for
// PCA-like analysis
DiagonalizeTraits::diagonalize_selfadjoint_covariance_matrix
(covariance, eigenvalues, eigenvectors);
// Variation of the set defined as lambda_min / (lambda_0 + lambda_1 + lambda_2)
double var = eigenvalues[0] / (eigenvalues[0] + eigenvalues[1] + eigenvalues[2]);
// Split the set if size OR variance of the cluster is too large
if (current_cluster->first.size () > size || var > var_max)
{
clusters_stack.push_front (cluster (std::list<Input_type>(), Point (0., 0., 0.)));
cluster_iterator negative_side = clusters_stack.begin ();
// positive_side is built directly from current_cluster
// The plane which splits the point set into 2 point sets:
// * Normal to the eigenvector with highest eigenvalue
// * Passes through the centroid of the set
Vector v (eigenvectors[6], eigenvectors[7], eigenvectors[8]);
std::size_t current_cluster_size = 0;
typename std::list<Input_type>::iterator it = current_cluster->first.begin ();
while (it != current_cluster->first.end ())
{
typename std::list<Input_type>::iterator current = it ++;
// Test if point is on negative side of plane and
// transfer it to the negative_side cluster if it is
if (Vector (current_cluster->second, *current) * v < 0)
negative_side->first.splice (negative_side->first.end (),
current_cluster->first, current);
++ current_cluster_size;
}
// If one of the clusters is empty, stop to avoid infinite
// loop and keep the non-empty one
if (current_cluster->first.empty () || negative_side->first.empty ())
{
cluster_iterator nonempty = (current_cluster->first.empty ()
? negative_side : current_cluster);
// Compute the centroid
nonempty->second = internal::hsps_centroid (nonempty->first.begin (),
nonempty->first.end (),
point_pmap, Kernel());
internal::hsc_terminate_cluster (nonempty->first,
points_to_keep,
points_to_remove,
point_pmap,
nonempty->second,
Kernel ());
clusters_stack.pop_front ();
clusters_stack.pop_front ();
}
else
{
// Save old centroid for faster computation
Point old_centroid = current_cluster->second;
// Compute the first centroid
current_cluster->second = internal::hsps_centroid (current_cluster->first.begin (),
current_cluster->first.end (),
point_pmap, Kernel());
// The second centroid can be computed with the first and
// the old ones :
// centroid_neg = (n_total * old_centroid - n_pos * first_centroid)
// / n_neg;
negative_side->second = Point ((current_cluster_size * old_centroid.x ()
- current_cluster->first.size () * current_cluster->second.x ())
/ negative_side->first.size (),
(current_cluster_size * old_centroid.y ()
- current_cluster->first.size () * current_cluster->second.y ())
/ negative_side->first.size (),
(current_cluster_size * old_centroid.z ()
- current_cluster->first.size () * current_cluster->second.z ())
/ negative_side->first.size ());
}
}
// If the size/variance are small enough, add the centroid as
// and output point
else
{
internal::hsc_terminate_cluster (current_cluster->first,
points_to_keep,
points_to_remove,
point_pmap,
current_cluster->second,
Kernel ());
clusters_stack.pop_front ();
}
}
ForwardIterator first_point_to_remove =
std::copy (points_to_keep.begin(), points_to_keep.end(), begin);
std::copy (points_to_remove.begin(), points_to_remove.end(), first_point_to_remove);
return first_point_to_remove;
}
/// @endcond
/// @cond SKIP_IN_MANUAL
// This variant deduces the kernel from the iterator type.
template <typename ForwardIterator,
typename PointPMap,
typename DiagonalizeTraits>
ForwardIterator hierarchy_simplify_point_set (ForwardIterator begin,
ForwardIterator end,
PointPMap point_pmap,
const unsigned int size,
const double var_max,
const DiagonalizeTraits& diagonalize_traits)
{
typedef typename boost::property_traits<PointPMap>::value_type Point;
typedef typename Kernel_traits<Point>::Kernel Kernel;
return hierarchy_simplify_point_set (begin, end, point_pmap, size, var_max,
diagonalize_traits, Kernel());
}
/// @endcond
/// @cond SKIP_IN_MANUAL
// This variant uses default diagonalize traits
template <typename ForwardIterator,
typename PointPMap >
ForwardIterator hierarchy_simplify_point_set (ForwardIterator begin,
ForwardIterator end,
PointPMap point_pmap,
const unsigned int size,
const double var_max)
{
typedef typename boost::property_traits<PointPMap>::value_type Point;
typedef typename Kernel_traits<Point>::Kernel Kernel;
return hierarchy_simplify_point_set (begin, end, point_pmap, size, var_max,
Default_diagonalize_traits<double, 3> (), Kernel());
}
/// @endcond
/// @cond SKIP_IN_MANUAL
// This variant creates a default point property map = Identity_property_map.
template <typename ForwardIterator >
ForwardIterator hierarchy_simplify_point_set (ForwardIterator begin,
ForwardIterator end,
const unsigned int size = 10,
const double var_max = 0.333)
{
return hierarchy_simplify_point_set
(begin, end,
#ifdef CGAL_USE_PROPERTY_MAPS_API_V1
make_dereference_property_map(first),
#else
make_identity_property_map (typename std::iterator_traits<ForwardIterator>::value_type()),
#endif
size, var_max);
}
/// @endcond
} // namespace CGAL
#endif // HIERARCHY_SIMPLIFY_POINT_SET_H

View File

@ -71,6 +71,7 @@ if ( CGAL_FOUND )
if(EIGEN3_FOUND OR LAPACK_FOUND) if(EIGEN3_FOUND OR LAPACK_FOUND)
# Executables that require Eigen or BLAS and LAPACK # Executables that require Eigen or BLAS and LAPACK
create_single_source_cgal_program( "normal_estimation_test.cpp" ) create_single_source_cgal_program( "normal_estimation_test.cpp" )
create_single_source_cgal_program( "hierarchy_simplification_test.cpp" )
create_single_source_cgal_program( "smoothing_test.cpp" ) create_single_source_cgal_program( "smoothing_test.cpp" )
create_single_source_cgal_program( "vcm_plane_test.cpp" ) create_single_source_cgal_program( "vcm_plane_test.cpp" )
create_single_source_cgal_program( "vcm_all_test.cpp" ) create_single_source_cgal_program( "vcm_all_test.cpp" )

View File

@ -0,0 +1,90 @@
#include <limits>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/hierarchy_simplify_point_set.h>
#include <CGAL/IO/read_xyz_points.h>
#include <CGAL/IO/write_xyz_points.h>
#include <CGAL/Timer.h>
#include <CGAL/Memory_sizer.h>
#include <vector>
#include <fstream>
// types
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::Point_3 Point;
typedef Kernel::FT FT;
void test (std::vector<Point>& input,
int result0 = 1, int result1 = 1, int result2 = 1, int result3 = 1, int result4 = 1)
{
std::vector<Point>::iterator it =
CGAL::hierarchy_simplify_point_set (input.begin (), input.end (), 1);
if (result0 > 0 && std::distance (input.begin (), it) != (result0))
exit (EXIT_FAILURE);
it = CGAL::hierarchy_simplify_point_set (input.begin (), input.end ());
if (result1 > 0 && std::distance (input.begin (), it) != (result1))
exit (EXIT_FAILURE);
it = CGAL::hierarchy_simplify_point_set (input.begin (), input.end (), 100);
if (result2 > 0 && std::distance (input.begin (), it) != (result2))
exit (EXIT_FAILURE);
it = CGAL::hierarchy_simplify_point_set (input.begin (), input.end (), 1000, 0.1);
if (result3 > 0 && std::distance (input.begin (), it) != (result3))
exit (EXIT_FAILURE);
it = CGAL::hierarchy_simplify_point_set (input.begin (), input.end (),
CGAL::Identity_property_map<Point>(),
(std::numeric_limits<unsigned int>::max)(),
0.0001);
if (result4 > 0 && std::distance (input.begin (), it) != (result4))
exit (EXIT_FAILURE);
input.clear ();
}
int main(void)
{
std::vector<Point> input;
// Test 1 point
input.push_back (Point (0., 0., 0.));
test (input);
// Test twice the same point
input.push_back (Point (0., 0., 0.));
input.push_back (Point (0., 0., 0.));
test (input);
// Test 2 points
input.push_back (Point (0., 0., 0.));
input.push_back (Point (1., 0., 0.));
test (input, 2);
// Test line
for (std::size_t i = 0; i < 1000; ++ i)
input.push_back (Point (0., 0., i));
test (input, input.size (), 128, 16, 1, 1);
// Test plane
for (std::size_t i = 0; i < 128; ++ i)
for (std::size_t j = 0; j < 128; ++ j)
input.push_back (Point (0., j, i));
test (input, input.size (), 2048, 256, 32, 1);
// Test random
for (std::size_t i = 0; i < 10000; ++ i)
input.push_back (Point (rand() / (FT)RAND_MAX,
rand() / (FT)RAND_MAX,
rand() / (FT)RAND_MAX));
test (input, input.size (), -1, -1, -1, -1);
return EXIT_SUCCESS;
}

View File

@ -5,6 +5,7 @@
#include <CGAL/grid_simplify_point_set.h> #include <CGAL/grid_simplify_point_set.h>
#include <CGAL/random_simplify_point_set.h> #include <CGAL/random_simplify_point_set.h>
#include <CGAL/hierarchy_simplify_point_set.h>
#include <CGAL/compute_average_spacing.h> #include <CGAL/compute_average_spacing.h>
#include <CGAL/Timer.h> #include <CGAL/Timer.h>
#include <CGAL/Memory_sizer.h> #include <CGAL/Memory_sizer.h>
@ -55,6 +56,7 @@ public:
public Q_SLOTS: public Q_SLOTS:
void on_actionSimplify_triggered(); void on_actionSimplify_triggered();
}; // end Polyhedron_demo_point_set_simplification_plugin }; // end Polyhedron_demo_point_set_simplification_plugin
class Point_set_demo_point_set_simplification_dialog : public QDialog, private Ui::PointSetSimplificationDialog class Point_set_demo_point_set_simplification_dialog : public QDialog, private Ui::PointSetSimplificationDialog
@ -66,9 +68,44 @@ class Point_set_demo_point_set_simplification_dialog : public QDialog, private U
setupUi(this); setupUi(this);
} }
QString simplificationMethod() const { return m_simplificationMethod->currentText(); } unsigned int simplificationMethod() const
{
if (Random->isChecked())
return 0;
else if (Grid->isChecked())
return 1;
else
return 2;
}
double randomSimplificationPercentage() const { return m_randomSimplificationPercentage->value(); } double randomSimplificationPercentage() const { return m_randomSimplificationPercentage->value(); }
double gridCellSize() const { return m_gridCellSize->value(); } double gridCellSize() const { return m_gridCellSize->value(); }
unsigned int maximumClusterSize() const { return m_maximumClusterSize->value(); }
double maximumSurfaceVariation() const { return m_maximumSurfaceVariation->value(); }
public Q_SLOTS:
void on_Random_toggled (bool toggled)
{
m_randomSimplificationPercentage->setEnabled (toggled);
m_gridCellSize->setEnabled (!toggled);
m_maximumClusterSize->setEnabled (!toggled);
m_maximumSurfaceVariation->setEnabled (!toggled);
}
void on_Grid_toggled (bool toggled)
{
m_randomSimplificationPercentage->setEnabled (!toggled);
m_gridCellSize->setEnabled (toggled);
m_maximumClusterSize->setEnabled (!toggled);
m_maximumSurfaceVariation->setEnabled (!toggled);
}
void on_Hierarchy_toggled (bool toggled)
{
m_randomSimplificationPercentage->setEnabled (!toggled);
m_gridCellSize->setEnabled (!toggled);
m_maximumClusterSize->setEnabled (toggled);
m_maximumSurfaceVariation->setEnabled (toggled);
}
}; };
void Polyhedron_demo_point_set_simplification_plugin::on_actionSimplify_triggered() void Polyhedron_demo_point_set_simplification_plugin::on_actionSimplify_triggered()
@ -97,18 +134,19 @@ void Polyhedron_demo_point_set_simplification_plugin::on_actionSimplify_triggere
// First point to delete // First point to delete
Point_set::iterator first_point_to_remove = points->end(); Point_set::iterator first_point_to_remove = points->end();
if (dialog.simplificationMethod() == "Random") unsigned int method = dialog.simplificationMethod ();
if (method == 0)
{ {
std::cerr << "Random point cloud simplification (" << dialog.randomSimplificationPercentage() <<"%)...\n"; std::cerr << "Point set random simplification (" << dialog.randomSimplificationPercentage() <<"%)...\n";
// Computes points to remove by random simplification // Computes points to remove by random simplification
first_point_to_remove = first_point_to_remove =
CGAL::random_simplify_point_set(points->begin(), points->end(), CGAL::random_simplify_point_set(points->begin(), points->end(),
dialog.randomSimplificationPercentage()); dialog.randomSimplificationPercentage());
} }
else if (dialog.simplificationMethod() == "Grid Clustering") else if (method == 1)
{ {
std::cerr << "Point cloud simplification by clustering (cell size = " << dialog.gridCellSize() <<" * average spacing)...\n"; std::cerr << "Point set grid simplification (cell size = " << dialog.gridCellSize() <<" * average spacing)...\n";
// Computes average spacing // Computes average spacing
double average_spacing = CGAL::compute_average_spacing<Concurrency_tag>( double average_spacing = CGAL::compute_average_spacing<Concurrency_tag>(
@ -120,6 +158,17 @@ void Polyhedron_demo_point_set_simplification_plugin::on_actionSimplify_triggere
CGAL::grid_simplify_point_set(points->begin(), points->end(), CGAL::grid_simplify_point_set(points->begin(), points->end(),
dialog.gridCellSize()*average_spacing); dialog.gridCellSize()*average_spacing);
} }
else
{
std::cerr << "Point set hierarchy simplification (cluster size = " << dialog.maximumClusterSize()
<< ", maximum variation = " << dialog.maximumSurfaceVariation() << ")...\n";
// Computes points to remove by Grid Clustering
first_point_to_remove =
CGAL::hierarchy_simplify_point_set(points->begin(), points->end(),
dialog.maximumClusterSize(),
dialog.maximumSurfaceVariation());
}
std::size_t nb_points_to_remove = std::distance(first_point_to_remove, points->end()); std::size_t nb_points_to_remove = std::distance(first_point_to_remove, points->end());
std::size_t memory = CGAL::Memory_sizer().virtual_size(); std::size_t memory = CGAL::Memory_sizer().virtual_size();

View File

@ -1,104 +1,166 @@
<ui version="4.0" > <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PointSetSimplificationDialog</class> <class>PointSetSimplificationDialog</class>
<widget class="QDialog" name="PointSetSimplificationDialog" > <widget class="QDialog" name="PointSetSimplificationDialog">
<property name="geometry" > <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>403</width> <width>450</width>
<height>153</height> <height>251</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle">
<string>Simplification</string> <string>Simplification</string>
</property> </property>
<layout class="QGridLayout" > <layout class="QGridLayout">
<item row="0" column="0" > <item row="7" column="0">
<widget class="QLabel" name="label" > <widget class="QLabel" name="label_4">
<property name="text" > <property name="text">
<string>Method:</string> <string>Maximum cluster size</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1" > <item row="9" column="0" colspan="2">
<widget class="QComboBox" name="m_simplificationMethod" > <widget class="QDialogButtonBox" name="buttonBox">
<item> <property name="orientation">
<property name="text" > <enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QRadioButton" name="Random">
<property name="text">
<string>Random</string> <string>Random</string>
</property> </property>
</item> <property name="checked">
<item> <bool>true</bool>
<property name="text" >
<string>Grid Clustering</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>Points to Remove Randomly</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1" > <item row="4" column="0">
<widget class="QDoubleSpinBox" name="m_randomSimplificationPercentage" > <widget class="QRadioButton" name="Grid">
<property name="suffix" > <property name="text">
<string>Grid</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="m_randomSimplificationPercentage">
<property name="suffix">
<string> %</string> <string> %</string>
</property> </property>
<property name="decimals" > <property name="decimals">
<number>2</number> <number>2</number>
</property> </property>
<property name="minimum" > <property name="minimum">
<double>0.100000000000000</double> <double>0.100000000000000</double>
</property> </property>
<property name="maximum" > <property name="maximum">
<double>100.000000000000000</double> <double>100.000000000000000</double>
</property> </property>
<property name="singleStep" > <property name="singleStep">
<double>0.100000000000000</double> <double>0.100000000000000</double>
</property> </property>
<property name="value" > <property name="value">
<double>50.000000000000000</double> <double>50.000000000000000</double>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" > <item row="6" column="0">
<widget class="QLabel" name="label_3" > <widget class="QRadioButton" name="Hierarchy">
<property name="text" > <property name="text">
<string>Grid Cell Size</string> <string>Hierarchy</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1" > <item row="5" column="1">
<widget class="QDoubleSpinBox" name="m_gridCellSize" > <widget class="QDoubleSpinBox" name="m_gridCellSize">
<property name="suffix" > <property name="enabled">
<bool>false</bool>
</property>
<property name="suffix">
<string> * average spacing</string> <string> * average spacing</string>
</property> </property>
<property name="decimals" > <property name="decimals">
<number>2</number> <number>2</number>
</property> </property>
<property name="minimum" > <property name="minimum">
<double>0.100000000000000</double> <double>0.100000000000000</double>
</property> </property>
<property name="maximum" > <property name="maximum">
<double>10.000000000000000</double> <double>10.000000000000000</double>
</property> </property>
<property name="singleStep" > <property name="singleStep">
<double>0.100000000000000</double> <double>0.100000000000000</double>
</property> </property>
<property name="value" > <property name="value">
<double>1.000000000000000</double> <double>1.000000000000000</double>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2" > <item row="3" column="0">
<widget class="QDialogButtonBox" name="buttonBox" > <widget class="QLabel" name="label_2">
<property name="orientation" > <property name="text">
<enum>Qt::Horizontal</enum> <string>Points to Remove Randomly</string>
</property> </property>
<property name="standardButtons" > </widget>
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set> </item>
<item row="5" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Grid Cell Size</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QSpinBox" name="m_maximumClusterSize">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>2147483647</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Maximum surface variation</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QDoubleSpinBox" name="m_maximumSurfaceVariation">
<property name="enabled">
<bool>false</bool>
</property>
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>5</number>
</property>
<property name="minimum">
<double>0.000010000000000</double>
</property>
<property name="maximum">
<double>0.333330000000000</double>
</property>
<property name="singleStep">
<double>0.012340000000000</double>
</property>
<property name="value">
<double>0.333330000000000</double>
</property> </property>
</widget> </widget>
</item> </item>
@ -112,11 +174,11 @@
<receiver>PointSetSimplificationDialog</receiver> <receiver>PointSetSimplificationDialog</receiver>
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>248</x> <x>248</x>
<y>254</y> <y>254</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>157</x> <x>157</x>
<y>274</y> <y>274</y>
</hint> </hint>
@ -128,11 +190,11 @@
<receiver>PointSetSimplificationDialog</receiver> <receiver>PointSetSimplificationDialog</receiver>
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>316</x> <x>316</x>
<y>260</y> <y>260</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>286</x> <x>286</x>
<y>274</y> <y>274</y>
</hint> </hint>