4
0
mirror of https://github.com/nerfstudio-project/nerfstudio.git synced 2026-11-11 10:34:29 +08:00
nerfstudio/nerfstudio/utils/math.py
Justin Kerr 85ef567682
Flesh out viewer beta (#2086)
* Increase max_res of first proposal network

* fix render statemachine logic for viewer beta

* clicking on cameras in the viewer

* fix fov calculation, update camera positions while training

* cleanup

* backup

* fix configure theme

* Fix theme configure for latest dev version of viser

* Fix runtime errors

* add set up direction button

* add pause resume button

* Export panel

* Render tab skeleton

* make responsiveness more snappy by stopping training

* depth composite

* start implementing viewercontrol in new viewer

* refactor background img to use depth and add depth layering option

* show/hide images

* speed up rendering

* fix crop color background

* crop

* rotated crops

* rotated crops

* transform handle on crop box

* fix xyzw for crop

* oriented crop export for pointclouds, not for meshes yet

* Initial render panel

* Fix export button

* Theme tweaks

* fix background issue in cropping

* export rotated poisson meshe

* double normals definition in export panel

* Visual nits

* remove tsdf from export panel

* Add licenses

* Fix some CI errors

* Type fixes

* Fix tests

* Fix numpy broken

* various bugfix

---------

Co-authored-by: Brent Yi <yibrenth@gmail.com>
Co-authored-by: terrancewang <terrance.3512@gmail.com>
2023-09-08 05:10:00 +00:00

364 lines
12 KiB
Python

# 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.
""" Math Helper Functions """
from dataclasses import dataclass
from typing import Literal, Tuple
import torch
from jaxtyping import Bool, Float
from torch import Tensor
from nerfstudio.data.scene_box import OrientedBox
def components_from_spherical_harmonics(
levels: int, directions: Float[Tensor, "*batch 3"]
) -> Float[Tensor, "*batch components"]:
"""
Returns value for each component of spherical harmonics.
Args:
levels: Number of spherical harmonic levels to compute.
directions: Spherical harmonic coefficients
"""
num_components = levels**2
components = torch.zeros((*directions.shape[:-1], num_components), device=directions.device)
assert 1 <= levels <= 5, f"SH levels must be in [1,4], got {levels}"
assert directions.shape[-1] == 3, f"Direction input should have three dimensions. Got {directions.shape[-1]}"
x = directions[..., 0]
y = directions[..., 1]
z = directions[..., 2]
xx = x**2
yy = y**2
zz = z**2
# l0
components[..., 0] = 0.28209479177387814
# l1
if levels > 1:
components[..., 1] = 0.4886025119029199 * y
components[..., 2] = 0.4886025119029199 * z
components[..., 3] = 0.4886025119029199 * x
# l2
if levels > 2:
components[..., 4] = 1.0925484305920792 * x * y
components[..., 5] = 1.0925484305920792 * y * z
components[..., 6] = 0.9461746957575601 * zz - 0.31539156525251999
components[..., 7] = 1.0925484305920792 * x * z
components[..., 8] = 0.5462742152960396 * (xx - yy)
# l3
if levels > 3:
components[..., 9] = 0.5900435899266435 * y * (3 * xx - yy)
components[..., 10] = 2.890611442640554 * x * y * z
components[..., 11] = 0.4570457994644658 * y * (5 * zz - 1)
components[..., 12] = 0.3731763325901154 * z * (5 * zz - 3)
components[..., 13] = 0.4570457994644658 * x * (5 * zz - 1)
components[..., 14] = 1.445305721320277 * z * (xx - yy)
components[..., 15] = 0.5900435899266435 * x * (xx - 3 * yy)
# l4
if levels > 4:
components[..., 16] = 2.5033429417967046 * x * y * (xx - yy)
components[..., 17] = 1.7701307697799304 * y * z * (3 * xx - yy)
components[..., 18] = 0.9461746957575601 * x * y * (7 * zz - 1)
components[..., 19] = 0.6690465435572892 * y * z * (7 * zz - 3)
components[..., 20] = 0.10578554691520431 * (35 * zz * zz - 30 * zz + 3)
components[..., 21] = 0.6690465435572892 * x * z * (7 * zz - 3)
components[..., 22] = 0.47308734787878004 * (xx - yy) * (7 * zz - 1)
components[..., 23] = 1.7701307697799304 * x * z * (xx - 3 * yy)
components[..., 24] = 0.6258357354491761 * (xx * (xx - 3 * yy) - yy * (3 * xx - yy))
return components
@dataclass
class Gaussians:
"""Stores Gaussians
Args:
mean: Mean of multivariate Gaussian
cov: Covariance of multivariate Gaussian.
"""
mean: Float[Tensor, "*batch dim"]
cov: Float[Tensor, "*batch dim dim"]
def compute_3d_gaussian(
directions: Float[Tensor, "*batch 3"],
means: Float[Tensor, "*batch 3"],
dir_variance: Float[Tensor, "*batch 1"],
radius_variance: Float[Tensor, "*batch 1"],
) -> Gaussians:
"""Compute gaussian along ray.
Args:
directions: Axis of Gaussian.
means: Mean of Gaussian.
dir_variance: Variance along direction axis.
radius_variance: Variance tangent to direction axis.
Returns:
Gaussians: Oriented 3D gaussian.
"""
dir_outer_product = directions[..., :, None] * directions[..., None, :]
eye = torch.eye(directions.shape[-1], device=directions.device)
dir_mag_sq = torch.clamp(torch.sum(directions**2, dim=-1, keepdim=True), min=1e-10)
null_outer_product = eye - directions[..., :, None] * (directions / dir_mag_sq)[..., None, :]
dir_cov_diag = dir_variance[..., None] * dir_outer_product[..., :, :]
radius_cov_diag = radius_variance[..., None] * null_outer_product[..., :, :]
cov = dir_cov_diag + radius_cov_diag
return Gaussians(mean=means, cov=cov)
def cylinder_to_gaussian(
origins: Float[Tensor, "*batch 3"],
directions: Float[Tensor, "*batch 3"],
starts: Float[Tensor, "*batch 1"],
ends: Float[Tensor, "*batch 1"],
radius: Float[Tensor, "*batch 1"],
) -> Gaussians:
"""Approximates cylinders with a Gaussian distributions.
Args:
origins: Origins of cylinders.
directions: Direction (axis) of cylinders.
starts: Start of cylinders.
ends: End of cylinders.
radius: Radii of cylinders.
Returns:
Gaussians: Approximation of cylinders
"""
means = origins + directions * ((starts + ends) / 2.0)
dir_variance = (ends - starts) ** 2 / 12
radius_variance = radius**2 / 4.0
return compute_3d_gaussian(directions, means, dir_variance, radius_variance)
def conical_frustum_to_gaussian(
origins: Float[Tensor, "*batch 3"],
directions: Float[Tensor, "*batch 3"],
starts: Float[Tensor, "*batch 1"],
ends: Float[Tensor, "*batch 1"],
radius: Float[Tensor, "*batch 1"],
) -> Gaussians:
"""Approximates conical frustums with a Gaussian distributions.
Uses stable parameterization described in mip-NeRF publication.
Args:
origins: Origins of cones.
directions: Direction (axis) of frustums.
starts: Start of conical frustums.
ends: End of conical frustums.
radius: Radii of cone a distance of 1 from the origin.
Returns:
Gaussians: Approximation of conical frustums
"""
mu = (starts + ends) / 2.0
hw = (ends - starts) / 2.0
means = origins + directions * (mu + (2.0 * mu * hw**2.0) / (3.0 * mu**2.0 + hw**2.0))
dir_variance = (hw**2) / 3 - (4 / 15) * ((hw**4 * (12 * mu**2 - hw**2)) / (3 * mu**2 + hw**2) ** 2)
radius_variance = radius**2 * ((mu**2) / 4 + (5 / 12) * hw**2 - 4 / 15 * (hw**4) / (3 * mu**2 + hw**2))
return compute_3d_gaussian(directions, means, dir_variance, radius_variance)
def expected_sin(x_means: torch.Tensor, x_vars: torch.Tensor) -> torch.Tensor:
"""Computes the expected value of sin(y) where y ~ N(x_means, x_vars)
Args:
x_means: Mean values.
x_vars: Variance of values.
Returns:
torch.Tensor: The expected value of sin.
"""
return torch.exp(-0.5 * x_vars) * torch.sin(x_means)
# @torch_compile(dynamic=True, mode="reduce-overhead", backend="eager")
def intersect_aabb(
origins: torch.Tensor,
directions: torch.Tensor,
aabb: torch.Tensor,
max_bound: float = 1e10,
invalid_value: float = 1e10,
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Implementation of ray intersection with AABB box
Args:
origins: [N,3] tensor of 3d positions
directions: [N,3] tensor of normalized directions
aabb: [6] array of aabb box in the form of [x_min, y_min, z_min, x_max, y_max, z_max]
max_bound: Maximum value of t_max
invalid_value: Value to return in case of no intersection
Returns:
t_min, t_max - two tensors of shapes N representing distance of intersection from the origin.
"""
tx_min = (aabb[:3] - origins) / directions
tx_max = (aabb[3:] - origins) / directions
t_min = torch.stack((tx_min, tx_max)).amin(dim=0)
t_max = torch.stack((tx_min, tx_max)).amax(dim=0)
t_min = t_min.amax(dim=-1)
t_max = t_max.amin(dim=-1)
t_min = torch.clamp(t_min, min=0, max=max_bound)
t_max = torch.clamp(t_max, min=0, max=max_bound)
cond = t_max <= t_min
t_min = torch.where(cond, invalid_value, t_min)
t_max = torch.where(cond, invalid_value, t_max)
return t_min, t_max
def intersect_obb(
origins: torch.Tensor,
directions: torch.Tensor,
obb: OrientedBox,
max_bound: float = 1e10,
invalid_value: float = 1e10,
):
"""
Ray intersection with an oriented bounding box (OBB)
Args:
origins: [N,3] tensor of 3d positions
directions: [N,3] tensor of normalized directions
R: [3,3] rotation matrix
T: [3] translation vector
S: [3] extents of the bounding box
max_bound: Maximum value of t_max
invalid_value: Value to return in case of no intersection
"""
# Transform ray to OBB space
R, T, S = obb.R, obb.T, obb.S.to(origins.device)
H = torch.eye(4, device=origins.device, dtype=origins.dtype)
H[:3, :3] = R
H[:3, 3] = T
H_world2bbox = torch.inverse(H)
origins = torch.cat((origins, torch.ones_like(origins[..., :1])), dim=-1)
origins = torch.matmul(H_world2bbox, origins.T).T[..., :3]
directions = torch.matmul(H_world2bbox[:3, :3], directions.T).T
# Compute intersection with axis-aligned bounding box with min as -S and max as +S
aabb = torch.concat((-S / 2, S / 2))
t_min, t_max = intersect_aabb(origins, directions, aabb, max_bound=max_bound, invalid_value=invalid_value)
return t_min, t_max
def safe_normalize(
vectors: Float[Tensor, "*batch_dim N"],
eps: float = 1e-10,
) -> Float[Tensor, "*batch_dim N"]:
"""Normalizes vectors.
Args:
vectors: Vectors to normalize.
eps: Epsilon value to avoid division by zero.
Returns:
Normalized vectors.
"""
return vectors / (torch.norm(vectors, dim=-1, keepdim=True) + eps)
def masked_reduction(
input_tensor: Float[Tensor, "1 32 mult"],
mask: Bool[Tensor, "1 32 mult"],
reduction_type: Literal["image", "batch"],
) -> Tensor:
"""
Whether to consolidate the input_tensor across the batch or across the image
Args:
input_tensor: input tensor
mask: mask tensor
reduction_type: either "batch" or "image"
Returns:
input_tensor: reduced input_tensor
"""
if reduction_type == "batch":
# avoid division by 0 (if sum(M) = sum(sum(mask)) = 0: sum(image_loss) = 0)
divisor = torch.sum(mask)
if divisor == 0:
return torch.tensor(0, device=input_tensor.device)
input_tensor = torch.sum(input_tensor) / divisor
elif reduction_type == "image":
# avoid division by 0 (if M = sum(mask) = 0: image_loss = 0)
valid = mask.nonzero()
input_tensor[valid] = input_tensor[valid] / mask[valid]
input_tensor = torch.mean(input_tensor)
return input_tensor
def normalized_depth_scale_and_shift(
prediction: Float[Tensor, "1 32 mult"], target: Float[Tensor, "1 32 mult"], mask: Bool[Tensor, "1 32 mult"]
):
"""
More info here: https://arxiv.org/pdf/2206.00665.pdf supplementary section A2 Depth Consistency Loss
This function computes scale/shift required to normalizes predicted depth map,
to allow for using normalized depth maps as input from monocular depth estimation networks.
These networks are trained such that they predict normalized depth maps.
Solves for scale/shift using a least squares approach with a closed form solution:
Based on:
https://github.com/autonomousvision/monosdf/blob/d9619e948bf3d85c6adec1a643f679e2e8e84d4b/code/model/loss.py#L7
Args:
prediction: predicted depth map
target: ground truth depth map
mask: mask of valid pixels
Returns:
scale and shift for depth prediction
"""
# system matrix: A = [[a_00, a_01], [a_10, a_11]]
a_00 = torch.sum(mask * prediction * prediction, (1, 2))
a_01 = torch.sum(mask * prediction, (1, 2))
a_11 = torch.sum(mask, (1, 2))
# right hand side: b = [b_0, b_1]
b_0 = torch.sum(mask * prediction * target, (1, 2))
b_1 = torch.sum(mask * target, (1, 2))
# solution: x = A^-1 . b = [[a_11, -a_01], [-a_10, a_00]] / (a_00 * a_11 - a_01 * a_10) . b
scale = torch.zeros_like(b_0)
shift = torch.zeros_like(b_1)
det = a_00 * a_11 - a_01 * a_01
valid = det.nonzero()
scale[valid] = (a_11[valid] * b_0[valid] - a_01[valid] * b_1[valid]) / det[valid]
shift[valid] = (-a_01[valid] * b_0[valid] + a_00[valid] * b_1[valid]) / det[valid]
return scale, shift
return scale, shift