From f1828281228709ea3fe58d8280a192e1adf54cfc Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Fri, 2 Oct 2026 06:57:31 -0700 Subject: [PATCH] Preserve arbitrary ray batch dimensions in compositing weights The density and alpha compositing helpers are annotated for arbitrary leading ray batch dimensions, but create identity prefixes from only the first batch axis. The alpha helper also accumulates and slices along dimension 1. Unbatched rays and image-shaped ray batches consequently fail or composite along the wrong axis. --- nerfstudio/cameras/rays.py | 8 ++-- tests/cameras/test_ray_batch_compositing.py | 49 +++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 tests/cameras/test_ray_batch_compositing.py diff --git a/nerfstudio/cameras/rays.py b/nerfstudio/cameras/rays.py index f4f1086993..aaf751d3c9 100644 --- a/nerfstudio/cameras/rays.py +++ b/nerfstudio/cameras/rays.py @@ -141,9 +141,7 @@ def get_weights(self, densities: Float[Tensor, "*batch num_samples 1"]) -> Float alphas = 1 - torch.exp(-delta_density) transmittance = torch.cumsum(delta_density[..., :-1, :], dim=-2) - transmittance = torch.cat( - [torch.zeros((*transmittance.shape[:1], 1, 1), device=densities.device), transmittance], dim=-2 - ) + transmittance = torch.cat([torch.zeros_like(delta_density[..., :1, :]), transmittance], dim=-2) transmittance = torch.exp(-transmittance) # [..., "num_samples"] weights = alphas * transmittance # [..., "num_samples"] @@ -179,10 +177,10 @@ def get_weights_and_transmittance_from_alphas( """ transmittance = torch.cumprod( - torch.cat([torch.ones((*alphas.shape[:1], 1, 1), device=alphas.device), 1.0 - alphas + 1e-7], 1), 1 + torch.cat([alphas.new_ones((*alphas.shape[:-2], 1, 1)), 1.0 - alphas + 1e-7], dim=-2), dim=-2 ) - weights = alphas * transmittance[:, :-1, :] + weights = alphas * transmittance[..., :-1, :] if weights_only: return weights return weights, transmittance diff --git a/tests/cameras/test_ray_batch_compositing.py b/tests/cameras/test_ray_batch_compositing.py new file mode 100644 index 0000000000..9b3f712541 --- /dev/null +++ b/tests/cameras/test_ray_batch_compositing.py @@ -0,0 +1,49 @@ +# Copyright 2022 the Regents of the University of California, Nerfstudio Team and contributors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Volume compositing should preserve all leading ray batch dimensions.""" + +import pytest +import torch + +from nerfstudio.cameras.rays import Frustums, RaySamples + + +@pytest.mark.parametrize("batch_shape", [(), (2,), (2, 3), (2, 1, 3)]) +@pytest.mark.parametrize("dtype", [torch.float32, torch.float64]) +def test_weight_helpers_preserve_ray_batch_shape(batch_shape, dtype): + shape = batch_shape + (4, 1) + tau = torch.linspace(0.1, 0.7, 4, dtype=dtype).reshape((1,) * len(batch_shape) + (4, 1)).expand(shape) + starts = torch.zeros(shape, dtype=dtype) + samples = RaySamples( + frustums=Frustums( + origins=torch.zeros(batch_shape + (4, 3), dtype=dtype), + directions=torch.ones(batch_shape + (4, 3), dtype=dtype), + starts=starts, + ends=starts + 1, + pixel_area=starts + 1, + ), + deltas=torch.ones_like(tau), + ) + alphas = -torch.expm1(-tau) + expected_transmittance = torch.exp(-tau.cumsum(-2) + tau) + torch.testing.assert_close(samples.get_weights(tau), alphas * expected_transmittance) + weights, transmittance = RaySamples.get_weights_and_transmittance_from_alphas(alphas) + reference_transmittance = torch.cat( + [alphas.new_ones(batch_shape + (1, 1)), (1 - alphas + 1e-7).cumprod(-2)], dim=-2 + ) + assert transmittance.dtype == dtype + torch.testing.assert_close(transmittance, reference_transmittance) + torch.testing.assert_close(weights, alphas * reference_transmittance[..., :-1, :]) + torch.testing.assert_close(RaySamples.get_weights_and_transmittance_from_alphas(alphas, weights_only=True), weights)