ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
E57Filter.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 "E57Filter.h"
9 
10 #include "FileIO.h"
11 
12 // Local
13 #include "E57Header.h"
14 
15 // libE57Format
16 #include <E57Format.h>
17 
18 // qCC_db
19 #include <ecvCameraSensor.h>
20 #include <ecvColorScalesManager.h>
21 #include <ecvGBLSensor.h>
22 #include <ecvImage.h>
23 #include <ecvPointCloud.h>
24 #include <ecvProgressDialog.h>
25 #include <ecvScalarField.h>
26 
27 // Qt
28 #include <QApplication>
29 #include <QBuffer>
30 #include <QUuid>
31 
32 // system
33 #include <cassert>
34 #include <string>
35 
36 using colorFieldType = double;
37 
38 namespace {
39 constexpr char CC_E57_INTENSITY_FIELD_NAME[] = "Intensity";
40 constexpr char CC_E57_RETURN_INDEX_FIELD_NAME[] = "Return index";
41 constexpr char s_e57PoseKey[] = "E57_pose";
42 
43 unsigned s_absoluteScanIndex = 0;
44 bool s_cancelRequestedByUser = false;
45 
46 unsigned s_absoluteImageIndex = 0;
47 
48 ScalarType s_maxIntensity = 0;
49 ScalarType s_minIntensity = 0;
50 
51 // for coordinate shift handling
52 FileIOFilter::LoadParameters s_loadParameters;
53 
54 // Array chunks for reading/writing information out of E57 files
55 struct TempArrays {
56  // points
57  std::vector<double> xData;
58  std::vector<double> yData;
59  std::vector<double> zData;
60  std::vector<int8_t> isInvalidData;
61 
62  // normals
63  std::vector<double> xNormData;
64  std::vector<double> yNormData;
65  std::vector<double> zNormData;
66 
67  // scalar field
68  std::vector<double> intData;
69  std::vector<int8_t> isInvalidIntData;
70 
71  // scan index field
72  std::vector<int8_t> scanIndexData;
73 
74  // color
75  std::vector<colorFieldType> redData;
76  std::vector<colorFieldType> greenData;
77  std::vector<colorFieldType> blueData;
78 };
79 
80 inline QString GetNewGuid() { return QUuid::createUuid().toString(); }
81 } // namespace
82 
84  : FileIOFilter({"_E57 Filter",
85  4.0f, // priority
86  QStringList{"e57"}, "e57", QStringList{"E57 cloud (*.e57)"},
87  QStringList{"E57 cloud (*.e57)"}, Import | Export}) {}
88 
90  bool& multiple,
91  bool& exclusive) const {
92  if (type == CV_TYPES::POINT_CLOUD) {
93  multiple = true;
94  exclusive = true;
95  return true;
96  }
97  return false;
98 }
99 
100 // Helper: save pose information
101 static void SavePoseInformation(e57::StructureNode& parentNode,
102  const e57::ImageFile& imf,
103  const ccGLMatrixd& poseMat) {
105  parentNode.set("pose", pose);
106 
107  cloudViewer::SquareMatrixd transMat(poseMat.data(), true);
108  double q[4];
109  if (transMat.toQuaternion(q)) {
110  e57::StructureNode rotation = e57::StructureNode(imf);
111  rotation.set("w", e57::FloatNode(imf, q[0]));
112  rotation.set("x", e57::FloatNode(imf, q[1]));
113  rotation.set("y", e57::FloatNode(imf, q[2]));
114  rotation.set("z", e57::FloatNode(imf, q[3]));
115  pose.set("rotation", rotation);
116  }
117 
118  // translation
119  {
120  e57::StructureNode translation = e57::StructureNode(imf);
121  translation.set("x", e57::FloatNode(imf, poseMat.getTranslation()[0]));
122  translation.set("y", e57::FloatNode(imf, poseMat.getTranslation()[1]));
123  translation.set("z", e57::FloatNode(imf, poseMat.getTranslation()[2]));
124  pose.set("translation", translation);
125  }
126 }
127 
128 static bool SaveScan(ccPointCloud* cloud,
129  e57::StructureNode& scanNode,
130  e57::ImageFile& imf,
131  e57::VectorNode& data3D,
132  QString& guidStr,
133  ecvProgressDialog* progressDlg = nullptr) {
134  assert(cloud);
135 
136  unsigned pointCount = cloud->size();
137  if (pointCount == 0) {
138  CVLog::Error(QString("[E57Filter::SaveScan] Cloud '%1' is empty!")
139  .arg(cloud->getName()));
140  return false;
141  }
142 
143  ccGLMatrixd localPoseMat;
144  ccGLMatrixd shiftedPoseMat;
145  bool hasPoseMat = false;
146 
147  double globalScale = 1.0;
148  bool isScaled = false;
149  {
150  globalScale = cloud->getGlobalScale();
151  assert(globalScale != 0);
152  isScaled = (globalScale != 1.0);
153 
154  // restore initial pose (if any)
155  QString poseStr = cloud->getMetaData(s_e57PoseKey).toString();
156  if (!poseStr.isEmpty()) {
157  localPoseMat = ccGLMatrixd::FromString(poseStr, hasPoseMat);
158  if (hasPoseMat) {
159  // apply transformation history
160  localPoseMat =
161  ccGLMatrixd(
162  cloud->getGLTransformationHistory().data()) *
163  localPoseMat;
164  } else {
166  "[E57Filter::saveFile] Pose meta-data is invalid");
167  }
168  }
169 
170  // we apply the global shift as a pose matrix
171  CCVector3d Tshift = cloud->getGlobalShift();
172  if (Tshift.norm2d() != 0) {
173  shiftedPoseMat = localPoseMat;
174  shiftedPoseMat.setTranslation(
175  (localPoseMat.getTranslationAsVec3D() - Tshift).u);
176  SavePoseInformation(scanNode, imf, shiftedPoseMat);
177  } else if (hasPoseMat) {
178  shiftedPoseMat = localPoseMat;
179  SavePoseInformation(scanNode, imf, shiftedPoseMat);
180  }
181  }
182 
183  CCVector3d bbMin;
184  CCVector3d bbMax;
185  if (hasPoseMat) {
186  // we have to compute the rotated cloud bounding-box!
187  for (unsigned i = 0; i < pointCount; ++i) {
188  const CCVector3* Plocal = cloud->getPointPersistentPtr(i);
189  CCVector3d Pg = CCVector3d::fromArray(Plocal->u) / globalScale;
190  // Pg = shiftedPoseMat * Pg; //DGM: according to E57 specifications,
191  // the bounding-box is local
192  if (i != 0) {
193  bbMin.x = std::min(bbMin.x, Pg.x);
194  bbMin.y = std::min(bbMin.y, Pg.y);
195  bbMin.z = std::min(bbMin.z, Pg.z);
196  bbMax.x = std::max(bbMax.x, Pg.x);
197  bbMax.y = std::max(bbMax.y, Pg.y);
198  bbMax.z = std::max(bbMax.z, Pg.z);
199  } else {
200  bbMax.x = bbMin.x = Pg.x;
201  bbMax.y = bbMin.y = Pg.y;
202  bbMax.z = bbMin.z = Pg.z;
203  }
204  }
205  } else {
206  if (!cloud->getOwnGlobalBB(bbMin, bbMax)) {
207  CVLog::Error(QString("[E57Filter::SaveScan] Internal error: cloud "
208  "'%1' has an invalid bounding box?!")
209  .arg(cloud->getName()));
210  return false;
211  }
212  }
213 
214  // GUID
215  scanNode.set("guid",
216  e57::StringNode(imf, guidStr.toStdString())); // required
217 
218  // Name
219  if (!cloud->getName().isEmpty())
220  scanNode.set("name",
221  e57::StringNode(imf, cloud->getName().toStdString()));
222  else
223  scanNode.set("name",
224  e57::StringNode(imf, QString("Scan %1")
225  .arg(s_absoluteScanIndex)
226  .toStdString()));
227 
228  // Description
229  scanNode.set("description",
230  e57::StringNode(imf, FileIO::createdBy().toStdString()));
231 
232  // Original GUIDs (TODO)
233  // if (originalGuids.size() > 0 )
234  //{
235  // scanNode.set("originalGuids", e57::VectorNode(imf));
236  // e57::VectorNode originalGuids(scanNode.get("originalGuids"));
237  // for(unsigned i = 0; i < data3DHeader.originalGuids.size(); i++)
238  // originalGuids.append(e57::StringNode(imf,originalGuids[i]));
239  // }
240 
241  // Add various sensor and version strings to scan (TODO)
242  // scan.set("sensorVendor",
243  // e57::StringNode(imf,sensorVendor)); scan.set("sensorModel",
244  // e57::StringNode(imf,sensorModel)); scan.set("sensorSerialNumber",
245  // e57::StringNode(imf,sensorSerialNumber));
246  // scan.set("sensorHardwareVersion",
247  // e57::StringNode(imf,sensorHardwareVersion));
248  // scan.set("sensorSoftwareVersion",
249  // e57::StringNode(imf,sensorSoftwareVersion));
250  // scan.set("sensorFirmwareVersion",
251  // e57::StringNode(imf,sensorFirmwareVersion));
252 
253  // Add temp/humidity to scan (TODO)
254  // scanNode.set("temperature",
255  // e57::FloatNode(imf,temperature)); scanNode.set("relativeHumidity",
256  // e57::FloatNode(imf,relativeHumidity));
257  // scanNode.set("atmosphericPressure",
258  // e57::FloatNode(imf,atmosphericPressure));
259 
260  // No index bounds for unstructured clouds!
261  // But we can still have multiple return indexes
262  ccScalarField* returnIndexSF = nullptr;
263  int minReturnIndex = 0;
264  int maxReturnIndex = 0;
265  {
266  int returnIndexSFIndex = cloud->getScalarFieldIndexByName(
267  CC_E57_RETURN_INDEX_FIELD_NAME);
268  if (returnIndexSFIndex >= 0) {
269  ccScalarField* sf = static_cast<ccScalarField*>(
270  cloud->getScalarField(returnIndexSFIndex));
271  assert(sf);
272 
273  assert(sf->getMin() >= 0);
274  {
275  // get min and max index
276  double minIndex = static_cast<double>(sf->getMin());
277  double maxIndex = static_cast<double>(sf->getMax());
278 
279  double intMin = 0.0;
280  double intMax = 0.0;
281  double fracMin = modf(minIndex, &intMin);
282  double fracMax = modf(maxIndex, &intMax);
283 
284  if (fracMin == 0 && fracMax == 0 &&
285  static_cast<int>(intMax - intMin) < 256) {
286  int minScanIndex = static_cast<int>(intMin);
287  int maxScanIndex = static_cast<int>(intMax);
288 
289  minReturnIndex = minScanIndex;
290  maxReturnIndex = maxScanIndex;
291  if (maxReturnIndex > minReturnIndex) {
292  returnIndexSF = sf;
293 
294  // DGM FIXME: should we really save this for an
295  // unstructured point cloud?
297  ibox.set("rowMinimum", e57::IntegerNode(imf, 0));
298  ibox.set("rowMaximum",
299  e57::IntegerNode(imf, cloud->size() - 1));
300  ibox.set("columnMinimum", e57::IntegerNode(imf, 0));
301  ibox.set("columnMaximum", e57::IntegerNode(imf, 0));
302  ibox.set("returnMinimum",
303  e57::IntegerNode(imf, minReturnIndex));
304  ibox.set("returnMaximum",
305  e57::IntegerNode(imf, maxReturnIndex));
306  scanNode.set("indexBounds", ibox);
307  }
308  }
309  }
310  }
311  }
312 
313  // Intensity
314  ccScalarField* intensitySF = nullptr;
315  bool hasInvalidIntensities = false;
316  {
317  int intensitySFIndex =
318  cloud->getScalarFieldIndexByName(CC_E57_INTENSITY_FIELD_NAME);
319  if (intensitySFIndex < 0) {
320  intensitySFIndex = cloud->getCurrentDisplayedScalarFieldIndex();
321  if (intensitySFIndex >= 0)
322  CVLog::Print(
323  "[E57] No 'intensity' scalar field found, we'll use "
324  "the currently displayed one instead (%s)",
325  cloud->getScalarFieldName(intensitySFIndex));
326  }
327  if (intensitySFIndex >= 0) {
328  intensitySF = static_cast<ccScalarField*>(
329  cloud->getScalarField(intensitySFIndex));
330  assert(intensitySF);
331 
333  intbox.set("intensityMinimum",
334  e57::FloatNode(imf, intensitySF->getMin()));
335  intbox.set("intensityMaximum",
336  e57::FloatNode(imf, intensitySF->getMax()));
337  scanNode.set("intensityLimits", intbox);
338 
339  // look for 'invalid' scalar values
340  for (unsigned i = 0; i < intensitySF->currentSize(); ++i) {
341  ScalarType d = intensitySF->getValue(i);
342  if (!ccScalarField::ValidValue(d)) {
343  hasInvalidIntensities = true;
344  break;
345  }
346  }
347  }
348  }
349 
350  // Color
351  bool hasColors = cloud->hasColors();
352  if (hasColors) {
353  e57::StructureNode colorbox = e57::StructureNode(imf);
354  colorbox.set("colorRedMinimum", e57::IntegerNode(imf, 0));
355  colorbox.set("colorRedMaximum", e57::IntegerNode(imf, 255));
356  colorbox.set("colorGreenMinimum", e57::IntegerNode(imf, 0));
357  colorbox.set("colorGreenMaximum", e57::IntegerNode(imf, 255));
358  colorbox.set("colorBlueMinimum", e57::IntegerNode(imf, 0));
359  colorbox.set("colorBlueMaximum", e57::IntegerNode(imf, 255));
360  scanNode.set("colorLimits", colorbox);
361  }
362 
363  // Add Cartesian bounding box to scan.
364  {
365  e57::StructureNode bboxNode = e57::StructureNode(imf);
366  bboxNode.set("xMinimum", e57::FloatNode(imf, bbMin.x));
367  bboxNode.set("xMaximum", e57::FloatNode(imf, bbMax.x));
368  bboxNode.set("yMinimum", e57::FloatNode(imf, bbMin.y));
369  bboxNode.set("yMaximum", e57::FloatNode(imf, bbMax.y));
370  bboxNode.set("zMinimum", e57::FloatNode(imf, bbMin.z));
371  bboxNode.set("zMaximum", e57::FloatNode(imf, bbMax.z));
372  scanNode.set("cartesianBounds", bboxNode);
373  }
374 
375  // Add start/stop acquisition times to scan (TODO)
376  // e57::StructureNode acquisitionStart = StructureNode(imf);
377  // scanNode.set("acquisitionStart", acquisitionStart);
378  // acquisitionStart.set("dateTimeValue",
379  // e57::FloatNode(imf, dateTimeValue));
380  // acquisitionStart.set("isAtomicClockReferenced", e57::IntegerNode(imf,
381  // isAtomicClockReferenced)); e57::StructureNode acquisitionEnd =
382  // e57::StructureNode(imf); scanNode.set("acquisitionEnd", acquisitionEnd);
383  // acquisitionEnd.set("dateTimeValue", e57::FloatNode(imf, dateTimeValue));
384  // acquisitionEnd.set("isAtomicClockReferenced", e57::IntegerNode(imf,
385  // isAtomicClockReferenced));
386 
387  // Add grouping scheme area
388  // No point grouping scheme (unstructured cloud)
389 
390  // Make a prototype of datatypes that will be stored in points record.
393 
394  // prepare temporary structures
395  const unsigned chunkSize = std::min<unsigned>(
396  pointCount, (1 << 20)); // we save the file in several steps to
397  // limit the memory consumption
398  TempArrays arrays;
399  std::vector<e57::SourceDestBuffer> dbufs;
400 
401  // Cartesian field
402  {
403  e57::FloatPrecision precision =
404  sizeof(PointCoordinateType) == 8 || isScaled ? e57::E57_DOUBLE
405  : e57::E57_SINGLE;
406 
407  CCVector3d bbCenter = (bbMin + bbMax) / 2;
408 
409  proto.set("cartesianX",
410  e57::FloatNode(imf, bbCenter.x, precision, bbMin.x, bbMax.x));
411  arrays.xData.resize(chunkSize);
412  dbufs.emplace_back(imf, "cartesianX", arrays.xData.data(), chunkSize,
413  true, true);
414 
415  proto.set("cartesianY",
416  e57::FloatNode(imf, bbCenter.y, precision, bbMin.y, bbMax.y));
417  arrays.yData.resize(chunkSize);
418  dbufs.emplace_back(imf, "cartesianY", arrays.yData.data(), chunkSize,
419  true, true);
420 
421  proto.set("cartesianZ",
422  e57::FloatNode(imf, bbCenter.z, precision, bbMin.z, bbMax.z));
423  arrays.zData.resize(chunkSize);
424  dbufs.emplace_back(imf, "cartesianZ", arrays.zData.data(), chunkSize,
425  true, true);
426  }
427 
428  // Normals
429  bool hasNormals = cloud->hasNormals();
430  if (hasNormals) {
431  e57::FloatPrecision precision = sizeof(PointCoordinateType) == 8
433  : e57::E57_SINGLE;
434 
435  proto.set("nor:normalX",
436  e57::FloatNode(imf, 0.0, precision, -1.0, 1.0));
437  arrays.xNormData.resize(chunkSize);
438  dbufs.emplace_back(imf, "nor:normalX", arrays.xNormData.data(),
439  chunkSize, true, true);
440 
441  proto.set("nor:normalY",
442  e57::FloatNode(imf, 0.0, precision, -1.0, 1.0));
443  arrays.yNormData.resize(chunkSize);
444  dbufs.emplace_back(imf, "nor:normalY", arrays.yNormData.data(),
445  chunkSize, true, true);
446 
447  proto.set("nor:normalZ",
448  e57::FloatNode(imf, 0.0, precision, -1.0, 1.0));
449  arrays.zNormData.resize(chunkSize);
450  dbufs.emplace_back(imf, "nor:normalZ", arrays.zNormData.data(),
451  chunkSize, true, true);
452  }
453 
454  // Return index
455  if (returnIndexSF) {
456  assert(maxReturnIndex > minReturnIndex);
457  proto.set("returnIndex",
458  e57::IntegerNode(imf, minReturnIndex, minReturnIndex,
459  maxReturnIndex));
460  arrays.scanIndexData.resize(chunkSize);
461  dbufs.emplace_back(imf, "returnIndex", arrays.scanIndexData.data(),
462  chunkSize, true, true);
463  }
464  // Intensity field
465  if (intensitySF) {
466  proto.set("intensity",
467  e57::FloatNode(imf, intensitySF->getMin(),
468  sizeof(ScalarType) == 8 ? e57::E57_DOUBLE
469  : e57::E57_SINGLE,
470  intensitySF->getMin(), intensitySF->getMax()));
471  arrays.intData.resize(chunkSize);
472  dbufs.emplace_back(imf, "intensity", arrays.intData.data(), chunkSize,
473  true, true);
474 
475  if (hasInvalidIntensities) {
476  proto.set("isIntensityInvalid", e57::IntegerNode(imf, 0, 0, 1));
477  arrays.isInvalidIntData.resize(chunkSize);
478  dbufs.emplace_back(imf, "isIntensityInvalid",
479  arrays.isInvalidIntData.data(), chunkSize, true,
480  true);
481  }
482  }
483 
484  // Color fields
485  if (hasColors) {
486  proto.set("colorRed", e57::IntegerNode(imf, 0, 0, 255));
487  arrays.redData.resize(chunkSize);
488  dbufs.emplace_back(imf, "colorRed", arrays.redData.data(), chunkSize,
489  true, true);
490  proto.set("colorGreen", e57::IntegerNode(imf, 0, 0, 255));
491  arrays.greenData.resize(chunkSize);
492  dbufs.emplace_back(imf, "colorGreen", arrays.greenData.data(),
493  chunkSize, true, true);
494  proto.set("colorBlue", e57::IntegerNode(imf, 0, 0, 255));
495  arrays.blueData.resize(chunkSize);
496  dbufs.emplace_back(imf, "colorBlue", arrays.blueData.data(), chunkSize,
497  true, true);
498  }
499 
500  // ignored fields
501  //"sphericalRange"
502  //"sphericalAzimuth"
503  //"sphericalElevation"
504  //"rowIndex"
505  //"columnIndex"
506  //"timeStamp"
507  //"cartesianInvalidState"
508  //"sphericalInvalidState"
509  //"isColorInvalid"
510  //"isTimeStampInvalid"
511 
512  // Make empty codecs vector for use in creating points CompressedVector.
515  e57::VectorNode codecs = e57::VectorNode(imf, true);
516 
517  // Create CompressedVector for storing points.
520  e57::CompressedVectorNode(imf, proto, codecs);
521  scanNode.set("points", points);
522  data3D.append(scanNode);
523 
524  e57::CompressedVectorWriter writer = points.writer(dbufs);
525 
526  // progress bar
527  cloudViewer::NormalizedProgress nprogress(progressDlg, pointCount);
528  if (progressDlg) {
529  progressDlg->setMethodTitle(QObject::tr("Write E57 file"));
530  progressDlg->setInfo(QObject::tr("Scan #%1 - %2 points")
531  .arg(s_absoluteScanIndex)
532  .arg(pointCount));
533  progressDlg->start();
534  QApplication::processEvents();
535  }
536 
537  ccGLMatrix inversePoseMat;
538  if (hasPoseMat) {
539  inversePoseMat = ccGLMatrix(localPoseMat.inverse().data());
540  }
541 
542  unsigned index = 0;
543  unsigned remainingPointCount = pointCount;
544  while (remainingPointCount != 0) {
545  unsigned thisChunkSize = std::min(remainingPointCount, chunkSize);
546 
547  // load arrays
548  for (unsigned i = 0; i < thisChunkSize; ++i, ++index) {
549  const CCVector3* P = cloud->getPointPersistentPtr(index);
550  // CCVector3d Pglobal = cloud->toGlobal3d<PointCoordinateType>(*P);
551  CCVector3d Pglobal = CCVector3d::fromArray(P->u) / globalScale;
552  if (hasPoseMat) {
553  Pglobal = inversePoseMat * Pglobal;
554  }
555  arrays.xData[i] = Pglobal.x;
556  arrays.yData[i] = Pglobal.y;
557  arrays.zData[i] = Pglobal.z;
558 
559  if (intensitySF) {
560  assert(!arrays.intData.empty());
561  ScalarType sfVal = intensitySF->getValue(index);
562  arrays.intData[i] = static_cast<double>(sfVal);
563  if (!arrays.isInvalidIntData.empty())
564  arrays.isInvalidIntData[i] =
565  ccScalarField::ValidValue(sfVal) ? 0 : 1;
566  }
567 
568  if (hasNormals) {
569  const CCVector3& N = cloud->getPointNormal(index);
570  arrays.xNormData[i] = static_cast<double>(N.x);
571  arrays.yNormData[i] = static_cast<double>(N.y);
572  arrays.zNormData[i] = static_cast<double>(N.z);
573  }
574 
575  if (hasColors) {
576  // Normalize color to 0 - 255
577  const ecvColor::Rgb& C = cloud->getPointColor(index);
578  arrays.redData[i] = static_cast<double>(C.r);
579  arrays.greenData[i] = static_cast<double>(C.g);
580  arrays.blueData[i] = static_cast<double>(C.b);
581  }
582 
583  if (returnIndexSF) {
584  assert(!arrays.scanIndexData.empty());
585  arrays.scanIndexData[i] =
586  static_cast<int8_t>(returnIndexSF->getValue(index));
587  }
588 
589  if (!nprogress.oneStep()) {
590  QApplication::processEvents();
591  s_cancelRequestedByUser = true;
592  break;
593  }
594  }
595 
596  writer.write(thisChunkSize);
597 
598  assert(thisChunkSize <= remainingPointCount);
599  remainingPointCount -= thisChunkSize;
600  }
601 
602  writer.close();
603 
604  return true;
605 }
606 
607 void SaveImage(const ccImage* image,
608  const QString& scanGUID,
609  e57::ImageFile& imf,
610  e57::VectorNode& images2D) {
611  assert(image);
612 
613  e57::StructureNode imageNode = e57::StructureNode(imf);
614 
615  // GUID
616  imageNode.set(
617  "guid",
618  e57::StringNode(imf, GetNewGuid().toStdString())); // required
619 
620  // Name
621  if (!image->getName().isEmpty())
622  imageNode.set("name",
623  e57::StringNode(imf, image->getName().toStdString()));
624  else
625  imageNode.set("name",
626  e57::StringNode(imf, QString("Image %1")
627  .arg(s_absoluteImageIndex)
628  .toStdString()));
629 
630  // Description
631  // imageNode.set("description", e57::StringNode(imf, "Imported from
632  // ACloudViewer (EDF R&D / Telecom ParisTech)"));
633 
634  // Add various sensor and version strings to image (TODO)
635  // scan.set("sensorVendor",
636  // e57::StringNode(imf,sensorVendor)); scan.set("sensorModel",
637  // e57::StringNode(imf,sensorModel)); scan.set("sensorSerialNumber",
638  // e57::StringNode(imf,sensorSerialNumber));
639 
640  // Add temp/humidity to scan (TODO)
641  // scanNode.set("temperature",
642  // e57::FloatNode(imf,temperature)); scanNode.set("relativeHumidity",
643  // e57::FloatNode(imf,relativeHumidity));
644  // scanNode.set("atmosphericPressure",
645  // e57::FloatNode(imf,atmosphericPressure));
646 
647  imageNode.set("associatedData3DGuid",
648  e57::StringNode(imf, scanGUID.toStdString()));
649 
650  // acquisitionDateTime
651  {
652  // e57::StructureNode acquisitionDateTime = e57::StructureNode(imf);
653  // imageNode.set("acquisitionDateTime", acquisitionDateTime);
654  // acquisitionDateTime.set("dateTimeValue", e57::FloatNode(imf,
655  // dateTimeValue)); acquisitionDateTime.set("isAtomicClockReferenced",
656  // e57::IntegerNode(imf, isAtomicClockReferenced));
657  }
658 
659  // Create pose structure for scan (if any)
660  if (image->isA(CV_TYPES::CALIBRATED_IMAGE)) {
661  const ccCameraSensor* sensor =
662  static_cast<const ccImage*>(image)->getAssociatedSensor();
663  if (sensor) {
664  ccIndexedTransformation poseMat;
665  if (sensor->getActiveAbsoluteTransformation(poseMat)) {
666  SavePoseInformation(imageNode, imf,
667  ccGLMatrixd(poseMat.data()));
668  }
669  }
670  }
671 
672  // save image data as PNG
673  QByteArray ba;
674  {
675  QBuffer buffer(&ba);
676  buffer.open(QIODevice::WriteOnly);
677  image->data().save(&buffer,
678  "PNG"); // writes image into ba in PNG format
679  }
680  int imageSize = ba.size();
681 
682  e57::StructureNode cameraRepresentation = e57::StructureNode(imf);
683  QString cameraRepresentationStr("visualReferenceRepresentation");
684 
685  e57::BlobNode blob(imf, imageSize);
686  cameraRepresentation.set("pngImage", blob);
687  cameraRepresentation.set("imageHeight",
688  e57::IntegerNode(imf, image->getH()));
689  cameraRepresentation.set("imageWidth",
690  e57::IntegerNode(imf, image->getW()));
691 
692  //'pinhole' camera image
693  // if (image->isKindOf(CV_TYPES::IMAGE))
694  //{
695  // cameraRepresentationStr = "pinholeRepresentation";
696  // ccImage* calibImage = static_cast<ccImage*>(image);
697  // //DGM FIXME
698  // cameraRepresentation.set("focalLength", e57::FloatNode(imf,
699  // calibImage->getFocal())); cameraRepresentation.set("pixelHeight",
700  // e57::FloatNode(imf, image2DHeader.pinholeRepresentation.pixelHeight));
701  // cameraRepresentation.set("pixelWidth", e57::FloatNode(imf,
702  // image2DHeader.pinholeRepresentation.pixelWidth));
703  // cameraRepresentation.set("principalPointX", e57::FloatNode(imf,
704  // static_cast<double>(image->getW())/2.0));
705  // cameraRepresentation.set("principalPointY", e57::FloatNode(imf,
706  // static_cast<double>(image->getH())/2.0));
707  //}
708  // else //standard image (TODO: handle cylindrical and spherical cameras!)
709  //{
710  // //nothing to do
711  //}
712 
713  imageNode.set(cameraRepresentationStr.toStdString(), cameraRepresentation);
714  images2D.append(imageNode);
715  blob.write((uint8_t*)ba.data(), 0, static_cast<size_t>(imageSize));
716 }
717 
719  const QString& filename,
720  const SaveParameters& parameters) {
721  // we assume the input entity is either a cloud or a group of clouds
722  // (=multiple scans)
723  std::vector<ccPointCloud*> scans;
724 
725  if (entity->isA(CV_TYPES::POINT_CLOUD)) {
726  scans.push_back(static_cast<ccPointCloud*>(entity));
727  } else {
728  for (unsigned i = 0; i < entity->getChildrenNumber(); ++i) {
729  if (entity->getChild(i)->isA(CV_TYPES::POINT_CLOUD)) {
730  scans.push_back(
731  static_cast<ccPointCloud*>(entity->getChild(i)));
732  }
733  }
734  }
735 
736  if (scans.empty()) return CC_FERR_NO_SAVE;
737 
739 
740  try {
741  e57::ImageFile imf(qPrintable(filename),
742  "w"); // DGM: warning, toStdString doesn't preserve
743  // "local" characters
744  if (!imf.isOpen()) return CC_FERR_WRITING;
745 
746  // get root
747  e57::StructureNode root = imf.root();
748 
749  // header info
750 
756 
757  // Set per-file properties.
760  root.set("formatName",
761  e57::StringNode(imf, "ASTM E57 3D Imaging Data File"));
762  root.set("guid", e57::StringNode(imf, GetNewGuid().toStdString()));
763 
764  // Get ASTM version number supported by library, so can write it into
765  // file
766  int astmMajor;
767  int astmMinor;
768  e57::ustring libraryId;
769  e57::Utilities::getVersions(astmMajor, astmMinor, libraryId);
770 
771  root.set("versionMajor", e57::IntegerNode(imf, astmMajor));
772  root.set("versionMinor", e57::IntegerNode(imf, astmMinor));
773  root.set("e57LibraryVersion", e57::StringNode(imf, libraryId));
774 
775  // Save a dummy string for coordinate system.
778  root.set("coordinateMetadata", e57::StringNode(imf, ""));
779 
780  // Create creationDateTime structure
782  e57::StructureNode creationDateTime = e57::StructureNode(imf);
783  creationDateTime.set("dateTimeValue", e57::FloatNode(imf, 0.0));
784  creationDateTime.set("isAtomicClockReferenced",
785  e57::IntegerNode(imf, 0));
786  root.set("creationDateTime", creationDateTime);
787 
788  // 3D data
789  e57::VectorNode data3D(imf, true);
790  root.set("data3D", data3D);
791 
792  // Images
793  e57::VectorNode images2D(imf, true);
794  root.set("images2D", images2D);
795 
796  // we store (temporarily) the saved scans associated with
797  // their unique GUID in a map (to retrieve them later if
798  // necessary - for example to associate them with images)
799  QMap<ccHObject*, QString> scansGUID;
800  s_absoluteScanIndex = 0;
801 
802  // progress dialog
803  QScopedPointer<ecvProgressDialog> progressDlg(nullptr);
804  if (parameters.parentWidget) {
805  progressDlg.reset(
806  new ecvProgressDialog(true, parameters.parentWidget));
807  progressDlg->setAutoClose(false);
808  }
809  s_cancelRequestedByUser = false;
810 
811  // Extension for normals
812  bool hasNormals = false;
813 
814  for (auto cloud : scans) {
815  QString scanGUID = GetNewGuid();
816 
817  // we should only add the "normals" extension once
818  if (!hasNormals && cloud->hasNormals()) {
819  hasNormals = true;
820  imf.extensionsAdd(
821  "nor",
822  "http://www.libe57.org/E57_NOR_surface_normals.txt");
823  }
824 
825  // create corresponding node
826  e57::StructureNode scanNode = e57::StructureNode(imf);
827  if (SaveScan(cloud, scanNode, imf, data3D, scanGUID,
828  progressDlg.data())) {
829  ++s_absoluteScanIndex;
830  scansGUID.insert(cloud, scanGUID);
831  } else {
833  break;
834  }
835 
836  if (s_cancelRequestedByUser) {
838  break;
839  }
840  }
841 
842  if (result == CC_FERR_NO_ERROR) {
843  // Save images
844  s_absoluteImageIndex = 0;
845  size_t scanCount = scans.size();
846  for (size_t i = 0; i < scanCount; ++i) {
847  ccPointCloud* cloud = scans[i];
848  ccHObject::Container images;
849  unsigned imageCount =
850  cloud->filterChildren(images, false, CV_TYPES::IMAGE);
851 
852  if (imageCount != 0) {
853  // progress bar
855  progressDlg.data(), imageCount);
856  if (progressDlg) {
857  progressDlg->setMethodTitle(
858  QObject::tr("Write E57 file"));
859  progressDlg->setInfo(
860  QObject::tr("Cloud #%1 - Images: %2")
861  .arg(i)
862  .arg(imageCount));
863  progressDlg->start();
864  QApplication::processEvents();
865  }
866 
867  for (unsigned j = 0; j < imageCount; ++j) {
868  assert(images[j]->isKindOf(CV_TYPES::IMAGE));
869  assert(scansGUID.contains(cloud));
870  QString scanGUID = scansGUID.value(cloud);
871  SaveImage(static_cast<ccImage*>(images[j]), scanGUID,
872  imf, images2D);
873  ++s_absoluteImageIndex;
874  if (!nprogress.oneStep()) {
875  s_cancelRequestedByUser = true;
876  i = scanCount; // double break!
878  break;
879  }
880  }
881  }
882  }
883  }
884 
885  imf.close();
886  } catch (const e57::E57Exception& e) {
888  QStringLiteral("[E57] Error: %1 (%2 line %3)")
890  .c_str())
891  .arg(e.sourceFileName())
892  .arg(e.sourceLineNumber()));
893 
894  if (!e.context().empty()) {
895  CVLog::Warning(QStringLiteral(" context: %1")
896  .arg(QString::fromStdString(e.context())));
897  }
898 
900  } catch (...) {
901  CVLog::Warning("[E57] Unknown error");
903  }
904 
905  return result;
906 }
907 
908 static bool NodeStructureToTree(ccHObject* currentTreeNode,
909  const e57::Node& currentE57Node) {
910  assert(currentTreeNode);
911  ccHObject* obj = new ccHObject(currentE57Node.elementName().c_str());
912  currentTreeNode->addChild(obj);
913 
914  e57::ustring name = currentE57Node.elementName();
915  QString infoStr = QString(name.c_str() == nullptr || name.c_str()[0] == 0
916  ? "No name"
917  : name.c_str());
918 
919  switch (currentE57Node.type()) {
920  case e57::E57_STRUCTURE: {
921  infoStr += QString(" [STRUCTURE]");
923  static_cast<e57::StructureNode>(currentE57Node);
924  for (int64_t i = 0; i < s.childCount(); ++i)
925  NodeStructureToTree(obj, s.get(i));
926  } break;
927  case e57::E57_VECTOR: {
928  infoStr += QString(" [VECTOR]");
929  e57::VectorNode v = static_cast<e57::VectorNode>(currentE57Node);
930  for (int64_t i = 0; i < v.childCount(); ++i)
931  NodeStructureToTree(obj, v.get(i));
932  } break;
935  static_cast<e57::CompressedVectorNode>(currentE57Node);
936  infoStr += QString(" [COMPRESSED VECTOR (%1 elements)]")
937  .arg(cv.childCount());
938  } break;
939  case e57::E57_INTEGER: {
940  e57::IntegerNode i = static_cast<e57::IntegerNode>(currentE57Node);
941  infoStr += QString(" [INTEGER: %1]").arg(i.value());
942  } break;
945  static_cast<e57::ScaledIntegerNode>(currentE57Node);
946  infoStr += QString(" [SCALED INTEGER: %1]").arg(si.scaledValue());
947  } break;
948  case e57::E57_FLOAT: {
949  e57::FloatNode f = static_cast<e57::FloatNode>(currentE57Node);
950  infoStr += QString(" [FLOAT: %1]").arg(f.value());
951  } break;
952  case e57::E57_STRING: {
953  e57::StringNode s = static_cast<e57::StringNode>(currentE57Node);
954  infoStr += QString(" [STRING: %1]").arg(s.value().c_str());
955  } break;
956  case e57::E57_BLOB: {
957  e57::BlobNode b = static_cast<e57::BlobNode>(currentE57Node);
958  infoStr += QString(" [BLOB (%1 bytes)]").arg(b.byteCount());
959  } break;
960  default: {
961  infoStr += QString("[INVALID]");
962  obj->setName(infoStr);
963  return false;
964  }
965  }
966 
967  obj->setName(infoStr);
968  return true;
969 }
970 
971 static void NodeToConsole(const e57::Node& node) {
972  QString infoStr = QString("[E57] '%1' - ").arg(node.elementName().c_str());
973  switch (node.type()) {
974  case e57::E57_STRUCTURE: {
975  e57::StructureNode s = static_cast<e57::StructureNode>(node);
976  infoStr += QString("STRUCTURE, %1 child(ren)").arg(s.childCount());
977  } break;
978  case e57::E57_VECTOR: {
979  e57::VectorNode v = static_cast<e57::VectorNode>(node);
980  infoStr += QString("VECTOR, %1 child(ren)").arg(v.childCount());
981  } break;
984  static_cast<e57::CompressedVectorNode>(node);
985  infoStr += QString("COMPRESSED VECTOR, %1 elements")
986  .arg(cv.childCount());
987  } break;
988  case e57::E57_INTEGER: {
989  e57::IntegerNode i = static_cast<e57::IntegerNode>(node);
990  infoStr += QString("%1 (INTEGER)").arg(i.value());
991  } break;
994  static_cast<e57::ScaledIntegerNode>(node);
995  infoStr += QString("%1 (SCALED INTEGER)").arg(si.scaledValue());
996  } break;
997  case e57::E57_FLOAT: {
998  e57::FloatNode f = static_cast<e57::FloatNode>(node);
999  infoStr += QString("%1 (FLOAT)").arg(f.value());
1000  } break;
1001  case e57::E57_STRING: {
1002  e57::StringNode s = static_cast<e57::StringNode>(node);
1003  infoStr += QString(s.value().c_str());
1004  } break;
1005  case e57::E57_BLOB: {
1006  e57::BlobNode b = static_cast<e57::BlobNode>(node);
1007  infoStr += QString("BLOB, size=%1").arg(b.byteCount());
1008  } break;
1009  default: {
1010  infoStr += QString("INVALID");
1011  } break;
1012  }
1013 
1014  CVLog::Print(infoStr);
1015 }
1016 
1017 static bool ChildNodeToConsole(const e57::Node& node, const char* childName) {
1018  assert(childName);
1019  if (node.type() == e57::E57_STRUCTURE) {
1020  e57::StructureNode s = static_cast<e57::StructureNode>(node);
1021  if (!s.isDefined(childName)) {
1022  CVLog::Warning("[E57] Couldn't find element named '%s'", childName);
1023  return false;
1024  } else {
1025  try {
1026  NodeToConsole(s.get(childName));
1027  } catch (e57::E57Exception& ex) {
1028  ex.report(__FILE__, __LINE__, __FUNCTION__);
1029  return false;
1030  }
1031  }
1032  } else if (node.type() == e57::E57_VECTOR) {
1033  e57::VectorNode v = static_cast<e57::VectorNode>(node);
1034  if (!v.isDefined(childName)) {
1035  CVLog::Warning("[E57] Couldn't find element named '%s'", childName);
1036  return false;
1037  } else {
1038  try {
1039  NodeToConsole(v.get(childName));
1040  } catch (e57::E57Exception& ex) {
1041  ex.report(__FILE__, __LINE__, __FUNCTION__);
1042  return false;
1043  }
1044  }
1045  } else {
1047  "[E57] Element '%s' has no child (not a structure nor a "
1048  "vector!)",
1049  node.elementName().c_str());
1050  return false;
1051  }
1052 
1053  return true;
1054 }
1055 
1056 // Freely inspired from "E57 Simple API" by Stan Coleby
1057 static void DecodePrototype(const e57::StructureNode& scan,
1058  const e57::StructureNode& proto,
1059  E57ScanHeader& header) {
1060  // Get a prototype of datatypes that will be stored in points record.
1061  header.pointFields.cartesianXField = proto.isDefined("cartesianX");
1062  header.pointFields.cartesianYField = proto.isDefined("cartesianY");
1063  header.pointFields.cartesianZField = proto.isDefined("cartesianZ");
1065  proto.isDefined("cartesianInvalidState");
1066 
1067  header.pointFields.pointRangeScaledInteger = 0; // FloatNode
1068  header.pointFields.pointRangeMinimum = 0;
1069  header.pointFields.pointRangeMaximum = 0;
1070 
1071  if (proto.isDefined("cartesianX")) {
1072  if (proto.get("cartesianX").type() == e57::E57_SCALED_INTEGER) {
1073  double scale =
1074  e57::ScaledIntegerNode(proto.get("cartesianX")).scale();
1075  double offset =
1076  e57::ScaledIntegerNode(proto.get("cartesianX")).offset();
1077  int64_t minimum =
1078  e57::ScaledIntegerNode(proto.get("cartesianX")).minimum();
1079  int64_t maximum =
1080  e57::ScaledIntegerNode(proto.get("cartesianX")).maximum();
1081  header.pointFields.pointRangeMinimum = minimum * scale + offset;
1082  header.pointFields.pointRangeMaximum = maximum * scale + offset;
1083  header.pointFields.pointRangeScaledInteger = scale;
1084 
1085  } else if (proto.get("cartesianX").type() == e57::E57_FLOAT) {
1087  e57::FloatNode(proto.get("cartesianX")).minimum();
1089  e57::FloatNode(proto.get("cartesianX")).maximum();
1090  }
1091  } else if (proto.isDefined("sphericalRange")) {
1092  if (proto.get("sphericalRange").type() == e57::E57_SCALED_INTEGER) {
1093  double scale =
1094  e57::ScaledIntegerNode(proto.get("sphericalRange")).scale();
1095  double offset = e57::ScaledIntegerNode(proto.get("sphericalRange"))
1096  .offset();
1097  int64_t minimum =
1098  e57::ScaledIntegerNode(proto.get("sphericalRange"))
1099  .minimum();
1100  int64_t maximum =
1101  e57::ScaledIntegerNode(proto.get("sphericalRange"))
1102  .maximum();
1103  header.pointFields.pointRangeMinimum = minimum * scale + offset;
1104  header.pointFields.pointRangeMaximum = maximum * scale + offset;
1105  header.pointFields.pointRangeScaledInteger = scale;
1106 
1107  } else if (proto.get("sphericalRange").type() == e57::E57_FLOAT) {
1109  e57::FloatNode(proto.get("sphericalRange")).minimum();
1111  e57::FloatNode(proto.get("sphericalRange")).maximum();
1112  }
1113  }
1114 
1115  header.pointFields.sphericalRangeField = proto.isDefined("sphericalRange");
1117  proto.isDefined("sphericalAzimuth");
1119  proto.isDefined("sphericalElevation");
1121  proto.isDefined("sphericalInvalidState");
1122 
1123  header.pointFields.angleScaledInteger = 0.; // FloatNode
1124  header.pointFields.angleMinimum = 0.;
1125  header.pointFields.angleMaximum = 0.;
1126 
1127  if (proto.isDefined("sphericalAzimuth")) {
1128  if (proto.get("sphericalAzimuth").type() == e57::E57_SCALED_INTEGER) {
1129  double scale = e57::ScaledIntegerNode(proto.get("sphericalAzimuth"))
1130  .scale();
1131  double offset =
1132  e57::ScaledIntegerNode(proto.get("sphericalAzimuth"))
1133  .offset();
1134  int64_t minimum =
1135  e57::ScaledIntegerNode(proto.get("sphericalAzimuth"))
1136  .minimum();
1137  int64_t maximum =
1138  e57::ScaledIntegerNode(proto.get("sphericalAzimuth"))
1139  .maximum();
1140  header.pointFields.angleMinimum = minimum * scale + offset;
1141  header.pointFields.angleMaximum = maximum * scale + offset;
1142  header.pointFields.angleScaledInteger = scale;
1143 
1144  } else if (proto.get("sphericalAzimuth").type() == e57::E57_FLOAT) {
1145  header.pointFields.angleMinimum =
1146  e57::FloatNode(proto.get("sphericalAzimuth")).minimum();
1147  header.pointFields.angleMaximum =
1148  e57::FloatNode(proto.get("sphericalAzimuth")).maximum();
1149  }
1150  }
1151 
1152  header.pointFields.rowIndexField = proto.isDefined("rowIndex");
1153  header.pointFields.columnIndexField = proto.isDefined("columnIndex");
1154  header.pointFields.rowIndexMaximum = 0;
1155  header.pointFields.columnIndexMaximum = 0;
1156 
1157  if (proto.isDefined("rowIndex")) {
1158  header.pointFields.rowIndexMaximum = static_cast<uint32_t>(
1159  e57::IntegerNode(proto.get("rowIndex")).maximum());
1160  }
1161 
1162  if (proto.isDefined("columnIndex")) {
1163  header.pointFields.columnIndexMaximum = static_cast<uint32_t>(
1164  e57::IntegerNode(proto.get("columnIndex")).maximum());
1165  }
1166 
1167  header.pointFields.returnIndexField = proto.isDefined("returnIndex");
1168  header.pointFields.returnCountField = proto.isDefined("returnCount");
1169  header.pointFields.returnMaximum = 0;
1170 
1171  if (proto.isDefined("returnIndex")) {
1172  header.pointFields.returnMaximum = static_cast<uint8_t>(
1173  e57::IntegerNode(proto.get("returnIndex")).maximum());
1174  }
1175 
1176  header.pointFields.normXField = proto.isDefined("nor:normalX");
1177  header.pointFields.normYField = proto.isDefined("nor:normalY");
1178  header.pointFields.normZField = proto.isDefined("nor:normalZ");
1179 
1180  if (proto.isDefined("nor:normalX")) {
1181  if (proto.get("nor:normalX").type() == e57::E57_SCALED_INTEGER) {
1182  double scale =
1183  e57::ScaledIntegerNode(proto.get("nor:normalX")).scale();
1184  double offset =
1185  e57::ScaledIntegerNode(proto.get("nor:normalX")).offset();
1186  int64_t minimum =
1187  e57::ScaledIntegerNode(proto.get("nor:normalX")).minimum();
1188  int64_t maximum =
1189  e57::ScaledIntegerNode(proto.get("nor:normalX")).maximum();
1190  header.pointFields.normRangeMinimum = minimum * scale + offset;
1191  header.pointFields.normRangeMaximum = maximum * scale + offset;
1192  header.pointFields.normRangeScaledInteger = scale;
1193 
1194  } else if (proto.get("nor:normalX").type() == e57::E57_FLOAT) {
1195  header.pointFields.normRangeMinimum =
1196  e57::FloatNode(proto.get("nor:normalX")).minimum();
1197  header.pointFields.normRangeMaximum =
1198  e57::FloatNode(proto.get("nor:normalX")).maximum();
1199  }
1200  }
1201 
1202  header.pointFields.timeStampField = proto.isDefined("timeStamp");
1204  proto.isDefined("isTimeStampInvalid");
1205  header.pointFields.timeMaximum = 0.;
1206 
1207  if (proto.isDefined("timeStamp")) {
1208  if (proto.get("timeStamp").type() == e57::E57_INTEGER)
1209  header.pointFields.timeMaximum = static_cast<double>(
1210  e57::IntegerNode(proto.get("timeStamp")).maximum());
1211  else if (proto.get("timeStamp").type() == e57::E57_FLOAT)
1212  header.pointFields.timeMaximum = static_cast<double>(
1213  e57::FloatNode(proto.get("timeStamp")).maximum());
1214  }
1215 
1216  header.pointFields.intensityField = proto.isDefined("intensity");
1218  proto.isDefined("isIntensityInvalid");
1219  header.pointFields.intensityScaledInteger = 0.;
1220 
1221  header.intensityLimits.intensityMinimum = 0.;
1222  header.intensityLimits.intensityMaximum = 0.;
1223 
1224  if (scan.isDefined("intensityLimits")) {
1225  e57::StructureNode intbox(scan.get("intensityLimits"));
1226  if (intbox.get("intensityMaximum").type() == e57::E57_SCALED_INTEGER) {
1228  e57::ScaledIntegerNode(intbox.get("intensityMaximum"))
1229  .scaledValue();
1231  e57::ScaledIntegerNode(intbox.get("intensityMinimum"))
1232  .scaledValue();
1233  } else if (intbox.get("intensityMaximum").type() == e57::E57_FLOAT) {
1235  e57::FloatNode(intbox.get("intensityMaximum")).value();
1237  e57::FloatNode(intbox.get("intensityMinimum")).value();
1238  } else if (intbox.get("intensityMaximum").type() == e57::E57_INTEGER) {
1239  header.intensityLimits.intensityMaximum = static_cast<double>(
1240  e57::IntegerNode(intbox.get("intensityMaximum")).value());
1241  header.intensityLimits.intensityMinimum = static_cast<double>(
1242  e57::IntegerNode(intbox.get("intensityMinimum")).value());
1243  }
1244  }
1245 
1246  if (proto.isDefined("intensity")) {
1247  if (proto.get("intensity").type() == e57::E57_INTEGER) {
1248  if (header.intensityLimits.intensityMaximum == 0.) {
1249  header.intensityLimits.intensityMinimum = static_cast<double>(
1250  e57::IntegerNode(proto.get("intensity")).minimum());
1251  header.intensityLimits.intensityMaximum = static_cast<double>(
1252  e57::IntegerNode(proto.get("intensity")).maximum());
1253  }
1254  header.pointFields.intensityScaledInteger = -1.;
1255 
1256  } else if (proto.get("intensity").type() == e57::E57_SCALED_INTEGER) {
1257  double scale =
1258  e57::ScaledIntegerNode(proto.get("intensity")).scale();
1259  double offset =
1260  e57::ScaledIntegerNode(proto.get("intensity")).offset();
1261 
1262  if (header.intensityLimits.intensityMaximum == 0.) {
1263  int64_t minimum = e57::ScaledIntegerNode(proto.get("intensity"))
1264  .minimum();
1265  int64_t maximum = e57::ScaledIntegerNode(proto.get("intensity"))
1266  .maximum();
1268  minimum * scale + offset;
1270  maximum * scale + offset;
1271  }
1272  header.pointFields.intensityScaledInteger = scale;
1273  } else if (proto.get("intensity").type() == e57::E57_FLOAT) {
1274  if (header.intensityLimits.intensityMaximum == 0.) {
1276  e57::FloatNode(proto.get("intensity")).minimum();
1278  e57::FloatNode(proto.get("intensity")).maximum();
1279  }
1280  }
1281  }
1282 
1283  header.pointFields.colorRedField = proto.isDefined("colorRed");
1284  header.pointFields.colorGreenField = proto.isDefined("colorGreen");
1285  header.pointFields.colorBlueField = proto.isDefined("colorBlue");
1286  header.pointFields.isColorInvalidField = proto.isDefined("isColorInvalid");
1287 
1288  header.colorLimits.colorRedMinimum = 0.;
1289  header.colorLimits.colorRedMaximum = 0.;
1290  header.colorLimits.colorGreenMinimum = 0.;
1291  header.colorLimits.colorGreenMaximum = 0.;
1292  header.colorLimits.colorBlueMinimum = 0.;
1293  header.colorLimits.colorBlueMaximum = 0.;
1294 
1295  if (scan.isDefined("colorLimits")) {
1296  e57::StructureNode colorbox(scan.get("colorLimits"));
1297  if (colorbox.get("colorRedMaximum").type() == e57::E57_SCALED_INTEGER) {
1298  header.colorLimits.colorRedMaximum =
1299  e57::ScaledIntegerNode(colorbox.get("colorRedMaximum"))
1300  .scaledValue();
1301  header.colorLimits.colorRedMinimum =
1302  e57::ScaledIntegerNode(colorbox.get("colorRedMinimum"))
1303  .scaledValue();
1305  e57::ScaledIntegerNode(colorbox.get("colorGreenMaximum"))
1306  .scaledValue();
1308  e57::ScaledIntegerNode(colorbox.get("colorGreenMinimum"))
1309  .scaledValue();
1310  header.colorLimits.colorBlueMaximum =
1311  e57::ScaledIntegerNode(colorbox.get("colorBlueMaximum"))
1312  .scaledValue();
1313  header.colorLimits.colorBlueMinimum =
1314  e57::ScaledIntegerNode(colorbox.get("colorBlueMinimum"))
1315  .scaledValue();
1316  } else if (colorbox.get("colorRedMaximum").type() == e57::E57_FLOAT) {
1317  header.colorLimits.colorRedMaximum =
1318  e57::FloatNode(colorbox.get("colorRedMaximum")).value();
1319  header.colorLimits.colorRedMinimum =
1320  e57::FloatNode(colorbox.get("colorRedMinimum")).value();
1322  e57::FloatNode(colorbox.get("colorGreenMaximum")).value();
1324  e57::FloatNode(colorbox.get("colorGreenMinimum")).value();
1325  header.colorLimits.colorBlueMaximum =
1326  e57::FloatNode(colorbox.get("colorBlueMaximum")).value();
1327  header.colorLimits.colorBlueMinimum =
1328  e57::FloatNode(colorbox.get("colorBlueMinimum")).value();
1329  } else if (colorbox.get("colorRedMaximum").type() == e57::E57_INTEGER) {
1330  header.colorLimits.colorRedMaximum = static_cast<double>(
1331  e57::IntegerNode(colorbox.get("colorRedMaximum")).value());
1332  header.colorLimits.colorRedMinimum = static_cast<double>(
1333  e57::IntegerNode(colorbox.get("colorRedMinimum")).value());
1334  header.colorLimits.colorGreenMaximum = static_cast<double>(
1335  e57::IntegerNode(colorbox.get("colorGreenMaximum"))
1336  .value());
1337  header.colorLimits.colorGreenMinimum = static_cast<double>(
1338  e57::IntegerNode(colorbox.get("colorGreenMinimum"))
1339  .value());
1340  header.colorLimits.colorBlueMaximum = static_cast<double>(
1341  e57::IntegerNode(colorbox.get("colorBlueMaximum")).value());
1342  header.colorLimits.colorBlueMinimum = static_cast<double>(
1343  e57::IntegerNode(colorbox.get("colorBlueMinimum")).value());
1344  }
1345  }
1346 
1347  if ((header.colorLimits.colorRedMaximum == 0.) &&
1348  proto.isDefined("colorRed")) {
1349  if (proto.get("colorRed").type() == e57::E57_INTEGER) {
1350  header.colorLimits.colorRedMinimum = static_cast<double>(
1351  e57::IntegerNode(proto.get("colorRed")).minimum());
1352  header.colorLimits.colorRedMaximum = static_cast<double>(
1353  e57::IntegerNode(proto.get("colorRed")).maximum());
1354  } else if (proto.get("colorRed").type() == e57::E57_FLOAT) {
1355  header.colorLimits.colorRedMinimum =
1356  e57::FloatNode(proto.get("colorRed")).minimum();
1357  header.colorLimits.colorRedMaximum =
1358  e57::FloatNode(proto.get("colorRed")).maximum();
1359  } else if (proto.get("colorRed").type() == e57::E57_SCALED_INTEGER) {
1360  double scale =
1361  e57::ScaledIntegerNode(proto.get("colorRed")).scale();
1362  double offset =
1363  e57::ScaledIntegerNode(proto.get("colorRed")).offset();
1364  int64_t minimum =
1365  e57::ScaledIntegerNode(proto.get("colorRed")).minimum();
1366  int64_t maximum =
1367  e57::ScaledIntegerNode(proto.get("colorRed")).maximum();
1368  header.colorLimits.colorRedMinimum = minimum * scale + offset;
1369  header.colorLimits.colorRedMaximum = maximum * scale + offset;
1370  }
1371  }
1372 
1373  if ((header.colorLimits.colorGreenMaximum == 0.) &&
1374  proto.isDefined("colorGreen")) {
1375  if (proto.get("colorGreen").type() == e57::E57_INTEGER) {
1376  header.colorLimits.colorGreenMinimum = static_cast<double>(
1377  e57::IntegerNode(proto.get("colorGreen")).minimum());
1378  header.colorLimits.colorGreenMaximum = static_cast<double>(
1379  e57::IntegerNode(proto.get("colorGreen")).maximum());
1380  } else if (proto.get("colorGreen").type() == e57::E57_FLOAT) {
1382  e57::FloatNode(proto.get("colorGreen")).minimum();
1384  e57::FloatNode(proto.get("colorGreen")).maximum();
1385  } else if (proto.get("colorGreen").type() == e57::E57_SCALED_INTEGER) {
1386  double scale =
1387  e57::ScaledIntegerNode(proto.get("colorGreen")).scale();
1388  double offset =
1389  e57::ScaledIntegerNode(proto.get("colorGreen")).offset();
1390  int64_t minimum =
1391  e57::ScaledIntegerNode(proto.get("colorGreen")).minimum();
1392  int64_t maximum =
1393  e57::ScaledIntegerNode(proto.get("colorGreen")).maximum();
1394  header.colorLimits.colorGreenMinimum = minimum * scale + offset;
1395  header.colorLimits.colorGreenMaximum = maximum * scale + offset;
1396  }
1397  }
1398  if ((header.colorLimits.colorBlueMaximum == 0.) &&
1399  proto.isDefined("colorBlue")) {
1400  if (proto.get("colorBlue").type() == e57::E57_INTEGER) {
1401  header.colorLimits.colorBlueMinimum = static_cast<double>(
1402  e57::IntegerNode(proto.get("colorBlue")).minimum());
1403  header.colorLimits.colorBlueMaximum = static_cast<double>(
1404  e57::IntegerNode(proto.get("colorBlue")).maximum());
1405  } else if (proto.get("colorBlue").type() == e57::E57_FLOAT) {
1406  header.colorLimits.colorBlueMinimum =
1407  e57::FloatNode(proto.get("colorBlue")).minimum();
1408  header.colorLimits.colorBlueMaximum =
1409  e57::FloatNode(proto.get("colorBlue")).maximum();
1410  } else if (proto.get("colorBlue").type() == e57::E57_SCALED_INTEGER) {
1411  double scale =
1412  e57::ScaledIntegerNode(proto.get("colorBlue")).scale();
1413  double offset =
1414  e57::ScaledIntegerNode(proto.get("colorBlue")).offset();
1415  int64_t minimum =
1416  e57::ScaledIntegerNode(proto.get("colorBlue")).minimum();
1417  int64_t maximum =
1418  e57::ScaledIntegerNode(proto.get("colorBlue")).maximum();
1419  header.colorLimits.colorRedMinimum = minimum * scale + offset;
1420  header.colorLimits.colorRedMaximum = maximum * scale + offset;
1421  }
1422  }
1423 }
1424 
1425 // Helper: decode pose information
1426 static bool GetPoseInformation(const e57::StructureNode& node,
1427  ccGLMatrixd& poseMat) {
1428  bool validPoseMat = false;
1429  if (node.isDefined("pose")) {
1430  e57::StructureNode pose(node.get("pose"));
1431  if (pose.isDefined("rotation")) {
1432  e57::StructureNode rotNode(pose.get("rotation"));
1433  double quaternion[4];
1434  quaternion[0] = e57::FloatNode(rotNode.get("w")).value();
1435  quaternion[1] = e57::FloatNode(rotNode.get("x")).value();
1436  quaternion[2] = e57::FloatNode(rotNode.get("y")).value();
1437  quaternion[3] = e57::FloatNode(rotNode.get("z")).value();
1438 
1439  cloudViewer::SquareMatrixd rotMat(3);
1440  rotMat.initFromQuaternion(quaternion);
1441  rotMat.toGlMatrix(poseMat.data());
1442  validPoseMat = true;
1443  }
1444 
1445  if (pose.isDefined("translation")) {
1446  e57::StructureNode transNode(pose.get("translation"));
1447  poseMat.getTranslation()[0] =
1448  e57::FloatNode(transNode.get("x")).value();
1449  poseMat.getTranslation()[1] =
1450  e57::FloatNode(transNode.get("y")).value();
1451  poseMat.getTranslation()[2] =
1452  e57::FloatNode(transNode.get("z")).value();
1453  validPoseMat = true;
1454  }
1455  }
1456 
1457  return validPoseMat;
1458 }
1459 
1460 static ccHObject* LoadScan(const e57::Node& node,
1461  QString& guidStr,
1462  ecvProgressDialog* progressDlg = nullptr) {
1463  if (node.type() != e57::E57_STRUCTURE) {
1464  CVLog::Warning("[E57Filter] Scan nodes should be STRUCTURES!");
1465  return nullptr;
1466  }
1467  e57::StructureNode scanNode(node);
1468 
1469  // log
1470  CVLog::Print(QString("[E57] Reading new scan node (%1)")
1471  .arg(scanNode.elementName().c_str()));
1472 
1473  if (!scanNode.isDefined("points")) {
1474  CVLog::Warning(QString("[E57Filter] No point in scan '%1'!")
1475  .arg(scanNode.elementName().c_str()));
1476  return nullptr;
1477  }
1478 
1479  // unique GUID
1480  if (scanNode.isDefined("guid")) {
1481  e57::Node guidNode = scanNode.get("guid");
1482  assert(guidNode.type() == e57::E57_STRING);
1483  guidStr =
1484  QString(static_cast<e57::StringNode>(guidNode).value().c_str());
1485  } else {
1486  // No GUID!
1487  guidStr.clear();
1488  }
1489 
1490  // points
1491  e57::CompressedVectorNode points(scanNode.get("points"));
1492  const int64_t pointCount = points.childCount();
1493 
1494  // prototype for points
1495  e57::StructureNode prototype(points.prototype());
1496  E57ScanHeader header;
1497  DecodePrototype(scanNode, prototype, header);
1498 
1499  bool sphericalMode = false;
1500  // no cartesian fields?
1501  if (!header.pointFields.cartesianXField &&
1502  !header.pointFields.cartesianYField &&
1503  !header.pointFields.cartesianZField) {
1504  // let's look for spherical ones
1505  if (!header.pointFields.sphericalRangeField &&
1508  CVLog::Warning(QString("[E57Filter] No readable point in scan "
1509  "'%1'! (only cartesian and spherical "
1510  "coordinates are supported right now)")
1511  .arg(scanNode.elementName().c_str()));
1512  return nullptr;
1513  }
1514  sphericalMode = true;
1515  }
1516 
1517  ccPointCloud* cloud = new ccPointCloud();
1518 
1519  if (scanNode.isDefined("name")) {
1520  cloud->setName(QString::fromStdString(
1521  e57::StringNode(scanNode.get("name")).value()));
1522  }
1523 
1524  if (scanNode.isDefined("description")) {
1525  CVLog::Print(
1526  QStringLiteral("[E57] Internal description: %1")
1527  .arg(QString::fromStdString(
1528  e57::StringNode(scanNode.get("description"))
1529  .value())));
1530  }
1531 
1532  /* //Ignored fields (for the moment)
1533  if (scanNode.isDefined("indexBounds"))
1534  if (scanNode.isDefined("originalGuids"))
1535  if (scanNode.isDefined("sensorVendor"))
1536  if (scanNode.isDefined("sensorModel"))
1537  if (scanNode.isDefined("sensorSerialNumber"))
1538  if (scanNode.isDefined("sensorHardwareVersion"))
1539  if (scanNode.isDefined("sensorSoftwareVersion"))
1540  if (scanNode.isDefined("sensorFirmwareVersion"))
1541  if (scanNode.isDefined("temperature"))
1542  if (scanNode.isDefined("relativeHumidity"))
1543  if (scanNode.isDefined("atmosphericPressure"))
1544  if (scanNode.isDefined("pointGroupingSchemes"))
1545  if (scanNode.isDefined("cartesianBounds"))
1546  if (scanNode.isDefined("sphericalBounds"))
1547  if (scan.isDefined("acquisitionStart"))
1548  if (scan.isDefined("acquisitionEnd"))
1549  //*/
1550 
1551  // scan "pose" relatively to the others
1552  ccGLMatrixd poseMat;
1553  const bool validPoseMat = GetPoseInformation(scanNode, poseMat);
1554  bool poseMatWasShifted = false;
1555  ccGBLSensor* sensor = nullptr;
1556 
1557  if (validPoseMat) {
1558  const CCVector3d T = poseMat.getTranslationAsVec3D();
1559  CCVector3d Tshift;
1560  bool preserveCoordinateShift = true;
1561  if (FileIOFilter::HandleGlobalShift(T, Tshift, preserveCoordinateShift,
1562  s_loadParameters)) {
1563  poseMat.setTranslation((T + Tshift).u);
1564  if (preserveCoordinateShift) {
1565  cloud->setGlobalShift(Tshift);
1566  }
1567  poseMatWasShifted = true;
1569  "[E57Filter::loadFile] Cloud %s has been recentered! "
1570  "Translation: (%.2f ; %.2f ; %.2f)",
1571  qPrintable(guidStr), Tshift.x, Tshift.y, Tshift.z);
1572  }
1573 
1574  // cloud->setGLTransformation(poseMat); //TODO-> apply it at the end
1575  // instead! Otherwise we will loose original coordinates!
1576 
1577  sensor = new ccGBLSensor();
1578  sensor->setRigidTransformation(ccGLMatrix(poseMat.data()));
1579  }
1580 
1581  // prepare temporary structures
1582  const unsigned chunkSize = std::min<unsigned>(
1583  pointCount, (1 << 20)); // we load the file in several steps to
1584  // limit the memory consumption
1585  TempArrays arrays;
1586  std::vector<e57::SourceDestBuffer> dbufs;
1587 
1588  if (!cloud->reserve(static_cast<unsigned>(pointCount))) {
1589  CVLog::Error("[E57] Not enough memory!");
1590  delete cloud;
1591  return nullptr;
1592  }
1593 
1594  if (sphericalMode) {
1595  // spherical coordinates
1596  if (header.pointFields.sphericalRangeField) {
1597  arrays.xData.resize(chunkSize);
1598  dbufs.emplace_back(node.destImageFile(), "sphericalRange",
1599  arrays.xData.data(), chunkSize, true,
1600  (prototype.get("sphericalRange").type() ==
1602  }
1603  if (header.pointFields.sphericalAzimuthField) {
1604  arrays.yData.resize(chunkSize);
1605  dbufs.emplace_back(node.destImageFile(), "sphericalAzimuth",
1606  arrays.yData.data(), chunkSize, true,
1607  (prototype.get("sphericalAzimuth").type() ==
1609  }
1610  if (header.pointFields.sphericalElevationField) {
1611  arrays.zData.resize(chunkSize);
1612  dbufs.emplace_back(node.destImageFile(), "sphericalElevation",
1613  arrays.zData.data(), chunkSize, true,
1614  (prototype.get("sphericalElevation").type() ==
1616  }
1617 
1618  // data validity
1620  arrays.isInvalidData.resize(chunkSize);
1621  dbufs.emplace_back(node.destImageFile(), "sphericalInvalidState",
1622  arrays.isInvalidData.data(), chunkSize, true,
1623  (prototype.get("sphericalInvalidState").type() ==
1625  }
1626  } else {
1627  // cartesian coordinates
1628  if (header.pointFields.cartesianXField) {
1629  arrays.xData.resize(chunkSize);
1630  dbufs.emplace_back(node.destImageFile(), "cartesianX",
1631  arrays.xData.data(), chunkSize, true,
1632  (prototype.get("cartesianX").type() ==
1634  }
1635  if (header.pointFields.cartesianYField) {
1636  arrays.yData.resize(chunkSize);
1637  dbufs.emplace_back(node.destImageFile(), "cartesianY",
1638  arrays.yData.data(), chunkSize, true,
1639  (prototype.get("cartesianY").type() ==
1641  }
1642  if (header.pointFields.cartesianZField) {
1643  arrays.zData.resize(chunkSize);
1644  dbufs.emplace_back(node.destImageFile(), "cartesianZ",
1645  arrays.zData.data(), chunkSize, true,
1646  (prototype.get("cartesianZ").type() ==
1648  }
1649 
1650  // data validity
1652  arrays.isInvalidData.resize(chunkSize);
1653  dbufs.emplace_back(node.destImageFile(), "cartesianInvalidState",
1654  arrays.isInvalidData.data(), chunkSize, true,
1655  (prototype.get("cartesianInvalidState").type() ==
1657  }
1658  }
1659 
1660  // normals
1661  bool hasNormals =
1662  (header.pointFields.normXField || header.pointFields.normYField ||
1663  header.pointFields.normZField);
1664  if (hasNormals) {
1665  if (!cloud->reserveTheNormsTable()) {
1666  CVLog::Error("[E57] Not enough memory!");
1667  delete cloud;
1668  return nullptr;
1669  }
1670  cloud->showNormals(true);
1671  if (header.pointFields.normXField) {
1672  arrays.xNormData.resize(chunkSize);
1673  dbufs.emplace_back(node.destImageFile(), "nor:normalX",
1674  arrays.xNormData.data(), chunkSize, true,
1675  (prototype.get("nor:normalX").type() ==
1677  }
1678  if (header.pointFields.normYField) {
1679  arrays.yNormData.resize(chunkSize);
1680  dbufs.emplace_back(node.destImageFile(), "nor:normalY",
1681  arrays.yNormData.data(), chunkSize, true,
1682  (prototype.get("nor:normalY").type() ==
1684  }
1685  if (header.pointFields.normZField) {
1686  arrays.zNormData.resize(chunkSize);
1687  dbufs.emplace_back(node.destImageFile(), "nor:normalZ",
1688  arrays.zNormData.data(), chunkSize, true,
1689  (prototype.get("nor:normalZ").type() ==
1691  }
1692  }
1693 
1694  // intensity
1695  // double intRange = 0;
1696  // double intOffset = 0;
1697  // ScalarType invalidSFValue = 0;
1698 
1699  ccScalarField* intensitySF = nullptr;
1700  if (header.pointFields.intensityField) {
1701  intensitySF = new ccScalarField(CC_E57_INTENSITY_FIELD_NAME);
1702  if (!intensitySF->resizeSafe(static_cast<unsigned>(pointCount))) {
1703  CVLog::Error("[E57] Not enough memory!");
1704  intensitySF->release();
1705  delete cloud;
1706  return nullptr;
1707  }
1708  cloud->addScalarField(intensitySF);
1709 
1710  arrays.intData.resize(chunkSize);
1711  dbufs.emplace_back(
1712  node.destImageFile(), "intensity", arrays.intData.data(),
1713  chunkSize, true,
1714  (prototype.get("intensity").type() == e57::E57_SCALED_INTEGER));
1715  // intRange = header.intensityLimits.intensityMaximum -
1716  // header.intensityLimits.intensityMinimum; intOffset =
1717  // header.intensityLimits.intensityMinimum;
1718 
1719  if (header.pointFields.isIntensityInvalidField) {
1720  arrays.isInvalidIntData.resize(chunkSize);
1721  dbufs.emplace_back(node.destImageFile(), "isIntensityInvalid",
1722  arrays.isInvalidIntData.data(), chunkSize, true,
1723  (prototype.get("isIntensityInvalid").type() ==
1725  }
1726  }
1727 
1728  // color buffers
1729  double colorRedRange = 1;
1730  double colorRedOffset = 0;
1731  double colorGreenRange = 1;
1732  double colorGreenOffset = 0;
1733  double colorBlueRange = 1;
1734  double colorBlueOffset = 0;
1735  bool hasColors = (header.pointFields.colorRedField ||
1736  header.pointFields.colorGreenField ||
1737  header.pointFields.colorBlueField);
1738  if (hasColors) {
1739  if (!cloud->reserveTheRGBTable()) {
1740  CVLog::Error("[E57] Not enough memory!");
1741  delete cloud;
1742  return nullptr;
1743  }
1744  if (header.pointFields.colorRedField) {
1745  arrays.redData.resize(chunkSize);
1746  colorRedOffset = header.colorLimits.colorRedMinimum;
1747  colorRedRange = header.colorLimits.colorRedMaximum -
1749  if (colorRedRange <= 0.0) colorRedRange = 1.0;
1750  dbufs.emplace_back(node.destImageFile(), "colorRed",
1751  arrays.redData.data(), chunkSize, true,
1752  (prototype.get("colorRed").type() ==
1754  }
1755  if (header.pointFields.colorGreenField) {
1756  arrays.greenData.resize(chunkSize);
1757  colorGreenOffset = header.colorLimits.colorGreenMinimum;
1758  colorGreenRange = header.colorLimits.colorGreenMaximum -
1760  if (colorGreenRange <= 0.0) colorGreenRange = 1.0;
1761  dbufs.emplace_back(node.destImageFile(), "colorGreen",
1762  arrays.greenData.data(), chunkSize, true,
1763  (prototype.get("colorGreen").type() ==
1765  }
1766  if (header.pointFields.colorBlueField) {
1767  arrays.blueData.resize(chunkSize);
1768  colorBlueOffset = header.colorLimits.colorBlueMinimum;
1769  colorBlueRange = header.colorLimits.colorBlueMaximum -
1771  if (colorBlueRange <= 0.0) colorBlueRange = 1.0;
1772  dbufs.emplace_back(node.destImageFile(), "colorBlue",
1773  arrays.blueData.data(), chunkSize, true,
1774  (prototype.get("colorBlue").type() ==
1776  }
1777  }
1778 
1779  // return index (multiple shoots scanners)
1780  ccScalarField* returnIndexSF = nullptr;
1781  if (header.pointFields.returnIndexField &&
1782  header.pointFields.returnMaximum > 0) {
1783  // we store the point return index as a scalar field
1784  returnIndexSF = new ccScalarField(CC_E57_RETURN_INDEX_FIELD_NAME);
1785  if (!returnIndexSF->resizeSafe(static_cast<unsigned>(pointCount))) {
1786  CVLog::Error("[E57] Not enough memory!");
1787  delete cloud;
1788  returnIndexSF->release();
1789  return nullptr;
1790  }
1791  cloud->addScalarField(returnIndexSF);
1792  arrays.scanIndexData.resize(chunkSize);
1793  dbufs.emplace_back(node.destImageFile(), "returnIndex",
1794  arrays.scanIndexData.data(), chunkSize, true,
1795  (prototype.get("returnIndex").type() ==
1797  }
1798 
1799  // Read the point data
1800  e57::CompressedVectorReader dataReader = points.reader(dbufs);
1801 
1802  // local progress bar
1804  progressDlg, static_cast<unsigned>(pointCount / chunkSize));
1805  if (progressDlg) {
1806  progressDlg->setMethodTitle(QObject::tr("Read E57 file"));
1807  progressDlg->setInfo(QObject::tr("Scan #%1 - %2 points")
1808  .arg(s_absoluteScanIndex)
1809  .arg(pointCount));
1810  progressDlg->start();
1811  QApplication::processEvents();
1812  }
1813 
1814  CCVector3d Pshift(0, 0, 0);
1815  unsigned size = 0;
1816  int64_t realCount = 0;
1817  int64_t invalidCount = 0;
1818  while ((size = dataReader.read())) {
1819  for (unsigned i = 0; i < size; ++i) {
1820  // we skip invalid points!
1821  if (!arrays.isInvalidData.empty() && arrays.isInvalidData[i] != 0) {
1822  ++invalidCount;
1823  continue;
1824  }
1825 
1826  CCVector3d Pd(0, 0, 0);
1827  if (sphericalMode) {
1828  double r = (arrays.xData.empty() ? 0 : arrays.xData[i]);
1829  double theta =
1830  (arrays.yData.empty() ? 0
1831  : arrays.yData[i]); // Azimuth
1832  double phi =
1833  (arrays.zData.empty() ? 0
1834  : arrays.zData[i]); // Elevation
1835 
1836  double cos_phi = cos(phi);
1837  Pd.x = r * cos_phi * cos(theta);
1838  Pd.y = r * cos_phi * sin(theta);
1839  Pd.z = r * sin(phi);
1840  }
1841  // DGM TODO: not handled yet (-->what are the standard cylindrical
1842  // field names?)
1843  /*else if (cylindricalMode)
1844  {
1845  //from cylindrical coordinates
1846  assert(arrays.xData);
1847  double theta = (arrays.yData ? arrays.yData[i] : 0);
1848  Pd.x = arrays.xData[i] * cos(theta);
1849  Pd.y = arrays.xData[i] * sin(theta);
1850  if (arrays.zData)
1851  Pd.z = arrays.zData[i];
1852  }
1853  //*/
1854  else // cartesian
1855  {
1856  if (!arrays.xData.empty()) Pd.x = arrays.xData[i];
1857  if (!arrays.yData.empty()) Pd.y = arrays.yData[i];
1858  if (!arrays.zData.empty()) Pd.z = arrays.zData[i];
1859  }
1860 
1861  // first point: check for 'big' coordinates
1862  if (realCount == 0 && (!validPoseMat || !poseMatWasShifted)) {
1863  bool preserveCoordinateShift = true;
1864  if (FileIOFilter::HandleGlobalShift(Pd, Pshift,
1865  preserveCoordinateShift,
1866  s_loadParameters)) {
1867  if (preserveCoordinateShift) {
1868  cloud->setGlobalShift(Pshift);
1869  }
1871  "[E57Filter::loadFile] Cloud %s has been "
1872  "recentered! Translation: (%.2f ; %.2f ; %.2f)",
1873  qPrintable(guidStr), Pshift.x, Pshift.y, Pshift.z);
1874  }
1875  }
1876 
1877  const CCVector3 P = CCVector3::fromArray((Pd + Pshift).u);
1878  cloud->addPoint(P);
1879 
1880  if (hasNormals) {
1881  CCVector3 N(0, 0, 0);
1882  if (!arrays.xNormData.empty())
1883  N.x = static_cast<PointCoordinateType>(arrays.xNormData[i]);
1884  if (!arrays.yNormData.empty())
1885  N.y = static_cast<PointCoordinateType>(arrays.yNormData[i]);
1886  if (!arrays.zNormData.empty())
1887  N.z = static_cast<PointCoordinateType>(arrays.zNormData[i]);
1888  N.normalize();
1889  cloud->addNorm(N);
1890  }
1891 
1892  if (!arrays.intData.empty()) {
1893  assert(intensitySF);
1894  if (!header.pointFields.isIntensityInvalidField ||
1895  arrays.isInvalidIntData[i] != 0) {
1896  // ScalarType intensity = (ScalarType)((arrays.intData[i] -
1897  // intOffset)/intRange); //Normalize intensity to 0 - 1.
1898  const ScalarType intensity =
1899  static_cast<ScalarType>(arrays.intData[i]);
1900  intensitySF->setValue(static_cast<unsigned>(realCount),
1901  intensity);
1902 
1903  // track max intensity (for proper visualization)
1904  if (s_absoluteScanIndex != 0 || realCount != 0) {
1905  if (s_maxIntensity < intensity)
1906  s_maxIntensity = intensity;
1907  else if (s_minIntensity > intensity)
1908  s_minIntensity = intensity;
1909  } else {
1910  s_maxIntensity = s_minIntensity = intensity;
1911  }
1912  } else {
1913  intensitySF->flagValueAsInvalid(
1914  static_cast<unsigned>(realCount));
1915  }
1916  }
1917 
1918  if (hasColors) {
1919  // Normalize color to 0 - 255
1920  ecvColor::Rgb C(0, 0, 0);
1921  if (!arrays.redData.empty())
1922  C.r = static_cast<ColorCompType>(
1923  ((arrays.redData[i] - colorRedOffset) * 255) /
1924  colorRedRange);
1925  if (!arrays.greenData.empty())
1926  C.g = static_cast<ColorCompType>(
1927  ((arrays.greenData[i] - colorGreenOffset) * 255) /
1928  colorGreenRange);
1929  if (!arrays.blueData.empty())
1930  C.b = static_cast<ColorCompType>(
1931  ((arrays.blueData[i] - colorBlueOffset) * 255) /
1932  colorBlueRange);
1933 
1934  cloud->addRGBColor(C);
1935  }
1936 
1937  if (!arrays.scanIndexData.empty()) {
1938  assert(returnIndexSF);
1939  const ScalarType s =
1940  static_cast<ScalarType>(arrays.scanIndexData[i]);
1941  returnIndexSF->setValue(static_cast<unsigned>(realCount), s);
1942  }
1943 
1944  realCount++;
1945  }
1946 
1947  if (progressDlg && !nprogress.oneStep()) {
1948  QApplication::processEvents();
1949  s_cancelRequestedByUser = true;
1950  break;
1951  }
1952  }
1953 
1954  dataReader.close();
1955 
1956  if (realCount == 0) {
1957  CVLog::Warning(QString("[E57] No valid point in scan '%1'!")
1958  .arg(scanNode.elementName().c_str()));
1959  delete cloud;
1960  return nullptr;
1961  } else if (realCount < pointCount) {
1962  if ((realCount + invalidCount) != pointCount) {
1963  CVLog::Warning(QString("[E57] We read fewer points than expected "
1964  "for scan '%1' (%2/%3)")
1965  .arg(scanNode.elementName().c_str())
1966  .arg(realCount)
1967  .arg(pointCount));
1968  }
1969 
1970  cloud->resize(static_cast<unsigned>(realCount));
1971  }
1972 
1973  // Scalar fields
1974  if (intensitySF) {
1975  intensitySF->computeMinAndMax();
1976  if (intensitySF->getMin() >= 0 && intensitySF->getMax() <= 1.0)
1979  else
1983  cloud->getScalarFieldIndexByName(intensitySF->getName()));
1984  cloud->showSF(true);
1985  }
1986 
1987  if (returnIndexSF) {
1988  returnIndexSF->computeMinAndMax();
1990  cloud->getScalarFieldIndexByName(returnIndexSF->getName()));
1992  "[E57] Cloud has multiple echoes: use 'Edit > Scalar Fields > "
1993  "Filter by value' to extract one component");
1994  cloud->showSF(true);
1995  }
1996 
1997  cloud->showColors(hasColors);
1998  cloud->setVisible(true);
1999 
2000  // we don't deal with virtual transformation (yet)
2001  if (validPoseMat) {
2002  const ccGLMatrix poseMatf(poseMat.data());
2003 
2004  cloud->applyGLTransformation_recursive(&poseMatf);
2005  // this transformation is of no interest for the user
2007 
2008  // save the original pose matrix as meta-data
2009  cloud->setMetaData(s_e57PoseKey, poseMat.toString(12, ' '));
2010  }
2011 
2012  if (sensor) // add the sensor at the end, after calling
2013  // applyGLTransformation_recursive!
2014  {
2015  sensor->setVisible(false);
2016  sensor->setEnabled(false);
2017  sensor->setGraphicScale(cloud->getOwnBB().getDiagNorm() / 20);
2018  cloud->addChild(sensor);
2019  }
2020 
2021  return cloud;
2022 }
2023 
2024 static ccHObject* LoadImage(const e57::Node& node,
2025  QString& associatedData3DGuid) {
2026  if (node.type() != e57::E57_STRUCTURE) {
2027  CVLog::Warning("[E57Filter] Image nodes should be STRUCTURES!");
2028  return nullptr;
2029  }
2030  e57::StructureNode imageNode(node);
2031 
2032  // log
2033  CVLog::Print(QString("[E57] Reading new image node (%1)")
2034  .arg(imageNode.elementName().c_str()));
2035 
2036  e57::ustring name = "none";
2037  if (imageNode.isDefined("name"))
2038  name = e57::StringNode(imageNode.get("name")).value();
2039  if (imageNode.isDefined("description"))
2040  CVLog::Print(QString("[E57] Description: %1")
2041  .arg(e57::StringNode(imageNode.get("description"))
2042  .value()
2043  .c_str()));
2044 
2045  if (imageNode.isDefined("associatedData3DGuid"))
2046  associatedData3DGuid =
2047  e57::StringNode(imageNode.get("associatedData3DGuid"))
2048  .value()
2049  .c_str();
2050  else
2051  associatedData3DGuid.clear();
2052 
2053  // Get pose information
2054  ccGLMatrixd poseMat;
2055  bool validPoseMat = GetPoseInformation(imageNode, poseMat);
2056 
2057  // camera information
2058  e57::ustring cameraRepresentationStr("none");
2059  CameraRepresentation* cameraRepresentation = nullptr;
2060  if (imageNode.isDefined("visualReferenceRepresentation")) {
2061  cameraRepresentation = new VisualReferenceRepresentation;
2062  cameraRepresentationStr = "visualReferenceRepresentation";
2063  } else if (imageNode.isDefined("pinholeRepresentation")) {
2064  cameraRepresentation = new PinholeRepresentation;
2065  cameraRepresentationStr = "pinholeRepresentation";
2066  } else if (imageNode.isDefined("sphericalRepresentation")) {
2067  cameraRepresentation = new SphericalRepresentation;
2068  cameraRepresentationStr = "sphericalRepresentation";
2069  } else if (imageNode.isDefined("cylindricalRepresentation")) {
2070  cameraRepresentation = new CylindricalRepresentation;
2071  cameraRepresentationStr = "cylindricalRepresentation";
2072  }
2073 
2074  if (!cameraRepresentation) {
2075  CVLog::Warning(QString("[E57] Image %1 has no associated camera "
2076  "representation!")
2077  .arg(name.c_str()));
2078  return nullptr;
2079  }
2080  Image2DProjection cameraType = cameraRepresentation->getType();
2081 
2082  assert(cameraRepresentationStr != "none");
2083  assert(cameraType != E57_NO_PROJECTION);
2084  e57::StructureNode cameraRepresentationNode(
2085  imageNode.get(cameraRepresentationStr));
2086 
2087  // read standard image information
2088  VisualReferenceRepresentation* visualRefRepresentation =
2089  static_cast<VisualReferenceRepresentation*>(cameraRepresentation);
2090  visualRefRepresentation->imageType = E57_NO_IMAGE;
2091  visualRefRepresentation->imageMaskSize = 0;
2092 
2093  if (cameraRepresentationNode.isDefined("jpegImage")) {
2094  visualRefRepresentation->imageType = E57_JPEG_IMAGE;
2095  visualRefRepresentation->imageSize =
2096  e57::BlobNode(cameraRepresentationNode.get("jpegImage"))
2097  .byteCount();
2098  } else if (cameraRepresentationNode.isDefined("pngImage")) {
2099  visualRefRepresentation->imageType = E57_PNG_IMAGE;
2100  visualRefRepresentation->imageSize =
2101  e57::BlobNode(cameraRepresentationNode.get("pngImage"))
2102  .byteCount();
2103  } else {
2105  "[E57] Image format not handled (only jpg and png for the "
2106  "moment)");
2107  return nullptr;
2108  }
2109 
2110  if (visualRefRepresentation->imageSize == 0) {
2111  CVLog::Warning("[E57] Invalid image size!");
2112  return nullptr;
2113  }
2114 
2115  uint8_t* imageBits = new uint8_t[visualRefRepresentation->imageSize];
2116  if (!imageBits) {
2117  CVLog::Warning("[E57] Not enough memory to load image!");
2118  return nullptr;
2119  }
2120 
2121  if (cameraRepresentationNode.isDefined("imageMask"))
2122  visualRefRepresentation->imageMaskSize =
2123  e57::BlobNode(cameraRepresentationNode.get("imageMask"))
2124  .byteCount();
2125 
2126  visualRefRepresentation->imageHeight = static_cast<int32_t>(
2127  e57::IntegerNode(cameraRepresentationNode.get("imageHeight"))
2128  .value());
2129  visualRefRepresentation->imageWidth = static_cast<int32_t>(
2130  e57::IntegerNode(cameraRepresentationNode.get("imageWidth"))
2131  .value());
2132 
2133  // Pixel size
2134  switch (cameraType) {
2135  case E57_PINHOLE:
2136  case E57_CYLINDRICAL:
2137  case E57_SPHERICAL: {
2138  SphericalRepresentation* spherical =
2139  static_cast<SphericalRepresentation*>(cameraRepresentation);
2140  spherical->pixelHeight =
2141  e57::FloatNode(cameraRepresentationNode.get("pixelHeight"))
2142  .value();
2143  spherical->pixelWidth =
2144  e57::FloatNode(cameraRepresentationNode.get("pixelWidth"))
2145  .value();
2146  } break;
2147  case E57_NO_PROJECTION:
2148  case E57_VISUAL:
2149  break;
2150  }
2151 
2152  if (cameraType == E57_PINHOLE) {
2153  PinholeRepresentation* pinhole =
2154  static_cast<PinholeRepresentation*>(cameraRepresentation);
2155 
2156  pinhole->focalLength =
2157  e57::FloatNode(cameraRepresentationNode.get("focalLength"))
2158  .value();
2159  pinhole->principalPointX =
2160  e57::FloatNode(cameraRepresentationNode.get("principalPointX"))
2161  .value();
2162  pinhole->principalPointY =
2163  e57::FloatNode(cameraRepresentationNode.get("principalPointY"))
2164  .value();
2165  } else if (cameraType == E57_CYLINDRICAL) {
2166  CylindricalRepresentation* cylindrical =
2167  static_cast<CylindricalRepresentation*>(cameraRepresentation);
2168 
2169  cylindrical->principalPointY =
2170  e57::FloatNode(cameraRepresentationNode.get("principalPointY"))
2171  .value();
2172  cylindrical->radius =
2173  e57::FloatNode(cameraRepresentationNode.get("radius")).value();
2174  }
2175 
2176  // reading image data
2177  char imageFormat[4] = "jpg";
2178  switch (visualRefRepresentation->imageType) {
2179  case E57_JPEG_IMAGE: {
2180  assert(cameraRepresentationNode.isDefined("jpegImage"));
2181  e57::BlobNode jpegImage(cameraRepresentationNode.get("jpegImage"));
2182  jpegImage.read(
2183  imageBits, 0,
2184  static_cast<size_t>(visualRefRepresentation->imageSize));
2185  // #ifdef QT_DEBUG
2186  // FILE* fp = fopen("test_e57.jpg","wb");
2187  // fwrite(imageBits,visualRefRepresentation->imageSize,1,fp);
2188  // fclose(fp);
2189  // #endif
2190  break;
2191  }
2192  case E57_PNG_IMAGE: {
2193  strcpy(imageFormat, "png");
2194  assert(cameraRepresentationNode.isDefined("pngImage"));
2195  e57::BlobNode pngImage(cameraRepresentationNode.get("pngImage"));
2196  pngImage.read(
2197  (uint8_t*)imageBits, 0,
2198  static_cast<size_t>(visualRefRepresentation->imageSize));
2199  break;
2200  }
2201  default:
2202  assert(false);
2203  }
2204 
2205  // handle mask?
2206  if (visualRefRepresentation->imageMaskSize > 0) {
2207  assert(cameraRepresentationNode.isDefined("imageMask"));
2208  e57::BlobNode imageMask(cameraRepresentationNode.get("imageMask"));
2209  // imageMask.read((uint8_t*)pBuffer, start, (size_t) count);
2210  // DGM: TODO
2211  }
2212 
2213  QImage qImage;
2214  assert(imageBits);
2215  bool loadResult = qImage.loadFromData(
2216  imageBits, static_cast<int>(visualRefRepresentation->imageSize),
2217  imageFormat);
2218  delete[] imageBits;
2219  imageBits = nullptr;
2220 
2221  if (!loadResult) {
2222  CVLog::Warning("[E57] Failed to load image from blob data!");
2223  return nullptr;
2224  }
2225 
2226  ccImage* imageObj = nullptr;
2227  switch (cameraType) {
2228  case E57_CYLINDRICAL:
2229  case E57_SPHERICAL:
2231  "[E57] Unhandled camera type (image will be loaded as is)");
2232 #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
2233  Q_FALLTHROUGH();
2234 #endif
2235  case E57_VISUAL:
2236  imageObj = new ccImage();
2237  break;
2238  case E57_PINHOLE: {
2239  PinholeRepresentation* pinhole =
2240  static_cast<PinholeRepresentation*>(cameraRepresentation);
2241  float focal_mm = static_cast<float>(pinhole->focalLength * 1000.0);
2242  float pixelWidth_mm =
2243  static_cast<float>(pinhole->pixelWidth * 1000.0);
2244  float pixelHeight_mm =
2245  static_cast<float>(pinhole->pixelHeight * 1000.0);
2246  float ccdHeight_mm =
2247  static_cast<float>(pinhole->imageHeight * pixelHeight_mm);
2248 
2251  focal_mm, pixelHeight_mm);
2252  params.arrayWidth = pinhole->imageWidth;
2253  params.arrayHeight = pinhole->imageHeight;
2254  params.principal_point[0] =
2255  static_cast<float>(pinhole->principalPointX);
2256  params.principal_point[1] =
2257  static_cast<float>(pinhole->principalPointY);
2258  params.pixelSize_mm[0] = pixelWidth_mm;
2259  params.pixelSize_mm[1] = pixelHeight_mm;
2261  focal_mm, ccdHeight_mm);
2262 
2263  ccCameraSensor* sensor = new ccCameraSensor(params);
2264  if (validPoseMat) {
2265  ccGLMatrix poseMatf = ccGLMatrix(poseMat.data());
2266  sensor->setRigidTransformation(poseMatf);
2267  }
2268 
2269  sensor->setEnabled(false);
2270  sensor->setVisible(true);
2271 
2272  ccImage* calibImage = new ccImage();
2273  calibImage->addChild(sensor);
2274  calibImage->setAssociatedSensor(sensor);
2275 
2276  imageObj = calibImage;
2277  } break;
2278  case E57_NO_PROJECTION:
2279  assert(false);
2280  break;
2281  }
2282 
2283  assert(imageObj);
2284  imageObj->setData(qImage);
2285  imageObj->setName(name.c_str());
2286 
2287  // don't forget image aspect ratio
2288  if (cameraType == E57_CYLINDRICAL || cameraType == E57_PINHOLE ||
2289  cameraType == E57_SPHERICAL) {
2290  SphericalRepresentation* spherical =
2291  static_cast<SphericalRepresentation*>(cameraRepresentation);
2292  imageObj->setAspectRatio(static_cast<float>(spherical->pixelWidth /
2293  spherical->pixelHeight) *
2294  imageObj->getAspectRatio());
2295  }
2296 
2297  delete cameraRepresentation;
2298 
2299  return imageObj;
2300 }
2301 
2303  ccHObject& container,
2304  LoadParameters& parameters) {
2305  s_loadParameters = parameters;
2306 
2308  try {
2309  e57::ImageFile imf(
2310  qPrintable(filename), "r",
2311  e57::CHECKSUM_POLICY_SPARSE); // DGM: warning, toStdString
2312  // doesn't preserve "local"
2313  // characters
2314 
2315  if (!imf.isOpen()) {
2316  return CC_FERR_READING;
2317  }
2318 
2319  // for normals handling
2320  static const e57::ustring normalsExtension(
2321  "http://www.libe57.org/E57_NOR_surface_normals.txt");
2322  e57::ustring _normalsExtension;
2323  if (!imf.extensionsLookupPrefix(
2324  "nor", _normalsExtension)) // the extension may already be
2325  // registered
2326  {
2327  imf.extensionsAdd("nor", normalsExtension);
2328  }
2329 
2330  e57::StructureNode root = imf.root();
2331 
2332  // header info
2333  e57::StructureNode rootStruct = e57::StructureNode(root);
2334 
2335  if (!ChildNodeToConsole(rootStruct, "formatName") ||
2336  !ChildNodeToConsole(rootStruct, "guid") ||
2337  !ChildNodeToConsole(rootStruct, "versionMajor") ||
2338  !ChildNodeToConsole(rootStruct, "versionMinor")) {
2339  imf.close();
2340  return CC_FERR_MALFORMED_FILE;
2341  }
2342 
2343  // unroll structure in tree (it's a quick to check structure +
2344  // informative for user)
2345  ccHObject* fileStructureTree = new ccHObject("File structure");
2346  if (!NodeStructureToTree(fileStructureTree, rootStruct)) {
2347  imf.close();
2348  return CC_FERR_MALFORMED_FILE;
2349  }
2350  container.addChild(fileStructureTree);
2351 
2352  // we store (temporarily) the loaded scans associated with
2353  // their unique GUID in a map (to retrieve them later if
2354  // necessary - for example to associate them with images)
2355  QMap<QString, ccHObject*> scans;
2356 
2357  // 3D data?
2358  if (root.isDefined("/data3D")) {
2359  e57::Node n =
2360  root.get("/data3D"); // E57 standard: "data3D is a vector
2361  // for storing an arbitrary number of
2362  // 3D data sets "
2363  if (n.type() != e57::E57_VECTOR) {
2364  imf.close();
2365  return CC_FERR_MALFORMED_FILE;
2366  }
2367  e57::VectorNode data3D(n);
2368 
2369  unsigned scanCount = static_cast<unsigned>(data3D.childCount());
2370 
2371  // global progress bar
2372  QScopedPointer<ecvProgressDialog> progressDlg(nullptr);
2373  if (parameters.parentWidget) {
2374  progressDlg.reset(
2375  new ecvProgressDialog(true, parameters.parentWidget));
2376  progressDlg->setAutoClose(false);
2377  }
2378 
2379  bool showGlobalProgress = (scanCount > 10);
2380  if (progressDlg && showGlobalProgress) {
2381  // Too many scans, will display a global progress bar
2382  progressDlg->setMethodTitle(QObject::tr("Read E57 file"));
2383  progressDlg->setInfo(QObject::tr("Scans: %1").arg(scanCount));
2384  progressDlg->start();
2385  QApplication::processEvents();
2386  }
2388  progressDlg.data(), showGlobalProgress ? scanCount : 100);
2389 
2390  // static states
2391  s_absoluteScanIndex = 0;
2392  s_cancelRequestedByUser = false;
2393  s_minIntensity = s_maxIntensity = 0;
2394  for (unsigned i = 0; i < scanCount; ++i) {
2395  const e57::Node scanNode = data3D.get(i);
2396  QString scanGUID;
2397 
2398  ccHObject* scan = LoadScan(
2399  scanNode, scanGUID,
2400  showGlobalProgress ? nullptr : progressDlg.data());
2401 
2402  if (scan) {
2403  if (scan->getName().isEmpty()) {
2404  QString name("Scan ");
2405  e57::ustring nodeName = scanNode.elementName();
2406 
2407  if (!nodeName.empty())
2408  name += QString::fromStdString(nodeName);
2409  else
2410  name += QString::number(i);
2411 
2412  scan->setName(name);
2413  }
2414  container.addChild(scan);
2415 
2416  // we also add the scan to the GUID/object map
2417  if (!scanGUID.isEmpty()) {
2418  scans.insert(scanGUID, scan);
2419  }
2420  }
2421 
2422  if ((showGlobalProgress && progressDlg &&
2423  !nprogress.oneStep()) ||
2424  s_cancelRequestedByUser) {
2425  break;
2426  }
2427  ++s_absoluteScanIndex;
2428  }
2429 
2430  if (progressDlg) {
2431  progressDlg->stop();
2432  QApplication::processEvents();
2433  }
2434 
2435  // set global max intensity (saturation) for proper display
2436  for (unsigned i = 0; i < container.getChildrenNumber(); ++i) {
2437  if (container.getChild(i)->isA(CV_TYPES::POINT_CLOUD)) {
2438  ccPointCloud* pc =
2439  static_cast<ccPointCloud*>(container.getChild(i));
2441  if (sf) {
2442  sf->setSaturationStart(s_minIntensity);
2443  sf->setSaturationStop(s_maxIntensity);
2444  }
2445  }
2446  }
2447  }
2448 
2449  // we save parameters
2450  parameters = s_loadParameters;
2451 
2452  // Image data?
2453  if (!s_cancelRequestedByUser && root.isDefined("/images2D")) {
2454  e57::Node n = root.get(
2455  "/images2D"); // E57 standard: "images2D is a vector for
2456  // storing two dimensional images"
2457  if (n.type() != e57::E57_VECTOR) {
2458  imf.close();
2459  return CC_FERR_MALFORMED_FILE;
2460  }
2461 
2462  e57::VectorNode images2D(n);
2463 
2464  unsigned imageCount = static_cast<unsigned>(images2D.childCount());
2465  if (imageCount) {
2466  // progress bar
2467  QScopedPointer<ecvProgressDialog> progressDlg(nullptr);
2468  if (parameters.parentWidget) {
2469  progressDlg.reset(new ecvProgressDialog(
2470  true, parameters.parentWidget));
2471  progressDlg->setMethodTitle(QObject::tr("Read E57 file"));
2472  progressDlg->setInfo(
2473  QObject::tr("Images: %1").arg(imageCount));
2474  progressDlg->start();
2475  QApplication::processEvents();
2476  }
2477  cloudViewer::NormalizedProgress nprogress(progressDlg.data(),
2478  imageCount);
2479 
2480  for (unsigned i = 0; i < imageCount; ++i) {
2481  e57::Node imageNode = images2D.get(i);
2482  QString associatedData3DGuid;
2483  ccHObject* image =
2484  LoadImage(imageNode, associatedData3DGuid);
2485  if (image) {
2486  // no name?
2487  if (image->getName().isEmpty()) {
2488  QString name("Image");
2489  e57::ustring nodeName = imageNode.elementName();
2490  if (nodeName.c_str() != nullptr &&
2491  nodeName.c_str()[0] != 0)
2492  name += QString(nodeName.c_str());
2493  else
2494  name += QString::number(i);
2495  image->setName(name);
2496  }
2497  image->setEnabled(false); // not displayed by default
2498 
2499  // existing link to a loaded scan?
2500  ccHObject* parentScan = nullptr;
2501  if (!associatedData3DGuid.isEmpty()) {
2502  if (scans.contains(associatedData3DGuid))
2503  parentScan = scans.value(associatedData3DGuid);
2504  }
2505 
2506  if (parentScan)
2507  parentScan->addChild(image);
2508  else
2509  container.addChild(image);
2510  }
2511 
2512  if (progressDlg && !nprogress.oneStep()) {
2513  s_cancelRequestedByUser = true;
2514  break;
2515  }
2516  }
2517  }
2518  }
2519 
2520  imf.close();
2521  } catch (const e57::E57Exception& e) {
2523  QString("[E57] Error: %1")
2525  .c_str()));
2526 
2527  if (!e.context().empty()) {
2528  CVLog::Warning(QStringLiteral(" context: %1")
2529  .arg(QString::fromStdString(e.context())));
2530  }
2531 
2533  } catch (...) {
2534  CVLog::Warning("[E57] Unknown error");
2536  }
2537 
2538  // special case: process has been cancelled by user
2539  if (result == CC_FERR_NO_ERROR && s_cancelRequestedByUser) {
2541  }
2542 
2543  return result;
2544 }
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int64_t CV_CLASS_ENUM
Type of object type flags (64 bits)
Definition: CVTypes.h:97
std::string filename
static bool SaveScan(ccPointCloud *cloud, e57::StructureNode &scanNode, e57::ImageFile &imf, e57::VectorNode &data3D, QString &guidStr, ecvProgressDialog *progressDlg=nullptr)
Definition: E57Filter.cpp:128
static bool GetPoseInformation(const e57::StructureNode &node, ccGLMatrixd &poseMat)
Definition: E57Filter.cpp:1426
static void NodeToConsole(const e57::Node &node)
Definition: E57Filter.cpp:971
double colorFieldType
Definition: E57Filter.cpp:36
static ccHObject * LoadImage(const e57::Node &node, QString &associatedData3DGuid)
Definition: E57Filter.cpp:2024
static bool NodeStructureToTree(ccHObject *currentTreeNode, const e57::Node &currentE57Node)
Definition: E57Filter.cpp:908
void SaveImage(const ccImage *image, const QString &scanGUID, e57::ImageFile &imf, e57::VectorNode &images2D)
Definition: E57Filter.cpp:607
static ccHObject * LoadScan(const e57::Node &node, QString &guidStr, ecvProgressDialog *progressDlg=nullptr)
Definition: E57Filter.cpp:1460
static void SavePoseInformation(e57::StructureNode &parentNode, const e57::ImageFile &imf, const ccGLMatrixd &poseMat)
Definition: E57Filter.cpp:101
static void DecodePrototype(const e57::StructureNode &scan, const e57::StructureNode &proto, E57ScanHeader &header)
Definition: E57Filter.cpp:1057
static bool ChildNodeToConsole(const e57::Node &node, const char *childName)
Definition: E57Filter.cpp:1017
header file for the E57 API
Image2DProjection
Identifies the representation for the image data.
Definition: E57Header.h:185
@ E57_PINHOLE
PinholeRepresentation for the image data.
Definition: E57Header.h:188
@ E57_CYLINDRICAL
CylindricalRepresentation for the image data.
Definition: E57Header.h:190
@ E57_VISUAL
VisualReferenceRepresentation for the image data.
Definition: E57Header.h:187
@ E57_NO_PROJECTION
No representation for the image data is present.
Definition: E57Header.h:186
@ E57_SPHERICAL
SphericalRepresentation for the image data.
Definition: E57Header.h:189
@ E57_NO_IMAGE
No image data.
Definition: E57Header.h:197
@ E57_PNG_IMAGE
PNG format image data.
Definition: E57Header.h:199
@ E57_JPEG_IMAGE
JPEG format image data.
Definition: E57Header.h:198
std::shared_ptr< core::Tensor > image
int size
std::string name
int offset
int points
char type
CC_FILE_ERROR
Typical I/O filter errors.
Definition: FileIOFilter.h:20
@ CC_FERR_CANCELED_BY_USER
Definition: FileIOFilter.h:30
@ CC_FERR_WRITING
Definition: FileIOFilter.h:25
@ CC_FERR_THIRD_PARTY_LIB_EXCEPTION
Definition: FileIOFilter.h:37
@ CC_FERR_MALFORMED_FILE
Definition: FileIOFilter.h:32
@ CC_FERR_NO_SAVE
Definition: FileIOFilter.h:27
@ CC_FERR_NO_ERROR
Definition: FileIOFilter.h:21
@ CC_FERR_READING
Definition: FileIOFilter.h:26
cmdLineReadable * params[]
core::Tensor result
Definition: VtkUtils.cpp:76
virtual void release()
Decrease counter and deletes object when 0.
Definition: CVShareable.cpp:35
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
Interface for E57 camera representation.
Definition: E57Header.h:204
virtual Image2DProjection getType()
Definition: E57Header.h:207
double colorBlueMinimum
Definition: E57Header.h:163
double colorBlueMaximum
Definition: E57Header.h:165
double colorRedMaximum
Definition: E57Header.h:157
double colorRedMinimum
Definition: E57Header.h:155
double colorGreenMaximum
Definition: E57Header.h:161
double colorGreenMinimum
Definition: E57Header.h:159
CC_FILE_ERROR loadFile(const QString &filename, ccHObject &container, LoadParameters &parameters) override
Loads one or more entities from a file.
Definition: E57Filter.cpp:2302
bool canSave(CV_CLASS_ENUM type, bool &multiple, bool &exclusive) const override
Returns whether this I/O filter can save the specified type of entity.
Definition: E57Filter.cpp:89
CC_FILE_ERROR saveToFile(ccHObject *entity, const QString &filename, const SaveParameters &parameters) override
Saves an entity (or a group of) to a file.
Definition: E57Filter.cpp:718
Generic file I/O filter.
Definition: FileIOFilter.h:46
static bool HandleGlobalShift(const CCVector3d &P, CCVector3d &Pshift, bool &preserveCoordinateShift, LoadParameters &loadParameters, bool useInputCoordinatesShiftIfPossible=false)
Shortcut to the ecvGlobalShiftManager mechanism specific for files.
static QString createdBy()
double intensityMinimum
Definition: E57Header.h:143
double intensityMaximum
Definition: E57Header.h:145
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
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:428
double norm2d() const
Returns vector square norm (forces double precision output)
Definition: CVGeom.h:419
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
int32_t imageWidth
image width (in pixels). Shall be positive
Definition: E57Header.h:221
int64_t imageMaskSize
size of image mask data in BlobNode (if any).
Definition: E57Header.h:220
int32_t imageHeight
image height (in pixels). Shall be positive
Definition: E57Header.h:222
Image2DType imageType
image type.
Definition: E57Header.h:218
int64_t imageSize
size of image data in BlobNode.
Definition: E57Header.h:219
Camera (projective) sensor.
static float ConvertFocalMMToPix(float focal_mm, float ccdPixelSize_mm)
Helper: converts camera focal from mm to pixels.
static float ComputeFovRadFromFocalMm(float focal_mm, float ccdSize_mm)
Helper: deduces camera f.o.v. (in radians) from focal (in mm)
static ccColorScale::Shared GetDefaultScale(DEFAULT_SCALES scale=BGYR)
Returns a pre-defined color scale (static shortcut)
virtual void setVisible(bool state)
Sets entity visibility.
virtual void showNormals(bool state)
Sets normals visibility.
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
Ground-based Laser sensor.
Definition: ecvGBLSensor.h:26
QString toString(int precision=12, QChar separator=' ') const
Returns matrix as a string.
Vector3Tpl< T > getTranslationAsVec3D() const
Returns a copy of the translation as a CCVector3.
T * getTranslation()
Retruns a pointer to internal translation.
T * data()
Returns a pointer to internal data.
ccGLMatrixTpl< T > inverse() const
Returns inverse transformation.
void setTranslation(const Vector3Tpl< float > &Tr)
Sets translation (float version)
static ccGLMatrixTpl< double > FromString(const QString &matText, bool &success)
Converts a 'text' matrix to a ccGLMatrix.
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
Double version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:56
ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual const ccGLMatrix & getGLTransformationHistory() const
Returns the transformation 'history' matrix.
Definition: ecvHObject.h:631
virtual void resetGLTransformationHistory_recursive()
Definition: ecvHObject.h:550
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.
unsigned filterChildren(Container &filteredChildren, bool recursive=false, CV_CLASS_ENUM filter=CV_TYPES::OBJECT, bool strict=false) const
Collects the children corresponding to a certain pattern.
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
Generic image.
Definition: ecvImage.h:19
void setData(const QImage &image)
Sets image data.
void setAspectRatio(float ar)
Manually sets aspect ratio.
Definition: ecvImage.h:63
float getAspectRatio() const
Returns aspect ratio.
Definition: ecvImage.h:66
void setAssociatedSensor(ccCameraSensor *sensor)
Sets associated sensor.
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
void setMetaData(const QString &key, const QVariant &data)
Sets a meta-data element.
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
QVariant getMetaData(const QString &key) const
Returns a given associated meta data.
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
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
void addNorm(const CCVector3 &N)
Pushes a normal vector on stack (shortcut)
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
bool hasNormals() const override
Returns whether normals are enabled or not.
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool reserveTheNormsTable()
Reserves memory to store the compressed normals.
bool reserveTheRGBTable()
Reserves memory to store the RGB colors.
int getCurrentDisplayedScalarFieldIndex() const
Returns the currently displayed scalar field index (or -1 if none)
bool hasColors() const override
Returns whether colors are enabled or not.
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
ccScalarField * getCurrentDisplayedScalarField() const
Returns the currently displayed scalar (or 0 if none)
const ecvColor::Rgb & getPointColor(unsigned pointIndex) const override
Returns color corresponding to a given point.
const CCVector3 & getPointNormal(unsigned pointIndex) const override
Returns normal corresponding to a given point.
void addRGBColor(const ecvColor::Rgb &C)
Pushes an RGB color on stack.
A scalar field associated to display-related parameters.
void setSaturationStop(ScalarType val)
Sets the value at which to stop color gradient.
void setColorScale(ccColorScale::Shared scale)
Sets associated color scale.
void computeMinAndMax() override
Determines the min and max values.
void setSaturationStart(ScalarType val)
Sets the value at which to start color gradient.
bool getActiveAbsoluteTransformation(ccIndexedTransformation &trans) const
Gets currently active absolute transformation.
virtual void setRigidTransformation(const ccGLMatrix &mat)
Definition: ecvSensor.h:99
void setGraphicScale(PointCoordinateType scale)
Sets the sensor graphic representation scale.
Definition: ecvSensor.h:125
bool getOwnGlobalBB(CCVector3d &minCorner, CCVector3d &maxCorner) override
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.
T getDiagNorm() const
Returns diagonal length.
Definition: BoundingBox.h:172
bool oneStep()
Increments total progress value of a single unit.
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.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
const CCVector3 * getPointPersistentPtr(unsigned index) override
unsigned size() const override
Definition: PointCloudTpl.h:38
const char * getScalarFieldName(int index) const
Returns the name of a specific scalar field.
ScalarType getMin() const
Returns the minimum value.
Definition: ScalarField.h:72
ScalarType & getValue(std::size_t index)
Definition: ScalarField.h:92
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
void flagValueAsInvalid(std::size_t index)
Sets the value as 'invalid' (i.e. NAN_VALUE)
Definition: ScalarField.h:66
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
ScalarType getMax() const
Returns the maximum value.
Definition: ScalarField.h:74
void initFromQuaternion(const float q[])
Creates a rotation matrix from a quaternion (float version)
Definition: SquareMatrix.h:480
bool toQuaternion(double q[])
Converts rotation matrix to quaternion.
Definition: SquareMatrix.h:525
void toGlMatrix(float M16f[]) const
Converts a 3*3 or 4*4 matrix to an OpenGL-style float matrix (float[16])
Definition: SquareMatrix.h:601
int64_t byteCount() const
Get size of blob declared when it was created.
Definition: E57Format.cpp:4218
void write(uint8_t *buf, int64_t start, size_t count)
Write a buffer of bytes to a blob.
Definition: E57Format.cpp:4292
void read(uint8_t *buf, int64_t start, size_t count)
Read a buffer of bytes from a blob.
Definition: E57Format.cpp:4250
int64_t childCount() const
Get current number of records in a CompressedVectorNode.
Definition: E57Format.cpp:2894
unsigned read()
Request transfer of blocks of data from CompressedVectorNode into previously designated destination b...
Definition: E57Format.cpp:2285
void close()
End the read operation.
Definition: E57Format.cpp:2400
void write(const size_t recordCount)
Request transfer of blocks of data to CompressedVectorNode from previously designated source buffers.
Definition: E57Format.cpp:2563
void close()
End the write operation.
Definition: E57Format.cpp:2666
Object thrown by E57 API functions to communicate the conditions of an error.
Definition: E57Exception.h:100
const char * sourceFileName() const
Get name of source file where exception occurred, for debugging.
std::string context() const
Get human-readable string that describes the context of the error.
int sourceLineNumber() const
Get line number in source code file where exception occurred, for debugging.
ErrorCode errorCode() const
Get numeric ErrorCode associated with the exception.
void report(const char *reportingFileName=nullptr, int reportingLineNumber=0, const char *reportingFunctionName=nullptr, std::ostream &os=std::cout) const
Print error information on a given output stream.
double minimum() const
Get the declared minimum that the value may take.
Definition: E57Format.cpp:3820
double maximum() const
Get the declared maximum that the value may take.
Definition: E57Format.cpp:3838
double value() const
Get IEEE floating point value stored.
Definition: E57Format.cpp:3787
StructureNode root() const
Get the pre-established root StructureNode of the E57 ImageFile.
Definition: E57Format.cpp:4521
bool isOpen() const
Test whether ImageFile is still open for accessing.
Definition: E57Format.cpp:4586
bool extensionsLookupPrefix(const ustring &prefix, ustring &uri) const
Get URI associated with an E57 extension prefix in the ImageFile.
Definition: E57Format.cpp:4721
void extensionsAdd(const ustring &prefix, const ustring &uri)
Declare the use of an E57 extension in an ImageFile being written.
Definition: E57Format.cpp:4697
void close()
Complete any write operations on an ImageFile, and close the file on the disk.
Definition: E57Format.cpp:4557
int64_t value() const
Get integer value stored.
Definition: E57Format.cpp:3199
int64_t maximum() const
Get the declared maximum that the value may take.
Definition: E57Format.cpp:3227
int64_t minimum() const
Get the declared minimum that the value may take.
Definition: E57Format.cpp:3213
ustring elementName() const
Get element name of node.
Definition: E57Format.cpp:1060
NodeType type() const
Return the NodeType of a generic Node.
Definition: E57Format.cpp:942
ImageFile destImageFile() const
Get the ImageFile that was declared as the destination for the node when it was created.
Definition: E57Format.cpp:1083
double scaledValue() const
Get scaled value of element.
Definition: E57Format.cpp:3495
double scale() const
Get declared scaling factor.
Definition: E57Format.cpp:3561
int64_t minimum() const
Get the declared minimum that the raw value may take.
Definition: E57Format.cpp:3509
int64_t maximum() const
Get the declared maximum that the raw value may take.
Definition: E57Format.cpp:3535
double offset() const
Get declared offset.
Definition: E57Format.cpp:3575
ustring value() const
Get Unicode character string value stored.
Definition: E57Format.cpp:4013
ustring elementName() const
Get elementName string, that identifies the node in its parent.
Definition: E57Format.cpp:1245
bool isDefined(const ustring &pathName) const
Is the given pathName defined relative to this node.
Definition: E57Format.cpp:1297
void set(const ustring &pathName, const Node &n)
Add a new child at a given path.
Definition: E57Format.cpp:1388
Node get(int64_t index) const
Get a child element by positional index.
Definition: E57Format.cpp:1319
int64_t childCount() const
Return number of child nodes contained by this StructureNode.
Definition: E57Format.cpp:1275
Node get(int64_t index) const
Get a child element by positional index.
Definition: E57Format.cpp:1641
bool isDefined(const ustring &pathName) const
Is the given pathName defined relative to this node.
Definition: E57Format.cpp:1624
void append(const Node &n)
Append a child element to end of VectorNode.
Definition: E57Format.cpp:1701
int64_t childCount() const
Get number of child elements in this VectorNode.
Definition: E57Format.cpp:1599
RGB color structure.
Definition: ecvColorTypes.h:49
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
unsigned char ColorCompType
Default color components type (R,G and B)
Definition: ecvColorTypes.h:29
@ CALIBRATED_IMAGE
Definition: CVTypes.h:115
@ IMAGE
Definition: CVTypes.h:114
@ POINT_CLOUD
Definition: CVTypes.h:104
E57_DLL std::string errorCodeToString(ErrorCode ecode)
Get short string description of an E57 ErrorCode.
E57_DLL void getVersions(int &astmMajor, int &astmMinor, std::string &libraryId)
Get the version of ASTM E57 standard that the API implementation supports, and library id string.
constexpr char E57_V1_0_URI[]
Verify all checksums. This is the default. (slow)
Definition: E57Format.h:107
FloatPrecision
The IEEE floating point number precisions supported.
Definition: E57Format.h:71
@ E57_SINGLE
32 bit IEEE floating point number format
Definition: E57Format.h:72
@ E57_DOUBLE
64 bit IEEE floating point number format
Definition: E57Format.h:73
std::string ustring
UTF-8 encodeded Unicode string.
Definition: E57Format.h:54
constexpr ReadChecksumPolicy CHECKSUM_POLICY_SPARSE
Do not verify the checksums. (fast)
Definition: E57Format.h:98
@ E57_COMPRESSED_VECTOR
CompressedVectorNode class.
Definition: E57Format.h:61
@ E57_BLOB
BlobNode class.
Definition: E57Format.h:66
@ E57_STRUCTURE
StructureNode class.
Definition: E57Format.h:59
@ E57_VECTOR
VectorNode class.
Definition: E57Format.h:60
@ E57_INTEGER
IntegerNode class.
Definition: E57Format.h:62
@ E57_SCALED_INTEGER
ScaledIntegerNode class.
Definition: E57Format.h:63
@ E57_FLOAT
FloatNode class.
Definition: E57Format.h:64
@ E57_STRING
StringNode class.
Definition: E57Format.h:65
E57 scan useful header information (minimal set for ACloudViewer only ;)
Definition: E57Header.h:170
IntensityLimits intensityLimits
Definition: E57Header.h:172
PointStandardizedFieldsAvailable pointFields
Definition: E57Header.h:178
ColorLimits colorLimits
Definition: E57Header.h:175
Generic loading parameters.
Definition: FileIOFilter.h:51
QWidget * parentWidget
Parent widget (if any)
Definition: FileIOFilter.h:78
Generic saving parameters.
Definition: FileIOFilter.h:84
QWidget * parentWidget
Parent widget (if any)
Definition: FileIOFilter.h:93
Intrinsic parameters of the camera sensor.