// Copyright (c) 2016 INRIA Sophia-Antipolis (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org). // You can redistribute it and/or modify it under the terms of the GNU // General Public License as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // Licensees holding a valid commercial license may use this file in // accordance with the commercial license agreement provided with the software. // // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. // // $URL$ // $Id$ // // Author(s) : Simon Giraudot #ifndef CGAL_POINT_SET_CLASSIFIER_H #define CGAL_POINT_SET_CLASSIFIER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace CGAL { /*! \ingroup PkgClassification \brief TODO \tparam Kernel model of \cgal Kernel. \tparam Range range of items, model of `ConstRange`. Its iterator type is `RandomAccessIterator`. \tparam PointMap model of `ReadablePropertyMap` whose key type is the value type of the iterator of `Range` and value type is `Point_3`. \tparam DiagonalizeTraits model of `DiagonalizeTraits` used for matrix diagonalization. */ template > class Point_set_classifier : public Classifier { public: typedef typename Kernel::Iso_cuboid_3 Iso_cuboid_3; typedef Classifier Base; typedef typename Range::const_iterator Iterator; using Base::m_input; using Base::m_item_map; typedef Classification::Planimetric_grid Planimetric_grid; typedef Classification::Point_set_neighborhood Neighborhood; typedef Classification::Local_eigen_analysis Local_eigen_analysis; typedef Classification::Attribute_handle Attribute_handle; typedef Classification::Type Type; typedef Classification::Type_handle Type_handle; /// \cond SKIP_IN_MANUAL typedef typename Kernel::Point_3 Point; typedef Classification::Attribute::Anisotropy Anisotropy; typedef Classification::Attribute::Distance_to_plane Distance_to_plane; typedef Classification::Attribute::Eigentropy Eigentropy; typedef Classification::Attribute::Elevation Elevation; typedef Classification::Attribute::Linearity Linearity; typedef Classification::Attribute::Omnivariance Omnivariance; typedef Classification::Attribute::Planarity Planarity; typedef Classification::Attribute::Sphericity Sphericity; typedef Classification::Attribute::Sum_eigenvalues Sum_eigen; typedef Classification::Attribute::Surface_variation Surface_variation; typedef Classification::Attribute::Vertical_dispersion Dispersion; typedef Classification::Attribute::Verticality Verticality; /// \endcond typedef typename Classification::RGB_Color RGB_Color; private: struct Scale { Neighborhood* neighborhood; Planimetric_grid* grid; Local_eigen_analysis* eigen; double voxel_size; std::vector attributes; Scale (const Range& input, PointMap point_map, const Iso_cuboid_3& bbox, double voxel_size) : voxel_size (voxel_size) { CGAL::Timer t; t.start(); if (voxel_size < 0.) neighborhood = new Neighborhood (input, point_map); else neighborhood = new Neighborhood (input, point_map, voxel_size); t.stop(); if (voxel_size < 0.) std::cerr << "Neighborhood computed in " << t.time() << " second(s)" << std::endl; else std::cerr << "Neighborhood with voxel size " << voxel_size << " computed in " << t.time() << " second(s)" << std::endl; t.reset(); t.start(); eigen = new Local_eigen_analysis (input, point_map, neighborhood->k_neighbor_query(6)); double range = eigen->mean_range(); if (this->voxel_size < 0) this->voxel_size = range / 3; t.stop(); std::cerr << "Eigen values computed in " << t.time() << " second(s)" << std::endl; std::cerr << "Range = " << range << std::endl; t.reset(); t.start(); grid = new Planimetric_grid (input, point_map, bbox, this->voxel_size); t.stop(); std::cerr << "Planimetric grid computed in " << t.time() << " second(s)" << std::endl; t.reset(); } ~Scale() { delete neighborhood; delete grid; delete eigen; } double grid_resolution() const { return voxel_size; } double radius_neighbors() const { return voxel_size * 5; } double radius_dtm() const { return voxel_size * 100; } }; Iso_cuboid_3 m_bbox; std::vector m_scales; public: /// \cond SKIP_IN_MANUAL Point_set_classifier(const Range& input, PointMap point_map) : Base (input, point_map) { } /// \endcond template const T& get_parameter (const T& t) { return t; } template void generate_attributes (std::size_t nb_scales, VectorMap normal_map = VectorMap(), ColorMap color_map = ColorMap(), EchoMap echo_map = EchoMap()) { typedef typename Default::Get::type Vmap; typedef typename Default::Get::type Cmap; typedef typename Default::Get::type Emap; generate_attributes_impl (nb_scales, get_parameter(normal_map), get_parameter(color_map), get_parameter(echo_map)); } template Default_property_map get_parameter (const Default&) { return Default_property_map(); } template void generate_attributes_impl (std::size_t nb_scales, VectorMap normal_map, ColorMap color_map, EchoMap echo_map) { m_bbox = CGAL::bounding_box (boost::make_transform_iterator (m_input.begin(), CGAL::Property_map_to_unary_function(m_item_map)), boost::make_transform_iterator (m_input.end(), CGAL::Property_map_to_unary_function(m_item_map))); CGAL::Timer t; t.start(); m_scales.reserve (nb_scales); double voxel_size = - 1.; m_scales.push_back (new Scale (m_input, m_item_map, m_bbox, voxel_size)); voxel_size = m_scales[0]->grid_resolution(); for (std::size_t i = 1; i < nb_scales; ++ i) { voxel_size *= 2; m_scales.push_back (new Scale (m_input, m_item_map, m_bbox, voxel_size)); } generate_point_based_attributes (); generate_normal_based_attributes (normal_map); generate_color_based_attributes (color_map); generate_echo_based_attributes (echo_map); } /// \cond SKIP_IN_MANUAL virtual ~Point_set_classifier() { clear(); } /// \endcond /*! \brief Returns the bounding box of the input point set. */ const Iso_cuboid_3& bbox() const { return m_bbox; } /*! \brief Returns the neighborhood structure at scale `scale`. */ const Neighborhood& neighborhood(std::size_t scale = 0) const { return (*m_scales[scale]->neighborhood); } /*! \brief Returns the planimetric grid structure at scale `scale`. */ const Planimetric_grid& grid(std::size_t scale = 0) const { return *(m_scales[scale]->grid); } /*! \brief Returns the local eigen analysis structure at scale `scale`. */ const Local_eigen_analysis& eigen(std::size_t scale = 0) const { return *(m_scales[scale]->eigen); } /*! \brief Returns the grid resolution at scale `scale`. */ double grid_resolution(std::size_t scale = 0) const { return m_scales[scale]->grid_resolution(); } /*! \brief Returns the radius used for neighborhood queries at scale `scale`. */ double radius_neighbors(std::size_t scale = 0) const { return m_scales[scale]->radius_neighbors(); } /*! \brief Returns the radius used for digital terrain modeling at scale `scale`. */ double radius_dtm(std::size_t scale = 0) const { return m_scales[scale]->radius_dtm(); } /// \cond SKIP_IN_MANUAL void info() const { std::cerr << m_scales.size() << " scale(s) used:" << std::endl; for (std::size_t i = 0; i < m_scales.size(); ++ i) { std::size_t nb_useful = 0; for (std::size_t j = 0; j < m_scales[i]->attributes.size(); ++ j) if (m_scales[i]->attributes[j]->weight() != 0.) nb_useful ++; std::cerr << " * scale " << i << " with size " << m_scales[i]->voxel_size << ", " << nb_useful << " useful attribute(s)"; if (nb_useful != 0) std::cerr << ":" << std::endl; else std::cerr << std::endl; for (std::size_t j = 0; j < m_scales[i]->attributes.size(); ++ j) if (m_scales[i]->attributes[j]->weight() != 0.) std::cerr << " - " << m_scales[i]->attributes[j]->name() << " (weight = " << m_scales[i]->attributes[j]->weight() << ")" << std::endl; } } /// \endcond /*! \brief Clears all computed data structures. */ void clear() { for (std::size_t i = 0; i < m_scales.size(); ++ i) delete m_scales[i]; m_scales.clear(); this->clear_classification_types(); this->clear_attributes(); } /*! \brief Generate all possible attributes from an input range. This method calls `generate_point_based_attributes()`, `generate_normal_based_attributes()`, `generate_color_based_attributes()` and `generate_echo_based_attributes()`. If a property map is left to its default `CGAL::Default_property_map` type, the corresponding attributes are not computed (this method can thus be called even if a point set does not have associated normal, color or echo properties). \tparam VectorMap is a model of `ReadablePropertyMap` with value type `Vector_3`. \tparam ColorMap is a model of `ReadablePropertyMap` with value type `CGAL::Classification::RGB_Color`. \tparam EchoMap is a model of `ReadablePropertyMap` with value type `std::size_`. \param psc The classification object where to store the attributes \param begin Iterator to the first input object \param end Past-the-end iterator \param point_map Property map to access the input points \param normal_map Property map to access the normal vectors of the input points (if any). \param color_map Property map to access the colors of the input points (if any). \param echo_map Property map to access the echo values of the input points (if any). */ /*! \brief Generate all points attributes from an input range. Generate, for all precomputed scales, the following attributes: - `CGAL::Classification::Attribute::Anisotropy` - `CGAL::Classification::Attribute::Distance_to_plane` - `CGAL::Classification::Attribute::Eigentropy` - `CGAL::Classification::Attribute::Elevation` - `CGAL::Classification::Attribute::Linearity` - `CGAL::Classification::Attribute::Omnivariance` - `CGAL::Classification::Attribute::Planarity` - `CGAL::Classification::Attribute::Sphericity` - `CGAL::Classification::Attribute::Sum_eigenvalues` - `CGAL::Classification::Attribute::Surface_variation` - `CGAL::Classification::Attribute::Vertical_dispersion` \param psc The classification object where to store the attributes \param begin Iterator to the first input object \param end Past-the-end iterator \param point_map Property map to access the input points */ void generate_point_based_attributes () { CGAL::Timer teigen, tpoint; teigen.start(); generate_multiscale_attribute_variant_0 (); generate_multiscale_attribute_variant_1 (); generate_multiscale_attribute_variant_0 (); generate_multiscale_attribute_variant_0 (); generate_multiscale_attribute_variant_0 (); generate_multiscale_attribute_variant_0 (); generate_multiscale_attribute_variant_0 (); generate_multiscale_attribute_variant_0 (); generate_multiscale_attribute_variant_0 (); teigen.stop(); tpoint.start(); generate_multiscale_attribute_variant_2 (); generate_multiscale_attribute_variant_3 (); tpoint.stop(); std::cerr << "Point based attributes computed in " << tpoint.time() << " second(s)" << std::endl; std::cerr << "Eigen based attributes computed in " << teigen.time() << " second(s)" << std::endl; } /*! \brief Generate all normal attributes from an input range. Generate, for all precomputed scales, the following attribute: - `CGAL::Classification::Attribute::Verticality` If the normal map is left to its default type `CGAL::Default_property_map`, then the verticality attributes are still computed by using an approximation of the normal vector provided by the corresponding `Local_eigen_analysis` object. \tparam VectorMap Property map to access the normal vectors of the input points (if any). \param psc The classification object where to store the attributes \param begin Iterator to the first input object \param end Past-the-end iterator \param normal_map Property map to access the normal vectors of the input points (if any). */ #ifdef DOXYGEN_RUNNING template > #else template #endif void generate_normal_based_attributes( #ifdef DOXYGEN_RUNNING VectorMap normal_map = VectorMap()) #else VectorMap normal_map) #endif { CGAL::Timer t; t.start(); this->template add_attribute (normal_map); m_scales[0]->attributes.push_back (this->get_attribute (this->number_of_attributes() - 1)); t.stop(); std::cerr << "Normal based attributes computed in " << t.time() << " second(s)" << std::endl; } /// \cond SKIP_IN_MANUAL void generate_normal_based_attributes(const CGAL::Default_property_map& = CGAL::Default_property_map()) { CGAL::Timer t; t.start(); generate_multiscale_attribute_variant_0 (); std::cerr << "Normal based attributes computed in " << t.time() << " second(s)" << std::endl; } /// \endcond /*! \brief Generate a set of color attributes from an input range. Generate the following attributes: - 9 attributes `CGAL::Classification::Attribute::Hsv` on channel 0 (hue) with mean ranging from 0° to 360° and standard deviation of 22.5. - 5 attributes `CGAL::Classification::Attribute::Hsv` on channel 1 (saturation) with mean ranging from 0 to 100 and standard deviation of 12.5 - 5 attributes `CGAL::Classification::Attribute::Hsv` on channel 2 (value) with mean ranging from 0 to 100 and standard deviation of 12.5 This decomposition allows to handle all the color spectrum with a usually sufficiently accurate precision. \tparam ColorMap is a model of `ReadablePropertyMap` with value type `CGAL::Classification::RGB_Color`. \param psc The classification object where to store the attributes \param begin Iterator to the first input object \param end Past-the-end iterator \param color_map Property map to access the colors of the input points. */ template void generate_color_based_attributes(ColorMap color_map) { typedef Classification::Attribute::Hsv Hsv; CGAL::Timer t; t.start(); for (std::size_t i = 0; i <= 8; ++ i) { this->template add_attribute (color_map, 0, 45 * i, 22.5); m_scales[0]->attributes.push_back (this->get_attribute (this->number_of_attributes() - 1)); } for (std::size_t i = 0; i <= 4; ++ i) { this->template add_attribute (color_map, 1, 25 * i, 12.5); m_scales[0]->attributes.push_back (this->get_attribute (this->number_of_attributes() - 1)); } for (std::size_t i = 0; i <= 4; ++ i) { this->template add_attribute (color_map, 2, 25 * i, 12.5); m_scales[0]->attributes.push_back (this->get_attribute (this->number_of_attributes() - 1)); } t.stop(); std::cerr << "Color based attributes computed in " << t.time() << " second(s)" << std::endl; } /// \cond SKIP_IN_MANUAL void generate_color_based_attributes(const CGAL::Default_property_map&) { } /// \endcond /*! \brief Generate all echo attributes from an input range. Generate, for all precomputed scales, the following attribute: - `CGAL::Classification::Attribute::Echo_scatter` \tparam EchoMap Property map to access the echo values of the input points (if any). \param psc The classification object where to store the attributes \param begin Iterator to the first input object \param end Past-the-end iterator \param echo_map Property map to access the echo values of the input points (if any). */ template void generate_echo_based_attributes(EchoMap echo_map) { typedef Classification::Attribute::Echo_scatter Echo_scatter; CGAL::Timer t; t.start(); for (std::size_t i = 0; i < m_scales.size(); ++ i) { this->template add_attribute(echo_map, *(m_scales[i]->grid), m_scales[i]->grid_resolution(), m_scales[i]->radius_neighbors()); m_scales[i]->attributes.push_back (this->get_attribute (this->number_of_attributes() - 1)); } t.stop(); std::cerr << "Echo based attributes computed in " << t.time() << " second(s)" << std::endl; } /// \cond SKIP_IN_MANUAL void generate_echo_based_attributes(const CGAL::Default_property_map&) { } void get_map_scale (std::map& map_scale) { for (std::size_t i = 0; i < m_scales.size(); ++ i) for (std::size_t j = 0; j < m_scales[i]->attributes.size(); ++ j) map_scale[m_scales[i]->attributes[j]] = i; } std::size_t scale_of_attribute (Attribute_handle att) { for (std::size_t i = 0; i < m_scales.size(); ++ i) for (std::size_t j = 0; j < m_scales[i]->attributes.size(); ++ j) if (m_scales[i]->attributes[j] == att) return i; return (std::size_t)(-1); } std::string name_att (Attribute_handle att, std::map& map_scale) { std::ostringstream oss; oss << att->name() << "_" << map_scale[att]; return oss.str(); } /// \endcond /*! \brief Saves the current configuration in the stream `output`. This allows to easily save and recover a specific classification configuration, that is to say: - The smallest voxel size defined - The attributes and their respective weights - The classification types and the effect the attributes have on them The output file is written in an XML format that is readable by the `load()` method and the constructor that takes a file name as parameter. \param output Output stream \param psc Classification object whose attributes and types must be saved */ void save_configuration (std::ostream& output) { boost::property_tree::ptree tree; // tree.put("classification.parameters.grid_resolution", m_grid_resolution); // tree.put("classification.parameters.radius_neighbors", m_radius_neighbors); tree.put("classification.parameters.voxel_size", m_scales[0]->voxel_size); std::map map_scale; get_map_scale (map_scale); for (std::size_t i = 0; i < this->number_of_attributes(); ++ i) { Attribute_handle att = this->get_attribute(i); if (att->weight() == 0) continue; boost::property_tree::ptree ptr; ptr.put("id", name_att (att, map_scale)); ptr.put("weight", att->weight()); tree.add_child("classification.attributes.attribute", ptr); } for (std::size_t i = 0; i < this->number_of_classification_types(); ++ i) { Type_handle type = this->get_classification_type(i); boost::property_tree::ptree ptr; ptr.put("id", type->name()); for (std::size_t j = 0; j < this->number_of_attributes(); ++ j) { Attribute_handle att = this->get_attribute(j); if (att->weight() == 0) continue; boost::property_tree::ptree ptr2; ptr2.put("id", name_att (att, map_scale)); Classification::Attribute::Effect effect = type->attribute_effect(att); if (effect == Classification::Attribute::PENALIZING) ptr2.put("effect", "penalized"); else if (effect == Classification::Attribute::NEUTRAL) ptr2.put("effect", "neutral"); else if (effect == Classification::Attribute::FAVORING) ptr2.put("effect", "favored"); ptr.add_child("attribute", ptr2); } tree.add_child("classification.types.type", ptr); } // Write property tree to XML file boost::property_tree::xml_writer_settings settings(' ', 3); boost::property_tree::write_xml(output, tree, settings); } /*! \brief Load a configuration from the stream `input`. All data structures, attributes and types specified in the input stream `input` are instantiated if possible (in particular, property maps needed should be provided). The input file is written in an XML format written by the `save()` method. \tparam VectorMap is a model of `ReadablePropertyMap` with value type `Vector_3`. \tparam ColorMap is a model of `ReadablePropertyMap` with value type `CGAL::Classification::RGB_Color`. \tparam EchoMap is a model of `ReadablePropertyMap` with value type `std::size_`. \param input Input stream \param psc Classification object where to store attributes and types \param begin Iterator to the first input object \param end Past-the-end iterator \param point_map Property map to access the input points \param normal_map Property map to access the normal vectors of the input points (if any). \param color_map Property map to access the colors of the input points (if any). \param echo_map Property map to access the echo values of the input points (if any). */ template bool load_configuration (std::istream& input, VectorMap normal_map = VectorMap(), ColorMap color_map = ColorMap(), EchoMap echo_map = EchoMap()) { typedef typename Default::Get::type Vmap; typedef typename Default::Get::type Cmap; typedef typename Default::Get::type Emap; return load_configuration_impl (input, get_parameter(normal_map), get_parameter(color_map), get_parameter(echo_map)); } template bool load_configuration_impl (std::istream& input, VectorMap normal_map, ColorMap color_map, EchoMap echo_map) { typedef Classification::Attribute::Echo_scatter Echo_scatter; typedef Classification::Attribute::Hsv Hsv; clear(); m_bbox = CGAL::bounding_box (boost::make_transform_iterator (m_input.begin(), CGAL::Property_map_to_unary_function(m_item_map)), boost::make_transform_iterator (m_input.end(), CGAL::Property_map_to_unary_function(m_item_map))); boost::property_tree::ptree tree; boost::property_tree::read_xml(input, tree); double voxel_size = tree.get("classification.parameters.voxel_size"); m_scales.push_back (new Scale (m_input, m_item_map, m_bbox, voxel_size)); CGAL::Timer t; std::map att_map; BOOST_FOREACH(boost::property_tree::ptree::value_type &v, tree.get_child("classification.attributes")) { std::string full_id = v.second.get("id"); std::vector splitted_id; boost::split(splitted_id, full_id, boost::is_any_of("_")); std::string id = splitted_id[0]; for (std::size_t i = 1; i < splitted_id.size() - 1; ++ i) id = id + "_" + splitted_id[i]; std::size_t scale = std::atoi (splitted_id.back().c_str()); while (m_scales.size() <= scale) { voxel_size *= 2; m_scales.push_back (new Scale (m_input, m_item_map, m_bbox, voxel_size)); } double weight = v.second.get("weight"); // Generate the right attribute if possible if (id == "anisotropy") this->template add_attribute(*(m_scales[scale]->eigen)); else if (id == "distance_to_plane") this->template add_attribute(m_item_map, *(m_scales[scale]->eigen)); else if (id == "eigentropy") this->template add_attribute(*(m_scales[scale]->eigen)); else if (id == "elevation") { t.start(); this->template add_attribute(m_item_map, *(m_scales[scale]->grid), m_scales[scale]->grid_resolution(), m_scales[scale]->radius_dtm()); t.stop(); } else if (id == "linearity") this->template add_attribute(*(m_scales[scale]->eigen)); else if (id == "omnivariance") this->template add_attribute(*(m_scales[scale]->eigen)); else if (id == "planarity") this->template add_attribute(*(m_scales[scale]->eigen)); else if (id == "sphericity") this->template add_attribute(*(m_scales[scale]->eigen)); else if (id == "sum_eigen") this->template add_attribute(*(m_scales[scale]->eigen)); else if (id == "surface_variation") this->template add_attribute(*(m_scales[scale]->eigen)); else if (id == "vertical_dispersion") this->template add_attribute(m_item_map, *(m_scales[scale]->grid), m_scales[scale]->grid_resolution(), m_scales[scale]->radius_neighbors()); else if (id == "verticality") { if (boost::is_convertible >::value) this->template add_attribute(*(m_scales[scale]->eigen)); else this->template add_attribute(normal_map); } else if (id == "echo_scatter") { if (boost::is_convertible >::value) { std::cerr << "Warning: echo_scatter required but no echo map given." << std::endl; continue; } this->template add_attribute(echo_map, *(m_scales[scale]->grid), m_scales[scale]->grid_resolution(), m_scales[scale]->radius_neighbors()); } else if (boost::starts_with(id.c_str(), "hue") || boost::starts_with(id.c_str(), "saturation") || boost::starts_with(id.c_str(), "value")) { if (boost::is_convertible >::value) { std::cerr << "Warning: color attribute required but no color map given." << std::endl; continue; } if (boost::starts_with(id.c_str(), "hue")) { double value = boost::lexical_cast(id.c_str() + 4); this->template add_attribute(color_map, 0, value, 22.5); } else if (boost::starts_with(id.c_str(), "saturation")) { double value = boost::lexical_cast(id.c_str() + 11); this->template add_attribute(color_map, 1, value, 12.5); } else if (boost::starts_with(id.c_str(), "value")) { double value = boost::lexical_cast(id.c_str() + 6); this->template add_attribute(color_map, 2, value, 12.5); } } else { std::cerr << "Warning: unknown attribute \"" << id << "\"" << std::endl; continue; } Attribute_handle att = this->get_attribute (this->number_of_attributes() - 1); m_scales[scale]->attributes.push_back (att); att->set_weight(weight); att_map[full_id] = att; } std::cerr << "Elevation took " << t.time() << " second(s)" << std::endl; BOOST_FOREACH(boost::property_tree::ptree::value_type &v, tree.get_child("classification.types")) { std::string type_id = v.second.get("id"); Type_handle new_type = this->add_classification_type (type_id.c_str()); BOOST_FOREACH(boost::property_tree::ptree::value_type &v2, v.second) { if (v2.first == "id") continue; std::string att_id = v2.second.get("id"); std::map::iterator it = att_map.find(att_id); if (it == att_map.end()) continue; Attribute_handle att = it->second; std::string effect = v2.second.get("effect"); if (effect == "penalized") new_type->set_attribute_effect (att, Classification::Attribute::PENALIZING); else if (effect == "neutral") new_type->set_attribute_effect (att, Classification::Attribute::NEUTRAL); else new_type->set_attribute_effect (att, Classification::Attribute::FAVORING); } } return true; } /*! \brief Writes a classification in a colored and labeled PLY format. The input point set is written in a PLY format with the addition of several PLY properties: - a property `label` to indicate which classification type is assigned to the point. The types are indexed from 0 to N (the correspondancy is given as comments in the PLY header). - 3 properties `red`, `green` and `blue` to associate each label to a color (this is useful to visualize the classification in a viewer that supports PLY colors) \param stream The output stream where to write the content \param begin Iterator to the first input object \param end Past-the-end iterator \param point_map Property map to access the input points \param psc The classification object to write from \param colors A set of colors to be used to represent the different classification types. If none is given, random colors are picked. */ void write_classification_to_ply (std::ostream& stream) { stream << "ply" << std::endl << "format ascii 1.0" << std::endl << "comment Generated by the CGAL library www.cgal.org" << std::endl << "element vertex " << m_input.size() << std::endl << "property double x" << std::endl << "property double y" << std::endl << "property double z" << std::endl << "property uchar red" << std::endl << "property uchar green" << std::endl << "property uchar blue" << std::endl << "property int label" << std::endl; std::vector colors; std::map map_types; stream << "comment label -1 is (unclassified)" << std::endl; for (std::size_t i = 0; i < this->number_of_classification_types(); ++ i) { map_types.insert (std::make_pair (this->get_classification_type(i), i)); stream << "comment label " << i << " is " << this->get_classification_type(i)->name() << std::endl; RGB_Color c = {{ (unsigned char)(64 + rand() % 128), (unsigned char)(64 + rand() % 128), (unsigned char)(64 + rand() % 128) }}; colors.push_back (c); } map_types.insert (std::make_pair (Type_handle(), this->number_of_classification_types())); stream << "end_header" << std::endl; std::size_t i = 0; for (Iterator it = m_input.begin(); it != m_input.end(); ++ it) { Type_handle t = this->classification_type_of(i); std::size_t idx = map_types[t]; if (idx == this->number_of_classification_types()) stream << get(m_item_map, *it) << " 0 0 0 -1" << std::endl; else stream << get(m_item_map, *it) << " " << (int)(colors[idx][0]) << " " << (int)(colors[idx][1]) << " " << (int)(colors[idx][2]) << " " << idx << std::endl; ++ i; } } private: /// \cond SKIP_IN_MANUAL template void generate_multiscale_attribute_variant_0 () { for (std::size_t i = 0; i < m_scales.size(); ++ i) { this->template add_attribute(*(m_scales[i]->eigen)); m_scales[i]->attributes.push_back (this->get_attribute (this->number_of_attributes() - 1)); } } template void generate_multiscale_attribute_variant_1 () { for (std::size_t i = 0; i < m_scales.size(); ++ i) { this->template add_attribute(m_item_map, *(m_scales[i]->eigen)); m_scales[i]->attributes.push_back (this->get_attribute (this->number_of_attributes() - 1)); } } template void generate_multiscale_attribute_variant_2 () { for (std::size_t i = 0; i < m_scales.size(); ++ i) { this->template add_attribute(m_item_map, *(m_scales[i]->grid), m_scales[i]->grid_resolution(), m_scales[i]->radius_neighbors()); m_scales[i]->attributes.push_back (this->get_attribute (this->number_of_attributes() - 1)); } } template void generate_multiscale_attribute_variant_3 () { for (std::size_t i = 0; i < m_scales.size(); ++ i) { this->template add_attribute(m_item_map, *(m_scales[i]->grid), m_scales[i]->grid_resolution(), m_scales[i]->radius_dtm()); m_scales[i]->attributes.push_back (this->get_attribute (this->number_of_attributes() - 1)); } } /// \endcond }; } // namespace CGAL #endif // CGAL_POINT_SET_CLASSIFIER_H