ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvScalarFieldArithmeticsDlg.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 
9 
10 // Qt
11 #include <QMessageBox>
12 #include <QPushButton>
13 
14 // CV_DB_LIB
15 #include <ecvPointCloud.h>
16 #include <ecvScalarField.h>
17 
18 // system
19 #include <assert.h>
20 #ifdef _MSC_VER
21 #include <windows.h>
22 #endif
23 #include <cmath>
24 
25 // number of valid operations
26 static const unsigned s_opCount = 18;
27 // operation names
28 static const char s_opNames[s_opCount][12] = {
29  "add", "sub", "mult", "div", "sqrt", "pow2",
30  "pow3", "exp", "log", "log10", "cos", "sin",
31  "tan", "acos", "asin", "atan", "int", "inverse"};
32 
33 // semi persitent
35 static bool s_applyInPlace = false;
36 static double s_previousConstValue = 1.0;
37 
39  QWidget* parent /*=0*/)
40  : QDialog(parent, Qt::Tool), Ui::SFArithmeticsDlg() {
41  assert(cloud);
42 
43  setupUi(this);
44 
45  QStringList sfLabels;
46  unsigned sfCount = cloud ? cloud->getNumberOfScalarFields() : 0;
47  if (sfCount < 1) {
48  sf1ComboBox->setEnabled(false);
49  sf2ComboBox->setEnabled(false);
50  buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
51  } else {
52  for (unsigned i = 0; i < sfCount; ++i) {
53  sfLabels << QString(cloud->getScalarFieldName(static_cast<int>(i)));
54  }
55 
56  sf1ComboBox->addItems(sfLabels);
57  sf1ComboBox->setCurrentIndex(0);
58 
59  sfLabels << "[Constant value]";
60  sf2ComboBox->addItems(sfLabels);
61  sf2ComboBox->setCurrentIndex(std::min<unsigned>(1, sfCount - 1));
62  }
63 
64  // connect signals/slots
65  connect(operationComboBox,
66  static_cast<void (QComboBox::*)(int)>(
67  &QComboBox::currentIndexChanged),
69  connect(sf2ComboBox,
70  static_cast<void (QComboBox::*)(int)>(
71  &QComboBox::currentIndexChanged),
73 
74  operationComboBox->setCurrentIndex(s_previouslySelectedOperationIndex);
75  constantDoubleSpinBox->setValue(s_previousConstValue);
76  updateSF1CheckBox->setChecked(s_applyInPlace);
77 }
78 
80  sf2ComboBox->setEnabled(index <= DIVIDE); // only the 4 first operations
81  // are applied with 2 SFs
82 }
83 
85  // the last element is always the 'constant' field
86  constantDoubleSpinBox->setEnabled(sf2ComboBox->currentIndex() + 1 ==
87  sf2ComboBox->count());
88 }
89 
91  return sf1ComboBox->currentIndex();
92 }
93 
95  return sf2ComboBox->currentIndex();
96 }
97 
100  int opIndex = operationComboBox->currentIndex();
101  if (opIndex < static_cast<int>(s_opCount)) {
102  return static_cast<ccScalarFieldArithmeticsDlg::Operation>(opIndex);
103  } else {
104  assert(false);
105  return INVALID;
106  }
107 }
108 
110  QString sf1, QString sf2 /*=QString()*/) const {
111  Operation op = getOperation();
112  return GetOperationName(op, sf1, sf2);
113 }
114 
117  name = name.toUpper();
118 
119  // test all known names...
120  for (unsigned i = 0; i < s_opCount; ++i) {
121  if (name == QString(s_opNames[i]).toUpper()) {
122  return static_cast<ccScalarFieldArithmeticsDlg::Operation>(i);
123  }
124  }
125 
126  return INVALID;
127 }
128 
130  Operation op, QString sf1, QString sf2 /*=QString()*/) {
131  switch (op) {
132  case PLUS:
133  return QString("%1 + %2").arg(sf1, sf2);
134  case MINUS:
135  return QString("%1 - %2").arg(sf1, sf2);
136  case MULTIPLY:
137  return QString("%1 * %2").arg(sf1, sf2);
138  case DIVIDE:
139  return QString("%1 / %2").arg(sf1, sf2);
140  default:
141  if (op != INVALID)
142  return QString("%1(%2)").arg(s_opNames[op], sf1);
143  else
144  assert(false);
145  break;
146  }
147 
148  return QString();
149 }
150 
152  Operation op = getOperation();
153  int sf1Idx = getSF1Index();
154  int sf2Idx = getSF2Index();
155 
156  // save persistent parameters
157  s_previouslySelectedOperationIndex = operationComboBox->currentIndex();
158  s_previousConstValue = constantDoubleSpinBox->value();
159  s_applyInPlace = updateSF1CheckBox->isChecked();
160 
161  SF2 sf2Desc;
162  sf2Desc.isConstantValue = constantDoubleSpinBox->isEnabled();
163  sf2Desc.constantValue = constantDoubleSpinBox->value();
164  sf2Desc.sfIndex = sf2Desc.isConstantValue ? -1 : sf2Idx;
165 
166  return Apply(cloud, op, sf1Idx, s_applyInPlace, &sf2Desc, this);
167 }
168 
170  Operation op,
171  int sf1Idx,
172  bool inplace,
173  SF2* sf2Desc /*=0*/,
174  QWidget* parent /*=0*/) {
175  assert(cloud);
176 
177  if (!cloud || !cloud->hasScalarFields()) {
179  "[ccScalarFieldArithmeticsDlg::apply] No input cloud, or cloud "
180  "has no SF?!");
181  assert(false);
182  return false;
183  }
184 
185  if (op == INVALID) {
187  "[ccScalarFieldArithmeticsDlg::apply] Invalid/unhandled "
188  "operation");
189  assert(false);
190  return false;
191  }
192 
193  unsigned sfCount = cloud->getNumberOfScalarFields();
194  cloudViewer::ScalarField* sf1 = nullptr;
195  {
196  if (sf1Idx >= static_cast<int>(sfCount)) {
198  "[ccScalarFieldArithmeticsDlg::apply] Invalid SF1 index!");
199  assert(false);
200  return false;
201  }
202  sf1 = cloud->getScalarField(sf1Idx);
203  assert(sf1);
204  }
205 
206  cloudViewer::ScalarField* sf2 = nullptr;
207  if (op <= DIVIDE) {
208  if (!sf2Desc || (!sf2Desc->isConstantValue &&
209  sf2Desc->sfIndex >= static_cast<int>(sfCount))) {
211  "[ccScalarFieldArithmeticsDlg::apply] Invalid SF2 "
212  "index/descriptor!");
213  assert(false);
214  return false;
215  } else if (sf2Desc->isConstantValue) {
216  if (op == DIVIDE && sf2Desc->constantValue == 0) {
217  CVLog::Error("Invalid constant value (can't divide by zero)");
218  return false;
219  }
220  }
221  sf2 = (!sf2Desc->isConstantValue && sf2Desc->sfIndex >= 0
222  ? cloud->getScalarField(sf2Desc->sfIndex)
223  : nullptr);
224  }
225 
226  // output SF
227  int sfIdx = -1;
228  if (!inplace) {
229  // generate new sf name based on the operation
230  QString sf1Name(sf1->getName());
231  QString sf2Name;
232  if (sf2) {
233  sf2Name = sf2->getName();
234  QString sfName = GetOperationName(op, sf1Name, sf2Name);
235  if (sfName.length() > 24) {
236  // if the resulting SF name is too long, we use shortcuts
237  // instead
238  sf1Name = QString("(SF#%1)").arg(sf1Idx);
239  sf2Name = QString("(SF#%1)").arg(sf2Desc->sfIndex);
240  }
241  }
242  QString sfName = GetOperationName(op, sf1Name, sf2Name);
243 
244  sfIdx = cloud->getScalarFieldIndexByName(qPrintable(sfName));
245  if (sfIdx >= 0) {
246  if (sfIdx == sf1Idx || sfIdx == sf2Desc->sfIndex) {
247  CVLog::Warning(QString("[ccScalarFieldArithmeticsDlg::apply] "
248  "Resulting scalar field would have the "
249  "same name as one of the operand (%1)! "
250  "Rename it first...")
251  .arg(sfName));
252  return false;
253  }
254  if (parent &&
255  QMessageBox::warning(
256  parent, "Same scalar field name",
257  "Resulting scalar field already exists! Overwrite it?",
258  QMessageBox::Ok | QMessageBox::Cancel,
259  QMessageBox::Ok) != QMessageBox::Ok) {
260  return false;
261  }
262 
263  cloud->deleteScalarField(sfIdx);
264  }
265 
266  sfIdx = cloud->addScalarField(qPrintable(sfName));
267  if (sfIdx < 0) {
269  "[ccScalarFieldArithmeticsDlg::apply] Failed to create "
270  "destination SF! (not enough memory?)");
271  return false;
272  }
273  } else {
274  sfIdx = sf1Idx;
275  }
276  cloudViewer::ScalarField* sfDest = cloud->getScalarField(sfIdx);
277  assert(sfDest);
278 
279  unsigned valCount = sf1->currentSize();
280  assert(!sf2 || valCount == sf2->currentSize());
281 
282  // resize destination SF
283  if (!sfDest->resizeSafe(valCount)) {
285  "[ccScalarFieldArithmeticsDlg::apply] Not enough memory!");
286  cloud->deleteScalarField(sfIdx);
287  sfDest = nullptr;
288  return false;
289  }
290  assert(valCount == sfDest->currentSize());
291 
292  for (unsigned i = 0; i < valCount; ++i) {
293  ScalarType val = NAN_VALUE;
294 
295  // we must handle 'invalid' values
296  const ScalarType& val1 = sf1->getValue(i);
297  if (ccScalarField::ValidValue(val1)) {
298  switch (op) {
299  case PLUS: {
300  if (sf2Desc->isConstantValue) {
301  val = val1 +
302  static_cast<ScalarType>(sf2Desc->constantValue);
303  } else {
304  const ScalarType& val2 = sf2->getValue(i);
305  if (ccScalarField::ValidValue(val2)) val = val1 + val2;
306  }
307  } break;
308  case MINUS: {
309  if (sf2Desc->isConstantValue) {
310  val = val1 -
311  static_cast<ScalarType>(sf2Desc->constantValue);
312  } else {
313  const ScalarType& val2 = sf2->getValue(i);
314  if (ccScalarField::ValidValue(val2)) val = val1 - val2;
315  }
316  } break;
317  case MULTIPLY: {
318  if (sf2Desc->isConstantValue) {
319  val = val1 *
320  static_cast<ScalarType>(sf2Desc->constantValue);
321  } else {
322  const ScalarType& val2 = sf2->getValue(i);
323  if (ccScalarField::ValidValue(val2)) val = val1 * val2;
324  }
325  } break;
326  case DIVIDE: {
327  if (sf2Desc->isConstantValue) {
328  val = val1 /
329  static_cast<ScalarType>(sf2Desc->constantValue);
330  } else {
331  const ScalarType& val2 = sf2->getValue(i);
332  if (ccScalarField::ValidValue(val2) &&
334  val = val1 / val2;
335  }
336  } break;
337  case SQRT:
338  if (val1 >= 0) val = std::sqrt(val1);
339  break;
340  case POW2:
341  val = val1 * val1;
342  break;
343  case POW3:
344  val = val1 * val1 * val1;
345  break;
346  case EXP:
347  val = std::exp(val1);
348  break;
349  case LOG:
350  if (val1 >= 0) val = std::log(val1);
351  break;
352  case LOG10:
353  if (val1 >= 0) val = std::log10(val1);
354  break;
355  case COS:
356  val = std::cos(val1);
357  break;
358  case SIN:
359  val = std::sin(val1);
360  break;
361  case TAN:
362  val = std::tan(val1);
363  break;
364  case ACOS:
365  if (val1 >= -1 && val1 <= 1) val = std::acos(val1);
366  break;
367  case ASIN:
368  if (val1 >= -1 && val1 <= 1) val = std::asin(val1);
369  break;
370  case ATAN:
371  val = std::atan(val1);
372  break;
373  case INT:
374  val = static_cast<ScalarType>(static_cast<int>(
375  val1)); // integer part ('round' doesn't seem to be
376  // available on MSVC?!)
377  break;
378  case INVERSE:
380  ? NAN_VALUE
381  : static_cast<ScalarType>(1.0f / val1);
382  break;
383  default:
384  assert(false);
385  break;
386  }
387  }
388 
389  sfDest->setValue(i, val);
390  }
391 
392  sfDest->computeMinAndMax();
393  cloud->setCurrentDisplayedScalarField(sfIdx);
394 
395  return true;
396 }
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h:76
std::string name
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
void deleteScalarField(int index) override
Deletes a specific scalar field.
bool hasScalarFields() const override
Returns whether one or more scalar fields are instantiated.
int getSF1Index()
Returns first selected SF index.
static Operation GetOperationByName(QString name)
Returns the operation enumerator based on its name.
Operation getOperation() const
Returns selected operation.
QString getOperationName(QString sf1, QString sf2=QString()) const
Returns selected operation name.
void onOperationIndexChanged(int index)
Called when the operation combo-box is modified.
int getSF2Index()
Returns second selected SF index.
static bool Apply(ccPointCloud *cloud, Operation op, int sf1Idx, bool inplace, SF2 *sf2=0, QWidget *parent=0)
Applies operation on a given cloud.
ccScalarFieldArithmeticsDlg(ccPointCloud *cloud, QWidget *parent=0)
Default constructor.
void onSF2IndexChanged(int index)
Called when the SF2 combo-box is modified.
bool apply(ccPointCloud *cloud)
Applies operation on a given cloud.
static QString GetOperationName(Operation op, QString sf1, QString sf2=QString())
Returns operation name.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
unsigned getNumberOfScalarFields() const
Returns the number of associated (and active) scalar fields.
const char * getScalarFieldName(int index) const
Returns the name of a specific scalar field.
A simple scalar field (to be associated to a point cloud)
Definition: ScalarField.h:25
virtual void computeMinAndMax()
Determines the min and max values.
Definition: ScalarField.h:123
ScalarType & getValue(std::size_t index)
Definition: ScalarField.h:92
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
const char * getName() const
Returns scalar field name.
Definition: ScalarField.h:43
bool resizeSafe(std::size_t count, bool initNewElements=false, ScalarType valueForNewElements=0)
Resizes memory (no exception thrown)
Definition: ScalarField.cpp:81
static bool ValidValue(ScalarType value)
Returns whether a scalar value is valid or not.
Definition: ScalarField.h:61
unsigned currentSize() const
Definition: ScalarField.h:100
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
static const char s_opNames[s_opCount][12]
static double s_previousConstValue
static int s_previouslySelectedOperationIndex
static const unsigned s_opCount
static bool s_applyInPlace
bool GreaterThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:37
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23