add documention workflow

This commit is contained in:
AndreaRigoni
2026-03-06 10:45:33 +00:00
parent f3ebba4931
commit b64afe8773
15 changed files with 1513 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
# Developer Guide Python Bindings
This guide is aimed at contributors who want to extend or modify the Python bindings for `uLib`.
---
## Repository Layout
```
ulib/
├── src/
│ └── Python/
│ ├── module.cpp # pybind11 module entry point
│ ├── core_bindings.cpp # uLib::Core bindings
│ ├── math_bindings.cpp # uLib::Math bindings
│ ├── math_filters_bindings.cpp# VoxImageFilter bindings
│ ├── CMakeLists.txt # builds uLib_python shared lib
│ ├── testing/ # Python unit tests
│ │ ├── pybind_test.py
│ │ ├── core_pybind_test.py
│ │ ├── math_pybind_test.py
│ │ └── math_filters_test.py
│ └── uLib/ # Python package (uLib_python.so lands here)
│ └── __init__.py
├── build_python.py # poetry build hook (calls CMake)
├── pyproject.toml # poetry metadata
└── condaenv.yml # conda/micromamba environment
```
---
## Adding a New Binding
All bindings live in the four source files listed above. The module entry point `module.cpp` calls `init_core()`, `init_math()`, and `init_math_filters()` in order.
### 1. Pick (or create) the right binding file
| C++ header location | Binding file |
|---|---|
| `src/Core/` | `core_bindings.cpp` |
| `src/Math/` (geometry, grids, VoxImage) | `math_bindings.cpp` |
| `src/Math/VoxImageFilter*.hpp` | `math_filters_bindings.cpp` |
### 2. Add the `#include` directive
```cpp
// math_bindings.cpp
#include "Math/MyNewClass.h"
```
### 3. Write the pybind11 binding inside the appropriate `init_*` function
```cpp
void init_math(py::module_ &m) {
// ... existing bindings ...
py::class_<MyNewClass>(m, "MyNewClass")
.def(py::init<>())
.def("MyMethod", &MyNewClass::MyMethod)
.def("AnotherMethod", &MyNewClass::AnotherMethod,
py::arg("x"), py::arg("y") = 0.0f);
}
```
### 4. Rebuild only the Python target
```bash
cmake --build build --target uLib_python -j$(nproc)
```
### 5. Write a Python test
Add a new test class to the relevant test file (or create a new one under `src/Python/testing/`):
```python
# src/Python/testing/math_pybind_test.py
class TestMyNewClass(unittest.TestCase):
def test_basic(self):
obj = uLib.Math.MyNewClass()
result = obj.MyMethod()
self.assertAlmostEqual(result, expected_value)
```
Register the test in `src/Python/CMakeLists.txt` if you add a new file:
```cmake
add_test(NAME pybind_my_new
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/testing/my_new_test.py)
set_tests_properties(pybind_my_new PROPERTIES
ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:uLib_python>:${PROJECT_SOURCE_DIR}/src/Python")
```
---
## Build System Details
### CMakeLists.txt (`src/Python/`)
`pybind11_add_module` compiles the shared library `uLib_python` and links it against the C++ static/shared libraries `uLibCore` and `uLibMath`. The install target copies the `.so` into the standard library directory.
```cmake
pybind11_add_module(uLib_python
module.cpp core_bindings.cpp math_bindings.cpp math_filters_bindings.cpp)
target_link_libraries(uLib_python PRIVATE uLibCore uLibMath)
```
### poetry / build_python.py
`pyproject.toml` declares `build_python.py` as the custom build hook. When `poetry install` or `poetry build` is invoked it:
1. Calls `cmake <root> -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=<pkg_dir> ...` in `build_python/`.
2. Builds only the `uLib_python` target.
3. The resulting `.so` is placed inside `src/Python/uLib/` so it is picked up by Poetry as a package data file.
The `USE_CUDA` environment variable gates CUDA support at build time:
```bash
USE_CUDA=ON poetry install # with CUDA
USE_CUDA=OFF poetry install # CPU only (default)
```
---
## Running All Tests
```bash
# From the repository root, with PYTHONPATH set:
export PYTHONPATH="$(pwd)/build/src/Python:$(pwd)/src/Python"
python -m pytest src/Python/testing/ -v
```
Or through CMake's test runner (after building the full project):
```bash
cd build
ctest --output-on-failure -R pybind
```
Expected output (all passing):
```
Start 1: pybind_general
1/4 Test #1: pybind_general ............. Passed
Start 2: pybind_core
2/4 Test #2: pybind_core ................ Passed
Start 3: pybind_math
3/4 Test #3: pybind_math ................ Passed
Start 4: pybind_math_filters
4/4 Test #4: pybind_math_filters ........ Passed
```
---
## Memory Management Notes
`uLib::Vector<T>` has explicit GPU memory management. When wrapping methods that return references to internal data, use `py::return_value_policy::reference_internal` to avoid dangling references:
```cpp
.def("Data", &VoxImage<Voxel>::Data,
py::return_value_policy::reference_internal)
```
For objects held by `std::unique_ptr` without Python-side deletion, use `py::nodelete`:
```cpp
py::class_<Abstract::VoxImageFilter,
std::unique_ptr<Abstract::VoxImageFilter, py::nodelete>>(m, "AbstractVoxImageFilter")
```
---
## Useful References
- [pybind11 documentation](https://pybind11.readthedocs.io)
- [pybind11 STL containers](https://pybind11.readthedocs.io/en/stable/advanced/cast/stl.html)
- [pybind11 Eigen integration](https://pybind11.readthedocs.io/en/stable/advanced/cast/eigen.html)
- [CMake pybind11 integration](https://pybind11.readthedocs.io/en/stable/compiling.html)