Files
nerfstudio/mattport/nerf/sampler.py
T
Ethan Weber 676beebb14 Setting near and far with scene bounds collider. Also LR schedules. (#74)
* some initial cuda code as I learn. using git to save progress.

* first cuda kernel for ray sampling that mostly works

* cleaning up code. working on lr. structs for scene bounds aabb

* added scene bounds collider code

* updated setup

* setup

* setup

* black

* including some files

* exporting env variable

* new file

* git workflows

* github actions

* github actions

* github actions

* removing env var
2022-05-16 14:03:19 -07:00

154 lines
5.5 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, 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.num_samples = num_samples
self.train_stratified = train_stratified
@torch.no_grad()
def forward(
self,
ray_bundle: RayBundle,
num_samples: Optional[int] = None,
) -> RaySamples:
"""Generates position samples uniformly.
Args:
ray_bundle (RayBundle): Rays to generate samples for
num_samples (Optional[int]): Number of samples per ray
Returns:
RaySamples: Positions and deltas for samples along a ray
"""
assert ray_bundle.nears is not None
assert ray_bundle.fars is not None
num_samples = num_samples or self.num_samples
num_rays = ray_bundle.origins.shape[0]
bins = torch.linspace(0.0, 1.0, num_samples + 1).to(ray_bundle.origins.device) # shape (num_samples+1,)
bins = ray_bundle.nears[:, None] + bins[None, :] * (
ray_bundle.fars[:, None] - ray_bundle.nears[:, None]
) # shape (num_rays, 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[:, :-1] + t_rand * (bins[:, 1:] - bins[:, :-1]) # shape (num_rays, num_samples)
else:
ts = (bins[:, 1:] + bins[:, :-1]) / 2
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