Files
nerfstudio/mattport/nerf/sampler.py
T
2022-05-10 10:18:20 -07:00

159 lines
5.8 KiB
Python

"""
Collection of sampling strategies
"""
from typing import Optional
import torch
from torch import nn
from torchtyping import TensorType
from mattport.structures.rays import RayBundle, RaySamples
class UniformSampler(nn.Module):
"""Sample uniformly along a ray"""
def __init__(self, near_plane: float, far_plane: float, num_samples: int, train_stratified=True) -> None:
"""
Args:
near_plane (float): Minimum distance along ray to sample
far_plane (float): Maximum distance along ray to sample
num_samples (int): Number of samples per ray
train_stratified (bool): Use stratified sampling during training. Defults to True
"""
super().__init__()
self.near_plane = near_plane
self.far_plane = far_plane
self.num_samples = num_samples
self.train_stratified = train_stratified
@torch.no_grad()
def forward(
self,
ray_bundle: RayBundle,
near_plane: Optional[float] = None,
far_plane: Optional[float] = None,
num_samples: Optional[int] = None,
) -> RaySamples:
"""Generates position samples uniformly.
Args:
ray_bundle (RayBundle): Rays to generate samples for
TensorType[..., "output_dim"]: A encoded input tensor
near_plane (Optional[float]): Minimum distance along ray to sample
far_plane (Optional[float]): Maximum distance along ray to sample
num_samples (Optional[int]): Number of samples per ray
Returns:
RaySamples: Positions and deltas for samples along a ray
"""
near_plane = near_plane or self.near_plane
far_plane = far_plane or self.far_plane
num_samples = num_samples or self.num_samples
num_rays = ray_bundle.origins.shape[0]
bins = torch.linspace(near_plane, far_plane, num_samples + 1).to(ray_bundle.origins.device) # (num_samples+1)
if self.train_stratified and self.training:
t_rand = torch.rand((num_rays, num_samples), dtype=bins.dtype, device=bins.device)
ts = bins[None, :-1] + t_rand * (bins[None, 1:] - bins[None, :-1]) # (num_rays, num_samples)
else:
ts = (bins[1:] + bins[:-1]) / 2
ts = ts.unsqueeze(0).repeat(num_rays, 1) # (num_rays, num_samples)
ray_samples = RaySamples(
ts=ts,
ray_bundle=ray_bundle,
)
return ray_samples
class PDFSampler(nn.Module):
"""Sample based on probability distribution"""
def __init__(self, num_samples: int, train_stratified: bool = True, include_original: bool = True) -> None:
"""
Args:
num_samples (int): Number of samples per ray
train_stratified: boolean: Randomize location within each bin during training. Defaults to True
include_original: Add original samples to ray. Defaults to True
"""
super().__init__()
self.num_samples = num_samples
self.include_original = include_original
self.train_stratified = train_stratified
@torch.no_grad()
def forward(
self,
coarse_ray_samples: RaySamples,
weights: TensorType[..., "num_samples"],
num_samples: Optional[int] = None,
eps: float = 1e-5,
) -> RaySamples:
"""Generates position samples given a distribution.
Args:
ray_bundle (RayBundle): Rays to generate samples for
TensorType[..., "output_dim"]: A encoded input tensor
bins (TensorType[..., "num_samples"]): Ray bins
weights: (TensorType[..., "num_samples"]): Weights for each bin
num_samples (Optional[int]): Number of samples per ray
eps: float: Small value to prevent numerical issues. Defaults to 1e-5
Returns:
RaySamples: Positions and deltas for samples along a ray
"""
num_samples = num_samples or self.num_samples
weights = weights[..., :-1]
# Add small offset to rays with zero weight to prevent NaNs
weights_sum = torch.sum(weights, dim=-1, keepdim=True)
padding = torch.relu(eps - weights_sum)
weights = weights + padding / weights.shape[-1]
weights_sum += padding
pdf = weights / weights_sum
cdf = torch.min(torch.ones_like(pdf), torch.cumsum(pdf, dim=-1))
cdf = torch.cat([torch.zeros_like(cdf[..., :1]), cdf], dim=-1)
if self.train_stratified and self.training:
u = torch.rand(size=(*cdf.shape[:-1], num_samples), device=cdf.device)
else:
u = torch.linspace(0.0, 1.0, steps=num_samples, device=cdf.device)
u = u.expand(size=(*cdf.shape[:-1], num_samples))
u = u.contiguous()
indicies = torch.searchsorted(cdf, u, right=True)
below = torch.max(torch.zeros_like(indicies), indicies - 1)
above = torch.min(cdf.shape[-1] - torch.ones_like(indicies), indicies)
indicies_g = torch.stack([below, above], -1)
matched_shape = (indicies_g.shape[0], indicies_g.shape[1], cdf.shape[-1])
cdf_g = torch.gather(cdf.unsqueeze(1).expand(matched_shape), dim=2, index=indicies_g)
bins_g = torch.gather(coarse_ray_samples.ts.unsqueeze(1).expand(matched_shape), dim=2, index=indicies_g)
denom = cdf_g[..., 1] - cdf_g[..., 0]
denom = torch.where(denom < eps, torch.ones_like(denom), denom)
t = (u - cdf_g[..., 0]) / denom
ts = bins_g[..., 0] + t * (bins_g[..., 1] - bins_g[..., 0])
if self.include_original:
ts, _ = torch.sort(torch.cat([coarse_ray_samples.ts, ts], -1), -1)
else:
ts, _ = torch.sort(ts, -1)
# Stop gradients
ts = ts.detach()
ray_samples = RaySamples(
ts=ts,
ray_bundle=coarse_ray_samples.ray_bundle,
)
return ray_samples