ccSubsamplingDlg.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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 "ccSubsamplingDlg.h"
  18. #include "ui_subsamplingDlg.h"
  19. //CCCoreLib
  20. #include <CloudSamplingTools.h>
  21. #include <ScalarField.h>
  22. //qCC_db
  23. #include <ccGenericPointCloud.h>
  24. //Qt
  25. #include <QSettings>
  26. //Exponent of the 'log' scale used for 'SPATIAL' interval
  27. static const double SPACE_RANGE_EXPONENT = 0.05;
  28. ccSubsamplingDlg::ccSubsamplingDlg(unsigned maxPointCount, double maxCloudRadius, QWidget* parent/*=nullptr*/)
  29. : QDialog(parent, Qt::Tool)
  30. , m_maxPointCount(maxPointCount)
  31. , m_maxRadius(maxCloudRadius)
  32. , m_sfModEnabled(false)
  33. , m_sfMin(0)
  34. , m_sfMax(0)
  35. , m_ui( new Ui::SubsamplingDialog )
  36. {
  37. m_ui->setupUi(this);
  38. m_ui->samplingMethodComboBox->addItem( tr( "Random" ) );
  39. m_ui->samplingMethodComboBox->addItem( tr( "Random (%)" ) );
  40. m_ui->samplingMethodComboBox->addItem( tr( "Spatial" ) );
  41. m_ui->samplingMethodComboBox->addItem( tr( "Octree" ) );
  42. connect(m_ui->slider, &QSlider::sliderMoved, this, &ccSubsamplingDlg::sliderMoved);
  43. connect(m_ui->valueDoubleSpinBox, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &ccSubsamplingDlg::valueChanged);
  44. connect(m_ui->samplingMethodComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &ccSubsamplingDlg::changeSamplingMethod);
  45. m_ui->samplingMethodComboBox->setCurrentIndex(SPATIAL);
  46. sliderMoved(m_ui->slider->sliderPosition());
  47. // Init the 'last used values' (used when switching from one method to another)
  48. m_lastUsedValues[RANDOM] = static_cast<double>(maxPointCount);
  49. m_lastUsedValues[RANDOM_PERCENT] = 100.0;
  50. m_lastUsedValues[SPATIAL] = maxCloudRadius;
  51. m_lastUsedValues[OCTREE] = static_cast<double>(CCCoreLib::DgmOctree::MAX_OCTREE_LEVEL);
  52. }
  53. ccSubsamplingDlg::~ccSubsamplingDlg()
  54. {
  55. delete m_ui;
  56. }
  57. CCCoreLib::ReferenceCloud* ccSubsamplingDlg::getSampledCloud(ccGenericPointCloud* cloud, CCCoreLib::GenericProgressCallback* progressCb/*=nullptr*/)
  58. {
  59. if (!cloud || cloud->size() == 0)
  60. {
  61. ccLog::Warning("[ccSubsamplingDlg::getSampledCloud] Invalid input cloud!");
  62. return nullptr;
  63. }
  64. switch (m_ui->samplingMethodComboBox->currentIndex())
  65. {
  66. case RANDOM:
  67. {
  68. assert(m_ui->valueDoubleSpinBox->value() >= 0);
  69. unsigned count = static_cast<unsigned>(m_ui->valueDoubleSpinBox->value());
  70. return CCCoreLib::CloudSamplingTools::subsampleCloudRandomly( cloud,
  71. count,
  72. progressCb);
  73. }
  74. break;
  75. case RANDOM_PERCENT:
  76. {
  77. assert(m_ui->valueDoubleSpinBox->value() >= 0);
  78. unsigned count = cloud->size();
  79. count = static_cast<unsigned>(count * (m_ui->valueDoubleSpinBox->value() / 100.0));
  80. return CCCoreLib::CloudSamplingTools::subsampleCloudRandomly( cloud,
  81. count,
  82. progressCb);
  83. }
  84. break;
  85. case SPATIAL:
  86. {
  87. ccOctree::Shared octree = cloud->getOctree();
  88. if (!octree)
  89. {
  90. octree = cloud->computeOctree(progressCb);
  91. }
  92. if (octree)
  93. {
  94. PointCoordinateType minDist = static_cast<PointCoordinateType>(m_ui->valueDoubleSpinBox->value());
  95. CCCoreLib::CloudSamplingTools::SFModulationParams modParams;
  96. {
  97. modParams.enabled = m_ui->sfGroupBox->isEnabled() && m_ui->sfGroupBox->isChecked();
  98. }
  99. if (modParams.enabled)
  100. {
  101. double deltaSF = static_cast<double>(m_sfMax) - m_sfMin;
  102. assert(deltaSF >= 0);
  103. if ( CCCoreLib::GreaterThanEpsilon( deltaSF ) )
  104. {
  105. double sfMinSpacing = m_ui->minSFSpacingDoubleSpinBox->value();
  106. double sfMaxSpacing = m_ui->maxSFSpacingDoubleSpinBox->value();
  107. modParams.a = (sfMaxSpacing - sfMinSpacing) / deltaSF;
  108. modParams.b = sfMinSpacing - modParams.a * m_sfMin;
  109. }
  110. else
  111. {
  112. modParams.a = 0.0;
  113. modParams.b = m_sfMin;
  114. }
  115. }
  116. return CCCoreLib::CloudSamplingTools::resampleCloudSpatially( cloud,
  117. minDist,
  118. modParams,
  119. octree.data(),
  120. progressCb);
  121. }
  122. else
  123. {
  124. ccLog::Warning(QString("[ccSubsamplingDlg::getSampledCloud] Failed to compute octree for cloud '%1'").arg(cloud->getName()));
  125. }
  126. }
  127. break;
  128. case OCTREE:
  129. {
  130. ccOctree::Shared octree = cloud->getOctree();
  131. if (!octree)
  132. octree = cloud->computeOctree(progressCb);
  133. if (octree)
  134. {
  135. assert(m_ui->valueDoubleSpinBox->value() >= 0);
  136. unsigned char level = static_cast<unsigned char>(m_ui->valueDoubleSpinBox->value());
  137. assert(level <= CCCoreLib::DgmOctree::MAX_OCTREE_LEVEL);
  138. return CCCoreLib::CloudSamplingTools::subsampleCloudWithOctreeAtLevel( cloud,
  139. level,
  140. CCCoreLib::CloudSamplingTools::NEAREST_POINT_TO_CELL_CENTER,
  141. progressCb,
  142. octree.data());
  143. }
  144. else
  145. {
  146. ccLog::Warning(QString("[ccSubsamplingDlg::getSampledCloud] Failed to compute octree for cloud '%1'").arg(cloud->getName()));
  147. }
  148. }
  149. break;
  150. }
  151. //something went wrong!
  152. return nullptr;
  153. }
  154. void ccSubsamplingDlg::updateLabels()
  155. {
  156. switch(m_ui->samplingMethodComboBox->currentIndex())
  157. {
  158. case RANDOM:
  159. m_ui->labelSliderMin->setText( tr( "none" ) );
  160. m_ui->labelSliderMax->setText( tr( "all" ) );
  161. m_ui->valueLabel->setText( tr( "remaining points" ) );
  162. break;
  163. case RANDOM_PERCENT:
  164. m_ui->labelSliderMin->setText( tr( "none" ) );
  165. m_ui->labelSliderMax->setText( tr( "all" ) );
  166. m_ui->valueLabel->setText(tr("remaining points") + "(%)" );
  167. break;
  168. case SPATIAL:
  169. m_ui->labelSliderMin->setText( tr( "large" ) );
  170. m_ui->labelSliderMax->setText( tr( "small" ) );
  171. m_ui->valueLabel->setText( tr( "min. space between points" ) );
  172. break;
  173. case OCTREE:
  174. m_ui->labelSliderMin->setText( tr( "min" ) );
  175. m_ui->labelSliderMax->setText( tr( "max" ) );
  176. m_ui->valueLabel->setText( tr( "subdivision level" ) );
  177. break;
  178. default:
  179. break;
  180. }
  181. }
  182. void ccSubsamplingDlg::sliderMoved(int sliderPos)
  183. {
  184. double sliderRange = static_cast<double>(m_ui->slider->maximum() - m_ui->slider->minimum());
  185. double rate = (sliderPos - m_ui->slider->minimum()) / sliderRange;
  186. if (m_ui->samplingMethodComboBox->currentIndex() == SPATIAL)
  187. {
  188. rate = pow(rate, SPACE_RANGE_EXPONENT);
  189. rate = 1.0 - rate;
  190. }
  191. double valueRange = static_cast<double>(m_ui->valueDoubleSpinBox->maximum() - m_ui->valueDoubleSpinBox->minimum());
  192. double newValue = m_ui->valueDoubleSpinBox->minimum() + rate * valueRange;
  193. m_ui->valueDoubleSpinBox->setValue(newValue);
  194. }
  195. void ccSubsamplingDlg::valueChanged(double value)
  196. {
  197. double valueRange = static_cast<double>(m_ui->valueDoubleSpinBox->maximum() - m_ui->valueDoubleSpinBox->minimum());
  198. double rate = (value - m_ui->valueDoubleSpinBox->minimum()) / valueRange;
  199. if (m_ui->samplingMethodComboBox->currentIndex() == SPATIAL)
  200. {
  201. rate = 1.0 - rate;
  202. rate = pow(rate, 1.0 / SPACE_RANGE_EXPONENT);
  203. if (m_sfModEnabled && !m_ui->sfGroupBox->isChecked())
  204. {
  205. m_ui->minSFSpacingDoubleSpinBox->setValue(value);
  206. m_ui->maxSFSpacingDoubleSpinBox->setValue(value);
  207. }
  208. }
  209. double sliderRange = static_cast<double>(m_ui->slider->maximum() - m_ui->slider->minimum());
  210. int newSliderPos = m_ui->slider->minimum() + static_cast<int>(rate * sliderRange);
  211. //remember the last used value
  212. m_lastUsedValues[m_ui->samplingMethodComboBox->currentIndex()] = value;
  213. m_ui->slider->blockSignals(true);
  214. m_ui->slider->setSliderPosition(newSliderPos);
  215. m_ui->slider->blockSignals(false);
  216. }
  217. void ccSubsamplingDlg::changeSamplingMethod(int index)
  218. {
  219. m_ui->sfGroupBox->setEnabled(false);
  220. //update the labels
  221. m_ui->valueDoubleSpinBox->blockSignals(true);
  222. switch (index)
  223. {
  224. case RANDOM:
  225. {
  226. m_ui->valueDoubleSpinBox->setDecimals(0);
  227. m_ui->valueDoubleSpinBox->setMinimum(1.0);
  228. m_ui->valueDoubleSpinBox->setMaximum(static_cast<double>(m_maxPointCount));
  229. m_ui->valueDoubleSpinBox->setSingleStep(1.0);
  230. m_ui->valueDoubleSpinBox->setEnabled(true);
  231. m_ui->valueDoubleSpinBox->setSuffix(QString());
  232. }
  233. break;
  234. case RANDOM_PERCENT:
  235. {
  236. m_ui->valueDoubleSpinBox->setDecimals(3);
  237. m_ui->valueDoubleSpinBox->setSuffix("%");
  238. m_ui->valueDoubleSpinBox->setMinimum(0.0);
  239. m_ui->valueDoubleSpinBox->setMaximum(100.0);
  240. m_ui->valueDoubleSpinBox->setSingleStep(1.0);
  241. m_ui->valueDoubleSpinBox->setEnabled(true);
  242. }
  243. break;
  244. case SPATIAL:
  245. {
  246. m_ui->valueDoubleSpinBox->setDecimals(4);
  247. m_ui->valueDoubleSpinBox->setMinimum(0.0);
  248. m_ui->valueDoubleSpinBox->setMaximum(m_maxRadius);
  249. double step = m_maxRadius / 1000.0;
  250. m_ui->valueDoubleSpinBox->setSingleStep(step);
  251. m_ui->minSFSpacingDoubleSpinBox->setMaximum(m_maxRadius);
  252. m_ui->minSFSpacingDoubleSpinBox->setSingleStep(step);
  253. m_ui->maxSFSpacingDoubleSpinBox->setMaximum(m_maxRadius);
  254. m_ui->maxSFSpacingDoubleSpinBox->setSingleStep(step);
  255. m_ui->sfGroupBox->setEnabled(m_sfModEnabled);
  256. m_ui->valueDoubleSpinBox->setDisabled(m_ui->sfGroupBox->isEnabled() && m_ui->sfGroupBox->isChecked());
  257. m_ui->valueDoubleSpinBox->setSuffix(QString());
  258. }
  259. break;
  260. case OCTREE:
  261. {
  262. m_ui->valueDoubleSpinBox->setDecimals(0);
  263. m_ui->valueDoubleSpinBox->setMinimum(1.0);
  264. m_ui->valueDoubleSpinBox->setMaximum(static_cast<double>(CCCoreLib::DgmOctree::MAX_OCTREE_LEVEL));
  265. m_ui->valueDoubleSpinBox->setSingleStep(1.0);
  266. m_ui->valueDoubleSpinBox->setEnabled(true);
  267. m_ui->valueDoubleSpinBox->setSuffix(QString());
  268. }
  269. break;
  270. default:
  271. break;
  272. }
  273. m_ui->valueDoubleSpinBox->setValue(m_lastUsedValues[index]);
  274. m_ui->valueDoubleSpinBox->blockSignals(false);
  275. // force the updates of the labels and the sliders
  276. updateLabels();
  277. valueChanged(m_ui->valueDoubleSpinBox->value());
  278. }
  279. void ccSubsamplingDlg::enableSFModulation(ScalarType sfMin, ScalarType sfMax)
  280. {
  281. m_sfModEnabled = CCCoreLib::ScalarField::ValidValue(sfMin) && CCCoreLib::ScalarField::ValidValue(sfMax);
  282. if (!m_sfModEnabled)
  283. {
  284. ccLog::Warning("[ccSubsamplingDlg::enableSFModulation] Invalid input SF values");
  285. return;
  286. }
  287. if (sfMin == sfMax)
  288. {
  289. m_sfModEnabled = false;
  290. ccLog::Warning("[ccSubsamplingDlg::enableSFModulation] Can't modulate sampling if all scalar values are the same");
  291. return;
  292. }
  293. m_sfMin = sfMin;
  294. m_sfMax = sfMax;
  295. m_ui->sfGroupBox->setEnabled(m_ui->samplingMethodComboBox->currentIndex() == SPATIAL);
  296. m_ui->minSFlabel->setText(QString::number(sfMin));
  297. m_ui->maxSFlabel->setText(QString::number(sfMax));
  298. }
  299. void ccSubsamplingDlg::saveToPersistentSettings() const
  300. {
  301. QSettings settings;
  302. settings.beginGroup("SubsamplingDialog");
  303. {
  304. settings.setValue("method", m_ui->samplingMethodComboBox->currentIndex());
  305. settings.setValue("value", m_ui->valueDoubleSpinBox->value());
  306. settings.setValue("useActiveSF", m_ui->sfGroupBox->isChecked());
  307. settings.setValue("minSFRatio", m_ui->minSFSpacingDoubleSpinBox->value());
  308. settings.setValue("maxSFRatio", m_ui->maxSFSpacingDoubleSpinBox->value());
  309. }
  310. settings.endGroup();
  311. }
  312. void ccSubsamplingDlg::loadFromPersistentSettings()
  313. {
  314. QSettings settings;
  315. settings.beginGroup("SubsamplingDialog");
  316. {
  317. int methodIndex = settings.value("method", m_ui->samplingMethodComboBox->currentIndex()).toInt();
  318. double value = settings.value("value", m_ui->valueDoubleSpinBox->value()).toDouble();
  319. bool useActiveSF = settings.value("useActiveSF", m_ui->sfGroupBox->isChecked()).toBool();
  320. double minSFRatio = settings.value("minSFRatio", m_ui->minSFSpacingDoubleSpinBox->value()).toDouble();
  321. double maxSFRatio = settings.value("maxSFRatio", m_ui->maxSFSpacingDoubleSpinBox->value()).toDouble();
  322. // force the update of the dialog
  323. m_ui->samplingMethodComboBox->blockSignals(true);
  324. m_ui->samplingMethodComboBox->setCurrentIndex(methodIndex);
  325. m_ui->samplingMethodComboBox->blockSignals(false);
  326. changeSamplingMethod(methodIndex);
  327. m_ui->valueDoubleSpinBox->setValue(value);
  328. m_ui->sfGroupBox->setChecked(useActiveSF);
  329. m_ui->minSFSpacingDoubleSpinBox->setValue(minSFRatio);
  330. m_ui->maxSFSpacingDoubleSpinBox->setValue(maxSFRatio);
  331. }
  332. settings.endGroup();
  333. }