ccWaveformDialog.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  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: CNRS / OSUR #
  15. //# #
  16. //##########################################################################
  17. #include "ccWaveformDialog.h"
  18. //Local
  19. #include "ccFileUtils.h"
  20. #include "ccPersistentSettings.h"
  21. #include "ccQCustomPlot.h"
  22. //common
  23. #include <ccPickingHub.h>
  24. //qCC_db
  25. #include <ccPointCloud.h>
  26. #include <ccProgressDialog.h>
  27. //Qt
  28. #include <QCloseEvent>
  29. #include <QSettings>
  30. //System
  31. #include <cassert>
  32. #include <cmath>
  33. //Gui
  34. #include "ui_waveDlg.h"
  35. ccWaveWidget::ccWaveWidget(QWidget* parent/*=nullptr*/)
  36. : QCustomPlot(parent)
  37. , m_titlePlot(nullptr)
  38. , m_curve(nullptr)
  39. , m_dt(0.0)
  40. , m_minA(0.0)
  41. , m_maxA(0.0)
  42. , m_echoPos(-1.0)
  43. , m_vertBar(nullptr)
  44. , m_drawVerticalIndicator(false)
  45. , m_verticalIndicatorPositionPercent(0.0)
  46. , m_peakBar(nullptr)
  47. , m_lastMouseClick(0, 0)
  48. {
  49. setWindowTitle("Waveform");
  50. setFocusPolicy(Qt::StrongFocus);
  51. setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
  52. setAutoAddPlottableToLegend(false);
  53. //default font for text rendering
  54. m_renderingFont.setFamily(QString::fromUtf8("Arial"));
  55. m_renderingFont.setBold(false);
  56. //m_renderingFont.setWeight(75);
  57. // make ticks on bottom axis go outward
  58. assert(xAxis && yAxis);
  59. xAxis->setTickLength(0, 5);
  60. xAxis->setSubTickLength(0, 3);
  61. yAxis->setTickLength(0, 5);
  62. yAxis->setSubTickLength(0, 3);
  63. }
  64. ccWaveWidget::~ccWaveWidget()
  65. {
  66. clearInternal();
  67. }
  68. void ccWaveWidget::clear()
  69. {
  70. clearInternal();
  71. refresh();
  72. }
  73. void ccWaveWidget::clearInternal()
  74. {
  75. m_curveValues.resize(0);
  76. m_dt = 0;
  77. m_minA = m_maxA = 0;
  78. }
  79. void ccWaveWidget::setTitle(const QString& str)
  80. {
  81. m_titleStr = str;
  82. }
  83. void ccWaveWidget::setAxisLabels(const QString& xLabel, const QString& yLabel)
  84. {
  85. if (xLabel.isNull())
  86. {
  87. xAxis->setVisible(false);
  88. }
  89. else
  90. {
  91. // set labels
  92. xAxis->setLabel(xLabel);
  93. xAxis->setVisible(true);
  94. }
  95. if (yLabel.isNull())
  96. {
  97. yAxis->setVisible(false);
  98. }
  99. else
  100. {
  101. // set labels
  102. yAxis->setLabel(yLabel);
  103. yAxis->setVisible(true);
  104. }
  105. }
  106. static double AbsLog(double c) { return (c >= 0 ? log1p(c) : -log1p(-c)); }
  107. void ccWaveWidget::init(ccPointCloud* cloud, unsigned pointIndex, bool logScale, double maxValue/*=0.0*/)
  108. {
  109. clearInternal();
  110. if (!cloud || !cloud->hasFWF())
  111. {
  112. return;
  113. }
  114. const ccWaveformProxy& w = cloud->waveformProxy(pointIndex);
  115. if (!w.isValid())
  116. {
  117. //no valid descriptor
  118. return;
  119. }
  120. if (w.numberOfSamples() == 0)
  121. {
  122. return;
  123. }
  124. try
  125. {
  126. m_curveValues.resize(w.numberOfSamples(), 0);
  127. }
  128. catch (const std::bad_alloc&)
  129. {
  130. //not enough memory
  131. ccLog::Error("Not enough memory");
  132. return;
  133. }
  134. for (uint32_t i = 0; i < w.numberOfSamples(); ++i)
  135. {
  136. double c = w.getSample(i);
  137. if (logScale)
  138. {
  139. c = AbsLog(c);
  140. }
  141. m_curveValues[i] = c;
  142. if (i)
  143. {
  144. m_maxA = std::max(m_maxA, c);
  145. m_minA = std::min(m_minA, c);
  146. }
  147. else
  148. {
  149. m_minA = m_maxA = c;
  150. }
  151. }
  152. if (maxValue != 0)
  153. {
  154. m_maxA = logScale ? AbsLog(maxValue) : maxValue;
  155. }
  156. m_dt = w.descriptor().samplingRate_ps;
  157. m_echoPos = w.echoTime_ps();
  158. }
  159. void ccWaveWidget::refresh()
  160. {
  161. // set ranges appropriate to show data
  162. xAxis->setRange(0, m_curveValues.size() * m_dt);
  163. yAxis->setRange(m_minA, m_maxA);
  164. if (!m_titleStr.isEmpty())
  165. {
  166. // add title layout element
  167. if (!m_titlePlot)
  168. {
  169. //add a row for the title
  170. plotLayout()->insertRow(0);
  171. }
  172. else
  173. {
  174. //remove previous title
  175. plotLayout()->remove(m_titlePlot);
  176. m_titlePlot = nullptr;
  177. }
  178. m_titlePlot = new QCPTextElement(this, m_titleStr);
  179. //title font
  180. m_renderingFont.setPointSize(ccGui::Parameters().defaultFontSize);
  181. m_titlePlot->setFont(m_renderingFont);
  182. plotLayout()->addElement(0, 0, m_titlePlot);
  183. }
  184. //clear previous display
  185. m_vertBar = nullptr;
  186. m_curve = nullptr;
  187. m_peakBar = nullptr;
  188. this->clearGraphs();
  189. this->clearPlottables();
  190. //wave curve
  191. int curveSize = static_cast<int>(m_curveValues.size());
  192. if (curveSize != 0)
  193. {
  194. QVector<double> x(curveSize);
  195. QVector<double> y(curveSize);
  196. for (int i = 0; i < curveSize; ++i)
  197. {
  198. x[i] = i * m_dt;
  199. y[i] = m_curveValues[i];
  200. }
  201. // create graph and assign data to it:
  202. m_curve = addGraph();
  203. m_curve->setData(x, y);
  204. m_curve->setName("WaveCurve");
  205. //set pen color
  206. QPen pen(Qt::blue);
  207. m_curve->setPen(pen);
  208. //set width
  209. updateCurveWidth(rect().width(), rect().height());
  210. }
  211. if (m_drawVerticalIndicator) //vertical hint
  212. {
  213. m_vertBar = new QCPBarsWithText(xAxis, yAxis);
  214. // now we can modify properties of vertBar
  215. m_vertBar->setName("VertLine");
  216. m_vertBar->setWidth(0);
  217. m_vertBar->setBrush(QBrush(Qt::red));
  218. m_vertBar->setPen(QPen(Qt::red));
  219. m_vertBar->setAntialiasedFill(false);
  220. QVector<double> keyData(1);
  221. QVector<double> valueData(1);
  222. //horizontal position
  223. int curvePos = static_cast<int>(curveSize * m_verticalIndicatorPositionPercent);
  224. keyData[0] = curvePos * m_dt;
  225. valueData[0] = m_maxA;
  226. m_vertBar->setData(keyData, valueData);
  227. //precision
  228. QString valueStr = QString("Sample %0").arg(curvePos);
  229. m_vertBar->setText(valueStr);
  230. valueStr = QString("= %0").arg(curvePos < curveSize ? m_curveValues[curvePos] : 0);
  231. m_vertBar->appendText(valueStr);
  232. m_vertBar->setTextAlignment(m_verticalIndicatorPositionPercent > 0.5);
  233. }
  234. if (m_echoPos >= 0)
  235. {
  236. m_peakBar = new QCPBarsWithText(xAxis, yAxis);
  237. // now we can modify properties of vertBar
  238. m_peakBar->setName("PeakLine");
  239. m_peakBar->setWidth(0);
  240. m_peakBar->setBrush(QBrush(Qt::blue));
  241. m_peakBar->setPen(QPen(Qt::blue));
  242. m_peakBar->setAntialiasedFill(false);
  243. QVector<double> keyData(1);
  244. QVector<double> valueData(1);
  245. //horizontal position
  246. keyData[0] = m_echoPos;
  247. valueData[0] = m_maxA;
  248. m_peakBar->setData(keyData, valueData);
  249. //precision
  250. m_peakBar->setText("Peak");
  251. m_peakBar->setTextAlignment(m_echoPos > 0.5 * curveSize * m_dt);
  252. }
  253. //rescaleAxes();
  254. // redraw
  255. replot();
  256. }
  257. void ccWaveWidget::updateCurveWidth(int w, int h)
  258. {
  259. if (m_curve)
  260. {
  261. int penWidth = std::max(w, h) / 200;
  262. if (m_curve->pen().width() != penWidth)
  263. {
  264. QPen pen = m_curve->pen();
  265. pen.setWidth(penWidth);
  266. m_curve->setPen(pen);
  267. }
  268. }
  269. }
  270. void ccWaveWidget::resizeEvent(QResizeEvent * event)
  271. {
  272. QCustomPlot::resizeEvent(event);
  273. updateCurveWidth(event->size().width(), event->size().height());
  274. refresh();
  275. }
  276. void ccWaveWidget::mousePressEvent(QMouseEvent *event)
  277. {
  278. m_lastMouseClick = event->pos();
  279. mouseMoveEvent(event);
  280. }
  281. void ccWaveWidget::mouseMoveEvent(QMouseEvent *event)
  282. {
  283. if (event->buttons() & Qt::LeftButton)
  284. {
  285. if (m_curve && !m_curveValues.empty())
  286. {
  287. QRect roi = /*m_curve->*/rect();
  288. if (roi.contains(event->pos(), false))
  289. {
  290. m_drawVerticalIndicator = true;
  291. m_verticalIndicatorPositionPercent = static_cast<double>(event->x() - roi.x()) / roi.width();
  292. refresh();
  293. }
  294. }
  295. }
  296. else
  297. {
  298. event->ignore();
  299. }
  300. }
  301. ccWaveDialog::ccWaveDialog( ccPointCloud* cloud,
  302. ccPickingHub* pickingHub,
  303. QWidget* parent/*=nullptr*/)
  304. : QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint)
  305. , m_cloud(cloud)
  306. , m_widget(new ccWaveWidget(this))
  307. , m_pickingHub(pickingHub)
  308. , m_gui(new Ui_WaveDialog)
  309. , m_waveMax(0)
  310. , m_label(std::shared_ptr<cc2DLabel>(new cc2DLabel()))
  311. , m_display(cloud ? cloud->getDisplay() : nullptr)
  312. {
  313. m_gui->setupUi(this);
  314. QHBoxLayout* hboxLayout = new QHBoxLayout(m_gui->waveFrame);
  315. hboxLayout->addWidget(m_widget);
  316. hboxLayout->setContentsMargins(0, 0, 0, 0);
  317. m_gui->waveFrame->setLayout(hboxLayout);
  318. if (cloud && cloud->size())
  319. {
  320. m_gui->pointIndexSpinBox->setMaximum(static_cast<int>(cloud->size()));
  321. m_gui->pointIndexSpinBox->setSuffix(QString(" / %1").arg(cloud->size() - 1));
  322. //init m_waveMax
  323. double waveMin = 0;
  324. ccProgressDialog pDlg(parent);
  325. if (cloud->computeFWFAmplitude(waveMin, m_waveMax, &pDlg))
  326. {
  327. ccLog::Print(QString("[ccWaveDialog] Cloud '%1': max FWF amplitude = %2").arg(cloud->getName()).arg(m_waveMax));
  328. }
  329. else
  330. {
  331. ccLog::Warning("[ccWaveDialog] Input cloud has no valid FWF data");
  332. }
  333. }
  334. connect(m_gui->pointIndexSpinBox, qOverload<int>(&QSpinBox::valueChanged), this, &ccWaveDialog::onPointIndexChanged);
  335. connect(m_gui->logScaleCheckBox, &QCheckBox::toggled, this, &ccWaveDialog::updateCurrentWaveform);
  336. connect(m_gui->fixedAmplitudeCheckBox, &QCheckBox::toggled, this, &ccWaveDialog::updateCurrentWaveform);
  337. connect(m_gui->pointPickingToolButton, &QToolButton::toggled, this, &ccWaveDialog::onPointPickingButtonToggled);
  338. connect(m_gui->saveWaveToolButton, &QToolButton::clicked, this, &ccWaveDialog::onExportWaveAsCSV);
  339. connect(this, &QDialog::finished, [&]() { m_gui->pointPickingToolButton->setChecked(false); }); //auto disable picking mode when the dialog is closed
  340. //force update
  341. onPointIndexChanged(0);
  342. }
  343. ccWaveDialog::~ccWaveDialog()
  344. {
  345. delete m_gui;
  346. if (m_display)
  347. m_display->redraw();
  348. }
  349. void ccWaveDialog::onPointIndexChanged(int index)
  350. {
  351. if (!m_widget)
  352. {
  353. assert(false);
  354. return;
  355. }
  356. if (index < 0 || !m_cloud)
  357. {
  358. assert(false);
  359. return;
  360. }
  361. m_widget->init(m_cloud, static_cast<unsigned>(index), m_gui->logScaleCheckBox->isChecked(), m_gui->fixedAmplitudeCheckBox->isChecked() ? m_waveMax : 0.0);
  362. add2DLabel(m_cloud, index);
  363. m_widget->refresh();
  364. }
  365. void ccWaveDialog::add2DLabel(ccPointCloud* cloud, unsigned pointIndex)
  366. {
  367. ccGLCameraParameters camera;
  368. if (m_display == nullptr)
  369. return;
  370. m_display->getGLCameraParameters(camera);
  371. const CCVector3& P = *cloud->getPoint(pointIndex);
  372. CCVector3d Y;
  373. camera.project(P, Y);
  374. Y.y = m_display->getScreenSize().height() - Y.y;
  375. m_label->clear();
  376. m_label->addPickedPoint(cloud, pointIndex, false);
  377. m_label->setVisible(true);
  378. m_label->setPosition(static_cast<float>(Y.x + 20) / m_display->getScreenSize().width(),
  379. static_cast<float>(Y.y + 20) / m_display->getScreenSize().height());
  380. m_label->setDisplayedIn2D(true);
  381. m_label->displayPointLegend(false);
  382. m_label->setDisplay(m_display);
  383. if (!cloud->find(m_label->getUniqueID()))
  384. {
  385. cloud->addChild(m_label.get());
  386. }
  387. m_display->redraw();
  388. return;
  389. }
  390. void ccWaveDialog::onItemPicked(const PickedItem& pi)
  391. {
  392. if (pi.entity == m_cloud)
  393. {
  394. assert(!pi.entityCenter);
  395. m_gui->pointIndexSpinBox->setValue(static_cast<int>(pi.itemIndex));
  396. }
  397. }
  398. void ccWaveDialog::updateCurrentWaveform()
  399. {
  400. onPointIndexChanged(m_gui->pointIndexSpinBox->value());
  401. }
  402. void ccWaveDialog::onPointPickingButtonToggled(bool state)
  403. {
  404. if (!m_pickingHub)
  405. {
  406. assert(false);
  407. return;
  408. }
  409. if (state)
  410. {
  411. if (!m_pickingHub->addListener(this))
  412. {
  413. ccLog::Error("Another tool is currently using the point picking mechanism.\nYou'll have to close it first.");
  414. m_gui->pointPickingToolButton->blockSignals(true);
  415. m_gui->pointPickingToolButton->setChecked(false);
  416. m_gui->pointPickingToolButton->blockSignals(false);
  417. return;
  418. }
  419. }
  420. else
  421. {
  422. m_pickingHub->removeListener(this);
  423. }
  424. }
  425. void ccWaveDialog::onExportWaveAsCSV()
  426. {
  427. if (!m_cloud)
  428. {
  429. assert(false);
  430. return;
  431. }
  432. int pointIndex = m_gui->pointIndexSpinBox->value();
  433. if (pointIndex >= static_cast<int>(m_cloud->waveforms().size()))
  434. {
  435. assert(false);
  436. return;
  437. }
  438. const ccWaveformProxy& w = m_cloud->waveformProxy(pointIndex);
  439. if (!w.isValid())
  440. {
  441. //no valid descriptor
  442. return;
  443. }
  444. if (w.numberOfSamples() == 0)
  445. {
  446. //nothing to do
  447. return;
  448. }
  449. //persistent settings
  450. QSettings settings;
  451. settings.beginGroup(ccPS::SaveFile());
  452. QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
  453. currentPath += QString("/") + QString("waveform_%1.csv").arg(pointIndex);
  454. //ask for a filename
  455. QString filename = QFileDialog::getSaveFileName(this, "Select output file", currentPath, "*.csv");
  456. if (filename.isEmpty())
  457. {
  458. //process cancelled by user
  459. return;
  460. }
  461. //save last saving location
  462. settings.setValue(ccPS::CurrentPath(), QFileInfo(filename).absolutePath());
  463. settings.endGroup();
  464. //save file
  465. w.toASCII(filename);
  466. }