jaxmetaverse's picture
Upload folder using huggingface_hub
82ea528 verified
# Modified from https://github.com/hehao13/CameraCtrl/blob/main/cameractrl/models/pose_adaptor.py
# (whose parts were also taken from https://github.com/TencentARC/T2I-Adapter)
import torch
import torch.nn as nn
import numpy as np
from torch import Tensor
from einops import rearrange
import comfy.ops
from .context import ContextOptions, ContextFuseMethod, ContextSchedules
from .motion_module_ad import TemporalTransformerBlock, get_position_encoding_max_len
from .logger import logger
def conv_nd(dims, *args, **kwargs):
"""
Create a 1D, 2D, or 3D convolution module.
"""
if dims == 1:
return nn.Conv1d(*args, **kwargs)
elif dims == 2:
return nn.Conv2d(*args, **kwargs)
elif dims == 3:
return nn.Conv3d(*args, **kwargs)
raise ValueError(f"unsupported dimensions: {dims}")
def avg_pool_nd(dims, *args, **kwargs):
"""
Create a 1D, 2D, or 3D average pooling module.
"""
if dims == 1:
return nn.AvgPool1d(*args, **kwargs)
elif dims == 2:
return nn.AvgPool2d(*args, **kwargs)
elif dims == 3:
return nn.AvgPool3d(*args, **kwargs)
raise ValueError(f"unsupported dimensions: {dims}")
class CameraEntry:
def __init__(self, entry: list[float]):
self.entry = entry.copy()
self.orig_pose_width = entry[5]
self.orig_pose_height = entry[6]
# focal length/intrinsic camera parameters
fx, fy, cx, cy = entry[1:5]
self.fx = fx
self.fy = fy
self.cx = cx
self.cy = cy
w2c_mat = np.array(entry[7:]).reshape(3, 4)
w2c_mat_4x4 = np.eye(4)
w2c_mat_4x4[:3, :] = w2c_mat
self.w2c_mat = w2c_mat_4x4
self.c2w_mat = np.linalg.inv(w2c_mat_4x4)
def clone(self):
return CameraEntry(entry=self.entry)
def get_parameter_dtype(parameter: torch.nn.Module):
params = tuple(parameter.parameters())
if len(params) > 0:
return params[0].dtype
buffers = tuple(parameter.buffers())
if len(buffers) > 0:
return buffers[0].dtype
def get_parameter_device(parameter: torch.nn.Module):
params = tuple(parameter.parameters())
if len(params) > 0:
return params[0].device
buffers = tuple(parameter.buffers())
if len(buffers) > 0:
return buffers[0].device
def custom_meshgrid(*args):
# ref: https://pytorch.org/docs/stable/generated/torch.meshgrid.html?highlight=meshgrid#torch.meshgrid
return torch.meshgrid(*args, indexing='ij')
def get_relative_pose(cam_params: list[CameraEntry]):
abs_w2cs = [cam_param.w2c_mat for cam_param in cam_params]
abs_c2ws = [cam_param.c2w_mat for cam_param in cam_params]
cam_to_origin = 0
target_cam_c2w = np.array([
[1, 0, 0, 0],
[0, 1, 0, -cam_to_origin],
[0, 0, 1, 0],
[0, 0, 0, 1]
])
abs2rel = target_cam_c2w @ abs_w2cs[0]
ret_poses = [target_cam_c2w, ] + [abs2rel @ abs_c2w for abs_c2w in abs_c2ws[1:]]
ret_poses = np.array(ret_poses, dtype=np.float32)
return ret_poses
def ray_condition(K: Tensor, c2w: Tensor, H, W, device):
# c2w: B, V, 4, 4
# K: B, V, 4
B = K.shape[0]
j, i = custom_meshgrid(
torch.linspace(0, H - 1, H, device=device, dtype=c2w.dtype),
torch.linspace(0, W - 1, W, device=device, dtype=c2w.dtype),
)
i = i.reshape([1, 1, H * W]).expand([B, 1, H * W]) + 0.5 # [B, HxW]
j = j.reshape([1, 1, H * W]).expand([B, 1, H * W]) + 0.5 # [B, HxW]
fx, fy, cx, cy = K.chunk(4, dim=-1) # B,V, 1
zs = torch.ones_like(i) # [B, HxW]
xs = (i - cx) / fx * zs
ys = (j - cy) / fy * zs
zs = zs.expand_as(ys)
directions = torch.stack((xs, ys, zs), dim=-1) # B, V, HW, 3
directions = directions / directions.norm(dim=-1, keepdim=True) # B, V, HW, 3
rays_d = directions @ c2w[..., :3, :3].transpose(-1, -2) # B, V, 3, HW
rays_o = c2w[..., :3, 3] # B, V, 3
rays_o = rays_o[:, :, None].expand_as(rays_d) # B, V, 3, HW
# c2w @ directions
rays_dxo = torch.cross(rays_o, rays_d)
plucker = torch.cat([rays_dxo, rays_d], dim=-1)
plucker = plucker.reshape(B, c2w.shape[1], H, W, 6) # B, V, H, W, 6
# plucker = plucker.permute(0, 1, 4, 2, 3)
return plucker
def prepare_pose_embedding(cam_params: list[CameraEntry], image_width, image_height):
# clone each CameraEntry in list so that CameraEntries don't get spoiled after a single run
cam_params = [entry.clone() for entry in cam_params]
sample_wh_ratio = image_width / image_height
for cam_param in cam_params:
pose_wh_ratio = cam_param.orig_pose_width / cam_param.orig_pose_height
if pose_wh_ratio > sample_wh_ratio:
resized_ori_w = image_height * pose_wh_ratio
cam_param.fx = resized_ori_w * cam_param.fx / image_width
else:
resized_ori_h = image_width / pose_wh_ratio
cam_param.fy = resized_ori_h * cam_param.fy / image_height
intrinsic = np.asarray([[cam_param.fx * image_width,
cam_param.fy * image_height,
cam_param.cx * image_width,
cam_param.cy * image_height]
for cam_param in cam_params], dtype=np.float32)
K = torch.as_tensor(intrinsic)[None] # [1, 1, 4]
c2ws = get_relative_pose(cam_params)
c2ws = torch.as_tensor(c2ws)[None] # [1, n_frame, 4, 4]
plucker_embedding = ray_condition(K, c2ws, image_height, image_width, device='cpu')[0].permute(0, 3, 1, 2).contiguous() # V, 6, H, W
plucker_embedding = rearrange(plucker_embedding, "f c h w -> c f h w")
return plucker_embedding
class CameraPoseEncoder(nn.Module):
def __init__(self,
downscale_factor=8,
channels=[320, 640, 1280, 1280],
nums_rb=2,
cin=384,
ksize=1,
sk=True,
use_conv=False,
compression_factor=1,
temporal_attention_nhead=8,
attention_block_types=("Temporal_Self", ),
temporal_position_encoding=True,
temporal_position_encoding_max_len=16,
rescale_output_factor=1.0,
ops=comfy.ops.disable_weight_init):
super(CameraPoseEncoder, self).__init__()
self.unshuffle = nn.PixelUnshuffle(downscale_factor)
self.channels = channels
self.nums_rb = nums_rb
self.encoder_conv_in = ops.Conv2d(cin, channels[0], 3, 1, 1)
self.encoder_down_conv_blocks = nn.ModuleList()
self.encoder_down_attention_blocks = nn.ModuleList()
for i in range(len(channels)):
conv_layers = nn.ModuleList()
temporal_attention_layers = nn.ModuleList()
for j in range(nums_rb):
if j == 0 and i != 0:
in_dim = channels[i - 1]
out_dim = int(channels[i] / compression_factor)
conv_layer = ResnetBlockCameraCtrl(in_dim, out_dim, down=True, ksize=ksize, sk=sk, use_conv=use_conv, ops=ops)
elif j == 0:
in_dim = channels[0]
out_dim = int(channels[i] / compression_factor)
conv_layer = ResnetBlockCameraCtrl(in_dim, out_dim, down=False, ksize=ksize, sk=sk, use_conv=use_conv, ops=ops)
elif j == nums_rb - 1:
in_dim = channels[i] / compression_factor
out_dim = channels[i]
conv_layer = ResnetBlockCameraCtrl(in_dim, out_dim, down=False, ksize=ksize, sk=sk, use_conv=use_conv, ops=ops)
else:
in_dim = int(channels[i] / compression_factor)
out_dim = int(channels[i] / compression_factor)
conv_layer = ResnetBlockCameraCtrl(in_dim, out_dim, down=False, ksize=ksize, sk=sk, use_conv=use_conv, ops=ops)
temporal_attention_layer = TemporalTransformerBlock(dim=out_dim,
num_attention_heads=temporal_attention_nhead,
attention_head_dim=int(out_dim / temporal_attention_nhead),
attention_block_types=attention_block_types,
dropout=0.0,
cross_attention_dim=None,
temporal_pe=temporal_position_encoding,
temporal_pe_max_len=temporal_position_encoding_max_len,
ops=ops)
conv_layers.append(conv_layer)
temporal_attention_layers.append(temporal_attention_layer)
self.encoder_down_conv_blocks.append(conv_layers)
self.encoder_down_attention_blocks.append(temporal_attention_layers)
self.temporal_pe_max_len = 16
def forward(self, x: Tensor, video_length: int, batched_number: int=1):
# rearrange to match expected format
x = rearrange(x, "c f h w -> f c h w")
# logger.info(f"x: {x.shape}, {float(x[0][0][0][-1])}")
# unshuffle
x = self.unshuffle(x)
# extract features
features = []
# prepare view_options, if needed
view_options = ContextOptions(
context_length=self.temporal_pe_max_len,
context_overlap=self.temporal_pe_max_len//2, # at 16 max_len, context_overlap will be 8
context_schedule=ContextSchedules.STATIC_STANDARD,
fuse_method=ContextFuseMethod.PYRAMID,
)
# logger.warn(f"x dtype: {x.dtype}, device: {x.device}")
# logger.warn(f"dtype: {get_parameter_dtype(self)}, device: {get_parameter_device(self)}")
x = self.encoder_conv_in(x.to(dtype=get_parameter_dtype(self), device=get_parameter_device(self)))
for res_block, attention_block in zip(self.encoder_down_conv_blocks, self.encoder_down_attention_blocks):
for res_layer, attention_layer in zip(res_block, attention_block):
x = res_layer(x)
h, w = x.shape[-2:]
x = rearrange(x, 'b c h w -> b (h w) c') # h w are in middle instead of beginning like in diffusers
x = attention_layer(x, video_length=video_length, view_options=view_options)
x = rearrange(x, 'b (h w) c -> b c h w', h=h, w=w) # h w are in middle instead of beginning like in diffusers
features.append(x)
# for idx, feature in enumerate(features):
# logger.info(f"{idx}: {feature.shape}, {float(feature[0][0][0][0])}")
for idx, x1 in enumerate(features):
x1 = x1.to(x.dtype).to(x.device)
x1 = rearrange(x1, 'b c h w -> (h w) b c')
x1 = torch.cat([x1] * batched_number, dim=0)
features[idx] = x1
return features
class ResnetBlockCameraCtrl(nn.Module):
def __init__(self, in_c, out_c, down: bool, ksize=3, sk=False, use_conv=True,
ops=comfy.ops.disable_weight_init):
super().__init__()
ps = ksize // 2 # padding size
if in_c != out_c or sk == False:
self.in_conv = ops.Conv2d(in_c, out_c, ksize, 1, ps)
else:
self.in_conv = None
self.block1 = ops.Conv2d(out_c, out_c, 3, 1, 1)
self.act = nn.ReLU()
self.block2 = ops.Conv2d(out_c, out_c, ksize, 1, ps)
if sk == False:
self.skep = ops.Conv2d(in_c, out_c, ksize, 1, ps)
else:
self.skep = None
self.down = down
if self.down == True:
self.down_opt = DownsampleCameraCtrl(in_c, use_conv=use_conv)
def forward(self, x: Tensor):
if self.down == True:
x = self.down_opt(x)
if self.in_conv is not None: # edit
x = self.in_conv(x)
h = self.block1(x)
h = self.act(h)
h = self.block2(h)
if self.skep is not None:
return h + self.skep(x)
else:
return h + x
class DownsampleCameraCtrl(nn.Module):
"""
A downsampling layer with an optional convolution.
:param channels: channels in the inputs and outputs.
:param use_conv: a bool determining if a convolution is applied.
:param dims: determines if the signal is 1D, 2D, or 3D. If 3D, then
downsampling occurs in the inner-two dimensions.
"""
def __init__(self, channels, use_conv: bool, dims=2, out_channels=None, padding=1,
ops=comfy.ops.disable_weight_init):
super().__init__()
self.channels = channels
self.out_channels = out_channels or channels
self.use_conv = use_conv
self.dims = dims
stride = 2 if dims != 3 else (1, 2, 2)
if use_conv:
self.operation = ops.conv_nd(dims, in_channels=self.channels, out_channels=self.out_channels,
kernel_size=3, stride=stride, padding=padding)
else:
assert self.channels == self.out_channels
self.operation = avg_pool_nd(dims, kernel_size=stride, stride=stride) # both are stride value on purpose
def forward(self, x: Tensor):
assert x.shape[1] == self.channels
return self.operation(x)