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
@@ -0,0 +1,220 @@
# 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 os,sys,h5py,bisect,io,json
code_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(f'{code_dir}/../../../../')
from Utils import *
from learning.datasets.pose_dataset import *
class PairH5Dataset(torch.utils.data.Dataset):
def __init__(self, cfg, h5_file, mode='train', max_num_key=None, cache_data=None):
self.cfg = cfg
self.h5_file = h5_file
self.mode = mode
logging.info(f"self.h5_file:{self.h5_file}")
self.n_perturb = None
self.H_ori = None
self.W_ori = None
self.cache_data = cache_data
if self.mode=='test':
pass
else:
self.object_keys = []
key_file = h5_file.replace('.h5','_keys.pkl')
if os.path.exists(key_file):
with open(key_file, 'rb') as ff:
self.object_keys = pickle.load(ff)
logging.info(f'object_keys loaded#:{len(self.object_keys)} from {key_file}')
if max_num_key is not None:
self.object_keys = self.object_keys[:max_num_key]
else:
with h5py.File(h5_file, 'r', libver='latest') as hf:
for k in hf:
self.object_keys.append(k)
if max_num_key is not None and len(self.object_keys)>=max_num_key:
logging.info("break due to max_num_key")
break
logging.info(f'self.object_keys#:{len(self.object_keys)}, max_num_key:{max_num_key}')
with h5py.File(h5_file, 'r', libver='latest') as hf:
group = hf[self.object_keys[0]]
cnt = 0
for k_perturb in group:
if 'i_perturb' in k_perturb:
cnt += 1
if 'crop_ratio' in group[k_perturb]:
self.cfg['crop_ratio'] = float(group[k_perturb]['crop_ratio'][()])
if self.H_ori is None:
if 'H_ori' in group[k_perturb]:
self.H_ori = int(group[k_perturb]['H_ori'][()])
self.W_ori = int(group[k_perturb]['W_ori'][()])
else:
self.H_ori = 540
self.W_ori = 720
self.n_perturb = cnt
logging.info(f'self.n_perturb:{self.n_perturb}')
def __len__(self):
if self.mode=='test':
return 1
return len(self.object_keys)
def transform_depth_to_xyzmap(self, batch:BatchPoseData, H_ori, W_ori, bound=1):
bs = len(batch.rgbAs)
H,W = batch.rgbAs.shape[-2:]
mesh_radius = batch.mesh_diameters.cuda()/2
tf_to_crops = batch.tf_to_crops.cuda()
crop_to_oris = batch.tf_to_crops.inverse().cuda() #(B,3,3)
batch.poseA = batch.poseA.cuda()
batch.Ks = batch.Ks.cuda()
if batch.xyz_mapAs is None:
depthAs_ori = kornia.geometry.transform.warp_perspective(batch.depthAs.cuda().expand(bs,-1,-1,-1), crop_to_oris, dsize=(H_ori, W_ori), mode='nearest', align_corners=False)
batch.xyz_mapAs = depth2xyzmap_batch(depthAs_ori[:,0], batch.Ks, zfar=np.inf).permute(0,3,1,2) #(B,3,H,W)
batch.xyz_mapAs = kornia.geometry.transform.warp_perspective(batch.xyz_mapAs, tf_to_crops, dsize=(H,W), mode='nearest', align_corners=False)
batch.xyz_mapAs = batch.xyz_mapAs.cuda()
if self.cfg['normalize_xyz']:
invalid = batch.xyz_mapAs[:,2:3]<0.001
batch.xyz_mapAs = batch.xyz_mapAs-batch.poseA[:,:3,3].reshape(bs,3,1,1)
if self.cfg['normalize_xyz']:
batch.xyz_mapAs *= 1/mesh_radius.reshape(bs,1,1,1)
invalid = invalid.expand(bs,3,-1,-1) | (torch.abs(batch.xyz_mapAs)>=2)
batch.xyz_mapAs[invalid.expand(bs,3,-1,-1)] = 0
if batch.xyz_mapBs is None:
depthBs_ori = kornia.geometry.transform.warp_perspective(batch.depthBs.cuda().expand(bs,-1,-1,-1), crop_to_oris, dsize=(H_ori, W_ori), mode='nearest', align_corners=False)
batch.xyz_mapBs = depth2xyzmap_batch(depthBs_ori[:,0], batch.Ks, zfar=np.inf).permute(0,3,1,2) #(B,3,H,W)
batch.xyz_mapBs = kornia.geometry.transform.warp_perspective(batch.xyz_mapBs, tf_to_crops, dsize=(H,W), mode='nearest', align_corners=False)
batch.xyz_mapBs = batch.xyz_mapBs.cuda()
if self.cfg['normalize_xyz']:
invalid = batch.xyz_mapBs[:,2:3]<0.001
batch.xyz_mapBs = batch.xyz_mapBs-batch.poseA[:,:3,3].reshape(bs,3,1,1)
if self.cfg['normalize_xyz']:
batch.xyz_mapBs *= 1/mesh_radius.reshape(bs,1,1,1)
invalid = invalid.expand(bs,3,-1,-1) | (torch.abs(batch.xyz_mapBs)>=2)
batch.xyz_mapBs[invalid.expand(bs,3,-1,-1)] = 0
return batch
def transform_batch(self, batch:BatchPoseData, H_ori, W_ori, bound=1):
'''Transform the batch before feeding to the network
!NOTE the H_ori, W_ori could be different at test time from the training data, and needs to be set
'''
bs = len(batch.rgbAs)
batch.rgbAs = batch.rgbAs.cuda().float()/255.0
batch.rgbBs = batch.rgbBs.cuda().float()/255.0
batch = self.transform_depth_to_xyzmap(batch, H_ori, W_ori, bound=bound)
return batch
class TripletH5Dataset(PairH5Dataset):
def __init__(self, cfg, h5_file, mode, max_num_key=None, cache_data=None):
super().__init__(cfg, h5_file, mode, max_num_key, cache_data=cache_data)
def transform_depth_to_xyzmap(self, batch:BatchPoseData, H_ori, W_ori, bound=1):
bs = len(batch.rgbAs)
H,W = batch.rgbAs.shape[-2:]
mesh_radius = batch.mesh_diameters.cuda()/2
tf_to_crops = batch.tf_to_crops.cuda()
crop_to_oris = batch.tf_to_crops.inverse().cuda() #(B,3,3)
batch.poseA = batch.poseA.cuda()
batch.Ks = batch.Ks.cuda()
if batch.xyz_mapAs is None:
depthAs_ori = kornia.geometry.transform.warp_perspective(batch.depthAs.cuda().expand(bs,-1,-1,-1), crop_to_oris, dsize=(H_ori, W_ori), mode='nearest', align_corners=False)
batch.xyz_mapAs = depth2xyzmap_batch(depthAs_ori[:,0], batch.Ks, zfar=np.inf).permute(0,3,1,2) #(B,3,H,W)
batch.xyz_mapAs = kornia.geometry.transform.warp_perspective(batch.xyz_mapAs, tf_to_crops, dsize=(H,W), mode='nearest', align_corners=False)
batch.xyz_mapAs = batch.xyz_mapAs.cuda()
invalid = batch.xyz_mapAs[:,2:3]<0.1
batch.xyz_mapAs = (batch.xyz_mapAs-batch.poseA[:,:3,3].reshape(bs,3,1,1))
if self.cfg['normalize_xyz']:
batch.xyz_mapAs *= 1/mesh_radius.reshape(bs,1,1,1)
invalid = invalid.expand(bs,3,-1,-1) | (torch.abs(batch.xyz_mapAs)>=2)
batch.xyz_mapAs[invalid.expand(bs,3,-1,-1)] = 0
if batch.xyz_mapBs is None:
depthBs_ori = kornia.geometry.transform.warp_perspective(batch.depthBs.cuda().expand(bs,-1,-1,-1), crop_to_oris, dsize=(H_ori, W_ori), mode='nearest', align_corners=False)
batch.xyz_mapBs = depth2xyzmap_batch(depthBs_ori[:,0], batch.Ks, zfar=np.inf).permute(0,3,1,2) #(B,3,H,W)
batch.xyz_mapBs = kornia.geometry.transform.warp_perspective(batch.xyz_mapBs, tf_to_crops, dsize=(H,W), mode='nearest', align_corners=False)
batch.xyz_mapBs = batch.xyz_mapBs.cuda()
invalid = batch.xyz_mapBs[:,2:3]<0.1
batch.xyz_mapBs = (batch.xyz_mapBs-batch.poseA[:,:3,3].reshape(bs,3,1,1))
if self.cfg['normalize_xyz']:
batch.xyz_mapBs *= 1/mesh_radius.reshape(bs,1,1,1)
invalid = invalid.expand(bs,3,-1,-1) | (torch.abs(batch.xyz_mapBs)>=2)
batch.xyz_mapBs[invalid.expand(bs,3,-1,-1)] = 0
return batch
def transform_batch(self, batch:BatchPoseData, H_ori, W_ori, bound=1):
bs = len(batch.rgbAs)
batch.rgbAs = batch.rgbAs.cuda().float()/255.0
batch.rgbBs = batch.rgbBs.cuda().float()/255.0
batch = self.transform_depth_to_xyzmap(batch, H_ori, W_ori, bound=bound)
return batch
class ScoreMultiPairH5Dataset(TripletH5Dataset):
def __init__(self, cfg, h5_file, mode, max_num_key=None, cache_data=None):
super().__init__(cfg, h5_file, mode, max_num_key, cache_data=cache_data)
if mode in ['train', 'val']:
self.cfg['train_num_pair'] = self.n_perturb
class PoseRefinePairH5Dataset(PairH5Dataset):
def __init__(self, cfg, h5_file, mode='train', max_num_key=None, cache_data=None):
super().__init__(cfg=cfg, h5_file=h5_file, mode=mode, max_num_key=max_num_key, cache_data=cache_data)
if mode!='test':
with h5py.File(h5_file, 'r', libver='latest') as hf:
group = hf[self.object_keys[0]]
for key_perturb in group:
depthA = imageio.imread(group[key_perturb]['depthA'][()])
depthB = imageio.imread(group[key_perturb]['depthB'][()])
self.cfg['n_view'] = min(self.cfg['n_view'], depthA.shape[1]//depthB.shape[1])
logging.info(f'n_view:{self.cfg["n_view"]}')
self.trans_normalizer = group[key_perturb]['trans_normalizer'][()]
if isinstance(self.trans_normalizer, np.ndarray):
self.trans_normalizer = self.trans_normalizer.tolist()
self.rot_normalizer = group[key_perturb]['rot_normalizer'][()]/180.0*np.pi
logging.info(f'self.trans_normalizer:{self.trans_normalizer}, self.rot_normalizer:{self.rot_normalizer}')
break
def transform_batch(self, batch:BatchPoseData, H_ori, W_ori, bound=1):
'''Transform the batch before feeding to the network
!NOTE the H_ori, W_ori could be different at test time from the training data, and needs to be set
'''
bs = len(batch.rgbAs)
batch.rgbAs = batch.rgbAs.cuda().float()/255.0
batch.rgbBs = batch.rgbBs.cuda().float()/255.0
batch = self.transform_depth_to_xyzmap(batch, H_ori, W_ori, bound=bound)
return batch
@@ -0,0 +1,135 @@
# 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 os,sys
from dataclasses import dataclass
from typing import Iterator, List, Optional, Set, Union
import numpy as np
import torch
code_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(f'{code_dir}/../../../../')
from Utils import *
@dataclass
class PoseData:
"""
rgb: (h, w, 3) uint8
depth: (bsz, h, w) float32
bbox: (4, ) int
K: (3, 3) float32
"""
rgb: np.ndarray = None
bbox: np.ndarray = None
K: np.ndarray = None
depth: Optional[np.ndarray] = None
object_data = None
mesh_diameter: float = None
rgbA: np.ndarray = None
rgbB: np.ndarray = None
depthA: np.ndarray = None
depthB: np.ndarray = None
maskA = None
maskB = None
poseA: np.ndarray = None #(4,4)
target: float = None
def __init__(self, rgbA=None, rgbB=None, depthA=None, depthB=None, maskA=None, maskB=None, normalA=None, normalB=None, xyz_mapA=None, xyz_mapB=None, poseA=None, poseB=None, K=None, target=None, mesh_diameter=None, tf_to_crop=None, crop_mask=None, model_pts=None, label=None, model_scale=None):
self.rgbA = rgbA #(H,W,3) or (H,W*n_view,3) when multiview
self.rgbB = rgbB
self.depthA = depthA
self.depthB = depthB
self.poseA = poseA
self.poseB = poseB
self.maskA = maskA
self.maskB = maskB
self.crop_mask = crop_mask
self.normalA = normalA
self.normalB = normalB
self.xyz_mapA = xyz_mapA
self.xyz_mapB = xyz_mapB
self.target = target
self.K = K
self.mesh_diameter = mesh_diameter
self.tf_to_crop = tf_to_crop
self.model_pts = model_pts
self.label = label
self.model_scale = model_scale
@dataclass
class BatchPoseData:
"""
rgbs: (bsz, 3, h, w) torch tensor uint8
depths: (bsz, h, w) float32
bboxes: (bsz, 4) int
K: (bsz, 3, 3) float32
"""
rgbs: torch.Tensor = None
object_datas = None
bboxes: torch.Tensor = None
K: torch.Tensor = None
depths: Optional[torch.Tensor] = None
rgbAs = None
rgbBs = None
depthAs = None
depthBs = None
normalAs = None
normalBs = None
poseA = None #(B,4,4)
poseB = None
targets = None # Score targets, torch tensor (B)
def __init__(self, rgbAs=None, rgbBs=None, depthAs=None, depthBs=None, normalAs=None, normalBs=None, maskAs=None, maskBs=None, poseA=None, poseB=None, xyz_mapAs=None, xyz_mapBs=None, tf_to_crops=None, Ks=None, crop_masks=None, model_pts=None, mesh_diameters=None, labels=None):
self.rgbAs = rgbAs
self.rgbBs = rgbBs
self.depthAs = depthAs
self.depthBs = depthBs
self.normalAs = normalAs
self.normalBs = normalBs
self.poseA = poseA
self.poseB = poseB
self.maskAs = maskAs
self.maskBs = maskBs
self.xyz_mapAs = xyz_mapAs
self.xyz_mapBs = xyz_mapBs
self.tf_to_crops = tf_to_crops
self.crop_masks = crop_masks
self.Ks = Ks
self.model_pts = model_pts
self.mesh_diameters = mesh_diameters
self.labels = labels
def pin_memory(self) -> "BatchPoseData":
for k in self.__dict__:
if self.__dict__[k] is not None:
try:
self.__dict__[k] = self.__dict__[k].pin_memory()
except Exception as e:
pass
return self
def cuda(self):
for k in self.__dict__:
if self.__dict__[k] is not None:
try:
self.__dict__[k] = self.__dict__[k].cuda()
except:
pass
return self
def select_by_indices(self, ids):
out = BatchPoseData()
for k in self.__dict__:
if self.__dict__[k] is not None:
out.__dict__[k] = self.__dict__[k][ids.to(self.__dict__[k].device)]
return out
@@ -0,0 +1,138 @@
# 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 os,sys,copy,math,tqdm
import numpy as np
dir_path = os.path.dirname(os.path.realpath(__file__))
sys.path.append(dir_path)
import torch.nn.functional as F
import torch
import torch.nn as nn
import time
import cv2
sys.path.append(f'{dir_path}/../../../../')
from Utils import *
class ConvBN(nn.Module):
def __init__(self, C_in, C_out, kernel_size=3, stride=1, groups=1, bias=True,dilation=1,):
super().__init__()
padding = (kernel_size - 1) // 2
self.net = nn.Sequential(
nn.Conv2d(C_in, C_out, kernel_size, stride, padding, groups=groups, bias=bias,dilation=dilation),
nn.BatchNorm2d(C_out),
)
def forward(self, x):
return self.net(x)
class ConvBNReLU(nn.Module):
def __init__(self, C_in, C_out, kernel_size=3, stride=1, groups=1, bias=True,dilation=1, norm_layer=nn.BatchNorm2d):
super().__init__()
padding = (kernel_size - 1) // 2
layers = [
nn.Conv2d(C_in, C_out, kernel_size, stride, padding, groups=groups, bias=bias,dilation=dilation),
]
if norm_layer is not None:
layers.append(norm_layer(C_out))
layers.append(nn.ReLU(inplace=True))
self.net = nn.Sequential(*layers)
def forward(self, x):
return self.net(x)
class ConvPadding(nn.Module):
def __init__(self,C_in, C_out, kernel_size=3, stride=1, groups=1, bias=True,dilation=1):
super(ConvPadding, self).__init__()
padding = (kernel_size - 1) // 2
self.conv = nn.Conv2d(C_in, C_out, kernel_size, stride, padding, groups=groups, bias=bias,dilation=dilation)
def forward(self,x):
return self.conv(x)
def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1, bias=False):
"""3x3 convolution with padding"""
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=dilation, groups=groups, bias=bias, dilation=dilation)
def conv1x1(in_planes, out_planes, stride=1, bias=False):
"""1x1 convolution"""
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=bias)
class ResnetBasicBlock(nn.Module):
__constants__ = ['downsample']
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1, base_width=64, dilation=1, norm_layer=nn.BatchNorm2d, bias=False):
super().__init__()
self.norm_layer = norm_layer
if groups != 1 or base_width != 64:
raise ValueError('BasicBlock only supports groups=1 and base_width=64')
if dilation > 1:
raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
# Both self.conv1 and self.downsample layers downsample the input when stride != 1
self.conv1 = conv3x3(inplanes, planes, stride,bias=bias)
if self.norm_layer is not None:
self.bn1 = norm_layer(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes,bias=bias)
if self.norm_layer is not None:
self.bn2 = norm_layer(planes)
self.downsample = downsample
self.stride = stride
def forward(self, x):
identity = x
out = self.conv1(x)
if self.norm_layer is not None:
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
if self.norm_layer is not None:
out = self.bn2(out)
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out
class PositionalEmbedding(nn.Module):
def __init__(self, d_model, max_len=512):
super().__init__()
# Compute the positional encodings once in log space.
pe = torch.zeros(max_len, d_model).float()
pe.require_grad = False
position = torch.arange(0, max_len).float().unsqueeze(1) #(N,1)
div_term = (torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model)).exp()[None]
pe[:, 0::2] = torch.sin(position * div_term) #(N, d_model/2)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe) #(1, max_len, D)
def forward(self, x):
'''
@x: (B,N,D)
'''
return x + self.pe[:, :x.size(1)]
@@ -0,0 +1,93 @@
# 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 os,sys
import numpy as np
code_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(code_dir)
sys.path.append(f'{code_dir}/../../../../')
from Utils import *
import torch.nn.functional as F
import torch
import torch.nn as nn
import cv2
from functools import partial
from network_modules import *
from Utils import *
class RefineNet(nn.Module):
def __init__(self, cfg=None, c_in=4, n_view=1):
super().__init__()
self.cfg = cfg
if self.cfg.use_BN:
norm_layer = nn.BatchNorm2d
norm_layer1d = nn.BatchNorm1d
else:
norm_layer = None
norm_layer1d = None
self.encodeA = nn.Sequential(
ConvBNReLU(C_in=c_in,C_out=64,kernel_size=7,stride=2, norm_layer=norm_layer),
ConvBNReLU(C_in=64,C_out=128,kernel_size=3,stride=2, norm_layer=norm_layer),
ResnetBasicBlock(128,128,bias=True, norm_layer=norm_layer),
ResnetBasicBlock(128,128,bias=True, norm_layer=norm_layer),
)
self.encodeAB = nn.Sequential(
ResnetBasicBlock(256,256,bias=True, norm_layer=norm_layer),
ResnetBasicBlock(256,256,bias=True, norm_layer=norm_layer),
ConvBNReLU(256,512,kernel_size=3,stride=2, norm_layer=norm_layer),
ResnetBasicBlock(512,512,bias=True, norm_layer=norm_layer),
ResnetBasicBlock(512,512,bias=True, norm_layer=norm_layer),
)
embed_dim = 512
num_heads = 4
self.pos_embed = PositionalEmbedding(d_model=embed_dim, max_len=400)
self.trans_head = nn.Sequential(
nn.TransformerEncoderLayer(d_model=embed_dim, nhead=num_heads, dim_feedforward=512, batch_first=True),
nn.Linear(512, 3),
)
if self.cfg['rot_rep']=='axis_angle':
rot_out_dim = 3
elif self.cfg['rot_rep']=='6d':
rot_out_dim = 6
else:
raise RuntimeError
self.rot_head = nn.Sequential(
nn.TransformerEncoderLayer(d_model=embed_dim, nhead=num_heads, dim_feedforward=512, batch_first=True),
nn.Linear(512, rot_out_dim),
)
def forward(self, A, B):
"""
@A: (B,C,H,W)
"""
bs = len(A)
output = {}
x = torch.cat([A,B], dim=0)
x = self.encodeA(x)
a = x[:bs]
b = x[bs:]
ab = torch.cat((a,b),1).contiguous()
ab = self.encodeAB(ab) #(B,C,H,W)
ab = self.pos_embed(ab.reshape(bs, ab.shape[1], -1).permute(0,2,1))
output['trans'] = self.trans_head(ab).mean(dim=1)
output['rot'] = self.rot_head(ab).mean(dim=1)
return output
@@ -0,0 +1,90 @@
# 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 os,sys
import numpy as np
code_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(code_dir)
sys.path.append(f'{code_dir}/../../../../')
from Utils import *
from functools import partial
import torch.nn.functional as F
import torch
import torch.nn as nn
import cv2
from network_modules import *
from Utils import *
class ScoreNetMultiPair(nn.Module):
def __init__(self, cfg=None, c_in=4):
super().__init__()
self.cfg = cfg
if self.cfg.use_BN:
norm_layer = nn.BatchNorm2d
else:
norm_layer = None
self.encoderA = nn.Sequential(
ConvBNReLU(C_in=c_in,C_out=64,kernel_size=7,stride=2, norm_layer=norm_layer),
ConvBNReLU(C_in=64,C_out=128,kernel_size=3,stride=2, norm_layer=norm_layer),
ResnetBasicBlock(128,128,bias=True, norm_layer=norm_layer),
ResnetBasicBlock(128,128,bias=True, norm_layer=norm_layer),
)
self.encoderAB = nn.Sequential(
ResnetBasicBlock(256,256,bias=True, norm_layer=norm_layer),
ResnetBasicBlock(256,256,bias=True, norm_layer=norm_layer),
ConvBNReLU(256,512,kernel_size=3,stride=2, norm_layer=norm_layer),
ResnetBasicBlock(512,512,bias=True, norm_layer=norm_layer),
ResnetBasicBlock(512,512,bias=True, norm_layer=norm_layer),
)
embed_dim = 512
num_heads = 4
self.att = nn.MultiheadAttention(embed_dim=embed_dim, num_heads=num_heads, bias=True, batch_first=True)
self.att_cross = nn.MultiheadAttention(embed_dim=embed_dim, num_heads=num_heads, bias=True, batch_first=True)
self.pos_embed = PositionalEmbedding(d_model=embed_dim, max_len=400)
self.linear = nn.Linear(embed_dim, 1)
def extract_feat(self, A, B):
"""
@A: (B*L,C,H,W) L is num of pairs
"""
bs = A.shape[0] # B*L
x = torch.cat([A,B], dim=0)
x = self.encoderA(x)
a = x[:bs]
b = x[bs:]
ab = torch.cat((a,b), dim=1)
ab = self.encoderAB(ab)
ab = self.pos_embed(ab.reshape(bs, ab.shape[1], -1).permute(0,2,1))
ab, _ = self.att(ab, ab, ab)
return ab.mean(dim=1).reshape(bs,-1)
def forward(self, A, B, L):
"""
@A: (B*L,C,H,W) L is num of pairs
@L: num of pairs
"""
output = {}
bs = A.shape[0]//L
feats = self.extract_feat(A, B) #(B*L, C)
x = feats.reshape(bs,L,-1)
x, _ = self.att_cross(x, x, x)
output['score_logit'] = self.linear(x).reshape(bs,L) # (B,L)
return output
@@ -0,0 +1,296 @@
# 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 functools
import os,sys,kornia
import time
code_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(f'{code_dir}/../../')
import numpy as np
import torch
from omegaconf import OmegaConf
from learning.models.refine_network import RefineNet
from learning.datasets.h5_dataset import *
from Utils import *
from datareader import *
@torch.inference_mode()
def make_crop_data_batch(render_size, ob_in_cams, mesh, rgb, depth, K, crop_ratio, xyz_map, normal_map=None, mesh_diameter=None, cfg=None, glctx=None, mesh_tensors=None, dataset:PoseRefinePairH5Dataset=None):
logging.info("Welcome make_crop_data_batch")
H,W = depth.shape[:2]
args = []
method = 'box_3d'
tf_to_crops = compute_crop_window_tf_batch(pts=mesh.vertices, H=H, W=W, poses=ob_in_cams, K=K, crop_ratio=crop_ratio, out_size=(render_size[1], render_size[0]), method=method, mesh_diameter=mesh_diameter)
logging.info("make tf_to_crops done")
B = len(ob_in_cams)
poseA = torch.as_tensor(ob_in_cams, dtype=torch.float, device='cuda')
bs = 512
rgb_rs = []
depth_rs = []
normal_rs = []
xyz_map_rs = []
bbox2d_crop = torch.as_tensor(np.array([0, 0, cfg['input_resize'][0]-1, cfg['input_resize'][1]-1]).reshape(2,2), device='cuda', dtype=torch.float)
bbox2d_ori = transform_pts(bbox2d_crop, tf_to_crops.inverse()).reshape(-1,4)
for b in range(0,len(poseA),bs):
extra = {}
rgb_r, depth_r, normal_r = nvdiffrast_render(K=K, H=H, W=W, ob_in_cams=poseA[b:b+bs], context='cuda', get_normal=cfg['use_normal'], glctx=glctx, mesh_tensors=mesh_tensors, output_size=cfg['input_resize'], bbox2d=bbox2d_ori[b:b+bs], use_light=True, extra=extra)
rgb_rs.append(rgb_r)
depth_rs.append(depth_r[...,None])
normal_rs.append(normal_r)
xyz_map_rs.append(extra['xyz_map'])
rgb_rs = torch.cat(rgb_rs, dim=0).permute(0,3,1,2) * 255
depth_rs = torch.cat(depth_rs, dim=0).permute(0,3,1,2) #(B,1,H,W)
xyz_map_rs = torch.cat(xyz_map_rs, dim=0).permute(0,3,1,2) #(B,3,H,W)
Ks = torch.as_tensor(K, device='cuda', dtype=torch.float).reshape(1,3,3)
if cfg['use_normal']:
normal_rs = torch.cat(normal_rs, dim=0).permute(0,3,1,2) #(B,3,H,W)
logging.info("render done")
rgbBs = kornia.geometry.transform.warp_perspective(torch.as_tensor(rgb, dtype=torch.float, device='cuda').permute(2,0,1)[None].expand(B,-1,-1,-1), tf_to_crops, dsize=render_size, mode='bilinear', align_corners=False)
if rgb_rs.shape[-2:]!=cfg['input_resize']:
rgbAs = kornia.geometry.transform.warp_perspective(rgb_rs, tf_to_crops, dsize=render_size, mode='bilinear', align_corners=False)
else:
rgbAs = rgb_rs
if xyz_map_rs.shape[-2:]!=cfg['input_resize']:
xyz_mapAs = kornia.geometry.transform.warp_perspective(xyz_map_rs, tf_to_crops, dsize=render_size, mode='nearest', align_corners=False)
else:
xyz_mapAs = xyz_map_rs
xyz_mapBs = kornia.geometry.transform.warp_perspective(torch.as_tensor(xyz_map, device='cuda', dtype=torch.float).permute(2,0,1)[None].expand(B,-1,-1,-1), tf_to_crops, dsize=render_size, mode='nearest', align_corners=False) #(B,3,H,W)
if cfg['use_normal']:
normalAs = kornia.geometry.transform.warp_perspective(normal_rs, tf_to_crops, dsize=render_size, mode='nearest', align_corners=False)
normalBs = kornia.geometry.transform.warp_perspective(torch.as_tensor(normal_map, dtype=torch.float, device='cuda').permute(2,0,1)[None].expand(B,-1,-1,-1), tf_to_crops, dsize=render_size, mode='nearest', align_corners=False)
else:
normalAs = None
normalBs = None
logging.info("warp done")
mesh_diameters = torch.ones((len(rgbAs)), dtype=torch.float, device='cuda')*mesh_diameter
pose_data = BatchPoseData(rgbAs=rgbAs, rgbBs=rgbBs, depthAs=None, depthBs=None, normalAs=normalAs, normalBs=normalBs, poseA=poseA, poseB=None, xyz_mapAs=xyz_mapAs, xyz_mapBs=xyz_mapBs, tf_to_crops=tf_to_crops, Ks=Ks, mesh_diameters=mesh_diameters)
pose_data = dataset.transform_batch(batch=pose_data, H_ori=H, W_ori=W, bound=1)
logging.info("pose batch data done")
return pose_data
class PoseRefinePredictor:
def __init__(self,):
logging.info("welcome")
self.amp = True
self.run_name = "2023-10-28-18-33-37"
model_name = 'model_best.pth'
code_dir = os.path.dirname(os.path.realpath(__file__))
ckpt_dir = f'{code_dir}/../../weights/{self.run_name}/{model_name}'
self.cfg = OmegaConf.load(f'{code_dir}/../../weights/{self.run_name}/config.yml')
self.cfg['ckpt_dir'] = ckpt_dir
self.cfg['enable_amp'] = True
########## Defaults, to be backward compatible
if 'use_normal' not in self.cfg:
self.cfg['use_normal'] = False
if 'use_mask' not in self.cfg:
self.cfg['use_mask'] = False
if 'use_BN' not in self.cfg:
self.cfg['use_BN'] = False
if 'c_in' not in self.cfg:
self.cfg['c_in'] = 4
if 'crop_ratio' not in self.cfg or self.cfg['crop_ratio'] is None:
self.cfg['crop_ratio'] = 1.2
if 'n_view' not in self.cfg:
self.cfg['n_view'] = 1
if 'trans_rep' not in self.cfg:
self.cfg['trans_rep'] = 'tracknet'
if 'rot_rep' not in self.cfg:
self.cfg['rot_rep'] = 'axis_angle'
if 'zfar' not in self.cfg:
self.cfg['zfar'] = 3
if 'normalize_xyz' not in self.cfg:
self.cfg['normalize_xyz'] = False
if isinstance(self.cfg['zfar'], str) and 'inf' in self.cfg['zfar'].lower():
self.cfg['zfar'] = np.inf
if 'normal_uint8' not in self.cfg:
self.cfg['normal_uint8'] = False
logging.info(f"self.cfg: \n {OmegaConf.to_yaml(self.cfg)}")
self.dataset = PoseRefinePairH5Dataset(cfg=self.cfg, h5_file='', mode='test')
self.model = RefineNet(cfg=self.cfg, c_in=self.cfg['c_in']).cuda()
logging.info(f"Using pretrained model from {ckpt_dir}")
ckpt = torch.load(ckpt_dir)
if 'model' in ckpt:
ckpt = ckpt['model']
self.model.load_state_dict(ckpt)
self.model.cuda().eval()
logging.info("init done")
self.last_trans_update = None
self.last_rot_update = None
@torch.inference_mode()
def predict(self, rgb, depth, K, ob_in_cams, xyz_map, normal_map=None, get_vis=False, mesh=None, mesh_tensors=None, glctx=None, mesh_diameter=None, iteration=5):
'''
@rgb: np array (H,W,3)
@ob_in_cams: np array (N,4,4)
'''
torch.set_default_tensor_type('torch.cuda.FloatTensor')
logging.info(f'ob_in_cams:{ob_in_cams.shape}')
tf_to_center = np.eye(4)
ob_centered_in_cams = ob_in_cams
mesh_centered = mesh
logging.info(f'self.cfg.use_normal:{self.cfg.use_normal}')
if not self.cfg.use_normal:
normal_map = None
crop_ratio = self.cfg['crop_ratio']
logging.info(f"trans_normalizer:{self.cfg['trans_normalizer']}, rot_normalizer:{self.cfg['rot_normalizer']}")
bs = 1024
B_in_cams = torch.as_tensor(ob_centered_in_cams, device='cuda', dtype=torch.float)
if mesh_tensors is None:
mesh_tensors = make_mesh_tensors(mesh_centered)
rgb_tensor = torch.as_tensor(rgb, device='cuda', dtype=torch.float)
depth_tensor = torch.as_tensor(depth, device='cuda', dtype=torch.float)
xyz_map_tensor = torch.as_tensor(xyz_map, device='cuda', dtype=torch.float)
trans_normalizer = self.cfg['trans_normalizer']
if not isinstance(trans_normalizer, float):
trans_normalizer = torch.as_tensor(list(trans_normalizer), device='cuda', dtype=torch.float).reshape(1,3)
for _ in range(iteration):
logging.info("making cropped data")
pose_data = make_crop_data_batch(self.cfg.input_resize, B_in_cams, mesh_centered, rgb_tensor, depth_tensor, K, crop_ratio=crop_ratio, normal_map=normal_map, xyz_map=xyz_map_tensor, cfg=self.cfg, glctx=glctx, mesh_tensors=mesh_tensors, dataset=self.dataset, mesh_diameter=mesh_diameter)
B_in_cams = []
for b in range(0, pose_data.rgbAs.shape[0], bs):
A = torch.cat([pose_data.rgbAs[b:b+bs].cuda(), pose_data.xyz_mapAs[b:b+bs].cuda()], dim=1).float()
B = torch.cat([pose_data.rgbBs[b:b+bs].cuda(), pose_data.xyz_mapBs[b:b+bs].cuda()], dim=1).float()
logging.info("forward start")
with torch.cuda.amp.autocast(enabled=self.amp):
output = self.model(A,B)
for k in output:
output[k] = output[k].float()
logging.info("forward done")
if self.cfg['trans_rep']=='tracknet':
if not self.cfg['normalize_xyz']:
trans_delta = torch.tanh(output["trans"])*trans_normalizer
else:
trans_delta = output["trans"]
elif self.cfg['trans_rep']=='deepim':
def project_and_transform_to_crop(centers):
uvs = (pose_data.Ks[b:b+bs]@centers.reshape(-1,3,1)).reshape(-1,3)
uvs = uvs/uvs[:,2:3]
uvs = (pose_data.tf_to_crops[b:b+bs]@uvs.reshape(-1,3,1)).reshape(-1,3)
return uvs[:,:2]
rot_delta = output["rot"]
z_pred = output['trans'][:,2]*pose_data.poseA[b:b+bs][...,2,3]
uvA_crop = project_and_transform_to_crop(pose_data.poseA[b:b+bs][...,:3,3])
uv_pred_crop = uvA_crop + output['trans'][:,:2]*self.cfg['input_resize'][0]
uv_pred = transform_pts(uv_pred_crop, pose_data.tf_to_crops[b:b+bs].inverse().cuda())
center_pred = torch.cat([uv_pred, torch.ones((len(rot_delta),1), dtype=torch.float, device='cuda')], dim=-1)
center_pred = (pose_data.Ks[b:b+bs].inverse().cuda()@center_pred.reshape(len(rot_delta),3,1)).reshape(len(rot_delta),3) * z_pred.reshape(len(rot_delta),1)
trans_delta = center_pred-pose_data.poseA[b:b+bs][...,:3,3]
else:
trans_delta = output["trans"]
if self.cfg['rot_rep']=='axis_angle':
rot_mat_delta = torch.tanh(output["rot"])*self.cfg['rot_normalizer']
rot_mat_delta = so3_exp_map(rot_mat_delta).permute(0,2,1)
elif self.cfg['rot_rep']=='6d':
rot_mat_delta = rotation_6d_to_matrix(output['rot']).permute(0,2,1)
else:
raise RuntimeError
if self.cfg['normalize_xyz']:
trans_delta *= (mesh_diameter/2)
B_in_cam = egocentric_delta_pose_to_pose(pose_data.poseA[b:b+bs], trans_delta=trans_delta, rot_mat_delta=rot_mat_delta)
B_in_cams.append(B_in_cam)
B_in_cams = torch.cat(B_in_cams, dim=0).reshape(len(ob_in_cams),4,4)
B_in_cams_out = B_in_cams@torch.tensor(tf_to_center[None], device='cuda', dtype=torch.float)
torch.cuda.empty_cache()
self.last_trans_update = trans_delta
self.last_rot_update = rot_mat_delta
if get_vis:
logging.info("get_vis...")
canvas = []
padding = 2
pose_data = make_crop_data_batch(self.cfg.input_resize, torch.as_tensor(ob_centered_in_cams), mesh_centered, rgb, depth, K, crop_ratio=crop_ratio, normal_map=normal_map, xyz_map=xyz_map_tensor, cfg=self.cfg, glctx=glctx, mesh_tensors=mesh_tensors, dataset=self.dataset, mesh_diameter=mesh_diameter)
for id in range(0, len(B_in_cams)):
rgbA_vis = (pose_data.rgbAs[id]*255).permute(1,2,0).data.cpu().numpy()
rgbB_vis = (pose_data.rgbBs[id]*255).permute(1,2,0).data.cpu().numpy()
row = [rgbA_vis, rgbB_vis]
H,W = rgbA_vis.shape[:2]
if pose_data.depthAs is not None:
depthA = pose_data.depthAs[id].data.cpu().numpy().reshape(H,W)
depthB = pose_data.depthBs[id].data.cpu().numpy().reshape(H,W)
elif pose_data.xyz_mapAs is not None:
depthA = pose_data.xyz_mapAs[id][2].data.cpu().numpy().reshape(H,W)
depthB = pose_data.xyz_mapBs[id][2].data.cpu().numpy().reshape(H,W)
zmin = min(depthA.min(), depthB.min())
zmax = max(depthA.max(), depthB.max())
depthA_vis = depth_to_vis(depthA, zmin=zmin, zmax=zmax, inverse=False)
depthB_vis = depth_to_vis(depthB, zmin=zmin, zmax=zmax, inverse=False)
row += [depthA_vis, depthB_vis]
if pose_data.normalAs is not None:
pass
row = make_grid_image(row, nrow=len(row), padding=padding, pad_value=255)
row = cv_draw_text(row, text=f'id:{id}', uv_top_left=(10,10), color=(0,255,0), fontScale=0.5)
canvas.append(row)
canvas = make_grid_image(canvas, nrow=1, padding=padding, pad_value=255)
pose_data = make_crop_data_batch(self.cfg.input_resize, B_in_cams, mesh_centered, rgb, depth, K, crop_ratio=crop_ratio, normal_map=normal_map, xyz_map=xyz_map_tensor, cfg=self.cfg, glctx=glctx, mesh_tensors=mesh_tensors, dataset=self.dataset, mesh_diameter=mesh_diameter)
canvas_refined = []
for id in range(0, len(B_in_cams)):
rgbA_vis = (pose_data.rgbAs[id]*255).permute(1,2,0).data.cpu().numpy()
rgbB_vis = (pose_data.rgbBs[id]*255).permute(1,2,0).data.cpu().numpy()
row = [rgbA_vis, rgbB_vis]
H,W = rgbA_vis.shape[:2]
if pose_data.depthAs is not None:
depthA = pose_data.depthAs[id].data.cpu().numpy().reshape(H,W)
depthB = pose_data.depthBs[id].data.cpu().numpy().reshape(H,W)
elif pose_data.xyz_mapAs is not None:
depthA = pose_data.xyz_mapAs[id][2].data.cpu().numpy().reshape(H,W)
depthB = pose_data.xyz_mapBs[id][2].data.cpu().numpy().reshape(H,W)
zmin = min(depthA.min(), depthB.min())
zmax = max(depthA.max(), depthB.max())
depthA_vis = depth_to_vis(depthA, zmin=zmin, zmax=zmax, inverse=False)
depthB_vis = depth_to_vis(depthB, zmin=zmin, zmax=zmax, inverse=False)
row += [depthA_vis, depthB_vis]
row = make_grid_image(row, nrow=len(row), padding=padding, pad_value=255)
canvas_refined.append(row)
canvas_refined = make_grid_image(canvas_refined, nrow=1, padding=padding, pad_value=255)
canvas = make_grid_image([canvas, canvas_refined], nrow=2, padding=padding, pad_value=255)
torch.cuda.empty_cache()
return B_in_cams_out, canvas
return B_in_cams_out, None
@@ -0,0 +1,227 @@
# 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 functools
import os,sys,kornia
import time
import numpy as np
import torch
import torch.distributed as dist
from omegaconf import OmegaConf
from tqdm import tqdm
code_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(f'{code_dir}/../../../')
from learning.datasets.h5_dataset import *
from learning.models.score_network import *
from learning.datasets.pose_dataset import *
from Utils import *
from datareader import *
def vis_batch_data_scores(pose_data, ids, scores, pad_margin=5):
assert len(scores)==len(ids)
canvas = []
for id in ids:
rgbA_vis = (pose_data.rgbAs[id]*255).permute(1,2,0).data.cpu().numpy()
rgbB_vis = (pose_data.rgbBs[id]*255).permute(1,2,0).data.cpu().numpy()
H,W = rgbA_vis.shape[:2]
zmin = pose_data.depthAs[id].data.cpu().numpy().reshape(H,W).min()
zmax = pose_data.depthAs[id].data.cpu().numpy().reshape(H,W).max()
depthA_vis = depth_to_vis(pose_data.depthAs[id].data.cpu().numpy().reshape(H,W), zmin=zmin, zmax=zmax, inverse=False)
depthB_vis = depth_to_vis(pose_data.depthBs[id].data.cpu().numpy().reshape(H,W), zmin=zmin, zmax=zmax, inverse=False)
if pose_data.normalAs is not None:
pass
pad = np.ones((rgbA_vis.shape[0],pad_margin,3))*255
if pose_data.normalAs is not None:
pass
else:
row = np.concatenate([rgbA_vis, pad, depthA_vis, pad, rgbB_vis, pad, depthB_vis], axis=1)
s = 100/row.shape[0]
row = cv2.resize(row, fx=s, fy=s, dsize=None)
row = cv_draw_text(row, text=f'id:{id}, score:{scores[id]:.3f}', uv_top_left=(10,10), color=(0,255,0), fontScale=0.5)
canvas.append(row)
pad = np.ones((pad_margin, row.shape[1], 3))*255
canvas.append(pad)
canvas = np.concatenate(canvas, axis=0).astype(np.uint8)
return canvas
@torch.no_grad()
def make_crop_data_batch(render_size, ob_in_cams, mesh, rgb, depth, K, crop_ratio, normal_map=None, mesh_diameter=None, glctx=None, mesh_tensors=None, dataset:TripletH5Dataset=None, cfg=None):
logging.info("Welcome make_crop_data_batch")
H,W = depth.shape[:2]
args = []
method = 'box_3d'
tf_to_crops = compute_crop_window_tf_batch(pts=mesh.vertices, H=H, W=W, poses=ob_in_cams, K=K, crop_ratio=crop_ratio, out_size=(render_size[1], render_size[0]), method=method, mesh_diameter=mesh_diameter)
logging.info("make tf_to_crops done")
B = len(ob_in_cams)
poseAs = torch.as_tensor(ob_in_cams, dtype=torch.float, device='cuda')
bs = 512
rgb_rs = []
depth_rs = []
xyz_map_rs = []
bbox2d_crop = torch.as_tensor(np.array([0, 0, cfg['input_resize'][0]-1, cfg['input_resize'][1]-1]).reshape(2,2), device='cuda', dtype=torch.float)
bbox2d_ori = transform_pts(bbox2d_crop, tf_to_crops.inverse()[:,None]).reshape(-1,4)
for b in range(0,len(ob_in_cams),bs):
extra = {}
rgb_r, depth_r, normal_r = nvdiffrast_render(K=K, H=H, W=W, ob_in_cams=poseAs[b:b+bs], context='cuda', get_normal=cfg['use_normal'], glctx=glctx, mesh_tensors=mesh_tensors, output_size=cfg['input_resize'], bbox2d=bbox2d_ori[b:b+bs], use_light=True, extra=extra)
rgb_rs.append(rgb_r)
depth_rs.append(depth_r[...,None])
xyz_map_rs.append(extra['xyz_map'])
rgb_rs = torch.cat(rgb_rs, dim=0).permute(0,3,1,2) * 255
depth_rs = torch.cat(depth_rs, dim=0).permute(0,3,1,2)
xyz_map_rs = torch.cat(xyz_map_rs, dim=0).permute(0,3,1,2) #(B,3,H,W)
logging.info("render done")
rgbBs = kornia.geometry.transform.warp_perspective(torch.as_tensor(rgb, dtype=torch.float, device='cuda').permute(2,0,1)[None].expand(B,-1,-1,-1), tf_to_crops, dsize=render_size, mode='bilinear', align_corners=False)
depthBs = kornia.geometry.transform.warp_perspective(torch.as_tensor(depth, dtype=torch.float, device='cuda')[None,None].expand(B,-1,-1,-1), tf_to_crops, dsize=render_size, mode='nearest', align_corners=False)
if rgb_rs.shape[-2:]!=cfg['input_resize']:
rgbAs = kornia.geometry.transform.warp_perspective(rgb_rs, tf_to_crops, dsize=render_size, mode='bilinear', align_corners=False)
depthAs = kornia.geometry.transform.warp_perspective(depth_rs, tf_to_crops, dsize=render_size, mode='nearest', align_corners=False)
else:
rgbAs = rgb_rs
depthAs = depth_rs
if xyz_map_rs.shape[-2:]!=cfg['input_resize']:
xyz_mapAs = kornia.geometry.transform.warp_perspective(xyz_map_rs, tf_to_crops, dsize=render_size, mode='nearest', align_corners=False)
else:
xyz_mapAs = xyz_map_rs
normalAs = None
normalBs = None
Ks = torch.as_tensor(K, dtype=torch.float).reshape(1,3,3).expand(len(rgbAs),3,3)
mesh_diameters = torch.ones((len(rgbAs)), dtype=torch.float, device='cuda')*mesh_diameter
pose_data = BatchPoseData(rgbAs=rgbAs, rgbBs=rgbBs, depthAs=depthAs, depthBs=depthBs, normalAs=normalAs, normalBs=normalBs, poseA=poseAs, xyz_mapAs=xyz_mapAs, tf_to_crops=tf_to_crops, Ks=Ks, mesh_diameters=mesh_diameters)
pose_data = dataset.transform_batch(pose_data, H_ori=H, W_ori=W, bound=1)
logging.info("pose batch data done")
return pose_data
class ScorePredictor:
def __init__(self, amp=True):
self.amp = amp
self.run_name = "2024-01-11-20-02-45"
model_name = 'model_best.pth'
code_dir = os.path.dirname(os.path.realpath(__file__))
ckpt_dir = f'{code_dir}/../../weights/{self.run_name}/{model_name}'
self.cfg = OmegaConf.load(f'{code_dir}/../../weights/{self.run_name}/config.yml')
self.cfg['ckpt_dir'] = ckpt_dir
self.cfg['enable_amp'] = True
########## Defaults, to be backward compatible
if 'use_normal' not in self.cfg:
self.cfg['use_normal'] = False
if 'use_BN' not in self.cfg:
self.cfg['use_BN'] = False
if 'zfar' not in self.cfg:
self.cfg['zfar'] = np.inf
if 'c_in' not in self.cfg:
self.cfg['c_in'] = 4
if 'normalize_xyz' not in self.cfg:
self.cfg['normalize_xyz'] = False
if 'crop_ratio' not in self.cfg or self.cfg['crop_ratio'] is None:
self.cfg['crop_ratio'] = 1.2
logging.info(f"self.cfg: \n {OmegaConf.to_yaml(self.cfg)}")
self.dataset = ScoreMultiPairH5Dataset(cfg=self.cfg, mode='test', h5_file=None, max_num_key=1)
self.model = ScoreNetMultiPair(cfg=self.cfg, c_in=self.cfg['c_in']).cuda()
logging.info(f"Using pretrained model from {ckpt_dir}")
ckpt = torch.load(ckpt_dir)
if 'model' in ckpt:
ckpt = ckpt['model']
self.model.load_state_dict(ckpt)
self.model.cuda().eval()
logging.info("init done")
@torch.inference_mode()
def predict(self, rgb, depth, K, ob_in_cams, normal_map=None, get_vis=False, mesh=None, mesh_tensors=None, glctx=None, mesh_diameter=None):
'''
@rgb: np array (H,W,3)
'''
logging.info(f"ob_in_cams:{ob_in_cams.shape}")
ob_in_cams = torch.as_tensor(ob_in_cams, dtype=torch.float, device='cuda')
logging.info(f'self.cfg.use_normal:{self.cfg.use_normal}')
if not self.cfg.use_normal:
normal_map = None
logging.info("making cropped data")
if mesh_tensors is None:
mesh_tensors = make_mesh_tensors(mesh)
rgb = torch.as_tensor(rgb, device='cuda', dtype=torch.float)
depth = torch.as_tensor(depth, device='cuda', dtype=torch.float)
pose_data = make_crop_data_batch(self.cfg.input_resize, ob_in_cams, mesh, rgb, depth, K, crop_ratio=self.cfg['crop_ratio'], glctx=glctx, mesh_tensors=mesh_tensors, dataset=self.dataset, cfg=self.cfg, mesh_diameter=mesh_diameter)
def find_best_among_pairs(pose_data:BatchPoseData):
logging.info(f'pose_data.rgbAs.shape[0]: {pose_data.rgbAs.shape[0]}')
ids = []
scores = []
bs = pose_data.rgbAs.shape[0]
for b in range(0, pose_data.rgbAs.shape[0], bs):
A = torch.cat([pose_data.rgbAs[b:b+bs].cuda(), pose_data.xyz_mapAs[b:b+bs].cuda()], dim=1).float()
B = torch.cat([pose_data.rgbBs[b:b+bs].cuda(), pose_data.xyz_mapBs[b:b+bs].cuda()], dim=1).float()
if pose_data.normalAs is not None:
A = torch.cat([A, pose_data.normalAs.cuda().float()], dim=1)
B = torch.cat([B, pose_data.normalBs.cuda().float()], dim=1)
with torch.cuda.amp.autocast(enabled=self.amp):
output = self.model(A, B, L=len(A))
scores_cur = output["score_logit"].float().reshape(-1)
ids.append(scores_cur.argmax()+b)
scores.append(scores_cur)
ids = torch.stack(ids, dim=0).reshape(-1)
scores = torch.cat(scores, dim=0).reshape(-1)
return ids, scores
pose_data_iter = pose_data
global_ids = torch.arange(len(ob_in_cams), device='cuda', dtype=torch.long)
scores_global = torch.zeros((len(ob_in_cams)), dtype=torch.float, device='cuda')
while 1:
ids, scores = find_best_among_pairs(pose_data_iter)
if len(ids)==1:
scores_global[global_ids] = scores + 100
break
global_ids = global_ids[ids]
pose_data_iter = pose_data.select_by_indices(global_ids)
scores = scores_global
logging.info(f'forward done')
torch.cuda.empty_cache()
if get_vis:
logging.info("get_vis...")
canvas = []
ids = scores.argsort(descending=True)
canvas = vis_batch_data_scores(pose_data, ids=ids, scores=scores)
return scores, canvas
return scores, None
@@ -0,0 +1,102 @@
# 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 os,sys
from dataclasses import dataclass, field
from typing import List, Optional, Tuple,Union
import numpy as np
import omegaconf
import torch
@dataclass
class TrainingConfig(omegaconf.dictconfig.DictConfig):
input_resize: tuple = (160, 160)
normalize_xyz:Optional[bool] = True
use_mask:Optional[bool] = False
crop_ratio:Optional[float] = None
split_objects_across_gpus: bool = True
max_num_key: Optional[int] = None
use_normal:bool = False
n_view:int = 1
zfar:float = np.inf
c_in:int = 6
train_num_pair:Optional[int] = None
make_pair_online:Optional[bool] = False
render_backend:Optional[str] = 'nvdiffrast'
# Run management
run_id: Optional[str] = None
exp_name:Optional[str] = None
resume_run_id: Optional[str] = None
save_dir: Optional[str] = None
batch_size: int = 64
epoch_size: int = 115200
val_size: int = 1280
n_epochs: int = 25
save_epoch_interval: int = 100
n_dataloader_workers: int = 20
n_rendering_workers: int = 1
gradient_max_norm:float = np.inf
max_step_per_epoch: Optional[int] = 25000
# Network
use_BN:bool = True
loss_type:Optional[str] = 'pairwise_valid'
# Optimizer
optimizer: str = "adam"
weight_decay: float = 0.0
clip_grad_norm: float = np.inf
lr: float = 0.0001
warmup_step: int = -1 # -1 means disable
n_epochs_warmup: int = 1
# Visualization
vis_interval: Optional[int] = 1000
debug: Optional[bool] = None
@dataclass
class TrainRefinerConfig:
# Datasets
input_resize: tuple = (160, 160) #(W,H)
crop_ratio:Optional[float] = None
max_num_key: Optional[int] = None
use_normal:bool = False
use_mask:Optional[bool] = False
normal_uint8:bool = False
normalize_xyz:Optional[bool] = True
trans_normalizer:Optional[list] = None
rot_normalizer:Optional[float] = None
c_in:int = 6
n_view:int = 1
zfar:float = np.inf
trans_rep:str = 'tracknet' # tracknet/deepim
rot_rep:Optional[str] = 'axis_angle' # 6d/axis_angle
save_dir: Optional[str] = None
# Run management
run_id: Optional[str] = None
exp_name:Optional[str] = None
batch_size: int = 64
use_BN:bool = True
optimizer: str = "adam"
weight_decay: float = 0.0
clip_grad_norm: float = np.inf
lr: float = 0.0001
warmup_step: int = -1
loss_type:str = 'l2' # l1/l2/add
vis_interval: Optional[int] = 1000
debug: Optional[bool] = None