// Copyright (c) 2007-09 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/remove_outliers.h $ // $Id: remove_outliers.h 0715654 2020-06-03T19:01:46+02:00 Mael Rouxel-Labbé // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial // // Author(s) : Laurent Saboret and Nader Salman and Pierre Alliez #ifndef CGAL_REMOVE_OUTLIERS_H #define CGAL_REMOVE_OUTLIERS_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace CGAL { // ---------------------------------------------------------------------------- // Private section // ---------------------------------------------------------------------------- /// \cond SKIP_IN_MANUAL namespace internal { /// Utility function for remove_outliers(): /// Computes average squared distance to the K nearest neighbors. /// /// \pre `k >= 2` /// /// @tparam Kernel Geometric traits class. /// @tparam Tree KD-tree. /// /// @return computed distance. template typename NeighborQuery::Kernel::FT compute_avg_knn_sq_distance_3( const typename NeighborQuery::Kernel::Point_3& query, ///< 3D point to project NeighborQuery& neighbor_query, ///< KD-tree unsigned int k, ///< number of neighbors typename NeighborQuery::Kernel::FT neighbor_radius) { // geometric types typedef typename NeighborQuery::Kernel Kernel; typedef typename Kernel::FT FT; typedef typename Kernel::Point_3 Point; std::vector points; neighbor_query.get_points (query, k, neighbor_radius, std::back_inserter(points)); // compute average squared distance typename Kernel::Compute_squared_distance_3 sqd; FT sq_distance = (FT)0.0; for(typename std::vector::iterator neighbor = points.begin(); neighbor != points.end(); neighbor++) sq_distance += sqd(*neighbor, query); sq_distance /= FT(points.size()); return sq_distance; } } /* namespace internal */ /// \endcond // ---------------------------------------------------------------------------- // Public section // ---------------------------------------------------------------------------- /** \ingroup PkgPointSetProcessing3Algorithms Removes outliers: - computes average squared distance to the nearest neighbors, - and partitions the points either using a threshold on the of average distance or selecting a fixed percentage of points with the highest average distances 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 `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 number of neighbors \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{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{threshold_percent} \cgalParamDescription{the maximum percentage of points to remove} \cgalParamType{double} \cgalParamDefault{`10`} \cgalParamNEnd \cgalParamNBegin{threshold_distance} \cgalParamDescription{the minimum distance for a point to be considered as outlier} \cgalParamType{double} \cgalParamDefault{`0`} \cgalParamExtra{Distance here is the square root of the average squared distance to K-nearest neighbors} \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 `points.size()`.} \cgalParamExtra{The callback will be copied and therefore needs to be lightweight.} \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 iterator over the first point to remove. \note There are two thresholds that can be used: `threshold_percent` and `threshold_distance`. This function returns the smallest number of outliers such that at least one of these threshold is fulfilled. This means that if `threshold_percent=100`, only `threshold_distance` is taken into account; if `threshold_distance=0` only `threshold_percent` is taken into account. */ template typename PointRange::iterator remove_outliers( PointRange& points, unsigned int k, const NamedParameters& np) { using parameters::choose_parameter; using parameters::get_parameter; // geometric types typedef typename CGAL::GetPointMap::type PointMap; typedef typename Point_set_processing_3::GetK::Kernel Kernel; PointMap point_map = choose_parameter(get_parameter(np, internal_np::point_map)); typename Kernel::FT neighbor_radius = choose_parameter(get_parameter(np, internal_np::neighbor_radius), typename Kernel::FT(0)); double threshold_percent = choose_parameter(get_parameter(np, internal_np::threshold_percent), 10.); double threshold_distance = choose_parameter(get_parameter(np, internal_np::threshold_distance), 0.); const std::function& callback = choose_parameter(get_parameter(np, internal_np::callback), std::function()); typedef typename Kernel::FT FT; // basic geometric types typedef typename PointRange::iterator iterator; typedef typename iterator::value_type value_type; // types for K nearest neighbors search structure typedef Point_set_processing_3::internal::Neighbor_query Neighbor_query; // precondition: at least one element in the container. // to fix: should have at least three distinct points // but this is costly to check CGAL_point_set_processing_precondition(points.begin() != points.end()); // precondition: at least 2 nearest neighbors CGAL_point_set_processing_precondition(k >= 2); CGAL_point_set_processing_precondition(threshold_percent >= 0 && threshold_percent <= 100); Neighbor_query neighbor_query (points, point_map); std::size_t nb_points = points.size(); // iterate over input points and add them to multimap sorted by distance to k std::vector > sorted_points; sorted_points.reserve (nb_points); for (const value_type& p : points) sorted_points.push_back(std::make_pair (FT(0), p)); Point_set_processing_3::internal::Callback_wrapper callback_wrapper (callback, nb_points); CGAL::for_each (sorted_points, [&](std::pair& p) -> bool { if (callback_wrapper.interrupted()) return false; p.first = internal::compute_avg_knn_sq_distance_3( get(point_map, p.second), neighbor_query, k, neighbor_radius); ++ callback_wrapper.advancement(); return true; }); std::size_t first_index_to_remove = std::size_t(double(sorted_points.size()) * ((100.0-threshold_percent)/100.0)); typename std::vector >::iterator f2r = sorted_points.begin(); if (threshold_distance != FT(0)) f2r = std::partition (sorted_points.begin(), sorted_points.end(), [&threshold_distance](const std::pair& p) -> bool { return p.first < threshold_distance * threshold_distance; }); if (static_cast(std::distance (sorted_points.begin(), f2r)) < first_index_to_remove) { std::nth_element (f2r, sorted_points.begin() + first_index_to_remove, sorted_points.end()); f2r = sorted_points.begin() + first_index_to_remove; } // Replaces [points.begin(), points.end()) range by the sorted content. iterator pit = points.begin(); iterator out = points.begin(); for (auto sit = sorted_points.begin(); sit != sorted_points.end(); ++ sit) { *pit = sit->second; if (sit == f2r) out = pit; ++ pit; } callback_wrapper.join(); // Returns the iterator on the first point to remove return out; } /// \cond SKIP_IN_MANUAL // variant with default NP template typename PointRange::iterator remove_outliers( PointRange& points, unsigned int k) ///< number of neighbors. { return remove_outliers (points, k, CGAL::Point_set_processing_3::parameters::all_default(points)); } /// \endcond } //namespace CGAL #include #endif // CGAL_REMOVE_OUTLIERS_H