Forward projection
Projector model and derivative choices.
Purpose
tomojax.forward provides differentiable projection, projection-domain
residuals, and geometry-parameter reductions.
The projector adapts GeometryState to core Grid, Detector, detector
grids, and per-view 4x4 T_all, then projects with
tomojax.core.projector.forward_project_view_T.
Public API
project_parallel_referenceproject_parallel_reference_from_inputProjectionArrayGeometryInputProjectionOperatorNamecore_projection_geometry_from_statecore_projection_geometry_from_inputnominal_axis_unit_from_geometryCoreProjectionGeometryPROJECTION_OPERATORproject_josephjoseph_l2_value_and_gradjoseph_pose_normal_equationsapply_residual_filterapply_residual_filter_scheduleResidualFilterConfigResidualFilterKindResidualFilterResultmasked_whitened_residualpseudo_huber_losspseudo_huber_weightsresidual_lossResidualResult
Plane-sampled projection
project_joseph(volume, poses, grid, detector, backend="jax") exposes the same
default linear Joseph discretization the reconstruction solvers use by default.
Set interpolation="cubic" on either Joseph function for Keys cubic convolution
with parameter -1/2. The 4-by-4 transverse stencil has continuous first
coordinate derivatives and includes negative weights. It is more expensive and
can overshoot nonnegative data. Both models have matching CUDA volume/pose
derivatives and a matched volume transpose; dominant-axis switches remain
nonsmooth. To reconstruct with the cubic model, use
CGLSConfig(projector_model="joseph", joseph_interpolation="cubic").
By default it samples voxel-centre planes along the dominant ray direction,
bilinearly interpolates the other two coordinates with zero extension, and multiplies by
physical path length between planes. This differs from the default trilinear
ray operator. Supply finite rigid world-from-object matrices (views, 4, 4);
voxel origins and detector centres retain their physical units.
The explicit backend="cuda" runs on NVIDIA CUDA and supports first-order
jax.grad, jax.jvp, jax.jacfwd, jax.jacrev, jax.linearize, transposing
that linearization, and jax.vmap. The input volume and geometry are retained
for reverse mode; the kernel recomputes sample derivatives without saving a
ray-by-plane tape. Use the JAX backend for higher derivatives. Interpolation
knots for the linear model and dominant-axis changes are nonsmooth; derivatives follow the branch
selected at the supplied pose. Backend selection does not silently fall back.
For raw least squares, the fused operation avoids a separate projection pass when computing pose gradients:
import jax
from tomojax.forward import joseph_l2_value_and_grad, project_joseph
project = jax.jit(
lambda volume, poses: project_joseph(volume, poses, grid, detector, backend="cuda")
)
loss_and_grad = jax.jit(
lambda volume, poses, data: joseph_l2_value_and_grad(
volume, poses, data, grid, detector, backend="cuda"
)
)
prediction = project(volume, poses)
loss, (volume_grad, matrix_grad) = loss_and_grad(volume, poses, data)The loss is 0.5 * sum((prediction - data)**2), without mean normalization,
masking, or fitted scale. To optimize rigid pose parameters, pull matrix_grad
through the function that builds your pose matrices; matrix entries themselves
are not unconstrained rigid-pose parameters. Other differentiable losses can
compose project_joseph with ordinary JAX operations. The alignment pipeline
samples with the same linear Joseph model by default (its ray_integrator
setting) but does not call these fused functions.
joseph_pose_normal_equations(volume, poses, pose_directions, data, grid, detector, backend="cuda", interpolation="cubic") computes per-view raw least-squares
loss, parameter gradient, Gauss–Newton matrix and residual images. Supply
pose_directions as (views, 4, 4, P), for 1–16 matrix-pose directions in your
parameter units. For a function make_pose(parameters, nominal_pose),
jax.vmap(jax.jacfwd(make_pose))(parameters, nominal_poses) provides those
directions. The outputs have shapes (views,), (views, P), (views, P, P)
and (views, nv, nu).
The CUDA kernel computes the small normal matrices directly while traversing
planes. It retains a residual for line search, without storing a full projection
Jacobian or a ray-by-plane tape. This returns J.T @ J, not the full loss Hessian.
Damping, parameter steps, priors and gauge constraints remain caller choices.
Shared global parameters can sum these per-view contributions. CUDA supplies
first-order information; the ordinary JAX reference supports further AD.
Dependencies
Allowed: tomojax.core, tomojax.geometry, tomojax.motion,
tomojax.nuisance, tomojax.backends.
Forbidden: private files from other modules, reconstruction/alignment orchestration, CUDA fast paths without JAX-reference equivalence tests.
Invariants
- Projection residuals support masks and robust whitening.
- Backend fast paths report provenance and compare against the reference path.
- Supported DOFs: nominal theta, theta scale/offset, per-view alpha/beta/phi residuals, detector u/v shift, detector roll, axis x/y tilt, per-view dx/dz.
- Detector roll applies around the zero-centre detector plane; detector centre offsets are independent.
- Axis rotations use the core rotation-axis pose convention: nominal axis from
acquisition metadata, x/y setup corrections on top,
T_allbuilt withaxis_pose_stack. - Alpha/beta pose rotations compose after nominal axis/theta in object coordinates.
- Parallel laminography uses a tilted nominal rotation axis with the same projector.
- Residual filter policies:
raw,lowpass_gaussian,bandpass_difference_of_gaussians.
Tests
tests/test_numerical_engines.pyverifies the public grouped-input projection path and core numerical invariants used by reconstruction.tests/test_product_surface.pyverifies the public module import surface.tests/test_joseph_forward.py,tests/test_joseph_derivatives.pyandtests/test_joseph_projector.pycover the public plane model, independent physical matrices/finite differences, first-order transformations and CUDA equivalence.