pax_global_header00006660000000000000000000000064144354544210014520gustar00rootroot0000000000000052 comment=fe35a3b941ce1a55b01cd3951932520cd63a666d tmontaigu-laszip-python-12d2474/000077500000000000000000000000001443545442100166175ustar00rootroot00000000000000tmontaigu-laszip-python-12d2474/.clang-format000066400000000000000000000005511443545442100211730ustar00rootroot00000000000000--- IndentWidth: 4 Language: Cpp # Force pointers to the type for C++. PointerAlignment: Right BinPackArguments: false BinPackParameters: false ColumnLimit: 110 AccessModifierOffset: -2 SpaceBeforeParens: ControlStatements AllowShortLoopsOnASingleLine: false BreakBeforeBraces: Allman AllowShortBlocksOnASingleLine: Empty AllowShortFunctionsOnASingleLine: Emptytmontaigu-laszip-python-12d2474/.github/000077500000000000000000000000001443545442100201575ustar00rootroot00000000000000tmontaigu-laszip-python-12d2474/.github/workflows/000077500000000000000000000000001443545442100222145ustar00rootroot00000000000000tmontaigu-laszip-python-12d2474/.github/workflows/build.yml000066400000000000000000000077111443545442100240440ustar00rootroot00000000000000--- name: CI 'on': pull_request: push: schedule: - cron: "0 0 1 * *" jobs: Build-ManyLinux-Wheels: runs-on: ubuntu-latest steps: - name: Clone uses: actions/checkout@v2 - name: Build Wheel run: docker run --rm -v `pwd`:/laszip-python -e PLAT=manylinux2014_x86_64 quay.io/pypa/manylinux2014_x86_64 /bin/sh /laszip-python/scripts/build-wheels-linux.sh - name: Upload wheels as artifacts uses: actions/upload-artifact@v2 with: name: "manylinux-wheels" path: "./wheelhouse/*manylinux*.whl" Build-ManyLinux-Arm64-Wheels: runs-on: ubuntu-latest steps: - name: Clone uses: actions/checkout@v2 - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Build Wheel run: docker run --rm -v `pwd`:/laszip-python --platform linux/arm64 -e PLAT=manylinux2014_aarch64 quay.io/pypa/manylinux2014_aarch64 /bin/sh /laszip-python/scripts/build-wheels-linux.sh - name: Upload wheels as artifacts uses: actions/upload-artifact@v2 with: name: "manylinux-arm64-wheels" path: "./wheelhouse/*manylinux*.whl" Build-MacOs-Wheels: runs-on: macos-latest strategy: matrix: python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ] steps: - name: Clone uses: actions/checkout@v2 - name: Setup python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} architecture: x64 - name: Print Python info run: | which python${{ matrix.python-version }} python${{ matrix.python-version }} --version - name: Install dependencies (macos) run: | brew install laszip ninja python${{ matrix.python-version }} -m pip install --upgrade pip python${{ matrix.python-version }} -m pip install delocate - name: Build Wheels run: python${{ matrix.python-version }} -m pip wheel --wheel-dir dist . env: SKBUILD_CMAKE_ARGS: "-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14" - name: Delocate wheels run: /bin/sh scripts/delocate-wheel.sh - name: Test importing the wheel run: | brew uninstall laszip whl_path="$(ls ./dist/*.whl | head -1)" python${{ matrix.python-version }} -m pip install $whl_path python${{ matrix.python-version }} -c "import laszip" - name: Upload wheel as artifacts uses: actions/upload-artifact@v2 with: name: "macOs-python${{ matrix.python-version }}" path: "./dist/*.whl" Build-Windows-Wheels: runs-on: windows-latest strategy: matrix: python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11"] steps: - name: Clone uses: actions/checkout@v2 - name: Setup python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} architecture: x64 - name: Print Python info run: | py -${{ matrix.python-version }} --version - name: Install dependencies run: | vcpkg install laszip:x64-windows py -${{ matrix.python-version }} -m pip install --upgrade pip - name: Build Wheels shell: pwsh run: | py -${{ matrix.python-version }} -m pip wheel --wheel-dir dist .` env: SKBUILD_CMAKE_ARGS: "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" - name: Test importing the wheel run: | vcpkg remove laszip:x64-windows $whl_path=ls ./dist/*.whl | Select-Object -Index 0 py -${{ matrix.python-version }} -m pip install "$whl_path" py -${{ matrix.python-version }} -c "import laszip" - name: Upload wheel as artifacts uses: actions/upload-artifact@v2 with: name: "windows-python${{ matrix.python-version }}" path: "./dist/*.whl" tmontaigu-laszip-python-12d2474/.gitignore000066400000000000000000000001341443545442100206050ustar00rootroot00000000000000# IDEs .idea # Python & C++ build Stuff *build* *.egg-info dist # Data file *.las *.laz tmontaigu-laszip-python-12d2474/CMakeLists.txt000066400000000000000000000014611443545442100213610ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.16) project(laszip-python) set(CMAKE_CXX_STANDARD 14) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) if(NOT SKBUILD) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/PythonEnvHelper.cmake) set_python_executable_from_current_venv() ensure_pybind11_cmake_module_is_in_path() endif() find_package(pybind11) find_package(LASzip MODULE REQUIRED) pybind11_add_module(laszip src/laszip.cpp src/laszip_error.h src/lasunzipper.h src/lasunzipper.cpp src/laszipper.h src/laszipper.cpp src/python_istreambuf.h src/python_ostreambuf.h) target_link_libraries(laszip PRIVATE LASzip::LASzip) install(TARGETS laszip DESTINATION laszip) if (WIN32) install(FILES ${LASZIP_DLL} DESTINATION laszip) endif() tmontaigu-laszip-python-12d2474/Changelog.md000066400000000000000000000011121443545442100210230ustar00rootroot00000000000000# 0.2.3 30 May 2023 - Fix LasUnZipper::decompress and LasZipper::compress when the number of bytes to compress/decompress exceeded the mac capacity of the inner stringstream used. # 0.2.2 8 Feb 2023 - Added missing MIT License # 0.2.1 26 Dec 2022 - Fixed selective decompressor constructor # 0.2.0 26 Dec 2022 - Added support for selective decompression # 0.1.0 30 May 2021 - Added `seek` method to LasUnZipper # 0.0.1 28 Dec 2020 - Initial release - Added LasUnZipper - Added LasZipper - Added LasZipDll - Added bindings to laszip_header - Added bindings to laszip_pointtmontaigu-laszip-python-12d2474/License.txt000066400000000000000000000020601443545442100207400ustar00rootroot00000000000000MIT License Copyright (c) 2023 Thomas Montaigu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tmontaigu-laszip-python-12d2474/Readme.md000066400000000000000000000016531443545442100203430ustar00rootroot00000000000000# laszip-python Unofficial bindings between [Python][python-site] and [LASzip][laszip-github] made using [pybind11][pybind11-github]. The main purpose is for integration within [laspy][laspy-github]. # Building Building can be done using cmake or pip. First, `laszip` and `pybind11` needs to be installed. You can install it via `vcpkg` or `conda` or other means. To help cmake find Laszip you may have to use `-DCMAKE_TOOLCHAIN_FILE=/some/path/vcpg.cmake` if you used vcpkg to install laszip, or `-DCMAKE_PREFIX_PARTH` (or `-DCMAKE_INCLUDE_PATH=...` and `-DCMAKE_LIBRARY_PATH=...` in rare cases). As setup.py calls cmake the same options make need to be given: ```shell set -gx SKBUILD_CMAKE_ARGS -DCMAKE_PREFIX_PATH=... pip install . ``` [laszip-github]: https://github.com/LASzip/LASzip [python-site]: https://www.python.org/ [pybind11-github]: https://github.com/pybind/pybind11 [laspy-github]: https://github.com/laspy/laspytmontaigu-laszip-python-12d2474/Releasing.md000066400000000000000000000000671443545442100210550ustar00rootroot00000000000000- Bump version in pyproject.toml - Update Changelog.md tmontaigu-laszip-python-12d2474/cmake/000077500000000000000000000000001443545442100176775ustar00rootroot00000000000000tmontaigu-laszip-python-12d2474/cmake/FindLASzip.cmake000066400000000000000000000033121443545442100226430ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) # TODO laszip_api && laszip version from header message(FATALL_ERROR "toolchain: ${CMAKE_TOOLCHAIN_FILE}") if (DEFINED ENV{CONDA_PREFIX}) set(CONDA_INCLUDE_DIR $ENV{CONDA_PREFIX}/Library/include) if (WIN32) set(CONDA_LIBRARY_DIR $ENV{CONDA_PREFIX}/Library/lib) else() set(CONDA_LIBRARY_DIR $ENV{CONDA_PREFIX}/lib) endif() endif() find_library(LASZIP_LIBRARY NAMES laszip3 laszip HINTS /usr/lib /usr/local/lib "${CONDA_LIBRARY_DIR}" ) find_path(LASZIP_INCLUDE_DIR NAMES laszip HINTS /usr/include /usr/local/include "${CONDA_INCLUDE_DIR}" ) if (WIN32) find_file(LASZIP_DLL NAMES laszip3.dll HINTS "${LASZIP_INCLUDE_DIR}/../bin" ) else () set(LASZIP_DLL "Dummy Value so that handle args does not fail") endif () message(DEBUG "LASZIP_LIBRARY: ${LASZIP_LIBRARY}") message(DEBUG "LASZIP_DLL: ${LASZIP_DLL}") find_package_handle_standard_args(LASzip REQUIRED_VARS LASZIP_LIBRARY LASZIP_INCLUDE_DIR LASZIP_DLL HANDLE_COMPONENTS ) if (LASzip_FOUND) mark_as_advanced(LASZIP_LIBRARY LASZIP_INCLUDE_DIR) endif () if (LASzip_FOUND AND NOT TARGET LASzip::LASzip) add_library(LASzip::LASzip SHARED IMPORTED) if (WIN32) set_target_properties(LASzip::LASzip PROPERTIES IMPORTED_LOCATION ${LASZIP_DLL} IMPORTED_IMPLIB ${LASZIP_LIBRARY} ) else () set_target_properties(LASzip::LASzip PROPERTIES IMPORTED_LOCATION ${LASZIP_LIBRARY}) endif () target_include_directories(LASzip::LASzip INTERFACE ${LASZIP_INCLUDE_DIR}) endif ()tmontaigu-laszip-python-12d2474/cmake/PythonEnvHelper.cmake000066400000000000000000000022621443545442100237750ustar00rootroot00000000000000macro(ensure_pybind11_cmake_module_is_in_path) execute_process( COMMAND "${PYTHON_EXECUTABLE}" "-m" "pybind11" "--cmake" RESULT_VARIABLE _PYTHON_SUCCESS OUTPUT_VARIABLE PYBIND11_CMAKE_MODULES_PATH ) if(_PYTHON_SUCCESS MATCHES 0) string(REGEX REPLACE "\n" "" PYBIND11_CMAKE_MODULES_PATH ${PYBIND11_CMAKE_MODULES_PATH} ) list(INSERT CMAKE_PREFIX_PATH 0 "${PYBIND11_CMAKE_MODULES_PATH}") else() message( WARNING "Failed to get pybind11 cmake prefix path ${_PYTHON_SUCCESS}" ) endif() endmacro() macro(set_python_executable_from_current_venv) if(WIN32) if(DEFINED ENV{CONDA_PREFIX}) list(INSERT CMAKE_PREFIX_PATH 0 "$ENV{CONDA_PREFIX}/Library/share/cmake") set(PYTHON_EXECUTABLE "$ENV{CONDA_PREFIX}/python.exe") elseif(DEFINED ENV{VIRTUAL_ENV}) set(PYTHON_EXECUTABLE "$ENV{VIRTUAL_ENV}/Scripts/python.exe") endif() else() if(DEFINED ENV{CONDA_PREFIX}) set(PYTHON_EXECUTABLE "$ENV{CONDA_PREFIX}/bin/python") elseif(DEFINED ENV{VIRTUAL_ENV}) set(PYTHON_EXECUTABLE "$ENV{VIRTUAL_ENV}/bin/python") else() set(PYTHON_EXECUTABLE "python3") endif() endif() endmacro() tmontaigu-laszip-python-12d2474/pyproject.toml000066400000000000000000000006511443545442100215350ustar00rootroot00000000000000[build-system] requires = [ "scikit-build-core>=0.1.5", "cmake>=3.20", "ninja", "pybind11>=2.10", ] build-backend = "scikit_build_core.build" [project] name = "laszip" version = "0.2.3" description = "Bindings for LASzip made with pybind11" readme = "Readme.md" authors = [ { name = "Thomas Montaigu", email = "thomas.montaigu@laposte.net" } ] license = {file = "License.txt"} requires-python = ">= 3.7" tmontaigu-laszip-python-12d2474/scripts/000077500000000000000000000000001443545442100203065ustar00rootroot00000000000000tmontaigu-laszip-python-12d2474/scripts/build-wheels-linux.sh000066400000000000000000000016521443545442100243670ustar00rootroot00000000000000set -e function install_laszip() { version="$1" git clone -b "$version" https://github.com/laszip/laszip mkdir laszip-build cmake -B laszip-build -DCMAKE_BUILD_TYPE=Release laszip cmake --build laszip-build cmake --install laszip-build } function compile_wheels() { for PYBIN in /opt/python/*/bin; do is_greater_or_eq_than_3_7=$("${PYBIN}/python" -c 'import sys;v = sys.version_info;print(v.major == 3 and v.minor >= 7)') if [[ "$is_greater_or_eq_than_3_7" == "True" ]]; then "${PYBIN}/pip" wheel --no-deps . -w wheelhouse fi done } function repair_wheel() { wheel="$1" if ! auditwheel show "$wheel"; then echo "Skipping non-platform wheel $wheel" else auditwheel repair "$wheel" --plat "$PLAT" -w wheelhouse fi } function repair_wheels() { for wheel in wheelhouse/*.whl; do repair_wheel "$wheel" done } install_laszip 3.4.3 cd /laszip-python compile_wheels repair_wheels tmontaigu-laszip-python-12d2474/scripts/delocate-wheel.sh000066400000000000000000000010011443545442100235140ustar00rootroot00000000000000#!/bin/sh set -e # Taken from # https://github.com/matthew-brett/delocate/issues/72#issuecomment-623070388 package_name="laszip" origindir=$(pwd) whl_path="$origindir/$(ls dist/*.whl | head -1)" echo "The wheel is $whl_path" delocate-listdeps --depending "$whl_path" cd $(mktemp -d) unzip "$whl_path" delocate-path -L "$package_name".dylibs . wheel=$(basename "$whl_path") zip -r "$wheel" * mv "$wheel" "$whl_path" tempdir=$(pwd) cd "$origindir" rm -rf "$tempdir" delocate-listdeps --depending "$whl_path" tmontaigu-laszip-python-12d2474/src/000077500000000000000000000000001443545442100174065ustar00rootroot00000000000000tmontaigu-laszip-python-12d2474/src/lasunzipper.cpp000066400000000000000000000101011443545442100224570ustar00rootroot00000000000000#include "lasunzipper.h" #include "laszip_error.h" #include #include LasUnZipper::LasUnZipper(py::object &file_obj) : LasUnZipper(file_obj, laszip_DECOMPRESS_SELECTIVE_ALL) {} LasUnZipper::LasUnZipper(py::object &file_obj, laszip_U32 decompression_selection) : m_ibuf(file_obj), m_input_stream(&m_ibuf) { laszip_BOOL is_compressed; if (laszip_create(&m_reader)) { throw laszip_error("Failed to create the reader"); } if (laszip_decompress_selective(m_reader, decompression_selection)) { throw laszip_error::last_error(m_reader); }; if (laszip_open_reader_stream(m_reader, m_input_stream, &is_compressed)) { throw laszip_error::last_error(m_reader); } if (laszip_get_header_pointer(m_reader, &m_header)) { throw laszip_error::last_error(m_reader); } if (laszip_get_point_pointer(m_reader, &m_point)) { throw laszip_error::last_error(m_reader); } laszip_BOOL dont_compress = 0; laszip_BOOL dont_write_header = 1; if (laszip_create(&m_writer)) { throw laszip_error("Failed to create the reader"); } if (laszip_set_header(m_writer, m_header)) { throw laszip_error::last_error(m_writer); } if (laszip_open_writer_stream(m_writer, m_output_stream, dont_compress, dont_write_header)) { throw laszip_error::last_error(m_writer); } } void LasUnZipper::decompress_into(py::buffer &buffer) { if (m_header->point_data_record_length == 0) { return; } py::buffer_info buf_info = buffer.request(); if (buf_info.itemsize != sizeof(char)) { throw std::invalid_argument("Buffer must be byte buffer"); } if (buf_info.ndim != 1) { throw std::invalid_argument("Buffer must be one dimensional"); } size_t num_points_to_read = buf_info.size / static_cast(m_header->point_data_record_length); // How many bytes the `m_output_stream` can store size_t max_bytes_in_output_stream = std::numeric_limits::max(); size_t max_points_before_filling_stream = max_bytes_in_output_stream / static_cast(m_header->point_data_record_length); auto *out_ptr = static_cast(buf_info.ptr); while (num_points_to_read != 0) { py::ssize_t num_points_for_this_iter = std::min(num_points_to_read, max_points_before_filling_stream); for (py::ssize_t i = 0; i < num_points_for_this_iter; ++i) { if (laszip_read_point(m_reader)) { throw laszip_error::last_error(m_reader); } if (laszip_set_point(m_writer, m_point)) { throw laszip_error::last_error(m_writer); } if (laszip_write_point(m_writer)) { throw laszip_error::last_error(m_writer); } } num_points_to_read -= num_points_for_this_iter; size_t bytes_read = num_points_for_this_iter * static_cast(m_header->point_data_record_length); m_output_stream.read(out_ptr, bytes_read); m_output_stream.seekg(0, std::ios_base::beg); m_output_stream.seekp(0, std::ios_base::beg); out_ptr += bytes_read; } } void LasUnZipper::close() { if (!m_closed) { m_closed = true; if (laszip_close_reader(m_reader)) { throw laszip_error::last_error(m_reader); } if (laszip_close_writer(m_writer)) { throw laszip_error::last_error(m_writer); } } } LasUnZipper::~LasUnZipper() { if (!m_closed) { // Destructors should not throw laszip_close_reader(m_reader); laszip_close_reader(m_writer); } laszip_destroy(m_reader); laszip_destroy(m_writer); } const laszip_header &LasUnZipper::header() const { assert(m_header); return *m_header; } void LasUnZipper::seek(laszip_I64 index) { if (laszip_seek_point(m_reader, index)) { throw laszip_error::last_error(m_reader); } } tmontaigu-laszip-python-12d2474/src/lasunzipper.h000066400000000000000000000015321443545442100221340ustar00rootroot00000000000000#ifndef LASZIP_BIND_LASUNZIPPER_H #define LASZIP_BIND_LASUNZIPPER_H #include "python_istreambuf.h" #include #include #include namespace py = pybind11; class LasUnZipper { public: explicit LasUnZipper(py::object &file_obj); LasUnZipper(py::object &file_obj, laszip_U32 decompression_selection); void decompress_into(py::buffer &buffer); void seek(laszip_I64 index); const laszip_header &header() const; void close(); ~LasUnZipper(); private: python_istreambuf m_ibuf; std::istream m_input_stream; std::stringstream m_output_stream; bool m_closed{false}; laszip_POINTER m_reader{nullptr}; laszip_POINTER m_writer{nullptr}; laszip_header *m_header{nullptr}; laszip_point *m_point{nullptr}; }; #endif // LASZIP_BIND_LASUNZIPPER_H tmontaigu-laszip-python-12d2474/src/laszip.cpp000066400000000000000000000436171443545442100214270ustar00rootroot00000000000000#include "lasunzipper.h" #include "laszip_error.h" #include "laszipper.h" #include "python_istreambuf.h" #include "python_ostreambuf.h" #include #include #include #include #include namespace py = pybind11; using namespace pybind11::literals; void no_op_delete(void *) {} template py::array_t wrap_as_py_array(T *array, size_t len) { auto capsule = py::capsule(array, no_op_delete); return py::array(len, array, capsule); } class LasZipDll { public: LasZipDll() { if (laszip_create(&m_laszipPtr)) { throw laszip_error("Failed to create laszip pointer"); } } bool open_reader(const char *path) { laszip_BOOL is_compressed; if (laszip_open_reader(m_laszipPtr, path, &is_compressed)) { throw laszip_error::last_error(m_laszipPtr); } m_readerIsOpen = true; return is_compressed == 1; } bool open_reader(py::object &file_object) { m_inputStreamBuf = std::make_unique(file_object); m_inputStream = std::make_unique(m_inputStreamBuf.get()); laszip_BOOL is_compressed; if (laszip_open_reader_stream(m_laszipPtr, *m_inputStream, &is_compressed)) { throw laszip_error::last_error(m_laszipPtr); } m_readerIsOpen = true; return is_compressed; } void open_writer(const char *path, bool compress) { auto do_compress = static_cast(compress); if (laszip_open_writer(m_laszipPtr, path, do_compress)) { throw laszip_error::last_error(m_laszipPtr); } m_writerIsOpen = true; } void open_writer(py::object &file_object, bool compress, bool do_not_write_header = false) { auto do_compress = static_cast(compress); m_outputStreamBuf = std::make_unique(file_object); m_outputStream = std::make_unique(m_outputStreamBuf.get()); if (laszip_open_writer_stream( m_laszipPtr, *m_outputStream, do_compress, static_cast(do_not_write_header))) { throw laszip_error::last_error(m_laszipPtr); } m_writerIsOpen = true; } laszip_header *header() { laszip_header *header{nullptr}; if (laszip_get_header_pointer(m_laszipPtr, &header)) { throw laszip_error::last_error(m_laszipPtr); } return header; } laszip_point *point() { laszip_point *point{nullptr}; if (laszip_get_point_pointer(m_laszipPtr, &point)) { throw laszip_error::last_error(m_laszipPtr); } return point; } void read_point() { if (laszip_read_point(m_laszipPtr)) { throw laszip_error::last_error(m_laszipPtr); } } void set_header(const laszip_header &header) { if (laszip_set_header(m_laszipPtr, &header)) { throw laszip_error::last_error(m_laszipPtr); } } void set_point_type_and_size(laszip_U8 point_type, laszip_U16 point_size) { if (laszip_set_point_type_and_size(m_laszipPtr, point_size, point_size)) { throw laszip_error::last_error(m_laszipPtr); } } void set_point(const laszip_point &point) { if (laszip_set_point(m_laszipPtr, &point)) { throw laszip_error::last_error(m_laszipPtr); } } void write_point() { if (laszip_write_point(m_laszipPtr)) { throw laszip_error::last_error(m_laszipPtr); } } void update_inventory() { if (laszip_update_inventory(m_laszipPtr)) { throw laszip_error::last_error(m_laszipPtr); } } const char *get_warning() { char *warning; if (laszip_get_warning(m_laszipPtr, &warning)) { throw laszip_error::last_error(m_laszipPtr); } return warning; } void close_reader() { if (!m_readerIsOpen) { return; } if (laszip_close_reader(m_laszipPtr)) { throw laszip_error::last_error(m_laszipPtr); } m_readerIsOpen = false; } void close_writer() { if (!m_writerIsOpen) { return; } if (laszip_close_writer(m_laszipPtr)) { throw laszip_error::last_error(m_laszipPtr); } m_writerIsOpen = false; } ~LasZipDll() { if (m_readerIsOpen) { laszip_close_reader(m_laszipPtr); } if (m_writerIsOpen) { laszip_close_writer(m_laszipPtr); } if (m_outputStreamBuf) { m_outputStreamBuf->pubsync(); } laszip_destroy(m_laszipPtr); } private: laszip_POINTER m_laszipPtr{nullptr}; bool m_readerIsOpen{false}; std::unique_ptr m_inputStreamBuf{nullptr}; std::unique_ptr m_inputStream{nullptr}; bool m_writerIsOpen{false}; std::unique_ptr m_outputStreamBuf{nullptr}; std::unique_ptr m_outputStream{nullptr}; }; PYBIND11_MODULE(laszip, m) { py::register_exception(m, "LaszipError"); m.attr("DECOMPRESS_SELECTIVE_ALL") = laszip_DECOMPRESS_SELECTIVE_ALL; m.attr("DECOMPRESS_SELECTIVE_CHANNEL_RETURNS_XY") = laszip_DECOMPRESS_SELECTIVE_CHANNEL_RETURNS_XY; m.attr("DECOMPRESS_SELECTIVE_Z") = laszip_DECOMPRESS_SELECTIVE_Z; m.attr("DECOMPRESS_SELECTIVE_CLASSIFICATION") = laszip_DECOMPRESS_SELECTIVE_CLASSIFICATION; m.attr("DECOMPRESS_SELECTIVE_FLAGS") = laszip_DECOMPRESS_SELECTIVE_FLAGS; m.attr("DECOMPRESS_SELECTIVE_INTENSITY") = laszip_DECOMPRESS_SELECTIVE_INTENSITY; m.attr("DECOMPRESS_SELECTIVE_SCAN_ANGLE") = laszip_DECOMPRESS_SELECTIVE_SCAN_ANGLE; m.attr("DECOMPRESS_SELECTIVE_USER_DATA") = laszip_DECOMPRESS_SELECTIVE_USER_DATA; m.attr("DECOMPRESS_SELECTIVE_POINT_SOURCE") = laszip_DECOMPRESS_SELECTIVE_POINT_SOURCE; m.attr("DECOMPRESS_SELECTIVE_GPS_TIME") = laszip_DECOMPRESS_SELECTIVE_GPS_TIME; m.attr("DECOMPRESS_SELECTIVE_RGB") = laszip_DECOMPRESS_SELECTIVE_RGB; m.attr("DECOMPRESS_SELECTIVE_NIR") = laszip_DECOMPRESS_SELECTIVE_NIR; m.attr("DECOMPRESS_SELECTIVE_WAVEPACKET") = laszip_DECOMPRESS_SELECTIVE_WAVEPACKET; m.attr("DECOMPRESS_SELECTIVE_BYTE0") = laszip_DECOMPRESS_SELECTIVE_BYTE0; m.attr("DECOMPRESS_SELECTIVE_BYTE1") = laszip_DECOMPRESS_SELECTIVE_BYTE1; m.attr("DECOMPRESS_SELECTIVE_BYTE2") = laszip_DECOMPRESS_SELECTIVE_BYTE2; m.attr("DECOMPRESS_SELECTIVE_BYTE3") = laszip_DECOMPRESS_SELECTIVE_BYTE3; m.attr("DECOMPRESS_SELECTIVE_BYTE4") = laszip_DECOMPRESS_SELECTIVE_BYTE4; m.attr("DECOMPRESS_SELECTIVE_BYTE5") = laszip_DECOMPRESS_SELECTIVE_BYTE5; m.attr("DECOMPRESS_SELECTIVE_BYTE6") = laszip_DECOMPRESS_SELECTIVE_BYTE6; m.attr("DECOMPRESS_SELECTIVE_BYTE7") = laszip_DECOMPRESS_SELECTIVE_BYTE7; m.attr("DECOMPRESS_SELECTIVE_EXTRA_BYTES") = laszip_DECOMPRESS_SELECTIVE_EXTRA_BYTES; m.attr("DECOMPRESS_SELECTIVE_ALL") = laszip_DECOMPRESS_SELECTIVE_ALL; py::class_(m, "LasUnZipper") .def(py::init(), "file_object"_a) .def(py::init(), "file_object"_a, "decompression_selection"_a) .def_property_readonly("header", &LasUnZipper::header) .def("decompress_into", &LasUnZipper::decompress_into, "buffer"_a) .def("seek", &LasUnZipper::seek, "index"_a) .def("close", &LasUnZipper::close); py::class_(m, "LasZipper") .def(py::init()) .def_property_readonly("header", &LasZipper::header) .def("compress", &LasZipper::compress) .def("done", &LasZipper::done); py::class_(m, "LasZipDll") .def(py::init<>()) .def("open_reader", (bool (LasZipDll::*)(const char *))(&LasZipDll::open_reader)) .def("open_reader", (bool (LasZipDll::*)(py::object &))(&LasZipDll::open_reader)) .def("header", &LasZipDll::header, py::return_value_policy::reference) .def("point", &LasZipDll::point, py::return_value_policy::reference) .def("read_point", &LasZipDll::read_point) .def("close_reader", &LasZipDll::close_reader) .def("close_writer", &LasZipDll::close_writer) .def("set_header", &LasZipDll::set_header) .def("get_warning", &LasZipDll::get_warning) .def("set_point_type_and_size", &LasZipDll::set_point_type_and_size) .def("set_point", &LasZipDll::set_point) .def("write_point", &LasZipDll::write_point) .def("update_inventory", &LasZipDll::update_inventory); py::class_(m, "LasZipHeader") .def(py::init<>()) .def_readwrite("file_source_ID", &laszip_header::file_source_ID) .def_readwrite("global_encoding", &laszip_header::global_encoding) .def_readwrite("project_ID_GUID_data_1", &laszip_header::project_ID_GUID_data_1) .def_readwrite("project_ID_GUID_data_2", &laszip_header::project_ID_GUID_data_2) .def_readwrite("project_ID_GUID_data_3", &laszip_header::project_ID_GUID_data_3) .def_property( "project_ID_GUID_data_4", [](const laszip_header &header) { return py::str(header.project_ID_GUID_data_4, 8); }, [](laszip_header &header, const std::string &new_value) { if (new_value.size() != 8) { throw std::invalid_argument("project_ID_GUID_data_4 must be 8 bytes long"); } std::copy(new_value.begin(), new_value.end(), header.project_ID_GUID_data_4); }) .def_readwrite("version_major", &laszip_header::version_major) .def_readwrite("version_minor", &laszip_header::version_minor) .def_property( "system_identifier", [](const laszip_header &header) { return py::str(header.system_identifier, 32); }, [](laszip_header &header, const std::string &new_value) { if (new_value.size() > 32) { throw std::invalid_argument("system_identifier cannot exceed 32 bytes"); } std::fill(header.system_identifier, header.system_identifier + 32, '\0'); std::copy(new_value.begin(), new_value.end(), header.system_identifier); }) .def_property( "generating_software", [](const laszip_header &header) { return py::str(header.generating_software, 32); }, [](laszip_header &header, const std::string &new_value) { if (new_value.size() > 32) { throw std::invalid_argument("generating_software cannot exceed 32 bytes"); } std::fill(header.generating_software, header.generating_software + 32, '\0'); std::copy(new_value.begin(), new_value.end(), header.generating_software); }) .def_readwrite("file_creation_day", &laszip_header::file_creation_day) .def_readwrite("file_creation_year", &laszip_header::file_creation_year) .def_readwrite("header_size", &laszip_header::header_size) .def_readwrite("offset_to_point_data", &laszip_header::offset_to_point_data) .def_readwrite("number_of_variable_length_records", &laszip_header::number_of_variable_length_records) .def_readwrite("point_data_format", &laszip_header::point_data_format) .def_readwrite("point_data_record_length", &laszip_header::point_data_record_length) .def_readwrite("number_of_point_records", &laszip_header::number_of_point_records) .def_property_readonly("number_of_points_by_return", [](const laszip_header &header) { return wrap_as_py_array(header.number_of_points_by_return, 5); }) .def_readwrite("x_scale_factor", &laszip_header::x_scale_factor) .def_readwrite("y_scale_factor", &laszip_header::y_scale_factor) .def_readwrite("z_scale_factor", &laszip_header::z_scale_factor) .def_readwrite("x_offset", &laszip_header::x_offset) .def_readwrite("y_offset", &laszip_header::y_offset) .def_readwrite("z_offset", &laszip_header::z_offset) .def_readwrite("max_x", &laszip_header::max_x) .def_readwrite("min_x", &laszip_header::min_x) .def_readwrite("max_y", &laszip_header::max_y) .def_readwrite("min_y", &laszip_header::min_y) .def_readwrite("max_z", &laszip_header::max_z) .def_readwrite("min_z", &laszip_header::min_z) // 1.3 things .def_readwrite("start_of_waveform_data_packet_record", &laszip_header::start_of_waveform_data_packet_record) // 1.4 .def_readwrite("start_of_first_extended_variable_length_record", &laszip_header::start_of_first_extended_variable_length_record) .def_readwrite("number_of_extended_variable_length_records", &laszip_header::number_of_extended_variable_length_records) .def_readwrite("extended_number_of_point_records", &laszip_header::extended_number_of_point_records) .def_property_readonly("extended_number_of_points_by_return", [](const laszip_header &header) { return wrap_as_py_array(header.extended_number_of_points_by_return, 15); }); py::class_(m, "LasZipPoint") .def(py::init<>()) .def_readwrite("X", &laszip_point::X) .def_readwrite("Y", &laszip_point::Y) .def_readwrite("Z", &laszip_point::Z) .def_readwrite("intensity", &laszip_point::intensity) .def_property( "return_number", [](const laszip_point &point) { return point.return_number; }, [](laszip_point &point, int value) { point.return_number = value; }) .def_property( "number_of_returns", [](const laszip_point &point) { return point.number_of_returns; }, [](laszip_point &point, int value) { point.number_of_returns = value; }) .def_property( "scan_direction_flag", [](const laszip_point &point) { return point.scan_direction_flag; }, [](laszip_point &point, int value) { point.scan_direction_flag = value; }) .def_property( "edge_of_flight_line", [](const laszip_point &point) { return point.edge_of_flight_line; }, [](laszip_point &point, int value) { point.edge_of_flight_line = value; }) .def_property( "classification", [](const laszip_point &point) { return point.classification; }, [](laszip_point &point, int value) { point.classification = value; }) .def_property( "synthetic_flag", [](const laszip_point &point) { return point.synthetic_flag; }, [](laszip_point &point, int value) { point.synthetic_flag = value; }) .def_property( "keypoint_flag", [](const laszip_point &point) { return point.keypoint_flag; }, [](laszip_point &point, int value) { point.keypoint_flag = value; }) .def_property( "withheld_flag", [](const laszip_point &point) { return point.withheld_flag; }, [](laszip_point &point, int value) { point.withheld_flag = value; }) .def_readwrite("scan_angle_rank", &laszip_point::scan_angle_rank) .def_readwrite("user_data", &laszip_point::user_data) .def_readwrite("point_source_ID", &laszip_point::point_source_ID) .def_readwrite("extended_scan_angle", &laszip_point::extended_scan_angle) .def_property( "extended_point_type", [](const laszip_point &point) { return point.extended_point_type; }, [](laszip_point &point, int value) { point.extended_point_type = value; }) .def_property( "extended_scanner_channel", [](const laszip_point &point) { return point.extended_scanner_channel; }, [](laszip_point &point, int value) { point.extended_scanner_channel = value; }) .def_property( "extended_classification_flags", [](const laszip_point &point) { return point.extended_classification_flags; }, [](laszip_point &point, int value) { point.extended_classification_flags = value; }) .def_property( "extended_classification", [](const laszip_point &point) { return point.extended_classification; }, [](laszip_point &point, int value) { point.extended_classification = value; }) .def_property( "extended_return_number", [](const laszip_point &point) { return point.extended_return_number; }, [](laszip_point &point, int value) { point.extended_return_number = value; }) .def_property( "extended_number_of_returns", [](const laszip_point &point) { return point.extended_number_of_returns; }, [](laszip_point &point, int value) { point.extended_number_of_returns = value; }) .def_readwrite("gps_time", &laszip_point::gps_time) .def_property_readonly("rgb", [](const laszip_point &point) { return wrap_as_py_array(point.rgb, 4); }) .def_property_readonly( "wave_packet", [](const laszip_point &point) { return wrap_as_py_array(point.wave_packet, 4); }) .def_property_readonly("extra_bytes", [](const laszip_point &point) { return wrap_as_py_array(point.extra_bytes, point.num_extra_bytes); }); m.def("get_version", []() { laszip_U8 major{0}, minor{0}; laszip_U16 revision{0}; laszip_U32 build{0}; laszip_get_version(&major, &minor, &revision, &build); return py::make_tuple(major, minor, revision, build); }); } tmontaigu-laszip-python-12d2474/src/laszip/000077500000000000000000000000001443545442100207105ustar00rootroot00000000000000tmontaigu-laszip-python-12d2474/src/laszip/__init__.py000066400000000000000000000000261443545442100230170ustar00rootroot00000000000000from .laszip import * tmontaigu-laszip-python-12d2474/src/laszip_error.h000066400000000000000000000007311443545442100222730ustar00rootroot00000000000000#ifndef LASZIP_BIND_LASZIP_ERROR_H #define LASZIP_BIND_LASZIP_ERROR_H #include class laszip_error : public std::runtime_error { public: static laszip_error last_error(laszip_POINTER laszip_ptr) { char *error_msg; laszip_get_error(laszip_ptr, &error_msg); return laszip_error(error_msg); } explicit laszip_error(const char *message) : std::runtime_error(message) {} }; #endif // LASZIP_BIND_LASZIP_ERROR_H tmontaigu-laszip-python-12d2474/src/laszipper.cpp000066400000000000000000000074201443545442100221260ustar00rootroot00000000000000#include "laszipper.h" #include "laszip_error.h" LasZipper::LasZipper(py::object &file_obj, py::bytes &header_bytes) : m_is(), m_b(file_obj), m_output_stream(&m_b) { // TODO avoid this copy std::string data(header_bytes); m_is.write(data.data(), data.size()); if (laszip_create(&m_reader)) { throw laszip_error("Failed to create the reader"); } laszip_BOOL is_compresesd; if (laszip_open_reader_stream(m_reader, m_is, &is_compresesd)) { throw laszip_error::last_error(m_reader); } if (laszip_get_header_pointer(m_reader, &m_header)) { throw laszip_error::last_error(m_reader); } if (laszip_get_point_pointer(m_reader, &m_point)) { throw laszip_error::last_error(m_reader); } if (laszip_create(&m_writer)) { throw laszip_error("Failed to create the writer"); } if (laszip_set_header(m_writer, m_header)) { throw laszip_error::last_error(m_writer); } if (laszip_open_writer_stream(m_writer, m_output_stream, 1, 0)) { throw laszip_error::last_error(m_writer); } } size_t LasZipper::compress(py::buffer &buffer) { if (m_header->point_data_record_length == 0) { return 0; } py::buffer_info buf_info = buffer.request(); if (buf_info.itemsize != sizeof(char)) { throw std::invalid_argument("Buffer must be byte buffer"); } if (buf_info.ndim != 1) { throw std::invalid_argument("Buffer must be one dimensional"); } size_t num_points_in_buffer = buf_info.size / m_header->point_data_record_length; size_t max_bytes_in_stream = std::numeric_limits::max(); size_t max_points_before_filling_stream = max_bytes_in_stream / static_cast(m_header->point_data_record_length); auto *in_ptr = static_cast(buf_info.ptr); while (num_points_in_buffer != 0) { m_is.seekp(0); m_is.seekg(0); py::ssize_t num_points_for_this_iter = std::min(num_points_in_buffer, max_points_before_filling_stream); size_t num_bytes_for_this_iter = num_points_for_this_iter * static_cast(m_header->point_data_record_length); m_is.write(in_ptr, num_bytes_for_this_iter); for (size_t i{0}; i < num_points_for_this_iter; ++i) { if (laszip_read_point(m_reader)) { throw laszip_error::last_error(m_reader); } if (laszip_set_point(m_writer, m_point)) { throw laszip_error::last_error(m_writer); } if (laszip_update_inventory(m_writer)) { throw laszip_error::last_error(m_writer); } if (laszip_write_point(m_writer)) { throw laszip_error::last_error(m_writer); } } in_ptr += num_points_for_this_iter; num_points_in_buffer -= num_points_for_this_iter; } return num_points_in_buffer; } void LasZipper::done() { if (!m_closed) { if (laszip_close_reader(m_reader)) { throw laszip_error::last_error(m_reader); } if (laszip_close_writer(m_writer)) { throw laszip_error::last_error(m_writer); } // FIXME I don't think we should have to call this // maybe something is wrong in python_ostream ? m_b.pubsync(); } } LasZipper::~LasZipper() { if (!m_closed) { // Destructors should not throw laszip_close_reader(m_reader); laszip_close_reader(m_writer); } laszip_destroy(m_reader); laszip_destroy(m_writer); } const laszip_header &LasZipper::header() const { assert(m_header); return *m_header; } tmontaigu-laszip-python-12d2474/src/laszipper.h000066400000000000000000000013461443545442100215740ustar00rootroot00000000000000#ifndef LASZIP_BIND_LASZIPPER_H #define LASZIP_BIND_LASZIPPER_H #include #include #include "python_ostreambuf.h" #include namespace py = pybind11; class LasZipper { public: explicit LasZipper(py::object &file_obj, py::bytes &header_bytes); size_t compress(py::buffer &buffer); void done(); ~LasZipper(); const laszip_header &header() const; private: python_ostreambuf m_b; std::ostream m_output_stream; std::stringstream m_is; bool m_closed{false}; laszip_POINTER m_reader{nullptr}; laszip_POINTER m_writer{nullptr}; laszip_header *m_header{nullptr}; laszip_point *m_point{nullptr}; }; #endif // LASZIP_BIND_LASZIPPER_H tmontaigu-laszip-python-12d2474/src/python_istreambuf.h000066400000000000000000000063401443545442100233240ustar00rootroot00000000000000#ifndef LASZIP_BIND_PYTHON_ISTREAMBUF_H #define LASZIP_BIND_PYTHON_ISTREAMBUF_H #include #include #include namespace py = pybind11; #define DEFAULT_BUFFER_SIZE 8192 class python_istreambuf : public std::basic_streambuf { private: typedef std::basic_streambuf base_t; public: typedef base_t::char_type char_type; typedef base_t::int_type int_type; typedef base_t::pos_type pos_type; typedef base_t::off_type off_type; typedef base_t::traits_type traits_type; explicit python_istreambuf(const py::object &file_obj) : m_read_fn(file_obj.attr("read").cast()), m_tell_fn(file_obj.attr("tell").cast()), m_seek_fn(file_obj.attr("seek").cast()), m_read_buf(), m_pos_in_file(m_tell_fn().cast()) { } protected: int_type underflow() override { py::bytes data_read = m_read_fn(DEFAULT_BUFFER_SIZE).cast(); // TODO this copies data (the conversiion to std::string) // we can probably avoid this by using the buffer directly m_read_buf = std::move(std::string(data_read)); m_pos_in_file += m_read_buf.size(); setg(&m_read_buf[0], &m_read_buf[0], &m_read_buf[0] + m_read_buf.size()); if (m_read_buf.empty()) { return traits_type::eof(); } return traits_type::to_int_type(m_read_buf[0]); } std::streamsize showmanyc() override { int_type status = underflow(); if (status == traits_type::eof()) { return -1; } return egptr() - gptr(); } int sync() override { int result = 0; if (gptr() && gptr() < egptr()) { m_seek_fn(gptr() - egptr(), 1); } return result; } pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode which) override { assert(which == std::ios_base::in); if (!gptr()) { if (traits_type::eq_int_type(underflow(), traits_type::eof())) { return traits_type::eof(); } } int whence; switch (way) { case std::ios_base::beg: whence = 0; break; case std::ios_base::cur: // Because the true pos in the python file obj // is: the pos you think it is + buffer_size // So we need to move the off to reflect that off -= egptr() - gptr(); whence = 1; break; case std::ios_base::end: whence = 2; break; default: return traits_type::eof(); } m_seek_fn(off, whence); off_type new_pos = m_tell_fn().cast(); underflow(); return new_pos; } pos_type seekpos(pos_type sp, std::ios_base::openmode which) override { return seekoff(sp, std::ios_base::beg, which); } private: py::function m_read_fn; py::function m_tell_fn; py::function m_seek_fn; std::string m_read_buf; off_type m_pos_in_file; }; #endif // LASZIP_BIND_PYTHON_ISTREAMBUF_H tmontaigu-laszip-python-12d2474/src/python_ostreambuf.h000066400000000000000000000057011443545442100233320ustar00rootroot00000000000000#ifndef LASZIP_BIND_PYTHON_OSTREAMBUF_H #define LASZIP_BIND_PYTHON_OSTREAMBUF_H #include namespace py = pybind11; #include class python_ostreambuf : public std::basic_streambuf { private: typedef std::basic_streambuf base_t; public: typedef base_t::char_type char_type; typedef base_t::int_type int_type; typedef base_t::pos_type pos_type; typedef base_t::off_type off_type; typedef base_t::traits_type traits_type; explicit python_ostreambuf(const py::object &file_obj) : m_write_fn(file_obj.attr("write").cast()), m_tell_fn(file_obj.attr("tell").cast()), m_seek_fn(file_obj.attr("seek").cast()), m_write_buf(), m_pos_in_file(m_tell_fn().cast()) { setp(&m_write_buf[0], &m_write_buf[0] + m_write_buf.size()); } protected: int_type overflow(int_type ch) override { py::bytes bytes_to_write(pbase(), pptr() - pbase()); std::size_t num_bytes_written = m_write_fn(bytes_to_write).cast(); assert(num_bytes_written == pptr() - pbase()); m_pos_in_file += num_bytes_written; if (!traits_type::eq_int_type(ch, traits_type::eof())) { char c = traits_type::to_char_type(ch); py::bytes b(&c, 1); m_write_fn(b); num_bytes_written++; } if (num_bytes_written > 0) { m_pos_in_file += num_bytes_written; setp(pbase(), epptr()); } return traits_type::eq_int_type(ch, traits_type::eof()) ? traits_type::not_eof(ch) : ch; } int sync() override { overflow(traits_type::eof()); return 0; } pos_type seekpos(pos_type off, std::ios_base::openmode which) override { return seekoff(off, std::ios_base::beg, which); } std::streamsize xsputn(const char *ptr, std::streamsize count) override { auto res = base_t::xsputn(ptr, count); return res; } pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode which) override { assert(which == std::ios_base::out); int whence; switch (way) { case std::ios_base::beg: whence = 0; break; case std::ios_base::cur: whence = 1; break; case std::ios_base::end: whence = 2; break; default: return traits_type::eof(); } overflow(traits_type::eof()); assert(pptr() - pbase() == 0); m_seek_fn(off, whence); off_type new_pos = m_tell_fn().cast(); return new_pos; } private: py::function m_write_fn; py::function m_tell_fn; py::function m_seek_fn; std::array m_write_buf; off_type m_pos_in_file; }; #endif // LASZIP_BIND_PYTHON_OSTREAMBUF_H