| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122 |
- //##########################################################################
- //# #
- //# CLOUDCOMPARE #
- //# #
- //# 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: EDF R&D / TELECOM ParisTech (ENST-TSI) #
- //# #
- //##########################################################################
- #include "ccComparisonDlg.h"
- //Qt
- #include <QHeaderView>
- #include <QMessageBox>
- //CCCoreLib
- #include <DistanceComputationTools.h>
- #include <MeshSamplingTools.h>
- #include <ScalarField.h>
- #include <DgmOctree.h>
- #include <ScalarFieldTools.h>
- //qCC_db
- #include <ccLog.h>
- #include <ccHObject.h>
- #include <ccPointCloud.h>
- #include <ccGenericMesh.h>
- #include <ccOctree.h>
- #include <ccProgressDialog.h>
- #include <ccGBLSensor.h>
- //CCPluginAPI
- #include <ccQtHelpers.h>
- //Local
- #include "mainwindow.h"
- #include "ccCommon.h"
- #include "ccHistogramWindow.h"
- //Qt
- #include <QElapsedTimer>
- #include <QThreadPool>
- //System
- #include <assert.h>
- const unsigned char DEFAULT_OCTREE_LEVEL = 7;
- static int s_maxThreadCount = ccQtHelpers::GetMaxThreadCount();
- ccComparisonDlg::ccComparisonDlg( ccHObject* compEntity,
- ccHObject* refEntity,
- CC_COMPARISON_TYPE cpType,
- QWidget* parent/*=nullptr*/,
- bool noDisplay/*=false*/)
- : QDialog(parent, Qt::Tool)
- , Ui::ComparisonDialog()
- , m_compEnt(compEntity)
- , m_compCloud(nullptr)
- , m_compOctree(nullptr)
- , m_compOctreeIsPartial(false)
- , m_compSFVisibility(false)
- , m_refEnt(refEntity)
- , m_refCloud(nullptr)
- , m_refMesh(nullptr)
- , m_refOctree(nullptr)
- , m_refOctreeIsPartial(false)
- , m_refVisibility(false)
- , m_compType(cpType)
- , m_noDisplay(noDisplay)
- , m_bestOctreeLevel(0)
- {
- setupUi(this);
- static const int MaxThreadCount = QThread::idealThreadCount();
- maxThreadCountSpinBox->setRange(1, MaxThreadCount);
- maxThreadCountSpinBox->setSuffix(QString(" / %1").arg(MaxThreadCount));
- maxThreadCountSpinBox->setValue(s_maxThreadCount);
- //populate the combo-boxes
- {
- //octree level
- octreeLevelComboBox->addItem("AUTO");
- for (int i = 1; i <= CCCoreLib::DgmOctree::MAX_OCTREE_LEVEL; ++i)
- octreeLevelComboBox->addItem(QString::number(i));
- //local model
- localModelComboBox->addItem("NONE");
- localModelComboBox->addItem("Least Square Plane");
- localModelComboBox->addItem("2D1/2 Triangulation");
- localModelComboBox->addItem("Quadric");
- localModelComboBox->setCurrentIndex(0);
- }
- signedDistCheckBox->setChecked(false);
- split3DCheckBox->setEnabled(false);
- okButton->setEnabled(false);
- compName->setText(m_compEnt ? m_compEnt->getName() : QString());
- refName->setText(m_refEnt ? m_refEnt->getName() : QString());
- preciseResultsTabWidget->setCurrentIndex(0);
- m_refVisibility = (m_refEnt ? m_refEnt->isVisible() : false);
- m_compSFVisibility = (m_compEnt ? m_compEnt->sfShown() : false);
- if (!prepareEntitiesForComparison())
- return;
- assert(compEntity);
- ccBBox compEntBBox = compEntity->getOwnBB();
- maxSearchDistSpinBox->setValue(compEntBBox.getDiagNorm());
- if (m_refMesh)
- {
- localModelingTab->setEnabled(false);
- signedDistCheckBox->setEnabled(true);
- signedDistCheckBox->setChecked(true);
- filterVisibilityCheckBox->setEnabled(false);
- filterVisibilityCheckBox->setVisible(false);
- }
- else
- {
- signedDistCheckBox->setEnabled(false);
- split3DCheckBox->setEnabled(true);
- lmRadiusDoubleSpinBox->setValue(compEntBBox.getDiagNorm() / 200.0);
- filterVisibilityCheckBox->setEnabled(m_refCloud && m_refCloud->isA(CC_TYPES::POINT_CLOUD) && static_cast<ccPointCloud*>(m_refCloud)->hasSensor());
- }
- connect(cancelButton, &QPushButton::clicked, this, &ccComparisonDlg::cancelAndExit);
- connect(okButton, &QPushButton::clicked, this, &ccComparisonDlg::applyAndExit);
- connect(computeButton, &QPushButton::clicked, this, &ccComparisonDlg::computeDistances);
- connect(histoButton, &QPushButton::clicked, this, &ccComparisonDlg::showHisto);
- connect(maxDistCheckBox, &QCheckBox::toggled, this, &ccComparisonDlg::maxDistUpdated);
- connect(localModelComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &ccComparisonDlg::locaModelChanged);
- connect(maxSearchDistSpinBox, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &ccComparisonDlg::maxDistUpdated);
- connect(split3DCheckBox, &QCheckBox::toggled, this, &ccComparisonDlg::enableCompute2D);
- }
- ccComparisonDlg::~ccComparisonDlg()
- {
- releaseOctrees();
- }
- bool ccComparisonDlg::prepareEntitiesForComparison()
- {
- if (!m_compEnt || !m_refEnt)
- return false;
- //compared entity
- if (!m_compEnt->isA(CC_TYPES::POINT_CLOUD)) //TODO --> pas possible avec des GenericPointCloud ? :(
- {
- if (m_compType == CLOUDCLOUD_DIST || (m_compType == CLOUDMESH_DIST && !m_compEnt->isKindOf(CC_TYPES::MESH)))
- {
- ccLog::Error("Dialog initialization error! (bad entity type)");
- return false;
- }
- ccGenericMesh* compMesh = ccHObjectCaster::ToGenericMesh(m_compEnt);
- if (!compMesh->getAssociatedCloud()->isA(CC_TYPES::POINT_CLOUD)) //TODO
- {
- ccLog::Error("Dialog initialization error! (bad entity type - works only with real point clouds [todo])");
- return false;
- }
- m_compCloud = static_cast<ccPointCloud*>(compMesh->getAssociatedCloud());
- }
- else
- {
- m_compCloud = static_cast<ccPointCloud*>(m_compEnt);
- }
- //whatever the case, we always need the compared cloud's octree
- m_compOctree = m_compCloud->getOctree();
- if (!m_compOctree)
- {
- m_compOctree = ccOctree::Shared(new ccOctree(m_compCloud));
- }
- m_compOctreeIsPartial = false;
- //backup currently displayed SF (on compared cloud)
- int oldSfIdx = m_compCloud->getCurrentDisplayedScalarFieldIndex();
- if (oldSfIdx >= 0)
- {
- m_oldSfName = QString::fromStdString(m_compCloud->getScalarFieldName(oldSfIdx));
- }
- //reference entity
- if ( (m_compType == CLOUDMESH_DIST && !m_refEnt->isKindOf(CC_TYPES::MESH))
- || (m_compType == CLOUDCLOUD_DIST && !m_refEnt->isA(CC_TYPES::POINT_CLOUD)) )
- {
- ccLog::Error("Dialog initialization error! (bad entity type)");
- return false;
- }
- if (m_compType == CLOUDMESH_DIST)
- {
- m_refMesh = ccHObjectCaster::ToGenericMesh(m_refEnt);
- m_refCloud = m_refMesh->getAssociatedCloud();
- m_refOctree.clear();
- }
- else /*if (m_compType == CLOUDCLOUD_DIST)*/
- {
- m_refCloud = ccHObjectCaster::ToGenericPointCloud(m_refEnt);
- //for computing cloud/cloud distances we need also the reference cloud's octree
- m_refOctree = m_refCloud->getOctree();
- if (!m_refOctree)
- {
- m_refOctree = ccOctree::Shared(new ccOctree(m_refCloud));
- }
- }
- m_refOctreeIsPartial = false;
- return true;
- }
- void ccComparisonDlg::maxDistUpdated()
- {
- //the current 'best octree level' is depreacted
- m_bestOctreeLevel = 0;
- }
- void ccComparisonDlg::enableCompute2D(bool state)
- {
- compute2DCheckBox->setEnabled(state);
- }
- int ccComparisonDlg::getBestOctreeLevel()
- {
- if (m_bestOctreeLevel == 0)
- {
- double maxDistance = (maxDistCheckBox->isChecked() ? maxSearchDistSpinBox->value() : 0);
-
- int bestOctreeLevel = determineBestOctreeLevel(maxDistance);
- if (bestOctreeLevel <= 0)
- {
- ccLog::Error("Can't evaluate best octree level! Try to set it manually ...");
- return -1;
- }
- m_bestOctreeLevel = bestOctreeLevel;
- }
-
- return m_bestOctreeLevel;
- }
- void ccComparisonDlg::locaModelChanged(int index)
- {
- localModelParamsFrame->setEnabled(index != 0);
- if (index != 0)
- {
- unsigned minKNN = CCCoreLib::CC_LOCAL_MODEL_MIN_SIZE[index];
- lmKNNSpinBox->setMinimum(minKNN);
- }
- }
- void ccComparisonDlg::releaseOctrees()
- {
- if (m_compOctree && m_compCloud)
- {
- m_compOctree.clear();
- m_compOctreeIsPartial = false;
- }
- if (m_refOctree && m_refCloud)
- {
- m_refOctree.clear();
- m_refOctreeIsPartial = false;
- }
- }
- void ccComparisonDlg::updateDisplay(bool showSF, bool showRef)
- {
- if (m_noDisplay)
- return;
- if (m_compEnt)
- {
- m_compEnt->setVisible(true);
- m_compEnt->setEnabled(true);
- m_compEnt->showSF(showSF);
- m_compEnt->prepareDisplayForRefresh_recursive();
- }
- if (m_refEnt)
- {
- m_refEnt->setVisible(showRef);
- m_refEnt->prepareDisplayForRefresh_recursive();
- }
- MainWindow::UpdateUI();
- MainWindow::RefreshAllGLWindow(false);
- }
- bool ccComparisonDlg::isValid()
- {
- if ( !m_compCloud
- || !m_compOctree
- || (!m_refMesh && !m_refCloud)
- || (!m_refMesh && !m_refOctree))
- {
- ccLog::Error("Dialog initialization error! (void entity)");
- return false;
- }
- return true;
- }
- bool ccComparisonDlg::computeApproxDistances()
- {
- histoButton->setEnabled(false);
- preciseResultsTabWidget->widget(2)->setEnabled(false);
- if (!isValid())
- return false;
- //create the approximate dist. SF if necessary
- int sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME);
- if (sfIdx < 0)
- {
- sfIdx = m_compCloud->addScalarField(CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME);
- if (sfIdx < 0)
- {
- ccLog::Error("Failed to allocate a new scalar field for computing approx. distances! Try to free some memory ...");
- return false;
- }
- }
- m_compCloud->setCurrentScalarField(sfIdx);
- CCCoreLib::ScalarField* sf = m_compCloud->getCurrentInScalarField();
- assert(sf);
- //prepare the octree structures
- QScopedPointer<ccProgressDialog> progressDlg;
- if (parentWidget())
- {
- progressDlg.reset(new ccProgressDialog(true, this));
- progressDlg->show();
- }
- int approxResult = -1;
- QElapsedTimer eTimer;
- eTimer.start();
- switch (m_compType)
- {
- case CLOUDCLOUD_DIST: //cloud-cloud
- {
- approxResult = CCCoreLib::DistanceComputationTools::computeApproxCloud2CloudDistance( m_compCloud,
- m_refCloud,
- DEFAULT_OCTREE_LEVEL,
- 0,
- progressDlg.data(),
- m_compOctree.data(),
- m_refOctree.data());
- }
- break;
-
- case CLOUDMESH_DIST: //cloud-mesh
- {
- CCCoreLib::DistanceComputationTools::Cloud2MeshDistancesComputationParams c2mParams;
- {
- c2mParams.octreeLevel = DEFAULT_OCTREE_LEVEL;
- c2mParams.maxSearchDist = 0;
- c2mParams.useDistanceMap = true;
- c2mParams.signedDistances = false;
- c2mParams.flipNormals = false;
- c2mParams.multiThread = false;
- }
- approxResult = CCCoreLib::DistanceComputationTools::computeCloud2MeshDistances( m_compCloud,
- m_refMesh,
- c2mParams,
- progressDlg.data(),
- m_compOctree.data());
- }
- break;
- default:
- assert(false);
- break;
- }
- qint64 elapsedTime_ms = eTimer.elapsed();
- if (progressDlg)
- {
- progressDlg->stop();
- }
- //if the approximate distances comptation failed...
- if (approxResult < 0)
- {
- ccLog::Warning("[computeApproxDistances] Computation failed (error code %i)", approxResult);
- m_compCloud->deleteScalarField(sfIdx);
- sfIdx = -1;
- }
- else
- {
- ccLog::Print("[computeApproxDistances] Time: %3.2f s.", elapsedTime_ms / 1.0e3);
- //display approx. dist. statistics
- ScalarType mean;
- ScalarType variance;
- sf->computeMinAndMax();
- sf->computeMeanAndVariance(mean,&variance);
- approxStats->setColumnCount(2);
- approxStats->setRowCount(5);
- approxStats->setColumnWidth(1,200);
- approxStats->horizontalHeader()->hide();
- int curRow = 0;
- //min dist
- approxStats->setItem(curRow, 0, new QTableWidgetItem("Min dist."));
- approxStats->setItem(curRow++, 1, new QTableWidgetItem(QString("%1").arg(sf->getMin())));
- //max dist
- approxStats->setItem(curRow, 0, new QTableWidgetItem("Max dist."));
- approxStats->setItem(curRow++, 1, new QTableWidgetItem(QString("%1").arg(sf->getMax())));
- //mean dist
- approxStats->setItem(curRow, 0, new QTableWidgetItem("Avg dist."));
- approxStats->setItem(curRow++, 1, new QTableWidgetItem(QString("%1").arg(mean)));
- //sigma
- approxStats->setItem(curRow, 0, new QTableWidgetItem("Sigma"));
- approxStats->setItem(curRow++, 1, new QTableWidgetItem(QString("%1").arg(variance >= 0.0 ? sqrt(variance) : variance)));
- //Max relative error
- PointCoordinateType cs = m_compOctree->getCellSize(DEFAULT_OCTREE_LEVEL);
- double e = cs / 2.0;
- approxStats->setItem(curRow, 0, new QTableWidgetItem("Max error"));
- approxStats->setItem(curRow++, 1, new QTableWidgetItem(QString("%1").arg(e)));
- for (int i = 0; i < curRow; ++i)
- {
- approxStats->setRowHeight(i, 20);
- }
- approxStats->setEditTriggers(QAbstractItemView::NoEditTriggers);
- //enable the corresponding UI items
- preciseResultsTabWidget->widget(2)->setEnabled(true);
- histoButton->setEnabled(true);
- //init the max search distance
- maxSearchDistSpinBox->setValue(sf->getMax());
- //update display
- m_compCloud->setCurrentDisplayedScalarField(sfIdx);
- m_compCloud->showSF(sfIdx >= 0);
- }
- computeButton->setEnabled(true);
- preciseResultsTabWidget->setEnabled(true);
- //we don't let the user leave with approximate distances!!!
- okButton->setEnabled(false);
- updateDisplay(sfIdx >= 0, false);
- return true;
- }
- int ccComparisonDlg::determineBestOctreeLevel(double maxSearchDist)
- {
- if (!isValid())
- {
- return -1;
- }
- //make sure a the temporary dist. SF is activated
- int sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME);
- if (sfIdx < 0)
- {
- //we must compute approx. results again
- if (!computeApproxDistances())
- {
- //failed to compute approx distances?!
- return -1;
- }
- sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME);
- }
- const CCCoreLib::ScalarField* approxDistances = m_compCloud->getScalarField(sfIdx);
- if (!approxDistances)
- {
- assert(sfIdx >= 0);
- return -1;
- }
- //evalutate the theoretical time for each octree level
- const int MAX_OCTREE_LEVEL = m_refMesh ? 9 : CCCoreLib::DgmOctree::MAX_OCTREE_LEVEL; //DGM: can't go higher than level 9 with a mesh as the grid is 'plain' and would take too much memory!
- std::vector<double> timings;
- try
- {
- timings.resize(MAX_OCTREE_LEVEL, 0);
- }
- catch (const std::bad_alloc&)
- {
- ccLog::Warning("Can't determine best octree level: not enough memory!");
- return -1;
- }
- //if the reference is a mesh
- double meanTriangleSurface = 1.0;
- CCCoreLib::GenericIndexedMesh* mesh = nullptr;
- if (!m_refOctree)
- {
- if (!m_refMesh)
- {
- ccLog::Error("Internal error: reference entity should be a mesh!");
- return -1;
- }
- mesh = static_cast<CCCoreLib::GenericIndexedMesh*>(m_refMesh);
- if (!mesh || mesh->size() == 0)
- {
- ccLog::Warning("Can't determine best octree level: mesh is empty!");
- return -1;
- }
- //total mesh surface
- double meshSurface = CCCoreLib::MeshSamplingTools::computeMeshArea(mesh);
- //average triangle surface
- if (meshSurface > 0)
- {
- meanTriangleSurface = meshSurface / mesh->size();
- }
- }
- //we skip the lowest subdivision levels (useless + incompatible with below formulas ;)
- static const int s_minOctreeLevel = 6;
- int theBestOctreeLevel = s_minOctreeLevel;
- //we don't test the very first and very last level
- QScopedPointer<ccProgressDialog> progressDlg;
- if (parentWidget())
- {
- progressDlg.reset(new ccProgressDialog(false, this));
- progressDlg->setMethodTitle(tr("Determining optimal octree level"));
- progressDlg->setInfo(tr("Testing %1 levels...").arg(MAX_OCTREE_LEVEL)); //we lie here ;)
- progressDlg->start();
- }
- CCCoreLib::NormalizedProgress nProgress(progressDlg.data(), MAX_OCTREE_LEVEL - 2);
- QApplication::processEvents();
- bool maxDistanceDefined = maxDistCheckBox->isChecked();
- PointCoordinateType maxDistance = static_cast<PointCoordinateType>(maxDistanceDefined ? maxSearchDistSpinBox->value() : 0);
- uint64_t maxNeighbourhoodVolume = static_cast<uint64_t>(1) << (3 * MAX_OCTREE_LEVEL);
- //for each level
- for (int level = s_minOctreeLevel; level < MAX_OCTREE_LEVEL; ++level)
- {
- const unsigned char bitDec = CCCoreLib::DgmOctree::GET_BIT_SHIFT(level);
- unsigned numberOfPointsInCell = 0;
- unsigned index = 0;
- double cellDist = -1;
- //unsigned skippedCells = 0;
- //we compute a 'correction factor' that converts an approximate distance into an
- //approximate size of the neighborhood (in terms of cells)
- PointCoordinateType cellSize = m_compOctree->getCellSize(static_cast<unsigned char>(level));
- //we also use the reference cloud density (points/cell) if we have the info
- double refListDensity = 1.0;
- if (m_refOctree)
- {
- refListDensity = m_refOctree->computeMeanOctreeDensity(static_cast<unsigned char>(level));
- }
- CCCoreLib::DgmOctree::CellCode tempCode = 0xFFFFFFFF;
- //scan the octree structure
- const CCCoreLib::DgmOctree::cellsContainer& compCodes = m_compOctree->pointsAndTheirCellCodes();
- for (CCCoreLib::DgmOctree::cellsContainer::const_iterator c = compCodes.begin(); c != compCodes.end(); ++c)
- {
- CCCoreLib::DgmOctree::CellCode truncatedCode = (c->theCode >> bitDec);
- //new cell?
- if (truncatedCode != tempCode)
- {
- //if it's a real cell
- if (numberOfPointsInCell != 0)
- {
- //if 'maxSearchDist' has been defined by the user, we must take it into account!
- //(in this case we skip the cell if its approx. distance is superior)
- if (maxSearchDist <= 0 || cellDist <= maxSearchDist)
- {
- //approx. neighborhood radius
- cellDist /= cellSize;
- //approx. neighborhood width (in terms of cells)
- double neighbourSize = 2.0*cellDist + 1.0;
- //if the reference is a mesh
- if (mesh)
- {
- //(integer) approximation of the neighborhood size (in terms of cells)
- int nCell = static_cast<int>(ceil(cellDist));
- //Probable mesh surface in this neighborhood
- double crossingMeshSurface = (2.0*nCell+1.0) * cellSize;
- //squared surface!
- crossingMeshSurface *= crossingMeshSurface;
- //neighborhood "volume" (in terms of cells)
- double neighbourSize3 = (neighbourSize*neighbourSize*neighbourSize) / maxNeighbourhoodVolume;
- //TIME = NEIGHBORS SEARCH + proportional factor * POINTS/TRIANGLES COMPARISONS
- timings[level] += neighbourSize3 + ((0.5 * numberOfPointsInCell) / maxNeighbourhoodVolume) * (crossingMeshSurface / meanTriangleSurface);
- }
- else
- {
- //we ignore the "central" cell
- neighbourSize -= 1.0;
- //neighborhood "volume" (in terms of cells)
- double neighbourSize3 = (neighbourSize*neighbourSize*neighbourSize) / maxNeighbourhoodVolume;
- //volume of the last "slice" (in terms of cells)
- //=V(n)-V(n-1) = (2*n+1)^3 - (2*n-1)^3 = 24 * n^2 + 2 (if n > 0)
- double lastSliceCellCount = (cellDist > 0 ? cellDist*cellDist * 24.0 + 2.0 : 1.0);
- //TIME = NEIGHBORS SEARCH + proportional factor * POINTS/TRIANGLES COMPARISONS
- //(we admit that the filled cells roughly correspond to the sqrt of the total number of cells)
- timings[level] += neighbourSize3 + 0.1 * ((numberOfPointsInCell * sqrt(lastSliceCellCount) * refListDensity) / maxNeighbourhoodVolume);
- }
- }
- //else
- //{
- // ++skippedCells;
- //}
- }
- numberOfPointsInCell = 0;
- cellDist = 0;
- tempCode = truncatedCode;
- }
- ScalarType pointDist = approxDistances->getValue(index);
- if (maxDistanceDefined && pointDist > maxDistance)
- {
- pointDist = maxDistance;
- }
- //cellDist += pointDist;
- cellDist = std::max<double>(cellDist, pointDist);
- ++index;
- ++numberOfPointsInCell;
- }
- ////very high levels are unlikely (levelModifier ~ 0.85 @ level 20)
- //{
- // double levelModifier = level < 12 ? 1.0 : exp(-pow(level-12,2)/(20*20));
- // timings[level] /= levelModifier;
- // ccLog::PrintDebug(QString("[Distances] Level %1 - timing = %2 (modifier = %3)").arg(level).arg(timings[level]).arg(levelModifier));
- //}
- //ccLog::Print("[Timing] Level %i --> %2.12f", level, timings[level]);
-
- if (timings[level] * 1.05 < timings[theBestOctreeLevel]) //avoid increasing the octree level for super small differences (which is generally counter productive)
- {
- theBestOctreeLevel = level;
- }
- if (!nProgress.oneStep())
- {
- break;
- }
- }
- ccLog::PrintDebug("[Distances] Best level: %i (maxSearchDist = %f)", theBestOctreeLevel, maxSearchDist);
- return theBestOctreeLevel;
- }
- bool ccComparisonDlg::computeDistances()
- {
- if (!isValid())
- return false;
- int octreeLevel = octreeLevelComboBox->currentIndex();
- assert(octreeLevel <= CCCoreLib::DgmOctree::MAX_OCTREE_LEVEL);
- if (octreeLevel == 0)
- {
- //we'll try to guess the best octree level
- octreeLevel = getBestOctreeLevel();
- if (octreeLevel <= 0)
- {
- //best octree level computation failed?!
- return false;
- }
- ccLog::Print(QString("[Distances] Octree level (auto): %1").arg(octreeLevel));
- }
- //options
- bool signedDistances = signedDistCheckBox->isEnabled() && signedDistCheckBox->isChecked();
- bool flipNormals = (signedDistances ? flipNormalsCheckBox->isChecked() : false);
- bool robust = (signedDistances ? robustCheckBox->isChecked() : true);
- bool split3D = split3DCheckBox->isEnabled() && split3DCheckBox->isChecked();
- bool mergeXY = compute2DCheckBox->isChecked();
- //does the cloud has already a temporary scalar field that we can use?
- int sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
- if (sfIdx < 0)
- {
- //we need to create a new scalar field
- sfIdx = m_compCloud->addScalarField(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
- if (sfIdx < 0)
- {
- ccLog::Error("Couldn't allocate a new scalar field for computing distances! Try to free some memory ...");
- return false;
- }
- }
- m_compCloud->setCurrentScalarField(sfIdx);
- CCCoreLib::ScalarField* sf = m_compCloud->getCurrentInScalarField();
- assert(sf);
- //max search distance
- ScalarType maxSearchDist = static_cast<ScalarType>(maxDistCheckBox->isChecked() ? maxSearchDistSpinBox->value() : 0);
- //multi-thread
- bool multiThread = multiThreadedCheckBox->isChecked();
- CCCoreLib::DistanceComputationTools::Cloud2CloudDistancesComputationParams c2cParams;
- CCCoreLib::DistanceComputationTools::Cloud2MeshDistancesComputationParams c2mParams;
- s_maxThreadCount = c2cParams.maxThreadCount = c2mParams.maxThreadCount = maxThreadCountSpinBox->value();
- ccLog::Print(QString("[Distances] Will use %1 threads").arg(s_maxThreadCount));
- int result = -1;
- QScopedPointer<ccProgressDialog> progressDlg;
- if (parentWidget())
- {
- progressDlg.reset(new ccProgressDialog(true, this));
- }
- QElapsedTimer eTimer;
- eTimer.start();
- switch (m_compType)
- {
- case CLOUDCLOUD_DIST: //cloud-cloud
- if (split3D)
- {
- //we create 3 new scalar fields, one for each dimension
- unsigned count = m_compCloud->size();
- bool success = true;
- for (unsigned j = 0; j < 3; ++j)
- {
- ccScalarField* sfDim = new ccScalarField();
- if (sfDim->resizeSafe(count))
- {
- sfDim->link();
- c2cParams.splitDistances[j] = sfDim;
- }
- else
- {
- success = false;
- break;
- }
- }
- if (!success)
- {
- ccLog::Error("[ComputeDistances] Not enough memory to generate 3D split fields!");
- for (unsigned j = 0; j < 3; ++j)
- {
- if (c2cParams.splitDistances[j])
- {
- c2cParams.splitDistances[j]->release();
- c2cParams.splitDistances[j] = nullptr;
- }
- }
- }
- }
-
- if (m_refCloud->isA(CC_TYPES::POINT_CLOUD))
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(m_refCloud);
- //we enable the visibility checking if the user asked for it
- bool filterVisibility = filterVisibilityCheckBox->isEnabled() && filterVisibilityCheckBox->isChecked();
- if (filterVisibility)
- {
- size_t validDB = 0;
- //we also make sure that the sensors have valid depth buffer!
- for (unsigned i = 0; i < pc->getChildrenNumber(); ++i)
- {
- ccHObject* child = pc->getChild(i);
- if (child && child->isA(CC_TYPES::GBL_SENSOR))
- {
- ccGBLSensor* sensor = static_cast<ccGBLSensor*>(child);
- if (sensor->getDepthBuffer().zBuff.empty())
- {
- int errorCode;
- if (!sensor->computeDepthBuffer(pc, errorCode))
- {
- ccLog::Warning(QString("[ComputeDistances] ") + ccGBLSensor::GetErrorString(errorCode));
- }
- else
- {
- ++validDB;
- }
- }
- else
- {
- ++validDB;
- }
- }
- }
- if (validDB == 0)
- {
- filterVisibilityCheckBox->setChecked(false);
- if (QMessageBox::warning( this,
- "Error",
- "Failed to find/init the depth buffer(s) on the associated sensor! Do you want to continue?",
- QMessageBox::Yes,
- QMessageBox::No) == QMessageBox::No)
- {
- break;
- }
- filterVisibility = false;
- }
- }
- pc->enableVisibilityCheck(filterVisibility);
- }
- //setup parameters
- {
- c2cParams.octreeLevel = static_cast<unsigned char>(octreeLevel);
- if (localModelingTab->isEnabled())
- {
- c2cParams.localModel = (CCCoreLib::LOCAL_MODEL_TYPES)localModelComboBox->currentIndex();
- if (c2cParams.localModel != CCCoreLib::NO_MODEL)
- {
- c2cParams.useSphericalSearchForLocalModel = lmRadiusRadioButton->isChecked();
- c2cParams.kNNForLocalModel = static_cast<unsigned>(std::max(0,lmKNNSpinBox->value()));
- c2cParams.radiusForLocalModel = static_cast<ScalarType>(lmRadiusDoubleSpinBox->value());
- c2cParams.reuseExistingLocalModels = lmOptimizeCheckBox->isChecked();
- }
- }
- c2cParams.maxSearchDist = maxSearchDist;
- c2cParams.multiThread = multiThread;
- c2cParams.CPSet = nullptr;
- }
-
- result = CCCoreLib::DistanceComputationTools::computeCloud2CloudDistances( m_compCloud,
- m_refCloud,
- c2cParams,
- progressDlg.data(),
- m_compOctree.data(),
- m_refOctree.data());
- break;
- case CLOUDMESH_DIST: //cloud-mesh
- //setup parameters
- {
- c2mParams.octreeLevel = static_cast<unsigned char>(octreeLevel);
- c2mParams.maxSearchDist = maxSearchDist;
- c2mParams.useDistanceMap = false;
- c2mParams.signedDistances = signedDistances;
- c2mParams.flipNormals = flipNormals;
- c2mParams.multiThread = multiThread;
- c2mParams.robust = robust;
- }
-
- result = CCCoreLib::DistanceComputationTools::computeCloud2MeshDistances( m_compCloud,
- m_refMesh,
- c2mParams,
- progressDlg.data(),
- m_compOctree.data());
- break;
- }
- qint64 elapsedTime_ms = eTimer.elapsed();
- if (progressDlg)
- {
- progressDlg->stop();
- }
- if (result >= 0)
- {
- ccLog::Print("[ComputeDistances] Time: %3.2f s.", elapsedTime_ms / 1.0e3);
- //display some statics about the computed distances
- ScalarType mean;
- ScalarType variance;
- sf->computeMinAndMax();
- sf->computeMeanAndVariance(mean, &variance);
- ccLog::Print("[ComputeDistances] " + tr("Mean distance = %1 / std deviation = %2").arg(mean).arg(sqrt(variance)));
- m_compCloud->setCurrentDisplayedScalarField(sfIdx);
- m_compCloud->showSF(sfIdx >= 0);
- //restore UI items
- okButton->setEnabled(true);
- m_sfName.clear();
- switch(m_compType)
- {
- case CLOUDCLOUD_DIST: //hausdorff
- m_sfName = QString(CC_CLOUD2CLOUD_DISTANCES_DEFAULT_SF_NAME);
- break;
- case CLOUDMESH_DIST: //cloud-mesh
- m_sfName = QString(signedDistances ? CC_CLOUD2MESH_SIGNED_DISTANCES_DEFAULT_SF_NAME : CC_CLOUD2MESH_DISTANCES_DEFAULT_SF_NAME);
- break;
- }
- if (c2cParams.localModel != CCCoreLib::NO_MODEL)
- {
- m_sfName += QString("[%1]").arg(localModelComboBox->currentText());
- if (c2cParams.useSphericalSearchForLocalModel)
- m_sfName += QString("[r=%1]").arg(c2cParams.radiusForLocalModel);
- else
- m_sfName += QString("[k=%1]").arg(c2cParams.kNNForLocalModel);
- if (c2cParams.reuseExistingLocalModels)
- m_sfName += QString("[fast]");
- }
- if (flipNormals)
- {
- m_sfName += QString("[-]");
- }
- if (maxSearchDist > 0)
- {
- m_sfName += QString("[<%1]").arg(maxSearchDist);
- }
- if (split3D)
- {
- //we add the corresponding scalar fields (one for each dimension)
- for (unsigned j = 0; j < 3; ++j)
- {
- CCCoreLib::ScalarField* sf = c2cParams.splitDistances[j];
- if (sf)
- {
- static const QChar CharDim[3]{ 'X', 'Y', 'Z' };
- QString dimSFName = m_sfName + QString(" (%1)").arg(CharDim[j]);
- sf->setName(dimSFName.toStdString());
- sf->computeMinAndMax();
- //check that SF doesn't already exist
- int sfExit = m_compCloud->getScalarFieldIndexByName(sf->getName());
- if (sfExit >= 0)
- m_compCloud->deleteScalarField(sfExit);
- int sfEnter = m_compCloud->addScalarField(static_cast<ccScalarField*>(sf));
- assert(sfEnter >= 0);
- }
- }
- ccLog::Warning("[ComputeDistances] Result has been split along each dimension (check the 3 other scalar fields with '_X', '_Y' and '_Z' suffix!)");
- if (mergeXY)
- {
- ccLog::Warning("[ComputeDistances] compute 2D distances (xy plane)");
- QString sfNameXY = m_sfName + " (XY)";
- int sf2D = m_compCloud->getScalarFieldIndexByName(sfNameXY.toStdString());
- if (sf2D < 0)
- {
- sf2D = m_compCloud->addScalarField(sfNameXY.toStdString());
- }
- if (sf2D < 0)
- {
- ccLog::Error("[ComputeDistances] impossible to add XY scalar field");
- return 0;
- }
- CCCoreLib::ScalarField* sf = m_compCloud->getScalarField(sf2D);
- for (unsigned idx = 0; idx < m_compCloud->size(); idx++)
- {
- float d2D = pow(pow(c2cParams.splitDistances[0]->getValue(idx), 2) + pow(c2cParams.splitDistances[1]->getValue(idx), 2), 0.5);
- sf->setValue(idx, d2D);
- }
- sf->computeMinAndMax();
- }
- }
- }
- else
- {
- ccLog::Error("[ComputeDistances] Error (%i)",result);
-
- m_compCloud->deleteScalarField(sfIdx);
- m_compCloud->showSF(false);
- sfIdx = -1;
- }
- for (unsigned j = 0; j < 3; ++j)
- {
- CCCoreLib::ScalarField* &sf = c2cParams.splitDistances[j];
- if (sf)
- {
- sf->release();
- sf = nullptr;
- }
- }
-
- updateDisplay(sfIdx >= 0, false);
- return result >= 0;
- }
- void ccComparisonDlg::showHisto()
- {
- if (!m_compCloud)
- return;
- ccScalarField* sf = m_compCloud->getCurrentDisplayedScalarField();
- if (!sf)
- return;
- ccHistogramWindowDlg* hDlg = new ccHistogramWindowDlg(this);
- hDlg->setWindowTitle(QString("Histogram [%1]").arg(m_compCloud->getName()));
- {
- ccHistogramWindow* histogram = hDlg->window();
- histogram->setTitle(QString("Approximate distances (%1 values)").arg(m_compCloud->size()));
- histogram->fromSF(sf, 8, false);
- histogram->setAxisLabels("Approximate distances", "Count");
- }
- hDlg->resize(400, 300);
- hDlg->show();
- }
- void ccComparisonDlg::applyAndExit()
- {
- if (m_compCloud)
- {
- //m_compCloud->setCurrentDisplayedScalarField(-1);
- //m_compCloud->showSF(false);
- //remove the approximate dist. SF
- int tmpSfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME);
- if (tmpSfIdx >= 0)
- {
- m_compCloud->deleteScalarField(tmpSfIdx);
- tmpSfIdx = -1;
- }
- //now, if we have a temp distance scalar field (the 'real' distances computed by the user)
- //we should rename it properly
- int sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
- if (sfIdx >= 0)
- {
- if (m_sfName.isEmpty()) //hum,hum
- {
- ccLog::Warning("Something went wrong!");
- m_compCloud->deleteScalarField(sfIdx);
- m_compCloud->setCurrentDisplayedScalarField(-1);
- m_compCloud->showSF(false);
- }
- else
- {
- //we delete any existing scalar field with the exact same name
- int _sfIdx = m_compCloud->getScalarFieldIndexByName(m_sfName.toStdString());
- if (_sfIdx >= 0)
- {
- m_compCloud->deleteScalarField(_sfIdx);
- //we update sfIdx because indexes are all messed up after deletion
- sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
- }
- m_compCloud->renameScalarField(sfIdx,m_sfName.toStdString());
- m_compCloud->setCurrentDisplayedScalarField(sfIdx);
- m_compCloud->showSF(sfIdx >= 0);
- }
- }
- //m_compCloud->setCurrentDisplayedScalarField(-1);
- //m_compCloud->showSF(false);
- }
- updateDisplay(true, m_refVisibility);
- releaseOctrees();
- accept();
- }
- void ccComparisonDlg::cancelAndExit()
- {
- if (m_compCloud)
- {
- m_compCloud->setCurrentDisplayedScalarField(-1);
- m_compCloud->showSF(false);
- //remove the approximate dist. SF
- int tmpSfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME);
- if (tmpSfIdx >= 0)
- {
- m_compCloud->deleteScalarField(tmpSfIdx);
- tmpSfIdx = -1;
- }
- int sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
- if (sfIdx >= 0)
- {
- m_compCloud->deleteScalarField(sfIdx);
- sfIdx = -1;
- }
- if (!m_oldSfName.isEmpty())
- {
- int oldSfIdx = m_compCloud->getScalarFieldIndexByName(m_oldSfName.toStdString());
- if (oldSfIdx)
- {
- m_compCloud->setCurrentDisplayedScalarField(oldSfIdx);
- m_compCloud->showSF(oldSfIdx >= 0);
- }
- }
- }
- updateDisplay(m_compSFVisibility, m_refVisibility);
- releaseOctrees();
- reject();
- }
|