Repository: RockRobotic/copc-lib (language: python)

Issue:
Point restructure to allow conversion between LAS point formats



Discussion / hints:
A concern I have with `point14` and `point10` as the underlying format is that performing a `ToPointFormat` on a list of them requires copying each point to new memory, whereas if we had a single `point` object we could do it without copying them. Since most of the differences between Point14 and Point10 are just enlarging/shrinking the size of some fields, we could reduce the problem space from deleting/recreating all point objects in memory to changing some values that would cause an overflow.

For example, if going from point10 -> point14, and we had one "point" object, we wouldn't need to do any conversion (we can store the scanangle as the point14 format for all point types). If going from point14 -> point10, we can just iterate through fields like classification and return angle and either reset them to 0 or cap them at the maximum. But this operation can happen in-place.
tldr I think we should change our implementation to be basically akin to laszip's LasPoint object (we could just copy most of it tbh, it does almost exactly what we need 😄 ). He already handles point14 vs point10 and the rgb/gpstime attribute correctly.

Then, all we'll have to do is add some getter/setter logic around get_extended_classification and get_classification based on `extended_point_type` and we should be good. Let me know your thoughts.

https://github.com/LAStools/LAStools/blob/master/LASzip/src/laspoint.hpp
I question remaining is about the behavior when going from point14 to point10, what should we do if classification>31, returns>7, scan angle >90, etc.

I currently have a default constructor for Point, initializing everything to 0. and for the bitfields I reset the bitfield on conversion yep.
I don't actually 0 out the values when going to a more restrictive point type, since you can't access them anyways. For instance if you go from 3 to 0 to 3, you will still have your GPS value, it won't be zeroed out when going to format 0. Instead when you're under format 0 you just can't call the getter for GPS.

Should I change this behavior?
It should be okay for gps/rgb, as long as it's not random initialized. Lmk when you're ready for review
I wonder if we should hide/rename some of the bit fields to make them less accessible to the user, so they don't get confused between say returnnumber and extendedreturns, what do you think? We could either not expose them at all or maybe change the naming schema for them, unless you can think of another way?
The bitfield access is pretty handy to avoid making multiple operations when reassigning the whole bitfield. Could change the name I guess and call it BitField.

Fix patch:
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 <memory>
+#include <string>
+
+#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<las::LasHeader>(header)), copc_info_(std::make_shared<copc::CopcInfo>(copc_info)),
+          copc_extents_(std::make_shared<copc::CopcExtents>(copc_extents)), wkt_(wkt),
+          eb_vlr_(std::make_shared<las::EbVlr>(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<las::LasHeader> header_;
+    std::shared_ptr<copc::CopcInfo> copc_info_;
+    std::shared_ptr<copc::CopcExtents> copc_extents_;
+    std::string wkt_;
+    std::shared_ptr<las::EbVlr> 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<las::LasHeader> LasHeader() { return header_; }
+    las::LasHeader LasHeader() const override { return *header_; }
+
+    std::shared_ptr<copc::CopcInfo> CopcInfo() { return copc_info_; }
+    copc::CopcInfo CopcInfo() const override { return *copc_info_; }
+
+    std::shared_ptr<copc::CopcExtents> 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 <string>
+#include <vector>
+
+#include <lazperf/vlr.hpp>
+
+#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<double> &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<std::shared_ptr<CopcExtent>> Extents() { return extents_; }
+
+    // Get all extents as a copy vector
+    std::vector<CopcExtent> Extents() const
+    {
+        std::vector<CopcExtent> vec;
+        vec.reserve(extents_.size());
+
+        for (int i{0}; i < extents_.size(); i++)
+            vec.push_back(*extents_[i]);
+        return vec;
+    }
+
+    std::shared_ptr<CopcExtent> Intensity() { return extents_[0]; }
+    std::shared_ptr<CopcExtent> ReturnNumber() { return extents_[1]; }
+    std::shared_ptr<CopcExtent> NumberOfReturns() { return extents_[2]; }
+    std::shared_ptr<CopcExtent> ScannerChannel() { return extents_[3]; }
+    std::shared_ptr<CopcExtent> ScanDirectionFlag() { return extents_[4]; }
+    std::shared_ptr<CopcExtent> EdgeOfFlightLine() { return extents_[5]; }
+    std::shared_ptr<CopcExtent> Classification() { return extents_[6]; }
+    std::shared_ptr<CopcExtent> UserData() { return extents_[7]; }
+    std::shared_ptr<CopcExtent> ScanAngle() { return extents_[8]; }
+    std::shared_ptr<CopcExtent> PointSourceId() { return extents_[9]; }
+    std::shared_ptr<CopcExtent> GpsTime() { return extents_[10]; }
+
+    std::shared_ptr<CopcExtent> 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<CopcExtent> 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<CopcExtent> 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<CopcExtent> 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<std::shared_ptr<CopcExtent>> ExtraBytes()
+    {
+        std::vector<std::shared_ptr<CopcExtent>> 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<CopcExtent> intensity) { extents_[0] = intensity; }
+    void ReturnNumber(std::shared_ptr<CopcExtent> return_number) { extents_[1] = return_number; }
+    void NumberOfReturns(std::shared_ptr<CopcExtent> number_of_returns) { extents_[2] = number_of_returns; }
+    void ScannerChannel(std::shared_ptr<CopcExtent> scanner_channel) { extents_[3] = scanner_channel; }
+    void ScanDirectionFlag(std::shared_ptr<CopcExtent> scan_direction_flag) { extents_[4] = scan_direction_flag; }
+    void EdgeOfFlightLine(std::shared_ptr<CopcExtent> edge_of_flight_line) { extents_[5] = edge_of_flight_line; }
+    void Classification(std::shared_ptr<CopcExtent> classification) { extents_[6] = classification; }
+    void UserData(std::shared_ptr<CopcExtent> user_data) { extents_[7] = user_data; }
+    void ScanAngle(std::shared_ptr<CopcExtent> scan_angle) { extents_[8] = scan_angle; }
+    void PointSourceId(std::shared_ptr<CopcExtent> point_source_id) { extents_[9] = point_source_id; }
+    void GpsTime(std::shared_ptr<CopcExtent> gps_time) { extents_[10] = gps_time; }
+    void Red(std::shared_ptr<CopcExtent> 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<CopcExtent> 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<CopcExtent> 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<CopcExtent> 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<std::shared_ptr<CopcExtent>> 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<std::shared_ptr<CopcExtent>> 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 <iostream>
-
-#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 <string>
+
+#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<PageInternal>(VoxelKey::BaseKey(), -1, -1);
+        seen_pages_[VoxelKey::RootKey()] = std::make_shared<PageInternal>(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<PageInternal>(VoxelKey::BaseKey(), root_hier_offset, root_hier_size);
+        seen_pages_[VoxelKey::RootKey()] =
+            std::make_shared<PageInternal>(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 <stdexcept>
 #include <vector>
 
+#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<int32_t> &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<VoxelKey> GetParents(bool include_current = false) const;
+    std::vector<VoxelKey> 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<CopcFile> file_;
     std::shared_ptr<Internal::Hierarchy> hierarchy_;
     virtual std::vector<Entry> ReadPage(std::shared_ptr<Internal::PageInternal> page) = 0;
     void ReadAndParsePage(const std::shared_ptr<Internal::PageInternal> &page);
     // Recursively reads all subpages and nodes given a root and returns all the nodes that were loaded
     void LoadPageHierarchy(const std::shared_ptr<Internal::PageInternal> &page, std::vector<Node> &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 <ostream>
 
-#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<uint32_t>::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<CopcFile> &file,
+    WriterInternal(std::ostream &out_stream, std::shared_ptr<CopcConfigWriter> copc_config,
                    std::shared_ptr<Hierarchy> hierarchy);
 
     // Writes the header and COPC vlrs
@@ -33,19 +32,21 @@ class WriterInternal
     Entry WriteNode(std::vector<char> in, int32_t point_count, bool compressed);
 
   private:
-    bool open_;
+    bool open_{};
 
-    las::CopcVlr copc_data_;
     std::ostream &out_stream_;
-    std::shared_ptr<CopcFile> file_;
+    std::shared_ptr<CopcConfigWriter> copc_config_;
     std::shared_ptr<Hierarchy> hierarchy_;
 
     std::vector<lazperf::chunk> 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<PageInternal> &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 <istream>
 #include <limits>
+#include <map>
 #include <string>
 
-#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<Node> GetAllChildren(const VoxelKey &key);
+    std::vector<Node> GetAllChildrenOfPage(const VoxelKey &key);
     // Helper function to get all nodes from the root
-    std::vector<Node> GetAllChildren() { return GetAllChildren(VoxelKey::BaseKey()); }
+    std::vector<Node> 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<Node> 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<uint64_t, las::VlrHeader> vlrs_; // maps from absolute offsets to VLR entries
+
     std::istream *in_stream_;
 
     std::unique_ptr<lazperf::reader::generic_file> reader_;
 
     // Constructor helper function, initializes the file and hierarchy
     void InitReader();
-    // Reads file VLRs into vlrs_
-    std::map<uint64_t, las::VlrHeader> ReadVlrs();
+    // Reads file VLRs and EVLRs into vlrs_
+    std::map<uint64_t, las::VlrHeader> 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<uint64_t, las::VlrHeader> &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<uint64_t, las::VlrHeader> &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<uint64_t, las::VlrHeader> &vlrs);
+    las::WktVlr ReadWktVlr(std::map<uint64_t, las::VlrHeader> &vlrs);
+    // Finds and loads EB vlr
+    las::EbVlr ReadExtraBytesVlr(std::map<uint64_t, las::VlrHeader> &vlrs);
 
     std::vector<Entry> ReadPage(std::shared_ptr<Internal::PageInternal> 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 <stdexcept>
 #include <string>
 
-#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<uint64_t, 15> 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<uint64_t, 15> points_by_return_14)
-    {
-        this->file_->SetPointsByReturn(points_by_return_14);
-    }
+    std::shared_ptr<CopcConfigWriter> CopcConfig() { return config_; }
+
     virtual ~Writer();
 
   protected:
     Writer() = default;
 
+    std::shared_ptr<CopcConfigWriter> config_;
+
     std::shared_ptr<Internal::WriterInternal> writer_;
 
     Node DoAddNode(Page &page, VoxelKey key, std::vector<char> 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<las::EbVlr::ebfield> &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 <iostream>
-#include <map>
-
-#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<uint64_t, las::VlrHeader> 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<uint64_t, 15> 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<uint32_t, 5> 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<uint64_t, 15> 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<uint64_t, 15> 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<int16_t>(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<int8_t>(scan_angle);
-        }
-    }
+    void ScanAngleDegrees(const float &scan_angle) { scan_angle_ = static_cast<int16_t>(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<uint16_t> &rgb)
+    void Rgb(const std::vector<uint16_t> &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<uint8_t> ExtraBytes() const { return extra_bytes_; }
     void ExtraBytes(const std::vector<uint8_t> &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<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);
     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<std::shared_ptr<Point>> &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<std::shared_ptr<Point>> Get() { return points_; }
@@ -44,13 +44,12 @@ class Points
     // Add points functions
     void AddPoint(const std::shared_ptr<Point> &point);
     void AddPoints(Points points);
-    // TODO[Leo]: Add this to tests
     void AddPoints(std::vector<std::shared_ptr<Point>> points);
 
     // Point functions
     std::shared_ptr<Point> CreatePoint()
     {
-        return std::make_shared<Point>(point_format_id_, scale_, offset_, NumExtraBytes());
+        return std::make_shared<Point>(point_format_id_, scale_, offset_, EbByteSize());
     }
     void ToPointFormat(const int8_t &point_format_id);
 
@@ -58,7 +57,7 @@ class Points
     std::vector<char> Pack();
     void Pack(std::ostream &out_stream);
     static Points Unpack(const std::vector<char> &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<char> &point_data, const LasHeader &header);
 
     std::string ToString() const;
@@ -131,21 +130,21 @@ class Points
             points_[i]->Classification(in[i]);
     }
 
-    std::vector<uint8_t> PointSourceID() const
+    std::vector<uint8_t> PointSourceId() const
     {
         std::vector<uint8_t> out;
         out.resize(Size());
         std::transform(points_.begin(), points_.end(), out.begin(),
-                       [](const std::shared_ptr<Point> &p) { return p->PointSourceID(); });
+                       [](const std::shared_ptr<Point> &p) { return p->PointSourceId(); });
         return out;
     }
-    void PointSourceID(const std::vector<uint8_t> &in)
+    void PointSourceId(const std::vector<uint8_t> &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<uint16_t> 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 <typename T> double ApplyScale(T value, double scale, double offset) { return (value * scale) + offset; }
 template <typename T> 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 <cstring>
+#include <stdexcept>
+#include <vector>
+
 #include <lazperf/vlr.hpp>
 
 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<EbVlr::ebfield> &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 <istream>
+#include <stdexcept>
+#include <vector>
+
 #include "copc-lib/io/writer.hpp"
 #include "copc-lib/las/utils.hpp"
 
 #include <lazperf/filestream.hpp>
 
-#include <istream>
-#include <vector>
-
 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<char> &in)
+    static uint32_t CompressBytes(std::ostream &out_stream, const int8_t &point_format_id, const uint16_t &eb_byte_size,
+                                  std::vector<char> &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<char> &in)
     {
-        return CompressBytes(out_stream, header.point_format_id, header.NumExtraBytes(), in);
+        return CompressBytes(out_stream, header.PointFormatId(), header.EbByteSize(), in);
     }
 
     static std::vector<char> CompressBytes(std::vector<char> &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<char>(ostr.begin(), ostr.end());
     }
 
     static std::vector<char> CompressBytes(std::vector<char> &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<char> 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<char> 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<char> 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<char> DecompressBytes(const std::vector<char> &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<char> DecompressBytes(const std::vector<char> &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 <stdexcept>
+
+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<las::LasHeader>(
+        point_format_id, las::PointBaseByteSize(point_format_id) + las::NumBytesFromExtraBytes(extra_bytes_vlr.items),
+        scale, offset);
+    copc_info_ = std::make_shared<copc::CopcInfo>();
+    copc_extents_ =
+        std::make_shared<copc::CopcExtents>(point_format_id, extra_bytes_vlr.items.size(), has_extended_stats);
+    eb_vlr_ = std::make_shared<las::EbVlr>(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 <sstream>
+
+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<double> &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<CopcExtent>());
+}
+
+// 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<CopcExtent>(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<CopcExtent>(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 <sstream>
+
+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<double>::max(), x_max, y_max, std::numeric_limits<double>::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> VoxelKey::GetParents(bool include_current) const
+std::vector<VoxelKey> VoxelKey::GetParents(bool include_self) const
 {
     std::vector<VoxelKey> 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 <iostream>
 #include <stdexcept>
 
+#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 <lazperf/readers.hpp>
+#include <lazperf/vlr.hpp>
 
 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<lazperf::reader::generic_file>(*this->in_stream_);
+    reader_ = std::make_unique<lazperf::reader::generic_file>(*in_stream_);
 
-    auto header = las::LasHeader::FromLazPerf(this->reader_->header());
+    auto header = las::LasHeader::FromLazPerf(reader_->header());
 
-    std::map<uint64_t, las::VlrHeader> 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<CopcFile>(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<Internal::Hierarchy>(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<Internal::Hierarchy>(copc_info.root_hier_offset, copc_info.root_hier_size);
 }
 
-std::map<uint64_t, las::VlrHeader> Reader::ReadVlrs()
+std::map<uint64_t, las::VlrHeader> Reader::ReadVlrHeaders()
 {
     std::map<uint64_t, las::VlrHeader> 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<uint64_t, las::VlrHeader> &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<int8_t>(reader_->header().point_format_id),
+                        static_cast<uint16_t>(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<uint64_t, las::VlrHeader> &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<uint64_t, las::VlrHeader> &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<uint64_t, las::VlrHeader> &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<Entry> Reader::ReadPage(std::shared_ptr<Internal::PageInternal> page)
 {
     std::vector<Entry> 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<Entry> Reader::ReadPage(std::shared_ptr<Internal::PageInternal> page
 las::Points Reader::GetPoints(Node const &node)
 {
     std::vector<char> 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<char> 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<char> 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<char> point_data = laz::Decompressor::DecompressBytes(*in_stream_, las_header, node.point_count);
     return point_data;
 }
@@ -136,7 +201,7 @@ std::vector<char> Reader::GetPointData(VoxelKey const &key)
 std::vector<char> 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<char> Reader::GetPointDataCompressed(VoxelKey const &key)
     return GetPointDataCompressed(node);
 }
 
-std::vector<Node> Reader::GetAllChildren(const VoxelKey &key)
+std::vector<Node> Reader::GetAllChildrenOfPage(const VoxelKey &key)
 {
     std::vector<Node> out;
     if (!key.IsValid())
@@ -182,12 +247,12 @@ std::vector<Node> 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<Node> Reader::GetNodesWithinBox(const Box &box, double resolution)
 {
     std::vector<Node> 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<Node> Reader::GetNodesIntersectBox(const Box &box, double resolution
 {
     std::vector<Node> 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<Node> 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<Node> Reader::GetNodesAtResolution(double resolution)
 
     std::vector<Node> 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<Node> Reader::GetNodesWithinResolution(double resolution)
 
     std::vector<Node> 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<uint64_t, las::VlrHeader> &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 <cstring>
 #include <iterator>
+#include <stdexcept>
 
+#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 <lazperf/filestream.hpp>
 #include <lazperf/lazperf.hpp>
-
-using namespace lazperf;
+#include <lazperf/vlr.hpp>
 
 namespace copc::Internal
 {
 
-WriterInternal::WriterInternal(std::ostream &out_stream, const std::shared_ptr<CopcFile> &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<CopcConfigWriter> copc_config,
                                std::shared_ptr<Hierarchy> 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<char>(out_stream_), FIRST_CHUNK_OFFSET(), 0);
+    std::fill_n(std::ostream_iterator<char>(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<int64_t>(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<uint32_t>::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<uint64_t>(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<int64_t>(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<int64_t>(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<char *>(&chunk_table_offset), sizeof(chunk_table_offset));
 }
 
@@ -163,9 +176,9 @@ Entry WriterInternal::WriteNode(std::vector<char> 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<PageInternal> &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<PageInternal> &page)
     page->byte_size = static_cast<int32_t>(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 <cstring>
-
 #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<CopcFile>(header, span, wkt, config.extra_bytes);
+    this->config_ = std::make_shared<CopcConfigWriter>(copc_file_writer);
     this->hierarchy_ = std::make_shared<Internal::Hierarchy>();
-    this->writer_ = std::make_unique<Internal::WriterInternal>(out_stream, this->file_, this->hierarchy_);
+    this->writer_ = std::make_unique<Internal::WriterInternal>(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<char> 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<char> 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<char> 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<char
     return DoAddNode(page, key, compressed, point_count, true);
 }
 
-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 Writer::NumBytesFromExtraBytes(const std::vector<las::EbVlr::ebfield> &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 <cstring>
+#include <limits>
+#include <stdexcept>
 
 #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<int8_t>(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<int8_t>(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<uint32_t>::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<int>(version_major_) << "." << static_cast<int>(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<int>(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> 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<Point> p = std::make_shared<Point>(point_format_id, scale, offset, num_extra_bytes);
+    std::shared_ptr<Point> p = std::make_shared<Point>(point_format_id, scale, offset, eb_byte_size);
 
     p->x_ = unpack<int32_t>(in_stream);
     p->y_ = unpack<int32_t>(in_stream);
     p->z_ = unpack<int32_t>(in_stream);
     p->intensity_ = unpack<uint16_t>(in_stream);
-    if (p->extended_point_type_)
-    {
-        p->extended_returns_ = unpack<uint8_t>(in_stream);
-        p->extended_flags_ = unpack<uint8_t>(in_stream);
-        p->extended_classification_ = unpack<uint8_t>(in_stream);
-        p->user_data_ = unpack<uint8_t>(in_stream);
-        p->extended_scan_angle_ = unpack<int16_t>(in_stream);
-    }
-    else
-    {
-        p->returns_flags_eof_ = unpack<uint8_t>(in_stream);
-        p->classification_ = unpack<uint8_t>(in_stream);
-        p->scan_angle_rank_ = unpack<int8_t>(in_stream);
-        p->user_data_ = unpack<uint8_t>(in_stream);
-    }
+    p->returns_ = unpack<uint8_t>(in_stream);
+    p->flags_ = unpack<uint8_t>(in_stream);
+    p->classification_ = unpack<uint8_t>(in_stream);
+    p->user_data_ = unpack<uint8_t>(in_stream);
+    p->scan_angle_ = unpack<int16_t>(in_stream);
     p->point_source_id_ = unpack<uint16_t>(in_stream);
-
-    if (p->has_gps_time_)
-    {
-        p->gps_time_ = unpack<double>(in_stream);
-    }
+    p->gps_time_ = unpack<double>(in_stream);
     if (p->has_rgb_)
     {
         p->rgb_[0] = unpack<uint16_t>(in_stream);
@@ -139,7 +105,7 @@ std::shared_ptr<Point> Point::Unpack(std::istream &in_stream, const int8_t &poin
         p->nir_ = unpack<uint16_t>(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<uint8_t>(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<int8_t>(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<int16_t>(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<short>(ReturnNumber())
-           << ", Number of Returns: " << static_cast<short>(NumberOfReturns()) << std::endl;
-        ss << "\t\tClassification Flags: Synthetic: " << Synthetic() << ", Key Point: " << KeyPoint()
-           << ", Withheld: " << Withheld() << ", Overlap: " << Overlap() << std::endl;
-        ss << "\t\tScannerChannel: " << static_cast<short>(ScannerChannel()) << std::endl;
-        ss << "\t\tScan Direction: " << ScanDirectionFlag() << std::endl;
-        ss << "\t\tEdge of Flight Line: " << EdgeOfFlightLineFlag() << std::endl;
-        ss << "\t\tClasification: " << static_cast<short>(Classification()) << std::endl;
-        ss << "\t\tUser Data: " << static_cast<short>(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<short>(ReturnNumber())
-           << ", Number of Returns: " << static_cast<short>(NumberOfReturns()) << std::endl;
-        ss << "\t\tScan Direction: " << ScanDirectionFlag() << std::endl;
-        ss << "\t\tEdge of Flight Line: " << EdgeOfFlightLineFlag() << std::endl;
-        ss << "\t\tClasification: " << static_cast<short>(Classification()) << std::endl;
-        ss << "\t\tClassification Flags: Synthetic: " << Synthetic() << ", Key Point: " << KeyPoint()
-           << ", Withheld: " << Withheld() << std::endl;
-        ss << "\t\tScan Angle Rank: " << static_cast<short>(scan_angle_rank_) << std::endl;
-        ss << "\t\tUser Data: " << static_cast<short>(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<short>(ReturnNumber())
+       << ", Number of Returns: " << static_cast<short>(NumberOfReturns()) << std::endl;
+    ss << "\tClassification Flags: Synthetic: " << Synthetic() << ", Key Point: " << KeyPoint()
+       << ", Withheld: " << Withheld() << ", Overlap: " << Overlap() << std::endl;
+    ss << "\tScannerChannel: " << static_cast<short>(ScannerChannel()) << std::endl;
+    ss << "\tScan Direction: " << ScanDirectionFlag() << std::endl;
+    ss << "\tEdge of Flight Line: " << EdgeOfFlightLineFlag() << std::endl;
+    ss << "\tClasification: " << static_cast<short>(Classification()) << std::endl;
+    ss << "\tUser Data: " << static_cast<short>(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<std::shared_ptr<Point>> &points)
 {
@@ -26,7 +25,7 @@ Points::Points(const std::vector<std::shared_ptr<Point>> &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<std::shared_ptr<Point>> &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> &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> &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<std::shared_ptr<Point>> 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<std::shared_ptr<Point>> points)
 
 Points Points::Unpack(const std::vector<char> &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<char> &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<char> &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<char> &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<int>(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<EbVlr::ebfield> &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<char> uncompressed_points = reader.GetPointData(node);
                 std::vector<char> 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<char> compressed_points = reader.GetPointDataCompressed(node.key);
             std::vector<char> 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 <cmath>
 #include <iostream>
-#include <limits>
 #include <string>
 #include <vector>
 
@@ -9,15 +8,17 @@
 #include <pybind11/stl.h>
 #include <pybind11/stl_bind.h>
 
+#include <copc-lib/copc/extents.hpp>
+#include <copc-lib/copc/info.hpp>
 #include <copc-lib/geometry/box.hpp>
 #include <copc-lib/hierarchy/key.hpp>
 #include <copc-lib/hierarchy/node.hpp>
 #include <copc-lib/io/reader.hpp>
 #include <copc-lib/io/writer.hpp>
-#include <copc-lib/las/file.hpp>
 #include <copc-lib/las/header.hpp>
 #include <copc-lib/las/point.hpp>
 #include <copc-lib/las/points.hpp>
+#include <copc-lib/las/vlr.hpp>
 #include <copc-lib/laz/compressor.hpp>
 #include <copc-lib/laz/decompressor.hpp>
 
@@ -28,7 +29,6 @@ PYBIND11_MAKE_OPAQUE(std::vector<char>);
 
 PYBIND11_MODULE(copclib, m)
 {
-
     py::bind_vector<std::vector<char>>(m, "VectorChar", py::buffer_protocol())
         .def(py::pickle(
             [](const std::vector<char> &vec) { // __getstate__
@@ -43,16 +43,17 @@ PYBIND11_MODULE(copclib, m)
 
     py::class_<VoxelKey>(m, "VoxelKey")
         .def(py::init<>())
-        .def(py::init<int32_t, int32_t, int32_t, int32_t>(), py::arg("d"), py::arg("x"), py::arg("y"), py::arg("z"))
+        .def(py::init<const int32_t &, const int32_t &, const int32_t &, const int32_t &>(), py::arg("d"), py::arg("x"),
+             py::arg("y"), py::arg("z"))
+        .def(py::init<const std::vector<int32_t> &>(), 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<const Box &>(&Box::Contains, py::const_))
         .def("Contains", py::overload_cast<const Vector3 &>(&Box::Contains, py::const_))
@@ -112,7 +113,6 @@ PYBIND11_MODULE(copclib, m)
         .def("__str__", &Box::ToString)
         .def("__repr__", &Box::ToString);
 
-    py::implicitly_convertible<py::list, Box>();
     py::implicitly_convertible<py::tuple, Box>();
 
     py::class_<Node>(m, "Node")
@@ -156,8 +156,8 @@ PYBIND11_MODULE(copclib, m)
 
     py::class_<Vector3>(m, "Vector3")
         .def(py::init<>())
-        .def(py::init<const double &, const double &, const double &>())
-        .def(py::init<const std::vector<double> &>())
+        .def(py::init<const double &, const double &, const double &>(), py::arg("x"), py::arg("y"), py::arg("z"))
+        .def(py::init<const std::vector<double> &>(), 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<double>(), t[1].cast<double>(), t[2].cast<double>());
             }));
+
     py::implicitly_convertible<py::list, Vector3>();
     py::implicitly_convertible<py::tuple, Vector3>();
 
     py::class_<las::Point, std::shared_ptr<las::Point>>(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<const double &>(&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<const double &>(&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<const double &>(&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<const int32_t &>(&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<const int32_t &>(&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<const int32_t &>(&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<const uint16_t &>(&las::Point::Intensity))
-        .def_property("ReturnsScanDirEofBitFields",
-                      py::overload_cast<>(&las::Point::ReturnsScanDirEofBitFields, py::const_),
-                      py::overload_cast<const uint8_t &>(&las::Point::ReturnsScanDirEofBitFields))
-        .def_property("ExtendedReturnsBitFields",
-                      py::overload_cast<>(&las::Point::ExtendedReturnsBitFields, py::const_),
-                      py::overload_cast<const uint8_t &>(&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<const uint8_t &>(&las::Point::ReturnsBitField))
+        .def_property("return_number", py::overload_cast<>(&las::Point::ReturnNumber, py::const_),
                       py::overload_cast<const uint8_t &>(&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<const uint8_t &>(&las::Point::NumberOfReturns))
-        .def_property("ExtendedFlagsBitFields", py::overload_cast<>(&las::Point::ExtendedFlagsBitFields, py::const_),
-                      py::overload_cast<const uint8_t &>(&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<const uint8_t &>(&las::Point::FlagsBitField))
+        .def_property("synthetic", py::overload_cast<>(&las::Point::Synthetic, py::const_),
                       py::overload_cast<const bool &>(&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<const bool &>(&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<const bool &>(&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<const bool &>(&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<const uint8_t &>(&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<const bool &>(&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<const bool &>(&las::Point::EdgeOfFlightLineFlag))
-        .def_property("ClassificationBitFields", py::overload_cast<>(&las::Point::ClassificationBitFields, py::const_),
-                      py::overload_cast<const uint8_t &>(&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<const uint8_t &>(&las::Point::Classification))
-        .def_property("ScanAngleRank", py::overload_cast<>(&las::Point::ScanAngleRank, py::const_),
-                      py::overload_cast<const int8_t &>(&las::Point::ScanAngleRank))
-        .def_property("ExtendedScanAngle", py::overload_cast<>(&las::Point::ExtendedScanAngle, py::const_),
-                      py::overload_cast<const int16_t &>(&las::Point::ExtendedScanAngle))
-        .def_property("ScanAngle", py::overload_cast<>(&las::Point::ScanAngle, py::const_),
-                      py::overload_cast<const float &>(&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<const int16_t &>(&las::Point::ScanAngle))
+        .def_property("scan_angle_degrees", py::overload_cast<>(&las::Point::ScanAngleDegrees, py::const_),
+                      py::overload_cast<const float &>(&las::Point::ScanAngleDegrees))
+        .def_property("user_data", py::overload_cast<>(&las::Point::UserData, py::const_),
                       py::overload_cast<const uint8_t &>(&las::Point::UserData))
-        .def_property("PointSourceID", py::overload_cast<>(&las::Point::PointSourceID, py::const_),
-                      py::overload_cast<const uint16_t &>(&las::Point::PointSourceID))
-        .def_property("RGB", nullptr, py::overload_cast<const std::vector<uint16_t> &>(&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<const uint16_t &>(&las::Point::PointSourceId))
+        .def_property("rgb", nullptr, py::overload_cast<const std::vector<uint16_t> &>(&las::Point::Rgb))
+        .def_property("red", py::overload_cast<>(&las::Point::Red, py::const_),
                       py::overload_cast<const uint16_t &>(&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<const uint16_t &>(&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<const uint16_t &>(&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<const double &>(&las::Point::GPSTime))
-        .def_property("NIR", py::overload_cast<>(&las::Point::NIR, py::const_),
-                      py::overload_cast<const uint16_t &>(&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<const uint16_t &>(&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<const std::vector<uint8_t> &>(&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_<las::Points>(m, "Points")
         .def(py::init<const uint8_t &, const Vector3 &, const Vector3 &, const uint16_t &>(),
-             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<const std::vector<std::shared_ptr<las::Point>> &>(), py::arg("points"))
         .def(py::init<const las::LasHeader &>())
-        .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<const std::vector<double> &>(&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<const std::vector<double> &>(&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<const std::vector<double> &>(&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<const std::vector<uint8_t> &>(&las::Points::Classification))
-        .def_property("PointSourceID", py::overload_cast<>(&las::Points::PointSourceID, py::const_),
-                      py::overload_cast<const std::vector<uint8_t> &>(&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<const std::vector<uint8_t> &>(&las::Points::PointSourceId))
+        .def_property("red", py::overload_cast<>(&las::Points::Red, py::const_),
                       py::overload_cast<const std::vector<uint16_t> &>(&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<const std::vector<uint16_t> &>(&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<const std::vector<uint16_t> &>(&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>(&las::Points::AddPoints))
         .def("AddPoints", py::overload_cast<std::vector<std::shared_ptr<las::Point>>>(&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<std::string &>())
         .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<const Node &>(&Reader::GetPointData), py::arg("node"))
         .def("GetPointData", py::overload_cast<const VoxelKey &>(&Reader::GetPointData), py::arg("key"))
         .def("GetPoints", py::overload_cast<const Node &>(&Reader::GetPoints), py::arg("node"))
@@ -417,8 +392,8 @@ PYBIND11_MODULE(copclib, m)
              py::arg("node"))
         .def("GetPointDataCompressed", py::overload_cast<const VoxelKey &>(&Reader::GetPointDataCompressed),
              py::arg("key"))
-        .def("GetAllChildren", py::overload_cast<const VoxelKey &>(&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_<FileWriter>(m, "FileWriter")
-        .def(py::init<const std::string &, Writer::LasConfig const &, const int &, const std::string &>(),
-             py::arg("file_path"), py::arg("config"), py::arg("span") = 0, py::arg("wkt") = "")
+        .def(py::init<const std::string &, CopcConfigWriter const &>(), 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<Page &, const VoxelKey &, las::Points &>(&Writer::AddNode), py::arg("page"),
@@ -443,14 +414,11 @@ PYBIND11_MODULE(copclib, m)
         .def("AddNodeCompressed", &Writer::AddNodeCompressed)
         .def("AddNode", py::overload_cast<Page &, const VoxelKey &, std::vector<char> 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<std::vector<char> &, 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<std::vector<char> &, const las::LasHeader &>(&laz::Compressor::CompressBytes));
 
@@ -461,36 +429,35 @@ PYBIND11_MODULE(copclib, m)
     m.def("DecompressBytes",
           py::overload_cast<const std::vector<char> &, 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_<las::LasHeader>(m, "LasHeader")
+    py::class_<las::LasHeader, std::shared_ptr<las::LasHeader>>(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<const std::string &>(&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<const std::string &>(&las::LasHeader::SystemIdentifier))
         .def_property("generating_software", py::overload_cast<>(&las::LasHeader::GeneratingSoftware, py::const_),
                       py::overload_cast<const std::string &>(&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<int8_t>(), t[10].cast<uint16_t>(), t[7].cast<uint32_t>(),
+                                 t[17].cast<uint64_t>(), t[8].cast<uint32_t>(), t[11].cast<Vector3>(),
+                                 t[12].cast<Vector3>(), t[15].cast<uint64_t>(), t[16].cast<uint32_t>());
                 h.file_source_id = t[0].cast<uint16_t>();
                 h.global_encoding = t[1].cast<uint16_t>();
                 h.GUID(t[2].cast<std::string>());
-                h.version_major = t[3].cast<uint8_t>();
-                h.version_minor = t[4].cast<uint8_t>();
-                h.SystemIdentifier(t[5].cast<std::string>());
-                h.GeneratingSoftware(t[6].cast<std::string>());
-                h.creation_day = t[7].cast<uint16_t>();
-                h.creation_year = t[8].cast<uint16_t>();
-                h.header_size = t[9].cast<uint16_t>();
-                h.point_offset = t[10].cast<uint32_t>();
-                h.vlr_count = t[11].cast<uint32_t>();
-                h.point_format_id = t[12].cast<int8_t>();
-                h.point_record_length = t[13].cast<uint16_t>();
-                h.point_count = t[14].cast<uint32_t>();
-                h.points_by_return = t[15].cast<std::array<uint32_t, 5>>();
-                h.scale = t[16].cast<Vector3>();
-                h.offset = t[17].cast<Vector3>();
-                h.max = t[18].cast<Vector3>();
-                h.min = t[19].cast<Vector3>();
-                h.wave_offset = t[20].cast<uint64_t>();
-                h.evlr_offset = t[21].cast<uint64_t>();
-                h.evlr_count = t[22].cast<uint32_t>();
-                h.point_count_14 = t[23].cast<uint64_t>();
-                h.points_by_return_14 = t[24].cast<std::array<uint64_t, 15>>();
+                h.SystemIdentifier(t[3].cast<std::string>());
+                h.GeneratingSoftware(t[4].cast<std::string>());
+                h.creation_day = t[5].cast<uint16_t>();
+                h.creation_year = t[6].cast<uint16_t>();
+                h.max = t[13].cast<Vector3>();
+                h.min = t[14].cast<Vector3>();
+                h.points_by_return = t[18].cast<std::array<uint64_t, 15>>();
                 return h;
             }));
 
-    py::class_<Writer::LasConfig>(m, "LasConfig")
-        .def(py::init<const int8_t &, const Vector3 &, const Vector3 &>(), py::arg("point_format_id"),
-             py::arg("scale") = Vector3::DefaultScale(), py::arg("offset") = Vector3::DefaultOffset())
-        .def(py::init<const las::LasHeader &, const las::EbVlr &>())
-        .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<const std::string &>(&Writer::LasConfig::GUID))
-        .def_property("system_identifier", py::overload_cast<>(&Writer::LasConfig::SystemIdentifier, py::const_),
-                      py::overload_cast<const std::string &>(&Writer::LasConfig::SystemIdentifier))
-        .def_property("generating_software", py::overload_cast<>(&Writer::LasConfig::GeneratingSoftware, py::const_),
-                      py::overload_cast<const std::string &>(&Writer::LasConfig::GeneratingSoftware))
-        .def("NumExtraBytes", &Writer::LasConfig::NumExtraBytes);
-
-    py::class_<las::CopcVlr>(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_<las::EbVlr>(m, "EbVlr").def(py::init<int>());
+    py::class_<las::EbVlr>(m, "EbVlr").def(py::init<int>()).def_readwrite("items", &las::EbVlr::items);
+
+    py::class_<las::EbVlr::ebfield>(m, "EbField").def(py::init<>());
+
+    py::class_<CopcConfig, std::shared_ptr<CopcConfig>>(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_<CopcConfigWriter, std::shared_ptr<CopcConfigWriter>>(m, "CopcConfigWriter")
+        .def(
+            py::init<const int8_t &, const Vector3 &, const Vector3 &, const std::string &, const las::EbVlr &, bool>(),
+            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<const CopcConfig &>())
+        .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<CopcConfig, CopcConfigWriter>();
+
+    py::class_<CopcInfo, std::shared_ptr<CopcInfo>>(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_<CopcExtent, std::shared_ptr<CopcExtent>>(m, "CopcExtent")
+        .def(py::init<>())
+        .def(py::init<double, double, double, double>(), py::arg("minimum"), py::arg("maximum"), py::arg("mean") = 0,
+             py::arg("var") = 1)
+        .def(py::init<const std::vector<double> &>(), 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::tuple, CopcExtent>();
+
+    py::class_<CopcExtents, std::shared_ptr<CopcExtents>>(m, "CopcExtents")
+        .def(py::init<int8_t, uint16_t>(), 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<std::shared_ptr<CopcExtent>>(&CopcExtents::Intensity))
+        .def_property("return_number", py::overload_cast<>(&CopcExtents::ReturnNumber),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::ReturnNumber))
+        .def_property("number_of_returns", py::overload_cast<>(&CopcExtents::NumberOfReturns),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::NumberOfReturns))
+        .def_property("scanner_channel", py::overload_cast<>(&CopcExtents::ScannerChannel),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::ScannerChannel))
+        .def_property("scan_direction_flag", py::overload_cast<>(&CopcExtents::ScanDirectionFlag),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::ScanDirectionFlag))
+        .def_property("edge_of_flight_line", py::overload_cast<>(&CopcExtents::EdgeOfFlightLine),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::EdgeOfFlightLine))
+        .def_property("classification", py::overload_cast<>(&CopcExtents::Classification),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::Classification))
+        .def_property("user_data", py::overload_cast<>(&CopcExtents::UserData),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::UserData))
+        .def_property("scan_angle", py::overload_cast<>(&CopcExtents::ScanAngle),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::ScanAngle))
+        .def_property("point_source_id", py::overload_cast<>(&CopcExtents::PointSourceId),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::PointSourceId))
+        .def_property("gps_time", py::overload_cast<>(&CopcExtents::GpsTime),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::GpsTime))
+        .def_property("red", py::overload_cast<>(&CopcExtents::Red),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::Red))
+        .def_property("green", py::overload_cast<>(&CopcExtents::Green),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::Green))
+        .def_property("blue", py::overload_cast<>(&CopcExtents::Blue),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::Blue))
+        .def_property("nir", py::overload_cast<>(&CopcExtents::Nir),
+                      py::overload_cast<std::shared_ptr<CopcExtent>>(&CopcExtents::Nir))
+        .def_property("extra_bytes", py::overload_cast<>(&CopcExtents::ExtraBytes),
+                      py::overload_cast<std::vector<std::shared_ptr<CopcExtent>>>(&CopcExtents::ExtraBytes))
+        .def_property_readonly("extents", py::overload_cast<>(&CopcExtents::Extents))
+        .def("__str__", &CopcExtents::ToString)
+        .def("__repr__", &CopcExtents::ToString);
 }


Test patch:
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/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 <catch2/catch.hpp>
+#include <copc-lib/copc/config.hpp>
+#include <copc-lib/copc/extents.hpp>
+#include <copc-lib/geometry/vector3.hpp>
+#include <copc-lib/las/header.hpp>
+#include <copc-lib/las/utils.hpp>
+#include <copc-lib/las/vlr.hpp>
+#include <string>
+
+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,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 4bd9afab..00000000
Binary files a/test/data/las/first_20_pts.las and /dev/null differ
diff --git a/test/data/las/first_20_pts.laz b/test/data/las/first_20_pts.laz
deleted file mode 100644
index bb091f72..00000000
Binary files a/test/data/las/first_20_pts.laz and /dev/null differ
diff --git a/test/data/las/last_60_pts.las b/test/data/las/last_60_pts.las
deleted file mode 100644
index 37ba7cdb..00000000
Binary files a/test/data/las/last_60_pts.las and /dev/null differ
diff --git a/test/data/las/last_60_pts.laz b/test/data/las/last_60_pts.laz
deleted file mode 100644
index 9f052114..00000000
Binary files a/test/data/las/last_60_pts.laz and /dev/null differ
diff --git a/test/data/las/next_12_pts.las b/test/data/las/next_12_pts.las
deleted file mode 100644
index 4c765ba9..00000000
Binary files a/test/data/las/next_12_pts.las and /dev/null differ
diff --git a/test/data/las/next_12_pts.laz b/test/data/las/next_12_pts.laz
deleted file mode 100644
index 1e56d6a5..00000000
Binary files a/test/data/las/next_12_pts.laz and /dev/null differ
diff --git a/test/data/test_data.h b/test/data/test_data.h
index e3b17d84..1c7e2b6e 100644
--- a/test/data/test_data.h
+++ b/test/data/test_data.h
@@ -1,263 +1,263 @@
-// PRDF 3
-// these are generated by 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 -set_point_size 34 from files test/las/*
-unsigned char twenty_pts_prdf3[680] = {
-    0x36, 0xA8, 0xFC, 0xFF, 0x85, 0xDD, 0x02, 0x00, 0x1D, 0x79, 0xFC, 0xFF, 0x03, 0x00, 0x49, 0x00, 0xF6, 0x80, 0xA4,
-    0x1C, 0x6C, 0xA5, 0xFF, 0xCB, 0x82, 0x5B, 0x0E, 0x41, 0x5C, 0x00, 0x8C, 0x00, 0x78, 0x00, 0x02, 0xA6, 0xFC, 0xFF,
-    0x8A, 0xE0, 0x02, 0x00, 0x13, 0x7A, 0xFC, 0xFF, 0x01, 0x00, 0x49, 0x00, 0xF6, 0x80, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F,
-    0xF3, 0x82, 0x5B, 0x0E, 0x41, 0xAE, 0x00, 0xA9, 0x00, 0xA9, 0x00, 0x1E, 0xA6, 0xFC, 0xFF, 0xCF, 0xDF, 0x02, 0x00,
-    0x19, 0x7A, 0xFC, 0xFF, 0x0A, 0x00, 0x49, 0x00, 0xF6, 0x80, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0xF3, 0x82, 0x5B, 0x0E,
-    0x41, 0xA4, 0x00, 0xA3, 0x00, 0x9E, 0x00, 0xB0, 0xA6, 0xFC, 0xFF, 0x4C, 0xDC, 0x02, 0x00, 0x93, 0x78, 0xFC, 0xFF,
-    0x02, 0x00, 0x49, 0x00, 0xF6, 0x80, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0xF3, 0x82, 0x5B, 0x0E, 0x41, 0x4A, 0x00, 0x78,
-    0x00, 0x70, 0x00, 0xAA, 0xA5, 0xFC, 0xFF, 0x8E, 0xDB, 0x02, 0x00, 0xE5, 0x78, 0xFC, 0xFF, 0x1B, 0x00, 0x09, 0x00,
-    0xF6, 0x7C, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x0F, 0x83, 0x5B, 0x0E, 0x41, 0x56, 0x00, 0x80, 0x00, 0x71, 0x00, 0x0E,
-    0xA5, 0xFC, 0xFF, 0x52, 0xDC, 0x02, 0x00, 0x30, 0x79, 0xFC, 0xFF, 0xF3, 0x00, 0x49, 0x00, 0xF6, 0x7E, 0xA4, 0x1C,
-    0x6C, 0xA5, 0x7F, 0x1A, 0x83, 0x5B, 0x0E, 0x41, 0x58, 0x00, 0x75, 0x00, 0x71, 0x00, 0x2D, 0xA5, 0xFC, 0xFF, 0x90,
-    0xDB, 0x02, 0x00, 0x02, 0x79, 0xFC, 0xFF, 0xB7, 0x00, 0x49, 0x00, 0xF6, 0x7E, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x1A,
-    0x83, 0x5B, 0x0E, 0x41, 0x5A, 0x00, 0x82, 0x00, 0x72, 0x00, 0xB4, 0xA5, 0xFC, 0xFF, 0x48, 0xE0, 0x02, 0x00, 0x2A,
-    0x7A, 0xFC, 0xFF, 0x05, 0x00, 0x09, 0x00, 0xFD, 0x78, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0xCA, 0x56, 0x71, 0x0E, 0x41,
-    0xA6, 0x00, 0xA4, 0x00, 0xB1, 0x00, 0x37, 0xA5, 0xFC, 0xFF, 0x49, 0xDC, 0x02, 0x00, 0x20, 0x79, 0xFC, 0xFF, 0xA6,
-    0x00, 0x49, 0x00, 0xFD, 0x7A, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0xDA, 0x56, 0x71, 0x0E, 0x41, 0x58, 0x00, 0x75, 0x00,
-    0x71, 0x00, 0x01, 0xA7, 0xFC, 0xFF, 0xDD, 0xDF, 0x02, 0x00, 0x0F, 0x7A, 0xFC, 0xFF, 0x01, 0x00, 0x09, 0x00, 0xFD,
-    0x78, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0xF1, 0x56, 0x71, 0x0E, 0x41, 0xA8, 0x00, 0xA2, 0x00, 0x97, 0x00, 0x0D, 0xA6,
-    0xFC, 0xFF, 0x49, 0xDC, 0x02, 0x00, 0x02, 0x79, 0xFC, 0xFF, 0x92, 0x00, 0x09, 0x00, 0xFD, 0x78, 0xA5, 0x1C, 0x6C,
-    0xA5, 0x7F, 0xF1, 0x56, 0x71, 0x0E, 0x41, 0x4C, 0x00, 0x73, 0x00, 0x6E, 0x00, 0x4D, 0xB2, 0xFC, 0xFF, 0x0D, 0xE6,
-    0x02, 0x00, 0x0F, 0x7B, 0xFC, 0xFF, 0x02, 0x00, 0x09, 0x00, 0xF4, 0x7C, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0xD5, 0x81,
-    0x5B, 0x0E, 0x41, 0x66, 0x00, 0x8D, 0x00, 0x7B, 0x00, 0x2E, 0xB2, 0xFC, 0xFF, 0xBE, 0xE6, 0x02, 0x00, 0x2A, 0x7B,
-    0xFC, 0xFF, 0x01, 0x00, 0x09, 0x00, 0xF4, 0x7C, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0xD5, 0x81, 0x5B, 0x0E, 0x41, 0x73,
-    0x00, 0x92, 0x00, 0x83, 0x00, 0xD0, 0xA7, 0xFC, 0xFF, 0xF9, 0x11, 0x03, 0x00, 0x3C, 0x79, 0xFC, 0xFF, 0x5F, 0x00,
-    0x09, 0x00, 0xF2, 0x7D, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x25, 0x82, 0x5B, 0x0E, 0x41, 0x7B, 0x00, 0x87, 0x00, 0x93,
-    0x00, 0xC0, 0xA8, 0xFC, 0xFF, 0x46, 0x1B, 0x03, 0x00, 0xBD, 0x79, 0xFC, 0xFF, 0x03, 0x00, 0x09, 0x00, 0xFF, 0x76,
-    0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0x8E, 0x55, 0x71, 0x0E, 0x41, 0x85, 0x00, 0x8E, 0x00, 0x96, 0x00, 0x89, 0xA8, 0xFC,
-    0xFF, 0x5E, 0x1A, 0x03, 0x00, 0x16, 0x7A, 0xFC, 0xFF, 0x04, 0x00, 0x09, 0x00, 0xFF, 0x76, 0xA5, 0x1C, 0x6C, 0xA5,
-    0xFF, 0x8E, 0x55, 0x71, 0x0E, 0x41, 0x85, 0x00, 0x8D, 0x00, 0x96, 0x00, 0x32, 0xA9, 0xFC, 0xFF, 0x7B, 0x1B, 0x03,
-    0x00, 0xE2, 0x78, 0xFC, 0xFF, 0x0C, 0x00, 0x49, 0x00, 0x0D, 0x81, 0xA6, 0x1C, 0x6C, 0xA5, 0xFF, 0x72, 0xBD, 0x7D,
-    0x0E, 0x41, 0x84, 0x00, 0x8E, 0x00, 0x96, 0x00, 0x3A, 0xA9, 0xFC, 0xFF, 0x4F, 0x1B, 0x03, 0x00, 0xC1, 0x79, 0xFC,
-    0xFF, 0xF8, 0x00, 0x49, 0x00, 0x0D, 0x80, 0xA6, 0x1C, 0x6C, 0xA5, 0xFF, 0x72, 0xBD, 0x7D, 0x0E, 0x41, 0x84, 0x00,
-    0x8E, 0x00, 0x96, 0x00, 0x0E, 0xA9, 0xFC, 0xFF, 0x24, 0x1B, 0x03, 0x00, 0xB7, 0x79, 0xFC, 0xFF, 0x0C, 0x00, 0x09,
-    0x00, 0x0D, 0x84, 0xA6, 0x1C, 0x6C, 0xA5, 0xFF, 0x77, 0xBD, 0x7D, 0x0E, 0x41, 0x84, 0x00, 0x8E, 0x00, 0x96, 0x00,
-    0x01, 0xA9, 0xFC, 0xFF, 0x72, 0x1B, 0x03, 0x00, 0x6B, 0x79, 0xFC, 0xFF, 0x1B, 0x00, 0x09, 0x00, 0x0D, 0x84, 0xA6,
-    0x1C, 0x6C, 0xA5, 0xFF, 0x77, 0xBD, 0x7D, 0x0E, 0x41, 0x84, 0x00, 0x8E, 0x00, 0x96, 0x00};
+unsigned char first_20_pts_compressed[360] = {
+    0xD1, 0xB6, 0xFD, 0xFF, 0xFA, 0x9A, 0xFC, 0xFF, 0xE8, 0x80, 0xFC, 0xFF, 0x58, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65,
+    0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0x79, 0x3A, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x14, 0x00,
+    0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x19,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x00, 0x00,
+    0x14, 0x00, 0x00, 0x00, 0x20, 0x6B, 0xC1, 0xEA, 0x0B, 0x81, 0x2B, 0x95, 0x0E, 0x4E, 0x9C, 0x53, 0xE0, 0x21, 0x96,
+    0x33, 0x7A, 0xB5, 0x62, 0x87, 0xF4, 0xCB, 0x03, 0xC7, 0x89, 0xF7, 0xD6, 0x27, 0xCE, 0xFA, 0x2D, 0x2E, 0xF9, 0x75,
+    0x8E, 0x01, 0xE5, 0x40, 0xFF, 0x87, 0x09, 0xBB, 0x04, 0xB9, 0xE3, 0xDA, 0x1E, 0xB4, 0xBB, 0xE6, 0xCA, 0x9B, 0xBB,
+    0x19, 0x4E, 0x5E, 0x4E, 0x19, 0x7F, 0x9A, 0xED, 0x5E, 0x9C, 0x0D, 0xCA, 0x2C, 0x4C, 0xC2, 0x23, 0x34, 0x14, 0xE0,
+    0xA0, 0xB5, 0xF5, 0x5A, 0x28, 0x1D, 0x00, 0x00, 0x00, 0x44, 0x36, 0xC6, 0x70, 0xA2, 0x44, 0x1F, 0xC0, 0xC7, 0x6A,
+    0x71, 0x47, 0x09, 0xE7, 0x68, 0x46, 0x25, 0xED, 0x3B, 0x64, 0x56, 0x82, 0x00, 0x92, 0xAE, 0x25, 0xD8, 0xC8, 0xC1,
+    0xA0, 0x91, 0xB3, 0x92, 0xC1, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x01, 0x03, 0xEF, 0xBD, 0xF8, 0x00, 0x00, 0x00, 0x10,
+    0x3E, 0xFB, 0xE0, 0x00, 0x00, 0x00, 0x3B, 0x16, 0x60, 0x00, 0x09, 0xFE, 0x90, 0xA7, 0xD5, 0x04, 0xA9, 0xCC, 0xB0,
+    0x66, 0xDA, 0x34, 0x9B, 0xC3, 0x0A, 0x24, 0x92, 0xF5, 0x00, 0x00, 0x00, 0x7A, 0x7C, 0x3F, 0x3E, 0x3E, 0x3C, 0x3C,
+    0x3D, 0x3D, 0x40, 0x40, 0x3E, 0xBF, 0xBD, 0xBD, 0x3C, 0x3C, 0x3D, 0x3C, 0xC3, 0x00, 0x00, 0x00, 0x1E, 0x8A, 0x5B,
+    0xD4, 0xA3, 0x66, 0xCF, 0xA2, 0x9F, 0xCA, 0x3E, 0xC3, 0x65, 0x25, 0x89, 0x14, 0x45, 0xB3, 0x83, 0xD7, 0xD5, 0x3A,
+    0x15, 0x3E, 0x3A, 0xE3, 0xE7, 0x2C, 0x37, 0x15, 0x52, 0x73, 0x1A, 0xE9, 0xE7, 0x65, 0x1B, 0x5E, 0x7F, 0x26, 0xA9,
+    0xD9, 0xF4, 0x4E, 0xC9, 0x62, 0xBD, 0xA2, 0x19, 0xA7, 0x77, 0xFC, 0x94, 0x39, 0xC4, 0x17, 0x96, 0xC2, 0xC1, 0x81,
+    0x70, 0x89, 0xFE, 0x2F, 0x52, 0xCB, 0x6D, 0xEB, 0x44, 0x34, 0x24, 0xC3, 0xA4, 0x8C, 0x00, 0x00, 0x00, 0x81, 0x01,
+    0x83, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC0, 0x00, 0x00, 0x00};
 
-unsigned char twenty_pts_prdf3_compressed[325] = {
-    0x36, 0xA8, 0xFC, 0xFF, 0x85, 0xDD, 0x02, 0x00, 0x1D, 0x79, 0xFC, 0xFF, 0x03, 0x00, 0x49, 0x00, 0xF6, 0x80, 0xA4,
-    0x1C, 0x6C, 0xA5, 0xFF, 0xCB, 0x82, 0x5B, 0x0E, 0x41, 0x5C, 0x00, 0x8C, 0x00, 0x78, 0x00, 0x40, 0x27, 0x7F, 0x4A,
-    0x59, 0x4B, 0x6F, 0x91, 0xB3, 0x3A, 0x8E, 0x25, 0xFE, 0x86, 0x94, 0x3B, 0xED, 0x2F, 0x62, 0xF0, 0xE5, 0x35, 0x2A,
-    0x6E, 0x9C, 0x4A, 0x67, 0xBD, 0x22, 0xC8, 0x98, 0x0C, 0x8F, 0xA3, 0xBA, 0x54, 0xEF, 0x16, 0x76, 0x44, 0x20, 0xB2,
-    0x44, 0xF4, 0x4C, 0x6C, 0x6B, 0x6E, 0xBF, 0x90, 0x0F, 0x48, 0x10, 0xF1, 0xF2, 0xFF, 0xC2, 0x10, 0x0E, 0x35, 0x3A,
-    0x14, 0x1B, 0xB4, 0x53, 0x6E, 0x14, 0x4F, 0x5D, 0x36, 0x22, 0xFC, 0x0C, 0xE1, 0x8E, 0x9A, 0x24, 0x91, 0xD7, 0x02,
-    0x21, 0xE9, 0x75, 0x6D, 0xC3, 0xC9, 0x6B, 0xD0, 0x53, 0xC7, 0xA2, 0x2D, 0x93, 0x98, 0xAB, 0xE8, 0x8C, 0xBA, 0x5D,
-    0x66, 0xA1, 0xFA, 0x3F, 0xE6, 0x8E, 0xD0, 0x00, 0xF5, 0xA0, 0x2C, 0xCE, 0xF9, 0x90, 0x4C, 0x81, 0x9B, 0xEF, 0xF0,
-    0xB7, 0x36, 0xA5, 0x7D, 0xEE, 0x24, 0xBC, 0xB0, 0xBE, 0x8B, 0x75, 0x31, 0x92, 0x07, 0x4E, 0xF8, 0xEC, 0x56, 0xB5,
-    0x6B, 0x17, 0x31, 0xF2, 0x53, 0xE7, 0x14, 0x65, 0x25, 0x37, 0xCB, 0x77, 0x88, 0xF9, 0x41, 0x38, 0x88, 0x51, 0x4A,
-    0xE4, 0x8E, 0x0B, 0x91, 0x1A, 0x02, 0x3F, 0x99, 0x70, 0x96, 0xD8, 0x47, 0xDF, 0x42, 0xF3, 0x6A, 0x74, 0x01, 0xF9,
-    0x35, 0xC4, 0x8E, 0x7D, 0x73, 0x76, 0x13, 0xC6, 0x6A, 0x5C, 0x77, 0x62, 0xAF, 0xCE, 0xBD, 0x94, 0xD2, 0x9A, 0xCD,
-    0x60, 0xB4, 0xF0, 0xB8, 0xEC, 0x04, 0x7B, 0xE7, 0xEB, 0x7F, 0xEF, 0x18, 0x9B, 0x05, 0x2D, 0x1A, 0x8E, 0x23, 0xE6,
-    0xA6, 0xE5, 0x94, 0xA8, 0xCD, 0x8D, 0x37, 0x80, 0x52, 0x6E, 0x27, 0xFA, 0xC2, 0xFF, 0x99, 0x9B, 0xF3, 0xA7, 0x02,
-    0xBB, 0xF4, 0xF3, 0x29, 0x78, 0x1B, 0xCB, 0xF8, 0x5B, 0x33, 0x8C, 0xBA, 0x3E, 0xFF, 0xE9, 0x8D, 0xE6, 0xCE, 0xDE,
-    0x2E, 0x18, 0x47, 0x99, 0x3C, 0x40, 0x2F, 0x3A, 0x0B, 0x65, 0x45, 0x6B, 0xB6, 0xFF, 0xE9, 0x97, 0x25, 0x59, 0x44,
-    0x9E, 0xDC, 0x52, 0x68, 0x59, 0x38, 0xF2, 0xF5, 0xBF, 0xEA, 0x16, 0x60, 0x26, 0x02, 0x10, 0x18, 0x8F, 0x09, 0x17,
-    0x00, 0x00};
+unsigned char first_20_pts[720] = {
+    0xD1, 0xB6, 0xFD, 0xFF, 0xFA, 0x9A, 0xFC, 0xFF, 0xE8, 0x80, 0xFC, 0xFF, 0x58, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65,
+    0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0x79, 0x3A, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x0F, 0xB7,
+    0xFD, 0xFF, 0x10, 0x9A, 0xFC, 0xFF, 0xB4, 0x81, 0xFC, 0xFF, 0x60, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E,
+    0x1C, 0x6C, 0xE5, 0x7E, 0x3A, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x58, 0xB6, 0xFD, 0xFF,
+    0xE6, 0x98, 0xFC, 0xFF, 0x86, 0x7F, 0xFC, 0xFF, 0x8B, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C,
+    0x65, 0xF5, 0x51, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x6F, 0xB6, 0xFD, 0xFF, 0x7E, 0x99,
+    0xFC, 0xFF, 0xB7, 0x83, 0xFC, 0xFF, 0x19, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0xFA,
+    0x51, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xE5, 0xB5, 0xFD, 0xFF, 0x6A, 0x9B, 0xFC, 0xFF,
+    0x26, 0x81, 0xFC, 0xFF, 0x8B, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0x05, 0x52, 0x43,
+    0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x65, 0xB5, 0xFD, 0xFF, 0x48, 0x9D, 0xFC, 0xFF, 0x6C, 0x7F,
+    0xFC, 0xFF, 0x9C, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0x0F, 0x52, 0x43, 0xF4, 0x0D,
+    0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xE6, 0xB4, 0xFD, 0xFF, 0x11, 0x9D, 0xFC, 0xFF, 0x86, 0x7F, 0xFC, 0xFF,
+    0x7F, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0xA9, 0x61, 0x43, 0xF4, 0x0D, 0x41, 0x00,
+    0x42, 0x00, 0xFF, 0x00, 0x06, 0xBB, 0xB5, 0xFD, 0xFF, 0x27, 0x9A, 0xFC, 0xFF, 0x7F, 0x83, 0xFC, 0xFF, 0x84, 0x00,
+    0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0xB9, 0x61, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00,
+    0xFF, 0x00, 0x06, 0xDF, 0xB5, 0xFD, 0xFF, 0x52, 0x99, 0xFC, 0xFF, 0xB6, 0x82, 0xFC, 0xFF, 0x76, 0x00, 0x11, 0x40,
+    0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0xBE, 0x61, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00,
+    0x06, 0xDF, 0xB5, 0xFD, 0xFF, 0xA4, 0x98, 0xFC, 0xFF, 0x09, 0x80, 0xFC, 0xFF, 0x53, 0x00, 0x11, 0x40, 0x05, 0x7A,
+    0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0xC4, 0x61, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x02,
+    0xB5, 0xFD, 0xFF, 0xD3, 0x98, 0xFC, 0xFF, 0xA7, 0x80, 0xFC, 0xFF, 0x70, 0x00, 0x11, 0x00, 0x05, 0x7D, 0x65, 0xFD,
+    0x9E, 0x1C, 0x6C, 0x25, 0x30, 0x79, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x02, 0xB5, 0xFD,
+    0xFF, 0x32, 0x9A, 0xFC, 0xFF, 0x26, 0x86, 0xFC, 0xFF, 0x7C, 0x00, 0x11, 0x00, 0x05, 0x7D, 0x65, 0xFD, 0x9E, 0x1C,
+    0x6C, 0xA5, 0x3A, 0x79, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xC2, 0xB4, 0xFD, 0xFF, 0x1E,
+    0x9B, 0xFC, 0xFF, 0x44, 0x85, 0xFC, 0xFF, 0x7F, 0x00, 0x11, 0x00, 0x05, 0x7D, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5,
+    0x3F, 0x79, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xA0, 0xB4, 0xFD, 0xFF, 0xF0, 0x9B, 0xFC,
+    0xFF, 0x20, 0x86, 0xFC, 0xFF, 0x08, 0x00, 0x11, 0x00, 0x05, 0x7E, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0x45, 0x79,
+    0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xD9, 0xB2, 0xFD, 0xFF, 0x27, 0xA3, 0xFC, 0xFF, 0xF2,
+    0x81, 0xFC, 0xFF, 0x78, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0x6F, 0x79, 0x43, 0xF4,
+    0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x73, 0xB2, 0xFD, 0xFF, 0x3E, 0xA4, 0xFC, 0xFF, 0x03, 0x7F, 0xFC,
+    0xFF, 0x70, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0x74, 0x79, 0x43, 0xF4, 0x0D, 0x41,
+    0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xA3, 0xB2, 0xFD, 0xFF, 0x8D, 0xA1, 0xFC, 0xFF, 0x90, 0x81, 0xFC, 0xFF, 0x3F,
+    0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0xC9, 0x88, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42,
+    0x00, 0xFF, 0x00, 0x06, 0xDB, 0xB2, 0xFD, 0xFF, 0xA4, 0xA0, 0xFC, 0xFF, 0xFC, 0x81, 0xFC, 0xFF, 0x0E, 0x00, 0x11,
+    0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0xCF, 0x88, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF,
+    0x00, 0x06, 0x13, 0xB3, 0xFD, 0xFF, 0xBB, 0x9F, 0xFC, 0xFF, 0x65, 0x82, 0xFC, 0xFF, 0x63, 0x00, 0x11, 0x40, 0x05,
+    0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0xD4, 0x88, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06,
+    0x2A, 0xB3, 0xFD, 0xFF, 0xF7, 0x9E, 0xFC, 0xFF, 0x39, 0x81, 0xFC, 0xFF, 0x1C, 0x00, 0x21, 0x40, 0x05, 0x7A, 0x65,
+    0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0xD9, 0x88, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06};
 
-unsigned char twelve_pts_prdf3[408] = {
-    0x9F, 0xA7, 0xFC, 0xFF, 0x7C, 0x1B, 0x03, 0x00, 0x7C, 0x79, 0xFC, 0xFF, 0x13, 0x00, 0x49, 0x00, 0x0D, 0x82, 0xA6,
-    0x1C, 0x6C, 0xA5, 0xFF, 0x99, 0xBD, 0x7D, 0x0E, 0x41, 0x82, 0x00, 0x8A, 0x00, 0x96, 0x00, 0x45, 0xB8, 0xFC, 0xFF,
-    0x6D, 0x1B, 0x03, 0x00, 0xAA, 0x78, 0xFC, 0xFF, 0x02, 0x00, 0x49, 0x00, 0xF1, 0x80, 0xA4, 0x1C, 0x6C, 0xA5, 0xFF,
-    0xA4, 0x80, 0x5B, 0x0E, 0x41, 0x92, 0x00, 0x98, 0x00, 0x99, 0x00, 0x11, 0xAA, 0xFC, 0xFF, 0x01, 0x1A, 0x03, 0x00,
-    0xCE, 0x78, 0xFC, 0xFF, 0x22, 0x00, 0x09, 0x00, 0xFF, 0x78, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0xB6, 0x55, 0x71, 0x0E,
-    0x41, 0x81, 0x00, 0x8C, 0x00, 0x98, 0x00, 0x74, 0xE3, 0xFC, 0xFF, 0x7E, 0xAA, 0x02, 0x00, 0xEC, 0x75, 0xFC, 0xFF,
-    0x2F, 0x00, 0x12, 0x02, 0xF9, 0x7A, 0xA4, 0x1C, 0x6C, 0xA5, 0xFF, 0x13, 0x7D, 0x5B, 0x0E, 0x41, 0x2B, 0x00, 0x43,
-    0x00, 0x5F, 0x00, 0x3B, 0xE3, 0xFC, 0xFF, 0x1A, 0xAA, 0x02, 0x00, 0x20, 0x76, 0xFC, 0xFF, 0x00, 0x00, 0x64, 0x02,
-    0xF9, 0x7D, 0xA4, 0x1C, 0x6C, 0xA5, 0xFF, 0x21, 0x7D, 0x5B, 0x0E, 0x41, 0x2B, 0x00, 0x42, 0x00, 0x61, 0x00, 0xAC,
-    0xE2, 0xFC, 0xFF, 0x4A, 0xAA, 0x02, 0x00, 0xEC, 0x75, 0xFC, 0xFF, 0x1D, 0x00, 0x1B, 0x02, 0xF9, 0x7C, 0xA4, 0x1C,
-    0x6C, 0xA5, 0xFF, 0x3A, 0x7D, 0x5B, 0x0E, 0x41, 0x33, 0x00, 0x49, 0x00, 0x61, 0x00, 0x7F, 0xDB, 0xFC, 0xFF, 0xA4,
-    0xAA, 0x02, 0x00, 0x13, 0x76, 0xFC, 0xFF, 0x00, 0x00, 0x64, 0x02, 0xF8, 0x7E, 0xA4, 0x1C, 0x6C, 0xA5, 0xFF, 0x82,
-    0x7E, 0x5B, 0x0E, 0x41, 0x38, 0x00, 0x54, 0x00, 0x65, 0x00, 0x33, 0xD6, 0xFC, 0xFF, 0x80, 0xAA, 0x02, 0x00, 0x3A,
-    0x76, 0xFC, 0xFF, 0x23, 0x00, 0x09, 0x02, 0xF7, 0x7A, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x60, 0x7F, 0x5B, 0x0E, 0x41,
-    0x3E, 0x00, 0x65, 0x00, 0x6D, 0x00, 0xF0, 0xD0, 0xFC, 0xFF, 0xFD, 0xAA, 0x02, 0x00, 0xAA, 0x75, 0xFC, 0xFF, 0x6F,
-    0x00, 0x09, 0x02, 0xF6, 0x7A, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0xFD, 0x7F, 0x5B, 0x0E, 0x41, 0x37, 0x00, 0x54, 0x00,
-    0x6A, 0x00, 0x95, 0xD0, 0xFC, 0xFF, 0xF6, 0xA9, 0x02, 0x00, 0xA7, 0x75, 0xFC, 0xFF, 0xB0, 0x00, 0x52, 0x02, 0xF7,
-    0x82, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x0B, 0x80, 0x5B, 0x0E, 0x41, 0x3D, 0x00, 0x5B, 0x00, 0x6E, 0x00, 0x61, 0xC6,
-    0xFC, 0xFF, 0xC4, 0xAA, 0x02, 0x00, 0x86, 0x75, 0xFC, 0xFF, 0x26, 0x00, 0x52, 0x02, 0xF7, 0x82, 0xA4, 0x1C, 0x6C,
-    0xA5, 0x7F, 0xF6, 0x80, 0x5B, 0x0E, 0x41, 0x77, 0x00, 0x85, 0x00, 0x78, 0x00, 0x2A, 0xC5, 0xFC, 0xFF, 0xBA, 0xAA,
-    0x02, 0x00, 0xB7, 0x75, 0xFC, 0xFF, 0x00, 0x00, 0x1B, 0x02, 0xF7, 0x7C, 0xA4, 0x1C, 0x6C, 0xA5, 0xFF, 0x0F, 0x81,
-    0x5B, 0x0E, 0x41, 0x54, 0x00, 0x71, 0x00, 0x6E, 0x00};
+unsigned char next_12_pts_compressed[254] = {
+    0x87, 0xB3, 0xFD, 0xFF, 0x3E, 0x9D, 0xFC, 0xFF, 0xE8, 0x80, 0xFC, 0xFF, 0x35, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65,
+    0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0xE4, 0x88, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x0C, 0x00,
+    0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x11,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+    0x0D, 0x00, 0x00, 0x00, 0x20, 0x87, 0x34, 0xE3, 0x9A, 0x46, 0x60, 0x27, 0x2E, 0x0D, 0xA5, 0x24, 0xFF, 0xD2, 0x1A,
+    0x68, 0x22, 0x6A, 0x68, 0x7F, 0x2D, 0x58, 0xCC, 0x61, 0x44, 0x05, 0x01, 0xD7, 0x57, 0xA8, 0x66, 0x81, 0x8F, 0x03,
+    0x1D, 0x38, 0xDD, 0x68, 0xF3, 0xE5, 0xCC, 0xDC, 0x7F, 0xD9, 0x00, 0x00, 0x00, 0x5A, 0x3F, 0x31, 0xDC, 0xC4, 0x0E,
+    0xD2, 0xDC, 0x95, 0x49, 0xB4, 0xD4, 0xAD, 0xF5, 0xD2, 0x34, 0xB1, 0xDD, 0x7D, 0x00, 0x00, 0x00, 0x41, 0x03, 0xCF,
+    0x7E, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x67, 0x99, 0x0D, 0x3D, 0x5F, 0x6A, 0xC2, 0x7C, 0x01, 0x04,
+    0x93, 0xAD, 0x54, 0x2B, 0x00, 0x00, 0x00, 0x7A, 0x7A, 0x3D, 0x3D, 0x3F, 0x3F, 0x3E, 0x3E, 0x3E, 0x3E, 0x3D, 0xC2,
+    0x00, 0x00, 0x00, 0x20, 0x0B, 0x19, 0xC0, 0x43, 0x7C, 0x06, 0x10, 0x01, 0xA2, 0x16, 0x40, 0x2F, 0x51, 0xAB, 0x15,
+    0x78, 0xB7, 0x9D, 0xDB, 0xA8, 0x9C, 0x75, 0x0F, 0xD5, 0x7E, 0xAA, 0xA6, 0x2D, 0x75, 0x20, 0x9C, 0xC5, 0x57, 0xC0,
+    0x72, 0x67, 0x88, 0x2B, 0x46, 0x96, 0x1A, 0x35, 0x3E, 0x1D, 0x00, 0x00, 0x00, 0x81, 0x01, 0x83, 0x87, 0x0E, 0x1C,
+    0x38, 0x70, 0xE1, 0xC0, 0x00, 0x00, 0x00};
 
-unsigned char twelve_pts_prdf3_compressed[239] = {
-    0x9F, 0xA7, 0xFC, 0xFF, 0x7C, 0x1B, 0x03, 0x00, 0x7C, 0x79, 0xFC, 0xFF, 0x13, 0x00, 0x49, 0x00, 0x0D, 0x82, 0xA6,
-    0x1C, 0x6C, 0xA5, 0xFF, 0x99, 0xBD, 0x7D, 0x0E, 0x41, 0x82, 0x00, 0x8A, 0x00, 0x96, 0x00, 0x5C, 0x74, 0xFE, 0x7B,
-    0x2A, 0x74, 0xCB, 0x1E, 0xFF, 0x79, 0xAF, 0x36, 0xE8, 0xD8, 0x24, 0x66, 0xF3, 0x97, 0x3C, 0xB9, 0x69, 0xE7, 0x2C,
-    0xF0, 0x61, 0x38, 0x28, 0x28, 0x3C, 0x2F, 0x06, 0x54, 0x19, 0xBB, 0xD0, 0x29, 0xEC, 0x12, 0xFA, 0x95, 0xB4, 0x67,
-    0x23, 0x8E, 0x10, 0xAB, 0xE5, 0x69, 0x7C, 0x10, 0x87, 0xEA, 0x33, 0x11, 0x27, 0xC8, 0xC5, 0xC0, 0xA8, 0xC5, 0xD8,
-    0x77, 0x18, 0x11, 0x39, 0x96, 0x15, 0x94, 0xD4, 0xE3, 0x5E, 0xDF, 0xA9, 0xEC, 0xC7, 0xAD, 0x31, 0x7C, 0x6C, 0x1C,
-    0xF1, 0x3E, 0xE8, 0xB8, 0xF9, 0x7F, 0x20, 0x7A, 0x28, 0x85, 0xA1, 0x14, 0x81, 0xFD, 0xFF, 0xBE, 0x2C, 0x86, 0x35,
-    0xAE, 0x66, 0x30, 0xD1, 0x3B, 0x02, 0xC8, 0x90, 0xBC, 0x5A, 0xE9, 0x6F, 0x1B, 0xD6, 0x16, 0x97, 0x3C, 0x49, 0x82,
-    0x1A, 0x01, 0x7D, 0x26, 0x7C, 0x7C, 0x09, 0x1F, 0x03, 0x6D, 0xAE, 0xDF, 0x9C, 0x1A, 0x7E, 0xCA, 0x81, 0x90, 0x55,
-    0xE8, 0x70, 0x81, 0x37, 0xD0, 0x75, 0x59, 0xC5, 0xBE, 0x69, 0xE7, 0x76, 0x1C, 0x21, 0xBD, 0xEF, 0x97, 0xA3, 0x31,
-    0x19, 0x97, 0x44, 0xB9, 0x6B, 0x2D, 0xB9, 0x1C, 0xF2, 0x00, 0xD4, 0xE6, 0x2E, 0xBD, 0xEE, 0x33, 0x22, 0x04, 0xE0,
-    0x5A, 0x34, 0x16, 0x10, 0x93, 0xD8, 0x78, 0xB6, 0xC3, 0x49, 0x33, 0xCD, 0x55, 0xB7, 0xF3, 0x0D, 0x95, 0x5E, 0xE7,
-    0x56, 0x13, 0x79, 0x7F, 0xCD, 0x72, 0xB9, 0x11, 0xE3, 0x00, 0x00};
+unsigned char next_12_pts[432] = {
+    0x87, 0xB3, 0xFD, 0xFF, 0x3E, 0x9D, 0xFC, 0xFF, 0xE8, 0x80, 0xFC, 0xFF, 0x35, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65,
+    0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0xE4, 0x88, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x42, 0xB4,
+    0xFD, 0xFF, 0x1E, 0x9B, 0xFC, 0xFF, 0xFC, 0x85, 0xFC, 0xFF, 0x6E, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E,
+    0x1C, 0x6C, 0xE5, 0xEE, 0x88, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x6F, 0xB4, 0xFD, 0xFF,
+    0x46, 0x9A, 0xFC, 0xFF, 0xFC, 0x85, 0xFC, 0xFF, 0x5E, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C,
+    0x25, 0xF4, 0x88, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x96, 0xB4, 0xFD, 0xFF, 0x6C, 0x99,
+    0xFC, 0xFF, 0x75, 0x85, 0xFC, 0xFF, 0x0D, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0xF9,
+    0x88, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xA3, 0xB4, 0xFD, 0xFF, 0xFE, 0x97, 0xFC, 0xFF,
+    0xB4, 0x80, 0xFC, 0xFF, 0x74, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0x04, 0x89, 0x43,
+    0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xD5, 0xB3, 0xFD, 0xFF, 0x5C, 0x98, 0xFC, 0xFF, 0xE2, 0x82,
+    0xFC, 0xFF, 0x79, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0x6A, 0xA0, 0x43, 0xF4, 0x0D,
+    0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xBA, 0xB2, 0xFD, 0xFF, 0xE0, 0x9C, 0xFC, 0xFF, 0xB4, 0x80, 0xFC, 0xFF,
+    0x1A, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0x85, 0xA0, 0x43, 0xF4, 0x0D, 0x41, 0x00,
+    0x42, 0x00, 0xFF, 0x00, 0x06, 0x4F, 0xB2, 0xFD, 0xFF, 0xAF, 0x9E, 0xFC, 0xFF, 0xDB, 0x7F, 0xFC, 0xFF, 0x50, 0x00,
+    0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0x8F, 0xA0, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00,
+    0xFF, 0x00, 0x06, 0xF9, 0xB1, 0xFD, 0xFF, 0x07, 0xA1, 0xFC, 0xFF, 0x30, 0x83, 0xFC, 0xFF, 0x27, 0x00, 0x11, 0x00,
+    0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0x9F, 0xA0, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00,
+    0x06, 0xB8, 0xB1, 0xFD, 0xFF, 0xF6, 0xA1, 0xFC, 0xFF, 0x55, 0x82, 0xFC, 0xFF, 0x86, 0x00, 0x11, 0x00, 0x05, 0x7C,
+    0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0xA4, 0xA0, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x8D,
+    0xB1, 0xFD, 0xFF, 0xCB, 0xA2, 0xFC, 0xFF, 0x8C, 0x82, 0xFC, 0xFF, 0x9C, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD,
+    0x9E, 0x1C, 0x6C, 0x65, 0xAA, 0xA0, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x4E, 0xB1, 0xFD,
+    0xFF, 0xBA, 0xA3, 0xFC, 0xFF, 0xA3, 0x81, 0xFC, 0xFF, 0x91, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C,
+    0x6C, 0xA5, 0xAF, 0xA0, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06};
 
-unsigned char sixty_pts_prdf3[2040] = {
-    0x71, 0xC4, 0xFC, 0xFF, 0xD9, 0xAA, 0x02, 0x00, 0xAA, 0x75, 0xFC, 0xFF, 0x2D, 0x00, 0x52, 0x02, 0xF7, 0x82, 0xA4,
-    0x1C, 0x6C, 0xA5, 0xFF, 0x1D, 0x81, 0x5B, 0x0E, 0x41, 0x3B, 0x00, 0x5B, 0x00, 0x66, 0x00, 0x93, 0xBF, 0xFC, 0xFF,
-    0x34, 0xAA, 0x02, 0x00, 0x96, 0x75, 0xFC, 0xFF, 0x0F, 0x00, 0x1B, 0x02, 0xF7, 0x7C, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F,
-    0x85, 0x81, 0x5B, 0x0E, 0x41, 0x38, 0x00, 0x5F, 0x00, 0x67, 0x00, 0xB0, 0xBE, 0xFC, 0xFF, 0x5F, 0xAB, 0x02, 0x00,
-    0x9D, 0x75, 0xFC, 0xFF, 0x01, 0x00, 0x5B, 0x02, 0xF7, 0x80, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x93, 0x81, 0x5B, 0x0E,
-    0x41, 0x34, 0x00, 0x53, 0x00, 0x64, 0x00, 0xD8, 0xB9, 0xFC, 0xFF, 0xF6, 0xA9, 0x02, 0x00, 0x3A, 0x76, 0xFC, 0xFF,
-    0x0C, 0x00, 0x12, 0x02, 0xF8, 0x7E, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0xFB, 0x81, 0x5B, 0x0E, 0x41, 0x38, 0x00, 0x62,
-    0x00, 0x68, 0x00, 0x0F, 0xB9, 0xFC, 0xFF, 0xB0, 0xAA, 0x02, 0x00, 0xE8, 0x75, 0xFC, 0xFF, 0x08, 0x00, 0x5B, 0x02,
-    0xF8, 0x81, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x09, 0x82, 0x5B, 0x0E, 0x41, 0x37, 0x00, 0x5F, 0x00, 0x67, 0x00, 0x37,
-    0xB5, 0xFC, 0xFF, 0x46, 0xAB, 0x02, 0x00, 0xCB, 0x75, 0xFC, 0xFF, 0x2F, 0x00, 0x5B, 0x02, 0xF8, 0x82, 0xA4, 0x1C,
-    0x6C, 0xA5, 0xFF, 0x57, 0x82, 0x5B, 0x0E, 0x41, 0x40, 0x00, 0x67, 0x00, 0x69, 0x00, 0xF2, 0xB3, 0xFC, 0xFF, 0x99,
-    0xAB, 0x02, 0x00, 0xD1, 0x75, 0xFC, 0xFF, 0x25, 0x00, 0x12, 0x02, 0xF8, 0x7C, 0xA4, 0x1C, 0x6C, 0xA5, 0xFF, 0x70,
-    0x82, 0x5B, 0x0E, 0x41, 0x3A, 0x00, 0x57, 0x00, 0x66, 0x00, 0x5F, 0xB2, 0xFC, 0xFF, 0x26, 0xAA, 0x02, 0x00, 0xA7,
-    0x75, 0xFC, 0xFF, 0x03, 0x00, 0x1B, 0x02, 0xF8, 0x7C, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x98, 0x82, 0x5B, 0x0E, 0x41,
-    0x39, 0x00, 0x61, 0x00, 0x68, 0x00, 0x3B, 0xB2, 0xFC, 0xFF, 0xFD, 0xAA, 0x02, 0x00, 0xD1, 0x75, 0xFC, 0xFF, 0x2B,
-    0x00, 0x12, 0x02, 0xF8, 0x7C, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x98, 0x82, 0x5B, 0x0E, 0x41, 0x38, 0x00, 0x5B, 0x00,
-    0x67, 0x00, 0xEF, 0xAF, 0xFC, 0xFF, 0xA3, 0xAA, 0x02, 0x00, 0xBB, 0x75, 0xFC, 0xFF, 0x25, 0x00, 0x52, 0x02, 0xF8,
-    0x80, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0xCD, 0x82, 0x5B, 0x0E, 0x41, 0x3A, 0x00, 0x6E, 0x00, 0x66, 0x00, 0xA8, 0xAC,
-    0xFC, 0xFF, 0x19, 0xAA, 0x02, 0x00, 0x9F, 0x75, 0xFC, 0xFF, 0x3F, 0x00, 0x5B, 0x02, 0xF9, 0x7E, 0xA4, 0x1C, 0x6C,
-    0xA5, 0xFF, 0x1B, 0x83, 0x5B, 0x0E, 0x41, 0x3A, 0x00, 0x62, 0x00, 0x62, 0x00, 0x73, 0xAB, 0xFC, 0xFF, 0xE6, 0xAA,
-    0x02, 0x00, 0x79, 0x75, 0xFC, 0xFF, 0x6C, 0x00, 0x09, 0x02, 0xF9, 0x7C, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x35, 0x83,
-    0x5B, 0x0E, 0x41, 0x39, 0x00, 0x5D, 0x00, 0x63, 0x00, 0x50, 0xAB, 0xFC, 0xFF, 0xC0, 0xAB, 0x02, 0x00, 0x7C, 0x75,
-    0xFC, 0xFF, 0x43, 0x00, 0x12, 0x02, 0xF9, 0x7C, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x35, 0x83, 0x5B, 0x0E, 0x41, 0x48,
-    0x00, 0x76, 0x00, 0x6A, 0x00, 0x06, 0xA9, 0xFC, 0xFF, 0x33, 0xAC, 0x02, 0x00, 0x79, 0x75, 0xFC, 0xFF, 0x1D, 0x00,
-    0x52, 0x02, 0xF9, 0x80, 0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x6A, 0x83, 0x5B, 0x0E, 0x41, 0x4A, 0x00, 0x7C, 0x00, 0x6D,
-    0x00, 0x43, 0xA8, 0xFC, 0xFF, 0x66, 0xAA, 0x02, 0x00, 0x79, 0x75, 0xFC, 0xFF, 0x0F, 0x00, 0x12, 0x02, 0xF9, 0x7E,
-    0xA4, 0x1C, 0x6C, 0xA5, 0x7F, 0x83, 0x83, 0x5B, 0x0E, 0x41, 0x32, 0x00, 0x40, 0x00, 0x5E, 0x00, 0xC1, 0xA7, 0xFC,
-    0xFF, 0x2B, 0xAA, 0x02, 0x00, 0x79, 0x75, 0xFC, 0xFF, 0x20, 0x00, 0x5B, 0x02, 0xF9, 0x80, 0xA4, 0x1C, 0x6C, 0xA5,
-    0x7F, 0x91, 0x83, 0x5B, 0x0E, 0x41, 0x27, 0x00, 0x37, 0x00, 0x59, 0x00, 0x84, 0xA6, 0xFC, 0xFF, 0x78, 0xAB, 0x02,
-    0x00, 0x96, 0x75, 0xFC, 0xFF, 0x48, 0x00, 0x09, 0x02, 0xF9, 0x7C, 0xA4, 0x1C, 0x6C, 0xA5, 0xFF, 0xAA, 0x83, 0x5B,
-    0x0E, 0x41, 0x27, 0x00, 0x3D, 0x00, 0x57, 0x00, 0x02, 0xA6, 0xFC, 0xFF, 0x48, 0xAB, 0x02, 0x00, 0x93, 0x75, 0xFC,
-    0xFF, 0x52, 0x00, 0x49, 0x02, 0xF9, 0x7E, 0xA4, 0x1C, 0x6C, 0xA5, 0xFF, 0xB8, 0x83, 0x5B, 0x0E, 0x41, 0x28, 0x00,
-    0x3B, 0x00, 0x56, 0x00, 0x89, 0xA4, 0xFC, 0xFF, 0xC7, 0xAA, 0x02, 0x00, 0x9F, 0x75, 0xFC, 0xFF, 0x0D, 0x00, 0x5B,
-    0x02, 0xF9, 0x7E, 0xA4, 0x1C, 0x6C, 0xA5, 0xFF, 0xDF, 0x83, 0x5B, 0x0E, 0x41, 0x22, 0x00, 0x31, 0x00, 0x56, 0x00,
-    0x64, 0xA3, 0xFC, 0xFF, 0xC3, 0xAB, 0x02, 0x00, 0x7C, 0x75, 0xFC, 0xFF, 0x32, 0x00, 0x09, 0x02, 0xFB, 0x79, 0xA5,
-    0x1C, 0x6C, 0xA5, 0x7F, 0x05, 0x58, 0x71, 0x0E, 0x41, 0x23, 0x00, 0x37, 0x00, 0x58, 0x00, 0x74, 0xA4, 0xFC, 0xFF,
-    0x34, 0xAA, 0x02, 0x00, 0xAD, 0x75, 0xFC, 0xFF, 0x23, 0x00, 0x09, 0x02, 0xFB, 0x78, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F,
-    0x2C, 0x58, 0x71, 0x0E, 0x41, 0x24, 0x00, 0x34, 0x00, 0x51, 0x00, 0x37, 0xA5, 0xFC, 0xFF, 0x0A, 0xAB, 0x02, 0x00,
-    0x89, 0x75, 0xFC, 0xFF, 0x0B, 0x00, 0x5B, 0x02, 0xFB, 0x7D, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0x3A, 0x58, 0x71, 0x0E,
-    0x41, 0x20, 0x00, 0x2F, 0x00, 0x55, 0x00, 0x6E, 0xA5, 0xFC, 0xFF, 0xDA, 0xAB, 0x02, 0x00, 0x72, 0x75, 0xFC, 0xFF,
-    0x27, 0x00, 0x52, 0x02, 0xFB, 0x7D, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0x3A, 0x58, 0x71, 0x0E, 0x41, 0x29, 0x00, 0x3C,
-    0x00, 0x5A, 0x00, 0x02, 0xA6, 0xFC, 0xFF, 0x92, 0xAA, 0x02, 0x00, 0xC4, 0x75, 0xFC, 0xFF, 0x00, 0x00, 0x1B, 0x02,
-    0xFB, 0x7A, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0x53, 0x58, 0x71, 0x0E, 0x41, 0x23, 0x00, 0x34, 0x00, 0x56, 0x00, 0xB9,
-    0xA7, 0xFC, 0xFF, 0x8C, 0xAB, 0x02, 0x00, 0x65, 0x75, 0xFC, 0xFF, 0x04, 0x00, 0x1B, 0x02, 0xFB, 0x78, 0xA5, 0x1C,
-    0x6C, 0xA5, 0xFF, 0x7A, 0x58, 0x71, 0x0E, 0x41, 0x33, 0x00, 0x53, 0x00, 0x60, 0x00, 0xFE, 0xA7, 0xFC, 0xFF, 0x96,
-    0xAA, 0x02, 0x00, 0xB1, 0x75, 0xFC, 0xFF, 0x00, 0x00, 0x64, 0x02, 0xFB, 0x7E, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0x89,
-    0x58, 0x71, 0x0E, 0x41, 0x2B, 0x00, 0x38, 0x00, 0x5C, 0x00, 0x85, 0xA9, 0xFC, 0xFF, 0xBE, 0xAA, 0x02, 0x00, 0x6C,
-    0x75, 0xFC, 0xFF, 0x0D, 0x00, 0x5B, 0x02, 0xFB, 0x7E, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0xB0, 0x58, 0x71, 0x0E, 0x41,
-    0x34, 0x00, 0x57, 0x00, 0x61, 0x00, 0x8E, 0xAA, 0xFC, 0xFF, 0x1C, 0xAB, 0x02, 0x00, 0x8D, 0x75, 0xFC, 0xFF, 0x02,
-    0x00, 0x12, 0x02, 0xFB, 0x7A, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0xC9, 0x58, 0x71, 0x0E, 0x41, 0x2C, 0x00, 0x50, 0x00,
-    0x61, 0x00, 0x58, 0xAA, 0xFC, 0xFF, 0x4A, 0xAA, 0x02, 0x00, 0x7C, 0x75, 0xFC, 0xFF, 0x14, 0x00, 0x12, 0x02, 0xFB,
-    0x7A, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0xC9, 0x58, 0x71, 0x0E, 0x41, 0x27, 0x00, 0x44, 0x00, 0x5E, 0x00, 0x67, 0xAC,
-    0xFC, 0xFF, 0x89, 0xAA, 0x02, 0x00, 0xAA, 0x75, 0xFC, 0xFF, 0x00, 0x00, 0x5B, 0x02, 0xFB, 0x7E, 0xA5, 0x1C, 0x6C,
-    0xA5, 0xFF, 0xFE, 0x58, 0x71, 0x0E, 0x41, 0x42, 0x00, 0x70, 0x00, 0x67, 0x00, 0xAF, 0xB0, 0xFC, 0xFF, 0xD9, 0xAA,
-    0x02, 0x00, 0xC4, 0x75, 0xFC, 0xFF, 0x2E, 0x00, 0x52, 0x02, 0xFB, 0x7E, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0x74, 0x59,
-    0x71, 0x0E, 0x41, 0x3B, 0x00, 0x69, 0x00, 0x67, 0x00, 0x4C, 0xB3, 0xFC, 0xFF, 0xB3, 0xAB, 0x02, 0x00, 0xD1, 0x75,
-    0xFC, 0xFF, 0x39, 0x00, 0x12, 0x02, 0xFB, 0x7C, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0xB4, 0x59, 0x71, 0x0E, 0x41, 0x3A,
-    0x00, 0x5B, 0x00, 0x67, 0x00, 0x5F, 0xB4, 0xFC, 0xFF, 0x7E, 0xAA, 0x02, 0x00, 0xB4, 0x75, 0xFC, 0xFF, 0x0E, 0x00,
-    0x1B, 0x02, 0xFB, 0x7C, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0xDC, 0x59, 0x71, 0x0E, 0x41, 0x36, 0x00, 0x5F, 0x00, 0x67,
-    0x00, 0xDF, 0xB4, 0xFC, 0xFF, 0x73, 0xAA, 0x02, 0x00, 0xBE, 0x75, 0xFC, 0xFF, 0x3D, 0x00, 0x52, 0x02, 0xFB, 0x7E,
-    0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0xEA, 0x59, 0x71, 0x0E, 0x41, 0x39, 0x00, 0x61, 0x00, 0x68, 0x00, 0x14, 0xBA, 0xFC,
-    0xFF, 0x59, 0xAA, 0x02, 0x00, 0x20, 0x76, 0xFC, 0xFF, 0x05, 0x00, 0x12, 0x02, 0xFA, 0x7C, 0xA5, 0x1C, 0x6C, 0xA5,
-    0x7F, 0x79, 0x5A, 0x71, 0x0E, 0x41, 0x3A, 0x00, 0x61, 0x00, 0x67, 0x00, 0x8A, 0xBD, 0xFC, 0xFF, 0x7B, 0xAA, 0x02,
-    0x00, 0xA7, 0x75, 0xFC, 0xFF, 0x2B, 0x00, 0x52, 0x02, 0xF9, 0x7E, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0xD5, 0x5A, 0x71,
-    0x0E, 0x41, 0x3E, 0x00, 0x62, 0x00, 0x65, 0x00, 0x95, 0xBE, 0xFC, 0xFF, 0x14, 0xAB, 0x02, 0x00, 0xA7, 0x75, 0xFC,
-    0xFF, 0x07, 0x00, 0x1B, 0x02, 0xF9, 0x7C, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0xEE, 0x5A, 0x71, 0x0E, 0x41, 0x32, 0x00,
-    0x56, 0x00, 0x63, 0x00, 0xCF, 0xBE, 0xFC, 0xFF, 0x16, 0xAA, 0x02, 0x00, 0x96, 0x75, 0xFC, 0xFF, 0x22, 0x00, 0x5B,
-    0x02, 0xF9, 0x7D, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0xFC, 0x5A, 0x71, 0x0E, 0x41, 0x2F, 0x00, 0x4E, 0x00, 0x65, 0x00,
-    0x05, 0xBF, 0xFC, 0xFF, 0xE6, 0xAA, 0x02, 0x00, 0x96, 0x75, 0xFC, 0xFF, 0x31, 0x00, 0x52, 0x02, 0xF9, 0x7D, 0xA5,
-    0x1C, 0x6C, 0xA5, 0xFF, 0xFC, 0x5A, 0x71, 0x0E, 0x41, 0x34, 0x00, 0x55, 0x00, 0x63, 0x00, 0xF4, 0xBF, 0xFC, 0xFF,
-    0x1F, 0xAB, 0x02, 0x00, 0xAD, 0x75, 0xFC, 0xFF, 0x1F, 0x00, 0x12, 0x02, 0xF9, 0x7C, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF,
-    0x15, 0x5B, 0x71, 0x0E, 0x41, 0x3D, 0x00, 0x5E, 0x00, 0x66, 0x00, 0x2E, 0xC4, 0xFC, 0xFF, 0xE1, 0xAA, 0x02, 0x00,
-    0xA7, 0x75, 0xFC, 0xFF, 0x1B, 0x00, 0x12, 0x02, 0xF8, 0x7C, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0x8B, 0x5B, 0x71, 0x0E,
-    0x41, 0x3B, 0x00, 0x5B, 0x00, 0x66, 0x00, 0x90, 0xC4, 0xFC, 0xFF, 0x54, 0xAA, 0x02, 0x00, 0x86, 0x75, 0xFC, 0xFF,
-    0x3F, 0x00, 0x52, 0x02, 0xF8, 0x7C, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0x99, 0x5B, 0x71, 0x0E, 0x41, 0x39, 0x00, 0x57,
-    0x00, 0x65, 0x00, 0x68, 0xC5, 0xFC, 0xFF, 0x4C, 0xAA, 0x02, 0x00, 0x8D, 0x75, 0xFC, 0xFF, 0x6D, 0x00, 0x09, 0x02,
-    0xF8, 0x7C, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0xB2, 0x5B, 0x71, 0x0E, 0x41, 0x54, 0x00, 0x6B, 0x00, 0x6D, 0x00, 0xEA,
-    0xC5, 0xFC, 0xFF, 0x4A, 0xAA, 0x02, 0x00, 0x8D, 0x75, 0xFC, 0xFF, 0x49, 0x00, 0x49, 0x02, 0xF8, 0x7C, 0xA5, 0x1C,
-    0x6C, 0xA5, 0x7F, 0xC1, 0x5B, 0x71, 0x0E, 0x41, 0x66, 0x00, 0x7A, 0x00, 0x71, 0x00, 0x23, 0xC6, 0xFC, 0xFF, 0x21,
-    0xAB, 0x02, 0x00, 0x86, 0x75, 0xFC, 0xFF, 0x19, 0x00, 0x5B, 0x02, 0xF8, 0x7C, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0xC1,
-    0x5B, 0x71, 0x0E, 0x41, 0x77, 0x00, 0x85, 0x00, 0x78, 0x00, 0x19, 0xC8, 0xFC, 0xFF, 0xB1, 0xAA, 0x02, 0x00, 0x79,
-    0x75, 0xFC, 0xFF, 0x19, 0x00, 0x12, 0x02, 0xF8, 0x7B, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0x01, 0x5C, 0x71, 0x0E, 0x41,
-    0x7D, 0x00, 0x82, 0x00, 0x7E, 0x00, 0x49, 0xC9, 0xFC, 0xFF, 0xA3, 0xAA, 0x02, 0x00, 0x79, 0x75, 0xFC, 0xFF, 0x1F,
-    0x00, 0x09, 0x02, 0xF7, 0x78, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0x28, 0x5C, 0x71, 0x0E, 0x41, 0x91, 0x00, 0x89, 0x00,
-    0x7D, 0x00, 0xEB, 0xCA, 0xFC, 0xFF, 0x26, 0xAA, 0x02, 0x00, 0x72, 0x75, 0xFC, 0xFF, 0x5A, 0x00, 0x52, 0x02, 0xF7,
-    0x7C, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0x5D, 0x5C, 0x71, 0x0E, 0x41, 0x56, 0x00, 0x74, 0x00, 0x77, 0x00, 0xDA, 0xCB,
-    0xFC, 0xFF, 0x4C, 0xAA, 0x02, 0x00, 0x62, 0x75, 0xFC, 0xFF, 0x12, 0x00, 0x12, 0x02, 0xF7, 0x78, 0xA5, 0x1C, 0x6C,
-    0xA5, 0x7F, 0x77, 0x5C, 0x71, 0x0E, 0x41, 0x41, 0x00, 0x67, 0x00, 0x6E, 0x00, 0xE5, 0xCD, 0xFC, 0xFF, 0x26, 0xAA,
-    0x02, 0x00, 0x89, 0x75, 0xFC, 0xFF, 0x85, 0x00, 0x52, 0x02, 0xF6, 0x7C, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0xAC, 0x5C,
-    0x71, 0x0E, 0x41, 0x3C, 0x00, 0x5E, 0x00, 0x6D, 0x00, 0xC9, 0xCE, 0xFC, 0xFF, 0x1E, 0xAA, 0x02, 0x00, 0x90, 0x75,
-    0xFC, 0xFF, 0x51, 0x00, 0x09, 0x02, 0xF6, 0x78, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0xC5, 0x5C, 0x71, 0x0E, 0x41, 0x3C,
-    0x00, 0x5C, 0x00, 0x6B, 0x00, 0x4D, 0xD0, 0xFC, 0xFF, 0x4F, 0xAA, 0x02, 0x00, 0x90, 0x75, 0xFC, 0xFF, 0x02, 0x00,
-    0x12, 0x02, 0xF6, 0x78, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0xEC, 0x5C, 0x71, 0x0E, 0x41, 0x39, 0x00, 0x56, 0x00, 0x69,
-    0x00, 0xCC, 0xD0, 0xFC, 0xFF, 0x3D, 0xAA, 0x02, 0x00, 0xA4, 0x75, 0xFC, 0xFF, 0x56, 0x00, 0x52, 0x02, 0xF6, 0x7C,
-    0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0xFA, 0x5C, 0x71, 0x0E, 0x41, 0x35, 0x00, 0x55, 0x00, 0x69, 0x00, 0xC8, 0xD1, 0xFC,
-    0xFF, 0x6E, 0xAA, 0x02, 0x00, 0xB1, 0x75, 0xFC, 0xFF, 0x82, 0x00, 0x09, 0x02, 0xF6, 0x78, 0xA5, 0x1C, 0x6C, 0xA5,
-    0xFF, 0x13, 0x5D, 0x71, 0x0E, 0x41, 0x3C, 0x00, 0x56, 0x00, 0x68, 0x00, 0x32, 0xD2, 0xFC, 0xFF, 0x0E, 0xAA, 0x02,
-    0x00, 0xAD, 0x75, 0xFC, 0xFF, 0x3D, 0x00, 0x52, 0x02, 0xF6, 0x7C, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0x22, 0x5D, 0x71,
-    0x0E, 0x41, 0x3A, 0x00, 0x58, 0x00, 0x6A, 0x00, 0x28, 0xD3, 0xFC, 0xFF, 0x4F, 0xAA, 0x02, 0x00, 0xBB, 0x75, 0xFC,
-    0xFF, 0x18, 0x00, 0x1B, 0x02, 0xF5, 0x78, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0x3B, 0x5D, 0x71, 0x0E, 0x41, 0x3F, 0x00,
-    0x5B, 0x00, 0x6F, 0x00, 0xBE, 0xD4, 0xFC, 0xFF, 0x96, 0xAA, 0x02, 0x00, 0xE8, 0x75, 0xFC, 0xFF, 0x13, 0x00, 0x1B,
-    0x02, 0xF5, 0x78, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F, 0x62, 0x5D, 0x71, 0x0E, 0x41, 0x41, 0x00, 0x64, 0x00, 0x6F, 0x00,
-    0x6D, 0xD6, 0xFC, 0xFF, 0x39, 0xAA, 0x02, 0x00, 0x1A, 0x76, 0xFC, 0xFF, 0x03, 0x00, 0x12, 0x02, 0xF5, 0x78, 0xA5,
-    0x1C, 0x6C, 0xA5, 0xFF, 0x89, 0x5D, 0x71, 0x0E, 0x41, 0x3E, 0x00, 0x65, 0x00, 0x6D, 0x00, 0x7C, 0xDB, 0xFC, 0xFF,
-    0x71, 0xAA, 0x02, 0x00, 0xFF, 0x75, 0xFC, 0xFF, 0x02, 0x00, 0x1B, 0x02, 0xF5, 0x7C, 0xA5, 0x1C, 0x6C, 0xA5, 0x7F,
-    0xFF, 0x5D, 0x71, 0x0E, 0x41, 0x38, 0x00, 0x54, 0x00, 0x65, 0x00, 0xF0, 0xDE, 0xFC, 0xFF, 0x54, 0xAA, 0x02, 0x00,
-    0xD8, 0x75, 0xFC, 0xFF, 0x0B, 0x00, 0x1B, 0x02, 0xF5, 0x7B, 0xA5, 0x1C, 0x6C, 0xA5, 0xFF, 0x4D, 0x5E, 0x71, 0x0E,
-    0x41, 0x40, 0x00, 0x61, 0x00, 0x6B, 0x00};
+unsigned char last_60_pts_compressed[825] = {
+    0xE9, 0xB0, 0xFD, 0xFF, 0x52, 0xA3, 0xFC, 0xFF, 0x54, 0x83, 0xFC, 0xFF, 0x55, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65,
+    0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0xF9, 0xAF, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x3C, 0x00,
+    0x00, 0x00, 0xEA, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x42,
+    0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xA8, 0x00, 0x00, 0x00,
+    0x37, 0x00, 0x00, 0x00, 0x20, 0x66, 0x30, 0x15, 0x36, 0xD9, 0x34, 0xA1, 0xE7, 0xA8, 0x11, 0xA1, 0x96, 0x37, 0x23,
+    0x69, 0x0E, 0x4D, 0x0F, 0xB5, 0x6A, 0xD8, 0x4F, 0x74, 0xFA, 0x6F, 0x2B, 0x80, 0xBF, 0x4E, 0x79, 0xFE, 0x98, 0x3B,
+    0xB3, 0x77, 0x3D, 0xB5, 0xB9, 0x63, 0x52, 0xC7, 0x90, 0x11, 0xC7, 0x19, 0x23, 0x5E, 0xED, 0x76, 0x41, 0xC3, 0x42,
+    0x67, 0xEA, 0x0D, 0x9B, 0xFE, 0xD6, 0x1D, 0x15, 0x2A, 0x7D, 0x14, 0xE0, 0x84, 0xF6, 0xA5, 0x56, 0xD7, 0xB8, 0x18,
+    0xB7, 0x39, 0x1C, 0x28, 0xCC, 0xAC, 0x7F, 0xAB, 0x8B, 0x12, 0x0A, 0xF6, 0xFB, 0xBA, 0xC4, 0xED, 0xB0, 0xA0, 0x0A,
+    0xE8, 0x0A, 0x50, 0xE7, 0x3D, 0xBE, 0x92, 0xC4, 0x1D, 0xC2, 0x68, 0x8C, 0x67, 0x0D, 0x79, 0x17, 0x19, 0xFF, 0x82,
+    0xED, 0xBB, 0x46, 0x70, 0x0F, 0x97, 0xFC, 0x1D, 0x2C, 0x5A, 0x88, 0xAF, 0xE6, 0xDF, 0xF4, 0x0A, 0x4E, 0xBF, 0x38,
+    0x5B, 0x33, 0xC6, 0xB6, 0x32, 0xE7, 0x24, 0xF8, 0x65, 0xF4, 0x6C, 0x2E, 0x3B, 0x06, 0xC1, 0xF8, 0x82, 0x92, 0x17,
+    0x18, 0xDA, 0xF5, 0x52, 0x0E, 0x17, 0x66, 0xEF, 0xBC, 0xCD, 0x3F, 0x1B, 0xFB, 0x65, 0xCC, 0x21, 0xC7, 0xF7, 0xD8,
+    0x76, 0xD3, 0xBE, 0x08, 0x11, 0xAE, 0xB9, 0x9E, 0xA5, 0x73, 0x3D, 0x8C, 0x74, 0x31, 0x7C, 0xA1, 0x3D, 0xFF, 0x72,
+    0x26, 0x49, 0x28, 0x06, 0xF0, 0xE3, 0x19, 0xB4, 0xB2, 0x9C, 0x16, 0x71, 0x45, 0x0B, 0x20, 0xB4, 0x36, 0x0D, 0xCC,
+    0xC0, 0x22, 0xC0, 0xED, 0xF7, 0x24, 0x92, 0x53, 0xE5, 0xAA, 0xF1, 0x5E, 0x0A, 0x54, 0xB7, 0x90, 0xB3, 0x20, 0xB5,
+    0xCD, 0xEB, 0x44, 0xAC, 0x18, 0xD0, 0x76, 0x00, 0x00, 0x00, 0x41, 0x00, 0x3E, 0x16, 0x94, 0x3E, 0x68, 0xB7, 0x40,
+    0x94, 0x4F, 0x8A, 0xF3, 0x9F, 0x92, 0x87, 0x97, 0x31, 0x06, 0xA0, 0x4F, 0x3C, 0xDE, 0x9F, 0xF8, 0xBB, 0x06, 0xCF,
+    0xD2, 0x40, 0xD0, 0xCE, 0x53, 0x30, 0x5C, 0x74, 0x1B, 0xF5, 0x3F, 0xFC, 0x5D, 0xDD, 0xA2, 0x71, 0x71, 0x77, 0xFD,
+    0xCD, 0x74, 0x21, 0x62, 0x86, 0x3E, 0x77, 0xD0, 0x39, 0xD3, 0xF7, 0x29, 0x14, 0xA4, 0xEF, 0xDD, 0x7D, 0x6A, 0x9D,
+    0x96, 0xE9, 0x76, 0xEE, 0x79, 0x3C, 0x45, 0xE2, 0x7D, 0xD5, 0xBF, 0x46, 0xEA, 0xE5, 0x99, 0x28, 0x5F, 0x8E, 0xE9,
+    0xCD, 0xFC, 0x78, 0x04, 0xC7, 0xDF, 0x2B, 0x94, 0x72, 0x00, 0xC5, 0x0C, 0xE1, 0xFB, 0x00, 0x00, 0x00, 0x41, 0x03,
+    0xCF, 0xBE, 0xFB, 0xEF, 0xBE, 0xF7, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3E, 0xFB, 0xEF, 0xBD, 0xF8, 0x00,
+    0x00, 0x00, 0x00, 0x40, 0xFB, 0xEF, 0xBE, 0xF7, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3E, 0xFB, 0xDF, 0x80,
+    0x04, 0x0F, 0x7E, 0xFF, 0xE1, 0x00, 0x00, 0x00, 0x53, 0xEF, 0x21, 0x54, 0x34, 0xE7, 0x1F, 0x91, 0x74, 0xB3, 0x08,
+    0xED, 0xA0, 0xF5, 0xED, 0xBA, 0x39, 0xE8, 0x48, 0x0A, 0x49, 0x1B, 0xA5, 0x65, 0x40, 0xF1, 0x9B, 0xFC, 0x8C, 0xD6,
+    0x75, 0xFD, 0x0C, 0x5F, 0x58, 0xA4, 0x73, 0x47, 0xD3, 0x67, 0x80, 0xDA, 0xDA, 0x61, 0x60, 0x52, 0x87, 0x49, 0xE5,
+    0x59, 0x4C, 0x2E, 0x99, 0x42, 0x25, 0x78, 0xFE, 0x58, 0x87, 0xB5, 0x63, 0xFF, 0xFA, 0x6E, 0x00, 0x00, 0xAA, 0xE4,
+    0x54, 0x00, 0x00, 0x7A, 0x7A, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3F, 0x3F, 0x3E, 0x3E, 0x3E, 0x3E,
+    0x3E, 0x3E, 0x3E, 0x3C, 0x3C, 0x3D, 0x3D, 0x3D, 0x3D, 0x3F, 0x3F, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3C, 0x3C, 0x3D,
+    0x3D, 0x3D, 0x3D, 0x3F, 0x3F, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3C, 0x3C, 0x3D, 0x3D, 0x3F, 0x3F, 0x3E,
+    0x3A, 0x3A, 0x40, 0x3C, 0x41, 0xC4, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1E, 0x7D, 0xF3, 0xA4, 0x78, 0x6D,
+    0x05, 0x2C, 0x8F, 0x38, 0x28, 0x88, 0x44, 0x94, 0x62, 0x8B, 0x56, 0x4D, 0x17, 0x8E, 0x81, 0x2D, 0x59, 0x19, 0x0B,
+    0xFC, 0xB9, 0x2C, 0xE0, 0xCC, 0xB7, 0x70, 0x2C, 0x81, 0xEC, 0x84, 0x4E, 0x20, 0x81, 0xB4, 0x4A, 0x7F, 0x2B, 0x5B,
+    0x1F, 0xAE, 0x7B, 0x2C, 0xD1, 0x97, 0x6B, 0x48, 0x7E, 0xC3, 0xD8, 0x0C, 0x57, 0x70, 0xCE, 0x57, 0xF1, 0x8B, 0xED,
+    0x44, 0xE9, 0xAF, 0x66, 0xCE, 0x70, 0x84, 0x95, 0x32, 0x7B, 0xF5, 0x3D, 0x94, 0x64, 0xEA, 0x89, 0x08, 0xEF, 0xBC,
+    0xBF, 0x81, 0xD4, 0xE6, 0x4F, 0xA3, 0x94, 0x35, 0x29, 0x1B, 0x1A, 0x9B, 0xCE, 0x6C, 0xDF, 0x57, 0x0D, 0x1D, 0x45,
+    0x5F, 0xEB, 0xFE, 0xD2, 0x54, 0xA6, 0x28, 0x0F, 0x61, 0xFD, 0x0C, 0xC0, 0xF7, 0x96, 0xC1, 0x69, 0x36, 0x28, 0xC2,
+    0xC0, 0xA3, 0xB4, 0x3E, 0x40, 0x7B, 0xB4, 0x96, 0x77, 0x46, 0x30, 0xE8, 0xFD, 0xFD, 0xC7, 0x29, 0x08, 0x8A, 0x44,
+    0x77, 0xDD, 0x74, 0x3C, 0x7C, 0xB5, 0xB4, 0x25, 0x38, 0x83, 0x35, 0xA7, 0xF9, 0xFF, 0xB9, 0x48, 0x44, 0xA3, 0xA8,
+    0x1A, 0x2D, 0x51, 0x53, 0xB9, 0x47, 0xC5, 0x00, 0x00, 0x00, 0x81, 0x01, 0x83, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1,
+    0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0x38,
+    0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E,
+    0x1C, 0x38, 0x70, 0xE1, 0xC0, 0x00, 0x00, 0x00};
 
-unsigned char sixty_pts_prdf3_compressed[986] = {
-    0x71, 0xC4, 0xFC, 0xFF, 0xD9, 0xAA, 0x02, 0x00, 0xAA, 0x75, 0xFC, 0xFF, 0x2D, 0x00, 0x52, 0x02, 0xF7, 0x82, 0xA4,
-    0x1C, 0x6C, 0xA5, 0xFF, 0x1D, 0x81, 0x5B, 0x0E, 0x41, 0x3B, 0x00, 0x5B, 0x00, 0x66, 0x00, 0xC8, 0x6C, 0xB5, 0xCF,
-    0xA2, 0x65, 0xB8, 0x19, 0x6B, 0x01, 0xC9, 0xCB, 0x1A, 0xBD, 0xFF, 0xD1, 0x70, 0xF9, 0x42, 0x7A, 0x90, 0x2B, 0x13,
-    0x4E, 0x37, 0x17, 0x5E, 0x5A, 0x8D, 0x67, 0xBF, 0x52, 0xF6, 0x7B, 0x50, 0x31, 0x53, 0x00, 0x8F, 0xF8, 0xBE, 0x02,
-    0xCC, 0xE0, 0x97, 0xD0, 0x8A, 0xA0, 0x47, 0x4A, 0xDA, 0xBF, 0x11, 0x34, 0x17, 0xA3, 0x9A, 0xE8, 0xC2, 0x0A, 0xB4,
-    0x3D, 0x11, 0x26, 0x85, 0xDA, 0x1A, 0xD9, 0xFC, 0xC0, 0xBF, 0xD8, 0x89, 0x76, 0xF9, 0xB2, 0x3D, 0x68, 0x62, 0x82,
-    0xA8, 0x1B, 0x56, 0xBC, 0x7F, 0xFD, 0x40, 0x83, 0xBF, 0xAA, 0x8F, 0xB1, 0x36, 0x46, 0x6F, 0x2D, 0xA6, 0x31, 0xAA,
-    0xEA, 0xAD, 0x03, 0x7F, 0xB9, 0x92, 0xDE, 0x4C, 0xAA, 0x77, 0x77, 0xFE, 0x32, 0xD9, 0xEF, 0x40, 0x18, 0xA9, 0xDC,
-    0x13, 0xFF, 0xF1, 0x9A, 0x2C, 0x5C, 0x92, 0x68, 0xF9, 0x57, 0xFE, 0x2C, 0xDC, 0x1D, 0x9B, 0xE2, 0x26, 0x8E, 0x56,
-    0x71, 0xC7, 0x7F, 0x60, 0x8B, 0x16, 0x50, 0xDD, 0x25, 0xB0, 0xB4, 0xC3, 0xFF, 0xAD, 0x5A, 0x9A, 0x84, 0x7D, 0xDE,
-    0xB1, 0x47, 0x6C, 0xA3, 0x2F, 0xCC, 0x08, 0xE4, 0xDF, 0x54, 0x3B, 0x2B, 0x7F, 0xBB, 0x47, 0x1E, 0x70, 0x60, 0x63,
-    0x85, 0x41, 0x9C, 0xDA, 0xBA, 0x78, 0xC5, 0x39, 0x0F, 0x61, 0x7F, 0xE4, 0x99, 0xFD, 0x67, 0x19, 0x6F, 0xE0, 0x6E,
-    0x55, 0x20, 0x28, 0xC4, 0x0D, 0x0A, 0xAC, 0xC0, 0xDA, 0x5D, 0x24, 0xF2, 0x6B, 0x09, 0x52, 0xB3, 0xFD, 0x26, 0x38,
-    0x97, 0x89, 0x7F, 0x62, 0xC6, 0x60, 0x42, 0x34, 0x82, 0x7C, 0x83, 0x24, 0xE0, 0xA8, 0x58, 0x00, 0x63, 0x91, 0x93,
-    0x7F, 0xE0, 0x98, 0xBC, 0x38, 0xBB, 0x48, 0x75, 0x65, 0x0C, 0x77, 0x03, 0x87, 0xAA, 0x8C, 0xD3, 0x84, 0x8B, 0xE6,
-    0x61, 0x4D, 0x3B, 0x85, 0xF6, 0xDB, 0xFD, 0xBA, 0x97, 0x33, 0x5B, 0x0C, 0xDC, 0x05, 0x78, 0x67, 0xFF, 0xEA, 0x19,
-    0x7A, 0x66, 0xAD, 0xF7, 0xCA, 0x9C, 0x56, 0x21, 0x34, 0x3A, 0xBC, 0xCF, 0x28, 0x7F, 0xBA, 0x13, 0x1C, 0xBF, 0x9A,
-    0x51, 0x8F, 0x02, 0xE6, 0xD6, 0x39, 0x50, 0x50, 0x4B, 0x1E, 0x77, 0xD9, 0x04, 0xA1, 0xE3, 0xE9, 0x36, 0x6C, 0x5A,
-    0x30, 0x1C, 0xBC, 0x63, 0x14, 0xA9, 0x30, 0x20, 0xE1, 0xFD, 0xC4, 0x9B, 0x17, 0x16, 0xE6, 0x98, 0x5D, 0xFE, 0xA6,
-    0x58, 0xF9, 0xCC, 0x92, 0xBF, 0x7B, 0xC5, 0x7F, 0x70, 0x7F, 0xCE, 0x07, 0x1F, 0x7D, 0x71, 0x13, 0xA8, 0x0E, 0x94,
-    0xB0, 0x06, 0x1B, 0x0E, 0x39, 0xDE, 0xE4, 0xAB, 0x7E, 0xC0, 0x6B, 0x27, 0xA7, 0x96, 0x08, 0xD7, 0xD8, 0xA7, 0x32,
-    0xA8, 0xF3, 0x5D, 0xB5, 0xF6, 0x1C, 0x19, 0x1C, 0xDB, 0x4F, 0x65, 0x0E, 0xFF, 0x96, 0x83, 0x8A, 0x82, 0x7F, 0x8F,
-    0x7F, 0xC7, 0xFC, 0x94, 0x5A, 0x5F, 0xCE, 0x0D, 0x75, 0xD4, 0xD3, 0xC5, 0xDD, 0x85, 0x8A, 0x7E, 0x17, 0x64, 0x76,
-    0xFD, 0x28, 0x72, 0x21, 0xDA, 0xC7, 0xCC, 0x22, 0x47, 0x9D, 0x80, 0xFF, 0xCC, 0xD1, 0xA4, 0x72, 0x3C, 0xDC, 0x7D,
-    0x6A, 0x10, 0xA5, 0x10, 0x18, 0x99, 0xEE, 0x4E, 0x4E, 0xA2, 0x57, 0x9E, 0xFF, 0x83, 0x1F, 0xAF, 0x9E, 0xDA, 0x10,
-    0x52, 0x48, 0xCD, 0x7F, 0xCB, 0xEB, 0x53, 0x20, 0x5B, 0x2D, 0x3D, 0x36, 0x67, 0xFF, 0x3D, 0x49, 0xEE, 0x0E, 0x6D,
-    0x46, 0x15, 0x36, 0xA6, 0xE5, 0x1F, 0x8F, 0x88, 0x5E, 0x2D, 0x7B, 0x81, 0xFA, 0x3D, 0x82, 0x87, 0x5C, 0x34, 0x39,
-    0xE7, 0x1F, 0xCD, 0xCD, 0x69, 0x2F, 0x64, 0x77, 0xBF, 0xE7, 0x50, 0x01, 0xEF, 0x35, 0xEA, 0xE4, 0xD3, 0x93, 0xB2,
-    0x0F, 0x16, 0x92, 0x6C, 0xE1, 0x7F, 0xF3, 0x0E, 0xBD, 0x44, 0x50, 0x6D, 0x76, 0x47, 0x44, 0x7E, 0xDA, 0x0D, 0x09,
-    0x01, 0x1F, 0x2C, 0xFE, 0x97, 0xEB, 0x39, 0x0D, 0xA1, 0xCE, 0xEB, 0x48, 0x93, 0x28, 0x27, 0x6E, 0x8A, 0xFF, 0x3A,
-    0xF1, 0x4B, 0xFF, 0x4A, 0x5B, 0x63, 0xEA, 0xAA, 0x24, 0x2A, 0xB8, 0x20, 0xC2, 0xC6, 0x95, 0xDA, 0xF8, 0xAE, 0x25,
-    0xD1, 0x54, 0x32, 0xB9, 0xC1, 0xA5, 0x3B, 0xC8, 0x5B, 0x7B, 0xFA, 0xF6, 0x6B, 0xE7, 0x66, 0xFF, 0xD2, 0xB8, 0x05,
-    0x61, 0x7A, 0xB3, 0x32, 0x1F, 0x86, 0x57, 0x96, 0x3C, 0x72, 0xD1, 0xF9, 0x27, 0xF5, 0xDE, 0x3B, 0xC2, 0x94, 0x23,
-    0x3A, 0xEC, 0x57, 0x1E, 0x29, 0x2D, 0x2F, 0x92, 0x9F, 0x41, 0x7F, 0xC8, 0xCD, 0x39, 0x79, 0xED, 0xAD, 0xEC, 0xF2,
-    0x0C, 0x7D, 0x28, 0x6D, 0x00, 0x8F, 0xAA, 0xB8, 0x1C, 0xD7, 0x18, 0xCA, 0x37, 0x11, 0x52, 0xA8, 0xA4, 0x97, 0x63,
-    0x7E, 0xBF, 0x09, 0x2D, 0x4B, 0x0E, 0x15, 0xD7, 0x7B, 0xE4, 0x46, 0x2E, 0xB9, 0xE2, 0x9A, 0x7F, 0xF9, 0x01, 0x21,
-    0x12, 0x8F, 0x23, 0x0F, 0xD0, 0x27, 0x9B, 0x97, 0x04, 0xC3, 0x6C, 0xCA, 0xFE, 0x10, 0xB0, 0x1E, 0x89, 0xED, 0xC3,
-    0x84, 0xC1, 0x25, 0x76, 0x21, 0x74, 0x98, 0x74, 0x03, 0xFD, 0xA6, 0x99, 0x37, 0x17, 0x93, 0xC6, 0x77, 0x0C, 0x03,
-    0x5D, 0xD8, 0x43, 0x69, 0xD2, 0x7E, 0xD6, 0x5E, 0xAE, 0xD2, 0xBB, 0xC8, 0x82, 0x5B, 0xAA, 0xAC, 0x41, 0xE7, 0x6A,
-    0x06, 0x30, 0xA8, 0x44, 0x1B, 0x58, 0x67, 0x6B, 0xC5, 0xED, 0x42, 0xF0, 0x73, 0x8F, 0xFF, 0x91, 0xA1, 0xD8, 0xFE,
-    0xFD, 0xD0, 0xEA, 0x44, 0xA5, 0xBC, 0x36, 0x5F, 0x75, 0xE4, 0x58, 0x52, 0xD9, 0xFC, 0x39, 0x00, 0x18, 0x74, 0x39,
-    0x09, 0x24, 0x09, 0x04, 0xF3, 0x61, 0x68, 0xC7, 0x5B, 0x58, 0x1F, 0xFE, 0x44, 0x96, 0x10, 0x6B, 0xD2, 0x1E, 0x97,
-    0x87, 0xF9, 0x36, 0x21, 0x73, 0xC8, 0xDE, 0xB5, 0xCF, 0xFC, 0x06, 0x24, 0xD2, 0x56, 0x6A, 0xB6, 0x0A, 0x1D, 0x86,
-    0x6A, 0xF8, 0x96, 0x76, 0xD7, 0xAD, 0x6B, 0x6A, 0x7F, 0x5D, 0xEE, 0x84, 0xAD, 0x9C, 0xAB, 0x18, 0x0B, 0x9D, 0x60,
-    0x43, 0x28, 0x26, 0xAF, 0xF2, 0xF0, 0x63, 0x52, 0xE5, 0x6E, 0xB9, 0x19, 0xF6, 0xC3, 0x5F, 0x20, 0xF7, 0x0E, 0x2F,
-    0x7F, 0x84, 0xEA, 0x1B, 0x3D, 0xE5, 0x54, 0x45, 0xB5, 0xE7, 0xA8, 0x09, 0x72, 0x9A, 0x81, 0x7A, 0x5B, 0xF1, 0xF3,
-    0x08, 0xE1, 0xDE, 0x1C, 0x67, 0xF0, 0xB2, 0x7E, 0x51, 0x64, 0x63, 0x71, 0xAC, 0xFE, 0x08, 0xC6, 0x6D, 0x44, 0x99,
-    0x4E, 0x81, 0xCB, 0x78, 0x4B, 0x12, 0x4C, 0x50, 0x35, 0xCA, 0xFE, 0xD9, 0xB2, 0x1C, 0x13, 0xFA, 0x0A, 0x8D, 0xF0,
-    0xCB, 0x3B, 0xEE, 0xAC, 0xD0, 0x03, 0x8D, 0x72, 0x43, 0xF3, 0x64, 0xF2, 0x63, 0xCF, 0xE2, 0x29, 0xFA, 0xB2, 0xEA,
-    0x7F, 0xB0, 0x5A, 0xDD, 0x2D, 0xBA, 0xCF, 0x2D, 0x30, 0x6B, 0xAC, 0xC8, 0xD2, 0xE4, 0x64, 0x7D, 0xC1, 0x8F, 0x40,
-    0x57, 0x4B, 0x68, 0x92, 0x2A, 0xA6, 0x53, 0x5D, 0x19, 0x51, 0xF9, 0xB1, 0x17, 0xEA, 0x55, 0x66, 0x55, 0xA7, 0x0E,
-    0x96, 0x4F, 0x15, 0xF7, 0x1B, 0xD0, 0xB6, 0xB1, 0xE4, 0x7F, 0x2D, 0xDB, 0x3D, 0x45, 0x00, 0x00, 0x00};
+unsigned char last_60_pts[2160] = {
+    0xE9, 0xB0, 0xFD, 0xFF, 0x52, 0xA3, 0xFC, 0xFF, 0x54, 0x83, 0xFC, 0xFF, 0x55, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65,
+    0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0xF9, 0xAF, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x10, 0xB1,
+    0xFD, 0xFF, 0x7F, 0xA2, 0xFC, 0xFF, 0xB6, 0x82, 0xFC, 0xFF, 0x68, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E,
+    0x1C, 0x6C, 0x25, 0xFF, 0xAF, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x4B, 0xB1, 0xFD, 0xFF,
+    0x93, 0xA1, 0xFC, 0xFF, 0x6B, 0x83, 0xFC, 0xFF, 0x3B, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C,
+    0x65, 0x04, 0xB0, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x7A, 0xB1, 0xFD, 0xFF, 0xBB, 0xA0,
+    0xFC, 0xFF, 0x65, 0x83, 0xFC, 0xFF, 0x61, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0x09,
+    0xB0, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xC1, 0xB1, 0xFD, 0xFF, 0xC2, 0x9F, 0xFC, 0xFF,
+    0xBD, 0x84, 0xFC, 0xFF, 0x03, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0x0F, 0xB0, 0x43,
+    0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xCE, 0xB1, 0xFD, 0xFF, 0x07, 0x9F, 0xFC, 0xFF, 0xC4, 0x82,
+    0xFC, 0xFF, 0x60, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0x14, 0xB0, 0x43, 0xF4, 0x0D,
+    0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x06, 0xB2, 0xFD, 0xFF, 0x74, 0x9D, 0xFC, 0xFF, 0x93, 0x80, 0xFC, 0xFF,
+    0x32, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0x1E, 0xB0, 0x43, 0xF4, 0x0D, 0x41, 0x00,
+    0x42, 0x00, 0xFF, 0x00, 0x06, 0xA8, 0xB2, 0xFD, 0xFF, 0x74, 0x9B, 0xFC, 0xFF, 0x19, 0x84, 0xFC, 0xFF, 0x28, 0x00,
+    0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0x29, 0xB0, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00,
+    0xFF, 0x00, 0x06, 0xF3, 0xB2, 0xFD, 0xFF, 0x76, 0x9A, 0xFC, 0xFF, 0xBD, 0x85, 0xFC, 0xFF, 0x5B, 0x00, 0x11, 0x40,
+    0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0x2E, 0xB0, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00,
+    0x06, 0x17, 0xB3, 0xFD, 0xFF, 0xA8, 0x99, 0xFC, 0xFF, 0x06, 0x85, 0xFC, 0xFF, 0x71, 0x00, 0x11, 0x40, 0x05, 0x7A,
+    0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0x34, 0xB0, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x2D,
+    0xB3, 0xFD, 0xFF, 0x2F, 0x98, 0xFC, 0xFF, 0x16, 0x81, 0xFC, 0xFF, 0x4A, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD,
+    0x9E, 0x1C, 0x6C, 0xA5, 0x3E, 0xB0, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x55, 0xB2, 0xFD,
+    0xFF, 0x56, 0x98, 0xFC, 0xFF, 0x2D, 0x81, 0xFC, 0xFF, 0x6E, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C,
+    0x6C, 0x65, 0xA5, 0xC7, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xA7, 0xB1, 0xFD, 0xFF, 0x11,
+    0x9B, 0xFC, 0xFF, 0x90, 0x7F, 0xFC, 0xFF, 0x89, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65,
+    0xB5, 0xC7, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x20, 0xB1, 0xFD, 0xFF, 0xA8, 0x9D, 0xFC,
+    0xFF, 0x3B, 0x80, 0xFC, 0xFF, 0x40, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0xC5, 0xC7,
+    0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x20, 0xB1, 0xFD, 0xFF, 0x52, 0x9E, 0xFC, 0xFF, 0xAD,
+    0x82, 0xFC, 0xFF, 0x89, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0xCA, 0xC7, 0x43, 0xF4,
+    0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xF0, 0xB0, 0xFD, 0xFF, 0x31, 0x9F, 0xFC, 0xFF, 0xC1, 0x82, 0xFC,
+    0xFF, 0x87, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0xCF, 0xC7, 0x43, 0xF4, 0x0D, 0x41,
+    0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xBB, 0xB0, 0xFD, 0xFF, 0xB8, 0xA0, 0xFC, 0xFF, 0x3A, 0x85, 0xFC, 0xFF, 0x7B,
+    0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0xDA, 0xC7, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42,
+    0x00, 0xFF, 0x00, 0x06, 0x89, 0xB0, 0xFD, 0xFF, 0x9B, 0xA1, 0xFC, 0xFF, 0x37, 0x85, 0xFC, 0xFF, 0x77, 0x00, 0x11,
+    0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0xDF, 0xC7, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF,
+    0x00, 0x06, 0xF3, 0xAF, 0xFD, 0xFF, 0x93, 0xA3, 0xFC, 0xFF, 0xF5, 0x81, 0xFC, 0xFF, 0x53, 0x00, 0x11, 0x00, 0x05,
+    0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0xEA, 0xC7, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06,
+    0xC1, 0xAF, 0xFD, 0xFF, 0x70, 0xA4, 0xFC, 0xFF, 0xE5, 0x81, 0xFC, 0xFF, 0x9C, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65,
+    0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0xEF, 0xC7, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x3C, 0xAF,
+    0xFD, 0xFF, 0x5E, 0xA4, 0xFC, 0xFF, 0xCE, 0x81, 0xFC, 0xFF, 0x8C, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E,
+    0x1C, 0x6C, 0x65, 0x2F, 0xD7, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x37, 0xB0, 0xFD, 0xFF,
+    0x4B, 0xA1, 0xFC, 0xFF, 0xD7, 0x87, 0xFC, 0xFF, 0x4F, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C,
+    0x25, 0x3F, 0xD7, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x66, 0xB0, 0xFD, 0xFF, 0xCC, 0x9F,
+    0xFC, 0xFF, 0x0F, 0x85, 0xFC, 0xFF, 0x67, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0x49,
+    0xD7, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x20, 0xB1, 0xFD, 0xFF, 0xFD, 0x9A, 0xFC, 0xFF,
+    0x79, 0x7F, 0xFC, 0xFF, 0x0C, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0x69, 0xD7, 0x43,
+    0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x76, 0xB1, 0xFD, 0xFF, 0xF9, 0x99, 0xFC, 0xFF, 0xAA, 0x81,
+    0xFC, 0xFF, 0x31, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0x6E, 0xD7, 0x43, 0xF4, 0x0D,
+    0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xD5, 0xB1, 0xFD, 0xFF, 0x35, 0x98, 0xFC, 0xFF, 0xB4, 0x81, 0xFC, 0xFF,
+    0x53, 0x00, 0x21, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0x79, 0xD7, 0x43, 0xF4, 0x0D, 0x41, 0x00,
+    0x42, 0x00, 0xFF, 0x00, 0x06, 0xCB, 0xAF, 0xFD, 0xFF, 0x6C, 0x9D, 0xFC, 0xFF, 0xDF, 0x80, 0xFC, 0xFF, 0x60, 0x00,
+    0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0xFF, 0xEE, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00,
+    0xFF, 0x00, 0x06, 0x8A, 0xAF, 0xFD, 0xFF, 0x0D, 0x9F, 0xFC, 0xFF, 0xB4, 0x82, 0xFC, 0xFF, 0x6A, 0x00, 0x11, 0x00,
+    0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0x0A, 0xEF, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00,
+    0x06, 0x8D, 0xAF, 0xFD, 0xFF, 0xB5, 0x9F, 0xFC, 0xFF, 0x68, 0x85, 0xFC, 0xFF, 0x6A, 0x00, 0x11, 0x00, 0x05, 0x7C,
+    0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0x0F, 0xEF, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x76,
+    0xAF, 0xFD, 0xFF, 0x72, 0xA0, 0xFC, 0xFF, 0xAA, 0x86, 0xFC, 0xFF, 0x73, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD,
+    0x9E, 0x1C, 0x6C, 0x25, 0x15, 0xEF, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x53, 0xAF, 0xFD,
+    0xFF, 0x45, 0xA1, 0xFC, 0xFF, 0x57, 0x87, 0xFC, 0xFF, 0x70, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C,
+    0x6C, 0x65, 0x1A, 0xEF, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xA1, 0xAE, 0xFD, 0xFF, 0x54,
+    0xA3, 0xFC, 0xFF, 0x0C, 0x83, 0xFC, 0xFF, 0x86, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5,
+    0x24, 0xEF, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xFD, 0xAD, 0xFD, 0xFF, 0x58, 0xA5, 0xFC,
+    0xFF, 0x48, 0x7F, 0xFC, 0xFF, 0x5C, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0x2F, 0xEF,
+    0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xDD, 0xAD, 0xFD, 0xFF, 0x8F, 0xA4, 0xFC, 0xFF, 0x82,
+    0x83, 0xFC, 0xFF, 0x52, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0x69, 0xFE, 0x43, 0xF4,
+    0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x04, 0xAE, 0xFD, 0xFF, 0xBD, 0xA3, 0xFC, 0xFF, 0x10, 0x83, 0xFC,
+    0xFF, 0x7D, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0x6F, 0xFE, 0x43, 0xF4, 0x0D, 0x41,
+    0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x50, 0xAE, 0xFD, 0xFF, 0xC5, 0xA2, 0xFC, 0xFF, 0xA3, 0x84, 0xFC, 0xFF, 0x65,
+    0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0x74, 0xFE, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42,
+    0x00, 0xFF, 0x00, 0x06, 0xA7, 0xAE, 0xFD, 0xFF, 0xB7, 0xA1, 0xFC, 0xFF, 0xC4, 0x86, 0xFC, 0xFF, 0x31, 0x00, 0x11,
+    0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0x79, 0xFE, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF,
+    0x00, 0x06, 0xFD, 0xAE, 0xFD, 0xFF, 0x0A, 0xA0, 0xFC, 0xFF, 0x37, 0x86, 0xFC, 0xFF, 0x5A, 0x00, 0x11, 0x40, 0x05,
+    0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0x84, 0xFE, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06,
+    0x03, 0xAF, 0xFD, 0xFF, 0x58, 0x9F, 0xFC, 0xFF, 0xFC, 0x83, 0xFC, 0xFF, 0x57, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65,
+    0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0x89, 0xFE, 0x43, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x74, 0xAE,
+    0xFD, 0xFF, 0x38, 0x9D, 0xFC, 0xFF, 0xDF, 0x80, 0xFC, 0xFF, 0x30, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E,
+    0x1C, 0x6C, 0xA5, 0x3A, 0x16, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x6C, 0xAE, 0xFD, 0xFF,
+    0xEC, 0x9D, 0xFC, 0xFF, 0x27, 0x83, 0xFC, 0xFF, 0x72, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C,
+    0xE5, 0x3F, 0x16, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x3B, 0xAE, 0xFD, 0xFF, 0xCE, 0x9E,
+    0xFC, 0xFF, 0xF2, 0x82, 0xFC, 0xFF, 0x81, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0x45,
+    0x16, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x3B, 0xAE, 0xFD, 0xFF, 0x79, 0x9F, 0xFC, 0xFF,
+    0xE1, 0x85, 0xFC, 0xFF, 0x67, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0x4A, 0x16, 0x44,
+    0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xFD, 0xAD, 0xFD, 0xFF, 0x63, 0xA0, 0xFC, 0xFF, 0x0F, 0x85,
+    0xFC, 0xFF, 0x6F, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0x4F, 0x16, 0x44, 0xF4, 0x0D,
+    0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xAE, 0xAD, 0xFD, 0xFF, 0x62, 0xA1, 0xFC, 0xFF, 0x16, 0x83, 0xFC, 0xFF,
+    0x98, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0x54, 0x16, 0x44, 0xF4, 0x0D, 0x41, 0x00,
+    0x42, 0x00, 0xFF, 0x00, 0x06, 0x69, 0xAD, 0xFD, 0xFF, 0x56, 0xA2, 0xFC, 0xFF, 0x1D, 0x82, 0xFC, 0xFF, 0x93, 0x00,
+    0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0x5A, 0x16, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00,
+    0xFF, 0x00, 0x06, 0x3E, 0xAD, 0xFD, 0xFF, 0x2E, 0xA3, 0xFC, 0xFF, 0x5B, 0x82, 0xFC, 0xFF, 0x9B, 0x00, 0x11, 0x00,
+    0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5, 0x5F, 0x16, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00,
+    0x06, 0x10, 0xAD, 0xFD, 0xFF, 0x06, 0xA4, 0xFC, 0xFF, 0x93, 0x82, 0xFC, 0xFF, 0x51, 0x00, 0x21, 0x00, 0x05, 0x7C,
+    0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0x64, 0x16, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xB2,
+    0xAC, 0xFD, 0xFF, 0x2E, 0xA3, 0xFC, 0xFF, 0x72, 0x82, 0xFC, 0xFF, 0x7D, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD,
+    0x9E, 0x1C, 0x6C, 0x25, 0xAF, 0x25, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xDC, 0xAC, 0xFD,
+    0xFF, 0x56, 0xA2, 0xFC, 0xFF, 0x16, 0x82, 0xFC, 0xFF, 0x8D, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C,
+    0x6C, 0xA5, 0xB4, 0x25, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xB4, 0xAD, 0xFD, 0xFF, 0xC9,
+    0x9E, 0xFC, 0xFF, 0x65, 0x83, 0xFC, 0xFF, 0x52, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xA5,
+    0xC9, 0x25, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xDD, 0xAD, 0xFD, 0xFF, 0xF0, 0x9D, 0xFC,
+    0xFF, 0x16, 0x83, 0xFC, 0xFF, 0x0A, 0x00, 0x11, 0x40, 0x05, 0x7A, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0xCE, 0x25,
+    0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xB2, 0xAC, 0xFD, 0xFF, 0x0D, 0x9F, 0xFC, 0xFF, 0x13,
+    0x82, 0xFC, 0xFF, 0x8C, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0x7F, 0x3D, 0x44, 0xF4,
+    0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x81, 0xAC, 0xFD, 0xFF, 0xF0, 0x9F, 0xFC, 0xFF, 0xEC, 0x81, 0xFC,
+    0xFF, 0x7D, 0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x25, 0x85, 0x3D, 0x44, 0xF4, 0x0D, 0x41,
+    0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x9A, 0xAB, 0xFD, 0xFF, 0x89, 0xA3, 0xFC, 0xFF, 0xCE, 0x7F, 0xFC, 0xFF, 0x0A,
+    0x00, 0x11, 0x00, 0x05, 0x7C, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0x9A, 0x3D, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42,
+    0x00, 0xFF, 0x00, 0x06, 0x76, 0xAB, 0xFD, 0xFF, 0x13, 0xA2, 0xFC, 0xFF, 0x1D, 0x81, 0xFC, 0xFF, 0x43, 0x00, 0x21,
+    0x40, 0x05, 0x78, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0xF4, 0x4C, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF,
+    0x00, 0x06, 0xA1, 0xAB, 0xFD, 0xFF, 0x35, 0xA1, 0xFC, 0xFF, 0xEE, 0x80, 0xFC, 0xFF, 0x63, 0x00, 0x11, 0x40, 0x05,
+    0x78, 0x65, 0xFD, 0x9E, 0x1C, 0x6C, 0xE5, 0xF9, 0x4C, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06,
+    0xD6, 0xAA, 0xFD, 0xFF, 0x3E, 0xA1, 0xFC, 0xFF, 0x55, 0x81, 0xFC, 0xFF, 0x03, 0x00, 0x21, 0x00, 0x05, 0x7C, 0x65,
+    0xFD, 0x9E, 0x1C, 0x6C, 0x65, 0xCA, 0x64, 0x44, 0xF4, 0x0D, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0x39, 0xAB,
+    0xFD, 0xFF, 0x5C, 0xA1, 0xFC, 0xFF, 0x34, 0x81, 0xFC, 0xFF, 0x5F, 0x00, 0x11, 0x40, 0x05, 0x78, 0x30, 0xF8, 0x9F,
+    0x1C, 0x6C, 0xA5, 0x3F, 0xF6, 0x8E, 0x0A, 0x0E, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06, 0xB8, 0xAC, 0xFD, 0xFF,
+    0xCC, 0xA3, 0xFC, 0xFF, 0x72, 0x82, 0xFC, 0xFF, 0x78, 0x00, 0x11, 0x00, 0x05, 0x80, 0x30, 0xF8, 0x9F, 0x1C, 0x6C,
+    0xA5, 0x7F, 0x11, 0x8F, 0x0A, 0x0E, 0x41, 0x00, 0x42, 0x00, 0xFF, 0x00, 0x06};
diff --git a/test/extents_test.cpp b/test/extents_test.cpp
new file mode 100644
index 00000000..939e71a4
--- /dev/null
+++ b/test/extents_test.cpp
@@ -0,0 +1,189 @@
+#include <limits>
+#include <string>
+
+#include <catch2/catch.hpp>
+#include <copc-lib/copc/extents.hpp>
+#include <copc-lib/io/reader.hpp>
+#include <copc-lib/io/writer.hpp>
+#include <copc-lib/las/vlr.hpp>
+
+using namespace copc;
+using namespace std;
+
+TEST_CASE("COPC Extent", "[CopcExtent]")
+{
+    CopcExtent extent(-numeric_limits<double>::max(), numeric_limits<double>::max(), 5, 10);
+
+    REQUIRE(extent.minimum == -numeric_limits<double>::max());
+    REQUIRE(extent.maximum == numeric_limits<double>::max());
+    REQUIRE(extent.mean == 5);
+    REQUIRE(extent.var == 10);
+
+    CopcExtent other(-numeric_limits<double>::max(), numeric_limits<double>::max(), 5, 10);
+    REQUIRE(extent == other);
+
+    other = CopcExtent(-numeric_limits<double>::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<double>::max();
+            extents->Intensity()->maximum = numeric_limits<double>::max();
+            extents->Intensity()->mean = 15;
+            extents->Intensity()->var = 5;
+
+            extents->Classification()->minimum = 0;
+            extents->Classification()->maximum = 255;
+
+            extents->ExtraBytes()[0]->minimum = -numeric_limits<double>::max();
+            extents->ExtraBytes()[0]->maximum = numeric_limits<double>::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<double>::max());
+            REQUIRE(extents.Intensity()->maximum == numeric_limits<double>::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<double>::max());
+            REQUIRE(extents.ExtraBytes()[0]->maximum == numeric_limits<double>::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<int32_t>::max());
-        point0.UnscaledY(std::numeric_limits<int32_t>::max());
-        point0.UnscaledZ(std::numeric_limits<int32_t>::max());
-        REQUIRE(point0.UnscaledX() == std::numeric_limits<int32_t>::max());
-        REQUIRE(point0.UnscaledY() == std::numeric_limits<int32_t>::max());
-        REQUIRE(point0.UnscaledZ() == std::numeric_limits<int32_t>::max());
-
-        // Intensity
-        point0.Intensity(std::numeric_limits<uint16_t>::max());
-        REQUIRE(point0.Intensity() == std::numeric_limits<uint16_t>::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<uint8_t>::max());
-        REQUIRE(point0.ClassificationBitFields() == std::numeric_limits<uint8_t>::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<uint8_t>::max());
-        REQUIRE(point0.UserData() == std::numeric_limits<uint8_t>::max());
-
-        // Point Source ID
-        point0.PointSourceID(std::numeric_limits<uint16_t>::max());
-        REQUIRE(point0.PointSourceID() == std::numeric_limits<uint16_t>::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<uint16_t>::max()));
-        REQUIRE_THROWS(point0.NIR());
-        REQUIRE_THROWS(point0.Red(std::numeric_limits<uint16_t>::max()));
-        REQUIRE_THROWS(point0.Red());
-        REQUIRE_THROWS(point0.Green(std::numeric_limits<uint16_t>::max()));
-        REQUIRE_THROWS(point0.Green());
-        REQUIRE_THROWS(point0.Blue(std::numeric_limits<uint16_t>::max()));
-        REQUIRE_THROWS(point0.Blue());
-        REQUIRE_THROWS(point0.GPSTime(std::numeric_limits<double>::max()));
-        REQUIRE_THROWS(point0.GPSTime());
-        REQUIRE_THROWS(point0.ExtendedScanAngle(std::numeric_limits<int16_t>::max()));
-        REQUIRE_THROWS(point0.ExtendedScanAngle());
-        REQUIRE_THROWS(point0.Overlap(true));
-        REQUIRE_THROWS(point0.Overlap());
-        REQUIRE_THROWS(point0.ExtendedReturnsBitFields(std::numeric_limits<uint8_t>::max()));
-        REQUIRE_THROWS(point0.ExtendedReturnsBitFields());
-        REQUIRE_THROWS(point0.ExtendedFlagsBitFields(std::numeric_limits<uint8_t>::max()));
-        REQUIRE_THROWS(point0.ExtendedFlagsBitFields());
-        REQUIRE_NOTHROW(point0.ToString());
-
-        auto point1 = Point(1);
-
-        point1.GPSTime(std::numeric_limits<double>::max());
-        REQUIRE(point1.GPSTime() == std::numeric_limits<double>::max());
-        REQUIRE(point1.PointRecordLength() == 28);
-
-        REQUIRE_THROWS(point0.Red(std::numeric_limits<uint16_t>::max()));
-        REQUIRE_THROWS(point0.Red());
-        REQUIRE_THROWS(point0.Green(std::numeric_limits<uint16_t>::max()));
-        REQUIRE_THROWS(point0.Green());
-        REQUIRE_THROWS(point0.Blue(std::numeric_limits<uint16_t>::max()));
-        REQUIRE_THROWS(point0.Blue());
-        REQUIRE_THROWS(point0.NIR(std::numeric_limits<uint16_t>::max()));
-        REQUIRE_THROWS(point0.NIR());
-
-        auto point2 = Point(2);
-        REQUIRE(point2.PointRecordLength() == 26);
-
-        point2.Red(std::numeric_limits<uint16_t>::max());
-        REQUIRE(point2.Red() == std::numeric_limits<uint16_t>::max());
-        point2.Green(std::numeric_limits<uint16_t>::max());
-        REQUIRE(point2.Green() == std::numeric_limits<uint16_t>::max());
-        point2.Blue(std::numeric_limits<uint16_t>::max());
-        REQUIRE(point2.Blue() == std::numeric_limits<uint16_t>::max());
-
-        point2.RGB(std::numeric_limits<uint16_t>::max() / 2, std::numeric_limits<uint16_t>::max() / 2,
-                   std::numeric_limits<uint16_t>::max() / 2);
-        REQUIRE(point2.Red() == std::numeric_limits<uint16_t>::max() / 2);
-        REQUIRE(point2.Green() == std::numeric_limits<uint16_t>::max() / 2);
-        REQUIRE(point2.Blue() == std::numeric_limits<uint16_t>::max() / 2);
-
-        REQUIRE_THROWS(point2.GPSTime(std::numeric_limits<double>::max()));
-        REQUIRE_THROWS(point2.GPSTime());
-        REQUIRE_THROWS(point2.NIR(std::numeric_limits<uint16_t>::max()));
-        REQUIRE_THROWS(point2.NIR());
-
-        auto point3 = Point(3);
-        REQUIRE(point3.PointRecordLength() == 34);
-
-        REQUIRE_NOTHROW(point3.GPSTime(std::numeric_limits<double>::max()));
-        REQUIRE_NOTHROW(point3.GPSTime());
-        REQUIRE_NOTHROW(point3.RGB(std::numeric_limits<uint16_t>::max(), std::numeric_limits<uint16_t>::max(),
-                                   std::numeric_limits<uint16_t>::max()));
-        REQUIRE_NOTHROW(point3.Red());
-        REQUIRE_NOTHROW(point3.Green());
-        REQUIRE_NOTHROW(point3.Blue());
-        REQUIRE_THROWS(point3.NIR(std::numeric_limits<uint16_t>::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<uint16_t>::max());
 
         // Return BitField
-        point6.ExtendedReturnsBitFields(std::numeric_limits<uint8_t>::max());
-        REQUIRE(point6.ExtendedReturnsBitFields() == std::numeric_limits<uint8_t>::max());
+        point6.ReturnsBitField(std::numeric_limits<uint8_t>::max());
+        REQUIRE(point6.ReturnsBitField() == std::numeric_limits<uint8_t>::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<uint8_t>::max());
-        REQUIRE(point6.ExtendedFlagsBitFields() == std::numeric_limits<uint8_t>::max());
+        point6.FlagsBitField(std::numeric_limits<uint8_t>::max());
+        REQUIRE(point6.FlagsBitField() == std::numeric_limits<uint8_t>::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<uint8_t>::max());
         REQUIRE(point6.UserData() == std::numeric_limits<uint8_t>::max());
 
         // Point Source ID
-        point6.PointSourceID(std::numeric_limits<uint16_t>::max());
-        REQUIRE(point6.PointSourceID() == std::numeric_limits<uint16_t>::max());
+        point6.PointSourceId(std::numeric_limits<uint16_t>::max());
+        REQUIRE(point6.PointSourceId() == std::numeric_limits<uint16_t>::max());
 
         // GPS Time
         point6.GPSTime(std::numeric_limits<double>::max());
@@ -300,59 +138,35 @@ TEST_CASE("Point tests", "[Point]")
         REQUIRE(point6.PointRecordLength() == 30);
 
         // Checks
-        REQUIRE_THROWS(point6.RGB(std::numeric_limits<uint16_t>::max(), std::numeric_limits<uint16_t>::max(),
+        REQUIRE_THROWS(point6.Rgb(std::numeric_limits<uint16_t>::max(), std::numeric_limits<uint16_t>::max(),
                                   std::numeric_limits<uint16_t>::max()));
         REQUIRE_THROWS(point6.Red());
         REQUIRE_THROWS(point6.Green());
         REQUIRE_THROWS(point6.Blue());
-        REQUIRE_THROWS(point6.NIR(std::numeric_limits<uint16_t>::max()));
-        REQUIRE_THROWS(point6.NIR());
-        REQUIRE_THROWS(point6.ScanAngleRank());
-        REQUIRE_THROWS(point6.ScanAngleRank(INT8_MAX));
-        REQUIRE_THROWS(point6.ReturnsScanDirEofBitFields(std::numeric_limits<uint8_t>::max()));
-        REQUIRE_THROWS(point6.ReturnsScanDirEofBitFields());
-        REQUIRE_THROWS(point6.ClassificationBitFields(std::numeric_limits<uint8_t>::max()));
-        REQUIRE_THROWS(point6.ClassificationBitFields());
+        REQUIRE_THROWS(point6.Nir(std::numeric_limits<uint16_t>::max()));
+        REQUIRE_THROWS(point6.Nir());
         REQUIRE_NOTHROW(point6.ToString());
 
         auto point7 = Point(7);
 
-        point7.RGB(std::numeric_limits<uint16_t>::max(), std::numeric_limits<uint16_t>::max(),
+        point7.Rgb(std::numeric_limits<uint16_t>::max(), std::numeric_limits<uint16_t>::max(),
                    std::numeric_limits<uint16_t>::max());
         REQUIRE(point7.Red() == std::numeric_limits<uint16_t>::max());
         REQUIRE(point7.Green() == std::numeric_limits<uint16_t>::max());
         REQUIRE(point7.Blue() == std::numeric_limits<uint16_t>::max());
         REQUIRE(point7.PointRecordLength() == 36);
 
-        REQUIRE_THROWS(point7.NIR(std::numeric_limits<uint16_t>::max()));
-        REQUIRE_THROWS(point7.NIR());
+        REQUIRE_THROWS(point7.Nir(std::numeric_limits<uint16_t>::max()));
+        REQUIRE_THROWS(point7.Nir());
 
         auto point8 = Point(8);
 
-        point8.NIR(std::numeric_limits<uint16_t>::max());
-        REQUIRE(point8.NIR() == std::numeric_limits<uint16_t>::max());
+        point8.Nir(std::numeric_limits<uint16_t>::max());
+        REQUIRE(point8.Nir() == std::numeric_limits<uint16_t>::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<uint8_t>{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<uint8_t>{2, 3, 4}));
         REQUIRE_NOTHROW(point.ExtraBytes(std::vector<uint8_t>{2, 3, 4, 5, 6}));
         REQUIRE(point.ExtraBytes() == std::vector<uint8_t>{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<uint8_t>{2, 3, 4, 5, 6});
-        REQUIRE_THROWS(point.ExtraBytes(std::vector<uint8_t>{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<uint8_t>{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<std::shared_ptr<Point>>();
@@ -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<Point>(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 0);
+        point = std::make_shared<Point>(7, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 0);
         REQUIRE_THROWS(points.AddPoint(point));
 
         // Test check on extra bytes
-        point = std::make_shared<Point>(3, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 1);
+        point = std::make_shared<Point>(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 1);
         REQUIRE_THROWS(points.AddPoint(point));
     }
 
     SECTION("Adding Points to Points")
     {
         auto points = Points(std::vector<std::shared_ptr<Point>>(
-            10, std::make_shared<Point>(3, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4)));
+            10, std::make_shared<Point>(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4)));
         auto points_other = Points(std::vector<std::shared_ptr<Point>>(
-            10, std::make_shared<Point>(3, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4)));
+            10, std::make_shared<Point>(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<std::shared_ptr<Point>>(
-            10, std::make_shared<Point>(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4)));
+            10, std::make_shared<Point>(7, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4)));
         REQUIRE_THROWS(points.AddPoints(points_other));
 
         // Test check on extra bytes
         points_other = Points(std::vector<std::shared_ptr<Point>>(
-            10, std::make_shared<Point>(3, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 1)));
+            10, std::make_shared<Point>(6, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 1)));
         REQUIRE_THROWS(points.AddPoints(points_other));
     }
 
     SECTION("Points format conversion")
     {
         auto points = Points(std::vector<std::shared_ptr<Point>>(
-            10, std::make_shared<Point>(3, copc::Vector3::DefaultScale(), copc::Vector3::DefaultOffset(), 4)));
-        points.ToPointFormat(6);
+            10, std::make_shared<Point>(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<int32_t>::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<int8_t>::max());
-        points[0]->PointSourceID(std::numeric_limits<int8_t>::max());
+        points[0]->PointSourceId(std::numeric_limits<int8_t>::max());
         points[0]->GPSTime(std::numeric_limits<double>::max());
         points[0]->Red(std::numeric_limits<uint16_t>::max());
         points[0]->Green(std::numeric_limits<uint16_t>::max());
         points[0]->Blue(std::numeric_limits<uint16_t>::max());
-        REQUIRE_THROWS(points[0]->NIR(std::numeric_limits<uint16_t>::max()));
-        REQUIRE_THROWS(points[0]->ExtendedFlagsBitFields(std::numeric_limits<int8_t>::max()));
-        REQUIRE_THROWS(points[0]->ExtendedReturnsBitFields(std::numeric_limits<int8_t>::max()));
-        REQUIRE_THROWS(points[0]->ScannerChannel(3));
-        REQUIRE_THROWS(points[0]->ScanAngle(std::numeric_limits<int16_t>::max()));
+        REQUIRE_THROWS(points[0]->Nir(std::numeric_limits<uint16_t>::max()));
 
         REQUIRE(points[0]->UnscaledX() == std::numeric_limits<int32_t>::max());
         REQUIRE(points[0]->UnscaledY() == std::numeric_limits<int32_t>::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<int8_t>::max());
-        REQUIRE(points[0]->PointSourceID() == std::numeric_limits<int8_t>::max());
+        REQUIRE(points[0]->PointSourceId() == std::numeric_limits<int8_t>::max());
         REQUIRE(points[0]->GPSTime() == std::numeric_limits<double>::max());
         REQUIRE(points[0]->Red() == std::numeric_limits<uint16_t>::max());
         REQUIRE(points[0]->Green() == std::numeric_limits<uint16_t>::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<char>()));
         REQUIRE_THROWS(writer.AddNode(root_page, VoxelKey(0, 0, 0, 0), std::vector<char>()));
 
-        std::vector<char> root_node(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3));
+        std::vector<char> 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<char> twenty(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3));
+        std::vector<char> 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<char> twelve(twelve_pts_prdf3, twelve_pts_prdf3 + sizeof(twelve_pts_prdf3));
+        std::vector<char> 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<char> sixty(sixty_pts_prdf3, sixty_pts_prdf3 + sizeof(sixty_pts_prdf3));
+        std::vector<char> 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<char> twenty(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3));
+        std::vector<char> 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<char> twelve(twelve_pts_prdf3, twelve_pts_prdf3 + sizeof(twelve_pts_prdf3));
+        std::vector<char> 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<char> sixty(sixty_pts_prdf3, sixty_pts_prdf3 + sizeof(sixty_pts_prdf3));
+        std::vector<char> 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<char>(), 0));
 
-        std::vector<char> root_node(twenty_pts_prdf3_compressed,
-                                    twenty_pts_prdf3_compressed + sizeof(twenty_pts_prdf3_compressed));
+        std::vector<char> 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<char>(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3)));
+        REQUIRE(root_node_test == std::vector<char>(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<char> twenty(twenty_pts_prdf3_compressed,
-                                 twenty_pts_prdf3_compressed + sizeof(twenty_pts_prdf3_compressed));
+        std::vector<char> 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<char> twelve(twelve_pts_prdf3_compressed,
-                                 twelve_pts_prdf3_compressed + sizeof(twelve_pts_prdf3_compressed));
+        std::vector<char> 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<char> sixty(sixty_pts_prdf3_compressed,
-                                sixty_pts_prdf3_compressed + sizeof(sixty_pts_prdf3_compressed));
+        std::vector<char> 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<char>(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3)));
+            REQUIRE(sub_node_data == std::vector<char>(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<char>(twelve_pts_prdf3, twelve_pts_prdf3 + sizeof(twelve_pts_prdf3)));
+            REQUIRE(sub_node_data == std::vector<char>(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<char>(sixty_pts_prdf3, sixty_pts_prdf3 + sizeof(sixty_pts_prdf3)));
+            REQUIRE(sub_node_data == std::vector<char>(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<char> twenty(twenty_pts_prdf3_compressed,
-                                 twenty_pts_prdf3_compressed + sizeof(twenty_pts_prdf3_compressed));
+        std::vector<char> 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<char> twelve(twelve_pts_prdf3_compressed,
-                                 twelve_pts_prdf3_compressed + sizeof(twelve_pts_prdf3_compressed));
+        std::vector<char> 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<char> sixty(sixty_pts_prdf3_compressed,
-                                sixty_pts_prdf3_compressed + sizeof(sixty_pts_prdf3_compressed));
+        std::vector<char> 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<char>(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3)));
+            REQUIRE(sub_node_data == std::vector<char>(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<char>(sixty_pts_prdf3, sixty_pts_prdf3 + sizeof(sixty_pts_prdf3)));
+            REQUIRE(sub_node_data == std::vector<char>(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<char>(twelve_pts_prdf3, twelve_pts_prdf3 + sizeof(twelve_pts_prdf3)));
+            REQUIRE(sub_node_data == std::vector<char>(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<char>(twenty_pts_prdf3, twenty_pts_prdf3 + sizeof(twenty_pts_prdf3)));
+            REQUIRE(sub_node_data == std::vector<char>(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 <string>
 
 #include <catch2/catch.hpp>
+#include <copc-lib/copc/extents.hpp>
 #include <copc-lib/geometry/vector3.hpp>
 #include <copc-lib/io/reader.hpp>
 #include <copc-lib/io/writer.hpp>
@@ -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<double>::max();
+            extents->Classification()->maximum = std::numeric_limits<double>::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<uint64_t, 15> 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<uint64_t, 15>{0});
+            REQUIRE(writer.CopcConfig()->LasHeader()->min == min1);
+            REQUIRE(writer.CopcConfig()->LasHeader()->max == max1);
+            REQUIRE(writer.CopcConfig()->LasHeader()->points_by_return == std::array<uint64_t, 15>{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)