Skip to content

Nanobind #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ jobs:
# run tests on both mac and ubuntu, across all python versions
test:
runs-on: ${{ matrix.os }}
# We turn off stub generations, as there is some strange bug on github actions
# where an editable install with stubs obscures the _cpp.so module and it can't be imported
env:
SKBUILD_CMAKE_DEFINE: EVALIO_PYTHON_STUBS=OFF
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
Expand All @@ -48,7 +53,7 @@ jobs:
python-version: ${{ matrix.python-version }}
enable-cache: true
- name: Install the project
run: uv sync --dev
run: uv sync --dev --verbose
- name: Run tests
run: uv run pytest -v

Expand All @@ -62,12 +67,6 @@ jobs:
steps:
- uses: actions/checkout@v4
name: Checkout
# If things fail, this allows us to ssh in to fix things
# - name: Setup tmate session
# uses: mxschmitt/action-tmate@v3
# if: runner.debug == '1'
# with:
# detached: true

# Let vcpkg store caches in github actions
- name: Export GitHub Actions cache environment variables
Expand Down
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include(FetchContent)

# Make build a release build unless otherwise specified
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
set_property(
CACHE CMAKE_BUILD_TYPE
PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo"
)
endif()

# ------------------------- Dependencies ------------------------- #
# Pull eigen if it's not available already
FetchContent_Declare(
Expand Down
58 changes: 37 additions & 21 deletions cpp/bindings/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,33 +35,49 @@ endif()
message("###############################################################")

# ------------------------- Make Python Bindings ------------------------- #
set(PYBIND11_FINDPYTHON ON)
find_package(
Python
3.11
REQUIRED
COMPONENTS Interpreter Development.Module
OPTIONAL_COMPONENTS Development.SABIModule
)

FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
GIT_TAG v2.13.6
FIND_PACKAGE_ARGS # uses find_package first, git if it fails
nanobind
GIT_REPOSITORY https://github.com/wjakob/nanobind
GIT_TAG v2.7.0
FIND_PACKAGE_ARGS
CONFIG # uses find_package first, git if it fails
)
FetchContent_MakeAvailable(pybind11)
FetchContent_MakeAvailable(nanobind)

pybind11_add_module(_cpp main.cpp)
# Add the Python module
nanobind_add_module(_cpp STABLE_ABI NB_STATIC main.cpp)
target_link_libraries(_cpp PRIVATE ${LIBS})
target_compile_definitions(_cpp PRIVATE ${DEF})
install(TARGETS _cpp DESTINATION evalio)

# handle stubs
function(module_subs MOD FILE)
message(STATUS "Adding ${MOD} stubs")
nanobind_add_stub(
${MOD}_stubs
INSTALL_TIME
MODULE evalio.${MOD}
OUTPUT evalio/_cpp/${FILE} # install directly to final location
PYTHON_PATH "."
)
endfunction()

# option for github actions
# see ci.yml
option(EVALIO_PYTHON_STUBS "Build Python bindings with stubs" ON)
if(EVALIO_PYTHON_STUBS)
module_subs(_cpp __init__.pyi)
module_subs(_cpp.pipelines pipelines.pyi)
module_subs(_cpp.types types.pyi)
endif()

# install licenses as well
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../licenses DESTINATION .)

# Build stubs
# TODO: Something about this breaks python 3.12 and 3.13 builds
# add_custom_command(
# TARGET _cpp
# POST_BUILD
# COMMAND
# "${Python_EXECUTABLE}" -m pybind11_stubgen _cpp -o
# "${CMAKE_CURRENT_BINARY_DIR}" --numpy-array-wrap-with-annotated
# WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
# COMMENT "Generating pybind11 stubs"
# VERBATIM
# )
# install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/_cpp DESTINATION evalio)
16 changes: 10 additions & 6 deletions cpp/bindings/main.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#include <pybind11/eigen.h>
#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <nanobind/nanobind.h>

#include "bindings/pipeline.h"
#include "bindings/pipelines/bindings.h"
#include "bindings/ros_pc2.h"
#include "bindings/types.h"
#include "evalio/bindings.h"

PYBIND11_MODULE(_cpp, m) {
namespace nb = nanobind;

NB_MODULE(_cpp, m) {
m.def(
"abi_tag", []() { return nb::detail::abi_tag(); },
"Get the ABI tag of the current module. Useful for debugging when adding "
"external pipelines.");

auto m_types = m.def_submodule(
"types",
"Common types used for conversion between datasets and pipelines.");
Expand Down
68 changes: 68 additions & 0 deletions cpp/bindings/pipeline.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#pragma once

#include <nanobind/eigen/dense.h>
#include <nanobind/nanobind.h>
#include <nanobind/operators.h>
#include <nanobind/stl/map.h>
#include <nanobind/stl/string.h>
#include <nanobind/stl/variant.h>
#include <nanobind/stl/vector.h>
#include <nanobind/trampoline.h>

#include "evalio/pipeline.h"

namespace nb = nanobind;
using namespace nb::literals;

namespace evalio {

class PyPipeline : public evalio::Pipeline {
public:
NB_TRAMPOLINE(Pipeline, 9);

// Getters
const evalio::SE3 pose() override { NB_OVERRIDE_PURE(pose); }
const std::vector<evalio::Point> map() override { NB_OVERRIDE_PURE(map); }

// Setters
void set_imu_params(evalio::ImuParams params) override {
NB_OVERRIDE_PURE(set_imu_params, params);
}
void set_lidar_params(evalio::LidarParams params) override {
NB_OVERRIDE_PURE(set_lidar_params, params);
}
void set_imu_T_lidar(evalio::SE3 T) override {
NB_OVERRIDE_PURE(set_imu_T_lidar, T);
}
void set_params(std::map<std::string, Param> params) override {
NB_OVERRIDE_PURE(set_params, params);
}

// Doers
void initialize() override { NB_OVERRIDE_PURE(initialize); }
void add_imu(evalio::ImuMeasurement mm) override {
NB_OVERRIDE_PURE(add_imu, mm);
}
std::vector<Point> add_lidar(evalio::LidarMeasurement mm) override {
NB_OVERRIDE_PURE(add_lidar, mm);
}
};

inline void makeBasePipeline(nb::module_ &m) {
nb::class_<evalio::Pipeline, PyPipeline>(m, "Pipeline")
.def(nb::init<>())
.def_static("name", &evalio::Pipeline::name)
.def_static("url", &evalio::Pipeline::url)
.def_static("default_params", &evalio::Pipeline::default_params)
.def("pose", &evalio::Pipeline::pose)
.def("map", &evalio::Pipeline::map)
.def("initialize", &evalio::Pipeline::initialize)
.def("add_imu", &evalio::Pipeline::add_imu, "mm"_a)
.def("add_lidar", &evalio::Pipeline::add_lidar, "mm"_a)
.def("set_params", &evalio::Pipeline::set_params, "params"_a)
.def("set_imu_params", &evalio::Pipeline::set_imu_params, "params"_a)
.def("set_lidar_params", &evalio::Pipeline::set_lidar_params, "params"_a)
.def("set_imu_T_lidar", &evalio::Pipeline::set_imu_T_lidar, "T"_a);
}

} // namespace evalio
19 changes: 8 additions & 11 deletions cpp/bindings/pipelines/bindings.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
#pragma once

#include <pybind11/eigen.h>
#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <nanobind/nanobind.h>

#include "evalio/pipeline.h"

namespace py = pybind11;
using namespace pybind11::literals;
namespace nb = nanobind;
using namespace nb::literals;

#ifdef EVALIO_KISS_ICP
#include "bindings/pipelines/kiss_icp.h"
Expand All @@ -19,20 +16,20 @@ using namespace pybind11::literals;
#endif

namespace evalio {
inline void makePipelines(py::module &m) {
inline void makePipelines(nb::module_ &m) {
// List all the pipelines here
#ifdef EVALIO_KISS_ICP
py::class_<KissICP, evalio::Pipeline>(m, "KissICP")
.def(py::init<>())
nb::class_<KissICP, evalio::Pipeline>(m, "KissICP")
.def(nb::init<>())
.def_static("name", &KissICP::name)
.def_static("url", &KissICP::url)
.def_static("default_params", &KissICP::default_params);

#endif

#ifdef EVALIO_LIO_SAM
py::class_<LioSam, evalio::Pipeline>(m, "LioSAM")
.def(py::init<>())
nb::class_<LioSam, evalio::Pipeline>(m, "LioSAM")
.def(nb::init<>())
.def_static("name", &LioSam::name)
.def_static("url", &LioSam::url)
.def_static("default_params", &LioSam::default_params);
Expand Down
59 changes: 31 additions & 28 deletions cpp/bindings/ros_pc2.h
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
#pragma once
#include <cmath>
#include <cstddef>
#include <pybind11/eigen.h>
#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <nanobind/eigen/dense.h>
#include <nanobind/nanobind.h>
#include <nanobind/operators.h>
#include <nanobind/stl/string.h>
#include <nanobind/stl/vector.h>

#include <fstream>

#include "evalio/types.h"

namespace py = pybind11;
using namespace pybind11::literals;
namespace nb = nanobind;
using namespace nb::literals;

namespace evalio {

Expand Down Expand Up @@ -363,8 +364,8 @@ inline LidarMeasurement helipr_bin_to_evalio(const std::string &filename,
}

// ---------------------- Create python bindings ---------------------- //
inline void makeConversions(py::module &m) {
py::enum_<DataType>(m, "DataType")
inline void makeConversions(nb::module_ &m) {
nb::enum_<DataType>(m, "DataType")
.value("UINT8", DataType::UINT8)
.value("INT8", DataType::INT8)
.value("UINT16", DataType::UINT16)
Expand All @@ -374,29 +375,31 @@ inline void makeConversions(py::module &m) {
.value("FLOAT32", DataType::FLOAT32)
.value("FLOAT64", DataType::FLOAT64);

py::class_<Field>(m, "Field")
.def(py::init<std::string, DataType, uint32_t>(), py::kw_only(), "name"_a,
nb::class_<Field>(m, "Field")
.def(nb::init<std::string, DataType, uint32_t>(), nb::kw_only(), "name"_a,
"datatype"_a, "offset"_a)
.def_readwrite("name", &Field::name)
.def_readwrite("datatype", &Field::datatype)
.def_readwrite("offset", &Field::offset);
.def_rw("name", &Field::name)
.def_rw("datatype", &Field::datatype)
.def_rw("offset", &Field::offset);

py::class_<PointCloudMetadata>(m, "PointCloudMetadata")
.def(py::init<evalio::Stamp, int, int, int, int, int, int>(),
py::kw_only(), "stamp"_a, "width"_a, "height"_a, "point_step"_a,
nb::class_<PointCloudMetadata>(m, "PointCloudMetadata")
.def(nb::init<evalio::Stamp, int, int, int, int, int, int>(),
nb::kw_only(), "stamp"_a, "width"_a, "height"_a, "point_step"_a,
"row_step"_a, "is_bigendian"_a, "is_dense"_a)
.def_readwrite("stamp", &PointCloudMetadata::stamp)
.def_readwrite("width", &PointCloudMetadata::width)
.def_readwrite("height", &PointCloudMetadata::height)
.def_readwrite("point_step", &PointCloudMetadata::point_step)
.def_readwrite("row_step", &PointCloudMetadata::row_step)
.def_readwrite("is_bigendian", &PointCloudMetadata::is_bigendian)
.def_readwrite("is_dense", &PointCloudMetadata::is_dense);

m.def("pc2_to_evalio", [](const PointCloudMetadata &msg,
const std::vector<Field> &fields, char *c) {
return pc2_to_evalio(msg, fields, reinterpret_cast<uint8_t *>(c));
});
.def_rw("stamp", &PointCloudMetadata::stamp)
.def_rw("width", &PointCloudMetadata::width)
.def_rw("height", &PointCloudMetadata::height)
.def_rw("point_step", &PointCloudMetadata::point_step)
.def_rw("row_step", &PointCloudMetadata::row_step)
.def_rw("is_bigendian", &PointCloudMetadata::is_bigendian)
.def_rw("is_dense", &PointCloudMetadata::is_dense);

m.def("pc2_to_evalio",
[](const PointCloudMetadata &msg, const std::vector<Field> &fields,
nb::bytes c) -> evalio::LidarMeasurement {
return pc2_to_evalio(msg, fields,
reinterpret_cast<const uint8_t *>(c.c_str()));
});

m.def("fill_col_row_major", &fill_col_row_major);
m.def("fill_col_col_major", &fill_col_col_major);
Expand Down
Loading
Loading