Many improvements for plugin, statistics on features, etc.

This commit is contained in:
Simon Giraudot 2018-08-09 10:11:05 +02:00
parent 335375dbba
commit 4bebcae4f2
10 changed files with 381 additions and 105 deletions

View File

@ -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<QSpinBox> ("Number of scales:");
scales->setRange (1, 99);
scales->setValue (5);
QDoubleSpinBox* voxel_size = dialog.add<QDoubleSpinBox> ("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<float>::infinity();
float vmax = std::numeric_limits<float>::infinity();
classif->change_color (index, &vmin, &vmax);
if (vmin == std::numeric_limits<float>::infinity() || vmax == std::numeric_limits<float>::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<float>::infinity();
std::size_t pos2 = text.find(')');
if (pos2 == std::string::npos)
return std::numeric_limits<float>::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<float>::infinity()
|| vmax == std::numeric_limits<float>::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<float>::infinity()
|| vmax == std::numeric_limits<float>::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<QLabel> ("");
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;

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>289</width>
<height>154</height>
<width>368</width>
<height>143</height>
</rect>
</property>
<property name="sizePolicy">
@ -20,21 +20,49 @@
<string>Classification</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout_2">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="menu">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Classification</string>
</property>
</widget>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QPushButton" name="menu">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Classification</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_7">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Classifier:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="classifier">
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
@ -44,63 +72,87 @@
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>View:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="display">
<property name="currentIndex">
<number>0</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<property name="text">
<string>Real colors</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>View:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="display">
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>Real colors</string>
</property>
</item>
<item>
<property name="text">
<string>Classification</string>
</property>
</item>
<item>
<property name="text">
<string>Training sets</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<property name="text">
<string>Classification</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="minDisplay">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Min</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="maxDisplay">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Max</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<property name="text">
<string>Training sets</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_7">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Classifier:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="classifier">
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</layout>
</item>
</layout>
</item>

View File

@ -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();

View File

@ -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
{

View File

@ -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<CGAL::Three::Scene_item*>& items,

View File

@ -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<float>::max();
float max = -std::numeric_limits<float>::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<float>::infinity()
&& *vmax != std::numeric_limits<float>::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<boost::uint8_t>("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();

View File

@ -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<float>::max();
float vmax = -std::numeric_limits<float>::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
{

View File

@ -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();

View File

@ -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
{

View File

@ -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();
}
};