ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
LASFWFFilter.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 "LASFWFFilter.h"
9 
10 // CV_CORE_LIB
11 #include <CVLog.h>
12 #include <CVTools.h>
13 
14 // CV_DB_LIB
15 #include <ecvColorScalesManager.h>
16 #include <ecvHObjectCaster.h>
17 #include <ecvPointCloud.h>
18 #include <ecvProgressDialog.h>
19 #include <ecvScalarField.h>
20 #include <ecvWaveform.h>
21 
22 // qPDALIO
23 #include <LASFields.h>
24 
25 // Qt
26 #include <QCoreApplication>
27 #include <QFile>
28 #include <QFileInfo>
29 #include <QString>
30 
31 // LASLib
32 #include <laspoint.hpp> // must include after above
33 #include <lasreader_las.hpp>
34 #include <laswriter_las.hpp>
35 
36 // Qt gui
37 #include <ui_saveLASFileDlg.h>
38 
39 // system
40 #include <assert.h>
41 #include <string.h>
42 
46  ExtraLasField(QString name = "Undefined", ccScalarField* _sf = nullptr)
47  : LasField(LAS_EXTRA, 0.0, 0.0, 0.0),
48  fieldName(name),
50  isShifted(false),
51  startIndex(0) {
52  if (fieldName != sanitizedName) {
53  CVLog::Warning(QString("Extra field '%1' renamed '%2' to comply to "
54  "LAS specifications")
55  .arg(fieldName)
56  .arg(sanitizedName));
57  }
58 
59  sf = _sf;
60  isShifted = (sf && sf->getGlobalShift() != 0.0);
61  }
62 
63  typedef QSharedPointer<ExtraLasField> Shared;
64 
65  inline QString getName() const override { return fieldName; }
66 
67  QString fieldName;
68  QString sanitizedName;
69  bool isShifted;
71 };
72 
74 static const char ProjectionVLR[] = "LASF_Projection";
75 
77 static const uint16_t VLR_HEADER_SIZE =
78  static_cast<uint16_t>(2 + 16 + 2 + 2 + 32);
79 
80 static QByteArray ToQByteArray(const LASvlr& vlr) {
81  QByteArray buffer;
82  uint16_t bufferSize = VLR_HEADER_SIZE + vlr.record_length_after_header;
83  buffer.resize(bufferSize);
84  if (buffer.size() == bufferSize) {
85  char* bufferData = buffer.data();
86  // reserved short
87  *(uint16_t*)bufferData = vlr.reserved;
88  bufferData += 2;
89  // user_id
90  for (int j = 0; j < 16; ++j, ++bufferData) *bufferData = vlr.user_id[j];
91  // record_id
92  *(uint16_t*)bufferData = vlr.record_id;
93  bufferData += 2;
94  // record_length_after_header
95  *(uint16_t*)bufferData = vlr.record_length_after_header;
96  bufferData += 2;
97  // description
98  for (int j = 0; j < 32; ++j, ++bufferData)
99  *bufferData = vlr.description[j];
100  // data
101  memcpy_s(bufferData, bufferSize - VLR_HEADER_SIZE, vlr.data,
102  vlr.record_length_after_header);
103  } else {
104  buffer.clear(); // probably already the case, but just to be sure
105  }
106 
107  return buffer;
108 }
109 
110 static bool FromQByteArray(const QByteArray& buffer, LASvlr& vlr) {
111  if (buffer.size() <= VLR_HEADER_SIZE) {
112  CVLog::Warning("[LAS] Invalid VLR buffer size");
113  return false;
114  }
115 
116  const char* bufferData = buffer.data();
117  // reserved short
118  vlr.reserved = *(const uint16_t*)bufferData;
119  bufferData += 2;
120  // user_id
121  for (int j = 0; j < 16; ++j, ++bufferData) vlr.user_id[j] = *bufferData;
122  // record_id
123  vlr.record_id = *(const uint16_t*)bufferData;
124  bufferData += 2;
125  // record_length_after_header
126  vlr.record_length_after_header = *(const uint16_t*)bufferData;
127  bufferData += 2;
128 
129  // check consistency
130  if (vlr.record_length_after_header != buffer.size() - VLR_HEADER_SIZE) {
131  CVLog::Warning("[LAS] Invalid VLR buffer size");
132  return false;
133  }
134 
135  // description
136  for (int j = 0; j < 32; ++j, ++bufferData) vlr.description[j] = *bufferData;
137 
138  // data
139  vlr.data = new U8[vlr.record_length_after_header];
140  if (!vlr.data) {
141  CVLog::Warning("[LAS] Not enough memory to restore VLR");
142  return false;
143  }
144 
145  memcpy_s(vlr.data, vlr.record_length_after_header,
146  buffer.data() + VLR_HEADER_SIZE, buffer.size() - VLR_HEADER_SIZE);
147  return true;
148 }
149 
151 class LASSaveDlg : public QDialog, public Ui::SaveLASFileDialog {
152 public:
153  explicit LASSaveDlg(QWidget* parent = 0)
154  : QDialog(parent), Ui::SaveLASFileDialog() {
155  setupUi(this);
156  }
157 
158  void clearEVLRs() {
159  evlrListWidget->clear();
160  extraFieldGroupBox->setEnabled(false);
161  extraFieldGroupBox->setChecked(false);
162  }
163 
164  void addEVLR(const QString& description) {
165  QListWidgetItem* item = new QListWidgetItem(description);
166  evlrListWidget->addItem(item);
167  // auto select the entry
168  item->setSelected(true);
169  // auto enable the extraFieldGroupBox
170  extraFieldGroupBox->setEnabled(true);
171  extraFieldGroupBox->setChecked(false);
172  }
173 
174  bool doSaveEVLR(size_t index) const {
175  if (!extraFieldGroupBox->isChecked()) return false;
176 
177  QListWidgetItem* item = evlrListWidget->item(static_cast<int>(index));
178  return item && item->isSelected();
179  }
180 };
181 
183  : FileIOFilter({"_LASFW Filter",
184  3.5f, // priority
185  QStringList{"las", "laz"}, "las",
186  QStringList{GetFileFilter()}, QStringList{GetFileFilter()},
187  Import | Export}) {}
188 
190  bool& multiple,
191  bool& exclusive) const {
192  if (type != CV_TYPES::POINT_CLOUD) {
193  return false;
194  }
195 
196  multiple = false;
197  exclusive = true;
198  return true;
199 }
200 
202  const QString& filename,
203  const SaveParameters& parameters) {
204  if (!entity || filename.isEmpty()) {
205  return CC_FERR_BAD_ARGUMENT;
206  }
207 
209  if (!cloud) {
211  "[LAS_FWF] This filter can only save one cloud at a time!");
213  }
214 
215  try {
216  bool hasFWF = cloud->hasFWF();
217  bool hasColors = cloud->hasColors();
218  bool hasIntensity = (cloud->getScalarFieldIndexByName(
220  bool isShifted = cloud->isShifted();
221 
222  if (hasFWF) {
223  // try to compress the FWF data before creating the file
224  cloud->compressFWFData();
225 
226  // save the FWF data before anything (in case it fails)
227  // we save it in a separate file
228  QFileInfo fi(filename);
229  QString fwFilename =
230  fi.absolutePath() + "/" + fi.completeBaseName() + ".wdp";
231  QFile fwfFile(fwFilename);
232  if (fwfFile.open(QFile::WriteOnly)) {
233  // write the EVLR header first
234  uint16_t reserved = 0;
235  fwfFile.write((const char*)&reserved, 2);
236 
237  char userID[16] = {0};
238  strcpy(userID, "LASF_Spec");
239  fwfFile.write(userID, 16);
240 
241  uint16_t recordID = 65535;
242  fwfFile.write((const char*)&recordID, 2);
243 
245  cloud->fwfData();
246  assert(data);
247  uint64_t recordLength = data->size();
248  fwfFile.write((const char*)&recordLength, 8);
249 
250  char description[32] = {0};
251  strcpy(description, "WAVEFORM DATA PACKETS");
252  fwfFile.write(description, 32);
253 
254  // eventually write the FWF data
255  fwfFile.write((const char*)data->data(), data->size());
256  }
257 
258  if (fwfFile.error() != QFile::NoError) {
259  CVLog::Warning(QString("[LAS_FWF] An error occurred while "
260  "writing the FWF data file!\n(%1)")
261  .arg(fwFilename));
262  hasFWF = false;
263  } else {
264  CVLog::Print(QString("[LAS_FWF] FWF data file written: %1")
265  .arg(fwFilename));
266  }
267  }
268 
269  // match cloud SFs with official LAS fields
270  std::vector<LasField> fieldsToSave;
271  uint8_t minPointFormat = 0;
272  LasField::GetLASFields(cloud, fieldsToSave, minPointFormat);
273 
274  // extended fields (i.e. other scalar fields)
275  std::vector<ExtraLasField> extraFieldsToSave;
276  {
277  for (unsigned i = 0; i < cloud->getNumberOfScalarFields(); ++i) {
278  ccScalarField* sf =
279  static_cast<ccScalarField*>(cloud->getScalarField(i));
280 
281  // check if this SF is already an official field
282  bool standardField = false;
283  for (const LasField& lf : fieldsToSave) {
284  if (lf.sf == sf) {
285  standardField = true;
286  break;
287  }
288  }
289 
290  if (!standardField) {
291  // we can create the corresponding EVLR
292  extraFieldsToSave.emplace_back(
293  ExtraLasField(QString(sf->getName()), sf));
294  }
295  }
296  }
297 
298  LASheader lasheader;
299  {
300  // header.SetDataFormatId(liblas::ePointFormat3);
301  CCVector3d bbMin, bbMax;
302  if (!cloud->getOwnGlobalBB(bbMin, bbMax)) {
303  return CC_FERR_NO_SAVE;
304  }
305 
306  // let the user choose between the original scale and the 'optimal'
307  // one (for accuracy, not for compression ;)
308  bool hasScaleMetaData = false;
309  CCVector3d lasScale(0, 0, 0);
310  lasScale.x = cloud->getMetaData(LAS_SCALE_X_META_DATA)
311  .toDouble(&hasScaleMetaData);
312  if (hasScaleMetaData) {
313  lasScale.y = cloud->getMetaData(LAS_SCALE_Y_META_DATA)
314  .toDouble(&hasScaleMetaData);
315  if (hasScaleMetaData) {
316  lasScale.z = cloud->getMetaData(LAS_SCALE_Z_META_DATA)
317  .toDouble(&hasScaleMetaData);
318  }
319  }
320 
321  bool hasOffsetMetaData = false;
322  CCVector3d lasOffset(0, 0, 0);
323  lasOffset.x = cloud->getMetaData(LAS_OFFSET_X_META_DATA)
324  .toDouble(&hasOffsetMetaData);
325  if (hasOffsetMetaData) {
326  lasOffset.y = cloud->getMetaData(LAS_OFFSET_Y_META_DATA)
327  .toDouble(&hasOffsetMetaData);
328  if (hasOffsetMetaData) {
329  lasOffset.z = cloud->getMetaData(LAS_OFFSET_Z_META_DATA)
330  .toDouble(&hasOffsetMetaData);
331  }
332  }
333 
334  // Try to use the global shift if no LAS offset is defined
335  if (!hasOffsetMetaData && isShifted) {
336  lasOffset =
337  -cloud->getGlobalShift(); //'global shift' is the
338  // opposite of LAS offset ;)
339  hasOffsetMetaData = true;
340  }
341 
342  // If we don't have any offset, let's use the min bounding-box
343  // corner
344  if (!hasOffsetMetaData && ecvGlobalShiftManager::NeedShift(bbMax)) {
345  // we have no choice, we'll use the min bounding box
346  lasOffset = bbMin;
347  }
348 
349  lasheader.x_offset = lasOffset.x;
350  lasheader.y_offset = lasOffset.y;
351  lasheader.z_offset = lasOffset.z;
352 
353  // maximum cloud 'extents' relatively to the 'offset' point
354  CCVector3d diagPos = bbMax - lasOffset;
355  CCVector3d diagNeg = lasOffset - bbMin;
356  CCVector3d diag(std::max(diagPos.x, diagNeg.x),
357  std::max(diagPos.y, diagNeg.y),
358  std::max(diagPos.z, diagNeg.z));
359  // optimal scale (for accuracy) --> 1e-9 because the maximum integer
360  // is roughly +/-2e+9
361  CCVector3d optimalScale(1.0e-9 * std::max<double>(diag.x, 1.0),
362  1.0e-9 * std::max<double>(diag.y, 1.0),
363  1.0e-9 * std::max<double>(diag.z, 1.0));
364 
365  bool canUseOriginalScale = false;
366  if (hasScaleMetaData) {
367  // we may not be able to use the previous LAS scale
368  canUseOriginalScale = (lasScale.x >= optimalScale.x &&
369  lasScale.y >= optimalScale.y &&
370  lasScale.z >= optimalScale.z);
371  }
372 
373  // uniformize the value to make it less disturbing to some lastools
374  // users ;)
375  {
376  double maxScale =
377  std::max(optimalScale.x,
378  std::max(optimalScale.y, optimalScale.z));
379  double n = ceil(
380  log10(maxScale)); // ceil because n should be negative
381  maxScale = pow(10.0, n);
382  optimalScale.x = optimalScale.y = optimalScale.z = maxScale;
383  }
384 
385  if (parameters.alwaysDisplaySaveDialog) {
386  // semi persistent save dialog
387  static QSharedPointer<LASSaveDlg> s_saveDlg(nullptr);
388  if (!s_saveDlg) s_saveDlg.reset(new LASSaveDlg(nullptr));
389 
390  s_saveDlg->bestAccuracyLabel->setText(
391  QString("(%1, %2, %3)")
392  .arg(optimalScale.x)
393  .arg(optimalScale.y)
394  .arg(optimalScale.z));
395 
396  if (hasScaleMetaData) {
397  QString text = QString("(%1, %2, %3)")
398  .arg(lasScale.x)
399  .arg(lasScale.y)
400  .arg(lasScale.z);
401  if (!canUseOriginalScale) {
402  text += "[too small]";
403  }
404  s_saveDlg->origAccuracyLabel->setText(text);
405  } else {
406  s_saveDlg->origAccuracyLabel->setText("none");
407  }
408 
409  if (!canUseOriginalScale) {
410  if (s_saveDlg->origRadioButton->isChecked())
411  s_saveDlg->bestRadioButton->setChecked(true);
412  s_saveDlg->origRadioButton->setEnabled(false);
413  }
414 
415  // additional fields
416  for (const ExtraLasField& f : extraFieldsToSave) {
417  s_saveDlg->addEVLR(f.fieldName);
418  }
419 
420  s_saveDlg->exec();
421 
422  if (s_saveDlg->bestRadioButton->isChecked()) {
423  lasScale = optimalScale;
424  } else if (s_saveDlg->customRadioButton->isChecked()) {
425  double s = s_saveDlg->customScaleDoubleSpinBox->value();
426  lasScale = CCVector3d(s, s, s);
427  }
428  // else
429  //{
430  // lasScale = lasScale;
431  // }
432 
433  size_t evlrIndex = 0;
434  for (size_t i = 0; i < extraFieldsToSave.size(); ++i) {
435  const ExtraLasField& f = extraFieldsToSave[i];
436  if (s_saveDlg->doSaveEVLR(i)) {
437  if (evlrIndex != i)
438  extraFieldsToSave[evlrIndex] =
439  extraFieldsToSave[i]; // fill in the gaps
440  ++evlrIndex;
441  }
442  }
443  extraFieldsToSave.resize(
444  evlrIndex); // can't fail, always smaller
445  } else if (!hasScaleMetaData) {
446  lasScale = optimalScale;
447  }
448 
449  lasheader.x_scale_factor = lasScale.x;
450  lasheader.y_scale_factor = lasScale.y;
451  lasheader.z_scale_factor = lasScale.z;
452 
453  minPointFormat = LasField::UpdateMinPointFormat(
454  minPointFormat, hasColors, hasFWF,
455  false); // no legacy format with this plugin
456 
457  lasheader.point_data_format = minPointFormat;
458  lasheader.version_minor =
459  LasField::VersionMinorForPointFormat(minPointFormat);
460  if (lasheader.version_minor == 4) {
461  // add the 148 byte difference between LAS 1.4 and LAS 1.2
462  // header sizes
463  lasheader.header_size += 148;
464  lasheader.offset_to_point_data += 148;
465  }
466  lasheader.point_data_record_length =
468  lasheader.point_data_format);
469 
470  // FWF descriptors and other parameters
471  if (hasFWF) {
472  // we always use an external file for FWF data
473  lasheader.start_of_waveform_data_packet_record = 0;
474  lasheader.global_encoding |= ((U16)4); // set external bit
475 
476  // if (!lasheader.vlr_wave_packet_descr)
477  //{
478  // lasheader.vlr_wave_packet_descr = new
479  // LASvlr_wave_packet_descr*[256]; for (int i = 0; i < 256;
480  // ++i)
481  // {
482  // lasheader.vlr_wave_packet_descr[i] = 0;
483  // }
484  // }
485 
486  for (ccPointCloud::FWFDescriptorSet::const_iterator it =
487  cloud->fwfDescriptors().begin();
488  it != cloud->fwfDescriptors().end(); ++it) {
489  LASvlr_wave_packet_descr* d = new LASvlr_wave_packet_descr;
490  {
491  d->setBitsPerSample(
492  static_cast<U8>(it.value().bitsPerSample));
493  d->setCompressionType(static_cast<U8>(0));
494  d->setDigitizerGain(
495  static_cast<F64>(it.value().digitizerGain));
496  d->setDigitizerOffset(
497  static_cast<F64>(it.value().digitizerOffset));
498  d->setNumberOfSamples(
499  static_cast<F64>(it.value().numberOfSamples));
500  d->setTemporalSpacing(
501  static_cast<U32>(it.value().samplingRate_ps));
502  }
503  // lasheader.vlr_wave_packet_descr[it.key()] = d;
504 
505  lasheader.add_vlr("LASF_Spec",
506  99 + static_cast<U16>(it.key()), 26,
507  (U8*)d, FALSE, "Waveform descriptor");
508  }
509  }
510 
511  // LASF_Projection
512  if (cloud->hasMetaData(ProjectionVLR)) {
513  QByteArray buffer =
514  cloud->getMetaData(ProjectionVLR).toByteArray();
515  if (!buffer.isEmpty()) {
516  LASvlr vlr;
517  if (FromQByteArray(buffer, vlr)) {
518  lasheader.add_vlr(ProjectionVLR, vlr.record_id,
519  vlr.record_length_after_header,
520  vlr.data, FALSE, vlr.description);
521  } else {
522  // warning message already issued
523  }
524  } else {
526  "[LAS_FWF] Invalid projection VLR meta-data. Can't "
527  "restore Coordinate Reference System.");
528  }
529  }
530 
531  // additional fields
532  {
533  for (ExtraLasField& f : extraFieldsToSave) {
534  assert(f.sf);
535 
536  LASattribute attribute(
537  f.isShifted || sizeof(ScalarType) == 8
538  ? LAS_ATTRIBUTE_F64
539  : LAS_ATTRIBUTE_F32,
540  qPrintable(f.sanitizedName),
541  "additional attributes");
542  lasheader.point_data_record_length +=
543  (attribute.data_type == LAS_ATTRIBUTE_F32 + 1
544  ? 4
545  : 8); // strangely, LASlib shifts the
546  // official type indexes :|
547  I32 attributeIndex = lasheader.add_attribute(attribute);
548  f.startIndex =
549  lasheader.get_attribute_start(attributeIndex);
550 
551  // U8* data = new U8[192];
552  // memset(data, 0, 192);
553  // data[2] = (f.isShifted || sizeof(ScalarType) == 8 ? 10 :
554  // 9); //double if shifted or ScalarType is also double,
555  // float otherwise data[3] = 0; //no options
556  // assert(f.sanitizedName.length() <= 32);
557  // strncpy(reinterpret_cast<char*>(data + 4),
558  // qPrintable(f.sanitizedName), f.sanitizedName.length());
559  // //name strncpy(reinterpret_cast<char*>(data + 160),
560  // "ACloudViewer scalar field", 24); //description
561  // lasheader.add_vlr("LASF_Spec", 4, 192, data, TRUE);
562  }
563 
564  // update VLR
565  if (!extraFieldsToSave.empty()) {
566  lasheader.update_extra_bytes_vlr(TRUE);
567  }
568  }
569  }
570 
571  // init point
572  LASpoint laspoint;
573  if (!laspoint.init(&lasheader, lasheader.point_data_format,
574  lasheader.point_data_record_length, 0)) {
576  }
577 
578  // open laswriter
579  LASwriterLAS laswriter;
580  bool useLAZ = QFileInfo(filename).suffix().toUpper().endsWith('Z');
581  if (!laswriter.open(qUtf8Printable(filename), &lasheader,
582  useLAZ ? LASZIP_COMPRESSOR_LAYERED_CHUNKED
583  : LASZIP_COMPRESSOR_NONE)) {
584  return CC_FERR_WRITING;
585  }
586 
587  // progress dialog
588  QScopedPointer<ecvProgressDialog> progressDialog(0);
589  if (parameters.parentWidget) {
590  progressDialog.reset(
591  new ecvProgressDialog(false, parameters.parentWidget));
592  progressDialog->setWindowTitle(QObject::tr("Export LAS file"));
593  progressDialog->setLabelText(
594  QObject::tr("Points: %1").arg(cloud->size()));
595  progressDialog->show();
596  QCoreApplication::processEvents();
597  }
598  cloudViewer::NormalizedProgress nProgress(progressDialog.data(),
599  cloud->size());
600 
601  bool hasReturnNumberField = false;
602  bool hasPlainClassificationField = false;
603  for (const LasField& f : fieldsToSave) {
604  if (f.type == LAS_RETURN_NUMBER) {
605  hasReturnNumberField = true;
606  } else if (f.type == LAS_CLASSIFICATION) {
607  hasPlainClassificationField = true;
608  }
609  }
610 
611  for (unsigned i = 0; i < cloud->size(); ++i) {
612  nProgress.oneStep();
613 
614  const CCVector3* P = cloud->getPoint(i);
615 
616  // populate the point
617  if (isShifted) {
618  laspoint.set_X(
619  static_cast<U32>(P->x / lasheader.x_scale_factor));
620  laspoint.set_Y(
621  static_cast<U32>(P->y / lasheader.y_scale_factor));
622  laspoint.set_Z(
623  static_cast<U32>(P->z / lasheader.z_scale_factor));
624  } else {
625  CCVector3d Pglobal = cloud->toGlobal3d<PointCoordinateType>(*P);
626  laspoint.set_X(
627  static_cast<U32>((Pglobal.x - lasheader.x_offset) /
628  lasheader.x_scale_factor));
629  laspoint.set_Y(
630  static_cast<U32>((Pglobal.y - lasheader.y_offset) /
631  lasheader.y_scale_factor));
632  laspoint.set_Z(
633  static_cast<U32>((Pglobal.z - lasheader.z_offset) /
634  lasheader.z_scale_factor));
635  }
636 
637  // additional fields
638  U8 classification = 0;
639  bool compositeClassif = false;
640  for (std::vector<LasField>::const_iterator it =
641  fieldsToSave.begin();
642  it != fieldsToSave.end(); ++it) {
643  assert(it->sf);
644  switch (it->type) {
645  case LAS_X:
646  case LAS_Y:
647  case LAS_Z:
648  assert(false);
649  break;
650  case LAS_INTENSITY:
651  laspoint.set_intensity(
652  static_cast<U16>(it->sf->getValue(i)));
653  break;
654  case LAS_RETURN_NUMBER:
655  laspoint.set_return_number(
656  static_cast<U8>(it->sf->getValue(i)));
657  break;
659  laspoint.set_number_of_returns(
660  static_cast<U8>(it->sf->getValue(i)));
661  break;
662  case LAS_SCAN_DIRECTION:
663  laspoint.set_scan_direction_flag(
664  static_cast<U8>(it->sf->getValue(i)));
665  break;
667  laspoint.set_edge_of_flight_line(
668  static_cast<U8>(it->sf->getValue(i)));
669  break;
670  case LAS_CLASSIFICATION:
671  laspoint.set_classification(
672  static_cast<U8>(it->sf->getValue(i)));
673  break;
674  case LAS_SCAN_ANGLE_RANK:
675  laspoint.set_scan_angle_rank(
676  static_cast<U8>(it->sf->getValue(i)));
677  break;
678  case LAS_USER_DATA:
679  laspoint.set_user_data(
680  static_cast<U8>(it->sf->getValue(i)));
681  break;
682  case LAS_POINT_SOURCE_ID:
683  laspoint.set_point_source_ID(
684  static_cast<U16>(it->sf->getValue(i)));
685  break;
686  case LAS_RED:
687  case LAS_GREEN:
688  case LAS_BLUE:
689  assert(false);
690  break;
691  case LAS_TIME:
692  laspoint.set_gps_time(
693  static_cast<F64>(it->sf->getValue(i)) +
694  it->sf->getGlobalShift());
695  break;
696  case LAS_CLASSIF_VALUE:
697  classification |=
698  (static_cast<U8>(it->sf->getValue(i)) &
699  31); // 5 first bits
700  compositeClassif = true;
701  break;
703  classification |=
704  (static_cast<U8>(it->sf->getValue(i)) &
705  32); // 6th bit
706  compositeClassif = true;
707  break;
709  classification |=
710  (static_cast<U8>(it->sf->getValue(i)) &
711  64); // 7th bit
712  compositeClassif = true;
713  break;
715  classification |=
716  (static_cast<U8>(it->sf->getValue(i)) &
717  128); // 8th bit
718  compositeClassif = true;
719  break;
720  case LAS_INVALID:
721  default:
722  assert(false);
723  break;
724  }
725  }
726 
727  if (compositeClassif && !hasPlainClassificationField) {
728  laspoint.set_classification(classification);
729  }
730 
731  if (hasColors) {
732  const ecvColor::Rgb& rgb = cloud->getPointColor(i);
733  // DGM: LAS colors are stored on 16 bits!
734  laspoint.set_R(static_cast<U16>(rgb.r) << 8);
735  laspoint.set_G(static_cast<U16>(rgb.g) << 8);
736  laspoint.set_B(static_cast<U16>(rgb.b) << 8);
737  }
738 
739  if (hasFWF) {
740  ccWaveformProxy proxy = cloud->waveformProxy(i);
741  if (proxy.isValid()) {
742  const ccWaveform& w = proxy.waveform();
743  laspoint.wavepacket.setIndex(
744  static_cast<U8>(proxy.descriptorID()));
745  laspoint.wavepacket.setLocation(
746  static_cast<F32>(w.echoTime_ps()));
747  laspoint.wavepacket.setOffset(
748  static_cast<U64>(w.dataOffset()) +
749  60); // EVLR header
750  laspoint.wavepacket.setSize(
751  static_cast<U32>(w.byteCount()));
752  laspoint.wavepacket.setXt(static_cast<F32>(w.beamDir().x));
753  laspoint.wavepacket.setYt(static_cast<F32>(w.beamDir().y));
754  laspoint.wavepacket.setZt(static_cast<F32>(w.beamDir().z));
755 
756  if (!hasReturnNumberField) {
757  // we know the return number
758  laspoint.set_return_number(
759  static_cast<U8>(w.returnIndex()));
760  }
761  } else {
762  if (!hasReturnNumberField) {
763  laspoint.set_return_number(0);
764  }
765  }
766  }
767 
768  // extra fields
769  for (const ExtraLasField& f : extraFieldsToSave) {
770  ScalarType s = f.sf->getValue(i);
771  if (f.isShifted) {
772  double sd = s + f.sf->getGlobalShift();
773  laspoint.set_attribute(f.startIndex, sd);
774  } else {
775  laspoint.set_attribute(f.startIndex, s);
776  }
777  }
778 
779  // write the point
780  laswriter.write_point(&laspoint);
781 
782  // add it to the inventory
783  laswriter.update_inventory(&laspoint);
784  }
785 
786  laswriter.close();
787 
788  // if (lasheader.vlr_wave_packet_descr)
789  //{
790  // DGM: already handled by LASlib ('vlr' list)
791  // for (int i = 0; i < 256; ++i)
792  //{
793  // if (lasheader.vlr_wave_packet_descr[i])
794  // delete lasheader.vlr_wave_packet_descr[i];
795  // }
796  // delete lasheader.vlr_wave_packet_descr;
797  //}
798  } catch (const std::bad_alloc&) {
800  } catch (...) {
802  }
803 
804  return CC_FERR_NO_ERROR;
805 }
806 
808  LasField* lasField,
809  unsigned totalCount,
810  unsigned currentCount,
811  ScalarType defaultValue = 0) {
812  if (sf) {
813  assert(false);
814  return true;
815  }
816 
817  if (!lasField) {
818  assert(false);
819  return false;
820  }
821 
822  if (lasField->type == LAS_EXTRA) {
823  sf = new ccScalarField(
824  qPrintable(static_cast<ExtraLasField*>(lasField)->fieldName));
825  } else {
826  sf = new ccScalarField(LAS_FIELD_NAMES[lasField->type]);
827  }
828 
829  // try to reserve the memory to store the field values
830  if (!sf->reserveSafe(totalCount)) {
831  CVLog::Warning(QString("[LAS] Not enough memory to load a field: '%1'")
832  .arg(sf->getName()));
833  sf->release();
834  sf = nullptr;
835  return false;
836  }
837  sf->link();
838 
839  // fill the previous points values (if any)
840  for (unsigned i = 0; i < currentCount; ++i) {
841  // set the previous values!
842  sf->addElement(defaultValue);
843  }
844 
845  return true;
846 }
847 
849  ccHObject& container,
850  LoadParameters& parameters) {
852 
853  // parameters
854  bool ignoreDefaultFields = true;
855 
856  try {
857  LASreaderLAS lasreader;
858  if (!lasreader.open(qUtf8Printable(filename))) {
859  CVLog::Warning("LASLib", "Failed to open 'lasreader'");
861  }
862 
863  unsigned pointCount = static_cast<unsigned>(lasreader.npoints);
864  CVLog::Print(QString("[LASLib] " +
865  QObject::tr("Reading %1 points").arg(pointCount)));
866 
867  // progress dialog
868  QScopedPointer<ecvProgressDialog> progressDialog(0);
869  if (parameters.parentWidget) {
870  progressDialog.reset(
871  new ecvProgressDialog(true, parameters.parentWidget));
872  progressDialog->setWindowTitle(QObject::tr("Import LAS file"));
873  progressDialog->setLabelText(
874  QObject::tr("Points: %1").arg(pointCount));
875  progressDialog->setRange(
876  0, 0 /*static_cast<int>(pointCount)*/); // DGM FIXME: the
877  // progress doesn't
878  // update! Is it
879  // because it's in
880  // a separate
881  // DLL/plugin?
882  progressDialog->show();
883  QCoreApplication::processEvents();
884  }
885  cloudViewer::NormalizedProgress nProgress(progressDialog.data(),
886  pointCount);
887 
888  // number of points read from the beginning of the current cloud part
889  unsigned pointsRead = 0;
890  CCVector3d Pshift(0, 0, 0);
891 
892  // create cloud
893  ccPointCloud* cloud = new ccPointCloud("unnamed");
894  if (!cloud->reserve(pointCount)) {
895  // not enough memory
896  lasreader.close();
897  delete cloud;
899  }
900 
901  bool ignoreColors = false;
902  bool hasColors = false;
903  bool hasColorsAboveZero = false;
904  int colorBitDec = 0;
905  uint64_t fwfDataOffset = 0;
906 
907  // DGM: from now on, we only enable scalar fields when we detect a valid
908  // value!
909  std::vector<LasField::Shared> fieldsToLoad;
910  {
911  fieldsToLoad.push_back(LasField::Shared(
912  new LasField(LAS_CLASSIFICATION, 0, 0,
913  255))); // unsigned char: between 0 and 255
914  // fieldsToLoad.push_back(LasField::Shared(new
915  // LasField(LAS_CLASSIF_VALUE, 0, 0, 31))); //5 bits: between 0 and
916  // 31 fieldsToLoad.push_back(LasField::Shared(new
917  // LasField(LAS_CLASSIF_SYNTHETIC, 0, 0, 1))); //1 bit: 0 or 1
918  // fieldsToLoad.push_back(LasField::Shared(new
919  // LasField(LAS_CLASSIF_KEYPOINT, 0, 0, 1))); //1 bit: 0 or 1
920  // fieldsToLoad.push_back(LasField::Shared(new
921  // LasField(LAS_CLASSIF_WITHHELD, 0, 0, 1))); //1 bit: 0 or 1
922  fieldsToLoad.push_back(LasField::Shared(
923  new LasField(LAS_INTENSITY, 0, 0,
924  65535))); // 16 bits: between 0 and 65536
925  fieldsToLoad.push_back(LasField::Shared(new LasField(
926  LAS_TIME, 0, 0,
927  -1.0))); // 8 bytes (double) --> we use global shift!
928  fieldsToLoad.push_back(LasField::Shared(new LasField(
929  LAS_RETURN_NUMBER, 1, 1, 7))); // 3 bits: between 1 and 7
930  fieldsToLoad.push_back(LasField::Shared(
932  7))); // 3 bits: between 1 and 7
933  fieldsToLoad.push_back(LasField::Shared(new LasField(
934  LAS_SCAN_DIRECTION, 0, 0, 1))); // 1 bit: 0 or 1
935  fieldsToLoad.push_back(LasField::Shared(new LasField(
936  LAS_FLIGHT_LINE_EDGE, 0, 0, 1))); // 1 bit: 0 or 1
937  fieldsToLoad.push_back(LasField::Shared(
938  new LasField(LAS_SCAN_ANGLE_RANK, 0, -90,
939  90))); // signed char: between -90 and +90
940  fieldsToLoad.push_back(LasField::Shared(
941  new LasField(LAS_USER_DATA, 0, 0,
942  255))); // unsigned char: between 0 and 255
943  fieldsToLoad.push_back(LasField::Shared(
944  new LasField(LAS_POINT_SOURCE_ID, 0, 0,
945  65535))); // 16 bits: between 0 and 65536
946  }
947 
948  // now for the extra values
949  for (I32 i = 0; i < lasreader.header.number_attributes; ++i) {
950  const LASattribute& attribute = lasreader.header.attributes[i];
951  ExtraLasField* field = new ExtraLasField(attribute.name);
952  field->startIndex = i; // lasreader.header.attribute_starts[i];
953  field->isShifted = (attribute.data_type == 10);
954  fieldsToLoad.push_back(LasField::Shared(field));
955  }
956 
957  bool hasFWF = (lasreader.header.vlr_wave_packet_descr != 0);
958  for (int fakeIteration = 0; hasFWF && fakeIteration < 1;
959  ++fakeIteration) {
960  try {
961  cloud->waveforms().resize(pointCount);
962  } catch (const std::bad_alloc&) {
963  CVLog::Warning(QString(
964  "Not enough memory to import the waveform data"));
965  hasFWF = false;
966  break;
967  }
968 
969  // determine the total size of the FWF data
970  QFile fwfDataSource;
971  uint64_t fwfDataCount = 0;
972  if (lasreader.header.start_of_waveform_data_packet_record != 0) {
973  // the FWF data is internal
974  assert(lasreader.header.global_encoding & 2);
975  // open the same file
976  fwfDataSource.setFileName(filename);
977  if (!fwfDataSource.open(QFile::ReadOnly)) {
979  QString("Failed to read the associated waveform "
980  "data packets"));
981  hasFWF = false;
982  break;
983  }
984  // seek for the waveform EVLR
985  if (!fwfDataSource.seek(
986  lasreader.header
987  .start_of_waveform_data_packet_record)) {
989  QString("Failed to find the associated waveform "
990  "data packets header"));
991  hasFWF = false;
992  break;
993  }
994  QByteArray evlrHeader = fwfDataSource.read(60);
995  if (evlrHeader.size() < 60) {
997  QString("Failed to read the associated waveform "
998  "data packets"));
999  hasFWF = false;
1000  break;
1001  }
1002 
1003  // get the number of bytes
1004  unsigned short reserved =
1005  *reinterpret_cast<const unsigned short*>(
1006  evlrHeader.constData() + 0);
1007  // char userID[16];
1008  // memcpy(userID, evlrHeader.constData() + 2, 16);
1009  unsigned short recordID =
1010  *reinterpret_cast<const unsigned short*>(
1011  evlrHeader.constData() + 18);
1012  assert(recordID == 65535);
1013  fwfDataCount = *reinterpret_cast<const uint64_t*>(
1014  evlrHeader.constData() +
1015  20); // see LAS 1.4 EVLR header specifications
1016  if (fwfDataCount == 0) {
1017  CVLog::Warning(QString(
1018  "Invalid waveform data packet size (0). We'll load "
1019  "all the remaining part of the file!"));
1020  fwfDataCount = fwfDataSource.size() - fwfDataSource.pos();
1021  }
1022  // char description[32];
1023  // memcpy(description, evlrHeader.constData() + 28, 32);
1024  fwfDataOffset = 60;
1025  } else {
1026  QString wdpFilename = filename;
1027  wdpFilename.replace(QFileInfo(filename).suffix(), "wdp");
1028  fwfDataSource.setFileName(wdpFilename);
1029  if (!fwfDataSource.open(QFile::ReadOnly)) {
1031  QString("Failed to read the associated waveform "
1032  "data packets file (looking for '%1')")
1033  .arg(wdpFilename));
1034  hasFWF = false;
1035  break;
1036  }
1037 
1038  // the number of bytes is simply the file size
1039  fwfDataCount = fwfDataSource.size();
1040 
1041  if (fwfDataCount > 60) {
1042  QByteArray evlrHeader = fwfDataSource.read(60);
1043  const char* userID = reinterpret_cast<const char*>(
1044  evlrHeader.constData() +
1045  2); // see LAS 1.4 EVLR header specifications
1046  if (strncmp(userID, "LASF_Spec", 9) == 0) {
1047  // this is a valid EVLR header, we can skip it
1048  fwfDataCount -= 60;
1049  fwfDataOffset += 60;
1050  } else {
1051  // this doesn't look like a valid EVLR
1052  fwfDataSource.seek(0);
1053  }
1054  }
1055  }
1056 
1057  // load the FWF data
1058  if (fwfDataSource.isOpen() && fwfDataCount != 0) {
1059  ccPointCloud::FWFDataContainer* container =
1061  try {
1062  container->resize(fwfDataCount);
1063  } catch (const std::bad_alloc&) {
1064  CVLog::Warning(QString(
1065  "Not enough memory to import the waveform data"));
1066  cloud->waveforms().clear();
1067  delete container;
1068  hasFWF = false;
1069  break;
1070  }
1071 
1072  fwfDataSource.read((char*)container->data(), fwfDataCount);
1073  fwfDataSource.close();
1074 
1075  cloud->fwfData() =
1077  }
1078  }
1079 
1080  CCVector3d lasScale = CCVector3d(lasreader.header.x_scale_factor,
1081  lasreader.header.y_scale_factor,
1082  lasreader.header.z_scale_factor);
1083  CCVector3d lasShift = -CCVector3d(lasreader.header.x_offset,
1084  lasreader.header.y_offset,
1085  lasreader.header.z_offset);
1086 
1087  cloud->setMetaData(LAS_SCALE_X_META_DATA, QVariant(lasScale.x));
1088  cloud->setMetaData(LAS_SCALE_Y_META_DATA, QVariant(lasScale.y));
1089  cloud->setMetaData(LAS_SCALE_Z_META_DATA, QVariant(lasScale.z));
1090 
1092 
1093  // read the points
1094  for (size_t pointIndex = 0; lasreader.read_point(); ++pointIndex) {
1095  const LASpoint& point = lasreader.point;
1096 
1097  CCVector3d P(point.quantizer->get_x(point.X),
1098  point.quantizer->get_y(point.Y),
1099  point.quantizer->get_z(point.Z));
1100 
1101  // Waveform
1102  if (hasFWF && point.have_wavepacket) {
1103  // if (fwfReader->read_waveform(&point))
1104  {
1105  U8 packetIndex = point.wavepacket.getIndex();
1106  if (!descriptors.contains(packetIndex)) {
1107  LASvlr_wave_packet_descr* descriptor =
1108  lasreader.header
1109  .vlr_wave_packet_descr[packetIndex];
1110  WaveformDescriptor wfd;
1111  if (descriptor) {
1112  wfd.numberOfSamples =
1113  descriptor->getNumberOfSamples();
1114  wfd.bitsPerSample = descriptor->getBitsPerSample();
1115  wfd.digitizerGain = descriptor->getDigitizerGain();
1116  if (wfd.digitizerGain == 0) {
1117  // shouldn't be 0 by default!
1118  wfd.digitizerGain = 1.0;
1119  }
1120  wfd.digitizerOffset =
1121  descriptor->getDigitizerOffset();
1122  wfd.samplingRate_ps =
1123  descriptor->getTemporalSpacing();
1124  }
1125  descriptors.insert(packetIndex, wfd);
1126  }
1127 
1128  ccWaveform& w = cloud->waveforms()[pointIndex];
1129  w.setDescriptorID(packetIndex);
1131  point.wavepacket.getOffset() - fwfDataOffset,
1132  point.wavepacket.getSize());
1133  w.setBeamDir(CCVector3f(point.wavepacket.getXt(),
1134  point.wavepacket.getYt(),
1135  point.wavepacket.getZt()));
1136  w.setEchoTime_ps(point.wavepacket.getLocation());
1137  w.setReturnIndex(point.return_number);
1138  }
1139  }
1140 
1141  if (cloud->size() == 0) // first point
1142  {
1143  // backup input global parameters
1144  ecvGlobalShiftManager::Mode csModeBackup =
1145  parameters.shiftHandlingMode;
1146  bool useLasShift = false;
1147  // set the LAS shift as default shift (if none was provided)
1148  if (lasShift.norm2() != 0 &&
1149  (!parameters.coordinatesShiftEnabled ||
1150  !*parameters.coordinatesShiftEnabled)) {
1151  useLasShift = true;
1152  Pshift = lasShift;
1153  if (csModeBackup != ecvGlobalShiftManager::NO_DIALOG &&
1154  csModeBackup !=
1156  parameters.shiftHandlingMode =
1158  }
1159  }
1160  bool preserveCoordinateShift = true;
1161  if (HandleGlobalShift(P, Pshift, preserveCoordinateShift,
1162  parameters, useLasShift)) {
1163  if (preserveCoordinateShift) {
1164  cloud->setGlobalShift(Pshift);
1165  }
1167  "[LAS] Cloud has been recentered! Translation: "
1168  "(%.2f ; %.2f ; %.2f)",
1169  Pshift.x, Pshift.y, Pshift.z);
1170  }
1171 
1172  // restore previous parameters
1173  parameters.shiftHandlingMode = csModeBackup;
1174  }
1175 
1176  // color
1177  if (!ignoreColors) {
1178  if (!hasColors) {
1179  U16 mergedColorComp =
1180  point.rgb[0] | point.rgb[1] | point.rgb[2];
1181  if (mergedColorComp != 0) {
1182  hasColors = cloud->reserveTheRGBTable();
1183  if (!hasColors) {
1184  // not enough memory!
1186  "[LAS] Not enough memory to load RGB "
1187  "colors!");
1188  ignoreColors = true;
1189  } else {
1190  for (unsigned i = 0; i < cloud->size(); ++i) {
1191  // set all previous colors!
1192  cloud->addRGBColor(ecvColor::black);
1193  }
1194  }
1195  }
1196  }
1197 
1198  if (hasColors) {
1199  if (colorBitDec == 0) {
1200  U16 mergedColorComp =
1201  point.rgb[0] | point.rgb[1] | point.rgb[2];
1202  if (mergedColorComp > 255) {
1203  // by default we assume the colors are coded on 8
1204  // bits...
1205  //...while they are theoretically coded on 16 bits
1206  //(but some software wrongly export LAS files with
1207  // colors on 8 bits). As soon as we detect a value
1208  // higher than 255, we shift to 16 bits mode!
1209  colorBitDec = 8;
1210  for (unsigned i = 0; i < cloud->size(); ++i) {
1211  // reset all previous colors!
1212  cloud->setPointColor(i, ecvColor::black);
1213  }
1214  }
1215  }
1216 
1218  static_cast<unsigned char>(
1219  (point.rgb[0] >> colorBitDec) & 255),
1220  static_cast<unsigned char>(
1221  (point.rgb[1] >> colorBitDec) & 255),
1222  static_cast<unsigned char>(
1223  (point.rgb[2] >> colorBitDec) & 255));
1224 
1225  cloud->addRGBColor(color);
1226  }
1227  }
1228 
1229  // additional fields
1230  for (LasField::Shared& field : fieldsToLoad) {
1231  double value = 0.0;
1232  switch (field->type) {
1233  case LAS_INTENSITY:
1234  value = static_cast<double>(point.get_intensity());
1235  break;
1236  case LAS_RETURN_NUMBER:
1237  value = static_cast<double>(point.get_return_number());
1238  break;
1239  case LAS_NUMBER_OF_RETURNS:
1240  value = static_cast<double>(
1241  point.get_number_of_returns());
1242  break;
1243  case LAS_SCAN_DIRECTION:
1244  value = static_cast<double>(
1245  point.get_scan_direction_flag());
1246  break;
1247  case LAS_FLIGHT_LINE_EDGE:
1248  value = static_cast<double>(
1249  point.get_edge_of_flight_line());
1250  break;
1251  case LAS_CLASSIFICATION:
1252  value = static_cast<double>(point.get_classification());
1253  break;
1254  case LAS_SCAN_ANGLE_RANK:
1255  value = static_cast<double>(
1256  point.get_scan_angle_rank());
1257  break;
1258  case LAS_USER_DATA:
1259  value = static_cast<double>(point.get_user_data());
1260  break;
1261  case LAS_POINT_SOURCE_ID:
1262  value = static_cast<double>(
1263  point.get_point_source_ID());
1264  break;
1265  case LAS_TIME:
1266  value = point.get_gps_time();
1267  if (field->sf) {
1268  // shift time values (so as to avoid losing
1269  // accuracy)
1270  value -= field->sf->getGlobalShift();
1271  }
1272  break;
1273  case LAS_CLASSIF_VALUE:
1274  value = static_cast<double>(point.get_classification() &
1275  31); // 5 bits
1276  break;
1277  case LAS_CLASSIF_SYNTHETIC:
1278  value = static_cast<double>(point.get_classification() &
1279  32); // bit #6
1280  break;
1281  case LAS_CLASSIF_KEYPOINT:
1282  value = static_cast<double>(point.get_classification() &
1283  64); // bit #7
1284  break;
1285  case LAS_CLASSIF_WITHHELD:
1286  value = static_cast<double>(point.get_classification() &
1287  128); // bit #8
1288  break;
1289  case LAS_EXTRA:
1290  value = point.get_attribute_as_float(
1291  static_cast<ExtraLasField*>(field.data())
1292  ->startIndex);
1293  break;
1294  default:
1295  // ignored
1296  continue;
1297  }
1298 
1299  if (field->sf) {
1300  ScalarType s = static_cast<ScalarType>(value);
1301  field->sf->addElement(s);
1302  } else {
1303  // first point? we track its value
1304  if (cloud->size() == 0) {
1305  field->firstValue = value;
1306  }
1307 
1308  if (!ignoreDefaultFields || value != field->firstValue ||
1309  (field->firstValue != field->defaultValue &&
1310  field->firstValue >= field->minValue)) {
1311  if (PrepareLASField(field->sf, field.data(), pointCount,
1312  cloud->size(), field->firstValue)) {
1313  if (field->type == LAS_TIME ||
1314  (field->type == LAS_EXTRA &&
1315  static_cast<ExtraLasField*>(field.data())
1316  ->isShifted)) {
1317  // we use the first value as 'global shift'
1318  // (otherwise we will lose accuracy)
1319  field->sf->setGlobalShift(field->firstValue);
1320  value -= field->firstValue;
1322  "[LAS] Time SF has been shifted to "
1323  "prevent a loss of accuracy (%.2f)",
1324  field->firstValue);
1325  field->firstValue = 0;
1326  }
1327 
1328  ScalarType s = static_cast<ScalarType>(value);
1329  field->sf->addElement(s);
1330  } else {
1332  QString("[LAS] Not enough memory: '%1' "
1333  "field will be ignored!")
1334  .arg(LAS_FIELD_NAMES[field->type]));
1335  field->sf->release();
1336  field->sf = 0;
1337  }
1338  }
1339  }
1340  }
1341 
1342  cloud->addPoint(CCVector3::fromArray((P + Pshift).u));
1343 
1344  if (progressDialog && !nProgress.oneStep()) {
1346  break;
1347  }
1348  }
1349 
1350  for (uint32_t i = 0;
1351  i < lasreader.header.number_of_variable_length_records; ++i) {
1352  const LASvlr& vlr = lasreader.header.vlrs[i];
1353  if (QString(vlr.user_id) == ProjectionVLR) {
1354  QByteArray buffer = ToQByteArray(vlr);
1355 
1356  if (buffer.size() != 0) {
1357  cloud->setMetaData(ProjectionVLR, buffer);
1358  } else {
1360  "[LAS] Failed to save projection information (not "
1361  "enough memory)");
1362  }
1363  break;
1364  }
1365  }
1366 
1367  // if (fwfReader)
1368  //{
1369  // delete fwfReader;
1370  // fwfReader = 0;
1371  // }
1372 
1373  lasreader.close();
1374 
1375  if (cloud->size() == 0) {
1376  CVLog::Warning("LASLib", QObject::tr("No valid point in file"));
1377 
1378  // release scalar fields (if any)
1379  for (LasField::Shared& field : fieldsToLoad) {
1380  if (field->sf) {
1381  field->sf->release();
1382  }
1383  }
1384 
1385  // release the cloud
1386  // delete cloud;
1387  // cloud = 0;
1388  } else {
1389  // associate the cloud with the various fields
1390  for (LasField::Shared& field : fieldsToLoad) {
1391  if (field->sf) {
1392  field->sf->computeMinAndMax();
1393 
1394  if (field->type == LAS_CLASSIFICATION ||
1395  field->type == LAS_CLASSIF_VALUE ||
1396  field->type == LAS_CLASSIF_SYNTHETIC ||
1397  field->type == LAS_CLASSIF_KEYPOINT ||
1398  field->type == LAS_CLASSIF_WITHHELD ||
1399  field->type == LAS_RETURN_NUMBER ||
1400  field->type == LAS_NUMBER_OF_RETURNS) {
1401  int cMin = static_cast<int>(field->sf->getMin());
1402  int cMax = static_cast<int>(field->sf->getMax());
1403  field->sf->setColorRampSteps(
1404  std::min<int>(cMax - cMin + 1, 256));
1405  // classifSF->setMinSaturation(cMin);
1406  } else if (field->type == LAS_INTENSITY) {
1407  // set default grey color scale
1408  field->sf->setColorScale(
1411  }
1412 
1413  int sfIdx = cloud->addScalarField(field->sf);
1414  if (sfIdx == 0) {
1415  // enable the first one by default
1416  cloud->setCurrentDisplayedScalarField(sfIdx);
1417  cloud->showSF(true);
1418  }
1419 
1420  field->sf->release();
1421  field->sf = 0; // just in case
1422  } else {
1424  QString("[LAS] All '%1' values were the same (%2)! "
1425  "We ignored them...")
1426  .arg(field->type == LAS_EXTRA
1427  ? field->getName()
1428  : QString(LAS_FIELD_NAMES
1429  [field->type]))
1430  .arg(field->firstValue));
1431  }
1432  }
1433 
1434  if (hasColors) {
1435  cloud->showColors(true);
1436  }
1437 
1438  container.addChild(cloud);
1439  }
1440  } catch (const std::bad_alloc&) {
1442  } catch (...) {
1444  }
1445 
1446  return result;
1447 }
Vector3Tpl< float > CCVector3f
Float 3D Vector.
Definition: CVGeom.h:801
Vector3Tpl< double > CCVector3d
Double 3D Vector.
Definition: CVGeom.h:804
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
std::string name
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_THIRD_PARTY_LIB_FAILURE
Definition: FileIOFilter.h:36
@ CC_FERR_WRITING
Definition: FileIOFilter.h:25
@ CC_FERR_BAD_ARGUMENT
Definition: FileIOFilter.h:22
@ CC_FERR_NO_SAVE
Definition: FileIOFilter.h:27
@ CC_FERR_NO_ERROR
Definition: FileIOFilter.h:21
@ CC_FERR_BAD_ENTITY_TYPE
Definition: FileIOFilter.h:29
@ CC_FERR_NOT_ENOUGH_MEMORY
Definition: FileIOFilter.h:31
static QByteArray ToQByteArray(const LASvlr &vlr)
bool PrepareLASField(ccScalarField *&sf, LasField *lasField, unsigned totalCount, unsigned currentCount, ScalarType defaultValue=0)
static const uint16_t VLR_HEADER_SIZE
VLR buffer header size (in bytes)
static bool FromQByteArray(const QByteArray &buffer, LASvlr &vlr)
static const char ProjectionVLR[]
Projection VLR.
static const char LAS_OFFSET_X_META_DATA[]
Definition: LASFields.h:26
static const char LAS_SCALE_X_META_DATA[]
Definition: LASFields.h:23
const char LAS_FIELD_NAMES[][28]
Definition: LASFields.h:63
static const char LAS_OFFSET_Y_META_DATA[]
Definition: LASFields.h:27
@ LAS_INVALID
Definition: LASFields.h:60
@ LAS_CLASSIF_KEYPOINT
Definition: LASFields.h:56
@ LAS_Z
Definition: LASFields.h:38
@ LAS_SCAN_DIRECTION
Definition: LASFields.h:42
@ LAS_POINT_SOURCE_ID
Definition: LASFields.h:47
@ LAS_RETURN_NUMBER
Definition: LASFields.h:40
@ LAS_Y
Definition: LASFields.h:37
@ LAS_CLASSIF_VALUE
Definition: LASFields.h:54
@ LAS_FLIGHT_LINE_EDGE
Definition: LASFields.h:43
@ LAS_TIME
Definition: LASFields.h:51
@ LAS_NUMBER_OF_RETURNS
Definition: LASFields.h:41
@ LAS_SCAN_ANGLE_RANK
Definition: LASFields.h:45
@ LAS_EXTRA
Definition: LASFields.h:52
@ LAS_CLASSIF_WITHHELD
Definition: LASFields.h:57
@ LAS_BLUE
Definition: LASFields.h:50
@ LAS_X
Definition: LASFields.h:36
@ LAS_GREEN
Definition: LASFields.h:49
@ LAS_RED
Definition: LASFields.h:48
@ LAS_CLASSIF_SYNTHETIC
Definition: LASFields.h:55
@ LAS_USER_DATA
Definition: LASFields.h:46
@ LAS_CLASSIFICATION
Definition: LASFields.h:44
@ LAS_INTENSITY
Definition: LASFields.h:39
static const char LAS_SCALE_Z_META_DATA[]
Definition: LASFields.h:25
static const char LAS_SCALE_Y_META_DATA[]
Definition: LASFields.h:24
static const char LAS_OFFSET_Z_META_DATA[]
Definition: LASFields.h:28
QSharedPointer< LASSaveDlg > s_saveDlg(nullptr)
Semi persistent save dialog.
math::float4 color
core::Tensor result
Definition: VtkUtils.cpp:76
virtual void link()
Increase counter.
Definition: CVShareable.cpp:33
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
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.
virtual CC_FILE_ERROR saveToFile(ccHObject *entity, const QString &filename, const SaveParameters &parameters) override
Saves an entity (or a group of) to a file.
virtual CC_FILE_ERROR loadFile(const QString &filename, ccHObject &container, LoadParameters &parameters) override
Loads one or more entities from a file.
static QString GetFileFilter()
Definition: LASFWFFilter.h:19
virtual 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.
LAS Save dialog.
void addEVLR(const QString &description)
bool doSaveEVLR(size_t index) const
LASSaveDlg(QWidget *parent=0)
void clearEVLRs()
Type y
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
Type norm2() const
Returns vector square norm.
Definition: CVGeom.h:417
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
Waveform descriptor.
Definition: ecvWaveform.h:23
uint32_t samplingRate_ps
Sampling rate in pico seconds.
Definition: ecvWaveform.h:44
uint8_t bitsPerSample
Number of bits per sample.
Definition: ecvWaveform.h:49
uint32_t numberOfSamples
Number of samples.
Definition: ecvWaveform.h:43
double digitizerOffset
Definition: ecvWaveform.h:47
static ccColorScale::Shared GetDefaultScale(DEFAULT_SCALES scale=BGYR)
Returns a pre-defined color scale (static shortcut)
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
static ccPointCloud * ToPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccPointCloud.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
void setMetaData(const QString &key, const QVariant &data)
Sets a meta-data element.
QVariant getMetaData(const QString &key) const
Returns a given associated meta data.
bool hasMetaData(const QString &key) const
Returns whether a meta-data element with the given key exists or not.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
QMap< uint8_t, WaveformDescriptor > FWFDescriptorSet
Waveform descriptors set.
FWFDescriptorSet & fwfDescriptors()
Gives access to the FWF descriptors.
std::vector< ccWaveform > & waveforms()
Gives access to the associated FWF data.
QSharedPointer< const FWFDataContainer > SharedFWFDataContainer
ccWaveformProxy waveformProxy(unsigned index) const
Returns a proxy on a given waveform.
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool hasFWF() const
Returns whether the cloud has associated Full WaveForm data.
bool reserveTheRGBTable()
Reserves memory to store the RGB colors.
bool compressFWFData()
Compresses the associated FWF data container.
bool hasColors() const override
Returns whether colors are enabled or not.
std::vector< uint8_t > FWFDataContainer
Waveform data container.
void setPointColor(size_t pointIndex, const ecvColor::Rgb &col)
Sets a particular point color.
SharedFWFDataContainer & fwfData()
Gives access to the associated FWF data container.
const ecvColor::Rgb & getPointColor(unsigned pointIndex) const override
Returns color 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.
double getGlobalShift() const
Returns the global shift (if any)
bool getOwnGlobalBB(CCVector3d &minCorner, CCVector3d &maxCorner) override
CCVector3d toGlobal3d(const Vector3Tpl< T > &Plocal) const
Returns the point back-projected into the original coordinates system.
bool isShifted() const
Returns whether the cloud is shifted or not.
virtual void setGlobalShift(double x, double y, double z)
Sets shift applied to original coordinates (information storage only)
virtual const CCVector3d & getGlobalShift() const
Returns the shift applied to original coordinates.
Waveform proxy.
Definition: ecvWaveform.h:189
bool isValid() const
Returns whether the waveform (proxy) is valid or not.
Definition: ecvWaveform.h:198
uint8_t descriptorID() const
Returns the associated descriptor (ID)
Definition: ecvWaveform.h:206
const ccWaveform & waveform() const
Returns the waveform.
Definition: ecvWaveform.h:256
Waveform.
Definition: ecvWaveform.h:55
uint32_t byteCount() const
Returns the number of allocated bytes.
Definition: ecvWaveform.h:112
void setReturnIndex(uint8_t index)
Sets the return index.
Definition: ecvWaveform.h:143
void setDescriptorID(uint8_t id)
Sets the associated descriptor (ID)
Definition: ecvWaveform.h:70
void setBeamDir(const CCVector3f &dir)
Sets the beam direction.
Definition: ecvWaveform.h:126
float echoTime_ps() const
Returns the echo time (in picoseconds)
Definition: ecvWaveform.h:135
uint8_t returnIndex() const
Returns the return index.
Definition: ecvWaveform.h:141
const CCVector3f & beamDir() const
Returns the beam direction.
Definition: ecvWaveform.h:129
void setDataDescription(uint64_t dataOffset, uint32_t byteCount)
Describes the waveform data.
uint64_t dataOffset() const
Returns the byte offset to waveform data.
Definition: ecvWaveform.h:115
void setEchoTime_ps(float time_ps)
Set the echo time (in picoseconds)
Definition: ecvWaveform.h:132
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.
unsigned getNumberOfScalarFields() const
Returns the number of associated (and active) scalar fields.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
const CCVector3 * getPoint(unsigned index) const override
void addElement(ScalarType value)
Definition: ScalarField.h:99
const char * getName() const
Returns scalar field name.
Definition: ScalarField.h:43
bool reserveSafe(std::size_t count)
Reserves memory (no exception thrown)
Definition: ScalarField.cpp:71
RGB color structure.
Definition: ecvColorTypes.h:49
static bool NeedShift(const CCVector3d &P)
Returns whether a particular point (coordinates) is too big or not.
Mode
Strategy to handle coordinates shift/scale.
Graphical progress indicator (thread-safe)
int max(int a, int b)
Definition: cutil_math.h:48
#define TRUE
Definition: lsd.c:119
#define FALSE
Definition: lsd.c:115
unsigned long long U64
Definition: lz4.c:127
unsigned int U32
Definition: lz4.c:125
unsigned short U16
Definition: lz4.c:124
@ POINT_CLOUD
Definition: CVTypes.h:104
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
constexpr Rgb black(0, 0, 0)
cloudViewer::NormalizedProgress * nProgress
CorePointDescSet * descriptors
Custom ("Extra bytes") field (EVLR)
QSharedPointer< ExtraLasField > Shared
QString fieldName
ExtraLasField(QString name="Undefined", ccScalarField *_sf=nullptr)
Default constructor.
QString sanitizedName
QString getName() const override
Returns official field name.
Generic loading parameters.
Definition: FileIOFilter.h:51
ecvGlobalShiftManager::Mode shiftHandlingMode
How to handle big coordinates.
Definition: FileIOFilter.h:64
QWidget * parentWidget
Parent widget (if any)
Definition: FileIOFilter.h:78
bool * coordinatesShiftEnabled
Whether shift on load has been applied after loading (optional)
Definition: FileIOFilter.h:69
Generic saving parameters.
Definition: FileIOFilter.h:84
QWidget * parentWidget
Parent widget (if any)
Definition: FileIOFilter.h:93
LAS field descriptor.
Definition: LASFields.h:89
ccScalarField * sf
Definition: LASFields.h:333
static bool GetLASFields(ccPointCloud *cloud, std::vector< LasField > &fieldsToSave, uint8_t &minPointFormat)
Returns the (compliant) LAS fields in a point cloud.
Definition: LASFields.h:113
static uint8_t UpdateMinPointFormat(uint8_t minPointFormat, bool withRGB, bool withFWF, bool allowLegacyFormats=true)
Definition: LASFields.h:233
QSharedPointer< LasField > Shared
Shared type.
Definition: LASFields.h:91
static unsigned GetFormatRecordLength(uint8_t pointFormat)
Definition: LASFields.h:199
LAS_FIELDS type
Definition: LASFields.h:332
static QString SanitizeString(const QString &str)
Definition: LASFields.h:296
static uint8_t VersionMinorForPointFormat(uint8_t pointFormat)
Definition: LASFields.h:229
Definition: lsd.c:149