diff --git a/.github/workflows/python-bindings-linux.yml b/.github/workflows/python-bindings-linux.yml new file mode 100644 index 00000000..324e7a58 --- /dev/null +++ b/.github/workflows/python-bindings-linux.yml @@ -0,0 +1,60 @@ +name: Build Python Bindings on Linux + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + BUILD_TYPE: Release + +jobs: + build: + runs-on: ubuntu-22.04 + strategy: + matrix: + python-version: ["3.12"] + + steps: + - name: Load repository cache + run: sudo apt-get update --allow-releaseinfo-change + + - name: Install system libraries + run: sudo apt-get install -y --no-install-recommends libx11-dev libxi-dev libtbb-dev libegl1-mesa-dev libglu1-mesa-dev libopencv-dev + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Display Python version + run: python --version + + - name: Install CMake 4.0 + run: | + wget -O cmake.sh https://github.com/Kitware/CMake/releases/download/v4.0.0/cmake-4.0.0-linux-x86_64.sh + sudo sh cmake.sh --skip-license --prefix=/usr/local + cmake --version + + - name: Configure CMake with Python bindings + run: | + cmake -B ${{github.workspace}}/build_pybind \ + -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ + -DPYBIND=ON + + - name: Build Python bindings + run: cmake --build ${{github.workspace}}/build_pybind --target pybind_all --config ${{env.BUILD_TYPE}} + + - name: Test Python imports + working-directory: ${{github.workspace}}/build_pybind/bin/${{env.BUILD_TYPE}} + run: | + python -c "import sys; sys.path.insert(0, '.'); import core_py; print('core_py imported successfully')" + python -c "import sys; sys.path.insert(0, '.'); import lidar_odometry_py; print('lidar_odometry_py imported successfully')" + python -c "import sys; sys.path.insert(0, '.'); import multi_view_tls_registration_py; print('multi_view_tls_registration_py imported successfully')" + + diff --git a/.github/workflows/python-bindings-windows.yml b/.github/workflows/python-bindings-windows.yml new file mode 100644 index 00000000..967b0ca3 --- /dev/null +++ b/.github/workflows/python-bindings-windows.yml @@ -0,0 +1,56 @@ +name: Build Python Bindings on Windows + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + BUILD_TYPE: Release + +jobs: + build: + runs-on: windows-latest + strategy: + matrix: + python-version: ["3.12"] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Display Python version + run: python --version + + - name: Install CMake 4.0 on Windows + run: | + choco install cmake --version=4.0.0 -y + refreshenv + cmake --version + shell: pwsh + + - name: Configure CMake with Python bindings + run: | + cmake -B ${{github.workspace}}/build_pybind ` + -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ` + -DPYBIND=ON ` + shell: pwsh + + - name: Build Python bindings + run: cmake --build ${{github.workspace}}/build_pybind --target pybind_all --config ${{env.BUILD_TYPE}} + + - name: Test Python imports + working-directory: ${{github.workspace}}/build_pybind/bin/${{env.BUILD_TYPE}} + run: | + python -c "import sys; sys.path.insert(0, '.'); import core_py; print('core_py imported successfully')" + python -c "import sys; sys.path.insert(0, '.'); import lidar_odometry_py; print('lidar_odometry_py imported successfully')" + python -c "import sys; sys.path.insert(0, '.'); import multi_view_tls_registration_py; print('multi_view_tls_registration_py imported successfully')" + shell: pwsh + diff --git a/.gitignore b/.gitignore index bb696964..f8336b5f 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ imgui.ini /out/install/x64-Release/bin /out/install/x64-Release /out_old/install + diff --git a/apps/lidar_odometry_step_1/lidar_odometry_utils.h b/apps/lidar_odometry_step_1/lidar_odometry_utils.h index 81c0456f..f4f26d69 100644 --- a/apps/lidar_odometry_step_1/lidar_odometry_utils.h +++ b/apps/lidar_odometry_step_1/lidar_odometry_utils.h @@ -21,7 +21,9 @@ #include #include #include +#if WITH_GUI == 1 #include +#endif namespace fs = std::filesystem; @@ -134,7 +136,9 @@ struct LidarOdometryParams double total_length_of_calculated_trajectory = 0.0; NDTBucketMapType buckets_indoor; NDTBucketMapType buckets_outdoor; +#if WITH_GUI == 1 ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); +#endif bool use_removie_imu_bias_from_first_stationary_scan = false; diff --git a/apps/lidar_odometry_step_1/toml_io.cpp b/apps/lidar_odometry_step_1/toml_io.cpp index 867de64f..50be097c 100644 --- a/apps/lidar_odometry_step_1/toml_io.cpp +++ b/apps/lidar_odometry_step_1/toml_io.cpp @@ -13,12 +13,17 @@ bool TomlIO::SaveParametersToTomlFile(const std::string &filepath, LidarOdometry if (attribute == "in_out_params_indoor" || attribute == "in_out_params_outdoor") continue; +#if WITH_GUI == 1 if (attribute == "clear_color") { const ImVec4& c = params.clear_color; cat_tbl.insert(attribute, toml::array{ c.x, c.y, c.z, c.w }); continue; } +#else + if (attribute == "clear_color") + continue; // Skip GUI-only parameter when building without GUI +#endif // Check if it's a motion_model_correction parameter auto motion_it = MOTION_MODEL_CORRECTION_POINTERS.find(attribute); @@ -188,6 +193,7 @@ bool TomlIO::LoadParametersFromTomlFile(const std::string &filepath, LidarOdomet } }, it->second); } +#if WITH_GUI == 1 if (cat_tbl && cat_tbl->contains("clear_color")) { auto arr = cat_tbl->at("clear_color").as_array(); @@ -199,6 +205,7 @@ bool TomlIO::LoadParametersFromTomlFile(const std::string &filepath, LidarOdomet params.clear_color.w = static_cast(arr->at(3).as_floating_point()->get()); } } +#endif } read_grid_params(params.in_out_params_indoor, tbl["in_out_params_indoor"].as_table()); read_grid_params(params.in_out_params_outdoor, tbl["in_out_params_outdoor"].as_table()); diff --git a/pybind/CMakeLists.txt b/pybind/CMakeLists.txt index 451b10d6..df980fd9 100644 --- a/pybind/CMakeLists.txt +++ b/pybind/CMakeLists.txt @@ -45,6 +45,11 @@ if (MSVC) target_compile_options(multi_view_tls_registration_py PRIVATE /bigobj) endif() +add_custom_target(pybind_all + DEPENDS core_py lidar_odometry_py multi_view_tls_registration_py + COMMENT "Building all Python bindings" +) + target_sources(core_py PRIVATE ${CMAKE_SOURCE_DIR}/core/src/session.cpp @@ -134,33 +139,55 @@ if(WIN32) set_target_properties(lidar_odometry_py PROPERTIES SUFFIX ".pyd") set_target_properties(multi_view_tls_registration_py PROPERTIES SUFFIX ".pyd") else() - set_target_properties(core_py PROPERTIES SUFFIX ".so") - set_target_properties(lidar_odometry_py PROPERTIES SUFFIX ".so") - set_target_properties(multi_view_tls_registration_py PROPERTIES SUFFIX ".so") + set_target_properties(core_py PROPERTIES + PREFIX "" + SUFFIX ".so") + set_target_properties(lidar_odometry_py PROPERTIES + PREFIX "" + SUFFIX ".so") + set_target_properties(multi_view_tls_registration_py PROPERTIES + PREFIX "" + SUFFIX ".so") endif() set_target_properties(core_py PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/pybind" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/pybind" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/$" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/$" ) set_target_properties(lidar_odometry_py PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/pybind" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/pybind" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/$" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/$" ) set_target_properties(multi_view_tls_registration_py PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/pybind" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/pybind" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/$" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/$" ) -set(DLL_SOURCE_DIR "${CMAKE_BINARY_DIR}/bin/Release") -set(DLL_DEST_DIR "${CMAKE_SOURCE_DIR}/pybind/Release") - -file(GLOB DLL_FILES "${CMAKE_BINARY_DIR}/bin/Release/*.dll") -foreach(DLL ${DLL_FILES}) +if(WIN32) + add_custom_command( + TARGET core_py + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ + "${CMAKE_BINARY_DIR}/bin/$" + COMMAND_EXPAND_LISTS + ) + + add_custom_command( + TARGET lidar_odometry_py + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ + "${CMAKE_BINARY_DIR}/bin/$" + COMMAND_EXPAND_LISTS + ) + add_custom_command( - TARGET lidar_odometry_py POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DLL} "${CMAKE_SOURCE_DIR}/pybind/Release" + TARGET multi_view_tls_registration_py + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ + "${CMAKE_BINARY_DIR}/bin/$" + COMMAND_EXPAND_LISTS ) -endforeach() \ No newline at end of file +endif() + diff --git a/pybind/core_binding.cpp b/pybind/core_binding.cpp index e4e03f5b..8c99da1a 100644 --- a/pybind/core_binding.cpp +++ b/pybind/core_binding.cpp @@ -25,12 +25,14 @@ PYBIND11_MODULE(core_py, m) { // Bind WorkerData py::class_(m, "WorkerData") .def(py::init<>()) - .def_readwrite("intermediate_points", &WorkerData::intermediate_points) - .def_readwrite("original_points", &WorkerData::original_points) + .def_readwrite("intermediate_points_cache_file_name", &WorkerData::intermediate_points_cache_file_name) + .def_readwrite("original_points_cache_file_name", &WorkerData::original_points_cache_file_name) + .def_readwrite("original_points_to_save_cache_file_name", &WorkerData::original_points_to_save_cache_file_name) .def_readwrite("intermediate_trajectory", &WorkerData::intermediate_trajectory) .def_readwrite("intermediate_trajectory_motion_model", &WorkerData::intermediate_trajectory_motion_model) .def_readwrite("intermediate_trajectory_timestamps", &WorkerData::intermediate_trajectory_timestamps) .def_readwrite("imu_om_fi_ka", &WorkerData::imu_om_fi_ka) + .def_readwrite("show", &WorkerData::show) ; // Bind Session diff --git a/pybind/tls_registration_binding.cpp b/pybind/tls_registration_binding.cpp index 45ce5c4a..4878a52e 100644 --- a/pybind/tls_registration_binding.cpp +++ b/pybind/tls_registration_binding.cpp @@ -88,7 +88,7 @@ PYBIND11_MODULE(multi_view_tls_registration_py, m) { py::class_(m, "ICP") .def(py::init<>()) - .def_readwrite("search_radious", &ICP::search_radious) + .def_readwrite("search_radius", &ICP::search_radius) .def_readwrite("number_of_threads", &ICP::number_of_threads) .def_readwrite("number_of_iterations", &ICP::number_of_iterations) .def_readwrite("is_adaptive_robust_kernel", &ICP::is_adaptive_robust_kernel) @@ -96,7 +96,7 @@ PYBIND11_MODULE(multi_view_tls_registration_py, m) { py::class_(m, "RegistrationPlaneFeature") .def(py::init<>()) - .def_readwrite("search_radious", &RegistrationPlaneFeature::search_radious) + .def_readwrite("search_radius", &RegistrationPlaneFeature::search_radius) .def_readwrite("number_of_threads", &RegistrationPlaneFeature::number_of_threads) .def_readwrite("number_of_iterations", &RegistrationPlaneFeature::number_of_iterations) .def_readwrite("is_adaptive_robust_kernel", &RegistrationPlaneFeature::is_adaptive_robust_kernel); @@ -105,7 +105,7 @@ PYBIND11_MODULE(multi_view_tls_registration_py, m) { .def(py::init<>()) .def_readwrite("overlap_threshold", &PoseGraphSLAM::overlap_threshold) .def_readwrite("iterations", &PoseGraphSLAM::iterations) - .def_readwrite("search_radious", &PoseGraphSLAM::search_radious); + .def_readwrite("search_radius", &PoseGraphSLAM::search_radius); py::class_(m, "GlobalPose") .def(py::init<>())