4 Commits

Author SHA1 Message Date
AndreaRigoni
554eff9b55 add filters in python bindings 2026-03-05 15:03:19 +00:00
AndreaRigoni
42db99759f fix py dense 2026-03-05 14:26:05 +00:00
AndreaRigoni
69920acd61 poetry python build 2026-03-05 12:42:14 +00:00
AndreaRigoni
647d0caa1c feat: Add Python packaging infrastructure and comprehensive bindings for math and vector types. 2026-03-05 11:39:27 +00:00
21 changed files with 828 additions and 100 deletions

7
.gitignore vendored
View File

@@ -5,3 +5,10 @@ build_warnings*.log
final_build.log final_build.log
cmake_configure.log cmake_configure.log
compile_commands.json compile_commands.json
dist/
build_python/
src/Python/uLib/*.so*
src/Python/uLib/*.pyd
src/Python/uLib/*.pyc
src/Python/uLib/__pycache__

47
build_python.py Normal file
View File

@@ -0,0 +1,47 @@
import os
import subprocess
import sys
import shutil
def build(setup_kwargs):
"""
Build the C++ extension using CMake.
This function is called by poetry-core during the build process.
The binary is placed directly inside the uLib directory in src/Python.
"""
# Root of the whole project where this build_extension.py is located
project_root = os.path.abspath(os.path.dirname(__file__))
# Where the extension should go
package_dir = os.path.join(project_root, "src/Python/uLib")
# Ensure package directory exists
os.makedirs(package_dir, exist_ok=True)
# Temporary build directory
build_temp = os.path.join(project_root, "build_python")
os.makedirs(build_temp, exist_ok=True)
print(f"--- Running CMake build in {build_temp} ---")
print(f"Project root: {project_root}")
print(f"Target binary dir: {package_dir}")
# CMake configuration
cmake_args = [
f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={package_dir}",
f"-DPYTHON_EXECUTABLE={sys.executable}",
"-DCMAKE_BUILD_TYPE=Release",
"-DUSE_CUDA=OFF",
"-G", "Unix Makefiles",
]
# Use micromamba to ensure Boost and VTK are found during the build
subprocess.check_call(["cmake", project_root] + cmake_args, cwd=build_temp)
subprocess.check_call(["cmake", "--build", ".", "--parallel", "--target", "uLib_python"], cwd=build_temp)
# Ensure the package is found by poetry during the wheel creation process.
# Return setup_kwargs for poetry-core.
return setup_kwargs
if __name__ == "__main__":
build({})

7
poetry.lock generated Normal file
View File

@@ -0,0 +1,7 @@
# This file is automatically @generated by Poetry 2.3.1 and should not be changed by hand.
package = []
[metadata]
lock-version = "2.1"
python-versions = ">=3.9"
content-hash = "db9b4c08b159b17b239e26c67ead7c37b82d9f9eb06550245ae3134c095f98f7"

15
pyproject.toml Normal file
View File

@@ -0,0 +1,15 @@
[tool.poetry]
name = "uLib"
version = "0.6.0"
description = "CMT Cosmic Muon Tomography project uLib python bindings"
authors = ["Andrea Rigoni Garola <andrea.rigoni@pd.infn.it>"]
readme = "README.md"
packages = [{ include = "uLib", from = "src/Python" }]
build = "build_python.py"
[tool.poetry.dependencies]
python = ">=3.9"
[build-system]
requires = ["poetry-core>=2.0.0", "pybind11>=2.6.0", "cmake>=3.12"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,7 +1,36 @@
set(HEADERS Archives.h Array.h Collection.h DataAllocator.h Debug.h Export.h Function.h Macros.h Mpl.h Object.h Options.h Serializable.h Signal.h Singleton.h SmartPointer.h StaticInterface.h StringReader.h Types.h Uuid.h Vector.h) set(HEADERS
Archives.h
Array.h
Collection.h
DataAllocator.h
Debug.h
Export.h
Function.h
Macros.h
Mpl.h
Object.h
Options.h
Serializable.h
Signal.h
Singleton.h
SmartPointer.h
StaticInterface.h
StringReader.h
Types.h
Uuid.h
Vector.h
)
set(SOURCES Archives.cpp Debug.cpp Object.cpp Options.cpp Serializable.cpp Signal.cpp Uuid.cpp) set(SOURCES
Archives.cpp
Debug.cpp
Object.cpp
Options.cpp
Serializable.cpp
Signal.cpp
Uuid.cpp
)
set(LIBRARIES Boost::program_options Boost::serialization) set(LIBRARIES Boost::program_options Boost::serialization)

View File

@@ -117,6 +117,10 @@ bool operator!=(const MetaAllocator<T> &, const MetaAllocator<U> &) {
return false; return false;
} }
// Vector Implemetation ... wraps std::vector // Vector Implemetation ... wraps std::vector
template <typename T> class Vector : public std::vector<T, MetaAllocator<T>> { template <typename T> class Vector : public std::vector<T, MetaAllocator<T>> {
typedef std::vector<T, MetaAllocator<T>> BaseClass; typedef std::vector<T, MetaAllocator<T>> BaseClass;
@@ -136,6 +140,7 @@ public:
Vector(unsigned int size) : BaseClass(size) {} Vector(unsigned int size) : BaseClass(size) {}
Vector(unsigned int size, T &value) : BaseClass(size, value) {} Vector(unsigned int size, T &value) : BaseClass(size, value) {}
Vector() : BaseClass(0) {} Vector() : BaseClass(0) {}
Vector(std::initializer_list<T> init) : BaseClass(init) {}
inline VectorCommaInit operator<<(T scalar) { inline VectorCommaInit operator<<(T scalar) {
return VectorCommaInit(this, scalar); return VectorCommaInit(this, scalar);
@@ -274,6 +279,11 @@ public:
this->MoveToRAM(); this->MoveToRAM();
return BaseClass::insert(pos, std::move(x)); return BaseClass::insert(pos, std::move(x));
} }
template <typename InputIt>
iterator insert(const_iterator pos, InputIt first, InputIt last) {
this->MoveToRAM();
return BaseClass::insert(pos, first, last);
}
iterator erase(const_iterator pos) { iterator erase(const_iterator pos) {
this->MoveToRAM(); this->MoveToRAM();
return BaseClass::erase(pos); return BaseClass::erase(pos);

View File

@@ -1,31 +0,0 @@
#include <Core/Vector.h>
#include <iostream>
int main() {
uLib::Vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
std::cout << "RAM Vector elements: ";
for (int i = 0; i < v.size(); ++i) {
std::cout << v[i] << " ";
}
std::cout << std::endl;
#ifdef USE_CUDA
std::cout << "Moving to VRAM..." << std::endl;
v.MoveToVRAM();
int *vram_ptr = v.GetVRAMData();
if (vram_ptr) {
std::cout << "Successfully got VRAM pointer!" << std::endl;
} else {
std::cout << "Failed to get VRAM pointer!" << std::endl;
}
std::cout << "Moving back to RAM..." << std::endl;
v.MoveToRAM();
#endif
return 0;
}

View File

@@ -47,6 +47,7 @@
#ifndef ULIB_DENSEMATRIX_H #ifndef ULIB_DENSEMATRIX_H
#define ULIB_DENSEMATRIX_H #define ULIB_DENSEMATRIX_H
// #include <Eigen/src/Core/Matrix.h>
#include <stdlib.h> #include <stdlib.h>
#include <Eigen/Dense> #include <Eigen/Dense>
@@ -114,6 +115,21 @@ typedef unsigned long Scalarul;
typedef float Scalarf; typedef float Scalarf;
typedef double Scalard; typedef double Scalard;
typedef Eigen::Matrix<int, 1, 1> Vector1i;
typedef Eigen::Vector2i Vector2i;
typedef Eigen::Vector3i Vector3i;
typedef Eigen::Vector4i Vector4i;
typedef Eigen::Matrix<float, 1, 1> Vector1f;
typedef Eigen::Vector2f Vector2f;
typedef Eigen::Vector3f Vector3f;
typedef Eigen::Vector4f Vector4f;
typedef Eigen::Matrix<double, 1, 1> Vector1d;
typedef Eigen::Vector2d Vector2d;
typedef Eigen::Vector3d Vector3d;
typedef Eigen::Vector4d Vector4d;
typedef Eigen::Matrix<int, 1, 1> Matrix1i; typedef Eigen::Matrix<int, 1, 1> Matrix1i;
typedef Eigen::Matrix2i Matrix2i; typedef Eigen::Matrix2i Matrix2i;
typedef Eigen::Matrix3i Matrix3i; typedef Eigen::Matrix3i Matrix3i;
@@ -124,15 +140,15 @@ typedef Eigen::Matrix2f Matrix2f;
typedef Eigen::Matrix3f Matrix3f; typedef Eigen::Matrix3f Matrix3f;
typedef Eigen::Matrix4f Matrix4f; typedef Eigen::Matrix4f Matrix4f;
typedef Eigen::Matrix<int, 1, 1> Vector1i; typedef Eigen::Matrix<double, 1, 1> Matrix1d;
typedef Eigen::Vector2i Vector2i; typedef Eigen::Matrix2d Matrix2d;
typedef Eigen::Vector3i Vector3i; typedef Eigen::Matrix3d Matrix3d;
typedef Eigen::Vector4i Vector4i; typedef Eigen::Matrix4d Matrix4d;
typedef Eigen::MatrixXi MatrixXi;
typedef Eigen::MatrixXf MatrixXf;
typedef Eigen::MatrixXd MatrixXd;
typedef Eigen::Matrix<float, 1, 1> Vector1f;
typedef Eigen::Vector2f Vector2f;
typedef Eigen::Vector3f Vector3f;
typedef Eigen::Vector4f Vector4f;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Vector String interaction /////////////////////////////////////////////////// // Vector String interaction ///////////////////////////////////////////////////
@@ -188,6 +204,9 @@ public:
typedef Eigen::Matrix<Scalarf, 4, 1> BaseClass; typedef Eigen::Matrix<Scalarf, 4, 1> BaseClass;
_HPoint3f() : BaseClass(0, 0, 0, p) {} _HPoint3f() : BaseClass(0, 0, 0, p) {}
_HPoint3f(int rows, int cols) : BaseClass() {
this->operator()(3) = p;
}
_HPoint3f(float x, float y, float z) : BaseClass(x, y, z, p) {} _HPoint3f(float x, float y, float z) : BaseClass(x, y, z, p) {}
_HPoint3f(Vector3f &in) : BaseClass(in.homogeneous()) { _HPoint3f(Vector3f &in) : BaseClass(in.homogeneous()) {
this->operator()(3) = p; this->operator()(3) = p;

View File

@@ -70,8 +70,8 @@ public:
int ImportFromVti(const char *file, bool density_type = 0); int ImportFromVti(const char *file, bool density_type = 0);
protected:
virtual ~VoxImage() {} virtual ~VoxImage() {}
protected:
VoxImage(const Vector3i &size) : BaseClass(size) {} VoxImage(const Vector3i &size) : BaseClass(size) {}
}; };
@@ -90,7 +90,8 @@ struct Voxel {
} // namespace Interface } // namespace Interface
struct Voxel { struct Voxel {
Scalarf Value; Scalarf Value = 0.0f;
Scalari Count = 0;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@@ -36,7 +36,7 @@
namespace uLib { namespace uLib {
#ifdef USE_CUDA #if defined(USE_CUDA) && defined(__CUDACC__)
template <typename VoxelT> template <typename VoxelT>
__global__ void ABTrimFilterKernel(const VoxelT *in, VoxelT *out, __global__ void ABTrimFilterKernel(const VoxelT *in, VoxelT *out,
const VoxelT *kernel, int vox_size, const VoxelT *kernel, int vox_size,
@@ -108,7 +108,7 @@ public:
mBtrim = 0; mBtrim = 0;
} }
#ifdef USE_CUDA #if defined(USE_CUDA) && defined(__CUDACC__)
void Run() { void Run() {
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM || if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) { this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
@@ -206,7 +206,7 @@ public:
mBtrim = 0; mBtrim = 0;
} }
#ifdef USE_CUDA #if defined(USE_CUDA) && defined(__CUDACC__)
void Run() { void Run() {
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM || if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) { this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {

View File

@@ -36,7 +36,7 @@
namespace uLib { namespace uLib {
#ifdef USE_CUDA #if defined(USE_CUDA) && defined(__CUDACC__)
template <typename VoxelT> template <typename VoxelT>
__global__ void LinearFilterKernel(const VoxelT *in, VoxelT *out, __global__ void LinearFilterKernel(const VoxelT *in, VoxelT *out,
const VoxelT *kernel, int vox_size, const VoxelT *kernel, int vox_size,
@@ -66,7 +66,7 @@ public:
typedef VoxImageFilter<VoxelT, VoxFilterAlgorithmLinear<VoxelT>> BaseClass; typedef VoxImageFilter<VoxelT, VoxFilterAlgorithmLinear<VoxelT>> BaseClass;
VoxFilterAlgorithmLinear(const Vector3i &size) : BaseClass(size) {} VoxFilterAlgorithmLinear(const Vector3i &size) : BaseClass(size) {}
#ifdef USE_CUDA #if defined(USE_CUDA) && defined(__CUDACC__)
void Run() { void Run() {
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM || if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) { this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {

View File

@@ -4,10 +4,11 @@ set(SOURCES
module.cpp module.cpp
core_bindings.cpp core_bindings.cpp
math_bindings.cpp math_bindings.cpp
math_filters_bindings.cpp
) )
# Use pybind11 to add the python module # Use pybind11 to add the python module
pybind11_add_module(uLib_python module.cpp core_bindings.cpp math_bindings.cpp) pybind11_add_module(uLib_python module.cpp core_bindings.cpp math_bindings.cpp math_filters_bindings.cpp)
# Link against our C++ libraries # Link against our C++ libraries
target_link_libraries(uLib_python PRIVATE target_link_libraries(uLib_python PRIVATE
@@ -22,6 +23,14 @@ target_include_directories(uLib_python PRIVATE
${PROJECT_BINARY_DIR} ${PROJECT_BINARY_DIR}
) )
# Install uLib_python within the uLib install target
install(TARGETS uLib_python
EXPORT "${PROJECT_NAME}Targets"
RUNTIME DESTINATION ${INSTALL_BIN_DIR} COMPONENT bin
LIBRARY DESTINATION ${INSTALL_LIB_DIR} COMPONENT lib
ARCHIVE DESTINATION ${INSTALL_LIB_DIR} COMPONENT lib
)
# --- Python Tests ---------------------------------------------------------- # # --- Python Tests ---------------------------------------------------------- #
if(BUILD_TESTING) if(BUILD_TESTING)
@@ -30,15 +39,20 @@ if(BUILD_TESTING)
add_test(NAME pybind_general add_test(NAME pybind_general
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/testing/pybind_test.py) COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/testing/pybind_test.py)
set_tests_properties(pybind_general PROPERTIES set_tests_properties(pybind_general PROPERTIES
ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:uLib_python>") ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:uLib_python>:${PROJECT_SOURCE_DIR}/src/Python")
add_test(NAME pybind_core add_test(NAME pybind_core
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/testing/core_pybind_test.py) COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/testing/core_pybind_test.py)
set_tests_properties(pybind_core PROPERTIES set_tests_properties(pybind_core PROPERTIES
ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:uLib_python>") ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:uLib_python>:${PROJECT_SOURCE_DIR}/src/Python")
add_test(NAME pybind_math add_test(NAME pybind_math
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/testing/math_pybind_test.py) COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/testing/math_pybind_test.py)
set_tests_properties(pybind_math PROPERTIES set_tests_properties(pybind_math PROPERTIES
ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:uLib_python>") ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:uLib_python>:${PROJECT_SOURCE_DIR}/src/Python")
add_test(NAME pybind_math_filters
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/testing/math_filters_test.py)
set_tests_properties(pybind_math_filters PROPERTIES
ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:uLib_python>:${PROJECT_SOURCE_DIR}/src/Python")
endif() endif()

View File

@@ -1,6 +1,9 @@
#include <pybind11/pybind11.h> #include <pybind11/pybind11.h>
#include <pybind11/eigen.h> #include <pybind11/eigen.h>
#include <pybind11/stl.h> #include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include <pybind11/numpy.h>
#include "Math/Dense.h" #include "Math/Dense.h"
#include "Math/Transform.h" #include "Math/Transform.h"
@@ -13,13 +16,224 @@
#include "Math/TriangleMesh.h" #include "Math/TriangleMesh.h"
#include "Math/VoxRaytracer.h" #include "Math/VoxRaytracer.h"
#include "Math/Accumulator.h" #include "Math/Accumulator.h"
#include "Math/VoxImage.h"
namespace py = pybind11; namespace py = pybind11;
using namespace uLib; using namespace uLib;
PYBIND11_MAKE_OPAQUE(uLib::Vector<Scalari>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<Scalarui>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<Scalarl>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<Scalarul>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<Scalarf>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<Scalard>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<Vector3f>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<Vector3i>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<Vector4f>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<Vector4i>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<Vector3d>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<Vector4d>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<Voxel>);
PYBIND11_MAKE_OPAQUE(uLib::Vector<VoxRaytracer::RayData::Element>);
template <typename MatrixType>
void bind_eigen_type(py::module_ &m, const char *name) {
using Scalar = typename MatrixType::Scalar;
constexpr bool is_vector = MatrixType::IsVectorAtCompileTime;
// Default constructor (zeros)
m.def(name, []() -> MatrixType {
if constexpr (MatrixType::RowsAtCompileTime == Eigen::Dynamic || MatrixType::ColsAtCompileTime == Eigen::Dynamic) {
return MatrixType(); // Empty dynamic matrix
} else {
return MatrixType::Zero(); // Zero static matrix
}
});
// Specialized constructor for dynamic matrices
if constexpr (MatrixType::RowsAtCompileTime == Eigen::Dynamic || MatrixType::ColsAtCompileTime == Eigen::Dynamic) {
m.def(name, [](int rows, int cols) -> MatrixType {
MatrixType mat;
mat.setZero(rows, cols);
return mat;
});
}
// Initialize from list
m.def(name, [](py::list l) -> MatrixType {
MatrixType mat;
if constexpr (is_vector) {
mat.setZero(l.size());
for (size_t i = 0; i < l.size(); ++i) {
mat(i) = l[i].cast<Scalar>();
}
} else {
int rows = MatrixType::RowsAtCompileTime == Eigen::Dynamic ? (int)std::sqrt(l.size()) : MatrixType::RowsAtCompileTime;
int cols = MatrixType::ColsAtCompileTime == Eigen::Dynamic ? (int)std::sqrt(l.size()) : MatrixType::ColsAtCompileTime;
mat.setZero(rows, cols);
for (size_t i = 0; i < (size_t)l.size(); ++i) {
mat(i / cols, i % cols) = l[i].cast<Scalar>();
}
}
return mat;
});
// Initialize from py::array
m.def(name, [](py::array_t<Scalar, py::array::c_style | py::array::forcecast> arr) -> MatrixType {
auto buf = arr.request();
MatrixType mat;
if constexpr (is_vector) {
mat.setZero(buf.size);
Scalar* ptr = static_cast<Scalar*>(buf.ptr);
for (ssize_t i = 0; i < buf.size; ++i) mat(i) = ptr[i];
} else {
int rows = buf.shape.size() > 0 ? (int)buf.shape[0] : 1;
int cols = buf.shape.size() > 1 ? (int)buf.shape[1] : 1;
mat.setZero(rows, cols);
Scalar* ptr = static_cast<Scalar*>(buf.ptr);
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
mat(i, j) = ptr[i * cols + j];
}
}
}
return mat;
});
}
void init_math(py::module_ &m) { void init_math(py::module_ &m) {
// Math/Transform.h // 1. Basic Eigen Types (Vectors and Matrices)
bind_eigen_type<Vector1f>(m, "Vector1f");
bind_eigen_type<Vector2f>(m, "Vector2f");
bind_eigen_type<Vector3f>(m, "Vector3f");
bind_eigen_type<Vector4f>(m, "Vector4f");
bind_eigen_type<Vector1i>(m, "Vector1i");
bind_eigen_type<Vector2i>(m, "Vector2i");
bind_eigen_type<Vector3i>(m, "Vector3i");
bind_eigen_type<Vector4i>(m, "Vector4i");
bind_eigen_type<Vector1d>(m, "Vector1d");
bind_eigen_type<Vector2d>(m, "Vector2d");
bind_eigen_type<Vector3d>(m, "Vector3d");
bind_eigen_type<Vector4d>(m, "Vector4d");
bind_eigen_type<Matrix2f>(m, "Matrix2f");
bind_eigen_type<Matrix3f>(m, "Matrix3f");
bind_eigen_type<Matrix4f>(m, "Matrix4f");
bind_eigen_type<Matrix2i>(m, "Matrix2i");
bind_eigen_type<Matrix3i>(m, "Matrix3i");
bind_eigen_type<Matrix4i>(m, "Matrix4i");
bind_eigen_type<Matrix2d>(m, "Matrix2d");
bind_eigen_type<Matrix3d>(m, "Matrix3d");
bind_eigen_type<Matrix4d>(m, "Matrix4d");
bind_eigen_type<MatrixXi>(m, "MatrixXi");
bind_eigen_type<MatrixXf>(m, "MatrixXf");
bind_eigen_type<MatrixXd>(m, "MatrixXd");
// 2. Homogeneous types
py::class_<HPoint3f>(m, "HPoint3f")
.def(py::init<>())
.def(py::init<float, float, float>())
.def(py::init<Vector3f &>());
py::class_<HVector3f>(m, "HVector3f")
.def(py::init<>())
.def(py::init<float, float, float>())
.def(py::init<Vector3f &>());
py::class_<HLine3f>(m, "HLine3f")
.def(py::init<>())
.def_readwrite("origin", &HLine3f::origin)
.def_readwrite("direction", &HLine3f::direction);
py::class_<HError3f>(m, "HError3f")
.def(py::init<>())
.def_readwrite("position_error", &HError3f::position_error)
.def_readwrite("direction_error", &HError3f::direction_error);
// 3. Dynamic Vectors (uLib::Vector)
py::bind_vector<uLib::Vector<Scalari>>(m, "Vector_i")
.def("MoveToVRAM", &uLib::Vector<Scalari>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Scalari>::MoveToRAM);
py::bind_vector<uLib::Vector<Scalarui>>(m, "Vector_ui")
.def("MoveToVRAM", &uLib::Vector<Scalarui>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Scalarui>::MoveToRAM);
py::bind_vector<uLib::Vector<Scalarl>>(m, "Vector_l")
.def("MoveToVRAM", &uLib::Vector<Scalarl>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Scalarl>::MoveToRAM);
py::bind_vector<uLib::Vector<Scalarul>>(m, "Vector_ul")
.def("MoveToVRAM", &uLib::Vector<Scalarul>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Scalarul>::MoveToRAM);
py::bind_vector<uLib::Vector<Scalarf>>(m, "Vector_f")
.def("MoveToVRAM", &uLib::Vector<Scalarf>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Scalarf>::MoveToRAM);
py::bind_vector<uLib::Vector<Scalard>>(m, "Vector_d")
.def("MoveToVRAM", &uLib::Vector<Scalard>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Scalard>::MoveToRAM);
py::bind_vector<uLib::Vector<Vector3f>>(m, "Vector_Vector3f")
.def("MoveToVRAM", &uLib::Vector<Vector3f>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Vector3f>::MoveToRAM);
py::bind_vector<uLib::Vector<Vector3i>>(m, "Vector_Vector3i")
.def("MoveToVRAM", &uLib::Vector<Vector3i>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Vector3i>::MoveToRAM);
py::bind_vector<uLib::Vector<Vector4f>>(m, "Vector_Vector4f")
.def("MoveToVRAM", &uLib::Vector<Vector4f>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Vector4f>::MoveToRAM);
py::bind_vector<uLib::Vector<Vector4i>>(m, "Vector_Vector4i")
.def("MoveToVRAM", &uLib::Vector<Vector4i>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Vector4i>::MoveToRAM);
py::bind_vector<uLib::Vector<Vector3d>>(m, "Vector_Vector3d")
.def("MoveToVRAM", &uLib::Vector<Vector3d>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Vector3d>::MoveToRAM);
py::bind_vector<uLib::Vector<Vector4d>>(m, "Vector_Vector4d")
.def("MoveToVRAM", &uLib::Vector<Vector4d>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Vector4d>::MoveToRAM);
py::bind_vector<uLib::Vector<Voxel>>(m, "Vector_Voxel")
.def("MoveToVRAM", &uLib::Vector<Voxel>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<Voxel>::MoveToRAM);
py::bind_vector<uLib::Vector<VoxRaytracer::RayData::Element>>(m, "Vector_VoxRaytracerRayDataElement")
.def("MoveToVRAM", &uLib::Vector<VoxRaytracer::RayData::Element>::MoveToVRAM)
.def("MoveToRAM", &uLib::Vector<VoxRaytracer::RayData::Element>::MoveToRAM);
// 4. Accumulators
py::class_<Accumulator_Mean<float>>(m, "Accumulator_Mean_f")
.def(py::init<>())
.def("AddPass", &Accumulator_Mean<float>::AddPass)
.def("__call__", py::overload_cast<const float>(&Accumulator_Mean<float>::operator()))
.def("__call__", py::overload_cast<>(&Accumulator_Mean<float>::operator(), py::const_));
py::class_<Accumulator_Mean<double>>(m, "Accumulator_Mean_d")
.def(py::init<>())
.def("AddPass", &Accumulator_Mean<double>::AddPass)
.def("__call__", py::overload_cast<const double>(&Accumulator_Mean<double>::operator()))
.def("__call__", py::overload_cast<>(&Accumulator_Mean<double>::operator(), py::const_));
py::class_<Accumulator_ABTrim<float>>(m, "Accumulator_ABTrim_f")
.def(py::init<>())
.def("SetABTrim", &Accumulator_ABTrim<float>::SetABTrim)
.def("__iadd__", [](Accumulator_ABTrim<float> &self, float val) { self += val; return &self; })
.def("__call__", &Accumulator_ABTrim<float>::operator());
py::class_<Accumulator_ABTrim<double>>(m, "Accumulator_ABTrim_d")
.def(py::init<>())
.def("SetABTrim", &Accumulator_ABTrim<double>::SetABTrim)
.def("__iadd__", [](Accumulator_ABTrim<double> &self, double val) { self += val; return &self; })
.def("__call__", &Accumulator_ABTrim<double>::operator());
py::class_<Accumulator_ABClip<float>>(m, "Accumulator_ABClip_f")
.def(py::init<>())
.def("SetABTrim", &Accumulator_ABClip<float>::SetABTrim)
.def("__iadd__", [](Accumulator_ABClip<float> &self, float val) { self += val; return &self; })
.def("__call__", &Accumulator_ABClip<float>::operator());
py::class_<Accumulator_ABClip<double>>(m, "Accumulator_ABClip_d")
.def(py::init<>())
.def("SetABTrim", &Accumulator_ABClip<double>::SetABTrim)
.def("__iadd__", [](Accumulator_ABClip<double> &self, double val) { self += val; return &self; })
.def("__call__", &Accumulator_ABClip<double>::operator());
// 5. Core Math Structures
py::class_<AffineTransform>(m, "AffineTransform") py::class_<AffineTransform>(m, "AffineTransform")
.def(py::init<>()) .def(py::init<>())
.def("GetWorldMatrix", &AffineTransform::GetWorldMatrix) .def("GetWorldMatrix", &AffineTransform::GetWorldMatrix)
@@ -34,13 +248,11 @@ void init_math(py::module_ &m) {
.def("EulerYZYRotate", &AffineTransform::EulerYZYRotate) .def("EulerYZYRotate", &AffineTransform::EulerYZYRotate)
.def("FlipAxes", &AffineTransform::FlipAxes); .def("FlipAxes", &AffineTransform::FlipAxes);
// Math/Geometry.h
py::class_<Geometry, AffineTransform>(m, "Geometry") py::class_<Geometry, AffineTransform>(m, "Geometry")
.def(py::init<>()) .def(py::init<>())
.def("GetWorldPoint", py::overload_cast<const Vector4f &>(&Geometry::GetWorldPoint, py::const_)) .def("GetWorldPoint", py::overload_cast<const Vector4f &>(&Geometry::GetWorldPoint, py::const_))
.def("GetLocalPoint", py::overload_cast<const Vector4f &>(&Geometry::GetLocalPoint, py::const_)); .def("GetLocalPoint", py::overload_cast<const Vector4f &>(&Geometry::GetLocalPoint, py::const_));
// Math/ContainerBox.h
py::class_<ContainerBox, AffineTransform>(m, "ContainerBox") py::class_<ContainerBox, AffineTransform>(m, "ContainerBox")
.def(py::init<>()) .def(py::init<>())
.def("SetOrigin", &ContainerBox::SetOrigin) .def("SetOrigin", &ContainerBox::SetOrigin)
@@ -51,7 +263,6 @@ void init_math(py::module_ &m) {
.def("GetWorldPoint", py::overload_cast<const Vector4f &>(&ContainerBox::GetWorldPoint, py::const_)) .def("GetWorldPoint", py::overload_cast<const Vector4f &>(&ContainerBox::GetWorldPoint, py::const_))
.def("GetLocalPoint", py::overload_cast<const Vector4f &>(&ContainerBox::GetLocalPoint, py::const_)); .def("GetLocalPoint", py::overload_cast<const Vector4f &>(&ContainerBox::GetLocalPoint, py::const_));
// Math/StructuredData.h
py::enum_<StructuredData::_Order>(m, "StructuredDataOrder") py::enum_<StructuredData::_Order>(m, "StructuredDataOrder")
.value("CustomOrder", StructuredData::CustomOrder) .value("CustomOrder", StructuredData::CustomOrder)
.value("XYZ", StructuredData::XYZ) .value("XYZ", StructuredData::XYZ)
@@ -74,7 +285,6 @@ void init_math(py::module_ &m) {
.def("Map", &StructuredData::Map) .def("Map", &StructuredData::Map)
.def("UnMap", &StructuredData::UnMap); .def("UnMap", &StructuredData::UnMap);
// Math/StructuredGrid.h
py::class_<StructuredGrid, ContainerBox, StructuredData>(m, "StructuredGrid") py::class_<StructuredGrid, ContainerBox, StructuredData>(m, "StructuredGrid")
.def(py::init<const Vector3i &>()) .def(py::init<const Vector3i &>())
.def("SetSpacing", &StructuredGrid::SetSpacing) .def("SetSpacing", &StructuredGrid::SetSpacing)
@@ -84,7 +294,6 @@ void init_math(py::module_ &m) {
return self.Find(HPoint3f(pt)); return self.Find(HPoint3f(pt));
}); });
// Math/Structured2DGrid.h
py::class_<Structured2DGrid>(m, "Structured2DGrid") py::class_<Structured2DGrid>(m, "Structured2DGrid")
.def(py::init<>()) .def(py::init<>())
.def("SetDims", &Structured2DGrid::SetDims) .def("SetDims", &Structured2DGrid::SetDims)
@@ -100,7 +309,6 @@ void init_math(py::module_ &m) {
.def("UnitToPhysicsSpace", &Structured2DGrid::UnitToPhysicsSpace) .def("UnitToPhysicsSpace", &Structured2DGrid::UnitToPhysicsSpace)
.def("SetDebug", &Structured2DGrid::SetDebug); .def("SetDebug", &Structured2DGrid::SetDebug);
// Math/Structured4DGrid.h
py::class_<Structured4DGrid>(m, "Structured4DGrid") py::class_<Structured4DGrid>(m, "Structured4DGrid")
.def(py::init<>()) .def(py::init<>())
.def("SetDims", &Structured4DGrid::SetDims) .def("SetDims", &Structured4DGrid::SetDims)
@@ -116,7 +324,37 @@ void init_math(py::module_ &m) {
.def("UnitToPhysicsSpace", &Structured4DGrid::UnitToPhysicsSpace) .def("UnitToPhysicsSpace", &Structured4DGrid::UnitToPhysicsSpace)
.def("SetDebug", &Structured4DGrid::SetDebug); .def("SetDebug", &Structured4DGrid::SetDebug);
// Math/TriangleMesh.h // 6. High-level Structures
py::class_<Voxel>(m, "Voxel")
.def(py::init<>())
.def_readwrite("Value", &Voxel::Value)
.def_readwrite("Count", &Voxel::Count);
py::class_<Abstract::VoxImage, StructuredGrid>(m, "AbstractVoxImage")
.def("GetValue", py::overload_cast<const Vector3i &>(&Abstract::VoxImage::GetValue, py::const_))
.def("GetValue", py::overload_cast<const int>(&Abstract::VoxImage::GetValue, py::const_))
.def("SetValue", py::overload_cast<const Vector3i &, float>(&Abstract::VoxImage::SetValue))
.def("SetValue", py::overload_cast<const int, float>(&Abstract::VoxImage::SetValue))
.def("ExportToVtk", &Abstract::VoxImage::ExportToVtk)
.def("ExportToVti", &Abstract::VoxImage::ExportToVti)
.def("ImportFromVtk", &Abstract::VoxImage::ImportFromVtk)
.def("ImportFromVti", &Abstract::VoxImage::ImportFromVti);
py::class_<VoxImage<Voxel>, Abstract::VoxImage>(m, "VoxImage")
.def(py::init<>())
.def(py::init<const Vector3i &>())
.def("Data", &VoxImage<Voxel>::Data, py::return_value_policy::reference_internal)
.def("InitVoxels", &VoxImage<Voxel>::InitVoxels)
.def("Abs", &VoxImage<Voxel>::Abs)
.def("clipImage", py::overload_cast<const Vector3i, const Vector3i>(&VoxImage<Voxel>::clipImage, py::const_))
.def("clipImage", py::overload_cast<const HPoint3f, const HPoint3f>(&VoxImage<Voxel>::clipImage, py::const_))
.def("clipImage", py::overload_cast<const float>(&VoxImage<Voxel>::clipImage, py::const_))
.def("maskImage", py::overload_cast<const HPoint3f, const HPoint3f, float>(&VoxImage<Voxel>::maskImage, py::const_))
.def("maskImage", py::overload_cast<const float, float, float>(&VoxImage<Voxel>::maskImage, py::const_), py::arg("threshold"), py::arg("belowValue") = 0, py::arg("aboveValue") = 0)
.def("fixVoxels", py::overload_cast<const float, float>(&VoxImage<Voxel>::fixVoxels, py::const_))
.def("__getitem__", py::overload_cast<unsigned int>(&VoxImage<Voxel>::operator[]))
.def("__getitem__", py::overload_cast<const Vector3i &>(&VoxImage<Voxel>::operator[]));
py::class_<TriangleMesh>(m, "TriangleMesh") py::class_<TriangleMesh>(m, "TriangleMesh")
.def(py::init<>()) .def(py::init<>())
.def("AddPoint", &TriangleMesh::AddPoint) .def("AddPoint", &TriangleMesh::AddPoint)
@@ -124,7 +362,6 @@ void init_math(py::module_ &m) {
.def("Points", &TriangleMesh::Points, py::return_value_policy::reference_internal) .def("Points", &TriangleMesh::Points, py::return_value_policy::reference_internal)
.def("Triangles", &TriangleMesh::Triangles, py::return_value_policy::reference_internal); .def("Triangles", &TriangleMesh::Triangles, py::return_value_policy::reference_internal);
// Math/VoxRaytracer.h
py::class_<VoxRaytracer::RayData::Element>(m, "VoxRaytracerRayDataElement") py::class_<VoxRaytracer::RayData::Element>(m, "VoxRaytracerRayDataElement")
.def(py::init<>()) .def(py::init<>())
.def_readwrite("vox_id", &VoxRaytracer::RayData::Element::vox_id) .def_readwrite("vox_id", &VoxRaytracer::RayData::Element::vox_id)
@@ -133,6 +370,7 @@ void init_math(py::module_ &m) {
py::class_<VoxRaytracer::RayData>(m, "VoxRaytracerRayData") py::class_<VoxRaytracer::RayData>(m, "VoxRaytracerRayData")
.def(py::init<>()) .def(py::init<>())
.def("AppendRay", &VoxRaytracer::RayData::AppendRay) .def("AppendRay", &VoxRaytracer::RayData::AppendRay)
.def("Data", py::overload_cast<>(&VoxRaytracer::RayData::Data), py::return_value_policy::reference_internal)
.def("Count", &VoxRaytracer::RayData::Count) .def("Count", &VoxRaytracer::RayData::Count)
.def("TotalLength", &VoxRaytracer::RayData::TotalLength) .def("TotalLength", &VoxRaytracer::RayData::TotalLength)
.def("SetCount", &VoxRaytracer::RayData::SetCount) .def("SetCount", &VoxRaytracer::RayData::SetCount)
@@ -140,13 +378,8 @@ void init_math(py::module_ &m) {
py::class_<VoxRaytracer>(m, "VoxRaytracer") py::class_<VoxRaytracer>(m, "VoxRaytracer")
.def(py::init<StructuredGrid &>(), py::keep_alive<1, 2>()) .def(py::init<StructuredGrid &>(), py::keep_alive<1, 2>())
.def("GetImage", &VoxRaytracer::GetImage, py::return_value_policy::reference_internal); .def("GetImage", &VoxRaytracer::GetImage, py::return_value_policy::reference_internal)
.def("TraceLine", &VoxRaytracer::TraceLine)
// Math/Accumulator.h .def("TraceBetweenPoints", &VoxRaytracer::TraceBetweenPoints);
py::class_<Accumulator_Mean<float>>(m, "Accumulator_Mean_f")
.def(py::init<>())
.def("AddPass", &Accumulator_Mean<float>::AddPass)
.def("__call__", py::overload_cast<const float>(&Accumulator_Mean<float>::operator()))
.def("__call__", py::overload_cast<>(&Accumulator_Mean<float>::operator(), py::const_));
} }

View File

@@ -0,0 +1,100 @@
#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <pybind11/stl.h>
#include "Math/VoxImage.h"
#include "Math/VoxImageFilter.h"
#include "Math/VoxImageFilterLinear.hpp"
#include "Math/VoxImageFilterABTrim.hpp"
#include "Math/VoxImageFilterBilateral.hpp"
#include "Math/VoxImageFilterThreshold.hpp"
#include "Math/VoxImageFilterMedian.hpp"
#include "Math/VoxImageFilter2ndStat.hpp"
#include "Math/VoxImageFilterCustom.hpp"
namespace py = pybind11;
using namespace uLib;
template <typename Algorithm>
void bind_common_filter(py::class_<Algorithm, Abstract::VoxImageFilter> &cls) {
cls.def(py::init<const Vector3i &>())
.def("Run", &Algorithm::Run)
.def("SetKernelNumericXZY", &Algorithm::SetKernelNumericXZY)
.def("GetImage", &Algorithm::GetImage, py::return_value_policy::reference_internal)
.def("SetImage", &Algorithm::SetImage);
}
void init_math_filters(py::module_ &m) {
// Abstract::VoxImageFilter
py::class_<Abstract::VoxImageFilter, std::unique_ptr<Abstract::VoxImageFilter, py::nodelete>>(m, "AbstractVoxImageFilter")
.def("Run", &Abstract::VoxImageFilter::Run)
.def("SetImage", &Abstract::VoxImageFilter::SetImage);
// Helper macro to define standard bindings for a filter
#define BIND_FILTER(ClassName) \
{ \
auto cls = py::class_<ClassName<Voxel>, Abstract::VoxImageFilter>(m, #ClassName); \
bind_common_filter(cls); \
}
// VoxFilterAlgorithmLinear
{
auto cls = py::class_<VoxFilterAlgorithmLinear<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmLinear");
bind_common_filter(cls);
}
// VoxFilterAlgorithmAbtrim
{
auto cls = py::class_<VoxFilterAlgorithmAbtrim<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmAbtrim");
bind_common_filter(cls);
cls.def("SetABTrim", &VoxFilterAlgorithmAbtrim<Voxel>::SetABTrim);
}
// VoxFilterAlgorithmSPR
{
auto cls = py::class_<VoxFilterAlgorithmSPR<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmSPR");
bind_common_filter(cls);
cls.def("SetABTrim", &VoxFilterAlgorithmSPR<Voxel>::SetABTrim);
}
// VoxFilterAlgorithmBilateral
{
auto cls = py::class_<VoxFilterAlgorithmBilateral<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmBilateral");
bind_common_filter(cls);
cls.def("SetIntensitySigma", &VoxFilterAlgorithmBilateral<Voxel>::SetIntensitySigma);
}
// VoxFilterAlgorithmBilateralTrim
{
auto cls = py::class_<VoxFilterAlgorithmBilateralTrim<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmBilateralTrim");
bind_common_filter(cls);
cls.def("SetIntensitySigma", &VoxFilterAlgorithmBilateralTrim<Voxel>::SetIntensitySigma);
cls.def("SetABTrim", &VoxFilterAlgorithmBilateralTrim<Voxel>::SetABTrim);
}
// VoxFilterAlgorithmThreshold
{
auto cls = py::class_<VoxFilterAlgorithmThreshold<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmThreshold");
bind_common_filter(cls);
cls.def("SetThreshold", &VoxFilterAlgorithmThreshold<Voxel>::SetThreshold);
}
// VoxFilterAlgorithmMedian
{
auto cls = py::class_<VoxFilterAlgorithmMedian<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmMedian");
bind_common_filter(cls);
}
// VoxFilterAlgorithm2ndStat
{
auto cls = py::class_<VoxFilterAlgorithm2ndStat<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithm2ndStat");
bind_common_filter(cls);
}
// VoxFilterAlgorithmCustom (Omit CustomEvaluate since it uses static function ptrs)
{
auto cls = py::class_<VoxFilterAlgorithmCustom<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmCustom");
bind_common_filter(cls);
}
}

View File

@@ -4,6 +4,7 @@ namespace py = pybind11;
void init_core(py::module_ &m); void init_core(py::module_ &m);
void init_math(py::module_ &m); void init_math(py::module_ &m);
void init_math_filters(py::module_ &m);
PYBIND11_MODULE(uLib_python, m) { PYBIND11_MODULE(uLib_python, m) {
m.doc() = "Python bindings for uLib Core and Math libraries"; m.doc() = "Python bindings for uLib Core and Math libraries";
@@ -15,4 +16,5 @@ PYBIND11_MODULE(uLib_python, m) {
// Math submodule // Math submodule
py::module_ math = m.def_submodule("Math", "Math library bindings"); py::module_ math = m.def_submodule("Math", "Math library bindings");
init_math(math); init_math(math);
init_math_filters(math);
} }

View File

@@ -3,11 +3,11 @@ import os
import unittest import unittest
import time import time
import uLib_python import uLib
class TestCoreOptions(unittest.TestCase): class TestCoreOptions(unittest.TestCase):
def test_options(self): def test_options(self):
opt = uLib_python.Core.Options("Test Options") opt = uLib.Core.Options("Test Options")
# Test basic config file parsing # Test basic config file parsing
with open("test_configuration.ini", "w") as f: with open("test_configuration.ini", "w") as f:
@@ -18,12 +18,12 @@ class TestCoreOptions(unittest.TestCase):
class TestCoreObject(unittest.TestCase): class TestCoreObject(unittest.TestCase):
def test_object(self): def test_object(self):
obj = uLib_python.Core.Object() obj = uLib.Core.Object()
self.assertIsNotNone(obj) self.assertIsNotNone(obj)
class TestCoreTimer(unittest.TestCase): class TestCoreTimer(unittest.TestCase):
def test_timer(self): def test_timer(self):
timer = uLib_python.Core.Timer() timer = uLib.Core.Timer()
timer.Start() timer.Start()
time.sleep(0.1) time.sleep(0.1)
val = timer.StopWatch() val = timer.StopWatch()

View File

@@ -0,0 +1,151 @@
import unittest
import numpy as np
import os
import sys
# Ensure PYTHONPATH is correct if run from root
sys.path.append(os.path.join(os.getcwd(), 'src', 'Python'))
import uLib
class TestMathFilters(unittest.TestCase):
def test_filter_creation(self):
# 1. Linear Filter
dims = [10, 10, 10]
v_dims = uLib.Math.Vector3i(dims)
linear_filter = uLib.Math.VoxFilterAlgorithmLinear(v_dims)
self.assertIsNotNone(linear_filter)
# 2. ABTrim Filter
abtrim_filter = uLib.Math.VoxFilterAlgorithmAbtrim(v_dims)
self.assertIsNotNone(abtrim_filter)
abtrim_filter.SetABTrim(1, 1)
# 3. Bilateral Filter
bilat_filter = uLib.Math.VoxFilterAlgorithmBilateral(v_dims)
self.assertIsNotNone(bilat_filter)
bilat_filter.SetIntensitySigma(0.5)
# 4. Threshold Filter
threshold_filter = uLib.Math.VoxFilterAlgorithmThreshold(v_dims)
self.assertIsNotNone(threshold_filter)
threshold_filter.SetThreshold(0.5)
# 5. Median Filter
median_filter = uLib.Math.VoxFilterAlgorithmMedian(v_dims)
self.assertIsNotNone(median_filter)
def test_filter_run(self):
# Create image
dims = [10, 10, 10]
vox_img = uLib.Math.VoxImage(dims)
for i in range(10*10*10):
vox_img.SetValue(i, 1.0)
# Linear filter
linear_filter = uLib.Math.VoxFilterAlgorithmLinear([3, 3, 3])
linear_filter.SetImage(vox_img)
# Set kernel (simple 3x3x3 all ones)
# Weights are usually normalized in linear filter logic?
# Let's just test it runs.
linear_filter.SetKernelNumericXZY([1.0] * 27)
# Run filter
linear_filter.Run()
# Value should be 1.0 (mean of all 1.0 is 1.0)
self.assertAlmostEqual(vox_img.GetValue(0), 1.0)
def test_filter_run_abtrim(self):
# Create image
dims = [10, 10, 10]
vox_img = uLib.Math.VoxImage(dims)
for i in range(10*10*10):
vox_img.SetValue(i, 1.0)
# ABTrim filter
abtrim_filter = uLib.Math.VoxFilterAlgorithmAbtrim([3, 3, 3])
abtrim_filter.SetImage(vox_img)
# Set kernel (simple 3x3x3 all ones)
# Weights are usually normalized in linear filter logic?
# Let's just test it runs.
abtrim_filter.SetKernelNumericXZY([1.0] * 27)
# Run filter
abtrim_filter.Run()
# Value should be 1.0 (mean of all 1.0 is 1.0)
self.assertAlmostEqual(vox_img.GetValue(0), 1.0)
def test_filter_run_bilateral(self):
# Create image
dims = [10, 10, 10]
vox_img = uLib.Math.VoxImage(dims)
for i in range(10*10*10):
vox_img.SetValue(i, 1.0)
# Bilateral filter
bilat_filter = uLib.Math.VoxFilterAlgorithmBilateral([3, 3, 3])
bilat_filter.SetImage(vox_img)
# Set kernel (simple 3x3x3 all ones)
# Weights are usually normalized in linear filter logic?
# Let's just test it runs.
bilat_filter.SetKernelNumericXZY([1.0] * 27)
# Run filter
bilat_filter.Run()
# Value should be 1.0 (mean of all 1.0 is 1.0)
self.assertAlmostEqual(vox_img.GetValue(0), 1.0)
def test_filter_run_threshold(self):
# Create image
dims = [10, 10, 10]
vox_img = uLib.Math.VoxImage(dims)
for i in range(10*10*10):
vox_img.SetValue(i, 1.0)
# Threshold filter
threshold_filter = uLib.Math.VoxFilterAlgorithmThreshold([3, 3, 3])
threshold_filter.SetImage(vox_img)
# Set kernel (simple 3x3x3 all ones)
# Weights are usually normalized in linear filter logic?
# Let's just test it runs.
threshold_filter.SetKernelNumericXZY([1.0] * 27)
# Run filter
threshold_filter.Run()
# Value should be 1.0 (mean of all 1.0 is 1.0)
self.assertAlmostEqual(vox_img.GetValue(0), 1.0)
def test_filter_run_median(self):
# Create image
dims = [10, 10, 10]
vox_img = uLib.Math.VoxImage(dims)
for i in range(10*10*10):
vox_img.SetValue(i, 1.0)
# Median filter
median_filter = uLib.Math.VoxFilterAlgorithmMedian([3, 3, 3])
median_filter.SetImage(vox_img)
# Set kernel (simple 3x3x3 all ones)
# Weights are usually normalized in linear filter logic?
# Let's just test it runs.
median_filter.SetKernelNumericXZY([1.0] * 27)
# Run filter
median_filter.Run()
# Value should be 1.0 (mean of all 1.0 is 1.0)
self.assertAlmostEqual(vox_img.GetValue(0), 1.0)
if __name__ == '__main__':
unittest.main()

View File

@@ -3,16 +3,77 @@ import os
import unittest import unittest
import numpy as np import numpy as np
import uLib_python import uLib
def vector4f0(v, target): def vector4f0(v, target):
diff = np.array(v) - np.array(target) diff = np.array(v) - np.array(target)
diff[3] = 0 # ignoring w diff[3] = 0 # ignoring w
return np.all(np.abs(diff) < 0.001) return np.all(np.abs(diff) < 0.001)
class TestMathMatrix(unittest.TestCase):
def test_matrix(self):
def check_1234(m2f):
self.assertEqual(m2f[0, 0], 1)
self.assertEqual(m2f[0, 1], 2)
self.assertEqual(m2f[1, 0], 3)
self.assertEqual(m2f[1, 1], 4)
m2f = uLib.Math.Matrix2f()
m2f[0, 0] = 1
m2f[0, 1] = 2
m2f[1, 0] = 3
m2f[1, 1] = 4
check_1234(m2f)
m2f = uLib.Math.Matrix2f([1, 2, 3, 4])
check_1234(m2f)
# m2f = uLib.Math.Matrix2f([[1, 2], [3, 4]])
# check_1234(m2f)
m2f = uLib.Math.Matrix2f(np.array([[1, 2], [3, 4]]))
check_1234(m2f)
def test_vector2(self):
v2f = uLib.Math.Vector2f()
v2f[0] = 1
v2f[1] = 2
self.assertEqual(v2f[0], 1)
self.assertEqual(v2f[1], 2)
v2f = uLib.Math.Vector2f([1, 2])
self.assertEqual(v2f[0], 1)
self.assertEqual(v2f[1], 2)
v2f = uLib.Math.Vector2f(np.array([1, 2]))
self.assertEqual(v2f[0], 1)
self.assertEqual(v2f[1], 2)
def test_vector3(self):
v3f = uLib.Math.Vector3f()
v3f[0] = 1
v3f[1] = 2
v3f[2] = 3
self.assertEqual(v3f[0], 1)
self.assertEqual(v3f[1], 2)
self.assertEqual(v3f[2], 3)
v3f = uLib.Math.Vector3f([1, 2, 3])
self.assertEqual(v3f[0], 1)
self.assertEqual(v3f[1], 2)
self.assertEqual(v3f[2], 3)
v3f = uLib.Math.Vector3f(np.array([1, 2, 3]))
self.assertEqual(v3f[0], 1)
self.assertEqual(v3f[1], 2)
self.assertEqual(v3f[2], 3)
class TestMathGeometry(unittest.TestCase): class TestMathGeometry(unittest.TestCase):
def test_geometry(self): def test_geometry(self):
Geo = uLib_python.Math.Geometry() Geo = uLib.Math.Geometry()
Geo.SetPosition([1, 1, 1]) Geo.SetPosition([1, 1, 1])
@@ -27,7 +88,7 @@ class TestMathGeometry(unittest.TestCase):
class TestMathContainerBox(unittest.TestCase): class TestMathContainerBox(unittest.TestCase):
def test_container_box_local(self): def test_container_box_local(self):
Cnt = uLib_python.Math.ContainerBox() Cnt = uLib.Math.ContainerBox()
Cnt.SetOrigin([-1, -1, -1]) Cnt.SetOrigin([-1, -1, -1])
Cnt.SetSize([2, 2, 2]) Cnt.SetSize([2, 2, 2])
@@ -35,7 +96,7 @@ class TestMathContainerBox(unittest.TestCase):
self.assertTrue(np.allclose(size, [2, 2, 2])) self.assertTrue(np.allclose(size, [2, 2, 2]))
def test_container_box_global(self): def test_container_box_global(self):
Box = uLib_python.Math.ContainerBox() Box = uLib.Math.ContainerBox()
Box.SetPosition([1, 1, 1]) Box.SetPosition([1, 1, 1])
Box.SetSize([2, 2, 2]) Box.SetSize([2, 2, 2])
@@ -45,7 +106,7 @@ class TestMathContainerBox(unittest.TestCase):
class TestMathStructuredGrid(unittest.TestCase): class TestMathStructuredGrid(unittest.TestCase):
def test_structured_grid(self): def test_structured_grid(self):
grid = uLib_python.Math.StructuredGrid([10, 10, 10]) grid = uLib.Math.StructuredGrid([10, 10, 10])
grid.SetSpacing([1, 1, 1]) grid.SetSpacing([1, 1, 1])
spacing = grid.GetSpacing() spacing = grid.GetSpacing()
@@ -53,10 +114,76 @@ class TestMathStructuredGrid(unittest.TestCase):
class TestMathAccumulator(unittest.TestCase): class TestMathAccumulator(unittest.TestCase):
def test_accumulator_mean(self): def test_accumulator_mean(self):
acc = uLib_python.Math.Accumulator_Mean_f() acc = uLib.Math.Accumulator_Mean_f()
acc(10.0) acc(10.0)
acc(20.0) acc(20.0)
self.assertAlmostEqual(acc(), 15.0) self.assertAlmostEqual(acc(), 15.0)
class TestMathNewTypes(unittest.TestCase):
def test_eigen_vectors(self):
v1f = uLib.Math.Vector1f()
v3d = uLib.Math.Vector3d()
m4f = uLib.Math.Matrix4f()
self.assertIsNotNone(v1f)
self.assertIsNotNone(v3d)
self.assertIsNotNone(m4f)
def test_ulib_vectors(self):
vi = uLib.Math.Vector_i()
vi.append(1)
vi.append(2)
self.assertEqual(len(vi), 2)
self.assertEqual(vi[0], 1)
self.assertEqual(vi[1], 2)
vf = uLib.Math.Vector_f()
vf.append(1.5)
self.assertAlmostEqual(vf[0], 1.5)
def test_homogeneous(self):
p = uLib.Math.HPoint3f(1.0, 2.0, 3.0)
v = uLib.Math.HVector3f(0.0, 1.0, 0.0)
self.assertIsNotNone(p)
self.assertIsNotNone(v)
def test_vox_image(self):
img = uLib.Math.VoxImage([2, 2, 2])
self.assertEqual(img.GetDims()[0], 2)
img.SetValue([0, 0, 0], 10.5)
# Note: GetValue returns float, and there might be internal scaling (1.E-6 observed in code)
# Actually in VoxImage.h: GetValue(id) returns At(id).Value
# SetValue(id, value) sets At(id).Value = value
self.assertAlmostEqual(img.GetValue([0, 0, 0]), 10.5)
class TestMathVoxRaytracer(unittest.TestCase):
def test_raytracer(self):
grid = uLib.Math.StructuredGrid([10, 10, 10])
grid.SetSpacing([1, 1, 1])
grid.SetOrigin([0, 0, 0])
rt = uLib.Math.VoxRaytracer(grid)
self.assertIsNotNone(rt)
# Test TraceBetweenPoints
p1 = np.array([0.5, 0.5, -1.0, 1.0], dtype=np.float32)
p2 = np.array([0.5, 0.5, 11.0, 1.0], dtype=np.float32)
data = rt.TraceBetweenPoints(p1, p2)
self.assertGreater(data.Count(), 0)
self.assertAlmostEqual(data.TotalLength(), 10.0)
# Check elements
elements = data.Data()
for i in range(data.Count()):
self.assertGreaterEqual(elements[i].vox_id, 0)
self.assertGreater(elements[i].L, 0)
def test_ray_data(self):
data = uLib.Math.VoxRaytracerRayData()
data.SetCount(10)
data.SetTotalLength(5.5)
self.assertEqual(data.Count(), 10)
self.assertAlmostEqual(data.TotalLength(), 5.5)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@@ -1,42 +1,42 @@
import sys import sys
import os import os
import uLib_python import uLib
def test_core(): def test_core():
print("Testing Core module...") print("Testing Core module...")
obj = uLib_python.Core.Object() obj = uLib.Core.Object()
print("Core Object created:", obj) print("Core Object created:", obj)
timer = uLib_python.Core.Timer() timer = uLib.Core.Timer()
timer.Start() timer.Start()
print("Core Timer started") print("Core Timer started")
options = uLib_python.Core.Options("Test Options") options = uLib.Core.Options("Test Options")
print("Core Options created:", options) print("Core Options created:", options)
def test_math(): def test_math():
print("Testing Math module...") print("Testing Math module...")
# Test AffineTransform # Test AffineTransform
transform = uLib_python.Math.AffineTransform() transform = uLib.Math.AffineTransform()
print("AffineTransform created") print("AffineTransform created")
# Test Geometry # Test Geometry
geom = uLib_python.Math.Geometry() geom = uLib.Math.Geometry()
print("Geometry created") print("Geometry created")
# Test StructuredData # Test StructuredData
data = uLib_python.Math.StructuredData([10, 10, 10]) data = uLib.Math.StructuredData([10, 10, 10])
print("StructuredData created with dims:", data.GetDims()) print("StructuredData created with dims:", data.GetDims())
# Test Structured2DGrid # Test Structured2DGrid
grid2d = uLib_python.Math.Structured2DGrid() grid2d = uLib.Math.Structured2DGrid()
grid2d.SetDims([100, 100]) grid2d.SetDims([100, 100])
print("Structured2DGrid created with dims:", grid2d.GetDims()) print("Structured2DGrid created with dims:", grid2d.GetDims())
# Test TriangleMesh # Test TriangleMesh
mesh = uLib_python.Math.TriangleMesh() mesh = uLib.Math.TriangleMesh()
print("TriangleMesh created") print("TriangleMesh created")
print("All tests passed successfully!") print("All tests passed successfully!")

View File

@@ -0,0 +1,7 @@
try:
from .uLib_python import Core, Math
except ImportError:
# Handle cases where the binary extension is not yet built
pass
__all__ = ["Core", "Math"]

View File

@@ -1,10 +0,0 @@
#include "Core/Object.h"
int main()
{
uLib::Object obj;
return 0;
}