From 2f8d7b8b80321215933e9e8205660ba79f54b4f6 Mon Sep 17 00:00:00 2001 From: MrForExample <62230687+MrForExample@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:48:05 +0100 Subject: [PATCH] Integrate TripoSR --- README.md | 5 +- _Example_Workflows/TripoSR_to_Mesh.json | 342 +++++++++ ...orchmcubes-0.1.0-cp311-cp311-win_amd64.whl | Bin 0 -> 141282 bytes algorithms/NeuS_runner.py | 2 +- .../6d7b44061a6f8f191002c2af48e47bb887816288 | 29 - .../refs/main | 1 - .../model_index.json | 1 - configs/tsr_config.yaml | 38 + nodes.py | 110 ++- requirements_post.txt | 1 + requirements_post_win_py311_cu121.txt | 3 +- tsr/models/isosurface.py | 48 ++ tsr/models/nerf_renderer.py | 180 +++++ tsr/models/network_utils.py | 124 ++++ tsr/models/tokenizers/image.py | 66 ++ tsr/models/tokenizers/triplane.py | 45 ++ tsr/models/transformer/attention.py | 653 ++++++++++++++++++ .../transformer/basic_transformer_block.py | 334 +++++++++ tsr/models/transformer/transformer_1d.py | 219 ++++++ tsr/system.py | 214 ++++++ tsr/utils.py | 474 +++++++++++++ 21 files changed, 2853 insertions(+), 36 deletions(-) create mode 100644 _Example_Workflows/TripoSR_to_Mesh.json create mode 100644 _Wheels_win_py311_cu121/torchmcubes-0.1.0-cp311-cp311-win_amd64.whl delete mode 100644 checkpoints/models--flamehaze1115--wonder3d-v1.0/blobs/6d7b44061a6f8f191002c2af48e47bb887816288 delete mode 100644 checkpoints/models--flamehaze1115--wonder3d-v1.0/refs/main delete mode 120000 checkpoints/models--flamehaze1115--wonder3d-v1.0/snapshots/d6d2efc033a06a74d3761268de7295c97e6935d2/model_index.json create mode 100644 configs/tsr_config.yaml create mode 100644 tsr/models/isosurface.py create mode 100644 tsr/models/nerf_renderer.py create mode 100644 tsr/models/network_utils.py create mode 100644 tsr/models/tokenizers/image.py create mode 100644 tsr/models/tokenizers/triplane.py create mode 100644 tsr/models/transformer/attention.py create mode 100644 tsr/models/transformer/basic_transformer_block.py create mode 100644 tsr/models/transformer/transformer_1d.py create mode 100644 tsr/system.py create mode 100644 tsr/utils.py diff --git a/README.md b/README.md index efdba78..2e0365c 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,12 @@ ### Note: this project is still a WIP ## Currently support: -- For use case please check [Example Workflows](./_Example_Workflows/). [**Last update: 02/03/2024**] +- For use case please check [Example Workflows](./_Example_Workflows/). [**Last update: 05/03/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**: [VAST-AI-Research/TripoSR](https://github.com/VAST-AI-Research/TripoSR) | [ComfyUI-Flowty-TripoSR](https://github.com/flowtyone/ComfyUI-Flowty-TripoSR) + - Generate NeRF representation and using marching cube to turn it into 3D mesh + - **Wonder3D**: [xxlong0/Wonder3D](https://github.com/xxlong0/Wonder3D) - Generate spatial consistent 6 views images & normal maps from a single image ![Wonder3D_FatCat_MVs](_Example_Workflows/_Example_Outputs/Wonder3D_FatCat_MVs.jpg) diff --git a/_Example_Workflows/TripoSR_to_Mesh.json b/_Example_Workflows/TripoSR_to_Mesh.json new file mode 100644 index 0000000..a0ada08 --- /dev/null +++ b/_Example_Workflows/TripoSR_to_Mesh.json @@ -0,0 +1,342 @@ +{ + "last_node_id": 25, + "last_link_id": 35, + "nodes": [ + { + "id": 15, + "type": "[Comfy3D] Preview 3DMesh", + "pos": [ + 2554, + 464 + ], + "size": [ + 600, + 500 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "mesh_file_path", + "type": "STRING", + "link": 16, + "widget": { + "name": "mesh_file_path" + } + } + ], + "properties": { + "Node name for S&R": "[Comfy3D] Preview 3DMesh" + }, + "widgets_values": [ + "", + null + ] + }, + { + "id": 25, + "type": "[Comfy3D] Switch Mesh Axis", + "pos": [ + 1858, + 464 + ], + "size": { + "0": 315, + "1": 130 + }, + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "mesh", + "type": "MESH", + "link": 34 + } + ], + "outputs": [ + { + "name": "switched_mesh", + "type": "MESH", + "links": [ + 35 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "[Comfy3D] Switch Mesh Axis" + }, + "widgets_values": [ + "+y", + "+z", + "+x", + false + ] + }, + { + "id": 22, + "type": "InvertMask", + "pos": [ + 1301, + 546 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 32 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 33 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "InvertMask" + } + }, + { + "id": 21, + "type": "[Comfy3D] Load TripoSR Model", + "pos": [ + 1206, + 344 + ], + "size": { + "0": 315, + "1": 82 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "tsr_model", + "type": "TSR_MODEL", + "links": [ + 29 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "[Comfy3D] Load TripoSR Model" + }, + "widgets_values": [ + "model.ckpt", + 8192 + ] + }, + { + "id": 20, + "type": "[Comfy3D] TripoSR", + "pos": [ + 1559, + 465 + ], + "size": { + "0": 267.0216979980469, + "1": 122 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "tsr_model", + "type": "TSR_MODEL", + "link": 29 + }, + { + "name": "reference_image", + "type": "IMAGE", + "link": 30 + }, + { + "name": "reference_mask", + "type": "MASK", + "link": 33 + } + ], + "outputs": [ + { + "name": "MESH", + "type": "MESH", + "links": [ + 34 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "[Comfy3D] TripoSR" + }, + "widgets_values": [ + 256, + 25 + ] + }, + { + "id": 14, + "type": "[Comfy3D] Save 3D Mesh", + "pos": [ + 2218, + 464 + ], + "size": { + "0": 307.8253173828125, + "1": 60.248291015625 + }, + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "mesh", + "type": "MESH", + "link": 35 + } + ], + "outputs": [ + { + "name": "save_path", + "type": "STRING", + "links": [ + 16 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "[Comfy3D] Save 3D Mesh" + }, + "widgets_values": [ + "TripoSR_Test/KungFuCat.obj" + ] + }, + { + "id": 4, + "type": "LoadImage", + "pos": [ + 939, + 485 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 30 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": [ + 32 + ], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "KungFuCat.png", + "image" + ] + } + ], + "links": [ + [ + 16, + 14, + 0, + 15, + 0, + "STRING" + ], + [ + 29, + 21, + 0, + 20, + 0, + "TSR_MODEL" + ], + [ + 30, + 4, + 0, + 20, + 1, + "IMAGE" + ], + [ + 32, + 4, + 1, + 22, + 0, + "MASK" + ], + [ + 33, + 22, + 0, + 20, + 2, + "MASK" + ], + [ + 34, + 20, + 0, + 25, + 0, + "MESH" + ], + [ + 35, + 25, + 0, + 14, + 0, + "MESH" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file diff --git a/_Wheels_win_py311_cu121/torchmcubes-0.1.0-cp311-cp311-win_amd64.whl b/_Wheels_win_py311_cu121/torchmcubes-0.1.0-cp311-cp311-win_amd64.whl new file mode 100644 index 0000000000000000000000000000000000000000..90d7e1bd4b635c6a4d65c9c14ac634dca844734f GIT binary patch literal 141282 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

#*pyMD7E&E2G_?bY-1G;e>3$-6P1VV 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

Vg8J#`{>_ko$`9vgF4oT$U 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 torch.FloatTensor: + raise NotImplementedError + + +class MarchingCubeHelper(IsosurfaceHelper): + def __init__(self, resolution: int) -> None: + super().__init__() + self.resolution = resolution + self.mc_func: Callable = marching_cubes + self._grid_vertices: Optional[torch.FloatTensor] = None + + @property + def grid_vertices(self) -> torch.FloatTensor: + 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: torch.FloatTensor, + ) -> Tuple[torch.FloatTensor, torch.LongTensor]: + level = -level.view(self.resolution, self.resolution, self.resolution) + v_pos, t_pos_idx = self.mc_func(level.detach(), 0.0) + v_pos = v_pos[..., [2, 1, 0]] + v_pos = v_pos / (self.resolution - 1.0) + return v_pos, t_pos_idx diff --git a/tsr/models/nerf_renderer.py b/tsr/models/nerf_renderer.py new file mode 100644 index 0000000..0cf3ab6 --- /dev/null +++ b/tsr/models/nerf_renderer.py @@ -0,0 +1,180 @@ +from dataclasses import dataclass +from typing import Dict + +import torch +import torch.nn.functional as F +from einops import rearrange, reduce + +from ..utils import ( + BaseModule, + chunk_batch, + get_activation, + rays_intersect_bbox, + scale_tensor, +) + + +class TriplaneNeRFRenderer(BaseModule): + @dataclass + class Config(BaseModule.Config): + radius: float + + feature_reduction: str = "concat" + density_activation: str = "trunc_exp" + density_bias: float = -1.0 + color_activation: str = "sigmoid" + num_samples_per_ray: int = 128 + randomized: bool = False + + cfg: Config + + def configure(self) -> None: + assert self.cfg.feature_reduction in ["concat", "mean"] + self.chunk_size = 0 + + def set_chunk_size(self, chunk_size: int): + assert ( + chunk_size >= 0 + ), "chunk_size must be a non-negative integer (0 for no chunking)." + self.chunk_size = chunk_size + + def query_triplane( + self, + decoder: torch.nn.Module, + positions: torch.Tensor, + triplane: torch.Tensor, + ) -> Dict[str, torch.Tensor]: + input_shape = positions.shape[:-1] + positions = positions.view(-1, 3) + + # positions in (-radius, radius) + # normalized to (-1, 1) for grid sample + positions = scale_tensor( + positions, (-self.cfg.radius, self.cfg.radius), (-1, 1) + ) + + def _query_chunk(x): + indices2D: torch.Tensor = torch.stack( + (x[..., [0, 1]], x[..., [0, 2]], x[..., [1, 2]]), + dim=-3, + ) + out: torch.Tensor = F.grid_sample( + rearrange(triplane, "Np Cp Hp Wp -> Np Cp Hp Wp", Np=3), + rearrange(indices2D, "Np N Nd -> Np () N Nd", Np=3), + align_corners=False, + mode="bilinear", + ) + if self.cfg.feature_reduction == "concat": + out = rearrange(out, "Np Cp () N -> N (Np Cp)", Np=3) + elif self.cfg.feature_reduction == "mean": + out = reduce(out, "Np Cp () N -> N Cp", Np=3, reduction="mean") + else: + raise NotImplementedError + + net_out: Dict[str, torch.Tensor] = decoder(out) + return net_out + + if self.chunk_size > 0: + net_out = chunk_batch(_query_chunk, self.chunk_size, positions) + else: + net_out = _query_chunk(positions) + + net_out["density_act"] = get_activation(self.cfg.density_activation)( + net_out["density"] + self.cfg.density_bias + ) + net_out["color"] = get_activation(self.cfg.color_activation)( + net_out["features"] + ) + + net_out = {k: v.view(*input_shape, -1) for k, v in net_out.items()} + + return net_out + + def _forward( + self, + decoder: torch.nn.Module, + triplane: torch.Tensor, + rays_o: torch.Tensor, + rays_d: torch.Tensor, + **kwargs, + ): + rays_shape = rays_o.shape[:-1] + rays_o = rays_o.view(-1, 3) + rays_d = rays_d.view(-1, 3) + n_rays = rays_o.shape[0] + + t_near, t_far, rays_valid = rays_intersect_bbox(rays_o, rays_d, self.cfg.radius) + t_near, t_far = t_near[rays_valid], t_far[rays_valid] + + t_vals = torch.linspace( + 0, 1, self.cfg.num_samples_per_ray + 1, device=triplane.device + ) + t_mid = (t_vals[:-1] + t_vals[1:]) / 2.0 + z_vals = t_near * (1 - t_mid[None]) + t_far * t_mid[None] # (N_rays, N_samples) + + xyz = ( + rays_o[:, None, :] + z_vals[..., None] * rays_d[..., None, :] + ) # (N_rays, N_sample, 3) + + mlp_out = self.query_triplane( + decoder=decoder, + positions=xyz, + triplane=triplane, + ) + + eps = 1e-10 + # deltas = z_vals[:, 1:] - z_vals[:, :-1] # (N_rays, N_samples) + deltas = t_vals[1:] - t_vals[:-1] # (N_rays, N_samples) + alpha = 1 - torch.exp( + -deltas * mlp_out["density_act"][..., 0] + ) # (N_rays, N_samples) + accum_prod = torch.cat( + [ + torch.ones_like(alpha[:, :1]), + torch.cumprod(1 - alpha[:, :-1] + eps, dim=-1), + ], + dim=-1, + ) + weights = alpha * accum_prod # (N_rays, N_samples) + comp_rgb_ = (weights[..., None] * mlp_out["color"]).sum(dim=-2) # (N_rays, 3) + opacity_ = weights.sum(dim=-1) # (N_rays) + + comp_rgb = torch.zeros( + n_rays, 3, dtype=comp_rgb_.dtype, device=comp_rgb_.device + ) + opacity = torch.zeros(n_rays, dtype=opacity_.dtype, device=opacity_.device) + comp_rgb[rays_valid] = comp_rgb_ + opacity[rays_valid] = opacity_ + + comp_rgb += 1 - opacity[..., None] + comp_rgb = comp_rgb.view(*rays_shape, 3) + + return comp_rgb + + def forward( + self, + decoder: torch.nn.Module, + triplane: torch.Tensor, + rays_o: torch.Tensor, + rays_d: torch.Tensor, + ) -> Dict[str, torch.Tensor]: + if triplane.ndim == 4: + comp_rgb = self._forward(decoder, triplane, rays_o, rays_d) + else: + comp_rgb = torch.stack( + [ + self._forward(decoder, triplane[i], rays_o[i], rays_d[i]) + for i in range(triplane.shape[0]) + ], + dim=0, + ) + + return comp_rgb + + def train(self, mode=True): + self.randomized = mode and self.cfg.randomized + return super().train(mode=mode) + + def eval(self): + self.randomized = False + return super().eval() diff --git a/tsr/models/network_utils.py b/tsr/models/network_utils.py new file mode 100644 index 0000000..470c92b --- /dev/null +++ b/tsr/models/network_utils.py @@ -0,0 +1,124 @@ +from dataclasses import dataclass +from typing import Optional + +import torch +import torch.nn as nn +from einops import rearrange + +from ..utils import BaseModule + + +class TriplaneUpsampleNetwork(BaseModule): + @dataclass + class Config(BaseModule.Config): + in_channels: int + out_channels: int + + cfg: Config + + def configure(self) -> None: + self.upsample = nn.ConvTranspose2d( + self.cfg.in_channels, self.cfg.out_channels, kernel_size=2, stride=2 + ) + + def forward(self, triplanes: torch.Tensor) -> torch.Tensor: + 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 NeRFMLP(BaseModule): + @dataclass + class Config(BaseModule.Config): + in_channels: int + n_neurons: int + n_hidden_layers: int + activation: str = "relu" + bias: bool = True + weight_init: Optional[str] = "kaiming_uniform" + bias_init: Optional[str] = None + + cfg: Config + + def configure(self) -> None: + 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 - 1): + 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), + ] + layers += [ + self.make_linear( + self.cfg.n_neurons, + 4, # density 1 + features 3 + bias=self.cfg.bias, + weight_init=self.cfg.weight_init, + bias_init=self.cfg.bias_init, + ) + ] + self.layers = nn.Sequential(*layers) + + 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): + inp_shape = x.shape[:-1] + x = x.reshape(-1, x.shape[-1]) + + features = self.layers(x) + features = features.reshape(*inp_shape, -1) + out = {"density": features[..., 0:1], "features": features[..., 1:4]} + + return out diff --git a/tsr/models/tokenizers/image.py b/tsr/models/tokenizers/image.py new file mode 100644 index 0000000..d4034b2 --- /dev/null +++ b/tsr/models/tokenizers/image.py @@ -0,0 +1,66 @@ +from dataclasses import dataclass + +import torch +import torch.nn as nn +from einops import rearrange +from huggingface_hub import hf_hub_download +from transformers.models.vit.modeling_vit import ViTModel + +from ...utils import BaseModule + + +class DINOSingleImageTokenizer(BaseModule): + @dataclass + class Config(BaseModule.Config): + pretrained_model_name_or_path: str = "facebook/dino-vitb16" + enable_gradient_checkpointing: bool = False + + cfg: Config + + def configure(self) -> None: + self.model: ViTModel = ViTModel( + ViTModel.config_class.from_pretrained( + hf_hub_download( + repo_id=self.cfg.pretrained_model_name_or_path, + filename="config.json", + ) + ) + ) + + if self.cfg.enable_gradient_checkpointing: + self.model.encoder.gradient_checkpointing = True + + 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: torch.FloatTensor, **kwargs) -> torch.FloatTensor: + packed = False + if images.ndim == 4: + packed = True + images = images.unsqueeze(1) + + batch_size, n_input_views = images.shape[:2] + images = (images - self.image_mean) / self.image_std + out = self.model( + rearrange(images, "B N C H W -> (B N) C H W"), interpolate_pos_encoding=True + ) + local_features, global_features = out.last_hidden_state, out.pooler_output + 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 diff --git a/tsr/models/tokenizers/triplane.py b/tsr/models/tokenizers/triplane.py new file mode 100644 index 0000000..ecdd7fd --- /dev/null +++ b/tsr/models/tokenizers/triplane.py @@ -0,0 +1,45 @@ +import math +from dataclasses import dataclass + +import torch +import torch.nn as nn +from einops import rearrange, repeat + +from ...utils import BaseModule + + +class Triplane1DTokenizer(BaseModule): + @dataclass + class Config(BaseModule.Config): + plane_size: int + num_channels: int + + cfg: Config + + def configure(self) -> None: + 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) -> torch.Tensor: + 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: torch.Tensor) -> torch.Tensor: + 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, + ) diff --git a/tsr/models/transformer/attention.py b/tsr/models/transformer/attention.py new file mode 100644 index 0000000..34bd997 --- /dev/null +++ b/tsr/models/transformer/attention.py @@ -0,0 +1,653 @@ +# 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. +# +# -------- +# +# Modified 2024 by the Tripo AI and Stability AI Team. +# +# Copyright (c) 2024 Tripo AI & Stability AI +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from typing import Optional + +import torch +import torch.nn.functional as F +from torch import nn + + +class Attention(nn.Module): + r""" + A cross attention layer. + + Parameters: + query_dim (`int`): + The number of channels in the query. + cross_attention_dim (`int`, *optional*): + The number of channels in the encoder_hidden_states. If not given, defaults to `query_dim`. + heads (`int`, *optional*, defaults to 8): + The number of heads to use for multi-head attention. + dim_head (`int`, *optional*, defaults to 64): + The number of channels in each head. + dropout (`float`, *optional*, defaults to 0.0): + The dropout probability to use. + bias (`bool`, *optional*, defaults to False): + Set to `True` for the query, key, and value linear layers to contain a bias parameter. + upcast_attention (`bool`, *optional*, defaults to False): + Set to `True` to upcast the attention computation to `float32`. + upcast_softmax (`bool`, *optional*, defaults to False): + Set to `True` to upcast the softmax computation to `float32`. + cross_attention_norm (`str`, *optional*, defaults to `None`): + The type of normalization to use for the cross attention. Can be `None`, `layer_norm`, or `group_norm`. + cross_attention_norm_num_groups (`int`, *optional*, defaults to 32): + The number of groups to use for the group norm in the cross attention. + added_kv_proj_dim (`int`, *optional*, defaults to `None`): + The number of channels to use for the added key and value projections. If `None`, no projection is used. + norm_num_groups (`int`, *optional*, defaults to `None`): + The number of groups to use for the group norm in the attention. + spatial_norm_dim (`int`, *optional*, defaults to `None`): + The number of channels to use for the spatial normalization. + out_bias (`bool`, *optional*, defaults to `True`): + Set to `True` to use a bias in the output linear layer. + scale_qk (`bool`, *optional*, defaults to `True`): + Set to `True` to scale the query and key by `1 / sqrt(dim_head)`. + only_cross_attention (`bool`, *optional*, defaults to `False`): + Set to `True` to only use cross attention and not added_kv_proj_dim. Can only be set to `True` if + `added_kv_proj_dim` is not `None`. + eps (`float`, *optional*, defaults to 1e-5): + An additional value added to the denominator in group normalization that is used for numerical stability. + rescale_output_factor (`float`, *optional*, defaults to 1.0): + A factor to rescale the output by dividing it with this value. + residual_connection (`bool`, *optional*, defaults to `False`): + Set to `True` to add the residual connection to the output. + _from_deprecated_attn_block (`bool`, *optional*, defaults to `False`): + Set to `True` if the attention block is loaded from a deprecated state dict. + processor (`AttnProcessor`, *optional*, defaults to `None`): + The attention processor to use. If `None`, defaults to `AttnProcessor2_0` if `torch 2.x` is used and + `AttnProcessor` otherwise. + """ + + def __init__( + self, + query_dim: int, + cross_attention_dim: Optional[int] = None, + heads: int = 8, + dim_head: int = 64, + dropout: float = 0.0, + bias: bool = False, + upcast_attention: bool = False, + upcast_softmax: bool = False, + cross_attention_norm: Optional[str] = None, + cross_attention_norm_num_groups: int = 32, + added_kv_proj_dim: Optional[int] = None, + norm_num_groups: Optional[int] = None, + out_bias: bool = True, + scale_qk: bool = True, + only_cross_attention: bool = False, + eps: float = 1e-5, + rescale_output_factor: float = 1.0, + residual_connection: bool = False, + _from_deprecated_attn_block: bool = False, + processor: Optional["AttnProcessor"] = None, + out_dim: int = None, + ): + super().__init__() + self.inner_dim = out_dim if out_dim is not None else dim_head * heads + self.query_dim = query_dim + self.cross_attention_dim = ( + cross_attention_dim if cross_attention_dim is not None else query_dim + ) + self.upcast_attention = upcast_attention + self.upcast_softmax = upcast_softmax + self.rescale_output_factor = rescale_output_factor + self.residual_connection = residual_connection + self.dropout = dropout + self.fused_projections = False + self.out_dim = out_dim if out_dim is not None else query_dim + + # we make use of this private variable to know whether this class is loaded + # with an deprecated state dict so that we can convert it on the fly + self._from_deprecated_attn_block = _from_deprecated_attn_block + + self.scale_qk = scale_qk + self.scale = dim_head**-0.5 if self.scale_qk else 1.0 + + self.heads = out_dim // dim_head if out_dim is not None else heads + # for slice_size > 0 the attention score computation + # is split across the batch axis to save memory + # You can set slice_size with `set_attention_slice` + self.sliceable_head_dim = heads + + self.added_kv_proj_dim = added_kv_proj_dim + self.only_cross_attention = only_cross_attention + + if self.added_kv_proj_dim is None and self.only_cross_attention: + raise ValueError( + "`only_cross_attention` can only be set to True if `added_kv_proj_dim` is not None. Make sure to set either `only_cross_attention=False` or define `added_kv_proj_dim`." + ) + + if norm_num_groups is not None: + self.group_norm = nn.GroupNorm( + num_channels=query_dim, num_groups=norm_num_groups, eps=eps, affine=True + ) + else: + self.group_norm = None + + self.spatial_norm = None + + if cross_attention_norm is None: + self.norm_cross = None + elif cross_attention_norm == "layer_norm": + self.norm_cross = nn.LayerNorm(self.cross_attention_dim) + elif cross_attention_norm == "group_norm": + if self.added_kv_proj_dim is not None: + # The given `encoder_hidden_states` are initially of shape + # (batch_size, seq_len, added_kv_proj_dim) before being projected + # to (batch_size, seq_len, cross_attention_dim). The norm is applied + # before the projection, so we need to use `added_kv_proj_dim` as + # the number of channels for the group norm. + norm_cross_num_channels = added_kv_proj_dim + else: + norm_cross_num_channels = self.cross_attention_dim + + self.norm_cross = nn.GroupNorm( + num_channels=norm_cross_num_channels, + num_groups=cross_attention_norm_num_groups, + eps=1e-5, + affine=True, + ) + else: + raise ValueError( + f"unknown cross_attention_norm: {cross_attention_norm}. Should be None, 'layer_norm' or 'group_norm'" + ) + + linear_cls = nn.Linear + + self.linear_cls = linear_cls + self.to_q = linear_cls(query_dim, self.inner_dim, bias=bias) + + if not self.only_cross_attention: + # only relevant for the `AddedKVProcessor` classes + self.to_k = linear_cls(self.cross_attention_dim, self.inner_dim, bias=bias) + self.to_v = linear_cls(self.cross_attention_dim, self.inner_dim, bias=bias) + else: + self.to_k = None + self.to_v = None + + if self.added_kv_proj_dim is not None: + self.add_k_proj = linear_cls(added_kv_proj_dim, self.inner_dim) + self.add_v_proj = linear_cls(added_kv_proj_dim, self.inner_dim) + + self.to_out = nn.ModuleList([]) + self.to_out.append(linear_cls(self.inner_dim, self.out_dim, bias=out_bias)) + self.to_out.append(nn.Dropout(dropout)) + + # set attention processor + # We use the AttnProcessor2_0 by default when torch 2.x is used which uses + # torch.nn.functional.scaled_dot_product_attention for native Flash/memory_efficient_attention + # but only if it has the default `scale` argument. TODO remove scale_qk check when we move to torch 2.1 + if processor is None: + processor = ( + AttnProcessor2_0() + if hasattr(F, "scaled_dot_product_attention") and self.scale_qk + else AttnProcessor() + ) + self.set_processor(processor) + + def set_processor(self, processor: "AttnProcessor") -> None: + self.processor = processor + + def forward( + self, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + **cross_attention_kwargs, + ) -> torch.Tensor: + r""" + The forward method of the `Attention` class. + + Args: + hidden_states (`torch.Tensor`): + The hidden states of the query. + encoder_hidden_states (`torch.Tensor`, *optional*): + The hidden states of the encoder. + attention_mask (`torch.Tensor`, *optional*): + The attention mask to use. If `None`, no mask is applied. + **cross_attention_kwargs: + Additional keyword arguments to pass along to the cross attention. + + Returns: + `torch.Tensor`: The output of the attention layer. + """ + # The `Attention` class can call different attention processors / attention functions + # here we simply pass along all tensors to the selected processor class + # For standard processors that are defined here, `**cross_attention_kwargs` is empty + return self.processor( + self, + hidden_states, + encoder_hidden_states=encoder_hidden_states, + attention_mask=attention_mask, + **cross_attention_kwargs, + ) + + def batch_to_head_dim(self, tensor: torch.Tensor) -> torch.Tensor: + r""" + Reshape the tensor from `[batch_size, seq_len, dim]` to `[batch_size // heads, seq_len, dim * heads]`. `heads` + is the number of heads initialized while constructing the `Attention` class. + + Args: + tensor (`torch.Tensor`): The tensor to reshape. + + Returns: + `torch.Tensor`: The reshaped tensor. + """ + head_size = self.heads + batch_size, seq_len, dim = tensor.shape + tensor = tensor.reshape(batch_size // head_size, head_size, seq_len, dim) + tensor = tensor.permute(0, 2, 1, 3).reshape( + batch_size // head_size, seq_len, dim * head_size + ) + return tensor + + def head_to_batch_dim(self, tensor: torch.Tensor, out_dim: int = 3) -> torch.Tensor: + r""" + Reshape the tensor from `[batch_size, seq_len, dim]` to `[batch_size, seq_len, heads, dim // heads]` `heads` is + the number of heads initialized while constructing the `Attention` class. + + Args: + tensor (`torch.Tensor`): The tensor to reshape. + out_dim (`int`, *optional*, defaults to `3`): The output dimension of the tensor. If `3`, the tensor is + reshaped to `[batch_size * heads, seq_len, dim // heads]`. + + Returns: + `torch.Tensor`: The reshaped tensor. + """ + head_size = self.heads + batch_size, seq_len, dim = tensor.shape + tensor = tensor.reshape(batch_size, seq_len, head_size, dim // head_size) + tensor = tensor.permute(0, 2, 1, 3) + + if out_dim == 3: + tensor = tensor.reshape(batch_size * head_size, seq_len, dim // head_size) + + return tensor + + def get_attention_scores( + self, + query: torch.Tensor, + key: torch.Tensor, + attention_mask: torch.Tensor = None, + ) -> torch.Tensor: + r""" + Compute the attention scores. + + Args: + query (`torch.Tensor`): The query tensor. + key (`torch.Tensor`): The key tensor. + attention_mask (`torch.Tensor`, *optional*): The attention mask to use. If `None`, no mask is applied. + + Returns: + `torch.Tensor`: The attention probabilities/scores. + """ + dtype = query.dtype + if self.upcast_attention: + query = query.float() + key = key.float() + + if attention_mask is None: + baddbmm_input = torch.empty( + query.shape[0], + query.shape[1], + key.shape[1], + dtype=query.dtype, + device=query.device, + ) + beta = 0 + else: + baddbmm_input = attention_mask + beta = 1 + + attention_scores = torch.baddbmm( + baddbmm_input, + query, + key.transpose(-1, -2), + beta=beta, + alpha=self.scale, + ) + del baddbmm_input + + if self.upcast_softmax: + attention_scores = attention_scores.float() + + attention_probs = attention_scores.softmax(dim=-1) + del attention_scores + + attention_probs = attention_probs.to(dtype) + + return attention_probs + + def prepare_attention_mask( + self, + attention_mask: torch.Tensor, + target_length: int, + batch_size: int, + out_dim: int = 3, + ) -> torch.Tensor: + r""" + Prepare the attention mask for the attention computation. + + Args: + attention_mask (`torch.Tensor`): + The attention mask to prepare. + target_length (`int`): + The target length of the attention mask. This is the length of the attention mask after padding. + batch_size (`int`): + The batch size, which is used to repeat the attention mask. + out_dim (`int`, *optional*, defaults to `3`): + The output dimension of the attention mask. Can be either `3` or `4`. + + Returns: + `torch.Tensor`: The prepared attention mask. + """ + head_size = self.heads + if attention_mask is None: + return attention_mask + + current_length: int = attention_mask.shape[-1] + if current_length != target_length: + if attention_mask.device.type == "mps": + # HACK: MPS: Does not support padding by greater than dimension of input tensor. + # Instead, we can manually construct the padding tensor. + padding_shape = ( + attention_mask.shape[0], + attention_mask.shape[1], + target_length, + ) + padding = torch.zeros( + padding_shape, + dtype=attention_mask.dtype, + device=attention_mask.device, + ) + attention_mask = torch.cat([attention_mask, padding], dim=2) + else: + # TODO: for pipelines such as stable-diffusion, padding cross-attn mask: + # we want to instead pad by (0, remaining_length), where remaining_length is: + # remaining_length: int = target_length - current_length + # TODO: re-enable tests/models/test_models_unet_2d_condition.py#test_model_xattn_padding + attention_mask = F.pad(attention_mask, (0, target_length), value=0.0) + + if out_dim == 3: + if attention_mask.shape[0] < batch_size * head_size: + attention_mask = attention_mask.repeat_interleave(head_size, dim=0) + elif out_dim == 4: + attention_mask = attention_mask.unsqueeze(1) + attention_mask = attention_mask.repeat_interleave(head_size, dim=1) + + return attention_mask + + def norm_encoder_hidden_states( + self, encoder_hidden_states: torch.Tensor + ) -> torch.Tensor: + r""" + Normalize the encoder hidden states. Requires `self.norm_cross` to be specified when constructing the + `Attention` class. + + Args: + encoder_hidden_states (`torch.Tensor`): Hidden states of the encoder. + + Returns: + `torch.Tensor`: The normalized encoder hidden states. + """ + assert ( + self.norm_cross is not None + ), "self.norm_cross must be defined to call self.norm_encoder_hidden_states" + + if isinstance(self.norm_cross, nn.LayerNorm): + encoder_hidden_states = self.norm_cross(encoder_hidden_states) + elif isinstance(self.norm_cross, nn.GroupNorm): + # Group norm norms along the channels dimension and expects + # input to be in the shape of (N, C, *). In this case, we want + # to norm along the hidden dimension, so we need to move + # (batch_size, sequence_length, hidden_size) -> + # (batch_size, hidden_size, sequence_length) + encoder_hidden_states = encoder_hidden_states.transpose(1, 2) + encoder_hidden_states = self.norm_cross(encoder_hidden_states) + encoder_hidden_states = encoder_hidden_states.transpose(1, 2) + else: + assert False + + return encoder_hidden_states + + @torch.no_grad() + def fuse_projections(self, fuse=True): + is_cross_attention = self.cross_attention_dim != self.query_dim + device = self.to_q.weight.data.device + dtype = self.to_q.weight.data.dtype + + if not is_cross_attention: + # fetch weight matrices. + concatenated_weights = torch.cat( + [self.to_q.weight.data, self.to_k.weight.data, self.to_v.weight.data] + ) + in_features = concatenated_weights.shape[1] + out_features = concatenated_weights.shape[0] + + # create a new single projection layer and copy over the weights. + self.to_qkv = self.linear_cls( + in_features, out_features, bias=False, device=device, dtype=dtype + ) + self.to_qkv.weight.copy_(concatenated_weights) + + else: + concatenated_weights = torch.cat( + [self.to_k.weight.data, self.to_v.weight.data] + ) + in_features = concatenated_weights.shape[1] + out_features = concatenated_weights.shape[0] + + self.to_kv = self.linear_cls( + in_features, out_features, bias=False, device=device, dtype=dtype + ) + self.to_kv.weight.copy_(concatenated_weights) + + self.fused_projections = fuse + + +class AttnProcessor: + r""" + Default processor for performing attention-related computations. + """ + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.Tensor: + residual = hidden_states + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view( + batch_size, channel, height * width + ).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape + if encoder_hidden_states is None + else encoder_hidden_states.shape + ) + attention_mask = attn.prepare_attention_mask( + attention_mask, sequence_length, batch_size + ) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose( + 1, 2 + ) + + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states( + encoder_hidden_states + ) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + query = attn.head_to_batch_dim(query) + key = attn.head_to_batch_dim(key) + value = attn.head_to_batch_dim(value) + + attention_probs = attn.get_attention_scores(query, key, attention_mask) + hidden_states = torch.bmm(attention_probs, value) + hidden_states = attn.batch_to_head_dim(hidden_states) + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape( + batch_size, channel, height, width + ) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states + + +class AttnProcessor2_0: + r""" + Processor for implementing scaled dot-product attention (enabled by default if you're using PyTorch 2.0). + """ + + def __init__(self): + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError( + "AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0." + ) + + def __call__( + self, + attn: Attention, + hidden_states: torch.FloatTensor, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + ) -> torch.FloatTensor: + residual = hidden_states + + input_ndim = hidden_states.ndim + + if input_ndim == 4: + batch_size, channel, height, width = hidden_states.shape + hidden_states = hidden_states.view( + batch_size, channel, height * width + ).transpose(1, 2) + + batch_size, sequence_length, _ = ( + hidden_states.shape + if encoder_hidden_states is None + else encoder_hidden_states.shape + ) + + if attention_mask is not None: + attention_mask = attn.prepare_attention_mask( + attention_mask, sequence_length, batch_size + ) + # scaled_dot_product_attention expects attention_mask shape to be + # (batch, heads, source_length, target_length) + attention_mask = attention_mask.view( + batch_size, attn.heads, -1, attention_mask.shape[-1] + ) + + if attn.group_norm is not None: + hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose( + 1, 2 + ) + + query = attn.to_q(hidden_states) + + if encoder_hidden_states is None: + encoder_hidden_states = hidden_states + elif attn.norm_cross: + encoder_hidden_states = attn.norm_encoder_hidden_states( + encoder_hidden_states + ) + + key = attn.to_k(encoder_hidden_states) + value = attn.to_v(encoder_hidden_states) + + inner_dim = key.shape[-1] + head_dim = inner_dim // attn.heads + + query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + # TODO: add support for attn.scale when we move to Torch 2.1 + hidden_states = F.scaled_dot_product_attention( + query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False + ) + + hidden_states = hidden_states.transpose(1, 2).reshape( + batch_size, -1, attn.heads * head_dim + ) + hidden_states = hidden_states.to(query.dtype) + + # linear proj + hidden_states = attn.to_out[0](hidden_states) + # dropout + hidden_states = attn.to_out[1](hidden_states) + + if input_ndim == 4: + hidden_states = hidden_states.transpose(-1, -2).reshape( + batch_size, channel, height, width + ) + + if attn.residual_connection: + hidden_states = hidden_states + residual + + hidden_states = hidden_states / attn.rescale_output_factor + + return hidden_states diff --git a/tsr/models/transformer/basic_transformer_block.py b/tsr/models/transformer/basic_transformer_block.py new file mode 100644 index 0000000..1ea41b0 --- /dev/null +++ b/tsr/models/transformer/basic_transformer_block.py @@ -0,0 +1,334 @@ +# 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. +# +# -------- +# +# Modified 2024 by the Tripo AI and Stability AI Team. +# +# Copyright (c) 2024 Tripo AI & Stability AI +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from typing import Optional + +import torch +import torch.nn.functional as F +from torch import nn + +from .attention import Attention + + +class BasicTransformerBlock(nn.Module): + 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. + 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. + """ + + 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", + 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, + ): + super().__init__() + self.only_cross_attention = only_cross_attention + + assert norm_type == "layer_norm" + + # Define 3 blocks. Each block has its own normalization layer. + # 1. Self-Attn + 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. + 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 + self.norm3 = nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine) + self.ff = FeedForward( + dim, + dropout=dropout, + activation_fn=activation_fn, + final_dropout=final_dropout, + ) + + # 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, + ) -> torch.FloatTensor: + # Notice that normalization is always applied before the real computation in the following blocks. + # 0. Self-Attention + norm_hidden_states = self.norm1(hidden_states) + + attn_output = self.attn1( + norm_hidden_states, + encoder_hidden_states=( + encoder_hidden_states if self.only_cross_attention else None + ), + attention_mask=attention_mask, + ) + + hidden_states = attn_output + hidden_states + + # 3. Cross-Attention + if self.attn2 is not None: + 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, + ) + hidden_states = attn_output + hidden_states + + # 4. Feed-forward + norm_hidden_states = self.norm3(hidden_states) + + 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) + 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) + + 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) -> 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) diff --git a/tsr/models/transformer/transformer_1d.py b/tsr/models/transformer/transformer_1d.py new file mode 100644 index 0000000..a98d0fb --- /dev/null +++ b/tsr/models/transformer/transformer_1d.py @@ -0,0 +1,219 @@ +# 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. +# +# -------- +# +# Modified 2024 by the Tripo AI and Stability AI Team. +# +# Copyright (c) 2024 Tripo AI & Stability AI +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from dataclasses import dataclass +from typing import Optional + +import torch +import torch.nn.functional as F +from torch import nn + +from ...utils import BaseModule +from .basic_transformer_block import BasicTransformerBlock + + +class Transformer1D(BaseModule): + @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" + only_cross_attention: bool = False + double_self_attention: bool = False + upcast_attention: bool = False + norm_type: str = "layer_norm" + norm_elementwise_affine: bool = True + gradient_checkpointing: bool = False + + cfg: Config + + def configure(self) -> None: + 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 + + # 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, + 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, + ) + 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 + + def forward( + self, + hidden_states: torch.Tensor, + encoder_hidden_states: Optional[torch.Tensor] = 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. + 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. + + Returns: + torch.FloatTensor + """ + # 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, + 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, + ) + + # 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 diff --git a/tsr/system.py b/tsr/system.py new file mode 100644 index 0000000..baad4ca --- /dev/null +++ b/tsr/system.py @@ -0,0 +1,214 @@ +import math +import os +from dataclasses import dataclass, field +from typing import List, Union + +import numpy as np +import PIL.Image +import torch +import torch.nn.functional as F +import trimesh +from einops import rearrange +from huggingface_hub import hf_hub_download +from omegaconf import OmegaConf +from PIL import Image + +from .models.isosurface import MarchingCubeHelper +from .utils import ( + BaseModule, + ImagePreprocessor, + find_class, + get_spherical_cameras, + scale_tensor, +) + + +class TSR(BaseModule): + @dataclass + class Config(BaseModule.Config): + cond_image_size: int + + image_tokenizer_cls: str + image_tokenizer: dict + + tokenizer_cls: str + tokenizer: dict + + backbone_cls: str + backbone: dict + + post_processor_cls: str + post_processor: dict + + decoder_cls: str + decoder: dict + + renderer_cls: str + renderer: dict + + cfg: Config + + @classmethod + def from_pretrained( + cls, pretrained_model_name_or_path: str, config_name: str, weight_name: str + ): + if os.path.isdir(pretrained_model_name_or_path): + config_path = os.path.join(pretrained_model_name_or_path, config_name) + weight_path = os.path.join(pretrained_model_name_or_path, weight_name) + else: + config_path = hf_hub_download( + repo_id=pretrained_model_name_or_path, filename=config_name + ) + weight_path = hf_hub_download( + repo_id=pretrained_model_name_or_path, filename=weight_name + ) + + cfg = OmegaConf.load(config_path) + OmegaConf.resolve(cfg) + model = cls(cfg) + ckpt = torch.load(weight_path, map_location="cpu") + model.load_state_dict(ckpt) + return model + + @classmethod + def from_pretrained_custom( + cls, weight_path: str, config_path: str + ): + cfg = OmegaConf.load(config_path) + OmegaConf.resolve(cfg) + model = cls(cfg) + ckpt = torch.load(weight_path, map_location="cpu") + model.load_state_dict(ckpt) + return model + + def configure(self): + self.image_tokenizer = find_class(self.cfg.image_tokenizer_cls)( + self.cfg.image_tokenizer + ) + self.tokenizer = find_class(self.cfg.tokenizer_cls)(self.cfg.tokenizer) + self.backbone = find_class(self.cfg.backbone_cls)(self.cfg.backbone) + self.post_processor = find_class(self.cfg.post_processor_cls)( + self.cfg.post_processor + ) + self.decoder = find_class(self.cfg.decoder_cls)(self.cfg.decoder) + self.renderer = find_class(self.cfg.renderer_cls)(self.cfg.renderer) + self.image_processor = ImagePreprocessor() + self.isosurface_helper = None + + def forward( + self, + image: Union[ + PIL.Image.Image, + np.ndarray, + torch.FloatTensor, + List[PIL.Image.Image], + List[np.ndarray], + List[torch.FloatTensor], + ], + device: str, + ) -> torch.FloatTensor: + rgb_cond = self.image_processor(image, self.cfg.cond_image_size)[:, None].to( + device + ) + batch_size = rgb_cond.shape[0] + + input_image_tokens: torch.Tensor = self.image_tokenizer( + rearrange(rgb_cond, "B Nv H W C -> B Nv C H W", Nv=1), + ) + + input_image_tokens = rearrange( + input_image_tokens, "B Nv C Nt -> B (Nv Nt) C", Nv=1 + ) + + tokens: torch.Tensor = self.tokenizer(batch_size) + + tokens = self.backbone( + tokens, + encoder_hidden_states=input_image_tokens, + ) + + scene_codes = self.post_processor(self.tokenizer.detokenize(tokens)) + return scene_codes + + def render( + self, + scene_codes, + n_views: int, + elevation_deg: float = 0.0, + camera_distance: float = 1.9, + fovy_deg: float = 40.0, + height: int = 256, + width: int = 256, + return_type: str = "pil", + ): + rays_o, rays_d = get_spherical_cameras( + n_views, elevation_deg, camera_distance, fovy_deg, height, width + ) + rays_o, rays_d = rays_o.to(scene_codes.device), rays_d.to(scene_codes.device) + + def process_output(image: torch.FloatTensor): + if return_type == "pt": + return image + elif return_type == "np": + return image.detach().cpu().numpy() + elif return_type == "pil": + return Image.fromarray( + (image.detach().cpu().numpy() * 255.0).astype(np.uint8) + ) + else: + raise NotImplementedError + + images = [] + for scene_code in scene_codes: + images_ = [] + for i in range(n_views): + with torch.no_grad(): + image = self.renderer( + self.decoder, scene_code, rays_o[i], rays_d[i] + ) + images_.append(process_output(image)) + images.append(images_) + + return images + + def set_marching_cubes_resolution(self, resolution: int): + if ( + self.isosurface_helper is not None + and self.isosurface_helper.resolution == resolution + ): + return + self.isosurface_helper = MarchingCubeHelper(resolution) + + def extract_mesh(self, scene_codes, resolution: int = 256, threshold: float = 25.0): + self.set_marching_cubes_resolution(resolution) + meshes = [] + for scene_code in scene_codes: + with torch.no_grad(): + density = self.renderer.query_triplane( + self.decoder, + scale_tensor( + self.isosurface_helper.grid_vertices.to(scene_codes.device), + self.isosurface_helper.points_range, + (-self.renderer.cfg.radius, self.renderer.cfg.radius), + ), + scene_code, + )["density_act"] + v_pos, t_pos_idx = self.isosurface_helper(-(density - threshold)) + v_pos = scale_tensor( + v_pos, + self.isosurface_helper.points_range, + (-self.renderer.cfg.radius, self.renderer.cfg.radius), + ) + with torch.no_grad(): + color = self.renderer.query_triplane( + self.decoder, + v_pos, + scene_code, + )["color"] + mesh = trimesh.Trimesh( + vertices=v_pos.cpu().numpy(), + faces=t_pos_idx.cpu().numpy(), + vertex_colors=color.cpu().numpy(), + ) + meshes.append(mesh) + return meshes diff --git a/tsr/utils.py b/tsr/utils.py new file mode 100644 index 0000000..fc942d1 --- /dev/null +++ b/tsr/utils.py @@ -0,0 +1,474 @@ +import importlib +import math +from collections import defaultdict +from dataclasses import dataclass +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +import imageio +import numpy as np +import PIL.Image +import rembg +import torch +import torch.nn as nn +import torch.nn.functional as F +import trimesh +from omegaconf import DictConfig, OmegaConf +from PIL import Image + +def parse_structured(fields: Any, cfg: Optional[Union[dict, DictConfig]] = None) -> Any: + scfg = OmegaConf.merge(OmegaConf.structured(fields), cfg) + return scfg + + +def find_class(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 + + +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) + + +class BaseModule(nn.Module): + @dataclass + class Config: + pass + + 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.configure(*args, **kwargs) + + def configure(self, *args, **kwargs) -> None: + raise NotImplementedError + + +class ImagePreprocessor: + def convert_and_resize( + self, + image: Union[PIL.Image.Image, np.ndarray, torch.Tensor], + size: int, + ): + if isinstance(image, PIL.Image.Image): + image = torch.from_numpy(np.array(image).astype(np.float32) / 255.0) + elif isinstance(image, np.ndarray): + if image.dtype == np.uint8: + image = torch.from_numpy(image.astype(np.float32) / 255.0) + else: + image = torch.from_numpy(image) + elif isinstance(image, torch.Tensor): + pass + + batched = image.ndim == 4 + + if not batched: + image = image[None, ...] + image = F.interpolate( + image.permute(0, 3, 1, 2), + (size, size), + mode="bilinear", + align_corners=False, + antialias=True, + ).permute(0, 2, 3, 1) + if not batched: + image = image[0] + return image + + def __call__( + self, + image: Union[ + PIL.Image.Image, + np.ndarray, + torch.FloatTensor, + List[PIL.Image.Image], + List[np.ndarray], + List[torch.FloatTensor], + ], + size: int, + ) -> Any: + if isinstance(image, (np.ndarray, torch.FloatTensor)) and image.ndim == 4: + image = self.convert_and_resize(image, size) + else: + if not isinstance(image, list): + image = [image] + image = [self.convert_and_resize(im, size) for im in image] + image = torch.stack(image, dim=0) + return image + + +def rays_intersect_bbox( + rays_o: torch.Tensor, + rays_d: torch.Tensor, + radius: float, + near: float = 0.0, + valid_thresh: float = 0.01, +): + 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[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 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 + + +ValidScale = Union[Tuple[float, float], torch.FloatTensor] + + +def scale_tensor(dat: torch.FloatTensor, 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, torch.FloatTensor): + 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 + + +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 == "exp": + return lambda x: torch.exp(x) + elif name == "sigmoid": + return lambda x: torch.sigmoid(x) + elif name == "tanh": + return lambda x: torch.tanh(x) + elif name == "softplus": + return lambda x: F.softplus(x) + else: + try: + return getattr(F, name) + except AttributeError: + raise ValueError(f"Unknown activation function: {name}") + + +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, +) -> torch.FloatTensor: + """ + 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 = 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, + c2w, + keepdim=False, + normalize=False, +) -> Tuple[torch.FloatTensor, torch.FloatTensor]: + # 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) + + 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_spherical_cameras( + n_views: int, + elevation_deg: float, + camera_distance: float, + fovy_deg: float, + height: int, + width: int, +): + azimuth_deg = torch.linspace(0, 360.0, n_views + 1)[:n_views] + elevation_deg = torch.full_like(azimuth_deg, elevation_deg) + camera_distances = torch.full_like(elevation_deg, camera_distance) + + elevation = elevation_deg * math.pi / 180 + azimuth = azimuth_deg * math.pi / 180 + + # convert spherical coordinates to cartesian coordinates + # right hand coordinate system, x back, y right, z up + # elevation in (-90, 90), azimuth from +x to +y in (-180, 180) + camera_positions = torch.stack( + [ + camera_distances * torch.cos(elevation) * torch.cos(azimuth), + camera_distances * torch.cos(elevation) * torch.sin(azimuth), + camera_distances * torch.sin(elevation), + ], + dim=-1, + ) + + # default scene center at origin + center = torch.zeros_like(camera_positions) + # default camera up direction as +z + up = torch.as_tensor([0, 0, 1], dtype=torch.float32)[None, :].repeat(n_views, 1) + + fovy = torch.full_like(elevation_deg, fovy_deg) * math.pi / 180 + + lookat = F.normalize(center - camera_positions, dim=-1) + right = F.normalize(torch.cross(lookat, up), dim=-1) + up = F.normalize(torch.cross(right, lookat), dim=-1) + c2w3x4 = torch.cat( + [torch.stack([right, up, -lookat], dim=-1), camera_positions[:, :, None]], + dim=-1, + ) + c2w = torch.cat([c2w3x4, torch.zeros_like(c2w3x4[:, :1])], dim=1) + c2w[:, 3, 3] = 1.0 + + # get directions by dividing directions_unit_focal by focal length + focal_length = 0.5 * height / torch.tan(0.5 * fovy) + directions_unit_focal = get_ray_directions( + H=height, + W=width, + focal=1.0, + ) + directions = directions_unit_focal[None, :, :, :].repeat(n_views, 1, 1, 1) + directions[:, :, :, :2] = ( + directions[:, :, :, :2] / focal_length[:, None, None, None] + ) + # must use normalize=True to normalize directions here + rays_o, rays_d = get_rays(directions, c2w, keepdim=True, normalize=True) + + return rays_o, rays_d + + +def remove_background( + image: PIL.Image.Image, + rembg_session: Any = None, + force: bool = False, + **rembg_kwargs, +) -> PIL.Image.Image: + do_remove = True + if image.mode == "RGBA" and image.getextrema()[3][0] < 255: + do_remove = False + do_remove = do_remove or force + if do_remove: + image = rembg.remove(image, session=rembg_session, **rembg_kwargs) + return image + + +def resize_foreground( + image: PIL.Image.Image, + ratio: float, +) -> PIL.Image.Image: + image = np.array(image) + assert image.shape[-1] == 4 + alpha = np.where(image[..., 3] > 0) + y1, y2, x1, x2 = ( + alpha[0].min(), + alpha[0].max(), + alpha[1].min(), + alpha[1].max(), + ) + # crop the foreground + fg = image[y1:y2, x1:x2] + # pad to square + size = max(fg.shape[0], fg.shape[1]) + ph0, pw0 = (size - fg.shape[0]) // 2, (size - fg.shape[1]) // 2 + ph1, pw1 = size - fg.shape[0] - ph0, size - fg.shape[1] - pw0 + new_image = np.pad( + fg, + ((ph0, ph1), (pw0, pw1), (0, 0)), + mode="constant", + constant_values=((0, 0), (0, 0), (0, 0)), + ) + + # compute padding according to the ratio + new_size = int(new_image.shape[0] / ratio) + # pad to size, double side + ph0, pw0 = (new_size - size) // 2, (new_size - size) // 2 + ph1, pw1 = new_size - size - ph0, new_size - size - pw0 + new_image = np.pad( + new_image, + ((ph0, ph1), (pw0, pw1), (0, 0)), + mode="constant", + constant_values=((0, 0), (0, 0), (0, 0)), + ) + new_image = PIL.Image.fromarray(new_image) + return new_image + + +def save_video( + frames: List[PIL.Image.Image], + output_path: str, + fps: int = 30, +): + # use imageio to save video + frames = [np.array(frame) for frame in frames] + writer = imageio.get_writer(output_path, fps=fps) + for frame in frames: + writer.append_data(frame) + writer.close() + + +def to_gradio_3d_orientation(mesh): + mesh.apply_transform(trimesh.transformations.rotation_matrix(-np.pi/2, [1, 0, 0])) + mesh.apply_scale([1, 1, -1]) + mesh.apply_transform(trimesh.transformations.rotation_matrix(np.pi/2, [0, 1, 0])) + return mesh