Projector#

class diffct.Projector(trajectory, volume_shape, detector_shape, *, beam='cone', detector_spacing=1.0, voxel_spacing=1.0, devices=None, distributed=False, process_group=None, detector_surface=None, volume_chunk_shape=None, view_chunk_size=None, detector_chunk_shape=None, schedule='auto', partition='views', backend='auto', precision='fast', partition_grid=None, native_volume_layout=False)[source]#

Project floating tensors along parallel, fan, cone, or parallel3d trajectories.

A parallel trajectory is (ray_dir, det_origin, det_u) and a fan trajectory is (src_pos, det_center, det_u); each component has shape (views, 2). A cone trajectory is (src_pos, det_center, det_u, det_v) with components shaped (views, 3). A parallel3d trajectory is (ray_dir, det_center, det_u, det_v) with the same 3D component shapes; its infinite ray lines pass through detector pixels. Direction axes are unit vectors; parallel ray_dir and det_u and 3D detector det_u and det_v are orthogonal. Parallel3d rays can be oblique but must not be tangent to the detector plane. If no trajectory tensor requires gradients, the projector clones the geometry. Otherwise it keeps references, reads the current values at every call, and returns gradients for those tensors. Live default frames and overrides are checked on each call. project, backproject, __call__ and their *_into variants accept a keyword-only trajectory replacing all frames for that call. It must retain the constructor’s beam and view count; it is validated and snapshotted independently without replacing the default frames. Rebuild computed trajectories from trainable leaves on each optimization step. Second derivatives are available for volumes and sinograms, not for the geometry.

detector_surface(u, v) optionally supplies local (u, v, n) offsets for every pixel. It receives centred physical float64 grids on CPU and returns a floating-point tensor shaped (*detector_shape, 3) or (views, *detector_shape, 3). 3D normals are cross(det_u, det_v); 2D normals are (det_u_y, -det_u_x) with zero middle offsets. Offsets can be on CPU or CUDA in full-volume and streamed execution. World points expand on each compute device after view sharding. Native execution preserves the composition’s dtype promotion before converting complete coordinates to float32. The PyTorch backend uses the selected precision. The callback is sampled and validated on every call, with first-order gradients for captured PyTorch parameters. Omitting it preserves the flat detector.

ParameterizedSurface(sampler, parameters) instead samples only the selected global view IDs and detector rectangle. Its explicit parameters and trajectory values are snapshotted once per call, with batch-local first-order parameter gradients. detector_chunk_shape bounds pixel rectangles; their physical origins remain those of the full detector.

Volumes use (H, W) for parallel and fan beams and (D, H, W) for cone and parallel3d beams. Sinograms use (views, detectors) or (views, U, V). Any leading batch/channel dimensions are preserved. voxel_spacing is a positive scalar or a tuple in volume tensor order: (y, x) or (z, y, x). Physical coordinates retain the centred global volume frame. Results stay on the input device. precision="fast" and "float32" return float32; "float64" computes geometry, ray lengths and accumulations in float64. The selected backend, precision and dtype are public metadata.

backend="auto" selects the native "numba" backend for NVIDIA CUDA and fast precision with scalar or per-axis voxel spacing, except for hybrid partitions. Other configurations use portable "torch" execution on the input’s CPU or supported PyTorch accelerator. Explicit "numba" requires a compatible native configuration. Parallel3d uses torch; explicitly selecting numba for it raises an error. Data torch.func transforms use portable operations while preserving selected metadata.

Native CPU inputs stream spatial tiles and view batches through CUDA. Native CUDA inputs retain the full-volume path when its additional working set fits every device; otherwise they stream too. volume_chunk_shape sets explicit spatial limits in tensor order; view_chunk_size sets an explicit view limit. Either forces native streaming. Native automatic sizing uses live free memory, reusable allocator cache and the observable per-process fraction ceiling, retaining 25% headroom. Automatic sizing compares fitting first-axis slabs and Cartesian blocks; view batches are at most 32 and shrink if needed. Older PyTorch without a public fraction getter cannot account for that ceiling. Concurrent external allocations can still cause an allocation error.

For the native backend, schedule="auto" selects spatial, view or bounded two-tile window order using estimated full-workload time from short native pilots, with estimated copy bytes breaking ties. The same orders can be requested with "spatial", "views" or "window"; capacity may require a serial spatial fallback. Streaming uses distinct upload, compute and download streams and two reusable host slots capped at 8 MiB each. Store reads and completed output drains are fragmented independently of the resident GPU tile size. last_execution_stats reports actual copy payloads, native launches/layouts, planned capacity and pilot metadata; it is None before execution, after errors, and on the uninstrumented native full-CUDA path. Portable execution reports actual ray/tile pairs, store I/O, transfers and collective payloads, with conservative automatic chunks targeting 8 MiB work arrays. Explicit chunk hints take priority. Reports and selection caches retain metadata.

native_volume_layout=True makes native full-CUDA cone backprojections of unbatched inputs on a flat detector return (D, H, W) tensors with the kernels’ (W, H, D)-contiguous strides instead of contiguous copies; native projections accept such tensors without a copy. An iterative solver that keeps its volumes in this layout then avoids two whole-volume copies per normal-operator application on one device. The layout does not change the operator. Results from several local devices, curved detectors, batched inputs, streamed execution and the PyTorch backend still involve copies.

Full host volumes/sinograms stay on CPU; streamed native CUDA buffers are bounded by the chosen tile and view batch. Caller-owned CUDA data/results and CUDA work inside a supplied callback are outside this memory bound. Native local devices launch their view batches concurrently, but each device receives every owned volume tile, so host transfers limit the speedup. Local devices split views in list order. partition="views" returns rank-local projections and replicated backprojections. partition="space" assigns balanced first-axis slabs and returns replicated projections and owned backprojections. partition="hybrid" combines both with partition_grid=(view_ranks, space_ranks) and parent-group rank view_rank * space_world_size + space_rank. global_volume_shape is the constructor shape; volume_slice identifies the owned global slab, and volume_shape and local_volume_shape give its required tensor shape. Empty slabs are valid. Physical coordinates retain the global volume frame. For replicated outputs, count the loss once globally or divide each rank’s projection loss by space_world_size or backprojection loss by view_world_size. Geometry gradients already SUM over all parent ranks; do not apply an additional DDP reduction to them. Space ranks participate in backward even when their local input and geometry require no gradients.

__call__(volume, *, trajectory=None)[source]#

Alias for project().

backproject(sinogram, *, trajectory=None)[source]#

Apply the matched adjoint to a tensor ending in projection_shape.

Cone sinograms use (views, U, V) order after any leading batch/channel dimensions. The result preserves those dimensions, uses selected precision and stays on sinogram.device. View ranks SUM bounded volume contributions; spatial ranks return only the owned volume_slice. Divide replicated losses by view_world_size.

backproject_into(sinogram, output, *, trajectory=None)[source]#

Write the numerical adjoint into block output matching self.dtype.

Accepts tensors or stores with read/write/shape/dtype/flush metadata. Returns output after bounded view/pixel contributions complete.

property process_group#

Process group of a distributed operator, or None for the default group.

Pass it as group= to collectives that combine per-rank results of this operator, so they use the same ranks as the operator.

project(volume, *, trajectory=None)[source]#

Project a floating tensor ending in the owned volume_shape.

Leading batch/channel dimensions are preserved ahead of projection_shape. The result has the selected precision and stays on volume.device. View partitions own contiguous view slices; spatial ranks SUM their ray contributions and replicate the result. Portable execution supports CPU and PyTorch accelerator tensors; native execution uses NVIDIA CUDA and streams CPU data when needed.

project_into(volume, output, *, trajectory=None)[source]#

Numerically project into supplied output matching self.dtype.

Returns output; no complete result is allocated. Direct tensors requiring autograd must be detached or used under torch.no_grad(). Known overlapping/read-only outputs are rejected before execution.