Numba JIT Acceleration for uLib Recipes¶
uLib provides first-class support for high-performance JIT compilation and parallelization in Python recipes via Numba.
1. Architectural Model: Orchestration vs Computation¶
When writing recipes in gcompose, Python scripts follow a clean two-tier architecture:
┌────────────────────────────────────────────────────────┐
│ Python Orchestration Layer: def run(ctx): │
│ - Reads inputs / outputs (e.g. img = Input0) │
│ - Coordinates scene graph and objects context │
│ - Calls the compiled @njit kernel │
│ - Emits Qt/C++ signals (img.Updated()) │
└──────────────────────────┬─────────────────────────────┘
│ Dispatches raw buffers & domain objects
▼
┌────────────────────────────────────────────────────────┐
│ High-Performance Numba Layer: @njit(parallel=True) │
│ - Native LLVM compilation without GIL overhead │
│ - Direct zero-copy C++ buffer access │
│ - Multi-threaded loop execution (OpenMP / TBB) │
│ - Vector and Matrix linear algebra │
└────────────────────────────────────────────────────────┘
Why
run(ctx)is not@njit:ctxis anObjectsContext/Sceneinstance that manages C++ object lifecycles, Qt signals, and dynamic node connections. These framework reflection mechanisms require Python bytecode execution. Numerical computations (voxel processing, linear transformations, filters) are separated into@njitkernels.
2. Using Images (VoxImage) in Numba Kernels¶
uLib.numba_ext registers Math.VoxImage directly with Numba. You can pass img as an argument to any @njit function without manual conversion or copying.
Supported Operations inside @njit¶
| Operation | Syntax | Description |
|---|---|---|
| Get Dimensions | u, v, w = img.GetDims() |
Returns a 3-element tuple (dim_x, dim_y, dim_z) |
| Get Strides | sx, sy, sz = img.GetIncrements() |
Returns memory increments |
| 3D Voxel Read | val = img[i, j, k] |
Direct 3D float voxel access |
| 3D Voxel Write | img[i, j, k] = val |
Direct 3D float voxel write |
| Method Read | val = img.GetValue([i, j, k]) |
Supports lists, tuples, or flat index |
| Method Write | img.SetValue([i, j, k], val) |
Supports lists, tuples, or flat index |
| Voxel Count Read | cnt = img.GetCount([i, j, k]) |
Reads integer hit count |
| Voxel Count Write | img.SetCount([i, j, k], cnt) |
Writes integer hit count |
| Voxel Total Count | n = len(img) |
Total voxel count u * v * w |
| Bounds Check | is_valid = img.IsInsideGrid([i, j, k]) |
Fast boundary check |
Parallel Multi-Threading Pattern¶
Use prange from numba to distribute outer loops across all available CPU threads:
import numpy as np
from numba import njit, prange
Input0 = InputPort("Image")
@njit(fastmath=True, parallel=True)
def apply_voxel_filter(img, scale_factor):
u, v, w = img.GetDims()
for i in prange(u):
for j in range(v):
for k in range(w):
val = img[i, j, k]
img[i, j, k] = val * scale_factor
def run(ctx):
img = Input0
apply_voxel_filter(img, 1.5)
img.Updated()
3. Using Vectors and Matrices (Vector3f, Vector3i, Matrix3f, Matrix4f)¶
Vectors (Vector3f / Vector3i)¶
In Python, vectors can be created via Math.Vector3f([x, y, z]), Math.Vector3i([x, y, z]), tuples, or 1D NumPy arrays.
Inside @njit kernels:
- Tuples / Coordinates: (x, y, z) are lightweight, zero-overhead, and unbox automatically.
- 1D NumPy Arrays: np.array([x, y, z], dtype=np.float32) allow standard vector algebra (np.dot, np.cross, np.linalg.norm).
@njit(fastmath=True)
def distance_to_center(point, center):
# point and center can be 3-tuples or 1D float arrays
dx = point[0] - center[0]
dy = point[1] - center[1]
dz = point[2] - center[2]
return np.sqrt(dx*dx + dy*dy + dz*dz)
Matrices (Matrix3f / Matrix4f / Transforms)¶
In Python and Numba, matrices are represented as 2D NumPy float arrays (np.ndarray of shape (3, 3) or (4, 4)):
@njit(fastmath=True)
def transform_point(mat4x4, point3):
# point3: (x, y, z)
# mat4x4: 4x4 float array
x = mat4x4[0, 0]*point3[0] + mat4x4[0, 1]*point3[1] + mat4x4[0, 2]*point3[2] + mat4x4[0, 3]
y = mat4x4[1, 0]*point3[0] + mat4x4[1, 1]*point3[1] + mat4x4[1, 2]*point3[2] + mat4x4[1, 3]
z = mat4x4[2, 0]*point3[0] + mat4x4[2, 1]*point3[1] + mat4x4[2, 2]*point3[2] + mat4x4[2, 3]
return (x, y, z)
4. UI Integration in gcompose¶
The gcompose Recipe editor provides instant shortcuts for inserting Numba acceleration templates:
- New Recipes: Automatically generated with
import numpy as np. - ⚡ Numba Menu:
- ⚡ Parallel Kernel: Adds required
from numba import njit, prangeimports and inserts an optimized@njit(fastmath=True, parallel=True)3D voxel loop. - ⚡ Compiled Function: Inserts a standard
@njit(fastmath=True)kernel template.