From 647d0caa1cd15cc1ab33723ab95141be4e7f16c9 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Thu, 5 Mar 2026 11:39:27 +0000 Subject: [PATCH] feat: Add Python packaging infrastructure and comprehensive bindings for math and vector types. --- pyproject.toml | 10 ++ setup.py | 52 +++++++ src/Core/CMakeLists.txt | 33 ++++- src/Core/Vector.h | 5 + src/Core/test_meta_allocator.cpp | 31 ---- src/Math/Dense.h | 31 ++-- src/Math/VoxImage.h | 2 +- src/Python/CMakeLists.txt | 8 + src/Python/math_bindings.cpp | 194 ++++++++++++++++++++++--- src/Python/testing/math_pybind_test.py | 66 +++++++++ 10 files changed, 372 insertions(+), 60 deletions(-) create mode 100644 pyproject.toml create mode 100644 setup.py delete mode 100644 src/Core/test_meta_allocator.cpp diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1b54914 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,10 @@ + +[build-system] +requires = [ + "setuptools>=42", + "wheel", + "pybind11>=2.6.0", + "cmake>=3.12", + "ninja"] + +build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c6e6e20 --- /dev/null +++ b/setup.py @@ -0,0 +1,52 @@ +import os +import re +import subprocess +import sys + +from setuptools import Extension, setup +from setuptools.command.build_ext import build_ext + +class CMakeExtension(Extension): + def __init__(self, name, sourcedir=""): + Extension.__init__(self, name, sources=[]) + self.sourcedir = os.path.abspath(sourcedir) + +class CMakeBuild(build_ext): + def build_extension(self, ext): + extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) + + if not extdir.endswith(os.path.sep): + extdir += os.path.sep + + debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug + cfg = "Debug" if debug else "Release" + + cmake_args = [ + f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}", + f"-DPYTHON_EXECUTABLE={sys.executable}", + f"-DCMAKE_BUILD_TYPE={cfg}", + f"-DCMAKE_BUILD_WITH_INSTALL_RPATH=TRUE", + f"-DCMAKE_INSTALL_RPATH=$ORIGIN", + "-DUSE_CUDA=OFF", + "-G", "Unix Makefiles", + ] + build_args = [] + + build_temp = os.path.join(self.build_temp, ext.name) + if not os.path.exists(build_temp): + os.makedirs(build_temp) + + subprocess.check_call(["cmake", ext.sourcedir] + cmake_args, cwd=build_temp) + subprocess.check_call(["cmake", "--build", ".", "--target", "uLib_python"] + build_args, cwd=build_temp) + +setup( + name="ulib", + version="0.6.0", + author="Andrea Rigoni Garola", + author_email="andrea.rigoni@pd.infn.it", + description="CMT Cosmic Muon Tomography project uLib python bindings", + ext_modules=[CMakeExtension("uLib_python")], + cmdclass={"build_ext": CMakeBuild}, + zip_safe=False, + python_requires=">=3.6", +) diff --git a/src/Core/CMakeLists.txt b/src/Core/CMakeLists.txt index 586f2ef..d337c97 100644 --- a/src/Core/CMakeLists.txt +++ b/src/Core/CMakeLists.txt @@ -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) diff --git a/src/Core/Vector.h b/src/Core/Vector.h index 0bd1944..2622816 100644 --- a/src/Core/Vector.h +++ b/src/Core/Vector.h @@ -274,6 +274,11 @@ public: this->MoveToRAM(); return BaseClass::insert(pos, std::move(x)); } + template + iterator insert(const_iterator pos, InputIt first, InputIt last) { + this->MoveToRAM(); + return BaseClass::insert(pos, first, last); + } iterator erase(const_iterator pos) { this->MoveToRAM(); return BaseClass::erase(pos); diff --git a/src/Core/test_meta_allocator.cpp b/src/Core/test_meta_allocator.cpp deleted file mode 100644 index 07571ac..0000000 --- a/src/Core/test_meta_allocator.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include - -int main() { - uLib::Vector 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; -} diff --git a/src/Math/Dense.h b/src/Math/Dense.h index 7c4b587..ef302a3 100644 --- a/src/Math/Dense.h +++ b/src/Math/Dense.h @@ -114,6 +114,21 @@ typedef unsigned long Scalarul; typedef float Scalarf; typedef double Scalard; +typedef Eigen::Matrix Vector1i; +typedef Eigen::Vector2i Vector2i; +typedef Eigen::Vector3i Vector3i; +typedef Eigen::Vector4i Vector4i; + +typedef Eigen::Matrix Vector1f; +typedef Eigen::Vector2f Vector2f; +typedef Eigen::Vector3f Vector3f; +typedef Eigen::Vector4f Vector4f; + +typedef Eigen::Matrix Vector1d; +typedef Eigen::Vector2d Vector2d; +typedef Eigen::Vector3d Vector3d; +typedef Eigen::Vector4d Vector4d; + typedef Eigen::Matrix Matrix1i; typedef Eigen::Matrix2i Matrix2i; typedef Eigen::Matrix3i Matrix3i; @@ -124,15 +139,10 @@ typedef Eigen::Matrix2f Matrix2f; typedef Eigen::Matrix3f Matrix3f; typedef Eigen::Matrix4f Matrix4f; -typedef Eigen::Matrix Vector1i; -typedef Eigen::Vector2i Vector2i; -typedef Eigen::Vector3i Vector3i; -typedef Eigen::Vector4i Vector4i; - -typedef Eigen::Matrix Vector1f; -typedef Eigen::Vector2f Vector2f; -typedef Eigen::Vector3f Vector3f; -typedef Eigen::Vector4f Vector4f; +typedef Eigen::Matrix Matrix1d; +typedef Eigen::Matrix2d Matrix2d; +typedef Eigen::Matrix3d Matrix3d; +typedef Eigen::Matrix4d Matrix4d; //////////////////////////////////////////////////////////////////////////////// // Vector String interaction /////////////////////////////////////////////////// @@ -188,6 +198,9 @@ public: typedef Eigen::Matrix BaseClass; _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(Vector3f &in) : BaseClass(in.homogeneous()) { this->operator()(3) = p; diff --git a/src/Math/VoxImage.h b/src/Math/VoxImage.h index 7edd3c4..eba6125 100644 --- a/src/Math/VoxImage.h +++ b/src/Math/VoxImage.h @@ -70,8 +70,8 @@ public: int ImportFromVti(const char *file, bool density_type = 0); -protected: virtual ~VoxImage() {} +protected: VoxImage(const Vector3i &size) : BaseClass(size) {} }; diff --git a/src/Python/CMakeLists.txt b/src/Python/CMakeLists.txt index 0ef8874..095031f 100644 --- a/src/Python/CMakeLists.txt +++ b/src/Python/CMakeLists.txt @@ -22,6 +22,14 @@ target_include_directories(uLib_python PRIVATE ${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 ---------------------------------------------------------- # if(BUILD_TESTING) diff --git a/src/Python/math_bindings.cpp b/src/Python/math_bindings.cpp index 0f4434f..fcb2394 100644 --- a/src/Python/math_bindings.cpp +++ b/src/Python/math_bindings.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "Math/Dense.h" #include "Math/Transform.h" @@ -13,13 +14,154 @@ #include "Math/TriangleMesh.h" #include "Math/VoxRaytracer.h" #include "Math/Accumulator.h" +#include "Math/VoxImage.h" namespace py = pybind11; using namespace uLib; +PYBIND11_MAKE_OPAQUE(uLib::Vector); +PYBIND11_MAKE_OPAQUE(uLib::Vector); +PYBIND11_MAKE_OPAQUE(uLib::Vector); +PYBIND11_MAKE_OPAQUE(uLib::Vector); +PYBIND11_MAKE_OPAQUE(uLib::Vector); +PYBIND11_MAKE_OPAQUE(uLib::Vector); + +PYBIND11_MAKE_OPAQUE(uLib::Vector); +PYBIND11_MAKE_OPAQUE(uLib::Vector); +PYBIND11_MAKE_OPAQUE(uLib::Vector); +PYBIND11_MAKE_OPAQUE(uLib::Vector); +PYBIND11_MAKE_OPAQUE(uLib::Vector); +PYBIND11_MAKE_OPAQUE(uLib::Vector); +PYBIND11_MAKE_OPAQUE(uLib::Vector); +PYBIND11_MAKE_OPAQUE(uLib::Vector); + void init_math(py::module_ &m) { - // Math/Transform.h + // 1. Basic Eigen Types (Vectors and Matrices) + py::class_(m, "Vector1f").def(py::init<>()); + py::class_(m, "Vector2f").def(py::init<>()); + py::class_(m, "Vector3f").def(py::init<>()); + py::class_(m, "Vector4f").def(py::init<>()); + py::class_(m, "Vector1i").def(py::init<>()); + py::class_(m, "Vector2i").def(py::init<>()); + py::class_(m, "Vector3i").def(py::init<>()); + py::class_(m, "Vector4i").def(py::init<>()); + py::class_(m, "Vector1d").def(py::init<>()); + py::class_(m, "Vector2d").def(py::init<>()); + py::class_(m, "Vector3d").def(py::init<>()); + py::class_(m, "Vector4d").def(py::init<>()); + + py::class_(m, "Matrix2f").def(py::init<>()); + py::class_(m, "Matrix3f").def(py::init<>()); + py::class_(m, "Matrix4f").def(py::init<>()); + py::class_(m, "Matrix2i").def(py::init<>()); + py::class_(m, "Matrix3i").def(py::init<>()); + py::class_(m, "Matrix4i").def(py::init<>()); + py::class_(m, "Matrix2d").def(py::init<>()); + py::class_(m, "Matrix3d").def(py::init<>()); + py::class_(m, "Matrix4d").def(py::init<>()); + + // 2. Homogeneous types + py::class_(m, "HPoint3f") + .def(py::init<>()) + .def(py::init()) + .def(py::init()); + py::class_(m, "HVector3f") + .def(py::init<>()) + .def(py::init()) + .def(py::init()); + py::class_(m, "HLine3f") + .def(py::init<>()) + .def_readwrite("origin", &HLine3f::origin) + .def_readwrite("direction", &HLine3f::direction); + py::class_(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>(m, "Vector_i") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + py::bind_vector>(m, "Vector_ui") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + py::bind_vector>(m, "Vector_l") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + py::bind_vector>(m, "Vector_ul") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + py::bind_vector>(m, "Vector_f") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + py::bind_vector>(m, "Vector_d") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + + py::bind_vector>(m, "Vector_Vector3f") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + py::bind_vector>(m, "Vector_Vector3i") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + py::bind_vector>(m, "Vector_Vector4f") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + py::bind_vector>(m, "Vector_Vector4i") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + py::bind_vector>(m, "Vector_Vector3d") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + py::bind_vector>(m, "Vector_Vector4d") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + py::bind_vector>(m, "Vector_Voxel") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + py::bind_vector>(m, "Vector_VoxRaytracerRayDataElement") + .def("MoveToVRAM", &uLib::Vector::MoveToVRAM) + .def("MoveToRAM", &uLib::Vector::MoveToRAM); + + // 4. Accumulators + py::class_>(m, "Accumulator_Mean_f") + .def(py::init<>()) + .def("AddPass", &Accumulator_Mean::AddPass) + .def("__call__", py::overload_cast(&Accumulator_Mean::operator())) + .def("__call__", py::overload_cast<>(&Accumulator_Mean::operator(), py::const_)); + + py::class_>(m, "Accumulator_Mean_d") + .def(py::init<>()) + .def("AddPass", &Accumulator_Mean::AddPass) + .def("__call__", py::overload_cast(&Accumulator_Mean::operator())) + .def("__call__", py::overload_cast<>(&Accumulator_Mean::operator(), py::const_)); + + py::class_>(m, "Accumulator_ABTrim_f") + .def(py::init<>()) + .def("SetABTrim", &Accumulator_ABTrim::SetABTrim) + .def("__iadd__", [](Accumulator_ABTrim &self, float val) { self += val; return &self; }) + .def("__call__", &Accumulator_ABTrim::operator()); + + py::class_>(m, "Accumulator_ABTrim_d") + .def(py::init<>()) + .def("SetABTrim", &Accumulator_ABTrim::SetABTrim) + .def("__iadd__", [](Accumulator_ABTrim &self, double val) { self += val; return &self; }) + .def("__call__", &Accumulator_ABTrim::operator()); + + py::class_>(m, "Accumulator_ABClip_f") + .def(py::init<>()) + .def("SetABTrim", &Accumulator_ABClip::SetABTrim) + .def("__iadd__", [](Accumulator_ABClip &self, float val) { self += val; return &self; }) + .def("__call__", &Accumulator_ABClip::operator()); + + py::class_>(m, "Accumulator_ABClip_d") + .def(py::init<>()) + .def("SetABTrim", &Accumulator_ABClip::SetABTrim) + .def("__iadd__", [](Accumulator_ABClip &self, double val) { self += val; return &self; }) + .def("__call__", &Accumulator_ABClip::operator()); + + // 5. Core Math Structures py::class_(m, "AffineTransform") .def(py::init<>()) .def("GetWorldMatrix", &AffineTransform::GetWorldMatrix) @@ -34,13 +176,11 @@ void init_math(py::module_ &m) { .def("EulerYZYRotate", &AffineTransform::EulerYZYRotate) .def("FlipAxes", &AffineTransform::FlipAxes); - // Math/Geometry.h py::class_(m, "Geometry") .def(py::init<>()) .def("GetWorldPoint", py::overload_cast(&Geometry::GetWorldPoint, py::const_)) .def("GetLocalPoint", py::overload_cast(&Geometry::GetLocalPoint, py::const_)); - // Math/ContainerBox.h py::class_(m, "ContainerBox") .def(py::init<>()) .def("SetOrigin", &ContainerBox::SetOrigin) @@ -51,7 +191,6 @@ void init_math(py::module_ &m) { .def("GetWorldPoint", py::overload_cast(&ContainerBox::GetWorldPoint, py::const_)) .def("GetLocalPoint", py::overload_cast(&ContainerBox::GetLocalPoint, py::const_)); - // Math/StructuredData.h py::enum_(m, "StructuredDataOrder") .value("CustomOrder", StructuredData::CustomOrder) .value("XYZ", StructuredData::XYZ) @@ -74,7 +213,6 @@ void init_math(py::module_ &m) { .def("Map", &StructuredData::Map) .def("UnMap", &StructuredData::UnMap); - // Math/StructuredGrid.h py::class_(m, "StructuredGrid") .def(py::init()) .def("SetSpacing", &StructuredGrid::SetSpacing) @@ -84,7 +222,6 @@ void init_math(py::module_ &m) { return self.Find(HPoint3f(pt)); }); - // Math/Structured2DGrid.h py::class_(m, "Structured2DGrid") .def(py::init<>()) .def("SetDims", &Structured2DGrid::SetDims) @@ -100,7 +237,6 @@ void init_math(py::module_ &m) { .def("UnitToPhysicsSpace", &Structured2DGrid::UnitToPhysicsSpace) .def("SetDebug", &Structured2DGrid::SetDebug); - // Math/Structured4DGrid.h py::class_(m, "Structured4DGrid") .def(py::init<>()) .def("SetDims", &Structured4DGrid::SetDims) @@ -116,7 +252,36 @@ void init_math(py::module_ &m) { .def("UnitToPhysicsSpace", &Structured4DGrid::UnitToPhysicsSpace) .def("SetDebug", &Structured4DGrid::SetDebug); - // Math/TriangleMesh.h + // 6. High-level Structures + py::class_(m, "Voxel") + .def(py::init<>()) + .def_readwrite("Value", &Voxel::Value); + + py::class_(m, "AbstractVoxImage") + .def("GetValue", py::overload_cast(&Abstract::VoxImage::GetValue, py::const_)) + .def("GetValue", py::overload_cast(&Abstract::VoxImage::GetValue, py::const_)) + .def("SetValue", py::overload_cast(&Abstract::VoxImage::SetValue)) + .def("SetValue", py::overload_cast(&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_, Abstract::VoxImage>(m, "VoxImage") + .def(py::init<>()) + .def(py::init()) + .def("Data", &VoxImage::Data, py::return_value_policy::reference_internal) + .def("InitVoxels", &VoxImage::InitVoxels) + .def("Abs", &VoxImage::Abs) + .def("clipImage", py::overload_cast(&VoxImage::clipImage, py::const_)) + .def("clipImage", py::overload_cast(&VoxImage::clipImage, py::const_)) + .def("clipImage", py::overload_cast(&VoxImage::clipImage, py::const_)) + .def("maskImage", py::overload_cast(&VoxImage::maskImage, py::const_)) + .def("maskImage", py::overload_cast(&VoxImage::maskImage, py::const_), py::arg("threshold"), py::arg("belowValue") = 0, py::arg("aboveValue") = 0) + .def("fixVoxels", py::overload_cast(&VoxImage::fixVoxels, py::const_)) + .def("__getitem__", py::overload_cast(&VoxImage::operator[])) + .def("__getitem__", py::overload_cast(&VoxImage::operator[])); + py::class_(m, "TriangleMesh") .def(py::init<>()) .def("AddPoint", &TriangleMesh::AddPoint) @@ -124,7 +289,6 @@ void init_math(py::module_ &m) { .def("Points", &TriangleMesh::Points, py::return_value_policy::reference_internal) .def("Triangles", &TriangleMesh::Triangles, py::return_value_policy::reference_internal); - // Math/VoxRaytracer.h py::class_(m, "VoxRaytracerRayDataElement") .def(py::init<>()) .def_readwrite("vox_id", &VoxRaytracer::RayData::Element::vox_id) @@ -133,6 +297,7 @@ void init_math(py::module_ &m) { py::class_(m, "VoxRaytracerRayData") .def(py::init<>()) .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("TotalLength", &VoxRaytracer::RayData::TotalLength) .def("SetCount", &VoxRaytracer::RayData::SetCount) @@ -140,13 +305,8 @@ void init_math(py::module_ &m) { py::class_(m, "VoxRaytracer") .def(py::init(), py::keep_alive<1, 2>()) - .def("GetImage", &VoxRaytracer::GetImage, py::return_value_policy::reference_internal); - - // Math/Accumulator.h - py::class_>(m, "Accumulator_Mean_f") - .def(py::init<>()) - .def("AddPass", &Accumulator_Mean::AddPass) - .def("__call__", py::overload_cast(&Accumulator_Mean::operator())) - .def("__call__", py::overload_cast<>(&Accumulator_Mean::operator(), py::const_)); + .def("GetImage", &VoxRaytracer::GetImage, py::return_value_policy::reference_internal) + .def("TraceLine", &VoxRaytracer::TraceLine) + .def("TraceBetweenPoints", &VoxRaytracer::TraceBetweenPoints); } diff --git a/src/Python/testing/math_pybind_test.py b/src/Python/testing/math_pybind_test.py index 795252c..d638fca 100644 --- a/src/Python/testing/math_pybind_test.py +++ b/src/Python/testing/math_pybind_test.py @@ -58,5 +58,71 @@ class TestMathAccumulator(unittest.TestCase): acc(20.0) self.assertAlmostEqual(acc(), 15.0) +class TestMathNewTypes(unittest.TestCase): + def test_eigen_vectors(self): + v1f = uLib_python.Math.Vector1f() + v3d = uLib_python.Math.Vector3d() + m4f = uLib_python.Math.Matrix4f() + self.assertIsNotNone(v1f) + self.assertIsNotNone(v3d) + self.assertIsNotNone(m4f) + + def test_ulib_vectors(self): + vi = uLib_python.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_python.Math.Vector_f() + vf.append(1.5) + self.assertAlmostEqual(vf[0], 1.5) + + def test_homogeneous(self): + p = uLib_python.Math.HPoint3f(1.0, 2.0, 3.0) + v = uLib_python.Math.HVector3f(0.0, 1.0, 0.0) + self.assertIsNotNone(p) + self.assertIsNotNone(v) + + def test_vox_image(self): + img = uLib_python.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_python.Math.StructuredGrid([10, 10, 10]) + grid.SetSpacing([1, 1, 1]) + grid.SetOrigin([0, 0, 0]) + + rt = uLib_python.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_python.Math.VoxRaytracerRayData() + data.SetCount(10) + data.SetTotalLength(5.5) + self.assertEqual(data.Count(), 10) + self.assertAlmostEqual(data.TotalLength(), 5.5) + if __name__ == '__main__': unittest.main()