From f800798e0909a4375cb61bf186ff3b8599b98271 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Tue, 19 Aug 2025 12:19:38 -0600 Subject: [PATCH 1/8] Add serialization and deserialization methods to Octree --- cpp/open3d/geometry/Octree.cpp | 225 +++++++++++++++++++++++++++++++++ cpp/open3d/geometry/Octree.h | 17 +++ cpp/tests/geometry/Octree.cpp | 19 +++ 3 files changed, 261 insertions(+) diff --git a/cpp/open3d/geometry/Octree.cpp b/cpp/open3d/geometry/Octree.cpp index 23efcb06e81..5e1eb669adf 100644 --- a/cpp/open3d/geometry/Octree.cpp +++ b/cpp/open3d/geometry/Octree.cpp @@ -49,6 +49,32 @@ std::shared_ptr OctreeNode::ConstructFromJsonValue( return node; } +std::shared_ptr OctreeNode::ConstructFromBinaryStream(const std::string& in, size_t& offset) { + std::string class_id(&in[offset], 24); + + std::shared_ptr node = nullptr; + if (std::string(&in[offset], 18) == "OctreeInternalNode") { + node = std::make_shared(); + } else if (std::string(&in[offset], 23) == "OctreeInternalPointNode") { + node = std::make_shared(); + } else if (std::string(&in[offset], 19) == "OctreeColorLeafNode") { + node = std::make_shared(); + } else if (std::string(&in[offset], 24) == "OctreePointColorLeafNode") { + node = std::make_shared(); + } else { + utility::LogError("Unhandled class id {}", class_id); + } + + // Convert from binary + if (node != nullptr) { + bool deserialization_success = node->DeserializeFromBinaryStream(in, offset); + if (!deserialization_success) { + node = nullptr; + } + } + return node; +} + std::shared_ptr OctreeInternalNode::GetInsertionNodeInfo( const std::shared_ptr& node_info, const Eigen::Vector3d& point) { @@ -120,6 +146,44 @@ bool OctreeInternalNode::ConvertFromJsonValue(const Json::Value& value) { return rc; } +bool OctreeInternalNode::SerializeToBinaryStream(std::string& out) const { + bool rc = true; + // Write class identifier + const char class_id[] = "OctreeInternalNode"; + out.append(class_id, sizeof(class_id)); + // Write children + for (int cid = 0; cid < 8; ++cid) { + bool has_child = (children_[cid] != nullptr); + out.append(reinterpret_cast(&has_child), sizeof(has_child)); + if (has_child) { + rc = rc && children_[cid]->SerializeToBinaryStream(out); + } + } + return rc; +} + +bool OctreeInternalNode::DeserializeFromBinaryStream(const std::string& in, size_t& offset) { + // Read and check class identifier + if (in.size() < offset + 19) return false; + if (std::string(&in[offset], 18) != "OctreeInternalNode") return false; + offset += 19; + // Read children + for (int cid = 0; cid < 8; ++cid) { + if (in.size() < offset + sizeof(bool)) return false; + bool has_child = *reinterpret_cast(&in[offset]); + offset += sizeof(bool); + if (has_child) { + children_[cid] = OctreeNode::ConstructFromBinaryStream(in, offset); + if (!children_[cid]) { + return false; + } + } else { + children_[cid] = nullptr; + } + } + return true; +} + std::function()> OctreeInternalPointNode::GetInitFunction() { return []() -> std::shared_ptr { @@ -190,6 +254,65 @@ bool OctreeInternalPointNode::ConvertFromJsonValue(const Json::Value& value) { return rc; } +bool OctreeInternalPointNode::SerializeToBinaryStream(std::string& out) const { + bool rc = true; + // Write class identifier + const char class_id[] = "OctreeInternalPointNode"; + out.append(class_id, sizeof(class_id)); + // Write children + for (int cid = 0; cid < 8; ++cid) { + bool has_child = (children_[cid] != nullptr); + out.append(reinterpret_cast(&has_child), sizeof(has_child)); + if (has_child) { + rc = rc && children_[cid]->SerializeToBinaryStream(out); + } + } + // Write indices + size_t num_indices = indices_.size(); + out.append(reinterpret_cast(&num_indices), sizeof(num_indices)); + for (size_t idx : indices_) { + out.append(reinterpret_cast(&idx), sizeof(idx)); + } + return rc; +} + +bool OctreeInternalPointNode::DeserializeFromBinaryStream(const std::string& in, size_t& offset) { + // Read and check class identifier + if (in.size() < offset + 24) return false; + if (std::string(&in[offset], 23) != "OctreeInternalPointNode") return false; + offset += 24; + + // Read children + for (int cid = 0; cid < 8; ++cid) { + if (in.size() < offset + sizeof(bool)) return false; + bool has_child = *reinterpret_cast(&in[offset]); + offset += sizeof(bool); + if (has_child) { + children_[cid] = OctreeNode::ConstructFromBinaryStream(in, offset); + if (!children_[cid]) { + return false; + } + } else { + children_[cid] = nullptr; + } + } + + // Read indices size + if (in.size() < offset + sizeof(size_t)) return false; + size_t indices_size = *reinterpret_cast(&in[offset]); + offset += sizeof(size_t); + // Read indices + indices_.clear(); + for (size_t i = 0; i < indices_size; ++i) { + if (in.size() < offset + sizeof(size_t)) return false; + size_t idx = *reinterpret_cast(&in[offset]); + offset += sizeof(size_t); + indices_.push_back(idx); + } + + return true; +} + std::function()> OctreeColorLeafNode::GetInitFunction() { return []() -> std::shared_ptr { @@ -247,6 +370,27 @@ bool OctreeColorLeafNode::ConvertFromJsonValue(const Json::Value& value) { return EigenVector3dFromJsonArray(color_, value["color"]); } +bool OctreeColorLeafNode::SerializeToBinaryStream(std::string& out) const { + // Write class identifier + const char class_id[] = "OctreeColorLeafNode"; + out.append(class_id, sizeof(class_id)); + // Write color_ (Eigen::Vector3d) + out.append(reinterpret_cast(color_.data()), sizeof(double) * 3); + return true; +} + +bool OctreeColorLeafNode::DeserializeFromBinaryStream(const std::string& in, size_t& offset) { + // Read and check class identifier + if (in.size() < offset + 20) return false; + if (std::string(&in[offset], 19) != "OctreeColorLeafNode") return false; + offset += 20; + // Read color_ (Eigen::Vector3d) + if (in.size() < offset + sizeof(double) * 3) return false; + std::memcpy(color_.data(), &in[offset], sizeof(double) * 3); + offset += sizeof(double) * 3; + return true; +} + std::function()> OctreePointColorLeafNode::GetInitFunction() { return []() -> std::shared_ptr { @@ -322,6 +466,48 @@ bool OctreePointColorLeafNode::ConvertFromJsonValue(const Json::Value& value) { return true; } +bool OctreePointColorLeafNode::SerializeToBinaryStream(std::string& out) const { + bool rc = true; + // Write class identifier + const char class_id[] = "OctreePointColorLeafNode"; + out.append(class_id, sizeof(class_id)); + // Write color_ (Eigen::Vector3d) + out.append(reinterpret_cast(color_.data()), sizeof(double) * 3); + // Write indices + size_t num_indices = indices_.size(); + out.append(reinterpret_cast(&num_indices), sizeof(num_indices)); + for (size_t idx : indices_) { + out.append(reinterpret_cast(&idx), sizeof(idx)); + } + return rc; +} + +bool OctreePointColorLeafNode::DeserializeFromBinaryStream(const std::string& in, size_t& offset) { + // Read and check class identifier + if (in.size() < offset + 25) return false; + if (std::string(&in[offset], 24) != "OctreePointColorLeafNode") return false; + offset += 25; + // Read color_ (Eigen::Vector3d) + if (in.size() < offset + sizeof(double) * 3) return false; + std::memcpy(color_.data(), &in[offset], sizeof(double) * 3); + offset += sizeof(double) * 3; + + // Read indices size + if (in.size() < offset + sizeof(size_t)) return false; + size_t indices_size = *reinterpret_cast(&in[offset]); + offset += sizeof(size_t); + // Read indices + indices_.clear(); + for (size_t i = 0; i < indices_size; ++i) { + if (in.size() < offset + sizeof(size_t)) return false; + size_t idx = *reinterpret_cast(&in[offset]); + offset += sizeof(size_t); + indices_.push_back(idx); + } + + return true; +} + Octree::Octree(const Octree& src_octree) : Geometry3D(Geometry::GeometryType::Octree), origin_(src_octree.origin_), @@ -786,6 +972,45 @@ bool Octree::ConvertFromJsonValue(const Json::Value& value) { return rc; } +bool Octree::SerializeToBinaryStream(std::string& out) const { + bool rc = true; + // Serialize the basic attributes + out.clear(); + out.append(reinterpret_cast(origin_.data()), sizeof(double) * 3); + out.append(reinterpret_cast(&size_), sizeof(double)); + out.append(reinterpret_cast(&max_depth_), sizeof(size_t)); + // Serialize the root node + if (root_node_) { + rc = rc && root_node_->SerializeToBinaryStream(out); + } + return rc; +} + +bool Octree::DeserializeFromBinaryStream(const std::string& in) { + size_t offset = 0; + // Deserialize the basic attributes + if (in.size() < offset + sizeof(origin_) * 3) return false; + std::memcpy(origin_.data(), &in[offset], sizeof(origin_) * 3); + offset += sizeof(double) * 3; + + if (in.size() < offset + sizeof(size_)) return false; + std::memcpy(&size_, &in[offset], sizeof(size_)); + offset += sizeof(double); + + if (in.size() < offset + sizeof(max_depth_)) return false; + std::memcpy(&max_depth_, &in[offset], sizeof(max_depth_)); + offset += sizeof(size_t); + + // Deserialize the root node + root_node_ = OctreeNode::ConstructFromBinaryStream(in, offset); + if (root_node_) { + if (!root_node_->DeserializeFromBinaryStream(in, offset)) { + return false; + } + } + return true; +} + void Octree::CreateFromVoxelGrid(const geometry::VoxelGrid& voxel_grid) { origin_ = voxel_grid.origin_; size_ = (voxel_grid.GetMaxBound() - origin_).maxCoeff(); diff --git a/cpp/open3d/geometry/Octree.h b/cpp/open3d/geometry/Octree.h index 5b3b6d90c52..91ae20498c6 100644 --- a/cpp/open3d/geometry/Octree.h +++ b/cpp/open3d/geometry/Octree.h @@ -9,6 +9,7 @@ #include #include +#include #include #include "open3d/geometry/Geometry3D.h" @@ -77,6 +78,12 @@ class OctreeNode : public utility::IJsonConvertible { /// Factory function to construct an OctreeNode by parsing the json value. static std::shared_ptr ConstructFromJsonValue( const Json::Value& value); + /// Factory function to construct an OctreeNode by reading from a binary stream. + static std::shared_ptr ConstructFromBinaryStream( + const std::string& in, size_t& offset); + + virtual bool SerializeToBinaryStream(std::string& out) const = 0; + virtual bool DeserializeFromBinaryStream(const std::string& in, size_t& offset) = 0; }; /// \class OctreeInternalNode @@ -121,6 +128,8 @@ class OctreeInternalNode : public OctreeNode { bool ConvertToJsonValue(Json::Value& value) const override; bool ConvertFromJsonValue(const Json::Value& value) override; + bool SerializeToBinaryStream(std::string& out) const; + bool DeserializeFromBinaryStream(const std::string& in, size_t& offset); public: /// Use vector instead of C-array for Pybind11, otherwise, need to define @@ -156,6 +165,8 @@ class OctreeInternalPointNode : public OctreeInternalNode { bool ConvertToJsonValue(Json::Value& value) const override; bool ConvertFromJsonValue(const Json::Value& value) override; + bool SerializeToBinaryStream(std::string& out) const; + bool DeserializeFromBinaryStream(const std::string& in, size_t& offset); public: /// Indices of points associated with any children of this node @@ -199,6 +210,8 @@ class OctreeColorLeafNode : public OctreeLeafNode { bool ConvertToJsonValue(Json::Value& value) const override; bool ConvertFromJsonValue(const Json::Value& value) override; + bool SerializeToBinaryStream(std::string& out) const; + bool DeserializeFromBinaryStream(const std::string& in, size_t& offset); /// TODO: flexible data, with lambda function for handling node /// Color of the node. Eigen::Vector3d color_ = Eigen::Vector3d(0, 0, 0); @@ -233,6 +246,8 @@ class OctreePointColorLeafNode : public OctreeColorLeafNode { bool ConvertToJsonValue(Json::Value& value) const override; bool ConvertFromJsonValue(const Json::Value& value) override; + bool SerializeToBinaryStream(std::string& out) const; + bool DeserializeFromBinaryStream(const std::string& in, size_t& offset); /// Associated point indices with this node. std::vector indices_; @@ -301,6 +316,8 @@ class Octree : public Geometry3D, public utility::IJsonConvertible { const Eigen::Vector3d& center) override; bool ConvertToJsonValue(Json::Value& value) const override; bool ConvertFromJsonValue(const Json::Value& value) override; + bool SerializeToBinaryStream(std::string& out) const; + bool DeserializeFromBinaryStream(const std::string& in); public: /// \brief Convert octree from point cloud. diff --git a/cpp/tests/geometry/Octree.cpp b/cpp/tests/geometry/Octree.cpp index 9ed6f4cf33f..e25da60f75f 100644 --- a/cpp/tests/geometry/Octree.cpp +++ b/cpp/tests/geometry/Octree.cpp @@ -406,5 +406,24 @@ TEST(Octree, ConvertToJsonValue) { EXPECT_TRUE(src_octree == dst_octree); } +TEST(Octree, ConvertFromBinaryStream) { + geometry::PointCloud pcd; + data::PLYPointCloud pointcloud_ply; + io::ReadPointCloud(pointcloud_ply.GetPath(), pcd); + size_t max_depth = 5; + geometry::Octree src_octree(max_depth); + src_octree.ConvertFromPointCloud(pcd, 0.01); + + // Serialize to binary + std::string binary_data; + src_octree.SerializeToBinaryStream(binary_data); + + // Deserialize from binary + geometry::Octree dst_octree; + dst_octree.DeserializeFromBinaryStream(binary_data); + + EXPECT_TRUE(src_octree == dst_octree); +} + } // namespace tests } // namespace open3d From 9775aec2c50e50c88ea0b8a9b01f87b6ba94f626 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Tue, 19 Aug 2025 12:28:08 -0600 Subject: [PATCH 2/8] Fix code style --- cpp/open3d/geometry/Octree.cpp | 42 ++++++++++++++++++---------- cpp/open3d/geometry/Octree.h | 6 ++-- cpp/pybind/t/geometry/pointcloud.cpp | 7 +++-- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/cpp/open3d/geometry/Octree.cpp b/cpp/open3d/geometry/Octree.cpp index 5e1eb669adf..23b12279cb2 100644 --- a/cpp/open3d/geometry/Octree.cpp +++ b/cpp/open3d/geometry/Octree.cpp @@ -49,7 +49,8 @@ std::shared_ptr OctreeNode::ConstructFromJsonValue( return node; } -std::shared_ptr OctreeNode::ConstructFromBinaryStream(const std::string& in, size_t& offset) { +std::shared_ptr OctreeNode::ConstructFromBinaryStream( + const std::string& in, size_t& offset) { std::string class_id(&in[offset], 24); std::shared_ptr node = nullptr; @@ -67,7 +68,8 @@ std::shared_ptr OctreeNode::ConstructFromBinaryStream(const std::str // Convert from binary if (node != nullptr) { - bool deserialization_success = node->DeserializeFromBinaryStream(in, offset); + bool deserialization_success = + node->DeserializeFromBinaryStream(in, offset); if (!deserialization_success) { node = nullptr; } @@ -154,7 +156,8 @@ bool OctreeInternalNode::SerializeToBinaryStream(std::string& out) const { // Write children for (int cid = 0; cid < 8; ++cid) { bool has_child = (children_[cid] != nullptr); - out.append(reinterpret_cast(&has_child), sizeof(has_child)); + out.append(reinterpret_cast(&has_child), + sizeof(has_child)); if (has_child) { rc = rc && children_[cid]->SerializeToBinaryStream(out); } @@ -162,7 +165,8 @@ bool OctreeInternalNode::SerializeToBinaryStream(std::string& out) const { return rc; } -bool OctreeInternalNode::DeserializeFromBinaryStream(const std::string& in, size_t& offset) { +bool OctreeInternalNode::DeserializeFromBinaryStream(const std::string& in, + size_t& offset) { // Read and check class identifier if (in.size() < offset + 19) return false; if (std::string(&in[offset], 18) != "OctreeInternalNode") return false; @@ -262,21 +266,24 @@ bool OctreeInternalPointNode::SerializeToBinaryStream(std::string& out) const { // Write children for (int cid = 0; cid < 8; ++cid) { bool has_child = (children_[cid] != nullptr); - out.append(reinterpret_cast(&has_child), sizeof(has_child)); + out.append(reinterpret_cast(&has_child), + sizeof(has_child)); if (has_child) { rc = rc && children_[cid]->SerializeToBinaryStream(out); } } // Write indices size_t num_indices = indices_.size(); - out.append(reinterpret_cast(&num_indices), sizeof(num_indices)); + out.append(reinterpret_cast(&num_indices), + sizeof(num_indices)); for (size_t idx : indices_) { out.append(reinterpret_cast(&idx), sizeof(idx)); } return rc; } -bool OctreeInternalPointNode::DeserializeFromBinaryStream(const std::string& in, size_t& offset) { +bool OctreeInternalPointNode::DeserializeFromBinaryStream(const std::string& in, + size_t& offset) { // Read and check class identifier if (in.size() < offset + 24) return false; if (std::string(&in[offset], 23) != "OctreeInternalPointNode") return false; @@ -375,11 +382,13 @@ bool OctreeColorLeafNode::SerializeToBinaryStream(std::string& out) const { const char class_id[] = "OctreeColorLeafNode"; out.append(class_id, sizeof(class_id)); // Write color_ (Eigen::Vector3d) - out.append(reinterpret_cast(color_.data()), sizeof(double) * 3); + out.append(reinterpret_cast(color_.data()), + sizeof(double) * 3); return true; } -bool OctreeColorLeafNode::DeserializeFromBinaryStream(const std::string& in, size_t& offset) { +bool OctreeColorLeafNode::DeserializeFromBinaryStream(const std::string& in, + size_t& offset) { // Read and check class identifier if (in.size() < offset + 20) return false; if (std::string(&in[offset], 19) != "OctreeColorLeafNode") return false; @@ -472,20 +481,24 @@ bool OctreePointColorLeafNode::SerializeToBinaryStream(std::string& out) const { const char class_id[] = "OctreePointColorLeafNode"; out.append(class_id, sizeof(class_id)); // Write color_ (Eigen::Vector3d) - out.append(reinterpret_cast(color_.data()), sizeof(double) * 3); + out.append(reinterpret_cast(color_.data()), + sizeof(double) * 3); // Write indices size_t num_indices = indices_.size(); - out.append(reinterpret_cast(&num_indices), sizeof(num_indices)); + out.append(reinterpret_cast(&num_indices), + sizeof(num_indices)); for (size_t idx : indices_) { out.append(reinterpret_cast(&idx), sizeof(idx)); } return rc; } -bool OctreePointColorLeafNode::DeserializeFromBinaryStream(const std::string& in, size_t& offset) { +bool OctreePointColorLeafNode::DeserializeFromBinaryStream( + const std::string& in, size_t& offset) { // Read and check class identifier if (in.size() < offset + 25) return false; - if (std::string(&in[offset], 24) != "OctreePointColorLeafNode") return false; + if (std::string(&in[offset], 24) != "OctreePointColorLeafNode") + return false; offset += 25; // Read color_ (Eigen::Vector3d) if (in.size() < offset + sizeof(double) * 3) return false; @@ -976,7 +989,8 @@ bool Octree::SerializeToBinaryStream(std::string& out) const { bool rc = true; // Serialize the basic attributes out.clear(); - out.append(reinterpret_cast(origin_.data()), sizeof(double) * 3); + out.append(reinterpret_cast(origin_.data()), + sizeof(double) * 3); out.append(reinterpret_cast(&size_), sizeof(double)); out.append(reinterpret_cast(&max_depth_), sizeof(size_t)); // Serialize the root node diff --git a/cpp/open3d/geometry/Octree.h b/cpp/open3d/geometry/Octree.h index 91ae20498c6..3d26d769df4 100644 --- a/cpp/open3d/geometry/Octree.h +++ b/cpp/open3d/geometry/Octree.h @@ -78,12 +78,14 @@ class OctreeNode : public utility::IJsonConvertible { /// Factory function to construct an OctreeNode by parsing the json value. static std::shared_ptr ConstructFromJsonValue( const Json::Value& value); - /// Factory function to construct an OctreeNode by reading from a binary stream. + /// Factory function to construct an OctreeNode by reading from a binary + /// stream. static std::shared_ptr ConstructFromBinaryStream( const std::string& in, size_t& offset); virtual bool SerializeToBinaryStream(std::string& out) const = 0; - virtual bool DeserializeFromBinaryStream(const std::string& in, size_t& offset) = 0; + virtual bool DeserializeFromBinaryStream(const std::string& in, + size_t& offset) = 0; }; /// \class OctreeInternalNode diff --git a/cpp/pybind/t/geometry/pointcloud.cpp b/cpp/pybind/t/geometry/pointcloud.cpp index f872b9fcd68..b343debe08c 100644 --- a/cpp/pybind/t/geometry/pointcloud.cpp +++ b/cpp/pybind/t/geometry/pointcloud.cpp @@ -261,9 +261,10 @@ void pybind_pointcloud_definitions(py::module& m) { "non-negative number less than number of points in the " "input pointcloud.", "start_index"_a = 0); - pointcloud.def("remove_radius_outliers", &PointCloud::RemoveRadiusOutliers, - "nb_points"_a, "search_radius"_a, - R"(Remove points that have less than nb_points neighbors in a + pointcloud.def( + "remove_radius_outliers", &PointCloud::RemoveRadiusOutliers, + "nb_points"_a, "search_radius"_a, + R"(Remove points that have less than nb_points neighbors in a sphere of a given search radius. Args: From d2d9f3a862f464dde42e1f59b8cc1cb2db62dd37 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Tue, 19 Aug 2025 18:50:40 -0600 Subject: [PATCH 3/8] Add Octree bin IO --- cpp/open3d/geometry/Octree.cpp | 6 ++--- cpp/open3d/io/OctreeIO.cpp | 19 +++++++++++++ cpp/open3d/io/OctreeIO.h | 11 ++++++++ cpp/open3d/io/file_format/FileBIN.cpp | 39 +++++++++++++++++++++++++++ cpp/tests/io/OctreeIO.cpp | 27 +++++++++++-------- 5 files changed, 88 insertions(+), 14 deletions(-) diff --git a/cpp/open3d/geometry/Octree.cpp b/cpp/open3d/geometry/Octree.cpp index 23b12279cb2..055bac64705 100644 --- a/cpp/open3d/geometry/Octree.cpp +++ b/cpp/open3d/geometry/Octree.cpp @@ -63,7 +63,7 @@ std::shared_ptr OctreeNode::ConstructFromBinaryStream( } else if (std::string(&in[offset], 24) == "OctreePointColorLeafNode") { node = std::make_shared(); } else { - utility::LogError("Unhandled class id {}", class_id); + utility::LogWarning("Unhandled class id {}", class_id); } // Convert from binary @@ -1003,8 +1003,8 @@ bool Octree::SerializeToBinaryStream(std::string& out) const { bool Octree::DeserializeFromBinaryStream(const std::string& in) { size_t offset = 0; // Deserialize the basic attributes - if (in.size() < offset + sizeof(origin_) * 3) return false; - std::memcpy(origin_.data(), &in[offset], sizeof(origin_) * 3); + if (in.size() < offset + sizeof(double) * 3) return false; + std::memcpy(origin_.data(), &in[offset], sizeof(double) * 3); offset += sizeof(double) * 3; if (in.size() < offset + sizeof(size_)) return false; diff --git a/cpp/open3d/io/OctreeIO.cpp b/cpp/open3d/io/OctreeIO.cpp index ae43c3724b2..585a3e5a58a 100644 --- a/cpp/open3d/io/OctreeIO.cpp +++ b/cpp/open3d/io/OctreeIO.cpp @@ -21,6 +21,7 @@ static const std::unordered_map< std::function> file_extension_to_octree_read_function{ {"json", ReadOctreeFromJson}, + {"bin", ReadOctreeFromBIN}, }; static const std::unordered_map< @@ -28,6 +29,7 @@ static const std::unordered_map< std::function> file_extension_to_octree_write_function{ {"json", WriteOctreeToJson}, + {"bin", WriteOctreeToBIN}, }; std::shared_ptr CreateOctreeFromFile( @@ -90,5 +92,22 @@ bool WriteOctreeToJson(const std::string &filename, const geometry::Octree &octree) { return WriteIJsonConvertibleToJSON(filename, octree); } + +bool ReadOctreeFromBIN(const std::string &filename, geometry::Octree &octree) { + std::string bin_data; + if (!ReadOctreeBinaryStreamFromBIN(filename, bin_data)) { + return false; + } + // Deserialize bin_data into octree + octree.DeserializeFromBinaryStream(bin_data); + return true; +} + +bool WriteOctreeToBIN(const std::string &filename, + const geometry::Octree &octree) { + std::string bin_data; + octree.SerializeToBinaryStream(bin_data); + return WriteOctreeBinaryStreamToBIN(filename, bin_data); +} } // namespace io } // namespace open3d diff --git a/cpp/open3d/io/OctreeIO.h b/cpp/open3d/io/OctreeIO.h index 57ee3b3f400..80605f3dda7 100644 --- a/cpp/open3d/io/OctreeIO.h +++ b/cpp/open3d/io/OctreeIO.h @@ -38,5 +38,16 @@ bool ReadOctreeFromJson(const std::string &filename, geometry::Octree &octree); bool WriteOctreeToJson(const std::string &filename, const geometry::Octree &octree); +bool ReadOctreeFromBIN(const std::string &filename, geometry::Octree &octree); + +bool WriteOctreeToBIN(const std::string &filename, + const geometry::Octree &octree); + +bool ReadOctreeBinaryStreamFromBIN(const std::string &filename, + std::string &bin_data); + +bool WriteOctreeBinaryStreamToBIN(const std::string &filename, + const std::string &bin_data); + } // namespace io } // namespace open3d diff --git a/cpp/open3d/io/file_format/FileBIN.cpp b/cpp/open3d/io/file_format/FileBIN.cpp index 27ef362f13b..27c135d5aa4 100644 --- a/cpp/open3d/io/file_format/FileBIN.cpp +++ b/cpp/open3d/io/file_format/FileBIN.cpp @@ -9,6 +9,7 @@ #include #include "open3d/io/FeatureIO.h" +#include "open3d/io/OctreeIO.h" #include "open3d/utility/FileSystem.h" #include "open3d/utility/Logging.h" @@ -83,5 +84,43 @@ bool WriteFeatureToBIN(const std::string &filename, return success; } +bool ReadOctreeBinaryStreamFromBIN(const std::string &filename, + std::string &bin_data) { + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) { + utility::LogWarning("Read BIN failed: unable to open file: {}", + filename); + return false; + } + + // Read file contents into bin_data + bin_data.assign((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + file.close(); + return true; +} + +bool WriteOctreeBinaryStreamToBIN(const std::string &filename, + const std::string &bin_data) { + std::ofstream file(filename, std::ios::binary); + if (!file.is_open()) { + utility::LogWarning("Write BIN failed: unable to open file: {}", + filename); + return false; + } + + file.write(bin_data.data(), bin_data.size()); + if (!file) { + utility::LogWarning("Write BIN failed: error writing to file: {}", + filename); + file.close(); + return false; + } + + file.close(); + return true; +} + } // namespace io } // namespace open3d diff --git a/cpp/tests/io/OctreeIO.cpp b/cpp/tests/io/OctreeIO.cpp index b7b7b083cf9..4e3735e91f6 100644 --- a/cpp/tests/io/OctreeIO.cpp +++ b/cpp/tests/io/OctreeIO.cpp @@ -22,15 +22,16 @@ namespace open3d { namespace tests { -void WriteReadAndAssertEqual(const geometry::Octree& src_octree) { +void WriteReadAndAssertEqual(const geometry::Octree& src_octree, + const std::string& file_name) { // Write to file - std::string file_name = - utility::filesystem::GetTempDirectoryPath() + "/temp_octree.json"; - EXPECT_TRUE(io::WriteOctree(file_name, src_octree)); + std::string full_file_name = + utility::filesystem::GetTempDirectoryPath() + file_name; + EXPECT_TRUE(io::WriteOctree(full_file_name, src_octree)); // Read from file geometry::Octree dst_octree; - EXPECT_TRUE(io::ReadOctree(file_name, dst_octree)); + EXPECT_TRUE(io::ReadOctree(full_file_name, dst_octree)); EXPECT_TRUE(src_octree == dst_octree); } @@ -39,7 +40,8 @@ TEST(OctreeIO, EmptyTree) { ExpectEQ(octree.origin_, Eigen::Vector3d(0, 0, 0)); EXPECT_EQ(octree.size_, 0); - WriteReadAndAssertEqual(octree); + WriteReadAndAssertEqual(octree, "/temp_octree.json"); + WriteReadAndAssertEqual(octree, "/temp_octree.bin"); } TEST(OctreeIO, ZeroDepth) { @@ -49,10 +51,11 @@ TEST(OctreeIO, ZeroDepth) { octree.InsertPoint(point, geometry::OctreeColorLeafNode::GetInitFunction(), geometry::OctreeColorLeafNode::GetUpdateFunction(color)); - WriteReadAndAssertEqual(octree); + WriteReadAndAssertEqual(octree, "/temp_octree.json"); + WriteReadAndAssertEqual(octree, "/temp_octree.bin"); } -TEST(OctreeIO, JsonFileIOFragment) { +TEST(OctreeIO, FileIOFragment) { // Create octree geometry::PointCloud pcd; data::PLYPointCloud pointcloud_ply; @@ -61,10 +64,11 @@ TEST(OctreeIO, JsonFileIOFragment) { geometry::Octree octree(max_depth); octree.ConvertFromPointCloud(pcd, 0.01); - WriteReadAndAssertEqual(octree); + WriteReadAndAssertEqual(octree, "/temp_octree.json"); + WriteReadAndAssertEqual(octree, "/temp_octree.bin"); } -TEST(OctreeIO, JsonFileIOSevenCubes) { +TEST(OctreeIO, FileIOSevenCubes) { // Build octree std::vector points{ Eigen::Vector3d(0.5, 0.5, 0.5), Eigen::Vector3d(1.5, 0.5, 0.5), @@ -83,7 +87,8 @@ TEST(OctreeIO, JsonFileIOSevenCubes) { geometry::OctreeColorLeafNode::GetUpdateFunction(colors[i])); } - WriteReadAndAssertEqual(octree); + WriteReadAndAssertEqual(octree, "/temp_octree.json"); + WriteReadAndAssertEqual(octree, "/temp_octree.bin"); } } // namespace tests From e88f1e54a8a8759c5e3a24bae97bb87677e1dd7c Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Tue, 19 Aug 2025 19:06:59 -0600 Subject: [PATCH 4/8] Use fixed size types in Octree serialization --- cpp/open3d/geometry/Octree.cpp | 52 +++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/cpp/open3d/geometry/Octree.cpp b/cpp/open3d/geometry/Octree.cpp index 055bac64705..7e66386dd53 100644 --- a/cpp/open3d/geometry/Octree.cpp +++ b/cpp/open3d/geometry/Octree.cpp @@ -155,7 +155,7 @@ bool OctreeInternalNode::SerializeToBinaryStream(std::string& out) const { out.append(class_id, sizeof(class_id)); // Write children for (int cid = 0; cid < 8; ++cid) { - bool has_child = (children_[cid] != nullptr); + uint8_t has_child = (children_[cid] != nullptr); out.append(reinterpret_cast(&has_child), sizeof(has_child)); if (has_child) { @@ -173,9 +173,9 @@ bool OctreeInternalNode::DeserializeFromBinaryStream(const std::string& in, offset += 19; // Read children for (int cid = 0; cid < 8; ++cid) { - if (in.size() < offset + sizeof(bool)) return false; - bool has_child = *reinterpret_cast(&in[offset]); - offset += sizeof(bool); + if (in.size() < offset + sizeof(uint8_t)) return false; + uint8_t has_child = *reinterpret_cast(&in[offset]); + offset += sizeof(uint8_t); if (has_child) { children_[cid] = OctreeNode::ConstructFromBinaryStream(in, offset); if (!children_[cid]) { @@ -265,7 +265,7 @@ bool OctreeInternalPointNode::SerializeToBinaryStream(std::string& out) const { out.append(class_id, sizeof(class_id)); // Write children for (int cid = 0; cid < 8; ++cid) { - bool has_child = (children_[cid] != nullptr); + uint8_t has_child = (children_[cid] != nullptr); out.append(reinterpret_cast(&has_child), sizeof(has_child)); if (has_child) { @@ -273,10 +273,10 @@ bool OctreeInternalPointNode::SerializeToBinaryStream(std::string& out) const { } } // Write indices - size_t num_indices = indices_.size(); + uint64_t num_indices = indices_.size(); out.append(reinterpret_cast(&num_indices), sizeof(num_indices)); - for (size_t idx : indices_) { + for (uint64_t idx : indices_) { out.append(reinterpret_cast(&idx), sizeof(idx)); } return rc; @@ -291,9 +291,9 @@ bool OctreeInternalPointNode::DeserializeFromBinaryStream(const std::string& in, // Read children for (int cid = 0; cid < 8; ++cid) { - if (in.size() < offset + sizeof(bool)) return false; - bool has_child = *reinterpret_cast(&in[offset]); - offset += sizeof(bool); + if (in.size() < offset + sizeof(uint8_t)) return false; + uint8_t has_child = *reinterpret_cast(&in[offset]); + offset += sizeof(uint8_t); if (has_child) { children_[cid] = OctreeNode::ConstructFromBinaryStream(in, offset); if (!children_[cid]) { @@ -305,15 +305,15 @@ bool OctreeInternalPointNode::DeserializeFromBinaryStream(const std::string& in, } // Read indices size - if (in.size() < offset + sizeof(size_t)) return false; - size_t indices_size = *reinterpret_cast(&in[offset]); - offset += sizeof(size_t); + if (in.size() < offset + sizeof(uint64_t)) return false; + uint64_t indices_size = *reinterpret_cast(&in[offset]); + offset += sizeof(uint64_t); // Read indices indices_.clear(); - for (size_t i = 0; i < indices_size; ++i) { - if (in.size() < offset + sizeof(size_t)) return false; - size_t idx = *reinterpret_cast(&in[offset]); - offset += sizeof(size_t); + for (uint64_t i = 0; i < indices_size; ++i) { + if (in.size() < offset + sizeof(uint64_t)) return false; + uint64_t idx = *reinterpret_cast(&in[offset]); + offset += sizeof(uint64_t); indices_.push_back(idx); } @@ -484,10 +484,10 @@ bool OctreePointColorLeafNode::SerializeToBinaryStream(std::string& out) const { out.append(reinterpret_cast(color_.data()), sizeof(double) * 3); // Write indices - size_t num_indices = indices_.size(); + uint64_t num_indices = indices_.size(); out.append(reinterpret_cast(&num_indices), sizeof(num_indices)); - for (size_t idx : indices_) { + for (uint64_t idx : indices_) { out.append(reinterpret_cast(&idx), sizeof(idx)); } return rc; @@ -506,9 +506,9 @@ bool OctreePointColorLeafNode::DeserializeFromBinaryStream( offset += sizeof(double) * 3; // Read indices size - if (in.size() < offset + sizeof(size_t)) return false; - size_t indices_size = *reinterpret_cast(&in[offset]); - offset += sizeof(size_t); + if (in.size() < offset + sizeof(uint64_t)) return false; + uint64_t indices_size = *reinterpret_cast(&in[offset]); + offset += sizeof(uint64_t); // Read indices indices_.clear(); for (size_t i = 0; i < indices_size; ++i) { @@ -992,7 +992,7 @@ bool Octree::SerializeToBinaryStream(std::string& out) const { out.append(reinterpret_cast(origin_.data()), sizeof(double) * 3); out.append(reinterpret_cast(&size_), sizeof(double)); - out.append(reinterpret_cast(&max_depth_), sizeof(size_t)); + out.append(reinterpret_cast(&max_depth_), sizeof(uint64_t)); // Serialize the root node if (root_node_) { rc = rc && root_node_->SerializeToBinaryStream(out); @@ -1011,9 +1011,9 @@ bool Octree::DeserializeFromBinaryStream(const std::string& in) { std::memcpy(&size_, &in[offset], sizeof(size_)); offset += sizeof(double); - if (in.size() < offset + sizeof(max_depth_)) return false; - std::memcpy(&max_depth_, &in[offset], sizeof(max_depth_)); - offset += sizeof(size_t); + if (in.size() < offset + sizeof(uint64_t)) return false; + std::memcpy(&max_depth_, &in[offset], sizeof(uint64_t)); + offset += sizeof(uint64_t); // Deserialize the root node root_node_ = OctreeNode::ConstructFromBinaryStream(in, offset); From c85a7acb22d984f204e244f980386d341607db82 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Wed, 20 Aug 2025 10:12:05 -0600 Subject: [PATCH 5/8] Use FILE for the IO operations for Octree --- cpp/open3d/io/file_format/FileBIN.cpp | 44 +++++++++++++++++++-------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/cpp/open3d/io/file_format/FileBIN.cpp b/cpp/open3d/io/file_format/FileBIN.cpp index 27c135d5aa4..40eefed60c7 100644 --- a/cpp/open3d/io/file_format/FileBIN.cpp +++ b/cpp/open3d/io/file_format/FileBIN.cpp @@ -86,39 +86,59 @@ bool WriteFeatureToBIN(const std::string &filename, bool ReadOctreeBinaryStreamFromBIN(const std::string &filename, std::string &bin_data) { - std::ifstream file(filename, std::ios::binary); - if (!file.is_open()) { + FILE *file = utility::filesystem::FOpen(filename, "rb"); + if (file == NULL) { utility::LogWarning("Read BIN failed: unable to open file: {}", filename); return false; } - // Read file contents into bin_data - bin_data.assign((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); + // Get file size + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + fseek(file, 0, SEEK_SET); - file.close(); + if (file_size < 0) { + utility::LogWarning( + "Read BIN failed: unable to determine file size: {}", filename); + fclose(file); + return false; + } + + bin_data.resize(static_cast(file_size)); + if (file_size > 0) { + uint64_t read_size = + fread(&bin_data[0], 1, static_cast(file_size), file); + if (read_size != static_cast(file_size)) { + utility::LogWarning("Read BIN failed: error reading file: {}", + filename); + fclose(file); + return false; + } + } + + fclose(file); return true; } bool WriteOctreeBinaryStreamToBIN(const std::string &filename, const std::string &bin_data) { - std::ofstream file(filename, std::ios::binary); - if (!file.is_open()) { + FILE *file = utility::filesystem::FOpen(filename, "wb"); + if (file == NULL) { utility::LogWarning("Write BIN failed: unable to open file: {}", filename); return false; } - file.write(bin_data.data(), bin_data.size()); - if (!file) { + uint64_t write_size = fwrite(bin_data.data(), 1, bin_data.size(), file); + if (write_size != bin_data.size()) { utility::LogWarning("Write BIN failed: error writing to file: {}", filename); - file.close(); + fclose(file); return false; } - file.close(); + fclose(file); return true; } From a966ed651be587e98f5dda74d13392d2e94b363b Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Wed, 20 Aug 2025 15:57:04 -0600 Subject: [PATCH 6/8] Improve octree node handling --- cpp/open3d/geometry/Octree.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/open3d/geometry/Octree.cpp b/cpp/open3d/geometry/Octree.cpp index 7e66386dd53..b93aad73a53 100644 --- a/cpp/open3d/geometry/Octree.cpp +++ b/cpp/open3d/geometry/Octree.cpp @@ -51,6 +51,7 @@ std::shared_ptr OctreeNode::ConstructFromJsonValue( std::shared_ptr OctreeNode::ConstructFromBinaryStream( const std::string& in, size_t& offset) { + if (in.size() < offset + 18) return nullptr; std::string class_id(&in[offset], 24); std::shared_ptr node = nullptr; @@ -63,7 +64,7 @@ std::shared_ptr OctreeNode::ConstructFromBinaryStream( } else if (std::string(&in[offset], 24) == "OctreePointColorLeafNode") { node = std::make_shared(); } else { - utility::LogWarning("Unhandled class id {}", class_id); + utility::LogError("Unhandled class id {}", class_id); } // Convert from binary From 61535f6b83a7ba1c139bdf1c7b7e306f3da56be9 Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Thu, 21 Aug 2025 12:06:56 -0600 Subject: [PATCH 7/8] Use ifstream and ofstream for ocrtree IO --- cpp/open3d/io/file_format/FileBIN.cpp | 44 ++++++++------------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/cpp/open3d/io/file_format/FileBIN.cpp b/cpp/open3d/io/file_format/FileBIN.cpp index 40eefed60c7..27c135d5aa4 100644 --- a/cpp/open3d/io/file_format/FileBIN.cpp +++ b/cpp/open3d/io/file_format/FileBIN.cpp @@ -86,59 +86,39 @@ bool WriteFeatureToBIN(const std::string &filename, bool ReadOctreeBinaryStreamFromBIN(const std::string &filename, std::string &bin_data) { - FILE *file = utility::filesystem::FOpen(filename, "rb"); - if (file == NULL) { + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) { utility::LogWarning("Read BIN failed: unable to open file: {}", filename); return false; } - // Get file size - fseek(file, 0, SEEK_END); - long file_size = ftell(file); - fseek(file, 0, SEEK_SET); + // Read file contents into bin_data + bin_data.assign((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); - if (file_size < 0) { - utility::LogWarning( - "Read BIN failed: unable to determine file size: {}", filename); - fclose(file); - return false; - } - - bin_data.resize(static_cast(file_size)); - if (file_size > 0) { - uint64_t read_size = - fread(&bin_data[0], 1, static_cast(file_size), file); - if (read_size != static_cast(file_size)) { - utility::LogWarning("Read BIN failed: error reading file: {}", - filename); - fclose(file); - return false; - } - } - - fclose(file); + file.close(); return true; } bool WriteOctreeBinaryStreamToBIN(const std::string &filename, const std::string &bin_data) { - FILE *file = utility::filesystem::FOpen(filename, "wb"); - if (file == NULL) { + std::ofstream file(filename, std::ios::binary); + if (!file.is_open()) { utility::LogWarning("Write BIN failed: unable to open file: {}", filename); return false; } - uint64_t write_size = fwrite(bin_data.data(), 1, bin_data.size(), file); - if (write_size != bin_data.size()) { + file.write(bin_data.data(), bin_data.size()); + if (!file) { utility::LogWarning("Write BIN failed: error writing to file: {}", filename); - fclose(file); + file.close(); return false; } - fclose(file); + file.close(); return true; } From 1fada55e1c46344d4b0bd63d3668240a0ff2f9dc Mon Sep 17 00:00:00 2001 From: "Murillo Rojas, Luis" Date: Fri, 22 Aug 2025 10:30:21 -0600 Subject: [PATCH 8/8] Minor fixes --- cpp/open3d/geometry/Octree.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/cpp/open3d/geometry/Octree.cpp b/cpp/open3d/geometry/Octree.cpp index b93aad73a53..f2d6652e8e2 100644 --- a/cpp/open3d/geometry/Octree.cpp +++ b/cpp/open3d/geometry/Octree.cpp @@ -52,7 +52,6 @@ std::shared_ptr OctreeNode::ConstructFromJsonValue( std::shared_ptr OctreeNode::ConstructFromBinaryStream( const std::string& in, size_t& offset) { if (in.size() < offset + 18) return nullptr; - std::string class_id(&in[offset], 24); std::shared_ptr node = nullptr; if (std::string(&in[offset], 18) == "OctreeInternalNode") { @@ -64,7 +63,8 @@ std::shared_ptr OctreeNode::ConstructFromBinaryStream( } else if (std::string(&in[offset], 24) == "OctreePointColorLeafNode") { node = std::make_shared(); } else { - utility::LogError("Unhandled class id {}", class_id); + utility::LogError("Unhandled class id {}", + std::string(&in[offset], 24)); } // Convert from binary @@ -512,10 +512,10 @@ bool OctreePointColorLeafNode::DeserializeFromBinaryStream( offset += sizeof(uint64_t); // Read indices indices_.clear(); - for (size_t i = 0; i < indices_size; ++i) { - if (in.size() < offset + sizeof(size_t)) return false; - size_t idx = *reinterpret_cast(&in[offset]); - offset += sizeof(size_t); + for (uint64_t i = 0; i < indices_size; ++i) { + if (in.size() < offset + sizeof(uint64_t)) return false; + uint64_t idx = *reinterpret_cast(&in[offset]); + offset += sizeof(uint64_t); indices_.push_back(idx); } @@ -1018,11 +1018,6 @@ bool Octree::DeserializeFromBinaryStream(const std::string& in) { // Deserialize the root node root_node_ = OctreeNode::ConstructFromBinaryStream(in, offset); - if (root_node_) { - if (!root_node_->DeserializeFromBinaryStream(in, offset)) { - return false; - } - } return true; }