diff --git a/Point_set_processing_3/doc/Point_set_processing_3/PackageDescription.txt b/Point_set_processing_3/doc/Point_set_processing_3/PackageDescription.txt index f9e3c5b663e1..68381494f766 100644 --- a/Point_set_processing_3/doc/Point_set_processing_3/PackageDescription.txt +++ b/Point_set_processing_3/doc/Point_set_processing_3/PackageDescription.txt @@ -78,6 +78,7 @@ format. - `CGAL::vcm_estimate_normals()` - `CGAL::vcm_is_on_feature_edge()` - `CGAL::structure_point_set()` +- `CGAL::estimate_local_feature_size()` \cgalCRPSection{I/O (All Formats)} diff --git a/Point_set_processing_3/doc/Point_set_processing_3/Point_set_processing_3.txt b/Point_set_processing_3/doc/Point_set_processing_3/Point_set_processing_3.txt index 7d62380935d3..af5f3d6df2d9 100644 --- a/Point_set_processing_3/doc/Point_set_processing_3/Point_set_processing_3.txt +++ b/Point_set_processing_3/doc/Point_set_processing_3/Point_set_processing_3.txt @@ -52,12 +52,12 @@ typedef CGAL::First_of_pair_property_map Point_map; typedef CGAL::Second_of_pair_property_map Vector_map; CGAL::jet_estimate_normals - // concurrency tag - (points, // input range of points - 12, // parameter: number of neighbors - CGAL::parameters:: // named parameters: - point_map (Point_map()). // * point map - normal_map (Vector_map())); // * normal map + // concurrency tag + (points, // input range of points + 12, // parameter: number of neighbors + CGAL::parameters:: // named parameters: + point_map (Point_map()). // * point map + normal_map (Vector_map())); // * normal map \endcode This API was introduced in \cgal 4.12. Please refer to the @@ -116,10 +116,10 @@ std::vector points; // Old pre-CGAL 4.12 API CGAL::jet_estimate_normals - (points.begin(), points.end(), - CGAL::First_of_pair_property_map(), - CGAL::Second_of_pair_property_map(), - 12); // Number of neighbors + (points.begin(), points.end(), + CGAL::First_of_pair_property_map(), + CGAL::Second_of_pair_property_map(), + 12); // Number of neighbors \endcode The pair of iterators is replaced by a range and the optional @@ -133,10 +133,10 @@ std::vector points; // New API CGAL::jet_estimate_normals - (points, - 12, // Number of neighbors - CGAL::parameters::point_map (CGAL::First_of_pair_property_map()). - normal_map (CGAL::Second_of_pair_property_map())); + (points, + 12, // Number of neighbors + CGAL::parameters::point_map (CGAL::First_of_pair_property_map()). + normal_map (CGAL::Second_of_pair_property_map())); \endcode Please refer to the [Reference Manual](@ref PkgPointSetProcessing3Ref) for @@ -524,66 +524,66 @@ above-mentioned example was used. \cgalFigureAnchor{Point_set_processing_3tableRegistrationRegistration_visualization_table} - - - + + + - - - + + + - - - + + + - - - + + + - - - + + +
Scan 1 Scan 1 (possibly transformed, green) and Scan 2 (the reference, red) Scan 1 Scan 1 (possibly transformed, green) and Scan 2 (the reference, red)
Unregistered - \cgalFigureBegin{Point_set_processing_3figRegistrationUnregistered_hippo2_view1, registration_view1_hippo2_unregistered.png} - \cgalFigureEnd - - \cgalFigureBegin{Point_set_processing_3figRegistrationUnregistered_hippo1_hippo2_view1, registration_view1_hippo1_hippo2_unregistered.png} - \cgalFigureEnd - Unregistered + \cgalFigureBegin{Point_set_processing_3figRegistrationUnregistered_hippo2_view1, registration_view1_hippo2_unregistered.png} + \cgalFigureEnd + + \cgalFigureBegin{Point_set_processing_3figRegistrationUnregistered_hippo1_hippo2_view1, registration_view1_hippo1_hippo2_unregistered.png} + \cgalFigureEnd +
- Registered - using - %OpenGR - - \cgalFigureBegin{Point_set_processing_3figRegistrationOpenGR_hippo2_view1, registration_view1_hippo2_opengr.png} - \cgalFigureEnd - - \cgalFigureBegin{Point_set_processing_3figRegistrationOpenGR_hippo1_hippo2_view1, registration_view1_hippo1_hippo2_opengr.png} - \cgalFigureEnd - + Registered + using + %OpenGR + + \cgalFigureBegin{Point_set_processing_3figRegistrationOpenGR_hippo2_view1, registration_view1_hippo2_opengr.png} + \cgalFigureEnd + + \cgalFigureBegin{Point_set_processing_3figRegistrationOpenGR_hippo1_hippo2_view1, registration_view1_hippo1_hippo2_opengr.png} + \cgalFigureEnd +
- Registered - using - PointMatcher - - \cgalFigureBegin{Point_set_processing_3figRegistrationPointMatcher_hippo2_view1, registration_view1_hippo2_pointmatcher.png} - \cgalFigureEnd - - \cgalFigureBegin{Point_set_processing_3figRegistrationPointMatcher_hippo1_hippo2_view1, registration_view1_hippo1_hippo2_pointmatcher.png} - \cgalFigureEnd - + Registered + using + PointMatcher + + \cgalFigureBegin{Point_set_processing_3figRegistrationPointMatcher_hippo2_view1, registration_view1_hippo2_pointmatcher.png} + \cgalFigureEnd + + \cgalFigureBegin{Point_set_processing_3figRegistrationPointMatcher_hippo1_hippo2_view1, registration_view1_hippo1_hippo2_pointmatcher.png} + \cgalFigureEnd +
- Registered - using - OpenGR+PointMatcher - Pipeline - - \cgalFigureBegin{Point_set_processing_3figRegistrationPipeline_hippo2_view1, registration_view1_hippo2_opengr_pointmatcher_pipeline.png} - \cgalFigureEnd - - \cgalFigureBegin{Point_set_processing_3figRegistrationPipeline_hippo1_hippo2_view1, registration_view1_hippo1_hippo2_opengr_pointmatcher_pipeline.png} - \cgalFigureEnd - + Registered + using + OpenGR+PointMatcher + Pipeline + + \cgalFigureBegin{Point_set_processing_3figRegistrationPipeline_hippo2_view1, registration_view1_hippo2_opengr_pointmatcher_pipeline.png} + \cgalFigureEnd + + \cgalFigureBegin{Point_set_processing_3figRegistrationPipeline_hippo1_hippo2_view1, registration_view1_hippo1_hippo2_opengr_pointmatcher_pipeline.png} + \cgalFigureEnd +
\cgalFigureCaptionBegin{Point_set_processing_3tableRegistrationRegistration_visualization_table} @@ -698,7 +698,7 @@ 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 +`var_max`\f$=1/3\f$. \cgalFigureEnd \subsubsection Point_set_processing_3Hierarchy_simplification_parameter_var_max Parameter: var_max @@ -706,7 +706,7 @@ In addition to the size parameter, a variation parameter allows to increase simplification in monotonous 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] + \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 @@ -717,7 +717,7 @@ 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 +`size`\f$=1000\f$. \cgalFigureEnd \subsection Point_set_processing_3Example_wlop WLOP Simplification Example @@ -926,6 +926,30 @@ The following example reads a point set from a file, estimates the points that are on sharp edges: \cgalExample{Point_set_processing_3/edges_example.cpp} +\section Point_set_processing_3LFSEstimation Local Feature Size Estimation + +Function `estimate_local_feature_size()` can estimate the local feature size for each point +by giving a raw point set. If the points have no normals, the function also estimates normals using jet fitting. +The function works by calculating the curvature and shape diameter. +Then, the local feature size will be the minimum of the curvature radius and half of the shape diameter. + +Function `median_filter_smoothing_lfs()` performs the median filter to smooth the estimated local feature size. + +Function `lipschitz_continuity_smoothing_lfs()` smooth the local feature size +based on the property of 1-Lipschitz continuity of the local feature size function. + +Function `laplacian_smoothing_lfs()` performs the laplacian smoothing to smooth the estimated local feature size. + +\subsection Point_set_processing_3Example_LFS_point_set Point_set_3 Example + +The following example uses the `CGAL::Point_set_3` to store a point cloud and estimates the local feature size for each point: +\cgalExample{Point_set_processing_3/lfs_example_pointset.cpp} + +\subsection Point_set_processing_3Example_LFS_tuple std::vector Example + +The following example uses the `std::vector` to store a point cloud and estimates the local feature size for each point: +\cgalExample{Point_set_processing_3/lfs_example_tuple.cpp} + \section Point_set_processing_3Structuring Structuring diff --git a/Point_set_processing_3/doc/Point_set_processing_3/examples.txt b/Point_set_processing_3/doc/Point_set_processing_3/examples.txt index ea670af1b623..1847286a243f 100644 --- a/Point_set_processing_3/doc/Point_set_processing_3/examples.txt +++ b/Point_set_processing_3/doc/Point_set_processing_3/examples.txt @@ -21,6 +21,8 @@ \example Point_set_processing_3/bilateral_smooth_point_set_example.cpp \example Point_set_processing_3/edge_aware_upsample_point_set_example.cpp \example Point_set_processing_3/edges_example.cpp +\example Point_set_processing_3/lfs_example_tuple.cpp +\example Point_set_processing_3/lfs_example_pointset.cpp \example Point_set_processing_3/structuring_example.cpp \example Point_set_processing_3/callback_example.cpp */ diff --git a/Point_set_processing_3/examples/Point_set_processing_3/CMakeLists.txt b/Point_set_processing_3/examples/Point_set_processing_3/CMakeLists.txt index ac5e71b1aa11..338a6336ef82 100644 --- a/Point_set_processing_3/examples/Point_set_processing_3/CMakeLists.txt +++ b/Point_set_processing_3/examples/Point_set_processing_3/CMakeLists.txt @@ -72,6 +72,8 @@ if(TARGET CGAL::Eigen3_support) # Executables that require Eigen foreach( target + lfs_example_tuple + lfs_example_pointset jet_smoothing_example normal_estimation clustering_example diff --git a/Point_set_processing_3/examples/Point_set_processing_3/lfs_example_pointset.cpp b/Point_set_processing_3/examples/Point_set_processing_3/lfs_example_pointset.cpp new file mode 100644 index 000000000000..4735cdb36d42 --- /dev/null +++ b/Point_set_processing_3/examples/Point_set_processing_3/lfs_example_pointset.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include + +// types +typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; +typedef Kernel::FT FT; +typedef Kernel::Point_3 Point; +typedef Kernel::Vector_3 Vector; + +typedef CGAL::Point_set_3 Point_set; +typedef Point_set::Property_map Point_map; +typedef Point_set::Property_map FT_map; + + +// Concurrency +typedef CGAL::Parallel_if_available_tag Concurrency_tag; + +int main(void) +{ + // read xyz + const std::string filename = CGAL::data_file_path("points_3/kitten.xyz"); + + Point_set point_set; + + FT_map lfs_map; + boost::tie (lfs_map, boost::tuples::ignore) = point_set.add_property_map ("LFS", 0.); + + if (!CGAL::IO::read_point_set(filename, point_set)) + { + std::cerr << "Error: cannot read file " << filename<< std::endl; + return EXIT_FAILURE; + } + + + unsigned int jet_k = 24; + std::size_t N_rays = 60; + FT apex_angle = 30; + CGAL::estimate_local_feature_size(point_set, jet_k, N_rays, apex_angle, lfs_map, + CGAL::parameters::point_map(point_set.point_map()) + .normal_map(point_set.normal_map())); + + // optionally, smooth the raw LFS values for other purposes + unsigned int median_filter_k = 11, median_filter_T = 5; + CGAL::median_filter_smoothing_lfs(point_set, median_filter_k, median_filter_T, lfs_map, + CGAL::parameters::point_map(point_set.point_map())); + + unsigned int lipschitz_continuity_smoothing_k = 11; + FT lipschitz = 1.0; + CGAL::lipschitz_continuity_smoothing_lfs(point_set, lipschitz_continuity_smoothing_k, lipschitz, lfs_map, + CGAL::parameters::point_map(point_set.point_map())); + + unsigned int laplacian_smoothing_k = 11, laplacian_smoothing_T = 5; + FT laplacian_smoothing_lambda = 1.0; + CGAL::laplacian_smoothing_lfs(point_set, laplacian_smoothing_k, laplacian_smoothing_T, laplacian_smoothing_lambda, lfs_map, + CGAL::parameters::point_map(point_set.point_map())); + + // print + for (Point_set::iterator it = point_set.begin(); it != point_set.end(); ++ it) + { + // Point p = point_set.point(*it); + // Vector n = point_set.normal(*it); + FT lfs = lfs_map[*it]; + std::cout << lfs << std::endl; + } + + return EXIT_SUCCESS; +} diff --git a/Point_set_processing_3/examples/Point_set_processing_3/lfs_example_tuple.cpp b/Point_set_processing_3/examples/Point_set_processing_3/lfs_example_tuple.cpp new file mode 100644 index 000000000000..bde51ad07f7d --- /dev/null +++ b/Point_set_processing_3/examples/Point_set_processing_3/lfs_example_tuple.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include +#include +#include + +// types +typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; +typedef Kernel::FT FT; +typedef Kernel::Point_3 Point; +typedef Kernel::Vector_3 Vector; + +typedef std::tuple Point_with_normal_and_lfs; + +// Concurrency +typedef CGAL::Parallel_if_available_tag Concurrency_tag; + +int main(void) +{ + // read xyz + const std::string filename = CGAL::data_file_path("points_3/kitten.xyz"); + + std::vector points; + if(!CGAL::IO::read_points(filename, + std::back_inserter(points), + CGAL::parameters::point_map(CGAL::Nth_of_tuple_property_map<0, Point_with_normal_and_lfs>()) + .normal_map(CGAL::Nth_of_tuple_property_map<1, Point_with_normal_and_lfs>()))) + { + std::cerr << "Error: cannot read file " << filename<< std::endl; + return EXIT_FAILURE; + } + + unsigned int jet_k = 24; + std::size_t N_rays = 60; + FT apex_angle = 30; + auto lfs_map = CGAL::Nth_of_tuple_property_map<2, Point_with_normal_and_lfs>(); + CGAL::estimate_local_feature_size(points, + jet_k, + N_rays, + apex_angle, + lfs_map, + CGAL::parameters::point_map(CGAL::Nth_of_tuple_property_map<0, Point_with_normal_and_lfs>()) + .normal_map(CGAL::Nth_of_tuple_property_map<1, Point_with_normal_and_lfs>())); + + // optionally, smooth the raw LFS values for other purposes + unsigned int median_filter_k = 11, median_filter_T = 5; + CGAL::median_filter_smoothing_lfs(points, median_filter_k, median_filter_T, lfs_map, + CGAL::parameters::point_map(CGAL::Nth_of_tuple_property_map<0, Point_with_normal_and_lfs>())); + + unsigned int lipschitz_continuity_smoothing_k = 11; + FT lipschitz = 1.0; // 1 lipschitz_continuity for LFS function + CGAL::lipschitz_continuity_smoothing_lfs(points, lipschitz_continuity_smoothing_k, lipschitz, lfs_map, + CGAL::parameters::point_map(CGAL::Nth_of_tuple_property_map<0, Point_with_normal_and_lfs>())); + + unsigned int laplacian_smoothing_k = 11, laplacian_smoothing_T = 5; + FT laplacian_smoothing_lambda = 1.0; + CGAL::laplacian_smoothing_lfs(points, + laplacian_smoothing_k, laplacian_smoothing_T, laplacian_smoothing_lambda, lfs_map, + CGAL::parameters::point_map(CGAL::Nth_of_tuple_property_map<0, Point_with_normal_and_lfs>())); + + // print + for (const auto &pts : points) + { + // Point p = std::get<0>(pts); + // Vector n = std::get<1>(pts); + FT lfs = std::get<2>(pts); + std::cout << lfs << std::endl; + } + + return EXIT_SUCCESS; +} diff --git a/Point_set_processing_3/include/CGAL/estimate_local_feature_size.h b/Point_set_processing_3/include/CGAL/estimate_local_feature_size.h new file mode 100644 index 000000000000..f9ba979ddd6c --- /dev/null +++ b/Point_set_processing_3/include/CGAL/estimate_local_feature_size.h @@ -0,0 +1,1071 @@ +// Copyright (c) 2007-09 INRIA Sophia-Antipolis (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Rao Fu + +#ifndef CGAL_ESTIMATE_LFS_H +#define CGAL_ESTIMATE_LFS_H + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace CGAL { + +// ---------------------------------------------------------------------------- +// Private section +// ---------------------------------------------------------------------------- +/// \cond SKIP_IN_MANUAL +namespace internal { + +template +typename Geom_traits::Sphere_3 +calculate_loose_bsphere(const PointRange& points, const PointMap point_map, + const typename Geom_traits::FT loose_ratio=0.1) +{ + typedef typename Geom_traits::FT FT; + typedef typename Geom_traits::Point_3 Point; + typedef typename Geom_traits::Sphere_3 Sphere; + + CGAL_precondition(points.begin() != points.end()); + + Point center = CGAL::centroid(CGAL::make_transform_iterator_from_property_map + (points.begin(), point_map), + CGAL::make_transform_iterator_from_property_map + (points.end(), point_map)); + + FT max_distance = (std::numeric_limits::min)(); + for (const auto& pt : points) + { + const Point& point = get(point_map, pt); + FT distance = CGAL::squared_distance(center, point); + if (distance > max_distance) { + max_distance = distance; + } + } + + return Sphere(center, max_distance*(1.0 + loose_ratio)); +} + +template +typename NeighborQuery::FT +classical_point_dist_func(const typename NeighborQuery::Point_3& query, ///< point + const NeighborQuery& neighbor_query) +{ + // basic geometric types + typedef typename NeighborQuery::Kernel Kernel; + typedef typename NeighborQuery::FT FT; + typedef typename Kernel::Point_3 Point; + std::vector points; + // find the nearest neighbor + neighbor_query.get_points(query, 1, FT(0), std::back_inserter(points)); + + const FT dsq = CGAL::squared_distance(query, points[0]); + + return std::sqrt(dsq); +} + +template +typename NeighborQuery::FT +av_sqrt_sum_sq_distance_to_knn(const typename NeighborQuery::Point_3& query, ///< point + const NeighborQuery& neighbor_query, + const int knn) +{ + // basic geometric types + typedef typename NeighborQuery::Kernel Kernel; + typedef typename NeighborQuery::FT FT; + typedef typename Kernel::Point_3 Point; + std::vector points; + // find the nearest neighbor + neighbor_query.get_points(query, knn, FT(0), std::back_inserter(points)); + + // get distances + FT sum_sq_distances = FT(0.0); + for (const auto& point : points) + { + sum_sq_distances += CGAL::squared_distance(query, point); + } + + std::size_t sz = points.size(); + + if (sz == 0) + return 0.0; + else + return std::sqrt(sum_sq_distances / sz); +} + +template +typename NeighborQuery::FT +classical_point_dist_func_epsilon_band(const PointRange &points, + const NeighborQuery& neighbor_query, + const PointMap point_map, + const int band_knn=12) +{ + // basic geometric types + typedef typename NeighborQuery::Kernel Kernel; + typedef typename NeighborQuery::FT FT; + typedef typename Kernel::Point_3 Point; + + CGAL_precondition(points.begin() != points.end()); + CGAL_precondition(band_knn > 0); + + FT l_min = std::numeric_limits::infinity(); + for (const auto& pt : points) + { + const Point &query = get(point_map, pt); + + FT l = av_sqrt_sum_sq_distance_to_knn(query, neighbor_query, band_knn); + + if (l == FT(0)) continue; + + if (l < l_min) + { + l_min = l; + } + } + + return l_min; +} + +template +Point calculate_ray_sphere_intersection(const Point &p, const Vector &n, const Sphere &sphere) +{ + + Vector d = p - sphere.center(); + + auto d_len = std::sqrt(d.squared_length()); + + auto cos_theta = -1.0 * (d * n) / d_len / std::sqrt(n.squared_length()); + + auto l = d_len * cos_theta + + std::sqrt(sphere.squared_radius() - d.squared_length() * (1.0 - cos_theta * cos_theta)); + + Point t = p + n * l; + + return t; +} + +template +Point local2world(const Point &local, const Point &o, + const Vector &u_basis, const Vector &v_basis, const Vector &w_basis) +{ + Vector v = local.x() * u_basis + local.y() * v_basis + local.z() * w_basis; + Point world = o + v; + + return world; +} + + +template +void random_dual_cone_search_segs(const typename Geom_traits::Point_3 &p, + const typename Geom_traits::Vector_3 &n, + const typename Geom_traits::Sphere_3 &bsphere, + std::vector &segs, + const typename Geom_traits::FT half_apex_angle, std::size_t N) +{ + typedef typename Geom_traits::FT FT; + typedef typename Geom_traits::Point_3 Point; + typedef typename Geom_traits::Vector_3 Vector; + typedef typename Geom_traits::Segment_3 Segment; + + FT half_apex_radian = half_apex_angle * CGAL_PI / 180.0; + + // add the normal directions + const Point s = calculate_ray_sphere_intersection(p, n, bsphere); + const Point t = calculate_ray_sphere_intersection(p, -1.0 * n, bsphere); + segs.push_back(Segment(s, t)); + + // add rays within the cone + + // define local coordinate system + // normal should be normalized + FT eps = 1e-5; + assert(std::abs(n.squared_length() - 1.0) <= eps); + const Vector w_basis = n; + + Vector v_vec(0.0, 0.0, 0.0); + if (std::abs(n.x()) >= std::abs(n.y())) + v_vec = Vector(n.z(), 0.0, -1.0 * n.x()); + else + v_vec = Vector(0.0, n.z(), -1.0 * n.y()); + // normalize v_vec + FT v_vec_norm = v_vec.squared_length(); + v_vec_norm = v_vec_norm > eps ? v_vec_norm : eps; + const Vector v_basis = v_vec / std::sqrt(v_vec_norm); + + const Vector u_vec = CGAL::cross_product(v_basis, w_basis); + // normalize u_vec + FT u_vec_norm = u_vec.squared_length(); + u_vec_norm = u_vec_norm > eps ? u_vec_norm : eps; + const Vector u_basis = u_vec / std::sqrt(u_vec_norm); + + FT w = std::cos(half_apex_radian); + assert(w <= 1.0); + + FT R = std::sqrt(1.0 - w * w); + + for (int i = 0; i < N; i++) + { + FT rd1 = rand() / (double)RAND_MAX; + FT rd2 = rand() / (double)RAND_MAX; + + FT random_r = R * std::sqrt(rd1); + FT random_phi = 2.0 * CGAL_PI * rd2; + + FT u = random_r * std::cos(random_phi); + FT v = random_r * std::sin(random_phi); + + // local to world + const Point world = local2world(Point(u, v, w), p, u_basis, v_basis, w_basis); + const Vector ray_direction = world - p; + const Point s = calculate_ray_sphere_intersection(p, ray_direction, bsphere); + const Point t = calculate_ray_sphere_intersection(p, -1.0 * ray_direction, bsphere); + segs.push_back(Segment(s, t)); + } +} + +template +bool recursive_dichotomic_search_base(const typename NeighborQuery::Kernel::Segment_3 &e, + const typename NeighborQuery::Kernel::Point_2 &s, + const typename NeighborQuery::Kernel::Point_2 &t, + const NeighborQuery& neighbor_query, + const typename NeighborQuery::FT epsilon_band, + std::vector& intersections, + typename NeighborQuery::FT lipschitz, + typename NeighborQuery::FT eps) +{ + // basic geometric types + typedef typename NeighborQuery::Kernel Kernel; + typedef typename NeighborQuery::FT FT; + typedef typename Kernel::Point_3 Point; + typedef typename Kernel::Point_2 Point_2; + typedef typename Kernel::Vector_3 Vector; + + assert(t.x() + eps >= s.x() - eps); + CGAL_precondition(lipschitz >= 0.0); + + Point ps = e.source(); + Point pt = e.target(); + Vector v = pt - ps; + FT len = std::sqrt(v.squared_length()); + + // should satisfy lipschitz continuity -- keep it? + FT abs_dy = std::abs(t.y() - s.y()); + FT abs_dx = std::abs(t.x() - s.x()); + // it is better to abs_dy / abs_dx, when abs_dx is small + if (abs_dy / abs_dx > lipschitz + eps) + { + return false; + } + + FT lip_lbx = (s.x() + t.x()) * 0.5 - (t.y() - s.y()) * 0.5 / lipschitz; + FT lip_lby = s.y() - lipschitz * (lip_lbx - s.x()); + + assert(lip_lbx + eps >= s.x() - eps); + assert(lip_lbx - eps <= t.x() + eps); + + FT lip_ubx = ((t.y() - s.y()) / lipschitz + t.x() + s.x()) * 0.5; + FT lip_uby = s.y() + lipschitz * (lip_ubx - s.x()); + + assert(lip_ubx + eps >= s.x() - eps); + assert(lip_ubx - eps <= t.x() + eps); + + // stop + if (lip_lby >= epsilon_band || lip_uby <= epsilon_band) + { + return true; + } + + const bool s_sign = (s.y() > epsilon_band); + const bool t_sign = (t.y() > epsilon_band); + const FT s_slope = (s_sign) ? -lipschitz : lipschitz; + const FT t_slope = (t_sign) ? lipschitz : -lipschitz; + + FT ms_x = s.x() + (epsilon_band - s.y()) / s_slope; + const Point pms = ps + ms_x / len * v; + FT ms_y = classical_point_dist_func(pms, neighbor_query); + const Point_2 ms(ms_x, ms_y); + assert(ms_x + eps >= s.x() - eps); + + FT mt_x = t.x() + (epsilon_band - t.y()) / t_slope; + const Point pmt = ps + mt_x / len * v; + FT mt_y = classical_point_dist_func(pmt, neighbor_query); + const Point_2 mt(mt_x, mt_y); + assert(mt_x - eps <= t.x() + eps); + assert(mt_x + eps >= ms_x - eps); + + const FT mm_x = (ms_x + mt_x) / 2; + const Point pmm = ps + mm_x / len * v; + FT mm_y = classical_point_dist_func(pmm, neighbor_query); + Point_2 mm(mm_x, mm_y); + + if (std::abs(mm_y - epsilon_band) <= eps && (mt_x - ms_x) <= epsilon_band / lipschitz) + { + if (s_sign == t_sign) + { + intersections.push_back(pmm); + intersections.push_back(pmm); + } + else + { + intersections.push_back(pmm); + } + + return true; + } + + bool fl = recursive_dichotomic_search_base(e, ms, mm, neighbor_query, epsilon_band, + intersections, lipschitz, eps); + bool fr = recursive_dichotomic_search_base(e, mm, mt, neighbor_query, epsilon_band, + intersections, lipschitz, eps); + + return (fl && fr); +} + +template +bool recursive_dichotomic_search(const typename NeighborQuery::Kernel::Segment_3 &e, + const typename NeighborQuery::Kernel::Point_2 &s, + const typename NeighborQuery::Kernel::Point_2 &t, + const NeighborQuery &neighbor_query, + const typename NeighborQuery::FT epsilon_band, + std::vector& intersections, + typename NeighborQuery::FT lipschitz, + typename NeighborQuery::FT eps=1e-5) +{ + // basic geometric types + typedef typename NeighborQuery::Kernel Kernel; + typedef typename Kernel::Point_3 Point; + + CGAL_precondition(intersections.empty()); + bool flag = recursive_dichotomic_search_base(e, s, t, neighbor_query, epsilon_band, + intersections, lipschitz, eps); + + if (intersections.size() % 2 != 0) + { + const bool s_sign = (s.y() > epsilon_band); + const bool t_sign = (t.y() > epsilon_band); + + if ((!s_sign) && t_sign) + { + std::vector intersections_; + intersections_.reserve(intersections.size()); + intersections_.push_back(e.source()); + for (std::size_t i = 1; i < intersections.size(); i = i + 2) + { + const Point p0 = intersections[i]; + const Point p1 = intersections[i + 1]; + const Point p = p1 + (p0 - p1) / 2; + intersections_.push_back(p); + } + std::swap(intersections, intersections_); + } + + if ((!t_sign) && s_sign) + { + std::vector intersections_; + intersections_.reserve(intersections.size()); + for (std::size_t i = 0; i < intersections.size() - 1; i = i + 2) + { + const Point p0 = intersections[i]; + const Point p1 = intersections[i + 1]; + const Point p = p1 + (p0 - p1) / 2; + intersections_.push_back(p); + } + intersections_.push_back(e.target()); + std::swap(intersections, intersections_); + } + + if (s_sign && t_sign) + { + flag = false; + } + + // should not happen + if ((!s_sign) && (!t_sign)) + { + flag = false; + } + } + else + { + std::vector intersections_; + for (std::size_t i = 0; i < intersections.size(); i = i + 2) + { + const Point p0 = intersections[i]; + const Point p1 = intersections[i + 1]; + const Point p = p1 + (p0 - p1) / 2; + intersections_.push_back(p); + } + intersections = intersections_; + } + + return flag; +} + +template +typename NeighborQuery::FT +estimate_shape_diameter(const typename NeighborQuery::Point_3& query, ///< point + const typename NeighborQuery::Kernel::Vector_3& normal, + const NeighborQuery& neighbor_query, ///< KD-tree + const typename NeighborQuery::Kernel::Sphere_3& bsphere, + const typename NeighborQuery::FT epsilon_band, + const typename NeighborQuery::FT apex_angle, + std::size_t N_rays, + unsigned int reject_knn=6) +{ + // basic geometric types + typedef typename NeighborQuery::Kernel Kernel; + typedef typename NeighborQuery::FT FT; + typedef typename Kernel::Point_3 Point; + typedef typename Kernel::Point_2 Point_2; + typedef typename Kernel::Segment_3 Segment; + + std::vector points; + neighbor_query.get_points (query, reject_knn, FT(0), std::back_inserter(points)); + FT project_radius = reject_knn == 0 ? 0.0 : std::sqrt(CGAL::squared_distance(query, points[reject_knn-1])); + + // dual cone search segments + FT half_apex_angle = apex_angle / 2.0; + std::vector segs; + random_dual_cone_search_segs(query, normal, bsphere, segs, + half_apex_angle, N_rays); + + std::vector antipodal_point_dsqs; + for (const auto seg : segs) + { + const Point ps = seg.source(); + const Point pt = seg.target(); + FT l = std::sqrt(seg.squared_length()); + + FT ls = classical_point_dist_func(ps, neighbor_query); + FT lt = classical_point_dist_func(pt, neighbor_query); + FT lipschitz = 1.1; // slightly enlarge the lipschitz + std::vector intersections; + bool flag = recursive_dichotomic_search(seg, Point_2(0.0, ls), Point_2(l, lt), neighbor_query, + epsilon_band, intersections, lipschitz); + + if (flag && intersections.size() >= 2) + { + std::vector dsqs; + for (std::size_t i = 0; i < intersections.size(); i++) + { + const auto intersection = intersections[i]; + + FT dsq = (query - intersection).squared_length(); + dsqs.push_back(dsq); + } + std::sort(dsqs.begin(), dsqs.end(), std::less()); + // dist should be larger than epsilon_band + auto it = upper_bound(dsqs.begin(), dsqs.end(), project_radius*project_radius); + + if (it != dsqs.end()) + { + FT dsq_min = *it; + antipodal_point_dsqs.push_back(dsq_min); + } + } + } + + FT dsq_upper_bound = 4.0 * bsphere.squared_radius(); + + if(antipodal_point_dsqs.empty()) + antipodal_point_dsqs.push_back(dsq_upper_bound); + + // robust distance function here + FT sum_squared_distances = std::accumulate(antipodal_point_dsqs.begin(), antipodal_point_dsqs.end(), 0.0); + std::size_t sz = antipodal_point_dsqs.size(); + if (sz == 0) + return 0.0; + else + return std::sqrt(sum_squared_distances / sz); +} + +template +typename Monge_form::FT +min_curvature_radius(Monge_form &monge_form) +{ + // basic geometric types + typedef typename Monge_form::FT FT; + + FT max_principal_curvature = monge_form.principal_curvatures(0); + FT min_principal_curvature = monge_form.principal_curvatures(1); + + FT r1 = 1.0 / std::abs(max_principal_curvature); + FT r2 = 1.0 / std::abs(min_principal_curvature); + FT r = (std::min)(r1, r2); + + return r; +} + +template +typename NeighborQuery::FT +estimate_local_feature_size(const typename NeighborQuery::Point_3& query, ///< point + typename NeighborQuery::Kernel::Vector_3& normal, + const NeighborQuery& neighbor_query, ///< KD-tree + const typename NeighborQuery::Kernel::Sphere_3& bsphere, ///< bounding sphere + unsigned int jet_k, ///< number of neighbors + typename NeighborQuery::FT neighbor_radius, + unsigned int degree_fitting, + unsigned int degree_monge, + typename NeighborQuery::FT epsilon_band, + typename NeighborQuery::FT apex_angle, + std::size_t N_rays + ) +{ + // basic geometric types + typedef typename NeighborQuery::Kernel Kernel; + typedef typename Kernel::Point_3 Point; + typedef typename Kernel::Vector_3 Vector; + typedef typename Kernel::FT FT; + + // types for jet fitting + typedef Monge_via_jet_fitting< Kernel > Monge_jet_fitting; + typedef typename Monge_jet_fitting::Monge_form Monge_form; + + std::vector points; + // query using as fallback minimum requires nb points for jet fitting (d+1)*(d+2)/2 + neighbor_query.get_points (query, jet_k, neighbor_radius, std::back_inserter(points), + (degree_fitting + 1) * (degree_fitting + 2) / 2); + + // estimate jet + Monge_jet_fitting monge_fit; + Monge_form monge_form = monge_fit(points.begin(), points.end(), degree_fitting, degree_monge); + + if (normal * normal == 0.0) + normal = monge_form.normal_direction(); // already normalized in monge_form + else + monge_form.comply_wrt_given_normal(normal); // orient jet with valid given normal + + FT abs_curvature_r = min_curvature_radius(monge_form); + + // estimate shape diameter + FT shape_diameter = estimate_shape_diameter(query, normal, neighbor_query, bsphere, epsilon_band, apex_angle, N_rays); + FT half_shape_diameter = 0.5 * shape_diameter; + + FT lfs = (std::min)(abs_curvature_r, half_shape_diameter); + + return lfs; +} + +template +void +construct_knn_graph(PointRange& points, + const int knn, + std::vector>& knn_graph, + const NamedParameters& np = parameters::default_values()) +{ + using parameters::choose_parameter; + using parameters::get_parameter; + // basic geometric types + typedef Point_set_processing_3_np_helper NP_helper; + typedef typename NP_helper::Point_map PointMap; + typedef typename NP_helper::Geom_traits Kernel; + typedef typename Kernel::FT FT; + typedef typename Kernel::Point_3 Point; + + // types for K nearest neighbors search structure + typedef typename PointRange::iterator iterator; + typedef Point_set_processing_3::internal::Neighbor_query Neighbor_query; + // Property map typename PointRange::iterator -> index + typedef Index_property_map IndexMap; + + PointMap point_map = NP_helper::get_point_map(points, np); + + const std::function& callback = choose_parameter(get_parameter(np, internal_np::callback), + std::function()); + + CGAL_precondition(points.begin() != points.end()); + + std::size_t memory = CGAL::Memory_sizer().virtual_size(); + CGAL_TRACE_STREAM << (memory >> 20) << " Mb allocated\n"; + CGAL_TRACE_STREAM << " Creates KD-tree\n"; + + Neighbor_query neighbor_query(points, point_map); + + std::size_t nb_points = points.size(); + knn_graph.clear(); + knn_graph.resize(nb_points); + + Point_set_processing_3::internal::Callback_wrapper + callback_wrapper (callback, nb_points); + typedef boost::zip_iterator>::iterator> > Zip_iterator; + CGAL::for_each + (CGAL::make_range (boost::make_zip_iterator (boost::make_tuple (points.begin(), knn_graph.begin())), + boost::make_zip_iterator (boost::make_tuple (points.end(), knn_graph.end()))), + [&](const typename Zip_iterator::reference& t) + { + if (callback_wrapper.interrupted()) + return false; + + const Point& point = get(point_map, boost::get<0>(t)); + neighbor_query.get_iterators(point, knn, FT(0), std::back_inserter(boost::get<1>(t))); + + ++ callback_wrapper.advancement(); + return true; + }); +} + +} /* namespace internal */ +/// \endcond + +/** + \ingroup PkgPointSetProcessing3Algorithms + + Estimate the local feature size (LFS) for the input 3D point cloud. The function only works for 3D point cloud. + If the input 3D point cloud has no normals, the function will also estimate the normals using jet fitting. + + + \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 jet_k number of neighbors for jet fitting + \param N_rays number of rays for dual cone search + \param apex_angle angle for dual cone search + \param LfsMap the map to store the LFS value + \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 `ReadablePropertyMap` 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{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 The estimated local feature size is stored in `lfs_map`. +*/ +template +void +estimate_local_feature_size(PointRange& points, + const unsigned int jet_k, + const std::size_t N_rays, + const typename Point_set_processing_3_np_helper::Geom_traits::FT apex_angle, + LfsMap lfs_map, + const NamedParameters& np = parameters::default_values()) +{ + using parameters::choose_parameter; + using parameters::get_parameter; + + CGAL_TRACE_STREAM << "Calls estimate_local_feature_size()\n"; + + // basic geometric types + typedef Point_set_processing_3_np_helper NP_helper; + typedef typename NP_helper::Point_map PointMap; + typedef typename NP_helper::Normal_map NormalMap; + typedef typename NP_helper::Geom_traits Kernel; + typedef typename Kernel::FT FT; + typedef typename Kernel::Point_3 Point; + typedef typename Kernel::Vector_3 Vector; + typedef typename Kernel::Sphere_3 Sphere; + + CGAL_assertion_msg(NP_helper::has_normal_map(points, np), "Error: no normal map"); + PointMap point_map = NP_helper::get_point_map(points, np); + NormalMap normal_map = NP_helper::get_normal_map(points, np); + unsigned int degree_fitting = choose_parameter(get_parameter(np, internal_np::degree_fitting), 2); + unsigned int degree_monge = choose_parameter(get_parameter(np, internal_np::degree_monge), 2); + FT neighbor_radius = choose_parameter(get_parameter(np, internal_np::neighbor_radius), FT(0.0)); + + const std::function& callback = choose_parameter(get_parameter(np, internal_np::callback), + std::function()); + + // types for K nearest neighbors search structure + typedef Point_set_processing_3::internal::Neighbor_query Neighbor_query; + + CGAL_precondition(points.begin() != points.end()); + + // precondition: at least 2 nearest neighbors + // @todo fix this k as it does not exist + // CGAL_precondition(k >= 2); + + std::size_t memory = CGAL::Memory_sizer().virtual_size(); + CGAL_TRACE_STREAM << (memory >> 20) << " Mb allocated\n"; + CGAL_TRACE_STREAM << " Creates KD-tree\n"; + + Neighbor_query neighbor_query(points, point_map); + + // Input points types + typedef typename PointRange::iterator iterator; + typedef typename iterator::value_type value_type; + + std::size_t nb_points = points.size(); + + // calculate a loose bounding shpere + Sphere bsphere = CGAL::internal::calculate_loose_bsphere(points, point_map); + // estimate an epsilon band for the classical distance function + FT epsilon_band = CGAL::internal::classical_point_dist_func_epsilon_band(points, neighbor_query, point_map); + + Point_set_processing_3::internal::Callback_wrapper + callback_wrapper (callback, nb_points); + + CGAL::for_each + (points, + [&](value_type& vt) + { + if (callback_wrapper.interrupted()) + return false; + + const Point& point = get(point_map, vt); + Vector normal = get(normal_map, vt); + + bool need_jet_normal = false; + + FT squared_length = std::sqrt(normal.squared_length()); + if (squared_length == 0.0) + { + need_jet_normal = true; + } + else if (std::abs(squared_length - 1.0) > 1e-5) // normalize to unit vector + { + normal = normal / std::sqrt(squared_length); + } + + FT lfs = CGAL::internal::estimate_local_feature_size + (point, normal, neighbor_query, bsphere, + jet_k, neighbor_radius, degree_fitting, degree_monge, + epsilon_band, apex_angle, N_rays); + + put(lfs_map, vt, lfs); + + if (need_jet_normal) + put(normal_map, vt, normal); + + ++ callback_wrapper.advancement(); + + return true; + }); +} + +/** + \ingroup PkgPointSetProcessing3Algorithms + + Smooth the local feature size using median filter. + + + \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 knn number of neighbors for median filter + \param T number of iterations + \param LfsMap the map for the LFS value + \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 `ReadablePropertyMap` 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{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 The function will smooth the local feature size values directly in `lfs_map`. +*/ +template +void +median_filter_smoothing_lfs(PointRange& points, + const unsigned int knn, + const unsigned int T, + LfsMap lfs_map, + const NamedParameters& np = parameters::default_values()) +{ + // basic geometric types + typedef Point_set_processing_3_np_helper NP_helper; + typedef typename NP_helper::Geom_traits Kernel; + typedef typename Kernel::FT FT; + typedef typename PointRange::iterator iterator; + typedef typename iterator::value_type value_type; + + CGAL_precondition(points.begin() != points.end()); + + std::vector> knn_graph; + CGAL::internal::construct_knn_graph(points, knn, knn_graph, np); + + std::size_t nb_points = points.size(); + + assert(nb_points == knn_graph.size()); + + auto begin = boost::make_zip_iterator (boost::make_tuple (points.begin(), knn_graph.begin())); + auto end = boost::make_zip_iterator (boost::make_tuple (points.end(), knn_graph.end())); + + for (int t = 0; t < T; t++) + { + for(auto it = begin; it != end; it++) + { + value_type& vt = boost::get<0>(*it); + FT lfs = get(lfs_map, vt); + const std::vector& iterators = boost::get<1>(*it); + + std::vector knn_lfs_vals; + for (const auto& iterator : iterators) knn_lfs_vals.push_back(get(lfs_map, *iterator)); + const auto median = knn_lfs_vals.begin() + knn_lfs_vals.size() / 2; + std::nth_element(knn_lfs_vals.begin(), median, knn_lfs_vals.end()); + + put(lfs_map, vt, *median); + } + } +} + +/** + \ingroup PkgPointSetProcessing3Algorithms + + Smooth the local feature size based on the lipschitz continuity. + + + \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 knn number of neighbors for the smoothing based on the lipschitz continuity, suggested set it to be 1.0 + \param LfsMap the map for the LFS value + \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 `ReadablePropertyMap` 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{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 The function will smooth the local feature size values directly in `lfs_map`. +*/ +template +void +lipschitz_continuity_smoothing_lfs(PointRange& points, + const unsigned int knn, + const typename Point_set_processing_3_np_helper::Geom_traits::FT lipschitz, + LfsMap lfs_map, + const NamedParameters& np = parameters::default_values()) +{ + // basic geometric types + typedef Point_set_processing_3_np_helper NP_helper; + typedef typename NP_helper::Point_map PointMap; + typedef typename NP_helper::Geom_traits Kernel; + typedef typename Kernel::FT FT; + typedef typename PointRange::iterator iterator; + typedef typename iterator::value_type value_type; + + CGAL_precondition(points.begin() != points.end()); + + std::vector> knn_graph; + CGAL::internal::construct_knn_graph(points, knn, knn_graph, np); + + std::size_t nb_points = points.size(); + + assert(nb_points == knn_graph.size()); + + PointMap point_map = NP_helper::get_point_map(points, np); + + // Property map typename PointRange::iterator -> index + typedef Index_property_map IndexMap; + IndexMap index_map(points.begin(), points.end()); + + // starting from the minimum of the func_vals + auto min_iterator = std::min_element(points.begin(), points.end(), + [&](const value_type& vt1, const value_type& vt2) { + return get(lfs_map, vt1) < get(lfs_map, vt2); + } + ); + std::size_t min_lfs_index = std::distance(std::begin(points), min_iterator); + + std::unordered_map visited_map; + visited_map[min_lfs_index] = 1; + + std::function cmp = + [&](std::size_t ind1, std::size_t ind2) + { return get(lfs_map, *(points.begin() + ind1)) > get(lfs_map, *(points.begin() + ind2)); }; + std::priority_queue, decltype(cmp)> min_top_queue(cmp); + + for (iterator it = points.begin(); it != points.end(); it++) min_top_queue.push(get(index_map, it)); + + while (!min_top_queue.empty()) + { + std::size_t cur = min_top_queue.top(); + min_top_queue.pop(); + + // check if the current node satisfying lipschitz continuity + const std::vector& iterators = *(knn_graph.begin() + cur); + auto iterator_cur = points.begin() + cur; + FT cur_lfs = get(lfs_map, *iterator_cur); + for (const auto& iterator : iterators) + { + std::size_t nei = get(index_map, iterator); + auto iterator_nei = points.begin() + nei; + + if (nei == cur) continue; + + if (visited_map.find(nei) != visited_map.end()) + continue; + else + visited_map[nei] = 1; + + FT dist = CGAL::squared_distance(get(point_map, *iterator_cur), get(point_map, *iterator_nei)); + dist = std::sqrt(dist); + + FT nei_lfs = get(lfs_map, *iterator_nei); + + if (nei_lfs > cur_lfs) + { + if (std::abs(nei_lfs - cur_lfs) > lipschitz * dist) + nei_lfs = cur_lfs + dist; + put(lfs_map, *iterator_nei, nei_lfs); + } + else + { + min_top_queue.push(cur); + break; + } + } + } +} + +/** + \ingroup PkgPointSetProcessing3Algorithms + + Smooth the local feature size using median filter. + + + \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 knn number of neighbors for laplacian smoothing + \param T number of iterations + \param lambda lambda value for laplacian smoothing + \param LfsMap the map for the LFS value + \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 `ReadablePropertyMap` 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{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 The function will smooth the local feature size values directly in `lfs_map`. +*/ +template +void +laplacian_smoothing_lfs(PointRange& points, + const unsigned int knn, + const unsigned int T, + const typename Point_set_processing_3_np_helper::Geom_traits::FT lambda, + LfsMap lfs_map, + const NamedParameters& np = parameters::default_values()) +{ + // basic geometric types + typedef Point_set_processing_3_np_helper NP_helper; + typedef typename NP_helper::Geom_traits Kernel; + typedef typename Kernel::FT FT; + typedef typename PointRange::iterator iterator; + typedef typename iterator::value_type value_type; + + CGAL_precondition(points.begin() != points.end()); + + std::vector> knn_graph; + CGAL::internal::construct_knn_graph(points, knn, knn_graph, np); + + std::size_t nb_points = points.size(); + + assert(nb_points == knn_graph.size()); + + auto begin = boost::make_zip_iterator (boost::make_tuple (points.begin(), knn_graph.begin())); + auto end = boost::make_zip_iterator (boost::make_tuple (points.end(), knn_graph.end())); + + LfsMap smoothed_lfs_map = lfs_map; + for (int t = 0; t < T; t++) + { + for(auto it = begin; it != end; it++) + { + value_type& vt = boost::get<0>(*it); + FT lfs = get(lfs_map, vt); + const std::vector& iterators = boost::get<1>(*it); + + FT avg_lfs = 0.0; + for (const auto& iterator : iterators) avg_lfs += get(lfs_map, *iterator); + avg_lfs /= iterators.size(); + + FT smoothed_lfs = lfs + lambda * (avg_lfs - lfs); + put(smoothed_lfs_map, vt, smoothed_lfs); + } + } + + std::swap(lfs_map, smoothed_lfs_map); +} + +} //namespace CGAL + +#endif // CGAL_ESTIMATE_LFS_H diff --git a/Point_set_processing_3/test/Point_set_processing_3/CMakeLists.txt b/Point_set_processing_3/test/Point_set_processing_3/CMakeLists.txt index cbd77792290f..239710909f87 100644 --- a/Point_set_processing_3/test/Point_set_processing_3/CMakeLists.txt +++ b/Point_set_processing_3/test/Point_set_processing_3/CMakeLists.txt @@ -71,6 +71,9 @@ if(TARGET CGAL::Eigen3_support) create_single_source_cgal_program("psp_jet_includes.cpp") target_link_libraries(psp_jet_includes PUBLIC CGAL::Eigen3_support) + + create_single_source_cgal_program("lfs_estimation_test.cpp") + target_link_libraries(lfs_estimation_test PUBLIC CGAL::Eigen3_support) else() message(STATUS "NOTICE: Some tests require Eigen 3.1 (or greater), and will not be compiled.") endif() diff --git a/Point_set_processing_3/test/Point_set_processing_3/lfs_estimation_test.cpp b/Point_set_processing_3/test/Point_set_processing_3/lfs_estimation_test.cpp new file mode 100644 index 000000000000..5ded7ec6451c --- /dev/null +++ b/Point_set_processing_3/test/Point_set_processing_3/lfs_estimation_test.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +// types +typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; +typedef Kernel::FT FT; +typedef Kernel::Point_3 Point; +typedef Kernel::Vector_3 Vector; +typedef CGAL::Random_points_on_sphere_3 Random_points_on_sphere_3; + +typedef std::tuple Point_with_normal_and_lfs; + + +// Concurrency +typedef CGAL::Parallel_if_available_tag Concurrency_tag; + +void test (std::vector& input, + std::ptrdiff_t result0 = 1, int result1 = 1, int result2 = 1, int result3 = 1, int result4 = 1) +{ + ; +} + + +int main(void) +{ + const int num_points = 1000; // number of sampled points + FT r = 1.0; // unit sphere + + CGAL::Random rand = CGAL::Random(23); + + // std::vector point_coordinates; + // point_coordinates.reserve(num_points); + // std::copy_n(Random_points_on_sphere_3(r, rand), num_points, std::back_inserter(point_coordinates)); + + std::vector points; + std::generate_n(std::back_inserter(points), + num_points, + [&]() -> Point_with_normal_and_lfs { + Point point = *CGAL::Random_points_on_sphere_3(r, rand); + Vector normal = point - CGAL::ORIGIN; + return std::make_tuple(point, normal, FT(0)); + }); + + + unsigned int jet_k = 24; + std::size_t N_rays = 60; + FT apex_angle = 30; + auto lfs_map = CGAL::Nth_of_tuple_property_map<2, Point_with_normal_and_lfs>(); + CGAL::estimate_local_feature_size(points, + jet_k, + N_rays, + apex_angle, + lfs_map, + CGAL::parameters::point_map(CGAL::Nth_of_tuple_property_map<0, Point_with_normal_and_lfs>()) + .normal_map(CGAL::Nth_of_tuple_property_map<1, Point_with_normal_and_lfs>())); + + // evalute the LFS on the sphere + // the GT should be the radius + FT mse = 0.0; + for (const auto &pts : points) + { + FT lfs = std::get<2>(pts); + mse += std::pow((lfs - r), 2); + } + mse /= num_points; + + std::cout << "Mean Squared Error (MSE): " << mse << std::endl; + + return EXIT_SUCCESS; +} +