Compare commits
11 Commits
e69b29a259
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 40846bba78 | |||
| 4d681e3373 | |||
| 3a9efd5598 | |||
| fa1930f9d7 | |||
|
|
b64afe8773 | ||
|
|
f3ebba4931 | ||
|
|
79e1abb2ff | ||
|
|
554eff9b55 | ||
|
|
42db99759f | ||
|
|
69920acd61 | ||
|
|
647d0caa1c |
42
.gitea/workflows/publish-docs.yaml
Normal file
42
.gitea/workflows/publish-docs.yaml
Normal 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)
|
||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -4,4 +4,12 @@ build/
|
|||||||
build_warnings*.log
|
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
16
CMakePresets.json
Normal 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}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
54
build_python.py
Normal 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({})
|
||||||
41
docs/assets/css/extensions/tabbed.css
Normal file
41
docs/assets/css/extensions/tabbed.css
Normal 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
17
docs/assets/css/extra.css
Normal 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
30
docs/docker/Dockerfile
Normal 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;"]
|
||||||
14
docs/docker/Dockerfile.dev
Normal file
14
docs/docker/Dockerfile.dev
Normal 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"]
|
||||||
13
docs/docker/docker-compose.yml
Normal file
13
docs/docker/docker-compose.yml
Normal 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
|
||||||
17
docs/docker/requirements.txt
Normal file
17
docs/docker/requirements.txt
Normal 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
1
docs/docker/runtime.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.7
|
||||||
63
docs/index.md
Normal file
63
docs/index.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# uLib
|
||||||
|
|
||||||
|
[](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 |
|
||||||
179
docs/python/developer_guide.md
Normal file
179
docs/python/developer_guide.md
Normal 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
146
docs/python/installation.md
Normal 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
373
docs/python/usage.md
Normal 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
561
mkdocs.yml
Normal 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 © 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: |
|
||||||
|
© 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
7
poetry.lock
generated
Normal 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
15
pyproject.toml
Normal 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"
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ///////////////////////////////////////////////////
|
||||||
@@ -175,7 +191,7 @@ std::string VectorxT_ToString(const Eigen::Matrix<T, size, 1> &vec) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
template <typename T, int size>
|
template <typename T, int size>
|
||||||
void operator>>(std::string &str, Eigen::Matrix<T, size, 1> &vec) {
|
void operator >> (std::string &str, Eigen::Matrix<T, size, 1> &vec) {
|
||||||
VectorxT_StringTo(vec, str);
|
VectorxT_StringTo(vec, str);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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_));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
100
src/Python/math_filters_bindings.cpp
Normal file
100
src/Python/math_filters_bindings.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
151
src/Python/testing/math_filters_test.py
Normal file
151
src/Python/testing/math_filters_test.py
Normal 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()
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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!")
|
||||||
|
|||||||
7
src/Python/uLib/__init__.py
Normal file
7
src/Python/uLib/__init__.py
Normal 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"]
|
||||||
Reference in New Issue
Block a user