ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvWaveformDialog.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "ecvWaveformDialog.h"
9 
10 // Local
11 #include "ecvFileUtils.h"
12 #include "ecvGuiParameters.h"
13 #include "ecvPersistentSettings.h"
14 #include "ecvQCustomPlot.h"
15 
16 // common
17 #include <ecvPickingHub.h>
18 
19 // CV_DB_LIB
20 #include <ecvPointCloud.h>
21 #include <ecvProgressDialog.h>
22 
23 // Qt
24 #include <QCloseEvent>
25 #include <QSettings>
26 
27 // System
28 #include <assert.h>
29 
30 #include <cmath>
31 
32 // Gui
33 #include "ui_waveDlg.h"
34 
35 ccWaveWidget::ccWaveWidget(QWidget* parent /*=0*/)
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  setWindowTitle("Waveform");
49  setFocusPolicy(Qt::StrongFocus);
50 
51  setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
52 
53  setAutoAddPlottableToLegend(false);
54 
55  // default font for text rendering
56  m_renderingFont.setFamily(QString::fromUtf8("Arial"));
57  m_renderingFont.setBold(false);
58  // m_renderingFont.setWeight(75);
59 
60  // make ticks on bottom axis go outward
61  assert(xAxis && yAxis);
62  xAxis->setTickLength(0, 5);
63  xAxis->setSubTickLength(0, 3);
64  yAxis->setTickLength(0, 5);
65  yAxis->setSubTickLength(0, 3);
66 }
67 
68 ccWaveWidget::~ccWaveWidget() { clearInternal(); }
69 
71  clearInternal();
72  refresh();
73 }
74 
75 void ccWaveWidget::clearInternal() {
76  m_curveValues.resize(0);
77  m_dt = 0;
78  m_minA = m_maxA = 0;
79 }
80 
81 void ccWaveWidget::setTitle(const QString& str) { m_titleStr = str; }
82 
83 void ccWaveWidget::setAxisLabels(const QString& xLabel, const QString& yLabel) {
84  if (xLabel.isNull()) {
85  xAxis->setVisible(false);
86  } else {
87  // set labels
88  xAxis->setLabel(xLabel);
89  xAxis->setVisible(true);
90  }
91 
92  if (xLabel.isNull()) {
93  yAxis->setVisible(false);
94  } else {
95  // set labels
96  yAxis->setLabel(yLabel);
97  yAxis->setVisible(true);
98  }
99 }
100 
101 static double AbsLog(double c) { return (c >= 0 ? log1p(c) : -log1p(-c)); }
102 
104  unsigned pointIndex,
105  bool logScale,
106  double maxValue /*=0.0*/) {
107  clearInternal();
108 
109  if (!cloud || !cloud->hasFWF()) {
110  return;
111  }
112 
113  const ccWaveformProxy& w = cloud->waveformProxy(pointIndex);
114  if (!w.isValid()) {
115  // no valid descriptor
116  return;
117  }
118 
119  if (w.numberOfSamples() == 0) {
120  return;
121  }
122 
123  try {
124  m_curveValues.resize(w.numberOfSamples(), 0);
125  } catch (const std::bad_alloc&) {
126  // not enough memory
127  CVLog::Error("Not enough memory");
128  return;
129  }
130 
131  for (uint32_t i = 0; i < w.numberOfSamples(); ++i) {
132  double c = w.getSample(i);
133  if (logScale) {
134  c = AbsLog(c);
135  }
136  m_curveValues[i] = c;
137 
138  if (i) {
139  m_maxA = std::max(m_maxA, c);
140  m_minA = std::min(m_minA, c);
141  } else {
142  m_minA = m_maxA = c;
143  }
144  }
145 
146  if (maxValue != 0) {
147  m_maxA = logScale ? AbsLog(maxValue) : maxValue;
148  }
149 
151  m_echoPos = w.echoTime_ps();
152 }
153 
155  // set ranges appropriate to show data
156  xAxis->setRange(0, m_curveValues.size() * m_dt);
157  yAxis->setRange(m_minA, m_maxA);
158 
159  if (!m_titleStr.isEmpty()) {
160  // add title layout element
161  if (!m_titlePlot) {
162  // add a row for the title
163  plotLayout()->insertRow(0);
164  } else {
165  // remove previous title
166  plotLayout()->remove(m_titlePlot);
167  m_titlePlot = nullptr;
168  }
169  m_titlePlot = new QCPTextElement(this, m_titleStr);
170 
171  // title font
172  m_renderingFont.setPointSize(ecvGui::Parameters().defaultFontSize);
173  m_titlePlot->setFont(m_renderingFont);
174  plotLayout()->addElement(0, 0, m_titlePlot);
175  }
176 
177  // clear previous display
178  m_vertBar = nullptr;
179  m_curve = nullptr;
180  m_peakBar = nullptr;
181  this->clearGraphs();
182  this->clearPlottables();
183 
184  // wave curve
185  int curveSize = static_cast<int>(m_curveValues.size());
186  if (curveSize != 0) {
187  QVector<double> x(curveSize);
188  QVector<double> y(curveSize);
189 
190  for (int i = 0; i < curveSize; ++i) {
191  x[i] = i * m_dt;
192  y[i] = m_curveValues[i];
193  }
194 
195  // create graph and assign data to it:
196  m_curve = addGraph();
197  m_curve->setData(x, y);
198  m_curve->setName("WaveCurve");
199 
200  // set pen color
201  QPen pen(Qt::blue);
202  m_curve->setPen(pen);
203 
204  // set width
206  }
207 
208  if (m_drawVerticalIndicator) // vertical hint
209  {
210  m_vertBar = new QCPBarsWithText(xAxis, yAxis);
211 
212  // now we can modify properties of vertBar
213  m_vertBar->setName("VertLine");
214  m_vertBar->setWidth(0);
215  m_vertBar->setBrush(QBrush(Qt::red));
216  m_vertBar->setPen(QPen(Qt::red));
217  m_vertBar->setAntialiasedFill(false);
218  QVector<double> keyData(1);
219  QVector<double> valueData(1);
220 
221  // horizontal position
222  int curvePos = static_cast<int>(curveSize *
224  keyData[0] = curvePos * m_dt;
225  valueData[0] = m_maxA;
226 
227  m_vertBar->setData(keyData, valueData);
228 
229  // precision
230  QString valueStr = QString("Sample %0").arg(curvePos);
231  m_vertBar->setText(valueStr);
232  valueStr = QString("= %0").arg(
233  curvePos < curveSize ? m_curveValues[curvePos] : 0);
234  m_vertBar->appendText(valueStr);
236  }
237 
238  if (m_echoPos >= 0) {
239  m_peakBar = new QCPBarsWithText(xAxis, yAxis);
240 
241  // now we can modify properties of vertBar
242  m_peakBar->setName("PeakLine");
243  m_peakBar->setWidth(0);
244  m_peakBar->setBrush(QBrush(Qt::blue));
245  m_peakBar->setPen(QPen(Qt::blue));
246  m_peakBar->setAntialiasedFill(false);
247  QVector<double> keyData(1);
248  QVector<double> valueData(1);
249 
250  // horizontal position
251  keyData[0] = m_echoPos;
252  valueData[0] = m_maxA;
253 
254  m_peakBar->setData(keyData, valueData);
255 
256  // precision
257  m_peakBar->setText("Peak");
258  m_peakBar->setTextAlignment(m_echoPos > 0.5 * curveSize * m_dt);
259  }
260 
261  // rescaleAxes();
262 
263  // redraw
264  replot();
265 }
266 
267 void ccWaveWidget::updateCurveWidth(int w, int h) {
268  if (m_curve) {
269  int penWidth = std::max(w, h) / 200;
270  if (m_curve->pen().width() != penWidth) {
271  QPen pen = m_curve->pen();
272  pen.setWidth(penWidth);
273  m_curve->setPen(pen);
274  }
275  }
276 }
277 
278 void ccWaveWidget::resizeEvent(QResizeEvent* event) {
279  QCustomPlot::resizeEvent(event);
280 
281  updateCurveWidth(event->size().width(), event->size().height());
282 
283  refresh();
284 }
285 
287  m_lastMouseClick = event->pos();
288 
290 }
291 
293  if (event->buttons() & Qt::LeftButton) {
294  if (m_curve && !m_curveValues.empty()) {
295  QRect roi = /*m_curve->*/ rect();
296  if (roi.contains(event->pos(), false)) {
299  static_cast<double>(event->x() - roi.x()) / roi.width();
300  refresh();
301  }
302  }
303  } else {
304  event->ignore();
305  }
306 }
307 
309  ccPickingHub* pickingHub,
310  QWidget* parent /*=0*/)
311  : QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint),
312  m_cloud(cloud),
313  m_widget(new ccWaveWidget(this)),
314  m_pickingHub(pickingHub),
315  m_gui(new Ui_WaveDialog),
316  m_waveMax(0) {
317  m_gui->setupUi(this);
318 
319  QHBoxLayout* hboxLayout = new QHBoxLayout(m_gui->waveFrame);
320  hboxLayout->addWidget(m_widget);
321  hboxLayout->setContentsMargins(0, 0, 0, 0);
322  m_gui->waveFrame->setLayout(hboxLayout);
323 
324  if (cloud && cloud->size()) {
325  m_gui->pointIndexSpinBox->setMaximum(static_cast<int>(cloud->size()));
326  m_gui->pointIndexSpinBox->setSuffix(
327  QString(" / %1").arg(cloud->size() - 1));
328 
329  // init m_waveMax
330  double waveMin = 0;
331  ecvProgressDialog pDlg(parent);
332  if (cloud->computeFWFAmplitude(waveMin, m_waveMax, &pDlg)) {
333  CVLog::Print(
334  QString("[ccWaveDialog] Cloud '%1': max FWF amplitude = %2")
335  .arg(cloud->getName())
336  .arg(m_waveMax));
337  } else {
338  CVLog::Warning("[ccWaveDialog] Input cloud has no valid FWF data");
339  }
340  }
341 
342  connect(m_gui->pointIndexSpinBox,
343  static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
345  connect(m_gui->logScaleCheckBox, &QCheckBox::toggled, this,
347  connect(m_gui->fixedAmplitudeCheckBox, &QCheckBox::toggled, this,
349  connect(m_gui->pointPickingToolButton, &QToolButton::toggled, this,
351  connect(m_gui->saveWaveToolButton, &QToolButton::clicked, this,
353  connect(this, &QDialog::finished, [&]() {
354  m_gui->pointPickingToolButton->setChecked(false);
355  }); // auto disable picking mode when the dialog is closed
356 
357  // force update
359 }
360 
362 
364  if (!m_widget) {
365  assert(false);
366  return;
367  }
368  if (index < 0 || !m_cloud) {
369  assert(false);
370  return;
371  }
372 
373  m_widget->init(
374  m_cloud, static_cast<unsigned>(index),
375  m_gui->logScaleCheckBox->isChecked(),
376  m_gui->fixedAmplitudeCheckBox->isChecked() ? m_waveMax : 0.0);
377  m_widget->refresh();
378 }
379 
381  if (pi.entity == m_cloud) {
382  assert(!pi.entityCenter);
383  m_gui->pointIndexSpinBox->setValue(static_cast<int>(pi.itemIndex));
384  }
385 }
386 
388  onPointIndexChanged(m_gui->pointIndexSpinBox->value());
389 }
390 
392  if (!m_pickingHub) {
393  assert(false);
394  return;
395  }
396 
397  if (state) {
398  if (!m_pickingHub->addListener(this)) {
399  CVLog::Error(
400  "Another tool is currently using the point picking "
401  "mechanism.\nYou'll have to close it first.");
402  m_gui->pointPickingToolButton->blockSignals(true);
403  m_gui->pointPickingToolButton->setChecked(false);
404  m_gui->pointPickingToolButton->blockSignals(false);
405  return;
406  }
407  } else {
409  }
410 }
411 
413  if (!m_cloud) {
414  assert(false);
415  return;
416  }
417 
418  int pointIndex = m_gui->pointIndexSpinBox->value();
419  if (pointIndex >= static_cast<int>(m_cloud->waveforms().size())) {
420  assert(false);
421  return;
422  }
423 
424  const ccWaveformProxy& w = m_cloud->waveformProxy(pointIndex);
425  if (!w.isValid()) {
426  // no valid descriptor
427  return;
428  }
429  if (w.numberOfSamples() == 0) {
430  // nothing to do
431  return;
432  }
433 
434  // persistent settings
435  QSettings settings;
436  settings.beginGroup(ecvPS::SaveFile());
437  QString currentPath =
439  .toString();
440 
441  currentPath += QString("/") + QString("waveform_%1.csv").arg(pointIndex);
442 
443  // ask for a filename
444  QString filename = QFileDialog::getSaveFileName(this, "Select output file",
445  currentPath, "*.csv");
446  if (filename.isEmpty()) {
447  // process cancelled by user
448  return;
449  }
450 
451  // save last saving location
452  settings.setValue(ecvPS::CurrentPath(), QFileInfo(filename).absolutePath());
453  settings.endGroup();
454 
455  // save file
456  w.toASCII(filename);
457 }
MouseEvent event
std::string filename
int width
int height
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
QCustomPlot: vertical bar with text along side.
void setText(QString text)
void setTextAlignment(bool left)
void appendText(QString text)
uint32_t samplingRate_ps
Sampling rate in pico seconds.
Definition: ecvWaveform.h:44
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
Point/triangle picking hub.
Definition: ecvPickingHub.h:29
void removeListener(ccPickingListener *listener, bool autoStopPickingIfLast=true)
Removes a listener.
bool addListener(ccPickingListener *listener, bool exclusive=false, bool autoStartPicking=true, ecvDisplayTools::PICKING_MODE mode=ecvDisplayTools::POINT_OR_TRIANGLE_PICKING)
Adds a listener.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
std::vector< ccWaveform > & waveforms()
Gives access to the associated FWF data.
ccWaveformProxy waveformProxy(unsigned index) const
Returns a proxy on a given waveform.
bool hasFWF() const
Returns whether the cloud has associated Full WaveForm data.
bool computeFWFAmplitude(double &minVal, double &maxVal, ecvProgressDialog *pDlg=nullptr) const
Computes the maximum amplitude of all associated waveforms.
ccPickingHub * m_pickingHub
Picking hub.
double m_waveMax
Maximum wave amplitude (for all points)
ccWaveWidget * m_widget
Wave widget.
ccWaveDialog(ccPointCloud *cloud, ccPickingHub *pickingHub, QWidget *parent=nullptr)
Default constructor.
~ccWaveDialog() override
Destructor.
void updateCurrentWaveform()
ccPointCloud * m_cloud
Associated point cloud.
void onPointPickingButtonToggled(bool)
Ui_WaveDialog * m_gui
GUI.
virtual void onItemPicked(const PickedItem &pi) override
Method called whenever an item is picked.
void onPointIndexChanged(int)
Waveform widget.
void updateCurveWidth(int w, int h)
Updates overlay curve width depending on the widget display size.
QFont m_renderingFont
Rendering font.
std::vector< double > m_curveValues
void setAxisLabels(const QString &xLabel, const QString &yLabel)
Sets axis labels.
~ccWaveWidget() override
Destructor.
QCPBarsWithText * m_vertBar
QCPBarsWithText * m_peakBar
QCPTextElement * m_titlePlot
ccWaveWidget(QWidget *parent=nullptr)
Default constructor.
void init(ccPointCloud *cloud, unsigned pointIndex, bool logScale, double maxValue=0.0)
Computes the wave (curve) from a given point waveform.
bool m_drawVerticalIndicator
void resizeEvent(QResizeEvent *event) override
void mousePressEvent(QMouseEvent *event) override
QCPGraph * m_curve
Wave curve.
double m_verticalIndicatorPositionPercent
void clear()
Clears the display.
void setTitle(const QString &str)
Sets title.
void mouseMoveEvent(QMouseEvent *event) override
QPoint m_lastMouseClick
Last mouse click.
void refresh()
Updates the display.
Waveform proxy.
Definition: ecvWaveform.h:189
bool isValid() const
Returns whether the waveform (proxy) is valid or not.
Definition: ecvWaveform.h:198
bool toASCII(const QString &filename) const
Exports (real) samples to an ASCII file.
Definition: ecvWaveform.h:229
uint32_t numberOfSamples() const
Returns the number of samples.
Definition: ecvWaveform.h:251
const WaveformDescriptor & descriptor() const
Returns the descriptor.
Definition: ecvWaveform.h:254
double getSample(uint32_t i) const
Returns the (real) value of a given sample (in volts)
Definition: ecvWaveform.h:214
float echoTime_ps() const
Returns the echo time (in picoseconds)
Definition: ecvWaveform.h:248
unsigned size() const override
Definition: PointCloudTpl.h:38
static const ParamStruct & Parameters()
Returns the stored values of each parameter.
static const QString CurrentPath()
static const QString SaveFile()
Graphical progress indicator (thread-safe)
int min(int a, int b)
Definition: cutil_math.h:53
int max(int a, int b)
Definition: cutil_math.h:48
static double AbsLog(double c)
constexpr Rgb red(MAX, 0, 0)
constexpr Rgb blue(0, 0, MAX)
QString defaultDocPath()
Shortcut for getting the documents location path.
Definition: ecvFileUtils.h:30
Definition: lsd.c:1170