diff --git a/.github/workflows/build_doc.yml b/.github/workflows/build_doc.yml index 5378f637349..12bbd688af9 100644 --- a/.github/workflows/build_doc.yml +++ b/.github/workflows/build_doc.yml @@ -42,6 +42,7 @@ jobs: repository: ${{ github.repository }} ref: refs/pull/${{ steps.get_pr_number.outputs.result }}/merge token: ${{ secrets.PUSH_TO_CGAL_GITHUB_IO_TOKEN }} + fetch-depth: 2 - name: install dependencies @@ -60,10 +61,9 @@ jobs: if: steps.get_round.outputs.result != 'stop' run: | set -ex - git clone https://CGAL:${{ secrets.PUSH_TO_CGAL_GITHUB_IO_TOKEN }}@github.com/CGAL/cgal.github.io.git --depth=5 mkdir -p build_doc && cd build_doc && cmake ../Documentation/doc - - name: Upload Doc + - name: Build and Upload Doc if: steps.get_round.outputs.result != 'stop' run: | set -ex @@ -71,14 +71,27 @@ jobs: ROUND=${{ steps.get_round.outputs.result }} wget --no-verbose cgal.github.io -O tmp.html if ! egrep -q "\/$PR_NUMBER\/$ROUND" tmp.html; then - mkdir -p cgal.github.io/${PR_NUMBER}/$ROUND + #list impacted packages + LIST_OF_PKGS=$(git diff --name-only HEAD^1 HEAD |cut -s -d/ -f1 |sort -u | xargs -I {} ls -d {}/package_info 2>/dev/null |cut -d/ -f1 |egrep -v Installation||true) + if [ "$LIST_OF_PKGS" = "" ]; then + exit 1 + fi cd build_doc && make -j2 doc && make -j2 doc_with_postprocessing - cp -r ./doc_output/* ../cgal.github.io/${PR_NUMBER}/$ROUND - cd ../cgal.github.io - egrep -v " ${PR_NUMBER}\." index.html > tmp.html + cd .. + git clone https://CGAL:${{ secrets.PUSH_TO_CGAL_GITHUB_IO_TOKEN }}@github.com/CGAL/cgal.github.io.git + mkdir -p cgal.github.io/${PR_NUMBER}/$ROUND + for f in $LIST_OF_PKGS + do + if [ -d ./build_doc/doc_output/$f ]; then + cp -r ./build_doc/doc_output/$f ./cgal.github.io/${PR_NUMBER}/$ROUND + fi + done + cp -r ./build_doc/doc_output/Manual ./cgal.github.io/${PR_NUMBER}/$ROUND + cd ./cgal.github.io + egrep -v " ${PR_NUMBER}\." index.html > tmp.html || true echo "
  • Manual for PR ${PR_NUMBER} ($ROUND).
  • " >> ./tmp.html mv tmp.html index.html - git add ${PR_NUMBER}/$ROUND && git commit -q -a -m "Add ${PR_NUMBER} $ROUND" && git push -q -u origin master + git add ${PR_NUMBER}/$ROUND && git commit -q --amend -m "base commit" && git push -q -f -u origin master else exit 1 fi @@ -88,7 +101,7 @@ jobs: if: steps.get_round.outputs.result != 'stop' with: script: | - const address = "The documentation is built. You can find it here : https://cgal.github.io/${{ steps.get_pr_number.outputs.result }}/${{ steps.get_round.outputs.result }}/Manual/index.html" + const address = "The documentation is built. It will be available, after a few minutes, here : https://cgal.github.io/${{ steps.get_pr_number.outputs.result }}/${{ steps.get_round.outputs.result }}/Manual/index.html" github.issues.createComment({ owner: "CGAL", repo: "cgal", diff --git a/.github/workflows/delete_doc.yml b/.github/workflows/delete_doc.yml index c1152d37ca9..1304a7895c9 100644 --- a/.github/workflows/delete_doc.yml +++ b/.github/workflows/delete_doc.yml @@ -18,10 +18,15 @@ jobs: git clone https://maxGimeno:${{ secrets.PUSH_TO_CGAL_GITHUB_IO_TOKEN }}@github.com/CGAL/cgal.github.io.git --depth=5 PR_NUMBER=$(python -c "import json; import os; y = json.load(open(os.environ['GITHUB_EVENT_PATH'])); print(y[\"number\"])") cd cgal.github.io/ - egrep -v " ${PR_NUMBER}\." index.html > tmp.html + egrep -v " ${PR_NUMBER}\." index.html > tmp.html || true if [ -n "$(diff -q ./index.html ./tmp.html)" ]; then mv tmp.html index.html - #git rm -r ${PR_NUMBER} && git commit -a -m "Remove ${PR_NUMBER}" && git push -u origin master - git commit -a -m "Remove ${PR_NUMBER}" && git push -u origin master + fi + if [ -d ${PR_NUMBER} ]; then + git rm -r ${PR_NUMBER} + fi + #git diff exits with 1 if there is a diff + if !git diff --quiet; then + git commit -a --amend -m"base commit" && git push -f -u origin master fi diff --git a/.travis/build_package.sh b/.travis/build_package.sh index 0d275ee5885..dc41aa16dd1 100755 --- a/.travis/build_package.sh +++ b/.travis/build_package.sh @@ -88,6 +88,7 @@ cd $ROOT echo '#include "CGAL/remove_outliers.h"' >> main.cpp cd build mytime cmake -DCMAKE_INSTALL_PREFIX=../../install -DCGAL_BUILD_THREE_DOC=TRUE .. + exit 0 fi if [ "$ARG" = "Installation" ] diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_dcel_base.h b/Arrangement_on_surface_2/include/CGAL/Arr_dcel_base.h index 16befc318ee..16a15d7ea56 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_dcel_base.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_dcel_base.h @@ -91,6 +91,11 @@ public: /*! Destructor. */ virtual ~Arr_vertex_base() {} + // Access/modification for pointer squatting + void* inc() const { return p_inc; } + void set_inc(void * inc) const + { const_cast(*this).p_inc = inc; } + /*! Check if the point pointer is nullptr. */ bool has_null_point() const { return (p_pt == nullptr); } diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_overlay_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_overlay_2.h index e4c739c8c23..393958f3424 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_overlay_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_overlay_2.h @@ -40,6 +40,89 @@ namespace CGAL { +template +class Indexed_sweep_accessor +{ + const Arr1& arr1; + const Arr2& arr2; + mutable std::vector backup_inc; + +public: + + Indexed_sweep_accessor (const Arr1& arr1, const Arr2& arr2) + : arr1(arr1), arr2(arr2) { } + + std::size_t nb_vertices() const + { + return arr1.number_of_vertices() + arr2.number_of_vertices(); + } + + std::size_t min_end_index (const Curve& c) const + { + if (c.red_halfedge_handle() != typename Curve::HH_red()) + return reinterpret_cast(c.red_halfedge_handle()->target()->inc()); + // else + CGAL_assertion (c.blue_halfedge_handle() != typename Curve::HH_blue()); + return reinterpret_cast(c.blue_halfedge_handle()->target()->inc()); + } + + std::size_t max_end_index (const Curve& c) const + { + if (c.red_halfedge_handle() != typename Curve::HH_red()) + return reinterpret_cast(c.red_halfedge_handle()->source()->inc()); + // else + CGAL_assertion (c.blue_halfedge_handle() != typename Curve::HH_blue()); + return reinterpret_cast(c.blue_halfedge_handle()->source()->inc()); + } + + const Curve& curve (const Curve& c) const + { + return c; + } + + // Initializes indices by squatting Vertex::inc(); + void before_init() const + { + std::size_t idx = 0; + backup_inc.resize (nb_vertices()); + for (typename Arr1::Vertex_const_iterator vit = arr1.vertices_begin(); + vit != arr1.vertices_end(); ++vit, ++idx) + { + CGAL_assertion (idx < backup_inc.size()); + backup_inc[idx] = vit->inc(); + vit->set_inc (reinterpret_cast(idx)); + } + for (typename Arr2::Vertex_const_iterator vit = arr2.vertices_begin(); + vit != arr2.vertices_end(); ++vit, ++idx) + { + CGAL_assertion (idx < backup_inc.size()); + backup_inc[idx] = vit->inc(); + vit->set_inc (reinterpret_cast(idx)); + } + } + + // Restores state of arrangements before index squatting + void after_init() const + { + std::size_t idx = 0; + for (typename Arr1::Vertex_const_iterator vit = arr1.vertices_begin(); + vit != arr1.vertices_end(); ++vit, ++idx) + { + CGAL_assertion (idx < backup_inc.size()); + vit->set_inc (backup_inc[idx]); + } + for (typename Arr2::Vertex_const_iterator vit = arr2.vertices_begin(); + vit != arr2.vertices_end(); ++vit, ++idx) + { + CGAL_assertion (idx < backup_inc.size()); + vit->set_inc (backup_inc[idx]); + } + } + +private: + +}; + /*! Compute the overlay of two input arrangements. * \tparam GeometryTraitsA_2 the geometry traits of the first arrangement. * \tparam GeometryTraitsB_2 the geometry traits of the second arrangement. @@ -183,7 +266,14 @@ overlay(const Arrangement_on_surface_2& arr1 if (total_iso_verts == 0) { // Clear the result arrangement and perform the sweep to construct it. arr.clear(); - surface_sweep.sweep(xcvs_vec.begin(), xcvs_vec.end()); + if (std::is_same::value) + surface_sweep.sweep (xcvs_vec.begin(), xcvs_vec.end()); + else + surface_sweep.indexed_sweep (xcvs_vec, + Indexed_sweep_accessor + + (arr1, arr2)); xcvs_vec.clear(); return; } @@ -215,8 +305,16 @@ overlay(const Arrangement_on_surface_2& arr1 // Clear the result arrangement and perform the sweep to construct it. arr.clear(); - surface_sweep.sweep(xcvs_vec.begin(), xcvs_vec.end(), - pts_vec.begin(), pts_vec.end()); + if (std::is_same::value) + surface_sweep.sweep(xcvs_vec.begin(), xcvs_vec.end(), + pts_vec.begin(), pts_vec.end()); + else + surface_sweep.indexed_sweep (xcvs_vec, + Indexed_sweep_accessor + + (arr1, arr2), + pts_vec.begin(), pts_vec.end()); xcvs_vec.clear(); pts_vec.clear(); } diff --git a/Arrangement_on_surface_2/include/CGAL/Surface_sweep_2/Arr_overlay_traits_2.h b/Arrangement_on_surface_2/include/CGAL/Surface_sweep_2/Arr_overlay_traits_2.h index 48815d767f6..3243890c1be 100644 --- a/Arrangement_on_surface_2/include/CGAL/Surface_sweep_2/Arr_overlay_traits_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Surface_sweep_2/Arr_overlay_traits_2.h @@ -133,6 +133,8 @@ public: class Ex_x_monotone_curve_2 { public: typedef Base_x_monotone_curve_2 Base; + typedef Halfedge_handle_red HH_red; + typedef Halfedge_handle_blue HH_blue; protected: Base m_base_xcv; // The base curve. diff --git a/BGL/doc/BGL/Concepts/FaceGraph.h b/BGL/doc/BGL/Concepts/FaceGraph.h index 08501700261..40346595ac6 100644 --- a/BGL/doc/BGL/Concepts/FaceGraph.h +++ b/BGL/doc/BGL/Concepts/FaceGraph.h @@ -25,7 +25,7 @@ A face descriptor must be `DefaultConstructible`, `Assignable`, `EqualityCompara \sa \link PkgBGLConcepts Graph Concepts \endlink */ class FaceGraph { - /// Returns a special `boost::graph_traits::face_descriptor` object which + /// Returns a special `boost::graph_traits::%face_descriptor` object which /// does not refer to any face of graph object which type is `FaceGraph`. static boost::graph_traits::halfedge_descriptor null_face(); }; diff --git a/BGL/doc/BGL/Concepts/HalfedgeGraph.h b/BGL/doc/BGL/Concepts/HalfedgeGraph.h index b69cbb20447..3769db82e16 100644 --- a/BGL/doc/BGL/Concepts/HalfedgeGraph.h +++ b/BGL/doc/BGL/Concepts/HalfedgeGraph.h @@ -44,7 +44,7 @@ A model of `HalfedgeGraph` must have the interior property `vertex_point` attach \sa \link PkgBGLConcepts Graph Concepts \endlink */ class HalfedgeGraph { - /// Returns a special `boost::graph_traits::halfedge_descriptor` object which + /// Returns a special `boost::graph_traits::%halfedge_descriptor` object which /// does not refer to any halfedge of graph object which type is `HalfedgeGraph`. static boost::graph_traits::halfedge_descriptor null_halfedge(); }; diff --git a/CGAL_ImageIO/include/CGAL/ImageIO/analyze_impl.h b/CGAL_ImageIO/include/CGAL/ImageIO/analyze_impl.h index 1154d6b361a..1c326b6fa1f 100644 --- a/CGAL_ImageIO/include/CGAL/ImageIO/analyze_impl.h +++ b/CGAL_ImageIO/include/CGAL/ImageIO/analyze_impl.h @@ -33,17 +33,18 @@ /** Magic header for ANALYZE files written in big endian format */ #define ANALYZE_BE_MAGIC "\134\001\000\000" -#define DT_NONE 0 -#define DT_UNKNOWN 0 /*Unknown data type*/ -#define DT_BINARY 1 /*Binary (1 bit per voxel)*/ -#define DT_UNSIGNED_CHAR 2 /*Unsigned character (8 bits per voxel)*/ -#define DT_SIGNED_SHORT 4 /*Signed short (16 bits per voxel)*/ -#define DT_SIGNED_INT 8 /*Signed integer (32 bits per voxel)*/ -#define DT_FLOAT 16 /*Floating point (32 bits per voxel)*/ -#define DT_COMPLEX 32 /*Complex (64 bits per voxel; 2 floating point numbers) */ -#define DT_DOUBLE 64 /*Double precision (64 bits per voxel)*/ -#define DT_RGB 128 /* */ -#define DT_ALL 255 /* */ +//use prefix CGAL_analyze_impl_ to avoid clashing and breaking dirent.h +#define CGAL_analyze_impl_DT_NONE 0 +#define CGAL_analyze_impl_DT_UNKNOWN 0 /*Unknown data type*/ +#define CGAL_analyze_impl_DT_BINARY 1 /*Binary (1 bit per voxel)*/ +#define CGAL_analyze_impl_DT_UNSIGNED_CHAR 2 /*Unsigned character (8 bits per voxel)*/ +#define CGAL_analyze_impl_DT_SIGNED_SHORT 4 /*Signed short (16 bits per voxel)*/ +#define CGAL_analyze_impl_DT_SIGNED_INT 8 /*Signed integer (32 bits per voxel)*/ +#define CGAL_analyze_impl_DT_FLOAT 16 /*Floating point (32 bits per voxel)*/ +#define CGAL_analyze_impl_DT_COMPLEX 32 /*Complex (64 bits per voxel; 2 floating point numbers) */ +#define CGAL_analyze_impl_DT_DOUBLE 64 /*Double precision (64 bits per voxel)*/ +#define CGAL_analyze_impl_DT_RGB 128 /* */ +#define CGAL_analyze_impl_DT_ALL 255 /* */ #include @@ -373,17 +374,17 @@ int _readAnalyzeHeader( _image* im, const char* name, switch(analyzeHeader->dime.datatype) { - case DT_BINARY: - case DT_UNSIGNED_CHAR: - case DT_SIGNED_SHORT: - case DT_SIGNED_INT: - case DT_FLOAT: - case DT_COMPLEX: - case DT_DOUBLE: + case CGAL_analyze_impl_DT_BINARY: + case CGAL_analyze_impl_DT_UNSIGNED_CHAR: + case CGAL_analyze_impl_DT_SIGNED_SHORT: + case CGAL_analyze_impl_DT_SIGNED_INT: + case CGAL_analyze_impl_DT_FLOAT: + case CGAL_analyze_impl_DT_COMPLEX: + case CGAL_analyze_impl_DT_DOUBLE: im->vdim = 1; break ; - case DT_RGB: + case CGAL_analyze_impl_DT_RGB: im->vdim = 3; break ; @@ -396,17 +397,17 @@ int _readAnalyzeHeader( _image* im, const char* name, switch(analyzeHeader->dime.datatype) { - case DT_BINARY: - case DT_UNSIGNED_CHAR: - case DT_SIGNED_SHORT: - case DT_SIGNED_INT: - case DT_RGB: + case CGAL_analyze_impl_DT_BINARY: + case CGAL_analyze_impl_DT_UNSIGNED_CHAR: + case CGAL_analyze_impl_DT_SIGNED_SHORT: + case CGAL_analyze_impl_DT_SIGNED_INT: + case CGAL_analyze_impl_DT_RGB: im->wordKind = WK_FIXED; break ; - case DT_FLOAT: - case DT_COMPLEX: - case DT_DOUBLE: + case CGAL_analyze_impl_DT_FLOAT: + case CGAL_analyze_impl_DT_COMPLEX: + case CGAL_analyze_impl_DT_DOUBLE: im->wordKind = WK_FLOAT; break ; @@ -419,17 +420,17 @@ int _readAnalyzeHeader( _image* im, const char* name, switch(analyzeHeader->dime.datatype) { - case DT_BINARY: - case DT_UNSIGNED_CHAR: - case DT_RGB: + case CGAL_analyze_impl_DT_BINARY: + case CGAL_analyze_impl_DT_UNSIGNED_CHAR: + case CGAL_analyze_impl_DT_RGB: im->sign = SGN_UNSIGNED; break ; - case DT_SIGNED_SHORT: - case DT_SIGNED_INT: - case DT_FLOAT: - case DT_COMPLEX: - case DT_DOUBLE: + case CGAL_analyze_impl_DT_SIGNED_SHORT: + case CGAL_analyze_impl_DT_SIGNED_INT: + case CGAL_analyze_impl_DT_FLOAT: + case CGAL_analyze_impl_DT_COMPLEX: + case CGAL_analyze_impl_DT_DOUBLE: im->sign = SGN_SIGNED; break ; @@ -441,7 +442,7 @@ int _readAnalyzeHeader( _image* im, const char* name, } im->wdim = analyzeHeader->dime.bitpix; - if( analyzeHeader->dime.datatype == DT_RGB ) + if( analyzeHeader->dime.datatype == CGAL_analyze_impl_DT_RGB ) { im->wdim /= 3 ; } @@ -612,10 +613,10 @@ writeAnalyzeHeader( const _image* im ) if( im->wdim == 1 ) { if ( im->vdim == 1 ) { - hdr.dime.datatype = DT_UNSIGNED_CHAR ; + hdr.dime.datatype = CGAL_analyze_impl_DT_UNSIGNED_CHAR ; } else if ( im->vdim == 3 ) { - hdr.dime.datatype = DT_RGB ; + hdr.dime.datatype = CGAL_analyze_impl_DT_RGB ; } else { fprintf( stderr, "%s: unsupported image type\n", proc ); @@ -643,7 +644,7 @@ writeAnalyzeHeader( const _image* im ) if ( imin > *buf ) imin = *buf; } if ( imax < 32768 ) { - hdr.dime.datatype = DT_SIGNED_SHORT ; + hdr.dime.datatype = CGAL_analyze_impl_DT_SIGNED_SHORT ; } else { fprintf( stderr, "%s: conversion from unsigned short to short impossible, max=%d\n", proc, imax ); @@ -676,7 +677,7 @@ writeAnalyzeHeader( const _image* im ) if ( imax < *buf ) imax = *buf; if ( imin > *buf ) imin = *buf; } - hdr.dime.datatype = DT_SIGNED_SHORT ; + hdr.dime.datatype = CGAL_analyze_impl_DT_SIGNED_SHORT ; } else if( im->wdim == 4 ) { int *buf = (int*)im->data; @@ -686,7 +687,7 @@ writeAnalyzeHeader( const _image* im ) if ( imax < *buf ) imax = *buf; if ( imin > *buf ) imin = *buf; } - hdr.dime.datatype = DT_SIGNED_INT ; + hdr.dime.datatype = CGAL_analyze_impl_DT_SIGNED_INT ; } else { fprintf( stderr, "%s: unsupported image type\n", proc ); @@ -699,10 +700,10 @@ writeAnalyzeHeader( const _image* im ) return -1; } if( im->wdim == 4 ) { - hdr.dime.datatype = DT_FLOAT ; + hdr.dime.datatype = CGAL_analyze_impl_DT_FLOAT ; } else if( im->wdim == 8 ) { - hdr.dime.datatype = DT_DOUBLE ; + hdr.dime.datatype = CGAL_analyze_impl_DT_DOUBLE ; } else { fprintf( stderr, "%s: unsupported image type\n", proc ); diff --git a/Classification/doc/Classification/Classification.txt b/Classification/doc/Classification/Classification.txt index 56bf40b6a77..c1a578a8420 100644 --- a/Classification/doc/Classification/Classification.txt +++ b/Classification/doc/Classification/Classification.txt @@ -8,10 +8,6 @@ namespace CGAL { This component implements the algorithm described in \cgalCite{cgal:lm-clscm-12} (section 2), generalized to handle different types of data, multiple features and multiple labels. It classifies a data set into a user-defined set of labels, such as _ground_, _vegetation_ and _buildings_. A flexible API is provided so that users can classify any type of data which they can index and for which they can compute relevant features, compute their own local features on the input data set and define their own labels. -\note This component requires C++11 and depends on the Boost libraries -[Serialization](https://www.boost.org/libs/serialization) and -[IO Streams](https://www.boost.org/libs/iostreams) (compiled with the GZIP dependency). - \section Classification_Organization Package Organization %Classification of data sets is achieved as follows (see Figure \cgalFigureRef{Classification_organization_fig}): @@ -35,7 +31,9 @@ Currently, \cgal provides data structures to handle classification of point sets \subsection Classification_labels Label Set -A label represents how an item should be classified, for example: _vegetation_, _building_, _road_, etc. In \cgal, a label has a name and is simply identified by a [Label_handle](@ref CGAL::Classification::Label_handle). Note that names are not used for identification: two labels in the same set can have the same name (but not the same handle). +A label represents how an item should be classified, for example: _vegetation_, _building_, _road_, etc. In \cgal, a label has a name, an index (in a label set), a standard index (for example, the index of the label in the ASPRS standard) and a color. It is simply identified by a [Label_handle](@ref CGAL::Classification::Label_handle). Note that names, standard indices and colors are not used for identification: two labels in the same set can have the same name, standard index and color but not the same handle. + +If labels are initialized with their names only, standard indices and colors can be deduced in some cases (see [Label_set::add()](@ref CGAL::Classification::Label_set::add)). The following code snippet shows how to add labels to the classification object: @@ -231,6 +229,14 @@ An [example](\ref Classification_example_ethz_random_forest) shows how to use this classifier. For more details about the algorithm, please refer to README provided in the [ETH Zurich's code archive](https://www.ethz.ch/content/dam/ethz/special-interest/baug/igp/photogrammetry-remote-sensing-dam/documents/sourcecode-and-datasets/Random%20Forest/rforest.zip). +\subsubsection Classification_ETHZ_random_forest_deprecated Deprecated IO + +The IO functions of this classifier were changed in \cgal +5.2. Configurations generated from previous versions are not valid +anymore and should be converted first as shown in the following example: + +\cgalExample{Classification/example_deprecated_conversion.cpp} + \subsection Classification_OpenCV_random_forest OpenCV Random Forest The second classifier is [OpenCV::Random_forest_classifier](@ref CGAL::Classification::OpenCV::Random_forest_classifier). diff --git a/Classification/doc/Classification/PackageDescription.txt b/Classification/doc/Classification/PackageDescription.txt index 3d78a28993a..16c1b565b5e 100644 --- a/Classification/doc/Classification/PackageDescription.txt +++ b/Classification/doc/Classification/PackageDescription.txt @@ -77,8 +77,7 @@ Data structures specialized to classify clusters. \cgalPkgShortInfoBegin \cgalPkgSince{4.12} -\cgalPkgDependsOn{\ref PkgSolverInterface, \ref PkgSpatialSearchingD, [Boost Serialization](https://www.boost.org/libs/serialization) and -[Boost IO Streams](https://www.boost.org/libs/iostreams)} +\cgalPkgDependsOn{\ref PkgSolverInterface, \ref PkgSpatialSearchingD} \cgalPkgBib{cgal:lm-clscm-12} \cgalPkgLicense{\ref licensesGPL "GPL"} \cgalPkgDemo{Operations on Polyhedra,polyhedron_3.zip} diff --git a/Classification/doc/Classification/examples.txt b/Classification/doc/Classification/examples.txt index 90fde632cce..e456d0bea17 100644 --- a/Classification/doc/Classification/examples.txt +++ b/Classification/doc/Classification/examples.txt @@ -3,6 +3,7 @@ \example Classification/example_feature.cpp \example Classification/example_generation_and_training.cpp \example Classification/example_ethz_random_forest.cpp +\example Classification/example_deprecated_conversion.cpp \example Classification/example_opencv_random_forest.cpp \example Classification/example_tensorflow_neural_network.cpp \example Classification/example_mesh_classification.cpp diff --git a/Classification/examples/Classification/CMakeLists.txt b/Classification/examples/Classification/CMakeLists.txt index 55211fcf0e3..7eaf0379d84 100644 --- a/Classification/examples/Classification/CMakeLists.txt +++ b/Classification/examples/Classification/CMakeLists.txt @@ -69,6 +69,7 @@ create_single_source_cgal_program( "example_generation_and_training.cpp" ) create_single_source_cgal_program( "example_mesh_classification.cpp" ) create_single_source_cgal_program( "example_cluster_classification.cpp" ) create_single_source_cgal_program( "gis_tutorial_example.cpp" ) +create_single_source_cgal_program( "example_deprecated_conversion.cpp" ) if (TARGET CGAL::OpenCV_support) create_single_source_cgal_program( "example_opencv_random_forest.cpp" ) @@ -89,7 +90,8 @@ foreach(target example_cluster_classification example_opencv_random_forest example_tensorflow_neural_network - gis_tutorial_example) + gis_tutorial_example + example_deprecated_conversion) if(TARGET ${target}) target_link_libraries(${target} PUBLIC CGAL::Eigen_support diff --git a/Classification/examples/Classification/data/b9_clusters_config.bin b/Classification/examples/Classification/data/b9_clusters_config.bin new file mode 100644 index 00000000000..e578223b840 Binary files /dev/null and b/Classification/examples/Classification/data/b9_clusters_config.bin differ diff --git a/Classification/examples/Classification/data/b9_clusters_config.gz b/Classification/examples/Classification/data/b9_clusters_config.gz deleted file mode 100644 index 8d1303c1ad0..00000000000 Binary files a/Classification/examples/Classification/data/b9_clusters_config.gz and /dev/null differ diff --git a/Classification/examples/Classification/data/b9_mesh_config.bin b/Classification/examples/Classification/data/b9_mesh_config.bin new file mode 100644 index 00000000000..f09011c4d1e Binary files /dev/null and b/Classification/examples/Classification/data/b9_mesh_config.bin differ diff --git a/Classification/examples/Classification/data/b9_mesh_config.gz b/Classification/examples/Classification/data/b9_mesh_config.gz deleted file mode 100644 index 07af3e3496b..00000000000 Binary files a/Classification/examples/Classification/data/b9_mesh_config.gz and /dev/null differ diff --git a/Classification/examples/Classification/example_classification.cpp b/Classification/examples/Classification/example_classification.cpp index 9d820d37d92..b20a7860d69 100644 --- a/Classification/examples/Classification/example_classification.cpp +++ b/Classification/examples/Classification/example_classification.cpp @@ -13,11 +13,10 @@ #include #include #include +#include #include -typedef CGAL::Parallel_if_available_tag Concurrency_tag; - typedef CGAL::Simple_cartesian Kernel; typedef Kernel::Point_3 Point; typedef Kernel::Iso_cuboid_3 Iso_cuboid_3; @@ -84,9 +83,7 @@ int main (int argc, char** argv) std::cerr << "Computing features" << std::endl; Feature_set features; -#ifdef CGAL_LINKED_WITH_TBB - features.begin_parallel_additions(); -#endif + features.begin_parallel_additions(); // No effect in sequential mode Feature_handle distance_to_plane = features.add (pts, Pmap(), eigen); Feature_handle dispersion = features.add (pts, Pmap(), grid, @@ -94,9 +91,7 @@ int main (int argc, char** argv) Feature_handle elevation = features.add (pts, Pmap(), grid, radius_dtm); -#ifdef CGAL_LINKED_WITH_TBB - features.end_parallel_additions(); -#endif + features.end_parallel_additions(); // No effect in sequential mode //! [Features] /////////////////////////////////////////////////////////////////// @@ -105,9 +100,15 @@ int main (int argc, char** argv) //! [Labels] Label_set labels; + + // Init name only Label_handle ground = labels.add ("ground"); - Label_handle vegetation = labels.add ("vegetation"); - Label_handle roof = labels.add ("roof"); + + // Init name and color + Label_handle vegetation = labels.add ("vegetation", CGAL::Color(0,255,0)); + + // Init name, Color and standard index (here, ASPRS building index) + Label_handle roof = labels.add ("roof", CGAL::Color (255, 0, 0), 6); //! [Labels] /////////////////////////////////////////////////////////////////// @@ -146,7 +147,7 @@ int main (int argc, char** argv) CGAL::Real_timer t; t.start(); - Classification::classify (pts, labels, classifier, label_indices); + Classification::classify (pts, labels, classifier, label_indices); t.stop(); std::cerr << "Raw classification performed in " << t.time() << " second(s)" << std::endl; t.reset(); @@ -156,7 +157,7 @@ int main (int argc, char** argv) /////////////////////////////////////////////////////////////////// //! [Smoothing] t.start(); - Classification::classify_with_local_smoothing + Classification::classify_with_local_smoothing (pts, Pmap(), labels, classifier, neighborhood.sphere_neighbor_query(radius_neighbors), label_indices); @@ -169,7 +170,7 @@ int main (int argc, char** argv) /////////////////////////////////////////////////////////////////// //! [Graph_cut] t.start(); - Classification::classify_with_graphcut + Classification::classify_with_graphcut (pts, Pmap(), labels, classifier, neighborhood.k_neighbor_query(12), 0.2f, 4, label_indices); @@ -180,36 +181,43 @@ int main (int argc, char** argv) // Save the output in a colored PLY format - std::ofstream f ("classification.ply"); - f << "ply" << std::endl - << "format ascii 1.0" << std::endl - << "element vertex " << pts.size() << std::endl - << "property float x" << std::endl - << "property float y" << std::endl - << "property float z" << std::endl - << "property uchar red" << std::endl - << "property uchar green" << std::endl - << "property uchar blue" << std::endl - << "end_header" << std::endl; + std::vector red, green, blue; + red.reserve(pts.size()); + green.reserve(pts.size()); + blue.reserve(pts.size()); for (std::size_t i = 0; i < pts.size(); ++ i) { - f << pts[i] << " "; - Label_handle label = labels[std::size_t(label_indices[i])]; + unsigned r = 0, g = 0, b = 0; if (label == ground) - f << "245 180 0" << std::endl; - else if (label == vegetation) - f << "0 255 27" << std::endl; - else if (label == roof) - f << "255 0 170" << std::endl; - else { - f << "0 0 0" << std::endl; - std::cerr << "Error: unknown classification label" << std::endl; + r = 245; g = 180; b = 0; } + else if (label == vegetation) + { + r = 0; g = 255; b = 27; + } + else if (label == roof) + { + r = 255; g = 0; b = 170; + } + red.push_back(r); + green.push_back(g); + blue.push_back(b); } + std::ofstream f ("classification.ply"); + + CGAL::write_ply_points_with_properties + (f, CGAL::make_range (boost::counting_iterator(0), + boost::counting_iterator(pts.size())), + CGAL::make_ply_point_writer (CGAL::make_property_map(pts)), + std::make_pair(CGAL::make_property_map(red), CGAL::PLY_property("red")), + std::make_pair(CGAL::make_property_map(green), CGAL::PLY_property("green")), + std::make_pair(CGAL::make_property_map(blue), CGAL::PLY_property("blue"))); + + std::cerr << "All done" << std::endl; return EXIT_SUCCESS; } diff --git a/Classification/examples/Classification/example_cluster_classification.cpp b/Classification/examples/Classification/example_cluster_classification.cpp index 3c4b08f60e6..1573a0fc1ad 100644 --- a/Classification/examples/Classification/example_cluster_classification.cpp +++ b/Classification/examples/Classification/example_cluster_classification.cpp @@ -18,8 +18,6 @@ #include #include -typedef CGAL::Parallel_if_available_tag Concurrency_tag; - typedef CGAL::Simple_cartesian Kernel; typedef Kernel::Point_3 Point; typedef Kernel::Iso_cuboid_3 Iso_cuboid_3; @@ -37,6 +35,7 @@ typedef CGAL::Shape_detection::Point_set::Least_squares_plane_fit_region Region_growing; namespace Classification = CGAL::Classification; +namespace Feature = CGAL::Classification::Feature; typedef Classification::Label_handle Label_handle; typedef Classification::Feature_handle Feature_handle; @@ -50,7 +49,7 @@ typedef Classification::Cluster Clu int main (int argc, char** argv) { std::string filename = "data/b9.ply"; - std::string filename_config = "data/b9_clusters_config.gz"; + std::string filename_config = "data/b9_clusters_config.bin"; if (argc > 1) filename = argv[1]; @@ -67,7 +66,7 @@ int main (int argc, char** argv) CGAL::Real_timer t; t.start(); pts.add_normal_map(); - CGAL::jet_estimate_normals (pts, 12); + CGAL::jet_estimate_normals (pts, 12); t.stop(); std::cerr << "Done in " << t.time() << " second(s)" << std::endl; t.reset(); @@ -151,50 +150,38 @@ int main (int argc, char** argv) Feature_set features; -#ifdef CGAL_LINKED_WITH_TBB - features.begin_parallel_additions(); -#endif - // First, compute means of features. - for (std::size_t i = 0; i < pointwise_features.size(); ++ i) - features.add (clusters, pointwise_features[i]); - -#ifdef CGAL_LINKED_WITH_TBB - features.end_parallel_additions(); features.begin_parallel_additions(); -#endif + for (Feature_handle fh : pointwise_features) + features.add (clusters, fh); + features.end_parallel_additions(); // Then, compute variances of features (and remaining cluster features). + features.begin_parallel_additions(); for (std::size_t i = 0; i < pointwise_features.size(); ++ i) - features.add (clusters, - pointwise_features[i], // i^th feature - features[i]); // mean of i^th feature + features.add (clusters, + pointwise_features[i], // i^th feature + features[i]); // mean of i^th feature - features.add (clusters); - features.add (clusters); + features.add (clusters); + features.add (clusters); for (std::size_t i = 0; i < 3; ++ i) - features.add (clusters, eigen, (unsigned int)(i)); + features.add (clusters, eigen, (unsigned int)(i)); -#ifdef CGAL_LINKED_WITH_TBB features.end_parallel_additions(); -#endif //! [Features] /////////////////////////////////////////////////////////////////// t.stop(); - // Add types. - Label_set labels; - Label_handle ground = labels.add ("ground"); - Label_handle vegetation = labels.add ("vegetation"); - Label_handle roof = labels.add ("roof"); + Label_set labels = { "ground", "vegetation", "roof" }; std::vector label_indices(clusters.size(), -1); std::cerr << "Using ETHZ Random Forest Classifier" << std::endl; - Classification::ETHZ_random_forest_classifier classifier (labels, features); + Classification::ETHZ::Random_forest_classifier classifier (labels, features); std::cerr << "Loading configuration" << std::endl; std::ifstream in_config (filename_config, std::ios_base::in | std::ios_base::binary); @@ -203,7 +190,7 @@ int main (int argc, char** argv) std::cerr << "Classifying" << std::endl; t.reset(); t.start(); - Classification::classify (clusters, labels, classifier, label_indices); + Classification::classify (clusters, labels, classifier, label_indices); t.stop(); std::cerr << "Classification done in " << t.time() << " second(s)" << std::endl; diff --git a/Classification/examples/Classification/example_deprecated_conversion.cpp b/Classification/examples/Classification/example_deprecated_conversion.cpp new file mode 100644 index 00000000000..d63080d93af --- /dev/null +++ b/Classification/examples/Classification/example_deprecated_conversion.cpp @@ -0,0 +1,20 @@ +#include + +#include +#include + +int main (int argc, char** argv) +{ + if (argc != 3) + std::cerr << "Usage: " << argv[0] << " input.gz output.bin" << std::endl; + else + { + std::ifstream ifile (argv[1], std::ios_base::binary); + std::ofstream ofile (argv[2], std::ios_base::binary); + + CGAL::Classification::ETHZ::Random_forest_classifier:: + convert_deprecated_configuration_to_new_format(ifile, ofile); + } + + return EXIT_SUCCESS; +} diff --git a/Classification/examples/Classification/example_ethz_random_forest.cpp b/Classification/examples/Classification/example_ethz_random_forest.cpp index bfacdd4e047..b0333f58ec4 100644 --- a/Classification/examples/Classification/example_ethz_random_forest.cpp +++ b/Classification/examples/Classification/example_ethz_random_forest.cpp @@ -49,18 +49,13 @@ int main (int argc, char** argv) Imap label_map; bool lm_found = false; - boost::tie (label_map, lm_found) = pts.property_map ("label"); + std::tie (label_map, lm_found) = pts.property_map ("label"); if (!lm_found) { std::cerr << "Error: \"label\" property not found in input file." << std::endl; return EXIT_FAILURE; } - std::vector ground_truth; - ground_truth.reserve (pts.size()); - std::copy (pts.range(label_map).begin(), pts.range(label_map).end(), - std::back_inserter (ground_truth)); - Feature_set features; std::cerr << "Generating features" << std::endl; @@ -69,25 +64,23 @@ int main (int argc, char** argv) Feature_generator generator (pts, pts.point_map(), 5); // using 5 scales -#ifdef CGAL_LINKED_WITH_TBB features.begin_parallel_additions(); -#endif - generator.generate_point_based_features (features); - -#ifdef CGAL_LINKED_WITH_TBB features.end_parallel_additions(); -#endif t.stop(); std::cerr << "Done in " << t.time() << " second(s)" << std::endl; - // Add types + // Add labels Label_set labels; Label_handle ground = labels.add ("ground"); Label_handle vegetation = labels.add ("vegetation"); Label_handle roof = labels.add ("roof"); + // Check if ground truth is valid for this label set + if (!labels.is_valid_ground_truth (pts.range(label_map), true)) + return EXIT_FAILURE; + std::vector label_indices(pts.size(), -1); std::cerr << "Using ETHZ Random Forest Classifier" << std::endl; @@ -96,13 +89,13 @@ int main (int argc, char** argv) std::cerr << "Training" << std::endl; t.reset(); t.start(); - classifier.train (ground_truth); + classifier.train (pts.range(label_map)); t.stop(); std::cerr << "Done in " << t.time() << " second(s)" << std::endl; t.reset(); t.start(); - Classification::classify_with_graphcut + Classification::classify_with_graphcut (pts, pts.point_map(), labels, classifier, generator.neighborhood().k_neighbor_query(12), 0.2f, 1, label_indices); @@ -111,15 +104,15 @@ int main (int argc, char** argv) std::cerr << "Classification with graphcut done in " << t.time() << " second(s)" << std::endl; std::cerr << "Precision, recall, F1 scores and IoU:" << std::endl; - Classification::Evaluation evaluation (labels, ground_truth, label_indices); + Classification::Evaluation evaluation (labels, pts.range(label_map), label_indices); - for (std::size_t i = 0; i < labels.size(); ++ i) + for (Label_handle l : labels) { - std::cerr << " * " << labels[i]->name() << ": " - << evaluation.precision(labels[i]) << " ; " - << evaluation.recall(labels[i]) << " ; " - << evaluation.f1_score(labels[i]) << " ; " - << evaluation.intersection_over_union(labels[i]) << std::endl; + std::cerr << " * " << l->name() << ": " + << evaluation.precision(l) << " ; " + << evaluation.recall(l) << " ; " + << evaluation.f1_score(l) << " ; " + << evaluation.intersection_over_union(l) << std::endl; } std::cerr << "Accuracy = " << evaluation.accuracy() << std::endl @@ -136,21 +129,16 @@ int main (int argc, char** argv) label_map[i] = label_indices[i]; // update label map with computed classification Label_handle label = labels[label_indices[i]]; - - if (label == ground) - { - red[i] = 245; green[i] = 180; blue[i] = 0; - } - else if (label == vegetation) - { - red[i] = 0; green[i] = 255; blue[i] = 27; - } - else if (label == roof) - { - red[i] = 255; green[i] = 0; blue[i] = 170; - } + const CGAL::Color& color = label->color(); + red[i] = color.red(); + green[i] = color.green(); + blue[i] = color.blue(); } + // Save configuration for later use + std::ofstream fconfig ("ethz_random_forest.bin", std::ios_base::binary); + classifier.save_configuration(fconfig); + // Write result std::ofstream f ("classification.ply"); f.precision(18); diff --git a/Classification/examples/Classification/example_feature.cpp b/Classification/examples/Classification/example_feature.cpp index 4f77c18d941..68128ba08e8 100644 --- a/Classification/examples/Classification/example_feature.cpp +++ b/Classification/examples/Classification/example_feature.cpp @@ -115,7 +115,7 @@ int main (int argc, char** argv) std::cerr << "Classifying" << std::endl; std::vector label_indices(pts.size(), -1); - Classification::classify_with_graphcut + Classification::classify_with_graphcut (pts, Pmap(), labels, classifier, neighborhood.k_neighbor_query(12), 0.5, 1, label_indices); diff --git a/Classification/examples/Classification/example_generation_and_training.cpp b/Classification/examples/Classification/example_generation_and_training.cpp index 2a34076ef77..cb911262b32 100644 --- a/Classification/examples/Classification/example_generation_and_training.cpp +++ b/Classification/examples/Classification/example_generation_and_training.cpp @@ -46,18 +46,13 @@ int main (int argc, char** argv) Imap label_map; bool lm_found = false; - boost::tie (label_map, lm_found) = pts.property_map ("label"); + std::tie (label_map, lm_found) = pts.property_map ("label"); if (!lm_found) { std::cerr << "Error: \"label\" property not found in input file." << std::endl; return EXIT_FAILURE; } - std::vector ground_truth; - ground_truth.reserve (pts.size()); - std::copy (pts.range(label_map).begin(), pts.range(label_map).end(), - std::back_inserter (ground_truth)); - std::cerr << "Generating features" << std::endl; CGAL::Real_timer t; t.start(); @@ -70,15 +65,9 @@ int main (int argc, char** argv) std::size_t number_of_scales = 5; Feature_generator generator (pts, pts.point_map(), number_of_scales); -#ifdef CGAL_LINKED_WITH_TBB features.begin_parallel_additions(); -#endif - generator.generate_point_based_features (features); - -#ifdef CGAL_LINKED_WITH_TBB features.end_parallel_additions(); -#endif //! [Generator] /////////////////////////////////////////////////////////////////// @@ -86,25 +75,21 @@ int main (int argc, char** argv) t.stop(); std::cerr << features.size() << " feature(s) generated in " << t.time() << " second(s)" << std::endl; - // Add types - Label_set labels; - Label_handle ground = labels.add ("ground"); - Label_handle vegetation = labels.add ("vegetation"); - Label_handle roof = labels.add ("roof"); + Label_set labels = { "ground", "vegetation", "roof" }; Classifier classifier (labels, features); std::cerr << "Training" << std::endl; t.reset(); t.start(); - classifier.train (ground_truth, 800); + classifier.train (pts.range(label_map), 800); t.stop(); std::cerr << "Done in " << t.time() << " second(s)" << std::endl; t.reset(); t.start(); std::vector label_indices(pts.size(), -1); - Classification::classify_with_graphcut + Classification::classify_with_graphcut (pts, pts.point_map(), labels, classifier, generator.neighborhood().k_neighbor_query(12), 0.2f, 10, label_indices); @@ -112,15 +97,15 @@ int main (int argc, char** argv) std::cerr << "Classification with graphcut done in " << t.time() << " second(s)" << std::endl; std::cerr << "Precision, recall, F1 scores and IoU:" << std::endl; - Classification::Evaluation evaluation (labels, ground_truth, label_indices); + Classification::Evaluation evaluation (labels, pts.range(label_map), label_indices); - for (std::size_t i = 0; i < labels.size(); ++ i) + for (Label_handle l : labels) { - std::cerr << " * " << labels[i]->name() << ": " - << evaluation.precision(labels[i]) << " ; " - << evaluation.recall(labels[i]) << " ; " - << evaluation.f1_score(labels[i]) << " ; " - << evaluation.intersection_over_union(labels[i]) << std::endl; + std::cerr << " * " << l->name() << ": " + << evaluation.precision(l) << " ; " + << evaluation.recall(l) << " ; " + << evaluation.f1_score(l) << " ; " + << evaluation.intersection_over_union(l) << std::endl; } std::cerr << "Accuracy = " << evaluation.accuracy() << std::endl diff --git a/Classification/examples/Classification/example_mesh_classification.cpp b/Classification/examples/Classification/example_mesh_classification.cpp index 465b3a4bfcd..1d081e869fa 100644 --- a/Classification/examples/Classification/example_mesh_classification.cpp +++ b/Classification/examples/Classification/example_mesh_classification.cpp @@ -32,7 +32,7 @@ typedef Classification::Mesh_feature_generator int main (int argc, char** argv) { std::string filename = "data/b9_mesh.off"; - std::string filename_config = "data/b9_mesh_config.gz"; + std::string filename_config = "data/b9_mesh_config.bin"; if (argc > 1) filename = argv[1]; @@ -59,16 +59,10 @@ int main (int argc, char** argv) std::size_t number_of_scales = 5; Feature_generator generator (mesh, face_point_map, number_of_scales); -#ifdef CGAL_LINKED_WITH_TBB features.begin_parallel_additions(); -#endif - generator.generate_point_based_features (features); // Features that consider the mesh as a point set generator.generate_face_based_features (features); // Features computed directly on mesh faces - -#ifdef CGAL_LINKED_WITH_TBB features.end_parallel_additions(); -#endif //! [Generator] /////////////////////////////////////////////////////////////////// @@ -76,11 +70,7 @@ int main (int argc, char** argv) t.stop(); std::cerr << "Done in " << t.time() << " second(s)" << std::endl; - // Add types - Label_set labels; - Label_handle ground = labels.add ("ground"); - Label_handle vegetation = labels.add ("vegetation"); - Label_handle roof = labels.add ("roof"); + Label_set labels = { "ground", "vegetation", "roof" }; std::vector label_indices(mesh.number_of_faces(), -1); @@ -94,7 +84,7 @@ int main (int argc, char** argv) std::cerr << "Classifying with graphcut" << std::endl; t.reset(); t.start(); - Classification::classify_with_graphcut + Classification::classify_with_graphcut (mesh.faces(), Face_with_bbox_map(&mesh), labels, classifier, generator.neighborhood().n_ring_neighbor_query(2), 0.2f, 1, label_indices); diff --git a/Classification/examples/Classification/example_opencv_random_forest.cpp b/Classification/examples/Classification/example_opencv_random_forest.cpp index e0cd90e51f0..2491e05d0a7 100644 --- a/Classification/examples/Classification/example_opencv_random_forest.cpp +++ b/Classification/examples/Classification/example_opencv_random_forest.cpp @@ -49,18 +49,13 @@ int main (int argc, char** argv) Imap label_map; bool lm_found = false; - boost::tie (label_map, lm_found) = pts.property_map ("label"); + std::tie (label_map, lm_found) = pts.property_map ("label"); if (!lm_found) { std::cerr << "Error: \"label\" property not found in input file." << std::endl; return EXIT_FAILURE; } - std::vector ground_truth; - ground_truth.reserve (pts.size()); - std::copy (pts.range(label_map).begin(), pts.range(label_map).end(), - std::back_inserter (ground_truth)); - Feature_set features; std::cerr << "Generating features" << std::endl; @@ -69,19 +64,14 @@ int main (int argc, char** argv) Feature_generator generator (pts, pts.point_map(), 5); // using 5 scales -#ifdef CGAL_LINKED_WITH_TBB features.begin_parallel_additions(); -#endif - generator.generate_point_based_features (features); -#ifdef CGAL_LINKED_WITH_TBB features.end_parallel_additions(); -#endif t.stop(); std::cerr << "Done in " << t.time() << " second(s)" << std::endl; - // Add types + // Add labels Label_set labels; Label_handle ground = labels.add ("ground"); Label_handle vegetation = labels.add ("vegetation"); @@ -95,13 +85,13 @@ int main (int argc, char** argv) std::cerr << "Training" << std::endl; t.reset(); t.start(); - classifier.train (ground_truth); + classifier.train (pts.range(label_map)); t.stop(); std::cerr << "Done in " << t.time() << " second(s)" << std::endl; t.reset(); t.start(); - Classification::classify_with_graphcut + Classification::classify_with_graphcut (pts, pts.point_map(), labels, classifier, generator.neighborhood().k_neighbor_query(12), 0.2f, 1, label_indices); @@ -110,15 +100,15 @@ int main (int argc, char** argv) std::cerr << "Classification with graphcut done in " << t.time() << " second(s)" << std::endl; std::cerr << "Precision, recall, F1 scores and IoU:" << std::endl; - Classification::Evaluation evaluation (labels, ground_truth, label_indices); + Classification::Evaluation evaluation (labels, pts.range(label_map), label_indices); - for (std::size_t i = 0; i < labels.size(); ++ i) + for (Label_handle l : labels) { - std::cerr << " * " << labels[i]->name() << ": " - << evaluation.precision(labels[i]) << " ; " - << evaluation.recall(labels[i]) << " ; " - << evaluation.f1_score(labels[i]) << " ; " - << evaluation.intersection_over_union(labels[i]) << std::endl; + std::cerr << " * " << l->name() << ": " + << evaluation.precision(l) << " ; " + << evaluation.recall(l) << " ; " + << evaluation.f1_score(l) << " ; " + << evaluation.intersection_over_union(l) << std::endl; } std::cerr << "Accuracy = " << evaluation.accuracy() << std::endl @@ -135,19 +125,10 @@ int main (int argc, char** argv) label_map[i] = label_indices[i]; // update label map with computed classification Label_handle label = labels[label_indices[i]]; - - if (label == ground) - { - red[i] = 245; green[i] = 180; blue[i] = 0; - } - else if (label == vegetation) - { - red[i] = 0; green[i] = 255; blue[i] = 27; - } - else if (label == roof) - { - red[i] = 255; green[i] = 0; blue[i] = 170; - } + const CGAL::Color& color = label->color(); + red[i] = color.red(); + green[i] = color.green(); + blue[i] = color.blue(); } // Write result diff --git a/Classification/examples/Classification/example_tensorflow_neural_network.cpp b/Classification/examples/Classification/example_tensorflow_neural_network.cpp index 01fd4f1cac2..6a454360285 100644 --- a/Classification/examples/Classification/example_tensorflow_neural_network.cpp +++ b/Classification/examples/Classification/example_tensorflow_neural_network.cpp @@ -49,18 +49,13 @@ int main (int argc, char** argv) Imap label_map; bool lm_found = false; - boost::tie (label_map, lm_found) = pts.property_map ("label"); + std::tie (label_map, lm_found) = pts.property_map ("label"); if (!lm_found) { std::cerr << "Error: \"label\" property not found in input file." << std::endl; return EXIT_FAILURE; } - std::vector ground_truth; - ground_truth.reserve (pts.size()); - std::copy (pts.range(label_map).begin(), pts.range(label_map).end(), - std::back_inserter (ground_truth)); - Feature_set features; std::cerr << "Generating features" << std::endl; @@ -69,20 +64,14 @@ int main (int argc, char** argv) Feature_generator generator (pts, pts.point_map(), 5); // using 5 scales -#ifdef CGAL_LINKED_WITH_TBB features.begin_parallel_additions(); -#endif - generator.generate_point_based_features (features); - -#ifdef CGAL_LINKED_WITH_TBB features.end_parallel_additions(); -#endif t.stop(); std::cerr << "Done in " << t.time() << " second(s)" << std::endl; - // Add types + // Add labels Label_set labels; Label_handle ground = labels.add ("ground"); Label_handle vegetation = labels.add ("vegetation"); @@ -96,7 +85,7 @@ int main (int argc, char** argv) std::cerr << "Training" << std::endl; t.reset(); t.start(); - classifier.train (ground_truth, + classifier.train (pts.range(ground_truth), true, // restart from scratch 100); // 100 iterations t.stop(); @@ -113,15 +102,15 @@ int main (int argc, char** argv) std::cerr << "Classification with graphcut done in " << t.time() << " second(s)" << std::endl; std::cerr << "Precision, recall, F1 scores and IoU:" << std::endl; - Classification::Evaluation evaluation (labels, ground_truth, label_indices); + Classification::Evaluation evaluation (labels, pts.range(ground_truth), label_indices); - for (std::size_t i = 0; i < labels.size(); ++ i) + for (Label_handle l : labels) { - std::cerr << " * " << labels[i]->name() << ": " - << evaluation.precision(labels[i]) << " ; " - << evaluation.recall(labels[i]) << " ; " - << evaluation.f1_score(labels[i]) << " ; " - << evaluation.intersection_over_union(labels[i]) << std::endl; + std::cerr << " * " << l->name() << ": " + << evaluation.precision(l) << " ; " + << evaluation.recall(l) << " ; " + << evaluation.f1_score(l) << " ; " + << evaluation.intersection_over_union(l) << std::endl; } std::cerr << "Accuracy = " << evaluation.accuracy() << std::endl @@ -138,19 +127,10 @@ int main (int argc, char** argv) label_map[i] = label_indices[i]; // update label map with computed classification Label_handle label = labels[label_indices[i]]; - - if (label == ground) - { - red[i] = 245; green[i] = 180; blue[i] = 0; - } - else if (label == vegetation) - { - red[i] = 0; green[i] = 255; blue[i] = 27; - } - else if (label == roof) - { - red[i] = 255; green[i] = 0; blue[i] = 170; - } + const CGAL::Color& color = label->color(); + red[i] = color.red(); + green[i] = color.green(); + blue[i] = color.blue(); } // Write result diff --git a/Classification/include/CGAL/Classification/Cluster.h b/Classification/include/CGAL/Classification/Cluster.h index 4b8b002c3b5..a0d0f209602 100644 --- a/Classification/include/CGAL/Classification/Cluster.h +++ b/Classification/include/CGAL/Classification/Cluster.h @@ -46,7 +46,7 @@ class Cluster { public: - typedef typename ItemMap::value_type Item; + using Item = typename boost::property_traits::value_type; /// \cond SKIP_IN_MANUAL struct Neighbor_query @@ -64,9 +64,9 @@ public: class Point_idx_to_point_unary_function { public: - typedef std::size_t argument_type; - typedef typename ItemMap::reference result_type; - typedef boost::readable_property_map_tag category; + using argument_type = std::size_t; + using result_type = typename boost::property_traits::reference; + using category = boost::readable_property_map_tag; const ItemRange* m_range; ItemMap m_item_map; @@ -105,9 +105,9 @@ public: \param item_map property map to access the input items. */ Cluster (const ItemRange& range, ItemMap item_map) - : neighbors (new std::vector()) + : neighbors (std::make_shared >()) , m_range (&range), m_item_map (item_map) - , m_inliers (new std::vector()) + , m_inliers (std::make_shared >()) , m_training(-1), m_label(-1) { } diff --git a/Classification/include/CGAL/Classification/ETHZ/Random_forest_classifier.h b/Classification/include/CGAL/Classification/ETHZ/Random_forest_classifier.h index e2f831b5aca..6b22c41ad00 100644 --- a/Classification/include/CGAL/Classification/ETHZ/Random_forest_classifier.h +++ b/Classification/include/CGAL/Classification/ETHZ/Random_forest_classifier.h @@ -73,7 +73,7 @@ class Random_forest_classifier const Label_set& m_labels; const Feature_set& m_features; - Forest* m_rfc; + std::shared_ptr m_rfc; public: @@ -81,16 +81,16 @@ public: /// @{ /*! - \brief Instantiates the classifier using the sets of `labels` and `features`. + \brief instantiates the classifier using the sets of `labels` and `features`. */ Random_forest_classifier (const Label_set& labels, const Feature_set& features) - : m_labels (labels), m_features (features), m_rfc (nullptr) + : m_labels (labels), m_features (features) { } /*! - \brief Copies the `other` classifier's configuration using another + \brief copies the `other` classifier's configuration using another set of `features`. This constructor can be used to apply a trained random forest to @@ -105,7 +105,7 @@ public: defined(CGAL_LINKED_WITH_BOOST_SERIALIZATION)) Random_forest_classifier (const Random_forest_classifier& other, const Feature_set& features) - : m_labels (other.m_labels), m_features (features), m_rfc (nullptr) + : m_labels (other.m_labels), m_features (features) { std::stringstream stream; other.save_configuration(stream); @@ -113,14 +113,6 @@ public: } #endif - /// \cond SKIP_IN_MANUAL - ~Random_forest_classifier () - { - if (m_rfc != nullptr) - delete m_rfc; - } - /// \endcond - /// @} /// \name Training @@ -138,7 +130,7 @@ public: /// \endcond /*! - \brief Runs the training algorithm. + \brief runs the training algorithm. From the set of provided ground truth, this algorithm estimates sets up the random trees that produce the most accurate result @@ -179,6 +171,8 @@ public: std::size_t num_trees = 25, std::size_t max_depth = 20) { + CGAL_precondition (m_labels.is_valid_ground_truth (ground_truth)); + CGAL::internal::liblearning::RandomForest::ForestParams params; params.n_trees = num_trees; params.max_depth = max_depth; @@ -186,32 +180,40 @@ public: std::vector gt; std::vector ft; - std::size_t idx = 0; - for (const auto& ig : ground_truth) +#ifdef CGAL_CLASSIFICATION_VERBOSE + std::vector count (m_labels.size(), 0); +#endif + + std::size_t i = 0; + for (const auto& gt_value : ground_truth) { - int g = int(ig); + int g = int(gt_value); if (g != -1) { for (std::size_t f = 0; f < m_features.size(); ++ f) - ft.push_back(m_features[f]->value(idx)); + ft.push_back(m_features[f]->value(i)); gt.push_back(g); +#ifdef CGAL_CLASSIFICATION_VERBOSE + count[std::size_t(g)] ++; +#endif } - ++ idx; + ++ i; } - CGAL_CLASSIFICATION_CERR << "Using " << gt.size() << " inliers" << std::endl; + CGAL_CLASSIFICATION_CERR << "Using " << gt.size() << " inliers:" << std::endl; +#ifdef CGAL_CLASSIFICATION_VERBOSE + for (std::size_t i = 0; i < m_labels.size(); ++ i) + std::cerr << " * " << m_labels[i]->name() << ": " << count[i] << " inlier(s)" << std::endl; +#endif CGAL::internal::liblearning::DataView2D label_vector (&(gt[0]), gt.size(), 1); CGAL::internal::liblearning::DataView2D feature_vector(&(ft[0]), gt.size(), ft.size() / gt.size()); - if (m_rfc != nullptr && reset_trees) - { - delete m_rfc; - m_rfc = nullptr; - } + if (m_rfc && reset_trees) + m_rfc.reset(); - if (m_rfc == nullptr) - m_rfc = new Forest (params); + if (!m_rfc) + m_rfc = std::make_shared (params); CGAL::internal::liblearning::RandomForest::AxisAlignedRandomSplitGenerator generator; @@ -245,7 +247,7 @@ public: /// @{ /*! - \brief Computes, for each feature, how many nodes in the forest + \brief computes, for each feature, how many nodes in the forest uses it as a split criterion. Each tree of the random forest recursively splits the training @@ -280,35 +282,37 @@ public: /// @{ /*! - \brief Saves the current configuration in the stream `output`. + \brief saves the current configuration in the stream `output`. This allows to easily save and recover a specific classification configuration. - The output file is written in an GZIP container that is readable - by the `load_configuration()` method. + The output file is written in a binary format that is readable by + the `load_configuration()` method. */ #if defined(DOXYGEN_RUNNING) || \ (defined(CGAL_LINKED_WITH_BOOST_IOSTREAMS) && \ defined(CGAL_LINKED_WITH_BOOST_SERIALIZATION)) void save_configuration (std::ostream& output) const { - boost::iostreams::filtering_ostream outs; - outs.push(boost::iostreams::gzip_compressor()); - outs.push(output); - boost::archive::text_oarchive oas(outs); - oas << BOOST_SERIALIZATION_NVP(*m_rfc); + m_rfc->write(output); } #endif /*! - \brief Loads a configuration from the stream `input`. + \brief loads a configuration from the stream `input`. - The input file should be a GZIP container written by the + The input file should be a binary file written by the `save_configuration()` method. The feature set of the classifier should contain the exact same features in the exact same order as the ones present when the file was generated using `save_configuration()`. + + \warning If the file you are trying to load was saved using CGAL + 5.1 or earlier, you have to convert it first using + `convert_deprecated_configuration_to_new_format()` as the exchange + format for ETHZ Random Forest changed in CGAL 5.2. + */ #if defined(DOXYGEN_RUNNING) || \ (defined(CGAL_LINKED_WITH_BOOST_IOSTREAMS) && \ @@ -316,9 +320,51 @@ public: void load_configuration (std::istream& input) { CGAL::internal::liblearning::RandomForest::ForestParams params; - if (m_rfc != nullptr) - delete m_rfc; - m_rfc = new Forest (params); + m_rfc = std::make_shared (params); + + m_rfc->read(input); + } +#endif + + /// @} + + /// \name Deprecated Input/Output + /// @{ + + /*! + \brief converts a deprecated configuration (in compressed ASCII + format) to a new configuration (in binary format). + + The input file should be a GZIP container written by the + `save_configuration()` method from CGAL 5.1 and earlier. The + output is a valid configuration for CGAL 5.2 and later. + + \note This function depends on the Boost libraries + [Serialization](https://www.boost.org/libs/serialization) and + [IO Streams](https://www.boost.org/libs/iostreams) (compiled with the GZIP dependency). + */ +#if defined(DOXYGEN_RUNNING) || \ + (defined(CGAL_LINKED_WITH_BOOST_IOSTREAMS) && \ + defined(CGAL_LINKED_WITH_BOOST_SERIALIZATION)) + static void convert_deprecated_configuration_to_new_format (std::istream& input, std::ostream& output) + { + Label_set dummy_labels; + Feature_set dummy_features; + Random_forest_classifier classifier (dummy_labels, dummy_features); + classifier.load_deprecated_configuration(input); + classifier.save_configuration(output); + } +#endif + +/// @} + + /// \cond SKIP_IN_MANUAL +#if defined(CGAL_LINKED_WITH_BOOST_IOSTREAMS) && \ + defined(CGAL_LINKED_WITH_BOOST_SERIALIZATION) + void load_deprecated_configuration (std::istream& input) + { + CGAL::internal::liblearning::RandomForest::ForestParams params; + m_rfc = std::make_shared (params); boost::iostreams::filtering_istream ins; ins.push(boost::iostreams::gzip_decompressor()); @@ -327,8 +373,8 @@ public: ias >> BOOST_SERIALIZATION_NVP(*m_rfc); } #endif + /// \endcond -/// @} }; diff --git a/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/common-libraries.hpp b/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/common-libraries.hpp index 97e13f532ce..e2fdfd42c87 100644 --- a/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/common-libraries.hpp +++ b/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/common-libraries.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -109,6 +110,30 @@ struct ForestParams { ar & BOOST_SERIALIZATION_NVP(sample_reduction); } #endif + + void write (std::ostream& os) + { + I_Binary_write_size_t_into_uinteger32 (os, n_classes); + I_Binary_write_size_t_into_uinteger32 (os, n_features); + I_Binary_write_size_t_into_uinteger32 (os, n_samples); + I_Binary_write_size_t_into_uinteger32 (os, n_in_bag_samples); + I_Binary_write_size_t_into_uinteger32 (os, max_depth); + I_Binary_write_size_t_into_uinteger32 (os, n_trees); + I_Binary_write_size_t_into_uinteger32 (os, min_samples_per_node); + I_Binary_write_float32 (os, sample_reduction); + } + + void read (std::istream& is) + { + I_Binary_read_size_t_from_uinteger32 (is, n_classes); + I_Binary_read_size_t_from_uinteger32 (is, n_features); + I_Binary_read_size_t_from_uinteger32 (is, n_samples); + I_Binary_read_size_t_from_uinteger32 (is, n_in_bag_samples); + I_Binary_read_size_t_from_uinteger32 (is, max_depth); + I_Binary_read_size_t_from_uinteger32 (is, n_trees); + I_Binary_read_size_t_from_uinteger32 (is, min_samples_per_node); + I_Binary_read_float32 (is, sample_reduction); + } }; struct QuadraticSplitter { @@ -248,6 +273,18 @@ struct AxisAlignedSplitter { ar & BOOST_SERIALIZATION_NVP(threshold); } #endif + + void write (std::ostream& os) + { + os.write((char*)(&feature), sizeof(int)); + os.write((char*)(&threshold), sizeof(FeatureType)); + } + + void read (std::istream& is) + { + is.read((char*)(&feature), sizeof(int)); + is.read((char*)(&threshold), sizeof(FeatureType)); + } }; struct AxisAlignedRandomSplitGenerator { diff --git a/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/forest.hpp b/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/forest.hpp index 6ffe0e0b719..4611becf711 100644 --- a/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/forest.hpp +++ b/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/forest.hpp @@ -31,7 +31,7 @@ #endif #include - +#include #include #ifdef CGAL_LINKED_WITH_TBB @@ -164,10 +164,10 @@ public: f (seed_start, sample_idxes, trees, samples, labels, params.n_in_bag_samples, split_generator); #ifndef CGAL_LINKED_WITH_TBB - CGAL_static_assertion_msg (!(boost::is_convertible::value), + CGAL_static_assertion_msg (!(std::is_convertible::value), "Parallel_tag is enabled but TBB is unavailable."); #else - if (boost::is_convertible::value) + if (std::is_convertible::value) { tbb::parallel_for(tbb::blocked_range(nb_trees, nb_trees + params.n_trees), f); } @@ -232,6 +232,28 @@ public: } #endif + void write (std::ostream& os) + { + params.write(os); + + I_Binary_write_size_t_into_uinteger32 (os, trees.size()); + for (std::size_t i_tree = 0; i_tree < trees.size(); ++i_tree) + trees[i_tree].write(os); + } + + void read (std::istream& is) + { + params.read(is); + + std::size_t nb_trees; + I_Binary_read_size_t_from_uinteger32 (is, nb_trees); + for (std::size_t i = 0; i < nb_trees; ++ i) + { + trees.push_back (new TreeType(¶ms)); + trees.back().read(is); + } + } + void get_feature_usage (std::vector& count) const { for (std::size_t i_tree = 0; i_tree < trees.size(); ++i_tree) diff --git a/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/node.hpp b/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/node.hpp index f510119a557..75ecc1037e6 100644 --- a/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/node.hpp +++ b/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/node.hpp @@ -22,6 +22,8 @@ #include "../dataview.h" #include "common-libraries.hpp" +#include + #if defined(CGAL_LINKED_WITH_BOOST_IOSTREAMS) && defined(CGAL_LINKED_WITH_BOOST_SERIALIZATION) #include #include @@ -253,9 +255,46 @@ public: } #endif + void write (std::ostream& os) + { + I_Binary_write_bool (os, is_leaf); + I_Binary_write_size_t_into_uinteger32 (os, n_samples); + I_Binary_write_size_t_into_uinteger32 (os, depth); + splitter.write(os); + + for (const float& f : node_dist) + I_Binary_write_float32 (os, f); + + if (!is_leaf) + { + left->write(os); + right->write(os); + } + } + + void read (std::istream& is) + { + I_Binary_read_bool (is, is_leaf); + I_Binary_read_size_t_from_uinteger32 (is, n_samples); + I_Binary_read_size_t_from_uinteger32 (is, depth); + splitter.read(is); + + node_dist.resize(params->n_classes, 0.0f); + for (std::size_t i = 0; i < node_dist.size(); ++ i) + I_Binary_read_float32 (is, node_dist[i]); + + if (!is_leaf) + { + left.reset(new Derived(depth + 1, params)); + right.reset(new Derived(depth + 1, params)); + left->read(is); + right->read(is); + } + } + void get_feature_usage (std::vector& count) const { - if (!is_leaf) + if (!is_leaf && splitter.feature != -1) { count[std::size_t(splitter.feature)] ++; left->get_feature_usage(count); diff --git a/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/tree.hpp b/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/tree.hpp index c8a07f020d7..ba75c6e2f98 100644 --- a/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/tree.hpp +++ b/Classification/include/CGAL/Classification/ETHZ/internal/random-forest/tree.hpp @@ -129,6 +129,17 @@ public: } #endif + void write (std::ostream& os) + { + root_node->write(os); + } + + void read (std::istream& is) + { + root_node.reset(new NodeT(0, params)); + root_node->read(is); + } + void get_feature_usage (std::vector& count) const { root_node->get_feature_usage(count); diff --git a/Classification/include/CGAL/Classification/Evaluation.h b/Classification/include/CGAL/Classification/Evaluation.h index 071f6ab8c9f..eeff26f73d6 100644 --- a/Classification/include/CGAL/Classification/Evaluation.h +++ b/Classification/include/CGAL/Classification/Evaluation.h @@ -17,9 +17,10 @@ #include #include -#include #include +#include + #include #include // for std::isnan @@ -27,6 +28,7 @@ namespace CGAL { namespace Classification { + /*! \ingroup PkgClassificationDataStructures @@ -35,98 +37,103 @@ namespace Classification { */ class Evaluation { - mutable std::map m_map_labels; - - std::vector m_precision; - std::vector m_recall; - std::vector m_iou; // intersection over union - float m_accuracy; - float m_mean_iou; - float m_mean_f1; + const Label_set& m_labels; + std::vector > m_confusion; // confusion matrix public: - /// \name Constructor + /// \name Constructors /// @{ + /*! + \brief instantiates an empty evaluation object. -/*! + \param labels labels used. + */ + Evaluation (const Label_set& labels) + : m_labels (labels) + { + init(); + } - \brief Instantiates an evaluation object and computes all - measurements. + /*! - \param labels labels used. + \brief instantiates an evaluation object and computes all + measurements. - \param ground_truth vector of label indices: it should contain the - index of the corresponding label in the `Label_set` provided in the - constructor. Input items that do not have a ground truth information - should be given the value `-1`. + \param labels labels used. - \param result similar to `ground_truth` but contained the result of - a classification. + \param ground_truth vector of label indices: it should contain the + index of the corresponding label in the `Label_set` provided in the + constructor. Input items that do not have a ground truth information + should be given the value `-1`. -*/ + \param result similar to `ground_truth` but contained the result of + a classification. + + */ template Evaluation (const Label_set& labels, const GroundTruthIndexRange& ground_truth, const ResultIndexRange& result) - : m_precision (labels.size()), - m_recall (labels.size()), - m_iou (labels.size()) + : m_labels (labels) { - for (std::size_t i = 0; i < labels.size(); ++ i) - m_map_labels[labels[i]] = i; + init(); + append(ground_truth, result); + } - std::vector true_positives (labels.size()); - std::vector false_positives (labels.size()); - std::vector false_negatives (labels.size()); + /// \cond SKIP_IN_MANUAL + void init() + { + m_confusion.resize (m_labels.size()); + for (std::size_t i = 0; i < m_confusion.size(); ++ i) + m_confusion[i].resize (m_labels.size(), 0); + } - std::size_t sum_true_positives = 0; - std::size_t total = 0; + bool label_has_ground_truth (std::size_t label_idx) const + { + for (std::size_t i = 0; i < m_labels.size(); ++ i) + if (m_confusion[i][label_idx] != 0) + return true; + return false; + } + /// \endcond - for (const auto& zip : CGAL::make_range (boost::make_zip_iterator - (boost::make_tuple(ground_truth.begin(), result.begin())), - boost::make_zip_iterator - (boost::make_tuple(ground_truth.end(), result.end())))) + /// @} + + /// \name Modification + /// @{ + + /*! + \brief appends more items to the evaluation object. + + \param ground_truth vector of label indices: it should contain the + index of the corresponding label in the `Label_set` provided in the + constructor. Input items that do not have a ground truth information + should be given the value `-1`. + + \param result similar to `ground_truth` but contained the result of + a classification. + + */ + template + void append (const GroundTruthIndexRange& ground_truth, + const ResultIndexRange& result) + { + CGAL_precondition (m_labels.is_valid_ground_truth (ground_truth)); + CGAL_precondition (m_labels.is_valid_ground_truth (result)); + + for (const auto& p : CGAL::make_range + (boost::make_zip_iterator(boost::make_tuple(ground_truth.begin(), result.begin())), + boost::make_zip_iterator(boost::make_tuple(ground_truth.end(), result.end())))) { - int gt = static_cast(boost::get<0>(zip)); - int res = static_cast(boost::get<1>(zip)); + int gt = static_cast(boost::get<0>(p)); + int res = static_cast(boost::get<1>(p)); if (gt == -1 || res == -1) continue; - ++ total; - if (gt == res) - { - ++ true_positives[gt]; - ++ sum_true_positives; - continue; - } - ++ false_positives[res]; - ++ false_negatives[gt]; + + ++ m_confusion[std::size_t(res)][std::size_t(gt)]; } - - m_mean_iou = 0.; - m_mean_f1 = 0.; - - std::size_t correct_labels = 0; - - for (std::size_t j = 0; j < labels.size(); ++ j) - { - m_precision[j] = true_positives[j] / float(true_positives[j] + false_positives[j]); - m_recall[j] = true_positives[j] / float(true_positives[j] + false_negatives[j]); - m_iou[j] = true_positives[j] / float(true_positives[j] + false_positives[j] + false_negatives[j]); - - if (std::isnan(m_iou[j])) - continue; - - ++ correct_labels; - m_mean_iou += m_iou[j]; - m_mean_f1 += 2.f * (m_precision[j] * m_recall[j]) - / (m_precision[j] + m_recall[j]); - } - - m_mean_iou /= correct_labels; - m_mean_f1 /= correct_labels; - m_accuracy = sum_true_positives / float(total); } /// @} @@ -134,10 +141,20 @@ public: /// \name Label Evaluation /// @{ + /*! + \brief returns the number of items whose ground truth is + `ground_truth` and which were classified as `result`. + */ + std::size_t confusion (Label_handle ground_truth, Label_handle result) + { + std::size_t idx_gt = ground_truth->index(); + std::size_t idx_r = result->index(); + return m_confusion[idx_gt][idx_r]; + } /*! - \brief Returns the precision of the training for the given label. + \brief returns the precision of the training for the given label. Precision is the number of true positives divided by the sum of the true positives and the false positives. @@ -145,12 +162,23 @@ public: */ float precision (Label_handle label) const { - return m_precision[m_map_labels[label]]; + std::size_t idx = label->index(); + if (!label_has_ground_truth(idx)) + return std::numeric_limits::quiet_NaN(); + + std::size_t total = 0; + for (std::size_t i = 0; i < m_labels.size(); ++ i) + total += m_confusion[idx][i]; + + if (total == 0) + return 0.f; + + return m_confusion[idx][idx] / float(total); } /*! - \brief Returns the recall of the training for the given label. + \brief returns the recall of the training for the given label. Recall is the number of true positives divided by the sum of the true positives and the false negatives. @@ -158,12 +186,19 @@ public: */ float recall (Label_handle label) const { - return m_recall[m_map_labels[label]]; + std::size_t idx = label->index(); + if (!label_has_ground_truth(idx)) + return std::numeric_limits::quiet_NaN(); + + std::size_t total = 0; + for (std::size_t i = 0; i < m_labels.size(); ++ i) + total += m_confusion[i][idx]; + return m_confusion[idx][idx] / float(total); } /*! - \brief Returns the \f$F_1\f$ score of the training for the given label. + \brief returns the \f$F_1\f$ score of the training for the given label. \f$F_1\f$ score is the harmonic mean of `precision()` and `recall()`: @@ -174,13 +209,17 @@ public: */ float f1_score (Label_handle label) const { - std::size_t label_idx = m_map_labels[label]; - return 2.f * (m_precision[label_idx] * m_recall[label_idx]) - / (m_precision[label_idx] + m_recall[label_idx]); + float p = precision(label); + float r = recall(label); + + if (p == 0.f && r == 0.f) + return 0.f; + + return 2.f * p * r / (p + r); } /*! - \brief Returns the intersection over union of the training for the + \brief returns the intersection over union of the training for the given label. Intersection over union is the number of true positives divided by @@ -189,7 +228,17 @@ public: */ float intersection_over_union (Label_handle label) const { - return m_iou[m_map_labels[label]]; + std::size_t idx = label->index(); + + std::size_t total = 0; + for (std::size_t i = 0; i < m_labels.size(); ++ i) + { + total += m_confusion[i][idx]; + if (i != idx) + total += m_confusion[idx][i]; + } + + return m_confusion[idx][idx] / float(total); } /// @} @@ -197,26 +246,226 @@ public: /// \name Global Evaluation /// @{ + /*! + \brief returns the number of misclassified items. + */ + std::size_t number_of_misclassified_items() const + { + std::size_t total = 0; + for (std::size_t i = 0; i < m_labels.size(); ++ i) + for (std::size_t j = 0; j < m_labels.size(); ++ j) + if (i != j) + total += m_confusion[i][j]; + return total; + } /*! - \brief Returns the accuracy of the training. + \brief returns the total number of items used for evaluation. + */ + std::size_t number_of_items() const + { + std::size_t total = 0; + for (std::size_t i = 0; i < m_labels.size(); ++ i) + for (std::size_t j = 0; j < m_labels.size(); ++ j) + total += m_confusion[i][j]; + return total; + } + + /*! + \brief returns the accuracy of the training. Accuracy is the total number of true positives divided by the total number of provided inliers. */ - float accuracy() const { return m_accuracy; } + float accuracy() const + { + std::size_t true_positives = 0; + std::size_t total = 0; + for (std::size_t i = 0; i < m_labels.size(); ++ i) + { + true_positives += m_confusion[i][i]; + for (std::size_t j = 0; j < m_labels.size(); ++ j) + total += m_confusion[i][j]; + } + return true_positives / float(total); + } /*! - \brief Returns the mean \f$F_1\f$ score of the training over all + \brief returns the mean \f$F_1\f$ score of the training over all labels (see `f1_score()`). */ - float mean_f1_score() const { return m_mean_f1; } + float mean_f1_score() const + { + float mean = 0; + std::size_t nb = 0; + for (std::size_t i = 0; i < m_labels.size(); ++ i) + if (label_has_ground_truth(i)) + { + mean += f1_score(m_labels[i]); + ++ nb; + } + return mean / nb; + } /*! - \brief Returns the mean intersection over union of the training + \brief returns the mean intersection over union of the training over all labels (see `intersection_over_union()`). */ - float mean_intersection_over_union() const { return m_mean_iou; } + float mean_intersection_over_union() const + { + float mean = 0; + std::size_t nb = 0; + for (std::size_t i = 0; i < m_labels.size(); ++ i) + { + float iou = intersection_over_union(m_labels[i]); + if (!std::isnan(iou)) + { + mean += iou; + ++ nb; + } + } + return mean / nb; + } + + /// @} + + /// \name Output Formatting Functions + /// @{ + + /*! + \brief outputs the evaluation in a simple ASCII format to the stream `os`. + */ + friend std::ostream& operator<< (std::ostream& os, const Evaluation& evaluation) + { + os << "Evaluation of classification:" << std::endl; + os << " * Global results:" << std::endl; + os << " - " << evaluation.number_of_misclassified_items() + << " misclassified item(s) out of " << evaluation.number_of_items() << std::endl + << " - Accuracy = " << evaluation.accuracy() << std::endl + << " - Mean F1 score = " << evaluation.mean_f1_score() << std::endl + << " - Mean IoU = " << evaluation.mean_intersection_over_union() << std::endl; + os << " * Detailed results:" << std::endl; + for (std::size_t i = 0; i < evaluation.m_labels.size(); ++ i) + { + os << " - \"" << evaluation.m_labels[i]->name() << "\": "; + if (evaluation.label_has_ground_truth(i)) + os << "Precision = " << evaluation.precision(evaluation.m_labels[i]) << " ; " + << "Recall = " << evaluation.recall(evaluation.m_labels[i]) << " ; " + << "F1 score = " << evaluation.f1_score(evaluation.m_labels[i]) << " ; " + << "IoU = " << evaluation.intersection_over_union(evaluation.m_labels[i]) << std::endl; + else + os << "(no ground truth)" << std::endl; + } + return os; + } + + /*! + \brief outputs the evaluation as an HTML page to the stream `os`. + */ + static std::ostream& output_to_html (std::ostream& os, const Evaluation& evaluation) + { + os << "" << std::endl + << "" << std::endl + << "" << std::endl + << "" << std::endl + << "Evaluation of CGAL Classification results" << std::endl + << "" << std::endl + << "" << std::endl + << "

    Evaluation of CGAL Classification results

    " << std::endl; + + os << "

    Global Results

    " << std::endl + << "
      " << std::endl + << "
    • " << evaluation.number_of_misclassified_items() + << " misclassified item(s) out of " << evaluation.number_of_items() << "
    • " << std::endl + << "
    • Accuracy = " << evaluation.accuracy() << "
    • " << std::endl + << "
    • Mean F1 score = " << evaluation.mean_f1_score() << "
    • " << std::endl + << "
    • Mean IoU = " << evaluation.mean_intersection_over_union() << "
    • " << std::endl + << "
    " << std::endl; + + const Label_set& labels = evaluation.m_labels; + + os << "

    Detailed Results

    " << std::endl + << "" << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl; + for (std::size_t i = 0; i < labels.size(); ++ i) + if (evaluation.label_has_ground_truth(i)) + os << " " << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl; + else + os << " " << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl + << " " << std::endl; + + os << "
    LabelPrecisionRecallF1 scoreIoU
    " << labels[i]->name() << "" << evaluation.precision(labels[i]) << "" << evaluation.recall(labels[i]) << "" << evaluation.f1_score(labels[i]) << "" << evaluation.intersection_over_union(labels[i]) << "
    " << labels[i]->name() << "(no ground truth)
    " << std::endl; + + os << "

    Confusion Matrix

    " << std::endl + << "" << std::endl + << " " << std::endl + << " " << std::endl; + for (std::size_t i = 0; i < labels.size(); ++ i) + os << " " << std::endl; + os << " " << std::endl; + os << " " << std::endl; + + std::vector sums (labels.size(), 0); + for (std::size_t i = 0; i < labels.size(); ++ i) + { + os << " " << std::endl + << " " << std::endl; + std::size_t sum = 0; + for (std::size_t j = 0; j < labels.size(); ++ j) + { + if (i == j) + os << " " << std::endl; + else + os << " " << std::endl; + sum += evaluation.m_confusion[i][j]; + sums[j] += evaluation.m_confusion[i][j]; + } + os << " " << std::endl; + os << " " << std::endl; + } + + os << " " << std::endl + << " " << std::endl; + std::size_t total = 0; + for (std::size_t j = 0; j < labels.size(); ++ j) + { + os << " " << std::endl; + total += sums[j]; + } + os << " " << std::endl + << " " << std::endl + << "
    " << labels[i]->name() << "PREDICTIONS
    " << labels[i]->name() << "" << evaluation.m_confusion[i][j] << "" << evaluation.m_confusion[i][j] << "" << sum << "
    GROUND TRUTH" << sums[j] << "" << total << "
    " << std::endl + << "

    This page was generated by the CGAL \ +Classification package.

    " << std::endl + << "" << std::endl + << "" << std::endl; + + return os; + } /// @} diff --git a/Classification/include/CGAL/Classification/Feature/Cluster_mean_of_feature.h b/Classification/include/CGAL/Classification/Feature/Cluster_mean_of_feature.h index 0a3846fe2aa..4f39bcfaf62 100644 --- a/Classification/include/CGAL/Classification/Feature/Cluster_mean_of_feature.h +++ b/Classification/include/CGAL/Classification/Feature/Cluster_mean_of_feature.h @@ -40,7 +40,7 @@ class Cluster_mean_of_feature : public CGAL::Classification::Feature_base public: /*! - \brief Constructs the feature. + \brief constructs the feature. \tparam ClusterRange model of `ConstRange`. Its iterator type is `RandomAccessIterator` and its value type is the key type of diff --git a/Classification/include/CGAL/Classification/Feature/Cluster_size.h b/Classification/include/CGAL/Classification/Feature/Cluster_size.h index 52f8fac9d3d..772c87b8fa9 100644 --- a/Classification/include/CGAL/Classification/Feature/Cluster_size.h +++ b/Classification/include/CGAL/Classification/Feature/Cluster_size.h @@ -37,7 +37,7 @@ class Cluster_size : public CGAL::Classification::Feature_base public: /*! - \brief Constructs the feature. + \brief constructs the feature. \tparam ClusterRange model of `ConstRange`. Its iterator type is `RandomAccessIterator` and its value type is the key type of diff --git a/Classification/include/CGAL/Classification/Feature/Cluster_variance_of_feature.h b/Classification/include/CGAL/Classification/Feature/Cluster_variance_of_feature.h index dfa4bfe5891..30817fef411 100644 --- a/Classification/include/CGAL/Classification/Feature/Cluster_variance_of_feature.h +++ b/Classification/include/CGAL/Classification/Feature/Cluster_variance_of_feature.h @@ -40,7 +40,7 @@ class Cluster_variance_of_feature : public CGAL::Classification::Feature_base public: /*! - \brief Constructs the feature. + \brief constructs the feature. \tparam ClusterRange model of `ConstRange`. Its iterator type is `RandomAccessIterator` and its value type is the key type of diff --git a/Classification/include/CGAL/Classification/Feature/Cluster_vertical_extent.h b/Classification/include/CGAL/Classification/Feature/Cluster_vertical_extent.h index ed977c52da6..07d3a2214a5 100644 --- a/Classification/include/CGAL/Classification/Feature/Cluster_vertical_extent.h +++ b/Classification/include/CGAL/Classification/Feature/Cluster_vertical_extent.h @@ -40,7 +40,7 @@ class Cluster_vertical_extent : public CGAL::Classification::Feature_base public: /*! - \brief Constructs the feature. + \brief constructs the feature. \tparam ClusterRange model of `ConstRange`. Its iterator type is `RandomAccessIterator` and its value type is the key type of diff --git a/Classification/include/CGAL/Classification/Feature/Color_channel.h b/Classification/include/CGAL/Classification/Feature/Color_channel.h index 8dfaa93c97c..b0296d191bb 100644 --- a/Classification/include/CGAL/Classification/Feature/Color_channel.h +++ b/Classification/include/CGAL/Classification/Feature/Color_channel.h @@ -80,7 +80,7 @@ private: public: /*! - \brief Constructs a feature based on the given color channel. + \brief constructs a feature based on the given color channel. \param input point range. \param color_map property map to access the colors of the input points. diff --git a/Classification/include/CGAL/Classification/Feature/Distance_to_plane.h b/Classification/include/CGAL/Classification/Feature/Distance_to_plane.h index 19b6b83a959..46adeec1b41 100644 --- a/Classification/include/CGAL/Classification/Feature/Distance_to_plane.h +++ b/Classification/include/CGAL/Classification/Feature/Distance_to_plane.h @@ -46,8 +46,7 @@ namespace Feature { template class Distance_to_plane : public Feature_base { - - typedef typename CGAL::Kernel_traits::Kernel Kernel; + using Kernel = typename CGAL::Kernel_traits::Kernel; #ifdef CGAL_CLASSIFICATION_PRECOMPUTE_FEATURES std::vector distance_to_plane_feature; @@ -59,7 +58,7 @@ class Distance_to_plane : public Feature_base public: /*! - \brief Constructs the feature. + \brief constructs the feature. \param input point range. \param point_map property map to access the input points. diff --git a/Classification/include/CGAL/Classification/Feature/Echo_scatter.h b/Classification/include/CGAL/Classification/Feature/Echo_scatter.h index b2206910102..9fb63252d6e 100644 --- a/Classification/include/CGAL/Classification/Feature/Echo_scatter.h +++ b/Classification/include/CGAL/Classification/Feature/Echo_scatter.h @@ -52,9 +52,9 @@ template Grid; + using Grid = Classification::Planimetric_grid; private: - typedef Classification::Image Image_cfloat; + using Image_cfloat = Classification::Image; const Grid& grid; Image_cfloat Scatter; @@ -62,7 +62,7 @@ private: public: /*! - \brief Constructs the feature. + \brief constructs the feature. \param input point range. \param echo_map property map to access the echo values of the input points. diff --git a/Classification/include/CGAL/Classification/Feature/Elevation.h b/Classification/include/CGAL/Classification/Feature/Elevation.h index 1205c757403..175b20b6a44 100644 --- a/Classification/include/CGAL/Classification/Feature/Elevation.h +++ b/Classification/include/CGAL/Classification/Feature/Elevation.h @@ -50,11 +50,9 @@ namespace Feature { template class Elevation : public Feature_base { - typedef typename GeomTraits::Iso_cuboid_3 Iso_cuboid_3; - - typedef Image Image_float; - typedef Image Image_cfloat; - typedef Planimetric_grid Grid; + using Image_float = Image; + using Image_cfloat = Image; + using Grid = Planimetric_grid; const PointRange& input; PointMap point_map; @@ -66,7 +64,7 @@ class Elevation : public Feature_base public: /*! - \brief Constructs the feature. + \brief constructs the feature. \param input point range. \param point_map property map to access the input points. diff --git a/Classification/include/CGAL/Classification/Feature/Height_above.h b/Classification/include/CGAL/Classification/Feature/Height_above.h index 62eac6f20e2..b59b108c1ac 100644 --- a/Classification/include/CGAL/Classification/Feature/Height_above.h +++ b/Classification/include/CGAL/Classification/Feature/Height_above.h @@ -49,10 +49,8 @@ namespace Feature { template class Height_above : public Feature_base { - typedef typename GeomTraits::Iso_cuboid_3 Iso_cuboid_3; - - typedef Image Image_float; - typedef Planimetric_grid Grid; + using Image_float = Image; + using Grid = Planimetric_grid; const PointRange& input; PointMap point_map; @@ -62,7 +60,7 @@ class Height_above : public Feature_base public: /*! - \brief Constructs the feature. + \brief constructs the feature. \param input point range. \param point_map property map to access the input points. diff --git a/Classification/include/CGAL/Classification/Feature/Height_below.h b/Classification/include/CGAL/Classification/Feature/Height_below.h index 0eae37370f6..22371934155 100644 --- a/Classification/include/CGAL/Classification/Feature/Height_below.h +++ b/Classification/include/CGAL/Classification/Feature/Height_below.h @@ -49,8 +49,6 @@ namespace Feature { template class Height_below : public Feature_base { - typedef typename GeomTraits::Iso_cuboid_3 Iso_cuboid_3; - typedef Image Image_float; typedef Planimetric_grid Grid; @@ -62,7 +60,7 @@ class Height_below : public Feature_base public: /*! - \brief Constructs the feature. + \brief constructs the feature. \param input point range. \param point_map property map to access the input points. diff --git a/Classification/include/CGAL/Classification/Feature/Simple_feature.h b/Classification/include/CGAL/Classification/Feature/Simple_feature.h index 12efb1563be..8dd5f59168c 100644 --- a/Classification/include/CGAL/Classification/Feature/Simple_feature.h +++ b/Classification/include/CGAL/Classification/Feature/Simple_feature.h @@ -42,7 +42,7 @@ class Simple_feature : public Feature_base public: /*! - \brief Constructs the feature using an input range and a property map. + \brief constructs the feature using an input range and a property map. \param input point range. \param property_map property map to access scalar field. diff --git a/Classification/include/CGAL/Classification/Feature/Vertical_dispersion.h b/Classification/include/CGAL/Classification/Feature/Vertical_dispersion.h index c173f70acca..2a5c8e6e9c7 100644 --- a/Classification/include/CGAL/Classification/Feature/Vertical_dispersion.h +++ b/Classification/include/CGAL/Classification/Feature/Vertical_dispersion.h @@ -56,8 +56,8 @@ namespace Feature { template class Vertical_dispersion : public Feature_base { - typedef Classification::Image Image_cfloat; - typedef Classification::Planimetric_grid Grid; + using Image_cfloat = Classification::Image; + using Grid = Classification::Planimetric_grid; const Grid& grid; Image_cfloat Dispersion; @@ -65,7 +65,7 @@ class Vertical_dispersion : public Feature_base public: /*! - \brief Constructs the feature. + \brief constructs the feature. \param input point range. \param point_map property map to access the input points. diff --git a/Classification/include/CGAL/Classification/Feature/Vertical_range.h b/Classification/include/CGAL/Classification/Feature/Vertical_range.h index 95fc5042e18..45b9c98d3ee 100644 --- a/Classification/include/CGAL/Classification/Feature/Vertical_range.h +++ b/Classification/include/CGAL/Classification/Feature/Vertical_range.h @@ -49,10 +49,8 @@ namespace Feature { template class Vertical_range : public Feature_base { - typedef typename GeomTraits::Iso_cuboid_3 Iso_cuboid_3; - - typedef Image Image_float; - typedef Planimetric_grid Grid; + using Image_float = Image; + using Grid = Planimetric_grid; const PointRange& input; PointMap point_map; @@ -62,7 +60,7 @@ class Vertical_range : public Feature_base public: /*! - \brief Constructs the feature. + \brief constructs the feature. \param input point range. \param point_map property map to access the input points. diff --git a/Classification/include/CGAL/Classification/Feature/Verticality.h b/Classification/include/CGAL/Classification/Feature/Verticality.h index 8bfe7537bc1..2ef8cac6376 100644 --- a/Classification/include/CGAL/Classification/Feature/Verticality.h +++ b/Classification/include/CGAL/Classification/Feature/Verticality.h @@ -46,7 +46,7 @@ class Verticality : public Feature_base public: /*! - \brief Constructs the feature using local eigen analysis. + \brief constructs the feature using local eigen analysis. \tparam InputRange model of `ConstRange`. Its iterator type is `RandomAccessIterator`. @@ -64,7 +64,7 @@ public: /*! - \brief Constructs the feature using provided normals of points. + \brief constructs the feature using provided normals of points. \tparam PointRange model of `ConstRange`. Its iterator type is `RandomAccessIterator` and its value type is the key type of diff --git a/Classification/include/CGAL/Classification/Feature_base.h b/Classification/include/CGAL/Classification/Feature_base.h index 469bf079bc9..89ba6e60384 100644 --- a/Classification/include/CGAL/Classification/Feature_base.h +++ b/Classification/include/CGAL/Classification/Feature_base.h @@ -14,8 +14,8 @@ #include -#include - +#include +#include #include namespace CGAL { @@ -42,18 +42,18 @@ public: /// \endcond /*! - \brief Returns the name of the feature (initialized to + \brief returns the name of the feature (initialized to `abstract_feature` for `Feature_base`). */ const std::string& name() const { return m_name; } /*! - \brief Changes the name of the feature. + \brief changes the name of the feature. */ void set_name (const std::string& name) { m_name = name; } /*! - \brief Returns the value taken by the feature for at the item for + \brief returns the value taken by the feature for at the item for the item at position `index`. This method must be implemented by inherited classes. */ @@ -72,7 +72,6 @@ public: */ class Feature_handle { }; #else -//typedef boost::shared_ptr Feature_handle; class Feature_set; @@ -80,19 +79,23 @@ class Feature_handle { friend Feature_set; - boost::shared_ptr > m_base; + using Feature_base_ptr = std::unique_ptr; + std::shared_ptr m_base; - template - Feature_handle (Feature* f) : m_base (new boost::shared_ptr(f)) { } - - template - void attach (Feature* f) const + template + Feature_handle (Feature_ptr f) + : m_base (std::make_shared(std::move(f))) { - *m_base = boost::shared_ptr(f); + } + + template + void attach (Feature_ptr f) + { + *m_base = std::move(f); } public: - Feature_handle() : m_base (new boost::shared_ptr()) { } + Feature_handle() : m_base (std::make_shared()) { } Feature_base& operator*() { return **m_base; } @@ -107,6 +110,17 @@ public: #endif +/*! + \ingroup PkgClassificationFeature + + \brief casts a feature handle to a specialized feature pointer. +*/ +template +FeatureType* feature_cast (Feature_handle fh) +{ + return dynamic_cast(&*(fh)); +} + } // namespace Classification diff --git a/Classification/include/CGAL/Classification/Feature_set.h b/Classification/include/CGAL/Classification/Feature_set.h index 13fb59840e5..2573c3abde6 100644 --- a/Classification/include/CGAL/Classification/Feature_set.h +++ b/Classification/include/CGAL/Classification/Feature_set.h @@ -16,15 +16,14 @@ #include -#include - #ifdef CGAL_LINKED_WITH_TBB -#include #include #endif // CGAL_LINKED_WITH_TBB #include +#include #include +#include namespace CGAL { @@ -40,7 +39,7 @@ the addition and the deletion of features. */ class Feature_set { - typedef std::vector Base; + using Base = std::vector; Base m_features; struct Compare_name @@ -54,42 +53,35 @@ class Feature_set }; #ifdef CGAL_LINKED_WITH_TBB - tbb::task_group* m_tasks; + std::unique_ptr m_tasks; #endif // CGAL_LINKED_WITH_TBB public: +#ifdef DOXYGEN_RUNNING + using const_iterator = unspecified_type; ///< A random access constant iterator with value type `Feature_handle`. + using iterator = unspecified_type; ///< A random access iterator with value type `Feature_handle`. +#else + using const_iterator = std::vector::const_iterator; + using iterator = std::vector::iterator; +#endif + /// \name Constructor /// @{ /*! - \brief Creates an empty feature set. + \brief creates an empty feature set. */ Feature_set() -#ifdef CGAL_LINKED_WITH_TBB - : m_tasks(nullptr) -#endif { } /// @} - /// \cond SKIP_IN_MANUAL - virtual ~Feature_set() - { -#ifdef CGAL_LINKED_WITH_TBB - if (m_tasks != nullptr) - delete m_tasks; - for (std::size_t i = 0; i < m_adders.size(); ++ i) - delete m_adders[i]; -#endif - } - /// \endcond - /// \name Modifications /// @{ /*! - \brief Instantiates a new feature and adds it to the set. + \brief instantiates a new feature and adds it to the set. If several calls of `add()` are surrounded by `begin_parallel_additions()` and `end_parallel_additions()`, they @@ -113,20 +105,21 @@ public: Feature_handle add (T&& ... t) { #ifdef CGAL_LINKED_WITH_TBB - if (m_tasks != nullptr) + if (m_tasks) { m_features.push_back (Feature_handle()); - Parallel_feature_adder* adder - = new Parallel_feature_adder(m_features.back(), std::forward(t)...); - - m_adders.push_back (adder); + Parallel_feature_adder_ptr adder + = std::make_unique > + (m_features.back(), std::forward(t)...); m_tasks->run (*adder); + + m_adders.emplace_back (std::move (adder)); } else #endif { - m_features.push_back (Feature_handle (new Feature(std::forward(t)...))); + m_features.push_back (Feature_handle (std::make_unique(std::forward(t)...))); } return m_features.back(); } @@ -136,20 +129,21 @@ public: Feature_handle add_with_scale_id (std::size_t i, T&& ... t) { #ifdef CGAL_LINKED_WITH_TBB - if (m_tasks != nullptr) + if (m_tasks) { m_features.push_back (Feature_handle()); - Parallel_feature_adder* adder - = new Parallel_feature_adder(i, m_features.back(), std::forward(t)...); - - m_adders.push_back (adder); + Parallel_feature_adder_ptr adder + = std::make_unique > + (i, m_features.back(), std::forward(t)...); m_tasks->run (*adder); + + m_adders.emplace_back (std::move (adder)); } else #endif { - m_features.push_back (Feature_handle (new Feature(std::forward(t)...))); + m_features.push_back (Feature_handle (std::make_unique(std::forward(t)...))); m_features.back()->set_name (m_features.back()->name() + "_" + std::to_string(i)); } return m_features.back(); @@ -158,7 +152,7 @@ public: /*! - \brief Removes a feature. + \brief removes a feature. \param feature the handle to feature type that must be removed. @@ -177,7 +171,7 @@ public: } /*! - \brief Removes all features. + \brief removes all features. */ void clear () { @@ -190,16 +184,15 @@ public: /// @{ -#if defined(CGAL_LINKED_WITH_TBB) || defined(DOXYGEN_RUNNING) - /*! - \brief Initializes structures to compute features in parallel. + \brief initializes structures to compute features in parallel. If the user wants to add features in parallel, this function should be called before making several calls of `add()`. After the calls of `add()`, `end_parallel_additions()` should be called. - \note This function requires \ref thirdpartyTBB. + \note If \ref thirdpartyTBB is not available, this function does + nothing. \warning As arguments of `add()` are passed by reference and that new threads are started if `begin_parallel_additions()` is used, it is @@ -212,33 +205,33 @@ public: */ void begin_parallel_additions() { - m_tasks = new tbb::task_group; +#ifdef CGAL_LINKED_WITH_TBB + m_tasks = std::make_unique(); +#endif } /*! - \brief Waits for the end of parallel feature computation and + \brief waits for the end of parallel feature computation and clears dedicated data structures afterwards. If the user wants to add features in parallel, this function should be called after `begin_parallel_additions()` and several calls of `add()`. - \note This function requires \ref thirdpartyTBB. + \note If \ref thirdpartyTBB is not available, this function does + nothing. \sa `begin_parallel_additions()` */ void end_parallel_additions() { +#ifdef CGAL_LINKED_WITH_TBB m_tasks->wait(); - delete m_tasks; - m_tasks = nullptr; - - for (std::size_t i = 0; i < m_adders.size(); ++ i) - delete m_adders[i]; + m_tasks.release(); m_adders.clear(); - } #endif + } /// @} @@ -246,8 +239,13 @@ public: /// \name Access /// @{ + const_iterator begin() const { return m_features.begin(); } + iterator begin() { return m_features.begin(); } + const_iterator end() const { return m_features.end(); } + iterator end() { return m_features.end(); } + /*! - \brief Returns how many features are defined. + \brief returns how many features are defined. */ std::size_t size() const { @@ -256,7 +254,7 @@ public: /*! - \brief Returns the \f$i^{th}\f$ feature. + \brief returns the \f$i^{th}\f$ feature. */ Feature_handle operator[](std::size_t i) const { @@ -293,18 +291,18 @@ private: { std::size_t scale; mutable Feature_handle fh; - boost::shared_ptr > args; + std::shared_ptr > args; Parallel_feature_adder (Feature_handle fh, T&& ... t) : scale (std::size_t(-1)), fh (fh) { - args = boost::make_shared >(std::forward(t)...); + args = std::make_shared >(std::forward(t)...); } Parallel_feature_adder (std::size_t scale, Feature_handle fh, T&& ... t) : scale(scale), fh (fh) { - args = boost::make_shared >(std::forward(t)...); + args = std::make_shared >(std::forward(t)...); } template @@ -325,7 +323,7 @@ private: template void add_feature (Tuple& t, seq) const { - fh.attach (new Feature (std::forward(std::get(t))...)); + fh.attach (std::make_unique (std::forward(std::get(t))...)); if (scale != std::size_t(-1)) fh->set_name (fh->name() + "_" + std::to_string(scale)); } @@ -337,7 +335,11 @@ private: }; - std::vector m_adders; + using Abstract_parallel_feature_adder_ptr = std::unique_ptr; + template + using Parallel_feature_adder_ptr = std::unique_ptr >; + + std::vector m_adders; /// \endcond }; diff --git a/Classification/include/CGAL/Classification/Image.h b/Classification/include/CGAL/Classification/Image.h index 45759ed219d..924e39bde24 100644 --- a/Classification/include/CGAL/Classification/Image.h +++ b/Classification/include/CGAL/Classification/Image.h @@ -14,8 +14,8 @@ #include -#include #include +#include #define CGAL_CLASSIFICATION_IMAGE_SIZE_LIMIT 100000000 @@ -27,15 +27,15 @@ namespace Classification { template class Image { - typedef std::vector Vector; - typedef std::map Map; + using Vector = std::vector; + using Map = std::map; std::size_t m_width; std::size_t m_height; std::size_t m_depth; - boost::shared_ptr m_raw; - boost::shared_ptr m_sparse; + std::shared_ptr m_raw; + std::shared_ptr m_sparse; Type m_default; // Forbid using copy constructor @@ -45,7 +45,7 @@ class Image public: - Image () : m_width(0), m_height(0), m_depth(0), m_raw (nullptr) + Image () : m_width(0), m_height(0), m_depth(0) { } @@ -57,9 +57,9 @@ public: if (m_width * m_height * m_depth > 0) { if (m_width * m_height * m_depth < CGAL_CLASSIFICATION_IMAGE_SIZE_LIMIT) - m_raw = boost::shared_ptr (new Vector(m_width * m_height * m_depth)); + m_raw = std::make_shared (m_width * m_height * m_depth); else - m_sparse = boost::shared_ptr (new Map()); + m_sparse = std::make_shared (); } } @@ -69,8 +69,8 @@ public: void free() { - m_raw = boost::shared_ptr(); - m_sparse = boost::shared_ptr(); + m_raw.reset(); + m_sparse.reset(); } Image& operator= (const Image& other) @@ -94,7 +94,7 @@ public: Type& operator() (const std::size_t& x, const std::size_t& y, const std::size_t& z = 0) { - if (m_raw == boost::shared_ptr()) // sparse case + if (!m_raw) // sparse case { typename Map::iterator inserted = m_sparse->insert (std::make_pair (coord(x,y,z), Type())).first; @@ -105,7 +105,7 @@ public: } const Type& operator() (const std::size_t& x, const std::size_t& y, const std::size_t& z = 0) const { - if (m_raw == boost::shared_ptr()) // sparse case + if (!m_raw) // sparse case { typename Map::iterator found = m_sparse->find (coord(x,y,z)); if (found != m_sparse->end()) diff --git a/Classification/include/CGAL/Classification/Label.h b/Classification/include/CGAL/Classification/Label.h index 45b2545803c..6a21b630768 100644 --- a/Classification/include/CGAL/Classification/Label.h +++ b/Classification/include/CGAL/Classification/Label.h @@ -13,38 +13,89 @@ #define CGAL_CLASSIFICATION_LABEL_H #include +#include -#include +#include namespace CGAL { namespace Classification { +/// \cond SKIP_IN_MANUAL +class Label_set; +/// \endcond + /*! \ingroup PkgClassificationLabel -\brief %Classification label (for example: vegetation, ground, etc.) -defined as a set of relationships with classification features. +\brief %Classification label (for example: vegetation, ground, etc.). +\note Labels should always be constructed from a `CGAL::Classification::Label_set` object. */ class Label { private: std::string m_name; + std::size_t m_index; + std::size_t m_standard_index; + CGAL::Color m_color; + + friend Label_set; public: - /*! - \param name name of the classification label (e.g. vegetation). - */ - Label (std::string name) : m_name (name) { } + /// \cond SKIP_IN_MANUAL + // Undocumented: Labels should be created by the set + Label (std::string name, std::size_t index, std::size_t standard_index, + const CGAL::Color& color) + : m_name (name), m_index (index), m_standard_index (standard_index) + , m_color (color) + { } + /// \endcond + /// \name Access + /// @{ + + /*! + returns the name of the classification label (\a e.g. vegetation). + */ const std::string& name() const { return m_name; } - /// \cond SKIP_IN_MANUAL + /*! + returns the index of the classification label in the label set. + + \note This index cannot be changed by the user and is handled directly by the label set. + */ + std::size_t index() const { return m_index; } + + /*! + returns the standard index of the classification label (\a e.g. index in the ASPRS standard). + + \note This index is purely user-oriented and is not used by the classification algorithms. + */ + std::size_t standard_index() const { return m_standard_index; } + + /*! + returns the color used to represent the label. + + \note The color is purely user-oriented and is not used by the + classification algorithms. It is not to be confused with a color + attribute embedded in a data set which _can_ be used (see + `Color_channel`). + */ + const CGAL::Color& color() const { return m_color; } + + /// @} + + /// \name Modification + /// @{ + void set_name (const std::string& name) { m_name = name; } - /// \endcond + void set_standard_index(std::size_t idx) { m_standard_index = idx; } + void set_color (const Color& color) { m_color = color; } + + /// @} }; #ifdef DOXYGEN_RUNNING @@ -57,7 +108,7 @@ public: */ class Label_handle { }; #else -typedef boost::shared_ptr