Update to 2026-09-17 pipeline snapshot; add weights, L20 assets and recording via Git LFS

Source: RGB-D -> Dyn-HaMR -> L20 retargeting -> FoundationPose -> reference repair -> SPIDER,
documented in docs/PIPELINE_LATEST.md and docs/SETUP_AND_WEIGHTS.md. Adds FoundationPose and
nvdiffrast upstream snapshots, requirements/pipeline_venv.txt and the FoundationPose weight
manifest/downloader.

Assets (Git LFS): weights/ (WiLoR detector, HandFlow denoiser, UniDepth-L), FoundationPose
checkpoints, HaMeR checkpoint, Dyn-HaMR HMP model and BMC constraints, L20 URDF/meshes, the
20260915_171525 D405 recording and the two box CADs. MANO models are not redistributed
(third_party/hamer/_DATA/data/mano/README.txt). Environments, caches and run outputs excluded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
liyang
2026-09-17 11:27:46 +08:00
parent 7e4ef6f98b
commit ae28d55f81
761 changed files with 55598 additions and 59 deletions
+105
View File
@@ -0,0 +1,105 @@
notes: ''
n_step: 1000
netdepth: 8
netwidth: 256
netdepth_fine: 8
netwidth_fine: 256
N_rand: 2048 # Batch number of rays
first_frame_ray_in_batch: 0
lrate: 0.01
lrate_pose: 0.01
pose_optimize_start: 0
decay_rate: 0.1
chunk: 99999999999
netchunk: 6553600
no_batching: 0
amp: true
N_samples: 128 #number of coarse samples per ray
N_samples_around_depth: 128
N_importance: 0
N_importance_iter: 1
perturb: 1
use_viewdirs: 1
i_embed: 1 #set 1 for hashed embedding, 0 for default positional encoding, 2 for spherical; 3 for octree grid
i_embed_views: 2 #set 1 for hashed embedding, 0 for default positional encoding, 2 for spherical
multires: 8 #log2 of max freq for positional encoding (3D location)
multires_views: 3 #log2 of max freq for positional encoding (2D direction)
feature_grid_dim: 2
raw_noise_std: 0
white_bkgd: 0
gradient_max_norm: 0.1
gradient_pose_max_norm: 0.1
# logging/saving options
i_print: 500
i_img: 500
i_weights: 500
i_mesh: 500
i_nerf_normals: 500
i_save_ray: 500
i_pose: 500
save_octree_clouds: True
finest_res: 256
base_res: 32
num_levels: 16
log2_hashmap_size: 22
datadir: /mnt/9a72c439-d0a7-45e8-8d20-d7a235d02763/github/HashNeRF-pytorch/data/bundlesdf_bundlesdf_e03000196
n_train_image: 300
use_octree: 1
first_frame_weight: 1
denoise_depth_use_octree_cloud: true
octree_embed_base_voxel_size: 0.02
octree_smallest_voxel_size: 0.02 # This determines the smallest feature vox size
octree_raytracing_voxel_size: 0.02
octree_dilate_size: 0.02 # meters
down_scale_ratio: 1
bounding_box: [[-1,-1,-1], [1,1,1]]
farthest_pose_sampling: 0 # Sampling train images. This replace uniform skip
use_mask: 1
dilate_mask_size: 0
rays_valid_depth_only: true
near: 0.1
far: 2
rgb_weight: 1
depth_weight: 0
trunc: 0.01 #length of the truncation region in meters
trunc_start: 0.01
sdf_lambda: 5
neg_trunc_ratio: 1 # -trunc distance ratio compared to +trunc
trunc_decay_type: ''
sdf_loss_type: l2
fs_weight: 1000
empty_weight: 1
fs_rgb_weight: 0
trunc_weight: 6000
sparse_loss_weight: 0
tv_loss_weight: 0
frame_features: 2 #number of channels of the learnable per-frame features
optimize_poses: 1 #optimize a pose refinement for the initial poses
pose_reg_weight: 0
point_cloud_loss_weight: 0
point_cloud_loss_normal_weight: 0
eikonal_weight: 0
normal_loss_weight: 0
feature_reg_weight: 0.1
deformation_reg_weight: 0
use_deformation_field: 0 #use a deformation field to account for inaccuracies in intrinsic parameters
max_pix_shift: 0.02 # Pixel shift ratio relative to image size
share_coarse_fine: 1
mode: sdf
fs_sdf: 1 # Uncertain free space
crop: 0
mesh_resolution: 0.003
max_trans: 0.02 # meters
max_rot: 10 # deg
continual: True
######### dbscan
dbscan_eps: 0.01
dbscan_eps_min_samples: 1
####### bundlenerf
sync_max_delay: 0 # 0 for strict sync
+105
View File
@@ -0,0 +1,105 @@
notes: ''
n_step: 1000
netdepth: 8
netwidth: 256
netdepth_fine: 8
netwidth_fine: 256
N_rand: 2048 # Batch number of rays
first_frame_ray_in_batch: 0
lrate: 0.01
lrate_pose: 0.01
pose_optimize_start: 0
decay_rate: 0.1
chunk: 99999999999
netchunk: 6553600
no_batching: 0
amp: true
N_samples: 128 #number of coarse samples per ray
N_samples_around_depth: 128
N_importance: 0
N_importance_iter: 1
perturb: 1
use_viewdirs: 1
i_embed: 1 #set 1 for hashed embedding, 0 for default positional encoding, 2 for spherical; 3 for octree grid
i_embed_views: 2 #set 1 for hashed embedding, 0 for default positional encoding, 2 for spherical
multires: 8 #log2 of max freq for positional encoding (3D location)
multires_views: 3 #log2 of max freq for positional encoding (2D direction)
feature_grid_dim: 2
raw_noise_std: 0
white_bkgd: 0
gradient_max_norm: 0.1
gradient_pose_max_norm: 0.1
# logging/saving options
i_print: 500
i_img: 500
i_weights: 500
i_mesh: 500
i_nerf_normals: 500
i_save_ray: 500
i_pose: 500
save_octree_clouds: True
finest_res: 512
base_res: 32
num_levels: 16
log2_hashmap_size: 22
datadir: /mnt/9a72c439-d0a7-45e8-8d20-d7a235d02763/github/HashNeRF-pytorch/data/bundlesdf_bundlesdf_e03000196
n_train_image: 300
use_octree: 1
first_frame_weight: 1
denoise_depth_use_octree_cloud: true
octree_embed_base_voxel_size: 0.02
octree_smallest_voxel_size: 0.02 # This determines the smallest feature vox size
octree_raytracing_voxel_size: 0.02
octree_dilate_size: 0.02 # meters
down_scale_ratio: 1
bounding_box: [[-1,-1,-1], [1,1,1]]
farthest_pose_sampling: 0 # Sampling train images. This replace uniform skip
use_mask: 1
dilate_mask_size: 0
rays_valid_depth_only: true
near: 0.1
far: 2
rgb_weight: 100
depth_weight: 0
trunc: 0.01 #length of the truncation region in meters
trunc_start: 0.01
sdf_lambda: 5
neg_trunc_ratio: 1 # -trunc distance ratio compared to +trunc
trunc_decay_type: ''
sdf_loss_type: l2
fs_weight: 100
empty_weight: 1
fs_rgb_weight: 0
trunc_weight: 6000
sparse_loss_weight: 0
tv_loss_weight: 0
frame_features: 2 #number of channels of the learnable per-frame features
optimize_poses: 1 #optimize a pose refinement for the initial poses
pose_reg_weight: 0
point_cloud_loss_weight: 0
point_cloud_loss_normal_weight: 0
eikonal_weight: 0
normal_loss_weight: 0
feature_reg_weight: 0.1
deformation_reg_weight: 0
use_deformation_field: 0 #use a deformation field to account for inaccuracies in intrinsic parameters
max_pix_shift: 0.02 # Pixel shift ratio relative to image size
share_coarse_fine: 1
mode: sdf
fs_sdf: 1 # Uncertain free space
crop: 0
mesh_resolution: 0.003
max_trans: 0.02 # meters
max_rot: 10 # deg
continual: True
######### dbscan
dbscan_eps: 0.01
dbscan_eps_min_samples: 1
####### bundlenerf
sync_max_delay: 0 # 0 for strict sync
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
*
* NVIDIA CORPORATION and its licensors retain all intellectual property
* and proprietary rights in and to this software, related documentation
* and any modifications thereto. Any use, reproduction, disclosure or
* distribution of this software and related documentation without an express
* license agreement from NVIDIA CORPORATION is strictly prohibited.
*/
#include <torch/extension.h>
#include "common.h"
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("sampleRaysUniformOccupiedVoxels", &sampleRaysUniformOccupiedVoxels);
m.def("postprocessOctreeRayTracing", &postprocessOctreeRayTracing);
m.def("rayColorToTextureImageCUDA", &rayColorToTextureImageCUDA);
}
+313
View File
@@ -0,0 +1,313 @@
/*
* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
*
* NVIDIA CORPORATION and its licensors retain all intellectual property
* and proprietary rights in and to this software, related documentation
* and any modifications thereto. Any use, reproduction, disclosure or
* distribution of this software and related documentation without an express
* license agreement from NVIDIA CORPORATION is strictly prohibited.
*/
#include <cuda.h>
#include <cuda_fp16.h>
#include <cuda_runtime.h>
#include <bits/stdc++.h>
#include <ATen/cuda/CUDAContext.h>
#include <torch/torch.h>
#include <stdint.h>
#include <cstdio>
#include <stdint.h>
#include <stdexcept>
#include <limits>
#include <set>
#include "common.h"
#include "Eigen/Dense"
/**
* @brief
*
* @tparam scalar_t
* @param z_sampled
* @param z_in_out
* @param z_vals
* @return __global__
*/
template <typename scalar_t>
__global__ void sample_rays_uniform_occupied_voxels_kernel(const torch::PackedTensorAccessor32<scalar_t,2,torch::RestrictPtrTraits> z_sampled, const torch::PackedTensorAccessor32<scalar_t,3,torch::RestrictPtrTraits> z_in_out, torch::PackedTensorAccessor32<scalar_t,2,torch::RestrictPtrTraits> z_vals)
{
const int i_ray = blockIdx.x * blockDim.x + threadIdx.x;
const int i_sample = blockIdx.y * blockDim.y + threadIdx.y;
if (i_ray>=z_sampled.size(0)) return;
if (i_sample>=z_sampled.size(1)) return;
int i_box = 0;
float z_remain = z_sampled[i_ray][i_sample];
auto z_in_out_cur_ray = z_in_out[i_ray];
const float eps = 1e-4;
const int max_n_box = z_in_out.size(1);
if (z_in_out_cur_ray[0][0]==0) return;
while (1)
{
if (i_box>=max_n_box)
{
if (z_remain<=eps)
{
z_vals[i_ray][i_sample] = z_in_out_cur_ray[max_n_box-1][1];
}
else
{
printf("ERROR sample_rays_uniform_occupied_voxels_kernel: z_remain=%f, i_ray=%d, i_sample=%d, i_box=%d, z_in_out_cur_ray=(%f,%f)\n",z_remain,i_ray,i_sample,i_box,z_in_out_cur_ray[i_box][0],z_in_out_cur_ray[i_box][1]);
for (int i=0;i<z_in_out.size(1);i++)
{
printf("z_in_out_cur_ray[%d]=(%f,%f)\n",i,z_in_out_cur_ray[i][0],z_in_out_cur_ray[i][1]);
}
while (1){};
}
return;
}
if (z_in_out_cur_ray[i_box][0]==0)
{
if (z_remain<=eps && i_box>=1)
{
z_vals[i_ray][i_sample] = z_in_out_cur_ray[i_box-1][1];
return;
}
else
{
printf("ERROR sample_rays_uniform_occupied_voxels_kernel: z_remain=%f, i_ray=%d, i_sample=%d, i_box=%d, z_in_out_cur_ray=(%f,%f)\n",z_remain,i_ray,i_sample,i_box,z_in_out_cur_ray[i_box][0],z_in_out_cur_ray[i_box][1]);
for (int i=0;i<z_in_out.size(1);i++)
{
printf("z_in_out_cur_ray[%d]=(%f,%f)\n",i,z_in_out_cur_ray[i][0],z_in_out_cur_ray[i][1]);
}
while (1){};
}
}
float box_len = z_in_out_cur_ray[i_box][1]-z_in_out_cur_ray[i_box][0];
if (z_remain<=box_len)
{
z_vals[i_ray][i_sample] = z_in_out_cur_ray[i_box][0] + z_remain;
return;
}
z_remain -= box_len;
i_box++;
}
}
at::Tensor sampleRaysUniformOccupiedVoxels(const at::Tensor z_in_out, const at::Tensor z_sampled, at::Tensor z_vals)
{
CHECK_INPUT(z_in_out);
CHECK_INPUT(z_sampled);
CHECK_INPUT(z_vals);
AT_ASSERTM(z_vals.sizes()==z_sampled.sizes());
const int N_rays = z_sampled.sizes()[0];
const int N_samples = z_sampled.sizes()[1];
const int threadx = 32;
const int thready = 32;
AT_DISPATCH_FLOATING_TYPES(z_in_out.type(), "sample_rays_uniform_occupied_voxels_kernel", ([&]
{
sample_rays_uniform_occupied_voxels_kernel<scalar_t><<<{divCeil(N_rays,threadx),divCeil(N_samples,thready)}, {threadx,thready}>>>(z_sampled.packed_accessor32<scalar_t,2,torch::RestrictPtrTraits>(),z_in_out.packed_accessor32<scalar_t,3,torch::RestrictPtrTraits>(),z_vals.packed_accessor32<scalar_t,2,torch::RestrictPtrTraits>());
}));
return z_vals;
}
template<class scalar_t>
__global__ void postprocessOctreeRayTracingKernel(const torch::PackedTensorAccessor32<long,1,torch::RestrictPtrTraits> ray_index, const torch::PackedTensorAccessor32<scalar_t,2,torch::RestrictPtrTraits> depth_in_out, const torch::PackedTensorAccessor32<long,1,torch::RestrictPtrTraits> unique_intersect_ray_ids, const torch::PackedTensorAccessor32<long,1,torch::RestrictPtrTraits> start_poss, torch::PackedTensorAccessor32<scalar_t,3,torch::RestrictPtrTraits> depths_in_out_padded)
{
const int unique_id_pos = blockIdx.x * blockDim.x + threadIdx.x;
if (unique_id_pos>=unique_intersect_ray_ids.size(0)) return;
const int i_ray = unique_intersect_ray_ids[unique_id_pos];
int i_intersect = 0;
auto cur_depths_in_out_padded = depths_in_out_padded[i_ray];
for (int i=start_poss[unique_id_pos];i<ray_index.size(0);i++)
{
if (ray_index[i]!=i_ray) break;
if (depth_in_out[i][0]==0 || depth_in_out[i][1]==0) break;
if (depth_in_out[i][0]>depth_in_out[i][1]) continue;
if (abs(depth_in_out[i][1]-depth_in_out[i][0])<1e-4) continue;
cur_depths_in_out_padded[i_intersect][0] = depth_in_out[i][0];
cur_depths_in_out_padded[i_intersect][1] = depth_in_out[i][1];
i_intersect++;
}
}
at::Tensor postprocessOctreeRayTracing(const at::Tensor ray_index, const at::Tensor depth_in_out, const at::Tensor unique_intersect_ray_ids, const at::Tensor start_poss, const int max_intersections, const int N_rays)
{
CHECK_INPUT(ray_index);
CHECK_INPUT(depth_in_out);
CHECK_INPUT(start_poss);
const int n_unique_ids = unique_intersect_ray_ids.sizes()[0];
at::Tensor depths_in_out_padded = at::zeros({N_rays,max_intersections,2}, torch::TensorOptions().dtype(torch::kFloat32).device(torch::kCUDA, 0).requires_grad(false));
dim3 threads = {256};
dim3 blocks = {divCeil(n_unique_ids,threads.x)};
AT_DISPATCH_FLOATING_TYPES(depth_in_out.type(), "postprocessOctreeRayTracingKernel", ([&]
{
postprocessOctreeRayTracingKernel<scalar_t><<<blocks,threads>>>(ray_index.packed_accessor32<long,1,torch::RestrictPtrTraits>(), depth_in_out.packed_accessor32<scalar_t,2,torch::RestrictPtrTraits>(), unique_intersect_ray_ids.packed_accessor32<long,1,torch::RestrictPtrTraits>(), start_poss.packed_accessor32<long,1,torch::RestrictPtrTraits>(), depths_in_out_padded.packed_accessor32<scalar_t,3,torch::RestrictPtrTraits>());
}));
return depths_in_out_padded;
}
at::Tensor calculateBarycentricCoordinate3D(const at::Tensor &triangle, const at::Tensor &p)
{
auto vector_A = triangle[1]-triangle[2];
auto vector_B = triangle[1]-triangle[0];
auto normal = vector_A.cross(vector_B);
auto areaABC = (normal * at::cross(triangle[1]-triangle[0], triangle[2]-triangle[0])).sum();
auto areaPBC = (normal * at::cross(triangle[1]-p, triangle[2]-p)).sum();
auto areaPCA = (normal * at::cross(triangle[2]-p, triangle[0]-p)).sum();
at::Tensor ws = at::zeros({3}).to(torch::kFloat32);
ws[0] = areaPBC / areaABC;
ws[1] = areaPCA / areaABC;
ws[2] = 1-ws[0]-ws[1];
return ws;
}
__device__ Eigen::Vector3f calculateBarycentricCoordinate3DKernel(const Eigen::Matrix<float,3,3> &triangle, const Eigen::Vector3f &p)
{
Eigen::Vector3f vector_A = triangle.row(1)-triangle.row(2);
Eigen::Vector3f vector_B = triangle.row(1)-triangle.row(0);
Eigen::Vector3f normal = vector_A.cross(vector_B);
float areaABC = (normal.array() * (triangle.row(1)-triangle.row(0)).cross(triangle.row(2)-triangle.row(0)).transpose().array()).sum();
float areaPBC = (normal.array() * (triangle.row(1).transpose()-p).cross(triangle.row(2).transpose()-p).array()).sum();
float areaPCA = (normal.array() * (triangle.row(2).transpose()-p).cross(triangle.row(0).transpose()-p).array()).sum();
Eigen::Vector3f w;
w[0] = areaPBC / areaABC;
w[1] = areaPCA / areaABC;
w[2] = 1-w[0]-w[1];
return w;
}
__device__ void calculateBarycentricCoordinate2DKernel(const Eigen::Matrix<float,3,2> &triangle, const Eigen::Vector2f &p, Eigen::Vector3f &w)
{
Eigen::Vector2f CA = triangle.row(0)-triangle.row(2);
Eigen::Vector2f AC = -CA;
Eigen::Vector2f CP = p-triangle.row(2).transpose();
Eigen::Vector2f AB = triangle.row(1)-triangle.row(0);
Eigen::Vector2f AP = p-triangle.row(0).transpose();
Eigen::Matrix2f numerator, denominator;
numerator << CA, CP;
denominator<<AB, AC;
w(1) = numerator.determinant()/denominator.determinant();
numerator << AB, AP;
w(2) = numerator.determinant()/denominator.determinant();
w(0) = 1-w(1)-w(2);
}
template<class scalar_t>
__global__ void rayColorToTextureImageKernel(const torch::PackedTensorAccessor32<long,2,torch::RestrictPtrTraits> F, const torch::PackedTensorAccessor32<scalar_t,2,torch::RestrictPtrTraits> V, const torch::PackedTensorAccessor32<scalar_t,2,torch::RestrictPtrTraits> hit_locations, const torch::PackedTensorAccessor32<long,1,torch::RestrictPtrTraits> hit_face_ids, const torch::PackedTensorAccessor32<scalar_t,2,torch::RestrictPtrTraits> uvs_tex, torch::PackedTensorAccessor32<scalar_t,2,torch::RestrictPtrTraits> uvs)
{
const int i_hit = blockIdx.x*blockDim.x + threadIdx.x;
if (i_hit>=hit_locations.size(0)) return;
auto face = F[hit_face_ids[i_hit]];
Eigen::Matrix3f tri_v = Eigen::Matrix3f::Zero();
for (int r=0;r<3;r++)
{
for (int c=0;c<3;c++)
{
tri_v(r,c) = V[face[r]][c];
}
}
auto location = hit_locations[i_hit];
Eigen::Vector3f p(location[0], location[1], location[2]);
auto w = calculateBarycentricCoordinate3DKernel(tri_v, p);
Eigen::Matrix<float,3,2> tri_uvs;
for (int i=0;i<3;i++)
{
for (int j=0;j<2;j++)
{
tri_uvs(i,j) = uvs_tex[face[i]][j];
}
}
Eigen::Vector2f cur_uv = tri_uvs.transpose() * w;
uvs[i_hit][0] = cur_uv(0);
uvs[i_hit][1] = cur_uv(1);
}
void rayColorToTextureImageCUDA(const at::Tensor &F, const at::Tensor &V, const at::Tensor &hit_locations, const at::Tensor &hit_face_ids, const at::Tensor &uvs_tex, at::Tensor &uvs)
{
CHECK_CONTIGUOUS(F);
CHECK_CONTIGUOUS(V);
CHECK_CONTIGUOUS(hit_locations);
CHECK_CONTIGUOUS(hit_face_ids);
CHECK_CONTIGUOUS(uvs_tex);
dim3 threads = {512};
dim3 blocks = {divCeil(int(hit_locations.sizes()[0]),threads.x)};
AT_DISPATCH_FLOATING_TYPES(V.type(), "rayColorToTextureImageKernel", ([&]
{
rayColorToTextureImageKernel<scalar_t><<<blocks,threads>>>(F.packed_accessor32<long,2,torch::RestrictPtrTraits>(), V.packed_accessor32<scalar_t,2,torch::RestrictPtrTraits>(), hit_locations.packed_accessor32<scalar_t,2,torch::RestrictPtrTraits>(), hit_face_ids.packed_accessor32<long,1,torch::RestrictPtrTraits>(), uvs_tex.packed_accessor32<scalar_t,2,torch::RestrictPtrTraits>(), uvs.packed_accessor32<scalar_t,2,torch::RestrictPtrTraits>());
}));
}
__device__ bool isLineIntersectLine(Eigen::Vector2f l1p1, Eigen::Vector2f l1p2, Eigen::Vector2f l2p1, Eigen::Vector2f l2p2)
{
float q = (l1p1(1) - l2p1(1)) * (l2p2(0) - l2p1(0)) - (l1p1(0) - l2p1(0)) * (l2p2(1) - l2p1(1));
float d = (l1p2(0) - l1p1(0)) * (l2p2(1) - l2p1(1)) - (l1p2(1) - l1p1(1)) * (l2p2(0) - l2p1(0));
if ( d == 0 )
{
return false;
}
float r = q / d;
q = (l1p1(1) - l2p1(1)) * (l1p2(0) - l1p1(0)) - (l1p1(0) - l2p1(0)) * (l1p2(1) - l1p1(1));
float s = q / d;
if( r < 0 || r > 1 || s < 0 || s > 1 )
{
return false;
}
return true;
}
__device__ bool isLineIntersectSquare(const Eigen::Vector2f &la, const Eigen::Vector2f &lb, const Eigen::Vector2f &p0, const Eigen::Vector2f &p1, const Eigen::Vector2f &p2, const Eigen::Vector2f &p3)
{
return isLineIntersectLine(la, lb, p0, p1) || isLineIntersectLine(la, lb, p1, p2) || isLineIntersectLine(la, lb, p2, p3) || isLineIntersectLine(la, lb, p3, p0);
}
__device__ bool isPixelInsideTriangle(const Eigen::Matrix<float,3,2> &triangle, Eigen::Vector2f p, Eigen::Vector3f &w)
{
calculateBarycentricCoordinate2DKernel(triangle, p, w);
for (int j=0;j<3;j++)
{
if (w(j)<0) return false;
}
return true;
}
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
*
* NVIDIA CORPORATION and its licensors retain all intellectual property
* and proprietary rights in and to this software, related documentation
* and any modifications thereto. Any use, reproduction, disclosure or
* distribution of this software and related documentation without an express
* license agreement from NVIDIA CORPORATION is strictly prohibited.
*/
#pragma once
#include <torch/extension.h>
#include <vector>
#define CHECK_CUDA(x) AT_ASSERTM(x.is_cuda(), #x " must be a CUDA tensor")
#define CHECK_CONTIGUOUS(x) AT_ASSERTM(x.is_contiguous(), #x " must be contiguous")
#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x)
inline int divCeil(int a, int b)
{
return (a+b-1)/b;
};
at::Tensor sampleRaysUniformOccupiedVoxels(const at::Tensor z_in_out, const at::Tensor z_sampled, at::Tensor z_vals);
at::Tensor postprocessOctreeRayTracing(const at::Tensor ray_index, const at::Tensor depth_in_out, const at::Tensor unique_intersect_ray_ids, const at::Tensor start_poss, const int max_intersections, const int N_rays);
void rayColorToTextureImageCUDA(const at::Tensor &F, const at::Tensor &V, const at::Tensor &hit_locations, const at::Tensor &hit_face_ids, const at::Tensor &uvs_tex, at::Tensor &uvs);
+41
View File
@@ -0,0 +1,41 @@
# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto. Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
from setuptools import setup
import os,sys
from torch.utils.cpp_extension import BuildExtension, CUDAExtension
from torch.utils.cpp_extension import load
code_dir = os.path.dirname(os.path.realpath(__file__))
nvcc_flags = ['-Xcompiler', '-O3', '-std=c++14', '-U__CUDA_NO_HALF_OPERATORS__', '-U__CUDA_NO_HALF_CONVERSIONS__', '-U__CUDA_NO_HALF2_OPERATORS__']
c_flags = ['-O3', '-std=c++14']
setup(
name='common',
extra_cflags=c_flags,
extra_cuda_cflags=nvcc_flags,
ext_modules=[
CUDAExtension('common', [
'bindings.cpp',
'common.cu',
],extra_compile_args={'gcc': c_flags, 'nvcc': nvcc_flags}),
CUDAExtension('gridencoder', [
f"{code_dir}/torch_ngp_grid_encoder/gridencoder.cu",
f"{code_dir}/torch_ngp_grid_encoder/bindings.cpp",
],extra_compile_args={'gcc': c_flags, 'nvcc': nvcc_flags}),
],
include_dirs=[
"/usr/local/include/eigen3",
"/usr/include/eigen3",
],
cmdclass={
'build_ext': BuildExtension
})
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 hawkey
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,8 @@
#include <torch/extension.h>
#include "gridencoder.h"
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("grid_encode_forward", &grid_encode_forward, "grid_encode_forward (CUDA)");
m.def("grid_encode_backward", &grid_encode_backward, "grid_encode_backward (CUDA)");
}
@@ -0,0 +1,158 @@
import numpy as np
import os,sys,pdb
code_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(code_dir)
import torch
import torch.nn as nn
from torch.autograd import Function
from torch.autograd.function import once_differentiable
from torch.cuda.amp import custom_bwd, custom_fwd
from setuptools import setup
from torch.utils.cpp_extension import BuildExtension, CUDAExtension
from torch.utils.cpp_extension import load
import os,sys
import gridencoder
_gridtype_to_id = {
'hash': 0,
'tiled': 1,
}
class _grid_encode(Function):
@staticmethod
@custom_fwd
def forward(ctx, inputs, embeddings, offsets, per_level_scale, base_resolution, calc_grad_inputs=False, gridtype=0, align_corners=False):
# inputs: [B, D], float in [0, 1]
# embeddings: [sO, C], float
# offsets: [L + 1], int
# RETURN: [B, F], float
inputs = inputs.contiguous()
B, D = inputs.shape
L = offsets.shape[0] - 1
C = embeddings.shape[1]
S = np.log2(per_level_scale)
H = base_resolution
if torch.is_autocast_enabled() and C % 2 == 0:
embeddings = embeddings.to(torch.half)
outputs = torch.empty(L, B, C, device=inputs.device, dtype=embeddings.dtype)
if calc_grad_inputs:
dy_dx = torch.empty(B, L * D * C, device=inputs.device, dtype=embeddings.dtype)
else:
dy_dx = torch.empty(1, device=inputs.device, dtype=embeddings.dtype) # placeholder... TODO: a better way?
gridencoder.grid_encode_forward(inputs, embeddings, offsets, outputs, B, D, C, L, S, H, calc_grad_inputs, dy_dx, gridtype, align_corners)
outputs = outputs.permute(1, 0, 2).reshape(B, L * C)
ctx.save_for_backward(inputs, embeddings, offsets, dy_dx)
ctx.dims = [B, D, C, L, S, H, gridtype]
ctx.calc_grad_inputs = calc_grad_inputs
ctx.align_corners = align_corners
return outputs
@staticmethod
@custom_bwd
def backward(ctx, grad):
inputs, embeddings, offsets, dy_dx = ctx.saved_tensors
B, D, C, L, S, H, gridtype = ctx.dims
calc_grad_inputs = ctx.calc_grad_inputs
align_corners = ctx.align_corners
# grad: [B, L * C] --> [L, B, C]
grad = grad.view(B, L, C).permute(1, 0, 2).contiguous()
grad_embeddings = torch.zeros_like(embeddings)
if calc_grad_inputs:
grad_inputs = torch.zeros_like(inputs, dtype=embeddings.dtype)
else:
grad_inputs = torch.zeros(1, device=inputs.device, dtype=embeddings.dtype)
gridencoder.grid_encode_backward(grad, inputs, embeddings, offsets, grad_embeddings, B, D, C, L, S, H, calc_grad_inputs, dy_dx, grad_inputs, gridtype, align_corners)
if calc_grad_inputs:
grad_inputs = grad_inputs.to(inputs.dtype)
return grad_inputs, grad_embeddings, None, None, None, None, None, None
else:
return None, grad_embeddings, None, None, None, None, None, None
grid_encode = _grid_encode.apply
# https://github.com/ashawkey/torch-ngp/blob/main/gridencoder/grid.py
class GridEncoder(nn.Module):
def __init__(self, input_dim=3, n_levels=16, level_dim=2, base_resolution=16, log2_hashmap_size=19, desired_resolution=None, gridtype='hash', align_corners=False):
super().__init__()
per_level_scale = np.exp2(np.log2(desired_resolution / base_resolution) / (n_levels - 1))
self.input_dim = input_dim # coord dims, 2 or 3
self.n_levels = n_levels # num levels, each level multiply resolution by 2
self.level_dim = level_dim # encode channels per level
self.per_level_scale = per_level_scale # multiply resolution by this scale at each level.
self.log2_hashmap_size = log2_hashmap_size
self.base_resolution = base_resolution
self.out_dim = n_levels * level_dim
self.gridtype = gridtype
self.gridtype_id = _gridtype_to_id[gridtype] # "tiled" or "hash"
self.align_corners = align_corners
# allocate parameters
offsets = []
offset = 0
self.max_params = 2 ** log2_hashmap_size
for i in range(n_levels):
resolution = int(np.ceil(base_resolution * per_level_scale ** i))
params_in_level = min(self.max_params, (resolution if align_corners else resolution + 1) ** input_dim) # limit max number
params_in_level = int(np.ceil(params_in_level / 8) * 8) # make divisible
print(f"level {i}, resolution: {resolution}")
offsets.append(offset)
offset += params_in_level
offsets.append(offset)
offsets = torch.from_numpy(np.array(offsets, dtype=np.int32))
self.register_buffer('offsets', offsets)
self.n_params = offsets[-1] * level_dim
# parameters
self.embeddings = nn.Parameter(torch.empty(offset, level_dim))
# self.embeddings = nn.Embedding(offset, level_dim, sparse=True)
self.reset_parameters()
def reset_parameters(self):
std = 1e-4
self.embeddings.data.uniform_(-std, std)
# self.embeddings.weight.data.uniform_(-std, std)
def __repr__(self):
return f"GridEncoder: input_dim={self.input_dim} n_levels={self.n_levels} level_dim={self.level_dim} resolution={self.base_resolution} -> {int(round(self.base_resolution * self.per_level_scale ** (self.n_levels - 1)))} per_level_scale={self.per_level_scale:.4f} params={tuple(self.embeddings.shape)} gridtype={self.gridtype} align_corners={self.align_corners}"
def forward(self, inputs, bound=1):
# inputs: [..., input_dim], normalized real world positions in [-bound, bound]
# return: [..., num_levels * level_dim]
inputs = (inputs + bound) / (2 * bound) # map to [0, 1]
#print('inputs', inputs.shape, inputs.dtype, inputs.min().item(), inputs.max().item())
prefix_shape = list(inputs.shape[:-1])
inputs = inputs.view(-1, self.input_dim)
outputs = grid_encode(inputs, self.embeddings, self.offsets, self.per_level_scale, self.base_resolution, inputs.requires_grad, self.gridtype_id, self.align_corners)
outputs = outputs.view(prefix_shape + [self.out_dim])
#print('outputs', outputs.shape, outputs.dtype, outputs.min().item(), outputs.max().item())
return outputs
@@ -0,0 +1,500 @@
#include <cuda.h>
#include <cuda_fp16.h>
#include <cuda_runtime.h>
#include <ATen/cuda/CUDAContext.h>
#include <torch/torch.h>
#include <algorithm>
#include <stdexcept>
#include <stdint.h>
#include <cstdio>
#define CHECK_CUDA(x) TORCH_CHECK(x.device().is_cuda(), #x " must be a CUDA tensor")
#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be a contiguous tensor")
#define CHECK_IS_INT(x) TORCH_CHECK(x.scalar_type() == at::ScalarType::Int, #x " must be an int tensor")
#define CHECK_IS_FLOATING(x) TORCH_CHECK(x.scalar_type() == at::ScalarType::Float || x.scalar_type() == at::ScalarType::Half || x.scalar_type() == at::ScalarType::Double, #x " must be a floating tensor")
// just for compatability of half precision in AT_DISPATCH_FLOATING_TYPES_AND_HALF...
static inline __device__ at::Half atomicAdd(at::Half *address, at::Half val) {
// requires CUDA >= 10 and ARCH >= 70
// this is very slow compared to float or __half2, and never used.
//return atomicAdd(reinterpret_cast<__half*>(address), val);
}
template <typename T>
static inline __host__ __device__ T div_round_up(T val, T divisor) {
return (val + divisor - 1) / divisor;
}
template <uint32_t D>
__device__ uint32_t fast_hash(const uint32_t pos_grid[D]) {
static_assert(D <= 7, "fast_hash can only hash up to 7 dimensions.");
// While 1 is technically not a good prime for hashing (or a prime at all), it helps memory coherence
// and is sufficient for our use case of obtaining a uniformly colliding index from high-dimensional
// coordinates.
constexpr uint32_t primes[7] = { 1, 2654435761, 805459861, 3674653429, 2097192037, 1434869437, 2165219737 };
uint32_t result = 0;
#pragma unroll
for (uint32_t i = 0; i < D; ++i) {
result ^= pos_grid[i] * primes[i];
}
return result;
}
template <uint32_t D, uint32_t C>
__device__ uint32_t get_grid_index(const uint32_t gridtype, const bool align_corners, const uint32_t ch, const uint32_t hashmap_size, const uint32_t resolution, const uint32_t pos_grid[D]) {
uint32_t stride = 1;
uint32_t index = 0;
#pragma unroll
for (uint32_t d = 0; d < D && stride <= hashmap_size; d++) {
index += pos_grid[d] * stride;
stride *= align_corners ? resolution: (resolution + 1);
}
// NOTE: for NeRF, the hash is in fact not necessary. Check https://github.com/NVlabs/instant-ngp/issues/97.
// gridtype: 0 == hash, 1 == tiled
if (gridtype == 0 && stride > hashmap_size) {
index = fast_hash<D>(pos_grid);
}
return (index % hashmap_size) * C + ch;
}
/**
* @brief
*
* @tparam scalar_t
* @tparam D : point dimension usually 3D
* @tparam C : embedding dim 1/2/4
* @param inputs
* @param grid
* @param offsets
* @param outputs
* @param B
* @param L
* @param S
* @param H
* @param calc_grad_inputs
* @param dy_dx
* @param gridtype
* @param align_corners
* @return __global__
*/
template <typename scalar_t, uint32_t D, uint32_t C>
__global__ void kernel_grid(
const float * __restrict__ inputs,
const scalar_t * __restrict__ grid,
const int * __restrict__ offsets,
scalar_t * __restrict__ outputs,
const uint32_t B, const uint32_t L, const float S, const uint32_t H,
const bool calc_grad_inputs,
scalar_t * __restrict__ dy_dx,
const uint32_t gridtype,
const bool align_corners
) {
const uint32_t b = blockIdx.x * blockDim.x + threadIdx.x; // batch id
if (b >= B) return;
const uint32_t level = blockIdx.y;
// locate
grid += (uint32_t)offsets[level] * C;
inputs += b * D;
outputs += level * B * C + b * C;
// check input range (should be in [0, 1])
bool flag_oob = false;
#pragma unroll
for (uint32_t d = 0; d < D; d++) {
if (inputs[d] < 0 || inputs[d] > 1) {
flag_oob = true;
}
}
// if input out of bound, just set output to 0
if (flag_oob) {
#pragma unroll
for (uint32_t ch = 0; ch < C; ch++) {
outputs[ch] = 0;
}
if (calc_grad_inputs) {
dy_dx += b * D * L * C + level * D * C; // B L D C
#pragma unroll
for (uint32_t d = 0; d < D; d++) {
#pragma unroll
for (uint32_t ch = 0; ch < C; ch++) {
dy_dx[d * C + ch] = 0;
}
}
}
return;
}
const uint32_t hashmap_size = offsets[level + 1] - offsets[level];
const float scale = exp2f(level * S) * H - 1.0f;
const uint32_t resolution = (uint32_t)ceil(scale) + 1;
// calculate coordinate
float pos[D];
uint32_t pos_grid[D];
#pragma unroll
for (uint32_t d = 0; d < D; d++) {
pos[d] = inputs[d] * scale + (align_corners ? 0.0f : 0.5f);
pos_grid[d] = floorf(pos[d]);
pos[d] -= (float)pos_grid[d];
}
//printf("[b=%d, l=%d] pos=(%f, %f)+(%d, %d)\n", b, level, pos[0], pos[1], pos_grid[0], pos_grid[1]);
// interpolate
scalar_t results[C] = {0}; // temp results in register
#pragma unroll
for (uint32_t idx = 0; idx < (1 << D); idx++) {
float w = 1;
uint32_t pos_grid_local[D];
#pragma unroll
for (uint32_t d = 0; d < D; d++) {
if ((idx & (1 << d)) == 0) {
w *= 1 - pos[d];
pos_grid_local[d] = pos_grid[d];
} else {
w *= pos[d];
pos_grid_local[d] = pos_grid[d] + 1;
}
}
uint32_t index = get_grid_index<D, C>(gridtype, align_corners, 0, hashmap_size, resolution, pos_grid_local); // Hashing
// writing to register (fast)
#pragma unroll
for (uint32_t ch = 0; ch < C; ch++) {
results[ch] += w * grid[index + ch];
}
//printf("[b=%d, l=%d] int %d, idx %d, w %f, val %f\n", b, level, idx, index, w, grid[index]);
}
// writing to global memory (slow)
#pragma unroll
for (uint32_t ch = 0; ch < C; ch++) {
outputs[ch] = results[ch];
}
// prepare dy_dx for calc_grad_inputs
// differentiable (soft) indexing: https://discuss.pytorch.org/t/differentiable-indexing/17647/9
if (calc_grad_inputs) {
dy_dx += b * D * L * C + level * D * C; // B L D C
#pragma unroll
for (uint32_t gd = 0; gd < D; gd++) {
scalar_t results_grad[C] = {0};
#pragma unroll
for (uint32_t idx = 0; idx < (1 << (D - 1)); idx++) {
float w = scale;
uint32_t pos_grid_local[D];
#pragma unroll
for (uint32_t nd = 0; nd < D - 1; nd++) {
const uint32_t d = (nd >= gd) ? (nd + 1) : nd;
if ((idx & (1 << nd)) == 0) {
w *= 1 - pos[d];
pos_grid_local[d] = pos_grid[d];
} else {
w *= pos[d];
pos_grid_local[d] = pos_grid[d] + 1;
}
}
pos_grid_local[gd] = pos_grid[gd];
uint32_t index_left = get_grid_index<D, C>(gridtype, align_corners, 0, hashmap_size, resolution, pos_grid_local);
pos_grid_local[gd] = pos_grid[gd] + 1;
uint32_t index_right = get_grid_index<D, C>(gridtype, align_corners, 0, hashmap_size, resolution, pos_grid_local);
#pragma unroll
for (uint32_t ch = 0; ch < C; ch++) {
results_grad[ch] += w * (grid[index_right + ch] - grid[index_left + ch]);
}
}
#pragma unroll
for (uint32_t ch = 0; ch < C; ch++) {
dy_dx[gd * C + ch] = results_grad[ch];
}
}
}
}
template <typename scalar_t, uint32_t D, uint32_t C, uint32_t N_C>
__global__ void kernel_grid_backward(
const scalar_t * __restrict__ grad,
const float * __restrict__ inputs,
const scalar_t * __restrict__ grid,
const int * __restrict__ offsets,
scalar_t * __restrict__ grad_grid,
const uint32_t B, const uint32_t L, const float S, const uint32_t H,
const uint32_t gridtype,
const bool align_corners
) {
const uint32_t b = (blockIdx.x * blockDim.x + threadIdx.x) * N_C / C;
if (b >= B) return;
const uint32_t level = blockIdx.y;
const uint32_t ch = (blockIdx.x * blockDim.x + threadIdx.x) * N_C - b * C;
// locate
grad_grid += offsets[level] * C;
inputs += b * D;
grad += level * B * C + b * C + ch; // L, B, C
const uint32_t hashmap_size = offsets[level + 1] - offsets[level];
const float scale = exp2f(level * S) * H - 1.0f;
const uint32_t resolution = (uint32_t)ceil(scale) + 1;
// check input range (should be in [0, 1])
#pragma unroll
for (uint32_t d = 0; d < D; d++) {
if (inputs[d] < 0 || inputs[d] > 1) {
return; // grad is init as 0, so we simply return.
}
}
// calculate coordinate
float pos[D];
uint32_t pos_grid[D];
#pragma unroll
for (uint32_t d = 0; d < D; d++) {
pos[d] = inputs[d] * scale + (align_corners ? 0.0f : 0.5f);
pos_grid[d] = floorf(pos[d]);
pos[d] -= (float)pos_grid[d];
}
scalar_t grad_cur[N_C] = {0}; // fetch to register
#pragma unroll
for (uint32_t c = 0; c < N_C; c++) {
grad_cur[c] = grad[c];
}
// interpolate
#pragma unroll
for (uint32_t idx = 0; idx < (1 << D); idx++) {
float w = 1;
uint32_t pos_grid_local[D];
#pragma unroll
for (uint32_t d = 0; d < D; d++) {
if ((idx & (1 << d)) == 0) {
w *= 1 - pos[d];
pos_grid_local[d] = pos_grid[d];
} else {
w *= pos[d];
pos_grid_local[d] = pos_grid[d] + 1;
}
}
uint32_t index = get_grid_index<D, C>(gridtype, align_corners, ch, hashmap_size, resolution, pos_grid_local);
// atomicAdd for __half is slow (especially for large values), so we use __half2 if N_C % 2 == 0
// TODO: use float which is better than __half, if N_C % 2 != 0
if (std::is_same<scalar_t, at::Half>::value && N_C % 2 == 0) {
#pragma unroll
for (uint32_t c = 0; c < N_C; c += 2) {
// process two __half at once (by interpreting as a __half2)
__half2 v = {(__half)(w * grad_cur[c]), (__half)(w * grad_cur[c + 1])};
atomicAdd((__half2*)&grad_grid[index + c], v);
}
// float, or __half when N_C % 2 != 0 (which means C == 1)
} else {
#pragma unroll
for (uint32_t c = 0; c < N_C; c++) {
atomicAdd(&grad_grid[index + c], w * grad_cur[c]);
}
}
}
}
template <typename scalar_t, uint32_t D, uint32_t C>
__global__ void kernel_input_backward(
const scalar_t * __restrict__ grad,
const scalar_t * __restrict__ dy_dx,
scalar_t * __restrict__ grad_inputs,
uint32_t B, uint32_t L
) {
const uint32_t t = threadIdx.x + blockIdx.x * blockDim.x;
if (t >= B * D) return;
const uint32_t b = t / D;
const uint32_t d = t - b * D;
dy_dx += b * L * D * C;
scalar_t result = 0;
# pragma unroll
for (int l = 0; l < L; l++) {
# pragma unroll
for (int ch = 0; ch < C; ch++) {
result += grad[l * B * C + b * C + ch] * dy_dx[l * D * C + d * C + ch];
}
}
grad_inputs[t] = result;
}
template <typename scalar_t, uint32_t D>
void kernel_grid_wrapper(const float *inputs, const scalar_t *embeddings, const int *offsets, scalar_t *outputs, const uint32_t B, const uint32_t C, const uint32_t L, const float S, const uint32_t H, const bool calc_grad_inputs, scalar_t *dy_dx, const uint32_t gridtype, const bool align_corners) {
static constexpr uint32_t N_THREAD = 512;
const dim3 blocks_hashgrid = { div_round_up(B, N_THREAD), L, 1 };
switch (C) {
case 1: kernel_grid<scalar_t, D, 1><<<blocks_hashgrid, N_THREAD>>>(inputs, embeddings, offsets, outputs, B, L, S, H, calc_grad_inputs, dy_dx, gridtype, align_corners); break;
case 2: kernel_grid<scalar_t, D, 2><<<blocks_hashgrid, N_THREAD>>>(inputs, embeddings, offsets, outputs, B, L, S, H, calc_grad_inputs, dy_dx, gridtype, align_corners); break;
case 4: kernel_grid<scalar_t, D, 4><<<blocks_hashgrid, N_THREAD>>>(inputs, embeddings, offsets, outputs, B, L, S, H, calc_grad_inputs, dy_dx, gridtype, align_corners); break;
case 8: kernel_grid<scalar_t, D, 8><<<blocks_hashgrid, N_THREAD>>>(inputs, embeddings, offsets, outputs, B, L, S, H, calc_grad_inputs, dy_dx, gridtype, align_corners); break;
default: throw std::runtime_error{"GridEncoding: C must be 1, 2, 4, or 8."};
}
}
// inputs: [B, D], float, in [0, 1]
// embeddings: [sO, C], float
// offsets: [L + 1], uint32_t
// outputs: [L, B, C], float (L first, so only one level of hashmap needs to fit into cache at a time.)
// H: base resolution
// dy_dx: [B, L * D * C]
template <typename scalar_t>
void grid_encode_forward_cuda(const float *inputs, const scalar_t *embeddings, const int *offsets, scalar_t *outputs, const uint32_t B, const uint32_t D, const uint32_t C, const uint32_t L, const float S, const uint32_t H, const bool calc_grad_inputs, scalar_t *dy_dx, const uint32_t gridtype, const bool align_corners) {
switch (D) {
case 1: kernel_grid_wrapper<scalar_t, 1>(inputs, embeddings, offsets, outputs, B, C, L, S, H, calc_grad_inputs, dy_dx, gridtype, align_corners); break;
case 2: kernel_grid_wrapper<scalar_t, 2>(inputs, embeddings, offsets, outputs, B, C, L, S, H, calc_grad_inputs, dy_dx, gridtype, align_corners); break;
case 3: kernel_grid_wrapper<scalar_t, 3>(inputs, embeddings, offsets, outputs, B, C, L, S, H, calc_grad_inputs, dy_dx, gridtype, align_corners); break;
case 4: kernel_grid_wrapper<scalar_t, 4>(inputs, embeddings, offsets, outputs, B, C, L, S, H, calc_grad_inputs, dy_dx, gridtype, align_corners); break;
case 5: kernel_grid_wrapper<scalar_t, 5>(inputs, embeddings, offsets, outputs, B, C, L, S, H, calc_grad_inputs, dy_dx, gridtype, align_corners); break;
default: throw std::runtime_error{"GridEncoding: D must be 1, 2, 3, 4, or 5."};
}
}
template <typename scalar_t, uint32_t D>
void kernel_grid_backward_wrapper(const scalar_t *grad, const float *inputs, const scalar_t *embeddings, const int *offsets, scalar_t *grad_embeddings, const uint32_t B, const uint32_t C, const uint32_t L, const float S, const uint32_t H, const bool calc_grad_inputs, scalar_t *dy_dx, scalar_t *grad_inputs, const uint32_t gridtype, const bool align_corners) {
static constexpr uint32_t N_THREAD = 256;
const uint32_t N_C = std::min(2u, C); // n_features_per_thread
const dim3 blocks_hashgrid = { div_round_up(B * C / N_C, N_THREAD), L, 1 };
switch (C) {
case 1:
kernel_grid_backward<scalar_t, D, 1, 1><<<blocks_hashgrid, N_THREAD>>>(grad, inputs, embeddings, offsets, grad_embeddings, B, L, S, H, gridtype, align_corners);
if (calc_grad_inputs) kernel_input_backward<scalar_t, D, 1><<<div_round_up(B * D, N_THREAD), N_THREAD>>>(grad, dy_dx, grad_inputs, B, L);
break;
case 2:
kernel_grid_backward<scalar_t, D, 2, 2><<<blocks_hashgrid, N_THREAD>>>(grad, inputs, embeddings, offsets, grad_embeddings, B, L, S, H, gridtype, align_corners);
if (calc_grad_inputs) kernel_input_backward<scalar_t, D, 2><<<div_round_up(B * D, N_THREAD), N_THREAD>>>(grad, dy_dx, grad_inputs, B, L);
break;
case 4:
kernel_grid_backward<scalar_t, D, 4, 2><<<blocks_hashgrid, N_THREAD>>>(grad, inputs, embeddings, offsets, grad_embeddings, B, L, S, H, gridtype, align_corners);
if (calc_grad_inputs) kernel_input_backward<scalar_t, D, 4><<<div_round_up(B * D, N_THREAD), N_THREAD>>>(grad, dy_dx, grad_inputs, B, L);
break;
case 8:
kernel_grid_backward<scalar_t, D, 8, 2><<<blocks_hashgrid, N_THREAD>>>(grad, inputs, embeddings, offsets, grad_embeddings, B, L, S, H, gridtype, align_corners);
if (calc_grad_inputs) kernel_input_backward<scalar_t, D, 8><<<div_round_up(B * D, N_THREAD), N_THREAD>>>(grad, dy_dx, grad_inputs, B, L);
break;
default: throw std::runtime_error{"GridEncoding: C must be 1, 2, 4, or 8."};
}
}
// grad: [L, B, C], float
// inputs: [B, D], float, in [0, 1]
// embeddings: [sO, C], float
// offsets: [L + 1], uint32_t
// grad_embeddings: [sO, C]
// H: base resolution
template <typename scalar_t>
void grid_encode_backward_cuda(const scalar_t *grad, const float *inputs, const scalar_t *embeddings, const int *offsets, scalar_t *grad_embeddings, const uint32_t B, const uint32_t D, const uint32_t C, const uint32_t L, const float S, const uint32_t H, const bool calc_grad_inputs, scalar_t *dy_dx, scalar_t *grad_inputs, const uint32_t gridtype, const bool align_corners) {
switch (D) {
case 1: kernel_grid_backward_wrapper<scalar_t, 1>(grad, inputs, embeddings, offsets, grad_embeddings, B, C, L, S, H, calc_grad_inputs, dy_dx, grad_inputs, gridtype, align_corners); break;
case 2: kernel_grid_backward_wrapper<scalar_t, 2>(grad, inputs, embeddings, offsets, grad_embeddings, B, C, L, S, H, calc_grad_inputs, dy_dx, grad_inputs, gridtype, align_corners); break;
case 3: kernel_grid_backward_wrapper<scalar_t, 3>(grad, inputs, embeddings, offsets, grad_embeddings, B, C, L, S, H, calc_grad_inputs, dy_dx, grad_inputs, gridtype, align_corners); break;
case 4: kernel_grid_backward_wrapper<scalar_t, 4>(grad, inputs, embeddings, offsets, grad_embeddings, B, C, L, S, H, calc_grad_inputs, dy_dx, grad_inputs, gridtype, align_corners); break;
case 5: kernel_grid_backward_wrapper<scalar_t, 5>(grad, inputs, embeddings, offsets, grad_embeddings, B, C, L, S, H, calc_grad_inputs, dy_dx, grad_inputs, gridtype, align_corners); break;
default: throw std::runtime_error{"GridEncoding: D must be 1, 2, 3, 4, or 5."};
}
}
void grid_encode_forward(const at::Tensor inputs, const at::Tensor embeddings, const at::Tensor offsets, at::Tensor outputs, const uint32_t B, const uint32_t D, const uint32_t C, const uint32_t L, const float S, const uint32_t H, const bool calc_grad_inputs, at::Tensor dy_dx, const uint32_t gridtype, const bool align_corners) {
CHECK_CUDA(inputs);
CHECK_CUDA(embeddings);
CHECK_CUDA(offsets);
CHECK_CUDA(outputs);
CHECK_CUDA(dy_dx);
CHECK_CONTIGUOUS(inputs);
CHECK_CONTIGUOUS(embeddings);
CHECK_CONTIGUOUS(offsets);
CHECK_CONTIGUOUS(outputs);
CHECK_CONTIGUOUS(dy_dx);
CHECK_IS_FLOATING(inputs);
CHECK_IS_FLOATING(embeddings);
CHECK_IS_INT(offsets);
CHECK_IS_FLOATING(outputs);
CHECK_IS_FLOATING(dy_dx);
AT_DISPATCH_FLOATING_TYPES_AND_HALF(
embeddings.scalar_type(), "grid_encode_forward", ([&] {
grid_encode_forward_cuda<scalar_t>(inputs.data_ptr<float>(), embeddings.data_ptr<scalar_t>(), offsets.data_ptr<int>(), outputs.data_ptr<scalar_t>(), B, D, C, L, S, H, calc_grad_inputs, dy_dx.data_ptr<scalar_t>(), gridtype, align_corners);
}));
}
void grid_encode_backward(const at::Tensor grad, const at::Tensor inputs, const at::Tensor embeddings, const at::Tensor offsets, at::Tensor grad_embeddings, const uint32_t B, const uint32_t D, const uint32_t C, const uint32_t L, const float S, const uint32_t H, const bool calc_grad_inputs, const at::Tensor dy_dx, at::Tensor grad_inputs, const uint32_t gridtype, const bool align_corners) {
CHECK_CUDA(grad);
CHECK_CUDA(inputs);
CHECK_CUDA(embeddings);
CHECK_CUDA(offsets);
CHECK_CUDA(grad_embeddings);
CHECK_CUDA(dy_dx);
CHECK_CUDA(grad_inputs);
CHECK_CONTIGUOUS(grad);
CHECK_CONTIGUOUS(inputs);
CHECK_CONTIGUOUS(embeddings);
CHECK_CONTIGUOUS(offsets);
CHECK_CONTIGUOUS(grad_embeddings);
CHECK_CONTIGUOUS(dy_dx);
CHECK_CONTIGUOUS(grad_inputs);
CHECK_IS_FLOATING(grad);
CHECK_IS_FLOATING(inputs);
CHECK_IS_FLOATING(embeddings);
CHECK_IS_INT(offsets);
CHECK_IS_FLOATING(grad_embeddings);
CHECK_IS_FLOATING(dy_dx);
CHECK_IS_FLOATING(grad_inputs);
AT_DISPATCH_FLOATING_TYPES_AND_HALF(
grad.scalar_type(), "grid_encode_backward", ([&] {
grid_encode_backward_cuda<scalar_t>(grad.data_ptr<scalar_t>(), inputs.data_ptr<float>(), embeddings.data_ptr<scalar_t>(), offsets.data_ptr<int>(), grad_embeddings.data_ptr<scalar_t>(), B, D, C, L, S, H, calc_grad_inputs, dy_dx.data_ptr<scalar_t>(), grad_inputs.data_ptr<scalar_t>(), gridtype, align_corners);
}));
}
@@ -0,0 +1,15 @@
#ifndef _HASH_ENCODE_H
#define _HASH_ENCODE_H
#include <stdint.h>
#include <torch/torch.h>
// inputs: [B, D], float, in [0, 1]
// embeddings: [sO, C], float
// offsets: [L + 1], uint32_t
// outputs: [B, L * C], float
// H: base resolution
void grid_encode_forward(const at::Tensor inputs, const at::Tensor embeddings, const at::Tensor offsets, at::Tensor outputs, const uint32_t B, const uint32_t D, const uint32_t C, const uint32_t L, const float S, const uint32_t H, const bool calc_grad_inputs, at::Tensor dy_dx, const uint32_t gridtype, const bool align_corners);
void grid_encode_backward(const at::Tensor grad, const at::Tensor inputs, const at::Tensor embeddings, const at::Tensor offsets, at::Tensor grad_embeddings, const uint32_t B, const uint32_t D, const uint32_t C, const uint32_t L, const float S, const uint32_t H, const bool calc_grad_inputs, const at::Tensor dy_dx, at::Tensor grad_inputs, const uint32_t gridtype, const bool align_corners);
#endif
+475
View File
@@ -0,0 +1,475 @@
# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto. Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
import torch,pdb,os,sys
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
code_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(f'{code_dir}/../')
from Utils import *
from pytorch3d.transforms import so3_log_map,so3_exp_map,se3_exp_map
img2mse = lambda x, y : torch.mean((x - y) ** 2)
img2mae = lambda x, y: (torch.abs(x - y)).mean()
mse2psnr = lambda x : -10. * torch.log(x) / torch.log(torch.Tensor([10.]))
to8b = lambda x : (255*np.clip(x,0,1)).astype(np.uint8)
class FeatureArray(nn.Module):
"""
Per-frame corrective latent code.
"""
def __init__(self, num_frames, num_channels):
super().__init__()
self.num_frames = num_frames
self.num_channels = num_channels
self.data = nn.parameter.Parameter(torch.normal(0,1,size=[num_frames, num_channels]).float(), requires_grad=True)
self.register_parameter('data',self.data)
def __call__(self, ids):
return self.data[ids]
class PoseArray(nn.Module):
def __init__(self, num_frames,max_trans,max_rot):
super().__init__()
self.num_frames = num_frames
self.max_trans = max_trans
self.max_rot = max_rot
self.data = nn.parameter.Parameter(torch.zeros([num_frames, 6]).float(), requires_grad=True)
self.register_parameter('data',self.data)
def get_matrices(self,ids):
if not torch.is_tensor(ids):
ids = torch.tensor(ids).long()
theta = torch.tanh(self.data)
trans = theta[:,:3] * self.max_trans
rot = theta[:,3:6] * self.max_rot/180.0*np.pi
Ts_data = se3_exp_map(torch.cat((trans,rot),dim=-1)).permute(0,2,1)
Ts = torch.eye(4, device=self.data.device).reshape(1,4,4).repeat(len(ids),1,1)
mask = ids!=0
Ts[mask] = Ts_data[ids[mask]]
return Ts
class SHEncoder(nn.Module):
'''Spherical encoding
'''
def __init__(self, input_dim=3, degree=4):
super().__init__()
self.input_dim = input_dim
self.degree = degree
assert self.input_dim == 3
assert self.degree >= 1 and self.degree <= 5
self.out_dim = degree ** 2
self.C0 = 0.28209479177387814
self.C1 = 0.4886025119029199
self.C2 = [
1.0925484305920792,
-1.0925484305920792,
0.31539156525252005,
-1.0925484305920792,
0.5462742152960396
]
self.C3 = [
-0.5900435899266435,
2.890611442640554,
-0.4570457994644658,
0.3731763325901154,
-0.4570457994644658,
1.445305721320277,
-0.5900435899266435
]
self.C4 = [
2.5033429417967046,
-1.7701307697799304,
0.9461746957575601,
-0.6690465435572892,
0.10578554691520431,
-0.6690465435572892,
0.47308734787878004,
-1.7701307697799304,
0.6258357354491761
]
def forward(self, input, **kwargs):
result = torch.empty((*input.shape[:-1], self.out_dim), dtype=input.dtype, device=input.device)
x, y, z = input.unbind(-1)
result[..., 0] = self.C0
if self.degree > 1:
result[..., 1] = -self.C1 * y
result[..., 2] = self.C1 * z
result[..., 3] = -self.C1 * x
if self.degree > 2:
xx, yy, zz = x * x, y * y, z * z
xy, yz, xz = x * y, y * z, x * z
result[..., 4] = self.C2[0] * xy
result[..., 5] = self.C2[1] * yz
result[..., 6] = self.C2[2] * (2.0 * zz - xx - yy)
#result[..., 6] = self.C2[2] * (3.0 * zz - 1) # xx + yy + zz == 1, but this will lead to different backward gradients, interesting...
result[..., 7] = self.C2[3] * xz
result[..., 8] = self.C2[4] * (xx - yy)
if self.degree > 3:
result[..., 9] = self.C3[0] * y * (3 * xx - yy)
result[..., 10] = self.C3[1] * xy * z
result[..., 11] = self.C3[2] * y * (4 * zz - xx - yy)
result[..., 12] = self.C3[3] * z * (2 * zz - 3 * xx - 3 * yy)
result[..., 13] = self.C3[4] * x * (4 * zz - xx - yy)
result[..., 14] = self.C3[5] * z * (xx - yy)
result[..., 15] = self.C3[6] * x * (xx - 3 * yy)
if self.degree > 4:
result[..., 16] = self.C4[0] * xy * (xx - yy)
result[..., 17] = self.C4[1] * yz * (3 * xx - yy)
result[..., 18] = self.C4[2] * xy * (7 * zz - 1)
result[..., 19] = self.C4[3] * yz * (7 * zz - 3)
result[..., 20] = self.C4[4] * (zz * (35 * zz - 30) + 3)
result[..., 21] = self.C4[5] * xz * (7 * zz - 3)
result[..., 22] = self.C4[6] * (xx - yy) * (7 * zz - 1)
result[..., 23] = self.C4[7] * xz * (xx - 3 * yy)
result[..., 24] = self.C4[8] * (xx * (xx - 3 * yy) - yy * (3 * xx - yy))
return result
class Embedder(nn.Module):
def __init__(self, **kwargs):
super().__init__()
self.kwargs = kwargs
self.create_embedding_fn()
def create_embedding_fn(self):
embed_fns = []
d = self.kwargs['input_dims']
out_dim = 0
if self.kwargs['include_input']:
embed_fns.append(lambda x : x)
out_dim += d
max_freq = self.kwargs['max_freq_log2']
N_freqs = self.kwargs['num_freqs']
if self.kwargs['log_sampling']:
freq_bands = 2.**torch.linspace(0., max_freq, steps=N_freqs)
else:
freq_bands = torch.linspace(2.**0., 2.**max_freq, steps=N_freqs)
for freq in freq_bands:
for p_fn in self.kwargs['periodic_fns']:
embed_fns.append(lambda x, p_fn=p_fn, freq=freq : p_fn(x * freq))
out_dim += d
self.embed_fns = embed_fns
self.out_dim = out_dim
def forward(self, inputs):
return torch.cat([fn(inputs) for fn in self.embed_fns], -1)
def get_embedder(multires, cfg, i=0, octree_m=None):
if i == -1:
return nn.Identity(), 3
elif i==0:
embed_kwargs = {
'include_input' : True,
'input_dims' : 3,
'max_freq_log2' : multires-1,
'num_freqs' : multires,
'log_sampling' : True,
'periodic_fns' : [torch.sin, torch.cos],
}
embed = Embedder(**embed_kwargs)
out_dim = embed.out_dim
elif i==1:
from mycuda.torch_ngp_grid_encoder.grid import GridEncoder
embed = GridEncoder(input_dim=3, n_levels=cfg['num_levels'], log2_hashmap_size=cfg['log2_hashmap_size'], desired_resolution=cfg['finest_res'], base_resolution=cfg['base_res'], level_dim=cfg['feature_grid_dim'])
print(embed)
out_dim = embed.out_dim
elif i==2:
embed = SHEncoder(degree=cfg['multires_views'])
out_dim = embed.out_dim
return embed, out_dim
def mesh_to_real_world(mesh,pose_offset,translation,sc_factor):
'''
@pose_offset: optimized delta pose of the first frame. Usually it's identity
'''
mesh.vertices = mesh.vertices/sc_factor - np.array(translation).reshape(1,3)
mesh.apply_transform(pose_offset)
return mesh
def get_optimized_poses_in_real_world(poses_normalized, pose_array, sc_factor, translation):
'''
@poses_normalized: np array, cam_in_ob (opengl convention), normalized to [-1,1] and centered
@pose_array: PoseArray, delta poses
Return:
cam_in_ob, real-world unit, opencv convention
'''
original_poses = poses_normalized.copy()
original_poses[:, :3, 3] /= sc_factor # To true world scale
original_poses[:, :3, 3] -= translation
# Apply pose transformation
tf = pose_array.get_matrices(np.arange(len(poses_normalized))).reshape(-1,4,4).data.cpu().numpy()
optimized_poses = tf@poses_normalized
optimized_poses = np.array(optimized_poses).astype(np.float32)
optimized_poses[:, :3, 3] /= sc_factor
optimized_poses[:, :3, 3] -= translation
original_init_ob_in_cam = optimized_poses[0].copy()
offset = np.linalg.inv(original_init_ob_in_cam)@original_poses[0]
for i in range(len(optimized_poses)):
new_ob_in_cam = optimized_poses[i]@offset
optimized_poses[i] = new_ob_in_cam
optimized_poses[i] = optimized_poses[i]@glcam_in_cvcam
return optimized_poses,offset
def preprocess_data(rgbs,depths,masks,normal_maps,poses,sc_factor,translation):
'''
@rgbs: np array (N,H,W,3)
@depths: (N,H,W)
@masks: (N,H,W)
@normal_maps: (N,H,W,3)
@poses: (N,4,4)
'''
depths[depths<0.001] = BAD_DEPTH
if masks is not None:
rgbs[masks==0] = BAD_COLOR
depths[masks==0] = BAD_DEPTH
if normal_maps is not None:
normal_maps[...,[1,2]] *= -1 # To OpenGL
normal_maps[masks==0] = 0
masks = masks[...,None]
rgbs = (rgbs / 255.0).astype(np.float32)
depths *= sc_factor
depths = depths[...,None]
poses[:, :3, 3] += translation
poses[:, :3, 3] *= sc_factor
return rgbs,depths,masks,normal_maps,poses
class NeRFSmall(nn.Module):
def __init__(self,num_layers=3,hidden_dim=64,geo_feat_dim=15,num_layers_color=4,hidden_dim_color=64,input_ch=3, input_ch_views=3):
super(NeRFSmall, self).__init__()
self.input_ch = input_ch
self.input_ch_views = input_ch_views
# sigma network
self.num_layers = num_layers
self.hidden_dim = hidden_dim
self.geo_feat_dim = geo_feat_dim
sigma_net = []
for l in range(num_layers):
if l == 0:
in_dim = self.input_ch
else:
in_dim = hidden_dim
if l == num_layers - 1:
out_dim = 1 + self.geo_feat_dim # 1 sigma + 15 SH features for color
else:
out_dim = hidden_dim
sigma_net.append(nn.Linear(in_dim, out_dim, bias=True))
if l!=num_layers-1:
sigma_net.append(nn.ReLU(inplace=True))
self.sigma_net = nn.Sequential(*sigma_net)
torch.nn.init.constant_(self.sigma_net[-1].bias, 0.1) # Encourage last layer predict positive SDF
# color network
self.num_layers_color = num_layers_color
self.hidden_dim_color = hidden_dim_color
color_net = []
for l in range(num_layers_color):
if l == 0:
in_dim = self.input_ch_views + self.geo_feat_dim
else:
in_dim = hidden_dim
if l == num_layers_color - 1:
out_dim = 3 # 3 rgb
else:
out_dim = hidden_dim
color_net.append(nn.Linear(in_dim, out_dim, bias=True))
if l!=num_layers_color-1:
color_net.append(nn.ReLU(inplace=True))
self.color_net = nn.Sequential(*color_net)
def forward_sdf(self,x):
'''
@x: embedded positions
'''
h = self.sigma_net(x)
sigma, geo_feat = h[..., 0], h[..., 1:]
return sigma
def forward(self, x):
x = x.float()
input_pts, input_views = torch.split(x, [self.input_ch, self.input_ch_views], dim=-1)
# sigma
h = input_pts
h = self.sigma_net(h)
sigma, geo_feat = h[..., 0], h[..., 1:]
# color
h = torch.cat([input_views, geo_feat], dim=-1)
color = self.color_net(h)
outputs = torch.cat([color, sigma.unsqueeze(dim=-1)], -1)
return outputs
def sample_pdf(bins, weights, N_samples, det=False):
weights = weights + 1e-5 # prevent nans
pdf = weights / torch.sum(weights, -1, keepdim=True)
cdf = torch.cumsum(pdf, -1)
cdf = torch.cat([torch.zeros_like(cdf[...,:1]), cdf], -1) # (batch, len(bins))
if det:
u = torch.linspace(0., 1., steps=N_samples)
u = u.expand(list(cdf.shape[:-1]) + [N_samples])
else:
u = torch.rand(list(cdf.shape[:-1]) + [N_samples])
u = u.contiguous()
inds = torch.searchsorted(cdf, u, right=True)
below = torch.max(torch.zeros_like(inds-1), inds-1)
above = torch.min((cdf.shape[-1]-1) * torch.ones_like(inds), inds)
inds_g = torch.stack([below, above], -1) # (batch, N_samples, 2)
matched_shape = [inds_g.shape[0], inds_g.shape[1], cdf.shape[-1]]
cdf_g = torch.gather(cdf.unsqueeze(1).expand(matched_shape), 2, inds_g)
bins_g = torch.gather(bins.unsqueeze(1).expand(matched_shape), 2, inds_g)
denom = (cdf_g[...,1]-cdf_g[...,0])
denom = torch.where(denom<1e-5, torch.ones_like(denom), denom)
t = (u-cdf_g[...,0])/denom
samples = bins_g[...,0] + t * (bins_g[...,1]-bins_g[...,0])
return samples
def get_camera_rays_np(H, W, K):
"""Get ray origins, directions from a pinhole camera."""
i, j = np.meshgrid(np.arange(W, dtype=np.float32),
np.arange(H, dtype=np.float32), indexing='xy')
dirs = np.stack([(i - K[0,2])/K[0,0], -(j - K[1,2])/K[1,1], -np.ones_like(i)], axis=-1)
return dirs
def get_masks(z_vals, target_d, truncation, cfg, dir_norm=None):
valid_depth_mask = (target_d>=cfg['near']*cfg['sc_factor']) & (target_d<=cfg['far']*cfg['sc_factor'])
front_mask = (z_vals < target_d - truncation)
back_mask = (z_vals > target_d + truncation*cfg['neg_trunc_ratio'])
sdf_mask = (1.0 - front_mask.float()) * (1.0 - back_mask.float()) * valid_depth_mask
num_fs_samples = front_mask.sum()
num_sdf_samples = sdf_mask.sum()
num_samples = num_sdf_samples + num_fs_samples
fs_weight = 0.5
sdf_weight = 1.0 - fs_weight
return front_mask.bool(), sdf_mask.bool(), fs_weight, sdf_weight
def get_sdf_loss(z_vals, target_d, predicted_sdf, truncation, cfg, return_mask=False, sample_weights=None, rays_d=None):
dir_norm = rays_d.norm(dim=-1,keepdim=True)
front_mask, sdf_mask, fs_weight, sdf_weight = get_masks(z_vals, target_d, truncation, cfg, dir_norm=dir_norm)
front_mask = front_mask.bool()
mask = (target_d>cfg['far']*cfg['sc_factor']) & (predicted_sdf<cfg['fs_sdf'])
fs_loss = torch.mean(((predicted_sdf-cfg['fs_sdf']) * mask)**2 * sample_weights) * fs_weight
mask = front_mask & (target_d<=cfg['far']*cfg['sc_factor']) & (predicted_sdf<1)
empty_loss = torch.mean(torch.abs(predicted_sdf-1) * mask * sample_weights)
sdf_loss = torch.mean(((z_vals + predicted_sdf * truncation) * sdf_mask - target_d * sdf_mask)**2 * sample_weights) * sdf_weight
if return_mask:
return fs_loss,sdf_loss,empty_loss, front_mask,sdf_mask
return fs_loss, sdf_loss, empty_loss
def ray_box_intersection_batch(origins, dirs, bounds):
'''
@origins: (N,3) origin and directions. In the same coordinate frame as the bounding box
@bounds: (2,3) xyz_min and max
'''
if not torch.is_tensor(origins):
origins = torch.tensor(origins)
dirs = torch.tensor(dirs)
if not torch.is_tensor(bounds):
bounds = torch.tensor(bounds)
dirs = dirs/(torch.norm(dirs,dim=-1,keepdim=True)+1e-10)
inv_dirs = 1/dirs
bounds = bounds[None].expand(len(dirs),-1,-1) #(N,2,3)
sign = torch.zeros((len(dirs),3)).long().to(dirs.device) #(N,3)
sign[:,0] = (inv_dirs[:,0] < 0)
sign[:,1] = (inv_dirs[:,1] < 0)
sign[:,2] = (inv_dirs[:,2] < 0)
tmin = (torch.gather(bounds[...,0],dim=1,index=sign[:,0].reshape(-1,1)).reshape(-1) - origins[:,0]) * inv_dirs[:,0] #(N)
tmin[tmin<0] = 0
tmax = (torch.gather(bounds[...,0],dim=1,index=1-sign[:,0].reshape(-1,1)).reshape(-1) - origins[:,0]) * inv_dirs[:,0]
tymin = (torch.gather(bounds[...,1],dim=1,index=sign[:,1].reshape(-1,1)).reshape(-1) - origins[:,1]) * inv_dirs[:,1]
tymin[tymin<0] = 0
tymax = (torch.gather(bounds[...,1],dim=1,index=1-sign[:,1].reshape(-1,1)).reshape(-1) - origins[:,1]) * inv_dirs[:,1]
ishit = torch.ones(len(dirs)).bool().to(dirs.device)
ishit[(tmin > tymax) | (tymin > tmax)] = 0
tmin[tymin>tmin] = tymin[tymin>tmin]
tmax[tymax<tmax] = tymax[tymax<tmax]
tzmin = (torch.gather(bounds[...,2],dim=1,index=sign[:,2].reshape(-1,1)).reshape(-1) - origins[:,2]) * inv_dirs[:,2]
tzmin[tzmin<0] = 0
tzmax = (torch.gather(bounds[...,2],dim=1,index=1-sign[:,2].reshape(-1,1)).reshape(-1) - origins[:,2]) * inv_dirs[:,2]
ishit[(tmin > tzmax) | (tzmin > tmax)] = 0
tmin[tzmin>tmin] = tzmin[tzmin>tmin] #(N)
tmax[tzmax<tmax] = tzmax[tzmax<tmax]
tmin[ishit==0] = -1
tmax[ishit==0] = -1
return tmin, tmax
File diff suppressed because it is too large Load Diff
+115
View File
@@ -0,0 +1,115 @@
# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto. Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
from nerf_runner import *
code_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(f'{code_dir}/../')
from datareader import *
from bundlesdf.tool import *
import yaml,argparse
def run_neural_object_field(cfg, K, rgbs, depths, masks, cam_in_obs, debug=0, save_dir='/home/bowen/debug/foundationpose_bundlesdf'):
rgbs = np.asarray(rgbs)
depths = np.asarray(depths)
masks = np.asarray(masks)
cam_in_obs = np.asarray(cam_in_obs)
glcam_in_obs = cam_in_obs@glcam_in_cvcam
cfg['save_dir'] = save_dir
os.makedirs(save_dir, exist_ok=True)
for i,rgb in enumerate(rgbs):
imageio.imwrite(f'{save_dir}/rgb_{i:07d}.png', rgb)
sc_factor,translation,pcd_real_scale, pcd_normalized = compute_scene_bounds(None,glcam_in_obs, K, use_mask=True,base_dir=save_dir,rgbs=rgbs,depths=depths,masks=masks, eps=cfg['dbscan_eps'], min_samples=cfg['dbscan_eps_min_samples'])
cfg['sc_factor'] = sc_factor
cfg['translation'] = translation
o3d.io.write_point_cloud(f'{save_dir}/pcd_normalized.ply', pcd_normalized)
rgbs_, depths_, masks_, normal_maps,poses = preprocess_data(rgbs, depths, masks,normal_maps=None,poses=glcam_in_obs,sc_factor=cfg['sc_factor'],translation=cfg['translation'])
nerf = NerfRunner(cfg, rgbs_, depths_, masks_, normal_maps=None, poses=poses, K=K, occ_masks=None, build_octree_pcd=pcd_normalized)
nerf.train()
mesh = nerf.extract_mesh(isolevel=0,voxel_size=cfg['mesh_resolution'])
mesh = nerf.mesh_texture_from_train_images(mesh, rgbs_raw=rgbs, tex_res=1028)
optimized_cvcam_in_obs,offset = get_optimized_poses_in_real_world(poses,nerf.models['pose_array'],cfg['sc_factor'],cfg['translation'])
mesh = mesh_to_real_world(mesh, pose_offset=offset, translation=nerf.cfg['translation'], sc_factor=nerf.cfg['sc_factor'])
return mesh
def run_one_ob(base_dir, cfg, use_refined_mask=False):
save_dir = f'{base_dir}/nerf'
os.system(f'rm -rf {save_dir} && mkdir -p {save_dir}')
with open(f'{base_dir}/select_frames.yml','r') as ff:
info = yaml.safe_load(ff)
rgbs = []
depths = []
masks = []
cam_in_obs = []
color_files = sorted(glob.glob(f'{base_dir}/rgb/*.png'))
K = np.loadtxt(f'{base_dir}/K.txt')
for i,color_file in enumerate(color_files):
rgb = imageio.imread(color_file)
depth = cv2.imread(color_file.replace('rgb','depth_enhanced'), -1)/1e3
if use_refined_mask:
mask = cv2.imread(color_file.replace('rgb','mask_refined'), -1)
else:
mask = cv2.imread(color_file.replace('rgb','mask'), -1)
cam_in_ob = np.loadtxt(color_file.replace('rgb','cam_in_ob').replace('.png','.txt')).reshape(4,4)
rgbs.append(rgb)
depths.append(depth)
masks.append(mask)
cam_in_obs.append(cam_in_ob)
mesh = run_neural_object_field(cfg, K, rgbs, depths, masks, cam_in_obs, save_dir=save_dir, debug=0)
return mesh
def run_ycbv():
ob_ids = np.arange(1,22)
code_dir = os.path.dirname(os.path.realpath(__file__))
with open(f'{code_dir}/config_ycbv.yml','r') as ff:
cfg = yaml.safe_load(ff)
for ob_id in ob_ids:
base_dir = f'{args.ref_view_dir}/ob_{ob_id:07d}'
mesh = run_one_ob(base_dir=base_dir, cfg=cfg)
out_file = f'{base_dir}/model/model.obj'
os.makedirs(os.path.dirname(out_file), exist_ok=True)
mesh.export(out_file)
def run_linemod():
ob_ids = np.setdiff1d(np.arange(1,16), np.array([7,3])).tolist()
code_dir = os.path.dirname(os.path.realpath(__file__))
with open(f'{code_dir}/config_linemod.yml','r') as ff:
cfg = yaml.safe_load(ff)
for ob_id in ob_ids:
base_dir = f'{args.ref_view_dir}/ob_{ob_id:07d}'
mesh = run_one_ob(base_dir=base_dir, cfg=cfg, use_refined_mask=True)
out_file = f'{base_dir}/model/model.obj'
os.makedirs(os.path.dirname(out_file), exist_ok=True)
mesh.export(out_file)
logging.info(f"saved to {out_file}")
if __name__=="__main__":
parser = argparse.ArgumentParser()
code_dir = os.path.dirname(os.path.realpath(__file__))
parser.add_argument('--ref_view_dir', type=str, default=f'/mnt/9a72c439-d0a7-45e8-8d20-d7a235d02763/DATASET/YCB_Video/bowen_addon/ref_views_16')
parser.add_argument('--dataset', type=str, default=f'ycbv', help='one of [ycbv/linemod]')
args = parser.parse_args()
if args.dataset=='ycbv':
run_ycbv()
else:
run_linemod()
+132
View File
@@ -0,0 +1,132 @@
# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto. Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
import joblib,json,gzip,pickle
from sklearn.cluster import DBSCAN
import shutil,re,imageio,pdb,os,sys
from Utils import *
import pandas as pd
def find_biggest_cluster(pts, eps=0.06, min_samples=1):
dbscan = DBSCAN(eps=eps,min_samples=min_samples,n_jobs=-1)
dbscan.fit(pts)
ids, cnts = np.unique(dbscan.labels_, return_counts=True)
best_id = ids[cnts.argsort()[-1]]
keep_mask = dbscan.labels_==best_id
pts_cluster = pts[keep_mask]
return pts_cluster, keep_mask
def compute_translation_scales(pts,max_dim=2,cluster=True, eps=0.06, min_samples=1):
if cluster:
pts, keep_mask = find_biggest_cluster(pts, eps, min_samples)
else:
keep_mask = np.ones((len(pts)), dtype=bool)
max_xyz = pts.max(axis=0)
min_xyz = pts.min(axis=0)
center = (max_xyz+min_xyz)/2
sc_factor = max_dim/(max_xyz-min_xyz).max() #Normalize to [-1,1]
sc_factor *= 0.9
translation_cvcam = -center
return translation_cvcam, sc_factor, keep_mask
def compute_scene_bounds_worker(color_file,K,glcam_in_world,use_mask,rgb=None,depth=None,mask=None):
if rgb is None:
depth_file = color_file.replace('images','depth_filtered')
mask_file = color_file.replace('images','masks')
rgb = np.array(Image.open(color_file))[...,:3]
depth = cv2.imread(depth_file,-1)/1e3
xyz_map = depth2xyzmap(depth,K)
valid = depth>=0.1
if use_mask:
if mask is None:
mask = cv2.imread(mask_file,-1)
valid = valid & (mask>0)
pts = xyz_map[valid].reshape(-1,3)
if len(pts)==0:
return None
colors = rgb[valid].reshape(-1,3)
pcd = toOpen3dCloud(pts,colors)
pcd = pcd.voxel_down_sample(0.01)
pcd, ind = pcd.remove_statistical_outlier(nb_neighbors=30,std_ratio=2.0)
cam_in_world = glcam_in_world@glcam_in_cvcam
pcd.transform(cam_in_world)
return np.asarray(pcd.points).copy(), np.asarray(pcd.colors).copy()
def compute_scene_bounds(color_files,glcam_in_worlds,K,use_mask=True,base_dir=None,rgbs=None,depths=None,masks=None,cluster=True, translation_cvcam=None, sc_factor=None, eps=0.06, min_samples=1):
assert color_files is None or rgbs is None
if base_dir is None:
base_dir = os.path.dirname(color_files[0])+'/../'
args = []
if rgbs is not None:
for i in range(len(rgbs)):
args.append((None,K,glcam_in_worlds[i],use_mask,rgbs[i],depths[i],masks[i]))
else:
for i in range(len(color_files)):
args.append((color_files[i],K,glcam_in_worlds[i],use_mask))
logging.info(f"compute_scene_bounds_worker start")
ret = joblib.Parallel(n_jobs=10, prefer="threads")(joblib.delayed(compute_scene_bounds_worker)(*arg) for arg in args)
logging.info(f"compute_scene_bounds_worker done")
pcd_all = None
for r in ret:
if r is None:
continue
if pcd_all is None:
pcd_all = toOpen3dCloud(r[0],r[1])
else:
pcd_all += toOpen3dCloud(r[0],r[1])
pcd = pcd_all.voxel_down_sample(eps/5)
logging.info(f"merge pcd")
o3d.io.write_point_cloud(f'{base_dir}/naive_fusion.ply',pcd)
pts = np.asarray(pcd.points).copy()
def make_tf(translation_cvcam, sc_factor):
tf = np.eye(4)
tf[:3,3] = translation_cvcam
tf1 = np.eye(4)
tf1[:3,:3] *= sc_factor
tf = tf1@tf
return tf
if translation_cvcam is None:
translation_cvcam, sc_factor, keep_mask = compute_translation_scales(pts, cluster=cluster, eps=eps, min_samples=min_samples)
tf = make_tf(translation_cvcam, sc_factor)
else:
tf = make_tf(translation_cvcam, sc_factor)
tmp = copy.deepcopy(pcd)
tmp.transform(tf)
tmp_pts = np.asarray(tmp.points)
keep_mask = (np.abs(tmp_pts)<1).all(axis=-1)
logging.info(f"compute_translation_scales done")
pcd = toOpen3dCloud(pts[keep_mask],np.asarray(pcd.colors)[keep_mask])
o3d.io.write_point_cloud(f"{base_dir}/naive_fusion_biggest_cluster.ply",pcd)
pcd_real_scale = copy.deepcopy(pcd)
print(f'translation_cvcam={translation_cvcam}, sc_factor={sc_factor}')
with open(f'{base_dir}/normalization.yml','w') as ff:
tmp = {
'translation_cvcam':translation_cvcam.tolist(),
'sc_factor':float(sc_factor),
}
yaml.dump(tmp,ff)
pcd.transform(tf)
return sc_factor, translation_cvcam, pcd_real_scale, pcd