diff --git a/documentation/fvdb/_images/Minibatch2.png b/documentation/fvdb/_images/Minibatch2.png deleted file mode 100644 index 81de2667b..000000000 Binary files a/documentation/fvdb/_images/Minibatch2.png and /dev/null differ diff --git a/documentation/fvdb/_images/build_coarse_subdivide.png b/documentation/fvdb/_images/build_coarse_subdivide.png deleted file mode 100644 index b04e29a1f..000000000 Binary files a/documentation/fvdb/_images/build_coarse_subdivide.png and /dev/null differ diff --git a/documentation/fvdb/_images/build_dual.png b/documentation/fvdb/_images/build_dual.png deleted file mode 100644 index 9e1b09746..000000000 Binary files a/documentation/fvdb/_images/build_dual.png and /dev/null differ diff --git a/documentation/fvdb/_images/build_from_coordinates.png b/documentation/fvdb/_images/build_from_coordinates.png deleted file mode 100644 index 892fb258a..000000000 Binary files a/documentation/fvdb/_images/build_from_coordinates.png and /dev/null differ diff --git a/documentation/fvdb/_images/build_from_dense.png b/documentation/fvdb/_images/build_from_dense.png deleted file mode 100644 index 8457a5f42..000000000 Binary files a/documentation/fvdb/_images/build_from_dense.png and /dev/null differ diff --git a/documentation/fvdb/_images/build_from_mesh.png b/documentation/fvdb/_images/build_from_mesh.png deleted file mode 100644 index c21185179..000000000 Binary files a/documentation/fvdb/_images/build_from_mesh.png and /dev/null differ diff --git a/documentation/fvdb/_images/build_from_points.png b/documentation/fvdb/_images/build_from_points.png deleted file mode 100644 index 66043eda2..000000000 Binary files a/documentation/fvdb/_images/build_from_points.png and /dev/null differ diff --git a/documentation/fvdb/_images/build_from_points_nn.png b/documentation/fvdb/_images/build_from_points_nn.png deleted file mode 100644 index f44aa594f..000000000 Binary files a/documentation/fvdb/_images/build_from_points_nn.png and /dev/null differ diff --git a/documentation/fvdb/_images/clip.png b/documentation/fvdb/_images/clip.png deleted file mode 100644 index 4eee3f382..000000000 Binary files a/documentation/fvdb/_images/clip.png and /dev/null differ diff --git a/documentation/fvdb/_images/coords_in_grid.png b/documentation/fvdb/_images/coords_in_grid.png deleted file mode 100644 index 6ddbdc3df..000000000 Binary files a/documentation/fvdb/_images/coords_in_grid.png and /dev/null differ diff --git a/documentation/fvdb/_images/cubes_inside_grid.png b/documentation/fvdb/_images/cubes_inside_grid.png deleted file mode 100644 index 72076f4a1..000000000 Binary files a/documentation/fvdb/_images/cubes_inside_grid.png and /dev/null differ diff --git a/documentation/fvdb/_images/cubes_intersect_grid.png b/documentation/fvdb/_images/cubes_intersect_grid.png deleted file mode 100644 index 8c32e27e5..000000000 Binary files a/documentation/fvdb/_images/cubes_intersect_grid.png and /dev/null differ diff --git a/documentation/fvdb/_images/fvdb_teaser.png b/documentation/fvdb/_images/fvdb_teaser.png deleted file mode 100644 index 475340cf0..000000000 Binary files a/documentation/fvdb/_images/fvdb_teaser.png and /dev/null differ diff --git a/documentation/fvdb/_images/gridbatch.png b/documentation/fvdb/_images/gridbatch.png deleted file mode 100644 index 65f06d379..000000000 Binary files a/documentation/fvdb/_images/gridbatch.png and /dev/null differ diff --git a/documentation/fvdb/_images/gridbatch_conv.png b/documentation/fvdb/_images/gridbatch_conv.png deleted file mode 100644 index 8654f0eb7..000000000 Binary files a/documentation/fvdb/_images/gridbatch_conv.png and /dev/null differ diff --git a/documentation/fvdb/_images/jaggedtensor1.png b/documentation/fvdb/_images/jaggedtensor1.png deleted file mode 100644 index 86cf5b314..000000000 Binary files a/documentation/fvdb/_images/jaggedtensor1.png and /dev/null differ diff --git a/documentation/fvdb/_images/jaggedtensor4.png b/documentation/fvdb/_images/jaggedtensor4.png deleted file mode 100644 index ac601be6a..000000000 Binary files a/documentation/fvdb/_images/jaggedtensor4.png and /dev/null differ diff --git a/documentation/fvdb/_images/max_avg_pool.png b/documentation/fvdb/_images/max_avg_pool.png deleted file mode 100644 index e684d12b2..000000000 Binary files a/documentation/fvdb/_images/max_avg_pool.png and /dev/null differ diff --git a/documentation/fvdb/_images/mg_mask.png b/documentation/fvdb/_images/mg_mask.png deleted file mode 100644 index 2aca1aa0d..000000000 Binary files a/documentation/fvdb/_images/mg_mask.png and /dev/null differ diff --git a/documentation/fvdb/_images/mg_origin.png b/documentation/fvdb/_images/mg_origin.png deleted file mode 100644 index b0bf3e124..000000000 Binary files a/documentation/fvdb/_images/mg_origin.png and /dev/null differ diff --git a/documentation/fvdb/_images/mg_pts_after.png b/documentation/fvdb/_images/mg_pts_after.png deleted file mode 100644 index 2e81b4753..000000000 Binary files a/documentation/fvdb/_images/mg_pts_after.png and /dev/null differ diff --git a/documentation/fvdb/_images/mg_pts_before.png b/documentation/fvdb/_images/mg_pts_before.png deleted file mode 100644 index 15e5eff23..000000000 Binary files a/documentation/fvdb/_images/mg_pts_before.png and /dev/null differ diff --git a/documentation/fvdb/_images/mutable_grid.png b/documentation/fvdb/_images/mutable_grid.png deleted file mode 100644 index 454fb3d19..000000000 Binary files a/documentation/fvdb/_images/mutable_grid.png and /dev/null differ diff --git a/documentation/fvdb/_images/neighbor_indexes.png b/documentation/fvdb/_images/neighbor_indexes.png deleted file mode 100644 index 0724ff9c5..000000000 Binary files a/documentation/fvdb/_images/neighbor_indexes.png and /dev/null differ diff --git a/documentation/fvdb/_images/pts_in_grid.png b/documentation/fvdb/_images/pts_in_grid.png deleted file mode 100644 index 80b8c8155..000000000 Binary files a/documentation/fvdb/_images/pts_in_grid.png and /dev/null differ diff --git a/documentation/fvdb/_images/sampling_1.png b/documentation/fvdb/_images/sampling_1.png deleted file mode 100644 index 12ae5b032..000000000 Binary files a/documentation/fvdb/_images/sampling_1.png and /dev/null differ diff --git a/documentation/fvdb/_images/sampling_2.png b/documentation/fvdb/_images/sampling_2.png deleted file mode 100644 index 738380949..000000000 Binary files a/documentation/fvdb/_images/sampling_2.png and /dev/null differ diff --git a/documentation/fvdb/_images/screenshot_000000.png.trim.png b/documentation/fvdb/_images/screenshot_000000.png.trim.png deleted file mode 100644 index 9bc16264f..000000000 Binary files a/documentation/fvdb/_images/screenshot_000000.png.trim.png and /dev/null differ diff --git a/documentation/fvdb/_images/screenshot_000004.png.trim.png b/documentation/fvdb/_images/screenshot_000004.png.trim.png deleted file mode 100644 index 54dfe6274..000000000 Binary files a/documentation/fvdb/_images/screenshot_000004.png.trim.png and /dev/null differ diff --git a/documentation/fvdb/_images/screenshot_000006.png.trim.png b/documentation/fvdb/_images/screenshot_000006.png.trim.png deleted file mode 100644 index a6d099686..000000000 Binary files a/documentation/fvdb/_images/screenshot_000006.png.trim.png and /dev/null differ diff --git a/documentation/fvdb/_images/simple_conv.png b/documentation/fvdb/_images/simple_conv.png deleted file mode 100644 index c7d02100f..000000000 Binary files a/documentation/fvdb/_images/simple_conv.png and /dev/null differ diff --git a/documentation/fvdb/_images/stride_conv.png b/documentation/fvdb/_images/stride_conv.png deleted file mode 100644 index ce0f8f884..000000000 Binary files a/documentation/fvdb/_images/stride_conv.png and /dev/null differ diff --git a/documentation/fvdb/_images/struct_mask.png b/documentation/fvdb/_images/struct_mask.png deleted file mode 100644 index 07f60601d..000000000 Binary files a/documentation/fvdb/_images/struct_mask.png and /dev/null differ diff --git a/documentation/fvdb/_images/struct_optim.png b/documentation/fvdb/_images/struct_optim.png deleted file mode 100644 index d2600779b..000000000 Binary files a/documentation/fvdb/_images/struct_optim.png and /dev/null differ diff --git a/documentation/fvdb/_images/subdiv_mask.png b/documentation/fvdb/_images/subdiv_mask.png deleted file mode 100644 index dbaaab265..000000000 Binary files a/documentation/fvdb/_images/subdiv_mask.png and /dev/null differ diff --git a/documentation/fvdb/_images/subdiv_pool_correct.png b/documentation/fvdb/_images/subdiv_pool_correct.png deleted file mode 100644 index 93f77c0d0..000000000 Binary files a/documentation/fvdb/_images/subdiv_pool_correct.png and /dev/null differ diff --git a/documentation/fvdb/_images/subdiv_pool_incorrect.png b/documentation/fvdb/_images/subdiv_pool_incorrect.png deleted file mode 100644 index 88efc027d..000000000 Binary files a/documentation/fvdb/_images/subdiv_pool_incorrect.png and /dev/null differ diff --git a/documentation/fvdb/_images/subdivide.png b/documentation/fvdb/_images/subdivide.png deleted file mode 100644 index c48cb31c7..000000000 Binary files a/documentation/fvdb/_images/subdivide.png and /dev/null differ diff --git a/documentation/fvdb/_images/transposed_stride_conv.png b/documentation/fvdb/_images/transposed_stride_conv.png deleted file mode 100644 index cd26ec050..000000000 Binary files a/documentation/fvdb/_images/transposed_stride_conv.png and /dev/null differ diff --git a/documentation/fvdb/_modules/fvdb/nn/modules.html b/documentation/fvdb/_modules/fvdb/nn/modules.html deleted file mode 100644 index 8e69a5c7b..000000000 --- a/documentation/fvdb/_modules/fvdb/nn/modules.html +++ /dev/null @@ -1,704 +0,0 @@ - - - - - - - - fvdb.nn.modules — fVDB documentation - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for fvdb.nn.modules

-# Copyright Contributors to the OpenVDB Project
-# SPDX-License-Identifier: Apache-2.0
-#
-import math
-from typing import List, Optional, Sequence, Union
-
-import torch
-import torch.nn as nn
-from torch.profiler import record_function
-
-import fvdb
-from fvdb import GridBatch, JaggedTensor
-
-from .vdbtensor import VDBTensor
-
-
-def fvnn_module(module):
-    # Register class as a module in fvdb.nn
-    old_forward = module.forward
-
-    def _forward(self, *args, **kwargs):
-        with record_function(repr(self)):
-            return old_forward(self, *args, **kwargs)
-
-    module.forward = _forward
-    return module
-
-
-GridOrVDBTensor = Union[fvdb.GridBatch, VDBTensor]
-ListOrInt = Union[int, List[int]]
-
-
-
-[docs] -@fvnn_module -class MaxPool(nn.Module): - r"""Applies a 3D max pooling over an input signal. - - Args: - kernel_size: the size of the window to take a max over - stride: the stride of the window. Default value is :attr:`kernel_size` - - Note: - For target voxels that are not covered by any source voxels, the - output feature will be set to zero. - - """ - - def __init__(self, kernel_size: ListOrInt, stride: Optional[ListOrInt] = None): - super().__init__() - self.kernel_size = kernel_size - self.stride = stride or self.kernel_size - - def forward(self, input: VDBTensor, ref_coarse_data: Optional[GridOrVDBTensor] = None) -> VDBTensor: - if isinstance(ref_coarse_data, VDBTensor): - coarse_grid, coarse_kmap = ref_coarse_data.grid, ref_coarse_data.kmap - elif isinstance(ref_coarse_data, fvdb.GridBatch): - coarse_grid, coarse_kmap = ref_coarse_data, None - else: - coarse_grid, coarse_kmap = None, None - - new_feature, new_grid = input.grid.max_pool( - self.kernel_size, input.data, stride=self.stride, coarse_grid=coarse_grid - ) - new_feature.jdata[torch.isinf(new_feature.jdata)] = 0.0 - return VDBTensor(new_grid, new_feature, kmap=coarse_kmap) - - def extra_repr(self) -> str: - return "kernel_size={kernel_size}, stride={stride}".format(kernel_size=self.kernel_size, stride=self.stride)
- - - -
-[docs] -@fvnn_module -class AvgPool(nn.Module): - r"""Applies a 3D average pooling over an input signal. - - Args: - kernel_size: the size of the window to take average over - stride: the stride of the window. Default value is :attr:`kernel_size` - - """ - - def __init__(self, kernel_size: ListOrInt, stride: Optional[ListOrInt] = None): - super().__init__() - self.kernel_size = kernel_size - self.stride = stride or self.kernel_size - - def forward(self, input: VDBTensor, ref_coarse_data: Optional[GridOrVDBTensor] = None) -> VDBTensor: - if isinstance(ref_coarse_data, VDBTensor): - coarse_grid, coarse_kmap = ref_coarse_data.grid, ref_coarse_data.kmap - elif isinstance(ref_coarse_data, fvdb.GridBatch): - coarse_grid, coarse_kmap = ref_coarse_data, None - else: - coarse_grid, coarse_kmap = None, None - - new_feature, new_grid = input.grid.avg_pool( - self.kernel_size, input.data, stride=self.stride, coarse_grid=coarse_grid - ) - return VDBTensor(new_grid, new_feature, kmap=coarse_kmap) - - def extra_repr(self) -> str: - return "kernel_size={kernel_size}, stride={stride}".format(kernel_size=self.kernel_size, stride=self.stride)
- - - -
-[docs] -@fvnn_module -class UpsamplingNearest(nn.Module): - r"""Upsamples the input by a given scale factor using nearest upsampling. - - Args: - scale_factor: the upsampling factor - """ - - def __init__(self, scale_factor: ListOrInt): - super().__init__() - self.scale_factor = scale_factor - - def forward( - self, input: VDBTensor, mask: Optional[JaggedTensor] = None, ref_fine_data: Optional[GridOrVDBTensor] = None - ) -> VDBTensor: - if isinstance(ref_fine_data, VDBTensor): - fine_grid, fine_kmap = ref_fine_data.grid, ref_fine_data.kmap - elif isinstance(ref_fine_data, fvdb.GridBatch): - fine_grid, fine_kmap = ref_fine_data, None - else: - fine_grid, fine_kmap = None, None - - new_feature, new_grid = input.grid.subdivide(self.scale_factor, input.data, mask, fine_grid=fine_grid) - return VDBTensor(new_grid, new_feature, kmap=fine_kmap) - - def extra_repr(self) -> str: - return "scale_factor={scale_factor}".format(scale_factor=self.scale_factor)
- - - -
-[docs] -@fvnn_module -class FillFromGrid(nn.Module): - r""" - Fill the content of input vdb-tensor to another grid. - - Args: - default_value: the default value to fill in the new grid. - """ - - def __init__(self, default_value: float = 0.0) -> None: - super().__init__() - self.default_value = default_value - - def forward(self, input: VDBTensor, other_data: Optional[GridOrVDBTensor] = None) -> VDBTensor: - if isinstance(other_data, VDBTensor): - other_grid, other_kmap = other_data.grid, other_data.kmap - elif isinstance(other_data, fvdb.GridBatch): - other_grid, other_kmap = other_data, None - else: - return input - - new_feature = other_grid.fill_from_grid(input.data, input.grid, self.default_value) - return VDBTensor(other_grid, new_feature, kmap=other_kmap)
- - - -
-[docs] -@fvnn_module -class SparseConv3d(nn.Module): - r"""Applies a 3D convolution over an input signal composed of several input - planes, by performing a sparse convolution on the underlying VDB grid. - - Args: - in_channels: number of channels in the input tensor - out_channels: number of channels produced by the convolution - kernel_size: size of the convolving kernel - stride: stride of the convolution. Default value is 1 - bias: if ``True``, adds a learnable bias to the output. Default: ``True`` - transposed: if ``True``, uses a transposed convolution operator - """ - - CUTLASS_SUPPORTED_CHANNELS = [ - (32, 64), - (64, 128), - (128, 256), - (32, 32), - (64, 64), - (128, 128), - (256, 256), - (128, 64), - (64, 32), - (256, 128), - (384, 256), - (192, 128), - (256, 512), - (512, 256), - (512, 512), - ] - - """ - Backend for performing convolutions: - - "default": for now it is 'igemm_mode1' - - "legacy": the old slow implementation - - "me": MinkowskiEngine implementation - - "halo": 10x10x10 halo buffer implementation, stride 1, kernel 3 - - "cutlass": 4x4x6 cutlass implementation, stride 1, kernel 3, forward only, limited channels support - - "lggs": kernel optimized for sparse structures - - "igemm_mode0": unsorted - - "igemm_mode1": sorted + split=1 - - "igemm_mode2": sorted + split=3 - - "dense": dense convolution - """ - backend: str = "default" - allow_tf32: bool = True - - def __init__( - self, - in_channels: int, - out_channels: int, - kernel_size: Union[int, Sequence] = 3, - stride: Union[int, Sequence] = 1, - bias: bool = True, - transposed: bool = False, - ) -> None: - - super().__init__() - self.in_channels = in_channels - self.out_channels = out_channels - - if isinstance(kernel_size, int): - kernel_size = (kernel_size,) * 3 - assert len(kernel_size) == 3 - - if isinstance(stride, int): - stride = (stride,) * 3 - assert len(stride) == 3 - - self.kernel_size = kernel_size - self.stride = stride - self.transposed = transposed - - if self.transposed: - # Only change kernel size instead of module dict - out_channels, in_channels = in_channels, out_channels - - self.kernel_volume = math.prod(self.kernel_size) - if self.kernel_volume > 1: - # Weight tensor is of shape (Do, Di, K0, K1, K2), but the underlying data is (K2, K1, K0, Di, Do) - # so we don't need to make a copy of the permuted tensor within the conv kernel. - weight_shape = [out_channels, in_channels] + list(self.kernel_size) - weight = torch.zeros(*weight_shape[::-1]).permute(4, 3, 2, 1, 0) - self.weight = nn.Parameter(weight) - else: - self.weight = nn.Parameter(torch.zeros(out_channels, in_channels)) - - if bias: - self.bias = nn.Parameter(torch.Tensor(self.out_channels)) - else: - self.register_parameter("bias", None) - - self.reset_parameters() - - def extra_repr(self) -> str: - s = "{in_channels}, {out_channels}, kernel_size={kernel_size}" - if self.stride != (1, 1, 1): - s += ", stride={stride}" - if self.bias is None: - s += ", bias=False" - if self.transposed: - s += ", transposed=True" - return s.format(**self.__dict__) - - def reset_parameters(self) -> None: - std = 1 / math.sqrt((self.out_channels if self.transposed else self.in_channels) * self.kernel_volume) - self.weight.data.uniform_(-std, std) - if self.bias is not None: - self.bias.data.uniform_(-std, std) - - def _dispatch_conv(self, in_feature, in_grid, in_kmap, out_grid): - - backend = self.backend - - sm_arch = torch.cuda.get_device_capability()[0] + torch.cuda.get_device_capability()[1] / 10 - # tf32 requires compute capability >= 8.0 (Ampere) - if self.allow_tf32 and self.weight.is_cuda: - assert ( - sm_arch >= 8 - ), "TF32 requires GPU with compute capability >= 8.0. Please set fvdb.nn.SparseConv3d.allow_tf32 = False." - - # bf16 requires compute capability >= 8.0 (Ampere) - if self.weight.is_cuda and self.weight.dtype == torch.bfloat16: - assert sm_arch >= 8, "BF16 requires GPU with compute capability >= 8.0." - - # float16 requires compute capability >= 7.5 (Turing) - if self.weight.is_cuda and self.weight.dtype == torch.float16: - assert sm_arch >= 7.5, "FP16 requires GPU with compute capability >= 7.5." - - # cutlass, lggs, halo backends require compute capability >= 8.0 (Ampere) - if backend in ["cutlass", "lggs", "halo"]: - assert ( - torch.cuda.get_device_capability()[0] >= 8 - ), "cutlass, LGGS and Halo backends require GPU with compute capability >= 8.0." - - if backend == "cutlass" and ( - (not self.weight.is_cuda) or (self.in_channels, self.out_channels) not in self.CUTLASS_SUPPORTED_CHANNELS - ): - print( - f"Cutlass backend does not support {self.in_channels} -> {self.out_channels} convolutions, falling back to default" - ) - backend = "default" - - if backend == "lggs" and ((self.in_channels, self.out_channels) not in [(128, 128)]): - print("LGGS backend only supports 128 to 128 convolution, falling back to default") - backend = "default" - - if backend == "default": - if (not self.weight.is_cuda) or in_feature.dtype == torch.float64: - backend = "legacy" - else: - backend = "igemm_mode1" - - if backend == "halo" and self.stride == (1, 1, 1) and self.kernel_size == (3, 3, 3): - assert out_grid is None or in_grid.is_same(out_grid) - return in_grid, in_grid.sparse_conv_halo(in_feature, self.weight, 8), None - - elif backend == "dense" and self.stride == (1, 1, 1): - assert out_grid is None or in_grid.is_same(out_grid) - min_coord = in_grid.ijk.jdata.min(axis=0).values - # BWHDC -> BCDHW - dense_feature = in_grid.write_to_dense(in_feature, min_coord=min_coord).permute(0, 4, 3, 2, 1) - dense_feature = torch.nn.functional.conv3d(dense_feature, self.weight, padding=1, stride=1) - # BCDHW -> BWHDC - dense_feature = dense_feature.permute(0, 4, 3, 2, 1).contiguous() - dense_feature = in_grid.read_from_dense(dense_feature, dense_origins=min_coord) - - return in_grid, dense_feature, None - - else: - # Fallback to the default implementation - can_cache = self.stride == (1, 1, 1) and (out_grid is None or out_grid.is_same(in_grid)) - - if in_kmap is not None and in_kmap.kernel_size == self.kernel_size and can_cache: - kmap, out_grid = in_kmap, in_grid - else: - if self.transposed: - assert out_grid is not None - kmap, _ = out_grid.sparse_conv_kernel_map(self.kernel_size, self.stride, in_grid) - else: - kmap, out_grid = in_grid.sparse_conv_kernel_map(self.kernel_size, self.stride, out_grid) - - out_kmap = kmap if can_cache else None - - backend = self._build_kmap_and_convert_backend(kmap, backend) - - if not self.transposed: - out_feature = kmap.sparse_conv_3d(in_feature, self.weight, backend) - else: - out_feature = kmap.sparse_transpose_conv_3d(in_feature, self.weight, backend) - - return out_grid, out_feature, out_kmap - - def _build_kmap_and_convert_backend(self, kmap: fvdb.SparseConvPackInfo, backend: str) -> fvdb.ConvPackBackend: - if backend in ["legacy", "me"]: - kmap.build_gather_scatter(backend == "me") - return fvdb.ConvPackBackend.GATHER_SCATTER - - elif backend == "cutlass": - kmap.build_cutlass(benchmark=False) - return fvdb.ConvPackBackend.CUTLASS - - elif backend == "igemm_mode0": - kmap.build_implicit_gemm( - sorted=False, split_mask_num=1, training=self.training, split_mask_num_bwd=3, use_tf32=self.allow_tf32 - ) - return fvdb.ConvPackBackend.IGEMM - - elif backend == "igemm_mode1": - kmap.build_implicit_gemm( - sorted=True, split_mask_num=1, training=self.training, split_mask_num_bwd=3, use_tf32=self.allow_tf32 - ) - return fvdb.ConvPackBackend.IGEMM - - elif backend == "igemm_mode2": - kmap.build_implicit_gemm( - sorted=True, split_mask_num=3, training=self.training, split_mask_num_bwd=3, use_tf32=self.allow_tf32 - ) - return fvdb.ConvPackBackend.IGEMM - - elif backend == "lggs": - kmap.build_lggs() - return fvdb.ConvPackBackend.LGGS - - else: - raise NotImplementedError(f"Backend {backend} is not supported") - - def forward( - self, - input: VDBTensor, - out_grid: Optional[GridBatch] = None, - ) -> VDBTensor: - in_feature, in_grid, in_kmap = input.data, input.grid, input.kmap - - if self.kernel_size == (1, 1, 1) and self.stride == (1, 1, 1): - out_feature = in_feature.jdata.matmul(self.weight.transpose(0, 1)) - out_feature = in_feature.jagged_like(out_feature) - out_grid, out_kmap = in_grid, in_kmap - - else: - out_grid, out_feature, out_kmap = self._dispatch_conv(in_feature, in_grid, in_kmap, out_grid) - - if self.bias is not None: - out_feature.jdata = out_feature.jdata + self.bias - - if out_grid is None: - raise RuntimeError("Failed to compute output grid. This is a bug in the implementation.") - return VDBTensor(out_grid, out_feature, out_kmap)
- - - -
-[docs] -@fvnn_module -class GroupNorm(nn.GroupNorm): - r"""Applies Group Normalization over a VDBTensor. - See :class:`~torch.nn.GroupNorm` for detailed information. - """ - - def forward(self, input: VDBTensor) -> VDBTensor: - num_channels = input.data.jdata.size(1) - assert num_channels == self.num_channels, "Input feature should have the same number of channels as GroupNorm" - num_batches = input.grid.grid_count - - flat_data, flat_offsets = input.data.jdata, input.data.joffsets - - result_data = torch.empty_like(flat_data) - - for b in range(num_batches): - feat = flat_data[flat_offsets[b] : flat_offsets[b + 1]] - if feat.size(0) != 0: - feat = feat.transpose(0, 1).reshape(1, num_channels, -1) - feat = super().forward(feat) - feat = feat.reshape(num_channels, -1).transpose(0, 1) - - result_data[flat_offsets[b] : flat_offsets[b + 1]] = feat - - return VDBTensor(input.grid, input.grid.jagged_like(result_data), input.kmap)
- - - -@fvnn_module -class BatchNorm(nn.BatchNorm1d): - r"""Applies Batch Normalization over a VDBTensor. - See :class:`~torch.nn.BatchNorm1d` for detailed information. - """ - - def forward(self, input: VDBTensor) -> VDBTensor: - num_channels = input.data.jdata.size(1) - assert num_channels == self.num_features, "Input feature should have the same number of channels as BatchNorm" - result_data = super().forward(input.data.jdata) - return VDBTensor(input.grid, input.grid.jagged_like(result_data), input.kmap) - - -# Non-linear Activations - - -@fvnn_module -class ElementwiseMixin: - def forward(self, input: VDBTensor) -> VDBTensor: - assert isinstance(input, VDBTensor), "Input should have type VDBTensor" - res = super().forward(input.data.jdata) # type: ignore - return VDBTensor(input.grid, input.data.jagged_like(res), input.kmap) - - -class ELU(ElementwiseMixin, nn.ELU): - r""" - Applies the Exponential Linear Unit function element-wise: - .. math:: - \text{ELU}(x) = \begin{cases} - x, & \text{ if } x > 0\\ - \alpha * (\exp(x) - 1), & \text{ if } x \leq 0 - \end{cases} - """ - - -class CELU(ElementwiseMixin, nn.CELU): - r""" - Applies the CELU function element-wise. - - .. math:: - \text{CELU}(x) = \max(0,x) + \min(0, \alpha * (\exp(x/\alpha) - 1)) - """ - - -class GELU(ElementwiseMixin, nn.GELU): - r""" - Applies the Gaussian Error Linear Units function. - - .. math:: \text{GELU}(x) = x * \Phi(x) - - where :math:`\Phi(x)` is the Cumulative Distribution Function for Gaussian Distribution. - """ - - -
-[docs] -class Linear(ElementwiseMixin, nn.Linear): - r""" - Applies a linear transformation to the incoming data: :math:`y = xA^T + b`. - """
- - - -
-[docs] -class ReLU(ElementwiseMixin, nn.ReLU): - r""" - Applies the rectified linear unit function element-wise: :math:`\text{ReLU}(x) = (x)^+ = \max(0, x)` - """
- - - -
-[docs] -class LeakyReLU(ElementwiseMixin, nn.LeakyReLU): - r""" - Applies the element-wise function: :math:`\text{LeakyReLU}(x) = \max(0, x) + \text{negative\_slope} * \min(0, x)` - """
- - - -
-[docs] -class SELU(ElementwiseMixin, nn.SELU): - r""" - Applies element-wise, :math:`\text{SELU}(x) = \lambda \left\{ - \begin{array}{lr} - x, & \text{if } x > 0 \\ - \text{negative\_slope} \times e^x - \text{negative\_slope}, & \text{otherwise } - \end{array} - \right.` - """
- - - -
-[docs] -class SiLU(ElementwiseMixin, nn.SiLU): - r""" - Applies element-wise, :math:`\text{SiLU}(x) = x * \sigma(x)`, where :math:`\sigma(x)` is the sigmoid function. - """
- - - -
-[docs] -class Tanh(ElementwiseMixin, nn.Tanh): - r""" - Applies element-wise, :math:`\text{Tanh}(x) = \tanh(x) = \frac{e^x - e^{-x}} {e^x + e^{-x}}` - """
- - - -
-[docs] -class Sigmoid(ElementwiseMixin, nn.Sigmoid): - r""" - Applies element-wise, :math:`\text{Sigmoid}(x) = \frac{1}{1 + \exp(-x)}` - """
- - - -# Dropout Layers - - -
-[docs] -class Dropout(ElementwiseMixin, nn.Dropout): - r""" - During training, randomly zeroes some of the elements of the input tensor with probability :attr:`p` - using samples from a Bernoulli distribution. The elements to zero are randomized on every forward call. - """
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/_modules/fvdb/nn/vdbtensor.html b/documentation/fvdb/_modules/fvdb/nn/vdbtensor.html deleted file mode 100644 index 63bec4db0..000000000 --- a/documentation/fvdb/_modules/fvdb/nn/vdbtensor.html +++ /dev/null @@ -1,518 +0,0 @@ - - - - - - - - fvdb.nn.vdbtensor — fVDB documentation - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for fvdb.nn.vdbtensor

-# Copyright Contributors to the OpenVDB Project
-# SPDX-License-Identifier: Apache-2.0
-#
-from dataclasses import dataclass
-from typing import Any, Optional, Tuple, Union
-
-import torch
-
-import fvdb
-from fvdb import GridBatch, JaggedTensor, SparseConvPackInfo
-from fvdb.types import Vec3dBatch, Vec3dBatchOrScalar, Vec3i
-
-JaggedTensorOrTensor = Union[torch.Tensor, JaggedTensor]
-
-
-
-[docs] -@dataclass -class VDBTensor: - """ - A VDBTensor is a thin wrapper around a GridBatch and its corresponding feature JaggedTensor, conceptually denoting a batch of - sparse tensors along with its topology. - It works as the input and output arguments of fvdb's neural network layers. - One can simply construct a VDBTensor from a GridBatch and a JaggedTensor, or from a dense tensor using from_dense(). - """ - - grid: GridBatch - data: JaggedTensor - - # Only stores the kernel map that operates on this grid, reasons being: - # 1) A usual network seldom re-uses computation for down-up-sampling. This saves memory. - # 2) This keeps the implementation simple and the kmap transparent. - kmap: Optional[SparseConvPackInfo] = None - - def __post_init__(self): - if not isinstance(self.grid, GridBatch): - raise TypeError("grid should be of type GridBatch") - if not isinstance(self.data, JaggedTensor): - raise TypeError("data must be a JaggedTensor or a torch.Tensor") - if self.grid.grid_count != len(self.data): - raise ValueError("grid and feature should have the same batch size") - if self.grid.total_voxels != self.data.jdata.size(0): - raise ValueError("grid and feature should have the same total voxel count") - if self.kmap is not None: - if not ( - self.is_same(self.kmap.source_grid) - and self.is_same(self.kmap.target_grid) - and self.kmap.stride == (1, 1, 1) - ): - raise ValueError("kmap should operate on the same grid as this tensor") - - def __getitem__(self, idx): - return VDBTensor(self.grid, self.data[idx]) - - def __len__(self): - return self.grid.grid_count - - def to_dense(self) -> torch.Tensor: - # This would map grid.ijk.min() to dense_feature[0, 0, 0] - return self.grid.write_to_dense(self.data) - - def clear_cache(self): - self.kmap = None - - def is_same(self, other: Union["VDBTensor", GridBatch]): - if isinstance(other, VDBTensor): - return self.grid.address == other.grid.address and self.grid_count == other.grid_count - elif isinstance(other, GridBatch): - return self.grid.address == other.address and self.grid_count == other.grid_count - else: - raise TypeError( - f"Invalid argument 'other' to is_same, must " + f"be a VDBTensor or GridBatch but got {type(other)}" - ) - - # ----------------------------- - # Arithmetic and math functions - # ----------------------------- - def __add__(self, other): - return self._binop(other, lambda a, b: a + b) - - def __sub__(self, other): - return self._binop(other, lambda a, b: a - b) - - def __mul__(self, other): - return self._binop(other, lambda a, b: a * b) - - def __pow__(self, other): - return self._binop(other, lambda a, b: a**b) - - def __neg__(self): - return VDBTensor(self.grid, -self.data, self.kmap) - - def __truediv__(self, other): - return self._binop(other, lambda a, b: a / b) - - def __floordiv__(self, other): - return self._binop(other, lambda a, b: a // b) - - def __mod__(self, other): - return self._binop(other, lambda a, b: a % b) - - def __gt__(self, other): - return self._binop(other, lambda a, b: a > b) - - def __lt__(self, other): - return self._binop(other, lambda a, b: a < b) - - def __ge__(self, other): - return self._binop(other, lambda a, b: a >= b) - - def __le__(self, other): - return self._binop(other, lambda a, b: a <= b) - - def __eq__(self, other): - return self._binop(other, lambda a, b: a == b) - - def __ne__(self, other): - return self._binop(other, lambda a, b: a != b) - - def __iadd__(self, other): - def inplace_add(a, b): - a += b - - return self._binop_inplace(other, inplace_add) - - def __isub__(self, other): - def inplace_sub(a, b): - a -= b - - return self._binop_inplace(other, inplace_sub) - - def __imul__(self, other): - def inplace_mul(a, b): - a *= b - - return self._binop_inplace(other, inplace_mul) - - def __ipow__(self, other): - def inplace_pow(a, b): - a **= b - - return self._binop_inplace(other, inplace_pow) - - def __itruediv__(self, other): - def inplace_truediv(a, b): - a /= b - - return self._binop_inplace(other, inplace_truediv) - - def __ifloordiv__(self, other): - def inplace_floordiv(a, b): - a //= b - - return self._binop_inplace(other, inplace_floordiv) - - def __imod__(self, other): - def inplace_mod(a, b): - a %= b - - return self._binop_inplace(other, inplace_mod) - - def sqrt(self): - return VDBTensor(self.grid, self.data.sqrt(), self.kmap) - - def abs(self): - return VDBTensor(self.grid, self.data.abs(), self.kmap) - - def round(self): - return VDBTensor(self.grid, self.data.round(), self.kmap) - - def floor(self): - return VDBTensor(self.grid, self.data.floor(), self.kmap) - - def ceil(self): - return VDBTensor(self.grid, self.data.ceil(), self.kmap) - - def sqrt_(self): - self.data.sqrt_() - return self - - def abs_(self): - self.data.abs_() - return self - - def round_(self): - self.data.round_() - return self - - def floor_(self): - self.data.floor_() - return self - - def ceil_(self): - self.data.ceil_() - return self - - def _binop(self, other, op): - if isinstance(other, VDBTensor): - return VDBTensor(self.grid, op(self.data, other.data), self.kmap) - else: - return VDBTensor(self.grid, op(self.data, other), self.kmap) - - def _binop_inplace(self, other, op): - if isinstance(other, VDBTensor): - op(self.data, other.data) - return self - else: - op(self.data, other) - return self - - # ----------------------- - # Interpolation functions - # ----------------------- - - def sample_bezier(self, points: JaggedTensorOrTensor) -> JaggedTensor: - return self.grid.sample_bezier(points, self.data) - - def sample_bezier_with_grad(self, points: JaggedTensorOrTensor) -> Tuple[JaggedTensor, JaggedTensor]: - return self.grid.sample_bezier_with_grad(points, self.data) - - def sample_trilinear(self, points: JaggedTensorOrTensor) -> JaggedTensor: - return self.grid.sample_trilinear(points, self.data) - - def sample_trilinear_with_grad(self, points: JaggedTensorOrTensor) -> Tuple[JaggedTensor, JaggedTensor]: - return self.grid.sample_trilinear_with_grad(points, self.data) - - def cpu(self): - return VDBTensor(self.grid.to("cpu"), self.data.cpu(), self.kmap.cpu() if self.kmap is not None else None) - - def cuda(self): - return VDBTensor(self.grid.to("cuda"), self.data.cuda(), self.kmap.cuda() if self.kmap is not None else None) - - def to(self, device_or_dtype: Any): - return VDBTensor( - self.grid.to(device_or_dtype), - self.data.to(device_or_dtype), - self.kmap.to(device_or_dtype) if self.kmap is not None else None, - ) - - def detach(self): - return VDBTensor(self.grid, self.data.detach(), self.kmap) - - def type(self, arg0: torch.dtype): - return VDBTensor(self.grid, self.data.type(arg0)) - - def requires_grad_(self, required_grad): - self.data.requires_grad_(required_grad) - return self - - def clone(self): - return VDBTensor(self.grid, self.data.clone(), self.kmap) - - @property - def num_tensors(self): - return self.data.num_tensors - - @property - def is_cuda(self): - return self.data.is_cuda - - @property - def is_cpu(self): - return self.data.is_cpu - - @property - def device(self): - return self.data.device - - @property - def dtype(self): - return self.data.dtype - - @property - def jidx(self): - return self.data.jidx - - @property - def jlidx(self): - return self.data.jlidx - - @property - def joffsets(self): - return self.data.joffsets - - @property - def jdata(self): - return self.data.jdata - - @property - def rshape(self): - return self.data.rshape - - @property - def lshape(self): - return self.data.lshape - - @property - def ldim(self): - return self.data.ldim - - @property - def eshape(self): - return self.data.eshape - - @property - def edim(self): - return self.data.edim - - @property - def requires_grad(self): - return self.data.requires_grad - - @property - def cum_enabled_voxels(self) -> torch.LongTensor: - return self.grid.cum_enabled_voxels - - @property - def cum_voxels(self) -> torch.LongTensor: - return self.grid.cum_voxels - - @property - def grid_count(self) -> int: - return self.grid.grid_count - - @property - def ijk(self) -> JaggedTensor: - return self.grid.ijk - - @property - def num_voxels(self) -> torch.LongTensor: - return self.grid.num_voxels - - @property - def origins(self) -> torch.FloatTensor: - return self.grid.origins - - @property - def total_voxels(self) -> int: - return self.grid.total_voxels - - @property - def voxel_sizes(self) -> torch.FloatTensor: - return self.grid.voxel_sizes - - @property - def total_leaf_nodes(self) -> int: - return self.grid.total_leaf_nodes - - @property - def num_leaf_nodes(self) -> torch.LongTensor: - return self.grid.num_leaf_nodes - - @property - def grid_to_world_matrices(self) -> torch.FloatTensor: - return self.grid.grid_to_world_matrices - - @property - def world_to_grid_matrices(self) -> torch.FloatTensor: - return self.grid.world_to_grid_matrices - - @property - def bbox(self) -> torch.IntTensor: - return self.grid.bbox - - @property - def dual_bbox(self) -> torch.IntTensor: - return self.grid.dual_bbox - - @property - def total_bbox(self) -> torch.IntTensor: - return self.grid.total_bbox
- - - -def vdbtensor_from_dense( - dense_data: torch.Tensor, - ijk_min: Optional[Vec3i] = None, - voxel_sizes: Optional[Vec3dBatchOrScalar] = None, - origins: Optional[Vec3dBatch] = None, -) -> VDBTensor: - if origins is None: - origins = [0.0] * 3 - if voxel_sizes is None: - voxel_sizes = [1.0] * 3 - if ijk_min is None: - ijk_min = [0, 0, 0] - assert ijk_min is not None - grid = fvdb.gridbatch_from_dense( - dense_data.size(0), - dense_data.size()[1:4], - ijk_min=ijk_min, - voxel_sizes=voxel_sizes, - origins=origins, - device=dense_data.device, - ) - # Note: this would map dense_feature[0, 0, 0] to grid[ijk_min] - data = grid.read_from_dense(dense_data.contiguous(), dense_origins=ijk_min) - return VDBTensor(grid, data) -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/_modules/fvdb/utils/build_ext.html b/documentation/fvdb/_modules/fvdb/utils/build_ext.html deleted file mode 100644 index 6d7a9de6c..000000000 --- a/documentation/fvdb/_modules/fvdb/utils/build_ext.html +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - fvdb.utils.build_ext — fVDB documentation - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for fvdb.utils.build_ext

-# Copyright Contributors to the OpenVDB Project
-# SPDX-License-Identifier: Apache-2.0
-#
-import os
-
-from torch.utils import cpp_extension
-
-import fvdb
-
-
-
-[docs] -def FVDBExtension(name, sources, *args, **kwargs): - """ - Utility function for creating pytorch extensions that depend on fvdb. You then have access to all fVDB's internal - headers to program with. Example usage: - - .. code-block:: python - - from fvdb.utils import FVDBExtension - - ext = FVDBExtension( - name='my_extension', - sources=['my_extension.cpp'], - extra_compile_args={'cxx': ['-std=c++17']}, - libraries=['mylib'], - ) - - :param name: The name of the extension. - :param sources: The list of source files. - :param args: Other arguments to pass to :func:`torch.utils.cpp_extension.CppExtension`. - :param kwargs: Other keyword arguments to pass to :func:`torch.utils.cpp_extension.CppExtension`. - :return: A :class:`torch.utils.cpp_extension.CppExtension` object. - """ - - libraries = kwargs.get("libraries", []) - libraries.append("fvdb") - kwargs["libraries"] = libraries - - library_dirs = kwargs.get("library_dirs", []) - library_dirs.append(os.path.dirname(fvdb.__file__)) - kwargs["library_dirs"] = library_dirs - - include_dirs = kwargs.get("include_dirs", []) - include_dirs.append(os.path.join(os.path.dirname(fvdb.__file__), "include")) - - # We also need to add this because fvdb internally will refer to their headers without the fvdb/ prefix. - include_dirs.append(os.path.join(os.path.dirname(fvdb.__file__), "include/fvdb")) - kwargs["include_dirs"] = include_dirs - - extra_link_args = kwargs.get("extra_link_args", []) - extra_link_args.append(f"-Wl,-rpath={os.path.dirname(fvdb.__file__)}") - kwargs["extra_link_args"] = extra_link_args - - extra_compile_args = kwargs.get("extra_compile_args", {}) - extra_compile_args["nvcc"] = extra_compile_args.get("nvcc", []) - if "--extended-lambda" not in extra_compile_args["nvcc"]: - extra_compile_args["nvcc"].append("--extended-lambda") - kwargs["extra_compile_args"] = extra_compile_args - - return cpp_extension.CUDAExtension(name, sources, *args, **kwargs)
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/_modules/index.html b/documentation/fvdb/_modules/index.html deleted file mode 100644 index 58f1d2711..000000000 --- a/documentation/fvdb/_modules/index.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - Overview: module code — fVDB documentation - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- -

All modules for which code is available

- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/_sources/api/grid_batch.rst.txt b/documentation/fvdb/_sources/api/grid_batch.rst.txt deleted file mode 100644 index e7f478899..000000000 --- a/documentation/fvdb/_sources/api/grid_batch.rst.txt +++ /dev/null @@ -1,5 +0,0 @@ -GridBatch -=============== - -.. autoclass:: fvdb.GridBatch - :members: diff --git a/documentation/fvdb/_sources/api/jagged_tensor.rst.txt b/documentation/fvdb/_sources/api/jagged_tensor.rst.txt deleted file mode 100644 index 17427fda9..000000000 --- a/documentation/fvdb/_sources/api/jagged_tensor.rst.txt +++ /dev/null @@ -1,5 +0,0 @@ -JaggedTensor -=============== - -.. autoclass:: fvdb.JaggedTensor - :members: diff --git a/documentation/fvdb/_sources/api/nn.rst.txt b/documentation/fvdb/_sources/api/nn.rst.txt deleted file mode 100644 index 89db5c12a..000000000 --- a/documentation/fvdb/_sources/api/nn.rst.txt +++ /dev/null @@ -1,20 +0,0 @@ -fvdb.nn -======= - -`fvdb.nn` is a collection of neural network layers to build sparse neural networks. - -.. autoclass:: fvdb.nn.VDBTensor -.. autoclass:: fvdb.nn.MaxPool -.. autoclass:: fvdb.nn.AvgPool -.. autoclass:: fvdb.nn.UpsamplingNearest -.. autoclass:: fvdb.nn.FillFromGrid -.. autoclass:: fvdb.nn.SparseConv3d -.. autoclass:: fvdb.nn.GroupNorm -.. autoclass:: fvdb.nn.Linear -.. autoclass:: fvdb.nn.ReLU -.. autoclass:: fvdb.nn.LeakyReLU -.. autoclass:: fvdb.nn.SELU -.. autoclass:: fvdb.nn.SiLU -.. autoclass:: fvdb.nn.Tanh -.. autoclass:: fvdb.nn.Sigmoid -.. autoclass:: fvdb.nn.Dropout diff --git a/documentation/fvdb/_sources/api/utils.rst.txt b/documentation/fvdb/_sources/api/utils.rst.txt deleted file mode 100644 index 74a789d30..000000000 --- a/documentation/fvdb/_sources/api/utils.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -fvdb.utils -================== - -.. autofunction:: fvdb.utils.build_ext.FVDBExtension diff --git a/documentation/fvdb/_sources/index.rst.txt b/documentation/fvdb/_sources/index.rst.txt deleted file mode 100644 index 503dd6626..000000000 --- a/documentation/fvdb/_sources/index.rst.txt +++ /dev/null @@ -1,55 +0,0 @@ -Welcome to fVDB! -================= - -fVDB, inspired by function notation to resemble :math:`f(VDB)`, is a data structure for encoding and operating on *sparse* voxel hierarchies of features in PyTorch. -A sparse voxel hierarchy is a coarse-to-fine hierarchy of sparse voxel grids such that every fine voxel is contained within some coarse voxel. -fvdb supports storing PyTorch tensors at the corners and centers of voxels in a hierarchy and enables a number of differentiable operations on these tensors (e.g. trilinear interpolation, splatting, ray tracing). - -.. image:: imgs/fvdb_teaser.png - :align: center - :width: 400 - -Please refer to the tutorials for examples of how to install and use fVDB to build your own sparse voxel pipelines. - -.. toctree:: - :caption: Introduction - :hidden: - - self - -.. toctree:: - :maxdepth: 1 - :caption: Tutorials - - tutorials/installation - tutorials/basic_concepts - tutorials/building_grids - tutorials/basic_grid_ops - tutorials/simple_unet - tutorials/io - tutorials/mutable_grids - tutorials/volume_rendering - -.. toctree:: - :maxdepth: 1 - :caption: API References - - api/grid_batch - api/jagged_tensor - -.. toctree:: - :maxdepth: 2 - - api/nn - api/utils - -.. raw:: html - -
- -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/documentation/fvdb/_sources/tutorials/basic_concepts.md.txt b/documentation/fvdb/_sources/tutorials/basic_concepts.md.txt deleted file mode 100644 index fa1927ac2..000000000 --- a/documentation/fvdb/_sources/tutorials/basic_concepts.md.txt +++ /dev/null @@ -1,87 +0,0 @@ -# Basic Concepts - -We give a high-level overview of the main concepts in fVDB. Namely, how fVDB encodes sparse voxel grids with attributes, as well as how fVDB efficiently manages batches with non-uniform numbers of elements. - -## `GridBatch`: Voxel Grids with Attributes - -At its core, fVDB provides means to efficiently encode mini-batches of sparse voxel grids where each voxel contains arbitrary vector or scalar attributes encoded as torch tensors. In practice, such an encoding is implemented via the `GridBatch` class. - -![Minibatch2.png](../imgs/fig/Minibatch2.png) - -A `GridBatch` is an indexing structure which maps 3D `ijk` coordinates to integer offsets which can be used to look up attributes in a tensor. The figure below illustrates this process for a GridBatch containing a single grid. - -![gridbatch.png](../imgs/fig/gridbatch.png) - -In the figure, the `GridBatch` acts as an acceleration structure which encodes the sparsity pattern of the grid (also known as the **********topology**********) and can translate grid coordinates into offsets into a data tensor. - -By separating the grid topology and data tensors, the same grid can be used to operate on many different attributes without rebuilding. - -Every operation in fVDB is built upon this kind of query (e.g. Sparse Convolution uses this query to look up features in the neighborhood of each voxel). - -## `JaggedTensor` and Batching - -Each grid in a `GridBatch` can have a different number of voxels (****e.g.**** in the mini batch of four cars above, each car has a different number of voxels). This means that unlike the dense case, fVDB needs to handle parallel operations over ***jagged batches***. I.e. batches containing different numbers of elements. - -To handle jagged batches, fVDB provides a `JaggedTensor` class. Conceptually, a `JaggedTensor` is a list of tensors with shapes $[N_0, *], [N_1, *], \ldots, [N_{B-1}, *]$ where $B$ is the number of elements in the batch, $N_i$ is the number of elements in the $i^\text{th}$ batch item and $*$ is an arbitrary numer of additional dimensions that all match between the tensors. The figure below illustrates such a list of tensors pictorially. - -![jaggedtensor1.png](../imgs/fig/jaggedtensor1.png) - -In practice, `JaggedTensor`s are represented in memory by concatenating each tensor in the list into a single `jdata` (for Jagged Data) tensor of shape $[N_0 + N_1 + \ldots + N_{B-1}, *]$. Additionally, each `JaggedTensor` stores an additional `jidx` tensor (for Jagged Indexes) of shape $[N_0 + N_1 + \ldots + N_{B-1}]$ containing one int per element in the jagged tensor. `jidx[i]` is the batch index of the $i^\text{th}$ element of `jdata`. Finally, a `JaggedTensor` contains a `joffsets` tensor (for Jagged Offsets) of shape $[B, 2]$ which indicates the start and end positions of the $i^\text{th}$ tensor in the batch. - -![jaggedtensor4.png](../imgs/fig/jaggedtensor4.png) - -Similarly, each `GridBatch` also has `jidx` and `joffsets` corresponding to the batch index of each voxel in the grid, and the start and end offsets of each voxel index in a batch. - -## A simple example - -To illustrate the use of `GridBatch`and `JaggedTensor`, consider a simple example where we build a grid from a point cloud, splat some values onto the voxels of that grid, and then sample them again using a different set of points. - -First, we construct a minibatch of grids using the input points. These input points have corresponding color attributes. - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh -import torch -import point_cloud_utils as pcu - -# We're going to create a minibatch of two point clouds each of which -# has a different number of points -pts1, clrs1 = load_car_1_mesh(mode = "vn") -pts2, clrs2 = load_car_2_mesh(mode = "vn") - -# Creating JaggedTensors: one for points and one for colors -points = fvdb.JaggedTensor([pts1, pts2]) -colors = fvdb.JaggedTensor([clrs1, clrs2]) - -# Create a grid where the voxels each have unit sidelength -grid = fvdb.gridbatch_from_points(points, voxel_sizes=1.0) - -# Indexing into a JaggedTensor returns a JaggedTensor -print(points[0].jdata.shape) -print(points[1].jdata.shape) -``` - -![Minibatch of grids constructed from the input points. These input points have corresponding color attributes.](../imgs/fig/screenshot_000000.png.trim.png) - -Next, we splat the colors at the points to the constructed grid, yielding per-voxel colors. - -```python continuation -# Splat the colors into the grid with trilinear interpolation -# vox_colors is a JaggedTensor of per-voxel normas -vox_colors = grid.splat_trilinear(points, colors) -``` - -![Colors splat at the input points to grid, yielding per-voxel colors.](../imgs/fig/screenshot_000006.png.trim.png) - -Finally, we generate a new set of noisy points and sample the grid to recover colors at those new samples. - -```python continuation -# Now let's generate some random points and sample the grid at those points -sample_points = fvdb.JaggedTensor([torch.rand(10_000, 3), torch.rand(11_000, 3)]).cuda() - -# sampled_colors is a JaggedTensor with the same shape as sample_points with -# one color sampled from the grid at each point -sampled_colors = grid.sample_trilinear(sample_points, vox_colors) -``` - -![Colors resampled at random locations from the grid.](../imgs/fig/screenshot_000004.png.trim.png) diff --git a/documentation/fvdb/_sources/tutorials/basic_grid_ops.md.txt b/documentation/fvdb/_sources/tutorials/basic_grid_ops.md.txt deleted file mode 100644 index daf2d7a92..000000000 --- a/documentation/fvdb/_sources/tutorials/basic_grid_ops.md.txt +++ /dev/null @@ -1,888 +0,0 @@ -# Basic GridBatch Operations - -Here we describe basic operations you can perform on a `GridBatch`. Generally, these operations involve a `GridBatch` and an associated `JaggedTensor` of data representing the attributes/features at each voxel within the grid. - -## Sampling grids - -You can differentiably sample data stored at the voxels of a grid using *trilinear* or *Bézier* interpolation as follows: - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh -import torch -import numpy as np - -# We're going to create a minibatch of two meshes -v1, f1 = load_car_1_mesh(mode="vf") -v2, f2 = load_car_2_mesh(mode="vf") - -# Build a GridBatch from two meshes -mesh_v_jagged = fvdb.JaggedTensor([v1, v2]) -mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).int() -grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.1) - -# Generate some sample points by adding random gaussian noise to the center of each voxel -world_space_centers = grid.grid_to_world(grid.ijk.float()) -# 5 samples per voxel for the first grid and 7 samples per voxel for the second -sample_pts = fvdb.JaggedTensor([ - torch.cat([world_space_centers[0].jdata] * 5), - torch.cat([world_space_centers[1].jdata] * 7)]) -sample_pts += fvdb.JaggedTensor([ - torch.randn(grid.num_voxels_at(0) * 5, 3).to(grid.device) * grid.voxel_sizes[0]*0.3, - torch.randn(grid.num_voxels_at(1) * 7, 3).to(grid.device) * grid.voxel_sizes[1]*0.3]) - -# Generate RGB values per voxel as the normalized absolute grid coordinate of the voxel -per_voxel_colors = world_space_centers.clone() -per_voxel_colors.jdata = torch.abs(world_space_centers.jdata) / torch.norm(world_space_centers.jdata, dim=-1, keepdim=True) - -# Sample these RGB colors at each sample point with trilinear interpolation -# NOTE: You can use grid.sample_bezier to sample using bezier interpolation -sampled_colors = grid.sample_trilinear(sample_pts, per_voxel_colors) -``` - -Grid with color attributes | Points with sampled colors -:------------------------------------:|:------------------------------------: -![](../imgs/fig/sampling_1.png) | ![](../imgs/fig/sampling_2.png) - -## Splatting point data to a grid - -You can differentiably splat data at a set of points into voxels in a grid using *trilinear* or *Bézier* interpolation as follows: - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh -import torch -import point_cloud_utils as pcu - -# We're going to create a minibatch of two point clouds each of which -# has a different number of points -pts1, clrs1 = load_car_1_mesh(mode="vn") -pts2, clrs2 = load_car_2_mesh(mode="vn") - -# JaggedTensors of points and normals -points = fvdb.JaggedTensor([pts1, pts2]) -colors = fvdb.JaggedTensor([clrs1, clrs2]) - -# Create a grid where the voxels each have unit sidelength -grid = fvdb.gridbatch_from_points(points, voxel_sizes=1.0) - -# Splat the normals into the grid with trilinear interpolation -# vox_normals is a JaggedTensor of per-voxel normas -# NOTE: You can use grid.splat_bezier to splat using bezier interpolation -vox_colors = grid.splat_trilinear(points, colors) -``` - -Input grid and points with colors | Splat colors onto the grid -:----------------------------------------------------:|:----------------------------------------------------: -![](../imgs/fig/screenshot_000000.png.trim.png) | ![](../imgs/fig/screenshot_000006.png.trim.png) - -## Checking if points are in a grid - -You can query whether points lie in a grid as follows: - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh -import torch -import numpy as np -import point_cloud_utils as pcu - -# We're going to create a minibatch of two meshes -v1, f1 = load_car_1_mesh(mode="vf") -v2, f2 = load_car_2_mesh(mode="vf") -f1, f2 = f1.to(torch.int32), f2.to(torch.int32) - -# Build a GridBatch from two meshes -mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda() -mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda() -grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.1) - -# Generate some points and check if they lie within the grid -bbox_sizes = grid.bbox[:, 1] - grid.bbox[:, 0] -bbox_origins = grid.bbox[:, 0] -pts = fvdb.JaggedTensor([ - (torch.randn(10_000, 3, device='cuda') - bbox_origins[0]) * bbox_sizes[0], - (torch.randn(11_000, 3, device='cuda') - bbox_origins[0]) * bbox_sizes[0], -]) - -# Get a mask indicating which points lie in the grid -mask = grid.points_in_active_voxel(pts) -``` - -We visualize the points which intersect the grid (yellow points intersect and purple points do not). -![](../imgs/fig/pts_in_grid.png) - -## Checking if ijk coordinates are in a grid - -Similar to querying whether world space points lie in a grid, you can query whether the index space `ijk` integer coordinates lie in a grid as follows: - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh -import torch -import numpy as np -import point_cloud_utils as pcu -import polyscope as ps -import os - - -# We're going to create a minibatch of two meshes -v1, f1 = load_car_1_mesh(mode="vf") -v2, f2 = load_car_2_mesh(mode="vf") -f1, f2 = f1.to(torch.int32), f2.to(torch.int32) - -# Build a GridBatch from two meshes -mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda() -mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda() -grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.025) - -rand_idx_pts = [] -for b, b_grid in enumerate(grid.bbox): - pts = [torch.randint(int(b_grid[0][i]), int(b_grid[1][i]), size=(2_000 * (b+1),), device='cuda') for i in range(3)] - rand_idx_pts.append(torch.stack(pts, dim=1)) - -pts = fvdb.JaggedTensor(rand_idx_pts) - -coords_in_grid = grid.coords_in_active_voxel(pts) -``` - -We visualize the coordinates which intersect the grid (yellow coordinates intersect and purple coordinates do not). -![](../imgs/fig/coords_in_grid.png) - - -## Checking if axis aligned cubes intersect a grid - -There are methods of a grid to help ascertain whether a provided set of axis-aligned cubes (all of the same size) each are contained within voxels of the grid or intersect with any voxel of the grid. These methods are called `cubes_in_grid` and `cubes_intersect_grid`, respectively, and return a `JaggedTensor` of boolean values which indicate the result. - -In this example we create some random points to represent the centers of cubes of size `0.03` units along-a-side. Then we use these two methods to discover which of those cubes would intersect a voxel of the grid and which would be entirely enclosed by a voxel in the grid. - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh -import torch -import numpy as np -import point_cloud_utils as pcu -import polyscope as ps -import os - -# We're going to create a minibatch of two meshes -v1, f1 = load_car_1_mesh(mode="vf") -v2, f2 = load_car_2_mesh(mode="vf") -f1, f2 = f1.to(torch.int32), f2.to(torch.int32) - -# Build a GridBatch from two meshes -mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda() -mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda() -grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.025) - -# Generate some points and check if they lie within the grid -bbox_sizes = (grid.bbox[:, 1] - grid.bbox[:, 0]) * grid.voxel_sizes -bbox_origins = (grid.bbox[:, 0] + grid.origins) * grid.voxel_sizes -pts = fvdb.JaggedTensor( - [ - (torch.rand(3000, 3, device="cuda")) * bbox_sizes[0] + bbox_origins[0], - (torch.rand(2000, 3, device="cuda")) * bbox_sizes[1] + bbox_origins[1], - ] -) - -cube_size = 0.03 - -# We can check if the axis-aligned cubes intersect any voxels of the grid... -cubes_intersect_grid = grid.cubes_intersect_grid(pts, -cube_size / 2, cube_size / 2) -# ... or if they are fully contained within the grid -cubes_in_grid = grid.cubes_in_grid(pts, -cube_size / 2, cube_size / 2) -``` - -We visualize the cubes which intersect a voxel in the grid (yellow cubes intersect and purple cubes do not). -![](../imgs/fig/cubes_intersect_grid.png) - - -And which cubes lie entirely within a voxel of the grid -![](../imgs/fig/cubes_inside_grid.png) - - -## Converting ijk values to indexes - -There is a convenient method to convert ijk values, which are integer coordinates in the index space of a grid, to indexes, which are the linearized indices of the voxels in the GridBatch. This method, `ijk_to_index`, returns a `JaggedTensor` of indexes. If the provided ijk values do not correspond to any voxels in the grid, the returned index will be `-1`. - -```python - -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh -import torch -import numpy as np -import point_cloud_utils as pcu -import polyscope as ps -import os - -torch.random.manual_seed(0) - -def generate_random_points(bounding_box, num_points): - min_i, min_j, min_k = bounding_box[0] - max_i, max_j, max_k = bounding_box[1] - - # Generate random integer points within the bounding box - random_i = torch.randint(min_i, max_i, size=(num_points,)) - random_j = torch.randint(min_j, max_j, size=(num_points,)) - random_k = torch.randint(min_k, max_k, size=(num_points,)) - - random_points = torch.stack([random_i, random_j, random_k], dim=1) - - return random_points - -# We're going to create a minibatch of two meshes -v1, f1 = load_car_1_mesh(mode="vf") -v2, f2 = load_car_1_mesh(mode="vf") -f1, f2 = f1.to(torch.int32), f2.to(torch.int32) - -# Build a GridBatch from two meshes -mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda() -mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda() -grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.025) - -rand_pts = fvdb.JaggedTensor([generate_random_points(bbox, 1000) for bbox in grid.bbox]).cuda() - -rand_pts_indices= grid.ijk_to_index(rand_pts) - -print(rand_pts_indices.jdata) -``` -```bash -tensor([ -1, -1, 121, ..., 4614, 5695, -1], device='cuda:0') -``` - -## Converting indexes to ijk values - -If we have the value of an index into the feature data and want to obtain its corresponding ijk value, this is as simple as indexing into the `ijk` attribute of the grid which is itself just a `JaggedTensor` representing the `ijk` values of each index. Here we get the `ijk` values for 1000 random indexes of a `GridBatch`. - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh -import torch -import numpy as np -import point_cloud_utils as pcu -import polyscope as ps -import os - -# We're going to create a minibatch of two meshes -v1, f1 = load_car_1_mesh(mode="vf") -v2, f2 = load_car_2_mesh(mode="vf") -f1, f2 = f1.to(torch.int32), f2.to(torch.int32) - -# Build a GridBatch from two meshes -mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda() -mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda() -grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.025) - -rand_indexes = torch.randint(0, grid.total_voxels, size=(1000,)).cuda() -print(grid.ijk.jdata[rand_indexes]) -print(grid.ijk.jidx[rand_indexes]) -``` -```bash -tensor([[-11, 5, 7], - [ 8, -4, -5], - [-15, 3, -2], - ..., - [ 18, 1, 6], - [ 19, -3, 6], - [-14, -7, 8]], device='cuda:0', dtype=torch.int32) -``` - -However, you may also want to obtain the batch index of the grid that the `ijk` coordinate belongs to. This is just as simple by using the `jidx` attribute of the `JaggedTensor` which represents the batch index of each element in the `JaggedTensor`. Using the same random indexes as above, we can get the batch index of the grid for each random index. - -```python continuation -print(grid.ijk.jidx[rand_indexes]) -``` -```bash -tensor([0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, - 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, - 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, - 1, 0, 0, 0, 0, 1, 0, 1, 1, 0,…], device='cuda:0') -``` - -While this case of having a random set of `ijk` values is very contrived, it is more likely that we would have a `JaggedTensor` of features that correspond to each `ijk` value but are out-of-order in relation to another `grid.ijk`'s and we need to reorder these features to for this other grid. For this, there is the `ijk_to_inv_index` function. - -Given a `JaggedTensor` of `ijk` values, `grid_batch.ijk_to_inv_index` will return a scalar integer `JaggedTensor` of size `[B, -1]`, where `B` is the number of grids in the batch and -1 represents the number of voxels in each grid. This `JaggedTensor` of indexes can be used to permute the input to `ijk_to_inv_index` (or the feature `JaggedTensor` that correspond to those `ijk`'s) to match the ordering of the `grid_batch`. - -To concisely illustrate the properties of this function, -* if `idx = grid.ijk_to_inv_index(misordered_ijk)`, then `grid.ijk == misordered_ijk[idx]` -* if `idx = grid.ijk_to_index(misordered_ijk)`, then `grid.ijk[idx] == misordered_ijk` - -Note: If any `ijk` values in the grid are not present in the input to `ijk_to_inv_index`, the returned index will be `-1` at that position. - -In this example, let's illustrate the more useful case where we have corresponding features and `ijk` values from a grid that are ordered differently from another reference grid and we want to re-order the features to match the order they should be in the reference grid. - -```python continuation -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh -import torch -import numpy as np -import point_cloud_utils as pcu - -# We're going to create a minibatch of two meshes -v1, f1 = load_car_1_mesh(mode="vf") -v2, f2 = load_car_2_mesh(mode="vf") -f1, f2 = f1.to(torch.int32), f2.to(torch.int32) - -# Build a GridBatch from two meshes -mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda() -mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda() -reference_grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.025) - -# 7 random feature values for each voxel in the grid -features = reference_grid.jagged_like(torch.rand(grid.total_voxels, 7).to(grid.device)) - -# Create a set of randomly shuffled corresponding ijk/features from our original grid/features -shuffled_ijks = [] -shuffled_features = [] - -for i in range(reference_grid.grid_count): - perm = torch.randperm(reference_grid.num_voxels_at(i)) - shuffled_ijks.append(reference_grid.ijk.jdata[grid.ijk.jidx==i][perm]) - shuffled_features.append(features.jdata[features.jidx==i][perm]) - -shuffled_ijks = fvdb.JaggedTensor(shuffled_ijks) -shuffled_features = fvdb.JaggedTensor(shuffled_features) - -# Get the indexes to reorder the shuffled features to match the grid's original ijk ordering -idx = reference_grid.ijk_to_inv_index(shuffled_ijks) - -# Permute the shuffled features based on the ordering from `ijk_to_inv_index` -unshuffled_features = shuffled_features.jdata[idx.jdata] -print("Do the shuffled features that have been permuted based on `ijk_to_inv_index` match the original features? ", "Yes!" if torch.all(unshuffled_features == features.jdata) else "No!") -``` - -```bash -Do the shuffled features that have been permuted based on `ijk_to_inv_index` match the original features? Yes! -``` - - -## Getting indexes of neighbors - -If we want to get the indexes of all the spatial neighbors of a set of ijk values, we can use the `neighbor_indexes` method of a `GridBatch`. This method receives a set of `ijk` values and an `extent` (the number of voxels away from the ijk value to consider neighbors) and returns a `JaggedTensor` of indexes of neighbors for each ijk value. The returned `JaggedTensor` has `jdata` of size `[N, extent*2+1, extent*2+1, extent*2+1]` where `N` is the number of requested ijk values. - -In this example we create a set of 24 random ijk values per grid and get the indexes of all neighbors 2 voxels away from each ijk. - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh -import torch -import numpy as np -import point_cloud_utils as pcu -import polyscope as ps -import os - -# We're going to create a minibatch of two meshes -v1, f1 = load_car_1_mesh(mode="vf") -v2, f2 = load_car_2_mesh(mode="vf") -f1, f2 = f1.to(torch.int32), f2.to(torch.int32) - -# Build a GridBatch from two meshes -mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda() -mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda() -grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.025) - -# Build a set of 24 randomly selected ijk values per grid -rand_ijks = fvdb.JaggedTensor( - [ - grid.ijk.jdata[ - torch.randint(int(grid.ijk.joffsets[b]), int(grid.ijk.joffsets[b + 1]), (24,)) - ] - for b in range(grid.grid_count - 1) - ], -) - -# Get the indexes of all neighbors 2 voxels away from each ijk -# Returns a JaggedTensor with jdata of size [48, 5, 5, 5] ([ N x (extent*2+1)^3 ]) -# where each [5, 5, 5] describes the layout of neighbouring indexes of each ijk -neighbor_idxs = grid.neighbor_indexes(rand_ijks, 2) -``` - -We visualize the voxels we selected in red and their neighbors in blue. -![](../imgs/fig/neighbor_indexes.png) - - -## Clipping a grid to a bounding box - -If we want to 'clip' a grid, meaning remove all the voxels that are outside of a bounding box, we can use the `clipped_grid` method of a `GridBatch`. The minimum and maximum `ijk` extents of the bounding box used for this clipping are provided as arguments and can be specified per-grid of the batch. - -If we want to both clip the batch of grids and the accompanying `JaggedTensor` of batched features, we can use the `clip` method of a `GridBatch` which can be provided the `features` to be clipped along with the grid. - -In this example we clip a batch of grids to independent bounding boxes and visualize the result. - -```python -import os -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh, load_car_3_mesh, load_car_4_mesh -import torch -import numpy as np -import point_cloud_utils as pcu - -device = 'cuda' -mesh_vs = [] -mesh_fs = [] -mesh_load_funcs = [load_car_1_mesh, load_car_2_mesh, load_car_3_mesh, load_car_4_mesh] - -for func in mesh_load_funcs: - v, f = func(mode="vf") - mesh_vs.append(v) - mesh_fs.append(f.to(torch.int32)) - -mesh_vs = fvdb.JaggedTensor(mesh_vs) -mesh_fs = fvdb.JaggedTensor(mesh_fs) - -vox_size = 0.01 - -# Build GridBatch from meshes -grid = fvdb.gridbatch_from_mesh(mesh_vs, mesh_fs, vox_size) - -# Use `clipped_grid` to clip the grids outside of the specified minimum and maximum bounding boxes for each grid… -clipped_grid = grid.clipped_grid( - ijk_min=[[-200, -200, -200], [0, -200, -200], [-200, -200, -200], [-200, 0, -200]], - ijk_max=[[0, 200, 200], [200, 200, 200], [200, 0, 200], [200, 200, 200]]) - -# Use `clip` to both clip the grids and the accompanying JaggedTensor of features -features = grid.jagged_like(torch.rand(grid.total_voxels, 7).to(device)) -clipped_features, clipped_grid = grid.clip( - features=features, - ijk_min=[[-200, -200, -200], [0, -200, -200], [-200, -200, -200], [-200, 0, -200]], - ijk_max=[[0, 200, 200], [200, 200, 200], [200, 0, 200], [200, 200, 200]]) - -``` - -We visualize these grids, each clipped to a different bounding box. - -![](../imgs/fig/clip.png) - - -## Max/Mean Pooling - -'Pooling' a grid has the effect of coarsening the resolution of the grid by a constant factor (and can be performed anisotropically for different factors in each `xyz` spatial dimension). When pooling a grid, the values of the new, coarser grid can be determined by the maximum or mean of the values of the voxels in the original grid that are covered by each voxel in the new grid. Maximum and mean pooling can be accomplished by the `max_pool` and `avg_pool` operators. - -In this example, we create a grid from a mesh and then perform max and mean pooling on it to illustrate the difference between the two. - -```python -import os -import fvdb -from fvdb.utils.examples import load_car_1_mesh -import torch -import numpy as np -import point_cloud_utils as pcu - -vox_size = 0.02 -num_pts = 10_000 - -mesh_load_funcs = [load_car_1_mesh] - -points = [] -normals = [] - -for func in mesh_load_funcs: - pts, nms = func(mode="vn") - pmt = torch.randperm(pts.shape[0])[:num_pts] - pts, nms = pts[pmt], nms[pmt] - points.append(pts) - normals.append(nms) - -# JaggedTensors of points and normals -points = fvdb.JaggedTensor(points) -normals = fvdb.JaggedTensor(normals) - -# Create a grid -grid = fvdb.gridbatch_from_points(points, voxel_sizes=vox_size) - -# Splat the normals into the grid with trilinear interpolation -vox_normals = grid.splat_trilinear(points, normals) - -# Mean Pooling of normals features -avg_normals, avg_grid = grid.avg_pool(4, vox_normals) -# Max Pooling of normals features -max_normals, max_grid = grid.max_pool(4, vox_normals) -``` - -We visualize the original grid with values of the mesh normals as features, the features/grid after mean pooling, and the features/grid after max pooling to illustrate how these pooling modes affect the resulting features of the grid. - -![](../imgs/fig/max_avg_pool.png) - - -## Subdividing a grid - -Subdividing a grid has the effect of increasing the resolution of the grid by a constant factor (and can be performed anisotropically for different factors in each `xyz` spatial dimension). - -The most straightforward way to use the `subdivide` method is to provide an integer value for the subdivision factor. This will result in a grid that is `subdiv_factor` times the resolution of the original grid in each spatial dimension. - -```python -import os -import fvdb -from fvdb.utils.examples import load_car_1_mesh -import torch -import numpy as np -import point_cloud_utils as pcu - -vox_size = 0.02 -num_pts = 10_000 - -mesh_load_funcs = [load_car_1_mesh] - -points = [] -normals = [] - -for func in mesh_load_funcs: - pts, nms = func(mode="vn") - pmt = torch.randperm(pts.shape[0])[:num_pts] - pts, nms = pts[pmt], nms[pmt] - points.append(pts) - normals.append(nms) - -# JaggedTensors of points and normals -points = fvdb.JaggedTensor(points) -normals = fvdb.JaggedTensor(normals) - -# Create a grid -grid = fvdb.gridbatch_from_points(points, voxel_sizes=vox_size) - -# Splat the normals into the grid with trilinear interpolation -vox_normals = grid.splat_trilinear(points, normals) - -# Subdivide by a constant factor of 2 -subdiv_normals, subdiv_grid = grid.subdivide(2, vox_normals) -``` - -Here we visualize the original grid on the right and the grid after subdivision by a factor of 2 on the left. - -![](../imgs/fig/subdivide.png) - - -In practice, in a deep neural network like a U-Net architecture, the resolution of the grid can be decreased early in the network and then the grid's features need to be concatenated with features of the grid after its resolution is increased again. When working with traditional, dense 2D or 3D data, cropping any mismatched outputs is straightforward to be able to concatenate these features. However, in a sparse 3D grid, this is not straightforward and it's entirely unclear how to align the features. - -Take this simple example to illustrate the difficulties created by changing the grid topology in this way. We take the grid created from our mesh, perform Pooling by a factor of 2 and then try to Subdivide by an equal factor of 2 to invert the Pooling operation. - -```python continuation -max_normals, max_grid = grid.max_pool(2, vox_normals) - -subdiv_normals, subdiv_grid = max_grid.subdivide(2, max_normals) -``` - - -Let's visualize these results from left to right of the original grid, the grid after max pooling, and the grid after max pooling and then subdividing again by the same factor. - -![](../imgs/fig/subdiv_pool_incorrect.png) - - -Notice how we have not obtained the topology of the original grid and have obtained a grid with many more voxels than the original. - -To correctly invert the Pooling operation, we can provide the `subdivide` function with a `fine_grid` optional argument which describes the topology we want the grid to have after the subdivision. The original grid before the Pooling operation can be used as this `fine_grid`. - -```python continuation -max_normals, max_grid = grid.max_pool(2, vox_normals) - -# Providing the original grid as our fine_grid target -subdiv_normals, subdiv_grid = max_grid.subdivide(2, max_normals, fine_grid=grid) -``` - -![](../imgs/fig/subdiv_pool_correct.png) - - -Note the matching topology of the grid after the Subdivision operation to the original grid (though the features are different due to the Pooling operation). - -One other useful feature of the `subdivide` operator is that this operation can be *masked* so that subdivision is only performed on a subset of the voxels in the grid. - -The optional `mask` argument to `subdivide` is a `JaggedTensor` of boolean values that indicates which voxels should be subdivided. Given this `mask` is simply a `JaggedTensor`, this operation can be made differentiable in a neural network and the `subdivide` operator can be learned. - -Let's illustrate a very simple example of how to use the `mask` argument to only subdivide the voxels which have a feature value greater than a certain threshold. - -```python continuation -# Mask the grid with the normals where only the normals with a value greater than 0.5 on the x-axis are subdivided -mask = vox_normals.jdata[:, 0] > 0.5 - -subdiv_normals, subdiv_grid = grid.subdivide(2, vox_normals, mask=mask) -``` - -![](../imgs/fig/subdiv_mask.png) - - -## Getting the number of enabled voxels per grid in a batch - -Getting the number of voxels in the grids of a `GridBatch` can be easily accomplished with `num_voxels`: - -```python -import fvdb -import torch - -# Create a GridBatch of random points -batch_size = 4 -pts = fvdb.JaggedTensor([torch.rand(10_000*(i+1),3) for i in range(batch_size)]) -grid = fvdb.GridBatch(mutable=False) -grid.set_from_points(pts, voxel_sizes=0.02) - -# Get the number of voxels per grid in the batch -for batch, num_voxels in enumerate(grid.num_voxels): - print(f"Grid {batch} has {num_voxels} voxels") -``` -```bash -Grid 0 has 9645 voxels -Grid 1 has 18503 voxels -Grid 2 has 26749 voxels -Grid 3 has 34378 voxels -``` - -If the grid is *mutable* and the number of enabled voxels has changed, you can use `num_enabled_voxels` to get the number of *enabled* voxels. If a grid is not mutable, `num_enabled_voxels` will return the same value as `num_voxels`. - -```python continuation -# Create a mutable GridBatch of random points -grid = fvdb.GridBatch(mutable=True) -grid.set_from_points(pts, voxel_sizes=0.02) -# Disable some voxels randomly -grid.disable_ijk(fvdb.JaggedTensor([torch.randint(0,50,(100_000,3)) for _ in range(batch_size)])) - -# Get the number of enabled voxels per grid in the batch -for batch, num_voxels in enumerate(grid.num_enabled_voxels): - print(f"Grid {batch} has {grid.num_enabled_voxels_at(batch)} enabled voxels and {grid.num_voxels_at(batch)} total voxels") -``` -```bash -Grid 0 has 4434 enabled voxels and 9645 total voxels -Grid 1 has 8620 enabled voxels and 18503 total voxels -Grid 2 has 12456 enabled voxels and 26749 total voxels -Grid 3 has 16155 enabled voxels and 34378 total voxels -``` - -## Converting between ijk (grid) coordinates and world coordinates - -A `GridBatch` can contain multiple grids, each with its own coordinate system that relate the grids' `ijk` integer index space coordinates to `xyz` floating-point world-space coordinates. These axis-aligned coordinate systems are defined by specifying a list of three-dimensional world-space origin and scale values when we define the topology of the grids in the `GridBatch` like so: - -```python -import fvdb -import torch - -# Create a GridBatch of random points -batch_size = 4 -pts = fvdb.JaggedTensor([torch.rand(10_000*(i+1),3) for i in range(batch_size)]) -grid = fvdb.GridBatch() -grid.set_from_points(pts, - voxel_sizes=[[0.02, 0.02, 0.02], [0.03, 0.03, 0.03], [0.04, 0.04, 0.04], [0.05, 0.05, 0.05]], - origins=[[-.1,-.1,-.1], [0,0,0], [.1,.1,.1], [.2,-.2,.2]]) -``` - -When `voxel_sizes` and `origins` are not defined, it is assumed all grids have a unit scale voxel_size and an origin at `[0.0, 0.0, 0.0]`. - -We can use `GridBatch`'s `grid_to_world` function to convert between `ijk` index coordinates and their corresponding world-space `xyz` coordinates. In this example, let's obtain the world-space position that would lie at the index-space `[1, 1, 1]` point of each grid: - -```python continuation -# Convert ijk coordinates to world coordinates -ijk = fvdb.JaggedTensor([torch.ones(1,3, dtype=torch.float) for _ in range(batch_size)]) -world_coords = grid.grid_to_world(ijk) -for i in range(grid.grid_count): - print(f"World-space point that lies at index [1,1,1] for Grid {i} is positioned at {world_coords.jdata[world_coords.jidx==i].tolist()}") -``` -```bash -World-space point that lies at index [1,1,1] for Grid 0 is positioned at [[-0.07999999821186066, -0.07999999821186066, -0.07999999821186066]] -World-space point that lies at index [1,1,1] for Grid 1 is positioned at [[0.030000001192092896, 0.030000001192092896, 0.030000001192092896]] -World-space point that lies at index [1,1,1] for Grid 2 is positioned at [[0.14000000059604645, 0.14000000059604645, 0.14000000059604645]] -World-space point that lies at index [1,1,1] for Grid 3 is positioned at [[0.25, -0.15000000596046448, 0.25]] -``` - -We can also do the inverse operation and convert world-space `xyz` coordinates to their corresponding `ijk` index-space coordinates using `world_to_grid`. In this example, let's find the `ijk` index coordinates of the voxel which would contain the world-space point located at `[1.0, 1.0, 1.0]` for each grid in our `GridBatch`: - -```python continuation -# Convert world coordinates to ijk coordinates -xyz = fvdb.JaggedTensor([torch.ones(1,3, dtype=torch.float) for _ in range(batch_size)]) -ijk_coords = grid.world_to_grid(xyz) -for i in range(grid.grid_count): - print(f"Index-space voxel that contains the point [1.0, 1.0, 1.0] for Grid {i} {ijk_coords.jdata[ijk_coords.jidx==i].int().tolist()}") -``` -```bash -Index-space voxel that contains the point [1.0, 1.0, 1.0] for Grid 0 [[55, 55, 55]] -Index-space voxel that contains the point [1.0, 1.0, 1.0] for Grid 1 [[33, 33, 33]] -Index-space voxel that contains the point [1.0, 1.0, 1.0] for Grid 2 [[22, 22, 22]] -Index-space voxel that contains the point [1.0, 1.0, 1.0] for Grid 3 [[16, 24, 16]] -``` - -While `grid_to_world` and `world_to_grid` are convenient functions for these purposes, the row-major transformation matrices used for these calculations can be obtained directly from a `GridBatch` for use in your own logic: - -```python continuation -print(f"Grid to world matrices:\n{grid.grid_to_world_matrices}") -print(f"World to grid matrices:\n{grid.grid_to_world_matrices}") -``` -```bash -Grid to world matrices: -tensor([[[ 0.0200, 0.0000, 0.0000, 0.0000], - [ 0.0000, 0.0200, 0.0000, 0.0000], - [ 0.0000, 0.0000, 0.0200, 0.0000], - [-0.1000, -0.1000, -0.1000, 1.0000]], - - [[ 0.0300, 0.0000, 0.0000, 0.0000], - [ 0.0000, 0.0300, 0.0000, 0.0000], - [ 0.0000, 0.0000, 0.0300, 0.0000], - [ 0.0000, 0.0000, 0.0000, 1.0000]], - - [[ 0.0400, 0.0000, 0.0000, 0.0000], - [ 0.0000, 0.0400, 0.0000, 0.0000], - [ 0.0000, 0.0000, 0.0400, 0.0000], - [ 0.1000, 0.1000, 0.1000, 1.0000]], - - [[ 0.0500, 0.0000, 0.0000, 0.0000], - [ 0.0000, 0.0500, 0.0000, 0.0000], - [ 0.0000, 0.0000, 0.0500, 0.0000], - [ 0.2000, -0.2000, 0.2000, 1.0000]]]) -World to grid matrices: -tensor([[[ 0.0200, 0.0000, 0.0000, 0.0000], - [ 0.0000, 0.0200, 0.0000, 0.0000], - [ 0.0000, 0.0000, 0.0200, 0.0000], - [-0.1000, -0.1000, -0.1000, 1.0000]], - - [[ 0.0300, 0.0000, 0.0000, 0.0000], - [ 0.0000, 0.0300, 0.0000, 0.0000], - [ 0.0000, 0.0000, 0.0300, 0.0000], - [ 0.0000, 0.0000, 0.0000, 1.0000]], - - [[ 0.0400, 0.0000, 0.0000, 0.0000], - [ 0.0000, 0.0400, 0.0000, 0.0000], - [ 0.0000, 0.0000, 0.0400, 0.0000], - [ 0.1000, 0.1000, 0.1000, 1.0000]], - - [[ 0.0500, 0.0000, 0.0000, 0.0000], - [ 0.0000, 0.0500, 0.0000, 0.0000], - [ 0.0000, 0.0000, 0.0500, 0.0000], - [ 0.2000, -0.2000, 0.2000, 1.0000]]]) -``` - - -## Convolution - -Convolving the features of a `GridBatch` can be accomplished with either a high-level `torch.nn.Module` derived class provided by `fvdb.nn` or with more low-level methods available with `GridBatch`, we will illustrate both techniques. - -### High-level Usage with `fvdb.nn` - -`fvdb.nn.SparseConv3d` provides a high-level `torch.nn.Module` class for convolution on `fvdb` classes that is an analogue to the use of `torch.nn.Conv3d`. Using this module is the recommended functionality for performing convolution with `fvdb` because it not only manages functionality such as initializing the weights of the convolution and calling appropriate backend implementation functions but it also provides certain backend optimizations which will be illustrated in the [Low-level usage](#low-level-usage-with-gridbatch) section. - -One thing to note is `fvdb.nn.SparseConv3d` operates on a class that wraps a `GridBatch` and `JaggedTensor` together into a convenience object, `fvdb.VDBTensor`, which is used by all the `fvdb.nn` modules. - -A simple example of using `fvdb.nn.SparseConv3d` is as follows: - -```python -import fvdb -import fvdb.nn as fvdbnn -from fvdb.utils.examples import load_car_1_mesh -import torch -import numpy as np -import point_cloud_utils as pcu - -num_pts = 10_000 -vox_size = 0.02 - -mesh_load_funcs = [load_car_1_mesh] - -points = [] -normals = [] - -for func in mesh_load_funcs: - pts, nms = func(mode="vn") - pmt = torch.randperm(pts.shape[0])[:num_pts] - pts, nms = pts[pmt], nms[pmt] - points.append(pts) - normals.append(nms) - -# JaggedTensors of points and normals -points = fvdb.JaggedTensor(points) -normals = fvdb.JaggedTensor(normals) - -# Create a grid -grid = fvdb.gridbatch_from_points(points, voxel_sizes=vox_size) - -# Splat the normals into the grid with trilinear interpolation -vox_normals = grid.splat_trilinear(points, normals) - -# VDBTensor is a simple wrapper of a grid and a feature tensor -vdbtensor = fvdbnn.VDBTensor(grid, vox_normals) - -# fvdb.nn.SparseConv3d is a convenient torch.nn.Module implementing the fVDB convolution -conv = fvdbnn.SparseConv3d(in_channels=3, out_channels=3, kernel_size=3, stride=1, bias=False).to(vdbtensor.device) - -output = conv(vdbtensor) -``` -Let's visualize the original grid with normals visualized as colours alongside the result of these features after a convolution initialized with random weights: -![](../imgs/fig/simple_conv.png) - -For stride values greater than 1, the output of the convolution will be a grid with a smaller resolution than the input grid (similar in topological effect to the output of a Pooling operator). Let's illustrate this: - -```python continuation -# We would expect for stride=2 that the output grid would have half the resolution (or twice the world-space size) of the input grid -conv = fvdbnn.SparseConv3d(in_channels=3, out_channels=3, kernel_size=3, stride=2, bias=False).to(vdbtensor.device) - -output = conv(vdbtensor) -``` - -![](../imgs/fig/stride_conv.png) - - -Transposed convolution can be performed with `fvdb.nn.SparseConv3d` which can increase the resolution of the grid. It only really makes sense to perform transposed sparse convolution with a target grid topology we wish to produce with this operation (see the [Pooling Operators](#maxmean-pooling) for an explanation). Therefore, an `out_grid` argument must be provided in this case to specify the target grid topology: - -```python continuation -# Tranposed convolution operator, stride=2 -transposed_conv = fvdbnn.SparseConv3d(in_channels=3, out_channels=3, kernel_size=3, stride=2, bias=False, transposed=True).to(vdbtensor.device) - -# Note the use of the `out_grid` argument to specify the target grid topology -transposed_output = transposed_conv(output, out_grid=vdbtensor.grid) -``` - -Here we visuzlie the original grid, the grid after strided convolution and the grid after transposed convolution inverts the topological operation of the strided convolution to produce the same topology as the original grid with the features convolved by our two layers: - -![](../imgs/fig/transposed_stride_conv.png) - - -### Low-level Usage with `GridBatch` - -The [high-level `fvdb.nn.SparseConv3d` class](#high-level-convolution-with-fvdbnn) wraps several pieces of `GridBatch` functionality to provide a convenient `torch.nn.Module` for convolution. However, for a more low-level approach that accomplishes the same outcome, the `GridBatch` class itself can be the starting point for performing convolution on the grid and its features. We will illustrate this approach for completeness, though we do recommend the use of the `fvdb.nn.SparseConv3d` Module for most use-cases. - -Using the `GridBatch` convolution functions directly requires a little more knowledge about the implementation under-the-hood. Due to the nature of a sparse grid, in order to make convolution performant, it is useful to pre-compute a mapping of which features in the input grid will contribute to the values of the output grid when convolved by a kernel of a particular dimension and stride. This mapping structure is called a 'kernel map'. - -The kernel map, as well as the functionality for using it to compute the convolution, is contained within a `fvdb.SparseConvPackInfo` class which can be constructed by `GridBatch.sparse_conv_kernel_map`. A `fvdb.SparseConvPackInfo` must perform a pre-computation of the kernel map based on the style expected by the backend implementation of the convolution utilized by `fvdb.SparseConvPackInfo.sparse_conv_3d`. Here is an example of how to construct a `fvdb.SparseConvPackInfo` and use it to perform a convolution: - -```python -import fvdb -import fvdb.nn as fvdbnn -from fvdb.utils.examples import load_car_1_mesh -import torch -import numpy as np -import point_cloud_utils as pcu - -num_pts = 10_000 -vox_size = 0.02 - -mesh_load_funcs = [load_car_1_mesh] - -points = [] -normals = [] - -for func in mesh_load_funcs: - pts, nms = func(mode="vn") - pmt = torch.randperm(pts.shape[0])[:num_pts] - pts, nms = pts[pmt], nms[pmt] - points.append(pts) - normals.append(nms) - -# JaggedTensors of points and normals -points = fvdb.JaggedTensor(points) -normals = fvdb.JaggedTensor(normals) - -# Create a grid -grid = fvdb.gridbatch_from_points(points, voxel_sizes=vox_size) - -# Splat the normals into the grid with trilinear interpolation -vox_normals = grid.splat_trilinear(points, normals)\ - -# Create the kernel map (housed in a SparseConvPackInfo) and the output grid's topology based on the kernel parameters -sparse_conv_packinfo, out_grid = grid.sparse_conv_kernel_map(kernel_size=3, stride=1) - -# The kernel map must be pre-computed based on the backend implementation we plan on using, here we use gather/scatter, the default implementation -sparse_conv_packinfo.build_gather_scatter() - -# Create random weights for our convolution kernel of size 3x3x3 that takes 3 input channels and produces 3 output channels -kernel_weights = torch.randn(3, 3, 3, 3, 3, device=grid.device) - -# Perform convolution on the normals colours. Gather/scatter is used as the backend, it is the default -conv_vox_normals = sparse_conv_packinfo.sparse_conv_3d(vox_normals, weights=kernel_weights, backend=fvdb.ConvPackBackend.GATHER_SCATTER) -``` -Here we visualize the output of our convolution alongside the original grid with normals visualized as colours: -![](../imgs/fig/gridbatch_conv.png) - -The kernel map can potentially be expensive to compute, so it is often useful to re-use the `SparseConvPackInfo` in the same network to perform a convolution on other features or with different weights. This optimization is something `fvdb.nn.SparseConv3d` attempts to do where appropriate and is one reason we recommend using `fvdb.nn.SparseConv3d` over this low-level approach. \ No newline at end of file diff --git a/documentation/fvdb/_sources/tutorials/building_grids.md.txt b/documentation/fvdb/_sources/tutorials/building_grids.md.txt deleted file mode 100644 index 2a19e935d..000000000 --- a/documentation/fvdb/_sources/tutorials/building_grids.md.txt +++ /dev/null @@ -1,186 +0,0 @@ -# Building Sparse Grids - -We introduce several ways to construct sparse voxel grids from various data sources including point clouds, coordinate lists, triangle meshes, and deriving from other grids. -All the examples below could be found in full version (including visualization) at `examples/grid_building.py` and `examples/grid_subdivide_coarsen.py`. - -## From Coordinate Lists - -If you already have an integer list of the `ijk` coordinates for each voxel in the grid, you could directly build grids from the list. -Additionally, you will have to specify the voxel sizes and voxel origins for the grid. -An example is as follows: - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh - -coords_1, _ = load_car_1_mesh() -coords_2, _ = load_car_2_mesh() -coords_jagged = fvdb.JaggedTensor([ - coords_1.long().cuda(), - coords_2.long().cuda() -]) -voxel_sizes = [[0.1, 0.1, 0.1], [0.15, 0.15, 0.15]] - -grid = fvdb.gridbatch_from_ijk(coords_jagged, voxel_sizes=voxel_sizes, origins=[0.0] * 3) -``` - -The above code assumes that you want to build a grid with two batch elements, one with voxel size `[0.1, 0.1, 0.1]`, and the other with voxel size `[0.15, 0.15, 0.15]` (although usually you just want all the elements in your batch to have the same size, in which case you could just pass in `voxel_sizes=[0.1, 0.1, 0.1]`). -The same logic applies for `origins` that specifies the world coordinates of voxel `(0, 0, 0)`, which we set to the origin here. -The grid will be constructed on the same device as `coords_jagged`, which is a `JaggedTensor`. The JaggedTensor is built from two numpy `int64` arrays with size `[*, 3]` called `coords_1` and `coords_2`. - -![build_from_coordinates.png](../imgs/fig/build_from_coordinates.png) - -## From Point Clouds - -You could either choose to quantize the coordinates of your point cloud into `ijk` coordinates yourself (e.g. using `np.unique((xyz / voxel_size).floor(), axis=0)`), or let `fvdb` handle this logic. Specifically, you could do: - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh - -coords_1, _ = load_car_1_mesh() -coords_2, _ = load_car_2_mesh() - -# Assemble point clouds into JaggedTensor -pcd_jagged = fvdb.JaggedTensor([ - coords_1.cuda(), - coords_2.cuda() -]) -voxel_sizes = [[0.1, 0.1, 0.1], [0.15, 0.15, 0.15]] - -# Method 1: -grid_a1 = fvdb.gridbatch_from_points(pcd_jagged, voxel_sizes=voxel_sizes, origins=[0.0] * 3) - -# Method 2: -grid_a2 = fvdb.GridBatch(device=pcd_jagged.device) -grid_a2.set_from_points(pcd_jagged, voxel_sizes=voxel_sizes, origins=[0.0] * 3) -``` - -Above we show two methods of building grids from points. Similar functions exist for other grid building approaches. The built grids are shown as following: - -![build_from_points.png](../imgs/fig/build_from_points.png) - -In some applications, you may want to build a dilated version of the grid by ensuring that all $2\times 2 \times 2$ voxels around each point are included in the built grid. That said, you could build the grid by: - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh - -coords_1, _ = load_car_1_mesh() -coords_2, _ = load_car_2_mesh() - -# Assemble point clouds into JaggedTensor -pcd_jagged = fvdb.JaggedTensor([ - coords_1.cuda(), - coords_2.cuda() -]) -voxel_sizes = [[0.1, 0.1, 0.1], [0.15, 0.15, 0.15]] - -# Build grid from containing nearest voxels to the points -grid_b = fvdb.gridbatch_from_nearest_voxels_to_points(pcd_jagged, voxel_sizes=voxel_sizes, origins=[0.0] * 3) -``` - -![build_from_points_nn.png](../imgs/fig/build_from_points_nn.png) - - -## From Meshes - -We allow building grids enclosing a triangle mesh easily. The given triangle mesh does not have to be manifold nor watertight and it will be treated as a triangle soup internally. -An example to build grids from meshes is shown as follows: - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh - -mesh_1_v, mesh_1_f = load_car_1_mesh(mode='vf') -mesh_2_v, mesh_2_f = load_car_2_mesh(mode='vf') - -mesh_v_jagged = fvdb.JaggedTensor([ - mesh_1_v.float().cuda(), - mesh_2_v.float().cuda() -]) -mesh_f_jagged = fvdb.JaggedTensor([ - mesh_1_f.long().cuda(), - mesh_2_f.long().cuda() -]) - -voxel_sizes = [[0.1, 0.1, 0.1], [0.15, 0.15, 0.15]] -grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=voxel_sizes, origins=[0.0] * 3) -``` - -Here `mesh_1_v` and `mesh_1_f` are the vertex array and triangle array of the mesh to build grid from, with the shape of $(V, 3)$ and $(F, 3)$. The triangle array is an integer array that indexes into the vertex array (starting from 0 for each element in the batch). Same for another `mesh_2_v` and `mesh_2_f`. - -![build_from_mesh.png](../imgs/fig/build_from_mesh.png) - -## From Dense - -We have APIs for you to build dense grids of shape $(D, H, W)$ containing the full $D\times H \times W$ voxels. - -```python -import fvdb - -grid = fvdb.gridbatch_from_dense(num_grids=1, dense_dims=[32, 32, 32], device="cuda") -``` - -![build_from_dense.png](../imgs/fig/build_from_dense.png) - -If you are comparing the performance of dense pytorch 3D tensors vs sparse grids, it is usually very helpful to build the exact same input (including grid and features). In `fvdb.nn`, we provide a thin wrapper class `VDBTensor` that works like a `torch.Tensor`, yet enclosing the grid topology. To convert data back and forth from dense PyTorch `Tensor`s, we could do: - -```python -import torch -import fvdb -from fvdb.nn import VDBTensor - -# Easy way to initialize a VDBTensor from a torch 3D tensor [B, D, H, W, C] -dense_data = torch.ones(2, 32, 32, 32, 16).cuda() -sparse_data = fvdb.nn.vdbtensor_from_dense(dense_data, voxel_sizes=[0.1] * 3) -dense_data_back = sparse_data.to_dense() -assert torch.all(dense_data == dense_data_back) -``` - -Here `sparse_data` will be a `fvdb.nn.VDBTensor` class, containing both `feature` and `grid` attribute. -Such a class could be fed into all the neural network components available in `fvdb.nn`. - -## Deriving from other grids - -### Dual grid - -A dual grid (of a primal grid) is also a grid, with its voxel *centers* covering the *corners* of the primal grid, and *corners* taking the positions of the *centers* of the primal grid. -A dual grid shares the same voxel sizes as its primal grid, and is just a simple shift of half the voxel size in translation. -In the picture below, green grid is the primal grid while purple grid is its dual. - -![build_dual.png](../imgs/fig/build_dual.png) - -To create a dual grid from a given primal grid, use `GridBatch.dual_grid()`: - -```python -import fvdb -from fvdb.utils.examples import load_dragon_mesh - -coords_1, _ = load_dragon_mesh() - -grid_primal = fvdb.gridbatch_from_points(fvdb.JaggedTensor([coords_1])) - -grid_dual = grid_primal.dual_grid() -``` - -### Subdividing and Coarsening a grid - -The grid could be subdivided or coarsened with a subdivision/coarsening factor provided: - -```python -import fvdb -from fvdb.utils.examples import load_happy_mesh - -coords_1, _ = load_happy_mesh(mode='vf') - -grid = fvdb.gridbatch_from_points(fvdb.JaggedTensor([coords_1])) - -grid_subdivided = grid.subdivided_grid(2) -grid_coarsened = grid.coarsened_grid(2) -``` - -![build_coarse_subdivide.png](../imgs/fig/build_coarse_subdivide.png) - -The features associated with the grids could be processed via the examples in `Grid Operations`. -Refer to the corresponding section for more details. diff --git a/documentation/fvdb/_sources/tutorials/io.md.txt b/documentation/fvdb/_sources/tutorials/io.md.txt deleted file mode 100644 index cf5f4f923..000000000 --- a/documentation/fvdb/_sources/tutorials/io.md.txt +++ /dev/null @@ -1,150 +0,0 @@ -# Sparse Grid I/O - -We give an overview of ways to save and load sparse grids including how fVDB's serialized format relates to other libraries such as OpenVDB and NanoVDB. All of the examples in this tutorial are available in the `examples/io.py` file of the fVDB repository. - -In these examples we will be using tools which are part of the `NanoVDB` project such as `nanovdb_convert`. It is assumed that the `NanoVDB` tools are available to call (i.e. findable on the system's `$PATH`). If you have not already installed these tools, you can find instructions on building NanoVDB with OpenVDB from its documentation: - -https://www.openvdb.org/documentation/doxygen/NanoVDB_HowToBuild.html - -While not necessary for fVDB's functionality, they are useful utilities to have available for inspecting and manipulating sparse grids. - -## Python Serialization - -Batches of sparse grids can be serialized to a NanoVDB file using the `fvdb.save` method. Here, we create two grids of different sizes and numbers of points and save them to a compressed NanoVDB file with specified names. The names are optional. - -```python -import torch -import fvdb -import tempfile -import os -import subprocess - -p = fvdb.JaggedTensor( - [ - torch.randn(10, 3), - torch.randn(100, 3), - ] -) -grid = fvdb.gridbatch_from_points( - p, voxel_sizes=[[0.1, 0.1, 0.1], [0.15, 0.15, 0.15]], origins=[0.0] * 3 -) - -# save the grid and features to a compressed nvdb file -path = os.path.join(tempfile.gettempdir(), "two_random_grids.nvdb") -fvdb.save(path, grid, names=["taco1", "taco2"], compressed=True) -``` - -We can use the `nanovdb_print` command line tool to show information about our saved file. Note how our grids have the `INDEX` class since they have no features and only store the voxel indices. - -```bash - -The file "/tmp/tmpwnu7qc_7/two_random_grids.nvdb" contains the following 2 grids: -# Name Type Class Version Codec Size File Scale # Voxels Resolution -1 taco1 OnIndex INDEX 32.6.0 BLOSC 1.453 MB 7.181 KB (0.1,0.1,0.1) 10 22 x 43 x 37 -2 taco2 OnIndex INDEX 32.6.0 BLOSC 2.326 MB 12.7 KB (0.15,0.15,0.15) 100 33 x 39 x 38 -``` - -We can include N-dimensional features by passing a JaggedTensor as the second argument (or `data` kwarg) to `fvdb.save`. Here, we create a grid with a single, float feature channel for our grids and save it to a `nvdb` file. - -```python continuation -# a single, scalar float feature per grid -feats = fvdb.JaggedTensor([torch.randn(x, 1) for x in grid.num_voxels]) - -# save the grid and features to a compressed nvdb file -path = os.path.join(tempfile.gettempdir(), "two_random_grids.nvdb") -fvdb.save(path, grid, feats, names=["taco1", "taco2"], compressed=True) -``` - -Again, we can use the `nanovdb_print` command line tool to show information about our saved file. - -```bash -The file "/tmp/tmpg9ol5841/two_random_grids.nvdb" contains the following 2 grids: -# Name Type Class Version Codec Size File Scale # Voxels Resolution -1 taco1 float ? 32.6.0 BLOSC 1.47 MB 7.959 KB (1,1,1) 10 26 x 30 x 36 -2 taco2 float ? 32.6.0 BLOSC 2.411 MB 16.48 KB (1,1,1) 100 32 x 33 x 34 -``` - -Note how our serialized NanoVDB grids are now of type `float`. fVDB will automatically map N-dimensional features to appropriate NanoVDB types. For feature sizes that don't naturally map to any NanoVDB data types, fVDB will save the feature data as NanoVDB blind-data which will be appropriately read back as N-dimensional features by fVDB. - -Let's try to save the same two grids with a `Vec3d` type by creating a JaggedTensor of 3-dimensional double-precision features. - -```python continuation -# a 3-vector double feature per grid -feats = fvdb.JaggedTensor([torch.randn(x, 3, dtype=torch.float64) for x in grid.num_voxels]) - -# save the grid and features to a compressed nvdb file -saved_nvdb = os.path.join(tempfile.gettempdir(), "two_random_vec3d_grids.nvdb") -fvdb.save(saved_nvdb, grid, feats, names=["taco1", "taco2"], compressed=True) -``` - -```bash -The file "/tmp/tmpwnu7qc_7/two_random_grids.nvdb" contains the following 2 grids: -# Name Type Class Version Codec Size File Scale # Voxels Resolution -1 taco1 Vec3d ? 32.6.0 BLOSC 6.077 MB 28.18 KB (1,1,1) 10 23 x 36 x 35 -2 taco2 Vec3d ? 32.6.0 BLOSC 7.346 MB 41.37 KB (1,1,1) 100 37 x 40 x 34 -``` - -## Loading NanoVDB Files - -Loading NanoVDB files is as simple as calling `fvdb.load`. You can optionally supply a PyTorch device you'd like the grids and features loaded onto. Here, we load the two grids we saved in the previous section onto our GPU. - -```python continuation -# Load the grid and features from the compressed nvdb file -grid_batch, features, names = fvdb.load(saved_nvdb, device=torch.device("cuda:0")) -print("Loaded grid batch total number of voxels: ", grid_batch.total_voxels) -print("Loaded grid batch data type: %s, device: %s" % (features.dtype, features.device)) -``` - -```bash -Loaded grid batch total number of voxels: 110 -Loaded grid batch data type: torch.float64, device: cuda:0 -``` - -## Saving/Loading OpenVDB Files - -While saving and loading from OpenVDB files is not directly supported by fVDB, it is possible to easily convert between NanoVDB and OpenVDB files using the `nanovdb_convert` command line tool. Here, we convert our previously saved NanoVDB file to an OpenVDB file. - -```python notest -vdb_path = os.path.join(tempfile.gettempdir(), "two_random_grids.vdb") -convert_cmd = "nanovdb_convert -v %s %s" % (saved_nvdb, vdb_path) -print("nanovdb_convert our nvdb to vdb: ", convert_cmd) -print(subprocess.check_output(convert_cmd.split()).decode("utf-8")) -``` - -```bash -nanovdb_convert our nvdb to vdb: nanovdb_convert -v /tmp/tmpnr7gk0tf/two_random_vec3d_grids.nvdb /tmp/tmpnr7gk0tf/two_random_grids.vdb -Opening NanoVDB file named "/tmp/tmpnr7gk0tf/two_random_vec3d_grids.nvdb" -Read 1 NanoGrid(s) from the file named "/tmp/tmpnr7gk0tf/two_random_vec3d_grids.nvdb" -Converting NanoVDB grid named "taco1" to OpenVDB -Converting NanoVDB grid named "taco2" to OpenVDB -``` - -From here, our grid can be loaded by OpenVDB tools. Roundtripping our converted cache back to NanoVDB is possible with the `nanovdb_convert` tool as well. - -Loading the converted OpenVDB file into fVDB shows our familiar grids and features as we expect: - - -```python notest -convert_cmd = "nanovdb_convert -v -f %s %s" % ( # -f flag forces overwriting existing file - vdb_path, - saved_nvdb, -) -print("nanovdb_convert roundtrip the vdb to nvdb: ", convert_cmd) -print(subprocess.check_output(convert_cmd.split()).decode("utf-8")) - -# Load the nvdb file of the converted vdb -grid_batch, features, names = fvdb.load(saved_nvdb, device=torch.device("cuda:0")) -print("Loaded grid batch total number of voxels: ", grid_batch.total_voxels) -print("Loaded grid batch data type: %s, device: %s" % (features.dtype, features.device)) -print("\n") -``` - -```bash -nanovdb_convert roundtrip the vdb to nvdb: nanovdb_convert -v -f /tmp/tmpuwq2r5mx/two_random_grids.vdb /tmp/tmpuwq2r5mx/two_random_vec3d_grids.nvdb -Opening OpenVDB file named "/tmp/tmpuwq2r5mx/two_random_grids.vdb" -Converting OpenVDB grid named "taco1" to NanoVDB -Converting OpenVDB grid named "taco2" to NanoVDB - -Loaded grid batch total number of voxels: 110 -Loaded grid batch data type: torch.float64, device: cuda:0 -``` \ No newline at end of file diff --git a/documentation/fvdb/_sources/tutorials/mutable_grids.md.txt b/documentation/fvdb/_sources/tutorials/mutable_grids.md.txt deleted file mode 100644 index ae006b6ac..000000000 --- a/documentation/fvdb/_sources/tutorials/mutable_grids.md.txt +++ /dev/null @@ -1,179 +0,0 @@ -# Mutable Grids - -## Concepts - -Mutable grids refer to `GridBatch` whose voxels can be turned 'off'. -Each voxel hence not only stores an integer offset that indexes into the external feature array, but also includes a bit switch indicating whether the voxel exist or not. - -![mutable_grid.png](../imgs/fig/mutable_grid.png) - -The ability to turn on (enable) voxels and turn off (disable) voxels make it easier for downstream tasks such as structural optimization and neural rendering. -Note that even if you disable some voxels, the corresponding entry in the feature array still exists. -Such a design keeps the feature unchanged while changing the grid topology in a flexible way. - -## Examples - -### Basic example - -Mutable grids can be created by adding `mutable=True` arguments into the grid building function. -For example: - -```python -import fvdb -from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh - -v1, f1 = load_car_1_mesh(mode = "vf") -v2, f2 = load_car_2_mesh(mode = "vf") -mesh_v_jagged = fvdb.JaggedTensor([v1, v2]) -mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).int() - -# Create mutable grid -grid = fvdb.gridbatch_from_mesh( - mesh_v_jagged, mesh_f_jagged, - voxel_sizes=[0.01] * 3, origins=[0.0] * 3, - mutable=True -) - -# Create additional features for visualization purpose -feature = grid.grid_to_world(grid.ijk.float()) -feature.jdata = (feature.jdata - feature.jdata.min(dim=0).values) / \ - (feature.jdata.max(dim=0).values - feature.jdata.min(dim=0).values) -``` - -![mg_origin.png](../imgs/fig/mg_origin.png) - -Voxels can be disabled in batches via `disable_ijk`: - -```python continuation -# Get the IJK coordinates to be disabled -disable_ijk: fvdb.JaggedTensor = grid.ijk.rmask(feature.jdata[:, 0] > 0.5) - -# Disable them! -grid.disable_ijk(disable_ijk) -``` - -Once disabled, those voxels will virtually disappear, meaning that all subsequent grid operations such as sampling, splatting, or ray marching, will treat those voxels as they do not exist. -One can visualize the enable mask via: - -```python continuation -enabled_mask = grid.enabled_mask -``` - -![mg_mask.png](../imgs/fig/mg_mask.png) - -Note that in the above figure, white voxels are those still enabled, while black voxels are disabled voxels. -To verify, we try to sample features from the grid to a set of sampled points: - -```python continuation -pts_feature = grid.sample_trilinear(mesh_v_jagged, feature) -``` - -![mg_pts_after.png](../imgs/fig/mg_pts_after.png) - -Because the disabled voxels will be treated as non-existing, no voxels will contribute to the features on the points at the front. Hence those points are marked as black (i.e. feature = 0). - -The disabled voxels could be revived at any time using `enable_ijk`: - -```python continuation -grid.enable_ijk(disable_ijk) -``` - -Conducting the same feature sampling with `sample_trilinear`, one can get the full features being correctly sampled. - -![mg_pts_before.png](../imgs/fig/mg_pts_before.png) - -### Structure optimization - -In this example, we will cover a more advanced topic to perform structure optimization from images. -Suppose we have the following observations of a single object's mask: - -![struct_mask.png](../imgs/fig/struct_mask.png) - -The task is to recover the underlying 3D shape from the images. Here we use the underlying representation of our VDB grid. -Such a problem could be solved in many different ways, with one obvious one being space culling. -However, to demonstrate the wide applicability and flexibility of fVDB, we will take the differentiable rendering approach here. -Each voxel is hereby given an opacity value, and the entire sparse grid is volume rendered into a predicted mask. -A simple L1 loss is compared between the given ground-truth mask and the predicted mask to force them align. - -To begin, we create a mutable grid and the corresponding opacity (`alpha`) by: -```python -import fvdb -import torch -import math - -init_resolution = 96 - -# Suppose our shape lies within the unit bounding box from [-0.5, 0.5, 0.5] to [0.5, 0.5, 0.5] -grid = fvdb.gridbatch_from_dense( - num_grids=1, dense_dims=[init_resolution] * 3, - voxel_sizes=[1.0 / (init_resolution - 1)] * 3, origins=[-0.5, -0.5, -0.5], - device="cuda", - mutable=True -) - -def inv_sigmoid(x) -> float: - return -math.log(1 / x - 1) -alpha = torch.full((grid.total_voxels, ), inv_sigmoid(0.1), device=grid.device, requires_grad=True) -``` - -The structure optimization is done via `torch`'s Adam optimizer, with the loop being: - -```python notest -optimizer = torch.optim.Adam([alpha], lr=1.0) - -# Optimization loop -for it in range(100): - # Subsample rays from the given camera poses. - sub_inds = torch.randint(0, ray_orig.shape[0], (10000, ), device=grid.device) - pd_opacity = render_opacity( - grid, torch.sigmoid(alpha), - ray_orig=ray_orig[sub_inds], ray_dir=ray_dir[sub_inds] - ) - gt_opacity = ray_opacity[sub_inds] - - # Compute L1 loss - loss = torch.mean(torch.abs(pd_opacity - gt_opacity)) - - optimizer.zero_grad() - loss.backward() - optimizer.step() -``` - -Here `render_opacity` is an approximate differentiable rendering algorithm like: - -```python notest -pack_info, voxel_inds, out_times = grid.voxels_along_rays(ray_orig, ray_dir, 128, 0.0) -voxel_inds = grid.ijk_to_index(voxel_inds).jdata - -rgb, depth, opacity, _, _ = fvdb.utils.volume_render( - sigmas=-torch.log(1 - feature[voxel_inds]), - rgbs=torch.ones((voxel_inds.shape[0], 1), device=grid.device), - deltaTs=torch.ones(voxel_inds.shape[0], device=grid.device), - ts=out_times.jdata.mean(1), - packInfo=pack_info.jdata, transmittanceThresh=0.0 -) -``` - -During the optimization, the voxels of the grid could be disabled or enabled freely. -In this example, we demonstrate the following strategy similar to Instant-NGP. - -```python notest -if it > 0 and it % 5 == 0: - # Disable voxels that are transparent - bad_mask = torch.sigmoid(alpha) < 0.1 - grid.disable_ijk(grid.ijk.rmask(bad_mask)) - - # Randomly revive voxels at the beginning. - if it < 20: - enable_mask = torch.rand(grid.total_voxels, device=grid.device) < 0.01 - grid.enable_ijk(grid.ijk.rmask(enable_mask)) -``` - -Note that a way simpler strategy that only turns voxels off at a much sparse internal also works in this very simplified scenario. -The snippet above is solely for demonstration purpose of our API. - -The optimization procedure looks as follows. One can see that we are able to recover the ground-truth voxel structure of the provided car. - -![struct_optim.png](../imgs/fig/struct_optim.png) - -A full runnable example could be found at `examples/structure_optimization.py`. diff --git a/documentation/fvdb/_sources/tutorials/simple_unet.md.txt b/documentation/fvdb/_sources/tutorials/simple_unet.md.txt deleted file mode 100644 index c146cb3db..000000000 --- a/documentation/fvdb/_sources/tutorials/simple_unet.md.txt +++ /dev/null @@ -1,272 +0,0 @@ -# A Simple Convolutional U-Net - -In this tutorial, you will be guided on how to build a simple sparse convolutional neural network using fVDB. -If you were using MinkowskiEngine to tackle sparse 3D data previously, we will also guide you step-by-step to help you smoothly transfer from it and enjoy speed-ups and memory-savings. - -In our simplistic U-Net case, we want to build a Res-UNet with four layers, and each layer contains several blocks. -First, we import basic `fvdb` libraries: - -```python -import fvdb -import fvdb.nn as fvnn -from fvdb.nn import VDBTensor -import torch -``` - -Here `fvdb.nn` is a namespace similar to `torch.nn`, containing a broad definition of different neural layers. -`VDBTensor` is a very thin wrapper around a grid (with type `GridBatch`) and its corresponding feature (with type `JaggedTensor`), and internally makes sure that the two members align. -It also overloads a bunch of operators such as arithmetic computations. Please refer to our API docs to learn more. - -We could then build a basic block as follows: - -```python continuation -class BasicBlock(torch.nn.Module): - expansion = 1 - - def __init__(self, in_channels: int, out_channels: int, downsample=None, bn_momentum: float = 0.1): - super().__init__() - self.conv1 = fvnn.SparseConv3d(in_channels, out_channels, kernel_size=3, stride=1) - self.norm1 = fvnn.BatchNorm(out_channels, momentum=bn_momentum) - self.conv2 = fvnn.SparseConv3d(out_channels, out_channels, kernel_size=3, stride=1) - self.norm2 = fvnn.BatchNorm(out_channels, momentum=bn_momentum) - self.relu = fvnn.ReLU(inplace=True) - self.downsample = downsample - - def forward(self, x: VDBTensor): - residual = x - - out = self.conv1(x) - out = self.norm1(out) - out = self.relu(out) - - out = self.conv2(out) - out = self.norm2(out) - - if self.downsample is not None: - residual = self.downsample(x) - - out += residual - out = self.relu(out) - - return out -``` - -This defines a similar block as `MinkowskiEngine`: - -```python notest -import MinkowskiEngine as ME - - -class BasicBlock(torch.nn.Module): - expansion = 1 - - def __init__(self, in_channels: int, out_channels: int, downsample=None, bn_momentum: float = 0.1): - super().__init__() - self.conv1 = ME.MinkowskiConvolution( - in_channels, out_channels, kernel_size=3, stride=1, dilation=1, dimension=3) - self.norm1 = ME.MinkowskiBatchNorm(out_channels, momentum=bn_momentum) - self.conv2 = ME.MinkowskiConvolution( - out_channels, out_channels, kernel_size=3, stride=1, dilation=1, dimension=3) - self.norm2 = ME.MinkowskiBatchNorm(out_channels, momentum=bn_momentum) - self.relu = ME.MinkowskiReLU(inplace=True) - self.downsample = downsample - - def forward(self, x): - residual = x - - out = self.conv1(x) - out = self.norm1(out) - out = self.relu(out) - - out = self.conv2(out) - out = self.norm2(out) - - if self.downsample is not None: - residual = self.downsample(x) - - out += residual - out = self.relu(out) - - return out -``` - -All the network layers are fully compatible with `torch.nn`. The only difference is that they take `VDBTensor` as input and return a `VDBTensor`. -A full network definition could then be built as: - -```python continuation -class FVDBUNetBase(torch.nn.Module): - LAYERS = (2, 2, 2, 2, 2, 2, 2, 2) - CHANNELS = (32, 64, 128, 256, 256, 128, 96, 96) - INIT_DIM = 32 - OUT_TENSOR_STRIDE = 1 - - def __init__(self, in_channels, out_channels, D=3): - super().__init__() - - # Output of the first conv concated to conv6 - self.inplanes = self.INIT_DIM - self.conv0p1s1 = fvnn.SparseConv3d(in_channels, self.inplanes, kernel_size=5, stride=1, bias=False) - self.bn0 = fvnn.BatchNorm(self.inplanes) - - self.conv1p1s2 = fvnn.SparseConv3d(self.inplanes, self.inplanes, kernel_size=2, stride=2, bias=False) - self.bn1 = fvnn.BatchNorm(self.inplanes) - - self.block1 = self._make_layer(BasicBlock, self.CHANNELS[0], self.LAYERS[0]) - - self.conv2p2s2 = fvnn.SparseConv3d( - self.inplanes, self.inplanes, kernel_size=2, stride=2, bias=False) - self.bn2 = fvnn.BatchNorm(self.inplanes) - - self.block2 = self._make_layer(BasicBlock, self.CHANNELS[1], self.LAYERS[1]) - - self.conv3p4s2 = fvnn.SparseConv3d( - self.inplanes, self.inplanes, kernel_size=2, stride=2, bias=False) - - self.bn3 = fvnn.BatchNorm(self.inplanes) - self.block3 = self._make_layer(BasicBlock, self.CHANNELS[2], self.LAYERS[2]) - - self.conv4p8s2 = fvnn.SparseConv3d( - self.inplanes, self.inplanes, kernel_size=2, stride=2, bias=False) - self.bn4 = fvnn.BatchNorm(self.inplanes) - self.block4 = self._make_layer(BasicBlock, self.CHANNELS[3], self.LAYERS[3]) - - self.convtr4p16s2 = fvnn.SparseConv3d( - self.inplanes, self.CHANNELS[4], kernel_size=2, stride=2, transposed=True, bias=False) - self.bntr4 = fvnn.BatchNorm(self.CHANNELS[4]) - - self.inplanes = self.CHANNELS[4] + self.CHANNELS[2] - self.block5 = self._make_layer(BasicBlock, self.CHANNELS[4], self.LAYERS[4]) - self.convtr5p8s2 = fvnn.SparseConv3d( - self.inplanes, self.CHANNELS[5], kernel_size=2, stride=2, transposed=True, bias=False) - self.bntr5 = fvnn.BatchNorm(self.CHANNELS[5]) - - self.inplanes = self.CHANNELS[5] + self.CHANNELS[1] - self.block6 = self._make_layer(BasicBlock, self.CHANNELS[5], self.LAYERS[5]) - self.convtr6p4s2 = fvnn.SparseConv3d( - self.inplanes, self.CHANNELS[6], kernel_size=2, stride=2, transposed=True, bias=False) - self.bntr6 = fvnn.BatchNorm(self.CHANNELS[6]) - - self.inplanes = self.CHANNELS[6] + self.CHANNELS[0] - self.block7 = self._make_layer(BasicBlock, self.CHANNELS[6], self.LAYERS[6]) - self.convtr7p2s2 = fvnn.SparseConv3d( - self.inplanes, self.CHANNELS[7], kernel_size=2, stride=2, transposed=True, bias=False) - self.bntr7 = fvnn.BatchNorm(self.CHANNELS[7]) - - self.inplanes = self.CHANNELS[7] + self.INIT_DIM - self.block8 = self._make_layer(BasicBlock, self.CHANNELS[7], self.LAYERS[7]) - - self.final = fvnn.SparseConv3d(self.CHANNELS[7], out_channels, kernel_size=1) - self.relu = fvnn.ReLU(inplace=True) - - def _make_layer(self, block, planes, blocks): - downsample = None - if self.inplanes != planes * block.expansion: - downsample = torch.nn.Sequential( - fvnn.SparseConv3d( - self.inplanes, - planes * block.expansion, - kernel_size=1, - stride=1 - ), - fvnn.BatchNorm(planes * block.expansion), - ) - layers = [] - layers.append( - BasicBlock( - self.inplanes, planes, - downsample=downsample - ) - ) - self.inplanes = planes * block.expansion - for _ in range(1, blocks): - layers.append(BasicBlock(self.inplanes, planes)) - - return torch.nn.Sequential(*layers) - - def forward(self, x): - out = self.conv0p1s1(x) - out = self.bn0(out) - out_p1 = self.relu(out) - grid1 = out_p1.grid - - out = self.conv1p1s2(out_p1) - out = self.bn1(out) - out = self.relu(out) - out_b1p2 = self.block1(out) - grid2 = out_b1p2.grid - - out = self.conv2p2s2(out_b1p2) - out = self.bn2(out) - out = self.relu(out) - out_b2p4 = self.block2(out) - grid4 = out_b2p4.grid - - out = self.conv3p4s2(out_b2p4) - out = self.bn3(out) - out = self.relu(out) - out_b3p8 = self.block3(out) - grid8 = out_b3p8.grid - - # tensor_stride=16 - out = self.conv4p8s2(out_b3p8) - out = self.bn4(out) - out = self.relu(out) - out = self.block4(out) - - # tensor_stride=8 - out = self.convtr4p16s2(out, out_grid=grid8) - out = self.bntr4(out) - out = self.relu(out) - - out = fvdb.jcat([out, out_b3p8], dim=1) - out = self.block5(out) - - # tensor_stride=4 - out = self.convtr5p8s2(out, out_grid=grid4) - out = self.bntr5(out) - out = self.relu(out) - - out = fvdb.jcat([out, out_b2p4], dim=1) - out = self.block6(out) - - # tensor_stride=2 - out = self.convtr6p4s2(out, out_grid=grid2) - out = self.bntr6(out) - out = self.relu(out) - - out = fvdb.jcat([out, out_b1p2], dim=1) - out = self.block7(out) - - # tensor_stride=1 - out = self.convtr7p2s2(out, out_grid=grid1) - out = self.bntr7(out) - out = self.relu(out) - - out = fvdb.jcat([out, out_p1], dim=1) - out = self.block8(out) - - return self.final(out) -``` - -Please note that here, when we apply transposed convolution layers, we additionally introduce the `out_grid` keyword arguments. -This is needed to guide the output domain of the network, because for perception networks, the output grid topology should align with the input topology. -Note that fVDB will NOT cache the grids to maintain maximum flexibility. - -To perform inference with the network, you could simply create a VDBTensor and feed it into the model: - -```python continuation -coords = fvdb.JaggedTensor([ - (torch.randn(10_000, 3, device='cuda')), - (torch.randn(11_000, 3, device='cuda')), -]) - -grid = fvdb.gridbatch_from_points(coords) -features = grid.jagged_like(torch.randn(grid.total_voxels, 32, device='cuda')) -sinput = fvnn.VDBTensor(grid, features) - -model = FVDBUNetBase(32, 1).to('cuda') -soutput = model(sinput) -``` - -The output `soutput` will carry gradients during training, and you could train the sparse network accordingly. -Please find a fully working example at `examples/perception_example.py`. The same network is implemented using `MinkowskiEngine` for reference. diff --git a/documentation/fvdb/_sources/tutorials/volume_rendering.md.txt b/documentation/fvdb/_sources/tutorials/volume_rendering.md.txt deleted file mode 100644 index 60bc69670..000000000 --- a/documentation/fvdb/_sources/tutorials/volume_rendering.md.txt +++ /dev/null @@ -1,641 +0,0 @@ -# Volume Rendering - -In this example we replace `nerfacc`'s acceleration structure with fVDB and hence scale to unbounded scenes: - -```python -import json -import math -import os -from typing import Optional, Tuple, Union - -import imageio.v2 as imageio -import matplotlib.pyplot as plt -import numpy as np -import polyscope as ps -import torch -import tqdm - -from fvdb import GridBatch -from fvdb import volume_render - -TensorPair = Tuple[torch.Tensor, torch.Tensor] -TensorTriple = Tuple[torch.Tensor, torch.Tensor, torch.Tensor] - -class _TruncExp(torch.autograd.Function): - @staticmethod - def forward(ctx, x): - ctx.save_for_backward(x) - return torch.exp(x) - - @staticmethod - def backward(ctx, dL_dout): - x = ctx.saved_tensors[0] - return dL_dout * torch.exp(x.clamp(-15, 15)) - -# SH -MAX_SH_BASIS = 10 -def eval_sh_bases(basis_dim : int, dirs : torch.Tensor): - """ - Evaluate spherical harmonics bases at unit directions, - without taking linear combination. - At each point, the final result may the be - obtained through simple multiplication. - - :param basis_dim: int SH basis dim. Currently, 1-25 square numbers supported - :param dirs: torch.Tensor (..., 3) unit directions - - :return: torch.Tensor (..., basis_dim) - """ - SH_C0 = 0.28209479177387814 - SH_C1 = 0.4886025119029199 - SH_C2 = [ - 1.0925484305920792, - -1.0925484305920792, - 0.31539156525252005, - -1.0925484305920792, - 0.5462742152960396 - ] - SH_C3 = [ - -0.5900435899266435, - 2.890611442640554, - -0.4570457994644658, - 0.3731763325901154, - -0.4570457994644658, - 1.445305721320277, - -0.5900435899266435 - ] - SH_C4 = [ - 2.5033429417967046, - -1.7701307697799304, - 0.9461746957575601, - -0.6690465435572892, - 0.10578554691520431, - -0.6690465435572892, - 0.47308734787878004, - -1.7701307697799304, - 0.6258357354491761, - ] - result = torch.empty((*dirs.shape[:-1], basis_dim), dtype=dirs.dtype, device=dirs.device) - result[..., 0] = SH_C0 - if basis_dim > 1: - x, y, z = dirs.unbind(-1) - result[..., 1] = -SH_C1 * y; - result[..., 2] = SH_C1 * z; - result[..., 3] = -SH_C1 * x; - if basis_dim > 4: - xx, yy, zz = x * x, y * y, z * z - xy, yz, xz = x * y, y * z, x * z - result[..., 4] = SH_C2[0] * xy; - result[..., 5] = SH_C2[1] * yz; - result[..., 6] = SH_C2[2] * (2.0 * zz - xx - yy); - result[..., 7] = SH_C2[3] * xz; - result[..., 8] = SH_C2[4] * (xx - yy); - - if basis_dim > 9: - result[..., 9] = SH_C3[0] * y * (3 * xx - yy); - result[..., 10] = SH_C3[1] * xy * z; - result[..., 11] = SH_C3[2] * y * (4 * zz - xx - yy); - result[..., 12] = SH_C3[3] * z * (2 * zz - 3 * xx - 3 * yy); - result[..., 13] = SH_C3[4] * x * (4 * zz - xx - yy); - result[..., 14] = SH_C3[5] * z * (xx - yy); - result[..., 15] = SH_C3[6] * x * (xx - 3 * yy); - - if basis_dim > 16: - result[..., 16] = SH_C4[0] * xy * (xx - yy); - result[..., 17] = SH_C4[1] * yz * (3 * xx - yy); - result[..., 18] = SH_C4[2] * xy * (7 * zz - 1); - result[..., 19] = SH_C4[3] * yz * (7 * zz - 3); - result[..., 20] = SH_C4[4] * (zz * (35 * zz - 30) + 3); - result[..., 21] = SH_C4[5] * xz * (7 * zz - 3); - result[..., 22] = SH_C4[6] * (xx - yy) * (7 * zz - 1); - result[..., 23] = SH_C4[7] * xz * (xx - 3 * yy); - result[..., 24] = SH_C4[8] * (xx * (xx - 3 * yy) - yy * (3 * xx - yy)); - return result - -def speherical_harmonics(deg: int, sh: torch.Tensor, dirs: torch.Tensor): - C0 = 0.28209479177387814 - C1 = 0.4886025119029199 - C2 = [ - 1.0925484305920792, - -1.0925484305920792, - 0.31539156525252005, - -1.0925484305920792, - 0.5462742152960396 - ] - C3 = [ - -0.5900435899266435, - 2.890611442640554, - -0.4570457994644658, - 0.3731763325901154, - -0.4570457994644658, - 1.445305721320277, - -0.5900435899266435 - ] - C4 = [ - 2.5033429417967046, - -1.7701307697799304, - 0.9461746957575601, - -0.6690465435572892, - 0.10578554691520431, - -0.6690465435572892, - 0.47308734787878004, - -1.7701307697799304, - 0.6258357354491761, - ] - - # sh is a tensor of shape [N, C, (deg+1)**2] - # dirs is a tensor of shape [N, 3] - assert 0 <= deg <= 4 - assert (deg + 1) ** 2 == sh.shape[-1] - # C = sh.shape[-2] - - result = C0 * sh[..., 0] - if deg > 0: - x, y, z = dirs[..., 0:1], dirs[..., 1:2], dirs[..., 2:3] - result = (result - - C1 * y * sh[..., 1] + - C1 * z * sh[..., 2] - - C1 * x * sh[..., 3]) - if deg > 1: - xx, yy, zz = x * x, y * y, z * z - xy, yz, xz = x * y, y * z, x * z - result = (result + - C2[0] * xy * sh[..., 4] + - C2[1] * yz * sh[..., 5] + - C2[2] * (2.0 * zz - xx - yy) * sh[..., 6] + - C2[3] * xz * sh[..., 7] + - C2[4] * (xx - yy) * sh[..., 8]) - - if deg > 2: - result = (result + - C3[0] * y * (3 * xx - yy) * sh[..., 9] + - C3[1] * xy * z * sh[..., 10] + - C3[2] * y * (4 * zz - xx - yy)* sh[..., 11] + - C3[3] * z * (2 * zz - 3 * xx - 3 * yy) * sh[..., 12] + - C3[4] * x * (4 * zz - xx - yy) * sh[..., 13] + - C3[5] * z * (xx - yy) * sh[..., 14] + - C3[6] * x * (xx - 3 * yy) * sh[..., 15]) - if deg > 3: - result = (result + C4[0] * xy * (xx - yy) * sh[..., 16] + - C4[1] * yz * (3 * xx - yy) * sh[..., 17] + - C4[2] * xy * (7 * zz - 1) * sh[..., 18] + - C4[3] * yz * (7 * zz - 3) * sh[..., 19] + - C4[4] * (zz * (35 * zz - 30) + 3) * sh[..., 20] + - C4[5] * xz * (7 * zz - 3) * sh[..., 21] + - C4[6] * (xx - yy) * (7 * zz - 1) * sh[..., 22] + - C4[7] * xz * (xx - 3 * yy) * sh[..., 23] + - C4[8] * (xx * (xx - 3 * yy) - yy * (3 * xx - yy)) * sh[..., 24]) - return result - - - -def compute_psnr(rgb_gt: torch.Tensor, rgb_est: torch.Tensor) -> torch.Tensor: - x = torch.mean((rgb_gt - rgb_est)**2) - return -10. * torch.log10(x) - - -def nerf_matrix_to_ngp(pose: np.ndarray, scale: float = 0.33, offset: Union[tuple, list, torch.Tensor] = (0, 0, 0)) -> np.ndarray: - new_pose = np.array([ - [pose[1, 0], -pose[1, 1], -pose[1, 2], pose[1, 3] * scale + offset[0]], - [pose[2, 0], -pose[2, 1], -pose[2, 2], pose[2, 3] * scale + offset[1]], - [pose[0, 0], -pose[0, 1], -pose[0, 2], pose[0, 3] * scale + offset[2]], - [0, 0, 0, 1],], dtype=np.float32) - - return new_pose - - -def get_rays(pose: torch.Tensor, intrinsic: torch.Tensor, H: int, W: int, depth: Optional[torch.Tensor] = None) -> TensorTriple: - fx, fy, cx, cy = intrinsic - - i, j = torch.meshgrid(torch.linspace(0, W-1, W, device='cpu'), torch.linspace(0, H-1, H, device='cpu'), indexing='ij') - i = i.t().reshape([1, H*W]).expand([1, H*W]) + 0.5 - j = j.t().reshape([1, H*W]).expand([1, H*W]) + 0.5 - zs = torch.ones_like(i) - xs = (i - cx) / fx * zs - ys = (j - cy) / fy * zs - directions = torch.cat((xs.reshape(-1,1), ys.reshape(-1,1), zs.reshape(-1,1)), dim=-1) - - # compute distances - if depth is not None: - dist = torch.norm(directions * depth[:,None], dim=-1, keepdim=True) - else: - dist = torch.empty([]) - - directions = directions / torch.norm(directions, dim=-1, keepdim=True) - rays_d = (pose[:3,:3] @ directions.transpose(0,1)).transpose(0,1) - - rays_o = pose[:3, 3] # [3] - rays_o = rays_o[None, :].expand_as(rays_d) # [N, 3] - - return rays_o.squeeze(), rays_d.squeeze(), dist.squeeze() - - -class NeRFDataset: - def __init__(self, root_path: str = 'data/lego/', scale: float = 1.0, num_rays: int = 4096, mode: str = 'train'): - super().__init__() - - self.root_path = root_path - self.scale = scale - self.num_rays = num_rays - self.mode = mode - - with open(os.path.join(self.root_path, f'transforms_{self.mode}.json'), 'r', encoding='utf-8') as f: - transform = json.load(f) - - # read images - frames = transform["frames"] - self.n_frames = len(frames) - - # Read the intrinsics - image = imageio.imread(os.path.join(self.root_path, frames[0]['file_path'] + '.png')) # [H, W, 3] o [H, W, 4] - self.H, self.W = image.shape[:2] - fl_x = fl_y = self.W / (2 * np.tan(transform['camera_angle_x'] / 2)) if 'camera_angle_x' in transform else None - cx = (transform['cx']) if 'cx' in transform else (self.W / 2) - cy = (transform['cy']) if 'cy' in transform else (self.H / 2) - self.intrinsics = np.array([fl_x, fl_y, cx, cy]) - - self.rays = [] - self.rgbs = [] - self.depths = [] - self.poses = [] - self.pc = [] - self.pc_rgbs = [] - - for f in tqdm.tqdm(frames, desc=f'Loading {self.mode} data'): - f_path = os.path.join(self.root_path, f['file_path'] + '.png') - pose = nerf_matrix_to_ngp(np.array(f['transform_matrix'], dtype=np.float32), scale=self.scale) - image = imageio.imread(f_path) / 255.0 # [H, W, 3] o [H, W, 4] - depth = None - - if self.mode == 'train': - f_path_depth = os.path.join(self.root_path, f['file_path'] + '_depth.npy') - depth = np.load(f_path_depth).reshape(-1) - - ray_o, ray_d, depth = get_rays(torch.from_numpy(pose), torch.from_numpy(self.intrinsics), self.H, self.W, depth) - - # Scale the depth - depth_mask = depth < 1000 - depth *= scale - - rgbs = torch.from_numpy(image).reshape(self.H * self.W, -1) - self.poses.append(pose) - self.rays.append(torch.cat([ray_o, ray_d], 1)) - self.rgbs.append(rgbs) - - if self.mode == 'train': - self.depths.append(depth) - self.pc.append(ray_o[depth_mask, :3] + ray_d[depth_mask, :3] * depth[depth_mask,None]) - self.pc_rgbs.append(rgbs[depth_mask,:3]) - - self.rays = torch.vstack(self.rays) - self.rgbs = torch.vstack(self.rgbs) - - if self.mode == 'train': - self.depths = torch.cat(self.depths) # Note that depth denotes the distance along the ray - self.pc = torch.vstack(self.pc) - self.pc_rgbs = torch.vstack(self.pc_rgbs) - - def get_point_cloud(self, downsample_ratio: float = 1.0, return_color: bool = False) -> Union[torch.Tensor, TensorPair]: - if self.mode == 'train': - assert isinstance(self.pc, torch.Tensor) - if return_color: - assert isinstance(self.pc_rgbs, torch.Tensor) - dri = int(1 / downsample_ratio) - pts = self.pc[::dri, :] - rgb = self.pc_rgbs[::dri, :] - return pts, rgb - return self.pc[::int(1/downsample_ratio),:] - else: - raise ValueError('Only training data has depth information!') - - def __len__(self): - if self.mode == 'train': - return 1000 - else: - return self.n_frames - - - def __getitem__(self, idx): - # raise an error to now iterate in infinity - if idx >= len(self): raise IndexError - - if self.mode == 'train': - assert isinstance(self.rays, torch.Tensor) - idxs = np.random.choice(self.rays.shape[0], self.num_rays) - return {'rays_o': self.rays[idxs,:3], - 'rays_d': self.rays[idxs,3:6], - 'rgba': self.rgbs[idxs], - 'depth': self.depths[idxs], - 'idxs': idxs} - - else: - # raise an error to now iterate in infinity - if idx >= len(self): raise IndexError - assert isinstance(self.rays, torch.Tensor) - assert isinstance(self.rgbs, torch.Tensor) - return {'rays_o': self.rays[idx * self.W * self.H : (idx + 1) * self.W * self.H, :3], - 'rays_d': self.rays[idx * self.W * self.H : (idx + 1) * self.W * self.H, 3:6], - 'rgba': self.rgbs[idx * self.W * self.H : (idx + 1) * self.W * self.H,], - 'depth': None} - - -def make_ray_grid(origin, nrays, minb=(-0.45, -0.45), maxb=(0.45, 0.45), - device: Union[str, torch.device]='cpu', dtype=torch.float32): - - ray_o = torch.tensor([origin] * nrays**2) #+ p.mean(0, keepdim=True) - ray_d = torch.from_numpy( - np.stack([a.ravel() for a in - np.mgrid[minb[0]:maxb[0]:nrays*1j, - minb[1]:maxb[1]:nrays*1j]] + - [np.ones(nrays**2)], axis=-1).astype(np.float32)) - ray_d /= torch.norm(ray_d, dim=-1, keepdim=True) - - ray_o, ray_d = ray_o.to(device).to(dtype), ray_d.to(device).to(dtype) - - return ray_o, ray_d - - -def evaluate_density_and_color(dual_grid: GridBatch, sh_features: torch.Tensor, o_features: torch.Tensor, - ray_d: torch.Tensor, pts: torch.Tensor) -> TensorPair: - - pt_features = dual_grid.sample_trilinear(pts, sh_features.view(sh_features.shape[0], -1)).jdata.view(pts.shape[0], 3, 9) - pt_o_features = dual_grid.sample_trilinear(pts, o_features.unsqueeze(-1)).jdata.squeeze(-1) - return _TruncExp.apply(pt_o_features), torch.sigmoid(speherical_harmonics(2, pt_features, ray_d)) - - -def render(primal_grid: GridBatch, dual_grid: GridBatch, - sh_features: torch.Tensor, o_features: torch.Tensor, - ray_o: torch.Tensor, ray_d: torch.Tensor, - tmin: torch.Tensor, tmax: torch.Tensor, step_size: float, - t_threshold: float = 0.0, chunk: bool = False) -> TensorTriple: - - pack_info, ray_idx, ray_intervals = \ - primal_grid.uniform_ray_samples(ray_o, ray_d, tmin, tmax, step_size) - - ray_t = ray_intervals.jdata.mean(1) - ray_delta_t = (ray_intervals.jdata[:, 1] - ray_intervals.jdata[:, 0]).contiguous() - ray_pts = ray_o[ray_idx.jdata] + ray_t[:, None] * ray_d[ray_idx.jdata] - - if chunk: - ray_density = [] - ray_color = [] - ray_d = ray_d[ray_idx.jdata] - chunk_size = 400000 - - for i in range(ray_d.shape[0]//chunk_size + 1): - ray_density_chunk, ray_color_chunk = evaluate_density_and_color(dual_grid, sh_features, o_features, - ray_d[i*chunk_size:(i+1)*chunk_size, :], - ray_pts[i*chunk_size:(i+1)*chunk_size, :]) - - ray_density.append(ray_density_chunk) - ray_color.append(ray_color_chunk) - - ray_density = torch.cat(ray_density, 0) - ray_color = torch.vstack(ray_color) - else: - ray_density, ray_color = evaluate_density_and_color(dual_grid, sh_features, o_features, - ray_d[ray_idx.jdata], ray_pts) - - # Do the volume rendering - # print(ray_density.shape, ray_color.shape, ray_delta_t.shape, ray_t.shape, pack_info.jdata.shape) - rgb, depth, opacity, _, _ = volume_render(ray_density, ray_color, ray_delta_t, - ray_t, pack_info.jdata, t_threshold) - - return rgb, depth, opacity[:, None] - - -def tv_loss(dual_grid: GridBatch, ijk: torch.Tensor, sh_features: torch.Tensor, o_features: torch.Tensor, res) -> TensorPair: - nhood = dual_grid.neighbor_indexes(ijk, 1).jdata.view(-1, 3, 3, 3) - n_up = nhood[:, 1, 0, 0] - n_right = nhood[:, 0, 1, 0] - n_front = nhood[:, 0, 0, 1] - n_center = nhood[:, 0, 0, 0] - - mask = torch.logical_and(torch.logical_and(n_center != -1, n_up != -1), n_front != -1) - fmask = mask.float() - n_up_mask, n_right_mask, n_center_mask, n_front_mask = n_up[mask], n_right[mask], n_center[mask], n_front[mask] - - diff_up_sh = (sh_features[n_up_mask] - sh_features[n_center_mask]) / (256.0 / res) - diff_right_sh = (sh_features[n_right_mask] - sh_features[n_center_mask]) / (256.0 / res) - diff_front_sh = (sh_features[n_front_mask] - sh_features[n_center_mask]) / (256.0 / res) - - diff_up_o = (o_features[n_up] * fmask - o_features[n_center]) / (256.0 / res) - diff_right_o = (o_features[n_right] * fmask- o_features[n_center]) / (256.0 / res) - diff_front_o = (o_features[n_front] * fmask - o_features[n_center]) / (256.0 / res) - - tv_reg_sh = (diff_up_sh ** 2.0 + diff_right_sh ** 2.0 + diff_front_sh ** 2.0).sum(-1).sum(-1) - tv_reg_o = (diff_up_o ** 2.0 + diff_right_o ** 2.0 + diff_front_o ** 2.0) - return tv_reg_sh.mean(), tv_reg_o.mean() - - -def main(): - # Configuration parameters - device = torch.device('cuda') - dtype = torch.float32 - # scene_aabb = 1.0 - starting_resolution = 256 - resolution = starting_resolution - vox_size = (1.0 / resolution, 1.0 / resolution, 1.0 / resolution) - vox_origin = (vox_size[0]/2, vox_size[1]/2, vox_size[2]/2) - ray_step_size = math.sqrt(3) / 512 - rays_per_batch = 4096 - lr_o = 1e-1 - lr_sh = 1e-2 - - plot_every = 2 - num_epochs = 30 - bg_color = (0.0, 0.0, 0.0) - t_threshold = 1e-5 - - # Create the dataset. Assumes there is a file /data/lego_test.h5 - data_path = os.path.join(os.path.dirname(__file__), "..", "data/lego/") - - if not os.path.exists(data_path): - data_url = "https://drive.google.com/drive/folders/1i6qMn-mnPwPEioiNIFMO8QJlTjU0dS1b?usp=share_link" - raise RuntimeError(f"You need to download the data at {data_url} " - "into /data " - "in order to run this script") - - train_dataset = NeRFDataset(data_path, scale=0.33, num_rays=rays_per_batch, mode='train') - test_dataset = NeRFDataset(data_path, scale=0.33, num_rays=rays_per_batch, mode='test') - - - # Create a sparse grid used to support features and do ray queries - print("Building grid...") - primal_grid = GridBatch(device=device) - primal_grid.set_from_dense_grid(1, [resolution]*3, [-resolution//2]*3, voxel_sizes=vox_size, voxel_origins=vox_origin) - dual_grid = primal_grid # primal_grid.dual_grid() - - print("Done bulding the grid!") - - # Initialize features at the voxel centers - xyz = primal_grid.ijk.jdata / (resolution / 2.0) - print(xyz.min(0)[0], xyz.max(0)[0]) - - sh_features = torch.stack([eval_sh_bases(9, xyz)]*3, dim=1) - print(sh_features.shape) - sh_features = sh_features.to(device=device, dtype=dtype) - o_features = torch.rand(dual_grid.total_voxels) - o_features = o_features.to(device=device, dtype=dtype) - o_features.requires_grad = True - sh_features.requires_grad = True - - # Init optimizer - param_group = [] - param_group.append({'params': o_features, 'lr': lr_o }) - param_group.append({'params': sh_features, 'lr': lr_sh }) - # optimizer = torch.optim.Adam(param_group) - optimizer = torch.optim.RMSprop(param_group) - # scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30, eta_min=lr/30) - - - print("Starting training!") - ps.init() # Initialize 3d plotting - for epoch in tqdm.trange(num_epochs): - if resolution <= starting_resolution: - all_ijk = dual_grid.ijk.jdata - else: - all_ijk = None - pbar = tqdm.tqdm(enumerate(train_dataset)) # type: ignore - for _, batch in pbar: # type: ignore - optimizer.zero_grad() - ray_o, ray_d = batch['rays_o'].to(device=device, dtype=dtype), \ - batch['rays_d'].to(device=device, dtype=dtype) - tmin = torch.zeros(ray_o.shape[0]).to(ray_o) - tmax = torch.full_like(tmin, 1e10) - - # Render color and depth along rays - rgb, depth, opacity = render(primal_grid, dual_grid, sh_features, o_features, ray_o, ray_d, - tmin, tmax, ray_step_size, t_threshold=t_threshold) - - rgb = opacity * rgb + (1.0 - opacity) * torch.tensor(bg_color).to(rgb)[None, :] - - # RGB loss - rgb_gt = batch['rgba'].to(rgb)[:,:3] - loss_rgb = torch.nn.functional.mse_loss(rgb, rgb_gt) # torch.nn.functional.huber_loss(rgb, rgb_gt) / 5. - - # Depth loss - # depth_gt = batch['depth'].to(rgb) - # depth_mask = depth_gt < np.sqrt(3) # Mask out rays that miss the object - # loss_depth = torch.nn.functional.l1_loss(depth[depth_mask], depth_gt[depth_mask]) / 100 - - if resolution <= starting_resolution: - assert all_ijk is not None - random_ijk = all_ijk[torch.randperm(all_ijk.shape[0])[:int(.1*dual_grid.total_voxels)]] - tv_reg_sh, tv_reg_o = tv_loss(dual_grid, random_ijk, sh_features, o_features, resolution) - tv_reg = 1e-1 * tv_reg_sh + 1e-2 * tv_reg_o - else: - tv_reg_sh = torch.tensor([0.0]).to(device) - tv_reg_o = torch.tensor([0.0]).to(device) - tv_reg = torch.tensor([0.0]).to(device) - # Total loss to minimize - loss = loss_rgb + tv_reg #+ 0.1 * loss_depth - - loss.backward() - optimizer.step() - - # Compute current PSNR - psnr = compute_psnr(rgb, rgb_gt) - - # Log losses in tqdm progress bar - pbar.set_postfix({"Loss": f"{loss.item():.4f}", - "Loss RGB": f"{loss_rgb.item():.4f}", - # "Loss Depth": f"{loss_depth.item():.4f}", - "Loss TV (sh)": f"{tv_reg_sh.item():.4f}", - "Loss TV (o)": f"{tv_reg_o.item():.4f}", - "PSNR": f"{psnr.item():.2f}"}) - - # scheduler.step() - - if epoch % plot_every == 0: - with torch.no_grad(): - torch.cuda.empty_cache() - - grid_res = 512 - ray_o, ray_d = make_ray_grid((0., 0.15, 1.2), grid_res, device=device, dtype=dtype) - ray_d = - ray_d - tmin = torch.zeros(ray_o.shape[0]).to(ray_o) - tmax = torch.full_like(tmin, 1e10) - rgb, depth, opacity = render(primal_grid,dual_grid, sh_features, o_features, ray_o, ray_d, - tmin, tmax, ray_step_size, t_threshold=t_threshold, chunk=True) - rgb = opacity * rgb + (1.0 - opacity) * torch.tensor(bg_color).to(rgb)[None, :] - - rgb_img = rgb.clip(0.0, 1.0).detach().cpu().numpy().reshape([grid_res, grid_res, 3]) - depth_img = depth.detach().cpu().numpy().reshape([grid_res, grid_res]) - - plt.figure() - plt.imshow(rgb_img) - plt.figure() - plt.imshow(depth_img) - plt.show() - - ray_v = torch.cat([ray_o, ray_o + ray_d*0.33]).cpu().numpy() - ray_e = np.array([[i, i + ray_o.shape[0]] for i in range(ray_o.shape[0])]) - - ps.register_curve_network("rays", ray_v, ray_e, radius=0.00002) - vox_ijk = primal_grid.ijk.jdata - vox_ctrs = primal_grid.grid_to_world(vox_ijk.to(dtype)).jdata - vox_density, vox_color = evaluate_density_and_color(dual_grid, sh_features, o_features, - torch.ones_like(vox_ctrs), vox_ctrs) - - # Subdivide - if epoch > 0: - sh_features, sub_grid = dual_grid.subdivide(2, sh_features.view(sh_features.shape[0], -1), mask=vox_density > 0.25) - o_features, sub_grid = dual_grid.subdivide(2, o_features.unsqueeze(-1), mask=vox_density > 0.25) - o_features = o_features.jdata.squeeze(-1) - sh_features = sh_features.jdata.reshape(sh_features.rshape[0], 3, -1) - sh_features.requires_grad = True - o_features.requires_grad = True - resolution *= 2.0 - ray_step_size /= 2.0 - - print(f"Subdivided grid with {dual_grid.total_voxels} to {sub_grid.total_voxels}") - dual_grid = sub_grid - primal_grid = sub_grid - - camera_origins = [] - for pose in test_dataset.poses: - camera_origins.append(pose @ np.array([0.0, 0.0, 0.0, 1.0])) - camera_origins = np.stack(camera_origins)[:, :3] - ps.register_point_cloud("camera origins", camera_origins) - - v, e = primal_grid.viz_edge_network - v, e = v.jdata, e.jdata - ps.register_curve_network("grid", v.cpu(), e.cpu(), radius=0.0001) - pc = ps.register_point_cloud("vox centers", vox_ctrs.cpu(), - point_render_mode='quad') - pc.add_scalar_quantity("density", vox_density.cpu(), enabled=True) - pc.add_scalar_quantity("density thresh", (vox_density.cpu() < .25).float(), enabled=True) - pc.add_color_quantity("rgb", vox_color.cpu(), enabled=False) - ps.show() - - - print("Starting testing!") - pbar = tqdm.tqdm(enumerate(test_dataset)) # type: ignore - psnr_test = [] - for _, batch in pbar: # type: ignore - with torch.no_grad(): - ray_o, ray_d = batch['rays_o'].to(device=device, dtype=dtype), \ - batch['rays_d'].to(device=device, dtype=dtype) - tmin = torch.zeros(ray_o.shape[0]).to(ray_o) - tmax = torch.full_like(tmin, 1e10) - rgb_gt = batch['rgba'].to(rgb)[:,:3] # type: ignore - - # Render color and depth along rays - rgb, depth, opacity = render(primal_grid, dual_grid, sh_features, o_features, ray_o, ray_d, - tmin, tmax, ray_step_size, t_threshold=t_threshold, chunk=True) - - rgb = opacity * rgb + (1.0 - opacity) * torch.tensor(bg_color).to(rgb)[None, :] - - # Compute current PSNR - psnr = compute_psnr(rgb, rgb_gt) - psnr_test.append(psnr.item()) - # Log losses in tqdm progress bar - pbar.set_postfix({"PSNR": f"{psnr.item():.2f}"}) - - print(f"Mean PSNR on the test set across {len(test_dataset)} images: {torch.tensor(psnr_test).mean().item()} ") - -if __name__ == "__main__": - main() - -``` diff --git a/documentation/fvdb/_static/_sphinx_javascript_frameworks_compat.js b/documentation/fvdb/_static/_sphinx_javascript_frameworks_compat.js deleted file mode 100644 index 81415803e..000000000 --- a/documentation/fvdb/_static/_sphinx_javascript_frameworks_compat.js +++ /dev/null @@ -1,123 +0,0 @@ -/* Compatability shim for jQuery and underscores.js. - * - * Copyright Sphinx contributors - * Released under the two clause BSD licence - */ - -/** - * small helper function to urldecode strings - * - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL - */ -jQuery.urldecode = function(x) { - if (!x) { - return x - } - return decodeURIComponent(x.replace(/\+/g, ' ')); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} diff --git a/documentation/fvdb/_static/basic.css b/documentation/fvdb/_static/basic.css deleted file mode 100644 index 7ebbd6d07..000000000 --- a/documentation/fvdb/_static/basic.css +++ /dev/null @@ -1,914 +0,0 @@ -/* - * Sphinx stylesheet -- basic theme. - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -div.section::after { - display: block; - content: ''; - clear: left; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox form.search { - overflow: hidden; -} - -div.sphinxsidebar #searchbox input[type="text"] { - float: left; - width: 80%; - padding: 0.25em; - box-sizing: border-box; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - float: left; - width: 20%; - border-left: none; - padding: 0.25em; - box-sizing: border-box; -} - - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin-top: 10px; -} - -ul.search li { - padding: 5px 0; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li p.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body { - min-width: 360px; - max-width: 800px; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -a:visited { - color: #551A8B; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, figure.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, figure.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, figure.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -img.align-default, figure.align-default, .figure.align-default { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-default { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar, -aside.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px; - background-color: #ffe; - width: 40%; - float: right; - clear: right; - overflow-x: auto; -} - -p.sidebar-title { - font-weight: bold; -} - -nav.contents, -aside.topic, -div.admonition, div.topic, blockquote { - clear: left; -} - -/* -- topics ---------------------------------------------------------------- */ - -nav.contents, -aside.topic, -div.topic { - border: 1px solid #ccc; - padding: 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- content of sidebars/topics/admonitions -------------------------------- */ - -div.sidebar > :last-child, -aside.sidebar > :last-child, -nav.contents > :last-child, -aside.topic > :last-child, -div.topic > :last-child, -div.admonition > :last-child { - margin-bottom: 0; -} - -div.sidebar::after, -aside.sidebar::after, -nav.contents::after, -aside.topic::after, -div.topic::after, -div.admonition::after, -blockquote::after { - display: block; - content: ''; - clear: both; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - margin-top: 10px; - margin-bottom: 10px; - border: 0; - border-collapse: collapse; -} - -table.align-center { - margin-left: auto; - margin-right: auto; -} - -table.align-default { - margin-left: auto; - margin-right: auto; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -th > :first-child, -td > :first-child { - margin-top: 0px; -} - -th > :last-child, -td > :last-child { - margin-bottom: 0px; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure, figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption, figcaption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number, -figcaption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text, -figcaption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -/* -- hlist styles ---------------------------------------------------------- */ - -table.hlist { - margin: 1em 0; -} - -table.hlist td { - vertical-align: top; -} - -/* -- object description styles --------------------------------------------- */ - -.sig { - font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; -} - -.sig-name, code.descname { - background-color: transparent; - font-weight: bold; -} - -.sig-name { - font-size: 1.1em; -} - -code.descname { - font-size: 1.2em; -} - -.sig-prename, code.descclassname { - background-color: transparent; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.sig-param.n { - font-style: italic; -} - -/* C++ specific styling */ - -.sig-inline.c-texpr, -.sig-inline.cpp-texpr { - font-family: unset; -} - -.sig.c .k, .sig.c .kt, -.sig.cpp .k, .sig.cpp .kt { - color: #0033B3; -} - -.sig.c .m, -.sig.cpp .m { - color: #1750EB; -} - -.sig.c .s, .sig.c .sc, -.sig.cpp .s, .sig.cpp .sc { - color: #067D17; -} - - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -:not(li) > ol > li:first-child > :first-child, -:not(li) > ul > li:first-child > :first-child { - margin-top: 0px; -} - -:not(li) > ol > li:last-child > :last-child, -:not(li) > ul > li:last-child > :last-child { - margin-bottom: 0px; -} - -ol.simple ol p, -ol.simple ul p, -ul.simple ol p, -ul.simple ul p { - margin-top: 0; -} - -ol.simple > li:not(:first-child) > p, -ul.simple > li:not(:first-child) > p { - margin-top: 0; -} - -ol.simple p, -ul.simple p { - margin-bottom: 0; -} - -aside.footnote > span, -div.citation > span { - float: left; -} -aside.footnote > span:last-of-type, -div.citation > span:last-of-type { - padding-right: 0.5em; -} -aside.footnote > p { - margin-left: 2em; -} -div.citation > p { - margin-left: 4em; -} -aside.footnote > p:last-of-type, -div.citation > p:last-of-type { - margin-bottom: 0em; -} -aside.footnote > p:last-of-type:after, -div.citation > p:last-of-type:after { - content: ""; - clear: both; -} - -dl.field-list { - display: grid; - grid-template-columns: fit-content(30%) auto; -} - -dl.field-list > dt { - font-weight: bold; - word-break: break-word; - padding-left: 0.5em; - padding-right: 5px; -} - -dl.field-list > dd { - padding-left: 0.5em; - margin-top: 0em; - margin-left: 0em; - margin-bottom: 0em; -} - -dl { - margin-bottom: 15px; -} - -dd > :first-child { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -.sig dd { - margin-top: 0px; - margin-bottom: 0px; -} - -.sig dl { - margin-top: 0px; - margin-bottom: 0px; -} - -dl > dd:last-child, -dl > dd:last-child > :last-child { - margin-bottom: 0; -} - -dt:target, span.highlighted { - background-color: #fbe54e; -} - -rect.highlighted { - fill: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -.classifier:before { - font-style: normal; - margin: 0 0.5em; - content: ":"; - display: inline-block; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -.translated { - background-color: rgba(207, 255, 207, 0.2) -} - -.untranslated { - background-color: rgba(255, 207, 207, 0.2) -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -pre, div[class*="highlight-"] { - clear: both; -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; - white-space: nowrap; -} - -div[class*="highlight-"] { - margin: 1em 0; -} - -td.linenos pre { - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - display: block; -} - -table.highlighttable tbody { - display: block; -} - -table.highlighttable tr { - display: flex; -} - -table.highlighttable td { - margin: 0; - padding: 0; -} - -table.highlighttable td.linenos { - padding-right: 0.5em; -} - -table.highlighttable td.code { - flex: 1; - overflow: hidden; -} - -.highlight .hll { - display: block; -} - -div.highlight pre, -table.highlighttable pre { - margin: 0; -} - -div.code-block-caption + div { - margin-top: 0; -} - -div.code-block-caption { - margin-top: 1em; - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -table.highlighttable td.linenos, -span.linenos, -div.highlight span.gp { /* gp: Generic.Prompt */ - user-select: none; - -webkit-user-select: text; /* Safari fallback only */ - -webkit-user-select: none; /* Chrome/Safari */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* IE10+ */ -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - margin: 1em 0; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: absolute; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/documentation/fvdb/_static/css/badge_only.css b/documentation/fvdb/_static/css/badge_only.css deleted file mode 100644 index 88ba55b96..000000000 --- a/documentation/fvdb/_static/css/badge_only.css +++ /dev/null @@ -1 +0,0 @@ -.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/documentation/fvdb/_static/css/fonts/Roboto-Slab-Bold.woff b/documentation/fvdb/_static/css/fonts/Roboto-Slab-Bold.woff deleted file mode 100644 index 6cb600001..000000000 Binary files a/documentation/fvdb/_static/css/fonts/Roboto-Slab-Bold.woff and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/Roboto-Slab-Bold.woff2 b/documentation/fvdb/_static/css/fonts/Roboto-Slab-Bold.woff2 deleted file mode 100644 index 7059e2314..000000000 Binary files a/documentation/fvdb/_static/css/fonts/Roboto-Slab-Bold.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/Roboto-Slab-Regular.woff b/documentation/fvdb/_static/css/fonts/Roboto-Slab-Regular.woff deleted file mode 100644 index f815f63f9..000000000 Binary files a/documentation/fvdb/_static/css/fonts/Roboto-Slab-Regular.woff and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/Roboto-Slab-Regular.woff2 b/documentation/fvdb/_static/css/fonts/Roboto-Slab-Regular.woff2 deleted file mode 100644 index f2c76e5bd..000000000 Binary files a/documentation/fvdb/_static/css/fonts/Roboto-Slab-Regular.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/fontawesome-webfont.eot b/documentation/fvdb/_static/css/fonts/fontawesome-webfont.eot deleted file mode 100644 index e9f60ca95..000000000 Binary files a/documentation/fvdb/_static/css/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/fontawesome-webfont.svg b/documentation/fvdb/_static/css/fonts/fontawesome-webfont.svg deleted file mode 100644 index 855c845e5..000000000 --- a/documentation/fvdb/_static/css/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,2671 +0,0 @@ - - - - -Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 - By ,,, -Copyright Dave Gandy 2016. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/documentation/fvdb/_static/css/fonts/fontawesome-webfont.ttf b/documentation/fvdb/_static/css/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 35acda2fa..000000000 Binary files a/documentation/fvdb/_static/css/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/fontawesome-webfont.woff b/documentation/fvdb/_static/css/fonts/fontawesome-webfont.woff deleted file mode 100644 index 400014a4b..000000000 Binary files a/documentation/fvdb/_static/css/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/fontawesome-webfont.woff2 b/documentation/fvdb/_static/css/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc604..000000000 Binary files a/documentation/fvdb/_static/css/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/lato-bold-italic.woff b/documentation/fvdb/_static/css/fonts/lato-bold-italic.woff deleted file mode 100644 index 88ad05b9f..000000000 Binary files a/documentation/fvdb/_static/css/fonts/lato-bold-italic.woff and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/lato-bold-italic.woff2 b/documentation/fvdb/_static/css/fonts/lato-bold-italic.woff2 deleted file mode 100644 index c4e3d804b..000000000 Binary files a/documentation/fvdb/_static/css/fonts/lato-bold-italic.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/lato-bold.woff b/documentation/fvdb/_static/css/fonts/lato-bold.woff deleted file mode 100644 index c6dff51f0..000000000 Binary files a/documentation/fvdb/_static/css/fonts/lato-bold.woff and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/lato-bold.woff2 b/documentation/fvdb/_static/css/fonts/lato-bold.woff2 deleted file mode 100644 index bb195043c..000000000 Binary files a/documentation/fvdb/_static/css/fonts/lato-bold.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/lato-normal-italic.woff b/documentation/fvdb/_static/css/fonts/lato-normal-italic.woff deleted file mode 100644 index 76114bc03..000000000 Binary files a/documentation/fvdb/_static/css/fonts/lato-normal-italic.woff and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/lato-normal-italic.woff2 b/documentation/fvdb/_static/css/fonts/lato-normal-italic.woff2 deleted file mode 100644 index 3404f37e2..000000000 Binary files a/documentation/fvdb/_static/css/fonts/lato-normal-italic.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/lato-normal.woff b/documentation/fvdb/_static/css/fonts/lato-normal.woff deleted file mode 100644 index ae1307ff5..000000000 Binary files a/documentation/fvdb/_static/css/fonts/lato-normal.woff and /dev/null differ diff --git a/documentation/fvdb/_static/css/fonts/lato-normal.woff2 b/documentation/fvdb/_static/css/fonts/lato-normal.woff2 deleted file mode 100644 index 3bf984332..000000000 Binary files a/documentation/fvdb/_static/css/fonts/lato-normal.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/css/theme.css b/documentation/fvdb/_static/css/theme.css deleted file mode 100644 index 0f14f1064..000000000 --- a/documentation/fvdb/_static/css/theme.css +++ /dev/null @@ -1,4 +0,0 @@ -html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/documentation/fvdb/_static/doctools.js b/documentation/fvdb/_static/doctools.js deleted file mode 100644 index 0398ebb9f..000000000 --- a/documentation/fvdb/_static/doctools.js +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Base JavaScript utilities for all Sphinx HTML documentation. - */ -"use strict"; - -const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ - "TEXTAREA", - "INPUT", - "SELECT", - "BUTTON", -]); - -const _ready = (callback) => { - if (document.readyState !== "loading") { - callback(); - } else { - document.addEventListener("DOMContentLoaded", callback); - } -}; - -/** - * Small JavaScript module for the documentation. - */ -const Documentation = { - init: () => { - Documentation.initDomainIndexTable(); - Documentation.initOnKeyListeners(); - }, - - /** - * i18n support - */ - TRANSLATIONS: {}, - PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), - LOCALE: "unknown", - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext: (string) => { - const translated = Documentation.TRANSLATIONS[string]; - switch (typeof translated) { - case "undefined": - return string; // no translation - case "string": - return translated; // translation exists - default: - return translated[0]; // (singular, plural) translation tuple exists - } - }, - - ngettext: (singular, plural, n) => { - const translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated !== "undefined") - return translated[Documentation.PLURAL_EXPR(n)]; - return n === 1 ? singular : plural; - }, - - addTranslations: (catalog) => { - Object.assign(Documentation.TRANSLATIONS, catalog.messages); - Documentation.PLURAL_EXPR = new Function( - "n", - `return (${catalog.plural_expr})` - ); - Documentation.LOCALE = catalog.locale; - }, - - /** - * helper function to focus on search bar - */ - focusSearchBar: () => { - document.querySelectorAll("input[name=q]")[0]?.focus(); - }, - - /** - * Initialise the domain index toggle buttons - */ - initDomainIndexTable: () => { - const toggler = (el) => { - const idNumber = el.id.substr(7); - const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); - if (el.src.substr(-9) === "minus.png") { - el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; - toggledRows.forEach((el) => (el.style.display = "none")); - } else { - el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; - toggledRows.forEach((el) => (el.style.display = "")); - } - }; - - const togglerElements = document.querySelectorAll("img.toggler"); - togglerElements.forEach((el) => - el.addEventListener("click", (event) => toggler(event.currentTarget)) - ); - togglerElements.forEach((el) => (el.style.display = "")); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); - }, - - initOnKeyListeners: () => { - // only install a listener if it is really needed - if ( - !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && - !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS - ) - return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; - // bail with special keys - if (event.altKey || event.ctrlKey || event.metaKey) return; - - if (!event.shiftKey) { - switch (event.key) { - case "ArrowLeft": - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; - - const prevLink = document.querySelector('link[rel="prev"]'); - if (prevLink && prevLink.href) { - window.location.href = prevLink.href; - event.preventDefault(); - } - break; - case "ArrowRight": - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; - - const nextLink = document.querySelector('link[rel="next"]'); - if (nextLink && nextLink.href) { - window.location.href = nextLink.href; - event.preventDefault(); - } - break; - } - } - - // some keyboard layouts may need Shift to get / - switch (event.key) { - case "/": - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; - Documentation.focusSearchBar(); - event.preventDefault(); - } - }); - }, -}; - -// quick alias for translations -const _ = Documentation.gettext; - -_ready(Documentation.init); diff --git a/documentation/fvdb/_static/documentation_options.js b/documentation/fvdb/_static/documentation_options.js deleted file mode 100644 index 7e4c114f2..000000000 --- a/documentation/fvdb/_static/documentation_options.js +++ /dev/null @@ -1,13 +0,0 @@ -const DOCUMENTATION_OPTIONS = { - VERSION: '', - LANGUAGE: 'en', - COLLAPSE_INDEX: false, - BUILDER: 'html', - FILE_SUFFIX: '.html', - LINK_SUFFIX: '.html', - HAS_SOURCE: true, - SOURCELINK_SUFFIX: '.txt', - NAVIGATION_WITH_KEYS: false, - SHOW_SEARCH_SUMMARY: true, - ENABLE_SEARCH_SHORTCUTS: true, -}; \ No newline at end of file diff --git a/documentation/fvdb/_static/file.png b/documentation/fvdb/_static/file.png deleted file mode 100644 index a858a410e..000000000 Binary files a/documentation/fvdb/_static/file.png and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-bold.eot b/documentation/fvdb/_static/fonts/Lato/lato-bold.eot deleted file mode 100644 index 3361183a4..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-bold.eot and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-bold.ttf b/documentation/fvdb/_static/fonts/Lato/lato-bold.ttf deleted file mode 100644 index 29f691d5e..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-bold.ttf and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-bold.woff b/documentation/fvdb/_static/fonts/Lato/lato-bold.woff deleted file mode 100644 index c6dff51f0..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-bold.woff and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-bold.woff2 b/documentation/fvdb/_static/fonts/Lato/lato-bold.woff2 deleted file mode 100644 index bb195043c..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-bold.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-bolditalic.eot b/documentation/fvdb/_static/fonts/Lato/lato-bolditalic.eot deleted file mode 100644 index 3d4154936..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-bolditalic.eot and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-bolditalic.ttf b/documentation/fvdb/_static/fonts/Lato/lato-bolditalic.ttf deleted file mode 100644 index f402040b3..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-bolditalic.ttf and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-bolditalic.woff b/documentation/fvdb/_static/fonts/Lato/lato-bolditalic.woff deleted file mode 100644 index 88ad05b9f..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-bolditalic.woff and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-bolditalic.woff2 b/documentation/fvdb/_static/fonts/Lato/lato-bolditalic.woff2 deleted file mode 100644 index c4e3d804b..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-bolditalic.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-italic.eot b/documentation/fvdb/_static/fonts/Lato/lato-italic.eot deleted file mode 100644 index 3f826421a..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-italic.eot and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-italic.ttf b/documentation/fvdb/_static/fonts/Lato/lato-italic.ttf deleted file mode 100644 index b4bfc9b24..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-italic.ttf and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-italic.woff b/documentation/fvdb/_static/fonts/Lato/lato-italic.woff deleted file mode 100644 index 76114bc03..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-italic.woff and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-italic.woff2 b/documentation/fvdb/_static/fonts/Lato/lato-italic.woff2 deleted file mode 100644 index 3404f37e2..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-italic.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-regular.eot b/documentation/fvdb/_static/fonts/Lato/lato-regular.eot deleted file mode 100644 index 11e3f2a5f..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-regular.eot and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-regular.ttf b/documentation/fvdb/_static/fonts/Lato/lato-regular.ttf deleted file mode 100644 index 74decd9eb..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-regular.ttf and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-regular.woff b/documentation/fvdb/_static/fonts/Lato/lato-regular.woff deleted file mode 100644 index ae1307ff5..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-regular.woff and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/Lato/lato-regular.woff2 b/documentation/fvdb/_static/fonts/Lato/lato-regular.woff2 deleted file mode 100644 index 3bf984332..000000000 Binary files a/documentation/fvdb/_static/fonts/Lato/lato-regular.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot deleted file mode 100644 index 79dc8efed..000000000 Binary files a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf deleted file mode 100644 index df5d1df27..000000000 Binary files a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff deleted file mode 100644 index 6cb600001..000000000 Binary files a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 deleted file mode 100644 index 7059e2314..000000000 Binary files a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot deleted file mode 100644 index 2f7ca78a1..000000000 Binary files a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf deleted file mode 100644 index eb52a7907..000000000 Binary files a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff deleted file mode 100644 index f815f63f9..000000000 Binary files a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff and /dev/null differ diff --git a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 deleted file mode 100644 index f2c76e5bd..000000000 Binary files a/documentation/fvdb/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 and /dev/null differ diff --git a/documentation/fvdb/_static/jquery.js b/documentation/fvdb/_static/jquery.js deleted file mode 100644 index c4c6022f2..000000000 --- a/documentation/fvdb/_static/jquery.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t -
Languages
- ${config.projects.translations - .map( - (translation) => ` -
- ${translation.language.code} -
- `, - ) - .join("\n")} - - `; - return languagesHTML; - } - - function renderVersions(config) { - if (!config.versions.active.length) { - return ""; - } - const versionsHTML = ` -
-
Versions
- ${config.versions.active - .map( - (version) => ` -
- ${version.slug} -
- `, - ) - .join("\n")} -
- `; - return versionsHTML; - } - - function renderDownloads(config) { - if (!Object.keys(config.versions.current.downloads).length) { - return ""; - } - const downloadsNameDisplay = { - pdf: "PDF", - epub: "Epub", - htmlzip: "HTML", - }; - - const downloadsHTML = ` -
-
Downloads
- ${Object.entries(config.versions.current.downloads) - .map( - ([name, url]) => ` -
- ${downloadsNameDisplay[name]} -
- `, - ) - .join("\n")} -
- `; - return downloadsHTML; - } - - document.addEventListener("readthedocs-addons-data-ready", function (event) { - const config = event.detail.data(); - - const flyout = ` -
- - Read the Docs - v: ${config.versions.current.slug} - - -
-
- ${renderLanguages(config)} - ${renderVersions(config)} - ${renderDownloads(config)} -
-
On Read the Docs
-
- Project Home -
-
- Builds -
-
- Downloads -
-
-
-
Search
-
-
- -
-
-
-
- - Hosted by Read the Docs - -
-
- `; - - // Inject the generated flyout into the body HTML element. - document.body.insertAdjacentHTML("beforeend", flyout); - - // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. - document - .querySelector("#flyout-search-form") - .addEventListener("focusin", () => { - const event = new CustomEvent("readthedocs-search-show"); - document.dispatchEvent(event); - }); - }) -} - -if (themeLanguageSelector || themeVersionSelector) { - function onSelectorSwitch(event) { - const option = event.target.selectedIndex; - const item = event.target.options[option]; - window.location.href = item.dataset.url; - } - - document.addEventListener("readthedocs-addons-data-ready", function (event) { - const config = event.detail.data(); - - const versionSwitch = document.querySelector( - "div.switch-menus > div.version-switch", - ); - if (themeVersionSelector) { - let versions = config.versions.active; - if (config.versions.current.hidden || config.versions.current.type === "external") { - versions.unshift(config.versions.current); - } - const versionSelect = ` - - `; - - versionSwitch.innerHTML = versionSelect; - versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); - } - - const languageSwitch = document.querySelector( - "div.switch-menus > div.language-switch", - ); - - if (themeLanguageSelector) { - if (config.projects.translations.length) { - // Add the current language to the options on the selector - let languages = config.projects.translations.concat( - config.projects.current, - ); - languages = languages.sort((a, b) => - a.language.name.localeCompare(b.language.name), - ); - - const languageSelect = ` - - `; - - languageSwitch.innerHTML = languageSelect; - languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); - } - else { - languageSwitch.remove(); - } - } - }); -} - -document.addEventListener("readthedocs-addons-data-ready", function (event) { - // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. - document - .querySelector("[role='search'] input") - .addEventListener("focusin", () => { - const event = new CustomEvent("readthedocs-search-show"); - document.dispatchEvent(event); - }); -}); \ No newline at end of file diff --git a/documentation/fvdb/_static/language_data.js b/documentation/fvdb/_static/language_data.js deleted file mode 100644 index c7fe6c6fa..000000000 --- a/documentation/fvdb/_static/language_data.js +++ /dev/null @@ -1,192 +0,0 @@ -/* - * This script contains the language-specific data used by searchtools.js, - * namely the list of stopwords, stemmer, scorer and splitter. - */ - -var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; - - -/* Non-minified version is copied as a separate JS file, if available */ - -/** - * Porter Stemmer - */ -var Stemmer = function() { - - var step2list = { - ational: 'ate', - tional: 'tion', - enci: 'ence', - anci: 'ance', - izer: 'ize', - bli: 'ble', - alli: 'al', - entli: 'ent', - eli: 'e', - ousli: 'ous', - ization: 'ize', - ation: 'ate', - ator: 'ate', - alism: 'al', - iveness: 'ive', - fulness: 'ful', - ousness: 'ous', - aliti: 'al', - iviti: 'ive', - biliti: 'ble', - logi: 'log' - }; - - var step3list = { - icate: 'ic', - ative: '', - alize: 'al', - iciti: 'ic', - ical: 'ic', - ful: '', - ness: '' - }; - - var c = "[^aeiou]"; // consonant - var v = "[aeiouy]"; // vowel - var C = c + "[^aeiouy]*"; // consonant sequence - var V = v + "[aeiou]*"; // vowel sequence - - var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 - var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 - var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 - var s_v = "^(" + C + ")?" + v; // vowel in stem - - this.stemWord = function (w) { - var stem; - var suffix; - var firstch; - var origword = w; - - if (w.length < 3) - return w; - - var re; - var re2; - var re3; - var re4; - - firstch = w.substr(0,1); - if (firstch == "y") - w = firstch.toUpperCase() + w.substr(1); - - // Step 1a - re = /^(.+?)(ss|i)es$/; - re2 = /^(.+?)([^s])s$/; - - if (re.test(w)) - w = w.replace(re,"$1$2"); - else if (re2.test(w)) - w = w.replace(re2,"$1$2"); - - // Step 1b - re = /^(.+?)eed$/; - re2 = /^(.+?)(ed|ing)$/; - if (re.test(w)) { - var fp = re.exec(w); - re = new RegExp(mgr0); - if (re.test(fp[1])) { - re = /.$/; - w = w.replace(re,""); - } - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - re2 = new RegExp(s_v); - if (re2.test(stem)) { - w = stem; - re2 = /(at|bl|iz)$/; - re3 = new RegExp("([^aeiouylsz])\\1$"); - re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re2.test(w)) - w = w + "e"; - else if (re3.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - else if (re4.test(w)) - w = w + "e"; - } - } - - // Step 1c - re = /^(.+?)y$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(s_v); - if (re.test(stem)) - w = stem + "i"; - } - - // Step 2 - re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step2list[suffix]; - } - - // Step 3 - re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step3list[suffix]; - } - - // Step 4 - re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; - re2 = /^(.+?)(s|t)(ion)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - if (re.test(stem)) - w = stem; - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1] + fp[2]; - re2 = new RegExp(mgr1); - if (re2.test(stem)) - w = stem; - } - - // Step 5 - re = /^(.+?)e$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - re2 = new RegExp(meq1); - re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) - w = stem; - } - re = /ll$/; - re2 = new RegExp(mgr1); - if (re.test(w) && re2.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - - // and turn initial Y back to y - if (firstch == "y") - w = firstch.toLowerCase() + w.substr(1); - return w; - } -} - diff --git a/documentation/fvdb/_static/minus.png b/documentation/fvdb/_static/minus.png deleted file mode 100644 index d96755fda..000000000 Binary files a/documentation/fvdb/_static/minus.png and /dev/null differ diff --git a/documentation/fvdb/_static/plus.png b/documentation/fvdb/_static/plus.png deleted file mode 100644 index 7107cec93..000000000 Binary files a/documentation/fvdb/_static/plus.png and /dev/null differ diff --git a/documentation/fvdb/_static/pygments.css b/documentation/fvdb/_static/pygments.css deleted file mode 100644 index 6f8b210a1..000000000 --- a/documentation/fvdb/_static/pygments.css +++ /dev/null @@ -1,75 +0,0 @@ -pre { line-height: 125%; } -td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -.highlight .hll { background-color: #ffffcc } -.highlight { background: #f8f8f8; } -.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #F00 } /* Error */ -.highlight .k { color: #008000; font-weight: bold } /* Keyword */ -.highlight .o { color: #666 } /* Operator */ -.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ -.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #9C6500 } /* Comment.Preproc */ -.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ -.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ -.highlight .gr { color: #E40000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #008400 } /* Generic.Inserted */ -.highlight .go { color: #717171 } /* Generic.Output */ -.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #04D } /* Generic.Traceback */ -.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #008000 } /* Keyword.Pseudo */ -.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #B00040 } /* Keyword.Type */ -.highlight .m { color: #666 } /* Literal.Number */ -.highlight .s { color: #BA2121 } /* Literal.String */ -.highlight .na { color: #687822 } /* Name.Attribute */ -.highlight .nb { color: #008000 } /* Name.Builtin */ -.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */ -.highlight .no { color: #800 } /* Name.Constant */ -.highlight .nd { color: #A2F } /* Name.Decorator */ -.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #00F } /* Name.Function */ -.highlight .nl { color: #767600 } /* Name.Label */ -.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #19177C } /* Name.Variable */ -.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #BBB } /* Text.Whitespace */ -.highlight .mb { color: #666 } /* Literal.Number.Bin */ -.highlight .mf { color: #666 } /* Literal.Number.Float */ -.highlight .mh { color: #666 } /* Literal.Number.Hex */ -.highlight .mi { color: #666 } /* Literal.Number.Integer */ -.highlight .mo { color: #666 } /* Literal.Number.Oct */ -.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ -.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ -.highlight .sc { color: #BA2121 } /* Literal.String.Char */ -.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ -.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ -.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ -.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ -.highlight .sx { color: #008000 } /* Literal.String.Other */ -.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ -.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ -.highlight .ss { color: #19177C } /* Literal.String.Symbol */ -.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ -.highlight .fm { color: #00F } /* Name.Function.Magic */ -.highlight .vc { color: #19177C } /* Name.Variable.Class */ -.highlight .vg { color: #19177C } /* Name.Variable.Global */ -.highlight .vi { color: #19177C } /* Name.Variable.Instance */ -.highlight .vm { color: #19177C } /* Name.Variable.Magic */ -.highlight .il { color: #666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/documentation/fvdb/_static/searchtools.js b/documentation/fvdb/_static/searchtools.js deleted file mode 100644 index 2c774d17a..000000000 --- a/documentation/fvdb/_static/searchtools.js +++ /dev/null @@ -1,632 +0,0 @@ -/* - * Sphinx JavaScript utilities for the full-text search. - */ -"use strict"; - -/** - * Simple result scoring code. - */ -if (typeof Scorer === "undefined") { - var Scorer = { - // Implement the following function to further tweak the score for each result - // The function takes a result array [docname, title, anchor, descr, score, filename] - // and returns the new score. - /* - score: result => { - const [docname, title, anchor, descr, score, filename, kind] = result - return score - }, - */ - - // query matches the full name of an object - objNameMatch: 11, - // or matches in the last dotted part of the object name - objPartialMatch: 6, - // Additive scores depending on the priority of the object - objPrio: { - 0: 15, // used to be importantResults - 1: 5, // used to be objectResults - 2: -5, // used to be unimportantResults - }, - // Used when the priority is not in the mapping. - objPrioDefault: 0, - - // query found in title - title: 15, - partialTitle: 7, - // query found in terms - term: 5, - partialTerm: 2, - }; -} - -// Global search result kind enum, used by themes to style search results. -class SearchResultKind { - static get index() { return "index"; } - static get object() { return "object"; } - static get text() { return "text"; } - static get title() { return "title"; } -} - -const _removeChildren = (element) => { - while (element && element.lastChild) element.removeChild(element.lastChild); -}; - -/** - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping - */ -const _escapeRegExp = (string) => - string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string - -const _displayItem = (item, searchTerms, highlightTerms) => { - const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; - const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; - const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; - const contentRoot = document.documentElement.dataset.content_root; - - const [docName, title, anchor, descr, score, _filename, kind] = item; - - let listItem = document.createElement("li"); - // Add a class representing the item's type: - // can be used by a theme's CSS selector for styling - // See SearchResultKind for the class names. - listItem.classList.add(`kind-${kind}`); - let requestUrl; - let linkUrl; - if (docBuilder === "dirhtml") { - // dirhtml builder - let dirname = docName + "/"; - if (dirname.match(/\/index\/$/)) - dirname = dirname.substring(0, dirname.length - 6); - else if (dirname === "index/") dirname = ""; - requestUrl = contentRoot + dirname; - linkUrl = requestUrl; - } else { - // normal html builders - requestUrl = contentRoot + docName + docFileSuffix; - linkUrl = docName + docLinkSuffix; - } - let linkEl = listItem.appendChild(document.createElement("a")); - linkEl.href = linkUrl + anchor; - linkEl.dataset.score = score; - linkEl.innerHTML = title; - if (descr) { - listItem.appendChild(document.createElement("span")).innerHTML = - " (" + descr + ")"; - // highlight search terms in the description - if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js - highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); - } - else if (showSearchSummary) - fetch(requestUrl) - .then((responseData) => responseData.text()) - .then((data) => { - if (data) - listItem.appendChild( - Search.makeSearchSummary(data, searchTerms, anchor) - ); - // highlight search terms in the summary - if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js - highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); - }); - Search.output.appendChild(listItem); -}; -const _finishSearch = (resultCount) => { - Search.stopPulse(); - Search.title.innerText = _("Search Results"); - if (!resultCount) - Search.status.innerText = Documentation.gettext( - "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." - ); - else - Search.status.innerText = Documentation.ngettext( - "Search finished, found one page matching the search query.", - "Search finished, found ${resultCount} pages matching the search query.", - resultCount, - ).replace('${resultCount}', resultCount); -}; -const _displayNextItem = ( - results, - resultCount, - searchTerms, - highlightTerms, -) => { - // results left, load the summary and display it - // this is intended to be dynamic (don't sub resultsCount) - if (results.length) { - _displayItem(results.pop(), searchTerms, highlightTerms); - setTimeout( - () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), - 5 - ); - } - // search finished, update title and status message - else _finishSearch(resultCount); -}; -// Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. -// Order the results by score (in opposite order of appearance, since the -// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. -const _orderResultsByScoreThenName = (a, b) => { - const leftScore = a[4]; - const rightScore = b[4]; - if (leftScore === rightScore) { - // same score: sort alphabetically - const leftTitle = a[1].toLowerCase(); - const rightTitle = b[1].toLowerCase(); - if (leftTitle === rightTitle) return 0; - return leftTitle > rightTitle ? -1 : 1; // inverted is intentional - } - return leftScore > rightScore ? 1 : -1; -}; - -/** - * Default splitQuery function. Can be overridden in ``sphinx.search`` with a - * custom function per language. - * - * The regular expression works by splitting the string on consecutive characters - * that are not Unicode letters, numbers, underscores, or emoji characters. - * This is the same as ``\W+`` in Python, preserving the surrogate pair area. - */ -if (typeof splitQuery === "undefined") { - var splitQuery = (query) => query - .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) - .filter(term => term) // remove remaining empty strings -} - -/** - * Search Module - */ -const Search = { - _index: null, - _queued_query: null, - _pulse_status: -1, - - htmlToText: (htmlString, anchor) => { - const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); - for (const removalQuery of [".headerlink", "script", "style"]) { - htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); - } - if (anchor) { - const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); - if (anchorContent) return anchorContent.textContent; - - console.warn( - `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` - ); - } - - // if anchor not specified or not found, fall back to main content - const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent) return docContent.textContent; - - console.warn( - "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." - ); - return ""; - }, - - init: () => { - const query = new URLSearchParams(window.location.search).get("q"); - document - .querySelectorAll('input[name="q"]') - .forEach((el) => (el.value = query)); - if (query) Search.performSearch(query); - }, - - loadIndex: (url) => - (document.body.appendChild(document.createElement("script")).src = url), - - setIndex: (index) => { - Search._index = index; - if (Search._queued_query !== null) { - const query = Search._queued_query; - Search._queued_query = null; - Search.query(query); - } - }, - - hasIndex: () => Search._index !== null, - - deferQuery: (query) => (Search._queued_query = query), - - stopPulse: () => (Search._pulse_status = -1), - - startPulse: () => { - if (Search._pulse_status >= 0) return; - - const pulse = () => { - Search._pulse_status = (Search._pulse_status + 1) % 4; - Search.dots.innerText = ".".repeat(Search._pulse_status); - if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); - }; - pulse(); - }, - - /** - * perform a search for something (or wait until index is loaded) - */ - performSearch: (query) => { - // create the required interface elements - const searchText = document.createElement("h2"); - searchText.textContent = _("Searching"); - const searchSummary = document.createElement("p"); - searchSummary.classList.add("search-summary"); - searchSummary.innerText = ""; - const searchList = document.createElement("ul"); - searchList.setAttribute("role", "list"); - searchList.classList.add("search"); - - const out = document.getElementById("search-results"); - Search.title = out.appendChild(searchText); - Search.dots = Search.title.appendChild(document.createElement("span")); - Search.status = out.appendChild(searchSummary); - Search.output = out.appendChild(searchList); - - const searchProgress = document.getElementById("search-progress"); - // Some themes don't use the search progress node - if (searchProgress) { - searchProgress.innerText = _("Preparing search..."); - } - Search.startPulse(); - - // index already loaded, the browser was quick! - if (Search.hasIndex()) Search.query(query); - else Search.deferQuery(query); - }, - - _parseQuery: (query) => { - // stem the search terms and add them to the correct list - const stemmer = new Stemmer(); - const searchTerms = new Set(); - const excludedTerms = new Set(); - const highlightTerms = new Set(); - const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); - splitQuery(query.trim()).forEach((queryTerm) => { - const queryTermLower = queryTerm.toLowerCase(); - - // maybe skip this "word" - // stopwords array is from language_data.js - if ( - stopwords.indexOf(queryTermLower) !== -1 || - queryTerm.match(/^\d+$/) - ) - return; - - // stem the word - let word = stemmer.stemWord(queryTermLower); - // select the correct list - if (word[0] === "-") excludedTerms.add(word.substr(1)); - else { - searchTerms.add(word); - highlightTerms.add(queryTermLower); - } - }); - - if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js - localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) - } - - // console.debug("SEARCH: searching for:"); - // console.info("required: ", [...searchTerms]); - // console.info("excluded: ", [...excludedTerms]); - - return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; - }, - - /** - * execute search (requires search index to be loaded) - */ - _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - - // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename, kind]. - const normalResults = []; - const nonMainIndexResults = []; - - _removeChildren(document.getElementById("search-progress")); - - const queryLower = query.toLowerCase().trim(); - for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { - for (const [file, id] of foundTitles) { - const score = Math.round(Scorer.title * queryLower.length / title.length); - const boost = titles[file] === title ? 1 : 0; // add a boost for document titles - normalResults.push([ - docNames[file], - titles[file] !== title ? `${titles[file]} > ${title}` : title, - id !== null ? "#" + id : "", - null, - score + boost, - filenames[file], - SearchResultKind.title, - ]); - } - } - } - - // search for explicit entries in index directives - for (const [entry, foundEntries] of Object.entries(indexEntries)) { - if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { - for (const [file, id, isMain] of foundEntries) { - const score = Math.round(100 * queryLower.length / entry.length); - const result = [ - docNames[file], - titles[file], - id ? "#" + id : "", - null, - score, - filenames[file], - SearchResultKind.index, - ]; - if (isMain) { - normalResults.push(result); - } else { - nonMainIndexResults.push(result); - } - } - } - } - - // lookup as object - objectTerms.forEach((term) => - normalResults.push(...Search.performObjectSearch(term, objectTerms)) - ); - - // lookup as search terms in fulltext - normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); - - // let the scorer override scores with a custom scoring function - if (Scorer.score) { - normalResults.forEach((item) => (item[4] = Scorer.score(item))); - nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); - } - - // Sort each group of results by score and then alphabetically by name. - normalResults.sort(_orderResultsByScoreThenName); - nonMainIndexResults.sort(_orderResultsByScoreThenName); - - // Combine the result groups in (reverse) order. - // Non-main index entries are typically arbitrary cross-references, - // so display them after other results. - let results = [...nonMainIndexResults, ...normalResults]; - - // remove duplicate search results - // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept - let seen = new Set(); - results = results.reverse().reduce((acc, result) => { - let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); - if (!seen.has(resultStr)) { - acc.push(result); - seen.add(resultStr); - } - return acc; - }, []); - - return results.reverse(); - }, - - query: (query) => { - const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); - const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); - - // for debugging - //Search.lastresults = results.slice(); // a copy - // console.info("search results:", Search.lastresults); - - // print the results - _displayNextItem(results, results.length, searchTerms, highlightTerms); - }, - - /** - * search for object names - */ - performObjectSearch: (object, objectTerms) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const objects = Search._index.objects; - const objNames = Search._index.objnames; - const titles = Search._index.titles; - - const results = []; - - const objectSearchCallback = (prefix, match) => { - const name = match[4] - const fullname = (prefix ? prefix + "." : "") + name; - const fullnameLower = fullname.toLowerCase(); - if (fullnameLower.indexOf(object) < 0) return; - - let score = 0; - const parts = fullnameLower.split("."); - - // check for different match types: exact matches of full name or - // "last name" (i.e. last dotted part) - if (fullnameLower === object || parts.slice(-1)[0] === object) - score += Scorer.objNameMatch; - else if (parts.slice(-1)[0].indexOf(object) > -1) - score += Scorer.objPartialMatch; // matches in last name - - const objName = objNames[match[1]][2]; - const title = titles[match[0]]; - - // If more than one term searched for, we require other words to be - // found in the name/title/description - const otherTerms = new Set(objectTerms); - otherTerms.delete(object); - if (otherTerms.size > 0) { - const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); - if ( - [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) - ) - return; - } - - let anchor = match[3]; - if (anchor === "") anchor = fullname; - else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; - - const descr = objName + _(", in ") + title; - - // add custom score for some objects according to scorer - if (Scorer.objPrio.hasOwnProperty(match[2])) - score += Scorer.objPrio[match[2]]; - else score += Scorer.objPrioDefault; - - results.push([ - docNames[match[0]], - fullname, - "#" + anchor, - descr, - score, - filenames[match[0]], - SearchResultKind.object, - ]); - }; - Object.keys(objects).forEach((prefix) => - objects[prefix].forEach((array) => - objectSearchCallback(prefix, array) - ) - ); - return results; - }, - - /** - * search for full-text terms in the index - */ - performTermsSearch: (searchTerms, excludedTerms) => { - // prepare search - const terms = Search._index.terms; - const titleTerms = Search._index.titleterms; - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - - const scoreMap = new Map(); - const fileMap = new Map(); - - // perform the search on the required terms - searchTerms.forEach((word) => { - const files = []; - const arr = [ - { files: terms[word], score: Scorer.term }, - { files: titleTerms[word], score: Scorer.title }, - ]; - // add support for partial matches - if (word.length > 2) { - const escapedWord = _escapeRegExp(word); - if (!terms.hasOwnProperty(word)) { - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord)) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - } - if (!titleTerms.hasOwnProperty(word)) { - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord)) - arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); - }); - } - } - - // no match but word was a required one - if (arr.every((record) => record.files === undefined)) return; - - // found search word in contents - arr.forEach((record) => { - if (record.files === undefined) return; - - let recordFiles = record.files; - if (recordFiles.length === undefined) recordFiles = [recordFiles]; - files.push(...recordFiles); - - // set score for the word in each file - recordFiles.forEach((file) => { - if (!scoreMap.has(file)) scoreMap.set(file, {}); - scoreMap.get(file)[word] = record.score; - }); - }); - - // create the mapping - files.forEach((file) => { - if (!fileMap.has(file)) fileMap.set(file, [word]); - else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); - }); - }); - - // now check if the files don't contain excluded terms - const results = []; - for (const [file, wordList] of fileMap) { - // check if all requirements are matched - - // as search terms with length < 3 are discarded - const filteredTermCount = [...searchTerms].filter( - (term) => term.length > 2 - ).length; - if ( - wordList.length !== searchTerms.size && - wordList.length !== filteredTermCount - ) - continue; - - // ensure that none of the excluded terms is in the search result - if ( - [...excludedTerms].some( - (term) => - terms[term] === file || - titleTerms[term] === file || - (terms[term] || []).includes(file) || - (titleTerms[term] || []).includes(file) - ) - ) - break; - - // select one (max) score for the file. - const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); - // add result to the result list - results.push([ - docNames[file], - titles[file], - "", - null, - score, - filenames[file], - SearchResultKind.text, - ]); - } - return results; - }, - - /** - * helper function to return a node containing the - * search summary for a given text. keywords is a list - * of stemmed words. - */ - makeSearchSummary: (htmlText, keywords, anchor) => { - const text = Search.htmlToText(htmlText, anchor); - if (text === "") return null; - - const textLower = text.toLowerCase(); - const actualStartPosition = [...keywords] - .map((k) => textLower.indexOf(k.toLowerCase())) - .filter((i) => i > -1) - .slice(-1)[0]; - const startWithContext = Math.max(actualStartPosition - 120, 0); - - const top = startWithContext === 0 ? "" : "..."; - const tail = startWithContext + 240 < text.length ? "..." : ""; - - let summary = document.createElement("p"); - summary.classList.add("context"); - summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; - - return summary; - }, -}; - -_ready(Search.init); diff --git a/documentation/fvdb/_static/sphinx_highlight.js b/documentation/fvdb/_static/sphinx_highlight.js deleted file mode 100644 index 8a96c69a1..000000000 --- a/documentation/fvdb/_static/sphinx_highlight.js +++ /dev/null @@ -1,154 +0,0 @@ -/* Highlighting utilities for Sphinx HTML documentation. */ -"use strict"; - -const SPHINX_HIGHLIGHT_ENABLED = true - -/** - * highlight a given string on a node by wrapping it in - * span elements with the given class name. - */ -const _highlight = (node, addItems, text, className) => { - if (node.nodeType === Node.TEXT_NODE) { - const val = node.nodeValue; - const parent = node.parentNode; - const pos = val.toLowerCase().indexOf(text); - if ( - pos >= 0 && - !parent.classList.contains(className) && - !parent.classList.contains("nohighlight") - ) { - let span; - - const closestNode = parent.closest("body, svg, foreignObject"); - const isInSVG = closestNode && closestNode.matches("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.classList.add(className); - } - - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - const rest = document.createTextNode(val.substr(pos + text.length)); - parent.insertBefore( - span, - parent.insertBefore( - rest, - node.nextSibling - ) - ); - node.nodeValue = val.substr(0, pos); - /* There may be more occurrences of search term in this node. So call this - * function recursively on the remaining fragment. - */ - _highlight(rest, addItems, text, className); - - if (isInSVG) { - const rect = document.createElementNS( - "http://www.w3.org/2000/svg", - "rect" - ); - const bbox = parent.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute("class", className); - addItems.push({ parent: parent, target: rect }); - } - } - } else if (node.matches && !node.matches("button, select, textarea")) { - node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); - } -}; -const _highlightText = (thisNode, text, className) => { - let addItems = []; - _highlight(thisNode, addItems, text, className); - addItems.forEach((obj) => - obj.parent.insertAdjacentElement("beforebegin", obj.target) - ); -}; - -/** - * Small JavaScript module for the documentation. - */ -const SphinxHighlight = { - - /** - * highlight the search words provided in localstorage in the text - */ - highlightSearchWords: () => { - if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight - - // get and clear terms from localstorage - const url = new URL(window.location); - const highlight = - localStorage.getItem("sphinx_highlight_terms") - || url.searchParams.get("highlight") - || ""; - localStorage.removeItem("sphinx_highlight_terms") - url.searchParams.delete("highlight"); - window.history.replaceState({}, "", url); - - // get individual terms from highlight string - const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); - if (terms.length === 0) return; // nothing to do - - // There should never be more than one element matching "div.body" - const divBody = document.querySelectorAll("div.body"); - const body = divBody.length ? divBody[0] : document.querySelector("body"); - window.setTimeout(() => { - terms.forEach((term) => _highlightText(body, term, "highlighted")); - }, 10); - - const searchBox = document.getElementById("searchbox"); - if (searchBox === null) return; - searchBox.appendChild( - document - .createRange() - .createContextualFragment( - '" - ) - ); - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords: () => { - document - .querySelectorAll("#searchbox .highlight-link") - .forEach((el) => el.remove()); - document - .querySelectorAll("span.highlighted") - .forEach((el) => el.classList.remove("highlighted")); - localStorage.removeItem("sphinx_highlight_terms") - }, - - initEscapeListener: () => { - // only install a listener if it is really needed - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; - // bail with special keys - if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; - if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { - SphinxHighlight.hideSearchWords(); - event.preventDefault(); - } - }); - }, -}; - -_ready(() => { - /* Do not call highlightSearchWords() when we are on the search page. - * It will highlight words from the *previous* search query. - */ - if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); - SphinxHighlight.initEscapeListener(); -}); diff --git a/documentation/fvdb/api/grid_batch.html b/documentation/fvdb/api/grid_batch.html deleted file mode 100644 index 61eda6e0c..000000000 --- a/documentation/fvdb/api/grid_batch.html +++ /dev/null @@ -1,1061 +0,0 @@ - - - - - - - - - GridBatch — fVDB documentation - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

GridBatch

-
-
-class fvdb.GridBatch
-

A batch of sparse VDB grids.

-
-
-property address
-

The memory address of the underlying C++ GridBatch object.

-
- -
-
-avg_pool(self: fvdb.GridBatch, pool_factor: fvdb.Vec3iOrScalar, data: fvdb.JaggedTensor, stride: fvdb.Vec3iOrScalar = 0, coarse_grid: fvdb.GridBatch | None = None) tuple[fvdb.JaggedTensor, fvdb.GridBatch]
-

Downsample this batch of grids using average pooling.

-
-
Parameters:
-
    -
  • pool_factor (int or 3-tuple of ints) – How much to pool by (i,e, (2,2,2) means take average over 2x2x2 from start of window).

  • -
  • data (JaggedTensor) – Data at each voxel in this grid to be downsampled (JaggedTensor of shape [B, -1, *]).

  • -
  • stride (int) – The stride to use when pooling

  • -
  • coarse_grid (GridBatch, optional) – An optional coarse grid used to specify the output. This is mainly used -for memory efficiency so you can chache grids. If you don’t pass it in, we’ll just create it for you.

  • -
-
-
Returns:
-
    -
  • coarse_data (JaggedTensor) – a JaggedTensor of shape [B, -1, *] of downsampled data.

  • -
  • coarse_grid (GridBatch) – the downsampled grid batch.

  • -
-
-
-
- -
-
-property bbox
-

A [num_grids, 2, 3] tensor of the bounding box of each grid in this batch where bbox[i, 0] is the minimimum ijk coordinate of the i^th grid, and bbox[i, 1] is the maximum ijk coordinate.

-
- -
-
-bbox_at(self: fvdb.GridBatch, bi: int) torch.Tensor
-

Get the bounding box (in voxel coordinates) of the bi^th grid in the batch.

-
-
Parameters:
-

bi (int) – The index of the grid to get the bounding box of.

-
-
Returns:
-

bbox (torch.Tensor) – A tensor, bbox, of shape [2, 3] where bbox = [[bmin_i, bmin_j, bmin_z=k], -[bmax_i, bmax_j, bmax_k]] is the bi^th bounding box such that bmin <= ijk < bmax for all voxels -ijk in the bi^th grid.

-
-
-
- -
-
-clip(self: fvdb.GridBatch, features: fvdb.JaggedTensor, ijk_min: fvdb.Vec3iBatch, ijk_max: fvdb.Vec3iBatch) tuple[fvdb.JaggedTensor, fvdb.GridBatch]
-

Return a batch of grids representing the clipped version of this batch of grids and corresponding features.

-
-
Parameters:
-
    -
  • features (JaggedTensor) – A JaggedTensor of shape [B, -1, *] containing features associated with this batch of grids.

  • -
  • ijk_min (list of int triplets) – Index space minimum bound of the clip region.

  • -
  • ijk_max (list of int triplets) – Index space maximum bound of the clip region.

  • -
-
-
Returns:
-
    -
  • clipped_features (JaggedTensor) – a JaggedTensor of shape [B, -1, *] of clipped data.

  • -
  • clipped_grid (GridBatch) – the clipped grid batch.

  • -
-
-
-
- -
-
-clipped_grid(self: fvdb.GridBatch, ijk_min: fvdb.Vec3iBatch, ijk_max: fvdb.Vec3iBatch) fvdb.GridBatch
-

Return a batch of grids representing the clipped version of this batch. -Each voxel [i, j, k] in the input batch is included in the output if it lies within ijk_min and ijk_max.

-
-
Parameters:
-
    -
  • ijk_min (list of int triplets) – Index space minimum bound of the clip region.

  • -
  • ijk_max (list of int triplets) – Index space maximum bound of the clip region.

  • -
-
-
Returns:
-

clipped_grid (GridBatch) – A GridBatch representing the clipped version of this grid batch.

-
-
-
- -
-
-coarsened_grid(self: fvdb.GridBatch, coarsening_factor: fvdb.Vec3iOrScalar) fvdb.GridBatch
-

Return a batch of grids representing the coarsened version of this batch. -Each voxel [i, j, k] in this grid batch maps to voxel [i / branchFactor, j / branchFactor, k / branchFactor] in the coarse batch.

-
-
Parameters:
-

coarsening_factor (int or 3-tuple of ints) – How much to coarsen by (i,e, (2,2,2) means take every other voxel from start of window).

-
-
Returns:
-

coarsened_grid (GridBatch) – A GridBatch representing the coarsened version of this grid batch.

-
-
-
- -
-
-contiguous(self: fvdb.GridBatch) fvdb.GridBatch
-

Return a contiguous copy of this grid batch.

-
- -
-
-conv_grid(self: fvdb.GridBatch, kernel_size: fvdb.Vec3iOrScalar, stride: fvdb.Vec3iOrScalar) fvdb.GridBatch
-

Return a batch of grids representing the convolution of this batch with a given kernel. -Each voxel [i, j, k] in the output batch is the sum of the voxels in the input batch [i * stride, j * stride, k * stride] to [i * stride + kernel_size, j * stride + kernel_size, k * stride + kernel_size].

-
-
Parameters:
-
    -
  • kernel_size (int or 3-tuple of ints) – The size of the kernel to convolve with.

  • -
  • stride (int or 3-tuple of ints) – The stride to use when convolving.

  • -
-
-
Returns:
-

conv_grid (GridBatch) – A GridBatch representing the convolution of this grid batch.

-
-
-
- -
-
-coords_in_active_voxel(self: fvdb.GridBatch, ijk: fvdb.JaggedTensor, ignore_disabled: bool = False) fvdb.JaggedTensor
-

Given a set of ijk coordinates, return a JaggedTensor of booleans indicating which coordinates are active in this gridbatch

-
-
Parameters:
-
    -
  • ijk (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of integer ijk coordinates.

  • -
  • ignore_disabled (bool) – Whether to ignore disabled voxels when computing the output.

  • -
-
-
Returns:
-

coords_in_active_voxel (JaggedTensor) – A JaggedTensor of shape [num_grids, -1] of booleans indicating which coordinates are in the grid.

-
-
-
- -
-
-cpu(self: fvdb.GridBatch) fvdb.GridBatch
-
- -
-
-cubes_in_grid(self: fvdb.GridBatch, cube_centers: fvdb.JaggedTensor, cube_min: fvdb.Vec3dOrScalar = 0.0, cube_max: fvdb.Vec3dOrScalar = 0.0, ignore_disabled: bool = False) fvdb.JaggedTensor
-

Given a set of cube centers and extents, return a JaggedTensor of booleans indicating whether cubes fully reside in active voxels.

-
-
Parameters:
-
    -
  • cube_centers (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of cube centers.

  • -
  • cube_min (float or triple of floats) – The minimum extent of each cube (all cubes have the same size).

  • -
  • cube_max (float or triple of floats) – The maximum extent of the cube (all cubes have the same size).

  • -
  • ignore_disabled (bool) – Whether to ignore disabled voxels when computing the output.

  • -
-
-
Returns:
-

cubes_intersect_grid (JaggedTensor) – A JaggedTensor of shape [num_grids, -1] of booleans indicating whether cubes fully reside in active voxels.

-
-
-
- -
-
-cubes_intersect_grid(self: fvdb.GridBatch, cube_centers: fvdb.JaggedTensor, cube_min: fvdb.Vec3dOrScalar = 0.0, cube_max: fvdb.Vec3dOrScalar = 0.0, ignore_disabled: bool = False) fvdb.JaggedTensor
-

Given a set of cube centers and extents, return a JaggedTensor of booleans indicating whether cubes intersect active voxels.

-
-
Parameters:
-
    -
  • cube_centers (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of cube centers.

  • -
  • cube_min (float or triple of floats) – The minimum extent of each cube (all cubes have the same size).

  • -
  • cube_max (float or triple of floats) – The maximum extent of the cube (all cubes have the same size).

  • -
  • ignore_disabled (bool) – Whether to ignore disabled voxels when computing the output.

  • -
-
-
Returns:
-

cubes_intersect_grid (JaggedTensor) – A JaggedTensor of shape [num_grids, -1] of booleans indicating whether cubes intersect active voxels.

-
-
-
- -
-
-cuda(self: fvdb.GridBatch) fvdb.GridBatch
-
- -
-
-property cum_enabled_voxels
-

An integer tensor containing the cumulative number of voxels enabled in each grid in this batch. i.e. [nvox_0, nvox_0+nvox_1, nvox_0+nvox_1+nvox_2, …]

-
- -
-
-cum_enabled_voxels_at(self: fvdb.GridBatch, arg0: int) int
-

Get the cumulative number of enabled voxels in the bi^th grid in the batch. i.e. nvox_0+nvox_1+…+nvox_i. If this grid isn’t mutable, this returns the same value as cum_voxels_at.

-
- -
-
-property cum_voxels
-

An integer tensor containing the cumulative number of voxels indexed by the grids in this batch. -i.e. [nvox_0, nvox_0+nvox_1, nvox_0+nvox_1+nvox_2, …]

-
- -
-
-cum_voxels_at(self: fvdb.GridBatch, arg0: int) int
-

Get the cumulative number of voxels in the bi^th grid in the batch. i.e. nvox_0+nvox_1+…+nvox_i

-
- -
-
-property device
-

The device on which this grid is stored.

-
- -
-
-disable_ijk(self: fvdb.GridBatch, ijk: fvdb.JaggedTensor) None
-

If this is grid is mutable, disable voxels at the specified coordinates, otherwise throw an exception. -If the ijk values are already disabled or are not represented in this GridBatch, then this function is no-op.

-
-
Parameters:
-

ijk (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of ijk coordinates to disable.

-
-
-
- -
-
-property disabled_mask
-

A boolean JaggedTensor of shape [B, -1] indicating whether each voxel in the grid is disabled or not.

-
- -
-
-property dual_bbox
-

A [num_grids, 2, 3] tensor of the bounding box of the dual of each grid in this batch where bbox[i, 0] is the minimimum ijk coordinate of the i^th dual grid, and bbox[i, 1] is the maximum ijk coordinate.

-
- -
-
-dual_bbox_at(self: fvdb.GridBatch, arg0: int) torch.Tensor
-

Get the bounding box (in voxel coordinates) of the dual of the bi^th grid in the batch.

-
- -
-
-dual_grid(self: fvdb.GridBatch, exclude_border: bool = False) fvdb.GridBatch
-

Return a batch of grids representing the dual of this batch. -i.e. The centers of the dual grid correspond to the corners of this grid batch. The [i, j, k] coordinate of the dual grid corresponds to the bottom/left/back -corner of the [i, j, k] voxel in this grid batch.

-
-
Parameters:
-

exclude_border (bool) – Whether to exclude the border of the grid batch when computing the dual grid

-
-
Returns:
-

dual_grid (GridBatch) – A GridBatch representing the dual of this grid batch.

-
-
-
- -
-
-enable_ijk(self: fvdb.GridBatch, ijk: fvdb.JaggedTensor) None
-

If this is grid is mutable, enable voxels at the specified coordinates, otherwise throw an exception. -If the ijk values are already enabled or are not represented in this GridBatch, then this function is no-op.

-
-
Parameters:
-

ijk (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of ijk coordinates to disable.

-
-
-
- -
-
-property enabled_mask
-

A boolean JaggedTensor of shape [B, -1] indicating whether each voxel in the grid is enabled or not.

-
- -
-
-fill_from_grid(self: fvdb.GridBatch, features: fvdb.JaggedTensor, other_grid: fvdb.GridBatch, default_value: float = 0.0) fvdb.JaggedTensor
-

Given a GridBatch and features associated with it, return a JaggedTensor representing features for this batch of grid. -Fill any voxels not in the GridBatch with the default value.

-
-
Parameters:
-
    -
  • other_features (JaggedTensor) – A JaggedTensor of shape [B, -1, *] containing features associated with other_grid.

  • -
  • other_grid (GridBatch) – A GridBatch containing the grid to fill from.

  • -
  • default_value (float) – The value to fill in for voxels not in the GridBatch (default 0.0).

  • -
-
-
Returns:
-

filled_features (JaggedTensor) – A JaggedTensor of shape [B, -1, *] of features associated with this batch of grids.

-
-
-
- -
-
-property grid_count
-

The number of grids indexed by this batch.

-
- -
-
-grid_to_world(self: fvdb.GridBatch, ijk: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-property grid_to_world_matrices
-

A [num_grids, 4, 4] tensor of the grid to world transformation matrices for each grid in this batch.

-
- -
-
-property ijk
-

A [num_grids, -1, 3] JaggedTensor of the ijk coordinates of each voxel in this batch.

-
- -
-
-property ijk_enabled
-

A [num_grids, -1, 3] JaggedTensor of the ijk coordinates of each enabled voxel in this batch.

-
- -
-
-ijk_to_index(self: fvdb.GridBatch, ijk: fvdb.JaggedTensor, cumulative: bool = False) fvdb.JaggedTensor
-
- -
-
-ijk_to_inv_index(self: fvdb.GridBatch, ijk: fvdb.JaggedTensor, cumulative: bool = False) fvdb.JaggedTensor
-
- -
-
-is_contiguous(self: fvdb.GridBatch) bool
-

Whether this grid batch is contiguous.

-
- -
-
-is_same(self: fvdb.GridBatch, other: fvdb.GridBatch) bool
-

Check if this grid batch refers to the same underlying NanoVDB grid as another GridBatch.

-
-
Parameters:
-

other (GridBatch) – The other grid batch to compare to.

-
-
Returns:
-

is_same (bool) – Whether the two grid batches are the same.”

-
-
-
- -
-
-jagged_like(self: fvdb.GridBatch, data: torch.Tensor, ignore_disabled: bool = True) fvdb.JaggedTensor
-

Create a JaggedTensor with the same offsets as this grid batch.

-
-
Parameters:
-
    -
  • data (torch.Tensor) – A tensor of shape [total_voxels, *] to be converted to a JaggedTensor.

  • -
  • ignore_disabled (bool) – Whether to ignore disabled voxels when creating the JaggedTensor.

  • -
-
-
Returns:
-

jagged_data (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, *] with the same offsets as this grid batch.

-
-
-
- -
-
-property jidx
-

A [total_voxels,] tensor of the jagged index of each voxel in this batch.

-
- -
-
-property joffsets
-

A [num_grids+1,] tensor of the jagged offsets of each grid in this batch.

-
- -
-
-marching_cubes(self: fvdb.GridBatch, field: fvdb.JaggedTensor, level: float = 0.0) list[fvdb.JaggedTensor]
-
- -
-
-max_grids_per_batch = 1024
-
- -
-
-max_pool(self: fvdb.GridBatch, pool_factor: fvdb.Vec3iOrScalar, data: fvdb.JaggedTensor, stride: fvdb.Vec3iOrScalar = 0, coarse_grid: fvdb.GridBatch | None = None) tuple[fvdb.JaggedTensor, fvdb.GridBatch]
-

Downsample this batch of grids using maxpooling.

-
-
Parameters:
-
    -
  • pool_factor (int or 3-tuple of ints) – How much to pool by (i,e, (2,2,2) means take max over 2x2x2 from start of window).

  • -
  • data (JaggedTensor) – Data at each voxel in this grid to be downsampled (JaggedTensor of shape [B, -1, *]).

  • -
  • stride (int) – The stride to use when pooling

  • -
  • coarse_grid (GridBatch, optional) – An optional coarse grid used to specify the output. This is mainly used -for memory efficiency so you can chache grids. If you don’t pass it in, we’ll just create it for you.

  • -
-
-
Returns:
-
    -
  • coarse_data (JaggedTensor) – a JaggedTensor of shape [B, -1, *] of downsampled data.

  • -
  • coarse_grid (GridBatch) – the downsampled grid batch.

  • -
-
-
-
- -
-
-property mutable
-

Whether the grid is mutable.

-
- -
-
-neighbor_indexes(self: fvdb.GridBatch, ijk: fvdb.JaggedTensor, extent: int, bitshift: int = 0) fvdb.JaggedTensor
-
- -
-
-property num_bytes
-

A [num_grids] tensor of the number of bytes used by each grid in this batch.

-
- -
-
-property num_enabled_voxels
-

An integer tensor containing the number of enabled voxels per grid indexed by this batch. If this grid is not mutable, this will be the same as num_voxels.

-
- -
-
-num_enabled_voxels_at(self: fvdb.GridBatch, arg0: int) int
-

Get the number of enabled voxels in the bi^th grid in the batch. If this grid isn’t mutable, this returns the same value as num_voxels_at.

-
- -
-
-property num_leaf_nodes
-

A [num_grids] tensor of the number of leaf nodes used by each grid in this batch.

-
- -
-
-property num_voxels
-

An integer tensor containing the number of voxels per grid indexed by this batch.

-
- -
-
-num_voxels_at(self: fvdb.GridBatch, arg0: int) int
-

Get the number of voxels in the bi^th grid in the batch.

-
- -
-
-origin_at(self: fvdb.GridBatch, arg0: int) torch.Tensor
-

Get the origin of the bi^th grid in the batch.

-
- -
-
-property origins
-

A [num_grids, 3] tensor of world space origins for each grid in this batch.

-
- -
-
-points_in_active_voxel(self: fvdb.GridBatch, points: fvdb.JaggedTensor, ignore_disabled: bool = False) fvdb.JaggedTensor
-

Given a set of points, return a JaggedTensor of booleans indicating which points are in active voxels.

-
-
Parameters:
-
    -
  • points (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of point positions.

  • -
  • ignore_disabled (bool) – Whether to ignore disabled voxels when computing the output.

  • -
-
-
Returns:
-

points_in_active_voxel (JaggedTensor) – A JaggedTensor of shape [num_grids, -1] of booleans indicating which points are in active voxels.

-
-
-
- -
-
-ray_implicit_intersection(self: fvdb.GridBatch, ray_origins: fvdb.JaggedTensor, ray_directions: fvdb.JaggedTensor, grid_scalars: fvdb.JaggedTensor, eps: float = 0.0) fvdb.JaggedTensor
-
- -
-
-read_from_dense(self: fvdb.GridBatch, dense_data: torch.Tensor, dense_origins: fvdb.Vec3iBatch = tensor([0, 0, 0], dtype=torch.int32)) fvdb.JaggedTensor
-

Read the data in a dense tensor into a JaggedTensor indexed by this batch of grids. Non-indexed values are ignored.

-
-
Parameters:
-
    -
  • dense_data (torch.Tensor) – A tensor of shape [num_grids, width, height, depth, *] of values to be read from.

  • -
  • dense_origins (list of floats) – The ijk coordinate corresponding to dense_data[*, 0, 0, 0].

  • -
-
-
Returns:
-

sparse_data (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, *] of values indexed by this grid batch.

-
-
-
- -
-
-sample_bezier(self: fvdb.GridBatch, points: fvdb.JaggedTensor, voxel_data: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-sample_bezier_with_grad(self: fvdb.GridBatch, points: fvdb.JaggedTensor, voxel_data: fvdb.JaggedTensor) list[fvdb.JaggedTensor]
-
- -
-
-sample_trilinear(self: fvdb.GridBatch, points: fvdb.JaggedTensor, voxel_data: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-sample_trilinear_with_grad(self: fvdb.GridBatch, points: fvdb.JaggedTensor, voxel_data: fvdb.JaggedTensor) list[fvdb.JaggedTensor]
-
- -
-
-segments_along_rays(self: fvdb.GridBatch, ray_origins: fvdb.JaggedTensor, ray_directions: fvdb.JaggedTensor, max_segments: int, eps: float = 0.0, ignore_masked: bool = False) fvdb.JaggedTensor
-
- -
-
-set_from_dense_grid(self: fvdb.GridBatch, num_grids: int, dense_dims: fvdb.Vec3i, ijk_min: fvdb.Vec3i = tensor([0, 0, 0], dtype=torch.int32), voxel_sizes: fvdb.Vec3dBatchOrScalar = 1.0, origins: fvdb.Vec3dBatch = tensor([0., 0., 0.]), mask: torch.Tensor | None = None) None
-

Set the voxels in this grid batch to a dense grid with shape [num_grids, width, height, depth], otpionally masking out certain voxels

-
-
Parameters:
-
    -
  • num_grids (int) – The number of grids in the batch

  • -
  • dense_dims (triple of ints) – The dimensions of the dense grid [width, height, depth]

  • -
  • ijk_min (triple of ints) – Index space minimum bound of the dense grid.

  • -
  • voxel_sizes (float, list, tensor) – Either a float or triple specifyng the voxel size of all the grids in the batch or a tensor of shape [num_grids, 3] specifying the voxel size for each grid.

  • -
  • origins (float, list, tensor) – Either a float or triple specifyng the world space origin of all the grids in the batch or a tensor of shape [num_grids, 3] specifying the world space origin for each grid.

  • -
  • mask (torch.Tensor) – A tensor of shape [num_grids, width, height, depth] of booleans indicating which voxels to include/exclude.

  • -
-
-
-
- -
-
-set_from_ijk(self: fvdb.GridBatch, ijk: fvdb.JaggedTensor, pad_min: fvdb.Vec3i = tensor([0, 0, 0], dtype=torch.int32), pad_max: fvdb.Vec3i = tensor([0, 0, 0], dtype=torch.int32), voxel_sizes: fvdb.Vec3dBatchOrScalar = 1.0, origins: fvdb.Vec3dBatch = tensor([0., 0., 0.])) None
-

Set the voxels in this grid batch to those specified by a given set of ijk coordinates (with optional padding)

-
-
Parameters:
-
    -
  • ijk (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of ijk coordinates.

  • -
  • pad_min (triple of ints) – Index space minimum bound of the padding region.

  • -
  • pad_max (triple of ints) – Index space maximum bound of the padding region.

  • -
  • voxel_sizes (float, list, tensor) – Either a float or triple specifyng the voxel size of all the grids in the batch or a tensor of shape [num_grids, 3] specifying the voxel size for each grid.

  • -
  • origins (float, list, tensor) – Either a float or triple specifyng the world space origin of all the grids in the batch or a tensor of shape [num_grids, 3] specifying the world space origin for each grid.

  • -
-
-
-
- -
-
-set_from_mesh(self: fvdb.GridBatch, mesh_vertices: fvdb.JaggedTensor, mesh_faces: fvdb.JaggedTensor, voxel_sizes: fvdb.Vec3dBatchOrScalar = 1.0, origins: fvdb.Vec3dBatch = tensor([0, 0, 0], dtype=torch.int32)) None
-

Set the voxels in this grid batch to those which intersect a given triangle mesh

-
-
Parameters:
-
    -
  • mesh_vertices (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of mesh vertex positions.

  • -
  • mesh_faces (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of integer indexes into mesh_vertices specifying the faces of each mesh.

  • -
  • voxel_sizes (float, list, tensor) – Either a float or triple specifyng the voxel size of all the grids in the batch or a tensor of shape [num_grids, 3] specifying the voxel size for each grid.

  • -
  • origins (float, list, tensor) – Either a float or triple specifyng the world space origin of all the grids in the batch or a tensor of shape [num_grids, 3] specifying the world space origin for each grid.

  • -
-
-
-
- -
-
-set_from_nearest_voxels_to_points(self: fvdb.GridBatch, points: fvdb.JaggedTensor, voxel_sizes: fvdb.Vec3dBatchOrScalar = 1.0, origins: fvdb.Vec3dBatch = tensor([0., 0., 0.])) None
-

Set the voxels in this grid batch to the nearest voxel to each point in a given point cloud

-
-
Parameters:
-
    -
  • points (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of point positions.

  • -
  • voxel_sizes (float, list, tensor) – Either a float or triple specifyng the voxel size of all the grids in the batch or a tensor of shape [num_grids, 3] specifying the voxel size for each grid.

  • -
  • origins (float, list, tensor) – Either a float or triple specifyng the world space origin of all the grids in the batch or a tensor of shape [num_grids, 3] specifying the world space origin for each grid.

  • -
-
-
-
- -
-
-set_from_points(self: fvdb.GridBatch, points: fvdb.JaggedTensor, pad_min: fvdb.Vec3i = tensor([0, 0, 0], dtype=torch.int32), pad_max: fvdb.Vec3i = tensor([0, 0, 0], dtype=torch.int32), voxel_sizes: fvdb.Vec3dBatchOrScalar = 1.0, origins: fvdb.Vec3dBatch = tensor([0, 0, 0], dtype=torch.int32)) None
-

Set the voxels in this grid batch to those which contain a point in a given point cloud (with optional padding)

-
-
Parameters:
-
    -
  • points (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of point positions.

  • -
  • pad_min (triple of ints) – Index space minimum bound of the padding region.

  • -
  • pad_max (triple of ints) – Index space maximum bound of the padding region.

  • -
  • mesh_faces (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of integer indexes into mesh_vertices specifying the faces of each mesh.

  • -
  • voxel_sizes (float, list, tensor) – Either a float or triple specifyng the voxel size of all the grids in the batch or a tensor of shape [num_grids, 3] specifying the voxel size for each grid.

  • -
  • origins (float, list, tensor) – Either a float or triple specifyng the world space origin of all the grids in the batch or a tensor of shape [num_grids, 3] specifying the world space origin for each grid.

  • -
-
-
-
- -
-
-set_global_origin(self: fvdb.GridBatch, origin: fvdb.Vec3d) None
-

Set the origin of all grids in this batch.

-
-
Parameters:
-

origin (list of floats) – The new global origin of this batch of grids.

-
-
-
- -
-
-set_global_voxel_size(self: fvdb.GridBatch, voxel_size: fvdb.Vec3dOrScalar) None
-

Set the voxel size of all grids in this batch.

-
-
Parameters:
-

voxel_size (list of floats) – The new global voxel size of this batch of grids.

-
-
-
- -
-
-sparse_conv_halo(self: fvdb.GridBatch, input: fvdb.JaggedTensor, weight: torch.Tensor, variant: int = 8) fvdb.JaggedTensor
-

Perform in-grid convolution using fast Halo Buffer method. Currently only supports 3x3x3 kernels.

-
-
Parameters:
-
    -
  • input (JaggedTensor) – A JaggedTensor of shape [B, -1, *] containing features associated with this batch of grids.

  • -
  • weight (torch.Tensor) – A tensor of shape [O, I, 3, 3, 3] containing the convolution kernel.

  • -
  • variant (int) – Which variant of the Halo Buffer method to use. Currently 8 and 64 are supported.

  • -
-
-
Returns:
-

out (JaggedTensor) – a JaggedTensor of shape [B, -1, *] of convolved data.

-
-
-
- -
-
-sparse_conv_kernel_map(self: fvdb.GridBatch, kernel_size: fvdb.Vec3iOrScalar, stride: fvdb.Vec3iOrScalar, target_grid: fvdb.GridBatch | None = None) tuple[fvdb.SparseConvPackInfo, fvdb.GridBatch]
-
- -
-
-splat_bezier(self: fvdb.GridBatch, points: fvdb.JaggedTensor, points_data: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-splat_trilinear(self: fvdb.GridBatch, points: fvdb.JaggedTensor, points_data: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-subdivide(self: fvdb.GridBatch, subdiv_factor: fvdb.Vec3iOrScalar, data: fvdb.JaggedTensor, mask: fvdb.JaggedTensor | None = None, fine_grid: fvdb.GridBatch | None = None) tuple[fvdb.JaggedTensor, fvdb.GridBatch]
-

Subdivide the grid batch and associated data tensor into a finer GridBatch and data tensor using nearest neighbor sampling. -Each voxel [i, j, k] in this grid batch maps to voxels [i * subdivFactor, j * subdivFactor, k * subdivFactor] in the fine batch. -Each data value in the subdividided data tensor inherits its parent value

-
-
Parameters:
-
    -
  • subdiv_factor (int or 3-tuple of ints) – How much to subdivide by (i,e, (2,2,2) means subdivide each voxel into 2^3 voxels).

  • -
  • data (JaggedTensor) – A JaggedTensor of shape [B, -1, *] containing data associated with this batch of grids.

  • -
  • mask (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of booleans indicating which voxels to subdivide.

  • -
  • fine_grid (GridBatch) – An optional fine grid used to specify the output. This is mainly used -for memory efficiency so you can chache grids. If you don’t pass it in, we’ll just create it for you.

  • -
-
-
Returns:
-
    -
  • fine_data (JaggedTensor) – A JaggedTensor of shape [B, -1, *] of data associated with the fine grid batch.

  • -
  • fine_grid (GridBatch) – A GridBatch representing the subdivided version of this grid batch.

  • -
-
-
-
- -
-
-subdivided_grid(self: fvdb.GridBatch, subdiv_factor: fvdb.Vec3iOrScalar, mask: fvdb.JaggedTensor | None = None) fvdb.GridBatch
-

Subdivide the grid batch into a finer grid batch. -Each voxel [i, j, k] in this grid batch maps to voxels [i * subdivFactor, j * subdivFactor, k * subdivFactor] in the fine batch.

-
-
Parameters:
-
    -
  • subdiv_factor (int or 3-tuple of ints) – How much to subdivide by (i,e, (2,2,2) means subdivide each voxel into 2^3 voxels).

  • -
  • mask (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, 3] of booleans indicating which voxels to subdivide.

  • -
-
-
Returns:
-

subdivided_grid (GridBatch) – A GridBatch representing the subdivided version of this grid batch.

-
-
-
- -
-
-to(*args, **kwargs)
-

Overloaded function.

-
    -
  1. to(self: fvdb._Cpp.GridBatch, device: fvdb._Cpp.TorchDeviceOrString) -> fvdb._Cpp.GridBatch

  2. -
  3. to(self: fvdb._Cpp.GridBatch, to_tensor: torch.Tensor) -> fvdb._Cpp.GridBatch

  4. -
  5. to(self: fvdb._Cpp.GridBatch, to_jtensor: fvdb::JaggedTensor) -> fvdb._Cpp.GridBatch

  6. -
  7. to(self: fvdb._Cpp.GridBatch, to_grid: fvdb._Cpp.GridBatch) -> fvdb._Cpp.GridBatch

  8. -
-
- -
-
-property total_bbox
-

A tensor, total_bbox, of shape [2, 3] where total_bbox = [[bmin_i, bmin_j, bmin_z=k], -[bmax_i, bmax_j, bmax_k]] is the bounding box such that bmin <= ijk < bmax for all voxels -ijk in the batch.

-
- -
-
-property total_bytes
-

The total number of bytes used by this batch of grids.

-
- -
-
-property total_enabled_voxels
-

The total number of enabled voxels indexed by this batch of grids.

-
- -
-
-property total_leaf_nodes
-

The total number of leaf nodes used by this batch of grids.

-
- -
-
-property total_voxels
-

The total number of voxels indexed by this batch of grids.

-
- -
-
-uniform_ray_samples(self: fvdb.GridBatch, ray_origins: fvdb.JaggedTensor, ray_directions: fvdb.JaggedTensor, t_min: fvdb.JaggedTensor, t_max: fvdb.JaggedTensor, step_size: float, cone_angle: float = 0.0, include_end_segments: bool = True, return_midpoints: bool = False, eps: float = 0.0) fvdb.JaggedTensor
-
- -
-
-property viz_edge_network
-

A pair of JaggedTensors (gv, ge) of shape [num_grids, -1, 3] and [num_grids, -1, 2] where gv are the corner positions of each voxel and ge are edge indices indexing into gv. This property is useful for visualizing the grid.

-
- -
-
-voxel_size_at(self: fvdb.GridBatch, arg0: int) torch.Tensor
-

Get the voxel size of the bi^th grid in the batch.

-
- -
-
-property voxel_sizes
-

A [num_grids, 3] tensor of voxel sizes for each grid in this batch.

-
- -
-
-voxels_along_rays(self: fvdb.GridBatch, ray_origins: fvdb.JaggedTensor, ray_directions: fvdb.JaggedTensor, max_voxels: int, eps: float = 0.0, return_ijk: bool = True, cumulative: bool = False) list[fvdb.JaggedTensor]
-
- -
-
-world_to_grid(self: fvdb.GridBatch, points: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-property world_to_grid_matrices
-

A [num_grids, 4, 4] tensor of the world to grid transformation matrices for each grid in this batch.

-
- -
-
-write_to_dense(self: fvdb.GridBatch, sparse_data: fvdb.JaggedTensor, min_coord: fvdb.Vec3iBatch | None = None, grid_size: fvdb.Vec3i | None = None) torch.Tensor
-

Read the data in a tensor indexed by this batch of grids into a dense tensor, setting non indexed values to zero.

-
-
Parameters:
-
    -
  • sparse_data (JaggedTensor) – A JaggedTensor of shape [num_grids, -1, *] of values indexed by this grid batch.

  • -
  • min_coord (list of ints) – Index space minimum bound of the dense grid.

  • -
  • grid_size (list of ints) – The dimensions of the dense grid to read into [width, height, depth]

  • -
-
-
Returns:
-

dense_data (torch.Tensor) – A tensor of shape [num_grids, width, height, depth, *] of values indexed by this grid batch.

-
-
-
- -
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/api/jagged_tensor.html b/documentation/fvdb/api/jagged_tensor.html deleted file mode 100644 index c0872065a..000000000 --- a/documentation/fvdb/api/jagged_tensor.html +++ /dev/null @@ -1,520 +0,0 @@ - - - - - - - - - JaggedTensor — fVDB documentation - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

JaggedTensor

-
-
-class fvdb.JaggedTensor
-
-
-abs(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-abs_(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-ceil(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-ceil_(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-clone(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-cpu(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-cuda(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-detach(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-property device
-
- -
-
-double(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-property dtype
-
- -
-
-property edim
-

]).

-
-
Type:
-

The number of dimensions of each element indexed by this JaggedTensor. Same as len(jdata.shape[1

-
-
-
- -
-
-property eshape
-

].

-
-
Type:
-

The shape each element indexed by this JaggedTensor. Same as jdata.shape[1

-
-
-
- -
-
-float(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-floor(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-floor_(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-static from_data_and_indices(arg0: torch.Tensor, arg1: torch.Tensor, arg2: int) fvdb.JaggedTensor
-

Initialize jagged tensor with ldim=1 from data and indices.

-
-
Parameters:
-
    -
  • data (torch.Tensor) – The data of the JaggedTensor.

  • -
  • indices (torch.Tensor) – The indices indicating the batch index where the element belong to.

  • -
  • num_tensors (int) – The batch size of the JaggedTensor.

  • -
-
-
Returns:
-

jt (JaggedTensor) – The JaggedTensor with the given data and indices.

-
-
-
- -
-
-static from_data_and_offsets(arg0: torch.Tensor, arg1: torch.Tensor) fvdb.JaggedTensor
-

Initialize jagged tensor with ldim=1 from data and offsets.

-
-
Parameters:
-
    -
  • data (torch.Tensor) – The data of the JaggedTensor.

  • -
  • offsets (torch.Tensor) – The 1-dimensional offsets indicating the start and end of each tensor in data.

  • -
-
-
Returns:
-

jt (JaggedTensor) – The JaggedTensor with the given data and offsets.

-
-
-
- -
-
-static from_data_indices_and_list_ids(data: torch.Tensor, indices: torch.Tensor, list_ids: torch.Tensor, num_tensors: int) fvdb.JaggedTensor
-
- -
-
-static from_data_offsets_and_list_ids(data: torch.Tensor, offsets: torch.Tensor, list_ids: torch.Tensor) fvdb.JaggedTensor
-
- -
-
-int(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-property is_cpu
-

Whether the JaggedTensor is on a CPU device.

-
- -
-
-property is_cuda
-

Whether the JaggedTensor is on a CUDA device.

-
- -
-
-jagged_like(self: fvdb.JaggedTensor, data: torch.Tensor) fvdb.JaggedTensor
-
- -
-
-property jdata
-

The data of the JaggedTensor.

-
- -
-
-jflatten(self: fvdb.JaggedTensor, dim: int = 0) fvdb.JaggedTensor
-
- -
-
-property jidx
-

The indices indicating the batch index where the element belong to.

-
- -
-
-property jlidx
-

The list index of the JaggedTensor with size [num_tensors, ldim].

-
- -
-
-jmax(self: fvdb.JaggedTensor, dim: int = 0, keepdim: bool = False) list[fvdb.JaggedTensor]
-

Returns the maximum of each batch element.

-
-
Returns:
-
    -
  • values (torch.Tensor) – A tensor of size (batch_size, *) containing the maximum value.

  • -
  • indices (torch.Tensor) – A tensor of size (batch_size, *) containing the argmax.

  • -
-
-
-
- -
-
-jmin(self: fvdb.JaggedTensor, dim: int = 0, keepdim: bool = False) list[fvdb.JaggedTensor]
-

Returns the minimum of each batch element.

-
-
Returns:
-
    -
  • values (torch.Tensor) – A tensor of size (batch_size, *) containing the minimum value.

  • -
  • indices (torch.Tensor) – A tensor of size (batch_size, *) containing the argmin.

  • -
-
-
-
- -
-
-property joffsets
-

A [num_tensors + 1] array where each row contains the start and end row index in jdata.

-
- -
-
-jreshape(*args, **kwargs)
-

Overloaded function.

-
    -
  1. jreshape(self: fvdb._Cpp.JaggedTensor, lshape: list[int]) -> fvdb._Cpp.JaggedTensor

  2. -
  3. jreshape(self: fvdb._Cpp.JaggedTensor, lshape: list[list[int]]) -> fvdb._Cpp.JaggedTensor

  4. -
-
- -
-
-jreshape_as(self: fvdb.JaggedTensor, other: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-jsum(self: fvdb.JaggedTensor, dim: int = 0, keepdim: bool = False) fvdb.JaggedTensor
-

Returns the sum of each batch element.

-
-
Returns:
-

sum (torch.Tensor) – A tensor of size (batch_size, *) containing the sum of each batch element, feature dimensions are preserved.

-
-
-
- -
-
-property ldim
-

The list dimension of this JaggedTensor. E.g. a list has ldim 1, a list-of-lists has ldim2 and so on.

-
- -
-
-long(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-property lshape
-

The shape of jdata.

-
- -
-
-property num_tensors
-

The number of tensors in the JaggedTensor.

-
- -
-
-property requires_grad
-
- -
-
-requires_grad_(self: fvdb.JaggedTensor, arg0: bool) fvdb.JaggedTensor
-
- -
-
-rmask(self: fvdb.JaggedTensor, mask: torch.Tensor) fvdb.JaggedTensor
-
- -
-
-round(self: fvdb.JaggedTensor, decimals: int = 0) fvdb.JaggedTensor
-
- -
-
-round_(self: fvdb.JaggedTensor, decimals: int = 0) fvdb.JaggedTensor
-
- -
-
-property rshape
-

The shape of the raw data tensor.

-
- -
-
-sqrt(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-sqrt_(self: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-to(*args, **kwargs)
-

Overloaded function.

-
    -
  1. to(self: fvdb._Cpp.JaggedTensor, arg0: fvdb._Cpp.TorchDeviceOrString) -> fvdb._Cpp.JaggedTensor

  2. -
  3. to(self: fvdb._Cpp.JaggedTensor, arg0: torch.dtype) -> fvdb._Cpp.JaggedTensor

  4. -
  5. to(self: fvdb._Cpp.JaggedTensor, device: fvdb._Cpp.TorchDeviceOrString) -> fvdb._Cpp.JaggedTensor

  6. -
-
- -
-
-type(self: fvdb.JaggedTensor, arg0: torch.dtype) fvdb.JaggedTensor
-
- -
-
-type_as(self: fvdb.JaggedTensor, arg0: fvdb.JaggedTensor) fvdb.JaggedTensor
-
- -
-
-unbind(self: fvdb.JaggedTensor) object
-
- -
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/api/nn.html b/documentation/fvdb/api/nn.html deleted file mode 100644 index c6ec85f96..000000000 --- a/documentation/fvdb/api/nn.html +++ /dev/null @@ -1,292 +0,0 @@ - - - - - - - - - fvdb.nn — fVDB documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

fvdb.nn

-

fvdb.nn is a collection of neural network layers to build sparse neural networks.

-
-
-class fvdb.nn.VDBTensor(grid: GridBatch, data: JaggedTensor, kmap: SparseConvPackInfo | None = None)[source]
-

A VDBTensor is a thin wrapper around a GridBatch and its corresponding feature JaggedTensor, conceptually denoting a batch of -sparse tensors along with its topology. -It works as the input and output arguments of fvdb’s neural network layers. -One can simply construct a VDBTensor from a GridBatch and a JaggedTensor, or from a dense tensor using from_dense().

-
- -
-
-class fvdb.nn.MaxPool(kernel_size: int | List[int], stride: int | List[int] | None = None)[source]
-

Applies a 3D max pooling over an input signal.

-
-
Parameters:
-
    -
  • kernel_size – the size of the window to take a max over

  • -
  • stride – the stride of the window. Default value is kernel_size

  • -
-
-
-
-

Note

-

For target voxels that are not covered by any source voxels, the -output feature will be set to zero.

-
-
- -
-
-class fvdb.nn.AvgPool(kernel_size: int | List[int], stride: int | List[int] | None = None)[source]
-

Applies a 3D average pooling over an input signal.

-
-
Parameters:
-
    -
  • kernel_size – the size of the window to take average over

  • -
  • stride – the stride of the window. Default value is kernel_size

  • -
-
-
-
- -
-
-class fvdb.nn.UpsamplingNearest(scale_factor: int | List[int])[source]
-

Upsamples the input by a given scale factor using nearest upsampling.

-
-
Parameters:
-

scale_factor – the upsampling factor

-
-
-
- -
-
-class fvdb.nn.FillFromGrid(default_value: float = 0.0)[source]
-

Fill the content of input vdb-tensor to another grid.

-
-
Parameters:
-

default_value – the default value to fill in the new grid.

-
-
-
- -
-
-class fvdb.nn.SparseConv3d(in_channels: int, out_channels: int, kernel_size: int | Sequence = 3, stride: int | Sequence = 1, bias: bool = True, transposed: bool = False)[source]
-

Applies a 3D convolution over an input signal composed of several input -planes, by performing a sparse convolution on the underlying VDB grid.

-
-
Parameters:
-
    -
  • in_channels – number of channels in the input tensor

  • -
  • out_channels – number of channels produced by the convolution

  • -
  • kernel_size – size of the convolving kernel

  • -
  • stride – stride of the convolution. Default value is 1

  • -
  • bias – if True, adds a learnable bias to the output. Default: True

  • -
  • transposed – if True, uses a transposed convolution operator

  • -
-
-
-
- -
-
-class fvdb.nn.GroupNorm(num_groups: int, num_channels: int, eps: float = 1e-05, affine: bool = True, device=None, dtype=None)[source]
-

Applies Group Normalization over a VDBTensor. -See GroupNorm for detailed information.

-
- -
-
-class fvdb.nn.Linear(in_features: int, out_features: int, bias: bool = True, device=None, dtype=None)[source]
-

Applies a linear transformation to the incoming data: \(y = xA^T + b\).

-
- -
-
-class fvdb.nn.ReLU(inplace: bool = False)[source]
-

Applies the rectified linear unit function element-wise: \(\text{ReLU}(x) = (x)^+ = \max(0, x)\)

-
- -
-
-class fvdb.nn.LeakyReLU(negative_slope: float = 0.01, inplace: bool = False)[source]
-

Applies the element-wise function: \(\text{LeakyReLU}(x) = \max(0, x) + \text{negative\_slope} * \min(0, x)\)

-
- -
-
-class fvdb.nn.SELU(inplace: bool = False)[source]
-

Applies element-wise, \(\text{SELU}(x) = \lambda \left\{ -\begin{array}{lr} -x, & \text{if } x > 0 \\ -\text{negative\_slope} \times e^x - \text{negative\_slope}, & \text{otherwise } -\end{array} -\right.\)

-
- -
-
-class fvdb.nn.SiLU(inplace: bool = False)[source]
-

Applies element-wise, \(\text{SiLU}(x) = x * \sigma(x)\), where \(\sigma(x)\) is the sigmoid function.

-
- -
-
-class fvdb.nn.Tanh(*args, **kwargs)[source]
-

Applies element-wise, \(\text{Tanh}(x) = \tanh(x) = \frac{e^x - e^{-x}} {e^x + e^{-x}}\)

-
- -
-
-class fvdb.nn.Sigmoid(*args, **kwargs)[source]
-

Applies element-wise, \(\text{Sigmoid}(x) = \frac{1}{1 + \exp(-x)}\)

-
- -
-
-class fvdb.nn.Dropout(p: float = 0.5, inplace: bool = False)[source]
-

During training, randomly zeroes some of the elements of the input tensor with probability p -using samples from a Bernoulli distribution. The elements to zero are randomized on every forward call.

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/api/utils.html b/documentation/fvdb/api/utils.html deleted file mode 100644 index 97b919e5f..000000000 --- a/documentation/fvdb/api/utils.html +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - - - - fvdb.utils — fVDB documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

fvdb.utils

-
-
-fvdb.utils.build_ext.FVDBExtension(name, sources, *args, **kwargs)[source]
-

Utility function for creating pytorch extensions that depend on fvdb. You then have access to all fVDB’s internal -headers to program with. Example usage:

-
from fvdb.utils import FVDBExtension
-
-ext = FVDBExtension(
-    name='my_extension',
-    sources=['my_extension.cpp'],
-    extra_compile_args={'cxx': ['-std=c++17']},
-    libraries=['mylib'],
-)
-
-
-
-
Parameters:
-
    -
  • name – The name of the extension.

  • -
  • sources – The list of source files.

  • -
  • args – Other arguments to pass to torch.utils.cpp_extension.CppExtension().

  • -
  • kwargs – Other keyword arguments to pass to torch.utils.cpp_extension.CppExtension().

  • -
-
-
Returns:
-

A torch.utils.cpp_extension.CppExtension object.

-
-
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/genindex.html b/documentation/fvdb/genindex.html deleted file mode 100644 index e4206d76b..000000000 --- a/documentation/fvdb/genindex.html +++ /dev/null @@ -1,626 +0,0 @@ - - - - - - - - Index — fVDB documentation - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - -

Index

- -
- A - | B - | C - | D - | E - | F - | G - | I - | J - | L - | M - | N - | O - | P - | R - | S - | T - | U - | V - | W - -
-

A

- - - -
- -

B

- - - -
- -

C

- - - -
- -

D

- - - -
- -

E

- - - -
- -

F

- - - -
- -

G

- - - -
- -

I

- - - -
- -

J

- - - -
- -

L

- - - -
- -

M

- - - -
- -

N

- - - -
- -

O

- - - -
- -

P

- - -
- -

R

- - - -
- -

S

- - - -
- -

T

- - - -
- -

U

- - - -
- -

V

- - - -
- -

W

- - - -
- - - -
-
-
- -
- -
-

© Copyright Contributors to the OpenVDB Project.

-
- - Built with Sphinx using a - theme - provided by Read the Docs. - - -
-
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/index.html b/documentation/fvdb/index.html deleted file mode 100644 index 3917d60be..000000000 --- a/documentation/fvdb/index.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - Welcome to fVDB! — fVDB documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Welcome to fVDB!

-

fVDB, inspired by function notation to resemble \(f(VDB)\), is a data structure for encoding and operating on sparse voxel hierarchies of features in PyTorch. -A sparse voxel hierarchy is a coarse-to-fine hierarchy of sparse voxel grids such that every fine voxel is contained within some coarse voxel. -fvdb supports storing PyTorch tensors at the corners and centers of voxels in a hierarchy and enables a number of differentiable operations on these tensors (e.g. trilinear interpolation, splatting, ray tracing).

-_images/fvdb_teaser.png - -

Please refer to the tutorials for examples of how to install and use fVDB to build your own sparse voxel pipelines.

-
-
- -
-

API References

- -
- -
-
-

Indices and tables

- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/objects.inv b/documentation/fvdb/objects.inv deleted file mode 100644 index b80af9fa4..000000000 Binary files a/documentation/fvdb/objects.inv and /dev/null differ diff --git a/documentation/fvdb/search.html b/documentation/fvdb/search.html deleted file mode 100644 index ba81c081f..000000000 --- a/documentation/fvdb/search.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - Search — fVDB documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - - - -
- -
- -
-
-
- -
- -
-

© Copyright Contributors to the OpenVDB Project.

-
- - Built with Sphinx using a - theme - provided by Read the Docs. - - -
-
-
-
-
- - - - - - - - - \ No newline at end of file diff --git a/documentation/fvdb/searchindex.js b/documentation/fvdb/searchindex.js deleted file mode 100644 index 5976180c4..000000000 --- a/documentation/fvdb/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({"alltitles": {"A Simple Convolutional U-Net": [[10, null]], "A simple example": [[5, "a-simple-example"]], "API References": [[4, null]], "Basic Concepts": [[5, null]], "Basic GridBatch Operations": [[6, null]], "Basic example": [[9, "basic-example"]], "Building Sparse Grids": [[7, null]], "Checking if axis aligned cubes intersect a grid": [[6, "checking-if-axis-aligned-cubes-intersect-a-grid"]], "Checking if ijk coordinates are in a grid": [[6, "checking-if-ijk-coordinates-are-in-a-grid"]], "Checking if points are in a grid": [[6, "checking-if-points-are-in-a-grid"]], "Clipping a grid to a bounding box": [[6, "clipping-a-grid-to-a-bounding-box"]], "Concepts": [[9, "concepts"]], "Converting between ijk (grid) coordinates and world coordinates": [[6, "converting-between-ijk-grid-coordinates-and-world-coordinates"]], "Converting ijk values to indexes": [[6, "converting-ijk-values-to-indexes"]], "Converting indexes to ijk values": [[6, "converting-indexes-to-ijk-values"]], "Convolution": [[6, "convolution"]], "Deriving from other grids": [[7, "deriving-from-other-grids"]], "Dual grid": [[7, "dual-grid"]], "Examples": [[9, "examples"]], "From Coordinate Lists": [[7, "from-coordinate-lists"]], "From Dense": [[7, "from-dense"]], "From Meshes": [[7, "from-meshes"]], "From Point Clouds": [[7, "from-point-clouds"]], "Getting indexes of neighbors": [[6, "getting-indexes-of-neighbors"]], "Getting the number of enabled voxels per grid in a batch": [[6, "getting-the-number-of-enabled-voxels-per-grid-in-a-batch"]], "GridBatch": [[0, null]], "GridBatch: Voxel Grids with Attributes": [[5, "gridbatch-voxel-grids-with-attributes"]], "High-level Usage with fvdb.nn": [[6, "high-level-usage-with-fvdb-nn"]], "Indices and tables": [[4, "indices-and-tables"]], "JaggedTensor": [[1, null]], "JaggedTensor and Batching": [[5, "jaggedtensor-and-batching"]], "Loading NanoVDB Files": [[8, "loading-nanovdb-files"]], "Low-level Usage with GridBatch": [[6, "low-level-usage-with-gridbatch"]], "Max/Mean Pooling": [[6, "max-mean-pooling"]], "Mutable Grids": [[9, null]], "Python Serialization": [[8, "python-serialization"]], "Sampling grids": [[6, "sampling-grids"]], "Saving/Loading OpenVDB Files": [[8, "saving-loading-openvdb-files"]], "Sparse Grid I/O": [[8, null]], "Splatting point data to a grid": [[6, "splatting-point-data-to-a-grid"]], "Structure optimization": [[9, "structure-optimization"]], "Subdividing a grid": [[6, "subdividing-a-grid"]], "Subdividing and Coarsening a grid": [[7, "subdividing-and-coarsening-a-grid"]], "Tutorials": [[4, null]], "Volume Rendering": [[11, null]], "Welcome to fVDB!": [[4, null]], "fvdb.nn": [[2, null]], "fvdb.utils": [[3, null]]}, "docnames": ["api/grid_batch", "api/jagged_tensor", "api/nn", "api/utils", "index", "tutorials/basic_concepts", "tutorials/basic_grid_ops", "tutorials/building_grids", "tutorials/io", "tutorials/mutable_grids", "tutorials/simple_unet", "tutorials/volume_rendering"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1}, "filenames": ["api/grid_batch.rst", "api/jagged_tensor.rst", "api/nn.rst", "api/utils.rst", "index.rst", "tutorials/basic_concepts.md", "tutorials/basic_grid_ops.md", "tutorials/building_grids.md", "tutorials/io.md", "tutorials/mutable_grids.md", "tutorials/simple_unet.md", "tutorials/volume_rendering.md"], "indexentries": {"abs() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.abs", false]], "abs_() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.abs_", false]], "address (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.address", false]], "avg_pool() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.avg_pool", false]], "avgpool (class in fvdb.nn)": [[2, "fvdb.nn.AvgPool", false]], "bbox (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.bbox", false]], "bbox_at() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.bbox_at", false]], "ceil() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.ceil", false]], "ceil_() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.ceil_", false]], "clip() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.clip", false]], "clipped_grid() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.clipped_grid", false]], "clone() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.clone", false]], "coarsened_grid() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.coarsened_grid", false]], "contiguous() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.contiguous", false]], "conv_grid() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.conv_grid", false]], "coords_in_active_voxel() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.coords_in_active_voxel", false]], "cpu() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.cpu", false]], "cpu() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.cpu", false]], "cubes_in_grid() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.cubes_in_grid", false]], "cubes_intersect_grid() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.cubes_intersect_grid", false]], "cuda() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.cuda", false]], "cuda() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.cuda", false]], "cum_enabled_voxels (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.cum_enabled_voxels", false]], "cum_enabled_voxels_at() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.cum_enabled_voxels_at", false]], "cum_voxels (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.cum_voxels", false]], "cum_voxels_at() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.cum_voxels_at", false]], "detach() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.detach", false]], "device (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.device", false]], "device (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.device", false]], "disable_ijk() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.disable_ijk", false]], "disabled_mask (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.disabled_mask", false]], "double() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.double", false]], "dropout (class in fvdb.nn)": [[2, "fvdb.nn.Dropout", false]], "dtype (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.dtype", false]], "dual_bbox (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.dual_bbox", false]], "dual_bbox_at() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.dual_bbox_at", false]], "dual_grid() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.dual_grid", false]], "edim (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.edim", false]], "enable_ijk() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.enable_ijk", false]], "enabled_mask (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.enabled_mask", false]], "eshape (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.eshape", false]], "fill_from_grid() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.fill_from_grid", false]], "fillfromgrid (class in fvdb.nn)": [[2, "fvdb.nn.FillFromGrid", false]], "float() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.float", false]], "floor() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.floor", false]], "floor_() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.floor_", false]], "from_data_and_indices() (fvdb.jaggedtensor static method)": [[1, "fvdb.JaggedTensor.from_data_and_indices", false]], "from_data_and_offsets() (fvdb.jaggedtensor static method)": [[1, "fvdb.JaggedTensor.from_data_and_offsets", false]], "from_data_indices_and_list_ids() (fvdb.jaggedtensor static method)": [[1, "fvdb.JaggedTensor.from_data_indices_and_list_ids", false]], "from_data_offsets_and_list_ids() (fvdb.jaggedtensor static method)": [[1, "fvdb.JaggedTensor.from_data_offsets_and_list_ids", false]], "fvdbextension() (in module fvdb.utils.build_ext)": [[3, "fvdb.utils.build_ext.FVDBExtension", false]], "grid_count (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.grid_count", false]], "grid_to_world() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.grid_to_world", false]], "grid_to_world_matrices (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.grid_to_world_matrices", false]], "gridbatch (class in fvdb)": [[0, "fvdb.GridBatch", false]], "groupnorm (class in fvdb.nn)": [[2, "fvdb.nn.GroupNorm", false]], "ijk (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.ijk", false]], "ijk_enabled (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.ijk_enabled", false]], "ijk_to_index() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.ijk_to_index", false]], "ijk_to_inv_index() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.ijk_to_inv_index", false]], "int() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.int", false]], "is_contiguous() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.is_contiguous", false]], "is_cpu (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.is_cpu", false]], "is_cuda (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.is_cuda", false]], "is_same() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.is_same", false]], "jagged_like() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.jagged_like", false]], "jagged_like() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.jagged_like", false]], "jaggedtensor (class in fvdb)": [[1, "fvdb.JaggedTensor", false]], "jdata (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.jdata", false]], "jflatten() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.jflatten", false]], "jidx (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.jidx", false]], "jidx (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.jidx", false]], "jlidx (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.jlidx", false]], "jmax() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.jmax", false]], "jmin() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.jmin", false]], "joffsets (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.joffsets", false]], "joffsets (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.joffsets", false]], "jreshape() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.jreshape", false]], "jreshape_as() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.jreshape_as", false]], "jsum() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.jsum", false]], "ldim (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.ldim", false]], "leakyrelu (class in fvdb.nn)": [[2, "fvdb.nn.LeakyReLU", false]], "linear (class in fvdb.nn)": [[2, "fvdb.nn.Linear", false]], "long() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.long", false]], "lshape (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.lshape", false]], "marching_cubes() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.marching_cubes", false]], "max_grids_per_batch (fvdb.gridbatch attribute)": [[0, "fvdb.GridBatch.max_grids_per_batch", false]], "max_pool() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.max_pool", false]], "maxpool (class in fvdb.nn)": [[2, "fvdb.nn.MaxPool", false]], "mutable (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.mutable", false]], "neighbor_indexes() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.neighbor_indexes", false]], "num_bytes (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.num_bytes", false]], "num_enabled_voxels (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.num_enabled_voxels", false]], "num_enabled_voxels_at() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.num_enabled_voxels_at", false]], "num_leaf_nodes (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.num_leaf_nodes", false]], "num_tensors (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.num_tensors", false]], "num_voxels (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.num_voxels", false]], "num_voxels_at() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.num_voxels_at", false]], "origin_at() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.origin_at", false]], "origins (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.origins", false]], "points_in_active_voxel() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.points_in_active_voxel", false]], "ray_implicit_intersection() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.ray_implicit_intersection", false]], "read_from_dense() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.read_from_dense", false]], "relu (class in fvdb.nn)": [[2, "fvdb.nn.ReLU", false]], "requires_grad (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.requires_grad", false]], "requires_grad_() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.requires_grad_", false]], "rmask() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.rmask", false]], "round() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.round", false]], "round_() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.round_", false]], "rshape (fvdb.jaggedtensor property)": [[1, "fvdb.JaggedTensor.rshape", false]], "sample_bezier() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.sample_bezier", false]], "sample_bezier_with_grad() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.sample_bezier_with_grad", false]], "sample_trilinear() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.sample_trilinear", false]], "sample_trilinear_with_grad() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.sample_trilinear_with_grad", false]], "segments_along_rays() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.segments_along_rays", false]], "selu (class in fvdb.nn)": [[2, "fvdb.nn.SELU", false]], "set_from_dense_grid() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.set_from_dense_grid", false]], "set_from_ijk() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.set_from_ijk", false]], "set_from_mesh() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.set_from_mesh", false]], "set_from_nearest_voxels_to_points() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.set_from_nearest_voxels_to_points", false]], "set_from_points() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.set_from_points", false]], "set_global_origin() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.set_global_origin", false]], "set_global_voxel_size() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.set_global_voxel_size", false]], "sigmoid (class in fvdb.nn)": [[2, "fvdb.nn.Sigmoid", false]], "silu (class in fvdb.nn)": [[2, "fvdb.nn.SiLU", false]], "sparse_conv_halo() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.sparse_conv_halo", false]], "sparse_conv_kernel_map() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.sparse_conv_kernel_map", false]], "sparseconv3d (class in fvdb.nn)": [[2, "fvdb.nn.SparseConv3d", false]], "splat_bezier() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.splat_bezier", false]], "splat_trilinear() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.splat_trilinear", false]], "sqrt() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.sqrt", false]], "sqrt_() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.sqrt_", false]], "subdivide() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.subdivide", false]], "subdivided_grid() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.subdivided_grid", false]], "tanh (class in fvdb.nn)": [[2, "fvdb.nn.Tanh", false]], "to() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.to", false]], "to() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.to", false]], "total_bbox (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.total_bbox", false]], "total_bytes (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.total_bytes", false]], "total_enabled_voxels (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.total_enabled_voxels", false]], "total_leaf_nodes (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.total_leaf_nodes", false]], "total_voxels (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.total_voxels", false]], "type() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.type", false]], "type_as() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.type_as", false]], "unbind() (fvdb.jaggedtensor method)": [[1, "fvdb.JaggedTensor.unbind", false]], "uniform_ray_samples() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.uniform_ray_samples", false]], "upsamplingnearest (class in fvdb.nn)": [[2, "fvdb.nn.UpsamplingNearest", false]], "vdbtensor (class in fvdb.nn)": [[2, "fvdb.nn.VDBTensor", false]], "viz_edge_network (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.viz_edge_network", false]], "voxel_size_at() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.voxel_size_at", false]], "voxel_sizes (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.voxel_sizes", false]], "voxels_along_rays() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.voxels_along_rays", false]], "world_to_grid() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.world_to_grid", false]], "world_to_grid_matrices (fvdb.gridbatch property)": [[0, "fvdb.GridBatch.world_to_grid_matrices", false]], "write_to_dense() (fvdb.gridbatch method)": [[0, "fvdb.GridBatch.write_to_dense", false]]}, "objects": {"fvdb": [[0, 0, 1, "", "GridBatch"], [1, 0, 1, "", "JaggedTensor"]], "fvdb.GridBatch": [[0, 1, 1, "", "address"], [0, 2, 1, "", "avg_pool"], [0, 1, 1, "", "bbox"], [0, 2, 1, "", "bbox_at"], [0, 2, 1, "", "clip"], [0, 2, 1, "", "clipped_grid"], [0, 2, 1, "", "coarsened_grid"], [0, 2, 1, "", "contiguous"], [0, 2, 1, "", "conv_grid"], [0, 2, 1, "", "coords_in_active_voxel"], [0, 2, 1, "", "cpu"], [0, 2, 1, "", "cubes_in_grid"], [0, 2, 1, "", "cubes_intersect_grid"], [0, 2, 1, "", "cuda"], [0, 1, 1, "", "cum_enabled_voxels"], [0, 2, 1, "", "cum_enabled_voxels_at"], [0, 1, 1, "", "cum_voxels"], [0, 2, 1, "", "cum_voxels_at"], [0, 1, 1, "", "device"], [0, 2, 1, "", "disable_ijk"], [0, 1, 1, "", "disabled_mask"], [0, 1, 1, "", "dual_bbox"], [0, 2, 1, "", "dual_bbox_at"], [0, 2, 1, "", "dual_grid"], [0, 2, 1, "", "enable_ijk"], [0, 1, 1, "", "enabled_mask"], [0, 2, 1, "", "fill_from_grid"], [0, 1, 1, "", "grid_count"], [0, 2, 1, "", "grid_to_world"], [0, 1, 1, "", "grid_to_world_matrices"], [0, 1, 1, "", "ijk"], [0, 1, 1, "", "ijk_enabled"], [0, 2, 1, "", "ijk_to_index"], [0, 2, 1, "", "ijk_to_inv_index"], [0, 2, 1, "", "is_contiguous"], [0, 2, 1, "", "is_same"], [0, 2, 1, "", "jagged_like"], [0, 1, 1, "", "jidx"], [0, 1, 1, "", "joffsets"], [0, 2, 1, "", "marching_cubes"], [0, 3, 1, "", "max_grids_per_batch"], [0, 2, 1, "", "max_pool"], [0, 1, 1, "", "mutable"], [0, 2, 1, "", "neighbor_indexes"], [0, 1, 1, "", "num_bytes"], [0, 1, 1, "", "num_enabled_voxels"], [0, 2, 1, "", "num_enabled_voxels_at"], [0, 1, 1, "", "num_leaf_nodes"], [0, 1, 1, "", "num_voxels"], [0, 2, 1, "", "num_voxels_at"], [0, 2, 1, "", "origin_at"], [0, 1, 1, "", "origins"], [0, 2, 1, "", "points_in_active_voxel"], [0, 2, 1, "", "ray_implicit_intersection"], [0, 2, 1, "", "read_from_dense"], [0, 2, 1, "", "sample_bezier"], [0, 2, 1, "", "sample_bezier_with_grad"], [0, 2, 1, "", "sample_trilinear"], [0, 2, 1, "", "sample_trilinear_with_grad"], [0, 2, 1, "", "segments_along_rays"], [0, 2, 1, "", "set_from_dense_grid"], [0, 2, 1, "", "set_from_ijk"], [0, 2, 1, "", "set_from_mesh"], [0, 2, 1, "", "set_from_nearest_voxels_to_points"], [0, 2, 1, "", "set_from_points"], [0, 2, 1, "", "set_global_origin"], [0, 2, 1, "", "set_global_voxel_size"], [0, 2, 1, "", "sparse_conv_halo"], [0, 2, 1, "", "sparse_conv_kernel_map"], [0, 2, 1, "", "splat_bezier"], [0, 2, 1, "", "splat_trilinear"], [0, 2, 1, "", "subdivide"], [0, 2, 1, "", "subdivided_grid"], [0, 2, 1, "", "to"], [0, 1, 1, "", "total_bbox"], [0, 1, 1, "", "total_bytes"], [0, 1, 1, "", "total_enabled_voxels"], [0, 1, 1, "", "total_leaf_nodes"], [0, 1, 1, "", "total_voxels"], [0, 2, 1, "", "uniform_ray_samples"], [0, 1, 1, "", "viz_edge_network"], [0, 2, 1, "", "voxel_size_at"], [0, 1, 1, "", "voxel_sizes"], [0, 2, 1, "", "voxels_along_rays"], [0, 2, 1, "", "world_to_grid"], [0, 1, 1, "", "world_to_grid_matrices"], [0, 2, 1, "", "write_to_dense"]], "fvdb.JaggedTensor": [[1, 2, 1, "", "abs"], [1, 2, 1, "", "abs_"], [1, 2, 1, "", "ceil"], [1, 2, 1, "", "ceil_"], [1, 2, 1, "", "clone"], [1, 2, 1, "", "cpu"], [1, 2, 1, "", "cuda"], [1, 2, 1, "", "detach"], [1, 1, 1, "", "device"], [1, 2, 1, "", "double"], [1, 1, 1, "", "dtype"], [1, 1, 1, "", "edim"], [1, 1, 1, "", "eshape"], [1, 2, 1, "", "float"], [1, 2, 1, "", "floor"], [1, 2, 1, "", "floor_"], [1, 2, 1, "", "from_data_and_indices"], [1, 2, 1, "", "from_data_and_offsets"], [1, 2, 1, "", "from_data_indices_and_list_ids"], [1, 2, 1, "", "from_data_offsets_and_list_ids"], [1, 2, 1, "", "int"], [1, 1, 1, "", "is_cpu"], [1, 1, 1, "", "is_cuda"], [1, 2, 1, "", "jagged_like"], [1, 1, 1, "", "jdata"], [1, 2, 1, "", "jflatten"], [1, 1, 1, "", "jidx"], [1, 1, 1, "", "jlidx"], [1, 2, 1, "", "jmax"], [1, 2, 1, "", "jmin"], [1, 1, 1, "", "joffsets"], [1, 2, 1, "", "jreshape"], [1, 2, 1, "", "jreshape_as"], [1, 2, 1, "", "jsum"], [1, 1, 1, "", "ldim"], [1, 2, 1, "", "long"], [1, 1, 1, "", "lshape"], [1, 1, 1, "", "num_tensors"], [1, 1, 1, "", "requires_grad"], [1, 2, 1, "", "requires_grad_"], [1, 2, 1, "", "rmask"], [1, 2, 1, "", "round"], [1, 2, 1, "", "round_"], [1, 1, 1, "", "rshape"], [1, 2, 1, "", "sqrt"], [1, 2, 1, "", "sqrt_"], [1, 2, 1, "", "to"], [1, 2, 1, "", "type"], [1, 2, 1, "", "type_as"], [1, 2, 1, "", "unbind"]], "fvdb.nn": [[2, 0, 1, "", "AvgPool"], [2, 0, 1, "", "Dropout"], [2, 0, 1, "", "FillFromGrid"], [2, 0, 1, "", "GroupNorm"], [2, 0, 1, "", "LeakyReLU"], [2, 0, 1, "", "Linear"], [2, 0, 1, "", "MaxPool"], [2, 0, 1, "", "ReLU"], [2, 0, 1, "", "SELU"], [2, 0, 1, "", "SiLU"], [2, 0, 1, "", "Sigmoid"], [2, 0, 1, "", "SparseConv3d"], [2, 0, 1, "", "Tanh"], [2, 0, 1, "", "UpsamplingNearest"], [2, 0, 1, "", "VDBTensor"]], "fvdb.utils.build_ext": [[3, 4, 1, "", "FVDBExtension"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "property", "Python property"], "2": ["py", "method", "Python method"], "3": ["py", "attribute", "Python attribute"], "4": ["py", "function", "Python function"]}, "objtypes": {"0": "py:class", "1": "py:property", "2": "py:method", "3": "py:attribute", "4": "py:function"}, "terms": {"": [2, 3, 5, 6, 7, 8, 9, 11], "0": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11], "0000": 6, "00002": 11, "0001": 11, "01": [2, 6, 9], "02": 6, "0200": 6, "025": 6, "03": 6, "0300": 6, "030000001192092896": 6, "04": 6, "0400": 6, "05": [2, 6], "0500": 6, "077": 8, "07999999821186066": 6, "0925484305920792": 11, "1": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11], "10": [8, 11], "100": [8, 9, 11], "1000": [6, 11], "10000": 9, "100_000": 6, "1024": 0, "10578554691520431": 11, "10_000": [5, 6, 10], "11": [6, 11], "110": 8, "11_000": [5, 6, 10], "12": [8, 11], "121": 6, "12456": 6, "128": [9, 10], "13": 11, "14": [6, 11], "14000000059604645": 6, "15": [6, 7, 8, 11], "15000000596046448": 6, "16": [6, 7, 8, 10, 11], "16155": 6, "17": [3, 11], "18": [6, 8, 11], "181": 8, "18503": 6, "19": [6, 11], "1e": [2, 11], "1e10": 11, "1i6qmn": 11, "1j": 11, "2": [0, 5, 6, 7, 8, 10, 11], "20": [9, 11], "200": 6, "2000": 6, "21": 11, "22": [6, 8, 11], "23": [8, 11], "24": [6, 11], "25": [6, 11], "255": 11, "256": [10, 11], "26": 8, "26749": 6, "28": 8, "28209479177387814": 11, "2_000": 6, "2d": 6, "2f": 11, "2x2x2": 0, "3": [0, 2, 5, 6, 7, 8, 9, 10, 11], "30": [8, 11], "3000": 6, "31539156525252005": 11, "32": [7, 8, 10], "326": 8, "33": [6, 8, 11], "34": 8, "34378": 6, "346": 8, "35": [8, 11], "36": 8, "37": 8, "3731763325901154": 11, "38": 8, "39": 8, "3d": [2, 5, 6, 7, 9, 10, 11], "3x3x3": [0, 6], "4": [0, 6, 10, 11], "40": 8, "400000": 11, "4096": 11, "41": 8, "411": 8, "43": 8, "4434": 6, "445305721320277": 11, "45": 11, "453": 8, "4570457994644658": 11, "4614": 6, "47": 8, "47308734787878004": 11, "48": [6, 8], "4886025119029199": 11, "4f": 11, "5": [2, 6, 9, 10, 11], "50": 6, "5033429417967046": 11, "512": 11, "5462742152960396": 11, "55": 6, "5695": 6, "5900435899266435": 11, "6": [6, 8, 10, 11], "6258357354491761": 11, "64": [0, 10], "6690465435572892": 11, "7": [6, 8, 10, 11], "7701307697799304": 11, "8": [0, 6, 8, 10, 11], "8620": 6, "890611442640554": 11, "9": 11, "9461746957575601": 11, "959": 8, "96": [9, 10], "9645": 6, "A": [0, 1, 2, 3, 4, 6, 7, 9], "And": 6, "At": [5, 11], "By": 5, "For": [2, 6, 8, 9], "If": [0, 6, 7, 8, 10], "In": [5, 6, 7, 8, 9, 10, 11], "It": [2, 6, 8, 10], "NOT": 10, "No": 6, "One": [2, 6, 9], "Such": [7, 9], "That": 7, "The": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10], "Then": 6, "There": 6, "These": [5, 6], "To": [5, 6, 7, 9, 10], "_": [6, 7, 9, 10, 11], "__file__": 11, "__getitem__": 11, "__init__": [10, 11], "__len__": 11, "__main__": 11, "__name__": 11, "_cpp": [0, 1], "_depth": 11, "_make_lay": 10, "_slope": 2, "_truncexp": 11, "ab": [1, 6, 9], "abil": 9, "abl": [6, 9], "about": [6, 8], "abov": [5, 6, 7, 9], "abs_": 1, "absolut": 6, "acceler": [5, 11], "access": 3, "accompani": 6, "accomplish": 6, "accordingli": 10, "across": 11, "act": 5, "activ": 0, "ad": [6, 9], "adam": [9, 11], "add": 2, "add_color_quant": 11, "add_scalar_quant": 11, "addit": [5, 9], "addition": [5, 7, 10], "address": 0, "advanc": 9, "affect": 6, "affin": 2, "after": 6, "again": [5, 6, 8], "algorithm": 9, "align": [9, 10], "all": [0, 3, 5, 6, 7, 8, 9, 10], "all_ijk": 11, "allow": 7, "along": [2, 6, 11], "alongsid": 6, "alpha": 9, "alreadi": [0, 7, 8], "also": [5, 6, 7, 9, 10], "although": 7, "an": [0, 2, 5, 6, 7, 8, 9, 11], "analogu": 6, "ani": [0, 2, 6, 8, 9], "anisotrop": 6, "anoth": [0, 2, 6, 7], "api": [7, 9, 10], "append": [6, 10, 11], "appli": [2, 7, 10, 11], "applic": [7, 9], "approach": [6, 7, 9], "appropri": [6, 8], "approxim": 9, "ar": [0, 1, 2, 5, 7, 8, 9, 10], "arbitrari": 5, "architectur": 6, "arg": [0, 1, 2, 3], "arg0": [0, 1], "arg1": 1, "arg2": 1, "argmax": 1, "argmin": 1, "argument": [2, 3, 6, 8, 9, 10], "arithmet": 10, "around": [2, 7, 10], "arrai": [1, 2, 7, 9, 11], "ascertain": 6, "assembl": 7, "assert": [7, 11], "associ": [0, 6, 7], "assum": [6, 7, 8, 11], "astyp": 11, "attempt": 6, "attribut": [6, 7], "autograd": 11, "automat": 8, "avail": [6, 7, 8], "averag": [0, 2], "avg_grid": 6, "avg_norm": 6, "avg_pool": [0, 6], "avgpool": [2, 4], "awai": 6, "axi": [7, 11], "b": [0, 2, 5, 6, 7], "b_grid": 6, "back": [0, 7, 8], "backend": 6, "backward": [9, 11], "bad_mask": 9, "bar": 11, "base": [6, 11], "basi": 11, "basic": [4, 10], "basicblock": 10, "basis_dim": 11, "batch": [0, 1, 2, 7, 8, 9, 11], "batch_siz": [1, 6], "batchnorm": 10, "bbox": [0, 6], "bbox_at": 0, "bbox_origin": 6, "bbox_siz": 6, "becaus": [6, 9, 10], "been": 6, "befor": 6, "begin": [2, 9], "being": 9, "belong": [1, 6], "below": [5, 7], "bernoulli": 2, "between": [5, 8, 9], "bezier": 6, "bg_color": 11, "bi": 0, "bia": [2, 6, 10], "bit": 9, "bitshift": 0, "black": 9, "blind": 8, "block": 10, "block1": 10, "block2": 10, "block3": 10, "block4": 10, "block5": 10, "block6": 10, "block7": 10, "block8": 10, "blosc": 8, "blue": 6, "bmax": 0, "bmax_i": 0, "bmax_j": 0, "bmax_k": 0, "bmin": 0, "bmin_i": 0, "bmin_j": 0, "bmin_z": 0, "bn0": 10, "bn1": 10, "bn2": 10, "bn3": 10, "bn4": 10, "bn_momentum": 10, "bntr4": 10, "bntr5": 10, "bntr6": 10, "bntr7": 10, "bool": [0, 1, 2, 11], "boolean": [0, 6], "border": 0, "both": [6, 7], "bottom": 0, "bound": [0, 9], "bounding_box": 6, "box": [0, 9], "branchfactor": 0, "broad": 10, "buffer": 0, "build": [2, 4, 5, 6, 8, 9, 10, 11], "build_ext": 3, "build_gather_scatt": 6, "built": [5, 7, 10], "buld": 11, "bunch": 10, "byte": 0, "b\u00e9zier": 6, "c": [0, 3, 7, 11], "c0": 11, "c1": 11, "c2": 11, "c3": 11, "c4": 11, "cach": [8, 10], "calcul": 6, "call": [2, 6, 7, 8], "camera": [9, 11], "camera_angle_x": 11, "camera_origin": 11, "can": [0, 2, 5, 6, 8, 9], "car": [5, 9], "carri": 10, "case": [5, 6, 7, 10], "cat": [6, 11], "ceil": 1, "ceil_": 1, "center": [0, 4, 6, 7, 11], "certain": [0, 6], "chach": 0, "chang": [6, 9], "channel": [2, 6, 8, 10], "check": 0, "check_output": 8, "choic": 11, "choos": 7, "chunk": 11, "chunk_siz": 11, "clamp": 11, "class": [0, 1, 2, 5, 6, 7, 8, 10, 11], "clip": [0, 11], "clipped_featur": [0, 6], "clipped_grid": [0, 6], "clone": [1, 6], "cloud": [0, 5, 6], "clrs1": [5, 6], "clrs2": [5, 6], "coars": [0, 4], "coarse_data": 0, "coarse_grid": 0, "coarsen": [0, 6], "coarsened_grid": [0, 7], "coarsening_factor": 0, "coarser": 6, "code": 7, "codec": 8, "collect": 2, "color": [5, 6, 11], "colour": 6, "com": 11, "combin": 11, "command": 8, "compar": [0, 7, 9], "compat": 10, "complet": 6, "compon": 7, "compos": 2, "compress": 8, "comput": [0, 6, 9, 10, 11], "compute_psnr": 11, "concat": 10, "concaten": [5, 6], "concept": 4, "conceptu": [2, 5], "concis": 6, "conduct": 9, "cone_angl": 0, "configur": 11, "consid": [5, 6], "constant": 6, "construct": [2, 5, 6, 7], "contain": [0, 1, 4, 5, 6, 7, 8, 10], "content": 2, "contigu": [0, 11], "contribut": [6, 9], "contriv": 6, "conv": [6, 10], "conv0p1s1": 10, "conv1": 10, "conv1p1s2": 10, "conv2": 10, "conv2p2s2": 10, "conv3d": 6, "conv3p4s2": 10, "conv4p8s2": 10, "conv6": 10, "conv_grid": 0, "conv_vox_norm": 6, "conveni": 6, "convert": [0, 7, 8], "convert_cmd": 8, "convolut": [0, 2, 4, 5], "convolv": [0, 2, 6], "convpackbackend": 6, "convtr4p16s2": 10, "convtr5p8s2": 10, "convtr6p4s2": 10, "convtr7p2s2": 10, "coord": 10, "coordin": [0, 5, 9], "coords_1": 7, "coords_2": 7, "coords_in_active_voxel": [0, 6], "coords_in_grid": 6, "coords_jag": 7, "copi": 0, "core": 5, "corner": [0, 4, 7], "correctli": [6, 9], "correspond": [0, 2, 5, 6, 7, 9, 10], "cosineannealinglr": 11, "could": [7, 9, 10], "cover": [2, 6, 7, 9], "cpp": 3, "cpp_extens": 3, "cppextens": 3, "cpu": [0, 1, 11], "creat": [0, 3, 5, 6, 7, 8, 9, 10, 11], "crop": 6, "ctx": 11, "cube": 0, "cube_cent": 0, "cube_max": 0, "cube_min": 0, "cube_s": 6, "cubes_in_grid": [0, 6], "cubes_intersect_grid": [0, 6], "cuda": [0, 1, 5, 6, 7, 8, 9, 10, 11], "cull": 9, "cum_enabled_voxel": 0, "cum_enabled_voxels_at": 0, "cum_voxel": 0, "cum_voxels_at": 0, "cumul": 0, "current": [0, 11], "cx": 11, "cxx": 3, "cy": 11, "d": [7, 8, 10], "data": [0, 1, 2, 4, 5, 7, 8, 10, 11], "data_path": 11, "data_url": 11, "dataset": 11, "decim": 1, "decod": 8, "decreas": 6, "deep": 6, "def": [6, 9, 10, 11], "default": [0, 2, 6], "default_valu": [0, 2], "defin": [6, 10], "definit": 10, "deg": 11, "deltat": 9, "demonstr": 9, "denot": [2, 11], "dens": [0, 2, 5, 6], "dense_data": [0, 7], "dense_data_back": 7, "dense_dim": [0, 7, 9], "dense_origin": 0, "densiti": 11, "depend": 3, "depth": [0, 9, 11], "depth_gt": 11, "depth_img": 11, "depth_mask": 11, "deriv": 6, "desc": 11, "describ": 6, "design": 9, "detach": [1, 11], "detail": [2, 7], "determin": 6, "devic": [0, 1, 2, 6, 7, 8, 9, 10, 11], "diff_front_o": 11, "diff_front_sh": 11, "diff_right_o": 11, "diff_right_sh": 11, "diff_up_o": 11, "diff_up_sh": 11, "differ": [5, 6, 8, 9, 10], "differenti": [4, 6, 9], "difficulti": 6, "dilat": [7, 10], "dim": [1, 6, 9, 10, 11], "dimens": [0, 1, 5, 6, 10], "dimension": [1, 6, 8], "dir": 11, "direct": 11, "directli": [6, 7, 8], "dirnam": 11, "disabl": [0, 6, 9], "disable_ijk": [0, 6, 9], "disabled_mask": 0, "disappear": 9, "discov": 6, "dist": 11, "distanc": 11, "distribut": 2, "dl_dout": 11, "do": [6, 7, 9, 11], "doc": 10, "document": 8, "doe": 7, "domain": 10, "don": [0, 8], "done": [9, 11], "doubl": [1, 8], "download": 11, "downsampl": [0, 10], "downsample_ratio": 11, "downstream": 9, "doxygen": 8, "dri": 11, "drive": 11, "dropout": [2, 4], "dtype": [0, 1, 2, 6, 8, 11], "dual": 0, "dual_bbox": 0, "dual_bbox_at": 0, "dual_grid": [0, 7, 11], "due": 6, "dure": [2, 9, 10], "e": [0, 1, 2, 4, 5, 7, 8, 9, 11], "each": [0, 1, 5, 6, 7, 9, 10, 11], "earli": 6, "easi": 7, "easier": 9, "easili": [6, 7, 8], "edg": 0, "edim": 1, "effect": 6, "effici": [0, 5], "either": [0, 6, 7], "element": [1, 2, 5, 6, 7], "els": [6, 11], "empti": 11, "empty_cach": 11, "enabl": [0, 4, 9, 11], "enable_ijk": [0, 9], "enable_mask": 9, "enabled_mask": [0, 9], "enclos": [6, 7], "encod": [4, 5, 11], "end": [1, 2, 5], "enjoi": 10, "ensur": 7, "entir": [6, 9], "entri": 9, "enumer": [6, 11], "ep": [0, 2], "epoch": 11, "equal": 6, "error": 11, "eshap": 1, "eta_min": 11, "eval_sh_bas": 11, "evalu": 11, "evaluate_density_and_color": 11, "even": 9, "everi": [0, 2, 4, 5], "exact": 7, "exampl": [3, 4, 6, 7, 8, 10, 11], "except": 0, "exclud": 0, "exclude_bord": 0, "exist": [7, 8, 9, 11], "exp": [2, 11], "expand": 11, "expand_a": 11, "expans": 10, "expect": [6, 8], "expens": 6, "explan": 6, "ext": 3, "extens": 3, "extent": [0, 6], "extern": 9, "extra_compile_arg": 3, "f": [4, 6, 7, 8, 11], "f1": [6, 9], "f2": [6, 9], "f_path": 11, "f_path_depth": 11, "face": 0, "factor": [2, 6, 7], "fals": [0, 1, 2, 6, 10, 11], "familiar": 8, "fast": 0, "feat": 8, "featur": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11], "fed": 7, "feed": 10, "field": 0, "figur": [5, 9, 11], "file": [3, 11], "file_path": 11, "fill": [0, 2], "fill_from_grid": 0, "filled_featur": 0, "fillfromgrid": [2, 4], "final": [5, 10, 11], "find": [6, 8, 10], "findabl": 8, "fine": [0, 4], "fine_data": 0, "fine_grid": [0, 6], "finer": 0, "first": [5, 6, 10], "fl_x": 11, "fl_y": 11, "flag": 8, "flexibl": [9, 10], "float": [0, 1, 2, 6, 7, 8, 9, 10, 11], "float32": 11, "float64": 8, "floor": [1, 7], "floor_": 1, "fmask": 11, "folder": 11, "follow": [6, 7, 8, 9, 10], "forc": [8, 9], "format": 8, "forth": 7, "forward": [2, 10, 11], "found": [7, 9], "four": [5, 10], "frac": 2, "frame": 11, "freeli": 9, "from": [0, 1, 2, 3, 5, 6, 8, 9, 10, 11], "from_data_and_indic": 1, "from_data_and_offset": 1, "from_data_indices_and_list_id": 1, "from_data_offsets_and_list_id": 1, "from_dens": 2, "from_numpi": 11, "front": 9, "full": [7, 9, 10], "full_lik": 11, "fulli": [0, 6, 10], "func": 6, "function": [0, 1, 2, 3, 4, 6, 7, 8, 9, 11], "fvdb": [0, 1, 5, 7, 8, 9, 10, 11], "fvdbextens": [3, 4], "fvdbnn": 6, "fvdbunetbas": 10, "fvnn": 10, "fx": 11, "fy": 11, "g": [1, 4, 5, 7], "gather": 6, "gather_scatt": 6, "gaussian": 6, "ge": 0, "gener": [5, 6], "generate_random_point": 6, "get": [0, 9], "get_point_cloud": 11, "get_rai": 11, "gettempdir": 8, "give": [5, 8], "given": [0, 1, 2, 6, 7, 9], "global": 0, "go": [5, 6], "googl": 11, "gpu": 8, "gradient": 10, "greater": 6, "green": 7, "grid": [0, 2, 4, 10, 11], "grid1": 10, "grid2": 10, "grid4": 10, "grid8": 10, "grid_a1": 7, "grid_a2": 7, "grid_b": 7, "grid_batch": [6, 8], "grid_build": 7, "grid_coarsen": 7, "grid_count": [0, 6], "grid_dual": 7, "grid_prim": 7, "grid_r": 11, "grid_scalar": 0, "grid_siz": 0, "grid_subdivid": 7, "grid_subdivide_coarsen": 7, "grid_to_world": [0, 6, 9, 11], "grid_to_world_matric": [0, 6], "gridbatch": [2, 4, 7, 9, 10, 11], "gridbatch_from_dens": [7, 9], "gridbatch_from_ijk": 7, "gridbatch_from_mesh": [6, 7, 9], "gridbatch_from_nearest_voxels_to_point": 7, "gridbatch_from_point": [5, 6, 7, 8, 10], "ground": 9, "group": 2, "groupnorm": [2, 4], "gt_opac": 9, "guid": 10, "gv": 0, "h": [7, 11], "h5": 11, "ha": [1, 5, 6, 11], "half": [6, 7], "halo": 0, "handl": [5, 7], "harmon": 11, "have": [0, 3, 5, 6, 7, 8, 9], "header": 3, "height": 0, "help": [6, 7, 10], "henc": [9, 11], "here": [6, 7, 8, 9, 10], "herebi": 9, "hierarchi": 4, "high": 5, "hood": 6, "hous": 6, "how": [0, 4, 5, 6, 8, 10], "howev": [6, 9], "html": 8, "http": [8, 11], "huber_loss": 11, "i": [0, 1, 2, 4, 5, 6, 7, 9, 10, 11], "idx": [6, 11], "ignor": [0, 11], "ignore_dis": 0, "ignore_mask": 0, "ij": 11, "ijk": [0, 5, 7, 9, 11], "ijk_coord": 6, "ijk_en": 0, "ijk_max": [0, 6], "ijk_min": [0, 6], "ijk_to_index": [0, 6, 9], "ijk_to_inv_index": [0, 6], "illustr": [5, 6], "imag": [9, 11], "imageio": 11, "implement": [5, 6, 10], "import": [3, 5, 6, 7, 8, 9, 10, 11], "imread": 11, "imshow": 11, "in_channel": [2, 6, 10], "in_featur": 2, "includ": [0, 7, 8, 9], "include_end_seg": 0, "incom": 2, "increas": 6, "independ": 6, "index": [0, 1, 4, 5, 7, 8, 9, 11], "indexerror": 11, "indic": [0, 1, 5, 6, 8, 9], "infer": 10, "infin": 11, "inform": [2, 8, 11], "inherit": 0, "init": 11, "init_dim": 10, "init_resolut": 9, "initi": [1, 6, 7, 11], "inplac": [2, 10], "inplan": 10, "input": [0, 2, 5, 6, 7, 10], "inspect": 8, "inspir": 4, "instal": [4, 8], "instant": 9, "instruct": 8, "int": [0, 1, 2, 5, 6, 9, 10, 11], "int32": [0, 6], "int64": 7, "integ": [0, 5, 6, 7, 9], "intern": [3, 7, 9, 10], "interpol": [4, 5, 6], "intersect": 0, "intrins": 11, "introduc": [7, 10], "inv_sigmoid": 9, "invers": 6, "invert": 6, "involv": 6, "io": 8, "is_contigu": 0, "is_cpu": 1, "is_cuda": 1, "is_sam": 0, "isinst": 11, "isn": 0, "item": [5, 11], "iter": 11, "its": [0, 2, 5, 6, 7, 8, 10], "itself": 6, "j": [0, 11], "jag": [0, 1, 5], "jagged_data": 0, "jagged_lik": [0, 1, 6, 10], "jaggedtensor": [0, 2, 4, 6, 7, 8, 9, 10], "jcat": 10, "jdata": [1, 5, 6, 9, 11], "jflatten": 1, "jidx": [0, 1, 5, 6], "jlidx": 1, "jmax": 1, "jmin": 1, "joffset": [0, 1, 5, 6], "join": [8, 11], "jreshap": 1, "jreshape_a": 1, "json": 11, "jsum": 1, "jt": 1, "just": [0, 6, 7], "k": 0, "kb": 8, "keep": 9, "keepdim": [1, 6, 11], "kernel": [0, 2, 6], "kernel_s": [0, 2, 6, 10], "kernel_weight": 6, "keyword": [3, 10], "kind": 5, "kmap": 2, "knowledg": 6, "known": 5, "kwarg": [0, 1, 2, 3, 8], "l1": 9, "l1_loss": 11, "lambda": 2, "layer": [2, 6, 10], "layout": 6, "ldim": 1, "ldim2": 1, "ldot": 5, "leaf": 0, "leakyrelu": [2, 4], "learn": [6, 10], "learnabl": 2, "left": [0, 2, 6], "lego": 11, "lego_test": 11, "len": [1, 11], "let": [5, 6, 7, 8], "level": [0, 5], "li": [0, 6, 9], "librari": [3, 8, 10], "lie": 6, "like": [6, 7, 8, 9], "line": 8, "linear": [2, 4, 6, 11], "linspac": 11, "list": [0, 1, 2, 3, 5, 6, 11], "list_id": 1, "littl": 6, "ll": 0, "load": 11, "load_car_1_mesh": [5, 6, 7, 9], "load_car_2_mesh": [5, 6, 7, 9], "load_car_3_mesh": 6, "load_car_4_mesh": 6, "load_dragon_mesh": 7, "load_happy_mesh": 7, "locat": 6, "log": [9, 11], "log10": 11, "logic": [6, 7], "logical_and": 11, "long": [1, 7], "look": [5, 9], "loop": 9, "loss": [9, 11], "loss_depth": 11, "loss_rgb": 11, "lr": [2, 9, 11], "lr_o": 11, "lr_schedul": 11, "lr_sh": 11, "lshape": 1, "made": 6, "mai": [6, 7, 11], "main": [5, 11], "mainli": 0, "maintain": 10, "major": 6, "make": [6, 9, 10], "make_ray_grid": 11, "manag": [5, 6], "mani": [5, 6, 9], "manifold": 7, "manipul": 8, "manual_se": 6, "map": [0, 5, 6, 8], "march": 9, "marching_cub": 0, "mark": 9, "mask": [0, 1, 6, 9, 11], "match": [5, 6], "math": [9, 11], "matplotlib": 11, "matric": [0, 6], "max": [0, 2, 9, 11], "max_grid": 6, "max_grids_per_batch": 0, "max_i": 6, "max_j": 6, "max_k": 6, "max_norm": 6, "max_pool": [0, 6], "max_seg": 0, "max_sh_basi": 11, "max_voxel": 0, "maxb": 11, "maximum": [0, 1, 6, 10], "maxpool": [0, 2, 4], "mb": 8, "me": 10, "mean": [0, 5, 9, 11], "member": 10, "memori": [0, 5, 10], "mesh": [0, 6], "mesh_1_f": 7, "mesh_1_v": 7, "mesh_2_f": 7, "mesh_2_v": 7, "mesh_f": 6, "mesh_f_jag": [6, 7, 9], "mesh_fac": 0, "mesh_load_func": 6, "mesh_v": 6, "mesh_v_jag": [6, 7, 9], "mesh_vertic": 0, "meshgrid": 11, "method": [0, 6, 7, 8], "mgrid": 11, "min": [2, 9, 11], "min_coord": 0, "min_i": 6, "min_j": 6, "min_k": 6, "minb": 11, "mini": 5, "minibatch": [5, 6], "minim": 11, "minimimum": 0, "minimum": [0, 1, 6], "minkowskibatchnorm": 10, "minkowskiconvolut": 10, "minkowskiengin": 10, "minkowskirelu": 10, "mismatch": 6, "misordered_ijk": 6, "miss": 11, "mnpwpeioinifmo8qjltju0ds1b": 11, "mode": [5, 6, 7, 9, 11], "model": 10, "modul": [4, 6, 10], "momentum": 10, "more": [6, 7, 9, 10], "most": 6, "mse_loss": 11, "much": [0, 9], "multipl": [6, 11], "must": 6, "mutabl": [0, 4, 6], "my_extens": 3, "mylib": 3, "n": [6, 8, 11], "n_": 5, "n_0": 5, "n_1": 5, "n_center": 11, "n_center_mask": 11, "n_frame": 11, "n_front": 11, "n_front_mask": 11, "n_i": 5, "n_right": 11, "n_right_mask": 11, "n_up": 11, "n_up_mask": 11, "name": [3, 5, 8], "namespac": 10, "nanogrid": 8, "nanovdb": 0, "nanovdb_convert": 8, "nanovdb_howtobuild": 8, "nanovdb_print": 8, "natur": [6, 8], "ndarrai": 11, "nearest": [0, 2, 7], "necessari": 8, "need": [5, 6, 10, 11], "neg": 2, "negative_slop": 2, "neighbor": 0, "neighbor_idx": 6, "neighbor_index": [0, 6, 11], "neighborhood": 5, "neighbour": 6, "nerf_matrix_to_ngp": 11, "nerfacc": 11, "nerfdataset": 11, "net": [4, 6], "network": [2, 6, 7, 10], "neural": [2, 6, 7, 9, 10], "new": [0, 2, 5, 6], "new_pos": 11, "next": 5, "ngp": 9, "nhood": 11, "nm": 6, "nn": [4, 7, 10, 11], "no_grad": 11, "node": 0, "nois": 6, "noisi": 5, "non": [0, 5, 9], "none": [0, 2, 10, 11], "nor": 7, "norm": [6, 11], "norm1": 10, "norm2": 10, "norma": [5, 6], "normal": [2, 6], "notat": 4, "note": [6, 8, 9, 10, 11], "notic": 6, "now": [5, 8, 11], "np": [6, 7, 11], "npy": 11, "nrai": 11, "num_byt": 0, "num_channel": 2, "num_enabled_voxel": [0, 6], "num_enabled_voxels_at": [0, 6], "num_epoch": 11, "num_grid": [0, 7, 9], "num_group": 2, "num_leaf_nod": 0, "num_point": 6, "num_pt": 6, "num_rai": 11, "num_tensor": 1, "num_voxel": [0, 6, 8], "num_voxels_at": [0, 6], "number": [0, 1, 2, 4, 5, 8, 11], "numer": 5, "numpi": [6, 7, 11], "nvdb": 8, "nvox_0": 0, "nvox_1": 0, "nvox_2": 0, "nvox_i": 0, "o": [0, 4, 6, 11], "o_featur": 11, "object": [0, 1, 3, 6, 9, 11], "observ": 9, "obtain": [6, 11], "obviou": 9, "off": 9, "offset": [0, 1, 5, 9, 11], "often": 6, "onc": 9, "one": [5, 6, 7, 9], "ones": [6, 7, 9, 11], "ones_lik": 11, "onindex": 8, "onli": [0, 6, 8, 9, 10, 11], "onto": [5, 6, 8], "op": 0, "opac": [9, 11], "open": [8, 11], "oper": [2, 4, 5, 7, 9, 10], "optim": [6, 11], "option": [0, 6, 8, 11], "order": [6, 11], "org": 8, "origin": [0, 6, 7, 8, 9, 11], "origin_at": 0, "other": [0, 1, 3, 6, 8], "other_featur": 0, "other_grid": 0, "otherwis": [0, 2], "otpion": 0, "our": [6, 8, 9, 10], "out": [0, 6, 10, 11], "out_b1p2": 10, "out_b2p4": 10, "out_b3p8": 10, "out_channel": [2, 6, 10], "out_featur": 2, "out_grid": [6, 10], "out_p1": 10, "out_tensor_strid": 10, "out_tim": 9, "outcom": 6, "output": [0, 2, 6, 10], "outsid": 6, "over": [0, 2, 5, 6], "overload": [0, 1, 10], "overview": [5, 8], "overwrit": 8, "own": [4, 6], "p": [2, 6, 8, 11], "pack_info": [9, 11], "packinfo": 9, "pad": 0, "pad_max": 0, "pad_min": 0, "page": 4, "pair": 0, "parallel": 5, "param": 11, "param_group": 11, "paramet": [0, 1, 2, 3, 6, 11], "parent": 0, "part": 8, "particular": 6, "pass": [0, 3, 7, 8], "path": [8, 11], "pattern": 5, "pbar": 11, "pc": 11, "pc_rgb": 11, "pcd_jag": 7, "pcu": [5, 6], "pd_opac": 9, "per": [0, 5, 8], "per_voxel_color": 6, "percept": 10, "perception_exampl": 10, "perform": [0, 2, 6, 7, 9, 10], "perm": 6, "permut": 6, "pictori": 5, "pictur": 7, "piec": 6, "pipelin": 4, "plan": 6, "plane": [2, 10], "pleas": [4, 10], "plot": 11, "plot_everi": 11, "plt": 11, "pmt": 6, "png": 11, "point": [0, 5, 8, 9, 11], "point_cloud_util": [5, 6], "point_render_mod": 11, "points_data": 0, "points_in_active_voxel": [0, 6], "polyscop": [6, 11], "pool": [0, 2], "pool_factor": 0, "pose": [9, 11], "posit": [0, 5, 6, 7], "possibl": 8, "potenti": 6, "practic": [5, 6], "pre": 6, "precis": 8, "predict": 9, "present": 6, "preserv": 1, "previou": 8, "previous": [8, 10], "primal": 7, "primal_grid": 11, "print": [5, 6, 8, 11], "probabl": 2, "problem": 9, "procedur": 9, "process": [5, 7], "produc": [2, 6], "program": 3, "progress": 11, "project": 8, "properti": [0, 1, 6], "provid": [5, 6, 7, 9], "psnr": 11, "psnr_test": 11, "pt": [6, 11], "pt_featur": 11, "pt_o_featur": 11, "pts1": [5, 6], "pts2": [5, 6], "pts_featur": 9, "purpl": [6, 7], "purpos": [6, 9], "py": [7, 8, 9, 10], "pyplot": 11, "pytorch": [3, 4, 7, 8], "quad": 11, "quantiz": 7, "queri": [5, 6, 11], "r": 11, "radiu": 11, "rai": [4, 9, 11], "rais": 11, "rand": [5, 6, 9, 11], "rand_idx_pt": 6, "rand_ijk": 6, "rand_index": 6, "rand_pt": 6, "rand_pts_indic": 6, "randint": [6, 9], "randn": [6, 8, 10], "random": [2, 5, 6, 11], "random_i": 6, "random_ijk": 11, "random_j": 6, "random_k": 6, "random_point": 6, "randomli": [2, 6, 9], "randperm": [6, 11], "rang": [6, 9, 10, 11], "ravel": 11, "raw": 1, "ray_": 11, "ray_color": 11, "ray_color_chunk": 11, "ray_d": 11, "ray_delta_t": 11, "ray_dens": 11, "ray_density_chunk": 11, "ray_dir": 9, "ray_direct": 0, "ray_idx": 11, "ray_implicit_intersect": 0, "ray_interv": 11, "ray_o": 11, "ray_opac": 9, "ray_orig": 9, "ray_origin": 0, "ray_pt": 11, "ray_step_s": 11, "ray_t": 11, "ray_v": 11, "rays_d": 11, "rays_o": 11, "rays_per_batch": 11, "re": [5, 6, 10, 11], "read": [0, 8, 11], "read_from_dens": 0, "realli": 6, "reason": 6, "rebuild": 5, "receiv": 6, "recommend": 6, "recov": [5, 9], "rectifi": 2, "red": 6, "refer": [0, 6, 7, 9, 10], "reference_grid": 6, "region": 0, "register_curve_network": 11, "register_point_cloud": 11, "relat": [6, 8], "relu": [2, 4, 10], "remov": 6, "render": [4, 9], "render_opac": 9, "reorder": 6, "replac": 11, "repositori": 8, "repository_root": 11, "repres": [0, 5, 6], "represent": 9, "request": 6, "requir": 6, "requires_grad": [1, 9, 11], "requires_grad_": 1, "resembl": 4, "reshap": 11, "resid": 0, "residu": 10, "resolut": [6, 8, 11], "respect": 6, "result": [6, 11], "return": [0, 1, 3, 5, 6, 9, 10, 11], "return_color": 11, "return_ijk": 0, "return_midpoint": 0, "reviv": 9, "rgb": [6, 9, 11], "rgb_est": 11, "rgb_gt": 11, "rgb_img": 11, "rgba": 11, "right": [2, 6], "rmask": [1, 9], "rmsprop": 11, "root_path": 11, "round": 1, "round_": 1, "roundtrip": 8, "row": [1, 6], "rshape": [1, 11], "run": 11, "runnabl": 9, "runtimeerror": 11, "said": 7, "same": [0, 1, 5, 6, 7, 8, 9, 10], "sampl": [0, 2, 5, 9], "sample_bezi": [0, 6], "sample_bezier_with_grad": 0, "sample_point": 5, "sample_pt": 6, "sample_trilinear": [0, 5, 6, 9, 11], "sample_trilinear_with_grad": 0, "sampled_color": [5, 6], "save": 10, "save_for_backward": 11, "saved_nvdb": 8, "saved_tensor": 11, "scalar": [5, 6, 8], "scale": [2, 6, 8, 11], "scale_factor": 2, "scatter": 6, "scenario": 9, "scene": 11, "scene_aabb": 11, "schedul": 11, "script": 11, "search": 4, "second": [6, 8], "section": [6, 7, 8], "see": [2, 6, 9], "segments_along_rai": 0, "select": 6, "self": [0, 1, 10, 11], "selu": [2, 4], "sens": 6, "separ": 5, "sequenc": 2, "sequenti": 10, "set": [0, 2, 5, 6, 7, 9, 11], "set_from_dense_grid": [0, 11], "set_from_ijk": 0, "set_from_mesh": 0, "set_from_nearest_voxels_to_point": 0, "set_from_point": [0, 6, 7], "set_global_origin": 0, "set_global_voxel_s": 0, "set_postfix": 11, "sever": [2, 6, 7, 10], "sh": 11, "sh_c0": 11, "sh_c1": 11, "sh_c2": 11, "sh_c3": 11, "sh_c4": 11, "sh_featur": 11, "shape": [0, 1, 5, 6, 7, 9, 11], "share": 7, "share_link": 11, "shift": 7, "should": [6, 10], "show": [7, 8, 11], "shown": 7, "shuffl": 6, "shuffled_featur": 6, "shuffled_ijk": 6, "side": 6, "sidelength": [5, 6], "sigma": [2, 9], "sigmoid": [2, 4, 9, 11], "signal": 2, "silu": [2, 4], "similar": [6, 7, 9, 10], "similarli": 5, "simpl": [4, 6, 7, 8, 9, 11], "simpler": 9, "simpli": [2, 6, 10], "simplifi": 9, "simplist": 10, "sinc": 8, "singl": [5, 8, 9], "sinput": 10, "size": [0, 1, 2, 6, 7, 8], "smaller": 6, "smoothli": 10, "snippet": 9, "so": [0, 1, 6], "sole": 9, "solv": 9, "some": [2, 4, 5, 6, 7, 9], "someth": 6, "soup": 7, "sourc": [2, 3, 7], "soutput": 10, "space": [0, 6, 9], "spars": [0, 2, 4, 5, 6, 9, 10, 11], "sparse_conv_3d": 6, "sparse_conv_halo": 0, "sparse_conv_kernel_map": [0, 6], "sparse_conv_packinfo": 6, "sparse_data": [0, 7], "sparseconv3d": [2, 4, 6, 10], "sparseconvpackinfo": [0, 2, 6], "sparsiti": 5, "spatial": 6, "specif": 7, "specifi": [0, 6, 7, 8], "specifyng": 0, "speed": 10, "speherical_harmon": 11, "spheric": 11, "splat": [4, 5, 9], "splat_bezi": [0, 6], "splat_trilinear": [0, 5, 6], "split": 8, "sqrt": [1, 11], "sqrt_": 1, "squar": 11, "squeez": 11, "stack": [6, 11], "start": [0, 1, 5, 6, 7, 11], "starting_resolut": 11, "static": 1, "staticmethod": 11, "std": 3, "step": [9, 10, 11], "step_siz": [0, 11], "still": 9, "store": [0, 4, 5, 6, 8, 9], "str": 11, "straightforward": 6, "strategi": 9, "stride": [0, 2, 6, 10], "structur": [4, 5, 6, 11], "structure_optim": 9, "style": 6, "sub_grid": 11, "sub_ind": 9, "subdiv_factor": [0, 6], "subdiv_grid": 6, "subdiv_norm": 6, "subdivfactor": 0, "subdivid": [0, 11], "subdivided_grid": [0, 7], "subdividid": 0, "subdivis": [6, 7], "subprocess": 8, "subsampl": 9, "subsequ": 9, "subset": 6, "sum": [0, 1, 11], "super": [10, 11], "suppli": 8, "support": [0, 4, 8, 11], "suppos": 9, "sure": 10, "switch": 9, "system": [6, 8], "t": [0, 2, 8, 9, 11], "t_max": [0, 11], "t_min": 0, "t_threshold": 11, "tackl": 10, "taco1": 8, "taco2": 8, "take": [0, 2, 6, 7, 9, 10, 11], "tan": 11, "tanh": [2, 4], "target": [2, 6], "target_grid": 0, "task": 9, "techniqu": 6, "tempfil": 8, "tensor": [0, 1, 2, 4, 5, 6, 7, 11], "tensor_strid": 10, "tensorpair": 11, "tensortripl": 11, "test": 11, "test_dataset": 11, "text": [2, 5], "th": [0, 5], "than": 6, "thei": [6, 8, 9, 10], "them": [5, 8, 9], "therefor": 6, "thi": [0, 1, 5, 6, 7, 8, 9, 10, 11], "thin": [2, 7, 10], "thing": 6, "those": [0, 5, 6, 9], "though": 6, "three": 6, "thresh": 11, "threshold": 6, "through": 11, "throw": 0, "time": [2, 6, 7, 9], "tmax": 11, "tmin": 11, "tmp": 8, "tmpg9ol5841": 8, "tmpnr7gk0tf": 8, "tmpuwq2r5mx": 8, "tmpwnu7qc_7": 8, "to_dens": 7, "to_grid": 0, "to_jtensor": 0, "to_tensor": 0, "togeth": 6, "tolist": 6, "tool": 8, "topic": 9, "topolog": 6, "topologi": [2, 5, 6, 7, 9, 10], "torch": [0, 1, 3, 5, 6, 7, 8, 9, 10, 11], "torchdeviceorstr": [0, 1], "total": [0, 6, 8, 11], "total_bbox": 0, "total_byt": 0, "total_enabled_voxel": 0, "total_leaf_nod": 0, "total_voxel": [0, 6, 8, 9, 10, 11], "tqdm": 11, "trace": 4, "tradit": 6, "train": [2, 10, 11], "train_dataset": 11, "trang": 11, "tranpos": 6, "transfer": 10, "transform": [0, 2, 6, 11], "transform_matrix": 11, "transforms_": 11, "translat": [5, 7], "transmittancethresh": 9, "transpar": 9, "transpos": [2, 6, 10, 11], "transposed_conv": 6, "transposed_output": 6, "treat": [7, 9], "triangl": [0, 7], "trilinear": [4, 5, 6], "tripl": 0, "triplet": 0, "true": [0, 2, 6, 8, 9, 10, 11], "truth": 9, "try": [6, 8, 9], "tupl": [0, 11], "turn": 9, "tutori": [8, 10], "tv": 11, "tv_loss": 11, "tv_reg": 11, "tv_reg_o": 11, "tv_reg_sh": 11, "twice": 6, "two": [0, 5, 6, 7, 8, 10], "two_random_grid": 8, "two_random_vec3d_grid": 8, "type": [1, 8, 10, 11], "type_a": 1, "u": [4, 6], "unbind": [1, 11], "unbound": 11, "unchang": 9, "unclear": 6, "under": 6, "underli": [0, 2, 9], "unet": 10, "uniform": 5, "uniform_ray_sampl": [0, 11], "union": 11, "uniqu": 7, "unit": [2, 5, 6, 9, 11], "unlik": 5, "unshuffled_featur": 6, "unsqueez": 11, "up": [5, 10], "upon": 5, "upsampl": 2, "upsamplingnearest": [2, 4], "us": [0, 2, 4, 5, 6, 7, 8, 9, 10, 11], "usag": 3, "usp": 11, "usual": 7, "utf": [8, 11], "util": [4, 5, 6, 7, 8, 9], "v": [6, 7, 8, 11], "v1": [6, 9], "v2": [6, 9, 11], "valu": [0, 1, 2, 5, 9], "valueerror": 11, "variant": 0, "variou": 7, "vdb": [0, 2, 4, 8, 9], "vdb_path": 8, "vdbtensor": [2, 4, 6, 7, 10], "vdbtensor_from_dens": 7, "vec3d": [0, 8], "vec3dbatch": 0, "vec3dbatchorscalar": 0, "vec3dorscalar": 0, "vec3i": 0, "vec3ibatch": 0, "vec3iorscalar": 0, "vector": [5, 8], "veri": [6, 7, 9, 10], "verifi": 9, "version": [0, 7, 8], "vertex": [0, 7], "vf": [6, 7, 9], "via": [5, 7, 9], "view": 11, "virtual": 9, "visual": [0, 6, 7, 9], "visuzli": 6, "viz_edge_network": [0, 11], "vn": [5, 6], "volum": [4, 9], "volume_rend": [9, 11], "vox": 11, "vox_color": [5, 6, 11], "vox_ctr": 11, "vox_dens": 11, "vox_ijk": 11, "vox_norm": 6, "vox_origin": 11, "vox_siz": [6, 11], "voxel": [0, 2, 4, 7, 8, 9, 11], "voxel_data": 0, "voxel_ind": 9, "voxel_origin": 11, "voxel_s": [0, 5, 6, 7, 8, 9, 11], "voxel_size_at": 0, "voxels_along_rai": [0, 9], "vstack": 11, "w": [7, 11], "wai": [6, 7, 8, 9], "want": [6, 7, 10], "watertight": 7, "we": [0, 5, 6, 7, 8, 9, 10, 11], "weight": [0, 6], "well": [5, 6, 8], "were": 10, "when": [0, 6, 10], "where": [0, 1, 2, 5, 6], "whether": [0, 1, 6, 9], "which": [0, 5, 6, 7, 8], "while": [6, 7, 8, 9], "white": 9, "whose": 9, "wide": 9, "width": 0, "window": [0, 2], "wise": 2, "wish": 6, "within": [0, 4, 6, 9], "without": [5, 11], "work": [2, 6, 7, 9, 10], "world": [0, 7], "world_coord": 6, "world_space_cent": 6, "world_to_grid": [0, 6], "world_to_grid_matric": 0, "would": 6, "wrap": 6, "wrapper": [2, 6, 7, 10], "write_to_dens": 0, "www": 8, "x": [2, 6, 8, 9, 10, 11], "xa": 2, "xx": 11, "xy": 11, "xyz": [6, 7, 11], "xz": 11, "y": [2, 11], "ye": 6, "yellow": 6, "yet": 7, "yield": 5, "you": [0, 3, 6, 7, 8, 9, 10, 11], "your": [4, 6, 7], "yourself": 7, "yy": 11, "yz": 11, "z": 11, "zero": [0, 2, 11], "zero_grad": [9, 11], "zz": 11}, "titles": ["GridBatch", "JaggedTensor", "fvdb.nn", "fvdb.utils", "Welcome to fVDB!", "Basic Concepts", "Basic GridBatch Operations", "Building Sparse Grids", "Sparse Grid I/O", "Mutable Grids", "A Simple Convolutional U-Net", "Volume Rendering"], "titleterms": {"A": [5, 10], "align": 6, "api": 4, "ar": 6, "attribut": 5, "axi": 6, "basic": [5, 6, 9], "batch": [5, 6], "between": 6, "bound": 6, "box": 6, "build": 7, "check": 6, "clip": 6, "cloud": 7, "coarsen": 7, "concept": [5, 9], "convert": 6, "convolut": [6, 10], "coordin": [6, 7], "cube": 6, "data": 6, "dens": 7, "deriv": 7, "dual": 7, "enabl": 6, "exampl": [5, 9], "file": 8, "from": 7, "fvdb": [2, 3, 4, 6], "get": 6, "grid": [5, 6, 7, 8, 9], "gridbatch": [0, 5, 6], "high": 6, "i": 8, "ijk": 6, "index": 6, "indic": 4, "intersect": 6, "jaggedtensor": [1, 5], "level": 6, "list": 7, "load": 8, "low": 6, "max": 6, "mean": 6, "mesh": 7, "mutabl": 9, "nanovdb": 8, "neighbor": 6, "net": 10, "nn": [2, 6], "number": 6, "o": 8, "openvdb": 8, "oper": 6, "optim": 9, "other": 7, "per": 6, "point": [6, 7], "pool": 6, "python": 8, "refer": 4, "render": 11, "sampl": 6, "save": 8, "serial": 8, "simpl": [5, 10], "spars": [7, 8], "splat": 6, "structur": 9, "subdivid": [6, 7], "tabl": 4, "tutori": 4, "u": 10, "usag": 6, "util": 3, "valu": 6, "volum": 11, "voxel": [5, 6], "welcom": 4, "world": 6}}) \ No newline at end of file diff --git a/documentation/fvdb/tutorials/basic_concepts.html b/documentation/fvdb/tutorials/basic_concepts.html deleted file mode 100644 index 6769938c9..000000000 --- a/documentation/fvdb/tutorials/basic_concepts.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - Basic Concepts — fVDB documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Basic Concepts

-

We give a high-level overview of the main concepts in fVDB. Namely, how fVDB encodes sparse voxel grids with attributes, as well as how fVDB efficiently manages batches with non-uniform numbers of elements.

-
-

GridBatch: Voxel Grids with Attributes

-

At its core, fVDB provides means to efficiently encode mini-batches of sparse voxel grids where each voxel contains arbitrary vector or scalar attributes encoded as torch tensors. In practice, such an encoding is implemented via the GridBatch class.

-

Minibatch2.png

-

A GridBatch is an indexing structure which maps 3D ijk coordinates to integer offsets which can be used to look up attributes in a tensor. The figure below illustrates this process for a GridBatch containing a single grid.

-

gridbatch.png

-

In the figure, the GridBatch acts as an acceleration structure which encodes the sparsity pattern of the grid (also known as the topology) and can translate grid coordinates into offsets into a data tensor.

-

By separating the grid topology and data tensors, the same grid can be used to operate on many different attributes without rebuilding.

-

Every operation in fVDB is built upon this kind of query (e.g. Sparse Convolution uses this query to look up features in the neighborhood of each voxel).

-
-
-

JaggedTensor and Batching

-

Each grid in a GridBatch can have a different number of voxels (e.g. in the mini batch of four cars above, each car has a different number of voxels). This means that unlike the dense case, fVDB needs to handle parallel operations over jagged batches. I.e. batches containing different numbers of elements.

-

To handle jagged batches, fVDB provides a JaggedTensor class. Conceptually, a JaggedTensor is a list of tensors with shapes \([N_0, *], [N_1, *], \ldots, [N_{B-1}, *]\) where \(B\) is the number of elements in the batch, \(N_i\) is the number of elements in the \(i^\text{th}\) batch item and \(*\) is an arbitrary numer of additional dimensions that all match between the tensors. The figure below illustrates such a list of tensors pictorially.

-

jaggedtensor1.png

-

In practice, JaggedTensors are represented in memory by concatenating each tensor in the list into a single jdata (for Jagged Data) tensor of shape \([N_0 + N_1 + \ldots + N_{B-1}, *]\). Additionally, each JaggedTensor stores an additional jidx tensor (for Jagged Indexes) of shape \([N_0 + N_1 + \ldots + N_{B-1}]\) containing one int per element in the jagged tensor. jidx[i] is the batch index of the \(i^\text{th}\) element of jdata. Finally, a JaggedTensor contains a joffsets tensor (for Jagged Offsets) of shape \([B, 2]\) which indicates the start and end positions of the \(i^\text{th}\) tensor in the batch.

-

jaggedtensor4.png

-

Similarly, each GridBatch also has jidx and joffsets corresponding to the batch index of each voxel in the grid, and the start and end offsets of each voxel index in a batch.

-
-
-

A simple example

-

To illustrate the use of GridBatchand JaggedTensor, consider a simple example where we build a grid from a point cloud, splat some values onto the voxels of that grid, and then sample them again using a different set of points.

-

First, we construct a minibatch of grids using the input points. These input points have corresponding color attributes.

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-import torch
-import point_cloud_utils as pcu
-
-# We're going to create a minibatch of two point clouds each of which
-# has a different number of points
-pts1, clrs1 = load_car_1_mesh(mode = "vn")
-pts2, clrs2 = load_car_2_mesh(mode = "vn")
-
-# Creating JaggedTensors: one for points and one for colors
-points = fvdb.JaggedTensor([pts1, pts2])
-colors = fvdb.JaggedTensor([clrs1, clrs2])
-
-# Create a grid where the voxels each have unit sidelength
-grid = fvdb.gridbatch_from_points(points, voxel_sizes=1.0)
-
-# Indexing into a JaggedTensor returns a JaggedTensor
-print(points[0].jdata.shape)
-print(points[1].jdata.shape)
-
-
-

Minibatch of grids constructed from the input points. These input points have corresponding color attributes.

-

Next, we splat the colors at the points to the constructed grid, yielding per-voxel colors.

-
# Splat the colors into the grid with trilinear interpolation
-# vox_colors is a JaggedTensor of per-voxel normas
-vox_colors = grid.splat_trilinear(points, colors)
-
-
-

Colors splat at the input points to grid, yielding per-voxel colors.

-

Finally, we generate a new set of noisy points and sample the grid to recover colors at those new samples.

-
# Now let's generate some random points and sample the grid at those points
-sample_points = fvdb.JaggedTensor([torch.rand(10_000, 3), torch.rand(11_000, 3)]).cuda()
-
-# sampled_colors is a JaggedTensor with the same shape as sample_points with
-# one color sampled from the grid at each point
-sampled_colors = grid.sample_trilinear(sample_points, vox_colors)
-
-
-

Colors resampled at random locations from the grid.

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/tutorials/basic_grid_ops.html b/documentation/fvdb/tutorials/basic_grid_ops.html deleted file mode 100644 index 7dc487999..000000000 --- a/documentation/fvdb/tutorials/basic_grid_ops.html +++ /dev/null @@ -1,960 +0,0 @@ - - - - - - - - - Basic GridBatch Operations — fVDB documentation - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Basic GridBatch Operations

-

Here we describe basic operations you can perform on a GridBatch. Generally, these operations involve a GridBatch and an associated JaggedTensor of data representing the attributes/features at each voxel within the grid.

-
-

Sampling grids

-

You can differentiably sample data stored at the voxels of a grid using trilinear or Bézier interpolation as follows:

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-import torch
-import numpy as np
-
-# We're going to create a minibatch of two meshes
-v1, f1 = load_car_1_mesh(mode="vf")
-v2, f2 = load_car_2_mesh(mode="vf")
-
-# Build a GridBatch from two meshes
-mesh_v_jagged = fvdb.JaggedTensor([v1, v2])
-mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).int()
-grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.1)
-
-# Generate some sample points by adding random gaussian noise to the center of each voxel
-world_space_centers = grid.grid_to_world(grid.ijk.float())
-# 5 samples per voxel for the first grid and 7 samples per voxel for the second
-sample_pts = fvdb.JaggedTensor([
-    torch.cat([world_space_centers[0].jdata] * 5),
-    torch.cat([world_space_centers[1].jdata] * 7)])
-sample_pts += fvdb.JaggedTensor([
-    torch.randn(grid.num_voxels_at(0) * 5, 3).to(grid.device) * grid.voxel_sizes[0]*0.3,
-    torch.randn(grid.num_voxels_at(1) * 7, 3).to(grid.device) * grid.voxel_sizes[1]*0.3])
-
-# Generate RGB values per voxel as the normalized absolute grid coordinate of the voxel
-per_voxel_colors = world_space_centers.clone()
-per_voxel_colors.jdata = torch.abs(world_space_centers.jdata) / torch.norm(world_space_centers.jdata, dim=-1, keepdim=True)
-
-# Sample these RGB colors at each sample point with trilinear interpolation
-# NOTE: You can use grid.sample_bezier to sample using bezier interpolation
-sampled_colors = grid.sample_trilinear(sample_pts, per_voxel_colors)
-
-
- - - - - - - - - - - -

Grid with color attributes

Points with sampled colors

-
-
-

Splatting point data to a grid

-

You can differentiably splat data at a set of points into voxels in a grid using trilinear or Bézier interpolation as follows:

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-import torch
-import point_cloud_utils as pcu
-
-# We're going to create a minibatch of two point clouds each of which
-# has a different number of points
-pts1, clrs1 = load_car_1_mesh(mode="vn")
-pts2, clrs2 = load_car_2_mesh(mode="vn")
-
-# JaggedTensors of points and normals
-points = fvdb.JaggedTensor([pts1, pts2])
-colors = fvdb.JaggedTensor([clrs1, clrs2])
-
-# Create a grid where the voxels each have unit sidelength
-grid = fvdb.gridbatch_from_points(points, voxel_sizes=1.0)
-
-# Splat the normals into the grid with trilinear interpolation
-# vox_normals is a JaggedTensor of per-voxel normas
-# NOTE: You can use grid.splat_bezier to splat using bezier interpolation
-vox_colors = grid.splat_trilinear(points, colors)
-
-
- - - - - - - - - - - -

Input grid and points with colors

Splat colors onto the grid

-
-
-

Checking if points are in a grid

-

You can query whether points lie in a grid as follows:

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-import torch
-import numpy as np
-import point_cloud_utils as pcu
-
-# We're going to create a minibatch of two meshes
-v1, f1 = load_car_1_mesh(mode="vf")
-v2, f2 = load_car_2_mesh(mode="vf")
-f1, f2 = f1.to(torch.int32), f2.to(torch.int32)
-
-# Build a GridBatch from two meshes
-mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda()
-mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda()
-grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.1)
-
-# Generate some points and check if they lie within the grid
-bbox_sizes = grid.bbox[:, 1] - grid.bbox[:, 0]
-bbox_origins = grid.bbox[:, 0]
-pts = fvdb.JaggedTensor([
-    (torch.randn(10_000, 3, device='cuda') - bbox_origins[0]) * bbox_sizes[0],
-    (torch.randn(11_000, 3, device='cuda') - bbox_origins[0]) * bbox_sizes[0],
-])
-
-# Get a mask indicating which points lie in the grid
-mask = grid.points_in_active_voxel(pts)
-
-
-

We visualize the points which intersect the grid (yellow points intersect and purple points do not). -

-
-
-

Checking if ijk coordinates are in a grid

-

Similar to querying whether world space points lie in a grid, you can query whether the index space ijk integer coordinates lie in a grid as follows:

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-import torch
-import numpy as np
-import point_cloud_utils as pcu
-import polyscope as ps
-import os
-
-
-# We're going to create a minibatch of two meshes
-v1, f1 = load_car_1_mesh(mode="vf")
-v2, f2 = load_car_2_mesh(mode="vf")
-f1, f2 = f1.to(torch.int32), f2.to(torch.int32)
-
-# Build a GridBatch from two meshes
-mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda()
-mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda()
-grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.025)
-
-rand_idx_pts = []
-for b, b_grid in enumerate(grid.bbox):
-    pts = [torch.randint(int(b_grid[0][i]), int(b_grid[1][i]), size=(2_000 * (b+1),), device='cuda') for i in range(3)]
-    rand_idx_pts.append(torch.stack(pts, dim=1))
-
-pts = fvdb.JaggedTensor(rand_idx_pts)
-
-coords_in_grid = grid.coords_in_active_voxel(pts)
-
-
-

We visualize the coordinates which intersect the grid (yellow coordinates intersect and purple coordinates do not). -

-
-
-

Checking if axis aligned cubes intersect a grid

-

There are methods of a grid to help ascertain whether a provided set of axis-aligned cubes (all of the same size) each are contained within voxels of the grid or intersect with any voxel of the grid. These methods are called cubes_in_grid and cubes_intersect_grid, respectively, and return a JaggedTensor of boolean values which indicate the result.

-

In this example we create some random points to represent the centers of cubes of size 0.03 units along-a-side. Then we use these two methods to discover which of those cubes would intersect a voxel of the grid and which would be entirely enclosed by a voxel in the grid.

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-import torch
-import numpy as np
-import point_cloud_utils as pcu
-import polyscope as ps
-import os
-
-# We're going to create a minibatch of two meshes
-v1, f1 = load_car_1_mesh(mode="vf")
-v2, f2 = load_car_2_mesh(mode="vf")
-f1, f2 = f1.to(torch.int32), f2.to(torch.int32)
-
-# Build a GridBatch from two meshes
-mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda()
-mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda()
-grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.025)
-
-# Generate some points and check if they lie within the grid
-bbox_sizes = (grid.bbox[:, 1] - grid.bbox[:, 0]) * grid.voxel_sizes
-bbox_origins = (grid.bbox[:, 0] + grid.origins) * grid.voxel_sizes
-pts = fvdb.JaggedTensor(
-    [
-        (torch.rand(3000, 3, device="cuda")) * bbox_sizes[0] + bbox_origins[0],
-        (torch.rand(2000, 3, device="cuda")) * bbox_sizes[1] + bbox_origins[1],
-    ]
-)
-
-cube_size = 0.03
-
-# We can check if the axis-aligned cubes intersect any voxels of the grid...
-cubes_intersect_grid = grid.cubes_intersect_grid(pts, -cube_size / 2, cube_size / 2)
-# ... or if they are fully contained within the grid
-cubes_in_grid = grid.cubes_in_grid(pts, -cube_size / 2, cube_size / 2)
-
-
-

We visualize the cubes which intersect a voxel in the grid (yellow cubes intersect and purple cubes do not). -

-

And which cubes lie entirely within a voxel of the grid -

-
-
-

Converting ijk values to indexes

-

There is a convenient method to convert ijk values, which are integer coordinates in the index space of a grid, to indexes, which are the linearized indices of the voxels in the GridBatch. This method, ijk_to_index, returns a JaggedTensor of indexes. If the provided ijk values do not correspond to any voxels in the grid, the returned index will be -1.

-

-import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-import torch
-import numpy as np
-import point_cloud_utils as pcu
-import polyscope as ps
-import os
-
-torch.random.manual_seed(0)
-
-def generate_random_points(bounding_box, num_points):
-    min_i, min_j, min_k = bounding_box[0]
-    max_i, max_j, max_k = bounding_box[1]
-
-    # Generate random integer points within the bounding box
-    random_i = torch.randint(min_i, max_i, size=(num_points,))
-    random_j = torch.randint(min_j, max_j, size=(num_points,))
-    random_k = torch.randint(min_k, max_k, size=(num_points,))
-
-    random_points = torch.stack([random_i, random_j, random_k], dim=1)
-
-    return random_points
-
-# We're going to create a minibatch of two meshes
-v1, f1 = load_car_1_mesh(mode="vf")
-v2, f2 = load_car_1_mesh(mode="vf")
-f1, f2 = f1.to(torch.int32), f2.to(torch.int32)
-
-# Build a GridBatch from two meshes
-mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda()
-mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda()
-grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.025)
-
-rand_pts = fvdb.JaggedTensor([generate_random_points(bbox, 1000) for bbox in grid.bbox]).cuda()
-
-rand_pts_indices= grid.ijk_to_index(rand_pts)
-
-print(rand_pts_indices.jdata)
-
-
-
tensor([  -1,   -1,  121,  ..., 4614, 5695,   -1], device='cuda:0')
-
-
-
-
-

Converting indexes to ijk values

-

If we have the value of an index into the feature data and want to obtain its corresponding ijk value, this is as simple as indexing into the ijk attribute of the grid which is itself just a JaggedTensor representing the ijk values of each index. Here we get the ijk values for 1000 random indexes of a GridBatch.

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-import torch
-import numpy as np
-import point_cloud_utils as pcu
-import polyscope as ps
-import os
-
-# We're going to create a minibatch of two meshes
-v1, f1 = load_car_1_mesh(mode="vf")
-v2, f2 = load_car_2_mesh(mode="vf")
-f1, f2 = f1.to(torch.int32), f2.to(torch.int32)
-
-# Build a GridBatch from two meshes
-mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda()
-mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda()
-grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.025)
-
-rand_indexes = torch.randint(0, grid.total_voxels, size=(1000,)).cuda()
-print(grid.ijk.jdata[rand_indexes])
-print(grid.ijk.jidx[rand_indexes])
-
-
-
tensor([[-11,   5,   7],
-        [  8,  -4,  -5],
-        [-15,   3,  -2],
-        ...,
-        [ 18,   1,   6],
-        [ 19,  -3,   6],
-        [-14,  -7,   8]], device='cuda:0', dtype=torch.int32)
-
-
-

However, you may also want to obtain the batch index of the grid that the ijk coordinate belongs to. This is just as simple by using the jidx attribute of the JaggedTensor which represents the batch index of each element in the JaggedTensor. Using the same random indexes as above, we can get the batch index of the grid for each random index.

-
print(grid.ijk.jidx[rand_indexes])
-
-
-
tensor([0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1,
-        1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1,
-        0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1,
-        1, 0, 0, 0, 0, 1, 0, 1, 1, 0,…], device='cuda:0')
-
-
-

While this case of having a random set of ijk values is very contrived, it is more likely that we would have a JaggedTensor of features that correspond to each ijk value but are out-of-order in relation to another grid.ijk’s and we need to reorder these features to for this other grid. For this, there is the ijk_to_inv_index function.

-

Given a JaggedTensor of ijk values, grid_batch.ijk_to_inv_index will return a scalar integer JaggedTensor of size [B, -1], where B is the number of grids in the batch and -1 represents the number of voxels in each grid. This JaggedTensor of indexes can be used to permute the input to ijk_to_inv_index (or the feature JaggedTensor that correspond to those ijk’s) to match the ordering of the grid_batch.

-

To concisely illustrate the properties of this function,

-
    -
  • if idx = grid.ijk_to_inv_index(misordered_ijk), then grid.ijk == misordered_ijk[idx]

  • -
  • if idx = grid.ijk_to_index(misordered_ijk), then grid.ijk[idx] == misordered_ijk

  • -
-

Note: If any ijk values in the grid are not present in the input to ijk_to_inv_index, the returned index will be -1 at that position.

-

In this example, let’s illustrate the more useful case where we have corresponding features and ijk values from a grid that are ordered differently from another reference grid and we want to re-order the features to match the order they should be in the reference grid.

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-import torch
-import numpy as np
-import point_cloud_utils as pcu
-
-# We're going to create a minibatch of two meshes
-v1, f1 = load_car_1_mesh(mode="vf")
-v2, f2 = load_car_2_mesh(mode="vf")
-f1, f2 = f1.to(torch.int32), f2.to(torch.int32)
-
-# Build a GridBatch from two meshes
-mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda()
-mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda()
-reference_grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.025)
-
-# 7 random feature values for each voxel in the grid
-features = reference_grid.jagged_like(torch.rand(grid.total_voxels, 7).to(grid.device))
-
-# Create a set of randomly shuffled corresponding ijk/features from our original grid/features
-shuffled_ijks = []
-shuffled_features = []
-
-for i in range(reference_grid.grid_count):
-    perm = torch.randperm(reference_grid.num_voxels_at(i))
-    shuffled_ijks.append(reference_grid.ijk.jdata[grid.ijk.jidx==i][perm])
-    shuffled_features.append(features.jdata[features.jidx==i][perm])
-
-shuffled_ijks = fvdb.JaggedTensor(shuffled_ijks)
-shuffled_features = fvdb.JaggedTensor(shuffled_features)
-
-# Get the indexes to reorder the shuffled features to match the grid's original ijk ordering
-idx = reference_grid.ijk_to_inv_index(shuffled_ijks)
-
-# Permute the shuffled features based on the ordering from `ijk_to_inv_index`
-unshuffled_features = shuffled_features.jdata[idx.jdata]
-print("Do the shuffled features that have been permuted based on `ijk_to_inv_index` match the original features? ", "Yes!" if torch.all(unshuffled_features == features.jdata) else "No!")
-
-
-
Do the shuffled features that have been permuted based on `ijk_to_inv_index` match the original features?  Yes!
-
-
-
-
-

Getting indexes of neighbors

-

If we want to get the indexes of all the spatial neighbors of a set of ijk values, we can use the neighbor_indexes method of a GridBatch. This method receives a set of ijk values and an extent (the number of voxels away from the ijk value to consider neighbors) and returns a JaggedTensor of indexes of neighbors for each ijk value. The returned JaggedTensor has jdata of size [N, extent*2+1, extent*2+1, extent*2+1] where N is the number of requested ijk values.

-

In this example we create a set of 24 random ijk values per grid and get the indexes of all neighbors 2 voxels away from each ijk.

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-import torch
-import numpy as np
-import point_cloud_utils as pcu
-import polyscope as ps
-import os
-
-# We're going to create a minibatch of two meshes
-v1, f1 = load_car_1_mesh(mode="vf")
-v2, f2 = load_car_2_mesh(mode="vf")
-f1, f2 = f1.to(torch.int32), f2.to(torch.int32)
-
-# Build a GridBatch from two meshes
-mesh_v_jagged = fvdb.JaggedTensor([v1, v2]).cuda()
-mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).cuda()
-grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=0.025)
-
-# Build a set of 24 randomly selected ijk values per grid
-rand_ijks = fvdb.JaggedTensor(
-    [
-        grid.ijk.jdata[
-            torch.randint(int(grid.ijk.joffsets[b]), int(grid.ijk.joffsets[b + 1]), (24,))
-        ]
-        for b in range(grid.grid_count - 1)
-    ],
-)
-
-# Get the indexes of all neighbors 2 voxels away from each ijk
-# Returns a JaggedTensor with jdata of size [48, 5, 5, 5] ([ N x (extent*2+1)^3 ])
-#   where each [5, 5, 5] describes the layout of neighbouring indexes of each ijk
-neighbor_idxs = grid.neighbor_indexes(rand_ijks, 2)
-
-
-

We visualize the voxels we selected in red and their neighbors in blue. -

-
-
-

Clipping a grid to a bounding box

-

If we want to ‘clip’ a grid, meaning remove all the voxels that are outside of a bounding box, we can use the clipped_grid method of a GridBatch. The minimum and maximum ijk extents of the bounding box used for this clipping are provided as arguments and can be specified per-grid of the batch.

-

If we want to both clip the batch of grids and the accompanying JaggedTensor of batched features, we can use the clip method of a GridBatch which can be provided the features to be clipped along with the grid.

-

In this example we clip a batch of grids to independent bounding boxes and visualize the result.

-
import os
-import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh, load_car_3_mesh, load_car_4_mesh
-import torch
-import numpy as np
-import point_cloud_utils as pcu
-
-device = 'cuda'
-mesh_vs = []
-mesh_fs = []
-mesh_load_funcs = [load_car_1_mesh, load_car_2_mesh, load_car_3_mesh, load_car_4_mesh]
-
-for func in mesh_load_funcs:
-    v, f = func(mode="vf")
-    mesh_vs.append(v)
-    mesh_fs.append(f.to(torch.int32))
-
-mesh_vs = fvdb.JaggedTensor(mesh_vs)
-mesh_fs = fvdb.JaggedTensor(mesh_fs)
-
-vox_size = 0.01
-
-# Build GridBatch from meshes
-grid = fvdb.gridbatch_from_mesh(mesh_vs, mesh_fs, vox_size)
-
-# Use `clipped_grid` to clip the grids outside of the specified minimum and maximum bounding boxes for each grid…
-clipped_grid = grid.clipped_grid(
-                         ijk_min=[[-200, -200, -200], [0, -200, -200],  [-200, -200, -200], [-200, 0, -200]],
-                         ijk_max=[[0, 200, 200],      [200, 200, 200],  [200, 0, 200],       [200, 200, 200]])
-
-# Use `clip` to both clip the grids and the accompanying JaggedTensor of features
-features = grid.jagged_like(torch.rand(grid.total_voxels, 7).to(device))
-clipped_features, clipped_grid = grid.clip(
-                         features=features,
-                         ijk_min=[[-200, -200, -200], [0, -200, -200],  [-200, -200, -200], [-200, 0, -200]],
-                         ijk_max=[[0, 200, 200],      [200, 200, 200],  [200, 0, 200],       [200, 200, 200]])
-
-
-
-

We visualize these grids, each clipped to a different bounding box.

-

-
-
-

Max/Mean Pooling

-

‘Pooling’ a grid has the effect of coarsening the resolution of the grid by a constant factor (and can be performed anisotropically for different factors in each xyz spatial dimension). When pooling a grid, the values of the new, coarser grid can be determined by the maximum or mean of the values of the voxels in the original grid that are covered by each voxel in the new grid. Maximum and mean pooling can be accomplished by the max_pool and avg_pool operators.

-

In this example, we create a grid from a mesh and then perform max and mean pooling on it to illustrate the difference between the two.

-
import os
-import fvdb
-from fvdb.utils.examples import load_car_1_mesh
-import torch
-import numpy as np
-import point_cloud_utils as pcu
-
-vox_size = 0.02
-num_pts = 10_000
-
-mesh_load_funcs = [load_car_1_mesh]
-
-points = []
-normals = []
-
-for func in mesh_load_funcs:
-    pts, nms = func(mode="vn")
-    pmt = torch.randperm(pts.shape[0])[:num_pts]
-    pts, nms = pts[pmt], nms[pmt]
-    points.append(pts)
-    normals.append(nms)
-
-# JaggedTensors of points and normals
-points = fvdb.JaggedTensor(points)
-normals = fvdb.JaggedTensor(normals)
-
-# Create a grid
-grid = fvdb.gridbatch_from_points(points, voxel_sizes=vox_size)
-
-# Splat the normals into the grid with trilinear interpolation
-vox_normals = grid.splat_trilinear(points, normals)
-
-# Mean Pooling of normals features
-avg_normals, avg_grid = grid.avg_pool(4, vox_normals)
-# Max Pooling of normals features
-max_normals, max_grid = grid.max_pool(4, vox_normals)
-
-
-

We visualize the original grid with values of the mesh normals as features, the features/grid after mean pooling, and the features/grid after max pooling to illustrate how these pooling modes affect the resulting features of the grid.

-

-
-
-

Subdividing a grid

-

Subdividing a grid has the effect of increasing the resolution of the grid by a constant factor (and can be performed anisotropically for different factors in each xyz spatial dimension).

-

The most straightforward way to use the subdivide method is to provide an integer value for the subdivision factor. This will result in a grid that is subdiv_factor times the resolution of the original grid in each spatial dimension.

-
import os
-import fvdb
-from fvdb.utils.examples import load_car_1_mesh
-import torch
-import numpy as np
-import point_cloud_utils as pcu
-
-vox_size = 0.02
-num_pts = 10_000
-
-mesh_load_funcs = [load_car_1_mesh]
-
-points = []
-normals = []
-
-for func in mesh_load_funcs:
-    pts, nms = func(mode="vn")
-    pmt = torch.randperm(pts.shape[0])[:num_pts]
-    pts, nms = pts[pmt], nms[pmt]
-    points.append(pts)
-    normals.append(nms)
-
-# JaggedTensors of points and normals
-points = fvdb.JaggedTensor(points)
-normals = fvdb.JaggedTensor(normals)
-
-# Create a grid
-grid = fvdb.gridbatch_from_points(points, voxel_sizes=vox_size)
-
-# Splat the normals into the grid with trilinear interpolation
-vox_normals = grid.splat_trilinear(points, normals)
-
-# Subdivide by a constant factor of 2
-subdiv_normals, subdiv_grid = grid.subdivide(2, vox_normals)
-
-
-

Here we visualize the original grid on the right and the grid after subdivision by a factor of 2 on the left.

-

-

In practice, in a deep neural network like a U-Net architecture, the resolution of the grid can be decreased early in the network and then the grid’s features need to be concatenated with features of the grid after its resolution is increased again. When working with traditional, dense 2D or 3D data, cropping any mismatched outputs is straightforward to be able to concatenate these features. However, in a sparse 3D grid, this is not straightforward and it’s entirely unclear how to align the features.

-

Take this simple example to illustrate the difficulties created by changing the grid topology in this way. We take the grid created from our mesh, perform Pooling by a factor of 2 and then try to Subdivide by an equal factor of 2 to invert the Pooling operation.

-
max_normals, max_grid = grid.max_pool(2, vox_normals)
-
-subdiv_normals, subdiv_grid = max_grid.subdivide(2, max_normals)
-
-
-

Let’s visualize these results from left to right of the original grid, the grid after max pooling, and the grid after max pooling and then subdividing again by the same factor.

-

-

Notice how we have not obtained the topology of the original grid and have obtained a grid with many more voxels than the original.

-

To correctly invert the Pooling operation, we can provide the subdivide function with a fine_grid optional argument which describes the topology we want the grid to have after the subdivision. The original grid before the Pooling operation can be used as this fine_grid.

-
max_normals, max_grid = grid.max_pool(2, vox_normals)
-
-# Providing the original grid as our fine_grid target
-subdiv_normals, subdiv_grid = max_grid.subdivide(2, max_normals, fine_grid=grid)
-
-
-

-

Note the matching topology of the grid after the Subdivision operation to the original grid (though the features are different due to the Pooling operation).

-

One other useful feature of the subdivide operator is that this operation can be masked so that subdivision is only performed on a subset of the voxels in the grid.

-

The optional mask argument to subdivide is a JaggedTensor of boolean values that indicates which voxels should be subdivided. Given this mask is simply a JaggedTensor, this operation can be made differentiable in a neural network and the subdivide operator can be learned.

-

Let’s illustrate a very simple example of how to use the mask argument to only subdivide the voxels which have a feature value greater than a certain threshold.

-
# Mask the grid with the normals where only the normals with a value greater than 0.5 on the x-axis are subdivided
-mask = vox_normals.jdata[:, 0] > 0.5
-
-subdiv_normals, subdiv_grid = grid.subdivide(2, vox_normals, mask=mask)
-
-
-

-
-
-

Getting the number of enabled voxels per grid in a batch

-

Getting the number of voxels in the grids of a GridBatch can be easily accomplished with num_voxels:

-
import fvdb
-import torch
-
-# Create a GridBatch of random points
-batch_size = 4
-pts = fvdb.JaggedTensor([torch.rand(10_000*(i+1),3) for i in range(batch_size)])
-grid = fvdb.GridBatch(mutable=False)
-grid.set_from_points(pts, voxel_sizes=0.02)
-
-# Get the number of voxels per grid in the batch
-for batch, num_voxels in enumerate(grid.num_voxels):
-    print(f"Grid {batch} has {num_voxels} voxels")
-
-
-
Grid 0 has 9645 voxels
-Grid 1 has 18503 voxels
-Grid 2 has 26749 voxels
-Grid 3 has 34378 voxels
-
-
-

If the grid is mutable and the number of enabled voxels has changed, you can use num_enabled_voxels to get the number of enabled voxels. If a grid is not mutable, num_enabled_voxels will return the same value as num_voxels.

-
# Create a mutable GridBatch of random points
-grid = fvdb.GridBatch(mutable=True)
-grid.set_from_points(pts, voxel_sizes=0.02)
-# Disable some voxels randomly
-grid.disable_ijk(fvdb.JaggedTensor([torch.randint(0,50,(100_000,3)) for _ in range(batch_size)]))
-
-# Get the number of enabled voxels per grid in the batch
-for batch, num_voxels in enumerate(grid.num_enabled_voxels):
-    print(f"Grid {batch} has {grid.num_enabled_voxels_at(batch)} enabled voxels and {grid.num_voxels_at(batch)} total voxels")
-
-
-
Grid 0 has 4434 enabled voxels and 9645 total voxels
-Grid 1 has 8620 enabled voxels and 18503 total voxels
-Grid 2 has 12456 enabled voxels and 26749 total voxels
-Grid 3 has 16155 enabled voxels and 34378 total voxels
-
-
-
-
-

Converting between ijk (grid) coordinates and world coordinates

-

A GridBatch can contain multiple grids, each with its own coordinate system that relate the grids’ ijk integer index space coordinates to xyz floating-point world-space coordinates. These axis-aligned coordinate systems are defined by specifying a list of three-dimensional world-space origin and scale values when we define the topology of the grids in the GridBatch like so:

-
import fvdb
-import torch
-
-# Create a GridBatch of random points
-batch_size = 4
-pts = fvdb.JaggedTensor([torch.rand(10_000*(i+1),3) for i in range(batch_size)])
-grid = fvdb.GridBatch()
-grid.set_from_points(pts,
-                    voxel_sizes=[[0.02, 0.02, 0.02], [0.03, 0.03, 0.03], [0.04, 0.04, 0.04], [0.05, 0.05, 0.05]],
-                    origins=[[-.1,-.1,-.1], [0,0,0], [.1,.1,.1], [.2,-.2,.2]])
-
-
-

When voxel_sizes and origins are not defined, it is assumed all grids have a unit scale voxel_size and an origin at [0.0, 0.0, 0.0].

-

We can use GridBatch’s grid_to_world function to convert between ijk index coordinates and their corresponding world-space xyz coordinates. In this example, let’s obtain the world-space position that would lie at the index-space [1, 1, 1] point of each grid:

-
# Convert ijk coordinates to world coordinates
-ijk = fvdb.JaggedTensor([torch.ones(1,3, dtype=torch.float) for _ in range(batch_size)])
-world_coords = grid.grid_to_world(ijk)
-for i in range(grid.grid_count):
-    print(f"World-space point that lies at index [1,1,1] for Grid {i} is positioned at {world_coords.jdata[world_coords.jidx==i].tolist()}")
-
-
-
World-space point that lies at index [1,1,1] for Grid 0 is positioned at [[-0.07999999821186066, -0.07999999821186066, -0.07999999821186066]]
-World-space point that lies at index [1,1,1] for Grid 1 is positioned at [[0.030000001192092896, 0.030000001192092896, 0.030000001192092896]]
-World-space point that lies at index [1,1,1] for Grid 2 is positioned at [[0.14000000059604645, 0.14000000059604645, 0.14000000059604645]]
-World-space point that lies at index [1,1,1] for Grid 3 is positioned at [[0.25, -0.15000000596046448, 0.25]]
-
-
-

We can also do the inverse operation and convert world-space xyz coordinates to their corresponding ijk index-space coordinates using world_to_grid. In this example, let’s find the ijk index coordinates of the voxel which would contain the world-space point located at [1.0, 1.0, 1.0] for each grid in our GridBatch:

-
# Convert world coordinates to ijk coordinates
-xyz = fvdb.JaggedTensor([torch.ones(1,3, dtype=torch.float) for _ in range(batch_size)])
-ijk_coords = grid.world_to_grid(xyz)
-for i in range(grid.grid_count):
-    print(f"Index-space voxel that contains the point [1.0, 1.0, 1.0] for Grid {i} {ijk_coords.jdata[ijk_coords.jidx==i].int().tolist()}")
-
-
-
Index-space voxel that contains the point [1.0, 1.0, 1.0] for Grid 0 [[55, 55, 55]]
-Index-space voxel that contains the point [1.0, 1.0, 1.0] for Grid 1 [[33, 33, 33]]
-Index-space voxel that contains the point [1.0, 1.0, 1.0] for Grid 2 [[22, 22, 22]]
-Index-space voxel that contains the point [1.0, 1.0, 1.0] for Grid 3 [[16, 24, 16]]
-
-
-

While grid_to_world and world_to_grid are convenient functions for these purposes, the row-major transformation matrices used for these calculations can be obtained directly from a GridBatch for use in your own logic:

-
print(f"Grid to world matrices:\n{grid.grid_to_world_matrices}")
-print(f"World to grid matrices:\n{grid.grid_to_world_matrices}")
-
-
-
Grid to world matrices:
-tensor([[[ 0.0200,  0.0000,  0.0000,  0.0000],
-         [ 0.0000,  0.0200,  0.0000,  0.0000],
-         [ 0.0000,  0.0000,  0.0200,  0.0000],
-         [-0.1000, -0.1000, -0.1000,  1.0000]],
-
-        [[ 0.0300,  0.0000,  0.0000,  0.0000],
-         [ 0.0000,  0.0300,  0.0000,  0.0000],
-         [ 0.0000,  0.0000,  0.0300,  0.0000],
-         [ 0.0000,  0.0000,  0.0000,  1.0000]],
-
-        [[ 0.0400,  0.0000,  0.0000,  0.0000],
-         [ 0.0000,  0.0400,  0.0000,  0.0000],
-         [ 0.0000,  0.0000,  0.0400,  0.0000],
-         [ 0.1000,  0.1000,  0.1000,  1.0000]],
-
-        [[ 0.0500,  0.0000,  0.0000,  0.0000],
-         [ 0.0000,  0.0500,  0.0000,  0.0000],
-         [ 0.0000,  0.0000,  0.0500,  0.0000],
-         [ 0.2000, -0.2000,  0.2000,  1.0000]]])
-World to grid matrices:
-tensor([[[ 0.0200,  0.0000,  0.0000,  0.0000],
-         [ 0.0000,  0.0200,  0.0000,  0.0000],
-         [ 0.0000,  0.0000,  0.0200,  0.0000],
-         [-0.1000, -0.1000, -0.1000,  1.0000]],
-
-        [[ 0.0300,  0.0000,  0.0000,  0.0000],
-         [ 0.0000,  0.0300,  0.0000,  0.0000],
-         [ 0.0000,  0.0000,  0.0300,  0.0000],
-         [ 0.0000,  0.0000,  0.0000,  1.0000]],
-
-        [[ 0.0400,  0.0000,  0.0000,  0.0000],
-         [ 0.0000,  0.0400,  0.0000,  0.0000],
-         [ 0.0000,  0.0000,  0.0400,  0.0000],
-         [ 0.1000,  0.1000,  0.1000,  1.0000]],
-
-        [[ 0.0500,  0.0000,  0.0000,  0.0000],
-         [ 0.0000,  0.0500,  0.0000,  0.0000],
-         [ 0.0000,  0.0000,  0.0500,  0.0000],
-         [ 0.2000, -0.2000,  0.2000,  1.0000]]])
-
-
-
-
-

Convolution

-

Convolving the features of a GridBatch can be accomplished with either a high-level torch.nn.Module derived class provided by fvdb.nn or with more low-level methods available with GridBatch, we will illustrate both techniques.

-
-

High-level Usage with fvdb.nn

-

fvdb.nn.SparseConv3d provides a high-level torch.nn.Module class for convolution on fvdb classes that is an analogue to the use of torch.nn.Conv3d. Using this module is the recommended functionality for performing convolution with fvdb because it not only manages functionality such as initializing the weights of the convolution and calling appropriate backend implementation functions but it also provides certain backend optimizations which will be illustrated in the Low-level usage section.

-

One thing to note is fvdb.nn.SparseConv3d operates on a class that wraps a GridBatch and JaggedTensor together into a convenience object, fvdb.VDBTensor, which is used by all the fvdb.nn modules.

-

A simple example of using fvdb.nn.SparseConv3d is as follows:

-
import fvdb
-import fvdb.nn as fvdbnn
-from fvdb.utils.examples import load_car_1_mesh
-import torch
-import numpy as np
-import point_cloud_utils as pcu
-
-num_pts = 10_000
-vox_size = 0.02
-
-mesh_load_funcs = [load_car_1_mesh]
-
-points = []
-normals = []
-
-for func in mesh_load_funcs:
-    pts, nms = func(mode="vn")
-    pmt = torch.randperm(pts.shape[0])[:num_pts]
-    pts, nms = pts[pmt], nms[pmt]
-    points.append(pts)
-    normals.append(nms)
-
-# JaggedTensors of points and normals
-points = fvdb.JaggedTensor(points)
-normals = fvdb.JaggedTensor(normals)
-
-# Create a grid
-grid = fvdb.gridbatch_from_points(points, voxel_sizes=vox_size)
-
-# Splat the normals into the grid with trilinear interpolation
-vox_normals = grid.splat_trilinear(points, normals)
-
-# VDBTensor is a simple wrapper of a grid and a feature tensor
-vdbtensor = fvdbnn.VDBTensor(grid, vox_normals)
-
-# fvdb.nn.SparseConv3d is a convenient torch.nn.Module implementing the fVDB convolution
-conv = fvdbnn.SparseConv3d(in_channels=3, out_channels=3, kernel_size=3, stride=1, bias=False).to(vdbtensor.device)
-
-output = conv(vdbtensor)
-
-
-

Let’s visualize the original grid with normals visualized as colours alongside the result of these features after a convolution initialized with random weights: -

-

For stride values greater than 1, the output of the convolution will be a grid with a smaller resolution than the input grid (similar in topological effect to the output of a Pooling operator). Let’s illustrate this:

-
# We would expect for stride=2 that the output grid would have half the resolution (or twice the world-space size) of the input grid
-conv = fvdbnn.SparseConv3d(in_channels=3, out_channels=3, kernel_size=3, stride=2, bias=False).to(vdbtensor.device)
-
-output = conv(vdbtensor)
-
-
-

-

Transposed convolution can be performed with fvdb.nn.SparseConv3d which can increase the resolution of the grid. It only really makes sense to perform transposed sparse convolution with a target grid topology we wish to produce with this operation (see the Pooling Operators for an explanation). Therefore, an out_grid argument must be provided in this case to specify the target grid topology:

-
# Tranposed convolution operator, stride=2
-transposed_conv = fvdbnn.SparseConv3d(in_channels=3, out_channels=3, kernel_size=3, stride=2, bias=False, transposed=True).to(vdbtensor.device)
-
-# Note the use of the `out_grid` argument to specify the target grid topology
-transposed_output = transposed_conv(output, out_grid=vdbtensor.grid)
-
-
-

Here we visuzlie the original grid, the grid after strided convolution and the grid after transposed convolution inverts the topological operation of the strided convolution to produce the same topology as the original grid with the features convolved by our two layers:

-

-
-
-

Low-level Usage with GridBatch

-

The high-level fvdb.nn.SparseConv3d class wraps several pieces of GridBatch functionality to provide a convenient torch.nn.Module for convolution. However, for a more low-level approach that accomplishes the same outcome, the GridBatch class itself can be the starting point for performing convolution on the grid and its features. We will illustrate this approach for completeness, though we do recommend the use of the fvdb.nn.SparseConv3d Module for most use-cases.

-

Using the GridBatch convolution functions directly requires a little more knowledge about the implementation under-the-hood. Due to the nature of a sparse grid, in order to make convolution performant, it is useful to pre-compute a mapping of which features in the input grid will contribute to the values of the output grid when convolved by a kernel of a particular dimension and stride. This mapping structure is called a ‘kernel map’.

-

The kernel map, as well as the functionality for using it to compute the convolution, is contained within a fvdb.SparseConvPackInfo class which can be constructed by GridBatch.sparse_conv_kernel_map. A fvdb.SparseConvPackInfo must perform a pre-computation of the kernel map based on the style expected by the backend implementation of the convolution utilized by fvdb.SparseConvPackInfo.sparse_conv_3d. Here is an example of how to construct a fvdb.SparseConvPackInfo and use it to perform a convolution:

-
import fvdb
-import fvdb.nn as fvdbnn
-from fvdb.utils.examples import load_car_1_mesh
-import torch
-import numpy as np
-import point_cloud_utils as pcu
-
-num_pts = 10_000
-vox_size = 0.02
-
-mesh_load_funcs = [load_car_1_mesh]
-
-points = []
-normals = []
-
-for func in mesh_load_funcs:
-    pts, nms = func(mode="vn")
-    pmt = torch.randperm(pts.shape[0])[:num_pts]
-    pts, nms = pts[pmt], nms[pmt]
-    points.append(pts)
-    normals.append(nms)
-
-# JaggedTensors of points and normals
-points = fvdb.JaggedTensor(points)
-normals = fvdb.JaggedTensor(normals)
-
-# Create a grid
-grid = fvdb.gridbatch_from_points(points, voxel_sizes=vox_size)
-
-# Splat the normals into the grid with trilinear interpolation
-vox_normals = grid.splat_trilinear(points, normals)\
-
-# Create the kernel map (housed in a SparseConvPackInfo) and the output grid's topology based on the kernel parameters
-sparse_conv_packinfo, out_grid = grid.sparse_conv_kernel_map(kernel_size=3, stride=1)
-
-# The kernel map must be pre-computed based on the backend implementation we plan on using, here we use gather/scatter, the default implementation
-sparse_conv_packinfo.build_gather_scatter()
-
-# Create random weights for our convolution kernel of size 3x3x3 that takes 3 input channels and produces 3 output channels
-kernel_weights = torch.randn(3, 3, 3, 3, 3, device=grid.device)
-
-# Perform convolution on the normals colours.  Gather/scatter is used as the backend, it is the default
-conv_vox_normals = sparse_conv_packinfo.sparse_conv_3d(vox_normals, weights=kernel_weights, backend=fvdb.ConvPackBackend.GATHER_SCATTER)
-
-
-

Here we visualize the output of our convolution alongside the original grid with normals visualized as colours: -

-

The kernel map can potentially be expensive to compute, so it is often useful to re-use the SparseConvPackInfo in the same network to perform a convolution on other features or with different weights. This optimization is something fvdb.nn.SparseConv3d attempts to do where appropriate and is one reason we recommend using fvdb.nn.SparseConv3d over this low-level approach.

-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/tutorials/building_grids.html b/documentation/fvdb/tutorials/building_grids.html deleted file mode 100644 index ccd3db08a..000000000 --- a/documentation/fvdb/tutorials/building_grids.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - - - - - - Building Sparse Grids — fVDB documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Building Sparse Grids

-

We introduce several ways to construct sparse voxel grids from various data sources including point clouds, coordinate lists, triangle meshes, and deriving from other grids. -All the examples below could be found in full version (including visualization) at examples/grid_building.py and examples/grid_subdivide_coarsen.py.

-
-

From Coordinate Lists

-

If you already have an integer list of the ijk coordinates for each voxel in the grid, you could directly build grids from the list. -Additionally, you will have to specify the voxel sizes and voxel origins for the grid. -An example is as follows:

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-
-coords_1, _ = load_car_1_mesh()
-coords_2, _ = load_car_2_mesh()
-coords_jagged = fvdb.JaggedTensor([
-    coords_1.long().cuda(),
-    coords_2.long().cuda()
-])
-voxel_sizes = [[0.1, 0.1, 0.1], [0.15, 0.15, 0.15]]
-
-grid = fvdb.gridbatch_from_ijk(coords_jagged, voxel_sizes=voxel_sizes, origins=[0.0] * 3)
-
-
-

The above code assumes that you want to build a grid with two batch elements, one with voxel size [0.1, 0.1, 0.1], and the other with voxel size [0.15, 0.15, 0.15] (although usually you just want all the elements in your batch to have the same size, in which case you could just pass in voxel_sizes=[0.1, 0.1, 0.1]). -The same logic applies for origins that specifies the world coordinates of voxel (0, 0, 0), which we set to the origin here. -The grid will be constructed on the same device as coords_jagged, which is a JaggedTensor. The JaggedTensor is built from two numpy int64 arrays with size [*, 3] called coords_1 and coords_2.

-

build_from_coordinates.png

-
-
-

From Point Clouds

-

You could either choose to quantize the coordinates of your point cloud into ijk coordinates yourself (e.g. using np.unique((xyz / voxel_size).floor(), axis=0)), or let fvdb handle this logic. Specifically, you could do:

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-
-coords_1, _ = load_car_1_mesh()
-coords_2, _ = load_car_2_mesh()
-
-# Assemble point clouds into JaggedTensor
-pcd_jagged = fvdb.JaggedTensor([
-    coords_1.cuda(),
-    coords_2.cuda()
-])
-voxel_sizes = [[0.1, 0.1, 0.1], [0.15, 0.15, 0.15]]
-
-# Method 1:
-grid_a1 = fvdb.gridbatch_from_points(pcd_jagged, voxel_sizes=voxel_sizes, origins=[0.0] * 3)
-
-# Method 2:
-grid_a2 = fvdb.GridBatch(device=pcd_jagged.device)
-grid_a2.set_from_points(pcd_jagged, voxel_sizes=voxel_sizes, origins=[0.0] * 3)
-
-
-

Above we show two methods of building grids from points. Similar functions exist for other grid building approaches. The built grids are shown as following:

-

build_from_points.png

-

In some applications, you may want to build a dilated version of the grid by ensuring that all \(2\times 2 \times 2\) voxels around each point are included in the built grid. That said, you could build the grid by:

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-
-coords_1, _ = load_car_1_mesh()
-coords_2, _ = load_car_2_mesh()
-
-# Assemble point clouds into JaggedTensor
-pcd_jagged = fvdb.JaggedTensor([
-    coords_1.cuda(),
-    coords_2.cuda()
-])
-voxel_sizes = [[0.1, 0.1, 0.1], [0.15, 0.15, 0.15]]
-
-# Build grid from containing nearest voxels to the points
-grid_b = fvdb.gridbatch_from_nearest_voxels_to_points(pcd_jagged, voxel_sizes=voxel_sizes, origins=[0.0] * 3)
-
-
-

build_from_points_nn.png

-
-
-

From Meshes

-

We allow building grids enclosing a triangle mesh easily. The given triangle mesh does not have to be manifold nor watertight and it will be treated as a triangle soup internally. -An example to build grids from meshes is shown as follows:

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-
-mesh_1_v, mesh_1_f = load_car_1_mesh(mode='vf')
-mesh_2_v, mesh_2_f = load_car_2_mesh(mode='vf')
-
-mesh_v_jagged = fvdb.JaggedTensor([
-    mesh_1_v.float().cuda(),
-    mesh_2_v.float().cuda()
-])
-mesh_f_jagged = fvdb.JaggedTensor([
-    mesh_1_f.long().cuda(),
-    mesh_2_f.long().cuda()
-])
-
-voxel_sizes = [[0.1, 0.1, 0.1], [0.15, 0.15, 0.15]]
-grid = fvdb.gridbatch_from_mesh(mesh_v_jagged, mesh_f_jagged, voxel_sizes=voxel_sizes, origins=[0.0] * 3)
-
-
-

Here mesh_1_v and mesh_1_f are the vertex array and triangle array of the mesh to build grid from, with the shape of \((V, 3)\) and \((F, 3)\). The triangle array is an integer array that indexes into the vertex array (starting from 0 for each element in the batch). Same for another mesh_2_v and mesh_2_f.

-

build_from_mesh.png

-
-
-

From Dense

-

We have APIs for you to build dense grids of shape \((D, H, W)\) containing the full \(D\times H \times W\) voxels.

-
import fvdb
-
-grid = fvdb.gridbatch_from_dense(num_grids=1, dense_dims=[32, 32, 32], device="cuda")
-
-
-

build_from_dense.png

-

If you are comparing the performance of dense pytorch 3D tensors vs sparse grids, it is usually very helpful to build the exact same input (including grid and features). In fvdb.nn, we provide a thin wrapper class VDBTensor that works like a torch.Tensor, yet enclosing the grid topology. To convert data back and forth from dense PyTorch Tensors, we could do:

-
import torch
-import fvdb
-from fvdb.nn import VDBTensor
-
-# Easy way to initialize a VDBTensor from a torch 3D tensor [B, D, H, W, C]
-dense_data = torch.ones(2, 32, 32, 32, 16).cuda()
-sparse_data = fvdb.nn.vdbtensor_from_dense(dense_data, voxel_sizes=[0.1] * 3)
-dense_data_back = sparse_data.to_dense()
-assert torch.all(dense_data == dense_data_back)
-
-
-

Here sparse_data will be a fvdb.nn.VDBTensor class, containing both feature and grid attribute. -Such a class could be fed into all the neural network components available in fvdb.nn.

-
-
-

Deriving from other grids

-
-

Dual grid

-

A dual grid (of a primal grid) is also a grid, with its voxel centers covering the corners of the primal grid, and corners taking the positions of the centers of the primal grid. -A dual grid shares the same voxel sizes as its primal grid, and is just a simple shift of half the voxel size in translation. -In the picture below, green grid is the primal grid while purple grid is its dual.

-

build_dual.png

-

To create a dual grid from a given primal grid, use GridBatch.dual_grid():

-
import fvdb
-from fvdb.utils.examples import load_dragon_mesh
-
-coords_1, _ = load_dragon_mesh()
-
-grid_primal = fvdb.gridbatch_from_points(fvdb.JaggedTensor([coords_1]))
-
-grid_dual = grid_primal.dual_grid()
-
-
-
-
-

Subdividing and Coarsening a grid

-

The grid could be subdivided or coarsened with a subdivision/coarsening factor provided:

-
import fvdb
-from fvdb.utils.examples import load_happy_mesh
-
-coords_1, _ = load_happy_mesh(mode='vf')
-
-grid = fvdb.gridbatch_from_points(fvdb.JaggedTensor([coords_1]))
-
-grid_subdivided = grid.subdivided_grid(2)
-grid_coarsened = grid.coarsened_grid(2)
-
-
-

build_coarse_subdivide.png

-

The features associated with the grids could be processed via the examples in Grid Operations. -Refer to the corresponding section for more details.

-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/tutorials/io.html b/documentation/fvdb/tutorials/io.html deleted file mode 100644 index e4269cc8e..000000000 --- a/documentation/fvdb/tutorials/io.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - - - - - Sparse Grid I/O — fVDB documentation - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Sparse Grid I/O

-

We give an overview of ways to save and load sparse grids including how fVDB’s serialized format relates to other libraries such as OpenVDB and NanoVDB. All of the examples in this tutorial are available in the examples/io.py file of the fVDB repository.

-

In these examples we will be using tools which are part of the NanoVDB project such as nanovdb_convert. It is assumed that the NanoVDB tools are available to call (i.e. findable on the system’s $PATH). If you have not already installed these tools, you can find instructions on building NanoVDB with OpenVDB from its documentation:

-

https://www.openvdb.org/documentation/doxygen/NanoVDB_HowToBuild.html

-

While not necessary for fVDB’s functionality, they are useful utilities to have available for inspecting and manipulating sparse grids.

-
-

Python Serialization

-

Batches of sparse grids can be serialized to a NanoVDB file using the fvdb.save method. Here, we create two grids of different sizes and numbers of points and save them to a compressed NanoVDB file with specified names. The names are optional.

-
import torch
-import fvdb
-import tempfile
-import os
-import subprocess
-
-p = fvdb.JaggedTensor(
-    [
-        torch.randn(10, 3),
-        torch.randn(100, 3),
-    ]
-)
-grid = fvdb.gridbatch_from_points(
-    p, voxel_sizes=[[0.1, 0.1, 0.1], [0.15, 0.15, 0.15]], origins=[0.0] * 3
-)
-
-# save the grid and features to a compressed nvdb file
-path = os.path.join(tempfile.gettempdir(), "two_random_grids.nvdb")
-fvdb.save(path, grid, names=["taco1", "taco2"], compressed=True)
-
-
-

We can use the nanovdb_print command line tool to show information about our saved file. Note how our grids have the INDEX class since they have no features and only store the voxel indices.

-
The file "/tmp/tmpwnu7qc_7/two_random_grids.nvdb" contains the following 2 grids:
-#  Name   Type     Class  Version  Codec  Size      File      Scale             # Voxels  Resolution
-1  taco1  OnIndex  INDEX  32.6.0   BLOSC  1.453 MB  7.181 KB  (0.1,0.1,0.1)     10        22 x 43 x 37
-2  taco2  OnIndex  INDEX  32.6.0   BLOSC  2.326 MB  12.7 KB   (0.15,0.15,0.15)  100       33 x 39 x 38
-
-
-

We can include N-dimensional features by passing a JaggedTensor as the second argument (or data kwarg) to fvdb.save. Here, we create a grid with a single, float feature channel for our grids and save it to a nvdb file.

-
# a single, scalar float feature per grid
-feats = fvdb.JaggedTensor([torch.randn(x, 1) for x in grid.num_voxels])
-
-# save the grid and features to a compressed nvdb file
-path = os.path.join(tempfile.gettempdir(), "two_random_grids.nvdb")
-fvdb.save(path, grid, feats, names=["taco1", "taco2"], compressed=True)
-
-
-

Again, we can use the nanovdb_print command line tool to show information about our saved file.

-
The file "/tmp/tmpg9ol5841/two_random_grids.nvdb" contains the following 2 grids:
-#  Name   Type   Class  Version  Codec  Size      File      Scale    # Voxels  Resolution
-1  taco1  float  ?      32.6.0   BLOSC  1.47 MB   7.959 KB  (1,1,1)  10        26 x 30 x 36
-2  taco2  float  ?      32.6.0   BLOSC  2.411 MB  16.48 KB  (1,1,1)  100       32 x 33 x 34
-
-
-

Note how our serialized NanoVDB grids are now of type float. fVDB will automatically map N-dimensional features to appropriate NanoVDB types. For feature sizes that don’t naturally map to any NanoVDB data types, fVDB will save the feature data as NanoVDB blind-data which will be appropriately read back as N-dimensional features by fVDB.

-

Let’s try to save the same two grids with a Vec3d type by creating a JaggedTensor of 3-dimensional double-precision features.

-
# a 3-vector double feature per grid
-feats = fvdb.JaggedTensor([torch.randn(x, 3, dtype=torch.float64) for x in grid.num_voxels])
-
-# save the grid and features to a compressed nvdb file
-saved_nvdb = os.path.join(tempfile.gettempdir(), "two_random_vec3d_grids.nvdb")
-fvdb.save(saved_nvdb, grid, feats, names=["taco1", "taco2"], compressed=True)
-
-
-
The file "/tmp/tmpwnu7qc_7/two_random_grids.nvdb" contains the following 2 grids:
-#  Name   Type   Class  Version  Codec  Size      File      Scale    # Voxels  Resolution
-1  taco1  Vec3d  ?      32.6.0   BLOSC  6.077 MB  28.18 KB  (1,1,1)  10        23 x 36 x 35
-2  taco2  Vec3d  ?      32.6.0   BLOSC  7.346 MB  41.37 KB  (1,1,1)  100       37 x 40 x 34
-
-
-
-
-

Loading NanoVDB Files

-

Loading NanoVDB files is as simple as calling fvdb.load. You can optionally supply a PyTorch device you’d like the grids and features loaded onto. Here, we load the two grids we saved in the previous section onto our GPU.

-
# Load the grid and features from the compressed nvdb file
-grid_batch, features, names = fvdb.load(saved_nvdb, device=torch.device("cuda:0"))
-print("Loaded grid batch total number of voxels: ", grid_batch.total_voxels)
-print("Loaded grid batch data type: %s, device: %s" % (features.dtype, features.device))
-
-
-
Loaded grid batch total number of voxels:  110
-Loaded grid batch data type: torch.float64, device: cuda:0
-
-
-
-
-

Saving/Loading OpenVDB Files

-

While saving and loading from OpenVDB files is not directly supported by fVDB, it is possible to easily convert between NanoVDB and OpenVDB files using the nanovdb_convert command line tool. Here, we convert our previously saved NanoVDB file to an OpenVDB file.

-
vdb_path = os.path.join(tempfile.gettempdir(), "two_random_grids.vdb")
-convert_cmd = "nanovdb_convert -v %s %s" % (saved_nvdb, vdb_path)
-print("nanovdb_convert our nvdb to vdb: ", convert_cmd)
-print(subprocess.check_output(convert_cmd.split()).decode("utf-8"))
-
-
-
nanovdb_convert our nvdb to vdb:  nanovdb_convert -v /tmp/tmpnr7gk0tf/two_random_vec3d_grids.nvdb /tmp/tmpnr7gk0tf/two_random_grids.vdb
-Opening NanoVDB file named "/tmp/tmpnr7gk0tf/two_random_vec3d_grids.nvdb"
-Read 1 NanoGrid(s) from the file named "/tmp/tmpnr7gk0tf/two_random_vec3d_grids.nvdb"
-Converting NanoVDB grid named "taco1" to OpenVDB
-Converting NanoVDB grid named "taco2" to OpenVDB
-
-
-

From here, our grid can be loaded by OpenVDB tools. Roundtripping our converted cache back to NanoVDB is possible with the nanovdb_convert tool as well.

-

Loading the converted OpenVDB file into fVDB shows our familiar grids and features as we expect:

-
convert_cmd = "nanovdb_convert -v -f %s %s" % (  # -f flag forces overwriting existing file
-    vdb_path,
-    saved_nvdb,
-)
-print("nanovdb_convert roundtrip the vdb to nvdb: ", convert_cmd)
-print(subprocess.check_output(convert_cmd.split()).decode("utf-8"))
-
-# Load the nvdb file of the converted vdb
-grid_batch, features, names = fvdb.load(saved_nvdb, device=torch.device("cuda:0"))
-print("Loaded grid batch total number of voxels: ", grid_batch.total_voxels)
-print("Loaded grid batch data type: %s, device: %s" % (features.dtype, features.device))
-print("\n")
-
-
-
nanovdb_convert roundtrip the vdb to nvdb:  nanovdb_convert -v -f /tmp/tmpuwq2r5mx/two_random_grids.vdb /tmp/tmpuwq2r5mx/two_random_vec3d_grids.nvdb
-Opening OpenVDB file named "/tmp/tmpuwq2r5mx/two_random_grids.vdb"
-Converting OpenVDB grid named "taco1" to NanoVDB
-Converting OpenVDB grid named "taco2" to NanoVDB
-
-Loaded grid batch total number of voxels:  110
-Loaded grid batch data type: torch.float64, device: cuda:0
-
-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/tutorials/mutable_grids.html b/documentation/fvdb/tutorials/mutable_grids.html deleted file mode 100644 index b8ce51b5e..000000000 --- a/documentation/fvdb/tutorials/mutable_grids.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - - - - - - Mutable Grids — fVDB documentation - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Mutable Grids

-
-

Concepts

-

Mutable grids refer to GridBatch whose voxels can be turned ‘off’. -Each voxel hence not only stores an integer offset that indexes into the external feature array, but also includes a bit switch indicating whether the voxel exist or not.

-

mutable_grid.png

-

The ability to turn on (enable) voxels and turn off (disable) voxels make it easier for downstream tasks such as structural optimization and neural rendering. -Note that even if you disable some voxels, the corresponding entry in the feature array still exists. -Such a design keeps the feature unchanged while changing the grid topology in a flexible way.

-
-
-

Examples

-
-

Basic example

-

Mutable grids can be created by adding mutable=True arguments into the grid building function. -For example:

-
import fvdb
-from fvdb.utils.examples import load_car_1_mesh, load_car_2_mesh
-
-v1, f1 = load_car_1_mesh(mode = "vf")
-v2, f2 = load_car_2_mesh(mode = "vf")
-mesh_v_jagged = fvdb.JaggedTensor([v1, v2])
-mesh_f_jagged = fvdb.JaggedTensor([f1, f2]).int()
-
-# Create mutable grid
-grid = fvdb.gridbatch_from_mesh(
-    mesh_v_jagged, mesh_f_jagged,
-    voxel_sizes=[0.01] * 3, origins=[0.0] * 3,
-    mutable=True
-)
-
-# Create additional features for visualization purpose
-feature = grid.grid_to_world(grid.ijk.float())
-feature.jdata = (feature.jdata - feature.jdata.min(dim=0).values) / \
-    (feature.jdata.max(dim=0).values - feature.jdata.min(dim=0).values)
-
-
-

mg_origin.png

-

Voxels can be disabled in batches via disable_ijk:

-
# Get the IJK coordinates to be disabled
-disable_ijk: fvdb.JaggedTensor = grid.ijk.rmask(feature.jdata[:, 0] > 0.5)
-
-# Disable them!
-grid.disable_ijk(disable_ijk)
-
-
-

Once disabled, those voxels will virtually disappear, meaning that all subsequent grid operations such as sampling, splatting, or ray marching, will treat those voxels as they do not exist. -One can visualize the enable mask via:

-
enabled_mask = grid.enabled_mask
-
-
-

mg_mask.png

-

Note that in the above figure, white voxels are those still enabled, while black voxels are disabled voxels. -To verify, we try to sample features from the grid to a set of sampled points:

-
pts_feature = grid.sample_trilinear(mesh_v_jagged, feature)
-
-
-

mg_pts_after.png

-

Because the disabled voxels will be treated as non-existing, no voxels will contribute to the features on the points at the front. Hence those points are marked as black (i.e. feature = 0).

-

The disabled voxels could be revived at any time using enable_ijk:

-
grid.enable_ijk(disable_ijk)
-
-
-

Conducting the same feature sampling with sample_trilinear, one can get the full features being correctly sampled.

-

mg_pts_before.png

-
-
-

Structure optimization

-

In this example, we will cover a more advanced topic to perform structure optimization from images. -Suppose we have the following observations of a single object’s mask:

-

struct_mask.png

-

The task is to recover the underlying 3D shape from the images. Here we use the underlying representation of our VDB grid. -Such a problem could be solved in many different ways, with one obvious one being space culling. -However, to demonstrate the wide applicability and flexibility of fVDB, we will take the differentiable rendering approach here. -Each voxel is hereby given an opacity value, and the entire sparse grid is volume rendered into a predicted mask. -A simple L1 loss is compared between the given ground-truth mask and the predicted mask to force them align.

-

To begin, we create a mutable grid and the corresponding opacity (alpha) by:

-
import fvdb
-import torch
-import math
-
-init_resolution = 96
-
-# Suppose our shape lies within the unit bounding box from [-0.5, 0.5, 0.5] to [0.5, 0.5, 0.5]
-grid = fvdb.gridbatch_from_dense(
-    num_grids=1, dense_dims=[init_resolution] * 3,
-    voxel_sizes=[1.0 / (init_resolution - 1)] * 3, origins=[-0.5, -0.5, -0.5],
-    device="cuda",
-    mutable=True
-)
-
-def inv_sigmoid(x) -> float:
-    return -math.log(1 / x - 1)
-alpha = torch.full((grid.total_voxels, ), inv_sigmoid(0.1), device=grid.device, requires_grad=True)
-
-
-

The structure optimization is done via torch’s Adam optimizer, with the loop being:

-
optimizer = torch.optim.Adam([alpha], lr=1.0)
-
-# Optimization loop
-for it in range(100):
-    # Subsample rays from the given camera poses.
-    sub_inds = torch.randint(0, ray_orig.shape[0], (10000, ), device=grid.device)
-    pd_opacity = render_opacity(
-        grid, torch.sigmoid(alpha),
-        ray_orig=ray_orig[sub_inds], ray_dir=ray_dir[sub_inds]
-    )
-    gt_opacity = ray_opacity[sub_inds]
-
-    # Compute L1 loss
-    loss = torch.mean(torch.abs(pd_opacity - gt_opacity))
-
-    optimizer.zero_grad()
-    loss.backward()
-    optimizer.step()
-
-
-

Here render_opacity is an approximate differentiable rendering algorithm like:

-
pack_info, voxel_inds, out_times = grid.voxels_along_rays(ray_orig, ray_dir, 128, 0.0)
-voxel_inds = grid.ijk_to_index(voxel_inds).jdata
-
-rgb, depth, opacity, _, _ = fvdb.utils.volume_render(
-    sigmas=-torch.log(1 - feature[voxel_inds]),
-    rgbs=torch.ones((voxel_inds.shape[0], 1), device=grid.device),
-    deltaTs=torch.ones(voxel_inds.shape[0], device=grid.device),
-    ts=out_times.jdata.mean(1),
-    packInfo=pack_info.jdata, transmittanceThresh=0.0
-)
-
-
-

During the optimization, the voxels of the grid could be disabled or enabled freely. -In this example, we demonstrate the following strategy similar to Instant-NGP.

-
if it > 0 and it % 5 == 0:
-    # Disable voxels that are transparent
-    bad_mask = torch.sigmoid(alpha) < 0.1
-    grid.disable_ijk(grid.ijk.rmask(bad_mask))
-
-    # Randomly revive voxels at the beginning.
-    if it < 20:
-        enable_mask = torch.rand(grid.total_voxels, device=grid.device) < 0.01
-        grid.enable_ijk(grid.ijk.rmask(enable_mask))
-
-
-

Note that a way simpler strategy that only turns voxels off at a much sparse internal also works in this very simplified scenario. -The snippet above is solely for demonstration purpose of our API.

-

The optimization procedure looks as follows. One can see that we are able to recover the ground-truth voxel structure of the provided car.

-

struct_optim.png

-

A full runnable example could be found at examples/structure_optimization.py.

-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/tutorials/simple_unet.html b/documentation/fvdb/tutorials/simple_unet.html deleted file mode 100644 index 6ca1bd2fd..000000000 --- a/documentation/fvdb/tutorials/simple_unet.html +++ /dev/null @@ -1,385 +0,0 @@ - - - - - - - - - A Simple Convolutional U-Net — fVDB documentation - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

A Simple Convolutional U-Net

-

In this tutorial, you will be guided on how to build a simple sparse convolutional neural network using fVDB. -If you were using MinkowskiEngine to tackle sparse 3D data previously, we will also guide you step-by-step to help you smoothly transfer from it and enjoy speed-ups and memory-savings.

-

In our simplistic U-Net case, we want to build a Res-UNet with four layers, and each layer contains several blocks. -First, we import basic fvdb libraries:

-
import fvdb
-import fvdb.nn as fvnn
-from fvdb.nn import VDBTensor
-import torch
-
-
-

Here fvdb.nn is a namespace similar to torch.nn, containing a broad definition of different neural layers. -VDBTensor is a very thin wrapper around a grid (with type GridBatch) and its corresponding feature (with type JaggedTensor), and internally makes sure that the two members align. -It also overloads a bunch of operators such as arithmetic computations. Please refer to our API docs to learn more.

-

We could then build a basic block as follows:

-
class BasicBlock(torch.nn.Module):
-    expansion = 1
-
-    def __init__(self, in_channels: int, out_channels: int, downsample=None, bn_momentum: float = 0.1):
-        super().__init__()
-        self.conv1 = fvnn.SparseConv3d(in_channels, out_channels, kernel_size=3, stride=1)
-        self.norm1 = fvnn.BatchNorm(out_channels, momentum=bn_momentum)
-        self.conv2 = fvnn.SparseConv3d(out_channels, out_channels, kernel_size=3, stride=1)
-        self.norm2 = fvnn.BatchNorm(out_channels, momentum=bn_momentum)
-        self.relu = fvnn.ReLU(inplace=True)
-        self.downsample = downsample
-
-    def forward(self, x: VDBTensor):
-        residual = x
-
-        out = self.conv1(x)
-        out = self.norm1(out)
-        out = self.relu(out)
-
-        out = self.conv2(out)
-        out = self.norm2(out)
-
-        if self.downsample is not None:
-          residual = self.downsample(x)
-
-        out += residual
-        out = self.relu(out)
-
-        return out
-
-
-

This defines a similar block as MinkowskiEngine:

-
import MinkowskiEngine as ME
-
-
-class BasicBlock(torch.nn.Module):
-    expansion = 1
-
-    def __init__(self, in_channels: int, out_channels: int, downsample=None, bn_momentum: float = 0.1):
-        super().__init__()
-        self.conv1 = ME.MinkowskiConvolution(
-            in_channels, out_channels, kernel_size=3, stride=1, dilation=1, dimension=3)
-        self.norm1 = ME.MinkowskiBatchNorm(out_channels, momentum=bn_momentum)
-        self.conv2 = ME.MinkowskiConvolution(
-            out_channels, out_channels, kernel_size=3, stride=1, dilation=1, dimension=3)
-        self.norm2 = ME.MinkowskiBatchNorm(out_channels, momentum=bn_momentum)
-        self.relu = ME.MinkowskiReLU(inplace=True)
-        self.downsample = downsample
-
-    def forward(self, x):
-        residual = x
-
-        out = self.conv1(x)
-        out = self.norm1(out)
-        out = self.relu(out)
-
-        out = self.conv2(out)
-        out = self.norm2(out)
-
-        if self.downsample is not None:
-          residual = self.downsample(x)
-
-        out += residual
-        out = self.relu(out)
-
-        return out
-
-
-

All the network layers are fully compatible with torch.nn. The only difference is that they take VDBTensor as input and return a VDBTensor. -A full network definition could then be built as:

-
class FVDBUNetBase(torch.nn.Module):
-    LAYERS = (2, 2, 2, 2, 2, 2, 2, 2)
-    CHANNELS = (32, 64, 128, 256, 256, 128, 96, 96)
-    INIT_DIM = 32
-    OUT_TENSOR_STRIDE = 1
-
-    def __init__(self, in_channels, out_channels, D=3):
-        super().__init__()
-
-        # Output of the first conv concated to conv6
-        self.inplanes = self.INIT_DIM
-        self.conv0p1s1 = fvnn.SparseConv3d(in_channels, self.inplanes, kernel_size=5, stride=1, bias=False)
-        self.bn0 = fvnn.BatchNorm(self.inplanes)
-
-        self.conv1p1s2 = fvnn.SparseConv3d(self.inplanes, self.inplanes, kernel_size=2, stride=2, bias=False)
-        self.bn1 = fvnn.BatchNorm(self.inplanes)
-
-        self.block1 = self._make_layer(BasicBlock, self.CHANNELS[0], self.LAYERS[0])
-
-        self.conv2p2s2 = fvnn.SparseConv3d(
-            self.inplanes, self.inplanes, kernel_size=2, stride=2, bias=False)
-        self.bn2 = fvnn.BatchNorm(self.inplanes)
-
-        self.block2 = self._make_layer(BasicBlock, self.CHANNELS[1], self.LAYERS[1])
-
-        self.conv3p4s2 = fvnn.SparseConv3d(
-            self.inplanes, self.inplanes, kernel_size=2, stride=2, bias=False)
-
-        self.bn3 = fvnn.BatchNorm(self.inplanes)
-        self.block3 = self._make_layer(BasicBlock, self.CHANNELS[2], self.LAYERS[2])
-
-        self.conv4p8s2 = fvnn.SparseConv3d(
-            self.inplanes, self.inplanes, kernel_size=2, stride=2, bias=False)
-        self.bn4 = fvnn.BatchNorm(self.inplanes)
-        self.block4 = self._make_layer(BasicBlock, self.CHANNELS[3], self.LAYERS[3])
-
-        self.convtr4p16s2 = fvnn.SparseConv3d(
-            self.inplanes, self.CHANNELS[4], kernel_size=2, stride=2, transposed=True, bias=False)
-        self.bntr4 = fvnn.BatchNorm(self.CHANNELS[4])
-
-        self.inplanes = self.CHANNELS[4] + self.CHANNELS[2]
-        self.block5 = self._make_layer(BasicBlock, self.CHANNELS[4], self.LAYERS[4])
-        self.convtr5p8s2 = fvnn.SparseConv3d(
-            self.inplanes, self.CHANNELS[5], kernel_size=2, stride=2, transposed=True, bias=False)
-        self.bntr5 = fvnn.BatchNorm(self.CHANNELS[5])
-
-        self.inplanes = self.CHANNELS[5] + self.CHANNELS[1]
-        self.block6 = self._make_layer(BasicBlock, self.CHANNELS[5], self.LAYERS[5])
-        self.convtr6p4s2 = fvnn.SparseConv3d(
-            self.inplanes, self.CHANNELS[6], kernel_size=2, stride=2, transposed=True, bias=False)
-        self.bntr6 = fvnn.BatchNorm(self.CHANNELS[6])
-
-        self.inplanes = self.CHANNELS[6] + self.CHANNELS[0]
-        self.block7 = self._make_layer(BasicBlock, self.CHANNELS[6], self.LAYERS[6])
-        self.convtr7p2s2 = fvnn.SparseConv3d(
-            self.inplanes, self.CHANNELS[7], kernel_size=2, stride=2, transposed=True, bias=False)
-        self.bntr7 = fvnn.BatchNorm(self.CHANNELS[7])
-
-        self.inplanes = self.CHANNELS[7] + self.INIT_DIM
-        self.block8 = self._make_layer(BasicBlock, self.CHANNELS[7], self.LAYERS[7])
-
-        self.final = fvnn.SparseConv3d(self.CHANNELS[7], out_channels, kernel_size=1)
-        self.relu = fvnn.ReLU(inplace=True)
-
-    def _make_layer(self, block, planes, blocks):
-        downsample = None
-        if self.inplanes != planes * block.expansion:
-            downsample = torch.nn.Sequential(
-                fvnn.SparseConv3d(
-                    self.inplanes,
-                    planes * block.expansion,
-                    kernel_size=1,
-                    stride=1
-                ),
-                fvnn.BatchNorm(planes * block.expansion),
-            )
-        layers = []
-        layers.append(
-            BasicBlock(
-                self.inplanes, planes,
-                downsample=downsample
-            )
-        )
-        self.inplanes = planes * block.expansion
-        for _ in range(1, blocks):
-            layers.append(BasicBlock(self.inplanes, planes))
-
-        return torch.nn.Sequential(*layers)
-
-    def forward(self, x):
-        out = self.conv0p1s1(x)
-        out = self.bn0(out)
-        out_p1 = self.relu(out)
-        grid1 = out_p1.grid
-
-        out = self.conv1p1s2(out_p1)
-        out = self.bn1(out)
-        out = self.relu(out)
-        out_b1p2 = self.block1(out)
-        grid2 = out_b1p2.grid
-
-        out = self.conv2p2s2(out_b1p2)
-        out = self.bn2(out)
-        out = self.relu(out)
-        out_b2p4 = self.block2(out)
-        grid4 = out_b2p4.grid
-
-        out = self.conv3p4s2(out_b2p4)
-        out = self.bn3(out)
-        out = self.relu(out)
-        out_b3p8 = self.block3(out)
-        grid8 = out_b3p8.grid
-
-        # tensor_stride=16
-        out = self.conv4p8s2(out_b3p8)
-        out = self.bn4(out)
-        out = self.relu(out)
-        out = self.block4(out)
-
-        # tensor_stride=8
-        out = self.convtr4p16s2(out, out_grid=grid8)
-        out = self.bntr4(out)
-        out = self.relu(out)
-
-        out = fvdb.jcat([out, out_b3p8], dim=1)
-        out = self.block5(out)
-
-        # tensor_stride=4
-        out = self.convtr5p8s2(out, out_grid=grid4)
-        out = self.bntr5(out)
-        out = self.relu(out)
-
-        out = fvdb.jcat([out, out_b2p4], dim=1)
-        out = self.block6(out)
-
-        # tensor_stride=2
-        out = self.convtr6p4s2(out, out_grid=grid2)
-        out = self.bntr6(out)
-        out = self.relu(out)
-
-        out = fvdb.jcat([out, out_b1p2], dim=1)
-        out = self.block7(out)
-
-        # tensor_stride=1
-        out = self.convtr7p2s2(out, out_grid=grid1)
-        out = self.bntr7(out)
-        out = self.relu(out)
-
-        out = fvdb.jcat([out, out_p1], dim=1)
-        out = self.block8(out)
-
-        return self.final(out)
-
-
-

Please note that here, when we apply transposed convolution layers, we additionally introduce the out_grid keyword arguments. -This is needed to guide the output domain of the network, because for perception networks, the output grid topology should align with the input topology. -Note that fVDB will NOT cache the grids to maintain maximum flexibility.

-

To perform inference with the network, you could simply create a VDBTensor and feed it into the model:

-
coords = fvdb.JaggedTensor([
-    (torch.randn(10_000, 3, device='cuda')),
-    (torch.randn(11_000, 3, device='cuda')),
-])
-
-grid = fvdb.gridbatch_from_points(coords)
-features = grid.jagged_like(torch.randn(grid.total_voxels, 32, device='cuda'))
-sinput = fvnn.VDBTensor(grid, features)
-
-model = FVDBUNetBase(32, 1).to('cuda')
-soutput = model(sinput)
-
-
-

The output soutput will carry gradients during training, and you could train the sparse network accordingly. -Please find a fully working example at examples/perception_example.py. The same network is implemented using MinkowskiEngine for reference.

-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/fvdb/tutorials/volume_rendering.html b/documentation/fvdb/tutorials/volume_rendering.html deleted file mode 100644 index 8215d567b..000000000 --- a/documentation/fvdb/tutorials/volume_rendering.html +++ /dev/null @@ -1,766 +0,0 @@ - - - - - - - - - Volume Rendering — fVDB documentation - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Volume Rendering

-

In this example we replace nerfacc’s acceleration structure with fVDB and hence scale to unbounded scenes:

-
import json
-import math
-import os
-from typing import Optional, Tuple, Union
-
-import imageio.v2 as imageio
-import matplotlib.pyplot as plt
-import numpy as np
-import polyscope as ps
-import torch
-import tqdm
-
-from fvdb import GridBatch
-from fvdb import volume_render
-
-TensorPair = Tuple[torch.Tensor, torch.Tensor]
-TensorTriple = Tuple[torch.Tensor, torch.Tensor, torch.Tensor]
-
-class _TruncExp(torch.autograd.Function):
-    @staticmethod
-    def forward(ctx, x):
-        ctx.save_for_backward(x)
-        return torch.exp(x)
-
-    @staticmethod
-    def backward(ctx, dL_dout):
-        x = ctx.saved_tensors[0]
-        return dL_dout * torch.exp(x.clamp(-15, 15))
-
-# SH
-MAX_SH_BASIS = 10
-def eval_sh_bases(basis_dim : int, dirs : torch.Tensor):
-    """
-    Evaluate spherical harmonics bases at unit directions,
-    without taking linear combination.
-    At each point, the final result may the be
-    obtained through simple multiplication.
-
-    :param basis_dim: int SH basis dim. Currently, 1-25 square numbers supported
-    :param dirs: torch.Tensor (..., 3) unit directions
-
-    :return: torch.Tensor (..., basis_dim)
-    """
-    SH_C0 = 0.28209479177387814
-    SH_C1 = 0.4886025119029199
-    SH_C2 = [
-        1.0925484305920792,
-        -1.0925484305920792,
-        0.31539156525252005,
-        -1.0925484305920792,
-        0.5462742152960396
-    ]
-    SH_C3 = [
-        -0.5900435899266435,
-        2.890611442640554,
-        -0.4570457994644658,
-        0.3731763325901154,
-        -0.4570457994644658,
-        1.445305721320277,
-        -0.5900435899266435
-    ]
-    SH_C4 = [
-        2.5033429417967046,
-        -1.7701307697799304,
-        0.9461746957575601,
-        -0.6690465435572892,
-        0.10578554691520431,
-        -0.6690465435572892,
-        0.47308734787878004,
-        -1.7701307697799304,
-        0.6258357354491761,
-    ]
-    result = torch.empty((*dirs.shape[:-1], basis_dim), dtype=dirs.dtype, device=dirs.device)
-    result[..., 0] = SH_C0
-    if basis_dim > 1:
-        x, y, z = dirs.unbind(-1)
-        result[..., 1] = -SH_C1 * y;
-        result[..., 2] = SH_C1 * z;
-        result[..., 3] = -SH_C1 * x;
-        if basis_dim > 4:
-            xx, yy, zz = x * x, y * y, z * z
-            xy, yz, xz = x * y, y * z, x * z
-            result[..., 4] = SH_C2[0] * xy;
-            result[..., 5] = SH_C2[1] * yz;
-            result[..., 6] = SH_C2[2] * (2.0 * zz - xx - yy);
-            result[..., 7] = SH_C2[3] * xz;
-            result[..., 8] = SH_C2[4] * (xx - yy);
-
-            if basis_dim > 9:
-                result[..., 9] = SH_C3[0] * y * (3 * xx - yy);
-                result[..., 10] = SH_C3[1] * xy * z;
-                result[..., 11] = SH_C3[2] * y * (4 * zz - xx - yy);
-                result[..., 12] = SH_C3[3] * z * (2 * zz - 3 * xx - 3 * yy);
-                result[..., 13] = SH_C3[4] * x * (4 * zz - xx - yy);
-                result[..., 14] = SH_C3[5] * z * (xx - yy);
-                result[..., 15] = SH_C3[6] * x * (xx - 3 * yy);
-
-                if basis_dim > 16:
-                    result[..., 16] = SH_C4[0] * xy * (xx - yy);
-                    result[..., 17] = SH_C4[1] * yz * (3 * xx - yy);
-                    result[..., 18] = SH_C4[2] * xy * (7 * zz - 1);
-                    result[..., 19] = SH_C4[3] * yz * (7 * zz - 3);
-                    result[..., 20] = SH_C4[4] * (zz * (35 * zz - 30) + 3);
-                    result[..., 21] = SH_C4[5] * xz * (7 * zz - 3);
-                    result[..., 22] = SH_C4[6] * (xx - yy) * (7 * zz - 1);
-                    result[..., 23] = SH_C4[7] * xz * (xx - 3 * yy);
-                    result[..., 24] = SH_C4[8] * (xx * (xx - 3 * yy) - yy * (3 * xx - yy));
-    return result
-
-def speherical_harmonics(deg: int, sh: torch.Tensor, dirs: torch.Tensor):
-    C0 = 0.28209479177387814
-    C1 = 0.4886025119029199
-    C2 = [
-        1.0925484305920792,
-        -1.0925484305920792,
-        0.31539156525252005,
-        -1.0925484305920792,
-        0.5462742152960396
-    ]
-    C3 = [
-        -0.5900435899266435,
-        2.890611442640554,
-        -0.4570457994644658,
-        0.3731763325901154,
-        -0.4570457994644658,
-        1.445305721320277,
-        -0.5900435899266435
-    ]
-    C4 = [
-        2.5033429417967046,
-        -1.7701307697799304,
-        0.9461746957575601,
-        -0.6690465435572892,
-        0.10578554691520431,
-        -0.6690465435572892,
-        0.47308734787878004,
-        -1.7701307697799304,
-        0.6258357354491761,
-    ]
-
-    # sh is a tensor of shape [N, C, (deg+1)**2]
-    # dirs is a tensor of shape [N, 3]
-    assert 0 <= deg <= 4
-    assert (deg + 1) ** 2 == sh.shape[-1]
-    # C = sh.shape[-2]
-
-    result = C0 * sh[..., 0]
-    if deg > 0:
-        x, y, z = dirs[..., 0:1], dirs[..., 1:2], dirs[..., 2:3]
-        result = (result -
-                C1 * y * sh[..., 1] +
-                C1 * z * sh[..., 2] -
-                C1 * x * sh[..., 3])
-        if deg > 1:
-            xx, yy, zz = x * x, y * y, z * z
-            xy, yz, xz = x * y, y * z, x * z
-            result = (result +
-                    C2[0] * xy * sh[..., 4] +
-                    C2[1] * yz * sh[..., 5] +
-                    C2[2] * (2.0 * zz - xx - yy) * sh[..., 6] +
-                    C2[3] * xz * sh[..., 7] +
-                    C2[4] * (xx - yy) * sh[..., 8])
-
-            if deg > 2:
-                result = (result +
-                        C3[0] * y * (3 * xx - yy) * sh[..., 9] +
-                        C3[1] * xy * z * sh[..., 10] +
-                        C3[2] * y * (4 * zz - xx - yy)* sh[..., 11] +
-                        C3[3] * z * (2 * zz - 3 * xx - 3 * yy) * sh[..., 12] +
-                        C3[4] * x * (4 * zz - xx - yy) * sh[..., 13] +
-                        C3[5] * z * (xx - yy) * sh[..., 14] +
-                        C3[6] * x * (xx - 3 * yy) * sh[..., 15])
-                if deg > 3:
-                    result = (result + C4[0] * xy * (xx - yy) * sh[..., 16] +
-                            C4[1] * yz * (3 * xx - yy) * sh[..., 17] +
-                            C4[2] * xy * (7 * zz - 1) * sh[..., 18] +
-                            C4[3] * yz * (7 * zz - 3) * sh[..., 19] +
-                            C4[4] * (zz * (35 * zz - 30) + 3) * sh[..., 20] +
-                            C4[5] * xz * (7 * zz - 3) * sh[..., 21] +
-                            C4[6] * (xx - yy) * (7 * zz - 1) * sh[..., 22] +
-                            C4[7] * xz * (xx - 3 * yy) * sh[..., 23] +
-                            C4[8] * (xx * (xx - 3 * yy) - yy * (3 * xx - yy)) * sh[..., 24])
-    return result
-
-
-
-def compute_psnr(rgb_gt: torch.Tensor, rgb_est: torch.Tensor) ->  torch.Tensor:
-    x = torch.mean((rgb_gt - rgb_est)**2)
-    return -10. * torch.log10(x)
-
-
-def nerf_matrix_to_ngp(pose: np.ndarray, scale: float = 0.33, offset: Union[tuple, list, torch.Tensor] = (0, 0, 0)) -> np.ndarray:
-    new_pose = np.array([
-        [pose[1, 0], -pose[1, 1], -pose[1, 2], pose[1, 3] * scale + offset[0]],
-        [pose[2, 0], -pose[2, 1], -pose[2, 2], pose[2, 3] * scale + offset[1]],
-        [pose[0, 0], -pose[0, 1], -pose[0, 2], pose[0, 3] * scale + offset[2]],
-        [0, 0, 0, 1],], dtype=np.float32)
-
-    return new_pose
-
-
-def get_rays(pose: torch.Tensor, intrinsic: torch.Tensor, H: int, W: int, depth: Optional[torch.Tensor] = None) -> TensorTriple:
-    fx, fy, cx, cy = intrinsic
-
-    i, j = torch.meshgrid(torch.linspace(0, W-1, W, device='cpu'), torch.linspace(0, H-1, H, device='cpu'), indexing='ij')
-    i = i.t().reshape([1, H*W]).expand([1, H*W]) + 0.5
-    j = j.t().reshape([1, H*W]).expand([1, H*W]) + 0.5
-    zs = torch.ones_like(i)
-    xs = (i - cx) / fx * zs
-    ys = (j - cy) / fy * zs
-    directions = torch.cat((xs.reshape(-1,1), ys.reshape(-1,1), zs.reshape(-1,1)), dim=-1)
-
-    # compute distances
-    if depth is not None:
-        dist = torch.norm(directions * depth[:,None], dim=-1, keepdim=True)
-    else:
-        dist = torch.empty([])
-
-    directions = directions / torch.norm(directions, dim=-1, keepdim=True)
-    rays_d = (pose[:3,:3] @ directions.transpose(0,1)).transpose(0,1)
-
-    rays_o = pose[:3, 3] # [3]
-    rays_o = rays_o[None, :].expand_as(rays_d) # [N, 3]
-
-    return rays_o.squeeze(), rays_d.squeeze(), dist.squeeze()
-
-
-class NeRFDataset:
-    def __init__(self, root_path: str = 'data/lego/', scale: float = 1.0, num_rays: int = 4096, mode: str = 'train'):
-        super().__init__()
-
-        self.root_path  = root_path
-        self.scale = scale
-        self.num_rays = num_rays
-        self.mode = mode
-
-        with open(os.path.join(self.root_path, f'transforms_{self.mode}.json'), 'r', encoding='utf-8') as f:
-            transform = json.load(f)
-
-        # read images
-        frames = transform["frames"]
-        self.n_frames = len(frames)
-
-        # Read the intrinsics
-        image = imageio.imread(os.path.join(self.root_path, frames[0]['file_path'] + '.png')) # [H, W, 3] o [H, W, 4]
-        self.H, self.W  = image.shape[:2]
-        fl_x = fl_y = self.W / (2 * np.tan(transform['camera_angle_x'] / 2)) if 'camera_angle_x' in transform else None
-        cx = (transform['cx']) if 'cx' in transform else (self.W / 2)
-        cy = (transform['cy']) if 'cy' in transform else (self.H / 2)
-        self.intrinsics = np.array([fl_x, fl_y, cx, cy])
-
-        self.rays = []
-        self.rgbs = []
-        self.depths = []
-        self.poses = []
-        self.pc = []
-        self.pc_rgbs = []
-
-        for f in tqdm.tqdm(frames, desc=f'Loading {self.mode} data'):
-            f_path = os.path.join(self.root_path, f['file_path'] + '.png')
-            pose = nerf_matrix_to_ngp(np.array(f['transform_matrix'], dtype=np.float32), scale=self.scale)
-            image = imageio.imread(f_path) / 255.0 # [H, W, 3] o [H, W, 4]
-            depth = None
-
-            if self.mode == 'train':
-                f_path_depth = os.path.join(self.root_path, f['file_path'] + '_depth.npy')
-                depth = np.load(f_path_depth).reshape(-1)
-
-            ray_o, ray_d, depth = get_rays(torch.from_numpy(pose), torch.from_numpy(self.intrinsics), self.H, self.W, depth)
-
-            # Scale the depth
-            depth_mask = depth < 1000
-            depth *= scale
-
-            rgbs = torch.from_numpy(image).reshape(self.H * self.W, -1)
-            self.poses.append(pose)
-            self.rays.append(torch.cat([ray_o, ray_d], 1))
-            self.rgbs.append(rgbs)
-
-            if self.mode == 'train':
-                self.depths.append(depth)
-                self.pc.append(ray_o[depth_mask, :3] + ray_d[depth_mask, :3] * depth[depth_mask,None])
-                self.pc_rgbs.append(rgbs[depth_mask,:3])
-
-        self.rays = torch.vstack(self.rays)
-        self.rgbs = torch.vstack(self.rgbs)
-
-        if self.mode == 'train':
-            self.depths = torch.cat(self.depths) # Note that depth denotes the distance along the ray
-            self.pc = torch.vstack(self.pc)
-            self.pc_rgbs = torch.vstack(self.pc_rgbs)
-
-    def get_point_cloud(self, downsample_ratio: float = 1.0, return_color: bool = False) -> Union[torch.Tensor, TensorPair]:
-        if self.mode == 'train':
-            assert isinstance(self.pc, torch.Tensor)
-            if return_color:
-                assert isinstance(self.pc_rgbs, torch.Tensor)
-                dri = int(1 / downsample_ratio)
-                pts = self.pc[::dri, :]
-                rgb = self.pc_rgbs[::dri, :]
-                return pts, rgb
-            return self.pc[::int(1/downsample_ratio),:]
-        else:
-            raise ValueError('Only training data has depth information!')
-
-    def __len__(self):
-        if self.mode == 'train':
-            return 1000
-        else:
-            return self.n_frames
-
-
-    def __getitem__(self, idx):
-        # raise an error to now iterate in infinity
-        if idx >= len(self): raise IndexError
-
-        if self.mode == 'train':
-            assert isinstance(self.rays, torch.Tensor)
-            idxs = np.random.choice(self.rays.shape[0], self.num_rays)
-            return  {'rays_o': self.rays[idxs,:3],
-                     'rays_d': self.rays[idxs,3:6],
-                     'rgba': self.rgbs[idxs],
-                     'depth': self.depths[idxs],
-                     'idxs': idxs}
-
-        else:
-            # raise an error to now iterate in infinity
-            if idx >= len(self): raise IndexError
-            assert isinstance(self.rays, torch.Tensor)
-            assert isinstance(self.rgbs, torch.Tensor)
-            return  {'rays_o': self.rays[idx * self.W * self.H : (idx + 1) * self.W * self.H, :3],
-                     'rays_d': self.rays[idx * self.W * self.H : (idx + 1) * self.W * self.H, 3:6],
-                     'rgba': self.rgbs[idx * self.W * self.H : (idx + 1) * self.W * self.H,],
-                     'depth': None}
-
-
-def make_ray_grid(origin, nrays, minb=(-0.45, -0.45), maxb=(0.45, 0.45),
-                  device: Union[str, torch.device]='cpu', dtype=torch.float32):
-
-    ray_o = torch.tensor([origin] * nrays**2) #+ p.mean(0, keepdim=True)
-    ray_d = torch.from_numpy(
-        np.stack([a.ravel() for a in
-                  np.mgrid[minb[0]:maxb[0]:nrays*1j,
-                           minb[1]:maxb[1]:nrays*1j]] +
-                  [np.ones(nrays**2)], axis=-1).astype(np.float32))
-    ray_d /= torch.norm(ray_d, dim=-1, keepdim=True)
-
-    ray_o, ray_d = ray_o.to(device).to(dtype), ray_d.to(device).to(dtype)
-
-    return ray_o, ray_d
-
-
-def evaluate_density_and_color(dual_grid: GridBatch, sh_features: torch.Tensor, o_features: torch.Tensor,
-                               ray_d: torch.Tensor, pts: torch.Tensor) -> TensorPair:
-
-    pt_features = dual_grid.sample_trilinear(pts, sh_features.view(sh_features.shape[0], -1)).jdata.view(pts.shape[0], 3, 9)
-    pt_o_features = dual_grid.sample_trilinear(pts, o_features.unsqueeze(-1)).jdata.squeeze(-1)
-    return _TruncExp.apply(pt_o_features), torch.sigmoid(speherical_harmonics(2, pt_features, ray_d))
-
-
-def render(primal_grid: GridBatch, dual_grid: GridBatch,
-           sh_features: torch.Tensor, o_features: torch.Tensor,
-           ray_o: torch.Tensor, ray_d: torch.Tensor,
-           tmin: torch.Tensor, tmax: torch.Tensor, step_size: float,
-           t_threshold: float = 0.0, chunk: bool = False) -> TensorTriple:
-
-    pack_info, ray_idx, ray_intervals = \
-        primal_grid.uniform_ray_samples(ray_o, ray_d, tmin, tmax, step_size)
-
-    ray_t = ray_intervals.jdata.mean(1)
-    ray_delta_t = (ray_intervals.jdata[:, 1] - ray_intervals.jdata[:, 0]).contiguous()
-    ray_pts = ray_o[ray_idx.jdata] + ray_t[:, None] * ray_d[ray_idx.jdata]
-
-    if chunk:
-        ray_density = []
-        ray_color = []
-        ray_d = ray_d[ray_idx.jdata]
-        chunk_size = 400000
-
-        for i in range(ray_d.shape[0]//chunk_size + 1):
-            ray_density_chunk, ray_color_chunk = evaluate_density_and_color(dual_grid, sh_features, o_features,
-                                                                            ray_d[i*chunk_size:(i+1)*chunk_size, :],
-                                                                            ray_pts[i*chunk_size:(i+1)*chunk_size, :])
-
-            ray_density.append(ray_density_chunk)
-            ray_color.append(ray_color_chunk)
-
-        ray_density = torch.cat(ray_density, 0)
-        ray_color = torch.vstack(ray_color)
-    else:
-        ray_density, ray_color = evaluate_density_and_color(dual_grid, sh_features, o_features,
-                                                            ray_d[ray_idx.jdata], ray_pts)
-
-    # Do the volume rendering
-    # print(ray_density.shape, ray_color.shape, ray_delta_t.shape, ray_t.shape, pack_info.jdata.shape)
-    rgb, depth, opacity, _, _ = volume_render(ray_density, ray_color, ray_delta_t,
-                                                         ray_t, pack_info.jdata, t_threshold)
-
-    return rgb, depth, opacity[:, None]
-
-
-def tv_loss(dual_grid: GridBatch, ijk: torch.Tensor, sh_features: torch.Tensor, o_features: torch.Tensor, res) -> TensorPair:
-    nhood = dual_grid.neighbor_indexes(ijk, 1).jdata.view(-1, 3, 3, 3)
-    n_up = nhood[:, 1, 0, 0]
-    n_right = nhood[:, 0, 1, 0]
-    n_front = nhood[:, 0, 0, 1]
-    n_center = nhood[:, 0, 0, 0]
-
-    mask = torch.logical_and(torch.logical_and(n_center != -1, n_up != -1), n_front != -1)
-    fmask = mask.float()
-    n_up_mask, n_right_mask, n_center_mask, n_front_mask = n_up[mask], n_right[mask], n_center[mask], n_front[mask]
-
-    diff_up_sh = (sh_features[n_up_mask] - sh_features[n_center_mask]) / (256.0 / res)
-    diff_right_sh = (sh_features[n_right_mask] - sh_features[n_center_mask]) / (256.0 / res)
-    diff_front_sh = (sh_features[n_front_mask] - sh_features[n_center_mask]) / (256.0 / res)
-
-    diff_up_o = (o_features[n_up] * fmask - o_features[n_center]) / (256.0 / res)
-    diff_right_o = (o_features[n_right] * fmask- o_features[n_center]) / (256.0 / res)
-    diff_front_o = (o_features[n_front] * fmask - o_features[n_center]) / (256.0 / res)
-
-    tv_reg_sh = (diff_up_sh ** 2.0 + diff_right_sh ** 2.0 + diff_front_sh ** 2.0).sum(-1).sum(-1)
-    tv_reg_o = (diff_up_o ** 2.0 + diff_right_o ** 2.0 + diff_front_o ** 2.0)
-    return tv_reg_sh.mean(), tv_reg_o.mean()
-
-
-def main():
-    # Configuration parameters
-    device = torch.device('cuda')
-    dtype = torch.float32
-    # scene_aabb = 1.0
-    starting_resolution = 256
-    resolution = starting_resolution
-    vox_size = (1.0 / resolution, 1.0 / resolution, 1.0 / resolution)
-    vox_origin = (vox_size[0]/2, vox_size[1]/2, vox_size[2]/2)
-    ray_step_size = math.sqrt(3) / 512
-    rays_per_batch = 4096
-    lr_o = 1e-1
-    lr_sh = 1e-2
-
-    plot_every = 2
-    num_epochs = 30
-    bg_color = (0.0, 0.0, 0.0)
-    t_threshold = 1e-5
-
-    # Create the dataset. Assumes there is a file <repository_root>/data/lego_test.h5
-    data_path = os.path.join(os.path.dirname(__file__), "..", "data/lego/")
-
-    if not os.path.exists(data_path):
-        data_url = "https://drive.google.com/drive/folders/1i6qMn-mnPwPEioiNIFMO8QJlTjU0dS1b?usp=share_link"
-        raise RuntimeError(f"You need to download the data at {data_url} "
-                           "into <repository_root>/data "
-                           "in order to run this script")
-
-    train_dataset = NeRFDataset(data_path, scale=0.33, num_rays=rays_per_batch, mode='train')
-    test_dataset = NeRFDataset(data_path, scale=0.33, num_rays=rays_per_batch, mode='test')
-
-
-    # Create a sparse grid used to support features and do ray queries
-    print("Building grid...")
-    primal_grid = GridBatch(device=device)
-    primal_grid.set_from_dense_grid(1, [resolution]*3, [-resolution//2]*3, voxel_sizes=vox_size, voxel_origins=vox_origin)
-    dual_grid = primal_grid # primal_grid.dual_grid()
-
-    print("Done bulding the grid!")
-
-    # Initialize features at the voxel centers
-    xyz = primal_grid.ijk.jdata / (resolution / 2.0)
-    print(xyz.min(0)[0], xyz.max(0)[0])
-
-    sh_features = torch.stack([eval_sh_bases(9, xyz)]*3, dim=1)
-    print(sh_features.shape)
-    sh_features = sh_features.to(device=device, dtype=dtype)
-    o_features = torch.rand(dual_grid.total_voxels)
-    o_features = o_features.to(device=device, dtype=dtype)
-    o_features.requires_grad = True
-    sh_features.requires_grad = True
-
-    # Init optimizer
-    param_group = []
-    param_group.append({'params': o_features, 'lr': lr_o })
-    param_group.append({'params': sh_features, 'lr': lr_sh })
-    # optimizer = torch.optim.Adam(param_group)
-    optimizer = torch.optim.RMSprop(param_group)
-    # scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30, eta_min=lr/30)
-
-
-    print("Starting training!")
-    ps.init()  # Initialize 3d plotting
-    for epoch in tqdm.trange(num_epochs):
-        if resolution <= starting_resolution:
-            all_ijk = dual_grid.ijk.jdata
-        else:
-            all_ijk = None
-        pbar = tqdm.tqdm(enumerate(train_dataset))  # type: ignore
-        for _, batch in pbar: # type: ignore
-            optimizer.zero_grad()
-            ray_o, ray_d = batch['rays_o'].to(device=device, dtype=dtype), \
-                           batch['rays_d'].to(device=device, dtype=dtype)
-            tmin = torch.zeros(ray_o.shape[0]).to(ray_o)
-            tmax = torch.full_like(tmin, 1e10)
-
-            # Render color and depth along rays
-            rgb, depth, opacity = render(primal_grid, dual_grid, sh_features, o_features, ray_o, ray_d,
-                                         tmin, tmax, ray_step_size, t_threshold=t_threshold)
-
-            rgb = opacity * rgb + (1.0 - opacity) * torch.tensor(bg_color).to(rgb)[None, :]
-
-            # RGB loss
-            rgb_gt = batch['rgba'].to(rgb)[:,:3]
-            loss_rgb = torch.nn.functional.mse_loss(rgb, rgb_gt) # torch.nn.functional.huber_loss(rgb, rgb_gt) / 5.
-
-            # Depth loss
-            # depth_gt = batch['depth'].to(rgb)
-            # depth_mask = depth_gt < np.sqrt(3) # Mask out rays that miss the object
-            # loss_depth = torch.nn.functional.l1_loss(depth[depth_mask], depth_gt[depth_mask]) / 100
-
-            if resolution <= starting_resolution:
-                assert all_ijk is not None
-                random_ijk = all_ijk[torch.randperm(all_ijk.shape[0])[:int(.1*dual_grid.total_voxels)]]
-                tv_reg_sh, tv_reg_o = tv_loss(dual_grid, random_ijk, sh_features, o_features, resolution)
-                tv_reg = 1e-1 * tv_reg_sh + 1e-2 * tv_reg_o
-            else:
-                tv_reg_sh = torch.tensor([0.0]).to(device)
-                tv_reg_o = torch.tensor([0.0]).to(device)
-                tv_reg = torch.tensor([0.0]).to(device)
-            # Total loss to minimize
-            loss = loss_rgb + tv_reg #+ 0.1 * loss_depth
-
-            loss.backward()
-            optimizer.step()
-
-            # Compute current PSNR
-            psnr = compute_psnr(rgb, rgb_gt)
-
-            # Log losses in tqdm progress bar
-            pbar.set_postfix({"Loss": f"{loss.item():.4f}",
-                              "Loss RGB": f"{loss_rgb.item():.4f}",
-                            #   "Loss Depth": f"{loss_depth.item():.4f}",
-                              "Loss TV (sh)": f"{tv_reg_sh.item():.4f}",
-                              "Loss TV (o)": f"{tv_reg_o.item():.4f}",
-                              "PSNR": f"{psnr.item():.2f}"})
-
-        # scheduler.step()
-
-        if epoch % plot_every == 0:
-            with torch.no_grad():
-                torch.cuda.empty_cache()
-
-                grid_res = 512
-                ray_o, ray_d = make_ray_grid((0., 0.15, 1.2), grid_res, device=device, dtype=dtype)
-                ray_d = - ray_d
-                tmin = torch.zeros(ray_o.shape[0]).to(ray_o)
-                tmax = torch.full_like(tmin, 1e10)
-                rgb, depth, opacity = render(primal_grid,dual_grid, sh_features, o_features, ray_o, ray_d,
-                                             tmin, tmax, ray_step_size, t_threshold=t_threshold, chunk=True)
-                rgb = opacity * rgb + (1.0 - opacity) * torch.tensor(bg_color).to(rgb)[None, :]
-
-                rgb_img = rgb.clip(0.0, 1.0).detach().cpu().numpy().reshape([grid_res, grid_res, 3])
-                depth_img = depth.detach().cpu().numpy().reshape([grid_res, grid_res])
-
-                plt.figure()
-                plt.imshow(rgb_img)
-                plt.figure()
-                plt.imshow(depth_img)
-                plt.show()
-
-                ray_v = torch.cat([ray_o, ray_o + ray_d*0.33]).cpu().numpy()
-                ray_e = np.array([[i, i + ray_o.shape[0]] for i in range(ray_o.shape[0])])
-
-                ps.register_curve_network("rays", ray_v, ray_e, radius=0.00002)
-                vox_ijk = primal_grid.ijk.jdata
-                vox_ctrs = primal_grid.grid_to_world(vox_ijk.to(dtype)).jdata
-                vox_density, vox_color = evaluate_density_and_color(dual_grid, sh_features, o_features,
-                                                                    torch.ones_like(vox_ctrs), vox_ctrs)
-
-                # Subdivide
-                if epoch > 0:
-                    sh_features, sub_grid = dual_grid.subdivide(2, sh_features.view(sh_features.shape[0], -1), mask=vox_density > 0.25)
-                    o_features, sub_grid = dual_grid.subdivide(2, o_features.unsqueeze(-1), mask=vox_density > 0.25)
-                    o_features = o_features.jdata.squeeze(-1)
-                    sh_features = sh_features.jdata.reshape(sh_features.rshape[0], 3, -1)
-                    sh_features.requires_grad = True
-                    o_features.requires_grad = True
-                    resolution *= 2.0
-                    ray_step_size /= 2.0
-
-                    print(f"Subdivided grid with {dual_grid.total_voxels} to {sub_grid.total_voxels}")
-                    dual_grid = sub_grid
-                    primal_grid = sub_grid
-
-                camera_origins = []
-                for pose in test_dataset.poses:
-                    camera_origins.append(pose @ np.array([0.0, 0.0, 0.0, 1.0]))
-                camera_origins = np.stack(camera_origins)[:, :3]
-                ps.register_point_cloud("camera origins", camera_origins)
-
-                v, e = primal_grid.viz_edge_network
-                v, e = v.jdata, e.jdata
-                ps.register_curve_network("grid", v.cpu(), e.cpu(), radius=0.0001)
-                pc = ps.register_point_cloud("vox centers", vox_ctrs.cpu(),
-                                            point_render_mode='quad')
-                pc.add_scalar_quantity("density", vox_density.cpu(), enabled=True)
-                pc.add_scalar_quantity("density thresh", (vox_density.cpu() < .25).float(), enabled=True)
-                pc.add_color_quantity("rgb", vox_color.cpu(), enabled=False)
-                ps.show()
-
-
-    print("Starting testing!")
-    pbar = tqdm.tqdm(enumerate(test_dataset))  # type: ignore
-    psnr_test = []
-    for _, batch in pbar: # type: ignore
-        with torch.no_grad():
-            ray_o, ray_d = batch['rays_o'].to(device=device, dtype=dtype), \
-                           batch['rays_d'].to(device=device, dtype=dtype)
-            tmin = torch.zeros(ray_o.shape[0]).to(ray_o)
-            tmax = torch.full_like(tmin, 1e10)
-            rgb_gt = batch['rgba'].to(rgb)[:,:3]  # type: ignore
-
-            # Render color and depth along rays
-            rgb, depth, opacity = render(primal_grid, dual_grid, sh_features, o_features, ray_o, ray_d,
-                                         tmin, tmax, ray_step_size, t_threshold=t_threshold, chunk=True)
-
-            rgb = opacity * rgb + (1.0 - opacity) * torch.tensor(bg_color).to(rgb)[None, :]
-
-            # Compute current PSNR
-            psnr = compute_psnr(rgb, rgb_gt)
-            psnr_test.append(psnr.item())
-            # Log losses in tqdm progress bar
-            pbar.set_postfix({"PSNR": f"{psnr.item():.2f}"})
-
-    print(f"Mean PSNR on the test set across {len(test_dataset)} images: {torch.tensor(psnr_test).mean().item()} ")
-
-if __name__ == "__main__":
-    main()
-
-
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/documentation/index.html b/documentation/index.html index 250971488..2397393b2 100644 --- a/documentation/index.html +++ b/documentation/index.html @@ -66,7 +66,7 @@

Documentation

for an introduction to the interface between OpenVDB and Mathematica.

See - the ƒVDB online documentation + the ƒVDB online documentation for an introduction to ƒVDB and its API reference.

SIGGRAPH 2023 course