From 1e41282a10f2235e32c4e70a36af15232d0bf6b2 Mon Sep 17 00:00:00 2001 From: Leo Stanislas Date: Tue, 21 Sep 2021 14:33:56 -0400 Subject: [PATCH 1/4] Add pickle support to LasHeader with tests. --- python/bindings.cpp | 55 ++++++++++++++++++++++++++++++++++++++++- src/las/header.cpp | 2 ++ test/las_header_test.py | 36 +++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/python/bindings.cpp b/python/bindings.cpp index 65783a1d..c02d5ba8 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -277,7 +277,60 @@ PYBIND11_MODULE(copclib, m) .def_readwrite("wave_offset", &las::LasHeader::wave_offset) .def_readwrite("evlr_offset", &las::LasHeader::evlr_offset) .def_readwrite("evlr_count", &las::LasHeader::evlr_count) - .def_readwrite("points_by_return_14", &las::LasHeader::points_by_return_14); + .def_readwrite("point_count_14", &las::LasHeader::point_count) + .def_readwrite("points_by_return_14", &las::LasHeader::points_by_return_14) + .def(py::pickle( + [](const las::LasHeader &h) { // __getstate__ + /* Return a tuple that fully encodes the state of the object */ + return py::make_tuple(h.file_source_id, h.global_encoding, h.GUID(), h.version_major, h.version_minor, + h.SystemIdentifier(), h.GeneratingSoftware(), h.creation_day, h.creation_year, + h.header_size, h.point_offset, h.vlr_count, h.point_format_id, + h.point_record_length, h.point_count, h.points_by_return, h.scale.x, h.scale.y, + h.scale.z, h.offset.x, h.offset.y, h.offset.z, h.max.x, h.min.x, h.max.y, h.min.y, + h.max.z, h.min.z, h.wave_offset, h.evlr_offset, h.evlr_count, h.point_count_14, + h.points_by_return_14); + }, + [](py::tuple t) { // __setstate__ + if (t.size() != 33) + throw std::runtime_error("Invalid state!"); + + /* Create a new C++ instance */ + las::LasHeader h; + h.file_source_id = t[0].cast(); + h.global_encoding = t[1].cast(); + h.GUID(t[2].cast()); + h.version_major = t[3].cast(); + h.version_minor = t[4].cast(); + h.SystemIdentifier(t[5].cast()); + h.GeneratingSoftware(t[6].cast()); + h.creation_day = t[7].cast(); + h.creation_year = t[8].cast(); + h.header_size = t[9].cast(); + h.point_offset = t[10].cast(); + h.vlr_count = t[11].cast(); + h.point_format_id = t[12].cast(); + h.point_record_length = t[13].cast(); + h.point_count = t[14].cast(); + h.points_by_return = t[15].cast>(); + h.scale.x = t[16].cast(); + h.scale.y = t[17].cast(); + h.scale.z = t[18].cast(); + h.offset.x = t[19].cast(); + h.offset.y = t[20].cast(); + h.offset.z = t[21].cast(); + h.max.x = t[22].cast(); + h.min.x = t[23].cast(); + h.max.y = t[24].cast(); + h.min.y = t[25].cast(); + h.max.z = t[26].cast(); + h.min.z = t[27].cast(); + h.wave_offset = t[28].cast(); + h.evlr_offset = t[29].cast(); + h.evlr_count = t[30].cast(); + h.point_count_14 = t[31].cast(); + h.points_by_return_14 = t[32].cast>(); + return h; + })); py::class_(m, "LasConfig") .def(py::init(), py::arg("point_format_id"), diff --git a/src/las/header.cpp b/src/las/header.cpp index e8a5df82..67b3fe1c 100644 --- a/src/las/header.cpp +++ b/src/las/header.cpp @@ -44,6 +44,7 @@ LasHeader LasHeader::FromLazPerf(const lazperf::header14 &header) h.wave_offset = header.wave_offset; h.evlr_offset = header.evlr_offset; h.evlr_count = header.evlr_count; + h.point_count_14 = header.point_count_14; std::copy(std::begin(header.points_by_return_14), std::end(header.points_by_return_14), std::begin(h.points_by_return_14)); return h; @@ -86,6 +87,7 @@ lazperf::header14 LasHeader::ToLazPerf() const h.wave_offset = wave_offset; h.evlr_offset = evlr_offset; h.evlr_count = evlr_count; + h.point_count_14 = point_count_14; std::copy(std::begin(points_by_return_14), std::end(points_by_return_14), std::begin(h.points_by_return_14)); return h; diff --git a/test/las_header_test.py b/test/las_header_test.py index 4f22c1ae..ec1557ce 100644 --- a/test/las_header_test.py +++ b/test/las_header_test.py @@ -1,3 +1,4 @@ +import pickle import copclib as copc @@ -34,3 +35,38 @@ def test_las_header(): assert las_header.evlr_offset == las_header.evlr_offset assert las_header.evlr_count == las_header.evlr_count assert las_header.points_by_return_14 == las_header.points_by_return_14 + + +def test_pickling(): + + reader = copc.FileReader("../test/data/autzen-classified.copc.laz") + las_header = reader.GetLasHeader() + + data = pickle.dumps(las_header, -1) + las_header2 = pickle.loads(data) + + assert las_header.file_source_id == las_header2.file_source_id + assert las_header.global_encoding == las_header2.global_encoding + assert las_header.guid == las_header2.guid + assert las_header.version_major == las_header2.version_major + assert las_header.version_minor == las_header2.version_minor + assert las_header.system_identifier == las_header2.system_identifier + assert las_header.generating_software == las_header2.generating_software + assert las_header.creation_day == las_header2.creation_day + assert las_header.creation_year == las_header2.creation_year + assert las_header.header_size == las_header2.header_size + assert las_header.point_offset == las_header2.point_offset + assert las_header.vlr_count == las_header2.vlr_count + assert las_header.point_format_id == las_header2.point_format_id + assert las_header.point_record_length == las_header2.point_record_length + assert las_header.point_count == las_header2.point_count + assert las_header.points_by_return == las_header2.points_by_return + assert las_header.scale == las_header2.scale + assert las_header.offset == las_header2.offset + assert las_header.max == las_header2.max + assert las_header.min == las_header2.min + assert las_header.wave_offset == las_header2.wave_offset + assert las_header.evlr_offset == las_header2.evlr_offset + assert las_header.evlr_count == las_header2.evlr_count + assert las_header.point_count_14 == las_header2.point_count_14 + assert las_header.points_by_return_14 == las_header2.points_by_return_14 From 677fc60870d2f0cc267755259643cdbe7050de43 Mon Sep 17 00:00:00 2001 From: Leo Stanislas Date: Tue, 21 Sep 2021 17:26:44 -0400 Subject: [PATCH 2/4] Add VectorChar pickling. --- python/bindings.cpp | 14 +++++++++++++- test/las_header_test.py | 12 ++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/python/bindings.cpp b/python/bindings.cpp index c02d5ba8..7b6b9000 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -24,7 +25,18 @@ PYBIND11_MAKE_OPAQUE(std::vector); PYBIND11_MODULE(copclib, m) { - py::bind_vector>(m, "VectorChar", py::buffer_protocol()); + + py::bind_vector>(m, "VectorChar", py::buffer_protocol()) + .def(py::pickle( + [](const std::vector &vec) { // __getstate__ + // Convert vector to string for pickling + return py::make_tuple(std::string(vec.begin(), vec.end())); + }, + [](const py::tuple &t) { // __setstate__ + auto s = t[0].cast(); + // Convert string back to vector for unpickling + return std::vector(s.begin(), s.end()); + })); py::class_(m, "VoxelKey") .def(py::init<>()) diff --git a/test/las_header_test.py b/test/las_header_test.py index ec1557ce..1a6e40de 100644 --- a/test/las_header_test.py +++ b/test/las_header_test.py @@ -70,3 +70,15 @@ def test_pickling(): assert las_header.evlr_count == las_header2.evlr_count assert las_header.point_count_14 == las_header2.point_count_14 assert las_header.points_by_return_14 == las_header2.points_by_return_14 + + # Also tests VectorChar pickling while we're testing pickle + test_vec = copc.VectorChar() + test_vec.append("t") + test_vec.append("e") + test_vec.append("s") + test_vec.append("t") + data = pickle.dumps(test_vec, -1) + test_vec2 = pickle.loads(data) + + for c1, c2 in zip(test_vec, test_vec2): + assert c1 == c2 From 45b16d2a27bbf89e34120bfb7ecf1dc2bb59ec45 Mon Sep 17 00:00:00 2001 From: Leo Stanislas Date: Tue, 21 Sep 2021 17:37:38 -0400 Subject: [PATCH 3/4] Add Vector3 pickling. --- python/bindings.cpp | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/python/bindings.cpp b/python/bindings.cpp index 7b6b9000..afc00f21 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -90,7 +90,14 @@ PYBIND11_MODULE(copclib, m) .def("DefaultOffset", &Vector3::DefaultOffset) .def(py::self == py::self) .def("__str__", &Vector3::ToString) - .def("__repr__", &Vector3::ToString); + .def("__repr__", &Vector3::ToString) + .def(py::pickle( + [](const Vector3 &vec) { // __getstate__ + return py::make_tuple(vec.x, vec.y, vec.z); + }, + [](const py::tuple &t) { // __setstate__ + return Vector3(t[0].cast(), t[1].cast(), t[2].cast()); + })); py::implicitly_convertible(); @@ -297,13 +304,12 @@ PYBIND11_MODULE(copclib, m) return py::make_tuple(h.file_source_id, h.global_encoding, h.GUID(), h.version_major, h.version_minor, h.SystemIdentifier(), h.GeneratingSoftware(), h.creation_day, h.creation_year, h.header_size, h.point_offset, h.vlr_count, h.point_format_id, - h.point_record_length, h.point_count, h.points_by_return, h.scale.x, h.scale.y, - h.scale.z, h.offset.x, h.offset.y, h.offset.z, h.max.x, h.min.x, h.max.y, h.min.y, - h.max.z, h.min.z, h.wave_offset, h.evlr_offset, h.evlr_count, h.point_count_14, + h.point_record_length, h.point_count, h.points_by_return, h.scale, h.offset, + h.max, h.min, h.wave_offset, h.evlr_offset, h.evlr_count, h.point_count_14, h.points_by_return_14); }, [](py::tuple t) { // __setstate__ - if (t.size() != 33) + if (t.size() != 25) throw std::runtime_error("Invalid state!"); /* Create a new C++ instance */ @@ -324,23 +330,15 @@ PYBIND11_MODULE(copclib, m) h.point_record_length = t[13].cast(); h.point_count = t[14].cast(); h.points_by_return = t[15].cast>(); - h.scale.x = t[16].cast(); - h.scale.y = t[17].cast(); - h.scale.z = t[18].cast(); - h.offset.x = t[19].cast(); - h.offset.y = t[20].cast(); - h.offset.z = t[21].cast(); - h.max.x = t[22].cast(); - h.min.x = t[23].cast(); - h.max.y = t[24].cast(); - h.min.y = t[25].cast(); - h.max.z = t[26].cast(); - h.min.z = t[27].cast(); - h.wave_offset = t[28].cast(); - h.evlr_offset = t[29].cast(); - h.evlr_count = t[30].cast(); - h.point_count_14 = t[31].cast(); - h.points_by_return_14 = t[32].cast>(); + h.scale = t[16].cast(); + h.offset = t[17].cast(); + h.max = t[18].cast(); + h.min = t[19].cast(); + h.wave_offset = t[20].cast(); + h.evlr_offset = t[21].cast(); + h.evlr_count = t[22].cast(); + h.point_count_14 = t[23].cast(); + h.points_by_return_14 = t[24].cast>(); return h; })); From 09c75cbcfda4df84a92db187d6e369bc19a5cecf Mon Sep 17 00:00:00 2001 From: Leo Stanislas Date: Tue, 21 Sep 2021 17:45:01 -0400 Subject: [PATCH 4/4] Add Vector3 pickling and move pickle tests to pickle_test.py. --- test/las_header_test.py | 47 ---------------------------------- test/pickle_test.py | 56 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 47 deletions(-) create mode 100644 test/pickle_test.py diff --git a/test/las_header_test.py b/test/las_header_test.py index 1a6e40de..c7e68648 100644 --- a/test/las_header_test.py +++ b/test/las_header_test.py @@ -35,50 +35,3 @@ def test_las_header(): assert las_header.evlr_offset == las_header.evlr_offset assert las_header.evlr_count == las_header.evlr_count assert las_header.points_by_return_14 == las_header.points_by_return_14 - - -def test_pickling(): - - reader = copc.FileReader("../test/data/autzen-classified.copc.laz") - las_header = reader.GetLasHeader() - - data = pickle.dumps(las_header, -1) - las_header2 = pickle.loads(data) - - assert las_header.file_source_id == las_header2.file_source_id - assert las_header.global_encoding == las_header2.global_encoding - assert las_header.guid == las_header2.guid - assert las_header.version_major == las_header2.version_major - assert las_header.version_minor == las_header2.version_minor - assert las_header.system_identifier == las_header2.system_identifier - assert las_header.generating_software == las_header2.generating_software - assert las_header.creation_day == las_header2.creation_day - assert las_header.creation_year == las_header2.creation_year - assert las_header.header_size == las_header2.header_size - assert las_header.point_offset == las_header2.point_offset - assert las_header.vlr_count == las_header2.vlr_count - assert las_header.point_format_id == las_header2.point_format_id - assert las_header.point_record_length == las_header2.point_record_length - assert las_header.point_count == las_header2.point_count - assert las_header.points_by_return == las_header2.points_by_return - assert las_header.scale == las_header2.scale - assert las_header.offset == las_header2.offset - assert las_header.max == las_header2.max - assert las_header.min == las_header2.min - assert las_header.wave_offset == las_header2.wave_offset - assert las_header.evlr_offset == las_header2.evlr_offset - assert las_header.evlr_count == las_header2.evlr_count - assert las_header.point_count_14 == las_header2.point_count_14 - assert las_header.points_by_return_14 == las_header2.points_by_return_14 - - # Also tests VectorChar pickling while we're testing pickle - test_vec = copc.VectorChar() - test_vec.append("t") - test_vec.append("e") - test_vec.append("s") - test_vec.append("t") - data = pickle.dumps(test_vec, -1) - test_vec2 = pickle.loads(data) - - for c1, c2 in zip(test_vec, test_vec2): - assert c1 == c2 diff --git a/test/pickle_test.py b/test/pickle_test.py new file mode 100644 index 00000000..2eb283c9 --- /dev/null +++ b/test/pickle_test.py @@ -0,0 +1,56 @@ +import pickle +import copclib as copc + + +def test_vector_char(): + char_vec = copc.VectorChar() + char_vec.append("t") + char_vec.append("e") + char_vec.append("s") + char_vec.append("t") + + char_vec_other = pickle.loads(pickle.dumps(char_vec, -1)) + + assert char_vec == char_vec_other + + +def test_vector3(): + vec3 = copc.Vector3(0, 1, 2) + + vec3_other = pickle.loads(pickle.dumps(vec3, -1)) + + assert vec3 == vec3_other + + +def test_las_header(): + + reader = copc.FileReader("../test/data/autzen-classified.copc.laz") + las_header = reader.GetLasHeader() + + las_header_other = pickle.loads(pickle.dumps(las_header, -1)) + + assert las_header.file_source_id == las_header_other.file_source_id + assert las_header.global_encoding == las_header_other.global_encoding + assert las_header.guid == las_header_other.guid + assert las_header.version_major == las_header_other.version_major + assert las_header.version_minor == las_header_other.version_minor + assert las_header.system_identifier == las_header_other.system_identifier + assert las_header.generating_software == las_header_other.generating_software + assert las_header.creation_day == las_header_other.creation_day + assert las_header.creation_year == las_header_other.creation_year + assert las_header.header_size == las_header_other.header_size + assert las_header.point_offset == las_header_other.point_offset + assert las_header.vlr_count == las_header_other.vlr_count + assert las_header.point_format_id == las_header_other.point_format_id + assert las_header.point_record_length == las_header_other.point_record_length + assert las_header.point_count == las_header_other.point_count + assert las_header.points_by_return == las_header_other.points_by_return + assert las_header.scale == las_header_other.scale + assert las_header.offset == las_header_other.offset + assert las_header.max == las_header_other.max + assert las_header.min == las_header_other.min + assert las_header.wave_offset == las_header_other.wave_offset + assert las_header.evlr_offset == las_header_other.evlr_offset + assert las_header.evlr_count == las_header_other.evlr_count + assert las_header.point_count_14 == las_header_other.point_count_14 + assert las_header.points_by_return_14 == las_header_other.points_by_return_14