Integrate CharacterGen & Improve its workflow with Unique3D
@@ -156,6 +156,7 @@ cython_debug/
|
||||
*.bin
|
||||
*.pth
|
||||
*.safetensors
|
||||
*.pkl
|
||||
|
||||
# Ignore test outputs
|
||||
outputs/
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
*.7z filter=lfs diff=lfs merge=lfs -text
|
||||
*.arrow filter=lfs diff=lfs merge=lfs -text
|
||||
*.bin filter=lfs diff=lfs merge=lfs -text
|
||||
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
||||
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
||||
*.ftz filter=lfs diff=lfs merge=lfs -text
|
||||
*.gz filter=lfs diff=lfs merge=lfs -text
|
||||
*.h5 filter=lfs diff=lfs merge=lfs -text
|
||||
*.joblib filter=lfs diff=lfs merge=lfs -text
|
||||
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
||||
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
||||
*.model filter=lfs diff=lfs merge=lfs -text
|
||||
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
||||
*.npy filter=lfs diff=lfs merge=lfs -text
|
||||
*.npz filter=lfs diff=lfs merge=lfs -text
|
||||
*.onnx filter=lfs diff=lfs merge=lfs -text
|
||||
*.ot filter=lfs diff=lfs merge=lfs -text
|
||||
*.parquet filter=lfs diff=lfs merge=lfs -text
|
||||
*.pb filter=lfs diff=lfs merge=lfs -text
|
||||
*.pickle filter=lfs diff=lfs merge=lfs -text
|
||||
*.pkl filter=lfs diff=lfs merge=lfs -text
|
||||
*.pt filter=lfs diff=lfs merge=lfs -text
|
||||
*.pth filter=lfs diff=lfs merge=lfs -text
|
||||
*.rar filter=lfs diff=lfs merge=lfs -text
|
||||
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
||||
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
||||
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
||||
*.tar filter=lfs diff=lfs merge=lfs -text
|
||||
*.tflite filter=lfs diff=lfs merge=lfs -text
|
||||
*.tgz filter=lfs diff=lfs merge=lfs -text
|
||||
*.wasm 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
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3c63c3d2e7b7f18788aca8a5a0e766ad4633f631d49192602d8532b6b0df7615
|
||||
size 559
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"_name_or_path": "./image_encoder",
|
||||
"architectures": [
|
||||
"CLIPVisionModelWithProjection"
|
||||
],
|
||||
"attention_dropout": 0.0,
|
||||
"dropout": 0.0,
|
||||
"hidden_act": "gelu",
|
||||
"hidden_size": 1280,
|
||||
"image_size": 224,
|
||||
"initializer_factor": 1.0,
|
||||
"initializer_range": 0.02,
|
||||
"intermediate_size": 5120,
|
||||
"layer_norm_eps": 1e-05,
|
||||
"model_type": "clip_vision_model",
|
||||
"num_attention_heads": 16,
|
||||
"num_channels": 3,
|
||||
"num_hidden_layers": 32,
|
||||
"patch_size": 14,
|
||||
"projection_dim": 1024,
|
||||
"torch_dtype": "float16",
|
||||
"transformers_version": "4.28.0.dev0"
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
license: apache-2.0
|
||||
tags:
|
||||
- dino
|
||||
- vision
|
||||
---
|
||||
|
||||
# Vision Transformer (base-sized model) trained using DINOv2
|
||||
|
||||
Vision Transformer (ViT) model trained using the DINOv2 method. It was introduced in the paper [DINOv2: Learning Robust Visual Features without Supervision](https://arxiv.org/abs/2304.07193) by Oquab et al. and first released in [this repository](https://github.com/facebookresearch/dinov2).
|
||||
|
||||
Disclaimer: The team releasing DINOv2 did not write a model card for this model so this model card has been written by the Hugging Face team.
|
||||
|
||||
## Model description
|
||||
|
||||
The Vision Transformer (ViT) is a transformer encoder model (BERT-like) pretrained on a large collection of images in a self-supervised fashion.
|
||||
|
||||
Images are presented to the model as a sequence of fixed-size patches, which are linearly embedded. One also adds a [CLS] token to the beginning of a sequence to use it for classification tasks. One also adds absolute position embeddings before feeding the sequence to the layers of the Transformer encoder.
|
||||
|
||||
Note that this model does not include any fine-tuned heads.
|
||||
|
||||
By pre-training the model, it learns an inner representation of images that can then be used to extract features useful for downstream tasks: if you have a dataset of labeled images for instance, you can train a standard classifier by placing a linear layer on top of the pre-trained encoder. One typically places a linear layer on top of the [CLS] token, as the last hidden state of this token can be seen as a representation of an entire image.
|
||||
|
||||
## Intended uses & limitations
|
||||
|
||||
You can use the raw model for feature extraction. See the [model hub](https://huggingface.co/models?search=facebook/dinov2) to look for
|
||||
fine-tuned versions on a task that interests you.
|
||||
|
||||
### How to use
|
||||
|
||||
Here is how to use this model:
|
||||
|
||||
```python
|
||||
from transformers import AutoImageProcessor, AutoModel
|
||||
from PIL import Image
|
||||
import requests
|
||||
|
||||
url = 'http://images.cocodataset.org/val2017/000000039769.jpg'
|
||||
image = Image.open(requests.get(url, stream=True).raw)
|
||||
|
||||
processor = AutoImageProcessor.from_pretrained('facebook/dinov2-base')
|
||||
model = AutoModel.from_pretrained('facebook/dinov2-base')
|
||||
|
||||
inputs = processor(images=image, return_tensors="pt")
|
||||
outputs = model(**inputs)
|
||||
last_hidden_states = outputs.last_hidden_state
|
||||
```
|
||||
|
||||
### BibTeX entry and citation info
|
||||
|
||||
```bibtex
|
||||
misc{oquab2023dinov2,
|
||||
title={DINOv2: Learning Robust Visual Features without Supervision},
|
||||
author={Maxime Oquab and Timothée Darcet and Théo Moutakanni and Huy Vo and Marc Szafraniec and Vasil Khalidov and Pierre Fernandez and Daniel Haziza and Francisco Massa and Alaaeldin El-Nouby and Mahmoud Assran and Nicolas Ballas and Wojciech Galuba and Russell Howes and Po-Yao Huang and Shang-Wen Li and Ishan Misra and Michael Rabbat and Vasu Sharma and Gabriel Synnaeve and Hu Xu and Hervé Jegou and Julien Mairal and Patrick Labatut and Armand Joulin and Piotr Bojanowski},
|
||||
year={2023},
|
||||
eprint={2304.07193},
|
||||
archivePrefix={arXiv},
|
||||
primaryClass={cs.CV}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"architectures": [
|
||||
"Dinov2Model"
|
||||
],
|
||||
"attention_probs_dropout_prob": 0.0,
|
||||
"drop_path_rate": 0.0,
|
||||
"hidden_act": "gelu",
|
||||
"hidden_dropout_prob": 0.0,
|
||||
"hidden_size": 768,
|
||||
"image_size": 518,
|
||||
"initializer_range": 0.02,
|
||||
"layer_norm_eps": 1e-06,
|
||||
"layerscale_value": 1.0,
|
||||
"mlp_ratio": 4,
|
||||
"model_type": "dinov2",
|
||||
"num_attention_heads": 12,
|
||||
"num_channels": 3,
|
||||
"num_hidden_layers": 12,
|
||||
"patch_size": 14,
|
||||
"qkv_bias": true,
|
||||
"torch_dtype": "float32",
|
||||
"transformers_version": "4.31.0.dev0",
|
||||
"use_swiglu_ffn": false
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"crop_size": {
|
||||
"height": 256,
|
||||
"width": 256
|
||||
},
|
||||
"do_center_crop": false,
|
||||
"do_convert_rgb": false,
|
||||
"do_normalize": true,
|
||||
"do_rescale": true,
|
||||
"do_resize": false,
|
||||
"image_mean": [
|
||||
0.485,
|
||||
0.456,
|
||||
0.406
|
||||
],
|
||||
"image_processor_type": "BitImageProcessor",
|
||||
"image_std": [
|
||||
0.229,
|
||||
0.224,
|
||||
0.225
|
||||
],
|
||||
"resample": 3,
|
||||
"rescale_factor": 0.00392156862745098,
|
||||
"size": {
|
||||
"shortest_edge": 256
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
license: apache-2.0
|
||||
language:
|
||||
- en
|
||||
pipeline_tag: image-to-3d
|
||||
---
|
||||
|
||||
This is the model parameters of [CharacterGen](https://github.com/zjp-shadow/CharacterGen).
|
||||
Thanks for downloading the checkpoint~
|
||||
If you find our work helpful, please consider citing
|
||||
|
||||
```bibtex
|
||||
@article
|
||||
{peng2024charactergen,
|
||||
title ={CharacterGen: Efficient 3D Character Generation from Single Images with Multi-View Pose Canonicalization},
|
||||
author ={Hao-Yang Peng and Jia-Peng Zhang and Meng-Hao Guo and Yan-Pei Cao and Shi-Min Hu},
|
||||
journal ={ACM Transactions on Graphics (TOG)},
|
||||
year ={2024},
|
||||
volume ={43},
|
||||
number ={4},
|
||||
doi ={10.1145/3658217}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
*.7z filter=lfs diff=lfs merge=lfs -text
|
||||
*.arrow filter=lfs diff=lfs merge=lfs -text
|
||||
*.bin filter=lfs diff=lfs merge=lfs -text
|
||||
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
||||
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
||||
*.ftz filter=lfs diff=lfs merge=lfs -text
|
||||
*.gz filter=lfs diff=lfs merge=lfs -text
|
||||
*.h5 filter=lfs diff=lfs merge=lfs -text
|
||||
*.joblib filter=lfs diff=lfs merge=lfs -text
|
||||
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
||||
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
||||
*.model filter=lfs diff=lfs merge=lfs -text
|
||||
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
||||
*.npy filter=lfs diff=lfs merge=lfs -text
|
||||
*.npz filter=lfs diff=lfs merge=lfs -text
|
||||
*.onnx filter=lfs diff=lfs merge=lfs -text
|
||||
*.ot filter=lfs diff=lfs merge=lfs -text
|
||||
*.parquet filter=lfs diff=lfs merge=lfs -text
|
||||
*.pb filter=lfs diff=lfs merge=lfs -text
|
||||
*.pickle filter=lfs diff=lfs merge=lfs -text
|
||||
*.pkl filter=lfs diff=lfs merge=lfs -text
|
||||
*.pt filter=lfs diff=lfs merge=lfs -text
|
||||
*.pth filter=lfs diff=lfs merge=lfs -text
|
||||
*.rar filter=lfs diff=lfs merge=lfs -text
|
||||
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
||||
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
||||
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
||||
*.tar filter=lfs diff=lfs merge=lfs -text
|
||||
*.tflite filter=lfs diff=lfs merge=lfs -text
|
||||
*.tgz filter=lfs diff=lfs merge=lfs -text
|
||||
*.wasm 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
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
license: mit
|
||||
---
|
||||
To enable download model using huggingface_hub package
|
||||
<br>Copied from https://huggingface.co/spaces/Wuvin/Unique3D/tree/main/ckpt
|
||||
@@ -0,0 +1,23 @@
|
||||
pretrained_model_path: "stabilityai/stable-diffusion-2-1"
|
||||
image_encoder_path: "./2D_Stage/models/image_encoder"
|
||||
ckpt_dir: "./2D_Stage/models/checkpoint"
|
||||
|
||||
validation:
|
||||
use_inv_latent: False
|
||||
video_length: 4
|
||||
|
||||
use_pose_guider: True
|
||||
use_noise: False
|
||||
use_shifted_noise: False
|
||||
unet_condition_type: image
|
||||
|
||||
unet_from_pretrained_kwargs:
|
||||
camera_embedding_type: 'e_de_da_sincos'
|
||||
projection_class_embeddings_input_dim: 10 # modify
|
||||
joint_attention: false # modify
|
||||
num_views: 4
|
||||
sample_size: 96
|
||||
zero_init_conv_in: false
|
||||
zero_init_camera_projection: false
|
||||
in_channels: 4
|
||||
use_safetensors: true
|
||||
@@ -0,0 +1,104 @@
|
||||
system_cls: CharacterGen.Stage_3D.lrm.systems.multiview_lrm.MultiviewLRM
|
||||
data:
|
||||
cond_width: 504
|
||||
cond_height: 504
|
||||
|
||||
system:
|
||||
weights: 3D_Stage/models/lrm.ckpt
|
||||
|
||||
weights_ignore_modules:
|
||||
- decoder.heads.density
|
||||
|
||||
check_train_every_n_steps: 100
|
||||
|
||||
camera_embedder_cls: CharacterGen.Stage_3D.lrm.models.camera.LinearCameraEmbedder
|
||||
camera_embedder:
|
||||
in_channels: 16
|
||||
out_channels: 768
|
||||
conditions:
|
||||
- c2w_cond
|
||||
|
||||
# image tokenizer transforms input images to tokens
|
||||
image_tokenizer_cls: CharacterGen.Stage_3D.lrm.models.tokenizers.image.DINOV2SingleImageTokenizer
|
||||
image_tokenizer:
|
||||
pretrained_model_name_or_path: "3D_Stage/models/base"
|
||||
freeze_backbone_params: false
|
||||
enable_memory_efficient_attention: true
|
||||
enable_gradient_checkpointing: true
|
||||
# camera modulation to the DINO transformer layers
|
||||
modulation: true
|
||||
modulation_zero_init: true
|
||||
modulation_single_layer: true
|
||||
modulation_cond_dim: ${system.camera_embedder.out_channels}
|
||||
|
||||
# tokenizer gives a tokenized representation for the 3D scene
|
||||
# triplane tokens in this case
|
||||
tokenizer_cls: CharacterGen.Stage_3D.lrm.models.tokenizers.triplane.TriplaneLearnablePositionalEmbedding
|
||||
tokenizer:
|
||||
plane_size: 32
|
||||
num_channels: 512
|
||||
|
||||
# backbone network is a transformer that takes scene tokens (potentially with conditional image tokens)
|
||||
# and outputs scene tokens of the same size
|
||||
backbone_cls: CharacterGen.Stage_3D.lrm.models.transformers.transformer_1d.Transformer1D
|
||||
backbone:
|
||||
in_channels: ${system.tokenizer.num_channels}
|
||||
num_attention_heads: 16
|
||||
attention_head_dim: 64
|
||||
num_layers: 12
|
||||
cross_attention_dim: 768 # hard-code, =DINO feature dim
|
||||
# camera modulation to the transformer layers
|
||||
# if not needed, set norm_type=layer_norm and do not specify cond_dim_ada_norm_continuous
|
||||
norm_type: "layer_norm"
|
||||
enable_memory_efficient_attention: true
|
||||
gradient_checkpointing: true
|
||||
|
||||
# post processor takes scene tokens and outputs the final scene parameters that will be used for rendering
|
||||
# in this case, triplanes are upsampled and the features are condensed
|
||||
post_processor_cls: CharacterGen.Stage_3D.lrm.models.networks.TriplaneUpsampleNetwork
|
||||
post_processor:
|
||||
in_channels: 512
|
||||
out_channels: 80
|
||||
|
||||
renderer_cls: CharacterGen.Stage_3D.lrm.models.renderers.triplane_dmtet.TriplaneDMTetRenderer
|
||||
renderer:
|
||||
radius: 0.6 # slightly larger than 0.5
|
||||
feature_reduction: concat
|
||||
sdf_bias: -2.
|
||||
tet_dir: "tets"
|
||||
isosurface_resolution: 256
|
||||
enable_isosurface_grid_deformation: false
|
||||
sdf_activation: negative
|
||||
|
||||
decoder_cls: CharacterGen.Stage_3D.lrm.models.networks.MultiHeadMLP
|
||||
decoder:
|
||||
in_channels: 240 # 3 * 80
|
||||
n_neurons: 64
|
||||
n_hidden_layers_share: 8
|
||||
heads:
|
||||
- name: sdf
|
||||
out_channels: 1
|
||||
n_hidden_layers: 1
|
||||
output_activation: null
|
||||
- name: features
|
||||
out_channels: 3
|
||||
n_hidden_layers: 1
|
||||
output_activation: null # activate in material
|
||||
activation: silu
|
||||
chunk_mode: deferred
|
||||
chunk_size: 131072
|
||||
|
||||
exporter:
|
||||
fmt: "obj"
|
||||
#visual: "vertex"
|
||||
visual: "uv"
|
||||
save_uv: True
|
||||
save_texture: True
|
||||
uv_unwrap_method: "open3d"
|
||||
output_path: "3D_Stage/outputs"
|
||||
|
||||
material_cls: CharacterGen.Stage_3D.lrm.models.materials.no_material.NoMaterial
|
||||
|
||||
background_cls: CharacterGen.Stage_3D.lrm.models.background.solid_color_background.SolidColorBackground
|
||||
background:
|
||||
color: [0.5, 0.5, 0.5]
|
||||
@@ -0,0 +1,38 @@
|
||||
[
|
||||
[
|
||||
[
|
||||
0, 0, -1, 0,
|
||||
0, 1, 0, 0,
|
||||
1, 0, 0, 0,
|
||||
1.5, 0, 0, 1
|
||||
],
|
||||
"pose0.png"
|
||||
],
|
||||
[
|
||||
[
|
||||
0, 0, 1, 0,
|
||||
0, 1, 0, 0,
|
||||
-1, 0, 0, 0,
|
||||
-1.5, 0, 0, 1
|
||||
],
|
||||
"pose1.png"
|
||||
],
|
||||
[
|
||||
[
|
||||
0, 0, 1, 0,
|
||||
0, 1, 0, 0,
|
||||
-1, 0, 0, 0,
|
||||
-1.5, 0, 0, 1
|
||||
],
|
||||
"pose2.png"
|
||||
],
|
||||
[
|
||||
[
|
||||
-1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, -1, 0,
|
||||
0, 0, -1.5, 1
|
||||
],
|
||||
"pose3.png"
|
||||
]
|
||||
]
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
@@ -0,0 +1,59 @@
|
||||
import os
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.init as init
|
||||
from einops import rearrange
|
||||
|
||||
class PoseGuider(nn.Module):
|
||||
def __init__(self, noise_latent_channels=4):
|
||||
super(PoseGuider, self).__init__()
|
||||
|
||||
self.conv_layers = nn.Sequential(
|
||||
nn.Conv2d(in_channels=3, out_channels=16, kernel_size=4, stride=2, padding=1),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(in_channels=16, out_channels=32, kernel_size=4, stride=2, padding=1),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=4, stride=2, padding=1),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
|
||||
nn.ReLU()
|
||||
)
|
||||
|
||||
# Final projection layer
|
||||
self.final_proj = nn.Conv2d(in_channels=128, out_channels=noise_latent_channels, kernel_size=1)
|
||||
|
||||
# Initialize layers
|
||||
self._initialize_weights()
|
||||
|
||||
def _initialize_weights(self):
|
||||
# Initialize weights with Gaussian distribution and zero out the final layer
|
||||
for m in self.conv_layers:
|
||||
if isinstance(m, nn.Conv2d):
|
||||
init.normal_(m.weight, mean=0.0, std=0.02)
|
||||
if m.bias is not None:
|
||||
init.zeros_(m.bias)
|
||||
|
||||
init.zeros_(self.final_proj.weight)
|
||||
if self.final_proj.bias is not None:
|
||||
init.zeros_(self.final_proj.bias)
|
||||
|
||||
def forward(self, pose_image):
|
||||
x = self.conv_layers(pose_image)
|
||||
x = self.final_proj(x)
|
||||
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def from_pretrained(pretrained_model_path):
|
||||
if not os.path.exists(pretrained_model_path):
|
||||
print(f"There is no model file in {pretrained_model_path}")
|
||||
print(f"loaded PoseGuider's pretrained weights from {pretrained_model_path} ...")
|
||||
|
||||
state_dict = torch.load(pretrained_model_path, map_location="cpu")
|
||||
model = PoseGuider(noise_latent_channels=4)
|
||||
m, u = model.load_state_dict(state_dict, strict=False)
|
||||
print(f"### missing keys: {len(m)}; \n### unexpected keys: {len(u)};")
|
||||
params = [p.numel() if "temporal" in n else 0 for n, p in model.named_parameters()]
|
||||
print(f"### PoseGuider's Parameters: {sum(params) / 1e6} M")
|
||||
|
||||
return model
|
||||
@@ -0,0 +1,344 @@
|
||||
# Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention.py
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
|
||||
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
||||
from diffusers import ModelMixin
|
||||
from diffusers.utils import BaseOutput
|
||||
from diffusers.utils.import_utils import is_xformers_available
|
||||
from diffusers.models.attention import CrossAttention, FeedForward, AdaLayerNorm
|
||||
|
||||
from einops import rearrange, repeat
|
||||
|
||||
|
||||
@dataclass
|
||||
class Transformer3DModelOutput(BaseOutput):
|
||||
sample: torch.FloatTensor
|
||||
|
||||
|
||||
if is_xformers_available():
|
||||
import xformers
|
||||
import xformers.ops
|
||||
else:
|
||||
xformers = None
|
||||
|
||||
|
||||
class Transformer3DModel(ModelMixin, ConfigMixin):
|
||||
@register_to_config
|
||||
def __init__(
|
||||
self,
|
||||
num_attention_heads: int = 16,
|
||||
attention_head_dim: int = 88,
|
||||
in_channels: Optional[int] = None,
|
||||
num_layers: int = 1,
|
||||
dropout: float = 0.0,
|
||||
norm_num_groups: int = 32,
|
||||
cross_attention_dim: Optional[int] = None,
|
||||
attention_bias: bool = False,
|
||||
activation_fn: str = "geglu",
|
||||
num_embeds_ada_norm: Optional[int] = None,
|
||||
use_linear_projection: bool = False,
|
||||
only_cross_attention: bool = False,
|
||||
upcast_attention: bool = False,
|
||||
use_attn_temp: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.use_linear_projection = use_linear_projection
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.attention_head_dim = attention_head_dim
|
||||
inner_dim = num_attention_heads * attention_head_dim
|
||||
|
||||
# Define input layers
|
||||
self.in_channels = in_channels
|
||||
|
||||
self.norm = torch.nn.GroupNorm(num_groups=norm_num_groups, num_channels=in_channels, eps=1e-6, affine=True)
|
||||
if use_linear_projection:
|
||||
self.proj_in = nn.Linear(in_channels, inner_dim)
|
||||
else:
|
||||
self.proj_in = nn.Conv2d(in_channels, inner_dim, kernel_size=1, stride=1, padding=0)
|
||||
|
||||
# Define transformers blocks
|
||||
self.transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock(
|
||||
inner_dim,
|
||||
num_attention_heads,
|
||||
attention_head_dim,
|
||||
dropout=dropout,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
activation_fn=activation_fn,
|
||||
num_embeds_ada_norm=num_embeds_ada_norm,
|
||||
attention_bias=attention_bias,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
use_attn_temp = use_attn_temp,
|
||||
)
|
||||
for d in range(num_layers)
|
||||
]
|
||||
)
|
||||
|
||||
# 4. Define output layers
|
||||
if use_linear_projection:
|
||||
self.proj_out = nn.Linear(in_channels, inner_dim)
|
||||
else:
|
||||
self.proj_out = nn.Conv2d(inner_dim, in_channels, kernel_size=1, stride=1, padding=0)
|
||||
|
||||
def forward(self, hidden_states, encoder_hidden_states=None, timestep=None, return_dict: bool = True):
|
||||
# Input
|
||||
assert hidden_states.dim() == 5, f"Expected hidden_states to have ndim=5, but got ndim={hidden_states.dim()}."
|
||||
video_length = hidden_states.shape[2]
|
||||
hidden_states = rearrange(hidden_states, "b c f h w -> (b f) c h w")
|
||||
encoder_hidden_states = repeat(encoder_hidden_states, 'b n c -> (b f) n c', f=video_length)
|
||||
|
||||
batch, channel, height, weight = hidden_states.shape
|
||||
residual = hidden_states
|
||||
|
||||
hidden_states = self.norm(hidden_states)
|
||||
if not self.use_linear_projection:
|
||||
hidden_states = self.proj_in(hidden_states)
|
||||
inner_dim = hidden_states.shape[1]
|
||||
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(batch, height * weight, inner_dim)
|
||||
else:
|
||||
inner_dim = hidden_states.shape[1]
|
||||
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(batch, height * weight, inner_dim)
|
||||
hidden_states = self.proj_in(hidden_states)
|
||||
|
||||
# Blocks
|
||||
for block in self.transformer_blocks:
|
||||
hidden_states = block(
|
||||
hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
timestep=timestep,
|
||||
video_length=video_length
|
||||
)
|
||||
|
||||
# Output
|
||||
if not self.use_linear_projection:
|
||||
hidden_states = (
|
||||
hidden_states.reshape(batch, height, weight, inner_dim).permute(0, 3, 1, 2).contiguous()
|
||||
)
|
||||
hidden_states = self.proj_out(hidden_states)
|
||||
else:
|
||||
hidden_states = self.proj_out(hidden_states)
|
||||
hidden_states = (
|
||||
hidden_states.reshape(batch, height, weight, inner_dim).permute(0, 3, 1, 2).contiguous()
|
||||
)
|
||||
|
||||
output = hidden_states + residual
|
||||
|
||||
output = rearrange(output, "(b f) c h w -> b c f h w", f=video_length)
|
||||
if not return_dict:
|
||||
return (output,)
|
||||
|
||||
return Transformer3DModelOutput(sample=output)
|
||||
|
||||
|
||||
class BasicTransformerBlock(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
num_attention_heads: int,
|
||||
attention_head_dim: int,
|
||||
dropout=0.0,
|
||||
cross_attention_dim: Optional[int] = None,
|
||||
activation_fn: str = "geglu",
|
||||
num_embeds_ada_norm: Optional[int] = None,
|
||||
attention_bias: bool = False,
|
||||
only_cross_attention: bool = False,
|
||||
upcast_attention: bool = False,
|
||||
use_attn_temp: bool = False
|
||||
):
|
||||
super().__init__()
|
||||
self.only_cross_attention = only_cross_attention
|
||||
self.use_ada_layer_norm = num_embeds_ada_norm is not None
|
||||
self.use_attn_temp = use_attn_temp
|
||||
# SC-Attn
|
||||
self.attn1 = SparseCausalAttention(
|
||||
query_dim=dim,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
cross_attention_dim=cross_attention_dim if only_cross_attention else None,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
self.norm1 = AdaLayerNorm(dim, num_embeds_ada_norm) if self.use_ada_layer_norm else nn.LayerNorm(dim)
|
||||
|
||||
# Cross-Attn
|
||||
if cross_attention_dim is not None:
|
||||
self.attn2 = CrossAttention(
|
||||
query_dim=dim,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
else:
|
||||
self.attn2 = None
|
||||
|
||||
if cross_attention_dim is not None:
|
||||
self.norm2 = AdaLayerNorm(dim, num_embeds_ada_norm) if self.use_ada_layer_norm else nn.LayerNorm(dim)
|
||||
else:
|
||||
self.norm2 = None
|
||||
|
||||
# Feed-forward
|
||||
self.ff = FeedForward(dim, dropout=dropout, activation_fn=activation_fn)
|
||||
self.norm3 = nn.LayerNorm(dim)
|
||||
|
||||
# Temp-Attn
|
||||
if self.use_attn_temp:
|
||||
self.attn_temp = CrossAttention(
|
||||
query_dim=dim,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
nn.init.zeros_(self.attn_temp.to_out[0].weight.data)
|
||||
self.norm_temp = AdaLayerNorm(dim, num_embeds_ada_norm) if self.use_ada_layer_norm else nn.LayerNorm(dim)
|
||||
|
||||
def set_use_memory_efficient_attention_xformers(self, use_memory_efficient_attention_xformers: bool):
|
||||
if not is_xformers_available():
|
||||
print("Here is how to install it")
|
||||
raise ModuleNotFoundError(
|
||||
"Refer to https://github.com/facebookresearch/xformers for more information on how to install"
|
||||
" xformers",
|
||||
name="xformers",
|
||||
)
|
||||
elif not torch.cuda.is_available():
|
||||
raise ValueError(
|
||||
"torch.cuda.is_available() should be True but is False. xformers' memory efficient attention is only"
|
||||
" available for GPU "
|
||||
)
|
||||
else:
|
||||
try:
|
||||
# Make sure we can run the memory efficient attention
|
||||
_ = xformers.ops.memory_efficient_attention(
|
||||
torch.randn((1, 2, 40), device="cuda"),
|
||||
torch.randn((1, 2, 40), device="cuda"),
|
||||
torch.randn((1, 2, 40), device="cuda"),
|
||||
)
|
||||
except Exception as e:
|
||||
raise e
|
||||
self.attn1._use_memory_efficient_attention_xformers = use_memory_efficient_attention_xformers
|
||||
if self.attn2 is not None:
|
||||
self.attn2._use_memory_efficient_attention_xformers = use_memory_efficient_attention_xformers
|
||||
#self.attn_temp._use_memory_efficient_attention_xformers = use_memory_efficient_attention_xformers
|
||||
|
||||
def forward(self, hidden_states, encoder_hidden_states=None, timestep=None, attention_mask=None, video_length=None):
|
||||
# SparseCausal-Attention
|
||||
norm_hidden_states = (
|
||||
self.norm1(hidden_states, timestep) if self.use_ada_layer_norm else self.norm1(hidden_states)
|
||||
)
|
||||
|
||||
if self.only_cross_attention:
|
||||
hidden_states = (
|
||||
self.attn1(norm_hidden_states, encoder_hidden_states, attention_mask=attention_mask) + hidden_states
|
||||
)
|
||||
else:
|
||||
hidden_states = self.attn1(norm_hidden_states, attention_mask=attention_mask, video_length=video_length) + hidden_states
|
||||
|
||||
if self.attn2 is not None:
|
||||
# Cross-Attention
|
||||
norm_hidden_states = (
|
||||
self.norm2(hidden_states, timestep) if self.use_ada_layer_norm else self.norm2(hidden_states)
|
||||
)
|
||||
hidden_states = (
|
||||
self.attn2(
|
||||
norm_hidden_states, encoder_hidden_states=encoder_hidden_states, attention_mask=attention_mask
|
||||
)
|
||||
+ hidden_states
|
||||
)
|
||||
|
||||
# Feed-forward
|
||||
hidden_states = self.ff(self.norm3(hidden_states)) + hidden_states
|
||||
|
||||
# Temporal-Attention
|
||||
if self.use_attn_temp:
|
||||
d = hidden_states.shape[1]
|
||||
hidden_states = rearrange(hidden_states, "(b f) d c -> (b d) f c", f=video_length)
|
||||
norm_hidden_states = (
|
||||
self.norm_temp(hidden_states, timestep) if self.use_ada_layer_norm else self.norm_temp(hidden_states)
|
||||
)
|
||||
hidden_states = self.attn_temp(norm_hidden_states) + hidden_states
|
||||
hidden_states = rearrange(hidden_states, "(b d) f c -> (b f) d c", d=d)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class SparseCausalAttention(CrossAttention):
|
||||
def forward(self, hidden_states, encoder_hidden_states=None, attention_mask=None, video_length=None, use_full_attn=True):
|
||||
batch_size, sequence_length, _ = hidden_states.shape
|
||||
|
||||
encoder_hidden_states = encoder_hidden_states
|
||||
|
||||
if self.group_norm is not None:
|
||||
hidden_states = self.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)
|
||||
|
||||
query = self.to_q(hidden_states)
|
||||
# query = rearrange(query, "(b f) d c -> b (f d) c", f=video_length)
|
||||
dim = query.shape[-1]
|
||||
query = self.reshape_heads_to_batch_dim(query)
|
||||
|
||||
if self.added_kv_proj_dim is not None:
|
||||
raise NotImplementedError
|
||||
|
||||
encoder_hidden_states = encoder_hidden_states if encoder_hidden_states is not None else hidden_states
|
||||
key = self.to_k(encoder_hidden_states)
|
||||
value = self.to_v(encoder_hidden_states)
|
||||
|
||||
former_frame_index = torch.arange(video_length) - 1
|
||||
former_frame_index[0] = 0
|
||||
|
||||
key = rearrange(key, "(b f) d c -> b f d c", f=video_length)
|
||||
if not use_full_attn:
|
||||
key = torch.cat([key[:, [0] * video_length], key[:, former_frame_index]], dim=2)
|
||||
else:
|
||||
# key = torch.cat([key[:, [0] * video_length], key[:, [1] * video_length], key[:, [2] * video_length], key[:, [3] * video_length]], dim=2)
|
||||
key_video_length = [key[:, [i] * video_length] for i in range(video_length)]
|
||||
key = torch.cat(key_video_length, dim=2)
|
||||
key = rearrange(key, "b f d c -> (b f) d c")
|
||||
|
||||
value = rearrange(value, "(b f) d c -> b f d c", f=video_length)
|
||||
if not use_full_attn:
|
||||
value = torch.cat([value[:, [0] * video_length], value[:, former_frame_index]], dim=2)
|
||||
else:
|
||||
# value = torch.cat([value[:, [0] * video_length], value[:, [1] * video_length], value[:, [2] * video_length], value[:, [3] * video_length]], dim=2)
|
||||
value_video_length = [value[:, [i] * video_length] for i in range(video_length)]
|
||||
value = torch.cat(value_video_length, dim=2)
|
||||
value = rearrange(value, "b f d c -> (b f) d c")
|
||||
|
||||
key = self.reshape_heads_to_batch_dim(key)
|
||||
value = self.reshape_heads_to_batch_dim(value)
|
||||
|
||||
if attention_mask is not None:
|
||||
if attention_mask.shape[-1] != query.shape[1]:
|
||||
target_length = query.shape[1]
|
||||
attention_mask = F.pad(attention_mask, (0, target_length), value=0.0)
|
||||
attention_mask = attention_mask.repeat_interleave(self.heads, dim=0)
|
||||
|
||||
# attention, what we cannot get enough of
|
||||
if self._use_memory_efficient_attention_xformers:
|
||||
hidden_states = self._memory_efficient_attention_xformers(query, key, value, attention_mask)
|
||||
# Some versions of xformers return output in fp32, cast it back to the dtype of the input
|
||||
hidden_states = hidden_states.to(query.dtype)
|
||||
else:
|
||||
if self._slice_size is None or query.shape[0] // self._slice_size == 1:
|
||||
hidden_states = self._attention(query, key, value, attention_mask)
|
||||
else:
|
||||
hidden_states = self._sliced_attention(query, key, value, sequence_length, dim, attention_mask)
|
||||
|
||||
# linear proj
|
||||
hidden_states = self.to_out[0](hidden_states)
|
||||
|
||||
# dropout
|
||||
hidden_states = self.to_out[1](hidden_states)
|
||||
return hidden_states
|
||||
@@ -0,0 +1,118 @@
|
||||
# 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 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)
|
||||
@@ -0,0 +1,125 @@
|
||||
import torch
|
||||
from einops import rearrange
|
||||
from typing import Any, Dict, Optional
|
||||
from diffusers.utils.import_utils import is_xformers_available
|
||||
from .transformer_mv2d import XFormersMVAttnProcessor, MVAttnProcessor
|
||||
class ReferenceOnlyAttnProc(torch.nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
chained_proc,
|
||||
enabled=False,
|
||||
name=None
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.enabled = enabled
|
||||
self.chained_proc = chained_proc
|
||||
self.name = name
|
||||
|
||||
def __call__(
|
||||
self, attn, hidden_states, encoder_hidden_states=None, attention_mask=None,
|
||||
mode="w", ref_dict: dict = None, is_cfg_guidance = False,num_views=4,
|
||||
multiview_attention=True,
|
||||
cross_domain_attention=False,
|
||||
) -> Any:
|
||||
if encoder_hidden_states is None:
|
||||
encoder_hidden_states = hidden_states
|
||||
# print(self.enabled)
|
||||
if self.enabled:
|
||||
if mode == 'w':
|
||||
ref_dict[self.name] = encoder_hidden_states
|
||||
res = self.chained_proc(attn, hidden_states, encoder_hidden_states, attention_mask, num_views=1,
|
||||
multiview_attention=False,
|
||||
cross_domain_attention=False,)
|
||||
elif mode == 'r':
|
||||
encoder_hidden_states = rearrange(encoder_hidden_states, '(b t) d c-> b (t d) c', t=num_views)
|
||||
if self.name in ref_dict:
|
||||
encoder_hidden_states = torch.cat([encoder_hidden_states, ref_dict.pop(self.name)], dim=1).unsqueeze(1).repeat(1,num_views,1,1).flatten(0,1)
|
||||
res = self.chained_proc(attn, hidden_states, encoder_hidden_states, attention_mask, num_views=num_views,
|
||||
multiview_attention=False,
|
||||
cross_domain_attention=False,)
|
||||
elif mode == 'm':
|
||||
encoder_hidden_states = torch.cat([encoder_hidden_states, ref_dict[self.name]], dim=1)
|
||||
elif mode == 'n':
|
||||
encoder_hidden_states = rearrange(encoder_hidden_states, '(b t) d c-> b (t d) c', t=num_views)
|
||||
encoder_hidden_states = torch.cat([encoder_hidden_states], dim=1).unsqueeze(1).repeat(1,num_views,1,1).flatten(0,1)
|
||||
res = self.chained_proc(attn, hidden_states, encoder_hidden_states, attention_mask, num_views=num_views,
|
||||
multiview_attention=False,
|
||||
cross_domain_attention=False,)
|
||||
else:
|
||||
assert False, mode
|
||||
else:
|
||||
res = self.chained_proc(attn, hidden_states, encoder_hidden_states, attention_mask)
|
||||
return res
|
||||
|
||||
class RefOnlyNoisedUNet(torch.nn.Module):
|
||||
def __init__(self, unet, train_sched, val_sched) -> None:
|
||||
super().__init__()
|
||||
self.unet = unet
|
||||
self.train_sched = train_sched
|
||||
self.val_sched = val_sched
|
||||
|
||||
unet_lora_attn_procs = dict()
|
||||
for name, _ in unet.attn_processors.items():
|
||||
if is_xformers_available():
|
||||
default_attn_proc = XFormersMVAttnProcessor()
|
||||
else:
|
||||
default_attn_proc = MVAttnProcessor()
|
||||
unet_lora_attn_procs[name] = ReferenceOnlyAttnProc(
|
||||
default_attn_proc, enabled=name.endswith("attn1.processor"), name=name)
|
||||
|
||||
self.unet.set_attn_processor(unet_lora_attn_procs)
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
try:
|
||||
return super().__getattr__(name)
|
||||
except AttributeError:
|
||||
return getattr(self.unet, name)
|
||||
|
||||
def forward_cond(self, noisy_cond_lat, timestep, encoder_hidden_states, class_labels, ref_dict, is_cfg_guidance, **kwargs):
|
||||
if is_cfg_guidance:
|
||||
encoder_hidden_states = encoder_hidden_states[1:]
|
||||
class_labels = class_labels[1:]
|
||||
self.unet(
|
||||
noisy_cond_lat, timestep,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
class_labels=class_labels,
|
||||
cross_attention_kwargs=dict(mode="w", ref_dict=ref_dict),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def forward(
|
||||
self, sample, timestep, encoder_hidden_states, class_labels=None,
|
||||
*args, cross_attention_kwargs,
|
||||
down_block_res_samples=None, mid_block_res_sample=None,
|
||||
**kwargs
|
||||
):
|
||||
cond_lat = cross_attention_kwargs['cond_lat']
|
||||
is_cfg_guidance = cross_attention_kwargs.get('is_cfg_guidance', False)
|
||||
noise = torch.randn_like(cond_lat)
|
||||
if self.training:
|
||||
noisy_cond_lat = self.train_sched.add_noise(cond_lat, noise, timestep)
|
||||
noisy_cond_lat = self.train_sched.scale_model_input(noisy_cond_lat, timestep)
|
||||
else:
|
||||
noisy_cond_lat = self.val_sched.add_noise(cond_lat, noise, timestep.reshape(-1))
|
||||
noisy_cond_lat = self.val_sched.scale_model_input(noisy_cond_lat, timestep.reshape(-1))
|
||||
ref_dict = {}
|
||||
self.forward_cond(
|
||||
noisy_cond_lat, timestep,
|
||||
encoder_hidden_states, class_labels,
|
||||
ref_dict, is_cfg_guidance, **kwargs
|
||||
)
|
||||
weight_dtype = self.unet.dtype
|
||||
return self.unet(
|
||||
sample, timestep,
|
||||
encoder_hidden_states, *args,
|
||||
class_labels=class_labels,
|
||||
cross_attention_kwargs=dict(mode="r", ref_dict=ref_dict, is_cfg_guidance=is_cfg_guidance),
|
||||
down_block_additional_residuals=[
|
||||
sample.to(dtype=weight_dtype) for sample in down_block_res_samples
|
||||
] if down_block_res_samples is not None else None,
|
||||
mid_block_additional_residual=(
|
||||
mid_block_res_sample.to(dtype=weight_dtype)
|
||||
if mid_block_res_sample is not None else None
|
||||
),
|
||||
**kwargs
|
||||
)
|
||||
@@ -0,0 +1,210 @@
|
||||
# Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/resnet.py
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from einops import rearrange
|
||||
|
||||
|
||||
class InflatedConv3d(nn.Conv2d):
|
||||
def forward(self, x):
|
||||
video_length = x.shape[2]
|
||||
|
||||
x = rearrange(x, "b c f h w -> (b f) c h w")
|
||||
x = super().forward(x)
|
||||
x = rearrange(x, "(b f) c h w -> b c f h w", f=video_length)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class Upsample3D(nn.Module):
|
||||
def __init__(self, channels, use_conv=False, use_conv_transpose=False, out_channels=None, name="conv"):
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.out_channels = out_channels or channels
|
||||
self.use_conv = use_conv
|
||||
self.use_conv_transpose = use_conv_transpose
|
||||
self.name = name
|
||||
|
||||
conv = None
|
||||
if use_conv_transpose:
|
||||
raise NotImplementedError
|
||||
elif use_conv:
|
||||
conv = InflatedConv3d(self.channels, self.out_channels, 3, padding=1)
|
||||
|
||||
if name == "conv":
|
||||
self.conv = conv
|
||||
else:
|
||||
self.Conv2d_0 = conv
|
||||
|
||||
def forward(self, hidden_states, output_size=None):
|
||||
assert hidden_states.shape[1] == self.channels
|
||||
|
||||
if self.use_conv_transpose:
|
||||
raise NotImplementedError
|
||||
|
||||
# Cast to float32 to as 'upsample_nearest2d_out_frame' op does not support bfloat16
|
||||
dtype = hidden_states.dtype
|
||||
if dtype == torch.bfloat16:
|
||||
hidden_states = hidden_states.to(torch.float32)
|
||||
|
||||
# upsample_nearest_nhwc fails with large batch sizes. see https://github.com/huggingface/diffusers/issues/984
|
||||
if hidden_states.shape[0] >= 64:
|
||||
hidden_states = hidden_states.contiguous()
|
||||
|
||||
# if `output_size` is passed we force the interpolation output
|
||||
# size and do not make use of `scale_factor=2`
|
||||
if output_size is None:
|
||||
hidden_states = F.interpolate(hidden_states, scale_factor=[1.0, 2.0, 2.0], mode="nearest")
|
||||
else:
|
||||
hidden_states = F.interpolate(hidden_states, size=output_size, mode="nearest")
|
||||
|
||||
# If the input is bfloat16, we cast back to bfloat16
|
||||
if dtype == torch.bfloat16:
|
||||
hidden_states = hidden_states.to(dtype)
|
||||
|
||||
if self.use_conv:
|
||||
if self.name == "conv":
|
||||
hidden_states = self.conv(hidden_states)
|
||||
else:
|
||||
hidden_states = self.Conv2d_0(hidden_states)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class Downsample3D(nn.Module):
|
||||
def __init__(self, channels, use_conv=False, out_channels=None, padding=1, name="conv"):
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.out_channels = out_channels or channels
|
||||
self.use_conv = use_conv
|
||||
self.padding = padding
|
||||
stride = 2
|
||||
self.name = name
|
||||
|
||||
if use_conv:
|
||||
conv = InflatedConv3d(self.channels, self.out_channels, 3, stride=stride, padding=padding)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if name == "conv":
|
||||
self.Conv2d_0 = conv
|
||||
self.conv = conv
|
||||
elif name == "Conv2d_0":
|
||||
self.conv = conv
|
||||
else:
|
||||
self.conv = conv
|
||||
|
||||
def forward(self, hidden_states):
|
||||
assert hidden_states.shape[1] == self.channels
|
||||
if self.use_conv and self.padding == 0:
|
||||
raise NotImplementedError
|
||||
|
||||
assert hidden_states.shape[1] == self.channels
|
||||
hidden_states = self.conv(hidden_states)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class ResnetBlock3D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
in_channels,
|
||||
out_channels=None,
|
||||
conv_shortcut=False,
|
||||
dropout=0.0,
|
||||
temb_channels=512,
|
||||
groups=32,
|
||||
groups_out=None,
|
||||
pre_norm=True,
|
||||
eps=1e-6,
|
||||
non_linearity="swish",
|
||||
time_embedding_norm="default",
|
||||
output_scale_factor=1.0,
|
||||
use_in_shortcut=None,
|
||||
):
|
||||
super().__init__()
|
||||
self.pre_norm = pre_norm
|
||||
self.pre_norm = True
|
||||
self.in_channels = in_channels
|
||||
out_channels = in_channels if out_channels is None else out_channels
|
||||
self.out_channels = out_channels
|
||||
self.use_conv_shortcut = conv_shortcut
|
||||
self.time_embedding_norm = time_embedding_norm
|
||||
self.output_scale_factor = output_scale_factor
|
||||
|
||||
if groups_out is None:
|
||||
groups_out = groups
|
||||
|
||||
self.norm1 = torch.nn.GroupNorm(num_groups=groups, num_channels=in_channels, eps=eps, affine=True)
|
||||
|
||||
self.conv1 = InflatedConv3d(in_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
||||
|
||||
if temb_channels is not None:
|
||||
if self.time_embedding_norm == "default":
|
||||
time_emb_proj_out_channels = out_channels
|
||||
elif self.time_embedding_norm == "scale_shift":
|
||||
time_emb_proj_out_channels = out_channels * 2
|
||||
else:
|
||||
raise ValueError(f"unknown time_embedding_norm : {self.time_embedding_norm} ")
|
||||
|
||||
self.time_emb_proj = torch.nn.Linear(temb_channels, time_emb_proj_out_channels)
|
||||
else:
|
||||
self.time_emb_proj = None
|
||||
|
||||
self.norm2 = torch.nn.GroupNorm(num_groups=groups_out, num_channels=out_channels, eps=eps, affine=True)
|
||||
self.dropout = torch.nn.Dropout(dropout)
|
||||
self.conv2 = InflatedConv3d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
||||
|
||||
if non_linearity == "swish":
|
||||
self.nonlinearity = lambda x: F.silu(x)
|
||||
elif non_linearity == "mish":
|
||||
self.nonlinearity = Mish()
|
||||
elif non_linearity == "silu":
|
||||
self.nonlinearity = nn.SiLU()
|
||||
|
||||
self.use_in_shortcut = self.in_channels != self.out_channels if use_in_shortcut is None else use_in_shortcut
|
||||
|
||||
self.conv_shortcut = None
|
||||
if self.use_in_shortcut:
|
||||
self.conv_shortcut = InflatedConv3d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
|
||||
|
||||
def forward(self, input_tensor, temb):
|
||||
hidden_states = input_tensor
|
||||
|
||||
hidden_states = self.norm1(hidden_states)
|
||||
hidden_states = self.nonlinearity(hidden_states)
|
||||
|
||||
hidden_states = self.conv1(hidden_states)
|
||||
|
||||
if temb is not None:
|
||||
# temb = self.time_emb_proj(self.nonlinearity(temb))[:, :, None, None, None]
|
||||
temb = self.time_emb_proj(self.nonlinearity(temb))[:, :, :, None, None].permute(0,2,1,3,4)
|
||||
|
||||
if temb is not None and self.time_embedding_norm == "default":
|
||||
hidden_states = hidden_states + temb
|
||||
|
||||
hidden_states = self.norm2(hidden_states)
|
||||
|
||||
if temb is not None and self.time_embedding_norm == "scale_shift":
|
||||
scale, shift = torch.chunk(temb, 2, dim=1)
|
||||
hidden_states = hidden_states * (1 + scale) + shift
|
||||
|
||||
hidden_states = self.nonlinearity(hidden_states)
|
||||
|
||||
hidden_states = self.dropout(hidden_states)
|
||||
hidden_states = self.conv2(hidden_states)
|
||||
|
||||
if self.conv_shortcut is not None:
|
||||
input_tensor = self.conv_shortcut(input_tensor)
|
||||
|
||||
output_tensor = (input_tensor + hidden_states) / self.output_scale_factor
|
||||
|
||||
return output_tensor
|
||||
|
||||
|
||||
class Mish(torch.nn.Module):
|
||||
def forward(self, hidden_states):
|
||||
return hidden_states * torch.tanh(torch.nn.functional.softplus(hidden_states))
|
||||
@@ -0,0 +1,497 @@
|
||||
# Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/unet_2d_condition.py
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.utils.checkpoint
|
||||
|
||||
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
||||
from diffusers import ModelMixin
|
||||
from diffusers.utils import BaseOutput, logging
|
||||
from diffusers.models.embeddings import TimestepEmbedding, Timesteps
|
||||
from .unet_blocks import (
|
||||
CrossAttnDownBlock3D,
|
||||
CrossAttnUpBlock3D,
|
||||
DownBlock3D,
|
||||
UNetMidBlock3DCrossAttn,
|
||||
UpBlock3D,
|
||||
get_down_block,
|
||||
get_up_block,
|
||||
)
|
||||
from .resnet import InflatedConv3d
|
||||
|
||||
|
||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@dataclass
|
||||
class UNet3DConditionOutput(BaseOutput):
|
||||
sample: torch.FloatTensor
|
||||
|
||||
|
||||
class UNet3DConditionModel(ModelMixin, ConfigMixin):
|
||||
_supports_gradient_checkpointing = True
|
||||
|
||||
@register_to_config
|
||||
def __init__(
|
||||
self,
|
||||
sample_size: Optional[int] = None,
|
||||
in_channels: int = 4,
|
||||
out_channels: int = 4,
|
||||
center_input_sample: bool = False,
|
||||
flip_sin_to_cos: bool = True,
|
||||
freq_shift: int = 0,
|
||||
down_block_types: Tuple[str] = (
|
||||
"CrossAttnDownBlock3D",
|
||||
"CrossAttnDownBlock3D",
|
||||
"CrossAttnDownBlock3D",
|
||||
"DownBlock3D",
|
||||
),
|
||||
mid_block_type: str = "UNetMidBlock3DCrossAttn",
|
||||
up_block_types: Tuple[str] = (
|
||||
"UpBlock3D",
|
||||
"CrossAttnUpBlock3D",
|
||||
"CrossAttnUpBlock3D",
|
||||
"CrossAttnUpBlock3D"
|
||||
),
|
||||
only_cross_attention: Union[bool, Tuple[bool]] = False,
|
||||
block_out_channels: Tuple[int] = (320, 640, 1280, 1280),
|
||||
layers_per_block: int = 2,
|
||||
downsample_padding: int = 1,
|
||||
mid_block_scale_factor: float = 1,
|
||||
act_fn: str = "silu",
|
||||
norm_num_groups: int = 32,
|
||||
norm_eps: float = 1e-5,
|
||||
cross_attention_dim: int = 1280,
|
||||
attention_head_dim: Union[int, Tuple[int]] = 8,
|
||||
dual_cross_attention: bool = False,
|
||||
use_linear_projection: bool = False,
|
||||
class_embed_type: Optional[str] = None,
|
||||
num_class_embeds: Optional[int] = None,
|
||||
upcast_attention: bool = False,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
use_attn_temp: bool = False,
|
||||
camera_input_dim: int = 12,
|
||||
camera_hidden_dim: int = 320,
|
||||
camera_output_dim: int = 1280,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.sample_size = sample_size
|
||||
time_embed_dim = block_out_channels[0] * 4
|
||||
|
||||
# input
|
||||
self.conv_in = InflatedConv3d(in_channels, block_out_channels[0], kernel_size=3, padding=(1, 1))
|
||||
|
||||
# time
|
||||
self.time_proj = Timesteps(block_out_channels[0], flip_sin_to_cos, freq_shift)
|
||||
timestep_input_dim = block_out_channels[0]
|
||||
|
||||
self.time_embedding = TimestepEmbedding(timestep_input_dim, time_embed_dim)
|
||||
|
||||
# class embedding
|
||||
if class_embed_type is None and num_class_embeds is not None:
|
||||
self.class_embedding = nn.Embedding(num_class_embeds, time_embed_dim)
|
||||
elif class_embed_type == "timestep":
|
||||
self.class_embedding = TimestepEmbedding(timestep_input_dim, time_embed_dim)
|
||||
elif class_embed_type == "identity":
|
||||
self.class_embedding = nn.Identity(time_embed_dim, time_embed_dim)
|
||||
else:
|
||||
self.class_embedding = None
|
||||
|
||||
# camera metrix
|
||||
# def init_linear(l, stddev):
|
||||
# nn.init.normal_(l.weight, std=stddev)
|
||||
# if l.bias is not None:
|
||||
# nn.init.constant_(l.bias, 0.0)
|
||||
# self.camera_embedding_1 = nn.Linear(camera_input_dim, camera_hidden_dim)
|
||||
# self.camera_embedding_2 = nn.Linear(camera_hidden_dim, camera_output_dim)
|
||||
# init_linear(self.camera_embedding_1, 0.25)
|
||||
# init_linear(self.camera_embedding_2, 0.25)
|
||||
|
||||
self.camera_embedding = nn.Sequential(
|
||||
nn.Linear(camera_input_dim, time_embed_dim),
|
||||
nn.SiLU(),
|
||||
nn.Linear(time_embed_dim, time_embed_dim),
|
||||
)
|
||||
|
||||
self.down_blocks = nn.ModuleList([])
|
||||
self.mid_block = None
|
||||
self.up_blocks = nn.ModuleList([])
|
||||
|
||||
if isinstance(only_cross_attention, bool):
|
||||
only_cross_attention = [only_cross_attention] * len(down_block_types)
|
||||
|
||||
if isinstance(attention_head_dim, int):
|
||||
attention_head_dim = (attention_head_dim,) * len(down_block_types)
|
||||
|
||||
# down
|
||||
output_channel = block_out_channels[0]
|
||||
for i, down_block_type in enumerate(down_block_types):
|
||||
input_channel = output_channel
|
||||
output_channel = block_out_channels[i]
|
||||
is_final_block = i == len(block_out_channels) - 1
|
||||
|
||||
down_block = get_down_block(
|
||||
down_block_type,
|
||||
num_layers=layers_per_block,
|
||||
in_channels=input_channel,
|
||||
out_channels=output_channel,
|
||||
temb_channels=time_embed_dim,
|
||||
add_downsample=not is_final_block,
|
||||
resnet_eps=norm_eps,
|
||||
resnet_act_fn=act_fn,
|
||||
resnet_groups=norm_num_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attn_num_head_channels=attention_head_dim[i],
|
||||
downsample_padding=downsample_padding,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention[i],
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
use_attn_temp=use_attn_temp
|
||||
)
|
||||
self.down_blocks.append(down_block)
|
||||
|
||||
# mid
|
||||
if mid_block_type == "UNetMidBlock3DCrossAttn":
|
||||
self.mid_block = UNetMidBlock3DCrossAttn(
|
||||
in_channels=block_out_channels[-1],
|
||||
temb_channels=time_embed_dim,
|
||||
resnet_eps=norm_eps,
|
||||
resnet_act_fn=act_fn,
|
||||
output_scale_factor=mid_block_scale_factor,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attn_num_head_channels=attention_head_dim[-1],
|
||||
resnet_groups=norm_num_groups,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"unknown mid_block_type : {mid_block_type}")
|
||||
|
||||
# count how many layers upsample the videos
|
||||
self.num_upsamplers = 0
|
||||
|
||||
# up
|
||||
reversed_block_out_channels = list(reversed(block_out_channels))
|
||||
reversed_attention_head_dim = list(reversed(attention_head_dim))
|
||||
only_cross_attention = list(reversed(only_cross_attention))
|
||||
output_channel = reversed_block_out_channels[0]
|
||||
for i, up_block_type in enumerate(up_block_types):
|
||||
is_final_block = i == len(block_out_channels) - 1
|
||||
|
||||
prev_output_channel = output_channel
|
||||
output_channel = reversed_block_out_channels[i]
|
||||
input_channel = reversed_block_out_channels[min(i + 1, len(block_out_channels) - 1)]
|
||||
|
||||
# add upsample block for all BUT final layer
|
||||
if not is_final_block:
|
||||
add_upsample = True
|
||||
self.num_upsamplers += 1
|
||||
else:
|
||||
add_upsample = False
|
||||
|
||||
up_block = get_up_block(
|
||||
up_block_type,
|
||||
num_layers=layers_per_block + 1,
|
||||
in_channels=input_channel,
|
||||
out_channels=output_channel,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=time_embed_dim,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=norm_eps,
|
||||
resnet_act_fn=act_fn,
|
||||
resnet_groups=norm_num_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attn_num_head_channels=reversed_attention_head_dim[i],
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention[i],
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
use_attn_temp=use_attn_temp,
|
||||
)
|
||||
self.up_blocks.append(up_block)
|
||||
prev_output_channel = output_channel
|
||||
|
||||
# out
|
||||
self.conv_norm_out = nn.GroupNorm(num_channels=block_out_channels[0], num_groups=norm_num_groups, eps=norm_eps)
|
||||
self.conv_act = nn.SiLU()
|
||||
self.conv_out = InflatedConv3d(block_out_channels[0], out_channels, kernel_size=3, padding=1)
|
||||
|
||||
def set_attention_slice(self, slice_size):
|
||||
r"""
|
||||
Enable sliced attention computation.
|
||||
|
||||
When this option is enabled, the attention module will split the input tensor in slices, to compute attention
|
||||
in several steps. This is useful to save some memory in exchange for a small speed decrease.
|
||||
|
||||
Args:
|
||||
slice_size (`str` or `int` or `list(int)`, *optional*, defaults to `"auto"`):
|
||||
When `"auto"`, halves the input to the attention heads, so attention will be computed in two steps. If
|
||||
`"max"`, maxium amount of memory will be saved by running only one slice at a time. If a number is
|
||||
provided, uses as many slices as `attention_head_dim // slice_size`. In this case, `attention_head_dim`
|
||||
must be a multiple of `slice_size`.
|
||||
"""
|
||||
sliceable_head_dims = []
|
||||
|
||||
def fn_recursive_retrieve_slicable_dims(module: torch.nn.Module):
|
||||
if hasattr(module, "set_attention_slice"):
|
||||
sliceable_head_dims.append(module.sliceable_head_dim)
|
||||
|
||||
for child in module.children():
|
||||
fn_recursive_retrieve_slicable_dims(child)
|
||||
|
||||
# retrieve number of attention layers
|
||||
for module in self.children():
|
||||
fn_recursive_retrieve_slicable_dims(module)
|
||||
|
||||
num_slicable_layers = len(sliceable_head_dims)
|
||||
|
||||
if slice_size == "auto":
|
||||
# half the attention head size is usually a good trade-off between
|
||||
# speed and memory
|
||||
slice_size = [dim // 2 for dim in sliceable_head_dims]
|
||||
elif slice_size == "max":
|
||||
# make smallest slice possible
|
||||
slice_size = num_slicable_layers * [1]
|
||||
|
||||
slice_size = num_slicable_layers * [slice_size] if not isinstance(slice_size, list) else slice_size
|
||||
|
||||
if len(slice_size) != len(sliceable_head_dims):
|
||||
raise ValueError(
|
||||
f"You have provided {len(slice_size)}, but {self.config} has {len(sliceable_head_dims)} different"
|
||||
f" attention layers. Make sure to match `len(slice_size)` to be {len(sliceable_head_dims)}."
|
||||
)
|
||||
|
||||
for i in range(len(slice_size)):
|
||||
size = slice_size[i]
|
||||
dim = sliceable_head_dims[i]
|
||||
if size is not None and size > dim:
|
||||
raise ValueError(f"size {size} has to be smaller or equal to {dim}.")
|
||||
|
||||
# Recursively walk through all the children.
|
||||
# Any children which exposes the set_attention_slice method
|
||||
# gets the message
|
||||
def fn_recursive_set_attention_slice(module: torch.nn.Module, slice_size: List[int]):
|
||||
if hasattr(module, "set_attention_slice"):
|
||||
module.set_attention_slice(slice_size.pop())
|
||||
|
||||
for child in module.children():
|
||||
fn_recursive_set_attention_slice(child, slice_size)
|
||||
|
||||
reversed_slice_size = list(reversed(slice_size))
|
||||
for module in self.children():
|
||||
fn_recursive_set_attention_slice(module, reversed_slice_size)
|
||||
|
||||
def _set_gradient_checkpointing(self, module, value=False):
|
||||
if isinstance(module, (CrossAttnDownBlock3D, DownBlock3D, CrossAttnUpBlock3D, UpBlock3D)):
|
||||
module.gradient_checkpointing = value
|
||||
|
||||
def forward(
|
||||
self,
|
||||
sample: torch.FloatTensor,
|
||||
timestep: Union[torch.Tensor, float, int],
|
||||
encoder_hidden_states: torch.Tensor,
|
||||
camera_matrixs: Optional[torch.Tensor] = None,
|
||||
class_labels: Optional[torch.Tensor] = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
return_dict: bool = True,
|
||||
) -> Union[UNet3DConditionOutput, Tuple]:
|
||||
r"""
|
||||
Args:
|
||||
sample (`torch.FloatTensor`): (batch, channel, height, width) noisy inputs tensor
|
||||
timestep (`torch.FloatTensor` or `float` or `int`): (batch) timesteps
|
||||
encoder_hidden_states (`torch.FloatTensor`): (batch, sequence_length, feature_dim) encoder hidden states
|
||||
return_dict (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to return a [`models.unet_2d_condition.UNet2DConditionOutput`] instead of a plain tuple.
|
||||
|
||||
Returns:
|
||||
[`~models.unet_2d_condition.UNet2DConditionOutput`] or `tuple`:
|
||||
[`~models.unet_2d_condition.UNet2DConditionOutput`] if `return_dict` is True, otherwise a `tuple`. When
|
||||
returning a tuple, the first element is the sample tensor.
|
||||
"""
|
||||
# By default samples have to be AT least a multiple of the overall upsampling factor.
|
||||
# The overall upsampling factor is equal to 2 ** (# num of upsampling layears).
|
||||
# However, the upsampling interpolation output size can be forced to fit any upsampling size
|
||||
# on the fly if necessary.
|
||||
default_overall_up_factor = 2**self.num_upsamplers
|
||||
|
||||
# upsample size should be forwarded when sample is not a multiple of `default_overall_up_factor`
|
||||
forward_upsample_size = False
|
||||
upsample_size = None
|
||||
|
||||
if any(s % default_overall_up_factor != 0 for s in sample.shape[-2:]):
|
||||
logger.info("Forward upsample size to force interpolation output size.")
|
||||
forward_upsample_size = True
|
||||
|
||||
# prepare attention_mask
|
||||
if attention_mask is not None:
|
||||
attention_mask = (1 - attention_mask.to(sample.dtype)) * -10000.0
|
||||
attention_mask = attention_mask.unsqueeze(1)
|
||||
|
||||
# center input if necessary
|
||||
if self.config.center_input_sample:
|
||||
sample = 2 * sample - 1.0
|
||||
# time
|
||||
timesteps = timestep
|
||||
if not torch.is_tensor(timesteps):
|
||||
# This would be a good case for the `match` statement (Python 3.10+)
|
||||
is_mps = sample.device.type == "mps"
|
||||
if isinstance(timestep, float):
|
||||
dtype = torch.float32 if is_mps else torch.float64
|
||||
else:
|
||||
dtype = torch.int32 if is_mps else torch.int64
|
||||
timesteps = torch.tensor([timesteps], dtype=dtype, device=sample.device)
|
||||
elif len(timesteps.shape) == 0:
|
||||
timesteps = timesteps[None].to(sample.device)
|
||||
|
||||
# broadcast to batch dimension in a way that's compatible with ONNX/Core ML
|
||||
timesteps = timesteps.expand(sample.shape[0])
|
||||
|
||||
t_emb = self.time_proj(timesteps)
|
||||
|
||||
# timesteps does not contain any weights and will always return f32 tensors
|
||||
# but time_embedding might actually be running in fp16. so we need to cast here.
|
||||
# there might be better ways to encapsulate this.
|
||||
t_emb = t_emb.to(dtype=self.dtype)
|
||||
emb = self.time_embedding(t_emb) #torch.Size([32, 1280])
|
||||
emb = torch.unsqueeze(emb, 1)
|
||||
if camera_matrixs is not None:
|
||||
# came emb
|
||||
cam_emb = self.camera_embedding(camera_matrixs)
|
||||
# cam_emb = self.camera_embedding_2(cam_emb)
|
||||
emb = emb.repeat(1,cam_emb.shape[1],1) #torch.Size([32, 4, 1280])
|
||||
emb = emb + cam_emb
|
||||
|
||||
# import pdb;pdb.set_trace()
|
||||
if self.class_embedding is not None:
|
||||
# if class_labels is None:
|
||||
# raise ValueError("class_labels should be provided when num_class_embeds > 0")
|
||||
if class_labels is not None:
|
||||
|
||||
if self.config.class_embed_type == "timestep":
|
||||
class_labels = self.time_proj(class_labels)
|
||||
|
||||
class_emb = self.class_embedding(class_labels)
|
||||
emb = emb + class_emb
|
||||
|
||||
# pre-process
|
||||
sample = self.conv_in(sample)
|
||||
|
||||
# down
|
||||
down_block_res_samples = (sample,)
|
||||
for downsample_block in self.down_blocks:
|
||||
if hasattr(downsample_block, "has_cross_attention") and downsample_block.has_cross_attention:
|
||||
sample, res_samples = downsample_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
attention_mask=attention_mask,
|
||||
)
|
||||
else:
|
||||
sample, res_samples = downsample_block(hidden_states=sample, temb=emb)
|
||||
|
||||
down_block_res_samples += res_samples
|
||||
|
||||
# mid
|
||||
sample = self.mid_block(
|
||||
sample, emb, encoder_hidden_states=encoder_hidden_states, attention_mask=attention_mask
|
||||
)
|
||||
|
||||
# up
|
||||
for i, upsample_block in enumerate(self.up_blocks):
|
||||
is_final_block = i == len(self.up_blocks) - 1
|
||||
|
||||
res_samples = down_block_res_samples[-len(upsample_block.resnets) :]
|
||||
down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)]
|
||||
|
||||
# if we have not reached the final block and need to forward the
|
||||
# upsample size, we do it here
|
||||
if not is_final_block and forward_upsample_size:
|
||||
upsample_size = down_block_res_samples[-1].shape[2:]
|
||||
|
||||
if hasattr(upsample_block, "has_cross_attention") and upsample_block.has_cross_attention:
|
||||
sample = upsample_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
res_hidden_states_tuple=res_samples,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
upsample_size=upsample_size,
|
||||
attention_mask=attention_mask,
|
||||
)
|
||||
else:
|
||||
sample = upsample_block(
|
||||
hidden_states=sample, temb=emb, res_hidden_states_tuple=res_samples, upsample_size=upsample_size
|
||||
)
|
||||
# post-process
|
||||
sample = self.conv_norm_out(sample)
|
||||
sample = self.conv_act(sample)
|
||||
sample = self.conv_out(sample)
|
||||
|
||||
if not return_dict:
|
||||
return (sample,)
|
||||
|
||||
return UNet3DConditionOutput(sample=sample)
|
||||
|
||||
@classmethod
|
||||
def from_pretrained_2d(cls, pretrained_model_path, subfolder=None):
|
||||
if subfolder is not None:
|
||||
pretrained_model_path = os.path.join(pretrained_model_path, subfolder)
|
||||
|
||||
config_file = os.path.join(pretrained_model_path, 'config.json')
|
||||
if not os.path.isfile(config_file):
|
||||
raise RuntimeError(f"{config_file} does not exist")
|
||||
with open(config_file, "r") as f:
|
||||
config = json.load(f)
|
||||
config["_class_name"] = cls.__name__
|
||||
config["down_block_types"] = [
|
||||
"CrossAttnDownBlock3D",
|
||||
"CrossAttnDownBlock3D",
|
||||
"CrossAttnDownBlock3D",
|
||||
"DownBlock3D"
|
||||
]
|
||||
config["up_block_types"] = [
|
||||
"UpBlock3D",
|
||||
"CrossAttnUpBlock3D",
|
||||
"CrossAttnUpBlock3D",
|
||||
"CrossAttnUpBlock3D"
|
||||
]
|
||||
|
||||
from diffusers.utils import WEIGHTS_NAME, SAFETENSORS_WEIGHTS_NAME
|
||||
# model = cls.from_config(config)
|
||||
# model_file = os.path.join(pretrained_model_path, WEIGHTS_NAME)
|
||||
# if not os.path.isfile(model_file):
|
||||
# raise RuntimeError(f"{model_file} does not exist")
|
||||
# state_dict = torch.load(model_file, map_location="cpu")
|
||||
|
||||
import safetensors
|
||||
model = cls.from_config(config)
|
||||
model_file = os.path.join(pretrained_model_path, WEIGHTS_NAME)
|
||||
if not os.path.isfile(model_file):
|
||||
model_file = os.path.join(pretrained_model_path, SAFETENSORS_WEIGHTS_NAME)
|
||||
if not os.path.isfile(model_file):
|
||||
raise RuntimeError(f"{model_file} does not exist")
|
||||
else:
|
||||
state_dict = safetensors.torch.load_file(model_file, device="cpu")
|
||||
else:
|
||||
state_dict = torch.load(model_file, map_location="cpu")
|
||||
|
||||
for k, v in model.state_dict().items():
|
||||
if '_temp.' in k or 'camera_embedding' in k or 'class_embedding' in k:
|
||||
state_dict.update({k: v})
|
||||
for k in list(state_dict.keys()):
|
||||
if 'camera_embedding_' in k:
|
||||
v = state_dict.pop(k)
|
||||
model.load_state_dict(state_dict)
|
||||
|
||||
return model
|
||||
@@ -0,0 +1,596 @@
|
||||
# Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/unet_2d_blocks.py
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
|
||||
# from .attention import Transformer3DModel
|
||||
from .resnet import Downsample3D, ResnetBlock3D, Upsample3D
|
||||
|
||||
|
||||
def get_down_block(
|
||||
down_block_type,
|
||||
num_layers,
|
||||
in_channels,
|
||||
out_channels,
|
||||
temb_channels,
|
||||
add_downsample,
|
||||
resnet_eps,
|
||||
resnet_act_fn,
|
||||
attn_num_head_channels,
|
||||
resnet_groups=None,
|
||||
cross_attention_dim=None,
|
||||
downsample_padding=None,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
resnet_time_scale_shift="default",
|
||||
use_attn_temp=False,
|
||||
):
|
||||
down_block_type = down_block_type[7:] if down_block_type.startswith("UNetRes") else down_block_type
|
||||
if down_block_type == "DownBlock3D":
|
||||
return DownBlock3D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif down_block_type == "CrossAttnDownBlock3D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for CrossAttnDownBlock3D")
|
||||
return CrossAttnDownBlock3D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attn_num_head_channels=attn_num_head_channels,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
use_attn_temp=use_attn_temp,
|
||||
)
|
||||
raise ValueError(f"{down_block_type} does not exist.")
|
||||
|
||||
|
||||
def get_up_block(
|
||||
up_block_type,
|
||||
num_layers,
|
||||
in_channels,
|
||||
out_channels,
|
||||
prev_output_channel,
|
||||
temb_channels,
|
||||
add_upsample,
|
||||
resnet_eps,
|
||||
resnet_act_fn,
|
||||
attn_num_head_channels,
|
||||
resnet_groups=None,
|
||||
cross_attention_dim=None,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
resnet_time_scale_shift="default",
|
||||
use_attn_temp=False,
|
||||
):
|
||||
up_block_type = up_block_type[7:] if up_block_type.startswith("UNetRes") else up_block_type
|
||||
if up_block_type == "UpBlock3D":
|
||||
return UpBlock3D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif up_block_type == "CrossAttnUpBlock3D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for CrossAttnUpBlock3D")
|
||||
return CrossAttnUpBlock3D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attn_num_head_channels=attn_num_head_channels,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
use_attn_temp=use_attn_temp,
|
||||
)
|
||||
raise ValueError(f"{up_block_type} does not exist.")
|
||||
|
||||
|
||||
class UNetMidBlock3DCrossAttn(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
attn_num_head_channels=1,
|
||||
output_scale_factor=1.0,
|
||||
cross_attention_dim=1280,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
upcast_attention=False,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.has_cross_attention = True
|
||||
self.attn_num_head_channels = attn_num_head_channels
|
||||
resnet_groups = resnet_groups if resnet_groups is not None else min(in_channels // 4, 32)
|
||||
|
||||
# there is always at least one resnet
|
||||
resnets = [
|
||||
ResnetBlock3D(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
]
|
||||
attentions = []
|
||||
|
||||
for _ in range(num_layers):
|
||||
if dual_cross_attention:
|
||||
raise NotImplementedError
|
||||
attentions.append(
|
||||
Transformer3DModel(
|
||||
attn_num_head_channels,
|
||||
in_channels // attn_num_head_channels,
|
||||
in_channels=in_channels,
|
||||
num_layers=1,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
norm_num_groups=resnet_groups,
|
||||
use_linear_projection=use_linear_projection,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
)
|
||||
resnets.append(
|
||||
ResnetBlock3D(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
|
||||
self.attentions = nn.ModuleList(attentions)
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
def forward(self, hidden_states, temb=None, encoder_hidden_states=None, attention_mask=None):
|
||||
hidden_states = self.resnets[0](hidden_states, temb)
|
||||
for attn, resnet in zip(self.attentions, self.resnets[1:]):
|
||||
hidden_states = attn(hidden_states, encoder_hidden_states=encoder_hidden_states).sample
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class CrossAttnDownBlock3D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
attn_num_head_channels=1,
|
||||
cross_attention_dim=1280,
|
||||
output_scale_factor=1.0,
|
||||
downsample_padding=1,
|
||||
add_downsample=True,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
use_attn_temp=False,
|
||||
):
|
||||
super().__init__()
|
||||
resnets = []
|
||||
attentions = []
|
||||
|
||||
self.has_cross_attention = True
|
||||
self.attn_num_head_channels = attn_num_head_channels
|
||||
|
||||
for i in range(num_layers):
|
||||
in_channels = in_channels if i == 0 else out_channels
|
||||
resnets.append(
|
||||
ResnetBlock3D(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
if dual_cross_attention:
|
||||
raise NotImplementedError
|
||||
attentions.append(
|
||||
Transformer3DModel(
|
||||
attn_num_head_channels,
|
||||
out_channels // attn_num_head_channels,
|
||||
in_channels=out_channels,
|
||||
num_layers=1,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
norm_num_groups=resnet_groups,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
use_attn_temp=use_attn_temp,
|
||||
)
|
||||
)
|
||||
self.attentions = nn.ModuleList(attentions)
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
if add_downsample:
|
||||
self.downsamplers = nn.ModuleList(
|
||||
[
|
||||
Downsample3D(
|
||||
out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op"
|
||||
)
|
||||
]
|
||||
)
|
||||
else:
|
||||
self.downsamplers = None
|
||||
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(self, hidden_states, temb=None, encoder_hidden_states=None, attention_mask=None):
|
||||
output_states = ()
|
||||
|
||||
for resnet, attn in zip(self.resnets, self.attentions):
|
||||
if self.training and self.gradient_checkpointing:
|
||||
|
||||
def create_custom_forward(module, return_dict=None):
|
||||
def custom_forward(*inputs):
|
||||
if return_dict is not None:
|
||||
return module(*inputs, return_dict=return_dict)
|
||||
else:
|
||||
return module(*inputs)
|
||||
|
||||
return custom_forward
|
||||
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(create_custom_forward(resnet), hidden_states, temb)
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(attn, return_dict=False),
|
||||
hidden_states,
|
||||
encoder_hidden_states,
|
||||
)[0]
|
||||
else:
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
hidden_states = attn(hidden_states, encoder_hidden_states=encoder_hidden_states).sample
|
||||
|
||||
output_states += (hidden_states,)
|
||||
|
||||
if self.downsamplers is not None:
|
||||
for downsampler in self.downsamplers:
|
||||
hidden_states = downsampler(hidden_states)
|
||||
|
||||
output_states += (hidden_states,)
|
||||
|
||||
return hidden_states, output_states
|
||||
|
||||
|
||||
class DownBlock3D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
output_scale_factor=1.0,
|
||||
add_downsample=True,
|
||||
downsample_padding=1,
|
||||
):
|
||||
super().__init__()
|
||||
resnets = []
|
||||
|
||||
for i in range(num_layers):
|
||||
in_channels = in_channels if i == 0 else out_channels
|
||||
resnets.append(
|
||||
ResnetBlock3D(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
if add_downsample:
|
||||
self.downsamplers = nn.ModuleList(
|
||||
[
|
||||
Downsample3D(
|
||||
out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op"
|
||||
)
|
||||
]
|
||||
)
|
||||
else:
|
||||
self.downsamplers = None
|
||||
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(self, hidden_states, temb=None):
|
||||
output_states = ()
|
||||
|
||||
for resnet in self.resnets:
|
||||
if self.training and self.gradient_checkpointing:
|
||||
|
||||
def create_custom_forward(module):
|
||||
def custom_forward(*inputs):
|
||||
return module(*inputs)
|
||||
|
||||
return custom_forward
|
||||
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(create_custom_forward(resnet), hidden_states, temb)
|
||||
else:
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
|
||||
output_states += (hidden_states,)
|
||||
|
||||
if self.downsamplers is not None:
|
||||
for downsampler in self.downsamplers:
|
||||
hidden_states = downsampler(hidden_states)
|
||||
|
||||
output_states += (hidden_states,)
|
||||
|
||||
return hidden_states, output_states
|
||||
|
||||
|
||||
class CrossAttnUpBlock3D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
prev_output_channel: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
attn_num_head_channels=1,
|
||||
cross_attention_dim=1280,
|
||||
output_scale_factor=1.0,
|
||||
add_upsample=True,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
use_attn_temp=False,
|
||||
):
|
||||
super().__init__()
|
||||
resnets = []
|
||||
attentions = []
|
||||
|
||||
self.has_cross_attention = True
|
||||
self.attn_num_head_channels = attn_num_head_channels
|
||||
|
||||
for i in range(num_layers):
|
||||
res_skip_channels = in_channels if (i == num_layers - 1) else out_channels
|
||||
resnet_in_channels = prev_output_channel if i == 0 else out_channels
|
||||
|
||||
resnets.append(
|
||||
ResnetBlock3D(
|
||||
in_channels=resnet_in_channels + res_skip_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
if dual_cross_attention:
|
||||
raise NotImplementedError
|
||||
attentions.append(
|
||||
Transformer3DModel(
|
||||
attn_num_head_channels,
|
||||
out_channels // attn_num_head_channels,
|
||||
in_channels=out_channels,
|
||||
num_layers=1,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
norm_num_groups=resnet_groups,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
use_attn_temp=use_attn_temp,
|
||||
)
|
||||
)
|
||||
|
||||
self.attentions = nn.ModuleList(attentions)
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
if add_upsample:
|
||||
self.upsamplers = nn.ModuleList([Upsample3D(out_channels, use_conv=True, out_channels=out_channels)])
|
||||
else:
|
||||
self.upsamplers = None
|
||||
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states,
|
||||
res_hidden_states_tuple,
|
||||
temb=None,
|
||||
encoder_hidden_states=None,
|
||||
upsample_size=None,
|
||||
attention_mask=None,
|
||||
):
|
||||
for resnet, attn in zip(self.resnets, self.attentions):
|
||||
# pop res hidden states
|
||||
res_hidden_states = res_hidden_states_tuple[-1]
|
||||
res_hidden_states_tuple = res_hidden_states_tuple[:-1]
|
||||
hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1)
|
||||
|
||||
if self.training and self.gradient_checkpointing:
|
||||
|
||||
def create_custom_forward(module, return_dict=None):
|
||||
def custom_forward(*inputs):
|
||||
if return_dict is not None:
|
||||
return module(*inputs, return_dict=return_dict)
|
||||
else:
|
||||
return module(*inputs)
|
||||
|
||||
return custom_forward
|
||||
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(create_custom_forward(resnet), hidden_states, temb)
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(attn, return_dict=False),
|
||||
hidden_states,
|
||||
encoder_hidden_states,
|
||||
)[0]
|
||||
else:
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
hidden_states = attn(hidden_states, encoder_hidden_states=encoder_hidden_states).sample
|
||||
|
||||
if self.upsamplers is not None:
|
||||
for upsampler in self.upsamplers:
|
||||
hidden_states = upsampler(hidden_states, upsample_size)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class UpBlock3D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
prev_output_channel: int,
|
||||
out_channels: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
output_scale_factor=1.0,
|
||||
add_upsample=True,
|
||||
):
|
||||
super().__init__()
|
||||
resnets = []
|
||||
|
||||
for i in range(num_layers):
|
||||
res_skip_channels = in_channels if (i == num_layers - 1) else out_channels
|
||||
resnet_in_channels = prev_output_channel if i == 0 else out_channels
|
||||
|
||||
resnets.append(
|
||||
ResnetBlock3D(
|
||||
in_channels=resnet_in_channels + res_skip_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
if add_upsample:
|
||||
self.upsamplers = nn.ModuleList([Upsample3D(out_channels, use_conv=True, out_channels=out_channels)])
|
||||
else:
|
||||
self.upsamplers = None
|
||||
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(self, hidden_states, res_hidden_states_tuple, temb=None, upsample_size=None):
|
||||
for resnet in self.resnets:
|
||||
# pop res hidden states
|
||||
res_hidden_states = res_hidden_states_tuple[-1]
|
||||
res_hidden_states_tuple = res_hidden_states_tuple[:-1]
|
||||
hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1)
|
||||
|
||||
if self.training and self.gradient_checkpointing:
|
||||
|
||||
def create_custom_forward(module):
|
||||
def custom_forward(*inputs):
|
||||
return module(*inputs)
|
||||
|
||||
return custom_forward
|
||||
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(create_custom_forward(resnet), hidden_states, temb)
|
||||
else:
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
|
||||
if self.upsamplers is not None:
|
||||
for upsampler in self.upsamplers:
|
||||
hidden_states = upsampler(hidden_states, upsample_size)
|
||||
|
||||
return hidden_states
|
||||
@@ -0,0 +1,926 @@
|
||||
# Copyright 2023 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.
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
|
||||
from diffusers.utils import is_torch_version, logging
|
||||
# from diffusers.models.attention import AdaGroupNorm
|
||||
from diffusers.models.attention_processor import Attention, AttnAddedKVProcessor, AttnAddedKVProcessor2_0
|
||||
from diffusers.models.transformers.dual_transformer_2d import DualTransformer2DModel
|
||||
from diffusers.models.resnet import Downsample2D, FirDownsample2D, FirUpsample2D, KDownsample2D, KUpsample2D, ResnetBlock2D, Upsample2D
|
||||
from .transformer_mv2d import TransformerMV2DModel
|
||||
|
||||
from diffusers.models.unets.unet_2d_blocks import DownBlock2D, ResnetDownsampleBlock2D, AttnDownBlock2D, CrossAttnDownBlock2D, SimpleCrossAttnDownBlock2D, SkipDownBlock2D, AttnSkipDownBlock2D, DownEncoderBlock2D, AttnDownEncoderBlock2D, KDownBlock2D, KCrossAttnDownBlock2D
|
||||
from diffusers.models.unets.unet_2d_blocks import UpBlock2D, ResnetUpsampleBlock2D, CrossAttnUpBlock2D, SimpleCrossAttnUpBlock2D, AttnUpBlock2D, SkipUpBlock2D, AttnSkipUpBlock2D, UpDecoderBlock2D, AttnUpDecoderBlock2D, KUpBlock2D, KCrossAttnUpBlock2D
|
||||
|
||||
|
||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def get_down_block(
|
||||
down_block_type,
|
||||
num_layers,
|
||||
in_channels,
|
||||
out_channels,
|
||||
temb_channels,
|
||||
add_downsample,
|
||||
resnet_eps,
|
||||
resnet_act_fn,
|
||||
transformer_layers_per_block=1,
|
||||
num_attention_heads=None,
|
||||
resnet_groups=None,
|
||||
cross_attention_dim=None,
|
||||
downsample_padding=None,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
resnet_time_scale_shift="default",
|
||||
resnet_skip_time_act=False,
|
||||
resnet_out_scale_factor=1.0,
|
||||
cross_attention_norm=None,
|
||||
attention_head_dim=None,
|
||||
downsample_type=None,
|
||||
num_views=1,
|
||||
joint_attention: bool = False,
|
||||
joint_attention_twice: bool = False,
|
||||
multiview_attention: bool = True,
|
||||
cross_domain_attention: bool=False
|
||||
):
|
||||
# If attn head dim is not defined, we default it to the number of heads
|
||||
if attention_head_dim is None:
|
||||
logger.warn(
|
||||
f"It is recommended to provide `attention_head_dim` when calling `get_down_block`. Defaulting `attention_head_dim` to {num_attention_heads}."
|
||||
)
|
||||
attention_head_dim = num_attention_heads
|
||||
|
||||
down_block_type = down_block_type[7:] if down_block_type.startswith("UNetRes") else down_block_type
|
||||
if down_block_type == "DownBlock2D":
|
||||
return DownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif down_block_type == "ResnetDownsampleBlock2D":
|
||||
return ResnetDownsampleBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
skip_time_act=resnet_skip_time_act,
|
||||
output_scale_factor=resnet_out_scale_factor,
|
||||
)
|
||||
elif down_block_type == "AttnDownBlock2D":
|
||||
if add_downsample is False:
|
||||
downsample_type = None
|
||||
else:
|
||||
downsample_type = downsample_type or "conv" # default to 'conv'
|
||||
return AttnDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
downsample_type=downsample_type,
|
||||
)
|
||||
elif down_block_type == "CrossAttnDownBlock2D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for CrossAttnDownBlock2D")
|
||||
return CrossAttnDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
transformer_layers_per_block=transformer_layers_per_block,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
num_attention_heads=num_attention_heads,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
# custom MV2D attention block
|
||||
elif down_block_type == "CrossAttnDownBlockMV2D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for CrossAttnDownBlockMV2D")
|
||||
return CrossAttnDownBlockMV2D(
|
||||
num_layers=num_layers,
|
||||
transformer_layers_per_block=transformer_layers_per_block,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
num_attention_heads=num_attention_heads,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
num_views=num_views,
|
||||
joint_attention=joint_attention,
|
||||
joint_attention_twice=joint_attention_twice,
|
||||
multiview_attention=multiview_attention,
|
||||
cross_domain_attention=cross_domain_attention
|
||||
)
|
||||
elif down_block_type == "SimpleCrossAttnDownBlock2D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for SimpleCrossAttnDownBlock2D")
|
||||
return SimpleCrossAttnDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
skip_time_act=resnet_skip_time_act,
|
||||
output_scale_factor=resnet_out_scale_factor,
|
||||
only_cross_attention=only_cross_attention,
|
||||
cross_attention_norm=cross_attention_norm,
|
||||
)
|
||||
elif down_block_type == "SkipDownBlock2D":
|
||||
return SkipDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
downsample_padding=downsample_padding,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif down_block_type == "AttnSkipDownBlock2D":
|
||||
return AttnSkipDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif down_block_type == "DownEncoderBlock2D":
|
||||
return DownEncoderBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif down_block_type == "AttnDownEncoderBlock2D":
|
||||
return AttnDownEncoderBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif down_block_type == "KDownBlock2D":
|
||||
return KDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
)
|
||||
elif down_block_type == "KCrossAttnDownBlock2D":
|
||||
return KCrossAttnDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attention_head_dim=attention_head_dim,
|
||||
add_self_attention=True if not add_downsample else False,
|
||||
)
|
||||
raise ValueError(f"{down_block_type} does not exist.")
|
||||
|
||||
|
||||
def get_up_block(
|
||||
up_block_type,
|
||||
num_layers,
|
||||
in_channels,
|
||||
out_channels,
|
||||
prev_output_channel,
|
||||
temb_channels,
|
||||
add_upsample,
|
||||
resnet_eps,
|
||||
resnet_act_fn,
|
||||
transformer_layers_per_block=1,
|
||||
num_attention_heads=None,
|
||||
resnet_groups=None,
|
||||
cross_attention_dim=None,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
resnet_time_scale_shift="default",
|
||||
resnet_skip_time_act=False,
|
||||
resnet_out_scale_factor=1.0,
|
||||
cross_attention_norm=None,
|
||||
attention_head_dim=None,
|
||||
upsample_type=None,
|
||||
num_views=1,
|
||||
joint_attention: bool = False,
|
||||
joint_attention_twice: bool = False,
|
||||
multiview_attention: bool = True,
|
||||
cross_domain_attention: bool=False
|
||||
):
|
||||
# If attn head dim is not defined, we default it to the number of heads
|
||||
if attention_head_dim is None:
|
||||
logger.warn(
|
||||
f"It is recommended to provide `attention_head_dim` when calling `get_up_block`. Defaulting `attention_head_dim` to {num_attention_heads}."
|
||||
)
|
||||
attention_head_dim = num_attention_heads
|
||||
|
||||
up_block_type = up_block_type[7:] if up_block_type.startswith("UNetRes") else up_block_type
|
||||
if up_block_type == "UpBlock2D":
|
||||
return UpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif up_block_type == "ResnetUpsampleBlock2D":
|
||||
return ResnetUpsampleBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
skip_time_act=resnet_skip_time_act,
|
||||
output_scale_factor=resnet_out_scale_factor,
|
||||
)
|
||||
elif up_block_type == "CrossAttnUpBlock2D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for CrossAttnUpBlock2D")
|
||||
return CrossAttnUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
transformer_layers_per_block=transformer_layers_per_block,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
num_attention_heads=num_attention_heads,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
# custom MV2D attention block
|
||||
elif up_block_type == "CrossAttnUpBlockMV2D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for CrossAttnUpBlockMV2D")
|
||||
return CrossAttnUpBlockMV2D(
|
||||
num_layers=num_layers,
|
||||
transformer_layers_per_block=transformer_layers_per_block,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
num_attention_heads=num_attention_heads,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
num_views=num_views,
|
||||
joint_attention=joint_attention,
|
||||
joint_attention_twice=joint_attention_twice,
|
||||
multiview_attention=multiview_attention,
|
||||
cross_domain_attention=cross_domain_attention
|
||||
)
|
||||
elif up_block_type == "SimpleCrossAttnUpBlock2D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for SimpleCrossAttnUpBlock2D")
|
||||
return SimpleCrossAttnUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
skip_time_act=resnet_skip_time_act,
|
||||
output_scale_factor=resnet_out_scale_factor,
|
||||
only_cross_attention=only_cross_attention,
|
||||
cross_attention_norm=cross_attention_norm,
|
||||
)
|
||||
elif up_block_type == "AttnUpBlock2D":
|
||||
if add_upsample is False:
|
||||
upsample_type = None
|
||||
else:
|
||||
upsample_type = upsample_type or "conv" # default to 'conv'
|
||||
|
||||
return AttnUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
upsample_type=upsample_type,
|
||||
)
|
||||
elif up_block_type == "SkipUpBlock2D":
|
||||
return SkipUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif up_block_type == "AttnSkipUpBlock2D":
|
||||
return AttnSkipUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif up_block_type == "UpDecoderBlock2D":
|
||||
return UpDecoderBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
temb_channels=temb_channels,
|
||||
)
|
||||
elif up_block_type == "AttnUpDecoderBlock2D":
|
||||
return AttnUpDecoderBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
temb_channels=temb_channels,
|
||||
)
|
||||
elif up_block_type == "KUpBlock2D":
|
||||
return KUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
)
|
||||
elif up_block_type == "KCrossAttnUpBlock2D":
|
||||
return KCrossAttnUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attention_head_dim=attention_head_dim,
|
||||
)
|
||||
|
||||
raise ValueError(f"{up_block_type} does not exist.")
|
||||
|
||||
|
||||
class UNetMidBlockMV2DCrossAttn(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
transformer_layers_per_block: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
num_attention_heads=1,
|
||||
output_scale_factor=1.0,
|
||||
cross_attention_dim=1280,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
upcast_attention=False,
|
||||
num_views: int = 1,
|
||||
joint_attention: bool = False,
|
||||
joint_attention_twice: bool = False,
|
||||
multiview_attention: bool = True,
|
||||
cross_domain_attention: bool=False
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.has_cross_attention = True
|
||||
self.num_attention_heads = num_attention_heads
|
||||
resnet_groups = resnet_groups if resnet_groups is not None else min(in_channels // 4, 32)
|
||||
|
||||
# there is always at least one resnet
|
||||
resnets = [
|
||||
ResnetBlock2D(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
]
|
||||
attentions = []
|
||||
|
||||
for _ in range(num_layers):
|
||||
if not dual_cross_attention:
|
||||
attentions.append(
|
||||
TransformerMV2DModel(
|
||||
num_attention_heads,
|
||||
in_channels // num_attention_heads,
|
||||
in_channels=in_channels,
|
||||
num_layers=transformer_layers_per_block,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
norm_num_groups=resnet_groups,
|
||||
use_linear_projection=use_linear_projection,
|
||||
upcast_attention=upcast_attention,
|
||||
num_views=num_views,
|
||||
joint_attention=joint_attention,
|
||||
joint_attention_twice=joint_attention_twice,
|
||||
multiview_attention=multiview_attention,
|
||||
cross_domain_attention=cross_domain_attention
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
resnets.append(
|
||||
ResnetBlock2D(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
|
||||
self.attentions = nn.ModuleList(attentions)
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.FloatTensor,
|
||||
temb: Optional[torch.FloatTensor] = None,
|
||||
encoder_hidden_states: Optional[torch.FloatTensor] = None,
|
||||
attention_mask: Optional[torch.FloatTensor] = None,
|
||||
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
|
||||
encoder_attention_mask: Optional[torch.FloatTensor] = None,
|
||||
) -> torch.FloatTensor:
|
||||
hidden_states = self.resnets[0](hidden_states, temb)
|
||||
for attn, resnet in zip(self.attentions, self.resnets[1:]):
|
||||
hidden_states = attn(
|
||||
hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
attention_mask=attention_mask,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
return_dict=False,
|
||||
)[0]
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class CrossAttnUpBlockMV2D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
prev_output_channel: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
transformer_layers_per_block: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
num_attention_heads=1,
|
||||
cross_attention_dim=1280,
|
||||
output_scale_factor=1.0,
|
||||
add_upsample=True,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
num_views: int = 1,
|
||||
joint_attention: bool = False,
|
||||
joint_attention_twice: bool = False,
|
||||
multiview_attention: bool = True,
|
||||
cross_domain_attention: bool=False
|
||||
):
|
||||
super().__init__()
|
||||
resnets = []
|
||||
attentions = []
|
||||
|
||||
self.has_cross_attention = True
|
||||
self.num_attention_heads = num_attention_heads
|
||||
|
||||
for i in range(num_layers):
|
||||
res_skip_channels = in_channels if (i == num_layers - 1) else out_channels
|
||||
resnet_in_channels = prev_output_channel if i == 0 else out_channels
|
||||
|
||||
resnets.append(
|
||||
ResnetBlock2D(
|
||||
in_channels=resnet_in_channels + res_skip_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
if not dual_cross_attention:
|
||||
attentions.append(
|
||||
TransformerMV2DModel(
|
||||
num_attention_heads,
|
||||
out_channels // num_attention_heads,
|
||||
in_channels=out_channels,
|
||||
num_layers=transformer_layers_per_block,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
norm_num_groups=resnet_groups,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
num_views=num_views,
|
||||
joint_attention=joint_attention,
|
||||
joint_attention_twice=joint_attention_twice,
|
||||
multiview_attention=multiview_attention,
|
||||
cross_domain_attention=cross_domain_attention
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
self.attentions = nn.ModuleList(attentions)
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
if add_upsample:
|
||||
self.upsamplers = nn.ModuleList([Upsample2D(out_channels, use_conv=True, out_channels=out_channels)])
|
||||
else:
|
||||
self.upsamplers = None
|
||||
if num_views == 4:
|
||||
self.gradient_checkpointing = False
|
||||
else:
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.FloatTensor,
|
||||
res_hidden_states_tuple: Tuple[torch.FloatTensor, ...],
|
||||
temb: Optional[torch.FloatTensor] = None,
|
||||
encoder_hidden_states: Optional[torch.FloatTensor] = None,
|
||||
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
|
||||
upsample_size: Optional[int] = None,
|
||||
attention_mask: Optional[torch.FloatTensor] = None,
|
||||
encoder_attention_mask: Optional[torch.FloatTensor] = None,
|
||||
):
|
||||
for resnet, attn in zip(self.resnets, self.attentions):
|
||||
# pop res hidden states
|
||||
res_hidden_states = res_hidden_states_tuple[-1]
|
||||
res_hidden_states_tuple = res_hidden_states_tuple[:-1]
|
||||
hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1)
|
||||
|
||||
if self.training and self.gradient_checkpointing:
|
||||
|
||||
def create_custom_forward(module, return_dict=None):
|
||||
def custom_forward(*inputs):
|
||||
if return_dict is not None:
|
||||
return module(*inputs, return_dict=return_dict)
|
||||
else:
|
||||
return module(*inputs)
|
||||
|
||||
return custom_forward
|
||||
|
||||
ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {}
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(resnet),
|
||||
hidden_states,
|
||||
temb,
|
||||
**ckpt_kwargs,
|
||||
)
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(attn, return_dict=False),
|
||||
hidden_states,
|
||||
encoder_hidden_states,
|
||||
None, # timestep
|
||||
None, # class_labels
|
||||
cross_attention_kwargs,
|
||||
attention_mask,
|
||||
encoder_attention_mask,
|
||||
**ckpt_kwargs,
|
||||
)[0]
|
||||
# hidden_states = attn(
|
||||
# hidden_states,
|
||||
# encoder_hidden_states=encoder_hidden_states,
|
||||
# cross_attention_kwargs=cross_attention_kwargs,
|
||||
# attention_mask=attention_mask,
|
||||
# encoder_attention_mask=encoder_attention_mask,
|
||||
# return_dict=False,
|
||||
# )[0]
|
||||
else:
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
hidden_states = attn(
|
||||
hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
attention_mask=attention_mask,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
return_dict=False,
|
||||
)[0]
|
||||
|
||||
if self.upsamplers is not None:
|
||||
for upsampler in self.upsamplers:
|
||||
hidden_states = upsampler(hidden_states, upsample_size)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class CrossAttnDownBlockMV2D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
transformer_layers_per_block: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
num_attention_heads=1,
|
||||
cross_attention_dim=1280,
|
||||
output_scale_factor=1.0,
|
||||
downsample_padding=1,
|
||||
add_downsample=True,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
num_views: int = 1,
|
||||
joint_attention: bool = False,
|
||||
joint_attention_twice: bool = False,
|
||||
multiview_attention: bool = True,
|
||||
cross_domain_attention: bool=False
|
||||
):
|
||||
super().__init__()
|
||||
resnets = []
|
||||
attentions = []
|
||||
|
||||
self.has_cross_attention = True
|
||||
self.num_attention_heads = num_attention_heads
|
||||
|
||||
for i in range(num_layers):
|
||||
in_channels = in_channels if i == 0 else out_channels
|
||||
resnets.append(
|
||||
ResnetBlock2D(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
if not dual_cross_attention:
|
||||
attentions.append(
|
||||
TransformerMV2DModel(
|
||||
num_attention_heads,
|
||||
out_channels // num_attention_heads,
|
||||
in_channels=out_channels,
|
||||
num_layers=transformer_layers_per_block,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
norm_num_groups=resnet_groups,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
num_views=num_views,
|
||||
joint_attention=joint_attention,
|
||||
joint_attention_twice=joint_attention_twice,
|
||||
multiview_attention=multiview_attention,
|
||||
cross_domain_attention=cross_domain_attention
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
self.attentions = nn.ModuleList(attentions)
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
if add_downsample:
|
||||
self.downsamplers = nn.ModuleList(
|
||||
[
|
||||
Downsample2D(
|
||||
out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op"
|
||||
)
|
||||
]
|
||||
)
|
||||
else:
|
||||
self.downsamplers = None
|
||||
if num_views == 4:
|
||||
self.gradient_checkpointing = False
|
||||
else:
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.FloatTensor,
|
||||
temb: Optional[torch.FloatTensor] = None,
|
||||
encoder_hidden_states: Optional[torch.FloatTensor] = None,
|
||||
attention_mask: Optional[torch.FloatTensor] = None,
|
||||
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
|
||||
encoder_attention_mask: Optional[torch.FloatTensor] = None,
|
||||
additional_residuals=None,
|
||||
):
|
||||
output_states = ()
|
||||
|
||||
blocks = list(zip(self.resnets, self.attentions))
|
||||
|
||||
for i, (resnet, attn) in enumerate(blocks):
|
||||
if self.training and self.gradient_checkpointing:
|
||||
|
||||
def create_custom_forward(module, return_dict=None):
|
||||
def custom_forward(*inputs):
|
||||
if return_dict is not None:
|
||||
return module(*inputs, return_dict=return_dict)
|
||||
else:
|
||||
return module(*inputs)
|
||||
|
||||
return custom_forward
|
||||
|
||||
ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {}
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(resnet),
|
||||
hidden_states,
|
||||
temb,
|
||||
**ckpt_kwargs,
|
||||
)
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(attn, return_dict=False),
|
||||
hidden_states,
|
||||
encoder_hidden_states,
|
||||
None, # timestep
|
||||
None, # class_labels
|
||||
cross_attention_kwargs,
|
||||
attention_mask,
|
||||
encoder_attention_mask,
|
||||
**ckpt_kwargs,
|
||||
)[0]
|
||||
else:
|
||||
# import ipdb
|
||||
# ipdb.set_trace()
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
hidden_states = attn(
|
||||
hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
attention_mask=attention_mask,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
return_dict=False,
|
||||
)[0]
|
||||
|
||||
# apply additional residuals to the output of the last pair of resnet and attention blocks
|
||||
if i == len(blocks) - 1 and additional_residuals is not None:
|
||||
hidden_states = hidden_states + additional_residuals
|
||||
|
||||
output_states = output_states + (hidden_states,)
|
||||
|
||||
if self.downsamplers is not None:
|
||||
for downsampler in self.downsamplers:
|
||||
hidden_states = downsampler(hidden_states)
|
||||
|
||||
output_states = output_states + (hidden_states,)
|
||||
|
||||
return hidden_states, output_states
|
||||
|
||||
@@ -0,0 +1,589 @@
|
||||
# Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion.py
|
||||
|
||||
import tqdm
|
||||
|
||||
import inspect
|
||||
from typing import Callable, List, Optional, Union
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from diffusers.utils import is_accelerate_available
|
||||
from packaging import version
|
||||
from transformers import CLIPTextModel, CLIPTokenizer
|
||||
import torchvision.transforms.functional as TF
|
||||
|
||||
from diffusers.configuration_utils import FrozenDict
|
||||
from diffusers.models import AutoencoderKL
|
||||
from diffusers import DiffusionPipeline
|
||||
from diffusers.schedulers import (
|
||||
DDIMScheduler,
|
||||
DPMSolverMultistepScheduler,
|
||||
EulerAncestralDiscreteScheduler,
|
||||
EulerDiscreteScheduler,
|
||||
LMSDiscreteScheduler,
|
||||
PNDMScheduler,
|
||||
)
|
||||
from diffusers.utils import deprecate, logging, BaseOutput
|
||||
|
||||
from einops import rearrange
|
||||
|
||||
from ..models.unet import UNet3DConditionModel
|
||||
from torchvision.transforms import InterpolationMode
|
||||
|
||||
import comfy.utils
|
||||
|
||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@dataclass
|
||||
class TuneAVideoPipelineOutput(BaseOutput):
|
||||
videos: Union[torch.Tensor, np.ndarray]
|
||||
|
||||
|
||||
class TuneAVideoPipeline(DiffusionPipeline):
|
||||
_optional_components = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vae: AutoencoderKL,
|
||||
text_encoder: CLIPTextModel,
|
||||
tokenizer: CLIPTokenizer,
|
||||
unet: UNet3DConditionModel,
|
||||
|
||||
scheduler: Union[
|
||||
DDIMScheduler,
|
||||
PNDMScheduler,
|
||||
LMSDiscreteScheduler,
|
||||
EulerDiscreteScheduler,
|
||||
EulerAncestralDiscreteScheduler,
|
||||
DPMSolverMultistepScheduler,
|
||||
],
|
||||
ref_unet = None,
|
||||
feature_extractor=None,
|
||||
image_encoder=None
|
||||
):
|
||||
super().__init__()
|
||||
self.ref_unet = ref_unet
|
||||
self.feature_extractor = feature_extractor
|
||||
self.image_encoder = image_encoder
|
||||
|
||||
if hasattr(scheduler.config, "steps_offset") and scheduler.config.steps_offset != 1:
|
||||
deprecation_message = (
|
||||
f"The configuration file of this scheduler: {scheduler} is outdated. `steps_offset`"
|
||||
f" should be set to 1 instead of {scheduler.config.steps_offset}. Please make sure "
|
||||
"to update the config accordingly as leaving `steps_offset` might led to incorrect results"
|
||||
" in future versions. If you have downloaded this checkpoint from the Hugging Face Hub,"
|
||||
" it would be very nice if you could open a Pull request for the `scheduler/scheduler_config.json`"
|
||||
" file"
|
||||
)
|
||||
deprecate("steps_offset!=1", "1.0.0", deprecation_message, standard_warn=False)
|
||||
new_config = dict(scheduler.config)
|
||||
new_config["steps_offset"] = 1
|
||||
scheduler._internal_dict = FrozenDict(new_config)
|
||||
|
||||
if hasattr(scheduler.config, "clip_sample") and scheduler.config.clip_sample is True:
|
||||
deprecation_message = (
|
||||
f"The configuration file of this scheduler: {scheduler} has not set the configuration `clip_sample`."
|
||||
" `clip_sample` should be set to False in the configuration file. Please make sure to update the"
|
||||
" config accordingly as not setting `clip_sample` in the config might lead to incorrect results in"
|
||||
" future versions. If you have downloaded this checkpoint from the Hugging Face Hub, it would be very"
|
||||
" nice if you could open a Pull request for the `scheduler/scheduler_config.json` file"
|
||||
)
|
||||
deprecate("clip_sample not set", "1.0.0", deprecation_message, standard_warn=False)
|
||||
new_config = dict(scheduler.config)
|
||||
new_config["clip_sample"] = False
|
||||
scheduler._internal_dict = FrozenDict(new_config)
|
||||
|
||||
is_unet_version_less_0_9_0 = hasattr(unet.config, "_diffusers_version") and version.parse(
|
||||
version.parse(unet.config._diffusers_version).base_version
|
||||
) < version.parse("0.9.0.dev0")
|
||||
is_unet_sample_size_less_64 = hasattr(unet.config, "sample_size") and unet.config.sample_size < 64
|
||||
if is_unet_version_less_0_9_0 and is_unet_sample_size_less_64:
|
||||
deprecation_message = (
|
||||
"The configuration file of the unet has set the default `sample_size` to smaller than"
|
||||
" 64 which seems highly unlikely. If your checkpoint is a fine-tuned version of any of the"
|
||||
" following: \n- CompVis/stable-diffusion-v1-4 \n- CompVis/stable-diffusion-v1-3 \n-"
|
||||
" CompVis/stable-diffusion-v1-2 \n- CompVis/stable-diffusion-v1-1 \n- runwayml/stable-diffusion-v1-5"
|
||||
" \n- runwayml/stable-diffusion-inpainting \n you should change 'sample_size' to 64 in the"
|
||||
" configuration file. Please make sure to update the config accordingly as leaving `sample_size=32`"
|
||||
" in the config might lead to incorrect results in future versions. If you have downloaded this"
|
||||
" checkpoint from the Hugging Face Hub, it would be very nice if you could open a Pull request for"
|
||||
" the `unet/config.json` file"
|
||||
)
|
||||
deprecate("sample_size<64", "1.0.0", deprecation_message, standard_warn=False)
|
||||
new_config = dict(unet.config)
|
||||
new_config["sample_size"] = 64
|
||||
unet._internal_dict = FrozenDict(new_config)
|
||||
|
||||
self.register_modules(
|
||||
vae=vae,
|
||||
text_encoder=text_encoder,
|
||||
tokenizer=tokenizer,
|
||||
unet=unet,
|
||||
scheduler=scheduler,
|
||||
)
|
||||
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
|
||||
|
||||
def enable_vae_slicing(self):
|
||||
self.vae.enable_slicing()
|
||||
|
||||
def disable_vae_slicing(self):
|
||||
self.vae.disable_slicing()
|
||||
|
||||
def enable_sequential_cpu_offload(self, gpu_id=0):
|
||||
if is_accelerate_available():
|
||||
from accelerate import cpu_offload
|
||||
else:
|
||||
raise ImportError("Please install accelerate via `pip install accelerate`")
|
||||
|
||||
device = torch.device(f"cuda:{gpu_id}")
|
||||
|
||||
for cpu_offloaded_model in [self.unet, self.text_encoder, self.vae]:
|
||||
if cpu_offloaded_model is not None:
|
||||
cpu_offload(cpu_offloaded_model, device)
|
||||
|
||||
|
||||
@property
|
||||
def _execution_device(self):
|
||||
if self.device != torch.device("meta") or not hasattr(self.unet, "_hf_hook"):
|
||||
return self.device
|
||||
for module in self.unet.modules():
|
||||
if (
|
||||
hasattr(module, "_hf_hook")
|
||||
and hasattr(module._hf_hook, "execution_device")
|
||||
and module._hf_hook.execution_device is not None
|
||||
):
|
||||
return torch.device(module._hf_hook.execution_device)
|
||||
return self.device
|
||||
|
||||
def _encode_image(self, image_pil, device, num_images_per_prompt, do_classifier_free_guidance, img_proj=None):
|
||||
dtype = next(self.image_encoder.parameters()).dtype
|
||||
|
||||
# image_pt = self.feature_extractor(images=image_pil, return_tensors="pt").pixel_values
|
||||
# image_pt = image_pt.to(device=device, dtype=dtype)
|
||||
# image_embeddings = self.image_encoder(image_pt).image_embeds
|
||||
# image_embeddings = image_embeddings.unsqueeze(1)
|
||||
|
||||
# # image encoding
|
||||
clip_image_mean = torch.as_tensor(self.feature_extractor.image_mean)[:,None,None].to(device, dtype=torch.float32)
|
||||
clip_image_std = torch.as_tensor(self.feature_extractor.image_std)[:,None,None].to(device, dtype=torch.float32)
|
||||
imgs_in_proc = TF.resize(image_pil, (self.feature_extractor.crop_size['height'], self.feature_extractor.crop_size['width']), interpolation=InterpolationMode.BICUBIC)
|
||||
# do the normalization in float32 to preserve precision
|
||||
imgs_in_proc = ((imgs_in_proc.float() - clip_image_mean) / clip_image_std).to(dtype)
|
||||
if img_proj is None:
|
||||
# (B*Nv, 1, 768)
|
||||
image_embeddings = self.image_encoder(imgs_in_proc).image_embeds.unsqueeze(1)
|
||||
# duplicate image embeddings for each generation per prompt, using mps friendly method
|
||||
# Note: repeat differently from official pipelines
|
||||
# B1B2B3B4 -> B1B2B3B4B1B2B3B4
|
||||
bs_embed, seq_len, _ = image_embeddings.shape
|
||||
image_embeddings = image_embeddings.repeat(num_images_per_prompt, 1, 1)
|
||||
if do_classifier_free_guidance:
|
||||
negative_prompt_embeds = torch.zeros_like(image_embeddings)
|
||||
|
||||
# For classifier free guidance, we need to do two forward passes.
|
||||
# Here we concatenate the unconditional and text embeddings into a single batch
|
||||
# to avoid doing two forward passes
|
||||
image_embeddings = torch.cat([negative_prompt_embeds, image_embeddings])
|
||||
else:
|
||||
if do_classifier_free_guidance:
|
||||
negative_image_proc = torch.zeros_like(imgs_in_proc)
|
||||
|
||||
# For classifier free guidance, we need to do two forward passes.
|
||||
# Here we concatenate the unconditional and text embeddings into a single batch
|
||||
# to avoid doing two forward passes
|
||||
imgs_in_proc = torch.cat([negative_image_proc, imgs_in_proc])
|
||||
|
||||
image_embeds = self.image_encoder(imgs_in_proc, output_hidden_states=True).hidden_states[-2]
|
||||
image_embeddings = img_proj(image_embeds)
|
||||
|
||||
# image_embeddings_unet = rearrange(image_embeddings_unet, 'B Nv d c -> (B Nv) d c')
|
||||
|
||||
# image_pt = torch.stack([TF.to_tensor(img) for img in image_pil], dim=0).to(device)
|
||||
# image_pil = image_pil * 2.0 - 1.0
|
||||
image_latents = self.vae.encode(image_pil* 2.0 - 1.0).latent_dist.mode() * self.vae.config.scaling_factor
|
||||
|
||||
# Note: repeat differently from official pipelines
|
||||
# B1B2B3B4 -> B1B2B3B4B1B2B3B4
|
||||
image_latents = image_latents.repeat(num_images_per_prompt, 1, 1, 1)
|
||||
|
||||
# if do_classifier_free_guidance:
|
||||
# image_latents = torch.cat([torch.zeros_like(image_latents), image_latents])
|
||||
|
||||
return image_embeddings, image_latents
|
||||
|
||||
def _encode_prompt(self, prompt, device, num_videos_per_prompt, do_classifier_free_guidance, negative_prompt):
|
||||
batch_size = len(prompt) if isinstance(prompt, list) else 1
|
||||
|
||||
text_inputs = self.tokenizer(
|
||||
prompt,
|
||||
padding="max_length",
|
||||
max_length=self.tokenizer.model_max_length,
|
||||
truncation=True,
|
||||
return_tensors="pt",
|
||||
)
|
||||
text_input_ids = text_inputs.input_ids
|
||||
untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids
|
||||
|
||||
if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
|
||||
removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1])
|
||||
logger.warning(
|
||||
"The following part of your input was truncated because CLIP can only handle sequences up to"
|
||||
f" {self.tokenizer.model_max_length} tokens: {removed_text}"
|
||||
)
|
||||
|
||||
if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask:
|
||||
attention_mask = text_inputs.attention_mask.to(device)
|
||||
else:
|
||||
attention_mask = None
|
||||
|
||||
text_embeddings = self.text_encoder(
|
||||
text_input_ids.to(device),
|
||||
attention_mask=attention_mask,
|
||||
)
|
||||
text_embeddings = text_embeddings[0]
|
||||
|
||||
# duplicate text embeddings for each generation per prompt, using mps friendly method
|
||||
bs_embed, seq_len, _ = text_embeddings.shape
|
||||
text_embeddings = text_embeddings.repeat(1, num_videos_per_prompt, 1)
|
||||
text_embeddings = text_embeddings.view(bs_embed * num_videos_per_prompt, seq_len, -1)
|
||||
|
||||
# get unconditional embeddings for classifier free guidance
|
||||
if do_classifier_free_guidance:
|
||||
uncond_tokens: List[str]
|
||||
if negative_prompt is None:
|
||||
uncond_tokens = [""] * batch_size
|
||||
elif type(prompt) is not type(negative_prompt):
|
||||
raise TypeError(
|
||||
f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !="
|
||||
f" {type(prompt)}."
|
||||
)
|
||||
elif isinstance(negative_prompt, str):
|
||||
uncond_tokens = [negative_prompt]
|
||||
elif batch_size != len(negative_prompt):
|
||||
raise ValueError(
|
||||
f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:"
|
||||
f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches"
|
||||
" the batch size of `prompt`."
|
||||
)
|
||||
else:
|
||||
uncond_tokens = negative_prompt
|
||||
|
||||
max_length = text_input_ids.shape[-1]
|
||||
uncond_input = self.tokenizer(
|
||||
uncond_tokens,
|
||||
padding="max_length",
|
||||
max_length=max_length,
|
||||
truncation=True,
|
||||
return_tensors="pt",
|
||||
)
|
||||
|
||||
if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask:
|
||||
attention_mask = uncond_input.attention_mask.to(device)
|
||||
else:
|
||||
attention_mask = None
|
||||
|
||||
uncond_embeddings = self.text_encoder(
|
||||
uncond_input.input_ids.to(device),
|
||||
attention_mask=attention_mask,
|
||||
)
|
||||
uncond_embeddings = uncond_embeddings[0]
|
||||
|
||||
# duplicate unconditional embeddings for each generation per prompt, using mps friendly method
|
||||
seq_len = uncond_embeddings.shape[1]
|
||||
uncond_embeddings = uncond_embeddings.repeat(1, num_videos_per_prompt, 1)
|
||||
uncond_embeddings = uncond_embeddings.view(batch_size * num_videos_per_prompt, seq_len, -1)
|
||||
|
||||
# For classifier free guidance, we need to do two forward passes.
|
||||
# Here we concatenate the unconditional and text embeddings into a single batch
|
||||
# to avoid doing two forward passes
|
||||
text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
|
||||
|
||||
return text_embeddings
|
||||
|
||||
def decode_latents(self, latents):
|
||||
video_length = latents.shape[2]
|
||||
latents = 1 / 0.18215 * latents
|
||||
latents = rearrange(latents, "b c f h w -> (b f) c h w")
|
||||
video = self.vae.decode(latents).sample
|
||||
video = rearrange(video, "(b f) c h w -> b c f h w", f=video_length)
|
||||
video = (video / 2 + 0.5).clamp(0, 1)
|
||||
# we always cast to float32 as this does not cause significant overhead and is compatible with bfloa16
|
||||
video = video.cpu().float().numpy()
|
||||
return video
|
||||
|
||||
def prepare_extra_step_kwargs(self, generator, eta):
|
||||
# prepare extra kwargs for the scheduler step, since not all schedulers have the same signature
|
||||
# eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.
|
||||
# eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502
|
||||
# and should be between [0, 1]
|
||||
|
||||
accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
|
||||
extra_step_kwargs = {}
|
||||
if accepts_eta:
|
||||
extra_step_kwargs["eta"] = eta
|
||||
|
||||
# check if the scheduler accepts generator
|
||||
accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
|
||||
if accepts_generator:
|
||||
extra_step_kwargs["generator"] = generator
|
||||
return extra_step_kwargs
|
||||
|
||||
def check_inputs(self, prompt, height, width, callback_steps):
|
||||
if not isinstance(prompt, str) and not isinstance(prompt, list):
|
||||
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
|
||||
|
||||
if height % 8 != 0 or width % 8 != 0:
|
||||
raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
|
||||
|
||||
if (callback_steps is None) or (
|
||||
callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)
|
||||
):
|
||||
raise ValueError(
|
||||
f"`callback_steps` has to be a positive integer but is {callback_steps} of type"
|
||||
f" {type(callback_steps)}."
|
||||
)
|
||||
|
||||
def prepare_latents(self, batch_size, num_channels_latents, video_length, height, width, dtype, device, generator, latents=None):
|
||||
shape = (batch_size, num_channels_latents, video_length, height // self.vae_scale_factor, width // self.vae_scale_factor)
|
||||
if isinstance(generator, list) and len(generator) != batch_size:
|
||||
raise ValueError(
|
||||
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
|
||||
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
|
||||
)
|
||||
|
||||
if latents is None:
|
||||
rand_device = "cpu" if device.type == "mps" else device
|
||||
|
||||
if isinstance(generator, list):
|
||||
shape = (1,) + shape[1:]
|
||||
latents = [
|
||||
torch.randn(shape, generator=generator[i], device=rand_device, dtype=dtype)
|
||||
for i in range(batch_size)
|
||||
]
|
||||
latents = torch.cat(latents, dim=0).to(device)
|
||||
else:
|
||||
latents = torch.randn(shape, generator=generator, device=rand_device, dtype=dtype).to(device)
|
||||
else:
|
||||
if latents.shape != shape:
|
||||
raise ValueError(f"Unexpected latents shape, got {latents.shape}, expected {shape}")
|
||||
latents = latents.to(device)
|
||||
|
||||
# scale the initial noise by the standard deviation required by the scheduler
|
||||
latents = latents * self.scheduler.init_noise_sigma
|
||||
return latents
|
||||
|
||||
@torch.no_grad()
|
||||
def __call__(
|
||||
self,
|
||||
prompt: Union[str, List[str]],
|
||||
image: Union[str, List[str]],
|
||||
video_length: Optional[int],
|
||||
height: Optional[int] = None,
|
||||
width: Optional[int] = None,
|
||||
num_inference_steps: int = 50,
|
||||
guidance_scale: float = 7.5,
|
||||
negative_prompt: Optional[Union[str, List[str]]] = None,
|
||||
num_videos_per_prompt: Optional[int] = 1,
|
||||
eta: float = 0.0,
|
||||
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
|
||||
latents: Optional[torch.FloatTensor] = None,
|
||||
output_type: Optional[str] = "tensor",
|
||||
return_dict: bool = True,
|
||||
callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,
|
||||
callback_steps: Optional[int] = 1,
|
||||
camera_matrixs = None,
|
||||
class_labels = None,
|
||||
prompt_ids = None,
|
||||
unet_condition_type = None,
|
||||
pose_guider = None,
|
||||
pose_image = None,
|
||||
img_proj=None,
|
||||
use_noise=True,
|
||||
use_shifted_noise=False,
|
||||
rescale = 0.7,
|
||||
**kwargs,
|
||||
):
|
||||
# Default height and width to unet
|
||||
height = height or self.unet.config.sample_size * self.vae_scale_factor
|
||||
width = width or self.unet.config.sample_size * self.vae_scale_factor
|
||||
|
||||
# Check inputs. Raise error if not correct
|
||||
self.check_inputs(prompt, height, width, callback_steps)
|
||||
if isinstance(image, list):
|
||||
batch_size = len(image)
|
||||
else:
|
||||
batch_size = image.shape[0]
|
||||
# assert batch_size >= video_length and batch_size % video_length == 0
|
||||
# Define call parameters
|
||||
batch_size = 1 if isinstance(prompt, str) else len(prompt)
|
||||
device = self._execution_device
|
||||
# here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
|
||||
# of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
|
||||
# corresponds to doing no classifier free guidance.
|
||||
do_classifier_free_guidance = guidance_scale > 1.0
|
||||
|
||||
# 3. Encode input image
|
||||
# if isinstance(image, list):
|
||||
# image_pil = image
|
||||
# elif isinstance(image, torch.Tensor):
|
||||
# image_pil = [TF.to_pil_image(image[i]) for i in range(image.shape[0])]
|
||||
# encode input reference image
|
||||
image_embeddings, image_latents = self._encode_image(image, device, num_videos_per_prompt, do_classifier_free_guidance, img_proj=img_proj) #torch.Size([64, 1, 768]) torch.Size([64, 4, 32, 32])
|
||||
image_latents = rearrange(image_latents, "(b f) c h w -> b c f h w", f=1) #torch.Size([64, 4, 1, 32, 32])
|
||||
|
||||
# Encode input prompt_id
|
||||
# encoder_hidden_states = self.text_encoder(prompt_ids)[0] #torch.Size([32, 77, 768])
|
||||
|
||||
# Encode input prompt
|
||||
text_embeddings = self._encode_prompt( #torch.Size([64, 77, 768])
|
||||
prompt, device, num_videos_per_prompt, do_classifier_free_guidance, negative_prompt
|
||||
)
|
||||
|
||||
# Prepare timesteps
|
||||
self.scheduler.set_timesteps(num_inference_steps, device=device)
|
||||
timesteps = self.scheduler.timesteps
|
||||
|
||||
# Prepare latent variables
|
||||
num_channels_latents = self.unet.in_channels
|
||||
latents = self.prepare_latents( #torch.Size([32, 4, 4, 32, 32])
|
||||
batch_size * num_videos_per_prompt,
|
||||
num_channels_latents,
|
||||
video_length,
|
||||
height,
|
||||
width,
|
||||
text_embeddings.dtype,
|
||||
device,
|
||||
generator,
|
||||
latents,
|
||||
)
|
||||
latents_dtype = latents.dtype
|
||||
# import ipdb
|
||||
# ipdb.set_trace()
|
||||
# Prepare extra step kwargs.
|
||||
extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
|
||||
# prepare camera_matrix
|
||||
if camera_matrixs is not None:
|
||||
camera_matrixs = torch.cat([camera_matrixs] * 2) if do_classifier_free_guidance else camera_matrixs #(64, 4, 12)
|
||||
# Denoising loop
|
||||
comfy_pbar = comfy.utils.ProgressBar(num_inference_steps)
|
||||
|
||||
num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order
|
||||
if pose_guider is not None:
|
||||
if len(pose_image.shape) == 5:
|
||||
pose_embeds = pose_guider(rearrange(pose_image, "b f c h w -> (b f) c h w"))
|
||||
pose_embeds = rearrange(pose_embeds, "(b f) c h w-> b c f h w ", f=video_length)
|
||||
else:
|
||||
pose_embeds = pose_guider(pose_image).unsqueeze(0)
|
||||
pose_embeds = torch.cat([pose_embeds]*2, dim=0)
|
||||
# import ipdb
|
||||
# ipdb.set_trace()
|
||||
with self.progress_bar(total=num_inference_steps) as progress_bar:
|
||||
for i, t in enumerate(tqdm.tqdm(timesteps)):
|
||||
# expand the latents if we are doing classifier free guidance
|
||||
latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
|
||||
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
|
||||
if pose_guider is not None:
|
||||
latent_model_input = latent_model_input + pose_embeds
|
||||
|
||||
noise_cond = torch.randn_like(image_latents)
|
||||
if use_noise:
|
||||
cond_latents = self.scheduler.add_noise(image_latents, noise_cond, t)
|
||||
else:
|
||||
cond_latents = image_latents
|
||||
cond_latent_model_input = torch.cat([cond_latents] * 2) if do_classifier_free_guidance else cond_latents
|
||||
cond_latent_model_input = self.scheduler.scale_model_input(cond_latent_model_input, t)
|
||||
|
||||
# predict the noise residual
|
||||
# ref text condition
|
||||
ref_dict = {}
|
||||
if self.ref_unet is not None:
|
||||
noise_pred_cond = self.ref_unet(
|
||||
cond_latent_model_input, #torch.Size([64, 4, 1, 32, 32])
|
||||
t, #torch.Size([32])
|
||||
encoder_hidden_states=text_embeddings.to(torch.float32), #torch.Size([64, 77, 768])
|
||||
cross_attention_kwargs=dict(mode="w", ref_dict=ref_dict)
|
||||
).sample.to(dtype=latents_dtype)
|
||||
|
||||
# if torch.isnan(noise_pred_cond).any():
|
||||
# ipdb.set_trace()
|
||||
# Predict the noise residual and compute loss
|
||||
# model_pred = unet(noisy_latents, timesteps, encoder_hidden_states, camera_matrixs).sample
|
||||
# unet
|
||||
#text condition for unet
|
||||
text_embeddings_unet = text_embeddings.unsqueeze(1).repeat(1,latents.shape[2],1,1)
|
||||
text_embeddings_unet = rearrange(text_embeddings_unet, 'B Nv d c -> (B Nv) d c')
|
||||
#image condition for unet
|
||||
image_embeddings_unet = image_embeddings.unsqueeze(1).repeat(1,latents.shape[2],1, 1)
|
||||
image_embeddings_unet = rearrange(image_embeddings_unet, 'B Nv d c -> (B Nv) d c')
|
||||
|
||||
if unet_condition_type == 'text':
|
||||
encoder_hidden_states_unet_cond = text_embeddings_unet
|
||||
elif unet_condition_type == 'image':
|
||||
encoder_hidden_states_unet_cond = image_embeddings_unet
|
||||
else:
|
||||
raise('need unet_condition_type')
|
||||
|
||||
if self.ref_unet is not None:
|
||||
noise_pred = self.unet(
|
||||
latent_model_input.to(torch.float32), #torch.Size([64, 4, 4, 32, 32])
|
||||
t,
|
||||
encoder_hidden_states=encoder_hidden_states_unet_cond.to(torch.float32),
|
||||
camera_matrixs=camera_matrixs.to(torch.float32), #torch.Size([64, 4, 12])
|
||||
cross_attention_kwargs=dict(mode="r", ref_dict=ref_dict, is_cfg_guidance=do_classifier_free_guidance)
|
||||
# cross_attention_kwargs=dict(mode="n", ref_dict=ref_dict, is_cfg_guidance=do_classifier_free_guidance)
|
||||
).sample.to(dtype=latents_dtype)
|
||||
else:
|
||||
noise_pred = self.unet(
|
||||
latent_model_input.to(torch.float32), #torch.Size([64, 4, 4, 32, 32])
|
||||
t,
|
||||
encoder_hidden_states=encoder_hidden_states_unet_cond.to(torch.float32),
|
||||
camera_matrixs=camera_matrixs.to(torch.float32), #torch.Size([64, 4, 12])
|
||||
# cross_attention_kwargs=dict(mode="r", ref_dict=ref_dict, is_cfg_guidance=do_classifier_free_guidance)
|
||||
cross_attention_kwargs=dict(mode="n", ref_dict=ref_dict, is_cfg_guidance=do_classifier_free_guidance)
|
||||
).sample.to(dtype=latents_dtype)
|
||||
# perform guidance
|
||||
if do_classifier_free_guidance:
|
||||
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
|
||||
if use_shifted_noise:
|
||||
# Apply regular classifier-free guidance.
|
||||
cfg = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
|
||||
# Calculate standard deviations.
|
||||
std_pos = noise_pred_text.std([1,2,3], keepdim=True)
|
||||
std_cfg = cfg.std([1,2,3], keepdim=True)
|
||||
# Apply guidance rescale with fused operations.
|
||||
factor = std_pos / std_cfg
|
||||
factor = rescale * factor + (1 - rescale)
|
||||
noise_pred = cfg * factor
|
||||
else:
|
||||
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
|
||||
# noise_pred_uncond_, noise_pred_text_ = noise_pred_cond.chunk(2)
|
||||
# noise_pred_cond = noise_pred_uncond_ + guidance_scale * (noise_pred_text_ - noise_pred_uncond_)
|
||||
|
||||
# compute the previous noisy sample x_t -> x_t-1
|
||||
noise_pred = rearrange(noise_pred, "(b f) c h w -> b c f h w", f=video_length)
|
||||
latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample
|
||||
# noise_pred_cond = rearrange(noise_pred_cond, "(b f) c h w -> b c f h w", f=1)
|
||||
# cond_latents = self.scheduler.step(noise_pred_cond, t, cond_latents, **extra_step_kwargs).prev_sample
|
||||
|
||||
# call the callback, if provided
|
||||
if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
|
||||
progress_bar.update()
|
||||
if callback is not None and i % callback_steps == 0:
|
||||
callback(i, t, latents)
|
||||
|
||||
comfy_pbar.update_absolute(i + 1)
|
||||
|
||||
# Post-processing
|
||||
video = self.decode_latents(latents)
|
||||
|
||||
# Convert to tensor
|
||||
if output_type == "tensor":
|
||||
video = torch.from_numpy(video)
|
||||
|
||||
if not return_dict:
|
||||
return video
|
||||
|
||||
return TuneAVideoPipelineOutput(videos=video)
|
||||
@@ -0,0 +1,128 @@
|
||||
import os
|
||||
import imageio
|
||||
import numpy as np
|
||||
from typing import Union
|
||||
import cv2
|
||||
import torch
|
||||
import torchvision
|
||||
|
||||
from tqdm import tqdm
|
||||
from einops import rearrange
|
||||
|
||||
def shifted_noise(betas, image_d=512, noise_d=256, shifted_noise=True):
|
||||
alphas = 1 - betas
|
||||
alphas_bar = torch.cumprod(alphas, dim=0)
|
||||
d = (image_d / noise_d) ** 2
|
||||
if shifted_noise:
|
||||
alphas_bar = alphas_bar / (d - (d - 1) * alphas_bar)
|
||||
alphas_bar_sqrt = torch.sqrt(alphas_bar)
|
||||
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 save_videos_grid(videos: torch.Tensor, path: str, rescale=False, n_rows=4, fps=8):
|
||||
videos = rearrange(videos, "b c t h w -> t b c h w")
|
||||
outputs = []
|
||||
for x in videos:
|
||||
x = torchvision.utils.make_grid(x, nrow=n_rows)
|
||||
x = x.transpose(0, 1).transpose(1, 2).squeeze(-1)
|
||||
if rescale:
|
||||
x = (x + 1.0) / 2.0 # -1,1 -> 0,1
|
||||
x = (x * 255).numpy().astype(np.uint8)
|
||||
outputs.append(x)
|
||||
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
imageio.mimsave(path, outputs, duration=1000/fps)
|
||||
|
||||
def save_imgs_grid(videos: torch.Tensor, path: str, rescale=False, n_rows=4, fps=8):
|
||||
videos = rearrange(videos, "b c t h w -> t b c h w")
|
||||
for i, x in enumerate(videos):
|
||||
x = torchvision.utils.make_grid(x, nrow=n_rows)
|
||||
x = x.transpose(0, 1).transpose(1, 2).squeeze(-1)
|
||||
if rescale:
|
||||
x = (x + 1.0) / 2.0 # -1,1 -> 0,1
|
||||
x = (x * 255).numpy().astype(np.uint8)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
cv2.imwrite(os.path.join(path, f'view_{i}.png'), x[:,:,::-1])
|
||||
|
||||
def imgs_grid(videos: torch.Tensor, rescale=False, n_rows=4, fps=8):
|
||||
videos = rearrange(videos, "b c t h w -> t b c h w")
|
||||
image_list = []
|
||||
for i, x in enumerate(videos):
|
||||
x = torchvision.utils.make_grid(x, nrow=n_rows)
|
||||
x = x.transpose(0, 1).transpose(1, 2).squeeze(-1)
|
||||
if rescale:
|
||||
x = (x + 1.0) / 2.0 # -1,1 -> 0,1
|
||||
x = (x * 255).numpy().astype(np.uint8)
|
||||
# image_list.append(x[:,:,::-1])
|
||||
image_list.append(x)
|
||||
return image_list
|
||||
|
||||
# DDIM Inversion
|
||||
@torch.no_grad()
|
||||
def init_prompt(prompt, pipeline):
|
||||
uncond_input = pipeline.tokenizer(
|
||||
[""], padding="max_length", max_length=pipeline.tokenizer.model_max_length,
|
||||
return_tensors="pt"
|
||||
)
|
||||
uncond_embeddings = pipeline.text_encoder(uncond_input.input_ids.to(pipeline.device))[0]
|
||||
text_input = pipeline.tokenizer(
|
||||
[prompt],
|
||||
padding="max_length",
|
||||
max_length=pipeline.tokenizer.model_max_length,
|
||||
truncation=True,
|
||||
return_tensors="pt",
|
||||
)
|
||||
text_embeddings = pipeline.text_encoder(text_input.input_ids.to(pipeline.device))[0]
|
||||
context = torch.cat([uncond_embeddings, text_embeddings])
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def next_step(model_output: Union[torch.FloatTensor, np.ndarray], timestep: int,
|
||||
sample: Union[torch.FloatTensor, np.ndarray], ddim_scheduler):
|
||||
timestep, next_timestep = min(
|
||||
timestep - ddim_scheduler.config.num_train_timesteps // ddim_scheduler.num_inference_steps, 999), timestep
|
||||
alpha_prod_t = ddim_scheduler.alphas_cumprod[timestep] if timestep >= 0 else ddim_scheduler.final_alpha_cumprod
|
||||
alpha_prod_t_next = ddim_scheduler.alphas_cumprod[next_timestep]
|
||||
beta_prod_t = 1 - alpha_prod_t
|
||||
next_original_sample = (sample - beta_prod_t ** 0.5 * model_output) / alpha_prod_t ** 0.5
|
||||
next_sample_direction = (1 - alpha_prod_t_next) ** 0.5 * model_output
|
||||
next_sample = alpha_prod_t_next ** 0.5 * next_original_sample + next_sample_direction
|
||||
return next_sample
|
||||
|
||||
|
||||
def get_noise_pred_single(latents, t, context, unet):
|
||||
noise_pred = unet(latents, t, encoder_hidden_states=context)["sample"]
|
||||
return noise_pred
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def ddim_loop(pipeline, ddim_scheduler, latent, num_inv_steps, prompt):
|
||||
context = init_prompt(prompt, pipeline)
|
||||
uncond_embeddings, cond_embeddings = context.chunk(2)
|
||||
all_latent = [latent]
|
||||
latent = latent.clone().detach()
|
||||
for i in tqdm(range(num_inv_steps)):
|
||||
t = ddim_scheduler.timesteps[len(ddim_scheduler.timesteps) - i - 1]
|
||||
noise_pred = get_noise_pred_single(latent.to(torch.float32), t, cond_embeddings.to(torch.float32), pipeline.unet)
|
||||
latent = next_step(noise_pred, t, latent, ddim_scheduler)
|
||||
all_latent.append(latent)
|
||||
return all_latent
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def ddim_inversion(pipeline, ddim_scheduler, video_latent, num_inv_steps, prompt=""):
|
||||
ddim_latents = ddim_loop(pipeline, ddim_scheduler, video_latent, num_inv_steps, prompt)
|
||||
return ddim_latents
|
||||
@@ -0,0 +1,185 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import torch
|
||||
import nvdiffrast.torch as dr
|
||||
import json
|
||||
import torch.nn.functional as F
|
||||
from PIL import Image
|
||||
import pymeshlab
|
||||
import cv2
|
||||
|
||||
def back_to_texture(glctx, look_at, pos, tri, tex, uv, uv_idx, idx, vn):
|
||||
rast_out, rast_out_db = dr.rasterize(glctx, pos, tri, resolution=[tex.shape[0],tex.shape[1]])
|
||||
gb_normal, _ = dr.interpolate(vn[None], rast_out, tri)
|
||||
gb_normal = F.normalize(gb_normal, dim=-1)
|
||||
if idx == 2 or idx == 0:
|
||||
filter_camera = [torch.tensor([[[[1,0.,0.]]]]).cuda(), torch.tensor([[[[-1,0.,0.]]]]).cuda()]
|
||||
else:
|
||||
filter_camera = [torch.tensor([[[[0,-1.,0.]]]]).cuda(), torch.tensor([[[[0,1.,0.]]]]).cuda()]
|
||||
nmasks = []
|
||||
for fc in filter_camera:
|
||||
nmasks.append(((gb_normal * fc) > 0.75).int().sum(keepdim=True, dim=-1))
|
||||
gb_normal_mask = 1 - (nmasks[0] | nmasks[1])
|
||||
#Image.fromarray(np.clip(gb_normal_mask[0,...,0].detach().cpu().numpy() * 255, 0, 255).astype(np.uint8)).save(f"mask_normal_{idx}.png")
|
||||
gb_mask = rast_out[...,3:4] > 0
|
||||
tri_list = torch.unique(rast_out[...,3:4].reshape(-1))
|
||||
tri_list = (tri_list[1:] - 1).to(torch.int32)
|
||||
pos = pos[0]
|
||||
|
||||
depth_map = rast_out[...,3:4].clone()
|
||||
depth_map[depth_map > 0] = 1
|
||||
depth_map = depth_map.to(torch.float32)
|
||||
dmax = (rast_out[...,2:3] * gb_mask).max()
|
||||
uv = torch.cat([uv * 2 - 1, torch.zeros(uv.shape[0], 1).cuda(), torch.ones(uv.shape[0], 1).cuda()], dim=1).unsqueeze(0)
|
||||
uv_idx = uv_idx[tri_list.to(torch.long)]
|
||||
rast_uv, rast_uv_db = dr.rasterize(glctx, uv, uv_idx, resolution=(1024, 1024))
|
||||
pos_clip = torch.cat([pos[...,:2], pos[...,3:]], -1)
|
||||
pos_2d, _ = dr.interpolate(pos_clip, rast_uv, tri[tri_list.to(torch.long)]) # pos (x, y, z, w)
|
||||
pos_coord = (pos_2d[...,:2] / (pos_2d[...,2:3] + 1e-6) + 1) / 2.
|
||||
texture_mask = (rast_uv[...,3:4] > 0).int()
|
||||
color = dr.texture(tex[None, ...] * gb_normal_mask, pos_coord, filter_mode='linear')
|
||||
color_mask = dr.texture(gb_normal_mask.to(torch.float32), pos_coord, filter_mode='linear')
|
||||
color_mask[color_mask > 0.82] = 1
|
||||
color_mask[color_mask <= 0.82] = 0
|
||||
color_mask = color_mask.to(torch.int32)
|
||||
#Image.fromarray(np.clip(color_mask[0].repeat(1,1,3).detach().cpu().numpy() * 255, 0, 255).astype(np.uint8)).save(f"depth_{idx}.png")
|
||||
texture_mask = texture_mask * color_mask
|
||||
#Image.fromarray(np.clip(color[0].detach().cpu().numpy() * 255, 0, 255).astype(np.uint8)).save(f"{idx}.png")
|
||||
#Image.fromarray(np.clip(texture_mask[0].repeat(1,1,3).detach().cpu().numpy() * 255, 0, 255).astype(np.uint8)).convert("RGB").save(f"mask-{idx}.png")
|
||||
return color, texture_mask, rast_uv
|
||||
|
||||
def perspective(fovy=0.6913, aspect=1.0, n=0.1, f=1000.0, device=None):
|
||||
y = np.tan(fovy / 2)
|
||||
return torch.tensor([[1/(y*aspect), 0, 0, 0],
|
||||
[ 0, 1/-y, 0, 0],
|
||||
[ 0, 0, -(f+n)/(f-n), -(2*f*n)/(f-n)],
|
||||
[ 0, 0, -1, 0]]).to(torch.float32).cuda()
|
||||
|
||||
def rec_mvp(trans, h, w):
|
||||
mv = trans
|
||||
fov = 40. / 180. * np.pi
|
||||
proj = perspective(fov, h / w, n=0.1, f=1000)
|
||||
mvp = proj @ mv
|
||||
return mvp
|
||||
|
||||
def aggregate_texture(kd_map, textures, texture_masks, rast_uvs):
|
||||
texture = torch.zeros_like(textures[0])
|
||||
texture_mask = torch.zeros_like(texture_masks[0])
|
||||
ctex = []
|
||||
for idx in range(len(textures)):
|
||||
ctex.append(textures[idx] * texture_masks[idx] + 10 * (1 - texture_masks[idx]))
|
||||
cat_textures = torch.stack(ctex, dim=-2)
|
||||
dis_measure = (cat_textures - kd_map.unsqueeze(-2)).abs().sum(-1)
|
||||
_, choose_idx = dis_measure.min(-1)
|
||||
|
||||
choose_idx = choose_idx.unsqueeze(-1).unsqueeze(-1).repeat(1, 1, 1, 1, 3)
|
||||
final_texture_map = torch.gather(cat_textures, 3, choose_idx).squeeze(-2)
|
||||
#cv2.imwrite("final_texture_map.png", cv2.cvtColor((final_texture_map[0].cpu().numpy() * 255).astype(np.uint8), cv2.COLOR_BGR2RGB))
|
||||
#cv2.imwrite("final_texture_mask.png", (texture_mask[0].cpu().numpy() * 255).astype(np.uint8))
|
||||
zero_mask = (final_texture_map.max(dim=-1, keepdim=True)[0] > 0.1)
|
||||
close_mask = ((final_texture_map[0] - kd_map).abs().sum(dim=-1, keepdim=True) < 1.0).int()
|
||||
for idx in range(len(textures)):
|
||||
texture += textures[idx] * texture_masks[idx]
|
||||
texture_mask |= texture_masks[idx]
|
||||
texture_mask = texture_mask * zero_mask * close_mask[None]
|
||||
optimize_mask = (texture_mask == 0).int()
|
||||
|
||||
#import pdb; pdb.set_trace()
|
||||
#mask = (texture_mask[0].cpu().numpy() * 255).astype(np.uint8)
|
||||
#cv2.imwrite("mask.png", mask)
|
||||
#kernel = np.ones((5,5), np.uint8)
|
||||
#dilated = cv2.dilate(mask, kernel, iterations=1)
|
||||
#cv2.imwrite("di_mask.png", dilated)
|
||||
#texture_mask[0] = torch.from_numpy(dilated).unsqueeze(-1).to(torch.float32) / 255.
|
||||
|
||||
final_texture_map = final_texture_map[0] * texture_mask[0]
|
||||
Image.fromarray(np.rint(final_texture_map.cpu().numpy() * 255).astype(np.uint8)).save(f"final_texture.png")
|
||||
|
||||
#cv2.imwrite("kd_map.png", cv2.cvtColor((kd_map.cpu().numpy() * 255).astype(np.uint8), cv2.COLOR_BGR2RGB))
|
||||
#cv2.imwrite("texture_map.png", cv2.cvtColor((final_texture_map.cpu().numpy() * 255).astype(np.uint8), cv2.COLOR_BGR2RGB))
|
||||
#result = cv2.seamlessClone((final_texture_map.cpu().numpy() * 255).astype(np.uint8), (kd_map.cpu().numpy() * 255).astype(np.uint8), mask, (mask.shape[1]//2, mask.shape[0]//2), cv2.NORMAL_CLONE)
|
||||
#cv2.imwrite("result.png", cv2.cvtColor(result * 255, cv2.COLOR_BGR2RGB))
|
||||
|
||||
kd_map = kd_map * (1 - texture_mask[0]) + final_texture_map
|
||||
return kd_map, optimize_mask
|
||||
|
||||
def refine(save_path, front_image, back_image, left_image, right_image):
|
||||
ms = pymeshlab.MeshSet()
|
||||
mesh_path = f"{save_path}/model-00.obj"
|
||||
ms.load_new_mesh(mesh_path)
|
||||
ms.apply_coord_laplacian_smoothing(stepsmoothnum=10)
|
||||
tl = open(mesh_path, "r").readlines()
|
||||
tex_uv = []
|
||||
uv_idx = []
|
||||
for line in tl:
|
||||
if line.startswith("vt"):
|
||||
uvs = line.split(" ")[1:3]
|
||||
tex_uv += [float(uvs[0]), 1.0-float(uvs[1])]
|
||||
tex_uv = torch.from_numpy(np.array(tex_uv)).to(torch.float32).cuda().reshape(-1, 2)
|
||||
m = ms.current_mesh()
|
||||
v_matrix = m.vertex_matrix()
|
||||
f_matrix = m.face_matrix()
|
||||
vn = m.vertex_normal_matrix()
|
||||
uv_idx = torch.arange(f_matrix.shape[0] * 3).reshape(-1, 3).to(torch.int32).cuda()
|
||||
vn = torch.tensor(vn).contiguous().cuda().to(torch.float32)
|
||||
|
||||
frames = []
|
||||
front_camera = torch.tensor([[
|
||||
1,0,0,0,
|
||||
0,0,1,0,
|
||||
0,-1,0,-1.5,
|
||||
0,0,0,1,
|
||||
]]).to(torch.float32).reshape(4,4).cuda()
|
||||
back_camera = torch.tensor([[
|
||||
1,0,0,0,
|
||||
0,0,1,0,
|
||||
0,1,0,-1.5,
|
||||
0,0,0,1,
|
||||
]]).to(torch.float32).reshape(4,4).cuda()
|
||||
right_camera = torch.tensor([[
|
||||
0,-1,0,0,
|
||||
0,0,1,0,
|
||||
1,0,0,-1.5,
|
||||
0,0,0,1,
|
||||
]]).to(torch.float32).reshape(4,4).cuda()
|
||||
left_camera = torch.tensor([[
|
||||
0,1,0,0,
|
||||
0,0,1,0,
|
||||
-1,0,0,-1.5,
|
||||
0,0,0,1,
|
||||
]]).to(torch.float32).reshape(4,4).cuda()
|
||||
frames = [front_camera, left_camera, back_camera, right_camera]
|
||||
|
||||
target_images = []
|
||||
for target_image in [front_image, left_image, back_image, right_image]:
|
||||
target_images.append(torch.from_numpy(np.asarray(target_image.convert("RGB"))).to(torch.float32).cuda() / 255.)
|
||||
|
||||
pos = torch.tensor(v_matrix, dtype=torch.float32).contiguous().cuda()
|
||||
tri = torch.tensor(f_matrix, dtype=torch.int32).contiguous().cuda()
|
||||
|
||||
kd_map = (torch.tensor(np.asarray(Image.open(f"{save_path}/texture_kd.jpg"))) / 255.).cuda()
|
||||
translate_tensor = torch.zeros((1,1,3)).cuda()
|
||||
pos = torch.cat([pos, torch.ones([pos.shape[0], 1]).cuda()],-1).unsqueeze(0)
|
||||
glctx = dr.RasterizeCudaContext()
|
||||
target_texture = []
|
||||
target_mask = []
|
||||
rast_uvs = []
|
||||
with torch.no_grad():
|
||||
for idx, trans in enumerate(frames):
|
||||
target_image = target_images[idx]
|
||||
look_at = -torch.linalg.inv(trans)[:3,2]
|
||||
mvp = rec_mvp(trans, h=target_images[0].shape[0], w=target_images[0].shape[1])
|
||||
trans_pos = pos.clone()
|
||||
trans_pos[...,:3] += translate_tensor
|
||||
view_pos = torch.matmul(mvp, trans_pos.unsqueeze(-1)).squeeze(-1)
|
||||
texture, mask, rast_uv = back_to_texture(glctx, look_at, view_pos, tri, target_image, tex_uv, uv_idx, idx, vn)
|
||||
target_texture.append(texture)
|
||||
target_mask.append(mask)
|
||||
rast_uvs.append(rast_uv)
|
||||
kd_map, opt_mask = aggregate_texture(kd_map, target_texture, target_mask, rast_uvs)
|
||||
opt_mask = opt_mask[0]
|
||||
Image.fromarray((np.clip(kd_map.detach().cpu().numpy() * 255, 0, 255)).astype(np.uint8)).save(f"{save_path}/refined_texture_kd.jpg")
|
||||
|
||||
#ms.save_current_mesh(f"{save_path}/model-00.obj")
|
||||
with open(f"{save_path}/model-00.mtl", "w") as f:
|
||||
f.write(f"newmtl default\nKa 0.0 0.0 0.0\nmap_Kd refined_texture_kd.jpg\nKs 0.0 0.0 0.0")
|
||||
@@ -0,0 +1,29 @@
|
||||
import importlib
|
||||
|
||||
|
||||
def find(cls_string):
|
||||
module_string = ".".join(cls_string.split(".")[:-1])
|
||||
cls_name = cls_string.split(".")[-1]
|
||||
module = importlib.import_module(module_string, package=None)
|
||||
cls = getattr(module, cls_name)
|
||||
return cls
|
||||
|
||||
|
||||
### grammar sugar for logging utilities ###
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("pytorch_lightning")
|
||||
|
||||
from pytorch_lightning.utilities.rank_zero import (
|
||||
rank_zero_debug,
|
||||
rank_zero_info,
|
||||
rank_zero_only,
|
||||
)
|
||||
|
||||
debug = rank_zero_debug
|
||||
info = rank_zero_info
|
||||
|
||||
|
||||
@rank_zero_only
|
||||
def warn(*args, **kwargs):
|
||||
logger.warn(*args, **kwargs)
|
||||
@@ -0,0 +1,18 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ...utils.base import BaseModule
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class BaseBackground(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
pass
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self):
|
||||
pass
|
||||
|
||||
def forward(self, dirs: Float[Tensor, "B H W 3"]) -> Float[Tensor, "B H W Nc"]:
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,56 @@
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from .base import BaseBackground
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class SolidColorBackground(BaseBackground):
|
||||
@dataclass
|
||||
class Config(BaseBackground.Config):
|
||||
n_output_dims: int = 3
|
||||
color: Tuple = (1.0, 1.0, 1.0)
|
||||
learned: bool = False
|
||||
random_aug: bool = False
|
||||
random_aug_prob: float = 0.5
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
self.env_color: Float[Tensor, "Nc"]
|
||||
if self.cfg.learned:
|
||||
self.env_color = nn.Parameter(
|
||||
torch.as_tensor(self.cfg.color, dtype=torch.float32)
|
||||
)
|
||||
else:
|
||||
self.register_buffer(
|
||||
"env_color", torch.as_tensor(self.cfg.color, dtype=torch.float32)
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
dirs: Float[Tensor, "B H W Nc"],
|
||||
color_spec: Optional[Float[Tensor, "Nc"]] = None,
|
||||
) -> Float[Tensor, "B H W Nc"]:
|
||||
color = torch.ones(*dirs.shape[:-1], self.cfg.n_output_dims).to(dirs) * (
|
||||
color_spec if color_spec is not None else self.env_color
|
||||
)
|
||||
if (
|
||||
self.training
|
||||
and self.cfg.random_aug
|
||||
and random.random() < self.cfg.random_aug_prob
|
||||
):
|
||||
# use random background color with probability random_aug_prob
|
||||
# color = color * 0 + (
|
||||
# torch.ones(*dirs.shape[:-1], self.cfg.n_output_dims).to(dirs) *
|
||||
# torch.rand(self.cfg.n_output_dims).to(dirs)
|
||||
# )
|
||||
color = color * 0 + ( # prevent checking for unused parameters in DDP
|
||||
torch.rand(dirs.shape[0], 1, 1, self.cfg.n_output_dims)
|
||||
.to(dirs)
|
||||
.expand(*dirs.shape[:-1], -1)
|
||||
)
|
||||
return color
|
||||
@@ -0,0 +1,33 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from ..utils.base import BaseModule
|
||||
from ..utils.typing import *
|
||||
|
||||
|
||||
class LinearCameraEmbedder(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
in_channels: int = 0
|
||||
out_channels: int = 0
|
||||
conditions: List[str] = field(default_factory=list)
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.linear = nn.Linear(self.cfg.in_channels, self.cfg.out_channels)
|
||||
|
||||
def forward(self, **kwargs):
|
||||
cond_tensors = []
|
||||
for cond_name in self.cfg.conditions:
|
||||
assert cond_name in kwargs
|
||||
cond = kwargs[cond_name]
|
||||
# cond in shape (B, Nv, ...)
|
||||
cond_tensors.append(cond.view(*cond.shape[:2], -1))
|
||||
cond_tensor = torch.cat(cond_tensors, dim=-1)
|
||||
assert cond_tensor.shape[-1] == self.cfg.in_channels
|
||||
embedding = self.linear(cond_tensor)
|
||||
return embedding
|
||||
@@ -0,0 +1,32 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ..renderers.base import BaseRenderer
|
||||
from ...utils.base import BaseObject
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExporterOutput:
|
||||
save_name: str
|
||||
save_type: str
|
||||
params: Dict[str, Any]
|
||||
|
||||
|
||||
class Exporter(BaseObject):
|
||||
@dataclass
|
||||
class Config(BaseObject.Config):
|
||||
save_video: bool = False
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self, renderer: BaseRenderer) -> None:
|
||||
self.renderer = renderer
|
||||
|
||||
def __call__(self, *args, **kwargs) -> List[ExporterOutput]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DummyExporter(Exporter):
|
||||
def __call__(self, *args, **kwargs) -> List[ExporterOutput]:
|
||||
# DummyExporter does not export anything
|
||||
return []
|
||||
@@ -0,0 +1,262 @@
|
||||
from dataclasses import dataclass, field
|
||||
import os
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from ..renderers.base import BaseRenderer
|
||||
from .base import Exporter, ExporterOutput
|
||||
from ..mesh import Mesh
|
||||
from ...utils.rasterize import NVDiffRasterizerContext
|
||||
from ...utils.typing import *
|
||||
from ...utils.misc import time_recorder as tr, time_recorder_enabled
|
||||
|
||||
|
||||
def uv_padding_cpu(image, hole_mask, padding):
|
||||
uv_padding_size = padding
|
||||
inpaint_image = (
|
||||
cv2.inpaint(
|
||||
(image.detach().cpu().numpy() * 255).astype(np.uint8),
|
||||
(hole_mask.detach().cpu().numpy() * 255).astype(np.uint8),
|
||||
uv_padding_size,
|
||||
cv2.INPAINT_TELEA,
|
||||
)
|
||||
/ 255.0
|
||||
)
|
||||
return torch.from_numpy(inpaint_image).to(image)
|
||||
|
||||
|
||||
def uv_padding_cvc(image, hole_mask, padding):
|
||||
import cvcuda
|
||||
|
||||
torch_to_cvc = lambda x, layout: cvcuda.as_tensor(x, layout)
|
||||
cvc_to_torch = lambda x: torch.as_tensor(x.cuda())
|
||||
|
||||
uv_padding_size = padding
|
||||
image_cvc = torch_to_cvc((image.detach() * 255).to(torch.uint8), "HWC")
|
||||
hole_mask_cvc = torch_to_cvc((hole_mask.detach() * 255).to(torch.uint8), "HW")
|
||||
inpaint_image = cvcuda.inpaint(image_cvc, hole_mask_cvc, uv_padding_size)
|
||||
inpaint_image = cvc_to_torch(inpaint_image) / 255.0
|
||||
return inpaint_image.to(image)
|
||||
|
||||
|
||||
def uv_padding(image, hole_mask, padding):
|
||||
try:
|
||||
inpaint_image = uv_padding_cvc(image, hole_mask, padding)
|
||||
except:
|
||||
lrm.info(f"CVCUDA not available, fallback to CPU UV padding.")
|
||||
inpaint_image = uv_padding_cpu(image, hole_mask, padding)
|
||||
return inpaint_image
|
||||
|
||||
|
||||
class MeshExporter(Exporter):
|
||||
@dataclass
|
||||
class Config(Exporter.Config):
|
||||
fmt: str = "obj" # in ['obj', 'glb']
|
||||
visual: str = "uv" # in ['uv', 'vertex']
|
||||
save_name: str = "model"
|
||||
save_normal: bool = False
|
||||
save_uv: bool = True
|
||||
save_texture: bool = True
|
||||
texture_size: int = 1024
|
||||
texture_format: str = "jpg"
|
||||
uv_unwrap_method: str = "xatlas"
|
||||
xatlas_chart_options: dict = field(default_factory=dict)
|
||||
xatlas_pack_options: dict = field(default_factory=dict)
|
||||
smartuv_options: dict = field(default_factory=dict)
|
||||
uv_padding_size: int = 2
|
||||
subdivide: bool = False
|
||||
post_process: bool = False
|
||||
post_process_options: dict = field(default_factory=dict)
|
||||
context_type: str = "gl"
|
||||
output_path: str = "outputs"
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self, renderer: BaseRenderer) -> None:
|
||||
super().configure(renderer)
|
||||
self.ctx = NVDiffRasterizerContext(self.cfg.context_type, self.device)
|
||||
if self.cfg.fmt == "obj-mtl":
|
||||
lrm.warn(
|
||||
f"fmt=obj-mtl is deprecated, please us fmt=obj and visual=uv instead."
|
||||
)
|
||||
self.cfg.fmt = "obj"
|
||||
self.cfg.visual = "uv"
|
||||
|
||||
if self.cfg.fmt == "glb":
|
||||
assert self.cfg.visual in [
|
||||
"vertex",
|
||||
"uv-blender",
|
||||
], "GLB format only supports visual=vertex and visual=uv-blender!"
|
||||
|
||||
def get_geometry(self, scene_code: torch.Tensor) -> Mesh:
|
||||
tr.start("Surface extraction")
|
||||
mesh: Mesh = self.renderer.isosurface(scene_code)
|
||||
tr.end("Surface extraction")
|
||||
return mesh
|
||||
|
||||
def get_texture_maps(
|
||||
self, scene_code: torch.Tensor, mesh: Mesh
|
||||
) -> Dict[str, torch.Tensor]:
|
||||
assert mesh.has_uv
|
||||
# clip space transform
|
||||
uv_clip = mesh.v_tex * 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, _ = self.ctx.rasterize_one(
|
||||
uv_clip4,
|
||||
mesh.t_tex_idx,
|
||||
(self.cfg.texture_size, self.cfg.texture_size),
|
||||
)
|
||||
|
||||
hole_mask = ~(rast[:, :, 3] > 0)
|
||||
|
||||
# Interpolate world space position
|
||||
gb_pos, _ = self.ctx.interpolate_one(
|
||||
mesh.v_pos, rast[None, ...], mesh.t_pos_idx
|
||||
)
|
||||
gb_pos = gb_pos[0]
|
||||
|
||||
# Sample out textures from MLP
|
||||
tr.start("Query color")
|
||||
geo_out = self.renderer.query(scene_code, points=gb_pos)
|
||||
tr.end("Query color")
|
||||
mat_out = self.renderer.material.export(points=gb_pos, **geo_out)
|
||||
|
||||
textures = {}
|
||||
tr.start("UV padding")
|
||||
if "albedo" in mat_out:
|
||||
textures["map_Kd"] = uv_padding(
|
||||
mat_out["albedo"], hole_mask, self.cfg.uv_padding_size
|
||||
)
|
||||
else:
|
||||
lrm.warn(
|
||||
"save_texture is True but no albedo texture found, using default white texture"
|
||||
)
|
||||
if "metallic" in mat_out:
|
||||
textures["map_Pm"] = uv_padding(
|
||||
mat_out["metallic"], hole_mask, self.cfg.uv_padding_size
|
||||
)
|
||||
if "roughness" in mat_out:
|
||||
textures["map_Pr"] = uv_padding(
|
||||
mat_out["roughness"], hole_mask, self.cfg.uv_padding_size
|
||||
)
|
||||
if "bump" in mat_out:
|
||||
textures["map_Bump"] = uv_padding(
|
||||
mat_out["bump"], hole_mask, self.cfg.uv_padding_size
|
||||
)
|
||||
tr.end("UV padding")
|
||||
return textures
|
||||
|
||||
def __call__(self, names, scene_codes) -> List[ExporterOutput]:
|
||||
outputs = []
|
||||
for name, scene_code in zip(names, scene_codes):
|
||||
mesh = self.get_geometry(scene_code)
|
||||
if self.cfg.post_process:
|
||||
tr.start("Mesh post-processing")
|
||||
mesh = mesh.post_process(self.cfg.post_process_options)
|
||||
tr.end("Mesh post-processing")
|
||||
if self.cfg.visual == "uv":
|
||||
output = self.export_model_with_mtl(
|
||||
name, self.cfg.fmt, scene_code, mesh
|
||||
)
|
||||
elif self.cfg.visual == "vertex":
|
||||
output = self.export_model(name, self.cfg.fmt, scene_code, mesh)
|
||||
elif self.cfg.visual == "uv-blender":
|
||||
output = self.export_model_blender(name, self.cfg.fmt, scene_code, mesh)
|
||||
else:
|
||||
raise ValueError(f"Unsupported visual format: {self.cfg.visual}")
|
||||
outputs.append(output)
|
||||
return outputs
|
||||
|
||||
def export_model_with_mtl(
|
||||
self, name: str, fmt: str, scene_code: torch.Tensor, mesh: Mesh
|
||||
) -> ExporterOutput:
|
||||
params = {
|
||||
"mesh": mesh,
|
||||
"save_mat": True,
|
||||
"save_normal": self.cfg.save_normal,
|
||||
"save_uv": self.cfg.save_uv,
|
||||
"save_vertex_color": False,
|
||||
"map_Kd": None, # Base Color
|
||||
"map_Ks": None, # Specular
|
||||
"map_Bump": None, # Normal
|
||||
# ref: https://en.wikipedia.org/wiki/Wavefront_.obj_file#Physically-based_Rendering
|
||||
"map_Pm": None, # Metallic
|
||||
"map_Pr": None, # Roughness
|
||||
"map_format": self.cfg.texture_format,
|
||||
}
|
||||
|
||||
if self.cfg.save_uv:
|
||||
mesh.unwrap_uv(
|
||||
self.cfg.uv_unwrap_method,
|
||||
self.cfg.xatlas_chart_options,
|
||||
self.cfg.xatlas_pack_options,
|
||||
self.cfg.smartuv_options,
|
||||
)
|
||||
|
||||
if self.cfg.save_texture:
|
||||
lrm.info("Exporting textures ...")
|
||||
assert self.cfg.save_uv, "save_uv must be True when save_texture is True"
|
||||
|
||||
with time_recorder_enabled():
|
||||
textures = self.get_texture_maps(scene_code, mesh)
|
||||
params.update(textures)
|
||||
os.makedirs(self.cfg.output_path, exist_ok=True)
|
||||
np.savez(f"{self.cfg.output_path}/tex_info.npz", v_tex=mesh.v_tex.cpu().numpy(), t_tex_idx=mesh.t_tex_idx.cpu().numpy())
|
||||
return ExporterOutput(
|
||||
save_name=f"{self.cfg.save_name}-{name}.{fmt}", save_type=fmt, params=params
|
||||
)
|
||||
|
||||
def export_model(
|
||||
self, name: str, fmt: str, scene_code, mesh: Mesh
|
||||
) -> ExporterOutput:
|
||||
params = {
|
||||
"mesh": mesh,
|
||||
"save_mat": False,
|
||||
"save_normal": self.cfg.save_normal,
|
||||
"save_uv": self.cfg.save_uv,
|
||||
"save_vertex_color": False,
|
||||
"map_Kd": None, # Base Color
|
||||
"map_Ks": None, # Specular
|
||||
"map_Bump": None, # Normal
|
||||
# ref: https://en.wikipedia.org/wiki/Wavefront_.obj_file#Physically-based_Rendering
|
||||
"map_Pm": None, # Metallic
|
||||
"map_Pr": None, # Roughness
|
||||
"map_format": self.cfg.texture_format,
|
||||
}
|
||||
|
||||
if self.cfg.save_uv:
|
||||
mesh.unwrap_uv(
|
||||
self.cfg.uv_unwrap_method,
|
||||
self.cfg.xatlas_chart_options,
|
||||
self.cfg.xatlas_pack_options,
|
||||
self.cfg.smartuv_options,
|
||||
)
|
||||
|
||||
if self.cfg.save_texture:
|
||||
lrm.info("Exporting textures ...")
|
||||
geo_out = self.renderer.query(scene_code, points=mesh.v_pos)
|
||||
mat_out = self.renderer.material.export(points=mesh.v_pos, **geo_out)
|
||||
|
||||
if "albedo" in mat_out:
|
||||
mesh.set_vertex_color(mat_out["albedo"])
|
||||
params["save_vertex_color"] = True
|
||||
else:
|
||||
lrm.warn(
|
||||
"save_texture is True but no albedo texture found, not saving vertex color"
|
||||
)
|
||||
|
||||
return ExporterOutput(
|
||||
save_name=f"{self.cfg.save_name}-{name}.{fmt}", save_type=fmt, params=params
|
||||
)
|
||||
@@ -0,0 +1,271 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from ..models.mesh import Mesh
|
||||
from ..utils.typing import *
|
||||
from ..utils.ops import scale_tensor
|
||||
|
||||
|
||||
class IsosurfaceHelper(nn.Module):
|
||||
points_range: Tuple[float, float] = (0, 1)
|
||||
|
||||
@property
|
||||
def grid_vertices(self) -> Float[Tensor, "N 3"]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class MarchingCubeCPUHelper(IsosurfaceHelper):
|
||||
def __init__(self, resolution: int) -> None:
|
||||
super().__init__()
|
||||
self.resolution = resolution
|
||||
import mcubes
|
||||
|
||||
self.mc_func: Callable = mcubes.marching_cubes
|
||||
self._grid_vertices: Optional[Float[Tensor, "N3 3"]] = None
|
||||
self._dummy: Float[Tensor, "..."]
|
||||
self.register_buffer(
|
||||
"_dummy", torch.zeros(0, dtype=torch.float32), persistent=False
|
||||
)
|
||||
|
||||
@property
|
||||
def grid_vertices(self) -> Float[Tensor, "N3 3"]:
|
||||
if self._grid_vertices is None:
|
||||
# keep the vertices on CPU so that we can support very large resolution
|
||||
x, y, z = (
|
||||
torch.linspace(*self.points_range, self.resolution),
|
||||
torch.linspace(*self.points_range, self.resolution),
|
||||
torch.linspace(*self.points_range, self.resolution),
|
||||
)
|
||||
x, y, z = torch.meshgrid(x, y, z, indexing="ij")
|
||||
verts = torch.cat(
|
||||
[x.reshape(-1, 1), y.reshape(-1, 1), z.reshape(-1, 1)], dim=-1
|
||||
).reshape(-1, 3)
|
||||
self._grid_vertices = verts
|
||||
return self._grid_vertices
|
||||
|
||||
def forward(
|
||||
self,
|
||||
level: Float[Tensor, "N3 1"],
|
||||
deformation: Optional[Float[Tensor, "N3 3"]] = None,
|
||||
) -> Mesh:
|
||||
if deformation is not None:
|
||||
lrm.warn(
|
||||
f"{self.__class__.__name__} does not support deformation. Ignoring."
|
||||
)
|
||||
level = -level.view(self.resolution, self.resolution, self.resolution)
|
||||
print(level.shape, level.min(), level.max())
|
||||
v_pos, t_pos_idx = self.mc_func(
|
||||
level.detach().cpu().numpy(), 0.0
|
||||
) # transform to numpy
|
||||
# test
|
||||
v_pos, t_pos_idx = (
|
||||
torch.from_numpy(v_pos).float().to(self._dummy.device),
|
||||
torch.from_numpy(t_pos_idx.astype(np.int64)).long().to(self._dummy.device),
|
||||
) # transform back to torch tensor on CUDA
|
||||
v_pos = v_pos / (self.resolution - 1.0)
|
||||
return Mesh(v_pos=v_pos, t_pos_idx=t_pos_idx)
|
||||
|
||||
|
||||
def get_center_boundary_index(verts):
|
||||
# Assuming the verts are in range [-1.0, 1.0]
|
||||
length_ = torch.linalg.norm(verts**2, ord=2, dim=-1, keepdim=False)
|
||||
center_idx = torch.argmin(length_)
|
||||
# center_idx = torch.where(length_ < 0.1)[0]
|
||||
boundary_neg = verts == verts.max()
|
||||
boundary_pos = verts == verts.min()
|
||||
boundary = torch.bitwise_or(boundary_pos, boundary_neg)
|
||||
boundary = torch.sum(boundary.float(), dim=-1)
|
||||
boundary_idx = torch.nonzero(boundary)
|
||||
return center_idx.unsqueeze(0), boundary_idx.squeeze(dim=-1)
|
||||
|
||||
|
||||
class MarchingTetrahedraHelper(IsosurfaceHelper):
|
||||
def __init__(self, resolution: int, tets_path: str):
|
||||
super().__init__()
|
||||
self.resolution = resolution
|
||||
self.tets_path = tets_path
|
||||
|
||||
self.triangle_table: Float[Tensor, "..."]
|
||||
self.register_buffer(
|
||||
"triangle_table",
|
||||
torch.as_tensor(
|
||||
[
|
||||
[-1, -1, -1, -1, -1, -1],
|
||||
[1, 0, 2, -1, -1, -1],
|
||||
[4, 0, 3, -1, -1, -1],
|
||||
[1, 4, 2, 1, 3, 4],
|
||||
[3, 1, 5, -1, -1, -1],
|
||||
[2, 3, 0, 2, 5, 3],
|
||||
[1, 4, 0, 1, 5, 4],
|
||||
[4, 2, 5, -1, -1, -1],
|
||||
[4, 5, 2, -1, -1, -1],
|
||||
[4, 1, 0, 4, 5, 1],
|
||||
[3, 2, 0, 3, 5, 2],
|
||||
[1, 3, 5, -1, -1, -1],
|
||||
[4, 1, 2, 4, 3, 1],
|
||||
[3, 0, 4, -1, -1, -1],
|
||||
[2, 0, 1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1],
|
||||
],
|
||||
dtype=torch.long,
|
||||
),
|
||||
persistent=False,
|
||||
)
|
||||
self.num_triangles_table: Integer[Tensor, "..."]
|
||||
self.register_buffer(
|
||||
"num_triangles_table",
|
||||
torch.as_tensor(
|
||||
[0, 1, 1, 2, 1, 2, 2, 1, 1, 2, 2, 1, 2, 1, 1, 0], dtype=torch.long
|
||||
),
|
||||
persistent=False,
|
||||
)
|
||||
self.base_tet_edges: Integer[Tensor, "..."]
|
||||
self.register_buffer(
|
||||
"base_tet_edges",
|
||||
torch.as_tensor([0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3], dtype=torch.long),
|
||||
persistent=False,
|
||||
)
|
||||
|
||||
tets = np.load(self.tets_path)
|
||||
self._grid_vertices: Float[Tensor, "..."]
|
||||
self.register_buffer(
|
||||
"_grid_vertices",
|
||||
torch.from_numpy(tets["vertices"]).float(),
|
||||
persistent=False,
|
||||
)
|
||||
self.indices: Integer[Tensor, "..."]
|
||||
self.register_buffer(
|
||||
"indices", torch.from_numpy(tets["indices"]).long(), persistent=False
|
||||
)
|
||||
|
||||
self._all_edges: Optional[Integer[Tensor, "Ne 2"]] = None
|
||||
self.center_indices, self.boundary_indices = get_center_boundary_index(
|
||||
scale_tensor(self.grid_vertices, self.points_range, (-1.0, 1.0))
|
||||
)
|
||||
|
||||
def normalize_grid_deformation(
|
||||
self, grid_vertex_offsets: Float[Tensor, "Nv 3"]
|
||||
) -> Float[Tensor, "Nv 3"]:
|
||||
return (
|
||||
(self.points_range[1] - self.points_range[0])
|
||||
/ (self.resolution) # half tet size is approximately 1 / self.resolution
|
||||
* torch.tanh(grid_vertex_offsets)
|
||||
) # FIXME: hard-coded activation
|
||||
|
||||
@property
|
||||
def grid_vertices(self) -> Float[Tensor, "Nv 3"]:
|
||||
return self._grid_vertices
|
||||
|
||||
@property
|
||||
def all_edges(self) -> Integer[Tensor, "Ne 2"]:
|
||||
if self._all_edges is None:
|
||||
# compute edges on GPU, or it would be VERY SLOW (basically due to the unique operation)
|
||||
edges = torch.tensor(
|
||||
[0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3],
|
||||
dtype=torch.long,
|
||||
device=self.indices.device,
|
||||
)
|
||||
_all_edges = self.indices[:, edges].reshape(-1, 2)
|
||||
_all_edges_sorted = torch.sort(_all_edges, dim=1)[0]
|
||||
_all_edges = torch.unique(_all_edges_sorted, dim=0)
|
||||
self._all_edges = _all_edges
|
||||
return self._all_edges
|
||||
|
||||
def sort_edges(self, edges_ex2):
|
||||
with torch.no_grad():
|
||||
order = (edges_ex2[:, 0] > edges_ex2[:, 1]).long()
|
||||
order = order.unsqueeze(dim=1)
|
||||
|
||||
a = torch.gather(input=edges_ex2, index=order, dim=1)
|
||||
b = torch.gather(input=edges_ex2, index=1 - order, dim=1)
|
||||
|
||||
return torch.stack([a, b], -1)
|
||||
|
||||
def _forward(self, pos_nx3, sdf_n, tet_fx4):
|
||||
with torch.no_grad():
|
||||
occ_n = sdf_n > 0
|
||||
occ_fx4 = occ_n[tet_fx4.reshape(-1)].reshape(-1, 4)
|
||||
occ_sum = torch.sum(occ_fx4, -1)
|
||||
valid_tets = (occ_sum > 0) & (occ_sum < 4)
|
||||
occ_sum = occ_sum[valid_tets]
|
||||
|
||||
# find all vertices
|
||||
all_edges = tet_fx4[valid_tets][:, self.base_tet_edges].reshape(-1, 2)
|
||||
all_edges = self.sort_edges(all_edges)
|
||||
unique_edges, idx_map = torch.unique(all_edges, dim=0, return_inverse=True)
|
||||
|
||||
unique_edges = unique_edges.long()
|
||||
mask_edges = occ_n[unique_edges.reshape(-1)].reshape(-1, 2).sum(-1) == 1
|
||||
mapping = (
|
||||
torch.ones(
|
||||
(unique_edges.shape[0]), dtype=torch.long, device=pos_nx3.device
|
||||
)
|
||||
* -1
|
||||
)
|
||||
mapping[mask_edges] = torch.arange(
|
||||
mask_edges.sum(), dtype=torch.long, device=pos_nx3.device
|
||||
)
|
||||
idx_map = mapping[idx_map] # map edges to verts
|
||||
|
||||
interp_v = unique_edges[mask_edges]
|
||||
edges_to_interp = pos_nx3[interp_v.reshape(-1)].reshape(-1, 2, 3)
|
||||
edges_to_interp_sdf = sdf_n[interp_v.reshape(-1)].reshape(-1, 2, 1)
|
||||
edges_to_interp_sdf[:, -1] *= -1
|
||||
|
||||
denominator = edges_to_interp_sdf.sum(1, keepdim=True)
|
||||
|
||||
edges_to_interp_sdf = torch.flip(edges_to_interp_sdf, [1]) / denominator
|
||||
verts = (edges_to_interp * edges_to_interp_sdf).sum(1)
|
||||
|
||||
idx_map = idx_map.reshape(-1, 6)
|
||||
|
||||
v_id = torch.pow(2, torch.arange(4, dtype=torch.long, device=pos_nx3.device))
|
||||
tetindex = (occ_fx4[valid_tets] * v_id.unsqueeze(0)).sum(-1)
|
||||
num_triangles = self.num_triangles_table[tetindex]
|
||||
|
||||
# Generate triangle indices
|
||||
faces = torch.cat(
|
||||
(
|
||||
torch.gather(
|
||||
input=idx_map[num_triangles == 1],
|
||||
dim=1,
|
||||
index=self.triangle_table[tetindex[num_triangles == 1]][:, :3],
|
||||
).reshape(-1, 3),
|
||||
torch.gather(
|
||||
input=idx_map[num_triangles == 2],
|
||||
dim=1,
|
||||
index=self.triangle_table[tetindex[num_triangles == 2]][:, :6],
|
||||
).reshape(-1, 3),
|
||||
),
|
||||
dim=0,
|
||||
)
|
||||
|
||||
return verts, faces
|
||||
|
||||
def forward(
|
||||
self,
|
||||
level: Float[Tensor, "N3 1"],
|
||||
deformation: Optional[Float[Tensor, "N3 3"]] = None,
|
||||
) -> Mesh:
|
||||
if deformation is not None:
|
||||
grid_vertices = self.grid_vertices + self.normalize_grid_deformation(
|
||||
deformation
|
||||
)
|
||||
else:
|
||||
grid_vertices = self.grid_vertices
|
||||
|
||||
v_pos, t_pos_idx = self._forward(grid_vertices, level, self.indices)
|
||||
|
||||
mesh = Mesh(
|
||||
v_pos=v_pos,
|
||||
t_pos_idx=t_pos_idx,
|
||||
# extras
|
||||
grid_vertices=grid_vertices,
|
||||
tet_edges=self.all_edges,
|
||||
grid_level=level,
|
||||
grid_deformation=deformation,
|
||||
)
|
||||
|
||||
return mesh
|
||||
@@ -0,0 +1,17 @@
|
||||
from ..utils.ops import scale_tensor
|
||||
from ..utils.misc import get_device
|
||||
|
||||
|
||||
class LPIPS:
|
||||
def __init__(self):
|
||||
import lpips
|
||||
self.model = lpips.LPIPS(net="vgg").to(get_device())
|
||||
self.model.eval()
|
||||
for params in self.model.parameters():
|
||||
params.requires_grad = False
|
||||
self.model_input_range = (-1, 1)
|
||||
|
||||
def __call__(self, x1, x2, return_layers=False, input_range=(0, 1)):
|
||||
x1 = scale_tensor(x1, input_range, self.model_input_range)
|
||||
x2 = scale_tensor(x2, input_range, self.model_input_range)
|
||||
return self.model(x1, x2, retPerLayer=return_layers, normalize=False)
|
||||
@@ -0,0 +1,23 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ...utils.base import BaseModule
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class BaseMaterial(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
pass
|
||||
|
||||
cfg: Config
|
||||
requires_normal: bool = False
|
||||
requires_tangent: bool = False
|
||||
|
||||
def configure(self):
|
||||
pass
|
||||
|
||||
def forward(self, *args, **kwargs) -> Float[Tensor, "*B 3"]:
|
||||
raise NotImplementedError
|
||||
|
||||
def export(self, *args, **kwargs) -> Dict[str, Any]:
|
||||
return {}
|
||||
@@ -0,0 +1,60 @@
|
||||
import random
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from .base import BaseMaterial
|
||||
from ..networks import get_encoding, get_mlp
|
||||
from ...utils.ops import dot, get_activation
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class NoMaterial(BaseMaterial):
|
||||
@dataclass
|
||||
class Config(BaseMaterial.Config):
|
||||
n_output_dims: int = 3
|
||||
color_activation: str = "sigmoid"
|
||||
input_feature_dims: Optional[int] = None
|
||||
mlp_network_config: Optional[dict] = None
|
||||
requires_normal: bool = False
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
self.use_network = False
|
||||
if (
|
||||
self.cfg.input_feature_dims is not None
|
||||
and self.cfg.mlp_network_config is not None
|
||||
):
|
||||
self.network = get_mlp(
|
||||
self.cfg.input_feature_dims,
|
||||
self.cfg.n_output_dims,
|
||||
self.cfg.mlp_network_config,
|
||||
)
|
||||
self.use_network = True
|
||||
self.requires_normal = self.cfg.requires_normal
|
||||
|
||||
def forward(
|
||||
self, features: Float[Tensor, "B ... Nf"], **kwargs
|
||||
) -> Float[Tensor, "B ... Nc"]:
|
||||
if not self.use_network:
|
||||
assert (
|
||||
features.shape[-1] == self.cfg.n_output_dims
|
||||
), f"Expected {self.cfg.n_output_dims} output dims, only got {features.shape[-1]} dims input."
|
||||
color = get_activation(self.cfg.color_activation)(features)
|
||||
else:
|
||||
color = self.network(features.view(-1, features.shape[-1])).view(
|
||||
*features.shape[:-1], self.cfg.n_output_dims
|
||||
)
|
||||
color = get_activation(self.cfg.color_activation)(color)
|
||||
return color
|
||||
|
||||
def export(self, features: Float[Tensor, "*N Nf"], **kwargs) -> Dict[str, Any]:
|
||||
color = self(features, **kwargs).clamp(0, 1)
|
||||
assert color.shape[-1] >= 3, "Output color must have at least 3 channels"
|
||||
if color.shape[-1] > 3:
|
||||
lrm.warn("Output color has >3 channels, treating the first 3 as RGB")
|
||||
return {"albedo": color[..., :3]}
|
||||
@@ -0,0 +1,470 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from ..utils.ops import dot
|
||||
from ..utils.typing import *
|
||||
from ..utils.misc import time_recorder as tr, time_recorder_enabled
|
||||
|
||||
|
||||
class Mesh:
|
||||
def __init__(
|
||||
self, v_pos: Float[Tensor, "Nv 3"], t_pos_idx: Integer[Tensor, "Nf 3"], **kwargs
|
||||
) -> None:
|
||||
self.v_pos: Float[Tensor, "Nv 3"] = v_pos
|
||||
self.t_pos_idx: Integer[Tensor, "Nf 3"] = t_pos_idx
|
||||
self._v_nrm: Optional[Float[Tensor, "Nv 3"]] = None
|
||||
self._v_tng: Optional[Float[Tensor, "Nv 3"]] = None
|
||||
self._v_tex: Optional[Float[Tensor, "Nt 3"]] = None
|
||||
self._t_tex_idx: Optional[Float[Tensor, "Nf 3"]] = None
|
||||
self._v_rgb: Optional[Float[Tensor, "Nv 3"]] = None
|
||||
self._edges: Optional[Integer[Tensor, "Ne 2"]] = None
|
||||
self.extras: Dict[str, Any] = {}
|
||||
for k, v in kwargs.items():
|
||||
self.add_extra(k, v)
|
||||
|
||||
def add_extra(self, k, v) -> None:
|
||||
self.extras[k] = v
|
||||
|
||||
def remove_outlier(self, outlier_n_faces_threshold: Union[int, float]) -> Mesh:
|
||||
if self.requires_grad:
|
||||
lrm.debug("Mesh is differentiable, not removing outliers")
|
||||
return self
|
||||
|
||||
# use trimesh to first split the mesh into connected components
|
||||
# then remove the components with less than n_face_threshold faces
|
||||
import trimesh
|
||||
|
||||
# construct a trimesh object
|
||||
mesh = trimesh.Trimesh(
|
||||
vertices=self.v_pos.detach().cpu().numpy(),
|
||||
faces=self.t_pos_idx.detach().cpu().numpy(),
|
||||
)
|
||||
|
||||
# split the mesh into connected components
|
||||
components = mesh.split(only_watertight=False)
|
||||
# log the number of faces in each component
|
||||
lrm.debug(
|
||||
"Mesh has {} components, with faces: {}".format(
|
||||
len(components), [c.faces.shape[0] for c in components]
|
||||
)
|
||||
)
|
||||
|
||||
n_faces_threshold: int
|
||||
if isinstance(outlier_n_faces_threshold, float):
|
||||
# set the threshold to the number of faces in the largest component multiplied by outlier_n_faces_threshold
|
||||
n_faces_threshold = int(
|
||||
max([c.faces.shape[0] for c in components]) * outlier_n_faces_threshold
|
||||
)
|
||||
else:
|
||||
# set the threshold directly to outlier_n_faces_threshold
|
||||
n_faces_threshold = outlier_n_faces_threshold
|
||||
|
||||
# log the threshold
|
||||
lrm.debug(
|
||||
"Removing components with less than {} faces".format(n_faces_threshold)
|
||||
)
|
||||
|
||||
# remove the components with less than n_face_threshold faces
|
||||
components = [c for c in components if c.faces.shape[0] >= n_faces_threshold]
|
||||
|
||||
# log the number of faces in each component after removing outliers
|
||||
lrm.debug(
|
||||
"Mesh has {} components after removing outliers, with faces: {}".format(
|
||||
len(components), [c.faces.shape[0] for c in components]
|
||||
)
|
||||
)
|
||||
# merge the components
|
||||
mesh = trimesh.util.concatenate(components)
|
||||
|
||||
# convert back to our mesh format
|
||||
v_pos = torch.from_numpy(mesh.vertices).to(self.v_pos)
|
||||
t_pos_idx = torch.from_numpy(mesh.faces).to(self.t_pos_idx)
|
||||
|
||||
clean_mesh = Mesh(v_pos, t_pos_idx)
|
||||
# keep the extras unchanged
|
||||
|
||||
if len(self.extras) > 0:
|
||||
clean_mesh.extras = self.extras
|
||||
lrm.debug(
|
||||
f"The following extra attributes are inherited from the original mesh unchanged: {list(self.extras.keys())}"
|
||||
)
|
||||
return clean_mesh
|
||||
|
||||
def subdivide(self):
|
||||
if self.requires_grad:
|
||||
lrm.debug("Mesh is differentiable, not performing subdivision")
|
||||
return self
|
||||
|
||||
import trimesh
|
||||
|
||||
mesh = trimesh.Trimesh(
|
||||
vertices=self.v_pos.detach().cpu().numpy(),
|
||||
faces=self.t_pos_idx.detach().cpu().numpy(),
|
||||
)
|
||||
|
||||
mesh.subdivide_loop()
|
||||
|
||||
v_pos = torch.from_numpy(mesh.vertices).to(self.v_pos)
|
||||
t_pos_idx = torch.from_numpy(mesh.faces).to(self.t_pos_idx)
|
||||
|
||||
subdivided_mesh = Mesh(v_pos, t_pos_idx)
|
||||
|
||||
if len(self.extras) > 0:
|
||||
subdivided_mesh.extras = self.extras
|
||||
lrm.debug(
|
||||
f"The following extra attributes are inherited from the original mesh unchanged: {list(self.extras.keys())}"
|
||||
)
|
||||
|
||||
return subdivided_mesh
|
||||
|
||||
def post_process(self, options):
|
||||
if self.requires_grad:
|
||||
lrm.debug("Mesh is differentiable, not performing post processing")
|
||||
return self
|
||||
|
||||
from extern.mesh_process.MeshProcess import process_mesh
|
||||
|
||||
v_pos, t_pos_idx = process_mesh(
|
||||
vertices=self.v_pos.detach().cpu().numpy(),
|
||||
faces=self.t_pos_idx.detach().cpu().numpy(),
|
||||
**options,
|
||||
)
|
||||
|
||||
v_pos = torch.from_numpy(v_pos).to(self.v_pos).contiguous()
|
||||
t_pos_idx = torch.from_numpy(t_pos_idx).to(self.t_pos_idx).contiguous()
|
||||
|
||||
processed_mesh = Mesh(v_pos, t_pos_idx)
|
||||
|
||||
if len(self.extras) > 0:
|
||||
processed_mesh.extras = self.extras
|
||||
lrm.debug(
|
||||
f"The following extra attributes are inherited from the original mesh unchanged: {list(self.extras.keys())}"
|
||||
)
|
||||
|
||||
return processed_mesh
|
||||
|
||||
@property
|
||||
def requires_grad(self):
|
||||
return self.v_pos.requires_grad
|
||||
|
||||
@property
|
||||
def v_nrm(self):
|
||||
if self._v_nrm is None:
|
||||
self._v_nrm = self._compute_vertex_normal()
|
||||
return self._v_nrm
|
||||
|
||||
@property
|
||||
def v_tng(self):
|
||||
if self._v_tng is None:
|
||||
self._v_tng = self._compute_vertex_tangent()
|
||||
return self._v_tng
|
||||
|
||||
@property
|
||||
def v_tex(self):
|
||||
if self._v_tex is None:
|
||||
self._v_tex, self._t_tex_idx = self._unwrap_uv()
|
||||
return self._v_tex
|
||||
|
||||
@property
|
||||
def t_tex_idx(self):
|
||||
if self._t_tex_idx is None:
|
||||
self._v_tex, self._t_tex_idx = self._unwrap_uv()
|
||||
return self._t_tex_idx
|
||||
|
||||
@property
|
||||
def v_rgb(self):
|
||||
return self._v_rgb
|
||||
|
||||
@property
|
||||
def edges(self):
|
||||
if self._edges is None:
|
||||
self._edges = self._compute_edges()
|
||||
return self._edges
|
||||
|
||||
def _compute_vertex_normal(self):
|
||||
i0 = self.t_pos_idx[:, 0]
|
||||
i1 = self.t_pos_idx[:, 1]
|
||||
i2 = self.t_pos_idx[:, 2]
|
||||
|
||||
v0 = self.v_pos[i0, :]
|
||||
v1 = self.v_pos[i1, :]
|
||||
v2 = self.v_pos[i2, :]
|
||||
|
||||
face_normals = torch.cross(v1 - v0, v2 - v0)
|
||||
|
||||
# Splat face normals to vertices
|
||||
v_nrm = torch.zeros_like(self.v_pos)
|
||||
v_nrm.scatter_add_(0, i0[:, None].repeat(1, 3), face_normals)
|
||||
v_nrm.scatter_add_(0, i1[:, None].repeat(1, 3), face_normals)
|
||||
v_nrm.scatter_add_(0, i2[:, None].repeat(1, 3), face_normals)
|
||||
|
||||
# Normalize, replace zero (degenerated) normals with some default value
|
||||
v_nrm = torch.where(
|
||||
dot(v_nrm, v_nrm) > 1e-20, v_nrm, torch.as_tensor([0.0, 0.0, 1.0]).to(v_nrm)
|
||||
)
|
||||
v_nrm = F.normalize(v_nrm, dim=1)
|
||||
|
||||
if torch.is_anomaly_enabled():
|
||||
assert torch.all(torch.isfinite(v_nrm))
|
||||
|
||||
return v_nrm
|
||||
|
||||
def _compute_vertex_tangent(self):
|
||||
vn_idx = [None] * 3
|
||||
pos = [None] * 3
|
||||
tex = [None] * 3
|
||||
for i in range(0, 3):
|
||||
pos[i] = self.v_pos[self.t_pos_idx[:, i]]
|
||||
tex[i] = self.v_tex[self.t_tex_idx[:, i]]
|
||||
# t_nrm_idx is always the same as t_pos_idx
|
||||
vn_idx[i] = self.t_pos_idx[:, i]
|
||||
|
||||
tangents = torch.zeros_like(self.v_nrm)
|
||||
tansum = torch.zeros_like(self.v_nrm)
|
||||
|
||||
# Compute tangent space for each triangle
|
||||
uve1 = tex[1] - tex[0]
|
||||
uve2 = tex[2] - tex[0]
|
||||
pe1 = pos[1] - pos[0]
|
||||
pe2 = pos[2] - pos[0]
|
||||
|
||||
nom = pe1 * uve2[..., 1:2] - pe2 * uve1[..., 1:2]
|
||||
denom = uve1[..., 0:1] * uve2[..., 1:2] - uve1[..., 1:2] * uve2[..., 0:1]
|
||||
|
||||
# Avoid division by zero for degenerated texture coordinates
|
||||
tang = nom / torch.where(
|
||||
denom > 0.0, torch.clamp(denom, min=1e-6), torch.clamp(denom, max=-1e-6)
|
||||
)
|
||||
|
||||
# Update all 3 vertices
|
||||
for i in range(0, 3):
|
||||
idx = vn_idx[i][:, None].repeat(1, 3)
|
||||
tangents.scatter_add_(0, idx, tang) # tangents[n_i] = tangents[n_i] + tang
|
||||
tansum.scatter_add_(
|
||||
0, idx, torch.ones_like(tang)
|
||||
) # tansum[n_i] = tansum[n_i] + 1
|
||||
tangents = tangents / tansum
|
||||
|
||||
# Normalize and make sure tangent is perpendicular to normal
|
||||
tangents = F.normalize(tangents, dim=1)
|
||||
tangents = F.normalize(tangents - dot(tangents, self.v_nrm) * self.v_nrm)
|
||||
|
||||
if torch.is_anomaly_enabled():
|
||||
assert torch.all(torch.isfinite(tangents))
|
||||
|
||||
return tangents
|
||||
|
||||
def _unwrap_uv_open3d(
|
||||
self
|
||||
):
|
||||
import open3d as o3d
|
||||
mesh = o3d.t.geometry.TriangleMesh()
|
||||
mesh.vertex.positions = o3d.core.Tensor(self.v_pos.detach().cpu().numpy())
|
||||
mesh.triangle.indices = o3d.core.Tensor(self.t_pos_idx.cpu().numpy())
|
||||
mesh.compute_uvatlas(size=1024)
|
||||
texture_uvs = torch.from_numpy(mesh.triangle.texture_uvs.numpy()).reshape(-1, 2).cuda()
|
||||
indices = torch.arange(self.t_pos_idx.shape[0] * 3).reshape(-1, 3).to(torch.int64).cuda()
|
||||
# Add a wood texture and visualize
|
||||
return texture_uvs, indices
|
||||
|
||||
def _unwrap_uv_xatlas(
|
||||
self, xatlas_chart_options: dict = {}, xatlas_pack_options: dict = {}
|
||||
):
|
||||
lrm.info("Using xatlas to perform UV unwrapping, may take a while ...")
|
||||
|
||||
import xatlas
|
||||
|
||||
atlas = xatlas.Atlas()
|
||||
atlas.add_mesh(
|
||||
self.v_pos.detach().cpu().numpy(),
|
||||
self.t_pos_idx.cpu().numpy(),
|
||||
)
|
||||
co = xatlas.ChartOptions()
|
||||
po = xatlas.PackOptions()
|
||||
for k, v in xatlas_chart_options.items():
|
||||
setattr(co, k, v)
|
||||
for k, v in xatlas_pack_options.items():
|
||||
setattr(po, k, v)
|
||||
atlas.generate(co, po)
|
||||
vmapping, indices, uvs = atlas.get_mesh(0)
|
||||
vmapping = (
|
||||
torch.from_numpy(
|
||||
vmapping.astype(np.uint64, casting="same_kind").view(np.int64)
|
||||
)
|
||||
.to(self.v_pos.device)
|
||||
.long()
|
||||
)
|
||||
uvs = torch.from_numpy(uvs).to(self.v_pos.device).float()
|
||||
indices = (
|
||||
torch.from_numpy(
|
||||
indices.astype(np.uint64, casting="same_kind").view(np.int64)
|
||||
)
|
||||
.to(self.v_pos.device)
|
||||
.long()
|
||||
)
|
||||
return uvs, indices
|
||||
|
||||
def _unwrap_uv_smartuv(self, options: dict = {}):
|
||||
from extern.mesh_process.MeshProcess import (
|
||||
mesh_to_bpy,
|
||||
get_uv_from_bpy,
|
||||
bpy_context,
|
||||
bpy_export,
|
||||
)
|
||||
from CharacterGen.Stage_3D.lrm.utils.misc import time_recorder as tr
|
||||
|
||||
v_pos, t_pos_idx = self.v_pos.cpu().numpy(), self.t_pos_idx.cpu().numpy()
|
||||
with bpy_context():
|
||||
mesh_bpy = mesh_to_bpy("_", v_pos, t_pos_idx)
|
||||
v_tex = get_uv_from_bpy(mesh_bpy, **options).astype(np.float32)
|
||||
|
||||
assert v_tex.shape[0] == self.t_pos_idx.shape[0] * 3
|
||||
|
||||
t_tex_idx = torch.arange(
|
||||
self.t_pos_idx.shape[0] * 3, device=self.t_pos_idx.device, dtype=torch.long
|
||||
).reshape(-1, 3)
|
||||
|
||||
"""
|
||||
# super efficient de-duplication
|
||||
v_tex_u_uint32 = v_tex[..., 0].view(np.uint32)
|
||||
v_tex_v_uint32 = v_tex[..., 1].view(np.uint32)
|
||||
v_hashed = (v_tex_u_uint32.astype(np.uint64) << 32) | v_tex_v_uint32
|
||||
v_hashed = torch.from_numpy(v_hashed.view(np.int64)).to(self.v_pos.device)
|
||||
|
||||
v_tex = torch.from_numpy(v_tex).to(
|
||||
device=self.v_pos.device, dtype=torch.float32
|
||||
)
|
||||
t_pos_idx_f3 = torch.arange(
|
||||
self.t_pos_idx.shape[0] * 3, device=self.t_pos_idx.device, dtype=torch.long
|
||||
).reshape(-1, 3)
|
||||
v_pos_f3 = self.v_pos[self.t_pos_idx].reshape(-1, 3)
|
||||
|
||||
# super efficient de-duplication
|
||||
v_hashed_dedup, inverse_indices = torch.unique(v_hashed, return_inverse=True)
|
||||
dedup_size, full_size = v_hashed_dedup.shape[0], inverse_indices.shape[0]
|
||||
indices = torch.scatter_reduce(
|
||||
torch.full(
|
||||
[dedup_size],
|
||||
fill_value=full_size,
|
||||
device=inverse_indices.device,
|
||||
dtype=torch.long,
|
||||
),
|
||||
index=inverse_indices,
|
||||
src=torch.arange(
|
||||
full_size, device=inverse_indices.device, dtype=torch.int64
|
||||
),
|
||||
dim=0,
|
||||
reduce="amin",
|
||||
)
|
||||
v_tex = v_tex[indices]
|
||||
t_tex_idx = inverse_indices.reshape(-1, 3)
|
||||
|
||||
v_pos = v_pos_f3[indices]
|
||||
t_pos_idx = inverse_indices[t_pos_idx_f3]
|
||||
"""
|
||||
|
||||
return self.v_pos, self.t_pos_idx, v_tex, t_tex_idx
|
||||
|
||||
def unwrap_uv(
|
||||
self,
|
||||
method: str,
|
||||
xatlas_chart_options: dict = {},
|
||||
xatlas_pack_options: dict = {},
|
||||
smartuv_options: dict = {},
|
||||
):
|
||||
if method == "xatlas":
|
||||
with time_recorder_enabled():
|
||||
tr.start("UV unwrapping xatlas")
|
||||
self._v_tex, self._t_tex_idx = self._unwrap_uv_xatlas(
|
||||
xatlas_chart_options, xatlas_pack_options
|
||||
)
|
||||
tr.end("UV unwrapping xatlas")
|
||||
elif method == "open3d":
|
||||
with time_recorder_enabled():
|
||||
tr.start("UV unwrapping o3d")
|
||||
self._v_tex, self._t_tex_idx = self._unwrap_uv_open3d()
|
||||
tr.end("UV unwrapping o3d")
|
||||
elif method == "smartuv":
|
||||
with time_recorder_enabled():
|
||||
tr.start("UV unwrapping smartuv")
|
||||
(
|
||||
self.v_pos,
|
||||
self.t_pos_idx,
|
||||
self._v_tex,
|
||||
self._t_tex_idx,
|
||||
) = self._unwrap_uv_smartuv(smartuv_options)
|
||||
tr.end("UV unwrapping smartuv")
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def set_vertex_color(self, v_rgb):
|
||||
assert v_rgb.shape[0] == self.v_pos.shape[0]
|
||||
self._v_rgb = v_rgb
|
||||
|
||||
def set_uv(self, v_tex, t_tex_idx):
|
||||
self._v_tex = v_tex
|
||||
self._t_tex_idx = t_tex_idx
|
||||
|
||||
@property
|
||||
def has_uv(self):
|
||||
return self._v_tex is not None and self._t_tex_idx is not None
|
||||
|
||||
def _compute_edges(self):
|
||||
# Compute edges
|
||||
edges = torch.cat(
|
||||
[
|
||||
self.t_pos_idx[:, [0, 1]],
|
||||
self.t_pos_idx[:, [1, 2]],
|
||||
self.t_pos_idx[:, [2, 0]],
|
||||
],
|
||||
dim=0,
|
||||
)
|
||||
edges = edges.sort()[0]
|
||||
edges = torch.unique(edges, dim=0)
|
||||
return edges
|
||||
|
||||
def normal_consistency(self) -> Float[Tensor, ""]:
|
||||
edge_nrm: Float[Tensor, "Ne 2 3"] = self.v_nrm[self.edges]
|
||||
nc = (
|
||||
1.0 - torch.cosine_similarity(edge_nrm[:, 0], edge_nrm[:, 1], dim=-1)
|
||||
).mean()
|
||||
return nc
|
||||
|
||||
def _laplacian_uniform(self):
|
||||
# from stable-dreamfusion
|
||||
# https://github.com/ashawkey/stable-dreamfusion/blob/8fb3613e9e4cd1ded1066b46e80ca801dfb9fd06/nerf/renderer.py#L224
|
||||
verts, faces = self.v_pos, self.t_pos_idx
|
||||
|
||||
V = verts.shape[0]
|
||||
F = faces.shape[0]
|
||||
|
||||
# Neighbor indices
|
||||
ii = faces[:, [1, 2, 0]].flatten()
|
||||
jj = faces[:, [2, 0, 1]].flatten()
|
||||
adj = torch.stack([torch.cat([ii, jj]), torch.cat([jj, ii])], dim=0).unique(
|
||||
dim=1
|
||||
)
|
||||
adj_values = torch.ones(adj.shape[1]).to(verts)
|
||||
|
||||
# Diagonal indices
|
||||
diag_idx = adj[0]
|
||||
|
||||
# Build the sparse matrix
|
||||
idx = torch.cat((adj, torch.stack((diag_idx, diag_idx), dim=0)), dim=1)
|
||||
values = torch.cat((-adj_values, adj_values))
|
||||
|
||||
# The coalesce operation sums the duplicate indices, resulting in the
|
||||
# correct diagonal
|
||||
return torch.sparse_coo_tensor(idx, values, (V, V)).coalesce()
|
||||
|
||||
def laplacian(self) -> Float[Tensor, ""]:
|
||||
with torch.no_grad():
|
||||
L = self._laplacian_uniform()
|
||||
loss = L.mm(self.v_pos)
|
||||
loss = loss.norm(dim=1)
|
||||
loss = loss.mean()
|
||||
return loss
|
||||
@@ -0,0 +1,390 @@
|
||||
from dataclasses import dataclass, field
|
||||
from copy import deepcopy
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from einops import rearrange
|
||||
|
||||
from ..utils.base import BaseModule
|
||||
from ..utils.ops import get_activation
|
||||
from ..utils.typing import *
|
||||
|
||||
|
||||
class TriplaneUpsampleNetwork(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
in_channels: int = 1024
|
||||
out_channels: int = 80
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.upsample = nn.ConvTranspose2d(
|
||||
self.cfg.in_channels, self.cfg.out_channels, kernel_size=2, stride=2
|
||||
)
|
||||
|
||||
def forward(
|
||||
self, triplanes: Float[Tensor, "B 3 Ci Hp Wp"]
|
||||
) -> Float[Tensor, "B 3 Co Hp2 Wp2"]:
|
||||
triplanes_up = rearrange(
|
||||
self.upsample(
|
||||
rearrange(triplanes, "B Np Ci Hp Wp -> (B Np) Ci Hp Wp", Np=3)
|
||||
),
|
||||
"(B Np) Co Hp Wp -> B Np Co Hp Wp",
|
||||
Np=3,
|
||||
)
|
||||
return triplanes_up
|
||||
|
||||
|
||||
class MLP(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim_in: int,
|
||||
dim_out: int,
|
||||
n_neurons: int,
|
||||
n_hidden_layers: int,
|
||||
activation: str = "relu",
|
||||
output_activation: Optional[str] = None,
|
||||
bias: bool = True,
|
||||
weight_init: Optional[str] = "kaiming_uniform",
|
||||
bias_init: Optional[str] = None,
|
||||
):
|
||||
super().__init__()
|
||||
layers = [
|
||||
self.make_linear(
|
||||
dim_in,
|
||||
n_neurons,
|
||||
is_first=True,
|
||||
is_last=False,
|
||||
bias=bias,
|
||||
weight_init=weight_init,
|
||||
bias_init=bias_init,
|
||||
),
|
||||
self.make_activation(activation),
|
||||
]
|
||||
for i in range(n_hidden_layers - 1):
|
||||
layers += [
|
||||
self.make_linear(
|
||||
n_neurons,
|
||||
n_neurons,
|
||||
is_first=False,
|
||||
is_last=False,
|
||||
bias=bias,
|
||||
weight_init=weight_init,
|
||||
bias_init=bias_init,
|
||||
),
|
||||
self.make_activation(activation),
|
||||
]
|
||||
layers += [
|
||||
self.make_linear(
|
||||
n_neurons,
|
||||
dim_out,
|
||||
is_first=False,
|
||||
is_last=True,
|
||||
bias=bias,
|
||||
weight_init=weight_init,
|
||||
bias_init=bias_init,
|
||||
)
|
||||
]
|
||||
self.layers = nn.Sequential(*layers)
|
||||
self.output_activation = get_activation(output_activation)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.layers(x)
|
||||
x = self.output_activation(x)
|
||||
return x
|
||||
|
||||
def make_linear(
|
||||
self,
|
||||
dim_in,
|
||||
dim_out,
|
||||
is_first,
|
||||
is_last,
|
||||
bias=True,
|
||||
weight_init=None,
|
||||
bias_init=None,
|
||||
):
|
||||
layer = nn.Linear(dim_in, dim_out, bias=bias)
|
||||
|
||||
if weight_init is None:
|
||||
pass
|
||||
elif weight_init == "kaiming_uniform":
|
||||
torch.nn.init.kaiming_uniform_(layer.weight, nonlinearity="relu")
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if bias:
|
||||
if bias_init is None:
|
||||
pass
|
||||
elif bias_init == "zero":
|
||||
torch.nn.init.zeros_(layer.bias)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
return layer
|
||||
|
||||
def make_activation(self, activation):
|
||||
if activation == "relu":
|
||||
return nn.ReLU(inplace=True)
|
||||
elif activation == "silu":
|
||||
return nn.SiLU(inplace=True)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclass
|
||||
class HeadSpec:
|
||||
name: str
|
||||
out_channels: int
|
||||
n_hidden_layers: int
|
||||
output_activation: Optional[str] = None
|
||||
|
||||
|
||||
class MultiHeadMLP(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
in_channels: int = 0
|
||||
n_neurons: int = 0
|
||||
n_hidden_layers_share: int = 0
|
||||
heads: List[HeadSpec] = field(default_factory=lambda: [])
|
||||
activation: str = "relu"
|
||||
bias: bool = True
|
||||
weight_init: Optional[str] = "kaiming_uniform"
|
||||
bias_init: Optional[str] = None
|
||||
chunk_mode: Optional[str] = None
|
||||
chunk_size: int = -1
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
shared_layers = [
|
||||
self.make_linear(
|
||||
self.cfg.in_channels,
|
||||
self.cfg.n_neurons,
|
||||
bias=self.cfg.bias,
|
||||
weight_init=self.cfg.weight_init,
|
||||
bias_init=self.cfg.bias_init,
|
||||
),
|
||||
self.make_activation(self.cfg.activation),
|
||||
]
|
||||
for i in range(self.cfg.n_hidden_layers_share - 1):
|
||||
shared_layers += [
|
||||
self.make_linear(
|
||||
self.cfg.n_neurons,
|
||||
self.cfg.n_neurons,
|
||||
bias=self.cfg.bias,
|
||||
weight_init=self.cfg.weight_init,
|
||||
bias_init=self.cfg.bias_init,
|
||||
),
|
||||
self.make_activation(self.cfg.activation),
|
||||
]
|
||||
self.shared_layers = nn.Sequential(*shared_layers)
|
||||
|
||||
assert len(self.cfg.heads) > 0
|
||||
heads = {}
|
||||
for head in self.cfg.heads:
|
||||
head_layers = []
|
||||
for i in range(head.n_hidden_layers):
|
||||
head_layers += [
|
||||
self.make_linear(
|
||||
self.cfg.n_neurons,
|
||||
self.cfg.n_neurons,
|
||||
bias=self.cfg.bias,
|
||||
weight_init=self.cfg.weight_init,
|
||||
bias_init=self.cfg.bias_init,
|
||||
),
|
||||
self.make_activation(self.cfg.activation),
|
||||
]
|
||||
head_layers += [
|
||||
self.make_linear(
|
||||
self.cfg.n_neurons,
|
||||
head.out_channels,
|
||||
bias=self.cfg.bias,
|
||||
weight_init=self.cfg.weight_init,
|
||||
bias_init=self.cfg.bias_init,
|
||||
),
|
||||
]
|
||||
heads[head.name] = nn.Sequential(*head_layers)
|
||||
self.heads = nn.ModuleDict(heads)
|
||||
|
||||
if self.cfg.chunk_mode is not None:
|
||||
assert self.cfg.chunk_size > 0
|
||||
|
||||
def make_linear(
|
||||
self,
|
||||
dim_in,
|
||||
dim_out,
|
||||
bias=True,
|
||||
weight_init=None,
|
||||
bias_init=None,
|
||||
):
|
||||
layer = nn.Linear(dim_in, dim_out, bias=bias)
|
||||
|
||||
if weight_init is None:
|
||||
pass
|
||||
elif weight_init == "kaiming_uniform":
|
||||
torch.nn.init.kaiming_uniform_(layer.weight, nonlinearity="relu")
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if bias:
|
||||
if bias_init is None:
|
||||
pass
|
||||
elif bias_init == "zero":
|
||||
torch.nn.init.zeros_(layer.bias)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
return layer
|
||||
|
||||
def make_activation(self, activation):
|
||||
if activation == "relu":
|
||||
return nn.ReLU(inplace=True)
|
||||
elif activation == "silu":
|
||||
return nn.SiLU(inplace=True)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def forward(
|
||||
self, x, include: Optional[List] = None, exclude: Optional[List] = None
|
||||
):
|
||||
inp_shape = x.shape[:-1]
|
||||
x = x.reshape(-1, x.shape[-1])
|
||||
|
||||
if self.cfg.chunk_mode is None:
|
||||
shared_features = self.shared_layers(x)
|
||||
elif self.cfg.chunk_mode == "deferred":
|
||||
shared_features = DeferredFunc.apply(
|
||||
self.shared_layers, x, self.cfg.chunk_size
|
||||
)
|
||||
elif self.cfg.chunk_mode == "checkpointing":
|
||||
shared_features = apply_batch_checkpointing(
|
||||
self.shared_layers, x, self.cfg.chunk_size
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
shared_features = shared_features.reshape(*inp_shape, -1)
|
||||
|
||||
if include is not None and exclude is not None:
|
||||
raise ValueError("Cannot specify both include and exclude.")
|
||||
if include is not None:
|
||||
heads = [h for h in self.cfg.heads if h.name in include]
|
||||
elif exclude is not None:
|
||||
heads = [h for h in self.cfg.heads if h.name not in exclude]
|
||||
else:
|
||||
heads = self.cfg.heads
|
||||
|
||||
out = {
|
||||
head.name: get_activation(head.output_activation)(
|
||||
self.heads[head.name](shared_features)
|
||||
)
|
||||
for head in heads
|
||||
}
|
||||
"""
|
||||
# TypeError
|
||||
if self.cfg.chunk_mode is None:
|
||||
out = {
|
||||
head.name: get_activation(head.output_activation)(
|
||||
self.heads[head.name](shared_features)
|
||||
)
|
||||
for head in heads
|
||||
}
|
||||
elif self.cfg.chunk_mode == "deferred":
|
||||
out = {
|
||||
head.name: get_activation(head.output_activation)(
|
||||
DeferredFunc.apply(self.heads[head.name], shared_features, self.cfg.chunk_size)
|
||||
)
|
||||
for head in heads
|
||||
}
|
||||
else:
|
||||
raise NotImplementedError
|
||||
"""
|
||||
return out
|
||||
|
||||
|
||||
class DeferredFunc(torch.autograd.Function):
|
||||
# Note that forward, setup_context, and backward are @staticmethods
|
||||
@staticmethod
|
||||
def forward(ctx, model, x, chunk_size):
|
||||
model_copy = deepcopy(model)
|
||||
model_copy.requires_grad_(False)
|
||||
|
||||
ret = []
|
||||
x_split = torch.split(x, chunk_size, dim=0)
|
||||
|
||||
with torch.no_grad():
|
||||
for cur_x in x_split:
|
||||
ret.append(model_copy(cur_x))
|
||||
|
||||
ctx.model = model
|
||||
ctx.save_for_backward(x.detach(), torch.as_tensor(chunk_size))
|
||||
|
||||
ret = torch.cat(ret, dim=0)
|
||||
|
||||
return ret
|
||||
|
||||
# This function has only a single output, so it gets only one gradient
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
model = ctx.model
|
||||
x, chunk_size = ctx.saved_tensors
|
||||
chunk_size = chunk_size.item()
|
||||
|
||||
model_copy = deepcopy(model)
|
||||
|
||||
x_split = torch.split(x, chunk_size, dim=0)
|
||||
grad_output_split = torch.split(grad_output, chunk_size, 0)
|
||||
grad_input_split = []
|
||||
|
||||
with torch.set_grad_enabled(True):
|
||||
model_copy.requires_grad_(True)
|
||||
model_copy.zero_grad()
|
||||
for cur_x, cur_grad_output in zip(x_split, grad_output_split):
|
||||
cur_x.requires_grad_(True)
|
||||
cur_y = model_copy(cur_x)
|
||||
cur_y.backward(cur_grad_output)
|
||||
|
||||
grad_input_split.append(cur_x.grad.clone())
|
||||
|
||||
grad_input = torch.cat(grad_input_split, dim=0)
|
||||
|
||||
model_copy_params = list(model_copy.parameters())
|
||||
model_params = list(model.parameters())
|
||||
|
||||
for param, param_copy in zip(model_params, model_copy_params):
|
||||
if param.grad is None:
|
||||
param.grad = param_copy.grad.clone()
|
||||
else:
|
||||
param.grad.add_(param_copy.grad)
|
||||
|
||||
return None, grad_input, None
|
||||
|
||||
|
||||
def apply_batch_checkpointing(func, x, chunk_size):
|
||||
if chunk_size >= len(x):
|
||||
# return func(x)
|
||||
return torch.utils.checkpoint.checkpoint(func, x, use_reentrant=False)
|
||||
|
||||
x_split = torch.split(x, chunk_size, dim=0)
|
||||
|
||||
def cat_and_query(y_all, x):
|
||||
return torch.cat([y_all, func(x)])
|
||||
|
||||
y_all = func(x_split[0])
|
||||
for cur_x in x_split[1:]:
|
||||
y_all = torch.utils.checkpoint.checkpoint(
|
||||
cat_and_query, y_all, cur_x, use_reentrant=False
|
||||
)
|
||||
|
||||
return y_all
|
||||
|
||||
|
||||
def get_encoding(n_input_dims: int, config) -> nn.Module:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def get_mlp(n_input_dims, n_output_dims, config) -> nn.Module:
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,66 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
|
||||
from ..networks import MultiHeadMLP
|
||||
from ..background.base import BaseBackground
|
||||
from ..materials.base import BaseMaterial
|
||||
from ...utils.base import BaseModule
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class BaseRenderer(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
radius: float = 1.0
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(
|
||||
self,
|
||||
decoder: MultiHeadMLP,
|
||||
material: BaseMaterial,
|
||||
background: BaseBackground,
|
||||
) -> None:
|
||||
super().configure()
|
||||
|
||||
self.set_decoder(decoder)
|
||||
self.set_material(material)
|
||||
self.set_background(background)
|
||||
|
||||
# set up bounding box
|
||||
self.bbox: Float[Tensor, "2 3"]
|
||||
self.register_buffer(
|
||||
"bbox",
|
||||
torch.as_tensor(
|
||||
[
|
||||
[-self.cfg.radius, -self.cfg.radius, -self.cfg.radius],
|
||||
[self.cfg.radius, self.cfg.radius, self.cfg.radius],
|
||||
],
|
||||
dtype=torch.float32,
|
||||
),
|
||||
)
|
||||
|
||||
def forward(self, *args, **kwargs) -> Dict[str, Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def decoder(self) -> MultiHeadMLP:
|
||||
return self.non_module("decoder")
|
||||
|
||||
@property
|
||||
def material(self) -> BaseMaterial:
|
||||
return self.non_module("material")
|
||||
|
||||
@property
|
||||
def background(self) -> BaseBackground:
|
||||
return self.non_module("background")
|
||||
|
||||
def set_decoder(self, decoder: MultiHeadMLP) -> None:
|
||||
self.register_non_module("decoder", decoder)
|
||||
|
||||
def set_material(self, material: BaseMaterial) -> None:
|
||||
self.register_non_module("material", material)
|
||||
|
||||
def set_background(self, background: BaseBackground) -> None:
|
||||
self.register_non_module("background", background)
|
||||
@@ -0,0 +1,369 @@
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from einops import rearrange, reduce
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from ..renderers.base import BaseRenderer
|
||||
from ..isosurface import MarchingTetrahedraHelper
|
||||
from ...utils.ops import (
|
||||
get_activation,
|
||||
scale_tensor,
|
||||
dot,
|
||||
chunk_batch
|
||||
)
|
||||
from ...utils.rasterize import NVDiffRasterizerContext
|
||||
from ..mesh import Mesh
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class TriplaneDMTetRenderer(BaseRenderer):
|
||||
@dataclass
|
||||
class Config(BaseRenderer.Config):
|
||||
feature_reduction: str = "concat"
|
||||
sdf_activation: Optional[str] = None
|
||||
sdf_bias: Union[str, float] = 0.0
|
||||
sdf_bias_params: Any = None
|
||||
inside_out: bool = False
|
||||
|
||||
isosurface_resolution: int = 256
|
||||
tet_dir: str = "tets/"
|
||||
enable_isosurface_grid_deformation: bool = False
|
||||
eval_chunk_size: int = 262144
|
||||
context_type: str = "gl"
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self, *args, **kwargs) -> None:
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
assert self.cfg.feature_reduction in ["concat", "mean"]
|
||||
|
||||
self.ctx = NVDiffRasterizerContext(self.cfg.context_type, self.device)
|
||||
self.isosurface_helper = MarchingTetrahedraHelper(
|
||||
self.cfg.isosurface_resolution,
|
||||
os.path.join(self.cfg.tet_dir, f"{self.cfg.isosurface_resolution}_tets.npz"),
|
||||
).to(self.device)
|
||||
|
||||
def query_triplane(
|
||||
self,
|
||||
positions: Float[Tensor, "*B N 3"],
|
||||
triplanes: Float[Tensor, "*B 3 Cp Hp Wp"],
|
||||
) -> Dict[str, Tensor]:
|
||||
batched = positions.ndim == 3
|
||||
if not batched:
|
||||
# no batch dimension
|
||||
triplanes = triplanes[None, ...]
|
||||
positions = positions[None, ...]
|
||||
# import ipdb
|
||||
# ipdb.set_trace()
|
||||
assert triplanes.ndim == 5 and positions.ndim == 3
|
||||
|
||||
# assume positions in [-1, 1]
|
||||
# normalized to (-1, 1) for grid sample
|
||||
positions = scale_tensor(
|
||||
positions, (-self.cfg.radius, self.cfg.radius), (-1, 1)
|
||||
)
|
||||
|
||||
indices2D: Float[Tensor, "B 3 N 2"] = torch.stack(
|
||||
(positions[..., [0, 1]], positions[..., [0, 2]], positions[..., [1, 2]]),
|
||||
dim=-3,
|
||||
)
|
||||
out: Float[Tensor, "B3 Cp 1 N"] = F.grid_sample(
|
||||
rearrange(triplanes, "B Np Cp Hp Wp -> (B Np) Cp Hp Wp", Np=3),
|
||||
rearrange(indices2D, "B Np N Nd -> (B Np) () N Nd", Np=3),
|
||||
align_corners=False,
|
||||
mode="bilinear",
|
||||
)
|
||||
if self.cfg.feature_reduction == "concat":
|
||||
out = rearrange(out, "(B Np) Cp () N -> B N (Np Cp)", Np=3)
|
||||
elif self.cfg.feature_reduction == "mean":
|
||||
out = reduce(out, "(B Np) Cp () N -> B N Cp", Np=3, reduction="mean")
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
net_out: Dict[str, Float[Tensor, "B N ..."]] = self.decoder(out)
|
||||
assert "sdf" in net_out
|
||||
net_out["sdf"] = get_activation(self.cfg.sdf_activation)(
|
||||
self.get_shifted_sdf(positions, net_out["sdf"])
|
||||
)
|
||||
|
||||
if not batched:
|
||||
net_out = {k: v.squeeze(0) for k, v in net_out.items()}
|
||||
|
||||
return net_out
|
||||
|
||||
def get_shifted_sdf(
|
||||
self, points: Float[Tensor, "*N Di"], sdf: Float[Tensor, "*N 1"]
|
||||
) -> Float[Tensor, "*N 1"]:
|
||||
sdf_bias: Union[float, Float[Tensor, "*N 1"]]
|
||||
if self.cfg.sdf_bias == "ellipsoid":
|
||||
assert (
|
||||
isinstance(self.cfg.sdf_bias_params, Sized)
|
||||
and len(self.cfg.sdf_bias_params) == 3
|
||||
)
|
||||
size = torch.as_tensor(self.cfg.sdf_bias_params).to(points)
|
||||
sdf_bias = ((points / size) ** 2).sum(
|
||||
dim=-1, keepdim=True
|
||||
).sqrt() - 1.0 # pseudo signed distance of an ellipsoid
|
||||
elif self.cfg.sdf_bias == "sphere":
|
||||
assert isinstance(self.cfg.sdf_bias_params, float)
|
||||
radius = self.cfg.sdf_bias_params
|
||||
sdf_bias = (points**2).sum(dim=-1, keepdim=True).sqrt() - radius
|
||||
elif isinstance(self.cfg.sdf_bias, float):
|
||||
sdf_bias = self.cfg.sdf_bias
|
||||
else:
|
||||
raise ValueError(f"Unknown sdf bias {self.cfg.sdf_bias}")
|
||||
return sdf + sdf_bias
|
||||
|
||||
def forward_single(
|
||||
self,
|
||||
triplane: Float[Tensor, "3 Cp Hp Wp"],
|
||||
mvp_mtx: Float[Tensor, "Nv 4 4"],
|
||||
camera_positions: Float[Tensor, "Nv 3"],
|
||||
height: int,
|
||||
width: int,
|
||||
background_color: Optional[Float[Tensor, "3"]],
|
||||
extra_sdf_query: Any = None,
|
||||
) -> Dict[str, Tensor]:
|
||||
Nv = mvp_mtx.shape[0]
|
||||
|
||||
out = {}
|
||||
|
||||
query_vertices = []
|
||||
query_sizes = []
|
||||
|
||||
grid_vertices = scale_tensor(
|
||||
self.isosurface_helper.grid_vertices,
|
||||
self.isosurface_helper.points_range,
|
||||
self.bbox,
|
||||
)
|
||||
|
||||
query_vertices.append(grid_vertices)
|
||||
query_sizes.append(len(grid_vertices))
|
||||
|
||||
if extra_sdf_query is not None:
|
||||
query_vertices.append(extra_sdf_query)
|
||||
query_sizes.append(len(extra_sdf_query))
|
||||
|
||||
query_vertices = torch.cat(query_vertices, dim=0)
|
||||
triplane_out = self.query_triplane(query_vertices, triplane)
|
||||
|
||||
all_sdf = triplane_out["sdf"]
|
||||
if extra_sdf_query is not None:
|
||||
sdf, sdf_ex_query = torch.split(all_sdf, query_sizes)
|
||||
else:
|
||||
sdf, sdf_ex_query = all_sdf, None
|
||||
|
||||
out.update({"sdf_ex_query": sdf_ex_query})
|
||||
|
||||
if self.cfg.enable_isosurface_grid_deformation:
|
||||
all_deformation = triplane_out["deformation"]
|
||||
if extra_sdf_query is not None:
|
||||
deformation, _ = torch.split(all_deformation, query_sizes)
|
||||
else:
|
||||
deformation, _ = all_deformation, None
|
||||
else:
|
||||
deformation = None
|
||||
|
||||
# Fix some sdf if we observe empty shape (full positive or full negative)
|
||||
pos_shape = torch.sum((sdf.squeeze(dim=-1) > 0).int(), dim=-1)
|
||||
neg_shape = torch.sum((sdf.squeeze(dim=-1) < 0).int(), dim=-1)
|
||||
zero_surface = torch.bitwise_or(pos_shape == 0, neg_shape == 0)
|
||||
if torch.sum(zero_surface).item() > 0:
|
||||
lrm.warn("Empty mesh! Fixing by adding fake faces.")
|
||||
sdf = torch.nan_to_num(sdf, nan=0.0, posinf=1.0, neginf=-1.0)
|
||||
update_sdf = torch.zeros_like(sdf)
|
||||
max_sdf = sdf.max()
|
||||
min_sdf = sdf.min()
|
||||
update_sdf[self.isosurface_helper.center_indices] += (
|
||||
-1.0 - max_sdf
|
||||
) # greater than zero
|
||||
update_sdf[self.isosurface_helper.boundary_indices] += (
|
||||
1.0 - min_sdf
|
||||
) # smaller than zero
|
||||
new_sdf = sdf.clone().detach()
|
||||
if zero_surface:
|
||||
new_sdf += update_sdf
|
||||
update_mask = (update_sdf == 0).float()
|
||||
# Regulraization here is used to push the sdf to be a different sign (make it not fully positive or fully negative)
|
||||
sdf_reg_loss = sdf.abs().mean()
|
||||
sdf_reg_loss = sdf_reg_loss * zero_surface.float()
|
||||
sdf = sdf * update_mask + new_sdf * (1 - update_mask)
|
||||
lrm.debug(
|
||||
"max sdf: {}, min sdf: {}".format(sdf.max().item(), sdf.min().item())
|
||||
)
|
||||
out.update({"sdf_reg": sdf_reg_loss})
|
||||
|
||||
# Here we remove the gradient for the bad sdf (full positive or full negative)
|
||||
if zero_surface:
|
||||
sdf = sdf.detach()
|
||||
|
||||
mesh: Mesh = self.isosurface_helper(sdf, deformation=deformation)
|
||||
|
||||
mesh.v_pos = scale_tensor(
|
||||
mesh.v_pos, self.isosurface_helper.points_range, self.bbox
|
||||
) # scale to bbox as the grid vertices are in [0, 1]
|
||||
# import ipdb
|
||||
# ipdb.set_trace()
|
||||
v_pos_clip: Float[Tensor, "Nv V 4"] = self.ctx.vertex_transform(
|
||||
mesh.v_pos, mvp_mtx
|
||||
)
|
||||
rast, _ = self.ctx.rasterize(v_pos_clip, mesh.t_pos_idx, (height, width))
|
||||
mask = rast[..., 3:] > 0
|
||||
mask_aa = self.ctx.antialias(mask.float(), rast, v_pos_clip, mesh.t_pos_idx)
|
||||
|
||||
out.update({"opacity": mask_aa, "mesh": mesh})
|
||||
|
||||
gb_normal, _ = self.ctx.interpolate_one(mesh.v_nrm, rast, mesh.t_pos_idx)
|
||||
gb_normal = F.normalize(gb_normal, dim=-1)
|
||||
gb_normal_aa = torch.lerp(
|
||||
torch.zeros_like(gb_normal), (gb_normal + 1.0) / 2.0, mask.float()
|
||||
)
|
||||
gb_normal_aa = self.ctx.antialias(
|
||||
gb_normal_aa, rast, v_pos_clip, mesh.t_pos_idx
|
||||
)
|
||||
out.update({"comp_normal": gb_normal_aa}) # in [0, 1]
|
||||
|
||||
gb_pos, _ = self.ctx.interpolate_one(mesh.v_pos, rast, mesh.t_pos_idx)
|
||||
|
||||
# FIXME: this depth corresponds to the one provided in the dataset, but assumes camera looking at scene center
|
||||
gb_depth = dot(
|
||||
gb_pos - camera_positions[:, None, None, :],
|
||||
F.normalize(-camera_positions[:, None, None, :], dim=-1),
|
||||
)
|
||||
|
||||
gb_depth = torch.lerp(torch.zeros_like(gb_depth), gb_depth, mask.float())
|
||||
out.update({"depth": gb_depth})
|
||||
|
||||
gb_viewdirs = F.normalize(gb_pos - camera_positions[:, None, None, :], dim=-1)
|
||||
gb_rgb_fg = torch.zeros(
|
||||
(Nv, height, width, 3), device=self.device, dtype=torch.float32
|
||||
)
|
||||
gb_rgb_bg = self.background(dirs=gb_viewdirs, color_spec=background_color)
|
||||
|
||||
selector = mask[..., 0]
|
||||
if selector.sum() > 0:
|
||||
positions = gb_pos[selector]
|
||||
geo_out = self.query_triplane(positions, triplane)
|
||||
|
||||
extra_geo_info = {}
|
||||
if self.material.requires_normal:
|
||||
extra_geo_info["shading_normal"] = gb_normal[selector]
|
||||
if self.material.requires_tangent:
|
||||
gb_tangent, _ = self.ctx.interpolate_one(
|
||||
mesh.v_tng, rast, mesh.t_pos_idx
|
||||
)
|
||||
gb_tangent = F.normalize(gb_tangent, dim=-1)
|
||||
extra_geo_info["tangent"] = gb_tangent[selector]
|
||||
|
||||
rgb_fg = self.material(
|
||||
viewdirs=gb_viewdirs[selector],
|
||||
positions=positions,
|
||||
**extra_geo_info,
|
||||
**geo_out,
|
||||
)
|
||||
|
||||
gb_rgb_fg[selector] = rgb_fg.to(
|
||||
gb_rgb_fg.dtype
|
||||
) # TODO: don't know if this is correct
|
||||
|
||||
gb_rgb = torch.lerp(gb_rgb_bg, gb_rgb_fg, mask.float())
|
||||
gb_rgb_aa = self.ctx.antialias(gb_rgb, rast, v_pos_clip, mesh.t_pos_idx)
|
||||
|
||||
out.update(
|
||||
{"comp_rgb": gb_rgb_aa, "comp_rgb_fg": gb_rgb_fg, "comp_rgb_bg": gb_rgb_bg}
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
def forward(
|
||||
self,
|
||||
triplanes: Float[Tensor, "B 3 Cp Hp Wp"],
|
||||
mvp_mtx: Float[Tensor, "B Nv 4 4"],
|
||||
camera_positions: Float[Tensor, "B Nv 3"],
|
||||
height: int,
|
||||
width: int,
|
||||
background_color: Optional[Float[Tensor, "B 3"]] = None,
|
||||
extra_sdf_query: Optional[List[Float[Tensor, "N 3"]]] = None,
|
||||
**kwargs,
|
||||
) -> Dict[str, Tensor]:
|
||||
batch_size = triplanes.shape[0]
|
||||
out_list = []
|
||||
for b in range(batch_size):
|
||||
out_list.append(
|
||||
self.forward_single(
|
||||
triplanes[b],
|
||||
mvp_mtx[b],
|
||||
camera_positions[b],
|
||||
height,
|
||||
width,
|
||||
background_color=background_color[b]
|
||||
if background_color is not None
|
||||
else None,
|
||||
extra_sdf_query=extra_sdf_query[b]
|
||||
if extra_sdf_query is not None
|
||||
else None,
|
||||
)
|
||||
)
|
||||
|
||||
out = defaultdict(list)
|
||||
for out_ in out_list:
|
||||
for k, v in out_.items():
|
||||
out[k].append(v)
|
||||
|
||||
for k, v in out.items():
|
||||
# some properties cannot be batched
|
||||
if isinstance(v[0], torch.Tensor) and (
|
||||
all([vv.ndim == 0 for vv in v])
|
||||
or all([vv.shape[0] == v[0].shape[0] for vv in v])
|
||||
):
|
||||
out[k] = torch.stack(v, dim=0)
|
||||
else:
|
||||
out[k] = v
|
||||
|
||||
return out
|
||||
|
||||
def isosurface(self, triplane: Float[Tensor, "3 Cp Hp Wp"]):
|
||||
grid_vertices = scale_tensor(
|
||||
self.isosurface_helper.grid_vertices,
|
||||
self.isosurface_helper.points_range,
|
||||
self.bbox,
|
||||
)
|
||||
triplane_out = chunk_batch(
|
||||
partial(self.query_triplane, triplanes=triplane), self.cfg.eval_chunk_size, grid_vertices,
|
||||
)
|
||||
|
||||
sdf = triplane_out["sdf"]
|
||||
|
||||
if self.cfg.inside_out:
|
||||
sdf = -sdf
|
||||
|
||||
if self.cfg.enable_isosurface_grid_deformation:
|
||||
deformation = triplane_out["deformation"]
|
||||
else:
|
||||
deformation = None
|
||||
|
||||
mesh: Mesh = self.isosurface_helper(sdf, deformation=deformation)
|
||||
|
||||
mesh.v_pos = scale_tensor(
|
||||
mesh.v_pos, self.isosurface_helper.points_range, self.bbox
|
||||
)
|
||||
|
||||
return mesh
|
||||
|
||||
def query(
|
||||
self, triplane: Float[Tensor, "3 Cp Hp Wp"], points: Float[Tensor, "*N 3"]
|
||||
):
|
||||
input_shape = points.shape[:-1]
|
||||
triplane_out = chunk_batch(
|
||||
partial(self.query_triplane, triplanes=triplane), self.cfg.eval_chunk_size, points.view(-1, 3)
|
||||
)
|
||||
triplane_out = {
|
||||
k: v.view(*input_shape, v.shape[-1]) for k, v in triplane_out.items()
|
||||
}
|
||||
return triplane_out
|
||||
@@ -0,0 +1,268 @@
|
||||
from dataclasses import dataclass
|
||||
import random
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from einops import rearrange
|
||||
|
||||
from ...utils.base import BaseModule
|
||||
from .dinov2 import Dinov2Model, CustomEmbeddings
|
||||
from ..transformers.attention import Modulation
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class NaiveImageTokenizer(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
num_tokens: int = 1024
|
||||
num_channels: int = 768
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
|
||||
def forward(self, images: Float[Tensor, "B N C H W"]) -> Float[Tensor, "B Ct Nt"]:
|
||||
return torch.rand(
|
||||
(
|
||||
images.shape[0],
|
||||
self.cfg.num_channels,
|
||||
self.cfg.num_tokens,
|
||||
),
|
||||
device=images.device,
|
||||
dtype=images.dtype,
|
||||
)
|
||||
|
||||
def detokenize(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DINOV2SingleImageTokenizer(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
pretrained_model_name_or_path: str = "facebook/dinov2-base"
|
||||
width: int = 224
|
||||
height: int = 224
|
||||
modulation: bool = False
|
||||
modulation_zero_init: bool = False
|
||||
modulation_single_layer: bool = False
|
||||
modulation_cond_dim: int = 16
|
||||
freeze_backbone_params: bool = True
|
||||
enable_memory_efficient_attention: bool = False
|
||||
enable_gradient_checkpointing: bool = False
|
||||
use_patch_embeddings: bool = False
|
||||
patch_embeddings_aggr_method: str = "concat"
|
||||
append_plucker_rays: bool = False
|
||||
drop_rate: float = 0.0
|
||||
drop_type: str = "all_but_first"
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
model: Dinov2Model
|
||||
|
||||
if self.cfg.freeze_backbone_params:
|
||||
# freeze dino backbone parameters
|
||||
self.register_non_module(
|
||||
"model",
|
||||
Dinov2Model.from_pretrained(self.cfg.pretrained_model_name_or_path).to(
|
||||
self.device
|
||||
),
|
||||
)
|
||||
|
||||
model = self.non_module("model")
|
||||
for p in model.parameters():
|
||||
p.requires_grad_(False)
|
||||
model.eval()
|
||||
else:
|
||||
self.model = Dinov2Model.from_pretrained(
|
||||
self.cfg.pretrained_model_name_or_path
|
||||
).to(self.device)
|
||||
model = self.model
|
||||
|
||||
if self.cfg.append_plucker_rays:
|
||||
model.expand_input_channels(6)
|
||||
|
||||
model.set_use_memory_efficient_attention_xformers(
|
||||
self.cfg.enable_memory_efficient_attention
|
||||
)
|
||||
model.set_gradient_checkpointing(self.cfg.enable_gradient_checkpointing)
|
||||
|
||||
# add modulation
|
||||
if self.cfg.modulation:
|
||||
modulations = []
|
||||
for layer in model.encoder.layer:
|
||||
norm1_modulation = Modulation(
|
||||
model.config.hidden_size,
|
||||
self.cfg.modulation_cond_dim,
|
||||
zero_init=self.cfg.modulation_zero_init,
|
||||
single_layer=self.cfg.modulation_single_layer,
|
||||
)
|
||||
norm2_modulation = Modulation(
|
||||
model.config.hidden_size,
|
||||
self.cfg.modulation_cond_dim,
|
||||
zero_init=self.cfg.modulation_zero_init,
|
||||
single_layer=self.cfg.modulation_single_layer,
|
||||
)
|
||||
layer.register_ada_norm_modulation(norm1_modulation, norm2_modulation)
|
||||
modulations += [norm1_modulation, norm2_modulation]
|
||||
self.modulations = nn.ModuleList(modulations)
|
||||
|
||||
self.register_buffer(
|
||||
"image_mean",
|
||||
torch.as_tensor([0.485, 0.456, 0.406]).reshape(1, 1, 3, 1, 1),
|
||||
persistent=False,
|
||||
)
|
||||
self.register_buffer(
|
||||
"image_std",
|
||||
torch.as_tensor([0.229, 0.224, 0.225]).reshape(1, 1, 3, 1, 1),
|
||||
persistent=False,
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
images: Float[Tensor, "B *N C H W"],
|
||||
modulation_cond: Optional[Float[Tensor, "B *N Cc"]],
|
||||
plucker_rays: Optional[Float[Tensor, "B *N 6 H W"]],
|
||||
**kwargs
|
||||
) -> Float[Tensor, "B *N Ct Nt"]:
|
||||
model: Dinov2Model
|
||||
if self.cfg.freeze_backbone_params:
|
||||
model = self.non_module("model")
|
||||
else:
|
||||
model = self.model
|
||||
|
||||
packed = False
|
||||
if images.ndim == 4:
|
||||
packed = True
|
||||
images = images.unsqueeze(1)
|
||||
if modulation_cond is not None:
|
||||
assert modulation_cond.ndim == 2
|
||||
modulation_cond = modulation_cond.unsqueeze(1)
|
||||
if plucker_rays is not None:
|
||||
assert plucker_rays.ndim == 4
|
||||
plucker_rays = plucker_rays.unsqueeze(1)
|
||||
|
||||
if (
|
||||
self.training
|
||||
and self.cfg.drop_rate > 0
|
||||
and random.random() < self.cfg.drop_rate
|
||||
):
|
||||
if self.cfg.drop_type == "all_but_first":
|
||||
drop_func = lambda x: x if x is None else x[:, 0:1]
|
||||
images = drop_func(images)
|
||||
modulation_cond = drop_func(modulation_cond)
|
||||
plucker_rays = drop_func(plucker_rays)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
batch_size, n_input_views = images.shape[:2]
|
||||
images = (images - self.image_mean) / self.image_std
|
||||
if self.cfg.append_plucker_rays and plucker_rays is not None:
|
||||
images = torch.cat([images, plucker_rays], dim=2)
|
||||
out = model(
|
||||
rearrange(images, "B N C H W -> (B N) C H W"),
|
||||
modulation_cond=rearrange(modulation_cond, "B N Cc -> (B N) Cc")
|
||||
if modulation_cond is not None
|
||||
else None,
|
||||
)
|
||||
local_features, global_features = out.last_hidden_state, out.pooler_output
|
||||
if self.cfg.use_patch_embeddings:
|
||||
patch_embeddings = out.patch_embeddings
|
||||
if self.cfg.patch_embeddings_aggr_method == "concat":
|
||||
local_features = torch.cat([local_features, patch_embeddings], dim=1)
|
||||
elif self.cfg.patch_embeddings_aggr_method == "add":
|
||||
local_features = local_features + patch_embeddings
|
||||
else:
|
||||
raise NotImplementedError
|
||||
local_features = local_features.permute(0, 2, 1)
|
||||
local_features = rearrange(
|
||||
local_features, "(B N) Ct Nt -> B N Ct Nt", B=batch_size
|
||||
)
|
||||
if packed:
|
||||
local_features = local_features.squeeze(1)
|
||||
|
||||
return local_features
|
||||
|
||||
def detokenize(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DINOV2CustomLowLevelSingleImageTokenizer(DINOV2SingleImageTokenizer):
|
||||
@dataclass
|
||||
class Config(DINOV2SingleImageTokenizer.Config):
|
||||
custom_embeddings_aggr_method: str = "concat"
|
||||
custom_embeddings_scale: float = 1.0
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.custom_embeddings = CustomEmbeddings(
|
||||
self.model.config.image_size,
|
||||
self.model.config.patch_size,
|
||||
self.model.config.num_channels,
|
||||
self.model.config.hidden_size,
|
||||
)
|
||||
self.custom_embeddings.load_state_dict(
|
||||
self.model.embeddings.state_dict(), strict=False
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
images: Float[Tensor, "B *N C H W"],
|
||||
modulation_cond: Optional[Float[Tensor, "B *N Cc"]],
|
||||
**kwargs
|
||||
) -> Float[Tensor, "B *N Ct Nt"]:
|
||||
model: Dinov2Model
|
||||
if self.cfg.freeze_backbone_params:
|
||||
model = self.non_module("model")
|
||||
else:
|
||||
model = self.model
|
||||
|
||||
packed = False
|
||||
if images.ndim == 4:
|
||||
packed = True
|
||||
images = images.unsqueeze(1)
|
||||
if modulation_cond is not None:
|
||||
assert modulation_cond.ndim == 2
|
||||
modulation_cond = modulation_cond.unsqueeze(1)
|
||||
|
||||
batch_size, n_input_views = images.shape[:2]
|
||||
images = (images - self.image_mean) / self.image_std
|
||||
images = rearrange(images, "B N C H W -> (B N) C H W")
|
||||
out = model(
|
||||
images,
|
||||
modulation_cond=rearrange(modulation_cond, "B N Cc -> (B N) Cc")
|
||||
if modulation_cond is not None
|
||||
else None,
|
||||
)
|
||||
local_features, global_features = out.last_hidden_state, out.pooler_output
|
||||
if self.cfg.use_patch_embeddings:
|
||||
patch_embeddings = out.patch_embeddings
|
||||
if self.cfg.patch_embeddings_aggr_method == "concat":
|
||||
local_features = torch.cat([local_features, patch_embeddings], dim=1)
|
||||
elif self.cfg.patch_embeddings_aggr_method == "add":
|
||||
local_features = local_features + patch_embeddings
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
custom_embeddings = (
|
||||
self.custom_embeddings(images) * self.cfg.custom_embeddings_scale
|
||||
)
|
||||
if self.cfg.custom_embeddings_aggr_method == "concat":
|
||||
local_features = torch.cat([local_features, custom_embeddings], dim=1)
|
||||
elif self.cfg.custom_embeddings_aggr_method == "add":
|
||||
local_features = local_features + custom_embeddings
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
local_features = local_features.permute(0, 2, 1)
|
||||
local_features = rearrange(
|
||||
local_features, "(B N) Ct Nt -> B N Ct Nt", B=batch_size
|
||||
)
|
||||
if packed:
|
||||
local_features = local_features.squeeze(1)
|
||||
|
||||
return local_features
|
||||
@@ -0,0 +1,49 @@
|
||||
from dataclasses import dataclass
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from einops import rearrange, repeat
|
||||
|
||||
from ...utils.base import BaseModule
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class TriplaneLearnablePositionalEmbedding(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
plane_size: int = 32
|
||||
num_channels: int = 1024
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.embeddings = nn.Parameter(
|
||||
torch.randn(
|
||||
(3, self.cfg.num_channels, self.cfg.plane_size, self.cfg.plane_size),
|
||||
dtype=torch.float32,
|
||||
)
|
||||
* 1
|
||||
/ math.sqrt(self.cfg.num_channels)
|
||||
)
|
||||
|
||||
def forward(self, batch_size: int) -> Float[Tensor, "B Ct Nt"]:
|
||||
return rearrange(
|
||||
repeat(self.embeddings, "Np Ct Hp Wp -> B Np Ct Hp Wp", B=batch_size),
|
||||
"B Np Ct Hp Wp -> B Ct (Np Hp Wp)",
|
||||
)
|
||||
|
||||
def detokenize(
|
||||
self, tokens: Float[Tensor, "B Ct Nt"]
|
||||
) -> Float[Tensor, "B 3 Ct Hp Wp"]:
|
||||
batch_size, Ct, Nt = tokens.shape
|
||||
assert Nt == self.cfg.plane_size**2 * 3
|
||||
assert Ct == self.cfg.num_channels
|
||||
return rearrange(
|
||||
tokens,
|
||||
"B Ct (Np Hp Wp) -> B Np Ct Hp Wp",
|
||||
Np=3,
|
||||
Hp=self.cfg.plane_size,
|
||||
Wp=self.cfg.plane_size,
|
||||
)
|
||||
@@ -0,0 +1,669 @@
|
||||
# Copyright 2023 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 torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
|
||||
from diffusers.utils.torch_utils import maybe_allow_in_graph
|
||||
from diffusers.models.activations import get_activation
|
||||
from diffusers.models.attention_processor import Attention
|
||||
from diffusers.models.embeddings import CombinedTimestepLabelEmbeddings
|
||||
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class MemoryEfficientAttentionMixin:
|
||||
def enable_xformers_memory_efficient_attention(
|
||||
self, attention_op: Optional[Callable] = None
|
||||
):
|
||||
r"""
|
||||
Enable memory efficient attention from [xFormers](https://facebookresearch.github.io/xformers/). When this
|
||||
option is enabled, you should observe lower GPU memory usage and a potential speed up during inference. Speed
|
||||
up during training is not guaranteed.
|
||||
|
||||
<Tip warning={true}>
|
||||
|
||||
⚠️ When memory efficient attention and sliced attention are both enabled, memory efficient attention takes
|
||||
precedent.
|
||||
|
||||
</Tip>
|
||||
|
||||
Parameters:
|
||||
attention_op (`Callable`, *optional*):
|
||||
Override the default `None` operator for use as `op` argument to the
|
||||
[`memory_efficient_attention()`](https://facebookresearch.github.io/xformers/components/ops.html#xformers.ops.memory_efficient_attention)
|
||||
function of xFormers.
|
||||
|
||||
Examples:
|
||||
|
||||
```py
|
||||
>>> import torch
|
||||
>>> from diffusers import DiffusionPipeline
|
||||
>>> from xformers.ops import MemoryEfficientAttentionFlashAttentionOp
|
||||
|
||||
>>> pipe = DiffusionPipeline.from_pretrained("stabilityai/stable-diffusion-2-1", torch_dtype=torch.float16)
|
||||
>>> pipe = pipe.to("cuda")
|
||||
>>> pipe.enable_xformers_memory_efficient_attention(attention_op=MemoryEfficientAttentionFlashAttentionOp)
|
||||
>>> # Workaround for not accepting attention shape using VAE for Flash Attention
|
||||
>>> pipe.vae.enable_xformers_memory_efficient_attention(attention_op=None)
|
||||
```
|
||||
"""
|
||||
self.set_use_memory_efficient_attention_xformers(True, attention_op)
|
||||
|
||||
def disable_xformers_memory_efficient_attention(self):
|
||||
r"""
|
||||
Disable memory efficient attention from [xFormers](https://facebookresearch.github.io/xformers/).
|
||||
"""
|
||||
self.set_use_memory_efficient_attention_xformers(False)
|
||||
|
||||
def set_use_memory_efficient_attention_xformers(
|
||||
self, valid: bool, attention_op: Optional[Callable] = None
|
||||
) -> None:
|
||||
# Recursively walk through all the children.
|
||||
# Any children which exposes the set_use_memory_efficient_attention_xformers method
|
||||
# gets the message
|
||||
def fn_recursive_set_mem_eff(module: torch.nn.Module):
|
||||
if hasattr(module, "set_use_memory_efficient_attention_xformers"):
|
||||
module.set_use_memory_efficient_attention_xformers(valid, attention_op)
|
||||
|
||||
for child in module.children():
|
||||
fn_recursive_set_mem_eff(child)
|
||||
|
||||
for module in self.children():
|
||||
if isinstance(module, torch.nn.Module):
|
||||
fn_recursive_set_mem_eff(module)
|
||||
|
||||
|
||||
@maybe_allow_in_graph
|
||||
class GatedSelfAttentionDense(nn.Module):
|
||||
r"""
|
||||
A gated self-attention dense layer that combines visual features and object features.
|
||||
|
||||
Parameters:
|
||||
query_dim (`int`): The number of channels in the query.
|
||||
context_dim (`int`): The number of channels in the context.
|
||||
n_heads (`int`): The number of heads to use for attention.
|
||||
d_head (`int`): The number of channels in each head.
|
||||
"""
|
||||
|
||||
def __init__(self, query_dim: int, context_dim: int, n_heads: int, d_head: int):
|
||||
super().__init__()
|
||||
|
||||
# we need a linear projection since we need cat visual feature and obj feature
|
||||
self.linear = nn.Linear(context_dim, query_dim)
|
||||
|
||||
self.attn = Attention(query_dim=query_dim, heads=n_heads, dim_head=d_head)
|
||||
self.ff = FeedForward(query_dim, activation_fn="geglu")
|
||||
|
||||
self.norm1 = nn.LayerNorm(query_dim)
|
||||
self.norm2 = nn.LayerNorm(query_dim)
|
||||
|
||||
self.register_parameter("alpha_attn", nn.Parameter(torch.tensor(0.0)))
|
||||
self.register_parameter("alpha_dense", nn.Parameter(torch.tensor(0.0)))
|
||||
|
||||
self.enabled = True
|
||||
|
||||
def forward(self, x: torch.Tensor, objs: torch.Tensor) -> torch.Tensor:
|
||||
if not self.enabled:
|
||||
return x
|
||||
|
||||
n_visual = x.shape[1]
|
||||
objs = self.linear(objs)
|
||||
|
||||
x = (
|
||||
x
|
||||
+ self.alpha_attn.tanh()
|
||||
* self.attn(self.norm1(torch.cat([x, objs], dim=1)))[:, :n_visual, :]
|
||||
)
|
||||
x = x + self.alpha_dense.tanh() * self.ff(self.norm2(x))
|
||||
|
||||
return x
|
||||
|
||||
|
||||
@maybe_allow_in_graph
|
||||
class BasicTransformerBlock(nn.Module, MemoryEfficientAttentionMixin):
|
||||
r"""
|
||||
A basic Transformer block.
|
||||
|
||||
Parameters:
|
||||
dim (`int`): The number of channels in the input and output.
|
||||
num_attention_heads (`int`): The number of heads to use for multi-head attention.
|
||||
attention_head_dim (`int`): The number of channels in each head.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
cross_attention_dim (`int`, *optional*): The size of the encoder_hidden_states vector for cross attention.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to be used in feed-forward.
|
||||
num_embeds_ada_norm (:
|
||||
obj: `int`, *optional*): The number of diffusion steps used during training. See `Transformer2DModel`.
|
||||
attention_bias (:
|
||||
obj: `bool`, *optional*, defaults to `False`): Configure if the attentions should contain a bias parameter.
|
||||
only_cross_attention (`bool`, *optional*):
|
||||
Whether to use only cross-attention layers. In this case two cross attention layers are used.
|
||||
double_self_attention (`bool`, *optional*):
|
||||
Whether to use two self-attention layers. In this case no cross attention layers are used.
|
||||
upcast_attention (`bool`, *optional*):
|
||||
Whether to upcast the attention computation to float32. This is useful for mixed precision training.
|
||||
norm_elementwise_affine (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use learnable elementwise affine parameters for normalization.
|
||||
norm_type (`str`, *optional*, defaults to `"layer_norm"`):
|
||||
The normalization layer to use. Can be `"layer_norm"`, `"ada_norm"` or `"ada_norm_zero"`.
|
||||
final_dropout (`bool` *optional*, defaults to False):
|
||||
Whether to apply a final dropout after the last feed-forward layer.
|
||||
attention_type (`str`, *optional*, defaults to `"default"`):
|
||||
The type of attention to use. Can be `"default"` or `"gated"` or `"gated-text-image"`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
num_attention_heads: int,
|
||||
attention_head_dim: int,
|
||||
dropout=0.0,
|
||||
cross_attention_dim: Optional[int] = None,
|
||||
activation_fn: str = "geglu",
|
||||
num_embeds_ada_norm: Optional[int] = None,
|
||||
cond_dim_ada_norm_continuous: Optional[int] = None,
|
||||
attention_bias: bool = False,
|
||||
only_cross_attention: bool = False,
|
||||
double_self_attention: bool = False,
|
||||
upcast_attention: bool = False,
|
||||
norm_elementwise_affine: bool = True,
|
||||
norm_type: str = "layer_norm",
|
||||
final_dropout: bool = False,
|
||||
attention_type: str = "default",
|
||||
):
|
||||
super().__init__()
|
||||
self.only_cross_attention = only_cross_attention
|
||||
|
||||
self.use_ada_layer_norm_zero = (
|
||||
num_embeds_ada_norm is not None
|
||||
) and norm_type == "ada_norm_zero"
|
||||
self.use_ada_layer_norm = (
|
||||
num_embeds_ada_norm is not None
|
||||
) and norm_type == "ada_norm"
|
||||
self.use_ada_layer_norm_continuous = (
|
||||
cond_dim_ada_norm_continuous is not None
|
||||
) and norm_type == "ada_norm_continuous"
|
||||
|
||||
assert (
|
||||
int(self.use_ada_layer_norm)
|
||||
+ int(self.use_ada_layer_norm_continuous)
|
||||
+ int(self.use_ada_layer_norm_zero)
|
||||
<= 1
|
||||
)
|
||||
|
||||
if norm_type in ("ada_norm", "ada_norm_zero") and num_embeds_ada_norm is None:
|
||||
raise ValueError(
|
||||
f"`norm_type` is set to {norm_type}, but `num_embeds_ada_norm` is not defined. Please make sure to"
|
||||
f" define `num_embeds_ada_norm` if setting `norm_type` to {norm_type}."
|
||||
)
|
||||
|
||||
# Define 3 blocks. Each block has its own normalization layer.
|
||||
# 1. Self-Attn
|
||||
if self.use_ada_layer_norm:
|
||||
self.norm1 = AdaLayerNorm(dim, num_embeds_ada_norm)
|
||||
elif self.use_ada_layer_norm_continuous:
|
||||
self.norm1 = AdaLayerNormContinuous(dim, cond_dim_ada_norm_continuous)
|
||||
elif self.use_ada_layer_norm_zero:
|
||||
self.norm1 = AdaLayerNormZero(dim, num_embeds_ada_norm)
|
||||
else:
|
||||
self.norm1 = nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine)
|
||||
self.attn1 = Attention(
|
||||
query_dim=dim,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
cross_attention_dim=cross_attention_dim if only_cross_attention else None,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
|
||||
# 2. Cross-Attn
|
||||
if cross_attention_dim is not None or double_self_attention:
|
||||
# We currently only use AdaLayerNormZero for self attention where there will only be one attention block.
|
||||
# I.e. the number of returned modulation chunks from AdaLayerZero would not make sense if returned during
|
||||
# the second cross attention block.
|
||||
if self.use_ada_layer_norm:
|
||||
self.norm2 = AdaLayerNorm(dim, num_embeds_ada_norm)
|
||||
elif self.use_ada_layer_norm_continuous:
|
||||
self.norm2 = AdaLayerNormContinuous(dim, cond_dim_ada_norm_continuous)
|
||||
else:
|
||||
self.norm2 = nn.LayerNorm(
|
||||
dim, elementwise_affine=norm_elementwise_affine
|
||||
)
|
||||
|
||||
self.attn2 = Attention(
|
||||
query_dim=dim,
|
||||
cross_attention_dim=cross_attention_dim
|
||||
if not double_self_attention
|
||||
else None,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
upcast_attention=upcast_attention,
|
||||
) # is self-attn if encoder_hidden_states is none
|
||||
else:
|
||||
self.norm2 = None
|
||||
self.attn2 = None
|
||||
|
||||
# 3. Feed-forward
|
||||
if self.use_ada_layer_norm_continuous:
|
||||
self.norm3 = AdaLayerNormContinuous(dim, cond_dim_ada_norm_continuous)
|
||||
else:
|
||||
self.norm3 = nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine)
|
||||
self.ff = FeedForward(
|
||||
dim,
|
||||
dropout=dropout,
|
||||
activation_fn=activation_fn,
|
||||
final_dropout=final_dropout,
|
||||
)
|
||||
|
||||
# 4. Fuser
|
||||
if attention_type == "gated" or attention_type == "gated-text-image":
|
||||
self.fuser = GatedSelfAttentionDense(
|
||||
dim, cross_attention_dim, num_attention_heads, attention_head_dim
|
||||
)
|
||||
|
||||
# let chunk size default to None
|
||||
self._chunk_size = None
|
||||
self._chunk_dim = 0
|
||||
|
||||
def set_chunk_feed_forward(self, chunk_size: Optional[int], dim: int):
|
||||
# Sets chunk feed-forward
|
||||
self._chunk_size = chunk_size
|
||||
self._chunk_dim = dim
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.FloatTensor,
|
||||
attention_mask: Optional[torch.FloatTensor] = None,
|
||||
encoder_hidden_states: Optional[torch.FloatTensor] = None,
|
||||
encoder_attention_mask: Optional[torch.FloatTensor] = None,
|
||||
timestep: Optional[torch.LongTensor] = None,
|
||||
modulation_cond: Optional[torch.FloatTensor] = None,
|
||||
cross_attention_kwargs: Dict[str, Any] = None,
|
||||
class_labels: Optional[torch.LongTensor] = None,
|
||||
) -> torch.FloatTensor:
|
||||
# Notice that normalization is always applied before the real computation in the following blocks.
|
||||
# 0. Self-Attention
|
||||
if self.use_ada_layer_norm:
|
||||
norm_hidden_states = self.norm1(hidden_states, timestep)
|
||||
elif self.use_ada_layer_norm_continuous:
|
||||
norm_hidden_states = self.norm1(hidden_states, modulation_cond)
|
||||
elif self.use_ada_layer_norm_zero:
|
||||
norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1(
|
||||
hidden_states, timestep, class_labels, hidden_dtype=hidden_states.dtype
|
||||
)
|
||||
else:
|
||||
norm_hidden_states = self.norm1(hidden_states)
|
||||
|
||||
# 1. Retrieve lora scale.
|
||||
lora_scale = (
|
||||
cross_attention_kwargs.get("scale", 1.0)
|
||||
if cross_attention_kwargs is not None
|
||||
else 1.0
|
||||
)
|
||||
|
||||
# 2. Prepare GLIGEN inputs
|
||||
cross_attention_kwargs = (
|
||||
cross_attention_kwargs.copy() if cross_attention_kwargs is not None else {}
|
||||
)
|
||||
gligen_kwargs = cross_attention_kwargs.pop("gligen", None)
|
||||
|
||||
attn_output = self.attn1(
|
||||
norm_hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states
|
||||
if self.only_cross_attention
|
||||
else None,
|
||||
attention_mask=attention_mask,
|
||||
**cross_attention_kwargs,
|
||||
)
|
||||
if self.use_ada_layer_norm_zero:
|
||||
attn_output = gate_msa.unsqueeze(1) * attn_output
|
||||
hidden_states = attn_output + hidden_states
|
||||
|
||||
# 2.5 GLIGEN Control
|
||||
if gligen_kwargs is not None:
|
||||
hidden_states = self.fuser(hidden_states, gligen_kwargs["objs"])
|
||||
# 2.5 ends
|
||||
|
||||
# 3. Cross-Attention
|
||||
if self.attn2 is not None:
|
||||
if self.use_ada_layer_norm:
|
||||
norm_hidden_states = self.norm2(hidden_states, timestep)
|
||||
elif self.use_ada_layer_norm_continuous:
|
||||
norm_hidden_states = self.norm2(hidden_states, modulation_cond)
|
||||
else:
|
||||
norm_hidden_states = self.norm2(hidden_states)
|
||||
|
||||
attn_output = self.attn2(
|
||||
norm_hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
attention_mask=encoder_attention_mask,
|
||||
**cross_attention_kwargs,
|
||||
)
|
||||
hidden_states = attn_output + hidden_states
|
||||
|
||||
# 4. Feed-forward
|
||||
if self.use_ada_layer_norm_continuous:
|
||||
norm_hidden_states = self.norm3(hidden_states, modulation_cond)
|
||||
else:
|
||||
norm_hidden_states = self.norm3(hidden_states)
|
||||
|
||||
if self.use_ada_layer_norm_zero:
|
||||
norm_hidden_states = (
|
||||
norm_hidden_states * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
|
||||
)
|
||||
|
||||
if self._chunk_size is not None:
|
||||
# "feed_forward_chunk_size" can be used to save memory
|
||||
if norm_hidden_states.shape[self._chunk_dim] % self._chunk_size != 0:
|
||||
raise ValueError(
|
||||
f"`hidden_states` dimension to be chunked: {norm_hidden_states.shape[self._chunk_dim]} has to be divisible by chunk size: {self._chunk_size}. Make sure to set an appropriate `chunk_size` when calling `unet.enable_forward_chunking`."
|
||||
)
|
||||
|
||||
num_chunks = norm_hidden_states.shape[self._chunk_dim] // self._chunk_size
|
||||
ff_output = torch.cat(
|
||||
[
|
||||
self.ff(hid_slice, scale=lora_scale)
|
||||
for hid_slice in norm_hidden_states.chunk(
|
||||
num_chunks, dim=self._chunk_dim
|
||||
)
|
||||
],
|
||||
dim=self._chunk_dim,
|
||||
)
|
||||
else:
|
||||
ff_output = self.ff(norm_hidden_states, scale=lora_scale)
|
||||
|
||||
if self.use_ada_layer_norm_zero:
|
||||
ff_output = gate_mlp.unsqueeze(1) * ff_output
|
||||
|
||||
hidden_states = ff_output + hidden_states
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class FeedForward(nn.Module):
|
||||
r"""
|
||||
A feed-forward layer.
|
||||
|
||||
Parameters:
|
||||
dim (`int`): The number of channels in the input.
|
||||
dim_out (`int`, *optional*): The number of channels in the output. If not given, defaults to `dim`.
|
||||
mult (`int`, *optional*, defaults to 4): The multiplier to use for the hidden dimension.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to be used in feed-forward.
|
||||
final_dropout (`bool` *optional*, defaults to False): Apply a final dropout.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
dim_out: Optional[int] = None,
|
||||
mult: int = 4,
|
||||
dropout: float = 0.0,
|
||||
activation_fn: str = "geglu",
|
||||
final_dropout: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
inner_dim = int(dim * mult)
|
||||
dim_out = dim_out if dim_out is not None else dim
|
||||
linear_cls = nn.Linear
|
||||
|
||||
if activation_fn == "gelu":
|
||||
act_fn = GELU(dim, inner_dim)
|
||||
if activation_fn == "gelu-approximate":
|
||||
act_fn = GELU(dim, inner_dim, approximate="tanh")
|
||||
elif activation_fn == "geglu":
|
||||
act_fn = GEGLU(dim, inner_dim)
|
||||
elif activation_fn == "geglu-approximate":
|
||||
act_fn = ApproximateGELU(dim, inner_dim)
|
||||
|
||||
self.net = nn.ModuleList([])
|
||||
# project in
|
||||
self.net.append(act_fn)
|
||||
# project dropout
|
||||
self.net.append(nn.Dropout(dropout))
|
||||
# project out
|
||||
self.net.append(linear_cls(inner_dim, dim_out))
|
||||
# FF as used in Vision Transformer, MLP-Mixer, etc. have a final dropout
|
||||
if final_dropout:
|
||||
self.net.append(nn.Dropout(dropout))
|
||||
|
||||
def forward(self, hidden_states: torch.Tensor, scale: float = 1.0) -> torch.Tensor:
|
||||
for module in self.net:
|
||||
hidden_states = module(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class GELU(nn.Module):
|
||||
r"""
|
||||
GELU activation function with tanh approximation support with `approximate="tanh"`.
|
||||
|
||||
Parameters:
|
||||
dim_in (`int`): The number of channels in the input.
|
||||
dim_out (`int`): The number of channels in the output.
|
||||
approximate (`str`, *optional*, defaults to `"none"`): If `"tanh"`, use tanh approximation.
|
||||
"""
|
||||
|
||||
def __init__(self, dim_in: int, dim_out: int, approximate: str = "none"):
|
||||
super().__init__()
|
||||
self.proj = nn.Linear(dim_in, dim_out)
|
||||
self.approximate = approximate
|
||||
|
||||
def gelu(self, gate: torch.Tensor) -> torch.Tensor:
|
||||
if gate.device.type != "mps":
|
||||
return F.gelu(gate, approximate=self.approximate)
|
||||
# mps: gelu is not implemented for float16
|
||||
return F.gelu(gate.to(dtype=torch.float32), approximate=self.approximate).to(
|
||||
dtype=gate.dtype
|
||||
)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
hidden_states = self.proj(hidden_states)
|
||||
hidden_states = self.gelu(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class GEGLU(nn.Module):
|
||||
r"""
|
||||
A variant of the gated linear unit activation function from https://arxiv.org/abs/2002.05202.
|
||||
|
||||
Parameters:
|
||||
dim_in (`int`): The number of channels in the input.
|
||||
dim_out (`int`): The number of channels in the output.
|
||||
"""
|
||||
|
||||
def __init__(self, dim_in: int, dim_out: int):
|
||||
super().__init__()
|
||||
linear_cls = nn.Linear
|
||||
|
||||
self.proj = linear_cls(dim_in, dim_out * 2)
|
||||
|
||||
def gelu(self, gate: torch.Tensor) -> torch.Tensor:
|
||||
if gate.device.type != "mps":
|
||||
return F.gelu(gate)
|
||||
# mps: gelu is not implemented for float16
|
||||
return F.gelu(gate.to(dtype=torch.float32)).to(dtype=gate.dtype)
|
||||
|
||||
def forward(self, hidden_states, scale: float = 1.0):
|
||||
args = ()
|
||||
hidden_states, gate = self.proj(hidden_states, *args).chunk(2, dim=-1)
|
||||
return hidden_states * self.gelu(gate)
|
||||
|
||||
|
||||
class ApproximateGELU(nn.Module):
|
||||
r"""
|
||||
The approximate form of Gaussian Error Linear Unit (GELU). For more details, see section 2:
|
||||
https://arxiv.org/abs/1606.08415.
|
||||
|
||||
Parameters:
|
||||
dim_in (`int`): The number of channels in the input.
|
||||
dim_out (`int`): The number of channels in the output.
|
||||
"""
|
||||
|
||||
def __init__(self, dim_in: int, dim_out: int):
|
||||
super().__init__()
|
||||
self.proj = nn.Linear(dim_in, dim_out)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
x = self.proj(x)
|
||||
return x * torch.sigmoid(1.702 * x)
|
||||
|
||||
|
||||
class AdaLayerNorm(nn.Module):
|
||||
r"""
|
||||
Norm layer modified to incorporate timestep embeddings.
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
num_embeddings (`int`): The size of the dictionary of embeddings.
|
||||
"""
|
||||
|
||||
def __init__(self, embedding_dim: int, num_embeddings: int):
|
||||
super().__init__()
|
||||
self.emb = nn.Embedding(num_embeddings, embedding_dim)
|
||||
self.silu = nn.SiLU()
|
||||
self.linear = nn.Linear(embedding_dim, embedding_dim * 2)
|
||||
self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False)
|
||||
|
||||
def forward(self, x: torch.Tensor, timestep: torch.Tensor) -> torch.Tensor:
|
||||
emb = self.linear(self.silu(self.emb(timestep)))
|
||||
scale, shift = torch.chunk(emb, 2, dim=1)
|
||||
x = self.norm(x) * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class AdaLayerNormContinuous(nn.Module):
|
||||
r"""
|
||||
Norm layer modified to incorporate arbitrary continuous embeddings.
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
"""
|
||||
|
||||
def __init__(self, embedding_dim: int, condition_dim: int):
|
||||
super().__init__()
|
||||
self.silu = nn.SiLU()
|
||||
self.linear1 = nn.Linear(condition_dim, condition_dim)
|
||||
self.linear2 = nn.Linear(condition_dim, embedding_dim * 2)
|
||||
self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False)
|
||||
|
||||
def forward(self, x: torch.Tensor, condition: torch.Tensor) -> torch.Tensor:
|
||||
emb = self.linear2(self.silu(self.linear1(condition)))
|
||||
scale, shift = torch.chunk(emb, 2, dim=1)
|
||||
x = self.norm(x) * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class Modulation(nn.Module):
|
||||
def __init__(self, embedding_dim: int, condition_dim: int, zero_init: bool = False, single_layer: bool = False):
|
||||
super().__init__()
|
||||
self.silu = nn.SiLU()
|
||||
if single_layer:
|
||||
self.linear1 = nn.Identity()
|
||||
else:
|
||||
self.linear1 = nn.Linear(condition_dim, condition_dim)
|
||||
|
||||
self.linear2 = nn.Linear(condition_dim, embedding_dim * 2)
|
||||
|
||||
# Only zero init the last linear layer
|
||||
if zero_init:
|
||||
nn.init.zeros_(self.linear2.weight)
|
||||
nn.init.zeros_(self.linear2.bias)
|
||||
|
||||
def forward(self, x: torch.Tensor, condition: torch.Tensor) -> torch.Tensor:
|
||||
emb = self.linear2(self.silu(self.linear1(condition)))
|
||||
scale, shift = torch.chunk(emb, 2, dim=1)
|
||||
x = x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class AdaLayerNormZero(nn.Module):
|
||||
r"""
|
||||
Norm layer adaptive layer norm zero (adaLN-Zero).
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
num_embeddings (`int`): The size of the dictionary of embeddings.
|
||||
"""
|
||||
|
||||
def __init__(self, embedding_dim: int, num_embeddings: int):
|
||||
super().__init__()
|
||||
|
||||
self.emb = CombinedTimestepLabelEmbeddings(num_embeddings, embedding_dim)
|
||||
|
||||
self.silu = nn.SiLU()
|
||||
self.linear = nn.Linear(embedding_dim, 6 * embedding_dim, bias=True)
|
||||
self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False, eps=1e-6)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
timestep: torch.Tensor,
|
||||
class_labels: torch.LongTensor,
|
||||
hidden_dtype: Optional[torch.dtype] = None,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
emb = self.linear(
|
||||
self.silu(self.emb(timestep, class_labels, hidden_dtype=hidden_dtype))
|
||||
)
|
||||
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = emb.chunk(
|
||||
6, dim=1
|
||||
)
|
||||
x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None]
|
||||
return x, gate_msa, shift_mlp, scale_mlp, gate_mlp
|
||||
|
||||
|
||||
class AdaGroupNorm(nn.Module):
|
||||
r"""
|
||||
GroupNorm layer modified to incorporate timestep embeddings.
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
num_embeddings (`int`): The size of the dictionary of embeddings.
|
||||
num_groups (`int`): The number of groups to separate the channels into.
|
||||
act_fn (`str`, *optional*, defaults to `None`): The activation function to use.
|
||||
eps (`float`, *optional*, defaults to `1e-5`): The epsilon value to use for numerical stability.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embedding_dim: int,
|
||||
out_dim: int,
|
||||
num_groups: int,
|
||||
act_fn: Optional[str] = None,
|
||||
eps: float = 1e-5,
|
||||
):
|
||||
super().__init__()
|
||||
self.num_groups = num_groups
|
||||
self.eps = eps
|
||||
|
||||
if act_fn is None:
|
||||
self.act = None
|
||||
else:
|
||||
self.act = get_activation(act_fn)
|
||||
|
||||
self.linear = nn.Linear(embedding_dim, out_dim * 2)
|
||||
|
||||
def forward(self, x: torch.Tensor, emb: torch.Tensor) -> torch.Tensor:
|
||||
if self.act:
|
||||
emb = self.act(emb)
|
||||
emb = self.linear(emb)
|
||||
emb = emb[:, :, None, None]
|
||||
scale, shift = emb.chunk(2, dim=1)
|
||||
|
||||
x = F.group_norm(x, self.num_groups, eps=self.eps)
|
||||
x = x * (1 + scale) + shift
|
||||
return x
|
||||
@@ -0,0 +1,249 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
|
||||
from ...utils.base import BaseModule
|
||||
from .attention import (
|
||||
BasicTransformerBlock,
|
||||
MemoryEfficientAttentionMixin,
|
||||
)
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class Transformer1D(BaseModule, MemoryEfficientAttentionMixin):
|
||||
"""
|
||||
A 1D Transformer model for sequence data.
|
||||
|
||||
Parameters:
|
||||
num_attention_heads (`int`, *optional*, defaults to 16): The number of heads to use for multi-head attention.
|
||||
attention_head_dim (`int`, *optional*, defaults to 88): The number of channels in each head.
|
||||
in_channels (`int`, *optional*):
|
||||
The number of channels in the input and output (specify if the input is **continuous**).
|
||||
num_layers (`int`, *optional*, defaults to 1): The number of layers of Transformer blocks to use.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
cross_attention_dim (`int`, *optional*): The number of `encoder_hidden_states` dimensions to use.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to use in feed-forward.
|
||||
num_embeds_ada_norm ( `int`, *optional*):
|
||||
The number of diffusion steps used during training. Pass if at least one of the norm_layers is
|
||||
`AdaLayerNorm`. This is fixed during training since it is used to learn a number of embeddings that are
|
||||
added to the hidden states.
|
||||
|
||||
During inference, you can denoise for up to but not more steps than `num_embeds_ada_norm`.
|
||||
attention_bias (`bool`, *optional*):
|
||||
Configure if the `TransformerBlocks` attention should contain a bias parameter.
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
num_attention_heads: int = 16
|
||||
attention_head_dim: int = 88
|
||||
in_channels: Optional[int] = None
|
||||
out_channels: Optional[int] = None
|
||||
num_layers: int = 1
|
||||
dropout: float = 0.0
|
||||
norm_num_groups: int = 32
|
||||
cross_attention_dim: Optional[int] = None
|
||||
attention_bias: bool = False
|
||||
activation_fn: str = "geglu"
|
||||
num_embeds_ada_norm: Optional[int] = None
|
||||
cond_dim_ada_norm_continuous: Optional[int] = None
|
||||
only_cross_attention: bool = False
|
||||
double_self_attention: bool = False
|
||||
upcast_attention: bool = False
|
||||
norm_type: str = "layer_norm"
|
||||
norm_elementwise_affine: bool = True
|
||||
attention_type: str = "default"
|
||||
enable_memory_efficient_attention: bool = False
|
||||
gradient_checkpointing: bool = False
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
|
||||
self.num_attention_heads = self.cfg.num_attention_heads
|
||||
self.attention_head_dim = self.cfg.attention_head_dim
|
||||
inner_dim = self.num_attention_heads * self.attention_head_dim
|
||||
|
||||
linear_cls = nn.Linear
|
||||
|
||||
if self.cfg.norm_type == "layer_norm" and (
|
||||
self.cfg.num_embeds_ada_norm is not None
|
||||
or self.cfg.cond_dim_ada_norm_continuous is not None
|
||||
):
|
||||
raise ValueError("Incorrect norm_type.")
|
||||
|
||||
# 2. Define input layers
|
||||
self.in_channels = self.cfg.in_channels
|
||||
|
||||
self.norm = torch.nn.GroupNorm(
|
||||
num_groups=self.cfg.norm_num_groups,
|
||||
num_channels=self.cfg.in_channels,
|
||||
eps=1e-6,
|
||||
affine=True,
|
||||
)
|
||||
self.proj_in = linear_cls(self.cfg.in_channels, inner_dim)
|
||||
|
||||
# 3. Define transformers blocks
|
||||
self.transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock(
|
||||
inner_dim,
|
||||
self.num_attention_heads,
|
||||
self.attention_head_dim,
|
||||
dropout=self.cfg.dropout,
|
||||
cross_attention_dim=self.cfg.cross_attention_dim,
|
||||
activation_fn=self.cfg.activation_fn,
|
||||
num_embeds_ada_norm=self.cfg.num_embeds_ada_norm,
|
||||
cond_dim_ada_norm_continuous=self.cfg.cond_dim_ada_norm_continuous,
|
||||
attention_bias=self.cfg.attention_bias,
|
||||
only_cross_attention=self.cfg.only_cross_attention,
|
||||
double_self_attention=self.cfg.double_self_attention,
|
||||
upcast_attention=self.cfg.upcast_attention,
|
||||
norm_type=self.cfg.norm_type,
|
||||
norm_elementwise_affine=self.cfg.norm_elementwise_affine,
|
||||
attention_type=self.cfg.attention_type,
|
||||
)
|
||||
for d in range(self.cfg.num_layers)
|
||||
]
|
||||
)
|
||||
|
||||
# 4. Define output layers
|
||||
self.out_channels = (
|
||||
self.cfg.in_channels
|
||||
if self.cfg.out_channels is None
|
||||
else self.cfg.out_channels
|
||||
)
|
||||
|
||||
self.proj_out = linear_cls(inner_dim, self.cfg.in_channels)
|
||||
|
||||
self.gradient_checkpointing = self.cfg.gradient_checkpointing
|
||||
|
||||
self.set_use_memory_efficient_attention_xformers(
|
||||
self.cfg.enable_memory_efficient_attention
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
encoder_hidden_states: Optional[torch.Tensor] = None,
|
||||
timestep: Optional[torch.LongTensor] = None,
|
||||
modulation_cond: Optional[torch.FloatTensor] = None,
|
||||
class_labels: Optional[torch.LongTensor] = None,
|
||||
cross_attention_kwargs: Dict[str, Any] = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
encoder_attention_mask: Optional[torch.Tensor] = None,
|
||||
):
|
||||
"""
|
||||
The [`Transformer1DModel`] forward method.
|
||||
|
||||
Args:
|
||||
hidden_states (`torch.LongTensor` of shape `(batch size, num latent pixels)` if discrete, `torch.FloatTensor` of shape `(batch size, channel, height, width)` if continuous):
|
||||
Input `hidden_states`.
|
||||
encoder_hidden_states ( `torch.FloatTensor` of shape `(batch size, sequence len, embed dims)`, *optional*):
|
||||
Conditional embeddings for cross attention layer. If not given, cross-attention defaults to
|
||||
self-attention.
|
||||
timestep ( `torch.LongTensor`, *optional*):
|
||||
Used to indicate denoising step. Optional timestep to be applied as an embedding in `AdaLayerNorm`.
|
||||
class_labels ( `torch.LongTensor` of shape `(batch size, num classes)`, *optional*):
|
||||
Used to indicate class labels conditioning. Optional class labels to be applied as an embedding in
|
||||
`AdaLayerZeroNorm`.
|
||||
cross_attention_kwargs ( `Dict[str, Any]`, *optional*):
|
||||
A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under
|
||||
`self.processor` in
|
||||
[diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
|
||||
attention_mask ( `torch.Tensor`, *optional*):
|
||||
An attention mask of shape `(batch, key_tokens)` is applied to `encoder_hidden_states`. If `1` the mask
|
||||
is kept, otherwise if `0` it is discarded. Mask will be converted into a bias, which adds large
|
||||
negative values to the attention scores corresponding to "discard" tokens.
|
||||
encoder_attention_mask ( `torch.Tensor`, *optional*):
|
||||
Cross-attention mask applied to `encoder_hidden_states`. Two formats supported:
|
||||
|
||||
* Mask `(batch, sequence_length)` True = keep, False = discard.
|
||||
* Bias `(batch, 1, sequence_length)` 0 = keep, -10000 = discard.
|
||||
|
||||
If `ndim == 2`: will be interpreted as a mask, then converted into a bias consistent with the format
|
||||
above. This bias will be added to the cross-attention scores.
|
||||
return_dict (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to return a [`~models.unet_2d_condition.UNet2DConditionOutput`] instead of a plain
|
||||
tuple.
|
||||
|
||||
Returns:
|
||||
If `return_dict` is True, an [`~models.transformer_2d.Transformer2DModelOutput`] is returned, otherwise a
|
||||
`tuple` where the first element is the sample tensor.
|
||||
"""
|
||||
# ensure attention_mask is a bias, and give it a singleton query_tokens dimension.
|
||||
# we may have done this conversion already, e.g. if we came here via UNet2DConditionModel#forward.
|
||||
# we can tell by counting dims; if ndim == 2: it's a mask rather than a bias.
|
||||
# expects mask of shape:
|
||||
# [batch, key_tokens]
|
||||
# adds singleton query_tokens dimension:
|
||||
# [batch, 1, key_tokens]
|
||||
# this helps to broadcast it as a bias over attention scores, which will be in one of the following shapes:
|
||||
# [batch, heads, query_tokens, key_tokens] (e.g. torch sdp attn)
|
||||
# [batch * heads, query_tokens, key_tokens] (e.g. xformers or classic attn)
|
||||
if attention_mask is not None and attention_mask.ndim == 2:
|
||||
# assume that mask is expressed as:
|
||||
# (1 = keep, 0 = discard)
|
||||
# convert mask into a bias that can be added to attention scores:
|
||||
# (keep = +0, discard = -10000.0)
|
||||
attention_mask = (1 - attention_mask.to(hidden_states.dtype)) * -10000.0
|
||||
attention_mask = attention_mask.unsqueeze(1)
|
||||
|
||||
# convert encoder_attention_mask to a bias the same way we do for attention_mask
|
||||
if encoder_attention_mask is not None and encoder_attention_mask.ndim == 2:
|
||||
encoder_attention_mask = (
|
||||
1 - encoder_attention_mask.to(hidden_states.dtype)
|
||||
) * -10000.0
|
||||
encoder_attention_mask = encoder_attention_mask.unsqueeze(1)
|
||||
|
||||
# 1. Input
|
||||
batch, _, seq_len = hidden_states.shape
|
||||
residual = hidden_states
|
||||
|
||||
hidden_states = self.norm(hidden_states)
|
||||
inner_dim = hidden_states.shape[1]
|
||||
hidden_states = hidden_states.permute(0, 2, 1).reshape(
|
||||
batch, seq_len, inner_dim
|
||||
)
|
||||
hidden_states = self.proj_in(hidden_states)
|
||||
|
||||
# 2. Blocks
|
||||
for block in self.transformer_blocks:
|
||||
if self.training and self.gradient_checkpointing:
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
block,
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
timestep,
|
||||
modulation_cond,
|
||||
cross_attention_kwargs,
|
||||
class_labels,
|
||||
use_reentrant=False,
|
||||
)
|
||||
else:
|
||||
hidden_states = block(
|
||||
hidden_states,
|
||||
attention_mask=attention_mask,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
timestep=timestep,
|
||||
modulation_cond=modulation_cond,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
class_labels=class_labels,
|
||||
)
|
||||
|
||||
# 3. Output
|
||||
hidden_states = self.proj_out(hidden_states)
|
||||
hidden_states = (
|
||||
hidden_states.reshape(batch, seq_len, inner_dim)
|
||||
.permute(0, 2, 1)
|
||||
.contiguous()
|
||||
)
|
||||
|
||||
output = hidden_states + residual
|
||||
|
||||
return output
|
||||
@@ -0,0 +1,414 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
from diffusers.models.embeddings import ImagePositionalEmbeddings, PatchEmbed
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from ...utils.base import BaseModule
|
||||
from .attention import (
|
||||
BasicTransformerBlock,
|
||||
MemoryEfficientAttentionMixin,
|
||||
)
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class Transformer2D(BaseModule, MemoryEfficientAttentionMixin):
|
||||
"""
|
||||
A 2D Transformer model for image-like data.
|
||||
|
||||
Parameters:
|
||||
num_attention_heads (`int`, *optional*, defaults to 16): The number of heads to use for multi-head attention.
|
||||
attention_head_dim (`int`, *optional*, defaults to 88): The number of channels in each head.
|
||||
in_channels (`int`, *optional*):
|
||||
The number of channels in the input and output (specify if the input is **continuous**).
|
||||
num_layers (`int`, *optional*, defaults to 1): The number of layers of Transformer blocks to use.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
cross_attention_dim (`int`, *optional*): The number of `encoder_hidden_states` dimensions to use.
|
||||
sample_size (`int`, *optional*): The width of the latent images (specify if the input is **discrete**).
|
||||
This is fixed during training since it is used to learn a number of position embeddings.
|
||||
num_vector_embeds (`int`, *optional*):
|
||||
The number of classes of the vector embeddings of the latent pixels (specify if the input is **discrete**).
|
||||
Includes the class for the masked latent pixel.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to use in feed-forward.
|
||||
num_embeds_ada_norm ( `int`, *optional*):
|
||||
The number of diffusion steps used during training. Pass if at least one of the norm_layers is
|
||||
`AdaLayerNorm`. This is fixed during training since it is used to learn a number of embeddings that are
|
||||
added to the hidden states.
|
||||
|
||||
During inference, you can denoise for up to but not more steps than `num_embeds_ada_norm`.
|
||||
attention_bias (`bool`, *optional*):
|
||||
Configure if the `TransformerBlocks` attention should contain a bias parameter.
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
num_attention_heads: int = 16
|
||||
attention_head_dim: int = 88
|
||||
in_channels: Optional[int] = None
|
||||
out_channels: Optional[int] = None
|
||||
num_layers: int = 1
|
||||
dropout: float = 0.0
|
||||
norm_num_groups: int = 32
|
||||
cross_attention_dim: Optional[int] = None
|
||||
attention_bias: bool = False
|
||||
sample_size: Optional[int] = None
|
||||
num_vector_embeds: Optional[int] = None
|
||||
patch_size: Optional[int] = None
|
||||
activation_fn: str = "geglu"
|
||||
num_embeds_ada_norm: Optional[int] = None
|
||||
use_linear_projection: bool = False
|
||||
only_cross_attention: bool = False
|
||||
double_self_attention: bool = False
|
||||
upcast_attention: bool = False
|
||||
norm_type: str = "layer_norm"
|
||||
norm_elementwise_affine: bool = True
|
||||
attention_type: str = "default"
|
||||
enable_memory_efficient_attention: bool = False
|
||||
gradient_checkpointing: bool = False
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
|
||||
self.use_linear_projection = self.cfg.use_linear_projection
|
||||
self.num_attention_heads = self.cfg.num_attention_heads
|
||||
self.attention_head_dim = self.cfg.attention_head_dim
|
||||
inner_dim = self.num_attention_heads * self.attention_head_dim
|
||||
|
||||
conv_cls = nn.Conv2d
|
||||
linear_cls = nn.Linear
|
||||
|
||||
# 1. Transformer2DModel can process both standard continuous images of shape `(batch_size, num_channels, width, height)` as well as quantized image embeddings of shape `(batch_size, num_image_vectors)`
|
||||
# Define whether input is continuous or discrete depending on configuration
|
||||
self.is_input_continuous = (self.cfg.in_channels is not None) and (
|
||||
self.cfg.patch_size is None
|
||||
)
|
||||
self.is_input_vectorized = self.cfg.num_vector_embeds is not None
|
||||
self.is_input_patches = (
|
||||
self.cfg.in_channels is not None and self.cfg.patch_size is not None
|
||||
)
|
||||
|
||||
if (
|
||||
self.cfg.norm_type == "layer_norm"
|
||||
and self.cfg.num_embeds_ada_norm is not None
|
||||
):
|
||||
deprecation_message = (
|
||||
f"The configuration file of this model: {self.__class__} is outdated. `norm_type` is either not set or"
|
||||
" incorrectly set to `'layer_norm'`.Make sure to set `norm_type` to `'ada_norm'` in the config."
|
||||
" Please make sure to update the config accordingly as leaving `norm_type` might led to incorrect"
|
||||
" results in future versions. If you have downloaded this checkpoint from the Hugging Face Hub, it"
|
||||
" would be very nice if you could open a Pull request for the `transformer/config.json` file"
|
||||
)
|
||||
lrm.warn(deprecation_message)
|
||||
self.cfg.norm_type = "ada_norm"
|
||||
|
||||
if self.is_input_continuous and self.is_input_vectorized:
|
||||
raise ValueError(
|
||||
f"Cannot define both `in_channels`: {self.cfg.in_channels} and `num_vector_embeds`: {self.cfg.num_vector_embeds}. Make"
|
||||
" sure that either `in_channels` or `num_vector_embeds` is None."
|
||||
)
|
||||
elif self.is_input_vectorized and self.is_input_patches:
|
||||
raise ValueError(
|
||||
f"Cannot define both `num_vector_embeds`: {self.cfg.num_vector_embeds} and `patch_size`: {self.cfg.patch_size}. Make"
|
||||
" sure that either `num_vector_embeds` or `num_patches` is None."
|
||||
)
|
||||
elif (
|
||||
not self.is_input_continuous
|
||||
and not self.is_input_vectorized
|
||||
and not self.is_input_patches
|
||||
):
|
||||
raise ValueError(
|
||||
f"Has to define `in_channels`: {self.cfg.in_channels}, `num_vector_embeds`: {self.cfg.num_vector_embeds}, or patch_size:"
|
||||
f" {self.cfg.patch_size}. Make sure that `in_channels`, `num_vector_embeds` or `num_patches` is not None."
|
||||
)
|
||||
|
||||
# 2. Define input layers
|
||||
if self.is_input_continuous:
|
||||
self.in_channels = self.cfg.in_channels
|
||||
|
||||
self.norm = torch.nn.GroupNorm(
|
||||
num_groups=self.cfg.norm_num_groups,
|
||||
num_channels=self.cfg.in_channels,
|
||||
eps=1e-6,
|
||||
affine=True,
|
||||
)
|
||||
if self.cfg.use_linear_projection:
|
||||
self.proj_in = linear_cls(self.cfg.in_channels, inner_dim)
|
||||
else:
|
||||
self.proj_in = conv_cls(
|
||||
self.cfg.in_channels, inner_dim, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
elif self.is_input_vectorized:
|
||||
assert (
|
||||
self.cfg.sample_size is not None
|
||||
), "Transformer2DModel over discrete input must provide sample_size"
|
||||
assert (
|
||||
self.cfg.num_vector_embeds is not None
|
||||
), "Transformer2DModel over discrete input must provide num_embed"
|
||||
|
||||
self.height = self.cfg.sample_size
|
||||
self.width = self.cfg.sample_size
|
||||
self.num_vector_embeds = self.cfg.num_vector_embeds
|
||||
self.num_latent_pixels = self.height * self.width
|
||||
|
||||
self.latent_image_embedding = ImagePositionalEmbeddings(
|
||||
num_embed=self.cfg.num_vector_embeds,
|
||||
embed_dim=inner_dim,
|
||||
height=self.height,
|
||||
width=self.width,
|
||||
)
|
||||
elif self.is_input_patches:
|
||||
assert (
|
||||
self.cfg.sample_size is not None
|
||||
), "Transformer2DModel over patched input must provide sample_size"
|
||||
|
||||
self.height = self.cfg.sample_size
|
||||
self.width = self.cfg.sample_size
|
||||
|
||||
self.patch_size = self.cfg.patch_size
|
||||
self.pos_embed = PatchEmbed(
|
||||
height=self.cfg.sample_size,
|
||||
width=self.cfg.sample_size,
|
||||
patch_size=self.cfg.patch_size,
|
||||
in_channels=self.cfg.in_channels,
|
||||
embed_dim=inner_dim,
|
||||
)
|
||||
|
||||
# 3. Define transformers blocks
|
||||
self.transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock(
|
||||
inner_dim,
|
||||
self.num_attention_heads,
|
||||
self.attention_head_dim,
|
||||
dropout=self.cfg.dropout,
|
||||
cross_attention_dim=self.cfg.cross_attention_dim,
|
||||
activation_fn=self.cfg.activation_fn,
|
||||
num_embeds_ada_norm=self.cfg.num_embeds_ada_norm,
|
||||
attention_bias=self.cfg.attention_bias,
|
||||
only_cross_attention=self.cfg.only_cross_attention,
|
||||
double_self_attention=self.cfg.double_self_attention,
|
||||
upcast_attention=self.cfg.upcast_attention,
|
||||
norm_type=self.cfg.norm_type,
|
||||
norm_elementwise_affine=self.cfg.norm_elementwise_affine,
|
||||
attention_type=self.cfg.attention_type,
|
||||
)
|
||||
for d in range(self.cfg.num_layers)
|
||||
]
|
||||
)
|
||||
|
||||
# 4. Define output layers
|
||||
self.out_channels = (
|
||||
self.cfg.in_channels
|
||||
if self.cfg.out_channels is None
|
||||
else self.cfg.out_channels
|
||||
)
|
||||
if self.is_input_continuous:
|
||||
# TODO: should use out_channels for continuous projections
|
||||
if self.cfg.use_linear_projection:
|
||||
self.proj_out = linear_cls(inner_dim, self.cfg.in_channels)
|
||||
else:
|
||||
self.proj_out = conv_cls(
|
||||
inner_dim, self.cfg.in_channels, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
elif self.is_input_vectorized:
|
||||
self.norm_out = nn.LayerNorm(inner_dim)
|
||||
self.out = nn.Linear(inner_dim, self.num_vector_embeds - 1)
|
||||
elif self.is_input_patches:
|
||||
self.norm_out = nn.LayerNorm(inner_dim, elementwise_affine=False, eps=1e-6)
|
||||
self.proj_out_1 = nn.Linear(inner_dim, 2 * inner_dim)
|
||||
self.proj_out_2 = nn.Linear(
|
||||
inner_dim, self.cfg.patch_size * self.cfg.patch_size * self.out_channels
|
||||
)
|
||||
|
||||
self.gradient_checkpointing = self.cfg.gradient_checkpointing
|
||||
|
||||
self.set_use_memory_efficient_attention_xformers(
|
||||
self.cfg.enable_memory_efficient_attention
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
encoder_hidden_states: Optional[torch.Tensor] = None,
|
||||
timestep: Optional[torch.LongTensor] = None,
|
||||
class_labels: Optional[torch.LongTensor] = None,
|
||||
cross_attention_kwargs: Dict[str, Any] = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
encoder_attention_mask: Optional[torch.Tensor] = None,
|
||||
):
|
||||
"""
|
||||
The [`Transformer2DModel`] forward method.
|
||||
|
||||
Args:
|
||||
hidden_states (`torch.LongTensor` of shape `(batch size, num latent pixels)` if discrete, `torch.FloatTensor` of shape `(batch size, channel, height, width)` if continuous):
|
||||
Input `hidden_states`.
|
||||
encoder_hidden_states ( `torch.FloatTensor` of shape `(batch size, sequence len, embed dims)`, *optional*):
|
||||
Conditional embeddings for cross attention layer. If not given, cross-attention defaults to
|
||||
self-attention.
|
||||
timestep ( `torch.LongTensor`, *optional*):
|
||||
Used to indicate denoising step. Optional timestep to be applied as an embedding in `AdaLayerNorm`.
|
||||
class_labels ( `torch.LongTensor` of shape `(batch size, num classes)`, *optional*):
|
||||
Used to indicate class labels conditioning. Optional class labels to be applied as an embedding in
|
||||
`AdaLayerZeroNorm`.
|
||||
cross_attention_kwargs ( `Dict[str, Any]`, *optional*):
|
||||
A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under
|
||||
`self.processor` in
|
||||
[diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
|
||||
attention_mask ( `torch.Tensor`, *optional*):
|
||||
An attention mask of shape `(batch, key_tokens)` is applied to `encoder_hidden_states`. If `1` the mask
|
||||
is kept, otherwise if `0` it is discarded. Mask will be converted into a bias, which adds large
|
||||
negative values to the attention scores corresponding to "discard" tokens.
|
||||
encoder_attention_mask ( `torch.Tensor`, *optional*):
|
||||
Cross-attention mask applied to `encoder_hidden_states`. Two formats supported:
|
||||
|
||||
* Mask `(batch, sequence_length)` True = keep, False = discard.
|
||||
* Bias `(batch, 1, sequence_length)` 0 = keep, -10000 = discard.
|
||||
|
||||
If `ndim == 2`: will be interpreted as a mask, then converted into a bias consistent with the format
|
||||
above. This bias will be added to the cross-attention scores.
|
||||
return_dict (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to return a [`~models.unet_2d_condition.UNet2DConditionOutput`] instead of a plain
|
||||
tuple.
|
||||
|
||||
Returns:
|
||||
If `return_dict` is True, an [`~models.transformer_2d.Transformer2DModelOutput`] is returned, otherwise a
|
||||
`tuple` where the first element is the sample tensor.
|
||||
"""
|
||||
# ensure attention_mask is a bias, and give it a singleton query_tokens dimension.
|
||||
# we may have done this conversion already, e.g. if we came here via UNet2DConditionModel#forward.
|
||||
# we can tell by counting dims; if ndim == 2: it's a mask rather than a bias.
|
||||
# expects mask of shape:
|
||||
# [batch, key_tokens]
|
||||
# adds singleton query_tokens dimension:
|
||||
# [batch, 1, key_tokens]
|
||||
# this helps to broadcast it as a bias over attention scores, which will be in one of the following shapes:
|
||||
# [batch, heads, query_tokens, key_tokens] (e.g. torch sdp attn)
|
||||
# [batch * heads, query_tokens, key_tokens] (e.g. xformers or classic attn)
|
||||
if attention_mask is not None and attention_mask.ndim == 2:
|
||||
# assume that mask is expressed as:
|
||||
# (1 = keep, 0 = discard)
|
||||
# convert mask into a bias that can be added to attention scores:
|
||||
# (keep = +0, discard = -10000.0)
|
||||
attention_mask = (1 - attention_mask.to(hidden_states.dtype)) * -10000.0
|
||||
attention_mask = attention_mask.unsqueeze(1)
|
||||
|
||||
# convert encoder_attention_mask to a bias the same way we do for attention_mask
|
||||
if encoder_attention_mask is not None and encoder_attention_mask.ndim == 2:
|
||||
encoder_attention_mask = (
|
||||
1 - encoder_attention_mask.to(hidden_states.dtype)
|
||||
) * -10000.0
|
||||
encoder_attention_mask = encoder_attention_mask.unsqueeze(1)
|
||||
|
||||
# 1. Input
|
||||
if self.is_input_continuous:
|
||||
batch, _, height, width = hidden_states.shape
|
||||
residual = hidden_states
|
||||
|
||||
hidden_states = self.norm(hidden_states)
|
||||
if not self.use_linear_projection:
|
||||
hidden_states = self.proj_in(hidden_states)
|
||||
inner_dim = hidden_states.shape[1]
|
||||
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(
|
||||
batch, height * width, inner_dim
|
||||
)
|
||||
else:
|
||||
inner_dim = hidden_states.shape[1]
|
||||
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(
|
||||
batch, height * width, inner_dim
|
||||
)
|
||||
hidden_states = self.proj_in(hidden_states)
|
||||
|
||||
elif self.is_input_vectorized:
|
||||
hidden_states = self.latent_image_embedding(hidden_states)
|
||||
elif self.is_input_patches:
|
||||
hidden_states = self.pos_embed(hidden_states)
|
||||
|
||||
# 2. Blocks
|
||||
for block in self.transformer_blocks:
|
||||
if self.training and self.gradient_checkpointing:
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
block,
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
timestep,
|
||||
cross_attention_kwargs,
|
||||
class_labels,
|
||||
use_reentrant=False,
|
||||
)
|
||||
else:
|
||||
hidden_states = block(
|
||||
hidden_states,
|
||||
attention_mask=attention_mask,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
timestep=timestep,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
class_labels=class_labels,
|
||||
)
|
||||
|
||||
# 3. Output
|
||||
if self.is_input_continuous:
|
||||
if not self.use_linear_projection:
|
||||
hidden_states = (
|
||||
hidden_states.reshape(batch, height, width, inner_dim)
|
||||
.permute(0, 3, 1, 2)
|
||||
.contiguous()
|
||||
)
|
||||
hidden_states = self.proj_out(hidden_states)
|
||||
else:
|
||||
hidden_states = self.proj_out(hidden_states)
|
||||
hidden_states = (
|
||||
hidden_states.reshape(batch, height, width, inner_dim)
|
||||
.permute(0, 3, 1, 2)
|
||||
.contiguous()
|
||||
)
|
||||
|
||||
output = hidden_states + residual
|
||||
elif self.is_input_vectorized:
|
||||
hidden_states = self.norm_out(hidden_states)
|
||||
logits = self.out(hidden_states)
|
||||
# (batch, self.num_vector_embeds - 1, self.num_latent_pixels)
|
||||
logits = logits.permute(0, 2, 1)
|
||||
|
||||
# log(p(x_0))
|
||||
output = F.log_softmax(logits.double(), dim=1).float()
|
||||
elif self.is_input_patches:
|
||||
# TODO: cleanup!
|
||||
conditioning = self.transformer_blocks[0].norm1.emb(
|
||||
timestep, class_labels, hidden_dtype=hidden_states.dtype
|
||||
)
|
||||
shift, scale = self.proj_out_1(F.silu(conditioning)).chunk(2, dim=1)
|
||||
hidden_states = (
|
||||
self.norm_out(hidden_states) * (1 + scale[:, None]) + shift[:, None]
|
||||
)
|
||||
hidden_states = self.proj_out_2(hidden_states)
|
||||
|
||||
# unpatchify
|
||||
height = width = int(hidden_states.shape[1] ** 0.5)
|
||||
hidden_states = hidden_states.reshape(
|
||||
shape=(
|
||||
-1,
|
||||
height,
|
||||
width,
|
||||
self.patch_size,
|
||||
self.patch_size,
|
||||
self.out_channels,
|
||||
)
|
||||
)
|
||||
hidden_states = torch.einsum("nhwpqc->nchpwq", hidden_states)
|
||||
output = hidden_states.reshape(
|
||||
shape=(
|
||||
-1,
|
||||
self.out_channels,
|
||||
height * self.patch_size,
|
||||
width * self.patch_size,
|
||||
)
|
||||
)
|
||||
|
||||
return output
|
||||
@@ -0,0 +1,283 @@
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import pytorch_lightning as pl
|
||||
import torch.nn.functional as F
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from .utils import parse_optimizer, parse_scheduler
|
||||
from ..utils.base import (
|
||||
Updateable,
|
||||
update_end_if_possible,
|
||||
update_if_possible,
|
||||
)
|
||||
from ..models.exporters.base import Exporter, ExporterOutput
|
||||
from ..utils.config import parse_structured
|
||||
from ..utils.misc import C, cleanup, get_device, load_module_weights
|
||||
#from ..utils.saving import SaverMixin
|
||||
from ..utils.typing import *
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseLossConfig:
|
||||
pass
|
||||
|
||||
#class BaseSystem(pl.LightningModule, Updateable, SaverMixin):
|
||||
class BaseSystem(pl.LightningModule, Updateable):
|
||||
@dataclass
|
||||
class Config:
|
||||
#loss: BaseLossConfig = BaseLossConfig()
|
||||
optimizer: dict = field(default_factory=dict)
|
||||
scheduler: Optional[dict] = None
|
||||
weights: Optional[str] = None
|
||||
weights_ignore_modules: Optional[List[str]] = None
|
||||
weights_mapping: Optional[List[Dict[str, str]]] = None
|
||||
check_train_every_n_steps: int = 0
|
||||
check_val_limit_rank: int = 8
|
||||
cleanup_after_validation_step: bool = False
|
||||
cleanup_after_test_step: bool = False
|
||||
|
||||
exporter_cls: str = "CharacterGen.Stage_3D.lrm.models.exporters.mesh_exporter.MeshExporter"
|
||||
exporter: dict = field(default_factory=lambda: {"fmt": "obj", "save_uv": False})
|
||||
|
||||
cfg: Config
|
||||
|
||||
def __init__(self, cfg, resumed=False) -> None:
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
self._save_dir: Optional[str] = None
|
||||
self._resumed: bool = resumed
|
||||
self._resumed_eval: bool = False
|
||||
self._resumed_eval_status: dict = {"global_step": 0, "current_epoch": 0}
|
||||
|
||||
self.configure()
|
||||
print(self.cfg.weights)
|
||||
if self.cfg.weights is not None:
|
||||
self.load_weights(
|
||||
self.cfg.weights,
|
||||
self.cfg.weights_ignore_modules,
|
||||
self.cfg.weights_mapping,
|
||||
)
|
||||
print("finish loading!!")
|
||||
self.post_configure()
|
||||
|
||||
def load_weights(
|
||||
self,
|
||||
weights: str,
|
||||
ignore_modules: Optional[List[str]] = None,
|
||||
mapping: Optional[List[Dict[str, str]]] = None,
|
||||
):
|
||||
state_dict, epoch, global_step = load_module_weights(
|
||||
weights,
|
||||
ignore_modules=ignore_modules,
|
||||
mapping=mapping,
|
||||
map_location="cpu",
|
||||
)
|
||||
self.load_state_dict(state_dict, strict=False)
|
||||
# restore step-dependent states
|
||||
self.do_update_step(epoch, global_step, on_load_weights=True)
|
||||
|
||||
def set_resume_status(self, current_epoch: int, global_step: int):
|
||||
# restore correct epoch and global step in eval
|
||||
self._resumed_eval = True
|
||||
self._resumed_eval_status["current_epoch"] = current_epoch
|
||||
self._resumed_eval_status["global_step"] = global_step
|
||||
|
||||
@property
|
||||
def resumed(self):
|
||||
# whether from resumed checkpoint
|
||||
return self._resumed
|
||||
|
||||
@property
|
||||
def true_global_step(self):
|
||||
if self._resumed_eval:
|
||||
return self._resumed_eval_status["global_step"]
|
||||
else:
|
||||
return self.global_step
|
||||
|
||||
@property
|
||||
def true_current_epoch(self):
|
||||
if self._resumed_eval:
|
||||
return self._resumed_eval_status["current_epoch"]
|
||||
else:
|
||||
return self.current_epoch
|
||||
|
||||
def configure(self) -> None:
|
||||
pass
|
||||
|
||||
def post_configure(self) -> None:
|
||||
"""
|
||||
executed after weights are loaded
|
||||
"""
|
||||
pass
|
||||
|
||||
def C(self, value: Any) -> float:
|
||||
return C(value, self.true_current_epoch, self.true_global_step)
|
||||
|
||||
def configure_optimizers(self):
|
||||
optim = parse_optimizer(self.cfg.optimizer, self)
|
||||
ret = {
|
||||
"optimizer": optim,
|
||||
}
|
||||
if self.cfg.scheduler is not None:
|
||||
ret.update(
|
||||
{
|
||||
"lr_scheduler": parse_scheduler(self.cfg.scheduler, optim),
|
||||
}
|
||||
)
|
||||
return ret
|
||||
|
||||
def on_fit_start(self) -> None:
|
||||
if self._save_dir is not None:
|
||||
lrm.info(f"Validation results will be saved to {self._save_dir}")
|
||||
else:
|
||||
lrm.warn(
|
||||
f"Saving directory not set for the system, visualization results will not be saved"
|
||||
)
|
||||
|
||||
def training_step(self, batch, batch_idx):
|
||||
raise NotImplementedError
|
||||
|
||||
def check_train(self, batch, outputs, **kwargs):
|
||||
if (
|
||||
self.global_rank == 0
|
||||
and self.cfg.check_train_every_n_steps > 0
|
||||
and self.true_global_step % self.cfg.check_train_every_n_steps == 0
|
||||
):
|
||||
self.on_check_train(batch, outputs, **kwargs)
|
||||
|
||||
def on_check_train(self, batch, outputs, **kwargs):
|
||||
pass
|
||||
|
||||
def validation_step(self, batch, batch_idx):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_validation_epoch_end(self):
|
||||
pass
|
||||
|
||||
def test_step(self, batch, batch_idx):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_test_epoch_end(self):
|
||||
pass
|
||||
|
||||
def on_test_end(self) -> None:
|
||||
if self._save_dir is not None:
|
||||
lrm.info(f"Test results saved to {self._save_dir}")
|
||||
|
||||
def on_predict_start(self) -> None:
|
||||
pass
|
||||
|
||||
def predict_step(self, batch, batch_idx):
|
||||
batch_size = batch["index"].shape[0]
|
||||
scene_codes = self(batch)
|
||||
for b in range(batch_size):
|
||||
if batch["view_index"][b, 0] == 0:
|
||||
exporter_output: List[ExporterOutput] = self.exporter(
|
||||
batch["index"][b][None], scene_codes[b][None]
|
||||
)
|
||||
for out in exporter_output:
|
||||
save_func_name = f"save_{out.save_type}"
|
||||
if not hasattr(self, save_func_name):
|
||||
raise ValueError(
|
||||
f"{save_func_name} not supported by the SaverMixin"
|
||||
)
|
||||
save_func = getattr(self, save_func_name)
|
||||
save_func(
|
||||
f"it{self.true_global_step}-export/{out.save_name}",
|
||||
**out.params,
|
||||
)
|
||||
if self.exporter.cfg.save_video:
|
||||
self.test_step(batch, batch_idx)
|
||||
|
||||
def on_predict_epoch_end(self) -> None:
|
||||
if self.exporter.cfg.save_video:
|
||||
self.on_test_epoch_end()
|
||||
|
||||
def on_predict_end(self) -> None:
|
||||
if self._save_dir is not None:
|
||||
lrm.info(f"Export assets saved to {self._save_dir}")
|
||||
|
||||
def preprocess_data(self, batch, stage):
|
||||
pass
|
||||
|
||||
"""
|
||||
Implementing on_after_batch_transfer of DataModule does the same.
|
||||
But on_after_batch_transfer does not support DP.
|
||||
"""
|
||||
|
||||
def on_train_batch_start(self, batch, batch_idx, unused=0):
|
||||
self.preprocess_data(batch, "train")
|
||||
self.dataset = self.trainer.train_dataloader.dataset
|
||||
update_if_possible(self.dataset, self.true_current_epoch, self.true_global_step)
|
||||
self.do_update_step(self.true_current_epoch, self.true_global_step)
|
||||
|
||||
def on_validation_batch_start(self, batch, batch_idx, dataloader_idx=0):
|
||||
self.preprocess_data(batch, "validation")
|
||||
self.dataset = self.trainer.val_dataloaders.dataset
|
||||
update_if_possible(self.dataset, self.true_current_epoch, self.true_global_step)
|
||||
self.do_update_step(self.true_current_epoch, self.true_global_step)
|
||||
|
||||
def on_test_batch_start(self, batch, batch_idx, dataloader_idx=0):
|
||||
self.preprocess_data(batch, "test")
|
||||
self.dataset = self.trainer.test_dataloaders.dataset
|
||||
update_if_possible(self.dataset, self.true_current_epoch, self.true_global_step)
|
||||
self.do_update_step(self.true_current_epoch, self.true_global_step)
|
||||
|
||||
def on_predict_batch_start(self, batch, batch_idx, dataloader_idx=0):
|
||||
self.preprocess_data(batch, "predict")
|
||||
self.dataset = self.trainer.predict_dataloaders.dataset
|
||||
update_if_possible(self.dataset, self.true_current_epoch, self.true_global_step)
|
||||
self.do_update_step(self.true_current_epoch, self.true_global_step)
|
||||
|
||||
def on_train_batch_end(self, outputs, batch, batch_idx):
|
||||
self.dataset = self.trainer.train_dataloader.dataset
|
||||
update_end_if_possible(
|
||||
self.dataset, self.true_current_epoch, self.true_global_step
|
||||
)
|
||||
self.do_update_step_end(self.true_current_epoch, self.true_global_step)
|
||||
|
||||
def on_validation_batch_end(self, outputs, batch, batch_idx):
|
||||
self.dataset = self.trainer.val_dataloaders.dataset
|
||||
update_end_if_possible(
|
||||
self.dataset, self.true_current_epoch, self.true_global_step
|
||||
)
|
||||
self.do_update_step_end(self.true_current_epoch, self.true_global_step)
|
||||
if self.cfg.cleanup_after_validation_step:
|
||||
# cleanup to save vram
|
||||
cleanup()
|
||||
|
||||
def on_test_batch_end(self, outputs, batch, batch_idx):
|
||||
self.dataset = self.trainer.test_dataloaders.dataset
|
||||
update_end_if_possible(
|
||||
self.dataset, self.true_current_epoch, self.true_global_step
|
||||
)
|
||||
self.do_update_step_end(self.true_current_epoch, self.true_global_step)
|
||||
if self.cfg.cleanup_after_test_step:
|
||||
# cleanup to save vram
|
||||
cleanup()
|
||||
|
||||
def on_predict_batch_end(self, outputs, batch, batch_idx):
|
||||
self.dataset = self.trainer.predict_dataloaders.dataset
|
||||
update_end_if_possible(
|
||||
self.dataset, self.true_current_epoch, self.true_global_step
|
||||
)
|
||||
self.do_update_step_end(self.true_current_epoch, self.true_global_step)
|
||||
if self.cfg.cleanup_after_test_step:
|
||||
# cleanup to save vram
|
||||
cleanup()
|
||||
|
||||
def update_step(self, epoch: int, global_step: int, on_load_weights: bool = False):
|
||||
pass
|
||||
|
||||
def on_before_optimizer_step(self, optimizer):
|
||||
"""
|
||||
# some gradient-related debugging goes here, example:
|
||||
from lightning.pytorch.utilities import grad_norm
|
||||
norms = grad_norm(self.geometry, norm_type=2)
|
||||
print(norms)
|
||||
for name, p in self.named_parameters():
|
||||
if p.grad is None:
|
||||
lrm.info(f"{name} does not receive gradients!")
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,342 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from einops import rearrange
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from CharacterGen.Stage_3D.lrm.models.mesh import Mesh
|
||||
from CharacterGen.Stage_3D.lrm.systems.base import BaseLossConfig, BaseSystem
|
||||
from CharacterGen.Stage_3D.lrm.utils.ops import binary_cross_entropy, get_plucker_rays
|
||||
from CharacterGen.Stage_3D.lrm.utils.typing import *
|
||||
from CharacterGen.Stage_3D.lrm.models.lpips import LPIPS
|
||||
from CharacterGen.Stage_3D.lrm.utils.misc import time_recorder as tr
|
||||
|
||||
|
||||
@dataclass
|
||||
class MultiviewLRMLossConfig(BaseLossConfig):
|
||||
lambda_mse: Any = 0.0
|
||||
lambda_mse_coarse: Any = 0.0
|
||||
lambda_smooth_l1: Any = 0.0
|
||||
lambda_smooth_l1_coarse: Any = 0.0
|
||||
lambda_lpips: Any = 0.0
|
||||
lambda_lpips_coarse: Any = 0.0
|
||||
lambda_mask: Any = 0.0
|
||||
lambda_mask_coarse: Any = 0.0
|
||||
|
||||
|
||||
class MultiviewLRM(BaseSystem):
|
||||
@dataclass
|
||||
class Config(BaseSystem.Config):
|
||||
#loss: MultiviewLRMLossConfig = MultiviewLRMLossConfig()
|
||||
|
||||
camera_embedder_cls: str = ""
|
||||
camera_embedder: dict = field(default_factory=dict)
|
||||
|
||||
image_tokenizer_cls: str = ""
|
||||
image_tokenizer: dict = field(default_factory=dict)
|
||||
|
||||
tokenizer_cls: str = ""
|
||||
tokenizer: dict = field(default_factory=dict)
|
||||
|
||||
backbone_cls: str = ""
|
||||
backbone: dict = field(default_factory=dict)
|
||||
|
||||
post_processor_cls: str = ""
|
||||
post_processor: dict = field(default_factory=dict)
|
||||
|
||||
decoder_cls: str = ""
|
||||
decoder: dict = field(default_factory=dict)
|
||||
|
||||
material_cls: str = ""
|
||||
material: dict = field(default_factory=dict)
|
||||
|
||||
background_cls: str = ""
|
||||
background: dict = field(default_factory=dict)
|
||||
|
||||
renderer_cls: str = ""
|
||||
renderer: dict = field(default_factory=dict)
|
||||
|
||||
resume_ckpt_path: str = ""
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self):
|
||||
super().configure()
|
||||
self.image_tokenizer = lrm.find(self.cfg.image_tokenizer_cls)(
|
||||
self.cfg.image_tokenizer
|
||||
)
|
||||
if self.cfg.image_tokenizer.modulation:
|
||||
self.camera_embedder = lrm.find(self.cfg.camera_embedder_cls)(
|
||||
self.cfg.camera_embedder
|
||||
)
|
||||
self.tokenizer = lrm.find(self.cfg.tokenizer_cls)(self.cfg.tokenizer)
|
||||
self.backbone = lrm.find(self.cfg.backbone_cls)(self.cfg.backbone)
|
||||
self.post_processor = lrm.find(self.cfg.post_processor_cls)(
|
||||
self.cfg.post_processor
|
||||
)
|
||||
self.decoder = lrm.find(self.cfg.decoder_cls)(self.cfg.decoder)
|
||||
self.material = lrm.find(self.cfg.material_cls)(self.cfg.material)
|
||||
self.background = lrm.find(self.cfg.background_cls)(self.cfg.background)
|
||||
self.renderer = lrm.find(self.cfg.renderer_cls)(
|
||||
self.cfg.renderer, self.decoder, self.material, self.background
|
||||
)
|
||||
|
||||
self.exporter = lrm.find(self.cfg.exporter_cls)(
|
||||
self.cfg.exporter, self.renderer
|
||||
)
|
||||
|
||||
def on_fit_start(self):
|
||||
super().on_fit_start()
|
||||
self.lpips_loss_fn = LPIPS()
|
||||
|
||||
def forward(self, batch: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# batch["rgb_cond"]: B, N_cond, H, W, 3
|
||||
# batch["rgb"]: B, N_render, H, W, 3
|
||||
# batch["c2w_cond"]: B, N_cond, 4, 4
|
||||
# for single image input (like LRM), N_cond = 1
|
||||
|
||||
batch_size, n_input_views = batch["rgb_cond"].shape[:2]
|
||||
|
||||
# Camera modulation
|
||||
camera_embeds: Optional[Float[Tensor, "B Nv Cc"]]
|
||||
if self.cfg.image_tokenizer.modulation:
|
||||
camera_embeds = self.camera_embedder(**batch)
|
||||
else:
|
||||
camera_embeds = None
|
||||
|
||||
tr.start("image tokenizer")
|
||||
input_image_tokens: Float[Tensor, "B Nv Cit Nit"] = self.image_tokenizer(
|
||||
rearrange(batch["rgb_cond"], "B Nv H W C -> B Nv C H W"),
|
||||
modulation_cond=camera_embeds,
|
||||
plucker_rays=rearrange(
|
||||
get_plucker_rays(batch["rays_o_cond"], batch["rays_d_cond"]),
|
||||
"B Nv H W C -> B Nv C H W",
|
||||
)
|
||||
if "rays_o_cond" in batch
|
||||
else None,
|
||||
)
|
||||
tr.end("image tokenizer")
|
||||
|
||||
input_image_tokens = rearrange(
|
||||
input_image_tokens, "B Nv C Nt -> B (Nv Nt) C", Nv=n_input_views
|
||||
)
|
||||
|
||||
tokens: Float[Tensor, "B Ct Nt"] = self.tokenizer(batch_size)
|
||||
|
||||
tr.start("backbone")
|
||||
tokens = self.backbone(
|
||||
tokens,
|
||||
encoder_hidden_states=input_image_tokens,
|
||||
modulation_cond=None,
|
||||
)
|
||||
tr.end("backbone")
|
||||
|
||||
scene_codes = self.post_processor(self.tokenizer.detokenize(tokens))
|
||||
return scene_codes
|
||||
|
||||
def forward_renderer_nerf(
|
||||
self, batch: Dict[str, Any], scene_codes
|
||||
) -> Dict[str, Any]:
|
||||
tr.start("render")
|
||||
render_out = self.renderer(scene_codes, **batch)
|
||||
tr.end("render")
|
||||
return render_out
|
||||
|
||||
def training_step(self, batch, batch_idx):
|
||||
scene_codes = self(batch)
|
||||
out = self.forward_renderer_nerf(batch, scene_codes)
|
||||
|
||||
loss = 0.0
|
||||
|
||||
for suffix in ["", "_coarse"]:
|
||||
if not f"comp_rgb{suffix}" in out:
|
||||
continue
|
||||
|
||||
comp_rgb: Float[Tensor, "B Nv H W 3"] = out["comp_rgb{}".format(suffix)]
|
||||
gt_rgb: Float[Tensor, "B Nv H W 3"] = batch["rgb"]
|
||||
|
||||
self.log(f"train/comp_rgb_min{suffix}", comp_rgb.min())
|
||||
|
||||
loss_mse = F.mse_loss(comp_rgb, gt_rgb, reduction="mean")
|
||||
self.log(f"train/loss_mse{suffix}", loss_mse)
|
||||
loss += loss_mse * self.C(self.cfg.loss[f"lambda_mse{suffix}"])
|
||||
|
||||
loss_smooth_l1 = F.smooth_l1_loss(
|
||||
comp_rgb, gt_rgb, beta=0.1, reduction="mean"
|
||||
)
|
||||
self.log(f"train/loss_smooth_l1{suffix}", loss_smooth_l1)
|
||||
loss += loss_smooth_l1 * self.C(self.cfg.loss[f"lambda_smooth_l1{suffix}"])
|
||||
|
||||
if self.C(self.cfg.loss[f"lambda_lpips{suffix}"]) > 0:
|
||||
loss_lpips = self.lpips_loss_fn(
|
||||
rearrange(comp_rgb, "B Nv H W C -> (B Nv) C H W"),
|
||||
rearrange(gt_rgb, "B Nv H W C -> (B Nv) C H W"),
|
||||
input_range=(0, 1),
|
||||
).mean()
|
||||
self.log(f"train/loss_lpips{suffix}", loss_lpips)
|
||||
loss += loss_lpips * self.C(self.cfg.loss[f"lambda_lpips{suffix}"])
|
||||
|
||||
loss_mask = binary_cross_entropy(
|
||||
out[f"opacity{suffix}"].clamp(1e-5, 1 - 1e-5), batch["mask"]
|
||||
)
|
||||
self.log(f"train/loss_mask{suffix}", loss_mask)
|
||||
loss += loss_mask * self.C(self.cfg.loss[f"lambda_mask{suffix}"])
|
||||
|
||||
for name, value in self.cfg.loss.items():
|
||||
self.log(f"train_params/{name}", self.C(value))
|
||||
|
||||
# will execute self.on_check_train every self.cfg.check_train_every_n_steps steps
|
||||
self.check_train(
|
||||
batch,
|
||||
out,
|
||||
extra=f"m{loss_mse:.2f}_l{loss_smooth_l1:.2f}_p{loss_lpips:.2f}_ma{loss_mask:.2f}",
|
||||
)
|
||||
|
||||
return {"loss": loss}
|
||||
|
||||
def get_input_visualizations(self, batch):
|
||||
return [
|
||||
{
|
||||
"type": "rgb",
|
||||
"img": rearrange(batch["rgb_cond"], "B N H W C -> (B H) (N W) C"),
|
||||
"kwargs": {"data_format": "HWC"},
|
||||
}
|
||||
]
|
||||
|
||||
def get_output_visualizations(self, batch, outputs):
|
||||
out = outputs
|
||||
images = []
|
||||
if "rgb" in batch:
|
||||
images += [
|
||||
{
|
||||
"type": "rgb",
|
||||
"img": rearrange(batch["rgb"], "B N H W C -> (B H) (N W) C"),
|
||||
"kwargs": {"data_format": "HWC"},
|
||||
},
|
||||
{
|
||||
"type": "grayscale",
|
||||
"img": rearrange(batch["mask"], "B N H W C -> (B H) (N W) C")[
|
||||
..., 0
|
||||
],
|
||||
"kwargs": {"cmap": None, "data_range": None},
|
||||
},
|
||||
]
|
||||
for suffix in ["", "_coarse"]:
|
||||
if not f"comp_rgb{suffix}" in out:
|
||||
continue
|
||||
images += [
|
||||
{
|
||||
"type": "rgb",
|
||||
"img": rearrange(
|
||||
out[f"comp_rgb{suffix}"], "B N H W C -> (B H) (N W) C"
|
||||
),
|
||||
"kwargs": {"data_format": "HWC"},
|
||||
},
|
||||
{
|
||||
"type": "grayscale",
|
||||
"img": rearrange(
|
||||
out[f"opacity{suffix}"], "B N H W C -> (B H) (N W) C"
|
||||
)[..., 0],
|
||||
"kwargs": {"cmap": None, "data_range": None},
|
||||
},
|
||||
{
|
||||
"type": "grayscale",
|
||||
"img": rearrange(
|
||||
out[f"depth{suffix}"], "B N H W C -> (B H) (N W) C"
|
||||
)[..., 0],
|
||||
"kwargs": {"cmap": None, "data_range": None},
|
||||
},
|
||||
]
|
||||
return images
|
||||
|
||||
# def check_train(self, batch, outputs, **kwargs):
|
||||
# self.on_check_train(batch, outputs, **kwargs)
|
||||
|
||||
def on_check_train(self, batch, outputs, extra=""):
|
||||
self.save_image_grid(
|
||||
f"it{self.true_global_step}-train.jpg",
|
||||
self.get_output_visualizations(batch, outputs),
|
||||
name="train_step_output",
|
||||
step=self.true_global_step,
|
||||
)
|
||||
# self.save_image_grid(
|
||||
# f"debug/it{self.true_global_step}-{self.global_rank}-{extra}.jpg",
|
||||
# self.get_output_visualizations(batch, outputs),
|
||||
# name="train_step_output",
|
||||
# step=self.true_global_step,
|
||||
# )
|
||||
# self.save_json(
|
||||
# f"debug_list/it{self.true_global_step}-{self.global_rank}-ids.json",
|
||||
# batch["scene_id"],
|
||||
# )
|
||||
|
||||
def validation_step(self, batch, batch_idx):
|
||||
scene_codes = self(batch)
|
||||
out = self.forward_renderer_nerf(batch, scene_codes)
|
||||
if (
|
||||
self.cfg.check_val_limit_rank > 0
|
||||
and self.global_rank < self.cfg.check_val_limit_rank
|
||||
):
|
||||
self.save_image_grid(
|
||||
f"it{self.true_global_step}-validation-{self.global_rank}_{batch_idx}-input.jpg",
|
||||
self.get_input_visualizations(batch),
|
||||
name=f"validation_step_input_{self.global_rank}_{batch_idx}",
|
||||
step=self.true_global_step,
|
||||
)
|
||||
self.save_image_grid(
|
||||
f"it{self.true_global_step}-validation-{self.global_rank}_{batch_idx}.jpg",
|
||||
self.get_output_visualizations(batch, out),
|
||||
name=f"validation_step_output_{self.global_rank}_{batch_idx}",
|
||||
step=self.true_global_step,
|
||||
)
|
||||
|
||||
def test_step(self, batch, batch_idx):
|
||||
# not saved to wandb
|
||||
scene_codes = self(batch)
|
||||
out = self.forward_renderer_nerf(batch, scene_codes)
|
||||
batch_size = batch["index"].shape[0]
|
||||
for b in range(batch_size):
|
||||
if batch["view_index"][b, 0] == 0:
|
||||
self.save_image_grid(
|
||||
f"it{self.true_global_step}-test/{batch['index'][b]}-input.jpg",
|
||||
[
|
||||
{
|
||||
"type": "rgb",
|
||||
"img": rearrange(
|
||||
batch["rgb_cond"][b], "N H W C -> H (N W) C"
|
||||
),
|
||||
"kwargs": {"data_format": "HWC"},
|
||||
},
|
||||
],
|
||||
)
|
||||
self.save_image_grid(
|
||||
f"it{self.true_global_step}-test/{batch['index'][b]}/{batch['view_index'][b,0]}.png",
|
||||
[
|
||||
{
|
||||
"type": "rgb",
|
||||
"img": out["comp_rgb"][b][0],
|
||||
"kwargs": {"data_format": "HWC"},
|
||||
},
|
||||
{
|
||||
"type": "grayscale",
|
||||
"img": out["depth"][b][0, ..., 0],
|
||||
"kwargs": {"cmap": None, "data_range": None},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
def on_test_end(self):
|
||||
if self.global_rank == 0:
|
||||
self.save_img_sequences(
|
||||
f"it{self.true_global_step}-test",
|
||||
"(\d+)\.png",
|
||||
save_format="mp4",
|
||||
fps=30,
|
||||
)
|
||||
|
||||
def get_geometry(self, scene_code: torch.Tensor) -> Mesh:
|
||||
tr.start("Surface extraction")
|
||||
mesh: Mesh = self.renderer.isosurface(scene_code)
|
||||
tr.end("Surface extraction")
|
||||
return mesh
|
||||
@@ -0,0 +1,100 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.optim import lr_scheduler
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
|
||||
|
||||
def get_scheduler(name):
|
||||
if hasattr(lr_scheduler, name):
|
||||
return getattr(lr_scheduler, name)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def getattr_recursive(m, attr):
|
||||
for name in attr.split("."):
|
||||
m = getattr(m, name)
|
||||
return m
|
||||
|
||||
|
||||
def get_parameters(model, name):
|
||||
module = getattr_recursive(model, name)
|
||||
if isinstance(module, nn.Module):
|
||||
return module.parameters()
|
||||
elif isinstance(module, nn.Parameter):
|
||||
return module
|
||||
return []
|
||||
|
||||
|
||||
def parse_optimizer(config, model):
|
||||
if hasattr(config, "params"):
|
||||
params = [
|
||||
{"params": get_parameters(model, name), "name": name, **args}
|
||||
for name, args in config.params.items()
|
||||
]
|
||||
lrm.debug(f"Specify optimizer params: {config.params}")
|
||||
else:
|
||||
params = model.parameters()
|
||||
if config.name in ["FusedAdam"]:
|
||||
import apex
|
||||
|
||||
optim = getattr(apex.optimizers, config.name)(params, **config.args)
|
||||
elif config.name in ["Adam8bit", "AdamW8bit"]:
|
||||
import bitsandbytes as bnb
|
||||
|
||||
optim = bnb.optim.Adam8bit(params, **config.args)
|
||||
else:
|
||||
optim = getattr(torch.optim, config.name)(params, **config.args)
|
||||
return optim
|
||||
|
||||
|
||||
def parse_scheduler_to_instance(config, optimizer):
|
||||
if config.name == "ChainedScheduler":
|
||||
schedulers = [
|
||||
parse_scheduler_to_instance(conf, optimizer) for conf in config.schedulers
|
||||
]
|
||||
scheduler = lr_scheduler.ChainedScheduler(schedulers)
|
||||
elif config.name == "Sequential":
|
||||
schedulers = [
|
||||
parse_scheduler_to_instance(conf, optimizer) for conf in config.schedulers
|
||||
]
|
||||
scheduler = lr_scheduler.SequentialLR(
|
||||
optimizer, schedulers, milestones=config.milestones
|
||||
)
|
||||
else:
|
||||
scheduler = getattr(lr_scheduler, config.name)(optimizer, **config.args)
|
||||
return scheduler
|
||||
|
||||
|
||||
def parse_scheduler(config, optimizer):
|
||||
interval = config.get("interval", "epoch")
|
||||
assert interval in ["epoch", "step"]
|
||||
if config.name == "SequentialLR":
|
||||
scheduler = {
|
||||
"scheduler": lr_scheduler.SequentialLR(
|
||||
optimizer,
|
||||
[
|
||||
parse_scheduler(conf, optimizer)["scheduler"]
|
||||
for conf in config.schedulers
|
||||
],
|
||||
milestones=config.milestones,
|
||||
),
|
||||
"interval": interval,
|
||||
}
|
||||
elif config.name == "ChainedScheduler":
|
||||
scheduler = {
|
||||
"scheduler": lr_scheduler.ChainedScheduler(
|
||||
[
|
||||
parse_scheduler(conf, optimizer)["scheduler"]
|
||||
for conf in config.schedulers
|
||||
]
|
||||
),
|
||||
"interval": interval,
|
||||
}
|
||||
else:
|
||||
scheduler = {
|
||||
"scheduler": get_scheduler(config.name)(optimizer, **config.args),
|
||||
"interval": interval,
|
||||
}
|
||||
return scheduler
|
||||
@@ -0,0 +1,123 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from .config import parse_structured
|
||||
from .misc import get_device, load_module_weights
|
||||
from .typing import *
|
||||
|
||||
|
||||
class Configurable:
|
||||
@dataclass
|
||||
class Config:
|
||||
pass
|
||||
|
||||
def __init__(self, cfg: Optional[dict] = None) -> None:
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
|
||||
|
||||
class Updateable:
|
||||
def do_update_step(
|
||||
self, epoch: int, global_step: int, on_load_weights: bool = False
|
||||
):
|
||||
for attr in self.__dir__():
|
||||
if attr.startswith("_"):
|
||||
continue
|
||||
try:
|
||||
module = getattr(self, attr)
|
||||
except:
|
||||
continue # ignore attributes like property, which can't be retrived using getattr?
|
||||
if isinstance(module, Updateable):
|
||||
module.do_update_step(
|
||||
epoch, global_step, on_load_weights=on_load_weights
|
||||
)
|
||||
self.update_step(epoch, global_step, on_load_weights=on_load_weights)
|
||||
|
||||
def do_update_step_end(self, epoch: int, global_step: int):
|
||||
for attr in self.__dir__():
|
||||
if attr.startswith("_"):
|
||||
continue
|
||||
try:
|
||||
module = getattr(self, attr)
|
||||
except:
|
||||
continue # ignore attributes like property, which can't be retrived using getattr?
|
||||
if isinstance(module, Updateable):
|
||||
module.do_update_step_end(epoch, global_step)
|
||||
self.update_step_end(epoch, global_step)
|
||||
|
||||
def update_step(self, epoch: int, global_step: int, on_load_weights: bool = False):
|
||||
# override this method to implement custom update logic
|
||||
# if on_load_weights is True, you should be careful doing things related to model evaluations,
|
||||
# as the models and tensors are not guarenteed to be on the same device
|
||||
pass
|
||||
|
||||
def update_step_end(self, epoch: int, global_step: int):
|
||||
pass
|
||||
|
||||
|
||||
def update_if_possible(module: Any, epoch: int, global_step: int) -> None:
|
||||
if isinstance(module, Updateable):
|
||||
module.do_update_step(epoch, global_step)
|
||||
|
||||
|
||||
def update_end_if_possible(module: Any, epoch: int, global_step: int) -> None:
|
||||
if isinstance(module, Updateable):
|
||||
module.do_update_step_end(epoch, global_step)
|
||||
|
||||
|
||||
class BaseObject(Updateable):
|
||||
@dataclass
|
||||
class Config:
|
||||
pass
|
||||
|
||||
cfg: Config # add this to every subclass of BaseObject to enable static type checking
|
||||
|
||||
def __init__(
|
||||
self, cfg: Optional[Union[dict, DictConfig]] = None, *args, **kwargs
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
self.device = get_device()
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class BaseModule(nn.Module, Updateable):
|
||||
@dataclass
|
||||
class Config:
|
||||
weights: Optional[str] = None
|
||||
|
||||
cfg: Config # add this to every subclass of BaseModule to enable static type checking
|
||||
|
||||
def __init__(
|
||||
self, cfg: Optional[Union[dict, DictConfig]] = None, *args, **kwargs
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
self.device = get_device()
|
||||
self._non_modules = {}
|
||||
self.configure(*args, **kwargs)
|
||||
if self.cfg.weights is not None:
|
||||
# format: path/to/weights:module_name
|
||||
weights_path, module_name = self.cfg.weights.split(":")
|
||||
state_dict, epoch, global_step = load_module_weights(
|
||||
weights_path, module_name=module_name, map_location="cpu"
|
||||
)
|
||||
self.load_state_dict(state_dict)
|
||||
self.do_update_step(
|
||||
epoch, global_step, on_load_weights=True
|
||||
) # restore states
|
||||
|
||||
def configure(self, *args, **kwargs) -> None:
|
||||
pass
|
||||
|
||||
def register_non_module(self, name: str, module: nn.Module) -> None:
|
||||
# non-modules won't be treated as model parameters
|
||||
self._non_modules[name] = module
|
||||
|
||||
def non_module(self, name: str):
|
||||
return self._non_modules.get(name, None)
|
||||
@@ -0,0 +1,156 @@
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import pytorch_lightning
|
||||
|
||||
from .config import dump_config
|
||||
from .misc import parse_version
|
||||
|
||||
if parse_version(pytorch_lightning.__version__) > parse_version("1.8"):
|
||||
from pytorch_lightning.callbacks import Callback
|
||||
else:
|
||||
from pytorch_lightning.callbacks.base import Callback
|
||||
|
||||
from pytorch_lightning.callbacks.progress import TQDMProgressBar
|
||||
from pytorch_lightning.utilities.rank_zero import rank_zero_only, rank_zero_warn
|
||||
|
||||
|
||||
class VersionedCallback(Callback):
|
||||
def __init__(self, save_root, version=None, use_version=True):
|
||||
self.save_root = save_root
|
||||
self._version = version
|
||||
self.use_version = use_version
|
||||
|
||||
@property
|
||||
def version(self) -> int:
|
||||
"""Get the experiment version.
|
||||
|
||||
Returns:
|
||||
The experiment version if specified else the next version.
|
||||
"""
|
||||
if self._version is None:
|
||||
self._version = self._get_next_version()
|
||||
return self._version
|
||||
|
||||
def _get_next_version(self):
|
||||
existing_versions = []
|
||||
if os.path.isdir(self.save_root):
|
||||
for f in os.listdir(self.save_root):
|
||||
bn = os.path.basename(f)
|
||||
if bn.startswith("version_"):
|
||||
dir_ver = os.path.splitext(bn)[0].split("_")[1].replace("/", "")
|
||||
existing_versions.append(int(dir_ver))
|
||||
if len(existing_versions) == 0:
|
||||
return 0
|
||||
return max(existing_versions) + 1
|
||||
|
||||
@property
|
||||
def savedir(self):
|
||||
if not self.use_version:
|
||||
return self.save_root
|
||||
return os.path.join(
|
||||
self.save_root,
|
||||
self.version
|
||||
if isinstance(self.version, str)
|
||||
else f"version_{self.version}",
|
||||
)
|
||||
|
||||
|
||||
class CodeSnapshotCallback(VersionedCallback):
|
||||
def __init__(self, save_root, version=None, use_version=True):
|
||||
super().__init__(save_root, version, use_version)
|
||||
|
||||
def get_file_list(self):
|
||||
return [
|
||||
b.decode()
|
||||
for b in set(
|
||||
subprocess.check_output(
|
||||
'git ls-files -- ":!:load/*"', shell=True
|
||||
).splitlines()
|
||||
)
|
||||
| set( # hard code, TODO: use config to exclude folders or files
|
||||
subprocess.check_output(
|
||||
"git ls-files --others --exclude-standard", shell=True
|
||||
).splitlines()
|
||||
)
|
||||
]
|
||||
|
||||
@rank_zero_only
|
||||
def save_code_snapshot(self):
|
||||
os.makedirs(self.savedir, exist_ok=True)
|
||||
for f in self.get_file_list():
|
||||
if not os.path.exists(f) or os.path.isdir(f):
|
||||
continue
|
||||
os.makedirs(os.path.join(self.savedir, os.path.dirname(f)), exist_ok=True)
|
||||
shutil.copyfile(f, os.path.join(self.savedir, f))
|
||||
|
||||
def on_fit_start(self, trainer, pl_module):
|
||||
try:
|
||||
self.save_code_snapshot()
|
||||
except:
|
||||
rank_zero_warn(
|
||||
"Code snapshot is not saved. Please make sure you have git installed and are in a git repository."
|
||||
)
|
||||
|
||||
|
||||
class ConfigSnapshotCallback(VersionedCallback):
|
||||
def __init__(self, config_path, config, save_root, version=None, use_version=True):
|
||||
super().__init__(save_root, version, use_version)
|
||||
self.config_path = config_path
|
||||
self.config = config
|
||||
|
||||
@rank_zero_only
|
||||
def save_config_snapshot(self):
|
||||
os.makedirs(self.savedir, exist_ok=True)
|
||||
dump_config(os.path.join(self.savedir, "parsed.yaml"), self.config)
|
||||
shutil.copyfile(self.config_path, os.path.join(self.savedir, "raw.yaml"))
|
||||
|
||||
def on_fit_start(self, trainer, pl_module):
|
||||
self.save_config_snapshot()
|
||||
|
||||
|
||||
class CustomProgressBar(TQDMProgressBar):
|
||||
def get_metrics(self, *args, **kwargs):
|
||||
# don't show the version number
|
||||
items = super().get_metrics(*args, **kwargs)
|
||||
items.pop("v_num", None)
|
||||
return items
|
||||
|
||||
|
||||
class ProgressCallback(Callback):
|
||||
def __init__(self, save_path):
|
||||
super().__init__()
|
||||
self.save_path = save_path
|
||||
self._file_handle = None
|
||||
|
||||
@property
|
||||
def file_handle(self):
|
||||
if self._file_handle is None:
|
||||
self._file_handle = open(self.save_path, "w")
|
||||
return self._file_handle
|
||||
|
||||
@rank_zero_only
|
||||
def write(self, msg: str) -> None:
|
||||
self.file_handle.seek(0)
|
||||
self.file_handle.truncate()
|
||||
self.file_handle.write(msg)
|
||||
self.file_handle.flush()
|
||||
|
||||
@rank_zero_only
|
||||
def on_train_batch_end(self, trainer, pl_module, *args, **kwargs):
|
||||
self.write(
|
||||
f"Generation progress: {pl_module.true_global_step / trainer.max_steps * 100:.2f}%"
|
||||
)
|
||||
|
||||
@rank_zero_only
|
||||
def on_validation_start(self, trainer, pl_module):
|
||||
self.write(f"Rendering validation image ...")
|
||||
|
||||
@rank_zero_only
|
||||
def on_test_start(self, trainer, pl_module):
|
||||
self.write(f"Rendering video ...")
|
||||
|
||||
@rank_zero_only
|
||||
def on_predict_start(self, trainer, pl_module):
|
||||
self.write(f"Exporting mesh assets ...")
|
||||
@@ -0,0 +1,138 @@
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from .typing import *
|
||||
|
||||
# ============ Register OmegaConf Resolvers ============= #
|
||||
OmegaConf.register_new_resolver("calc_exp_lr_decay_rate", lambda factor, n: factor ** (1.0 / n), replace=True)
|
||||
OmegaConf.register_new_resolver("add", lambda a, b: a + b, replace=True)
|
||||
OmegaConf.register_new_resolver("sub", lambda a, b: a - b, replace=True)
|
||||
OmegaConf.register_new_resolver("mul", lambda a, b: a * b, replace=True)
|
||||
OmegaConf.register_new_resolver("div", lambda a, b: a / b, replace=True)
|
||||
OmegaConf.register_new_resolver("idiv", lambda a, b: a // b, replace=True)
|
||||
OmegaConf.register_new_resolver("basename", lambda p: os.path.basename(p), replace=True)
|
||||
OmegaConf.register_new_resolver("rmspace", lambda s, sub: s.replace(" ", sub), replace=True)
|
||||
OmegaConf.register_new_resolver("tuple2", lambda s: [float(s), float(s)], replace=True)
|
||||
OmegaConf.register_new_resolver("gt0", lambda s: s > 0, replace=True)
|
||||
OmegaConf.register_new_resolver("not", lambda s: not s, replace=True)
|
||||
|
||||
|
||||
def calc_num_train_steps(num_data, batch_size, max_epochs, num_nodes, num_cards=8):
|
||||
return int(num_data / (num_nodes * num_cards * batch_size)) * max_epochs
|
||||
|
||||
|
||||
OmegaConf.register_new_resolver("calc_num_train_steps", calc_num_train_steps)
|
||||
|
||||
# ======================================================= #
|
||||
|
||||
|
||||
# ============== Automatic Name Resolvers =============== #
|
||||
def get_naming_convention(cfg):
|
||||
# TODO
|
||||
name = f"lrm_{cfg.system.backbone.num_layers}"
|
||||
return name
|
||||
|
||||
|
||||
# ======================================================= #
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExperimentConfig:
|
||||
name: str = "default"
|
||||
description: str = ""
|
||||
tag: str = ""
|
||||
seed: int = 0
|
||||
use_timestamp: bool = True
|
||||
timestamp: Optional[str] = None
|
||||
exp_root_dir: str = "outputs"
|
||||
|
||||
### these shouldn't be set manually
|
||||
exp_dir: str = "outputs/default"
|
||||
trial_name: str = "exp"
|
||||
trial_dir: str = "outputs/default/exp"
|
||||
n_gpus: int = 1
|
||||
###
|
||||
|
||||
resume: Optional[str] = None
|
||||
|
||||
data_cls: str = ""
|
||||
data: dict = field(default_factory=dict)
|
||||
|
||||
system_cls: str = ""
|
||||
system: dict = field(default_factory=dict)
|
||||
|
||||
# accept pytorch-lightning trainer parameters
|
||||
# see https://lightning.ai/docs/pytorch/stable/common/trainer.html#trainer-class-api
|
||||
trainer: dict = field(default_factory=dict)
|
||||
|
||||
# accept pytorch-lightning checkpoint callback parameters
|
||||
# see https://lightning.ai/docs/pytorch/stable/api/lightning.pytorch.callbacks.ModelCheckpoint.html#modelcheckpoint
|
||||
checkpoint: dict = field(default_factory=dict)
|
||||
|
||||
|
||||
def load_config(
|
||||
*yamls: str, cli_args: list = [], from_string=False, makedirs=False, **kwargs
|
||||
) -> Any:
|
||||
if from_string:
|
||||
parse_func = OmegaConf.create
|
||||
else:
|
||||
parse_func = OmegaConf.load
|
||||
yaml_confs = []
|
||||
for y in yamls:
|
||||
conf = parse_func(y)
|
||||
extends = conf.pop("extends", None)
|
||||
if extends:
|
||||
assert os.path.exists(extends), f"File {extends} does not exist."
|
||||
yaml_confs.append(OmegaConf.load(extends))
|
||||
yaml_confs.append(conf)
|
||||
cli_conf = OmegaConf.from_cli(cli_args)
|
||||
cfg = OmegaConf.merge(*yaml_confs, cli_conf, kwargs)
|
||||
OmegaConf.resolve(cfg)
|
||||
assert isinstance(cfg, DictConfig)
|
||||
scfg: ExperimentConfig = parse_structured(ExperimentConfig, cfg)
|
||||
|
||||
# post processing
|
||||
# auto naming
|
||||
if scfg.name == "auto":
|
||||
scfg.name = get_naming_convention(scfg)
|
||||
# add timestamp
|
||||
if not scfg.tag and not scfg.use_timestamp:
|
||||
raise ValueError("Either tag is specified or use_timestamp is True.")
|
||||
scfg.trial_name = scfg.tag
|
||||
# if resume from an existing config, scfg.timestamp should not be None
|
||||
if scfg.timestamp is None:
|
||||
scfg.timestamp = ""
|
||||
if scfg.use_timestamp:
|
||||
if scfg.n_gpus > 1:
|
||||
lrm.warn(
|
||||
"Timestamp is disabled when using multiple GPUs, please make sure you have a unique tag."
|
||||
)
|
||||
else:
|
||||
scfg.timestamp = datetime.now().strftime("@%Y%m%d-%H%M%S")
|
||||
# make directories
|
||||
scfg.trial_name += scfg.timestamp
|
||||
scfg.exp_dir = os.path.join(scfg.exp_root_dir, scfg.name)
|
||||
scfg.trial_dir = os.path.join(scfg.exp_dir, scfg.trial_name)
|
||||
|
||||
if makedirs:
|
||||
os.makedirs(scfg.trial_dir, exist_ok=True)
|
||||
|
||||
return scfg
|
||||
|
||||
|
||||
def config_to_primitive(config, resolve: bool = True) -> Any:
|
||||
return OmegaConf.to_container(config, resolve=resolve)
|
||||
|
||||
|
||||
def dump_config(path: str, config) -> None:
|
||||
with open(path, "w") as fp:
|
||||
OmegaConf.save(config=config, f=fp)
|
||||
|
||||
|
||||
def parse_structured(fields: Any, cfg: Optional[Union[dict, DictConfig]] = None) -> Any:
|
||||
scfg = OmegaConf.merge(OmegaConf.structured(fields), cfg)
|
||||
return scfg
|
||||
@@ -0,0 +1,211 @@
|
||||
import gc
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
|
||||
import torch
|
||||
from packaging import version
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from .config import config_to_primitive
|
||||
from .typing import *
|
||||
|
||||
|
||||
def parse_version(ver: str):
|
||||
return version.parse(ver)
|
||||
|
||||
|
||||
def get_rank():
|
||||
# SLURM_PROCID can be set even if SLURM is not managing the multiprocessing,
|
||||
# therefore LOCAL_RANK needs to be checked first
|
||||
rank_keys = ("RANK", "LOCAL_RANK", "SLURM_PROCID", "JSM_NAMESPACE_RANK")
|
||||
for key in rank_keys:
|
||||
rank = os.environ.get(key)
|
||||
if rank is not None:
|
||||
return int(rank)
|
||||
return 0
|
||||
|
||||
|
||||
def get_device():
|
||||
return torch.device(f"cuda:{get_rank()}")
|
||||
|
||||
|
||||
def load_module_weights(
|
||||
path, module_name=None, ignore_modules=None, mapping=None, map_location=None
|
||||
) -> Tuple[dict, int, int]:
|
||||
if module_name is not None and ignore_modules is not None:
|
||||
raise ValueError("module_name and ignore_modules cannot be both set")
|
||||
if map_location is None:
|
||||
map_location = get_device()
|
||||
|
||||
ckpt = torch.load(path, map_location=map_location)
|
||||
state_dict = ckpt["state_dict"]
|
||||
|
||||
if mapping is not None:
|
||||
state_dict_to_load = {}
|
||||
for k, v in state_dict.items():
|
||||
if any([k.startswith(m["to"]) for m in mapping]):
|
||||
pass
|
||||
else:
|
||||
state_dict_to_load[k] = v
|
||||
for k, v in state_dict.items():
|
||||
for m in mapping:
|
||||
if k.startswith(m["from"]):
|
||||
k_dest = k.replace(m["from"], m["to"])
|
||||
lrm.info(f"Mapping {k} => {k_dest}")
|
||||
state_dict_to_load[k_dest] = v.clone()
|
||||
state_dict = state_dict_to_load
|
||||
|
||||
state_dict_to_load = state_dict
|
||||
|
||||
if ignore_modules is not None:
|
||||
state_dict_to_load = {}
|
||||
for k, v in state_dict.items():
|
||||
ignore = any(
|
||||
[k.startswith(ignore_module + ".") for ignore_module in ignore_modules]
|
||||
)
|
||||
if ignore:
|
||||
continue
|
||||
state_dict_to_load[k] = v
|
||||
|
||||
if module_name is not None:
|
||||
state_dict_to_load = {}
|
||||
for k, v in state_dict.items():
|
||||
m = re.match(rf"^{module_name}\.(.*)$", k)
|
||||
if m is None:
|
||||
continue
|
||||
state_dict_to_load[m.group(1)] = v
|
||||
|
||||
return state_dict_to_load, ckpt["epoch"], ckpt["global_step"]
|
||||
|
||||
|
||||
def C(value: Any, epoch: int, global_step: int) -> float:
|
||||
if isinstance(value, int) or isinstance(value, float):
|
||||
pass
|
||||
else:
|
||||
value = config_to_primitive(value)
|
||||
if not isinstance(value, list):
|
||||
raise TypeError("Scalar specification only supports list, got", type(value))
|
||||
if len(value) == 3:
|
||||
value = [0] + value
|
||||
assert len(value) == 4
|
||||
start_step, start_value, end_value, end_step = value
|
||||
if isinstance(end_step, int):
|
||||
current_step = global_step
|
||||
value = start_value + (end_value - start_value) * max(
|
||||
min(1.0, (current_step - start_step) / (end_step - start_step)), 0.0
|
||||
)
|
||||
elif isinstance(end_step, float):
|
||||
current_step = epoch
|
||||
value = start_value + (end_value - start_value) * max(
|
||||
min(1.0, (current_step - start_step) / (end_step - start_step)), 0.0
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def cleanup():
|
||||
gc.collect()
|
||||
torch.cuda.empty_cache()
|
||||
try:
|
||||
import tinycudann as tcnn
|
||||
|
||||
tcnn.free_temporary_memory()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def finish_with_cleanup(func: Callable):
|
||||
def wrapper(*args, **kwargs):
|
||||
out = func(*args, **kwargs)
|
||||
cleanup()
|
||||
return out
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def _distributed_available():
|
||||
return torch.distributed.is_available() and torch.distributed.is_initialized()
|
||||
|
||||
|
||||
def barrier():
|
||||
if not _distributed_available():
|
||||
return
|
||||
else:
|
||||
torch.distributed.barrier()
|
||||
|
||||
|
||||
def broadcast(tensor, src=0):
|
||||
if not _distributed_available():
|
||||
return tensor
|
||||
else:
|
||||
torch.distributed.broadcast(tensor, src=src)
|
||||
return tensor
|
||||
|
||||
|
||||
def enable_gradient(model, enabled: bool = True) -> None:
|
||||
for param in model.parameters():
|
||||
param.requires_grad_(enabled)
|
||||
|
||||
|
||||
class TimeRecorder:
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
self.items = {}
|
||||
self.accumulations = defaultdict(list)
|
||||
self.time_scale = 1000.0 # ms
|
||||
self.time_unit = "ms"
|
||||
self.enabled = False
|
||||
|
||||
def __new__(cls):
|
||||
# singleton
|
||||
if cls._instance is None:
|
||||
cls._instance = super(TimeRecorder, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def enable(self, enabled: bool) -> None:
|
||||
self.enabled = enabled
|
||||
|
||||
def start(self, name: str) -> None:
|
||||
if not self.enabled:
|
||||
return
|
||||
torch.cuda.synchronize()
|
||||
self.items[name] = time.time()
|
||||
|
||||
def end(self, name: str, accumulate: bool = False) -> float:
|
||||
if not self.enabled or name not in self.items:
|
||||
return
|
||||
torch.cuda.synchronize()
|
||||
start_time = self.items.pop(name)
|
||||
delta = time.time() - start_time
|
||||
if accumulate:
|
||||
self.accumulations[name].append(delta)
|
||||
t = delta * self.time_scale
|
||||
lrm.info(f"{name}: {t:.2f}{self.time_unit}")
|
||||
|
||||
def get_accumulation(self, name: str, average: bool = False) -> float:
|
||||
if not self.enabled or name not in self.accumulations:
|
||||
return
|
||||
acc = self.accumulations.pop(name)
|
||||
total = sum(acc)
|
||||
if average:
|
||||
t = total / len(acc) * self.time_scale
|
||||
else:
|
||||
t = total * self.time_scale
|
||||
lrm.info(f"{name} for {len(acc)} times: {t:.2f}{self.time_unit}")
|
||||
|
||||
|
||||
### global time recorder
|
||||
time_recorder = TimeRecorder()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def time_recorder_enabled():
|
||||
enabled = time_recorder.enabled
|
||||
time_recorder.enable(enabled=True)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
time_recorder.enable(enabled=enabled)
|
||||
@@ -0,0 +1,435 @@
|
||||
from collections import defaultdict
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from torch.autograd import Function
|
||||
from torch.cuda.amp import custom_bwd, custom_fwd
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from .typing import *
|
||||
|
||||
|
||||
def dot(x, y):
|
||||
return torch.sum(x * y, -1, keepdim=True)
|
||||
|
||||
|
||||
def reflect(x, n):
|
||||
return 2 * dot(x, n) * n - x
|
||||
|
||||
|
||||
ValidScale = Union[Tuple[float, float], Num[Tensor, "2 D"]]
|
||||
|
||||
|
||||
def scale_tensor(
|
||||
dat: Num[Tensor, "... D"], inp_scale: ValidScale, tgt_scale: ValidScale
|
||||
):
|
||||
if inp_scale is None:
|
||||
inp_scale = (0, 1)
|
||||
if tgt_scale is None:
|
||||
tgt_scale = (0, 1)
|
||||
if isinstance(tgt_scale, Tensor):
|
||||
assert dat.shape[-1] == tgt_scale.shape[-1]
|
||||
dat = (dat - inp_scale[0]) / (inp_scale[1] - inp_scale[0])
|
||||
dat = dat * (tgt_scale[1] - tgt_scale[0]) + tgt_scale[0]
|
||||
return dat
|
||||
|
||||
|
||||
class _TruncExp(Function): # pylint: disable=abstract-method
|
||||
# Implementation from torch-ngp:
|
||||
# https://github.com/ashawkey/torch-ngp/blob/93b08a0d4ec1cc6e69d85df7f0acdfb99603b628/activation.py
|
||||
@staticmethod
|
||||
@custom_fwd(cast_inputs=torch.float32)
|
||||
def forward(ctx, x): # pylint: disable=arguments-differ
|
||||
ctx.save_for_backward(x)
|
||||
return torch.exp(x)
|
||||
|
||||
@staticmethod
|
||||
@custom_bwd
|
||||
def backward(ctx, g): # pylint: disable=arguments-differ
|
||||
x = ctx.saved_tensors[0]
|
||||
return g * torch.exp(torch.clamp(x, max=15))
|
||||
|
||||
|
||||
trunc_exp = _TruncExp.apply
|
||||
|
||||
|
||||
def get_activation(name) -> Callable:
|
||||
if name is None:
|
||||
return lambda x: x
|
||||
name = name.lower()
|
||||
if name == "none":
|
||||
return lambda x: x
|
||||
elif name == "lin2srgb":
|
||||
return lambda x: torch.where(
|
||||
x > 0.0031308,
|
||||
torch.pow(torch.clamp(x, min=0.0031308), 1.0 / 2.4) * 1.055 - 0.055,
|
||||
12.92 * x,
|
||||
).clamp(0.0, 1.0)
|
||||
elif name == "exp":
|
||||
return lambda x: torch.exp(x)
|
||||
elif name == "shifted_exp":
|
||||
return lambda x: torch.exp(x - 1.0)
|
||||
elif name == "trunc_exp":
|
||||
return trunc_exp
|
||||
elif name == "shifted_trunc_exp":
|
||||
return lambda x: trunc_exp(x - 1.0)
|
||||
elif name == "sigmoid":
|
||||
return lambda x: torch.sigmoid(x)
|
||||
elif name == "tanh":
|
||||
return lambda x: torch.tanh(x)
|
||||
elif name == "shifted_softplus":
|
||||
return lambda x: F.softplus(x - 1.0)
|
||||
elif name == "scale_-11_01":
|
||||
return lambda x: x * 0.5 + 0.5
|
||||
elif name == "negative":
|
||||
return lambda x: -x
|
||||
else:
|
||||
try:
|
||||
return getattr(F, name)
|
||||
except AttributeError:
|
||||
raise ValueError(f"Unknown activation function: {name}")
|
||||
|
||||
|
||||
def chunk_batch(func: Callable, chunk_size: int, *args, **kwargs) -> Any:
|
||||
if chunk_size <= 0:
|
||||
return func(*args, **kwargs)
|
||||
B = None
|
||||
for arg in list(args) + list(kwargs.values()):
|
||||
if isinstance(arg, torch.Tensor):
|
||||
B = arg.shape[0]
|
||||
break
|
||||
assert (
|
||||
B is not None
|
||||
), "No tensor found in args or kwargs, cannot determine batch size."
|
||||
out = defaultdict(list)
|
||||
out_type = None
|
||||
# max(1, B) to support B == 0
|
||||
for i in range(0, max(1, B), chunk_size):
|
||||
out_chunk = func(
|
||||
*[
|
||||
arg[i : i + chunk_size] if isinstance(arg, torch.Tensor) else arg
|
||||
for arg in args
|
||||
],
|
||||
**{
|
||||
k: arg[i : i + chunk_size] if isinstance(arg, torch.Tensor) else arg
|
||||
for k, arg in kwargs.items()
|
||||
},
|
||||
)
|
||||
if out_chunk is None:
|
||||
continue
|
||||
out_type = type(out_chunk)
|
||||
if isinstance(out_chunk, torch.Tensor):
|
||||
out_chunk = {0: out_chunk}
|
||||
elif isinstance(out_chunk, tuple) or isinstance(out_chunk, list):
|
||||
chunk_length = len(out_chunk)
|
||||
out_chunk = {i: chunk for i, chunk in enumerate(out_chunk)}
|
||||
elif isinstance(out_chunk, dict):
|
||||
pass
|
||||
else:
|
||||
print(
|
||||
f"Return value of func must be in type [torch.Tensor, list, tuple, dict], get {type(out_chunk)}."
|
||||
)
|
||||
exit(1)
|
||||
for k, v in out_chunk.items():
|
||||
v = v if torch.is_grad_enabled() else v.detach()
|
||||
out[k].append(v)
|
||||
|
||||
if out_type is None:
|
||||
return None
|
||||
|
||||
out_merged: Dict[Any, Optional[torch.Tensor]] = {}
|
||||
for k, v in out.items():
|
||||
if all([vv is None for vv in v]):
|
||||
# allow None in return value
|
||||
out_merged[k] = None
|
||||
elif all([isinstance(vv, torch.Tensor) for vv in v]):
|
||||
out_merged[k] = torch.cat(v, dim=0)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Unsupported types in return value of func: {[type(vv) for vv in v if not isinstance(vv, torch.Tensor)]}"
|
||||
)
|
||||
|
||||
if out_type is torch.Tensor:
|
||||
return out_merged[0]
|
||||
elif out_type in [tuple, list]:
|
||||
return out_type([out_merged[i] for i in range(chunk_length)])
|
||||
elif out_type is dict:
|
||||
return out_merged
|
||||
|
||||
|
||||
def get_ray_directions(
|
||||
H: int,
|
||||
W: int,
|
||||
focal: Union[float, Tuple[float, float]],
|
||||
principal: Optional[Tuple[float, float]] = None,
|
||||
use_pixel_centers: bool = True,
|
||||
normalize: bool = True,
|
||||
) -> Float[Tensor, "H W 3"]:
|
||||
"""
|
||||
Get ray directions for all pixels in camera coordinate.
|
||||
Reference: https://www.scratchapixel.com/lessons/3d-basic-rendering/
|
||||
ray-tracing-generating-camera-rays/standard-coordinate-systems
|
||||
|
||||
Inputs:
|
||||
H, W, focal, principal, use_pixel_centers: image height, width, focal length, principal point and whether use pixel centers
|
||||
Outputs:
|
||||
directions: (H, W, 3), the direction of the rays in camera coordinate
|
||||
"""
|
||||
pixel_center = 0.5 if use_pixel_centers else 0
|
||||
|
||||
if isinstance(focal, float):
|
||||
fx, fy = focal, focal
|
||||
cx, cy = W / 2, H / 2
|
||||
else:
|
||||
fx, fy = focal
|
||||
assert principal is not None
|
||||
cx, cy = principal
|
||||
|
||||
i, j = torch.meshgrid(
|
||||
torch.arange(W, dtype=torch.float32) + pixel_center,
|
||||
torch.arange(H, dtype=torch.float32) + pixel_center,
|
||||
indexing="xy",
|
||||
)
|
||||
|
||||
directions: Float[Tensor, "H W 3"] = torch.stack(
|
||||
[(i - cx) / fx, -(j - cy) / fy, -torch.ones_like(i)], -1
|
||||
)
|
||||
|
||||
if normalize:
|
||||
directions = F.normalize(directions, dim=-1)
|
||||
|
||||
return directions
|
||||
|
||||
|
||||
def get_rays(
|
||||
directions: Float[Tensor, "... 3"],
|
||||
c2w: Float[Tensor, "... 4 4"],
|
||||
keepdim=False,
|
||||
noise_scale=0.0,
|
||||
normalize=False,
|
||||
) -> Tuple[Float[Tensor, "... 3"], Float[Tensor, "... 3"]]:
|
||||
# Rotate ray directions from camera coordinate to the world coordinate
|
||||
assert directions.shape[-1] == 3
|
||||
|
||||
if directions.ndim == 2: # (N_rays, 3)
|
||||
if c2w.ndim == 2: # (4, 4)
|
||||
c2w = c2w[None, :, :]
|
||||
assert c2w.ndim == 3 # (N_rays, 4, 4) or (1, 4, 4)
|
||||
rays_d = (directions[:, None, :] * c2w[:, :3, :3]).sum(-1) # (N_rays, 3)
|
||||
rays_o = c2w[:, :3, 3].expand(rays_d.shape)
|
||||
elif directions.ndim == 3: # (H, W, 3)
|
||||
assert c2w.ndim in [2, 3]
|
||||
if c2w.ndim == 2: # (4, 4)
|
||||
rays_d = (directions[:, :, None, :] * c2w[None, None, :3, :3]).sum(
|
||||
-1
|
||||
) # (H, W, 3)
|
||||
rays_o = c2w[None, None, :3, 3].expand(rays_d.shape)
|
||||
elif c2w.ndim == 3: # (B, 4, 4)
|
||||
rays_d = (directions[None, :, :, None, :] * c2w[:, None, None, :3, :3]).sum(
|
||||
-1
|
||||
) # (B, H, W, 3)
|
||||
rays_o = c2w[:, None, None, :3, 3].expand(rays_d.shape)
|
||||
elif directions.ndim == 4: # (B, H, W, 3)
|
||||
assert c2w.ndim == 3 # (B, 4, 4)
|
||||
rays_d = (directions[:, :, :, None, :] * c2w[:, None, None, :3, :3]).sum(
|
||||
-1
|
||||
) # (B, H, W, 3)
|
||||
rays_o = c2w[:, None, None, :3, 3].expand(rays_d.shape)
|
||||
|
||||
# add camera noise to avoid grid-like artifect
|
||||
# https://github.com/ashawkey/stable-dreamfusion/blob/49c3d4fa01d68a4f027755acf94e1ff6020458cc/nerf/utils.py#L373
|
||||
if noise_scale > 0:
|
||||
rays_o = rays_o + torch.randn(3, device=rays_o.device) * noise_scale
|
||||
rays_d = rays_d + torch.randn(3, device=rays_d.device) * noise_scale
|
||||
|
||||
if normalize:
|
||||
rays_d = F.normalize(rays_d, dim=-1)
|
||||
if not keepdim:
|
||||
rays_o, rays_d = rays_o.reshape(-1, 3), rays_d.reshape(-1, 3)
|
||||
|
||||
return rays_o, rays_d
|
||||
|
||||
|
||||
def get_projection_matrix(
|
||||
fovy: Union[float, Float[Tensor, "B"]], aspect_wh: float, near: float, far: float
|
||||
) -> Float[Tensor, "*B 4 4"]:
|
||||
if isinstance(fovy, float):
|
||||
proj_mtx = torch.zeros(4, 4, dtype=torch.float32)
|
||||
proj_mtx[0, 0] = 1.0 / (math.tan(fovy / 2.0) * aspect_wh)
|
||||
proj_mtx[1, 1] = -1.0 / math.tan(
|
||||
fovy / 2.0
|
||||
) # add a negative sign here as the y axis is flipped in nvdiffrast output
|
||||
proj_mtx[2, 2] = -(far + near) / (far - near)
|
||||
proj_mtx[2, 3] = -2.0 * far * near / (far - near)
|
||||
proj_mtx[3, 2] = -1.0
|
||||
else:
|
||||
batch_size = fovy.shape[0]
|
||||
proj_mtx = torch.zeros(batch_size, 4, 4, dtype=torch.float32)
|
||||
proj_mtx[:, 0, 0] = 1.0 / (torch.tan(fovy / 2.0) * aspect_wh)
|
||||
proj_mtx[:, 1, 1] = -1.0 / torch.tan(
|
||||
fovy / 2.0
|
||||
) # add a negative sign here as the y axis is flipped in nvdiffrast output
|
||||
proj_mtx[:, 2, 2] = -(far + near) / (far - near)
|
||||
proj_mtx[:, 2, 3] = -2.0 * far * near / (far - near)
|
||||
proj_mtx[:, 3, 2] = -1.0
|
||||
return proj_mtx
|
||||
|
||||
|
||||
def get_mvp_matrix(
|
||||
c2w: Float[Tensor, "*B 4 4"], proj_mtx: Float[Tensor, "*B 4 4"]
|
||||
) -> Float[Tensor, "*B 4 4"]:
|
||||
# calculate w2c from c2w: R' = Rt, t' = -Rt * t
|
||||
# mathematically equivalent to (c2w)^-1
|
||||
if c2w.ndim == 2:
|
||||
assert proj_mtx.ndim == 2
|
||||
w2c: Float[Tensor, "4 4"] = torch.zeros(4, 4).to(c2w)
|
||||
w2c[:3, :3] = c2w[:3, :3].permute(1, 0)
|
||||
w2c[:3, 3:] = -c2w[:3, :3].permute(1, 0) @ c2w[:3, 3:]
|
||||
w2c[3, 3] = 1.0
|
||||
else:
|
||||
w2c: Float[Tensor, "B 4 4"] = torch.zeros(c2w.shape[0], 4, 4).to(c2w)
|
||||
w2c[:, :3, :3] = c2w[:, :3, :3].permute(0, 2, 1)
|
||||
w2c[:, :3, 3:] = -c2w[:, :3, :3].permute(0, 2, 1) @ c2w[:, :3, 3:]
|
||||
w2c[:, 3, 3] = 1.0
|
||||
# calculate mvp matrix by proj_mtx @ w2c (mv_mtx)
|
||||
mvp_mtx = proj_mtx @ w2c
|
||||
return mvp_mtx
|
||||
|
||||
|
||||
def get_intrinsic_from_fov(fov, H, W, bs=-1):
|
||||
focal_length = 0.5 * H / np.tan(0.5 * fov)
|
||||
intrinsic = np.identity(3, dtype=np.float32)
|
||||
intrinsic[0, 0] = focal_length
|
||||
intrinsic[1, 1] = focal_length
|
||||
intrinsic[0, 2] = W / 2.0
|
||||
intrinsic[1, 2] = H / 2.0
|
||||
|
||||
if bs > 0:
|
||||
intrinsic = intrinsic[None].repeat(bs, axis=0)
|
||||
|
||||
return torch.from_numpy(intrinsic)
|
||||
|
||||
|
||||
def binary_cross_entropy(input, target):
|
||||
"""
|
||||
F.binary_cross_entropy is not numerically stable in mixed-precision training.
|
||||
"""
|
||||
return -(target * torch.log(input) + (1 - target) * torch.log(1 - input)).mean()
|
||||
|
||||
|
||||
def tet_sdf_diff(
|
||||
vert_sdf: Float[Tensor, "Nv 1"], tet_edges: Integer[Tensor, "Ne 2"]
|
||||
) -> Float[Tensor, ""]:
|
||||
sdf_f1x6x2 = vert_sdf[:, 0][tet_edges.reshape(-1)].reshape(-1, 2)
|
||||
mask = torch.sign(sdf_f1x6x2[..., 0]) != torch.sign(sdf_f1x6x2[..., 1])
|
||||
sdf_f1x6x2 = sdf_f1x6x2[mask]
|
||||
sdf_diff = F.binary_cross_entropy_with_logits(
|
||||
sdf_f1x6x2[..., 0], (sdf_f1x6x2[..., 1] > 0).float()
|
||||
) + F.binary_cross_entropy_with_logits(
|
||||
sdf_f1x6x2[..., 1], (sdf_f1x6x2[..., 0] > 0).float()
|
||||
)
|
||||
return sdf_diff
|
||||
|
||||
|
||||
def validate_empty_rays(ray_indices, t_start, t_end):
|
||||
if ray_indices.nelement() == 0:
|
||||
lrm.warn("Empty rays_indices!")
|
||||
ray_indices = torch.LongTensor([0]).to(ray_indices)
|
||||
t_start = torch.Tensor([0]).to(ray_indices)
|
||||
t_end = torch.Tensor([0]).to(ray_indices)
|
||||
return ray_indices, t_start, t_end
|
||||
|
||||
|
||||
def rays_intersect_bbox(
|
||||
rays_o: Float[Tensor, "N 3"],
|
||||
rays_d: Float[Tensor, "N 3"],
|
||||
radius: Float,
|
||||
near: Float = 0.0,
|
||||
valid_thresh: Float = 0.01,
|
||||
background: bool = False,
|
||||
):
|
||||
input_shape = rays_o.shape[:-1]
|
||||
rays_o, rays_d = rays_o.view(-1, 3), rays_d.view(-1, 3)
|
||||
rays_d_valid = torch.where(
|
||||
rays_d.abs() < 1e-6, torch.full_like(rays_d, 1e-6), rays_d
|
||||
)
|
||||
if type(radius) in [int, float]:
|
||||
radius = torch.FloatTensor(
|
||||
[[-radius, radius], [-radius, radius], [-radius, radius]]
|
||||
).to(rays_o.device)
|
||||
radius = (
|
||||
1.0 - 1.0e-3
|
||||
) * radius # tighten the radius to make sure the intersection point lies in the bounding box
|
||||
interx0 = (radius[..., 1] - rays_o) / rays_d_valid
|
||||
interx1 = (radius[..., 0] - rays_o) / rays_d_valid
|
||||
t_near = torch.minimum(interx0, interx1).amax(dim=-1).clamp_min(near)
|
||||
t_far = torch.maximum(interx0, interx1).amin(dim=-1)
|
||||
|
||||
# check wheter a ray intersects the bbox or not
|
||||
rays_valid = t_far - t_near > valid_thresh
|
||||
|
||||
t_near_valid, t_far_valid = t_near[rays_valid], t_far[rays_valid]
|
||||
global_near = t_near_valid.min().item()
|
||||
global_far = t_far_valid.max().item()
|
||||
|
||||
t_near[torch.where(~rays_valid)] = 0.0
|
||||
t_far[torch.where(~rays_valid)] = 0.0
|
||||
|
||||
t_near = t_near.view(*input_shape, 1)
|
||||
t_far = t_far.view(*input_shape, 1)
|
||||
rays_valid = rays_valid.view(*input_shape)
|
||||
|
||||
return t_near, t_far, rays_valid
|
||||
|
||||
|
||||
def get_plucker_rays(rays_o: Float[Tensor, "*N 3"], rays_d: Float[Tensor, "*N 3"]) -> Float[Tensor, "*N 6"]:
|
||||
rays_o = F.normalize(rays_o, dim=-1)
|
||||
rays_d = F.normalize(rays_d, dim=-1)
|
||||
return torch.cat([
|
||||
rays_o.cross(rays_d),
|
||||
rays_d
|
||||
], dim=-1)
|
||||
|
||||
|
||||
def c2w_to_polar(c2w: Float[Tensor, "4 4"]) -> Tuple[float, float, float]:
|
||||
cam_pos = c2w[:3, 3]
|
||||
x, y, z = cam_pos.tolist()
|
||||
distance = cam_pos.norm().item()
|
||||
elevation = math.asin(z / distance)
|
||||
if abs(x) < 1.0e-5 and abs(y) < 1.0e-5:
|
||||
azimuth = 0
|
||||
else:
|
||||
azimuth = math.atan2(y, x)
|
||||
if azimuth < 0:
|
||||
azimuth += 2 * math.pi
|
||||
|
||||
return elevation, azimuth, distance
|
||||
|
||||
|
||||
def polar_to_c2w(elevation: float, azimuth: float, distance: float) -> Float[Tensor, "4 4"]:
|
||||
"""
|
||||
Compute L = p - C.
|
||||
Normalize L.
|
||||
Compute s = L x u. (cross product)
|
||||
Normalize s.
|
||||
Compute u' = s x L.
|
||||
rotation = [s, u, -l]
|
||||
"""
|
||||
z = distance * math.sin(elevation)
|
||||
x = distance * math.cos(elevation) * math.cos(azimuth)
|
||||
y = distance * math.cos(elevation) * math.sin(azimuth)
|
||||
l = -torch.as_tensor([x, y, z]).float()
|
||||
l = F.normalize(l, dim=0)
|
||||
u = torch.as_tensor([0.0, 0.0, 1.0]).float()
|
||||
s = l.cross(u)
|
||||
s = F.normalize(s, dim=0)
|
||||
u = s.cross(l)
|
||||
rot = torch.stack([s, u, -l], dim=0).T
|
||||
c2w = torch.zeros((4, 4), dtype=torch.float32)
|
||||
c2w[:3, :3] = rot
|
||||
c2w[:3, 3] = torch.as_tensor([x, y, z])
|
||||
c2w[3, 3] = 1
|
||||
return c2w
|
||||
@@ -0,0 +1,81 @@
|
||||
import nvdiffrast.torch as dr
|
||||
import torch
|
||||
|
||||
from .typing import *
|
||||
|
||||
|
||||
class NVDiffRasterizerContext:
|
||||
def __init__(self, context_type: str, device: torch.device) -> None:
|
||||
self.device = device
|
||||
self.ctx = self.initialize_context(context_type, device)
|
||||
|
||||
def initialize_context(
|
||||
self, context_type: str, device: torch.device
|
||||
) -> Union[dr.RasterizeGLContext, dr.RasterizeCudaContext]:
|
||||
context_type = "cuda" # This will throw: RuntimeError: Error building extension 'nvdiffrast_plugin', 'cl.exe' is not recognized as an internal or external command
|
||||
if context_type == "gl":
|
||||
return dr.RasterizeGLContext(device=device)
|
||||
elif context_type == "cuda":
|
||||
return dr.RasterizeCudaContext(device=device)
|
||||
else:
|
||||
raise ValueError(f"Unknown rasterizer context type: {context_type}")
|
||||
|
||||
def vertex_transform(
|
||||
self, verts: Float[Tensor, "Nv 3"], mvp_mtx: Float[Tensor, "B 4 4"]
|
||||
) -> Float[Tensor, "B Nv 4"]:
|
||||
with torch.cuda.amp.autocast(enabled=False):
|
||||
verts_homo = torch.cat(
|
||||
[verts, torch.ones([verts.shape[0], 1]).to(verts)], dim=-1
|
||||
)
|
||||
verts_clip = torch.matmul(verts_homo, mvp_mtx.permute(0, 2, 1))
|
||||
return verts_clip
|
||||
|
||||
def rasterize(
|
||||
self,
|
||||
pos: Float[Tensor, "B Nv 4"],
|
||||
tri: Integer[Tensor, "Nf 3"],
|
||||
resolution: Union[int, Tuple[int, int]],
|
||||
):
|
||||
# rasterize in instance mode (single topology)
|
||||
return dr.rasterize(self.ctx, pos.float(), tri.int(), resolution, grad_db=True)
|
||||
|
||||
def rasterize_one(
|
||||
self,
|
||||
pos: Float[Tensor, "Nv 4"],
|
||||
tri: Integer[Tensor, "Nf 3"],
|
||||
resolution: Union[int, Tuple[int, int]],
|
||||
):
|
||||
# rasterize one single mesh under a single viewpoint
|
||||
rast, rast_db = self.rasterize(pos[None, ...], tri, resolution)
|
||||
return rast[0], rast_db[0]
|
||||
|
||||
def antialias(
|
||||
self,
|
||||
color: Float[Tensor, "B H W C"],
|
||||
rast: Float[Tensor, "B H W 4"],
|
||||
pos: Float[Tensor, "B Nv 4"],
|
||||
tri: Integer[Tensor, "Nf 3"],
|
||||
) -> Float[Tensor, "B H W C"]:
|
||||
return dr.antialias(color.float(), rast, pos.float(), tri.int())
|
||||
|
||||
def interpolate(
|
||||
self,
|
||||
attr: Float[Tensor, "B Nv C"],
|
||||
rast: Float[Tensor, "B H W 4"],
|
||||
tri: Integer[Tensor, "Nf 3"],
|
||||
rast_db=None,
|
||||
diff_attrs=None,
|
||||
) -> Float[Tensor, "B H W C"]:
|
||||
return dr.interpolate(
|
||||
attr.float(), rast, tri.int(), rast_db=rast_db, diff_attrs=diff_attrs
|
||||
)
|
||||
|
||||
def interpolate_one(
|
||||
self,
|
||||
attr: Float[Tensor, "Nv C"],
|
||||
rast: Float[Tensor, "B H W 4"],
|
||||
tri: Integer[Tensor, "Nf 3"],
|
||||
rast_db=None,
|
||||
diff_attrs=None,
|
||||
) -> Float[Tensor, "B H W C"]:
|
||||
return self.interpolate(attr[None, ...], rast, tri, rast_db, diff_attrs)
|
||||
@@ -0,0 +1,725 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import cv2
|
||||
import imageio
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import torch
|
||||
from matplotlib import cm
|
||||
from matplotlib.colors import LinearSegmentedColormap
|
||||
from PIL import Image, ImageDraw
|
||||
from pytorch_lightning.loggers import WandbLogger
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from ..models.mesh import Mesh
|
||||
from ..utils.typing import *
|
||||
|
||||
|
||||
class SaverMixin:
|
||||
import wandb
|
||||
_save_dir: Optional[str] = None
|
||||
_wandb_logger: Optional[WandbLogger] = None
|
||||
|
||||
def set_save_dir(self, save_dir: str):
|
||||
self._save_dir = save_dir
|
||||
|
||||
def get_save_dir(self):
|
||||
if self._save_dir is None:
|
||||
raise ValueError("Save dir is not set")
|
||||
return self._save_dir
|
||||
|
||||
def convert_data(self, data):
|
||||
if data is None:
|
||||
return None
|
||||
elif isinstance(data, np.ndarray):
|
||||
return data
|
||||
elif isinstance(data, torch.Tensor):
|
||||
if data.dtype in [torch.float16, torch.bfloat16]:
|
||||
data = data.float()
|
||||
return data.detach().cpu().numpy()
|
||||
elif isinstance(data, list):
|
||||
return [self.convert_data(d) for d in data]
|
||||
elif isinstance(data, dict):
|
||||
return {k: self.convert_data(v) for k, v in data.items()}
|
||||
else:
|
||||
raise TypeError(
|
||||
"Data must be in type numpy.ndarray, torch.Tensor, list or dict, getting",
|
||||
type(data),
|
||||
)
|
||||
|
||||
def get_save_path(self, filename):
|
||||
save_path = os.path.join(self.get_save_dir(), filename)
|
||||
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
||||
return save_path
|
||||
|
||||
DEFAULT_RGB_KWARGS = {"data_format": "HWC", "data_range": (0, 1)}
|
||||
DEFAULT_UV_KWARGS = {
|
||||
"data_format": "HWC",
|
||||
"data_range": (0, 1),
|
||||
"cmap": "checkerboard",
|
||||
}
|
||||
DEFAULT_GRAYSCALE_KWARGS = {"data_range": None, "cmap": "jet"}
|
||||
DEFAULT_GRID_KWARGS = {"align": "max"}
|
||||
|
||||
def get_rgb_image_(self, img, data_format, data_range, rgba=False):
|
||||
img = self.convert_data(img)
|
||||
assert data_format in ["CHW", "HWC"]
|
||||
if data_format == "CHW":
|
||||
img = img.transpose(1, 2, 0)
|
||||
if img.dtype != np.uint8:
|
||||
img = img.clip(min=data_range[0], max=data_range[1])
|
||||
img = (
|
||||
(img - data_range[0]) / (data_range[1] - data_range[0]) * 255.0
|
||||
).astype(np.uint8)
|
||||
nc = 4 if rgba else 3
|
||||
imgs = [img[..., start : start + nc] for start in range(0, img.shape[-1], nc)]
|
||||
imgs = [
|
||||
img_
|
||||
if img_.shape[-1] == nc
|
||||
else np.concatenate(
|
||||
[
|
||||
img_,
|
||||
np.zeros(
|
||||
(img_.shape[0], img_.shape[1], nc - img_.shape[2]),
|
||||
dtype=img_.dtype,
|
||||
),
|
||||
],
|
||||
axis=-1,
|
||||
)
|
||||
for img_ in imgs
|
||||
]
|
||||
img = np.concatenate(imgs, axis=1)
|
||||
if rgba:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA)
|
||||
else:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
return img
|
||||
|
||||
def _save_rgb_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_format,
|
||||
data_range,
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
):
|
||||
img = self.get_rgb_image_(img, data_format, data_range)
|
||||
cv2.imwrite(filename, img)
|
||||
if name and self._wandb_logger:
|
||||
self._wandb_logger.log_image(
|
||||
key=name, images=[self.get_save_path(filename)], step=step
|
||||
)
|
||||
|
||||
def save_rgb_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_format=DEFAULT_RGB_KWARGS["data_format"],
|
||||
data_range=DEFAULT_RGB_KWARGS["data_range"],
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
self._save_rgb_image(save_path, img, data_format, data_range, name, step)
|
||||
return save_path
|
||||
|
||||
def get_uv_image_(self, img, data_format, data_range, cmap):
|
||||
img = self.convert_data(img)
|
||||
assert data_format in ["CHW", "HWC"]
|
||||
if data_format == "CHW":
|
||||
img = img.transpose(1, 2, 0)
|
||||
img = img.clip(min=data_range[0], max=data_range[1])
|
||||
img = (img - data_range[0]) / (data_range[1] - data_range[0])
|
||||
assert cmap in ["checkerboard", "color"]
|
||||
if cmap == "checkerboard":
|
||||
n_grid = 64
|
||||
mask = (img * n_grid).astype(int)
|
||||
mask = (mask[..., 0] + mask[..., 1]) % 2 == 0
|
||||
img = np.ones((img.shape[0], img.shape[1], 3), dtype=np.uint8) * 255
|
||||
img[mask] = np.array([255, 0, 255], dtype=np.uint8)
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
elif cmap == "color":
|
||||
img_ = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
|
||||
img_[..., 0] = (img[..., 0] * 255).astype(np.uint8)
|
||||
img_[..., 1] = (img[..., 1] * 255).astype(np.uint8)
|
||||
img_ = cv2.cvtColor(img_, cv2.COLOR_RGB2BGR)
|
||||
img = img_
|
||||
return img
|
||||
|
||||
def save_uv_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_format=DEFAULT_UV_KWARGS["data_format"],
|
||||
data_range=DEFAULT_UV_KWARGS["data_range"],
|
||||
cmap=DEFAULT_UV_KWARGS["cmap"],
|
||||
) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
img = self.get_uv_image_(img, data_format, data_range, cmap)
|
||||
cv2.imwrite(save_path, img)
|
||||
return save_path
|
||||
|
||||
def get_grayscale_image_(self, img, data_range, cmap):
|
||||
img = self.convert_data(img)
|
||||
img = np.nan_to_num(img)
|
||||
if data_range is None:
|
||||
img = (img - img.min()) / (img.max() - img.min())
|
||||
else:
|
||||
img = img.clip(data_range[0], data_range[1])
|
||||
img = (img - data_range[0]) / (data_range[1] - data_range[0])
|
||||
assert cmap in [None, "jet", "magma", "spectral"]
|
||||
if cmap == None:
|
||||
img = (img * 255.0).astype(np.uint8)
|
||||
img = np.repeat(img[..., None], 3, axis=2)
|
||||
elif cmap == "jet":
|
||||
img = (img * 255.0).astype(np.uint8)
|
||||
img = cv2.applyColorMap(img, cv2.COLORMAP_JET)
|
||||
elif cmap == "magma":
|
||||
img = 1.0 - img
|
||||
base = cm.get_cmap("magma")
|
||||
num_bins = 256
|
||||
colormap = LinearSegmentedColormap.from_list(
|
||||
f"{base.name}{num_bins}", base(np.linspace(0, 1, num_bins)), num_bins
|
||||
)(np.linspace(0, 1, num_bins))[:, :3]
|
||||
a = np.floor(img * 255.0)
|
||||
b = (a + 1).clip(max=255.0)
|
||||
f = img * 255.0 - a
|
||||
a = a.astype(np.uint16).clip(0, 255)
|
||||
b = b.astype(np.uint16).clip(0, 255)
|
||||
img = colormap[a] + (colormap[b] - colormap[a]) * f[..., None]
|
||||
img = (img * 255.0).astype(np.uint8)
|
||||
elif cmap == "spectral":
|
||||
colormap = plt.get_cmap("Spectral")
|
||||
|
||||
def blend_rgba(image):
|
||||
image = image[..., :3] * image[..., -1:] + (
|
||||
1.0 - image[..., -1:]
|
||||
) # blend A to RGB
|
||||
return image
|
||||
|
||||
img = colormap(img)
|
||||
img = blend_rgba(img)
|
||||
img = (img * 255).astype(np.uint8)
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
return img
|
||||
|
||||
def _save_grayscale_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_range,
|
||||
cmap,
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
):
|
||||
img = self.get_grayscale_image_(img, data_range, cmap)
|
||||
cv2.imwrite(filename, img)
|
||||
if name and self._wandb_logger:
|
||||
self._wandb_logger.log_image(
|
||||
key=name, images=[self.get_save_path(filename)], step=step
|
||||
)
|
||||
|
||||
def save_grayscale_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_range=DEFAULT_GRAYSCALE_KWARGS["data_range"],
|
||||
cmap=DEFAULT_GRAYSCALE_KWARGS["cmap"],
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
self._save_grayscale_image(save_path, img, data_range, cmap, name, step)
|
||||
return save_path
|
||||
|
||||
def get_image_grid_(self, imgs, align):
|
||||
if isinstance(imgs[0], list):
|
||||
return np.concatenate(
|
||||
[self.get_image_grid_(row, align) for row in imgs], axis=0
|
||||
)
|
||||
cols = []
|
||||
for col in imgs:
|
||||
assert col["type"] in ["rgb", "uv", "grayscale"]
|
||||
if col["type"] == "rgb":
|
||||
rgb_kwargs = self.DEFAULT_RGB_KWARGS.copy()
|
||||
rgb_kwargs.update(col["kwargs"])
|
||||
cols.append(self.get_rgb_image_(col["img"], **rgb_kwargs))
|
||||
elif col["type"] == "uv":
|
||||
uv_kwargs = self.DEFAULT_UV_KWARGS.copy()
|
||||
uv_kwargs.update(col["kwargs"])
|
||||
cols.append(self.get_uv_image_(col["img"], **uv_kwargs))
|
||||
elif col["type"] == "grayscale":
|
||||
grayscale_kwargs = self.DEFAULT_GRAYSCALE_KWARGS.copy()
|
||||
grayscale_kwargs.update(col["kwargs"])
|
||||
cols.append(self.get_grayscale_image_(col["img"], **grayscale_kwargs))
|
||||
|
||||
if align == "max":
|
||||
h = max([col.shape[0] for col in cols])
|
||||
elif align == "min":
|
||||
h = min([col.shape[0] for col in cols])
|
||||
elif isinstance(align, int):
|
||||
h = align
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported image grid align: {align}, should be min, max, or int"
|
||||
)
|
||||
|
||||
for i in range(len(cols)):
|
||||
if cols[i].shape[0] != h:
|
||||
w = int(cols[i].shape[1] * h / cols[i].shape[0])
|
||||
cols[i] = cv2.resize(cols[i], (w, h), interpolation=cv2.INTER_CUBIC)
|
||||
return np.concatenate(cols, axis=1)
|
||||
|
||||
def save_image_grid(
|
||||
self,
|
||||
filename,
|
||||
imgs,
|
||||
align=DEFAULT_GRID_KWARGS["align"],
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
texts: Optional[List[float]] = None,
|
||||
):
|
||||
save_path = self.get_save_path(filename)
|
||||
img = self.get_image_grid_(imgs, align=align)
|
||||
|
||||
if texts is not None:
|
||||
img = Image.fromarray(img)
|
||||
draw = ImageDraw.Draw(img)
|
||||
black, white = (0, 0, 0), (255, 255, 255)
|
||||
for i, text in enumerate(texts):
|
||||
draw.text((2, (img.size[1] // len(texts)) * i + 1), f"{text}", white)
|
||||
draw.text((0, (img.size[1] // len(texts)) * i + 1), f"{text}", white)
|
||||
draw.text((2, (img.size[1] // len(texts)) * i - 1), f"{text}", white)
|
||||
draw.text((0, (img.size[1] // len(texts)) * i - 1), f"{text}", white)
|
||||
draw.text((1, (img.size[1] // len(texts)) * i), f"{text}", black)
|
||||
img = np.asarray(img)
|
||||
|
||||
cv2.imwrite(save_path, img)
|
||||
if name and self._wandb_logger:
|
||||
self._wandb_logger.log_image(key=name, images=[save_path], step=step)
|
||||
return save_path
|
||||
|
||||
def save_image(self, filename, img) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
img = self.convert_data(img)
|
||||
assert img.dtype == np.uint8 or img.dtype == np.uint16
|
||||
if img.ndim == 3 and img.shape[-1] == 3:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
elif img.ndim == 3 and img.shape[-1] == 4:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA)
|
||||
cv2.imwrite(save_path, img)
|
||||
return save_path
|
||||
|
||||
def save_cubemap(self, filename, img, data_range=(0, 1), rgba=False) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
img = self.convert_data(img)
|
||||
assert img.ndim == 4 and img.shape[0] == 6 and img.shape[1] == img.shape[2]
|
||||
|
||||
imgs_full = []
|
||||
for start in range(0, img.shape[-1], 3):
|
||||
img_ = img[..., start : start + 3]
|
||||
img_ = np.stack(
|
||||
[
|
||||
self.get_rgb_image_(img_[i], "HWC", data_range, rgba=rgba)
|
||||
for i in range(img_.shape[0])
|
||||
],
|
||||
axis=0,
|
||||
)
|
||||
size = img_.shape[1]
|
||||
placeholder = np.zeros((size, size, 3), dtype=np.float32)
|
||||
img_full = np.concatenate(
|
||||
[
|
||||
np.concatenate(
|
||||
[placeholder, img_[2], placeholder, placeholder], axis=1
|
||||
),
|
||||
np.concatenate([img_[1], img_[4], img_[0], img_[5]], axis=1),
|
||||
np.concatenate(
|
||||
[placeholder, img_[3], placeholder, placeholder], axis=1
|
||||
),
|
||||
],
|
||||
axis=0,
|
||||
)
|
||||
imgs_full.append(img_full)
|
||||
|
||||
imgs_full = np.concatenate(imgs_full, axis=1)
|
||||
cv2.imwrite(save_path, imgs_full)
|
||||
return save_path
|
||||
|
||||
def save_data(self, filename, data) -> str:
|
||||
data = self.convert_data(data)
|
||||
if isinstance(data, dict):
|
||||
if not filename.endswith(".npz"):
|
||||
filename += ".npz"
|
||||
save_path = self.get_save_path(filename)
|
||||
np.savez(save_path, **data)
|
||||
else:
|
||||
if not filename.endswith(".npy"):
|
||||
filename += ".npy"
|
||||
save_path = self.get_save_path(filename)
|
||||
np.save(save_path, data)
|
||||
return save_path
|
||||
|
||||
def save_state_dict(self, filename, data) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
torch.save(data, save_path)
|
||||
return save_path
|
||||
|
||||
def save_img_sequence(
|
||||
self,
|
||||
filename,
|
||||
img_dir,
|
||||
matcher,
|
||||
save_format="mp4",
|
||||
fps=30,
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
) -> str:
|
||||
assert save_format in ["gif", "mp4"]
|
||||
if not filename.endswith(save_format):
|
||||
filename += f".{save_format}"
|
||||
save_path = self.get_save_path(filename)
|
||||
matcher = re.compile(matcher)
|
||||
img_dir = os.path.join(self.get_save_dir(), img_dir)
|
||||
imgs = []
|
||||
for f in os.listdir(img_dir):
|
||||
if matcher.search(f):
|
||||
imgs.append(f)
|
||||
imgs = sorted(imgs, key=lambda f: int(matcher.search(f).groups()[0]))
|
||||
imgs = [cv2.imread(os.path.join(img_dir, f)) for f in imgs]
|
||||
|
||||
if save_format == "gif":
|
||||
imgs = [cv2.cvtColor(i, cv2.COLOR_BGR2RGB) for i in imgs]
|
||||
imageio.mimsave(save_path, imgs, fps=fps, palettesize=256)
|
||||
elif save_format == "mp4":
|
||||
imgs = [cv2.cvtColor(i, cv2.COLOR_BGR2RGB) for i in imgs]
|
||||
imageio.mimsave(save_path, imgs, fps=fps)
|
||||
if name and self._wandb_logger:
|
||||
lrm.warn("Wandb logger does not support video logging yet!")
|
||||
return save_path
|
||||
|
||||
def save_img_sequences(
|
||||
self,
|
||||
seq_dir,
|
||||
matcher,
|
||||
save_format="mp4",
|
||||
fps=30,
|
||||
delete=True,
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
):
|
||||
seq_dir_ = os.path.join(self.get_save_dir(), seq_dir)
|
||||
for f in os.listdir(seq_dir_):
|
||||
img_dir_ = os.path.join(seq_dir_, f)
|
||||
if not os.path.isdir(img_dir_):
|
||||
continue
|
||||
try:
|
||||
self.save_img_sequence(
|
||||
os.path.join(seq_dir, f),
|
||||
os.path.join(seq_dir, f),
|
||||
matcher,
|
||||
save_format=save_format,
|
||||
fps=fps,
|
||||
name=f"{name}_{f}",
|
||||
step=step,
|
||||
)
|
||||
if delete:
|
||||
shutil.rmtree(img_dir_)
|
||||
except:
|
||||
lrm.warn(f"Video saving for directory {seq_dir_} failed!")
|
||||
|
||||
def save_mesh(self, filename, v_pos, t_pos_idx, v_tex=None, t_tex_idx=None) -> str:
|
||||
import trimesh
|
||||
|
||||
save_path = self.get_save_path(filename)
|
||||
v_pos = self.convert_data(v_pos)
|
||||
t_pos_idx = self.convert_data(t_pos_idx)
|
||||
mesh = trimesh.Trimesh(vertices=v_pos, faces=t_pos_idx)
|
||||
mesh.export(save_path)
|
||||
return save_path
|
||||
|
||||
def save_obj(
|
||||
self,
|
||||
filename: str,
|
||||
mesh: Mesh,
|
||||
save_mat: bool = False,
|
||||
save_normal: bool = False,
|
||||
save_uv: bool = False,
|
||||
save_vertex_color: bool = True,
|
||||
map_Kd: Optional[Float[Tensor, "H W 3"]] = None,
|
||||
map_Ks: Optional[Float[Tensor, "H W 3"]] = None,
|
||||
map_Bump: Optional[Float[Tensor, "H W 3"]] = None,
|
||||
map_Pm: Optional[Float[Tensor, "H W 1"]] = None,
|
||||
map_Pr: Optional[Float[Tensor, "H W 1"]] = None,
|
||||
map_format: str = "jpg",
|
||||
) -> List[str]:
|
||||
save_paths: List[str] = []
|
||||
if not filename.endswith(".obj"):
|
||||
filename += ".obj"
|
||||
v_pos, t_pos_idx = self.convert_data(mesh.v_pos), self.convert_data(
|
||||
mesh.t_pos_idx
|
||||
)
|
||||
v_nrm, v_tex, t_tex_idx, v_rgb = None, None, None, None
|
||||
if save_normal:
|
||||
v_nrm = self.convert_data(mesh.v_nrm)
|
||||
if save_uv:
|
||||
v_tex, t_tex_idx = self.convert_data(mesh.v_tex), self.convert_data(
|
||||
mesh.t_tex_idx
|
||||
)
|
||||
if save_vertex_color:
|
||||
v_rgb = self.convert_data(mesh.v_rgb)
|
||||
matname, mtllib = None, None
|
||||
if save_mat:
|
||||
matname = "default"
|
||||
mtl_filename = filename.replace(".obj", ".mtl")
|
||||
mtllib = os.path.basename(mtl_filename)
|
||||
mtl_save_paths = self._save_mtl(
|
||||
mtl_filename,
|
||||
matname,
|
||||
map_Kd=self.convert_data(map_Kd),
|
||||
map_Ks=self.convert_data(map_Ks),
|
||||
map_Bump=self.convert_data(map_Bump),
|
||||
map_Pm=self.convert_data(map_Pm),
|
||||
map_Pr=self.convert_data(map_Pr),
|
||||
map_format=map_format,
|
||||
)
|
||||
save_paths += mtl_save_paths
|
||||
obj_save_path = self._save_obj(
|
||||
filename,
|
||||
v_pos,
|
||||
t_pos_idx,
|
||||
v_nrm=v_nrm,
|
||||
v_tex=v_tex,
|
||||
t_tex_idx=t_tex_idx,
|
||||
v_rgb=v_rgb,
|
||||
matname=matname,
|
||||
mtllib=mtllib,
|
||||
)
|
||||
save_paths.append(obj_save_path)
|
||||
return save_paths
|
||||
|
||||
def _save_obj(
|
||||
self,
|
||||
filename,
|
||||
v_pos,
|
||||
t_pos_idx,
|
||||
v_nrm=None,
|
||||
v_tex=None,
|
||||
t_tex_idx=None,
|
||||
v_rgb=None,
|
||||
matname=None,
|
||||
mtllib=None,
|
||||
) -> str:
|
||||
obj_str = ""
|
||||
if matname is not None:
|
||||
obj_str += f"mtllib {mtllib}\n"
|
||||
obj_str += f"g object\n"
|
||||
obj_str += f"usemtl {matname}\n"
|
||||
for i in range(len(v_pos)):
|
||||
obj_str += f"v {v_pos[i][0]} {v_pos[i][1]} {v_pos[i][2]}"
|
||||
if v_rgb is not None:
|
||||
obj_str += f" {v_rgb[i][0]} {v_rgb[i][1]} {v_rgb[i][2]}"
|
||||
obj_str += "\n"
|
||||
if v_nrm is not None:
|
||||
for v in v_nrm:
|
||||
obj_str += f"vn {v[0]} {v[1]} {v[2]}\n"
|
||||
if v_tex is not None:
|
||||
for v in v_tex:
|
||||
obj_str += f"vt {v[0]} {1.0 - v[1]}\n"
|
||||
|
||||
for i in range(len(t_pos_idx)):
|
||||
obj_str += "f"
|
||||
for j in range(3):
|
||||
obj_str += f" {t_pos_idx[i][j] + 1}/"
|
||||
if v_tex is not None:
|
||||
obj_str += f"{t_tex_idx[i][j] + 1}"
|
||||
obj_str += "/"
|
||||
if v_nrm is not None:
|
||||
obj_str += f"{t_pos_idx[i][j] + 1}"
|
||||
obj_str += "\n"
|
||||
|
||||
save_path = self.get_save_path(filename)
|
||||
with open(save_path, "w") as f:
|
||||
f.write(obj_str)
|
||||
return save_path
|
||||
|
||||
def _save_mtl(
|
||||
self,
|
||||
filename,
|
||||
matname,
|
||||
Ka=(0.0, 0.0, 0.0),
|
||||
Kd=(1.0, 1.0, 1.0),
|
||||
Ks=(0.0, 0.0, 0.0),
|
||||
map_Kd=None,
|
||||
map_Ks=None,
|
||||
map_Bump=None,
|
||||
map_Pm=None,
|
||||
map_Pr=None,
|
||||
map_format="jpg",
|
||||
step: Optional[int] = None,
|
||||
) -> List[str]:
|
||||
mtl_save_path = self.get_save_path(filename)
|
||||
save_paths = [mtl_save_path]
|
||||
mtl_str = f"newmtl {matname}\n"
|
||||
mtl_str += f"Ka {Ka[0]} {Ka[1]} {Ka[2]}\n"
|
||||
if map_Kd is not None:
|
||||
map_Kd_save_path = os.path.join(
|
||||
os.path.dirname(mtl_save_path), f"texture_kd.{map_format}"
|
||||
)
|
||||
mtl_str += f"map_Kd texture_kd.{map_format}\n"
|
||||
self._save_rgb_image(
|
||||
map_Kd_save_path,
|
||||
map_Kd,
|
||||
data_format="HWC",
|
||||
data_range=(0, 1),
|
||||
name=f"{matname}_Kd",
|
||||
step=step,
|
||||
)
|
||||
save_paths.append(map_Kd_save_path)
|
||||
else:
|
||||
mtl_str += f"Kd {Kd[0]} {Kd[1]} {Kd[2]}\n"
|
||||
if map_Ks is not None:
|
||||
map_Ks_save_path = os.path.join(
|
||||
os.path.dirname(mtl_save_path), f"texture_ks.{map_format}"
|
||||
)
|
||||
mtl_str += f"map_Ks texture_ks.{map_format}\n"
|
||||
self._save_rgb_image(
|
||||
map_Ks_save_path,
|
||||
map_Ks,
|
||||
data_format="HWC",
|
||||
data_range=(0, 1),
|
||||
name=f"{matname}_Ks",
|
||||
step=step,
|
||||
)
|
||||
save_paths.append(map_Ks_save_path)
|
||||
else:
|
||||
mtl_str += f"Ks {Ks[0]} {Ks[1]} {Ks[2]}\n"
|
||||
if map_Bump is not None:
|
||||
map_Bump_save_path = os.path.join(
|
||||
os.path.dirname(mtl_save_path), f"texture_nrm.{map_format}"
|
||||
)
|
||||
mtl_str += f"map_Bump texture_nrm.{map_format}\n"
|
||||
self._save_rgb_image(
|
||||
map_Bump_save_path,
|
||||
map_Bump,
|
||||
data_format="HWC",
|
||||
data_range=(0, 1),
|
||||
name=f"{matname}_Bump",
|
||||
step=step,
|
||||
)
|
||||
save_paths.append(map_Bump_save_path)
|
||||
if map_Pm is not None:
|
||||
map_Pm_save_path = os.path.join(
|
||||
os.path.dirname(mtl_save_path), f"texture_metallic.{map_format}"
|
||||
)
|
||||
mtl_str += f"map_Pm texture_metallic.{map_format}\n"
|
||||
self._save_grayscale_image(
|
||||
map_Pm_save_path,
|
||||
map_Pm,
|
||||
data_range=(0, 1),
|
||||
cmap=None,
|
||||
name=f"{matname}_refl",
|
||||
step=step,
|
||||
)
|
||||
save_paths.append(map_Pm_save_path)
|
||||
if map_Pr is not None:
|
||||
map_Pr_save_path = os.path.join(
|
||||
os.path.dirname(mtl_save_path), f"texture_roughness.{map_format}"
|
||||
)
|
||||
mtl_str += f"map_Pr texture_roughness.{map_format}\n"
|
||||
self._save_grayscale_image(
|
||||
map_Pr_save_path,
|
||||
map_Pr,
|
||||
data_range=(0, 1),
|
||||
cmap=None,
|
||||
name=f"{matname}_Ns",
|
||||
step=step,
|
||||
)
|
||||
save_paths.append(map_Pr_save_path)
|
||||
with open(self.get_save_path(filename), "w") as f:
|
||||
f.write(mtl_str)
|
||||
return save_paths
|
||||
|
||||
def save_glb(
|
||||
self,
|
||||
filename: str,
|
||||
mesh: Mesh,
|
||||
save_mat: bool = False,
|
||||
save_normal: bool = True,
|
||||
save_uv: bool = True,
|
||||
save_vertex_color: bool = True,
|
||||
map_Kd: Optional[Float[Tensor, "H W 3"]] = None,
|
||||
map_Ks: Optional[Float[Tensor, "H W 3"]] = None,
|
||||
map_Bump: Optional[Float[Tensor, "H W 3"]] = None,
|
||||
map_Pm: Optional[Float[Tensor, "H W 1"]] = None,
|
||||
map_Pr: Optional[Float[Tensor, "H W 1"]] = None,
|
||||
map_format: str = "jpg",
|
||||
) -> List[str]:
|
||||
save_paths: List[str] = []
|
||||
if not filename.endswith(".glb"):
|
||||
filename += ".glb"
|
||||
v_pos, t_pos_idx = self.convert_data(mesh.v_pos), self.convert_data(
|
||||
mesh.t_pos_idx
|
||||
)
|
||||
v_nrm, v_tex, t_tex_idx, v_rgb = None, None, None, None
|
||||
if save_normal:
|
||||
v_nrm = self.convert_data(mesh.v_nrm)
|
||||
if save_uv:
|
||||
v_tex, t_tex_idx = self.convert_data(mesh.v_tex), self.convert_data(
|
||||
mesh.t_tex_idx
|
||||
)
|
||||
if save_vertex_color:
|
||||
v_rgb = self.convert_data(mesh.v_rgb)
|
||||
|
||||
obj_save_path = self._save_glb(
|
||||
filename,
|
||||
v_pos,
|
||||
t_pos_idx,
|
||||
v_nrm=v_nrm,
|
||||
v_tex=v_tex,
|
||||
t_tex_idx=t_tex_idx,
|
||||
v_rgb=v_rgb,
|
||||
)
|
||||
save_paths.append(obj_save_path)
|
||||
return save_paths
|
||||
|
||||
def _save_glb(
|
||||
self,
|
||||
filename,
|
||||
v_pos,
|
||||
t_pos_idx,
|
||||
v_nrm=None,
|
||||
v_tex=None,
|
||||
t_tex_idx=None,
|
||||
v_rgb=None,
|
||||
matname=None,
|
||||
mtllib=None,
|
||||
) -> str:
|
||||
import trimesh
|
||||
|
||||
mesh = trimesh.Trimesh(
|
||||
vertices=v_pos, faces=t_pos_idx, vertex_normals=v_nrm, vertex_colors=v_rgb
|
||||
)
|
||||
# not tested
|
||||
if v_tex is not None:
|
||||
mesh.visual = trimesh.visual.TextureVisuals(uv=v_tex)
|
||||
|
||||
save_path = self.get_save_path(filename)
|
||||
mesh.export(save_path)
|
||||
return save_path
|
||||
|
||||
def save_file(self, filename, src_path, delete=False) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
shutil.copyfile(src_path, save_path)
|
||||
if delete:
|
||||
os.remove(src_path)
|
||||
return save_path
|
||||
|
||||
def save_json(self, filename, payload) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
with open(save_path, "w") as f:
|
||||
f.write(json.dumps(payload))
|
||||
return save_path
|
||||
@@ -0,0 +1,13 @@
|
||||
import pySDF as SDF
|
||||
import numpy as np
|
||||
from CharacterGen.Stage_3D.lrm.models.isosurface import MarchingTetrahedraHelper
|
||||
|
||||
def get_tetra_for_mesh(mesh_path, resolution=128):
|
||||
isosurface_helper = MarchingTetrahedraHelper(resolution, f"load/{resolution}_tets.npz")
|
||||
isosurface_helper.points_range = (-1, 1)
|
||||
mesh = trimesh.load(mesh_path)
|
||||
dmtet = np.load(f"")
|
||||
sdf = SDF(mesh.vertices, mesh.faces)
|
||||
sdf_gt = sdf(isosurface_helper.grid_vertices.numpy())
|
||||
return sdf_gt
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
This module contains type annotations for the project, using
|
||||
1. Python type hints (https://docs.python.org/3/library/typing.html) for Python objects
|
||||
2. jaxtyping (https://github.com/google/jaxtyping/blob/main/API.md) for PyTorch tensors
|
||||
|
||||
Two types of typing checking can be used:
|
||||
1. Static type checking with mypy (install with pip and enabled as the default linter in VSCode)
|
||||
2. Runtime type checking with typeguard (install with pip and triggered at runtime, mainly for tensor dtype and shape checking)
|
||||
"""
|
||||
|
||||
# Basic types
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
NewType,
|
||||
Optional,
|
||||
Sized,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
# Tensor dtype
|
||||
# for jaxtyping usage, see https://github.com/google/jaxtyping/blob/main/API.md
|
||||
from jaxtyping import Bool, Complex, Float, Inexact, Int, Integer, Num, Shaped, UInt
|
||||
|
||||
# Config type
|
||||
from omegaconf import DictConfig
|
||||
|
||||
# PyTorch Tensor type
|
||||
from torch import Tensor
|
||||
|
||||
# Runtime type checking decorator
|
||||
from typeguard import typechecked as typechecker
|
||||
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"locations": [
|
||||
{
|
||||
"transform_matrix": [
|
||||
[ 1.0, 0.0, 0.0, 0.0 ],
|
||||
[ 0.0, 0.0, 1.0, 1.5 ],
|
||||
[ 0.0, 1.0, 0.0, 0.0 ],
|
||||
[ 0.0, 0.0, 0.0, 1.0 ]
|
||||
]
|
||||
},
|
||||
{
|
||||
"transform_matrix": [
|
||||
[ -1.0, 0.0, 0.0, 0.0 ],
|
||||
[ 0.0, 0.0, -1.0, -1.5 ],
|
||||
[ 0.0, 1.0, 0.0, 0.0 ],
|
||||
[ 0.0, 0.0, 0.0, 1.0 ]
|
||||
]
|
||||
},
|
||||
{
|
||||
"transform_matrix": [
|
||||
[ 0.0, 0.0, 1.0, 1.5 ],
|
||||
[ -1.0, 0.0, 0.0, 0.0 ],
|
||||
[ 0.0, 1.0, 0.0, 0.0 ],
|
||||
[ 0.0, 0.0, 0.0, 1.0 ]
|
||||
]
|
||||
},
|
||||
{
|
||||
"transform_matrix": [
|
||||
[ 0.0, 0.0, -1.0, -1.5 ],
|
||||
[ 1.0, 0.0, 0.0, 0.0 ],
|
||||
[ 0.0, 1.0, 0.0, 0.0 ],
|
||||
[ 0.0, 0.0, 0.0, 1.0 ]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# Copyright (c) 2020-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
#
|
||||
# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
|
||||
# property and proprietary rights in and to this material, related
|
||||
# documentation and any modifications thereto. Any use, reproduction,
|
||||
# disclosure or distribution of this material and related documentation
|
||||
# without an express license agreement from NVIDIA CORPORATION or
|
||||
# its affiliates is strictly prohibited.
|
||||
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
|
||||
"""
|
||||
This code segment shows how to use Quartet: https://github.com/crawforddoran/quartet,
|
||||
to generate a tet grid
|
||||
1) Download, compile and run Quartet as described in the link above. Example usage `quartet meshes/cube.obj 0.5 cube_5.tet`
|
||||
2) Run the function below to generate a file `cube_32_tet.tet`
|
||||
"""
|
||||
|
||||
|
||||
def generate_tetrahedron_grid_file(res=32, root=".."):
|
||||
frac = 1.0 / res
|
||||
command = f"cd {root}; ./quartet meshes/cube.obj {frac} meshes/cube_{res}_tet.tet -s meshes/cube_boundary_{res}.obj"
|
||||
os.system(command)
|
||||
|
||||
|
||||
"""
|
||||
This code segment shows how to convert from a quartet .tet file to compressed npz file
|
||||
"""
|
||||
|
||||
|
||||
def convert_from_quartet_to_npz(quartetfile="cube_32_tet.tet", npzfile="32_tets"):
|
||||
file1 = open(quartetfile, "r")
|
||||
header = file1.readline()
|
||||
numvertices = int(header.split(" ")[1])
|
||||
numtets = int(header.split(" ")[2])
|
||||
print(numvertices, numtets)
|
||||
|
||||
# load vertices
|
||||
vertices = np.loadtxt(quartetfile, skiprows=1, max_rows=numvertices)
|
||||
print(vertices.shape)
|
||||
|
||||
# load indices
|
||||
indices = np.loadtxt(
|
||||
quartetfile, dtype=int, skiprows=1 + numvertices, max_rows=numtets
|
||||
)
|
||||
print(indices.shape)
|
||||
|
||||
np.savez_compressed(npzfile, vertices=vertices, indices=indices)
|
||||
|
||||
|
||||
root = "/home/gyc/quartet"
|
||||
for res in [300, 350, 400]:
|
||||
generate_tetrahedron_grid_file(res, root)
|
||||
convert_from_quartet_to_npz(
|
||||
os.path.join(root, f"meshes/cube_{res}_tet.tet"), npzfile=f"{res}_tets"
|
||||
)
|
||||
@@ -0,0 +1,236 @@
|
||||
import sys
|
||||
import os
|
||||
from os.path import dirname
|
||||
from typing import Dict, List
|
||||
import numpy as np
|
||||
|
||||
import torch
|
||||
import torch.utils.checkpoint
|
||||
|
||||
from diffusers import AutoencoderKL, DDIMScheduler
|
||||
from transformers import CLIPTextModel, CLIPTokenizer, CLIPImageProcessor, CLIPVisionModelWithProjection
|
||||
from torchvision import transforms
|
||||
|
||||
from CharacterGen.Stage_2D.tuneavideo.models.unet_mv2d_condition import UNetMV2DConditionModel
|
||||
from CharacterGen.Stage_2D.tuneavideo.models.unet_mv2d_ref import UNetMV2DRefModel
|
||||
from CharacterGen.Stage_2D.tuneavideo.models.PoseGuider import PoseGuider
|
||||
from CharacterGen.Stage_2D.tuneavideo.pipelines.pipeline_tuneavideo import TuneAVideoPipeline
|
||||
|
||||
from einops import rearrange
|
||||
from PIL import Image
|
||||
import json
|
||||
|
||||
import trimesh
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from datetime import datetime
|
||||
from pygltflib import GLTF2
|
||||
import pymeshlab
|
||||
|
||||
CHARACTER_GEN_ROOT_ABS_PATH = dirname(__file__)
|
||||
CHARACTER_GEN_2D_DATA_ABS_PATH = os.path.join(CHARACTER_GEN_ROOT_ABS_PATH, "Stage_2D/material")
|
||||
CHARACTER_GEN_3D_ABS_PATH = os.path.join(CHARACTER_GEN_ROOT_ABS_PATH, "Stage_3D")
|
||||
CHARACTER_GEN_3D_DATA_ABS_PATH = os.path.join(CHARACTER_GEN_3D_ABS_PATH, "material")
|
||||
|
||||
sys.path.append(CHARACTER_GEN_3D_ABS_PATH)
|
||||
|
||||
def get_bg_color(bg_color):
|
||||
if bg_color == 'white':
|
||||
bg_color = np.array([1., 1., 1.], dtype=np.float32)
|
||||
elif bg_color == 'black':
|
||||
bg_color = np.array([0., 0., 0.], dtype=np.float32)
|
||||
elif bg_color == 'gray':
|
||||
bg_color = np.array([0.5, 0.5, 0.5], dtype=np.float32)
|
||||
elif bg_color == 'random':
|
||||
bg_color = np.random.rand(3)
|
||||
elif isinstance(bg_color, float):
|
||||
bg_color = np.array([bg_color] * 3, dtype=np.float32)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
return bg_color
|
||||
|
||||
def process_image(image, totensor, w=512, h=768):
|
||||
if not image.mode == "RGBA":
|
||||
image = image.convert("RGBA")
|
||||
|
||||
# Find non-transparent pixels
|
||||
non_transparent = np.nonzero(np.array(image)[..., 3])
|
||||
min_x, max_x = non_transparent[1].min(), non_transparent[1].max()
|
||||
min_y, max_y = non_transparent[0].min(), non_transparent[0].max()
|
||||
image = image.crop((min_x, min_y, max_x, max_y))
|
||||
|
||||
# paste to center
|
||||
max_dim = max(image.width, image.height)
|
||||
max_height = max_dim
|
||||
max_width = int(max_dim / 3 * 2)
|
||||
new_image = Image.new("RGBA", (max_width, max_height))
|
||||
left = (max_width - image.width) // 2
|
||||
top = (max_height - image.height) // 2
|
||||
new_image.paste(image, (left, top))
|
||||
|
||||
image = new_image.resize((w, h), resample=Image.Resampling.BICUBIC)
|
||||
image = np.array(image)
|
||||
image = image.astype(np.float32) / 255.
|
||||
assert image.shape[-1] == 4 # RGBA
|
||||
alpha = image[..., 3:4]
|
||||
bg_color = get_bg_color("gray")
|
||||
image = image[..., :3] * alpha + bg_color * (1 - alpha)
|
||||
# save image
|
||||
new_image = Image.fromarray((image * 255).astype(np.uint8))
|
||||
return totensor(image)
|
||||
|
||||
class Inference2D_API:
|
||||
|
||||
def __init__(self,
|
||||
pretrained_model_path: str,
|
||||
checkpoint_root_path: str,
|
||||
image_encoder_path: str,
|
||||
ckpt_dir: str,
|
||||
validation: Dict,
|
||||
local_crossattn: bool = True,
|
||||
unet_from_pretrained_kwargs=None,
|
||||
unet_condition_type=None,
|
||||
use_pose_guider=False,
|
||||
use_shifted_noise=False,
|
||||
use_noise=True,
|
||||
device="cuda",
|
||||
weight_dtype=torch.float16
|
||||
):
|
||||
self.validation = validation
|
||||
self.use_noise = use_noise
|
||||
self.use_shifted_noise = use_shifted_noise
|
||||
self.unet_condition_type = unet_condition_type
|
||||
self.device = device
|
||||
self.weight_dtype = weight_dtype
|
||||
image_encoder_path = os.path.join(checkpoint_root_path, image_encoder_path)
|
||||
ckpt_dir = os.path.join(checkpoint_root_path, ckpt_dir)
|
||||
|
||||
self.tokenizer = CLIPTokenizer.from_pretrained(pretrained_model_path, subfolder="tokenizer")
|
||||
text_encoder = CLIPTextModel.from_pretrained(pretrained_model_path, subfolder="text_encoder")
|
||||
image_encoder = CLIPVisionModelWithProjection.from_pretrained(image_encoder_path)
|
||||
feature_extractor = CLIPImageProcessor()
|
||||
vae = AutoencoderKL.from_pretrained(pretrained_model_path, subfolder="vae")
|
||||
unet = UNetMV2DConditionModel.from_pretrained_2d(pretrained_model_path, subfolder="unet", local_crossattn=local_crossattn, **unet_from_pretrained_kwargs)
|
||||
ref_unet = UNetMV2DRefModel.from_pretrained_2d(pretrained_model_path, subfolder="unet", local_crossattn=local_crossattn, **unet_from_pretrained_kwargs)
|
||||
if use_pose_guider:
|
||||
pose_guider = PoseGuider(noise_latent_channels=4).to(device)
|
||||
else:
|
||||
pose_guider = None
|
||||
|
||||
unet_params = torch.load(os.path.join(ckpt_dir, "pytorch_model.bin"), map_location="cpu")
|
||||
if use_pose_guider:
|
||||
pose_guider_params = torch.load(os.path.join(ckpt_dir, "pytorch_model_1.bin"), map_location="cpu")
|
||||
ref_unet_params = torch.load(os.path.join(ckpt_dir, "pytorch_model_2.bin"), map_location="cpu")
|
||||
pose_guider.load_state_dict(pose_guider_params)
|
||||
else:
|
||||
ref_unet_params = torch.load(os.path.join(ckpt_dir, "pytorch_model_1.bin"), map_location="cpu")
|
||||
unet.load_state_dict(unet_params)
|
||||
ref_unet.load_state_dict(ref_unet_params)
|
||||
|
||||
text_encoder.to(device, dtype=weight_dtype)
|
||||
image_encoder.to(device, dtype=weight_dtype)
|
||||
vae.to(device, dtype=weight_dtype)
|
||||
ref_unet.to(device, dtype=weight_dtype)
|
||||
unet.to(device, dtype=weight_dtype)
|
||||
|
||||
vae.requires_grad_(False)
|
||||
unet.requires_grad_(False)
|
||||
ref_unet.requires_grad_(False)
|
||||
|
||||
noise_scheduler = DDIMScheduler.from_pretrained(pretrained_model_path, subfolder="scheduler")
|
||||
self.validation_pipeline = TuneAVideoPipeline(
|
||||
vae=vae, text_encoder=text_encoder, tokenizer=self.tokenizer, unet=unet, ref_unet=ref_unet,feature_extractor=feature_extractor,image_encoder=image_encoder,
|
||||
scheduler=noise_scheduler
|
||||
)
|
||||
self.validation_pipeline.enable_vae_slicing()
|
||||
self.validation_pipeline.set_progress_bar_config(disable=True)
|
||||
|
||||
@torch.no_grad()
|
||||
def inference(
|
||||
self, input_image, val_width, val_height, prompt="high quality, best quality", prompt_neg=None,
|
||||
guidance_scale=7.5, num_inference_steps=40, seed=100, use_shifted_noise=False, crop=False
|
||||
):
|
||||
|
||||
generator = torch.Generator(device=self.device).manual_seed(seed)
|
||||
totensor = transforms.ToTensor()
|
||||
|
||||
metas = json.load(open(os.path.join(CHARACTER_GEN_2D_DATA_ABS_PATH, "pose.json"), "r"))
|
||||
cameras = []
|
||||
pose_images = []
|
||||
for lm in metas:
|
||||
cameras.append(torch.tensor(np.array(lm[0]).reshape(4, 4).transpose(1,0)[:3, :4]).reshape(-1))
|
||||
if not crop:
|
||||
pose_images.append(totensor(np.asarray(Image.open(os.path.join(CHARACTER_GEN_2D_DATA_ABS_PATH, lm[1])).resize(
|
||||
(val_height, val_width), resample=Image.Resampling.BICUBIC)).astype(np.float32) / 255.))
|
||||
else:
|
||||
pose_image = Image.open(os.path.join(CHARACTER_GEN_2D_DATA_ABS_PATH, lm[1]))
|
||||
crop_area = (128, 0, 640, 768)
|
||||
pose_images.append(totensor(np.array(pose_image.crop(crop_area)).astype(np.float32)) / 255.)
|
||||
camera_matrixs = torch.stack(cameras).unsqueeze(0).to(self.device)
|
||||
pose_imgs_in = torch.stack(pose_images).to(self.device)
|
||||
prompt_ids = self.tokenizer(
|
||||
prompt, max_length=self.tokenizer.model_max_length, padding="max_length", truncation=True, return_tensors="pt"
|
||||
).input_ids[0]
|
||||
|
||||
# (B*Nv, 3, H, W) B==1
|
||||
imgs_in = process_image(input_image, totensor, val_width, val_height)
|
||||
imgs_in = rearrange(imgs_in.unsqueeze(0).unsqueeze(0), "B Nv C H W -> (B Nv) C H W")
|
||||
|
||||
with torch.autocast(self.device, dtype=self.weight_dtype):
|
||||
imgs_in = imgs_in.to(self.device)
|
||||
# B*Nv images
|
||||
out = self.validation_pipeline(prompt=prompt, negative_prompt=prompt_neg, image=imgs_in.to(self.weight_dtype), generator=generator,
|
||||
num_inference_steps=num_inference_steps, guidance_scale=guidance_scale,
|
||||
camera_matrixs=camera_matrixs.to(self.weight_dtype), prompt_ids=prompt_ids,
|
||||
height=val_height, width=val_width, unet_condition_type=self.unet_condition_type,
|
||||
pose_guider=None, pose_image=pose_imgs_in, use_noise=self.use_noise,
|
||||
use_shifted_noise=use_shifted_noise, **self.validation).videos
|
||||
out = rearrange(out, "B C f H W -> (B f) H W C", f=self.validation.video_length)
|
||||
|
||||
torch.cuda.empty_cache()
|
||||
return out
|
||||
|
||||
class Inference3D_API:
|
||||
|
||||
def __init__(self, checkpoint_root_path, cfg, device="cuda"):
|
||||
self.cfg = cfg
|
||||
self.device = device
|
||||
self.cfg.system.weights = os.path.join(checkpoint_root_path, self.cfg.system.weights)
|
||||
self.cfg.system.image_tokenizer.pretrained_model_name_or_path = os.path.join(checkpoint_root_path, self.cfg.system.image_tokenizer.pretrained_model_name_or_path)
|
||||
self.cfg.system.renderer.tet_dir = os.path.join(CHARACTER_GEN_3D_DATA_ABS_PATH, self.cfg.system.renderer.tet_dir)
|
||||
self.system = lrm.find(self.cfg.system_cls)(self.cfg.system).to(self.device)
|
||||
self.system.eval()
|
||||
|
||||
def inference(self, mv_imgs: List[Image.Image]):
|
||||
meta = json.load(open(os.path.join(CHARACTER_GEN_3D_DATA_ABS_PATH, "meta.json")))
|
||||
c2w_cond = [np.array(loc["transform_matrix"]) for loc in meta["locations"]]
|
||||
c2w_cond = torch.from_numpy(np.stack(c2w_cond, axis=0)).float()[None].to(self.device)
|
||||
|
||||
rgb_cond = []
|
||||
new_images = []
|
||||
for pil_img in mv_imgs:
|
||||
image = np.array(pil_img)
|
||||
image = Image.fromarray(image)
|
||||
if image.width != image.height:
|
||||
max_dim = max(image.width, image.height)
|
||||
new_image = Image.new("RGBA", (max_dim, max_dim))
|
||||
left = (max_dim - image.width) // 2
|
||||
top = (max_dim - image.height) // 2
|
||||
new_image.paste(image, (left, top))
|
||||
image = new_image
|
||||
|
||||
image = image.convert('RGB')
|
||||
rgb = image.resize((self.cfg.data.cond_width, self.cfg.data.cond_height), Image.Resampling.BILINEAR)
|
||||
rgb = np.array(rgb).astype(np.float32) / 255.0
|
||||
new_images.append(image)
|
||||
rgb_cond.append(rgb)
|
||||
assert len(rgb_cond) == 4, "Please provide 4 images"
|
||||
|
||||
rgb_cond = torch.from_numpy(np.stack(rgb_cond, axis=0)).float()[None].to(self.device)
|
||||
|
||||
scene_code = self.system({"rgb_cond": rgb_cond, "c2w_cond": c2w_cond})[0]
|
||||
mesh = self.system.get_geometry(scene_code)
|
||||
|
||||
# Flip mesh horizontally
|
||||
#mesh.v_pos[:, 0] = -mesh.v_pos[:, 0]
|
||||
|
||||
return mesh.v_pos.detach(), mesh.t_pos_idx.detach()
|
||||
@@ -25,26 +25,28 @@ This is an extensive node suite that enables ComfyUI to process 3D inputs (Mesh
|
||||
- **Note:** at this moment, you'll still need to install [Visual Studio Build Tools for windows](_Pre_Builds/README.md#build-for-windows) and [install `gcc g++` for Linux](_Pre_Builds/README.md#build-for-linux) in order for `InstantNGP & Convert 3DGS to Mesh with NeRF and Marching_Cubes` nodes to work, since those two nodes used JIT torch cpp extension that builds in runtime, but I plan to replace those nodes soon
|
||||
|
||||
## Features:
|
||||
- For use cases please check out [Example Workflows](./_Example_Workflows/). [**Last update: 04/July/2024**]
|
||||
- For use cases please check out [Example Workflows](./_Example_Workflows/). [**Last update: 09/July/2024**]
|
||||
- **Note:** you need to put [Example Inputs Files & Folders](_Example_Workflows/_Example_Inputs_Files/) under ComfyUI Root Directory\ComfyUI\input folder before you can run the example workflow
|
||||
- [tripoSR-layered-diffusion workflow](https://github.com/C0nsumption/Consume-ComfyUI-Workflows/tree/main/assets/tripo_sr/00) by [@Consumption](https://twitter.com/c0nsumption_)
|
||||
|
||||
- **CharacterGen**: [zjp-shadow/CharacterGen](https://github.com/zjp-shadow/CharacterGen)
|
||||
- Single front view of a character with arbitrary pose
|
||||
- Can [combine with Unique3D workflow](./_Example_Workflows/CharacterGen/CharacterGen_to_Unique3D.json) for better result
|
||||
|
||||
- **Unique3D**: [AiuniAI/Unique3D](https://github.com/AiuniAI/Unique3D)
|
||||
- Four stages pipeline:
|
||||
1. Single image to 4 multi-view images with resulution: 256X256
|
||||
2. Consistent Multi-view images Upscale to 512X512, super resolution to 2048X2048
|
||||
3. Multi-view images to Normal maps with resulution: 512X512, super resolution to 2048X2048
|
||||
4. Multi-view images & Normal maps to 3D mesh with texture
|
||||
- To use the [pure Unique3D workflow](./_Example_Workflows/Unique3D/Unique3D_All_Stages.json), Download Models:
|
||||
- [img2mvimg](https://huggingface.co/spaces/Wuvin/Unique3D/tree/main/ckpt/img2mvimg) and put it into [./checkpoints/Wuvin/Unique3D/image2mvimage](./checkpoints/Wuvin/Unique3D/image2mvimage)
|
||||
- [image2normal](https://huggingface.co/spaces/Wuvin/Unique3D/tree/main/ckpt/image2normal) and put it into [./checkpoints/Wuvin/Unique3D/image2normal](./checkpoints/Wuvin/Unique3D/image2normal)
|
||||
- To use the [All stage Unique3D workflow](./_Example_Workflows/Unique3D/Unique3D_All_Stages.json), Download Models:
|
||||
- [fine-tuned controlnet-tile](https://huggingface.co/spaces/Wuvin/Unique3D/tree/main/ckpt/controlnet-tile) and put it into `Your ComfyUI root directory/ComfyUI/models/controlnet`
|
||||
- [ip-adapter_sd15](https://huggingface.co/h94/IP-Adapter/blob/main/models/ip-adapter_sd15.safetensors) and put it into `Your ComfyUI root directory/ComfyUI/models/ipadapter`
|
||||
- [RealESRGAN_x4plus](https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth) and put it into `Your ComfyUI root directory/ComfyUI/models/upscale_models`
|
||||
|
||||
<video controls autoplay loop src="https://github.com/MrForExample/ComfyUI-3D-Pack/assets/62230687/45dd6bfc-4f2b-4b1f-baed-13a1b0722896"></video>
|
||||
|
||||
- **Era3D Diffusion Model**: [pengHTYX/Era3D](https://github.com/pengHTYX/Era3D)
|
||||
- **Era3D MVDiffusion Model**: [pengHTYX/Era3D](https://github.com/pengHTYX/Era3D)
|
||||
- Single image to 6 multi-view images & normal maps with resulution: 512X512
|
||||
- *Note: you need at least 16GB vram to run this model*
|
||||
|
||||
@@ -155,7 +157,7 @@ This is an extensive node suite that enables ComfyUI to process 3D inputs (Mesh
|
||||
- Custom clients IP address
|
||||
|
||||
## Roadmap:
|
||||
- [ ] Integrate [CharacterGen](https://github.com/zjp-shadow/CharacterGen)
|
||||
- [X] Integrate [CharacterGen](https://github.com/zjp-shadow/CharacterGen)
|
||||
|
||||
- [ ] Improve 3DGS/Nerf to Mesh conversion algorithms:
|
||||
- Find better methods to converts 3DGS or Points Cloud to Mesh (Normal maps reconstruction maybe?)
|
||||
|
||||
@@ -1,40 +1,7 @@
|
||||
{
|
||||
"last_node_id": 30,
|
||||
"last_link_id": 46,
|
||||
"last_node_id": 32,
|
||||
"last_link_id": 48,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 2,
|
||||
"type": "[Comfy3D] Load CRM MVDiffusion Model",
|
||||
"pos": [
|
||||
-102,
|
||||
131
|
||||
],
|
||||
"size": {
|
||||
"0": 489.21466064453125,
|
||||
"1": 95.50737762451172
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "crm_mvdiffusion_sampler",
|
||||
"type": "CRM_MVDIFFUSION_SAMPLER",
|
||||
"links": [
|
||||
1
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Load CRM MVDiffusion Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
"pixel-diffusion.pth",
|
||||
"configs/crm_configs/sd_v2_base_ipmv_zero_SNR.yaml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "[Comfy3D] Save 3D Mesh",
|
||||
@@ -47,7 +14,7 @@
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -86,7 +53,7 @@
|
||||
"1": 130
|
||||
},
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -116,69 +83,6 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "[Comfy3D] CRM Images MVDiffusion Model",
|
||||
"pos": [
|
||||
600,
|
||||
300
|
||||
],
|
||||
"size": {
|
||||
"0": 506.4000244140625,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "crm_mvdiffusion_sampler",
|
||||
"type": "CRM_MVDIFFUSION_SAMPLER",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "reference_image",
|
||||
"type": "IMAGE",
|
||||
"link": 2
|
||||
},
|
||||
{
|
||||
"name": "reference_mask",
|
||||
"type": "MASK",
|
||||
"link": 28
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "multiview_images",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
39
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "orbit_camposes",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"links": [
|
||||
41
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] CRM Images MVDiffusion Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
"3D assets",
|
||||
"uniform low no texture ugly, boring, bad anatomy, blurry, pixelated, obscure, unnatural colors, poor lighting, dull, and unclear.",
|
||||
1234,
|
||||
"fixed",
|
||||
5.5,
|
||||
50
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"type": "[Comfy3D] Preview 3DMesh",
|
||||
@@ -191,7 +95,7 @@
|
||||
500
|
||||
],
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -211,45 +115,6 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"type": "GetImagesFromBatchIndexed",
|
||||
"pos": [
|
||||
1141,
|
||||
214
|
||||
],
|
||||
"size": {
|
||||
"0": 255.99082946777344,
|
||||
"1": 76.0000228881836
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 39
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
44
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "GetImagesFromBatchIndexed"
|
||||
},
|
||||
"widgets_values": [
|
||||
"5, 3, 2, 0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "[Comfy3D] Load InstantMesh Reconstruction Model",
|
||||
@@ -262,7 +127,7 @@
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
@@ -294,7 +159,7 @@
|
||||
"1": 76
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -333,7 +198,7 @@
|
||||
"1": 250
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -381,7 +246,7 @@
|
||||
"1": 122
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -467,7 +332,7 @@
|
||||
"1": 545.7767944335938
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
@@ -497,19 +362,180 @@
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "[Comfy3D] CRM Images MVDiffusion Model",
|
||||
"pos": [
|
||||
600,
|
||||
300
|
||||
],
|
||||
"size": {
|
||||
"0": 506.4000244140625,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "crm_mvdiffusion_sampler",
|
||||
"type": "CRM_MVDIFFUSION_SAMPLER",
|
||||
"link": 48
|
||||
},
|
||||
{
|
||||
"name": "reference_image",
|
||||
"type": "IMAGE",
|
||||
"link": 2
|
||||
},
|
||||
{
|
||||
"name": "reference_mask",
|
||||
"type": "MASK",
|
||||
"link": 28
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "multiview_images",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
39,
|
||||
47
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "orbit_camposes",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"links": [
|
||||
41
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] CRM Images MVDiffusion Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
"3D assets",
|
||||
"uniform low no texture ugly, boring, bad anatomy, blurry, pixelated, obscure, unnatural colors, poor lighting, dull, and unclear.",
|
||||
1234,
|
||||
"fixed",
|
||||
5.5,
|
||||
50
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"type": "GetImagesFromBatchIndexed",
|
||||
"pos": [
|
||||
1152,
|
||||
214
|
||||
],
|
||||
"size": {
|
||||
"0": 255.99082946777344,
|
||||
"1": 76.0000228881836
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 39
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
44
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "GetImagesFromBatchIndexed"
|
||||
},
|
||||
"widgets_values": [
|
||||
"5, 3, 2, 0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"type": "[Comfy3D] Load CRM MVDiffusion Model",
|
||||
"pos": [
|
||||
-157,
|
||||
163
|
||||
],
|
||||
"size": [
|
||||
435.4573393577026,
|
||||
83.6087870826683
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "crm_mvdiffusion_sampler",
|
||||
"type": "CRM_MVDIFFUSION_SAMPLER",
|
||||
"links": [
|
||||
48
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Load CRM MVDiffusion Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
"pixel-diffusion.pth",
|
||||
"sd_v2_base_ipmv_zero_SNR.yaml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1103,
|
||||
-110
|
||||
],
|
||||
"size": [
|
||||
685.57771038656,
|
||||
142.33505712129315
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 47
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1805,
|
||||
494
|
||||
1820,
|
||||
35
|
||||
],
|
||||
"size": [
|
||||
675.7496017470316,
|
||||
189.98764924222667
|
||||
],
|
||||
"size": {
|
||||
"0": 770.9938354492188,
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -524,14 +550,6 @@
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"CRM_MVDIFFUSION_SAMPLER"
|
||||
],
|
||||
[
|
||||
2,
|
||||
1,
|
||||
@@ -635,16 +653,32 @@
|
||||
23,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
47,
|
||||
3,
|
||||
0,
|
||||
31,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
48,
|
||||
32,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"CRM_MVDIFFUSION_SAMPLER"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.5644739300537773,
|
||||
"scale": 0.42409761837248683,
|
||||
"offset": [
|
||||
148.6926373068756,
|
||||
262.952479355259
|
||||
-1895.6373310621698,
|
||||
224.7040148153894
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,942 @@
|
||||
{
|
||||
"last_node_id": 43,
|
||||
"last_link_id": 56,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 18,
|
||||
"type": "[Comfy3D] Switch Mesh Axis",
|
||||
"pos": [
|
||||
1680,
|
||||
520
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 154
|
||||
},
|
||||
"flags": {},
|
||||
"order": 16,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"link": 21
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "switched_mesh",
|
||||
"type": "MESH",
|
||||
"links": [
|
||||
19
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Switch Mesh Axis"
|
||||
},
|
||||
"widgets_values": [
|
||||
"+y",
|
||||
"+z",
|
||||
"+x",
|
||||
false,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "[Comfy3D] Load InstantMesh Reconstruction Model",
|
||||
"pos": [
|
||||
930,
|
||||
300
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "lrm_model",
|
||||
"type": "LRM_MODEL",
|
||||
"links": [
|
||||
18
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Load InstantMesh Reconstruction Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
"instant_mesh_large.ckpt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"type": "IMG_padder",
|
||||
"pos": [
|
||||
210,
|
||||
440
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 82
|
||||
},
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 43
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
44
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "IMG_padder"
|
||||
},
|
||||
"widgets_values": [
|
||||
0.25,
|
||||
"left"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"type": "[Comfy3D] Load CharacterGen MVDiffusion Model",
|
||||
"pos": [
|
||||
-1900,
|
||||
360
|
||||
],
|
||||
"size": {
|
||||
"0": 294,
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "character_mv_gen_pipe",
|
||||
"type": "CHARACTER_MV_GEN_PIPE",
|
||||
"links": [
|
||||
46
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Load CharacterGen MVDiffusion Model"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"type": "InvertMask",
|
||||
"pos": [
|
||||
-1820,
|
||||
530
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 45
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
48
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "InvertMask"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"type": "Image Rembg (Remove Background)",
|
||||
"pos": [
|
||||
-750,
|
||||
510
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 250
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 50
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
51,
|
||||
53
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "Image Rembg (Remove Background)"
|
||||
},
|
||||
"widgets_values": [
|
||||
false,
|
||||
"isnet-anime",
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
240,
|
||||
10,
|
||||
10,
|
||||
"white"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"type": "ImageToMask",
|
||||
"pos": [
|
||||
-400,
|
||||
500
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 51
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
42
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ImageToMask"
|
||||
},
|
||||
"widgets_values": [
|
||||
"red"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"type": "[Comfy3D] Image Add Pure Color Background",
|
||||
"pos": [
|
||||
-160,
|
||||
440
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 126
|
||||
},
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 41
|
||||
},
|
||||
{
|
||||
"name": "masks",
|
||||
"type": "MASK",
|
||||
"link": 42
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
43
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Image Add Pure Color Background"
|
||||
},
|
||||
"widgets_values": [
|
||||
255,
|
||||
255,
|
||||
255
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"type": "IMG_padder",
|
||||
"pos": [
|
||||
570,
|
||||
440
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 82
|
||||
},
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 44
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
40
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "IMG_padder"
|
||||
},
|
||||
"widgets_values": [
|
||||
0.2,
|
||||
"right"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"type": "ImageScale",
|
||||
"pos": [
|
||||
940,
|
||||
440
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 130
|
||||
},
|
||||
"flags": {},
|
||||
"order": 14,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 40
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
54
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ImageScale"
|
||||
},
|
||||
"widgets_values": [
|
||||
"lanczos",
|
||||
1024,
|
||||
1024,
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"type": "GetImagesFromBatchIndexed",
|
||||
"pos": [
|
||||
-1060,
|
||||
440
|
||||
],
|
||||
"size": {
|
||||
"0": 255.99082946777344,
|
||||
"1": 76.0000228881836
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 49
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
41,
|
||||
50,
|
||||
52
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "GetImagesFromBatchIndexed"
|
||||
},
|
||||
"widgets_values": [
|
||||
"3, 1, 2, 0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"type": "[Comfy3D] CharacterGen MVDiffusion Model",
|
||||
"pos": [
|
||||
-1570,
|
||||
440
|
||||
],
|
||||
"size": {
|
||||
"0": 456,
|
||||
"1": 334
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "character_mv_gen_pipe",
|
||||
"type": "CHARACTER_MV_GEN_PIPE",
|
||||
"link": 46
|
||||
},
|
||||
{
|
||||
"name": "reference_image",
|
||||
"type": "IMAGE",
|
||||
"link": 47
|
||||
},
|
||||
{
|
||||
"name": "reference_mask",
|
||||
"type": "MASK",
|
||||
"link": 48
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "multiviews",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
49
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "orbit_camposes",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"links": [
|
||||
55
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] CharacterGen MVDiffusion Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
768,
|
||||
2333,
|
||||
"fixed",
|
||||
5,
|
||||
40,
|
||||
"high quality, best quality",
|
||||
"",
|
||||
1.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
-1273,
|
||||
109
|
||||
],
|
||||
"size": [
|
||||
718.229248046875,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 52
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
-457,
|
||||
104
|
||||
],
|
||||
"size": [
|
||||
608.1014404296875,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 53
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "[Comfy3D] InstantMesh Reconstruction Model",
|
||||
"pos": [
|
||||
1330,
|
||||
520
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 122
|
||||
},
|
||||
"flags": {},
|
||||
"order": 15,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "lrm_model",
|
||||
"type": "LRM_MODEL",
|
||||
"link": 18
|
||||
},
|
||||
{
|
||||
"name": "multiview_images",
|
||||
"type": "IMAGE",
|
||||
"link": 54
|
||||
},
|
||||
{
|
||||
"name": "orbit_camera_poses",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"link": 56
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"links": [
|
||||
21
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] InstantMesh Reconstruction Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
40,
|
||||
1024
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"type": "[Comfy3D] Get Camposes From List Indexed",
|
||||
"pos": [
|
||||
-988,
|
||||
821
|
||||
],
|
||||
"size": {
|
||||
"0": 531.5999755859375,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "original_orbit_camera_poses",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"link": 55
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "ORBIT_CAMPOSES",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"links": [
|
||||
56
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Get Camposes From List Indexed"
|
||||
},
|
||||
"widgets_values": [
|
||||
"3, 1, 2, 0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
-2230,
|
||||
460
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 314
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
47
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
45
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"CharacterGen_Test_2.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "[Comfy3D] Save 3D Mesh",
|
||||
"pos": [
|
||||
2021,
|
||||
520
|
||||
],
|
||||
"size": [
|
||||
288.29027143395615,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 17,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"link": 19
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "save_path",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
20
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Save 3D Mesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"InstantMesh_test\\Mesh_Girl.glb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"type": "[Comfy3D] Preview 3DMesh",
|
||||
"pos": [
|
||||
2344,
|
||||
519
|
||||
],
|
||||
"size": [
|
||||
600,
|
||||
500
|
||||
],
|
||||
"flags": {},
|
||||
"order": 18,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mesh_file_path",
|
||||
"type": "STRING",
|
||||
"link": 20,
|
||||
"widget": {
|
||||
"name": "mesh_file_path"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Preview 3DMesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
18,
|
||||
14,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"LRM_MODEL"
|
||||
],
|
||||
[
|
||||
19,
|
||||
18,
|
||||
0,
|
||||
16,
|
||||
0,
|
||||
"MESH"
|
||||
],
|
||||
[
|
||||
20,
|
||||
16,
|
||||
0,
|
||||
17,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
21,
|
||||
15,
|
||||
0,
|
||||
18,
|
||||
0,
|
||||
"MESH"
|
||||
],
|
||||
[
|
||||
40,
|
||||
33,
|
||||
0,
|
||||
30,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
41,
|
||||
38,
|
||||
0,
|
||||
31,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
42,
|
||||
40,
|
||||
0,
|
||||
31,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
43,
|
||||
31,
|
||||
0,
|
||||
32,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
44,
|
||||
32,
|
||||
0,
|
||||
33,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
45,
|
||||
35,
|
||||
1,
|
||||
36,
|
||||
0,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
46,
|
||||
34,
|
||||
0,
|
||||
37,
|
||||
0,
|
||||
"CHARACTER_MV_GEN_PIPE"
|
||||
],
|
||||
[
|
||||
47,
|
||||
35,
|
||||
0,
|
||||
37,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
48,
|
||||
36,
|
||||
0,
|
||||
37,
|
||||
2,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
49,
|
||||
37,
|
||||
0,
|
||||
38,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
50,
|
||||
38,
|
||||
0,
|
||||
39,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
51,
|
||||
39,
|
||||
0,
|
||||
40,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
52,
|
||||
38,
|
||||
0,
|
||||
41,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
53,
|
||||
39,
|
||||
0,
|
||||
42,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
54,
|
||||
30,
|
||||
0,
|
||||
15,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
55,
|
||||
37,
|
||||
1,
|
||||
43,
|
||||
0,
|
||||
"ORBIT_CAMPOSES"
|
||||
],
|
||||
[
|
||||
56,
|
||||
43,
|
||||
0,
|
||||
15,
|
||||
2,
|
||||
"ORBIT_CAMPOSES"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
-1287.2902103988,
|
||||
-400.040713409522
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,680 @@
|
||||
{
|
||||
"last_node_id": 32,
|
||||
"last_link_id": 45,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 6,
|
||||
"type": "InvertMask",
|
||||
"pos": [
|
||||
-130,
|
||||
510
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 13
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
16
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "InvertMask"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "[Comfy3D] Load CharacterGen MVDiffusion Model",
|
||||
"pos": [
|
||||
-210,
|
||||
340
|
||||
],
|
||||
"size": {
|
||||
"0": 294,
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "character_mv_gen_pipe",
|
||||
"type": "CHARACTER_MV_GEN_PIPE",
|
||||
"links": [
|
||||
14
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Load CharacterGen MVDiffusion Model"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "[Comfy3D] Load CharacterGen Reconstruction Model",
|
||||
"pos": [
|
||||
923,
|
||||
351
|
||||
],
|
||||
"size": {
|
||||
"0": 319.20001220703125,
|
||||
"1": 28.166826248168945
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "character_lrm_pipe",
|
||||
"type": "CHARACTER_LRM_PIPE",
|
||||
"links": [
|
||||
34
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Load CharacterGen Reconstruction Model"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"type": "[Comfy3D] CharacterGen Reconstruction Model",
|
||||
"pos": [
|
||||
1621,
|
||||
400
|
||||
],
|
||||
"size": {
|
||||
"0": 292.20001220703125,
|
||||
"1": 66
|
||||
},
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "character_lrm_pipe",
|
||||
"type": "CHARACTER_LRM_PIPE",
|
||||
"link": 34
|
||||
},
|
||||
{
|
||||
"name": "multiview_images",
|
||||
"type": "IMAGE",
|
||||
"link": 38
|
||||
},
|
||||
{
|
||||
"name": "multiview_masks",
|
||||
"type": "MASK",
|
||||
"link": 37
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"links": [
|
||||
43
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] CharacterGen Reconstruction Model"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"type": "[Comfy3D] Switch Mesh Axis",
|
||||
"pos": [
|
||||
1956,
|
||||
400
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 154
|
||||
},
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"link": 43
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "switched_mesh",
|
||||
"type": "MESH",
|
||||
"links": [
|
||||
44
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Switch Mesh Axis"
|
||||
},
|
||||
"widgets_values": [
|
||||
"+x",
|
||||
"-z",
|
||||
"+y",
|
||||
false,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"type": "[Comfy3D] Save 3D Mesh",
|
||||
"pos": [
|
||||
2320,
|
||||
400
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"link": 44
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "save_path",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
41
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Save 3D Mesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"CharacterGen\\Mesh\\Mesh_Girl.glb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "[Comfy3D] CharacterGen MVDiffusion Model",
|
||||
"pos": [
|
||||
120,
|
||||
420
|
||||
],
|
||||
"size": {
|
||||
"0": 456,
|
||||
"1": 334
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "character_mv_gen_pipe",
|
||||
"type": "CHARACTER_MV_GEN_PIPE",
|
||||
"link": 14
|
||||
},
|
||||
{
|
||||
"name": "reference_image",
|
||||
"type": "IMAGE",
|
||||
"link": 15
|
||||
},
|
||||
{
|
||||
"name": "reference_mask",
|
||||
"type": "MASK",
|
||||
"link": 16
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "multiviews",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
26
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "orbit_camposes",
|
||||
"type": "ORBIT_CAMPOSES",
|
||||
"links": [],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] CharacterGen MVDiffusion Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
768,
|
||||
2333,
|
||||
"fixed",
|
||||
5,
|
||||
40,
|
||||
"high quality, best quality",
|
||||
"",
|
||||
1.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "Image Rembg (Remove Background)",
|
||||
"pos": [
|
||||
940,
|
||||
483
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 250
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 24
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
36,
|
||||
42
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "Image Rembg (Remove Background)"
|
||||
},
|
||||
"widgets_values": [
|
||||
false,
|
||||
"isnet-anime",
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
240,
|
||||
10,
|
||||
10,
|
||||
"white"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "GetImagesFromBatchIndexed",
|
||||
"pos": [
|
||||
619,
|
||||
420
|
||||
],
|
||||
"size": {
|
||||
"0": 255.99082946777344,
|
||||
"1": 76.0000228881836
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 26
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
24,
|
||||
38,
|
||||
45
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "GetImagesFromBatchIndexed"
|
||||
},
|
||||
"widgets_values": [
|
||||
"2, 3, 0, 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "ImageToMask",
|
||||
"pos": [
|
||||
1290,
|
||||
486
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 36
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
37
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ImageToMask"
|
||||
},
|
||||
"widgets_values": [
|
||||
"red"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "SaveImage",
|
||||
"pos": [
|
||||
930,
|
||||
820
|
||||
],
|
||||
"size": {
|
||||
"0": 526.6419677734375,
|
||||
"1": 270
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 45
|
||||
}
|
||||
],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"CharacterGen\\Multi_Views\\image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"type": "SaveImage",
|
||||
"pos": [
|
||||
1507,
|
||||
825
|
||||
],
|
||||
"size": {
|
||||
"0": 526.6419677734375,
|
||||
"1": 270
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 42
|
||||
}
|
||||
],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"CharacterGen\\Multi_View_Masks\\image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"type": "[Comfy3D] Preview 3DMesh",
|
||||
"pos": [
|
||||
2663,
|
||||
398
|
||||
],
|
||||
"size": [
|
||||
600,
|
||||
500
|
||||
],
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mesh_file_path",
|
||||
"type": "STRING",
|
||||
"link": 41,
|
||||
"widget": {
|
||||
"name": "mesh_file_path"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Preview 3DMesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
-540,
|
||||
440
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 314
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
15
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
13
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Test_ChunLi 768X512.png",
|
||||
"image"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
13,
|
||||
3,
|
||||
1,
|
||||
6,
|
||||
0,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
14,
|
||||
1,
|
||||
0,
|
||||
13,
|
||||
0,
|
||||
"CHARACTER_MV_GEN_PIPE"
|
||||
],
|
||||
[
|
||||
15,
|
||||
3,
|
||||
0,
|
||||
13,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
16,
|
||||
6,
|
||||
0,
|
||||
13,
|
||||
2,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
24,
|
||||
19,
|
||||
0,
|
||||
21,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
26,
|
||||
13,
|
||||
0,
|
||||
19,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
34,
|
||||
24,
|
||||
0,
|
||||
25,
|
||||
0,
|
||||
"CHARACTER_LRM_PIPE"
|
||||
],
|
||||
[
|
||||
36,
|
||||
21,
|
||||
0,
|
||||
26,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
37,
|
||||
26,
|
||||
0,
|
||||
25,
|
||||
2,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
38,
|
||||
19,
|
||||
0,
|
||||
25,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
41,
|
||||
28,
|
||||
0,
|
||||
29,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
42,
|
||||
21,
|
||||
0,
|
||||
30,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
43,
|
||||
25,
|
||||
0,
|
||||
32,
|
||||
0,
|
||||
"MESH"
|
||||
],
|
||||
[
|
||||
44,
|
||||
32,
|
||||
0,
|
||||
28,
|
||||
0,
|
||||
"MESH"
|
||||
],
|
||||
[
|
||||
45,
|
||||
19,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.7513148009015781,
|
||||
"offset": [
|
||||
621.9534614969469,
|
||||
-192.59110955183658
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -128,7 +128,7 @@
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "[Comfy3D] Era3D Diffusion Model",
|
||||
"type": "[Comfy3D] Era3D MVDiffusion Model",
|
||||
"pos": [
|
||||
1118,
|
||||
232
|
||||
@@ -188,7 +188,7 @@
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Era3D Diffusion Model"
|
||||
"Node name for S&R": "[Comfy3D] Era3D MVDiffusion Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
420,
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"type": "[Comfy3D] Unique3D Diffusion Model",
|
||||
"type": "[Comfy3D] Unique3D MVDiffusion Model",
|
||||
"pos": [
|
||||
1374,
|
||||
159
|
||||
@@ -124,7 +124,7 @@
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Unique3D Diffusion Model"
|
||||
"Node name for S&R": "[Comfy3D] Unique3D MVDiffusion Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
1145,
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "[Comfy3D] Unique3D Diffusion Model",
|
||||
"type": "[Comfy3D] Unique3D MVDiffusion Model",
|
||||
"pos": [
|
||||
1397,
|
||||
254
|
||||
@@ -262,7 +262,7 @@
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Unique3D Diffusion Model"
|
||||
"Node name for S&R": "[Comfy3D] Unique3D MVDiffusion Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
1145,
|
||||
|
||||
@@ -1516,7 +1516,7 @@
|
||||
},
|
||||
{
|
||||
"id": 317,
|
||||
"type": "[Comfy3D] Unique3D Diffusion Model",
|
||||
"type": "[Comfy3D] Unique3D MVDiffusion Model",
|
||||
"pos": [
|
||||
726.0260388541542,
|
||||
742.5761190469728
|
||||
@@ -1558,7 +1558,7 @@
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Unique3D Diffusion Model"
|
||||
"Node name for S&R": "[Comfy3D] Unique3D MVDiffusion Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
1145,
|
||||
@@ -1607,7 +1607,7 @@
|
||||
},
|
||||
{
|
||||
"id": 307,
|
||||
"type": "[Comfy3D] Unique3D Diffusion Model",
|
||||
"type": "[Comfy3D] Unique3D MVDiffusion Model",
|
||||
"pos": [
|
||||
-67,
|
||||
-1160
|
||||
@@ -1650,7 +1650,7 @@
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Unique3D Diffusion Model"
|
||||
"Node name for S&R": "[Comfy3D] Unique3D MVDiffusion Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
1145,
|
||||
|
||||
@@ -240,7 +240,7 @@
|
||||
},
|
||||
{
|
||||
"id": 336,
|
||||
"type": "[Comfy3D] Era3D Diffusion Model",
|
||||
"type": "[Comfy3D] Era3D MVDiffusion Model",
|
||||
"pos": [
|
||||
-643.2975797406153,
|
||||
-610.8013992433897
|
||||
@@ -297,7 +297,7 @@
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Era3D Diffusion Model"
|
||||
"Node name for S&R": "[Comfy3D] Era3D MVDiffusion Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
420,
|
||||
|
||||
|
After Width: | Height: | Size: 298 KiB |
|
After Width: | Height: | Size: 233 KiB |
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"_class_name": "ControlNetModel",
|
||||
"_diffusers_version": "0.27.2",
|
||||
"_name_or_path": "lllyasviel/control_v11f1e_sd15_tile",
|
||||
"act_fn": "silu",
|
||||
"addition_embed_type": null,
|
||||
"addition_embed_type_num_heads": 64,
|
||||
"addition_time_embed_dim": null,
|
||||
"attention_head_dim": 8,
|
||||
"block_out_channels": [
|
||||
320,
|
||||
640,
|
||||
1280,
|
||||
1280
|
||||
],
|
||||
"class_embed_type": null,
|
||||
"conditioning_channels": 3,
|
||||
"conditioning_embedding_out_channels": [
|
||||
16,
|
||||
32,
|
||||
96,
|
||||
256
|
||||
],
|
||||
"controlnet_conditioning_channel_order": "rgb",
|
||||
"cross_attention_dim": 768,
|
||||
"down_block_types": [
|
||||
"CrossAttnDownBlock2D",
|
||||
"CrossAttnDownBlock2D",
|
||||
"CrossAttnDownBlock2D",
|
||||
"DownBlock2D"
|
||||
],
|
||||
"downsample_padding": 1,
|
||||
"encoder_hid_dim": null,
|
||||
"encoder_hid_dim_type": null,
|
||||
"flip_sin_to_cos": true,
|
||||
"freq_shift": 0,
|
||||
"global_pool_conditions": false,
|
||||
"in_channels": 4,
|
||||
"layers_per_block": 2,
|
||||
"mid_block_scale_factor": 1,
|
||||
"mid_block_type": "UNetMidBlock2DCrossAttn",
|
||||
"norm_eps": 1e-05,
|
||||
"norm_num_groups": 32,
|
||||
"num_attention_heads": null,
|
||||
"num_class_embeds": null,
|
||||
"only_cross_attention": false,
|
||||
"projection_class_embeddings_input_dim": null,
|
||||
"resnet_time_scale_shift": "default",
|
||||
"transformer_layers_per_block": 1,
|
||||
"upcast_attention": false,
|
||||
"use_linear_projection": false
|
||||
}
|
||||
@@ -12,7 +12,6 @@ from .render import NormalsRenderer, Pytorch3DNormalsRenderer
|
||||
from Unique3D.scripts.utils import to_py3d_mesh, init_target
|
||||
|
||||
def reconstruct_stage1(pils: List[Image.Image], steps=100, vertices=None, faces=None, start_edge_len=0.1, end_edge_len=0.02, decay=0.995, return_mesh=False, loss_expansion_weight=0.1, gain=0.05):
|
||||
vertices, faces = vertices.to("cuda"), faces.to("cuda")
|
||||
assert len(pils) == 4
|
||||
mv,proj = make_star_cameras_orthographic(4, 1)
|
||||
renderer = NormalsRenderer(mv,proj,list(pils[0].size))
|
||||
@@ -24,7 +23,7 @@ def reconstruct_stage1(pils: List[Image.Image], steps=100, vertices=None, faces=
|
||||
target_images = target_images[[0, 3, 2, 1]]
|
||||
|
||||
# 2. init from coarse mesh
|
||||
opt = MeshOptimizer(vertices,faces, local_edgelen=False, gain=gain, edge_len_lims=(end_edge_len, start_edge_len))
|
||||
opt = MeshOptimizer(vertices, faces, local_edgelen=False, gain=gain, edge_len_lims=(end_edge_len, start_edge_len))
|
||||
|
||||
vertices = opt.vertices
|
||||
|
||||
|
||||
@@ -95,7 +95,6 @@ def calc_vertex_normals(
|
||||
faces:torch.Tensor, #F,3 long, first face may be all zero
|
||||
face_normals:torch.Tensor=None, #F,3, not normalized
|
||||
)->torch.Tensor: #F,3
|
||||
|
||||
F = faces.shape[0]
|
||||
|
||||
if face_normals is None:
|
||||
|
||||
@@ -678,7 +678,7 @@ class Mesh:
|
||||
)
|
||||
|
||||
# append texture info
|
||||
if self.vt is not None:
|
||||
if self.vt is not None and self.albedo is not None:
|
||||
|
||||
vt_np = self.vt.detach().cpu().numpy().astype(np.float32)
|
||||
vt_np_blob = vt_np.tobytes()
|
||||
|
||||
@@ -29,6 +29,7 @@ from diffusers import (
|
||||
KDPM2AncestralDiscreteScheduler,
|
||||
KDPM2DiscreteScheduler,
|
||||
)
|
||||
from huggingface_hub import snapshot_download
|
||||
|
||||
from plyfile import PlyData
|
||||
from PIL import Image
|
||||
@@ -77,6 +78,8 @@ from Unique3D.scripts.utils import from_py3d_mesh, to_py3d_mesh, to_pyml_mesh, s
|
||||
from Unique3D.scripts.project_mesh import multiview_color_projection, get_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
|
||||
from CharacterGen.Stage_3D.lrm.utils.config import load_config as load_config_cg3d
|
||||
|
||||
from .shared_utils.image_utils import (
|
||||
prepare_torch_img, torch_imgs_to_pils, troch_image_dilate,
|
||||
@@ -588,6 +591,7 @@ class Switch_Mesh_Axis:
|
||||
"axis_y_to": (["+y", "-y", "+z", "-z", "+x", "-x"],),
|
||||
"axis_z_to": (["+z", "-z", "+x", "-x", "+y", "-y"],),
|
||||
"flip_normal": ("BOOLEAN", {"default": False},),
|
||||
"scale": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 100, "step": 0.01}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -600,12 +604,12 @@ class Switch_Mesh_Axis:
|
||||
FUNCTION = "switch_axis_and_scale"
|
||||
CATEGORY = "Comfy3D/Preprocessor"
|
||||
|
||||
def switch_axis_and_scale(self, mesh, axis_x_to, axis_y_to, axis_z_to, flip_normal):
|
||||
def switch_axis_and_scale(self, mesh, axis_x_to, axis_y_to, axis_z_to, flip_normal, scale):
|
||||
|
||||
switched_mesh = None
|
||||
|
||||
if axis_x_to[1] != axis_y_to[1] and axis_x_to[1] != axis_z_to[1] and axis_y_to[1] != axis_z_to[1]:
|
||||
target_axis, target_scale, coordinate_invert_count = get_target_axis_and_scale([axis_x_to, axis_y_to, axis_z_to])
|
||||
target_axis, target_scale, coordinate_invert_count = get_target_axis_and_scale([axis_x_to, axis_y_to, axis_z_to], scale)
|
||||
switched_mesh = switch_mesh_axis_and_scale(mesh, target_axis, target_scale, flip_normal)
|
||||
else:
|
||||
cstr(f"[{self.__class__.__name__}] axis_x_to: {axis_x_to}, axis_y_to: {axis_y_to}, axis_z_to: {axis_z_to} have to be on separated axis").error.print()
|
||||
@@ -1292,12 +1296,12 @@ class Load_Triplane_Gaussian_Transformers:
|
||||
checkpoints_dir = "TriplaneGaussian"
|
||||
default_ckpt_name = "model_lvis_rel.ckpt"
|
||||
default_repo_id = "VAST-AI/TriplaneGaussian"
|
||||
tgs_config_path = "TriplaneGaussian_config.yaml"
|
||||
config_path = "TriplaneGaussian_config.yaml"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
cls.checkpoints_dir_abs = os.path.join(CKPT_ROOT_PATH, cls.checkpoints_dir)
|
||||
cls.config_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.tgs_config_path)
|
||||
cls.config_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.config_path)
|
||||
all_models_names = get_list_filenames(cls.checkpoints_dir_abs, SUPPORTED_CHECKPOINTS_EXTENSIONS)
|
||||
if cls.default_ckpt_name not in all_models_names:
|
||||
all_models_names += [cls.default_ckpt_name]
|
||||
@@ -1334,11 +1338,11 @@ class Load_Triplane_Gaussian_Transformers:
|
||||
|
||||
class Triplane_Gaussian_Transformers:
|
||||
|
||||
tgs_config_path = "TriplaneGaussian_config.yaml"
|
||||
config_path = "TriplaneGaussian_config.yaml"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
cls.config_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.tgs_config_path)
|
||||
cls.config_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.config_path)
|
||||
return {
|
||||
"required": {
|
||||
"reference_image": ("IMAGE", ),
|
||||
@@ -1384,7 +1388,7 @@ class Load_Diffusers_Pipeline:
|
||||
return {
|
||||
"required": {
|
||||
"diffusers_pipeline_name": (list(DIFFUSERS_PIPE_DICT.keys()),),
|
||||
"model_name": ("STRING", {"default": "ashawkey/imagedream-ipmv-diffusers", "multiline": False}),
|
||||
"repo_id": ("STRING", {"default": "ashawkey/imagedream-ipmv-diffusers", "multiline": False}),
|
||||
"custom_pipeline": ("STRING", {"default": "", "multiline": False}),
|
||||
"download_from_remote": ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
@@ -1399,14 +1403,12 @@ class Load_Diffusers_Pipeline:
|
||||
FUNCTION = "load_diffusers_pipe"
|
||||
CATEGORY = "Comfy3D/Import|Export"
|
||||
|
||||
def load_diffusers_pipe(self, diffusers_pipeline_name, model_name, custom_pipeline, download_from_remote):
|
||||
def load_diffusers_pipe(self, diffusers_pipeline_name, repo_id, custom_pipeline, download_from_remote):
|
||||
|
||||
# resume pretrained checkpoint
|
||||
ckpt_path = os.path.join(CKPT_DIFFUSERS_PATH, model_name)
|
||||
|
||||
# resume download pretrained checkpoint
|
||||
ckpt_path = os.path.join(CKPT_DIFFUSERS_PATH, repo_id)
|
||||
if download_from_remote:
|
||||
from huggingface_hub import snapshot_download
|
||||
snapshot_download(repo_id=model_name, local_dir=ckpt_path, repo_type="model", ignore_patterns=["*.json", "*.py"])
|
||||
snapshot_download(repo_id=repo_id, local_dir=ckpt_path, repo_type="model", ignore_patterns=["*.json", "*.py"])
|
||||
|
||||
diffusers_pipeline_class = DIFFUSERS_PIPE_DICT[diffusers_pipeline_name]
|
||||
|
||||
@@ -1487,12 +1489,12 @@ class Set_Diffusers_Pipeline_State_Dict:
|
||||
|
||||
class Wonder3D_MVDiffusion_Model:
|
||||
|
||||
wonder3d_config_path = "Wonder3D_config.yaml"
|
||||
config_path = "Wonder3D_config.yaml"
|
||||
fix_cam_pose_dir = "Wonder3D/data/fixed_poses/nine_views"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
cls.config_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.wonder3d_config_path)
|
||||
cls.config_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.config_path)
|
||||
cls.fix_cam_pose_dir_abs = os.path.join(MODULE_ROOT_PATH, cls.fix_cam_pose_dir)
|
||||
return {
|
||||
"required": {
|
||||
@@ -1801,7 +1803,7 @@ class Load_TripoSR_Model:
|
||||
checkpoints_dir = "TripoSR"
|
||||
default_ckpt_name = "model.ckpt"
|
||||
default_repo_id = "stabilityai/TripoSR"
|
||||
tsr_config_path = "TripoSR_config.yaml"
|
||||
config_path = "TripoSR_config.yaml"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -1810,7 +1812,7 @@ class Load_TripoSR_Model:
|
||||
if cls.default_ckpt_name not in all_models_names:
|
||||
all_models_names += [cls.default_ckpt_name]
|
||||
|
||||
cls.config_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.tsr_config_path)
|
||||
cls.config_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.config_path)
|
||||
return {
|
||||
"required": {
|
||||
"model_name": (all_models_names, ),
|
||||
@@ -1899,7 +1901,7 @@ class Load_CRM_MVDiffusion_Model:
|
||||
default_ckpt_name = ["pixel-diffusion.pth", "ccm-diffusion.pth"]
|
||||
default_conf_name = ["sd_v2_base_ipmv_zero_SNR.yaml", "sd_v2_base_ipmv_chin8_zero_snr.yaml"]
|
||||
default_repo_id = "Zhengyi/CRM"
|
||||
crm_config_path = "CRM_configs"
|
||||
config_path = "CRM_configs"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -1909,7 +1911,7 @@ class Load_CRM_MVDiffusion_Model:
|
||||
if ckpt_name not in all_models_names:
|
||||
all_models_names += [ckpt_name]
|
||||
|
||||
cls.config_root_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.crm_config_path)
|
||||
cls.config_root_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.config_path)
|
||||
return {
|
||||
"required": {
|
||||
"model_name": (all_models_names, ),
|
||||
@@ -2323,12 +2325,12 @@ class InstantMesh_Reconstruction_Model:
|
||||
mesh.auto_normal()
|
||||
return (mesh,)
|
||||
|
||||
class Era3D_Diffusion_Model:
|
||||
class Era3D_MVDiffusion_Model:
|
||||
|
||||
era3d_config_path = "Era3D_config.yaml"
|
||||
config_path = "Era3D_config.yaml"
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
cls.config_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.era3d_config_path)
|
||||
cls.config_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.config_path)
|
||||
return {
|
||||
"required": {
|
||||
"era3d_pipe": ("DIFFUSERS_PIPE",),
|
||||
@@ -2574,6 +2576,7 @@ class FlexiCubes_MVS:
|
||||
|
||||
class Load_Unique3D_Custom_UNet:
|
||||
checkpoints_dir = "Wuvin/Unique3D"
|
||||
default_repo_id = "MrForExample/Unique3D"
|
||||
config_root_dir = "Unique3D_configs"
|
||||
|
||||
@classmethod
|
||||
@@ -2600,9 +2603,12 @@ class Load_Unique3D_Custom_UNet:
|
||||
from Unique3D.custum_3d_diffusion.trainings.config_classes import ExprimentConfig
|
||||
from Unique3D.custum_3d_diffusion.custum_modules.unifield_processor import AttnConfig, ConfigurableUNet2DConditionModel
|
||||
from Unique3D.custum_3d_diffusion.trainings.utils import load_config
|
||||
# Download models and configs
|
||||
ckpt_path = os.path.join(CKPT_DIFFUSERS_PATH, self.checkpoints_dir)
|
||||
snapshot_download(repo_id=self.default_repo_id, local_dir=ckpt_path, repo_type="model", ignore_patterns=["*.json", "*.py"])
|
||||
|
||||
cfg_path = os.path.join(self.config_path_abs, config_name + ".yaml")
|
||||
checkpoint_dir_path = os.path.join(CKPT_DIFFUSERS_PATH, self.checkpoints_dir, config_name)
|
||||
checkpoint_dir_path = os.path.join(ckpt_path, config_name)
|
||||
checkpoint_path = os.path.join(checkpoint_dir_path, "unet_state_dict.pth")
|
||||
|
||||
cfg: ExprimentConfig = load_config(ExprimentConfig, cfg_path)
|
||||
@@ -2620,7 +2626,7 @@ class Load_Unique3D_Custom_UNet:
|
||||
pipe.unet = configurable_unet.unet
|
||||
return (pipe, )
|
||||
|
||||
class Unique3D_Diffusion_Model:
|
||||
class Unique3D_MVDiffusion_Model:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -2730,10 +2736,10 @@ class ExplicitTarget_Mesh_Optimization:
|
||||
"mesh": ("MESH",),
|
||||
"normal_maps": ("IMAGE",),
|
||||
"normal_masks": ("MASK",),
|
||||
"reconstruction_steps": ("INT", {"default": 200, "min": 1, "max": 0xffffffffffffffff}),
|
||||
"reconstruction_steps": ("INT", {"default": 200, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"coarse_reconstruct_resolution": ("INT", {"default": 512, "min": 128, "max": 8192}),
|
||||
"loss_expansion_weight": ("FLOAT", {"default": 0.1, "min": 0.01, "step": 0.01}),
|
||||
"refinement_steps": ("INT", {"default": 100, "min": 1, "max": 0xffffffffffffffff}),
|
||||
"refinement_steps": ("INT", {"default": 100, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"target_warmup_update_num": ("INT", {"default": 5, "min": 1, "max": 0xffffffffffffffff}),
|
||||
"target_update_interval": ("INT", {"default": 20, "min": 1, "max": 0xffffffffffffffff}),
|
||||
},
|
||||
@@ -2769,9 +2775,12 @@ class ExplicitTarget_Mesh_Optimization:
|
||||
pil_normal_list = torch_imgs_to_pils(normal_maps, normal_masks)
|
||||
normal_stg1 = [img.resize((coarse_reconstruct_resolution, coarse_reconstruct_resolution)) for img in pil_normal_list]
|
||||
with torch.inference_mode(False):
|
||||
vertices, faces = reconstruct_stage1(normal_stg1, steps=reconstruction_steps, vertices=mesh.v, faces=mesh.f, loss_expansion_weight=loss_expansion_weight)
|
||||
|
||||
vertices, faces = run_mesh_refine(vertices, faces, pil_normal_list, steps=refinement_steps, update_normal_interval=target_update_interval, update_warmup=target_warmup_update_num, )
|
||||
vertices, faces = mesh.v.detach().clone().to(DEVICE), mesh.f.detach().clone().to(DEVICE).type(torch.int64)
|
||||
if reconstruction_steps > 0:
|
||||
vertices, faces = reconstruct_stage1(normal_stg1, steps=reconstruction_steps, vertices=vertices, faces=faces, loss_expansion_weight=loss_expansion_weight)
|
||||
|
||||
if refinement_steps > 0:
|
||||
vertices, faces = run_mesh_refine(vertices, faces, pil_normal_list, steps=refinement_steps, update_normal_interval=target_update_interval, update_warmup=target_warmup_update_num, )
|
||||
|
||||
mesh = Mesh(v=vertices, f=faces, device=DEVICE)
|
||||
return (mesh,)
|
||||
@@ -2833,4 +2842,165 @@ class ExplicitTarget_Color_Projection:
|
||||
|
||||
mesh = Mesh(v=vertices, f=faces, vc=vertex_colors, device=DEVICE)
|
||||
mesh.auto_normal()
|
||||
return (mesh,)
|
||||
|
||||
class Load_CharacterGen_MVDiffusion_Model:
|
||||
checkpoints_dir = "CharacterGen"
|
||||
default_repo_id = "zjpshadow/CharacterGen"
|
||||
config_path = "CharacterGen_configs/Stage_2D_infer.yaml"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
cls.checkpoints_dir_abs = os.path.join(CKPT_ROOT_PATH, cls.checkpoints_dir)
|
||||
cls.config_root_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.config_path)
|
||||
return {
|
||||
"required": {
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
"CHARACTER_MV_GEN_PIPE",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"character_mv_gen_pipe",
|
||||
)
|
||||
FUNCTION = "load_model"
|
||||
CATEGORY = "Comfy3D/Import|Export"
|
||||
|
||||
def load_model(self):
|
||||
# Download checkpoints
|
||||
snapshot_download(repo_id=self.default_repo_id, local_dir=self.checkpoints_dir_abs, repo_type="model", ignore_patterns=["*.json", "*.py"])
|
||||
# Load pre-trained models
|
||||
character_mv_gen_pipe = Inference2D_API(checkpoint_root_path=self.checkpoints_dir_abs, **OmegaConf.load(self.config_root_path_abs))
|
||||
return (character_mv_gen_pipe,)
|
||||
|
||||
class CharacterGen_MVDiffusion_Model:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"character_mv_gen_pipe": ("CHARACTER_MV_GEN_PIPE",),
|
||||
"reference_image": ("IMAGE", ),
|
||||
"reference_mask": ("MASK",),
|
||||
"target_image_width": ("INT", {"default": 512, "min": 128, "max": 8192}),
|
||||
"target_image_height": ("INT", {"default": 768, "min": 128, "max": 8192}),
|
||||
"seed": ("INT", {"default": 2333, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"guidance_scale": ("FLOAT", {"default": 5.0, "min": 0.0, "step": 0.01}),
|
||||
"num_inference_steps": ("INT", {"default": 40, "min": 1}),
|
||||
"prompt": ("STRING", {
|
||||
"default": "high quality, best quality",
|
||||
"multiline": True
|
||||
}),
|
||||
"prompt_neg": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": True
|
||||
}),
|
||||
"radius": ("FLOAT", {"default": 1.5, "min": 0.1, "step": 0.01})
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
"IMAGE",
|
||||
"ORBIT_CAMPOSES", # [orbit radius, elevation, azimuth, orbit center X, orbit center Y, orbit center Z]
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"multiviews",
|
||||
"orbit_camposes",
|
||||
)
|
||||
FUNCTION = "run_model"
|
||||
CATEGORY = "Comfy3D/Algorithm"
|
||||
|
||||
@torch.no_grad()
|
||||
def run_model(
|
||||
self,
|
||||
character_mv_gen_pipe,
|
||||
reference_image,
|
||||
reference_mask,
|
||||
target_image_width,
|
||||
target_image_height,
|
||||
seed,
|
||||
guidance_scale,
|
||||
num_inference_steps,
|
||||
prompt,
|
||||
prompt_neg,
|
||||
radius
|
||||
):
|
||||
single_image = torch_imgs_to_pils(reference_image, reference_mask)[0]
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
azimuths = [-90, 90, 180, 0]
|
||||
elevations = [0.0] * 4
|
||||
radius = [radius] * 4
|
||||
center = [0.0] * 4
|
||||
|
||||
orbit_camposes = [azimuths, elevations, radius, center, center, center]
|
||||
|
||||
return (multiview_images, orbit_camposes)
|
||||
|
||||
class Load_CharacterGen_Reconstruction_Model:
|
||||
checkpoints_dir = "CharacterGen"
|
||||
default_repo_id = "zjpshadow/CharacterGen"
|
||||
config_path = "CharacterGen_configs/Stage_3D_infer.yaml"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
cls.checkpoints_dir_abs = os.path.join(CKPT_ROOT_PATH, cls.checkpoints_dir)
|
||||
cls.config_root_path_abs = os.path.join(CONFIG_ROOT_PATH, cls.config_path)
|
||||
return {
|
||||
"required": {
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
"CHARACTER_LRM_PIPE",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"character_lrm_pipe",
|
||||
)
|
||||
FUNCTION = "load_model"
|
||||
CATEGORY = "Comfy3D/Import|Export"
|
||||
|
||||
def load_model(self):
|
||||
# Download checkpoints
|
||||
snapshot_download(repo_id=self.default_repo_id, local_dir=self.checkpoints_dir_abs, repo_type="model", ignore_patterns=["*.json", "*.py"])
|
||||
# Load pre-trained models
|
||||
character_lrm_pipe = Inference3D_API(checkpoint_root_path=self.checkpoints_dir_abs, cfg=load_config_cg3d(self.config_root_path_abs))
|
||||
return (character_lrm_pipe,)
|
||||
|
||||
class CharacterGen_Reconstruction_Model:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"character_lrm_pipe": ("CHARACTER_LRM_PIPE", ),
|
||||
"multiview_images": ("IMAGE",),
|
||||
"multiview_masks": ("MASK",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
"MESH",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"mesh",
|
||||
)
|
||||
|
||||
FUNCTION = "run_LRM"
|
||||
CATEGORY = "Comfy3D/Algorithm"
|
||||
|
||||
@torch.no_grad()
|
||||
def run_LRM(self, character_lrm_pipe, multiview_images, multiview_masks):
|
||||
pil_mv_image_list = torch_imgs_to_pils(multiview_images, multiview_masks, alpha_min=0.2)
|
||||
|
||||
vertices, faces = character_lrm_pipe.inference(pil_mv_image_list)
|
||||
|
||||
mesh = Mesh(v=vertices, f=faces.to(torch.int64), device=DEVICE)
|
||||
mesh.auto_normal()
|
||||
mesh.auto_uv()
|
||||
|
||||
return (mesh,)
|
||||