andrea-dev #1

Merged
andrea merged 19 commits from andrea-dev into main 2026-03-06 17:17:52 +01:00
9 changed files with 110 additions and 92 deletions
Showing only changes of commit 69920acd61 - Show all commits

6
.gitignore vendored
View File

@@ -4,4 +4,8 @@ build/
build_warnings*.log
final_build.log
cmake_configure.log
compile_commands.json
compile_commands.json
dist/
build_python/
src/Python/uLib/*.so*

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"

View File

@@ -1,10 +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 = [
"setuptools>=42",
"wheel",
"pybind11>=2.6.0",
"cmake>=3.12",
"ninja"]
build-backend = "setuptools.build_meta"
requires = ["poetry-core>=2.0.0", "pybind11>=2.6.0", "cmake>=3.12"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,52 +0,0 @@
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",
)

View File

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

View File

@@ -3,7 +3,7 @@ import os
import unittest
import numpy as np
import uLib_python
import uLib
def vector4f0(v, target):
diff = np.array(v) - np.array(target)
@@ -12,7 +12,7 @@ def vector4f0(v, target):
class TestMathGeometry(unittest.TestCase):
def test_geometry(self):
Geo = uLib_python.Math.Geometry()
Geo = uLib.Math.Geometry()
Geo.SetPosition([1, 1, 1])
@@ -27,7 +27,7 @@ class TestMathGeometry(unittest.TestCase):
class TestMathContainerBox(unittest.TestCase):
def test_container_box_local(self):
Cnt = uLib_python.Math.ContainerBox()
Cnt = uLib.Math.ContainerBox()
Cnt.SetOrigin([-1, -1, -1])
Cnt.SetSize([2, 2, 2])
@@ -35,7 +35,7 @@ class TestMathContainerBox(unittest.TestCase):
self.assertTrue(np.allclose(size, [2, 2, 2]))
def test_container_box_global(self):
Box = uLib_python.Math.ContainerBox()
Box = uLib.Math.ContainerBox()
Box.SetPosition([1, 1, 1])
Box.SetSize([2, 2, 2])
@@ -45,7 +45,7 @@ class TestMathContainerBox(unittest.TestCase):
class TestMathStructuredGrid(unittest.TestCase):
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])
spacing = grid.GetSpacing()
@@ -53,40 +53,40 @@ class TestMathStructuredGrid(unittest.TestCase):
class TestMathAccumulator(unittest.TestCase):
def test_accumulator_mean(self):
acc = uLib_python.Math.Accumulator_Mean_f()
acc = uLib.Math.Accumulator_Mean_f()
acc(10.0)
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()
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_python.Math.Vector_i()
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_python.Math.Vector_f()
vf = uLib.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)
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_python.Math.VoxImage([2, 2, 2])
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)
@@ -96,11 +96,11 @@ class TestMathNewTypes(unittest.TestCase):
class TestMathVoxRaytracer(unittest.TestCase):
def test_raytracer(self):
grid = uLib_python.Math.StructuredGrid([10, 10, 10])
grid = uLib.Math.StructuredGrid([10, 10, 10])
grid.SetSpacing([1, 1, 1])
grid.SetOrigin([0, 0, 0])
rt = uLib_python.Math.VoxRaytracer(grid)
rt = uLib.Math.VoxRaytracer(grid)
self.assertIsNotNone(rt)
# Test TraceBetweenPoints
@@ -118,7 +118,7 @@ class TestMathVoxRaytracer(unittest.TestCase):
self.assertGreater(elements[i].L, 0)
def test_ray_data(self):
data = uLib_python.Math.VoxRaytracerRayData()
data = uLib.Math.VoxRaytracerRayData()
data.SetCount(10)
data.SetTotalLength(5.5)
self.assertEqual(data.Count(), 10)

View File

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