| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include "util/ply.h" |
| |
|
| | #include <fstream> |
| |
|
| | #include <Eigen/Core> |
| |
|
| | #include "util/logging.h" |
| | #include "util/misc.h" |
| |
|
| | namespace colmap { |
| |
|
| | std::vector<PlyPoint> ReadPly(const std::string& path) { |
| | std::ifstream file(path, std::ios::binary); |
| | CHECK(file.is_open()) << path; |
| |
|
| | std::vector<PlyPoint> points; |
| |
|
| | std::string line; |
| |
|
| | |
| | int X_index = -1; |
| | int Y_index = -1; |
| | int Z_index = -1; |
| | int NX_index = -1; |
| | int NY_index = -1; |
| | int NZ_index = -1; |
| | int R_index = -1; |
| | int G_index = -1; |
| | int B_index = -1; |
| |
|
| | |
| | int X_byte_pos = -1; |
| | int Y_byte_pos = -1; |
| | int Z_byte_pos = -1; |
| | int NX_byte_pos = -1; |
| | int NY_byte_pos = -1; |
| | int NZ_byte_pos = -1; |
| | int R_byte_pos = -1; |
| | int G_byte_pos = -1; |
| | int B_byte_pos = -1; |
| |
|
| | |
| | bool X_double = false; |
| | bool Y_double = false; |
| | bool Z_double = false; |
| | bool NX_double = false; |
| | bool NY_double = false; |
| | bool NZ_double = false; |
| |
|
| | bool in_vertex_section = false; |
| | bool is_binary = false; |
| | bool is_little_endian = false; |
| | size_t num_bytes_per_line = 0; |
| | size_t num_vertices = 0; |
| |
|
| | int index = 0; |
| | while (std::getline(file, line)) { |
| | StringTrim(&line); |
| |
|
| | if (line.empty()) { |
| | continue; |
| | } |
| |
|
| | if (line == "end_header") { |
| | break; |
| | } |
| |
|
| | if (line.size() >= 6 && line.substr(0, 6) == "format") { |
| | if (line == "format ascii 1.0") { |
| | is_binary = false; |
| | } else if (line == "format binary_little_endian 1.0") { |
| | is_binary = true; |
| | is_little_endian = true; |
| | } else if (line == "format binary_big_endian 1.0") { |
| | is_binary = true; |
| | is_little_endian = false; |
| | } |
| | } |
| |
|
| | const std::vector<std::string> line_elems = StringSplit(line, " "); |
| |
|
| | if (line_elems.size() >= 3 && line_elems[0] == "element") { |
| | in_vertex_section = false; |
| | if (line_elems[1] == "vertex") { |
| | num_vertices = std::stoll(line_elems[2]); |
| | in_vertex_section = true; |
| | } else if (std::stoll(line_elems[2]) > 0) { |
| | std::cout << "WARN: Only vertex elements supported; ignoring " |
| | << line_elems[1] << std::endl; |
| | } |
| | } |
| |
|
| | if (!in_vertex_section) { |
| | continue; |
| | } |
| |
|
| | |
| |
|
| | if (line_elems.size() >= 3 && line_elems[0] == "property") { |
| | CHECK(line_elems[1] == "float" || line_elems[1] == "float32" || |
| | line_elems[1] == "double" || line_elems[1] == "float64" || |
| | line_elems[1] == "uchar") |
| | << "PLY import only supports float, double, and uchar data types"; |
| |
|
| | if (line == "property float x" || line == "property float32 x" || |
| | line == "property double x" || line == "property float64 x") { |
| | X_index = index; |
| | X_byte_pos = num_bytes_per_line; |
| | X_double = (line_elems[1] == "double" || line_elems[1] == "float64"); |
| | } else if (line == "property float y" || line == "property float32 y" || |
| | line == "property double y" || line == "property float64 y") { |
| | Y_index = index; |
| | Y_byte_pos = num_bytes_per_line; |
| | Y_double = (line_elems[1] == "double" || line_elems[1] == "float64"); |
| | } else if (line == "property float z" || line == "property float32 z" || |
| | line == "property double z" || line == "property float64 z") { |
| | Z_index = index; |
| | Z_byte_pos = num_bytes_per_line; |
| | Z_double = (line_elems[1] == "double" || line_elems[1] == "float64"); |
| | } else if (line == "property float nx" || line == "property float32 nx" || |
| | line == "property double nx" || |
| | line == "property float64 nx") { |
| | NX_index = index; |
| | NX_byte_pos = num_bytes_per_line; |
| | NX_double = (line_elems[1] == "double" || line_elems[1] == "float64"); |
| | } else if (line == "property float ny" || line == "property float32 ny" || |
| | line == "property double ny" || |
| | line == "property float64 ny") { |
| | NY_index = index; |
| | NY_byte_pos = num_bytes_per_line; |
| | NY_double = (line_elems[1] == "double" || line_elems[1] == "float64"); |
| | } else if (line == "property float nz" || line == "property float32 nz" || |
| | line == "property double nz" || |
| | line == "property float64 nz") { |
| | NZ_index = index; |
| | NZ_byte_pos = num_bytes_per_line; |
| | NZ_double = (line_elems[1] == "double" || line_elems[1] == "float64"); |
| | } else if (line == "property uchar r" || line == "property uchar red" || |
| | line == "property uchar diffuse_red" || |
| | line == "property uchar ambient_red" || |
| | line == "property uchar specular_red") { |
| | R_index = index; |
| | R_byte_pos = num_bytes_per_line; |
| | } else if (line == "property uchar g" || line == "property uchar green" || |
| | line == "property uchar diffuse_green" || |
| | line == "property uchar ambient_green" || |
| | line == "property uchar specular_green") { |
| | G_index = index; |
| | G_byte_pos = num_bytes_per_line; |
| | } else if (line == "property uchar b" || line == "property uchar blue" || |
| | line == "property uchar diffuse_blue" || |
| | line == "property uchar ambient_blue" || |
| | line == "property uchar specular_blue") { |
| | B_index = index; |
| | B_byte_pos = num_bytes_per_line; |
| | } |
| |
|
| | index += 1; |
| | if (line_elems[1] == "float" || line_elems[1] == "float32") { |
| | num_bytes_per_line += 4; |
| | } else if (line_elems[1] == "double" || line_elems[1] == "float64") { |
| | num_bytes_per_line += 8; |
| | } else if (line_elems[1] == "uchar") { |
| | num_bytes_per_line += 1; |
| | } else { |
| | LOG(FATAL) << "Invalid data type: " << line_elems[1]; |
| | } |
| | } |
| | } |
| |
|
| | const bool is_normal_missing = |
| | (NX_index == -1) || (NY_index == -1) || (NZ_index == -1); |
| | const bool is_rgb_missing = |
| | (R_index == -1) || (G_index == -1) || (B_index == -1); |
| |
|
| | CHECK(X_index != -1 && Y_index != -1 && Z_index) |
| | << "Invalid PLY file format: x, y, z properties missing"; |
| |
|
| | points.reserve(num_vertices); |
| |
|
| | if (is_binary) { |
| | std::vector<char> buffer(num_bytes_per_line); |
| | for (size_t i = 0; i < num_vertices; ++i) { |
| | file.read(buffer.data(), num_bytes_per_line); |
| |
|
| | PlyPoint point; |
| |
|
| | if (is_little_endian) { |
| | point.x = LittleEndianToNative( |
| | X_double ? *reinterpret_cast<double*>(&buffer[X_byte_pos]) |
| | : *reinterpret_cast<float*>(&buffer[X_byte_pos])); |
| | point.y = LittleEndianToNative( |
| | Y_double ? *reinterpret_cast<double*>(&buffer[Y_byte_pos]) |
| | : *reinterpret_cast<float*>(&buffer[Y_byte_pos])); |
| | point.z = LittleEndianToNative( |
| | Z_double ? *reinterpret_cast<double*>(&buffer[Z_byte_pos]) |
| | : *reinterpret_cast<float*>(&buffer[Z_byte_pos])); |
| |
|
| | if (!is_normal_missing) { |
| | point.nx = LittleEndianToNative( |
| | NX_double ? *reinterpret_cast<double*>(&buffer[NX_byte_pos]) |
| | : *reinterpret_cast<float*>(&buffer[NX_byte_pos])); |
| | point.ny = LittleEndianToNative( |
| | NY_double ? *reinterpret_cast<double*>(&buffer[NY_byte_pos]) |
| | : *reinterpret_cast<float*>(&buffer[NY_byte_pos])); |
| | point.nz = LittleEndianToNative( |
| | NZ_double ? *reinterpret_cast<double*>(&buffer[NZ_byte_pos]) |
| | : *reinterpret_cast<float*>(&buffer[NZ_byte_pos])); |
| | } |
| |
|
| | if (!is_rgb_missing) { |
| | point.r = LittleEndianToNative( |
| | *reinterpret_cast<uint8_t*>(&buffer[R_byte_pos])); |
| | point.g = LittleEndianToNative( |
| | *reinterpret_cast<uint8_t*>(&buffer[G_byte_pos])); |
| | point.b = LittleEndianToNative( |
| | *reinterpret_cast<uint8_t*>(&buffer[B_byte_pos])); |
| | } |
| | } else { |
| | point.x = BigEndianToNative( |
| | X_double ? *reinterpret_cast<double*>(&buffer[X_byte_pos]) |
| | : *reinterpret_cast<float*>(&buffer[X_byte_pos])); |
| | point.y = BigEndianToNative( |
| | Y_double ? *reinterpret_cast<double*>(&buffer[Y_byte_pos]) |
| | : *reinterpret_cast<float*>(&buffer[Y_byte_pos])); |
| | point.z = BigEndianToNative( |
| | Z_double ? *reinterpret_cast<double*>(&buffer[Z_byte_pos]) |
| | : *reinterpret_cast<float*>(&buffer[Z_byte_pos])); |
| |
|
| | if (!is_normal_missing) { |
| | point.nx = BigEndianToNative( |
| | NX_double ? *reinterpret_cast<double*>(&buffer[NX_byte_pos]) |
| | : *reinterpret_cast<float*>(&buffer[NX_byte_pos])); |
| | point.ny = BigEndianToNative( |
| | NY_double ? *reinterpret_cast<double*>(&buffer[NY_byte_pos]) |
| | : *reinterpret_cast<float*>(&buffer[NY_byte_pos])); |
| | point.nz = BigEndianToNative( |
| | NZ_double ? *reinterpret_cast<double*>(&buffer[NZ_byte_pos]) |
| | : *reinterpret_cast<float*>(&buffer[NZ_byte_pos])); |
| | } |
| |
|
| | if (!is_rgb_missing) { |
| | point.r = BigEndianToNative( |
| | *reinterpret_cast<uint8_t*>(&buffer[R_byte_pos])); |
| | point.g = BigEndianToNative( |
| | *reinterpret_cast<uint8_t*>(&buffer[G_byte_pos])); |
| | point.b = BigEndianToNative( |
| | *reinterpret_cast<uint8_t*>(&buffer[B_byte_pos])); |
| | } |
| | } |
| |
|
| | points.push_back(point); |
| | } |
| | } else { |
| | while (std::getline(file, line)) { |
| | StringTrim(&line); |
| | std::stringstream line_stream(line); |
| |
|
| | std::string item; |
| | std::vector<std::string> items; |
| | while (!line_stream.eof()) { |
| | std::getline(line_stream, item, ' '); |
| | StringTrim(&item); |
| | items.push_back(item); |
| | } |
| |
|
| | PlyPoint point; |
| |
|
| | point.x = std::stold(items.at(X_index)); |
| | point.y = std::stold(items.at(Y_index)); |
| | point.z = std::stold(items.at(Z_index)); |
| |
|
| | if (!is_normal_missing) { |
| | point.nx = std::stold(items.at(NX_index)); |
| | point.ny = std::stold(items.at(NY_index)); |
| | point.nz = std::stold(items.at(NZ_index)); |
| | } |
| |
|
| | if (!is_rgb_missing) { |
| | point.r = std::stoi(items.at(R_index)); |
| | point.g = std::stoi(items.at(G_index)); |
| | point.b = std::stoi(items.at(B_index)); |
| | } |
| |
|
| | points.push_back(point); |
| | } |
| | } |
| |
|
| | return points; |
| | } |
| |
|
| | void WriteTextPlyPoints(const std::string& path, |
| | const std::vector<PlyPoint>& points, |
| | const bool write_normal, const bool write_rgb) { |
| | std::ofstream file(path); |
| | CHECK(file.is_open()) << path; |
| |
|
| | file << "ply" << std::endl; |
| | file << "format ascii 1.0" << std::endl; |
| | file << "element vertex " << points.size() << std::endl; |
| |
|
| | file << "property float x" << std::endl; |
| | file << "property float y" << std::endl; |
| | file << "property float z" << std::endl; |
| |
|
| | if (write_normal) { |
| | file << "property float nx" << std::endl; |
| | file << "property float ny" << std::endl; |
| | file << "property float nz" << std::endl; |
| | } |
| |
|
| | if (write_rgb) { |
| | file << "property uchar red" << std::endl; |
| | file << "property uchar green" << std::endl; |
| | file << "property uchar blue" << std::endl; |
| | } |
| |
|
| | file << "end_header" << std::endl; |
| |
|
| | for (const auto& point : points) { |
| | file << point.x << " " << point.y << " " << point.z; |
| |
|
| | if (write_normal) { |
| | file << " " << point.nx << " " << point.ny << " " << point.nz; |
| | } |
| |
|
| | if (write_rgb) { |
| | file << " " << static_cast<int>(point.r) << " " |
| | << static_cast<int>(point.g) << " " << static_cast<int>(point.b); |
| | } |
| |
|
| | file << std::endl; |
| | } |
| |
|
| | file.close(); |
| | } |
| |
|
| | void WriteBinaryPlyPoints(const std::string& path, |
| | const std::vector<PlyPoint>& points, |
| | const bool write_normal, const bool write_rgb) { |
| | std::fstream text_file(path, std::ios::out); |
| | CHECK(text_file.is_open()) << path; |
| |
|
| | text_file << "ply" << std::endl; |
| | text_file << "format binary_little_endian 1.0" << std::endl; |
| | text_file << "element vertex " << points.size() << std::endl; |
| |
|
| | text_file << "property float x" << std::endl; |
| | text_file << "property float y" << std::endl; |
| | text_file << "property float z" << std::endl; |
| |
|
| | if (write_normal) { |
| | text_file << "property float nx" << std::endl; |
| | text_file << "property float ny" << std::endl; |
| | text_file << "property float nz" << std::endl; |
| | } |
| |
|
| | if (write_rgb) { |
| | text_file << "property uchar red" << std::endl; |
| | text_file << "property uchar green" << std::endl; |
| | text_file << "property uchar blue" << std::endl; |
| | } |
| |
|
| | text_file << "end_header" << std::endl; |
| | text_file.close(); |
| |
|
| | std::fstream binary_file(path, |
| | std::ios::out | std::ios::binary | std::ios::app); |
| | CHECK(binary_file.is_open()) << path; |
| |
|
| | for (const auto& point : points) { |
| | WriteBinaryLittleEndian<float>(&binary_file, point.x); |
| | WriteBinaryLittleEndian<float>(&binary_file, point.y); |
| | WriteBinaryLittleEndian<float>(&binary_file, point.z); |
| |
|
| | if (write_normal) { |
| | WriteBinaryLittleEndian<float>(&binary_file, point.nx); |
| | WriteBinaryLittleEndian<float>(&binary_file, point.ny); |
| | WriteBinaryLittleEndian<float>(&binary_file, point.nz); |
| | } |
| |
|
| | if (write_rgb) { |
| | WriteBinaryLittleEndian<uint8_t>(&binary_file, point.r); |
| | WriteBinaryLittleEndian<uint8_t>(&binary_file, point.g); |
| | WriteBinaryLittleEndian<uint8_t>(&binary_file, point.b); |
| | } |
| | } |
| |
|
| | binary_file.close(); |
| | } |
| |
|
| | void WriteTextPlyMesh(const std::string& path, const PlyMesh& mesh) { |
| | std::fstream file(path, std::ios::out); |
| | CHECK(file.is_open()); |
| |
|
| | file << "ply" << std::endl; |
| | file << "format ascii 1.0" << std::endl; |
| | file << "element vertex " << mesh.vertices.size() << std::endl; |
| | file << "property float x" << std::endl; |
| | file << "property float y" << std::endl; |
| | file << "property float z" << std::endl; |
| | file << "element face " << mesh.faces.size() << std::endl; |
| | file << "property list uchar int vertex_index" << std::endl; |
| | file << "end_header" << std::endl; |
| |
|
| | for (const auto& vertex : mesh.vertices) { |
| | file << vertex.x << " " << vertex.y << " " << vertex.z << std::endl; |
| | } |
| |
|
| | for (const auto& face : mesh.faces) { |
| | file << StringPrintf("3 %d %d %d", face.vertex_idx1, face.vertex_idx2, |
| | face.vertex_idx3) |
| | << std::endl; |
| | } |
| | } |
| |
|
| | void WriteBinaryPlyMesh(const std::string& path, const PlyMesh& mesh) { |
| | std::fstream text_file(path, std::ios::out); |
| | CHECK(text_file.is_open()); |
| |
|
| | text_file << "ply" << std::endl; |
| | text_file << "format binary_little_endian 1.0" << std::endl; |
| | text_file << "element vertex " << mesh.vertices.size() << std::endl; |
| | text_file << "property float x" << std::endl; |
| | text_file << "property float y" << std::endl; |
| | text_file << "property float z" << std::endl; |
| | text_file << "element face " << mesh.faces.size() << std::endl; |
| | text_file << "property list uchar int vertex_index" << std::endl; |
| | text_file << "end_header" << std::endl; |
| | text_file.close(); |
| |
|
| | std::fstream binary_file(path, |
| | std::ios::out | std::ios::binary | std::ios::app); |
| | CHECK(binary_file.is_open()) << path; |
| |
|
| | for (const auto& vertex : mesh.vertices) { |
| | WriteBinaryLittleEndian<float>(&binary_file, vertex.x); |
| | WriteBinaryLittleEndian<float>(&binary_file, vertex.y); |
| | WriteBinaryLittleEndian<float>(&binary_file, vertex.z); |
| | } |
| |
|
| | for (const auto& face : mesh.faces) { |
| | CHECK_LT(face.vertex_idx1, mesh.vertices.size()); |
| | CHECK_LT(face.vertex_idx2, mesh.vertices.size()); |
| | CHECK_LT(face.vertex_idx3, mesh.vertices.size()); |
| | const uint8_t kNumVertices = 3; |
| | WriteBinaryLittleEndian<uint8_t>(&binary_file, kNumVertices); |
| | WriteBinaryLittleEndian<int>(&binary_file, face.vertex_idx1); |
| | WriteBinaryLittleEndian<int>(&binary_file, face.vertex_idx2); |
| | WriteBinaryLittleEndian<int>(&binary_file, face.vertex_idx3); |
| | } |
| |
|
| | binary_file.close(); |
| | } |
| |
|
| | } |
| |
|