00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036 #include "IO.h"
00037 #include "IOFunctions.h"
00038 #include "InspectorsImpl.h"
00039
00040
00041 #include "PointMatcherPrivate.h"
00042
00043 #include <iostream>
00044 #include <fstream>
00045 #include <stdexcept>
00046 #include <ctype.h>
00047 #include "boost/algorithm/string.hpp"
00048 #include "boost/filesystem.hpp"
00049 #include "boost/filesystem/path.hpp"
00050 #include "boost/filesystem/operations.hpp"
00051 #include "boost/lexical_cast.hpp"
00052 #include "boost/foreach.hpp"
00053
00054 #ifdef WIN32
00055 #define strtok_r strtok_s
00056 #endif // WIN32
00057
00058 namespace PointMatcherSupport {
00059 namespace {
00060 const int one = 1;
00061 }
00062 const bool isBigEndian = *reinterpret_cast<const unsigned char*>(&one) == static_cast<unsigned char>(0);
00063 const int oneBigEndian = isBigEndian ? 1 : 1 << 8 * (sizeof(int) - 1);
00064 }
00065
00066 using namespace std;
00067 using namespace PointMatcherSupport;
00068
00069
00070
00071 static std::vector<string> csvLineToVector(const char* line)
00072 {
00073 std::vector<string> parsedLine;
00074 char delimiters[] = " \t,;";
00075 char *token;
00076 char tmpLine[1024];
00077 char *brkt = 0;
00078 strcpy(tmpLine, line);
00079 token = strtok_r(tmpLine, delimiters, &brkt);
00080 if(line[0] != '%')
00081 {
00082 while (token)
00083 {
00084 parsedLine.push_back(string(token));
00085 token = strtok_r(NULL, delimiters, &brkt);
00086 }
00087 }
00088
00089 return parsedLine;
00090 }
00091
00092
00093 CsvElements parseCsvWithHeader(const std::string& fileName)
00094 {
00095 validateFile(fileName);
00096
00097 ifstream is(fileName.c_str());
00098
00099 unsigned elementCount=0;
00100 std::map<string, unsigned> keywordCols;
00101 CsvElements data;
00102
00103 bool firstLine(true);
00104 unsigned lineCount=0;
00105
00106 string line;
00107 while (safeGetLine(is, line))
00108 {
00109
00110 if(firstLine)
00111 {
00112 std::vector<string> header = csvLineToVector(line.c_str());
00113
00114 elementCount = header.size();
00115 for(unsigned int i = 0; i < elementCount; i++)
00116 {
00117 keywordCols[header[i]] = i;
00118 }
00119
00120 firstLine = false;
00121 }
00122 else
00123 {
00124 std::vector<string> parsedLine = csvLineToVector(line.c_str());
00125 if(parsedLine.size() != elementCount && parsedLine.size() !=0)
00126 {
00127 stringstream errorMsg;
00128 errorMsg << "Error at line " << lineCount+1 << ": expecting " << elementCount << " columns but read " << parsedLine.size() << " elements.";
00129 throw runtime_error(errorMsg.str());
00130 }
00131
00132 for(unsigned int i = 0; i < parsedLine.size(); i++)
00133 {
00134 for(BOOST_AUTO(it,keywordCols.begin()); it!=keywordCols.end(); it++)
00135 {
00136 if(i == (*it).second)
00137 {
00138 data[(*it).first].push_back(parsedLine[i]);
00139 }
00140 }
00141 }
00142 }
00143
00144 lineCount++;
00145 }
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160 return data;
00161 }
00162
00163
00165 template<typename T>
00166 PointMatcherIO<T>::FileInfo::FileInfo(const std::string& readingFileName, const std::string& referenceFileName, const std::string& configFileName, const TransformationParameters& initialTransformation, const TransformationParameters& groundTruthTransformation, const Vector& gravity):
00167 readingFileName(readingFileName),
00168 referenceFileName(referenceFileName),
00169 configFileName(configFileName),
00170 initialTransformation(initialTransformation),
00171 groundTruthTransformation(groundTruthTransformation),
00172 gravity(gravity)
00173 {}
00174
00175 template struct PointMatcherIO<float>::FileInfo;
00176 template struct PointMatcherIO<double>::FileInfo;
00177
00178
00179 template<typename T>
00180 PointMatcherIO<T>::FileInfoVector::FileInfoVector()
00181 {
00182 }
00183
00185
00198 template<typename T>
00199 PointMatcherIO<T>::FileInfoVector::FileInfoVector(const std::string& fileName, std::string dataPath, std::string configPath)
00200 {
00201 if (dataPath.empty())
00202 {
00203 #if BOOST_FILESYSTEM_VERSION >= 3
00204 dataPath = boost::filesystem::path(fileName).parent_path().string();
00205 #else
00206 dataPath = boost::filesystem::path(fileName).parent_path().file_string();
00207 #endif
00208 }
00209 if (configPath.empty())
00210 {
00211 #if BOOST_FILESYSTEM_VERSION >= 3
00212 configPath = boost::filesystem::path(fileName).parent_path().string();
00213 #else
00214 configPath = boost::filesystem::path(fileName).parent_path().file_string();
00215 #endif
00216 }
00217
00218 const CsvElements data = parseCsvWithHeader(fileName);
00219
00220
00221 const bool found3dInitialTrans(findTransform(data, "iT", 3));
00222 bool found2dInitialTrans(findTransform(data, "iT", 2));
00223 const bool found3dGroundTruthTrans(findTransform(data, "gT", 3));
00224 bool found2dGroundTruthTrans(findTransform(data, "gT", 2));
00225 if (found3dInitialTrans)
00226 found2dInitialTrans = false;
00227 if (found3dGroundTruthTrans)
00228 found2dGroundTruthTrans = false;
00229
00230
00231 if (found3dInitialTrans && found2dGroundTruthTrans)
00232 throw runtime_error("Initial transformation is in 3D but ground-truth is in 2D");
00233 if (found2dInitialTrans && found3dGroundTruthTrans)
00234 throw runtime_error("Initial transformation is in 2D but ground-truth is in 3D");
00235 CsvElements::const_iterator readingIt(data.find("reading"));
00236 if (readingIt == data.end())
00237 throw runtime_error("Error transfering CSV to structure: The header should at least contain \"reading\".");
00238 CsvElements::const_iterator referenceIt(data.find("reference"));
00239 CsvElements::const_iterator configIt(data.find("config"));
00240
00241
00242 const std::vector<string>& readingFileNames = readingIt->second;
00243 const unsigned lineCount = readingFileNames.size();
00244 boost::optional<std::vector<string> > referenceFileNames;
00245 boost::optional<std::vector<string> > configFileNames;
00246 if (referenceIt != data.end())
00247 {
00248 referenceFileNames = referenceIt->second;
00249 assert (referenceFileNames->size() == lineCount);
00250 }
00251 if (configIt != data.end())
00252 {
00253 configFileNames = configIt->second;
00254 assert (configFileNames->size() == lineCount);
00255 }
00256
00257
00258 for(unsigned line=0; line<lineCount; line++)
00259 {
00260 FileInfo info;
00261
00262
00263 info.readingFileName = localToGlobalFileName(dataPath, readingFileNames[line]);
00264 if (referenceFileNames)
00265 info.referenceFileName = localToGlobalFileName(dataPath, (*referenceFileNames)[line]);
00266 if (configFileNames)
00267 info.configFileName = localToGlobalFileName(configPath, (*configFileNames)[line]);
00268
00269
00270 if(found3dInitialTrans)
00271 info.initialTransformation = getTransform(data, "iT", 3, line);
00272 if(found2dInitialTrans)
00273 info.initialTransformation = getTransform(data, "iT", 2, line);
00274 if(found3dGroundTruthTrans)
00275 info.groundTruthTransformation = getTransform(data, "gT", 3, line);
00276 if(found2dGroundTruthTrans)
00277 info.groundTruthTransformation = getTransform(data, "gT", 2, line);
00278
00279
00280 this->push_back(info);
00281 }
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295 }
00296
00298 template<typename T>
00299 std::string PointMatcherIO<T>::FileInfoVector::localToGlobalFileName(const std::string& parentPath, const std::string& fileName)
00300 {
00301 std::string globalFileName(fileName);
00302 if (!boost::filesystem::exists(globalFileName))
00303 {
00304 const boost::filesystem::path globalFilePath(boost::filesystem::path(parentPath) / boost::filesystem::path(fileName));
00305 #if BOOST_FILESYSTEM_VERSION >= 3
00306 globalFileName = globalFilePath.string();
00307 #else
00308 globalFileName = globalFilePath.file_string();
00309 #endif
00310 }
00311 validateFile(globalFileName);
00312 return globalFileName;
00313 }
00314
00316 template<typename T>
00317 bool PointMatcherIO<T>::FileInfoVector::findTransform(const CsvElements& data, const std::string& prefix, unsigned dim)
00318 {
00319 bool found(true);
00320 for(unsigned i=0; i<dim+1; i++)
00321 {
00322 for(unsigned j=0; j<dim+1; j++)
00323 {
00324 stringstream transName;
00325 transName << prefix << i << j;
00326 found = found && (data.find(transName.str()) != data.end());
00327 }
00328 }
00329 return found;
00330 }
00331
00333 template<typename T>
00334 typename PointMatcherIO<T>::TransformationParameters PointMatcherIO<T>::FileInfoVector::getTransform(const CsvElements& data, const std::string& prefix, unsigned dim, unsigned line)
00335 {
00336 TransformationParameters transformation(TransformationParameters::Identity(dim+1, dim+1));
00337 for(unsigned i=0; i<dim+1; i++)
00338 {
00339 for(unsigned j=0; j<dim+1; j++)
00340 {
00341 stringstream transName;
00342 transName << prefix << i << j;
00343 CsvElements::const_iterator colIt(data.find(transName.str()));
00344 const T value = boost::lexical_cast<T> (colIt->second[line]);
00345 transformation(i,j) = value;
00346 }
00347 }
00348 return transformation;
00349 }
00350
00351 template struct PointMatcherIO<float>::FileInfoVector;
00352 template struct PointMatcherIO<double>::FileInfoVector;
00353
00355 void PointMatcherSupport::validateFile(const std::string& fileName)
00356 {
00357 boost::filesystem::path fullPath(fileName);
00358
00359 ifstream ifs(fileName.c_str());
00360 if (!ifs.good() || !boost::filesystem::is_regular_file(fullPath))
00361 #if BOOST_FILESYSTEM_VERSION >= 3
00362 #if BOOST_VERSION >= 105000
00363 throw runtime_error(string("Cannot open file ") + boost::filesystem::complete(fullPath).generic_string());
00364 #else
00365 throw runtime_error(string("Cannot open file ") + boost::filesystem3::complete(fullPath).generic_string());
00366 #endif
00367 #else
00368 throw runtime_error(string("Cannot open file ") + boost::filesystem::complete(fullPath).native_file_string());
00369 #endif
00370 }
00371
00372
00374 template<typename T>
00375 typename PointMatcher<T>::DataPoints PointMatcher<T>::DataPoints::load(const std::string& fileName)
00376 {
00377 const boost::filesystem::path path(fileName);
00378 const string& ext(boost::filesystem::extension(path));
00379 if (boost::iequals(ext, ".vtk"))
00380 return PointMatcherIO<T>::loadVTK(fileName);
00381 else if (boost::iequals(ext, ".csv"))
00382 return PointMatcherIO<T>::loadCSV(fileName);
00383 else if (boost::iequals(ext, ".ply"))
00384 return PointMatcherIO<T>::loadPLY(fileName);
00385 else if (boost::iequals(ext, ".pcd"))
00386 return PointMatcherIO<T>::loadPCD(fileName);
00387 else
00388 throw runtime_error("loadAnyFormat(): Unknown extension \"" + ext + "\" for file \"" + fileName + "\", extension must be either \".vtk\" or \".csv\"");
00389 }
00390
00391 template
00392 PointMatcher<float>::DataPoints PointMatcher<float>::DataPoints::load(const std::string& fileName);
00393 template
00394 PointMatcher<double>::DataPoints PointMatcher<double>::DataPoints::load(const std::string& fileName);
00395
00396
00407 template<typename T>
00408 typename PointMatcher<T>::DataPoints PointMatcherIO<T>::loadCSV(const std::string& fileName)
00409 {
00410 ifstream ifs(fileName.c_str());
00411
00412 validateFile(fileName);
00413
00414 return loadCSV(ifs);
00415 }
00416
00417 template<typename T>
00418 PointMatcherIO<T>::SupportedLabel::SupportedLabel(const std::string& internalName, const std::string& externalName, const PMPropTypes& type):
00419 internalName(internalName),
00420 externalName(externalName),
00421 type(type)
00422 {
00423 }
00424
00425
00426
00427 template<typename T>
00428 void PointMatcherIO<T>::LabelGenerator::add(const std::string internalName)
00429 {
00430 bool findLabel = false;
00431 for(size_t i=0; i<labels.size(); ++i)
00432 {
00433 if(internalName == labels[i].text)
00434 {
00435 labels[i].span++;
00436 findLabel = true;
00437 break;
00438 }
00439
00440 }
00441
00442 if(!findLabel)
00443 {
00444 labels.push_back(Label(internalName,1));
00445 }
00446 }
00447
00448 template<typename T>
00449 void PointMatcherIO<T>::LabelGenerator::add(const std::string internalName, const unsigned int dim)
00450 {
00451 labels.push_back(Label(internalName, dim));
00452 }
00453
00454
00455
00456 template<typename T>
00457 typename PointMatcher<T>::DataPoints::Labels PointMatcherIO<T>::LabelGenerator::getLabels() const
00458 {
00459 return labels;
00460 }
00461
00462 template
00463 class PointMatcherIO<float>::LabelGenerator;
00464 template
00465 class PointMatcherIO<double>::LabelGenerator;
00466 template <typename T>
00467
00468
00469 std::string PointMatcherIO<T>::getColLabel(const Label& label, const int row)
00470 {
00471 std::string externalName;
00472 if (label.text == "normals")
00473 {
00474 if (row == 0)
00475 {
00476 externalName = "nx";
00477 }
00478 if (row == 1)
00479 {
00480 externalName = "ny";
00481 }
00482 if (row == 2)
00483 {
00484 externalName = "nz";
00485 }
00486 }
00487 else if (label.text == "color")
00488 {
00489 if (row == 0)
00490 {
00491 externalName = "red";
00492 }
00493 if (row == 1)
00494 {
00495 externalName = "green";
00496 }
00497 if (row == 2)
00498 {
00499 externalName = "blue";
00500 }
00501 if (row == 3)
00502 externalName = "alpha";
00503 }
00504 else if (label.text == "eigValues")
00505 {
00506 externalName = "eigValues" + boost::lexical_cast<string>(row);
00507 }
00508 else if (label.text == "eigVectors")
00509 {
00510
00511 externalName = "eigVectors" + boost::lexical_cast<string>(row/3);
00512
00513 int row_mod = row % 3;
00514 if (row_mod == 0)
00515 externalName += "X";
00516 else if (row_mod == 1)
00517 externalName += "Y";
00518 else if (row_mod == 2)
00519 externalName += "Z";
00520 }
00521 else if (label.span == 1)
00522 {
00523 externalName = label.text;
00524 }
00525 else
00526 externalName = label.text + boost::lexical_cast<std::string>(row);
00527
00528 return externalName;
00529 }
00530
00531
00534 template<typename T>
00535 typename PointMatcher<T>::DataPoints PointMatcherIO<T>::loadCSV(std::istream& is)
00536 {
00537 vector<GenericInputHeader> csvHeader;
00538 LabelGenerator featLabelGen, descLabelGen, timeLabelGen;
00539 Matrix features;
00540 Matrix descriptors;
00541 Int64Matrix times;
00542 unsigned int csvCol = 0;
00543 unsigned int csvRow = 0;
00544 bool hasHeader(false);
00545 bool firstLine(true);
00546
00547
00548
00549 is.unsetf(std::ios_base::skipws);
00550 unsigned int line_count = std::count(
00551 std::istream_iterator<char>(is),
00552 std::istream_iterator<char>(),
00553 '\n');
00554
00555 is.clear();
00556 is.seekg(0, ios::beg);
00557
00558 char delimiters[] = " \t,;";
00559 char *token;
00560 string line;
00561 while (safeGetLine(is, line))
00562 {
00563
00564
00565 if(line.empty())
00566 break;
00567
00568
00569 unsigned int len = strspn(line.c_str(), " ,+-.1234567890Ee");
00570 if(len != line.length())
00571 {
00572
00573 hasHeader = true;
00574 }
00575 else
00576 {
00577 hasHeader = false;
00578 }
00579
00580
00581 if(firstLine)
00582 {
00583 unsigned int dim = 0;
00584 char tmpLine[1024];
00585 strcpy(tmpLine, line.c_str());
00586 char *brkt = 0;
00587 token = strtok_r(tmpLine, delimiters, &brkt);
00588
00589
00590 while (token)
00591 {
00592
00593 if(hasHeader)
00594 {
00595 csvHeader.push_back(GenericInputHeader(string(token)));
00596 }
00597 dim++;
00598 token = strtok_r(NULL, delimiters, &brkt);
00599 }
00600
00601 if (!hasHeader)
00602 {
00603
00604 if (!(dim == 2 || dim == 3))
00605 {
00606 int idX=0, idY=0, idZ=0;
00607
00608 cout << "WARNING: " << dim << " columns detected. Not obvious which columns to load for x, y or z." << endl;
00609 cout << endl << "Enter column ID (starting from 0) for x: ";
00610 cin >> idX;
00611 cout << "Enter column ID (starting from 0) for y: ";
00612 cin >> idY;
00613 cout << "Enter column ID (starting from 0, -1 if 2D data) for z: ";
00614 cin >> idZ;
00615
00616
00617 for(unsigned int i=0; i<dim; i++)
00618 {
00619 std::ostringstream os;
00620 os << "empty" << i;
00621
00622 csvHeader.push_back(GenericInputHeader(os.str()));
00623 }
00624
00625
00626 csvHeader[idX] = GenericInputHeader("x");
00627 csvHeader[idY] = GenericInputHeader("y");
00628 if(idZ != -1)
00629 csvHeader[idZ] = GenericInputHeader("z");
00630 }
00631 else
00632 {
00633
00634 csvHeader.push_back(GenericInputHeader("x"));
00635 csvHeader.push_back(GenericInputHeader("y"));
00636 if(dim == 3)
00637 csvHeader.push_back(GenericInputHeader("z"));
00638 }
00639 }
00640
00641
00642
00643 const SupportedLabels externalLabels = getSupportedExternalLabels();
00644
00645
00646 int rowIdFeatures = 0;
00647 int rowIdDescriptors = 0;
00648 int rowIdTime = 0;
00649
00650
00651
00652 for(size_t i=0; i<externalLabels.size(); i++)
00653 {
00654 const SupportedLabel supLabel = externalLabels[i];
00655
00656 for(size_t j=0; j < csvHeader.size(); j++)
00657 {
00658 if(supLabel.externalName == csvHeader[j].name)
00659 {
00660 csvHeader[j].matrixType = supLabel.type;
00661
00662 switch (supLabel.type)
00663 {
00664 case FEATURE:
00665 csvHeader[j].matrixRowId = rowIdFeatures;
00666 featLabelGen.add(supLabel.internalName);
00667 rowIdFeatures++;
00668 break;
00669 case DESCRIPTOR:
00670 csvHeader[j].matrixRowId = rowIdDescriptors;
00671 descLabelGen.add(supLabel.internalName);
00672 rowIdDescriptors++;
00673 break;
00674 case TIME:
00675 csvHeader[j].matrixRowId = rowIdTime;
00676 timeLabelGen.add(supLabel.internalName);
00677 rowIdTime++;
00678 break;
00679 default:
00680 throw runtime_error(string("CSV parse error: encounter a type different from FEATURE, DESCRIPTOR and TIME. Implementation not supported. See the definition of 'enum PMPropTypes'"));
00681 break;
00682 }
00683
00684
00685 break;
00686 }
00687 }
00688 }
00689
00690
00691 for(unsigned int i=0; i<csvHeader.size(); i++)
00692 {
00693 if(csvHeader[i].matrixType == UNSUPPORTED)
00694 {
00695 csvHeader[i].matrixType = DESCRIPTOR;
00696 csvHeader[i].matrixRowId = rowIdDescriptors;
00697 descLabelGen.add(csvHeader[i].name);
00698 rowIdDescriptors++;
00699 }
00700 }
00701
00702
00703
00704 if(hasHeader && line_count > 0)
00705 line_count--;
00706
00707 const unsigned int featDim = featLabelGen.getLabels().totalDim();
00708 const unsigned int descDim = descLabelGen.getLabels().totalDim();
00709 const unsigned int timeDim = timeLabelGen.getLabels().totalDim();
00710 const unsigned int nbPoints = line_count;
00711
00712 features = Matrix(featDim, nbPoints);
00713 descriptors = Matrix(descDim, nbPoints);
00714 times = Int64Matrix(timeDim, nbPoints);
00715 }
00716
00717
00718
00719 char* brkt = 0;
00720 char line_c[1024];
00721 strcpy(line_c,line.c_str());
00722 token = strtok_r(line_c, delimiters, &brkt);
00723
00724 if(!(hasHeader && firstLine))
00725 {
00726
00727 csvCol = 0;
00728 while (token)
00729 {
00730 if(csvCol > (csvHeader.size() - 1))
00731 {
00732
00733 throw runtime_error(
00734 (boost::format("CSV parse error: at line %1%, too many elements to parse compare to the header number of columns (col=%2%).") % csvRow % csvHeader.size()).str());
00735 }
00736
00737
00738 const int matrixRow = csvHeader[csvCol].matrixRowId;
00739 const int matrixCol = csvRow;
00740
00741 switch (csvHeader[csvCol].matrixType)
00742 {
00743 case FEATURE:
00744 features(matrixRow, matrixCol) = lexical_cast_scalar_to_string<T>(string(token));
00745 break;
00746 case DESCRIPTOR:
00747 descriptors(matrixRow, matrixCol) = lexical_cast_scalar_to_string<T>(token);
00748 break;
00749 case TIME:
00750 times(matrixRow, matrixCol) = lexical_cast_scalar_to_string<std::int64_t>(token);
00751 break;
00752 default:
00753 throw runtime_error(string("CSV parse error: encounter a type different from FEATURE, DESCRIPTOR and TIME. Implementation not supported. See the definition of 'enum PMPropTypes'"));
00754 break;
00755
00756 }
00757
00758
00759 token = strtok_r(NULL, delimiters, &brkt);
00760 csvCol++;
00761 }
00762
00763
00764
00765 if(csvCol != (csvHeader.size()))
00766 {
00767 throw runtime_error(
00768 (boost::format("CSV parse error: at line %1%, not enough elements to parse compare to the header number of columns (col=%2%).") % csvRow % csvHeader.size()).str());
00769 }
00770
00771 csvRow++;
00772 }
00773
00774 firstLine = false;
00775
00776 }
00777
00778
00779 DataPoints loadedPoints(features, featLabelGen.getLabels());
00780
00781 if (descriptors.rows() > 0)
00782 {
00783 loadedPoints.descriptors = descriptors;
00784 loadedPoints.descriptorLabels = descLabelGen.getLabels();
00785 }
00786
00787 if(times.rows() > 0)
00788 {
00789 loadedPoints.times = times;
00790 loadedPoints.timeLabels = timeLabelGen.getLabels();
00791 }
00792
00793
00794 if(!loadedPoints.featureExists("pad"))
00795 {
00796 loadedPoints.addFeature("pad", Matrix::Ones(1,features.cols()));
00797 }
00798
00799 return loadedPoints;
00800 }
00801
00802 template
00803 PointMatcher<float>::DataPoints PointMatcherIO<float>::loadCSV(const std::string& fileName);
00804 template
00805 PointMatcher<double>::DataPoints PointMatcherIO<double>::loadCSV(const std::string& fileName);
00806
00808 template<typename T>
00809 void PointMatcher<T>::DataPoints::save(const std::string& fileName, bool binary) const
00810 {
00811 const boost::filesystem::path path(fileName);
00812 const string& ext(boost::filesystem::extension(path));
00813 if (boost::iequals(ext, ".vtk"))
00814 return PointMatcherIO<T>::saveVTK(*this, fileName, binary);
00815
00816 if (binary)
00817 throw runtime_error("save(): Binary writing is not supported together with extension \"" + ext + "\". Currently binary writing is only supported with \".vtk\".");
00818
00819 if (boost::iequals(ext, ".csv"))
00820 return PointMatcherIO<T>::saveCSV(*this, fileName);
00821 else if (boost::iequals(ext, ".ply"))
00822 return PointMatcherIO<T>::savePLY(*this, fileName);
00823 else if (boost::iequals(ext, ".pcd"))
00824 return PointMatcherIO<T>::savePCD(*this, fileName);
00825 else
00826 throw runtime_error("save(): Unknown extension \"" + ext + "\" for file \"" + fileName + "\", extension must be either \".vtk\", \".ply\", \".pcd\" or \".csv\"");
00827 }
00828
00829 template
00830 void PointMatcher<float>::DataPoints::save(const std::string& fileName, bool binary) const;
00831 template
00832 void PointMatcher<double>::DataPoints::save(const std::string& fileName, bool binary) const;
00833
00835 template<typename T>
00836 void PointMatcherIO<T>::saveCSV(const DataPoints& data, const std::string& fileName)
00837 {
00838 ofstream ofs(fileName.c_str());
00839 if (!ofs.good())
00840 throw runtime_error(string("Cannot open file ") + fileName);
00841 saveCSV(data, ofs);
00842 }
00843
00845 template<typename T>
00846 void PointMatcherIO<T>::saveCSV(const DataPoints& data, std::ostream& os)
00847 {
00848 const int pointCount(data.features.cols());
00849 const int dimCount(data.features.rows());
00850 const int descDimCount(data.descriptors.rows());
00851
00852 if (pointCount == 0)
00853 {
00854 LOG_WARNING_STREAM( "Warning, no points, doing nothing");
00855 return;
00856 }
00857
00858
00859 for (int i = 0; i < dimCount - 1; i++)
00860 {
00861 os << data.featureLabels[i].text;
00862
00863 if (!((i == (dimCount - 2)) && descDimCount == 0))
00864 os << ",";
00865 }
00866
00867 int n = 0;
00868 for (size_t i = 0; i < data.descriptorLabels.size(); i++)
00869 {
00870 Label lab = data.descriptorLabels[i];
00871 for (size_t s = 0; s < lab.span; s++)
00872 {
00873 os << getColLabel(lab,s);
00874 if (n != (descDimCount - 1))
00875 os << ",";
00876 n++;
00877 }
00878 }
00879
00880 os << "\n";
00881
00882
00883 for (int p = 0; p < pointCount; ++p)
00884 {
00885 for (int i = 0; i < dimCount-1; ++i)
00886 {
00887 os << data.features(i, p);
00888 if(!((i == (dimCount - 2)) && descDimCount == 0))
00889 os << " , ";
00890 }
00891
00892 for (int i = 0; i < descDimCount; i++)
00893 {
00894 os << data.descriptors(i,p);
00895 if (i != (descDimCount - 1))
00896 os << ",";
00897 }
00898 os << "\n";
00899 }
00900
00901 }
00902
00903 template
00904 void PointMatcherIO<float>::saveCSV(const DataPoints& data, const std::string& fileName);
00905 template
00906 void PointMatcherIO<double>::saveCSV(const DataPoints& data, const std::string& fileName);
00907
00909 template<typename T>
00910 typename PointMatcher<T>::DataPoints PointMatcherIO<T>::loadVTK(const std::string& fileName)
00911 {
00912 ifstream ifs(fileName.c_str(), std::ios::binary);
00913 if (!ifs.good())
00914 throw runtime_error(string("Cannot open file ") + fileName);
00915 return loadVTK(ifs);
00916 }
00917
00918 void skipBlock(bool binary, int binarySize, std::istream & is, bool hasSeparateSizeParameter = true){
00919 int n;
00920 int size;
00921 is >> n;
00922 if(!is.good()){
00923 throw std::runtime_error("File violates the VTK format : parameter 'n' is missing after a field name.");
00924 }
00925
00926 if(hasSeparateSizeParameter) {
00927 is >> size;
00928 if(!is.good()){
00929 throw std::runtime_error("File violates the VTK format : parameter 'size' is missing after a field name.");
00930 }
00931 } else {
00932 size = n;
00933 }
00934
00935 std::string line;
00936 safeGetLine(is, line);
00937 if(binary){
00938 is.seekg(size * binarySize, std::ios_base::cur);
00939 } else {
00940 for (int p = 0; p < n; p++)
00941 {
00942 safeGetLine(is, line);
00943 }
00944 }
00945 }
00946
00948 template<typename T>
00949 typename PointMatcher<T>::DataPoints PointMatcherIO<T>::loadVTK(std::istream& is)
00950 {
00951 std::map<std::string, SplitTime> labelledSplitTime;
00952
00953 DataPoints loadedPoints;
00954
00955
00956 string line;
00957 safeGetLine(is, line);
00958 if (line.find("# vtk DataFile Version") != 0)
00959 throw runtime_error(string("Wrong magic header, found ") + line);
00960 safeGetLine(is, line);
00961 safeGetLine(is, line);
00962
00963 const bool isBinary = (line == "BINARY");
00964 if (line != "ASCII"){
00965 if(!isBinary){
00966 throw runtime_error(string("Wrong file type, expecting ASCII or BINARY, found ") + line);
00967 }
00968 }
00969 safeGetLine(is, line);
00970
00971 SupportedVTKDataTypes dataType;
00972 if (line == "DATASET POLYDATA")
00973 dataType = POLYDATA;
00974 else if (line == "DATASET UNSTRUCTURED_GRID")
00975 dataType = UNSTRUCTURED_GRID;
00976 else
00977 throw runtime_error(string("Wrong data type, expecting DATASET POLYDATA, found ") + line);
00978
00979
00980
00981 string fieldName;
00982 string name;
00983 int dim = 0;
00984 int pointCount = 0;
00985 string type;
00986
00987 while (is >> fieldName)
00988 {
00989
00990 if(fieldName == "POINTS")
00991 {
00992 is >> pointCount;
00993 is >> type;
00994 safeGetLine(is, line);
00995
00996 if(!(type == "float" || type == "double"))
00997 throw runtime_error(string("Field POINTS can only be of type double or float"));
00998
00999 Matrix features(4, pointCount);
01000 for (int p = 0; p < pointCount; ++p)
01001 {
01002 readVtkData(type, isBinary, features.template block<3, 1>(0, p), is);
01003 features(3, p) = 1.0;
01004 }
01005 loadedPoints.addFeature("x", features.row(0));
01006 loadedPoints.addFeature("y", features.row(1));
01007 loadedPoints.addFeature("z", features.row(2));
01008 loadedPoints.addFeature("pad", features.row(3));
01009 }
01010
01012
01013
01014 else if(dataType == POLYDATA && fieldName == "VERTICES")
01015 {
01016 skipBlock(isBinary, 4, is);
01017 }
01018
01019 else if(dataType == POLYDATA && fieldName == "LINES")
01020 {
01021 skipBlock(isBinary, 4, is);
01022 }
01023
01024 else if(dataType == POLYDATA && fieldName == "POLYGONS")
01025 {
01026 skipBlock(isBinary, 4, is);
01027 }
01028
01029 else if(dataType == POLYDATA && fieldName == "TRIANGLE_STRIPS")
01030 {
01031 skipBlock(isBinary, 4, is);
01032 }
01033
01034
01035 else if(dataType == UNSTRUCTURED_GRID && fieldName == "CELLS")
01036 {
01037 skipBlock(isBinary, 4, is);
01038 }
01039 else if(dataType == UNSTRUCTURED_GRID && fieldName == "CELL_TYPES")
01040 {
01041 skipBlock(isBinary, 4, is, false);
01042 }
01043
01045
01046 else if(fieldName == "POINT_DATA")
01047 {
01048 int descriptorCount;
01049 is >> descriptorCount;
01050 if(pointCount != descriptorCount)
01051 throw runtime_error(string("The size of POINTS is different than POINT_DATA"));
01052 }
01054
01055 else if (fieldName == "FIELD")
01056 {
01057 string fieldDataName;
01058 int fieldDataCount;
01059 is >> fieldDataName >> fieldDataCount;
01060
01061 for (int f = 0; f < fieldDataCount; f++)
01062 {
01063 int numTuples;
01064 is >> name >> dim >> numTuples >> type;
01065
01066 if(type == "vtkIdType")
01067 {
01068 if(isBinary){
01069 is.seekg(dim * numTuples * 4, std::ios_base::cur);
01070 } else {
01071 int t_val;
01072 for (int t = 0; t < dim * numTuples; t++ )
01073 {
01074 is >> t_val;
01075 }
01076 }
01077 }
01078 else if(!(type == "float" || type == "double"))
01079 throw runtime_error(string("Field " + fieldName + " is " + type + " but can only be of type double or float"));
01080
01081
01082 Matrix descriptor(dim, pointCount);
01083 readVtkData(type, isBinary, descriptor.transpose(), is);
01084 loadedPoints.addDescriptor(name, descriptor);
01085 }
01086 }
01087 else if(fieldName == "METADATA")
01088 {
01089 safeGetLine(is, line);
01090 safeGetLine(is, line);
01091 while(!line.empty())
01092 {
01093 safeGetLine(is, line);
01094 }
01095 }
01096 else
01097 {
01098
01099
01100 is >> name;
01101
01102 bool isTimeSec = false;
01103 bool isTimeNsec = false;
01104
01105
01106 if(boost::algorithm::ends_with(name, "_splitTime_high32"))
01107 {
01108 isTimeSec = true;
01109 boost::algorithm::erase_last(name, "_splitTime_high32");
01110 }
01111
01112 if(boost::algorithm::ends_with(name, "_splitTime_low32"))
01113 {
01114 isTimeNsec = true;
01115 boost::algorithm::erase_last(name, "_splitTime_low32");
01116 }
01117
01118
01119 bool skipLookupTable = false;
01120 bool isColorScalars = false;
01121 if(fieldName == "SCALARS")
01122 {
01123 dim = 1;
01124 is >> type;
01125 skipLookupTable = true;
01126 }
01127 else if(fieldName == "VECTORS")
01128 {
01129 dim = 3;
01130 is >> type;
01131 }
01132 else if(fieldName == "TENSORS")
01133 {
01134 dim = 9;
01135 is >> type;
01136 }
01137 else if(fieldName == "NORMALS")
01138 {
01139 dim = 3;
01140 is >> type;
01141 }
01142 else if(fieldName == "COLOR_SCALARS")
01143 {
01144 is >> dim;
01145 type = "float";
01146 isColorScalars = true;
01147 }
01148 else
01149 throw runtime_error(string("Unknown field name " + fieldName + ", expecting SCALARS, VECTORS, TENSORS, NORMALS or COLOR_SCALARS."));
01150
01151
01152 safeGetLine(is, line);
01153
01154
01155
01156 if(isTimeSec || isTimeNsec)
01157 {
01158
01159 if(skipLookupTable)
01160 {
01161 safeGetLine(is, line);
01162 }
01163
01164 typename std::map<std::string, SplitTime>::iterator it;
01165
01166 it = labelledSplitTime.find(name);
01167
01168 if(it == labelledSplitTime.end())
01169 {
01170 SplitTime t;
01171 t.high32 = Eigen::Matrix<unsigned int, Eigen::Dynamic, Eigen::Dynamic> (dim, pointCount);
01172 t.low32 = t.high32;
01173 labelledSplitTime[name] = t;
01174 }
01175
01176
01177 if(isTimeSec)
01178 {
01179 assert(labelledSplitTime[name].isHigh32Found == false);
01180 readVtkData(type, isBinary, labelledSplitTime[name].high32.transpose(), is);
01181 labelledSplitTime[name].isHigh32Found = true;
01182 }
01183
01184
01185 if(isTimeNsec)
01186 {
01187 assert(labelledSplitTime[name].isLow32Found == false);
01188 readVtkData(type, isBinary, labelledSplitTime[name].low32.transpose(), is);
01189 labelledSplitTime[name].isLow32Found = true;
01190 }
01191 }
01192 else
01193 {
01194
01195 Matrix descriptorData(dim, pointCount);
01196
01197 if(isColorScalars && isBinary)
01198 {
01199 std::vector<unsigned char> buffer(dim);
01200 for (int i = 0; i < pointCount; ++i){
01201 is.read(reinterpret_cast<char *>(&buffer.front()), dim);
01202 for(int r=0; r < dim; ++r){
01203 descriptorData(r, i) = buffer[r] / static_cast<T>(255.0);
01204 }
01205 }
01206 }
01207 else
01208 {
01209 if(!(type == "float" || type == "double"))
01210 throw runtime_error(string("Field " + fieldName + " is " + type + " but can only be of type double or float."));
01211
01212
01213 if(skipLookupTable)
01214 {
01215 safeGetLine(is, line);
01216 }
01217 readVtkData(type, isBinary, descriptorData.transpose(), is);
01218 }
01219 loadedPoints.addDescriptor(name, descriptorData);
01220 }
01221 }
01222 }
01223
01224
01225 typename std::map<std::string, SplitTime>::iterator it;
01226 for(it=labelledSplitTime.begin(); it!=labelledSplitTime.end(); it++)
01227 {
01228
01229 if(it->second.isHigh32Found == false)
01230 {
01231 throw runtime_error(string("Missing time field representing the higher 32 bits. Expecting SCALARS with name " + it->first + "_splitTime_high32 in the VTK file."));
01232 }
01233
01234 if(it->second.isLow32Found == false)
01235 {
01236 throw runtime_error(string("Missing time field representing the lower 32 bits. Expecting SCALARS with name " + it->first + "_splitTime_low32 in the VTK file."));
01237 }
01238
01239
01240 Int64Matrix timeData(1,pointCount);
01241 for(int i=0; i<it->second.high32.cols(); i++)
01242 {
01243
01244 timeData(0,i) = (((std::int64_t) it->second.high32(0,i)) << 32) | ((std::int64_t) it->second.low32(0,i));
01245 }
01246
01247 loadedPoints.addTime(it->first, timeData);
01248 }
01249
01250 return loadedPoints;
01251 }
01252
01253 template
01254 PointMatcherIO<float>::DataPoints PointMatcherIO<float>::loadVTK(const std::string& fileName);
01255 template
01256 PointMatcherIO<double>::DataPoints PointMatcherIO<double>::loadVTK(const std::string& fileName);
01257
01258
01260 template<typename T>
01261 void PointMatcherIO<T>::saveVTK(const DataPoints& data, const std::string& fileName, bool binary)
01262 {
01263 typedef typename InspectorsImpl<T>::VTKFileInspector VTKInspector;
01264
01265 Parametrizable::Parameters param;
01266 boost::assign::insert(param) ("baseFileName", "");
01267 boost::assign::insert(param) ("writeBinary", toParam(binary));
01268 VTKInspector vtkInspector(param);
01269 vtkInspector.dumpDataPoints(data, fileName);
01270 }
01271
01272
01273 template
01274 void PointMatcherIO<float>::saveVTK(const PointMatcherIO<float>::DataPoints& data, const std::string& fileName, bool binary);
01275 template
01276 void PointMatcherIO<double>::saveVTK(const PointMatcher<double>::DataPoints& data, const std::string& fileName, bool binary);
01277
01286 template<typename T>
01287 typename PointMatcherIO<T>::DataPoints PointMatcherIO<T>::loadPLY(const std::string& fileName)
01288 {
01289 ifstream ifs(fileName.c_str());
01290 if (!ifs.good())
01291 throw runtime_error(string("Cannot open file ") + fileName);
01292 return loadPLY(ifs);
01293 }
01294
01295 template
01296 PointMatcherIO<float>::DataPoints PointMatcherIO<float>::loadPLY(const string& fileName);
01297 template
01298 PointMatcherIO<double>::DataPoints PointMatcherIO<double>::loadPLY(const string& fileName);
01299
01302 template <typename T>
01303 typename PointMatcherIO<T>::DataPoints PointMatcherIO<T>::loadPLY(std::istream& is)
01304 {
01305 class Elements : public vector<PLYElement*>{
01306 public:
01307 ~Elements(){
01308 for (typename vector<PLYElement*>::const_iterator it = this->begin(); it != this->end(); it++ )
01309 {
01310 delete *it;
01311 }
01312 }
01313 };
01314
01315
01316
01317
01318
01319
01320
01321
01322
01323
01324
01325
01326
01327
01328
01329
01330
01331
01332
01333
01334
01336
01337 bool format_defined = false;
01338 bool header_processed = false;
01339
01340 Elements elements;
01341 PLYElementF element_f;
01342 PLYElement* current_element = NULL;
01343 bool skip_props = false;
01344 unsigned elem_offset = 0;
01345 string line;
01346 safeGetLine(is, line);
01347
01348 if (line.find("ply") != 0) {
01349 throw runtime_error(string("PLY parse error: wrong magic header, found <") + line + string(">"));
01350 }
01351
01352 while (!header_processed)
01353 {
01354 if(!safeGetLine(is, line))
01355 throw runtime_error("PLY parse error: reached end of file before end of header definition");
01356
01357
01358 if ( line.empty() )
01359 continue;
01360 istringstream stringstream (line);
01361
01362 string keyword;
01363 stringstream >> keyword;
01364
01365
01366 if (keyword == "comment") {
01367 continue;
01368 }
01369
01370
01371 if (keyword == "format")
01372 {
01373 if (format_defined)
01374 throw runtime_error("PLY parse error: format already defined");
01375
01376 string format_str, version_str;
01377 stringstream >> format_str >> version_str;
01378
01379 if (format_str != "ascii" && format_str != "binary_little_endian" && format_str != "binary_big_endian")
01380 throw runtime_error(string("PLY parse error: format <") + format_str + string("> is not supported"));
01381
01382 if (format_str == "binary_little_endian" || format_str == "binary_big_endian")
01383 throw runtime_error(string("PLY parse error: binary PLY files are not supported"));
01384 if (version_str != "1.0")
01385 {
01386 throw runtime_error(string("PLY parse error: version <") + version_str + string("> of ply is not supported"));
01387 }
01388
01389 format_defined = true;
01390
01391 }
01392 else if (keyword == "element")
01393 {
01394
01395
01396 string elem_name, elem_num_s;
01397 stringstream >> elem_name >> elem_num_s;
01398
01399 unsigned elem_num;
01400 try
01401 {
01402 elem_num = boost::lexical_cast<unsigned>(elem_num_s);
01403 }
01404 catch (boost::bad_lexical_cast& e)
01405 {
01406 throw runtime_error(string("PLY parse error: bad number of elements ") + elem_num_s + string(" for element ") + elem_name);
01407 }
01408
01409 if (element_f.elementSupported(elem_name))
01410 {
01411
01412 PLYElement* elem = element_f.createElement(elem_name, elem_num, elem_offset);
01413 current_element = elem;
01414
01415
01416 for (typename Elements::const_iterator it = elements.begin(); it != elements.end(); it++ )
01417 {
01418 if (**it == *elem) {
01419 delete elem;
01420 throw runtime_error(string("PLY parse error: element: ") + elem_name + string( "is already defined"));
01421 }
01422 }
01423 elements.push_back(elem);
01424 skip_props = false;
01425 }
01426 else
01427 {
01428 LOG_WARNING_STREAM("PLY parse warning: element " << elem_name << " not supported. Skipping.");
01429 skip_props = true;
01430 }
01431
01432 elem_offset += elem_num;
01433 }
01434 else if (keyword == "property")
01435 {
01436 if (current_element == NULL)
01437 {
01438 throw runtime_error("PLY parse error: property listed without defining an element");
01439 }
01440
01441 if (skip_props)
01442 continue;
01443
01444 string next, prop_type, prop_name;
01445 stringstream >> next;
01446
01447
01448 if (next == "list")
01449 {
01450 string prop_idx_type;
01451 stringstream >> prop_idx_type >> prop_type >> prop_name;
01452
01453 PLYProperty list_prop(prop_idx_type, prop_type, prop_name, current_element->total_props);
01454
01455 current_element->properties.push_back(list_prop);
01456 }
01457
01458 else
01459 {
01460 prop_type = next;
01461 stringstream >> prop_name;
01462 PLYProperty prop(prop_type, prop_name, current_element->total_props);
01463
01464 current_element->properties.push_back(prop);
01465 }
01466
01467 current_element->total_props++;
01468 }
01469 else if (keyword == "end_header")
01470 {
01471 if (!format_defined)
01472 {
01473 throw runtime_error(string("PLY parse error: format not defined in header"));
01474 }
01475
01476 if (elements.size() == 0)
01477 {
01478 throw runtime_error(string("PLY parse error: no elements defined in header"));
01479 }
01480
01481 header_processed = true;
01482 }
01483 }
01484
01486
01487
01488
01489 PLYElement* vertex = elements[0];
01490
01491 if(vertex->name != "vertex")
01492 {
01493 throw runtime_error(string("PLY parse error: vertex should be the first element defined."));
01494 }
01495
01496
01497 const SupportedLabels & externalLabels = getSupportedExternalLabels();
01498
01499 int rowIdFeatures = 0;
01500 int rowIdDescriptors = 0;
01501 int rowIdTime= 0;
01502
01503 LabelGenerator featLabelGen, descLabelGen, timeLabelGen;
01504
01505
01506
01507 for(size_t i=0; i<externalLabels.size(); i++)
01508 {
01509 const SupportedLabel & supLabel = externalLabels[i];
01510
01511
01512 for(it_PLYProp it=vertex->properties.begin(); it!=vertex->properties.end(); ++it)
01513 {
01514
01515 if(supLabel.externalName == it->name)
01516 {
01517 it->pmType = supLabel.type;
01518
01519
01520 switch (supLabel.type)
01521 {
01522 case FEATURE:
01523 it->pmRowID = rowIdFeatures;
01524 featLabelGen.add(supLabel.internalName);
01525 rowIdFeatures++;
01526 break;
01527 case DESCRIPTOR:
01528 it->pmRowID = rowIdDescriptors;
01529 descLabelGen.add(supLabel.internalName);
01530 rowIdDescriptors++;
01531 break;
01532 case TIME:
01533 it->pmRowID = rowIdTime;
01534 timeLabelGen.add(supLabel.internalName);
01535 rowIdTime++;
01536 default:
01537 throw runtime_error(string("PLY Implementation Error: encounter a type different from FEATURE, DESCRIPTOR and TIME. Implementation not supported. See the definition of 'enum PMPropTypes'"));
01538 break;
01539 }
01540
01541
01542 break;
01543 }
01544 }
01545 }
01546
01547
01548 for(it_PLYProp it=vertex->properties.begin(); it!=vertex->properties.end(); ++it)
01549 {
01550 if(it->pmType == UNSUPPORTED)
01551 {
01552 it->pmType = DESCRIPTOR;
01553 it->pmRowID = rowIdDescriptors;
01554 descLabelGen.add(it->name);
01555 rowIdDescriptors++;
01556 }
01557 }
01558
01560
01561
01562 const unsigned int featDim = featLabelGen.getLabels().totalDim();
01563 const unsigned int descDim = descLabelGen.getLabels().totalDim();
01564 const unsigned int timeDim = timeLabelGen.getLabels().totalDim();
01565 const unsigned int nbPoints = vertex->num;
01566
01567 Matrix features = Matrix(featDim, nbPoints);
01568 Matrix descriptors = Matrix(descDim, nbPoints);
01569 Int64Matrix times = Int64Matrix(timeDim, nbPoints);
01570
01571
01573
01574 const int nbProp = vertex->total_props;
01575 const int nbValues = nbPoints*nbProp;
01576 int propID = 0;
01577 int col = 0;
01578 for(int i=0; i<nbValues; i++)
01579 {
01580 T value;
01581 if(!(is >> value))
01582 {
01583 throw runtime_error(
01584 (boost::format("PLY parse error: expected %1% values (%2% points with %3% properties) but only found %4% values.") % nbValues % nbPoints % nbProp % i).str());
01585 }
01586 else
01587 {
01588 const int row = vertex->properties[propID].pmRowID;
01589 const PMPropTypes type = vertex->properties[propID].pmType;
01590
01591
01592
01593 if (vertex->properties[propID].name == "red" || vertex->properties[propID].name == "green" || vertex->properties[propID].name == "blue" || vertex->properties[propID].name == "alpha") {
01594 value /= 255.0;
01595 }
01596
01597 switch (type)
01598 {
01599 case FEATURE:
01600 features(row, col) = value;
01601 break;
01602 case DESCRIPTOR:
01603 descriptors(row, col) = value;
01604 break;
01605 case TIME:
01606 times(row, col) = value;
01607 break;
01608 case UNSUPPORTED:
01609 throw runtime_error("Implementation error in loadPLY(). This should not throw.");
01610 break;
01611 }
01612
01613 ++propID;
01614
01615 if(propID >= nbProp)
01616 {
01617 propID = 0;
01618 ++col;
01619 }
01620 }
01621 }
01622
01623
01624
01626
01627
01628 DataPoints loadedPoints(features, featLabelGen.getLabels());
01629
01630 if (descriptors.rows() > 0)
01631 {
01632 loadedPoints.descriptors = descriptors;
01633 loadedPoints.descriptorLabels = descLabelGen.getLabels();
01634 }
01635
01636 if(times.rows() > 0)
01637 {
01638 loadedPoints.times = times;
01639 loadedPoints.timeLabels = timeLabelGen.getLabels();
01640 }
01641
01642
01643 if(!loadedPoints.featureExists("pad"))
01644 {
01645 loadedPoints.addFeature("pad", Matrix::Ones(1,features.cols()));
01646 }
01647
01648 return loadedPoints;
01649
01650 }
01651
01652 template<typename T>
01653 void PointMatcherIO<T>::savePLY(const DataPoints& data,
01654 const std::string& fileName)
01655 {
01656
01657
01658 ofstream ofs(fileName.c_str());
01659 if (!ofs.good())
01660 throw runtime_error(string("Cannot open file ") + fileName);
01661
01662 const int pointCount(data.features.cols());
01663 const int featCount(data.features.rows());
01664 const int descRows(data.descriptors.rows());
01665
01666
01667 if (pointCount == 0)
01668 {
01669 LOG_WARNING_STREAM("Warning, no points, doing nothing");
01670 return;
01671 }
01672
01673 ofs << "ply\n" <<"format ascii 1.0\n";
01674 ofs << "element vertex " << pointCount << "\n";
01675 for (int f=0; f <(featCount-1); f++)
01676 {
01677 ofs << "property float " << data.featureLabels[f].text << "\n";
01678 }
01679
01680 for (size_t i = 0; i < data.descriptorLabels.size(); i++)
01681 {
01682 Label lab = data.descriptorLabels[i];
01683 for (size_t s = 0; s < lab.span; s++)
01684
01685 {
01686 ofs << "property float " << getColLabel(lab,s) << "\n";
01687 }
01688 }
01689
01690 ofs << "end_header\n";
01691
01692
01693 for (int p = 0; p < pointCount; ++p)
01694 {
01695 for (int f = 0; f < featCount - 1; ++f)
01696 {
01697 ofs << data.features(f, p);
01698 if(!(f == featCount-2 && descRows == 0))
01699 ofs << " ";
01700 }
01701
01702 bool datawithColor = data.descriptorExists("color");
01703 int colorStartingRow = data.getDescriptorStartingRow("color");
01704 int colorEndRow = colorStartingRow + data.getDescriptorDimension("color");
01705 for (int d = 0; d < descRows; ++d)
01706 {
01707 if (datawithColor && d >= colorStartingRow && d < colorEndRow) {
01708 ofs << static_cast<unsigned>(data.descriptors(d, p) * 255.0);
01709 } else {
01710 ofs << data.descriptors(d, p);
01711 }
01712 if(d != descRows-1)
01713 ofs << " ";
01714 }
01715 ofs << "\n";
01716 }
01717
01718 ofs.close();
01719 }
01720
01721 template
01722 void PointMatcherIO<float>::savePLY(const DataPoints& data, const std::string& fileName);
01723 template
01724 void PointMatcherIO<double>::savePLY(const DataPoints& data, const std::string& fileName);
01725
01727 template<typename T>
01728 PointMatcherIO<T>::PLYProperty::PLYProperty(const std::string& type,
01729 const std::string& name, const unsigned pos) :
01730 name(name),
01731 type(type),
01732 pos(pos)
01733 {
01734 if (plyPropTypeValid(type))
01735 {
01736 is_list = false;
01737 }
01738 else
01739 {
01740 throw std::runtime_error(
01741 std::string("PLY parse error: property type ") + type
01742 + std::string(" for property ") + name
01743 + std::string(" is invalid"));
01744 }
01745
01746 pmType = UNSUPPORTED;
01747 pmRowID = -1;
01748
01749 }
01750
01752 template<typename T>
01753 PointMatcherIO<T>::PLYProperty::PLYProperty(const std::string& idx_type,
01754 const std::string& type, const std::string& name, const unsigned pos) :
01755 name(name),
01756 type(type),
01757 idx_type(idx_type),
01758 pos(pos)
01759 {
01760 if (plyPropTypeValid(idx_type) && plyPropTypeValid(type))
01761 {
01762 is_list = true;
01763 }
01764 else
01765 {
01766 throw std::runtime_error(
01767 std::string("PLY parse error: property list type ") + idx_type
01768 + std::string(" ") + type
01769 + std::string(" for property ") + name
01770 + std::string(" is invalid"));
01771 }
01772
01773 pmType = UNSUPPORTED;
01774 pmRowID = -1;
01775 }
01776
01777 template
01778 class PointMatcherIO<float>::PLYElement;
01779 template
01780 class PointMatcherIO<double>::PLYElement;
01781
01782 template
01783 class PointMatcherIO<float>::PLYProperty;
01784 template
01785 class PointMatcherIO<double>::PLYProperty;
01786
01787
01788 template <typename T>
01789 typename PointMatcherIO<T>::PLYElementF::ElementTypes PointMatcherIO<T>::PLYElementF::getElementType(const std::string& elem_name)
01790 {
01791 string lc = elem_name;
01792 boost::algorithm::to_lower(lc);
01793 if (lc == "vertex")
01794 {
01795 return VERTEX;
01796 }
01797 else
01798 {
01799 return UNSUPPORTED;
01800 }
01801 }
01802
01803
01804 template <typename T>
01805 bool PointMatcherIO<T>::PLYElementF::elementSupported(const std::string& elem_name)
01806 {
01807 return getElementType(elem_name) != UNSUPPORTED;
01808 }
01809
01810
01811 template<typename T>
01812 typename PointMatcherIO<T>::PLYElement* PointMatcherIO<T>::PLYElementF::createElement(
01813 const std::string& elem_name, const int elem_num, const unsigned offset) {
01814 ElementTypes type = getElementType(elem_name);
01815 if (type == VERTEX)
01816 return new PLYVertex(elem_num, offset);
01817 else
01818 return NULL;
01819 }
01820
01821
01822 template<typename T>
01823 bool PointMatcherIO<T>::plyPropTypeValid(const std::string& type) {
01824 return (type == "char" || type == "uchar" || type == "short"
01825 || type == "ushort" || type == "int" || type == "uint"
01826 || type == "float" || type == "double");
01827 }
01828
01829
01830 template <typename T>
01831 bool PointMatcherIO<T>::PLYElement::operator==(const PLYElement& rhs) const
01832 {
01833 return name == rhs.name;
01834 }
01835
01836
01837 template <typename T>
01838 bool PointMatcherIO<T>::PLYProperty::operator==(const PLYProperty& rhs) const
01839 {
01840 return name == rhs.name && type == rhs.type;
01841 }
01842
01843
01846 template<typename T>
01847 typename PointMatcherIO<T>::DataPoints PointMatcherIO<T>::loadPCD(const string& fileName) {
01848 ifstream ifs(fileName.c_str());
01849 if (!ifs.good())
01850 throw runtime_error(string("Cannot open file ") + fileName);
01851 return loadPCD(ifs);
01852 }
01853
01854
01855 template
01856 PointMatcherIO<float>::DataPoints PointMatcherIO<float>::loadPCD(const string& fileName);
01857 template
01858 PointMatcherIO<double>::DataPoints PointMatcherIO<double>::loadPCD(const string& fileName);
01859
01860
01863 template<typename T>
01864 typename PointMatcherIO<T>::DataPoints PointMatcherIO<T>::loadPCD(std::istream& is) {
01865
01866
01867
01868
01869
01870
01871
01872
01873
01874
01875
01876
01877
01878
01879
01880
01881
01882
01883
01884
01885
01886
01887
01888
01889
01890
01891
01892
01893
01894
01896
01897
01898 size_t lineNum = 0;
01899 PCDheader header;
01900
01901 string line;
01902 while (safeGetLine(is, line))
01903 {
01904
01905
01906 boost::trim (line);
01907
01908
01909 if (line.substr(0,1) == "#" || line == "")
01910 {
01911 lineNum++;
01912 continue;
01913 }
01914
01915 vector<string> tokens;
01916 boost::split(tokens, line, boost::is_any_of("\t\r "), boost::token_compress_on);
01917
01918 string pcd_version_str;
01919 if (tokens[0] == "VERSION")
01920 {
01921 header.version = tokens[1];
01922
01923 if (tokens[1] != "0.7" && tokens[1] != ".7")
01924 throw runtime_error("PCD Parse Error: Only PCD Version 0.7 is supported");
01925 }
01926
01927 else if (tokens[0] == "FIELDS")
01928 {
01929 header.properties.resize(tokens.size() - 1);
01930
01931 for (size_t i = 1; i < tokens.size(); i++)
01932 {
01933 header.properties[i-1].field = tokens[i];
01934 }
01935
01936 }
01937
01938 else if (tokens[0] == "SIZE")
01939 {
01940 if((tokens.size() - 1) != header.properties.size())
01941 throw runtime_error("PCD Parse Error: number of elements for SIZE must be the same as FIELDS");
01942
01943 for (size_t i = 1; i < tokens.size(); i++)
01944 {
01945 const unsigned int size = boost::lexical_cast<unsigned int >(tokens[i]);
01946 header.properties[i-1].size = size;
01947 }
01948
01949 }
01950
01951 else if (tokens[0] == "TYPE")
01952 {
01953 if((tokens.size() - 1) != header.properties.size())
01954 throw runtime_error("PCD Parse Error: number of elements for TYPE must be the same as FIELDS");
01955
01956 for (size_t i = 1; i < tokens.size(); i++)
01957 {
01958 const char type = boost::lexical_cast<char>(tokens[i]);
01959 header.properties[i-1].type = type;
01960 if (type != 'I' && type != 'U' && type != 'F')
01961 throw runtime_error("PCD Parse Error: invalid TYPE, it must be 'I', 'U', or 'F'");
01962 }
01963
01964 }
01965
01966 else if (tokens[0] == "COUNT")
01967 {
01968
01969 if((tokens.size() - 1) != header.properties.size())
01970 throw runtime_error("PCD Parse Error: number of elements for COUNT must be the same as FIELDS");
01971
01972 for (size_t i = 1; i < tokens.size(); i++)
01973 {
01974 const unsigned int count = boost::lexical_cast<unsigned int >(tokens[i]);
01975 header.properties[i-1].count = count;
01976 }
01977
01978 }
01979
01980 else if (tokens[0] == "WIDTH")
01981 {
01982 try
01983 {
01984 header.width = boost::lexical_cast<unsigned int >(tokens[1]);
01985 }
01986 catch (boost::bad_lexical_cast& e)
01987 {
01988 throw runtime_error("PCD Parse Error: invalid WIDTH");
01989 }
01990
01991 }
01992
01993 else if (tokens[0] == "HEIGHT")
01994 {
01995 try
01996 {
01997 header.height= boost::lexical_cast<unsigned int >(tokens[1]);
01998 }
01999 catch (boost::bad_lexical_cast& e)
02000 {
02001 throw runtime_error("PCD Parse Error: invalid HEIGHT");
02002 }
02003
02004 }
02005
02006 else if (tokens[0] == "VIEWPOINT")
02007 {
02008 if((tokens.size() - 1) != 7 )
02009 throw runtime_error("PCD Parse Error: number of elements for VIEWPOINT must be 7");
02010
02011 for (size_t i = 1; i < tokens.size(); i++)
02012 {
02013 try
02014 {
02015 header.viewPoint(i-1, 0) = boost::lexical_cast<T>(tokens[i]);
02016 }
02017 catch (boost::bad_lexical_cast& e)
02018 {
02019 stringstream ss;
02020 ss << "PCD Parse Error: invalid value(" << tokens[i] << ") of VIEWPOINT";
02021 throw runtime_error(ss.str());
02022 }
02023 }
02024
02025 }
02026
02027 else if (tokens[0] == "POINTS")
02028 {
02029 try
02030 {
02031 header.nbPoints = boost::lexical_cast<unsigned int>(tokens[1]);
02032 }
02033 catch (boost::bad_lexical_cast& e)
02034 {
02035 stringstream ss;
02036 ss << "PCD Parse Error: the value in the element POINTS (" << tokens[1] << ") could not be cast as unsigned int";
02037 throw runtime_error(ss.str());
02038 }
02039 }
02040
02041 else if (tokens[0] == "DATA")
02042 {
02043 header.dataType= tokens[1];
02044
02045 if (header.dataType == "ascii")
02046 {
02047
02048 break;
02049 }
02050 else if(header.dataType == "binary")
02051 {
02052 throw runtime_error("PCD Implementation Error: the option for DATA binary is not implemented yet");
02053 }
02054 else
02055 {
02056 stringstream ss;
02057 ss << "PCD Parse Error: the value in the element DATA (" << tokens[1] << ") must be ascii or binary";
02058 throw runtime_error(ss.str());
02059 }
02060
02061 }
02062
02063 lineNum++;
02064 }
02065
02066 if (header.properties.size() == 0)
02067 throw runtime_error("PCD Parse Error: no FIELDS were find in the header");
02068
02069
02070
02071 if (header.width * header.height != header.nbPoints)
02072 throw runtime_error("PCD Parse Error: POINTS field does not match WIDTH and HEIGHT fields");
02073
02075
02076
02077
02078 const SupportedLabels & externalLabels = getSupportedExternalLabels();
02079
02080 int rowIdFeatures = 0;
02081 int rowIdDescriptors = 0;
02082 int rowIdTime= 0;
02083
02084 LabelGenerator featLabelGen, descLabelGen, timeLabelGen;
02085
02086
02087 for(size_t i=0; i<externalLabels.size(); i++)
02088 {
02089 const SupportedLabel & supLabel = externalLabels[i];
02090
02091
02092 for(size_t i=0; i < header.properties.size(); i++)
02093 {
02094 const PCDproperty prop = header.properties[i];
02095
02096
02097
02098 if(supLabel.externalName == prop.field)
02099 {
02100 header.properties[i].pmType = supLabel.type;
02101
02102
02103 switch (supLabel.type)
02104 {
02105 case FEATURE:
02106 header.properties[i].pmRowID = rowIdFeatures;
02107 featLabelGen.add(supLabel.internalName);
02108 rowIdFeatures++;
02109 if(prop.count != 1)
02110 {
02111 stringstream ss;
02112 ss << "PCD Parse Error: the field " << prop.field << " must have a count of 1";
02113 throw runtime_error(ss.str());
02114 }
02115 break;
02116 case DESCRIPTOR:
02117 header.properties[i].pmRowID = rowIdDescriptors;
02118 descLabelGen.add(supLabel.internalName, prop.count);
02119 rowIdDescriptors += prop.count;
02120 break;
02121 case TIME:
02122 header.properties[i].pmRowID = rowIdTime;
02123 timeLabelGen.add(supLabel.internalName, prop.count);
02124 rowIdTime += prop.count;
02125 default:
02126 throw runtime_error(string("PCD Implementation Error: encounter a type different from FEATURE, DESCRIPTOR and TIME. Implementation not supported. See the definition of 'enum PMPropTypes'"));
02127 break;
02128 }
02129
02130
02131 break;
02132 }
02133 }
02134 }
02135
02136
02137 for(size_t i=0; i < header.properties.size(); i++)
02138 {
02139 const PCDproperty prop = header.properties[i];
02140 if(prop.pmType == UNSUPPORTED)
02141 {
02142 header.properties[i].pmType = DESCRIPTOR;
02143 header.properties[i].pmRowID = rowIdDescriptors;
02144 descLabelGen.add(prop.field, prop.count);
02145 rowIdDescriptors += prop.count;
02146 }
02147 }
02148
02149
02151
02152
02153 const unsigned int featDim = featLabelGen.getLabels().totalDim();
02154 const unsigned int descDim = descLabelGen.getLabels().totalDim();
02155 const unsigned int timeDim = timeLabelGen.getLabels().totalDim();
02156 const unsigned int totalDim = featDim + descDim + timeDim;
02157 const unsigned int nbPoints = header.nbPoints;
02158
02159 Matrix features = Matrix(featDim, nbPoints);
02160 Matrix descriptors = Matrix(descDim, nbPoints);
02161 Int64Matrix times = Int64Matrix(timeDim, nbPoints);
02162
02163
02165
02166
02167 size_t col = 0;
02168 while (safeGetLine(is, line))
02169 {
02170
02171
02172 boost::trim (line);
02173
02174
02175 if (line.substr(0,1) == "#" || line == "")
02176 {
02177 lineNum++;
02178 continue;
02179 }
02180
02181 vector<string> tokens;
02182 boost::split(tokens, line, boost::is_any_of("\t\r "), boost::token_compress_on);
02183
02184
02185 if (tokens.size() != totalDim)
02186 throw runtime_error(string("PCD Parse Error: number of data columns does not match number of fields at line: ") + boost::lexical_cast<string>(lineNum));
02187
02188 unsigned int fileCol = 0;
02189 for(size_t i=0; i<header.properties.size(); i++)
02190 {
02191 const unsigned int count = header.properties[i].count;
02192 const unsigned int row = header.properties[i].pmRowID;
02193 const PMPropTypes type = header.properties[i].pmType;
02194
02195
02196 for(size_t j=0; j<count; j++)
02197 {
02198 switch (type)
02199 {
02200 case FEATURE:
02201 features(row+j, col) = boost::lexical_cast<T>(tokens[fileCol]);
02202 break;
02203 case DESCRIPTOR:
02204 descriptors(row+j, col) = boost::lexical_cast<T>(tokens[fileCol]);
02205 break;
02206 case TIME:
02207 times(row+j, col) = boost::lexical_cast<std::int64_t>(tokens[fileCol]);
02208 break;
02209 case UNSUPPORTED:
02210 throw runtime_error("Implementation error in loadPCD(). This should not throw.");
02211 break;
02212 }
02213
02214 fileCol++;
02215 }
02216
02217 }
02218
02219 col++;
02220 lineNum++;
02221 }
02222
02223 if (col != nbPoints)
02224 {
02225 stringstream ss;
02226 ss << "PCD Parse Error: the number of points in the file (" << col << ") is less than the specified number of points (" << nbPoints << ")";
02227 throw runtime_error(ss.str());
02228 }
02229
02231
02232
02233 DataPoints loadedPoints(features, featLabelGen.getLabels());
02234
02235 if (descriptors.rows() > 0)
02236 {
02237 loadedPoints.descriptors = descriptors;
02238 loadedPoints.descriptorLabels = descLabelGen.getLabels();
02239 }
02240
02241 if(times.rows() > 0)
02242 {
02243 loadedPoints.times = times;
02244 loadedPoints.timeLabels = timeLabelGen.getLabels();
02245 }
02246
02247
02248 if(!loadedPoints.featureExists("pad"))
02249 {
02250 loadedPoints.addFeature("pad", Matrix::Ones(1,features.cols()));
02251 }
02252
02253 return loadedPoints;
02254 }
02255
02256 template<typename T>
02257 void PointMatcherIO<T>::savePCD(const DataPoints& data,
02258 const std::string& fileName) {
02259 ofstream ofs(fileName.c_str());
02260 if (!ofs.good())
02261 throw runtime_error(string("Cannot open file ") + fileName);
02262
02263 const int pointCount(data.features.cols());
02264 const int featCount(data.features.rows());
02265 const int descRows(data.descriptors.rows());
02266 const int descCount(data.descriptorLabels.size());
02267
02268 if (pointCount == 0)
02269 {
02270 LOG_WARNING_STREAM("Warning, no points, doing nothing");
02271 return;
02272 }
02273
02274 ofs << "# .PCD v.7 - Point Cloud Data file format\n" <<"VERSION .7\n";
02275 ofs << "FIELDS";
02276
02277 for (int f=0; f < (featCount - 1); f++)
02278 {
02279 ofs << " " << data.featureLabels[f].text;
02280 }
02281
02282 if (descRows == 0)
02283 ofs << "\n";
02284 else
02285 {
02286 for (int i = 0; i < descCount; i++)
02287 {
02288 ofs << " " << data.descriptorLabels[i].text;
02289 }
02290 ofs << "\n";
02291 }
02292
02293 ofs << "SIZE";
02294 for (int i =0; i < featCount - 1 + descCount; i++)
02295 {
02296 ofs << " 4";
02297 }
02298 ofs << "\n";
02299
02300 ofs << "TYPE";
02301 for (int i =0; i < featCount - 1 + descCount; i++)
02302 {
02303 ofs << " F";
02304 }
02305 ofs << "\n";
02306
02307 ofs << "COUNT";
02308 for (int f = 0; f < featCount - 1 ; f++ )
02309 ofs << " 1";
02310 if (descCount == 0)
02311 ofs << "\n";
02312 else
02313 {
02314 for (int i = 0; i < descCount; i++)
02315 {
02316 ofs << " " << data.descriptorLabels[i].span;
02317 }
02318 ofs << "\n";
02319 }
02320
02321 ofs << "WIDTH " << pointCount << "\n";
02322 ofs << "HEIGHT 1\n";
02323 ofs << "POINTS " << pointCount << "\n";
02324 ofs << "DATA ascii\n";
02325
02326
02327 for (int p = 0; p < pointCount; ++p)
02328 {
02329 for (int f = 0; f < featCount - 1; ++f)
02330 {
02331 ofs << data.features(f, p);
02332 if(!(f == featCount-2 && descRows == 0))
02333 ofs << " ";
02334 }
02335 for (int d = 0; d < descRows; ++d)
02336 {
02337 ofs << data.descriptors(d, p);
02338 if(d != descRows-1)
02339 ofs << " ";
02340 }
02341 ofs << "\n";
02342 }
02343
02344 ofs.close();
02345 }
02346
02347 template
02348 void PointMatcherIO<float>::savePCD(const DataPoints& data, const std::string& fileName);
02349 template
02350 void PointMatcherIO<double>::savePCD(const DataPoints& data, const std::string& fileName);
02351
02352
02353