dashdoas's picture
Upload 101 files
736dcf7 verified
Raw
History Blame
7.27 kB
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import torch
from torch import nn, einsum
import torch.nn.functional as F
from skinning_models.networks import PreNorm, FeedForward, Attention, PointEmbed
from third_partys.Michelangelo.encode import load_model
from third_partys.PartField.encode import partfield
class SkinningBlock(nn.Module):
def __init__(self, dim, args, heads=8, dim_head=64, ff_mult=4):
super().__init__()
# -- Self-Attn (joint)
self.self_attn_j = PreNorm(dim, Attention(dim, dim, heads=heads, dim_head=dim_head), context_dim = dim)
self.ff_j = PreNorm(dim, FeedForward(dim, mult=ff_mult))
# -- Cross-Attn (point -> shape)
self.cross_attn_ps = PreNorm(dim, Attention(dim, dim, heads=heads, dim_head=dim_head), context_dim = dim)
self.ff_ps = PreNorm(dim, FeedForward(dim, mult=ff_mult))
# -- Cross-Attn (joint -> shape)
self.cross_attn_js = PreNorm(dim, Attention(dim, dim, heads=heads, dim_head=dim_head), context_dim = dim)
self.ff_js = PreNorm(dim, FeedForward(dim, mult=ff_mult))
# -- Cross-Attn (joint -> point)
self.cross_attn_jp = PreNorm(dim, Attention(dim, dim, heads=heads, dim_head=dim_head), context_dim = dim)
self.ff_jp = PreNorm(dim, FeedForward(dim, mult=ff_mult))
# -- Cross-Attn (point -> joint)
self.cross_attn_pj = PreNorm(dim, Attention(dim, dim, heads=heads, dim_head=dim_head), context_dim = dim)
self.ff_pj = PreNorm(dim, FeedForward(dim, mult=ff_mult))
self.use_TAJA = args.use_TAJA
if self.use_TAJA:
self.rel_pos_embedding = nn.Embedding(10, dim // 4)
self.rel_pos_proj = nn.Linear(dim // 4, heads)
self.rel_pos_scale = nn.Parameter(torch.ones(1) * 0.1) # Initial value 0.1
def forward(self, point, joint, shape, valid_mask=None, graph_dist=None):
"""
point: (B, Np, dim)
joint: (B, Nj, dim)
shape: (B, Ns, dim)
valid_mask: (B, Nj) or None
return:
updated_point, updated_joint
"""
# 1) joint self-attention with TAJA
if self.use_TAJA:
batch_size, n_joints = joint.shape[0], joint.shape[1]
dist_mask = valid_mask.unsqueeze(1) & valid_mask.unsqueeze(2)
safe_dist = torch.where(dist_mask, graph_dist, torch.zeros_like(graph_dist))
distances_clamped = torch.clamp(safe_dist, 0, 9).long() # (B, Nj, Nj)
rel_pos_embeddings = self.rel_pos_embedding(distances_clamped) # (B, Nj, Nj, dim//4)
rel_pos_encoding = self.rel_pos_proj(rel_pos_embeddings) * self.rel_pos_scale # (B, Nj, Nj, heads)
rel_pos_encoding = torch.where(
dist_mask.unsqueeze(-1).expand_as(rel_pos_encoding),
rel_pos_encoding,
torch.zeros_like(rel_pos_encoding)
)
else:
rel_pos_encoding = None
joint_enhance = self.self_attn_j(joint, context=joint, context_mask=valid_mask, rel_pos=rel_pos_encoding) + joint
joint_enhance = self.ff_j(joint_enhance) + joint_enhance
# 2) point->shape
point_context = self.cross_attn_ps(point, context=shape) + point
point_context = self.ff_ps(point_context) + point_context
# 2) joint->shape
joint_context = self.cross_attn_js(joint_enhance, context=shape, query_mask=valid_mask) + joint_enhance
joint_context = self.ff_js(joint_context) + joint_context
# 3) joint->point
joint_refine = self.cross_attn_jp(joint_context, context=point_context, query_mask=valid_mask) + joint_context
joint_refine = self.ff_jp(joint_refine) + joint_refine
# 4) point->joint
point_final = self.cross_attn_pj(point_context, context=joint_refine, context_mask=valid_mask) + point_context
point_final = self.ff_pj(point_final) + point_final
return point_final, joint_refine
class SkinningNetStacked(nn.Module):
def __init__(self, args, dim=768, heads=8, dim_head=64, ff_mult=4, scale_init=1.):
super().__init__()
self.args = args
self.max_joints = args.max_joints
self.skeleton_condition = PointEmbed(dim=dim)
self.scale = nn.Parameter(torch.tensor(scale_init), requires_grad=True)
self.point_encoder = load_model()
self.point_encoder.eval()
for param in self.point_encoder.parameters():
param.requires_grad = False
self.point_embed_pe = PointEmbed(dim=dim)
self.point_embed = partfield()
self.proj = nn.Sequential(
nn.Linear(448, dim),
nn.SiLU(),
nn.Linear(dim, dim),
)
# multiple blocks
self.blocks = nn.ModuleList([
SkinningBlock(dim, args, heads=heads, dim_head=dim_head, ff_mult=ff_mult)
for _ in range(args.depth)
])
def process_point_feature(self, point_feature):
shape_latents = self.point_encoder.to_shape_latents(point_feature[:, 1:])
point_feature_first_column = point_feature[:, 0:1]
encode_feature = torch.cat([point_feature_first_column, shape_latents], dim=1)
return encode_feature
def forward(self,
sample_points, skeleton, pc_w_norm, dist_graph,
valid_mask=None, # (B, Nj)
target=None): # (B, Np, Nj)
"""
cosine similarity + softmax + loss
"""
point_out1 = self.point_embed(sample_points)
point_out1 = self.proj(point_out1)
point_out2 = self.point_embed_pe(sample_points)
point_out = point_out1 + point_out2 # (bs, 8192, 768)
joint_out = self.skeleton_condition(skeleton) # (bs, args.max_joints, 768)
point_feature = self.point_encoder.encode_latents(pc_w_norm)
shape_feature = self.process_point_feature(point_feature=point_feature) # (bs, 257, 768)
for block in self.blocks:
point_out, joint_out = block(point_out, joint_out, shape_feature, valid_mask=valid_mask, graph_dist=dist_graph)
point_norm = F.normalize(point_out, p=2, dim=-1) # (B, Np, D)
joint_norm = F.normalize(joint_out, p=2, dim=-1) # (B, Nj, D)
score_cos = einsum('b i d, b j d -> b i j', point_norm, joint_norm)
score = (self.scale.abs()+1e-9) * score_cos
skinning_weight = F.softmax(score, dim=-1)
if target is None:
return skinning_weight