From 6b8e92ca41e39ae70747e958b862beb705d56d5a Mon Sep 17 00:00:00 2001 From: Leo Stanislas Date: Thu, 28 Oct 2021 12:23:29 -0400 Subject: [PATCH] copc-lib v2 (update to latest spec, incl. extents and prdf 6-8) (#51) * FEAT-437: Tackle some TODOs. - Fix static function calls in python - Update operator definitions * FEAT-437: Implement LasBase Class. * FEAT-437: Add ToString functions to LasHeader and LasConfig. * FEAT-437: Fix post-merge issues. * FEAT-437: Add python implicit conversion of VoxelKey to list and tuple. * FEAT-431: Setup scaffolding for copc extents VLR. * FEAT-431: Implement WriterInternal::WriteExtents. * FEAT-431: Implement extents after lazperf update, bug in reading extents. * FEAT-431: Fix remaining bug. * FEAT-431: Add python bindings. * FEAT-431: Add writer extents initialization and tests for extent size. * FEAT-431: Add CopcStats. Buggy. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * FEAT-431: Fix bug and integrate CopcExtents class to code. Buggy. * FEAT-431: Move and update CopcExtent. * FEAT-431: Clang and re-organize extents. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * FEAT-431: Fix remaining issues with extents. * FEAT-431: Add python bindings. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * FEAT-431: Add extents to example-writer.cpp. * FEAT-434: Implement name changes for new Info VLR. * FEAT-434: Move WKT and Extents as VLR. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * FEAT-434: First round of addressing reviews, more to come. * FEAT-434: Fix bugs after testing with new autzen file. - Make python las header etc as properties instead of functions. - Update values of test based on test file to match new file * FEAT-434: Remove parentheses for def_static python functions. * FEAT-434: Make Extents a vector of shared pointers. * FEAT-434: Make VLR class to merge lazperf VLR and EVLRs. * FEAT-434: Remove debug comment. * fix headers * fix pathes and cmakefile * add updated autzen test file temporarily * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Remove temporary test file, update test file link * FEAT-434: Updated tests for new hobu test file. * FEAT-434: Update CI tests to use laz-perf master branch * FEAT-434: Fix CI changes. * FEAT-434: Fix CI changes. * FEAT-434: Fix CI changes. * FEAT-434: Fix CI changes. * FEAT-434: Implement CopcConfig. * FEAT-434: Fix typo. * FEAT-434: Add Extents to Reader tests. * FEAT-434: Addressing PR comments. - Pin Autzen test file commit - Address extents TODOs - Update CI to fix windows - Update VLR reading functions - Add Extent implicit conversion to tuple * FEAT-434: Fix CI tests * fix changelog history * FEAT-434: Update EbByteSize and NumEbItems for clarity. * FEAT-434: More renaming of EBs. * FEAT-434: Update lazperf version in README.md. * FEAT-434: Remove wave_offset setting. * FEAT-434: Remove PRDF setter from Extents. * FEAT-434: Remove X,Y,Z from COPC extents. * update test data * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * FEAT-434: Update Point and Points to support only PRDF 6-8. * FEAT-434: Update Point and Points to support only PRDF 6-8. * FEAT-434: Update writer_node_test.cpp with new data. * FEAT-434: Fix writer_node_test.cpp. * FEAT-434: Add handle for wrong file paths for FileReader and FileWriter. * FEAT-434: Update CHANGELOG.md. * FEAT-434: Update use of VoxelKey to implicit tuple. * Remove windows tests. * FEAT-434: Fix extents constructor. * add lazperf build instructions * default copc-lib to release mode build * FEAT-434: Initial changes. * FEAT-434: More progress. * FEAT-434: Cpp side working and test pass. * FEAT-434: Fix python side. * FEAT-434: Add tests to writer_test.cpp to update cfg values. * FEAT-434: Add tests to writer_test.py to update cfg values. * FEAT-434: Address PR reviews. * FEAT-434: Add TODO. * FEAT-434: Naming Convention Start. * FEAT-434: Naming Convention Fix-up. * FEAT-434: Naming Convention Fix-up. * FEAT-434: Naming Convention Fix-up. * FEAT-434: PR Review pt1. * FEAT-434: PR Review pt2. - CopcConfig tests - Copy constructors - Rename Point bit fields members - Check WKT bit - Make WKT VLR optional * FEAT-434: PR Review pt2. - Clean-up - CHANGELOG.md update * FEAT-434: Add extended stats (mean/var) to Extents. * FEAT-434: Update extended stats VLR header. * FEAT-434: Update extended stats VLR header. * FEAT-434: CHANGELOG.md and README.md updates. * FEAT-434: Name changes. * FEAT-434: Accronyms name changes. * FEAT-434: Merge with main. * FEAT-434: Rename ScanAngleFloat to ScanAngleDegrees. * FEAT-434: Add optional flag for extended stats. * FEAT-434: Add extents to examples files. * python name changes and readme update * final renaming * update changelog Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christopher Lee --- .github/workflows/run_tests.yml | 25 +- CHANGELOG.md | 53 ++ CMakeLists.txt | 16 +- README.md | 103 ++- cmake/DownloadExampleFiles.cmake | 2 +- cpp/CMakeLists.txt | 9 +- cpp/include/copc-lib/copc/config.hpp | 80 ++ cpp/include/copc-lib/copc/extents.hpp | 195 +++++ cpp/include/copc-lib/copc/file.hpp | 35 - cpp/include/copc-lib/copc/info.hpp | 30 + cpp/include/copc-lib/geometry/box.hpp | 7 +- cpp/include/copc-lib/geometry/vector3.hpp | 2 +- .../copc-lib/hierarchy/internal/hierarchy.hpp | 8 +- cpp/include/copc-lib/hierarchy/key.hpp | 18 +- cpp/include/copc-lib/io/base_io.hpp | 17 +- .../copc-lib/io/internal/writer_internal.hpp | 23 +- cpp/include/copc-lib/io/reader.hpp | 34 +- cpp/include/copc-lib/io/writer.hpp | 94 +- cpp/include/copc-lib/las/file.hpp | 37 - cpp/include/copc-lib/las/header.hpp | 102 ++- cpp/include/copc-lib/las/point.hpp | 385 ++------- cpp/include/copc-lib/las/points.hpp | 21 +- cpp/include/copc-lib/las/utils.hpp | 10 +- cpp/include/copc-lib/las/vlr.hpp | 22 +- cpp/include/copc-lib/laz/compressor.hpp | 23 +- cpp/include/copc-lib/laz/decompressor.hpp | 14 +- cpp/src/copc/config.cpp | 28 + cpp/src/copc/extents.cpp | 167 ++++ cpp/src/copc/info.cpp | 45 + cpp/src/geometry/box.cpp | 7 +- cpp/src/hierarchy/key.cpp | 12 +- cpp/src/io/reader.cpp | 194 +++-- cpp/src/io/writer_internal.cpp | 165 ++-- cpp/src/io/writer_public.cpp | 80 +- cpp/src/las/header.cpp | 127 ++- cpp/src/las/point.cpp | 206 ++--- cpp/src/las/points.cpp | 35 +- cpp/src/las/utils.cpp | 80 +- cpp/src/las/vlr.cpp | 58 ++ example/example-reader.cpp | 34 +- example/example-reader.py | 39 +- example/example-writer.cpp | 89 +- example/example-writer.py | 150 ++-- python/CMakeLists.txt | 2 +- python/bindings.cpp | 389 +++++---- test/box_test.py | 10 +- test/config_test.cpp | 168 ++++ test/data/.gitignore | 1 + test/data/create_test_data.py | 43 + test/data/create_test_data.sh | 9 + test/data/las/first_20_pts.las | Bin 1055 -> 0 bytes test/data/las/first_20_pts.laz | Bin 827 -> 0 bytes test/data/las/last_60_pts.las | Bin 2415 -> 0 bytes test/data/las/last_60_pts.laz | Bin 1488 -> 0 bytes test/data/las/next_12_pts.las | Bin 783 -> 0 bytes test/data/las/next_12_pts.laz | Bin 741 -> 0 bytes test/data/test_data.h | 516 +++++------ test/extents_test.cpp | 189 ++++ test/extents_test.py | 128 +++ test/hierarchy_test.cpp | 23 - test/key_test.cpp | 21 +- test/key_test.py | 55 +- test/las_header_test.cpp | 70 +- test/las_header_test.py | 6 +- test/pickle_test.py | 8 +- test/point_test.cpp | 415 ++------- test/point_test.py | 818 ++++++------------ test/points_test.cpp | 63 +- test/points_test.py | 338 ++++---- test/reader_node_test.cpp | 57 +- test/reader_test.cpp | 147 ++-- test/reader_test.py | 106 ++- test/vector3_test.py | 4 +- test/writer_node_test.cpp | 99 +-- test/writer_test.cpp | 361 ++++---- test/writer_test.py | 291 ++++--- 76 files changed, 3884 insertions(+), 3334 deletions(-) create mode 100644 cpp/include/copc-lib/copc/config.hpp create mode 100644 cpp/include/copc-lib/copc/extents.hpp delete mode 100644 cpp/include/copc-lib/copc/file.hpp create mode 100644 cpp/include/copc-lib/copc/info.hpp delete mode 100644 cpp/include/copc-lib/las/file.hpp create mode 100644 cpp/src/copc/config.cpp create mode 100644 cpp/src/copc/extents.cpp create mode 100644 cpp/src/copc/info.cpp create mode 100644 cpp/src/las/vlr.cpp create mode 100644 test/config_test.cpp create mode 100644 test/data/.gitignore create mode 100644 test/data/create_test_data.py create mode 100644 test/data/create_test_data.sh delete mode 100644 test/data/las/first_20_pts.las delete mode 100644 test/data/las/first_20_pts.laz delete mode 100644 test/data/las/last_60_pts.las delete mode 100644 test/data/las/last_60_pts.laz delete mode 100644 test/data/las/next_12_pts.las delete mode 100644 test/data/las/next_12_pts.laz create mode 100644 test/extents_test.cpp create mode 100644 test/extents_test.py diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 42a84371..a1be404f 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-18.04, macos-10.15, windows-latest] + os: [ubuntu-18.04, macos-10.15] #, windows-latest] python-version: [3.6, 3.7, 3.8, 3.9] steps: @@ -36,12 +36,33 @@ jobs: - name: Install dependencies run: | - conda install -c conda-forge "laz-perf>=2.1.0" Catch2 pybind11 --yes + conda install -c conda-forge Catch2 pybind11 --yes python -m pip install --upgrade pip pip install pytest wheel shell: bash -l {0} + - name: Checkout Lazperf + uses: actions/checkout@v2 + with: + path: lazperf + repository: hobu/laz-perf + ref: 4819611b279cb791508a0ac0cedd913f8c1d2103 + + - name: Build lazperf + shell: bash -l {0} + working-directory: lazperf + run: | + mkdir build + cd build + cmake .. -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -Dgtest_force_shared_crt=ON + cmake --build . --config ${{env.BUILD_TYPE}} + if [ "$RUNNER_OS" == "Windows" ]; then + cmake --install . + else + sudo cmake --install . + fi + - name: Build Copclib Python Bindings run: | pip install . diff --git a/CHANGELOG.md b/CHANGELOG.md index dffea2bd..4e3f44f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **\[Python/C++\]** Add `CopcExtent` and `CopcExtents` classes +- **\[Python/C++\]** Add extended stats (mean/var) to `CopcExtent` using an additional Lazperf extents VLR (see README.md for more info). +- **\[Python/C++\]** Add `GetCopcExtents` function to `CopcConfig` +- **\[Python/C++\]** Add `GetCopcExtents` and `SetCopcExtents` functions to `Writer` +- **\[Python\]** Add `reader.extents` property +- **\[Python/C++\]** Add `CopcInfo` class +- **\[Python/C++\]** Add vector(c++)/list(python) constructor to `VoxelKey` +- **\[Python/C++\]** Add `CopcConfig` class +- **\[Python/C++\]** Add `GetCopcConfig` function to `BaseIO` +- **\[Python/C++\]** Support COPC Extents, WKT, and Extra Bytes as VLR or EVLR +- **\[Python/C++\]** Add `SetCopcExtents` and `SetCopcInfo` to `Writer` +- **\[Python/C++\]** Add `LasHeaderBase` class +- **\[Python/C++\]** Add `ToString` to `LasHeader` class +- **\[Python/C++\]** Add `VlrHeader` class +- **\[Python/C++\]** Add a check for `WKT` byte in `LasHeader::FromLazPerf` + +### Changed + +- **\[Python/C++\]** Supported LAS point formats are now strictly 6 to 8 throughout the library +- **\[Python/C++\]** Remove `point_count_14`, `points_by_return_14`, `header_size_`, `wave_offset`, and `version` from `LasHeader` +- **\[Python/C++\]** Change lazperf version requirement to > 2.1.0 +- **\[Python/C++\]** Update autzen-classified.copc.laz test file to be latest official +- **\[Python/C++\]** Change use of `las::CopcVlr::span` to `CopcInfo::spacing` +- **\[Python/C++\]** Change `CopcConfig::GetWkt` return type from `las::WktVlr` to `std::string` +- **\[Python/C++\]** Rename `CopcConfig::GetCopc` to `CopcConfig::GetCopcInfo` and now returns a `CopcInfo` class +- **\[Python/C++\]** Rename `Reader::GetCopcHeader` to `Reader::GetCopcInfo` and now returns a `CopcInfo` class +- **\[Python/C++\]** `VoxelKey::Resolution` and `VoxelKey::GetResolutionAtDepth` now take `CopcInfo` argument instead of `las::CopcVlr` +- **\[Python/C++\]** Remove `Writer::LasConfig`, now using `CopcConfig` instead. +- **\[Python/C++\]** `Writer` and `FileWriter` constructors now takes a `CopcConfig` class as argument +- **\[Python/C++\]** Rename `NumExtraBytes` to `EbByteSize` +- **\[Python\]** Change `reader.GetCopcHeader()` function to `reader.copc_info` property +- **\[Python\]** Change `reader.CopcConfig().LasHeader()` function to `reader.las_header` property +- **\[Python\]** Change `reader.CopcConfig().Wkt()` function to `reader.wkt` property +- **\[Python\]** Change `reader.CopcConfig().ExtraBytesVlr()` function to `reader.extra_bytes_vlr` property +- **\[C++\]** Add `create_test_data.py` and `create_test_data.sh` to create data for `writer_node_test.cpp` +- **\[Python\]** Make `VoxelKey.BaseKey` and `VoxelKey.InvalidKey` static functions +- **\[Python\]** Update `Point` and `Points` property names from `CamelCase` to `under_score` +- **\[Python/C++\]** Rename `Reader::GetAllChildren(VoxelKey)` to `Reader::GetAllChildrenOfPage(VoxelKey)` +- **\[Python/C++\]** Rename `Reader::GetAllChildren()` to `Reader::GetAllNodes()` +- **\[C++\]** Rename `PointFormatID` to `PointFormatId` +- **\[C++\]** Rename `PointSourceID` to `PointSourceId` +- **\[C++\]** Rename `HasRGB` and `HasNIR` to `HasRgb` and `HasNir` respectively +- **\[C++\]** Rename `NIR` to `Nir` +- **\[Python/C++\]** Rename `ScanAngleFloat` to `ScanAngleDegrees` +- **\[Python\]** Rename `unscaled_x`, `unscaled_y`, `unscaled_z` to `x`, `y`, `z` +- **\[General\]** Rename CMake flag `WITH_TESTS_AND_EXAMPLES` to `WITH_TESTS` +- **\[Python/C++\]** Rename `Box::ZeroBox` to `Box::EmptyBox` +- **\[Python/C++\]** Rename `VoxelKey::BaseKey` to `VoxelKey::RootKey` + ## [1.3.1] - 2021-10-19 ### Added @@ -28,6 +79,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **\[Python/C++\]** Change order of arguments in `VoxelKey` spatial functions `Intersects`, `Contains`, and `Within` - **\[Python/C++\]** Add optional `resolution` argument to `Reader` spatial query functions `GetNodesWithinBox`, `GetNodesIntersectBox`, `GetPointsWithinBox`, and `GetAllPoints` . `resolution` can be used to limit the resolution during spatial queries - **\[Python/C++\]** Update `span` of `autzen-classified.copc.laz` test file from 0 to 128 +- **\[Python/C++\]** Rename `ExtendedReturnsBitFields` to `ReturnsBitField` and `ExtendedFlagsBitFields` to `FlagsBitField` in `Point` class +- **\[Python/C++\]** Make WKT VLR optional ### Fixed diff --git a/CMakeLists.txt b/CMakeLists.txt index 826beae9..147ca3d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,13 +13,13 @@ include(GNUInstallDirs) # Shared/Dynamic or Static library? option(BUILD_SHARED_LIBS "Build libraries as shared as opposed to static" ON) -option(WITH_TESTS_AND_EXAMPLES "Build test and example files." OFF) +option(WITH_TESTS "Build test and example files." OFF) option(WITH_PYTHON "Build python bindings." OFF) option(ONLY_PYTHON "Build only python bindings, for setup.py env" OFF) if (ONLY_PYTHON) set(WITH_PYTHON ON) - set(WITH_TESTS_AND_EXAMPLES OFF) + set(WITH_TESTS OFF) set(BUILD_SHARED_LIBS ON) endif() @@ -76,7 +76,7 @@ set(CMAKE_CXX_EXTENSIONS ON) # find_package(YCM REQUIRED) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -if(WITH_TESTS_AND_EXAMPLES) +if(WITH_TESTS) enable_testing() endif() @@ -90,6 +90,14 @@ add_install_rpath_support(BIN_DIRS "${CMAKE_INSTALL_FULL_BINDIR}" INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}" USE_LINK_PATH) +# Encourage user to specify a build type (e.g. Release, Debug, etc.), otherwise set it to Release. +if(NOT CMAKE_CONFIGURATION_TYPES) + if(NOT CMAKE_BUILD_TYPE) + message(STATUS "Setting build type to 'Release' as none was specified.") + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY VALUE "Release") + endif() +endif() + ### Compile- and install-related commands. add_subdirectory(cpp) @@ -121,7 +129,7 @@ install_basic_package_files(${PROJECT_NAME} include(AddUninstallTarget) # Build Submodules -if (WITH_TESTS_AND_EXAMPLES) +if (WITH_TESTS) add_subdirectory(test) add_subdirectory(example) endif() diff --git a/README.md b/README.md index 9941facf..d28fc177 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,23 @@ copc-lib is a library which provides an easy-to-use reader and writer interface copc-lib has the following dependencies: -- [laz-perf](https://github.com/hobu/laz-perf) >= 2.1.0 +- laz-perf >= [commit 4819611](https://github.com/hobu/laz-perf/commits/4819611b279cb791508a0ac0cedd913f8c1d2103) - Catch2 - Pybind11 +To build the latest version of laz-perf: + +```bash +git clone https://github.com/hobu/laz-perf.git +cd laz-perf +git checkout 4819611b279cb791508a0ac0cedd913f8c1d2103 +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +cmake --build . +sudo cmake --install . +``` + ### C++ ```bash @@ -56,7 +69,7 @@ To build the copc-lib examples and unit tests along with the main library, you m ```bash mkdir build && cd build -cmake .. -DWITH_TESTS_AND_EXAMPLES=ON +cmake .. -DWITH_TESTS=ON cmake --build . ``` @@ -97,8 +110,30 @@ for point in points.Get(): - \[x\] Add writer for COPC data - \[x\] Python bindings - \[x\] JavaScript bindings (not planned, see below) +- \[x\] Spatial querying for nodes (given spatial coordinates, retrieve the appropriate Entry object) - \[ \] Conda and pip packages -- \[ \] Spatial querying for nodes (given spatial coordinates, retrieve the appropriate Entry object) + +## Conformity to Spec + +This version of copc-lib is pinned to a draft version of COPC respective of the state at [COPC.io](https://github.com/copcio/copcio.github.io/tree/a6e8654f65db7c7d438ebea90993bd7a8d59091a). + +### ``extended stats`` VLR + +| User ID | Record ID | +| -------------------------- | ---------------- | +| ``rock_robotic`` | ``10001`` | + +We additionally add an ``extended stats extents`` VLR to store mean and (population) variance values for each dimension. This VLR can be parsed in the same way as the ``extents`` VLR defined by the COPC spec. + + struct CopcExtentExtended + { + double mean; // replaces minimum + double var; // replaces maximum + } + +This VLR is optional to process existing COPC files. If present, mean/variance are set appropriately for each dimension in `CopcExtents`; if not, `CopcExtents` will have default values of `mean=0` and `var=1`. + +This VLR is only written by the `Writer` if the flag `has_extended_stats` is true in `CopcConfigWriter::CopcExtents`. ## Helpful Links @@ -112,6 +147,68 @@ Pull requests are welcome. For major changes, please open an issue first to disc Please make sure to update tests as appropriate. +### Naming Convention + +#### C++ + +We mostly use Google's [Style Guide](https://google.github.io/styleguide/cppguide.html). +```c++ + +namespace name +{ +class ClassName +{ +public: +// Default constructor +ClassName() = default; +ClassName(int public_variable, int private_variable, bool private_read_only) +: public_variable(public_variable), private_variable_(private_variable), +private_read_only_(private_read_only){}; + +int public_variable{}; + +// Getters and Setters +void PrivateVariable(int private_variable) { private_variable_ = private_variable; } +int PrivateVariable() const { return private_variable_; } +bool PrivateReadOnly() const { return private_read_only_; } + +// Any other function +int PrivateVariablePlusOne() const { return private_variable_ + 1; } +int SumPublicAndPrivate() const { return public_variable + private_variable_; } +static std::string ReturnEmptyString() { return {}; } + +private: +int private_variable_{}; +bool private_read_only_{false}; +}; +} // namespace name +``` + +#### Python +```python +test_class = ClassName(public_variable=1,private_variable=2,private_read_only=True) + +# using pybind .def_readwrite +test_class.public_variable = 4 +assert test_class.public_variable == 4 + +# using pybind .def_property +test_class.private_variable = 5 +assert test_class.private_variable == 5 + +# using pybind .def_property_readonly +assert test_class.private_read_only == True + +# using pybind .def +assert test_class.PrivateVariablePlusOne() == 6 +assert test_class.SumPublicAndPrivate() == 9 + +# using pybind .def_static +assert test_class.ReturnEmptyString == "" +``` + +Note that dimension names for points follow the [laspy naming scheme](https://laspy.readthedocs.io/en/latest/intro.html#point-format-6), with the exception of `scan_angle`. + ## License Please see [LICENSE.md](LICENSE.md) diff --git a/cmake/DownloadExampleFiles.cmake b/cmake/DownloadExampleFiles.cmake index 8589bcfd..79116b11 100644 --- a/cmake/DownloadExampleFiles.cmake +++ b/cmake/DownloadExampleFiles.cmake @@ -7,7 +7,7 @@ macro(download_test_files OUT_PATH) else() message(STATUS "Downloading test files to ${OUT_PATH}") file (DOWNLOAD - "https://cloudfront.rockrobotic.com/public/laz-examples/copc/drafts/span/autzen-classified.copc.laz" + "https://github.com/PDAL/data/raw/a3d2a351ca1002c7ea4daa96b5c5fcb0fafeaa6f/autzen/autzen-classified.copc.laz" ${OUT_PATH} ) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index cfd0f261..1c69e9fc 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -2,7 +2,9 @@ set(LIBRARY_TARGET_NAME copc-lib) # Only public header files go here. set(${LIBRARY_TARGET_NAME}_HDR - include/${LIBRARY_TARGET_NAME}/copc/file.hpp + include/${LIBRARY_TARGET_NAME}/copc/info.hpp + include/${LIBRARY_TARGET_NAME}/copc/extents.hpp + include/${LIBRARY_TARGET_NAME}/copc/config.hpp include/${LIBRARY_TARGET_NAME}/geometry/box.hpp include/${LIBRARY_TARGET_NAME}/geometry/vector3.hpp include/${LIBRARY_TARGET_NAME}/hierarchy/entry.hpp @@ -12,7 +14,6 @@ set(${LIBRARY_TARGET_NAME}_HDR include/${LIBRARY_TARGET_NAME}/io/base_io.hpp include/${LIBRARY_TARGET_NAME}/io/reader.hpp include/${LIBRARY_TARGET_NAME}/io/writer.hpp - include/${LIBRARY_TARGET_NAME}/las/file.hpp include/${LIBRARY_TARGET_NAME}/las/header.hpp include/${LIBRARY_TARGET_NAME}/las/point.hpp include/${LIBRARY_TARGET_NAME}/las/points.hpp @@ -27,6 +28,9 @@ set(${LIBRARY_TARGET_NAME}_SRC include/${LIBRARY_TARGET_NAME}/hierarchy/internal/page.hpp include/${LIBRARY_TARGET_NAME}/hierarchy/internal/hierarchy.hpp include/${LIBRARY_TARGET_NAME}/io/internal/writer_internal.hpp + src/copc/info.cpp + src/copc/extents.cpp + src/copc/config.cpp src/geometry/box.cpp src/hierarchy/key.cpp src/hierarchy/page.cpp @@ -38,6 +42,7 @@ set(${LIBRARY_TARGET_NAME}_SRC src/las/point.cpp src/las/points.cpp src/las/utils.cpp + src/las/vlr.cpp ) add_library(${LIBRARY_TARGET_NAME} ${${LIBRARY_TARGET_NAME}_SRC} ${${LIBRARY_TARGET_NAME}_HDR}) diff --git a/cpp/include/copc-lib/copc/config.hpp b/cpp/include/copc-lib/copc/config.hpp new file mode 100644 index 00000000..03fd44ce --- /dev/null +++ b/cpp/include/copc-lib/copc/config.hpp @@ -0,0 +1,80 @@ +#ifndef COPCLIB_COPC_CONFIG_H_ +#define COPCLIB_COPC_CONFIG_H_ + +#include +#include + +#include "copc-lib/copc/extents.hpp" +#include "copc-lib/copc/info.hpp" +#include "copc-lib/las/header.hpp" +#include "copc-lib/las/utils.hpp" +#include "copc-lib/las/vlr.hpp" + +namespace copc +{ +const int COPC_OFFSET = 429; + +class CopcConfig +{ + public: + CopcConfig() = default; + CopcConfig(const las::LasHeader &header, const CopcInfo &copc_info, const CopcExtents &copc_extents, + const std::string &wkt, const las::EbVlr &extra_bytes_vlr) + : header_(std::make_shared(header)), copc_info_(std::make_shared(copc_info)), + copc_extents_(std::make_shared(copc_extents)), wkt_(wkt), + eb_vlr_(std::make_shared(extra_bytes_vlr)){}; + + virtual las::LasHeader LasHeader() const { return *header_; } + + virtual copc::CopcInfo CopcInfo() const { return *copc_info_; } + + virtual copc::CopcExtents CopcExtents() const { return *copc_extents_; } + + std::string Wkt() const { return wkt_; } + + las::EbVlr ExtraBytesVlr() const { return *eb_vlr_; } + + protected: + CopcConfig(const int8_t &point_format_id, const Vector3 &scale, const Vector3 &offset, const std::string &wkt, + const las::EbVlr &extra_bytes_vlr, bool has_extended_stats); + + std::shared_ptr header_; + std::shared_ptr copc_info_; + std::shared_ptr copc_extents_; + std::string wkt_; + std::shared_ptr eb_vlr_; +}; + +class CopcConfigWriter : public CopcConfig +{ + public: + CopcConfigWriter(const int8_t &point_format_id, const Vector3 &scale = Vector3::DefaultScale(), + const Vector3 &offset = Vector3::DefaultOffset(), const std::string &wkt = "", + const las::EbVlr &extra_bytes_vlr = las::EbVlr(0), bool has_extended_stats = false); + + // Copy constructor + CopcConfigWriter(const CopcConfigWriter &copc_config) + : CopcConfig(copc_config.LasHeader(), copc_config.CopcInfo(), copc_config.CopcExtents(), copc_config.Wkt(), + copc_config.ExtraBytesVlr()) + { + } + + // Allow copy from CopcFile + CopcConfigWriter(const CopcConfig &copc_config) + : CopcConfig(copc_config.LasHeader(), copc_config.CopcInfo(), copc_config.CopcExtents(), copc_config.Wkt(), + copc_config.ExtraBytesVlr()) + { + } + + std::shared_ptr LasHeader() { return header_; } + las::LasHeader LasHeader() const override { return *header_; } + + std::shared_ptr CopcInfo() { return copc_info_; } + copc::CopcInfo CopcInfo() const override { return *copc_info_; } + + std::shared_ptr CopcExtents() { return copc_extents_; } + copc::CopcExtents CopcExtents() const override { return *copc_extents_; } +}; + +} // namespace copc +#endif // COPCLIB_COPC_CONFIG_H_ diff --git a/cpp/include/copc-lib/copc/extents.hpp b/cpp/include/copc-lib/copc/extents.hpp new file mode 100644 index 00000000..dee3b5b9 --- /dev/null +++ b/cpp/include/copc-lib/copc/extents.hpp @@ -0,0 +1,195 @@ +#ifndef COPCLIB_COPC_EXTENTS_H_ +#define COPCLIB_COPC_EXTENTS_H_ + +#include +#include + +#include + +#include "copc-lib/las/utils.hpp" +#include "copc-lib/las/vlr.hpp" + +namespace copc +{ + +class CopcExtent : public lazperf::copc_extents_vlr::CopcExtent +{ + public: + // TODO[Leo]: Make mean and var only accessible if has_extended_stats + double mean{0}; + double var{1}; + + CopcExtent(); + + CopcExtent(double minimum, double maximum, double mean = 0, double var = 1); + + CopcExtent(const std::vector &vec); + + CopcExtent(const las::CopcExtentsVlr::CopcExtent &other); + + std::string ToString() const; + + // Operators + bool operator==(const CopcExtent &other) const + { + return minimum == other.minimum && maximum == other.maximum && mean == other.mean && var == other.var; + } + bool operator!=(const CopcExtent &other) const { return !(*this == other); } +}; + +class CopcExtents +{ + public: + // Empty constructor + CopcExtents(int8_t point_format_id, uint16_t num_eb_items = 0, bool has_extended_stats = false); + + // Copy constructor + CopcExtents(const CopcExtents &extents); + + // VLR constructor + CopcExtents(const las::CopcExtentsVlr &vlr, int8_t point_format_id, uint16_t num_eb_items = 0, + bool has_extended_stats = false); + + // Getters + int8_t PointFormatId() const { return point_format_id_; } + bool HasExtendedStats() const { return has_extended_stats_; } + + // Get all extents as a vector of shared_ptrs + std::vector> Extents() { return extents_; } + + // Get all extents as a copy vector + std::vector Extents() const + { + std::vector vec; + vec.reserve(extents_.size()); + + for (int i{0}; i < extents_.size(); i++) + vec.push_back(*extents_[i]); + return vec; + } + + std::shared_ptr Intensity() { return extents_[0]; } + std::shared_ptr ReturnNumber() { return extents_[1]; } + std::shared_ptr NumberOfReturns() { return extents_[2]; } + std::shared_ptr ScannerChannel() { return extents_[3]; } + std::shared_ptr ScanDirectionFlag() { return extents_[4]; } + std::shared_ptr EdgeOfFlightLine() { return extents_[5]; } + std::shared_ptr Classification() { return extents_[6]; } + std::shared_ptr UserData() { return extents_[7]; } + std::shared_ptr ScanAngle() { return extents_[8]; } + std::shared_ptr PointSourceId() { return extents_[9]; } + std::shared_ptr GpsTime() { return extents_[10]; } + + std::shared_ptr Red() + { + if (point_format_id_ > 6) + return extents_[11]; + else + throw std::runtime_error("CopcExtents::Red: This point format does not have Red"); + } + std::shared_ptr Green() + { + if (point_format_id_ > 6) + return extents_[12]; + else + throw std::runtime_error("CopcExtents::Green: This point format does not have Green"); + } + std::shared_ptr Blue() + { + if (point_format_id_ > 6) + return extents_[13]; + else + throw std::runtime_error("CopcExtents::Blue: This point format does not have Blue"); + } + + std::shared_ptr Nir() + { + if (point_format_id_ == 8) + return extents_[14]; + else + throw std::runtime_error("CopcExtents::NIR: This point format does not have NIR"); + } + + std::vector> ExtraBytes() + { + std::vector> extra_bytes; + extra_bytes.reserve(extents_.size() - las::PointBaseNumberDimensions(point_format_id_) + 3); + extra_bytes.assign(extents_.begin() + las::PointBaseNumberDimensions(point_format_id_) - 3, extents_.end()); + return extra_bytes; + } + + // Setters for python bindings + void Intensity(std::shared_ptr intensity) { extents_[0] = intensity; } + void ReturnNumber(std::shared_ptr return_number) { extents_[1] = return_number; } + void NumberOfReturns(std::shared_ptr number_of_returns) { extents_[2] = number_of_returns; } + void ScannerChannel(std::shared_ptr scanner_channel) { extents_[3] = scanner_channel; } + void ScanDirectionFlag(std::shared_ptr scan_direction_flag) { extents_[4] = scan_direction_flag; } + void EdgeOfFlightLine(std::shared_ptr edge_of_flight_line) { extents_[5] = edge_of_flight_line; } + void Classification(std::shared_ptr classification) { extents_[6] = classification; } + void UserData(std::shared_ptr user_data) { extents_[7] = user_data; } + void ScanAngle(std::shared_ptr scan_angle) { extents_[8] = scan_angle; } + void PointSourceId(std::shared_ptr point_source_id) { extents_[9] = point_source_id; } + void GpsTime(std::shared_ptr gps_time) { extents_[10] = gps_time; } + void Red(std::shared_ptr red) + { + if (point_format_id_ > 6) + extents_[1] = red; + else + throw std::runtime_error("CopcExtents::Red: This point format does not have Red"); + } + void Green(std::shared_ptr green) + { + if (point_format_id_ > 6) + extents_[12] = green; + else + throw std::runtime_error("CopcExtents::Green: This point format does not have Green"); + } + void Blue(std::shared_ptr blue) + { + if (point_format_id_ > 6) + extents_[13] = blue; + else + throw std::runtime_error("CopcExtents::Blue: This point format does not have Blue"); + } + + void Nir(std::shared_ptr nir) + { + if (point_format_id_ == 8) + extents_[14] = nir; + else + throw std::runtime_error("CopcExtents::NIR: This point format does not have NIR"); + } + + void ExtraBytes(std::vector> extra_bytes) + { + if (extra_bytes.size() != (extents_.size() - NumberOfExtents(point_format_id_))) + throw std::runtime_error("CopcExtents::ExtraBytesVlr: Vector of extra byte must be the right length."); + std::copy(extra_bytes.begin(), extra_bytes.end(), extents_.begin() + NumberOfExtents(point_format_id_)); + } + + // Convert to lazperf vlr + las::CopcExtentsVlr ToLazPerf(const CopcExtent &x, const CopcExtent &y, const CopcExtent &z) const; + + // Convert to lazperf vlr extended (mean,var) + las::CopcExtentsVlr ToLazPerfExtended() const; + + void SetExtendedStats(const las::CopcExtentsVlr &vlr); + + // Return the total number of extents + size_t NumberOfExtents() const { return extents_.size(); } + + // Return the total number of extents + static int NumberOfExtents(int8_t point_format_id, uint16_t num_eb_items = 0); + + // Return the size in bytes of the extents + static size_t ByteSize(int8_t point_format_id, uint16_t num_eb_items); + + std::string ToString() const; + + private: + int8_t point_format_id_; + bool has_extended_stats_{false}; + std::vector> extents_; +}; +} // namespace copc +#endif // COPCLIB_COPC_EXTENTS_H_ diff --git a/cpp/include/copc-lib/copc/file.hpp b/cpp/include/copc-lib/copc/file.hpp deleted file mode 100644 index 64ff5e6b..00000000 --- a/cpp/include/copc-lib/copc/file.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef COPCLIB_COPC_FILE_H_ -#define COPCLIB_COPC_FILE_H_ - -#include - -#include "copc-lib/las/file.hpp" -#include "copc-lib/las/vlr.hpp" - -namespace copc -{ -const int COPC_OFFSET = 429; - -class CopcFile : public las::LasFile -{ - public: - CopcFile(las::LasHeader header, las::CopcVlr copc, las::WktVlr wkt, las::EbVlr eb) - : LasFile(header, eb), copc(copc), wkt(wkt){}; - CopcFile(las::LasHeader header, int span, std::string wkt, las::EbVlr eb) : LasFile(header, eb) - { - this->wkt.wkt = wkt; - this->copc.span = span; - }; - - // WKT string if defined, else empty - std::string GetWkt() const { return wkt.wkt; } - - // CopcData - las::CopcVlr GetCopc() const { return copc; } - - private: - las::WktVlr wkt; - las::CopcVlr copc; -}; -} // namespace copc -#endif // COPCLIB_COPC_FILE_H_ diff --git a/cpp/include/copc-lib/copc/info.hpp b/cpp/include/copc-lib/copc/info.hpp new file mode 100644 index 00000000..74e734d6 --- /dev/null +++ b/cpp/include/copc-lib/copc/info.hpp @@ -0,0 +1,30 @@ +#ifndef COPCLIB_COPC_INFO_H_ +#define COPCLIB_COPC_INFO_H_ +#include + +#include "lazperf/vlr.hpp" + +namespace copc +{ + +class CopcInfo +{ + public: + CopcInfo() = default; + + CopcInfo(const lazperf::copc_info_vlr &copc_info_vlr); + + lazperf::copc_info_vlr ToLazPerf() const; + + std::string ToString() const; + + double center_x{0}; + double center_y{0}; + double center_z{0}; + double halfsize{0}; + double spacing{0}; + uint64_t root_hier_offset{0}; + uint64_t root_hier_size{0}; +}; +} // namespace copc +#endif // COPCLIB_COPC_INFO_H_ diff --git a/cpp/include/copc-lib/geometry/box.hpp b/cpp/include/copc-lib/geometry/box.hpp index f5f938da..6274b0b0 100644 --- a/cpp/include/copc-lib/geometry/box.hpp +++ b/cpp/include/copc-lib/geometry/box.hpp @@ -19,11 +19,10 @@ class Box Box() = default; // 3D box constructor - Box(const double &x_min, const double &y_min, const double &z_min, const double &x_max, const double &y_max, - const double &z_max); + Box(double x_min, double y_min, double z_min, double x_max, double y_max, double z_max); // 2D box constructor - Box(const double &x_min, const double &y_min, const double &x_max, const double &y_max); + Box(double x_min, double y_min, double x_max, double y_max); // Vector3 constructor Box(const Vector3 &min, const Vector3 &max); @@ -34,7 +33,7 @@ class Box // Constructor from Node Box(const VoxelKey &key, const las::LasHeader &header); - static Box ZeroBox() { return Box(); } + static Box EmptyBox() { return Box(); } static Box MaxBox(); bool Intersects(const Box &box) const; diff --git a/cpp/include/copc-lib/geometry/vector3.hpp b/cpp/include/copc-lib/geometry/vector3.hpp index 67efc690..b622df3e 100644 --- a/cpp/include/copc-lib/geometry/vector3.hpp +++ b/cpp/include/copc-lib/geometry/vector3.hpp @@ -34,7 +34,7 @@ struct Vector3 Vector3 operator+(const Vector3 &other) const { return Vector3(x + other.x, y + other.y, z + other.z); } Vector3 operator-(const Vector3 &other) const { return Vector3(x - other.x, y - other.y, z - other.z); } - std::string ToString() + std::string ToString() const { std::stringstream ss; ss << "Vector3: x=" << x << ", y=" << y << ", z=" << z; diff --git a/cpp/include/copc-lib/hierarchy/internal/hierarchy.hpp b/cpp/include/copc-lib/hierarchy/internal/hierarchy.hpp index 8341ed28..e5ea74ad 100644 --- a/cpp/include/copc-lib/hierarchy/internal/hierarchy.hpp +++ b/cpp/include/copc-lib/hierarchy/internal/hierarchy.hpp @@ -17,16 +17,16 @@ class Hierarchy Hierarchy() { // add the root page to the pages list - seen_pages_[VoxelKey::BaseKey()] = std::make_shared(VoxelKey::BaseKey(), -1, -1); + seen_pages_[VoxelKey::RootKey()] = std::make_shared(VoxelKey::RootKey(), -1, -1); // Set as "loaded" for the writer - seen_pages_[VoxelKey::BaseKey()]->loaded = true; + seen_pages_[VoxelKey::RootKey()]->loaded = true; } // Reader Constructor Hierarchy(int64_t root_hier_offset, int32_t root_hier_size) { // add the root page to the pages list - seen_pages_[VoxelKey::BaseKey()] = - std::make_shared(VoxelKey::BaseKey(), root_hier_offset, root_hier_size); + seen_pages_[VoxelKey::RootKey()] = + std::make_shared(VoxelKey::RootKey(), root_hier_offset, root_hier_size); }; // Find the lowest depth page that has been seen within a hierarchy list diff --git a/cpp/include/copc-lib/hierarchy/key.hpp b/cpp/include/copc-lib/hierarchy/key.hpp index 5c92b39a..dee4bcb2 100644 --- a/cpp/include/copc-lib/hierarchy/key.hpp +++ b/cpp/include/copc-lib/hierarchy/key.hpp @@ -8,6 +8,7 @@ #include #include +#include "copc-lib/copc/info.hpp" #include "copc-lib/geometry/box.hpp" #include "copc-lib/geometry/vector3.hpp" #include "copc-lib/las/vlr.hpp" @@ -26,9 +27,18 @@ class VoxelKey public: VoxelKey(int32_t d, int32_t x, int32_t y, int32_t z) : d(d), x(x), y(y), z(z) {} VoxelKey() : VoxelKey(-1, -1, -1, -1) {} + VoxelKey(const std::vector &vec) + { + if (vec.size() != 4) + throw std::runtime_error("Vector size must be 4."); + d = vec[0]; + x = vec[1]; + y = vec[2]; + z = vec[3]; + }; static VoxelKey InvalidKey() { return VoxelKey(); } - static VoxelKey BaseKey() { return VoxelKey(0, 0, 0, 0); } + static VoxelKey RootKey() { return VoxelKey(0, 0, 0, 0); } bool IsValid() const { return d >= 0 && x >= 0 && y >= 0 && z >= 0; } @@ -43,7 +53,7 @@ class VoxelKey // A list of the key's parents from the key to the root node // optionally including the key itself - std::vector GetParents(bool include_current = false) const; + std::vector GetParents(bool include_self = false) const; // Tests whether the current key is a child of a given key bool ChildOf(VoxelKey parent_key) const; @@ -56,8 +66,8 @@ class VoxelKey bool Within(const las::LasHeader &header, const Box &box) const; bool Crosses(const las::LasHeader &header, const Box &box) const; - double Resolution(const las::LasHeader &header, const las::CopcVlr &copc_info) const; - static double GetResolutionAtDepth(int32_t d, const las::LasHeader &header, const las::CopcVlr &copc_info); + double Resolution(const las::LasHeader &header, const CopcInfo &copc_info) const; + static double GetResolutionAtDepth(int32_t d, const las::LasHeader &header, const CopcInfo &copc_info); int32_t d; int32_t x; diff --git a/cpp/include/copc-lib/io/base_io.hpp b/cpp/include/copc-lib/io/base_io.hpp index 8b528b1f..9249009d 100644 --- a/cpp/include/copc-lib/io/base_io.hpp +++ b/cpp/include/copc-lib/io/base_io.hpp @@ -1,7 +1,9 @@ #ifndef COPCLIB_IO_BASE_H_ #define COPCLIB_IO_BASE_H_ -#include "copc-lib/copc/file.hpp" +#include "copc-lib/copc/config.hpp" +#include "copc-lib/copc/extents.hpp" +#include "copc-lib/copc/info.hpp" #include "copc-lib/hierarchy/node.hpp" #include "copc-lib/hierarchy/page.hpp" #include "copc-lib/las/vlr.hpp" @@ -14,6 +16,8 @@ class Hierarchy; class PageInternal; } // namespace Internal +struct CopcConfig; + // Base class for all IO reader/writers class BaseIO { @@ -21,22 +25,13 @@ class BaseIO // Find a node object given a key Node FindNode(VoxelKey key); - // WKT string if defined, else empty - std::string GetWkt() const { return this->file_->GetWkt(); } - // CopcData - las::CopcVlr GetCopcHeader() const { return this->file_->GetCopc(); } - // Las header - las::LasHeader GetLasHeader() const { return this->file_->GetLasHeader(); } - // EB Vlr - las::EbVlr GetExtraByteVlr() const { return this->file_->GetExtraBytes(); } - protected: - std::shared_ptr file_; std::shared_ptr hierarchy_; virtual std::vector ReadPage(std::shared_ptr page) = 0; void ReadAndParsePage(const std::shared_ptr &page); // Recursively reads all subpages and nodes given a root and returns all the nodes that were loaded void LoadPageHierarchy(const std::shared_ptr &page, std::vector &loaded_nodes); }; + } // namespace copc #endif // COPCLIB_IO_BASE_H_ diff --git a/cpp/include/copc-lib/io/internal/writer_internal.hpp b/cpp/include/copc-lib/io/internal/writer_internal.hpp index 8e157fad..30f6c057 100644 --- a/cpp/include/copc-lib/io/internal/writer_internal.hpp +++ b/cpp/include/copc-lib/io/internal/writer_internal.hpp @@ -3,7 +3,7 @@ #include -#include "copc-lib/copc/file.hpp" +#include "copc-lib/copc/config.hpp" #include "copc-lib/io/base_io.hpp" #include "copc-lib/las/header.hpp" @@ -14,12 +14,11 @@ class WriterInternal { public: const uint32_t VARIABLE_CHUNK_SIZE = (std::numeric_limits::max)(); - // header + COPC vlr + LAZ vlr (max 4 items) - size_t OFFSET_TO_POINT_DATA = 375 + (54 + 160) + (54 + (34 + 4 * 6)); + // 8 bytes for the chunk table offset - uint64_t FIRST_CHUNK_OFFSET() const { return OFFSET_TO_POINT_DATA + sizeof(uint64_t); }; + uint64_t FirstChunkOffset() const { return OffsetToPointData() + sizeof(uint64_t); }; - WriterInternal(std::ostream &out_stream, const std::shared_ptr &file, + WriterInternal(std::ostream &out_stream, std::shared_ptr copc_config, std::shared_ptr hierarchy); // Writes the header and COPC vlrs @@ -33,19 +32,21 @@ class WriterInternal Entry WriteNode(std::vector in, int32_t point_count, bool compressed); private: - bool open_; + bool open_{}; - las::CopcVlr copc_data_; std::ostream &out_stream_; - std::shared_ptr file_; + std::shared_ptr copc_config_; std::shared_ptr hierarchy_; std::vector chunks_; - uint64_t point_count_14_ = 0; + uint64_t point_count_{}; + uint64_t evlr_offset_{}; + uint32_t evlr_count_{}; + + size_t OffsetToPointData() const; - void WriteHeader(las::LasHeader &head14); + void WriteHeader(); void WriteChunkTable(); - void WriteWkt(las::LasHeader &head14); void WritePage(const std::shared_ptr &page); // Iterates through a given page in a postorder traversal and writes the pages diff --git a/cpp/include/copc-lib/io/reader.hpp b/cpp/include/copc-lib/io/reader.hpp index 4231081c..ffad4729 100644 --- a/cpp/include/copc-lib/io/reader.hpp +++ b/cpp/include/copc-lib/io/reader.hpp @@ -3,11 +3,10 @@ #include #include +#include #include -#include "copc-lib/copc/file.hpp" -#include "copc-lib/hierarchy/node.hpp" -#include "copc-lib/hierarchy/page.hpp" +#include "copc-lib/copc/config.hpp" #include "copc-lib/io/base_io.hpp" #include "copc-lib/las/points.hpp" #include "copc-lib/las/vlr.hpp" @@ -38,9 +37,9 @@ class Reader : public BaseIO // Return all children of a page with a given key // (or the node itself, if it exists, if there isn't a page with that key) - std::vector GetAllChildren(const VoxelKey &key); + std::vector GetAllChildrenOfPage(const VoxelKey &key); // Helper function to get all nodes from the root - std::vector GetAllChildren() { return GetAllChildren(VoxelKey::BaseKey()); } + std::vector GetAllNodes() { return GetAllChildrenOfPage(VoxelKey::RootKey()); } // Helper function to get all points from the root las::Points GetAllPoints(double resolution = 0); @@ -59,24 +58,35 @@ class Reader : public BaseIO std::vector GetNodesIntersectBox(const Box &box, double resolution = 0); las::Points GetPointsWithinBox(const Box &box, double resolution = 0); bool ValidateSpatialBounds(bool verbose = false); + // TODO: Add a function to validate extents. + + copc::CopcConfig CopcConfig() { return config_; } protected: Reader() = default; + copc::CopcConfig config_; + std::map vlrs_; // maps from absolute offsets to VLR entries + std::istream *in_stream_; std::unique_ptr reader_; // Constructor helper function, initializes the file and hierarchy void InitReader(); - // Reads file VLRs into vlrs_ - std::map ReadVlrs(); + // Reads file VLRs and EVLRs into vlrs_ + std::map ReadVlrHeaders(); // TODO: Allow user to create/reader arbitrary VLRs + // Fetchs the map key for a query vlr user and record IDs + static uint64_t FetchVlr(const std::map &vlrs, const std::string &user_id, + uint16_t record_id); + // Finds and loads the COPC vlr + CopcInfo ReadCopcInfoVlr(); // Finds and loads the COPC vlr - las::CopcVlr ReadCopcData(); + CopcExtents ReadCopcExtentsVlr(std::map &vlrs, const las::EbVlr &eb_vlr) const; // Finds and loads the WKT vlr - las::WktVlr ReadWktData(const las::CopcVlr &copc_data); - // finds and loads EB vlr - las::EbVlr ReadExtraByteVlr(std::map &vlrs); + las::WktVlr ReadWktVlr(std::map &vlrs); + // Finds and loads EB vlr + las::EbVlr ReadExtraBytesVlr(std::map &vlrs); std::vector ReadPage(std::shared_ptr page) override; }; @@ -88,6 +98,8 @@ class FileReader : public Reader { auto f_stream = new std::fstream; f_stream->open(file_path.c_str(), std::ios::in | std::ios::binary); + if (!f_stream->good()) + throw std::runtime_error("FileReader: Error while opening file path."); in_stream_ = f_stream; InitReader(); diff --git a/cpp/include/copc-lib/io/writer.hpp b/cpp/include/copc-lib/io/writer.hpp index 8110b6b3..a9da019b 100644 --- a/cpp/include/copc-lib/io/writer.hpp +++ b/cpp/include/copc-lib/io/writer.hpp @@ -6,11 +6,12 @@ #include #include -#include "copc-lib/copc/file.hpp" +#include "copc-lib/copc/config.hpp" #include "copc-lib/geometry/box.hpp" #include "copc-lib/io/base_io.hpp" #include "copc-lib/las/header.hpp" #include "copc-lib/las/points.hpp" +#include "copc-lib/las/utils.hpp" namespace copc { @@ -23,74 +24,9 @@ class WriterInternal; class Writer : public BaseIO { public: - // Header config for creating a new file - struct LasConfig + Writer(std::ostream &out_stream, CopcConfigWriter const &copc_file_writer) { - // Not sure we should allow default constructor - // LasConfig()= default; - LasConfig(const int8_t &point_format_id, const Vector3 &scale = {DEFAULT_SCALE, DEFAULT_SCALE, DEFAULT_SCALE}, - const Vector3 &offset = {0, 0, 0}) - : point_format_id(point_format_id), scale(scale), offset(offset){}; - // Allow for "copying" a lasheader from one file to another - LasConfig(const las::LasHeader &config, const las::EbVlr &extra_bytes_); - - // Getters/Setters for string attributes - void GUID(const std::string &guid) - { - if (guid.size() > 16) - throw std::runtime_error("GUID length must be <= 16."); - guid_ = guid; - } - std::string GUID() const { return guid_; } - - void SystemIdentifier(const std::string &system_identifier) - { - if (system_identifier.size() > 32) - throw std::runtime_error("System Identifier length must be <= 32."); - system_identifier_ = system_identifier; - } - std::string SystemIdentifier() const { return system_identifier_; } - - void GeneratingSoftware(const std::string &generating_software) - { - if (generating_software.size() > 32) - throw std::runtime_error("System Identifier length must be <= 32."); - generating_software_ = generating_software; - } - std::string GeneratingSoftware() const { return generating_software_; } - - uint16_t NumExtraBytes() const { return extra_bytes.items.size(); } - - uint16_t file_source_id{}; - uint16_t global_encoding{}; - - uint16_t creation_day{}; - uint16_t creation_year{}; - - // default to 0 - int8_t point_format_id{}; - - las::EbVlr extra_bytes; - - // xyz scale/offset - Vector3 scale{0.01, 0.01, 0.01}; - Vector3 offset{0, 0, 0}; - // xyz min/max for las header - Vector3 max{0, 0, 0}; - Vector3 min{0, 0, 0}; - - // # of points per return 0-14 - std::array points_by_return_14{}; - - private: - std::string guid_{}; - std::string system_identifier_{}; - std::string generating_software_{}; - }; - - Writer(std::ostream &out_stream, LasConfig const &config, int span = 0, const std::string &wkt = "") - { - InitWriter(out_stream, config, span, wkt); + InitWriter(out_stream, copc_file_writer); } Page GetRootPage(); @@ -106,18 +42,15 @@ class Writer : public BaseIO // Adds a subpage to a given page Page AddSubPage(Page &page, VoxelKey key); - // Update file internals - void SetMin(Vector3 min) { this->file_->SetMin(min); } - void SetMax(Vector3 max) { this->file_->SetMax(max); } - void SetPointsByReturn(std::array points_by_return_14) - { - this->file_->SetPointsByReturn(points_by_return_14); - } + std::shared_ptr CopcConfig() { return config_; } + virtual ~Writer(); protected: Writer() = default; + std::shared_ptr config_; + std::shared_ptr writer_; Node DoAddNode(Page &page, VoxelKey key, std::vector in, uint64_t point_count, bool compressed); @@ -127,9 +60,7 @@ class Writer : public BaseIO }; // Constructor helper function, initializes the file and hierarchy - void InitWriter(std::ostream &out_stream, LasConfig const &config, const int &span, const std::string &wkt); - // Converts the lasconfig object into an actual LasHeader - static las::LasHeader HeaderFromConfig(LasConfig const &config); + void InitWriter(std::ostream &out_stream, const CopcConfigWriter &copc_file_writer); // Gets the sum of the byte size the extra bytes will take up, for calculating point_record_len static int NumBytesFromExtraBytes(const std::vector &items); }; @@ -137,10 +68,13 @@ class Writer : public BaseIO class FileWriter : public Writer { public: - FileWriter(const std::string &file_path, LasConfig const &config, const int &span = 0, const std::string &wkt = "") + FileWriter(const std::string &file_path, const CopcConfigWriter &copc_file_writer) { + f_stream_.open(file_path.c_str(), std::ios::out | std::ios::binary); - InitWriter(f_stream_, config, span, wkt); + if (!f_stream_.good()) + throw std::runtime_error("FileWriter: Error while opening file path."); + InitWriter(f_stream_, copc_file_writer); } void Close() override; diff --git a/cpp/include/copc-lib/las/file.hpp b/cpp/include/copc-lib/las/file.hpp deleted file mode 100644 index a3fe196e..00000000 --- a/cpp/include/copc-lib/las/file.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef COPCLIB_LAS_FILE_H_ -#define COPCLIB_LAS_FILE_H_ - -#include -#include - -#include "copc-lib/geometry/vector3.hpp" -#include "copc-lib/las/header.hpp" -#include "copc-lib/las/vlr.hpp" - -namespace copc::las -{ -class LasFile -{ - public: - LasFile(const LasHeader &header, const las::EbVlr &eb) : header_(header), eb_(eb){}; - - std::map vlrs; // maps from absolute offsets to VLR entries - LasHeader GetLasHeader() const { return header_; } - - // Extra bytes - las::EbVlr GetExtraBytes() const { return eb_; } - - // Update header - void SetMin(Vector3 min) { header_.min = min; } - void SetMax(Vector3 max) { header_.max = max; } - void SetPointsByReturn(std::array points_by_return_14) - { - header_.points_by_return_14 = points_by_return_14; - } - - protected: - LasHeader header_; - las::EbVlr eb_; -}; -} // namespace copc::las -#endif // COPCLIB_LAS_FILE_H_ diff --git a/cpp/include/copc-lib/las/header.hpp b/cpp/include/copc-lib/las/header.hpp index a2f6d043..c8092c61 100644 --- a/cpp/include/copc-lib/las/header.hpp +++ b/cpp/include/copc-lib/las/header.hpp @@ -17,17 +17,43 @@ namespace copc class Box; namespace las { -using VlrHeader = lazperf::vlr_header; + class LasHeader { public: - LasHeader(){}; - uint16_t NumExtraBytes() const; + LasHeader() = default; + uint16_t EbByteSize() const; + LasHeader(int8_t point_format_id, uint16_t point_record_length, const Vector3 &scale, const Vector3 &offset) + : point_format_id_(point_format_id), point_record_length_{point_record_length}, scale_(scale), + offset_(offset){}; + + // Constructor for python pickling + // TODO: Add a CMAKE flag to only compute python-specific code when python is compiled + LasHeader(int8_t point_format_id, uint16_t point_record_length, uint32_t point_offset, uint64_t point_count, + uint32_t vlr_count, const Vector3 &scale, const Vector3 &offset, uint64_t evlr_offset, + uint32_t evlr_count) + : point_format_id_(point_format_id), point_record_length_(point_record_length), point_offset_(point_offset), + point_count_(point_count), vlr_count_(vlr_count), scale_(scale), offset_(offset), evlr_offset_(evlr_offset), + evlr_count_(evlr_count){}; static LasHeader FromLazPerf(const lazperf::header14 &header); - lazperf::header14 ToLazPerf() const; + lazperf::header14 ToLazPerf(uint32_t point_offset, uint64_t point_count, uint64_t evlr_offset, uint32_t evlr_count, + bool wkt_flag, bool eb_flag, bool extended_stats_flag) const; + + std::string ToString() const; + + // Getters/Setters for protected attributes + + uint8_t PointFormatId() const { return point_format_id_; } + uint16_t PointRecordLength() const { return point_record_length_; } + Vector3 Scale() const { return scale_; } + Vector3 Offset() const { return offset_; } + uint64_t PointCount() const { return point_count_; } + uint32_t PointOffset() const { return point_offset_; } + uint32_t VlrCount() const { return vlr_count_; } + uint32_t EvlrCount() const { return evlr_count_; } + uint64_t EvlrOffset() const { return evlr_offset_; } - // Getters/Setters for string attributes void GUID(const std::string &guid) { if (guid.size() > 16) @@ -52,56 +78,58 @@ class LasHeader } std::string GeneratingSoftware() const { return generating_software_; } - double GetSpan() const { return std::max({max.x - min.x, max.y - min.y, max.z - min.z}); } - Box GetBounds() const; + // Returns the scaled size of the maximum dimension of the point cloud + double Span() const { return std::max({max.x - min.x, max.y - min.y, max.z - min.z}); } + + // Returns a box that fits the dimensions of the point cloud based on min and max + Box Bounds() const; // Apply Las scale factors to Vector3 or double - Vector3 ApplyScale(const Vector3 &unscaled_value) const { return unscaled_value * scale + offset; } - Vector3 ApplyInverseScale(const Vector3 &scaled_value) const { return scaled_value / scale - offset; } - double ApplyScaleX(double unscaled_value) const { return unscaled_value * scale.x + offset.x; } - double ApplyScaleY(double unscaled_value) const { return unscaled_value * scale.y + offset.y; } - double ApplyScaleZ(double unscaled_value) const { return unscaled_value * scale.z + offset.z; } - double ApplyInverseScaleX(double scaled_value) const { return (scaled_value - offset.x) / scale.x; } - double ApplyInverseScaleY(double scaled_value) const { return (scaled_value - offset.y) / scale.y; } - double ApplyInverseScaleZ(double scaled_value) const { return (scaled_value - offset.z) / scale.z; } + Vector3 ApplyScale(const Vector3 &unscaled_value) const { return unscaled_value * scale_ + offset_; } + Vector3 ApplyInverseScale(const Vector3 &scaled_value) const { return scaled_value / scale_ - offset_; } + double ApplyScaleX(double unscaled_value) const { return unscaled_value * scale_.x + offset_.x; } + double ApplyScaleY(double unscaled_value) const { return unscaled_value * scale_.y + offset_.y; } + double ApplyScaleZ(double unscaled_value) const { return unscaled_value * scale_.z + offset_.z; } + double ApplyInverseScaleX(double scaled_value) const { return (scaled_value - offset_.x) / scale_.x; } + double ApplyInverseScaleY(double scaled_value) const { return (scaled_value - offset_.y) / scale_.y; } + double ApplyInverseScaleZ(double scaled_value) const { return (scaled_value - offset_.z) / scale_.z; } uint16_t file_source_id{}; uint16_t global_encoding{}; - uint8_t version_major{1}; - uint8_t version_minor{4}; - uint16_t creation_day{}; uint16_t creation_year{}; - uint16_t header_size{}; - uint32_t point_offset{}; - uint32_t vlr_count{}; - - int8_t point_format_id{}; - uint16_t point_record_length{}; - - uint32_t point_count{}; - std::array points_by_return{}; - - Vector3 scale{Vector3::DefaultScale()}; - Vector3 offset{Vector3::DefaultOffset()}; // xyz min/max for las header Vector3 max{}; Vector3 min{}; - uint64_t wave_offset{0}; + // # of points per return 0-14 + std::array points_by_return{}; + + protected: + int8_t point_format_id_{6}; + uint16_t point_record_length_{}; + + // xyz scale/offset + Vector3 scale_{Vector3::DefaultScale()}; + Vector3 offset_{Vector3::DefaultOffset()}; - uint64_t evlr_offset{0}; - uint32_t evlr_count{0}; - uint64_t point_count_14{0}; - std::array points_by_return_14{}; + uint64_t point_count_{}; + uint32_t point_offset_{}; + uint32_t vlr_count_{}; + + uint64_t evlr_offset_{}; + uint32_t evlr_count_{}; - static const size_t size = 375; // Size of header for LAS 1.4 - private: std::string guid_{}; std::string system_identifier_{}; std::string generating_software_{}; + + const uint8_t version_major_{1}; + const uint8_t version_minor_{4}; + + const uint16_t header_size_{375}; }; } // namespace las diff --git a/cpp/include/copc-lib/las/point.hpp b/cpp/include/copc-lib/las/point.hpp index 48b5a600..a814866c 100644 --- a/cpp/include/copc-lib/las/point.hpp +++ b/cpp/include/copc-lib/las/point.hpp @@ -14,7 +14,7 @@ class Point { public: Point(const int8_t &point_format_id, const Vector3 &scale = Vector3::DefaultScale(), - const Vector3 &offset = Vector3::DefaultOffset(), const uint16_t &num_extra_bytes = 0); + const Vector3 &offset = Vector3::DefaultOffset(), const uint16_t &eb_byte_size = 0); Point(const LasHeader &header); Point(const Point &other); @@ -45,390 +45,178 @@ class Point uint16_t Intensity() const { return intensity_; } void Intensity(const uint16_t &intensity) { intensity_ = intensity; } - uint8_t ReturnNumber() const { return extended_point_type_ ? extended_returns_ & 0xF : returns_flags_eof_ & 0x7; } + uint8_t ReturnNumber() const { return returns_ & 0xF; } void ReturnNumber(const uint8_t &return_number) { - if (extended_point_type_) - { - if (return_number > 15) - throw std::runtime_error("Return Number must be <= 15"); - else - extended_returns_ = return_number | (extended_returns_ & 0xF0); - } - else - { - if (return_number > 7) - throw std::runtime_error("Return Number must be <= 7"); - else - returns_flags_eof_ = (returns_flags_eof_ & 0xF8) | return_number; - } + if (return_number > 15) + throw std::runtime_error("Return Number must be <= 15"); + returns_ = return_number | (returns_ & 0xF0); } - uint8_t NumberOfReturns() const - { - return extended_point_type_ ? extended_returns_ >> 4 : (returns_flags_eof_ >> 3) & 0x7; - } + uint8_t NumberOfReturns() const { return returns_ >> 4; } void NumberOfReturns(const uint8_t &number_of_returns) { - if (extended_point_type_) - { - if (number_of_returns > 15) - throw std::runtime_error("Number of Returns must be <= 15"); - else - extended_returns_ = (number_of_returns << 4) | (extended_returns_ & 0xF); - } - else - { - if (number_of_returns > 7) - throw std::runtime_error("Number of Return must be <= 7"); - else - returns_flags_eof_ = (returns_flags_eof_ & 0xC7) | (number_of_returns << 3); - } - } - - uint8_t Classification() const { return extended_point_type_ ? extended_classification_ : classification_ & 0x1F; } - void Classification(const uint8_t &classification) - { - if (extended_point_type_) - extended_classification_ = classification; - else - { - if (classification > 31) - throw std::runtime_error( - "Classification for Point10 must be <= 31. To override this, use ClassificationBitFields."); - else - classification_ = (classification_ & 0xE0) | classification; - } - } - -#pragma region ScanAngle - int8_t ScanAngleRank() const - { - if (extended_point_type_) - throw std::runtime_error("Point14 does not have Scan Angle Rank."); - else - return scan_angle_rank_; + if (number_of_returns > 15) + throw std::runtime_error("Number of Returns must be <= 15"); + returns_ = (number_of_returns << 4) | (returns_ & 0xF); } - void ScanAngleRank(const int8_t &scan_angle_rank) - { - if (extended_point_type_) - throw std::runtime_error("Point14 does not have Scan Angle Rank."); - else if (scan_angle_rank < -90 || scan_angle_rank > 90) - throw std::runtime_error("Scan Angle Rank must be between -90 and 90"); - else - scan_angle_rank_ = scan_angle_rank; - } + uint8_t Classification() const { return classification_; } + void Classification(const uint8_t &classification) { classification_ = classification; } - int16_t ExtendedScanAngle() const - { - if (extended_point_type_) - return extended_scan_angle_; - else - throw std::runtime_error("Point10 does not have Scan Angle."); - } + int16_t ScanAngle() const { return scan_angle_; } - void ExtendedScanAngle(const int16_t &scan_angle) + void ScanAngle(const int16_t &scan_angle) { - if (extended_point_type_) - { - if (scan_angle < -30000 || scan_angle > 30000) - throw std::runtime_error("Scan Angle must be between -30000 and 30000"); - else - extended_scan_angle_ = scan_angle; - } - else - throw std::runtime_error("Point10 does not have Scan Angle."); + if (scan_angle < -30000 || scan_angle > 30000) + throw std::runtime_error("Scan Angle must be between -30000 and 30000"); + scan_angle_ = scan_angle; } -#pragma endregion ScanAngle - float ScanAngle() const - { - return extended_point_type_ ? 0.006f * float(extended_scan_angle_) : float(scan_angle_rank_); - } + float ScanAngleDegrees() const { return 0.006f * float(scan_angle_); } - void ScanAngle(const float &scan_angle) - { - if (extended_point_type_) - extended_scan_angle_ = static_cast(scan_angle / 0.006f); - else - { - if (scan_angle < -90 || scan_angle > 90) - throw std::runtime_error("Scan Angle Rank must be between -90 and 90"); - else - scan_angle_rank_ = static_cast(scan_angle); - } - } + void ScanAngleDegrees(const float &scan_angle) { scan_angle_ = static_cast(scan_angle / 0.006f); } uint8_t UserData() const { return user_data_; } void UserData(const uint8_t &user_data) { user_data_ = user_data; } - uint16_t PointSourceID() const { return point_source_id_; } - void PointSourceID(const uint16_t &point_source_id) { point_source_id_ = point_source_id; } + uint16_t PointSourceId() const { return point_source_id_; } + void PointSourceId(const uint16_t &point_source_id) { point_source_id_ = point_source_id; } #pragma region Flags - bool Synthetic() const { return extended_point_type_ ? extended_flags_ & 0x1 : (classification_ >> 5) & 0x1; } - void Synthetic(const bool &synthetic) - { - extended_point_type_ ? extended_flags_ = (extended_flags_ & 0xFE) | synthetic - : classification_ = (classification_ & 0xDF) | (synthetic << 5); - } + bool Synthetic() const { return flags_ & 0x1; } + void Synthetic(const bool &synthetic) { flags_ = (flags_ & 0xFE) | synthetic; } - bool KeyPoint() const { return extended_point_type_ ? (extended_flags_ >> 1) & 0x1 : (classification_ >> 6) & 0x1; } - void KeyPoint(const bool &key_point) - { - extended_point_type_ ? extended_flags_ = (extended_flags_ & 0xFD) | (key_point << 1) - : classification_ = (classification_ & 0xBF) | (key_point << 6); - } + bool KeyPoint() const { return (flags_ >> 1) & 0x1; } + void KeyPoint(const bool &key_point) { flags_ = (flags_ & 0xFD) | (key_point << 1); } - bool Withheld() const { return extended_point_type_ ? (extended_flags_ >> 2) & 0x1 : (classification_ >> 7) & 0x1; } - void Withheld(const bool &withheld) - { - extended_point_type_ ? extended_flags_ = (extended_flags_ & 0xFB) | (withheld << 2) - : classification_ = (classification_ & 0x7F) | (withheld << 7); - } + bool Withheld() const { return (flags_ >> 2) & 0x1; } + void Withheld(const bool &withheld) { flags_ = (flags_ & 0xFB) | (withheld << 2); } - bool Overlap() const - { - if (extended_point_type_) - return (extended_flags_ >> 3) & 0x1; - else - throw std::runtime_error("Point10 does not have Overlap."); - } - void Overlap(const bool &overlap) - { - if (extended_point_type_) - extended_flags_ = (extended_flags_ & 0xF7) | (overlap << 3); - else - throw std::runtime_error("Point10 does not have Overlap."); - } + bool Overlap() const { return (flags_ >> 3) & 0x1; } + void Overlap(const bool &overlap) { flags_ = (flags_ & 0xF7) | (overlap << 3); } - uint8_t ScannerChannel() const - { - if (extended_point_type_) - return (extended_flags_ >> 4) & 0x03; - else - throw std::runtime_error("Point10 does not have Scanner Channel."); - } + uint8_t ScannerChannel() const { return (flags_ >> 4) & 0x03; } void ScannerChannel(const uint8_t &scanner_channel) { - if (extended_point_type_) - if (scanner_channel > 3) - throw std::runtime_error("Scanner channel must be <= 3"); - else - extended_flags_ = (extended_flags_ & 0xCF) | (scanner_channel << 4); - else - throw std::runtime_error("Point10 does not have Scanner Channel."); + if (scanner_channel > 3) + throw std::runtime_error("Scanner channel must be <= 3"); + flags_ = (flags_ & 0xCF) | (scanner_channel << 4); } - bool ScanDirectionFlag() const - { - return extended_point_type_ ? (extended_flags_ >> 6) & 0x1 : (returns_flags_eof_ >> 6) & 0x1; - } - void ScanDirectionFlag(const bool &scan_direction_flag) - { - if (extended_point_type_) - extended_flags_ = (extended_flags_ & 0xBF) | (scan_direction_flag << 6); - else - returns_flags_eof_ = (returns_flags_eof_ & 0xBF) | (scan_direction_flag << 6); - } + bool ScanDirectionFlag() const { return (flags_ >> 6) & 0x1; } + void ScanDirectionFlag(const bool &scan_direction_flag) { flags_ = (flags_ & 0xBF) | (scan_direction_flag << 6); } - bool EdgeOfFlightLineFlag() const { return extended_point_type_ ? extended_flags_ >> 7 : returns_flags_eof_ >> 7; } + bool EdgeOfFlightLineFlag() const { return flags_ >> 7; } void EdgeOfFlightLineFlag(const bool &edge_of_flight_line) { - if (extended_point_type_) - extended_flags_ = (extended_flags_ & 0x7F) | (edge_of_flight_line << 7); - else - returns_flags_eof_ = (returns_flags_eof_ & 0x7F) | (edge_of_flight_line << 7); + flags_ = (flags_ & 0x7F) | (edge_of_flight_line << 7); } #pragma endregion Flags #pragma region RGB - void RGB(const std::vector &rgb) + void Rgb(const std::vector &rgb) { - if (has_rgb_) - { - if (rgb.size() != 3) - throw std::runtime_error("RGB vector must be of size 3."); - rgb_[0] = rgb[0]; - rgb_[1] = rgb[1]; - rgb_[2] = rgb[2]; - } - else + if (!has_rgb_) throw std::runtime_error("This point format does not have RGB."); + + if (rgb.size() != 3) + throw std::runtime_error("RGB vector must be of size 3."); + rgb_[0] = rgb[0]; + rgb_[1] = rgb[1]; + rgb_[2] = rgb[2]; } - void RGB(const uint16_t &red, const uint16_t &green, const uint16_t &blue) + void Rgb(const uint16_t &red, const uint16_t &green, const uint16_t &blue) { - if (has_rgb_) - { - rgb_[0] = red; - rgb_[1] = green; - rgb_[2] = blue; - } - else + if (!has_rgb_) throw std::runtime_error("This point format does not have RGB."); + rgb_[0] = red; + rgb_[1] = green; + rgb_[2] = blue; } uint16_t Red() const { - if (has_rgb_) - return rgb_[0]; - else + if (!has_rgb_) throw std::runtime_error("This point format does not have RGB"); + return rgb_[0]; } void Red(const uint16_t &red) { - if (has_rgb_) - rgb_[0] = red; - else + if (!has_rgb_) throw std::runtime_error("This point format does not have RGB."); + rgb_[0] = red; } uint16_t Green() const { - if (has_rgb_) - return rgb_[1]; - else + if (!has_rgb_) throw std::runtime_error("This point format does not have RGB"); + return rgb_[1]; } void Green(const uint16_t &green) { - if (has_rgb_) - rgb_[1] = green; - else + if (!has_rgb_) throw std::runtime_error("This point format does not have RGB."); + rgb_[1] = green; } uint16_t Blue() const { - if (has_rgb_) - return rgb_[2]; - else + if (!has_rgb_) throw std::runtime_error("This point format does not have RGB"); + return rgb_[2]; } void Blue(const uint16_t &blue) { - if (has_rgb_) - rgb_[2] = blue; - else + if (!has_rgb_) throw std::runtime_error("This point format does not have RGB."); + rgb_[2] = blue; } #pragma endregion RGB - double GPSTime() const - { - if (has_gps_time_) - return gps_time_; - else - throw std::runtime_error("This point format does not have GPS time."); - } - void GPSTime(const double &gps_time) - { - if (has_gps_time_) - gps_time_ = gps_time; - else - throw std::runtime_error("This point format does not have GPS time."); - } + double GPSTime() const { return gps_time_; } + void GPSTime(const double &gps_time) { gps_time_ = gps_time; } - uint16_t NIR() const + uint16_t Nir() const { - if (has_nir_) - return nir_; - else + if (!has_nir_) throw std::runtime_error("This point format does not have NIR."); + return nir_; } - void NIR(const uint16_t &nir) + void Nir(const uint16_t &nir) { - if (has_nir_) - nir_ = nir; - else + if (!has_nir_) throw std::runtime_error("This point format does not have NIR."); + nir_ = nir; } std::vector ExtraBytes() const { return extra_bytes_; } void ExtraBytes(const std::vector &in) { - if (in.size() != NumExtraBytes()) + if (in.size() != EbByteSize()) throw std::runtime_error("Number of input bytes " + std::to_string(in.size()) + - " does not match number of point bytes " + std::to_string(NumExtraBytes())); + " does not match number of point bytes " + std::to_string(EbByteSize())); extra_bytes_ = in; } #pragma region BitFields - uint8_t ReturnsScanDirEofBitFields() const - { - if (extended_point_type_) - throw std::runtime_error("Point14 does not have ReturnsScanDirEofBitFields, use ExtendedReturnsBitFields " - "or ExtendedFlagsBitFields instead."); - else - return returns_flags_eof_; - } - void ReturnsScanDirEofBitFields(const uint8_t &returns_bit_fields) - { - if (extended_point_type_) - throw std::runtime_error("Point14 does not have ReturnsScanDirEofBitFields, use ExtendedReturnsBitFields " - "or ExtendedFlagsBitFields instead."); - else - returns_flags_eof_ = returns_bit_fields; - } - - uint8_t ExtendedReturnsBitFields() const - { - if (!extended_point_type_) - throw std::runtime_error("Point10 does not have ReturnsBitFields, use ReturnsScanDirEofBitFields instead."); - else - return extended_returns_; - } - void ExtendedReturnsBitFields(const uint8_t &extended_returns) - { - if (!extended_point_type_) - throw std::runtime_error("Point10 does not have ReturnsBitFields, use ReturnsScanDirEofBitFields instead."); - else - extended_returns_ = extended_returns; - } - - uint8_t ExtendedFlagsBitFields() const - { - if (extended_point_type_) - return extended_flags_; - else - throw std::runtime_error("Point10 does not have extended flags, use ReturnsScanDirEofBitFields instead."); - } - void ExtendedFlagsBitFields(const uint8_t &class_flags) - { - if (extended_point_type_) - extended_flags_ = class_flags; - else - throw std::runtime_error("Point10 does not have FlagsBitFields, use ReturnsScanDirEofBitFields instead."); - } - uint8_t ClassificationBitFields() const - { - if (extended_point_type_) - throw std::runtime_error("Point14 does not have Classification BitFields."); - else - return classification_; - } + uint8_t ReturnsBitField() const { return returns_; } + void ReturnsBitField(const uint8_t &extended_returns) { returns_ = extended_returns; } - void ClassificationBitFields(const uint8_t &classification_bitfields) - { - if (extended_point_type_) - throw std::runtime_error("Point14 does not have Classification Bit Fields."); - else - classification_ = classification_bitfields; - } + uint8_t FlagsBitField() const { return flags_; } + void FlagsBitField(const uint8_t &class_flags) { flags_ = class_flags; } #pragma endregion BitFields - bool HasExtendedPoint() const { return extended_point_type_; } - bool HasGPSTime() const { return has_gps_time_; } - bool HasRGB() const { return has_rgb_; } - bool HasNIR() const { return has_nir_; } + bool HasRgb() const { return has_rgb_; } + bool HasNir() const { return has_nir_; } uint32_t PointRecordLength() const { return point_record_length_; } - int8_t PointFormatID() const { return point_format_id_; } - uint16_t NumExtraBytes() const; + int8_t PointFormatId() const { return point_format_id_; } + uint16_t EbByteSize() const; Vector3 Scale() const { return scale_; } Vector3 Offset() const { return offset_; } @@ -441,7 +229,7 @@ class Point std::string ToString() const; static std::shared_ptr Unpack(std::istream &in_stream, const int8_t &point_format_id, const Vector3 &scale, - const Vector3 &offset, const uint16_t &num_extra_bytes); + const Vector3 &offset, const uint16_t &eb_byte_size); void Pack(std::ostream &out_stream) const; void ToPointFormat(const int8_t &point_format_id); @@ -450,24 +238,17 @@ class Point int32_t y_{}; int32_t z_{}; uint16_t intensity_{}; - uint8_t returns_flags_eof_{}; + uint8_t returns_{}; + uint8_t flags_{}; uint8_t classification_{}; - int8_t scan_angle_rank_{}; + int16_t scan_angle_{}; uint8_t user_data_{}; uint16_t point_source_id_{}; - // LAS 1.4 only - bool extended_point_type_{false}; - uint8_t extended_returns_{}; - uint8_t extended_flags_{}; - uint8_t extended_classification_{}; - int16_t extended_scan_angle_{}; - double gps_time_{}; uint16_t rgb_[3]{}; uint16_t nir_{}; - bool has_gps_time_{false}; bool has_rgb_{false}; bool has_nir_{false}; diff --git a/cpp/include/copc-lib/las/points.hpp b/cpp/include/copc-lib/las/points.hpp index d155d8b9..8afac11d 100644 --- a/cpp/include/copc-lib/las/points.hpp +++ b/cpp/include/copc-lib/las/points.hpp @@ -19,15 +19,15 @@ class Points { public: Points(const int8_t &point_format_id, const Vector3 &scale, const Vector3 &offset, - const uint16_t &num_extra_bytes = 0); + const uint16_t &eb_byte_size = 0); Points(const LasHeader &header); // Will create Points object given a points vector Points(const std::vector> &points); // Getters - int8_t PointFormatID() const { return point_format_id_; } + int8_t PointFormatId() const { return point_format_id_; } uint32_t PointRecordLength() const { return point_record_length_; } - uint32_t NumExtraBytes() const { return ComputeNumExtraBytes(point_format_id_, point_record_length_); } + uint32_t EbByteSize() const { return copc::las::EbByteSize(point_format_id_, point_record_length_); } // Vector functions std::vector> Get() { return points_; } @@ -44,13 +44,12 @@ class Points // Add points functions void AddPoint(const std::shared_ptr &point); void AddPoints(Points points); - // TODO[Leo]: Add this to tests void AddPoints(std::vector> points); // Point functions std::shared_ptr CreatePoint() { - return std::make_shared(point_format_id_, scale_, offset_, NumExtraBytes()); + return std::make_shared(point_format_id_, scale_, offset_, EbByteSize()); } void ToPointFormat(const int8_t &point_format_id); @@ -58,7 +57,7 @@ class Points std::vector Pack(); void Pack(std::ostream &out_stream); static Points Unpack(const std::vector &point_data, const int8_t &point_format_id, - const uint16_t &num_extra_bytes, const Vector3 &scale, const Vector3 &offset); + const uint16_t &eb_byte_size, const Vector3 &scale, const Vector3 &offset); static Points Unpack(const std::vector &point_data, const LasHeader &header); std::string ToString() const; @@ -131,21 +130,21 @@ class Points points_[i]->Classification(in[i]); } - std::vector PointSourceID() const + std::vector PointSourceId() const { std::vector out; out.resize(Size()); std::transform(points_.begin(), points_.end(), out.begin(), - [](const std::shared_ptr &p) { return p->PointSourceID(); }); + [](const std::shared_ptr &p) { return p->PointSourceId(); }); return out; } - void PointSourceID(const std::vector &in) + void PointSourceId(const std::vector &in) { if (in.size() != Size()) - throw std::runtime_error("PointSourceID setter array must be same size as Points array!"); + throw std::runtime_error("PointSourceId setter array must be same size as Points array!"); for (unsigned i = 0; i < points_.size(); ++i) - points_[i]->PointSourceID(in[i]); + points_[i]->PointSourceId(in[i]); } std::vector Red() const diff --git a/cpp/include/copc-lib/las/utils.hpp b/cpp/include/copc-lib/las/utils.hpp index 35714cf1..75af069c 100644 --- a/cpp/include/copc-lib/las/utils.hpp +++ b/cpp/include/copc-lib/las/utils.hpp @@ -7,12 +7,12 @@ namespace copc::las { uint8_t PointBaseByteSize(const int8_t &point_format_id); -uint16_t ComputeNumExtraBytes(const int8_t &point_format_id, const uint32_t &point_record_length); -uint16_t ComputePointBytes(const int8_t &point_format_id, const uint16_t &num_extra_bytes); +uint8_t PointBaseNumberDimensions(const int8_t &point_format_id); +uint16_t EbByteSize(const int8_t &point_format_id, const uint32_t &point_record_length); +uint16_t PointByteSize(const int8_t &point_format_id, const uint16_t &eb_byte_size); -bool FormatHasGPSTime(const uint8_t &point_format_id); -bool FormatHasRGB(const uint8_t &point_format_id); -bool FormatHasNIR(const uint8_t &point_format_id); +bool FormatHasRgb(const uint8_t &point_format_id); +bool FormatHasNir(const uint8_t &point_format_id); template double ApplyScale(T value, double scale, double offset) { return (value * scale) + offset; } template T RemoveScale(double value, double scale, double offset) diff --git a/cpp/include/copc-lib/las/vlr.hpp b/cpp/include/copc-lib/las/vlr.hpp index c5bacab3..cd908964 100644 --- a/cpp/include/copc-lib/las/vlr.hpp +++ b/cpp/include/copc-lib/las/vlr.hpp @@ -2,15 +2,33 @@ #define COPCLIB_LAS_VLR_H_ #include +#include +#include + #include namespace copc::las { using WktVlr = lazperf::wkt_vlr; -using CopcVlr = lazperf::copc_vlr; using EbVlr = lazperf::eb_vlr; -using VlrHeader = lazperf::vlr_header; +using CopcExtentsVlr = lazperf::copc_extents_vlr; + +int NumBytesFromExtraBytes(const std::vector &items); + +class VlrHeader : public lazperf::evlr_header +{ + public: + bool evlr_flag{false}; + + VlrHeader() = default; + VlrHeader(const lazperf::evlr_header &evlr_header) : evlr_flag(true), lazperf::evlr_header(evlr_header){}; + VlrHeader(const lazperf::vlr_header &vlr_header); + VlrHeader(const VlrHeader &vlr_header); + + lazperf::vlr_header ToLazperfVlrHeader() const; + lazperf::evlr_header ToLazperfEvlrHeader() const; +}; } // namespace copc::las diff --git a/cpp/include/copc-lib/laz/compressor.hpp b/cpp/include/copc-lib/laz/compressor.hpp index 67698e79..2e993ea7 100644 --- a/cpp/include/copc-lib/laz/compressor.hpp +++ b/cpp/include/copc-lib/laz/compressor.hpp @@ -1,14 +1,15 @@ #ifndef COPCLIB_LAZ_COMPRESS_H_ #define COPCLIB_LAZ_COMPRESS_H_ +#include +#include +#include + #include "copc-lib/io/writer.hpp" #include "copc-lib/las/utils.hpp" #include -#include -#include - using namespace lazperf; namespace copc::laz @@ -18,14 +19,14 @@ class Compressor { public: // Compresses bytes and writes them to the out stream - static uint32_t CompressBytes(std::ostream &out_stream, const int8_t &point_format_id, - const uint16_t &num_extra_bytes, std::vector &in) + static uint32_t CompressBytes(std::ostream &out_stream, const int8_t &point_format_id, const uint16_t &eb_byte_size, + std::vector &in) { OutFileStream stream(out_stream); - las_compressor::ptr compressor = build_las_compressor(stream.cb(), point_format_id, num_extra_bytes); + las_compressor::ptr compressor = build_las_compressor(stream.cb(), point_format_id, eb_byte_size); - int point_size = copc::las::ComputePointBytes(point_format_id, num_extra_bytes); + int point_size = copc::las::PointByteSize(point_format_id, eb_byte_size); if (in.size() % point_size != 0) throw std::runtime_error("Invalid input stream for compression!"); @@ -43,21 +44,21 @@ class Compressor static uint32_t CompressBytes(std::ostream &out_stream, las::LasHeader const &header, std::vector &in) { - return CompressBytes(out_stream, header.point_format_id, header.NumExtraBytes(), in); + return CompressBytes(out_stream, header.PointFormatId(), header.EbByteSize(), in); } static std::vector CompressBytes(std::vector &in, const int8_t &point_format_id, - const uint16_t &num_extra_bytes) + const uint16_t &eb_byte_size) { std::ostringstream out_stream; - CompressBytes(out_stream, point_format_id, num_extra_bytes, in); + CompressBytes(out_stream, point_format_id, eb_byte_size, in); auto ostr = out_stream.str(); return std::vector(ostr.begin(), ostr.end()); } static std::vector CompressBytes(std::vector &in, const las::LasHeader &header) { - return CompressBytes(in, header.point_format_id, header.NumExtraBytes()); + return CompressBytes(in, header.PointFormatId(), header.EbByteSize()); } }; } // namespace copc::laz diff --git a/cpp/include/copc-lib/laz/decompressor.hpp b/cpp/include/copc-lib/laz/decompressor.hpp index 0d8f8646..034d6ace 100644 --- a/cpp/include/copc-lib/laz/decompressor.hpp +++ b/cpp/include/copc-lib/laz/decompressor.hpp @@ -20,14 +20,14 @@ class Decompressor public: // Decompresses bytes from the instream and returns them static std::vector DecompressBytes(std::istream &in_stream, const int8_t &point_format_id, - const uint16_t &num_extra_bytes, const int &point_count) + const uint16_t &eb_byte_size, const int &point_count) { std::vector out; InFileStream stre(in_stream); - las_decompressor::ptr decompressor = build_las_decompressor(stre.cb(), point_format_id, num_extra_bytes); + las_decompressor::ptr decompressor = build_las_decompressor(stre.cb(), point_format_id, eb_byte_size); - int point_size = copc::las::ComputePointBytes(point_format_id, num_extra_bytes); + int point_size = copc::las::PointByteSize(point_format_id, eb_byte_size); char buff[255]; for (int i = 0; i < point_count; i++) { @@ -42,20 +42,20 @@ class Decompressor static std::vector DecompressBytes(std::istream &in_stream, const las::LasHeader &header, const int &point_count) { - return DecompressBytes(in_stream, header.point_format_id, header.NumExtraBytes(), point_count); + return DecompressBytes(in_stream, header.PointFormatId(), header.EbByteSize(), point_count); } static std::vector DecompressBytes(const std::vector &compressed_data, const int8_t &point_format_id, - const uint16_t &num_extra_bytes, const int &point_count) + const uint16_t &eb_byte_size, const int &point_count) { std::istringstream in_stream(std::string(compressed_data.begin(), compressed_data.end())); - return DecompressBytes(in_stream, point_format_id, num_extra_bytes, point_count); + return DecompressBytes(in_stream, point_format_id, eb_byte_size, point_count); } static std::vector DecompressBytes(const std::vector &compressed_data, const las::LasHeader &header, const int &point_count) { - return DecompressBytes(compressed_data, header.point_format_id, header.NumExtraBytes(), point_count); + return DecompressBytes(compressed_data, header.PointFormatId(), header.EbByteSize(), point_count); } }; } // namespace copc::laz diff --git a/cpp/src/copc/config.cpp b/cpp/src/copc/config.cpp new file mode 100644 index 00000000..22620bcc --- /dev/null +++ b/cpp/src/copc/config.cpp @@ -0,0 +1,28 @@ +#include "copc-lib/copc/config.hpp" + +#include + +namespace copc +{ +CopcConfig::CopcConfig(const int8_t &point_format_id, const Vector3 &scale, const Vector3 &offset, + const std::string &wkt, const las::EbVlr &extra_bytes_vlr, bool has_extended_stats) + : wkt_(wkt) +{ + header_ = std::make_shared( + point_format_id, las::PointBaseByteSize(point_format_id) + las::NumBytesFromExtraBytes(extra_bytes_vlr.items), + scale, offset); + copc_info_ = std::make_shared(); + copc_extents_ = + std::make_shared(point_format_id, extra_bytes_vlr.items.size(), has_extended_stats); + eb_vlr_ = std::make_shared(extra_bytes_vlr); +} + +CopcConfigWriter::CopcConfigWriter(const int8_t &point_format_id, const Vector3 &scale, const Vector3 &offset, + const std::string &wkt, const las::EbVlr &extra_bytes_vlr, bool has_extended_stats) + : CopcConfig(point_format_id, scale, offset, wkt, extra_bytes_vlr, has_extended_stats) +{ + if (point_format_id < 6 || point_format_id > 8) + throw std::runtime_error("LasConfig: Supported point formats are 6 to 8."); +} + +} // namespace copc diff --git a/cpp/src/copc/extents.cpp b/cpp/src/copc/extents.cpp new file mode 100644 index 00000000..18e955a4 --- /dev/null +++ b/cpp/src/copc/extents.cpp @@ -0,0 +1,167 @@ +#include "copc-lib/copc/extents.hpp" + +#include + +namespace copc +{ + +CopcExtent::CopcExtent() : las::CopcExtentsVlr::CopcExtent() {} + +CopcExtent::CopcExtent(double minimum, double maximum, double mean, double var) + : las::CopcExtentsVlr::CopcExtent(minimum, maximum), mean(mean), var(var) +{ + if (minimum > maximum) + throw std::runtime_error("CopcExtent: Minimum value must be less or equal than maximum value."); +} + +CopcExtent::CopcExtent(const std::vector &vec) +{ + if (vec.size() != 2 && vec.size() != 4) + throw std::runtime_error("CopcExtent: Vector size must be 2 or 4."); + + if (vec[0] > vec[1]) + throw std::runtime_error("CopcExtent: Minimum value must be less or equal than maximum value."); + minimum = vec[0]; + maximum = vec[1]; + + if (vec.size() == 4) + { + mean = vec[2]; + var = vec[3]; + } +} + +CopcExtent::CopcExtent(const las::CopcExtentsVlr::CopcExtent &other) + : las::CopcExtentsVlr::CopcExtent(other.minimum, other.maximum) +{ + if (other.minimum > other.maximum) + throw std::runtime_error("CopcExtent: Minimum value must be less or equal than maximum value."); +} + +std::string CopcExtent::ToString() const +{ + std::stringstream ss; + ss << "(" << minimum << "/" << maximum << "/" << mean << "/" << var << ")"; + return ss.str(); +} + +// Empty constructor +CopcExtents::CopcExtents(int8_t point_format_id, uint16_t num_eb_items, bool has_extended_stats) + : point_format_id_(point_format_id), has_extended_stats_(has_extended_stats) +{ + if (point_format_id < 6 || point_format_id > 8) + throw std::runtime_error("CopcExtents: Supported point formats are 6 to 8."); + auto num_extents = NumberOfExtents(point_format_id, num_eb_items); + extents_.reserve(num_extents); + for (int i{0}; i < num_extents; i++) + extents_.push_back(std::make_shared()); +} + +// Copy constructor +CopcExtents::CopcExtents(const CopcExtents &extents) + : point_format_id_(extents.PointFormatId()), has_extended_stats_(extents.HasExtendedStats()) +{ + extents_.reserve(extents.NumberOfExtents()); + for (int i{0}; i < extents.NumberOfExtents(); i++) + extents_.push_back(std::make_shared(extents.Extents()[i])); +} + +// VLR constructor +CopcExtents::CopcExtents(const las::CopcExtentsVlr &vlr, int8_t point_format_id, uint16_t num_eb_items, + bool has_extended_stats) + : point_format_id_(point_format_id), has_extended_stats_(has_extended_stats) +{ + if (point_format_id < 6 || point_format_id > 8) + throw std::runtime_error("CopcExtents: Supported point formats are 6 to 8."); + + if (vlr.items.size() - 3 != + NumberOfExtents(point_format_id, num_eb_items)) // -3 takes into account extra extents for x,y,z from LAS header + throw std::runtime_error("CopcExtents: Number of extents incorrect."); + extents_.reserve(NumberOfExtents(point_format_id, num_eb_items)); + for (int i = 3; i < vlr.items.size(); i++) + { + extents_.push_back(std::make_shared(vlr.items[i])); + } +} + +las::CopcExtentsVlr CopcExtents::ToLazPerf(const CopcExtent &x, const CopcExtent &y, const CopcExtent &z) const +{ + las::CopcExtentsVlr vlr; + vlr.items.reserve(extents_.size() + 3); // +3 takes into account extra extents for x,y,z from LAS header + vlr.items.push_back(x); + vlr.items.push_back(y); + vlr.items.push_back(z); + for (const auto &extent : extents_) + vlr.items.push_back(*extent); + + return vlr; +} + +las::CopcExtentsVlr CopcExtents::ToLazPerfExtended() const +{ + las::CopcExtentsVlr vlr; + vlr.items.reserve(extents_.size() + 3); // +3 takes into account extra extents for x,y,z from LAS header + vlr.items.emplace_back(); // TODO: Handle x,y,z later + vlr.items.emplace_back(); + vlr.items.emplace_back(); + for (const auto &extent : extents_) + vlr.items.emplace_back(extent->mean, extent->var); // Add mean/var instead of min/max + return vlr; +} + +void CopcExtents::SetExtendedStats(const las::CopcExtentsVlr &vlr) +{ + if (!has_extended_stats_) + throw std::runtime_error("CopcExtents::SetExtendedStats: This instance does not have extended stats."); + if (vlr.items.size() - 3 != extents_.size()) // -3 takes into account extra extents for x,y,z from LAS header + throw std::runtime_error("CopcExtents::SetExtendedStats: Number of extended extents incorrect."); + for (int i = 3; i < vlr.items.size(); i++) + { + extents_[i - 3]->mean = vlr.items[i].minimum; + extents_[i - 3]->var = vlr.items[i].maximum; + } +} + +int CopcExtents::NumberOfExtents(int8_t point_format_id, uint16_t num_eb_items) +{ + return las::PointBaseNumberDimensions(point_format_id) - 3 + + num_eb_items; // -3 disregards x,y,z since they are not handled in Extents +} + +size_t CopcExtents::ByteSize(int8_t point_format_id, uint16_t num_eb_items) +{ + return CopcExtents(point_format_id, num_eb_items).ToLazPerf({}, {}, {}).size(); +} + +std::string CopcExtents::ToString() const +{ + std::stringstream ss; + ss << "Copc Extents (Min/Max/Mean/Var):" << std::endl; + ss << "\tIntensity: " << extents_[0]->ToString() << std::endl; + ss << "\tReturn Number: " << extents_[1]->ToString() << std::endl; + ss << "\tNumber Of Returns: " << extents_[2]->ToString() << std::endl; + ss << "\tScanner Channel: " << extents_[3]->ToString() << std::endl; + ss << "\tScan Direction Flag: " << extents_[4]->ToString() << std::endl; + ss << "\tEdge Of Flight Line: " << extents_[5]->ToString() << std::endl; + ss << "\tClassification: " << extents_[6]->ToString() << std::endl; + ss << "\tUser Data: " << extents_[7]->ToString() << std::endl; + ss << "\tScan Angle: " << extents_[8]->ToString() << std::endl; + ss << "\tPoint Source ID: " << extents_[9]->ToString() << std::endl; + ss << "\tGPS Time: " << extents_[10]->ToString() << std::endl; + if (point_format_id_ > 6) + { + ss << "\tRed: " << extents_[11]->ToString() << std::endl; + ss << "\tGreen: " << extents_[12]->ToString() << std::endl; + ss << "\tBlue: " << extents_[13]->ToString() << std::endl; + } + if (point_format_id_ == 8) + ss << "\tNIR: " << extents_[14]->ToString() << std::endl; + ss << "\tExtra Bytes:" << std::endl; + for (int i = las::PointBaseNumberDimensions(point_format_id_); i < extents_.size(); i++) + { + ss << "\t\t" << extents_[i]->ToString() << std::endl; + } + return ss.str(); +} + +} // namespace copc diff --git a/cpp/src/copc/info.cpp b/cpp/src/copc/info.cpp new file mode 100644 index 00000000..d0099c85 --- /dev/null +++ b/cpp/src/copc/info.cpp @@ -0,0 +1,45 @@ +#include "copc-lib/copc/info.hpp" + +#include + +namespace copc +{ +CopcInfo::CopcInfo(const lazperf::copc_info_vlr &copc_info_vlr) +{ + center_x = copc_info_vlr.center_x; + center_y = copc_info_vlr.center_y; + center_z = copc_info_vlr.center_z; + halfsize = copc_info_vlr.halfsize; + spacing = copc_info_vlr.spacing; + root_hier_offset = copc_info_vlr.root_hier_offset; + root_hier_size = copc_info_vlr.root_hier_size; +} + +lazperf::copc_info_vlr CopcInfo::ToLazPerf() const +{ + lazperf::copc_info_vlr copc_info_vlr; + + copc_info_vlr.center_x = center_x; + copc_info_vlr.center_y = center_y; + copc_info_vlr.center_z = center_z; + copc_info_vlr.halfsize = halfsize; + copc_info_vlr.spacing = spacing; + copc_info_vlr.root_hier_offset = root_hier_offset; + copc_info_vlr.root_hier_size = root_hier_size; + + return copc_info_vlr; +} + +std::string CopcInfo::ToString() const +{ + std::stringstream ss; + ss << "CopcInfo:" << std::endl; + ss << "\tcenter_x: " << center_x << std::endl; + ss << "\tcenter_y: " << center_y << std::endl; + ss << "\tcenter_z: " << center_z << std::endl; + ss << "\thalfsize: " << halfsize << std::endl; + ss << "\tspacing: " << spacing << std::endl; + return ss.str(); +} + +} // namespace copc diff --git a/cpp/src/geometry/box.cpp b/cpp/src/geometry/box.cpp index 0473ebd2..4a6f7330 100644 --- a/cpp/src/geometry/box.cpp +++ b/cpp/src/geometry/box.cpp @@ -6,8 +6,7 @@ namespace copc { // 3D box constructor -Box::Box(const double &x_min, const double &y_min, const double &z_min, const double &x_max, const double &y_max, - const double &z_max) +Box::Box(double x_min, double y_min, double z_min, double x_max, double y_max, double z_max) : x_min(x_min), y_min(y_min), z_min(z_min), x_max(x_max), y_max(y_max), z_max(z_max) { if (x_max < x_min || y_max < y_min || z_max < z_min) @@ -15,7 +14,7 @@ Box::Box(const double &x_min, const double &y_min, const double &z_min, const do } // 2D box constructor -Box::Box(const double &x_min, const double &y_min, const double &x_max, const double &y_max) +Box::Box(double x_min, double y_min, double x_max, double y_max) : Box(x_min, y_min, -std::numeric_limits::max(), x_max, y_max, std::numeric_limits::max()) { } @@ -57,7 +56,7 @@ Box::Box(const VoxelKey &key, const las::LasHeader &header) { // Step size accounts for depth level - double step = header.GetSpan() / std::pow(2, key.d); + double step = header.Span() / std::pow(2, key.d); x_min = step * key.x + header.min.x; y_min = step * key.y + header.min.y; diff --git a/cpp/src/hierarchy/key.cpp b/cpp/src/hierarchy/key.cpp index c72a0a03..8d7b08b8 100644 --- a/cpp/src/hierarchy/key.cpp +++ b/cpp/src/hierarchy/key.cpp @@ -48,13 +48,13 @@ VoxelKey VoxelKey::GetParent() const return {}; } -std::vector VoxelKey::GetParents(bool include_current) const +std::vector VoxelKey::GetParents(bool include_self) const { std::vector out; if (!IsValid()) return out; - if (include_current) + if (include_self) out.push_back(*this); auto parentKey = this->GetParent(); @@ -77,14 +77,12 @@ bool VoxelKey::ChildOf(VoxelKey parent_key) const return false; } -double VoxelKey::Resolution(const las::LasHeader &header, const las::CopcVlr &copc_info) const +double VoxelKey::Resolution(const las::LasHeader &header, const CopcInfo &copc_info) const { - if (copc_info.span <= 0) - throw std::runtime_error("VoxelKey::Resolution: Octree span must be greater than 0."); - return (header.max.x - header.min.x) / copc_info.span / std::pow(2, d); + return copc_info.spacing / std::pow(2, d); } -double VoxelKey::GetResolutionAtDepth(int32_t d, const las::LasHeader &header, const las::CopcVlr &copc_info) +double VoxelKey::GetResolutionAtDepth(int32_t d, const las::LasHeader &header, const CopcInfo &copc_info) { return VoxelKey(d, 0, 0, 0).Resolution(header, copc_info); } diff --git a/cpp/src/io/reader.cpp b/cpp/src/io/reader.cpp index d1578954..3a7a35c5 100644 --- a/cpp/src/io/reader.cpp +++ b/cpp/src/io/reader.cpp @@ -2,12 +2,15 @@ #include #include +#include "copc-lib/copc/config.hpp" +#include "copc-lib/copc/extents.hpp" #include "copc-lib/hierarchy/internal/hierarchy.hpp" #include "copc-lib/io/reader.hpp" #include "copc-lib/las/header.hpp" #include "copc-lib/laz/decompressor.hpp" #include +#include namespace copc { @@ -15,64 +18,126 @@ namespace copc void Reader::InitReader() { - if (!this->in_stream_->good()) + if (!in_stream_->good()) throw std::runtime_error("Invalid input stream!"); - this->reader_ = std::make_unique(*this->in_stream_); + reader_ = std::make_unique(*in_stream_); - auto header = las::LasHeader::FromLazPerf(this->reader_->header()); + auto header = las::LasHeader::FromLazPerf(reader_->header()); - std::map vlrs = ReadVlrs(); - auto copc_data = ReadCopcData(); - auto wkt = ReadWktData(copc_data); - auto eb = ReadExtraByteVlr(vlrs); + // Load vlrs and evlrs + vlrs_ = ReadVlrHeaders(); - this->file_ = std::make_shared(header, copc_data, wkt, eb); - this->file_->vlrs = vlrs; + auto copc_info = ReadCopcInfoVlr(); + auto wkt = ReadWktVlr(vlrs_); + auto eb = ReadExtraBytesVlr(vlrs_); + auto copc_extents = ReadCopcExtentsVlr(vlrs_, eb); - this->hierarchy_ = std::make_shared(copc_data.root_hier_offset, copc_data.root_hier_size); + config_ = copc::CopcConfig(header, copc_info, copc_extents, wkt.wkt, eb); + + hierarchy_ = std::make_shared(copc_info.root_hier_offset, copc_info.root_hier_size); } -std::map Reader::ReadVlrs() +std::map Reader::ReadVlrHeaders() { std::map out; // Move stream to beginning of VLRs - this->in_stream_->seekg(this->reader_->header().header_size); + in_stream_->seekg(reader_->header().header_size); // Iterate through all vlr's and add them to the `vlrs` list - size_t count = 0; - while (count < this->reader_->header().vlr_count && this->in_stream_->good() && !this->in_stream_->eof()) + for (int i = 0; i < reader_->header().vlr_count; i++) { - uint64_t cur_pos = this->in_stream_->tellg(); - las::VlrHeader h = las::VlrHeader::create(*this->in_stream_); - out[cur_pos] = h; + uint64_t cur_pos = in_stream_->tellg(); + auto h = las::VlrHeader(lazperf::vlr_header::create(*in_stream_)); + out.insert({cur_pos, h}); - this->in_stream_->seekg(h.data_length, std::ios::cur); // jump foward - count++; + in_stream_->seekg(h.data_length, std::ios::cur); // jump foward } + + // Move stream to beginning of EVLRs + in_stream_->seekg(reader_->header().evlr_offset); + + // Iterate through all vlr's and add them to the `vlrs` list + for (int i = 0; i < reader_->header().evlr_count; i++) + { + uint64_t cur_pos = in_stream_->tellg(); + auto h = las::VlrHeader(lazperf::evlr_header::create(*in_stream_)); + out.insert({cur_pos, h}); + + in_stream_->seekg(h.data_length, std::ios::cur); // jump foward + } + return out; } -las::CopcVlr Reader::ReadCopcData() +CopcInfo Reader::ReadCopcInfoVlr() +{ + in_stream_->seekg(COPC_OFFSET); + return {lazperf::copc_info_vlr::create(*in_stream_)}; +} + +CopcExtents Reader::ReadCopcExtentsVlr(std::map &vlrs, const las::EbVlr &eb_vlr) const +{ + auto offset = FetchVlr(vlrs, "copc", 10000); + auto extended_offset = FetchVlr(vlrs, "rock_robotic", 10001); + if (offset == 0) + throw std::runtime_error("Reader::ReadCopcExtentsVlr: No COPC Extents VLR found in file."); + + in_stream_->seekg(offset + lazperf::vlr_header::Size); + CopcExtents extents(las::CopcExtentsVlr::create(*in_stream_, vlrs[offset].data_length), + static_cast(reader_->header().point_format_id), + static_cast(eb_vlr.items.size()), extended_offset != 0); + + // Load mean/var if they exist + if (extended_offset != 0) + { + in_stream_->seekg(extended_offset + lazperf::vlr_header::Size); + extents.SetExtendedStats(las::CopcExtentsVlr::create(*in_stream_, vlrs[extended_offset].data_length)); + } + return extents; +} + +las::WktVlr Reader::ReadWktVlr(std::map &vlrs) +{ + auto offset = FetchVlr(vlrs, "LASF_Projection", 2112); + if (offset != 0) + { + in_stream_->seekg(offset + lazperf::vlr_header::Size); + return las::WktVlr::create(*in_stream_, vlrs[offset].data_length); + } + return las::WktVlr(); +} + +las::EbVlr Reader::ReadExtraBytesVlr(std::map &vlrs) { - this->in_stream_->seekg(COPC_OFFSET); - las::CopcVlr copc = las::CopcVlr::create(*this->in_stream_); - return copc; + auto offset = FetchVlr(vlrs, "LASF_Spec", 4); + if (offset != 0) + { + in_stream_->seekg(offset + lazperf::vlr_header::Size); + return las::EbVlr::create(*in_stream_, vlrs[offset].data_length); + } + return las::EbVlr(); } -las::WktVlr Reader::ReadWktData(const las::CopcVlr &copc_data) +uint64_t Reader::FetchVlr(const std::map &vlrs, const std::string &user_id, + uint16_t record_id) { - this->in_stream_->seekg(copc_data.wkt_vlr_offset); - las::WktVlr wkt = las::WktVlr::create(*this->in_stream_, copc_data.wkt_vlr_size); - return wkt; + for (auto &[offset, vlr_header] : vlrs) + { + if (vlr_header.user_id == user_id && vlr_header.record_id == record_id) + { + return offset; + } + } + return 0; } std::vector Reader::ReadPage(std::shared_ptr page) { std::vector out; if (!page->IsValid()) - throw std::runtime_error("Cannot load an invalid page."); + throw std::runtime_error("Reader::ReadPage: Cannot load an invalid page."); // reset the stream to the page's offset in_stream_->seekg(page->offset); @@ -95,7 +160,7 @@ std::vector Reader::ReadPage(std::shared_ptr page las::Points Reader::GetPoints(Node const &node) { std::vector point_data = GetPointData(node); - return las::Points::Unpack(point_data, file_->GetLasHeader()); + return las::Points::Unpack(point_data, config_.LasHeader()); } las::Points Reader::GetPoints(VoxelKey const &key) @@ -103,19 +168,19 @@ las::Points Reader::GetPoints(VoxelKey const &key) std::vector point_data = GetPointData(key); if (point_data.empty()) - return las::Points(file_->GetLasHeader()); + return las::Points(config_.LasHeader()); - return las::Points::Unpack(point_data, file_->GetLasHeader()); + return las::Points::Unpack(point_data, config_.LasHeader()); } std::vector Reader::GetPointData(Node const &node) { if (!node.IsValid()) - throw std::runtime_error("Cannot load an invalid node."); + throw std::runtime_error("Reader::GetPointData: Cannot load an invalid node."); in_stream_->seekg(node.offset); - auto las_header = file_->GetLasHeader(); + auto las_header = config_.LasHeader(); std::vector point_data = laz::Decompressor::DecompressBytes(*in_stream_, las_header, node.point_count); return point_data; } @@ -136,7 +201,7 @@ std::vector Reader::GetPointData(VoxelKey const &key) std::vector Reader::GetPointDataCompressed(Node const &node) { if (!node.IsValid()) - throw std::runtime_error("Cannot load an invalid node."); + throw std::runtime_error("Reader::GetPointDataCompressed: Cannot load an invalid node."); in_stream_->seekg(node.offset); @@ -159,7 +224,7 @@ std::vector Reader::GetPointDataCompressed(VoxelKey const &key) return GetPointDataCompressed(node); } -std::vector Reader::GetAllChildren(const VoxelKey &key) +std::vector Reader::GetAllChildrenOfPage(const VoxelKey &key) { std::vector out; if (!key.IsValid()) @@ -182,12 +247,12 @@ std::vector Reader::GetAllChildren(const VoxelKey &key) las::Points Reader::GetAllPoints(double resolution) { - auto out = las::Points(GetLasHeader()); + auto out = las::Points(config_.LasHeader()); auto max_depth = GetDepthAtResolution(resolution); // Get all nodes in octree - for (const auto &node : GetAllChildren()) + for (const auto &node : GetAllNodes()) if (node.key.d <= max_depth) out.AddPoints(GetPoints(node)); return out; @@ -197,13 +262,11 @@ std::vector Reader::GetNodesWithinBox(const Box &box, double resolution) { std::vector out; - auto header = GetLasHeader(); - auto copc_info = GetCopcHeader(); auto max_depth = GetDepthAtResolution(resolution); - for (const auto &node : GetAllChildren()) + for (const auto &node : GetAllNodes()) { - if (node.key.Within(header, box) && node.key.d <= max_depth) + if (node.key.Within(config_.LasHeader(), box) && node.key.d <= max_depth) out.push_back(node); } @@ -214,14 +277,12 @@ std::vector Reader::GetNodesIntersectBox(const Box &box, double resolution { std::vector out; - auto header = GetLasHeader(); - auto copc_info = GetCopcHeader(); auto max_depth = GetDepthAtResolution(resolution); // Get all nodes in octree - for (const auto &node : GetAllChildren()) + for (const auto &node : GetAllNodes()) { - if (node.key.Intersects(header, box) && node.key.d <= max_depth) + if (node.key.Intersects(config_.LasHeader(), box) && node.key.d <= max_depth) out.push_back(node); } @@ -230,23 +291,21 @@ std::vector Reader::GetNodesIntersectBox(const Box &box, double resolution las::Points Reader::GetPointsWithinBox(const Box &box, double resolution) { - auto header = GetLasHeader(); - auto copc_info = GetCopcHeader(); auto max_depth = GetDepthAtResolution(resolution); - auto out = las::Points(header); + auto out = las::Points(config_.LasHeader()); // Get all nodes in octree - for (const auto &node : GetAllChildren()) + for (const auto &node : GetAllNodes()) { if (node.key.d <= max_depth) { // If node fits in Box - if (node.key.Within(header, box)) + if (node.key.Within(config_.LasHeader(), box)) { // If the node is within the box add all points out.AddPoints(GetPoints(node)); } - else if (node.key.Intersects(header, box)) + else if (node.key.Intersects(config_.LasHeader(), box)) { // If the node only crosses the box then get subset of points within box auto points = GetPoints(node); @@ -262,7 +321,7 @@ int32_t Reader::GetDepthAtResolution(double resolution) // Compute max depth int32_t max_depth = -1; // Get all nodes in octree - for (const auto &node : GetAllChildren()) + for (const auto &node : GetAllNodes()) { if (node.key.d > max_depth) max_depth = node.key.d; @@ -271,10 +330,8 @@ int32_t Reader::GetDepthAtResolution(double resolution) // If query resolution is <=0 return the octree's max depth if (resolution <= 0.0) return max_depth; - if (GetCopcHeader().span <= 0) - throw std::runtime_error("Reader::GetDepthAtResolution: Octree span must be greater than 0."); - auto current_resolution = GetLasHeader().GetSpan() / GetCopcHeader().span; + auto current_resolution = config_.CopcInfo().spacing; for (int32_t i = 0; i <= max_depth; i++) { @@ -291,7 +348,7 @@ std::vector Reader::GetNodesAtResolution(double resolution) std::vector out; - for (const auto &node : GetAllChildren()) + for (const auto &node : GetAllNodes()) { if (node.key.d == target_depth) out.push_back(node); @@ -306,7 +363,7 @@ std::vector Reader::GetNodesWithinResolution(double resolution) std::vector out; - for (const auto &node : GetAllChildren()) + for (const auto &node : GetAllNodes()) { if (node.key.d <= target_depth) out.push_back(node); @@ -319,13 +376,13 @@ bool Reader::ValidateSpatialBounds(bool verbose) { bool is_valid = true; - auto header = GetLasHeader(); + auto header = config_.LasHeader(); - for (const auto &node : GetAllChildren()) + for (const auto &node : GetAllNodes()) { // Check if node intersects las header bounds - if (!Box(node.key, header).Intersects(header.GetBounds())) + if (!Box(node.key, header).Intersects(header.Bounds())) { is_valid = false; if (verbose) @@ -337,11 +394,11 @@ bool Reader::ValidateSpatialBounds(bool verbose) { auto points = GetPoints(node); // If node not within las header bounds then check individual points - if (!Box(node.key, header).Within(header.GetBounds())) + if (!Box(node.key, header).Within(header.Bounds())) { for (auto const &point : points) { - if (!point->Within(header.GetBounds())) + if (!point->Within(header.Bounds())) { is_valid = false; if (verbose) @@ -371,17 +428,4 @@ bool Reader::ValidateSpatialBounds(bool verbose) return is_valid; } -las::EbVlr Reader::ReadExtraByteVlr(std::map &vlrs) -{ - for (auto &[offset, vlr_header] : vlrs) - { - if (vlr_header.user_id == "LASF_Spec" && vlr_header.record_id == 4) - { - in_stream_->seekg(offset + vlr_header.Size); - return las::EbVlr::create(*in_stream_, vlr_header.data_length); - } - } - return {}; -} - } // namespace copc diff --git a/cpp/src/io/writer_internal.cpp b/cpp/src/io/writer_internal.cpp index e2c282e8..a039da01 100644 --- a/cpp/src/io/writer_internal.cpp +++ b/cpp/src/io/writer_internal.cpp @@ -1,28 +1,47 @@ #include #include +#include +#include "copc-lib/copc/extents.hpp" #include "copc-lib/hierarchy/internal/hierarchy.hpp" #include "copc-lib/io/internal/writer_internal.hpp" #include "copc-lib/laz/compressor.hpp" #include #include - -using namespace lazperf; +#include namespace copc::Internal { -WriterInternal::WriterInternal(std::ostream &out_stream, const std::shared_ptr &file, +size_t WriterInternal::OffsetToPointData() const +{ + size_t base_offset(375 + (54 + 160) + (54 + (34 + 4 * 6))); // header + COPC vlr + LAZ vlr (max 4 items) + + size_t extents_offset = + CopcExtents::ByteSize(copc_config_->LasHeader()->PointFormatId(), copc_config_->ExtraBytesVlr().items.size()) + + lazperf::vlr_header::Size; + // If we store extended stats we need two extents VLRs + if (copc_config_->CopcExtents()->HasExtendedStats()) + extents_offset *= 2; + + size_t wkt_offset = copc_config_->Wkt().size(); + if (wkt_offset > 0) + wkt_offset += lazperf::vlr_header::Size; + + size_t eb_offset = copc_config_->ExtraBytesVlr().size(); + if (eb_offset > 0) + eb_offset += lazperf::vlr_header::Size; + + return base_offset + extents_offset + wkt_offset + eb_offset; +} + +WriterInternal::WriterInternal(std::ostream &out_stream, std::shared_ptr copc_config, std::shared_ptr hierarchy) - : out_stream_(out_stream), file_(file), hierarchy_(hierarchy) + : out_stream_(out_stream), copc_config_(copc_config), hierarchy_(hierarchy) { - // are extra bytes allowed to be an EVLR? If so we should just move it to be an EVLR - // but I don't know... - size_t eb_offset = file_->GetExtraBytes().size(); - OFFSET_TO_POINT_DATA += eb_offset + las::VlrHeader().Size; // reserve enough space for the header & VLRs in the file - std::fill_n(std::ostream_iterator(out_stream_), FIRST_CHUNK_OFFSET(), 0); + std::fill_n(std::ostream_iterator(out_stream_), FirstChunkOffset(), 0); open_ = true; } @@ -31,102 +50,96 @@ void WriterInternal::Close() if (!open_) return; - auto head14 = file_->GetLasHeader(); WriteChunkTable(); - // Always write the wkt for now, since it sets evlr_offset - // if (!file_->GetWkt().empty()) - WriteWkt(head14); + // Set hierarchy evlr + out_stream_.seekp(0, std::ios::end); + evlr_offset_ = static_cast(out_stream_.tellp()); + evlr_count_ += hierarchy_->seen_pages_.size(); // Page writing must be done in a postorder traversal because each parent // has to write the offset of all of its children, which we don't know in advance - WritePageTree(hierarchy_->seen_pages_[VoxelKey::BaseKey()]); - head14.evlr_count += hierarchy_->seen_pages_.size(); + WritePageTree(hierarchy_->seen_pages_[VoxelKey::RootKey()]); - WriteHeader(head14); + WriteHeader(); open_ = false; } // Writes the LAS header and VLRs -void WriterInternal::WriteHeader(las::LasHeader &head14) +void WriterInternal::WriteHeader() { + laz_vlr lazVlr(copc_config_->LasHeader()->PointFormatId(), copc_config_->LasHeader()->EbByteSize(), + VARIABLE_CHUNK_SIZE); - // Convert back to lazperf header for writing - auto laz_header = head14.ToLazPerf(); - - laz_vlr lazVlr(laz_header.point_format_id, laz_header.ebCount(), VARIABLE_CHUNK_SIZE); - - // point_format_id and point_record_length are set on open(). - laz_header.header_size = laz_header.sizeFromVersion(); - laz_header.vlr_count = 2; // copc + laz - - laz_header.point_format_id |= (1 << 7); - laz_header.point_offset = OFFSET_TO_POINT_DATA; - laz_header.point_count_14 = point_count_14_; - - if (laz_header.ebCount()) - { - laz_header.vlr_count++; - } - - if (laz_header.point_count_14 > (std::numeric_limits::max)()) - laz_header.point_count = 0; - else - laz_header.point_count = (uint32_t)laz_header.point_count_14; - // Set the WKT bit. - laz_header.global_encoding |= (1 << 4); + auto las_header_vlr = copc_config_->LasHeader()->ToLazPerf( + OffsetToPointData(), point_count_, evlr_offset_, evlr_count_, !copc_config_->Wkt().empty(), + copc_config_->LasHeader()->EbByteSize(), copc_config_->CopcExtents()->HasExtendedStats()); out_stream_.seekp(0); + las_header_vlr.write(out_stream_); + + // Write the COPC Info VLR. + auto copc_info_vlr = copc_config_->CopcInfo()->ToLazPerf(); + copc_info_vlr.header().write(out_stream_); + copc_info_vlr.write(out_stream_); + + // Write the COPC Extents VLR. + auto extents_vlr = copc_config_->CopcExtents()->ToLazPerf({las_header_vlr.minx, las_header_vlr.maxx}, + {las_header_vlr.miny, las_header_vlr.maxy}, + {las_header_vlr.minz, las_header_vlr.maxz}); + extents_vlr.header().write(out_stream_); + extents_vlr.write(out_stream_); + + // Write the COPC Extended stats VLR. + if (copc_config_->CopcExtents()->HasExtendedStats()) + { + auto extended_stats_vlr = copc_config_->CopcExtents()->ToLazPerfExtended(); - laz_header.write(out_stream_); + auto header = extended_stats_vlr.header(); + header.user_id = "rock_robotic"; + header.record_id = 10001; + header.description = "COPC extended stats"; - // Write the VLR. - copc_data_.span = file_->GetCopc().span; - copc_data_.header().write(out_stream_); - copc_data_.write(out_stream_); + header.write(out_stream_); + extended_stats_vlr.write(out_stream_); + } + // Write the LAZ VLR lazVlr.header().write(out_stream_); lazVlr.write(out_stream_); - if (head14.NumExtraBytes()) + // Write optional WKT VLR + if (!copc_config_->Wkt().empty()) + { + lazperf::wkt_vlr wkt_vlr(copc_config_->Wkt()); + wkt_vlr.header().write(out_stream_); + wkt_vlr.write(out_stream_); + } + + // Write optional Extra Byte VLR + if (copc_config_->LasHeader()->EbByteSize() > 0) { - auto ebVlr = this->file_->GetExtraBytes(); + auto ebVlr = this->copc_config_->ExtraBytesVlr(); ebVlr.header().write(out_stream_); ebVlr.write(out_stream_); } -} - -void WriterInternal::WriteWkt(las::LasHeader &head14) -{ - auto wkt = file_->GetWkt(); - evlr_header h{0, "LASF_Projection", 2112, (uint64_t)wkt.size(), ""}; - - wkt_vlr vlr(wkt); - - out_stream_.seekp(0, std::ios::end); - auto offset = static_cast(out_stream_.tellp()); - copc_data_.wkt_vlr_offset = offset + evlr_header::Size; - copc_data_.wkt_vlr_size = vlr.size(); - - h.write(out_stream_); - vlr.write(out_stream_); - // TODO: Let the Page writer set the evlr offset if no wkt exists - head14.evlr_offset = offset; - head14.evlr_count++; + // Make sure that we haven't gone over allocated size + if (static_cast(out_stream_.tellp()) > OffsetToPointData()) + throw std::runtime_error("WriterInternal::WriteHeader: LasHeader + VLRs are bigger than offset to point data."); } void WriterInternal::WriteChunkTable() { - // move to the end of the file to start emitting our compresed table + // move to the end of the file to start emitting our compressed table out_stream_.seekp(0, std::ios::end); // take note of where we're writing the chunk table, we need this later auto chunk_table_offset = static_cast(out_stream_.tellp()); // Fixup the chunk table to be relative offsets rather than absolute ones. - uint64_t prevOffset = FIRST_CHUNK_OFFSET(); + uint64_t prevOffset = FirstChunkOffset(); for (auto &c : chunks_) { uint64_t relOffset = c.offset - prevOffset; @@ -147,7 +160,7 @@ void WriterInternal::WriteChunkTable() compress_chunk_table(w.cb(), chunks_, true); // go back to where we're supposed to write chunk table offset - out_stream_.seekp(OFFSET_TO_POINT_DATA); + out_stream_.seekp(OffsetToPointData()); out_stream_.write(reinterpret_cast(&chunk_table_offset), sizeof(chunk_table_offset)); } @@ -163,9 +176,9 @@ Entry WriterInternal::WriteNode(std::vector in, int32_t point_count, bool if (compressed) out_stream_.write(in.data(), in.size()); else - point_count = laz::Compressor::CompressBytes(out_stream_, file_->GetLasHeader(), in); + point_count = laz::Compressor::CompressBytes(out_stream_, *copc_config_->LasHeader(), in); - point_count_14_ += point_count; + point_count_ += point_count; auto endpos = out_stream_.tellp(); if (endpos <= 0) @@ -188,7 +201,7 @@ void WriterInternal::WritePage(const std::shared_ptr &page) auto page_size = page->nodes.size() * 32; page_size += page->sub_pages.size() * 32; - evlr_header h{0, "entwine", 1000, page_size, page->key.ToString()}; + evlr_header h{0, "copc", 1000, page_size, page->key.ToString()}; out_stream_.seekp(0, std::ios::end); h.write(out_stream_); @@ -201,10 +214,10 @@ void WriterInternal::WritePage(const std::shared_ptr &page) page->byte_size = static_cast(page_size); // Set the copc header info if needed - if (page->key == VoxelKey::BaseKey()) + if (page->key == VoxelKey::RootKey()) { - copc_data_.root_hier_offset = offset; - copc_data_.root_hier_size = page_size; + copc_config_->CopcInfo()->root_hier_offset = offset; + copc_config_->CopcInfo()->root_hier_size = page_size; } for (const auto &node : page->nodes) diff --git a/cpp/src/io/writer_public.cpp b/cpp/src/io/writer_public.cpp index 6fb976ef..0030630c 100644 --- a/cpp/src/io/writer_public.cpp +++ b/cpp/src/io/writer_public.cpp @@ -1,5 +1,3 @@ -#include - #include "copc-lib/hierarchy/internal/hierarchy.hpp" #include "copc-lib/hierarchy/internal/page.hpp" #include "copc-lib/io/internal/writer_internal.hpp" @@ -10,18 +8,17 @@ namespace copc { -void Writer::InitWriter(std::ostream &out_stream, LasConfig const &config, const int &span, const std::string &wkt) +void Writer::InitWriter(std::ostream &out_stream, const CopcConfigWriter &copc_file_writer) { - auto header = HeaderFromConfig(config); - this->file_ = std::make_shared(header, span, wkt, config.extra_bytes); + this->config_ = std::make_shared(copc_file_writer); this->hierarchy_ = std::make_shared(); - this->writer_ = std::make_unique(out_stream, this->file_, this->hierarchy_); + this->writer_ = std::make_unique(out_stream, this->config_, this->hierarchy_); } Writer::~Writer() { writer_->Close(); } void Writer::Close() { writer_->Close(); } -Page Writer::GetRootPage() { return *this->hierarchy_->seen_pages_[VoxelKey::BaseKey()]; } +Page Writer::GetRootPage() { return *this->hierarchy_->seen_pages_[VoxelKey::RootKey()]; } // Create a page, add it to the hierarchy and reference it as a subpage in the parent Page Writer::AddSubPage(Page &parent, VoxelKey key) @@ -67,8 +64,8 @@ Node Writer::DoAddNode(Page &page, VoxelKey key, std::vector in, uint64_t Node Writer::AddNode(Page &page, const VoxelKey &key, las::Points &points) { - auto header = file_->GetLasHeader(); - if (points.PointFormatID() != header.point_format_id || points.PointRecordLength() != header.point_record_length) + if (points.PointFormatId() != config_->LasHeader()->PointFormatId() || + points.PointRecordLength() != config_->LasHeader()->PointRecordLength()) throw std::runtime_error("Writer::AddNode: New points must be of same format and size."); std::vector uncompressed = points.Pack(); @@ -77,7 +74,7 @@ Node Writer::AddNode(Page &page, const VoxelKey &key, las::Points &points) Node Writer::AddNode(Page &page, const VoxelKey &key, std::vector const &uncompressed) { - int point_size = file_->GetLasHeader().point_record_length; + int point_size = config_->LasHeader()->PointRecordLength(); if (uncompressed.size() < point_size || uncompressed.size() % point_size != 0) throw std::runtime_error("Invalid point data array!"); @@ -93,71 +90,10 @@ Node Writer::AddNodeCompressed(Page &page, const VoxelKey &key, std::vector &items) -{ - int out = 0; - for (const auto &item : items) - { - if (item.data_type == 0) - out += item.options; - else - out += EXTRA_BYTE_DATA_TYPE[item.data_type]; - } - return out; -} - -las::LasHeader Writer::HeaderFromConfig(LasConfig const &config) -{ - las::LasHeader h; - h.file_source_id = config.file_source_id; - h.global_encoding = config.global_encoding; - h.creation_day = config.creation_day; - h.creation_year = config.creation_year; - h.point_format_id = config.point_format_id; - h.point_record_length = - las::PointBaseByteSize(config.point_format_id) + NumBytesFromExtraBytes(config.extra_bytes.items); - - h.GUID(config.GUID()); - h.SystemIdentifier(config.SystemIdentifier()); - h.GeneratingSoftware(config.GeneratingSoftware()); - - h.offset = config.offset; - h.scale = config.scale; - h.max = config.max; - h.min = config.min; - - h.points_by_return_14 = config.points_by_return_14; - return h; -} - -copc::Writer::LasConfig::LasConfig(const las::LasHeader &config, const las::EbVlr &extra_bytes_) -{ - file_source_id = config.file_source_id; - global_encoding = config.global_encoding; - creation_day = config.creation_day; - creation_year = config.creation_year; - point_format_id = config.point_format_id; - - guid_ = config.GUID(); - system_identifier_ = config.SystemIdentifier(); - generating_software_ = config.GeneratingSoftware(); - - offset = config.offset; - scale = config.scale; - - max = config.max; - min = config.min; - - points_by_return_14 = config.points_by_return_14; - extra_bytes = extra_bytes_; -} - void FileWriter::Close() { writer_->Close(); f_stream_.close(); } + } // namespace copc diff --git a/cpp/src/las/header.cpp b/cpp/src/las/header.cpp index fee2d8bc..8b6751c0 100644 --- a/cpp/src/las/header.cpp +++ b/cpp/src/las/header.cpp @@ -1,6 +1,8 @@ #include "copc-lib/las/header.hpp" #include +#include +#include #include "copc-lib/geometry/box.hpp" #include "copc-lib/las/utils.hpp" @@ -8,76 +10,97 @@ namespace copc::las { +Box LasHeader::Bounds() const { return Box(min, max); } -uint16_t LasHeader::NumExtraBytes() const { return ComputeNumExtraBytes(point_format_id, point_record_length); } +uint16_t LasHeader::EbByteSize() const { return copc::las::EbByteSize(point_format_id_, point_record_length_); } LasHeader LasHeader::FromLazPerf(const lazperf::header14 &header) { LasHeader h; h.file_source_id = header.file_source_id; + // Check WKT bit + if (!(header.global_encoding & 0x10)) + throw std::runtime_error("LasHeader::FromLazPerf: WKT bit must be set."); h.global_encoding = header.global_encoding; h.guid_ = header.guid; - h.version_major = header.version.major; - h.version_minor = header.version.minor; + if (header.version.major != 1 || header.version.minor != 4) + throw std::runtime_error("LasHeader::FromLazPerf: Header version needs to be 1.4"); h.system_identifier_ = header.system_identifier; h.generating_software_ = header.generating_software; h.creation_day = header.creation.day; h.creation_year = header.creation.year; - h.header_size = header.header_size; - h.point_offset = header.point_offset; - h.vlr_count = header.vlr_count; - h.point_format_id = static_cast(header.point_format_id); - h.point_record_length = header.point_record_length; - h.point_count = header.point_count; + if (header.header_size != 375) + throw std::runtime_error("LasHeader::FromLazPerf: Header size must be 375."); + h.point_offset_ = header.point_offset; + h.vlr_count_ = header.vlr_count; + if (header.point_format_id < 6 || header.point_format_id > 8) + throw std::runtime_error("LasHeader::FromLazPerf: Supported point formats are 6 to 8."); + h.point_format_id_ = static_cast(header.point_format_id); + h.point_record_length_ = header.point_record_length; std::copy(std::begin(header.points_by_return), std::end(header.points_by_return), std::begin(h.points_by_return)); - h.scale.x = header.scale.x; - h.scale.y = header.scale.y; - h.scale.z = header.scale.z; - h.offset.x = header.offset.x; - h.offset.y = header.offset.y; - h.offset.z = header.offset.z; + h.scale_.x = header.scale.x; + h.scale_.y = header.scale.y; + h.scale_.z = header.scale.z; + h.offset_.x = header.offset.x; + h.offset_.y = header.offset.y; + h.offset_.z = header.offset.z; h.max.x = header.maxx; h.min.x = header.minx; h.max.y = header.maxy; h.min.y = header.miny; h.max.z = header.maxz; h.min.z = header.minz; - 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; + h.evlr_offset_ = header.evlr_offset; + h.evlr_count_ = header.evlr_count; + h.point_count_ = 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)); + std::begin(h.points_by_return)); return h; } -lazperf::header14 LasHeader::ToLazPerf() const +lazperf::header14 LasHeader::ToLazPerf(uint32_t point_offset, uint64_t point_count, uint64_t evlr_offset, + uint32_t evlr_count, bool wkt_flag, bool eb_flag, bool extended_stats_flag) const { lazperf::header14 h; h.file_source_id = file_source_id; h.global_encoding = global_encoding; + h.global_encoding |= (1 << 4); // Set the WKT bit. std::strncpy(h.guid, guid_.c_str(), 16); - h.version.major = version_major; - h.version.minor = version_minor; + h.version.major = version_major_; + h.version.minor = version_minor_; std::strncpy(h.system_identifier, system_identifier_.c_str(), 32); std::strncpy(h.generating_software, generating_software_.c_str(), 32); h.creation.day = creation_day; h.creation.year = creation_year; - h.header_size = header_size; + h.header_size = header_size_; h.point_offset = point_offset; - h.vlr_count = vlr_count; - h.point_format_id = point_format_id; - h.point_record_length = point_record_length; - h.point_count = point_count; - std::copy(std::begin(points_by_return), std::end(points_by_return), std::begin(h.points_by_return)); + h.vlr_count = 3; // copc_info + copc_extent + laz; + // If WKT is not empty, count an extra VLR + if (wkt_flag) + h.vlr_count++; + // If there are Extra Bytes, count an extra VLR + if (eb_flag) + h.vlr_count++; + // If there are Extended Stats, count an extra VLR + if (extended_stats_flag) + h.vlr_count++; + h.point_format_id = point_format_id_; + h.point_format_id |= (1 << 7); // Do the lazperf trick + h.point_record_length = point_record_length_; + // Set the legacy point count as per LAS specs + if (point_count > (std::numeric_limits::max)()) + h.point_count = 0; + else + h.point_count = (uint32_t)point_count; + std::fill(h.points_by_return, h.points_by_return + 5, 0); // Fill with zeros - h.offset.x = offset.x; - h.offset.y = offset.y; - h.offset.z = offset.z; + h.offset.x = offset_.x; + h.offset.y = offset_.y; + h.offset.z = offset_.z; - h.scale.x = scale.x; - h.scale.y = scale.y; - h.scale.z = scale.z; + h.scale.x = scale_.x; + h.scale.y = scale_.y; + h.scale.z = scale_.z; h.maxx = max.x; h.minx = min.x; @@ -86,15 +109,41 @@ lazperf::header14 LasHeader::ToLazPerf() const h.maxz = max.z; h.minz = min.z; - h.wave_offset = wave_offset; h.evlr_offset = evlr_offset; h.evlr_count = evlr_count; - h.point_count_14 = point_count_14; + h.point_count_14 = point_count; - std::copy(std::begin(points_by_return_14), std::end(points_by_return_14), std::begin(h.points_by_return_14)); + std::copy(std::begin(points_by_return), std::end(points_by_return), std::begin(h.points_by_return_14)); return h; } -Box LasHeader::GetBounds() const { return Box(min, max); } +std::string LasHeader::ToString() const +{ + std::stringstream ss; + ss << "LasHeader:" << std::endl; + ss << "\tFile Source ID: " << file_source_id << std::endl; + ss << "\tGlobal Encoding ID: " << global_encoding << std::endl; + ss << "\tGUID: " << GUID() << std::endl; + ss << "\tVersion: " << static_cast(version_major_) << "." << static_cast(version_minor_) << std::endl; + ss << "\tSystem Identifier: " << SystemIdentifier() << std::endl; + ss << "\tGenerating Software: " << GeneratingSoftware() << std::endl; + ss << "\tCreation (Day/Year): (" << creation_day << "/" << creation_year << ")" << std::endl; + ss << "\tHeader Size: " << header_size_ << std::endl; + ss << "\tPoint Offset: " << point_offset_ << std::endl; + ss << "\tVLR Count: " << vlr_count_ << std::endl; + ss << "\tPoint Format ID: " << static_cast(point_format_id_) << std::endl; + ss << "\tPoint Record Length: " << point_record_length_ << std::endl; + ss << "\tPoint Count: " << point_count_ << std::endl; + ss << "\tScale: " << scale_.ToString() << std::endl; + ss << "\tOffset: " << offset_.ToString() << std::endl; + ss << "\tMax: " << max.ToString() << std::endl; + ss << "\tMin: " << min.ToString() << std::endl; + ss << "\tEVLR Offset: " << evlr_offset_ << std::endl; + ss << "\tEVLR count: " << evlr_count_ << std::endl; + ss << "\tPoints By Return:" << std::endl; + for (int i = 0; i < points_by_return.size(); i++) + ss << "\t\t[" << i << "]: " << points_by_return[i] << std::endl; + return ss.str(); +} } // namespace copc::las diff --git a/cpp/src/las/point.cpp b/cpp/src/las/point.cpp index 0dc22d31..86236fd3 100644 --- a/cpp/src/las/point.cpp +++ b/cpp/src/las/point.cpp @@ -2,54 +2,38 @@ namespace copc::las { -Point::Point(const int8_t &point_format_id, const Vector3 &scale, const Vector3 &offset, - const uint16_t &num_extra_bytes) +Point::Point(const int8_t &point_format_id, const Vector3 &scale, const Vector3 &offset, const uint16_t &eb_byte_size) : scale_(scale), offset_(offset), point_format_id_(point_format_id) { - if (point_format_id > 10) - throw std::runtime_error("Point format must be 0-10"); + if (point_format_id < 6 || point_format_id > 8) + throw std::runtime_error("Point: Point format must be 6-8"); - if (point_format_id > 5) - extended_point_type_ = true; + point_record_length_ = PointBaseByteSize(point_format_id) + eb_byte_size; - point_record_length_ = PointBaseByteSize(point_format_id) + num_extra_bytes; + has_rgb_ = FormatHasRgb(point_format_id); + has_nir_ = FormatHasNir(point_format_id); - has_gps_time_ = FormatHasGPSTime(point_format_id); - has_rgb_ = FormatHasRGB(point_format_id); - has_nir_ = FormatHasNIR(point_format_id); - - extra_bytes_.resize(num_extra_bytes, 0); + extra_bytes_.resize(eb_byte_size, 0); } Point::Point(const LasHeader &header) - : Point(header.point_format_id, header.scale, header.offset, header.NumExtraBytes()){}; + : Point(header.PointFormatId(), header.Scale(), header.Offset(), header.EbByteSize()){}; -Point::Point(const Point &other) : Point(other.point_format_id_, other.scale_, other.offset_, other.NumExtraBytes()) +Point::Point(const Point &other) : Point(other.point_format_id_, other.Scale(), other.Offset(), other.EbByteSize()) { x_ = other.x_; y_ = other.y_; z_ = other.z_; intensity_ = other.intensity_; - if (other.extended_point_type_) - { - extended_returns_ = other.extended_returns_; - extended_flags_ = other.extended_flags_; - extended_classification_ = other.extended_classification_; - extended_scan_angle_ = other.extended_scan_angle_; - } - else - { - returns_flags_eof_ = other.returns_flags_eof_; - classification_ = other.classification_; - scan_angle_rank_ = other.scan_angle_rank_; - } + returns_ = other.returns_; + flags_ = other.flags_; + classification_ = other.classification_; + scan_angle_ = other.scan_angle_; + user_data_ = other.user_data_; point_source_id_ = other.point_source_id_; - if (other.has_gps_time_) - { - gps_time_ = other.gps_time_; - } + gps_time_ = other.gps_time_; if (other.has_rgb_) { rgb_[0] = other.Red(); @@ -64,7 +48,7 @@ Point::Point(const Point &other) : Point(other.point_format_id_, other.scale_, o extra_bytes_ = other.extra_bytes_; } -uint16_t Point::NumExtraBytes() const { return point_record_length_ - PointBaseByteSize(point_format_id_); } +uint16_t Point::EbByteSize() const { return point_record_length_ - PointBaseByteSize(point_format_id_); } bool Point::operator==(const Point &other) const { @@ -73,25 +57,21 @@ bool Point::operator==(const Point &other) const if (x_ != other.UnscaledX() || y_ != other.UnscaledY() || z_ != other.UnscaledZ() || intensity_ != other.Intensity()) return false; - if (ReturnNumber() != other.ReturnNumber() || NumberOfReturns() != other.NumberOfReturns()) + if (returns_ != other.ReturnsBitField()) return false; - if (ScanDirectionFlag() != other.ScanDirectionFlag() || EdgeOfFlightLineFlag() != other.EdgeOfFlightLineFlag()) + if (flags_ != other.FlagsBitField()) return false; - if (Classification() != other.Classification()) + if (classification_ != other.Classification()) return false; - if (Synthetic() != other.Synthetic() || KeyPoint() != other.KeyPoint() || Withheld() != other.Withheld()) + if (scan_angle_ != other.ScanAngle() || user_data_ != other.UserData() || point_source_id_ != other.PointSourceId()) return false; - if (ScanAngle() != other.ScanAngle() || user_data_ != other.UserData() || point_source_id_ != other.PointSourceID()) + if (extra_bytes_ != other.ExtraBytes()) return false; - if (ExtraBytes() != other.ExtraBytes()) - return false; - if (extended_point_type_ && (Overlap() != other.Overlap() || ScannerChannel() != other.ScannerChannel())) - return false; - if (has_gps_time_ && (gps_time_ != other.GPSTime())) + if (gps_time_ != other.GPSTime()) return false; if (has_rgb_ && (rgb_[0] != other.Red() || rgb_[1] != other.Green() || rgb_[2] != other.Blue())) return false; - if (has_nir_ && (nir_ != other.NIR())) + if (has_nir_ && (nir_ != other.Nir())) return false; return true; } @@ -99,35 +79,21 @@ bool Point::operator==(const Point &other) const bool Point::Within(const Box &box) const { return box.Contains(Vector3(X(), Y(), Z())); } std::shared_ptr Point::Unpack(std::istream &in_stream, const int8_t &point_format_id, const Vector3 &scale, - const Vector3 &offset, const uint16_t &num_extra_bytes) + const Vector3 &offset, const uint16_t &eb_byte_size) { - std::shared_ptr p = std::make_shared(point_format_id, scale, offset, num_extra_bytes); + std::shared_ptr p = std::make_shared(point_format_id, scale, offset, eb_byte_size); p->x_ = unpack(in_stream); p->y_ = unpack(in_stream); p->z_ = unpack(in_stream); p->intensity_ = unpack(in_stream); - if (p->extended_point_type_) - { - p->extended_returns_ = unpack(in_stream); - p->extended_flags_ = unpack(in_stream); - p->extended_classification_ = unpack(in_stream); - p->user_data_ = unpack(in_stream); - p->extended_scan_angle_ = unpack(in_stream); - } - else - { - p->returns_flags_eof_ = unpack(in_stream); - p->classification_ = unpack(in_stream); - p->scan_angle_rank_ = unpack(in_stream); - p->user_data_ = unpack(in_stream); - } + p->returns_ = unpack(in_stream); + p->flags_ = unpack(in_stream); + p->classification_ = unpack(in_stream); + p->user_data_ = unpack(in_stream); + p->scan_angle_ = unpack(in_stream); p->point_source_id_ = unpack(in_stream); - - if (p->has_gps_time_) - { - p->gps_time_ = unpack(in_stream); - } + p->gps_time_ = unpack(in_stream); if (p->has_rgb_) { p->rgb_[0] = unpack(in_stream); @@ -139,7 +105,7 @@ std::shared_ptr Point::Unpack(std::istream &in_stream, const int8_t &poin p->nir_ = unpack(in_stream); } - for (uint32_t i = 0; i < num_extra_bytes; i++) + for (uint32_t i = 0; i < eb_byte_size; i++) { p->extra_bytes_[i] = unpack(in_stream); } @@ -153,25 +119,14 @@ void Point::Pack(std::ostream &out_stream) const pack(y_, out_stream); pack(z_, out_stream); pack(intensity_, out_stream); - if (extended_point_type_) - { - pack(extended_returns_, out_stream); - pack(extended_flags_, out_stream); - pack(extended_classification_, out_stream); - pack(user_data_, out_stream); - pack(extended_scan_angle_, out_stream); - } - else - { - pack(returns_flags_eof_, out_stream); - pack(classification_, out_stream); - pack(scan_angle_rank_, out_stream); - pack(user_data_, out_stream); - } + pack(returns_, out_stream); + pack(flags_, out_stream); + pack(classification_, out_stream); + pack(user_data_, out_stream); + pack(scan_angle_, out_stream); pack(point_source_id_, out_stream); - if (has_gps_time_) - pack(gps_time_, out_stream); + pack(gps_time_, out_stream); if (has_rgb_) { pack(rgb_[0], out_stream); @@ -187,39 +142,11 @@ void Point::Pack(std::ostream &out_stream) const void Point::ToPointFormat(const int8_t &point_format_id) { - - if (extended_point_type_ && point_format_id < 6) - { - // Convert points values from 1.4 to 1.0 - // Returns and flags - returns_flags_eof_ = (EdgeOfFlightLineFlag() << 7) | (ScanDirectionFlag() << 6) | - (std::min(int(NumberOfReturns()), 7) << 3) | std::min(int(ReturnNumber()), 7); - // Classification - classification_ = - (Withheld() << 7) | (KeyPoint() << 6) | (Synthetic() << 5) | std::min(int(Classification()), 31); - - // Scan angle - scan_angle_rank_ = static_cast(std::max(-90, std::min(90, int(float(ExtendedScanAngle()) * 0.006f)))); - } - else if (!extended_point_type_ && point_format_id > 5) - { - // Convert point values from 1.0 to 1.4 - // Returns - extended_returns_ = (NumberOfReturns() << 4) | ReturnNumber(); - // Flags - extended_flags_ = (EdgeOfFlightLineFlag() << 7) | (ScanDirectionFlag() << 6) | (Withheld() << 2) | - (KeyPoint() << 1) | Synthetic(); - // Classification - extended_classification_ = Classification(); - // Scan angle - extended_scan_angle_ = static_cast(float(ScanAngleRank()) / 0.006f); - } - + if (point_format_id < 6 || point_format_id > 8) + throw std::runtime_error("Point format must be 6-8."); // Update flags - extended_point_type_ = point_format_id > 5; - has_gps_time_ = FormatHasGPSTime(point_format_id); - has_rgb_ = FormatHasRGB(point_format_id); - has_nir_ = FormatHasNIR(point_format_id); + has_rgb_ = FormatHasRgb(point_format_id); + has_nir_ = FormatHasNir(point_format_id); point_record_length_ = PointBaseByteSize(point_format_id) + extra_bytes_.size(); point_format_id_ = point_format_id; } @@ -228,41 +155,20 @@ std::string Point::ToString() const { std::stringstream ss; ss << "Point: " << std::endl; - if (extended_point_type_) - { - ss << "\tPoint14: " << std::endl; - ss << "\t\tX: " << x_ << ", Y: " << y_ << ", Z: " << z_ << std::endl; - ss << "\t\tIntensity: " << intensity_ << std::endl; - ss << "\t\tReturn Number: " << static_cast(ReturnNumber()) - << ", Number of Returns: " << static_cast(NumberOfReturns()) << std::endl; - ss << "\t\tClassification Flags: Synthetic: " << Synthetic() << ", Key Point: " << KeyPoint() - << ", Withheld: " << Withheld() << ", Overlap: " << Overlap() << std::endl; - ss << "\t\tScannerChannel: " << static_cast(ScannerChannel()) << std::endl; - ss << "\t\tScan Direction: " << ScanDirectionFlag() << std::endl; - ss << "\t\tEdge of Flight Line: " << EdgeOfFlightLineFlag() << std::endl; - ss << "\t\tClasification: " << static_cast(Classification()) << std::endl; - ss << "\t\tUser Data: " << static_cast(user_data_) << std::endl; - ss << "\t\tScan Angle: " << extended_scan_angle_ << std::endl; - ss << "\t\tPoint Source ID: " << point_source_id_ << std::endl; - } - else - { - ss << "\tPoint10: " << std::endl; - ss << "\t\tX: " << x_ << ", Y: " << y_ << ", Z: " << z_ << std::endl; - ss << "\t\tIntensity: " << intensity_ << std::endl; - ss << "\t\tReturn Number: " << static_cast(ReturnNumber()) - << ", Number of Returns: " << static_cast(NumberOfReturns()) << std::endl; - ss << "\t\tScan Direction: " << ScanDirectionFlag() << std::endl; - ss << "\t\tEdge of Flight Line: " << EdgeOfFlightLineFlag() << std::endl; - ss << "\t\tClasification: " << static_cast(Classification()) << std::endl; - ss << "\t\tClassification Flags: Synthetic: " << Synthetic() << ", Key Point: " << KeyPoint() - << ", Withheld: " << Withheld() << std::endl; - ss << "\t\tScan Angle Rank: " << static_cast(scan_angle_rank_) << std::endl; - ss << "\t\tUser Data: " << static_cast(user_data_) << std::endl; - ss << "\t\tPoint Source ID: " << point_source_id_ << std::endl; - } - if (has_gps_time_) - ss << "\tGPS Time: " << gps_time_ << std::endl; + ss << "\tX: " << x_ << ", Y: " << y_ << ", Z: " << z_ << std::endl; + ss << "\tIntensity: " << intensity_ << std::endl; + ss << "\tReturn Number: " << static_cast(ReturnNumber()) + << ", Number of Returns: " << static_cast(NumberOfReturns()) << std::endl; + ss << "\tClassification Flags: Synthetic: " << Synthetic() << ", Key Point: " << KeyPoint() + << ", Withheld: " << Withheld() << ", Overlap: " << Overlap() << std::endl; + ss << "\tScannerChannel: " << static_cast(ScannerChannel()) << std::endl; + ss << "\tScan Direction: " << ScanDirectionFlag() << std::endl; + ss << "\tEdge of Flight Line: " << EdgeOfFlightLineFlag() << std::endl; + ss << "\tClasification: " << static_cast(Classification()) << std::endl; + ss << "\tUser Data: " << static_cast(user_data_) << std::endl; + ss << "\tScan Angle: " << scan_angle_ << std::endl; + ss << "\tPoint Source ID: " << point_source_id_ << std::endl; + ss << "\tGPS Time: " << gps_time_ << std::endl; if (has_rgb_) ss << "\tRGB: [ " << rgb_[0] << ", " << rgb_[1] << ", " << rgb_[2] << " ]" << std::endl; if (has_nir_) diff --git a/cpp/src/las/points.cpp b/cpp/src/las/points.cpp index 6a0892d0..c4c903c1 100644 --- a/cpp/src/las/points.cpp +++ b/cpp/src/las/points.cpp @@ -7,18 +7,17 @@ namespace copc::las { -Points::Points(const int8_t &point_format_id, const Vector3 &scale, const Vector3 &offset, - const uint16_t &num_extra_bytes) +Points::Points(const int8_t &point_format_id, const Vector3 &scale, const Vector3 &offset, const uint16_t &eb_byte_size) : point_format_id_(point_format_id), scale_(scale), offset_(offset) { if (point_format_id < 0 || point_format_id > 10) throw std::runtime_error("Point format must be 0-10."); - point_record_length_ = ComputePointBytes(point_format_id, num_extra_bytes); + point_record_length_ = PointByteSize(point_format_id, eb_byte_size); } Points::Points(const LasHeader &header) - : Points(header.point_format_id, header.scale, header.offset, header.NumExtraBytes()){}; + : Points(header.PointFormatId(), header.Scale(), header.Offset(), header.EbByteSize()){}; Points::Points(const std::vector> &points) { @@ -26,7 +25,7 @@ Points::Points(const std::vector> &points) throw std::runtime_error("Can't add empty vector of points to Points!"); point_record_length_ = points[0]->PointRecordLength(); - point_format_id_ = points[0]->PointFormatID(); + point_format_id_ = points[0]->PointFormatId(); scale_ = points[0]->Scale(); offset_ = points[0]->Offset(); @@ -35,16 +34,18 @@ Points::Points(const std::vector> &points) void Points::ToPointFormat(const int8_t &point_format_id) { - if (point_format_id < 0 || point_format_id > 10) - throw std::runtime_error("Point format must be 0-10."); + if (point_format_id < 6 || point_format_id > 8) + throw std::runtime_error("Point format must be 6-8."); for (auto &point : points_) point->ToPointFormat(point_format_id); + unsigned int eb_byte_size = point_record_length_ - PointBaseByteSize(point_format_id_); point_format_id_ = point_format_id; + point_record_length_ = PointByteSize(point_format_id, eb_byte_size); } void Points::AddPoint(const std::shared_ptr &point) { - if (point->PointFormatID() == point_format_id_ && point->PointRecordLength() == point_record_length_) + if (point->PointFormatId() == point_format_id_ && point->PointRecordLength() == point_record_length_) points_.push_back(point); else throw std::runtime_error("New point must be of same format and byte_size."); @@ -52,7 +53,7 @@ void Points::AddPoint(const std::shared_ptr &point) void Points::AddPoints(Points points) { - if (points.PointFormatID() != point_format_id_ || points.PointRecordLength() != point_record_length_) + if (points.PointFormatId() != point_format_id_ || points.PointRecordLength() != point_record_length_) throw std::runtime_error("New points must be of same format and byte_size."); auto point_vec = points.Get(); @@ -63,7 +64,7 @@ void Points::AddPoints(std::vector> points) { for (const auto &point : points) { - if (point->PointFormatID() != point_format_id_ || point->PointRecordLength() != point_record_length_) + if (point->PointFormatId() != point_format_id_ || point->PointRecordLength() != point_record_length_) throw std::runtime_error("New points must be of same format and byte_size."); } @@ -72,13 +73,13 @@ void Points::AddPoints(std::vector> points) Points Points::Unpack(const std::vector &point_data, const LasHeader &header) { - return Unpack(point_data, header.point_format_id, header.NumExtraBytes(), header.scale, header.offset); + return Unpack(point_data, header.PointFormatId(), header.EbByteSize(), header.Scale(), header.Offset()); } -Points Points::Unpack(const std::vector &point_data, const int8_t &point_format_id, - const uint16_t &num_extra_bytes, const Vector3 &scale, const Vector3 &offset) +Points Points::Unpack(const std::vector &point_data, const int8_t &point_format_id, const uint16_t &eb_byte_size, + const Vector3 &scale, const Vector3 &offset) { - auto point_record_length = ComputePointBytes(point_format_id, num_extra_bytes); + auto point_record_length = PointByteSize(point_format_id, eb_byte_size); if (point_data.size() % point_record_length != 0) throw std::runtime_error("Invalid input point array!"); @@ -88,13 +89,13 @@ Points Points::Unpack(const std::vector &point_data, const int8_t &point_f auto ss = std::istringstream(std::string(point_data.begin(), point_data.end())); // Go through each Point to unpack the data from the stream - Points points(point_format_id, scale, offset, num_extra_bytes); + Points points(point_format_id, scale, offset, eb_byte_size); points.Reserve(point_count); // Unpack points for (int i = 0; i < point_count; i++) { - points.AddPoint(las::Point::Unpack(ss, point_format_id, scale, offset, num_extra_bytes)); + points.AddPoint(las::Point::Unpack(ss, point_format_id, scale, offset, eb_byte_size)); } return points; @@ -118,7 +119,7 @@ std::string Points::ToString() const { std::stringstream ss; ss << "# of points: " << Size() << ", Point Format: " << static_cast(point_format_id_) - << ", # Extra Bytes: " << NumExtraBytes() << ", Point Record Length: " << point_record_length_; + << ", # Extra Bytes: " << EbByteSize() << ", Point Record Length: " << point_record_length_; return ss.str(); } diff --git a/cpp/src/las/utils.cpp b/cpp/src/las/utils.cpp index edc0e29a..5e59d0aa 100644 --- a/cpp/src/las/utils.cpp +++ b/cpp/src/las/utils.cpp @@ -7,122 +7,68 @@ uint8_t PointBaseByteSize(const int8_t &point_format_id) { switch (point_format_id) { - case 0: - return 20; - case 1: - return 28; - case 2: - return 26; - case 3: - return 34; - case 4: - // return 28; - case 5: - // return 34; - throw std::runtime_error("Point formats with Wave Packets not yet supported"); case 6: return 30; case 7: return 36; case 8: return 38; - case 9: - // return 30; - case 10: - // return 38; - throw std::runtime_error("Point formats with Wave Packets not yet supported"); default: - throw std::runtime_error("Point format must be 0-10"); + throw std::runtime_error("PointBaseByteSize: Point format must be 6-8"); } } -bool FormatHasGPSTime(const uint8_t &point_format_id) +uint8_t PointBaseNumberDimensions(const int8_t &point_format_id) { switch (point_format_id) { - case 0: - return false; - case 1: - return true; - case 2: - return false; - case 3: - return true; - case 4: - case 5: - throw std::runtime_error("Point formats with Wave Packets not yet supported"); case 6: + return 14; case 7: + return 17; case 8: - return true; - case 9: - case 10: - throw std::runtime_error("Point formats with Wave Packets not yet supported"); - + return 18; default: - throw std::runtime_error("Point format must be 0-10"); + throw std::runtime_error("PointBaseNumberDimensions: Point format must be 6-8"); } } -bool FormatHasRGB(const uint8_t &point_format_id) +bool FormatHasRgb(const uint8_t &point_format_id) { switch (point_format_id) { - case 0: - case 1: - return false; - case 2: - case 3: - return true; - case 4: - case 5: - throw std::runtime_error("Point formats with Wave Packets not yet supported"); case 6: return false; case 7: case 8: return true; - case 9: - case 10: - throw std::runtime_error("Point formats with Wave Packets not yet supported"); default: - throw std::runtime_error("Point format must be 0-10"); + throw std::runtime_error("FormatHasRgb: Point format must be 6-8"); } } -bool FormatHasNIR(const uint8_t &point_format_id) +bool FormatHasNir(const uint8_t &point_format_id) { switch (point_format_id) { - case 0: - case 1: - case 2: - case 3: - return false; - case 4: - case 5: - throw std::runtime_error("Point formats with Wave Packets not yet supported"); case 6: case 7: return false; case 8: return true; - case 9: - case 10: - throw std::runtime_error("Point formats with Wave Packets not yet supported"); default: - throw std::runtime_error("Point format must be 0-10"); + throw std::runtime_error("FormatHasNir: Point format must be 6-8"); } } -uint16_t ComputeNumExtraBytes(const int8_t &point_format_id, const uint32_t &point_record_length) +uint16_t EbByteSize(const int8_t &point_format_id, const uint32_t &point_record_length) { return point_record_length - PointBaseByteSize(point_format_id); } -uint16_t ComputePointBytes(const int8_t &point_format_id, const uint16_t &num_extra_bytes) +uint16_t PointByteSize(const int8_t &point_format_id, const uint16_t &eb_byte_size) { - return PointBaseByteSize(point_format_id) + num_extra_bytes; + return PointBaseByteSize(point_format_id) + eb_byte_size; } } // namespace copc::las diff --git a/cpp/src/las/vlr.cpp b/cpp/src/las/vlr.cpp new file mode 100644 index 00000000..c5bc0331 --- /dev/null +++ b/cpp/src/las/vlr.cpp @@ -0,0 +1,58 @@ +#include "copc-lib/las/vlr.hpp" + +namespace copc::las +{ + +uint8_t EXTRA_BYTE_DATA_TYPE[31]{0, 1, 1, 2, 2, 4, 4, 8, 8, 4, 8, 2, 2, 4, 4, 8, + 8, 16, 16, 8, 16, 3, 3, 6, 6, 12, 12, 24, 24, 12, 24}; + +int NumBytesFromExtraBytes(const std::vector &items) +{ + int out = 0; + for (const auto &item : items) + { + if (item.data_type == 0) + out += item.options; + else + out += EXTRA_BYTE_DATA_TYPE[item.data_type]; + } + return out; +} + +VlrHeader::VlrHeader(const lazperf::vlr_header &vlr_header) : evlr_flag(false) +{ + reserved = vlr_header.reserved; + user_id = vlr_header.user_id; + record_id = vlr_header.record_id; + data_length = vlr_header.data_length; + description = vlr_header.description; +} + +VlrHeader::VlrHeader(const VlrHeader &vlr_header) : evlr_header(vlr_header) { evlr_flag = vlr_header.evlr_flag; } + +lazperf::vlr_header VlrHeader::ToLazperfVlrHeader() const +{ + lazperf::vlr_header header; + + header.reserved = reserved; + header.user_id = user_id; + header.record_id = record_id; + header.data_length = data_length; + header.description = description; + + return header; +} +lazperf::evlr_header VlrHeader::ToLazperfEvlrHeader() const +{ + lazperf::evlr_header header; + + header.reserved = reserved; + header.user_id = user_id; + header.record_id = record_id; + header.data_length = data_length; + header.description = description; + + return header; +} + +} // namespace copc::las diff --git a/example/example-reader.cpp b/example/example-reader.cpp index fb5ea82a..f8507dcf 100644 --- a/example/example-reader.cpp +++ b/example/example-reader.cpp @@ -9,22 +9,30 @@ void ReaderExample() // Create a reader object FileReader reader("autzen-classified.copc.laz"); - // We can get the CopcData struct - auto copc_vlr = reader.GetCopcHeader(); - cout << "CopcData: " << endl; - cout << "\tSpan: " << copc_vlr.span << endl - << "\tRoot Offset: " << copc_vlr.root_hier_offset << endl - << "\tRoot Size: " << copc_vlr.root_hier_size << endl; - // Get the Las Header - auto las_header = reader.GetLasHeader(); + auto las_header = reader.CopcConfig().LasHeader(); cout << endl << "Las Header:" << endl; - cout << "\tPoint Format: " << (int)las_header.point_format_id << endl - << "\tPoint Count: " << (int)las_header.point_count << endl; + cout << "\tPoint Format: " << (int)las_header.PointFormatId() << endl + << "\tPoint Count: " << (int)las_header.PointCount() << endl; + + // Get the CopcInfo struct + auto copc_info = reader.CopcConfig().CopcInfo(); + cout << "Copc Info: " << endl; + cout << "\tSpacing: " << copc_info.spacing << endl + << "\tRoot Offset: " << copc_info.root_hier_offset << endl + << "\tRoot Size: " << copc_info.root_hier_size << endl; + + // Get the CopcInfo struct + auto copc_extents = reader.CopcConfig().CopcExtents(); + cout << "Copc Extents (Min/Max): " << endl; + cout << "\tIntensity : (" << copc_extents.Intensity()->minimum << "/" << copc_extents.Intensity()->maximum << ")" + << endl + << "\tClassification : (" << copc_extents.Classification()->minimum << "/" + << copc_extents.Classification()->maximum << ")" << endl + << "\tGpsTime : (" << copc_extents.GpsTime()->minimum << "/" << copc_extents.GpsTime()->maximum << ")" << endl; // Get the WKT string - auto wkt = reader.GetWkt(); - cout << endl << "WKT: " << endl << wkt << endl; + cout << "WKT: " << reader.CopcConfig().Wkt() << endl; cout << endl; @@ -67,7 +75,7 @@ void ReaderExample() copc::laz::Decompressor::DecompressBytes(compressed_data, las_header, num_points_to_decompress); cout << endl - << "Successfully decompressed " << uncompressed_data.size() / las_header.point_record_length << " points!" + << "Successfully decompressed " << uncompressed_data.size() / las_header.PointRecordLength() << "points!" << endl; } } diff --git a/example/example-reader.py b/example/example-reader.py index 659968c5..506a8657 100644 --- a/example/example-reader.py +++ b/example/example-reader.py @@ -5,24 +5,41 @@ def reader_example(): # Create a reader object reader = copc.FileReader("autzen-classified.copc.laz") - # We can get the CopcData struct - copc_vlr = reader.GetCopcHeader() - print("CopcData: ") - print("\tSpan: %d" % copc_vlr.span) - print("\tRoot Offset: %d" % copc_vlr.root_hier_offset) - print("\tRoot Size: %d" % copc_vlr.root_hier_size) - # Get the Las Header - las_header = reader.GetLasHeader() + las_header = reader.copc_config.las_header + cfg = reader.copc_config print() print("Las Header:") print("\tPoint Format: %d" % las_header.point_format_id) print("\tPoint Count: %d" % las_header.point_count) + # Get the Copc Info + copc_info = reader.copc_config.copc_info + print("Copc Info: ") + print("\tSpacing: %d" % copc_info.spacing) + print("\tRoot Offset: %d" % copc_info.root_hier_offset) + print("\tRoot Size: %d" % copc_info.root_hier_size) + + # Get the Copc Extents + copc_extents = reader.copc_config.copc_extents + print("Copc Extents (Min/Max): ") + print( + "\tIntensity: (%f,%f)" + % (copc_extents.intensity.minimum, copc_extents.intensity.maximum) + ) + print( + "\tClassification: (%f,%f)" + % (copc_extents.classification.minimum, copc_extents.classification.maximum) + ) + print( + "\tUser Data: (%f,%f)" + % (copc_extents.user_data.minimum, copc_extents.user_data.maximum) + ) + # Get the WKT string - print("WKT: %s" % reader.GetWkt()) + print("WKT: %s" % reader.copc_config.wkt) - load_key = copc.VoxelKey(4, 11, 9, 0) + load_key = (4, 11, 9, 0) # FindNode will automatically load the minimum pages needed # to find the key you request @@ -43,7 +60,7 @@ def reader_example(): # We can also get the raw compressed data if we want to decompress it ourselves: - loadKey = copc.VoxelKey(4, 11, 9, 0) + loadKey = (4, 11, 9, 0) node = reader.FindNode(loadKey) if not node.IsValid(): diff --git a/example/example-writer.cpp b/example/example-writer.cpp index a7ee6703..52677a8f 100644 --- a/example/example-writer.cpp +++ b/example/example-writer.cpp @@ -19,20 +19,20 @@ void TrimFileExample(bool compressor_example_flag) { // We'll get our point data from this file FileReader reader("autzen-classified.copc.laz"); - auto old_header = reader.GetLasHeader(); + auto old_header = reader.CopcConfig().LasHeader(); { - // Copy the header to the new file - Writer::LasConfig cfg(old_header, reader.GetExtraByteVlr()); + // Copy the config to the new file + auto cfg = reader.CopcConfig(); - // Now, we can create our actual writer, with an optional `span` and `wkt`: - FileWriter writer("autzen-trimmed.copc.laz", cfg, reader.GetCopcHeader().span, reader.GetWkt()); + // Now, we can create our actual writer, with an optional `spacing` and `wkt`: + FileWriter writer("autzen-trimmed.copc.laz", cfg); // The root page is automatically created and added for us Page root_page = writer.GetRootPage(); - // GetAllChildren will load the entire hierarchy under a given key - for (const auto &node : reader.GetAllChildren(root_page.key)) + // GetAllNodes will load the entire hierarchy under a given key + for (const auto &node : reader.GetAllChildrenOfPage(root_page.key)) { // In this example, we'll only save up to depth level 3. if (node.key.d > 3) @@ -51,7 +51,7 @@ void TrimFileExample(bool compressor_example_flag) std::vector uncompressed_points = reader.GetPointData(node); std::vector compressed_points = - laz::Compressor::CompressBytes(uncompressed_points, writer.GetLasHeader()); + laz::Compressor::CompressBytes(uncompressed_points, *writer.CopcConfig()->LasHeader()); writer.AddNodeCompressed(root_page, node.key, compressed_points, node.point_count); } } @@ -64,7 +64,7 @@ void TrimFileExample(bool compressor_example_flag) FileReader new_reader("autzen-trimmed.copc.laz"); // Let's go through each node we've written and make sure it matches the original - for (const auto &node : new_reader.GetAllChildren()) + for (const auto &node : new_reader.GetAllNodes()) { assert(new_reader.GetPointDataCompressed(node) == reader.GetPointDataCompressed(node.key)); @@ -72,7 +72,7 @@ void TrimFileExample(bool compressor_example_flag) // and decompress it later using the Decompressor class if (compressor_example_flag) { - las::LasHeader header = new_reader.GetLasHeader(); + las::LasHeader header = new_reader.CopcConfig().LasHeader(); std::vector compressed_points = reader.GetPointDataCompressed(node.key); std::vector uncompressed_points = laz::Decompressor::DecompressBytes(compressed_points, header, node.point_count); @@ -85,7 +85,7 @@ void BoundsTrimFileExample() { // We'll get our point data from this file FileReader reader("autzen-classified.copc.laz"); - auto old_header = reader.GetLasHeader(); + auto old_header = reader.CopcConfig().LasHeader(); // Take horizontal 2D box of [400,400] roughly in the middle of the point cloud. @@ -93,16 +93,16 @@ void BoundsTrimFileExample() Box box(middle.x - 200, middle.y - 200, middle.x + 200, middle.y + 200); { - // Copy the header to the new file - Writer::LasConfig cfg(old_header, reader.GetExtraByteVlr()); + // Copy the config to the new file + auto cfg = reader.CopcConfig(); // Now, we can create our actual writer, with an optional `span` and `wkt`: - FileWriter writer("autzen-bounds-trimmed.copc.laz", cfg, reader.GetCopcHeader().span, reader.GetWkt()); + FileWriter writer("autzen-bounds-trimmed.copc.laz", cfg); // The root page is automatically created and added for us Page root_page = writer.GetRootPage(); - for (const auto &node : reader.GetAllChildren()) + for (const auto &node : reader.GetAllNodes()) { if (node.key.Within(old_header, box)) @@ -127,7 +127,7 @@ void BoundsTrimFileExample() FileReader new_reader("autzen-bounds-trimmed.copc.laz"); // Let's go through each point and make sure they fit within the Box - for (const auto &node : new_reader.GetAllChildren()) + for (const auto &node : new_reader.GetAllNodes()) { auto points = new_reader.GetPoints(node); assert(points.Within(box)); @@ -139,23 +139,23 @@ void ResolutionTrimFileExample() { // We'll get our point data from this file FileReader reader("autzen-classified.copc.laz"); - auto old_header = reader.GetLasHeader(); + auto old_header = reader.CopcConfig().LasHeader(); double resolution = 10; auto target_depth = reader.GetDepthAtResolution(resolution); // Check that the resolution of the target depth is equal or smaller to the requested resolution. - assert(VoxelKey::GetResolutionAtDepth(target_depth, old_header, reader.GetCopcHeader()) <= resolution); + assert(VoxelKey::GetResolutionAtDepth(target_depth, old_header, reader.CopcConfig().CopcInfo()) <= resolution); { - // Copy the header to the new file - Writer::LasConfig cfg(old_header, reader.GetExtraByteVlr()); + // Copy the config to the new file + auto cfg = reader.CopcConfig(); // Now, we can create our actual writer, with an optional `span` and `wkt`: - FileWriter writer("autzen-resolution-trimmed.copc.laz", cfg, reader.GetCopcHeader().span, reader.GetWkt()); + FileWriter writer("autzen-resolution-trimmed.copc.laz", cfg); // The root page is automatically created and added for us Page root_page = writer.GetRootPage(); - for (const auto &node : reader.GetAllChildren()) + for (const auto &node : reader.GetAllNodes()) { if (node.key.d <= target_depth) { @@ -170,11 +170,11 @@ void ResolutionTrimFileExample() // Now, let's test our new file FileReader new_reader("autzen-resolution-trimmed.copc.laz"); - auto new_header = new_reader.GetLasHeader(); - auto new_copc_info = new_reader.GetCopcHeader(); + auto new_header = new_reader.CopcConfig().LasHeader(); + auto new_copc_info = new_reader.CopcConfig().CopcInfo(); // Let's go through each node we've written and make sure the resolution is correct - for (const auto &node : new_reader.GetAllChildren()) + for (const auto &node : new_reader.GetAllNodes()) { assert(node.key.d <= target_depth); } @@ -213,7 +213,7 @@ las::Points RandomPoints(const VoxelKey &key, const las::LasHeader &header, int std::uniform_int_distribution<> rand_z(std::ceil(header.ApplyInverseScaleZ(std::max(header.min.z, z_min))), std::floor(header.ApplyInverseScaleZ(std::min(header.max.z, z_min + step)))); - las::Points points(header.point_format_id, header.scale, header.offset); + las::Points points(header.PointFormatId(), header.Scale(), header.Offset()); for (int i = 0; i < number_points; i++) { // Create a point with a given point format @@ -224,7 +224,7 @@ las::Points RandomPoints(const VoxelKey &key, const las::LasHeader &header, int point->UnscaledY(rand_y(gen)); point->UnscaledZ(rand_z(gen)); // For visualization purposes - point->PointSourceID(key.d + key.x + key.y + key.z); + point->PointSourceId(key.d + key.x + key.y + key.z); points.AddPoint(point); } @@ -234,22 +234,35 @@ las::Points RandomPoints(const VoxelKey &key, const las::LasHeader &header, int // In this example, we'll create our own file from scratch void NewFileExample() { - // Create our new file with the specified format, scale, and offset - Writer::LasConfig cfg(8, {0.1, 0.1, 0.1}, {50, 50, 50}); - // As of now, the library will not automatically compute the min/max of added points + // Create our new file with the specified format, scale, offset, wkt, and extended_stats + CopcConfigWriter cfg(8, {0.1, 0.1, 0.1}, {50, 50, 50}, "TEST_WKT", {}, true); + // copc-lib will not automatically compute the min/max of added points // so we will have to calculate it ourselves - cfg.min = MIN_BOUNDS; - cfg.max = MAX_BOUNDS; + cfg.LasHeader()->min = MIN_BOUNDS; + cfg.LasHeader()->max = MAX_BOUNDS; + cfg.CopcInfo()->spacing = 10; // Now, we can create our COPC writer, with an optional `span` and `wkt`: - FileWriter writer("new-copc.copc.laz", cfg, 256, "TEST_WKT"); - auto header = writer.GetLasHeader(); + FileWriter writer("new-copc.copc.laz", cfg); + auto header = writer.CopcConfig()->LasHeader(); + + // Set the COPC Extents + auto extents = writer.CopcConfig()->CopcExtents(); + + extents->Intensity()->minimum = 0; + extents->Intensity()->maximum = 10000; + extents->Intensity()->mean = 50; + extents->Intensity()->var = 5; + + extents->Classification()->minimum = 5; + extents->Classification()->maximum = 201; + // The root page is automatically created Page root_page = writer.GetRootPage(); // First we'll add a root node VoxelKey key(0, 0, 0, 0); - auto points = RandomPoints(key, header, NUM_POINTS); + auto points = RandomPoints(key, *header, NUM_POINTS); // The node will be written to the file when we call AddNode writer.AddNode(root_page, key, points); @@ -260,16 +273,16 @@ void NewFileExample() // Once our page is created, we can add nodes to it like before key = VoxelKey(1, 1, 1, 0); - points = RandomPoints(key, header, NUM_POINTS); + points = RandomPoints(key, *header, NUM_POINTS); writer.AddNode(page, key, points); key = VoxelKey(2, 2, 2, 0); - points = RandomPoints(key, header, NUM_POINTS); + points = RandomPoints(key, *header, NUM_POINTS); writer.AddNode(page, key, points); // We can nest subpages as much as we want, as long as they are children of the parent auto sub_page = writer.AddSubPage(page, VoxelKey(3, 4, 4, 0)); - points = RandomPoints(sub_page.key, header, NUM_POINTS); + points = RandomPoints(sub_page.key, *header, NUM_POINTS); writer.AddNode(page, sub_page.key, points); } diff --git a/example/example-writer.py b/example/example-writer.py index dbe37e8e..4bda0298 100644 --- a/example/example-writer.py +++ b/example/example-writer.py @@ -9,24 +9,18 @@ def TrimFileExample(compressor_example_flag): # We'll get our point data from this file reader = copc.FileReader("autzen-classified.copc.laz") - old_header = reader.GetLasHeader() # Copy the header to the new file - cfg = copc.LasConfig(old_header, reader.GetExtraByteVlr()) - - # Now, we can create our actual writer, with an optional `span` and `wkt`: - writer = copc.FileWriter( - "autzen-trimmed.copc.laz", - cfg, - reader.GetCopcHeader().span, - reader.GetWkt(), - ) + cfg = reader.copc_config + + # Now, we can create our actual writer: + writer = copc.FileWriter("autzen-trimmed.copc.laz", cfg) # The root page is automatically created and added for us root_page = writer.GetRootPage() - # GetAllChildren will load the entire hierarchy under a given key - for node in reader.GetAllChildren(root_page.key): + # GetAllNodes will load the entire hierarchy under a given key + for node in reader.GetAllNodes(): # In this example, we'll only save up to depth level 3. if node.key.d > 3: continue @@ -44,10 +38,9 @@ def TrimFileExample(compressor_example_flag): # (for example, compress multiple nodes in parallel and have one thread writing the data), # we can use the Compressor class: else: - header = writer.GetLasHeader() uncompressed_points = reader.GetPointData(node) compressed_points = copc.CompressBytes( - uncompressed_points, header.point_format_id, cfg.NumExtraBytes() + uncompressed_points, writer.copc_config.las_header ) writer.AddNodeCompressed( root_page, node.key, compressed_points, node.point_count @@ -59,7 +52,7 @@ def TrimFileExample(compressor_example_flag): new_reader = copc.FileReader("autzen-trimmed.copc.laz") # Let's go through each node we've written and make sure it matches the original - for node in new_reader.GetAllChildren(): + for node in new_reader.GetAllNodes(): assert new_reader.GetPointDataCompressed(node) == reader.GetPointDataCompressed( node.key ) @@ -67,10 +60,9 @@ def TrimFileExample(compressor_example_flag): # Similarly, we could retrieve the compressed node data from the file # and decompress it later using the Decompressor class if compressor_example_flag: - header = writer.GetLasHeader() compressed_points = reader.GetPointDataCompressed(node.key) uncompressed_points = copc.DecompressBytes( - compressed_points, header, node.point_count + compressed_points, writer.copc_config.las_header, node.point_count ) reader.Close() new_reader.Close() @@ -80,26 +72,21 @@ def TrimFileExample(compressor_example_flag): def BoundsTrimFileExample(): # We'll get our point data from this file reader = copc.FileReader("autzen-classified.copc.laz") - old_header = reader.GetLasHeader() + old_header = reader.copc_config.las_header middle = (old_header.max + old_header.min) / 2 box = copc.Box(middle.x - 200, middle.y - 200, middle.x + 200, middle.y + 200) # Copy the header to the new file - cfg = copc.LasConfig(old_header, reader.GetExtraByteVlr()) - - # Now, we can create our actual writer, with an optional `span` and `wkt`: - writer = copc.FileWriter( - "autzen-bounds-trimmed.copc.laz", - cfg, - reader.GetCopcHeader().span, - reader.GetWkt(), - ) + cfg = reader.copc_config + + # Now, we can create our actual writer: + writer = copc.FileWriter("autzen-bounds-trimmed.copc.laz", cfg) # The root page is automatically created and added for us root_page = writer.GetRootPage() - for node in reader.GetAllChildren(): + for node in reader.GetAllNodes(): if node.key.Within(old_header, box): # If node is within the box then add all points (without decompressing) writer.AddNodeCompressed( @@ -120,7 +107,7 @@ def BoundsTrimFileExample(): new_reader = copc.FileReader("autzen-bounds-trimmed.copc.laz") # Let's go through each point and make sure they fit in the within the Box - for node in new_reader.GetAllChildren(): + for node in new_reader.GetAllNodes(): points = new_reader.GetPoints(node) assert points.Within(box) @@ -129,33 +116,28 @@ def BoundsTrimFileExample(): def ResolutionTrimFileExample(): # We'll get our point data from this file reader = copc.FileReader("autzen-classified.copc.laz") - old_header = reader.GetLasHeader() + old_header = reader.copc_config.las_header resolution = 10 target_depth = reader.GetDepthAtResolution(resolution) # Check that the resolution of the target depth is equal or smaller to the requested resolution assert ( copc.VoxelKey.GetResolutionAtDepth( - target_depth, old_header, reader.GetCopcHeader() + target_depth, old_header, reader.copc_config.copc_info ) <= resolution ) # Copy the header to the new file - cfg = copc.LasConfig(old_header, reader.GetExtraByteVlr()) - - # Now, we can create our actual writer, with an optional `span` and `wkt`: - writer = copc.FileWriter( - "autzen-resolution-trimmed.copc.laz", - cfg, - reader.GetCopcHeader().span, - reader.GetWkt(), - ) + cfg = reader.copc_config + + # Now, we can create our actual writer: + writer = copc.FileWriter("autzen-resolution-trimmed.copc.laz", cfg) # The root page is automatically created and added for us root_page = writer.GetRootPage() - for node in reader.GetAllChildren(): + for node in reader.GetAllNodes(): if node.key.d <= target_depth: writer.AddNodeCompressed( root_page, @@ -170,11 +152,11 @@ def ResolutionTrimFileExample(): # Now, let's test our new file new_reader = copc.FileReader("autzen-resolution-trimmed.copc.laz") - new_header = new_reader.GetLasHeader() - new_copc_info = new_reader.GetCopcHeader() + new_header = new_reader.copc_config.las_header + new_copc_info = new_reader.copc_config.copc_info # Let's go through each node we've written and make sure the resolution is correct - for node in new_reader.GetAllChildren(): + for node in new_reader.GetAllNodes(): assert node.key.d <= target_depth # Let's make sure the max resolution is at least as much as we requested @@ -193,47 +175,53 @@ def ResolutionTrimFileExample(): # This function will generate `NUM_POINTS` random points within the voxel bounds -def RandomPoints(key, header, number_points): +def RandomPoints(key, las_header, number_points): - # Voxel cube dimensions will be calculated from the maximum span of the file + # Voxel cube dimensions will be calculated from the maximum spacing of the file span = max( { - header.max.x - header.min.x, - header.max.y - header.min.y, - header.max.z - header.min.z, + las_header.max.x - las_header.min.x, + las_header.max.y - las_header.min.y, + las_header.max.z - las_header.min.z, } ) # Step size accounts for depth level step = span / pow(2, key.d) - minx = header.min.x + (step * key.x) - miny = header.min.y + (step * key.y) - minz = header.min.z + (step * key.z) + minx = las_header.min.x + (step * key.x) + miny = las_header.min.y + (step * key.y) + minz = las_header.min.z + (step * key.z) points = copc.Points( - header.point_format_id, - header.scale, - header.offset, + las_header.point_format_id, + las_header.scale, + las_header.offset, ) for i in range(number_points): # Create a point with a given point format point = points.CreatePoint() # point has getters/setters for all attributes - point.UnscaledX = random.randint( - math.ceil(header.ApplyInverseScaleX(max(header.min.x, minx))), - math.floor(header.ApplyInverseScaleX(min(header.max.x, minx + step))), + point.X = random.randint( + math.ceil(las_header.ApplyInverseScaleX(max(las_header.min.x, minx))), + math.floor( + las_header.ApplyInverseScaleX(min(las_header.max.x, minx + step)) + ), ) - point.UnscaledY = random.randint( - math.ceil(header.ApplyInverseScaleY(max(header.min.y, miny))), - math.floor(header.ApplyInverseScaleY(min(header.max.y, miny + step))), + point.Y = random.randint( + math.ceil(las_header.ApplyInverseScaleY(max(las_header.min.y, miny))), + math.floor( + las_header.ApplyInverseScaleY(min(las_header.max.y, miny + step)) + ), ) - point.UnscaledZ = random.randint( - math.ceil(header.ApplyInverseScaleZ(max(header.min.z, minz))), - math.floor(header.ApplyInverseScaleZ(min(header.max.z, minz + step))), + point.Z = random.randint( + math.ceil(las_header.ApplyInverseScaleZ(max(las_header.min.z, minz))), + math.floor( + las_header.ApplyInverseScaleZ(min(las_header.max.z, minz + step)) + ), ) # For visualization purposes - point.PointSourceID = key.d + key.x + key.y + key.z + point.point_source_id = key.d + key.x + key.y + key.z points.AddPoint(point) return points @@ -243,15 +231,31 @@ def RandomPoints(key, header, number_points): def NewFileExample(): # Create our new file with the specified format, scale, and offset - cfg = copc.LasConfig(8, [0.1, 0.1, 0.1], [50, 50, 50]) + cfg = copc.CopcConfigWriter( + 8, [0.1, 0.1, 0.1], [50, 50, 50], "TEST_WKT", has_extended_stats=True + ) # As of now, the library will not automatically compute the min/max of added points # so we will have to calculate it ourselves - cfg.min = MIN_BOUNDS - cfg.max = MAX_BOUNDS + cfg.las_header.min = MIN_BOUNDS + cfg.las_header.max = MAX_BOUNDS + + cfg.copc_info.spacing = 10 + + # Now, we can create our COPC writer: + writer = copc.FileWriter("new-copc.copc.laz", cfg) + header = writer.copc_config.las_header + + # Set the COPC Extents + extents = writer.copc_config.copc_extents + + extents.intensity.minimum = 0 + extents.intensity.maximum = 10000 + extents.intensity.mean = 50 + extents.intensity.var = 5 + + extents.classification.minimum = 5 + extents.classification.maximum = 201 - # Now, we can create our COPC writer, with an optional `span` and `wkt`: - writer = copc.FileWriter("new-copc.copc.laz", cfg, 256, "TEST_WKT") - header = writer.GetLasHeader() # The root page is automatically created root_page = writer.GetRootPage() @@ -264,7 +268,7 @@ def NewFileExample(): # We can also add pages in the same way, as long as the Key we specify # is a child of the parent page - page = writer.AddSubPage(root_page, copc.VoxelKey(1, 1, 1, 0)) + page = writer.AddSubPage(root_page, (1, 1, 1, 0)) # Once our page is created, we can add nodes to it like before key = copc.VoxelKey(1, 1, 1, 0) @@ -276,7 +280,7 @@ def NewFileExample(): writer.AddNode(page, key, points) # We can nest subpages as much as we want, as long as they are children of the parent - sub_page = writer.AddSubPage(page, copc.VoxelKey(3, 4, 4, 0)) + sub_page = writer.AddSubPage(page, (3, 4, 4, 0)) points = RandomPoints(sub_page.key, header, NUM_POINTS) writer.AddNode(page, sub_page.key, points) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index b2b25edd..78df5059 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -8,7 +8,7 @@ find_package(pybind11 CONFIG REQUIRED) pybind11_add_module(${COPC_PYTHON_LIB} bindings.cpp) target_link_libraries(${COPC_PYTHON_LIB} PRIVATE COPCLIB::copc-lib) -if(WITH_TESTS_AND_EXAMPLES) +if(WITH_TESTS) set_target_properties(${COPC_PYTHON_LIB} PROPERTIES # have the python lib output to the python build dir # not the /lib dir diff --git a/python/bindings.cpp b/python/bindings.cpp index a007e2fa..83384bd2 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include @@ -9,15 +8,17 @@ #include #include +#include +#include #include #include #include #include #include -#include #include #include #include +#include #include #include @@ -28,7 +29,6 @@ PYBIND11_MAKE_OPAQUE(std::vector); PYBIND11_MODULE(copclib, m) { - py::bind_vector>(m, "VectorChar", py::buffer_protocol()) .def(py::pickle( [](const std::vector &vec) { // __getstate__ @@ -43,16 +43,17 @@ PYBIND11_MODULE(copclib, m) py::class_(m, "VoxelKey") .def(py::init<>()) - .def(py::init(), py::arg("d"), py::arg("x"), py::arg("y"), py::arg("z")) + .def(py::init(), py::arg("d"), py::arg("x"), + py::arg("y"), py::arg("z")) + .def(py::init &>(), py::arg("list")) .def(py::self == py::self) - .def(py::self != py::self) .def_readwrite("d", &VoxelKey::d) .def_readwrite("x", &VoxelKey::x) .def_readwrite("y", &VoxelKey::y) .def_readwrite("z", &VoxelKey::z) .def("IsValid", &VoxelKey::IsValid) - .def("BaseKey", &VoxelKey::BaseKey) - .def("InvalidKey", &VoxelKey::InvalidKey) + .def_static("RootKey", &VoxelKey::RootKey) + .def_static("InvalidKey", &VoxelKey::InvalidKey) .def("Bisect", &VoxelKey::Bisect) .def("GetChildren", &VoxelKey::GetChildren) .def("GetParent", &VoxelKey::GetParent) @@ -104,7 +105,7 @@ PYBIND11_MODULE(copclib, m) .def_readwrite("y_max", &Box::y_max) .def_readwrite("z_max", &Box::z_max) .def_static("MaxBox", &Box::MaxBox) - .def_static("ZeroBox", &Box::ZeroBox) + .def_static("EmptyBox", &Box::EmptyBox) .def("Intersects", &Box::Intersects) .def("Contains", py::overload_cast(&Box::Contains, py::const_)) .def("Contains", py::overload_cast(&Box::Contains, py::const_)) @@ -112,7 +113,6 @@ PYBIND11_MODULE(copclib, m) .def("__str__", &Box::ToString) .def("__repr__", &Box::ToString); - py::implicitly_convertible(); py::implicitly_convertible(); py::class_(m, "Node") @@ -156,8 +156,8 @@ PYBIND11_MODULE(copclib, m) py::class_(m, "Vector3") .def(py::init<>()) - .def(py::init()) - .def(py::init &>()) + .def(py::init(), py::arg("x"), py::arg("y"), py::arg("z")) + .def(py::init &>(), py::arg("list")) .def_readwrite("x", &Vector3::x) .def_readwrite("y", &Vector3::y) .def_readwrite("z", &Vector3::z) @@ -201,91 +201,76 @@ PYBIND11_MODULE(copclib, m) [](const py::tuple &t) { // __setstate__ return Vector3(t[0].cast(), t[1].cast(), t[2].cast()); })); + py::implicitly_convertible(); py::implicitly_convertible(); py::class_>(m, "Point") - .def_property("X", py::overload_cast<>(&las::Point::X, py::const_), + .def_property("x", py::overload_cast<>(&las::Point::X, py::const_), py::overload_cast(&las::Point::X)) - .def_property("Y", py::overload_cast<>(&las::Point::Y, py::const_), + .def_property("y", py::overload_cast<>(&las::Point::Y, py::const_), py::overload_cast(&las::Point::Y)) - .def_property("Z", py::overload_cast<>(&las::Point::Z, py::const_), + .def_property("z", py::overload_cast<>(&las::Point::Z, py::const_), py::overload_cast(&las::Point::Z)) - .def_property("UnscaledX", py::overload_cast<>(&las::Point::UnscaledX, py::const_), + .def_property("X", py::overload_cast<>(&las::Point::UnscaledX, py::const_), py::overload_cast(&las::Point::UnscaledX)) - .def_property("UnscaledY", py::overload_cast<>(&las::Point::UnscaledY, py::const_), + .def_property("Y", py::overload_cast<>(&las::Point::UnscaledY, py::const_), py::overload_cast(&las::Point::UnscaledY)) - .def_property("UnscaledZ", py::overload_cast<>(&las::Point::UnscaledZ, py::const_), + .def_property("Z", py::overload_cast<>(&las::Point::UnscaledZ, py::const_), py::overload_cast(&las::Point::UnscaledZ)) - .def_property("Intensity", py::overload_cast<>(&las::Point::Intensity, py::const_), + .def_property("intensity", py::overload_cast<>(&las::Point::Intensity, py::const_), py::overload_cast(&las::Point::Intensity)) - .def_property("ReturnsScanDirEofBitFields", - py::overload_cast<>(&las::Point::ReturnsScanDirEofBitFields, py::const_), - py::overload_cast(&las::Point::ReturnsScanDirEofBitFields)) - .def_property("ExtendedReturnsBitFields", - py::overload_cast<>(&las::Point::ExtendedReturnsBitFields, py::const_), - py::overload_cast(&las::Point::ExtendedReturnsBitFields)) - .def_property("ReturnNumber", py::overload_cast<>(&las::Point::ReturnNumber, py::const_), + .def_property("returns_bit_field", py::overload_cast<>(&las::Point::ReturnsBitField, py::const_), + py::overload_cast(&las::Point::ReturnsBitField)) + .def_property("return_number", py::overload_cast<>(&las::Point::ReturnNumber, py::const_), py::overload_cast(&las::Point::ReturnNumber)) - .def_property("NumberOfReturns", py::overload_cast<>(&las::Point::NumberOfReturns, py::const_), + .def_property("number_of_returns", py::overload_cast<>(&las::Point::NumberOfReturns, py::const_), py::overload_cast(&las::Point::NumberOfReturns)) - .def_property("ExtendedFlagsBitFields", py::overload_cast<>(&las::Point::ExtendedFlagsBitFields, py::const_), - py::overload_cast(&las::Point::ExtendedFlagsBitFields)) - .def_property("Synthetic", py::overload_cast<>(&las::Point::Synthetic, py::const_), + .def_property("flags_bit_field", py::overload_cast<>(&las::Point::FlagsBitField, py::const_), + py::overload_cast(&las::Point::FlagsBitField)) + .def_property("synthetic", py::overload_cast<>(&las::Point::Synthetic, py::const_), py::overload_cast(&las::Point::Synthetic)) - .def_property("KeyPoint", py::overload_cast<>(&las::Point::KeyPoint, py::const_), + .def_property("key_point", py::overload_cast<>(&las::Point::KeyPoint, py::const_), py::overload_cast(&las::Point::KeyPoint)) - .def_property("Withheld", py::overload_cast<>(&las::Point::Withheld, py::const_), + .def_property("withheld", py::overload_cast<>(&las::Point::Withheld, py::const_), py::overload_cast(&las::Point::Withheld)) - .def_property("Overlap", py::overload_cast<>(&las::Point::Overlap, py::const_), + .def_property("overlap", py::overload_cast<>(&las::Point::Overlap, py::const_), py::overload_cast(&las::Point::Overlap)) - .def_property("ScannerChannel", py::overload_cast<>(&las::Point::ScannerChannel, py::const_), + .def_property("scanner_channel", py::overload_cast<>(&las::Point::ScannerChannel, py::const_), py::overload_cast(&las::Point::ScannerChannel)) - .def_property("ScanDirectionFlag", py::overload_cast<>(&las::Point::ScanDirectionFlag, py::const_), + .def_property("scan_direction_flag", py::overload_cast<>(&las::Point::ScanDirectionFlag, py::const_), py::overload_cast(&las::Point::ScanDirectionFlag)) - .def_property("EdgeOfFlightLineFlag", py::overload_cast<>(&las::Point::EdgeOfFlightLineFlag, py::const_), + .def_property("edge_of_flight_line", py::overload_cast<>(&las::Point::EdgeOfFlightLineFlag, py::const_), py::overload_cast(&las::Point::EdgeOfFlightLineFlag)) - .def_property("ClassificationBitFields", py::overload_cast<>(&las::Point::ClassificationBitFields, py::const_), - py::overload_cast(&las::Point::ClassificationBitFields)) - .def_property("Classification", py::overload_cast<>(&las::Point::Classification, py::const_), + .def_property("classification", py::overload_cast<>(&las::Point::Classification, py::const_), py::overload_cast(&las::Point::Classification)) - .def_property("ScanAngleRank", py::overload_cast<>(&las::Point::ScanAngleRank, py::const_), - py::overload_cast(&las::Point::ScanAngleRank)) - .def_property("ExtendedScanAngle", py::overload_cast<>(&las::Point::ExtendedScanAngle, py::const_), - py::overload_cast(&las::Point::ExtendedScanAngle)) - .def_property("ScanAngle", py::overload_cast<>(&las::Point::ScanAngle, py::const_), - py::overload_cast(&las::Point::ScanAngle)) - .def_property("UserData", py::overload_cast<>(&las::Point::UserData, py::const_), + .def_property("scan_angle", py::overload_cast<>(&las::Point::ScanAngle, py::const_), + py::overload_cast(&las::Point::ScanAngle)) + .def_property("scan_angle_degrees", py::overload_cast<>(&las::Point::ScanAngleDegrees, py::const_), + py::overload_cast(&las::Point::ScanAngleDegrees)) + .def_property("user_data", py::overload_cast<>(&las::Point::UserData, py::const_), py::overload_cast(&las::Point::UserData)) - .def_property("PointSourceID", py::overload_cast<>(&las::Point::PointSourceID, py::const_), - py::overload_cast(&las::Point::PointSourceID)) - .def_property("RGB", nullptr, py::overload_cast &>(&las::Point::RGB)) - .def_property("Red", py::overload_cast<>(&las::Point::Red, py::const_), + .def_property("point_source_id", py::overload_cast<>(&las::Point::PointSourceId, py::const_), + py::overload_cast(&las::Point::PointSourceId)) + .def_property("rgb", nullptr, py::overload_cast &>(&las::Point::Rgb)) + .def_property("red", py::overload_cast<>(&las::Point::Red, py::const_), py::overload_cast(&las::Point::Red)) - .def_property("Green", py::overload_cast<>(&las::Point::Green, py::const_), + .def_property("green", py::overload_cast<>(&las::Point::Green, py::const_), py::overload_cast(&las::Point::Green)) - .def_property("Blue", py::overload_cast<>(&las::Point::Blue, py::const_), + .def_property("blue", py::overload_cast<>(&las::Point::Blue, py::const_), py::overload_cast(&las::Point::Blue)) - .def_property("GPSTime", py::overload_cast<>(&las::Point::GPSTime, py::const_), + .def_property("gps_time", py::overload_cast<>(&las::Point::GPSTime, py::const_), py::overload_cast(&las::Point::GPSTime)) - .def_property("NIR", py::overload_cast<>(&las::Point::NIR, py::const_), - py::overload_cast(&las::Point::NIR)) - - .def_property_readonly("HasExtendedPoint", &las::Point::HasExtendedPoint) - .def_property_readonly("HasGPSTime", &las::Point::HasGPSTime) - .def_property_readonly("HasRGB", &las::Point::HasRGB) - .def_property_readonly("HasRGB", &las::Point::HasRGB) - .def_property_readonly("HasNIR", &las::Point::HasNIR) - - .def("ToPointFormat", &las::Point::ToPointFormat, py::arg("point_format_id")) - - .def_property_readonly("PointFormatID", &las::Point::PointFormatID) - .def_property_readonly("PointRecordLength", &las::Point::PointRecordLength) - .def_property_readonly("NumExtraBytes", &las::Point::NumExtraBytes) - - .def_property("ExtraBytes", py::overload_cast<>(&las::Point::ExtraBytes, py::const_), + .def_property("nir", py::overload_cast<>(&las::Point::Nir, py::const_), + py::overload_cast(&las::Point::Nir)) + .def_property_readonly("point_format_id", &las::Point::PointFormatId) + .def_property_readonly("point_record_length", &las::Point::PointRecordLength) + .def_property("extra_bytes", py::overload_cast<>(&las::Point::ExtraBytes, py::const_), py::overload_cast &>(&las::Point::ExtraBytes)) - + .def("HasRgb", &las::Point::HasRgb) + .def("HasNir", &las::Point::HasNir) + .def("ToPointFormat", &las::Point::ToPointFormat, py::arg("point_format_id")) + .def("EbByteSize", &las::Point::EbByteSize) .def(py::self == py::self) .def(py::self != py::self) .def("Within", &las::Point::Within, py::arg("box")) @@ -306,28 +291,28 @@ PYBIND11_MODULE(copclib, m) py::class_(m, "Points") .def(py::init(), - py::arg("point_format_id"), py::arg("scale"), py::arg("offset"), py::arg("num_extra_bytes") = 0) + py::arg("point_format_id"), py::arg("scale"), py::arg("offset"), py::arg("eb_byte_size") = 0) .def(py::init> &>(), py::arg("points")) .def(py::init()) - .def_property("X", py::overload_cast<>(&las::Points::X, py::const_), + .def_property("x", py::overload_cast<>(&las::Points::X, py::const_), py::overload_cast &>(&las::Points::X)) - .def_property("Y", py::overload_cast<>(&las::Points::Y, py::const_), + .def_property("y", py::overload_cast<>(&las::Points::Y, py::const_), py::overload_cast &>(&las::Points::Y)) - .def_property("Z", py::overload_cast<>(&las::Points::Z, py::const_), + .def_property("z", py::overload_cast<>(&las::Points::Z, py::const_), py::overload_cast &>(&las::Points::Z)) - .def_property("Classification", py::overload_cast<>(&las::Points::Classification, py::const_), + .def_property("classification", py::overload_cast<>(&las::Points::Classification, py::const_), py::overload_cast &>(&las::Points::Classification)) - .def_property("PointSourceID", py::overload_cast<>(&las::Points::PointSourceID, py::const_), - py::overload_cast &>(&las::Points::PointSourceID)) - .def_property("Red", py::overload_cast<>(&las::Points::Red, py::const_), + .def_property("point_source_id", py::overload_cast<>(&las::Points::PointSourceId, py::const_), + py::overload_cast &>(&las::Points::PointSourceId)) + .def_property("red", py::overload_cast<>(&las::Points::Red, py::const_), py::overload_cast &>(&las::Points::Red)) - .def_property("Green", py::overload_cast<>(&las::Points::Green, py::const_), + .def_property("green", py::overload_cast<>(&las::Points::Green, py::const_), py::overload_cast &>(&las::Points::Green)) - .def_property("Blue", py::overload_cast<>(&las::Points::Blue, py::const_), + .def_property("blue", py::overload_cast<>(&las::Points::Blue, py::const_), py::overload_cast &>(&las::Points::Blue)) - .def_property_readonly("PointFormatID", &las::Points::PointFormatID) - .def_property_readonly("PointRecordLength", &las::Points::PointRecordLength) - .def_property_readonly("NumExtraBytes", &las::Points::NumExtraBytes) + .def_property_readonly("point_format_id", &las::Points::PointFormatId) + .def_property_readonly("point_record_length", &las::Points::PointRecordLength) + .def_property_readonly("eb_byte_size", &las::Points::EbByteSize) .def("AddPoint", &las::Points::AddPoint) .def("AddPoints", py::overload_cast(&las::Points::AddPoints)) .def("AddPoints", py::overload_cast>>(&las::Points::AddPoints)) @@ -350,13 +335,6 @@ PYBIND11_MODULE(copclib, m) i = wrap_i(i, s.Size()); s[(SizeType)i] = v; }) - // todo? - //.def( - // "__delitem__", - // [wrap_i](las::Points &s, size_t i) { - // i = wrap_i(i, s.Size()); - // }, - // "Delete the list elements at index ``i``") .def("__len__", &las::Points::Size) /// Optional sequence protocol operations .def( @@ -405,10 +383,7 @@ PYBIND11_MODULE(copclib, m) .def(py::init()) .def("Close", &FileReader::Close) .def("FindNode", &Reader::FindNode, py::arg("key")) - .def("GetWkt", &Reader::GetWkt) - .def("GetCopcHeader", &Reader::GetCopcHeader) - .def("GetLasHeader", &Reader::GetLasHeader) - .def("GetExtraByteVlr", &Reader::GetExtraByteVlr) + .def_property_readonly("copc_config", &Reader::CopcConfig) .def("GetPointData", py::overload_cast(&Reader::GetPointData), py::arg("node")) .def("GetPointData", py::overload_cast(&Reader::GetPointData), py::arg("key")) .def("GetPoints", py::overload_cast(&Reader::GetPoints), py::arg("node")) @@ -417,8 +392,8 @@ PYBIND11_MODULE(copclib, m) py::arg("node")) .def("GetPointDataCompressed", py::overload_cast(&Reader::GetPointDataCompressed), py::arg("key")) - .def("GetAllChildren", py::overload_cast(&Reader::GetAllChildren), py::arg("key")) - .def("GetAllChildren", py::overload_cast<>(&Reader::GetAllChildren)) + .def("GetAllChildrenOfPage", &Reader::GetAllChildrenOfPage, py::arg("key")) + .def("GetAllNodes", &Reader::GetAllNodes) .def("GetAllPoints", &Reader::GetAllPoints, py::arg("resolution") = 0) .def("GetNodesWithinBox", &Reader::GetNodesWithinBox, py::arg("box"), py::arg("resolution") = 0) .def("GetNodesIntersectBox", &Reader::GetNodesIntersectBox, py::arg("box"), py::arg("resolution") = 0) @@ -429,13 +404,9 @@ PYBIND11_MODULE(copclib, m) .def("ValidateSpatialBounds", &Reader::ValidateSpatialBounds, py::arg("verbose") = false); py::class_(m, "FileWriter") - .def(py::init(), - py::arg("file_path"), py::arg("config"), py::arg("span") = 0, py::arg("wkt") = "") + .def(py::init(), py::arg("file_path"), py::arg("config")) + .def_property_readonly("copc_config", &Writer::CopcConfig) .def("FindNode", &Writer::FindNode) - .def("GetWkt", &Writer::GetWkt) - .def("GetCopcHeader", &Writer::GetCopcHeader) - .def("GetLasHeader", &Writer::GetLasHeader) - .def("GetExtraByteVlr", &Writer::GetExtraByteVlr) .def("GetRootPage", &Writer::GetRootPage) .def("Close", &FileWriter::Close) .def("AddNode", py::overload_cast(&Writer::AddNode), py::arg("page"), @@ -443,14 +414,11 @@ PYBIND11_MODULE(copclib, m) .def("AddNodeCompressed", &Writer::AddNodeCompressed) .def("AddNode", py::overload_cast const &>(&Writer::AddNode), py::arg("page"), py::arg("key"), py::arg("uncompressed_data")) - .def("AddSubPage", &Writer::AddSubPage, py::arg("parent_page"), py::arg("key")) - .def("SetMin", &Writer::SetMin) - .def("SetMax", &Writer::SetMax) - .def("SetPointsByReturn", &Writer::SetPointsByReturn); + .def("AddSubPage", &Writer::AddSubPage, py::arg("parent_page"), py::arg("key")); m.def("CompressBytes", py::overload_cast &, const int8_t &, const uint16_t &>(&laz::Compressor::CompressBytes), - py::arg("in"), py::arg("point_format_id"), py::arg("num_extra_bytes")); + py::arg("in"), py::arg("point_format_id"), py::arg("eb_byte_size")); m.def("CompressBytes", py::overload_cast &, const las::LasHeader &>(&laz::Compressor::CompressBytes)); @@ -461,36 +429,35 @@ PYBIND11_MODULE(copclib, m) m.def("DecompressBytes", py::overload_cast &, const int8_t &, const uint16_t &, const int &>( &laz::Decompressor::DecompressBytes), - py::arg("compressed_data"), py::arg("point_format_id"), py::arg("num_extra_bytes"), py::arg("point_count")); + py::arg("compressed_data"), py::arg("point_format_id"), py::arg("eb_byte_size"), py::arg("point_count")); - py::class_(m, "LasHeader") + py::class_>(m, "LasHeader") .def(py::init<>()) - .def_property_readonly("num_extra_bytes", &las::LasHeader::NumExtraBytes) .def_readwrite("file_source_id", &las::LasHeader::file_source_id) .def_readwrite("global_encoding", &las::LasHeader::global_encoding) .def_property("guid", py::overload_cast<>(&las::LasHeader::GUID, py::const_), py::overload_cast(&las::LasHeader::GUID)) - .def_readwrite("version_minor", &las::LasHeader::version_minor) - .def_readwrite("version_major", &las::LasHeader::version_major) .def_property("system_identifier", py::overload_cast<>(&las::LasHeader::SystemIdentifier, py::const_), py::overload_cast(&las::LasHeader::SystemIdentifier)) .def_property("generating_software", py::overload_cast<>(&las::LasHeader::GeneratingSoftware, py::const_), py::overload_cast(&las::LasHeader::GeneratingSoftware)) .def_readwrite("creation_day", &las::LasHeader::creation_day) .def_readwrite("creation_year", &las::LasHeader::creation_year) - .def_readwrite("header_size", &las::LasHeader::header_size) - .def_readwrite("point_offset", &las::LasHeader::point_offset) - .def_readwrite("vlr_count", &las::LasHeader::vlr_count) - .def_readwrite("point_format_id", &las::LasHeader::point_format_id) - .def_readwrite("point_record_length", &las::LasHeader::point_record_length) - .def_readwrite("point_count", &las::LasHeader::point_count) - .def_readwrite("points_by_return", &las::LasHeader::points_by_return) - .def_readwrite("scale", &las::LasHeader::scale) - .def_readwrite("offset", &las::LasHeader::offset) + .def_property_readonly("point_offset", &las::LasHeader::PointOffset) + .def_property_readonly("vlr_count", &las::LasHeader::VlrCount) + .def_property_readonly("point_format_id", &las::LasHeader::PointFormatId) + .def_property_readonly("point_record_length", &las::LasHeader::PointRecordLength) + .def_property_readonly("scale", &las::LasHeader::Scale) + .def_property_readonly("offset", &las::LasHeader::Offset) .def_readwrite("max", &las::LasHeader::max) .def_readwrite("min", &las::LasHeader::min) - .def("GetSpan", &las::LasHeader::GetSpan) - .def("GetBounds", &las::LasHeader::GetBounds) + .def_property_readonly("evlr_offset", &las::LasHeader::EvlrOffset) + .def_property_readonly("evlr_count", &las::LasHeader::EvlrCount) + .def_property_readonly("point_count", &las::LasHeader::PointCount) + .def_readwrite("points_by_return", &las::LasHeader::points_by_return) + .def("EbByteSize", &las::LasHeader::EbByteSize) + .def("Span", &las::LasHeader::Span) + .def("Bounds", &las::LasHeader::Bounds) .def("ApplyScale", &las::LasHeader::ApplyScale) .def("ApplyInverseScale", &las::LasHeader::ApplyInverseScale) .def("ApplyScaleX", &las::LasHeader::ApplyScaleX) @@ -499,87 +466,127 @@ PYBIND11_MODULE(copclib, m) .def("ApplyInverseScaleX", &las::LasHeader::ApplyInverseScaleX) .def("ApplyInverseScaleY", &las::LasHeader::ApplyInverseScaleY) .def("ApplyInverseScaleZ", &las::LasHeader::ApplyInverseScaleZ) - .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("point_count_14", &las::LasHeader::point_count) - .def_readwrite("points_by_return_14", &las::LasHeader::points_by_return_14) + .def("__str__", &las::LasHeader::ToString) + .def("__repr__", &las::LasHeader::ToString) .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, h.offset, - h.max, h.min, h.wave_offset, h.evlr_offset, h.evlr_count, h.point_count_14, - h.points_by_return_14); + return py::make_tuple(h.file_source_id, h.global_encoding, h.GUID(), h.SystemIdentifier(), + h.GeneratingSoftware(), h.creation_day, h.creation_year, h.PointOffset(), + h.VlrCount(), h.PointFormatId(), h.PointRecordLength(), h.Scale(), h.Offset(), + h.max, h.min, h.EvlrOffset(), h.EvlrCount(), h.PointCount(), h.points_by_return); }, [](py::tuple t) { // __setstate__ - if (t.size() != 25) + if (t.size() != 19) throw std::runtime_error("Invalid state!"); /* Create a new C++ instance */ - las::LasHeader h; + las::LasHeader h(t[9].cast(), t[10].cast(), t[7].cast(), + t[17].cast(), t[8].cast(), t[11].cast(), + t[12].cast(), t[15].cast(), t[16].cast()); 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 = 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>(); + h.SystemIdentifier(t[3].cast()); + h.GeneratingSoftware(t[4].cast()); + h.creation_day = t[5].cast(); + h.creation_year = t[6].cast(); + h.max = t[13].cast(); + h.min = t[14].cast(); + h.points_by_return = t[18].cast>(); return h; })); - py::class_(m, "LasConfig") - .def(py::init(), py::arg("point_format_id"), - py::arg("scale") = Vector3::DefaultScale(), py::arg("offset") = Vector3::DefaultOffset()) - .def(py::init()) - .def_readwrite("file_source_id", &Writer::LasConfig::file_source_id) - .def_readwrite("global_encoding", &Writer::LasConfig::global_encoding) - .def_readwrite("creation_day", &Writer::LasConfig::creation_day) - .def_readwrite("creation_year", &Writer::LasConfig::creation_year) - .def_readwrite("point_format_id", &Writer::LasConfig::point_format_id) - .def_readwrite("scale", &Writer::LasConfig::scale) - .def_readwrite("offset", &Writer::LasConfig::offset) - .def_readwrite("max", &Writer::LasConfig::max) - .def_readwrite("min", &Writer::LasConfig::min) - .def_readwrite("points_by_return_14", &Writer::LasConfig::points_by_return_14) - .def_property("guid", py::overload_cast<>(&Writer::LasConfig::GUID, py::const_), - py::overload_cast(&Writer::LasConfig::GUID)) - .def_property("system_identifier", py::overload_cast<>(&Writer::LasConfig::SystemIdentifier, py::const_), - py::overload_cast(&Writer::LasConfig::SystemIdentifier)) - .def_property("generating_software", py::overload_cast<>(&Writer::LasConfig::GeneratingSoftware, py::const_), - py::overload_cast(&Writer::LasConfig::GeneratingSoftware)) - .def("NumExtraBytes", &Writer::LasConfig::NumExtraBytes); - - py::class_(m, "CopcVlr") - .def_readwrite("span", &las::CopcVlr::span) - .def_readwrite("root_hier_offset", &las::CopcVlr::root_hier_offset) - .def_readwrite("root_hier_size", &las::CopcVlr::root_hier_size) - .def_readwrite("laz_vlr_offset", &las::CopcVlr::laz_vlr_offset) - .def_readwrite("laz_vlr_size", &las::CopcVlr::laz_vlr_size) - .def_readwrite("wkt_vlr_offset", &las::CopcVlr::wkt_vlr_offset) - .def_readwrite("wkt_vlr_size", &las::CopcVlr::wkt_vlr_size) - .def_readwrite("eb_vlr_offset", &las::CopcVlr::eb_vlr_offset) - .def_readwrite("eb_vlr_size", &las::CopcVlr::eb_vlr_size); - - py::class_(m, "EbVlr").def(py::init()); + py::class_(m, "EbVlr").def(py::init()).def_readwrite("items", &las::EbVlr::items); + + py::class_(m, "EbField").def(py::init<>()); + + py::class_>(m, "CopcConfig") + .def_property_readonly("las_header", &CopcConfig::LasHeader) + .def_property_readonly("copc_info", &CopcConfig::CopcInfo) + .def_property_readonly("copc_extents", &CopcConfig::CopcExtents) + .def_property_readonly("extra_bytes_vlr", &CopcConfig::ExtraBytesVlr) + .def_property_readonly("wkt", &CopcConfig::Wkt); + + py::class_>(m, "CopcConfigWriter") + .def( + py::init(), + py::arg("point_format_id"), py::arg("scale") = Vector3::DefaultScale(), + py::arg("offset") = Vector3::DefaultOffset(), py::arg("wkt") = "", + py::arg("extra_bytes_vlr") = las::EbVlr(0), py::arg("has_extended_stats") = false) + .def(py::init()) + .def_property_readonly("las_header", py::overload_cast<>(&CopcConfigWriter::LasHeader)) + .def_property_readonly("copc_info", py::overload_cast<>(&CopcConfigWriter::CopcInfo)) + .def_property_readonly("copc_extents", py::overload_cast<>(&CopcConfigWriter::CopcExtents)) + .def_property_readonly("extra_bytes_vlr", &CopcConfig::ExtraBytesVlr) + .def_property_readonly("wkt", &CopcConfig::Wkt); + + py::implicitly_convertible(); + + py::class_>(m, "CopcInfo") + .def(py::init<>()) + .def_readwrite("center_x", &CopcInfo::center_x) + .def_readwrite("center_y", &CopcInfo::center_y) + .def_readwrite("center_z", &CopcInfo::center_z) + .def_readwrite("halfsize", &CopcInfo::halfsize) + .def_readwrite("spacing", &CopcInfo::spacing) + .def_readwrite("root_hier_offset", &CopcInfo::root_hier_offset) + .def_readwrite("root_hier_size", &CopcInfo::root_hier_size) + .def("__str__", &CopcInfo::ToString) + .def("__repr__", &CopcInfo::ToString); + + py::class_>(m, "CopcExtent") + .def(py::init<>()) + .def(py::init(), py::arg("minimum"), py::arg("maximum"), py::arg("mean") = 0, + py::arg("var") = 1) + .def(py::init &>(), py::arg("list")) + .def_readwrite("minimum", &CopcExtent::minimum) + .def_readwrite("maximum", &CopcExtent::maximum) + .def_readwrite("mean", &CopcExtent::mean) + .def_readwrite("var", &CopcExtent::var) + .def(py::self == py::self) + .def("__str__", &CopcExtent::ToString) + .def("__repr__", &CopcExtent::ToString); + + py::implicitly_convertible(); + + py::class_>(m, "CopcExtents") + .def(py::init(), py::arg("point_format_id"), py::arg("num_eb_items") = 0) + .def_property_readonly("point_format_id", &CopcExtents::PointFormatId) + .def_property_readonly("has_extended_stats", &CopcExtents::HasExtendedStats) + .def_property("intensity", py::overload_cast<>(&CopcExtents::Intensity), + py::overload_cast>(&CopcExtents::Intensity)) + .def_property("return_number", py::overload_cast<>(&CopcExtents::ReturnNumber), + py::overload_cast>(&CopcExtents::ReturnNumber)) + .def_property("number_of_returns", py::overload_cast<>(&CopcExtents::NumberOfReturns), + py::overload_cast>(&CopcExtents::NumberOfReturns)) + .def_property("scanner_channel", py::overload_cast<>(&CopcExtents::ScannerChannel), + py::overload_cast>(&CopcExtents::ScannerChannel)) + .def_property("scan_direction_flag", py::overload_cast<>(&CopcExtents::ScanDirectionFlag), + py::overload_cast>(&CopcExtents::ScanDirectionFlag)) + .def_property("edge_of_flight_line", py::overload_cast<>(&CopcExtents::EdgeOfFlightLine), + py::overload_cast>(&CopcExtents::EdgeOfFlightLine)) + .def_property("classification", py::overload_cast<>(&CopcExtents::Classification), + py::overload_cast>(&CopcExtents::Classification)) + .def_property("user_data", py::overload_cast<>(&CopcExtents::UserData), + py::overload_cast>(&CopcExtents::UserData)) + .def_property("scan_angle", py::overload_cast<>(&CopcExtents::ScanAngle), + py::overload_cast>(&CopcExtents::ScanAngle)) + .def_property("point_source_id", py::overload_cast<>(&CopcExtents::PointSourceId), + py::overload_cast>(&CopcExtents::PointSourceId)) + .def_property("gps_time", py::overload_cast<>(&CopcExtents::GpsTime), + py::overload_cast>(&CopcExtents::GpsTime)) + .def_property("red", py::overload_cast<>(&CopcExtents::Red), + py::overload_cast>(&CopcExtents::Red)) + .def_property("green", py::overload_cast<>(&CopcExtents::Green), + py::overload_cast>(&CopcExtents::Green)) + .def_property("blue", py::overload_cast<>(&CopcExtents::Blue), + py::overload_cast>(&CopcExtents::Blue)) + .def_property("nir", py::overload_cast<>(&CopcExtents::Nir), + py::overload_cast>(&CopcExtents::Nir)) + .def_property("extra_bytes", py::overload_cast<>(&CopcExtents::ExtraBytes), + py::overload_cast>>(&CopcExtents::ExtraBytes)) + .def_property_readonly("extents", py::overload_cast<>(&CopcExtents::Extents)) + .def("__str__", &CopcExtents::ToString) + .def("__repr__", &CopcExtents::ToString); } diff --git a/test/box_test.py b/test/box_test.py index 66d6663a..e3628ac1 100644 --- a/test/box_test.py +++ b/test/box_test.py @@ -52,7 +52,7 @@ def test_box_constructors(): header = copc.LasHeader() header.min = (0, 0, 0) header.max = (10, 10, 10) - box = copc.Box(copc.VoxelKey(1, 1, 0, 0), header) + box = copc.Box((1, 1, 0, 0), header) assert box.x_min == 5.0 assert box.y_min == 0.0 @@ -102,8 +102,8 @@ def test_box_functions(): header = copc.LasHeader() header.min = (0, 0, 0) header.max = (10, 10, 10) - box1 = copc.Box(copc.VoxelKey(0, 0, 0, 0), header) - box2 = copc.Box(copc.VoxelKey(1, 1, 0, 0), header) + box1 = copc.Box((0, 0, 0, 0), header) + box2 = copc.Box((1, 1, 0, 0), header) assert box1.Contains(box2) assert not box2.Contains(box1) ## A box contains itself @@ -125,8 +125,8 @@ def test_box_functions(): header = copc.LasHeader() header.min = (0, 0, 0) header.max = (10, 10, 10) - box1 = copc.Box(copc.VoxelKey(0, 0, 0, 0), header) - box2 = copc.Box(copc.VoxelKey(1, 1, 0, 0), header) + box1 = copc.Box((0, 0, 0, 0), header) + box2 = copc.Box((1, 1, 0, 0), header) assert not box1.Within(box2) assert box2.Within(box1) ## A box is within itself diff --git a/test/config_test.cpp b/test/config_test.cpp new file mode 100644 index 00000000..f0976239 --- /dev/null +++ b/test/config_test.cpp @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace copc; + +TEST_CASE("CopcConfig", "[CopcConfig]") +{ + + int8_t point_format_id = 7; + int num_eb_items = 1; + double test_spacing = 12; + double test_intensity_min = 5; + double test_intensity_max = 155; + Vector3 test_scale(1, 1, 1); + Vector3 test_offset(50, 50, 50); + std::string test_wkt = "test_wkt"; + + las::EbVlr test_extra_bytes_vlr(num_eb_items); + test_extra_bytes_vlr.items[0].data_type = 0; + test_extra_bytes_vlr.items[0].options = 4; + test_extra_bytes_vlr.items[0].name = "eb1"; + + CopcInfo copc_info; + copc_info.spacing = test_spacing; + CopcExtents copc_extents(point_format_id, num_eb_items); + copc_extents.Intensity()->minimum = test_intensity_min; + copc_extents.Intensity()->maximum = test_intensity_max; + std::string wkt(test_wkt); + + las::LasHeader header(point_format_id, las::PointBaseByteSize(point_format_id) + test_extra_bytes_vlr.size(), + test_scale, test_offset); + + CopcConfig cfg(header, copc_info, copc_extents, wkt, test_extra_bytes_vlr); + + REQUIRE(cfg.LasHeader().PointFormatId() == point_format_id); + REQUIRE(cfg.LasHeader().Scale() == test_scale); + REQUIRE(cfg.LasHeader().Offset() == test_offset); + + REQUIRE(cfg.CopcInfo().spacing == test_spacing); + + REQUIRE(*cfg.CopcExtents().Intensity() == CopcExtent(test_intensity_min, test_intensity_max)); + + REQUIRE(cfg.Wkt() == test_wkt); + + REQUIRE(cfg.ExtraBytesVlr().items[0].name == test_extra_bytes_vlr.items[0].name); +} + +TEST_CASE("CopcConfigWriter", "[CopcConfigWriter]") +{ + int8_t point_format_id = 7; + int num_eb_items = 1; + double test_spacing = 12; + double test_intensity_min = 5; + double test_intensity_max = 155; + Vector3 test_scale(1, 1, 1); + Vector3 test_offset(50, 50, 50); + Vector3 test_min(-5, -6, -7); + Vector3 test_max(5, 6, 7); + std::string test_wkt = "test_wkt"; + + las::EbVlr test_extra_bytes_vlr(num_eb_items); + test_extra_bytes_vlr.items[0].data_type = 0; + test_extra_bytes_vlr.items[0].options = 4; + test_extra_bytes_vlr.items[0].name = "eb1"; + + SECTION("Constructor with default arguments") + { + CopcConfigWriter cfg(point_format_id); + + REQUIRE(cfg.LasHeader()->PointFormatId() == point_format_id); + REQUIRE(cfg.LasHeader()->Scale() == Vector3::DefaultScale()); + + REQUIRE(cfg.CopcInfo()->spacing == 0); + + REQUIRE(*cfg.CopcExtents()->Intensity() == CopcExtent()); + + REQUIRE(cfg.Wkt().empty()); + + REQUIRE(cfg.ExtraBytesVlr().items.empty()); + } + + SECTION("Constructor with specified arguments") + { + CopcConfigWriter cfg(point_format_id, test_scale, test_offset, test_wkt, test_extra_bytes_vlr); + + REQUIRE(cfg.LasHeader()->PointFormatId() == point_format_id); + REQUIRE(cfg.LasHeader()->Scale() == test_scale); + REQUIRE(cfg.LasHeader()->Offset() == test_offset); + + REQUIRE(cfg.CopcInfo()->spacing == 0); + + REQUIRE(*cfg.CopcExtents()->Intensity() == CopcExtent()); + + REQUIRE(cfg.Wkt() == test_wkt); + + REQUIRE(cfg.ExtraBytesVlr().items[0].name == test_extra_bytes_vlr.items[0].name); + } + + SECTION("Copy constructor from CopcConfig") + { + CopcInfo copc_info; + copc_info.spacing = test_spacing; + CopcExtents copc_extents(point_format_id, num_eb_items); + copc_extents.Intensity()->minimum = test_intensity_min; + copc_extents.Intensity()->maximum = test_intensity_max; + std::string wkt(test_wkt); + + las::LasHeader header(point_format_id, las::PointBaseByteSize(point_format_id) + test_extra_bytes_vlr.size(), + test_scale, test_offset); + + header.min = test_min; + + CopcConfig original(header, copc_info, copc_extents, wkt, test_extra_bytes_vlr); + + CopcConfigWriter copy(original); + + copy.LasHeader()->min = Vector3(); + copy.CopcInfo()->spacing = 1; + copy.CopcExtents()->Intensity()->minimum = 17; + + // Updating copy should not change original + REQUIRE(original.LasHeader().min == test_min); + REQUIRE(original.CopcInfo().spacing == test_spacing); + REQUIRE(original.CopcExtents().Intensity()->minimum == test_intensity_min); + } + + SECTION("Updating Config Values") + { + CopcConfigWriter cfg(point_format_id, test_scale, test_offset, test_wkt, test_extra_bytes_vlr); + + // Las Header + cfg.LasHeader()->min = test_min; + cfg.LasHeader()->max = test_max; + REQUIRE(cfg.LasHeader()->min == test_min); + REQUIRE(cfg.LasHeader()->max == test_max); + + // Copc Info + cfg.CopcInfo()->spacing = test_spacing; + REQUIRE(cfg.CopcInfo()->spacing == test_spacing); + + // Copc Extents + cfg.CopcExtents()->Intensity()->minimum = test_intensity_min; + cfg.CopcExtents()->Intensity()->maximum = test_intensity_max; + REQUIRE(*cfg.CopcExtents()->Intensity() == CopcExtent(test_intensity_min, test_intensity_max)); + } + + SECTION("Copy constructor") + { + CopcConfigWriter original(point_format_id); + + CopcConfigWriter copy(original); + + original.LasHeader()->min = test_min; + original.CopcInfo()->spacing = test_spacing; + original.CopcExtents()->Intensity()->minimum = test_intensity_min; + + // Updating original should not change copy + REQUIRE(copy.LasHeader()->min == Vector3()); + REQUIRE(copy.CopcInfo()->spacing == 0); + REQUIRE(copy.CopcExtents()->Intensity()->minimum == 0); + } +} diff --git a/test/data/.gitignore b/test/data/.gitignore new file mode 100644 index 00000000..c86e3f36 --- /dev/null +++ b/test/data/.gitignore @@ -0,0 +1 @@ +las/ diff --git a/test/data/create_test_data.py b/test/data/create_test_data.py new file mode 100644 index 00000000..b6898749 --- /dev/null +++ b/test/data/create_test_data.py @@ -0,0 +1,43 @@ +import os + +with open("test_data.h", mode="w") as out_file: + for x in ["first_20_pts", "next_12_pts", "last_60_pts"]: + with open(os.path.join("las", x + ".laz"), mode="rb") as f: + compressed_bytes = f.read() + start_pt_offset = int.from_bytes( + compressed_bytes[96:100], byteorder="little" + ) + first_pt_offset = start_pt_offset + 8 + last_pt_offset = int.from_bytes( + compressed_bytes[start_pt_offset:first_pt_offset], byteorder="little" + ) + print(start_pt_offset, first_pt_offset, last_pt_offset) + compressed_bytes = compressed_bytes[first_pt_offset:last_pt_offset] + + out_file.write( + "unsigned char %s_compressed[%d] = { " % (x, len(compressed_bytes)) + ) + + byte_str = compressed_bytes.hex(" ") + byte_str = ", ".join(["0x" + b.upper() for b in byte_str.split(" ")]) + out_file.write(byte_str) + out_file.write("};") + out_file.write("\n\n") + + with open(os.path.join("las", x + ".las"), mode="rb") as f: + uncompressed_bytes = f.read() + first_pt_offset = int.from_bytes( + uncompressed_bytes[96:100], byteorder="little" + ) + print(first_pt_offset) + uncompressed_bytes = uncompressed_bytes[first_pt_offset:] + + out_file.write("unsigned char %s[%d] = { " % (x, len(uncompressed_bytes))) + + byte_str = uncompressed_bytes.hex(" ") + byte_str = ", ".join(["0x" + b.upper() for b in byte_str.split(" ")]) + out_file.write(byte_str) + out_file.write("};") + out_file.write("\n\n") + + out_file.write("\n") diff --git a/test/data/create_test_data.sh b/test/data/create_test_data.sh new file mode 100644 index 00000000..b8528430 --- /dev/null +++ b/test/data/create_test_data.sh @@ -0,0 +1,9 @@ +mkdir las +cd las +wget https://github.com/PDAL/data/raw/a3d2a351ca1002c7ea4daa96b5c5fcb0fafeaa6f/autzen/autzen-classified.copc.laz +las2las -i autzen-classified.copc.laz -remove_all_vlrs -remove_all_evlrs -start_at_point 0 -stop_at_point 20 -o first_20_pts.las +las2las -i autzen-classified.copc.laz -remove_all_vlrs -remove_all_evlrs -start_at_point 20 -stop_at_point 32 -o next_12_pts.las +las2las -i autzen-classified.copc.laz -remove_all_vlrs -remove_all_evlrs -start_at_point 32 -stop_at_point 92 -o last_60_pts.las +laszip first_20_pts.las +laszip next_12_pts.las +laszip last_60_pts.las diff --git a/test/data/las/first_20_pts.las b/test/data/las/first_20_pts.las deleted file mode 100644 index 4bd9afab37cb17d751cabea6ededa3e6ba7dd7b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1055 zcmcK1O-K}B7zgnG%owP+YvEF)CPNmi3x)2XB9feoUu;B5A}NMef@JPjy6bM{H&LX7 zLftyFA8CP96eeU8-RxkOE|ru*ow}4I6(iICokfMQYcmY*%<$v+KhOJmwAylDE3B84 z=Fw^|dcEyI)p@2!ZRu76&Azs??ajfUSFO0vQVG7g`KZpWbAe!+w?lQ86qmRU6|Gg0 zAR#1$q|~>v8xFpS$9hE_Qx%Jmp`+H-Uz)ii7An+MoQ?B2e~PrFn6GrMAyM|~r!#-1 zwNQ)uN~=4$&J+ya*E3w&*Aq{ho}|09^vcDE<5{GvUIf)d%B*?fd##VP^PLX5BCzfG5TJ{XNo*zVC9nNqc?kyDRt8@@paN2BhKh|q}6Ji2)a0@n0D{6>7#x_z+;Xx1`IQB?4Y+qO- zD)u7-KT^}a?dL457(o>KDV|ffC?tyrVhn?L!#;j*Q*eYSrzyVonESC;oJ$l}Fpd{I z+wwdGvlI%2(PhpudXk+EV-z=Wm7*zE!FJA)wn>aoxF>ZZ${I^Nmn`mwtv@ODBQV=z s*M`g*hbfLzykyERA^NPsPO*n#nj(uSwjoo5Hfl%|0gCe!;`BF*zX+#VPXGV_ diff --git a/test/data/las/first_20_pts.laz b/test/data/las/first_20_pts.laz deleted file mode 100644 index bb091f726839d0e172fbdcaea7560f9b5da01c7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 827 zcmeZq40dB+5MV$Dj4VEm!6o_mImHSZ$(jmDl?p|P1(_*1iN(eF3huc{9t=RH5fCeA zl%*CGXXfWA7#SKE8d+$+2&02)k#+75t6J>Q=|WpB z@x|rJj^8w5&I|w2bbPe9!1h%1QisD|HDq?CDLXDHu=Oeh(mUl^SO4MEa(olGBW^2) zmLrG9!zz(IRSpp#i-M{g(Cq{9VeWv@*woNcZ1*Z)&{bs?D5T~k=clBmFfi;1GhqOR zGE_J}O#u=9@PK6kI*FNu8SJot!wd}n(Eu|88_;J=4D1XXK#~K05zeX9T3rdYq#Px|oG zR4Z?eSNdM16Ek@F7w-yrFIMKFu*v0%Pfm8;{t5gZ0v|v9KP13sY9%7QB{)ySKh{j? z56{ECSt=8+Gbz3-%{_cF`$F*XMY@w`tbWn6D>iN6FZ*YG7Z|=S&^h;Wf=}b@_aC;K zEv}M8CyWxJ{ z>2p>I<4@BgeYM49!pE{=%9GZg+dJjbtg{JQKJ0kIQvLjO{d~}`yMLE6I&K0@kMft_Ket(&BW{Eci{ME&eGje6Y3V%^q zsnZqxXtXOn>V}d74pcd+9OmzS;h4hTLiicQ&j@~k|H10eH)md|SwHR8?+SOwJxx^+ zo{q@uv%{}Nd46mvcc02?NiOvHwr?wp@T4CabG+CW;n{ff>gA&YANIt=#Q5LlcUQSP zq4ziqY9`mEBAAl>S$Zyr_L zqDQaI8sbSp7JS(JzS6NIwvZT#kydA|5P=3E;*oK zVv-Oh>3kuQnW6}P9Z-5JiE|_#6c;x6J28B1_v2dfMcPDyGsHVH8faaf4;+m&9$9CG?8y-9Ve zA>8b$MIwd7W)kU0y;1EUAtI54Of)tt{c*4#ci!yBMIpiwO(MTZ=?{a|y6DnV-6AtK zU_r$YPXu!;KqWO~3g4hhgb=H2VuBD4AQp2G*P?U?H5y3_rc2KnA#|b;PE5knC?#=$ z#0nDQ*s;Du6mNw^b}g^aG>>RpAh#1cLGq_G- z3pMuH8u?aFot?B%i1CO=HX3+x?IemxNI!x^n-H<=Xf9rEReBU%ULs-fG`tBlH$x9G>9W7IF$3NyuiS*F|3!jmJ^WN#5ILc3-NUDE+XL>dJ*AlDJQW zNaIEwU=G=a1aYag`*Bc+M0*=@4k}$omxCmRNIz^M@VyX^*?W7Dgr7t^33snc;EE7t z=R)66dLaoXZ-)VNxn?w~7uhbk%<=qtO0Om%dpllc-6Z`=h^gGC0=&vQD3nQs7|0ym zCh2cNxOi@PI7{MgYRDvq%M_aVP(n9s9LuCZ zml0XiaI@z=OyqrfDVTI5i7u1l1tH9Rx{<^@5*w&7jV?iAs}Ko1{Q@jKu5=m+IsFpO zdXU&J#5^qLW?vy8r!Pb*cg_%jBSQF4g9=1^r1bqvDlTEHwl7hI2CPEZ38nW@L++q3 zNxIseQ%5#6)}S7BNIhw8Iw8g~N2i(fNFg?%5o_?vDRZC7MlE73ou=BwLS&*6Rk(24 z)Z!&Ej4nnaP$h(iCtHf2&KM2Z+XiZMnA6`Q#1!_l1SdaLdJL0pBr%scI?V1LH$*l& zGW#>7p9&JPFNV;+3X#CmFTy7z+$5T*k=g5_uL&`kXIq35=aeobA$wcj>!OD*wsU0i z-QfD1GfqNIUnV)2G+c;8-cidC`Gv_L?}mFhg;!0|Bq8`FKpEPR zrYu9r*W5L_+{bO`$6AbrZWLlFr=tY5-zi-|LQ}&M4bw3#L_Fq^`QryOW9LZ>7Q#MT NJ$ng>WZoXj@h|95oGJhS diff --git a/test/data/las/last_60_pts.laz b/test/data/las/last_60_pts.laz deleted file mode 100644 index 9f052114819600efd9cd53b446f36d0762f2d8ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1488 zcmeZq40dB+5MV$Dj4VEm!6o_mImHSZ$(jmDl?p|P1(_*1iN(eF3huc{9t=RH5fCeA zl%*CGXXfWA7#SKE8d+$-L-NGQ^$%w3vDocVn#xno+`g*k za!dEKL|^OHZ@2&Mnru z8`z}zEbl#8U3VZ`efczw>o=Agt@s?f^_z^O%x(WvzW>vjyPE3z>yQ7L5*2@rxAe;8 zqjy`o>cmsZ{%RB{-a39p$$f6a|1%et6xrOV%@SBDATjfupWmYJdHk9YO#PJd{m%m!-ek?R5#rEpvCNyT!}ND6jKx`fE$xh38&-Ox94(>-ul?(fhwwbn>fJ zDq1@f4jr3%>&H6Piy=lk4=%Mn5ncW3TlVv`|Ce^KCRS}Wl5Y#2W>a+Wr~21>)`zAj zTfGUF)6~_UG~coQ#97PAw`<>g;;GfhW$0hEL*}}~DRaS~6-%Zk*X`%j_2v`3Uj4*P zZ|9>~^*p);K=TVl!CbA{~*Qee|u>-j%yDwo2twsFa}W&M~|c71Jj zR(QHXNS30NQ$~HP^z-oH-kfXx_nrL9frfm83vH zLP;p?cS}EZ(QZ4ng<9=2Ex6V5V)%Rd_UZ05?zbH%C7i78y7JNS$IWV`nE!pS5*S@O zy#DgKlhx8ak>WKcvwbbCaI1&ux^AxS@GzeYN~8k=<(S|0ITes+beEh(mYheHoJp3P zOV-xfe*6E=C(B%9`Ygz#xNzVib{C%>3{}L^o2;D`aSxAhZjFmDiE5GymBg8mvMr)4z)jWw)^gWE9pSaj0QKdd#ax zlUTv1+9=9X@;l}K@FR!_jCWkTrHs2Ij{XyMDH6DYBs}X}ye6?q0(6%~`!0JHNsJ>- G8GiwdH|b{p diff --git a/test/data/las/next_12_pts.laz b/test/data/las/next_12_pts.laz deleted file mode 100644 index 1e56d6a5c1a8cb6371599db62864f244eafb7827..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 741 zcmeZq40dB+5MV$Dj4VEm!6o_mImHSZ$(jmDl?p|P1(_*1iN(eF3huc{9t=RH5fCeA zl%*CGXXfWA7#SKE8d+$-j`1IS}yxB>Ob{N;cC*GMxn)KvcYFU;V{z}vJ;CTHpYnR{#b9Ge)r7^X4Al>DpK zDmg9pzjD3ViyJCwpQqdG%zUo%A<;rZ!$zMiL~{29%{M~7rUJvhPhj=a%o>6ASH^ +#include + +#include +#include +#include +#include +#include + +using namespace copc; +using namespace std; + +TEST_CASE("COPC Extent", "[CopcExtent]") +{ + CopcExtent extent(-numeric_limits::max(), numeric_limits::max(), 5, 10); + + REQUIRE(extent.minimum == -numeric_limits::max()); + REQUIRE(extent.maximum == numeric_limits::max()); + REQUIRE(extent.mean == 5); + REQUIRE(extent.var == 10); + + CopcExtent other(-numeric_limits::max(), numeric_limits::max(), 5, 10); + REQUIRE(extent == other); + + other = CopcExtent(-numeric_limits::max(), 5); + + REQUIRE(extent != other); + + REQUIRE_THROWS(CopcExtent(1, 0)); +} + +TEST_CASE("COPC Extents", "[CopcExtents]") +{ + + int8_t point_format_id{7}; + uint16_t num_eb_items{5}; + + SECTION("Constructor") + { + // Empty constructor + { + CopcExtents extents{point_format_id, num_eb_items}; + REQUIRE(extents.PointFormatId() == point_format_id); + REQUIRE(extents.ExtraBytes().size() == num_eb_items); + } + // Vlr Constructor + { + auto vlr = las::CopcExtentsVlr(); + vlr.items.resize(CopcExtents::NumberOfExtents(point_format_id, num_eb_items) + 3, {0, 0}); + CopcExtents extents{vlr, point_format_id, num_eb_items}; + REQUIRE(extents.PointFormatId() == point_format_id); + REQUIRE(extents.ExtraBytes().size() == num_eb_items); + REQUIRE(extents.Intensity()->minimum == 0); + REQUIRE(extents.Intensity()->maximum == 0); + REQUIRE(*extents.Intensity() == CopcExtent(0, 0)); // Test == operator + REQUIRE(*extents.Intensity() != CopcExtent(0, 1)); // Test != operator + } + // Point format checks + REQUIRE_THROWS(CopcExtents(5)); + REQUIRE_THROWS(CopcExtents(9)); + } + + SECTION("Default Extents") + { + string file_path = "writer_test.copc.laz"; + { + las::EbVlr eb_vlr(2); + eb_vlr.items[0].data_type = 29; // byte size 12 + eb_vlr.items[1].data_type = 29; // byte size 12 + + CopcConfigWriter cfg(7, {}, {}, {}, eb_vlr); + FileWriter writer(file_path, cfg); + + auto extents = writer.CopcConfig()->CopcExtents(); + REQUIRE(extents->PointFormatId() == 7); + REQUIRE(extents->HasExtendedStats() == false); + REQUIRE(extents->ExtraBytes().size() == 2); + + writer.Close(); + } + { + FileReader reader(file_path); + + auto extents = reader.CopcConfig().CopcExtents(); + + REQUIRE(extents.PointFormatId() == 7); + REQUIRE(extents.HasExtendedStats() == false); + REQUIRE(extents.ExtraBytes().size() == 2); + + REQUIRE(extents.Intensity()->minimum == 0); + REQUIRE(extents.Classification()->minimum == 0); + + for (const auto &extent : extents.Extents()) + { + REQUIRE(extent->minimum == 0); + REQUIRE(extent->maximum == 0); + } + } + } + + SECTION("Set Extents") + { + string file_path = "writer_test.copc.laz"; + { + las::EbVlr eb_vlr(1); + eb_vlr.items[0].data_type = 29; // byte size 12 + + CopcConfigWriter cfg(7, {}, {}, {}, eb_vlr, true); + FileWriter writer(file_path, cfg); + + auto extents = writer.CopcConfig()->CopcExtents(); + + REQUIRE(extents->HasExtendedStats() == true); + + extents->Intensity()->minimum = -numeric_limits::max(); + extents->Intensity()->maximum = numeric_limits::max(); + extents->Intensity()->mean = 15; + extents->Intensity()->var = 5; + + extents->Classification()->minimum = 0; + extents->Classification()->maximum = 255; + + extents->ExtraBytes()[0]->minimum = -numeric_limits::max(); + extents->ExtraBytes()[0]->maximum = numeric_limits::max(); + extents->ExtraBytes()[0]->mean = 566; + extents->ExtraBytes()[0]->var = 158; + + // Vector accessor + extents->Extents()[7]->minimum = 78; + extents->Extents()[7]->maximum = 79; + + extents->Extents()[8]->minimum = 80; + extents->Extents()[8]->maximum = 81; + + writer.Close(); + } + { + FileReader reader(file_path); + + auto extents = reader.CopcConfig().CopcExtents(); + + REQUIRE(extents.Intensity()->minimum == -numeric_limits::max()); + REQUIRE(extents.Intensity()->maximum == numeric_limits::max()); + REQUIRE(extents.Intensity()->mean == 15); + REQUIRE(extents.Intensity()->var == 5); + + REQUIRE(*extents.Classification() == CopcExtent(0, 255)); + REQUIRE(*extents.Classification() == CopcExtent(0, 255, 0, 1)); + + REQUIRE(extents.ExtraBytes()[0]->minimum == -numeric_limits::max()); + REQUIRE(extents.ExtraBytes()[0]->maximum == numeric_limits::max()); + REQUIRE(extents.ExtraBytes()[0]->mean == 566); + REQUIRE(extents.ExtraBytes()[0]->var == 158); + + // Vector accessor + REQUIRE(*extents.UserData() == CopcExtent(78, 79)); + + REQUIRE(extents.Extents()[8]->minimum == 80); + REQUIRE(extents.Extents()[8]->maximum == 81); + } + } + + SECTION("ToCopcExtentVlr") + { + + CopcExtents extents{point_format_id, num_eb_items}; + + auto vlr = extents.ToLazPerf({}, {}, {}); + REQUIRE(vlr.items.size() == CopcExtents::NumberOfExtents(point_format_id, num_eb_items) + 3); + } + + SECTION("Get/Set Extents") + { + CopcExtents extents{point_format_id, num_eb_items}; + + for (const auto &extent : extents.Extents()) + { + extent->minimum = 1; + extent->maximum = 1; + } + + for (const auto &extent : extents.Extents()) + { + REQUIRE(extent->minimum == 1); + REQUIRE(extent->maximum == 1); + } + } + // TODO[Leo]: Add all Extents functions to tests +} diff --git a/test/extents_test.py b/test/extents_test.py new file mode 100644 index 00000000..567d89a4 --- /dev/null +++ b/test/extents_test.py @@ -0,0 +1,128 @@ +import pytest +from sys import float_info +import copclib as copc + + +def test_copc_extents(): + + point_format_id = 7 + num_eb_items = 3 + + #### Constructor #### + + # Empty Constructor + + extents = copc.CopcExtents(point_format_id, num_eb_items) + assert extents.point_format_id == point_format_id + assert len(extents.extra_bytes) == num_eb_items + + # Point format checks + with pytest.raises(RuntimeError): + copc.CopcExtents(5) + copc.CopcExtents(9) + + #### Default Extents #### + + file_path = "writer_test.copc.laz" + + eb_vlr = copc.EbVlr(num_eb_items) + cfg = copc.CopcConfigWriter(point_format_id, extra_bytes_vlr=eb_vlr) + writer = copc.FileWriter(file_path, cfg) + + extents = writer.copc_config.copc_extents + assert extents.point_format_id == 7 + assert len(extents.extra_bytes) == 3 + + writer.Close() + + reader = copc.FileReader(file_path) + + extents = reader.copc_config.copc_extents + + assert extents.point_format_id == 7 + assert extents.has_extended_stats == False + assert len(extents.extra_bytes) == 3 + + assert extents.classification.minimum == 0 + assert extents.intensity.minimum == 0 + + for extent in extents.extents: + assert extent.minimum == 0 + assert extent.maximum == 0 + + #### Set Extents #### + cfg = copc.CopcConfigWriter( + point_format_id, extra_bytes_vlr=eb_vlr, has_extended_stats=True + ) + writer = copc.FileWriter(file_path, cfg) + + extents = writer.copc_config.copc_extents + + assert extents.has_extended_stats == True + + extents.classification.minimum = -float_info.max + extents.classification.maximum = float_info.max + extents.classification.mean = 15 + extents.classification.var = 5 + + extents.intensity = (-10, 5) # Tuple implicit conversion + + extents.extra_bytes[0].minimum = -float_info.max + extents.extra_bytes[0].maximum = float_info.max + extents.extra_bytes[0].mean = 566 + extents.extra_bytes[0].var = 158 + + writer.Close() + + reader = copc.FileReader(file_path) + + extents = reader.copc_config.copc_extents + + assert extents.classification.minimum == -float_info.max + assert extents.classification.maximum == float_info.max + assert extents.classification.mean == 15 + assert extents.classification.var == 5 + + assert extents.intensity == (-10, 5, 0, 1) # Tuple implicit conversion + assert extents.intensity == (-10, 5) # Tuple implicit conversion + + assert extents.extra_bytes[0].minimum == -float_info.max + assert extents.extra_bytes[0].maximum == float_info.max + assert extents.extra_bytes[0].mean == 566 + assert extents.extra_bytes[0].var == 158 + + #### Extra byte list setter #### + writer = copc.FileWriter(file_path, cfg) + extents = writer.copc_config.copc_extents + + with pytest.raises(RuntimeError): + extents.extra_bytes = [(0, 1), (2, 3)] + extents.extra_bytes = [(0, 1), (2, 3), (4, 5), (6, 7)] + + extents.extra_bytes = [(0, 1), (2, 3), (4, 5)] + + writer.Close() + + reader = copc.FileReader(file_path) + + extents = reader.copc_config.copc_extents + + assert extents.extra_bytes[0].minimum == 0 + assert extents.extra_bytes[0].maximum == 1 + + assert extents.extra_bytes[1].minimum == 2 + assert extents.extra_bytes[1].maximum == 3 + + assert extents.extra_bytes[2].minimum == 4 + assert extents.extra_bytes[2].maximum == 5 + + #### Get/Set Extents List #### + extents = copc.CopcExtents(point_format_id, num_eb_items) + + for extent in extents.extents: + extent.minimum = 1 + extent.maximum = 1 + + for extent in extents.extents: + assert extent.minimum == 1 + assert extent.maximum == 1 diff --git a/test/hierarchy_test.cpp b/test/hierarchy_test.cpp index 72aa7061..3c03a281 100644 --- a/test/hierarchy_test.cpp +++ b/test/hierarchy_test.cpp @@ -37,26 +37,3 @@ TEST_CASE("Packing Test", "[Hierarchy] ") REQUIRE(point_data_read == point_data_write); } } - -// -// TEST_CASE("LoadChildren Checks", "[Hierarchy]") -//{ -// fstream in_stream; -// in_stream.open("autzen-classified.copc.laz", ios::in | ios::binary); -// io::CopcReader reader(in_stream); -// -// auto copc = reader.GetCopcHeader(); -// -// auto rootPage = reader.ReadPage(copc.root_hier_offset, copc.root_hier_size); -// -// Hierarchy hier = Hierarchy(&reader); -// -// auto hier_entry = hier.GetKey(VoxelKey(0, 0, 0, 0)); -// REQUIRE(hier.LoadChildren(hier_entry) == 4); -// hier_entry = hier.GetKey(VoxelKey(1, 0, 0, 0)); -// REQUIRE(hier.LoadChildren(hier_entry) == 4); -// hier_entry = hier.GetKey(VoxelKey(4, 11, 9, 0)); -// REQUIRE(hier.LoadChildren(hier_entry) == 0); -// hier_entry = hier.GetKey(VoxelKey(-1, -1, -1, -1)); -// REQUIRE(hier.LoadChildren(hier_entry) == 0); -//} diff --git a/test/key_test.cpp b/test/key_test.cpp index 3041dfb8..eb6b636c 100644 --- a/test/key_test.cpp +++ b/test/key_test.cpp @@ -28,9 +28,9 @@ TEST_CASE("GetParent Checks", "[Key]") REQUIRE(VoxelKey(4, 5, 6, 13).GetParent() == VoxelKey(3, 2, 3, 6)); REQUIRE(VoxelKey(3, 2, 3, 6).GetParent() == VoxelKey(2, 1, 1, 3)); - REQUIRE(!(VoxelKey(3, 2, 3, 6).GetParent() == VoxelKey::BaseKey())); + REQUIRE(VoxelKey(3, 2, 3, 6).GetParent() != VoxelKey::RootKey()); - REQUIRE(VoxelKey(1, 1, 1, 1).GetParent() == VoxelKey::BaseKey()); + REQUIRE(VoxelKey(1, 1, 1, 1).GetParent() == VoxelKey::RootKey()); REQUIRE(VoxelKey(1, 1, 1, -1).GetParent() == VoxelKey::InvalidKey()); REQUIRE(VoxelKey(1, 1, 1, -1).GetParent().IsValid() == false); @@ -39,7 +39,7 @@ TEST_CASE("GetParent Checks", "[Key]") TEST_CASE("GetChildren Checks", "[Key]") { - auto key = VoxelKey::BaseKey(); + auto key = VoxelKey::RootKey(); auto children = key.GetChildren(); for (int i = 0; i < 8; i++) REQUIRE(key.Bisect(i) == children[i]); @@ -55,12 +55,12 @@ TEST_CASE("GetChildren Checks", "[Key]") TEST_CASE("IsChild Checks", "[Key]") { - REQUIRE(VoxelKey(-1, -1, -1, -1).ChildOf(VoxelKey::BaseKey()) == false); - REQUIRE(VoxelKey::BaseKey().ChildOf(VoxelKey::InvalidKey()) == false); + REQUIRE(VoxelKey(-1, -1, -1, -1).ChildOf(VoxelKey::RootKey()) == false); + REQUIRE(VoxelKey::RootKey().ChildOf(VoxelKey::InvalidKey()) == false); REQUIRE(VoxelKey(4, 4, 6, 12).ChildOf(VoxelKey(3, 2, 3, 6))); REQUIRE(VoxelKey(3, 2, 3, 6).ChildOf(VoxelKey(2, 1, 1, 3))); - REQUIRE(VoxelKey(3, 2, 3, 6).ChildOf(VoxelKey::BaseKey())); + REQUIRE(VoxelKey(3, 2, 3, 6).ChildOf(VoxelKey::RootKey())); REQUIRE(!VoxelKey(4, 4, 6, 12).ChildOf(VoxelKey(3, 4, 8, 6))); REQUIRE(!VoxelKey(3, 2, 3, 6).ChildOf(VoxelKey(2, 2, 2, 2))); @@ -117,8 +117,7 @@ TEST_CASE("VoxelKey Spatial functions", "[VoxelKey]") REQUIRE(VoxelKey(0, 0, 0, 0).Contains(header, Box(0, 0, 0, 1, 1, 1))); REQUIRE(!VoxelKey(2, 0, 0, 0).Contains(header, Box(0, 0, 0, 1, 1, 1))); // A box contains itself - REQUIRE( - VoxelKey(0, 0, 0, 0).Contains(header, Box(0, 0, 0, header.GetSpan(), header.GetSpan(), header.GetSpan()))); + REQUIRE(VoxelKey(0, 0, 0, 0).Contains(header, Box(0, 0, 0, header.Span(), header.Span(), header.Span()))); } SECTION("Contains vector") @@ -131,8 +130,7 @@ TEST_CASE("VoxelKey Spatial functions", "[VoxelKey]") { REQUIRE(VoxelKey(1, 1, 1, 1).Within(header, Box(0.99, 0.99, 0.99, 2.01, 2.01, 2.01))); // A box is within itself - REQUIRE( - VoxelKey(0, 0, 0, 0).Within(header, Box(0, 0, 0, header.GetSpan(), header.GetSpan(), header.GetSpan()))); + REQUIRE(VoxelKey(0, 0, 0, 0).Within(header, Box(0, 0, 0, header.Span(), header.Span(), header.Span()))); } SECTION("Crosses") @@ -142,8 +140,7 @@ TEST_CASE("VoxelKey Spatial functions", "[VoxelKey]") // Crossing on all axis REQUIRE(VoxelKey(1, 0, 0, 0).Crosses(header, Box(0.5, 0.5, 0.5, 1.5, 1.5, 1.5))); // Within - REQUIRE( - !VoxelKey(0, 0, 0, 0).Crosses(header, Box(0, 0, 0, header.GetSpan(), header.GetSpan(), header.GetSpan()))); + REQUIRE(!VoxelKey(0, 0, 0, 0).Crosses(header, Box(0, 0, 0, header.Span(), header.Span(), header.Span()))); // Outside REQUIRE(!VoxelKey(1, 0, 0, 0).Crosses(header, Box(1.1, 1.1, 1.1, 2, 2, 2))); } diff --git a/test/key_test.py b/test/key_test.py index 6f7d9a59..f534150d 100644 --- a/test/key_test.py +++ b/test/key_test.py @@ -16,37 +16,36 @@ def test_key_validity(): def test_key_operators(): - assert copc.VoxelKey(0, 0, 0, 0) == copc.VoxelKey(0, 0, 0, 0) - assert copc.VoxelKey(-1, -1, -1, -1) == copc.VoxelKey(-1, -1, -1, -1) - assert copc.VoxelKey(0, 0, 0, 0) != copc.VoxelKey(1, 1, 1, 1) + assert copc.VoxelKey(0, 0, 0, 0) == (0, 0, 0, 0) + assert copc.VoxelKey(-1, -1, -1, -1) == (-1, -1, -1, -1) + assert copc.VoxelKey(0, 0, 0, 0) != (1, 1, 1, 1) def test_get_children(): - key = copc.VoxelKey.BaseKey() + key = copc.VoxelKey.RootKey() children = key.GetChildren() for i in range(8): assert key.Bisect(i) == children[i] - assert children[0] == copc.VoxelKey(1, 0, 0, 0) - assert children[1] == copc.VoxelKey(1, 1, 0, 0) - assert children[2] == copc.VoxelKey(1, 0, 1, 0) - assert children[3] == copc.VoxelKey(1, 1, 1, 0) - assert children[4] == copc.VoxelKey(1, 0, 0, 1) - assert children[5] == copc.VoxelKey(1, 1, 0, 1) - assert children[6] == copc.VoxelKey(1, 0, 1, 1) - assert children[7] == copc.VoxelKey(1, 1, 1, 1) + assert children[0] == (1, 0, 0, 0) + assert children[1] == (1, 1, 0, 0) + assert children[2] == (1, 0, 1, 0) + assert children[3] == (1, 1, 1, 0) + assert children[4] == (1, 0, 0, 1) + assert children[5] == (1, 1, 0, 1) + assert children[6] == (1, 0, 1, 1) + assert children[7] == (1, 1, 1, 1) def test_get_parent(): assert copc.VoxelKey(-1, -1, -1, -1).GetParent().IsValid() is False - assert copc.VoxelKey(4, 4, 6, 12).GetParent() == copc.VoxelKey(3, 2, 3, 6) - assert copc.VoxelKey(4, 5, 6, 13).GetParent() == copc.VoxelKey(3, 2, 3, 6) - assert copc.VoxelKey(3, 2, 3, 6).GetParent() == copc.VoxelKey(2, 1, 1, 3) + assert copc.VoxelKey(4, 4, 6, 12).GetParent() == (3, 2, 3, 6) + assert copc.VoxelKey(4, 5, 6, 13).GetParent() == (3, 2, 3, 6) + assert copc.VoxelKey(3, 2, 3, 6).GetParent() == (2, 1, 1, 3) - # TODO[Leo]: Check if there is a way to avoid self in function definition - assert not copc.VoxelKey(3, 2, 3, 6).GetParent() == copc.VoxelKey.BaseKey() + assert copc.VoxelKey(3, 2, 3, 6).GetParent() != copc.VoxelKey.RootKey() - assert copc.VoxelKey(1, 1, 1, 1).GetParent() == copc.VoxelKey.BaseKey() + assert copc.VoxelKey(1, 1, 1, 1).GetParent() == copc.VoxelKey.RootKey() assert copc.VoxelKey(1, 1, 1, -1).GetParent() == copc.VoxelKey.InvalidKey() assert copc.VoxelKey(1, 1, 1, -1).GetParent().IsValid() is False @@ -55,17 +54,17 @@ def test_get_parent(): def test_is_child(): assert ( - copc.VoxelKey(-1, -1, -1, -1).ChildOf(parent_key=copc.VoxelKey.BaseKey()) + copc.VoxelKey(-1, -1, -1, -1).ChildOf(parent_key=copc.VoxelKey.RootKey()) is False ) - assert copc.VoxelKey.BaseKey().ChildOf(copc.VoxelKey.InvalidKey()) is False + assert copc.VoxelKey.RootKey().ChildOf(copc.VoxelKey.InvalidKey()) is False - assert copc.VoxelKey(4, 4, 6, 12).ChildOf(copc.VoxelKey(3, 2, 3, 6)) - assert copc.VoxelKey(3, 2, 3, 6).ChildOf(copc.VoxelKey(2, 1, 1, 3)) - assert copc.VoxelKey(3, 2, 3, 6).ChildOf(copc.VoxelKey.BaseKey()) + assert copc.VoxelKey(4, 4, 6, 12).ChildOf((3, 2, 3, 6)) + assert copc.VoxelKey(3, 2, 3, 6).ChildOf((2, 1, 1, 3)) + assert copc.VoxelKey(3, 2, 3, 6).ChildOf(copc.VoxelKey.RootKey()) - assert not copc.VoxelKey(4, 4, 6, 12).ChildOf(copc.VoxelKey(3, 4, 8, 6)) - assert not copc.VoxelKey(3, 2, 3, 6).ChildOf(copc.VoxelKey(2, 2, 2, 2)) + assert not copc.VoxelKey(4, 4, 6, 12).ChildOf((3, 4, 8, 6)) + assert not copc.VoxelKey(3, 2, 3, 6).ChildOf((2, 2, 2, 2)) def test_get_parents(): @@ -120,7 +119,7 @@ def test_key_spatial_functions(): assert not copc.VoxelKey(2, 0, 0, 0).Contains(header, (0, 0, 0, 1, 1, 1)) ## A box contains itself assert copc.VoxelKey(0, 0, 0, 0).Contains( - header, (0, 0, 0, header.GetSpan(), header.GetSpan(), header.GetSpan()) + header, (0, 0, 0, header.Span(), header.Span(), header.Span()) ) # Contains vector @@ -133,7 +132,7 @@ def test_key_spatial_functions(): ) ## A box is within itself assert copc.VoxelKey(0, 0, 0, 0).Within( - header, (0, 0, 0, header.GetSpan(), header.GetSpan(), header.GetSpan()) + header, (0, 0, 0, header.Span(), header.Span(), header.Span()) ) # Crosses @@ -143,7 +142,7 @@ def test_key_spatial_functions(): assert copc.VoxelKey(1, 0, 0, 0).Crosses(header, (0.5, 0.5, 0.5, 1.5, 1.5, 1.5)) ## Within assert not copc.VoxelKey(0, 0, 0, 0).Crosses( - header, (0, 0, 0, header.GetSpan(), header.GetSpan(), header.GetSpan()) + header, (0, 0, 0, header.Span(), header.Span(), header.Span()) ) ## Outside assert not copc.VoxelKey(1, 0, 0, 0).Crosses(header, (1.1, 1.1, 1.1, 2, 2, 2)) diff --git a/test/las_header_test.cpp b/test/las_header_test.cpp index 3c11bf8b..5c9df4bd 100644 --- a/test/las_header_test.cpp +++ b/test/las_header_test.cpp @@ -11,48 +11,50 @@ TEST_CASE("Test constructor and conversions", "[LasHeader]") GIVEN("A valid file_path") { FileReader reader("autzen-classified.copc.laz"); - auto las_header = reader.GetLasHeader(); - auto lazperf_header = las_header.ToLazPerf(); - auto las_header_orig = las::LasHeader::FromLazPerf(lazperf_header); + auto las_header = reader.CopcConfig().LasHeader(); + auto lazperf_header = + las_header.ToLazPerf(las_header.PointOffset(), las_header.PointCount(), las_header.EvlrOffset(), + las_header.EvlrCount(), !reader.CopcConfig().Wkt().empty(), las_header.EbByteSize(), + reader.CopcConfig().CopcExtents().HasExtendedStats()); + // Correct the bitshift happening in ToLazPerf for test purpose + lazperf_header.point_format_id = las_header.PointFormatId(); + auto las_header_origin = las::LasHeader::FromLazPerf(lazperf_header); - REQUIRE(las_header_orig.file_source_id == las_header.file_source_id); - REQUIRE(las_header_orig.global_encoding == las_header.global_encoding); - REQUIRE(las_header_orig.GUID() == las_header.GUID()); - REQUIRE(las_header_orig.version_major == las_header.version_major); - REQUIRE(las_header_orig.version_minor == las_header.version_minor); - REQUIRE(las_header_orig.SystemIdentifier() == las_header.SystemIdentifier()); - REQUIRE(las_header_orig.GeneratingSoftware() == las_header.GeneratingSoftware()); - REQUIRE(las_header_orig.creation_day == las_header.creation_day); - REQUIRE(las_header_orig.creation_year == las_header.creation_year); - REQUIRE(las_header_orig.header_size == las_header.header_size); - REQUIRE(las_header_orig.point_offset == las_header.point_offset); - REQUIRE(las_header_orig.vlr_count == las_header.vlr_count); - REQUIRE(las_header_orig.point_format_id == las_header.point_format_id); - REQUIRE(las_header_orig.point_record_length == las_header.point_record_length); - REQUIRE(las_header_orig.point_count == las_header.point_count); - REQUIRE(las_header_orig.points_by_return == las_header.points_by_return); - REQUIRE(las_header_orig.scale.x == las_header.scale.x); - REQUIRE(las_header_orig.scale.y == las_header.scale.y); - REQUIRE(las_header_orig.scale.z == las_header.scale.z); - REQUIRE(las_header_orig.offset.x == las_header.offset.x); - REQUIRE(las_header_orig.offset.y == las_header.offset.y); - REQUIRE(las_header_orig.offset.z == las_header.offset.z); - REQUIRE(las_header_orig.max == las_header.max); - REQUIRE(las_header_orig.min == las_header.min); - REQUIRE(las_header_orig.wave_offset == las_header.wave_offset); - REQUIRE(las_header_orig.evlr_offset == las_header.evlr_offset); - REQUIRE(las_header_orig.evlr_count == las_header.evlr_count); - REQUIRE(las_header_orig.points_by_return_14 == las_header.points_by_return_14); + REQUIRE(las_header_origin.file_source_id == las_header.file_source_id); + REQUIRE(las_header_origin.global_encoding == las_header.global_encoding); + REQUIRE(las_header_origin.GUID() == las_header.GUID()); + REQUIRE(las_header_origin.SystemIdentifier() == las_header.SystemIdentifier()); + REQUIRE(las_header_origin.GeneratingSoftware() == las_header.GeneratingSoftware()); + REQUIRE(las_header_origin.creation_day == las_header.creation_day); + REQUIRE(las_header_origin.creation_year == las_header.creation_year); + REQUIRE(las_header_origin.PointFormatId() == las_header.PointFormatId()); + REQUIRE(las_header_origin.PointRecordLength() == las_header.PointRecordLength()); + REQUIRE(las_header_origin.PointOffset() == las_header.PointOffset()); + REQUIRE(las_header_origin.PointCount() == las_header.PointCount()); + REQUIRE(las_header_origin.points_by_return == las_header.points_by_return); + REQUIRE(las_header_origin.Scale().x == las_header.Scale().x); + REQUIRE(las_header_origin.Scale().y == las_header.Scale().y); + REQUIRE(las_header_origin.Scale().z == las_header.Scale().z); + REQUIRE(las_header_origin.Offset().x == las_header.Offset().x); + REQUIRE(las_header_origin.Offset().y == las_header.Offset().y); + REQUIRE(las_header_origin.Offset().z == las_header.Offset().z); + REQUIRE(las_header_origin.max == las_header.max); + REQUIRE(las_header_origin.min == las_header.min); + // REQUIRE(las_header_origin.VlrCount() == las_header.VlrCount()); //TODO Allow copy of VLRs + REQUIRE(las_header_origin.EvlrOffset() == las_header.EvlrOffset()); + REQUIRE(las_header_origin.EvlrCount() == las_header.EvlrCount()); + + las_header_origin.ToString(); } } -TEST_CASE("GetBounds", "[LasHeader]") +TEST_CASE("Bounds", "[LasHeader]") { GIVEN("A valid file_path") { FileReader reader("autzen-classified.copc.laz"); - auto las_header = reader.GetLasHeader(); - auto box = las_header.GetBounds(); + auto las_header = reader.CopcConfig().LasHeader(); + auto box = las_header.Bounds(); REQUIRE(box.x_min == las_header.min.x); REQUIRE(box.y_min == las_header.min.y); REQUIRE(box.z_min == las_header.min.z); diff --git a/test/las_header_test.py b/test/las_header_test.py index 875bb172..a2f79545 100644 --- a/test/las_header_test.py +++ b/test/las_header_test.py @@ -4,11 +4,13 @@ def test_get_bounds(): reader = copc.FileReader("autzen-classified.copc.laz") - las_header = reader.GetLasHeader() - box = las_header.GetBounds() + las_header = reader.copc_config.las_header + box = las_header.Bounds() assert box.x_min == las_header.min.x assert box.y_min == las_header.min.y assert box.z_min == las_header.min.z assert box.x_max == las_header.max.x assert box.y_max == las_header.max.y assert box.z_max == las_header.max.z + + str(las_header) diff --git a/test/pickle_test.py b/test/pickle_test.py index 4920bfee..6ceab4b6 100644 --- a/test/pickle_test.py +++ b/test/pickle_test.py @@ -37,20 +37,17 @@ def test_node(): def test_las_header(): reader = copc.FileReader("autzen-classified.copc.laz") - las_header = reader.GetLasHeader() + las_header = reader.copc_config.las_header 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 @@ -61,8 +58,5 @@ def test_las_header(): 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 diff --git a/test/point_test.cpp b/test/point_test.cpp index 0d4e3ab1..8614691d 100644 --- a/test/point_test.cpp +++ b/test/point_test.cpp @@ -15,180 +15,18 @@ TEST_CASE("Point tests", "[Point]") SECTION("Point Format Initialization") { - auto point = Point(0); + auto point = Point(6); - REQUIRE(point.HasExtendedPoint() == false); - REQUIRE(point.HasGPSTime() == false); - REQUIRE(point.HasRGB() == false); - REQUIRE(point.HasNIR() == false); + REQUIRE(point.HasRgb() == false); + REQUIRE(point.HasNir() == false); auto point_ext = Point(8); - REQUIRE(point_ext.HasExtendedPoint() == true); - REQUIRE(point_ext.HasGPSTime() == true); - REQUIRE(point_ext.HasRGB() == true); - REQUIRE(point_ext.HasNIR() == true); - } - - SECTION("Point with format LAS 1.0 Test") - { - auto point0 = Point(0); - // Position - point0.UnscaledX(std::numeric_limits::max()); - point0.UnscaledY(std::numeric_limits::max()); - point0.UnscaledZ(std::numeric_limits::max()); - REQUIRE(point0.UnscaledX() == std::numeric_limits::max()); - REQUIRE(point0.UnscaledY() == std::numeric_limits::max()); - REQUIRE(point0.UnscaledZ() == std::numeric_limits::max()); - - // Intensity - point0.Intensity(std::numeric_limits::max()); - REQUIRE(point0.Intensity() == std::numeric_limits::max()); - - // Return Number - point0.ReturnNumber(7); - REQUIRE(point0.ReturnNumber() == 7); - REQUIRE_THROWS(point0.ReturnNumber(8)); - - // Number of Returns - point0.NumberOfReturns(7); - REQUIRE(point0.NumberOfReturns() == 7); - REQUIRE_THROWS(point0.NumberOfReturns(8)); - - // Scan Direction - point0.ScanDirectionFlag(true); - REQUIRE(point0.ScanDirectionFlag() == true); - - // Edge of Flight Line - point0.EdgeOfFlightLineFlag(true); - REQUIRE(point0.EdgeOfFlightLineFlag() == true); - - // ReturnsScanDirEofBitField - point0.ReturnsScanDirEofBitFields(172); - REQUIRE(point0.ReturnNumber() == 4); - REQUIRE(point0.NumberOfReturns() == 5); - REQUIRE(point0.ScanDirectionFlag() == false); - REQUIRE(point0.EdgeOfFlightLineFlag() == true); - - // Classification - point0.Classification(31); - REQUIRE(point0.Classification() == 31); - REQUIRE_THROWS(point0.Classification(32)); - - // Synthetic - point0.Synthetic(true); - REQUIRE(point0.Synthetic() == true); - - // KeyPoint - point0.KeyPoint(true); - REQUIRE(point0.KeyPoint() == true); - - // Withheld - point0.Withheld(true); - REQUIRE(point0.Withheld() == true); - - // Classification Bitfield - point0.ClassificationBitFields(std::numeric_limits::max()); - REQUIRE(point0.ClassificationBitFields() == std::numeric_limits::max()); - point0.ClassificationBitFields(78); - REQUIRE(point0.Classification() == 14); - REQUIRE(point0.Synthetic() == false); - REQUIRE(point0.KeyPoint() == true); - REQUIRE(point0.Withheld() == false); - - // Scan Angle - point0.ScanAngleRank(90); - REQUIRE(point0.ScanAngleRank() == 90); - REQUIRE(point0.ScanAngle() == 90.0); - REQUIRE_THROWS(point0.ScanAngleRank(91)); - REQUIRE_THROWS(point0.ScanAngleRank(-91)); - - // User Data - point0.UserData(std::numeric_limits::max()); - REQUIRE(point0.UserData() == std::numeric_limits::max()); - - // Point Source ID - point0.PointSourceID(std::numeric_limits::max()); - REQUIRE(point0.PointSourceID() == std::numeric_limits::max()); - - REQUIRE(point0.PointRecordLength() == 20); - - // Checks - REQUIRE_THROWS(point0.ExtendedFlagsBitFields(0)); - REQUIRE_THROWS(point0.ExtendedFlagsBitFields()); - REQUIRE_THROWS(point0.ScannerChannel(0)); - REQUIRE_THROWS(point0.ScannerChannel()); - REQUIRE_THROWS(point0.NIR(std::numeric_limits::max())); - REQUIRE_THROWS(point0.NIR()); - REQUIRE_THROWS(point0.Red(std::numeric_limits::max())); - REQUIRE_THROWS(point0.Red()); - REQUIRE_THROWS(point0.Green(std::numeric_limits::max())); - REQUIRE_THROWS(point0.Green()); - REQUIRE_THROWS(point0.Blue(std::numeric_limits::max())); - REQUIRE_THROWS(point0.Blue()); - REQUIRE_THROWS(point0.GPSTime(std::numeric_limits::max())); - REQUIRE_THROWS(point0.GPSTime()); - REQUIRE_THROWS(point0.ExtendedScanAngle(std::numeric_limits::max())); - REQUIRE_THROWS(point0.ExtendedScanAngle()); - REQUIRE_THROWS(point0.Overlap(true)); - REQUIRE_THROWS(point0.Overlap()); - REQUIRE_THROWS(point0.ExtendedReturnsBitFields(std::numeric_limits::max())); - REQUIRE_THROWS(point0.ExtendedReturnsBitFields()); - REQUIRE_THROWS(point0.ExtendedFlagsBitFields(std::numeric_limits::max())); - REQUIRE_THROWS(point0.ExtendedFlagsBitFields()); - REQUIRE_NOTHROW(point0.ToString()); - - auto point1 = Point(1); - - point1.GPSTime(std::numeric_limits::max()); - REQUIRE(point1.GPSTime() == std::numeric_limits::max()); - REQUIRE(point1.PointRecordLength() == 28); - - REQUIRE_THROWS(point0.Red(std::numeric_limits::max())); - REQUIRE_THROWS(point0.Red()); - REQUIRE_THROWS(point0.Green(std::numeric_limits::max())); - REQUIRE_THROWS(point0.Green()); - REQUIRE_THROWS(point0.Blue(std::numeric_limits::max())); - REQUIRE_THROWS(point0.Blue()); - REQUIRE_THROWS(point0.NIR(std::numeric_limits::max())); - REQUIRE_THROWS(point0.NIR()); - - auto point2 = Point(2); - REQUIRE(point2.PointRecordLength() == 26); - - point2.Red(std::numeric_limits::max()); - REQUIRE(point2.Red() == std::numeric_limits::max()); - point2.Green(std::numeric_limits::max()); - REQUIRE(point2.Green() == std::numeric_limits::max()); - point2.Blue(std::numeric_limits::max()); - REQUIRE(point2.Blue() == std::numeric_limits::max()); - - point2.RGB(std::numeric_limits::max() / 2, std::numeric_limits::max() / 2, - std::numeric_limits::max() / 2); - REQUIRE(point2.Red() == std::numeric_limits::max() / 2); - REQUIRE(point2.Green() == std::numeric_limits::max() / 2); - REQUIRE(point2.Blue() == std::numeric_limits::max() / 2); - - REQUIRE_THROWS(point2.GPSTime(std::numeric_limits::max())); - REQUIRE_THROWS(point2.GPSTime()); - REQUIRE_THROWS(point2.NIR(std::numeric_limits::max())); - REQUIRE_THROWS(point2.NIR()); - - auto point3 = Point(3); - REQUIRE(point3.PointRecordLength() == 34); - - REQUIRE_NOTHROW(point3.GPSTime(std::numeric_limits::max())); - REQUIRE_NOTHROW(point3.GPSTime()); - REQUIRE_NOTHROW(point3.RGB(std::numeric_limits::max(), std::numeric_limits::max(), - std::numeric_limits::max())); - REQUIRE_NOTHROW(point3.Red()); - REQUIRE_NOTHROW(point3.Green()); - REQUIRE_NOTHROW(point3.Blue()); - REQUIRE_THROWS(point3.NIR(std::numeric_limits::max())); - REQUIRE_THROWS(point3.NIR()); + REQUIRE(point_ext.HasRgb() == true); + REQUIRE(point_ext.HasNir() == true); } - SECTION("Point with format LAS 1.4 Test") + SECTION("Point Test") { auto point6 = Point(6); // Position @@ -204,8 +42,8 @@ TEST_CASE("Point tests", "[Point]") REQUIRE(point6.Intensity() == std::numeric_limits::max()); // Return BitField - point6.ExtendedReturnsBitFields(std::numeric_limits::max()); - REQUIRE(point6.ExtendedReturnsBitFields() == std::numeric_limits::max()); + point6.ReturnsBitField(std::numeric_limits::max()); + REQUIRE(point6.ReturnsBitField() == std::numeric_limits::max()); // Return Number point6.ReturnNumber(0); @@ -222,8 +60,8 @@ TEST_CASE("Point tests", "[Point]") REQUIRE_THROWS(point6.NumberOfReturns(16)); // Flags - point6.ExtendedFlagsBitFields(std::numeric_limits::max()); - REQUIRE(point6.ExtendedFlagsBitFields() == std::numeric_limits::max()); + point6.FlagsBitField(std::numeric_limits::max()); + REQUIRE(point6.FlagsBitField() == std::numeric_limits::max()); // Synthetic point6.Synthetic(false); @@ -275,22 +113,22 @@ TEST_CASE("Point tests", "[Point]") REQUIRE(point6.Classification() == 0); // Scan Angle - point6.ExtendedScanAngle(-30000); - REQUIRE(point6.ExtendedScanAngle() == -30000); - REQUIRE(point6.ScanAngle() == -180.0); - REQUIRE_THROWS(point6.ExtendedScanAngle(-30001)); - point6.ExtendedScanAngle(30000); - REQUIRE(point6.ExtendedScanAngle() == 30000); - REQUIRE(point6.ScanAngle() == 180.0); - REQUIRE_THROWS(point6.ExtendedScanAngle(30001)); + point6.ScanAngle(-30000); + REQUIRE(point6.ScanAngle() == -30000); + REQUIRE(point6.ScanAngleDegrees() == -180.0); + REQUIRE_THROWS(point6.ScanAngle(-30001)); + point6.ScanAngle(30000); + REQUIRE(point6.ScanAngle() == 30000); + REQUIRE(point6.ScanAngleDegrees() == 180.0); + REQUIRE_THROWS(point6.ScanAngle(30001)); // User Data point6.UserData(std::numeric_limits::max()); REQUIRE(point6.UserData() == std::numeric_limits::max()); // Point Source ID - point6.PointSourceID(std::numeric_limits::max()); - REQUIRE(point6.PointSourceID() == std::numeric_limits::max()); + point6.PointSourceId(std::numeric_limits::max()); + REQUIRE(point6.PointSourceId() == std::numeric_limits::max()); // GPS Time point6.GPSTime(std::numeric_limits::max()); @@ -300,59 +138,35 @@ TEST_CASE("Point tests", "[Point]") REQUIRE(point6.PointRecordLength() == 30); // Checks - REQUIRE_THROWS(point6.RGB(std::numeric_limits::max(), std::numeric_limits::max(), + REQUIRE_THROWS(point6.Rgb(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max())); REQUIRE_THROWS(point6.Red()); REQUIRE_THROWS(point6.Green()); REQUIRE_THROWS(point6.Blue()); - REQUIRE_THROWS(point6.NIR(std::numeric_limits::max())); - REQUIRE_THROWS(point6.NIR()); - REQUIRE_THROWS(point6.ScanAngleRank()); - REQUIRE_THROWS(point6.ScanAngleRank(INT8_MAX)); - REQUIRE_THROWS(point6.ReturnsScanDirEofBitFields(std::numeric_limits::max())); - REQUIRE_THROWS(point6.ReturnsScanDirEofBitFields()); - REQUIRE_THROWS(point6.ClassificationBitFields(std::numeric_limits::max())); - REQUIRE_THROWS(point6.ClassificationBitFields()); + REQUIRE_THROWS(point6.Nir(std::numeric_limits::max())); + REQUIRE_THROWS(point6.Nir()); REQUIRE_NOTHROW(point6.ToString()); auto point7 = Point(7); - point7.RGB(std::numeric_limits::max(), std::numeric_limits::max(), + point7.Rgb(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()); REQUIRE(point7.Red() == std::numeric_limits::max()); REQUIRE(point7.Green() == std::numeric_limits::max()); REQUIRE(point7.Blue() == std::numeric_limits::max()); REQUIRE(point7.PointRecordLength() == 36); - REQUIRE_THROWS(point7.NIR(std::numeric_limits::max())); - REQUIRE_THROWS(point7.NIR()); + REQUIRE_THROWS(point7.Nir(std::numeric_limits::max())); + REQUIRE_THROWS(point7.Nir()); auto point8 = Point(8); - point8.NIR(std::numeric_limits::max()); - REQUIRE(point8.NIR() == std::numeric_limits::max()); + point8.Nir(std::numeric_limits::max()); + REQUIRE(point8.Nir() == std::numeric_limits::max()); REQUIRE(point8.PointRecordLength() == 38); } - SECTION("Point conversion Point10") - { - auto point = Point(1); - - REQUIRE(point.GPSTime() == 0); - - point.ToPointFormat(2); - REQUIRE(point.Red() == 0); - REQUIRE(point.Green() == 0); - REQUIRE(point.Blue() == 0); - - point.ToPointFormat(3); - REQUIRE(point.GPSTime() == 0); - REQUIRE(point.Red() == 0); - REQUIRE(point.Green() == 0); - REQUIRE(point.Blue() == 0); - } - - SECTION("Point conversion Point14") + SECTION("Point format conversion") { auto point = Point(6); @@ -378,12 +192,12 @@ TEST_CASE("Point tests", "[Point]") REQUIRE(point.Red() == 0); REQUIRE(point.Green() == 0); REQUIRE(point.Blue() == 0); - REQUIRE(point.NIR() == 0); + REQUIRE(point.Nir() == 0); } - SECTION("Point conversion Point10 to Poin14") + SECTION("Point conversion") { - auto point = Point(0); + auto point = Point(6); point.ReturnNumber(5); point.NumberOfReturns(6); @@ -393,9 +207,9 @@ TEST_CASE("Point tests", "[Point]") point.Synthetic(true); point.KeyPoint(false); point.Withheld(true); - point.ScanAngleRank(45); + point.ScanAngleDegrees(45); - point.ToPointFormat(6); + point.ToPointFormat(7); REQUIRE(point.ReturnNumber() == 5); REQUIRE(point.NumberOfReturns() == 6); @@ -406,11 +220,18 @@ TEST_CASE("Point tests", "[Point]") REQUIRE(point.KeyPoint() == false); REQUIRE(point.Withheld() == true); REQUIRE(point.Overlap() == false); - REQUIRE(point.ExtendedScanAngle() == 7500); + REQUIRE(point.ScanAngleDegrees() == 45); REQUIRE(point.ScannerChannel() == 0); - REQUIRE_THROWS(point.ReturnsScanDirEofBitFields()); + REQUIRE(point.Red() == 0); + REQUIRE(point.Green() == 0); + REQUIRE(point.Blue() == 0); + REQUIRE_THROWS(point.Nir()); - point.ToPointFormat(0); + point.Red(150); + point.Green(200); + point.Blue(250); + + point.ToPointFormat(8); REQUIRE(point.ReturnNumber() == 5); REQUIRE(point.NumberOfReturns() == 6); @@ -420,71 +241,21 @@ TEST_CASE("Point tests", "[Point]") REQUIRE(point.Synthetic() == true); REQUIRE(point.KeyPoint() == false); REQUIRE(point.Withheld() == true); - REQUIRE(point.ScanAngleRank() == 45); - - point.ToPointFormat(6); - - point.ReturnNumber(13); - point.NumberOfReturns(14); - point.Classification(134); - point.ScannerChannel(2); - point.Synthetic(true); - point.KeyPoint(false); - point.Withheld(true); - point.ExtendedScanAngle(28000); - - point.ToPointFormat(0); - - REQUIRE(point.ReturnNumber() == 7); - REQUIRE(point.NumberOfReturns() == 7); - REQUIRE(point.Classification() == 31); - REQUIRE(point.ScanAngleRank() == 90); - REQUIRE_THROWS(point.Overlap()); - REQUIRE_THROWS(point.ScannerChannel()); - REQUIRE_THROWS(point.ExtendedReturnsBitFields()); - REQUIRE_THROWS(point.ExtendedFlagsBitFields()); - - point.ToPointFormat(6); - - REQUIRE(point.ReturnNumber() == 7); - REQUIRE(point.NumberOfReturns() == 7); - REQUIRE(point.Classification() == 31); - REQUIRE(point.ExtendedScanAngle() == 15000); REQUIRE(point.Overlap() == false); + REQUIRE(point.ScanAngleDegrees() == 45); REQUIRE(point.ScannerChannel() == 0); + REQUIRE(point.Red() == 150); + REQUIRE(point.Green() == 200); + REQUIRE(point.Blue() == 250); + REQUIRE(point.Nir() == 0); } SECTION("Operator == and !=") { - // Format 0 - auto point = Point(0); - auto point_other = Point(0); - - REQUIRE(point == point_other); - - // Format 1 - point_other.ToPointFormat(1); - REQUIRE(point != point_other); - point.ToPointFormat(1); - REQUIRE(point == point_other); - - // Format 2 - point_other.ToPointFormat(2); - REQUIRE(point != point_other); - point.ToPointFormat(2); - REQUIRE(point == point_other); - - // Format 3 - point_other.ToPointFormat(3); - REQUIRE(point != point_other); - point.ToPointFormat(3); - REQUIRE(point == point_other); - // Format 6 - point_other.ToPointFormat(6); - REQUIRE(point != point_other); - point.ToPointFormat(6); + auto point = Point(6); + auto point_other = Point(6); REQUIRE(point == point_other); // Format 7 @@ -576,9 +347,9 @@ TEST_CASE("Point tests", "[Point]") point_other.UserData(4); REQUIRE(point == point_other); - point.PointSourceID(4); + point.PointSourceId(4); REQUIRE(point != point_other); - point_other.PointSourceID(4); + point_other.PointSourceId(4); REQUIRE(point == point_other); point.GPSTime(4.0); @@ -601,37 +372,29 @@ TEST_CASE("Point tests", "[Point]") point_other.Blue(4); REQUIRE(point == point_other); - point.NIR(4); + point.Nir(4); REQUIRE(point != point_other); - point_other.NIR(4); + point_other.Nir(4); REQUIRE(point == point_other); } SECTION("Point ExtraByte") { - auto point = Point(0); - REQUIRE(point.PointFormatID() == 0); - REQUIRE(point.PointRecordLength() == 20); + auto point = Point(6); + REQUIRE(point.PointFormatId() == 6); + REQUIRE(point.PointRecordLength() == 30); REQUIRE_THROWS(point.ExtraBytes(std::vector{2, 3, 4})); REQUIRE(point.ExtraBytes().size() == 0); - REQUIRE(point.NumExtraBytes() == 0); + REQUIRE(point.EbByteSize() == 0); - point = Point(0, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 5); - REQUIRE(point.PointFormatID() == 0); - REQUIRE(point.PointRecordLength() == 20 + 5); - REQUIRE(point.NumExtraBytes() == 5); + point = Point(6, {}, {}, 5); + REQUIRE(point.PointFormatId() == 6); + REQUIRE(point.PointRecordLength() == 30 + 5); + REQUIRE(point.EbByteSize() == 5); REQUIRE(point.ExtraBytes().size() == 5); REQUIRE_THROWS(point.ExtraBytes(std::vector{2, 3, 4})); REQUIRE_NOTHROW(point.ExtraBytes(std::vector{2, 3, 4, 5, 6})); REQUIRE(point.ExtraBytes() == std::vector{2, 3, 4, 5, 6}); - - point.ToPointFormat(6); - REQUIRE(point.PointFormatID() == 6); - REQUIRE(point.PointRecordLength() == 30 + 5); - REQUIRE(point.NumExtraBytes() == 5); - REQUIRE(point.ExtraBytes().size() == 5); - REQUIRE(point.ExtraBytes() == std::vector{2, 3, 4, 5, 6}); - REQUIRE_THROWS(point.ExtraBytes(std::vector{2, 3, 4})); } SECTION("Operator =") @@ -644,11 +407,11 @@ TEST_CASE("Point tests", "[Point]") point.GPSTime(4.0); point.Red(4.0); - point.NIR(4.0); + point.Nir(4.0); point.ExtraBytes(std::vector{2, 5}); - auto point_other = Point(0); + auto point_other = Point(6); point_other = point; REQUIRE(point == point_other); @@ -659,64 +422,32 @@ TEST_CASE("Point tests", "[Point]") std::stringstream ss; auto orig_point = - Point(0, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 2); // use two extra bytes + Point(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 2); // use two extra bytes orig_point.UnscaledX(20); orig_point.UnscaledY(-20); orig_point.UnscaledZ(100000); - orig_point.ScanAngleRank(90); - orig_point.ClassificationBitFields(124); + orig_point.ScanAngle(2000); - // Format 0 + // Format 6 auto point = orig_point; point.Pack(ss); auto point_other = - *Point::Unpack(ss, 0, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), point.NumExtraBytes()); - REQUIRE(point == point_other); - - // Format 1 - point.ToPointFormat(1); - point.Pack(ss); - point_other = - *Point::Unpack(ss, 1, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), point.NumExtraBytes()); - REQUIRE(point == point_other); - - // Format 2 - point.ToPointFormat(2); - point.Pack(ss); - point_other = - *Point::Unpack(ss, 2, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), point.NumExtraBytes()); - REQUIRE(point == point_other); - - // Format 3 - point.ToPointFormat(3); - point.Pack(ss); - point_other = - *Point::Unpack(ss, 3, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), point.NumExtraBytes()); - REQUIRE(point == point_other); - - // Format 6 - point.ToPointFormat(6); - point.Pack(ss); - point_other = - *Point::Unpack(ss, 6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), point.NumExtraBytes()); + *Point::Unpack(ss, 6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), point.EbByteSize()); REQUIRE(point == point_other); // Format 7 point.ToPointFormat(7); point.Pack(ss); point_other = - *Point::Unpack(ss, 7, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), point.NumExtraBytes()); + *Point::Unpack(ss, 7, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), point.EbByteSize()); REQUIRE(point == point_other); // Format 8 point.ToPointFormat(8); point.Pack(ss); point_other = - *Point::Unpack(ss, 8, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), point.NumExtraBytes()); + *Point::Unpack(ss, 8, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), point.EbByteSize()); REQUIRE(point == point_other); - - point.ToPointFormat(0); - REQUIRE(point == orig_point); } SECTION("Scaled XYZ") @@ -832,14 +563,14 @@ TEST_CASE("Point tests", "[Point]") } SECTION("Within") { - auto point = Point(3, {1, 1, 1}, copc::Vector3::DefaultOffset()); + auto point = Point(6, {1, 1, 1}, copc::Vector3::DefaultOffset()); point.X(5); point.Y(5); point.Z(5); REQUIRE(point.Within(copc::Box::MaxBox())); - REQUIRE(!point.Within(copc::Box::ZeroBox())); + REQUIRE(!point.Within(copc::Box::EmptyBox())); // 2D box REQUIRE(point.Within(copc::Box(0, 0, 5, 5))); diff --git a/test/point_test.py b/test/point_test.py index ef16e573..5ce9e5f8 100644 --- a/test/point_test.py +++ b/test/point_test.py @@ -6,329 +6,152 @@ def test_constructor(): point = copc.Points( - point_format_id=0, + point_format_id=6, scale=copc.Vector3.DefaultScale(), offset=copc.Vector3.DefaultOffset(), - num_extra_bytes=0, + eb_byte_size=0, ).CreatePoint() - assert point.HasExtendedPoint is False - assert point.HasGPSTime is False - assert point.HasRGB is False - assert point.HasNIR is False + assert point.HasRgb() is False + assert point.HasNir() is False point_ext = copc.Points( 8, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() ).CreatePoint() - assert point_ext.HasExtendedPoint is True - assert point_ext.HasGPSTime is True - assert point_ext.HasRGB is True - assert point_ext.HasNIR is True + assert point_ext.HasRgb() is True + assert point_ext.HasNir() is True -def test_point_format10(): - point0 = copc.Points( - 0, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() - ).CreatePoint() - # Position - point0.UnscaledX = 2147483647 - point0.UnscaledY = 2147483647 - point0.UnscaledZ = 2147483647 - assert point0.UnscaledX == 2147483647 - assert point0.UnscaledY == 2147483647 - assert point0.UnscaledZ == 2147483647 - - # Intensity - point0.Intensity = 65535 - assert point0.Intensity == 65535 - - # Return Number - point0.ReturnNumber = 7 - assert point0.ReturnNumber == 7 - with pytest.raises(RuntimeError): - point0.ReturnNumber = 8 - - # Number of Returns - point0.NumberOfReturns = 7 - assert point0.NumberOfReturns == 7 - with pytest.raises(RuntimeError): - point0.NumberOfReturns = 8 - - # Scan Direction - point0.ScanDirectionFlag = True - assert point0.ScanDirectionFlag is True - - # Edge of Flight Line - point0.EdgeOfFlightLineFlag = True - assert point0.EdgeOfFlightLineFlag is True - - # ReturnsScanDirEofBitField - point0.ReturnsScanDirEofBitFields = 172 - assert point0.ReturnNumber == 4 - assert point0.NumberOfReturns == 5 - assert point0.ScanDirectionFlag is False - assert point0.EdgeOfFlightLineFlag is True - - # Classification - point0.Classification = 31 - assert point0.Classification == 31 - with pytest.raises(RuntimeError): - point0.Classification = 32 - - # Synthetic - point0.Synthetic = True - assert point0.Synthetic is True - - # KeyPoint - point0.KeyPoint = True - assert point0.KeyPoint is True - - # Withheld - point0.Withheld = True - assert point0.Withheld is True - - # Classification Bitfield - point0.ClassificationBitFields = 255 - assert point0.ClassificationBitFields == 255 - point0.ClassificationBitFields = 78 - assert point0.Classification == 14 - assert point0.Synthetic is False - assert point0.KeyPoint is True - assert point0.Withheld is False - - # Scan Angle - point0.ScanAngleRank = 90 - assert point0.ScanAngleRank == 90 - assert point0.ScanAngle == 90.0 - with pytest.raises(RuntimeError): - point0.ScanAngleRank = 91 - point0.ScanAngleRank = -91 - - # User Data - point0.UserData = 255 - assert point0.UserData == 255 - - # Point Source ID - point0.PointSourceID = 65535 - assert point0.PointSourceID == 65535 - - assert point0.PointRecordLength == 20 - - # Checks - with pytest.raises(RuntimeError): - point0.ExtendedFlagsBitFields = 0 - assert point0.ExtendedFlagsBitFields - point0.ScannerChannel = 0 - assert point0.ScannerChannel - point0.NIR = 65535 - assert point0.NIR - point0.Red = 65535 - assert point0.Red - point0.Green = 65535 - assert point0.Green - point0.Blue = 65535 - assert point0.Blue - point0.GPSTime = sys.float_info.max - assert point0.GPSTime - point0.ExtendedScanAngle = 32767 - assert point0.ExtendedScanAngle - point0.Overlap = True - assert point0.Overlap - point0.ExtendedReturnsBitFields = 255 - assert point0.ExtendedReturnsBitFields - point0.ExtendedFlagsBitFields = 255 - assert point0.ExtendedFlagsBitFields - - str(point0) - - point1 = copc.Points( - 1, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() - ).CreatePoint() - - point1.GPSTime = sys.float_info.max - assert point1.GPSTime == sys.float_info.max - assert point1.PointRecordLength == 28 - with pytest.raises(RuntimeError): - point0.Red = 65535 - assert point0.Red - point0.Green = 65535 - assert point0.Green - point0.Blue = 65535 - assert point0.Blue - point0.NIR = 65535 - assert point0.NIR - - point2 = copc.Points( - 2, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() - ).CreatePoint() - assert point2.PointRecordLength == 26 - - point2.Red = 65535 - assert point2.Red == 65535 - point2.Green = 65535 - assert point2.Green == 65535 - point2.Blue = 65535 - assert point2.Blue == 65535 - - point2.RGB = [65535, 65535, 65535] - assert point2.Red == 65535 - assert point2.Green == 65535 - assert point2.Blue == 65535 - - with pytest.raises(RuntimeError): - point2.GPSTime = sys.float_info.max - assert point2.GPSTime - point2.NIR = 65535 - assert point2.NIR - - point3 = copc.Points( - 3, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() - ).CreatePoint() - assert point3.PointRecordLength == 34 - - point3.GPSTime = sys.float_info.max - assert point3.GPSTime - point3.RGB = [65535, 65535, 65535] - assert point3.Red - assert point3.Green - assert point3.Blue - with pytest.raises(RuntimeError): - point3.NIR = 65535 - assert point3.NIR - - -def test_point_format14(): +def test_point(): point6 = copc.Points( 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() ).CreatePoint() # Position - point6.UnscaledX = 2147483647 - point6.UnscaledY = 2147483647 - point6.UnscaledZ = 2147483647 - assert point6.UnscaledX == 2147483647 - assert point6.UnscaledY == 2147483647 - assert point6.UnscaledZ == 2147483647 + point6.X = 2147483647 + point6.Y = 2147483647 + point6.Z = 2147483647 + assert point6.X == 2147483647 + assert point6.Y == 2147483647 + assert point6.Z == 2147483647 - # Intensity - point6.Intensity = 65535 - assert point6.Intensity == 65535 + # intensity + point6.intensity = 65535 + assert point6.intensity == 65535 # Return BitField - point6.ExtendedReturnsBitFields = 255 - assert point6.ExtendedReturnsBitFields == 255 + point6.returns_bit_field = 255 + assert point6.returns_bit_field == 255 # Return Number - point6.ReturnNumber = 0 - assert point6.ReturnNumber == 0 - point6.ReturnNumber = 15 - assert point6.ReturnNumber == 15 + point6.return_number = 0 + assert point6.return_number == 0 + point6.return_number = 15 + assert point6.return_number == 15 with pytest.raises(RuntimeError): - point6.ReturnNumber = 16 + point6.return_number = 16 # Number return - point6.NumberOfReturns = 0 - assert point6.NumberOfReturns == 0 - point6.NumberOfReturns = 15 - assert point6.NumberOfReturns == 15 + point6.number_of_returns = 0 + assert point6.number_of_returns == 0 + point6.number_of_returns = 15 + assert point6.number_of_returns == 15 with pytest.raises(RuntimeError): - point6.NumberOfReturns = 16 + point6.number_of_returns = 16 # Flags - point6.ExtendedFlagsBitFields = 255 - assert point6.ExtendedFlagsBitFields == 255 - - # Synthetic - point6.Synthetic = False - assert point6.Synthetic is False - point6.Synthetic = True - assert point6.Synthetic is True - - # KeyPoint - point6.KeyPoint = False - assert point6.KeyPoint is False - point6.KeyPoint = True - assert point6.KeyPoint is True - - # Withheld - point6.Withheld = False - assert point6.Withheld is False - point6.Withheld = True - assert point6.Withheld is True - - # Overlap - point6.Overlap = False - assert point6.Overlap is False - point6.Overlap = True - assert point6.Overlap is True + point6.flags_bit_field = 255 + assert point6.flags_bit_field == 255 + + # synthetic + point6.synthetic = False + assert point6.synthetic is False + point6.synthetic = True + assert point6.synthetic is True + + # key_point + point6.key_point = False + assert point6.key_point is False + point6.key_point = True + assert point6.key_point is True + + # withheld + point6.withheld = False + assert point6.withheld is False + point6.withheld = True + assert point6.withheld is True + + # overlap + point6.overlap = False + assert point6.overlap is False + point6.overlap = True + assert point6.overlap is True # Scanner Channel - point6.ScannerChannel = 0 - assert point6.ScannerChannel == 0 - point6.ScannerChannel = 3 - assert point6.ScannerChannel == 3 + point6.scanner_channel = 0 + assert point6.scanner_channel == 0 + point6.scanner_channel = 3 + assert point6.scanner_channel == 3 with pytest.raises(RuntimeError): - point6.ScannerChannel = 4 + point6.scanner_channel = 4 # Scan Direction - point6.ScanDirectionFlag = True - assert point6.ScanDirectionFlag is True - point6.ScanDirectionFlag = False - assert point6.ScanDirectionFlag is False + point6.scan_direction_flag = True + assert point6.scan_direction_flag is True + point6.scan_direction_flag = False + assert point6.scan_direction_flag is False # Edge of Flight Line - point6.EdgeOfFlightLineFlag = True - assert point6.EdgeOfFlightLineFlag is True - point6.EdgeOfFlightLineFlag = False - assert point6.EdgeOfFlightLineFlag is False + point6.edge_of_flight_line = True + assert point6.edge_of_flight_line is True + point6.edge_of_flight_line = False + assert point6.edge_of_flight_line is False - # Classification - point6.Classification = 255 - assert point6.Classification == 255 - point6.Classification = 0 - assert point6.Classification == 0 + # classification + point6.classification = 255 + assert point6.classification == 255 + point6.classification = 0 + assert point6.classification == 0 # Scan Angle - point6.ExtendedScanAngle = -30000 - assert point6.ExtendedScanAngle == -30000 - assert point6.ScanAngle == -180.0 + point6.scan_angle = -30000 + assert point6.scan_angle == -30000 + assert point6.scan_angle_degrees == -180.0 + point6.scan_angle = 30000 + assert point6.scan_angle == 30000 + assert point6.scan_angle_degrees == 180.0 with pytest.raises(RuntimeError): - point6.ExtendedScanAngle = -30001 - point6.ExtendedScanAngle = 30000 - assert point6.ExtendedScanAngle == 30000 - assert point6.ScanAngle == 180.0 - with pytest.raises(RuntimeError): - point6.ExtendedScanAngle = 30001 + point6.scan_angle = 30001 # User Data - point6.UserData = 255 - assert point6.UserData == 255 + point6.user_data = 255 + assert point6.user_data == 255 # Point Source ID - point6.PointSourceID = 65535 - assert point6.PointSourceID == 65535 + point6.point_source_id = 65535 + assert point6.point_source_id == 65535 # GPS Time - point6.GPSTime = sys.float_info.max - assert point6.GPSTime == sys.float_info.max + point6.gps_time = sys.float_info.max + assert point6.gps_time == sys.float_info.max # Point Record Length - assert point6.PointRecordLength == 30 + assert point6.point_record_length == 30 # Checks with pytest.raises(RuntimeError): - point6.RGB = [65535, 65535, 65535] - assert point6.Red - assert point6.Green - assert point6.Blue - point6.NIR = 65535 - assert point6.NIR - assert point6.ScanAngleRank - point6.ScanAngleRank = 127 + point6.rgb = [65535, 65535, 65535] + assert point6.red + assert point6.green + assert point6.blue + point6.nir = 65535 + assert point6.nir + assert point6.scan_angleRank + point6.scan_angleRank = 127 point6.ReturnsScanDirEofBitFields = 255 assert point6.ReturnsScanDirEofBitFields - point6.ClassificationBitFields = 255 - assert point6.ClassificationBitFields + point6.classificationBitFields = 255 + assert point6.classificationBitFields # ToString str(point6) @@ -337,183 +160,63 @@ def test_point_format14(): 7, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() ).CreatePoint() - point7.RGB = [65535, 65535, 65535] - assert point7.Red == 65535 - assert point7.Green == 65535 - assert point7.Blue == 65535 - assert point7.PointRecordLength == 36 + point7.rgb = [65535, 65535, 65535] + assert point7.red == 65535 + assert point7.green == 65535 + assert point7.blue == 65535 + assert point7.point_record_length == 36 with pytest.raises(RuntimeError): - point7.NIR = 65535 - assert point7.NIR + point7.nir = 65535 + assert point7.nir point8 = copc.Points( 8, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() ).CreatePoint() - point8.NIR = 65535 - assert point8.NIR == 65535 - assert point8.PointRecordLength == 38 + point8.nir = 65535 + assert point8.nir == 65535 + assert point8.point_record_length == 38 -def test_point_conversion_10(): - point = copc.Points( - 1, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() - ).CreatePoint() - - assert point.GPSTime == 0 - - point.ToPointFormat(point_format_id=2) - assert point.Red == 0 - assert point.Green == 0 - assert point.Blue == 0 - - point.ToPointFormat(3) - assert point.GPSTime == 0 - assert point.Red == 0 - assert point.Green == 0 - assert point.Blue == 0 - - -def test_point_conversion_14(): +def test_point_conversion(): point = copc.Points( 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() ).CreatePoint() - assert point.GPSTime == 0 - assert point.ScannerChannel == 0 - assert point.Overlap == 0 - assert point.GPSTime == 0 + assert point.gps_time == 0 + assert point.scanner_channel == 0 + assert point.overlap == 0 + assert point.gps_time == 0 point.ToPointFormat(7) - assert point.GPSTime == 0 - assert point.ScannerChannel == 0 - assert point.Overlap == 0 - assert point.GPSTime == 0 - assert point.Red == 0 - assert point.Green == 0 - assert point.Blue == 0 + assert point.gps_time == 0 + assert point.scanner_channel == 0 + assert point.overlap == 0 + assert point.gps_time == 0 + assert point.red == 0 + assert point.green == 0 + assert point.blue == 0 point.ToPointFormat(8) - assert point.GPSTime == 0 - assert point.ScannerChannel == 0 - assert point.Overlap == 0 - assert point.GPSTime == 0 - assert point.Red == 0 - assert point.Green == 0 - assert point.Blue == 0 - assert point.NIR == 0 - - -def test_point_conversion_10_to_14(): - point = copc.Points( - 0, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() - ).CreatePoint() - - point.ReturnNumber = 5 - point.NumberOfReturns = 6 - point.ScanDirectionFlag = False - point.EdgeOfFlightLineFlag = True - point.Classification = 15 - point.Synthetic = True - point.KeyPoint = False - point.Withheld = True - point.ScanAngleRank = 45 - - point.ToPointFormat(6) - - assert point.ReturnNumber == 5 - assert point.NumberOfReturns == 6 - assert point.ScanDirectionFlag is False - assert point.EdgeOfFlightLineFlag is True - assert point.Classification == 15 - assert point.Synthetic is True - assert point.KeyPoint is False - assert point.Withheld is True - assert point.Overlap is False - assert point.ExtendedScanAngle == 7500 - assert point.ScannerChannel == 0 - with pytest.raises(RuntimeError): - assert point.ReturnsScanDirEofBitFields - - point.ToPointFormat(0) - - assert point.ReturnNumber == 5 - assert point.NumberOfReturns == 6 - assert point.ScanDirectionFlag is False - assert point.EdgeOfFlightLineFlag is True - assert point.Classification == 15 - assert point.Synthetic is True - assert point.KeyPoint is False - assert point.Withheld is True - assert point.ScanAngleRank == 45 - - point.ToPointFormat(6) - - point.ReturnNumber = 13 - point.NumberOfReturns = 14 - point.Classification = 134 - point.ScannerChannel = 2 - point.Synthetic = True - point.KeyPoint = False - point.Withheld = True - point.ExtendedScanAngle = 28000 - - point.ToPointFormat(0) - - assert point.ReturnNumber == 7 - assert point.NumberOfReturns == 7 - assert point.Classification == 31 - assert point.ScanAngleRank == 90 - with pytest.raises(RuntimeError): - assert point.Overlap - assert point.ScannerChannel - assert point.ExtendedReturnsBitFields - assert point.ExtendedFlagsBitFields - - point.ToPointFormat(6) - - assert point.ReturnNumber == 7 - assert point.NumberOfReturns == 7 - assert point.Classification == 31 - assert point.ExtendedScanAngle == 15000 - assert point.Overlap is False - assert point.ScannerChannel == 0 + assert point.gps_time == 0 + assert point.scanner_channel == 0 + assert point.overlap == 0 + assert point.gps_time == 0 + assert point.red == 0 + assert point.green == 0 + assert point.blue == 0 + assert point.nir == 0 def test_operators_equal(): # Format 0 point = copc.Points( - 0, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() ).CreatePoint() point_other = copc.Points( - 0, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() ).CreatePoint() - - assert point == point_other - - # Format 1 - point_other.ToPointFormat(1) - assert point != point_other - point.ToPointFormat(1) - assert point == point_other - - # Format 2 - point_other.ToPointFormat(2) - assert point != point_other - point.ToPointFormat(2) - assert point == point_other - - # Format 3 - point_other.ToPointFormat(3) - assert point != point_other - point.ToPointFormat(3) - assert point == point_other - - # Format 6 - point_other.ToPointFormat(6) - assert point != point_other - point.ToPointFormat(6) assert point == point_other # Format 7 @@ -530,159 +233,156 @@ def test_operators_equal(): # Atributes - point.UnscaledX = 4 + point.X = 4 assert point != point_other - point_other.UnscaledX = 4 + point_other.X = 4 assert point == point_other - point.UnscaledY = 4 + point.Y = 4 assert point != point_other - point_other.UnscaledY = 4 + point_other.Y = 4 assert point == point_other - point.UnscaledZ = 4 + point.Z = 4 assert point != point_other - point_other.UnscaledZ = 4 + point_other.Z = 4 assert point == point_other - point.Intensity = 4 + point.intensity = 4 assert point != point_other - point_other.Intensity = 4 + point_other.intensity = 4 assert point == point_other - point.ReturnNumber = 4 + point.return_number = 4 assert point != point_other - point_other.ReturnNumber = 4 + point_other.return_number = 4 assert point == point_other - point.NumberOfReturns = 4 + point.number_of_returns = 4 assert point != point_other - point_other.NumberOfReturns = 4 + point_other.number_of_returns = 4 assert point == point_other - point.Classification = 4 + point.classification = 4 assert point != point_other - point_other.Classification = 4 + point_other.classification = 4 assert point == point_other - point.ScanDirectionFlag = True + point.scan_direction_flag = True assert point != point_other - point_other.ScanDirectionFlag = True + point_other.scan_direction_flag = True assert point == point_other - point.EdgeOfFlightLineFlag = True + point.edge_of_flight_line = True assert point != point_other - point_other.EdgeOfFlightLineFlag = True + point_other.edge_of_flight_line = True assert point == point_other - point.Synthetic = True + point.synthetic = True assert point != point_other - point_other.Synthetic = True + point_other.synthetic = True assert point == point_other - point.KeyPoint = True + point.key_point = True assert point != point_other - point_other.KeyPoint = True + point_other.key_point = True assert point == point_other - point.Withheld = True + point.withheld = True assert point != point_other - point_other.Withheld = True + point_other.withheld = True assert point == point_other - point.Overlap = True + point.overlap = True assert point != point_other - point_other.Overlap = True + point_other.overlap = True assert point == point_other - point.ScannerChannel = 2 + point.scanner_channel = 2 assert point != point_other - point_other.ScannerChannel = 2 + point_other.scanner_channel = 2 assert point == point_other - point.UserData = 4 + point.user_data = 4 assert point != point_other - point_other.UserData = 4 + point_other.user_data = 4 assert point == point_other - point.PointSourceID = 4 + point.point_source_id = 4 assert point != point_other - point_other.PointSourceID = 4 + point_other.point_source_id = 4 assert point == point_other - point.GPSTime = 4.0 + point.gps_time = 4.0 assert point != point_other - point_other.GPSTime = 4.0 + point_other.gps_time = 4.0 assert point == point_other - point.Red = 4 + point.red = 4 assert point != point_other - point_other.Red = 4 + point_other.red = 4 assert point == point_other - point.Green = 4 + point.green = 4 assert point != point_other - point_other.Green = 4 + point_other.green = 4 assert point == point_other - point.Blue = 4 + point.blue = 4 assert point != point_other - point_other.Blue = 4 + point_other.blue = 4 assert point == point_other - point.NIR = 4 + point.nir = 4 assert point != point_other - point_other.NIR = 4 + point_other.nir = 4 assert point == point_other def test_extra_byte(): point = copc.Points( - 0, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() ).CreatePoint() - assert point.PointFormatID == 0 - assert point.PointRecordLength == 20 + assert point.point_format_id == 6 + assert point.point_record_length == 30 with pytest.raises(RuntimeError): - point.ExtraBytes = [2, 3, 4] - assert len(point.ExtraBytes) == 0 - assert point.NumExtraBytes == 0 + point.extra_bytes = [2, 3, 4] + assert len(point.extra_bytes) == 0 + assert point.EbByteSize() == 0 point = copc.Points( - 0, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), num_extra_bytes=5 + 6, + copc.Vector3.DefaultScale(), + copc.Vector3.DefaultOffset(), + eb_byte_size=5, ).CreatePoint() - assert point.PointFormatID == 0 - assert point.PointRecordLength == 20 + 5 - assert point.NumExtraBytes == 5 - assert len(point.ExtraBytes) == 5 - with pytest.raises(RuntimeError): - point.ExtraBytes = [2, 3, 4] - point.ExtraBytes = [2, 3, 4, 5, 6] - assert point.ExtraBytes == [2, 3, 4, 5, 6] - - point.ToPointFormat(6) - assert point.PointFormatID == 6 - assert point.PointRecordLength == 30 + 5 - assert point.NumExtraBytes == 5 - assert len(point.ExtraBytes) == 5 - assert point.ExtraBytes == [2, 3, 4, 5, 6] + assert point.point_format_id == 6 + assert point.point_record_length == 30 + 5 + assert point.EbByteSize() == 5 + assert len(point.extra_bytes) == 5 with pytest.raises(RuntimeError): - point.ExtraBytes = [2, 3, 4] + point.extra_bytes = [2, 3, 4] + point.extra_bytes = [2, 3, 4, 5, 6] + assert point.extra_bytes == [2, 3, 4, 5, 6] def test_operator_copy(): point = copc.Points( - 8, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), num_extra_bytes=2 + 8, + copc.Vector3.DefaultScale(), + copc.Vector3.DefaultOffset(), + eb_byte_size=2, ).CreatePoint() - point.UnscaledX = 4 - point.UnscaledY = 4 - point.UnscaledZ = 4 + point.X = 4 + point.Y = 4 + point.Z = 4 - point.GPSTime = 4.0 - point.Red = 4 - point.NIR = 4 + point.gps_time = 4.0 + point.red = 4 + point.nir = 4 - point.ExtraBytes = [2, 5] + point.extra_bytes = [2, 5] point_other = point @@ -696,115 +396,115 @@ def test_scaled_xyz(): # No scale and offset point = copc.Points(pfid, scale=[1, 1, 1], offset=[0, 0, 0]).CreatePoint() - point.UnscaledX = 4 - point.UnscaledY = 4 - point.UnscaledZ = 4 + point.X = 4 + point.Y = 4 + point.Z = 4 - assert point.X == 4 - assert point.Y == 4 - assert point.Z == 4 + assert point.x == 4 + assert point.y == 4 + assert point.z == 4 - point.X = 5 - point.Y = 6 - point.Z = 7 + point.x = 5 + point.y = 6 + point.z = 7 - assert point.UnscaledX == 5 - assert point.UnscaledY == 6 - assert point.UnscaledZ == 7 + assert point.X == 5 + assert point.Y == 6 + assert point.Z == 7 # Scale test point = copc.Points( pfid, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset() ).CreatePoint() - point.UnscaledX = 1 - point.UnscaledY = 2 - point.UnscaledZ = 3 + point.X = 1 + point.Y = 2 + point.Z = 3 - assert point.X == 0.01 - assert point.Y == 0.02 - assert point.Z == 0.03 + assert point.x == 0.01 + assert point.y == 0.02 + assert point.z == 0.03 - point.X = 200 - point.Y = 300 - point.Z = 400 + point.x = 200 + point.y = 300 + point.z = 400 - assert point.UnscaledX == 200 * 100 - assert point.UnscaledY == 300 * 100 - assert point.UnscaledZ == 400 * 100 + assert point.X == 200 * 100 + assert point.Y == 300 * 100 + assert point.Z == 400 * 100 # Offset test scale = [1, 1, 1] offset = copc.Vector3([50001.456, 4443.123, -255.001]) point = copc.Points(pfid, scale, offset).CreatePoint() - point.UnscaledX = 0 - point.UnscaledY = -800 - point.UnscaledZ = 3 + point.X = 0 + point.Y = -800 + point.Z = 3 - assert point.X == 0 + offset.x - assert point.Y == -800 + offset.y - assert point.Z == 3 + offset.z + assert point.x == 0 + offset.x + assert point.y == -800 + offset.y + assert point.z == 3 + offset.z - point.X = 50502.888 - point.Y = 4002.111 - point.Z = -80.5 + point.x = 50502.888 + point.y = 4002.111 + point.z = -80.5 - assert point.UnscaledX == 501 # 50502.888 - 50001.456 = 501.432 - assert point.UnscaledY == -441 - assert point.UnscaledZ == 175 # -80.5 - -255.001 = 255.001 - 80.5 = 175 + assert point.X == 501 # 50502.888 - 50001.456 = 501.432 + assert point.Y == -441 + assert point.Z == 175 # -80.5 - -255.001 = 255.001 - 80.5 = 175 # (value * scale) + offset - assert point.X == pytest.approx(50502.456, 0.000001) - assert point.Y == pytest.approx(4002.123, 0.000001) - assert point.Z == pytest.approx(-80.001, 0.000001) + assert point.x == pytest.approx(50502.456, 0.000001) + assert point.y == pytest.approx(4002.123, 0.000001) + assert point.z == pytest.approx(-80.001, 0.000001) # Scale and Offset test point = copc.Points( pfid, scale=[0.001, 0.001, 0.001], offset=[50001.456, 4443.123, -255.001] ).CreatePoint() - point.UnscaledX = 0 - point.UnscaledY = -800 - point.UnscaledZ = 300532 + point.X = 0 + point.Y = -800 + point.Z = 300532 - assert point.X == pytest.approx(50001.456, 0.000001) - assert point.Y == pytest.approx(4442.323, 0.000001) - assert point.Z == pytest.approx(45.531, 0.000001) + assert point.x == pytest.approx(50001.456, 0.000001) + assert point.y == pytest.approx(4442.323, 0.000001) + assert point.z == pytest.approx(45.531, 0.000001) - point.X = 50502.888 - point.Y = 4002.111 - point.Z = -80.5 + point.x = 50502.888 + point.y = 4002.111 + point.z = -80.5 - assert point.UnscaledX == 501432 - assert point.UnscaledY == -441012 - assert point.UnscaledZ == 174501 + assert point.X == 501432 + assert point.Y == -441012 + assert point.Z == 174501 # (value * scale) + offset - assert point.X == pytest.approx(50502.888, 0.000001) - assert point.Y == pytest.approx(4002.111, 0.000001) - assert point.Z == pytest.approx(-80.5, 0.000001) + assert point.x == pytest.approx(50502.888, 0.000001) + assert point.y == pytest.approx(4002.111, 0.000001) + assert point.z == pytest.approx(-80.5, 0.000001) # Precision checks point = copc.Points(pfid, [0.000001, 0.000001, 0.000001], [0, 0, 0]).CreatePoint() with pytest.raises(RuntimeError): - point.X = 50501132.888123 + point.x = 50501132.888123 point = copc.Points(pfid, [1, 1, 1], [-8001100065, 0, 0]).CreatePoint() with pytest.raises(RuntimeError): - point.X = 0 + point.x = 0 def test_within(): - point = copc.Points(3, (1, 1, 1), copc.Vector3.DefaultOffset()).CreatePoint() + point = copc.Points(6, (1, 1, 1), copc.Vector3.DefaultOffset()).CreatePoint() - point.X = 5 - point.Y = 5 - point.Z = 5 + point.x = 5 + point.y = 5 + point.z = 5 assert point.Within(copc.Box.MaxBox()) - assert not point.Within(copc.Box.ZeroBox()) + assert not point.Within(copc.Box.EmptyBox()) # 2D box assert point.Within(copc.Box(0, 0, 5, 5)) diff --git a/test/points_test.cpp b/test/points_test.cpp index 82e509e9..8e711438 100644 --- a/test/points_test.cpp +++ b/test/points_test.cpp @@ -14,9 +14,9 @@ TEST_CASE("Points tests", "[Point]") { SECTION("Points constructors") { - auto points = Points(3, {1, 1, 1}, {0, 0, 0}, 4); - REQUIRE(points.PointFormatID() == 3); - REQUIRE(points.PointRecordLength() == 38); + auto points = Points(6, {1, 1, 1}, {0, 0, 0}, 4); + REQUIRE(points.PointFormatId() == 6); + REQUIRE(points.PointRecordLength() == 34); REQUIRE(points.Get().empty()); auto point_vec = std::vector>(); @@ -33,11 +33,10 @@ TEST_CASE("Points tests", "[Point]") point_vec.push_back(point3); points = Points(point_vec); - REQUIRE(points.PointFormatID() == 3); - REQUIRE(points.PointRecordLength() == 38); + REQUIRE(points.PointFormatId() == 6); + REQUIRE(points.PointRecordLength() == 34); for (const auto &point : points.Get()) - REQUIRE(point->PointFormatID() == 3); - REQUIRE(points.PointRecordLength() == 38); + REQUIRE(point->PointFormatId() == 6); REQUIRE(points.Get(0)->UnscaledX() == 11); REQUIRE(points.Get(0)->UnscaledY() == 11); REQUIRE(points.Get(0)->UnscaledZ() == 11); @@ -47,7 +46,7 @@ TEST_CASE("Points tests", "[Point]") SECTION("Adding Point to Points") { - auto points = Points(3, {1, 1, 1}, {0, 0, 0}, 0); + auto points = Points(6, {1, 1, 1}, {0, 0, 0}, 0); auto point = points.CreatePoint(); point->UnscaledX(11); point->UnscaledY(11); @@ -72,20 +71,20 @@ TEST_CASE("Points tests", "[Point]") REQUIRE(points.Get(1)->UnscaledZ() == 22); // Test check on point format - point = std::make_shared(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 0); + point = std::make_shared(7, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 0); REQUIRE_THROWS(points.AddPoint(point)); // Test check on extra bytes - point = std::make_shared(3, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 1); + point = std::make_shared(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 1); REQUIRE_THROWS(points.AddPoint(point)); } SECTION("Adding Points to Points") { auto points = Points(std::vector>( - 10, std::make_shared(3, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4))); + 10, std::make_shared(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4))); auto points_other = Points(std::vector>( - 10, std::make_shared(3, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4))); + 10, std::make_shared(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4))); points.AddPoints(points_other); @@ -93,28 +92,28 @@ TEST_CASE("Points tests", "[Point]") // Test check on point format points_other = Points(std::vector>( - 10, std::make_shared(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4))); + 10, std::make_shared(7, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4))); REQUIRE_THROWS(points.AddPoints(points_other)); // Test check on extra bytes points_other = Points(std::vector>( - 10, std::make_shared(3, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 1))); + 10, std::make_shared(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 1))); REQUIRE_THROWS(points.AddPoints(points_other)); } SECTION("Points format conversion") { auto points = Points(std::vector>( - 10, std::make_shared(3, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4))); - points.ToPointFormat(6); + 10, std::make_shared(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4))); + points.ToPointFormat(7); - REQUIRE(points.PointFormatID() == 6); - REQUIRE(points.PointRecordLength() == 38); - REQUIRE(points.Get(1)->PointFormatID() == 6); - REQUIRE(points.Get(1)->PointRecordLength() == 34); + REQUIRE(points.PointFormatId() == 7); + REQUIRE(points.PointRecordLength() == 40); + REQUIRE(points.Get(1)->PointFormatId() == 7); + REQUIRE(points.Get(1)->PointRecordLength() == 40); - REQUIRE_THROWS(points.ToPointFormat(-1)); - REQUIRE_THROWS(points.ToPointFormat(11)); + REQUIRE_THROWS(points.ToPointFormat(5)); + REQUIRE_THROWS(points.ToPointFormat(9)); } SECTION("Points Group Accessors") @@ -130,7 +129,7 @@ TEST_CASE("Points tests", "[Point]") p->Y(i * 3); p->Z(i - 80); p->Classification(i * 255 / num_points); - p->PointSourceID(i * 255 / num_points); + p->PointSourceId(i * 255 / num_points); p->Red(i * 4); p->Green(i * 5); p->Blue(i * 6); @@ -144,7 +143,7 @@ TEST_CASE("Points tests", "[Point]") auto Y = points.Y(); auto Z = points.Z(); auto classification = points.Classification(); - auto point_source_id = points.PointSourceID(); + auto point_source_id = points.PointSourceId(); auto red = points.Red(); auto green = points.Green(); auto blue = points.Blue(); @@ -174,7 +173,7 @@ TEST_CASE("Points tests", "[Point]") REQUIRE_THROWS(points.Y(Yn)); REQUIRE_THROWS(points.Z(Zn)); REQUIRE_THROWS(points.Classification(classification_n)); - REQUIRE_THROWS(points.PointSourceID(point_source_id_n)); + REQUIRE_THROWS(points.PointSourceId(point_source_id_n)); REQUIRE_THROWS(points.Red(red_n)); REQUIRE_THROWS(points.Green(green_n)); REQUIRE_THROWS(points.Blue(blue_n)); @@ -195,7 +194,7 @@ TEST_CASE("Points tests", "[Point]") REQUIRE_THROWS(points.Y(Yn)); REQUIRE_THROWS(points.Z(Zn)); REQUIRE_THROWS(points.Classification(classification_n)); - REQUIRE_THROWS(points.PointSourceID(point_source_id_n)); + REQUIRE_THROWS(points.PointSourceId(point_source_id_n)); REQUIRE_THROWS(points.Red(red_n)); REQUIRE_THROWS(points.Green(green_n)); REQUIRE_THROWS(points.Blue(blue_n)); @@ -215,7 +214,7 @@ TEST_CASE("Points tests", "[Point]") REQUIRE_NOTHROW(points.Y(Yn)); REQUIRE_NOTHROW(points.Z(Zn)); REQUIRE_NOTHROW(points.Classification(classification_n)); - REQUIRE_NOTHROW(points.PointSourceID(point_source_id_n)); + REQUIRE_NOTHROW(points.PointSourceId(point_source_id_n)); REQUIRE_NOTHROW(points.Red(red_n)); REQUIRE_NOTHROW(points.Green(green_n)); REQUIRE_NOTHROW(points.Blue(blue_n)); @@ -227,7 +226,7 @@ TEST_CASE("Points tests", "[Point]") REQUIRE(p->Y() == i + 800); REQUIRE(p->Z() == i * 4); REQUIRE(p->Classification() == i * 255 / 2000); - REQUIRE(p->PointSourceID() == i * 255 / 2000); + REQUIRE(p->PointSourceId() == i * 255 / 2000); REQUIRE(p->Red() == i * 4); REQUIRE(p->Green() == i * 5); REQUIRE(p->Blue() == i * 6); @@ -239,7 +238,7 @@ TEST_CASE("Points tests", "[Point]") REQUIRE(last_point->Y() == 2); REQUIRE(last_point->Z() == 3); REQUIRE(last_point->Classification() == 255); - REQUIRE(last_point->PointSourceID() == 255); + REQUIRE(last_point->PointSourceId() == 255); REQUIRE(last_point->Red() == num_points * 4); REQUIRE(last_point->Green() == num_points * 5); REQUIRE(last_point->Blue() == num_points * 6); @@ -247,7 +246,7 @@ TEST_CASE("Points tests", "[Point]") SECTION("Points Indexers") { - auto points = Points(3, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4); + auto points = Points(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4); // generate points int num_points = 2000; @@ -342,7 +341,7 @@ TEST_CASE("Points tests", "[Point]") SECTION("Within") { - auto points = Points(3, {1, 1, 1}, copc::Vector3::DefaultOffset()); + auto points = Points(6, {1, 1, 1}, copc::Vector3::DefaultOffset()); std::random_device rd; std::mt19937 gen(rd()); @@ -372,7 +371,7 @@ TEST_CASE("Points tests", "[Point]") SECTION("GetWithin") { - auto points = Points(3, {1, 1, 1}, copc::Vector3::DefaultOffset()); + auto points = Points(6, {1, 1, 1}, copc::Vector3::DefaultOffset()); std::random_device rd; std::mt19937 gen(rd()); diff --git a/test/points_test.py b/test/points_test.py index fe0fc278..5bd85f50 100644 --- a/test/points_test.py +++ b/test/points_test.py @@ -5,88 +5,102 @@ def test_points_constructor(): points = copc.Points( - 3, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), 4 + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), 4 ) - assert points.PointFormatID == 3 - assert points.PointRecordLength == 38 + assert points.point_format_id == 6 + assert points.point_record_length == 34 assert len(points) == 0 point1 = copc.Points( - 3, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), num_extra_bytes=4 + 6, + copc.Vector3.DefaultScale(), + copc.Vector3.DefaultOffset(), + eb_byte_size=4, ).CreatePoint() - point1.UnscaledX = 11 - point1.UnscaledY = 11 - point1.UnscaledZ = 11 + point1.X = 11 + point1.Y = 11 + point1.Z = 11 point_list = [ point1, copc.Points( - 3, + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), - num_extra_bytes=4, + eb_byte_size=4, ).CreatePoint(), copc.Points( - 3, + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), - num_extra_bytes=4, + eb_byte_size=4, ).CreatePoint(), ] points = copc.Points(point_list) - assert points.PointFormatID == 3 - assert points.PointRecordLength == 38 + assert points.point_format_id == 6 + assert points.point_record_length == 34 for point in points: - assert point.PointFormatID == 3 - assert points.PointRecordLength == 38 - assert points[0].UnscaledY == 11 - assert points[0].UnscaledZ == 11 + assert point.point_format_id == 6 + assert points[0].Y == 11 + assert points[0].Z == 11 str(points) def test_adding_point_to_points(): points = copc.Points( - 3, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), 0 + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), 0 ) point = copc.Points( - 3, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), num_extra_bytes=0 + 6, + copc.Vector3.DefaultScale(), + copc.Vector3.DefaultOffset(), + eb_byte_size=0, ).CreatePoint() - point.UnscaledX = 11 - point.UnscaledY = 11 - point.UnscaledZ = 11 + point.X = 11 + point.Y = 11 + point.Z = 11 points.AddPoint(point) assert len(points) == 1 - assert points[0].UnscaledX == 11 - assert points[0].UnscaledY == 11 - assert points[0].UnscaledZ == 11 + assert points[0].X == 11 + assert points[0].Y == 11 + assert points[0].Z == 11 point = copc.Points( - 3, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), num_extra_bytes=0 + 6, + copc.Vector3.DefaultScale(), + copc.Vector3.DefaultOffset(), + eb_byte_size=0, ).CreatePoint() - point.UnscaledX = 22 - point.UnscaledY = 22 - point.UnscaledZ = 22 + point.X = 22 + point.Y = 22 + point.Z = 22 points.AddPoint(point) assert len(points) == 2 - assert points[1].UnscaledX == 22 - assert points[1].UnscaledY == 22 - assert points[1].UnscaledZ == 22 + assert points[1].X == 22 + assert points[1].Y == 22 + assert points[1].Z == 22 # Test check on point format point = copc.Points( - 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), num_extra_bytes=0 + 7, + copc.Vector3.DefaultScale(), + copc.Vector3.DefaultOffset(), + eb_byte_size=0, ).CreatePoint() with pytest.raises(RuntimeError): points.AddPoint(point) # Test check on extra bytes point = copc.Points( - 3, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), num_extra_bytes=1 + 6, + copc.Vector3.DefaultScale(), + copc.Vector3.DefaultOffset(), + eb_byte_size=1, ).CreatePoint() with pytest.raises(RuntimeError): points.AddPoint(point) @@ -96,10 +110,10 @@ def test_adding_points_to_points(): points = copc.Points( [ copc.Points( - 3, + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), - num_extra_bytes=4, + eb_byte_size=4, ).CreatePoint() for _ in range(10) ] @@ -107,10 +121,10 @@ def test_adding_points_to_points(): points_other = copc.Points( [ copc.Points( - 3, + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), - num_extra_bytes=4, + eb_byte_size=4, ).CreatePoint() for _ in range(10) ] @@ -124,10 +138,10 @@ def test_adding_points_to_points(): points_other = copc.Points( [ copc.Points( - 6, + 7, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), - num_extra_bytes=4, + eb_byte_size=4, ).CreatePoint() for _ in range(10) ] @@ -139,10 +153,10 @@ def test_adding_points_to_points(): points_other = copc.Points( [ copc.Points( - 3, + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), - num_extra_bytes=1, + eb_byte_size=1, ).CreatePoint() for _ in range(10) ] @@ -155,41 +169,41 @@ def test_points_format_conversion(): points = copc.Points( [ copc.Points( - 3, + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), - num_extra_bytes=4, + eb_byte_size=4, ).CreatePoint() for _ in range(10) ] ) - points.ToPointFormat(6) + points.ToPointFormat(7) - assert points.PointFormatID == 6 - assert points.PointRecordLength == 38 - assert points[0].PointFormatID == 6 - assert points[0].PointRecordLength == 34 + assert points.point_format_id == 7 + assert points.point_record_length == 40 + assert points[0].point_format_id == 7 + assert points[0].point_record_length == 40 with pytest.raises(RuntimeError): - points.ToPointFormat(-1) + points.ToPointFormat(5) with pytest.raises(RuntimeError): - points.ToPointFormat(11) + points.ToPointFormat(9) def test_points_iterator(): points = copc.Points( - 3, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), 4 + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), 4 ) # generate points num_points = 2000 for i in range(num_points): p = points.CreatePoint() - p.Classification = i % 32 + p.classification = i % 32 points.AddPoint(p) - classification_index = [points[i].Classification for i in range(num_points)] - classification_iterator = [p.Classification for p in points] + classification_index = [points[i].classification for i in range(num_points)] + classification_iterator = [p.classification for p in points] for i, clas in enumerate(classification_index): assert clas == i % 32 @@ -199,96 +213,96 @@ def test_points_iterator(): def test_points_group_accessors(): points = copc.Points( - 3, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), 4 + 7, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), 4 ) # generate points num_points = 2000 for i in range(num_points): p = points.CreatePoint() - p.X = i - p.Y = i * 3 - p.Z = i - 80 - p.Classification = i * 255 / num_points - p.PointSourceID = i * 255 / num_points - p.Red = i * 4 - p.Green = i * 5 - p.Blue = i * 6 + p.x = i + p.y = i * 3 + p.z = i - 80 + p.classification = i * 255 // num_points + p.point_source_id = i * 255 // num_points + p.red = i * 4 + p.green = i * 5 + p.blue = i * 6 points.AddPoint(p) assert len(points) == num_points # test that the getters work for i in range(num_points): - assert points.X[i] == i - assert points.Y[i] == i * 3 - assert points.Z[i] == i - 80 - assert points.Classification[i] == i * 255 / num_points - assert points.PointSourceID[i] == i * 255 / num_points - assert points.Red[i] == i * 4 - assert points.Green[i] == i * 5 - assert points.Blue[i] == i * 6 + assert points.x[i] == i + assert points.y[i] == i * 3 + assert points.z[i] == i - 80 + assert points.classification[i] == i * 255 // num_points + assert points.point_source_id[i] == i * 255 // num_points + assert points.red[i] == i * 4 + assert points.green[i] == i * 5 + assert points.blue[i] == i * 6 # generate vector of coordinates - Xn = [] - Yn = [] - Zn = [] + xn = [] + yn = [] + zn = [] with pytest.raises(RuntimeError): - points.X = Xn - points.Y = Yn - points.Z = Zn + points.x = xn + points.y = yn + points.z = zn for i in range(num_points - 1): - Xn.append(i * 50 + 8) - Yn.append(i + 800) - Zn.append(i * 4) + xn.append(i * 50 + 8) + yn.append(i + 800) + zn.append(i * 4) with pytest.raises(RuntimeError): - points.X = Xn - points.Y = Yn - points.Z = Zn + points.x = xn + points.y = yn + points.z = zn # add the last point - Xn.append(1) - Yn.append(2) - Zn.append(3) + xn.append(1) + yn.append(2) + zn.append(3) # test setters - points.X = Xn - points.Y = Yn - points.Z = Zn + points.x = xn + points.y = yn + points.z = zn for i in range(num_points - 1): p = points[i] - assert p.X == i * 50 + 8 - assert p.Y == i + 800 - assert p.Z == i * 4 + assert p.x == i * 50 + 8 + assert p.y == i + 800 + assert p.z == i * 4 # test negative indices last_point = points[-1] - assert last_point.X == 1 - assert last_point.Y == 2 - assert last_point.Z == 3 + assert last_point.x == 1 + assert last_point.y == 2 + assert last_point.z == 3 def test_within(): - points = copc.Points(3, (1, 1, 1), copc.Vector3.DefaultOffset()) + points = copc.Points(6, (1, 1, 1), copc.Vector3.DefaultOffset()) # generate points for i in range(2000): p = points.CreatePoint() - p.X = random.uniform(0, 5) - p.Y = random.uniform(0, 5) - p.Z = random.uniform(0, 5) + p.x = random.uniform(0, 5) + p.y = random.uniform(0, 5) + p.z = random.uniform(0, 5) points.AddPoint(p) assert points.Within(copc.Box(0, 0, 0, 5, 5, 5)) p = points.CreatePoint() - p.X = random.uniform(0, 5) - p.Y = random.uniform(0, 5) - p.Z = 6 + p.x = random.uniform(0, 5) + p.y = random.uniform(0, 5) + p.z = 6 points.AddPoint(p) assert not points.Within(copc.Box(0, 0, 0, 5, 5, 5)) @@ -296,14 +310,14 @@ def test_within(): def test_get_within(): - points = copc.Points(3, (1, 1, 1), copc.Vector3.DefaultOffset()) + points = copc.Points(6, (1, 1, 1), copc.Vector3.DefaultOffset()) # generate points for i in range(2000): p = points.CreatePoint() - p.X = random.uniform(0, 5) - p.Y = random.uniform(0, 5) - p.Z = random.uniform(0, 5) + p.x = random.uniform(0, 5) + p.y = random.uniform(0, 5) + p.z = random.uniform(0, 5) points.AddPoint(p) box = copc.Box(0, 0, 0, 2.5, 2.5, 2.5) @@ -320,15 +334,15 @@ def test_points_accessors(): num_points = 2000 for i in range(num_points): p = points.CreatePoint() - p.X = i - p.Y = i * 3 - p.Z = i - 80 - p.Classification = i * 255 // num_points - p.PointSourceID = i * 255 // num_points + p.x = i + p.y = i * 3 + p.z = i - 80 + p.classification = i * 255 // num_points + p.point_source_id = i * 255 // num_points points.AddPoint(p) for i, (x, y, z, classification, point_source_id) in enumerate( - zip(points.X, points.Y, points.Z, points.Classification, points.PointSourceID) + zip(points.x, points.y, points.z, points.classification, points.point_source_id) ): assert x == i assert y == i * 3 @@ -337,41 +351,41 @@ def test_points_accessors(): assert point_source_id == i * 255 // num_points # test slice - assert points.X[5:10] == [x for x in range(5, 10)] - assert points.Y[-10:] == [x * 3 for x in range(1990, 2000)] - assert points.Z[:10] == [x - 80 for x in range(0, 10)] - assert points.Classification[:10] == [x * 255 // num_points for x in range(0, 10)] - assert points.PointSourceID[:10] == [x * 255 // num_points for x in range(0, 10)] + assert points.x[5:10] == [x for x in range(5, 10)] + assert points.y[-10:] == [x * 3 for x in range(1990, 2000)] + assert points.z[:10] == [x - 80 for x in range(0, 10)] + assert points.classification[:10] == [x * 255 // num_points for x in range(0, 10)] + assert points.point_source_id[:10] == [x * 255 // num_points for x in range(0, 10)] # test index setter for i in range(len(points)): p = points[i] - p.X = 20 - p.Y = 30 - p.Z = 40 - p.Classification = 50 - p.PointSourceID = 50 - - assert all([x == 20 for x in points.X]) - assert all([y == 30 for y in points.Y]) - assert all([z == 40 for z in points.Z]) - assert all([classification == 50 for classification in points.Classification]) - assert all([point_source_id == 50 for point_source_id in points.PointSourceID]) + p.x = 20 + p.y = 30 + p.z = 40 + p.classification = 50 + p.point_source_id = 50 + + assert all([x == 20 for x in points.x]) + assert all([y == 30 for y in points.y]) + assert all([z == 40 for z in points.z]) + assert all([classification == 50 for classification in points.classification]) + assert all([point_source_id == 50 for point_source_id in points.point_source_id]) # test iterator setter for p in points: - p.X = -50 - p.Y = -60 - p.Z = -70 + p.x = -50 + p.y = -60 + p.z = -70 - assert all([x == -50 for x in points.X]) - assert all([y == -60 for y in points.Y]) - assert all([z == -70 for z in points.Z]) + assert all([x == -50 for x in points.x]) + assert all([y == -60 for y in points.y]) + assert all([z == -70 for z in points.z]) def test_points_indexer_setter(): points = copc.Points( - 3, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), 4 + 6, copc.Vector3.DefaultScale(), copc.Vector3.DefaultOffset(), 4 ) # generate points @@ -379,39 +393,39 @@ def test_points_indexer_setter(): for i in range(num_points): points.AddPoint(points.CreatePoint()) - assert all([x == 0 for x in points.X]) - assert all([y == 0 for y in points.Y]) - assert all([z == 0 for z in points.Z]) + assert all([x == 0 for x in points.x]) + assert all([y == 0 for y in points.y]) + assert all([z == 0 for z in points.z]) - points[0].X = 20 - points[0].Y = 40 - points[0].Z = 80 - points[2].Intensity = 60000 + points[0].x = 20 + points[0].y = 40 + points[0].z = 80 + points[2].intensity = 60000 - assert points[0].X == 20 - assert points[0].Y == 40 - assert points[0].Z == 80 - assert points[2].Intensity == 60000 + assert points[0].x == 20 + assert points[0].y == 40 + assert points[0].z == 80 + assert points[2].intensity == 60000 # test slicing setters - points[:].X = [2] * len(points) - points[:].Y = [4] * len(points) - points[:].Z = [8] * len(points) + points[:].x = [2] * len(points) + points[:].y = [4] * len(points) + points[:].z = [8] * len(points) - assert all([x == 2 for x in points.X]) - assert all([x == 4 for x in points.Y]) - assert all([x == 8 for x in points.Z]) + assert all([x == 2 for x in points.x]) + assert all([x == 4 for x in points.y]) + assert all([x == 8 for x in points.z]) # TODO: Allow the user to set all values the same - points[1000:].X = [-2] * 1000 - points[1500:1600].Y = [-4] * 100 - points[-5:].Z = [-8] * 5 + points[1000:].x = [-2] * 1000 + points[1500:1600].y = [-4] * 100 + points[-5:].z = [-8] * 5 - assert all([x == -2 for i, x in enumerate(points.X) if i >= 1000]) - assert all([x != -2 for i, x in enumerate(points.X) if not i >= 1000]) - assert all([x == -4 for i, x in enumerate(points.Y) if i >= 1500 and i < 1600]) + assert all([x == -2 for i, x in enumerate(points.x) if i >= 1000]) + assert all([x != -2 for i, x in enumerate(points.x) if not i >= 1000]) + assert all([x == -4 for i, x in enumerate(points.y) if i >= 1500 and i < 1600]) assert all( - [x != -4 for i, x in enumerate(points.Y) if not (i >= 1500 and i < 1600)] + [x != -4 for i, x in enumerate(points.y) if not (i >= 1500 and i < 1600)] ) - assert all([x == -8 for i, x in enumerate(points.Z) if i >= len(points) - 5]) - assert all([x != -8 for i, x in enumerate(points.Z) if not (i >= len(points) - 5)]) + assert all([x == -8 for i, x in enumerate(points.z) if i >= len(points) - 5]) + assert all([x != -8 for i, x in enumerate(points.z) if not (i >= len(points) - 5)]) diff --git a/test/reader_node_test.cpp b/test/reader_node_test.cpp index 7341a17f..225e15d3 100644 --- a/test/reader_node_test.cpp +++ b/test/reader_node_test.cpp @@ -14,7 +14,7 @@ TEST_CASE("GetPointData Test", "[Reader] ") { FileReader reader("autzen-classified.copc.laz"); - int record_length = reader.GetLasHeader().point_record_length; + int record_length = reader.CopcConfig().LasHeader().PointRecordLength(); { auto key = VoxelKey(0, 0, 0, 0); @@ -26,15 +26,15 @@ TEST_CASE("GetPointData Test", "[Reader] ") int x; std::memcpy(&x, point_buff, sizeof(x)); - REQUIRE(x == -144147); + REQUIRE(x == -113822); std::memcpy(&x, point_buff + record_length, sizeof(x)); - REQUIRE(x == -172520); + REQUIRE(x == -215872); std::memcpy(&x, point_buff + record_length * 2, sizeof(x)); - REQUIRE(x == -121603); + REQUIRE(x == -94105); std::memcpy(&x, point_buff + record_length * 3, sizeof(x)); - REQUIRE(x == -49828); + REQUIRE(x == -141210); std::memcpy(&x, point_buff + record_length * 4, sizeof(x)); - REQUIRE(x == -138082); + REQUIRE(x == -156093); } { @@ -76,32 +76,27 @@ TEST_CASE("GetPoints Test", "[Reader] ") REQUIRE(points.size() == hier_entry.point_count); // Getters - REQUIRE(points[0]->UnscaledX() == -144147); - REQUIRE(points[0]->UnscaledY() == -13541); - REQUIRE(points[0]->UnscaledZ() == -227681); + REQUIRE(points[0]->UnscaledX() == -113822); + REQUIRE(points[0]->UnscaledY() == -118589); + REQUIRE(points[0]->UnscaledZ() == -221965); REQUIRE(points[0]->Intensity() == 11); REQUIRE(points[0]->NumberOfReturns() == 3); REQUIRE(points[0]->ReturnNumber() == 1); REQUIRE(points[0]->ScanDirectionFlag() == true); REQUIRE(points[0]->EdgeOfFlightLineFlag() == false); REQUIRE(points[0]->Classification() == 5); - REQUIRE(points[0]->ScanAngleRank() == 5); - REQUIRE(points[0]->ScanAngle() == 5.0); + REQUIRE(points[0]->ScanAngle() == 1667); + REQUIRE_THAT(points[0]->ScanAngleDegrees(), Catch::Matchers::WithinAbs(10, 0.01)); REQUIRE(points[0]->UserData() == 124); - REQUIRE(points[0]->PointSourceID() == 7330); - REQUIRE(points[0]->GPSTime() > 247570); - REQUIRE(points[0]->GPSTime() < 247570.5); - REQUIRE(points[0]->Red() == 46); - REQUIRE(points[0]->Green() == 60); - REQUIRE(points[0]->Blue() == 92); - REQUIRE(points[0]->PointFormatID() == 3); + REQUIRE(points[0]->PointSourceId() == 7327); + REQUIRE_THAT(points[0]->GPSTime(), Catch::Matchers::WithinAbs(246098.8141472744, 0.0001)); + REQUIRE(points[0]->Red() == 16896); + REQUIRE(points[0]->Green() == 65280); + REQUIRE(points[0]->Blue() == 1536); + REQUIRE(points[0]->PointFormatId() == 7); REQUIRE(points[0]->PointRecordLength() == 36); - REQUIRE(points[0]->ExtraBytes().size() == 2); - REQUIRE_THROWS(points[0]->NIR()); - REQUIRE_THROWS(points[0]->ExtendedFlagsBitFields()); - REQUIRE_THROWS(points[0]->ExtendedReturnsBitFields()); - REQUIRE_THROWS(points[0]->ScannerChannel()); - REQUIRE_THROWS(points[0]->ExtendedScanAngle()); + REQUIRE(points[0]->ExtraBytes().empty()); + REQUIRE_THROWS(points[0]->Nir()); // Setters points[0]->UnscaledX(std::numeric_limits::max()); @@ -113,18 +108,14 @@ TEST_CASE("GetPoints Test", "[Reader] ") points[0]->ScanDirectionFlag(false); points[0]->EdgeOfFlightLineFlag(true); points[0]->Classification(31); - points[0]->ScanAngleRank(90); + points[0]->ScanAngle(90); points[0]->UserData(std::numeric_limits::max()); - points[0]->PointSourceID(std::numeric_limits::max()); + points[0]->PointSourceId(std::numeric_limits::max()); points[0]->GPSTime(std::numeric_limits::max()); points[0]->Red(std::numeric_limits::max()); points[0]->Green(std::numeric_limits::max()); points[0]->Blue(std::numeric_limits::max()); - REQUIRE_THROWS(points[0]->NIR(std::numeric_limits::max())); - REQUIRE_THROWS(points[0]->ExtendedFlagsBitFields(std::numeric_limits::max())); - REQUIRE_THROWS(points[0]->ExtendedReturnsBitFields(std::numeric_limits::max())); - REQUIRE_THROWS(points[0]->ScannerChannel(3)); - REQUIRE_THROWS(points[0]->ScanAngle(std::numeric_limits::max())); + REQUIRE_THROWS(points[0]->Nir(std::numeric_limits::max())); REQUIRE(points[0]->UnscaledX() == std::numeric_limits::max()); REQUIRE(points[0]->UnscaledY() == std::numeric_limits::max()); @@ -135,9 +126,9 @@ TEST_CASE("GetPoints Test", "[Reader] ") REQUIRE(points[0]->ScanDirectionFlag() == false); REQUIRE(points[0]->EdgeOfFlightLineFlag() == true); REQUIRE(points[0]->Classification() == 31); - REQUIRE(points[0]->ScanAngleRank() == 90); + REQUIRE(points[0]->ScanAngle() == 90); REQUIRE(points[0]->UserData() == std::numeric_limits::max()); - REQUIRE(points[0]->PointSourceID() == std::numeric_limits::max()); + REQUIRE(points[0]->PointSourceId() == std::numeric_limits::max()); REQUIRE(points[0]->GPSTime() == std::numeric_limits::max()); REQUIRE(points[0]->Red() == std::numeric_limits::max()); REQUIRE(points[0]->Green() == std::numeric_limits::max()); diff --git a/test/reader_test.cpp b/test/reader_test.cpp index 8bd0b868..a268fe4d 100644 --- a/test/reader_test.cpp +++ b/test/reader_test.cpp @@ -14,35 +14,68 @@ TEST_CASE("Reader tests", "[Reader]") FileReader reader("autzen-classified.copc.laz"); - SECTION("GetCopc Test") + SECTION("GetHeader Test") { - auto copc = reader.GetCopcHeader(); - REQUIRE(copc.span == 128); - REQUIRE(copc.root_hier_offset == 93169718); - REQUIRE(copc.root_hier_size == 8896); - REQUIRE(copc.laz_vlr_offset == 643); - REQUIRE(copc.laz_vlr_size == 58); - REQUIRE(copc.wkt_vlr_offset == 755); - REQUIRE(copc.wkt_vlr_size == 993); - REQUIRE(copc.eb_vlr_offset == 1802); - REQUIRE(copc.eb_vlr_size == 384); + auto header = reader.CopcConfig().LasHeader(); + REQUIRE(header.PointFormatId() == 7); + REQUIRE(header.PointCount() == 10653336); + REQUIRE(header.PointRecordLength() == 36); + REQUIRE(header.EbByteSize() == 0); } - SECTION("GetHeader Test") + SECTION("GetCopcInfo Test") { - auto header = reader.GetLasHeader(); - REQUIRE(header.header_size == 375); - REQUIRE(header.point_format_id == 3); - REQUIRE(header.point_count == 10653336); - REQUIRE(header.point_record_length == 36); - REQUIRE(header.NumExtraBytes() == 2); + auto copc_info = reader.CopcConfig().CopcInfo(); + REQUIRE_THAT(copc_info.center_x, Catch::Matchers::WithinAbs(637905.5448, 0.0001)); + REQUIRE_THAT(copc_info.center_y, Catch::Matchers::WithinAbs(851209.9048, 0.0001)); + REQUIRE_THAT(copc_info.center_z, Catch::Matchers::WithinAbs(2733.8948, 0.0001)); + REQUIRE_THAT(copc_info.halfsize, Catch::Matchers::WithinAbs(2327.7548, 0.0001)); + REQUIRE_THAT(copc_info.spacing, Catch::Matchers::WithinAbs(36.3711, 0.0001)); + REQUIRE(copc_info.root_hier_offset == 73017045); + REQUIRE(copc_info.root_hier_size == 8896); + } + + SECTION("GetCopcExtents Test") + { + auto copc_extents = reader.CopcConfig().CopcExtents(); + REQUIRE(!copc_extents.HasExtendedStats()); + REQUIRE(copc_extents.PointFormatId() == 7); + REQUIRE_THAT(copc_extents.Intensity()->minimum, Catch::Matchers::WithinAbs(0, 0.0001)); + REQUIRE_THAT(copc_extents.Intensity()->maximum, Catch::Matchers::WithinAbs(254, 0.0001)); + REQUIRE_THAT(copc_extents.ReturnNumber()->minimum, Catch::Matchers::WithinAbs(1, 0.0001)); + REQUIRE_THAT(copc_extents.ReturnNumber()->maximum, Catch::Matchers::WithinAbs(4, 0.0001)); + REQUIRE_THAT(copc_extents.NumberOfReturns()->minimum, Catch::Matchers::WithinAbs(1, 0.0001)); + REQUIRE_THAT(copc_extents.NumberOfReturns()->maximum, Catch::Matchers::WithinAbs(4, 0.0001)); + REQUIRE_THAT(copc_extents.ScannerChannel()->minimum, Catch::Matchers::WithinAbs(0, 0.0001)); + REQUIRE_THAT(copc_extents.ScannerChannel()->maximum, Catch::Matchers::WithinAbs(0, 0.0001)); + REQUIRE_THAT(copc_extents.ScanDirectionFlag()->minimum, Catch::Matchers::WithinAbs(0, 0.0001)); + REQUIRE_THAT(copc_extents.ScanDirectionFlag()->maximum, Catch::Matchers::WithinAbs(1, 0.0001)); + REQUIRE_THAT(copc_extents.EdgeOfFlightLine()->minimum, Catch::Matchers::WithinAbs(0, 0.0001)); + REQUIRE_THAT(copc_extents.EdgeOfFlightLine()->maximum, Catch::Matchers::WithinAbs(0, 0.0001)); + REQUIRE_THAT(copc_extents.Classification()->minimum, Catch::Matchers::WithinAbs(0, 0.0001)); + REQUIRE_THAT(copc_extents.Classification()->maximum, Catch::Matchers::WithinAbs(77, 0.0001)); + REQUIRE_THAT(copc_extents.UserData()->minimum, Catch::Matchers::WithinAbs(115, 0.0001)); + REQUIRE_THAT(copc_extents.UserData()->maximum, Catch::Matchers::WithinAbs(156, 0.0001)); + REQUIRE_THAT(copc_extents.ScanAngle()->minimum, Catch::Matchers::WithinAbs(-21, 0.0001)); + REQUIRE_THAT(copc_extents.ScanAngle()->maximum, Catch::Matchers::WithinAbs(20, 0.0001)); + REQUIRE_THAT(copc_extents.PointSourceId()->minimum, Catch::Matchers::WithinAbs(7326, 0.0001)); + REQUIRE_THAT(copc_extents.PointSourceId()->maximum, Catch::Matchers::WithinAbs(7334, 0.0001)); + REQUIRE_THAT(copc_extents.GpsTime()->minimum, Catch::Matchers::WithinAbs(245369.89656857715, 0.0001)); + REQUIRE_THAT(copc_extents.GpsTime()->maximum, Catch::Matchers::WithinAbs(249783.70312432514, 0.0001)); + REQUIRE_THAT(copc_extents.Red()->minimum, Catch::Matchers::WithinAbs(4352, 0.0001)); + REQUIRE_THAT(copc_extents.Red()->maximum, Catch::Matchers::WithinAbs(65280, 0.0001)); + REQUIRE_THAT(copc_extents.Green()->minimum, Catch::Matchers::WithinAbs(0, 0.0001)); + REQUIRE_THAT(copc_extents.Green()->maximum, Catch::Matchers::WithinAbs(65280, 0.0001)); + REQUIRE_THAT(copc_extents.Blue()->minimum, Catch::Matchers::WithinAbs(1536, 0.0001)); + REQUIRE_THAT(copc_extents.Blue()->maximum, Catch::Matchers::WithinAbs(65280, 0.0001)); + REQUIRE_THROWS(copc_extents.Nir()); + REQUIRE(copc_extents.ExtraBytes().empty()); } SECTION("WKT") { - auto wkt = reader.GetWkt(); - REQUIRE(!wkt.empty()); - REQUIRE(wkt.rfind("COMPD_CS[", 0) == 0); + auto wkt = reader.CopcConfig().Wkt(); + REQUIRE(wkt.empty()); } } @@ -53,33 +86,29 @@ TEST_CASE("Reader tests", "[Reader]") Reader reader(&in_stream); - SECTION("GetCopc Test") + SECTION("GetCopcInfo Test") { - auto copc = reader.GetCopcHeader(); - REQUIRE(copc.span == 128); - REQUIRE(copc.root_hier_offset == 93169718); - REQUIRE(copc.root_hier_size == 8896); - REQUIRE(copc.laz_vlr_offset == 643); - REQUIRE(copc.laz_vlr_size == 58); - REQUIRE(copc.wkt_vlr_offset == 755); - REQUIRE(copc.wkt_vlr_size == 993); - REQUIRE(copc.eb_vlr_offset == 1802); - REQUIRE(copc.eb_vlr_size == 384); + auto copc_info = reader.CopcConfig().CopcInfo(); + REQUIRE_THAT(copc_info.center_x, Catch::Matchers::WithinAbs(637905.5448828125, 0.0001)); + REQUIRE_THAT(copc_info.center_y, Catch::Matchers::WithinAbs(851209.9048828125, 0.0001)); + REQUIRE_THAT(copc_info.center_z, Catch::Matchers::WithinAbs(2733.8948828125, 0.0001)); + REQUIRE_THAT(copc_info.halfsize, Catch::Matchers::WithinAbs(2327.7548828125, 0.0001)); + REQUIRE_THAT(copc_info.spacing, Catch::Matchers::WithinAbs(36.3711700439, 0.0001)); + REQUIRE(copc_info.root_hier_offset == 73017045); + REQUIRE(copc_info.root_hier_size == 8896); } SECTION("GetHeader Test") { - auto header = reader.GetLasHeader(); - REQUIRE(header.header_size == 375); - REQUIRE(header.point_format_id == 3); - REQUIRE(header.point_count == 10653336); + auto header = reader.CopcConfig().LasHeader(); + REQUIRE(header.PointFormatId() == 7); + REQUIRE(header.PointCount() == 10653336); } SECTION("WKT") { - auto wkt = reader.GetWkt(); - REQUIRE(!wkt.empty()); - REQUIRE(wkt.rfind("COMPD_CS[", 0) == 0); + auto wkt = reader.CopcConfig().Wkt(); + REQUIRE(wkt.empty()); } } } @@ -90,19 +119,19 @@ TEST_CASE("FindKey Check", "[Reader]") { FileReader reader("autzen-classified.copc.laz"); - auto key = VoxelKey::BaseKey(); + auto key = VoxelKey::RootKey(); auto hier_entry = reader.FindNode(key); REQUIRE(hier_entry.IsValid() == true); REQUIRE(hier_entry.key == key); - REQUIRE(hier_entry.point_count == 61022); + REQUIRE(hier_entry.point_count == 60978); key = VoxelKey(5, 9, 7, 0); hier_entry = reader.FindNode(key); REQUIRE(hier_entry.IsValid() == true); REQUIRE(hier_entry.key == key); - REQUIRE(hier_entry.point_count == 12019); + REQUIRE(hier_entry.point_count == 12021); } } @@ -112,18 +141,12 @@ TEST_CASE("GetExtraByteVlrs Test", "[Reader]") { FileReader reader("autzen-classified.copc.laz"); - auto eb_vlr = reader.GetExtraByteVlr(); - REQUIRE(eb_vlr.items.size() == 2); - - REQUIRE(eb_vlr.items[0].data_type == 1); - REQUIRE(eb_vlr.items[0].name == "FIELD_0"); - - REQUIRE(eb_vlr.items[1].data_type == 1); - REQUIRE(eb_vlr.items[1].name == "FIELD_1"); + auto eb_vlr = reader.CopcConfig().ExtraBytesVlr(); + REQUIRE(eb_vlr.items.size() == 0); } } -TEST_CASE("GetAllChildren Test", "[Reader]") +TEST_CASE("GetAllChildrenOfPage Test", "[Reader]") { GIVEN("A valid file path") { @@ -131,19 +154,19 @@ TEST_CASE("GetAllChildren Test", "[Reader]") { // Get root key - auto nodes = reader.GetAllChildren(); + auto nodes = reader.GetAllNodes(); REQUIRE(nodes.size() == 278); } { // Get an invalid key - auto nodes = reader.GetAllChildren(VoxelKey::InvalidKey()); + auto nodes = reader.GetAllChildrenOfPage(VoxelKey::InvalidKey()); REQUIRE(nodes.empty()); } { // Get an existing key - auto nodes = reader.GetAllChildren(VoxelKey(5, 9, 7, 0)); + auto nodes = reader.GetAllChildrenOfPage(VoxelKey(5, 9, 7, 0)); REQUIRE(nodes.size() == 1); REQUIRE(nodes[0].IsValid()); REQUIRE(nodes[0].key == VoxelKey(5, 9, 7, 0)); @@ -151,7 +174,7 @@ TEST_CASE("GetAllChildren Test", "[Reader]") { // Get a non-existing key - auto nodes = reader.GetAllChildren(VoxelKey(20, 20, 20, 20)); + auto nodes = reader.GetAllChildrenOfPage(VoxelKey(20, 20, 20, 20)); REQUIRE(nodes.empty()); } } @@ -161,7 +184,7 @@ TEST_CASE("GetAllChildren Test", "[Reader]") TEST_CASE("GetAllPoints Test", "[Reader]") { // FileReader reader("autzen-classified.copc.laz"); - // REQUIRE(reader.GetAllPoints().Get().size() == reader.GetLasHeader().point_count); + // REQUIRE(reader.GetAllPoints().Get().size() == reader.CopcFile().LasHeader().point_count); } TEST_CASE("Point Error Handling Test", "[Reader]") @@ -194,18 +217,18 @@ TEST_CASE("Spatial Query Functions", "[Reader]") FileReader reader("autzen-classified.copc.laz"); // Make horizontal 2D box of [400,400] roughly in the middle of the point cloud. - auto middle = (reader.GetLasHeader().max + reader.GetLasHeader().min) / 2; + auto middle = (reader.CopcConfig().LasHeader().max + reader.CopcConfig().LasHeader().min) / 2; Box middle_box(middle.x - 200, middle.y - 200, middle.x + 200, middle.y + 200); SECTION("GetNodesWithinBox") { // Check that no nodes fit in a zero-sized box - auto subset_nodes = reader.GetNodesWithinBox(Box::ZeroBox()); + auto subset_nodes = reader.GetNodesWithinBox(Box::EmptyBox()); REQUIRE(subset_nodes.empty()); // Check that all nodes fit in a max-sized box subset_nodes = reader.GetNodesWithinBox(Box::MaxBox()); - auto all_nodes = reader.GetAllChildren(); + auto all_nodes = reader.GetAllNodes(); REQUIRE(subset_nodes.size() == all_nodes.size()); } @@ -219,13 +242,13 @@ TEST_CASE("Spatial Query Functions", "[Reader]") { { // Check that no points fit in a zero-sized box - auto subset_points = reader.GetPointsWithinBox(Box::ZeroBox()); + auto subset_points = reader.GetPointsWithinBox(Box::EmptyBox()); REQUIRE(subset_points.Get().empty()); } { // TODO[Leo]: Make this test optional // Check that all points fit in a box sized from header - // auto header = reader.GetLasHeader(); + // auto header = reader.CopcFile().LasHeader(); // auto subset_points = reader.GetPointsWithinBox(Box(std::floor(header.min.x), // std::floor(header.min.y), // std::floor(header.min.z), @@ -261,6 +284,6 @@ TEST_CASE("Spatial Query Functions", "[Reader]") { auto subset_nodes = reader.GetNodesWithinResolution(3); REQUIRE(subset_nodes.size() == 257); - REQUIRE(reader.GetNodesWithinResolution(0).size() == reader.GetAllChildren().size()); + REQUIRE(reader.GetNodesWithinResolution(0).size() == reader.GetAllNodes().size()); } } diff --git a/test/reader_test.py b/test/reader_test.py index b8f15f91..09a7d896 100644 --- a/test/reader_test.py +++ b/test/reader_test.py @@ -7,70 +7,102 @@ def test_reader(): # Given a valid file path reader = copc.FileReader("autzen-classified.copc.laz") - # GetLasHeader Test - copc_header = reader.GetCopcHeader() - assert copc_header.span == 128 - assert copc_header.root_hier_offset == 93169718 - assert copc_header.root_hier_size == 8896 - assert copc_header.laz_vlr_offset == 643 - assert copc_header.laz_vlr_size == 58 - assert copc_header.wkt_vlr_offset == 755 - assert copc_header.wkt_vlr_size == 993 - assert copc_header.eb_vlr_offset == 1802 - assert copc_header.eb_vlr_size == 384 - - # GetLasHeader Test - header = reader.GetLasHeader() - assert header.header_size == 375 - assert header.point_format_id == 3 + # LasHeader Test + header = reader.copc_config.las_header + assert header.point_format_id == 7 assert header.point_count == 10653336 assert header.point_record_length == 36 - assert header.num_extra_bytes == 2 + assert header.EbByteSize() == 0 + + # GetCopcInfo Test + copc_info = reader.copc_config.copc_info + assert copc_info.center_x == pytest.approx(637905.5448, 0.0001) + assert copc_info.center_y == pytest.approx(851209.9048, 0.0001) + assert copc_info.center_z == pytest.approx(2733.8948, 0.0001) + assert copc_info.halfsize == pytest.approx(2327.7548, 0.0001) + assert copc_info.spacing == pytest.approx(36.3711, 0.0001) + assert copc_info.root_hier_offset == 73017045 + assert copc_info.root_hier_size == 8896 + + # GetCopcExtents Test + copc_extents = reader.copc_config.copc_extents + assert ~copc_extents.has_extended_stats + assert copc_extents.point_format_id == 7 + assert copc_extents.intensity.minimum == pytest.approx(0, 0.0001) + assert copc_extents.intensity.maximum == pytest.approx(254, 0.0001) + assert copc_extents.return_number.minimum == pytest.approx(1, 0.0001) + assert copc_extents.return_number.maximum == pytest.approx(4, 0.0001) + assert copc_extents.number_of_returns.minimum == pytest.approx(1, 0.0001) + assert copc_extents.number_of_returns.maximum == pytest.approx(4, 0.0001) + assert copc_extents.scanner_channel.minimum == pytest.approx(0, 0.0001) + assert copc_extents.scanner_channel.maximum == pytest.approx(0, 0.0001) + assert copc_extents.scan_direction_flag.minimum == pytest.approx(0, 0.0001) + assert copc_extents.scan_direction_flag.maximum == pytest.approx(1, 0.0001) + assert copc_extents.edge_of_flight_line.minimum == pytest.approx(0, 0.0001) + assert copc_extents.edge_of_flight_line.maximum == pytest.approx(0, 0.0001) + assert copc_extents.classification.minimum == pytest.approx(0, 0.0001) + assert copc_extents.classification.maximum == pytest.approx(77, 0.0001) + assert copc_extents.user_data.minimum == pytest.approx(115, 0.0001) + assert copc_extents.user_data.maximum == pytest.approx(156, 0.0001) + assert copc_extents.scan_angle.minimum == pytest.approx(-21, 0.0001) + assert copc_extents.scan_angle.maximum == pytest.approx(20, 0.0001) + assert copc_extents.point_source_id.minimum == pytest.approx(7326, 0.0001) + assert copc_extents.point_source_id.maximum == pytest.approx(7334, 0.0001) + assert copc_extents.gps_time.minimum == pytest.approx(245369.89656857715, 0.0001) + assert copc_extents.gps_time.maximum == pytest.approx(249783.70312432514, 0.0001) + assert copc_extents.red.minimum == pytest.approx(4352, 0.0001) + assert copc_extents.red.maximum == pytest.approx(65280, 0.0001) + assert copc_extents.green.minimum == pytest.approx(0, 0.0001) + assert copc_extents.green.maximum == pytest.approx(65280, 0.0001) + assert copc_extents.blue.minimum == pytest.approx(1536, 0.0001) + assert copc_extents.blue.maximum == pytest.approx(65280, 0.0001) + with pytest.raises(RuntimeError): + assert copc_extents.nir + assert len(copc_extents.extra_bytes) == 0 # WKT Test - wkt = reader.GetWkt() - assert len(wkt) > 0 - assert wkt.startswith("COMPD_CS[") + wkt = reader.copc_config.wkt + assert len(wkt) == 0 def test_find_key(): # Given a valid file path reader = copc.FileReader("autzen-classified.copc.laz") - key = copc.VoxelKey.BaseKey() + key = copc.VoxelKey.RootKey() hier_entry = reader.FindNode(key) assert hier_entry.IsValid() == True assert hier_entry.key == key - assert hier_entry.point_count == 61022 + assert hier_entry.point_count == 60978 - key = copc.VoxelKey(5, 9, 7, 0) + key = (5, 9, 7, 0) # Test implicit conversion of key to tuple hier_entry = reader.FindNode(key) assert hier_entry.IsValid() == True assert hier_entry.key == key - assert hier_entry.point_count == 12019 + assert hier_entry.point_count == 12021 def test_get_all_children(): reader = copc.FileReader("autzen-classified.copc.laz") # Get root key - nodes = reader.GetAllChildren() + nodes = reader.GetAllNodes() assert len(nodes) == 278 # Get an invalid key - nodes = reader.GetAllChildren(copc.VoxelKey.InvalidKey()) + nodes = reader.GetAllChildrenOfPage(copc.VoxelKey.InvalidKey()) assert len(nodes) == 0 # Get an existing key - nodes = reader.GetAllChildren(copc.VoxelKey(5, 9, 7, 0)) + nodes = reader.GetAllChildrenOfPage((5, 9, 7, 0)) assert len(nodes) == 1 assert nodes[0].IsValid() - assert nodes[0].key == copc.VoxelKey(5, 9, 7, 0) + assert nodes[0].key == (5, 9, 7, 0) # Test implicit conversion of key to tuple # Get a non-existing key - nodes = reader.GetAllChildren(copc.VoxelKey(20, 20, 20, 20)) + nodes = reader.GetAllChildrenOfPage((20, 20, 20, 20)) assert len(nodes) == 0 @@ -78,7 +110,7 @@ def test_get_all_children(): # def test_get_all_points(): # reader = copc.FileReader("autzen-classified.copc.laz") -# assert len(reader.GetAllPoints()) == reader.GetLasHeader().point_count +# assert len(reader.GetAllPoints()) == reader.las_header.point_count def test_point_error_handling(): @@ -87,7 +119,7 @@ def test_point_error_handling(): invalid_node = copc.Node() with pytest.raises(RuntimeError): reader.GetPointData(node=invalid_node) - valid_node = reader.FindNode(copc.VoxelKey(5, 9, 7, 0)) + valid_node = reader.FindNode((5, 9, 7, 0)) reader.GetPointData(valid_node) assert len(reader.GetPointData(key=invalid_node.key)) == 0 @@ -110,18 +142,18 @@ def test_spatial_query_functions(): reader = copc.FileReader("autzen-classified.copc.laz") # Make horizontal 2D box of [400,400] roughly in the middle of the point cloud. - middle = (reader.GetLasHeader().max + reader.GetLasHeader().min) / 2 + middle = (reader.copc_config.las_header.max + reader.copc_config.las_header.min) / 2 middle_box = (middle.x - 200, middle.y - 200, middle.x + 200, middle.y + 200) # GetNodesWithinBox ## Check that no nodes fit in a zero-sized box - subset_nodes = reader.GetNodesWithinBox(copc.Box.ZeroBox()) + subset_nodes = reader.GetNodesWithinBox(copc.Box.EmptyBox()) assert len(subset_nodes) == 0 ## Check that all nodes fit in a max-sized box subset_nodes = reader.GetNodesWithinBox(copc.Box.MaxBox()) - all_nodes = reader.GetAllChildren() + all_nodes = reader.GetAllNodes() assert len(subset_nodes) == len(all_nodes) # GetNodesIntersectBox @@ -132,12 +164,12 @@ def test_spatial_query_functions(): # GetPointsWithinBox ## Check that no points fit in a zero-sized box - subset_points = reader.GetPointsWithinBox(copc.Box.ZeroBox()) + subset_points = reader.GetPointsWithinBox(copc.Box.EmptyBox()) assert len(subset_points) == 0 # TODO[Leo]: Make this test optional ## Check that all points fit in a box sized from header - # header = reader.GetLasHeader() + # header = reader.las_header # subset_points = reader.GetPointsWithinBox( # copc.Box( # math.floor(header.min.x), @@ -168,4 +200,4 @@ def test_spatial_query_functions(): # GetNodesWithinResolution subset_nodes = reader.GetNodesWithinResolution(3) assert len(subset_nodes) == 257 - assert len(reader.GetNodesWithinResolution(0)) == len(reader.GetAllChildren()) + assert len(reader.GetNodesWithinResolution(0)) == len(reader.GetAllNodes()) diff --git a/test/vector3_test.py b/test/vector3_test.py index d13344ea..49faba93 100644 --- a/test/vector3_test.py +++ b/test/vector3_test.py @@ -20,12 +20,12 @@ def test_vector3(): assert vec.y == 2.0 assert vec.z == 3.0 - vec = copc.Vector3().DefaultScale() + vec = copc.Vector3.DefaultScale() assert vec.x == 0.01 assert vec.y == 0.01 assert vec.z == 0.01 - vec = copc.Vector3().DefaultOffset() + vec = copc.Vector3.DefaultOffset() assert vec.x == 0.0 assert vec.y == 0.0 assert vec.z == 0.0 diff --git a/test/writer_node_test.cpp b/test/writer_node_test.cpp index ebb46046..50134eab 100644 --- a/test/writer_node_test.cpp +++ b/test/writer_node_test.cpp @@ -13,7 +13,7 @@ TEST_CASE("Writer Node Uncompressed", "[Writer]") { stringstream out_stream; - Writer::LasConfig cfg(3); + CopcConfigWriter cfg(7); Writer writer(out_stream, cfg); Page root_page = writer.GetRootPage(); @@ -21,16 +21,16 @@ TEST_CASE("Writer Node Uncompressed", "[Writer]") REQUIRE_THROWS(writer.AddNode(root_page, VoxelKey::InvalidKey(), std::vector())); REQUIRE_THROWS(writer.AddNode(root_page, VoxelKey(0, 0, 0, 0), std::vector())); - std::vector root_node(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3)); + std::vector root_node(first_20_pts, first_20_pts + sizeof(first_20_pts)); REQUIRE_NOTHROW(writer.AddNode(root_page, VoxelKey(0, 0, 0, 0), root_node)); writer.Close(); Reader reader(&out_stream); - REQUIRE(reader.GetCopcHeader().root_hier_offset > 0); - REQUIRE(reader.GetCopcHeader().root_hier_size == 32); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_offset > 0); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_size == 32); - auto node = reader.FindNode(VoxelKey::BaseKey()); + auto node = reader.FindNode(VoxelKey::RootKey()); REQUIRE(node.IsValid()); auto root_node_test = reader.GetPointData(node); @@ -41,29 +41,29 @@ TEST_CASE("Writer Node Uncompressed", "[Writer]") { stringstream out_stream; - Writer::LasConfig cfg(3); - Writer writer(out_stream, cfg, 256, "test_wkt"); + CopcConfigWriter cfg(7); + Writer writer(out_stream, cfg); Page root_page = writer.GetRootPage(); - std::vector twenty(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3)); + std::vector twenty(first_20_pts, first_20_pts + sizeof(first_20_pts)); REQUIRE_NOTHROW(writer.AddNode(root_page, VoxelKey(0, 0, 0, 0), twenty)); - std::vector twelve(twelve_pts_prdf3, twelve_pts_prdf3 + sizeof(twelve_pts_prdf3)); + std::vector twelve(next_12_pts, next_12_pts + sizeof(next_12_pts)); REQUIRE_NOTHROW(writer.AddNode(root_page, VoxelKey(1, 1, 1, 1), twelve)); - std::vector sixty(sixty_pts_prdf3, sixty_pts_prdf3 + sizeof(sixty_pts_prdf3)); + std::vector sixty(last_60_pts, last_60_pts + sizeof(last_60_pts)); REQUIRE_NOTHROW(writer.AddNode(root_page, VoxelKey(1, 1, 1, 0), sixty)); writer.Close(); std::string ostr = out_stream.str(); Reader reader(&out_stream); - REQUIRE(reader.GetCopcHeader().root_hier_offset > 0); - REQUIRE(reader.GetCopcHeader().root_hier_size == 32 * 3); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_offset > 0); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_size == 32 * 3); { - auto node = reader.FindNode(VoxelKey::BaseKey()); + auto node = reader.FindNode(VoxelKey::RootKey()); REQUIRE(node.IsValid()); auto node_data = reader.GetPointData(node); REQUIRE(node_data == twenty); @@ -88,20 +88,20 @@ TEST_CASE("Writer Node Uncompressed", "[Writer]") { stringstream out_stream; - Writer::LasConfig cfg(3); + CopcConfigWriter cfg(7); Writer writer(out_stream, cfg); Page root_page = writer.GetRootPage(); Page sub_page1 = writer.AddSubPage(root_page, VoxelKey(1, 0, 0, 0)); Page sub_page2 = writer.AddSubPage(root_page, VoxelKey(1, 1, 1, 1)); - std::vector twenty(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3)); + std::vector twenty(first_20_pts, first_20_pts + sizeof(first_20_pts)); REQUIRE_NOTHROW(writer.AddNode(root_page, VoxelKey(0, 0, 0, 0), twenty)); - std::vector twelve(twelve_pts_prdf3, twelve_pts_prdf3 + sizeof(twelve_pts_prdf3)); + std::vector twelve(next_12_pts, next_12_pts + sizeof(next_12_pts)); REQUIRE_NOTHROW(writer.AddNode(sub_page1, VoxelKey(1, 0, 0, 0), twelve)); - std::vector sixty(sixty_pts_prdf3, sixty_pts_prdf3 + sizeof(sixty_pts_prdf3)); + std::vector sixty(last_60_pts, last_60_pts + sizeof(last_60_pts)); REQUIRE_NOTHROW(writer.AddNode(sub_page2, VoxelKey(1, 1, 1, 1), sixty)); REQUIRE_NOTHROW(writer.AddNode(sub_page2, VoxelKey(2, 2, 2, 2), twenty)); @@ -122,8 +122,8 @@ TEST_CASE("Writer Node Uncompressed", "[Writer]") */ Reader reader(&out_stream); - REQUIRE(reader.GetCopcHeader().root_hier_offset > 0); - REQUIRE(reader.GetCopcHeader().root_hier_size == 32 * 3); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_offset > 0); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_size == 32 * 3); { auto sub_node = reader.FindNode(VoxelKey(2, 2, 2, 2)); @@ -158,77 +158,73 @@ TEST_CASE("Writer Node Compressed", "[Writer]") { stringstream out_stream; - Writer::LasConfig cfg(3); + CopcConfigWriter cfg(7); Writer writer(out_stream, cfg); Page root_page = writer.GetRootPage(); REQUIRE_THROWS(writer.AddNodeCompressed(root_page, VoxelKey::InvalidKey(), std::vector(), 0)); - std::vector root_node(twenty_pts_prdf3_compressed, - twenty_pts_prdf3_compressed + sizeof(twenty_pts_prdf3_compressed)); + std::vector root_node(first_20_pts_compressed, first_20_pts_compressed + sizeof(first_20_pts_compressed)); REQUIRE_NOTHROW(writer.AddNodeCompressed(root_page, VoxelKey(0, 0, 0, 0), root_node, 20)); writer.Close(); Reader reader(&out_stream); - REQUIRE(reader.GetCopcHeader().root_hier_offset > 0); - REQUIRE(reader.GetCopcHeader().root_hier_size == 32); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_offset > 0); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_size == 32); - auto node = reader.FindNode(VoxelKey::BaseKey()); + auto node = reader.FindNode(VoxelKey::RootKey()); REQUIRE(node.IsValid()); auto root_node_test = reader.GetPointData(node); - REQUIRE(root_node_test == std::vector(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3))); + REQUIRE(root_node_test == std::vector(first_20_pts, first_20_pts + sizeof(first_20_pts))); } SECTION("Add multiple") { stringstream out_stream; - Writer::LasConfig cfg(3); - Writer writer(out_stream, cfg, 256, "test_wkt"); + CopcConfigWriter cfg(7); + Writer writer(out_stream, cfg); Page root_page = writer.GetRootPage(); - std::vector twenty(twenty_pts_prdf3_compressed, - twenty_pts_prdf3_compressed + sizeof(twenty_pts_prdf3_compressed)); + std::vector twenty(first_20_pts_compressed, first_20_pts_compressed + sizeof(first_20_pts_compressed)); REQUIRE_NOTHROW(writer.AddNodeCompressed(root_page, VoxelKey(0, 0, 0, 0), twenty, 20)); - std::vector twelve(twelve_pts_prdf3_compressed, - twelve_pts_prdf3_compressed + sizeof(twelve_pts_prdf3_compressed)); + std::vector twelve(next_12_pts_compressed, next_12_pts_compressed + sizeof(next_12_pts_compressed)); REQUIRE_NOTHROW(writer.AddNodeCompressed(root_page, VoxelKey(1, 1, 1, 1), twelve, 12)); - std::vector sixty(sixty_pts_prdf3_compressed, - sixty_pts_prdf3_compressed + sizeof(sixty_pts_prdf3_compressed)); + std::vector sixty(last_60_pts_compressed, last_60_pts_compressed + sizeof(last_60_pts_compressed)); REQUIRE_NOTHROW(writer.AddNodeCompressed(root_page, VoxelKey(1, 1, 1, 0), sixty, 60)); writer.Close(); std::string ostr = out_stream.str(); Reader reader(&out_stream); - REQUIRE(reader.GetCopcHeader().root_hier_offset > 0); - REQUIRE(reader.GetCopcHeader().root_hier_size == 32 * 3); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_offset > 0); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_size == 32 * 3); { - auto sub_node = reader.FindNode(VoxelKey::BaseKey()); + auto sub_node = reader.FindNode(VoxelKey::RootKey()); REQUIRE(sub_node.IsValid()); auto sub_node_data = reader.GetPointData(sub_node); - REQUIRE(sub_node_data == std::vector(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3))); + REQUIRE(sub_node_data == std::vector(first_20_pts, first_20_pts + sizeof(first_20_pts))); } { auto sub_node = reader.FindNode(VoxelKey(1, 1, 1, 1)); REQUIRE(sub_node.IsValid()); auto sub_node_data = reader.GetPointData(sub_node); - REQUIRE(sub_node_data == std::vector(twelve_pts_prdf3, twelve_pts_prdf3 + sizeof(twelve_pts_prdf3))); + REQUIRE(sub_node_data == std::vector(next_12_pts, next_12_pts + sizeof(next_12_pts))); } { auto sub_node = reader.FindNode(VoxelKey(1, 1, 1, 0)); REQUIRE(sub_node.IsValid()); auto sub_node_data = reader.GetPointData(sub_node); - REQUIRE(sub_node_data == std::vector(sixty_pts_prdf3, sixty_pts_prdf3 + sizeof(sixty_pts_prdf3))); + REQUIRE(sub_node_data == std::vector(last_60_pts, last_60_pts + sizeof(last_60_pts))); } } @@ -236,23 +232,20 @@ TEST_CASE("Writer Node Compressed", "[Writer]") { stringstream out_stream; - Writer::LasConfig cfg(3); + CopcConfigWriter cfg(7); Writer writer(out_stream, cfg); Page root_page = writer.GetRootPage(); Page sub_page1 = writer.AddSubPage(root_page, VoxelKey(1, 0, 0, 0)); Page sub_page2 = writer.AddSubPage(root_page, VoxelKey(1, 1, 1, 1)); - std::vector twenty(twenty_pts_prdf3_compressed, - twenty_pts_prdf3_compressed + sizeof(twenty_pts_prdf3_compressed)); + std::vector twenty(first_20_pts_compressed, first_20_pts_compressed + sizeof(first_20_pts_compressed)); REQUIRE_NOTHROW(writer.AddNodeCompressed(root_page, VoxelKey(0, 0, 0, 0), twenty, 20)); - std::vector twelve(twelve_pts_prdf3_compressed, - twelve_pts_prdf3_compressed + sizeof(twelve_pts_prdf3_compressed)); + std::vector twelve(next_12_pts_compressed, next_12_pts_compressed + sizeof(next_12_pts_compressed)); REQUIRE_NOTHROW(writer.AddNodeCompressed(sub_page1, VoxelKey(1, 0, 0, 0), twelve, 12)); - std::vector sixty(sixty_pts_prdf3_compressed, - sixty_pts_prdf3_compressed + sizeof(sixty_pts_prdf3_compressed)); + std::vector sixty(last_60_pts_compressed, last_60_pts_compressed + sizeof(last_60_pts_compressed)); REQUIRE_NOTHROW(writer.AddNodeCompressed(sub_page2, VoxelKey(1, 1, 1, 1), sixty, 60)); REQUIRE_NOTHROW(writer.AddNodeCompressed(sub_page2, VoxelKey(2, 2, 2, 2), twenty, 20)); @@ -273,32 +266,32 @@ TEST_CASE("Writer Node Compressed", "[Writer]") */ Reader reader(&out_stream); - REQUIRE(reader.GetCopcHeader().root_hier_offset > 0); - REQUIRE(reader.GetCopcHeader().root_hier_size == 32 * 3); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_offset > 0); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_size == 32 * 3); { auto sub_node = reader.FindNode(VoxelKey(2, 2, 2, 2)); REQUIRE(sub_node.IsValid()); auto sub_node_data = reader.GetPointData(sub_node); - REQUIRE(sub_node_data == std::vector(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3))); + REQUIRE(sub_node_data == std::vector(first_20_pts, first_20_pts + sizeof(first_20_pts))); } { auto sub_node = reader.FindNode(VoxelKey(1, 1, 1, 1)); REQUIRE(sub_node.IsValid()); auto sub_node_data = reader.GetPointData(sub_node); - REQUIRE(sub_node_data == std::vector(sixty_pts_prdf3, sixty_pts_prdf3 + sizeof(sixty_pts_prdf3))); + REQUIRE(sub_node_data == std::vector(last_60_pts, last_60_pts + sizeof(last_60_pts))); } { auto sub_node = reader.FindNode(VoxelKey(1, 0, 0, 0)); REQUIRE(sub_node.IsValid()); auto sub_node_data = reader.GetPointData(sub_node); - REQUIRE(sub_node_data == std::vector(twelve_pts_prdf3, twelve_pts_prdf3 + sizeof(twelve_pts_prdf3))); + REQUIRE(sub_node_data == std::vector(next_12_pts, next_12_pts + sizeof(next_12_pts))); } { auto sub_node = reader.FindNode(VoxelKey(0, 0, 0, 0)); REQUIRE(sub_node.IsValid()); auto sub_node_data = reader.GetPointData(sub_node); - REQUIRE(sub_node_data == std::vector(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3))); + REQUIRE(sub_node_data == std::vector(first_20_pts, first_20_pts + sizeof(first_20_pts))); } } } diff --git a/test/writer_test.cpp b/test/writer_test.cpp index 945a6c5e..c9d895f2 100644 --- a/test/writer_test.cpp +++ b/test/writer_test.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -19,100 +20,133 @@ TEST_CASE("Writer Config Tests", "[Writer]") { string file_path = "writer_test.copc.laz"; - Writer::LasConfig cfg(0); + CopcConfigWriter cfg(6); FileWriter writer(file_path, cfg); - auto las_header = writer.GetLasHeader(); - REQUIRE(las_header.scale.z == 0.01); - REQUIRE(las_header.offset.z == 0); - REQUIRE(las_header.point_format_id == 0); + auto las_header = writer.CopcConfig()->LasHeader(); + REQUIRE(las_header->Scale().z == 0.01); + REQUIRE(las_header->Offset().z == 0); + REQUIRE(las_header->PointFormatId() == 6); writer.Close(); + + REQUIRE_THROWS(CopcConfigWriter(5)); + REQUIRE_THROWS(CopcConfigWriter(9)); } SECTION("Custom Config") { string file_path = "writer_test.copc.laz"; - Writer::LasConfig cfg(8, {2, 3, 4}, {-0.02, -0.03, -40.8}); - cfg.file_source_id = 200; + CopcConfigWriter cfg(8, {2, 3, 4}, {-0.02, -0.03, -40.8}); + cfg.LasHeader()->file_source_id = 200; // Test checks on string attributes - cfg.SystemIdentifier("test_string"); - REQUIRE(cfg.SystemIdentifier() == "test_string"); - REQUIRE_THROWS(cfg.SystemIdentifier(std::string(33, 'a'))); - cfg.GeneratingSoftware("test_string"); - REQUIRE(cfg.GeneratingSoftware() == "test_string"); - REQUIRE_THROWS(cfg.GeneratingSoftware(std::string(33, 'a'))); + cfg.LasHeader()->SystemIdentifier("test_string"); + REQUIRE(cfg.LasHeader()->SystemIdentifier() == "test_string"); + REQUIRE_THROWS(cfg.LasHeader()->SystemIdentifier(std::string(33, 'a'))); + cfg.LasHeader()->GeneratingSoftware("test_string"); + REQUIRE(cfg.LasHeader()->GeneratingSoftware() == "test_string"); + REQUIRE_THROWS(cfg.LasHeader()->GeneratingSoftware(std::string(33, 'a'))); FileWriter writer(file_path, cfg); - auto las_header = writer.GetLasHeader(); - REQUIRE(las_header.file_source_id == 200); - REQUIRE(las_header.point_format_id == 8); - REQUIRE(las_header.scale.x == 2); - REQUIRE(las_header.offset.x == -0.02); - REQUIRE(las_header.scale.y == 3); - REQUIRE(las_header.offset.y == -0.03); - REQUIRE(las_header.scale.z == 4); - REQUIRE(las_header.offset.z == -40.8); + auto las_header = writer.CopcConfig()->LasHeader(); + REQUIRE(las_header->file_source_id == 200); + REQUIRE(las_header->PointFormatId() == 8); + REQUIRE(las_header->Scale().x == 2); + REQUIRE(las_header->Offset().x == -0.02); + REQUIRE(las_header->Scale().y == 3); + REQUIRE(las_header->Offset().y == -0.03); + REQUIRE(las_header->Scale().z == 4); + REQUIRE(las_header->Offset().z == -40.8); writer.Close(); } - SECTION("COPC Span") + SECTION("COPC Spacing") { string file_path = "writer_test.copc.laz"; - Writer::LasConfig cfg(0); - FileWriter writer(file_path, cfg, 256); + { + CopcConfigWriter cfg(6); + cfg.CopcInfo()->spacing = 10; + FileWriter writer(file_path, cfg); - // todo: use Reader to check all of these - auto span = writer.GetCopcHeader().span; - REQUIRE(span == 256); + REQUIRE(writer.CopcConfig()->CopcInfo()->spacing == 10); + + writer.Close(); + } + FileReader reader(file_path); + REQUIRE(reader.CopcConfig().CopcInfo().spacing == 10); + } + + SECTION("Extents") + { + string file_path = "writer_test.copc.laz"; + + CopcConfigWriter cfg(6); + FileWriter writer(file_path, cfg); + + auto extents = writer.CopcConfig()->CopcExtents(); + extents->Intensity()->minimum = -1.0; + extents->Intensity()->maximum = 1; + + extents->Classification()->minimum = -std::numeric_limits::max(); + extents->Classification()->maximum = std::numeric_limits::max(); + + REQUIRE(writer.CopcConfig()->CopcExtents()->Intensity()->minimum == extents->Intensity()->minimum); + REQUIRE(writer.CopcConfig()->CopcExtents()->Intensity()->maximum == extents->Intensity()->maximum); + REQUIRE(writer.CopcConfig()->CopcExtents()->Classification()->minimum == + extents->Classification()->minimum); + REQUIRE(writer.CopcConfig()->CopcExtents()->Classification()->maximum == + extents->Classification()->maximum); writer.Close(); + // Test reading of extents FileReader reader(file_path); - REQUIRE(reader.GetCopcHeader().span == 256); + REQUIRE(reader.CopcConfig().CopcExtents().Intensity()->minimum == extents->Intensity()->minimum); + REQUIRE(reader.CopcConfig().CopcExtents().Intensity()->maximum == extents->Intensity()->maximum); + REQUIRE(reader.CopcConfig().CopcExtents().Classification()->minimum == extents->Classification()->minimum); + REQUIRE(reader.CopcConfig().CopcExtents().Classification()->maximum == extents->Classification()->maximum); } SECTION("WKT") { string file_path = "writer_test.copc.laz"; - Writer::LasConfig cfg(0); - FileWriter writer(file_path, cfg, 256, "TEST_WKT"); + CopcConfigWriter cfg(6, {}, {}, "TEST_WKT"); + FileWriter writer(file_path, cfg); - // todo: use Reader to check all of these - REQUIRE(writer.GetWkt() == "TEST_WKT"); + REQUIRE(writer.CopcConfig()->Wkt() == "TEST_WKT"); writer.Close(); FileReader reader(file_path); - REQUIRE(reader.GetWkt() == "TEST_WKT"); + REQUIRE(reader.CopcConfig().Wkt() == "TEST_WKT"); } - SECTION("Copy") { FileReader orig("autzen-classified.copc.laz"); string file_path = "writer_test.copc.laz"; - Writer::LasConfig cfg(orig.GetLasHeader(), orig.GetExtraByteVlr()); + auto cfg = orig.CopcConfig(); FileWriter writer(file_path, cfg); writer.Close(); FileReader reader(file_path); - REQUIRE(reader.GetLasHeader().file_source_id == orig.GetLasHeader().file_source_id); - REQUIRE(reader.GetLasHeader().global_encoding == orig.GetLasHeader().global_encoding); - REQUIRE(reader.GetLasHeader().creation_day == orig.GetLasHeader().creation_day); - REQUIRE(reader.GetLasHeader().creation_year == orig.GetLasHeader().creation_year); - REQUIRE(reader.GetLasHeader().file_source_id == orig.GetLasHeader().file_source_id); - REQUIRE(reader.GetLasHeader().point_format_id == orig.GetLasHeader().point_format_id); - REQUIRE(reader.GetLasHeader().point_record_length == orig.GetLasHeader().point_record_length); - REQUIRE(reader.GetLasHeader().point_count == 0); - REQUIRE(reader.GetLasHeader().scale == reader.GetLasHeader().scale); - REQUIRE(reader.GetLasHeader().offset == reader.GetLasHeader().offset); + REQUIRE(reader.CopcConfig().LasHeader().file_source_id == orig.CopcConfig().LasHeader().file_source_id); + REQUIRE(reader.CopcConfig().LasHeader().global_encoding == orig.CopcConfig().LasHeader().global_encoding); + REQUIRE(reader.CopcConfig().LasHeader().creation_day == orig.CopcConfig().LasHeader().creation_day); + REQUIRE(reader.CopcConfig().LasHeader().creation_year == orig.CopcConfig().LasHeader().creation_year); + REQUIRE(reader.CopcConfig().LasHeader().file_source_id == orig.CopcConfig().LasHeader().file_source_id); + REQUIRE(reader.CopcConfig().LasHeader().PointFormatId() == orig.CopcConfig().LasHeader().PointFormatId()); + REQUIRE(reader.CopcConfig().LasHeader().PointRecordLength() == + orig.CopcConfig().LasHeader().PointRecordLength()); + REQUIRE(reader.CopcConfig().LasHeader().PointCount() == 0); + REQUIRE(reader.CopcConfig().LasHeader().Scale() == reader.CopcConfig().LasHeader().Scale()); + REQUIRE(reader.CopcConfig().LasHeader().Offset() == reader.CopcConfig().LasHeader().Offset()); } } GIVEN("A valid output stream") @@ -121,13 +155,13 @@ TEST_CASE("Writer Config Tests", "[Writer]") { stringstream out_stream; - Writer::LasConfig cfg(0); + CopcConfigWriter cfg(6); Writer writer(out_stream, cfg); - auto las_header = writer.GetLasHeader(); - REQUIRE(las_header.scale.z == 0.01); - REQUIRE(las_header.offset.z == 0); - REQUIRE(las_header.point_format_id == 0); + auto las_header = writer.CopcConfig()->LasHeader(); + REQUIRE(las_header->Scale().z == 0.01); + REQUIRE(las_header->Offset().z == 0); + REQUIRE(las_header->PointFormatId() == 6); writer.Close(); @@ -135,26 +169,26 @@ TEST_CASE("Writer Config Tests", "[Writer]") REQUIRE(f.pointCount() == 0); REQUIRE(f.header().scale.z == 0.01); REQUIRE(f.header().offset.z == 0); - REQUIRE(f.header().point_format_id == 0); + REQUIRE(f.header().point_format_id == 6); } SECTION("Custom Config") { stringstream out_stream; - Writer::LasConfig cfg(8, {2, 3, 4}, {-0.02, -0.03, -40.8}); - cfg.file_source_id = 200; + CopcConfigWriter cfg(8, {2, 3, 4}, {-0.02, -0.03, -40.8}); + cfg.LasHeader()->file_source_id = 200; Writer writer(out_stream, cfg); - auto las_header = writer.GetLasHeader(); - REQUIRE(las_header.file_source_id == 200); - REQUIRE(las_header.point_format_id == 8); - REQUIRE(las_header.scale.x == 2); - REQUIRE(las_header.offset.x == -0.02); - REQUIRE(las_header.scale.y == 3); - REQUIRE(las_header.offset.y == -0.03); - REQUIRE(las_header.scale.z == 4); - REQUIRE(las_header.offset.z == -40.8); + auto las_header = writer.CopcConfig()->LasHeader(); + REQUIRE(las_header->file_source_id == 200); + REQUIRE(las_header->PointFormatId() == 8); + REQUIRE(las_header->Scale().x == 2); + REQUIRE(las_header->Offset().x == -0.02); + REQUIRE(las_header->Scale().y == 3); + REQUIRE(las_header->Offset().y == -0.03); + REQUIRE(las_header->Scale().z == 4); + REQUIRE(las_header->Offset().z == -40.8); writer.Close(); @@ -170,37 +204,37 @@ TEST_CASE("Writer Config Tests", "[Writer]") REQUIRE(f.header().offset.z == -40.8); } - SECTION("COPC Span") + SECTION("COPC Spacing") { stringstream out_stream; - Writer::LasConfig cfg(0); - Writer writer(out_stream, cfg, 256); + CopcConfigWriter cfg(6); + cfg.CopcInfo()->spacing = 10; + Writer writer(out_stream, cfg); // todo: use Reader to check all of these - auto span = writer.GetCopcHeader().span; - REQUIRE(span == 256); + auto spacing = writer.CopcConfig()->CopcInfo()->spacing; + REQUIRE(spacing == 10); writer.Close(); Reader reader(&out_stream); - REQUIRE(reader.GetCopcHeader().span == 256); + REQUIRE(reader.CopcConfig().CopcInfo().spacing == 10); } SECTION("WKT") { stringstream out_stream; - Writer::LasConfig cfg(0); - Writer writer(out_stream, cfg, 256, "TEST_WKT"); + CopcConfigWriter cfg(6, {}, {}, "TEST_WKT"); + Writer writer(out_stream, cfg); - // todo: use Reader to check all of these - REQUIRE(writer.GetWkt() == "TEST_WKT"); + REQUIRE(writer.CopcConfig()->Wkt() == "TEST_WKT"); writer.Close(); Reader reader(&out_stream); - REQUIRE(reader.GetWkt() == "TEST_WKT"); + REQUIRE(reader.CopcConfig().Wkt() == "TEST_WKT"); } SECTION("Copy") @@ -210,21 +244,23 @@ TEST_CASE("Writer Config Tests", "[Writer]") Reader orig(&in_stream); stringstream out_stream; - Writer::LasConfig cfg(orig.GetLasHeader(), orig.GetExtraByteVlr()); + auto cfg = orig.CopcConfig(); + Writer writer(out_stream, cfg); writer.Close(); Reader reader(&out_stream); - REQUIRE(reader.GetLasHeader().file_source_id == orig.GetLasHeader().file_source_id); - REQUIRE(reader.GetLasHeader().global_encoding == orig.GetLasHeader().global_encoding); - REQUIRE(reader.GetLasHeader().creation_day == orig.GetLasHeader().creation_day); - REQUIRE(reader.GetLasHeader().creation_year == orig.GetLasHeader().creation_year); - REQUIRE(reader.GetLasHeader().file_source_id == orig.GetLasHeader().file_source_id); - REQUIRE(reader.GetLasHeader().point_format_id == orig.GetLasHeader().point_format_id); - REQUIRE(reader.GetLasHeader().point_record_length == orig.GetLasHeader().point_record_length); - REQUIRE(reader.GetLasHeader().point_count == 0); - REQUIRE(reader.GetLasHeader().scale == reader.GetLasHeader().scale); - REQUIRE(reader.GetLasHeader().offset == reader.GetLasHeader().offset); + REQUIRE(reader.CopcConfig().LasHeader().file_source_id == orig.CopcConfig().LasHeader().file_source_id); + REQUIRE(reader.CopcConfig().LasHeader().global_encoding == orig.CopcConfig().LasHeader().global_encoding); + REQUIRE(reader.CopcConfig().LasHeader().creation_day == orig.CopcConfig().LasHeader().creation_day); + REQUIRE(reader.CopcConfig().LasHeader().creation_year == orig.CopcConfig().LasHeader().creation_year); + REQUIRE(reader.CopcConfig().LasHeader().file_source_id == orig.CopcConfig().LasHeader().file_source_id); + REQUIRE(reader.CopcConfig().LasHeader().PointFormatId() == orig.CopcConfig().LasHeader().PointFormatId()); + REQUIRE(reader.CopcConfig().LasHeader().PointRecordLength() == + orig.CopcConfig().LasHeader().PointRecordLength()); + REQUIRE(reader.CopcConfig().LasHeader().PointCount() == 0); + REQUIRE(reader.CopcConfig().LasHeader().Scale() == reader.CopcConfig().LasHeader().Scale()); + REQUIRE(reader.CopcConfig().LasHeader().Offset() == reader.CopcConfig().LasHeader().Offset()); } SECTION("Update") @@ -236,29 +272,30 @@ TEST_CASE("Writer Config Tests", "[Writer]") const Vector3 max2 = {20, 30, 40}; std::array points_by_return = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - Writer::LasConfig cfg(0); - cfg.min = min1; - cfg.max = max1; - Writer writer(out_stream, cfg, 256, "TEST_WKT"); + CopcConfigWriter cfg(6, {}, {}, "TEST_WKT"); + cfg.LasHeader()->min = min1; + cfg.LasHeader()->max = max1; + cfg.CopcInfo()->spacing = 10; + Writer writer(out_stream, cfg); - REQUIRE(writer.GetLasHeader().min == min1); - REQUIRE(writer.GetLasHeader().max == max1); - REQUIRE(writer.GetLasHeader().points_by_return_14 == std::array{0}); + REQUIRE(writer.CopcConfig()->LasHeader()->min == min1); + REQUIRE(writer.CopcConfig()->LasHeader()->max == max1); + REQUIRE(writer.CopcConfig()->LasHeader()->points_by_return == std::array{0}); - writer.SetMin(min2); - writer.SetMax(max2); - writer.SetPointsByReturn(points_by_return); + writer.CopcConfig()->LasHeader()->min = min2; + writer.CopcConfig()->LasHeader()->max = max2; + writer.CopcConfig()->LasHeader()->points_by_return = points_by_return; - REQUIRE(writer.GetLasHeader().min == min2); - REQUIRE(writer.GetLasHeader().max == max2); - REQUIRE(writer.GetLasHeader().points_by_return_14 == points_by_return); + REQUIRE(writer.CopcConfig()->LasHeader()->min == min2); + REQUIRE(writer.CopcConfig()->LasHeader()->max == max2); + REQUIRE(writer.CopcConfig()->LasHeader()->points_by_return == points_by_return); writer.Close(); Reader reader(&out_stream); - REQUIRE(reader.GetLasHeader().min == min2); - REQUIRE(reader.GetLasHeader().max == max2); - REQUIRE(reader.GetLasHeader().points_by_return_14 == points_by_return); + REQUIRE(reader.CopcConfig().LasHeader().min == min2); + REQUIRE(reader.CopcConfig().LasHeader().max == max2); + REQUIRE(reader.CopcConfig().LasHeader().points_by_return == points_by_return); } } } @@ -269,9 +306,9 @@ TEST_CASE("Writer Pages", "[Writer]") { stringstream out_stream; - Writer writer(out_stream, Writer::LasConfig(0)); + Writer writer(out_stream, {6}); - REQUIRE(!writer.FindNode(VoxelKey::BaseKey()).IsValid()); + REQUIRE(!writer.FindNode(VoxelKey::RootKey()).IsValid()); REQUIRE(!writer.FindNode(VoxelKey::InvalidKey()).IsValid()); REQUIRE(!writer.FindNode(VoxelKey(5, 4, 3, 2)).IsValid()); @@ -286,8 +323,8 @@ TEST_CASE("Writer Pages", "[Writer]") writer.Close(); Reader reader(&out_stream); - REQUIRE(reader.GetCopcHeader().root_hier_offset > 0); - REQUIRE(reader.GetCopcHeader().root_hier_size == 0); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_offset > 0); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_size == 0); REQUIRE(!reader.FindNode(VoxelKey::InvalidKey()).IsValid()); } @@ -295,7 +332,7 @@ TEST_CASE("Writer Pages", "[Writer]") { stringstream out_stream; - Writer writer(out_stream, Writer::LasConfig(0)); + Writer writer(out_stream, {6}); Page root_page = writer.GetRootPage(); @@ -310,8 +347,8 @@ TEST_CASE("Writer Pages", "[Writer]") writer.Close(); Reader reader(&out_stream); - REQUIRE(reader.GetCopcHeader().root_hier_offset > 0); - REQUIRE(reader.GetCopcHeader().root_hier_size == 32); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_offset > 0); + REQUIRE(reader.CopcConfig().CopcInfo().root_hier_size == 32); REQUIRE(!reader.FindNode(VoxelKey::InvalidKey()).IsValid()); } } @@ -321,83 +358,97 @@ TEST_CASE("Writer EBs", "[Writer]") SECTION("Data type 0") { stringstream out_stream; - Writer::LasConfig config(7); las::EbVlr eb_vlr(1); // Always initialize with the ebCount constructor // don't make ebfields yourself unless you set their names correctly eb_vlr.items[0].data_type = 0; eb_vlr.items[0].options = 4; - config.extra_bytes = eb_vlr; - Writer writer(out_stream, config); + CopcConfigWriter cfg(7, {}, {}, {}, eb_vlr); + Writer writer(out_stream, cfg); - REQUIRE(writer.GetLasHeader().point_record_length == 40); // 36 + 4 + REQUIRE(writer.CopcConfig()->LasHeader()->PointRecordLength() == 40); // 36 + 4 writer.Close(); Reader reader(&out_stream); - REQUIRE(reader.GetExtraByteVlr().items.size() == 1); - REQUIRE(reader.GetExtraByteVlr().items[0].data_type == 0); - REQUIRE(reader.GetExtraByteVlr().items[0].options == 4); - REQUIRE(reader.GetExtraByteVlr().items[0].name == "FIELD_0"); - REQUIRE(reader.GetExtraByteVlr().items[0].maxval[2] == 0); - REQUIRE(reader.GetExtraByteVlr().items[0].minval[2] == 0); - REQUIRE(reader.GetExtraByteVlr().items[0].offset[2] == 0); - REQUIRE(reader.GetExtraByteVlr().items[0].scale[2] == 0); - REQUIRE(reader.GetLasHeader().point_record_length == 40); + REQUIRE(reader.CopcConfig().ExtraBytesVlr().items.size() == 1); + REQUIRE(reader.CopcConfig().ExtraBytesVlr().items[0].data_type == 0); + REQUIRE(reader.CopcConfig().ExtraBytesVlr().items[0].options == 4); + REQUIRE(reader.CopcConfig().ExtraBytesVlr().items[0].name == "FIELD_0"); + REQUIRE(reader.CopcConfig().ExtraBytesVlr().items[0].maxval[2] == 0); + REQUIRE(reader.CopcConfig().ExtraBytesVlr().items[0].minval[2] == 0); + REQUIRE(reader.CopcConfig().ExtraBytesVlr().items[0].offset[2] == 0); + REQUIRE(reader.CopcConfig().ExtraBytesVlr().items[0].scale[2] == 0); + REQUIRE(reader.CopcConfig().LasHeader().PointRecordLength() == 40); } SECTION("Data type 29") { stringstream out_stream; - Writer::LasConfig config(7); las::EbVlr eb_vlr(1); eb_vlr.items[0].data_type = 29; - config.extra_bytes = eb_vlr; - Writer writer(out_stream, config); + CopcConfigWriter cfg(7, {}, {}, {}, eb_vlr); + Writer writer(out_stream, cfg); - REQUIRE(writer.GetLasHeader().point_record_length == 48); // 36 + 12 + REQUIRE(writer.CopcConfig()->LasHeader()->PointRecordLength() == 48); // 36 + 12 writer.Close(); Reader reader(&out_stream); - REQUIRE(reader.GetExtraByteVlr().items.size() == 1); - REQUIRE(reader.GetLasHeader().point_record_length == 48); + REQUIRE(reader.CopcConfig().ExtraBytesVlr().items.size() == 1); + REQUIRE(reader.CopcConfig().LasHeader().PointRecordLength() == 48); } +} +TEST_CASE("Writer Copy", "[Writer]") +{ - SECTION("Copy Vlr") + SECTION("Copy Copc Config") { FileReader reader("autzen-classified.copc.laz"); - auto in_eb_vlr = reader.GetExtraByteVlr(); - stringstream out_stream; - Writer::LasConfig config(3); - config.extra_bytes = in_eb_vlr; - Writer writer(out_stream, config); + auto cfg = reader.CopcConfig(); - REQUIRE(writer.GetLasHeader().point_record_length == 36); // 34 + 1 + 1 + Writer writer(out_stream, cfg); + + writer.CopcConfig()->LasHeader()->min = {1, 1, 1}; // Update some values in the process + writer.CopcConfig()->LasHeader()->max = {50, 50, 50}; + + REQUIRE(writer.CopcConfig()->LasHeader()->PointRecordLength() == + reader.CopcConfig().LasHeader().PointRecordLength()); + REQUIRE(writer.CopcConfig()->LasHeader()->Scale() == reader.CopcConfig().LasHeader().Scale()); + REQUIRE(writer.CopcConfig()->LasHeader()->Offset() == reader.CopcConfig().LasHeader().Offset()); + REQUIRE(writer.CopcConfig()->LasHeader()->min == Vector3(1, 1, 1)); + REQUIRE(writer.CopcConfig()->LasHeader()->max == Vector3(50, 50, 50)); writer.Close(); - Reader reader2(&out_stream); - REQUIRE(reader2.GetExtraByteVlr().items.size() == 2); - REQUIRE(reader2.GetLasHeader().point_record_length == reader.GetLasHeader().point_record_length); - REQUIRE(reader2.GetExtraByteVlr().items == reader.GetExtraByteVlr().items); + Reader new_reader(&out_stream); + REQUIRE(new_reader.CopcConfig().LasHeader().PointRecordLength() == + reader.CopcConfig().LasHeader().PointRecordLength()); + REQUIRE(new_reader.CopcConfig().LasHeader().Scale() == reader.CopcConfig().LasHeader().Scale()); + REQUIRE(new_reader.CopcConfig().LasHeader().Offset() == reader.CopcConfig().LasHeader().Offset()); + REQUIRE(new_reader.CopcConfig().LasHeader().min == Vector3(1, 1, 1)); + REQUIRE(new_reader.CopcConfig().LasHeader().max == Vector3(50, 50, 50)); + REQUIRE(new_reader.CopcConfig().CopcInfo().spacing == reader.CopcConfig().CopcInfo().spacing); + REQUIRE(new_reader.CopcConfig().CopcExtents().Intensity()->minimum == + reader.CopcConfig().CopcExtents().Intensity()->minimum); + REQUIRE(new_reader.CopcConfig().Wkt() == reader.CopcConfig().Wkt()); + REQUIRE(new_reader.CopcConfig().ExtraBytesVlr().items == reader.CopcConfig().ExtraBytesVlr().items); } -} -TEST_CASE("Writer Copy", "[Writer]") -{ SECTION("Autzen") { FileReader reader("autzen-classified.copc.laz"); stringstream out_stream; - Writer::LasConfig cfg(reader.GetLasHeader(), reader.GetExtraByteVlr()); + + auto cfg = reader.CopcConfig(); + Writer writer(out_stream, cfg); Page root_page = writer.GetRootPage(); - for (auto node : reader.GetAllChildren()) + for (const auto &node : reader.GetAllNodes()) { // only write/compare compressed data or otherwise tests take too long writer.AddNodeCompressed(root_page, node.key, reader.GetPointDataCompressed(node), node.point_count); @@ -407,7 +458,7 @@ TEST_CASE("Writer Copy", "[Writer]") Reader new_reader(&out_stream); - for (auto node : reader.GetAllChildren()) + for (const auto &node : reader.GetAllNodes()) { REQUIRE(node.IsValid()); auto new_node = new_reader.FindNode(node.key); @@ -427,19 +478,19 @@ TEST_CASE("Writer Copy", "[Writer]") TEST_CASE("Check Spatial Bounds", "[Writer]") { string file_path = "writer_test.copc.laz"; - Writer::LasConfig cfg(7, {0.1, 0.1, 0.1}, {50, 50, 50}); - cfg.min = {-10, -10, -5}; - cfg.max = {10, 10, 5}; + CopcConfigWriter cfg(7, {0.1, 0.1, 0.1}, {50, 50, 50}); + cfg.LasHeader()->min = {-10, -10, -5}; + cfg.LasHeader()->max = {10, 10, 5}; bool verbose = false; SECTION("Las Header Bounds check (pass)") { FileWriter writer(file_path, cfg); - auto header = writer.GetLasHeader(); + auto header = *writer.CopcConfig()->LasHeader(); Page root_page = writer.GetRootPage(); - las::Points points(header.point_format_id, header.scale, header.offset); + las::Points points(header.PointFormatId(), header.Scale(), header.Offset()); auto point = points.CreatePoint(); point->X(10); @@ -459,10 +510,10 @@ TEST_CASE("Check Spatial Bounds", "[Writer]") { FileWriter writer(file_path, cfg); - auto header = writer.GetLasHeader(); + auto header = *writer.CopcConfig()->LasHeader(); Page root_page = writer.GetRootPage(); - las::Points points(header.point_format_id, header.scale, header.offset); + las::Points points(header.PointFormatId(), header.Scale(), header.Offset()); auto point = points.CreatePoint(); point->X(10); @@ -482,10 +533,10 @@ TEST_CASE("Check Spatial Bounds", "[Writer]") { FileWriter writer(file_path, cfg); - auto header = writer.GetLasHeader(); + auto header = *writer.CopcConfig()->LasHeader(); Page root_page = writer.GetRootPage(); - las::Points points(header.point_format_id, header.scale, header.offset); + las::Points points(header.PointFormatId(), header.Scale(), header.Offset()); auto point = points.CreatePoint(); point->X(10); @@ -505,10 +556,10 @@ TEST_CASE("Check Spatial Bounds", "[Writer]") { FileWriter writer(file_path, cfg); - auto header = writer.GetLasHeader(); + auto header = *writer.CopcConfig()->LasHeader(); Page root_page = writer.GetRootPage(); - las::Points points(header.point_format_id, header.scale, header.offset); + las::Points points(header.PointFormatId(), header.Scale(), header.Offset()); auto point = points.CreatePoint(); point->X(0.1); diff --git a/test/writer_test.py b/test/writer_test.py index 1c0bf1dd..b6201add 100644 --- a/test/writer_test.py +++ b/test/writer_test.py @@ -1,5 +1,6 @@ import copclib as copc import pytest +from sys import float_info def test_writer_config(): @@ -7,46 +8,52 @@ def test_writer_config(): file_path = "writer_test.copc.laz" # Default config - cfg = copc.LasConfig(0) + cfg = copc.CopcConfigWriter(6) writer = copc.FileWriter(file_path, cfg) - las_header = writer.GetLasHeader() + las_header = writer.copc_config.las_header assert las_header.scale == [0.01, 0.01, 0.01] assert las_header.offset == [0.0, 0.0, 0.0] - assert las_header.point_format_id == 0 + assert las_header.point_format_id == 6 + + str(cfg) + + with pytest.raises(RuntimeError): + assert copc.CopcConfigWriter(5) + assert copc.CopcConfigWriter(9) writer.Close() # Custom config - cfg = copc.LasConfig(8, [2, 3, 4], [-0.02, -0.03, -40.8]) - cfg.file_source_id = 200 + cfg = copc.CopcConfigWriter(8, [2, 3, 4], [-0.02, -0.03, -40.8]) + cfg.las_header.file_source_id = 200 - # Test LasConfig attributes - cfg.creation_day = 18 - assert cfg.creation_day == 18 - cfg.creation_year = 11 - assert cfg.creation_year == 11 + # Test LasHeaderConfig attributes + cfg.las_header.creation_day = 18 + assert cfg.las_header.creation_day == 18 + cfg.las_header.creation_year = 11 + assert cfg.las_header.creation_year == 11 # Test checks on string attributes - cfg.guid = "test_string" - assert cfg.guid == "test_string" + cfg.las_header.guid = "test_string" + assert cfg.las_header.guid == "test_string" with pytest.raises(RuntimeError): - cfg.guid = "a" * 17 + cfg.las_header.guid = "a" * 17 - cfg.system_identifier = "test_string" - assert cfg.system_identifier == "test_string" + cfg.las_header.system_identifier = "test_string" + assert cfg.las_header.system_identifier == "test_string" with pytest.raises(RuntimeError): - cfg.system_identifier = "a" * 33 + cfg.las_header.system_identifier = "a" * 33 - cfg.generating_software = "test_string" - assert cfg.generating_software == "test_string" + cfg.las_header.generating_software = "test_string" + assert cfg.las_header.generating_software == "test_string" with pytest.raises(RuntimeError): - cfg.generating_software = "a" * 33 + cfg.las_header.generating_software = "a" * 33 writer = copc.FileWriter(file_path, cfg) - las_header = writer.GetLasHeader() + las_header = writer.copc_config.las_header assert las_header.file_source_id == 200 assert las_header.point_format_id == 8 assert las_header.scale == [2.0, 3.0, 4.0] @@ -54,98 +61,169 @@ def test_writer_config(): writer.Close() - # COPC Span + # COPC Spacing - cfg = copc.LasConfig(0) - writer = copc.FileWriter(file_path, cfg, 256) + cfg = copc.CopcConfigWriter(6) + cfg.copc_info.spacing = 10 + writer = copc.FileWriter(file_path, cfg) - # todo: use Reader to check all of these - assert writer.GetCopcHeader().span == 256 + assert writer.copc_config.copc_info.spacing == 10 + + writer.copc_config.copc_info.spacing = 15 writer.Close() reader = copc.FileReader(file_path) - assert reader.GetCopcHeader().span == 256 + assert reader.copc_config.copc_info.spacing == 15 + + # Extents + cfg = copc.CopcConfigWriter(6) + writer = copc.FileWriter(file_path, cfg) + + extents = writer.copc_config.copc_extents + + extents.intensity.minimum = -1.0 + extents.intensity.maximum = 1 + + extents.classification.minimum = -float_info.max + extents.classification.maximum = float_info.max + + assert ( + writer.copc_config.copc_extents.intensity.minimum == extents.intensity.minimum + ) + assert ( + writer.copc_config.copc_extents.intensity.maximum == extents.intensity.maximum + ) + assert ( + writer.copc_config.copc_extents.classification.minimum + == extents.classification.minimum + ) + assert ( + writer.copc_config.copc_extents.classification.maximum + == extents.classification.maximum + ) + + writer.Close() + + reader = copc.FileReader(file_path) + assert ( + reader.copc_config.copc_extents.intensity.minimum == extents.intensity.minimum + ) + assert ( + reader.copc_config.copc_extents.intensity.maximum == extents.intensity.maximum + ) + assert ( + reader.copc_config.copc_extents.classification.minimum + == extents.classification.minimum + ) + assert ( + reader.copc_config.copc_extents.classification.maximum + == extents.classification.maximum + ) # WKT - cfg = copc.LasConfig(0) - writer = copc.FileWriter(file_path, cfg, 256, "TEST_WKT") + cfg = copc.CopcConfigWriter(6, wkt="TEST_WKT") + writer = copc.FileWriter(file_path, cfg) - # todo: use Reader to check all of these - assert writer.GetWkt() == "TEST_WKT" + assert writer.copc_config.wkt == "TEST_WKT" writer.Close() reader = copc.FileReader(file_path) - assert reader.GetWkt() == "TEST_WKT" + assert reader.copc_config.wkt == "TEST_WKT" # Copy orig = copc.FileReader("autzen-classified.copc.laz") - cfg = copc.LasConfig(orig.GetLasHeader(), orig.GetExtraByteVlr()) + cfg = orig.copc_config writer = copc.FileWriter(file_path, cfg) + + writer.copc_config.las_header.min = (1, 1, 1) + writer.copc_config.las_header.max = (50, 50, 50) + + assert writer.copc_config.las_header.min == (1, 1, 1) + assert writer.copc_config.las_header.max == (50, 50, 50) + writer.Close() reader = copc.FileReader(file_path) - assert reader.GetLasHeader().file_source_id == orig.GetLasHeader().file_source_id - assert reader.GetLasHeader().global_encoding == orig.GetLasHeader().global_encoding - assert reader.GetLasHeader().creation_day == orig.GetLasHeader().creation_day - assert reader.GetLasHeader().creation_year == orig.GetLasHeader().creation_year - assert reader.GetLasHeader().file_source_id == orig.GetLasHeader().file_source_id - assert reader.GetLasHeader().point_format_id == orig.GetLasHeader().point_format_id assert ( - reader.GetLasHeader().point_record_length - == orig.GetLasHeader().point_record_length + reader.copc_config.las_header.file_source_id + == orig.copc_config.las_header.file_source_id + ) + assert ( + reader.copc_config.las_header.global_encoding + == orig.copc_config.las_header.global_encoding + ) + assert ( + reader.copc_config.las_header.creation_day + == orig.copc_config.las_header.creation_day + ) + assert ( + reader.copc_config.las_header.creation_year + == orig.copc_config.las_header.creation_year + ) + assert ( + reader.copc_config.las_header.file_source_id + == orig.copc_config.las_header.file_source_id + ) + assert ( + reader.copc_config.las_header.point_format_id + == orig.copc_config.las_header.point_format_id + ) + assert ( + reader.copc_config.las_header.point_record_length + == orig.copc_config.las_header.point_record_length ) - assert reader.GetLasHeader().point_count == 0 - assert reader.GetLasHeader().scale == reader.GetLasHeader().scale - assert reader.GetLasHeader().offset == reader.GetLasHeader().offset + assert reader.copc_config.las_header.point_count == 0 + assert reader.copc_config.las_header.min == (1, 1, 1) + assert reader.copc_config.las_header.max == (50, 50, 50) # Update - min1 = copc.Vector3([-800, 300, 800]) - max1 = copc.Vector3([5000, 8444, 3333]) - min2 = copc.Vector3([-20, -30, -40]) - max2 = copc.Vector3([20, 30, 40]) + min1 = (-800, 300, 800) + max1 = (5000, 8444, 3333) + min2 = (-20, -30, -40) + max2 = (20, 30, 40) points_by_return = list(range(15)) - cfg = copc.LasConfig(0) - cfg.min = min1 - cfg.max = max1 - writer = copc.FileWriter(file_path, cfg, 256, "TEST_WKT") + cfg = copc.CopcConfigWriter(6, wkt="TEST_WKT") + cfg.las_header.min = min1 + cfg.las_header.max = max1 + cfg.copc_info.spacing = 10 + writer = copc.FileWriter(file_path, cfg) - # todo: use Reader to check all of these - assert writer.GetLasHeader().min == min1 - assert writer.GetLasHeader().max == max1 - assert writer.GetLasHeader().points_by_return_14 == [0] * 15 + assert writer.copc_config.las_header.min == min1 + assert writer.copc_config.las_header.max == max1 + assert writer.copc_config.las_header.points_by_return == [0] * 15 with pytest.raises(TypeError): - writer.SetPointsByReturn([20] * 800) + writer.copc_config.las_header.points_by_return = [20] * 800 - writer.SetMin(min2) - writer.SetMax(max2) - writer.SetPointsByReturn(points_by_return) + writer.copc_config.las_header.min = min2 + writer.copc_config.las_header.max = max2 + writer.copc_config.las_header.points_by_return = points_by_return - assert writer.GetLasHeader().min == min2 - assert writer.GetLasHeader().max == max2 - assert writer.GetLasHeader().points_by_return_14 == points_by_return + assert writer.copc_config.las_header.min == min2 + assert writer.copc_config.las_header.max == max2 + assert writer.copc_config.las_header.points_by_return == points_by_return writer.Close() reader = copc.FileReader(file_path) - assert reader.GetLasHeader().min == min2 - assert reader.GetLasHeader().max == max2 - assert reader.GetLasHeader().points_by_return_14 == points_by_return + assert reader.copc_config.las_header.min == min2 + assert reader.copc_config.las_header.max == max2 + assert reader.copc_config.las_header.points_by_return == points_by_return def test_writer_pages(): # Given a valid file path file_path = "writer_test.copc.laz" - writer = copc.FileWriter(file_path, copc.LasConfig(0)) + writer = copc.FileWriter(file_path, copc.CopcConfigWriter(6)) - assert not writer.FindNode(copc.VoxelKey.BaseKey()).IsValid() + assert not writer.FindNode(copc.VoxelKey.RootKey()).IsValid() assert not writer.FindNode(copc.VoxelKey.InvalidKey()).IsValid() - assert not writer.FindNode(copc.VoxelKey(5, 4, 3, 2)).IsValid() + assert not writer.FindNode((5, 4, 3, 2)).IsValid() # Root Page writer.GetRootPage() @@ -159,29 +237,29 @@ def test_writer_pages(): writer.Close() reader = copc.FileReader(file_path) - assert reader.GetCopcHeader().root_hier_offset > 0 - assert reader.GetCopcHeader().root_hier_size == 0 + assert reader.copc_config.copc_info.root_hier_offset > 0 + assert reader.copc_config.copc_info.root_hier_size == 0 assert not reader.FindNode(key=copc.VoxelKey.InvalidKey()).IsValid() # Nested page - writer = copc.FileWriter(file_path, copc.LasConfig(0)) + writer = copc.FileWriter(file_path, copc.CopcConfigWriter(6)) root_page = writer.GetRootPage() - sub_page = writer.AddSubPage(root_page, copc.VoxelKey(1, 1, 1, 1)) + sub_page = writer.AddSubPage(root_page, (1, 1, 1, 1)) assert sub_page.IsPage() assert sub_page.IsValid() assert sub_page.loaded is True with pytest.raises(RuntimeError): - writer.AddSubPage(sub_page, copc.VoxelKey(1, 1, 1, 0)) - writer.AddSubPage(sub_page, copc.VoxelKey(2, 4, 5, 0)) + writer.AddSubPage(sub_page, (1, 1, 1, 0)) + writer.AddSubPage(sub_page, (2, 4, 5, 0)) writer.Close() reader = copc.FileReader(file_path) - assert reader.GetCopcHeader().root_hier_offset > 0 - assert reader.GetCopcHeader().root_hier_size == 32 + assert reader.copc_config.copc_info.root_hier_offset > 0 + assert reader.copc_config.copc_info.root_hier_size == 32 assert not reader.FindNode(copc.VoxelKey.InvalidKey()).IsValid() @@ -191,12 +269,12 @@ def test_writer_copy(): reader = copc.FileReader("autzen-classified.copc.laz") - cfg = copc.LasConfig(reader.GetLasHeader(), reader.GetExtraByteVlr()) + cfg = reader.copc_config writer = copc.FileWriter(file_path, cfg) root_page = writer.GetRootPage() - for node in reader.GetAllChildren(): + for node in reader.GetAllNodes(): # only write/compare compressed data or otherwise tests take too long writer.AddNodeCompressed( root_page, node.key, reader.GetPointDataCompressed(node), node.point_count @@ -206,7 +284,7 @@ def test_writer_copy(): # validate new_reader = copc.FileReader(file_path) - for node in reader.GetAllChildren(): + for node in reader.GetAllNodes(): assert node.IsValid() new_node = new_reader.FindNode(node.key) assert new_node.IsValid() @@ -219,22 +297,21 @@ def test_writer_copy(): # we can do one uncompressed comparison here assert new_reader.GetPointData( - new_reader.FindNode(copc.VoxelKey(5, 9, 7, 0)) - ) == reader.GetPointData(reader.FindNode(copc.VoxelKey(5, 9, 7, 0))) + new_reader.FindNode((5, 9, 7, 0)) + ) == reader.GetPointData(reader.FindNode((5, 9, 7, 0))) def test_check_spatial_bounds(): file_path = "writer_test.copc.laz" - cfg = copc.LasConfig(7, (0.1, 0.1, 0.1), (50, 50, 50)) - cfg.min = (-10, -10, -5) - cfg.max = (10, 10, 5) + cfg = copc.CopcConfigWriter(7, (0.1, 0.1, 0.1), (50, 50, 50)) + cfg.las_header.min = (-10, -10, -5) + cfg.las_header.max = (10, 10, 5) verbose = False writer = copc.FileWriter(file_path, cfg) - header = writer.GetLasHeader() - root_page = writer.GetRootPage() + header = writer.copc_config.las_header ## Checks on las header bounds @@ -242,13 +319,13 @@ def test_check_spatial_bounds(): point = points.CreatePoint() # point has getters/setters for all attributes - point.X = 10 - point.Y = 10 - point.Z = 5 + point.x = 10 + point.y = 10 + point.z = 5 points.AddPoint(point) - writer.AddNode(root_page, copc.VoxelKey(1, 1, 1, 1), points) + writer.AddNode(writer.GetRootPage(), (1, 1, 1, 1), points) writer.Close() reader = copc.FileReader(file_path) @@ -258,18 +335,17 @@ def test_check_spatial_bounds(): # Las Header Bounds check (node outside) writer = copc.FileWriter(file_path, cfg) - header = writer.GetLasHeader() - root_page = writer.GetRootPage() + header = writer.copc_config.las_header points = copc.Points(header.point_format_id, header.scale, header.offset) point = points.CreatePoint() - point.X = 10 - point.Y = 10 - point.Z = 5.1 + point.x = 10 + point.y = 10 + point.z = 5.1 points.AddPoint(point) - writer.AddNode(root_page, copc.VoxelKey(2, 3, 3, 3), points) + writer.AddNode(writer.GetRootPage(), (2, 3, 3, 3), points) writer.Close() reader = copc.FileReader(file_path) @@ -279,18 +355,17 @@ def test_check_spatial_bounds(): # Las Header Bounds check (node intersects) writer = copc.FileWriter(file_path, cfg) - header = writer.GetLasHeader() - root_page = writer.GetRootPage() + header = writer.copc_config.las_header points = copc.Points(header.point_format_id, header.scale, header.offset) point = points.CreatePoint() - point.X = 10 - point.Y = 10 - point.Z = 5.1 + point.x = 10 + point.y = 10 + point.z = 5.1 points.AddPoint(point) - writer.AddNode(root_page, copc.VoxelKey(1, 1, 1, 1), points) + writer.AddNode(writer.GetRootPage(), (1, 1, 1, 1), points) writer.Close() reader = copc.FileReader(file_path) @@ -300,18 +375,16 @@ def test_check_spatial_bounds(): # Node Bounds check writer = copc.FileWriter(file_path, cfg) - header = writer.GetLasHeader() - root_page = writer.GetRootPage() - + header = writer.copc_config.las_header points = copc.Points(header.point_format_id, header.scale, header.offset) point = points.CreatePoint() - point.X = 0.1 - point.Y = 0.1 - point.Z = 0.1 + point.x = 0.1 + point.y = 0.1 + point.z = 0.1 points.AddPoint(point) - writer.AddNode(root_page, copc.VoxelKey(1, 0, 0, 0), points) + writer.AddNode(writer.GetRootPage(), (1, 0, 0, 0), points) writer.Close() reader = copc.FileReader(file_path)