ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvPointPairRegistrationDlg.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 // LOCAL
11 #include "MainWindow.h"
13 
14 // COMMON
15 #include <ecvPickingHub.h>
16 
17 // CV_CORE_LIB
19 #include <RegistrationTools.h>
20 
21 // CV_DB_LIB
22 #include <ecv2DLabel.h>
23 #include <ecvDisplayTools.h>
24 #include <ecvGenericPointCloud.h>
25 #include <ecvPointCloud.h>
26 #include <ecvProgressDialog.h>
27 #include <ecvSphere.h>
28 
29 // CV_IO_LIB
30 #include <ecvGlobalShiftManager.h>
31 
32 // QT
33 #include <QMdiSubWindow>
34 #include <QMessageBox>
35 #include <QSettings>
36 #include <QToolButton>
37 
38 // default position of each columns in the aligned and ref. table widgets
39 static const int XYZ_COL_INDEX = 0;
40 static const int RMS_COL_INDEX = 3;
41 static const int DEL_BUTTON_COL_INDEX = 4;
42 
43 // minimum number of pairs to let the user click on the align button
44 static const unsigned MIN_PAIRS_COUNT = 3;
45 
48  QWidget* parent /*=0*/)
49  : ccOverlayDialog(parent),
50  m_alignedPoints("aligned points"),
51  m_refPoints("reference points"),
52  m_refLabels("reference labels"),
53  m_alignedLabels("aligned labels"),
54  m_paused(false),
55  m_pickingHub(pickingHub),
56  m_app(app) {
57  assert(m_pickingHub);
58 
59  setupUi(this);
60 
61  // restore from persistent settings
62  {
63  QSettings settings;
64  settings.beginGroup("PointPairAlign");
65  bool pickSpheres =
66  settings.value("PickSpheres", useSphereToolButton->isChecked())
67  .toBool();
68  double sphereRadius =
69  settings.value("SphereRadius", radiusDoubleSpinBox->value())
70  .toDouble();
71  int maxRMS = settings.value("MaxRMS", maxRmsSpinBox->value()).toInt();
72  bool adjustScale =
73  settings.value("AdjustScale", adjustScaleCheckBox->isChecked())
74  .toBool();
75  bool autoUpdateZoom =
76  settings.value("AutoUpdateZom", autoZoomCheckBox->isChecked())
77  .toBool();
78  settings.endGroup();
79 
80  useSphereToolButton->setChecked(pickSpheres);
81  radiusDoubleSpinBox->setValue(sphereRadius);
82  maxRmsSpinBox->setValue(maxRMS);
83  adjustScaleCheckBox->setChecked(adjustScale);
84  autoZoomCheckBox->setChecked(autoUpdateZoom);
85  }
86 
87  connect(showAlignedCheckBox, &QCheckBox::toggled, this,
89  connect(showReferenceCheckBox, &QCheckBox::toggled, this,
91 
92  connect(typeAlignToolButton, &QToolButton::clicked, this,
94  connect(typeRefToolButton, &QToolButton::clicked, this,
96 
97  connect(unstackAlignToolButton, &QToolButton::clicked, this,
99  connect(unstackRefToolButton, &QToolButton::clicked, this,
101 
102  connect(alignToolButton, &QToolButton::clicked, this,
104  connect(resetToolButton, &QToolButton::clicked, this,
106 
107  connect(validToolButton, &QToolButton::clicked, this,
109  connect(cancelToolButton, &QToolButton::clicked, this,
111 
112  connect(adjustScaleCheckBox, &QCheckBox::toggled, this,
114  connect(TxCheckBox, &QCheckBox::toggled, this,
116  connect(TyCheckBox, &QCheckBox::toggled, this,
118  connect(TzCheckBox, &QCheckBox::toggled, this,
120  connect(rotComboBox,
121  static_cast<void (QComboBox::*)(int)>(
122  &QComboBox::currentIndexChanged),
124 
127 
128  m_refPoints.setEnabled(true);
129  m_refPoints.setVisible(false);
130 }
131 
133  : entity(ent),
134  wasVisible(entity ? entity->isVisible() : false),
135  wasEnabled(entity ? entity->isEnabled() : false),
136  wasSelected(entity ? entity->isSelected() : false) {}
137 
139  if (!entity) return;
140 
141  entity->setVisible(wasVisible);
142  entity->setEnabled(wasEnabled);
143  entity->setSelected(wasSelected);
146  ecvDisplayTools::RedrawDisplay(false, false);
147  }
148 }
149 
151  const ccHObject::Container& entities) {
152  clear();
153  isShifted = false;
154 
155  for (ccHObject* entity : entities) {
156  if (!entity) {
157  assert(false);
158  continue;
159  }
160 
161  if (!isShifted) {
162  ccShiftedObject* shiftedEntity = ccHObjectCaster::ToShifted(entity);
163  if (shiftedEntity && shiftedEntity->isShifted()) {
164  isShifted = true;
165  shift = shiftedEntity
166  ->getGlobalShift(); // we can only consider the
167  // first shift!
168  }
169  }
170 
171  insert(entity, EntityContext(entity));
172  }
173 }
174 
176  alignToolButton->setEnabled(false);
177  validToolButton->setEnabled(false);
178  while (alignedPointsTableWidget->rowCount() != 0)
179  alignedPointsTableWidget->removeRow(
180  alignedPointsTableWidget->rowCount() - 1);
181  while (refPointsTableWidget->rowCount() != 0)
182  refPointsTableWidget->removeRow(refPointsTableWidget->rowCount() - 1);
183 
184  for (auto it = m_alignedEntities.begin(); it != m_alignedEntities.end();
185  ++it) {
186  it.key()->setSelected(true);
187  it.key()->showSF(false);
188  }
189  for (auto it = m_referenceEntities.begin(); it != m_referenceEntities.end();
190  ++it) {
191  it.key()->setSelected(true);
192  it.key()->showSF(false);
193  }
194 
196  // clear aligned markers
197  for (unsigned i = 0; i < m_alignedLabels.getChildrenNumber(); ++i) {
198  ccHObject* child = m_alignedLabels.getChild(i);
199  if (child && child->isKindOf(CV_TYPES::LABEL_2D)) {
200  cc2DLabel* alignedLabel = ccHObjectCaster::To2DLabel(child);
201  alignedLabel->clearLabel();
202  } else { // probably sphere
203  updateSphereMarks(child, true);
204  }
205  }
206  // clear reference markers
207  for (unsigned i = 0; i < m_refLabels.getChildrenNumber(); ++i) {
208  ccHObject* child = m_refLabels.getChild(i);
209  if (child && child->isKindOf(CV_TYPES::LABEL_2D)) {
210  cc2DLabel* refLabel = ccHObjectCaster::To2DLabel(child);
211  refLabel->clearLabel();
212  } else // probably sphere
213  {
214  updateSphereMarks(child, true);
215  }
216  }
217  }
218 
224  m_alignedEntities.clear();
227  m_refPoints.resize(0);
228  m_refPoints.setGlobalShift(0, 0, 0);
230  m_referenceEntities.clear();
231 }
232 
234  if (!ccOverlayDialog::linkWith(win)) {
235  return false;
236  }
237 
240 
241  return true;
242 }
243 
245  assert(!m_alignedEntities.empty());
247  if (!m_pickingHub->addListener(this, true)) {
248  CVLog::Error(
249  "Picking mechanism is already in use! Close the other tool "
250  "first, and then restart this one.");
251  return false;
252  }
253 
256  }
257  return ccOverlayDialog::start();
258 }
259 
261  updateAlignInfo();
262 
266 
267  // clear the area
272  ccOverlayDialog::stop(state);
273 }
274 
275 static void SetEnabled_recursive(ccHObject* ent) {
276  assert(ent);
277  ent->setEnabled(true);
278  if (ent->getParent()) SetEnabled_recursive(ent->getParent());
279 }
280 
281 void ccPointPairRegistrationDlg::label2DMove(int x, int y, int dx, int dy) {
283  const int retinaScale = ecvDisplayTools::GetDevicePixelRatio();
284  for (unsigned i = 0; i < m_alignedLabels.getChildrenNumber(); ++i) {
285  ccHObject* child = m_alignedLabels.getChild(i);
286  if (child && child->isKindOf(CV_TYPES::LABEL_2D)) {
287  cc2DLabel* alignLabel = ccHObjectCaster::To2DLabel(child);
288  if (alignLabel) {
289  if (abs(dx) > 0 || abs(dy) > 0) {
290  alignLabel->move2D(x * retinaScale, y * retinaScale,
291  dx * retinaScale, dy * retinaScale,
294  }
295 
298  alignLabel->update2DLabelView(context);
299  }
300  } else // probably sphere
301  {
302  if (child) {
303  child->updateNameIn3DRecursive();
304  }
305  }
306  }
307  for (unsigned i = 0; i < m_refLabels.getChildrenNumber(); ++i) {
308  ccHObject* child = m_refLabels.getChild(i);
309  if (child && child->isKindOf(CV_TYPES::LABEL_2D)) {
310  cc2DLabel* refLabel = ccHObjectCaster::To2DLabel(child);
311  if (refLabel) {
312  if (abs(dx) > 0 || abs(dy) > 0) {
313  refLabel->move2D(x * retinaScale, y * retinaScale,
314  dx * retinaScale, dy * retinaScale,
317  }
318 
321  refLabel->update2DLabelView(context);
322  }
323  } else // probably sphere
324  {
325  if (child) {
326  child->updateNameIn3DRecursive();
327  }
328  }
329  }
330  }
331 }
332 
334  QWidget* win,
335  const ccHObject::Container& alignedEntities,
336  const ccHObject::Container* referenceEntities /*=nullptr*/) {
337  if (!win) {
338  assert(false);
339  return false;
340  }
341 
342  clear();
343 
344  if (alignedEntities.empty()) {
345  CVLog::Error(
346  "[PointPairRegistration] Need at least one aligned entity!");
347  return false;
348  }
349 
350  m_alignedEntities.fill(alignedEntities);
351  if (referenceEntities) {
352  m_referenceEntities.fill(*referenceEntities);
353  }
354 
355  // create dedicated 3D view
356  if (!m_associatedWin) {
357  linkWith(win);
358  assert(m_associatedWin);
359  }
360 
361  // add aligned entity to display
362  ecvViewportParameters originViewportParams;
363  bool hasOriginViewportParams = false;
364  for (auto it = m_alignedEntities.begin(); it != m_alignedEntities.end();
365  ++it) {
366  ccHObject* aligned = it.key();
368  hasOriginViewportParams = true;
369  originViewportParams = ecvDisplayTools::GetViewportParameters();
370  }
371  // DGM: it's already in the global DB!
372  // m_associatedWin->addToOwnDB(aligned);
373  aligned->setVisible(true);
374  SetEnabled_recursive(aligned);
375  // SetVisible_recursive(aligned);
376  }
377 
378  // add reference entity (if any) to display
379  for (auto it = m_referenceEntities.begin(); it != m_referenceEntities.end();
380  ++it) {
381  ccHObject* reference = it.key();
382  if (!hasOriginViewportParams && ecvDisplayTools::GetCurrentScreen()) {
383  hasOriginViewportParams = true;
384  originViewportParams = ecvDisplayTools::GetViewportParameters();
385  }
386  // DGM: it's already in the global DB!
387  // m_associatedWin->addToOwnDB(reference);
388  reference->setVisible(true);
389  SetEnabled_recursive(reference);
390  // SetVisible_recursive(reference);
391  }
392 
393  showReferenceCheckBox->setChecked(!m_referenceEntities.empty());
394  showReferenceCheckBox->setEnabled(!m_referenceEntities.empty());
395  showAlignedCheckBox->setChecked(true);
396 
397  ecvDisplayTools::GetCurrentScreen()->showMaximized();
398  resetTitle();
402  "(you can add points 'manually' if necessary)",
405  QString("Pick equivalent points on both clouds (at least %1 pairs "
406  "- mind the order)")
407  .arg(MIN_PAIRS_COUNT),
409 
411  if (hasOriginViewportParams) {
412  ecvDisplayTools::SetViewportParameters(originViewportParams);
414  } else {
416  }
417 
419  return true;
420 }
421 
422 static QString s_aligned_tooltip = QObject::tr(
423  "Whether the point is expressed in the entity original coordinate "
424  "system (before being shifted by CV) or not");
425 static double s_last_ax = 0;
426 static double s_last_ay = 0;
427 static double s_last_az = 0;
428 static bool s_lastAlignePointIsGlobal = true;
430  ccAskThreeDoubleValuesDlg ptsDlg("x", "y", "z", -1.0e12, 1.0e12, s_last_ax,
431  s_last_ay, s_last_az, 8,
432  "Add aligned point", this);
433 
434  // if the aligned entity is shifted, the user has the choice to input
435  // virtual point either in the original coordinate system or the shifted one
437  ptsDlg.showCheckbox("Not shifted", s_lastAlignePointIsGlobal,
439 
440  if (!ptsDlg.exec()) return;
441 
442  // save values for current session
443  s_last_ax = ptsDlg.doubleSpinBox1->value();
444  s_last_ay = ptsDlg.doubleSpinBox2->value();
445  s_last_az = ptsDlg.doubleSpinBox3->value();
446  bool shifted = true;
449  shifted = !s_lastAlignePointIsGlobal;
450  }
451 
453 
454  addAlignedPoint(P, nullptr, shifted);
455 }
456 
457 static double s_last_rx = 0;
458 static double s_last_ry = 0;
459 static double s_last_rz = 0;
460 static bool s_lastRefPointisGlobal = true;
462  ccAskThreeDoubleValuesDlg ptsDlg("x", "y", "z", -1.0e12, 1.0e12, s_last_rx,
463  s_last_ry, s_last_rz, 8,
464  "Add reference point", this);
465 
466  // if the reference entity is shifted, the user has the choice to input
467  // virtual points either in the original coordinate system or the shifted
468  // one
469  //(if there's no reference entity, we use a 'global' one by default)
471  ptsDlg.showCheckbox("Not shifted", s_lastRefPointisGlobal,
473 
474  if (!ptsDlg.exec()) return;
475 
476  // save values for current session
477  s_last_rx = ptsDlg.doubleSpinBox1->value();
478  s_last_ry = ptsDlg.doubleSpinBox2->value();
479  s_last_rz = ptsDlg.doubleSpinBox3->value();
480  bool shifted = (!m_referenceEntities.empty());
483  shifted = !s_lastRefPointisGlobal;
484  }
485 
487 
488  addReferencePoint(P, nullptr, shifted);
489 }
490 
492  m_paused = state;
493  setDisabled(state);
494 }
495 
497  CCVector3d& P, ccHObject* entity, PointCoordinateType& sphereRadius) {
498  sphereRadius = -PC_ONE;
499  if (!entity || !useSphereToolButton->isChecked() ||
500  !entity->isKindOf(
501  CV_TYPES::POINT_CLOUD)) // only works with cloud right now
502  {
503  // nothing to do
504  return true;
505  }
506 
507  // we'll now try to detect the sphere
508  double searchRadius = radiusDoubleSpinBox->value();
509  double maxRMSPercentage = maxRmsSpinBox->value() / 100.0;
510  ccGenericPointCloud* cloud = static_cast<ccGenericPointCloud*>(entity);
511  assert(cloud);
512 
513  // crop points inside a box centered on the current point
514  ccBBox box;
515  box.add(CCVector3::fromArray((P - CCVector3d(1, 1, 1) * searchRadius).u));
516  box.add(CCVector3::fromArray((P + CCVector3d(1, 1, 1) * searchRadius).u));
517  cloudViewer::ReferenceCloud* part = cloud->crop(box, true);
518 
519  bool success = false;
520  if (part && part->size() > 16) {
521  PointCoordinateType radius;
522  CCVector3 C;
523  double rms;
524  ecvProgressDialog pDlg(true, this);
525  // first roughly search for the sphere
527  part, 0.5, C, radius, rms, &pDlg, 0.9) ==
529  if (radius / searchRadius < 0.5 || radius / searchRadius > 2.0) {
531  QString("[ccPointPairRegistrationDlg] Detected sphere "
532  "radius (%1) is too far from search radius!")
533  .arg(radius));
534  } else {
535  // now look again (more precisely)
536  {
537  delete part;
538  box.clear();
539  box.add(C - CCVector3(1, 1, 1) * radius *
540  static_cast<PointCoordinateType>(
541  1.05)); // add 5%
542  box.add(C + CCVector3(1, 1, 1) * radius *
543  static_cast<PointCoordinateType>(
544  1.05)); // add 5%
545  part = cloud->crop(box, true);
546  if (part && part->size() > 16)
548  DetectSphereRobust(part, 0.5, C, radius, rms,
549  &pDlg, 0.99);
550  }
551  CVLog::Print(QString("[ccPointPairRegistrationDlg] Detected "
552  "sphere radius = %1 (rms = %2)")
553  .arg(radius)
554  .arg(rms));
555  if (radius / searchRadius < 0.5 ||
556  radius / searchRadius > 2.0) {
558  "[ccPointPairRegistrationDlg] Sphere radius is too "
559  "far from search radius!");
560  } else if (rms / searchRadius >= maxRMSPercentage) {
562  "[ccPointPairRegistrationDlg] RMS is too high!");
563  } else {
564  sphereRadius = radius;
565  P = CCVector3d::fromArray(C.u);
566  success = true;
567  }
568  }
569  } else {
571  "[ccPointPairRegistrationDlg] Failed to fit a sphere "
572  "around the picked point!");
573  }
574  } else {
575  // not enough memory? No points inside the
577  "[ccPointPairRegistrationDlg] Failed to crop points around the "
578  "picked point?!");
579  }
580 
581  if (part) delete part;
582 
583  return success;
584 }
585 
587  if (!ecvDisplayTools::GetCurrentScreen()) return;
588 
589  // no point picking when paused!
590  if (m_paused) return;
591 
592  if (!pi.entity) return;
593 
595 
596  if (m_alignedEntities.contains(pi.entity)) {
597  addAlignedPoint(pin, pi.entity,
598  true); // picked points are always shifted by default
599  } else if (m_referenceEntities.contains(pi.entity)) {
600  addReferencePoint(pin, pi.entity,
601  true); // picked points are always shifted by default
602  } else {
603  // assert(false);
605  QString("pick wrong entity : [%1]").arg(pi.entity->getName()));
606  return;
607  }
608 }
609 
611  bool canAlign = (m_alignedPoints.size() == m_refPoints.size() &&
613  alignToolButton->setEnabled(canAlign);
614  validToolButton->setEnabled(false);
615 
616  unstackAlignToolButton->setEnabled(m_alignedPoints.size() != 0);
617  unstackRefToolButton->setEnabled(m_refPoints.size() != 0);
618 
619  updateAlignInfo();
620 }
621 
622 static QToolButton* CreateDeleteButton() {
623  QToolButton* delButton = new QToolButton();
624  delButton->setIcon(QIcon(":/Resources/images/smallCancel.png"));
625  return delButton;
626 }
627 
629  ccPointCloud* cloud,
630  unsigned pointIndex,
631  QString pointName) {
632  assert(label);
633  label->addPickedPoint(cloud, pointIndex);
634  label->setName(pointName);
635  label->setVisible(true);
636  label->setEnabled(true);
637  label->setDisplayedIn2D(false);
638  label->displayPointLegend(true);
639 
640  return label;
641 }
642 
644  unsigned pointIndex,
645  QString pointName) {
646  return CreateLabel(new cc2DLabel, cloud, pointIndex, pointName);
647 }
648 
650  QObject* senderButton = sender();
651 
652  // go through all the buttons and find which one has been pushed!
653  bool alignedPoint = true;
654  int pointIndex = -1;
655  // test 'aligned' buttons first
656  {
657  for (int i = 0; i < alignedPointsTableWidget->rowCount(); ++i) {
658  if (alignedPointsTableWidget->cellWidget(i, DEL_BUTTON_COL_INDEX) ==
659  senderButton) {
660  pointIndex = i;
661  break;
662  }
663  }
664  }
665 
666  if (pointIndex < 0) {
667  // test reference points if necessary
668  alignedPoint = false;
669  for (int i = 0; i < refPointsTableWidget->rowCount(); ++i) {
670  if (refPointsTableWidget->cellWidget(i, DEL_BUTTON_COL_INDEX) ==
671  senderButton) {
672  pointIndex = i;
673  break;
674  }
675  }
676  }
677 
678  if (pointIndex < 0) {
679  assert(false);
680  return;
681  }
682 
683  if (alignedPoint)
684  removeAlignedPoint(pointIndex);
685  else
686  removeRefPoint(pointIndex);
687 }
688 
689 void ccPointPairRegistrationDlg::addPointToTable(QTableWidget* tableWidget,
690  int rowIndex,
691  const CCVector3d& P,
692  QString pointName) {
693  assert(tableWidget);
694  if (!tableWidget) return;
695 
696  // add corresponding row in table
697  tableWidget->setRowCount(
698  std::max<int>(rowIndex + 1, tableWidget->rowCount()));
699  tableWidget->setVerticalHeaderItem(rowIndex,
700  new QTableWidgetItem(pointName));
701 
702  // add point coordinates
703  for (int d = 0; d < 3; ++d) {
704  QTableWidgetItem* item = new QTableWidgetItem();
705  item->setData(Qt::EditRole, QString::number(P.u[d], 'f', 6));
706  tableWidget->setItem(rowIndex, XYZ_COL_INDEX + d, item);
707  }
708 
709  // add 'remove' button
710  {
711  if (rowIndex == 0)
712  tableWidget->setColumnWidth(DEL_BUTTON_COL_INDEX, 20);
713  // QTableWidgetItem* item = new QTableWidgetItem();
714  // tableWidget->setItem(rowIndex, DEL_BUTTON_COL_INDEX, item);
715  QToolButton* delButton = CreateDeleteButton();
716  connect(delButton, &QToolButton::clicked, this,
718  tableWidget->setCellWidget(rowIndex, DEL_BUTTON_COL_INDEX, delButton);
719  }
720 }
721 
723  ccHObject* entity /*=0*/,
724  bool shifted /*=true*/) {
725  assert(entity == nullptr || m_referenceEntities.contains(entity));
726 
727  ccGenericPointCloud* cloud =
728  entity ? ccHObjectCaster::ToGenericPointCloud(entity) : nullptr;
729 
730  // first point?
731  if (m_refPoints.size() == 0) {
732  if (entity) // picked point
733  {
734  // simply copy the cloud global shift/scale
735  if (cloud) {
738  }
739  } else // virtual point
740  {
742  m_refPoints.setGlobalShift(0, 0, 0);
743 
744  if (!shifted) {
745  // test that the input point has not too big coordinates
746  bool shiftEnabled = false;
747  CCVector3d Pshift(0, 0, 0);
748  double scale = 1.0;
749  // we use the aligned shift by default (if any)
750  ccGenericPointCloud* alignedCloud =
752  if (alignedCloud && alignedCloud->isShifted()) {
753  Pshift = alignedCloud->getGlobalShift();
754  scale = alignedCloud->getGlobalScale();
755  shiftEnabled = true;
756  }
759  shiftEnabled, Pshift, nullptr, &scale)) {
760  m_refPoints.setGlobalShift(Pshift);
762  }
763  }
764  }
765  }
766 
767  PointCoordinateType sphereRadius = -PC_ONE;
768  if (!convertToSphereCenter(Pin, entity, sphereRadius)) return false;
769 
770  // transform the input point in the 'global world' by default
771  if (shifted && cloud) {
772  Pin = cloud->toGlobal3d<double>(Pin);
773  }
774 
775  // check that we don't duplicate points
776  for (unsigned i = 0; i < m_refPoints.size(); ++i) {
777  // express the 'Pi' point in the current global coordinate system
779  *m_refPoints.getPoint(i));
780  if (cloudViewer::LessThanEpsilon((Pi - Pin).norm())) {
781  CVLog::Error(
782  "Point already picked or too close to an already selected "
783  "one!");
784  return false;
785  }
786  }
787 
788  // add point to the 'reference' set
789  unsigned newPointIndex = m_refPoints.size();
790  if (newPointIndex == m_refPoints.capacity() &&
791  !m_refPoints.reserve(newPointIndex + 1)) {
792  CVLog::Error("Not enough memory?!");
793  return false;
794  }
795 
796  // shift point to the local coordinate system before pushing it
797  CCVector3 P = m_refPoints.toLocal3pc<double>(Pin);
799 
800  QString pointName = QString("R%1").arg(newPointIndex);
801 
802  // add corresponding row in table
803  addPointToTable(refPointsTableWidget, newPointIndex, Pin, pointName);
804 
805  // eventually add a label (or a sphere)
806  if (sphereRadius <= 0) {
807  cc2DLabel* label = CreateLabel(&m_refPoints, newPointIndex, pointName);
808  m_refLabels.addChild(label);
809  label->updateLabel();
810  } else {
811  ccGLMatrix trans;
812  trans.setTranslation(P);
813  ccSphere* sphere = new ccSphere(sphereRadius, &trans, pointName);
814  sphere->showNameIn3D(true);
815  sphere->setTempColor(ecvColor::yellow, true);
816  m_refLabels.addChild(sphere);
817  updateSphereMarks(sphere, false);
818  }
819 
821 
822  return true;
823 }
824 
826  ccHObject* entity /*=0*/,
827  bool shifted /*=0*/) {
828  // if the input point is not shifted, we shift it to the aligned coordinate
829  // system
830  assert(entity == nullptr || m_alignedEntities.contains(entity));
831 
832  // first point?
833  if (m_alignedPoints.size() == 0) {
834  // simply copy the cloud global shift/scale
835  ccGenericPointCloud* cloud =
837  if (cloud) {
840  }
841  }
842 
843  PointCoordinateType sphereRadius = -PC_ONE;
844  if (!convertToSphereCenter(Pin, entity, sphereRadius)) return false;
845 
846  // transform the input point in the 'global world' by default
847  if (shifted) Pin = m_alignedPoints.toGlobal3d<double>(Pin);
848 
849  // check that we don't duplicate points
850  for (unsigned i = 0; i < m_alignedPoints.size(); ++i) {
853  if (cloudViewer::LessThanEpsilon((Pi - Pin).norm())) {
854  CVLog::Error(
855  "Point already picked or too close to an already selected "
856  "one!");
857  return false;
858  }
859  }
860 
861  unsigned newPointIndex = m_alignedPoints.size();
862  if (newPointIndex == m_alignedPoints.capacity() &&
863  !m_alignedPoints.reserve(newPointIndex + 1)) {
864  CVLog::Error("Not enough memory?!");
865  return false;
866  }
867 
868  // shift point to the local coordinate system before pushing it
869  CCVector3 P = m_alignedPoints.toLocal3pc<double>(Pin);
871 
872  QString pointName = QString("A%1").arg(newPointIndex);
873 
874  // add corresponding row in table
875  addPointToTable(alignedPointsTableWidget, newPointIndex, Pin, pointName);
876 
877  // eventually add a label (or a sphere)
878  if (sphereRadius <= 0) {
879  cc2DLabel* label =
880  CreateLabel(&m_alignedPoints, newPointIndex, pointName);
881  m_alignedLabels.addChild(label);
882  label->updateLabel();
883  } else {
884  ccGLMatrix trans;
885  trans.setTranslation(P);
886  ccSphere* sphere = new ccSphere(sphereRadius, &trans, pointName);
887  sphere->showNameIn3D(true);
888  sphere->setTempColor(ecvColor::red, true);
889  m_alignedLabels.addChild(sphere);
890  updateSphereMarks(sphere, false);
891  }
892 
894 
895  return true;
896 }
897 
899  unsigned pointCount = m_alignedPoints.size();
900  if (pointCount == 0) // nothing to do
901  return;
902 
903  assert(alignedPointsTableWidget->rowCount() > 0);
904  alignedPointsTableWidget->removeRow(alignedPointsTableWidget->rowCount() -
905  1);
906 
907  assert(m_alignedLabels.getChildrenNumber() == pointCount);
908  pointCount--;
909 
910  // remove label
911  updateAlignedMarkers(pointCount);
912 
913  // remove point
914  m_alignedPoints.resize(pointCount);
915 
917 }
918 
920  unsigned pointCount = m_refPoints.size();
921  if (pointCount == 0) return;
922 
923  assert(refPointsTableWidget->rowCount() > 0);
924  refPointsTableWidget->removeRow(refPointsTableWidget->rowCount() - 1);
925 
926  // remove label
927  assert(m_refLabels.getChildrenNumber() == pointCount);
928  pointCount--;
929 
930  // remove label
931  updateRefMarkers(pointCount);
932 
933  // remove point
934  m_refPoints.resize(pointCount);
935 
936  if (pointCount == 0) {
937  // reset global shift (if any)
938  m_refPoints.setGlobalShift(0, 0, 0);
940  }
941 
943 }
944 
946  int index, bool autoRemoveDualPoint /*=false*/) {
947  if (index >= static_cast<int>(m_alignedPoints.size())) {
948  CVLog::Error(
949  "[ccPointPairRegistrationDlg::removeAlignedPoint] Invalid "
950  "index!");
951  assert(false);
952  return;
953  }
954 
955  int pointCount = static_cast<int>(m_alignedPoints.size());
956 
957  // remove the label (or sphere)
958  updateAlignedMarkers(index);
959 
960  // remove array row
961  alignedPointsTableWidget->removeRow(index);
962 
963  // shift points & rename labels
964  for (int i = index + 1; i < pointCount; ++i) {
965  *const_cast<CCVector3*>(m_alignedPoints.getPoint(i - 1)) =
967 
968  // new name
969  QString pointName = QString("A%1").arg(i - 1);
970  // update the label (if any)
971  ccHObject* child = m_alignedLabels.getChild(i - 1);
972  if (child) {
973  if (child->isKindOf(CV_TYPES::LABEL_2D)) {
974  cc2DLabel* label = static_cast<cc2DLabel*>(child);
975  label->clear();
977  static_cast<unsigned>(i - 1), pointName);
978  label->updateLabel();
979  } else // probably a sphere
980  {
981  child->setName(pointName);
982  updateSphereMarks(child, false);
983  }
984  }
985  // update array
986  alignedPointsTableWidget->setVerticalHeaderItem(
987  i - 1, new QTableWidgetItem(pointName));
988  }
990 
991  pointCount--;
992  assert(pointCount >= 0);
993  m_alignedPoints.resize(static_cast<unsigned>(pointCount));
994 
995  if (m_alignedPoints.size() == 0) {
996  // reset global shift (if any)
999  }
1000 
1002 
1003  // auto-remove the other point?
1004  if (autoRemoveDualPoint && index < static_cast<int>(m_refPoints.size()) &&
1005  QMessageBox::question(0, "Remove dual point",
1006  "Remove the equivalent reference point as well?",
1007  QMessageBox::Yes,
1008  QMessageBox::No) == QMessageBox::Yes) {
1009  removeRefPoint(index, false);
1010  }
1011 }
1012 
1014  int index, bool autoRemoveDualPoint /*=false*/) {
1015  if (index >= static_cast<int>(m_refPoints.size())) {
1016  CVLog::Error(
1017  "[ccPointPairRegistrationDlg::removeRefPoint] Invalid index!");
1018  assert(false);
1019  return;
1020  }
1021 
1022  int pointCount = static_cast<int>(m_refPoints.size());
1023 
1024  // remove the label (or sphere)
1025  updateRefMarkers(index);
1026  // remove array row
1027  refPointsTableWidget->removeRow(index);
1028 
1029  // shift points & rename labels
1030  for (int i = index + 1; i < pointCount; ++i) {
1031  *const_cast<CCVector3*>(m_refPoints.getPoint(i - 1)) =
1032  *m_refPoints.getPoint(i);
1033 
1034  // new name
1035  QString pointName = QString("R%1").arg(i - 1);
1036  // update the label (if any)
1037  ccHObject* child = m_refLabels.getChild(i - 1);
1038  if (child) {
1039  if (child->isKindOf(CV_TYPES::LABEL_2D)) {
1040  cc2DLabel* label = static_cast<cc2DLabel*>(child);
1041  label->clear();
1042  CreateLabel(label, &m_refPoints, static_cast<unsigned>(i - 1),
1043  pointName);
1044  label->updateLabel();
1045  } else // probably a sphere
1046  {
1047  child->setName(pointName);
1048  updateSphereMarks(child, false);
1049  }
1050  }
1051 
1052  // update array
1053  refPointsTableWidget->setVerticalHeaderItem(
1054  i - 1, new QTableWidgetItem(pointName));
1055  }
1057 
1058  pointCount--;
1059  assert(pointCount >= 0);
1060  m_refPoints.resize(static_cast<unsigned>(pointCount));
1061 
1062  if (m_refPoints.size() == 0) {
1063  // reset global shift (if any)
1064  m_refPoints.setGlobalShift(0, 0, 0);
1066  }
1067 
1069 
1070  // auto-remove the other point?
1071  if (autoRemoveDualPoint &&
1072  index < static_cast<int>(m_alignedPoints.size()) &&
1073  QMessageBox::question(0, "Remove dual point",
1074  "Remove the equivalent aligned point as well?",
1075  QMessageBox::Yes,
1076  QMessageBox::No) == QMessageBox::Yes) {
1077  removeAlignedPoint(index, false);
1078  }
1079 }
1080 
1082  bool remove) {
1083  if (obj->isA(CV_TYPES::SPHERE)) {
1084  bool forceRedraw = !remove;
1086  context.forceRedraw = forceRedraw;
1087  if (remove) {
1088  context.removeEntityType = ENTITY_TYPE::ECV_MESH;
1089  context.removeViewID = obj->getViewId();
1091  obj->showNameIn3D(false);
1092  } else {
1093  obj->showNameIn3D(true);
1094  }
1095 
1096  context.drawingFlags = CC_DRAW_3D | CC_DRAW_FOREGROUND;
1097  obj->draw(context);
1098  context.drawingFlags = CC_DRAW_2D | CC_DRAW_FOREGROUND;
1099  obj->draw(context);
1100  }
1101 }
1102 
1104  if (index < 0) {
1105  return;
1106  }
1107 
1109  ccHObject* child = m_alignedLabels.getChild(index);
1110  if (child && child->isKindOf(CV_TYPES::LABEL_2D)) {
1111  cc2DLabel* label = ccHObjectCaster::To2DLabel(child);
1112  label->clearLabel();
1113  } else { // probably sphere
1114  updateSphereMarks(child, true);
1115  }
1117  }
1118 }
1119 
1121  if (index < 0) {
1122  return;
1123  }
1124 
1126  ccHObject* child = m_refLabels.getChild(index);
1127  if (child && child->isKindOf(CV_TYPES::LABEL_2D)) {
1128  cc2DLabel* label = ccHObjectCaster::To2DLabel(child);
1129  label->clearLabel();
1130  } else {
1131  updateSphereMarks(child, true);
1132  }
1133  m_refLabels.removeChild(index);
1134  }
1135 }
1136 
1138  if (m_alignedEntities.empty()) return;
1139 
1140  for (auto it = m_alignedEntities.begin(); it != m_alignedEntities.end();
1141  ++it)
1142  it.key()->setVisible(state);
1143  m_alignedPoints.setEnabled(state);
1144 
1145  for (unsigned i = 0; i < m_alignedLabels.getChildrenNumber(); ++i) {
1146  ccHObject* child = m_alignedLabels.getChild(i);
1147  if (child && child->isKindOf(CV_TYPES::LABEL_2D)) {
1148  cc2DLabel* alignLabel = ccHObjectCaster::To2DLabel(child);
1149  alignLabel->setEnabled(state);
1150  alignLabel->updateLabel();
1151  } else {
1152  updateSphereMarks(child, !state);
1153  }
1154  }
1155 
1157 
1158  if (autoZoomCheckBox->isChecked()) {
1160  } else {
1162  }
1163 }
1164 
1166  if (m_referenceEntities.empty()) return;
1167 
1168  for (auto it = m_referenceEntities.begin(); it != m_referenceEntities.end();
1169  ++it)
1170  it.key()->setVisible(state);
1171  m_refPoints.setEnabled(state);
1172 
1173  for (unsigned i = 0; i < m_refLabels.getChildrenNumber(); ++i) {
1174  ccHObject* child = m_refLabels.getChild(i);
1175  if (child && child->isKindOf(CV_TYPES::LABEL_2D)) {
1176  cc2DLabel* refLabel = ccHObjectCaster::To2DLabel(child);
1177  refLabel->setEnabled(state);
1178  refLabel->updateLabel();
1179  } else {
1180  updateSphereMarks(child, !state);
1181  }
1182  }
1183 
1185 
1186  if (autoZoomCheckBox->isChecked()) {
1188  } else {
1190  }
1191 }
1192 
1195  double& rms,
1196  bool autoUpdateTab) {
1197  if (m_alignedEntities.empty()) {
1198  assert(false);
1199  return false;
1200  }
1201 
1202  if (m_alignedPoints.size() != m_refPoints.size() ||
1204  assert(false);
1205  CVLog::Error(QString("Need at least %1 points for each entity (and the "
1206  "same number of points in both subsets)!")
1207  .arg(MIN_PAIRS_COUNT));
1208  return false;
1209  }
1210 
1211  // fixed scale?
1212  bool adjustScale = adjustScaleCheckBox->isChecked();
1213 
1214  // call Horn registration method
1216  &m_alignedPoints, &m_refPoints, trans, !adjustScale)) {
1217  CVLog::Error("Registration failed! (points are aligned?)");
1218  return false;
1219  }
1220 
1221  // apply constraints (if any)
1222  {
1223  int filters = 0;
1224  switch (rotComboBox->currentIndex()) {
1225  case 1:
1227  break;
1228  case 2:
1230  break;
1231  case 3:
1233  break;
1234  default:
1235  // nothing to do
1236  break;
1237  }
1238 
1239  if (!TxCheckBox->isChecked())
1241  if (!TyCheckBox->isChecked())
1243  if (!TzCheckBox->isChecked())
1245 
1246  if (filters != 0) {
1248  trans, filters, m_alignedPoints.computeGravityCenter(),
1250  }
1251  }
1252 
1253  // compute RMS
1255  &m_refPoints, trans);
1256 
1257  if (autoUpdateTab) {
1258  // display resulting RMS in colums
1259  if (rms >= 0) {
1260  assert(m_alignedPoints.size() == m_refPoints.size());
1261  for (unsigned i = 0; i < m_alignedPoints.size(); ++i) {
1262  const CCVector3* Ri = m_refPoints.getPoint(i);
1263  const CCVector3* Li = m_alignedPoints.getPoint(i);
1264  CCVector3d Lit = trans.apply(*Li);
1265  double dist = (Ri->toDouble() - Lit).norm();
1266 
1267  QTableWidgetItem* itemA = new QTableWidgetItem();
1268  itemA->setData(Qt::EditRole, dist);
1269  alignedPointsTableWidget->setItem(i, RMS_COL_INDEX, itemA);
1270  QTableWidgetItem* itemR = new QTableWidgetItem();
1271  itemR->setData(Qt::EditRole, dist);
1272  refPointsTableWidget->setItem(i, RMS_COL_INDEX, itemR);
1273  }
1274  } else {
1275  // clear RMS columns
1276  clearRMSColumns();
1277  }
1278  }
1279 
1280  return true;
1281 }
1282 
1284  for (int i = 0; alignedPointsTableWidget->rowCount(); ++i)
1285  alignedPointsTableWidget->setItem(i, RMS_COL_INDEX,
1286  new QTableWidgetItem());
1287  for (int i = 0; refPointsTableWidget->rowCount(); ++i)
1288  refPointsTableWidget->setItem(i, RMS_COL_INDEX, new QTableWidgetItem());
1289 }
1290 
1294  QString(), ecvDisplayTools::UPPER_CENTER_MESSAGE, false);
1296  "[Point-pair registration]",
1298  }
1299 }
1300 
1302  // reset title
1303  resetTitle();
1304 
1306  double rms;
1307 
1308  if (m_alignedPoints.size() == m_refPoints.size() &&
1310  callHornRegistration(trans, rms, true)) {
1311  QString rmsString = QString("Achievable RMS: %1").arg(rms);
1313  rmsString, ecvDisplayTools::UPPER_CENTER_MESSAGE, true,
1314  60 * 60);
1315  resetToolButton->setEnabled(true);
1316  validToolButton->setEnabled(true);
1317  } else {
1318  resetToolButton->setEnabled(false);
1319  validToolButton->setEnabled(false);
1320  }
1321 
1325  }
1326 }
1327 
1330  double rms;
1331 
1332  // reset title
1333  resetTitle();
1335  ecvDisplayTools::RedrawDisplay(true, false);
1336 
1337  if (callHornRegistration(trans, rms, true)) {
1338  if (rms >= 0) {
1339  QString rmsString = QString("Current RMS: %1").arg(rms);
1340  CVLog::Print(QString("[PointPairRegistration] ") + rmsString);
1342  rmsString, ecvDisplayTools::UPPER_CENTER_MESSAGE, true,
1343  60 * 60);
1344  } else {
1346  "[PointPairRegistration] Internal error (negative RMS?!)");
1347  return;
1348  }
1349 
1350  // apply (scaled) transformation (if not fixed)
1351  bool adjustScale = adjustScaleCheckBox->isChecked();
1352  if (adjustScale) {
1353  if (trans.R.isValid()) trans.R.scale(trans.s);
1354 
1355  QString scaleString = QString("Scale: %1").arg(trans.s);
1356  CVLog::Print(QString("[PointPairRegistration] ") + scaleString);
1357  } else {
1358  CVLog::Print(QString("[PointPairRegistration] Scale: fixed (1.0)"));
1359  }
1360 
1361  ccGLMatrix transMat = FromCCLibMatrix<double, float>(trans.R, trans.T);
1362  //...virtually
1363  m_transMatHistory = transMat;
1364  transformAlignedEntity(transMat, true);
1365 
1367  for (auto it = m_alignedEntities.begin(); it != m_alignedEntities.end();
1368  ++it)
1369  it.key()->setRedrawFlagRecursive(true);
1370 
1371  // update aligned sphere markers
1372  for (unsigned i = 0; i < m_alignedLabels.getChildrenNumber(); ++i) {
1373  ccHObject* child = m_alignedLabels.getChild(i);
1374  if (child && !child->isKindOf(CV_TYPES::LABEL_2D)) {
1375  child->setRedrawFlagRecursive(true);
1376  }
1377  }
1378 
1379  // force clouds visibility
1380  {
1381  // we don't want the window zoom to change or the window to be be
1382  // redrawn
1383  if (!showAlignedCheckBox->isChecked())
1384  showAlignedCheckBox->setChecked(true);
1385  if (!showReferenceCheckBox->isChecked())
1386  showReferenceCheckBox->setChecked(true);
1387  // restore window ref
1388  }
1389 
1390  if (autoZoomCheckBox->isChecked()) {
1392  }
1393 
1396  }
1397 
1398  updateAllMarkers(1.0f / static_cast<float>(trans.s));
1399 
1400  resetToolButton->setEnabled(true);
1401  alignToolButton->setEnabled(false);
1402  validToolButton->setEnabled(true);
1403  }
1404 }
1405 
1407  // DGM: we have to 'counter-scale' the markers (otherwise they might appear
1408  // very big or very small!)
1409  for (unsigned i = 0; i < m_alignedLabels.getChildrenNumber(); ++i) {
1410  ccHObject* child = m_alignedLabels.getChild(i);
1411  if (child->isA(CV_TYPES::LABEL_2D)) {
1412  static_cast<cc2DLabel*>(child)->setRelativeMarkerScale(markerSize);
1413  child->setEnabled(true);
1414  static_cast<cc2DLabel*>(child)->updateLabel();
1415  } else // probably sphere
1416  {
1417  child->updateNameIn3DRecursive();
1418  }
1419  }
1420 
1421  // DGM: we have to reset the reference markers scale
1422  for (unsigned i = 0; i < m_refLabels.getChildrenNumber(); ++i) {
1423  ccHObject* child = m_refLabels.getChild(i);
1424  if (child->isA(CV_TYPES::LABEL_2D)) {
1425  static_cast<cc2DLabel*>(child)->setRelativeMarkerScale(markerSize);
1426  child->setEnabled(true);
1427  static_cast<cc2DLabel*>(child)->updateLabel();
1428  } else // probably sphere
1429  {
1430  child->updateNameIn3DRecursive();
1431  }
1432  }
1433 }
1434 
1436  const ccGLMatrix& transMat, bool apply /* = true*/) {
1437  assert(!m_alignedEntities.empty());
1438  // we temporarily detach entity, as it may undergo
1439  //"severe" modifications (octree deletion, etc.) --> see
1440  // ccHObject::applyGLTransformation
1441  for (auto it = m_alignedEntities.begin(); it != m_alignedEntities.end();
1442  ++it) {
1444  if (m_app)
1445  objContext = m_app->removeObjectTemporarilyFromDBTree(it.key());
1446  it.key()->applyGLTransformation_recursive(apply ? &transMat : nullptr);
1447  if (m_app) m_app->putObjectBackIntoDBTree(it.key(), objContext);
1448 
1449  if (!apply) {
1450  ecvDisplayTools::RemoveBB(it.key()->getViewId());
1451  }
1452  }
1454  : nullptr);
1455 }
1456 
1458  if (m_alignedEntities.empty()) return;
1459 
1462 
1463  // update aligned sphere markers
1464  for (unsigned i = 0; i < m_alignedLabels.getChildrenNumber(); ++i) {
1465  ccHObject* child = m_alignedLabels.getChild(i);
1466  if (child && !child->isKindOf(CV_TYPES::LABEL_2D)) {
1467  child->setRedrawFlagRecursive(true);
1468  }
1469  }
1470 
1473  for (auto it = m_alignedEntities.begin(); it != m_alignedEntities.end();
1474  ++it) {
1475  it.key()->setRedrawFlagRecursive(true);
1476  }
1477 
1478  if (autoZoomCheckBox->isChecked()) {
1480  }
1482  }
1483 
1484  updateAllMarkers(1.0);
1485 
1486  updateAlignInfo();
1487 
1488  alignToolButton->setEnabled(true);
1489  resetToolButton->setEnabled(false);
1490 }
1491 
1493  ccHObject tempGroup("TempGroup");
1494 
1495  for (auto it = m_alignedEntities.begin(); it != m_alignedEntities.end();
1496  ++it) {
1497  tempGroup.addChild(it.key(), ccHObject::DP_NONE);
1498  }
1499  for (auto it = m_referenceEntities.begin(); it != m_referenceEntities.end();
1500  ++it) {
1501  tempGroup.addChild(it.key(), ccHObject::DP_NONE);
1502  }
1503 
1504  ccBBox bbox;
1505  if (tempGroup.getChildrenNumber() != 0) {
1506  bbox = tempGroup.getDisplayBB_recursive(false);
1507  }
1508  if (bbox.isValid()) {
1510  }
1511 }
1512 
1515  double rms = -1.0;
1516 
1517  // restore current alignedPoints
1518  const ccGLMatrix& transMat = m_transMatHistory.inverse();
1520 
1521  if (callHornRegistration(trans, rms, false)) {
1522  QStringList summary;
1523  if (rms >= 0) {
1524  QString rmsString = QString("Final RMS: %1").arg(rms);
1525  CVLog::Print(QString("[PointPairRegistration] ") + rmsString);
1526  summary << rmsString;
1527  summary << "----------------";
1528  }
1529 
1530  // apply (scaled) transformation (if not fixed)
1531  bool adjustScale = adjustScaleCheckBox->isChecked();
1532  if (adjustScale && trans.R.isValid()) {
1533  trans.R.scale(trans.s);
1534  }
1535  ccGLMatrix transMat = FromCCLibMatrix<double, float>(trans.R, trans.T);
1536 
1537  //...for real this time!
1538  transformAlignedEntity(transMat, false);
1539 
1540  summary << QString("Transformation matrix");
1541  summary << transMat.toString(3,
1542  '\t'); // low precision, just for display
1543  summary << "----------------";
1544 
1545  CVLog::Print("[PointPairRegistration] Applied transformation matrix:");
1546  CVLog::Print(transMat.toString(12, ' ')); // full precision
1547 
1548  if (adjustScale) {
1549  QString scaleString =
1550  QString("Scale: %1 (already integrated in above matrix!)")
1551  .arg(trans.s);
1552  CVLog::Warning(QString("[PointPairRegistration] ") + scaleString);
1553  summary << scaleString;
1554  } else {
1555  CVLog::Print(QString("[PointPairRegistration] Scale: fixed (1.0)"));
1556  summary << "Scale: fixed (1.0)";
1557  }
1558  summary << "----------------";
1559 
1560  // pop-up summary
1561  summary << "Refer to Console (F8) for more details";
1562  QMessageBox::information(this, "Align info", summary.join("\n"));
1563 
1564  // don't forget global shift
1565  bool alwaysDropShift = false;
1566  bool firstQuestion = true;
1567  for (auto it = m_alignedEntities.begin(); it != m_alignedEntities.end();
1568  ++it) {
1569  ccGenericPointCloud* cloud =
1571  if (cloud) {
1572  if (m_refPoints.isShifted()) {
1573  const CCVector3d& Pshift = m_refPoints.getGlobalShift();
1574  const double& scale = m_refPoints.getGlobalScale();
1575  cloud->setGlobalShift(Pshift);
1576  cloud->setGlobalScale(scale);
1577  CVLog::Warning(tr("[PointPairRegistration] Aligned entity "
1578  "global shift has been updated to match "
1579  "the reference: (%1,%2,%3) [x%4]")
1580  .arg(Pshift.x)
1581  .arg(Pshift.y)
1582  .arg(Pshift.z)
1583  .arg(scale));
1584  } else if (cloud->isShifted()) // we'll ask the user first
1585  // before dropping the shift
1586  // information on the aligned
1587  // cloud
1588  {
1589  if (firstQuestion) {
1590  alwaysDropShift =
1591  (QMessageBox::question(
1592  this, tr("Drop shift information?"),
1593  tr("Aligned cloud is shifted but "
1594  "reference cloud is not: drop "
1595  "global shift information?"),
1596  QMessageBox::Yes,
1597  QMessageBox::No) == QMessageBox::Yes);
1598  firstQuestion = false;
1599  }
1600 
1601  if (alwaysDropShift) {
1602  cloud->setGlobalShift(0, 0, 0);
1603  cloud->setGlobalScale(1.0);
1604  CVLog::Warning(tr("[PointPairRegistration] Cloud %1: "
1605  "global shift has been reset to "
1606  "match the reference!")
1607  .arg(cloud->getName()));
1608  }
1609  }
1610  }
1611  }
1612  } else {
1613  CVLog::Warning(QString(
1614  "[PointPairRegistration] Failed to register entities?!"));
1615  }
1616 
1617  // save persistent settings
1618  {
1619  QSettings settings;
1620  settings.beginGroup("PointPairAlign");
1621  settings.setValue("PickSpheres", useSphereToolButton->isChecked());
1622  settings.setValue("SphereRadius", radiusDoubleSpinBox->value());
1623  settings.setValue("MaxRMS", maxRmsSpinBox->value());
1624  settings.setValue("AdjustScale", adjustScaleCheckBox->isChecked());
1625  settings.setValue("AutoUpdateZom", autoZoomCheckBox->isChecked());
1626  settings.endGroup();
1627  }
1628 
1629  stop(true);
1630 }
1631 
1633  for (auto it = m_alignedEntities.begin(); it != m_alignedEntities.end();
1634  ++it)
1635  it.key()->enableGLTransformation(false);
1636 
1639  for (auto it = m_alignedEntities.begin(); it != m_alignedEntities.end();
1640  ++it)
1641  it.key()->setRedrawFlagRecursive(true);
1642 
1644  stop(false);
1645 }
constexpr PointCoordinateType PC_ONE
'1' as a PointCoordinateType value
Definition: CVConst.h:67
Vector3Tpl< double > CCVector3d
Double 3D Vector.
Definition: CVGeom.h:804
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
Definition: CVGeom.h:798
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
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
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
Vector3Tpl< double > toDouble() const
Cast operator to a double vector (explicit call version)
Definition: CVGeom.h:255
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
2D label (typically attached to points)
Definition: ecv2DLabel.h:22
bool addPickedPoint(ccGenericPointCloud *cloud, unsigned pointIndex, bool entityCenter=false)
Adds a point to this label.
void clear(bool ignoreDependencies=false, bool ignoreCaption=true)
Clears label.
virtual bool move2D(int x, int y, int dx, int dy, int screenWidth, int screenHeight) override
Called on mouse move (for 2D interactors)
void updateLabel()
void setDisplayedIn2D(bool state)
Whether to display the label in 2D.
Definition: ecv2DLabel.h:114
void update2DLabelView(CC_DRAW_CONTEXT &context, bool updateScreen=true)
void clearLabel(bool ignoreCaption=true)
void displayPointLegend(bool state)
Whether to display the point(s) legend (title only)
Definition: ecv2DLabel.h:108
Generic dialog to query 3 (double) values.
void showCheckbox(const QString &label, bool state, QString tooltip=QString())
Enable the checkbox (bottom-left)
Bounding box structure.
Definition: ecvBBox.h:25
virtual void setTempColor(const ecvColor::Rgb &col, bool autoActivate=true)
Sets current temporary (unique)
virtual void setVisible(bool state)
Sets entity visibility.
virtual void showNameIn3D(bool state)
Sets whether name should be displayed in 3D.
QString toString(int precision=12, QChar separator=' ') const
Returns matrix as a string.
ccGLMatrixTpl< T > inverse() const
Returns inverse transformation.
void setTranslation(const Vector3Tpl< float > &Tr)
Sets translation (float version)
virtual void toIdentity()
Sets matrix to identity.
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
A 3D cloud interface with associated features (color, normals, octree, etc.)
virtual cloudViewer::ReferenceCloud * crop(const ccBBox &box, bool inside=true)=0
Crops the cloud inside (or outside) a bounding box.
static ccShiftedObject * ToShifted(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccShiftedObject.
static cc2DLabel * To2DLabel(ccHObject *obj)
Converts current object to cc2DLabel (if possible)
static ccGenericPointCloud * ToGenericPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccGenericPointCloud.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
void draw(CC_DRAW_CONTEXT &context) override
Draws entity and its children.
void removeAllChildren()
Removes all children.
virtual ccBBox getDisplayBB_recursive(bool relative)
Returns the bounding-box of this entity and it's children WHEN DISPLAYED.
QString getViewId() const
Definition: ecvHObject.h:225
void applyGLTransformation_recursive(const ccGLMatrix *trans=nullptr)
Applies the active OpenGL transformation to the entity (recursive)
unsigned getChildrenNumber() const
Returns the number of children.
Definition: ecvHObject.h:312
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
void setRedrawFlagRecursive(bool redraw=false)
void updateNameIn3DRecursive()
ccHObject * getParent() const
Returns parent object.
Definition: ecvHObject.h:245
void removeChild(ccHObject *child)
std::vector< ccHObject * > Container
Standard instances container (for children, etc.)
Definition: ecvHObject.h:337
ccHObject * getChild(unsigned childPos) const
Returns the ith child.
Definition: ecvHObject.h:325
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
bool isKindOf(CV_CLASS_ENUM type) const
Definition: ecvObject.h:128
Generic overlay dialog interface.
virtual void stop(bool accepted)
Stops process/dialog.
virtual bool start()
Starts process.
QWidget * m_associatedWin
Associated (MDI) window.
virtual bool linkWith(QWidget *win)
Links the overlay dialog with a MDI window.
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.)
void invalidateBoundingBox() override
Invalidates bounding box.
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
CCVector3 computeGravityCenter()
Returns the cloud gravity center.
bool start() override
Starts process.
void label2DMove(int x, int y, int dx, int dy)
EntityContexts m_alignedEntities
Aligned entity.
bool m_paused
Whether the dialog is paused or not.
void stop(bool state) override
Stops process/dialog.
EntityContexts m_referenceEntities
Reference entity (if any)
void onItemPicked(const PickedItem &pi) override
Inherited from ccPickingListener.
void unstackAligned()
Slot called to remove the last point on the 'align' stack.
void removeRefPoint(int index, bool autoRemoveDualPoint=true)
Removes a point from the 'reference' set.
bool convertToSphereCenter(CCVector3d &P, ccHObject *entity, PointCoordinateType &sphereRadius)
Converts a picked point to a sphere center (if necessary)
bool linkWith(QWidget *win) override
Links the overlay dialog with a MDI window.
void addManualRefPoint()
Slot called to add a manual point to the 'reference' set.
void unstackRef()
Slot called to remove the last point on the 'reference' stack.
void updateAlignInfo()
Updates the registration info and buttons states.
ccPickingHub * m_pickingHub
Picking hub.
void onDelButtonPushed()
Slot called when a "delete" button is pushed.
void resetTitle()
Resets the displayed title (3D view)
void addPointToTable(QTableWidget *tableWidget, int rowIndex, const CCVector3d &P, QString pointLabel)
Adds a point to one of the table (ref./aligned)
bool addAlignedPoint(CCVector3d &P, ccHObject *entity=nullptr, bool shifted=true)
Adds a point to the 'align' set.
bool callHornRegistration(cloudViewer::PointProjectionTools::Transformation &trans, double &rms, bool autoUpdateTab)
Calls Horn registration (cloudViewer::HornRegistrationTools)
void updateSphereMarks(ccHObject *obj, bool remove)
void onPointCountChanged()
Enables (or not) buttons depending on the number of points in both lists.
ccPointPairRegistrationDlg(ccPickingHub *pickingHub, ecvMainAppInterface *app, QWidget *parent=nullptr)
Default constructor.
void removeAlignedPoint(int index, bool autoRemoveDualPoint=true)
Removes a point from the 'align' set.
void clearRMSColumns()
Clears the RMS rows.
ecvMainAppInterface * m_app
Main application interface.
void addManualAlignedPoint()
Slot called to add a manual point to the 'align' set.
void transformAlignedEntity(const ccGLMatrix &transMat, bool apply=true)
void showAlignedEntities(bool)
Slot called to change aligned cloud visibility.
ccPointCloud m_alignedPoints
Aligned points set.
bool init(QWidget *win, const ccHObject::Container &alignedEntities, const ccHObject::Container *referenceEntities=nullptr)
Inits dialog.
ccPointCloud m_refPoints
Reference points set.
bool addReferencePoint(CCVector3d &P, ccHObject *entity=nullptr, bool shifted=true)
Adds a point to the 'reference' set.
void showReferenceEntities(bool)
Slot called to change reference cloud visibility.
void pause(bool state)
Pauses the dialog.
Shifted entity interface.
virtual void setGlobalScale(double scale)
CCVector3d toGlobal3d(const Vector3Tpl< T > &Plocal) const
Returns the point back-projected into the original coordinates system.
CCVector3 toLocal3pc(const Vector3Tpl< T > &Pglobal) const
Returns the point projected into the local (shifted) coordinates system.
bool isShifted() const
Returns whether the cloud is shifted or not.
virtual void setGlobalShift(double x, double y, double z)
Sets shift applied to original coordinates (information storage only)
virtual const CCVector3d & getGlobalShift() const
Returns the shift applied to original coordinates.
virtual double getGlobalScale() const
Returns the scale applied to original coordinates.
Sphere (primitive)
Definition: ecvSphere.h:16
void clear()
Resets the bounding box.
Definition: BoundingBox.h:125
bool isValid() const
Returns whether bounding box is valid or not.
Definition: BoundingBox.h:203
void add(const Vector3Tpl< T > &P)
'Enlarges' the bounding box with a point
Definition: BoundingBox.h:131
static ErrorCode DetectSphereRobust(GenericIndexedCloudPersist *cloud, double outliersRatio, CCVector3 &center, PointCoordinateType &radius, double &rms, GenericProgressCallback *progressCb=nullptr, double confidence=0.99, unsigned seed=0)
Tries to detect a sphere in a point cloud.
static bool FindAbsoluteOrientation(GenericCloud *lCloud, GenericCloud *rCloud, ScaledTransformation &trans, bool fixedScale=false)
static double ComputeRMS(GenericCloud *lCloud, GenericCloud *rCloud, const ScaledTransformation &trans)
Computes RMS between two clouds given a transformation and a scale.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
unsigned capacity() const
Returns cloud capacity (i.e. reserved size)
const CCVector3 * getPoint(unsigned index) const override
A very simple point cloud (no point duplication)
unsigned size() const override
Returns the number of points.
static void FilterTransformation(const ScaledTransformation &inTrans, int transformationFilters, const CCVector3 &toBeAlignedGravityCenter, const CCVector3 &referenceGravityCenter, ScaledTransformation &outTrans)
bool isValid() const
Returns matrix validity.
Definition: SquareMatrix.h:138
void scale(Scalar coef)
Scales matrix (all elements are multiplied by the same coef.)
Definition: SquareMatrix.h:459
static int GetDevicePixelRatio()
static void RemoveEntities(const ccHObject *obj)
static const ecvViewportParameters & GetViewportParameters()
static void GetContext(CC_DRAW_CONTEXT &CONTEXT)
Returns context information.
static int GlWidth()
Returns the OpenGL context width.
static ecvDisplayTools * TheInstance()
static void SetViewportParameters(const ecvViewportParameters &params)
static int GlHeight()
Returns the OpenGL context height.
static QWidget * GetCurrentScreen()
static void SetRedrawRecursive(bool redraw=false)
static QWidget * GetMainScreen()
static void RemoveBB(CC_DRAW_CONTEXT context)
void labelmove2D(int x, int y, int dx, int dy)
static void DisplayNewMessage(const QString &message, MessagePosition pos, bool append=false, int displayMaxDelay_sec=2, MessageType type=CUSTOM_MESSAGE)
Displays a status message in the bottom-left corner.
static void UpdateConstellationCenterAndZoom(const ccBBox *aBox=nullptr, bool redraw=true)
Center and zoom on a given bounding box.
static void RedrawDisplay(bool only2D=false, bool forceRedraw=true)
static void ZoomGlobal()
static bool Handle(const CCVector3d &P, double diagonal, Mode mode, bool useInputCoordinatesShiftIfPossible, CCVector3d &coordinatesShift, bool *preserveCoordinateShift, double *coordinatesScale, bool *applyAll=0)
Main application interface (for plugins)
virtual void putObjectBackIntoDBTree(ccHObject *obj, const ccHObjectContext &context)=0
Adds back object to DB tree.
virtual ccHObjectContext removeObjectTemporarilyFromDBTree(ccHObject *obj)=0
Removes object temporarily from DB tree.
Graphical progress indicator (thread-safe)
Standard parameters for GL displays/viewports.
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
@ CC_DRAW_2D
@ CC_DRAW_FOREGROUND
@ CC_DRAW_3D
@ ECV_MESH
static cc2DLabel * CreateLabel(cc2DLabel *label, ccPointCloud *cloud, unsigned pointIndex, QString pointName)
static void SetEnabled_recursive(ccHObject *ent)
static double s_last_ry
static double s_last_rz
static QToolButton * CreateDeleteButton()
static double s_last_az
static bool s_lastAlignePointIsGlobal
static const unsigned MIN_PAIRS_COUNT
static const int RMS_COL_INDEX
static const int DEL_BUTTON_COL_INDEX
static const int XYZ_COL_INDEX
static bool s_lastRefPointisGlobal
static QString s_aligned_tooltip
static double s_last_ay
static double s_last_rx
static double s_last_ax
ImGuiContext * context
Definition: Window.cpp:76
static double dist(double x1, double y1, double x2, double y2)
Definition: lsd.c:207
@ POINT_CLOUD
Definition: CVTypes.h:104
@ LABEL_2D
Definition: CVTypes.h:140
@ SPHERE
Definition: CVTypes.h:121
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23
constexpr Rgb red(MAX, 0, 0)
constexpr Rgb yellow(MAX, MAX, 0)
Display context.
EntityContext(ccHObject *ent)
Default constructor.
void fill(const ccHObject::Container &entities)
A scaled geometrical transformation (scale + rotation + translation)
CCVector3d apply(const CCVector3d &P) const
Applies the transformation to a point.
Backup "context" for an object.