IO.cpp
Go to the documentation of this file.
00001 // kate: replace-tabs off; indent-width 4; indent-mode normal
00002 // vim: ts=4:sw=4:noexpandtab
00003 /*
00004 
00005 Copyright (c) 2010--2012,
00006 François Pomerleau and Stephane Magnenat, ASL, ETHZ, Switzerland
00007 You can contact the authors at <f dot pomerleau at gmail dot com> and
00008 <stephane at magnenat dot net>
00009 
00010 All rights reserved.
00011 
00012 Redistribution and use in source and binary forms, with or without
00013 modification, are permitted provided that the following conditions are met:
00014     * Redistributions of source code must retain the above copyright
00015       notice, this list of conditions and the following disclaimer.
00016     * Redistributions in binary form must reproduce the above copyright
00017       notice, this list of conditions and the following disclaimer in the
00018       documentation and/or other materials provided with the distribution.
00019     * Neither the name of the <organization> nor the
00020       names of its contributors may be used to endorse or promote products
00021       derived from this software without specific prior written permission.
00022 
00023 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
00024 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00025 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00026 DISCLAIMED. IN NO EVENT SHALL ETH-ASL BE LIABLE FOR ANY
00027 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00028 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00029 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00030 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00031 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00032 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00033 
00034 */
00035 
00036 #include "IO.h"
00037 #include "IOFunctions.h"
00038 #include "InspectorsImpl.h"
00039 
00040 // For logging
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 // Tokenize a string, excepted if it begins with a '%' (a comment in CSV)
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] != '%') // Jump line if it's commented
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 // Open and parse a CSV file, return the data
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 // load the rest of the file
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         // Use for debug
00148         
00149         //for(BOOST_AUTO(it,data.begin()); it!=data.end(); it++)
00150         //{
00151         //      cout << "--------------------------" << endl;
00152         //      cout << "Header: |" << (*it).first << "|" << endl;
00153         //      //for(unsigned i=0; i<(*it).second.size(); i++)
00154         //      //{
00155         //      //      cout << (*it).second[i] << endl;
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 // Empty constructor
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         // Look for transformations
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         // Check for consistency
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         // Load reading
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         // for every lines
00258         for(unsigned line=0; line<lineCount; line++)
00259         {
00260                 FileInfo info;
00261                 
00262                 // Files
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                 // Load transformations
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                 // Build the list
00280                 this->push_back(info);
00281         }
00282         
00283         // Debug: Print the list
00284         /*for(unsigned i=0; i<list.size(); i++)
00285         {
00286                 cout << "\n--------------------------" << endl;
00287                 cout << "Sequence " << i << ":" << endl;
00288                 cout << "Reading path: " << list[i].readingFileName << endl;
00289                 cout << "Reference path: " << list[i].referenceFileName << endl;
00290                 cout << "Extension: " << list[i].fileExtension << endl;
00291                 cout << "Tranformation:\n" << list[i].initialTransformation << endl;
00292                 cout << "Grativity:\n" << list[i].gravity << endl;
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 // Class LabelGenerator
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 // Class LabelGenerator
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                 // format: eigVectors<0-2><X-Z>
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         //count lines in the file
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         //reset the stream
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                 // Skip empty lines
00565                 if(line.empty())
00566                         break;
00567         
00568                 // Look for text header
00569                 unsigned int len = strspn(line.c_str(), " ,+-.1234567890Ee");
00570                 if(len != line.length())
00571                 {
00572                         //cout << "Header detected" << endl;
00573                         hasHeader = true;
00574                 }
00575                 else
00576                 {
00577                         hasHeader = false;
00578                 }
00579 
00580                 // Count dimension using first line
00581                 if(firstLine)
00582                 {
00583                         unsigned int dim = 0;
00584                         char tmpLine[1024]; //FIXME: might be problematic for large file
00585                         strcpy(tmpLine, line.c_str());
00586                         char *brkt = 0;
00587                         token = strtok_r(tmpLine, delimiters, &brkt);
00588 
00589                         //1- BUILD HEADER
00590                         while (token)
00591                         {
00592                                 // Load text header
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                                 // Check if it is a simple file with only coordinates
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                                         // Fill with unkown column names
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                                         // Overwrite with user inputs
00626                                         csvHeader[idX] = GenericInputHeader("x");
00627                                         csvHeader[idY] = GenericInputHeader("y");
00628                                         if(idZ != -1)
00629                                                 csvHeader[idZ] = GenericInputHeader("z");
00630                                 }
00631                                 else
00632                                 {
00633                                         // Assume logical order...
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                         //2- PROCESS HEADER
00642                         // Load known features, descriptors, and time
00643                         const SupportedLabels externalLabels = getSupportedExternalLabels();
00644 
00645                         // Counters
00646                         int rowIdFeatures = 0;
00647                         int rowIdDescriptors = 0;
00648                         int rowIdTime = 0;
00649 
00650                         
00651                         // Loop through all known external names (ordered list)
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                                                 // we stop searching once we have a match
00685                                                 break;
00686                                         }
00687                                 }
00688                         }
00689 
00690                         // loop through the remaining UNSUPPORTED labels and assigned them to a descriptor row
00691                         for(unsigned int i=0; i<csvHeader.size(); i++)
00692                         {
00693                                 if(csvHeader[i].matrixType == UNSUPPORTED)
00694                                 {
00695                                         csvHeader[i].matrixType = DESCRIPTOR; // force descriptor
00696                                         csvHeader[i].matrixRowId = rowIdDescriptors;
00697                                         descLabelGen.add(csvHeader[i].name); // keep original name
00698                                         rowIdDescriptors++;
00699                                 }
00700                         }
00701                 
00702 
00703                         //3- RESERVE MEMORY
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                 //4- LOAD DATA (this start again from the first line)
00719                 char* brkt = 0;
00720                 char line_c[1024];//FIXME: this might be a problem for large files
00721                 strcpy(line_c,line.c_str());
00722                 token = strtok_r(line_c, delimiters, &brkt);
00723                 
00724                 if(!(hasHeader && firstLine))
00725                 {
00726                         // Parse a line
00727                         csvCol = 0;
00728                         while (token)
00729                         {
00730                                 if(csvCol > (csvHeader.size() - 1))
00731                                 {
00732                                         // Error check (too much data)
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                                 // Alias
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                                 //fetch next element
00759                                 token = strtok_r(NULL, delimiters, &brkt);
00760                                 csvCol++;
00761                         }
00762                 
00763                 
00764                         // Error check (not enough data)
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         // 5- ASSEMBLE FINAL DATAPOINTS
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         // Ensure homogeous coordinates
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         // write header
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         // write points
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); // remove line end after parameters;
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         // parse header
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         // parse points, descriptors and time
00981         string fieldName;
00982         string name;
00983         int dim = 0;
00984         int pointCount = 0;
00985         string type;
00986         
00987         while (is >> fieldName)
00988         {
00989                 // load features
00990                 if(fieldName == "POINTS")
00991                 {
00992                         is >> pointCount;
00993                         is >> type;
00994                         safeGetLine(is, line); // remove line end after parameters!
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                 // Dataset type
01013                 // POLYDATA
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                 // Unstructure Grid
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); // according to http://www.vtk.org/VTK/img/file-formats.pdf CELL_TYPES only has one parameter (n)
01042                 }
01043 
01045                 // Point data
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                 // Field data is ignored
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") // skip that type
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") // Skip METADATA block
01088                 {
01089                         safeGetLine(is, line);
01090                         safeGetLine(is, line);
01091                         while(!line.empty())
01092                         {
01093                                 safeGetLine(is, line);
01094                         }
01095                 }
01096                 else // Load descriptors or time
01097                 {
01098 
01099                         // label name
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); // remove rest of the parameter line including its line end;
01153 
01154                         
01155                         // Load time data
01156                         if(isTimeSec || isTimeNsec)
01157                         {
01158                                 // Skip LOOKUP_TABLE line
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                                 // reserve memory
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                                 // Load seconds
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                                 // Load nano seconds
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                                         // Skip LOOKUP_TABLE line
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         // Combine time and add to point cloud
01225         typename std::map<std::string, SplitTime>::iterator it;
01226         for(it=labelledSplitTime.begin(); it!=labelledSplitTime.end(); it++)
01227         {
01228                 // Confirm that both parts were loaded
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                 // Loop through points
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         Steps:
01317         1- PARSE PLY HEADER
01318         2- ASSIGN PLY PROPERTIES TO DATAPOINTS ROWS
01319         3- Reserve memory for a DataPoints
01320         4- Parse PLY vertex to appropriate DataPoints cols and rows 
01321         5- Assemble final DataPoints
01322 
01323         PLY organisation:
01324 
01325         element 1 [name, size]
01326                 - property 1 [type, name]
01327                 - property 2
01328                 - ...
01329         element 2
01330                 -...
01331 
01332         */
01333 
01334 
01336         // 1- PARSE PLY HEADER
01337         bool format_defined = false;
01338         bool header_processed = false;
01339 
01340         Elements elements;
01341         PLYElementF element_f; // factory
01342         PLYElement* current_element = NULL;
01343         bool skip_props = false; // flag to skip properties if element is not supported
01344         unsigned elem_offset = 0; // keep track of line position of elements that are supported
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                 // ignore comment
01366                 if (keyword == "comment") {
01367                         continue;
01368                 }
01369 
01370                 // We only support ascii and ply version 1.0
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                                 // Initialize current element
01412                                 PLYElement* elem = element_f.createElement(elem_name, elem_num, elem_offset);
01413                                 current_element = elem;
01414 
01415                                 // check that element is not already defined
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                         // PLY list property
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                         // PLY regular property
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         // 2- ASSIGN PLY PROPERTIES TO DATAPOINTS ROWS
01487         
01488         // Fetch vertex
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         // Fetch known features and descriptors
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         // Loop through all known external names (ordered list)
01507         for(size_t i=0; i<externalLabels.size(); i++)
01508         {
01509                 const SupportedLabel & supLabel = externalLabels[i];
01510 
01511                 //Search if that feature exist
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                                 // Assign rowId in that order
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                                 // we stop searching once we have a match
01542                                 break;
01543                         }
01544                 }
01545         }
01546         
01547         // loop through the remaining UNSUPPORTED labels and assigned them to a single descriptor row
01548         for(it_PLYProp it=vertex->properties.begin(); it!=vertex->properties.end(); ++it)
01549         {
01550                 if(it->pmType == UNSUPPORTED)
01551                 {
01552                         it->pmType = DESCRIPTOR; // force descriptor
01553                         it->pmRowID = rowIdDescriptors;
01554                         descLabelGen.add(it->name); // keep original name
01555                         rowIdDescriptors++;
01556                 }
01557         }
01558 
01560         // 3- RESERVE DATAPOINTS MEMORY
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         // 4- PARSE PLY DATA (vertex)
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                         // rescale color from [0,254] to [0, 1[
01592                         // FIXME: do we need that?
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         // 5- ASSEMBLE FINAL DATAPOINTS
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         // Ensure homogeous coordinates
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         //typedef typename DataPoints::Labels Labels;
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         // write points
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         Steps:
01869         1- PARSE PCD HEADER
01870         2- ASSIGN PCD PROPERTIES TO DATAPOINTS ROWS
01871         3- Reserve memory for a DataPoints
01872         4- Parse PCD XXX to appropriate DataPoints cols and rows 
01873         5- Assemble final DataPoints
01874 
01875         PCD organisation:
01876 
01877         # .PCD v.7 - Point Cloud Data file format
01878         VERSION number
01879         FIELDS prop1 prop2 prop3 ...
01880         SIZE nbBytes1 nbBytes2 nbBytes3
01881         TYPE type1 type2 type3
01882         COUNT nbDim1 nbDim2 nbDim3
01883         WIDTH w
01884         HEIGHT h
01885         VIEWPOINT 0 0 0 1 0 0 0
01886         POINTS size (should be w*h)
01887         DATA ascii or binary
01888         data1 data2 data3 ...
01889         data1 data2 data3 ...
01890         ...
01891 
01892         */
01893 
01894 
01896         // 1- PARSE PCD HEADER
01897 
01898         size_t lineNum = 0;
01899         PCDheader header;
01900 
01901         string line;
01902         while (safeGetLine(is, line))
01903         {
01904 
01905                 // get rid of white spaces before/after
01906                 boost::trim (line);
01907 
01908                 // ignore comments or empty line
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                                 // DATA is the last element of the header, we exit the loop
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         // Extra check for the number of points
02066         if (header.properties.size() == 0)
02067                 throw runtime_error("PCD Parse Error: no FIELDS were find in the header");
02068 
02069 
02070         // Extra check for the number of points
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         // 2- ASSIGN PCD PROPERTIES TO DATAPOINTS ROWS
02076 
02077         // Fetch known features and descriptors
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         // Loop through all known external names (ordered list)
02087         for(size_t i=0; i<externalLabels.size(); i++)
02088         {
02089                 const SupportedLabel & supLabel = externalLabels[i];
02090 
02091                 //Search if that feature exist
02092                 for(size_t i=0; i < header.properties.size(); i++)
02093                 {
02094                         const PCDproperty prop = header.properties[i];
02095 
02096                         //TODO: prop.field == "rgb" might be tricky
02097 
02098                         if(supLabel.externalName == prop.field)
02099                         {
02100                                 header.properties[i].pmType = supLabel.type;
02101 
02102                                 // Assign rowId in that order
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                                 // we stop searching once we have a match
02131                                 break;
02132                         }
02133                 }
02134         }
02135         
02136         // loop through the remaining UNSUPPORTED labels and assigned them to a single descriptor row
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; // force descriptor
02143                         header.properties[i].pmRowID = rowIdDescriptors;
02144                         descLabelGen.add(prop.field, prop.count); // keep original name
02145                         rowIdDescriptors += prop.count;
02146                 }
02147         }
02148 
02149 
02151         // 3- RESERVE DATAPOINTS MEMORY
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         // 4- PARSE PCD DATA
02166 
02167         size_t col = 0; // point count
02168         while (safeGetLine(is, line))
02169         {
02170 
02171                 // get rid of white spaces before/after
02172                 boost::trim (line);
02173 
02174                 // ignore comments or empty line
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         // 5- ASSEMBLE FINAL DATAPOINTS
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         // Ensure homogeous coordinates
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"; // for float
02297         }
02298         ofs << "\n";
02299 
02300         ofs << "TYPE";
02301         for (int i =0; i < featCount - 1 + descCount; i++)
02302         {
02303                 ofs << " F"; // for float
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         // write points
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 


libpointmatcher
Author(s):
autogenerated on Thu Jun 20 2019 19:51:31