ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
SimpleBinFilter.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 "SimpleBinFilter.h"
9 
10 // Qt
11 #include <QFileInfo>
12 #include <QSettings>
13 
14 // CV_DB_LIB
15 #include <ecvHObjectCaster.h>
16 #include <ecvPointCloud.h>
17 #include <ecvProgressDialog.h>
18 #include <ecvScalarField.h>
19 
20 // system
21 #include <assert.h>
22 
23 // header: 32 first bytes
24 static const size_t c_headerSize = 64;
25 // header flag
26 static const quint16 s_headerFlagSBF =
27  (static_cast<quint16>(42) | static_cast<quint16>(42 << 8));
28 
30  : FileIOFilter({"_Simple binary Filter",
31  6.0f, // priority
32  QStringList{"sbf", "data"}, "sbf",
33  QStringList{"Simple binary file (*.sbf)"},
34  QStringList{"Simple binary file (*.sbf)"},
35  Import | Export}) {}
36 
38  bool& multiple,
39  bool& exclusive) const {
40  multiple = false;
41  exclusive = true;
42  return (type == CV_TYPES::POINT_CLOUD);
43 }
44 
46  const QString& filename,
47  const SaveParameters& parameters) {
48  if (!root || filename.isNull() || !root->isA(CV_TYPES::POINT_CLOUD)) {
49  assert(false);
50  return CC_FERR_BAD_ARGUMENT;
51  }
52 
54  if (!cloud) {
55  return CC_FERR_BAD_ARGUMENT;
56  }
57 
58  QString headerFilename = filename;
59  QString dataFilename = filename + ".data";
60 
61  CVLog::Print(QString("[SBF] Saving file '%1'...").arg(headerFilename));
62 
63  // write the text file as an INI file
64  {
65  QSettings headerFile(headerFilename, QSettings::IniFormat);
66 
67  headerFile.beginGroup("SBF");
68  headerFile.setValue("Points", cloud->size());
69 
70  // save the global shift (if any)
71  const CCVector3d& globalShift = cloud->getGlobalShift();
72  if (globalShift.norm2() != 0) {
73  QStringList strGlobalShift;
74  strGlobalShift << QString::number(globalShift.x, 'f', 6);
75  strGlobalShift << QString::number(globalShift.y, 'f', 6);
76  strGlobalShift << QString::number(globalShift.z, 'f', 6);
77  headerFile.setValue("GlobalShift", strGlobalShift);
78  }
79 
80  double globalScale = cloud->getGlobalScale();
81  if (globalScale != 1.0) {
82  headerFile.setValue("GlobalScale", globalScale);
83  }
84 
85  // save the scalar field names (if any)
86  if (cloud->hasScalarFields()) {
87  unsigned sfCount = cloud->getNumberOfScalarFields();
88  headerFile.setValue("SFCount", sfCount);
89 
90  // try to load the description of each SF
91  for (int i = 0; i < static_cast<int>(sfCount); ++i) {
92  QString key = QString("SF%1").arg(i + 1);
93  QString sfName = cloud->getScalarFieldName(i);
94 
95  QStringList tokens;
96  tokens << sfName;
97 
98  ccScalarField* sf =
99  static_cast<ccScalarField*>(cloud->getScalarField(i));
100 
101  // global shift
102  if (sf && sf->getGlobalShift() != 0.0) {
103  tokens << "s=" + QString::number(sf->getGlobalShift(), 'f',
104  12);
105  }
106 
107  // precision
108  QString precisionKey = QString("{%1}.precision").arg(sfName);
109  if (cloud->hasMetaData(precisionKey)) {
110  bool ok = false;
111  double precision =
112  cloud->getMetaData(precisionKey).toDouble(&ok);
113  if (ok) {
114  tokens << "p=" + QString::number(precision, 'f', 12);
115  }
116  }
117 
118  headerFile.setValue(key, tokens);
119  }
120  }
121 
122  headerFile.endGroup();
123  headerFile.sync();
124  }
125 
126  // we can now save the data file
127  QFile dataFile(dataFilename);
128  if (!dataFile.open(QFile::WriteOnly)) {
129  CVLog::Warning("[SBF] Failed to read the data file");
130  return CC_FERR_READING;
131  }
132 
133  QDataStream dataStream(&dataFile);
134 
135  // internal coordinate shift (to avoid losing precision)
136  // warning: may be different from the cloud 'Global Shift'
137  CCVector3d coordinatesShift = cloud->toGlobal3d(*cloud->getPoint(0));
138 
139  // header
140  {
141  size_t writtenBytes = 0;
142  dataStream.setFloatingPointPrecision(
143  QDataStream::DoublePrecision); // we write real 'double' values
144  // in the header
145 
146  // 2 bytes = header flag
147  { dataStream << s_headerFlagSBF; }
148  writtenBytes += 2;
149 
150  // 8 bytes = point count
151  {
152  quint64 pointCount = cloud->size();
153  dataStream << pointCount;
154  }
155  writtenBytes += 8;
156 
157  // 2 bytes = sf count
158  {
159  quint16 sfCount =
160  static_cast<uint16_t>(cloud->getNumberOfScalarFields());
161  dataStream << sfCount;
162  }
163  writtenBytes += 2;
164 
165  // 8 bytes = internal coordinates shift
166  {
167  dataStream << coordinatesShift.x;
168  dataStream << coordinatesShift.y;
169  dataStream << coordinatesShift.z;
170  }
171  writtenBytes += 24;
172 
173  // remaining bytes (empty for now)
174  for (; writtenBytes < c_headerSize; ++writtenBytes) {
175  quint8 byte = 0;
176  dataStream << byte;
177  }
178  }
179 
180  unsigned sfCount = cloud->getNumberOfScalarFields();
181  unsigned pointCount = cloud->size();
182 
183  QScopedPointer<ecvProgressDialog> pDlg(nullptr);
184  if (parameters.parentWidget) {
185  pDlg.reset(new ecvProgressDialog(true, parameters.parentWidget));
186  pDlg->setMethodTitle(QObject::tr("Simple BIN file"));
187  pDlg->setInfo(QObject::tr("Saving %1 points / %2 scalar field(s)")
188  .arg(pointCount)
189  .arg(sfCount));
190  pDlg->setModal(true);
191  pDlg->start();
192  }
193 
194  cloudViewer::NormalizedProgress nProgress(pDlg.data(), pointCount);
195 
196  // we can eventually save the data
197  dataStream.setFloatingPointPrecision(
198  QDataStream::SinglePrecision); // we wave only 'float' values in
199  // the data
200  for (unsigned i = 0; i < pointCount; ++i) {
201  // save the point coordinates
202  CCVector3d Pd = cloud->toGlobal3d(*cloud->getPoint(i));
203  CCVector3f coords = CCVector3f::fromArray((Pd - coordinatesShift).u);
204  dataStream << coords.x;
205  dataStream << coords.y;
206  dataStream << coords.z;
207 
208  // and now for the scalar values
209  for (unsigned j = 0; j < sfCount; ++j) {
210  ScalarType val = cloud->getScalarField(j)->getValue(i);
211  float fVal = static_cast<float>(val);
212  dataStream << fVal;
213  }
214 
215  if (!nProgress.oneStep()) {
216  dataFile.close();
218  }
219  }
220 
221  return CC_FERR_NO_ERROR;
222 }
223 
224 struct SFDescriptor {
225  QString name;
226  double precision = std::numeric_limits<double>::quiet_NaN();
227  double shift = 0.0;
228  ccScalarField* sf = nullptr;
229 };
230 
232  size_t pointCount;
234  double globalScale = 1.0;
235  std::vector<SFDescriptor> SFs;
236 };
237 
239  ccHObject& container,
240  LoadParameters& parameters) {
241  if (filename.isEmpty()) {
242  assert(false);
243  return CC_FERR_BAD_ARGUMENT;
244  }
245 
246  if (!QFileInfo::exists(filename)) {
247  return CC_FERR_READING;
248  }
249 
250  QString headerFilename;
251  QString dataFilename;
252  if (filename.endsWith(".sbf.data", Qt::CaseInsensitive)) {
253  // we trim the '.data' and read the '.sbf' file instead
254  headerFilename = filename.left(filename.size() - 5);
255  dataFilename = filename;
256  } else {
257  headerFilename = filename;
258  dataFilename = filename + ".data";
259  }
260 
261  CVLog::Print(QString("[SBF] Loading file '%1'...").arg(headerFilename));
262  GlobalDescriptor descriptor;
263 
264  // read the text file as an INI file
265  if (QFileInfo::exists(headerFilename)) {
266  QSettings headerFile(headerFilename, QSettings::IniFormat);
267 
268  if (!headerFile.childGroups().contains("SBF")) {
269  CVLog::Error("[SBF] Missing SBF section");
270  return CC_FERR_MALFORMED_FILE;
271  }
272  headerFile.beginGroup("SBF");
273 
274  if (headerFile.contains("Points")) {
275  // only indicative (we don't use it!)
276  bool ok = false;
277  descriptor.pointCount = headerFile.value("Points").toLongLong(&ok);
278  if (!ok) {
279  CVLog::Error("[SBF] Invalid number of points");
280  return CC_FERR_MALFORMED_FILE;
281  }
282  }
283 
284  // read the global shift (if any)
285  if (headerFile.contains("GlobalShift")) {
286  QStringList strGlobalShift =
287  headerFile.value("GlobalShift").toStringList();
288  if (strGlobalShift.size() != 3) {
289  CVLog::Error("[SBF] Invalid global shift");
290  return CC_FERR_MALFORMED_FILE;
291  } else {
292  bool ok[3] = {false, false, false};
293  descriptor.globalShift.x = strGlobalShift[0].toDouble(ok);
294  descriptor.globalShift.y = strGlobalShift[1].toDouble(ok + 1);
295  descriptor.globalShift.z = strGlobalShift[2].toDouble(ok + 2);
296  if (!ok[0] || !ok[1] || !ok[2]) {
297  CVLog::Error("[SBF] Invalid global shift");
298  return CC_FERR_MALFORMED_FILE;
299  }
300  }
301  }
302 
303  // read the global scale (if any)
304  if (headerFile.contains("GlobalScale")) {
305  bool ok = false;
306  descriptor.globalScale =
307  headerFile.value("GlobalScale").toDouble(&ok);
308  if (!ok || descriptor.globalScale <= 0.0) {
309  CVLog::Error("[SBF] Invalid global scale value");
310  return CC_FERR_MALFORMED_FILE;
311  }
312  }
313 
314  // read the scalar field names (if any)
315  if (headerFile.contains("SFCount")) {
316  bool ok = false;
317  int sfCount = headerFile.value("SFCount").toInt(&ok);
318  if (!ok || sfCount < 0) {
319  CVLog::Error("[SBF] Invalid SF count");
320  return CC_FERR_MALFORMED_FILE;
321  }
322 
323  try {
324  descriptor.SFs.resize(static_cast<size_t>(sfCount));
325  } catch (const std::bad_alloc&) {
326  // not enough memory
328  }
329 
330  // try to load the description of each SF
331  for (int i = 0; i < sfCount; ++i) {
332  QString key = QString("SF%1").arg(i + 1);
333  QStringList tokens = headerFile.value(key).toStringList();
334  if (!tokens.empty()) {
335  descriptor.SFs[i].name = tokens[0];
336  for (int k = 1; k < tokens.size(); ++k) {
337  QString token = tokens[k];
338  if (token.startsWith("s=")) {
339  token = token.mid(2);
340  double shift = token.toDouble(&ok);
341  if (!ok) {
342  CVLog::Error(QString("[SBF] Invalid %1 "
343  "description (shift)")
344  .arg(key));
345  return CC_FERR_MALFORMED_FILE;
346  }
347  descriptor.SFs[i].shift = shift;
348  } else {
349  if (token.startsWith("p=")) token = token.mid(2);
350  descriptor.SFs[i].precision = token.toDouble(&ok);
351  if (!ok) {
352  CVLog::Error(QString("[SBF] Invalid %1 "
353  "description (precision)")
354  .arg(key));
355  return CC_FERR_MALFORMED_FILE;
356  }
357  }
358  }
359  }
360  }
361 
362  headerFile.endGroup();
363  }
364  } else {
365  CVLog::Warning("[SBF] Missing header file");
366  }
367 
368  // we can now load the data file
369  QFile dataFile(dataFilename);
370  if (!dataFile.open(QFile::ReadOnly)) {
371  CVLog::Warning("[SBF] Failed to read the data file");
372  return CC_FERR_READING;
373  }
374 
375  // internal coordinate shift (to avoid losing precision)
376  // warning: may be different from the cloud 'Global Shift'
377  CCVector3d coordinatesShift(0, 0, 0);
378 
379  QDataStream dataStream(&dataFile);
380 
381  // header
382  {
383  size_t readBytes = 0;
384  dataStream.setFloatingPointPrecision(
385  QDataStream::DoublePrecision); // we expect real 'double'
386  // values in the header
387 
388  // 2 bytes = header flag
389  {
390  quint16 headerFlag = 0;
391  dataStream >> headerFlag;
392  if (headerFlag != s_headerFlagSBF) {
393  return CC_FERR_MALFORMED_FILE;
394  }
395  }
396  readBytes += 2;
397 
398  // 8 bytes = point count
399  {
400  quint64 pointCount = 0;
401  dataStream >> pointCount;
402  // check consistency
403  if (descriptor.pointCount != pointCount) {
405  "[SBF] Inconsistent number of points between the "
406  "header and the data file!");
407  }
408  // anyway, the data file count supersedes the header's one
409  descriptor.pointCount = static_cast<size_t>(pointCount);
410  }
411  readBytes += 8;
412 
413  // 2 bytes = sf count
414  {
415  quint16 sfCount = 0;
416  dataStream >> sfCount;
417  // check consistency
418  if (sfCount != descriptor.SFs.size()) {
420  "[SBF] Inconsistent number of scalar fields between "
421  "the header and the data file!");
422  }
423  // once again, the data file count supersedes the header's one
424  try {
425  descriptor.SFs.resize(sfCount);
426  } catch (const std::bad_alloc&) {
427  // not enough memory
429  }
430  }
431  readBytes += 2;
432 
433  // 8 bytes = internal coordinates shift
434  {
435  dataStream >> coordinatesShift.x;
436  dataStream >> coordinatesShift.y;
437  dataStream >> coordinatesShift.z;
438  }
439  readBytes += 24;
440 
441  // remaining bytes (empty for now)
442  for (; readBytes < c_headerSize; ++readBytes) {
443  quint8 byte = 0;
444  dataStream >> byte;
445  }
446  }
447 
448  // check data consistency
449  size_t sizePerPoint = (3 + descriptor.SFs.size()) *
450  4; // 3 * 4 bytes (float) for coordinates + 4 bytes
451  // (float) for scalars
452  size_t totalSize = sizePerPoint * descriptor.pointCount + c_headerSize;
453  if (totalSize != dataFile.size()) {
454  return CC_FERR_MALFORMED_FILE;
455  }
456 
457  // init structures
458  QScopedPointer<ccPointCloud> cloud(new ccPointCloud("unnamed"));
459  if (!cloud->reserve(static_cast<unsigned>(descriptor.pointCount))) {
461  }
462 
463  QScopedPointer<ecvProgressDialog> pDlg(nullptr);
464  if (parameters.parentWidget) {
465  pDlg.reset(new ecvProgressDialog(true, parameters.parentWidget));
466  pDlg->setMethodTitle(QObject::tr("Simple BIN file"));
467  pDlg->setInfo(QObject::tr("Loading %1 points / %2 scalar field(s)")
468  .arg(descriptor.pointCount)
469  .arg(descriptor.SFs.size()));
470  pDlg->setModal(true);
471  pDlg->start();
472  }
474  pDlg.data(), static_cast<unsigned>(descriptor.pointCount));
475 
476  // reserve memory
477  for (size_t i = 0; i < descriptor.SFs.size(); ++i) {
478  SFDescriptor& sfDesc = descriptor.SFs[i];
479  if (sfDesc.name.isEmpty()) {
480  sfDesc.name = QString("Scalar field #%1").arg(i + 1);
481  }
482  sfDesc.sf = new ccScalarField(qPrintable(sfDesc.name));
483  if (!sfDesc.sf->reserveSafe(
484  static_cast<unsigned>(descriptor.pointCount))) {
485  sfDesc.sf->release();
486  sfDesc.sf = nullptr;
488  }
489 
490  if (sfDesc.shift != 0) {
491  sfDesc.sf->setGlobalShift(sfDesc.shift);
492  }
493 
494  cloud->addScalarField(sfDesc.sf);
495 
496  // for now we save the 'precision' info as meta-data of the cloud
497  if (!std::isnan(sfDesc.precision)) {
498  cloud->setMetaData(QString("{%1}.precision").arg(sfDesc.name),
499  sfDesc.precision);
500  }
501  }
502 
503  // we can eventually load the data
504  dataStream.setFloatingPointPrecision(
505  QDataStream::SinglePrecision); // we expect only 'float' values in
506  // the data
507  for (size_t i = 0; i < descriptor.pointCount; ++i) {
508  // read the point coordinates
509  CCVector3f Pf;
510  dataStream >> Pf.x;
511  dataStream >> Pf.y;
512  dataStream >> Pf.z;
513 
514  CCVector3d Pd = coordinatesShift + CCVector3d::fromArray(Pf.u);
515 
516  if (i == 0) {
517  // backup input global parameters
518  ecvGlobalShiftManager::Mode csModeBackup =
519  parameters.shiftHandlingMode;
520  bool useGlobalShift = false;
521  CCVector3d Pshift(0, 0, 0);
522  if ((descriptor.globalShift.norm2() != 0 ||
523  descriptor.globalScale != 1.0) &&
524  (!parameters.coordinatesShiftEnabled ||
525  !*parameters.coordinatesShiftEnabled)) {
526  if (csModeBackup !=
527  ecvGlobalShiftManager::NO_DIALOG) // No dialog, practically
528  // means that we don't
529  // want any shift!
530  {
531  useGlobalShift = true;
532  Pshift = descriptor.globalShift;
533  if (csModeBackup !=
535  parameters.shiftHandlingMode =
537  }
538  }
539  }
540 
541  bool preserveCoordinateShift = true;
542  if (HandleGlobalShift(Pd, Pshift, preserveCoordinateShift,
543  parameters, true)) {
544  // set global shift
545  descriptor.globalShift = Pshift;
546  if (preserveCoordinateShift) {
547  cloud->setGlobalShift(descriptor.globalShift);
548  }
550  "[SBF] Cloud has been recentered! Translation: (%.2f ; "
551  "%.2f ; %.2f)",
552  descriptor.globalShift.x, descriptor.globalShift.y,
553  descriptor.globalShift.z);
554  }
555 
556  // restore previous parameters
557  parameters.shiftHandlingMode = csModeBackup;
558  }
559 
560  CCVector3 P = CCVector3::fromArray((Pd + descriptor.globalShift).u);
561  cloud->addPoint(P);
562 
563  // and now for the scalar values
564  for (SFDescriptor& sfDesc : descriptor.SFs) {
565  float val;
566  dataStream >> val;
567  sfDesc.sf->addElement(val);
568  }
569 
570  if (!nProgress.oneStep()) {
571  break;
572  }
573  }
574 
575  dataFile.close();
576 
577  if (cloud->size() == 0) {
579  } else if (cloud->size() < descriptor.pointCount) {
580  cloud->shrinkToFit();
581  }
582 
583  // update scalar fields
584  if (!descriptor.SFs.empty()) {
585  for (auto& SF : descriptor.SFs) {
586  SF.sf->computeMinAndMax();
587  }
588  cloud->setCurrentDisplayedScalarField(0);
589  cloud->showSF(true);
590  }
591 
592  container.addChild(cloud.take());
593 
594  return CC_FERR_NO_ERROR;
595 }
int64_t CV_CLASS_ENUM
Type of object type flags (64 bits)
Definition: CVTypes.h:97
std::string filename
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_MALFORMED_FILE
Definition: FileIOFilter.h:32
@ CC_FERR_BAD_ARGUMENT
Definition: FileIOFilter.h:22
@ CC_FERR_NO_ERROR
Definition: FileIOFilter.h:21
@ CC_FERR_READING
Definition: FileIOFilter.h:26
@ CC_FERR_NOT_ENOUGH_MEMORY
Definition: FileIOFilter.h:31
static const size_t c_headerSize
static const quint16 s_headerFlagSBF
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
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 loadFile(const QString &filename, ccHObject &container, LoadParameters &parameters) override
Loads one or more entities from a file.
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.
virtual CC_FILE_ERROR saveToFile(ccHObject *entity, const QString &filename, const SaveParameters &parameters) override
Saves an entity (or a group of) to a file.
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
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
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.
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
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.)
bool hasScalarFields() const override
Returns whether one or more scalar fields are instantiated.
A scalar field associated to display-related parameters.
void setGlobalShift(double shift)
Sets the global shift.
double getGlobalShift() const
Returns the global shift (if any)
CCVector3d toGlobal3d(const Vector3Tpl< T > &Plocal) const
Returns the point back-projected into the original coordinates system.
virtual const CCVector3d & getGlobalShift() const
Returns the shift applied to original coordinates.
virtual double getGlobalScale() const
Returns the scale applied to original coordinates.
bool oneStep()
Increments total progress value of a single unit.
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.
unsigned size() const override
Definition: PointCloudTpl.h:38
const char * getScalarFieldName(int index) const
Returns the name of a specific scalar field.
const CCVector3 * getPoint(unsigned index) const override
void addElement(ScalarType value)
Definition: ScalarField.h:99
ScalarType & getValue(std::size_t index)
Definition: ScalarField.h:92
bool reserveSafe(std::size_t count)
Reserves memory (no exception thrown)
Definition: ScalarField.cpp:71
Mode
Strategy to handle coordinates shift/scale.
Graphical progress indicator (thread-safe)
@ POINT_CLOUD
Definition: CVTypes.h:104
constexpr QRegularExpression::PatternOption CaseInsensitive
Definition: QtCompat.h:174
cloudViewer::NormalizedProgress * nProgress
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
std::vector< SFDescriptor > SFs
ccScalarField * sf