Generated from the full canonical file for this source snapshot. Line numbers match the library source.
Source SHA256: bc28e2ff01464773a7929da110f45b9d74c954684b843abc5f70573ced6f63d9
1"""Streaming finite-ray quadrature and an explicit first-order discrete VJP.23One lane traces one ray. Values and pose derivatives use FP64 registers with4FP32 field storage. Reverse recomputes each sample; no ray-by-sample tensor or5image Jacobian is retained. Pose partials use a fixed tree; active-volume6cotangents use FP32 scatter atomics and are not bitwise deterministic.7"""89# Warp annotations are executable DSL expressions; host interfaces remain strict.10# The optional GPU import is resolved only when an operator is prepared.11# pyright: reportInvalidTypeForm=false, reportUnknownParameterType=false12# pyright: reportUnknownMemberType=false, reportUnknownArgumentType=false13# pyright: reportUnknownVariableType=false, reportUntypedFunctionDecorator=false14# pyright: reportMissingImports=false, reportUntypedClassDecorator=false1516from functools import cache1718import warp as wp1920from dpt.kernels.geometry import unpack_rotation, unpack_translation21from dpt.kernels.projection_checks import (22get_dependency_marker as get_dependency_marker,23)24from dpt.kernels.projection_checks import (25get_validate_finite as get_validate_finite,26)27from dpt.kernels.projection_checks import (28validate_finite as validate_finite,29)30from dpt.kernels.projection_checks import (31validate_nonnegative as validate_nonnegative,32)3334OPTIONS = {"fast_math": False, "fuse_fp": True, "enable_backward": False}35wp.set_module_options(OPTIONS)36BLOCK = 12837Vec12d = wp.types.vector(length=12, dtype=wp.float64)383940@wp.struct41class Configuration:42shape: wp.vec3i43object_to_grid: wp.mat33d44grid_origin: wp.vec3d45source: wp.vec3d46detector_origin: wp.vec3d47column_step: wp.vec3d48row_step: wp.vec3d49width: int50pixels: int51samples: int525354@wp.struct55class Ray:56origin: wp.vec3d57direction: wp.vec3d58object_source: wp.vec3d59object_direction: wp.vec3d60world_source_relative: wp.vec3d61world_direction: wp.vec3d62length: wp.float64636465@wp.func66def ray_for_pixel(config: Configuration, pose: wp.array(dtype=wp.float64), pixel: int) -> Ray:67ray = Ray()68row, column = pixel // config.width, pixel % config.width69end = (70config.detector_origin71+ wp.float64(column) * config.column_step72+ wp.float64(row) * config.row_step73)74inverse = wp.transpose(unpack_rotation(pose))75ray.world_source_relative = config.source - unpack_translation(pose)76ray.world_direction = end - config.source77ray.object_source = inverse * ray.world_source_relative78ray.object_direction = inverse * ray.world_direction79ray.origin = config.object_to_grid * (ray.object_source - config.grid_origin)80ray.direction = config.object_to_grid * ray.object_direction81ray.length = wp.length(ray.world_direction)82return ray838485@wp.func86def finite_point(point: wp.vec3d) -> bool:87return wp.isfinite(point[0]) and wp.isfinite(point[1]) and wp.isfinite(point[2])888990@wp.func91def valid_ray(ray: Ray) -> bool:92return (93finite_point(ray.origin)94and finite_point(ray.direction)95and finite_point(ray.object_source)96and finite_point(ray.object_direction)97and finite_point(ray.world_source_relative)98and finite_point(ray.world_direction)99and wp.isfinite(ray.length)100and ray.length > wp.float64(0.0)101)102103104@wp.func105def inside_support(point: wp.vec3d, shape: wp.vec3i) -> bool:106return (107finite_point(point)108and point[0] >= wp.float64(-0.5)109and point[1] >= wp.float64(-0.5)110and point[2] >= wp.float64(-0.5)111and point[0] <= wp.float64(shape[0]) - wp.float64(0.5)112and point[1] <= wp.float64(shape[1]) - wp.float64(0.5)113and point[2] <= wp.float64(shape[2]) - wp.float64(0.5)114)115116117@wp.struct118class Interval:119lower: wp.float64120upper: wp.float64121lower_origin_gradient: wp.vec3d122lower_direction_gradient: wp.vec3d123upper_origin_gradient: wp.vec3d124upper_direction_gradient: wp.vec3d125126127# region book:projection-active-intersection128@wp.func129def finite_interval(origin: wp.vec3d, direction: wp.vec3d, shape: wp.vec3i) -> Interval:130"""Clip a finite segment and retain the active face derivatives.131132Exact parallelism is handled separately; arbitrary epsilon thresholds would133change the physical interval. At tied faces a derivative is not unique:134strict comparisons choose the first active axis, a documented branch value.135"""136interval = Interval()137interval.lower = wp.float64(0.0)138interval.upper = wp.float64(1.0)139for axis in range(3):140low = wp.float64(-0.5)141high = wp.float64(shape[axis]) - wp.float64(0.5)142velocity = direction[axis]143if velocity == wp.float64(0.0):144if origin[axis] < low or origin[axis] > high:145interval.upper = wp.float64(-1.0)146else:147first = (low - origin[axis]) / velocity148last = (high - origin[axis]) / velocity149if first > last:150temporary = first151first = last152last = temporary153if first > interval.lower:154interval.lower = first155interval.lower_origin_gradient = wp.vec3d(0.0)156interval.lower_direction_gradient = wp.vec3d(0.0)157interval.lower_origin_gradient[axis] = -wp.float64(1.0) / velocity158interval.lower_direction_gradient[axis] = -first / velocity159if last < interval.upper:160interval.upper = last161interval.upper_origin_gradient = wp.vec3d(0.0)162interval.upper_direction_gradient = wp.vec3d(0.0)163interval.upper_origin_gradient[axis] = -wp.float64(1.0) / velocity164interval.upper_direction_gradient[axis] = -last / velocity165return interval166167168# endregion book:projection-active-intersection169170171@wp.struct172class Sample:173value: wp.float64174gradient: wp.vec3d175176177# region book:projection-clamped-trilinear178@wp.func179def sample_field(field: wp.array(dtype=wp.float32), point: wp.vec3d, shape: wp.vec3i) -> Sample:180"""Value and grid-coordinate slope of the half-cell-extended sampled field."""181result = Sample()182if not inside_support(point, shape):183return result184lower = wp.vec3i()185fraction = wp.vec3d()186slope = wp.vec3d()187for axis in range(3):188coordinate = wp.clamp(point[axis], wp.float64(0.0), wp.float64(shape[axis] - 1))189lower[axis] = wp.min(int(wp.floor(coordinate)), wp.max(shape[axis] - 2, 0))190fraction[axis] = coordinate - wp.float64(lower[axis])191if point[axis] >= wp.float64(0.0) and point[axis] < wp.float64(shape[axis] - 1):192slope[axis] = wp.float64(1.0)193# Separable interpolation reuses the same eight loads for value and slope.194# Form differences in FP64: FP32 subtraction would lose small field slopes.195# The compiler removes slope work from the forward-only caller.196x0, y0, z0 = lower[0], lower[1], lower[2]197x1 = wp.min(x0 + 1, shape[0] - 1)198y1 = wp.min(y0 + 1, shape[1] - 1)199z1 = wp.min(z0 + 1, shape[2] - 1)200row00 = (z0 * shape[1] + y0) * shape[0]201row10 = (z0 * shape[1] + y1) * shape[0]202row01 = (z1 * shape[1] + y0) * shape[0]203row11 = (z1 * shape[1] + y1) * shape[0]204v000 = wp.float64(field[row00 + x0])205v100 = wp.float64(field[row00 + x1])206v010 = wp.float64(field[row10 + x0])207v110 = wp.float64(field[row10 + x1])208v001 = wp.float64(field[row01 + x0])209v101 = wp.float64(field[row01 + x1])210v011 = wp.float64(field[row11 + x0])211v111 = wp.float64(field[row11 + x1])212dx00, dx10 = v100 - v000, v110 - v010213dx01, dx11 = v101 - v001, v111 - v011214fx, fy, fz = fraction[0], fraction[1], fraction[2]215# Weighted lerp preserves a small endpoint next to a huge neighbour.216# a+t*(b-a) would erase b at t=1 when the difference rounds to -a.217ax, ay, az = wp.float64(1.0) - fx, wp.float64(1.0) - fy, wp.float64(1.0) - fz218x00, x10 = ax * v000 + fx * v100, ax * v010 + fx * v110219x01, x11 = ax * v001 + fx * v101, ax * v011 + fx * v111220xy0, xy1 = ay * x00 + fy * x10, ay * x01 + fy * x11221result.value = az * xy0 + fz * xy1222# Differentiate corner values before interpolation. Subtracting two223# already interpolated values can erase a small slope beside a huge224# orthogonal background, even when its cotangent is representable.225dx0, dx1 = ay * dx00 + fy * dx10, ay * dx01 + fy * dx11226dy0 = ax * (v010 - v000) + fx * (v110 - v100)227dy1 = ax * (v011 - v001) + fx * (v111 - v101)228dz0 = ax * (v001 - v000) + fx * (v101 - v100)229dz1 = ax * (v011 - v010) + fx * (v111 - v110)230result.gradient = wp.vec3d(231slope[0] * (az * dx0 + fz * dx1),232slope[1] * (az * dy0 + fz * dy1),233slope[2] * (ay * dz0 + fy * dz1),234)235return result236237238# endregion book:projection-clamped-trilinear239240241@wp.func242def scatter_field(243gradient: wp.array(dtype=wp.float32),244point: wp.vec3d,245shape: wp.vec3i,246seed: wp.float64,247status: wp.array(dtype=wp.int32),248):249if not finite_point(point):250wp.atomic_or(status, 0, 2)251if not inside_support(point, shape):252return253lower = wp.vec3i()254fraction = wp.vec3d()255for axis in range(3):256coordinate = wp.clamp(point[axis], wp.float64(0.0), wp.float64(shape[axis] - 1))257lower[axis] = wp.min(int(wp.floor(coordinate)), wp.max(shape[axis] - 2, 0))258fraction[axis] = coordinate - wp.float64(lower[axis])259for corner in range(8):260bit = wp.vec3i(corner & 1, (corner >> 1) & 1, (corner >> 2) & 1)261index = wp.vec3i()262contribution = seed263for axis in range(3):264index[axis] = wp.min(lower[axis] + bit[axis], shape[axis] - 1)265if bit[axis] == 0:266contribution *= wp.float64(1.0) - fraction[axis]267else:268contribution *= fraction[axis]269rounded = wp.float32(contribution)270if not wp.isfinite(rounded):271wp.atomic_or(status, 0, 2)272wp.atomic_add(gradient, (index[2] * shape[1] + index[1]) * shape[0] + index[0], rounded)273274275@wp.struct276class CellTraversal:277plane: wp.vec3i278step: wp.vec3i279next: wp.vec3d280281282@wp.func283def cell_crossing(284ray: Ray, axis: int, plane: int, shape: wp.vec3i, upper: wp.float64285) -> wp.float64:286if ray.direction[axis] == wp.float64(0.0) or plane < 0 or plane >= shape[axis]:287return upper288return wp.min((wp.float64(plane) - ray.origin[axis]) / ray.direction[axis], upper)289290291@wp.func292def begin_cells(ray: Ray, interval: Interval, shape: wp.vec3i) -> CellTraversal:293"""Next strictly forward interpolation plane, including half-cell edge slabs."""294traversal = CellTraversal()295entry = ray.origin + interval.lower * ray.direction296for axis in range(3):297velocity = ray.direction[axis]298if velocity > wp.float64(0.0):299traversal.step[axis] = 1300traversal.plane[axis] = wp.max(int(wp.ceil(entry[axis])), 0)301elif velocity < wp.float64(0.0):302traversal.step[axis] = -1303traversal.plane[axis] = wp.min(int(wp.floor(entry[axis])), shape[axis] - 1)304crossing = cell_crossing(ray, axis, traversal.plane[axis], shape, interval.upper)305if crossing <= interval.lower:306traversal.plane[axis] += traversal.step[axis]307crossing = cell_crossing(ray, axis, traversal.plane[axis], shape, interval.upper)308traversal.next[axis] = crossing309return traversal310311312@wp.func313def advance_cells(314traversal: CellTraversal, ray: Ray, shape: wp.vec3i, end: wp.float64, upper: wp.float64315) -> CellTraversal:316for axis in range(3):317# Advance every tied plane; no epsilon changes which interval is integrated.318if traversal.next[axis] <= end:319traversal.plane[axis] += traversal.step[axis]320traversal.next[axis] = cell_crossing(ray, axis, traversal.plane[axis], shape, upper)321return traversal322323324@wp.func325def support_value(326field: wp.array(dtype=wp.float32), point: wp.vec3d, shape: wp.vec3i327) -> wp.float64:328# A clipped endpoint can round a few ulps outside its active support face.329bounded = point330for axis in range(3):331bounded[axis] = wp.clamp(332point[axis], wp.float64(-0.5), wp.float64(shape[axis]) - wp.float64(0.5)333)334return sample_field(field, bounded, shape).value335336337# region book:projection-streamed-quadrature338@cache339def get_forward(double_output: bool = False, cell_gauss: bool = False):340dtype = wp.float64 if double_output else wp.float32341342@wp.kernel(module="unique", module_options=OPTIONS)343def forward(344field: wp.array(dtype=wp.float32),345pose: wp.array(dtype=wp.float64),346config: Configuration,347output: wp.array(dtype=dtype),348status: wp.array(dtype=wp.int32),349):350pixel = wp.tid()351ray = ray_for_pixel(config, pose, pixel)352if not valid_ray(ray):353wp.atomic_or(status, 0, 2)354output[pixel] = dtype(0.0)355return356interval = finite_interval(ray.origin, ray.direction, config.shape)357integral = wp.float64(0.0)358if interval.upper > interval.lower:359if wp.static(cell_gauss):360traversal = begin_cells(ray, interval, config.shape)361start = interval.lower362segments = wp.int64(0)363limit = (364wp.int64(config.shape[0])365+ wp.int64(config.shape[1])366+ wp.int64(config.shape[2])367+ wp.int64(1)368)369while start < interval.upper and segments < limit:370end = wp.min(traversal.next[0], wp.min(traversal.next[1], traversal.next[2]))371if end <= start:372wp.atomic_or(status, 0, 2)373break374radius = (end - start) * wp.float64(0.5)375centre = start + radius376offset = radius / wp.sqrt(wp.float64(3.0))377for node in range(2):378position = centre + wp.float64(2 * node - 1) * offset379integral += (380radius381* sample_field(382field, ray.origin + position * ray.direction, config.shape383).value384)385start = end386traversal = advance_cells(traversal, ray, config.shape, end, interval.upper)387segments += wp.int64(1)388if start < interval.upper:389wp.atomic_or(status, 0, 2)390integral *= ray.length391else:392step = (interval.upper - interval.lower) / wp.float64(config.samples)393for sample in range(config.samples):394position = interval.lower + (wp.float64(sample) + wp.float64(0.5)) * step395point = ray.origin + position * ray.direction396if not finite_point(point):397wp.atomic_or(status, 0, 2)398integral += sample_field(field, point, config.shape).value399integral *= ray.length * step400output[pixel] = dtype(integral)401if not wp.isfinite(output[pixel]):402wp.atomic_or(status, 0, 2)403404return forward405406407# endregion book:projection-streamed-quadrature408409410# region book:projection-discrete-vjp411@cache412def get_vjp(413active_volume: bool,414matrix_gradient: bool,415active_pose: bool,416*,417per_ray: bool = False,418double_seed: bool = False,419cell_gauss: bool = False,420):421if per_ray and (active_volume or matrix_gradient or not active_pose):422raise ValueError("per-ray diagnostics require only the six local pose coordinates")423components = 12 if matrix_gradient else 6424seed_dtype = wp.float64 if double_seed else wp.float32425426@wp.kernel(module="unique", module_options=OPTIONS)427def vjp(428field: wp.array(dtype=wp.float32),429pose: wp.array(dtype=wp.float64),430config: Configuration,431seeds: wp.array(dtype=seed_dtype),432volume_gradient: wp.array(dtype=wp.float32),433partials: wp.array(dtype=wp.float64),434status: wp.array(dtype=wp.int32),435):436block, lane = wp.tid()437pixel = block * BLOCK + lane438gradient = Vec12d()439if pixel < config.pixels:440ray = ray_for_pixel(config, pose, pixel)441if not valid_ray(ray):442wp.atomic_or(status, 0, 2)443else:444interval = finite_interval(ray.origin, ray.direction, config.shape)445span = interval.upper - interval.lower446if span > wp.float64(0.0):447origin_gradient = wp.vec3d()448direction_gradient = wp.vec3d()449factor = wp.float64(seeds[pixel]) * ray.length450if wp.static(cell_gauss):451traversal = begin_cells(ray, interval, config.shape)452start = interval.lower453segments = wp.int64(0)454limit = (455wp.int64(config.shape[0])456+ wp.int64(config.shape[1])457+ wp.int64(config.shape[2])458+ wp.int64(1)459)460while start < interval.upper and segments < limit:461end = wp.min(462traversal.next[0], wp.min(traversal.next[1], traversal.next[2])463)464if end <= start:465wp.atomic_or(status, 0, 2)466break467radius = (end - start) * wp.float64(0.5)468centre = start + radius469offset = radius / wp.sqrt(wp.float64(3.0))470for node in range(2):471position = centre + wp.float64(2 * node - 1) * offset472point = ray.origin + position * ray.direction473if wp.static(active_pose):474value = sample_field(field, point, config.shape)475origin_gradient += radius * value.gradient476direction_gradient += radius * position * value.gradient477if wp.static(active_volume):478scatter_field(479volume_gradient,480point,481config.shape,482factor * radius,483status,484)485start = end486traversal = advance_cells(487traversal, ray, config.shape, end, interval.upper488)489segments += wp.int64(1)490if start < interval.upper:491wp.atomic_or(status, 0, 2)492if wp.static(active_pose):493# Field continuity cancels all internal moving-cell terms.494# The zero-extended support still has external endpoint terms.495lower_value = support_value(496field, ray.origin + interval.lower * ray.direction, config.shape497)498upper_value = support_value(499field, ray.origin + interval.upper * ray.direction, config.shape500)501origin_gradient += (502upper_value * interval.upper_origin_gradient503- lower_value * interval.lower_origin_gradient504)505direction_gradient += (506upper_value * interval.upper_direction_gradient507- lower_value * interval.lower_direction_gradient508)509else:510mean = wp.float64(0.0)511direct_origin = wp.vec3d()512direct_direction = wp.vec3d()513shift_lower = wp.float64(0.0)514shift_upper = wp.float64(0.0)515for sample in range(config.samples):516alpha = (wp.float64(sample) + wp.float64(0.5)) / wp.float64(517config.samples518)519position = interval.lower + alpha * span520point = ray.origin + position * ray.direction521if not finite_point(point):522wp.atomic_or(status, 0, 2)523if wp.static(active_pose):524value = sample_field(field, point, config.shape)525mean += value.value526direct_origin += value.gradient527direct_direction += position * value.gradient528along = wp.dot(value.gradient, ray.direction)529shift_lower += (wp.float64(1.0) - alpha) * along530shift_upper += alpha * along531if wp.static(active_volume):532scatter_field(533volume_gradient,534point,535config.shape,536wp.float64(seeds[pixel])537* ray.length538* span539/ wp.float64(config.samples),540status,541)542if wp.static(active_pose):543lower_seed = -mean + span * shift_lower544upper_seed = mean + span * shift_upper545origin_gradient = (546span * direct_origin547+ lower_seed * interval.lower_origin_gradient548+ upper_seed * interval.upper_origin_gradient549)550direction_gradient = (551span * direct_direction552+ lower_seed * interval.lower_direction_gradient553+ upper_seed * interval.upper_direction_gradient554)555factor /= wp.float64(config.samples)556if wp.static(active_pose):557source_adjoint = factor * (558wp.transpose(config.object_to_grid) * origin_gradient559)560direction_adjoint = factor * (561wp.transpose(config.object_to_grid) * direction_gradient562)563if wp.static(matrix_gradient):564for row in range(3):565for column in range(3):566gradient[3 * row + column] = (567ray.world_source_relative[row] * source_adjoint[column]568+ ray.world_direction[row] * direction_adjoint[column]569)570translation_adjoint = -(unpack_rotation(pose) * source_adjoint)571for axis in range(3):572gradient[9 + axis] = translation_adjoint[axis]573else:574rotation_adjoint = wp.cross(575source_adjoint, ray.object_source576) + wp.cross(direction_adjoint, ray.object_direction)577for axis in range(3):578gradient[axis] = -source_adjoint[axis]579gradient[axis + 3] = rotation_adjoint[axis]580# The diagnostic writes the same ray derivative before any reduction.581# Its caller owns O(6 P) storage; the production specialisation retains582# only reduction partials and performs exactly its existing reduction.583if wp.static(per_ray):584if pixel < config.pixels:585for component in range(components):586component_value = gradient[component]587partials[wp.int64(pixel) * wp.int64(components) + wp.int64(component)] = (588component_value589)590if not wp.isfinite(component_value):591wp.atomic_or(status, 0, 2)592# All lanes, including padded rays, participate in the fixed reduction.593elif wp.static(active_pose):594for component in range(components):595values = wp.tile(gradient[component])596total = wp.tile_sum(values)597wp.tile_store(partials, total, offset=block * components + component)598599return vjp600601602# endregion book:projection-discrete-vjp603