diff --git a/SuperBuild/CMakeLists.txt b/SuperBuild/CMakeLists.txt index 3b21da21..1aee12c3 100644 --- a/SuperBuild/CMakeLists.txt +++ b/SuperBuild/CMakeLists.txt @@ -2,6 +2,11 @@ cmake_minimum_required(VERSION 3.1) project(ODM-SuperBuild) +if (NOT CMAKE_BUILD_TYPE) + message(STATUS "No build type selected, default to Release") + set(CMAKE_BUILD_TYPE "Release") +endif() + # Setup SuperBuild root location set(SB_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index e1afd164..5786f461 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -1,3 +1,8 @@ +if (NOT CMAKE_BUILD_TYPE) + message(STATUS "No build type selected, default to Release") + set(CMAKE_BUILD_TYPE "Release") +endif() + # Add ODM sub-modules add_subdirectory(odm_extract_utm) add_subdirectory(odm_georef) diff --git a/modules/odm_georef/src/Georef.cpp b/modules/odm_georef/src/Georef.cpp index 2597e257..32fbafc0 100644 --- a/modules/odm_georef/src/Georef.cpp +++ b/modules/odm_georef/src/Georef.cpp @@ -13,6 +13,8 @@ using namespace std; // This #include "Georef.hpp" +#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100) + std::ostream& operator<<(std::ostream &os, const GeorefSystem &geo) { return os << setiosflags(ios::fixed) << setprecision(7) << geo.system_ << "\n" << geo.eastingOffset_ << " " << geo.northingOffset_; @@ -1404,98 +1406,74 @@ void Georef::performFinalTransform(Mat4 &transMat, pcl::TextureMesh &mesh, pcl:: template void Georef::transformPointCloud(const char *inputFile, const Eigen::Transform &transform, const char *outputFile){ try{ - std::ifstream ss(inputFile, std::ios::binary); - if (ss.fail()) throw GeorefException("Error when reading point cloud:\n" + std::string(inputFile) + "\n"); - PlyFile file; - - file.parse_header(ss); - - std::shared_ptr vertices = file.request_properties_from_element("vertex", { "x", "y", "z" }); - // std::shared_ptr normals; - std::shared_ptr colors; - - // Not all point clouds have normals and colors - // and different naming conventions apply - // try{ - // normals = file.request_properties_from_element("vertex", { "nx", "ny", "nz" }); - // }catch(const std::exception &){} - - // if (!normals){ - // try{ - // normals = file.request_properties_from_element("vertex", { "normal_x", "normal_y", "normal_z" }); - // }catch(const std::exception &){} - // } - - try{ - colors = file.request_properties_from_element("vertex", { "diffuse_red", "diffuse_green", "diffuse_blue" }); - }catch(const std::exception &){} - - if (!colors){ - try{ - colors = file.request_properties_from_element("vertex", { "red", "green", "blue" }); - }catch(const std::exception &){} + pcl::PointCloud pointCloud; + if(pcl::io::loadPLYFile (inputFile, pointCloud) == -1) { + throw GeorefException("Error when reading point cloud:\n" + std::string(inputFile) + "\n"); } + else + { + log_ << "Successfully loaded " << pointCloud.size() << " points with corresponding normals from file.\n"; + } + log_ << "Writing transformed point cloud to " << outputFile << "...\n"; - file.read(ss); - log_ << "Successfully loaded " << vertices->count << " points with corresponding normals from file.\n"; + // We don't use PCL's built-in functions + // because PCL does not support double coordinates + // precision + std::ofstream f (outputFile); + f << "ply" << std::endl; - const size_t numVerticesBytes = vertices->buffer.size_bytes(); - - struct float3 { float x, y, z; }; - struct double3 { double x, y, z; }; - - std::vector verts(vertices->count); - - if (vertices->t == tinyply::Type::FLOAT32) { - std::vector floatVerts(vertices->count); - std::memcpy(floatVerts.data(), vertices->buffer.get(), numVerticesBytes); - // Copy and cast to double - for (unsigned int i = 0; i < vertices->count; i++){ - verts[i].x = static_cast(floatVerts[i].x); - verts[i].y = static_cast(floatVerts[i].y); - verts[i].z = static_cast(floatVerts[i].z); - } - }else if (vertices->t == tinyply::Type::FLOAT64) { - std::memcpy(verts.data(), vertices->buffer.get(), numVerticesBytes); + if (IS_BIG_ENDIAN){ + f << "format binary_big_endian 1.0" << std::endl; }else{ - GeorefException ("Invalid data type (only float32 and float64 are supported): " + std::to_string((int)vertices->t)); + f << "format binary_little_endian 1.0" << std::endl; } + const char *type = "double"; + if (sizeof(Scalar) == sizeof(float)){ + type = "float"; + } + + f << "element vertex " << pointCloud.size() << std::endl + << "property " << type << " x" << std::endl + << "property " << type << " y" << std::endl + << "property " << type << " z" << std::endl + << "property uchar red" << std::endl + << "property uchar green" << std::endl + << "property uchar blue" << std::endl + << "end_header" << std::endl; + + struct PlyPoint{ + Scalar x; + Scalar y; + Scalar z; + uint8_t r; + uint8_t g; + uint8_t b; + } p; + size_t psize = sizeof(Scalar) * 3 + sizeof(uint8_t) * 3; + // Transform - for (unsigned int i = 0; i < verts.size(); i++){ - Scalar x = verts[i].x; - Scalar y = verts[i].y; - Scalar z = verts[i].z; + for (unsigned int i = 0; i < pointCloud.size(); i++){ + Scalar x = static_cast(pointCloud[i].x); + Scalar y = static_cast(pointCloud[i].y); + Scalar z = static_cast(pointCloud[i].z); + p.r = pointCloud[i].r; + p.g = pointCloud[i].g; + p.b = pointCloud[i].b; - verts[i].x = static_cast (transform (0, 0) * x + transform (0, 1) * y + transform (0, 2) * z + transform (0, 3)); - verts[i].y = static_cast (transform (1, 0) * x + transform (1, 1) * y + transform (1, 2) * z + transform (1, 3)); - verts[i].z = static_cast (transform (2, 0) * x + transform (2, 1) * y + transform (2, 2) * z + transform (2, 3)); + p.x = static_cast (transform (0, 0) * x + transform (0, 1) * y + transform (0, 2) * z + transform (0, 3)); + p.y = static_cast (transform (1, 0) * x + transform (1, 1) * y + transform (1, 2) * z + transform (1, 3)); + p.z = static_cast (transform (2, 0) * x + transform (2, 1) * y + transform (2, 2) * z + transform (2, 3)); + + f.write(reinterpret_cast(&p), psize); // TODO: normals can be computed using the inverse transpose // https://paroj.github.io/gltut/Illumination/Tut09%20Normal%20Transformation.html } - log_ << '\n'; - log_ << "Saving point cloud file to \'" << outputFile << "\'...\n"; + f.close(); - // Save - std::filebuf fb; - fb.open(outputFile, std::ios::out | std::ios::binary); - std::ostream outputStream(&fb); - - outputStream << std::setprecision(12); - - PlyFile outFile; - outFile.add_properties_to_element("vertex", { "x", "y", "z" }, Type::FLOAT64, verts.size() * 3, reinterpret_cast(verts.data()), Type::INVALID, 0); - // if (normals) outFile.add_properties_to_element("vertex", { "nx", "ny", "nz" }, Type::FLOAT32, verts.size() * 3, reinterpret_cast(normals->buffer.get()), Type::INVALID, 0); - if (colors) outFile.add_properties_to_element("vertex", { "red", "green", "blue" }, Type::UINT8, verts.size() * 3, reinterpret_cast(colors->buffer.get()), Type::INVALID, 0); - outFile.get_comments().push_back("generated by OpenDroneMap"); - - outFile.write(outputStream, true); - - fb.close(); - - log_ << ".. point cloud file saved.\n"; + log_ << "Point cloud file saved.\n"; } catch (const std::exception & e) { diff --git a/modules/odm_georef/src/Georef.hpp b/modules/odm_georef/src/Georef.hpp index a8eec990..d76f3d64 100644 --- a/modules/odm_georef/src/Georef.hpp +++ b/modules/odm_georef/src/Georef.hpp @@ -8,12 +8,10 @@ // PCL #include #include +#include // Modified PCL #include "modifiedPclFunctions.hpp" -#include "tinyply.h" -using namespace tinyply; - // Logger #include "Logger.hpp" diff --git a/modules/odm_georef/src/tinyply.cpp b/modules/odm_georef/src/tinyply.cpp deleted file mode 100644 index 8f0d78e3..00000000 --- a/modules/odm_georef/src/tinyply.cpp +++ /dev/null @@ -1,621 +0,0 @@ -// This software is in the public domain. Where that dedication is not -// recognized, you are granted a perpetual, irrevocable license to copy, -// distribute, and modify this file as you see fit. -// Authored in 2015 by Dimitri Diakopoulos (http://www.dimitridiakopoulos.com) -// https://github.com/ddiakopoulos/tinyply -// Version 2.0 - -#include "tinyply.h" -#include -#include -#include -#include -#include - -using namespace tinyply; -using namespace std; - -////////////////// -// Endian Utils // -////////////////// - -template T endian_swap(const T & v) { return v; } -template<> inline uint16_t endian_swap(const uint16_t & v) { return (v << 8) | (v >> 8); } -template<> inline uint32_t endian_swap(const uint32_t & v) { return (v << 24) | ((v << 8) & 0x00ff0000) | ((v >> 8) & 0x0000ff00) | (v >> 24); } -template<> inline uint64_t endian_swap(const uint64_t & v) -{ - return (((v & 0x00000000000000ffLL) << 56) | - ((v & 0x000000000000ff00LL) << 40) | - ((v & 0x0000000000ff0000LL) << 24) | - ((v & 0x00000000ff000000LL) << 8) | - ((v & 0x000000ff00000000LL) >> 8) | - ((v & 0x0000ff0000000000LL) >> 24) | - ((v & 0x00ff000000000000LL) >> 40) | - ((v & 0xff00000000000000LL) >> 56)); -} -template<> inline int16_t endian_swap(const int16_t & v) { uint16_t r = endian_swap(*(uint16_t*)&v); return *(int16_t*)&r; } -template<> inline int32_t endian_swap(const int32_t & v) { uint32_t r = endian_swap(*(uint32_t*)&v); return *(int32_t*)&r; } -template<> inline int64_t endian_swap(const int64_t & v) { uint64_t r = endian_swap(*(uint64_t*)&v); return *(int64_t*)&r; } -inline float endian_swap_float(const uint32_t & v) { union { float f; uint32_t i; }; i = endian_swap(v); return f; } -inline double endian_swap_double(const uint64_t & v) { union { double d; uint64_t i; }; i = endian_swap(v); return d; } - -///////////////////////////// -// Internal Implementation // -///////////////////////////// - -inline Type property_type_from_string(const std::string & t) -{ - if (t == "int8" || t == "char") return Type::INT8; - else if (t == "uint8" || t == "uchar") return Type::UINT8; - else if (t == "int16" || t == "short") return Type::INT16; - else if (t == "uint16" || t == "ushort") return Type::UINT16; - else if (t == "int32" || t == "int") return Type::INT32; - else if (t == "uint32" || t == "uint") return Type::UINT32; - else if (t == "float32" || t == "float") return Type::FLOAT32; - else if (t == "float64" || t == "double") return Type::FLOAT64; - return Type::INVALID; -} - -struct PlyFile::PlyFileImpl -{ - struct PlyCursor - { - size_t byteOffset; - size_t totalSizeBytes; - }; - - struct ParsingHelper - { - std::shared_ptr data; - std::shared_ptr cursor; - }; - - std::map userData; - - bool isBinary = false; - bool isBigEndian = false; - std::vector elements; - std::vector comments; - std::vector objInfo; - - void read(std::istream & is); - void write(std::ostream & os, bool isBinary); - - std::shared_ptr request_properties_from_element(const std::string & elementKey, const std::initializer_list propertyKeys); - void add_properties_to_element(const std::string & elementKey, const std::initializer_list propertyKeys, const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount); - - size_t read_property_binary(const Type t, void * dest, size_t & destOffset, std::istream & is); - size_t read_property_ascii(const Type t, void * dest, size_t & destOffset, std::istream & is); - size_t skip_property_binary(const PlyProperty & property, std::istream & is); - size_t skip_property_ascii(const PlyProperty & property, std::istream & is); - - bool parse_header(std::istream & is); - void parse_data(std::istream & is, bool firstPass); - void read_header_format(std::istream & is); - void read_header_element(std::istream & is); - void read_header_property(std::istream & is); - void read_header_text(std::string line, std::vector & place, int erase = 0); - - void write_header(std::ostream & os); - void write_ascii_internal(std::ostream & os); - void write_binary_internal(std::ostream & os); - void write_property_ascii(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset); - void write_property_binary(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset); -}; - -////////////////// -// PLY Property // -////////////////// - -PlyProperty::PlyProperty(std::istream & is) : isList(false) -{ - std::string type; - is >> type; - if (type == "list") - { - std::string countType; - is >> countType >> type; - listType = property_type_from_string(countType); - isList = true; - } - propertyType = property_type_from_string(type); - is >> name; -} - -///////////////// -// PLY Element // -///////////////// - -PlyElement::PlyElement(std::istream & is) -{ - is >> name >> size; -} - -/////////// -// Utils // -/////////// - -std::string make_key(const std::string & a, const std::string & b) -{ - return (a + "-" + b); -} - -template void ply_cast(void * dest, const char * src, bool be) -{ - *(static_cast(dest)) = (be) ? endian_swap(*(reinterpret_cast(src))) : *(reinterpret_cast(src)); -} - -template void ply_cast_float(void * dest, const char * src, bool be) -{ - *(static_cast(dest)) = (be) ? endian_swap_float(*(reinterpret_cast(src))) : *(reinterpret_cast(src)); -} - -template void ply_cast_double(void * dest, const char * src, bool be) -{ - *(static_cast(dest)) = (be) ? endian_swap_double(*(reinterpret_cast(src))) : *(reinterpret_cast(src)); -} - -template T ply_read_ascii(std::istream & is) -{ - T data; - is >> data; - return data; -} - -template void ply_cast_ascii(void * dest, std::istream & is) -{ - *(static_cast(dest)) = ply_read_ascii(is); -} - -size_t find_element(const std::string & key, const std::vector & list) -{ - for (size_t i = 0; i < list.size(); i++) if (list[i].name == key) return i; - return -1; -} - -size_t find_property(const std::string & key, const std::vector & list) -{ - for (size_t i = 0; i < list.size(); ++i) if (list[i].name == key) return i; - return -1; -} - -////////////// -// PLY File // -////////////// - -bool PlyFile::PlyFileImpl::parse_header(std::istream & is) -{ - std::string line; - while (std::getline(is, line)) - { - std::istringstream ls(line); - std::string token; - ls >> token; - if (token == "ply" || token == "PLY" || token == "") continue; - else if (token == "comment") read_header_text(line, comments, 8); - else if (token == "format") read_header_format(ls); - else if (token == "element") read_header_element(ls); - else if (token == "property") read_header_property(ls); - else if (token == "obj_info") read_header_text(line, objInfo, 9); - else if (token == "end_header") break; - else return false; - } - return true; -} - -void PlyFile::PlyFileImpl::read_header_text(std::string line, std::vector& place, int erase) -{ - place.push_back((erase > 0) ? line.erase(0, erase) : line); -} - -void PlyFile::PlyFileImpl::read_header_format(std::istream & is) -{ - std::string s; - (is >> s); - if (s == "binary_little_endian") isBinary = true; - else if (s == "binary_big_endian") isBinary = isBigEndian = true; -} - -void PlyFile::PlyFileImpl::read_header_element(std::istream & is) -{ - elements.emplace_back(is); -} - -void PlyFile::PlyFileImpl::read_header_property(std::istream & is) -{ - elements.back().properties.emplace_back(is); -} - -size_t PlyFile::PlyFileImpl::skip_property_binary(const PlyProperty & p, std::istream & is) -{ - static std::vector skip(PropertyTable[p.propertyType].stride); - if (p.isList) - { - size_t listSize = 0; - size_t dummyCount = 0; - read_property_binary(p.listType, &listSize, dummyCount, is); - for (size_t i = 0; i < listSize; ++i) is.read(skip.data(), PropertyTable[p.propertyType].stride); - return listSize * PropertyTable[p.propertyType].stride; // in bytes - } - else - { - is.read(skip.data(), PropertyTable[p.propertyType].stride); - return PropertyTable[p.propertyType].stride; - } -} - -size_t PlyFile::PlyFileImpl::skip_property_ascii(const PlyProperty & p, std::istream & is) -{ - std::string skip; - if (p.isList) - { - size_t listSize = 0; - size_t dummyCount = 0; - read_property_ascii(p.listType, &listSize, dummyCount, is); - for (size_t i = 0; i < listSize; ++i) is >> skip; - return listSize * PropertyTable[p.propertyType].stride; // in bytes - } - else - { - is >> skip; - return PropertyTable[p.propertyType].stride; - } -} - -size_t PlyFile::PlyFileImpl::read_property_binary(const Type t, void * dest, size_t & destOffset, std::istream & is) -{ - destOffset += PropertyTable[t].stride; - - std::vector src(PropertyTable[t].stride); - is.read(src.data(), PropertyTable[t].stride); - - switch (t) - { - case Type::INT8: ply_cast(dest, src.data(), isBigEndian); break; - case Type::UINT8: ply_cast(dest, src.data(), isBigEndian); break; - case Type::INT16: ply_cast(dest, src.data(), isBigEndian); break; - case Type::UINT16: ply_cast(dest, src.data(), isBigEndian); break; - case Type::INT32: ply_cast(dest, src.data(), isBigEndian); break; - case Type::UINT32: ply_cast(dest, src.data(), isBigEndian); break; - case Type::FLOAT32: ply_cast_float(dest, src.data(), isBigEndian); break; - case Type::FLOAT64: ply_cast_double(dest, src.data(), isBigEndian); break; - case Type::INVALID: throw std::invalid_argument("invalid ply property"); - } - - return PropertyTable[t].stride; -} - -size_t PlyFile::PlyFileImpl::read_property_ascii(const Type t, void * dest, size_t & destOffset, std::istream & is) -{ - destOffset += PropertyTable[t].stride; - - switch (t) - { - case Type::INT8: *((int8_t *)dest) = ply_read_ascii(is); break; - case Type::UINT8: *((uint8_t *)dest) = ply_read_ascii(is); break; - case Type::INT16: ply_cast_ascii(dest, is); break; - case Type::UINT16: ply_cast_ascii(dest, is); break; - case Type::INT32: ply_cast_ascii(dest, is); break; - case Type::UINT32: ply_cast_ascii(dest, is); break; - case Type::FLOAT32: ply_cast_ascii(dest, is); break; - case Type::FLOAT64: ply_cast_ascii(dest, is); break; - case Type::INVALID: throw std::invalid_argument("invalid ply property"); - } - return PropertyTable[t].stride; -} - -void PlyFile::PlyFileImpl::write_property_ascii(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset) -{ - switch (t) - { - case Type::INT8: os << static_cast(*reinterpret_cast(src)); break; - case Type::UINT8: os << static_cast(*reinterpret_cast(src)); break; - case Type::INT16: os << *reinterpret_cast(src); break; - case Type::UINT16: os << *reinterpret_cast(src); break; - case Type::INT32: os << *reinterpret_cast(src); break; - case Type::UINT32: os << *reinterpret_cast(src); break; - case Type::FLOAT32: os << *reinterpret_cast(src); break; - case Type::FLOAT64: os << *reinterpret_cast(src); break; - case Type::INVALID: throw std::invalid_argument("invalid ply property"); - } - os << " "; - srcOffset += PropertyTable[t].stride; -} - -void PlyFile::PlyFileImpl::write_property_binary(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset) -{ - os.write((char *)src, PropertyTable[t].stride); - srcOffset += PropertyTable[t].stride; -} - -void PlyFile::PlyFileImpl::read(std::istream & is) -{ - // Parse but only get the data size - parse_data(is, true); - - std::vector> buffers; - for (auto & entry : userData) buffers.push_back(entry.second.data); - - // Since group-requested properties share the same cursor, we need to find unique cursors so we only allocate once - std::sort(buffers.begin(), buffers.end()); - buffers.erase(std::unique(buffers.begin(), buffers.end()), buffers.end()); - - // Not great, but since we sorted by ptrs on PlyData, need to remap back onto its cursor in the userData table - for (auto & b : buffers) - { - for (auto & entry : userData) - { - if (entry.second.data == b && b->buffer.get() == nullptr) - { - b->buffer = Buffer(entry.second.cursor->totalSizeBytes); - } - } - } - - // Populate the data - parse_data(is, false); -} - -void PlyFile::PlyFileImpl::write(std::ostream & os, bool _isBinary) -{ - if (_isBinary) write_binary_internal(os); - else write_ascii_internal(os); -} - -void PlyFile::PlyFileImpl::write_binary_internal(std::ostream & os) -{ - isBinary = true; - write_header(os); - - for (auto & e : elements) - { - for (size_t i = 0; i < e.size; ++i) - { - for (auto & p : e.properties) - { - auto & helper = userData[make_key(e.name, p.name)]; - if (p.isList) - { - uint8_t listSize[4] = {0, 0, 0, 0}; - std::memcpy(listSize, &p.listCount, sizeof(uint32_t)); - size_t dummyCount = 0; - write_property_binary(p.listType, os, listSize, dummyCount); - for (int j = 0; j < p.listCount; ++j) - { - write_property_binary(p.propertyType, os, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset); - } - } - else - { - write_property_binary(p.propertyType, os, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset); - } - } - } - } -} - -void PlyFile::PlyFileImpl::write_ascii_internal(std::ostream & os) -{ - write_header(os); - - for (auto & e : elements) - { - for (size_t i = 0; i < e.size; ++i) - { - for (auto & p : e.properties) - { - auto & helper = userData[make_key(e.name, p.name)]; - if (p.isList) - { - os << p.listCount << " "; - for (int j = 0; j < p.listCount; ++j) - { - write_property_ascii(p.propertyType, os, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset); - } - } - else - { - write_property_ascii(p.propertyType, os, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset); - } - } - os << "\n"; - } - } -} - -void PlyFile::PlyFileImpl::write_header(std::ostream & os) -{ - const std::locale & fixLoc = std::locale("C"); - os.imbue(fixLoc); - - os << "ply\n"; - if (isBinary) os << ((isBigEndian) ? "format binary_big_endian 1.0" : "format binary_little_endian 1.0") << "\n"; - else os << "format ascii 1.0\n"; - - for (const auto & comment : comments) os << "comment " << comment << "\n"; - - for (auto & e : elements) - { - os << "element " << e.name << " " << e.size << "\n"; - for (const auto & p : e.properties) - { - if (p.isList) - { - os << "property list " << PropertyTable[p.listType].str << " " - << PropertyTable[p.propertyType].str << " " << p.name << "\n"; - } - else - { - os << "property " << PropertyTable[p.propertyType].str << " " << p.name << "\n"; - } - } - } - os << "end_header\n"; -} - -// Returns the size (in bytes) -std::shared_ptr PlyFile::PlyFileImpl::request_properties_from_element(const std::string & elementKey, const std::initializer_list propertyKeys) -{ - // All requested properties in the userDataTable share the same cursor (thrown into the same flat array) - ParsingHelper helper; - helper.data = std::make_shared(); - helper.data->count = 0; - helper.data->t = Type::INVALID; - helper.cursor = std::make_shared(); - helper.cursor->byteOffset = 0; - helper.cursor->totalSizeBytes = 0; - - if (elements.size() == 0) throw std::runtime_error("parsed header had no elements defined. malformed file?"); - if (!propertyKeys.size()) throw std::invalid_argument("`propertyKeys` argument is empty"); - if (elementKey.size() == 0) throw std::invalid_argument("`elementKey` argument is empty"); - - const int elementIndex = find_element(elementKey, elements); - - // Sanity check if the user requested element is in the pre-parsed header - if (elementIndex >= 0) - { - // We found the element - const PlyElement & element = elements[elementIndex]; - - helper.data->count = element.size; - - // Find each of the keys - for (auto key : propertyKeys) - { - const int propertyIndex = find_property(key, element.properties); - - if (propertyIndex >= 0) - { - // We found the property - const PlyProperty & property = element.properties[propertyIndex]; - - helper.data->t = property.propertyType; // hmm.... - - auto result = userData.insert(std::pair(make_key(element.name, property.name), helper)); - if (result.second == false) throw std::invalid_argument("element-property key has already been requested: " + make_key(element.name, property.name)); - } - else throw std::invalid_argument("one of the property keys was not found in the header: " + key); - } - } - else throw std::invalid_argument("the element key was not found in the header: " + elementKey); - - return helper.data; -} - -void PlyFile::PlyFileImpl::add_properties_to_element(const std::string & elementKey, const std::initializer_list propertyKeys, const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount) -{ - ParsingHelper helper; - helper.data = std::make_shared(); - helper.data->count = count; - helper.data->t = type; - helper.data->buffer = Buffer(data); - helper.cursor = std::make_shared(); - helper.cursor->byteOffset = 0; - helper.cursor->totalSizeBytes = 0; - - auto create_property_on_element = [&](PlyElement & e) - { - for (auto key : propertyKeys) - { - PlyProperty newProp = (listType == Type::INVALID) ? PlyProperty(type, key) : PlyProperty(listType, type, key, listCount); - /* auto result = */userData.insert(std::pair(make_key(elementKey, key), helper)); - e.properties.push_back(newProp); - } - }; - - int idx = find_element(elementKey, elements); - if (idx >= 0) - { - PlyElement & e = elements[idx]; - create_property_on_element(e); - } - else - { - PlyElement newElement = (listType == Type::INVALID) ? PlyElement(elementKey, count / propertyKeys.size()) : PlyElement(elementKey, count / listCount); - create_property_on_element(newElement); - elements.push_back(newElement); - } -} - -void PlyFile::PlyFileImpl::parse_data(std::istream & is, bool firstPass) -{ - std::function read; - std::function skip; - - const auto start = is.tellg(); - - if (isBinary) - { - read = [&](const Type t, void * dest, size_t & destOffset, std::istream & _is) { return read_property_binary(t, dest, destOffset, _is); }; - skip = [&](const PlyProperty & p, std::istream & _is) { return skip_property_binary(p, _is); }; - } - else - { - read = [&](const Type t, void * dest, size_t & destOffset, std::istream & _is) { return read_property_ascii(t, dest, destOffset, _is); }; - skip = [&](const PlyProperty & p, std::istream & _is) { return skip_property_ascii(p, _is); }; - } - - for (auto & element : elements) - { - for (size_t count = 0; count < element.size; ++count) - { - for (auto & property : element.properties) - { - auto cursorIt = userData.find(make_key(element.name, property.name)); - if (cursorIt != userData.end()) - { - auto & helper = cursorIt->second; - if (!firstPass) - { - if (property.isList) - { - size_t listSize = 0; - size_t dummyCount = 0; - read(property.listType, &listSize, dummyCount, is); - for (size_t i = 0; i < listSize; ++i) - { - read(property.propertyType, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset, is); - } - } - else - { - read(property.propertyType, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset, is); - } - } - else - { - helper.cursor->totalSizeBytes += skip(property, is); - } - } - else - { - skip(property, is); - } - } - } - } - - // Reset istream reader to the beginning - if (firstPass) is.seekg(start, is.beg); -} - -/////////////////////////////////// -// Pass-Through Public Interface // -/////////////////////////////////// - -PlyFile::PlyFile() { impl.reset(new PlyFileImpl()); }; -PlyFile::~PlyFile() { }; -bool PlyFile::parse_header(std::istream & is) { return impl->parse_header(is); } -void PlyFile::read(std::istream & is) { return impl->read(is); } -void PlyFile::write(std::ostream & os, bool isBinary) { return impl->write(os, isBinary); } -std::vector PlyFile::get_elements() const { return impl->elements; } -std::vector & PlyFile::get_comments() { return impl->comments; } -std::vector PlyFile::get_info() const { return impl->objInfo; } -std::shared_ptr PlyFile::request_properties_from_element(const std::string & elementKey, const std::initializer_list propertyKeys) -{ - return impl->request_properties_from_element(elementKey, propertyKeys); -} -void PlyFile::add_properties_to_element(const std::string & elementKey, const std::initializer_list propertyKeys, const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount) -{ - return impl->add_properties_to_element(elementKey, propertyKeys, type, count, data, listType, listCount); -} diff --git a/modules/odm_georef/src/tinyply.h b/modules/odm_georef/src/tinyply.h deleted file mode 100644 index 08fedf6f..00000000 --- a/modules/odm_georef/src/tinyply.h +++ /dev/null @@ -1,119 +0,0 @@ -// This software is in the public domain. Where that dedication is not -// recognized, you are granted a perpetual, irrevocable license to copy, -// distribute, and modify this file as you see fit. -// Authored in 2015 by Dimitri Diakopoulos (http://www.dimitridiakopoulos.com) -// https://github.com/ddiakopoulos/tinyply -// Version 2.0 - -#ifndef tinyply_h -#define tinyply_h - -#include -#include -#include -#include -#include -#include - -namespace tinyply -{ - - enum class Type : uint8_t - { - INVALID, - INT8, - UINT8, - INT16, - UINT16, - INT32, - UINT32, - FLOAT32, - FLOAT64 - }; - - struct PropertyInfo - { - int stride; - std::string str; - }; - - static std::map PropertyTable - { - { Type::INT8,{ 1, "char" } }, - { Type::UINT8,{ 1, "uchar" } }, - { Type::INT16,{ 2, "short" } }, - { Type::UINT16,{ 2, "ushort" } }, - { Type::INT32,{ 4, "int" } }, - { Type::UINT32,{ 4, "uint" } }, - { Type::FLOAT32,{ 4, "float" } }, - { Type::FLOAT64,{ 8, "double" } }, - { Type::INVALID,{ 0, "INVALID" } } - }; - - class Buffer - { - uint8_t * alias{ nullptr }; - struct delete_array { void operator()(uint8_t * p) { delete[] p; } }; - std::unique_ptr data; - size_t size; - public: - Buffer() {}; - Buffer(const size_t size) : data(new uint8_t[size], delete_array()), size(size) { alias = data.get(); } // allocating - Buffer(uint8_t * ptr) { alias = ptr; } // non-allocating, fixme: set size? - uint8_t * get() { return alias; } - size_t size_bytes() const { return size; } - }; - - struct PlyData - { - Type t; - size_t count; - Buffer buffer; - }; - - struct PlyProperty - { - PlyProperty(std::istream & is); - PlyProperty(Type type, std::string & _name) : name(_name), propertyType(type) {} - PlyProperty(Type list_type, Type prop_type, std::string & _name, int list_count) : name(_name), propertyType(prop_type), isList(true), listType(list_type), listCount(list_count) {} - std::string name; - Type propertyType; - bool isList{ false }; - Type listType{ Type::INVALID }; - int listCount{ 0 }; - }; - - struct PlyElement - { - PlyElement(std::istream & istream); - PlyElement(const std::string & _name, size_t count) : name(_name), size(count) {} - std::string name; - size_t size; - std::vector properties; - }; - - struct PlyFile - { - struct PlyFileImpl; - std::unique_ptr impl; - - PlyFile(); - ~PlyFile(); - - bool parse_header(std::istream & is); - - void read(std::istream & is); - - void write(std::ostream & os, bool isBinary); - - std::vector get_elements() const; - std::vector & get_comments(); - std::vector get_info() const; - - std::shared_ptr request_properties_from_element(const std::string & elementKey, const std::initializer_list propertyKeys); - void add_properties_to_element(const std::string & elementKey, const std::initializer_list propertyKeys, const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount); - }; - -} // namesapce tinyply - -#endif // tinyply_h diff --git a/opendm/config.py b/opendm/config.py index 52a1f5df..15d0e367 100644 --- a/opendm/config.py +++ b/opendm/config.py @@ -334,6 +334,11 @@ def config(): 'Default: ' '%(default)s') + parser.add_argument('--pc-csv', + action='store_true', + default=False, + help='Export the georeferenced point cloud in CSV format. Default: %(default)s') + parser.add_argument('--texturing-data-term', metavar='', default='gmi', diff --git a/opendm/types.py b/opendm/types.py index 2339c872..271660d5 100644 --- a/opendm/types.py +++ b/opendm/types.py @@ -434,7 +434,6 @@ class ODM_Tree(object): self.opensfm_bundle_list = io.join_paths(self.opensfm, 'list_r000.out') self.opensfm_image_list = io.join_paths(self.opensfm, 'image_list.txt') self.opensfm_reconstruction = io.join_paths(self.opensfm, 'reconstruction.json') - self.opensfm_reconstruction_meshed = io.join_paths(self.opensfm, 'reconstruction.meshed.json') self.opensfm_reconstruction_nvm = io.join_paths(self.opensfm, 'reconstruction.nvm') self.opensfm_model = io.join_paths(self.opensfm, 'depthmaps/merged.ply') self.opensfm_transformation = io.join_paths(self.opensfm, 'geocoords_transformation.txt') diff --git a/scripts/odm_georeferencing.py b/scripts/odm_georeferencing.py index 0c7263bc..054ba278 100644 --- a/scripts/odm_georeferencing.py +++ b/scripts/odm_georeferencing.py @@ -1,6 +1,7 @@ import ecto import csv import os +import struct from opendm import io from opendm import log @@ -143,21 +144,29 @@ class ODMGeoreferencingCell(ecto.Cell): reconstruction.georef = geo_ref # XYZ point cloud output - log.ODM_INFO("Creating geo-referenced CSV file (XYZ format)") - with open(tree.odm_georeferencing_xyz_file, "wb") as csvfile: - csvfile_writer = csv.writer(csvfile, delimiter=",") - reachedpoints = False - with open(odm_georeferencing_model_ply_geo) as f: - for lineNumber, line in enumerate(f): - if reachedpoints: - tokens = line.split(" ") + if args.pc_csv: + log.ODM_INFO("Creating geo-referenced CSV file (XYZ format)") + with open(tree.odm_georeferencing_xyz_file, "wb") as csvfile: + csvfile_writer = csv.writer(csvfile, delimiter=",") + with open(odm_georeferencing_model_ply_geo) as f: + endianess = '<' # little endian + while True: + line = f.readline() + if "binary_big_endian" in line: + endianess = '>' + if line.startswith("end_header"): + break + + fmt = '{}dddBBB'.format(endianess) + while True: + chunk = f.read(27) # 3 doubles, 3 uints + if len(chunk) < 27: + break + tokens = struct.unpack(fmt, chunk) csv_line = [float(tokens[0]), float(tokens[1]), tokens[2]] csvfile_writer.writerow(csv_line) - if line.startswith("end_header"): - reachedpoints = True - csvfile.close() if args.crop > 0: log.ODM_INFO("Calculating cropping area and generating bounds shapefile from point cloud") diff --git a/scripts/run_opensfm.py b/scripts/run_opensfm.py index af31d572..2f02a101 100644 --- a/scripts/run_opensfm.py +++ b/scripts/run_opensfm.py @@ -136,13 +136,6 @@ class ODMOpenSfMCell(ecto.Cell): log.ODM_WARNING('Found a valid OpenSfM reconstruction file in: %s' % tree.opensfm_reconstruction) - if not io.file_exists(tree.opensfm_reconstruction_meshed) or rerun_cell: - system.run('PYTHONPATH=%s %s/bin/opensfm mesh %s' % - (context.pyopencv_path, context.opensfm_path, tree.opensfm)) - else: - log.ODM_WARNING('Found a valid OpenSfM meshed reconstruction file in: %s' % - tree.opensfm_reconstruction_meshed) - if not args.use_pmvs: if not io.file_exists(tree.opensfm_reconstruction_nvm) or rerun_cell: system.run('PYTHONPATH=%s %s/bin/opensfm export_visualsfm %s' %