//########################################################################## //# # //# 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 #include //CCCoreLib #include #include #include #include #include //qCC_db #include #include #include #include #include #include #include //CCPluginAPI #include //Local #include "mainwindow.h" #include "ccCommon.h" #include "ccHistogramWindow.h" //Qt #include #include //System #include 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(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(&QComboBox::currentIndexChanged), this, &ccComparisonDlg::locaModelChanged); connect(maxSearchDistSpinBox, qOverload(&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(compMesh->getAssociatedCloud()); } else { m_compCloud = static_cast(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 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 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(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 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(maxDistanceDefined ? maxSearchDistSpinBox->value() : 0); uint64_t maxNeighbourhoodVolume = static_cast(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(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(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(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(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(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 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(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(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(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(std::max(0,lmKNNSpinBox->value())); c2cParams.radiusForLocalModel = static_cast(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(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(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(); }