dust3d/thirdparty/cgal/CGAL-5.1/include/CGAL/bilateral_smooth_point_set.h

463 lines
17 KiB
C
Raw Normal View History

2020-10-13 12:44:25 +00:00
// Copyright (c) 2013-06 INRIA Sophia-Antipolis (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v5.1/Point_set_processing_3/include/CGAL/bilateral_smooth_point_set.h $
// $Id: bilateral_smooth_point_set.h c253679 2020-04-18T16:27:58+02:00 Sébastien Loriot
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s) : Shihao Wu, Clement Jamin, Pierre Alliez
#ifndef CGAL_BILATERAL_SMOOTH_POINT_SET_H
#define CGAL_BILATERAL_SMOOTH_POINT_SET_H
#include <CGAL/license/Point_set_processing_3.h>
#include <CGAL/disable_warnings.h>
#include <CGAL/number_type_config.h>
#include <CGAL/Point_set_processing_3/internal/Neighbor_query.h>
#include <CGAL/Point_set_processing_3/internal/Callback_wrapper.h>
#include <CGAL/for_each.h>
#include <CGAL/property_map.h>
#include <CGAL/point_set_processing_assertions.h>
#include <CGAL/squared_distance_3.h>
#include <functional>
#include <CGAL/boost/graph/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>
#include <boost/iterator/zip_iterator.hpp>
#include <iterator>
#include <set>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <CGAL/Real_timer.h>
#include <CGAL/Memory_sizer.h>
#include <CGAL/property_map.h>
//#define CGAL_PSP3_VERBOSE
namespace CGAL {
// ----------------------------------------------------------------------------
// Private section
// ----------------------------------------------------------------------------
/// \cond SKIP_IN_MANUAL
namespace bilateral_smooth_point_set_internal{
/// Compute bilateral projection for each point
/// according to their KNN neighborhood points
///
/// \pre `k >= 2`, radius > 0 , sharpness_angle > 0 && sharpness_angle < 90
///
/// @tparam Kernel Geometric traits class.
/// @tparam Tree KD-tree.
///
/// @return
template <typename Kernel, typename PointRange,
typename PointMap, typename VectorMap>
std::pair<typename Kernel::Point_3, typename Kernel::Vector_3>
compute_denoise_projection(
const typename PointRange::iterator::value_type& vt,
PointMap point_map,
VectorMap normal_map,
const std::vector<typename PointRange::iterator>& neighbor_pwns,
typename Kernel::FT radius, ///< accept neighborhood radius
typename Kernel::FT sharpness_angle ///< control sharpness(0-90)
)
{
CGAL_point_set_processing_precondition(radius > 0);
CGAL_point_set_processing_precondition(sharpness_angle > 0
&& sharpness_angle < 90);
// basic geometric types
typedef typename Kernel::FT FT;
typedef typename Kernel::Vector_3 Vector;
typedef typename Kernel::Point_3 Point;
FT radius2 = radius * radius;
FT weight = (FT)0.0;
FT iradius16 = -(FT)4.0/radius2;
FT project_dist_sum = FT(0.0);
FT project_weight_sum = FT(0.0);
Vector normal_sum = CGAL::NULL_VECTOR;
FT cos_sigma = cos(sharpness_angle * CGAL_PI / 180.0);
FT sharpness_bandwidth = std::pow((CGAL::max)(1e-8, 1 - cos_sigma), 2);
for (typename PointRange::iterator it : neighbor_pwns)
{
const Point& np = get(point_map, *it);
const Vector& nn = get(normal_map, *it);
FT dist2 = CGAL::squared_distance(get(point_map, vt), np);
if (dist2 < radius2)
{
FT theta = std::exp(dist2 * iradius16);
FT psi = std::exp(-std::pow(1 - get(normal_map, vt) * nn, 2)
/ sharpness_bandwidth);
weight = theta * psi;
project_dist_sum += ((get(point_map, vt) - np) * nn) * weight;
project_weight_sum += weight;
normal_sum = normal_sum + nn * weight;
}
}
Vector update_normal = normal_sum / project_weight_sum;
update_normal = update_normal / sqrt(update_normal.squared_length());
Point update_point = get(point_map, vt) - update_normal *
(project_dist_sum / project_weight_sum);
return std::make_pair (update_point, update_normal);
}
/// Computes max-spacing of one query point from K nearest neighbors.
///
/// \pre `k >= 2`.
///
/// @tparam Kernel Geometric traits class.
/// @tparam Tree KD-tree.
///
/// @return max spacing.
template <typename NeighborQuery>
typename NeighborQuery::Kernel::FT
compute_max_spacing(
const typename NeighborQuery::value_type& vt,
typename NeighborQuery::Point_map point_map,
NeighborQuery& neighbor_query, ///< KD-tree
unsigned int k) ///< number of neighbors
{
// basic geometric types
typedef typename NeighborQuery::Kernel Kernel;
typedef typename Kernel::FT FT;
// performs k + 1 queries (if unique the query point is
// output first). search may be aborted when k is greater
// than number of input points
FT max_distance = (FT)0.0;
neighbor_query.get_iterators
(get(point_map, vt), k, (FT)(0.0),
boost::make_function_output_iterator
([&](const typename NeighborQuery::input_iterator& it)
{
double dist2 = CGAL::squared_distance (get(point_map, vt), get(point_map, *it));
max_distance = (CGAL::max)(dist2, max_distance);
}));
// output max spacing
return std::sqrt(max_distance);
}
} /* namespace internal */
/// \endcond
// ----------------------------------------------------------------------------
// Public section
// ----------------------------------------------------------------------------
/**
\ingroup PkgPointSetProcessing3Algorithms
This function smooths an input point set by iteratively projecting each
point onto the implicit surface patch fitted over its nearest neighbors.
Bilateral projection preserves sharp features according to the normal
(gradient) information. Both point positions and normals will be modified.
For more details, please see section 4 in \cgalCite{ear-2013}.
A parallel version of this function is provided and requires the executable to be
linked against the <a href="https://www.threadingbuildingblocks.org">Intel TBB library</a>.
To control the number of threads used, the user may use the tbb::task_scheduler_init class.
See the <a href="https://www.threadingbuildingblocks.org/documentation">TBB documentation</a>
for more details.
\pre Normals must be unit vectors
\pre k >= 2
\tparam ConcurrencyTag enables sequential versus parallel algorithm. Possible values are `Sequential_tag`,
`Parallel_tag`, and `Parallel_if_available_tag`.
\tparam PointRange is a model of `Range`. The value type of
its iterator is the key type of the named parameter `point_map`.
\param points input point range.
\param k size of the neighborhood for the implicit surface patch fitting.
The larger the value is, the smoother the result will be.
\param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
\cgalNamedParamsBegin
\cgalParamNBegin{point_map}
\cgalParamDescription{a property map associating points to the elements of the point set `points`}
\cgalParamType{a model of `ReadWritePropertyMap` whose key type is the value type
of the iterator of `PointRange` and whose value type is `geom_traits::Point_3`}
\cgalParamDefault{`CGAL::Identity_property_map<geom_traits::Point_3>`}
\cgalParamNEnd
\cgalParamNBegin{normal_map}
\cgalParamDescription{a property map associating normals to the elements of the point set `points`}
\cgalParamType{a model of `ReadWritePropertyMap` whose key type is the value type
of the iterator of `PointRange` and whose value type is `geom_traits::Vector_3`}
\cgalParamDefault{Normals are computed and stored internally.}
\cgalParamNEnd
\cgalParamNBegin{neighbor_radius}
\cgalParamDescription{the spherical neighborhood radius}
\cgalParamType{floating scalar value}
\cgalParamDefault{`0` (no limit)}
\cgalParamExtra{If provided, the neighborhood of a query point is computed with a fixed spherical
radius instead of a fixed number of neighbors. In that case, the parameter
`k` is used as a limit on the number of points returned by each spherical
query (to avoid overly large number of points in high density areas).}
\cgalParamNEnd
\cgalParamNBegin{sharpness_angle}
\cgalParamDescription{controls the sharpness of the result}
\cgalParamType{floating scalar value}
\cgalParamDefault{`30`}
\cgalParamExtra{The larger the value is, the smoother the result will be.
The range of possible value is `[0, 90]`}
\cgalParamNEnd
\cgalParamNBegin{callback}
\cgalParamDescription{a mechanism to get feedback on the advancement of the algorithm
while it's running and to interrupt it if needed}
\cgalParamType{an instance of `std::function<bool(double)>`.}
\cgalParamDefault{unused}
\cgalParamExtra{It is called regularly when the
algorithm is running: the current advancement (between 0. and
1.) is passed as parameter. If it returns `true`, then the
algorithm continues its execution normally; if it returns
`false`, the algorithm is stopped, all points are left unchanged
and the function return `NaN`.}
\cgalParamExtra{The callback will be copied and therefore needs to be lightweight.}
\cgalParamExtra{When `CGAL::Parallel_tag` is used, the `callback` mechanism is called asynchronously
on a separate thread and shouldn't access or modify the variables that are parameters of the algorithm.}
\cgalParamNEnd
\cgalParamNBegin{geom_traits}
\cgalParamDescription{an instance of a geometric traits class}
\cgalParamType{a model of `Kernel`}
\cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`}
\cgalParamNEnd
\cgalNamedParamsEnd
\return Average point movement error. It's a convergence criterium for the algorithm.
This value can help the user to decide how many iterations are
sufficient.
*/
template <typename ConcurrencyTag,
typename PointRange,
typename NamedParameters>
double
bilateral_smooth_point_set(
PointRange& points,
unsigned int k,
const NamedParameters& np)
{
using parameters::choose_parameter;
using parameters::get_parameter;
// basic geometric types
typedef typename PointRange::iterator iterator;
typedef typename iterator::value_type value_type;
typedef typename CGAL::GetPointMap<PointRange, NamedParameters>::type PointMap;
typedef typename Point_set_processing_3::GetNormalMap<PointRange, NamedParameters>::type NormalMap;
typedef typename Point_set_processing_3::GetK<PointRange, NamedParameters>::Kernel Kernel;
typedef typename Kernel::Point_3 Point_3;
typedef typename Kernel::Vector_3 Vector_3;
CGAL_static_assertion_msg(!(boost::is_same<NormalMap,
typename Point_set_processing_3::GetNormalMap<PointRange, NamedParameters>::NoMap>::value),
"Error: no normal map");
typedef typename Kernel::FT FT;
double sharpness_angle = choose_parameter(get_parameter(np, internal_np::sharpness_angle), 30.);
const std::function<bool(double)>& callback = choose_parameter(get_parameter(np, internal_np::callback),
std::function<bool(double)>());
CGAL_point_set_processing_precondition(points.begin() != points.end());
CGAL_point_set_processing_precondition(k > 1);
// types for K nearest neighbors search structure
typedef Point_set_processing_3::internal::Neighbor_query<Kernel, PointRange&, PointMap> Neighbor_query;
PointMap point_map = choose_parameter<PointMap>(get_parameter(np, internal_np::point_map));
NormalMap normal_map = choose_parameter<NormalMap>(get_parameter(np, internal_np::normal_map));
FT neighbor_radius = choose_parameter(get_parameter(np, internal_np::neighbor_radius), FT(0));
std::size_t nb_points = points.size();
#ifdef CGAL_PSP3_VERBOSE
std::cout << "Initialization and compute max spacing: " << std::endl;
#endif
// initiate a KD-tree search for points
Neighbor_query neighbor_query (points, point_map);
// Guess spacing
#ifdef CGAL_PSP3_VERBOSE
CGAL::Real_timer task_timer;
task_timer.start();
#endif
FT guess_neighbor_radius = 0.0;
for (const value_type& vt : points)
{
FT max_spacing = bilateral_smooth_point_set_internal::
compute_max_spacing (vt, point_map, neighbor_query, k);
guess_neighbor_radius = (CGAL::max)(max_spacing, guess_neighbor_radius);
}
#ifdef CGAL_PSP3_VERBOSE
task_timer.stop();
#endif
guess_neighbor_radius *= 0.95;
#ifdef CGAL_PSP3_VERBOSE
CGAL::Memory_sizer::size_type memory = CGAL::Memory_sizer().virtual_size();
std::cout << "done: " << task_timer.time() << " seconds, "
<< (memory>>20) << " Mb allocated" << std::endl;
std::cout << "Compute all neighbors: " << std::endl;
task_timer.reset();
task_timer.start();
#endif
// compute all neighbors
typedef std::vector<iterator> iterators;
std::vector<iterators> pwns_neighbors;
pwns_neighbors.resize(nb_points);
Point_set_processing_3::internal::Callback_wrapper<ConcurrencyTag>
callback_wrapper (callback, 2 * nb_points);
typedef boost::zip_iterator<boost::tuple<iterator, typename std::vector<iterators>::iterator> > Zip_iterator;
CGAL::for_each<ConcurrencyTag>
(CGAL::make_range (boost::make_zip_iterator (boost::make_tuple (points.begin(), pwns_neighbors.begin())),
boost::make_zip_iterator (boost::make_tuple (points.end(), pwns_neighbors.end()))),
[&](const typename Zip_iterator::reference& t)
{
if (callback_wrapper.interrupted())
return false;
neighbor_query.get_iterators (get(point_map, get<0>(t)), k, neighbor_radius,
std::back_inserter (get<1>(t)));
++ callback_wrapper.advancement();
return true;
});
bool interrupted = callback_wrapper.interrupted();
// We interrupt by hand as counter only goes halfway and won't terminate by itself
callback_wrapper.interrupted() = true;
callback_wrapper.join();
// If interrupted during this step, nothing is computed, we return NaN
if (interrupted)
return std::numeric_limits<double>::quiet_NaN();
#ifdef CGAL_PSP3_VERBOSE
task_timer.stop();
memory = CGAL::Memory_sizer().virtual_size();
std::cout << "done: " << task_timer.time() << " seconds, "
<< (memory>>20) << " Mb allocated" << std::endl;
std::cout << "Compute update points and normals: " << std::endl;
task_timer.reset();
task_timer.start();
#endif
// update points and normals
std::vector<std::pair<Point_3, Vector_3> > update_pwns(nb_points);
callback_wrapper.reset (2 * nb_points, nb_points);
typedef boost::zip_iterator
<boost::tuple<iterator,
typename std::vector<iterators>::iterator,
typename std::vector<std::pair<Point_3, Vector_3> >::iterator> > Zip_iterator_2;
CGAL::for_each<ConcurrencyTag>
(CGAL::make_range (boost::make_zip_iterator (boost::make_tuple
(points.begin(), pwns_neighbors.begin(), update_pwns.begin())),
boost::make_zip_iterator (boost::make_tuple
(points.end(), pwns_neighbors.end(), update_pwns.end()))),
[&](const typename Zip_iterator_2::reference& t)
{
if (callback_wrapper.interrupted())
return false;
get<2>(t) = bilateral_smooth_point_set_internal::
compute_denoise_projection<Kernel, PointRange>
(get<0>(t),
point_map, normal_map,
get<1>(t),
guess_neighbor_radius,
sharpness_angle);
++ callback_wrapper.advancement();
return true;
});
callback_wrapper.join();
// If interrupted during this step, nothing is computed, we return NaN
if (callback_wrapper.interrupted())
return std::numeric_limits<double>::quiet_NaN();
#ifdef CGAL_PSP3_VERBOSE
task_timer.stop();
memory = CGAL::Memory_sizer().virtual_size();
std::cout << "done: " << task_timer.time() << " seconds, "
<< (memory>>20) << " Mb allocated" << std::endl;
#endif
// save results
FT sum_move_error = 0;
std::size_t nb = 0;
for (value_type& vt : points)
{
sum_move_error += CGAL::squared_distance(get(point_map, vt), update_pwns[nb].first);
put (point_map, vt, update_pwns[nb].first);
put (normal_map, vt, update_pwns[nb].second);
++ nb;
}
return sum_move_error / nb_points;
}
/// \cond SKIP_IN_MANUAL
// variant with default NP
template <typename ConcurrencyTag,
typename PointRange>
double
bilateral_smooth_point_set(
PointRange& points,
unsigned int k) ///< size of the neighborhood for the implicit surface patch fitting.
///< The larger the value is, the smoother the result will be.
{
return bilateral_smooth_point_set<ConcurrencyTag>
(points, k, CGAL::Point_set_processing_3::parameters::all_default(points));
}
/// \endcond
} //namespace CGAL
#include <CGAL/enable_warnings.h>
#endif // CGAL_BILATERAL_SMOOTH_POINT_SET_H