11 Commits

Author SHA1 Message Date
40846bba78 Update .gitea/workflows/publish-docs.yaml
All checks were successful
MkDocs Subpath Deploy / build-and-deploy (push) Successful in 11s
2026-03-06 17:52:45 +01:00
4d681e3373 Update .gitea/workflows/publish-docs.yaml
Some checks failed
MkDocs Subpath Deploy / build-and-deploy (push) Failing after 12s
2026-03-06 17:51:53 +01:00
3a9efd5598 Update .gitea/workflows/publish-docs.yaml 2026-03-06 17:50:13 +01:00
fa1930f9d7 Merge pull request 'andrea-dev' (#1) from andrea-dev into main
Reviewed-on: #1
added CUDA for images and raytracing, added python bindings via pybind11, removed LTK, added documentation
2026-03-06 17:17:52 +01:00
AndreaRigoni
b64afe8773 add documention workflow 2026-03-06 10:45:33 +00:00
AndreaRigoni
f3ebba4931 add thrust 2026-03-06 10:45:14 +00:00
AndreaRigoni
79e1abb2ff add USE_CUDA env in python_build 2026-03-05 15:17:30 +00:00
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
39 changed files with 2428 additions and 103 deletions

View File

@@ -0,0 +1,42 @@
name: MkDocs Subpath Deploy
on:
workflow_dispatch: # trigger manuale
push:
branches:
- main # Trigger on main branch
jobs:
build-and-deploy:
runs-on: mildpub # Runner that can access to SSH_YFINPUB_HOST
steps:
- name: Checkout del codice
uses: actions/checkout@v4
- name: Configura Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Installa dipendenze
run: |
python -m pip install --upgrade pip
pip install mkdocs-material
pip install -r docs/docker/requirements.txt
- name: Build del sito
run: mkdocs build
- name: Deploy via SSH (SCP)
uses: https://github.com/appleboy/scp-action@master
with:
host: ${{ vars.SSH_YFINPUB_HOST }}
username: ${{ vars.SSH_YFINPUB_USER }}
key: ${{ secrets.MILD_PUB }}
port: 22
source: "site/*"
# Il percorso sul server deve corrispondere alla tua sottopagina
target: "/var/www/docs/cmt/uLib/"
strip_components: 1 # Rimuove la cartella "site/" e mette solo il contenuto
rm: true # Pulisce la cartella prima di copiare (opzionale, stile Vercel)

8
.gitignore vendored
View File

@@ -5,3 +5,11 @@ 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__
src/Python/uLib/.nfs*

16
CMakePresets.json Normal file
View File

@@ -0,0 +1,16 @@
{
"version": 8,
"configurePresets": [
{
"name": "andrea",
"displayName": "Custom configure preset",
"description": "Sets Ninja generator, build and install directory",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}"
}
}
]
}

View File

@@ -61,3 +61,10 @@ cmake --preset conan-release
```bash ```bash
cmake --build build -j10 cmake --build build -j10
``` ```
### Make python package
```bash
micromamba run -n mutom env USE_CUDA=ON poetry install
```

54
build_python.py Normal file
View File

@@ -0,0 +1,54 @@
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}")
# Determine if CUDA should be enabled
use_cuda = os.environ.get("USE_CUDA", "OFF").upper()
if use_cuda in ["ON", "1", "TRUE", "YES"]:
use_cuda = "ON"
else:
use_cuda = "OFF"
# CMake configuration
cmake_args = [
f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={package_dir}",
f"-DPYTHON_EXECUTABLE={sys.executable}",
"-DCMAKE_BUILD_TYPE=Release",
f"-DUSE_CUDA={use_cuda}",
"-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({})

View File

@@ -0,0 +1,41 @@
.tabbed-set {
display: flex;
position: relative;
flex-wrap: wrap;
}
.tabbed-set .highlight {
background: #ddd;
}
.tabbed-set .tabbed-content {
display: none;
order: 99;
width: 100%;
}
.tabbed-set label {
width: auto;
margin: 0 0.5em;
padding: 0.25em;
font-size: 120%;
cursor: pointer;
color: #ffffff !important;
}
.tabbed-set input {
position: absolute;
opacity: 0;
}
.tabbed-set input:nth-child(n+1) {
color: #333333;
}
.tabbed-set input:nth-child(n+1):checked + label {
color: cyan !important;
}
.tabbed-set input:nth-child(n+1):checked + label + .tabbed-content {
display: block;
}

17
docs/assets/css/extra.css Normal file
View File

@@ -0,0 +1,17 @@
@import "extensions/tabbed.css";
.md-grid {
max-width: 100%;
}
.md-main__inner {
margin-top: 0;
padding-top: 0;
}
.md-sidebar--secondary {
right: 1.5rem;
top: 4.8rem;
transform: none;
width: 18rem;
}

30
docs/docker/Dockerfile Normal file
View File

@@ -0,0 +1,30 @@
# Stage 1: Build the static site using MkDocs
FROM python:3.9-slim-buster as builder
# Set the working directory
WORKDIR /app
# Copy the requirements file
COPY requirements.txt .
# Install the Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of the application files
COPY ../.. .
# Build the MkDocs site
RUN mkdocs build
# Stage 2: Serve the static files with Nginx
FROM nginx:alpine
# Copy the built site from the builder stage
COPY --from=builder /app/site /usr/share/nginx/html
# Expose port 80 for the web server
EXPOSE 80
# Command to run Nginx in the foreground
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -0,0 +1,14 @@
# Dockerfile for development with live-reloading
FROM python:3.9-slim-buster
WORKDIR /app
# Copy and install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Expose the port MkDocs serve will run on
EXPOSE 8000
# Command to run the development server
CMD ["mkdocs", "serve", "--dev-addr", "0.0.0.0:8000"]

View File

@@ -0,0 +1,13 @@
version: '3.8'
services:
mkdocs:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "8000:8000"
volumes:
- ../..:/app
environment:
- GIT_DISCOVERY_ACROSS_FILESYSTEM=1

View File

@@ -0,0 +1,17 @@
# ------------------------------------------------------------------
# MkDocs runtime dependencies for the docs Docker image
# ------------------------------------------------------------------
# Core: theme (provides mkdocs itself as a transitive dep)
mkdocs-material==9.7.1
# pymdownx.* extensions used in mkdocs.yml:
# arithmatex, highlight, superfences, tabbed, details, blocks.caption
# (also a hard dep of mkdocs-material, pinned here for reproducibility)
pymdown-extensions>=10.0
# Markdown math rendering support (arithmatex generic mode)
# JS side is loaded via CDN (polyfill.io + MathJax), no extra Python pkg needed
# Optional: PDF export plugin (exporter: block, currently commented out in mkdocs.yml)
mkdocs-exporter

1
docs/docker/runtime.txt Normal file
View File

@@ -0,0 +1 @@
3.7

63
docs/index.md Normal file
View File

@@ -0,0 +1,63 @@
# uLib
[![DOI](https://zenodo.org/badge/36926725.svg)](https://zenodo.org/badge/latestdoi/36926725)
**uLib** is the base toolkit library for the **CMT (Cosmic Muon Tomography)** project, developed at the University of Padova and INFN Sezione di Padova, Italy.
It provides:
- **Core** object model, timers, configuration, UUID utilities.
- **Math** linear algebra (Eigen3), structured grids, voxel images, ray-tracing, image filters.
- **Python bindings** full pybind11 interface for scripting and analysis workflows.
- Optional **CUDA** acceleration for voxel filtering (transparent RAM ↔ VRAM management).
---
## Quick Start
=== "Users (pip / poetry)"
```bash
# Activate your conda/micromamba environment first
micromamba activate mutom
poetry install # CPU build
USE_CUDA=ON poetry install # GPU build
```
=== "Developers (CMake)"
```bash
conan install . --output-folder=build --build=missing
cmake --preset conan-release
cmake --build build --target uLib_python -j$(nproc)
export PYTHONPATH="$(pwd)/build/src/Python:$(pwd)/src/Python"
```
Then in Python:
```python
import uLib
# Core
timer = uLib.Core.Timer()
timer.Start()
# Math
grid = uLib.Math.StructuredGrid([10, 10, 10])
grid.SetSpacing([1.0, 1.0, 1.0])
img = uLib.Math.VoxImage([10, 10, 10])
img.SetValue(0, 3.14)
print(img.GetValue(0))
```
---
## Documentation Sections
| Section | Description |
|---|---|
| [Python Installation](python/installation.md) | Environment setup, user install, developer build |
| [Python API Usage](python/usage.md) | Full API reference with examples |
| [Python Developer Guide](python/developer_guide.md) | Adding bindings, running tests, build details |
| [C++ Build Usage & CUDA](usage/usage.md) | CMake build, CUDA configuration |

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)

146
docs/python/installation.md Normal file
View File

@@ -0,0 +1,146 @@
# Python Installation
The `uLib` Python package exposes the Core and Math C++ libraries via [pybind11](https://pybind11.readthedocs.io) bindings. There are two ways to install it: as an **end user** (pre-built wheel / pip) or as a **developer** (editable build from source).
---
## Prerequisites
`uLib` depends on native C++ libraries that must be compiled. Ensure the following are available in your environment before installing:
| Dependency | Minimum version | Notes |
|---|---|---|
| Python | 3.9 | |
| CMake | 3.12 | |
| pybind11 | 2.6.0 | |
| Conan | 2.x | for Eigen3 / Boost |
| micromamba / conda | any | recommended provides ROOT, VTK |
### Creating the `mutom` Conda/Micromamba Environment
A ready-to-use environment definition is provided as `condaenv.yml` at the repository root.
=== "Micromamba"
```bash
micromamba env create -f condaenv.yml
micromamba activate mutom
```
=== "Conda"
```bash
conda env create -f condaenv.yml
conda activate mutom
```
The environment installs CMake, Conan, ROOT, VTK, and the compiler toolchain.
> **CUDA (optional)**
> If you want GPU-accelerated voxel filtering, you also need NVCC inside the environment:
> ```bash
> micromamba install cuda-nvcc -c conda-forge
> ```
---
## User Installation (wheel / pip)
Once the native dependencies are present in your environment, install the package with Poetry or pip:
```bash
# Activate your environment first
micromamba activate mutom
# Build and install (CUDA disabled by default)
poetry install
# Build and install with CUDA support
USE_CUDA=ON poetry install
```
After installation the module is importable from anywhere in the environment:
```python
import uLib
print(dir(uLib.Core))
print(dir(uLib.Math))
```
---
## Developer Installation (editable / in-tree build)
For development you typically want to skip the packaging layer and work directly against the CMake build tree.
### Step 1 Install Conan dependencies
```bash
conan profile detect # first time only
conan install . --output-folder=build --build=missing
```
### Step 2 Configure and build
```bash
# Standard release build
cmake --preset conan-release
# …or manually
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \
-DCMAKE_BUILD_TYPE=Release \
-DUSE_CUDA=OFF # set to ON when a GPU is available
cmake --build build --target uLib_python -j$(nproc)
```
The shared library (`uLib_python*.so`) is written to `build/src/Python/`.
### Step 3 Make the module importable
Point `PYTHONPATH` at the build output **and** the Python source directory (the latter carries the `uLib/__init__.py` that stitches sub-modules together):
```bash
export PYTHONPATH="$(pwd)/build/src/Python:$(pwd)/src/Python:$PYTHONPATH"
python -c "import uLib; print(uLib.__version__)"
```
Or, for a one-shot check:
```bash
PYTHONPATH="build/src/Python:src/Python" python src/Python/testing/pybind_test.py
```
### Step 4 Run the tests
CMake registers the Python tests alongside the C++ ones; use `ctest` from the build directory:
```bash
cd build
ctest --output-on-failure -R pybind
```
Individual test scripts can also be run directly once `PYTHONPATH` is set:
```bash
python src/Python/testing/core_pybind_test.py
python src/Python/testing/math_pybind_test.py
python src/Python/testing/math_filters_test.py
```
---
## Verifying the Installation
```python
import uLib
# Core module
obj = uLib.Core.Object()
timer = uLib.Core.Timer()
timer.Start()
elapsed = timer.StopWatch() # float, seconds
# Math module
v3 = uLib.Math.Vector3f([1.0, 0.0, 0.0])
print(v3[0]) # 1.0
```

373
docs/python/usage.md Normal file
View File

@@ -0,0 +1,373 @@
# Python API Usage
The `uLib` Python package is split into two sub-modules mirroring the C++ library:
| Sub-module | Contents |
|---|---|
| `uLib.Core` | Low-level utilities: `Object`, `Timer`, `Options`, `TypeRegister` |
| `uLib.Math` | Geometry, grids, voxel images, ray-tracing, image filters |
```python
import uLib
# Sub-modules are accessible as attributes
uLib.Core # core utilities
uLib.Math # mathematical structures
```
---
## uLib.Core
### Object
Base class for uLib objects; exposed to Python for type-hierarchy purposes.
```python
obj = uLib.Core.Object()
copy = obj.DeepCopy()
```
### Timer
Precision wall-clock timer.
```python
import time
timer = uLib.Core.Timer()
timer.Start()
time.sleep(0.5)
elapsed = timer.StopWatch() # returns elapsed seconds as float
print(f"Elapsed: {elapsed:.3f} s")
```
### Options
Wraps Boost.ProgramOptions for INI-style configuration files.
```python
opt = uLib.Core.Options("My Program")
opt.parse_config_file("config.ini") # load settings
n = opt.count("my_key") # check if key exists
opt.save_config_file("out.ini")
```
---
## uLib.Math Linear Algebra
The math module exposes Eigen3 vectors and matrices as well-typed Python objects with NumPy interoperability.
### Fixed-size Vectors
```python
import numpy as np
import uLib
# Construct from list
v3 = uLib.Math.Vector3f([1.0, 2.0, 3.0])
print(v3[0], v3[1], v3[2]) # 1.0 2.0 3.0
# Construct from NumPy array
arr = np.array([4.0, 5.0, 6.0], dtype=np.float32)
v3b = uLib.Math.Vector3f(arr)
# Zero-initialise
v4d = uLib.Math.Vector4d() # all zeros
# Available types
# Vector1f / 2f / 3f / 4f (float32)
# Vector1d / 2d / 3d / 4d (float64)
# Vector1i / 2i / 3i / 4i (int32)
```
### Fixed-size Matrices
```python
# 2-by-2 float matrix
m2f = uLib.Math.Matrix2f()
m2f[0, 0] = 1; m2f[0, 1] = 2
m2f[1, 0] = 3; m2f[1, 1] = 4
# From list (row-major)
m4f = uLib.Math.Matrix4f([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1])
# From NumPy (2-D array)
mat = np.eye(3, dtype=np.float32)
m3f = uLib.Math.Matrix3f(mat)
# Dynamic matrices
mXf = uLib.Math.MatrixXf(4, 4) # 4×4 float, zeros
```
### Homogeneous Types
```python
# HPoint3f a 3-D point in homogeneous coordinates (w = 1)
p = uLib.Math.HPoint3f(1.0, 2.0, 3.0)
# HVector3f a free vector (w = 0)
v = uLib.Math.HVector3f(0.0, 1.0, 0.0)
# HLine3f a parametric ray
line = uLib.Math.HLine3f()
line.origin = uLib.Math.HPoint3f(0, 0, 0)
line.direction = uLib.Math.HVector3f(0, 0, 1)
```
---
## uLib.Math Transforms and Geometry
### AffineTransform
A rigid-body / affine transform stored as a 4×4 matrix.
```python
tf = uLib.Math.AffineTransform()
tf.SetPosition([1.0, 0.0, 0.0]) # translate
tf.Translate([0.0, 1.0, 0.0]) # cumulative translate
tf.Scale([2.0, 2.0, 2.0]) # uniform scale
tf.Rotate(uLib.Math.Vector3f([0, 0, 3.14159])) # Euler angles (rad)
mat4 = tf.GetWorldMatrix() # 4×4 matrix
pos = tf.GetPosition() # Vector3f
```
### Geometry
Inherits `AffineTransform`; converts points between world and local frames.
```python
geo = uLib.Math.Geometry()
geo.SetPosition([1.0, 1.0, 1.0])
world_pt = uLib.Math.Vector4f([2.0, 3.0, 2.0, 1.0])
local_pt = geo.GetLocalPoint(world_pt)
back = geo.GetWorldPoint(local_pt)
# back ≈ [2, 3, 2, 1]
```
### ContainerBox
An axis-aligned bounding box with an associated transform.
```python
box = uLib.Math.ContainerBox()
box.SetOrigin([-1.0, -1.0, -1.0])
box.SetSize([2.0, 2.0, 2.0])
print(box.GetSize()) # [2, 2, 2]
```
---
## uLib.Math Structured Grids
### StructuredGrid (3-D)
A 3-D voxel grid (origin, spacing, and integer dimensions).
```python
dims = uLib.Math.Vector3i([10, 10, 10])
grid = uLib.Math.StructuredGrid(dims)
grid.SetSpacing([1.0, 1.0, 1.0])
grid.SetOrigin([0.0, 0.0, 0.0])
print(grid.GetSpacing()) # [1, 1, 1]
print(grid.IsInsideBounds([5, 5, 5, 1])) # True
idx = grid.Find([2.5, 2.5, 2.5]) # returns grid cell index
```
### Structured2DGrid / Structured4DGrid
```python
grid2d = uLib.Math.Structured2DGrid()
grid2d.SetDims([100, 100])
grid2d.SetPhysicalSpace([0, 0], [1, 1])
print(grid2d.GetSpacing())
```
---
## uLib.Math VoxImage
`VoxImage` is a 3-D voxel volume where each cell stores a `Voxel` ( `.Value` + `.Count`).
```python
dims = uLib.Math.Vector3i([20, 20, 20])
img = uLib.Math.VoxImage(dims)
img.SetSpacing([0.5, 0.5, 0.5])
# Access by linear index
img.SetValue(0, 42.0)
print(img.GetValue(0)) # 42.0
# Access by 3-D index
img.SetValue(uLib.Math.Vector3i([1, 1, 1]), 7.5)
print(img.GetValue(uLib.Math.Vector3i([1, 1, 1]))) # 7.5
# Clipping / masking helpers
cropped = img.clipImage(uLib.Math.Vector3i([2, 2, 2]),
uLib.Math.Vector3i([18, 18, 18]))
masked = img.maskImage(0.0, 100.0, 0.0) # mask outside [0, 100]
# I/O
img.ExportToVti("output.vti")
img.ImportFromVti("output.vti")
```
### Voxel (element type)
```python
vox = uLib.Math.Voxel()
vox.Value = 1.5
vox.Count = 3
data = img.Data() # returns the underlying Vector_Voxel
vox0 = data[0]
print(vox0.Value, vox0.Count)
```
---
## uLib.Math VoxRaytracer
Performs ray-tracing through a `StructuredGrid` and returns per-voxel chord lengths.
```python
import numpy as np
import uLib
grid = uLib.Math.StructuredGrid([10, 10, 10])
grid.SetSpacing([1.0, 1.0, 1.0])
grid.SetOrigin([0.0, 0.0, 0.0])
rt = uLib.Math.VoxRaytracer(grid)
# Trace a ray between two homogeneous points (x, y, z, w=1)
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)
result = rt.TraceBetweenPoints(p1, p2)
print("Voxels crossed:", result.Count())
print("Total length :", result.TotalLength())
elements = result.Data()
for i in range(result.Count()):
print(f" vox_id={elements[i].vox_id} L={elements[i].L:.4f}")
```
---
## uLib.Math Image Filters
All filters share the same interface: construct with a kernel size, attach a `VoxImage`, optionally set parameters, then call `.Run()`.
```python
import uLib
dims = uLib.Math.Vector3i([10, 10, 10])
img = uLib.Math.VoxImage(dims)
for i in range(10**3):
img.SetValue(i, float(i))
kernel_dims = uLib.Math.Vector3i([3, 3, 3])
```
### Linear (Gaussian / Box) Filter
```python
filt = uLib.Math.VoxFilterAlgorithmLinear(kernel_dims)
filt.SetImage(img)
filt.SetKernelNumericXZY([1.0] * 27) # uniform box kernel, length = product of dims
filt.Run()
```
### ABTrim Filter
Applies alpha-beta trimming to remove outliers before averaging.
```python
filt = uLib.Math.VoxFilterAlgorithmAbtrim(kernel_dims)
filt.SetImage(img)
filt.SetKernelNumericXZY([1.0] * 27)
filt.SetABTrim(2, 2) # trim 2 low and 2 high values
filt.Run()
```
### Bilateral Filter
Edge-preserving smoothing controlled by a spatial sigma (from the kernel shape) and an intensity sigma.
```python
filt = uLib.Math.VoxFilterAlgorithmBilateral(kernel_dims)
filt.SetImage(img)
filt.SetKernelNumericXZY([1.0] * 27)
filt.SetIntensitySigma(0.3)
filt.Run()
```
### Threshold Filter
Zeros voxels below a threshold.
```python
filt = uLib.Math.VoxFilterAlgorithmThreshold(kernel_dims)
filt.SetImage(img)
filt.SetKernelNumericXZY([1.0] * 27)
filt.SetThreshold(0.5)
filt.Run()
```
### Median Filter
```python
filt = uLib.Math.VoxFilterAlgorithmMedian(kernel_dims)
filt.SetImage(img)
filt.SetKernelNumericXZY([1.0] * 27)
filt.Run()
```
---
## uLib.Math Accumulators
Accumulators collect scalar samples and return a summary statistic.
```python
# Arithmetic mean
acc = uLib.Math.Accumulator_Mean_f()
acc(10.0)
acc(20.0)
mean = acc() # 15.0
# Alpha-beta trimmed mean
acc2 = uLib.Math.Accumulator_ABTrim_f()
acc2.SetABTrim(0.1, 0.1) # trim bottom 10 % and top 10 %
acc2 += 1.0
acc2 += 9999.0 # outlier
acc2 += 5.0
result = acc2() # trimmed mean ≈ 3.0
```
---
## Dynamic Vectors (`uLib.Math.Vector_*`)
Typed dynamic arrays backed by `uLib::Vector<T>` with optional CUDA memory management.
```python
# Integer vector
vi = uLib.Math.Vector_i()
vi.append(1); vi.append(2); vi.append(3)
print(len(vi), vi[0])
# Float vector with CUDA management
vf = uLib.Math.Vector_f()
vf.append(1.5)
vf.MoveToVRAM() # copy to GPU (no-op when CUDA is absent)
vf.MoveToRAM() # copy back to CPU
# Other types: Vector_ui, Vector_l, Vector_ul, Vector_d
# Compound element types: Vector_Vector3f, Vector_Vector4f, Vector_Voxel …
```

561
mkdocs.yml Normal file
View File

@@ -0,0 +1,561 @@
# site_name: uLib Documentation
# site_description: CMT Cosmic Muon Tomography uLib toolkit
# site_author: Andrea Rigoni Garola
# repo_url: https://github.com/cmt/ulib
# docs_dir: docs
# +--------------------------------------------------------------------------------------------------------+
# | |
# | This is the main file used by MkDocs to build the pages. |
# | It contains a lot of information and settings for you to read and use. |
# | Comments may contain "Read More" URLs to more in-depth documentation about the option for you to read. |
# | |
# | You can check out https://www.mkdocs.org/user-guide/configuration/ for a more detailed explanation of |
# | all the options MkDocs offers by default. |
# | |
# +------------------------------------------------- NOTE -------------------------------------------------+
# | |
# | Some of the options listed here are only available through the usage of Material for MkDocs. |
# | Those options will usually have a link to the docs of this Theme and also mention "Material" as name. |
# | The actual name of the theme is "Material for MkDocs" and "Material" is used for simplicity reasons. |
# | |
# +--------------------------------------------------------------------------------------------------------+
# +--------------------------------------------------------------------------------------------------------+
# | |
# | Main Page Settings for MkDocs. |
# | Those settings are site name, site description, Site author and also Site URL (Canonical URL) |
# | |
# | Read More: |
# | - https://www.mkdocs.org/user-guide/configuration/#site_name |
# | - https://www.mkdocs.org/user-guide/configuration/#site_description |
# | - https://www.mkdocs.org/user-guide/configuration/#site_author |
# | - https://www.mkdocs.org/user-guide/configuration/#site_url |
# | |
# +--------------------------------------------------------------------------------------------------------+
site_name: OpenCMT uLib Documentation
site_url: https://docs.mildstone.org/uLib/ # <--- project subfolder
use_directory_urls: true
site_description: 'Documentation for OpenCMT uLib'
site_author: 'Andrea Rigoni Garola'
# +--------------------------------------------------------------------------------------------------------+
# | |
# | This setting allows you to define your own Copyright notice. |
# | The text is treated as HTML code so you can use things like <a> tags or &copy; to display the |
# | Copyright icon. |
# | |
# | Where or IF the Copyright is displayed depends on the theme you use. |
# | |
# | Read More: |
# | - https://www.mkdocs.org/user-guide/configuration/#copyright |
# | |
# +--------------------------------------------------------------------------------------------------------+
copyright: |
&copy; Author
# +--------------------------------------------------------------------------------------------------------+
# | |
# | The base folder to use. |
# | Any Markdown files you put into this folder will be turned into a static HTML page once you build or |
# | publish your page. |
# | |
# | It is also used as the base directory for other settings like the "extra_css" or "extra_javascript" |
# | option. |
# | |
# +--------------------------------------------------------------------------------------------------------+
docs_dir: docs/
# +--------------------------------------------------------------------------------------------------------+
# | |
# | These options allow to define a Repository to link to. |
# | The result will, depending on the theme, be a link somewhere shown on the page that links to the |
# | Repository with the specified repo_name. |
# | |
# | This will also enable a "edit" button on the page itself that allows the direct editing of the page. |
# | You can disable this by setting "edit_uri" to an empty String. |
# | |
# | Read More: |
# | - https://www.mkdocs.org/user-guide/configuration/#repo_name |
# | - https://www.mkdocs.org/user-guide/configuration/#repo_url |
# | - https://www.mkdocs.org/user-guide/configuration/#edit_uri |
# | |
# +--------------------------------------------------------------------------------------------------------+
repo_name: OpenCMT/uLib
repo_url: https://gitea.mildstone.org/OpenCMT/uLib.git
#edit_uri: tree/master/docs # Uncomment to define a different URI/URL for the "edit" option
# +--------------------------------------------------------------------------------------------------------+
# | |
# | The "nav" option is where you define the navigation to show in MkDocs. |
# | |
# | Depending on the theme you use will the resulting Navigation look different. |
# | |
# | You can set different types of navigations. Either just the path, the path with a separate title or |
# | an external URL. |
# | |
# | Read More: |
# | - https://www.mkdocs.org/user-guide/configuration/#documentation-layout |
# | |
# +--------------------------------------------------------------------------------------------------------+
nav:
- Home: index.md
- Python:
- Installation: python/installation.md
- API Usage: python/usage.md
- Developer Guide: python/developer_guide.md
- C++ Build:
- Usage & CUDA: usage/usage.md
# +--------------------------------------------------------------------------------------------------------+
# | |
# | The "theme" section allows you to define what theme to use. |
# | It is also used for theme-specific options, but also for advanced stuff such as theme-extensions, if |
# | the theme actually supports it. |
# | |
# | Read More: |
# | - https://www.mkdocs.org/user-guide/configuration/#theme |
# | |
# +--------------------------------------------------------------------------------------------------------+
theme:
# +------------------------------------------------------------------------------------------------------+
# | |
# | The "name" option is where you define the theme to use. |
# | |
# | Note that not all themes are included by default and will require you to install them first. |
# | The Material theme is one of them. See the "Read More" link for instructions on how to install it. |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/getting-started/ |
# | |
# +------------------------------------------------------------------------------------------------------+
name: 'material'
# +------------------------------------------------------------------------------------------------------+
# | |
# | The Material theme allows "theme-extsnions", meaning that you can override parts of it by either |
# | overriding a particular file, or only parts (blocks) of it. |
# | |
# | If you want to override parts of Material, uncomment the "custom_dir" option below and set the |
# | folder (relative to the mkdocs.yml file) where your theme extensions will be located at. |
# | |
# | Read More: |
# | - https://www.mkdocs.org/user-guide/configuration/#custom_dir |
# | - https://squidfunk.github.io/mkdocs-material/customization/#extending-the-theme |
# | |
# +------------------------------------------------------------------------------------------------------+
#custom_dir: 'theme'
# +------------------------------------------------------------------------------------------------------+
# | |
# | The "favicon" option allows you to set your own image/icon to use in the browser-tab. |
# | |
# | Pretty much all image types are supported, but it's recommended to use a PNG, SVG or ICO image for |
# | the favicon. |
# | |
# | The directory is relative to the "docs_dir". |
# | |
# | Example: Having a favicon.png in docs/assets/images will result in the "favicon" setting showing |
# | 'assets/images/favicon.png' |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/setup/changing-the-logo-and-icons/#favicon |
# | |
# +------------------------------------------------------------------------------------------------------+
#favicon: 'assets/images/favicon.png'
# +------------------------------------------------------------------------------------------------------+
# | |
# | The "palette" section is a Material option and allows you to define specific style options such as |
# | Color-Scheme, and primary and secondary Color. |
# | |
# | You can also define multiple palettes that can have different Color Schemses and primary and/or |
# | secondary Colors. |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/ |
# | - https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/#color-palette-toggle |
# | |
# +------------------------------------------------------------------------------------------------------+
palette:
# Palette toggle for light mode
- media: "(prefers-color-scheme: light)"
scheme: default
primary: 'indigo'
accent: 'indigo'
toggle:
icon: material/brightness-7
name: Switch to dark mode
# Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: 'indigo'
accent: 'indigo'
toggle:
icon: material/brightness-4
name: Switch to light mode
# +------------------------------------------------------------------------------------------------------+
# | |
# | With the "font" option can you set a different font to use. |
# | |
# | Material supports all Google fonts, but you can also define your own ones if you choose so. |
# | |
# | The "text" option is used for the regular font while "code" is used for code blocks, inline code and |
# | similar. |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/setup/changing-the-fonts/ |
# | |
# +------------------------------------------------------------------------------------------------------+
#font:
# text: 'Roboto'
# code: 'Roboto Mono'
# +------------------------------------------------------------------------------------------------------+
# | |
# | Material suppors more than 40 different languages which you can set using the "language" option |
# | below. |
# | |
# | The default language is "en" (English). |
# | |
# | You can also enable/set a "selector" to allow switching between languages. |
# | See the "alternate" option in the "extra" section below for more information on this topic. |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/setup/changing-the-language/ |
# | |
# +------------------------------------------------------------------------------------------------------+
#language: 'en'
# +------------------------------------------------------------------------------------------------------+
# | |
# | The "direction" option is commonly used together with the "language" option. |
# | |
# | It allows you to change the text direction from the default left-to-right (ltr) to right-to-left |
# | (rtl) which is used in certain languages. |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/setup/changing-the-language/#directionality |
# | |
# +------------------------------------------------------------------------------------------------------+
#direction: 'ltr'
# +------------------------------------------------------------------------------------------------------+
# | |
# | The "features" option allows you to enable specific features of Material, by adding them to the |
# | list. |
# | |
# | Features are in the format <category>.<name>. As an example, the feature to enable tabs is called |
# | navigation.tabs. |
# | |
# | The list below contains all known features of Material. |
# | |
# | Features marked with a * are currently Insiders-only. (Last update: 11th December 2021) |
# | https://squidfunk.github.io/mkdocs-material/insiders/ |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/ |
# | |
# +------------------------------------------------------------------------------------------------------+
features:
# Announce
#
#- announce.dismiss # Adds a "X" button to dismiss a news banner/mark it as read.*
# Header
#
#- header.autohide # Hide header when user scrolls past a specific point.
# Navigation:
#
#- navigation.expand # Expand all collapsable sections.
#- navigation.instant # Instant loading pages.
#- navigation.indexes # Attach pages directly to Sections. Incompatible with "toc.integrate"
#- navigation.sections # Render top sections as groups.
- navigation.tabs # Render top sections as tabs at the top.
#- navigation.tabs.sticky # Tabs won't disappear when scrolling down. Requires "navigation.tabs".
#- navigation.top # Adds a "Back to top" that is shown when scrolling up.
#- navigation.tracking # Updates the url with highlighted section anchor.
# Search
#
#- search.highlight # Search will highlight the searched word(s) on the page.*
#- search.share # Adds an option to share a search query link.*
#- search.suggest # Search will suggest the likeliest completion for a word.*
# Table of Contents
#
#- toc.integrate # Include the TOC sections in the left navugation.
# +------------------------------------------------------------------------------------------------------+
# | |
# | The "icon" section allows you to define a icon to use for the logo and/or repository. |
# | |
# | To use already available icons will you need to set the right path for it, depending on which you |
# | want to use. |
# | |
# | Available icons: |
# | - FontAwesome |
# | - Brands: fontawesome/brands/... (https://fontawesome.com/icons?d=gallery&p=2&s=brands&m=free) |
# | - Regular: fontawesome/regular/... (https://fontawesome.com/icons?d=gallery&p=2&s=regular&m=free) |
# | - Solid: fontawesome/solid/... (https://fontawesome.com/icons?d=gallery&p=2&s=solid&m=free) |
# | |
# | - Material Design Icons: material/... (https://materialdesignicons.com/) |
# | |
# | - Octicons: octicons/... (https://primer.style/octicons/) |
# | |
# | You can also define your own Image for the logo. To do that, remove the "logo" option from "icon" |
# | instead add a "logo" option on the same level as the "icon" one, where you then set the path |
# | (relative to the "docs_dir") to the icon to use. Supported are all images types, including SVG. |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/setup/changing-the-logo-and-icons/#logo |
# | |
# +------------------------------------------------------------------------------------------------------+
icon:
logo: 'material/library'
repo: 'material/library'
# +------------------------------------------------------------------------------------------------------+
# | |
# | The "admonition" option allows you to set a different icon for each admonition type. |
# | |
# | This is currently a Insiders-only feature. (Last update: 7th October 2021) |
# | https://squidfunk.github.io/mkdocs-material/insiders/ |
# | |
# | Supported are all bundled icons: |
# | - FontAwesome |
# | - Brands: fontawesome/brands/... (https://fontawesome.com/icons?d=gallery&p=2&s=brands&m=free) |
# | - Regular: fontawesome/regular/... (https://fontawesome.com/icons?d=gallery&p=2&s=regular&m=free) |
# | - Solid: fontawesome/solid/... (https://fontawesome.com/icons?d=gallery&p=2&s=solid&m=free) |
# | |
# | - Material Design Icons: material/... (https://materialdesignicons.com/) |
# | |
# | - Octicons: octicons/... (https://primer.style/octicons/) |
# | |
# | You can also create and use your own icons. See the documentation for more information. |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/reference/admonitions/#changing-the-icons |
# | |
# +------------------------------------------------------------------------------------------------------+
#admonition:
# note: 'octicons/tag-16'
# abstract: 'octicons/checklist-16'
# info: 'octicons/info-16'
# tip: 'octicons/squirrel-16'
# success: 'octicons/check-16'
# question: 'octicons/question-16'
# warning: 'octicons/alert-16'
# failure: 'octicons/x-circle-16'
# danger: 'octicons/zap-16'
# bug: 'octicons/bug-16'
# example: 'octicons/beaker-16'
# quote: 'octicons/quote-16'
# +--------------------------------------------------------------------------------------------------------+
# | |
# | With the "extra_css" option can you add your own (S)CSS files to enhance the documentation. |
# | |
# | The path to the file is relative to the "docs_dir". |
# | |
# | Read More: |
# | - https://www.mkdocs.org/user-guide/configuration/#extra_css |
# | |
# +--------------------------------------------------------------------------------------------------------+
extra_css:
- assets/css/extra.css
# +--------------------------------------------------------------------------------------------------------+
# | |
# | Similar to the "extra_css" option does the "extra_javascript" option allow you to set custom JS files |
# | to add extra featurues. |
# | |
# | The path to the file is relative to the "docs_dir". |
# | |
# | Read More: |
# | - https://www.mkdocs.org/user-guide/configuration/#extra_javascript |
# | |
# +--------------------------------------------------------------------------------------------------------+
extra_javascript:
- https://polyfill.io/v3/polyfill.min.js?features=es6
- https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
# +--------------------------------------------------------------------------------------------------------+
# | |
# | The "extra" section contains pretty much anything you want, as long as it is a valid key-value pair. |
# | |
# | Material uses this section for different custom settings that wouldn't fit in the theme section. |
# | |
# | Read More: |
# | - https://www.mkdocs.org/user-guide/configuration/#extra |
# | |
# +--------------------------------------------------------------------------------------------------------+
extra:
# +------------------------------------------------------------------------------------------------------+
# | |
# | The social section allows you to set a list of entries which would be displayed in the footer of the |
# | page. |
# | |
# | Each entry has the exact same options: |
# | - icon: Path to the SVG icon to use. See "icon" section for available icon sets. |
# | - link: URL to which the icon should link. |
# | - name: Optional Name that would be displayed as title on hover. |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/setup/setting-up-the-footer/#social-links |
# | |
# +------------------------------------------------------------------------------------------------------+
social:
- icon: 'fontawesome/brands/github'
link: 'https://github.com/Andre601/mkdocs-template'
# +------------------------------------------------------------------------------------------------------+
# | |
# | Allows you to hide the "Made with Material for MkDocs" text in the footer of the pages by setting |
# | this to "true". |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/setup/setting-up-the-footer/#generator-notice |
# | |
# +------------------------------------------------------------------------------------------------------+
#generator: true
# +------------------------------------------------------------------------------------------------------+
# | |
# | The "manifest" option allows you to define a .manifest file to use. |
# | |
# | A .manifest file makes the doc act like a web-application and tells it how to behave when installed. |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/reference/meta-tags/#adding-a-web-app-manifest |
# | |
# +------------------------------------------------------------------------------------------------------+
#manifest: manifest.webmanifest
# +------------------------------------------------------------------------------------------------------+
# | |
# | The "alternate" option can be used to create a selector to switch languages. |
# | |
# | Using this requires you to create a specific, more complicated MkDocs setup. |
# | |
# | A Setup Guide for multi-language docs can be found here: |
# | https://github.com/squidfunk/mkdocs-material/discussions/2346 |
# | |
# | Read More: |
# | - https://squidfunk.github.io/mkdocs-material/setup/changing-the-language/#site-language-selector |
# | |
# +------------------------------------------------------------------------------------------------------+
#alternate:
# +--------------------------------------------------------------------------------------------------------+
# | |
# | MkDocs allows the usage of Markdown extensions which can do various things. |
# | |
# | Material includes the pymdownx extension which provides a lot of useful features to use. |
# | |
# | Note that some extensions may use specific settings that you need to set. |
# | Please check out the official documentation of PyMdownx for more information: |
# | https://facelessuser.github.io/pymdown-extensions/ |
# | |
# | Material already provides required CSS and JS values for the PyMdownX Extensions, which means you do |
# | not need to set them up yourself. |
# | |
# | Read More: |
# | - https://www.mkdocs.org/user-guide/configuration/#markdown_extensions |
# | |
# +--------------------------------------------------------------------------------------------------------+
markdown_extensions:
- markdown.extensions.admonition:
- markdown.extensions.codehilite:
guess_lang: false
- markdown.extensions.toc:
permalink: true
- pymdownx.arithmatex:
generic: true
- attr_list
- md_in_html
- pymdownx.blocks.caption
- admonition
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- pymdownx.details
- attr_list
- tables
#- pymdownx.b64:
#- pymdownx.betterem:
#- pymdownx.caret:
#- pymdownx.critic:
#- pymdownx.details:
#- pymdownx.emoji:
#- pymdownx.escapeall:
#- pymdownx.extra:
#- pymdownx.extrarawhtml:
#- pymdownx.highlight:
#- pymdownx.inlinehilite:
#- pymdownx.keys:
#- pymdownx.magiclink:
#- pymdownx.mark:
#- pymdownx.pathconverter:
#- pymdownx.progressbar:
#- pymdownx.smartsymbols:
#- pymdownx.snippets:
#- pymdownx.striphtml:
#- pymdownx.superfences:
#- pymdownx.tabbed:
#- pymdownx.tasklist:
#- pymdownx.tilde:
# - exporter:
# formats:
# pdf:
# enabled: !ENV [MKDOCS_EXPORTER_PDF, true]
# concurrency: 8
# stylesheets:
# - resources/stylesheets/pdf.scss
# covers:
# front: resources/templates/covers/front.html.j2
# back: resources/templates/covers/back.html.j2
# aggregator:
# enabled: true
# output: .well-known/site.pdf
# covers: all
# theme:
# name: material
# palette:
# - scheme: default
# primary: indigo
# accent: blue
# toggle:
# icon: material/brightness-7
# name: Switch to dark mode
# - scheme: slate
# primary: indigo
# accent: blue
# toggle:
# icon: material/brightness-4
# name: Switch to light mode
# features:
# - navigation.tabs
# - navigation.sections
# - navigation.top
# - content.code.copy
# - content.tabs.link
# plugins:
# - search
# markdown_extensions:

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

@@ -34,6 +34,7 @@
#ifdef USE_CUDA #ifdef USE_CUDA
#include <cuda_runtime.h> #include <cuda_runtime.h>
#include <thrust/device_vector.h>
#endif #endif
namespace uLib { namespace uLib {

View File

@@ -37,6 +37,11 @@
#include <Core/SmartPointer.h> #include <Core/SmartPointer.h>
#include <Core/StaticInterface.h> #include <Core/StaticInterface.h>
#ifdef USE_CUDA
#include <thrust/device_ptr.h>
#include <thrust/device_vector.h>
#endif
namespace uLib { namespace uLib {
// MetaAllocator Implementation ... // MetaAllocator Implementation ...
@@ -136,6 +141,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);
@@ -168,6 +174,42 @@ public:
return nullptr; return nullptr;
} }
#ifdef USE_CUDA
/// Returns a thrust::device_ptr to the VRAM data (valid after MoveToVRAM()).
/// thrust::device_ptr<T> is itself a random-access iterator compatible with
/// all thrust algorithms (thrust::transform, thrust::sort,
/// thrust::for_each…).
thrust::device_ptr<T> DeviceData() {
if (auto alloc = MetaAllocator<T>::GetDataAllocator(BaseClass::data())) {
return thrust::device_pointer_cast(alloc->GetVRAMData());
}
return thrust::device_ptr<T>(nullptr);
}
thrust::device_ptr<const T> DeviceData() const {
if (auto alloc = MetaAllocator<T>::GetDataAllocator(
const_cast<T *>(BaseClass::data()))) {
return thrust::device_pointer_cast(
static_cast<const T *>(alloc->GetVRAMData()));
}
return thrust::device_ptr<const T>(nullptr);
}
/// Device-side begin iterator (valid after MoveToVRAM()).
thrust::device_ptr<T> DeviceBegin() { return DeviceData(); }
/// Device-side end iterator (valid after MoveToVRAM()).
thrust::device_ptr<T> DeviceEnd() {
return DeviceData() + static_cast<std::ptrdiff_t>(BaseClass::size());
}
thrust::device_ptr<const T> DeviceBegin() const { return DeviceData(); }
thrust::device_ptr<const T> DeviceEnd() const {
return DeviceData() + static_cast<std::ptrdiff_t>(BaseClass::size());
}
#endif // USE_CUDA
inline void PrintSelf(std::ostream &o); inline void PrintSelf(std::ostream &o);
// Overrides for auto-sync // // Overrides for auto-sync //
@@ -274,6 +316,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

@@ -31,3 +31,8 @@ set(LIBRARIES
${ROOT_LIBRARIES} ${ROOT_LIBRARIES}
) )
uLib_add_tests(Core) uLib_add_tests(Core)
if(USE_CUDA)
set_source_files_properties(VectorMetaAllocatorTest.cpp PROPERTIES LANGUAGE CUDA)
endif()

View File

@@ -12,6 +12,15 @@
#include "testing-prototype.h" #include "testing-prototype.h"
#include <Core/Vector.h> #include <Core/Vector.h>
#ifdef USE_CUDA
#include <thrust/device_ptr.h>
#include <thrust/transform.h>
struct DoubleFunctor {
__host__ __device__ int operator()(int x) const { return x * 2; }
};
#endif
int main() { int main() {
BEGIN_TESTING(VectorMetaAllocator); BEGIN_TESTING(VectorMetaAllocator);
@@ -41,14 +50,31 @@ int main() {
exit(1); exit(1);
} }
// Verify DeviceData() matches GetVRAMData()
{
thrust::device_ptr<int> dev_ptr = v.DeviceData();
if (dev_ptr.get() != vram_ptr) {
std::cout << "Error: DeviceData() does not match GetVRAMData()!\n";
exit(1);
}
std::cout << "DeviceData() matches GetVRAMData(). OK\n";
}
// Use thrust::transform via DeviceBegin()/DeviceEnd() to double all elements
// on device
std::cout << "Doubling elements on device via thrust::transform...\n";
thrust::transform(v.DeviceBegin(), v.DeviceEnd(), v.DeviceBegin(),
DoubleFunctor{});
std::cout << "Moving back to RAM...\n"; std::cout << "Moving back to RAM...\n";
v.MoveToRAM(); v.MoveToRAM();
std::cout << "RAM contents after VRAM trip: "; std::cout << "RAM contents after VRAM trip + thrust transform: ";
for (size_t i = 0; i < v.size(); ++i) { for (size_t i = 0; i < v.size(); ++i) {
std::cout << v[i] << " "; std::cout << v[i] << " ";
if (v[i] != (int)(i + 1)) { if (v[i] != (int)((i + 1) * 2)) {
std::cout << "\nError: Data corrupted after RAM->VRAM->RAM trip at index " std::cout << "\nError: Data corrupted after RAM->VRAM->thrust->RAM trip "
"at index "
<< i << "\n"; << i << "\n";
exit(1); exit(1);
} }

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;
}