@@ -32,4 +32,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
||||
*.xz filter=lfs diff=lfs merge=lfs -text
|
||||
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||
*.zst filter=lfs diff=lfs merge=lfs -text
|
||||
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
||||
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"_class_name": "T2IAdapterV2",
|
||||
"_diffusers_version": "0.30.0",
|
||||
"_name_or_path": "C:\\Users\\reall\\Softwares\\Blender 3.6\\_Projects\\train_output_save_v2\\checkpoint-210000\\t2iadapter",
|
||||
"adapter_type": "full_adapter",
|
||||
"channels": [
|
||||
320,
|
||||
640,
|
||||
1280,
|
||||
1280
|
||||
],
|
||||
"conditioning_embedding_out_channels": [
|
||||
16,
|
||||
32,
|
||||
64,
|
||||
128,
|
||||
256
|
||||
],
|
||||
"in_channels": 3,
|
||||
"num_res_blocks": 2
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
model:
|
||||
target: CRM_T2I_V2.imagedream.ldm.interface.LatentDiffusionInterface
|
||||
params:
|
||||
linear_start: 0.00085
|
||||
linear_end: 0.0120
|
||||
timesteps: 1000
|
||||
scale_factor: 0.18215
|
||||
parameterization: "eps"
|
||||
zero_snr: true
|
||||
|
||||
unet_config:
|
||||
target: CRM_T2I_V2.imagedream.ldm.modules.diffusionmodules.openaimodel.MultiViewUNetModelStage2
|
||||
params:
|
||||
image_size: 32 # unused
|
||||
in_channels: 8
|
||||
out_channels: 4
|
||||
model_channels: 320
|
||||
attention_resolutions: [ 4, 2, 1 ]
|
||||
num_res_blocks: 2
|
||||
channel_mult: [ 1, 2, 4, 4 ]
|
||||
num_head_channels: 64 # need to fix for flash-attn
|
||||
use_spatial_transformer: True
|
||||
use_linear_in_transformer: True
|
||||
transformer_depth: 1
|
||||
context_dim: 1024
|
||||
use_checkpoint: False
|
||||
legacy: False
|
||||
camera_dim: 16
|
||||
with_ip: True
|
||||
ip_dim: 16 # ip token length
|
||||
ip_mode: "local_resample"
|
||||
|
||||
vae_config:
|
||||
target: CRM_T2I_V2.imagedream.ldm.models.autoencoder.AutoencoderKL
|
||||
params:
|
||||
embed_dim: 4
|
||||
monitor: val/rec_loss
|
||||
ddconfig:
|
||||
#attn_type: "vanilla-xformers"
|
||||
double_z: true
|
||||
z_channels: 4
|
||||
resolution: 256
|
||||
in_channels: 3
|
||||
out_ch: 3
|
||||
ch: 128
|
||||
ch_mult:
|
||||
- 1
|
||||
- 2
|
||||
- 4
|
||||
- 4
|
||||
num_res_blocks: 2
|
||||
attn_resolutions: []
|
||||
dropout: 0.0
|
||||
lossconfig:
|
||||
target: torch.nn.Identity
|
||||
|
||||
clip_config:
|
||||
target: CRM_T2I_V2.imagedream.ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder
|
||||
params:
|
||||
freeze: True
|
||||
layer: "penultimate"
|
||||
ip_mode: "local_resample"
|
||||
@@ -0,0 +1,73 @@
|
||||
model:
|
||||
target: CRM_T2I_V2.imagedream.ldm.interface.LatentDiffusionInterface
|
||||
params:
|
||||
linear_start: 0.00085
|
||||
linear_end: 0.0120
|
||||
timesteps: 1000
|
||||
scale_factor: 0.18215
|
||||
parameterization: "eps"
|
||||
zero_snr: true
|
||||
|
||||
unet_config:
|
||||
target: CRM_T2I_V2.imagedream.ldm.modules.diffusionmodules.openaimodel.MultiViewUNetModel
|
||||
params:
|
||||
image_size: 32 # unused
|
||||
in_channels: 4
|
||||
out_channels: 4
|
||||
model_channels: 320
|
||||
attention_resolutions: [ 4, 2, 1 ]
|
||||
num_res_blocks: 2
|
||||
channel_mult: [ 1, 2, 4, 4 ]
|
||||
num_head_channels: 64 # need to fix for flash-attn
|
||||
use_spatial_transformer: True
|
||||
use_linear_in_transformer: True
|
||||
transformer_depth: 1
|
||||
context_dim: 1024
|
||||
use_checkpoint: False
|
||||
legacy: False
|
||||
camera_dim: 16
|
||||
with_ip: True
|
||||
ip_dim: 16 # ip token length
|
||||
ip_mode: "local_resample"
|
||||
|
||||
vae_config:
|
||||
target: CRM_T2I_V2.imagedream.ldm.models.autoencoder.AutoencoderKL
|
||||
params:
|
||||
embed_dim: 4
|
||||
monitor: val/rec_loss
|
||||
ddconfig:
|
||||
#attn_type: "vanilla-xformers"
|
||||
double_z: true
|
||||
z_channels: 4
|
||||
resolution: 256
|
||||
in_channels: 3
|
||||
out_ch: 3
|
||||
ch: 128
|
||||
ch_mult:
|
||||
- 1
|
||||
- 2
|
||||
- 4
|
||||
- 4
|
||||
num_res_blocks: 2
|
||||
attn_resolutions: []
|
||||
dropout: 0.0
|
||||
lossconfig:
|
||||
target: torch.nn.Identity
|
||||
|
||||
clip_config:
|
||||
target: CRM_T2I_V2.imagedream.ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder
|
||||
params:
|
||||
freeze: True
|
||||
layer: "penultimate"
|
||||
ip_mode: "local_resample"
|
||||
|
||||
sampler:
|
||||
target: CRM_T2I_V2.libs.sample.ImageDreamDiffusion
|
||||
params:
|
||||
mode: pixel
|
||||
num_frames: 7
|
||||
camera_views: [1, 2, 3, 4, 5, 0, 0]
|
||||
ref_position: 6
|
||||
random_background: false
|
||||
offset_noise: true
|
||||
resize_rate: 1.0
|
||||
@@ -0,0 +1 @@
|
||||
from .model_zoo import build_model
|
||||
@@ -0,0 +1,99 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
def create_camera_to_world_matrix(elevation, azimuth):
|
||||
elevation = np.radians(elevation)
|
||||
azimuth = np.radians(azimuth)
|
||||
# Convert elevation and azimuth angles to Cartesian coordinates on a unit sphere
|
||||
x = np.cos(elevation) * np.sin(azimuth)
|
||||
y = np.sin(elevation)
|
||||
z = np.cos(elevation) * np.cos(azimuth)
|
||||
|
||||
# Calculate camera position, target, and up vectors
|
||||
camera_pos = np.array([x, y, z])
|
||||
target = np.array([0, 0, 0])
|
||||
up = np.array([0, 1, 0])
|
||||
|
||||
# Construct view matrix
|
||||
forward = target - camera_pos
|
||||
forward /= np.linalg.norm(forward)
|
||||
right = np.cross(forward, up)
|
||||
right /= np.linalg.norm(right)
|
||||
new_up = np.cross(right, forward)
|
||||
new_up /= np.linalg.norm(new_up)
|
||||
cam2world = np.eye(4)
|
||||
cam2world[:3, :3] = np.array([right, new_up, -forward]).T
|
||||
cam2world[:3, 3] = camera_pos
|
||||
return cam2world
|
||||
|
||||
|
||||
def convert_opengl_to_blender(camera_matrix):
|
||||
if isinstance(camera_matrix, np.ndarray):
|
||||
# Construct transformation matrix to convert from OpenGL space to Blender space
|
||||
flip_yz = np.array([[1, 0, 0, 0], [0, 0, -1, 0], [0, 1, 0, 0], [0, 0, 0, 1]])
|
||||
camera_matrix_blender = np.dot(flip_yz, camera_matrix)
|
||||
else:
|
||||
# Construct transformation matrix to convert from OpenGL space to Blender space
|
||||
flip_yz = torch.tensor(
|
||||
[[1, 0, 0, 0], [0, 0, -1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]
|
||||
)
|
||||
if camera_matrix.ndim == 3:
|
||||
flip_yz = flip_yz.unsqueeze(0)
|
||||
camera_matrix_blender = torch.matmul(flip_yz.to(camera_matrix), camera_matrix)
|
||||
return camera_matrix_blender
|
||||
|
||||
|
||||
def normalize_camera(camera_matrix):
|
||||
"""normalize the camera location onto a unit-sphere"""
|
||||
if isinstance(camera_matrix, np.ndarray):
|
||||
camera_matrix = camera_matrix.reshape(-1, 4, 4)
|
||||
translation = camera_matrix[:, :3, 3]
|
||||
translation = translation / (
|
||||
np.linalg.norm(translation, axis=1, keepdims=True) + 1e-8
|
||||
)
|
||||
camera_matrix[:, :3, 3] = translation
|
||||
else:
|
||||
camera_matrix = camera_matrix.reshape(-1, 4, 4)
|
||||
translation = camera_matrix[:, :3, 3]
|
||||
translation = translation / (
|
||||
torch.norm(translation, dim=1, keepdim=True) + 1e-8
|
||||
)
|
||||
camera_matrix[:, :3, 3] = translation
|
||||
return camera_matrix.reshape(-1, 16)
|
||||
|
||||
|
||||
def get_camera(
|
||||
num_frames,
|
||||
elevation=15,
|
||||
azimuth_start=0,
|
||||
azimuth_span=360,
|
||||
blender_coord=True,
|
||||
extra_view=False,
|
||||
):
|
||||
angle_gap = azimuth_span / num_frames
|
||||
cameras = []
|
||||
for azimuth in np.arange(azimuth_start, azimuth_span + azimuth_start, angle_gap):
|
||||
camera_matrix = create_camera_to_world_matrix(elevation, azimuth)
|
||||
if blender_coord:
|
||||
camera_matrix = convert_opengl_to_blender(camera_matrix)
|
||||
cameras.append(camera_matrix.flatten())
|
||||
|
||||
if extra_view:
|
||||
dim = len(cameras[0])
|
||||
cameras.append(np.zeros(dim))
|
||||
return torch.tensor(np.stack(cameras, 0)).float()
|
||||
|
||||
|
||||
def get_camera_for_index(data_index):
|
||||
"""
|
||||
按照当前我们的数据格式, 以000为正对我们的情况:
|
||||
000是正面, ev: 0, azimuth: 0
|
||||
001是左边, ev: 0, azimuth: -90
|
||||
002是下面, ev: -90, azimuth: 0
|
||||
003是背面, ev: 0, azimuth: 180
|
||||
004是右边, ev: 0, azimuth: 90
|
||||
005是上面, ev: 90, azimuth: 0
|
||||
"""
|
||||
params = [(0, 0), (0, -90), (-90, 0), (0, 180), (0, 90), (90, 0)]
|
||||
return get_camera(1, *params[data_index])
|
||||
@@ -0,0 +1,61 @@
|
||||
model:
|
||||
target: imagedream.ldm.interface.LatentDiffusionInterface
|
||||
params:
|
||||
linear_start: 0.00085
|
||||
linear_end: 0.0120
|
||||
timesteps: 1000
|
||||
scale_factor: 0.18215
|
||||
parameterization: "eps"
|
||||
|
||||
unet_config:
|
||||
target: imagedream.ldm.modules.diffusionmodules.openaimodel.MultiViewUNetModel
|
||||
params:
|
||||
image_size: 32 # unused
|
||||
in_channels: 4
|
||||
out_channels: 4
|
||||
model_channels: 320
|
||||
attention_resolutions: [ 4, 2, 1 ]
|
||||
num_res_blocks: 2
|
||||
channel_mult: [ 1, 2, 4, 4 ]
|
||||
num_head_channels: 64 # need to fix for flash-attn
|
||||
use_spatial_transformer: True
|
||||
use_linear_in_transformer: True
|
||||
transformer_depth: 1
|
||||
context_dim: 1024
|
||||
use_checkpoint: False
|
||||
legacy: False
|
||||
camera_dim: 16
|
||||
with_ip: True
|
||||
ip_dim: 16 # ip token length
|
||||
ip_mode: "local_resample"
|
||||
|
||||
vae_config:
|
||||
target: imagedream.ldm.models.autoencoder.AutoencoderKL
|
||||
params:
|
||||
embed_dim: 4
|
||||
monitor: val/rec_loss
|
||||
ddconfig:
|
||||
#attn_type: "vanilla-xformers"
|
||||
double_z: true
|
||||
z_channels: 4
|
||||
resolution: 256
|
||||
in_channels: 3
|
||||
out_ch: 3
|
||||
ch: 128
|
||||
ch_mult:
|
||||
- 1
|
||||
- 2
|
||||
- 4
|
||||
- 4
|
||||
num_res_blocks: 2
|
||||
attn_resolutions: []
|
||||
dropout: 0.0
|
||||
lossconfig:
|
||||
target: torch.nn.Identity
|
||||
|
||||
clip_config:
|
||||
target: imagedream.ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder
|
||||
params:
|
||||
freeze: True
|
||||
layer: "penultimate"
|
||||
ip_mode: "local_resample"
|
||||
@@ -0,0 +1,61 @@
|
||||
model:
|
||||
target: imagedream.ldm.interface.LatentDiffusionInterface
|
||||
params:
|
||||
linear_start: 0.00085
|
||||
linear_end: 0.0120
|
||||
timesteps: 1000
|
||||
scale_factor: 0.18215
|
||||
parameterization: "eps"
|
||||
|
||||
unet_config:
|
||||
target: imagedream.ldm.modules.diffusionmodules.openaimodel.MultiViewUNetModel
|
||||
params:
|
||||
image_size: 32 # unused
|
||||
in_channels: 8
|
||||
out_channels: 8
|
||||
model_channels: 320
|
||||
attention_resolutions: [ 4, 2, 1 ]
|
||||
num_res_blocks: 2
|
||||
channel_mult: [ 1, 2, 4, 4 ]
|
||||
num_head_channels: 64 # need to fix for flash-attn
|
||||
use_spatial_transformer: True
|
||||
use_linear_in_transformer: True
|
||||
transformer_depth: 1
|
||||
context_dim: 1024
|
||||
use_checkpoint: False
|
||||
legacy: False
|
||||
camera_dim: 16
|
||||
with_ip: True
|
||||
ip_dim: 16 # ip token length
|
||||
ip_mode: "local_resample"
|
||||
|
||||
vae_config:
|
||||
target: imagedream.ldm.models.autoencoder.AutoencoderKL
|
||||
params:
|
||||
embed_dim: 4
|
||||
monitor: val/rec_loss
|
||||
ddconfig:
|
||||
#attn_type: "vanilla-xformers"
|
||||
double_z: true
|
||||
z_channels: 4
|
||||
resolution: 256
|
||||
in_channels: 3
|
||||
out_ch: 3
|
||||
ch: 128
|
||||
ch_mult:
|
||||
- 1
|
||||
- 2
|
||||
- 4
|
||||
- 4
|
||||
num_res_blocks: 2
|
||||
attn_resolutions: []
|
||||
dropout: 0.0
|
||||
lossconfig:
|
||||
target: torch.nn.Identity
|
||||
|
||||
clip_config:
|
||||
target: imagedream.ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder
|
||||
params:
|
||||
freeze: True
|
||||
layer: "penultimate"
|
||||
ip_mode: "local_resample"
|
||||
@@ -0,0 +1,61 @@
|
||||
model:
|
||||
target: imagedream.ldm.interface.LatentDiffusionInterface
|
||||
params:
|
||||
linear_start: 0.00085
|
||||
linear_end: 0.0120
|
||||
timesteps: 1000
|
||||
scale_factor: 0.18215
|
||||
parameterization: "eps"
|
||||
|
||||
unet_config:
|
||||
target: imagedream.ldm.modules.diffusionmodules.openaimodel.MultiViewUNetModelStage2
|
||||
params:
|
||||
image_size: 32 # unused
|
||||
in_channels: 8
|
||||
out_channels: 4
|
||||
model_channels: 320
|
||||
attention_resolutions: [ 4, 2, 1 ]
|
||||
num_res_blocks: 2
|
||||
channel_mult: [ 1, 2, 4, 4 ]
|
||||
num_head_channels: 64 # need to fix for flash-attn
|
||||
use_spatial_transformer: True
|
||||
use_linear_in_transformer: True
|
||||
transformer_depth: 1
|
||||
context_dim: 1024
|
||||
use_checkpoint: False
|
||||
legacy: False
|
||||
camera_dim: 16
|
||||
with_ip: True
|
||||
ip_dim: 16 # ip token length
|
||||
ip_mode: "local_resample"
|
||||
|
||||
vae_config:
|
||||
target: imagedream.ldm.models.autoencoder.AutoencoderKL
|
||||
params:
|
||||
embed_dim: 4
|
||||
monitor: val/rec_loss
|
||||
ddconfig:
|
||||
#attn_type: "vanilla-xformers"
|
||||
double_z: true
|
||||
z_channels: 4
|
||||
resolution: 256
|
||||
in_channels: 3
|
||||
out_ch: 3
|
||||
ch: 128
|
||||
ch_mult:
|
||||
- 1
|
||||
- 2
|
||||
- 4
|
||||
- 4
|
||||
num_res_blocks: 2
|
||||
attn_resolutions: []
|
||||
dropout: 0.0
|
||||
lossconfig:
|
||||
target: torch.nn.Identity
|
||||
|
||||
clip_config:
|
||||
target: imagedream.ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder
|
||||
params:
|
||||
freeze: True
|
||||
layer: "penultimate"
|
||||
ip_mode: "local_resample"
|
||||
@@ -0,0 +1,62 @@
|
||||
model:
|
||||
target: imagedream.ldm.interface.LatentDiffusionInterface
|
||||
params:
|
||||
linear_start: 0.00085
|
||||
linear_end: 0.0120
|
||||
timesteps: 1000
|
||||
scale_factor: 0.18215
|
||||
parameterization: "eps"
|
||||
|
||||
unet_config:
|
||||
target: imagedream.ldm.modules.diffusionmodules.openaimodel.MultiViewUNetModel
|
||||
params:
|
||||
image_size: 32 # unused
|
||||
in_channels: 4
|
||||
out_channels: 4
|
||||
model_channels: 320
|
||||
attention_resolutions: [ 4, 2, 1 ]
|
||||
num_res_blocks: 2
|
||||
channel_mult: [ 1, 2, 4, 4 ]
|
||||
num_head_channels: 64 # need to fix for flash-attn
|
||||
use_spatial_transformer: True
|
||||
use_linear_in_transformer: True
|
||||
transformer_depth: 1
|
||||
context_dim: 1024
|
||||
use_checkpoint: False
|
||||
legacy: False
|
||||
camera_dim: 16
|
||||
with_ip: True
|
||||
ip_dim: 16 # ip token length
|
||||
ip_mode: "local_resample"
|
||||
ip_weight: 1.0 # adjust for similarity to image
|
||||
|
||||
vae_config:
|
||||
target: imagedream.ldm.models.autoencoder.AutoencoderKL
|
||||
params:
|
||||
embed_dim: 4
|
||||
monitor: val/rec_loss
|
||||
ddconfig:
|
||||
#attn_type: "vanilla-xformers"
|
||||
double_z: true
|
||||
z_channels: 4
|
||||
resolution: 256
|
||||
in_channels: 3
|
||||
out_ch: 3
|
||||
ch: 128
|
||||
ch_mult:
|
||||
- 1
|
||||
- 2
|
||||
- 4
|
||||
- 4
|
||||
num_res_blocks: 2
|
||||
attn_resolutions: []
|
||||
dropout: 0.0
|
||||
lossconfig:
|
||||
target: torch.nn.Identity
|
||||
|
||||
clip_config:
|
||||
target: imagedream.ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder
|
||||
params:
|
||||
freeze: True
|
||||
layer: "penultimate"
|
||||
ip_mode: "local_resample"
|
||||
@@ -0,0 +1,205 @@
|
||||
from typing import List
|
||||
from functools import partial
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from .modules.diffusionmodules.util import (
|
||||
make_beta_schedule,
|
||||
extract_into_tensor,
|
||||
enforce_zero_terminal_snr,
|
||||
noise_like,
|
||||
)
|
||||
from .util import exists, default, instantiate_from_config
|
||||
from .modules.distributions.distributions import DiagonalGaussianDistribution
|
||||
|
||||
|
||||
class DiffusionWrapper(nn.Module):
|
||||
def __init__(self, diffusion_model):
|
||||
super().__init__()
|
||||
self.diffusion_model = diffusion_model
|
||||
|
||||
def forward(self, *args, **kwargs):
|
||||
return self.diffusion_model(*args, **kwargs)
|
||||
|
||||
|
||||
class LatentDiffusionInterface(nn.Module):
|
||||
"""a simple interface class for LDM inference"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
unet_config,
|
||||
clip_config,
|
||||
vae_config,
|
||||
parameterization="eps",
|
||||
scale_factor=0.18215,
|
||||
beta_schedule="linear",
|
||||
timesteps=1000,
|
||||
linear_start=0.00085,
|
||||
linear_end=0.0120,
|
||||
cosine_s=8e-3,
|
||||
given_betas=None,
|
||||
zero_snr=False,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
unet = instantiate_from_config(unet_config)
|
||||
self.model = DiffusionWrapper(unet)
|
||||
self.clip_model = instantiate_from_config(clip_config)
|
||||
self.vae_model = instantiate_from_config(vae_config)
|
||||
|
||||
self.parameterization = parameterization
|
||||
self.scale_factor = scale_factor
|
||||
self.register_schedule(
|
||||
given_betas=given_betas,
|
||||
beta_schedule=beta_schedule,
|
||||
timesteps=timesteps,
|
||||
linear_start=linear_start,
|
||||
linear_end=linear_end,
|
||||
cosine_s=cosine_s,
|
||||
zero_snr=zero_snr
|
||||
)
|
||||
|
||||
def register_schedule(
|
||||
self,
|
||||
given_betas=None,
|
||||
beta_schedule="linear",
|
||||
timesteps=1000,
|
||||
linear_start=1e-4,
|
||||
linear_end=2e-2,
|
||||
cosine_s=8e-3,
|
||||
zero_snr=False
|
||||
):
|
||||
if exists(given_betas):
|
||||
betas = given_betas
|
||||
else:
|
||||
betas = make_beta_schedule(
|
||||
beta_schedule,
|
||||
timesteps,
|
||||
linear_start=linear_start,
|
||||
linear_end=linear_end,
|
||||
cosine_s=cosine_s,
|
||||
)
|
||||
if zero_snr:
|
||||
print("--- using zero snr---")
|
||||
betas = enforce_zero_terminal_snr(betas).numpy()
|
||||
alphas = 1.0 - betas
|
||||
alphas_cumprod = np.cumprod(alphas, axis=0)
|
||||
alphas_cumprod_prev = np.append(1.0, alphas_cumprod[:-1])
|
||||
|
||||
(timesteps,) = betas.shape
|
||||
self.num_timesteps = int(timesteps)
|
||||
self.linear_start = linear_start
|
||||
self.linear_end = linear_end
|
||||
assert (
|
||||
alphas_cumprod.shape[0] == self.num_timesteps
|
||||
), "alphas have to be defined for each timestep"
|
||||
|
||||
to_torch = partial(torch.tensor, dtype=torch.float32)
|
||||
|
||||
self.register_buffer("betas", to_torch(betas))
|
||||
self.register_buffer("alphas_cumprod", to_torch(alphas_cumprod))
|
||||
self.register_buffer("alphas_cumprod_prev", to_torch(alphas_cumprod_prev))
|
||||
|
||||
# calculations for diffusion q(x_t | x_{t-1}) and others
|
||||
self.register_buffer("sqrt_alphas_cumprod", to_torch(np.sqrt(alphas_cumprod)))
|
||||
self.register_buffer(
|
||||
"sqrt_one_minus_alphas_cumprod", to_torch(np.sqrt(1.0 - alphas_cumprod))
|
||||
)
|
||||
self.register_buffer(
|
||||
"log_one_minus_alphas_cumprod", to_torch(np.log(1.0 - alphas_cumprod))
|
||||
)
|
||||
self.register_buffer(
|
||||
"sqrt_recip_alphas_cumprod", to_torch(np.sqrt(1.0 / alphas_cumprod))
|
||||
)
|
||||
self.register_buffer(
|
||||
"sqrt_recipm1_alphas_cumprod", to_torch(np.sqrt(1.0 / alphas_cumprod - 1))
|
||||
)
|
||||
|
||||
# calculations for posterior q(x_{t-1} | x_t, x_0)
|
||||
self.v_posterior = 0
|
||||
posterior_variance = (1 - self.v_posterior) * betas * (
|
||||
1.0 - alphas_cumprod_prev
|
||||
) / (1.0 - alphas_cumprod) + self.v_posterior * betas
|
||||
# above: equal to 1. / (1. / (1. - alpha_cumprod_tm1) + alpha_t / beta_t)
|
||||
self.register_buffer("posterior_variance", to_torch(posterior_variance))
|
||||
# below: log calculation clipped because the posterior variance is 0 at the beginning of the diffusion chain
|
||||
self.register_buffer(
|
||||
"posterior_log_variance_clipped",
|
||||
to_torch(np.log(np.maximum(posterior_variance, 1e-20))),
|
||||
)
|
||||
self.register_buffer(
|
||||
"posterior_mean_coef1",
|
||||
to_torch(betas * np.sqrt(alphas_cumprod_prev) / (1.0 - alphas_cumprod)),
|
||||
)
|
||||
self.register_buffer(
|
||||
"posterior_mean_coef2",
|
||||
to_torch(
|
||||
(1.0 - alphas_cumprod_prev) * np.sqrt(alphas) / (1.0 - alphas_cumprod)
|
||||
),
|
||||
)
|
||||
|
||||
def q_sample(self, x_start, t, noise=None):
|
||||
noise = default(noise, lambda: torch.randn_like(x_start))
|
||||
return (
|
||||
extract_into_tensor(self.sqrt_alphas_cumprod, t, x_start.shape) * x_start
|
||||
+ extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x_start.shape)
|
||||
* noise
|
||||
)
|
||||
|
||||
def get_v(self, x, noise, t):
|
||||
return (
|
||||
extract_into_tensor(self.sqrt_alphas_cumprod, t, x.shape) * noise
|
||||
- extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x.shape) * x
|
||||
)
|
||||
|
||||
def predict_start_from_noise(self, x_t, t, noise):
|
||||
return (
|
||||
extract_into_tensor(self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t
|
||||
- extract_into_tensor(self.sqrt_recipm1_alphas_cumprod, t, x_t.shape)
|
||||
* noise
|
||||
)
|
||||
|
||||
def predict_start_from_z_and_v(self, x_t, t, v):
|
||||
return (
|
||||
extract_into_tensor(self.sqrt_alphas_cumprod, t, x_t.shape) * x_t
|
||||
- extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x_t.shape) * v
|
||||
)
|
||||
|
||||
def predict_eps_from_z_and_v(self, x_t, t, v):
|
||||
return (
|
||||
extract_into_tensor(self.sqrt_alphas_cumprod, t, x_t.shape) * v
|
||||
+ extract_into_tensor(self.sqrt_one_minus_alphas_cumprod, t, x_t.shape)
|
||||
* x_t
|
||||
)
|
||||
|
||||
def apply_model(self, x_noisy, t, cond, **kwargs):
|
||||
assert isinstance(cond, dict), "cond has to be a dictionary"
|
||||
return self.model(x_noisy, t, **cond, **kwargs)
|
||||
|
||||
def get_learned_conditioning(self, prompts: List[str]):
|
||||
return self.clip_model(prompts)
|
||||
|
||||
def get_learned_image_conditioning(self, images):
|
||||
return self.clip_model.forward_image(images)
|
||||
|
||||
def get_first_stage_encoding(self, encoder_posterior):
|
||||
if isinstance(encoder_posterior, DiagonalGaussianDistribution):
|
||||
z = encoder_posterior.sample()
|
||||
elif isinstance(encoder_posterior, torch.Tensor):
|
||||
z = encoder_posterior
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"encoder_posterior of type '{type(encoder_posterior)}' not yet implemented"
|
||||
)
|
||||
return self.scale_factor * z
|
||||
|
||||
def encode_first_stage(self, x):
|
||||
return self.vae_model.encode(x)
|
||||
|
||||
def decode_first_stage(self, z):
|
||||
z = 1.0 / self.scale_factor * z
|
||||
return self.vae_model.decode(z)
|
||||
@@ -0,0 +1,270 @@
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from contextlib import contextmanager
|
||||
|
||||
from ..modules.diffusionmodules.model import Encoder, Decoder
|
||||
from ..modules.distributions.distributions import DiagonalGaussianDistribution
|
||||
|
||||
from ..util import instantiate_from_config
|
||||
from ..modules.ema import LitEma
|
||||
|
||||
|
||||
class AutoencoderKL(torch.nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
ddconfig,
|
||||
lossconfig,
|
||||
embed_dim,
|
||||
ckpt_path=None,
|
||||
ignore_keys=[],
|
||||
image_key="image",
|
||||
colorize_nlabels=None,
|
||||
monitor=None,
|
||||
ema_decay=None,
|
||||
learn_logvar=False,
|
||||
):
|
||||
super().__init__()
|
||||
self.learn_logvar = learn_logvar
|
||||
self.image_key = image_key
|
||||
self.encoder = Encoder(**ddconfig)
|
||||
self.decoder = Decoder(**ddconfig)
|
||||
self.loss = instantiate_from_config(lossconfig)
|
||||
assert ddconfig["double_z"]
|
||||
self.quant_conv = torch.nn.Conv2d(2 * ddconfig["z_channels"], 2 * embed_dim, 1)
|
||||
self.post_quant_conv = torch.nn.Conv2d(embed_dim, ddconfig["z_channels"], 1)
|
||||
self.embed_dim = embed_dim
|
||||
if colorize_nlabels is not None:
|
||||
assert type(colorize_nlabels) == int
|
||||
self.register_buffer("colorize", torch.randn(3, colorize_nlabels, 1, 1))
|
||||
if monitor is not None:
|
||||
self.monitor = monitor
|
||||
|
||||
self.use_ema = ema_decay is not None
|
||||
if self.use_ema:
|
||||
self.ema_decay = ema_decay
|
||||
assert 0.0 < ema_decay < 1.0
|
||||
self.model_ema = LitEma(self, decay=ema_decay)
|
||||
print(f"Keeping EMAs of {len(list(self.model_ema.buffers()))}.")
|
||||
|
||||
if ckpt_path is not None:
|
||||
self.init_from_ckpt(ckpt_path, ignore_keys=ignore_keys)
|
||||
|
||||
def init_from_ckpt(self, path, ignore_keys=list()):
|
||||
sd = torch.load(path, map_location="cpu")["state_dict"]
|
||||
keys = list(sd.keys())
|
||||
for k in keys:
|
||||
for ik in ignore_keys:
|
||||
if k.startswith(ik):
|
||||
print("Deleting key {} from state_dict.".format(k))
|
||||
del sd[k]
|
||||
self.load_state_dict(sd, strict=False)
|
||||
print(f"Restored from {path}")
|
||||
|
||||
@contextmanager
|
||||
def ema_scope(self, context=None):
|
||||
if self.use_ema:
|
||||
self.model_ema.store(self.parameters())
|
||||
self.model_ema.copy_to(self)
|
||||
if context is not None:
|
||||
print(f"{context}: Switched to EMA weights")
|
||||
try:
|
||||
yield None
|
||||
finally:
|
||||
if self.use_ema:
|
||||
self.model_ema.restore(self.parameters())
|
||||
if context is not None:
|
||||
print(f"{context}: Restored training weights")
|
||||
|
||||
def on_train_batch_end(self, *args, **kwargs):
|
||||
if self.use_ema:
|
||||
self.model_ema(self)
|
||||
|
||||
def encode(self, x):
|
||||
h = self.encoder(x)
|
||||
moments = self.quant_conv(h)
|
||||
posterior = DiagonalGaussianDistribution(moments)
|
||||
return posterior
|
||||
|
||||
def decode(self, z):
|
||||
z = self.post_quant_conv(z)
|
||||
dec = self.decoder(z)
|
||||
return dec
|
||||
|
||||
def forward(self, input, sample_posterior=True):
|
||||
posterior = self.encode(input)
|
||||
if sample_posterior:
|
||||
z = posterior.sample()
|
||||
else:
|
||||
z = posterior.mode()
|
||||
dec = self.decode(z)
|
||||
return dec, posterior
|
||||
|
||||
def get_input(self, batch, k):
|
||||
x = batch[k]
|
||||
if len(x.shape) == 3:
|
||||
x = x[..., None]
|
||||
x = x.permute(0, 3, 1, 2).to(memory_format=torch.contiguous_format).float()
|
||||
return x
|
||||
|
||||
def training_step(self, batch, batch_idx, optimizer_idx):
|
||||
inputs = self.get_input(batch, self.image_key)
|
||||
reconstructions, posterior = self(inputs)
|
||||
|
||||
if optimizer_idx == 0:
|
||||
# train encoder+decoder+logvar
|
||||
aeloss, log_dict_ae = self.loss(
|
||||
inputs,
|
||||
reconstructions,
|
||||
posterior,
|
||||
optimizer_idx,
|
||||
self.global_step,
|
||||
last_layer=self.get_last_layer(),
|
||||
split="train",
|
||||
)
|
||||
self.log(
|
||||
"aeloss",
|
||||
aeloss,
|
||||
prog_bar=True,
|
||||
logger=True,
|
||||
on_step=True,
|
||||
on_epoch=True,
|
||||
)
|
||||
self.log_dict(
|
||||
log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=False
|
||||
)
|
||||
return aeloss
|
||||
|
||||
if optimizer_idx == 1:
|
||||
# train the discriminator
|
||||
discloss, log_dict_disc = self.loss(
|
||||
inputs,
|
||||
reconstructions,
|
||||
posterior,
|
||||
optimizer_idx,
|
||||
self.global_step,
|
||||
last_layer=self.get_last_layer(),
|
||||
split="train",
|
||||
)
|
||||
|
||||
self.log(
|
||||
"discloss",
|
||||
discloss,
|
||||
prog_bar=True,
|
||||
logger=True,
|
||||
on_step=True,
|
||||
on_epoch=True,
|
||||
)
|
||||
self.log_dict(
|
||||
log_dict_disc, prog_bar=False, logger=True, on_step=True, on_epoch=False
|
||||
)
|
||||
return discloss
|
||||
|
||||
def validation_step(self, batch, batch_idx):
|
||||
log_dict = self._validation_step(batch, batch_idx)
|
||||
with self.ema_scope():
|
||||
log_dict_ema = self._validation_step(batch, batch_idx, postfix="_ema")
|
||||
return log_dict
|
||||
|
||||
def _validation_step(self, batch, batch_idx, postfix=""):
|
||||
inputs = self.get_input(batch, self.image_key)
|
||||
reconstructions, posterior = self(inputs)
|
||||
aeloss, log_dict_ae = self.loss(
|
||||
inputs,
|
||||
reconstructions,
|
||||
posterior,
|
||||
0,
|
||||
self.global_step,
|
||||
last_layer=self.get_last_layer(),
|
||||
split="val" + postfix,
|
||||
)
|
||||
|
||||
discloss, log_dict_disc = self.loss(
|
||||
inputs,
|
||||
reconstructions,
|
||||
posterior,
|
||||
1,
|
||||
self.global_step,
|
||||
last_layer=self.get_last_layer(),
|
||||
split="val" + postfix,
|
||||
)
|
||||
|
||||
self.log(f"val{postfix}/rec_loss", log_dict_ae[f"val{postfix}/rec_loss"])
|
||||
self.log_dict(log_dict_ae)
|
||||
self.log_dict(log_dict_disc)
|
||||
return self.log_dict
|
||||
|
||||
def configure_optimizers(self):
|
||||
lr = self.learning_rate
|
||||
ae_params_list = (
|
||||
list(self.encoder.parameters())
|
||||
+ list(self.decoder.parameters())
|
||||
+ list(self.quant_conv.parameters())
|
||||
+ list(self.post_quant_conv.parameters())
|
||||
)
|
||||
if self.learn_logvar:
|
||||
print(f"{self.__class__.__name__}: Learning logvar")
|
||||
ae_params_list.append(self.loss.logvar)
|
||||
opt_ae = torch.optim.Adam(ae_params_list, lr=lr, betas=(0.5, 0.9))
|
||||
opt_disc = torch.optim.Adam(
|
||||
self.loss.discriminator.parameters(), lr=lr, betas=(0.5, 0.9)
|
||||
)
|
||||
return [opt_ae, opt_disc], []
|
||||
|
||||
def get_last_layer(self):
|
||||
return self.decoder.conv_out.weight
|
||||
|
||||
@torch.no_grad()
|
||||
def log_images(self, batch, only_inputs=False, log_ema=False, **kwargs):
|
||||
log = dict()
|
||||
x = self.get_input(batch, self.image_key)
|
||||
x = x.to(self.device)
|
||||
if not only_inputs:
|
||||
xrec, posterior = self(x)
|
||||
if x.shape[1] > 3:
|
||||
# colorize with random projection
|
||||
assert xrec.shape[1] > 3
|
||||
x = self.to_rgb(x)
|
||||
xrec = self.to_rgb(xrec)
|
||||
log["samples"] = self.decode(torch.randn_like(posterior.sample()))
|
||||
log["reconstructions"] = xrec
|
||||
if log_ema or self.use_ema:
|
||||
with self.ema_scope():
|
||||
xrec_ema, posterior_ema = self(x)
|
||||
if x.shape[1] > 3:
|
||||
# colorize with random projection
|
||||
assert xrec_ema.shape[1] > 3
|
||||
xrec_ema = self.to_rgb(xrec_ema)
|
||||
log["samples_ema"] = self.decode(
|
||||
torch.randn_like(posterior_ema.sample())
|
||||
)
|
||||
log["reconstructions_ema"] = xrec_ema
|
||||
log["inputs"] = x
|
||||
return log
|
||||
|
||||
def to_rgb(self, x):
|
||||
assert self.image_key == "segmentation"
|
||||
if not hasattr(self, "colorize"):
|
||||
self.register_buffer("colorize", torch.randn(3, x.shape[1], 1, 1).to(x))
|
||||
x = F.conv2d(x, weight=self.colorize)
|
||||
x = 2.0 * (x - x.min()) / (x.max() - x.min()) - 1.0
|
||||
return x
|
||||
|
||||
|
||||
class IdentityFirstStage(torch.nn.Module):
|
||||
def __init__(self, *args, vq_interface=False, **kwargs):
|
||||
self.vq_interface = vq_interface
|
||||
super().__init__()
|
||||
|
||||
def encode(self, x, *args, **kwargs):
|
||||
return x
|
||||
|
||||
def decode(self, x, *args, **kwargs):
|
||||
return x
|
||||
|
||||
def quantize(self, x, *args, **kwargs):
|
||||
if self.vq_interface:
|
||||
return x, None, [None, None, None]
|
||||
return x
|
||||
|
||||
def forward(self, x, *args, **kwargs):
|
||||
return x
|
||||
@@ -0,0 +1,435 @@
|
||||
"""SAMPLING ONLY."""
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
from functools import partial
|
||||
|
||||
import comfy.utils
|
||||
|
||||
from ...modules.diffusionmodules.util import (
|
||||
make_ddim_sampling_parameters,
|
||||
make_ddim_timesteps,
|
||||
noise_like,
|
||||
extract_into_tensor,
|
||||
)
|
||||
|
||||
|
||||
class DDIMSampler(object):
|
||||
def __init__(self, model, schedule="linear", device="cuda", **kwargs):
|
||||
super().__init__()
|
||||
self.model = model
|
||||
self.ddpm_num_timesteps = model.num_timesteps #1000
|
||||
self.schedule = schedule
|
||||
self.device = torch.device(device) if isinstance(device, str) else device
|
||||
|
||||
def register_buffer(self, name, attr):
|
||||
if type(attr) == torch.Tensor:
|
||||
if attr.device != self.device:
|
||||
attr = attr.to(self.device)
|
||||
setattr(self, name, attr)
|
||||
|
||||
def make_schedule(
|
||||
self, ddim_num_steps, ddim_discretize="uniform", ddim_eta=0.0, verbose=True
|
||||
):
|
||||
self.ddim_timesteps = make_ddim_timesteps(
|
||||
ddim_discr_method=ddim_discretize,
|
||||
num_ddim_timesteps=ddim_num_steps,
|
||||
num_ddpm_timesteps=self.ddpm_num_timesteps,
|
||||
verbose=verbose,
|
||||
)
|
||||
alphas_cumprod = self.model.alphas_cumprod
|
||||
assert (
|
||||
alphas_cumprod.shape[0] == self.ddpm_num_timesteps
|
||||
), "alphas have to be defined for each timestep"
|
||||
to_torch = lambda x: x.clone().detach().to(torch.float32).to(self.model.device)
|
||||
|
||||
self.register_buffer("betas", to_torch(self.model.betas))
|
||||
self.register_buffer("alphas_cumprod", to_torch(alphas_cumprod))
|
||||
self.register_buffer(
|
||||
"alphas_cumprod_prev", to_torch(self.model.alphas_cumprod_prev)
|
||||
)
|
||||
|
||||
# calculations for diffusion q(x_t | x_{t-1}) and others
|
||||
self.register_buffer(
|
||||
"sqrt_alphas_cumprod", to_torch(np.sqrt(alphas_cumprod.cpu()))
|
||||
)
|
||||
self.register_buffer(
|
||||
"sqrt_one_minus_alphas_cumprod",
|
||||
to_torch(np.sqrt(1.0 - alphas_cumprod.cpu())),
|
||||
)
|
||||
self.register_buffer(
|
||||
"log_one_minus_alphas_cumprod", to_torch(np.log(1.0 - alphas_cumprod.cpu()))
|
||||
)
|
||||
self.register_buffer(
|
||||
"sqrt_recip_alphas_cumprod", to_torch(np.sqrt(1.0 / alphas_cumprod.cpu()))
|
||||
)
|
||||
self.register_buffer(
|
||||
"sqrt_recipm1_alphas_cumprod",
|
||||
to_torch(np.sqrt(1.0 / alphas_cumprod.cpu() - 1)),
|
||||
)
|
||||
|
||||
# ddim sampling parameters
|
||||
ddim_sigmas, ddim_alphas, ddim_alphas_prev = make_ddim_sampling_parameters(
|
||||
alphacums=alphas_cumprod.cpu(),
|
||||
ddim_timesteps=self.ddim_timesteps,
|
||||
eta=ddim_eta,
|
||||
verbose=verbose,
|
||||
)
|
||||
self.register_buffer("ddim_sigmas", ddim_sigmas)
|
||||
self.register_buffer("ddim_alphas", ddim_alphas)
|
||||
self.register_buffer("ddim_alphas_prev", ddim_alphas_prev)
|
||||
self.register_buffer("ddim_sqrt_one_minus_alphas", np.sqrt(1.0 - ddim_alphas))
|
||||
sigmas_for_original_sampling_steps = ddim_eta * torch.sqrt(
|
||||
(1 - self.alphas_cumprod_prev)
|
||||
/ (1 - self.alphas_cumprod)
|
||||
* (1 - self.alphas_cumprod / self.alphas_cumprod_prev)
|
||||
)
|
||||
self.register_buffer(
|
||||
"ddim_sigmas_for_original_num_steps", sigmas_for_original_sampling_steps
|
||||
)
|
||||
|
||||
def sample(
|
||||
self,
|
||||
S,
|
||||
batch_size,
|
||||
shape,
|
||||
conditioning=None,
|
||||
callback=None,
|
||||
normals_sequence=None,
|
||||
img_callback=None,
|
||||
quantize_x0=False,
|
||||
eta=0.0,
|
||||
mask=None,
|
||||
x0=None,
|
||||
temperature=1.0,
|
||||
noise_dropout=0.0,
|
||||
score_corrector=None,
|
||||
corrector_kwargs=None,
|
||||
verbose=True,
|
||||
x_T=None,
|
||||
log_every_t=100,
|
||||
unconditional_guidance_scale=1.0,
|
||||
unconditional_conditioning=None,
|
||||
# this has to come in the same format as the conditioning, # e.g. as encoded tokens, ...
|
||||
**kwargs,
|
||||
):
|
||||
if conditioning is not None:
|
||||
if isinstance(conditioning, dict):
|
||||
cbs = conditioning[list(conditioning.keys())[0]].shape[0]
|
||||
if cbs != batch_size:
|
||||
print(
|
||||
f"Warning: Got {cbs} conditionings but batch-size is {batch_size}"
|
||||
)
|
||||
else:
|
||||
if conditioning.shape[0] != batch_size:
|
||||
print(
|
||||
f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}"
|
||||
)
|
||||
|
||||
self.make_schedule(ddim_num_steps=S, ddim_eta=eta, verbose=verbose)
|
||||
# sampling
|
||||
C, H, W = shape
|
||||
size = (batch_size, C, H, W)
|
||||
|
||||
samples, intermediates = self.ddim_sampling(
|
||||
conditioning,
|
||||
size,
|
||||
callback=callback,
|
||||
img_callback=img_callback,
|
||||
quantize_denoised=quantize_x0,
|
||||
mask=mask,
|
||||
x0=x0,
|
||||
ddim_use_original_steps=False,
|
||||
noise_dropout=noise_dropout,
|
||||
temperature=temperature,
|
||||
score_corrector=score_corrector,
|
||||
corrector_kwargs=corrector_kwargs,
|
||||
x_T=x_T,
|
||||
log_every_t=log_every_t,
|
||||
unconditional_guidance_scale=unconditional_guidance_scale,
|
||||
unconditional_conditioning=unconditional_conditioning,
|
||||
**kwargs,
|
||||
)
|
||||
return samples, intermediates
|
||||
|
||||
def ddim_sampling(
|
||||
self,
|
||||
cond,
|
||||
shape,
|
||||
x_T=None,
|
||||
ddim_use_original_steps=False,
|
||||
callback=None,
|
||||
timesteps=None,
|
||||
quantize_denoised=False,
|
||||
mask=None,
|
||||
x0=None,
|
||||
img_callback=None,
|
||||
log_every_t=100,
|
||||
temperature=1.0,
|
||||
noise_dropout=0.0,
|
||||
score_corrector=None,
|
||||
corrector_kwargs=None,
|
||||
unconditional_guidance_scale=1.0,
|
||||
unconditional_conditioning=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
when inference time: all values of parameter
|
||||
cond.keys(): dict_keys(['context', 'camera', 'num_frames', 'ip', 'ip_img'])
|
||||
shape: (5, 4, 32, 32)
|
||||
x_T: None
|
||||
ddim_use_original_steps: False
|
||||
timesteps: None
|
||||
callback: None
|
||||
quantize_denoised: False
|
||||
mask: None
|
||||
image_callback: None
|
||||
log_every_t: 100
|
||||
temperature: 1.0
|
||||
noise_dropout: 0.0
|
||||
score_corrector: None
|
||||
corrector_kwargs: None
|
||||
unconditional_guidance_scale: 5
|
||||
unconditional_conditioning.keys(): dict_keys(['context', 'camera', 'num_frames', 'ip', 'ip_img'])
|
||||
kwargs: {}
|
||||
"""
|
||||
device = self.model.betas.device
|
||||
b = shape[0]
|
||||
if x_T is None:
|
||||
img = torch.randn(shape, device=device) # shape: torch.Size([5, 4, 32, 32]) mean: -0.00, std: 1.00, min: -3.64, max: 3.94
|
||||
else:
|
||||
img = x_T
|
||||
|
||||
if timesteps is None: # equal with set time step in hf
|
||||
timesteps = (
|
||||
self.ddpm_num_timesteps
|
||||
if ddim_use_original_steps
|
||||
else self.ddim_timesteps
|
||||
)
|
||||
elif timesteps is not None and not ddim_use_original_steps:
|
||||
subset_end = (
|
||||
int(
|
||||
min(timesteps / self.ddim_timesteps.shape[0], 1)
|
||||
* self.ddim_timesteps.shape[0]
|
||||
)
|
||||
- 1
|
||||
)
|
||||
timesteps = self.ddim_timesteps[:subset_end]
|
||||
|
||||
intermediates = {"x_inter": [img], "pred_x0": [img], "e_t": [img]}
|
||||
time_range = ( # reversed timesteps
|
||||
reversed(range(0, timesteps))
|
||||
if ddim_use_original_steps
|
||||
else np.flip(timesteps)
|
||||
)
|
||||
total_steps = timesteps if ddim_use_original_steps else timesteps.shape[0]
|
||||
iterator = tqdm(time_range, desc="DDIM Sampler", total=total_steps)
|
||||
comfy_pbar = comfy.utils.ProgressBar(total_steps)
|
||||
|
||||
for i, step in enumerate(iterator):
|
||||
index = total_steps - i - 1
|
||||
ts = torch.full((b,), step, device=device, dtype=torch.long)
|
||||
|
||||
if mask is not None:
|
||||
assert x0 is not None
|
||||
img_orig = self.model.q_sample(
|
||||
x0, ts
|
||||
) # TODO: deterministic forward pass?
|
||||
img = img_orig * mask + (1.0 - mask) * img
|
||||
|
||||
outs = self.p_sample_ddim(
|
||||
img,
|
||||
cond,
|
||||
ts,
|
||||
index=index,
|
||||
use_original_steps=ddim_use_original_steps,
|
||||
quantize_denoised=quantize_denoised,
|
||||
temperature=temperature,
|
||||
noise_dropout=noise_dropout,
|
||||
score_corrector=score_corrector,
|
||||
corrector_kwargs=corrector_kwargs,
|
||||
unconditional_guidance_scale=unconditional_guidance_scale,
|
||||
unconditional_conditioning=unconditional_conditioning,
|
||||
**kwargs,
|
||||
)
|
||||
img, pred_x0, e_t = outs
|
||||
if callback:
|
||||
callback(i)
|
||||
if img_callback:
|
||||
img_callback(pred_x0, i)
|
||||
|
||||
if index % log_every_t == 0 or index == total_steps - 1:
|
||||
intermediates["x_inter"].append(img)
|
||||
intermediates["pred_x0"].append(pred_x0)
|
||||
intermediates["e_t"].append(e_t)
|
||||
|
||||
comfy_pbar.update_absolute(i + 1)
|
||||
|
||||
return img, intermediates
|
||||
|
||||
def p_sample_ddim(
|
||||
self,
|
||||
x,
|
||||
c,
|
||||
t,
|
||||
index,
|
||||
repeat_noise=False,
|
||||
use_original_steps=False,
|
||||
quantize_denoised=False,
|
||||
temperature=1.0,
|
||||
noise_dropout=0.0,
|
||||
score_corrector=None,
|
||||
corrector_kwargs=None,
|
||||
unconditional_guidance_scale=1.0,
|
||||
unconditional_conditioning=None,
|
||||
dynamic_threshold=None,
|
||||
**kwargs,
|
||||
):
|
||||
b, *_, device = *x.shape, x.device
|
||||
|
||||
if unconditional_conditioning is None or unconditional_guidance_scale == 1.0:
|
||||
model_output = self.model.apply_model(x, t, c, **kwargs)
|
||||
else:
|
||||
x_in = torch.cat([x] * 2)
|
||||
t_in = torch.cat([t] * 2)
|
||||
if isinstance(c, dict):
|
||||
assert isinstance(unconditional_conditioning, dict)
|
||||
c_in = dict()
|
||||
for k in c:
|
||||
if isinstance(c[k], list):
|
||||
c_in[k] = [
|
||||
torch.cat([unconditional_conditioning[k][i], c[k][i]])
|
||||
for i in range(len(c[k]))
|
||||
]
|
||||
elif isinstance(c[k], torch.Tensor):
|
||||
c_in[k] = torch.cat([unconditional_conditioning[k], c[k]])
|
||||
else:
|
||||
assert c[k] == unconditional_conditioning[k]
|
||||
c_in[k] = c[k]
|
||||
elif isinstance(c, list):
|
||||
c_in = list()
|
||||
assert isinstance(unconditional_conditioning, list)
|
||||
for i in range(len(c)):
|
||||
c_in.append(torch.cat([unconditional_conditioning[i], c[i]]))
|
||||
else:
|
||||
c_in = torch.cat([unconditional_conditioning, c])
|
||||
model_uncond, model_t = self.model.apply_model(x_in, t_in, c_in, **kwargs).chunk(2)
|
||||
model_output = model_uncond + unconditional_guidance_scale * (
|
||||
model_t - model_uncond
|
||||
)
|
||||
|
||||
|
||||
if self.model.parameterization == "v":
|
||||
print("using v!")
|
||||
e_t = self.model.predict_eps_from_z_and_v(x, t, model_output)
|
||||
else:
|
||||
e_t = model_output
|
||||
|
||||
if score_corrector is not None:
|
||||
assert self.model.parameterization == "eps", "not implemented"
|
||||
e_t = score_corrector.modify_score(
|
||||
self.model, e_t, x, t, c, **corrector_kwargs
|
||||
)
|
||||
|
||||
alphas = self.model.alphas_cumprod if use_original_steps else self.ddim_alphas
|
||||
alphas_prev = (
|
||||
self.model.alphas_cumprod_prev
|
||||
if use_original_steps
|
||||
else self.ddim_alphas_prev
|
||||
)
|
||||
sqrt_one_minus_alphas = (
|
||||
self.model.sqrt_one_minus_alphas_cumprod
|
||||
if use_original_steps
|
||||
else self.ddim_sqrt_one_minus_alphas
|
||||
)
|
||||
sigmas = (
|
||||
self.model.ddim_sigmas_for_original_num_steps
|
||||
if use_original_steps
|
||||
else self.ddim_sigmas
|
||||
)
|
||||
# select parameters corresponding to the currently considered timestep
|
||||
a_t = torch.full((b, 1, 1, 1), alphas[index], device=device)
|
||||
a_prev = torch.full((b, 1, 1, 1), alphas_prev[index], device=device)
|
||||
sigma_t = torch.full((b, 1, 1, 1), sigmas[index], device=device)
|
||||
sqrt_one_minus_at = torch.full(
|
||||
(b, 1, 1, 1), sqrt_one_minus_alphas[index], device=device
|
||||
)
|
||||
|
||||
# current prediction for x_0
|
||||
if self.model.parameterization != "v":
|
||||
pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt()
|
||||
else:
|
||||
pred_x0 = self.model.predict_start_from_z_and_v(x, t, model_output)
|
||||
|
||||
if quantize_denoised:
|
||||
pred_x0, _, *_ = self.model.first_stage_model.quantize(pred_x0)
|
||||
|
||||
if dynamic_threshold is not None:
|
||||
raise NotImplementedError()
|
||||
|
||||
# direction pointing to x_t
|
||||
dir_xt = (1.0 - a_prev - sigma_t**2).sqrt() * e_t
|
||||
noise = sigma_t * noise_like(x.shape, device, repeat_noise) * temperature
|
||||
if noise_dropout > 0.0:
|
||||
noise = torch.nn.functional.dropout(noise, p=noise_dropout)
|
||||
x_prev = a_prev.sqrt() * pred_x0 + dir_xt + noise
|
||||
return x_prev, pred_x0, e_t
|
||||
|
||||
def add_noise(self, x0, t, noise=None, use_original_steps=False):
|
||||
# fast, but does not allow for exact reconstruction
|
||||
# t serves as an index to gather the correct alphas
|
||||
if use_original_steps:
|
||||
sqrt_alphas_cumprod = self.sqrt_alphas_cumprod
|
||||
sqrt_one_minus_alphas_cumprod = self.sqrt_one_minus_alphas_cumprod
|
||||
else:
|
||||
sqrt_alphas_cumprod = torch.sqrt(self.ddim_alphas)
|
||||
sqrt_one_minus_alphas_cumprod = self.ddim_sqrt_one_minus_alphas
|
||||
|
||||
if noise is None:
|
||||
noise = torch.randn_like(x0)
|
||||
|
||||
# sqrt_alphas_cumprod: torch.Size([50]); t torch.Size([1]); x0: torch.Size([6, 4, 32, 32])
|
||||
return (
|
||||
extract_into_tensor(sqrt_alphas_cumprod, t, x0.shape) * x0
|
||||
+ extract_into_tensor(sqrt_one_minus_alphas_cumprod, t, x0.shape) * noise
|
||||
)
|
||||
|
||||
def decode(
|
||||
self,
|
||||
x_latent,
|
||||
cond,
|
||||
t_start,
|
||||
unconditional_guidance_scale=1.0,
|
||||
unconditional_conditioning=None,
|
||||
use_original_steps=False,
|
||||
**kwargs,
|
||||
):
|
||||
timesteps = (
|
||||
np.arange(self.ddpm_num_timesteps)
|
||||
if use_original_steps
|
||||
else self.ddim_timesteps
|
||||
)
|
||||
timesteps = timesteps[:t_start]
|
||||
|
||||
time_range = np.flip(timesteps)
|
||||
total_steps = timesteps.shape[0]
|
||||
|
||||
iterator = tqdm(time_range, desc="Decoding image", total=total_steps)
|
||||
x_dec = x_latent
|
||||
for i, step in enumerate(iterator):
|
||||
index = total_steps - i - 1
|
||||
ts = torch.full(
|
||||
(x_latent.shape[0],), step, device=x_latent.device, dtype=torch.long
|
||||
)
|
||||
x_dec, _, _ = self.p_sample_ddim(
|
||||
x_dec,
|
||||
cond,
|
||||
ts,
|
||||
index=index,
|
||||
use_original_steps=use_original_steps,
|
||||
unconditional_guidance_scale=unconditional_guidance_scale,
|
||||
unconditional_conditioning=unconditional_conditioning,
|
||||
**kwargs,
|
||||
)
|
||||
return x_dec
|
||||
@@ -0,0 +1,456 @@
|
||||
from inspect import isfunction
|
||||
import math
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn, einsum
|
||||
from einops import rearrange, repeat
|
||||
from typing import Optional, Any
|
||||
|
||||
from .diffusionmodules.util import checkpoint
|
||||
|
||||
|
||||
try:
|
||||
import xformers
|
||||
import xformers.ops
|
||||
|
||||
XFORMERS_IS_AVAILBLE = True
|
||||
except:
|
||||
XFORMERS_IS_AVAILBLE = False
|
||||
|
||||
# CrossAttn precision handling
|
||||
import os
|
||||
|
||||
_ATTN_PRECISION = os.environ.get("ATTN_PRECISION", "fp32")
|
||||
|
||||
|
||||
def exists(val):
|
||||
return val is not None
|
||||
|
||||
|
||||
def uniq(arr):
|
||||
return {el: True for el in arr}.keys()
|
||||
|
||||
|
||||
def default(val, d):
|
||||
if exists(val):
|
||||
return val
|
||||
return d() if isfunction(d) else d
|
||||
|
||||
|
||||
def max_neg_value(t):
|
||||
return -torch.finfo(t.dtype).max
|
||||
|
||||
|
||||
def init_(tensor):
|
||||
dim = tensor.shape[-1]
|
||||
std = 1 / math.sqrt(dim)
|
||||
tensor.uniform_(-std, std)
|
||||
return tensor
|
||||
|
||||
|
||||
# feedforward
|
||||
class GEGLU(nn.Module):
|
||||
def __init__(self, dim_in, dim_out):
|
||||
super().__init__()
|
||||
self.proj = nn.Linear(dim_in, dim_out * 2)
|
||||
|
||||
def forward(self, x):
|
||||
x, gate = self.proj(x).chunk(2, dim=-1)
|
||||
return x * F.gelu(gate)
|
||||
|
||||
|
||||
class FeedForward(nn.Module):
|
||||
def __init__(self, dim, dim_out=None, mult=4, glu=False, dropout=0.0):
|
||||
super().__init__()
|
||||
inner_dim = int(dim * mult)
|
||||
dim_out = default(dim_out, dim)
|
||||
project_in = (
|
||||
nn.Sequential(nn.Linear(dim, inner_dim), nn.GELU())
|
||||
if not glu
|
||||
else GEGLU(dim, inner_dim)
|
||||
)
|
||||
|
||||
self.net = nn.Sequential(
|
||||
project_in, nn.Dropout(dropout), nn.Linear(inner_dim, dim_out)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.net(x)
|
||||
|
||||
|
||||
def zero_module(module):
|
||||
"""
|
||||
Zero out the parameters of a module and return it.
|
||||
"""
|
||||
for p in module.parameters():
|
||||
p.detach().zero_()
|
||||
return module
|
||||
|
||||
|
||||
def Normalize(in_channels):
|
||||
return torch.nn.GroupNorm(
|
||||
num_groups=32, num_channels=in_channels, eps=1e-6, affine=True
|
||||
)
|
||||
|
||||
|
||||
class SpatialSelfAttention(nn.Module):
|
||||
def __init__(self, in_channels):
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
|
||||
self.norm = Normalize(in_channels)
|
||||
self.q = torch.nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
self.k = torch.nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
self.v = torch.nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
self.proj_out = torch.nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
h_ = x
|
||||
h_ = self.norm(h_)
|
||||
q = self.q(h_)
|
||||
k = self.k(h_)
|
||||
v = self.v(h_)
|
||||
|
||||
# compute attention
|
||||
b, c, h, w = q.shape
|
||||
q = rearrange(q, "b c h w -> b (h w) c")
|
||||
k = rearrange(k, "b c h w -> b c (h w)")
|
||||
w_ = torch.einsum("bij,bjk->bik", q, k)
|
||||
|
||||
w_ = w_ * (int(c) ** (-0.5))
|
||||
w_ = torch.nn.functional.softmax(w_, dim=2)
|
||||
|
||||
# attend to values
|
||||
v = rearrange(v, "b c h w -> b c (h w)")
|
||||
w_ = rearrange(w_, "b i j -> b j i")
|
||||
h_ = torch.einsum("bij,bjk->bik", v, w_)
|
||||
h_ = rearrange(h_, "b c (h w) -> b c h w", h=h)
|
||||
h_ = self.proj_out(h_)
|
||||
|
||||
return x + h_
|
||||
|
||||
|
||||
class MemoryEfficientCrossAttention(nn.Module):
|
||||
# https://github.com/MatthieuTPHR/diffusers/blob/d80b531ff8060ec1ea982b65a1b8df70f73aa67c/src/diffusers/models/attention.py#L223
|
||||
def __init__(self, query_dim, context_dim=None, heads=8, dim_head=64, dropout=0.0, **kwargs):
|
||||
super().__init__()
|
||||
print(
|
||||
f"Setting up {self.__class__.__name__}. Query dim is {query_dim}, context_dim is {context_dim} and using "
|
||||
f"{heads} heads."
|
||||
)
|
||||
inner_dim = dim_head * heads
|
||||
context_dim = default(context_dim, query_dim)
|
||||
|
||||
self.heads = heads
|
||||
self.dim_head = dim_head
|
||||
|
||||
self.with_ip = kwargs.get("with_ip", False)
|
||||
if self.with_ip and (context_dim is not None):
|
||||
self.to_k_ip = nn.Linear(context_dim, inner_dim, bias=False)
|
||||
self.to_v_ip = nn.Linear(context_dim, inner_dim, bias=False)
|
||||
self.ip_dim= kwargs.get("ip_dim", 16)
|
||||
self.ip_weight = kwargs.get("ip_weight", 1.0)
|
||||
|
||||
self.to_q = nn.Linear(query_dim, inner_dim, bias=False)
|
||||
self.to_k = nn.Linear(context_dim, inner_dim, bias=False)
|
||||
self.to_v = nn.Linear(context_dim, inner_dim, bias=False)
|
||||
|
||||
self.to_out = nn.Sequential(
|
||||
nn.Linear(inner_dim, query_dim), nn.Dropout(dropout)
|
||||
)
|
||||
self.attention_op: Optional[Any] = None
|
||||
|
||||
def forward(self, x, context=None, mask=None):
|
||||
q = self.to_q(x)
|
||||
|
||||
has_ip = self.with_ip and (context is not None)
|
||||
if has_ip:
|
||||
# context dim [(b frame_num), (77 + img_token), 1024]
|
||||
token_len = context.shape[1]
|
||||
context_ip = context[:, -self.ip_dim:, :]
|
||||
k_ip = self.to_k_ip(context_ip)
|
||||
v_ip = self.to_v_ip(context_ip)
|
||||
context = context[:, :(token_len - self.ip_dim), :]
|
||||
|
||||
context = default(context, x)
|
||||
k = self.to_k(context)
|
||||
v = self.to_v(context)
|
||||
|
||||
b, _, _ = q.shape
|
||||
q, k, v = map(
|
||||
lambda t: t.unsqueeze(3)
|
||||
.reshape(b, t.shape[1], self.heads, self.dim_head)
|
||||
.permute(0, 2, 1, 3)
|
||||
.reshape(b * self.heads, t.shape[1], self.dim_head)
|
||||
.contiguous(),
|
||||
(q, k, v),
|
||||
)
|
||||
|
||||
# actually compute the attention, what we cannot get enough of
|
||||
out = xformers.ops.memory_efficient_attention(
|
||||
q, k, v, attn_bias=None, op=self.attention_op
|
||||
)
|
||||
|
||||
if has_ip:
|
||||
k_ip, v_ip = map(
|
||||
lambda t: t.unsqueeze(3)
|
||||
.reshape(b, t.shape[1], self.heads, self.dim_head)
|
||||
.permute(0, 2, 1, 3)
|
||||
.reshape(b * self.heads, t.shape[1], self.dim_head)
|
||||
.contiguous(),
|
||||
(k_ip, v_ip),
|
||||
)
|
||||
# actually compute the attention, what we cannot get enough of
|
||||
out_ip = xformers.ops.memory_efficient_attention(
|
||||
q, k_ip, v_ip, attn_bias=None, op=self.attention_op
|
||||
)
|
||||
out = out + self.ip_weight * out_ip
|
||||
|
||||
if exists(mask):
|
||||
raise NotImplementedError
|
||||
out = (
|
||||
out.unsqueeze(0)
|
||||
.reshape(b, self.heads, out.shape[1], self.dim_head)
|
||||
.permute(0, 2, 1, 3)
|
||||
.reshape(b, out.shape[1], self.heads * self.dim_head)
|
||||
)
|
||||
return self.to_out(out)
|
||||
|
||||
|
||||
class BasicTransformerBlock(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim,
|
||||
n_heads,
|
||||
d_head,
|
||||
dropout=0.0,
|
||||
context_dim=None,
|
||||
gated_ff=True,
|
||||
checkpoint=True,
|
||||
disable_self_attn=False,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__()
|
||||
assert XFORMERS_IS_AVAILBLE, "xformers is not available"
|
||||
attn_cls = MemoryEfficientCrossAttention
|
||||
self.disable_self_attn = disable_self_attn
|
||||
self.attn1 = attn_cls(
|
||||
query_dim=dim,
|
||||
heads=n_heads,
|
||||
dim_head=d_head,
|
||||
dropout=dropout,
|
||||
context_dim=context_dim if self.disable_self_attn else None,
|
||||
) # is a self-attention if not self.disable_self_attn
|
||||
self.ff = FeedForward(dim, dropout=dropout, glu=gated_ff)
|
||||
self.attn2 = attn_cls(
|
||||
query_dim=dim,
|
||||
context_dim=context_dim,
|
||||
heads=n_heads,
|
||||
dim_head=d_head,
|
||||
dropout=dropout,
|
||||
**kwargs
|
||||
) # is self-attn if context is none
|
||||
self.norm1 = nn.LayerNorm(dim)
|
||||
self.norm2 = nn.LayerNorm(dim)
|
||||
self.norm3 = nn.LayerNorm(dim)
|
||||
self.checkpoint = checkpoint
|
||||
|
||||
def forward(self, x, context=None):
|
||||
return checkpoint(
|
||||
self._forward, (x, context), self.parameters(), self.checkpoint
|
||||
)
|
||||
|
||||
def _forward(self, x, context=None):
|
||||
x = (
|
||||
self.attn1(
|
||||
self.norm1(x), context=context if self.disable_self_attn else None
|
||||
)
|
||||
+ x
|
||||
)
|
||||
x = self.attn2(self.norm2(x), context=context) + x
|
||||
x = self.ff(self.norm3(x)) + x
|
||||
return x
|
||||
|
||||
|
||||
class SpatialTransformer(nn.Module):
|
||||
"""
|
||||
Transformer block for image-like data.
|
||||
First, project the input (aka embedding)
|
||||
and reshape to b, t, d.
|
||||
Then apply standard transformer action.
|
||||
Finally, reshape to image
|
||||
NEW: use_linear for more efficiency instead of the 1x1 convs
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
n_heads,
|
||||
d_head,
|
||||
depth=1,
|
||||
dropout=0.0,
|
||||
context_dim=None,
|
||||
disable_self_attn=False,
|
||||
use_linear=False,
|
||||
use_checkpoint=True,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__()
|
||||
if exists(context_dim) and not isinstance(context_dim, list):
|
||||
context_dim = [context_dim]
|
||||
self.in_channels = in_channels
|
||||
inner_dim = n_heads * d_head
|
||||
self.norm = Normalize(in_channels)
|
||||
if not use_linear:
|
||||
self.proj_in = nn.Conv2d(
|
||||
in_channels, inner_dim, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
else:
|
||||
self.proj_in = nn.Linear(in_channels, inner_dim)
|
||||
|
||||
self.transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock(
|
||||
inner_dim,
|
||||
n_heads,
|
||||
d_head,
|
||||
dropout=dropout,
|
||||
context_dim=context_dim[d],
|
||||
disable_self_attn=disable_self_attn,
|
||||
checkpoint=use_checkpoint,
|
||||
**kwargs
|
||||
)
|
||||
for d in range(depth)
|
||||
]
|
||||
)
|
||||
if not use_linear:
|
||||
self.proj_out = zero_module(
|
||||
nn.Conv2d(inner_dim, in_channels, kernel_size=1, stride=1, padding=0)
|
||||
)
|
||||
else:
|
||||
self.proj_out = zero_module(nn.Linear(in_channels, inner_dim))
|
||||
self.use_linear = use_linear
|
||||
|
||||
def forward(self, x, context=None):
|
||||
# note: if no context is given, cross-attention defaults to self-attention
|
||||
if not isinstance(context, list):
|
||||
context = [context]
|
||||
b, c, h, w = x.shape
|
||||
x_in = x
|
||||
x = self.norm(x)
|
||||
if not self.use_linear:
|
||||
x = self.proj_in(x)
|
||||
x = rearrange(x, "b c h w -> b (h w) c").contiguous()
|
||||
if self.use_linear:
|
||||
x = self.proj_in(x)
|
||||
for i, block in enumerate(self.transformer_blocks):
|
||||
x = block(x, context=context[i])
|
||||
if self.use_linear:
|
||||
x = self.proj_out(x)
|
||||
x = rearrange(x, "b (h w) c -> b c h w", h=h, w=w).contiguous()
|
||||
if not self.use_linear:
|
||||
x = self.proj_out(x)
|
||||
return x + x_in
|
||||
|
||||
|
||||
class BasicTransformerBlock3D(BasicTransformerBlock):
|
||||
def forward(self, x, context=None, num_frames=1):
|
||||
return checkpoint(
|
||||
self._forward, (x, context, num_frames), self.parameters(), self.checkpoint
|
||||
)
|
||||
|
||||
def _forward(self, x, context=None, num_frames=1):
|
||||
x = rearrange(x, "(b f) l c -> b (f l) c", f=num_frames).contiguous()
|
||||
x = (
|
||||
self.attn1(
|
||||
self.norm1(x),
|
||||
context=context if self.disable_self_attn else None
|
||||
)
|
||||
+ x
|
||||
)
|
||||
x = rearrange(x, "b (f l) c -> (b f) l c", f=num_frames).contiguous()
|
||||
x = self.attn2(self.norm2(x), context=context) + x
|
||||
x = self.ff(self.norm3(x)) + x
|
||||
return x
|
||||
|
||||
|
||||
class SpatialTransformer3D(nn.Module):
|
||||
"""3D self-attention"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
n_heads,
|
||||
d_head,
|
||||
depth=1,
|
||||
dropout=0.0,
|
||||
context_dim=None,
|
||||
disable_self_attn=False,
|
||||
use_linear=False,
|
||||
use_checkpoint=True,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__()
|
||||
if exists(context_dim) and not isinstance(context_dim, list):
|
||||
context_dim = [context_dim]
|
||||
self.in_channels = in_channels
|
||||
inner_dim = n_heads * d_head
|
||||
self.norm = Normalize(in_channels)
|
||||
if not use_linear:
|
||||
self.proj_in = nn.Conv2d(
|
||||
in_channels, inner_dim, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
else:
|
||||
self.proj_in = nn.Linear(in_channels, inner_dim)
|
||||
|
||||
self.transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock3D(
|
||||
inner_dim,
|
||||
n_heads,
|
||||
d_head,
|
||||
dropout=dropout,
|
||||
context_dim=context_dim[d],
|
||||
disable_self_attn=disable_self_attn,
|
||||
checkpoint=use_checkpoint,
|
||||
**kwargs
|
||||
)
|
||||
for d in range(depth)
|
||||
]
|
||||
)
|
||||
if not use_linear:
|
||||
self.proj_out = zero_module(
|
||||
nn.Conv2d(inner_dim, in_channels, kernel_size=1, stride=1, padding=0)
|
||||
)
|
||||
else:
|
||||
self.proj_out = zero_module(nn.Linear(in_channels, inner_dim))
|
||||
self.use_linear = use_linear
|
||||
|
||||
def forward(self, x, context=None, num_frames=1):
|
||||
# note: if no context is given, cross-attention defaults to self-attention
|
||||
if not isinstance(context, list):
|
||||
context = [context]
|
||||
b, c, h, w = x.shape
|
||||
x_in = x
|
||||
x = self.norm(x)
|
||||
if not self.use_linear:
|
||||
x = self.proj_in(x)
|
||||
x = rearrange(x, "b c h w -> b (h w) c").contiguous()
|
||||
if self.use_linear:
|
||||
x = self.proj_in(x)
|
||||
for i, block in enumerate(self.transformer_blocks):
|
||||
x = block(x, context=context[i], num_frames=num_frames)
|
||||
if self.use_linear:
|
||||
x = self.proj_out(x)
|
||||
x = rearrange(x, "b (h w) c -> b c h w", h=h, w=w).contiguous()
|
||||
if not self.use_linear:
|
||||
x = self.proj_out(x)
|
||||
return x + x_in
|
||||
@@ -0,0 +1,163 @@
|
||||
# modified from https://github.com/mlfoundations/open_flamingo/blob/main/open_flamingo/src/helpers.py
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
# FFN
|
||||
def FeedForward(dim, mult=4):
|
||||
inner_dim = int(dim * mult)
|
||||
return nn.Sequential(
|
||||
nn.LayerNorm(dim),
|
||||
nn.Linear(dim, inner_dim, bias=False),
|
||||
nn.GELU(),
|
||||
nn.Linear(inner_dim, dim, bias=False),
|
||||
)
|
||||
|
||||
|
||||
def reshape_tensor(x, heads):
|
||||
bs, length, width = x.shape
|
||||
#(bs, length, width) --> (bs, length, n_heads, dim_per_head)
|
||||
x = x.view(bs, length, heads, -1)
|
||||
# (bs, length, n_heads, dim_per_head) --> (bs, n_heads, length, dim_per_head)
|
||||
x = x.transpose(1, 2)
|
||||
# (bs, n_heads, length, dim_per_head) --> (bs*n_heads, length, dim_per_head)
|
||||
x = x.reshape(bs, heads, length, -1)
|
||||
return x
|
||||
|
||||
|
||||
class PerceiverAttention(nn.Module):
|
||||
def __init__(self, *, dim, dim_head=64, heads=8):
|
||||
super().__init__()
|
||||
self.scale = dim_head**-0.5
|
||||
self.dim_head = dim_head
|
||||
self.heads = heads
|
||||
inner_dim = dim_head * heads
|
||||
|
||||
self.norm1 = nn.LayerNorm(dim)
|
||||
self.norm2 = nn.LayerNorm(dim)
|
||||
|
||||
self.to_q = nn.Linear(dim, inner_dim, bias=False)
|
||||
self.to_kv = nn.Linear(dim, inner_dim * 2, bias=False)
|
||||
self.to_out = nn.Linear(inner_dim, dim, bias=False)
|
||||
|
||||
|
||||
def forward(self, x, latents):
|
||||
"""
|
||||
Args:
|
||||
x (torch.Tensor): image features
|
||||
shape (b, n1, D)
|
||||
latent (torch.Tensor): latent features
|
||||
shape (b, n2, D)
|
||||
"""
|
||||
x = self.norm1(x)
|
||||
latents = self.norm2(latents)
|
||||
|
||||
b, l, _ = latents.shape
|
||||
|
||||
q = self.to_q(latents)
|
||||
kv_input = torch.cat((x, latents), dim=-2)
|
||||
k, v = self.to_kv(kv_input).chunk(2, dim=-1)
|
||||
|
||||
q = reshape_tensor(q, self.heads)
|
||||
k = reshape_tensor(k, self.heads)
|
||||
v = reshape_tensor(v, self.heads)
|
||||
|
||||
# attention
|
||||
scale = 1 / math.sqrt(math.sqrt(self.dim_head))
|
||||
weight = (q * scale) @ (k * scale).transpose(-2, -1) # More stable with f16 than dividing afterwards
|
||||
weight = torch.softmax(weight.float(), dim=-1).type(weight.dtype)
|
||||
out = weight @ v
|
||||
|
||||
out = out.permute(0, 2, 1, 3).reshape(b, l, -1)
|
||||
|
||||
return self.to_out(out)
|
||||
|
||||
|
||||
class ImageProjModel(torch.nn.Module):
|
||||
"""Projection Model"""
|
||||
def __init__(self,
|
||||
cross_attention_dim=1024,
|
||||
clip_embeddings_dim=1024,
|
||||
clip_extra_context_tokens=4):
|
||||
super().__init__()
|
||||
self.cross_attention_dim = cross_attention_dim
|
||||
self.clip_extra_context_tokens = clip_extra_context_tokens
|
||||
|
||||
# from 1024 -> 4 * 1024
|
||||
self.proj = torch.nn.Linear(
|
||||
clip_embeddings_dim,
|
||||
self.clip_extra_context_tokens * cross_attention_dim)
|
||||
self.norm = torch.nn.LayerNorm(cross_attention_dim)
|
||||
|
||||
def forward(self, image_embeds):
|
||||
embeds = image_embeds
|
||||
clip_extra_context_tokens = self.proj(embeds).reshape(-1, self.clip_extra_context_tokens, self.cross_attention_dim)
|
||||
clip_extra_context_tokens = self.norm(clip_extra_context_tokens)
|
||||
return clip_extra_context_tokens
|
||||
|
||||
|
||||
class SimpleReSampler(nn.Module):
|
||||
def __init__(self, embedding_dim=1280, output_dim=1024):
|
||||
super().__init__()
|
||||
self.proj_out = nn.Linear(embedding_dim, output_dim)
|
||||
self.norm_out = nn.LayerNorm(output_dim)
|
||||
|
||||
def forward(self, latents):
|
||||
"""
|
||||
latents: B 256 N
|
||||
"""
|
||||
latents = self.proj_out(latents)
|
||||
return self.norm_out(latents)
|
||||
|
||||
|
||||
class Resampler(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim=1024,
|
||||
depth=8,
|
||||
dim_head=64,
|
||||
heads=16,
|
||||
num_queries=8,
|
||||
embedding_dim=768,
|
||||
output_dim=1024,
|
||||
ff_mult=4,
|
||||
):
|
||||
super().__init__()
|
||||
self.latents = nn.Parameter(torch.randn(1, num_queries, dim) / dim**0.5)
|
||||
self.proj_in = nn.Linear(embedding_dim, dim)
|
||||
self.proj_out = nn.Linear(dim, output_dim)
|
||||
self.norm_out = nn.LayerNorm(output_dim)
|
||||
|
||||
self.layers = nn.ModuleList([])
|
||||
for _ in range(depth):
|
||||
self.layers.append(
|
||||
nn.ModuleList(
|
||||
[
|
||||
PerceiverAttention(dim=dim,
|
||||
dim_head=dim_head,
|
||||
heads=heads),
|
||||
FeedForward(dim=dim, mult=ff_mult),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
latents = self.latents.repeat(x.size(0), 1, 1)
|
||||
x = self.proj_in(x)
|
||||
for attn, ff in self.layers:
|
||||
latents = attn(x, latents) + latents
|
||||
latents = ff(latents) + latents
|
||||
|
||||
latents = self.proj_out(latents)
|
||||
return self.norm_out(latents)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
resampler = Resampler(embedding_dim=1280)
|
||||
resampler = SimpleReSampler(embedding_dim=1280)
|
||||
tensor = torch.rand(4, 257, 1280)
|
||||
embed = resampler(tensor)
|
||||
# embed = (tensor)
|
||||
print(embed.shape)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,355 @@
|
||||
# adopted from
|
||||
# https://github.com/openai/improved-diffusion/blob/main/improved_diffusion/gaussian_diffusion.py
|
||||
# and
|
||||
# https://github.com/lucidrains/denoising-diffusion-pytorch/blob/7706bdfc6f527f58d33f84b7b522e61e6e3164b3/denoising_diffusion_pytorch/denoising_diffusion_pytorch.py
|
||||
# and
|
||||
# https://github.com/openai/guided-diffusion/blob/0ba878e517b276c45d1195eb29f6f5f72659a05b/guided_diffusion/nn.py
|
||||
#
|
||||
# thanks!
|
||||
|
||||
|
||||
import os
|
||||
import math
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import numpy as np
|
||||
from einops import repeat
|
||||
import importlib
|
||||
|
||||
|
||||
def instantiate_from_config(config):
|
||||
if not "target" in config:
|
||||
if config == "__is_first_stage__":
|
||||
return None
|
||||
elif config == "__is_unconditional__":
|
||||
return None
|
||||
raise KeyError("Expected key `target` to instantiate.")
|
||||
return get_obj_from_str(config["target"])(**config.get("params", dict()))
|
||||
|
||||
|
||||
def get_obj_from_str(string, reload=False):
|
||||
module, cls = string.rsplit(".", 1)
|
||||
if reload:
|
||||
module_imp = importlib.import_module(module)
|
||||
importlib.reload(module_imp)
|
||||
return getattr(importlib.import_module(module, package=None), cls)
|
||||
|
||||
|
||||
def make_beta_schedule(
|
||||
schedule, n_timestep, linear_start=1e-4, linear_end=2e-2, cosine_s=8e-3
|
||||
):
|
||||
if schedule == "linear":
|
||||
betas = (
|
||||
torch.linspace(
|
||||
linear_start**0.5, linear_end**0.5, n_timestep, dtype=torch.float64
|
||||
)
|
||||
** 2
|
||||
)
|
||||
|
||||
elif schedule == "cosine":
|
||||
timesteps = (
|
||||
torch.arange(n_timestep + 1, dtype=torch.float64) / n_timestep + cosine_s
|
||||
)
|
||||
alphas = timesteps / (1 + cosine_s) * np.pi / 2
|
||||
alphas = torch.cos(alphas).pow(2)
|
||||
alphas = alphas / alphas[0]
|
||||
betas = 1 - alphas[1:] / alphas[:-1]
|
||||
betas = np.clip(betas, a_min=0, a_max=0.999)
|
||||
|
||||
elif schedule == "sqrt_linear":
|
||||
betas = torch.linspace(
|
||||
linear_start, linear_end, n_timestep, dtype=torch.float64
|
||||
)
|
||||
elif schedule == "sqrt":
|
||||
betas = (
|
||||
torch.linspace(linear_start, linear_end, n_timestep, dtype=torch.float64)
|
||||
** 0.5
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"schedule '{schedule}' unknown.")
|
||||
return betas.numpy()
|
||||
|
||||
def enforce_zero_terminal_snr(betas):
|
||||
betas = torch.tensor(betas) if not isinstance(betas, torch.Tensor) else betas
|
||||
# Convert betas to alphas_bar_sqrt
|
||||
alphas =1 - betas
|
||||
alphas_bar = alphas.cumprod(0)
|
||||
alphas_bar_sqrt = alphas_bar.sqrt()
|
||||
# Store old values.
|
||||
alphas_bar_sqrt_0 = alphas_bar_sqrt[0].clone()
|
||||
alphas_bar_sqrt_T = alphas_bar_sqrt[-1].clone()
|
||||
# Shift so last timestep is zero.
|
||||
alphas_bar_sqrt -= alphas_bar_sqrt_T
|
||||
# Scale so first timestep is back to old value.
|
||||
alphas_bar_sqrt *= alphas_bar_sqrt_0 / (alphas_bar_sqrt_0 - alphas_bar_sqrt_T)
|
||||
# Convert alphas_bar_sqrt to betas
|
||||
alphas_bar = alphas_bar_sqrt ** 2
|
||||
alphas = alphas_bar[1:] / alphas_bar[:-1]
|
||||
alphas = torch.cat ([alphas_bar[0:1], alphas])
|
||||
betas = 1 - alphas
|
||||
return betas
|
||||
|
||||
|
||||
def make_ddim_timesteps(
|
||||
ddim_discr_method, num_ddim_timesteps, num_ddpm_timesteps, verbose=True
|
||||
):
|
||||
if ddim_discr_method == "uniform":
|
||||
c = num_ddpm_timesteps // num_ddim_timesteps
|
||||
ddim_timesteps = np.asarray(list(range(0, num_ddpm_timesteps, c)))
|
||||
elif ddim_discr_method == "quad":
|
||||
ddim_timesteps = (
|
||||
(np.linspace(0, np.sqrt(num_ddpm_timesteps * 0.8), num_ddim_timesteps)) ** 2
|
||||
).astype(int)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'There is no ddim discretization method called "{ddim_discr_method}"'
|
||||
)
|
||||
|
||||
# assert ddim_timesteps.shape[0] == num_ddim_timesteps
|
||||
# add one to get the final alpha values right (the ones from first scale to data during sampling)
|
||||
steps_out = ddim_timesteps
|
||||
if len(ddim_timesteps) < num_ddpm_timesteps:
|
||||
steps_out += 1
|
||||
if verbose:
|
||||
print(f"Selected timesteps for ddim sampler: {steps_out}")
|
||||
return steps_out
|
||||
|
||||
|
||||
def make_ddim_sampling_parameters(alphacums, ddim_timesteps, eta, verbose=True):
|
||||
# select alphas for computing the variance schedule
|
||||
alphas = alphacums[ddim_timesteps]
|
||||
alphas_prev = np.asarray([alphacums[0]] + alphacums[ddim_timesteps[:-1]].tolist())
|
||||
|
||||
# according the the formula provided in https://arxiv.org/abs/2010.02502
|
||||
sigmas = eta * np.sqrt(
|
||||
(1 - alphas_prev) / (1 - alphas) * (1 - alphas / alphas_prev)
|
||||
)
|
||||
if verbose:
|
||||
print(
|
||||
f"Selected alphas for ddim sampler: a_t: {alphas}; a_(t-1): {alphas_prev}"
|
||||
)
|
||||
print(
|
||||
f"For the chosen value of eta, which is {eta}, "
|
||||
f"this results in the following sigma_t schedule for ddim sampler {sigmas}"
|
||||
)
|
||||
return sigmas, alphas, alphas_prev
|
||||
|
||||
|
||||
def betas_for_alpha_bar(num_diffusion_timesteps, alpha_bar, max_beta=0.999):
|
||||
"""
|
||||
Create a beta schedule that discretizes the given alpha_t_bar function,
|
||||
which defines the cumulative product of (1-beta) over time from t = [0,1].
|
||||
:param num_diffusion_timesteps: the number of betas to produce.
|
||||
:param alpha_bar: a lambda that takes an argument t from 0 to 1 and
|
||||
produces the cumulative product of (1-beta) up to that
|
||||
part of the diffusion process.
|
||||
:param max_beta: the maximum beta to use; use values lower than 1 to
|
||||
prevent singularities.
|
||||
"""
|
||||
betas = []
|
||||
for i in range(num_diffusion_timesteps):
|
||||
t1 = i / num_diffusion_timesteps
|
||||
t2 = (i + 1) / num_diffusion_timesteps
|
||||
betas.append(min(1 - alpha_bar(t2) / alpha_bar(t1), max_beta))
|
||||
return np.array(betas)
|
||||
|
||||
|
||||
def extract_into_tensor(a, t, x_shape):
|
||||
b, *_ = t.shape
|
||||
out = a.gather(-1, t)
|
||||
return out.reshape(b, *((1,) * (len(x_shape) - 1)))
|
||||
|
||||
|
||||
def checkpoint(func, inputs, params, flag):
|
||||
"""
|
||||
Evaluate a function without caching intermediate activations, allowing for
|
||||
reduced memory at the expense of extra compute in the backward pass.
|
||||
:param func: the function to evaluate.
|
||||
:param inputs: the argument sequence to pass to `func`.
|
||||
:param params: a sequence of parameters `func` depends on but does not
|
||||
explicitly take as arguments.
|
||||
:param flag: if False, disable gradient checkpointing.
|
||||
"""
|
||||
if flag:
|
||||
args = tuple(inputs) + tuple(params)
|
||||
return CheckpointFunction.apply(func, len(inputs), *args)
|
||||
else:
|
||||
return func(*inputs)
|
||||
|
||||
|
||||
class CheckpointFunction(torch.autograd.Function):
|
||||
@staticmethod
|
||||
def forward(ctx, run_function, length, *args):
|
||||
ctx.run_function = run_function
|
||||
ctx.input_tensors = list(args[:length])
|
||||
ctx.input_params = list(args[length:])
|
||||
|
||||
with torch.no_grad():
|
||||
output_tensors = ctx.run_function(*ctx.input_tensors)
|
||||
return output_tensors
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, *output_grads):
|
||||
ctx.input_tensors = [x.detach().requires_grad_(True) for x in ctx.input_tensors]
|
||||
with torch.enable_grad():
|
||||
# Fixes a bug where the first op in run_function modifies the
|
||||
# Tensor storage in place, which is not allowed for detach()'d
|
||||
# Tensors.
|
||||
shallow_copies = [x.view_as(x) for x in ctx.input_tensors]
|
||||
output_tensors = ctx.run_function(*shallow_copies)
|
||||
input_grads = torch.autograd.grad(
|
||||
output_tensors,
|
||||
ctx.input_tensors + ctx.input_params,
|
||||
output_grads,
|
||||
allow_unused=True,
|
||||
)
|
||||
del ctx.input_tensors
|
||||
del ctx.input_params
|
||||
del output_tensors
|
||||
return (None, None) + input_grads
|
||||
|
||||
|
||||
def timestep_embedding(timesteps, dim, max_period=10000, repeat_only=False):
|
||||
"""
|
||||
Create sinusoidal timestep embeddings.
|
||||
:param timesteps: a 1-D Tensor of N indices, one per batch element.
|
||||
These may be fractional.
|
||||
:param dim: the dimension of the output.
|
||||
:param max_period: controls the minimum frequency of the embeddings.
|
||||
:return: an [N x dim] Tensor of positional embeddings.
|
||||
"""
|
||||
if not repeat_only:
|
||||
half = dim // 2
|
||||
freqs = torch.exp(
|
||||
-math.log(max_period)
|
||||
* torch.arange(start=0, end=half, device=timesteps.device, dtype=torch.float32)
|
||||
/ half
|
||||
)
|
||||
args = timesteps[:, None].float() * freqs[None]
|
||||
embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
|
||||
if dim % 2:
|
||||
embedding = torch.cat(
|
||||
[embedding, torch.zeros_like(embedding[:, :1])], dim=-1
|
||||
)
|
||||
else:
|
||||
embedding = repeat(timesteps, "b -> b d", d=dim)
|
||||
# import pdb; pdb.set_trace()
|
||||
return embedding
|
||||
|
||||
|
||||
def zero_module(module):
|
||||
"""
|
||||
Zero out the parameters of a module and return it.
|
||||
"""
|
||||
for p in module.parameters():
|
||||
p.detach().zero_()
|
||||
return module
|
||||
|
||||
|
||||
def scale_module(module, scale):
|
||||
"""
|
||||
Scale the parameters of a module and return it.
|
||||
"""
|
||||
for p in module.parameters():
|
||||
p.detach().mul_(scale)
|
||||
return module
|
||||
|
||||
|
||||
def mean_flat(tensor):
|
||||
"""
|
||||
Take the mean over all non-batch dimensions.
|
||||
"""
|
||||
return tensor.mean(dim=list(range(1, len(tensor.shape))))
|
||||
|
||||
|
||||
def normalization(channels):
|
||||
"""
|
||||
Make a standard normalization layer.
|
||||
:param channels: number of input channels.
|
||||
:return: an nn.Module for normalization.
|
||||
"""
|
||||
return GroupNorm32(32, channels)
|
||||
|
||||
|
||||
# PyTorch 1.7 has SiLU, but we support PyTorch 1.5.
|
||||
class SiLU(nn.Module):
|
||||
def forward(self, x):
|
||||
return x * torch.sigmoid(x)
|
||||
|
||||
|
||||
class GroupNorm32(nn.GroupNorm):
|
||||
def forward(self, x):
|
||||
return super().forward(x.float()).type(x.dtype)
|
||||
|
||||
|
||||
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 linear(*args, **kwargs):
|
||||
"""
|
||||
Create a linear module.
|
||||
"""
|
||||
return nn.Linear(*args, **kwargs)
|
||||
|
||||
|
||||
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 HybridConditioner(nn.Module):
|
||||
def __init__(self, c_concat_config, c_crossattn_config):
|
||||
super().__init__()
|
||||
self.concat_conditioner = instantiate_from_config(c_concat_config)
|
||||
self.crossattn_conditioner = instantiate_from_config(c_crossattn_config)
|
||||
|
||||
def forward(self, c_concat, c_crossattn):
|
||||
c_concat = self.concat_conditioner(c_concat)
|
||||
c_crossattn = self.crossattn_conditioner(c_crossattn)
|
||||
return {"c_concat": [c_concat], "c_crossattn": [c_crossattn]}
|
||||
|
||||
|
||||
def noise_like(shape, device, repeat=False):
|
||||
repeat_noise = lambda: torch.randn((1, *shape[1:]), device=device).repeat(
|
||||
shape[0], *((1,) * (len(shape) - 1))
|
||||
)
|
||||
noise = lambda: torch.randn(shape, device=device)
|
||||
return repeat_noise() if repeat else noise()
|
||||
|
||||
|
||||
# dummy replace
|
||||
def convert_module_to_f16(l):
|
||||
"""
|
||||
Convert primitive modules to float16.
|
||||
"""
|
||||
if isinstance(l, (nn.Conv1d, nn.Conv2d, nn.Conv3d)):
|
||||
l.weight.data = l.weight.data.half()
|
||||
if l.bias is not None:
|
||||
l.bias.data = l.bias.data.half()
|
||||
|
||||
def convert_module_to_f32(l):
|
||||
"""
|
||||
Convert primitive modules to float32, undoing convert_module_to_f16().
|
||||
"""
|
||||
if isinstance(l, (nn.Conv1d, nn.Conv2d, nn.Conv3d)):
|
||||
l.weight.data = l.weight.data.float()
|
||||
if l.bias is not None:
|
||||
l.bias.data = l.bias.data.float()
|
||||
@@ -0,0 +1,102 @@
|
||||
import torch
|
||||
import numpy as np
|
||||
|
||||
|
||||
class AbstractDistribution:
|
||||
def sample(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def mode(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class DiracDistribution(AbstractDistribution):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def sample(self):
|
||||
return self.value
|
||||
|
||||
def mode(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class DiagonalGaussianDistribution(object):
|
||||
def __init__(self, parameters, deterministic=False):
|
||||
self.parameters = parameters
|
||||
self.mean, self.logvar = torch.chunk(parameters, 2, dim=1)
|
||||
self.logvar = torch.clamp(self.logvar, -30.0, 20.0)
|
||||
self.deterministic = deterministic
|
||||
self.std = torch.exp(0.5 * self.logvar)
|
||||
self.var = torch.exp(self.logvar)
|
||||
if self.deterministic:
|
||||
self.var = self.std = torch.zeros_like(self.mean).to(
|
||||
device=self.parameters.device
|
||||
)
|
||||
|
||||
def sample(self):
|
||||
x = self.mean + self.std * torch.randn(self.mean.shape).to(
|
||||
device=self.parameters.device
|
||||
)
|
||||
return x
|
||||
|
||||
def kl(self, other=None):
|
||||
if self.deterministic:
|
||||
return torch.Tensor([0.0])
|
||||
else:
|
||||
if other is None:
|
||||
return 0.5 * torch.sum(
|
||||
torch.pow(self.mean, 2) + self.var - 1.0 - self.logvar,
|
||||
dim=[1, 2, 3],
|
||||
)
|
||||
else:
|
||||
return 0.5 * torch.sum(
|
||||
torch.pow(self.mean - other.mean, 2) / other.var
|
||||
+ self.var / other.var
|
||||
- 1.0
|
||||
- self.logvar
|
||||
+ other.logvar,
|
||||
dim=[1, 2, 3],
|
||||
)
|
||||
|
||||
def nll(self, sample, dims=[1, 2, 3]):
|
||||
if self.deterministic:
|
||||
return torch.Tensor([0.0])
|
||||
logtwopi = np.log(2.0 * np.pi)
|
||||
return 0.5 * torch.sum(
|
||||
logtwopi + self.logvar + torch.pow(sample - self.mean, 2) / self.var,
|
||||
dim=dims,
|
||||
)
|
||||
|
||||
def mode(self):
|
||||
return self.mean
|
||||
|
||||
|
||||
def normal_kl(mean1, logvar1, mean2, logvar2):
|
||||
"""
|
||||
source: https://github.com/openai/guided-diffusion/blob/27c20a8fab9cb472df5d6bdd6c8d11c8f430b924/guided_diffusion/losses.py#L12
|
||||
Compute the KL divergence between two gaussians.
|
||||
Shapes are automatically broadcasted, so batches can be compared to
|
||||
scalars, among other use cases.
|
||||
"""
|
||||
tensor = None
|
||||
for obj in (mean1, logvar1, mean2, logvar2):
|
||||
if isinstance(obj, torch.Tensor):
|
||||
tensor = obj
|
||||
break
|
||||
assert tensor is not None, "at least one argument must be a Tensor"
|
||||
|
||||
# Force variances to be Tensors. Broadcasting helps convert scalars to
|
||||
# Tensors, but it does not work for torch.exp().
|
||||
logvar1, logvar2 = [
|
||||
x if isinstance(x, torch.Tensor) else torch.tensor(x).to(tensor)
|
||||
for x in (logvar1, logvar2)
|
||||
]
|
||||
|
||||
return 0.5 * (
|
||||
-1.0
|
||||
+ logvar2
|
||||
- logvar1
|
||||
+ torch.exp(logvar1 - logvar2)
|
||||
+ ((mean1 - mean2) ** 2) * torch.exp(-logvar2)
|
||||
)
|
||||
@@ -0,0 +1,86 @@
|
||||
import torch
|
||||
from torch import nn
|
||||
|
||||
|
||||
class LitEma(nn.Module):
|
||||
def __init__(self, model, decay=0.9999, use_num_upates=True):
|
||||
super().__init__()
|
||||
if decay < 0.0 or decay > 1.0:
|
||||
raise ValueError("Decay must be between 0 and 1")
|
||||
|
||||
self.m_name2s_name = {}
|
||||
self.register_buffer("decay", torch.tensor(decay, dtype=torch.float32))
|
||||
self.register_buffer(
|
||||
"num_updates",
|
||||
torch.tensor(0, dtype=torch.int)
|
||||
if use_num_upates
|
||||
else torch.tensor(-1, dtype=torch.int),
|
||||
)
|
||||
|
||||
for name, p in model.named_parameters():
|
||||
if p.requires_grad:
|
||||
# remove as '.'-character is not allowed in buffers
|
||||
s_name = name.replace(".", "")
|
||||
self.m_name2s_name.update({name: s_name})
|
||||
self.register_buffer(s_name, p.clone().detach().data)
|
||||
|
||||
self.collected_params = []
|
||||
|
||||
def reset_num_updates(self):
|
||||
del self.num_updates
|
||||
self.register_buffer("num_updates", torch.tensor(0, dtype=torch.int))
|
||||
|
||||
def forward(self, model):
|
||||
decay = self.decay
|
||||
|
||||
if self.num_updates >= 0:
|
||||
self.num_updates += 1
|
||||
decay = min(self.decay, (1 + self.num_updates) / (10 + self.num_updates))
|
||||
|
||||
one_minus_decay = 1.0 - decay
|
||||
|
||||
with torch.no_grad():
|
||||
m_param = dict(model.named_parameters())
|
||||
shadow_params = dict(self.named_buffers())
|
||||
|
||||
for key in m_param:
|
||||
if m_param[key].requires_grad:
|
||||
sname = self.m_name2s_name[key]
|
||||
shadow_params[sname] = shadow_params[sname].type_as(m_param[key])
|
||||
shadow_params[sname].sub_(
|
||||
one_minus_decay * (shadow_params[sname] - m_param[key])
|
||||
)
|
||||
else:
|
||||
assert not key in self.m_name2s_name
|
||||
|
||||
def copy_to(self, model):
|
||||
m_param = dict(model.named_parameters())
|
||||
shadow_params = dict(self.named_buffers())
|
||||
for key in m_param:
|
||||
if m_param[key].requires_grad:
|
||||
m_param[key].data.copy_(shadow_params[self.m_name2s_name[key]].data)
|
||||
else:
|
||||
assert not key in self.m_name2s_name
|
||||
|
||||
def store(self, parameters):
|
||||
"""
|
||||
Save the current parameters for restoring later.
|
||||
Args:
|
||||
parameters: Iterable of `torch.nn.Parameter`; the parameters to be
|
||||
temporarily stored.
|
||||
"""
|
||||
self.collected_params = [param.clone() for param in parameters]
|
||||
|
||||
def restore(self, parameters):
|
||||
"""
|
||||
Restore the parameters stored with the `store` method.
|
||||
Useful to validate the model with EMA parameters without affecting the
|
||||
original optimization process. Store the parameters before the
|
||||
`copy_to` method. After validation (or model saving), use this to
|
||||
restore the former parameters.
|
||||
Args:
|
||||
parameters: Iterable of `torch.nn.Parameter`; the parameters to be
|
||||
updated with the stored parameters.
|
||||
"""
|
||||
for c_param, param in zip(self.collected_params, parameters):
|
||||
param.data.copy_(c_param.data)
|
||||
@@ -0,0 +1,330 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.utils.checkpoint import checkpoint
|
||||
import torchvision.transforms.functional as TF
|
||||
|
||||
from transformers import T5Tokenizer, T5EncoderModel, CLIPTokenizer, CLIPTextModel
|
||||
|
||||
import numpy as np
|
||||
import open_clip
|
||||
from PIL import Image
|
||||
from ...util import default, count_params
|
||||
|
||||
|
||||
class AbstractEncoder(nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def encode(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class IdentityEncoder(AbstractEncoder):
|
||||
def encode(self, x):
|
||||
return x
|
||||
|
||||
|
||||
class ClassEmbedder(nn.Module):
|
||||
def __init__(self, embed_dim, n_classes=1000, key="class", ucg_rate=0.1):
|
||||
super().__init__()
|
||||
self.key = key
|
||||
self.embedding = nn.Embedding(n_classes, embed_dim)
|
||||
self.n_classes = n_classes
|
||||
self.ucg_rate = ucg_rate
|
||||
|
||||
def forward(self, batch, key=None, disable_dropout=False):
|
||||
if key is None:
|
||||
key = self.key
|
||||
# this is for use in crossattn
|
||||
c = batch[key][:, None]
|
||||
if self.ucg_rate > 0.0 and not disable_dropout:
|
||||
mask = 1.0 - torch.bernoulli(torch.ones_like(c) * self.ucg_rate)
|
||||
c = mask * c + (1 - mask) * torch.ones_like(c) * (self.n_classes - 1)
|
||||
c = c.long()
|
||||
c = self.embedding(c)
|
||||
return c
|
||||
|
||||
def get_unconditional_conditioning(self, bs, device="cuda"):
|
||||
uc_class = (
|
||||
self.n_classes - 1
|
||||
) # 1000 classes --> 0 ... 999, one extra class for ucg (class 1000)
|
||||
uc = torch.ones((bs,), device=device) * uc_class
|
||||
uc = {self.key: uc}
|
||||
return uc
|
||||
|
||||
|
||||
def disabled_train(self, mode=True):
|
||||
"""Overwrite model.train with this function to make sure train/eval mode
|
||||
does not change anymore."""
|
||||
return self
|
||||
|
||||
|
||||
class FrozenT5Embedder(AbstractEncoder):
|
||||
"""Uses the T5 transformer encoder for text"""
|
||||
|
||||
def __init__(
|
||||
self, version="google/t5-v1_1-large", device="cuda", max_length=77, freeze=True
|
||||
): # others are google/t5-v1_1-xl and google/t5-v1_1-xxl
|
||||
super().__init__()
|
||||
self.tokenizer = T5Tokenizer.from_pretrained(version)
|
||||
self.transformer = T5EncoderModel.from_pretrained(version)
|
||||
self.device = device
|
||||
self.max_length = max_length # TODO: typical value?
|
||||
if freeze:
|
||||
self.freeze()
|
||||
|
||||
def freeze(self):
|
||||
self.transformer = self.transformer.eval()
|
||||
# self.train = disabled_train
|
||||
for param in self.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def forward(self, text):
|
||||
batch_encoding = self.tokenizer(
|
||||
text,
|
||||
truncation=True,
|
||||
max_length=self.max_length,
|
||||
return_length=True,
|
||||
return_overflowing_tokens=False,
|
||||
padding="max_length",
|
||||
return_tensors="pt",
|
||||
)
|
||||
tokens = batch_encoding["input_ids"].to(self.device)
|
||||
outputs = self.transformer(input_ids=tokens)
|
||||
|
||||
z = outputs.last_hidden_state
|
||||
return z
|
||||
|
||||
def encode(self, text):
|
||||
return self(text)
|
||||
|
||||
|
||||
class FrozenCLIPEmbedder(AbstractEncoder):
|
||||
"""Uses the CLIP transformer encoder for text (from huggingface)"""
|
||||
|
||||
LAYERS = ["last", "pooled", "hidden"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
version="openai/clip-vit-large-patch14",
|
||||
device="cuda",
|
||||
max_length=77,
|
||||
freeze=True,
|
||||
layer="last",
|
||||
layer_idx=None,
|
||||
): # clip-vit-base-patch32
|
||||
super().__init__()
|
||||
assert layer in self.LAYERS
|
||||
self.tokenizer = CLIPTokenizer.from_pretrained(version)
|
||||
self.transformer = CLIPTextModel.from_pretrained(version)
|
||||
self.device = device
|
||||
self.max_length = max_length
|
||||
if freeze:
|
||||
self.freeze()
|
||||
self.layer = layer
|
||||
self.layer_idx = layer_idx
|
||||
if layer == "hidden":
|
||||
assert layer_idx is not None
|
||||
assert 0 <= abs(layer_idx) <= 12
|
||||
|
||||
def freeze(self):
|
||||
self.transformer = self.transformer.eval()
|
||||
# self.train = disabled_train
|
||||
for param in self.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def forward(self, text):
|
||||
batch_encoding = self.tokenizer(
|
||||
text,
|
||||
truncation=True,
|
||||
max_length=self.max_length,
|
||||
return_length=True,
|
||||
return_overflowing_tokens=False,
|
||||
padding="max_length",
|
||||
return_tensors="pt",
|
||||
)
|
||||
tokens = batch_encoding["input_ids"].to(self.device)
|
||||
outputs = self.transformer(
|
||||
input_ids=tokens, output_hidden_states=self.layer == "hidden"
|
||||
)
|
||||
if self.layer == "last":
|
||||
z = outputs.last_hidden_state
|
||||
elif self.layer == "pooled":
|
||||
z = outputs.pooler_output[:, None, :]
|
||||
else:
|
||||
z = outputs.hidden_states[self.layer_idx]
|
||||
return z
|
||||
|
||||
def encode(self, text):
|
||||
return self(text)
|
||||
|
||||
|
||||
class FrozenOpenCLIPEmbedder(AbstractEncoder, nn.Module):
|
||||
"""
|
||||
Uses the OpenCLIP transformer encoder for text
|
||||
"""
|
||||
|
||||
LAYERS = [
|
||||
# "pooled",
|
||||
"last",
|
||||
"penultimate",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
arch="ViT-H-14",
|
||||
version="laion2b_s32b_b79k",
|
||||
device="cuda",
|
||||
max_length=77,
|
||||
freeze=True,
|
||||
layer="last",
|
||||
ip_mode=None
|
||||
):
|
||||
"""_summary_
|
||||
|
||||
Args:
|
||||
ip_mode (str, optional): what is the image promcessing mode. Defaults to None.
|
||||
|
||||
"""
|
||||
super().__init__()
|
||||
assert layer in self.LAYERS
|
||||
model, _, preprocess = open_clip.create_model_and_transforms(
|
||||
arch, device=torch.device("cpu"), pretrained=version
|
||||
)
|
||||
if ip_mode is None:
|
||||
del model.visual
|
||||
|
||||
self.model = model
|
||||
self.preprocess = preprocess
|
||||
self.device = device
|
||||
self.max_length = max_length
|
||||
self.ip_mode = ip_mode
|
||||
if freeze:
|
||||
self.freeze()
|
||||
self.layer = layer
|
||||
if self.layer == "last":
|
||||
self.layer_idx = 0
|
||||
elif self.layer == "penultimate":
|
||||
self.layer_idx = 1
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
def freeze(self):
|
||||
self.model = self.model.eval()
|
||||
for param in self.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def forward(self, text):
|
||||
tokens = open_clip.tokenize(text)
|
||||
z = self.encode_with_transformer(tokens.to(self.device))
|
||||
return z
|
||||
|
||||
def forward_image(self, pil_image):
|
||||
if isinstance(pil_image, Image.Image):
|
||||
pil_image = [pil_image]
|
||||
if isinstance(pil_image, torch.Tensor):
|
||||
pil_image = pil_image.cpu().numpy()
|
||||
if isinstance(pil_image, np.ndarray):
|
||||
if pil_image.ndim == 3:
|
||||
pil_image = pil_image[None, :, :, :]
|
||||
pil_image = [Image.fromarray(x) for x in pil_image]
|
||||
|
||||
images = []
|
||||
for image in pil_image:
|
||||
images.append(self.preprocess(image).to(self.device))
|
||||
|
||||
image = torch.stack(images, 0) # to [b, 3, h, w]
|
||||
if self.ip_mode == "global":
|
||||
image_features = self.model.encode_image(image)
|
||||
image_features /= image_features.norm(dim=-1, keepdim=True)
|
||||
elif "local" in self.ip_mode:
|
||||
image_features = self.encode_image_with_transformer(image)
|
||||
|
||||
return image_features # b, l
|
||||
|
||||
def encode_image_with_transformer(self, x):
|
||||
visual = self.model.visual
|
||||
x = visual.conv1(x) # shape = [*, width, grid, grid]
|
||||
x = x.reshape(x.shape[0], x.shape[1], -1) # shape = [*, width, grid ** 2]
|
||||
x = x.permute(0, 2, 1) # shape = [*, grid ** 2, width]
|
||||
|
||||
# class embeddings and positional embeddings
|
||||
x = torch.cat(
|
||||
[visual.class_embedding.to(x.dtype) + \
|
||||
torch.zeros(x.shape[0], 1, x.shape[-1], dtype=x.dtype, device=x.device),
|
||||
x], dim=1) # shape = [*, grid ** 2 + 1, width]
|
||||
x = x + visual.positional_embedding.to(x.dtype)
|
||||
|
||||
# a patch_dropout of 0. would mean it is disabled and this function would do nothing but return what was passed in
|
||||
# x = visual.patch_dropout(x)
|
||||
x = visual.ln_pre(x)
|
||||
|
||||
x = x.permute(1, 0, 2) # NLD -> LND
|
||||
hidden = self.image_transformer_forward(x)
|
||||
x = hidden[-2].permute(1, 0, 2) # LND -> NLD
|
||||
return x
|
||||
|
||||
def image_transformer_forward(self, x):
|
||||
encoder_states = ()
|
||||
trans = self.model.visual.transformer
|
||||
for r in trans.resblocks:
|
||||
if trans.grad_checkpointing and not torch.jit.is_scripting():
|
||||
# TODO: handle kwargs https://github.com/pytorch/pytorch/issues/79887#issuecomment-1161758372
|
||||
x = checkpoint(r, x, None, None, None)
|
||||
else:
|
||||
x = r(x, attn_mask=None)
|
||||
encoder_states = encoder_states + (x, )
|
||||
return encoder_states
|
||||
|
||||
def encode_with_transformer(self, text):
|
||||
x = self.model.token_embedding(text) # [batch_size, n_ctx, d_model]
|
||||
x = x + self.model.positional_embedding
|
||||
x = x.permute(1, 0, 2) # NLD -> LND
|
||||
x = self.text_transformer_forward(x, attn_mask=self.model.attn_mask)
|
||||
x = x.permute(1, 0, 2) # LND -> NLD
|
||||
x = self.model.ln_final(x)
|
||||
return x
|
||||
|
||||
def text_transformer_forward(self, x: torch.Tensor, attn_mask=None):
|
||||
for i, r in enumerate(self.model.transformer.resblocks):
|
||||
if i == len(self.model.transformer.resblocks) - self.layer_idx:
|
||||
break
|
||||
if (
|
||||
self.model.transformer.grad_checkpointing
|
||||
and not torch.jit.is_scripting()
|
||||
):
|
||||
x = checkpoint(r, x, attn_mask)
|
||||
else:
|
||||
x = r(x, attn_mask=attn_mask)
|
||||
return x
|
||||
|
||||
def encode(self, text):
|
||||
return self(text)
|
||||
|
||||
|
||||
class FrozenCLIPT5Encoder(AbstractEncoder):
|
||||
def __init__(
|
||||
self,
|
||||
clip_version="openai/clip-vit-large-patch14",
|
||||
t5_version="google/t5-v1_1-xl",
|
||||
device="cuda",
|
||||
clip_max_length=77,
|
||||
t5_max_length=77,
|
||||
):
|
||||
super().__init__()
|
||||
self.clip_encoder = FrozenCLIPEmbedder(
|
||||
clip_version, device, max_length=clip_max_length
|
||||
)
|
||||
self.t5_encoder = FrozenT5Embedder(t5_version, device, max_length=t5_max_length)
|
||||
print(
|
||||
f"{self.clip_encoder.__class__.__name__} has {count_params(self.clip_encoder)*1.e-6:.2f} M parameters, "
|
||||
f"{self.t5_encoder.__class__.__name__} comes with {count_params(self.t5_encoder)*1.e-6:.2f} M params."
|
||||
)
|
||||
|
||||
def encode(self, text):
|
||||
return self(text)
|
||||
|
||||
def forward(self, text):
|
||||
clip_z = self.clip_encoder.encode(text)
|
||||
t5_z = self.t5_encoder.encode(text)
|
||||
return [clip_z, t5_z]
|
||||
@@ -0,0 +1,226 @@
|
||||
import importlib
|
||||
|
||||
import random
|
||||
import torch
|
||||
import numpy as np
|
||||
from collections import abc
|
||||
|
||||
import multiprocessing as mp
|
||||
from threading import Thread
|
||||
from queue import Queue
|
||||
|
||||
from inspect import isfunction
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
def log_txt_as_img(wh, xc, size=10):
|
||||
# wh a tuple of (width, height)
|
||||
# xc a list of captions to plot
|
||||
b = len(xc)
|
||||
txts = list()
|
||||
for bi in range(b):
|
||||
txt = Image.new("RGB", wh, color="white")
|
||||
draw = ImageDraw.Draw(txt)
|
||||
font = ImageFont.truetype("data/DejaVuSans.ttf", size=size)
|
||||
nc = int(40 * (wh[0] / 256))
|
||||
lines = "\n".join(
|
||||
xc[bi][start : start + nc] for start in range(0, len(xc[bi]), nc)
|
||||
)
|
||||
|
||||
try:
|
||||
draw.text((0, 0), lines, fill="black", font=font)
|
||||
except UnicodeEncodeError:
|
||||
print("Cant encode string for logging. Skipping.")
|
||||
|
||||
txt = np.array(txt).transpose(2, 0, 1) / 127.5 - 1.0
|
||||
txts.append(txt)
|
||||
txts = np.stack(txts)
|
||||
txts = torch.tensor(txts)
|
||||
return txts
|
||||
|
||||
|
||||
def ismap(x):
|
||||
if not isinstance(x, torch.Tensor):
|
||||
return False
|
||||
return (len(x.shape) == 4) and (x.shape[1] > 3)
|
||||
|
||||
|
||||
def isimage(x):
|
||||
if not isinstance(x, torch.Tensor):
|
||||
return False
|
||||
return (len(x.shape) == 4) and (x.shape[1] == 3 or x.shape[1] == 1)
|
||||
|
||||
|
||||
def exists(x):
|
||||
return x is not None
|
||||
|
||||
|
||||
def default(val, d):
|
||||
if exists(val):
|
||||
return val
|
||||
return d() if isfunction(d) else d
|
||||
|
||||
|
||||
def mean_flat(tensor):
|
||||
"""
|
||||
https://github.com/openai/guided-diffusion/blob/27c20a8fab9cb472df5d6bdd6c8d11c8f430b924/guided_diffusion/nn.py#L86
|
||||
Take the mean over all non-batch dimensions.
|
||||
"""
|
||||
return tensor.mean(dim=list(range(1, len(tensor.shape))))
|
||||
|
||||
|
||||
def count_params(model, verbose=False):
|
||||
total_params = sum(p.numel() for p in model.parameters())
|
||||
if verbose:
|
||||
print(f"{model.__class__.__name__} has {total_params * 1.e-6:.2f} M params.")
|
||||
return total_params
|
||||
|
||||
|
||||
def instantiate_from_config(config):
|
||||
if not "target" in config:
|
||||
if config == "__is_first_stage__":
|
||||
return None
|
||||
elif config == "__is_unconditional__":
|
||||
return None
|
||||
raise KeyError("Expected key `target` to instantiate.")
|
||||
# import pdb; pdb.set_trace()
|
||||
return get_obj_from_str(config["target"])(**config.get("params", dict()))
|
||||
|
||||
|
||||
def get_obj_from_str(string, reload=False):
|
||||
module, cls = string.rsplit(".", 1)
|
||||
# import pdb; pdb.set_trace()
|
||||
if reload:
|
||||
module_imp = importlib.import_module(module)
|
||||
importlib.reload(module_imp)
|
||||
return getattr(importlib.import_module(module, package=None), cls)
|
||||
|
||||
|
||||
def _do_parallel_data_prefetch(func, Q, data, idx, idx_to_fn=False):
|
||||
# create dummy dataset instance
|
||||
|
||||
# run prefetching
|
||||
if idx_to_fn:
|
||||
res = func(data, worker_id=idx)
|
||||
else:
|
||||
res = func(data)
|
||||
Q.put([idx, res])
|
||||
Q.put("Done")
|
||||
|
||||
|
||||
def parallel_data_prefetch(
|
||||
func: callable,
|
||||
data,
|
||||
n_proc,
|
||||
target_data_type="ndarray",
|
||||
cpu_intensive=True,
|
||||
use_worker_id=False,
|
||||
):
|
||||
# if target_data_type not in ["ndarray", "list"]:
|
||||
# raise ValueError(
|
||||
# "Data, which is passed to parallel_data_prefetch has to be either of type list or ndarray."
|
||||
# )
|
||||
if isinstance(data, np.ndarray) and target_data_type == "list":
|
||||
raise ValueError("list expected but function got ndarray.")
|
||||
elif isinstance(data, abc.Iterable):
|
||||
if isinstance(data, dict):
|
||||
print(
|
||||
f'WARNING:"data" argument passed to parallel_data_prefetch is a dict: Using only its values and disregarding keys.'
|
||||
)
|
||||
data = list(data.values())
|
||||
if target_data_type == "ndarray":
|
||||
data = np.asarray(data)
|
||||
else:
|
||||
data = list(data)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"The data, that shall be processed parallel has to be either an np.ndarray or an Iterable, but is actually {type(data)}."
|
||||
)
|
||||
|
||||
if cpu_intensive:
|
||||
Q = mp.Queue(1000)
|
||||
proc = mp.Process
|
||||
else:
|
||||
Q = Queue(1000)
|
||||
proc = Thread
|
||||
# spawn processes
|
||||
if target_data_type == "ndarray":
|
||||
arguments = [
|
||||
[func, Q, part, i, use_worker_id]
|
||||
for i, part in enumerate(np.array_split(data, n_proc))
|
||||
]
|
||||
else:
|
||||
step = (
|
||||
int(len(data) / n_proc + 1)
|
||||
if len(data) % n_proc != 0
|
||||
else int(len(data) / n_proc)
|
||||
)
|
||||
arguments = [
|
||||
[func, Q, part, i, use_worker_id]
|
||||
for i, part in enumerate(
|
||||
[data[i : i + step] for i in range(0, len(data), step)]
|
||||
)
|
||||
]
|
||||
processes = []
|
||||
for i in range(n_proc):
|
||||
p = proc(target=_do_parallel_data_prefetch, args=arguments[i])
|
||||
processes += [p]
|
||||
|
||||
# start processes
|
||||
print(f"Start prefetching...")
|
||||
import time
|
||||
|
||||
start = time.time()
|
||||
gather_res = [[] for _ in range(n_proc)]
|
||||
try:
|
||||
for p in processes:
|
||||
p.start()
|
||||
|
||||
k = 0
|
||||
while k < n_proc:
|
||||
# get result
|
||||
res = Q.get()
|
||||
if res == "Done":
|
||||
k += 1
|
||||
else:
|
||||
gather_res[res[0]] = res[1]
|
||||
|
||||
except Exception as e:
|
||||
print("Exception: ", e)
|
||||
for p in processes:
|
||||
p.terminate()
|
||||
|
||||
raise e
|
||||
finally:
|
||||
for p in processes:
|
||||
p.join()
|
||||
print(f"Prefetching complete. [{time.time() - start} sec.]")
|
||||
|
||||
if target_data_type == "ndarray":
|
||||
if not isinstance(gather_res[0], np.ndarray):
|
||||
return np.concatenate([np.asarray(r) for r in gather_res], axis=0)
|
||||
|
||||
# order outputs
|
||||
return np.concatenate(gather_res, axis=0)
|
||||
elif target_data_type == "list":
|
||||
out = []
|
||||
for r in gather_res:
|
||||
out.extend(r)
|
||||
return out
|
||||
else:
|
||||
return gather_res
|
||||
|
||||
def set_seed(seed=None):
|
||||
random.seed(seed)
|
||||
np.random.seed(seed)
|
||||
if seed is not None:
|
||||
torch.manual_seed(seed)
|
||||
torch.cuda.manual_seed_all(seed)
|
||||
|
||||
def add_random_background(image, bg_color=None):
|
||||
bg_color = np.random.rand() * 255 if bg_color is None else bg_color
|
||||
image = np.array(image)
|
||||
rgb, alpha = image[..., :3], image[..., 3:]
|
||||
alpha = alpha.astype(np.float32) / 255.0
|
||||
image_new = rgb * alpha + bg_color * (1 - alpha)
|
||||
return Image.fromarray(image_new.astype(np.uint8))
|
||||
@@ -0,0 +1,64 @@
|
||||
""" Utiliy functions to load pre-trained models more easily """
|
||||
import os
|
||||
import pkg_resources
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
import torch
|
||||
from huggingface_hub import hf_hub_download
|
||||
|
||||
from CRM_T2I_V2.imagedream.ldm.util import instantiate_from_config
|
||||
|
||||
|
||||
PRETRAINED_MODELS = {
|
||||
"sd-v2.1-base-4view-ipmv": {
|
||||
"config": "sd_v2_base_ipmv.yaml",
|
||||
"repo_id": "Peng-Wang/ImageDream",
|
||||
"filename": "sd-v2.1-base-4view-ipmv.pt",
|
||||
},
|
||||
"sd-v2.1-base-4view-ipmv-local": {
|
||||
"config": "sd_v2_base_ipmv_local.yaml",
|
||||
"repo_id": "Peng-Wang/ImageDream",
|
||||
"filename": "sd-v2.1-base-4view-ipmv-local.pt",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_config_file(config_path):
|
||||
cfg_file = pkg_resources.resource_filename(
|
||||
"imagedream", os.path.join("configs", config_path)
|
||||
)
|
||||
if not os.path.exists(cfg_file):
|
||||
raise RuntimeError(f"Config {config_path} not available!")
|
||||
return cfg_file
|
||||
|
||||
|
||||
def build_model(model_name, config_path=None, ckpt_path=None, cache_dir=None):
|
||||
if (config_path is not None) and (ckpt_path is not None):
|
||||
config = OmegaConf.load(config_path)
|
||||
model = instantiate_from_config(config.model)
|
||||
model.load_state_dict(torch.load(ckpt_path, map_location="cpu"), strict=False)
|
||||
return model
|
||||
|
||||
if not model_name in PRETRAINED_MODELS:
|
||||
raise RuntimeError(
|
||||
f"Model name {model_name} is not a pre-trained model. Available models are:\n- "
|
||||
+ "\n- ".join(PRETRAINED_MODELS.keys())
|
||||
)
|
||||
model_info = PRETRAINED_MODELS[model_name]
|
||||
|
||||
# Instiantiate the model
|
||||
print(f"Loading model from config: {model_info['config']}")
|
||||
config_file = get_config_file(model_info["config"])
|
||||
config = OmegaConf.load(config_file)
|
||||
model = instantiate_from_config(config.model)
|
||||
|
||||
# Load pre-trained checkpoint from huggingface
|
||||
if not ckpt_path:
|
||||
ckpt_path = hf_hub_download(
|
||||
repo_id=model_info["repo_id"],
|
||||
filename=model_info["filename"],
|
||||
cache_dir=cache_dir,
|
||||
)
|
||||
print(f"Loading model from cache file: {ckpt_path}")
|
||||
model.load_state_dict(torch.load(ckpt_path, map_location="cpu"), strict=False)
|
||||
return model
|
||||
@@ -0,0 +1,84 @@
|
||||
import numpy as np
|
||||
import cv2
|
||||
import torch
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def instantiate_from_config(config):
|
||||
if not "target" in config:
|
||||
raise KeyError("Expected key `target` to instantiate.")
|
||||
return get_obj_from_str(config["target"])(**config.get("params", dict()))
|
||||
|
||||
|
||||
def get_obj_from_str(string, reload=False):
|
||||
import importlib
|
||||
module, cls = string.rsplit(".", 1)
|
||||
if reload:
|
||||
module_imp = importlib.import_module(module)
|
||||
importlib.reload(module_imp)
|
||||
return getattr(importlib.import_module(module, package=None), cls)
|
||||
|
||||
|
||||
def tensor_detail(t):
|
||||
assert type(t) == torch.Tensor
|
||||
print(f"shape: {t.shape} mean: {t.mean():.2f}, std: {t.std():.2f}, min: {t.min():.2f}, max: {t.max():.2f}")
|
||||
|
||||
|
||||
|
||||
def drawRoundRec(draw, color, x, y, w, h, r):
|
||||
drawObject = draw
|
||||
|
||||
'''Rounds'''
|
||||
drawObject.ellipse((x, y, x + r, y + r), fill=color)
|
||||
drawObject.ellipse((x + w - r, y, x + w, y + r), fill=color)
|
||||
drawObject.ellipse((x, y + h - r, x + r, y + h), fill=color)
|
||||
drawObject.ellipse((x + w - r, y + h - r, x + w, y + h), fill=color)
|
||||
|
||||
'''rec.s'''
|
||||
drawObject.rectangle((x + r / 2, y, x + w - (r / 2), y + h), fill=color)
|
||||
drawObject.rectangle((x, y + r / 2, x + w, y + h - (r / 2)), fill=color)
|
||||
|
||||
|
||||
def do_resize_content(original_image: Image, scale_rate):
|
||||
# resize image content wile retain the original image size
|
||||
if scale_rate != 1:
|
||||
# Calculate the new size after rescaling
|
||||
new_size = tuple(int(dim * scale_rate) for dim in original_image.size)
|
||||
# Resize the image while maintaining the aspect ratio
|
||||
resized_image = original_image.resize(new_size)
|
||||
# Create a new image with the original size and black background
|
||||
padded_image = Image.new("RGBA", original_image.size, (0, 0, 0, 0))
|
||||
paste_position = ((original_image.width - resized_image.width) // 2, (original_image.height - resized_image.height) // 2)
|
||||
padded_image.paste(resized_image, paste_position)
|
||||
return padded_image
|
||||
else:
|
||||
return original_image
|
||||
|
||||
def add_stroke(img, color=(255, 255, 255), stroke_radius=3):
|
||||
# color in R, G, B format
|
||||
if isinstance(img, Image.Image):
|
||||
assert img.mode == "RGBA"
|
||||
img = cv2.cvtColor(np.array(img), cv2.COLOR_RGBA2BGRA)
|
||||
else:
|
||||
assert img.shape[2] == 4
|
||||
gray = img[:,:, 3]
|
||||
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
|
||||
contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
|
||||
res = cv2.drawContours(img, contours,-1, tuple(color)[::-1] + (255,), stroke_radius)
|
||||
return Image.fromarray(cv2.cvtColor(res,cv2.COLOR_BGRA2RGBA))
|
||||
|
||||
def make_blob(image_size=(512, 512), sigma=0.2):
|
||||
"""
|
||||
make 2D blob image with:
|
||||
I(x, y)=1-\exp \left(-\frac{(x-H / 2)^2+(y-W / 2)^2}{2 \sigma^2 HS}\right)
|
||||
"""
|
||||
import numpy as np
|
||||
H, W = image_size
|
||||
x = np.arange(0, W, 1, float)
|
||||
y = np.arange(0, H, 1, float)
|
||||
x, y = np.meshgrid(x, y)
|
||||
x0 = W // 2
|
||||
y0 = H // 2
|
||||
img = 1 - np.exp(-((x - x0) ** 2 + (y - y0) ** 2) / (2 * sigma ** 2 * H * W))
|
||||
return (img * 255).astype(np.uint8)
|
||||
@@ -0,0 +1,433 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
from CRM_T2I_V2.imagedream.camera_utils import get_camera_for_index
|
||||
from CRM_T2I_V2.imagedream.ldm.util import set_seed, add_random_background
|
||||
from CRM_T2I_V2.libs.base_utils import do_resize_content
|
||||
from CRM_T2I_V2.imagedream.ldm.models.diffusion.ddim import DDIMSampler
|
||||
from torchvision import transforms as TF
|
||||
|
||||
|
||||
class ImageDreamDiffusion:
|
||||
def __init__(
|
||||
self,
|
||||
model,
|
||||
device,
|
||||
dtype,
|
||||
mode,
|
||||
num_frames,
|
||||
camera_views,
|
||||
ref_position,
|
||||
random_background=False,
|
||||
offset_noise=False,
|
||||
resize_rate=1,
|
||||
image_size=256,
|
||||
seed=1234,
|
||||
) -> None:
|
||||
assert mode in ["pixel", "local"]
|
||||
size = image_size
|
||||
self.seed = seed
|
||||
batch_size = max(4, num_frames)
|
||||
|
||||
sampler = DDIMSampler(model, device=device)
|
||||
|
||||
# pre-compute camera matrices
|
||||
camera = [get_camera_for_index(i).squeeze() for i in camera_views]
|
||||
camera[ref_position] = torch.zeros_like(camera[ref_position]) # set ref camera to zero
|
||||
camera = torch.stack(camera)
|
||||
camera = camera.repeat(batch_size // num_frames, 1).to(device)
|
||||
|
||||
self.image_transform = TF.Compose(
|
||||
[
|
||||
TF.Resize((size, size)),
|
||||
TF.ToTensor(),
|
||||
TF.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
|
||||
]
|
||||
)
|
||||
self.dtype = dtype
|
||||
self.ref_position = ref_position
|
||||
self.mode = mode
|
||||
self.random_background = random_background
|
||||
self.resize_rate = resize_rate
|
||||
self.num_frames = num_frames
|
||||
self.size = size
|
||||
self.device = device
|
||||
self.batch_size = batch_size
|
||||
self.model = model
|
||||
self.sampler = sampler
|
||||
self.camera = camera
|
||||
self.offset_noise = offset_noise
|
||||
self.pixel_control = (self.mode == "pixel")
|
||||
|
||||
@staticmethod
|
||||
def get_ip_conditions(
|
||||
model,
|
||||
c_,
|
||||
uc_=None,
|
||||
ip=None,
|
||||
num_frames=7,
|
||||
pixel_control=True,
|
||||
transform=None,
|
||||
dtype=torch.float32,
|
||||
device="cuda"
|
||||
):
|
||||
with torch.no_grad(), torch.autocast(device_type=torch.device(device).type, dtype=dtype):
|
||||
if ip is not None:
|
||||
ip_embed = model.get_learned_image_conditioning(ip).to(
|
||||
device
|
||||
) # shape: torch.Size([num_ip, 257, 1280]) mean: 0.06, std: 0.53, min: -6.83, max: 11.12
|
||||
ip_ = ip_embed.repeat_interleave(num_frames, dim=0)
|
||||
c_["ip"] = ip_
|
||||
if uc_ is not None:
|
||||
uc_["ip"] = torch.zeros_like(ip_)
|
||||
|
||||
if pixel_control:
|
||||
ip = torch.stack([transform(img) for img in ip]).to(
|
||||
device
|
||||
) # shape: torch.Size([num_ip, 3, 256, 256]) mean: 0.33, std: 0.37, min: -1.00, max: 1.00
|
||||
ip_img = model.get_first_stage_encoding(
|
||||
model.encode_first_stage(ip)
|
||||
) # shape: torch.Size([num_ip, 4, 32, 32]) mean: 0.23, std: 0.77, min: -4.42, max: 3.55
|
||||
c_["ip_img"] = ip_img
|
||||
if uc_ is not None:
|
||||
uc_["ip_img"] = torch.zeros_like(ip_img)
|
||||
|
||||
return c_, uc_
|
||||
|
||||
@staticmethod
|
||||
def get_base_conditions(
|
||||
model,
|
||||
prompt,
|
||||
negative_prompt,
|
||||
batch_size=7,
|
||||
num_ip=1,
|
||||
num_frames=7,
|
||||
dtype=torch.float32,
|
||||
device="cuda",
|
||||
camera=None
|
||||
):
|
||||
"""
|
||||
Get all condition and uncondition embeddings/parameters
|
||||
|
||||
Reture conditions and uncondition dictionary
|
||||
"""
|
||||
|
||||
if type(prompt) != list:
|
||||
prompt = [prompt]
|
||||
if type(negative_prompt) != list:
|
||||
negative_prompt = [negative_prompt]
|
||||
|
||||
with torch.no_grad(), torch.autocast(device_type=torch.device(device).type, dtype=dtype):
|
||||
c = model.get_learned_conditioning(prompt).to(
|
||||
device
|
||||
) # shape: torch.Size([1, 77, 1024]) mean: -0.17, std: 1.02, min: -7.50, max: 13.05
|
||||
uc = model.get_learned_conditioning(negative_prompt).to(
|
||||
device
|
||||
)
|
||||
c_ = {"context": c.repeat(batch_size, 1, 1)} # batch_size
|
||||
uc_ = {"context": uc.repeat(batch_size, 1, 1)}
|
||||
|
||||
if camera is not None:
|
||||
c_["camera"] = uc_["camera"] = (
|
||||
camera.repeat(num_ip, 1) # shape: torch.Size([5, 16]) mean: 0.11, std: 0.49, min: -1.00, max: 1.00
|
||||
)
|
||||
c_["num_frames"] = uc_["num_frames"] = num_frames
|
||||
|
||||
return c_, uc_
|
||||
|
||||
@staticmethod
|
||||
def i2i(
|
||||
model,
|
||||
image_size,
|
||||
prompt,
|
||||
negative_prompt,
|
||||
sampler,
|
||||
ip=None,
|
||||
step=20,
|
||||
scale=5.0,
|
||||
batch_size=8,
|
||||
ddim_eta=0.0,
|
||||
dtype=torch.float32,
|
||||
device="cuda",
|
||||
camera=None,
|
||||
num_frames=4,
|
||||
pixel_control=False,
|
||||
transform=None,
|
||||
offset_noise=False,
|
||||
additional_residuals=None
|
||||
):
|
||||
""" The function supports additional image prompt.
|
||||
Args:
|
||||
model (_type_): the image dream model
|
||||
image_size (_type_): size of diffusion output (standard 256)
|
||||
prompt (_type_): text prompt for the image (prompt in type str)
|
||||
negative_prompt (_type_): negative text prompt (prompt in type str)
|
||||
sampler (_type_): imagedream.ldm.models.diffusion.ddim.DDIMSampler
|
||||
ip (Image, optional): the image prompt. Defaults to None.
|
||||
step (int, optional): _description_. Defaults to 20.
|
||||
scale (float, optional): _description_. Defaults to 7.5.
|
||||
batch_size (int, optional): _description_. Defaults to 8.
|
||||
ddim_eta (float, optional): _description_. Defaults to 0.0.
|
||||
dtype (_type_, optional): _description_. Defaults to torch.float32.
|
||||
device (str, optional): _description_. Defaults to "cuda".
|
||||
camera (_type_, optional): camera info in tensor, shape: torch.Size([5, 16]) mean: 0.11, std: 0.49, min: -1.00, max: 1.00
|
||||
num_frames (int, optional): _num of frames (views) to generate
|
||||
pixel_control: whether to use pixel conditioning. Defaults to False, True when using pixel mode
|
||||
transform: Compose(
|
||||
Resize(size=(256, 256), interpolation=bilinear, max_size=None, antialias=warn)
|
||||
ToTensor()
|
||||
Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))
|
||||
)
|
||||
"""
|
||||
if type(ip) != list:
|
||||
ip = [ip]
|
||||
|
||||
num_ip = len(ip)
|
||||
batch_size *= num_ip
|
||||
|
||||
c_, uc_ = ImageDreamDiffusion.get_base_conditions(model, prompt, negative_prompt, batch_size, num_ip, num_frames, dtype, device, camera)
|
||||
c_, uc_ = ImageDreamDiffusion.get_ip_conditions(model, c_, uc_, ip, num_frames, pixel_control, transform, dtype, device)
|
||||
|
||||
with torch.no_grad(), torch.autocast(device_type=torch.device(device).type, dtype=dtype):
|
||||
|
||||
shape = [4, image_size // 8, image_size // 8] # [4, 32, 32]
|
||||
if offset_noise:
|
||||
ip_img = c_["ip_img"]
|
||||
ref_mean = ip_img.mean(dim=(-1, -2), keepdim=True).repeat_interleave(num_frames, dim=0)
|
||||
time_steps = torch.randint(model.num_timesteps - 1, model.num_timesteps, (batch_size,), device=device)
|
||||
x_T = model.q_sample(torch.ones([batch_size] + shape, device=device) * ref_mean, time_steps)
|
||||
|
||||
kwargs = {}
|
||||
kwargs["additional_residuals"] = additional_residuals
|
||||
samples_ddim, _ = (
|
||||
sampler.sample( # shape: torch.Size([5, 4, 32, 32]) mean: 0.29, std: 0.85, min: -3.38, max: 4.43
|
||||
S=step,
|
||||
conditioning=c_,
|
||||
batch_size=batch_size,
|
||||
shape=shape,
|
||||
verbose=False,
|
||||
unconditional_guidance_scale=scale,
|
||||
unconditional_conditioning=uc_,
|
||||
eta=ddim_eta,
|
||||
x_T=x_T if offset_noise else None,
|
||||
**kwargs
|
||||
)
|
||||
)
|
||||
|
||||
x_sample = model.decode_first_stage(samples_ddim)
|
||||
x_sample = torch.clamp((x_sample + 1.0) / 2.0, min=0.0, max=1.0)
|
||||
x_sample = x_sample.permute(0, 2, 3, 1) # (N, H, W, 3) in [0, 1]
|
||||
|
||||
return x_sample
|
||||
|
||||
def diffuse(self, t, negative_prompt, ip, n_test=2):
|
||||
set_seed(self.seed)
|
||||
ip = do_resize_content(ip, self.resize_rate)
|
||||
if self.random_background:
|
||||
ip = add_random_background(ip)
|
||||
|
||||
images = []
|
||||
for _ in range(n_test):
|
||||
img = self.i2i(
|
||||
self.model,
|
||||
self.size,
|
||||
t,
|
||||
negative_prompt,
|
||||
self.sampler,
|
||||
ip=ip,
|
||||
step=50,
|
||||
scale=5,
|
||||
batch_size=self.batch_size,
|
||||
ddim_eta=0.0,
|
||||
dtype=self.dtype,
|
||||
device=self.device,
|
||||
camera=self.camera,
|
||||
num_frames=self.num_frames,
|
||||
pixel_control=self.pixel_control,
|
||||
transform=self.image_transform,
|
||||
offset_noise=self.offset_noise,
|
||||
)
|
||||
img = np.concatenate(img, 1)
|
||||
img = np.concatenate((img, ip.resize((self.size, self.size))), axis=1)
|
||||
images.append(img)
|
||||
set_seed() # unset random and numpy seed
|
||||
return images
|
||||
|
||||
|
||||
class ImageDreamDiffusionStage2:
|
||||
def __init__(
|
||||
self,
|
||||
model,
|
||||
device,
|
||||
dtype,
|
||||
num_frames,
|
||||
camera_views,
|
||||
ref_position,
|
||||
random_background=False,
|
||||
offset_noise=False,
|
||||
resize_rate=1,
|
||||
mode="pixel",
|
||||
image_size=256,
|
||||
seed=1234,
|
||||
) -> None:
|
||||
assert mode in ["pixel", "local"]
|
||||
|
||||
size = image_size
|
||||
self.seed = seed
|
||||
batch_size = max(4, num_frames)
|
||||
|
||||
neg_texts = "uniform low no texture ugly, boring, bad anatomy, blurry, pixelated, obscure, unnatural colors, poor lighting, dull, and unclear."
|
||||
uc = model.get_learned_conditioning([neg_texts]).to(device)
|
||||
sampler = DDIMSampler(model)
|
||||
|
||||
# pre-compute camera matrices
|
||||
camera = [get_camera_for_index(i).squeeze() for i in camera_views]
|
||||
if ref_position is not None:
|
||||
camera[ref_position] = torch.zeros_like(camera[ref_position]) # set ref camera to zero
|
||||
camera = torch.stack(camera)
|
||||
camera = camera.repeat(batch_size // num_frames, 1).to(device)
|
||||
|
||||
self.image_transform = TF.Compose(
|
||||
[
|
||||
TF.Resize((size, size)),
|
||||
TF.ToTensor(),
|
||||
TF.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
|
||||
]
|
||||
)
|
||||
|
||||
self.dtype = dtype
|
||||
self.mode = mode
|
||||
self.ref_position = ref_position
|
||||
self.random_background = random_background
|
||||
self.resize_rate = resize_rate
|
||||
self.num_frames = num_frames
|
||||
self.size = size
|
||||
self.device = device
|
||||
self.batch_size = batch_size
|
||||
self.model = model
|
||||
self.sampler = sampler
|
||||
self.uc = uc
|
||||
self.camera = camera
|
||||
self.offset_noise = offset_noise
|
||||
|
||||
@staticmethod
|
||||
def i2iStage2(
|
||||
model,
|
||||
image_size,
|
||||
prompt,
|
||||
uc,
|
||||
sampler,
|
||||
pixel_images,
|
||||
ip=None,
|
||||
step=20,
|
||||
scale=5.0,
|
||||
batch_size=8,
|
||||
ddim_eta=0.0,
|
||||
dtype=torch.float32,
|
||||
device="cuda",
|
||||
camera=None,
|
||||
num_frames=4,
|
||||
pixel_control=False,
|
||||
transform=None,
|
||||
offset_noise=False,
|
||||
):
|
||||
ip_raw = ip
|
||||
if type(prompt) != list:
|
||||
prompt = [prompt]
|
||||
with torch.no_grad(), torch.autocast(device_type=torch.device(device).type, dtype=dtype):
|
||||
c = model.get_learned_conditioning(prompt).to(
|
||||
device
|
||||
) # shape: torch.Size([1, 77, 1024]) mean: -0.17, std: 1.02, min: -7.50, max: 13.05
|
||||
c_ = {"context": c.repeat(batch_size, 1, 1)} # batch_size
|
||||
uc_ = {"context": uc.repeat(batch_size, 1, 1)}
|
||||
|
||||
if camera is not None:
|
||||
c_["camera"] = uc_["camera"] = (
|
||||
camera # shape: torch.Size([5, 16]) mean: 0.11, std: 0.49, min: -1.00, max: 1.00
|
||||
)
|
||||
c_["num_frames"] = uc_["num_frames"] = num_frames
|
||||
|
||||
if ip is not None:
|
||||
ip_embed = model.get_learned_image_conditioning(ip).to(
|
||||
device
|
||||
) # shape: torch.Size([1, 257, 1280]) mean: 0.06, std: 0.53, min: -6.83, max: 11.12
|
||||
ip_ = ip_embed.repeat(batch_size, 1, 1)
|
||||
c_["ip"] = ip_
|
||||
uc_["ip"] = torch.zeros_like(ip_)
|
||||
|
||||
if pixel_control:
|
||||
assert camera is not None
|
||||
|
||||
transed_pixel_images = torch.stack([transform(i).to(device) for i in pixel_images])
|
||||
latent_pixel_images = model.get_first_stage_encoding(model.encode_first_stage(transed_pixel_images))
|
||||
|
||||
c_["pixel_images"] = latent_pixel_images
|
||||
uc_["pixel_images"] = torch.zeros_like(latent_pixel_images)
|
||||
|
||||
shape = [4, image_size // 8, image_size // 8] # [4, 32, 32]
|
||||
if offset_noise:
|
||||
ref = transform(ip_raw).to(device)
|
||||
ref_latent = model.get_first_stage_encoding(model.encode_first_stage(ref[None, :, :, :]))
|
||||
ref_mean = ref_latent.mean(dim=(-1, -2), keepdim=True)
|
||||
time_steps = torch.randint(model.num_timesteps - 1, model.num_timesteps, (batch_size,), device=device)
|
||||
x_T = model.q_sample(torch.ones([batch_size] + shape, device=device) * ref_mean, time_steps)
|
||||
|
||||
samples_ddim, _ = (
|
||||
sampler.sample( # shape: torch.Size([5, 4, 32, 32]) mean: 0.29, std: 0.85, min: -3.38, max: 4.43
|
||||
S=step,
|
||||
conditioning=c_,
|
||||
batch_size=batch_size,
|
||||
shape=shape,
|
||||
verbose=False,
|
||||
unconditional_guidance_scale=scale,
|
||||
unconditional_conditioning=uc_,
|
||||
eta=ddim_eta,
|
||||
x_T=x_T if offset_noise else None,
|
||||
)
|
||||
)
|
||||
x_sample = model.decode_first_stage(samples_ddim)
|
||||
x_sample = torch.clamp((x_sample + 1.0) / 2.0, min=0.0, max=1.0)
|
||||
x_sample = 255.0 * x_sample.permute(0, 2, 3, 1).cpu().numpy()
|
||||
|
||||
return list(x_sample.astype(np.uint8))
|
||||
|
||||
@torch.no_grad()
|
||||
def diffuse(self, t, ip, pixel_images, n_test=2):
|
||||
set_seed(self.seed)
|
||||
ip = do_resize_content(ip, self.resize_rate)
|
||||
pixel_images = [do_resize_content(i, self.resize_rate) for i in pixel_images]
|
||||
|
||||
if self.random_background:
|
||||
bg_color = np.random.rand() * 255
|
||||
ip = add_random_background(ip, bg_color)
|
||||
pixel_images = [add_random_background(i, bg_color) for i in pixel_images]
|
||||
|
||||
images = []
|
||||
for _ in range(n_test):
|
||||
img = self.i2iStage2(
|
||||
self.model,
|
||||
self.size,
|
||||
t,
|
||||
self.uc,
|
||||
self.sampler,
|
||||
pixel_images=pixel_images,
|
||||
ip=ip,
|
||||
step=50,
|
||||
scale=5,
|
||||
batch_size=self.batch_size,
|
||||
ddim_eta=0.0,
|
||||
dtype=self.dtype,
|
||||
device=self.device,
|
||||
camera=self.camera,
|
||||
num_frames=self.num_frames,
|
||||
pixel_control=(self.mode == "pixel"),
|
||||
transform=self.image_transform,
|
||||
offset_noise=self.offset_noise,
|
||||
)
|
||||
img = np.concatenate(img, 1)
|
||||
img = np.concatenate(
|
||||
(img, ip.resize((self.size, self.size)), *[i.resize((self.size, self.size)) for i in pixel_images]),
|
||||
axis=1,
|
||||
)
|
||||
images.append(img)
|
||||
set_seed() # unset random and numpy seed
|
||||
return images
|
||||
@@ -0,0 +1 @@
|
||||
from CRM_T2I_V2.model.crm.model import ConvolutionalReconstructionModel
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
class TetTexNet(nn.Module):
|
||||
def __init__(self, plane_reso=64, padding=0.1, fea_concat=True):
|
||||
super().__init__()
|
||||
# self.c_dim = c_dim
|
||||
self.plane_reso = plane_reso
|
||||
self.padding = padding
|
||||
self.fea_concat = fea_concat
|
||||
|
||||
def forward(self, rolled_out_feature, query):
|
||||
# rolled_out_feature: rolled-out triplane feature
|
||||
# query: queried xyz coordinates (should be scaled consistently to ptr cloud)
|
||||
|
||||
plane_reso = self.plane_reso
|
||||
|
||||
triplane_feature = dict()
|
||||
triplane_feature['xy'] = rolled_out_feature[:, :, :, 0: plane_reso]
|
||||
triplane_feature['yz'] = rolled_out_feature[:, :, :, plane_reso: 2 * plane_reso]
|
||||
triplane_feature['zx'] = rolled_out_feature[:, :, :, 2 * plane_reso:]
|
||||
|
||||
query_feature_xy = self.sample_plane_feature(query, triplane_feature['xy'], 'xy')
|
||||
query_feature_yz = self.sample_plane_feature(query, triplane_feature['yz'], 'yz')
|
||||
query_feature_zx = self.sample_plane_feature(query, triplane_feature['zx'], 'zx')
|
||||
|
||||
if self.fea_concat:
|
||||
query_feature = torch.cat((query_feature_xy, query_feature_yz, query_feature_zx), dim=1)
|
||||
else:
|
||||
query_feature = query_feature_xy + query_feature_yz + query_feature_zx
|
||||
|
||||
output = query_feature.permute(0, 2, 1)
|
||||
|
||||
return output
|
||||
|
||||
# uses values from plane_feature and pixel locations from vgrid to interpolate feature
|
||||
def sample_plane_feature(self, query, plane_feature, plane):
|
||||
# CYF note:
|
||||
# for pretraining, query are uniformly sampled positions w.i. [-scale, scale]
|
||||
# for training, query are essentially tetrahedra grid vertices, which are
|
||||
# also within [-scale, scale] in the current version!
|
||||
# xy range [-scale, scale]
|
||||
if plane == 'xy':
|
||||
xy = query[:, :, [0, 1]]
|
||||
elif plane == 'yz':
|
||||
xy = query[:, :, [1, 2]]
|
||||
elif plane == 'zx':
|
||||
xy = query[:, :, [2, 0]]
|
||||
else:
|
||||
raise ValueError("Error! Invalid plane type!")
|
||||
|
||||
xy = xy[:, :, None].float()
|
||||
# not seem necessary to rescale the grid, because from
|
||||
# https://pytorch.org/docs/stable/generated/torch.nn.functional.grid_sample.html,
|
||||
# it specifies sampling locations normalized by plane_feature's spatial dimension,
|
||||
# which is within [-scale, scale] as specified by encoder's calling of coordinate2index()
|
||||
vgrid = 1.0 * xy
|
||||
sampled_feat = F.grid_sample(plane_feature, vgrid, padding_mode='border', align_corners=True, mode='bilinear').squeeze(-1)
|
||||
|
||||
return sampled_feat
|
||||
@@ -0,0 +1,40 @@
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
class SdfMlp(nn.Module):
|
||||
def __init__(self, input_dim, hidden_dim=512, bias=True):
|
||||
super().__init__()
|
||||
self.input_dim = input_dim
|
||||
self.hidden_dim = hidden_dim
|
||||
|
||||
self.fc1 = nn.Linear(input_dim, hidden_dim, bias=bias)
|
||||
self.fc2 = nn.Linear(hidden_dim, hidden_dim, bias=bias)
|
||||
self.fc3 = nn.Linear(hidden_dim, 4, bias=bias)
|
||||
|
||||
|
||||
def forward(self, input):
|
||||
x = F.relu(self.fc1(input))
|
||||
x = F.relu(self.fc2(x))
|
||||
out = self.fc3(x)
|
||||
return out
|
||||
|
||||
|
||||
class RgbMlp(nn.Module):
|
||||
def __init__(self, input_dim, hidden_dim=512, bias=True):
|
||||
super().__init__()
|
||||
self.input_dim = input_dim
|
||||
self.hidden_dim = hidden_dim
|
||||
|
||||
self.fc1 = nn.Linear(input_dim, hidden_dim, bias=bias)
|
||||
self.fc2 = nn.Linear(hidden_dim, hidden_dim, bias=bias)
|
||||
self.fc3 = nn.Linear(hidden_dim, 3, bias=bias)
|
||||
|
||||
def forward(self, input):
|
||||
x = F.relu(self.fc1(input))
|
||||
x = F.relu(self.fc2(x))
|
||||
out = self.fc3(x)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
'''
|
||||
Codes are from:
|
||||
https://github.com/jaxony/unet-pytorch/blob/master/model.py
|
||||
'''
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from diffusers import UNet2DModel
|
||||
import einops
|
||||
class UNetPP(nn.Module):
|
||||
'''
|
||||
Wrapper for UNet in diffusers
|
||||
'''
|
||||
def __init__(self, in_channels):
|
||||
super(UNetPP, self).__init__()
|
||||
self.in_channels = in_channels
|
||||
self.unet = UNet2DModel(
|
||||
sample_size=[256, 256*3],
|
||||
in_channels=in_channels,
|
||||
out_channels=32,
|
||||
layers_per_block=2,
|
||||
block_out_channels=(64, 128, 128, 128*2, 128*2, 128*4, 128*4),
|
||||
down_block_types=(
|
||||
"DownBlock2D",
|
||||
"DownBlock2D",
|
||||
"DownBlock2D",
|
||||
"AttnDownBlock2D",
|
||||
"AttnDownBlock2D",
|
||||
"AttnDownBlock2D",
|
||||
"DownBlock2D",
|
||||
),
|
||||
up_block_types=(
|
||||
"UpBlock2D",
|
||||
"AttnUpBlock2D",
|
||||
"AttnUpBlock2D",
|
||||
"AttnUpBlock2D",
|
||||
"UpBlock2D",
|
||||
"UpBlock2D",
|
||||
"UpBlock2D",
|
||||
),
|
||||
)
|
||||
|
||||
self.unet.enable_xformers_memory_efficient_attention()
|
||||
if in_channels > 12:
|
||||
self.learned_plane = torch.nn.parameter.Parameter(torch.zeros([1,in_channels-12,256,256*3]))
|
||||
|
||||
def forward(self, x, t=256):
|
||||
learned_plane = self.learned_plane
|
||||
if x.shape[1] < self.in_channels:
|
||||
learned_plane = einops.repeat(learned_plane, '1 C H W -> B C H W', B=x.shape[0]).to(x.device)
|
||||
x = torch.cat([x, learned_plane], dim = 1)
|
||||
return self.unet(x, t).sample
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
import torch.nn as nn
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
import cv2
|
||||
import trimesh
|
||||
import nvdiffrast.torch as dr
|
||||
|
||||
from CRM_T2I_V2.model.archs.decoders.shape_texture_net import TetTexNet
|
||||
from CRM_T2I_V2.model.archs.unet import UNetPP
|
||||
from CRM_T2I_V2.util.renderer import Renderer
|
||||
from CRM_T2I_V2.model.archs.mlp_head import SdfMlp, RgbMlp
|
||||
import xatlas
|
||||
|
||||
|
||||
class Dummy:
|
||||
pass
|
||||
|
||||
class ConvolutionalReconstructionModel(nn.Module):
|
||||
def __init__(self, specs):
|
||||
super(ConvolutionalReconstructionModel, self).__init__()
|
||||
|
||||
self.specs = specs
|
||||
# configs
|
||||
input_specs = specs["Input"]
|
||||
self.input = Dummy()
|
||||
self.input.scale = input_specs['scale']
|
||||
self.input.resolution = input_specs['resolution']
|
||||
self.tet_grid_size = input_specs['tet_grid_size']
|
||||
self.camera_angle_num = input_specs['camera_angle_num']
|
||||
|
||||
self.arch = Dummy()
|
||||
self.arch.fea_concat = specs["ArchSpecs"]["fea_concat"]
|
||||
self.arch.mlp_bias = specs["ArchSpecs"]["mlp_bias"]
|
||||
|
||||
self.dec = Dummy()
|
||||
self.dec.c_dim = specs["DecoderSpecs"]["c_dim"]
|
||||
self.dec.plane_resolution = specs["DecoderSpecs"]["plane_resolution"]
|
||||
|
||||
self.geo_type = specs["Train"].get("geo_type", "flex") # "dmtet" or "flex"
|
||||
|
||||
self.unet2 = UNetPP(in_channels=self.dec.c_dim)
|
||||
|
||||
mlp_chnl_s = 3 if self.arch.fea_concat else 1 # 3 for queried triplane feature concatenation
|
||||
self.decoder = TetTexNet(plane_reso=self.dec.plane_resolution, fea_concat=self.arch.fea_concat)
|
||||
|
||||
if self.geo_type == "flex":
|
||||
self.weightMlp = nn.Sequential(
|
||||
nn.Linear(mlp_chnl_s * 32 * 8, 512),
|
||||
nn.SiLU(),
|
||||
nn.Linear(512, 21))
|
||||
|
||||
self.sdfMlp = SdfMlp(mlp_chnl_s * 32, 512, bias=self.arch.mlp_bias)
|
||||
self.rgbMlp = RgbMlp(mlp_chnl_s * 32, 512, bias=self.arch.mlp_bias)
|
||||
self.renderer = Renderer(tet_grid_size=self.tet_grid_size, camera_angle_num=self.camera_angle_num,
|
||||
scale=self.input.scale, geo_type = self.geo_type)
|
||||
|
||||
|
||||
self.spob = True if specs['Pretrain']['mode'] is None else False # whether to add sphere
|
||||
self.radius = specs['Pretrain']['radius'] # used when spob
|
||||
|
||||
self.denoising = True
|
||||
from diffusers import DDIMScheduler
|
||||
self.scheduler = DDIMScheduler.from_pretrained("stabilityai/stable-diffusion-2-1-base", subfolder="scheduler")
|
||||
|
||||
def decode(self, data, triplane_feature2):
|
||||
if self.geo_type == "flex":
|
||||
tet_verts = self.renderer.flexicubes.verts.unsqueeze(0)
|
||||
tet_indices = self.renderer.flexicubes.indices
|
||||
|
||||
dec_verts = self.decoder(triplane_feature2, tet_verts)
|
||||
out = self.sdfMlp(dec_verts)
|
||||
|
||||
weight = None
|
||||
if self.geo_type == "flex":
|
||||
grid_feat = torch.index_select(input=dec_verts, index=self.renderer.flexicubes.indices.reshape(-1),dim=1)
|
||||
grid_feat = grid_feat.reshape(dec_verts.shape[0], self.renderer.flexicubes.indices.shape[0], self.renderer.flexicubes.indices.shape[1] * dec_verts.shape[-1])
|
||||
weight = self.weightMlp(grid_feat)
|
||||
weight = weight * 0.1
|
||||
|
||||
pred_sdf, deformation = out[..., 0], out[..., 1:]
|
||||
if self.spob:
|
||||
pred_sdf = pred_sdf + self.radius - torch.sqrt((tet_verts**2).sum(-1))
|
||||
|
||||
_, verts, faces = self.renderer(data, pred_sdf, deformation, tet_verts, tet_indices, weight= weight)
|
||||
return verts[0].unsqueeze(0), faces[0].int()
|
||||
|
||||
def export_mesh(self, data, out_dir, ind, device=None, tri_fea_2 = None):
|
||||
verts = data['verts']
|
||||
faces = data['faces']
|
||||
|
||||
dec_verts = self.decoder(tri_fea_2, verts.unsqueeze(0))
|
||||
colors = self.rgbMlp(dec_verts).squeeze().detach().cpu().numpy()
|
||||
# Expect predicted colors value range from [-1, 1]
|
||||
colors = (colors * 0.5 + 0.5).clip(0, 1)
|
||||
|
||||
verts = verts.squeeze().cpu().numpy()
|
||||
faces = faces[..., [2, 1, 0]].squeeze().cpu().numpy()
|
||||
|
||||
# export the final mesh
|
||||
with torch.no_grad():
|
||||
mesh = trimesh.Trimesh(verts, faces, vertex_colors=colors, process=False) # important, process=True leads to seg fault...
|
||||
mesh.export(out_dir / f'{ind}.obj')
|
||||
|
||||
def export_mesh_wt_uv(self, ctx, data, out_dir, ind, device, res, tri_fea_2=None):
|
||||
|
||||
mesh_v = data['verts'].squeeze().cpu().numpy()
|
||||
mesh_pos_idx = data['faces'].squeeze().cpu().numpy()
|
||||
|
||||
def interpolate(attr, rast, attr_idx, rast_db=None):
|
||||
return dr.interpolate(attr.contiguous(), rast, attr_idx, rast_db=rast_db,
|
||||
diff_attrs=None if rast_db is None else 'all')
|
||||
|
||||
vmapping, indices, uvs = xatlas.parametrize(mesh_v, mesh_pos_idx)
|
||||
|
||||
mesh_v = torch.tensor(mesh_v, dtype=torch.float32, device=device)
|
||||
mesh_pos_idx = torch.tensor(mesh_pos_idx, dtype=torch.int64, device=device)
|
||||
|
||||
# Convert to tensors
|
||||
indices_int64 = indices.astype(np.uint64, casting='same_kind').view(np.int64)
|
||||
|
||||
uvs = torch.tensor(uvs, dtype=torch.float32, device=mesh_v.device)
|
||||
mesh_tex_idx = torch.tensor(indices_int64, dtype=torch.int64, device=mesh_v.device)
|
||||
# mesh_v_tex. ture
|
||||
uv_clip = uvs[None, ...] * 2.0 - 1.0
|
||||
|
||||
# pad to four component coordinate
|
||||
uv_clip4 = torch.cat((uv_clip, torch.zeros_like(uv_clip[..., 0:1]), torch.ones_like(uv_clip[..., 0:1])), dim=-1)
|
||||
|
||||
# rasterize
|
||||
rast, _ = dr.rasterize(ctx, uv_clip4, mesh_tex_idx.int(), res)
|
||||
|
||||
# Interpolate world space position
|
||||
gb_pos, _ = interpolate(mesh_v[None, ...], rast, mesh_pos_idx.int())
|
||||
mask = rast[..., 3:4] > 0
|
||||
|
||||
# return uvs, mesh_tex_idx, gb_pos, mask
|
||||
gb_pos_unsqz = gb_pos.view(-1, 3)
|
||||
mask_unsqz = mask.view(-1)
|
||||
tex_unsqz = torch.zeros_like(gb_pos_unsqz) + 1
|
||||
|
||||
gb_mask_pos = gb_pos_unsqz[mask_unsqz]
|
||||
|
||||
gb_mask_pos = gb_mask_pos[None, ]
|
||||
|
||||
with torch.no_grad():
|
||||
|
||||
dec_verts = self.decoder(tri_fea_2, gb_mask_pos)
|
||||
colors = self.rgbMlp(dec_verts).squeeze()
|
||||
|
||||
# Expect predicted colors value range from [-1, 1]
|
||||
lo, hi = (-1, 1)
|
||||
colors = (colors - lo) * (255 / (hi - lo))
|
||||
colors = colors.clip(0, 255)
|
||||
|
||||
tex_unsqz[mask_unsqz] = colors
|
||||
|
||||
tex = tex_unsqz.view(res + (3,))
|
||||
|
||||
verts = mesh_v.squeeze().cpu().numpy()
|
||||
faces = mesh_pos_idx[..., [2, 1, 0]].squeeze().cpu().numpy()
|
||||
# faces = mesh_pos_idx
|
||||
# faces = faces.detach().cpu().numpy()
|
||||
# faces = faces[..., [2, 1, 0]]
|
||||
indices = indices[..., [2, 1, 0]]
|
||||
|
||||
# xatlas.export(f"{out_dir}/{ind}.obj", verts[vmapping], indices, uvs)
|
||||
matname = f'{out_dir}.mtl'
|
||||
# matname = f'{out_dir}/{ind}.mtl'
|
||||
fid = open(matname, 'w')
|
||||
fid.write('newmtl material_0\n')
|
||||
fid.write('Kd 1 1 1\n')
|
||||
fid.write('Ka 1 1 1\n')
|
||||
# fid.write('Ks 0 0 0\n')
|
||||
fid.write('Ks 0.4 0.4 0.4\n')
|
||||
fid.write('Ns 10\n')
|
||||
fid.write('illum 2\n')
|
||||
fid.write(f'map_Kd {out_dir.split("/")[-1]}.png\n')
|
||||
fid.close()
|
||||
|
||||
fid = open(f'{out_dir}.obj', 'w')
|
||||
# fid = open(f'{out_dir}/{ind}.obj', 'w')
|
||||
fid.write('mtllib %s.mtl\n' % out_dir.split("/")[-1])
|
||||
|
||||
for pidx, p in enumerate(verts):
|
||||
pp = p
|
||||
fid.write('v %f %f %f\n' % (pp[0], pp[2], - pp[1]))
|
||||
|
||||
for pidx, p in enumerate(uvs):
|
||||
pp = p
|
||||
fid.write('vt %f %f\n' % (pp[0], 1 - pp[1]))
|
||||
|
||||
fid.write('usemtl material_0\n')
|
||||
for i, f in enumerate(faces):
|
||||
f1 = f + 1
|
||||
f2 = indices[i] + 1
|
||||
fid.write('f %d/%d %d/%d %d/%d\n' % (f1[0], f2[0], f1[1], f2[1], f1[2], f2[2]))
|
||||
fid.close()
|
||||
|
||||
img = np.asarray(tex.data.cpu().numpy(), dtype=np.float32)
|
||||
mask = np.sum(img.astype(float), axis=-1, keepdims=True)
|
||||
mask = (mask <= 3.0).astype(float)
|
||||
kernel = np.ones((3, 3), 'uint8')
|
||||
dilate_img = cv2.dilate(img, kernel, iterations=1)
|
||||
img = img * (1 - mask) + dilate_img * mask
|
||||
img = img.clip(0, 255).astype(np.uint8)
|
||||
|
||||
cv2.imwrite(f'{out_dir}.png', img[..., [2, 1, 0]])
|
||||
# cv2.imwrite(f'{out_dir}/{ind}.png', img[..., [2, 1, 0]])
|
||||
@@ -0,0 +1,188 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
import time
|
||||
import nvdiffrast.torch as dr
|
||||
from PIL import Image
|
||||
from typing import List, Union
|
||||
|
||||
from CRM_T2I_V2.util.utils import get_tri
|
||||
|
||||
class CRMSamplerV2:
|
||||
@classmethod
|
||||
def process_pixel_img(
|
||||
cls,
|
||||
pixel_img: Image.Image,
|
||||
bg_color = "#7F7F7F"
|
||||
):
|
||||
# expand image to 1:1 squared and add fixed background color
|
||||
width, height = pixel_img.size
|
||||
new_size = (max(width, height), max(width, height))
|
||||
new_image = Image.new("RGBA", new_size, bg_color)
|
||||
paste_position = ((new_size[0] - width) // 2, (new_size[1] - height) // 2)
|
||||
new_image.paste(pixel_img, paste_position)
|
||||
|
||||
# given an RGBA image fixed background color, alpha channel is used as mask to add background color
|
||||
background = Image.new("RGBA", new_image.size, bg_color)
|
||||
return Image.alpha_composite(background, new_image).convert("RGB")
|
||||
|
||||
@classmethod
|
||||
def stage1_sample(
|
||||
cls,
|
||||
stage1_sampler,
|
||||
pixel_img: Union[Image.Image, List[Image.Image]],
|
||||
prompt="3D assets",
|
||||
neg_texts="uniform low no texture ugly, boring, bad anatomy, blurry, pixelated, obscure, unnatural colors, poor lighting, dull, and unclear.",
|
||||
seed=0,
|
||||
scale=5,
|
||||
step=50,
|
||||
ddim_eta=0.0,
|
||||
additional_residuals=None
|
||||
):
|
||||
"""
|
||||
The first stage was condition on single pixel image, gererate multi-view pixel image, based on the v2pp config
|
||||
"""
|
||||
|
||||
stage1_sampler.seed = seed
|
||||
|
||||
stage1_images = stage1_sampler.i2i(
|
||||
stage1_sampler.model,
|
||||
stage1_sampler.size,
|
||||
prompt,
|
||||
neg_texts,
|
||||
sampler=stage1_sampler.sampler,
|
||||
ip=pixel_img,
|
||||
step=step,
|
||||
scale=scale,
|
||||
batch_size=stage1_sampler.batch_size,
|
||||
ddim_eta=ddim_eta,
|
||||
dtype=stage1_sampler.dtype,
|
||||
device=stage1_sampler.device,
|
||||
camera=stage1_sampler.camera,
|
||||
num_frames=stage1_sampler.num_frames,
|
||||
pixel_control=(stage1_sampler.mode == "pixel"),
|
||||
transform=stage1_sampler.image_transform,
|
||||
offset_noise=stage1_sampler.offset_noise,
|
||||
additional_residuals=additional_residuals
|
||||
)
|
||||
|
||||
# remove reference views
|
||||
mask = torch.arange(1, stage1_images.shape[0]+1, 1) % 7 != 0
|
||||
return stage1_images[mask] # (N, H, W, 3) in [0, 255]
|
||||
|
||||
@classmethod
|
||||
def stage2_sample(
|
||||
cls,
|
||||
stage2_sampler,
|
||||
pixel_img: Image.Image,
|
||||
stage1_images,
|
||||
prompt="3D assets",
|
||||
neg_texts="uniform low no texture ugly, boring, bad anatomy, blurry, pixelated, obscure, unnatural colors, poor lighting, dull, and unclear.",
|
||||
seed=0,
|
||||
scale=5,
|
||||
step=50
|
||||
):
|
||||
"""
|
||||
The second stage was condition on multiview pixel image generated by the first stage, generate the final image, based on the stage2-test config
|
||||
"""
|
||||
|
||||
stage2_sampler.seed = seed
|
||||
|
||||
# Convert torch image to PIL.Image
|
||||
stage1_images = list((255.0 * stage1_images.cpu().numpy()).astype(np.uint8))
|
||||
stage1_images = [Image.fromarray(img) for img in stage1_images]
|
||||
|
||||
uc = stage2_sampler.model.get_learned_conditioning([neg_texts]).to(stage2_sampler.device)
|
||||
stage2_images = stage2_sampler.i2iStage2(
|
||||
stage2_sampler.model,
|
||||
stage2_sampler.size,
|
||||
prompt,
|
||||
uc=uc,
|
||||
sampler=stage2_sampler.sampler,
|
||||
pixel_images=stage1_images,
|
||||
ip=pixel_img,
|
||||
step=step,
|
||||
scale=scale,
|
||||
batch_size=stage2_sampler.batch_size,
|
||||
ddim_eta=0.0,
|
||||
dtype=stage2_sampler.dtype,
|
||||
device=stage2_sampler.device,
|
||||
camera=stage2_sampler.camera,
|
||||
num_frames=stage2_sampler.num_frames,
|
||||
pixel_control=(stage2_sampler.mode == "pixel"),
|
||||
transform=stage2_sampler.image_transform,
|
||||
offset_noise=stage2_sampler.offset_noise,
|
||||
)
|
||||
|
||||
return stage2_images # (N, H, W, 3) in [0, 1]
|
||||
|
||||
@classmethod
|
||||
def generate3d(cls, crm_model, rgb, ccm, device):
|
||||
|
||||
color_tri = torch.from_numpy(rgb)
|
||||
xyz_tri = torch.from_numpy(ccm[:,:,(2,1,0)])
|
||||
color = color_tri.permute(2,0,1)
|
||||
xyz = xyz_tri.permute(2,0,1)
|
||||
|
||||
|
||||
def get_imgs(color):
|
||||
# color : [C, H, W*6]
|
||||
color_list = []
|
||||
color_list.append(color[:,:,256*5:256*(1+5)])
|
||||
for i in range(0,5):
|
||||
color_list.append(color[:,:,256*i:256*(1+i)])
|
||||
return torch.stack(color_list, dim=0)# [6, C, H, W]
|
||||
|
||||
triplane_color = get_imgs(color).permute(0,2,3,1).unsqueeze(0).to(device)# [1, 6, H, W, C]
|
||||
|
||||
color = get_imgs(color)
|
||||
xyz = get_imgs(xyz)
|
||||
|
||||
color = get_tri(color, dim=0, blender= True, scale = 1).unsqueeze(0)
|
||||
xyz = get_tri(xyz, dim=0, blender= True, scale = 1, fix= True).unsqueeze(0)
|
||||
|
||||
triplane = torch.cat([color,xyz],dim=1).to(device)
|
||||
# 3D visualize
|
||||
crm_model.eval()
|
||||
glctx = dr.RasterizeCudaContext()
|
||||
|
||||
if crm_model.denoising == True:
|
||||
tnew = 20
|
||||
tnew = torch.randint(tnew, tnew+1, [triplane.shape[0]], dtype=torch.long, device=triplane.device)
|
||||
noise_new = torch.randn_like(triplane) *0.5+0.5
|
||||
triplane = crm_model.scheduler.add_noise(triplane, noise_new, tnew)
|
||||
start_time = time.time()
|
||||
with torch.no_grad():
|
||||
triplane_feature2 = crm_model.unet2(triplane,tnew)
|
||||
end_time = time.time()
|
||||
elapsed_time = end_time - start_time
|
||||
print(f"Running unet takes {elapsed_time}s")
|
||||
else:
|
||||
triplane_feature2 = crm_model.unet2(triplane)
|
||||
|
||||
|
||||
with torch.no_grad():
|
||||
data_config = {
|
||||
'resolution': [1024, 1024],
|
||||
"triview_color": triplane_color.to(device),
|
||||
}
|
||||
|
||||
verts, faces = crm_model.decode(data_config, triplane_feature2)
|
||||
|
||||
data_config['verts'] = verts[0]
|
||||
data_config['faces'] = faces
|
||||
|
||||
|
||||
from kiui.mesh_utils import clean_mesh
|
||||
verts, faces = clean_mesh(data_config['verts'].squeeze().cpu().numpy().astype(np.float32), data_config['faces'].squeeze().cpu().numpy().astype(np.int32), repair = False, remesh=False, remesh_size=0.005)
|
||||
data_config['verts'] = torch.from_numpy(verts).cuda().contiguous()
|
||||
data_config['faces'] = torch.from_numpy(faces).cuda().contiguous()
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
mesh = crm_model.get_mesh_wt_uv(glctx, data_config, device, res=(1024,1024), tri_fea_2=triplane_feature2)
|
||||
|
||||
end_time = time.time()
|
||||
elapsed_time = end_time - start_time
|
||||
print(f"Extracting mesh & texture takes {elapsed_time}s")
|
||||
|
||||
return mesh
|
||||
@@ -0,0 +1,640 @@
|
||||
# Copyright 2022 The HuggingFace Team. All rights reserved.
|
||||
#
|
||||
# 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 os
|
||||
from typing import Callable, List, Tuple, Optional, Union
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
||||
from diffusers.utils import logging
|
||||
from diffusers.models.modeling_utils import ModelMixin
|
||||
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
class ImageConv2dEncoder(nn.Module):
|
||||
"""
|
||||
Quoting from https://arxiv.org/abs/2302.05543: "Stable Diffusion uses a pre-processing method similar to VQ-GAN
|
||||
[11] to convert the entire dataset of 512 × 512 images into smaller 32 × 32 “latent images” for stabilized
|
||||
training. We use a tiny network E(·) of four convolution layers with 4 × 4 kernels and 2 × 2 strides
|
||||
(activated by ReLU, channels are 16, 32, 64, 128, initialized with Gaussian weights, trained jointly with the full
|
||||
model) to encode image-space conditions ... into feature maps ..."
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
conditioning_channels: int = 3,
|
||||
block_out_channels: Tuple[int, ...] = (16, 32, 64, 128, 256),
|
||||
output_embedding_channels: int = 320
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.conv_in = nn.Conv2d(conditioning_channels, block_out_channels[0], kernel_size=3, padding=1)
|
||||
|
||||
self.blocks = nn.ModuleList([])
|
||||
|
||||
for i in range(len(block_out_channels) - 1):
|
||||
channel_in = block_out_channels[i]
|
||||
channel_out = block_out_channels[i + 1]
|
||||
self.blocks.append(nn.Conv2d(channel_in, channel_in, kernel_size=3, padding=1))
|
||||
self.blocks.append(nn.Conv2d(channel_in, channel_out, kernel_size=3, padding=1, stride=2))
|
||||
|
||||
self.conv_out = self.zero_module(
|
||||
nn.Conv2d(block_out_channels[-1], output_embedding_channels, kernel_size=3, padding=1)
|
||||
)
|
||||
|
||||
def forward(self, conditioning):
|
||||
embedding = self.conv_in(conditioning)
|
||||
embedding = F.silu(embedding)
|
||||
|
||||
for block in self.blocks:
|
||||
embedding = block(embedding)
|
||||
embedding = F.silu(embedding)
|
||||
|
||||
embedding = self.conv_out(embedding)
|
||||
|
||||
return embedding
|
||||
|
||||
def zero_module(self, module):
|
||||
for p in module.parameters():
|
||||
nn.init.zeros_(p)
|
||||
return module
|
||||
|
||||
class MultiAdapterV2(ModelMixin):
|
||||
r"""
|
||||
MultiAdapter is a wrapper model that contains multiple adapter models and merges their outputs according to
|
||||
user-assigned weighting.
|
||||
|
||||
This model inherits from [`ModelMixin`]. Check the superclass documentation for the generic methods the library
|
||||
implements for all the model (such as downloading or saving, etc.)
|
||||
|
||||
Parameters:
|
||||
adapters (`List[T2IAdapterV2]`, *optional*, defaults to None):
|
||||
A list of `T2IAdapterV2` model instances.
|
||||
"""
|
||||
|
||||
def __init__(self, adapters: List["T2IAdapterV2"]):
|
||||
super(MultiAdapterV2, self).__init__()
|
||||
|
||||
self.num_adapter = len(adapters)
|
||||
self.adapters = nn.ModuleList(adapters)
|
||||
|
||||
if len(adapters) == 0:
|
||||
raise ValueError("Expecting at least one adapter")
|
||||
|
||||
if len(adapters) == 1:
|
||||
raise ValueError("For a single adapter, please use the `T2IAdapter` class instead of `MultiAdapter`")
|
||||
|
||||
# The outputs from each adapter are added together with a weight.
|
||||
# This means that the change in dimensions from downsampling must
|
||||
# be the same for all adapters. Inductively, it also means the
|
||||
# downscale_factor and total_downscale_factor must be the same for all
|
||||
# adapters.
|
||||
first_adapter_total_downscale_factor = adapters[0].total_downscale_factor
|
||||
first_adapter_downscale_factor = adapters[0].downscale_factor
|
||||
for idx in range(1, len(adapters)):
|
||||
if (
|
||||
adapters[idx].total_downscale_factor != first_adapter_total_downscale_factor
|
||||
or adapters[idx].downscale_factor != first_adapter_downscale_factor
|
||||
):
|
||||
raise ValueError(
|
||||
f"Expecting all adapters to have the same downscaling behavior, but got:\n"
|
||||
f"adapters[0].total_downscale_factor={first_adapter_total_downscale_factor}\n"
|
||||
f"adapters[0].downscale_factor={first_adapter_downscale_factor}\n"
|
||||
f"adapter[`{idx}`].total_downscale_factor={adapters[idx].total_downscale_factor}\n"
|
||||
f"adapter[`{idx}`].downscale_factor={adapters[idx].downscale_factor}"
|
||||
)
|
||||
|
||||
self.total_downscale_factor = first_adapter_total_downscale_factor
|
||||
self.downscale_factor = first_adapter_downscale_factor
|
||||
|
||||
def forward(self, xs: torch.Tensor, adapter_weights: Optional[List[float]] = None) -> List[torch.Tensor]:
|
||||
r"""
|
||||
Args:
|
||||
xs (`torch.Tensor`):
|
||||
(batch, channel, height, width) input images for multiple adapter models concated along dimension 1,
|
||||
`channel` should equal to `num_adapter` * "number of channel of image".
|
||||
adapter_weights (`List[float]`, *optional*, defaults to None):
|
||||
List of floats representing the weight which will be multiply to each adapter's output before adding
|
||||
them together.
|
||||
"""
|
||||
if adapter_weights is None:
|
||||
adapter_weights = torch.tensor([1 / self.num_adapter] * self.num_adapter)
|
||||
else:
|
||||
adapter_weights = torch.tensor(adapter_weights)
|
||||
|
||||
accume_state = None
|
||||
for x, w, adapter in zip(xs, adapter_weights, self.adapters):
|
||||
features = adapter(x)
|
||||
if accume_state is None:
|
||||
accume_state = features
|
||||
for i in range(len(accume_state)):
|
||||
accume_state[i] = w * accume_state[i]
|
||||
else:
|
||||
for i in range(len(features)):
|
||||
accume_state[i] += w * features[i]
|
||||
return accume_state
|
||||
|
||||
def save_pretrained(
|
||||
self,
|
||||
save_directory: Union[str, os.PathLike],
|
||||
is_main_process: bool = True,
|
||||
save_function: Callable = None,
|
||||
safe_serialization: bool = True,
|
||||
variant: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Save a model and its configuration file to a directory, so that it can be re-loaded using the
|
||||
`[`~models.adapter.MultiAdapter.from_pretrained`]` class method.
|
||||
|
||||
Arguments:
|
||||
save_directory (`str` or `os.PathLike`):
|
||||
Directory to which to save. Will be created if it doesn't exist.
|
||||
is_main_process (`bool`, *optional*, defaults to `True`):
|
||||
Whether the process calling this is the main process or not. Useful when in distributed training like
|
||||
TPUs and need to call this function on all processes. In this case, set `is_main_process=True` only on
|
||||
the main process to avoid race conditions.
|
||||
save_function (`Callable`):
|
||||
The function to use to save the state dictionary. Useful on distributed training like TPUs when one
|
||||
need to replace `torch.save` by another method. Can be configured with the environment variable
|
||||
`DIFFUSERS_SAVE_MODE`.
|
||||
safe_serialization (`bool`, *optional*, defaults to `True`):
|
||||
Whether to save the model using `safetensors` or the traditional PyTorch way (that uses `pickle`).
|
||||
variant (`str`, *optional*):
|
||||
If specified, weights are saved in the format pytorch_model.<variant>.bin.
|
||||
"""
|
||||
idx = 0
|
||||
model_path_to_save = save_directory
|
||||
for adapter in self.adapters:
|
||||
adapter.save_pretrained(
|
||||
model_path_to_save,
|
||||
is_main_process=is_main_process,
|
||||
save_function=save_function,
|
||||
safe_serialization=safe_serialization,
|
||||
variant=variant,
|
||||
)
|
||||
|
||||
idx += 1
|
||||
model_path_to_save = model_path_to_save + f"_{idx}"
|
||||
|
||||
@classmethod
|
||||
def from_pretrained(cls, pretrained_model_path: Optional[Union[str, os.PathLike]], **kwargs):
|
||||
r"""
|
||||
Instantiate a pretrained MultiAdapter model from multiple pre-trained adapter models.
|
||||
|
||||
The model is set in evaluation mode by default using `model.eval()` (Dropout modules are deactivated). To train
|
||||
the model, you should first set it back in training mode with `model.train()`.
|
||||
|
||||
The warning *Weights from XXX not initialized from pretrained model* means that the weights of XXX do not come
|
||||
pretrained with the rest of the model. It is up to you to train those weights with a downstream fine-tuning
|
||||
task.
|
||||
|
||||
The warning *Weights from XXX not used in YYY* means that the layer XXX is not used by YYY, therefore those
|
||||
weights are discarded.
|
||||
|
||||
Parameters:
|
||||
pretrained_model_path (`os.PathLike`):
|
||||
A path to a *directory* containing model weights saved using
|
||||
[`~diffusers.models.adapter.MultiAdapter.save_pretrained`], e.g., `./my_model_directory/adapter`.
|
||||
torch_dtype (`str` or `torch.dtype`, *optional*):
|
||||
Override the default `torch.dtype` and load the model under this dtype. If `"auto"` is passed the dtype
|
||||
will be automatically derived from the model's weights.
|
||||
output_loading_info(`bool`, *optional*, defaults to `False`):
|
||||
Whether or not to also return a dictionary containing missing keys, unexpected keys and error messages.
|
||||
device_map (`str` or `Dict[str, Union[int, str, torch.device]]`, *optional*):
|
||||
A map that specifies where each submodule should go. It doesn't need to be refined to each
|
||||
parameter/buffer name, once a given module name is inside, every submodule of it will be sent to the
|
||||
same device.
|
||||
|
||||
To have Accelerate compute the most optimized `device_map` automatically, set `device_map="auto"`. For
|
||||
more information about each option see [designing a device
|
||||
map](https://hf.co/docs/accelerate/main/en/usage_guides/big_modeling#designing-a-device-map).
|
||||
max_memory (`Dict`, *optional*):
|
||||
A dictionary device identifier to maximum memory. Will default to the maximum memory available for each
|
||||
GPU and the available CPU RAM if unset.
|
||||
low_cpu_mem_usage (`bool`, *optional*, defaults to `True` if torch version >= 1.9.0 else `False`):
|
||||
Speed up model loading by not initializing the weights and only loading the pre-trained weights. This
|
||||
also tries to not use more than 1x model size in CPU memory (including peak memory) while loading the
|
||||
model. This is only supported when torch version >= 1.9.0. If you are using an older version of torch,
|
||||
setting this argument to `True` will raise an error.
|
||||
variant (`str`, *optional*):
|
||||
If specified load weights from `variant` filename, *e.g.* pytorch_model.<variant>.bin. `variant` is
|
||||
ignored when using `from_flax`.
|
||||
use_safetensors (`bool`, *optional*, defaults to `None`):
|
||||
If set to `None`, the `safetensors` weights will be downloaded if they're available **and** if the
|
||||
`safetensors` library is installed. If set to `True`, the model will be forcibly loaded from
|
||||
`safetensors` weights. If set to `False`, loading will *not* use `safetensors`.
|
||||
"""
|
||||
idx = 0
|
||||
adapters = []
|
||||
|
||||
# load adapter and append to list until no adapter directory exists anymore
|
||||
# first adapter has to be saved under `./mydirectory/adapter` to be compliant with `DiffusionPipeline.from_pretrained`
|
||||
# second, third, ... adapters have to be saved under `./mydirectory/adapter_1`, `./mydirectory/adapter_2`, ...
|
||||
model_path_to_load = pretrained_model_path
|
||||
while os.path.isdir(model_path_to_load):
|
||||
adapter = T2IAdapterV2.from_pretrained(model_path_to_load, **kwargs)
|
||||
adapters.append(adapter)
|
||||
|
||||
idx += 1
|
||||
model_path_to_load = pretrained_model_path + f"_{idx}"
|
||||
|
||||
logger.info(f"{len(adapters)} adapters loaded from {pretrained_model_path}.")
|
||||
|
||||
if len(adapters) == 0:
|
||||
raise ValueError(
|
||||
f"No T2IAdapters found under {os.path.dirname(pretrained_model_path)}. Expected at least {pretrained_model_path + '_0'}."
|
||||
)
|
||||
|
||||
return cls(adapters)
|
||||
|
||||
|
||||
class T2IAdapterV2(ModelMixin, ConfigMixin):
|
||||
r"""
|
||||
A simple ResNet-like model that accepts images containing control signals such as keyposes and depth. The model
|
||||
generates multiple feature maps that are used as additional conditioning in [`UNet2DConditionModel`]. The model's
|
||||
architecture follows the original implementation of
|
||||
[Adapter](https://github.com/TencentARC/T2I-Adapter/blob/686de4681515662c0ac2ffa07bf5dda83af1038a/ldm/modules/encoders/adapter.py#L97)
|
||||
and
|
||||
[AdapterLight](https://github.com/TencentARC/T2I-Adapter/blob/686de4681515662c0ac2ffa07bf5dda83af1038a/ldm/modules/encoders/adapter.py#L235).
|
||||
|
||||
This model inherits from [`ModelMixin`]. Check the superclass documentation for the generic methods the library
|
||||
implements for all the model (such as downloading or saving, etc.)
|
||||
|
||||
Parameters:
|
||||
in_channels (`int`, *optional*, defaults to 3):
|
||||
Number of channels of Aapter's input(*control image*). Set this parameter to 1 if you're using gray scale
|
||||
image as *control image*.
|
||||
channels (`List[int]`, *optional*, defaults to `(320, 640, 1280, 1280)`):
|
||||
The number of channel of each downsample block's output hidden state. The `len(block_out_channels)` will
|
||||
also determine the number of downsample blocks in the Adapter.
|
||||
num_res_blocks (`int`, *optional*, defaults to 2):
|
||||
Number of ResNet blocks in each downsample block.
|
||||
downscale_factor (`int`, *optional*, defaults to 8):
|
||||
A factor that determines the total downscale factor of the Adapter.
|
||||
adapter_type (`str`, *optional*, defaults to `full_adapter`):
|
||||
The type of Adapter to use. Choose either `full_adapter` or `full_adapter_xl` or `light_adapter`.
|
||||
"""
|
||||
|
||||
@register_to_config
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int = 3,
|
||||
channels: List[int] = [320, 640, 1280, 1280],
|
||||
num_res_blocks: int = 2,
|
||||
conditioning_embedding_out_channels: Optional[Tuple[int, ...]] = (16, 32, 64, 128, 256),
|
||||
adapter_type: str = "full_adapter",
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
if adapter_type == "full_adapter":
|
||||
self.adapter = FullAdapterV2(in_channels, channels, num_res_blocks, conditioning_embedding_out_channels)
|
||||
elif adapter_type == "full_adapter_xl":
|
||||
self.adapter = FullAdapterXLV2(in_channels, channels, num_res_blocks, conditioning_embedding_out_channels)
|
||||
elif adapter_type == "light_adapter":
|
||||
self.adapter = LightAdapterV2(in_channels, channels, num_res_blocks, conditioning_embedding_out_channels)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported adapter_type: '{adapter_type}'. Choose either 'full_adapter' or "
|
||||
"'full_adapter_xl' or 'light_adapter'."
|
||||
)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> List[torch.Tensor]:
|
||||
r"""
|
||||
This function processes the input tensor `x` through the adapter model and returns a list of feature tensors,
|
||||
each representing information extracted at a different scale from the input. The length of the list is
|
||||
determined by the number of downsample blocks in the Adapter, as specified by the `channels` and
|
||||
`num_res_blocks` parameters during initialization.
|
||||
"""
|
||||
return self.adapter(x)
|
||||
|
||||
@property
|
||||
def total_downscale_factor(self):
|
||||
return self.adapter.total_downscale_factor
|
||||
|
||||
@property
|
||||
def downscale_factor(self):
|
||||
return self.adapter.downscale_factor
|
||||
|
||||
|
||||
# full adapter
|
||||
|
||||
|
||||
class FullAdapterV2(nn.Module):
|
||||
r"""
|
||||
See [`T2IAdapter`] for more information.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int = 3,
|
||||
channels: List[int] = [320, 640, 1280, 1280],
|
||||
num_res_blocks: int = 2,
|
||||
conditioning_embedding_out_channels: Optional[Tuple[int, ...]] = (16, 32, 64, 128, 256),
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
img_embedding_channel = channels[0]
|
||||
|
||||
self.conv2d_img_encoder = ImageConv2dEncoder(
|
||||
conditioning_channels=in_channels,
|
||||
block_out_channels=conditioning_embedding_out_channels,
|
||||
output_embedding_channels=img_embedding_channel,
|
||||
)
|
||||
|
||||
self.body = nn.ModuleList(
|
||||
[
|
||||
AdapterBlock(img_embedding_channel, img_embedding_channel, num_res_blocks),
|
||||
*[
|
||||
AdapterBlock(channels[i - 1], channels[i], num_res_blocks, down=True)
|
||||
for i in range(1, len(channels))
|
||||
],
|
||||
]
|
||||
)
|
||||
|
||||
self.downscale_factor = 16
|
||||
self.total_downscale_factor = self.downscale_factor * 2 ** (len(channels) - 1)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> List[torch.Tensor]:
|
||||
r"""
|
||||
This method processes the input tensor `x` through the FullAdapter model and performs operations including
|
||||
stack of convolutions, and a stack of AdapterBlocks. It returns a list of feature tensors, each
|
||||
capturing information at a different stage of processing within the FullAdapter model. The number of feature
|
||||
tensors in the list is determined by the number of downsample blocks specified during initialization.
|
||||
"""
|
||||
x = self.conv2d_img_encoder(x)
|
||||
|
||||
features = []
|
||||
|
||||
for block in self.body:
|
||||
x = block(x)
|
||||
features.append(x)
|
||||
|
||||
return features
|
||||
|
||||
|
||||
class FullAdapterXLV2(nn.Module):
|
||||
r"""
|
||||
See [`T2IAdapter`] for more information.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int = 3,
|
||||
channels: List[int] = [320, 640, 1280, 1280],
|
||||
num_res_blocks: int = 2,
|
||||
conditioning_embedding_out_channels: Optional[Tuple[int, ...]] = (16, 32, 64, 128, 256),
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
img_embedding_channel = channels[0]
|
||||
|
||||
self.conv2d_img_encoder = ImageConv2dEncoder(
|
||||
conditioning_channels=in_channels,
|
||||
block_out_channels=conditioning_embedding_out_channels,
|
||||
output_embedding_channels=img_embedding_channel,
|
||||
)
|
||||
|
||||
self.body = []
|
||||
# blocks to extract XL features with dimensions of [320, 64, 64], [640, 64, 64], [1280, 32, 32], [1280, 32, 32]
|
||||
for i in range(len(channels)):
|
||||
if i == 1:
|
||||
self.body.append(AdapterBlock(channels[i - 1], channels[i], num_res_blocks))
|
||||
elif i == 2:
|
||||
self.body.append(AdapterBlock(channels[i - 1], channels[i], num_res_blocks, down=True))
|
||||
else:
|
||||
self.body.append(AdapterBlock(channels[i], channels[i], num_res_blocks))
|
||||
|
||||
self.body = nn.ModuleList(self.body)
|
||||
# XL has only one downsampling AdapterBlock.
|
||||
self.downscale_factor = 16
|
||||
self.total_downscale_factor = self.downscale_factor * 2
|
||||
|
||||
def forward(self, x: torch.Tensor) -> List[torch.Tensor]:
|
||||
r"""
|
||||
This method takes the tensor x as input and processes it through FullAdapterXL model. It consists of operations
|
||||
stack of convolution layers and appending each block into list of feature tensors.
|
||||
"""
|
||||
x = self.conv2d_img_encoder(x)
|
||||
|
||||
features = []
|
||||
|
||||
for block in self.body:
|
||||
x = block(x)
|
||||
features.append(x)
|
||||
|
||||
return features
|
||||
|
||||
|
||||
class AdapterBlock(nn.Module):
|
||||
r"""
|
||||
An AdapterBlock is a helper model that contains multiple ResNet-like blocks. It is used in the `FullAdapter` and
|
||||
`FullAdapterXL` models.
|
||||
|
||||
Parameters:
|
||||
in_channels (`int`):
|
||||
Number of channels of AdapterBlock's input.
|
||||
out_channels (`int`):
|
||||
Number of channels of AdapterBlock's output.
|
||||
num_res_blocks (`int`):
|
||||
Number of ResNet blocks in the AdapterBlock.
|
||||
down (`bool`, *optional*, defaults to `False`):
|
||||
Whether to perform downsampling on AdapterBlock's input.
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels: int, out_channels: int, num_res_blocks: int, down: bool = False):
|
||||
super().__init__()
|
||||
|
||||
self.downsample = None
|
||||
if down:
|
||||
self.downsample = nn.AvgPool2d(kernel_size=2, stride=2, ceil_mode=True)
|
||||
|
||||
self.in_conv = None
|
||||
if in_channels != out_channels:
|
||||
self.in_conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)
|
||||
|
||||
self.resnets = nn.Sequential(
|
||||
*[AdapterResnetBlock(out_channels) for _ in range(num_res_blocks)],
|
||||
)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
r"""
|
||||
This method takes tensor x as input and performs operations downsampling and convolutional layers if the
|
||||
self.downsample and self.in_conv properties of AdapterBlock model are specified. Then it applies a series of
|
||||
residual blocks to the input tensor.
|
||||
"""
|
||||
if self.downsample is not None:
|
||||
x = self.downsample(x)
|
||||
|
||||
if self.in_conv is not None:
|
||||
x = self.in_conv(x)
|
||||
|
||||
x = self.resnets(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class AdapterResnetBlock(nn.Module):
|
||||
r"""
|
||||
An `AdapterResnetBlock` is a helper model that implements a ResNet-like block.
|
||||
|
||||
Parameters:
|
||||
channels (`int`):
|
||||
Number of channels of AdapterResnetBlock's input and output.
|
||||
"""
|
||||
|
||||
def __init__(self, channels: int):
|
||||
super().__init__()
|
||||
self.block1 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
|
||||
self.act = nn.ReLU()
|
||||
self.block2 = nn.Conv2d(channels, channels, kernel_size=1)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
r"""
|
||||
This method takes input tensor x and applies a convolutional layer, ReLU activation, and another convolutional
|
||||
layer on the input tensor. It returns addition with the input tensor.
|
||||
"""
|
||||
|
||||
h = self.act(self.block1(x))
|
||||
h = self.block2(h)
|
||||
|
||||
return h + x
|
||||
|
||||
|
||||
# light adapter
|
||||
|
||||
|
||||
class LightAdapterV2(nn.Module):
|
||||
r"""
|
||||
See [`T2IAdapter`] for more information.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int = 3,
|
||||
channels: List[int] = [320, 640, 1280],
|
||||
num_res_blocks: int = 4,
|
||||
conditioning_embedding_out_channels: Optional[Tuple[int, ...]] = (16, 32, 64, 128, 256),
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
img_embedding_channel = channels[0]
|
||||
|
||||
self.conv2d_img_encoder = ImageConv2dEncoder(
|
||||
conditioning_channels=in_channels,
|
||||
block_out_channels=conditioning_embedding_out_channels,
|
||||
output_embedding_channels=img_embedding_channel,
|
||||
)
|
||||
|
||||
self.body = nn.ModuleList(
|
||||
[
|
||||
LightAdapterBlock(img_embedding_channel, img_embedding_channel, num_res_blocks),
|
||||
*[
|
||||
LightAdapterBlock(channels[i], channels[i + 1], num_res_blocks, down=True)
|
||||
for i in range(len(channels) - 1)
|
||||
],
|
||||
LightAdapterBlock(channels[-1], channels[-1], num_res_blocks, down=True),
|
||||
]
|
||||
)
|
||||
|
||||
self.downscale_factor = 16
|
||||
self.total_downscale_factor = self.downscale_factor * (2 ** len(channels))
|
||||
|
||||
def forward(self, x: torch.Tensor) -> List[torch.Tensor]:
|
||||
r"""
|
||||
This method takes the input tensor x and performs downscaling and appends it in list of feature tensors. Each
|
||||
feature tensor corresponds to a different level of processing within the LightAdapter.
|
||||
"""
|
||||
x = self.conv2d_img_encoder(x)
|
||||
|
||||
features = []
|
||||
|
||||
for block in self.body:
|
||||
x = block(x)
|
||||
features.append(x)
|
||||
|
||||
return features
|
||||
|
||||
|
||||
class LightAdapterBlock(nn.Module):
|
||||
r"""
|
||||
A `LightAdapterBlock` is a helper model that contains multiple `LightAdapterResnetBlocks`. It is used in the
|
||||
`LightAdapter` model.
|
||||
|
||||
Parameters:
|
||||
in_channels (`int`):
|
||||
Number of channels of LightAdapterBlock's input.
|
||||
out_channels (`int`):
|
||||
Number of channels of LightAdapterBlock's output.
|
||||
num_res_blocks (`int`):
|
||||
Number of LightAdapterResnetBlocks in the LightAdapterBlock.
|
||||
down (`bool`, *optional*, defaults to `False`):
|
||||
Whether to perform downsampling on LightAdapterBlock's input.
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels: int, out_channels: int, num_res_blocks: int, down: bool = False):
|
||||
super().__init__()
|
||||
mid_channels = out_channels // 4
|
||||
|
||||
self.downsample = None
|
||||
if down:
|
||||
self.downsample = nn.AvgPool2d(kernel_size=2, stride=2, ceil_mode=True)
|
||||
|
||||
self.in_conv = nn.Conv2d(in_channels, mid_channels, kernel_size=1)
|
||||
self.resnets = nn.Sequential(*[LightAdapterResnetBlock(mid_channels) for _ in range(num_res_blocks)])
|
||||
self.out_conv = nn.Conv2d(mid_channels, out_channels, kernel_size=1)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
r"""
|
||||
This method takes tensor x as input and performs downsampling if required. Then it applies in convolution
|
||||
layer, a sequence of residual blocks, and out convolutional layer.
|
||||
"""
|
||||
if self.downsample is not None:
|
||||
x = self.downsample(x)
|
||||
|
||||
x = self.in_conv(x)
|
||||
x = self.resnets(x)
|
||||
x = self.out_conv(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class LightAdapterResnetBlock(nn.Module):
|
||||
"""
|
||||
A `LightAdapterResnetBlock` is a helper model that implements a ResNet-like block with a slightly different
|
||||
architecture than `AdapterResnetBlock`.
|
||||
|
||||
Parameters:
|
||||
channels (`int`):
|
||||
Number of channels of LightAdapterResnetBlock's input and output.
|
||||
"""
|
||||
|
||||
def __init__(self, channels: int):
|
||||
super().__init__()
|
||||
self.block1 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
|
||||
self.act = nn.ReLU()
|
||||
self.block2 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
r"""
|
||||
This function takes input tensor x and processes it through one convolutional layer, ReLU activation, and
|
||||
another convolutional layer and adds it to input tensor.
|
||||
"""
|
||||
|
||||
h = self.act(self.block1(x))
|
||||
h = self.block2(h)
|
||||
|
||||
return h + x
|
||||
@@ -0,0 +1,579 @@
|
||||
# Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
#
|
||||
# NVIDIA CORPORATION & AFFILIATES 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 & AFFILIATES is strictly prohibited.
|
||||
import torch
|
||||
from CRM_T2I_V2.util.tables import *
|
||||
|
||||
__all__ = [
|
||||
'FlexiCubes'
|
||||
]
|
||||
|
||||
|
||||
class FlexiCubes:
|
||||
"""
|
||||
This class implements the FlexiCubes method for extracting meshes from scalar fields.
|
||||
It maintains a series of lookup tables and indices to support the mesh extraction process.
|
||||
FlexiCubes, a differentiable variant of the Dual Marching Cubes (DMC) scheme, enhances
|
||||
the geometric fidelity and mesh quality of reconstructed meshes by dynamically adjusting
|
||||
the surface representation through gradient-based optimization.
|
||||
|
||||
During instantiation, the class loads DMC tables from a file and transforms them into
|
||||
PyTorch tensors on the specified device.
|
||||
|
||||
Attributes:
|
||||
device (str): Specifies the computational device (default is "cuda").
|
||||
dmc_table (torch.Tensor): Dual Marching Cubes (DMC) table that encodes the edges
|
||||
associated with each dual vertex in 256 Marching Cubes (MC) configurations.
|
||||
num_vd_table (torch.Tensor): Table holding the number of dual vertices in each of
|
||||
the 256 MC configurations.
|
||||
check_table (torch.Tensor): Table resolving ambiguity in cases C16 and C19
|
||||
of the DMC configurations.
|
||||
tet_table (torch.Tensor): Lookup table used in tetrahedralizing the isosurface.
|
||||
quad_split_1 (torch.Tensor): Indices for splitting a quad into two triangles
|
||||
along one diagonal.
|
||||
quad_split_2 (torch.Tensor): Alternative indices for splitting a quad into
|
||||
two triangles along the other diagonal.
|
||||
quad_split_train (torch.Tensor): Indices for splitting a quad into four triangles
|
||||
during training by connecting all edges to their midpoints.
|
||||
cube_corners (torch.Tensor): Defines the positions of a standard unit cube's
|
||||
eight corners in 3D space, ordered starting from the origin (0,0,0),
|
||||
moving along the x-axis, then y-axis, and finally z-axis.
|
||||
Used as a blueprint for generating a voxel grid.
|
||||
cube_corners_idx (torch.Tensor): Cube corners indexed as powers of 2, used
|
||||
to retrieve the case id.
|
||||
cube_edges (torch.Tensor): Edge connections in a cube, listed in pairs.
|
||||
Used to retrieve edge vertices in DMC.
|
||||
edge_dir_table (torch.Tensor): A mapping tensor that associates edge indices with
|
||||
their corresponding axis. For instance, edge_dir_table[0] = 0 indicates that the
|
||||
first edge is oriented along the x-axis.
|
||||
dir_faces_table (torch.Tensor): A tensor that maps the corresponding axis of shared edges
|
||||
across four adjacent cubes to the shared faces of these cubes. For instance,
|
||||
dir_faces_table[0] = [5, 4] implies that for four cubes sharing an edge along
|
||||
the x-axis, the first and second cubes share faces indexed as 5 and 4, respectively.
|
||||
This tensor is only utilized during isosurface tetrahedralization.
|
||||
adj_pairs (torch.Tensor):
|
||||
A tensor containing index pairs that correspond to neighboring cubes that share the same edge.
|
||||
qef_reg_scale (float):
|
||||
The scaling factor applied to the regularization loss to prevent issues with singularity
|
||||
when solving the QEF. This parameter is only used when a 'grad_func' is specified.
|
||||
weight_scale (float):
|
||||
The scale of weights in FlexiCubes. Should be between 0 and 1.
|
||||
"""
|
||||
|
||||
def __init__(self, device="cuda", qef_reg_scale=1e-3, weight_scale=0.99):
|
||||
|
||||
self.device = device
|
||||
self.dmc_table = torch.tensor(dmc_table, dtype=torch.long, device=device, requires_grad=False)
|
||||
self.num_vd_table = torch.tensor(num_vd_table,
|
||||
dtype=torch.long, device=device, requires_grad=False)
|
||||
self.check_table = torch.tensor(
|
||||
check_table,
|
||||
dtype=torch.long, device=device, requires_grad=False)
|
||||
|
||||
self.tet_table = torch.tensor(tet_table, dtype=torch.long, device=device, requires_grad=False)
|
||||
self.quad_split_1 = torch.tensor([0, 1, 2, 0, 2, 3], dtype=torch.long, device=device, requires_grad=False)
|
||||
self.quad_split_2 = torch.tensor([0, 1, 3, 3, 1, 2], dtype=torch.long, device=device, requires_grad=False)
|
||||
self.quad_split_train = torch.tensor(
|
||||
[0, 1, 1, 2, 2, 3, 3, 0], dtype=torch.long, device=device, requires_grad=False)
|
||||
|
||||
self.cube_corners = torch.tensor([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [
|
||||
1, 0, 1], [0, 1, 1], [1, 1, 1]], dtype=torch.float, device=device)
|
||||
self.cube_corners_idx = torch.pow(2, torch.arange(8, requires_grad=False))
|
||||
self.cube_edges = torch.tensor([0, 1, 1, 5, 4, 5, 0, 4, 2, 3, 3, 7, 6, 7, 2, 6,
|
||||
2, 0, 3, 1, 7, 5, 6, 4], dtype=torch.long, device=device, requires_grad=False)
|
||||
|
||||
self.edge_dir_table = torch.tensor([0, 2, 0, 2, 0, 2, 0, 2, 1, 1, 1, 1],
|
||||
dtype=torch.long, device=device)
|
||||
self.dir_faces_table = torch.tensor([
|
||||
[[5, 4], [3, 2], [4, 5], [2, 3]],
|
||||
[[5, 4], [1, 0], [4, 5], [0, 1]],
|
||||
[[3, 2], [1, 0], [2, 3], [0, 1]]
|
||||
], dtype=torch.long, device=device)
|
||||
self.adj_pairs = torch.tensor([0, 1, 1, 3, 3, 2, 2, 0], dtype=torch.long, device=device)
|
||||
self.qef_reg_scale = qef_reg_scale
|
||||
self.weight_scale = weight_scale
|
||||
|
||||
def construct_voxel_grid(self, res):
|
||||
"""
|
||||
Generates a voxel grid based on the specified resolution.
|
||||
|
||||
Args:
|
||||
res (int or list[int]): The resolution of the voxel grid. If an integer
|
||||
is provided, it is used for all three dimensions. If a list or tuple
|
||||
of 3 integers is provided, they define the resolution for the x,
|
||||
y, and z dimensions respectively.
|
||||
|
||||
Returns:
|
||||
(torch.Tensor, torch.Tensor): Returns the vertices and the indices of the
|
||||
cube corners (index into vertices) of the constructed voxel grid.
|
||||
The vertices are centered at the origin, with the length of each
|
||||
dimension in the grid being one.
|
||||
"""
|
||||
base_cube_f = torch.arange(8).to(self.device)
|
||||
if isinstance(res, int):
|
||||
res = (res, res, res)
|
||||
voxel_grid_template = torch.ones(res, device=self.device)
|
||||
|
||||
res = torch.tensor([res], dtype=torch.float, device=self.device)
|
||||
coords = torch.nonzero(voxel_grid_template).float() / res # N, 3
|
||||
verts = (self.cube_corners.unsqueeze(0) / res + coords.unsqueeze(1)).reshape(-1, 3)
|
||||
cubes = (base_cube_f.unsqueeze(0) +
|
||||
torch.arange(coords.shape[0], device=self.device).unsqueeze(1) * 8).reshape(-1)
|
||||
|
||||
verts_rounded = torch.round(verts * 10**5) / (10**5)
|
||||
verts_unique, inverse_indices = torch.unique(verts_rounded, dim=0, return_inverse=True)
|
||||
cubes = inverse_indices[cubes.reshape(-1)].reshape(-1, 8)
|
||||
|
||||
return verts_unique - 0.5, cubes
|
||||
|
||||
def __call__(self, x_nx3, s_n, cube_fx8, res, beta_fx12=None, alpha_fx8=None,
|
||||
gamma_f=None, training=False, output_tetmesh=False, grad_func=None):
|
||||
r"""
|
||||
Main function for mesh extraction from scalar field using FlexiCubes. This function converts
|
||||
discrete signed distance fields, encoded on voxel grids and additional per-cube parameters,
|
||||
to triangle or tetrahedral meshes using a differentiable operation as described in
|
||||
`Flexible Isosurface Extraction for Gradient-Based Mesh Optimization`_. FlexiCubes enhances
|
||||
mesh quality and geometric fidelity by adjusting the surface representation based on gradient
|
||||
optimization. The output surface is differentiable with respect to the input vertex positions,
|
||||
scalar field values, and weight parameters.
|
||||
|
||||
If you intend to extract a surface mesh from a fixed Signed Distance Field without the
|
||||
optimization of parameters, it is suggested to provide the "grad_func" which should
|
||||
return the surface gradient at any given 3D position. When grad_func is provided, the process
|
||||
to determine the dual vertex position adapts to solve a Quadratic Error Function (QEF), as
|
||||
described in the `Manifold Dual Contouring`_ paper, and employs an smart splitting strategy.
|
||||
Please note, this approach is non-differentiable.
|
||||
|
||||
For more details and example usage in optimization, refer to the
|
||||
`Flexible Isosurface Extraction for Gradient-Based Mesh Optimization`_ SIGGRAPH 2023 paper.
|
||||
|
||||
Args:
|
||||
x_nx3 (torch.Tensor): Coordinates of the voxel grid vertices, can be deformed.
|
||||
s_n (torch.Tensor): Scalar field values at each vertex of the voxel grid. Negative values
|
||||
denote that the corresponding vertex resides inside the isosurface. This affects
|
||||
the directions of the extracted triangle faces and volume to be tetrahedralized.
|
||||
cube_fx8 (torch.Tensor): Indices of 8 vertices for each cube in the voxel grid.
|
||||
res (int or list[int]): The resolution of the voxel grid. If an integer is provided, it
|
||||
is used for all three dimensions. If a list or tuple of 3 integers is provided, they
|
||||
specify the resolution for the x, y, and z dimensions respectively.
|
||||
beta_fx12 (torch.Tensor, optional): Weight parameters for the cube edges to adjust dual
|
||||
vertices positioning. Defaults to uniform value for all edges.
|
||||
alpha_fx8 (torch.Tensor, optional): Weight parameters for the cube corners to adjust dual
|
||||
vertices positioning. Defaults to uniform value for all vertices.
|
||||
gamma_f (torch.Tensor, optional): Weight parameters to control the splitting of
|
||||
quadrilaterals into triangles. Defaults to uniform value for all cubes.
|
||||
training (bool, optional): If set to True, applies differentiable quad splitting for
|
||||
training. Defaults to False.
|
||||
output_tetmesh (bool, optional): If set to True, outputs a tetrahedral mesh, otherwise,
|
||||
outputs a triangular mesh. Defaults to False.
|
||||
grad_func (callable, optional): A function to compute the surface gradient at specified
|
||||
3D positions (input: Nx3 positions). The function should return gradients as an Nx3
|
||||
tensor. If None, the original FlexiCubes algorithm is utilized. Defaults to None.
|
||||
|
||||
Returns:
|
||||
(torch.Tensor, torch.LongTensor, torch.Tensor): Tuple containing:
|
||||
- Vertices for the extracted triangular/tetrahedral mesh.
|
||||
- Faces for the extracted triangular/tetrahedral mesh.
|
||||
- Regularizer L_dev, computed per dual vertex.
|
||||
|
||||
.. _Flexible Isosurface Extraction for Gradient-Based Mesh Optimization:
|
||||
https://research.nvidia.com/labs/toronto-ai/flexicubes/
|
||||
.. _Manifold Dual Contouring:
|
||||
https://people.engr.tamu.edu/schaefer/research/dualsimp_tvcg.pdf
|
||||
"""
|
||||
|
||||
surf_cubes, occ_fx8 = self._identify_surf_cubes(s_n, cube_fx8)
|
||||
if surf_cubes.sum() == 0:
|
||||
return torch.zeros(
|
||||
(0, 3),
|
||||
device=self.device), torch.zeros(
|
||||
(0, 4),
|
||||
dtype=torch.long, device=self.device) if output_tetmesh else torch.zeros(
|
||||
(0, 3),
|
||||
dtype=torch.long, device=self.device), torch.zeros(
|
||||
(0),
|
||||
device=self.device)
|
||||
beta_fx12, alpha_fx8, gamma_f = self._normalize_weights(beta_fx12, alpha_fx8, gamma_f, surf_cubes)
|
||||
|
||||
case_ids = self._get_case_id(occ_fx8, surf_cubes, res)
|
||||
|
||||
surf_edges, idx_map, edge_counts, surf_edges_mask = self._identify_surf_edges(s_n, cube_fx8, surf_cubes)
|
||||
|
||||
vd, L_dev, vd_gamma, vd_idx_map = self._compute_vd(
|
||||
x_nx3, cube_fx8[surf_cubes], surf_edges, s_n, case_ids, beta_fx12, alpha_fx8, gamma_f, idx_map, grad_func)
|
||||
vertices, faces, s_edges, edge_indices = self._triangulate(
|
||||
s_n, surf_edges, vd, vd_gamma, edge_counts, idx_map, vd_idx_map, surf_edges_mask, training, grad_func)
|
||||
if not output_tetmesh:
|
||||
return vertices, faces, L_dev
|
||||
else:
|
||||
vertices, tets = self._tetrahedralize(
|
||||
x_nx3, s_n, cube_fx8, vertices, faces, surf_edges, s_edges, vd_idx_map, case_ids, edge_indices,
|
||||
surf_cubes, training)
|
||||
return vertices, tets, L_dev
|
||||
|
||||
def _compute_reg_loss(self, vd, ue, edge_group_to_vd, vd_num_edges):
|
||||
"""
|
||||
Regularizer L_dev as in Equation 8
|
||||
"""
|
||||
dist = torch.norm(ue - torch.index_select(input=vd, index=edge_group_to_vd, dim=0), dim=-1)
|
||||
mean_l2 = torch.zeros_like(vd[:, 0])
|
||||
mean_l2 = (mean_l2).index_add_(0, edge_group_to_vd, dist) / vd_num_edges.squeeze(1).float()
|
||||
mad = (dist - torch.index_select(input=mean_l2, index=edge_group_to_vd, dim=0)).abs()
|
||||
return mad
|
||||
|
||||
def _normalize_weights(self, beta_fx12, alpha_fx8, gamma_f, surf_cubes):
|
||||
"""
|
||||
Normalizes the given weights to be non-negative. If input weights are None, it creates and returns a set of weights of ones.
|
||||
"""
|
||||
n_cubes = surf_cubes.shape[0]
|
||||
|
||||
if beta_fx12 is not None:
|
||||
beta_fx12 = (torch.tanh(beta_fx12) * self.weight_scale + 1)
|
||||
else:
|
||||
beta_fx12 = torch.ones((n_cubes, 12), dtype=torch.float, device=self.device)
|
||||
|
||||
if alpha_fx8 is not None:
|
||||
alpha_fx8 = (torch.tanh(alpha_fx8) * self.weight_scale + 1)
|
||||
else:
|
||||
alpha_fx8 = torch.ones((n_cubes, 8), dtype=torch.float, device=self.device)
|
||||
|
||||
if gamma_f is not None:
|
||||
gamma_f = torch.sigmoid(gamma_f) * self.weight_scale + (1 - self.weight_scale)/2
|
||||
else:
|
||||
gamma_f = torch.ones((n_cubes), dtype=torch.float, device=self.device)
|
||||
|
||||
return beta_fx12[surf_cubes], alpha_fx8[surf_cubes], gamma_f[surf_cubes]
|
||||
|
||||
@torch.no_grad()
|
||||
def _get_case_id(self, occ_fx8, surf_cubes, res):
|
||||
"""
|
||||
Obtains the ID of topology cases based on cell corner occupancy. This function resolves the
|
||||
ambiguity in the Dual Marching Cubes (DMC) configurations as described in Section 1.3 of the
|
||||
supplementary material. It should be noted that this function assumes a regular grid.
|
||||
"""
|
||||
case_ids = (occ_fx8[surf_cubes] * self.cube_corners_idx.to(self.device).unsqueeze(0)).sum(-1)
|
||||
|
||||
problem_config = self.check_table.to(self.device)[case_ids]
|
||||
to_check = problem_config[..., 0] == 1
|
||||
problem_config = problem_config[to_check]
|
||||
if not isinstance(res, (list, tuple)):
|
||||
res = [res, res, res]
|
||||
|
||||
# The 'problematic_configs' only contain configurations for surface cubes. Next, we construct a 3D array,
|
||||
# 'problem_config_full', to store configurations for all cubes (with default config for non-surface cubes).
|
||||
# This allows efficient checking on adjacent cubes.
|
||||
problem_config_full = torch.zeros(list(res) + [5], device=self.device, dtype=torch.long)
|
||||
vol_idx = torch.nonzero(problem_config_full[..., 0] == 0) # N, 3
|
||||
vol_idx_problem = vol_idx[surf_cubes][to_check]
|
||||
problem_config_full[vol_idx_problem[..., 0], vol_idx_problem[..., 1], vol_idx_problem[..., 2]] = problem_config
|
||||
vol_idx_problem_adj = vol_idx_problem + problem_config[..., 1:4]
|
||||
|
||||
within_range = (
|
||||
vol_idx_problem_adj[..., 0] >= 0) & (
|
||||
vol_idx_problem_adj[..., 0] < res[0]) & (
|
||||
vol_idx_problem_adj[..., 1] >= 0) & (
|
||||
vol_idx_problem_adj[..., 1] < res[1]) & (
|
||||
vol_idx_problem_adj[..., 2] >= 0) & (
|
||||
vol_idx_problem_adj[..., 2] < res[2])
|
||||
|
||||
vol_idx_problem = vol_idx_problem[within_range]
|
||||
vol_idx_problem_adj = vol_idx_problem_adj[within_range]
|
||||
problem_config = problem_config[within_range]
|
||||
problem_config_adj = problem_config_full[vol_idx_problem_adj[..., 0],
|
||||
vol_idx_problem_adj[..., 1], vol_idx_problem_adj[..., 2]]
|
||||
# If two cubes with cases C16 and C19 share an ambiguous face, both cases are inverted.
|
||||
to_invert = (problem_config_adj[..., 0] == 1)
|
||||
idx = torch.arange(case_ids.shape[0], device=self.device)[to_check][within_range][to_invert]
|
||||
case_ids.index_put_((idx,), problem_config[to_invert][..., -1])
|
||||
return case_ids
|
||||
|
||||
@torch.no_grad()
|
||||
def _identify_surf_edges(self, s_n, cube_fx8, surf_cubes):
|
||||
"""
|
||||
Identifies grid edges that intersect with the underlying surface by checking for opposite signs. As each edge
|
||||
can be shared by multiple cubes, this function also assigns a unique index to each surface-intersecting edge
|
||||
and marks the cube edges with this index.
|
||||
"""
|
||||
occ_n = s_n < 0
|
||||
all_edges = cube_fx8[surf_cubes][:, self.cube_edges].reshape(-1, 2)
|
||||
unique_edges, _idx_map, counts = torch.unique(all_edges, dim=0, return_inverse=True, return_counts=True)
|
||||
|
||||
unique_edges = unique_edges.long()
|
||||
mask_edges = occ_n[unique_edges.reshape(-1)].reshape(-1, 2).sum(-1) == 1
|
||||
|
||||
surf_edges_mask = mask_edges[_idx_map]
|
||||
counts = counts[_idx_map]
|
||||
|
||||
mapping = torch.ones((unique_edges.shape[0]), dtype=torch.long, device=cube_fx8.device) * -1
|
||||
mapping[mask_edges] = torch.arange(mask_edges.sum(), device=cube_fx8.device)
|
||||
# Shaped as [number of cubes x 12 edges per cube]. This is later used to map a cube edge to the unique index
|
||||
# for a surface-intersecting edge. Non-surface-intersecting edges are marked with -1.
|
||||
idx_map = mapping[_idx_map]
|
||||
surf_edges = unique_edges[mask_edges]
|
||||
return surf_edges, idx_map, counts, surf_edges_mask
|
||||
|
||||
@torch.no_grad()
|
||||
def _identify_surf_cubes(self, s_n, cube_fx8):
|
||||
"""
|
||||
Identifies grid cubes that intersect with the underlying surface by checking if the signs at
|
||||
all corners are not identical.
|
||||
"""
|
||||
occ_n = s_n < 0
|
||||
occ_fx8 = occ_n[cube_fx8.reshape(-1)].reshape(-1, 8)
|
||||
_occ_sum = torch.sum(occ_fx8, -1)
|
||||
surf_cubes = (_occ_sum > 0) & (_occ_sum < 8)
|
||||
return surf_cubes, occ_fx8
|
||||
|
||||
def _linear_interp(self, edges_weight, edges_x):
|
||||
"""
|
||||
Computes the location of zero-crossings on 'edges_x' using linear interpolation with 'edges_weight'.
|
||||
"""
|
||||
edge_dim = edges_weight.dim() - 2
|
||||
assert edges_weight.shape[edge_dim] == 2
|
||||
edges_weight = torch.cat([torch.index_select(input=edges_weight, index=torch.tensor(1, device=self.device), dim=edge_dim), -
|
||||
torch.index_select(input=edges_weight, index=torch.tensor(0, device=self.device), dim=edge_dim)], edge_dim)
|
||||
denominator = edges_weight.sum(edge_dim)
|
||||
ue = (edges_x * edges_weight).sum(edge_dim) / denominator
|
||||
return ue
|
||||
|
||||
def _solve_vd_QEF(self, p_bxnx3, norm_bxnx3, c_bx3=None):
|
||||
p_bxnx3 = p_bxnx3.reshape(-1, 7, 3)
|
||||
norm_bxnx3 = norm_bxnx3.reshape(-1, 7, 3)
|
||||
c_bx3 = c_bx3.reshape(-1, 3)
|
||||
A = norm_bxnx3
|
||||
B = ((p_bxnx3) * norm_bxnx3).sum(-1, keepdims=True)
|
||||
|
||||
A_reg = (torch.eye(3, device=p_bxnx3.device) * self.qef_reg_scale).unsqueeze(0).repeat(p_bxnx3.shape[0], 1, 1)
|
||||
B_reg = (self.qef_reg_scale * c_bx3).unsqueeze(-1)
|
||||
A = torch.cat([A, A_reg], 1)
|
||||
B = torch.cat([B, B_reg], 1)
|
||||
dual_verts = torch.linalg.lstsq(A, B).solution.squeeze(-1)
|
||||
return dual_verts
|
||||
|
||||
def _compute_vd(self, x_nx3, surf_cubes_fx8, surf_edges, s_n, case_ids, beta_fx12, alpha_fx8, gamma_f, idx_map, grad_func):
|
||||
"""
|
||||
Computes the location of dual vertices as described in Section 4.2
|
||||
"""
|
||||
alpha_nx12x2 = torch.index_select(input=alpha_fx8, index=self.cube_edges, dim=1).reshape(-1, 12, 2)
|
||||
surf_edges_x = torch.index_select(input=x_nx3, index=surf_edges.reshape(-1), dim=0).reshape(-1, 2, 3)
|
||||
surf_edges_s = torch.index_select(input=s_n, index=surf_edges.reshape(-1), dim=0).reshape(-1, 2, 1)
|
||||
zero_crossing = self._linear_interp(surf_edges_s, surf_edges_x)
|
||||
|
||||
idx_map = idx_map.reshape(-1, 12)
|
||||
num_vd = torch.index_select(input=self.num_vd_table, index=case_ids, dim=0)
|
||||
edge_group, edge_group_to_vd, edge_group_to_cube, vd_num_edges, vd_gamma = [], [], [], [], []
|
||||
|
||||
total_num_vd = 0
|
||||
vd_idx_map = torch.zeros((case_ids.shape[0], 12), dtype=torch.long, device=self.device, requires_grad=False)
|
||||
if grad_func is not None:
|
||||
normals = torch.nn.functional.normalize(grad_func(zero_crossing), dim=-1)
|
||||
vd = []
|
||||
for num in torch.unique(num_vd):
|
||||
cur_cubes = (num_vd == num) # consider cubes with the same numbers of vd emitted (for batching)
|
||||
curr_num_vd = cur_cubes.sum() * num
|
||||
curr_edge_group = self.dmc_table[case_ids[cur_cubes], :num].reshape(-1, num * 7)
|
||||
curr_edge_group_to_vd = torch.arange(
|
||||
curr_num_vd, device=self.device).unsqueeze(-1).repeat(1, 7) + total_num_vd
|
||||
total_num_vd += curr_num_vd
|
||||
curr_edge_group_to_cube = torch.arange(idx_map.shape[0], device=self.device)[
|
||||
cur_cubes].unsqueeze(-1).repeat(1, num * 7).reshape_as(curr_edge_group)
|
||||
|
||||
curr_mask = (curr_edge_group != -1)
|
||||
edge_group.append(torch.masked_select(curr_edge_group, curr_mask))
|
||||
edge_group_to_vd.append(torch.masked_select(curr_edge_group_to_vd.reshape_as(curr_edge_group), curr_mask))
|
||||
edge_group_to_cube.append(torch.masked_select(curr_edge_group_to_cube, curr_mask))
|
||||
vd_num_edges.append(curr_mask.reshape(-1, 7).sum(-1, keepdims=True))
|
||||
vd_gamma.append(torch.masked_select(gamma_f, cur_cubes).unsqueeze(-1).repeat(1, num).reshape(-1))
|
||||
|
||||
if grad_func is not None:
|
||||
with torch.no_grad():
|
||||
cube_e_verts_idx = idx_map[cur_cubes]
|
||||
curr_edge_group[~curr_mask] = 0
|
||||
|
||||
verts_group_idx = torch.gather(input=cube_e_verts_idx, dim=1, index=curr_edge_group)
|
||||
verts_group_idx[verts_group_idx == -1] = 0
|
||||
verts_group_pos = torch.index_select(
|
||||
input=zero_crossing, index=verts_group_idx.reshape(-1), dim=0).reshape(-1, num.item(), 7, 3)
|
||||
v0 = x_nx3[surf_cubes_fx8[cur_cubes][:, 0]].reshape(-1, 1, 1, 3).repeat(1, num.item(), 1, 1)
|
||||
curr_mask = curr_mask.reshape(-1, num.item(), 7, 1)
|
||||
verts_centroid = (verts_group_pos * curr_mask).sum(2) / (curr_mask.sum(2))
|
||||
|
||||
normals_bx7x3 = torch.index_select(input=normals, index=verts_group_idx.reshape(-1), dim=0).reshape(
|
||||
-1, num.item(), 7,
|
||||
3)
|
||||
curr_mask = curr_mask.squeeze(2)
|
||||
vd.append(self._solve_vd_QEF((verts_group_pos - v0) * curr_mask, normals_bx7x3 * curr_mask,
|
||||
verts_centroid - v0.squeeze(2)) + v0.reshape(-1, 3))
|
||||
edge_group = torch.cat(edge_group)
|
||||
edge_group_to_vd = torch.cat(edge_group_to_vd)
|
||||
edge_group_to_cube = torch.cat(edge_group_to_cube)
|
||||
vd_num_edges = torch.cat(vd_num_edges)
|
||||
vd_gamma = torch.cat(vd_gamma)
|
||||
|
||||
if grad_func is not None:
|
||||
vd = torch.cat(vd)
|
||||
L_dev = torch.zeros([1], device=self.device)
|
||||
else:
|
||||
vd = torch.zeros((total_num_vd, 3), device=self.device)
|
||||
beta_sum = torch.zeros((total_num_vd, 1), device=self.device)
|
||||
|
||||
idx_group = torch.gather(input=idx_map.reshape(-1), dim=0, index=edge_group_to_cube * 12 + edge_group)
|
||||
|
||||
x_group = torch.index_select(input=surf_edges_x, index=idx_group.reshape(-1), dim=0).reshape(-1, 2, 3)
|
||||
s_group = torch.index_select(input=surf_edges_s, index=idx_group.reshape(-1), dim=0).reshape(-1, 2, 1)
|
||||
|
||||
zero_crossing_group = torch.index_select(
|
||||
input=zero_crossing, index=idx_group.reshape(-1), dim=0).reshape(-1, 3)
|
||||
|
||||
alpha_group = torch.index_select(input=alpha_nx12x2.reshape(-1, 2), dim=0,
|
||||
index=edge_group_to_cube * 12 + edge_group).reshape(-1, 2, 1)
|
||||
ue_group = self._linear_interp(s_group * alpha_group, x_group)
|
||||
|
||||
beta_group = torch.gather(input=beta_fx12.reshape(-1), dim=0,
|
||||
index=edge_group_to_cube * 12 + edge_group).reshape(-1, 1)
|
||||
beta_sum = beta_sum.index_add_(0, index=edge_group_to_vd, source=beta_group)
|
||||
vd = vd.index_add_(0, index=edge_group_to_vd, source=ue_group * beta_group) / beta_sum
|
||||
L_dev = self._compute_reg_loss(vd, zero_crossing_group, edge_group_to_vd, vd_num_edges)
|
||||
|
||||
v_idx = torch.arange(vd.shape[0], device=self.device) # + total_num_vd
|
||||
|
||||
vd_idx_map = (vd_idx_map.reshape(-1)).scatter(dim=0, index=edge_group_to_cube *
|
||||
12 + edge_group, src=v_idx[edge_group_to_vd])
|
||||
|
||||
return vd, L_dev, vd_gamma, vd_idx_map
|
||||
|
||||
def _triangulate(self, s_n, surf_edges, vd, vd_gamma, edge_counts, idx_map, vd_idx_map, surf_edges_mask, training, grad_func):
|
||||
"""
|
||||
Connects four neighboring dual vertices to form a quadrilateral. The quadrilaterals are then split into
|
||||
triangles based on the gamma parameter, as described in Section 4.3.
|
||||
"""
|
||||
with torch.no_grad():
|
||||
group_mask = (edge_counts == 4) & surf_edges_mask # surface edges shared by 4 cubes.
|
||||
group = idx_map.reshape(-1)[group_mask]
|
||||
vd_idx = vd_idx_map[group_mask]
|
||||
edge_indices, indices = torch.sort(group, stable=True)
|
||||
quad_vd_idx = vd_idx[indices].reshape(-1, 4)
|
||||
|
||||
# Ensure all face directions point towards the positive SDF to maintain consistent winding.
|
||||
s_edges = s_n[surf_edges[edge_indices.reshape(-1, 4)[:, 0]].reshape(-1)].reshape(-1, 2)
|
||||
flip_mask = s_edges[:, 0] > 0
|
||||
quad_vd_idx = torch.cat((quad_vd_idx[flip_mask][:, [0, 1, 3, 2]],
|
||||
quad_vd_idx[~flip_mask][:, [2, 3, 1, 0]]))
|
||||
if grad_func is not None:
|
||||
# when grad_func is given, split quadrilaterals along the diagonals with more consistent gradients.
|
||||
with torch.no_grad():
|
||||
vd_gamma = torch.nn.functional.normalize(grad_func(vd), dim=-1)
|
||||
quad_gamma = torch.index_select(input=vd_gamma, index=quad_vd_idx.reshape(-1), dim=0).reshape(-1, 4, 3)
|
||||
gamma_02 = (quad_gamma[:, 0] * quad_gamma[:, 2]).sum(-1, keepdims=True)
|
||||
gamma_13 = (quad_gamma[:, 1] * quad_gamma[:, 3]).sum(-1, keepdims=True)
|
||||
else:
|
||||
quad_gamma = torch.index_select(input=vd_gamma, index=quad_vd_idx.reshape(-1), dim=0).reshape(-1, 4)
|
||||
gamma_02 = torch.index_select(input=quad_gamma, index=torch.tensor(
|
||||
0, device=self.device), dim=1) * torch.index_select(input=quad_gamma, index=torch.tensor(2, device=self.device), dim=1)
|
||||
gamma_13 = torch.index_select(input=quad_gamma, index=torch.tensor(
|
||||
1, device=self.device), dim=1) * torch.index_select(input=quad_gamma, index=torch.tensor(3, device=self.device), dim=1)
|
||||
if not training:
|
||||
mask = (gamma_02 > gamma_13).squeeze(1)
|
||||
faces = torch.zeros((quad_gamma.shape[0], 6), dtype=torch.long, device=quad_vd_idx.device)
|
||||
faces[mask] = quad_vd_idx[mask][:, self.quad_split_1]
|
||||
faces[~mask] = quad_vd_idx[~mask][:, self.quad_split_2]
|
||||
faces = faces.reshape(-1, 3)
|
||||
else:
|
||||
vd_quad = torch.index_select(input=vd, index=quad_vd_idx.reshape(-1), dim=0).reshape(-1, 4, 3)
|
||||
vd_02 = (torch.index_select(input=vd_quad, index=torch.tensor(0, device=self.device), dim=1) +
|
||||
torch.index_select(input=vd_quad, index=torch.tensor(2, device=self.device), dim=1)) / 2
|
||||
vd_13 = (torch.index_select(input=vd_quad, index=torch.tensor(1, device=self.device), dim=1) +
|
||||
torch.index_select(input=vd_quad, index=torch.tensor(3, device=self.device), dim=1)) / 2
|
||||
weight_sum = (gamma_02 + gamma_13) + 1e-8
|
||||
vd_center = ((vd_02 * gamma_02.unsqueeze(-1) + vd_13 * gamma_13.unsqueeze(-1)) /
|
||||
weight_sum.unsqueeze(-1)).squeeze(1)
|
||||
vd_center_idx = torch.arange(vd_center.shape[0], device=self.device) + vd.shape[0]
|
||||
vd = torch.cat([vd, vd_center])
|
||||
faces = quad_vd_idx[:, self.quad_split_train].reshape(-1, 4, 2)
|
||||
faces = torch.cat([faces, vd_center_idx.reshape(-1, 1, 1).repeat(1, 4, 1)], -1).reshape(-1, 3)
|
||||
return vd, faces, s_edges, edge_indices
|
||||
|
||||
def _tetrahedralize(
|
||||
self, x_nx3, s_n, cube_fx8, vertices, faces, surf_edges, s_edges, vd_idx_map, case_ids, edge_indices,
|
||||
surf_cubes, training):
|
||||
"""
|
||||
Tetrahedralizes the interior volume to produce a tetrahedral mesh, as described in Section 4.5.
|
||||
"""
|
||||
occ_n = s_n < 0
|
||||
occ_fx8 = occ_n[cube_fx8.reshape(-1)].reshape(-1, 8)
|
||||
occ_sum = torch.sum(occ_fx8, -1)
|
||||
|
||||
inside_verts = x_nx3[occ_n]
|
||||
mapping_inside_verts = torch.ones((occ_n.shape[0]), dtype=torch.long, device=self.device) * -1
|
||||
mapping_inside_verts[occ_n] = torch.arange(occ_n.sum(), device=self.device) + vertices.shape[0]
|
||||
"""
|
||||
For each grid edge connecting two grid vertices with different
|
||||
signs, we first form a four-sided pyramid by connecting one
|
||||
of the grid vertices with four mesh vertices that correspond
|
||||
to the grid edge and then subdivide the pyramid into two tetrahedra
|
||||
"""
|
||||
inside_verts_idx = mapping_inside_verts[surf_edges[edge_indices.reshape(-1, 4)[:, 0]].reshape(-1, 2)[
|
||||
s_edges < 0]]
|
||||
if not training:
|
||||
inside_verts_idx = inside_verts_idx.unsqueeze(1).expand(-1, 2).reshape(-1)
|
||||
else:
|
||||
inside_verts_idx = inside_verts_idx.unsqueeze(1).expand(-1, 4).reshape(-1)
|
||||
|
||||
tets_surface = torch.cat([faces, inside_verts_idx.unsqueeze(-1)], -1)
|
||||
"""
|
||||
For each grid edge connecting two grid vertices with the
|
||||
same sign, the tetrahedron is formed by the two grid vertices
|
||||
and two vertices in consecutive adjacent cells
|
||||
"""
|
||||
inside_cubes = (occ_sum == 8)
|
||||
inside_cubes_center = x_nx3[cube_fx8[inside_cubes].reshape(-1)].reshape(-1, 8, 3).mean(1)
|
||||
inside_cubes_center_idx = torch.arange(
|
||||
inside_cubes_center.shape[0], device=inside_cubes.device) + vertices.shape[0] + inside_verts.shape[0]
|
||||
|
||||
surface_n_inside_cubes = surf_cubes | inside_cubes
|
||||
edge_center_vertex_idx = torch.ones(((surface_n_inside_cubes).sum(), 13),
|
||||
dtype=torch.long, device=x_nx3.device) * -1
|
||||
surf_cubes = surf_cubes[surface_n_inside_cubes]
|
||||
inside_cubes = inside_cubes[surface_n_inside_cubes]
|
||||
edge_center_vertex_idx[surf_cubes, :12] = vd_idx_map.reshape(-1, 12)
|
||||
edge_center_vertex_idx[inside_cubes, 12] = inside_cubes_center_idx
|
||||
|
||||
all_edges = cube_fx8[surface_n_inside_cubes][:, self.cube_edges].reshape(-1, 2)
|
||||
unique_edges, _idx_map, counts = torch.unique(all_edges, dim=0, return_inverse=True, return_counts=True)
|
||||
unique_edges = unique_edges.long()
|
||||
mask_edges = occ_n[unique_edges.reshape(-1)].reshape(-1, 2).sum(-1) == 2
|
||||
mask = mask_edges[_idx_map]
|
||||
counts = counts[_idx_map]
|
||||
mapping = torch.ones((unique_edges.shape[0]), dtype=torch.long, device=self.device) * -1
|
||||
mapping[mask_edges] = torch.arange(mask_edges.sum(), device=self.device)
|
||||
idx_map = mapping[_idx_map]
|
||||
|
||||
group_mask = (counts == 4) & mask
|
||||
group = idx_map.reshape(-1)[group_mask]
|
||||
edge_indices, indices = torch.sort(group)
|
||||
cube_idx = torch.arange((_idx_map.shape[0] // 12), dtype=torch.long,
|
||||
device=self.device).unsqueeze(1).expand(-1, 12).reshape(-1)[group_mask]
|
||||
edge_idx = torch.arange((12), dtype=torch.long, device=self.device).unsqueeze(
|
||||
0).expand(_idx_map.shape[0] // 12, -1).reshape(-1)[group_mask]
|
||||
# Identify the face shared by the adjacent cells.
|
||||
cube_idx_4 = cube_idx[indices].reshape(-1, 4)
|
||||
edge_dir = self.edge_dir_table[edge_idx[indices]].reshape(-1, 4)[..., 0]
|
||||
shared_faces_4x2 = self.dir_faces_table[edge_dir].reshape(-1)
|
||||
cube_idx_4x2 = cube_idx_4[:, self.adj_pairs].reshape(-1)
|
||||
# Identify an edge of the face with different signs and
|
||||
# select the mesh vertex corresponding to the identified edge.
|
||||
case_ids_expand = torch.ones((surface_n_inside_cubes).sum(), dtype=torch.long, device=x_nx3.device) * 255
|
||||
case_ids_expand[surf_cubes] = case_ids
|
||||
cases = case_ids_expand[cube_idx_4x2]
|
||||
quad_edge = edge_center_vertex_idx[cube_idx_4x2, self.tet_table[cases, shared_faces_4x2]].reshape(-1, 2)
|
||||
mask = (quad_edge == -1).sum(-1) == 0
|
||||
inside_edge = mapping_inside_verts[unique_edges[mask_edges][edge_indices].reshape(-1)].reshape(-1, 2)
|
||||
tets_inside = torch.cat([quad_edge, inside_edge], -1)[mask]
|
||||
|
||||
tets = torch.cat([tets_surface, tets_inside])
|
||||
vertices = torch.cat([vertices, inside_verts, inside_cubes_center])
|
||||
return vertices, tets
|
||||
@@ -0,0 +1,116 @@
|
||||
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
#
|
||||
# NVIDIA CORPORATION & AFFILIATES 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 & AFFILIATES is strictly prohibited.
|
||||
|
||||
import torch
|
||||
from CRM_T2I_V2.util.flexicubes import FlexiCubes # replace later
|
||||
# from dmtet import sdf_reg_loss_batch
|
||||
import torch.nn.functional as F
|
||||
|
||||
def get_center_boundary_index(grid_res, device):
|
||||
v = torch.zeros((grid_res + 1, grid_res + 1, grid_res + 1), dtype=torch.bool, device=device)
|
||||
v[grid_res // 2 + 1, grid_res // 2 + 1, grid_res // 2 + 1] = True
|
||||
center_indices = torch.nonzero(v.reshape(-1))
|
||||
|
||||
v[grid_res // 2 + 1, grid_res // 2 + 1, grid_res // 2 + 1] = False
|
||||
v[:2, ...] = True
|
||||
v[-2:, ...] = True
|
||||
v[:, :2, ...] = True
|
||||
v[:, -2:, ...] = True
|
||||
v[:, :, :2] = True
|
||||
v[:, :, -2:] = True
|
||||
boundary_indices = torch.nonzero(v.reshape(-1))
|
||||
return center_indices, boundary_indices
|
||||
|
||||
###############################################################################
|
||||
# Geometry interface
|
||||
###############################################################################
|
||||
class FlexiCubesGeometry(object):
|
||||
def __init__(
|
||||
self, grid_res=64, scale=2.0, device='cuda', renderer=None,
|
||||
render_type='neural_render', args=None):
|
||||
super(FlexiCubesGeometry, self).__init__()
|
||||
self.grid_res = grid_res
|
||||
self.device = device
|
||||
self.args = args
|
||||
self.fc = FlexiCubes(device, weight_scale=0.5)
|
||||
self.verts, self.indices = self.fc.construct_voxel_grid(grid_res)
|
||||
if isinstance(scale, list):
|
||||
self.verts[:, 0] = self.verts[:, 0] * scale[0]
|
||||
self.verts[:, 1] = self.verts[:, 1] * scale[1]
|
||||
self.verts[:, 2] = self.verts[:, 2] * scale[1]
|
||||
else:
|
||||
self.verts = self.verts * scale
|
||||
|
||||
all_edges = self.indices[:, self.fc.cube_edges].reshape(-1, 2)
|
||||
self.all_edges = torch.unique(all_edges, dim=0)
|
||||
|
||||
# Parameters used for fix boundary sdf
|
||||
self.center_indices, self.boundary_indices = get_center_boundary_index(self.grid_res, device)
|
||||
self.renderer = renderer
|
||||
self.render_type = render_type
|
||||
|
||||
def getAABB(self):
|
||||
return torch.min(self.verts, dim=0).values, torch.max(self.verts, dim=0).values
|
||||
|
||||
def get_mesh(self, v_deformed_nx3, sdf_n, weight_n=None, with_uv=False, indices=None, is_training=False):
|
||||
if indices is None:
|
||||
indices = self.indices
|
||||
|
||||
verts, faces, v_reg_loss = self.fc(v_deformed_nx3, sdf_n, indices, self.grid_res,
|
||||
beta_fx12=weight_n[:, :12], alpha_fx8=weight_n[:, 12:20],
|
||||
gamma_f=weight_n[:, 20], training=is_training
|
||||
)
|
||||
return verts, faces, v_reg_loss
|
||||
|
||||
|
||||
def render_mesh(self, mesh_v_nx3, mesh_f_fx3, camera_mv_bx4x4, resolution=256, hierarchical_mask=False):
|
||||
return_value = dict()
|
||||
if self.render_type == 'neural_render':
|
||||
tex_pos, mask, hard_mask, rast, v_pos_clip, mask_pyramid, depth = self.renderer.render_mesh(
|
||||
mesh_v_nx3.unsqueeze(dim=0),
|
||||
mesh_f_fx3.int(),
|
||||
camera_mv_bx4x4,
|
||||
mesh_v_nx3.unsqueeze(dim=0),
|
||||
resolution=resolution,
|
||||
device=self.device,
|
||||
hierarchical_mask=hierarchical_mask
|
||||
)
|
||||
|
||||
return_value['tex_pos'] = tex_pos
|
||||
return_value['mask'] = mask
|
||||
return_value['hard_mask'] = hard_mask
|
||||
return_value['rast'] = rast
|
||||
return_value['v_pos_clip'] = v_pos_clip
|
||||
return_value['mask_pyramid'] = mask_pyramid
|
||||
return_value['depth'] = depth
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
return return_value
|
||||
|
||||
def render(self, v_deformed_bxnx3=None, sdf_bxn=None, camera_mv_bxnviewx4x4=None, resolution=256):
|
||||
# Here I assume a batch of meshes (can be different mesh and geometry), for the other shapes, the batch is 1
|
||||
v_list = []
|
||||
f_list = []
|
||||
n_batch = v_deformed_bxnx3.shape[0]
|
||||
all_render_output = []
|
||||
for i_batch in range(n_batch):
|
||||
verts_nx3, faces_fx3 = self.get_mesh(v_deformed_bxnx3[i_batch], sdf_bxn[i_batch])
|
||||
v_list.append(verts_nx3)
|
||||
f_list.append(faces_fx3)
|
||||
render_output = self.render_mesh(verts_nx3, faces_fx3, camera_mv_bxnviewx4x4[i_batch], resolution)
|
||||
all_render_output.append(render_output)
|
||||
|
||||
# Concatenate all render output
|
||||
return_keys = all_render_output[0].keys()
|
||||
return_value = dict()
|
||||
for k in return_keys:
|
||||
value = [v[k] for v in all_render_output]
|
||||
return_value[k] = value
|
||||
# We can do concatenation outside of the render
|
||||
return return_value
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import nvdiffrast.torch as dr
|
||||
from CRM_T2I_V2.util.flexicubes_geometry import FlexiCubesGeometry
|
||||
|
||||
class Renderer(nn.Module):
|
||||
def __init__(self, tet_grid_size, camera_angle_num, scale, geo_type):
|
||||
super().__init__()
|
||||
|
||||
self.tet_grid_size = tet_grid_size
|
||||
self.camera_angle_num = camera_angle_num
|
||||
self.scale = scale
|
||||
self.geo_type = geo_type
|
||||
self.glctx = dr.RasterizeCudaContext()
|
||||
|
||||
if self.geo_type == "flex":
|
||||
self.flexicubes = FlexiCubesGeometry(grid_res = self.tet_grid_size)
|
||||
|
||||
def forward(self, data, sdf, deform, verts, tets, training=False, weight = None):
|
||||
|
||||
results = {}
|
||||
|
||||
deform = torch.tanh(deform) / self.tet_grid_size * self.scale / 0.95
|
||||
if self.geo_type == "flex":
|
||||
deform = deform *0.5
|
||||
|
||||
v_deformed = verts + deform
|
||||
|
||||
verts_list = []
|
||||
faces_list = []
|
||||
reg_list = []
|
||||
n_shape = verts.shape[0]
|
||||
for i in range(n_shape):
|
||||
verts_i, faces_i, reg_i = self.flexicubes.get_mesh(v_deformed[i], sdf[i].squeeze(dim=-1),
|
||||
with_uv=False, indices=tets, weight_n=weight[i], is_training=training)
|
||||
|
||||
verts_list.append(verts_i)
|
||||
faces_list.append(faces_i)
|
||||
reg_list.append(reg_i)
|
||||
verts = verts_list
|
||||
faces = faces_list
|
||||
|
||||
flexicubes_surface_reg = torch.cat(reg_list).mean()
|
||||
flexicubes_weight_reg = (weight ** 2).mean()
|
||||
results["flex_surf_loss"] = flexicubes_surface_reg
|
||||
results["flex_weight_loss"] = flexicubes_weight_reg
|
||||
|
||||
return results, verts, faces
|
||||
@@ -0,0 +1,791 @@
|
||||
# Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
#
|
||||
# NVIDIA CORPORATION & AFFILIATES 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 & AFFILIATES is strictly prohibited.
|
||||
dmc_table = [
|
||||
[[-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 8, 9, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 7, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 5, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 5, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 8, 9, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 7, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 7, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 5, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 8, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 8, 9, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 7, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [4, 7, 8, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 7, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 8, 11, -1, -1, -1], [4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 5, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 5, 8, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 8, 9, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 7, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 7, 8, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 5, 7, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 9, 10, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 8, 9, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 7, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 9, 10, -1, -1, -1], [4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 7, 9, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [4, 5, 9, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 5, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 5, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 8, 9, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 7, 9, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 7, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 5, 7, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 8, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 9, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[8, 9, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [1, 3, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 7, 10, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 9, 10, 11, -1, -1], [4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 9, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [1, 3, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 8, 10, 11, -1, -1], [4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 5, 10, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 8, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 8, 9, -1, -1, -1], [1, 3, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 7, 9, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 7, 8, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 8, 9, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 6, 8, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 6, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [4, 6, 8, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 6, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [4, 5, 9, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 5, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 5, 8, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 6, 8, 9, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 6, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 6, 8, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 5, 6, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 6, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 6, 7, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [2, 3, 6, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 6, 7, 8, 9, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 6, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [2, 3, 4, 6, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 6, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [2, 3, 6, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 6, 7, 8, -1, -1], [4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 5, -1, -1, -1], [2, 3, 6, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 5, 6, 7, 8], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 5, 6, 8, 9, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 6, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 2, 3, 5, 6, 8], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 5, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 10, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 9, 10, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 8, 9, 10, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 6, 8, 11, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 6, 11, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 9, 10, -1, -1, -1], [4, 6, 8, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 6, 9, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [4, 5, 9, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 5, 10, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 5, 8, 10, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 6, 8, 9, 11, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 6, 9, 11, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 6, 8, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 5, 6, 10, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 6, 7, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 6, 7, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 6, 7, 9, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[6, 7, 8, 9, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 6, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 6, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 6, 8, 9, 10], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 6, 9, 10, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [1, 3, 6, 7, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 6, 7, 8, 10, -1], [4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 5, 6, 7, 10], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 6, 7, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 5, 6, 8, 9, 10], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 6, 9, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 8, 9, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 7, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [4, 7, 8, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 7, 9, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 6, 9, 10, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [4, 6, 9, 10, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 6, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 6, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[6, 7, 8, 9, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 6, 7, 9, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 6, 7, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 6, 7, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 11, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 8, 11, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 8, 9, 11, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 7, 11, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [4, 7, 8, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 7, 9, 11, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 6, 9, 10, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 8, 11, -1, -1, -1], [4, 6, 9, 10, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 6, 10, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 6, 8, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[6, 7, 8, 9, 10, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 6, 7, 9, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 6, 7, 8, 10, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 6, 7, 10, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 5, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [1, 2, 5, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 6, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 5, 6, 8, 9, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [1, 2, 5, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 7, -1, -1, -1], [1, 2, 5, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 6, 9, -1, -1], [4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 5, 6, 7, 9], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 6, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [1, 2, 4, 6, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 6, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 6, 7, 8, 9, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 2, 3, 6, 7, 9], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 6, 7, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 6, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 5, 6, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 6, 8, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 6, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 6, 8, 9, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [1, 3, 5, 6, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 5, 6, 7, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 6, 9, 11, -1], [4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 6, 7, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 6, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 6, 8, 9, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 6, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 6, 8, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 6, 7, 8, 9, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 6, 7, 8, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [5, 7, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [5, 7, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 8, 9, -1, -1, -1], [5, 7, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 8, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 5, 10, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [4, 5, 8, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 5, 9, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 9, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [4, 7, 9, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 7, 10, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 7, 8, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[8, 9, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 9, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 8, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 5, 7, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 7, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [2, 3, 5, 7, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 5, 7, 8, 9, 10], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 5, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 5, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [2, 3, 4, 5, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 5, 9, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 7, 9, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 7, 8, 9, 10], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 2, 3, 4, 7, 10], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 8, 9, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 9, 10, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 2, 3, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 5, 7, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [1, 2, 5, 7, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 7, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 5, 7, 8, 9, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 5, 8, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 2, 3, 4, 5, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 5, 8, 9, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 7, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [1, 2, 4, 7, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 7, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 7, 8, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 8, 9, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 2, 3, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 8, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 5, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 7, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 7, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 8, 9, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 5, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 5, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 5, 8, 9, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 7, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 7, 8, 9, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 8, 9, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]]
|
||||
]
|
||||
num_vd_table = [0, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 2, 2,
|
||||
2, 1, 2, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 1, 2, 3, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2,
|
||||
1, 2, 1, 2, 2, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 2, 3, 2, 2, 1, 1, 1, 1,
|
||||
1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 2, 1, 1, 1, 1, 1, 2, 3, 2, 2, 2, 2, 2, 1, 3, 4, 2,
|
||||
2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 2, 2, 2,
|
||||
3, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 3, 2, 3, 2, 4, 2, 2, 2, 2, 1, 2, 1, 2, 1, 1,
|
||||
2, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1,
|
||||
1, 2, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2,
|
||||
1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
|
||||
check_table = [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 194],
|
||||
[1, -1, 0, 0, 193],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 164],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, -1, 0, 161],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 1, 152],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 1, 145],
|
||||
[1, 0, 0, 1, 144],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, -1, 137],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 133],
|
||||
[1, 0, 1, 0, 132],
|
||||
[1, 1, 0, 0, 131],
|
||||
[1, 1, 0, 0, 130],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 1, 100],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 1, 98],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 1, 96],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 88],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, -1, 0, 82],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 74],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 72],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, -1, 70],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, -1, 0, 0, 67],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, -1, 0, 0, 65],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 56],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, -1, 0, 0, 52],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 44],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 40],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, -1, 38],
|
||||
[1, 0, -1, 0, 37],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, -1, 0, 33],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, -1, 0, 0, 28],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, -1, 0, 26],
|
||||
[1, 0, 0, -1, 25],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, -1, 0, 0, 20],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, -1, 0, 18],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, -1, 9],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, -1, 6],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]
|
||||
]
|
||||
tet_table = [
|
||||
[-1, -1, -1, -1, -1, -1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 0, 0, 4, 4, -1],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[0, 4, 0, 4, 4, -1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 0, 2, -1, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, -1, 2, 4, 4, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 0, 2, 4, 4, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 4, 2, 4, 4, 2],
|
||||
[0, 4, 0, 4, 4, 0],
|
||||
[2, 0, 2, 0, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 5, 2, 5, 5, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 0, 2, 0, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, -1, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[4, 1, 1, 4, 4, 1],
|
||||
[0, 1, 1, 0, 0, 1],
|
||||
[4, 0, 0, 4, 4, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[-1, 1, 1, 4, 4, 1],
|
||||
[0, 1, 1, 4, 4, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[5, 1, 1, 5, 5, 1],
|
||||
[0, 1, 1, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[8, 8, 8, 8, 8, 8],
|
||||
[1, 1, 1, 4, 4, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 0, 0, 4, 4, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 4, 4, 1],
|
||||
[0, 4, 0, 4, 4, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 5, 5, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[6, 6, 6, 6, 6, 6],
|
||||
[6, -1, 0, 6, 0, 6],
|
||||
[6, 0, 0, 6, 0, 6],
|
||||
[6, 1, 1, 6, 1, 6],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 0, 0, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[6, 4, -1, 6, 4, 6],
|
||||
[6, 4, 0, 6, 4, 6],
|
||||
[6, 0, 0, 6, 0, 6],
|
||||
[6, 1, 1, 6, 1, 6],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 0, 2, 2, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 0, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 4, 2, 2, 4, 2],
|
||||
[0, 4, 0, 4, 4, 0],
|
||||
[2, 0, 2, 2, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[6, 1, 1, 6, -1, 6],
|
||||
[6, 1, 1, 6, 0, 6],
|
||||
[6, 0, 0, 6, 0, 6],
|
||||
[6, 2, 2, 6, 2, 6],
|
||||
[4, 1, 1, 4, 4, 1],
|
||||
[0, 1, 1, 0, 0, 1],
|
||||
[4, 0, 0, 4, 4, 4],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[6, 1, 1, 6, 4, 6],
|
||||
[6, 1, 1, 6, 4, 6],
|
||||
[6, 0, 0, 6, 0, 6],
|
||||
[6, 2, 2, 6, 2, 6],
|
||||
[5, 1, 1, 5, 5, 1],
|
||||
[0, 1, 1, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[6, 6, 6, 6, 6, 6],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 1, 4, 1],
|
||||
[0, 4, 0, 4, 4, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 5, 0, 5, 0, 5],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[0, 5, 0, 5, 0, 5],
|
||||
[-1, 5, 0, 5, 0, 5],
|
||||
[1, 5, 1, 5, 1, 5],
|
||||
[4, 5, -1, 5, 4, 5],
|
||||
[0, 5, 0, 5, 0, 5],
|
||||
[4, 5, 0, 5, 4, 5],
|
||||
[1, 5, 1, 5, 1, 5],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[0, 4, 0, 4, 4, 4],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[6, 6, 6, 6, 6, 6],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 5, 2, 5, -1, 5],
|
||||
[0, 5, 0, 5, 0, 5],
|
||||
[2, 5, 2, 5, 0, 5],
|
||||
[1, 5, 1, 5, 1, 5],
|
||||
[2, 5, 2, 5, 4, 5],
|
||||
[0, 5, 0, 5, 0, 5],
|
||||
[2, 5, 2, 5, 4, 5],
|
||||
[1, 5, 1, 5, 1, 5],
|
||||
[2, 4, 2, 4, 4, 2],
|
||||
[0, 4, 0, 4, 4, 4],
|
||||
[2, 0, 2, 0, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 6, 2, 6, 6, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 0, 2, 0, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[4, 1, 1, 1, 4, 1],
|
||||
[0, 1, 1, 1, 0, 1],
|
||||
[4, 0, 0, 4, 4, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[1, 1, 1, 1, 4, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 0, 0, 4, 4, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[6, 0, 0, 6, 0, 6],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[6, 6, 6, 6, 6, 6],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[5, 5, 0, 5, 0, 5],
|
||||
[5, 5, 0, 5, 0, 5],
|
||||
[5, 5, 1, 5, 1, 5],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 0, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[4, 4, 0, 4, 4, 4],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[8, 8, 8, 8, 8, 8],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 1, 1, 4, 4, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 4, 2, 4, 4, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[12, 12, 12, 12, 12, 12]
|
||||
]
|
||||
@@ -0,0 +1,194 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
import random
|
||||
|
||||
|
||||
# Reworked so this matches gluPerspective / glm::perspective, using fovy
|
||||
def perspective(fovx=0.7854, aspect=1.0, n=0.1, f=1000.0, device=None):
|
||||
# y = np.tan(fovy / 2)
|
||||
x = np.tan(fovx / 2)
|
||||
return torch.tensor([[1/x, 0, 0, 0],
|
||||
[ 0, -aspect/x, 0, 0],
|
||||
[ 0, 0, -(f+n)/(f-n), -(2*f*n)/(f-n)],
|
||||
[ 0, 0, -1, 0]], dtype=torch.float32, device=device)
|
||||
|
||||
|
||||
def translate(x, y, z, device=None):
|
||||
return torch.tensor([[1, 0, 0, x],
|
||||
[0, 1, 0, y],
|
||||
[0, 0, 1, z],
|
||||
[0, 0, 0, 1]], dtype=torch.float32, device=device)
|
||||
|
||||
|
||||
def rotate_x(a, device=None):
|
||||
s, c = np.sin(a), np.cos(a)
|
||||
return torch.tensor([[1, 0, 0, 0],
|
||||
[0, c, -s, 0],
|
||||
[0, s, c, 0],
|
||||
[0, 0, 0, 1]], dtype=torch.float32, device=device)
|
||||
|
||||
|
||||
def rotate_y(a, device=None):
|
||||
s, c = np.sin(a), np.cos(a)
|
||||
return torch.tensor([[ c, 0, s, 0],
|
||||
[ 0, 1, 0, 0],
|
||||
[-s, 0, c, 0],
|
||||
[ 0, 0, 0, 1]], dtype=torch.float32, device=device)
|
||||
|
||||
|
||||
def rotate_z(a, device=None):
|
||||
s, c = np.sin(a), np.cos(a)
|
||||
return torch.tensor([[c, -s, 0, 0],
|
||||
[s, c, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1]], dtype=torch.float32, device=device)
|
||||
|
||||
@torch.no_grad()
|
||||
def batch_random_rotation_translation(b, t, device=None):
|
||||
m = np.random.normal(size=[b, 3, 3])
|
||||
m[:, 1] = np.cross(m[:, 0], m[:, 2])
|
||||
m[:, 2] = np.cross(m[:, 0], m[:, 1])
|
||||
m = m / np.linalg.norm(m, axis=2, keepdims=True)
|
||||
m = np.pad(m, [[0, 0], [0, 1], [0, 1]], mode='constant')
|
||||
m[:, 3, 3] = 1.0
|
||||
m[:, :3, 3] = np.random.uniform(-t, t, size=[b, 3])
|
||||
return torch.tensor(m, dtype=torch.float32, device=device)
|
||||
|
||||
@torch.no_grad()
|
||||
def random_rotation_translation(t, device=None):
|
||||
m = np.random.normal(size=[3, 3])
|
||||
m[1] = np.cross(m[0], m[2])
|
||||
m[2] = np.cross(m[0], m[1])
|
||||
m = m / np.linalg.norm(m, axis=1, keepdims=True)
|
||||
m = np.pad(m, [[0, 1], [0, 1]], mode='constant')
|
||||
m[3, 3] = 1.0
|
||||
m[:3, 3] = np.random.uniform(-t, t, size=[3])
|
||||
return torch.tensor(m, dtype=torch.float32, device=device)
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def random_rotation(device=None):
|
||||
m = np.random.normal(size=[3, 3])
|
||||
m[1] = np.cross(m[0], m[2])
|
||||
m[2] = np.cross(m[0], m[1])
|
||||
m = m / np.linalg.norm(m, axis=1, keepdims=True)
|
||||
m = np.pad(m, [[0, 1], [0, 1]], mode='constant')
|
||||
m[3, 3] = 1.0
|
||||
m[:3, 3] = np.array([0,0,0]).astype(np.float32)
|
||||
return torch.tensor(m, dtype=torch.float32, device=device)
|
||||
|
||||
|
||||
def dot(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
|
||||
return torch.sum(x*y, -1, keepdim=True)
|
||||
|
||||
|
||||
def length(x: torch.Tensor, eps: float =1e-20) -> torch.Tensor:
|
||||
return torch.sqrt(torch.clamp(dot(x,x), min=eps)) # Clamp to avoid nan gradients because grad(sqrt(0)) = NaN
|
||||
|
||||
|
||||
def safe_normalize(x: torch.Tensor, eps: float =1e-20) -> torch.Tensor:
|
||||
return x / length(x, eps)
|
||||
|
||||
|
||||
def lr_schedule(iter, warmup_iter, scheduler_decay):
|
||||
if iter < warmup_iter:
|
||||
return iter / warmup_iter
|
||||
return max(0.0, 10 ** (
|
||||
-(iter - warmup_iter) * scheduler_decay))
|
||||
|
||||
|
||||
def trans_depth(depth):
|
||||
depth = depth[0].detach().cpu().numpy()
|
||||
valid = depth > 0
|
||||
depth[valid] -= depth[valid].min()
|
||||
depth[valid] = ((depth[valid] / depth[valid].max()) * 255)
|
||||
return depth.astype('uint8')
|
||||
|
||||
|
||||
def nan_to_num(input, nan=0.0, posinf=None, neginf=None, *, out=None):
|
||||
assert isinstance(input, torch.Tensor)
|
||||
if posinf is None:
|
||||
posinf = torch.finfo(input.dtype).max
|
||||
if neginf is None:
|
||||
neginf = torch.finfo(input.dtype).min
|
||||
assert nan == 0
|
||||
return torch.clamp(input.unsqueeze(0).nansum(0), min=neginf, max=posinf, out=out)
|
||||
|
||||
|
||||
def load_item(filepath):
|
||||
with open(filepath, 'r') as f:
|
||||
items = [name.strip() for name in f.readlines()]
|
||||
return set(items)
|
||||
|
||||
def load_prompt(filepath):
|
||||
uuid2prompt = {}
|
||||
with open(filepath, 'r') as f:
|
||||
for line in f.readlines():
|
||||
list_line = line.split(',')
|
||||
uuid2prompt[list_line[0]] = ','.join(list_line[1:]).strip()
|
||||
return uuid2prompt
|
||||
|
||||
def resize_and_center_image(image_tensor, scale=0.95, c = 0, shift = 0, rgb=False, aug_shift = 0):
|
||||
if scale == 1:
|
||||
return image_tensor
|
||||
B, C, H, W = image_tensor.shape
|
||||
new_H, new_W = int(H * scale), int(W * scale)
|
||||
resized_image = torch.nn.functional.interpolate(image_tensor, size=(new_H, new_W), mode='bilinear', align_corners=False).squeeze(0)
|
||||
background = torch.zeros_like(image_tensor) + c
|
||||
start_y, start_x = (H - new_H) // 2, (W - new_W) // 2
|
||||
if shift == 0:
|
||||
background[:, :, start_y:start_y + new_H, start_x:start_x + new_W] = resized_image
|
||||
else:
|
||||
for i in range(B):
|
||||
randx = random.randint(-shift, shift)
|
||||
randy = random.randint(-shift, shift)
|
||||
if rgb == True:
|
||||
if i == 0 or i==2 or i==4:
|
||||
randx = 0
|
||||
randy = 0
|
||||
background[i, :, start_y+randy:start_y + new_H+randy, start_x+randx:start_x + new_W+randx] = resized_image[i]
|
||||
if aug_shift == 0:
|
||||
return background
|
||||
for i in range(B):
|
||||
for j in range(C):
|
||||
background[i, j, :, :] += (random.random() - 0.5)*2 * aug_shift / 255
|
||||
return background
|
||||
|
||||
def get_tri(triview_color, dim = 1, blender=True, c = 0, scale=0.95, shift = 0, fix = False, rgb=False, aug_shift = 0):
|
||||
# triview_color: [6,C,H,W]
|
||||
# rgb is useful when shift is not 0
|
||||
triview_color = resize_and_center_image(triview_color, scale=scale, c = c, shift=shift,rgb=rgb, aug_shift = aug_shift)
|
||||
if blender is False:
|
||||
triview_color0 = torch.rot90(triview_color[0],k=2,dims=[1,2])
|
||||
triview_color1 = torch.rot90(triview_color[4],k=1,dims=[1,2]).flip(2).flip(1)
|
||||
triview_color2 = torch.rot90(triview_color[5],k=1,dims=[1,2]).flip(2)
|
||||
triview_color3 = torch.rot90(triview_color[3],k=2,dims=[1,2]).flip(2)
|
||||
triview_color4 = torch.rot90(triview_color[1],k=3,dims=[1,2]).flip(1)
|
||||
triview_color5 = torch.rot90(triview_color[2],k=3,dims=[1,2]).flip(1).flip(2)
|
||||
else:
|
||||
triview_color0 = torch.rot90(triview_color[2],k=2,dims=[1,2])
|
||||
triview_color1 = torch.rot90(triview_color[4],k=0,dims=[1,2]).flip(2).flip(1)
|
||||
triview_color2 = torch.rot90(torch.rot90(triview_color[0],k=3,dims=[1,2]).flip(2), k=2,dims=[1,2])
|
||||
triview_color3 = torch.rot90(torch.rot90(triview_color[5],k=2,dims=[1,2]).flip(2), k=2,dims=[1,2])
|
||||
triview_color4 = torch.rot90(triview_color[1],k=2,dims=[1,2]).flip(1).flip(1).flip(2)
|
||||
triview_color5 = torch.rot90(triview_color[3],k=1,dims=[1,2]).flip(1).flip(2)
|
||||
if fix == True:
|
||||
triview_color0[1] = triview_color0[1] * 0
|
||||
triview_color0[2] = triview_color0[2] * 0
|
||||
triview_color3[1] = triview_color3[1] * 0
|
||||
triview_color3[2] = triview_color3[2] * 0
|
||||
|
||||
triview_color1[0] = triview_color1[0] * 0
|
||||
triview_color1[1] = triview_color1[1] * 0
|
||||
triview_color4[0] = triview_color4[0] * 0
|
||||
triview_color4[1] = triview_color4[1] * 0
|
||||
|
||||
triview_color2[0] = triview_color2[0] * 0
|
||||
triview_color2[2] = triview_color2[2] * 0
|
||||
triview_color5[0] = triview_color5[0] * 0
|
||||
triview_color5[2] = triview_color5[2] * 0
|
||||
color_tensor1_gt = torch.cat((triview_color0, triview_color1, triview_color2), dim=2)
|
||||
color_tensor2_gt = torch.cat((triview_color3, triview_color4, triview_color5), dim=2)
|
||||
color_tensor_gt = torch.cat((color_tensor1_gt, color_tensor2_gt), dim = dim)
|
||||
return color_tensor_gt
|
||||
|
||||
BIN
Binary file not shown.
+4
@@ -14,3 +14,7 @@
|
||||
16413 16494 7442645089064001 _slangtorch_texture_baker_44136fa355b3678a.pyd 9b21a5c60eb03f2c
|
||||
24 11717 7442645206723305 texture_baker.o d2552713bf2b5a12
|
||||
11718 11796 7442645207640080 _slangtorch_texture_baker_44136fa355b3678a.pyd 9b21a5c60eb03f2c
|
||||
36 13104 7445113324154982 texture_baker.o d2552713bf2b5a12
|
||||
13106 13198 7445113325278709 _slangtorch_texture_baker_44136fa355b3678a.pyd 9b21a5c60eb03f2c
|
||||
22 11857 7445113444318882 texture_baker.o d2552713bf2b5a12
|
||||
11859 11938 7445113445275668 _slangtorch_texture_baker_44136fa355b3678a.pyd 9b21a5c60eb03f2c
|
||||
|
||||
BIN
Binary file not shown.
@@ -2,7 +2,7 @@ from typing import List
|
||||
import torch
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from pytorch3d.renderer.cameras import look_at_view_transform, OrthographicCameras, CamerasBase
|
||||
from pytorch3d.renderer.cameras import look_at_view_transform, OrthographicCameras, PerspectiveCameras, CamerasBase
|
||||
from pytorch3d.renderer.mesh.rasterizer import Fragments
|
||||
from pytorch3d.structures import Meshes
|
||||
from pytorch3d.renderer import (
|
||||
@@ -12,13 +12,14 @@ from pytorch3d.renderer import (
|
||||
FoVOrthographicCameras,
|
||||
)
|
||||
from pytorch3d.renderer import MeshRasterizer
|
||||
from mesh_processer.mesh import Mesh
|
||||
|
||||
def get_camera(world_to_cam, fov_in_degrees=60, focal_length=1 / (2**0.5), cam_type='fov'):
|
||||
# pytorch3d expects transforms as row-vectors, so flip rotation: https://github.com/facebookresearch/pytorch3d/issues/1183
|
||||
R = world_to_cam[:3, :3].t()[None, ...]
|
||||
T = world_to_cam[:3, 3][None, ...]
|
||||
if cam_type == 'fov':
|
||||
camera = FoVPerspectiveCameras(device=world_to_cam.device, R=R, T=T, fov=fov_in_degrees, degrees=True)
|
||||
camera = FoVPerspectiveCameras(device=world_to_cam.device, R=R, T=T, fov=fov_in_degrees, degrees=True, znear=0.1)
|
||||
else:
|
||||
focal_length = 1 / focal_length
|
||||
camera = FoVOrthographicCameras(device=world_to_cam.device, R=R, T=T, min_x=-focal_length, max_x=focal_length, min_y=-focal_length, max_y=focal_length)
|
||||
@@ -266,12 +267,10 @@ def multiview_color_projection(meshes: Meshes, image_list: List[Image.Image], ca
|
||||
elif len(image_list) == 2:
|
||||
weights = [1.0, 1.0]
|
||||
else:
|
||||
raise ValueError("weights is None, and can not be guessed from image_list")
|
||||
weights = [1. for _ in range(len(cameras_list))]
|
||||
|
||||
# 2. run projection
|
||||
meshes = meshes.clone().to(device)
|
||||
if weights is None:
|
||||
weights = [1. for _ in range(len(cameras_list))]
|
||||
assert len(cameras_list) == len(image_list) == len(weights)
|
||||
original_color = meshes.textures.verts_features_packed()
|
||||
assert not torch.isnan(original_color).any()
|
||||
@@ -310,13 +309,267 @@ def multiview_color_projection(meshes: Meshes, image_list: List[Image.Image], ca
|
||||
del meshes
|
||||
return ret_mesh
|
||||
|
||||
def project_color_onto_texture(meshes: Meshes, glctx, verts_coordinates, verts_normals, uv, ft, texture_map, cameras: CamerasBase, pil_image: Image.Image, use_alpha=True, eps=0.05, resolution=1024, device="cuda", ) -> dict:
|
||||
"""
|
||||
Projects color from a given image onto a 3D mesh.
|
||||
|
||||
Args:
|
||||
meshes (pytorch3d.structures.Meshes): The 3D mesh object.
|
||||
cameras (pytorch3d.renderer.cameras.CamerasBase): The camera object.
|
||||
pil_image (PIL.Image.Image): The input image.
|
||||
use_alpha (bool, optional): Whether to use the alpha channel of the image. Defaults to True.
|
||||
eps (float, optional): The threshold for selecting visible faces. Defaults to 0.05.
|
||||
resolution (int, optional): The resolution of the projection. Defaults to 1024.
|
||||
device (str, optional): The device to use for computation. Defaults to "cuda".
|
||||
debug (bool, optional): Whether to save debug images. Defaults to False.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the following keys:
|
||||
- "new_texture" (TexturesVertex): The updated texture with interpolated colors.
|
||||
- "valid_mask" (Tensor of [M,3]): The indices of the pixels on texture being projected.
|
||||
- "valid_colors" (Tensor of [M,3]): The interpolated colors for the valid pixels.
|
||||
"""
|
||||
print(f"########## project_color_onto_texture:")
|
||||
|
||||
image = torch.from_numpy(np.array(pil_image.convert("RGBA")) / 255.).permute((2, 0, 1)).float().to(device) # in CHW format of [0, 1.]
|
||||
print(f"########## image: {image.shape}")
|
||||
|
||||
meshes = meshes.to(device)
|
||||
cameras = cameras.to(device)
|
||||
unique_faces_idx = get_visible_faces(meshes, cameras, resolution=resolution)
|
||||
print(f"########## unique_faces_idx: {unique_faces_idx.shape}")
|
||||
|
||||
# visible faces
|
||||
faces_normals = meshes.faces_normals_packed()[unique_faces_idx]
|
||||
faces_normals = faces_normals / faces_normals.norm(dim=1, keepdim=True)
|
||||
world_points = cameras.unproject_points(torch.tensor([[[0., 0., 0.1], [0., 0., 0.2]]]).to(device))[0]
|
||||
view_direction = world_points[1] - world_points[0]
|
||||
view_direction = view_direction / view_direction.norm(dim=0, keepdim=True)
|
||||
|
||||
print(f"########## view_direction: {view_direction}")
|
||||
|
||||
# find invalid faces
|
||||
cos_angles = (faces_normals * view_direction).sum(dim=1)
|
||||
assert cos_angles.mean() < 0, f"The view direction is not correct. cos_angles.mean()={cos_angles.mean()}"
|
||||
selected_faces_idx = unique_faces_idx[cos_angles < -eps]
|
||||
|
||||
print(f"########## selected_faces_idx: {selected_faces_idx.shape}")
|
||||
|
||||
# find visible UV pixels and get their positions
|
||||
selected_faces = ft[selected_faces_idx] # [N, 3]
|
||||
uv = torch.cat((uv, torch.zeros_like(uv[..., :1]), torch.ones_like(uv[..., :1])), dim=-1).unsqueeze(0) # [1, N, 4]
|
||||
rast_uv, rast_uv_db = dr.rasterize(glctx, uv, selected_faces, resolution=(resolution, resolution))
|
||||
texture_mask = (rast_uv[...,3:4] > 0).squeeze(0).squeeze(-1)
|
||||
pos_2d, _ = dr.interpolate(verts_coordinates, rast_uv, selected_faces)
|
||||
pos_2d = pos_2d.squeeze(0)
|
||||
|
||||
print(f"########## pos_2d: {pos_2d.shape}; texture_mask: {texture_mask.shape}")
|
||||
|
||||
# compute color
|
||||
pt_tensor = cameras.transform_points(pos_2d)[..., :2] # NDC space points
|
||||
print(f"$$$$$$$$$$$$$$$$$$$$$$$ {(~((pt_tensor.isnan()|(pt_tensor<-1)|(1<pt_tensor)).any(dim=2))).shape}")
|
||||
valid_mask = texture_mask & (~((pt_tensor.isnan()|(pt_tensor<-1)|(1<pt_tensor)).any(dim=2))) # checked, correct
|
||||
valid_pt = pt_tensor[valid_mask]
|
||||
valid_color = torch.nn.functional.grid_sample(image[None, ...].flip((-1, -2)), valid_pt[None, :, None, :], align_corners=False, padding_mode="reflection", mode="bilinear")[0, :, :, 0].T.clamp(0, 1) # [N, 4], note that bicubic may give invalid value
|
||||
alpha, valid_color = valid_color[..., 3:], valid_color[..., :3]
|
||||
if not use_alpha:
|
||||
alpha = torch.ones_like(alpha)
|
||||
|
||||
print(f"########## valid_mask: {valid_mask.shape}; valid_pt: {valid_pt.shape}; valid_color: {valid_color.shape}")
|
||||
|
||||
# modify color
|
||||
texture_map[valid_mask] = valid_color * alpha + texture_map[valid_mask] * (1 - alpha)
|
||||
|
||||
# calculate cosine angle
|
||||
normal_2d, _ = dr.interpolate(verts_normals, rast_uv, selected_faces)
|
||||
normal_2d = normal_2d.squeeze(0)
|
||||
valid_normal_map = normal_2d[valid_mask]
|
||||
valid_normal_map = valid_normal_map / valid_normal_map.norm(dim=1, keepdim=True).clamp_min(0.001)
|
||||
cos_angles = (valid_normal_map * view_direction).sum(dim=1)
|
||||
|
||||
print(f"########## normal_2d: {normal_2d.shape}; valid_normal_map: {valid_normal_map.shape}; cos_angles: {cos_angles.shape}")
|
||||
|
||||
return {
|
||||
"new_texture": texture_map,
|
||||
"valid_mask": valid_mask,
|
||||
"valid_colors": valid_color,
|
||||
"valid_alpha": alpha,
|
||||
"cos_angles": cos_angles,
|
||||
}
|
||||
|
||||
def complete_unseen_texture_color(meshes: Meshes, valid_index: torch.Tensor) -> dict:
|
||||
"""
|
||||
meshes: the mesh with vertex color to be completed.
|
||||
valid_index: the index of the valid vertices, where valid means colors are fixed. [V, 1]
|
||||
"""
|
||||
valid_index = valid_index.to(meshes.device)
|
||||
colors = meshes.textures.verts_features_packed() # [V, 3]
|
||||
V = colors.shape[0]
|
||||
|
||||
invalid_index = torch.ones_like(colors[:, 0]).bool() # [V]
|
||||
invalid_index[valid_index] = False
|
||||
invalid_index = torch.arange(V).to(meshes.device)[invalid_index]
|
||||
|
||||
L = meshes.laplacian_packed()
|
||||
E = torch.sparse_coo_tensor(torch.tensor([list(range(V))] * 2), torch.ones((V,)), size=(V, V)).to(meshes.device)
|
||||
L = L + E
|
||||
# E = torch.eye(V, layout=torch.sparse_coo, device=meshes.device)
|
||||
# L = L + E
|
||||
colored_count = torch.ones_like(colors[:, 0]) # [V]
|
||||
colored_count[invalid_index] = 0
|
||||
L_invalid = torch.index_select(L, 0, invalid_index) # sparse [IV, V]
|
||||
|
||||
total_colored = colored_count.sum()
|
||||
coloring_round = 0
|
||||
stage = "uncolored"
|
||||
from tqdm import tqdm
|
||||
pbar = tqdm(miniters=100)
|
||||
while stage == "uncolored" or coloring_round > 0:
|
||||
new_color = torch.matmul(L_invalid, colors * colored_count[:, None]) # [IV, 3]
|
||||
new_count = torch.matmul(L_invalid, colored_count)[:, None] # [IV, 1]
|
||||
colors[invalid_index] = torch.where(new_count > 0, new_color / new_count, colors[invalid_index])
|
||||
colored_count[invalid_index] = (new_count[:, 0] > 0).float()
|
||||
|
||||
new_total_colored = colored_count.sum()
|
||||
if new_total_colored > total_colored:
|
||||
total_colored = new_total_colored
|
||||
coloring_round += 1
|
||||
else:
|
||||
stage = "colored"
|
||||
coloring_round -= 1
|
||||
pbar.update(1)
|
||||
if coloring_round > 10000:
|
||||
print("coloring_round > 10000, break")
|
||||
break
|
||||
assert not torch.isnan(colors).any()
|
||||
meshes.textures = TexturesVertex(verts_features=[colors])
|
||||
return meshes
|
||||
|
||||
def multiview_color_projection_texture(meshes: Meshes, original_mesh: Mesh, image_list: List[Image.Image], cameras_list: List[CamerasBase]=None, camera_focal: float = 2 / 1.35, weights=None, eps=0.05, resolution=1024, device="cuda", reweight_with_cosangle="square", use_alpha=True, confidence_threshold=0.1, complete_unseen=False, below_confidence_strategy="smooth", force_cuda_rast=False) -> Mesh:
|
||||
"""
|
||||
Projects color from a given image onto a 3D mesh.
|
||||
|
||||
Args:
|
||||
meshes (pytorch3d.structures.Meshes): The 3D mesh object, only one mesh.
|
||||
image_list (PIL.Image.Image): List of images.
|
||||
cameras_list (list): List of cameras.
|
||||
camera_focal (float, optional): The focal length of the camera, if cameras_list is not passed. Defaults to 2 / 1.35.
|
||||
weights (list, optional): List of weights for each image, for ['front', 'front_right', 'right', 'back', 'left', 'front_left']. Defaults to None.
|
||||
eps (float, optional): The threshold for selecting visible faces. Defaults to 0.05.
|
||||
resolution (int, optional): The resolution of the projection. Defaults to 1024.
|
||||
device (str, optional): The device to use for computation. Defaults to "cuda".
|
||||
reweight_with_cosangle (str, optional): Whether to reweight the color with the angle between the view direction and the vertex normal. Defaults to None.
|
||||
use_alpha (bool, optional): Whether to use the alpha channel of the image. Defaults to True.
|
||||
confidence_threshold (float, optional): The threshold for the confidence of the projected color, if final projection weight is less than this, we will use the original color. Defaults to 0.1.
|
||||
complete_unseen (bool, optional): Whether to complete the unseen vertex color using laplacian. Defaults to False.
|
||||
|
||||
Returns:
|
||||
Meshes: the colored mesh
|
||||
"""
|
||||
# 1. preprocess inputs
|
||||
if image_list is None:
|
||||
raise ValueError("image_list is None")
|
||||
if cameras_list is None:
|
||||
if len(image_list) == 8:
|
||||
cameras_list = get_8view_cameras(device, focal=camera_focal)
|
||||
elif len(image_list) == 6:
|
||||
cameras_list = get_6view_cameras(device, focal=camera_focal)
|
||||
elif len(image_list) == 4:
|
||||
cameras_list = get_4view_cameras(device, focal=camera_focal)
|
||||
elif len(image_list) == 2:
|
||||
cameras_list = get_2view_cameras(device, focal=camera_focal)
|
||||
else:
|
||||
raise ValueError("cameras_list is None, and can not be guessed from image_list")
|
||||
if weights is None:
|
||||
if len(image_list) == 8:
|
||||
weights = [2.0, 0.05, 0.2, 0.02, 1.0, 0.02, 0.2, 0.05]
|
||||
elif len(image_list) == 6:
|
||||
weights = [2.0, 0.05, 0.2, 1.0, 0.2, 0.05]
|
||||
elif len(image_list) == 4:
|
||||
weights = [2.0, 0.2, 1.0, 0.2]
|
||||
elif len(image_list) == 2:
|
||||
weights = [1.0, 1.0]
|
||||
else:
|
||||
weights = [1. for _ in range(len(cameras_list))]
|
||||
|
||||
# 2. run projection
|
||||
meshes = meshes.clone().to(device)
|
||||
assert len(cameras_list) == len(image_list) == len(weights)
|
||||
original_color = torch.zeros([resolution, resolution, 3], dtype=original_mesh.v.dtype, device=device)
|
||||
assert not torch.isnan(original_color).any()
|
||||
texture_counts = torch.zeros_like(original_color[..., :1])
|
||||
texture_values = torch.zeros_like(original_color)
|
||||
max_texture_counts = torch.zeros_like(original_color[..., :1])
|
||||
max_texture_values = torch.zeros_like(original_color)
|
||||
|
||||
if force_cuda_rast:
|
||||
glctx = dr.RasterizeCudaContext()
|
||||
else:
|
||||
glctx = dr.RasterizeGLContext()
|
||||
|
||||
print(f"########## original_color: {original_color.shape}; texture_values: {texture_values.shape}")
|
||||
|
||||
print(f"########## original_mesh.v: {original_mesh.v.shape}; original_mesh.vt: {original_mesh.vt.shape}; original_mesh.vn: {original_mesh.vn.shape}; original_mesh.f: {original_mesh.f.shape}; meshes.faces_normals_packed: {meshes.faces_normals_packed().shape}")
|
||||
|
||||
vt2v_mapping = original_mesh.get_default_vt_to_v_mapping()
|
||||
verts_coordinates = original_mesh.v[vt2v_mapping]
|
||||
vt2vn_mapping = original_mesh.get_default_vt_to_vn_mapping()
|
||||
verts_normals = original_mesh.vn[vt2vn_mapping]
|
||||
uv = original_mesh.vt * 2.0 - 1.0 # uvs to range [-1, 1]
|
||||
|
||||
print(f"########## verts_coordinates: {verts_coordinates.shape}; verts_normals: {verts_normals.shape}")
|
||||
|
||||
for camera, image, weight in zip(cameras_list, image_list, weights):
|
||||
ret = project_color_onto_texture(meshes, glctx, verts_coordinates, verts_normals, uv, original_mesh.ft, original_color.clone(), camera, image, eps=eps, resolution=resolution, device=device, use_alpha=use_alpha)
|
||||
if reweight_with_cosangle == "linear":
|
||||
weight = (ret['cos_angles'].abs() * weight)[:, None]
|
||||
elif reweight_with_cosangle == "square":
|
||||
weight = (ret['cos_angles'].abs() ** 2 * weight)[:, None]
|
||||
if use_alpha:
|
||||
weight = weight * ret['valid_alpha']
|
||||
assert weight.min() > -0.0001
|
||||
texture_counts[ret['valid_mask']] += weight
|
||||
texture_values[ret['valid_mask']] += ret['valid_colors'] * weight
|
||||
max_texture_values[ret['valid_mask']] = torch.where(weight > max_texture_counts[ret['valid_mask']], ret['valid_colors'], max_texture_values[ret['valid_mask']])
|
||||
max_texture_counts[ret['valid_mask']] = torch.max(max_texture_counts[ret['valid_mask']], weight)
|
||||
|
||||
# Method2
|
||||
texture_values = torch.where(texture_counts > confidence_threshold, texture_values / texture_counts, texture_values)
|
||||
if below_confidence_strategy == "smooth":
|
||||
texture_values = torch.where(texture_counts <= confidence_threshold, (original_color * (confidence_threshold - texture_counts) + texture_values) / confidence_threshold, texture_values)
|
||||
elif below_confidence_strategy == "original":
|
||||
texture_values = torch.where(texture_counts <= confidence_threshold, original_color, texture_values)
|
||||
else:
|
||||
raise ValueError(f"below_confidence_strategy={below_confidence_strategy} is not supported")
|
||||
assert not torch.isnan(texture_values).any()
|
||||
|
||||
return texture_values
|
||||
|
||||
#meshes.textures = TexturesVertex(verts_features=[texture_values])
|
||||
#
|
||||
#if complete_unseen:
|
||||
# meshes = complete_unseen_texture_color(meshes, torch.arange(texture_values.shape[0]).to(device)[texture_counts[:, 0] >= confidence_threshold])
|
||||
#ret_mesh = meshes.detach()
|
||||
#del meshes
|
||||
#return ret_mesh
|
||||
|
||||
def get_orbit_cameras_list(orbit_camera_poses, device, fov_in_degrees=60):
|
||||
ret = []
|
||||
for i in range(len(orbit_camera_poses)):
|
||||
campose = orbit_camera_poses[i]
|
||||
R, T = look_at_view_transform(campose[0], -campose[1], campose[2])
|
||||
w2c = torch.cat([R[0].T, T[0, :, None]], dim=1)
|
||||
camera: PerspectiveCameras = get_camera(w2c, fov_in_degrees=fov_in_degrees, cam_type='fov').to(device)
|
||||
ret.append(camera)
|
||||
return ret
|
||||
|
||||
def get_cameras_list(azim_list, device, focal=2/1.35, dist=1.1):
|
||||
ret = []
|
||||
for azim in azim_list:
|
||||
R, T = look_at_view_transform(dist, 0, azim)
|
||||
w2c = torch.cat([R[0].T, T[0, :, None]], dim=1)
|
||||
cameras: OrthographicCameras = get_camera(w2c, focal_length=focal, cam_type='orthogonal').to(device)
|
||||
ret.append(cameras)
|
||||
camera: OrthographicCameras = get_camera(w2c, focal_length=focal, cam_type='orthogonal').to(device)
|
||||
ret.append(camera)
|
||||
return ret
|
||||
|
||||
def get_8view_cameras(device, focal=2/1.35):
|
||||
|
||||
@@ -81,14 +81,18 @@ def to_pyml_mesh(vertices,faces):
|
||||
return m1
|
||||
|
||||
|
||||
def to_py3d_mesh(vertices, faces, normals=None):
|
||||
def to_py3d_mesh(vertices, faces, normals=None, vertices_color=None):
|
||||
from pytorch3d.structures import Meshes
|
||||
from pytorch3d.renderer.mesh.textures import TexturesVertex
|
||||
mesh = Meshes(verts=[vertices], faces=[faces], textures=None)
|
||||
if normals is None:
|
||||
normals = mesh.verts_normals_packed()
|
||||
# set normals as vertext colors
|
||||
mesh.textures = TexturesVertex(verts_features=[normals / 2 + 0.5])
|
||||
|
||||
if vertices_color is None:
|
||||
# set normals as vertext colors
|
||||
if normals is None:
|
||||
normals = mesh.verts_normals_packed()
|
||||
mesh.textures = TexturesVertex(verts_features=[normals / 2 + 0.5])
|
||||
else:
|
||||
mesh.textures = TexturesVertex(verts_features=[vertices_color])
|
||||
return mesh
|
||||
|
||||
|
||||
|
||||
@@ -132,8 +132,7 @@ class DiffRastRenderer(nn.Module):
|
||||
normal = safe_normalize(normal[0])
|
||||
|
||||
# rotated normal (where [0, 0, 1] always faces camera)
|
||||
rot_normal = normal @ pose[:3, :3]
|
||||
viewcos = rot_normal[..., [2]]
|
||||
viewcos = normal @ pose[:3, :3]
|
||||
|
||||
# antialias
|
||||
albedo = dr.antialias(albedo, rast, v_clip, self.mesh.f).squeeze(0).contiguous() # [H, W, 3]
|
||||
@@ -155,6 +154,6 @@ class DiffRastRenderer(nn.Module):
|
||||
results['depth'] = depth
|
||||
if 'normal' in optional_render_types:
|
||||
results['normal'] = (normal + 1) / 2
|
||||
results['viewcos'] = viewcos
|
||||
results['viewcos'] = (viewcos + 1) / 2
|
||||
|
||||
return results
|
||||
@@ -1,21 +1,12 @@
|
||||
import os
|
||||
import math
|
||||
import random
|
||||
import numpy as np
|
||||
from typing import Optional
|
||||
from torchtyping import TensorType
|
||||
from plyfile import PlyData, PlyElement
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
pytorch3d_capable = True
|
||||
try:
|
||||
import pytorch3d
|
||||
from pytorch3d.ops import knn_points
|
||||
except ImportError:
|
||||
pytorch3d_capable = False
|
||||
|
||||
from kornia.geometry.conversions import (
|
||||
quaternion_to_rotation_matrix,
|
||||
@@ -31,7 +22,7 @@ from kiui.op import inverse_sigmoid
|
||||
|
||||
from shared_utils.sh_utils import eval_sh, SH2RGB, RGB2SH
|
||||
from mesh_processer.mesh import Mesh, PointCloud
|
||||
from mesh_processer.mesh_utils import construct_list_of_gs_attributes, write_gs_ply, read_gs_ply
|
||||
from mesh_processer.mesh_utils import construct_list_of_gs_attributes, write_gs_ply, read_gs_ply, K_nearest_neighbors_func
|
||||
|
||||
def get_expon_lr_func(
|
||||
lr_init, lr_final, lr_delay_steps=0, lr_delay_mult=1.0, max_steps=1000000
|
||||
@@ -170,31 +161,6 @@ def find_points_within_radius(query_points, vertex_points, d):
|
||||
|
||||
return result
|
||||
|
||||
@torch.no_grad()
|
||||
def K_nearest_neighbors(
|
||||
points: torch.Tensor,
|
||||
K: int,
|
||||
query: Optional[torch.Tensor] = None,
|
||||
return_dist=False,
|
||||
):
|
||||
if not pytorch3d_capable:
|
||||
raise ImportError("pytorch3d is not installed, which is required for KNN")
|
||||
|
||||
# query/points: Tensor of shape (N, P1/P2, D) giving a batch of N point clouds, each containing up to P1/P2 points of dimension D
|
||||
if query is None:
|
||||
query = points
|
||||
dist, idx, nn = knn_points(query[None, ...], points[None, ...], K=K, return_nn=True)
|
||||
|
||||
# idx: Tensor of shape (N, P1, K)
|
||||
# nn: Tensor of shape (N, P1, K, D)
|
||||
|
||||
# take the index 1 since index 0 is the point itself
|
||||
if not return_dist:
|
||||
return nn[0, :, 1:, :], idx[0, :, 1:]
|
||||
else:
|
||||
return nn[0, :, 1:, :], idx[0, :, 1:], dist[0, :, 1:]
|
||||
|
||||
|
||||
def distance_to_gaussian_surface(points, svec, rotmat, query):
|
||||
"""
|
||||
Calculate the radius of the gaussian along the direction, which determined by offset between points and query
|
||||
@@ -773,7 +739,7 @@ class GaussianModel:
|
||||
return new_params
|
||||
|
||||
def densify_by_compatness(self, K=1):
|
||||
_, idx = K_nearest_neighbors(self._xyz, K=K+1)
|
||||
_, idx = K_nearest_neighbors_func(self._xyz, K=K+1)
|
||||
|
||||
new_params_list = []
|
||||
for i in range(K):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 229 KiB |
@@ -45,7 +45,7 @@ def get_dependency_dir(dependency):
|
||||
|
||||
def setup_build_env():
|
||||
# Set CMake argumens fo build packages based on CUDA
|
||||
os.environ["CMAKE_ARGS"]="-DBUILD_opencv_world=ON -DWITH_CUDA=ON -DCUDA_FAST_MATH=ON -DWITH_CUBLAS=ON -DCUDA_ARCH_PTX=9.0 -DWITH_NVCUVID=ON"
|
||||
os.environ["CMAKE_ARGS"] = "-DBUILD_opencv_world=ON -DWITH_CUDA=ON -DCUDA_FAST_MATH=ON -DWITH_CUBLAS=ON -DCUDA_ARCH_PTX=9.0 -DWITH_NVCUVID=ON"
|
||||
subprocess.run([PYTHON_PATH, "-s", "-m", "pip", "install", "-r", BUILD_REQUIREMENTS_FILE_ABS_PATH])
|
||||
|
||||
install_remote_packages(build_config.build_base_packages)
|
||||
|
||||
@@ -17,11 +17,11 @@ build_base_packages: ["torch", "torchvision"]
|
||||
# All packages will be automatically installed at beginning of install.py follwing orders from top to bottom
|
||||
remote_packages:
|
||||
torch:
|
||||
version: "2.3.0"
|
||||
version: "2.4.0"
|
||||
url: "https://download.pytorch.org/whl/${cuda_version}"
|
||||
|
||||
torchvision:
|
||||
version: "0.18.0"
|
||||
version: "0.19.0"
|
||||
url: "https://download.pytorch.org/whl/${cuda_version}"
|
||||
|
||||
torch-scatter:
|
||||
@@ -29,5 +29,5 @@ remote_packages:
|
||||
url: "https://data.pyg.org/whl/torch-${remote_packages.torch.version}+${cuda_version}.html"
|
||||
|
||||
xformers:
|
||||
version: "0.0.26.post1"
|
||||
version: "0.0.27.post2"
|
||||
url: "https://download.pytorch.org/whl/${cuda_version}"
|
||||
+1
-1
@@ -80,7 +80,7 @@ try:
|
||||
cstr("Successfully installed Comfy3D! Let's Accelerate!").msg.print()
|
||||
|
||||
except Exception as e:
|
||||
cstr("Comfy3D install failed: Dependency installation has failed. Please install manually: https://github.com/MrForExample/ComfyUI-3D-Pack/tree/main/_Pre_Builds/README.md.").error.print()
|
||||
traceback.print_exc()
|
||||
cstr("Comfy3D install failed: Dependency installation has failed. Please install manually: https://github.com/MrForExample/ComfyUI-3D-Pack/tree/main/_Pre_Builds/README.md.").error.print()
|
||||
|
||||
|
||||
|
||||
+37
-8
@@ -65,15 +65,15 @@ class Mesh:
|
||||
self.ori_scale = 1
|
||||
|
||||
@classmethod
|
||||
def load(cls, path, resize=True, clean=False, renormal=True, retex=False, bound=0.9, front_dir='+z', **kwargs):
|
||||
def load(cls, path, resize=True, renormal=True, retex=False, clean=False, bound=0.5, front_dir='+z', **kwargs):
|
||||
"""load mesh from path.
|
||||
|
||||
Args:
|
||||
path (str): path to mesh file, supports ply, obj, glb.
|
||||
clean (bool, optional): perform mesh cleaning at load (e.g., merge close vertices). Defaults to False.
|
||||
resize (bool, optional): auto resize the mesh using ``bound`` into [-bound, bound]^3. Defaults to True.
|
||||
renormal (bool, optional): re-calc the vertex normals. Defaults to True.
|
||||
retex (bool, optional): re-calc the uv coordinates, will overwrite the existing uv coordinates. Defaults to False.
|
||||
clean (bool, optional): perform mesh cleaning at load (e.g., merge close vertices). Defaults to False.
|
||||
bound (float, optional): bound to resize. Defaults to 0.9.
|
||||
front_dir (str, optional): front-view direction of the mesh, should be [+-][xyz][ 123]. Defaults to '+z'.
|
||||
device (torch.device, optional): torch device. Defaults to None.
|
||||
@@ -540,18 +540,47 @@ class Mesh:
|
||||
vmapping (np.ndarray, optional): the mapping relationship from f to ft. Defaults to None.
|
||||
"""
|
||||
if vmapping is None:
|
||||
ft = self.ft.view(-1).long()
|
||||
f = self.f.view(-1).long()
|
||||
vmapping = torch.zeros(self.vt.shape[0], dtype=torch.long, device=self.device)
|
||||
vmapping[ft] = f # scatter, randomly choose one if index is not unique
|
||||
vt2v_mapping = self.get_default_vt_to_v_mapping()
|
||||
else:
|
||||
vt2v_mapping = vmapping
|
||||
|
||||
self.v = self.v[vmapping]
|
||||
self.v = self.v[vt2v_mapping]
|
||||
if self.vc is not None:
|
||||
self.vc = self.vc[vt2v_mapping]
|
||||
self.f = self.ft
|
||||
# assume fn == f
|
||||
if self.vn is not None:
|
||||
self.vn = self.vn[vmapping]
|
||||
if vmapping is None:
|
||||
vt2vn_mapping = self.get_default_vt_to_vn_mapping()
|
||||
else:
|
||||
vt2vn_mapping = vmapping
|
||||
self.vn = self.vn[vt2vn_mapping]
|
||||
self.fn = self.ft
|
||||
|
||||
def get_default_vt_to_v_mapping(self):
|
||||
"""map from ft to f
|
||||
|
||||
Returns:
|
||||
vmapping: tensor array
|
||||
"""
|
||||
ft = self.ft.view(-1).long()
|
||||
f = self.f.view(-1).long()
|
||||
vmapping = torch.zeros(self.vt.shape[0], dtype=torch.long, device=self.device)
|
||||
vmapping[ft] = f # scatter, randomly choose one if index is not unique
|
||||
return vmapping
|
||||
|
||||
def get_default_vt_to_vn_mapping(self):
|
||||
"""map from ft to f
|
||||
|
||||
Returns:
|
||||
vmapping: tensor array
|
||||
"""
|
||||
ft = self.ft.view(-1).long()
|
||||
fn = self.fn.view(-1).long()
|
||||
vmapping = torch.zeros(self.vt.shape[0], dtype=torch.long, device=self.device)
|
||||
vmapping[ft] = fn # scatter, randomly choose one if index is not unique
|
||||
return vmapping
|
||||
|
||||
def to(self, device):
|
||||
"""move all tensor attributes to device.
|
||||
|
||||
|
||||
@@ -7,6 +7,15 @@ from kornia.geometry.conversions import (
|
||||
import pymeshlab as pml
|
||||
from plyfile import PlyData, PlyElement
|
||||
|
||||
from typing import Optional
|
||||
|
||||
pytorch3d_capable = True
|
||||
try:
|
||||
import pytorch3d
|
||||
from pytorch3d.ops import knn_points
|
||||
except ImportError:
|
||||
pytorch3d_capable = False
|
||||
|
||||
from .mesh import PointCloud
|
||||
from shared_utils.sh_utils import SH2RGB, RGB2SH
|
||||
|
||||
@@ -506,6 +515,9 @@ def color_func_to_albedo(mesh, get_rgb_func, texture_resolution=1024, padding=2,
|
||||
|
||||
# render uv maps
|
||||
h = w = texture_resolution
|
||||
if mesh.vt is None:
|
||||
mesh.auto_uv()
|
||||
|
||||
uv = mesh.vt * 2.0 - 1.0 # uvs to range [-1, 1]
|
||||
uv = torch.cat((uv, torch.zeros_like(uv[..., :1]), torch.ones_like(uv[..., :1])), dim=-1) # [N, 4]
|
||||
|
||||
@@ -538,4 +550,117 @@ def color_func_to_albedo(mesh, get_rgb_func, texture_resolution=1024, padding=2,
|
||||
mask = mask.view(h, w)
|
||||
albedo = uv_padding(albedo, mask, padding)
|
||||
|
||||
return albedo
|
||||
return albedo
|
||||
|
||||
@torch.no_grad()
|
||||
def K_nearest_neighbors_func(
|
||||
points: torch.Tensor,
|
||||
K: int,
|
||||
query: Optional[torch.Tensor] = None,
|
||||
return_dist=False,
|
||||
skip_first_index=True,
|
||||
):
|
||||
if not pytorch3d_capable:
|
||||
raise ImportError("pytorch3d is not installed, which is required for KNN")
|
||||
|
||||
# query/points: Tensor of shape (N, P1/P2, D) giving a batch of N point clouds, each containing up to P1/P2 points of dimension D
|
||||
if query is None:
|
||||
query = points
|
||||
dist, idx, nn = knn_points(query[None, ...], points[None, ...], K=K, return_nn=True)
|
||||
|
||||
# idx: Tensor of shape (N, P1, K)
|
||||
# nn: Tensor of shape (N, P1, K, D)
|
||||
|
||||
# take the index 1 since index 0 is the point itself
|
||||
if skip_first_index:
|
||||
if not return_dist:
|
||||
return nn[0, :, 1:, :], idx[0, :, 1:]
|
||||
else:
|
||||
return nn[0, :, 1:, :], idx[0, :, 1:], dist[0, :, 1:]
|
||||
else:
|
||||
if not return_dist:
|
||||
return nn[0, :, :, :], idx[0, :, :]
|
||||
else:
|
||||
return nn[0, :, :, :], idx[0, :, :], dist[0, :, :]
|
||||
|
||||
def interpolate_texture_map_attr(mesh, texture_size: int = 256, batch_size: int = 64, interpolate_color=True, interpolate_position=False):
|
||||
# Get UV coordinates and faces
|
||||
if mesh.vt is None:
|
||||
mesh.auto_uv()
|
||||
|
||||
# Get Faces on UV
|
||||
texture_size_minus_one = texture_size - 1
|
||||
faces = mesh.ft
|
||||
verts_uvs = mesh.vt * texture_size_minus_one
|
||||
verts_uvs_idx = verts_uvs.to(torch.long)
|
||||
|
||||
vmapping = mesh.get_default_vt_to_v_mapping()
|
||||
# Get vertex colors
|
||||
interpolate_color = interpolate_color and mesh.vc is not None
|
||||
texture_map = mesh.albedo
|
||||
if interpolate_color:
|
||||
verts_colors = mesh.vc[vmapping]
|
||||
# Create a blank texture map
|
||||
texture_map = torch.zeros((texture_size, texture_size, 3), device=verts_colors.device)
|
||||
|
||||
# Get vertex positions
|
||||
position_map = None
|
||||
if interpolate_position:
|
||||
verts_positions = mesh.v[vmapping]
|
||||
position_map = torch.zeros((texture_size, texture_size, 3), device=verts_colors.device)
|
||||
|
||||
# Create a grid of UV coordinates
|
||||
grid_x, grid_y = torch.meshgrid(torch.linspace(0, texture_size_minus_one, texture_size, dtype=verts_uvs.dtype, device=verts_colors.device), torch.linspace(0, texture_size_minus_one, texture_size, dtype=verts_uvs.dtype, device=verts_colors.device))
|
||||
grid = torch.stack([grid_x, grid_y], dim=-1)
|
||||
|
||||
batch_idx_range = torch.arange(0, batch_size**2, 1, device=verts_colors.device, dtype=torch.long)
|
||||
|
||||
all_face_verts = verts_uvs_idx[faces]
|
||||
for uv_i in range(0, texture_size_minus_one, batch_size):
|
||||
for uv_j in range(0, texture_size_minus_one, batch_size):
|
||||
# Divide faces into squre batchs
|
||||
faces_mask = (uv_i <= all_face_verts[:, :, 0]) & (all_face_verts[:, :, 0] <= uv_i + batch_size) & (uv_j <= all_face_verts[:, :, 1]) & (all_face_verts[:, :, 1] <= uv_j + batch_size)
|
||||
faces_mask, _ = torch.max(faces_mask, dim=1)
|
||||
faces_mask = faces_mask.nonzero().squeeze(-1)
|
||||
batch_faces = faces[faces_mask]
|
||||
|
||||
uvs = verts_uvs[batch_faces]
|
||||
if interpolate_color:
|
||||
colors = verts_colors[batch_faces]
|
||||
if interpolate_position:
|
||||
positions = verts_positions[batch_faces]
|
||||
|
||||
v0, v1, v2 = uvs[:, 0], uvs[:, 1], uvs[:, 2]
|
||||
v0_0, v0_1 = v0[:, 0], v0[:, 1]
|
||||
v1_0, v1_1 = v1[:, 0], v1[:, 1]
|
||||
v2_0, v2_1 = v2[:, 0], v2[:, 1]
|
||||
|
||||
sub_grid = grid[uv_i:uv_i + batch_size, uv_j:uv_j + batch_size].reshape(-1, 2).unsqueeze(1)
|
||||
|
||||
# Compute barycentric coordinates for the batch of faces
|
||||
denom = (v1_1 - v2_1) * (v0_0 - v2_0) + (v2_0 - v1_0) * (v0_1 - v2_1)
|
||||
a = ((v1_1 - v2_1) * (sub_grid[:, :, 0] - v2_0) + (v2_0 - v1_0) * (sub_grid[:, :, 1] - v2_1)) / denom
|
||||
b = ((v2_1 - v0_1) * (sub_grid[:, :, 0] - v2_0) + (v0_0 - v2_0) * (sub_grid[:, :, 1] - v2_1)) / denom
|
||||
c = 1 - a - b
|
||||
|
||||
# Mask for points inside the triangle
|
||||
mask = (a >= 0) & (b >= 0) & (c >= 0)
|
||||
mask, mask_idx = torch.max(mask, dim=1)
|
||||
|
||||
# Get texture coordinates
|
||||
sub_grid = sub_grid.squeeze(1)
|
||||
uv_coords = sub_grid[mask].to(torch.long)
|
||||
|
||||
# Interpolate colors
|
||||
a, b, c = a[batch_idx_range, mask_idx][mask], b[batch_idx_range, mask_idx][mask], c[batch_idx_range, mask_idx][mask]
|
||||
|
||||
if interpolate_color:
|
||||
colors = colors[mask_idx][mask]
|
||||
interpolated_colors = (a[:, None] * colors[:, 0] + (b[:, None] * colors[:, 1]) + (c[:, None] * colors[:, 2]))
|
||||
texture_map[uv_coords[:, 1], uv_coords[:, 0]] = interpolated_colors
|
||||
if interpolate_position:
|
||||
positions = positions[mask_idx][mask]
|
||||
interpolated_positions = (a[:, None] * positions[:, 0] + (b[:, None] * positions[:, 1]) + (c[:, None] * positions[:, 2]))
|
||||
position_map[uv_coords[:, 1], uv_coords[:, 0]] = interpolated_positions
|
||||
|
||||
return texture_map, position_map
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import gc
|
||||
import math
|
||||
import copy
|
||||
from enum import Enum
|
||||
@@ -43,6 +44,7 @@ from .mesh_processer.mesh_utils import (
|
||||
calculate_max_sh_degree_from_gs_ply,
|
||||
marching_cubes_density_to_mesh,
|
||||
color_func_to_albedo,
|
||||
interpolate_texture_map_attr,
|
||||
)
|
||||
|
||||
from FlexiCubes.flexicubes_trainer import FlexiCubesTrainer
|
||||
@@ -77,7 +79,7 @@ from Unique3D.custum_3d_diffusion.custum_pipeline.unifield_pipeline_img2mvimg im
|
||||
from Unique3D.custum_3d_diffusion.custum_pipeline.unifield_pipeline_img2img import StableDiffusionImageCustomPipeline
|
||||
from Unique3D.scripts.mesh_init import fast_geo
|
||||
from Unique3D.scripts.utils import from_py3d_mesh, to_py3d_mesh, to_pyml_mesh, simple_clean_mesh
|
||||
from Unique3D.scripts.project_mesh import multiview_color_projection, get_cameras_list
|
||||
from Unique3D.scripts.project_mesh import multiview_color_projection, multiview_color_projection_texture, get_cameras_list, get_orbit_cameras_list
|
||||
from Unique3D.mesh_reconstruction.recon import reconstruct_stage1
|
||||
from Unique3D.mesh_reconstruction.refine import run_mesh_refine
|
||||
from CharacterGen.character_inference import Inference2D_API, Inference3D_API
|
||||
@@ -85,11 +87,17 @@ from CharacterGen.Stage_3D.lrm.utils.config import load_config as load_config_cg
|
||||
import craftsman
|
||||
from craftsman.systems.base import BaseSystem
|
||||
from craftsman.utils.config import ExperimentConfig as ExperimentConfigCraftsman, load_config as load_config_craftsman
|
||||
from CRM_T2I_V2.model.crm.sampler import CRMSamplerV2
|
||||
from CRM_T2I_V2.model.t2i_adapter_v2 import T2IAdapterV2
|
||||
|
||||
from .shared_utils.image_utils import (
|
||||
prepare_torch_img, torch_imgs_to_pils, troch_image_dilate,
|
||||
pils_rgba_to_rgb, pil_make_image_grid, pil_split_image, pils_to_torch_imgs, pils_resize_foreground
|
||||
)
|
||||
from .shared_utils.camera_utils import (
|
||||
ORBITPOSE_PRESET_DICT, ELEVATION_MIN, ELEVATION_MAX, AZIMUTH_MIN, AZIMUTH_MAX,
|
||||
compose_orbit_camposes
|
||||
)
|
||||
from .shared_utils.log_utils import cstr
|
||||
from .shared_utils.common_utils import parse_save_filename, get_list_filenames, resume_or_download_model_from_hf
|
||||
|
||||
@@ -145,11 +153,6 @@ SUPPORTED_CHECKPOINTS_EXTENSIONS = (
|
||||
'.safetensors',
|
||||
)
|
||||
|
||||
ELEVATION_MIN = -90
|
||||
ELEVATION_MAX = 90.0
|
||||
AZIMUTH_MIN = -180.0
|
||||
AZIMUTH_MAX = 180.0
|
||||
|
||||
WEIGHT_DTYPE = torch.float16
|
||||
|
||||
DEVICE_STR = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
@@ -232,6 +235,8 @@ class Load_3D_Mesh:
|
||||
"renormal": ("BOOLEAN", {"default": True},),
|
||||
"retex": ("BOOLEAN", {"default": False},),
|
||||
"optimizable": ("BOOLEAN", {"default": False},),
|
||||
"clean": ("BOOLEAN", {"default": False},),
|
||||
"resize_bound": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1000.0, "step": 0.001}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -244,7 +249,7 @@ class Load_3D_Mesh:
|
||||
FUNCTION = "load_mesh"
|
||||
CATEGORY = "Comfy3D/Import|Export"
|
||||
|
||||
def load_mesh(self, mesh_file_path, resize, renormal, retex, optimizable):
|
||||
def load_mesh(self, mesh_file_path, resize, renormal, retex, optimizable, clean, resize_bound):
|
||||
mesh = None
|
||||
|
||||
if not os.path.isabs(mesh_file_path):
|
||||
@@ -254,7 +259,7 @@ class Load_3D_Mesh:
|
||||
folder, filename = os.path.split(mesh_file_path)
|
||||
if filename.lower().endswith(SUPPORTED_3D_EXTENSIONS):
|
||||
with torch.inference_mode(not optimizable):
|
||||
mesh = Mesh.load(mesh_file_path, resize, renormal, retex)
|
||||
mesh = Mesh.load(mesh_file_path, resize, renormal, retex, clean, resize_bound)
|
||||
else:
|
||||
cstr(f"[{self.__class__.__name__}] File name {filename} does not end with supported 3D file extensions: {SUPPORTED_3D_EXTENSIONS}").error.print()
|
||||
else:
|
||||
@@ -390,7 +395,7 @@ class Image_Add_Pure_Color_Background:
|
||||
image_pils = torch_imgs_to_pils(images, masks)
|
||||
image_pils = pils_rgba_to_rgb(image_pils, (R, G, B))
|
||||
|
||||
images = pils_to_torch_imgs(image_pils, images.device)
|
||||
images = pils_to_torch_imgs(image_pils, images.dtype, images.device)
|
||||
return (images,)
|
||||
|
||||
class Resize_Image_Foreground:
|
||||
@@ -420,7 +425,7 @@ class Resize_Image_Foreground:
|
||||
image_pils = torch_imgs_to_pils(images, masks)
|
||||
image_pils = pils_resize_foreground(image_pils, foreground_ratio)
|
||||
|
||||
images = pils_to_torch_imgs(image_pils, images.device, force_rgb=False)
|
||||
images = pils_to_torch_imgs(image_pils, images.dtype, images.device, force_rgb=False)
|
||||
images, masks = images[:, :, :, 0:-1], images[:, :, :, -1]
|
||||
return (images, masks,)
|
||||
|
||||
@@ -495,7 +500,7 @@ class Split_Image_Grid:
|
||||
|
||||
image_pils = pil_split_image(image_pil, rows, clos)
|
||||
|
||||
images.append(pils_to_torch_imgs(image_pils, image.device))
|
||||
images.append(pils_to_torch_imgs(image_pils, image.dtype, image.device))
|
||||
|
||||
images = torch.cat(images, dim=0)
|
||||
return (images,)
|
||||
@@ -552,7 +557,7 @@ class Rotate_Normal_Maps_Horizontally:
|
||||
from Unique3D.scripts.utils import rotate_normals_torch
|
||||
pil_image_list = torch_imgs_to_pils(normal_maps, normal_masks)
|
||||
pil_image_list = rotate_normals_torch(pil_image_list, return_types='pil', rotate_direction=int(clockwise))
|
||||
normal_maps = pils_to_torch_imgs(pil_image_list, normal_maps.device)
|
||||
normal_maps = pils_to_torch_imgs(pil_image_list, normal_maps.dtype, normal_maps.device)
|
||||
return (normal_maps,)
|
||||
|
||||
class Fast_Clean_Mesh:
|
||||
@@ -730,7 +735,7 @@ class Stack_Orbit_Camera_Poses:
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
"ORBIT_CAMPOSES", # [orbit radius, elevation, azimuth, orbit center X, orbit center Y, orbit center Z]
|
||||
"ORBIT_CAMPOSES", # [[orbit radius, elevation, azimuth, orbit center X, orbit center Y, orbit center Z], ...]
|
||||
"FLOAT",
|
||||
"FLOAT",
|
||||
"FLOAT",
|
||||
@@ -739,7 +744,7 @@ class Stack_Orbit_Camera_Poses:
|
||||
"FLOAT",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"orbit_camposes", # List of 6 lists
|
||||
"orbit_camposes",
|
||||
"orbit_radius_list",
|
||||
"elevation_list",
|
||||
"azimuth_list",
|
||||
@@ -1002,7 +1007,7 @@ class Mesh_Orbit_Renderer:
|
||||
|
||||
if 'normal' in extra_outputs:
|
||||
all_rendered_normals = extra_outputs['normal']
|
||||
all_rendered_viewcos = extra_outputs['viewcos'].repeat(1, 1, 1, 3)
|
||||
all_rendered_viewcos = extra_outputs['viewcos']
|
||||
else:
|
||||
all_rendered_normals = None
|
||||
all_rendered_viewcos = None
|
||||
@@ -1284,6 +1289,9 @@ class Fitting_Mesh_With_Multiview_Images:
|
||||
force_cuda_rasterize,
|
||||
):
|
||||
|
||||
if mesh.vt is None:
|
||||
mesh.auto_uv()
|
||||
|
||||
mesh.set_new_albedo(mesh_albedo_width, mesh_albedo_height)
|
||||
|
||||
trained_mesh = None
|
||||
@@ -1548,11 +1556,13 @@ class Wonder3D_MVDiffusion_Model:
|
||||
|
||||
RETURN_TYPES = (
|
||||
"IMAGE",
|
||||
"IMAGE",
|
||||
"IMAGE",
|
||||
"ORBIT_CAMPOSES",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"multiview_images",
|
||||
"multiview_normals",
|
||||
"orbit_camposes",
|
||||
)
|
||||
FUNCTION = "run_mvdiffusion"
|
||||
CATEGORY = "Comfy3D/Algorithm"
|
||||
@@ -1605,8 +1615,13 @@ class Wonder3D_MVDiffusion_Model:
|
||||
# [N, 3, H, W] -> [N, H, W, 3]
|
||||
mv_images = out[num_views:].permute(0, 2, 3, 1)
|
||||
mv_normals = out[:num_views].permute(0, 2, 3, 1)
|
||||
|
||||
orbit_radius = [4.0] * 6
|
||||
orbit_center = [0.0] * 6
|
||||
orbit_elevations, orbit_azimuths = ORBITPOSE_PRESET_DICT["Wonder3D(6)"]
|
||||
orbit_camposes = compose_orbit_camposes(orbit_radius, orbit_elevations, orbit_azimuths, orbit_center, orbit_center, orbit_center)
|
||||
|
||||
return (mv_images, mv_normals, )
|
||||
return (mv_images, mv_normals, orbit_camposes)
|
||||
|
||||
def prepare_data(self, ref_image, ref_mask):
|
||||
single_image = torch_imgs_to_pils(ref_image, ref_mask)[0]
|
||||
@@ -1639,7 +1654,7 @@ class MVDream_Model:
|
||||
|
||||
RETURN_TYPES = (
|
||||
"IMAGE",
|
||||
"ORBIT_CAMPOSES", # [orbit radius, elevation, azimuth, orbit center X, orbit center Y, orbit center Z]
|
||||
"ORBIT_CAMPOSES",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"multiview_images",
|
||||
@@ -1674,13 +1689,11 @@ class MVDream_Model:
|
||||
# generate multi-view images
|
||||
mv_images = mvdream_pipe(prompt, reference_image, generator=generator, negative_prompt=prompt_neg, guidance_scale=mv_guidance_scale, num_inference_steps=num_inference_steps, elevation=elevation)
|
||||
mv_images = torch.from_numpy(np.stack([mv_images[1], mv_images[2], mv_images[3], mv_images[0]], axis=0)).float() # [4, H, W, 3], float32
|
||||
|
||||
azimuths = [0, 90, 180, -90]
|
||||
elevations = [0, 0, 0, 0]
|
||||
radius = [4.0] * 4
|
||||
center = [0.0] * 4
|
||||
|
||||
orbit_camposes = [azimuths, elevations, radius, center, center, center]
|
||||
orbit_radius = [4.0] * 4
|
||||
orbit_center = [0.0] * 4
|
||||
orbit_elevations, orbit_azimuths = ORBITPOSE_PRESET_DICT["MVDream(4)"]
|
||||
orbit_camposes = compose_orbit_camposes(orbit_radius, orbit_elevations, orbit_azimuths, orbit_center, orbit_center, orbit_center)
|
||||
|
||||
return (mv_images, orbit_camposes)
|
||||
|
||||
@@ -2138,7 +2151,7 @@ class CRM_Images_MVDiffusion_Model:
|
||||
|
||||
RETURN_TYPES = (
|
||||
"IMAGE",
|
||||
"ORBIT_CAMPOSES", # [orbit radius, elevation, azimuth, orbit center X, orbit center Y, orbit center Z]
|
||||
"ORBIT_CAMPOSES",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"multiview_images",
|
||||
@@ -2169,15 +2182,13 @@ class CRM_Images_MVDiffusion_Model:
|
||||
seed,
|
||||
mv_guidance_scale,
|
||||
num_inference_steps
|
||||
)
|
||||
|
||||
azimuths = [-90, 0, 180, 90, 0, 0]
|
||||
elevations = [0, 90, 0, 0, -90, 0]
|
||||
radius = [4.0] * 6
|
||||
center = [0.0] * 6
|
||||
|
||||
orbit_camposes = [azimuths, elevations, radius, center, center, center]
|
||||
).to(dtype=reference_image.dtype, device=reference_image.device)
|
||||
|
||||
orbit_radius = [4.0] * 6
|
||||
orbit_center = [0.0] * 6
|
||||
orbit_elevations, orbit_azimuths = ORBITPOSE_PRESET_DICT["CRM(6)"]
|
||||
orbit_camposes = compose_orbit_camposes(orbit_radius, orbit_elevations, orbit_azimuths, orbit_center, orbit_center, orbit_center)
|
||||
|
||||
return (multiview_images, orbit_camposes)
|
||||
|
||||
class CRM_CCMs_MVDiffusion_Model:
|
||||
@@ -2332,7 +2343,7 @@ class Zero123Plus_Diffusion_Model:
|
||||
|
||||
RETURN_TYPES = (
|
||||
"IMAGE",
|
||||
"ORBIT_CAMPOSES", # [orbit radius, elevation, azimuth, orbit center X, orbit center Y, orbit center Z]
|
||||
"ORBIT_CAMPOSES",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"multiviews",
|
||||
@@ -2366,15 +2377,14 @@ class Zero123Plus_Diffusion_Model:
|
||||
).images[0]
|
||||
|
||||
multiview_images = np.asarray(output_image, dtype=np.float32) / 255.0
|
||||
multiview_images = torch.from_numpy(multiview_images).permute(2, 0, 1).contiguous().float() # (3, 960, 640)
|
||||
multiview_images = torch.from_numpy(multiview_images).permute(2, 0, 1).contiguous() # (3, 960, 640)
|
||||
multiview_images = rearrange(multiview_images, 'c (n h) (m w) -> (n m) h w c', n=3, m=2) # (6, 320, 320, 3)
|
||||
multiview_images = multiview_images.to(dtype=reference_image.dtype, device=reference_image.device)
|
||||
|
||||
azimuths = [30, 90, 150, -150, -90, -30]
|
||||
elevations = [-20, 10, -20, 10, -20, 10]
|
||||
radius = [4.0] * 6
|
||||
center = [0.0] * 6
|
||||
|
||||
orbit_camposes = [azimuths, elevations, radius, center, center, center]
|
||||
orbit_radius = [4.0] * 6
|
||||
orbit_center = [0.0] * 6
|
||||
orbit_elevations, orbit_azimuths = ORBITPOSE_PRESET_DICT["Zero123Plus(6)"]
|
||||
orbit_camposes = compose_orbit_camposes(orbit_radius, orbit_elevations, orbit_azimuths, orbit_center, orbit_center, orbit_center)
|
||||
|
||||
return (multiview_images, orbit_camposes)
|
||||
|
||||
@@ -2465,7 +2475,11 @@ class InstantMesh_Reconstruction_Model:
|
||||
images = v2.functional.resize(images, 320, interpolation=3, antialias=True).clamp(0, 1)
|
||||
|
||||
# convert camera format from orbit to lrm inputs
|
||||
azimuths, elevations, radius = orbit_camera_poses[0], orbit_camera_poses[1], orbit_camera_poses[2]
|
||||
azimuths, elevations, radius = [], [], []
|
||||
for i in range(len(orbit_camera_poses)):
|
||||
azimuths.append(orbit_camera_poses[i][2])
|
||||
elevations.append(orbit_camera_poses[i][1])
|
||||
radius.append(orbit_camera_poses[i][0])
|
||||
input_cameras = oribt_camera_poses_to_input_cameras(azimuths, elevations, radius=radius, fov=orbit_camera_fovy).to(DEVICE)
|
||||
|
||||
# get triplane
|
||||
@@ -2508,7 +2522,7 @@ class Era3D_MVDiffusion_Model:
|
||||
RETURN_TYPES = (
|
||||
"IMAGE",
|
||||
"IMAGE",
|
||||
"ORBIT_CAMPOSES", # [orbit radius, elevation, azimuth, orbit center X, orbit center Y, orbit center Z]
|
||||
"ORBIT_CAMPOSES",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"multiviews",
|
||||
@@ -2571,9 +2585,9 @@ class Era3D_MVDiffusion_Model:
|
||||
images_pred = out[bsz:]
|
||||
|
||||
# [N, 3, H, W] -> [N, H, W, 3]
|
||||
multiview_images = images_pred.permute(0, 2, 3, 1).to(reference_image.dtype)
|
||||
multiview_normals = normals_pred.permute(0, 2, 3, 1).to(reference_image.dtype)
|
||||
|
||||
multiview_images = images_pred.permute(0, 2, 3, 1).to(reference_image.dtype, reference_image.device)
|
||||
multiview_normals = normals_pred.permute(0, 2, 3, 1).to(reference_image.dtype, reference_image.device)
|
||||
|
||||
azimuths = [0, 45, 90, 180, -90, -45]
|
||||
elevations = [0.0] * 6
|
||||
radius = [radius] * 6
|
||||
@@ -2804,7 +2818,7 @@ class Unique3D_MVDiffusion_Model:
|
||||
|
||||
RETURN_TYPES = (
|
||||
"IMAGE",
|
||||
"ORBIT_CAMPOSES", # [orbit radius, elevation, azimuth, orbit center X, orbit center Y, orbit center Z]
|
||||
"ORBIT_CAMPOSES",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"multiviews",
|
||||
@@ -2848,14 +2862,12 @@ class Unique3D_MVDiffusion_Model:
|
||||
).images
|
||||
|
||||
# [N, H, W, 3]
|
||||
multiview_images = pils_to_torch_imgs(image_pils, reference_image.device)
|
||||
multiview_images = pils_to_torch_imgs(image_pils, reference_image.dtype, reference_image.device)
|
||||
|
||||
azimuths = [0, 90, 180, -90]
|
||||
elevations = [0.0] * 4
|
||||
radius = [radius] * 4
|
||||
center = [0.0] * 4
|
||||
|
||||
orbit_camposes = [azimuths, elevations, radius, center, center, center]
|
||||
orbit_radius = [radius] * 4
|
||||
orbit_center = [0.0] * 4
|
||||
orbit_elevations, orbit_azimuths = ORBITPOSE_PRESET_DICT["Unique3D(4)"]
|
||||
orbit_camposes = compose_orbit_camposes(orbit_radius, orbit_elevations, orbit_azimuths, orbit_center, orbit_center, orbit_center)
|
||||
|
||||
return (multiview_images, orbit_camposes)
|
||||
|
||||
@@ -2954,6 +2966,10 @@ class ExplicitTarget_Color_Projection:
|
||||
"reference_masks": ("MASK",),
|
||||
"projection_resolution": ("INT", {"default": 1024, "min": 128, "max": 8192}),
|
||||
"complete_unseen_rgb": ("BOOLEAN", {"default": True},),
|
||||
"render_orbit_camera_fovy": ("FLOAT", {"default": 47.5, "min": 0.0, "max": 180.0, "step": 0.1}),
|
||||
"projection_weights": ("STRING", {"default": "2.0, 0.2, 1.0, 0.2"}),
|
||||
"confidence_threshold": ("FLOAT", {"default": 0.02, "min": 0.001, "max": 1.0, "step": 0.001}),
|
||||
"texture_projecton": ("BOOLEAN", {"default": False},),
|
||||
},
|
||||
"optional": {
|
||||
"reference_orbit_camera_poses": ("ORBIT_CAMPOSES",), # [orbit radius, elevation, azimuth, orbit center X, orbit center Y, orbit center Z]
|
||||
@@ -2976,6 +2992,10 @@ class ExplicitTarget_Color_Projection:
|
||||
reference_masks,
|
||||
projection_resolution,
|
||||
complete_unseen_rgb,
|
||||
render_orbit_camera_fovy,
|
||||
projection_weights,
|
||||
confidence_threshold,
|
||||
texture_projecton,
|
||||
reference_orbit_camera_poses=None,
|
||||
):
|
||||
pil_image_list = torch_imgs_to_pils(reference_images, reference_masks)
|
||||
@@ -2991,16 +3011,62 @@ class ExplicitTarget_Color_Projection:
|
||||
for i in range(0, img_num):
|
||||
azimuths.append(angle)
|
||||
angle += interval
|
||||
|
||||
cam_list = get_cameras_list(azimuths, DEVICE, focal=1)
|
||||
else:
|
||||
azimuths = [360 + angle if angle < 0 else angle for angle in reference_orbit_camera_poses[0]]
|
||||
cam_list = get_cameras_list(azimuths, DEVICE, focal=1)
|
||||
#reference_orbit_camera_poses[0] = [360 + angle if angle < 0 else angle for angle in reference_orbit_camera_poses[0]]
|
||||
cam_list = get_orbit_cameras_list(reference_orbit_camera_poses, DEVICE, render_orbit_camera_fovy)
|
||||
|
||||
new_meshes = multiview_color_projection(meshes, pil_image_list, resolution=projection_resolution, device=DEVICE, complete_unseen=complete_unseen_rgb, confidence_threshold=0.2, cameras_list=cam_list)
|
||||
vertices, faces, vertex_colors = from_py3d_mesh(new_meshes)
|
||||
vertices = vertices / 2 * 1.35
|
||||
weights = projection_weights.split(",")
|
||||
if len(weights) == len(cam_list):
|
||||
weights = [float(item) for item in weights]
|
||||
else:
|
||||
weights = None
|
||||
|
||||
if texture_projecton:
|
||||
albedo_img = multiview_color_projection_texture(meshes, mesh, pil_image_list, weights=weights, resolution=projection_resolution, device=DEVICE, complete_unseen=complete_unseen_rgb, confidence_threshold=confidence_threshold, cameras_list=cam_list)
|
||||
mesh.albedo = troch_image_dilate(albedo_img)
|
||||
else:
|
||||
new_meshes = multiview_color_projection(meshes, pil_image_list, weights=weights, resolution=projection_resolution, device=DEVICE, complete_unseen=complete_unseen_rgb, confidence_threshold=confidence_threshold, cameras_list=cam_list)
|
||||
vertices, faces, vertex_colors = from_py3d_mesh(new_meshes)
|
||||
|
||||
mesh = Mesh(v=vertices, f=faces, vc=vertex_colors, device=DEVICE)
|
||||
mesh.auto_normal()
|
||||
mesh = Mesh(v=vertices, f=faces,
|
||||
vn=None if mesh.vn is None else mesh.vn.clone(), fn=None if mesh.fn is None else mesh.fn.clone(),
|
||||
vt=None if mesh.vt is None else mesh.vt.clone(), ft=None if mesh.ft is None else mesh.ft.clone(),
|
||||
vc=vertex_colors, device=DEVICE)
|
||||
if mesh.vn is None:
|
||||
mesh.auto_normal()
|
||||
|
||||
return (mesh,)
|
||||
|
||||
class Convert_Vertex_Color_To_Texture:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"mesh": ("MESH",),
|
||||
"texture_resolution": ("INT", {"default": 1024, "min": 128, "max": 8192}),
|
||||
"batch_size": ("INT", {"default": 128, "min": 1, "max": 0xffffffffffffffff}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
"MESH",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"mesh",
|
||||
)
|
||||
FUNCTION = "run_convert_func"
|
||||
CATEGORY = "Comfy3D/Algorithm"
|
||||
|
||||
def run_convert_func(self, mesh, texture_resolution, batch_size):
|
||||
|
||||
if mesh.vc is not None:
|
||||
albedo_img, _ = interpolate_texture_map_attr(mesh, texture_resolution, batch_size, interpolate_color=True)
|
||||
mesh.albedo = troch_image_dilate(albedo_img)
|
||||
else:
|
||||
cstr(f"[{self.__class__.__name__}] skip this node since there is no vertex color found in mesh").msg.print()
|
||||
|
||||
return (mesh,)
|
||||
|
||||
class Load_CharacterGen_MVDiffusion_Model:
|
||||
@@ -3064,7 +3130,7 @@ class CharacterGen_MVDiffusion_Model:
|
||||
|
||||
RETURN_TYPES = (
|
||||
"IMAGE",
|
||||
"ORBIT_CAMPOSES", # [orbit radius, elevation, azimuth, orbit center X, orbit center Y, orbit center Z]
|
||||
"ORBIT_CAMPOSES",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"multiviews",
|
||||
@@ -3093,14 +3159,12 @@ class CharacterGen_MVDiffusion_Model:
|
||||
multiview_images = character_mv_gen_pipe.inference(
|
||||
single_image, target_image_width, target_image_height, prompt=prompt, prompt_neg=prompt_neg,
|
||||
guidance_scale=guidance_scale, num_inference_steps=num_inference_steps, seed=seed
|
||||
)
|
||||
).to(dtype=reference_image.dtype, device=reference_image.device)
|
||||
|
||||
azimuths = [-90, 90, 180, 0]
|
||||
elevations = [0.0] * 4
|
||||
radius = [radius] * 4
|
||||
center = [0.0] * 4
|
||||
|
||||
orbit_camposes = [azimuths, elevations, radius, center, center, center]
|
||||
orbit_radius = [radius] * 4
|
||||
orbit_center = [0.0] * 4
|
||||
orbit_elevations, orbit_azimuths = ORBITPOSE_PRESET_DICT["CharacterGen(4)"]
|
||||
orbit_camposes = compose_orbit_camposes(orbit_radius, orbit_elevations, orbit_azimuths, orbit_center, orbit_center, orbit_center)
|
||||
|
||||
return (multiview_images, orbit_camposes)
|
||||
|
||||
@@ -3269,21 +3333,6 @@ class Craftsman_Shape_Diffusion_Model:
|
||||
mesh.auto_uv()
|
||||
|
||||
return (mesh,)
|
||||
|
||||
|
||||
|
||||
ORBITPOSE_PRESET = ["Custom", "CRM(6)", "Zero123Plus(6)", "Wonder3D(6)", "Era3D(6)", "MVDream(4)", "Unique3D(4)", "CharacterGen(4)"]
|
||||
|
||||
OrbitPosesList = {
|
||||
"Custom": [[-90.0, 0.0, 180.0, 90.0, 0.0, 0.0], [0.0, 90.0, 0.0, 0.0, -90.0, 0.0], [4.0, 4.0, 4.0, 4.0, 4.0, 4.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
|
||||
"CRM(6)": [[-90.0, 0.0, 180.0, 90.0, 0.0, 0.0], [0.0, 90.0, 0.0, 0.0, -90.0, 0.0], [4.0, 4.0, 4.0, 4.0, 4.0, 4.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
|
||||
"Wonder3D(6)": [[0.0, 45.0, 90.0, 180.0, -90.0, -45.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [4.0, 4.0, 4.0, 4.0, 4.0, 4.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
|
||||
"Zero123Plus(6)": [[30.0, 90.0, 150.0, -150.0, -90.0, -30.0], [-20.0, 10.0, -20.0, 10.0, -20.0, 10.0], [4.0, 4.0, 4.0, 4.0, 4.0, 4.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
|
||||
"Era3D(6)": [[0.0, 45.0, 90.0, 180.0, -90.0, -45.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]], #[[radius], [radius], [radius], [radius], [radius], [radius]], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
"MVDream(4)": [[0.0, 90.0, 180.0, -90.0], [0.0, 0.0, 0.0, 0.0], [4.0, 4.0, 4.0, 4.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]],
|
||||
"Unique3D(4)": [[0.0, 90.0, 180.0, -90.0], [0.0, 0.0, 0.0, 0.0]], #[[radius], [radius], [radius], [radius]], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]
|
||||
"CharacterGen(4)": [[-90.0, 180.0, 90.0, 0.0], [0.0, 0.0, 0.0, 0.0]], #[[radius], [radius], [radius], [radius]], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]
|
||||
}
|
||||
|
||||
class OrbitPoses_JK:
|
||||
def __init__(self):
|
||||
@@ -3293,116 +3342,188 @@ class OrbitPoses_JK:
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"orbitpose_preset": (ORBITPOSE_PRESET, {"default": "Custom"}),
|
||||
"azimuths": ("STRING", {"default": "-90.0, 0.0, 180.0, 90.0, 0.0, 0.0"}),
|
||||
"elevations": ("STRING", {"default": "0.0, 90.0, 0.0, 0.0, -90.0, 0.0"}),
|
||||
"orbitpose_preset": (list(ORBITPOSE_PRESET_DICT.keys()),),
|
||||
"radius": ("STRING", {"default": "4.0, 4.0, 4.0, 4.0, 4.0, 4.0"}),
|
||||
"center": ("STRING", {"default": "0.0, 0.0, 0.0, 0.0, 0.0, 0.0"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("ORBIT_CAMPOSES", "ORBIT_CAMPOSES",)
|
||||
RETURN_NAMES = ("orbit_lists", "orbit_camposes",)
|
||||
|
||||
FUNCTION = "get_orbit_poses"
|
||||
CATEGORY = "Comfy3D/Preprocessor"
|
||||
|
||||
def get_orbit_poses(self, orbitpose_preset, azimuths, elevations, radius, center):
|
||||
|
||||
orbit_lists = OrbitPosesList.get(f"{orbitpose_preset}")
|
||||
|
||||
if orbitpose_preset == "Custom":
|
||||
azimuths = azimuths.split(",")
|
||||
elevations = elevations.split(",")
|
||||
radius = radius.split(",")
|
||||
center = center.split(",")
|
||||
orbit_azimuths = [float(item) for item in azimuths]
|
||||
orbit_elevations = [float(item) for item in elevations]
|
||||
orbit_radius = [float(item) for item in radius]
|
||||
orbit_center = [float(item) for item in center]
|
||||
orbit_lists = [orbit_azimuths, orbit_elevations, orbit_radius, orbit_center, orbit_center, orbit_center]
|
||||
elif orbitpose_preset == "Era3D(6)":
|
||||
radius = radius.split(",")
|
||||
orbit_radius = [float(item) for item in radius]
|
||||
orbit_center = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
orbit_lists = [orbit_lists[0], orbit_lists[1], orbit_radius, orbit_center, orbit_center, orbit_center]
|
||||
elif orbitpose_preset == "Unique3D(4)" or orbitpose_preset == "CharacterGen(4)":
|
||||
radius = radius.split(",")
|
||||
orbit_radius = [float(item) for item in radius]
|
||||
orbit_radius.pop(4)
|
||||
orbit_radius.pop(4)
|
||||
orbit_center = [0.0, 0.0, 0.0, 0.0]
|
||||
orbit_lists = [orbit_lists[0], orbit_lists[1], orbit_radius, orbit_center, orbit_center, orbit_center]
|
||||
|
||||
orbit_camposes = []
|
||||
|
||||
for i in range(0, len(orbit_lists[0])):
|
||||
orbit_camposes.append([orbit_lists[2][i], orbit_lists[1][i], orbit_lists[0][i], orbit_lists[3][i], orbit_lists[4][i], orbit_lists[5][i]])
|
||||
|
||||
return (orbit_lists, orbit_camposes,)
|
||||
|
||||
class OrbitLists_to_OrbitPoses_JK:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"orbit_lists": ("ORBIT_CAMPOSES",),
|
||||
"elevations": ("STRING", {"default": "0.0, 90.0, 0.0, 0.0, -90.0, 0.0"}),
|
||||
"azimuths": ("STRING", {"default": "-90.0, 0.0, 180.0, 90.0, 0.0, 0.0"}),
|
||||
"centerX": ("STRING", {"default": "0.0, 0.0, 0.0, 0.0, 0.0, 0.0"}),
|
||||
"centerY": ("STRING", {"default": "0.0, 0.0, 0.0, 0.0, 0.0, 0.0"}),
|
||||
"centerZ": ("STRING", {"default": "0.0, 0.0, 0.0, 0.0, 0.0, 0.0"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("ORBIT_CAMPOSES",)
|
||||
RETURN_NAMES = ("orbit_camposes",)
|
||||
|
||||
FUNCTION = "convert_orbit_poses"
|
||||
FUNCTION = "get_orbit_poses"
|
||||
CATEGORY = "Comfy3D/Preprocessor"
|
||||
|
||||
def convert_orbit_poses(self, orbit_lists):
|
||||
def get_orbit_poses(self, orbitpose_preset, azimuths, elevations, radius, centerX, centerY, centerZ):
|
||||
radius = radius.split(",")
|
||||
orbit_radius = [float(item) for item in radius]
|
||||
|
||||
orbit_camposes = []
|
||||
centerX = centerX.split(",")
|
||||
centerY = centerY.split(",")
|
||||
centerZ = centerZ.split(",")
|
||||
orbit_center_x = [float(item) for item in centerX]
|
||||
orbit_center_y = [float(item) for item in centerY]
|
||||
orbit_center_z = [float(item) for item in centerZ]
|
||||
|
||||
if orbitpose_preset == "Custom":
|
||||
elevations = elevations.split(",")
|
||||
azimuths = azimuths.split(",")
|
||||
orbit_elevations = [float(item) for item in elevations]
|
||||
orbit_azimuths = [float(item) for item in azimuths]
|
||||
else:
|
||||
orbit_elevations, orbit_azimuths = ORBITPOSE_PRESET_DICT[orbitpose_preset]
|
||||
|
||||
orbit_camposes = compose_orbit_camposes(orbit_radius, orbit_elevations, orbit_azimuths, orbit_center_x, orbit_center_y, orbit_center_z)
|
||||
|
||||
for i in range(0, len(orbit_lists[0])):
|
||||
orbit_camposes.append([orbit_lists[2][i], orbit_lists[1][i], orbit_lists[0][i], orbit_lists[3][i], orbit_lists[4][i], orbit_lists[5][i]])
|
||||
|
||||
return (orbit_camposes,)
|
||||
|
||||
class Load_CRM_T2I_V2_Models:
|
||||
crm_checkpoints_dir = "CRM"
|
||||
t2i_v2_checkpoints_dir = "T2I_V2"
|
||||
default_crm_ckpt_name = ["pixel-diffusion.pth"]
|
||||
default_crm_conf_name = ["sd_v2_base_ipmv_zero_SNR.yaml"]
|
||||
default_crm_repo_id = "Zhengyi/CRM"
|
||||
config_path = "CRM_T2I_V2_configs"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
cls.crm_checkpoints_dir_abs = os.path.join(CKPT_ROOT_PATH, cls.crm_checkpoints_dir)
|
||||
all_crm_models_names = get_list_filenames(cls.crm_checkpoints_dir_abs, SUPPORTED_CHECKPOINTS_EXTENSIONS)
|
||||
for ckpt_name in cls.default_crm_ckpt_name:
|
||||
if ckpt_name not in all_crm_models_names:
|
||||
all_crm_models_names += [ckpt_name]
|
||||
|
||||
cls.t2i_v2_checkpoints_dir_abs = os.path.join(CKPT_ROOT_PATH, cls.t2i_v2_checkpoints_dir)
|
||||
|
||||
cls.config_root_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.config_path)
|
||||
return {
|
||||
"required": {
|
||||
"crm_model_name": (all_crm_models_names, ),
|
||||
"crm_config_path": (cls.default_crm_conf_name, ),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
"T2IADAPTER_V2",
|
||||
"CRM_MVDIFFUSION_SAMPLER_V2",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"t2iadapter_v2",
|
||||
"crm_mvdiffusion_sampler_v2",
|
||||
)
|
||||
FUNCTION = "load_CRM"
|
||||
CATEGORY = "Comfy3D/Import|Export"
|
||||
|
||||
def load_CRM(self, crm_model_name, crm_config_path):
|
||||
|
||||
from CRM_T2I_V2.imagedream.ldm.util import (
|
||||
instantiate_from_config,
|
||||
get_obj_from_str,
|
||||
)
|
||||
|
||||
t2iadapter_v2 = T2IAdapterV2.from_pretrained(self.t2i_v2_checkpoints_dir_abs).to(DEVICE).to(WEIGHT_DTYPE)
|
||||
|
||||
class OrbitPoses_to_OrbitLists_JK:
|
||||
def __init__(self):
|
||||
pass
|
||||
crm_config_path = os.path.join(self.config_root_path_abs, crm_config_path)
|
||||
|
||||
ckpt_path = resume_or_download_model_from_hf(self.crm_checkpoints_dir_abs, self.default_crm_repo_id, crm_model_name, self.__class__.__name__)
|
||||
|
||||
crm_config = OmegaConf.load(crm_config_path)
|
||||
|
||||
crm_mvdiffusion_model = instantiate_from_config(crm_config.model)
|
||||
crm_mvdiffusion_model.load_state_dict(torch.load(ckpt_path, map_location="cpu"), strict=False)
|
||||
crm_mvdiffusion_model = crm_mvdiffusion_model.to(DEVICE).to(WEIGHT_DTYPE)
|
||||
crm_mvdiffusion_model.device = DEVICE
|
||||
|
||||
crm_mvdiffusion_sampler_v2 = get_obj_from_str(crm_config.sampler.target)(
|
||||
crm_mvdiffusion_model, device=DEVICE, dtype=WEIGHT_DTYPE, **crm_config.sampler.params
|
||||
)
|
||||
|
||||
cstr(f"[{self.__class__.__name__}] loaded model ckpt from {ckpt_path}").msg.print()
|
||||
|
||||
return (t2iadapter_v2, crm_mvdiffusion_sampler_v2, )
|
||||
|
||||
class CRM_T2I_V2_Models:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"orbit_camposes": ("ORBIT_CAMPOSES",),
|
||||
"t2iadapter_v2": ("T2IADAPTER_V2",),
|
||||
"crm_mvdiffusion_sampler_v2": ("CRM_MVDIFFUSION_SAMPLER_V2",),
|
||||
"reference_image": ("IMAGE",),
|
||||
"reference_mask": ("MASK",),
|
||||
"normal_maps": ("IMAGE",),
|
||||
"prompt": ("STRING", {
|
||||
"default": "3D assets",
|
||||
"multiline": True
|
||||
}),
|
||||
"prompt_neg": ("STRING", {
|
||||
"default": "uniform low no texture ugly, boring, bad anatomy, blurry, pixelated, obscure, unnatural colors, poor lighting, dull, and unclear.",
|
||||
"multiline": True
|
||||
}),
|
||||
"seed": ("INT", {"default": 1234, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"mv_guidance_scale": ("FLOAT", {"default": 5.5, "min": 0.0, "step": 0.01}),
|
||||
"num_inference_steps": ("INT", {"default": 50, "min": 1}),
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("ORBIT_CAMPOSES",)
|
||||
RETURN_NAMES = ("orbit_lists",)
|
||||
RETURN_TYPES = (
|
||||
"IMAGE",
|
||||
"ORBIT_CAMPOSES",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"multiview_images",
|
||||
"orbit_camposes",
|
||||
)
|
||||
FUNCTION = "run_model"
|
||||
CATEGORY = "Comfy3D/Algorithm"
|
||||
|
||||
FUNCTION = "convert_orbit_poses"
|
||||
CATEGORY = "Comfy3D/Preprocessor"
|
||||
|
||||
def convert_orbit_poses(self, orbit_camposes):
|
||||
def run_model(
|
||||
self,
|
||||
t2iadapter_v2,
|
||||
crm_mvdiffusion_sampler_v2,
|
||||
reference_image, # [N, 256, 256, 3]
|
||||
reference_mask, # [N, 256, 256]
|
||||
normal_maps, # [N * 6, 512, 512, 3]
|
||||
prompt,
|
||||
prompt_neg,
|
||||
seed,
|
||||
mv_guidance_scale,
|
||||
num_inference_steps,
|
||||
):
|
||||
# Convert tensores to pil images
|
||||
batch_reference_images = [CRMSamplerV2.process_pixel_img(img) for img in torch_imgs_to_pils(reference_image, reference_mask)]
|
||||
|
||||
orbit_azimuths = []
|
||||
orbit_elevations = []
|
||||
orbit_radius = []
|
||||
orbit_center0 = []
|
||||
orbit_center1 = []
|
||||
orbit_center2 = []
|
||||
# Adapter conditioning.
|
||||
normal_maps = normal_maps.permute(0, 3, 1, 2).to(DEVICE, dtype=WEIGHT_DTYPE) # [N, H, W, 3] -> [N, 3, H, W]
|
||||
down_intrablock_additional_residuals = t2iadapter_v2(normal_maps)
|
||||
down_intrablock_additional_residuals = [
|
||||
sample.to(dtype=WEIGHT_DTYPE).chunk(reference_image.shape[0]) for sample in down_intrablock_additional_residuals
|
||||
] # List[ List[ feature maps tensor for one down sample block and for one ip image, ... ], ... ]
|
||||
|
||||
for i in range(0, len(orbit_camposes)):
|
||||
orbit_azimuths.append(orbit_camposes[i][2])
|
||||
orbit_elevations.append(orbit_camposes[i][1])
|
||||
orbit_radius.append(orbit_camposes[i][0])
|
||||
orbit_center0.append(orbit_camposes[i][3])
|
||||
orbit_center1.append(orbit_camposes[i][4])
|
||||
orbit_center2.append(orbit_camposes[i][5])
|
||||
# Inference
|
||||
multiview_images = CRMSamplerV2.stage1_sample(
|
||||
crm_mvdiffusion_sampler_v2,
|
||||
batch_reference_images,
|
||||
prompt,
|
||||
prompt_neg,
|
||||
seed,
|
||||
mv_guidance_scale,
|
||||
num_inference_steps,
|
||||
additional_residuals=down_intrablock_additional_residuals
|
||||
).to(dtype=reference_image.dtype, device=reference_image.device)
|
||||
|
||||
gc.collect()
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
orbit_radius = [4.0] * 6
|
||||
orbit_center = [0.0] * 6
|
||||
orbit_elevations, orbit_azimuths = ORBITPOSE_PRESET_DICT["CRM(6)"]
|
||||
orbit_camposes = compose_orbit_camposes(orbit_radius, orbit_elevations, orbit_azimuths, orbit_center, orbit_center, orbit_center)
|
||||
|
||||
orbit_lists = [orbit_azimuths, orbit_elevations, orbit_radius, orbit_center0, orbit_center1, orbit_center2]
|
||||
|
||||
return (orbit_lists,)
|
||||
return (multiview_images, orbit_camposes)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from collections import OrderedDict
|
||||
import numpy as np
|
||||
import math
|
||||
from scipy.spatial.transform import Rotation as R
|
||||
@@ -8,6 +8,22 @@ import torch
|
||||
|
||||
from kiui.cam import orbit_camera
|
||||
|
||||
#{Key: [elevation, azimuth], ...}
|
||||
ORBITPOSE_PRESET_DICT = OrderedDict([
|
||||
("Custom", [[0.0, 90.0, 0.0, 0.0, -90.0, 0.0], [-90.0, 0.0, 180.0, 90.0, 0.0, 0.0]]),
|
||||
("CRM(6)", [[0.0, 90.0, 0.0, 0.0, -90.0, 0.0], [-90.0, 0.0, 180.0, 90.0, 0.0, 0.0]]),
|
||||
("Wonder3D(6)", [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 45.0, 90.0, 180.0, -90.0, -45.0]]),
|
||||
("Zero123Plus(6)", [[-20.0, 10.0, -20.0, 10.0, -20.0, 10.0], [30.0, 90.0, 150.0, -150.0, -90.0, -30.0]]),
|
||||
("Era3D(6)", [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 45.0, 90.0, 180.0, -90.0, -45.0]]),
|
||||
("MVDream(4)", [[0.0, 0.0, 0.0, 0.0], [0.0, 90.0, 180.0, -90.0]]),
|
||||
("Unique3D(4)", [[0.0, 0.0, 0.0, 0.0], [0.0, 90.0, 180.0, -90.0]]),
|
||||
("CharacterGen(4)", [[0.0, 0.0, 0.0, 0.0], [-90.0, 180.0, 90.0, 0.0]]),
|
||||
])
|
||||
ELEVATION_MIN = -89.999
|
||||
ELEVATION_MAX = 89.999
|
||||
AZIMUTH_MIN = -180.0
|
||||
AZIMUTH_MAX = 180.0
|
||||
|
||||
def dot(x, y):
|
||||
if isinstance(x, np.ndarray):
|
||||
return np.sum(x * y, -1, keepdims=True)
|
||||
@@ -255,4 +271,18 @@ class BaseCameraController(ABC):
|
||||
extra_outputs[k] = torch.stack(extra_outputs[k], dim=0)
|
||||
|
||||
# [Number of Poses, 3, H, W], [Number of Poses, 1, H, W] both in [0, 1]
|
||||
return torch.stack(all_rendered_images, dim=0), torch.stack(all_rendered_masks, dim=0), extra_outputs
|
||||
return torch.stack(all_rendered_images, dim=0), torch.stack(all_rendered_masks, dim=0), extra_outputs
|
||||
|
||||
def compose_orbit_camposes(orbit_radius, orbit_elevations, orbit_azimuths, orbit_center_x, orbit_center_y, orbit_center_z):
|
||||
orbit_camposes = []
|
||||
|
||||
campose_num = len(orbit_radius)
|
||||
for i in range(campose_num):
|
||||
orbit_camposes.append([
|
||||
orbit_radius[i],
|
||||
np.clip(orbit_elevations[i], ELEVATION_MIN, ELEVATION_MAX),
|
||||
np.clip(orbit_azimuths[i], AZIMUTH_MIN, AZIMUTH_MAX),
|
||||
orbit_center_x[i], orbit_center_y[i], orbit_center_z[i]
|
||||
])
|
||||
|
||||
return orbit_camposes
|
||||
@@ -22,6 +22,8 @@ def torch_imgs_to_pils(images, masks=None, alpha_min=0.1):
|
||||
images = images.unsqueeze(0)
|
||||
|
||||
if masks is not None:
|
||||
masks = masks.to(dtype=images.dtype, device=images.device)
|
||||
|
||||
if len(masks.shape) == 2:
|
||||
masks = masks.unsqueeze(0)
|
||||
|
||||
@@ -55,7 +57,7 @@ def troch_image_dilate(img):
|
||||
img = (img.clip(0, 255) / 255).astype(np.float32)
|
||||
return torch.from_numpy(img)
|
||||
|
||||
def pils_to_torch_imgs(pils: Union[Image.Image, List[Image.Image]], device="cuda", force_rgb=True):
|
||||
def pils_to_torch_imgs(pils: Union[Image.Image, List[Image.Image]], dtype=torch.float16, device="cuda", force_rgb=True):
|
||||
if isinstance(pils, Image.Image):
|
||||
pils = [pils]
|
||||
|
||||
@@ -66,7 +68,7 @@ def pils_to_torch_imgs(pils: Union[Image.Image, List[Image.Image]], device="cuda
|
||||
|
||||
images.append(TF.to_tensor(pil).permute(1, 2, 0))
|
||||
|
||||
images = torch.stack(images, dim=0).to(device)
|
||||
images = torch.stack(images, dim=0).to(dtype=dtype, device=device)
|
||||
|
||||
return images
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ window.addEventListener("resize", handleResize);
|
||||
|
||||
var lastTimestamp = "";
|
||||
var needUpdate = false;
|
||||
let currentURL;
|
||||
var url = location.protocol + '//' + location.host;
|
||||
|
||||
function frameUpdate() {
|
||||
|
||||
@@ -57,14 +59,14 @@ async function main(filepath="") {
|
||||
if (/^.+\.[a-zA-Z]+$/.test(filepath)){
|
||||
|
||||
let params = {"filepath": filepath};
|
||||
const url = api.apiURL('/viewfile?' + new URLSearchParams(params));
|
||||
currentURL = url + '/viewfile?' + new URLSearchParams(params);
|
||||
var splat = null;
|
||||
|
||||
var fileExt = filepath.split('.').pop().toLowerCase();
|
||||
if (fileExt == "ply"){
|
||||
splat = await SPLAT.PLYLoader.LoadAsync(url, scene, onProgress);
|
||||
splat = await SPLAT.PLYLoader.LoadAsync(currentURL, scene, onProgress);
|
||||
} else if (fileExt == "splat") {
|
||||
splat = await SPLAT.Loader.LoadAsync(url, scene, onProgress);
|
||||
splat = await SPLAT.Loader.LoadAsync(currentURL, scene, onProgress);
|
||||
} else {
|
||||
throw new Error(`File extension name has to be either .ply or .splat, got .${fileExt}`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as THREE from 'three';
|
||||
import { api } from '/scripts/api.js';
|
||||
//import { api } from '/scripts/ui/api.ts';
|
||||
import {getRGBValue} from '/extensions/ComfyUI-3D-Pack/js/sharedFunctions.js';
|
||||
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
@@ -58,6 +58,7 @@ var lastTimestamp = "";
|
||||
var needUpdate = false;
|
||||
let mixer;
|
||||
let currentURL;
|
||||
var url = location.protocol + '//' + location.host;
|
||||
|
||||
downloadButton.addEventListener('click', e => {
|
||||
window.open(currentURL, '_blank');
|
||||
@@ -106,7 +107,7 @@ async function main(filepath="") {
|
||||
if (/^.+\.[a-zA-Z]+$/.test(filepath)){
|
||||
|
||||
let params = {"filepath": filepath};
|
||||
currentURL = api.apiURL('/viewfile?' + new URLSearchParams(params));
|
||||
currentURL = url + '/viewfile?' + new URLSearchParams(params);
|
||||
|
||||
var filepathSplit = filepath.split('.');
|
||||
var fileExt = filepathSplit.pop().toLowerCase();
|
||||
@@ -119,7 +120,7 @@ async function main(filepath="") {
|
||||
var mtlFilepath = filepathNoExt.replace(/^.*[\\\/]/, '') + ".mtl";
|
||||
|
||||
const mtlLoader = new MTLLoader();
|
||||
mtlLoader.setPath(api.apiURL('/viewfile?' + new URLSearchParams({"filepath": mtlFolderpath})));
|
||||
mtlLoader.setPath(url + '/viewfile?' + new URLSearchParams({"filepath": mtlFolderpath}));
|
||||
mtlLoader.load( mtlFilepath, function ( mtl ) {
|
||||
mtl.preload();
|
||||
loader.setMaterials( mtl );
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ def set_web_conf(new_web_conf):
|
||||
global web_conf
|
||||
web_conf = new_web_conf
|
||||
|
||||
@server.PromptServer.instance.routes.get("/extensions/ComfyUI-3D-Pack/html/viewfile")
|
||||
@server.PromptServer.instance.routes.get("/viewfile")
|
||||
async def view_file(request):
|
||||
query = request.rel_url.query
|
||||
# Security check to see if query client is local
|
||||
|
||||
Reference in New Issue
Block a user