Generated from the full canonical file for this source snapshot. Line numbers match the library source.
Source SHA256: 0d5152172eefbf0c9efd3dce534f0f179b42ceddef7de806b4766a55205de795
1"""Rank supplied next-view candidates by expected local target-position variance.23The known attenuation field and calibrated candidate beams are supplied inputs.4The design uses independent ideal Poisson counts, a fixed local pose chart and a5strictly positive-definite current precision. It does not acquire an image or6control imaging hardware. Only compact information matrices leave the GPU.7See the examples README for recorded policy comparisons and their limitations.8"""910from __future__ import annotations1112import importlib13import math14from typing import Any, Literal, cast1516from dpt._runtime import prepare_context17from dpt.contracts import ContractError, NumericalError, finite_scalar, integer18from dpt.examples._common import example_parser, load_case, write_array19from dpt.geometry import DetectorGeometry, RigidTransform20from dpt.projection import (21ProjectionSpec,22prepare_projection,23project_optical_depth,24projection_pose_sensitivities,25)26from dpt.transmission import TransmissionSpec, prepare_transmission, transmit27from dpt.volumes import GridSpec282930def _text(value: Any, name: str) -> str:31if not isinstance(value, str) or not value.strip():32raise ContractError(f"{name} needs a non-empty description")33return value343536def _positive(value: Any, name: str) -> float:37result = finite_scalar(value, name, minimum=0.0)38if result == 0:39raise ContractError(f"{name} must be strictly positive")40return result414243def _mapping(value: Any, name: str) -> dict[str, Any]:44if not isinstance(value, dict):45raise ContractError(f"{name} must be a JSON object with named fields")46if any(not isinstance(key, str) for key in cast(dict[Any, Any], value)):47raise ContractError(f"{name} must be a JSON object with named fields")48return cast(dict[str, Any], value)495051def _grid(raw: dict[str, Any]) -> GridSpec:52values = {key: tuple(value) for key, value in raw.items()}53return GridSpec(**values)545556def _pose(raw: dict[str, Any]) -> RigidTransform:57values = {key: tuple(value) for key, value in raw.items()}58return RigidTransform(**values)596061def _geometry(raw: dict[str, Any]) -> DetectorGeometry:62values = {key: tuple(value) for key, value in raw.items()}63return DetectorGeometry(**values)646566# region book:example-acquisition-targets67def target_jacobians(np: Any, pose: RigidTransform, targets: Any, scales: Any) -> Any:68"""Return d(world target)/dz in mm for T_WO exp((S z)^) at z=0."""69rotation = np.asarray(pose.rotation, dtype=np.float64).reshape(3, 3)70result = np.empty((len(targets), 3, 6), dtype=np.float64)71for index, (x, y, z) in enumerate(targets):72skew = np.asarray([[0.0, -z, y], [z, 0.0, -x], [-y, x, 0.0]])73result[index, :, :3] = rotation74result[index, :, 3:] = -rotation @ skew75result *= scales[None, None, :]76if not np.isfinite(result).all():77raise NumericalError("target derivatives exceed FP64; check target coordinates and scales")78return result798081def target_variance(82np: Any, precision: Any, target_jacobian: Any, maximum_condition: float83) -> tuple[float, Any, float]:84"""Mean target-position variance in mm², without forming an inverse to score."""85if not np.isfinite(precision).all():86raise NumericalError("pose precision is non-finite; discard this candidate score")87values = np.linalg.eigvalsh(precision)88if values[0] <= 0 or values[-1] / values[0] > maximum_condition:89raise NumericalError(90"pose precision must be positive definite and within maximum_precision_condition; "91"check the stated uncertainty and parameter scales, without adding numerical jitter"92)93factor = np.linalg.cholesky(precision)94right = target_jacobian.reshape(-1, 6).T95whitened = np.linalg.solve(factor, right)96if not np.isfinite(whitened).all():97raise NumericalError(98"target covariance solve exceeds FP64; check the precision and chart scales"99)100# hypot scales its sum of squares; individual products must not underflow101# before they contribute to an otherwise representable total variance.102normalisation = math.sqrt(len(target_jacobian))103root_variance = math.hypot(*(float(value) / normalisation for value in whitened.flat))104variance = root_variance * root_variance105if not math.isfinite(variance) or variance <= 0:106raise NumericalError(107"positive target-position variance is outside FP64 range; check the stated "108"uncertainty and physical units, or use a range-preserving score representation"109)110inverse_factor = np.linalg.solve(factor, np.eye(6))111covariance = inverse_factor.T @ inverse_factor112if not np.isfinite(covariance).all() or bool((np.diag(covariance) <= 0).any()):113raise NumericalError(114"local covariance is outside FP64 range; check the precision and chart scales "115"before exporting this candidate"116)117return variance, covariance, float(values[-1] / values[0])118119120# endregion book:example-acquisition-targets121122123# region book:example-acquisition-information124def candidate_information(125*,126np: Any,127torch: Any,128ctx: Any,129grid: GridSpec,130geometry: DetectorGeometry,131attenuation: Any,132pose: Any,133beam_host: Any,134scales: Any,135samples_per_ray: int,136precision: Literal["float32", "float64"] = "float32",137integration: Literal["midpoint", "cell_gauss"] = "midpoint",138) -> tuple[Any, dict[str, Any]]:139"""Use canonical depth derivatives and an FP64 CUDA Fisher reduction.140141Preparation allocates one candidate's O(6P) Jacobian and weighted copy.142Torch views alias Warp storage on the same CUDA stream. There is no143per-pixel Python loop and no image/Jacobian download. This is an offline144candidate evaluation, not a captured or profiled optimisation hot path.145"""146wp, pixels = ctx.wp, geometry.pixels147try:148projection = prepare_projection(149grid,150geometry,151ProjectionSpec(samples_per_ray, precision=precision, integration=integration),152device=str(ctx.device),153stream=ctx.stream,154)155transmission = (156prepare_transmission(157TransmissionSpec(beam="none"),158max_pixels=pixels,159device=str(ctx.device),160stream=ctx.stream,161)162if precision == "float32"163else None164)165with ctx.scope():166beam = wp.array(beam_host.reshape(-1), dtype=wp.float32, device=ctx.device)167depth = wp.empty(pixels, dtype=projection.dtype, device=ctx.device)168log_transmission = wp.empty(169pixels if precision == "float32" else 0, dtype=wp.float32, device=ctx.device170)171ones = wp.ones(pixels, dtype=projection.dtype, device=ctx.device)172jacobian = wp.empty(6 * pixels, dtype=wp.float64, device=ctx.device)173project_optical_depth(174attenuation,175pose,176workspace=projection,177out_L=depth,178stream=ctx.stream,179)180if transmission is not None:181transmit(182depth,183out_log_T=log_transmission,184workspace=transmission,185stream=ctx.stream,186)187projection_pose_sensitivities(188attenuation,189pose,190ones,191out_jacobian=jacobian,192workspace=projection,193stream=ctx.stream,194)195beam_tensor = wp.to_torch(beam)196log_mean = beam_tensor.to(dtype=torch.float64).log_()197if precision == "float64":198log_mean.sub_(wp.to_torch(depth))199else:200log_mean.add_(wp.to_torch(log_transmission))201illuminated = beam_tensor > 0202# A zero supplied beam carries no information. Positive means outside203# the normal FP64 range are rejected; no tail, floor or pixel is hidden.204log_tiny = math.log(float(np.finfo(np.float64).tiny))205if bool((illuminated & (log_mean < log_tiny)).any().item()):206raise NumericalError(207"a positive candidate count mean is below normal FP64 range; "208"use a range-preserving design implementation before ranking this case"209)210root_mean = log_mean.mul_(0.5).exp_()211depth_jacobian = wp.to_torch(jacobian).reshape(pixels, 6)212weighted = depth_jacobian.clone()213weighted.mul_(scales).mul_(root_mean[:, None])214if not bool(torch.isfinite(weighted).all().item()):215raise NumericalError("weighted pose derivatives exceed FP64; discard this candidate")216if bool(((depth_jacobian != 0) & illuminated[:, None] & (weighted == 0)).any().item()):217raise NumericalError(218"a nonzero weighted derivative underflowed; discard this candidate"219)220magnitude = weighted.abs()221minimum = math.sqrt(float(np.finfo(np.float64).tiny))222maximum = math.sqrt(float(np.finfo(np.float64).max) / (6.0 * pixels))223if bool((((magnitude > 0) & (magnitude < minimum)) | (magnitude > maximum)).any().item()):224raise NumericalError(225"candidate Fisher products exceed the declared FP64 accumulation range; "226"rescale the pose chart or use a range-preserving reduction"227)228# Weighted depth derivatives give J_L^T diag(lambda) J_L directly.229# Avoid a division by rounded, potentially zero count predictions.230information = weighted.T @ weighted231result = information.cpu().numpy().copy()232if not np.isfinite(result).all():233raise NumericalError("candidate information is non-finite; discard this score")234diagnostics = {235"pixels": pixels,236"precision": precision,237"integration": integration,238"zero_beam_pixels": int((~illuminated).sum().item()),239"omitted_positive_beam_pixels": 0,240"jacobian_and_weighted_copy_bytes": 2 * 6 * pixels * 8,241"fisher_reduction": "FP64 CUDA; only the 6 by 6 information matrix is downloaded",242}243return 0.5 * (result + result.T), diagnostics244finally:245# Warp owners and the host beam must survive every pending shared-stream operation.246wp.synchronize_stream(ctx.stream)247248249# endregion book:example-acquisition-information250251252def main() -> None:253args = example_parser(__doc__ or "Rank supplied next-view candidates").parse_args()254case = load_case(args.case)255cfg = _mapping(case.config["acquisition_design"], "acquisition_design")256np: Any = importlib.import_module("numpy")257grid = _grid(_mapping(cfg["grid"], "grid"))258pose = _pose(_mapping(cfg["pose"], "pose"))259attenuation_host = case.array(cfg["attenuation"], shape=grid.shape, units="mm^-1")260if (attenuation_host < 0).any():261raise ContractError("attenuation must be nonnegative and already converted to mm^-1")262prior = case.array(cfg["prior_precision"], dtype="float64", shape=(6, 6), units="dimensionless")263if not np.allclose(prior, prior.T, rtol=0.0, atol=1e-12 * float(abs(prior).max())):264raise ContractError("prior_precision must be symmetric in the supplied scaled pose chart")265prior = 0.5 * (prior + prior.T)266targets = case.array(cfg["targets_object_mm"], dtype="float64", units="mm")267if targets.ndim != 2 or targets.shape[1] != 3 or not 1 <= targets.shape[0] <= 256:268raise ContractError(269"targets_object_mm must contain 1 to 256 object-frame points, shape (N,3)"270)271raw_scales = cfg["parameter_scales"]272if len(raw_scales) != 6:273raise ContractError("parameter_scales needs six positive values: mm, mm, mm, rad, rad, rad")274scales = np.asarray([_positive(value, "parameter scale") for value in raw_scales])275target_derivatives = target_jacobians(np, pose, targets, scales)276samples = integer(cfg["samples_per_ray"], "samples_per_ray", minimum=1)277capacity = integer(cfg["max_candidate_pixels"], "max_candidate_pixels", minimum=1)278maximum_cost = _positive(cfg["maximum_cost"], "maximum_cost")279cost_unit = cfg["cost_unit"]280if cost_unit not in ("mAs", "s", "expected_detector_photons"):281raise ContractError(282"cost_unit must be mAs, s or expected_detector_photons; none is absorbed dose"283)284current_description = _text(cfg["current_information_description"], "current information")285maximum_condition = _positive(286cfg.get("maximum_precision_condition", 1e12), "precision condition"287)288if maximum_condition < 1:289raise ContractError("maximum_precision_condition must be at least one")290rank_tolerance = _positive(cfg.get("rank_relative_tolerance", 1e-10), "rank tolerance")291if rank_tolerance >= 1:292raise ContractError("rank_relative_tolerance must be smaller than one")293baseline, baseline_covariance, _ = target_variance(294np, prior, target_derivatives, maximum_condition295)296raw_candidates = cfg["candidates"]297if not isinstance(raw_candidates, list) or not raw_candidates:298raise ContractError("candidates must list at least one supplied feasible acquisition")299candidates: list[dict[str, Any]] = []300names: set[str] = set()301for raw in cast(list[Any], raw_candidates):302candidate = _mapping(raw, "candidate")303name = _text(candidate["name"], "candidate name")304if name in names:305raise ContractError(f"candidate name {name!r} is repeated; assign unique names")306names.add(name)307geometry = _geometry(_mapping(candidate["geometry"], f"{name} geometry"))308if geometry.pixels > capacity:309raise ContractError(310f"{name} exceeds max_candidate_pixels; declare a sufficient memory bound"311)312beam = case.array(candidate["open_beam"], shape=geometry.shape, units="photons/pixel")313if (beam < 0).any() or not (beam > 0).any():314raise ContractError(315f"{name} needs a nonnegative beam with at least one illuminated pixel"316)317cost = _positive(candidate["cost"], f"{name} cost")318if cost_unit == "expected_detector_photons":319expected = float(np.sum(beam, dtype=np.float64))320if not math.isclose(cost, expected, rel_tol=1e-6, abs_tol=0.0):321raise ContractError(f"{name} cost must equal the sum of its open-beam expectations")322candidates.append(323{324"name": name,325"geometry": geometry,326"beam": beam,327"cost": cost,328"feasibility_description": _text(329candidate["feasibility_description"], f"{name} feasibility"330),331}332)333eligible = [candidate for candidate in candidates if candidate["cost"] <= maximum_cost]334if not eligible:335raise ContractError(336"no supplied candidate fits maximum_cost; revise the candidate set or budget"337)338with case.record(339args.output,340entrypoint=__file__,341metadata={342"application": "finite_candidate_acquisition_design",343"evidence_kind": "expected local uncertainty under a fixed ideal Poisson model",344"current_information_description": current_description,345"pose_chart": "local right SE(3) at supplied pose; dimensionless z with supplied S",346"execution_validation": "this run does not establish achieved registration accuracy",347"tail_policy": (348"reject unrepresentable positive-beam contributions; no positive tails omitted"349),350},351) as run:352torch: Any = importlib.import_module("torch")353wp: Any = importlib.import_module("warp")354torch_device = torch.device(args.device)355if torch_device.type != "cuda":356raise ContractError("acquisition design requires CUDA; select --device cuda:N")357torch_stream = torch.cuda.current_stream(torch_device)358wp.init()359stream = wp.stream_from_torch(torch_stream)360ctx = prepare_context(device=args.device, stream=stream)361rows: list[dict[str, Any]] = []362matrices: list[Any] = []363covariances: list[Any] = []364try:365with (366torch.cuda.device(torch_device),367torch.cuda.stream(torch_stream),368torch.no_grad(),369ctx.scope(),370):371attenuation = wp.array(372attenuation_host.reshape(-1), dtype=wp.float32, device=ctx.device373)374pose_device = wp.array(pose.packed(), dtype=wp.float64, device=ctx.device)375scales_device = torch.as_tensor(scales, dtype=torch.float64, device=torch_device)376# region book:example-acquisition-rank377for candidate in eligible:378information, diagnostics = candidate_information(379np=np,380torch=torch,381ctx=ctx,382grid=grid,383geometry=candidate["geometry"],384attenuation=attenuation,385pose=pose_device,386beam_host=candidate["beam"],387scales=scales_device,388samples_per_ray=samples,389precision=cfg.get("precision", "float32"),390integration=cfg.get("integration", "midpoint"),391)392variance, covariance, condition = target_variance(393np, prior + information, target_derivatives, maximum_condition394)395eigenvalues = np.linalg.eigvalsh(information)396largest = float(eigenvalues[-1])397if eigenvalues[0] < -rank_tolerance * max(largest, np.finfo(np.float64).tiny):398raise NumericalError(399"candidate Fisher matrix is indefinite beyond rank tolerance"400)401rank = int(np.count_nonzero(eigenvalues > rank_tolerance * largest))402rows.append(403{404"name": candidate["name"],405"cost": candidate["cost"],406"mean_target_variance_mm2": variance,407"local_fisher_rank": rank,408"posterior_precision_condition": condition,409"diagnostics": diagnostics,410}411)412matrices.append(information)413covariances.append(covariance)414# Exact score ties prefer the smaller stated cost, then the name.415selected = min(416rows,417key=lambda row: (row["mean_target_variance_mm2"], row["cost"], row["name"]),418)419# endregion book:example-acquisition-rank420wp.synchronize_stream(stream)421finally:422torch_stream.synchronize()423run.set_metadata(device=str(ctx.device), warp=wp.__version__, torch=torch.__version__)424write_array(run, "candidate_information.npy", np.stack(matrices))425write_array(run, "candidate_local_covariance.npy", np.stack(covariances))426write_array(run, "current_local_covariance.npy", baseline_covariance)427run.write_json(428"design.json",429{430"selected_candidate": selected["name"],431"cost_unit": cost_unit,432"maximum_cost": maximum_cost,433"current_mean_target_variance_mm2": baseline,434"score": "mean trace of local target covariance; lower is preferred",435"rank_relative_tolerance": rank_tolerance,436"matrix_row_order": [row["name"] for row in rows],437"candidates": rows,438"excluded_by_budget": [439candidate["name"]440for candidate in candidates441if candidate["cost"] > maximum_cost442],443"interpretation": "predicted local uncertainty; no selected image acquired",444},445)446447448if __name__ == "__main__":449main()450