ccComparisonDlg.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122
  1. //##########################################################################
  2. //# #
  3. //# CLOUDCOMPARE #
  4. //# #
  5. //# This program is free software; you can redistribute it and/or modify #
  6. //# it under the terms of the GNU General Public License as published by #
  7. //# the Free Software Foundation; version 2 or later of the License. #
  8. //# #
  9. //# This program is distributed in the hope that it will be useful, #
  10. //# but WITHOUT ANY WARRANTY; without even the implied warranty of #
  11. //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
  12. //# GNU General Public License for more details. #
  13. //# #
  14. //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) #
  15. //# #
  16. //##########################################################################
  17. #include "ccComparisonDlg.h"
  18. //Qt
  19. #include <QHeaderView>
  20. #include <QMessageBox>
  21. //CCCoreLib
  22. #include <DistanceComputationTools.h>
  23. #include <MeshSamplingTools.h>
  24. #include <ScalarField.h>
  25. #include <DgmOctree.h>
  26. #include <ScalarFieldTools.h>
  27. //qCC_db
  28. #include <ccLog.h>
  29. #include <ccHObject.h>
  30. #include <ccPointCloud.h>
  31. #include <ccGenericMesh.h>
  32. #include <ccOctree.h>
  33. #include <ccProgressDialog.h>
  34. #include <ccGBLSensor.h>
  35. //CCPluginAPI
  36. #include <ccQtHelpers.h>
  37. //Local
  38. #include "mainwindow.h"
  39. #include "ccCommon.h"
  40. #include "ccHistogramWindow.h"
  41. //Qt
  42. #include <QElapsedTimer>
  43. #include <QThreadPool>
  44. //System
  45. #include <assert.h>
  46. const unsigned char DEFAULT_OCTREE_LEVEL = 7;
  47. static int s_maxThreadCount = ccQtHelpers::GetMaxThreadCount();
  48. ccComparisonDlg::ccComparisonDlg( ccHObject* compEntity,
  49. ccHObject* refEntity,
  50. CC_COMPARISON_TYPE cpType,
  51. QWidget* parent/*=nullptr*/,
  52. bool noDisplay/*=false*/)
  53. : QDialog(parent, Qt::Tool)
  54. , Ui::ComparisonDialog()
  55. , m_compEnt(compEntity)
  56. , m_compCloud(nullptr)
  57. , m_compOctree(nullptr)
  58. , m_compOctreeIsPartial(false)
  59. , m_compSFVisibility(false)
  60. , m_refEnt(refEntity)
  61. , m_refCloud(nullptr)
  62. , m_refMesh(nullptr)
  63. , m_refOctree(nullptr)
  64. , m_refOctreeIsPartial(false)
  65. , m_refVisibility(false)
  66. , m_compType(cpType)
  67. , m_noDisplay(noDisplay)
  68. , m_bestOctreeLevel(0)
  69. {
  70. setupUi(this);
  71. static const int MaxThreadCount = QThread::idealThreadCount();
  72. maxThreadCountSpinBox->setRange(1, MaxThreadCount);
  73. maxThreadCountSpinBox->setSuffix(QString(" / %1").arg(MaxThreadCount));
  74. maxThreadCountSpinBox->setValue(s_maxThreadCount);
  75. //populate the combo-boxes
  76. {
  77. //octree level
  78. octreeLevelComboBox->addItem("AUTO");
  79. for (int i = 1; i <= CCCoreLib::DgmOctree::MAX_OCTREE_LEVEL; ++i)
  80. octreeLevelComboBox->addItem(QString::number(i));
  81. //local model
  82. localModelComboBox->addItem("NONE");
  83. localModelComboBox->addItem("Least Square Plane");
  84. localModelComboBox->addItem("2D1/2 Triangulation");
  85. localModelComboBox->addItem("Quadric");
  86. localModelComboBox->setCurrentIndex(0);
  87. }
  88. signedDistCheckBox->setChecked(false);
  89. split3DCheckBox->setEnabled(false);
  90. okButton->setEnabled(false);
  91. compName->setText(m_compEnt ? m_compEnt->getName() : QString());
  92. refName->setText(m_refEnt ? m_refEnt->getName() : QString());
  93. preciseResultsTabWidget->setCurrentIndex(0);
  94. m_refVisibility = (m_refEnt ? m_refEnt->isVisible() : false);
  95. m_compSFVisibility = (m_compEnt ? m_compEnt->sfShown() : false);
  96. if (!prepareEntitiesForComparison())
  97. return;
  98. assert(compEntity);
  99. ccBBox compEntBBox = compEntity->getOwnBB();
  100. maxSearchDistSpinBox->setValue(compEntBBox.getDiagNorm());
  101. if (m_refMesh)
  102. {
  103. localModelingTab->setEnabled(false);
  104. signedDistCheckBox->setEnabled(true);
  105. signedDistCheckBox->setChecked(true);
  106. filterVisibilityCheckBox->setEnabled(false);
  107. filterVisibilityCheckBox->setVisible(false);
  108. }
  109. else
  110. {
  111. signedDistCheckBox->setEnabled(false);
  112. split3DCheckBox->setEnabled(true);
  113. lmRadiusDoubleSpinBox->setValue(compEntBBox.getDiagNorm() / 200.0);
  114. filterVisibilityCheckBox->setEnabled(m_refCloud && m_refCloud->isA(CC_TYPES::POINT_CLOUD) && static_cast<ccPointCloud*>(m_refCloud)->hasSensor());
  115. }
  116. connect(cancelButton, &QPushButton::clicked, this, &ccComparisonDlg::cancelAndExit);
  117. connect(okButton, &QPushButton::clicked, this, &ccComparisonDlg::applyAndExit);
  118. connect(computeButton, &QPushButton::clicked, this, &ccComparisonDlg::computeDistances);
  119. connect(histoButton, &QPushButton::clicked, this, &ccComparisonDlg::showHisto);
  120. connect(maxDistCheckBox, &QCheckBox::toggled, this, &ccComparisonDlg::maxDistUpdated);
  121. connect(localModelComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &ccComparisonDlg::locaModelChanged);
  122. connect(maxSearchDistSpinBox, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &ccComparisonDlg::maxDistUpdated);
  123. connect(split3DCheckBox, &QCheckBox::toggled, this, &ccComparisonDlg::enableCompute2D);
  124. }
  125. ccComparisonDlg::~ccComparisonDlg()
  126. {
  127. releaseOctrees();
  128. }
  129. bool ccComparisonDlg::prepareEntitiesForComparison()
  130. {
  131. if (!m_compEnt || !m_refEnt)
  132. return false;
  133. //compared entity
  134. if (!m_compEnt->isA(CC_TYPES::POINT_CLOUD)) //TODO --> pas possible avec des GenericPointCloud ? :(
  135. {
  136. if (m_compType == CLOUDCLOUD_DIST || (m_compType == CLOUDMESH_DIST && !m_compEnt->isKindOf(CC_TYPES::MESH)))
  137. {
  138. ccLog::Error("Dialog initialization error! (bad entity type)");
  139. return false;
  140. }
  141. ccGenericMesh* compMesh = ccHObjectCaster::ToGenericMesh(m_compEnt);
  142. if (!compMesh->getAssociatedCloud()->isA(CC_TYPES::POINT_CLOUD)) //TODO
  143. {
  144. ccLog::Error("Dialog initialization error! (bad entity type - works only with real point clouds [todo])");
  145. return false;
  146. }
  147. m_compCloud = static_cast<ccPointCloud*>(compMesh->getAssociatedCloud());
  148. }
  149. else
  150. {
  151. m_compCloud = static_cast<ccPointCloud*>(m_compEnt);
  152. }
  153. //whatever the case, we always need the compared cloud's octree
  154. m_compOctree = m_compCloud->getOctree();
  155. if (!m_compOctree)
  156. {
  157. m_compOctree = ccOctree::Shared(new ccOctree(m_compCloud));
  158. }
  159. m_compOctreeIsPartial = false;
  160. //backup currently displayed SF (on compared cloud)
  161. int oldSfIdx = m_compCloud->getCurrentDisplayedScalarFieldIndex();
  162. if (oldSfIdx >= 0)
  163. {
  164. m_oldSfName = QString::fromStdString(m_compCloud->getScalarFieldName(oldSfIdx));
  165. }
  166. //reference entity
  167. if ( (m_compType == CLOUDMESH_DIST && !m_refEnt->isKindOf(CC_TYPES::MESH))
  168. || (m_compType == CLOUDCLOUD_DIST && !m_refEnt->isA(CC_TYPES::POINT_CLOUD)) )
  169. {
  170. ccLog::Error("Dialog initialization error! (bad entity type)");
  171. return false;
  172. }
  173. if (m_compType == CLOUDMESH_DIST)
  174. {
  175. m_refMesh = ccHObjectCaster::ToGenericMesh(m_refEnt);
  176. m_refCloud = m_refMesh->getAssociatedCloud();
  177. m_refOctree.clear();
  178. }
  179. else /*if (m_compType == CLOUDCLOUD_DIST)*/
  180. {
  181. m_refCloud = ccHObjectCaster::ToGenericPointCloud(m_refEnt);
  182. //for computing cloud/cloud distances we need also the reference cloud's octree
  183. m_refOctree = m_refCloud->getOctree();
  184. if (!m_refOctree)
  185. {
  186. m_refOctree = ccOctree::Shared(new ccOctree(m_refCloud));
  187. }
  188. }
  189. m_refOctreeIsPartial = false;
  190. return true;
  191. }
  192. void ccComparisonDlg::maxDistUpdated()
  193. {
  194. //the current 'best octree level' is depreacted
  195. m_bestOctreeLevel = 0;
  196. }
  197. void ccComparisonDlg::enableCompute2D(bool state)
  198. {
  199. compute2DCheckBox->setEnabled(state);
  200. }
  201. int ccComparisonDlg::getBestOctreeLevel()
  202. {
  203. if (m_bestOctreeLevel == 0)
  204. {
  205. double maxDistance = (maxDistCheckBox->isChecked() ? maxSearchDistSpinBox->value() : 0);
  206. int bestOctreeLevel = determineBestOctreeLevel(maxDistance);
  207. if (bestOctreeLevel <= 0)
  208. {
  209. ccLog::Error("Can't evaluate best octree level! Try to set it manually ...");
  210. return -1;
  211. }
  212. m_bestOctreeLevel = bestOctreeLevel;
  213. }
  214. return m_bestOctreeLevel;
  215. }
  216. void ccComparisonDlg::locaModelChanged(int index)
  217. {
  218. localModelParamsFrame->setEnabled(index != 0);
  219. if (index != 0)
  220. {
  221. unsigned minKNN = CCCoreLib::CC_LOCAL_MODEL_MIN_SIZE[index];
  222. lmKNNSpinBox->setMinimum(minKNN);
  223. }
  224. }
  225. void ccComparisonDlg::releaseOctrees()
  226. {
  227. if (m_compOctree && m_compCloud)
  228. {
  229. m_compOctree.clear();
  230. m_compOctreeIsPartial = false;
  231. }
  232. if (m_refOctree && m_refCloud)
  233. {
  234. m_refOctree.clear();
  235. m_refOctreeIsPartial = false;
  236. }
  237. }
  238. void ccComparisonDlg::updateDisplay(bool showSF, bool showRef)
  239. {
  240. if (m_noDisplay)
  241. return;
  242. if (m_compEnt)
  243. {
  244. m_compEnt->setVisible(true);
  245. m_compEnt->setEnabled(true);
  246. m_compEnt->showSF(showSF);
  247. m_compEnt->prepareDisplayForRefresh_recursive();
  248. }
  249. if (m_refEnt)
  250. {
  251. m_refEnt->setVisible(showRef);
  252. m_refEnt->prepareDisplayForRefresh_recursive();
  253. }
  254. MainWindow::UpdateUI();
  255. MainWindow::RefreshAllGLWindow(false);
  256. }
  257. bool ccComparisonDlg::isValid()
  258. {
  259. if ( !m_compCloud
  260. || !m_compOctree
  261. || (!m_refMesh && !m_refCloud)
  262. || (!m_refMesh && !m_refOctree))
  263. {
  264. ccLog::Error("Dialog initialization error! (void entity)");
  265. return false;
  266. }
  267. return true;
  268. }
  269. bool ccComparisonDlg::computeApproxDistances()
  270. {
  271. histoButton->setEnabled(false);
  272. preciseResultsTabWidget->widget(2)->setEnabled(false);
  273. if (!isValid())
  274. return false;
  275. //create the approximate dist. SF if necessary
  276. int sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME);
  277. if (sfIdx < 0)
  278. {
  279. sfIdx = m_compCloud->addScalarField(CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME);
  280. if (sfIdx < 0)
  281. {
  282. ccLog::Error("Failed to allocate a new scalar field for computing approx. distances! Try to free some memory ...");
  283. return false;
  284. }
  285. }
  286. m_compCloud->setCurrentScalarField(sfIdx);
  287. CCCoreLib::ScalarField* sf = m_compCloud->getCurrentInScalarField();
  288. assert(sf);
  289. //prepare the octree structures
  290. QScopedPointer<ccProgressDialog> progressDlg;
  291. if (parentWidget())
  292. {
  293. progressDlg.reset(new ccProgressDialog(true, this));
  294. progressDlg->show();
  295. }
  296. int approxResult = -1;
  297. QElapsedTimer eTimer;
  298. eTimer.start();
  299. switch (m_compType)
  300. {
  301. case CLOUDCLOUD_DIST: //cloud-cloud
  302. {
  303. approxResult = CCCoreLib::DistanceComputationTools::computeApproxCloud2CloudDistance( m_compCloud,
  304. m_refCloud,
  305. DEFAULT_OCTREE_LEVEL,
  306. 0,
  307. progressDlg.data(),
  308. m_compOctree.data(),
  309. m_refOctree.data());
  310. }
  311. break;
  312. case CLOUDMESH_DIST: //cloud-mesh
  313. {
  314. CCCoreLib::DistanceComputationTools::Cloud2MeshDistancesComputationParams c2mParams;
  315. {
  316. c2mParams.octreeLevel = DEFAULT_OCTREE_LEVEL;
  317. c2mParams.maxSearchDist = 0;
  318. c2mParams.useDistanceMap = true;
  319. c2mParams.signedDistances = false;
  320. c2mParams.flipNormals = false;
  321. c2mParams.multiThread = false;
  322. }
  323. approxResult = CCCoreLib::DistanceComputationTools::computeCloud2MeshDistances( m_compCloud,
  324. m_refMesh,
  325. c2mParams,
  326. progressDlg.data(),
  327. m_compOctree.data());
  328. }
  329. break;
  330. default:
  331. assert(false);
  332. break;
  333. }
  334. qint64 elapsedTime_ms = eTimer.elapsed();
  335. if (progressDlg)
  336. {
  337. progressDlg->stop();
  338. }
  339. //if the approximate distances comptation failed...
  340. if (approxResult < 0)
  341. {
  342. ccLog::Warning("[computeApproxDistances] Computation failed (error code %i)", approxResult);
  343. m_compCloud->deleteScalarField(sfIdx);
  344. sfIdx = -1;
  345. }
  346. else
  347. {
  348. ccLog::Print("[computeApproxDistances] Time: %3.2f s.", elapsedTime_ms / 1.0e3);
  349. //display approx. dist. statistics
  350. ScalarType mean;
  351. ScalarType variance;
  352. sf->computeMinAndMax();
  353. sf->computeMeanAndVariance(mean,&variance);
  354. approxStats->setColumnCount(2);
  355. approxStats->setRowCount(5);
  356. approxStats->setColumnWidth(1,200);
  357. approxStats->horizontalHeader()->hide();
  358. int curRow = 0;
  359. //min dist
  360. approxStats->setItem(curRow, 0, new QTableWidgetItem("Min dist."));
  361. approxStats->setItem(curRow++, 1, new QTableWidgetItem(QString("%1").arg(sf->getMin())));
  362. //max dist
  363. approxStats->setItem(curRow, 0, new QTableWidgetItem("Max dist."));
  364. approxStats->setItem(curRow++, 1, new QTableWidgetItem(QString("%1").arg(sf->getMax())));
  365. //mean dist
  366. approxStats->setItem(curRow, 0, new QTableWidgetItem("Avg dist."));
  367. approxStats->setItem(curRow++, 1, new QTableWidgetItem(QString("%1").arg(mean)));
  368. //sigma
  369. approxStats->setItem(curRow, 0, new QTableWidgetItem("Sigma"));
  370. approxStats->setItem(curRow++, 1, new QTableWidgetItem(QString("%1").arg(variance >= 0.0 ? sqrt(variance) : variance)));
  371. //Max relative error
  372. PointCoordinateType cs = m_compOctree->getCellSize(DEFAULT_OCTREE_LEVEL);
  373. double e = cs / 2.0;
  374. approxStats->setItem(curRow, 0, new QTableWidgetItem("Max error"));
  375. approxStats->setItem(curRow++, 1, new QTableWidgetItem(QString("%1").arg(e)));
  376. for (int i = 0; i < curRow; ++i)
  377. {
  378. approxStats->setRowHeight(i, 20);
  379. }
  380. approxStats->setEditTriggers(QAbstractItemView::NoEditTriggers);
  381. //enable the corresponding UI items
  382. preciseResultsTabWidget->widget(2)->setEnabled(true);
  383. histoButton->setEnabled(true);
  384. //init the max search distance
  385. maxSearchDistSpinBox->setValue(sf->getMax());
  386. //update display
  387. m_compCloud->setCurrentDisplayedScalarField(sfIdx);
  388. m_compCloud->showSF(sfIdx >= 0);
  389. }
  390. computeButton->setEnabled(true);
  391. preciseResultsTabWidget->setEnabled(true);
  392. //we don't let the user leave with approximate distances!!!
  393. okButton->setEnabled(false);
  394. updateDisplay(sfIdx >= 0, false);
  395. return true;
  396. }
  397. int ccComparisonDlg::determineBestOctreeLevel(double maxSearchDist)
  398. {
  399. if (!isValid())
  400. {
  401. return -1;
  402. }
  403. //make sure a the temporary dist. SF is activated
  404. int sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME);
  405. if (sfIdx < 0)
  406. {
  407. //we must compute approx. results again
  408. if (!computeApproxDistances())
  409. {
  410. //failed to compute approx distances?!
  411. return -1;
  412. }
  413. sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME);
  414. }
  415. const CCCoreLib::ScalarField* approxDistances = m_compCloud->getScalarField(sfIdx);
  416. if (!approxDistances)
  417. {
  418. assert(sfIdx >= 0);
  419. return -1;
  420. }
  421. //evalutate the theoretical time for each octree level
  422. 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!
  423. std::vector<double> timings;
  424. try
  425. {
  426. timings.resize(MAX_OCTREE_LEVEL, 0);
  427. }
  428. catch (const std::bad_alloc&)
  429. {
  430. ccLog::Warning("Can't determine best octree level: not enough memory!");
  431. return -1;
  432. }
  433. //if the reference is a mesh
  434. double meanTriangleSurface = 1.0;
  435. CCCoreLib::GenericIndexedMesh* mesh = nullptr;
  436. if (!m_refOctree)
  437. {
  438. if (!m_refMesh)
  439. {
  440. ccLog::Error("Internal error: reference entity should be a mesh!");
  441. return -1;
  442. }
  443. mesh = static_cast<CCCoreLib::GenericIndexedMesh*>(m_refMesh);
  444. if (!mesh || mesh->size() == 0)
  445. {
  446. ccLog::Warning("Can't determine best octree level: mesh is empty!");
  447. return -1;
  448. }
  449. //total mesh surface
  450. double meshSurface = CCCoreLib::MeshSamplingTools::computeMeshArea(mesh);
  451. //average triangle surface
  452. if (meshSurface > 0)
  453. {
  454. meanTriangleSurface = meshSurface / mesh->size();
  455. }
  456. }
  457. //we skip the lowest subdivision levels (useless + incompatible with below formulas ;)
  458. static const int s_minOctreeLevel = 6;
  459. int theBestOctreeLevel = s_minOctreeLevel;
  460. //we don't test the very first and very last level
  461. QScopedPointer<ccProgressDialog> progressDlg;
  462. if (parentWidget())
  463. {
  464. progressDlg.reset(new ccProgressDialog(false, this));
  465. progressDlg->setMethodTitle(tr("Determining optimal octree level"));
  466. progressDlg->setInfo(tr("Testing %1 levels...").arg(MAX_OCTREE_LEVEL)); //we lie here ;)
  467. progressDlg->start();
  468. }
  469. CCCoreLib::NormalizedProgress nProgress(progressDlg.data(), MAX_OCTREE_LEVEL - 2);
  470. QApplication::processEvents();
  471. bool maxDistanceDefined = maxDistCheckBox->isChecked();
  472. PointCoordinateType maxDistance = static_cast<PointCoordinateType>(maxDistanceDefined ? maxSearchDistSpinBox->value() : 0);
  473. uint64_t maxNeighbourhoodVolume = static_cast<uint64_t>(1) << (3 * MAX_OCTREE_LEVEL);
  474. //for each level
  475. for (int level = s_minOctreeLevel; level < MAX_OCTREE_LEVEL; ++level)
  476. {
  477. const unsigned char bitDec = CCCoreLib::DgmOctree::GET_BIT_SHIFT(level);
  478. unsigned numberOfPointsInCell = 0;
  479. unsigned index = 0;
  480. double cellDist = -1;
  481. //unsigned skippedCells = 0;
  482. //we compute a 'correction factor' that converts an approximate distance into an
  483. //approximate size of the neighborhood (in terms of cells)
  484. PointCoordinateType cellSize = m_compOctree->getCellSize(static_cast<unsigned char>(level));
  485. //we also use the reference cloud density (points/cell) if we have the info
  486. double refListDensity = 1.0;
  487. if (m_refOctree)
  488. {
  489. refListDensity = m_refOctree->computeMeanOctreeDensity(static_cast<unsigned char>(level));
  490. }
  491. CCCoreLib::DgmOctree::CellCode tempCode = 0xFFFFFFFF;
  492. //scan the octree structure
  493. const CCCoreLib::DgmOctree::cellsContainer& compCodes = m_compOctree->pointsAndTheirCellCodes();
  494. for (CCCoreLib::DgmOctree::cellsContainer::const_iterator c = compCodes.begin(); c != compCodes.end(); ++c)
  495. {
  496. CCCoreLib::DgmOctree::CellCode truncatedCode = (c->theCode >> bitDec);
  497. //new cell?
  498. if (truncatedCode != tempCode)
  499. {
  500. //if it's a real cell
  501. if (numberOfPointsInCell != 0)
  502. {
  503. //if 'maxSearchDist' has been defined by the user, we must take it into account!
  504. //(in this case we skip the cell if its approx. distance is superior)
  505. if (maxSearchDist <= 0 || cellDist <= maxSearchDist)
  506. {
  507. //approx. neighborhood radius
  508. cellDist /= cellSize;
  509. //approx. neighborhood width (in terms of cells)
  510. double neighbourSize = 2.0*cellDist + 1.0;
  511. //if the reference is a mesh
  512. if (mesh)
  513. {
  514. //(integer) approximation of the neighborhood size (in terms of cells)
  515. int nCell = static_cast<int>(ceil(cellDist));
  516. //Probable mesh surface in this neighborhood
  517. double crossingMeshSurface = (2.0*nCell+1.0) * cellSize;
  518. //squared surface!
  519. crossingMeshSurface *= crossingMeshSurface;
  520. //neighborhood "volume" (in terms of cells)
  521. double neighbourSize3 = (neighbourSize*neighbourSize*neighbourSize) / maxNeighbourhoodVolume;
  522. //TIME = NEIGHBORS SEARCH + proportional factor * POINTS/TRIANGLES COMPARISONS
  523. timings[level] += neighbourSize3 + ((0.5 * numberOfPointsInCell) / maxNeighbourhoodVolume) * (crossingMeshSurface / meanTriangleSurface);
  524. }
  525. else
  526. {
  527. //we ignore the "central" cell
  528. neighbourSize -= 1.0;
  529. //neighborhood "volume" (in terms of cells)
  530. double neighbourSize3 = (neighbourSize*neighbourSize*neighbourSize) / maxNeighbourhoodVolume;
  531. //volume of the last "slice" (in terms of cells)
  532. //=V(n)-V(n-1) = (2*n+1)^3 - (2*n-1)^3 = 24 * n^2 + 2 (if n > 0)
  533. double lastSliceCellCount = (cellDist > 0 ? cellDist*cellDist * 24.0 + 2.0 : 1.0);
  534. //TIME = NEIGHBORS SEARCH + proportional factor * POINTS/TRIANGLES COMPARISONS
  535. //(we admit that the filled cells roughly correspond to the sqrt of the total number of cells)
  536. timings[level] += neighbourSize3 + 0.1 * ((numberOfPointsInCell * sqrt(lastSliceCellCount) * refListDensity) / maxNeighbourhoodVolume);
  537. }
  538. }
  539. //else
  540. //{
  541. // ++skippedCells;
  542. //}
  543. }
  544. numberOfPointsInCell = 0;
  545. cellDist = 0;
  546. tempCode = truncatedCode;
  547. }
  548. ScalarType pointDist = approxDistances->getValue(index);
  549. if (maxDistanceDefined && pointDist > maxDistance)
  550. {
  551. pointDist = maxDistance;
  552. }
  553. //cellDist += pointDist;
  554. cellDist = std::max<double>(cellDist, pointDist);
  555. ++index;
  556. ++numberOfPointsInCell;
  557. }
  558. ////very high levels are unlikely (levelModifier ~ 0.85 @ level 20)
  559. //{
  560. // double levelModifier = level < 12 ? 1.0 : exp(-pow(level-12,2)/(20*20));
  561. // timings[level] /= levelModifier;
  562. // ccLog::PrintDebug(QString("[Distances] Level %1 - timing = %2 (modifier = %3)").arg(level).arg(timings[level]).arg(levelModifier));
  563. //}
  564. //ccLog::Print("[Timing] Level %i --> %2.12f", level, timings[level]);
  565. if (timings[level] * 1.05 < timings[theBestOctreeLevel]) //avoid increasing the octree level for super small differences (which is generally counter productive)
  566. {
  567. theBestOctreeLevel = level;
  568. }
  569. if (!nProgress.oneStep())
  570. {
  571. break;
  572. }
  573. }
  574. ccLog::PrintDebug("[Distances] Best level: %i (maxSearchDist = %f)", theBestOctreeLevel, maxSearchDist);
  575. return theBestOctreeLevel;
  576. }
  577. bool ccComparisonDlg::computeDistances()
  578. {
  579. if (!isValid())
  580. return false;
  581. int octreeLevel = octreeLevelComboBox->currentIndex();
  582. assert(octreeLevel <= CCCoreLib::DgmOctree::MAX_OCTREE_LEVEL);
  583. if (octreeLevel == 0)
  584. {
  585. //we'll try to guess the best octree level
  586. octreeLevel = getBestOctreeLevel();
  587. if (octreeLevel <= 0)
  588. {
  589. //best octree level computation failed?!
  590. return false;
  591. }
  592. ccLog::Print(QString("[Distances] Octree level (auto): %1").arg(octreeLevel));
  593. }
  594. //options
  595. bool signedDistances = signedDistCheckBox->isEnabled() && signedDistCheckBox->isChecked();
  596. bool flipNormals = (signedDistances ? flipNormalsCheckBox->isChecked() : false);
  597. bool robust = (signedDistances ? robustCheckBox->isChecked() : true);
  598. bool split3D = split3DCheckBox->isEnabled() && split3DCheckBox->isChecked();
  599. bool mergeXY = compute2DCheckBox->isChecked();
  600. //does the cloud has already a temporary scalar field that we can use?
  601. int sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
  602. if (sfIdx < 0)
  603. {
  604. //we need to create a new scalar field
  605. sfIdx = m_compCloud->addScalarField(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
  606. if (sfIdx < 0)
  607. {
  608. ccLog::Error("Couldn't allocate a new scalar field for computing distances! Try to free some memory ...");
  609. return false;
  610. }
  611. }
  612. m_compCloud->setCurrentScalarField(sfIdx);
  613. CCCoreLib::ScalarField* sf = m_compCloud->getCurrentInScalarField();
  614. assert(sf);
  615. //max search distance
  616. ScalarType maxSearchDist = static_cast<ScalarType>(maxDistCheckBox->isChecked() ? maxSearchDistSpinBox->value() : 0);
  617. //multi-thread
  618. bool multiThread = multiThreadedCheckBox->isChecked();
  619. CCCoreLib::DistanceComputationTools::Cloud2CloudDistancesComputationParams c2cParams;
  620. CCCoreLib::DistanceComputationTools::Cloud2MeshDistancesComputationParams c2mParams;
  621. s_maxThreadCount = c2cParams.maxThreadCount = c2mParams.maxThreadCount = maxThreadCountSpinBox->value();
  622. ccLog::Print(QString("[Distances] Will use %1 threads").arg(s_maxThreadCount));
  623. int result = -1;
  624. QScopedPointer<ccProgressDialog> progressDlg;
  625. if (parentWidget())
  626. {
  627. progressDlg.reset(new ccProgressDialog(true, this));
  628. }
  629. QElapsedTimer eTimer;
  630. eTimer.start();
  631. switch (m_compType)
  632. {
  633. case CLOUDCLOUD_DIST: //cloud-cloud
  634. if (split3D)
  635. {
  636. //we create 3 new scalar fields, one for each dimension
  637. unsigned count = m_compCloud->size();
  638. bool success = true;
  639. for (unsigned j = 0; j < 3; ++j)
  640. {
  641. ccScalarField* sfDim = new ccScalarField();
  642. if (sfDim->resizeSafe(count))
  643. {
  644. sfDim->link();
  645. c2cParams.splitDistances[j] = sfDim;
  646. }
  647. else
  648. {
  649. success = false;
  650. break;
  651. }
  652. }
  653. if (!success)
  654. {
  655. ccLog::Error("[ComputeDistances] Not enough memory to generate 3D split fields!");
  656. for (unsigned j = 0; j < 3; ++j)
  657. {
  658. if (c2cParams.splitDistances[j])
  659. {
  660. c2cParams.splitDistances[j]->release();
  661. c2cParams.splitDistances[j] = nullptr;
  662. }
  663. }
  664. }
  665. }
  666. if (m_refCloud->isA(CC_TYPES::POINT_CLOUD))
  667. {
  668. ccPointCloud* pc = static_cast<ccPointCloud*>(m_refCloud);
  669. //we enable the visibility checking if the user asked for it
  670. bool filterVisibility = filterVisibilityCheckBox->isEnabled() && filterVisibilityCheckBox->isChecked();
  671. if (filterVisibility)
  672. {
  673. size_t validDB = 0;
  674. //we also make sure that the sensors have valid depth buffer!
  675. for (unsigned i = 0; i < pc->getChildrenNumber(); ++i)
  676. {
  677. ccHObject* child = pc->getChild(i);
  678. if (child && child->isA(CC_TYPES::GBL_SENSOR))
  679. {
  680. ccGBLSensor* sensor = static_cast<ccGBLSensor*>(child);
  681. if (sensor->getDepthBuffer().zBuff.empty())
  682. {
  683. int errorCode;
  684. if (!sensor->computeDepthBuffer(pc, errorCode))
  685. {
  686. ccLog::Warning(QString("[ComputeDistances] ") + ccGBLSensor::GetErrorString(errorCode));
  687. }
  688. else
  689. {
  690. ++validDB;
  691. }
  692. }
  693. else
  694. {
  695. ++validDB;
  696. }
  697. }
  698. }
  699. if (validDB == 0)
  700. {
  701. filterVisibilityCheckBox->setChecked(false);
  702. if (QMessageBox::warning( this,
  703. "Error",
  704. "Failed to find/init the depth buffer(s) on the associated sensor! Do you want to continue?",
  705. QMessageBox::Yes,
  706. QMessageBox::No) == QMessageBox::No)
  707. {
  708. break;
  709. }
  710. filterVisibility = false;
  711. }
  712. }
  713. pc->enableVisibilityCheck(filterVisibility);
  714. }
  715. //setup parameters
  716. {
  717. c2cParams.octreeLevel = static_cast<unsigned char>(octreeLevel);
  718. if (localModelingTab->isEnabled())
  719. {
  720. c2cParams.localModel = (CCCoreLib::LOCAL_MODEL_TYPES)localModelComboBox->currentIndex();
  721. if (c2cParams.localModel != CCCoreLib::NO_MODEL)
  722. {
  723. c2cParams.useSphericalSearchForLocalModel = lmRadiusRadioButton->isChecked();
  724. c2cParams.kNNForLocalModel = static_cast<unsigned>(std::max(0,lmKNNSpinBox->value()));
  725. c2cParams.radiusForLocalModel = static_cast<ScalarType>(lmRadiusDoubleSpinBox->value());
  726. c2cParams.reuseExistingLocalModels = lmOptimizeCheckBox->isChecked();
  727. }
  728. }
  729. c2cParams.maxSearchDist = maxSearchDist;
  730. c2cParams.multiThread = multiThread;
  731. c2cParams.CPSet = nullptr;
  732. }
  733. result = CCCoreLib::DistanceComputationTools::computeCloud2CloudDistances( m_compCloud,
  734. m_refCloud,
  735. c2cParams,
  736. progressDlg.data(),
  737. m_compOctree.data(),
  738. m_refOctree.data());
  739. break;
  740. case CLOUDMESH_DIST: //cloud-mesh
  741. //setup parameters
  742. {
  743. c2mParams.octreeLevel = static_cast<unsigned char>(octreeLevel);
  744. c2mParams.maxSearchDist = maxSearchDist;
  745. c2mParams.useDistanceMap = false;
  746. c2mParams.signedDistances = signedDistances;
  747. c2mParams.flipNormals = flipNormals;
  748. c2mParams.multiThread = multiThread;
  749. c2mParams.robust = robust;
  750. }
  751. result = CCCoreLib::DistanceComputationTools::computeCloud2MeshDistances( m_compCloud,
  752. m_refMesh,
  753. c2mParams,
  754. progressDlg.data(),
  755. m_compOctree.data());
  756. break;
  757. }
  758. qint64 elapsedTime_ms = eTimer.elapsed();
  759. if (progressDlg)
  760. {
  761. progressDlg->stop();
  762. }
  763. if (result >= 0)
  764. {
  765. ccLog::Print("[ComputeDistances] Time: %3.2f s.", elapsedTime_ms / 1.0e3);
  766. //display some statics about the computed distances
  767. ScalarType mean;
  768. ScalarType variance;
  769. sf->computeMinAndMax();
  770. sf->computeMeanAndVariance(mean, &variance);
  771. ccLog::Print("[ComputeDistances] " + tr("Mean distance = %1 / std deviation = %2").arg(mean).arg(sqrt(variance)));
  772. m_compCloud->setCurrentDisplayedScalarField(sfIdx);
  773. m_compCloud->showSF(sfIdx >= 0);
  774. //restore UI items
  775. okButton->setEnabled(true);
  776. m_sfName.clear();
  777. switch(m_compType)
  778. {
  779. case CLOUDCLOUD_DIST: //hausdorff
  780. m_sfName = QString(CC_CLOUD2CLOUD_DISTANCES_DEFAULT_SF_NAME);
  781. break;
  782. case CLOUDMESH_DIST: //cloud-mesh
  783. m_sfName = QString(signedDistances ? CC_CLOUD2MESH_SIGNED_DISTANCES_DEFAULT_SF_NAME : CC_CLOUD2MESH_DISTANCES_DEFAULT_SF_NAME);
  784. break;
  785. }
  786. if (c2cParams.localModel != CCCoreLib::NO_MODEL)
  787. {
  788. m_sfName += QString("[%1]").arg(localModelComboBox->currentText());
  789. if (c2cParams.useSphericalSearchForLocalModel)
  790. m_sfName += QString("[r=%1]").arg(c2cParams.radiusForLocalModel);
  791. else
  792. m_sfName += QString("[k=%1]").arg(c2cParams.kNNForLocalModel);
  793. if (c2cParams.reuseExistingLocalModels)
  794. m_sfName += QString("[fast]");
  795. }
  796. if (flipNormals)
  797. {
  798. m_sfName += QString("[-]");
  799. }
  800. if (maxSearchDist > 0)
  801. {
  802. m_sfName += QString("[<%1]").arg(maxSearchDist);
  803. }
  804. if (split3D)
  805. {
  806. //we add the corresponding scalar fields (one for each dimension)
  807. for (unsigned j = 0; j < 3; ++j)
  808. {
  809. CCCoreLib::ScalarField* sf = c2cParams.splitDistances[j];
  810. if (sf)
  811. {
  812. static const QChar CharDim[3]{ 'X', 'Y', 'Z' };
  813. QString dimSFName = m_sfName + QString(" (%1)").arg(CharDim[j]);
  814. sf->setName(dimSFName.toStdString());
  815. sf->computeMinAndMax();
  816. //check that SF doesn't already exist
  817. int sfExit = m_compCloud->getScalarFieldIndexByName(sf->getName());
  818. if (sfExit >= 0)
  819. m_compCloud->deleteScalarField(sfExit);
  820. int sfEnter = m_compCloud->addScalarField(static_cast<ccScalarField*>(sf));
  821. assert(sfEnter >= 0);
  822. }
  823. }
  824. ccLog::Warning("[ComputeDistances] Result has been split along each dimension (check the 3 other scalar fields with '_X', '_Y' and '_Z' suffix!)");
  825. if (mergeXY)
  826. {
  827. ccLog::Warning("[ComputeDistances] compute 2D distances (xy plane)");
  828. QString sfNameXY = m_sfName + " (XY)";
  829. int sf2D = m_compCloud->getScalarFieldIndexByName(sfNameXY.toStdString());
  830. if (sf2D < 0)
  831. {
  832. sf2D = m_compCloud->addScalarField(sfNameXY.toStdString());
  833. }
  834. if (sf2D < 0)
  835. {
  836. ccLog::Error("[ComputeDistances] impossible to add XY scalar field");
  837. return 0;
  838. }
  839. CCCoreLib::ScalarField* sf = m_compCloud->getScalarField(sf2D);
  840. for (unsigned idx = 0; idx < m_compCloud->size(); idx++)
  841. {
  842. float d2D = pow(pow(c2cParams.splitDistances[0]->getValue(idx), 2) + pow(c2cParams.splitDistances[1]->getValue(idx), 2), 0.5);
  843. sf->setValue(idx, d2D);
  844. }
  845. sf->computeMinAndMax();
  846. }
  847. }
  848. }
  849. else
  850. {
  851. ccLog::Error("[ComputeDistances] Error (%i)",result);
  852. m_compCloud->deleteScalarField(sfIdx);
  853. m_compCloud->showSF(false);
  854. sfIdx = -1;
  855. }
  856. for (unsigned j = 0; j < 3; ++j)
  857. {
  858. CCCoreLib::ScalarField* &sf = c2cParams.splitDistances[j];
  859. if (sf)
  860. {
  861. sf->release();
  862. sf = nullptr;
  863. }
  864. }
  865. updateDisplay(sfIdx >= 0, false);
  866. return result >= 0;
  867. }
  868. void ccComparisonDlg::showHisto()
  869. {
  870. if (!m_compCloud)
  871. return;
  872. ccScalarField* sf = m_compCloud->getCurrentDisplayedScalarField();
  873. if (!sf)
  874. return;
  875. ccHistogramWindowDlg* hDlg = new ccHistogramWindowDlg(this);
  876. hDlg->setWindowTitle(QString("Histogram [%1]").arg(m_compCloud->getName()));
  877. {
  878. ccHistogramWindow* histogram = hDlg->window();
  879. histogram->setTitle(QString("Approximate distances (%1 values)").arg(m_compCloud->size()));
  880. histogram->fromSF(sf, 8, false);
  881. histogram->setAxisLabels("Approximate distances", "Count");
  882. }
  883. hDlg->resize(400, 300);
  884. hDlg->show();
  885. }
  886. void ccComparisonDlg::applyAndExit()
  887. {
  888. if (m_compCloud)
  889. {
  890. //m_compCloud->setCurrentDisplayedScalarField(-1);
  891. //m_compCloud->showSF(false);
  892. //remove the approximate dist. SF
  893. int tmpSfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME);
  894. if (tmpSfIdx >= 0)
  895. {
  896. m_compCloud->deleteScalarField(tmpSfIdx);
  897. tmpSfIdx = -1;
  898. }
  899. //now, if we have a temp distance scalar field (the 'real' distances computed by the user)
  900. //we should rename it properly
  901. int sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
  902. if (sfIdx >= 0)
  903. {
  904. if (m_sfName.isEmpty()) //hum,hum
  905. {
  906. ccLog::Warning("Something went wrong!");
  907. m_compCloud->deleteScalarField(sfIdx);
  908. m_compCloud->setCurrentDisplayedScalarField(-1);
  909. m_compCloud->showSF(false);
  910. }
  911. else
  912. {
  913. //we delete any existing scalar field with the exact same name
  914. int _sfIdx = m_compCloud->getScalarFieldIndexByName(m_sfName.toStdString());
  915. if (_sfIdx >= 0)
  916. {
  917. m_compCloud->deleteScalarField(_sfIdx);
  918. //we update sfIdx because indexes are all messed up after deletion
  919. sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
  920. }
  921. m_compCloud->renameScalarField(sfIdx,m_sfName.toStdString());
  922. m_compCloud->setCurrentDisplayedScalarField(sfIdx);
  923. m_compCloud->showSF(sfIdx >= 0);
  924. }
  925. }
  926. //m_compCloud->setCurrentDisplayedScalarField(-1);
  927. //m_compCloud->showSF(false);
  928. }
  929. updateDisplay(true, m_refVisibility);
  930. releaseOctrees();
  931. accept();
  932. }
  933. void ccComparisonDlg::cancelAndExit()
  934. {
  935. if (m_compCloud)
  936. {
  937. m_compCloud->setCurrentDisplayedScalarField(-1);
  938. m_compCloud->showSF(false);
  939. //remove the approximate dist. SF
  940. int tmpSfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_APPROX_DISTANCES_DEFAULT_SF_NAME);
  941. if (tmpSfIdx >= 0)
  942. {
  943. m_compCloud->deleteScalarField(tmpSfIdx);
  944. tmpSfIdx = -1;
  945. }
  946. int sfIdx = m_compCloud->getScalarFieldIndexByName(CC_TEMP_DISTANCES_DEFAULT_SF_NAME);
  947. if (sfIdx >= 0)
  948. {
  949. m_compCloud->deleteScalarField(sfIdx);
  950. sfIdx = -1;
  951. }
  952. if (!m_oldSfName.isEmpty())
  953. {
  954. int oldSfIdx = m_compCloud->getScalarFieldIndexByName(m_oldSfName.toStdString());
  955. if (oldSfIdx)
  956. {
  957. m_compCloud->setCurrentDisplayedScalarField(oldSfIdx);
  958. m_compCloud->showSF(oldSfIdx >= 0);
  959. }
  960. }
  961. }
  962. updateDisplay(m_compSFVisibility, m_refVisibility);
  963. releaseOctrees();
  964. reject();
  965. }