| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551 |
- //##########################################################################
- //# #
- //# 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: CNRS / OSUR #
- //# #
- //##########################################################################
- #include "ccWaveformDialog.h"
- //Local
- #include "ccFileUtils.h"
- #include "ccPersistentSettings.h"
- #include "ccQCustomPlot.h"
- //common
- #include <ccPickingHub.h>
- //qCC_db
- #include <ccPointCloud.h>
- #include <ccProgressDialog.h>
- //Qt
- #include <QCloseEvent>
- #include <QSettings>
- //System
- #include <cassert>
- #include <cmath>
- //Gui
- #include "ui_waveDlg.h"
- ccWaveWidget::ccWaveWidget(QWidget* parent/*=nullptr*/)
- : QCustomPlot(parent)
- , m_titlePlot(nullptr)
- , m_curve(nullptr)
- , m_dt(0.0)
- , m_minA(0.0)
- , m_maxA(0.0)
- , m_echoPos(-1.0)
- , m_vertBar(nullptr)
- , m_drawVerticalIndicator(false)
- , m_verticalIndicatorPositionPercent(0.0)
- , m_peakBar(nullptr)
- , m_lastMouseClick(0, 0)
- {
- setWindowTitle("Waveform");
- setFocusPolicy(Qt::StrongFocus);
- setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
- setAutoAddPlottableToLegend(false);
- //default font for text rendering
- m_renderingFont.setFamily(QString::fromUtf8("Arial"));
- m_renderingFont.setBold(false);
- //m_renderingFont.setWeight(75);
- // make ticks on bottom axis go outward
- assert(xAxis && yAxis);
- xAxis->setTickLength(0, 5);
- xAxis->setSubTickLength(0, 3);
- yAxis->setTickLength(0, 5);
- yAxis->setSubTickLength(0, 3);
- }
- ccWaveWidget::~ccWaveWidget()
- {
- clearInternal();
- }
- void ccWaveWidget::clear()
- {
- clearInternal();
- refresh();
- }
- void ccWaveWidget::clearInternal()
- {
- m_curveValues.resize(0);
- m_dt = 0;
- m_minA = m_maxA = 0;
- }
- void ccWaveWidget::setTitle(const QString& str)
- {
- m_titleStr = str;
- }
- void ccWaveWidget::setAxisLabels(const QString& xLabel, const QString& yLabel)
- {
- if (xLabel.isNull())
- {
- xAxis->setVisible(false);
- }
- else
- {
- // set labels
- xAxis->setLabel(xLabel);
- xAxis->setVisible(true);
- }
- if (yLabel.isNull())
- {
- yAxis->setVisible(false);
- }
- else
- {
- // set labels
- yAxis->setLabel(yLabel);
- yAxis->setVisible(true);
- }
- }
- static double AbsLog(double c) { return (c >= 0 ? log1p(c) : -log1p(-c)); }
- void ccWaveWidget::init(ccPointCloud* cloud, unsigned pointIndex, bool logScale, double maxValue/*=0.0*/)
- {
- clearInternal();
- if (!cloud || !cloud->hasFWF())
- {
- return;
- }
- const ccWaveformProxy& w = cloud->waveformProxy(pointIndex);
- if (!w.isValid())
- {
- //no valid descriptor
- return;
- }
- if (w.numberOfSamples() == 0)
- {
- return;
- }
- try
- {
- m_curveValues.resize(w.numberOfSamples(), 0);
- }
- catch (const std::bad_alloc&)
- {
- //not enough memory
- ccLog::Error("Not enough memory");
- return;
- }
- for (uint32_t i = 0; i < w.numberOfSamples(); ++i)
- {
- double c = w.getSample(i);
- if (logScale)
- {
- c = AbsLog(c);
- }
- m_curveValues[i] = c;
- if (i)
- {
- m_maxA = std::max(m_maxA, c);
- m_minA = std::min(m_minA, c);
- }
- else
- {
- m_minA = m_maxA = c;
- }
- }
- if (maxValue != 0)
- {
- m_maxA = logScale ? AbsLog(maxValue) : maxValue;
- }
- m_dt = w.descriptor().samplingRate_ps;
- m_echoPos = w.echoTime_ps();
- }
- void ccWaveWidget::refresh()
- {
- // set ranges appropriate to show data
- xAxis->setRange(0, m_curveValues.size() * m_dt);
- yAxis->setRange(m_minA, m_maxA);
- if (!m_titleStr.isEmpty())
- {
- // add title layout element
- if (!m_titlePlot)
- {
- //add a row for the title
- plotLayout()->insertRow(0);
- }
- else
- {
- //remove previous title
- plotLayout()->remove(m_titlePlot);
- m_titlePlot = nullptr;
- }
- m_titlePlot = new QCPTextElement(this, m_titleStr);
-
- //title font
- m_renderingFont.setPointSize(ccGui::Parameters().defaultFontSize);
- m_titlePlot->setFont(m_renderingFont);
- plotLayout()->addElement(0, 0, m_titlePlot);
- }
- //clear previous display
- m_vertBar = nullptr;
- m_curve = nullptr;
- m_peakBar = nullptr;
- this->clearGraphs();
- this->clearPlottables();
- //wave curve
- int curveSize = static_cast<int>(m_curveValues.size());
- if (curveSize != 0)
- {
- QVector<double> x(curveSize);
- QVector<double> y(curveSize);
-
- for (int i = 0; i < curveSize; ++i)
- {
- x[i] = i * m_dt;
- y[i] = m_curveValues[i];
- }
- // create graph and assign data to it:
- m_curve = addGraph();
- m_curve->setData(x, y);
- m_curve->setName("WaveCurve");
- //set pen color
- QPen pen(Qt::blue);
- m_curve->setPen(pen);
- //set width
- updateCurveWidth(rect().width(), rect().height());
- }
-
- if (m_drawVerticalIndicator) //vertical hint
- {
- m_vertBar = new QCPBarsWithText(xAxis, yAxis);
-
- // now we can modify properties of vertBar
- m_vertBar->setName("VertLine");
- m_vertBar->setWidth(0);
- m_vertBar->setBrush(QBrush(Qt::red));
- m_vertBar->setPen(QPen(Qt::red));
- m_vertBar->setAntialiasedFill(false);
- QVector<double> keyData(1);
- QVector<double> valueData(1);
- //horizontal position
- int curvePos = static_cast<int>(curveSize * m_verticalIndicatorPositionPercent);
- keyData[0] = curvePos * m_dt;
- valueData[0] = m_maxA;
- m_vertBar->setData(keyData, valueData);
- //precision
- QString valueStr = QString("Sample %0").arg(curvePos);
- m_vertBar->setText(valueStr);
- valueStr = QString("= %0").arg(curvePos < curveSize ? m_curveValues[curvePos] : 0);
- m_vertBar->appendText(valueStr);
- m_vertBar->setTextAlignment(m_verticalIndicatorPositionPercent > 0.5);
- }
- if (m_echoPos >= 0)
- {
- m_peakBar = new QCPBarsWithText(xAxis, yAxis);
- // now we can modify properties of vertBar
- m_peakBar->setName("PeakLine");
- m_peakBar->setWidth(0);
- m_peakBar->setBrush(QBrush(Qt::blue));
- m_peakBar->setPen(QPen(Qt::blue));
- m_peakBar->setAntialiasedFill(false);
- QVector<double> keyData(1);
- QVector<double> valueData(1);
- //horizontal position
- keyData[0] = m_echoPos;
- valueData[0] = m_maxA;
- m_peakBar->setData(keyData, valueData);
- //precision
- m_peakBar->setText("Peak");
- m_peakBar->setTextAlignment(m_echoPos > 0.5 * curveSize * m_dt);
- }
- //rescaleAxes();
- // redraw
- replot();
- }
- void ccWaveWidget::updateCurveWidth(int w, int h)
- {
- if (m_curve)
- {
- int penWidth = std::max(w, h) / 200;
- if (m_curve->pen().width() != penWidth)
- {
- QPen pen = m_curve->pen();
- pen.setWidth(penWidth);
- m_curve->setPen(pen);
- }
- }
- }
- void ccWaveWidget::resizeEvent(QResizeEvent * event)
- {
- QCustomPlot::resizeEvent(event);
- updateCurveWidth(event->size().width(), event->size().height());
-
- refresh();
- }
- void ccWaveWidget::mousePressEvent(QMouseEvent *event)
- {
- m_lastMouseClick = event->pos();
- mouseMoveEvent(event);
- }
- void ccWaveWidget::mouseMoveEvent(QMouseEvent *event)
- {
- if (event->buttons() & Qt::LeftButton)
- {
- if (m_curve && !m_curveValues.empty())
- {
- QRect roi = /*m_curve->*/rect();
- if (roi.contains(event->pos(), false))
- {
- m_drawVerticalIndicator = true;
- m_verticalIndicatorPositionPercent = static_cast<double>(event->x() - roi.x()) / roi.width();
- refresh();
- }
- }
- }
- else
- {
- event->ignore();
- }
- }
- ccWaveDialog::ccWaveDialog( ccPointCloud* cloud,
- ccPickingHub* pickingHub,
- QWidget* parent/*=nullptr*/)
- : QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint)
- , m_cloud(cloud)
- , m_widget(new ccWaveWidget(this))
- , m_pickingHub(pickingHub)
- , m_gui(new Ui_WaveDialog)
- , m_waveMax(0)
- , m_label(std::shared_ptr<cc2DLabel>(new cc2DLabel()))
- , m_display(cloud ? cloud->getDisplay() : nullptr)
- {
- m_gui->setupUi(this);
-
- QHBoxLayout* hboxLayout = new QHBoxLayout(m_gui->waveFrame);
- hboxLayout->addWidget(m_widget);
- hboxLayout->setContentsMargins(0, 0, 0, 0);
- m_gui->waveFrame->setLayout(hboxLayout);
- if (cloud && cloud->size())
- {
- m_gui->pointIndexSpinBox->setMaximum(static_cast<int>(cloud->size()));
- m_gui->pointIndexSpinBox->setSuffix(QString(" / %1").arg(cloud->size() - 1));
- //init m_waveMax
- double waveMin = 0;
- ccProgressDialog pDlg(parent);
- if (cloud->computeFWFAmplitude(waveMin, m_waveMax, &pDlg))
- {
- ccLog::Print(QString("[ccWaveDialog] Cloud '%1': max FWF amplitude = %2").arg(cloud->getName()).arg(m_waveMax));
- }
- else
- {
- ccLog::Warning("[ccWaveDialog] Input cloud has no valid FWF data");
- }
- }
- connect(m_gui->pointIndexSpinBox, qOverload<int>(&QSpinBox::valueChanged), this, &ccWaveDialog::onPointIndexChanged);
- connect(m_gui->logScaleCheckBox, &QCheckBox::toggled, this, &ccWaveDialog::updateCurrentWaveform);
- connect(m_gui->fixedAmplitudeCheckBox, &QCheckBox::toggled, this, &ccWaveDialog::updateCurrentWaveform);
- connect(m_gui->pointPickingToolButton, &QToolButton::toggled, this, &ccWaveDialog::onPointPickingButtonToggled);
- connect(m_gui->saveWaveToolButton, &QToolButton::clicked, this, &ccWaveDialog::onExportWaveAsCSV);
- connect(this, &QDialog::finished, [&]() { m_gui->pointPickingToolButton->setChecked(false); }); //auto disable picking mode when the dialog is closed
- //force update
- onPointIndexChanged(0);
- }
- ccWaveDialog::~ccWaveDialog()
- {
- delete m_gui;
- if (m_display)
- m_display->redraw();
- }
- void ccWaveDialog::onPointIndexChanged(int index)
- {
- if (!m_widget)
- {
- assert(false);
- return;
- }
- if (index < 0 || !m_cloud)
- {
- assert(false);
- return;
- }
- m_widget->init(m_cloud, static_cast<unsigned>(index), m_gui->logScaleCheckBox->isChecked(), m_gui->fixedAmplitudeCheckBox->isChecked() ? m_waveMax : 0.0);
- add2DLabel(m_cloud, index);
- m_widget->refresh();
- }
- void ccWaveDialog::add2DLabel(ccPointCloud* cloud, unsigned pointIndex)
- {
- ccGLCameraParameters camera;
- if (m_display == nullptr)
- return;
- m_display->getGLCameraParameters(camera);
- const CCVector3& P = *cloud->getPoint(pointIndex);
- CCVector3d Y;
- camera.project(P, Y);
- Y.y = m_display->getScreenSize().height() - Y.y;
- m_label->clear();
- m_label->addPickedPoint(cloud, pointIndex, false);
- m_label->setVisible(true);
- m_label->setPosition(static_cast<float>(Y.x + 20) / m_display->getScreenSize().width(),
- static_cast<float>(Y.y + 20) / m_display->getScreenSize().height());
- m_label->setDisplayedIn2D(true);
- m_label->displayPointLegend(false);
- m_label->setDisplay(m_display);
- if (!cloud->find(m_label->getUniqueID()))
- {
- cloud->addChild(m_label.get());
- }
- m_display->redraw();
- return;
- }
- void ccWaveDialog::onItemPicked(const PickedItem& pi)
- {
- if (pi.entity == m_cloud)
- {
- assert(!pi.entityCenter);
- m_gui->pointIndexSpinBox->setValue(static_cast<int>(pi.itemIndex));
- }
- }
- void ccWaveDialog::updateCurrentWaveform()
- {
- onPointIndexChanged(m_gui->pointIndexSpinBox->value());
- }
- void ccWaveDialog::onPointPickingButtonToggled(bool state)
- {
- if (!m_pickingHub)
- {
- assert(false);
- return;
- }
- if (state)
- {
- if (!m_pickingHub->addListener(this))
- {
- ccLog::Error("Another tool is currently using the point picking mechanism.\nYou'll have to close it first.");
- m_gui->pointPickingToolButton->blockSignals(true);
- m_gui->pointPickingToolButton->setChecked(false);
- m_gui->pointPickingToolButton->blockSignals(false);
- return;
- }
- }
- else
- {
- m_pickingHub->removeListener(this);
- }
- }
- void ccWaveDialog::onExportWaveAsCSV()
- {
- if (!m_cloud)
- {
- assert(false);
- return;
- }
- int pointIndex = m_gui->pointIndexSpinBox->value();
- if (pointIndex >= static_cast<int>(m_cloud->waveforms().size()))
- {
- assert(false);
- return;
- }
-
- const ccWaveformProxy& w = m_cloud->waveformProxy(pointIndex);
- if (!w.isValid())
- {
- //no valid descriptor
- return;
- }
- if (w.numberOfSamples() == 0)
- {
- //nothing to do
- return;
- }
- //persistent settings
- QSettings settings;
- settings.beginGroup(ccPS::SaveFile());
- QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
- currentPath += QString("/") + QString("waveform_%1.csv").arg(pointIndex);
- //ask for a filename
- QString filename = QFileDialog::getSaveFileName(this, "Select output file", currentPath, "*.csv");
- if (filename.isEmpty())
- {
- //process cancelled by user
- return;
- }
- //save last saving location
- settings.setValue(ccPS::CurrentPath(), QFileInfo(filename).absolutePath());
- settings.endGroup();
- //save file
- w.toASCII(filename);
- }
|