diff --git a/CMakeLists.txt b/CMakeLists.txt index 955bdbf..114e1a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,11 +6,11 @@ cmake_minimum_required( VERSION 3.0 ) # AND ADAPT THE CODE BELOW TO YOUR OWN NEEDS! # Add an option to CMake to control whether we build this plugin or not -option( PLUGIN_SEGMENTER "Check to install segmenter plugin" OFF ) +option( PLUGIN__COLORIMETRIC_SEGMENTER "Check to install segmenter plugin" OFF ) -if ( PLUGIN_SEGMENTER ) +if ( PLUGIN__COLORIMETRIC_SEGMENTER ) # Name the plugin - project( SegmenterPlugin ) + project( ColorimetricSegmenter ) # load any subdirectories (see qAdditionalIO for an example) # add_subdirectory( LIB1 ) diff --git a/ColorimetricSegmenter.cpp b/ColorimetricSegmenter.cpp new file mode 100644 index 0000000..302208f --- /dev/null +++ b/ColorimetricSegmenter.cpp @@ -0,0 +1,250 @@ +//########################################################################## +//# # +//# CLOUDCOMPARE PLUGIN: ColorimetricSegmenter # +//# # +//# This program is free software; you can redistribute it and/or modify # +//# it under the terms of the GNU General Public License as published by # +//# the Free Software Foundation; version 2 of the License. # +//# # +//# This program is distributed in the hope that it will be useful, # +//# but WITHOUT ANY WARRANTY; without even the implied warranty of # +//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +//# GNU General Public License for more details. # +//# # +//# COPYRIGHT: Tri-Thien TRUONG, Ronan COLLIER, Mathieu LETRONE # +//# # +//########################################################################## + +// First: +// Replace all occurrences of 'ExamplePlugin' by your own plugin class name in this file. +// This includes the resource path to info.json in the constructor. + +// Second: +// Open ExamplePlugin.qrc, change the "prefix" and the icon filename for your plugin. +// Change the name of the file to .qrc + +// Third: +// Open the info.json file and fill in the information about the plugin. +// "type" should be one of: "Standard", "GL", or "I/O" (required) +// "name" is the name of the plugin (required) +// "icon" is the Qt resource path to the plugin's icon (from the .qrc file) +// "description" is used as a tootip if the plugin has actions and is displayed in the plugin dialog +// "authors", "maintainers", and "references" show up in the plugin dialog as well + +#include + +#include "ColorimetricSegmenter.h" +#include "RgbDialog.h" + +#include "ccPointCloud.h" +#include "ccScalarField.h" + + +// Default constructor: +// - pass the Qt resource path to the info.json file (from .qrc file) +// - constructor should mainly be used to initialize actions and other members +ColorimetricSegmenter::ColorimetricSegmenter( QObject *parent ) + : QObject( parent ) + , ccStdPluginInterface( ":/CC/plugin/ColorimetricSegmenter/info.json" ) + , m_action_filterScalar(nullptr ) + , m_action_filterRgb( nullptr ) +{ +} + + +void ColorimetricSegmenter::handleNewEntity(ccHObject* entity) +{ + assert(entity && m_app); + m_app->addToDB(entity); +} + +void ColorimetricSegmenter::handleEntityChange(ccHObject* entity) +{ + assert(entity && m_app); + entity->prepareDisplayForRefresh_recursive(); + m_app->refreshAll(); + m_app->updateUI(); +} + +void ColorimetricSegmenter::handleErrorMessage(QString message) +{ + if (m_app) + m_app->dispToConsole(message, ccMainAppInterface::ERR_CONSOLE_MESSAGE); +} + + +// This method should enable or disable your plugin actions +// depending on the currently selected entities ('selectedEntities'). +void ColorimetricSegmenter::onNewSelection( const ccHObject::Container &selectedEntities ) +{ + if (m_action_filterScalar == nullptr ) + { + return; + } + if (m_action_filterRgb == nullptr) + { + return; + } + + // If you need to check for a specific type of object, you can use the methods + // in ccHObjectCaster.h or loop and check the objects' classIDs like this: + // + // for ( ccHObject *object : selectedEntities ) + // { + // if ( object->getClassID() == CC_TYPES::VIEWPORT_2D_OBJECT ) + // { + // // ... do something with the viewports + // } + // } + + // For example - only enable our action if something is selected. + m_action_filterScalar->setEnabled( !selectedEntities.empty() ); + m_action_filterRgb->setEnabled(!selectedEntities.empty()); +} + +// This method returns all the 'actions' your plugin can perform. +// getActions() will be called only once, when plugin is loaded. +QList ColorimetricSegmenter::getActions() +{ + // default action (if it has not been already created, this is the moment to do it) + if ( !m_action_filterScalar ) + { + // Here we use the default plugin name, description, and icon, + // but each action should have its own. + m_action_filterScalar = new QAction( "Filter with scalar field", this ); + m_action_filterScalar->setToolTip( "Filter the points on the selected cloud by scalar value" ); + m_action_filterScalar->setIcon( getIcon() ); + + // Connect appropriate signal + connect(m_action_filterScalar, &QAction::triggered, this, &ColorimetricSegmenter::filterScalarField ); + + connect(m_action_filterScalar, SIGNAL(newEntity(ccHObject*)), this, SLOT(handleNewEntity(ccHObject*))); + connect(m_action_filterScalar, SIGNAL(entityHasChanged(ccHObject*)), this, SLOT(handleEntityChange(ccHObject*))); + connect(m_action_filterScalar, SIGNAL(newErrorMessage(QString)), this, SLOT(handleErrorMessage(QString))); + } + + if (!m_action_filterRgb) + { + // Here we use the default plugin name, description, and icon, + // but each action should have its own. + m_action_filterRgb = new QAction( "Filter RGB", this); + m_action_filterRgb->setToolTip( "Filter the points on the selected cloud by RGB color" ); + m_action_filterRgb->setIcon(getIcon()); + + // Connect appropriate signal + connect(m_action_filterRgb, &QAction::triggered, this, &ColorimetricSegmenter::filterRgb); + + connect(m_action_filterRgb, SIGNAL(newEntity(ccHObject*)), this, SLOT(handleNewEntity(ccHObject*))); + connect(m_action_filterRgb, SIGNAL(entityHasChanged(ccHObject*)), this, SLOT(handleEntityChange(ccHObject*))); + connect(m_action_filterRgb, SIGNAL(newErrorMessage(QString)), this, SLOT(handleErrorMessage(QString))); + } + + return { m_action_filterScalar, m_action_filterRgb }; +} + + +std::vector ColorimetricSegmenter::getSelectedPointClouds() +{ + if (m_app == nullptr) + { + // m_app should have already been initialized by CC when plugin is loaded + Q_ASSERT(false); + return std::vector{}; + } + + ccHObject::Container selectedEntities = m_app->getSelectedEntities(); + std::vector clouds; + for (size_t i = 0; i < selectedEntities.size(); ++i) + { + if (selectedEntities[i]->isKindOf(CC_TYPES::POINT_CLOUD)) { + clouds.push_back(static_cast (selectedEntities[i])); + } + } + + return clouds; +} + +void ColorimetricSegmenter::filterScalarField() +{ + if (m_app == nullptr) + { + // m_app should have already been initialized by CC when plugin is loaded + Q_ASSERT(false); + return; + } + int minVal; + int maxVal; + + std::vector clouds = getSelectedPointClouds(); + for (ccPointCloud* cloud : clouds) { + ccPointCloud* filteredCloud = cloud->filterPointsByScalarValue(minVal, maxVal, false); + cloud->getParent()->addChild(filteredCloud); + cloud->setRGBColorWithCurrentScalarField(); + m_app->dispToConsole("[ColorimetricSegmenter] Cloud successfully filtered ! ", ccMainAppInterface::STD_CONSOLE_MESSAGE); + + emit newEntity(filteredCloud); + emit entityHasChanged(cloud); + } +} + + +void ColorimetricSegmenter::filterRgb() +{ + if ( m_app == nullptr ) + { + // m_app should have already been initialized by CC when plugin is loaded + Q_ASSERT( false ); + + return; + } + + // Retrieve parameters from dialog + RgbDialog rgbDlg((QWidget*)m_app->getMainWindow()); + + if (!rgbDlg.exec()) + return; + + double marginError = static_cast(rgbDlg.spinBox_6->value()) / 100.0; + + int redInf = rgbDlg.spinBox_3->value() - (marginError * rgbDlg.spinBox_3->value()); + int redSup = rgbDlg.spinBox_2->value() + marginError * rgbDlg.spinBox_2->value(); + int greenInf = rgbDlg.spinBox->value() - marginError * rgbDlg.spinBox->value(); + int greenSup = rgbDlg.knnSpinBox->value() + marginError * rgbDlg.knnSpinBox->value(); + int blueInf = rgbDlg.spinBox_5->value() - marginError * rgbDlg.spinBox_5->value(); + int blueSup = rgbDlg.spinBox_4->value() + marginError * rgbDlg.spinBox_4->value(); + + std::vector clouds = getSelectedPointClouds(); + + for (ccPointCloud* cloud : clouds) { + if (cloud->hasColors()) + { + // Use only references for speed reasons + CCLib::ReferenceCloud* filteredCloud = new CCLib::ReferenceCloud(cloud); + + for (unsigned j = 0; j < cloud->size(); ++j) + { + const ccColor::Rgb& rgb = cloud->getPointColor(j); + + if (rgb.r > redInf && rgb.r < redSup && + rgb.g > greenInf && rgb.g < greenSup && + rgb.b > blueInf && rgb.b < blueSup) { // Points to select + if (!filteredCloud->addPointIndex(j)) + { + //not enough memory + delete filteredCloud; + filteredCloud = nullptr; + break; + } + } + + } + ccPointCloud* newCloud = cloud->partialClone(filteredCloud); + cloud->getParent()->addChild(newCloud); + + m_app->dispToConsole("[ColorimetricSegmenter] Cloud successfully filtered ! ", ccMainAppInterface::STD_CONSOLE_MESSAGE); + emit newEntity(newCloud); + emit entityHasChanged(cloud); + + } + } +} diff --git a/SegmenterPlugin.h b/ColorimetricSegmenter.h similarity index 71% rename from SegmenterPlugin.h rename to ColorimetricSegmenter.h index 5f6d187..911f82d 100644 --- a/SegmenterPlugin.h +++ b/ColorimetricSegmenter.h @@ -3,7 +3,7 @@ //########################################################################## //# # -//# CLOUDCOMPARE PLUGIN: ExamplePlugin # +//# CLOUDCOMPARE PLUGIN: ColorimetricSegmenter # //# # //# This program is free software; you can redistribute it and/or modify # //# it under the terms of the GNU General Public License as published by # @@ -14,11 +14,14 @@ //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # //# GNU General Public License for more details. # //# # -//# COPYRIGHT: XXX # +//# COPYRIGHT: Tri-Thien TRUONG, Ronan COLLIER, Mathieu LETRONE # //# # //########################################################################## #include "ccStdPluginInterface.h" +#include +#include + //! Example qCC plugin /** Replace 'ExamplePlugin' by your own plugin class name throughout and then @@ -38,7 +41,7 @@ components (database, 3D views, console, etc.) - see the ccMainAppInterface class in ccMainAppInterface.h. **/ -class SegmenterPlugin : public QObject, public ccStdPluginInterface +class ColorimetricSegmenter : public QObject, public ccStdPluginInterface { Q_OBJECT Q_INTERFACES(ccStdPluginInterface) @@ -49,23 +52,51 @@ class SegmenterPlugin : public QObject, public ccStdPluginInterface Q_PLUGIN_METADATA(IID "cccorp.cloudcompare.plugin.ColorimetricSegmenter" FILE "info.json") public: - explicit SegmenterPlugin( QObject *parent = nullptr ); - ~SegmenterPlugin() override = default; + explicit ColorimetricSegmenter( QObject *parent = nullptr ); + ~ColorimetricSegmenter() override = default; // inherited from ccStdPluginInterface void onNewSelection( const ccHObject::Container &selectedEntities ) override; QList getActions() override; - + +public slots: + //! Handles new entity + void handleNewEntity(ccHObject*); + + //! Handles entity (visual) modification + void handleEntityChange(ccHObject*); + + //! Handles new error message + void handleErrorMessage(QString); + +signals: + + //! Signal emitted when an entity is (visually) modified + void entityHasChanged(ccHObject*); + + //! Signal emitted when a new entity is created by the filter + void newEntity(ccHObject*); + + //! Signal emitted when a new error message is produced + void newErrorMessage(QString); + private: - /*** ADD YOUR CUSTOM ACTIONS HERE ***/ - void doAction(); + std::vector getSelectedPointClouds(); + + //! Filter a cloud with a scalar field (add one if there is none) + void filterScalarField(); + + //! Filter a cloud with RGB color + void filterRgb(); //! Default action /** You can add as many actions as you want in a plugin. Each action will correspond to an icon in the dedicated toolbar and an entry in the plugin menu. **/ - QAction* m_action; + QAction* m_action_filterScalar; + QAction* m_action_filterRgb; + }; #endif diff --git a/SegmenterPlugin.qrc b/ColorimetricSegmenter.qrc similarity index 60% rename from SegmenterPlugin.qrc rename to ColorimetricSegmenter.qrc index ca21554..c28aef0 100644 --- a/SegmenterPlugin.qrc +++ b/ColorimetricSegmenter.qrc @@ -1,5 +1,5 @@ - + images/icon.png info.json diff --git a/RgbDialog.cpp b/RgbDialog.cpp new file mode 100644 index 0000000..8e00401 --- /dev/null +++ b/RgbDialog.cpp @@ -0,0 +1,25 @@ +//########################################################################## +//# # +//# CLOUDCOMPARE PLUGIN: ColorimetricSegmenter # +//# # +//# This program is free software; you can redistribute it and/or modify # +//# it under the terms of the GNU General Public License as published by # +//# the Free Software Foundation; version 2 of the License. # +//# # +//# This program is distributed in the hope that it will be useful, # +//# but WITHOUT ANY WARRANTY; without even the implied warranty of # +//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +//# GNU General Public License for more details. # +//# # +//# COPYRIGHT: Tri-Thien TRUONG, Ronan COLLIER, Mathieu LETRONE # +//# # +//########################################################################## +#include "RgbDialog.h" +//Qt +#include +RgbDialog::RgbDialog(QWidget* parent) + : QDialog(parent) + , Ui::RgbDialog() +{ + setupUi(this); +} \ No newline at end of file diff --git a/RgbDialog.h b/RgbDialog.h new file mode 100644 index 0000000..837673a --- /dev/null +++ b/RgbDialog.h @@ -0,0 +1,33 @@ +//########################################################################## +//# # +//# CLOUDCOMPARE PLUGIN: ColorimetricSegmenter # +//# # +//# This program is free software; you can redistribute it and/or modify # +//# it under the terms of the GNU General Public License as published by # +//# the Free Software Foundation; version 2 of the License. # +//# # +//# This program is distributed in the hope that it will be useful, # +//# but WITHOUT ANY WARRANTY; without even the implied warranty of # +//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +//# GNU General Public License for more details. # +//# # +//# COPYRIGHT: Tri-Thien TRUONG, Ronan COLLIER, Mathieu LETRONE # +//# # +//########################################################################## + +#ifndef RgbDialog_H +#define RgbDialog_H + +#include + +//Qt +#include + +class RgbDialog : public QDialog, public Ui::RgbDialog +{ +public: + explicit RgbDialog(QWidget* parent = 0); + +}; + +#endif // RgbDialog_H diff --git a/RgbDialog.ui b/RgbDialog.ui new file mode 100644 index 0000000..cff3ffd --- /dev/null +++ b/RgbDialog.ui @@ -0,0 +1,181 @@ + + + RgbDialog + + + + 0 + 0 + 350 + 173 + + + + + 0 + 0 + + + + RGB color setting + + + + + + + + Red + + + Qt::AlignCenter + + + + + + + 255 + + + + + + + 255 + + + + + + + + + + + Green + + + Qt::AlignCenter + + + + + + + 255 + + + + + + + true + + + 255 + + + 0 + + + + + + + + + + + Blue + + + Qt::AlignCenter + + + + + + + 255 + + + + + + + 255 + + + + + + + + + + + Margin of erreur (%) : + + + + + + + 100 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + rejected() + RgbDialog + reject() + + + 260 + 120 + + + 280 + 118 + + + + + buttonBox + accepted() + RgbDialog + accept() + + + 218 + 114 + + + 157 + 118 + + + + + diff --git a/SegmenterPlugin.cpp b/SegmenterPlugin.cpp deleted file mode 100644 index 30896ab..0000000 --- a/SegmenterPlugin.cpp +++ /dev/null @@ -1,123 +0,0 @@ -//########################################################################## -//# # -//# CLOUDCOMPARE PLUGIN: ExamplePlugin # -//# # -//# This program is free software; you can redistribute it and/or modify # -//# it under the terms of the GNU General Public License as published by # -//# the Free Software Foundation; version 2 of the License. # -//# # -//# This program is distributed in the hope that it will be useful, # -//# but WITHOUT ANY WARRANTY; without even the implied warranty of # -//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -//# GNU General Public License for more details. # -//# # -//# COPYRIGHT: XXX # -//# # -//########################################################################## - -// First: -// Replace all occurrences of 'ExamplePlugin' by your own plugin class name in this file. -// This includes the resource path to info.json in the constructor. - -// Second: -// Open ExamplePlugin.qrc, change the "prefix" and the icon filename for your plugin. -// Change the name of the file to .qrc - -// Third: -// Open the info.json file and fill in the information about the plugin. -// "type" should be one of: "Standard", "GL", or "I/O" (required) -// "name" is the name of the plugin (required) -// "icon" is the Qt resource path to the plugin's icon (from the .qrc file) -// "description" is used as a tootip if the plugin has actions and is displayed in the plugin dialog -// "authors", "maintainers", and "references" show up in the plugin dialog as well - -#include - -#include "SegmenterPlugin.h" - -// Default constructor: -// - pass the Qt resource path to the info.json file (from .qrc file) -// - constructor should mainly be used to initialize actions and other members -SegmenterPlugin::SegmenterPlugin( QObject *parent ) - : QObject( parent ) - , ccStdPluginInterface( ":/CC/plugin/SegmenterPlugin/info.json" ) - , m_action( nullptr ) -{ -} - -// This method should enable or disable your plugin actions -// depending on the currently selected entities ('selectedEntities'). -void SegmenterPlugin::onNewSelection( const ccHObject::Container &selectedEntities ) -{ - if ( m_action == nullptr ) - { - return; - } - - // If you need to check for a specific type of object, you can use the methods - // in ccHObjectCaster.h or loop and check the objects' classIDs like this: - // - // for ( ccHObject *object : selectedEntities ) - // { - // if ( object->getClassID() == CC_TYPES::VIEWPORT_2D_OBJECT ) - // { - // // ... do something with the viewports - // } - // } - - // For example - only enable our action if something is selected. - m_action->setEnabled( !selectedEntities.empty() ); -} - -// This method returns all the 'actions' your plugin can perform. -// getActions() will be called only once, when plugin is loaded. -QList SegmenterPlugin::getActions() -{ - // default action (if it has not been already created, this is the moment to do it) - if ( !m_action ) - { - // Here we use the default plugin name, description, and icon, - // but each action should have its own. - m_action = new QAction( getName(), this ); - m_action->setToolTip( getDescription() ); - m_action->setIcon( getIcon() ); - - // Connect appropriate signal - connect( m_action, &QAction::triggered, this, &SegmenterPlugin::doAction ); - } - - return { m_action }; -} - -// This is an example of an action's method called when the corresponding action -// is triggered (i.e. the corresponding icon or menu entry is clicked in CC's -// main interface). You can access most of CC's components (database, -// 3D views, console, etc.) via the 'm_app' variable (see the ccMainAppInterface -// class in ccMainAppInterface.h). -void SegmenterPlugin::doAction() -{ - if ( m_app == nullptr ) - { - // m_app should have already been initialized by CC when plugin is loaded - Q_ASSERT( false ); - - return; - } - - /*** HERE STARTS THE ACTION ***/ - - // Put your code here - // --> you may want to start by asking for parameters (with a custom dialog, etc.) - - // This is how you can output messages - // Display a standard message in the console - m_app->dispToConsole( "[ExamplePlugin] Hello world!", ccMainAppInterface::STD_CONSOLE_MESSAGE ); - - // Display a warning message in the console - m_app->dispToConsole( "[ExamplePlugin] Warning: example plugin shouldn't be used as is", ccMainAppInterface::WRN_CONSOLE_MESSAGE ); - - // Display an error message in the console AND pop-up an error box - m_app->dispToConsole( "Example plugin shouldn't be used - it doesn't do anything!", ccMainAppInterface::ERR_CONSOLE_MESSAGE ); - - /*** HERE ENDS THE ACTION ***/ -} diff --git a/info.json b/info.json index c53558f..37a90de 100644 --- a/info.json +++ b/info.json @@ -1,34 +1,44 @@ { - "type" : "Standard", - "name" : "Colorimetric Segmenter", - "icon" : ":/CC/plugin/ExamplePlugin/images/icon.png", + "type": "Standard", + "name": "Colorimetric Segmenter", + "icon": ":/CC/plugin/ColorimetricSegmenter/images/icon.png", "description": "This is a description of the marvelous Example plugin. It does nothing.", - "authors" : [ + "authors": [ { - "name" : "Daniel Girardeau-Montaut", - "email" : "daniel.girardeau@gmail.com" + "name": "Ronan COLLIER", + "email": "ronan.collier@etu.univ-nantes.fr" + }, + { + "name": "Tri-thien TRUONG", + "email": "tri-thien.truong@etu.univ-nantes.fr" + }, + { + "name": "Mathieu LETRONE", + "email": "mathieu.letrone@etu.univ-nantes.fr" } ], - "maintainers" : [ + "maintainers": [ { - "name" : "Andy Maloney", - "email" : "asmaloney@gmail.com" + "name": "Ronan COLLIER", + "email": "ronan.collier@etu.univ-nantes.fr" }, { - "name" : "Example NoEmail" + "name": "Tri-thien TRUONG", + "email": "tri-thien.truong@etu.univ-nantes.fr" + }, + { + "name": "Mathieu LETRONE", + "email": "mathieu.letrone@etu.univ-nantes.fr" } ], - "references" : [ + "references": [ { - "text" : "The unsuccessful self-treatment of a case of “writer's block”", - "url" : "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC1311997/" + "text": "The unsuccessful self-treatment of a case of “writer's block”", + "url": "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC1311997/" }, { - "text" : "Sword swallowing and its side effects", - "url" : "http://www.bmj.com/content/333/7582/1285" - }, - { - "text" : "I thought of creating this wonderful plugin while I was hiking up ⛰ Mont Blanc" + "text": "Sword swallowing and its side effects", + "url": "http://www.bmj.com/content/333/7582/1285" } ] }