diff --git a/CMakeLists.txt b/CMakeLists.txt index 9759542..3c5c203 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ cmake_minimum_required(VERSION 2.8) -option( INSTALL_QCANUPO2_PLUGIN "Check to install qCanupo2 plugin" OFF ) +option( INSTALL_Q3DMASC_PLUGIN "Check to install q3DMASC plugin" OFF ) -if (INSTALL_QCANUPO2_PLUGIN) +if (INSTALL_Q3DMASC_PLUGIN) #New classification plugin (Dimitri Lague/OSUR) -project( QCANUPO2_PLUGIN_DLL ) +project( Q3DMASC_PLUGIN_DLL ) #need OpenCV find_package( OpenCV REQUIRED ) @@ -16,6 +16,7 @@ project( QCANUPO2_PLUGIN_DLL ) #include the whole CC directory (easier) include_directories( ${CloudCompare_SOURCE_DIR} ) + #include_directories( ${OpenCV_INCLUDE_DIR} ) #we need the "order choice" dialog file( GLOB CC_PLUGIN_CUSTOM_HEADER_LIST ${CloudCompare_SOURCE_DIR}/ccOrderChoiceDlg*.h ) diff --git a/Logo_cnrs_eub_eec.png b/Logo_cnrs_eub_eec.png new file mode 100644 index 0000000..69467bb Binary files /dev/null and b/Logo_cnrs_eub_eec.png differ diff --git a/classifyDisclaimerDlg.ui b/classifyDisclaimerDlg.ui new file mode 100644 index 0000000..c8f0091 --- /dev/null +++ b/classifyDisclaimerDlg.ui @@ -0,0 +1,112 @@ + + + ClassifyDisclaimerDialog + + + + 0 + 0 + 460 + 250 + + + + q3DMASC_classify (disclaimer) + + + + :/CC/plugin/qCanupoPlugin/iconClassify.png:/CC/plugin/qCanupoPlugin/iconClassify.png + + + + + + + + + 150 + 174 + + + + + + + :/CC/plugin/q3DMASC/Logo_cnrs_eub_eec.png + + + true + + + + + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; font-weight:600; color:#1f497d;">3D Multi-cloud, Multi-Attribute, multi-Scale, multi Class classification (3DMASC)</span></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#ffffff;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; font-style:italic; color:#1f497d;">Le Guennec, A., Lague, D., Corpetti, Th., Lefevre, S.</span></p> +<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Calibri,sans-serif'; font-size:10pt; font-style:italic; color:#1f497d; background-color:#ffffff;"><br /></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#ffffff;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; color:#1f497d; background-color:#ffffff;">Funded by UniversitĂ© EuropĂ©enne de Bretagne, Centre National de la Recherche Scientifique and EEC Marie-Curie actions</span></p> +<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Calibri,sans-serif'; font-size:10pt; color:#1f497d; background-color:#ffffff;"><br /></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#ffffff;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; color:#1f497d;">Enjoy!</span></p></body></html> + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + ClassifyDisclaimerDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ClassifyDisclaimerDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/iconClassify.png b/iconClassify.png new file mode 100644 index 0000000..9437a2d Binary files /dev/null and b/iconClassify.png differ diff --git a/iconCreate.png b/iconCreate.png new file mode 100644 index 0000000..663b3ae Binary files /dev/null and b/iconCreate.png differ diff --git a/info.json b/info.json new file mode 100644 index 0000000..dffa0c4 --- /dev/null +++ b/info.json @@ -0,0 +1,26 @@ +{ + "type": "Standard", + "name": "3DMASC", + "icon": ":/CC/plugin/q3DMASC/iconCreate.png", + "description": "Create or apply a classifier on a point cloud.", + "authors": [ + { + "name": "Dimitri Lague (OSUR/CNRS)" + } + ], + "maintainers": [ + { + "name": "Dimitri Lague (OSUR/CNRS)" + } + ], + "references": [ + { + "text": "Dimitri Lague, Geosciences Rennes", + "url": "https://geosciences.univ-rennes1.fr/interlocuteurs/dimitri-lague" + }, + { + "text": "Documentation", + "url": "http://www.cloudcompare.org/doc/wiki/index.php?title=3DMASC_(plugin)" + } + ] +} diff --git a/q3DMASC.cpp b/q3DMASC.cpp new file mode 100644 index 0000000..7000fa8 --- /dev/null +++ b/q3DMASC.cpp @@ -0,0 +1,171 @@ +//########################################################################## +//# # +//# CLOUDCOMPARE PLUGIN: q3DMASC # +//# # +//# 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 or later 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: Dimitri Lague / CNRS / UEB # +//# # +//########################################################################## + +#include "q3DMASC.h" + +//local +#include "q3DMASCDisclaimerDialog.h" + +//qCC_db +#include + +//Qt +#include +#include +#include +#include +#include + +q3DMASCPlugin::q3DMASCPlugin(QObject* parent/*=0*/) + : QObject(parent) + , ccStdPluginInterface( ":/CC/plugin/q3DMASCPlugin/info.json" ) + , m_classifyAction(0) + , m_trainAction(0) +{ +} + +void q3DMASCPlugin::onNewSelection(const ccHObject::Container& selectedEntities) +{ + if (m_classifyAction) + { + //classification: only one point cloud + m_classifyAction->setEnabled(selectedEntities.size() == 1 && selectedEntities[0]->isA(CC_TYPES::POINT_CLOUD)); + } + + if (m_trainAction) + { + m_trainAction->setEnabled(m_app && m_app->dbRootObject() && m_app->dbRootObject()->getChildrenNumber() != 0); //need some loaded entities to train the classifier! + } + + m_selectedEntities = selectedEntities; +} + +QList q3DMASCPlugin::getActions() +{ + QList group; + + if (!m_trainAction) + { + m_trainAction = new QAction("Train classifier", this); + m_trainAction->setToolTip("Train classifier"); + m_trainAction->setIcon(QIcon(QString::fromUtf8(":/CC/plugin/q3DMASCPlugin/iconCreate.png"))); + connect(m_trainAction, SIGNAL(triggered()), this, SLOT(doTrainAction())); + } + group.push_back(m_trainAction); + + if (!m_classifyAction) + { + m_classifyAction = new QAction("Classify", this); + m_classifyAction->setToolTip("Classify cloud"); + m_classifyAction->setIcon(QIcon(QString::fromUtf8(":/CC/plugin/q3DMASCPlugin/iconClassify.png"))); + connect(m_classifyAction, SIGNAL(triggered()), this, SLOT(doClassifyAction())); + } + group.push_back(m_classifyAction); + + return group; +} + +#include + +void q3DMASCPlugin::doClassifyAction() +{ + if (!m_app) + { + assert(false); + return; + } + + //disclaimer accepted? + if (!ShowClassifyDisclaimer(m_app)) + { + return; + } + + if (m_selectedEntities.empty() || !m_selectedEntities.front()->isA(CC_TYPES::POINT_CLOUD)) + { + m_app->dispToConsole("Select one and only one point cloud!", ccMainAppInterface::ERR_CONSOLE_MESSAGE); + return; + } + + ccPointCloud* cloud = static_cast(m_selectedEntities.front()); + + struct RTParams + { + int maxDepth = 25; //To be left as a parameter of the training plugin (default 25) + int minSampleCount = 1; //To be left as a parameter of the training plugin (default 1) + int maxCategories = 0; //Normally not important as there’s no categorical variable + const bool calcVarImportance = true; //Must be true + int activeVarCount = 0; //USE 0 as the default parameter (works best) + int maxTreeCount = 100; //Left as a parameter of the training plugin (default: 100) + }; + RTParams params; + + unsigned sampleCount = cloud->size(); + unsigned attributesPerSample = cloud->getNumberOfScalarFields(); + + //NUMBER_OF_TRAINING_SAMPLES = number of points + //ATTRIBUTES_PER_SAMPLE = number of scalar fields + cv::Mat training_data = cv::Mat(sampleCount, attributesPerSample, CV_32FC1); + cv::Mat train_labels = cv::Mat(attributesPerSample, 1, CV_32FC1); + + cv::Ptr rtrees; + rtrees = cv::ml::RTrees::create(); + rtrees->setMaxDepth(params.maxDepth); + rtrees->setMinSampleCount(params.minSampleCount); + rtrees->setCalculateVarImportance(params.calcVarImportance); + rtrees->setActiveVarCount(params.activeVarCount); + cv::TermCriteria terminationCriteria(cv::TermCriteria::MAX_ITER, params.maxTreeCount, std::numeric_limits::epsilon()); + rtrees->setTermCriteria(terminationCriteria); + + //rtrees->setRegressionAccuracy(0); + //rtrees->setUseSurrogates(false); + //rtrees->setMaxCategories(params.maxCategories); //not important? + //rtrees->setPriors(cv::Mat()); + + rtrees->train(training_data, cv::ml::ROW_SAMPLE, train_labels); + +} + +//OpenCV + +void q3DMASCPlugin::doTrainAction() +{ + //disclaimer accepted? + if (!ShowTrainDisclaimer(m_app)) + return; + + //if (m_selectedEntities.size() != 2 + // || !m_selectedEntities[0]->isA(CC_TYPES::POINT_CLOUD) + // || !m_selectedEntities[1]->isA(CC_TYPES::POINT_CLOUD)) + //{ + // m_app->dispToConsole("Select two point clouds!",ccMainAppInterface::ERR_CONSOLE_MESSAGE); + // return; + //} + // + //ccPointCloud* cloud1 = static_cast(m_selectedEntities[0]); + //ccPointCloud* cloud2 = static_cast(m_selectedEntities[1]); +} + +void q3DMASCPlugin::registerCommands(ccCommandLineInterface* cmd) +{ + if (!cmd) + { + assert(false); + return; + } + //cmd->registerCommand(ccCommandLineInterface::Command::Shared(new CommandCanupoClassif)); +} diff --git a/qCanupo2Plugin.h b/q3DMASC.h similarity index 55% rename from qCanupo2Plugin.h rename to q3DMASC.h index 9cc2a97..4f8e7f0 100644 --- a/qCanupo2Plugin.h +++ b/q3DMASC.h @@ -1,57 +1,62 @@ //########################################################################## //# # -//# CLOUDCOMPARE PLUGIN: qCanupo2 # +//# CLOUDCOMPARE PLUGIN: q3DMASC # //# # //# 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. # +//# the Free Software Foundation; version 2 or later 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 # +//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # //# GNU General Public License for more details. # //# # -//# COPYRIGHT: OSUR # +//# COPYRIGHT: Dimitri Lague / CNRS / UEB # //# # //########################################################################## -#ifndef QCANUPO2_PLUGIN_HEADER -#define QCANUPO2_PLUGIN_HEADER +#ifndef Q_3DMASC_PLUGIN_HEADER +#define Q_3DMASC_PLUGIN_HEADER //qCC #include -//! New classification plugin (Dimitri Lague / OSUR) -class qCanupo2Plugin : public QObject, public ccStdPluginInterface +//qCC_db +#include + +//! 3DMASC plugin +/** 3D Multi-cloud, multi-Attribute, multi-Scale, multi-Class classification +**/ +class q3DMASCPlugin : public QObject, public ccStdPluginInterface { Q_OBJECT Q_INTERFACES(ccStdPluginInterface) - //replace qDummy by the plugin name (IID should be unique - let's hope your plugin name is unique ;) - Q_PLUGIN_METADATA(IID "cccorp.cloudcompare.plugin.qCanupo2") + Q_PLUGIN_METADATA(IID "cccorp.cloudcompare.plugin.q3DMASC" FILE "info.json") public: //! Default constructor - explicit qCanupo2Plugin(QObject* parent = 0); - - //inherited from ccPluginInterface - virtual QString getName() const override { return "CANUPO V2"; } - virtual QString getDescription() const override { return "CANUPO V2 Classification (D. Lague, A. le Guennec)"; } - virtual QIcon getIcon() const override; + q3DMASCPlugin(QObject* parent = nullptr); //inherited from ccStdPluginInterface void onNewSelection(const ccHObject::Container& selectedEntities) override; virtual QList getActions() override; + virtual void registerCommands(ccCommandLineInterface* cmd) override; protected slots: - void doAction(); + void doClassifyAction(); + void doTrainAction(); protected: - //! Default action - QAction* m_action; + //! Calssift action + QAction* m_classifyAction; + //! Train action + QAction* m_trainAction; + + //! Currently selected entities + ccHObject::Container m_selectedEntities; }; -#endif //QCANUPO2_PLUGIN_HEADER - +#endif //Q_3DMASC_PLUGIN_HEADER diff --git a/q3DMASC.qrc b/q3DMASC.qrc new file mode 100644 index 0000000..6fa4b2e --- /dev/null +++ b/q3DMASC.qrc @@ -0,0 +1,7 @@ + + + iconClassify.png + iconCreate.png + Logo_cnrs_eub_eec.png + + diff --git a/q3DMASCDisclaimerDialog.h b/q3DMASCDisclaimerDialog.h new file mode 100644 index 0000000..3db831d --- /dev/null +++ b/q3DMASCDisclaimerDialog.h @@ -0,0 +1,84 @@ +//########################################################################## +//# # +//# CLOUDCOMPARE PLUGIN: q3DMASC # +//# # +//# 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 or later 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: Dimitri Lague / CNRS / UEB # +//# # +//########################################################################## + +#ifndef Q3DMASC_DISCLAIMER_DIALOG_HEADER +#define Q3DMASC_DISCLAIMER_DIALOG_HEADER + +#include +#include + +//qCC_plugins +#include + +//Qt +#include + +//! Dialog for displaying the 3DSMAC/UEB disclaimer +class TrainDisclaimerDialog : public QDialog, public Ui::TrainDisclaimerDialog +{ +public: + //! Default constructor + TrainDisclaimerDialog(QWidget* parent = 0) + : QDialog(parent) + , Ui::TrainDisclaimerDialog() + { + setupUi(this); + } +}; + +//whether disclaimer has already been displayed (and accepted) or not +static bool s_trainDisclaimerAccepted = false; + +static bool ShowTrainDisclaimer(ccMainAppInterface* app) +{ + if (!s_trainDisclaimerAccepted) + { + //if the user "cancels" it, then he refuses the diclaimer! + s_trainDisclaimerAccepted = TrainDisclaimerDialog(app ? app->getMainWindow() : 0).exec(); + } + + return s_trainDisclaimerAccepted; +} + +//! Dialog for displaying the M3C2/UEB disclaimer +class ClassifyDisclaimerDialog : public QDialog, public Ui::ClassifyDisclaimerDialog +{ +public: + //! Default constructor + ClassifyDisclaimerDialog(QWidget* parent = 0) + : QDialog(parent) + , Ui::ClassifyDisclaimerDialog() + { + setupUi(this); + } +}; + +//whether disclaimer has already been displayed (and accepted) or not +static bool s_classifyDisclaimerAccepted = false; + +static bool ShowClassifyDisclaimer(ccMainAppInterface* app) +{ + if (!s_classifyDisclaimerAccepted) + { + //if the user "cancels" it, then he refuses the diclaimer! + s_classifyDisclaimerAccepted = ClassifyDisclaimerDialog(app ? app->getMainWindow() : 0).exec(); + } + + return s_classifyDisclaimerAccepted; +} + +#endif //Q3DMASC_DISCLAIMER_DIALOG_HEADER diff --git a/qCanupo2Plugin.cpp b/qCanupo2Plugin.cpp deleted file mode 100644 index 4c8a807..0000000 --- a/qCanupo2Plugin.cpp +++ /dev/null @@ -1,65 +0,0 @@ -//########################################################################## -//# # -//# CLOUDCOMPARE PLUGIN: qDummy # -//# # -//# 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 # -//# # -//########################################################################## - -#include "qCanupo2Plugin.h" - -//Qt -#include - -qCanupo2Plugin::qCanupo2Plugin(QObject* parent/*=0*/) - : QObject(parent) - , m_action(0) -{ -} - -void qCanupo2Plugin::onNewSelection(const ccHObject::Container& selectedEntities) -{ - //if (m_action) - // m_action->setEnabled(!selectedEntities.empty()); -} - -QList qCanupo2Plugin::getActions() -{ - if (!m_action) - { - m_action = new QAction(getName(),this); - m_action->setToolTip(getDescription()); - m_action->setIcon(getIcon()); - //connect appropriate signal - connect(m_action, SIGNAL(triggered()), this, SLOT(doAction())); - } - - return QList{m_action}; -} - -void qCanupo2Plugin::doAction() -{ - //m_app should have already been initialized by CC when plugin is loaded! - //(--> pure internal check) - assert(m_app); - if (!m_app) - return; - - m_app->dispToConsole("[qCanupo2Plugin] Hello world!",ccMainAppInterface::STD_CONSOLE_MESSAGE); //a standard message is displayed in the console - m_app->dispToConsole("[qCanupo2Plugin] Warning: dummy plugin shouldn't be used as is!",ccMainAppInterface::WRN_CONSOLE_MESSAGE); //a warning message is displayed in the console - m_app->dispToConsole("Dummy plugin shouldn't be used as is!",ccMainAppInterface::ERR_CONSOLE_MESSAGE); //an error message is displayed in the console AND an error box will pop-up! -} - -QIcon qCanupo2Plugin::getIcon() const -{ - return QIcon(":/CC/plugin/qCanupo2/icon.png"); -} diff --git a/qCanupo2Plugin.qrc b/qCanupo2Plugin.qrc deleted file mode 100644 index b3a6622..0000000 --- a/qCanupo2Plugin.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - icon.png - - diff --git a/trainDisclaimerDlg.ui b/trainDisclaimerDlg.ui new file mode 100644 index 0000000..510681f --- /dev/null +++ b/trainDisclaimerDlg.ui @@ -0,0 +1,113 @@ + + + TrainDisclaimerDialog + + + + 0 + 0 + 460 + 250 + + + + q3DSMAC_create (disclaimer) + + + + :/CC/plugin/qCanupoPlugin/iconCreate.png:/CC/plugin/qCanupoPlugin/iconCreate.png + + + + + + + + + 150 + 174 + + + + + + + :/CC/plugin/q3DMASC/Logo_cnrs_eub_eec.png + + + true + + + + + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#ffffff;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; font-weight:600; color:#1f497d;">Classifier creation (3DSMAC)</span></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#ffffff;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; font-style:italic; color:#1f497d;">Le Guennec, A., Lague, D., Corpetti, Th., Lefevre, S.</span></p> +<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Calibri,sans-serif'; font-size:10pt; font-style:italic; color:#1f497d; background-color:#ffffff;"><br /></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#ffffff;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; color:#1f497d; background-color:#ffffff;">Funded by UniversitĂ© EuropĂ©enne de Bretagne, Centre National de la Recherche Scientifique and EEC Marie-Curie actions</span></p> +<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Calibri,sans-serif'; font-size:10pt; color:#1f497d; background-color:#ffffff;"><br /></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; background-color:#ffffff;"><span style=" font-family:'Calibri,sans-serif'; font-size:10pt; color:#1f497d;">Enjoy!</span></p></body></html> + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + buttonBox + accepted() + TrainDisclaimerDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + TrainDisclaimerDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +