diff --git a/BGL/include/CGAL/boost/graph/named_params_helper.h b/BGL/include/CGAL/boost/graph/named_params_helper.h index db7473882b9..d3cd5b68461 100644 --- a/BGL/include/CGAL/boost/graph/named_params_helper.h +++ b/BGL/include/CGAL/boost/graph/named_params_helper.h @@ -25,6 +25,8 @@ #include #include #include +#include + #include #include @@ -500,6 +502,18 @@ CGAL_DEF_GET_INITIALIZED_INDEX_MAP(face, typename boost::graph_traits::fa > ::type type; }; + template + class GetAdjacencies + { + public: + typedef Emptyset_iterator Empty; + typedef typename internal_np::Lookup_named_param_def < + internal_np::adjacencies_t, + NamedParameters, + Empty//default + > ::type type; + }; + } // namespace Point_set_processing_3 template diff --git a/BGL/include/CGAL/boost/graph/parameters_interface.h b/BGL/include/CGAL/boost/graph/parameters_interface.h index 17cf689d262..02dbd7bacf8 100644 --- a/BGL/include/CGAL/boost/graph/parameters_interface.h +++ b/BGL/include/CGAL/boost/graph/parameters_interface.h @@ -147,6 +147,7 @@ CGAL_add_named_parameter(transformation_checkers_t, transformation_checkers, tra CGAL_add_named_parameter(inspector_t, inspector, inspector) CGAL_add_named_parameter(logger_t, logger, logger) CGAL_add_named_parameter(pointmatcher_config_t, pointmatcher_config, pointmatcher_config) +CGAL_add_named_parameter(adjacencies_t, adjacencies, adjacencies) // List of named parameters used in Surface_mesh_approximation package CGAL_add_named_parameter(verbose_level_t, verbose_level, verbose_level) @@ -176,4 +177,3 @@ CGAL_add_named_parameter(accuracy_t, accuracy, accuracy) CGAL_add_named_parameter(maximum_running_time_t, maximum_running_time, maximum_running_time) CGAL_add_named_parameter(overlap_t, overlap, overlap) CGAL_add_named_parameter(maximum_normal_deviation_t, maximum_normal_deviation, maximum_normal_deviation) - diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index dbc8910ac48..2f973b6c657 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -21,6 +21,10 @@ Release History the signed distance of two points to a line, or the line passing through two given points. Corresponding functors in the model (`Compare_signed_distance_to_line_2`) are also added. +### Point Set Processing + - Add a function `CGAL::cluster_point_set()` that segments a point + cloud into connected components based on a distance threshold. + ### 2D Triangulations - Add function `split_subconstraint_graph_into_constraints()` to `Constrained_triangulation_plus_2` to initialize the constraints diff --git a/Point_set_3/package_info/Point_set_3/dependencies b/Point_set_3/package_info/Point_set_3/dependencies index cab851ef527..6f0ec394588 100644 --- a/Point_set_3/package_info/Point_set_3/dependencies +++ b/Point_set_3/package_info/Point_set_3/dependencies @@ -1,5 +1,6 @@ Algebraic_foundations BGL +Circulator GraphicsView Installation Interval_support diff --git a/Point_set_processing_3/doc/Point_set_processing_3/NamedParameters.txt b/Point_set_processing_3/doc/Point_set_processing_3/NamedParameters.txt index 412250023a6..ba478f86b6f 100644 --- a/Point_set_processing_3/doc/Point_set_processing_3/NamedParameters.txt +++ b/Point_set_processing_3/doc/Point_set_processing_3/NamedParameters.txt @@ -140,7 +140,7 @@ is the minimum distance for a point to be considered as outlier \cgalNPEnd \cgalNPBegin{attraction_factor} \anchor PSP_attraction_factor -multiple of a tolerance `epsilon` used to connect simplices. +multiplication factor used for adjacency computations. \b Type: floating scalar value\n Default value: `3` \cgalNPEnd @@ -267,6 +267,12 @@ No default value. is for logging information regarding the process.\n \b Type: a model of `CGAL::pointmatcher::ICP_config`\n No default value. + +\cgalNPBegin{adjacencies} \anchor PSP_adjacencies +is an output iterator used to store adjacencies.\n +\b Type: a class model of `OutputIterator` that accepts objects of +type `std::pair`. \n +Default value: `CGAL::Emptyset_iterator`. \cgalNPEnd \cgalNPTableEnd 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 78e0ba58613..3d482fdc4f2 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 @@ -56,6 +56,7 @@ format. - `CGAL::pointmatcher::compute_registration_transformation()` - `CGAL::pointmatcher::register_point_sets()` - `CGAL::remove_outliers()` +- `CGAL::cluster_point_set()` - `CGAL::grid_simplify_point_set()` - `CGAL::random_simplify_point_set()` - `CGAL::hierarchy_simplify_point_set()` 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 b408d95b7bd..2c9af7219d1 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 @@ -612,7 +612,34 @@ individually. \cgalFigureCaptionEnd +\section Point_set_processing_3Clustering Clustering +If an input point set represents several objects which are spatially +separated, a clustering algorithm can be applied to identify connected +components on a nearest neighbor graph built using a query sphere of +fixed radius centered on each point. + +The clustering is stored in a cluster map which associates each input +point with the index of the cluster it belongs to: users can then use +this map however they find it relevant to their use case, for example +segmenting the input point set into one point set per +cluster. \cgalFigureRef{Point_set_processing_3figclustering} shows +different clustering outputs. + +\cgalFigureBegin{Point_set_processing_3figclustering,clustering.png} +Point Set Clustering outputs (one color per cluster). Top: input point +set and clustering using a neighbor radius of 1.5 (147 clusters +extracted). Bottom: clustering with neighbor radius 3.0 (37 clusters +extracted), and with neighbor radius 6.0 (5 clusters extracted). +\cgalFigureEnd + + +\subsection Point_set_processing_3Example_clustering Example + +In the following example, clusters (and adjacencies between them) are +computed and stored as colors in a PLY file: + +\cgalExample{Point_set_processing_3/clustering_example.cpp} \section Point_set_processing_3OutlierRemoval Outlier Removal @@ -968,4 +995,3 @@ libraries that perform registration on two point sets. */ } /* namespace CGAL */ - 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 3cdfe3727b9..2374e1ea02f 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 @@ -9,6 +9,7 @@ \example Point_set_processing_3/registration_with_OpenGR.cpp \example Point_set_processing_3/registration_with_pointmatcher.cpp \example Point_set_processing_3/registration_with_opengr_pointmatcher_pipeline.cpp +\example Point_set_processing_3/clustering_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_simplify_indices.cpp diff --git a/Point_set_processing_3/doc/Point_set_processing_3/fig/clustering.png b/Point_set_processing_3/doc/Point_set_processing_3/fig/clustering.png new file mode 100644 index 00000000000..e50b92a0059 Binary files /dev/null and b/Point_set_processing_3/doc/Point_set_processing_3/fig/clustering.png differ 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 36412ac7b68..85f5f79eed8 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 @@ -69,7 +69,11 @@ if ( CGAL_FOUND ) create_single_source_cgal_program( "normal_estimation.cpp" ) CGAL_target_use_Eigen(normal_estimation) + create_single_source_cgal_program( "clustering_example.cpp" ) + CGAL_target_use_Eigen(clustering_example) + create_single_source_cgal_program( "edges_example.cpp" ) + CGAL_target_use_Eigen(edges_example) # Executables that require libpointmatcher find_package(libpointmatcher QUIET) @@ -101,8 +105,6 @@ if ( CGAL_FOUND ) message(STATUS "NOTICE : registration_with_opengr_pointmatcher_pipeline requires libpointmatcher and OpenGR, and will not be compiled.") endif() - CGAL_target_use_Eigen(edges_example) - create_single_source_cgal_program( "callback_example.cpp" ) CGAL_target_use_Eigen(callback_example) @@ -132,7 +134,8 @@ if ( CGAL_FOUND ) normals_example jet_smoothing_example normal_estimation - callback_example) + callback_example + clustering_example) if(TARGET ${target}) CGAL_target_use_TBB(${target}) endif() diff --git a/Point_set_processing_3/examples/Point_set_processing_3/clustering_example.cpp b/Point_set_processing_3/examples/Point_set_processing_3/clustering_example.cpp new file mode 100644 index 00000000000..934609dd152 --- /dev/null +++ b/Point_set_processing_3/examples/Point_set_processing_3/clustering_example.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include + +using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using Point_3 = Kernel::Point_3; +using Point_set = CGAL::Point_set_3; + +int main (int argc, char** argv) +{ + // Read input file + std::ifstream ifile ((argc > 1) ? argv[1] : "data/hippo1.ply", std::ios_base::binary); + Point_set points; + ifile >> points; + + // Add a cluster map + Point_set::Property_map cluster_map = points.add_property_map ("cluster", -1).first; + + // Compute average spacing + double spacing = CGAL::compute_average_spacing (points, 12); + std::cerr << "Spacing = " << spacing << std::endl; + + // Adjacencies stored in vector + std::vector > adjacencies; + + // Compute clusters + CGAL::Real_timer t; + t.start(); + std::size_t nb_clusters + = CGAL::cluster_point_set (points, cluster_map, + points.parameters().neighbor_radius(spacing). + adjacencies (std::back_inserter (adjacencies))); + t.stop(); + std::cerr << "Found " << nb_clusters << " clusters with " << adjacencies.size() + << " adjacencies in " << t.time() << " seconds" << std::endl; + + // Output a colored PLY file + Point_set::Property_map red = points.add_property_map("red", 0).first; + Point_set::Property_map green = points.add_property_map("green", 0).first; + Point_set::Property_map blue = points.add_property_map("blue", 0).first; + for (Point_set::Index idx : points) + { + // One color per cluster + CGAL::Random rand (cluster_map[idx]); + red[idx] = rand.get_int(64, 192); + green[idx] = rand.get_int(64, 192); + blue[idx] = rand.get_int(64, 192); + } + + std::ofstream ofile ("out.ply", std::ios_base::binary); + CGAL::set_binary_mode (ofile); + ofile << points; + + return EXIT_SUCCESS; +} diff --git a/Point_set_processing_3/include/CGAL/Point_set_processing_3/internal/Neighbor_query.h b/Point_set_processing_3/include/CGAL/Point_set_processing_3/internal/Neighbor_query.h index 5e8996db035..bdd9291c559 100644 --- a/Point_set_processing_3/include/CGAL/Point_set_processing_3/internal/Neighbor_query.h +++ b/Point_set_processing_3/include/CGAL/Point_set_processing_3/internal/Neighbor_query.h @@ -14,6 +14,7 @@ #include +#include #include #include #include @@ -39,8 +40,13 @@ public: typedef PointMap Point_map; typedef typename Kernel::FT FT; + typedef typename boost::property_traits::value_type Point; + + typedef typename Kernel::Point_2 Point_2; typedef typename Kernel::Point_3 Point_3; + typedef std::is_same Is_2d; + typedef typename Range_iterator_type::type input_iterator; typedef typename input_iterator::value_type value_type; @@ -64,7 +70,10 @@ public: } }; - typedef CGAL::Search_traits_3 Tree_traits_base; + typedef typename std::conditional, + CGAL::Search_traits_3 >::type Tree_traits_base; + typedef CGAL::Search_traits_adapter Tree_traits; typedef CGAL::Sliding_midpoint Splitter; typedef CGAL::Distance_adapter > Distance; @@ -102,8 +111,8 @@ public: PointMap point_map() const { return m_point_map; } template - void get_iterators (const Point_3& query, unsigned int k, FT neighbor_radius, - OutputIterator output) const + void get_iterators (const Point& query, unsigned int k, FT neighbor_radius, + OutputIterator output, bool fallback_k_if_sphere_empty = true) const { if (neighbor_radius != FT(0)) { @@ -135,7 +144,7 @@ public: // Fallback, if less than 3 points are return, search for the 3 // first points - if (nb < 3) + if (fallback_k_if_sphere_empty && nb < 3) k = 3; // Else, no need to search for K nearest neighbors else @@ -163,7 +172,7 @@ public: } template - void get_points (const Point_3& query, unsigned int k, FT neighbor_radius, + void get_points (const Point& query, unsigned int k, FT neighbor_radius, OutputIterator output) const { return get_iterators(query, k, neighbor_radius, diff --git a/Point_set_processing_3/include/CGAL/Point_set_processing_3/internal/bbox_diagonal.h b/Point_set_processing_3/include/CGAL/Point_set_processing_3/internal/bbox_diagonal.h new file mode 100644 index 00000000000..84976b8f23c --- /dev/null +++ b/Point_set_processing_3/include/CGAL/Point_set_processing_3/internal/bbox_diagonal.h @@ -0,0 +1,59 @@ +// Copyright (c) 2020 GeometryFactory (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) : Simon Giraudot + +#ifndef CGAL_PSP_INTERNAL_BBOX_DIAGONAL_H +#define CGAL_PSP_INTERNAL_BBOX_DIAGONAL_H + +#include + +namespace CGAL +{ +namespace Point_set_processing_3 +{ +namespace internal +{ + +template +double bbox_diagonal (const PointRange& points, PointMap point_map, const typename Kernel::Point_2&) +{ + CGAL::Bbox_2 bbox = CGAL::bbox_2 (CGAL::make_transform_iterator_from_property_map (points.begin(), point_map), + CGAL::make_transform_iterator_from_property_map (points.end(), point_map)); + + return CGAL::approximate_sqrt + ((bbox.xmax() - bbox.xmin()) * (bbox.xmax() - bbox.xmin()) + + (bbox.ymax() - bbox.ymin()) * (bbox.ymax() - bbox.ymin())); +} + +template +double bbox_diagonal (const PointRange& points, PointMap point_map, const typename Kernel::Point_3&) +{ + CGAL::Bbox_3 bbox = CGAL::bbox_3 (CGAL::make_transform_iterator_from_property_map (points.begin(), point_map), + CGAL::make_transform_iterator_from_property_map (points.end(), point_map)); + + return CGAL::approximate_sqrt + ((bbox.xmax() - bbox.xmin()) * (bbox.xmax() - bbox.xmin()) + + (bbox.ymax() - bbox.ymin()) * (bbox.ymax() - bbox.ymin()) + + (bbox.zmax() - bbox.zmin()) * (bbox.zmax() - bbox.zmin())); +} + +template +double bbox_diagonal (const PointRange& points, PointMap point_map) +{ + typedef typename boost::property_traits::value_type Point; + return bbox_diagonal::Kernel> (points, point_map, Point()); +} + +} // namespace internal +} // namespace Point_set_processing_3 +} // namespace CGAL + + +#endif // CGAL_PSP_INTERNAL_BBOX_DIAGONAL_H diff --git a/Point_set_processing_3/include/CGAL/cluster_point_set.h b/Point_set_processing_3/include/CGAL/cluster_point_set.h new file mode 100644 index 00000000000..3098db06c97 --- /dev/null +++ b/Point_set_processing_3/include/CGAL/cluster_point_set.h @@ -0,0 +1,251 @@ +// Copyright (c) 2020 GeometryFactory Sarl (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) : Simon Giraudot + +#ifndef CGAL_CLUSTER_POINT_SET_H +#define CGAL_CLUSTER_POINT_SET_H + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace CGAL +{ + + +/// \cond SKIP_IN_MANUAL +namespace Point_set_processing_3 +{ + +namespace internal +{ + +// Trick to both compile version with Emptyset_iterator and with +// user-provided OutputIterator. Many output iterators (such as +// `std::back_insert_iterator`) cannot be default constructed, which +// makes the mechanism `choose_param(get_param(...),Default())` fails. +template +OutputIterator get_adjacencies (const NamedParameters& np, OutputIterator*) +{ + return CGAL::parameters::get_parameter(np, internal_np::adjacencies); +} + +template +CGAL::Emptyset_iterator get_adjacencies (const NamedParameters&, CGAL::Emptyset_iterator*) +{ + return CGAL::Emptyset_iterator(); +} + +} // namespace internal + +} // namespace Point_set_processing_3 +/// \endcond + +// ---------------------------------------------------------------------------- +// Public section +// ---------------------------------------------------------------------------- + +/** + \ingroup PkgPointSetProcessing3Algorithms + Identifies connected components on a nearest neighbor graph built + using a query sphere of fixed radius centered on each point. + + \tparam PointRange is a model of `Range`. The value type of its + iterator is the key type of the named parameter `point_map`. + \tparam ClusterMap is a model of `ReadWritePropertyMap` with value + type `std::size_t`. + + \param points input point range. + \param cluster_map maps each point to the index of the cluster it belongs to. + \param np optional sequence of \ref psp_namedparameters "Named Parameters" among the ones listed below. + + \cgalNamedParamsBegin + \cgalParamBegin{point_map} a model of `ReadablePropertyMap` with value type `geom_traits::Point_3`. + If this parameter is omitted, `CGAL::Identity_property_map` is used.\cgalParamEnd + \cgalParamBegin{callback} an instance of + `std::function`. 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 and the number of already + computed clusters is returned.\cgalParamEnd + \cgalParamBegin{neighbor_radius} spherical neighborhood + radius. If no value is provided, the default value is 1% of the + bounding box diagonal.\cgalParamEnd + \cgalParamBegin{attraction_factor} used to compute adjacencies + between clusters. Adjacencies are computed using a nearest + neighbor graph built similarly to the one used for clustering, + using `attraction_factor * neighbor_radius` as + parameter. %Default value is `2`.\cgalParamEnd + \cgalParamBegin{adjacencies} model of `OutputIterator` that + accepts objects of type `std::pair`. Each pair contains the indices of two adjacent + clusters. If this parameter is not used, adjacencies are not + computed at all.\cgalParamEnd + \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd + \cgalNamedParamsEnd + + \return the number of clusters identified. +*/ +template +std::size_t cluster_point_set (PointRange& points, + ClusterMap cluster_map, + 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::GetK::Kernel Kernel; + typedef typename Point_set_processing_3::GetAdjacencies::type Adjacencies; + + CGAL_static_assertion_msg(!(boost::is_same::type, + typename GetSvdTraits::NoTraits>::value), + "Error: no SVD traits"); + + PointMap point_map = choose_parameter(get_parameter(np, internal_np::point_map), PointMap()); + typename Kernel::FT neighbor_radius = choose_parameter(get_parameter(np, internal_np::neighbor_radius), + typename Kernel::FT(-1)); + typename Kernel::FT factor = choose_parameter(get_parameter(np, internal_np::attraction_factor), + typename Kernel::FT(2)); + + const std::function& callback = choose_parameter(get_parameter(np, internal_np::callback), + std::function()); + + double callback_factor = 1.; + if (!std::is_same::Empty>::value) + callback_factor = 0.5; + + // 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()); + + // If no radius is given, init with 1% of bbox diagonal + if (neighbor_radius < 0) + neighbor_radius = 0.01 * Point_set_processing_3::internal::bbox_diagonal (points, point_map); + + // Init cluster map with -1 + for (const value_type& p : points) + put (cluster_map, p, -1); + + Neighbor_query neighbor_query (points, point_map); + + std::queue todo; + std::size_t nb_clusters = 0; + + // Flooding algorithm from each point + std::size_t done = 0; + std::size_t size = points.size(); + + for (iterator it = points.begin(); it != points.end(); ++ it) + { + const value_type& p = *it; + + if (int(get (cluster_map, p)) != -1) + continue; + + todo.push (it); + + while (!todo.empty()) + { + iterator current = todo.front(); + todo.pop(); + + if (int(get (cluster_map, *current)) != -1) + continue; + + put (cluster_map, *current, nb_clusters); + ++ done; + + if (callback && !callback (callback_factor * (done + 1) / double(size))) + return (nb_clusters + 1); + + neighbor_query.get_iterators (get (point_map, *current), 0, neighbor_radius, + boost::make_function_output_iterator + ([&](const iterator& it) { todo.push(it); }), true); + + } + + ++ nb_clusters; + } + + if (!std::is_same::Empty>::value) + { + Adjacencies adjacencies = Point_set_processing_3::internal::get_adjacencies(np, (Adjacencies*)(nullptr)); + neighbor_radius *= factor; + + std::vector neighbors; + std::vector > adj; + + done = 0; + for (const value_type& p : points) + { + std::size_t c0 = get (cluster_map, p); + + neighbors.clear(); + neighbor_query.get_iterators (get (point_map, p), 0, neighbor_radius, + std::back_inserter (neighbors), false); + + for (const iterator& it : neighbors) + { + std::size_t c1 = get (cluster_map, *it); + if (c0 < c1) + adj.push_back (std::make_pair (c0, c1)); + else if (c0 > c1) + adj.push_back (std::make_pair (c1, c0)); + // else c0 == c1, ignore + } + + ++ done; + if (callback && !callback (callback_factor + callback_factor * (done + 1) / double(size))) + return nb_clusters; + } + std::sort (adj.begin(), adj.end()); + auto last = std::unique (adj.begin(), adj.end()); + std::copy (adj.begin(), last, adjacencies); + } + + return nb_clusters; +} + +/// \cond SKIP_IN_MANUAL +// overload with default NP +template +std::size_t cluster_point_set (PointRange& points, + ClusterMap cluster_map, + unsigned int k) +{ + return cluster_point_set (points, cluster_map, k, + CGAL::Point_set_processing_3::parameters::all_default(points)); +} +/// \endcond + +} // namespace CGAL + + +#endif // CGAL_CLUSTER_POINT_SET_H diff --git a/Polyhedron/demo/Polyhedron/Plugins/Point_set/CMakeLists.txt b/Polyhedron/demo/Polyhedron/Plugins/Point_set/CMakeLists.txt index f1f2c7e4d0f..d29ca1cf7b8 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Point_set/CMakeLists.txt +++ b/Polyhedron/demo/Polyhedron/Plugins/Point_set/CMakeLists.txt @@ -94,6 +94,9 @@ endif() polyhedron_demo_plugin(point_set_wlop_plugin Point_set_wlop_plugin ${point_set_wlopFILES} KEYWORDS PointSetProcessing) target_link_libraries(point_set_wlop_plugin PUBLIC scene_points_with_normal_item scene_callback_signaler) + polyhedron_demo_plugin(point_set_clustering_plugin Point_set_clustering_plugin KEYWORDS PointSetProcessing) + target_link_libraries(point_set_clustering_plugin PUBLIC scene_points_with_normal_item scene_callback_signaler) + polyhedron_demo_plugin(merge_point_sets_plugin Merge_point_sets_plugin KEYWORDS PointSetProcessing Classification) target_link_libraries(merge_point_sets_plugin PUBLIC scene_points_with_normal_item) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_clustering_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_clustering_plugin.cpp new file mode 100644 index 00000000000..951ca806115 --- /dev/null +++ b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Point_set_clustering_plugin.cpp @@ -0,0 +1,241 @@ +#include "config.h" +#include "Scene_points_with_normal_item.h" +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "run_with_qprogressdialog.h" + +struct Clustering_functor + : public Functor_with_signal_callback +{ + Point_set* points; + Point_set::Property_map cluster_map; + const double neighbor_radius; + boost::shared_ptr result; + + Clustering_functor (Point_set* points, + const double neighbor_radius, + Point_set::Property_map cluster_map) + : points (points), cluster_map (cluster_map), + neighbor_radius (neighbor_radius), + result (new std::size_t(0)) { } + + void operator()() + { + *result = CGAL::cluster_point_set (*points, cluster_map, + points->parameters().neighbor_radius(neighbor_radius). + callback(*(this->callback()))); + } +}; + +using namespace CGAL::Three; + +class Polyhedron_demo_point_set_clustering_plugin : + public QObject, + public Polyhedron_demo_plugin_helper +{ + Q_OBJECT + Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface) + Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0") + + QAction* actionCluster; + +public: + void init(QMainWindow* mainWindow, CGAL::Three::Scene_interface* scene_interface, Messages_interface*) { + + scene = scene_interface; + mw = mainWindow; + actionCluster = new QAction(tr("Cluster Point Set"), mainWindow); + actionCluster->setObjectName("actionCluster"); + actionCluster->setProperty("subMenuName","Point Set Processing"); + autoConnectActions(); + } + + QList actions() const { + return QList() << actionCluster; + } + + bool applicable(QAction*) const { + Scene_points_with_normal_item* item = qobject_cast(scene->item(scene->mainSelectionIndex())); + return item; + } + +public Q_SLOTS: + void on_actionCluster_triggered(); + +}; // end + +void Polyhedron_demo_point_set_clustering_plugin::on_actionCluster_triggered() +{ + const CGAL::Three::Scene_interface::Item_id index = scene->mainSelectionIndex(); + + Scene_points_with_normal_item* item = + qobject_cast(scene->item(index)); + + if(item) + { + // Gets point set + Point_set* points = item->point_set(); + if(points == NULL) + return; + + QMultipleInputDialog dialog ("Clustering", mw); + QDoubleSpinBox* neighbor_radius = dialog.add ("Neighbor radius (0 = automatic):"); + neighbor_radius->setRange (0, 10000000); + neighbor_radius->setValue (0); + QSpinBox* min_nb = dialog.add ("Minimum number of points per cluster:"); + min_nb->setRange (1, 10000000); + min_nb->setValue (1); + + QCheckBox* add_property = dialog.add ("Add a \"cluster\" property to the input item"); + add_property->setChecked (true); + + QCheckBox* gen_color = dialog.add ("Generate one colored point set"); + gen_color->setChecked (true); + + QCheckBox* gen_sub = dialog.add ("Generate N point subsets"); + gen_sub->setChecked (false); + + if (!dialog.exec()) + return; + + QApplication::setOverrideCursor(Qt::BusyCursor); + QApplication::processEvents(); + CGAL::Real_timer task_timer; task_timer.start(); + + Point_set::Property_map cluster_map; + + if (add_property->isChecked()) + cluster_map = points->add_property_map ("cluster_map").first; + else + // Use long name to avoid overwriting potentially existing map + cluster_map = points->add_property_map ("cluster_point_set_property_map").first; + + // Default value + if (neighbor_radius->value() == 0) + { + neighbor_radius->setRange (-1, 10000000); + neighbor_radius->setValue(-1); + } + + // Computes average spacing + Clustering_functor functor (points, neighbor_radius->value(), cluster_map); + run_with_qprogressdialog (functor, "Clustering...", mw); + + std::size_t nb_clusters = *functor.result; + + Scene_group_item* group = nullptr; + std::vector new_items; + + if (gen_sub->isChecked()) + { + group = new Scene_group_item(QString("%1 (clusters)").arg(item->name())); + scene->addItem(group); + new_items.reserve (nb_clusters); + for (std::size_t i = 0; i < nb_clusters; ++ i) + { + Scene_points_with_normal_item* new_item = new Scene_points_with_normal_item; + new_item->point_set()->copy_properties (*points); + CGAL::Random rand((unsigned int)(i)); + unsigned char r, g, b; + r = static_cast(64 + rand.get_int(0, 192)); + g = static_cast(64 + rand.get_int(0, 192)); + b = static_cast(64 + rand.get_int(0, 192)); + new_item->setRgbColor(r, g, b); + new_item->setName (QString("Cluster %1 of %2").arg(i).arg(item->name())); + new_items.push_back (new_item); + } + } + + std::vector cluster_size (nb_clusters, 0); + for (Point_set::Index idx : *points) + { + if (gen_sub->isChecked()) + new_items[cluster_map[idx]]->point_set()->insert (*points, idx); + cluster_size[cluster_map[idx]] ++; + } + + if (gen_color->isChecked()) + { + Scene_points_with_normal_item* colored; + Point_set::Property_map red, green, blue; + + colored = new Scene_points_with_normal_item; + colored->setName (QString("%1 (clustering)").arg(item->name())); + + red = colored->point_set()->add_property_map("red", 0).first; + green = colored->point_set()->add_property_map("green", 0).first; + blue = colored->point_set()->add_property_map("blue", 0).first; + colored->point_set()->check_colors(); + + colored->point_set()->reserve (points->size()); + + for (Point_set::Index idx : *points) + { + Point_set::Index iidx = *(colored->point_set()->insert (points->point(idx))); + if (cluster_size[cluster_map[idx]] >= std::size_t(min_nb->value())) + { + CGAL::Random rand((unsigned int)(cluster_map[idx] + 1)); + unsigned char r, g, b; + r = static_cast(64 + rand.get_int(0, 192)); + g = static_cast(64 + rand.get_int(0, 192)); + b = static_cast(64 + rand.get_int(0, 192)); + red[iidx] = r; + green[iidx] = g; + blue[iidx] = b; + } + } + scene->addItem(colored); + } + + if (gen_sub->isChecked()) + { + for (Scene_points_with_normal_item* new_item : new_items) + { + if (new_item->point_set()->size() >= std::size_t(min_nb->value())) + { + scene->addItem(new_item); + scene->changeGroup (new_item, group); + } + else + delete new_item; + } + } + + if (!add_property->isChecked()) + points->remove_property_map (cluster_map); + + std::size_t memory = CGAL::Memory_sizer().virtual_size(); + std::cerr << "Number of clusters = " << nb_clusters << " (" + << task_timer.time() << " seconds, " + << (memory>>20) << " Mb allocated)" + << std::endl; + QApplication::restoreOverrideCursor(); + + item->setVisible (false); + item->invalidateOpenGLBuffers(); + scene->itemChanged(item); + } +} + + + +#include "Point_set_clustering_plugin.moc" diff --git a/Property_map/include/CGAL/property_map.h b/Property_map/include/CGAL/property_map.h index f58413b9755..988c7ab6583 100644 --- a/Property_map/include/CGAL/property_map.h +++ b/Property_map/include/CGAL/property_map.h @@ -27,6 +27,8 @@ #include // defines std::pair +#include +#include #include #include #include @@ -576,6 +578,38 @@ make_cartesian_converter_property_map(Vpm vpm) return Cartesian_converter_property_map(vpm); } +/// \cond SKIP_IN_MANUAL +// Syntaxic sugar for transform_iterator+pmap_to_unary_function +template +typename boost::transform_iterator, Iterator> +make_transform_iterator_from_property_map (Iterator it, Pmap pmap) +{ + return boost::make_transform_iterator (it, CGAL::Property_map_to_unary_function(pmap)); +} + +// Syntaxic sugar for make_range+transform_iterator+pmap_to_unary_function +template +CGAL::Iterator_range, + typename Range::const_iterator> > +make_transform_range_from_property_map (const Range& range, Pmap pmap) +{ + return CGAL::make_range + (make_transform_iterator_from_property_map (range.begin(), pmap), + make_transform_iterator_from_property_map (range.end(), pmap)); +} + +// Syntaxic sugar for make_range+transform_iterator+pmap_to_unary_function +template +CGAL::Iterator_range, + typename Range::iterator> > +make_transform_range_from_property_map (Range& range, Pmap pmap) +{ + return CGAL::make_range + (make_transform_iterator_from_property_map (range.begin(), pmap), + make_transform_iterator_from_property_map (range.end(), pmap)); +} +/// \endcond + } // namespace CGAL diff --git a/STL_Extension/include/CGAL/iterator.h b/STL_Extension/include/CGAL/iterator.h index 16733969b61..ea2b445b6f5 100644 --- a/STL_Extension/include/CGAL/iterator.h +++ b/STL_Extension/include/CGAL/iterator.h @@ -1480,8 +1480,6 @@ struct Range_iterator_type { typedef typename RangeRef::iterato template struct Range_iterator_type { typedef typename RangeRef::const_iterator type; }; - - } //namespace CGAL #include