// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#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 std::pair compute_denoise_projection( const typename PointRange::iterator::value_type& vt, PointMap point_map, VectorMap normal_map, const std::vector& 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::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 Intel TBB library. To control the number of threads used, the user may use the tbb::task_scheduler_init class. See the TBB documentation 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`} \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`.} \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 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::type PointMap; typedef typename Point_set_processing_3::GetNormalMap::type NormalMap; typedef typename Point_set_processing_3::GetK::Kernel Kernel; typedef typename Kernel::Point_3 Point_3; typedef typename Kernel::Vector_3 Vector_3; CGAL_static_assertion_msg(!(boost::is_same::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& callback = choose_parameter(get_parameter(np, internal_np::callback), std::function()); 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 Neighbor_query; PointMap point_map = choose_parameter(get_parameter(np, internal_np::point_map)); NormalMap normal_map = choose_parameter(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 iterators; std::vector pwns_neighbors; pwns_neighbors.resize(nb_points); Point_set_processing_3::internal::Callback_wrapper callback_wrapper (callback, 2 * nb_points); typedef boost::zip_iterator::iterator> > Zip_iterator; CGAL::for_each (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::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 > update_pwns(nb_points); callback_wrapper.reset (2 * nb_points, nb_points); typedef boost::zip_iterator ::iterator, typename std::vector >::iterator> > Zip_iterator_2; CGAL::for_each (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 (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::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 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 (points, k, CGAL::Point_set_processing_3::parameters::all_default(points)); } /// \endcond } //namespace CGAL #include #endif // CGAL_BILATERAL_SMOOTH_POINT_SET_H