diff --git a/Polyhedron/demo/Polyhedron/Plugins/Classification/Classification_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Classification/Classification_plugin.cpp index 92b45162a3d..9cb50727333 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Classification/Classification_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Classification/Classification_plugin.cpp @@ -210,6 +210,10 @@ public: connect(compute_features, SIGNAL(triggered()), this, SLOT(on_compute_features_button_clicked())); + action_statistics = ui_widget.menu->menu()->addAction ("Show feature statistics"); + connect(action_statistics, SIGNAL(triggered()), this, + SLOT(on_statistics_clicked())); + ui_widget.menu->menu()->addSection ("Training"); action_train = ui_widget.menu->menu()->addAction ("Train classifier"); @@ -264,6 +268,11 @@ public: connect(ui_widget.display, SIGNAL(currentIndexChanged(int)), this, SLOT(on_display_button_clicked(int))); + connect(ui_widget.minDisplay, SIGNAL(released()), this, + SLOT(on_min_display_button_clicked())); + connect(ui_widget.maxDisplay, SIGNAL(released()), this, + SLOT(on_max_display_button_clicked())); + connect(ui_widget_adv.selected_feature, SIGNAL(currentIndexChanged(int)), this, SLOT(on_selected_feature_changed(int))); connect(ui_widget_adv.feature_weight, SIGNAL(valueChanged(int)), this, @@ -278,6 +287,7 @@ public: connect(scene_obj, SIGNAL(itemIndexSelected(int)), this, SLOT(update_plugin(int))); } + } virtual void closure() { @@ -359,6 +369,7 @@ public Q_SLOTS: void enable_computation() { ui_widget.menu->setEnabled(true); + action_statistics->setEnabled(false); action_train->setEnabled(false); action_reset_local->setEnabled(false); action_reset->setEnabled(false); @@ -375,6 +386,7 @@ public Q_SLOTS: void enable_classif() { + action_statistics->setEnabled(true); action_train->setEnabled(true); action_reset_local->setEnabled(true); action_reset->setEnabled(true); @@ -428,9 +440,15 @@ public Q_SLOTS: ui_widget_adv.selected_feature->clear(); classif->fill_display_combo_box(ui_widget.display, ui_widget_adv.selected_feature); if (index >= ui_widget.display->count()) + { ui_widget.display->setCurrentIndex(1); + change_color (classif, 1); + } else + { ui_widget.display->setCurrentIndex(index); + change_color (classif, index); + } ui_widget_adv.selected_feature->setCurrentIndex(0); } } @@ -556,17 +574,26 @@ public Q_SLOTS: return; } - bool ok = false; - int nb_scales = QInputDialog::getInt((QWidget*)mw, - tr("Compute Features"), // dialog title - tr("Number of scales:"), // field label - 5, 1, 99, 1, &ok); - if (!ok) + QMultipleInputDialog dialog ("Compute Features", mw); + QSpinBox* scales = dialog.add ("Number of scales:"); + scales->setRange (1, 99); + scales->setValue (5); + + QDoubleSpinBox* voxel_size = dialog.add ("Voxel size (0 for automatic):"); + voxel_size->setRange (0.0, 10000.0); + voxel_size->setValue (0.0); + voxel_size->setSingleStep (0.01); + + if (dialog.exec() != QDialog::Accepted) return; QApplication::setOverrideCursor(Qt::WaitCursor); - classif->compute_features (std::size_t(nb_scales)); + float vsize = float(voxel_size->value()); + if (vsize == 0.f) + vsize = -1.f; // auto value + + classif->compute_features (std::size_t(scales->value()), vsize); update_plugin_from_item(classif); QApplication::restoreOverrideCursor(); @@ -683,7 +710,32 @@ public Q_SLOTS: item_changed(classif->item()); } + void change_color (Item_classification_base* classif, int index) + { + float vmin = std::numeric_limits::infinity(); + float vmax = std::numeric_limits::infinity(); + + classif->change_color (index, &vmin, &vmax); + if (vmin == std::numeric_limits::infinity() || vmax == std::numeric_limits::infinity()) + { + ui_widget.minDisplay->setEnabled(false); + ui_widget.minDisplay->setText("Min"); + ui_widget.maxDisplay->setEnabled(false); + ui_widget.maxDisplay->setText("Max"); + } + else + { + ui_widget.minDisplay->setEnabled(true); + ui_widget.minDisplay->setText(tr("Min (%1)").arg(vmin)); + ui_widget.maxDisplay->setEnabled(true); + ui_widget.maxDisplay->setText(tr("Max (%1)").arg(vmax)); + } + + item_changed(classif->item()); + } + + void on_display_button_clicked(int index) { Item_classification_base* classif @@ -691,7 +743,87 @@ public Q_SLOTS: if(!classif) return; - classif->change_color (index); + change_color (classif, index); + } + + float display_button_value (QPushButton* button) + { + std::string text = button->text().toStdString(); + + std::size_t pos1 = text.find('('); + if (pos1 == std::string::npos) + return std::numeric_limits::infinity(); + std::size_t pos2 = text.find(')'); + if (pos2 == std::string::npos) + return std::numeric_limits::infinity(); + + std::string fstring (text.begin() + pos1 + 1, + text.begin() + pos2); + + return float (std::atof(fstring.c_str())); + } + + void on_min_display_button_clicked() + { + Item_classification_base* classif + = get_classification(); + if(!classif) + return; + + float vmin = display_button_value (ui_widget.minDisplay); + float vmax = display_button_value (ui_widget.maxDisplay); + + if (vmin == std::numeric_limits::infinity() + || vmax == std::numeric_limits::infinity()) + return; + + bool ok = false; + vmin = float(QInputDialog::getDouble((QWidget*)mw, + tr("Set display ramp minimum value (saturate under):"), + tr("Minimum value (pale blue):"), + double(vmin), + -10000000.0, + double(vmax), 5, &ok)); + if (!ok) + return; + + int index = ui_widget.display->currentIndex(); + + classif->change_color (index, &vmin, &vmax); + ui_widget.minDisplay->setText(tr("Min* (%1)").arg(vmin)); + + item_changed(classif->item()); + } + + void on_max_display_button_clicked() + { + Item_classification_base* classif + = get_classification(); + if(!classif) + return; + + float vmin = display_button_value (ui_widget.minDisplay); + float vmax = display_button_value (ui_widget.maxDisplay); + + if (vmin == std::numeric_limits::infinity() + || vmax == std::numeric_limits::infinity()) + return; + + bool ok = false; + vmax = float(QInputDialog::getDouble((QWidget*)mw, + tr("Set display ramp maximum value (saturate over):"), + tr("Maximum value (dark red):"), + double(vmax), + double(vmin), + 10000000.0, 5, &ok)); + if (!ok) + return; + + int index = ui_widget.display->currentIndex(); + + classif->change_color (index, &vmin, &vmax); + ui_widget.maxDisplay->setText(tr("Max* (%1)").arg(vmax)); + item_changed(classif->item()); } @@ -1104,6 +1236,26 @@ public Q_SLOTS: (bbox.zmin() + bbox.zmax()) / 2.) + offset); } + void on_statistics_clicked() + { + Item_classification_base* classif + = get_classification(); + if(!classif) + { + print_message("Error: there is no point set classification item!"); + return; + } + + QApplication::setOverrideCursor(Qt::WaitCursor); + std::string str = classif->feature_statistics(); + QApplication::restoreOverrideCursor(); + + QMultipleInputDialog dialog ("Feature Statistics", mw); + QLabel* text = dialog.add (""); + text->setText(str.c_str()); + dialog.exec_no_cancel(); + } + void on_train_clicked() { Item_classification_base* classif @@ -1457,6 +1609,7 @@ private: QDockWidget* dock_widget; QDockWidget* dock_widget_adv; + QAction* action_statistics; QAction* action_train; QAction* action_reset_local; QAction* action_reset; diff --git a/Polyhedron/demo/Polyhedron/Plugins/Classification/Classification_widget.ui b/Polyhedron/demo/Polyhedron/Plugins/Classification/Classification_widget.ui index e0f452b086b..99a49be0487 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Classification/Classification_widget.ui +++ b/Polyhedron/demo/Polyhedron/Plugins/Classification/Classification_widget.ui @@ -6,8 +6,8 @@ 0 0 - 289 - 154 + 368 + 143 @@ -20,21 +20,49 @@ Classification - + - + - - - - 75 - true - - - - Classification - - + + + + + + 75 + true + + + + Classification + + + + + + + + + + 0 + 0 + + + + Classifier: + + + + + + + -1 + + + + + + @@ -44,63 +72,87 @@ - - - - 0 - 0 - - - - View: - - - - - - - 0 - + - - Real colors - + + + + + + 0 + 0 + + + + View: + + + + + + + 0 + + + + Real colors + + + + + Classification + + + + + Training sets + + + + + - - Classification - + + + + + false + + + Min + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Max + + + + - - - Training sets - - - - - - - - - - - - - 0 - 0 - - - - Classifier: - - - - - - - -1 - - + diff --git a/Polyhedron/demo/Polyhedron/Plugins/Classification/Cluster_classification.cpp b/Polyhedron/demo/Polyhedron/Plugins/Classification/Cluster_classification.cpp index bda87452066..94bbef928e2 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Classification/Cluster_classification.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Classification/Cluster_classification.cpp @@ -376,7 +376,7 @@ bool Cluster_classification::write_output(std::ostream& stream) } -void Cluster_classification::change_color (int index) +void Cluster_classification::change_color (int index, float* vmin, float* vmax) { m_index_color = index; @@ -591,13 +591,18 @@ void Cluster_classification::reset_indices () *(indices.begin() + i) = idx ++; } -void Cluster_classification::compute_features (std::size_t nb_scales) +void Cluster_classification::compute_features (std::size_t nb_scales, float voxel_size) { CGAL_assertion (!(m_points->point_set()->empty())); reset_indices(); - std::cerr << "Computing pointwise features with " << nb_scales << " scale(s)" << std::endl; + std::cerr << "Computing pointwise features with " << nb_scales << " scale(s) and "; + if (voxel_size == -1) + std::cerr << "automatic voxel size" << std::endl; + else + std::cerr << "voxel size = " << voxel_size << std::endl; + m_features.clear(); Point_set::Vector_map normal_map; @@ -615,7 +620,7 @@ void Cluster_classification::compute_features (std::size_t nb_scales) Feature_set pointwise_features; - Generator generator (*(m_points->point_set()), m_points->point_set()->point_map(), nb_scales); + Generator generator (*(m_points->point_set()), m_points->point_set()->point_map(), nb_scales, voxel_size); CGAL::Real_timer t; t.start(); diff --git a/Polyhedron/demo/Polyhedron/Plugins/Classification/Cluster_classification.h b/Polyhedron/demo/Polyhedron/Plugins/Classification/Cluster_classification.h index cfea6dbf015..5c71e2c05ec 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Classification/Cluster_classification.h +++ b/Polyhedron/demo/Polyhedron/Plugins/Classification/Cluster_classification.h @@ -97,7 +97,7 @@ class Cluster_classification : public Item_classification_base xcenter + dx, ycenter + dy, zcenter + dz); } - void compute_features (std::size_t nb_scales); + void compute_features (std::size_t nb_scales, float voxel_size); void add_remaining_point_set_properties_as_features(Feature_set& feature_set); void select_random_region(); @@ -206,7 +206,7 @@ class Cluster_classification : public Item_classification_base bool run (int method, int classifier, std::size_t subdivisions, double smoothing); void update_color () { change_color (m_index_color); } - void change_color (int index); + void change_color (int index, float* vmin = NULL, float* vmax = NULL); CGAL::Three::Scene_item* generate_one_item (const char* name, int label) const { diff --git a/Polyhedron/demo/Polyhedron/Plugins/Classification/Item_classification_base.h b/Polyhedron/demo/Polyhedron/Plugins/Classification/Item_classification_base.h index 1c9226b263a..3142c32695a 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Classification/Item_classification_base.h +++ b/Polyhedron/demo/Polyhedron/Plugins/Classification/Item_classification_base.h @@ -45,8 +45,10 @@ public: virtual CGAL::Bbox_3 bbox() { return item()->bbox(); } - virtual void compute_features (std::size_t nb_scales) = 0; + virtual void compute_features (std::size_t nb_scales, float voxel_size) = 0; + virtual std::string feature_statistics () const { return std::string(); } + virtual void add_selection_to_training_set (std::size_t label) = 0; virtual void reset_training_set (std::size_t label) = 0; virtual void reset_training_set_of_selection() = 0; @@ -58,7 +60,7 @@ public: virtual bool run (int method, int classifier, std::size_t subdivisions, double smoothing) = 0; virtual void update_color () = 0; - virtual void change_color (int index) = 0; + virtual void change_color (int index, float* vmin = NULL, float* vmax = NULL) = 0; virtual CGAL::Three::Scene_item* generate_one_item (const char* name, int label) const = 0; virtual void generate_one_item_per_label(std::vector& items, diff --git a/Polyhedron/demo/Polyhedron/Plugins/Classification/Point_set_item_classification.cpp b/Polyhedron/demo/Polyhedron/Plugins/Classification/Point_set_item_classification.cpp index b981ea06287..44a58ef4236 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Classification/Point_set_item_classification.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Classification/Point_set_item_classification.cpp @@ -320,7 +320,7 @@ bool Point_set_item_classification::write_output(std::ostream& stream) } -void Point_set_item_classification::change_color (int index) +void Point_set_item_classification::change_color (int index, float* vmin, float* vmax) { m_index_color = index; @@ -425,28 +425,44 @@ void Point_set_item_classification::change_color (int index) return; } Feature_handle feature = m_features[corrected_index]; - + float min = std::numeric_limits::max(); float max = -std::numeric_limits::max(); - - for (Point_set::const_iterator it = m_points->point_set()->begin(); - it != m_points->point_set()->first_selected(); ++ it) + + if (vmin != NULL && vmax != NULL + && *vmin != std::numeric_limits::infinity() + && *vmax != std::numeric_limits::infinity()) { - if (feature->value(*it) > max) - max = feature->value(*it); - if (feature->value(*it) < min) - min = feature->value(*it); + min = *vmin; + max = *vmax; + } + else + { + for (Point_set::const_iterator it = m_points->point_set()->begin(); + it != m_points->point_set()->end(); ++ it) + { + float v = feature->value(*it); + min = (std::min) (min, v); + max = (std::max) (max, v); + } } - std::cerr << "[Feature " << feature->name() << "] between " << min << " and " << max << std::endl; for (Point_set::const_iterator it = m_points->point_set()->begin(); it != m_points->point_set()->first_selected(); ++ it) { float v = (feature->value(*it) - min) / (max - min); + if (v < 0.f) v = 0.f; + if (v > 1.f) v = 1.f; m_red[*it] = (unsigned char)(ramp.r(v) * 255); m_green[*it] = (unsigned char)(ramp.g(v) * 255); m_blue[*it] = (unsigned char)(ramp.b(v) * 255); } + + if (vmin != NULL && vmax != NULL) + { + *vmin = min; + *vmax = max; + } } } @@ -481,7 +497,7 @@ void Point_set_item_classification::reset_indices () *(indices.begin() + i) = idx ++; } -void Point_set_item_classification::compute_features (std::size_t nb_scales) +void Point_set_item_classification::compute_features (std::size_t nb_scales, float voxel_size) { CGAL_assertion (!(m_points->point_set()->empty())); @@ -490,7 +506,12 @@ void Point_set_item_classification::compute_features (std::size_t nb_scales) reset_indices(); - std::cerr << "Computing features with " << nb_scales << " scale(s)" << std::endl; + std::cerr << "Computing features with " << nb_scales << " scale(s) and "; + if (voxel_size == -1) + std::cerr << "automatic voxel size" << std::endl; + else + std::cerr << "voxel size = " << voxel_size << std::endl; + m_features.clear(); Point_set::Vector_map normal_map; @@ -506,7 +527,7 @@ void Point_set_item_classification::compute_features (std::size_t nb_scales) if (!echo) boost::tie (echo_map, echo) = m_points->point_set()->template property_map("number_of_returns"); - m_generator = new Generator (*(m_points->point_set()), m_points->point_set()->point_map(), nb_scales); + m_generator = new Generator (*(m_points->point_set()), m_points->point_set()->point_map(), nb_scales, voxel_size); CGAL::Real_timer t; t.start(); diff --git a/Polyhedron/demo/Polyhedron/Plugins/Classification/Point_set_item_classification.h b/Polyhedron/demo/Polyhedron/Plugins/Classification/Point_set_item_classification.h index a5d2b6636ac..3a428b523ef 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Classification/Point_set_item_classification.h +++ b/Polyhedron/demo/Polyhedron/Plugins/Classification/Point_set_item_classification.h @@ -117,7 +117,35 @@ class Point_set_item_classification : public Item_classification_base xcenter + dx, ycenter + dy, zcenter + dz); } - void compute_features (std::size_t nb_scales); + void compute_features (std::size_t nb_scales, float voxel_size); + + std::string feature_statistics() const + { + std::ostringstream oss; + + for (std::size_t i = 0; i < m_features.size(); ++ i) + { + float vmin = std::numeric_limits::max(); + float vmax = -std::numeric_limits::max(); + float vmean = 0.f; + std::size_t nb = 0; + + for (Point_set::const_iterator it = m_points->point_set()->begin_or_selection_begin(); + it != m_points->point_set()->end(); ++ it) + { + float v = m_features[i]->value(std::size_t(it - m_points->point_set()->begin())); + vmin = (std::min) (vmin, v); + vmax = (std::max) (vmax, v); + vmean += v; + ++ nb; + } + + oss << m_features[i]->name() << " in [ " << vmin << " ; " << vmax << " ], mean = " << vmean / nb << std::endl; + } + + return oss.str(); + } + void add_remaining_point_set_properties_as_features(); void select_random_region(); @@ -195,7 +223,7 @@ class Point_set_item_classification : public Item_classification_base bool run (int method, int classifier, std::size_t subdivisions, double smoothing); void update_color () { change_color (m_index_color); } - void change_color (int index); + void change_color (int index, float* vmin = NULL, float* vmax = NULL); CGAL::Three::Scene_item* generate_one_item (const char* name, int label) const { diff --git a/Polyhedron/demo/Polyhedron/Plugins/Classification/Surface_mesh_item_classification.cpp b/Polyhedron/demo/Polyhedron/Plugins/Classification/Surface_mesh_item_classification.cpp index 003c132e916..39e9d5cbb68 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Classification/Surface_mesh_item_classification.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Classification/Surface_mesh_item_classification.cpp @@ -87,7 +87,7 @@ bool Surface_mesh_item_classification::write_output(std::ostream& ) } -void Surface_mesh_item_classification::change_color (int index) +void Surface_mesh_item_classification::change_color (int index, float* vmin, float* vmax) { m_index_color = index; int index_color = index; @@ -198,9 +198,14 @@ void Surface_mesh_item_classification::change_color (int index) } } -void Surface_mesh_item_classification::compute_features (std::size_t nb_scales) +void Surface_mesh_item_classification::compute_features (std::size_t nb_scales, float voxel_size) { - std::cerr << "Computing features with " << nb_scales << " scale(s)" << std::endl; + std::cerr << "Computing features with " << nb_scales << " scale(s) and "; + if (voxel_size == -1) + std::cerr << "automatic voxel size" << std::endl; + else + std::cerr << "voxel size = " << voxel_size << std::endl; + m_features.clear(); if (m_generator != NULL) @@ -208,7 +213,7 @@ void Surface_mesh_item_classification::compute_features (std::size_t nb_scales) Face_center_map fc_map (m_mesh->polyhedron()); - m_generator = new Generator (*(m_mesh->polyhedron()), fc_map, nb_scales); + m_generator = new Generator (*(m_mesh->polyhedron()), fc_map, nb_scales, voxel_size); #ifdef CGAL_LINKED_WITH_TBB m_features.begin_parallel_additions(); diff --git a/Polyhedron/demo/Polyhedron/Plugins/Classification/Surface_mesh_item_classification.h b/Polyhedron/demo/Polyhedron/Plugins/Classification/Surface_mesh_item_classification.h index b3a40b78fe5..a9983109bc1 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Classification/Surface_mesh_item_classification.h +++ b/Polyhedron/demo/Polyhedron/Plugins/Classification/Surface_mesh_item_classification.h @@ -50,7 +50,7 @@ public: CGAL::Three::Scene_item* item() { return m_mesh; } void erase_item() { m_mesh = NULL; } - void compute_features (std::size_t nb_scales); + void compute_features (std::size_t nb_scales, float voxel_size); void add_selection_to_training_set (std::size_t label) { @@ -120,7 +120,7 @@ public: bool run (int method, int classifier, std::size_t subdivisions, double smoothing); void update_color() { change_color (m_index_color); } - void change_color (int index); + void change_color (int index, float* vmin = NULL, float* vmax = NULL); CGAL::Three::Scene_item* generate_one_item (const char* /* name */, int /* label */) const { diff --git a/Polyhedron/demo/Polyhedron/include/QMultipleInputDialog.h b/Polyhedron/demo/Polyhedron/include/QMultipleInputDialog.h index cf6ae9db3a0..d36717bfb0a 100644 --- a/Polyhedron/demo/Polyhedron/include/QMultipleInputDialog.h +++ b/Polyhedron/demo/Polyhedron/include/QMultipleInputDialog.h @@ -63,6 +63,16 @@ public: return dialog->exec(); } + + void exec_no_cancel() + { + QDialogButtonBox* ok = new QDialogButtonBox + (QDialogButtonBox::Ok, Qt::Horizontal, dialog); + + form->addRow (ok); + QObject::connect (ok, SIGNAL(accepted()), dialog, SLOT(accept())); + dialog->exec(); + } };