serialization.h
Go to the documentation of this file.
00001 #ifndef RTABMAP_SERIALIZATION_H_
00002 #define RTABMAP_SERIALIZATION_H_
00003 
00004 #include <vector>
00005 #include <map>
00006 #include <cstdlib>
00007 #include <cstring>
00008 #include <stdio.h>
00009 #include "rtflann/ext/lz4.h"
00010 #include "rtflann/ext/lz4hc.h"
00011 
00012 
00013 namespace rtflann
00014 {
00015     struct IndexHeaderStruct {
00016         char signature[24];
00017         char version[16];
00018         flann_datatype_t data_type;
00019         flann_algorithm_t index_type;
00020         size_t rows;
00021         size_t cols;
00022         size_t compression;
00023         size_t first_block_size;
00024     };
00025 
00026 namespace serialization
00027 {
00028 
00029 struct access 
00030 {
00031     template<typename Archive, typename T>
00032     static inline void serialize(Archive& ar, T& type)
00033     {
00034         type.serialize(ar);
00035     }
00036 };
00037 
00038 
00039 template<typename Archive, typename T>
00040 inline void serialize(Archive& ar, T& type)
00041 {
00042     access::serialize(ar,type);
00043 }
00044 
00045 template<typename T>
00046 struct Serializer
00047 {
00048     template<typename InputArchive>
00049     static inline void load(InputArchive& ar, T& val)
00050     {
00051         serialization::serialize(ar,val);
00052     }
00053     template<typename OutputArchive>
00054     static inline void save(OutputArchive& ar, const T& val)
00055     {
00056         serialization::serialize(ar,const_cast<T&>(val));
00057     }
00058 };
00059 
00060 #define BASIC_TYPE_SERIALIZER(type)\
00061 template<> \
00062 struct Serializer<type> \
00063 {\
00064     template<typename InputArchive>\
00065     static inline void load(InputArchive& ar, type& val)\
00066     {\
00067         ar.load(val);\
00068     }\
00069     template<typename OutputArchive>\
00070     static inline void save(OutputArchive& ar, const type& val)\
00071     {\
00072         ar.save(val);\
00073     }\
00074 }
00075 
00076 #define ENUM_SERIALIZER(type)\
00077 template<>\
00078 struct Serializer<type>\
00079 {\
00080     template<typename InputArchive>\
00081     static inline void load(InputArchive& ar, type& val)\
00082     {\
00083         int int_val;\
00084         ar & int_val;\
00085         val = (type) int_val;\
00086     }\
00087     template<typename OutputArchive>\
00088     static inline void save(OutputArchive& ar, const type& val)\
00089     {\
00090         int int_val = (int)val;\
00091         ar & int_val;\
00092     }\
00093 }
00094 
00095 
00096 // declare serializers for simple types
00097 BASIC_TYPE_SERIALIZER(char);
00098 BASIC_TYPE_SERIALIZER(unsigned char);
00099 BASIC_TYPE_SERIALIZER(short);
00100 BASIC_TYPE_SERIALIZER(unsigned short);
00101 BASIC_TYPE_SERIALIZER(int);
00102 BASIC_TYPE_SERIALIZER(unsigned int);
00103 BASIC_TYPE_SERIALIZER(long);
00104 BASIC_TYPE_SERIALIZER(unsigned long);
00105 BASIC_TYPE_SERIALIZER(unsigned long long);
00106 BASIC_TYPE_SERIALIZER(float);
00107 BASIC_TYPE_SERIALIZER(double);
00108 BASIC_TYPE_SERIALIZER(bool);
00109 #ifdef _MSC_VER
00110 // unsigned __int64 ~= unsigned long long
00111 // Will throw error on VS2013
00112 #if _MSC_VER != 1800
00113 //BASIC_TYPE_SERIALIZER(unsigned __int64);
00114 #endif
00115 #endif
00116 
00117 
00118 // serializer for std::vector
00119 template<typename T>
00120 struct Serializer<std::vector<T> >
00121 {
00122     template<typename InputArchive>
00123     static inline void load(InputArchive& ar, std::vector<T>& val)
00124     {
00125         size_t size;
00126         ar & size;
00127         val.resize(size);
00128         for (size_t i=0;i<size;++i) {
00129             ar & val[i];
00130         }
00131     }
00132 
00133     template<typename OutputArchive>
00134     static inline void save(OutputArchive& ar, const std::vector<T>& val)
00135     {
00136         ar & val.size();
00137         for (size_t i=0;i<val.size();++i) {
00138             ar & val[i];
00139         }
00140     }
00141 };
00142 
00143 // serializer for std::vector
00144 template<typename K, typename V>
00145 struct Serializer<std::map<K,V> >
00146 {
00147     template<typename InputArchive>
00148     static inline void load(InputArchive& ar, std::map<K,V>& map_val)
00149     {
00150         size_t size;
00151         ar & size;
00152         for (size_t i = 0; i < size; ++i)
00153         {
00154             K key;
00155             ar & key;
00156             V value;
00157             ar & value;
00158             map_val[key] = value;
00159         }
00160     }
00161 
00162     template<typename OutputArchive>
00163     static inline void save(OutputArchive& ar, const std::map<K,V>& map_val)
00164     {
00165         ar & map_val.size();
00166         for (typename std::map<K,V>::const_iterator i=map_val.begin(); i!=map_val.end(); ++i) {
00167             ar & i->first;
00168             ar & i->second;
00169         }
00170     }
00171 };
00172 
00173 template<typename T>
00174 struct Serializer<T*>
00175 {
00176     template<typename InputArchive>
00177     static inline void load(InputArchive& ar, T*& val)
00178     {
00179         ar.load(val);
00180     }
00181 
00182     template<typename OutputArchive>
00183     static inline void save(OutputArchive& ar,  T* const& val)
00184     {
00185         ar.save(val);
00186     }
00187 };
00188 
00189 template<typename T, int N>
00190 struct Serializer<T[N]>
00191 {
00192     template<typename InputArchive>
00193     static inline void load(InputArchive& ar, T (&val)[N])
00194     {
00195         ar.load(val);
00196     }
00197 
00198     template<typename OutputArchive>
00199     static inline void save(OutputArchive& ar,  T const (&val)[N])
00200     {
00201         ar.save(val);
00202     }
00203 };
00204 
00205 
00206 
00207 
00208 struct binary_object
00209 {
00210     void const * ptr_;
00211     size_t size_;
00212 
00213     binary_object( void * const ptr, size_t size) :
00214         ptr_(ptr),
00215         size_(size)
00216     {}
00217     binary_object(const binary_object & rhs) :
00218         ptr_(rhs.ptr_),
00219         size_(rhs.size_)
00220     {}
00221 
00222     binary_object & operator=(const binary_object & rhs) {
00223         ptr_ = rhs.ptr_;
00224         size_ = rhs.size_;
00225         return *this;
00226     }
00227 };
00228 
00229 inline const binary_object make_binary_object(/* const */ void * t, size_t size){
00230     return binary_object(t, size);
00231 }
00232 
00233 template<>
00234 struct Serializer<const binary_object>
00235 {
00236     template<typename InputArchive>
00237     static inline void load(InputArchive& ar, const binary_object& b)
00238     {
00239         ar.load_binary(const_cast<void *>(b.ptr_), b.size_);
00240     }
00241 
00242     template<typename OutputArchive>
00243     static inline void save(OutputArchive& ar,  const binary_object& b)
00244     {
00245         ar.save_binary(b.ptr_, b.size_);
00246     }
00247 };
00248 
00249 template<>
00250 struct Serializer<binary_object>
00251 {
00252     template<typename InputArchive>
00253     static inline void load(InputArchive& ar, binary_object& b)
00254     {
00255         ar.load_binary(const_cast<void *>(b.ptr_), b.size_);
00256     }
00257 
00258     template<typename OutputArchive>
00259     static inline void save(OutputArchive& ar,  const binary_object& b)
00260     {
00261         ar.save_binary(b.ptr_, b.size_);
00262     }
00263 };
00264 
00265 
00266 
00267 template <bool C_> 
00268 struct bool_ {
00269     static const bool value = C_;
00270     typedef bool value_type;
00271 };
00272 
00273 
00274 class ArchiveBase
00275 {
00276 public:
00277         void* getObject() { return object_; }
00278 
00279         void setObject(void* object) { object_ = object; }
00280 
00281 private:
00282         void* object_;
00283 };
00284 
00285 
00286 template<typename Archive>
00287 class InputArchive : public ArchiveBase
00288 {
00289 protected:
00290     InputArchive() {};
00291 public:
00292     typedef bool_<true> is_loading;
00293     typedef bool_<false> is_saving;
00294 
00295     template<typename T>
00296     Archive& operator& (T& val)
00297     {
00298         Serializer<T>::load(*static_cast<Archive*>(this),val);
00299         return *static_cast<Archive*>(this);
00300     }
00301 };
00302 
00303 
00304 template<typename Archive>
00305 class OutputArchive : public ArchiveBase
00306 {
00307 protected:
00308     OutputArchive() {};
00309 public:
00310     typedef bool_<false> is_loading;
00311     typedef bool_<true> is_saving;
00312     
00313     template<typename T>
00314     Archive& operator& (const T& val)
00315     {
00316         Serializer<T>::save(*static_cast<Archive*>(this),val);
00317         return *static_cast<Archive*>(this);
00318     }
00319 };
00320 
00321 
00322 
00323 class SizeArchive : public OutputArchive<SizeArchive>
00324 {
00325     size_t size_;
00326 public:
00327 
00328     SizeArchive() : size_(0)
00329     {
00330     }
00331 
00332     template<typename T>
00333     void save(const T& val)
00334     {
00335         size_ += sizeof(val);
00336     }
00337 
00338     template<typename T>
00339     void save_binary(T* ptr, size_t size)
00340     {
00341         size_ += size;
00342     }
00343 
00344 
00345     void reset()
00346     {
00347         size_ = 0;
00348     }
00349 
00350     size_t size()
00351     {
00352         return size_;
00353     }
00354 };
00355 
00356 
00357 //
00358 //class PrintArchive : public OutputArchive<PrintArchive>
00359 //{
00360 //public:
00361 //    template<typename T>
00362 //    void save(const T& val)
00363 //    {
00364 //        std::cout << val << std::endl;
00365 //    }
00366 //
00367 //    template<typename T>
00368 //    void save_binary(T* ptr, size_t size)
00369 //    {
00370 //        std::cout << "<binary object>" << std::endl;
00371 //    }
00372 //};
00373     
00374 #define BLOCK_BYTES (1024 * 64)
00375 
00376 class SaveArchive : public OutputArchive<SaveArchive>
00377 {
00383     FILE* stream_;
00384     bool own_stream_;
00385     char *buffer_;
00386     size_t offset_;
00387 
00388     int first_block_;
00389     char *buffer_blocks_;
00390     char *compressed_buffer_;
00391     LZ4_streamHC_t lz4Stream_body;
00392     LZ4_streamHC_t* lz4Stream;
00393 
00394     void initBlock()
00395     {
00396         // Alloc the space for both buffer blocks (each compressed block
00397         // references the previous)
00398         buffer_ = buffer_blocks_ = (char *)malloc(BLOCK_BYTES*2);
00399         compressed_buffer_ = (char *)malloc(LZ4_COMPRESSBOUND(BLOCK_BYTES) + sizeof(size_t));
00400         if (buffer_ == NULL || compressed_buffer_ == NULL) {
00401             throw FLANNException("Error allocating compression buffer");
00402         }
00403         
00404         // Init the LZ4 stream
00405         lz4Stream = &lz4Stream_body;
00406         LZ4_resetStreamHC(lz4Stream, 9);
00407         first_block_ = true;
00408         
00409         offset_ = 0;
00410     }
00411     
00412     void flushBlock()
00413     {
00414         size_t compSz = 0;
00415         // Handle header
00416         if (first_block_) {
00417             // Copy & set the header
00418             IndexHeaderStruct *head = (IndexHeaderStruct *)buffer_;
00419             size_t headSz = sizeof(IndexHeaderStruct);
00420             
00421             assert(head->compression == 0);
00422             head->compression = 1; // Bool now, enum later
00423         
00424             // Do the compression for the block
00425             compSz = LZ4_compress_HC_continue(
00426                 lz4Stream, buffer_+headSz, compressed_buffer_+headSz, offset_-headSz,
00427                 LZ4_COMPRESSBOUND(BLOCK_BYTES));
00428             
00429             if(compSz <= 0) {
00430                 throw FLANNException("Error compressing (first block)");
00431             }
00432             
00433             // Handle header
00434             head->first_block_size = compSz;
00435             memcpy(compressed_buffer_, buffer_, headSz);
00436             
00437             compSz += headSz;
00438             first_block_ = false;
00439         } else {
00440             size_t headSz = sizeof(compSz);
00441             
00442             // Do the compression for the block
00443             compSz = LZ4_compress_HC_continue(
00444                 lz4Stream, buffer_, compressed_buffer_+headSz, offset_,
00445                 LZ4_COMPRESSBOUND(BLOCK_BYTES));
00446             
00447             if(compSz <= 0) {
00448                 throw FLANNException("Error compressing");
00449             }
00450             
00451             // Save the size of the compressed block as the header
00452             memcpy(compressed_buffer_, &compSz, headSz);
00453             compSz += headSz;
00454         }
00455         
00456         // Write the compressed buffer
00457         fwrite(compressed_buffer_, compSz, 1, stream_);
00458         
00459         // Switch the buffer to the *other* block
00460         if (buffer_ == buffer_blocks_)
00461             buffer_ = &buffer_blocks_[BLOCK_BYTES];
00462         else
00463             buffer_ = buffer_blocks_;
00464         offset_ = 0;
00465     }
00466     
00467     void endBlock()
00468     {
00469         // Cleanup memory
00470         free(buffer_blocks_);
00471         buffer_blocks_ = NULL;
00472         buffer_ = NULL;
00473         free(compressed_buffer_);
00474         compressed_buffer_ = NULL;
00475         
00476         // Write a '0' size for next block
00477         size_t z = 0;
00478         fwrite(&z, sizeof(z), 1, stream_);
00479     }
00480 
00481 public:
00482     SaveArchive(const char* filename)
00483     {
00484         stream_ = fopen(filename, "wb");
00485         own_stream_ = true;
00486         initBlock();
00487     }
00488 
00489     SaveArchive(FILE* stream) : stream_(stream), own_stream_(false)
00490     {
00491         initBlock();
00492     }
00493 
00494     ~SaveArchive()
00495     {
00496         flushBlock();
00497         endBlock();
00498         if (buffer_) {
00499             free(buffer_);
00500             buffer_ = NULL;
00501         }
00502         if (own_stream_) {
00503                 fclose(stream_);
00504         }
00505     }
00506 
00507     template<typename T>
00508     void save(const T& val)
00509     {
00510         assert(sizeof(val) < BLOCK_BYTES);
00511         if (offset_+sizeof(val) > BLOCK_BYTES)
00512             flushBlock();
00513         memcpy(buffer_+offset_, &val, sizeof(val));
00514         offset_ += sizeof(val);
00515     }
00516 
00517     template<typename T>
00518     void save(T* const& val)
00519     {
00520         // don't save pointers
00521         //fwrite(&val, sizeof(val), 1, handle_);
00522     }
00523     
00524     template<typename T>
00525     void save_binary(T* ptr, size_t size)
00526     {
00527         while (size > BLOCK_BYTES) {
00528             // Flush existing block
00529             flushBlock();
00530             
00531             // Save large chunk
00532             memcpy(buffer_, ptr, BLOCK_BYTES);
00533             offset_ += BLOCK_BYTES;
00534             ptr = ((char *)ptr) + BLOCK_BYTES;
00535             size -= BLOCK_BYTES;
00536         }
00537         
00538         // Save existing block if new data will make it too big
00539         if (offset_+size > BLOCK_BYTES)
00540             flushBlock();
00541         
00542         // Copy out requested data
00543         memcpy(buffer_+offset_, ptr, size);
00544         offset_ += size;
00545     }
00546 
00547 };
00548 
00549 
00550 class LoadArchive : public InputArchive<LoadArchive>
00551 {
00557     FILE* stream_;
00558     bool own_stream_;
00559     char *buffer_;
00560     char *ptr_;
00561     
00562     char *buffer_blocks_;
00563     char *compressed_buffer_;
00564     LZ4_streamDecode_t lz4StreamDecode_body;
00565     LZ4_streamDecode_t* lz4StreamDecode;
00566     size_t block_sz_;
00567 
00568     void decompressAndLoadV10(FILE* stream)
00569     {
00570         buffer_ = NULL;
00571         
00572         // Find file size
00573         size_t pos = ftell(stream);
00574         fseek(stream, 0, SEEK_END);
00575         size_t fileSize = ftell(stream)-pos;
00576         fseek(stream, pos, SEEK_SET);
00577         size_t headSz = sizeof(IndexHeaderStruct);
00578 
00579         // Read the (compressed) file to a buffer
00580         char *compBuffer = (char *)malloc(fileSize);
00581         if (compBuffer == NULL) {
00582             throw FLANNException("Error allocating file buffer space");
00583         }
00584         if (fread(compBuffer, fileSize, 1, stream) != 1) {
00585             free(compBuffer);
00586             throw FLANNException("Invalid index file, cannot read from disk (compressed)");
00587         }
00588 
00589         // Extract header
00590         IndexHeaderStruct *head = (IndexHeaderStruct *)(compBuffer);
00591         
00592         // Backward compatability
00593         size_t compressedSz = fileSize-headSz;
00594         size_t uncompressedSz = head->first_block_size-headSz;
00595         
00596         // Check for compression type
00597         if (head->compression != 1) {
00598             free(compBuffer);
00599             throw FLANNException("Compression type not supported");
00600         }
00601         
00602         // Allocate a decompressed buffer
00603         ptr_ = buffer_ = (char *)malloc(uncompressedSz+headSz);
00604         if (buffer_ == NULL) {
00605             free(compBuffer);
00606             throw FLANNException("Error (re)allocating decompression buffer");
00607         }
00608         
00609         // Extract body
00610         size_t usedSz = LZ4_decompress_safe(compBuffer+headSz,
00611                                             buffer_+headSz,
00612                                             compressedSz,
00613                                             uncompressedSz);
00614         
00615         // Check if the decompression was the expected size.
00616         if (usedSz != uncompressedSz) {
00617             free(compBuffer);
00618             throw FLANNException("Unexpected decompression size");
00619         }
00620         
00621         // Copy header data
00622         memcpy(buffer_, compBuffer, headSz);
00623         free(compBuffer);
00624         
00625         // Put the file pointer at the end of the data we've read
00626         if (compressedSz+headSz+pos != fileSize)
00627             fseek(stream, compressedSz+headSz+pos, SEEK_SET);
00628         block_sz_ = uncompressedSz+headSz;
00629     }
00630     
00631     void initBlock(FILE *stream)
00632     {
00633         size_t pos = ftell(stream);
00634         buffer_ = NULL;
00635         buffer_blocks_ = NULL;
00636         compressed_buffer_ = NULL;
00637         size_t headSz = sizeof(IndexHeaderStruct);
00638         
00639         // Read the file header to a buffer
00640         IndexHeaderStruct *head = (IndexHeaderStruct *)malloc(headSz);
00641         if (head == NULL) {
00642             throw FLANNException("Error allocating header buffer space");
00643         }
00644         if (fread(head, headSz, 1, stream) != 1) {
00645             free(head);
00646             throw FLANNException("Invalid index file, cannot read from disk (header)");
00647         }
00648         
00649         // Backward compatability
00650         if (head->signature[13] == '1' && head->signature[15] == '0') {
00651             free(head);
00652             fseek(stream, pos, SEEK_SET);
00653             return decompressAndLoadV10(stream);
00654         }
00655         
00656         // Alloc the space for both buffer blocks (each block
00657         // references the previous)
00658         buffer_ = buffer_blocks_ = (char *)malloc(BLOCK_BYTES*2);
00659         compressed_buffer_ = (char *)malloc(LZ4_COMPRESSBOUND(BLOCK_BYTES));
00660         if (buffer_ == NULL || compressed_buffer_ == NULL) {
00661             free(head);
00662             throw FLANNException("Error allocating compression buffer");
00663         }
00664         
00665         // Init the LZ4 stream
00666         lz4StreamDecode = &lz4StreamDecode_body;
00667         LZ4_setStreamDecode(lz4StreamDecode, NULL, 0);
00668         
00669         // Read first block
00670         memcpy(buffer_, head, headSz);
00671         loadBlock(buffer_+headSz, head->first_block_size, stream);
00672         block_sz_ += headSz;
00673         ptr_ = buffer_;
00674         free(head);
00675     }
00676 
00677     void loadBlock(char* buffer_, size_t compSz, FILE* stream)
00678     {
00679         if(compSz >= LZ4_COMPRESSBOUND(BLOCK_BYTES)) {
00680             throw FLANNException("Requested block size too large");
00681         }
00682         
00683         // Read the block into the compressed buffer
00684         if (fread(compressed_buffer_, compSz, 1, stream) != 1) {
00685             throw FLANNException("Invalid index file, cannot read from disk (block)");
00686         }
00687         
00688         // Decompress into the regular buffer
00689         const int decBytes = LZ4_decompress_safe_continue(
00690             lz4StreamDecode, compressed_buffer_, buffer_, compSz, BLOCK_BYTES);
00691         if(decBytes <= 0) {
00692             throw FLANNException("Invalid index file, cannot decompress block");
00693         }
00694         block_sz_ = decBytes;
00695     }
00696     
00697     void preparePtr(size_t size)
00698     {
00699         // Return if the new size is less than (or eq) the size of a block
00700         if (ptr_+size <= buffer_+block_sz_)
00701             return;
00702         
00703         // Switch the buffer to the *other* block
00704         if (buffer_ == buffer_blocks_)
00705             buffer_ = &buffer_blocks_[BLOCK_BYTES];
00706         else
00707             buffer_ = buffer_blocks_;
00708         
00709         // Find the size of the next block
00710         size_t cmpSz = 0;
00711         size_t readCnt = fread(&cmpSz, sizeof(cmpSz), 1, stream_);
00712         if(cmpSz <= 0 || readCnt != 1) {
00713             throw FLANNException("Requested to read next block past end of file");
00714         }
00715         
00716         // Load block & init ptr
00717         loadBlock(buffer_, cmpSz, stream_);
00718         ptr_ = buffer_;
00719     }
00720     
00721     void endBlock()
00722     {
00723         // If not v1.0 format hack...
00724         if (buffer_blocks_ != NULL) {
00725             // Read the last '0' in the file
00726             size_t zero = -1;
00727             if (fread(&zero, sizeof(zero), 1, stream_) != 1) {
00728                 throw FLANNException("Invalid index file, cannot read from disk (end)");
00729             }
00730             if (zero != 0) {
00731                 throw FLANNException("Invalid index file, last block not zero length");
00732             }
00733         }
00734         
00735         // Free resources
00736         if (buffer_blocks_ != NULL) {
00737             free(buffer_blocks_);
00738             buffer_blocks_ = NULL;
00739         }
00740         if (compressed_buffer_ != NULL) {
00741             free(compressed_buffer_);
00742             compressed_buffer_ = NULL;
00743         }
00744         ptr_ = NULL;
00745     }
00746     
00747 public:
00748     LoadArchive(const char* filename)
00749     {
00750         // Open the file
00751         stream_ = fopen(filename, "rb");
00752         own_stream_ = true;
00753 
00754         initBlock(stream_);
00755     }
00756 
00757     LoadArchive(FILE* stream)
00758     {
00759         stream_ = stream;
00760         own_stream_ = false;
00761 
00762         initBlock(stream);
00763     }
00764 
00765     ~LoadArchive()
00766     {
00767         endBlock();
00768         if (own_stream_) {
00769                 fclose(stream_);
00770         }
00771     }
00772 
00773     template<typename T>
00774     void load(T& val)
00775     {
00776         preparePtr(sizeof(val));
00777         memcpy(&val, ptr_, sizeof(val));
00778         ptr_ += sizeof(val);
00779     }
00780 
00781     template<typename T>
00782     void load(T*& val)
00783     {
00784         // don't load pointers
00785         //fread(&val, sizeof(val), 1, handle_);
00786     }
00787 
00788     template<typename T>
00789     void load_binary(T* ptr, size_t size)
00790     {
00791         while (size > BLOCK_BYTES) {
00792             // Load next block
00793             preparePtr(BLOCK_BYTES);
00794             
00795             // Load large chunk
00796             memcpy(ptr, ptr_, BLOCK_BYTES);
00797             ptr_ += BLOCK_BYTES;
00798             ptr = ((char *)ptr) + BLOCK_BYTES;
00799             size -= BLOCK_BYTES;
00800         }
00801         
00802         // Load next block if needed
00803         preparePtr(size);
00804         
00805         // Load the data
00806         memcpy(ptr, ptr_, size);
00807         ptr_ += size;
00808     }
00809 };
00810 
00811 } // namespace serialization
00812 } // namespace flann
00813 #endif // SERIALIZATION_H_


rtabmap
Author(s): Mathieu Labbe
autogenerated on Thu Jun 6 2019 21:59:22