mirror of
https://github.com/deepseek-ai/DreamCraft3D
synced 2025-06-26 18:25:49 +00:00
chores: rebase commits
This commit is contained in:
278
threestudio/utils/GAN/attention.py
Normal file
278
threestudio/utils/GAN/attention.py
Normal file
@@ -0,0 +1,278 @@
|
||||
import math
|
||||
from inspect import isfunction
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from einops import rearrange, repeat
|
||||
from torch import einsum, nn
|
||||
|
||||
from threestudio.utils.GAN.network_util import checkpoint
|
||||
|
||||
|
||||
def exists(val):
|
||||
return val is not None
|
||||
|
||||
|
||||
def uniq(arr):
|
||||
return {el: True for el in arr}.keys()
|
||||
|
||||
|
||||
def default(val, d):
|
||||
if exists(val):
|
||||
return val
|
||||
return d() if isfunction(d) else d
|
||||
|
||||
|
||||
def max_neg_value(t):
|
||||
return -torch.finfo(t.dtype).max
|
||||
|
||||
|
||||
def init_(tensor):
|
||||
dim = tensor.shape[-1]
|
||||
std = 1 / math.sqrt(dim)
|
||||
tensor.uniform_(-std, std)
|
||||
return tensor
|
||||
|
||||
|
||||
# feedforward
|
||||
class GEGLU(nn.Module):
|
||||
def __init__(self, dim_in, dim_out):
|
||||
super().__init__()
|
||||
self.proj = nn.Linear(dim_in, dim_out * 2)
|
||||
|
||||
def forward(self, x):
|
||||
x, gate = self.proj(x).chunk(2, dim=-1)
|
||||
return x * F.gelu(gate)
|
||||
|
||||
|
||||
class FeedForward(nn.Module):
|
||||
def __init__(self, dim, dim_out=None, mult=4, glu=False, dropout=0.0):
|
||||
super().__init__()
|
||||
inner_dim = int(dim * mult)
|
||||
dim_out = default(dim_out, dim)
|
||||
project_in = (
|
||||
nn.Sequential(nn.Linear(dim, inner_dim), nn.GELU())
|
||||
if not glu
|
||||
else GEGLU(dim, inner_dim)
|
||||
)
|
||||
|
||||
self.net = nn.Sequential(
|
||||
project_in, nn.Dropout(dropout), nn.Linear(inner_dim, dim_out)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.net(x)
|
||||
|
||||
|
||||
def zero_module(module):
|
||||
"""
|
||||
Zero out the parameters of a module and return it.
|
||||
"""
|
||||
for p in module.parameters():
|
||||
p.detach().zero_()
|
||||
return module
|
||||
|
||||
|
||||
def Normalize(in_channels):
|
||||
return torch.nn.GroupNorm(
|
||||
num_groups=32, num_channels=in_channels, eps=1e-6, affine=True
|
||||
)
|
||||
|
||||
|
||||
class LinearAttention(nn.Module):
|
||||
def __init__(self, dim, heads=4, dim_head=32):
|
||||
super().__init__()
|
||||
self.heads = heads
|
||||
hidden_dim = dim_head * heads
|
||||
self.to_qkv = nn.Conv2d(dim, hidden_dim * 3, 1, bias=False)
|
||||
self.to_out = nn.Conv2d(hidden_dim, dim, 1)
|
||||
|
||||
def forward(self, x):
|
||||
b, c, h, w = x.shape
|
||||
qkv = self.to_qkv(x)
|
||||
q, k, v = rearrange(
|
||||
qkv, "b (qkv heads c) h w -> qkv b heads c (h w)", heads=self.heads, qkv=3
|
||||
)
|
||||
k = k.softmax(dim=-1)
|
||||
context = torch.einsum("bhdn,bhen->bhde", k, v)
|
||||
out = torch.einsum("bhde,bhdn->bhen", context, q)
|
||||
out = rearrange(
|
||||
out, "b heads c (h w) -> b (heads c) h w", heads=self.heads, h=h, w=w
|
||||
)
|
||||
return self.to_out(out)
|
||||
|
||||
|
||||
class SpatialSelfAttention(nn.Module):
|
||||
def __init__(self, in_channels):
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
|
||||
self.norm = Normalize(in_channels)
|
||||
self.q = torch.nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
self.k = torch.nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
self.v = torch.nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
self.proj_out = torch.nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
h_ = x
|
||||
h_ = self.norm(h_)
|
||||
q = self.q(h_)
|
||||
k = self.k(h_)
|
||||
v = self.v(h_)
|
||||
|
||||
# compute attention
|
||||
b, c, h, w = q.shape
|
||||
q = rearrange(q, "b c h w -> b (h w) c")
|
||||
k = rearrange(k, "b c h w -> b c (h w)")
|
||||
w_ = torch.einsum("bij,bjk->bik", q, k)
|
||||
|
||||
w_ = w_ * (int(c) ** (-0.5))
|
||||
w_ = torch.nn.functional.softmax(w_, dim=2)
|
||||
|
||||
# attend to values
|
||||
v = rearrange(v, "b c h w -> b c (h w)")
|
||||
w_ = rearrange(w_, "b i j -> b j i")
|
||||
h_ = torch.einsum("bij,bjk->bik", v, w_)
|
||||
h_ = rearrange(h_, "b c (h w) -> b c h w", h=h)
|
||||
h_ = self.proj_out(h_)
|
||||
|
||||
return x + h_
|
||||
|
||||
|
||||
class CrossAttention(nn.Module):
|
||||
def __init__(self, query_dim, context_dim=None, heads=8, dim_head=64, dropout=0.0):
|
||||
super().__init__()
|
||||
inner_dim = dim_head * heads
|
||||
context_dim = default(context_dim, query_dim)
|
||||
|
||||
self.scale = dim_head**-0.5
|
||||
self.heads = heads
|
||||
|
||||
self.to_q = nn.Linear(query_dim, inner_dim, bias=False)
|
||||
self.to_k = nn.Linear(context_dim, inner_dim, bias=False)
|
||||
self.to_v = nn.Linear(context_dim, inner_dim, bias=False)
|
||||
|
||||
self.to_out = nn.Sequential(
|
||||
nn.Linear(inner_dim, query_dim), nn.Dropout(dropout)
|
||||
)
|
||||
|
||||
def forward(self, x, context=None, mask=None):
|
||||
h = self.heads
|
||||
|
||||
q = self.to_q(x)
|
||||
context = default(context, x)
|
||||
k = self.to_k(context)
|
||||
v = self.to_v(context)
|
||||
|
||||
q, k, v = map(lambda t: rearrange(t, "b n (h d) -> (b h) n d", h=h), (q, k, v))
|
||||
|
||||
sim = einsum("b i d, b j d -> b i j", q, k) * self.scale
|
||||
|
||||
if exists(mask):
|
||||
mask = rearrange(mask, "b ... -> b (...)")
|
||||
max_neg_value = -torch.finfo(sim.dtype).max
|
||||
mask = repeat(mask, "b j -> (b h) () j", h=h)
|
||||
sim.masked_fill_(~mask, max_neg_value)
|
||||
|
||||
# attention, what we cannot get enough of
|
||||
attn = sim.softmax(dim=-1)
|
||||
|
||||
out = einsum("b i j, b j d -> b i d", attn, v)
|
||||
out = rearrange(out, "(b h) n d -> b n (h d)", h=h)
|
||||
return self.to_out(out)
|
||||
|
||||
|
||||
class BasicTransformerBlock(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim,
|
||||
n_heads,
|
||||
d_head,
|
||||
dropout=0.0,
|
||||
context_dim=None,
|
||||
gated_ff=True,
|
||||
checkpoint=True,
|
||||
):
|
||||
super().__init__()
|
||||
self.attn1 = CrossAttention(
|
||||
query_dim=dim, heads=n_heads, dim_head=d_head, dropout=dropout
|
||||
) # is a self-attention
|
||||
self.ff = FeedForward(dim, dropout=dropout, glu=gated_ff)
|
||||
self.attn2 = CrossAttention(
|
||||
query_dim=dim,
|
||||
context_dim=context_dim,
|
||||
heads=n_heads,
|
||||
dim_head=d_head,
|
||||
dropout=dropout,
|
||||
) # is self-attn if context is none
|
||||
self.norm1 = nn.LayerNorm(dim)
|
||||
self.norm2 = nn.LayerNorm(dim)
|
||||
self.norm3 = nn.LayerNorm(dim)
|
||||
self.checkpoint = checkpoint
|
||||
|
||||
def forward(self, x, context=None):
|
||||
return checkpoint(
|
||||
self._forward, (x, context), self.parameters(), self.checkpoint
|
||||
)
|
||||
|
||||
def _forward(self, x, context=None):
|
||||
x = self.attn1(self.norm1(x)) + x
|
||||
x = self.attn2(self.norm2(x), context=context) + x
|
||||
x = self.ff(self.norm3(x)) + x
|
||||
return x
|
||||
|
||||
|
||||
class SpatialTransformer(nn.Module):
|
||||
"""
|
||||
Transformer block for image-like data.
|
||||
First, project the input (aka embedding)
|
||||
and reshape to b, t, d.
|
||||
Then apply standard transformer action.
|
||||
Finally, reshape to image
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, in_channels, n_heads, d_head, depth=1, dropout=0.0, context_dim=None
|
||||
):
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
inner_dim = n_heads * d_head
|
||||
self.norm = Normalize(in_channels)
|
||||
|
||||
self.proj_in = nn.Conv2d(
|
||||
in_channels, inner_dim, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
|
||||
self.transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock(
|
||||
inner_dim, n_heads, d_head, dropout=dropout, context_dim=context_dim
|
||||
)
|
||||
for d in range(depth)
|
||||
]
|
||||
)
|
||||
|
||||
self.proj_out = zero_module(
|
||||
nn.Conv2d(inner_dim, in_channels, kernel_size=1, stride=1, padding=0)
|
||||
)
|
||||
|
||||
def forward(self, x, context=None):
|
||||
# note: if no context is given, cross-attention defaults to self-attention
|
||||
b, c, h, w = x.shape
|
||||
x_in = x
|
||||
x = self.norm(x)
|
||||
x = self.proj_in(x)
|
||||
x = rearrange(x, "b c h w -> b (h w) c")
|
||||
for block in self.transformer_blocks:
|
||||
x = block(x, context=context)
|
||||
x = rearrange(x, "b (h w) c -> b c h w", h=h, w=w)
|
||||
x = self.proj_out(x)
|
||||
return x + x_in
|
||||
217
threestudio/utils/GAN/discriminator.py
Normal file
217
threestudio/utils/GAN/discriminator.py
Normal file
@@ -0,0 +1,217 @@
|
||||
import functools
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
def count_params(model):
|
||||
total_params = sum(p.numel() for p in model.parameters())
|
||||
return total_params
|
||||
|
||||
|
||||
class ActNorm(nn.Module):
|
||||
def __init__(
|
||||
self, num_features, logdet=False, affine=True, allow_reverse_init=False
|
||||
):
|
||||
assert affine
|
||||
super().__init__()
|
||||
self.logdet = logdet
|
||||
self.loc = nn.Parameter(torch.zeros(1, num_features, 1, 1))
|
||||
self.scale = nn.Parameter(torch.ones(1, num_features, 1, 1))
|
||||
self.allow_reverse_init = allow_reverse_init
|
||||
|
||||
self.register_buffer("initialized", torch.tensor(0, dtype=torch.uint8))
|
||||
|
||||
def initialize(self, input):
|
||||
with torch.no_grad():
|
||||
flatten = input.permute(1, 0, 2, 3).contiguous().view(input.shape[1], -1)
|
||||
mean = (
|
||||
flatten.mean(1)
|
||||
.unsqueeze(1)
|
||||
.unsqueeze(2)
|
||||
.unsqueeze(3)
|
||||
.permute(1, 0, 2, 3)
|
||||
)
|
||||
std = (
|
||||
flatten.std(1)
|
||||
.unsqueeze(1)
|
||||
.unsqueeze(2)
|
||||
.unsqueeze(3)
|
||||
.permute(1, 0, 2, 3)
|
||||
)
|
||||
|
||||
self.loc.data.copy_(-mean)
|
||||
self.scale.data.copy_(1 / (std + 1e-6))
|
||||
|
||||
def forward(self, input, reverse=False):
|
||||
if reverse:
|
||||
return self.reverse(input)
|
||||
if len(input.shape) == 2:
|
||||
input = input[:, :, None, None]
|
||||
squeeze = True
|
||||
else:
|
||||
squeeze = False
|
||||
|
||||
_, _, height, width = input.shape
|
||||
|
||||
if self.training and self.initialized.item() == 0:
|
||||
self.initialize(input)
|
||||
self.initialized.fill_(1)
|
||||
|
||||
h = self.scale * (input + self.loc)
|
||||
|
||||
if squeeze:
|
||||
h = h.squeeze(-1).squeeze(-1)
|
||||
|
||||
if self.logdet:
|
||||
log_abs = torch.log(torch.abs(self.scale))
|
||||
logdet = height * width * torch.sum(log_abs)
|
||||
logdet = logdet * torch.ones(input.shape[0]).to(input)
|
||||
return h, logdet
|
||||
|
||||
return h
|
||||
|
||||
def reverse(self, output):
|
||||
if self.training and self.initialized.item() == 0:
|
||||
if not self.allow_reverse_init:
|
||||
raise RuntimeError(
|
||||
"Initializing ActNorm in reverse direction is "
|
||||
"disabled by default. Use allow_reverse_init=True to enable."
|
||||
)
|
||||
else:
|
||||
self.initialize(output)
|
||||
self.initialized.fill_(1)
|
||||
|
||||
if len(output.shape) == 2:
|
||||
output = output[:, :, None, None]
|
||||
squeeze = True
|
||||
else:
|
||||
squeeze = False
|
||||
|
||||
h = output / self.scale - self.loc
|
||||
|
||||
if squeeze:
|
||||
h = h.squeeze(-1).squeeze(-1)
|
||||
return h
|
||||
|
||||
|
||||
class AbstractEncoder(nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def encode(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Labelator(AbstractEncoder):
|
||||
"""Net2Net Interface for Class-Conditional Model"""
|
||||
|
||||
def __init__(self, n_classes, quantize_interface=True):
|
||||
super().__init__()
|
||||
self.n_classes = n_classes
|
||||
self.quantize_interface = quantize_interface
|
||||
|
||||
def encode(self, c):
|
||||
c = c[:, None]
|
||||
if self.quantize_interface:
|
||||
return c, None, [None, None, c.long()]
|
||||
return c
|
||||
|
||||
|
||||
class SOSProvider(AbstractEncoder):
|
||||
# for unconditional training
|
||||
def __init__(self, sos_token, quantize_interface=True):
|
||||
super().__init__()
|
||||
self.sos_token = sos_token
|
||||
self.quantize_interface = quantize_interface
|
||||
|
||||
def encode(self, x):
|
||||
# get batch size from data and replicate sos_token
|
||||
c = torch.ones(x.shape[0], 1) * self.sos_token
|
||||
c = c.long().to(x.device)
|
||||
if self.quantize_interface:
|
||||
return c, None, [None, None, c]
|
||||
return c
|
||||
|
||||
|
||||
def weights_init(m):
|
||||
classname = m.__class__.__name__
|
||||
if classname.find("Conv") != -1:
|
||||
nn.init.normal_(m.weight.data, 0.0, 0.02)
|
||||
elif classname.find("BatchNorm") != -1:
|
||||
nn.init.normal_(m.weight.data, 1.0, 0.02)
|
||||
nn.init.constant_(m.bias.data, 0)
|
||||
|
||||
|
||||
class NLayerDiscriminator(nn.Module):
|
||||
"""Defines a PatchGAN discriminator as in Pix2Pix
|
||||
--> see https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/networks.py
|
||||
"""
|
||||
|
||||
def __init__(self, input_nc=3, ndf=64, n_layers=3, use_actnorm=False):
|
||||
"""Construct a PatchGAN discriminator
|
||||
Parameters:
|
||||
input_nc (int) -- the number of channels in input images
|
||||
ndf (int) -- the number of filters in the last conv layer
|
||||
n_layers (int) -- the number of conv layers in the discriminator
|
||||
norm_layer -- normalization layer
|
||||
"""
|
||||
super(NLayerDiscriminator, self).__init__()
|
||||
if not use_actnorm:
|
||||
norm_layer = nn.BatchNorm2d
|
||||
else:
|
||||
norm_layer = ActNorm
|
||||
if (
|
||||
type(norm_layer) == functools.partial
|
||||
): # no need to use bias as BatchNorm2d has affine parameters
|
||||
use_bias = norm_layer.func != nn.BatchNorm2d
|
||||
else:
|
||||
use_bias = norm_layer != nn.BatchNorm2d
|
||||
|
||||
kw = 4
|
||||
padw = 1
|
||||
sequence = [
|
||||
nn.Conv2d(input_nc, ndf, kernel_size=kw, stride=2, padding=padw),
|
||||
nn.LeakyReLU(0.2, True),
|
||||
]
|
||||
nf_mult = 1
|
||||
nf_mult_prev = 1
|
||||
for n in range(1, n_layers): # gradually increase the number of filters
|
||||
nf_mult_prev = nf_mult
|
||||
nf_mult = min(2**n, 8)
|
||||
sequence += [
|
||||
nn.Conv2d(
|
||||
ndf * nf_mult_prev,
|
||||
ndf * nf_mult,
|
||||
kernel_size=kw,
|
||||
stride=2,
|
||||
padding=padw,
|
||||
bias=use_bias,
|
||||
),
|
||||
norm_layer(ndf * nf_mult),
|
||||
nn.LeakyReLU(0.2, True),
|
||||
]
|
||||
|
||||
nf_mult_prev = nf_mult
|
||||
nf_mult = min(2**n_layers, 8)
|
||||
sequence += [
|
||||
nn.Conv2d(
|
||||
ndf * nf_mult_prev,
|
||||
ndf * nf_mult,
|
||||
kernel_size=kw,
|
||||
stride=1,
|
||||
padding=padw,
|
||||
bias=use_bias,
|
||||
),
|
||||
norm_layer(ndf * nf_mult),
|
||||
nn.LeakyReLU(0.2, True),
|
||||
]
|
||||
|
||||
sequence += [
|
||||
nn.Conv2d(ndf * nf_mult, 1, kernel_size=kw, stride=1, padding=padw)
|
||||
] # output 1 channel prediction map
|
||||
self.main = nn.Sequential(*sequence)
|
||||
|
||||
def forward(self, input):
|
||||
"""Standard forward."""
|
||||
return self.main(input)
|
||||
102
threestudio/utils/GAN/distribution.py
Normal file
102
threestudio/utils/GAN/distribution.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
class AbstractDistribution:
|
||||
def sample(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def mode(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class DiracDistribution(AbstractDistribution):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def sample(self):
|
||||
return self.value
|
||||
|
||||
def mode(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class DiagonalGaussianDistribution(object):
|
||||
def __init__(self, parameters, deterministic=False):
|
||||
self.parameters = parameters
|
||||
self.mean, self.logvar = torch.chunk(parameters, 2, dim=1)
|
||||
self.logvar = torch.clamp(self.logvar, -30.0, 20.0)
|
||||
self.deterministic = deterministic
|
||||
self.std = torch.exp(0.5 * self.logvar)
|
||||
self.var = torch.exp(self.logvar)
|
||||
if self.deterministic:
|
||||
self.var = self.std = torch.zeros_like(self.mean).to(
|
||||
device=self.parameters.device
|
||||
)
|
||||
|
||||
def sample(self):
|
||||
x = self.mean + self.std * torch.randn(self.mean.shape).to(
|
||||
device=self.parameters.device
|
||||
)
|
||||
return x
|
||||
|
||||
def kl(self, other=None):
|
||||
if self.deterministic:
|
||||
return torch.Tensor([0.0])
|
||||
else:
|
||||
if other is None:
|
||||
return 0.5 * torch.sum(
|
||||
torch.pow(self.mean, 2) + self.var - 1.0 - self.logvar,
|
||||
dim=[1, 2, 3],
|
||||
)
|
||||
else:
|
||||
return 0.5 * torch.sum(
|
||||
torch.pow(self.mean - other.mean, 2) / other.var
|
||||
+ self.var / other.var
|
||||
- 1.0
|
||||
- self.logvar
|
||||
+ other.logvar,
|
||||
dim=[1, 2, 3],
|
||||
)
|
||||
|
||||
def nll(self, sample, dims=[1, 2, 3]):
|
||||
if self.deterministic:
|
||||
return torch.Tensor([0.0])
|
||||
logtwopi = np.log(2.0 * np.pi)
|
||||
return 0.5 * torch.sum(
|
||||
logtwopi + self.logvar + torch.pow(sample - self.mean, 2) / self.var,
|
||||
dim=dims,
|
||||
)
|
||||
|
||||
def mode(self):
|
||||
return self.mean
|
||||
|
||||
|
||||
def normal_kl(mean1, logvar1, mean2, logvar2):
|
||||
"""
|
||||
source: https://github.com/openai/guided-diffusion/blob/27c20a8fab9cb472df5d6bdd6c8d11c8f430b924/guided_diffusion/losses.py#L12
|
||||
Compute the KL divergence between two gaussians.
|
||||
Shapes are automatically broadcasted, so batches can be compared to
|
||||
scalars, among other use cases.
|
||||
"""
|
||||
tensor = None
|
||||
for obj in (mean1, logvar1, mean2, logvar2):
|
||||
if isinstance(obj, torch.Tensor):
|
||||
tensor = obj
|
||||
break
|
||||
assert tensor is not None, "at least one argument must be a Tensor"
|
||||
|
||||
# Force variances to be Tensors. Broadcasting helps convert scalars to
|
||||
# Tensors, but it does not work for torch.exp().
|
||||
logvar1, logvar2 = [
|
||||
x if isinstance(x, torch.Tensor) else torch.tensor(x).to(tensor)
|
||||
for x in (logvar1, logvar2)
|
||||
]
|
||||
|
||||
return 0.5 * (
|
||||
-1.0
|
||||
+ logvar2
|
||||
- logvar1
|
||||
+ torch.exp(logvar1 - logvar2)
|
||||
+ ((mean1 - mean2) ** 2) * torch.exp(-logvar2)
|
||||
)
|
||||
35
threestudio/utils/GAN/loss.py
Normal file
35
threestudio/utils/GAN/loss.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
def generator_loss(discriminator, inputs, reconstructions, cond=None):
|
||||
if cond is None:
|
||||
logits_fake = discriminator(reconstructions.contiguous())
|
||||
else:
|
||||
logits_fake = discriminator(
|
||||
torch.cat((reconstructions.contiguous(), cond), dim=1)
|
||||
)
|
||||
g_loss = -torch.mean(logits_fake)
|
||||
return g_loss
|
||||
|
||||
|
||||
def hinge_d_loss(logits_real, logits_fake):
|
||||
loss_real = torch.mean(F.relu(1.0 - logits_real))
|
||||
loss_fake = torch.mean(F.relu(1.0 + logits_fake))
|
||||
d_loss = 0.5 * (loss_real + loss_fake)
|
||||
return d_loss
|
||||
|
||||
|
||||
def discriminator_loss(discriminator, inputs, reconstructions, cond=None):
|
||||
if cond is None:
|
||||
logits_real = discriminator(inputs.contiguous().detach())
|
||||
logits_fake = discriminator(reconstructions.contiguous().detach())
|
||||
else:
|
||||
logits_real = discriminator(
|
||||
torch.cat((inputs.contiguous().detach(), cond), dim=1)
|
||||
)
|
||||
logits_fake = discriminator(
|
||||
torch.cat((reconstructions.contiguous().detach(), cond), dim=1)
|
||||
)
|
||||
d_loss = hinge_d_loss(logits_real, logits_fake).mean()
|
||||
return d_loss
|
||||
254
threestudio/utils/GAN/mobilenet.py
Normal file
254
threestudio/utils/GAN/mobilenet.py
Normal file
@@ -0,0 +1,254 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
__all__ = ["MobileNetV3", "mobilenetv3"]
|
||||
|
||||
|
||||
def conv_bn(
|
||||
inp,
|
||||
oup,
|
||||
stride,
|
||||
conv_layer=nn.Conv2d,
|
||||
norm_layer=nn.BatchNorm2d,
|
||||
nlin_layer=nn.ReLU,
|
||||
):
|
||||
return nn.Sequential(
|
||||
conv_layer(inp, oup, 3, stride, 1, bias=False),
|
||||
norm_layer(oup),
|
||||
nlin_layer(inplace=True),
|
||||
)
|
||||
|
||||
|
||||
def conv_1x1_bn(
|
||||
inp, oup, conv_layer=nn.Conv2d, norm_layer=nn.BatchNorm2d, nlin_layer=nn.ReLU
|
||||
):
|
||||
return nn.Sequential(
|
||||
conv_layer(inp, oup, 1, 1, 0, bias=False),
|
||||
norm_layer(oup),
|
||||
nlin_layer(inplace=True),
|
||||
)
|
||||
|
||||
|
||||
class Hswish(nn.Module):
|
||||
def __init__(self, inplace=True):
|
||||
super(Hswish, self).__init__()
|
||||
self.inplace = inplace
|
||||
|
||||
def forward(self, x):
|
||||
return x * F.relu6(x + 3.0, inplace=self.inplace) / 6.0
|
||||
|
||||
|
||||
class Hsigmoid(nn.Module):
|
||||
def __init__(self, inplace=True):
|
||||
super(Hsigmoid, self).__init__()
|
||||
self.inplace = inplace
|
||||
|
||||
def forward(self, x):
|
||||
return F.relu6(x + 3.0, inplace=self.inplace) / 6.0
|
||||
|
||||
|
||||
class SEModule(nn.Module):
|
||||
def __init__(self, channel, reduction=4):
|
||||
super(SEModule, self).__init__()
|
||||
self.avg_pool = nn.AdaptiveAvgPool2d(1)
|
||||
self.fc = nn.Sequential(
|
||||
nn.Linear(channel, channel // reduction, bias=False),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Linear(channel // reduction, channel, bias=False),
|
||||
Hsigmoid()
|
||||
# nn.Sigmoid()
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
b, c, _, _ = x.size()
|
||||
y = self.avg_pool(x).view(b, c)
|
||||
y = self.fc(y).view(b, c, 1, 1)
|
||||
return x * y.expand_as(x)
|
||||
|
||||
|
||||
class Identity(nn.Module):
|
||||
def __init__(self, channel):
|
||||
super(Identity, self).__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x
|
||||
|
||||
|
||||
def make_divisible(x, divisible_by=8):
|
||||
import numpy as np
|
||||
|
||||
return int(np.ceil(x * 1.0 / divisible_by) * divisible_by)
|
||||
|
||||
|
||||
class MobileBottleneck(nn.Module):
|
||||
def __init__(self, inp, oup, kernel, stride, exp, se=False, nl="RE"):
|
||||
super(MobileBottleneck, self).__init__()
|
||||
assert stride in [1, 2]
|
||||
assert kernel in [3, 5]
|
||||
padding = (kernel - 1) // 2
|
||||
self.use_res_connect = stride == 1 and inp == oup
|
||||
|
||||
conv_layer = nn.Conv2d
|
||||
norm_layer = nn.BatchNorm2d
|
||||
if nl == "RE":
|
||||
nlin_layer = nn.ReLU # or ReLU6
|
||||
elif nl == "HS":
|
||||
nlin_layer = Hswish
|
||||
else:
|
||||
raise NotImplementedError
|
||||
if se:
|
||||
SELayer = SEModule
|
||||
else:
|
||||
SELayer = Identity
|
||||
|
||||
self.conv = nn.Sequential(
|
||||
# pw
|
||||
conv_layer(inp, exp, 1, 1, 0, bias=False),
|
||||
norm_layer(exp),
|
||||
nlin_layer(inplace=True),
|
||||
# dw
|
||||
conv_layer(exp, exp, kernel, stride, padding, groups=exp, bias=False),
|
||||
norm_layer(exp),
|
||||
SELayer(exp),
|
||||
nlin_layer(inplace=True),
|
||||
# pw-linear
|
||||
conv_layer(exp, oup, 1, 1, 0, bias=False),
|
||||
norm_layer(oup),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
if self.use_res_connect:
|
||||
return x + self.conv(x)
|
||||
else:
|
||||
return self.conv(x)
|
||||
|
||||
|
||||
class MobileNetV3(nn.Module):
|
||||
def __init__(
|
||||
self, n_class=1000, input_size=224, dropout=0.0, mode="small", width_mult=1.0
|
||||
):
|
||||
super(MobileNetV3, self).__init__()
|
||||
input_channel = 16
|
||||
last_channel = 1280
|
||||
if mode == "large":
|
||||
# refer to Table 1 in paper
|
||||
mobile_setting = [
|
||||
# k, exp, c, se, nl, s,
|
||||
[3, 16, 16, False, "RE", 1],
|
||||
[3, 64, 24, False, "RE", 2],
|
||||
[3, 72, 24, False, "RE", 1],
|
||||
[5, 72, 40, True, "RE", 2],
|
||||
[5, 120, 40, True, "RE", 1],
|
||||
[5, 120, 40, True, "RE", 1],
|
||||
[3, 240, 80, False, "HS", 2],
|
||||
[3, 200, 80, False, "HS", 1],
|
||||
[3, 184, 80, False, "HS", 1],
|
||||
[3, 184, 80, False, "HS", 1],
|
||||
[3, 480, 112, True, "HS", 1],
|
||||
[3, 672, 112, True, "HS", 1],
|
||||
[5, 672, 160, True, "HS", 2],
|
||||
[5, 960, 160, True, "HS", 1],
|
||||
[5, 960, 160, True, "HS", 1],
|
||||
]
|
||||
elif mode == "small":
|
||||
# refer to Table 2 in paper
|
||||
mobile_setting = [
|
||||
# k, exp, c, se, nl, s,
|
||||
[3, 16, 16, True, "RE", 2],
|
||||
[3, 72, 24, False, "RE", 2],
|
||||
[3, 88, 24, False, "RE", 1],
|
||||
[5, 96, 40, True, "HS", 2],
|
||||
[5, 240, 40, True, "HS", 1],
|
||||
[5, 240, 40, True, "HS", 1],
|
||||
[5, 120, 48, True, "HS", 1],
|
||||
[5, 144, 48, True, "HS", 1],
|
||||
[5, 288, 96, True, "HS", 2],
|
||||
[5, 576, 96, True, "HS", 1],
|
||||
[5, 576, 96, True, "HS", 1],
|
||||
]
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
# building first layer
|
||||
assert input_size % 32 == 0
|
||||
last_channel = (
|
||||
make_divisible(last_channel * width_mult)
|
||||
if width_mult > 1.0
|
||||
else last_channel
|
||||
)
|
||||
self.features = [conv_bn(3, input_channel, 2, nlin_layer=Hswish)]
|
||||
self.classifier = []
|
||||
|
||||
# building mobile blocks
|
||||
for k, exp, c, se, nl, s in mobile_setting:
|
||||
output_channel = make_divisible(c * width_mult)
|
||||
exp_channel = make_divisible(exp * width_mult)
|
||||
self.features.append(
|
||||
MobileBottleneck(
|
||||
input_channel, output_channel, k, s, exp_channel, se, nl
|
||||
)
|
||||
)
|
||||
input_channel = output_channel
|
||||
|
||||
# building last several layers
|
||||
if mode == "large":
|
||||
last_conv = make_divisible(960 * width_mult)
|
||||
self.features.append(
|
||||
conv_1x1_bn(input_channel, last_conv, nlin_layer=Hswish)
|
||||
)
|
||||
self.features.append(nn.AdaptiveAvgPool2d(1))
|
||||
self.features.append(nn.Conv2d(last_conv, last_channel, 1, 1, 0))
|
||||
self.features.append(Hswish(inplace=True))
|
||||
elif mode == "small":
|
||||
last_conv = make_divisible(576 * width_mult)
|
||||
self.features.append(
|
||||
conv_1x1_bn(input_channel, last_conv, nlin_layer=Hswish)
|
||||
)
|
||||
# self.features.append(SEModule(last_conv)) # refer to paper Table2, but I think this is a mistake
|
||||
self.features.append(nn.AdaptiveAvgPool2d(1))
|
||||
self.features.append(nn.Conv2d(last_conv, last_channel, 1, 1, 0))
|
||||
self.features.append(Hswish(inplace=True))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
# make it nn.Sequential
|
||||
self.features = nn.Sequential(*self.features)
|
||||
|
||||
# building classifier
|
||||
self.classifier = nn.Sequential(
|
||||
nn.Dropout(p=dropout), # refer to paper section 6
|
||||
nn.Linear(last_channel, n_class),
|
||||
)
|
||||
|
||||
self._initialize_weights()
|
||||
|
||||
def forward(self, x):
|
||||
x = self.features(x)
|
||||
x = x.mean(3).mean(2)
|
||||
x = self.classifier(x)
|
||||
return x
|
||||
|
||||
def _initialize_weights(self):
|
||||
# weight initialization
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
nn.init.kaiming_normal_(m.weight, mode="fan_out")
|
||||
if m.bias is not None:
|
||||
nn.init.zeros_(m.bias)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
nn.init.ones_(m.weight)
|
||||
nn.init.zeros_(m.bias)
|
||||
elif isinstance(m, nn.Linear):
|
||||
nn.init.normal_(m.weight, 0, 0.01)
|
||||
if m.bias is not None:
|
||||
nn.init.zeros_(m.bias)
|
||||
|
||||
|
||||
def mobilenetv3(pretrained=False, **kwargs):
|
||||
model = MobileNetV3(**kwargs)
|
||||
if pretrained:
|
||||
state_dict = torch.load("mobilenetv3_small_67.4.pth.tar")
|
||||
model.load_state_dict(state_dict, strict=True)
|
||||
# raise NotImplementedError
|
||||
return model
|
||||
296
threestudio/utils/GAN/network_util.py
Normal file
296
threestudio/utils/GAN/network_util.py
Normal file
@@ -0,0 +1,296 @@
|
||||
# adopted from
|
||||
# https://github.com/openai/improved-diffusion/blob/main/improved_diffusion/gaussian_diffusion.py
|
||||
# and
|
||||
# https://github.com/lucidrains/denoising-diffusion-pytorch/blob/7706bdfc6f527f58d33f84b7b522e61e6e3164b3/denoising_diffusion_pytorch/denoising_diffusion_pytorch.py
|
||||
# and
|
||||
# https://github.com/openai/guided-diffusion/blob/0ba878e517b276c45d1195eb29f6f5f72659a05b/guided_diffusion/nn.py
|
||||
#
|
||||
# thanks!
|
||||
|
||||
|
||||
import math
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from einops import repeat
|
||||
|
||||
from threestudio.utils.GAN.util import instantiate_from_config
|
||||
|
||||
|
||||
def make_beta_schedule(
|
||||
schedule, n_timestep, linear_start=1e-4, linear_end=2e-2, cosine_s=8e-3
|
||||
):
|
||||
if schedule == "linear":
|
||||
betas = (
|
||||
torch.linspace(
|
||||
linear_start**0.5, linear_end**0.5, n_timestep, dtype=torch.float64
|
||||
)
|
||||
** 2
|
||||
)
|
||||
|
||||
elif schedule == "cosine":
|
||||
timesteps = (
|
||||
torch.arange(n_timestep + 1, dtype=torch.float64) / n_timestep + cosine_s
|
||||
)
|
||||
alphas = timesteps / (1 + cosine_s) * np.pi / 2
|
||||
alphas = torch.cos(alphas).pow(2)
|
||||
alphas = alphas / alphas[0]
|
||||
betas = 1 - alphas[1:] / alphas[:-1]
|
||||
betas = np.clip(betas, a_min=0, a_max=0.999)
|
||||
|
||||
elif schedule == "sqrt_linear":
|
||||
betas = torch.linspace(
|
||||
linear_start, linear_end, n_timestep, dtype=torch.float64
|
||||
)
|
||||
elif schedule == "sqrt":
|
||||
betas = (
|
||||
torch.linspace(linear_start, linear_end, n_timestep, dtype=torch.float64)
|
||||
** 0.5
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"schedule '{schedule}' unknown.")
|
||||
return betas.numpy()
|
||||
|
||||
|
||||
def make_ddim_timesteps(
|
||||
ddim_discr_method, num_ddim_timesteps, num_ddpm_timesteps, verbose=True
|
||||
):
|
||||
if ddim_discr_method == "uniform":
|
||||
c = num_ddpm_timesteps // num_ddim_timesteps
|
||||
ddim_timesteps = np.asarray(list(range(0, num_ddpm_timesteps, c)))
|
||||
elif ddim_discr_method == "quad":
|
||||
ddim_timesteps = (
|
||||
(np.linspace(0, np.sqrt(num_ddpm_timesteps * 0.8), num_ddim_timesteps)) ** 2
|
||||
).astype(int)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'There is no ddim discretization method called "{ddim_discr_method}"'
|
||||
)
|
||||
|
||||
# assert ddim_timesteps.shape[0] == num_ddim_timesteps
|
||||
# add one to get the final alpha values right (the ones from first scale to data during sampling)
|
||||
steps_out = ddim_timesteps + 1
|
||||
if verbose:
|
||||
print(f"Selected timesteps for ddim sampler: {steps_out}")
|
||||
return steps_out
|
||||
|
||||
|
||||
def make_ddim_sampling_parameters(alphacums, ddim_timesteps, eta, verbose=True):
|
||||
# select alphas for computing the variance schedule
|
||||
alphas = alphacums[ddim_timesteps]
|
||||
alphas_prev = np.asarray([alphacums[0]] + alphacums[ddim_timesteps[:-1]].tolist())
|
||||
|
||||
# according the the formula provided in https://arxiv.org/abs/2010.02502
|
||||
sigmas = eta * np.sqrt(
|
||||
(1 - alphas_prev) / (1 - alphas) * (1 - alphas / alphas_prev)
|
||||
)
|
||||
if verbose:
|
||||
print(
|
||||
f"Selected alphas for ddim sampler: a_t: {alphas}; a_(t-1): {alphas_prev}"
|
||||
)
|
||||
print(
|
||||
f"For the chosen value of eta, which is {eta}, "
|
||||
f"this results in the following sigma_t schedule for ddim sampler {sigmas}"
|
||||
)
|
||||
return sigmas, alphas, alphas_prev
|
||||
|
||||
|
||||
def betas_for_alpha_bar(num_diffusion_timesteps, alpha_bar, max_beta=0.999):
|
||||
"""
|
||||
Create a beta schedule that discretizes the given alpha_t_bar function,
|
||||
which defines the cumulative product of (1-beta) over time from t = [0,1].
|
||||
:param num_diffusion_timesteps: the number of betas to produce.
|
||||
:param alpha_bar: a lambda that takes an argument t from 0 to 1 and
|
||||
produces the cumulative product of (1-beta) up to that
|
||||
part of the diffusion process.
|
||||
:param max_beta: the maximum beta to use; use values lower than 1 to
|
||||
prevent singularities.
|
||||
"""
|
||||
betas = []
|
||||
for i in range(num_diffusion_timesteps):
|
||||
t1 = i / num_diffusion_timesteps
|
||||
t2 = (i + 1) / num_diffusion_timesteps
|
||||
betas.append(min(1 - alpha_bar(t2) / alpha_bar(t1), max_beta))
|
||||
return np.array(betas)
|
||||
|
||||
|
||||
def extract_into_tensor(a, t, x_shape):
|
||||
b, *_ = t.shape
|
||||
out = a.gather(-1, t)
|
||||
return out.reshape(b, *((1,) * (len(x_shape) - 1)))
|
||||
|
||||
|
||||
def checkpoint(func, inputs, params, flag):
|
||||
"""
|
||||
Evaluate a function without caching intermediate activations, allowing for
|
||||
reduced memory at the expense of extra compute in the backward pass.
|
||||
:param func: the function to evaluate.
|
||||
:param inputs: the argument sequence to pass to `func`.
|
||||
:param params: a sequence of parameters `func` depends on but does not
|
||||
explicitly take as arguments.
|
||||
:param flag: if False, disable gradient checkpointing.
|
||||
"""
|
||||
if flag:
|
||||
args = tuple(inputs) + tuple(params)
|
||||
return CheckpointFunction.apply(func, len(inputs), *args)
|
||||
else:
|
||||
return func(*inputs)
|
||||
|
||||
|
||||
class CheckpointFunction(torch.autograd.Function):
|
||||
@staticmethod
|
||||
def forward(ctx, run_function, length, *args):
|
||||
ctx.run_function = run_function
|
||||
ctx.input_tensors = list(args[:length])
|
||||
ctx.input_params = list(args[length:])
|
||||
|
||||
with torch.no_grad():
|
||||
output_tensors = ctx.run_function(*ctx.input_tensors)
|
||||
return output_tensors
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, *output_grads):
|
||||
ctx.input_tensors = [x.detach().requires_grad_(True) for x in ctx.input_tensors]
|
||||
with torch.enable_grad():
|
||||
# Fixes a bug where the first op in run_function modifies the
|
||||
# Tensor storage in place, which is not allowed for detach()'d
|
||||
# Tensors.
|
||||
shallow_copies = [x.view_as(x) for x in ctx.input_tensors]
|
||||
output_tensors = ctx.run_function(*shallow_copies)
|
||||
input_grads = torch.autograd.grad(
|
||||
output_tensors,
|
||||
ctx.input_tensors + ctx.input_params,
|
||||
output_grads,
|
||||
allow_unused=True,
|
||||
)
|
||||
del ctx.input_tensors
|
||||
del ctx.input_params
|
||||
del output_tensors
|
||||
return (None, None) + input_grads
|
||||
|
||||
|
||||
def timestep_embedding(timesteps, dim, max_period=10000, repeat_only=False):
|
||||
"""
|
||||
Create sinusoidal timestep embeddings.
|
||||
:param timesteps: a 1-D Tensor of N indices, one per batch element.
|
||||
These may be fractional.
|
||||
:param dim: the dimension of the output.
|
||||
:param max_period: controls the minimum frequency of the embeddings.
|
||||
:return: an [N x dim] Tensor of positional embeddings.
|
||||
"""
|
||||
if not repeat_only:
|
||||
half = dim // 2
|
||||
freqs = torch.exp(
|
||||
-math.log(max_period)
|
||||
* torch.arange(start=0, end=half, dtype=torch.float32)
|
||||
/ half
|
||||
).to(device=timesteps.device)
|
||||
args = timesteps[:, None].float() * freqs[None]
|
||||
embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
|
||||
if dim % 2:
|
||||
embedding = torch.cat(
|
||||
[embedding, torch.zeros_like(embedding[:, :1])], dim=-1
|
||||
)
|
||||
else:
|
||||
embedding = repeat(timesteps, "b -> b d", d=dim)
|
||||
return embedding
|
||||
|
||||
|
||||
def zero_module(module):
|
||||
"""
|
||||
Zero out the parameters of a module and return it.
|
||||
"""
|
||||
for p in module.parameters():
|
||||
p.detach().zero_()
|
||||
return module
|
||||
|
||||
|
||||
def scale_module(module, scale):
|
||||
"""
|
||||
Scale the parameters of a module and return it.
|
||||
"""
|
||||
for p in module.parameters():
|
||||
p.detach().mul_(scale)
|
||||
return module
|
||||
|
||||
|
||||
def mean_flat(tensor):
|
||||
"""
|
||||
Take the mean over all non-batch dimensions.
|
||||
"""
|
||||
return tensor.mean(dim=list(range(1, len(tensor.shape))))
|
||||
|
||||
|
||||
def normalization(channels):
|
||||
"""
|
||||
Make a standard normalization layer.
|
||||
:param channels: number of input channels.
|
||||
:return: an nn.Module for normalization.
|
||||
"""
|
||||
return GroupNorm32(32, channels)
|
||||
|
||||
|
||||
# PyTorch 1.7 has SiLU, but we support PyTorch 1.5.
|
||||
class SiLU(nn.Module):
|
||||
def forward(self, x):
|
||||
return x * torch.sigmoid(x)
|
||||
|
||||
|
||||
class GroupNorm32(nn.GroupNorm):
|
||||
def forward(self, x):
|
||||
return super().forward(x.float()).type(x.dtype)
|
||||
|
||||
|
||||
def conv_nd(dims, *args, **kwargs):
|
||||
"""
|
||||
Create a 1D, 2D, or 3D convolution module.
|
||||
"""
|
||||
if dims == 1:
|
||||
return nn.Conv1d(*args, **kwargs)
|
||||
elif dims == 2:
|
||||
return nn.Conv2d(*args, **kwargs)
|
||||
elif dims == 3:
|
||||
return nn.Conv3d(*args, **kwargs)
|
||||
raise ValueError(f"unsupported dimensions: {dims}")
|
||||
|
||||
|
||||
def linear(*args, **kwargs):
|
||||
"""
|
||||
Create a linear module.
|
||||
"""
|
||||
return nn.Linear(*args, **kwargs)
|
||||
|
||||
|
||||
def avg_pool_nd(dims, *args, **kwargs):
|
||||
"""
|
||||
Create a 1D, 2D, or 3D average pooling module.
|
||||
"""
|
||||
if dims == 1:
|
||||
return nn.AvgPool1d(*args, **kwargs)
|
||||
elif dims == 2:
|
||||
return nn.AvgPool2d(*args, **kwargs)
|
||||
elif dims == 3:
|
||||
return nn.AvgPool3d(*args, **kwargs)
|
||||
raise ValueError(f"unsupported dimensions: {dims}")
|
||||
|
||||
|
||||
class HybridConditioner(nn.Module):
|
||||
def __init__(self, c_concat_config, c_crossattn_config):
|
||||
super().__init__()
|
||||
self.concat_conditioner = instantiate_from_config(c_concat_config)
|
||||
self.crossattn_conditioner = instantiate_from_config(c_crossattn_config)
|
||||
|
||||
def forward(self, c_concat, c_crossattn):
|
||||
c_concat = self.concat_conditioner(c_concat)
|
||||
c_crossattn = self.crossattn_conditioner(c_crossattn)
|
||||
return {"c_concat": [c_concat], "c_crossattn": [c_crossattn]}
|
||||
|
||||
|
||||
def noise_like(shape, device, repeat=False):
|
||||
repeat_noise = lambda: torch.randn((1, *shape[1:]), device=device).repeat(
|
||||
shape[0], *((1,) * (len(shape) - 1))
|
||||
)
|
||||
noise = lambda: torch.randn(shape, device=device)
|
||||
return repeat_noise() if repeat else noise()
|
||||
401
threestudio/utils/GAN/normalunet.py
Normal file
401
threestudio/utils/GAN/normalunet.py
Normal file
@@ -0,0 +1,401 @@
|
||||
"""
|
||||
Copyright (C) 2019 NVIDIA Corporation. Ting-Chun Wang, Ming-Yu Liu, Jun-Yan Zhu.
|
||||
BSD License. All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
"""
|
||||
import functools
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.autograd import Variable
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Functions
|
||||
###############################################################################
|
||||
def weights_init(m):
|
||||
classname = m.__class__.__name__
|
||||
if classname.find("Conv") != -1:
|
||||
m.weight.data.normal_(0.0, 0.02)
|
||||
elif classname.find("BatchNorm2d") != -1:
|
||||
m.weight.data.normal_(1.0, 0.02)
|
||||
m.bias.data.fill_(0)
|
||||
|
||||
|
||||
def get_norm_layer(norm_type="instance"):
|
||||
if norm_type == "batch":
|
||||
norm_layer = functools.partial(nn.BatchNorm2d, affine=True)
|
||||
elif norm_type == "instance":
|
||||
norm_layer = functools.partial(nn.InstanceNorm2d, affine=False)
|
||||
else:
|
||||
raise NotImplementedError("normalization layer [%s] is not found" % norm_type)
|
||||
return norm_layer
|
||||
|
||||
|
||||
def define_G(
|
||||
input_nc,
|
||||
output_nc,
|
||||
ngf,
|
||||
netG,
|
||||
n_downsample_global=3,
|
||||
n_blocks_global=9,
|
||||
n_local_enhancers=1,
|
||||
n_blocks_local=3,
|
||||
norm="instance",
|
||||
gpu_ids=[],
|
||||
last_op=nn.Tanh(),
|
||||
):
|
||||
norm_layer = get_norm_layer(norm_type=norm)
|
||||
if netG == "global":
|
||||
netG = GlobalGenerator(
|
||||
input_nc,
|
||||
output_nc,
|
||||
ngf,
|
||||
n_downsample_global,
|
||||
n_blocks_global,
|
||||
norm_layer,
|
||||
last_op=last_op,
|
||||
)
|
||||
elif netG == "local":
|
||||
netG = LocalEnhancer(
|
||||
input_nc,
|
||||
output_nc,
|
||||
ngf,
|
||||
n_downsample_global,
|
||||
n_blocks_global,
|
||||
n_local_enhancers,
|
||||
n_blocks_local,
|
||||
norm_layer,
|
||||
)
|
||||
elif netG == "encoder":
|
||||
netG = Encoder(input_nc, output_nc, ngf, n_downsample_global, norm_layer)
|
||||
else:
|
||||
raise ("generator not implemented!")
|
||||
# print(netG)
|
||||
if len(gpu_ids) > 0:
|
||||
assert torch.cuda.is_available()
|
||||
netG.cuda(gpu_ids[0])
|
||||
netG.apply(weights_init)
|
||||
return netG
|
||||
|
||||
|
||||
def print_network(net):
|
||||
if isinstance(net, list):
|
||||
net = net[0]
|
||||
num_params = 0
|
||||
for param in net.parameters():
|
||||
num_params += param.numel()
|
||||
print(net)
|
||||
print("Total number of parameters: %d" % num_params)
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Generator
|
||||
##############################################################################
|
||||
class LocalEnhancer(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
input_nc,
|
||||
output_nc,
|
||||
ngf=32,
|
||||
n_downsample_global=3,
|
||||
n_blocks_global=9,
|
||||
n_local_enhancers=1,
|
||||
n_blocks_local=3,
|
||||
norm_layer=nn.BatchNorm2d,
|
||||
padding_type="reflect",
|
||||
):
|
||||
super(LocalEnhancer, self).__init__()
|
||||
self.n_local_enhancers = n_local_enhancers
|
||||
|
||||
###### global generator model #####
|
||||
ngf_global = ngf * (2**n_local_enhancers)
|
||||
model_global = GlobalGenerator(
|
||||
input_nc,
|
||||
output_nc,
|
||||
ngf_global,
|
||||
n_downsample_global,
|
||||
n_blocks_global,
|
||||
norm_layer,
|
||||
).model
|
||||
model_global = [
|
||||
model_global[i] for i in range(len(model_global) - 3)
|
||||
] # get rid of final convolution layers
|
||||
self.model = nn.Sequential(*model_global)
|
||||
|
||||
###### local enhancer layers #####
|
||||
for n in range(1, n_local_enhancers + 1):
|
||||
### downsample
|
||||
ngf_global = ngf * (2 ** (n_local_enhancers - n))
|
||||
model_downsample = [
|
||||
nn.ReflectionPad2d(3),
|
||||
nn.Conv2d(input_nc, ngf_global, kernel_size=7, padding=0),
|
||||
norm_layer(ngf_global),
|
||||
nn.ReLU(True),
|
||||
nn.Conv2d(
|
||||
ngf_global, ngf_global * 2, kernel_size=3, stride=2, padding=1
|
||||
),
|
||||
norm_layer(ngf_global * 2),
|
||||
nn.ReLU(True),
|
||||
]
|
||||
### residual blocks
|
||||
model_upsample = []
|
||||
for i in range(n_blocks_local):
|
||||
model_upsample += [
|
||||
ResnetBlock(
|
||||
ngf_global * 2, padding_type=padding_type, norm_layer=norm_layer
|
||||
)
|
||||
]
|
||||
|
||||
### upsample
|
||||
model_upsample += [
|
||||
nn.ConvTranspose2d(
|
||||
ngf_global * 2,
|
||||
ngf_global,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
output_padding=1,
|
||||
),
|
||||
norm_layer(ngf_global),
|
||||
nn.ReLU(True),
|
||||
]
|
||||
|
||||
### final convolution
|
||||
if n == n_local_enhancers:
|
||||
model_upsample += [
|
||||
nn.ReflectionPad2d(3),
|
||||
nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0),
|
||||
nn.Tanh(),
|
||||
]
|
||||
|
||||
setattr(self, "model" + str(n) + "_1", nn.Sequential(*model_downsample))
|
||||
setattr(self, "model" + str(n) + "_2", nn.Sequential(*model_upsample))
|
||||
|
||||
self.downsample = nn.AvgPool2d(
|
||||
3, stride=2, padding=[1, 1], count_include_pad=False
|
||||
)
|
||||
|
||||
def forward(self, input):
|
||||
### create input pyramid
|
||||
input_downsampled = [input]
|
||||
for i in range(self.n_local_enhancers):
|
||||
input_downsampled.append(self.downsample(input_downsampled[-1]))
|
||||
|
||||
### output at coarest level
|
||||
output_prev = self.model(input_downsampled[-1])
|
||||
### build up one layer at a time
|
||||
for n_local_enhancers in range(1, self.n_local_enhancers + 1):
|
||||
model_downsample = getattr(self, "model" + str(n_local_enhancers) + "_1")
|
||||
model_upsample = getattr(self, "model" + str(n_local_enhancers) + "_2")
|
||||
input_i = input_downsampled[self.n_local_enhancers - n_local_enhancers]
|
||||
output_prev = model_upsample(model_downsample(input_i) + output_prev)
|
||||
return output_prev
|
||||
|
||||
|
||||
class NormalNet(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
name="normalnet",
|
||||
input_nc=3,
|
||||
output_nc=3,
|
||||
ngf=64,
|
||||
n_downsampling=4,
|
||||
n_blocks=9,
|
||||
norm_layer=nn.BatchNorm2d,
|
||||
padding_type="reflect",
|
||||
last_op=nn.Sigmoid(),
|
||||
):
|
||||
assert n_blocks >= 0
|
||||
super(NormalNet, self).__init__()
|
||||
self.name = name
|
||||
activation = nn.ReLU(True)
|
||||
|
||||
model = [
|
||||
nn.ReflectionPad2d(3),
|
||||
nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0),
|
||||
nn.BatchNorm2d(ngf),
|
||||
activation,
|
||||
]
|
||||
### downsample
|
||||
for i in range(n_downsampling):
|
||||
mult = 2**i
|
||||
model += [
|
||||
nn.Conv2d(
|
||||
ngf * mult, ngf * mult * 2, kernel_size=3, stride=2, padding=1
|
||||
),
|
||||
nn.BatchNorm2d(ngf * mult * 2),
|
||||
activation,
|
||||
]
|
||||
|
||||
### resnet blocks
|
||||
mult = 2**n_downsampling
|
||||
for i in range(n_blocks):
|
||||
model += [
|
||||
ResnetBlock(
|
||||
ngf * mult,
|
||||
padding_type=padding_type,
|
||||
activation=activation,
|
||||
norm_layer=norm_layer,
|
||||
)
|
||||
]
|
||||
|
||||
### upsample
|
||||
for i in range(n_downsampling):
|
||||
mult = 2 ** (n_downsampling - i)
|
||||
model += [
|
||||
nn.Upsample(scale_factor=2),
|
||||
nn.Conv2d(
|
||||
ngf * mult, int(ngf * mult / 2), kernel_size=3, stride=1, padding=1
|
||||
),
|
||||
nn.BatchNorm2d(int(ngf * mult / 2)),
|
||||
activation,
|
||||
]
|
||||
model += [
|
||||
nn.ReflectionPad2d(3),
|
||||
nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0),
|
||||
]
|
||||
if last_op is not None:
|
||||
model += [last_op]
|
||||
self.model = nn.Sequential(*model)
|
||||
|
||||
def forward(self, in_x, label=None):
|
||||
res_list = []
|
||||
return self.model(in_x)
|
||||
|
||||
|
||||
# Define a resnet block
|
||||
class ResnetBlock(nn.Module):
|
||||
def __init__(
|
||||
self, dim, padding_type, norm_layer, activation=nn.ReLU(True), use_dropout=False
|
||||
):
|
||||
super(ResnetBlock, self).__init__()
|
||||
self.conv_block = self.build_conv_block(
|
||||
dim, padding_type, norm_layer, activation, use_dropout
|
||||
)
|
||||
|
||||
def build_conv_block(self, dim, padding_type, norm_layer, activation, use_dropout):
|
||||
conv_block = []
|
||||
p = 0
|
||||
if padding_type == "reflect":
|
||||
conv_block += [nn.ReflectionPad2d(1)]
|
||||
elif padding_type == "replicate":
|
||||
conv_block += [nn.ReplicationPad2d(1)]
|
||||
elif padding_type == "zero":
|
||||
p = 1
|
||||
else:
|
||||
raise NotImplementedError("padding [%s] is not implemented" % padding_type)
|
||||
|
||||
conv_block += [
|
||||
nn.Conv2d(dim, dim, kernel_size=3, padding=p),
|
||||
nn.BatchNorm2d(dim),
|
||||
activation,
|
||||
]
|
||||
if use_dropout:
|
||||
conv_block += [nn.Dropout(0.5)]
|
||||
|
||||
p = 0
|
||||
if padding_type == "reflect":
|
||||
conv_block += [nn.ReflectionPad2d(1)]
|
||||
elif padding_type == "replicate":
|
||||
conv_block += [nn.ReplicationPad2d(1)]
|
||||
elif padding_type == "zero":
|
||||
p = 1
|
||||
else:
|
||||
raise NotImplementedError("padding [%s] is not implemented" % padding_type)
|
||||
conv_block += [
|
||||
nn.Conv2d(dim, dim, kernel_size=3, padding=p),
|
||||
nn.BatchNorm2d(dim),
|
||||
]
|
||||
|
||||
return nn.Sequential(*conv_block)
|
||||
|
||||
def forward(self, x):
|
||||
out = x + self.conv_block(x)
|
||||
return out
|
||||
|
||||
|
||||
class Encoder(nn.Module):
|
||||
def __init__(
|
||||
self, input_nc, output_nc, ngf=32, n_downsampling=4, norm_layer=nn.BatchNorm2d
|
||||
):
|
||||
super(Encoder, self).__init__()
|
||||
self.output_nc = output_nc
|
||||
|
||||
model = [
|
||||
nn.ReflectionPad2d(3),
|
||||
nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0),
|
||||
norm_layer(ngf),
|
||||
nn.ReLU(True),
|
||||
]
|
||||
### downsample
|
||||
for i in range(n_downsampling):
|
||||
mult = 2**i
|
||||
model += [
|
||||
nn.Conv2d(
|
||||
ngf * mult, ngf * mult * 2, kernel_size=3, stride=2, padding=1
|
||||
),
|
||||
norm_layer(ngf * mult * 2),
|
||||
nn.ReLU(True),
|
||||
]
|
||||
|
||||
### upsample
|
||||
for i in range(n_downsampling):
|
||||
mult = 2 ** (n_downsampling - i)
|
||||
model += [
|
||||
nn.ConvTranspose2d(
|
||||
ngf * mult,
|
||||
int(ngf * mult / 2),
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
output_padding=1,
|
||||
),
|
||||
norm_layer(int(ngf * mult / 2)),
|
||||
nn.ReLU(True),
|
||||
]
|
||||
|
||||
model += [
|
||||
nn.ReflectionPad2d(3),
|
||||
nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0),
|
||||
nn.Tanh(),
|
||||
]
|
||||
self.model = nn.Sequential(*model)
|
||||
|
||||
def forward(self, input, inst):
|
||||
outputs = self.model(input)
|
||||
|
||||
# instance-wise average pooling
|
||||
outputs_mean = outputs.clone()
|
||||
inst_list = np.unique(inst.cpu().numpy().astype(int))
|
||||
for i in inst_list:
|
||||
for b in range(input.size()[0]):
|
||||
indices = (inst[b : b + 1] == int(i)).nonzero() # n x 4
|
||||
for j in range(self.output_nc):
|
||||
output_ins = outputs[
|
||||
indices[:, 0] + b,
|
||||
indices[:, 1] + j,
|
||||
indices[:, 2],
|
||||
indices[:, 3],
|
||||
]
|
||||
mean_feat = torch.mean(output_ins).expand_as(output_ins)
|
||||
outputs_mean[
|
||||
indices[:, 0] + b,
|
||||
indices[:, 1] + j,
|
||||
indices[:, 2],
|
||||
indices[:, 3],
|
||||
] = mean_feat
|
||||
return outputs_mean
|
||||
208
threestudio/utils/GAN/util.py
Normal file
208
threestudio/utils/GAN/util.py
Normal file
@@ -0,0 +1,208 @@
|
||||
import importlib
|
||||
import multiprocessing as mp
|
||||
from collections import abc
|
||||
from functools import partial
|
||||
from inspect import isfunction
|
||||
from queue import Queue
|
||||
from threading import Thread
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from einops import rearrange
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
def log_txt_as_img(wh, xc, size=10):
|
||||
# wh a tuple of (width, height)
|
||||
# xc a list of captions to plot
|
||||
b = len(xc)
|
||||
txts = list()
|
||||
for bi in range(b):
|
||||
txt = Image.new("RGB", wh, color="white")
|
||||
draw = ImageDraw.Draw(txt)
|
||||
font = ImageFont.truetype("data/DejaVuSans.ttf", size=size)
|
||||
nc = int(40 * (wh[0] / 256))
|
||||
lines = "\n".join(
|
||||
xc[bi][start : start + nc] for start in range(0, len(xc[bi]), nc)
|
||||
)
|
||||
|
||||
try:
|
||||
draw.text((0, 0), lines, fill="black", font=font)
|
||||
except UnicodeEncodeError:
|
||||
print("Cant encode string for logging. Skipping.")
|
||||
|
||||
txt = np.array(txt).transpose(2, 0, 1) / 127.5 - 1.0
|
||||
txts.append(txt)
|
||||
txts = np.stack(txts)
|
||||
txts = torch.tensor(txts)
|
||||
return txts
|
||||
|
||||
|
||||
def ismap(x):
|
||||
if not isinstance(x, torch.Tensor):
|
||||
return False
|
||||
return (len(x.shape) == 4) and (x.shape[1] > 3)
|
||||
|
||||
|
||||
def isimage(x):
|
||||
if not isinstance(x, torch.Tensor):
|
||||
return False
|
||||
return (len(x.shape) == 4) and (x.shape[1] == 3 or x.shape[1] == 1)
|
||||
|
||||
|
||||
def exists(x):
|
||||
return x is not None
|
||||
|
||||
|
||||
def default(val, d):
|
||||
if exists(val):
|
||||
return val
|
||||
return d() if isfunction(d) else d
|
||||
|
||||
|
||||
def mean_flat(tensor):
|
||||
"""
|
||||
https://github.com/openai/guided-diffusion/blob/27c20a8fab9cb472df5d6bdd6c8d11c8f430b924/guided_diffusion/nn.py#L86
|
||||
Take the mean over all non-batch dimensions.
|
||||
"""
|
||||
return tensor.mean(dim=list(range(1, len(tensor.shape))))
|
||||
|
||||
|
||||
def count_params(model, verbose=False):
|
||||
total_params = sum(p.numel() for p in model.parameters())
|
||||
if verbose:
|
||||
print(f"{model.__class__.__name__} has {total_params * 1.e-6:.2f} M params.")
|
||||
return total_params
|
||||
|
||||
|
||||
def instantiate_from_config(config):
|
||||
if not "target" in config:
|
||||
if config == "__is_first_stage__":
|
||||
return None
|
||||
elif config == "__is_unconditional__":
|
||||
return None
|
||||
raise KeyError("Expected key `target` to instantiate.")
|
||||
return get_obj_from_str(config["target"])(**config.get("params", dict()))
|
||||
|
||||
|
||||
def get_obj_from_str(string, reload=False):
|
||||
module, cls = string.rsplit(".", 1)
|
||||
if reload:
|
||||
module_imp = importlib.import_module(module)
|
||||
importlib.reload(module_imp)
|
||||
return getattr(importlib.import_module(module, package=None), cls)
|
||||
|
||||
|
||||
def _do_parallel_data_prefetch(func, Q, data, idx, idx_to_fn=False):
|
||||
# create dummy dataset instance
|
||||
|
||||
# run prefetching
|
||||
if idx_to_fn:
|
||||
res = func(data, worker_id=idx)
|
||||
else:
|
||||
res = func(data)
|
||||
Q.put([idx, res])
|
||||
Q.put("Done")
|
||||
|
||||
|
||||
def parallel_data_prefetch(
|
||||
func: callable,
|
||||
data,
|
||||
n_proc,
|
||||
target_data_type="ndarray",
|
||||
cpu_intensive=True,
|
||||
use_worker_id=False,
|
||||
):
|
||||
# if target_data_type not in ["ndarray", "list"]:
|
||||
# raise ValueError(
|
||||
# "Data, which is passed to parallel_data_prefetch has to be either of type list or ndarray."
|
||||
# )
|
||||
if isinstance(data, np.ndarray) and target_data_type == "list":
|
||||
raise ValueError("list expected but function got ndarray.")
|
||||
elif isinstance(data, abc.Iterable):
|
||||
if isinstance(data, dict):
|
||||
print(
|
||||
f'WARNING:"data" argument passed to parallel_data_prefetch is a dict: Using only its values and disregarding keys.'
|
||||
)
|
||||
data = list(data.values())
|
||||
if target_data_type == "ndarray":
|
||||
data = np.asarray(data)
|
||||
else:
|
||||
data = list(data)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"The data, that shall be processed parallel has to be either an np.ndarray or an Iterable, but is actually {type(data)}."
|
||||
)
|
||||
|
||||
if cpu_intensive:
|
||||
Q = mp.Queue(1000)
|
||||
proc = mp.Process
|
||||
else:
|
||||
Q = Queue(1000)
|
||||
proc = Thread
|
||||
# spawn processes
|
||||
if target_data_type == "ndarray":
|
||||
arguments = [
|
||||
[func, Q, part, i, use_worker_id]
|
||||
for i, part in enumerate(np.array_split(data, n_proc))
|
||||
]
|
||||
else:
|
||||
step = (
|
||||
int(len(data) / n_proc + 1)
|
||||
if len(data) % n_proc != 0
|
||||
else int(len(data) / n_proc)
|
||||
)
|
||||
arguments = [
|
||||
[func, Q, part, i, use_worker_id]
|
||||
for i, part in enumerate(
|
||||
[data[i : i + step] for i in range(0, len(data), step)]
|
||||
)
|
||||
]
|
||||
processes = []
|
||||
for i in range(n_proc):
|
||||
p = proc(target=_do_parallel_data_prefetch, args=arguments[i])
|
||||
processes += [p]
|
||||
|
||||
# start processes
|
||||
print(f"Start prefetching...")
|
||||
import time
|
||||
|
||||
start = time.time()
|
||||
gather_res = [[] for _ in range(n_proc)]
|
||||
try:
|
||||
for p in processes:
|
||||
p.start()
|
||||
|
||||
k = 0
|
||||
while k < n_proc:
|
||||
# get result
|
||||
res = Q.get()
|
||||
if res == "Done":
|
||||
k += 1
|
||||
else:
|
||||
gather_res[res[0]] = res[1]
|
||||
|
||||
except Exception as e:
|
||||
print("Exception: ", e)
|
||||
for p in processes:
|
||||
p.terminate()
|
||||
|
||||
raise e
|
||||
finally:
|
||||
for p in processes:
|
||||
p.join()
|
||||
print(f"Prefetching complete. [{time.time() - start} sec.]")
|
||||
|
||||
if target_data_type == "ndarray":
|
||||
if not isinstance(gather_res[0], np.ndarray):
|
||||
return np.concatenate([np.asarray(r) for r in gather_res], axis=0)
|
||||
|
||||
# order outputs
|
||||
return np.concatenate(gather_res, axis=0)
|
||||
elif target_data_type == "list":
|
||||
out = []
|
||||
for r in gather_res:
|
||||
out.extend(r)
|
||||
return out
|
||||
else:
|
||||
return gather_res
|
||||
1028
threestudio/utils/GAN/vae.py
Normal file
1028
threestudio/utils/GAN/vae.py
Normal file
File diff suppressed because it is too large
Load Diff
1
threestudio/utils/__init__.py
Normal file
1
threestudio/utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import base
|
||||
118
threestudio/utils/base.py
Normal file
118
threestudio/utils/base.py
Normal file
@@ -0,0 +1,118 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from threestudio.utils.config import parse_structured
|
||||
from threestudio.utils.misc import get_device, load_module_weights
|
||||
from threestudio.utils.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.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
|
||||
# dummy tensor to indicate model state
|
||||
self._dummy: Float[Tensor, "..."]
|
||||
self.register_buffer("_dummy", torch.zeros(0).float(), persistent=False)
|
||||
|
||||
def configure(self, *args, **kwargs) -> None:
|
||||
pass
|
||||
156
threestudio/utils/callbacks.py
Normal file
156
threestudio/utils/callbacks.py
Normal file
@@ -0,0 +1,156 @@
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import pytorch_lightning
|
||||
|
||||
from threestudio.utils.config import dump_config
|
||||
from threestudio.utils.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 ...")
|
||||
131
threestudio/utils/config.py
Normal file
131
threestudio/utils/config.py
Normal file
@@ -0,0 +1,131 @@
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
import threestudio
|
||||
from threestudio.utils.typing import *
|
||||
|
||||
# ============ Register OmegaConf Recolvers ============= #
|
||||
OmegaConf.register_new_resolver(
|
||||
"calc_exp_lr_decay_rate", lambda factor, n: factor ** (1.0 / n)
|
||||
)
|
||||
OmegaConf.register_new_resolver("add", lambda a, b: a + b)
|
||||
OmegaConf.register_new_resolver("sub", lambda a, b: a - b)
|
||||
OmegaConf.register_new_resolver("mul", lambda a, b: a * b)
|
||||
OmegaConf.register_new_resolver("div", lambda a, b: a / b)
|
||||
OmegaConf.register_new_resolver("idiv", lambda a, b: a // b)
|
||||
OmegaConf.register_new_resolver("basename", lambda p: os.path.basename(p))
|
||||
OmegaConf.register_new_resolver("rmspace", lambda s, sub: s.replace(" ", sub))
|
||||
OmegaConf.register_new_resolver("tuple2", lambda s: [float(s), float(s)])
|
||||
OmegaConf.register_new_resolver("gt0", lambda s: s > 0)
|
||||
OmegaConf.register_new_resolver("cmaxgt0", lambda s: C_max(s) > 0)
|
||||
OmegaConf.register_new_resolver("not", lambda s: not s)
|
||||
OmegaConf.register_new_resolver(
|
||||
"cmaxgt0orcmaxgt0", lambda a, b: C_max(a) > 0 or C_max(b) > 0
|
||||
)
|
||||
# ======================================================= #
|
||||
|
||||
|
||||
def C_max(value: Any) -> 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) >= 6:
|
||||
max_value = value[2]
|
||||
for i in range(4, len(value), 2):
|
||||
max_value = max(max_value, value[i])
|
||||
value = [value[0], value[1], max_value, value[3]]
|
||||
if len(value) == 3:
|
||||
value = [0] + value
|
||||
assert len(value) == 4
|
||||
start_step, start_value, end_value, end_step = value
|
||||
value = max(start_value, end_value)
|
||||
return value
|
||||
|
||||
|
||||
@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"
|
||||
|
||||
# import custom extension
|
||||
custom_import: Tuple[str] = ()
|
||||
|
||||
### 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_type: str = ""
|
||||
data: dict = field(default_factory=dict)
|
||||
|
||||
system_type: 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 __post_init__(self):
|
||||
if not self.tag and not self.use_timestamp:
|
||||
raise ValueError("Either tag is specified or use_timestamp is True.")
|
||||
self.trial_name = self.tag
|
||||
# if resume from an existing config, self.timestamp should not be None
|
||||
if self.timestamp is None:
|
||||
self.timestamp = ""
|
||||
if self.use_timestamp:
|
||||
if self.n_gpus > 1:
|
||||
threestudio.warn(
|
||||
"Timestamp is disabled when using multiple GPUs, please make sure you have a unique tag."
|
||||
)
|
||||
else:
|
||||
self.timestamp = datetime.now().strftime("@%Y%m%d-%H%M%S")
|
||||
self.trial_name += self.timestamp
|
||||
self.exp_dir = os.path.join(self.exp_root_dir, self.name)
|
||||
self.trial_dir = os.path.join(self.exp_dir, self.trial_name)
|
||||
os.makedirs(self.trial_dir, exist_ok=True)
|
||||
|
||||
|
||||
def load_config(*yamls: str, cli_args: list = [], from_string=False, **kwargs) -> Any:
|
||||
if from_string:
|
||||
yaml_confs = [OmegaConf.create(s) for s in yamls]
|
||||
else:
|
||||
yaml_confs = [OmegaConf.load(f) for f in yamls]
|
||||
cli_conf = OmegaConf.from_cli(cli_args)
|
||||
cfg = OmegaConf.merge(*yaml_confs, cli_conf, kwargs)
|
||||
OmegaConf.resolve(cfg)
|
||||
assert isinstance(cfg, DictConfig)
|
||||
scfg = parse_structured(ExperimentConfig, cfg)
|
||||
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.structured(fields(**cfg))
|
||||
return scfg
|
||||
924
threestudio/utils/dpt.py
Normal file
924
threestudio/utils/dpt.py
Normal file
@@ -0,0 +1,924 @@
|
||||
import math
|
||||
import types
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
import timm
|
||||
|
||||
class BaseModel(torch.nn.Module):
|
||||
def load(self, path):
|
||||
"""Load model from file.
|
||||
Args:
|
||||
path (str): file path
|
||||
"""
|
||||
parameters = torch.load(path, map_location=torch.device('cpu'))
|
||||
|
||||
if "optimizer" in parameters:
|
||||
parameters = parameters["model"]
|
||||
|
||||
self.load_state_dict(parameters)
|
||||
|
||||
|
||||
def unflatten_with_named_tensor(input, dim, sizes):
|
||||
"""Workaround for unflattening with named tensor."""
|
||||
# tracer acts up with unflatten. See https://github.com/pytorch/pytorch/issues/49538
|
||||
new_shape = list(input.shape)[:dim] + list(sizes) + list(input.shape)[dim+1:]
|
||||
return input.view(*new_shape)
|
||||
|
||||
class Slice(nn.Module):
|
||||
def __init__(self, start_index=1):
|
||||
super(Slice, self).__init__()
|
||||
self.start_index = start_index
|
||||
|
||||
def forward(self, x):
|
||||
return x[:, self.start_index :]
|
||||
|
||||
|
||||
class AddReadout(nn.Module):
|
||||
def __init__(self, start_index=1):
|
||||
super(AddReadout, self).__init__()
|
||||
self.start_index = start_index
|
||||
|
||||
def forward(self, x):
|
||||
if self.start_index == 2:
|
||||
readout = (x[:, 0] + x[:, 1]) / 2
|
||||
else:
|
||||
readout = x[:, 0]
|
||||
return x[:, self.start_index :] + readout.unsqueeze(1)
|
||||
|
||||
|
||||
class ProjectReadout(nn.Module):
|
||||
def __init__(self, in_features, start_index=1):
|
||||
super(ProjectReadout, self).__init__()
|
||||
self.start_index = start_index
|
||||
|
||||
self.project = nn.Sequential(nn.Linear(2 * in_features, in_features), nn.GELU())
|
||||
|
||||
def forward(self, x):
|
||||
readout = x[:, 0].unsqueeze(1).expand_as(x[:, self.start_index :])
|
||||
features = torch.cat((x[:, self.start_index :], readout), -1)
|
||||
|
||||
return self.project(features)
|
||||
|
||||
|
||||
class Transpose(nn.Module):
|
||||
def __init__(self, dim0, dim1):
|
||||
super(Transpose, self).__init__()
|
||||
self.dim0 = dim0
|
||||
self.dim1 = dim1
|
||||
|
||||
def forward(self, x):
|
||||
x = x.transpose(self.dim0, self.dim1)
|
||||
return x
|
||||
|
||||
|
||||
def forward_vit(pretrained, x):
|
||||
b, c, h, w = x.shape
|
||||
|
||||
glob = pretrained.model.forward_flex(x)
|
||||
|
||||
layer_1 = pretrained.activations["1"]
|
||||
layer_2 = pretrained.activations["2"]
|
||||
layer_3 = pretrained.activations["3"]
|
||||
layer_4 = pretrained.activations["4"]
|
||||
|
||||
layer_1 = pretrained.act_postprocess1[0:2](layer_1)
|
||||
layer_2 = pretrained.act_postprocess2[0:2](layer_2)
|
||||
layer_3 = pretrained.act_postprocess3[0:2](layer_3)
|
||||
layer_4 = pretrained.act_postprocess4[0:2](layer_4)
|
||||
|
||||
|
||||
unflattened_dim = 2
|
||||
unflattened_size = (
|
||||
int(torch.div(h, pretrained.model.patch_size[1], rounding_mode='floor')),
|
||||
int(torch.div(w, pretrained.model.patch_size[0], rounding_mode='floor')),
|
||||
)
|
||||
unflatten = nn.Sequential(nn.Unflatten(unflattened_dim, unflattened_size))
|
||||
|
||||
|
||||
if layer_1.ndim == 3:
|
||||
layer_1 = unflatten(layer_1)
|
||||
if layer_2.ndim == 3:
|
||||
layer_2 = unflatten(layer_2)
|
||||
if layer_3.ndim == 3:
|
||||
layer_3 = unflatten_with_named_tensor(layer_3, unflattened_dim, unflattened_size)
|
||||
if layer_4.ndim == 3:
|
||||
layer_4 = unflatten_with_named_tensor(layer_4, unflattened_dim, unflattened_size)
|
||||
|
||||
layer_1 = pretrained.act_postprocess1[3 : len(pretrained.act_postprocess1)](layer_1)
|
||||
layer_2 = pretrained.act_postprocess2[3 : len(pretrained.act_postprocess2)](layer_2)
|
||||
layer_3 = pretrained.act_postprocess3[3 : len(pretrained.act_postprocess3)](layer_3)
|
||||
layer_4 = pretrained.act_postprocess4[3 : len(pretrained.act_postprocess4)](layer_4)
|
||||
|
||||
return layer_1, layer_2, layer_3, layer_4
|
||||
|
||||
|
||||
def _resize_pos_embed(self, posemb, gs_h, gs_w):
|
||||
posemb_tok, posemb_grid = (
|
||||
posemb[:, : self.start_index],
|
||||
posemb[0, self.start_index :],
|
||||
)
|
||||
|
||||
gs_old = int(math.sqrt(posemb_grid.shape[0]))
|
||||
|
||||
posemb_grid = posemb_grid.reshape(1, gs_old, gs_old, -1).permute(0, 3, 1, 2)
|
||||
posemb_grid = F.interpolate(posemb_grid, size=(gs_h, gs_w), mode="bilinear")
|
||||
posemb_grid = posemb_grid.permute(0, 2, 3, 1).reshape(1, gs_h * gs_w, -1)
|
||||
|
||||
posemb = torch.cat([posemb_tok, posemb_grid], dim=1)
|
||||
|
||||
return posemb
|
||||
|
||||
|
||||
def forward_flex(self, x):
|
||||
b, c, h, w = x.shape
|
||||
|
||||
pos_embed = self._resize_pos_embed(
|
||||
self.pos_embed, torch.div(h, self.patch_size[1], rounding_mode='floor'), torch.div(w, self.patch_size[0], rounding_mode='floor')
|
||||
)
|
||||
|
||||
B = x.shape[0]
|
||||
|
||||
if hasattr(self.patch_embed, "backbone"):
|
||||
x = self.patch_embed.backbone(x)
|
||||
if isinstance(x, (list, tuple)):
|
||||
x = x[-1] # last feature if backbone outputs list/tuple of features
|
||||
|
||||
x = self.patch_embed.proj(x).flatten(2).transpose(1, 2)
|
||||
|
||||
if getattr(self, "dist_token", None) is not None:
|
||||
cls_tokens = self.cls_token.expand(
|
||||
B, -1, -1
|
||||
) # stole cls_tokens impl from Phil Wang, thanks
|
||||
dist_token = self.dist_token.expand(B, -1, -1)
|
||||
x = torch.cat((cls_tokens, dist_token, x), dim=1)
|
||||
else:
|
||||
cls_tokens = self.cls_token.expand(
|
||||
B, -1, -1
|
||||
) # stole cls_tokens impl from Phil Wang, thanks
|
||||
x = torch.cat((cls_tokens, x), dim=1)
|
||||
|
||||
x = x + pos_embed
|
||||
x = self.pos_drop(x)
|
||||
|
||||
for blk in self.blocks:
|
||||
x = blk(x)
|
||||
|
||||
x = self.norm(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
activations = {}
|
||||
|
||||
|
||||
def get_activation(name):
|
||||
def hook(model, input, output):
|
||||
activations[name] = output
|
||||
|
||||
return hook
|
||||
|
||||
|
||||
def get_readout_oper(vit_features, features, use_readout, start_index=1):
|
||||
if use_readout == "ignore":
|
||||
readout_oper = [Slice(start_index)] * len(features)
|
||||
elif use_readout == "add":
|
||||
readout_oper = [AddReadout(start_index)] * len(features)
|
||||
elif use_readout == "project":
|
||||
readout_oper = [
|
||||
ProjectReadout(vit_features, start_index) for out_feat in features
|
||||
]
|
||||
else:
|
||||
assert (
|
||||
False
|
||||
), "wrong operation for readout token, use_readout can be 'ignore', 'add', or 'project'"
|
||||
|
||||
return readout_oper
|
||||
|
||||
|
||||
def _make_vit_b16_backbone(
|
||||
model,
|
||||
features=[96, 192, 384, 768],
|
||||
size=[384, 384],
|
||||
hooks=[2, 5, 8, 11],
|
||||
vit_features=768,
|
||||
use_readout="ignore",
|
||||
start_index=1,
|
||||
):
|
||||
pretrained = nn.Module()
|
||||
|
||||
pretrained.model = model
|
||||
pretrained.model.blocks[hooks[0]].register_forward_hook(get_activation("1"))
|
||||
pretrained.model.blocks[hooks[1]].register_forward_hook(get_activation("2"))
|
||||
pretrained.model.blocks[hooks[2]].register_forward_hook(get_activation("3"))
|
||||
pretrained.model.blocks[hooks[3]].register_forward_hook(get_activation("4"))
|
||||
|
||||
pretrained.activations = activations
|
||||
|
||||
readout_oper = get_readout_oper(vit_features, features, use_readout, start_index)
|
||||
|
||||
# 32, 48, 136, 384
|
||||
pretrained.act_postprocess1 = nn.Sequential(
|
||||
readout_oper[0],
|
||||
Transpose(1, 2),
|
||||
nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])),
|
||||
nn.Conv2d(
|
||||
in_channels=vit_features,
|
||||
out_channels=features[0],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
),
|
||||
nn.ConvTranspose2d(
|
||||
in_channels=features[0],
|
||||
out_channels=features[0],
|
||||
kernel_size=4,
|
||||
stride=4,
|
||||
padding=0,
|
||||
bias=True,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
),
|
||||
)
|
||||
|
||||
pretrained.act_postprocess2 = nn.Sequential(
|
||||
readout_oper[1],
|
||||
Transpose(1, 2),
|
||||
nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])),
|
||||
nn.Conv2d(
|
||||
in_channels=vit_features,
|
||||
out_channels=features[1],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
),
|
||||
nn.ConvTranspose2d(
|
||||
in_channels=features[1],
|
||||
out_channels=features[1],
|
||||
kernel_size=2,
|
||||
stride=2,
|
||||
padding=0,
|
||||
bias=True,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
),
|
||||
)
|
||||
|
||||
pretrained.act_postprocess3 = nn.Sequential(
|
||||
readout_oper[2],
|
||||
Transpose(1, 2),
|
||||
nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])),
|
||||
nn.Conv2d(
|
||||
in_channels=vit_features,
|
||||
out_channels=features[2],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
),
|
||||
)
|
||||
|
||||
pretrained.act_postprocess4 = nn.Sequential(
|
||||
readout_oper[3],
|
||||
Transpose(1, 2),
|
||||
nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])),
|
||||
nn.Conv2d(
|
||||
in_channels=vit_features,
|
||||
out_channels=features[3],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
),
|
||||
nn.Conv2d(
|
||||
in_channels=features[3],
|
||||
out_channels=features[3],
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
),
|
||||
)
|
||||
|
||||
pretrained.model.start_index = start_index
|
||||
pretrained.model.patch_size = [16, 16]
|
||||
|
||||
# We inject this function into the VisionTransformer instances so that
|
||||
# we can use it with interpolated position embeddings without modifying the library source.
|
||||
pretrained.model.forward_flex = types.MethodType(forward_flex, pretrained.model)
|
||||
pretrained.model._resize_pos_embed = types.MethodType(
|
||||
_resize_pos_embed, pretrained.model
|
||||
)
|
||||
|
||||
return pretrained
|
||||
|
||||
|
||||
def _make_pretrained_vitl16_384(pretrained, use_readout="ignore", hooks=None):
|
||||
model = timm.create_model("vit_large_patch16_384", pretrained=pretrained)
|
||||
|
||||
hooks = [5, 11, 17, 23] if hooks == None else hooks
|
||||
return _make_vit_b16_backbone(
|
||||
model,
|
||||
features=[256, 512, 1024, 1024],
|
||||
hooks=hooks,
|
||||
vit_features=1024,
|
||||
use_readout=use_readout,
|
||||
)
|
||||
|
||||
|
||||
def _make_pretrained_vitb16_384(pretrained, use_readout="ignore", hooks=None):
|
||||
model = timm.create_model("vit_base_patch16_384", pretrained=pretrained)
|
||||
|
||||
hooks = [2, 5, 8, 11] if hooks == None else hooks
|
||||
return _make_vit_b16_backbone(
|
||||
model, features=[96, 192, 384, 768], hooks=hooks, use_readout=use_readout
|
||||
)
|
||||
|
||||
|
||||
def _make_pretrained_deitb16_384(pretrained, use_readout="ignore", hooks=None):
|
||||
model = timm.create_model("vit_deit_base_patch16_384", pretrained=pretrained)
|
||||
|
||||
hooks = [2, 5, 8, 11] if hooks == None else hooks
|
||||
return _make_vit_b16_backbone(
|
||||
model, features=[96, 192, 384, 768], hooks=hooks, use_readout=use_readout
|
||||
)
|
||||
|
||||
|
||||
def _make_pretrained_deitb16_distil_384(pretrained, use_readout="ignore", hooks=None):
|
||||
model = timm.create_model(
|
||||
"vit_deit_base_distilled_patch16_384", pretrained=pretrained
|
||||
)
|
||||
|
||||
hooks = [2, 5, 8, 11] if hooks == None else hooks
|
||||
return _make_vit_b16_backbone(
|
||||
model,
|
||||
features=[96, 192, 384, 768],
|
||||
hooks=hooks,
|
||||
use_readout=use_readout,
|
||||
start_index=2,
|
||||
)
|
||||
|
||||
|
||||
def _make_vit_b_rn50_backbone(
|
||||
model,
|
||||
features=[256, 512, 768, 768],
|
||||
size=[384, 384],
|
||||
hooks=[0, 1, 8, 11],
|
||||
vit_features=768,
|
||||
use_vit_only=False,
|
||||
use_readout="ignore",
|
||||
start_index=1,
|
||||
):
|
||||
pretrained = nn.Module()
|
||||
|
||||
pretrained.model = model
|
||||
|
||||
if use_vit_only == True:
|
||||
pretrained.model.blocks[hooks[0]].register_forward_hook(get_activation("1"))
|
||||
pretrained.model.blocks[hooks[1]].register_forward_hook(get_activation("2"))
|
||||
else:
|
||||
pretrained.model.patch_embed.backbone.stages[0].register_forward_hook(
|
||||
get_activation("1")
|
||||
)
|
||||
pretrained.model.patch_embed.backbone.stages[1].register_forward_hook(
|
||||
get_activation("2")
|
||||
)
|
||||
|
||||
pretrained.model.blocks[hooks[2]].register_forward_hook(get_activation("3"))
|
||||
pretrained.model.blocks[hooks[3]].register_forward_hook(get_activation("4"))
|
||||
|
||||
pretrained.activations = activations
|
||||
|
||||
readout_oper = get_readout_oper(vit_features, features, use_readout, start_index)
|
||||
|
||||
if use_vit_only == True:
|
||||
pretrained.act_postprocess1 = nn.Sequential(
|
||||
readout_oper[0],
|
||||
Transpose(1, 2),
|
||||
nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])),
|
||||
nn.Conv2d(
|
||||
in_channels=vit_features,
|
||||
out_channels=features[0],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
),
|
||||
nn.ConvTranspose2d(
|
||||
in_channels=features[0],
|
||||
out_channels=features[0],
|
||||
kernel_size=4,
|
||||
stride=4,
|
||||
padding=0,
|
||||
bias=True,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
),
|
||||
)
|
||||
|
||||
pretrained.act_postprocess2 = nn.Sequential(
|
||||
readout_oper[1],
|
||||
Transpose(1, 2),
|
||||
nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])),
|
||||
nn.Conv2d(
|
||||
in_channels=vit_features,
|
||||
out_channels=features[1],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
),
|
||||
nn.ConvTranspose2d(
|
||||
in_channels=features[1],
|
||||
out_channels=features[1],
|
||||
kernel_size=2,
|
||||
stride=2,
|
||||
padding=0,
|
||||
bias=True,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
),
|
||||
)
|
||||
else:
|
||||
pretrained.act_postprocess1 = nn.Sequential(
|
||||
nn.Identity(), nn.Identity(), nn.Identity()
|
||||
)
|
||||
pretrained.act_postprocess2 = nn.Sequential(
|
||||
nn.Identity(), nn.Identity(), nn.Identity()
|
||||
)
|
||||
|
||||
pretrained.act_postprocess3 = nn.Sequential(
|
||||
readout_oper[2],
|
||||
Transpose(1, 2),
|
||||
nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])),
|
||||
nn.Conv2d(
|
||||
in_channels=vit_features,
|
||||
out_channels=features[2],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
),
|
||||
)
|
||||
|
||||
pretrained.act_postprocess4 = nn.Sequential(
|
||||
readout_oper[3],
|
||||
Transpose(1, 2),
|
||||
nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])),
|
||||
nn.Conv2d(
|
||||
in_channels=vit_features,
|
||||
out_channels=features[3],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
),
|
||||
nn.Conv2d(
|
||||
in_channels=features[3],
|
||||
out_channels=features[3],
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
),
|
||||
)
|
||||
|
||||
pretrained.model.start_index = start_index
|
||||
pretrained.model.patch_size = [16, 16]
|
||||
|
||||
# We inject this function into the VisionTransformer instances so that
|
||||
# we can use it with interpolated position embeddings without modifying the library source.
|
||||
pretrained.model.forward_flex = types.MethodType(forward_flex, pretrained.model)
|
||||
|
||||
# We inject this function into the VisionTransformer instances so that
|
||||
# we can use it with interpolated position embeddings without modifying the library source.
|
||||
pretrained.model._resize_pos_embed = types.MethodType(
|
||||
_resize_pos_embed, pretrained.model
|
||||
)
|
||||
|
||||
return pretrained
|
||||
|
||||
|
||||
def _make_pretrained_vitb_rn50_384(
|
||||
pretrained, use_readout="ignore", hooks=None, use_vit_only=False
|
||||
):
|
||||
model = timm.create_model("vit_base_resnet50_384", pretrained=pretrained)
|
||||
|
||||
hooks = [0, 1, 8, 11] if hooks == None else hooks
|
||||
return _make_vit_b_rn50_backbone(
|
||||
model,
|
||||
features=[256, 512, 768, 768],
|
||||
size=[384, 384],
|
||||
hooks=hooks,
|
||||
use_vit_only=use_vit_only,
|
||||
use_readout=use_readout,
|
||||
)
|
||||
|
||||
def _make_encoder(backbone, features, use_pretrained, groups=1, expand=False, exportable=True, hooks=None, use_vit_only=False, use_readout="ignore",):
|
||||
if backbone == "vitl16_384":
|
||||
pretrained = _make_pretrained_vitl16_384(
|
||||
use_pretrained, hooks=hooks, use_readout=use_readout
|
||||
)
|
||||
scratch = _make_scratch(
|
||||
[256, 512, 1024, 1024], features, groups=groups, expand=expand
|
||||
) # ViT-L/16 - 85.0% Top1 (backbone)
|
||||
elif backbone == "vitb_rn50_384":
|
||||
pretrained = _make_pretrained_vitb_rn50_384(
|
||||
use_pretrained,
|
||||
hooks=hooks,
|
||||
use_vit_only=use_vit_only,
|
||||
use_readout=use_readout,
|
||||
)
|
||||
scratch = _make_scratch(
|
||||
[256, 512, 768, 768], features, groups=groups, expand=expand
|
||||
) # ViT-H/16 - 85.0% Top1 (backbone)
|
||||
elif backbone == "vitb16_384":
|
||||
pretrained = _make_pretrained_vitb16_384(
|
||||
use_pretrained, hooks=hooks, use_readout=use_readout
|
||||
)
|
||||
scratch = _make_scratch(
|
||||
[96, 192, 384, 768], features, groups=groups, expand=expand
|
||||
) # ViT-B/16 - 84.6% Top1 (backbone)
|
||||
elif backbone == "resnext101_wsl":
|
||||
pretrained = _make_pretrained_resnext101_wsl(use_pretrained)
|
||||
scratch = _make_scratch([256, 512, 1024, 2048], features, groups=groups, expand=expand) # efficientnet_lite3
|
||||
elif backbone == "efficientnet_lite3":
|
||||
pretrained = _make_pretrained_efficientnet_lite3(use_pretrained, exportable=exportable)
|
||||
scratch = _make_scratch([32, 48, 136, 384], features, groups=groups, expand=expand) # efficientnet_lite3
|
||||
else:
|
||||
print(f"Backbone '{backbone}' not implemented")
|
||||
assert False
|
||||
|
||||
return pretrained, scratch
|
||||
|
||||
|
||||
def _make_scratch(in_shape, out_shape, groups=1, expand=False):
|
||||
scratch = nn.Module()
|
||||
|
||||
out_shape1 = out_shape
|
||||
out_shape2 = out_shape
|
||||
out_shape3 = out_shape
|
||||
out_shape4 = out_shape
|
||||
if expand==True:
|
||||
out_shape1 = out_shape
|
||||
out_shape2 = out_shape*2
|
||||
out_shape3 = out_shape*4
|
||||
out_shape4 = out_shape*8
|
||||
|
||||
scratch.layer1_rn = nn.Conv2d(
|
||||
in_shape[0], out_shape1, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
scratch.layer2_rn = nn.Conv2d(
|
||||
in_shape[1], out_shape2, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
scratch.layer3_rn = nn.Conv2d(
|
||||
in_shape[2], out_shape3, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
scratch.layer4_rn = nn.Conv2d(
|
||||
in_shape[3], out_shape4, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
|
||||
return scratch
|
||||
|
||||
|
||||
def _make_pretrained_efficientnet_lite3(use_pretrained, exportable=False):
|
||||
efficientnet = torch.hub.load(
|
||||
"rwightman/gen-efficientnet-pytorch",
|
||||
"tf_efficientnet_lite3",
|
||||
pretrained=use_pretrained,
|
||||
exportable=exportable
|
||||
)
|
||||
return _make_efficientnet_backbone(efficientnet)
|
||||
|
||||
|
||||
def _make_efficientnet_backbone(effnet):
|
||||
pretrained = nn.Module()
|
||||
|
||||
pretrained.layer1 = nn.Sequential(
|
||||
effnet.conv_stem, effnet.bn1, effnet.act1, *effnet.blocks[0:2]
|
||||
)
|
||||
pretrained.layer2 = nn.Sequential(*effnet.blocks[2:3])
|
||||
pretrained.layer3 = nn.Sequential(*effnet.blocks[3:5])
|
||||
pretrained.layer4 = nn.Sequential(*effnet.blocks[5:9])
|
||||
|
||||
return pretrained
|
||||
|
||||
|
||||
def _make_resnet_backbone(resnet):
|
||||
pretrained = nn.Module()
|
||||
pretrained.layer1 = nn.Sequential(
|
||||
resnet.conv1, resnet.bn1, resnet.relu, resnet.maxpool, resnet.layer1
|
||||
)
|
||||
|
||||
pretrained.layer2 = resnet.layer2
|
||||
pretrained.layer3 = resnet.layer3
|
||||
pretrained.layer4 = resnet.layer4
|
||||
|
||||
return pretrained
|
||||
|
||||
|
||||
def _make_pretrained_resnext101_wsl(use_pretrained):
|
||||
resnet = torch.hub.load("facebookresearch/WSL-Images", "resnext101_32x8d_wsl")
|
||||
return _make_resnet_backbone(resnet)
|
||||
|
||||
|
||||
|
||||
class Interpolate(nn.Module):
|
||||
"""Interpolation module.
|
||||
"""
|
||||
|
||||
def __init__(self, scale_factor, mode, align_corners=False):
|
||||
"""Init.
|
||||
Args:
|
||||
scale_factor (float): scaling
|
||||
mode (str): interpolation mode
|
||||
"""
|
||||
super(Interpolate, self).__init__()
|
||||
|
||||
self.interp = nn.functional.interpolate
|
||||
self.scale_factor = scale_factor
|
||||
self.mode = mode
|
||||
self.align_corners = align_corners
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward pass.
|
||||
Args:
|
||||
x (tensor): input
|
||||
Returns:
|
||||
tensor: interpolated data
|
||||
"""
|
||||
|
||||
x = self.interp(
|
||||
x, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners
|
||||
)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class ResidualConvUnit(nn.Module):
|
||||
"""Residual convolution module.
|
||||
"""
|
||||
|
||||
def __init__(self, features):
|
||||
"""Init.
|
||||
Args:
|
||||
features (int): number of features
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.conv1 = nn.Conv2d(
|
||||
features, features, kernel_size=3, stride=1, padding=1, bias=True
|
||||
)
|
||||
|
||||
self.conv2 = nn.Conv2d(
|
||||
features, features, kernel_size=3, stride=1, padding=1, bias=True
|
||||
)
|
||||
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward pass.
|
||||
Args:
|
||||
x (tensor): input
|
||||
Returns:
|
||||
tensor: output
|
||||
"""
|
||||
out = self.relu(x)
|
||||
out = self.conv1(out)
|
||||
out = self.relu(out)
|
||||
out = self.conv2(out)
|
||||
|
||||
return out + x
|
||||
|
||||
|
||||
class FeatureFusionBlock(nn.Module):
|
||||
"""Feature fusion block.
|
||||
"""
|
||||
|
||||
def __init__(self, features):
|
||||
"""Init.
|
||||
Args:
|
||||
features (int): number of features
|
||||
"""
|
||||
super(FeatureFusionBlock, self).__init__()
|
||||
|
||||
self.resConfUnit1 = ResidualConvUnit(features)
|
||||
self.resConfUnit2 = ResidualConvUnit(features)
|
||||
|
||||
def forward(self, *xs):
|
||||
"""Forward pass.
|
||||
Returns:
|
||||
tensor: output
|
||||
"""
|
||||
output = xs[0]
|
||||
|
||||
if len(xs) == 2:
|
||||
output += self.resConfUnit1(xs[1])
|
||||
|
||||
output = self.resConfUnit2(output)
|
||||
|
||||
output = nn.functional.interpolate(
|
||||
output, scale_factor=2, mode="bilinear", align_corners=True
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
|
||||
|
||||
class ResidualConvUnit_custom(nn.Module):
|
||||
"""Residual convolution module.
|
||||
"""
|
||||
|
||||
def __init__(self, features, activation, bn):
|
||||
"""Init.
|
||||
Args:
|
||||
features (int): number of features
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.bn = bn
|
||||
|
||||
self.groups=1
|
||||
|
||||
self.conv1 = nn.Conv2d(
|
||||
features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups
|
||||
)
|
||||
|
||||
self.conv2 = nn.Conv2d(
|
||||
features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups
|
||||
)
|
||||
|
||||
if self.bn==True:
|
||||
self.bn1 = nn.BatchNorm2d(features)
|
||||
self.bn2 = nn.BatchNorm2d(features)
|
||||
|
||||
self.activation = activation
|
||||
|
||||
self.skip_add = nn.quantized.FloatFunctional()
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward pass.
|
||||
Args:
|
||||
x (tensor): input
|
||||
Returns:
|
||||
tensor: output
|
||||
"""
|
||||
|
||||
out = self.activation(x)
|
||||
out = self.conv1(out)
|
||||
if self.bn==True:
|
||||
out = self.bn1(out)
|
||||
|
||||
out = self.activation(out)
|
||||
out = self.conv2(out)
|
||||
if self.bn==True:
|
||||
out = self.bn2(out)
|
||||
|
||||
if self.groups > 1:
|
||||
out = self.conv_merge(out)
|
||||
|
||||
return self.skip_add.add(out, x)
|
||||
|
||||
# return out + x
|
||||
|
||||
|
||||
class FeatureFusionBlock_custom(nn.Module):
|
||||
"""Feature fusion block.
|
||||
"""
|
||||
|
||||
def __init__(self, features, activation, deconv=False, bn=False, expand=False, align_corners=True):
|
||||
"""Init.
|
||||
Args:
|
||||
features (int): number of features
|
||||
"""
|
||||
super(FeatureFusionBlock_custom, self).__init__()
|
||||
|
||||
self.deconv = deconv
|
||||
self.align_corners = align_corners
|
||||
|
||||
self.groups=1
|
||||
|
||||
self.expand = expand
|
||||
out_features = features
|
||||
if self.expand==True:
|
||||
out_features = features//2
|
||||
|
||||
self.out_conv = nn.Conv2d(features, out_features, kernel_size=1, stride=1, padding=0, bias=True, groups=1)
|
||||
|
||||
self.resConfUnit1 = ResidualConvUnit_custom(features, activation, bn)
|
||||
self.resConfUnit2 = ResidualConvUnit_custom(features, activation, bn)
|
||||
|
||||
self.skip_add = nn.quantized.FloatFunctional()
|
||||
|
||||
def forward(self, *xs):
|
||||
"""Forward pass.
|
||||
Returns:
|
||||
tensor: output
|
||||
"""
|
||||
output = xs[0]
|
||||
|
||||
if len(xs) == 2:
|
||||
res = self.resConfUnit1(xs[1])
|
||||
output = self.skip_add.add(output, res)
|
||||
# output += res
|
||||
|
||||
output = self.resConfUnit2(output)
|
||||
|
||||
output = nn.functional.interpolate(
|
||||
output, scale_factor=2, mode="bilinear", align_corners=self.align_corners
|
||||
)
|
||||
|
||||
output = self.out_conv(output)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
|
||||
def _make_fusion_block(features, use_bn):
|
||||
return FeatureFusionBlock_custom(
|
||||
features,
|
||||
nn.ReLU(False),
|
||||
deconv=False,
|
||||
bn=use_bn,
|
||||
expand=False,
|
||||
align_corners=True,
|
||||
)
|
||||
|
||||
|
||||
class DPT(BaseModel):
|
||||
def __init__(
|
||||
self,
|
||||
head,
|
||||
features=256,
|
||||
backbone="vitb_rn50_384",
|
||||
readout="project",
|
||||
channels_last=False,
|
||||
use_bn=False,
|
||||
):
|
||||
|
||||
super(DPT, self).__init__()
|
||||
|
||||
self.channels_last = channels_last
|
||||
|
||||
hooks = {
|
||||
"vitb_rn50_384": [0, 1, 8, 11],
|
||||
"vitb16_384": [2, 5, 8, 11],
|
||||
"vitl16_384": [5, 11, 17, 23],
|
||||
}
|
||||
|
||||
# Instantiate backbone and reassemble blocks
|
||||
self.pretrained, self.scratch = _make_encoder(
|
||||
backbone,
|
||||
features,
|
||||
True, # Set to true of you want to train from scratch, uses ImageNet weights
|
||||
groups=1,
|
||||
expand=False,
|
||||
exportable=False,
|
||||
hooks=hooks[backbone],
|
||||
use_readout=readout,
|
||||
)
|
||||
|
||||
self.scratch.refinenet1 = _make_fusion_block(features, use_bn)
|
||||
self.scratch.refinenet2 = _make_fusion_block(features, use_bn)
|
||||
self.scratch.refinenet3 = _make_fusion_block(features, use_bn)
|
||||
self.scratch.refinenet4 = _make_fusion_block(features, use_bn)
|
||||
|
||||
self.scratch.output_conv = head
|
||||
|
||||
|
||||
def forward(self, x):
|
||||
if self.channels_last == True:
|
||||
x.contiguous(memory_format=torch.channels_last)
|
||||
|
||||
layer_1, layer_2, layer_3, layer_4 = forward_vit(self.pretrained, x)
|
||||
|
||||
layer_1_rn = self.scratch.layer1_rn(layer_1)
|
||||
layer_2_rn = self.scratch.layer2_rn(layer_2)
|
||||
layer_3_rn = self.scratch.layer3_rn(layer_3)
|
||||
layer_4_rn = self.scratch.layer4_rn(layer_4)
|
||||
|
||||
path_4 = self.scratch.refinenet4(layer_4_rn)
|
||||
path_3 = self.scratch.refinenet3(path_4, layer_3_rn)
|
||||
path_2 = self.scratch.refinenet2(path_3, layer_2_rn)
|
||||
path_1 = self.scratch.refinenet1(path_2, layer_1_rn)
|
||||
|
||||
out = self.scratch.output_conv(path_1)
|
||||
|
||||
return out
|
||||
|
||||
class DPTDepthModel(DPT):
|
||||
def __init__(self, path=None, non_negative=True, num_channels=1, **kwargs):
|
||||
features = kwargs["features"] if "features" in kwargs else 256
|
||||
|
||||
head = nn.Sequential(
|
||||
nn.Conv2d(features, features // 2, kernel_size=3, stride=1, padding=1),
|
||||
Interpolate(scale_factor=2, mode="bilinear", align_corners=True),
|
||||
nn.Conv2d(features // 2, 32, kernel_size=3, stride=1, padding=1),
|
||||
nn.ReLU(True),
|
||||
nn.Conv2d(32, num_channels, kernel_size=1, stride=1, padding=0),
|
||||
nn.ReLU(True) if non_negative else nn.Identity(),
|
||||
nn.Identity(),
|
||||
)
|
||||
|
||||
super().__init__(head, **kwargs)
|
||||
|
||||
if path is not None:
|
||||
self.load(path)
|
||||
|
||||
def forward(self, x):
|
||||
return super().forward(x).squeeze(dim=1)
|
||||
1
threestudio/utils/lpips/__init__.py
Normal file
1
threestudio/utils/lpips/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .lpips import LPIPS
|
||||
123
threestudio/utils/lpips/lpips.py
Normal file
123
threestudio/utils/lpips/lpips.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""Stripped version of https://github.com/richzhang/PerceptualSimilarity/tree/master/models"""
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torchvision import models
|
||||
from collections import namedtuple
|
||||
|
||||
from threestudio.utils.lpips.utils import get_ckpt_path
|
||||
|
||||
|
||||
class LPIPS(nn.Module):
|
||||
# Learned perceptual metric
|
||||
def __init__(self, use_dropout=True):
|
||||
super().__init__()
|
||||
self.scaling_layer = ScalingLayer()
|
||||
self.chns = [64, 128, 256, 512, 512] # vg16 features
|
||||
self.net = vgg16(pretrained=True, requires_grad=False)
|
||||
self.lin0 = NetLinLayer(self.chns[0], use_dropout=use_dropout)
|
||||
self.lin1 = NetLinLayer(self.chns[1], use_dropout=use_dropout)
|
||||
self.lin2 = NetLinLayer(self.chns[2], use_dropout=use_dropout)
|
||||
self.lin3 = NetLinLayer(self.chns[3], use_dropout=use_dropout)
|
||||
self.lin4 = NetLinLayer(self.chns[4], use_dropout=use_dropout)
|
||||
self.load_from_pretrained()
|
||||
for param in self.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def load_from_pretrained(self, name="vgg_lpips"):
|
||||
ckpt = get_ckpt_path(name, "threestudio/utils/lpips")
|
||||
self.load_state_dict(torch.load(ckpt, map_location=torch.device("cpu")), strict=False)
|
||||
print("loaded pretrained LPIPS loss from {}".format(ckpt))
|
||||
|
||||
@classmethod
|
||||
def from_pretrained(cls, name="vgg_lpips"):
|
||||
if name != "vgg_lpips":
|
||||
raise NotImplementedError
|
||||
model = cls()
|
||||
ckpt = get_ckpt_path(name)
|
||||
model.load_state_dict(torch.load(ckpt, map_location=torch.device("cpu")), strict=False)
|
||||
return model
|
||||
|
||||
def forward(self, input, target):
|
||||
in0_input, in1_input = (self.scaling_layer(input), self.scaling_layer(target))
|
||||
outs0, outs1 = self.net(in0_input), self.net(in1_input)
|
||||
feats0, feats1, diffs = {}, {}, {}
|
||||
lins = [self.lin0, self.lin1, self.lin2, self.lin3, self.lin4]
|
||||
for kk in range(len(self.chns)):
|
||||
feats0[kk], feats1[kk] = normalize_tensor(outs0[kk]), normalize_tensor(outs1[kk])
|
||||
diffs[kk] = (feats0[kk] - feats1[kk]) ** 2
|
||||
|
||||
res = [spatial_average(lins[kk].model(diffs[kk]), keepdim=True) for kk in range(len(self.chns))]
|
||||
val = res[0]
|
||||
for l in range(1, len(self.chns)):
|
||||
val += res[l]
|
||||
return val
|
||||
|
||||
|
||||
class ScalingLayer(nn.Module):
|
||||
def __init__(self):
|
||||
super(ScalingLayer, self).__init__()
|
||||
self.register_buffer('shift', torch.Tensor([-.030, -.088, -.188])[None, :, None, None])
|
||||
self.register_buffer('scale', torch.Tensor([.458, .448, .450])[None, :, None, None])
|
||||
|
||||
def forward(self, inp):
|
||||
return (inp - self.shift) / self.scale
|
||||
|
||||
|
||||
class NetLinLayer(nn.Module):
|
||||
""" A single linear layer which does a 1x1 conv """
|
||||
def __init__(self, chn_in, chn_out=1, use_dropout=False):
|
||||
super(NetLinLayer, self).__init__()
|
||||
layers = [nn.Dropout(), ] if (use_dropout) else []
|
||||
layers += [nn.Conv2d(chn_in, chn_out, 1, stride=1, padding=0, bias=False), ]
|
||||
self.model = nn.Sequential(*layers)
|
||||
|
||||
|
||||
class vgg16(torch.nn.Module):
|
||||
def __init__(self, requires_grad=False, pretrained=True):
|
||||
super(vgg16, self).__init__()
|
||||
vgg_pretrained_features = models.vgg16(pretrained=pretrained).features
|
||||
self.slice1 = torch.nn.Sequential()
|
||||
self.slice2 = torch.nn.Sequential()
|
||||
self.slice3 = torch.nn.Sequential()
|
||||
self.slice4 = torch.nn.Sequential()
|
||||
self.slice5 = torch.nn.Sequential()
|
||||
self.N_slices = 5
|
||||
for x in range(4):
|
||||
self.slice1.add_module(str(x), vgg_pretrained_features[x])
|
||||
for x in range(4, 9):
|
||||
self.slice2.add_module(str(x), vgg_pretrained_features[x])
|
||||
for x in range(9, 16):
|
||||
self.slice3.add_module(str(x), vgg_pretrained_features[x])
|
||||
for x in range(16, 23):
|
||||
self.slice4.add_module(str(x), vgg_pretrained_features[x])
|
||||
for x in range(23, 30):
|
||||
self.slice5.add_module(str(x), vgg_pretrained_features[x])
|
||||
if not requires_grad:
|
||||
for param in self.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def forward(self, X):
|
||||
h = self.slice1(X)
|
||||
h_relu1_2 = h
|
||||
h = self.slice2(h)
|
||||
h_relu2_2 = h
|
||||
h = self.slice3(h)
|
||||
h_relu3_3 = h
|
||||
h = self.slice4(h)
|
||||
h_relu4_3 = h
|
||||
h = self.slice5(h)
|
||||
h_relu5_3 = h
|
||||
vgg_outputs = namedtuple("VggOutputs", ['relu1_2', 'relu2_2', 'relu3_3', 'relu4_3', 'relu5_3'])
|
||||
out = vgg_outputs(h_relu1_2, h_relu2_2, h_relu3_3, h_relu4_3, h_relu5_3)
|
||||
return out
|
||||
|
||||
|
||||
def normalize_tensor(x,eps=1e-10):
|
||||
norm_factor = torch.sqrt(torch.sum(x**2,dim=1,keepdim=True))
|
||||
return x/(norm_factor+eps)
|
||||
|
||||
|
||||
def spatial_average(x, keepdim=True):
|
||||
return x.mean([2,3],keepdim=keepdim)
|
||||
|
||||
157
threestudio/utils/lpips/utils.py
Normal file
157
threestudio/utils/lpips/utils.py
Normal file
@@ -0,0 +1,157 @@
|
||||
import os, hashlib
|
||||
import requests
|
||||
from tqdm import tqdm
|
||||
|
||||
URL_MAP = {
|
||||
"vgg_lpips": "https://heibox.uni-heidelberg.de/f/607503859c864bc1b30b/?dl=1"
|
||||
}
|
||||
|
||||
CKPT_MAP = {
|
||||
"vgg_lpips": "vgg.pth"
|
||||
}
|
||||
|
||||
MD5_MAP = {
|
||||
"vgg_lpips": "d507d7349b931f0638a25a48a722f98a"
|
||||
}
|
||||
|
||||
|
||||
def download(url, local_path, chunk_size=1024):
|
||||
os.makedirs(os.path.split(local_path)[0], exist_ok=True)
|
||||
with requests.get(url, stream=True) as r:
|
||||
total_size = int(r.headers.get("content-length", 0))
|
||||
with tqdm(total=total_size, unit="B", unit_scale=True) as pbar:
|
||||
with open(local_path, "wb") as f:
|
||||
for data in r.iter_content(chunk_size=chunk_size):
|
||||
if data:
|
||||
f.write(data)
|
||||
pbar.update(chunk_size)
|
||||
|
||||
|
||||
def md5_hash(path):
|
||||
with open(path, "rb") as f:
|
||||
content = f.read()
|
||||
return hashlib.md5(content).hexdigest()
|
||||
|
||||
|
||||
def get_ckpt_path(name, root, check=False):
|
||||
assert name in URL_MAP
|
||||
path = os.path.join(root, CKPT_MAP[name])
|
||||
if not os.path.exists(path) or (check and not md5_hash(path) == MD5_MAP[name]):
|
||||
print("Downloading {} model from {} to {}".format(name, URL_MAP[name], path))
|
||||
download(URL_MAP[name], path)
|
||||
md5 = md5_hash(path)
|
||||
assert md5 == MD5_MAP[name], md5
|
||||
return path
|
||||
|
||||
|
||||
class KeyNotFoundError(Exception):
|
||||
def __init__(self, cause, keys=None, visited=None):
|
||||
self.cause = cause
|
||||
self.keys = keys
|
||||
self.visited = visited
|
||||
messages = list()
|
||||
if keys is not None:
|
||||
messages.append("Key not found: {}".format(keys))
|
||||
if visited is not None:
|
||||
messages.append("Visited: {}".format(visited))
|
||||
messages.append("Cause:\n{}".format(cause))
|
||||
message = "\n".join(messages)
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
def retrieve(
|
||||
list_or_dict, key, splitval="/", default=None, expand=True, pass_success=False
|
||||
):
|
||||
"""Given a nested list or dict return the desired value at key expanding
|
||||
callable nodes if necessary and :attr:`expand` is ``True``. The expansion
|
||||
is done in-place.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
list_or_dict : list or dict
|
||||
Possibly nested list or dictionary.
|
||||
key : str
|
||||
key/to/value, path like string describing all keys necessary to
|
||||
consider to get to the desired value. List indices can also be
|
||||
passed here.
|
||||
splitval : str
|
||||
String that defines the delimiter between keys of the
|
||||
different depth levels in `key`.
|
||||
default : obj
|
||||
Value returned if :attr:`key` is not found.
|
||||
expand : bool
|
||||
Whether to expand callable nodes on the path or not.
|
||||
|
||||
Returns
|
||||
-------
|
||||
The desired value or if :attr:`default` is not ``None`` and the
|
||||
:attr:`key` is not found returns ``default``.
|
||||
|
||||
Raises
|
||||
------
|
||||
Exception if ``key`` not in ``list_or_dict`` and :attr:`default` is
|
||||
``None``.
|
||||
"""
|
||||
|
||||
keys = key.split(splitval)
|
||||
|
||||
success = True
|
||||
try:
|
||||
visited = []
|
||||
parent = None
|
||||
last_key = None
|
||||
for key in keys:
|
||||
if callable(list_or_dict):
|
||||
if not expand:
|
||||
raise KeyNotFoundError(
|
||||
ValueError(
|
||||
"Trying to get past callable node with expand=False."
|
||||
),
|
||||
keys=keys,
|
||||
visited=visited,
|
||||
)
|
||||
list_or_dict = list_or_dict()
|
||||
parent[last_key] = list_or_dict
|
||||
|
||||
last_key = key
|
||||
parent = list_or_dict
|
||||
|
||||
try:
|
||||
if isinstance(list_or_dict, dict):
|
||||
list_or_dict = list_or_dict[key]
|
||||
else:
|
||||
list_or_dict = list_or_dict[int(key)]
|
||||
except (KeyError, IndexError, ValueError) as e:
|
||||
raise KeyNotFoundError(e, keys=keys, visited=visited)
|
||||
|
||||
visited += [key]
|
||||
# final expansion of retrieved value
|
||||
if expand and callable(list_or_dict):
|
||||
list_or_dict = list_or_dict()
|
||||
parent[last_key] = list_or_dict
|
||||
except KeyNotFoundError as e:
|
||||
if default is None:
|
||||
raise e
|
||||
else:
|
||||
list_or_dict = default
|
||||
success = False
|
||||
|
||||
if not pass_success:
|
||||
return list_or_dict
|
||||
else:
|
||||
return list_or_dict, success
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
config = {"keya": "a",
|
||||
"keyb": "b",
|
||||
"keyc":
|
||||
{"cc1": 1,
|
||||
"cc2": 2,
|
||||
}
|
||||
}
|
||||
from omegaconf import OmegaConf
|
||||
config = OmegaConf.create(config)
|
||||
print(config)
|
||||
retrieve(config, "keya")
|
||||
|
||||
156
threestudio/utils/misc.py
Normal file
156
threestudio/utils/misc.py
Normal file
@@ -0,0 +1,156 @@
|
||||
import gc
|
||||
import os
|
||||
import re
|
||||
|
||||
import tinycudann as tcnn
|
||||
import torch
|
||||
from packaging import version
|
||||
|
||||
from threestudio.utils.config import config_to_primitive
|
||||
from threestudio.utils.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 = ("LOCAL_RANK", "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, 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"]
|
||||
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
|
||||
if len(value) >= 6:
|
||||
select_i = 3
|
||||
for i in range(3, len(value) - 2, 2):
|
||||
if global_step >= value[i]:
|
||||
select_i = i + 2
|
||||
if select_i != 3:
|
||||
start_value, start_step = value[select_i - 3], value[select_i - 2]
|
||||
else:
|
||||
start_step, start_value = value[:2]
|
||||
end_value, end_step = value[select_i - 1], value[select_i]
|
||||
value = [start_step, start_value, end_value, end_step]
|
||||
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()
|
||||
tcnn.free_temporary_memory()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
def find_last_path(path: str):
|
||||
if (path is not None) and ("LAST" in path):
|
||||
path = path.replace(" ", "_")
|
||||
base_dir_prefix, suffix = path.split("LAST", 1)
|
||||
base_dir = os.path.dirname(base_dir_prefix)
|
||||
prefix = os.path.split(base_dir_prefix)[-1]
|
||||
base_dir_prefix = os.path.join(base_dir, prefix)
|
||||
all_path = os.listdir(base_dir)
|
||||
all_path = [os.path.join(base_dir, dir) for dir in all_path]
|
||||
filtered_path = [dir for dir in all_path if dir.startswith(base_dir_prefix)]
|
||||
filtered_path.sort(reverse=True)
|
||||
last_path = filtered_path[0]
|
||||
new_path = last_path + suffix
|
||||
if os.path.exists(new_path):
|
||||
return new_path
|
||||
else:
|
||||
raise FileNotFoundError(new_path)
|
||||
else:
|
||||
return path
|
||||
459
threestudio/utils/ops.py
Normal file
459
threestudio/utils/ops.py
Normal file
@@ -0,0 +1,459 @@
|
||||
import math
|
||||
from collections import defaultdict
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from igl import fast_winding_number_for_meshes, point_mesh_squared_distance, read_obj
|
||||
from torch.autograd import Function
|
||||
from torch.cuda.amp import custom_bwd, custom_fwd
|
||||
|
||||
import threestudio
|
||||
from threestudio.utils.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))
|
||||
|
||||
|
||||
class SpecifyGradient(Function):
|
||||
# Implementation from stable-dreamfusion
|
||||
# https://github.com/ashawkey/stable-dreamfusion
|
||||
@staticmethod
|
||||
@custom_fwd
|
||||
def forward(ctx, input_tensor, gt_grad):
|
||||
ctx.save_for_backward(gt_grad)
|
||||
# we return a dummy value 1, which will be scaled by amp's scaler so we get the scale in backward.
|
||||
return torch.ones([1], device=input_tensor.device, dtype=input_tensor.dtype)
|
||||
|
||||
@staticmethod
|
||||
@custom_bwd
|
||||
def backward(ctx, grad_scale):
|
||||
(gt_grad,) = ctx.saved_tensors
|
||||
gt_grad = gt_grad * grad_scale
|
||||
return gt_grad, None
|
||||
|
||||
|
||||
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
|
||||
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,
|
||||
) -> 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
|
||||
)
|
||||
|
||||
return directions
|
||||
|
||||
|
||||
def get_rays(
|
||||
directions: Float[Tensor, "... 3"],
|
||||
c2w: Float[Tensor, "... 4 4"],
|
||||
keepdim=False,
|
||||
noise_scale=0.0,
|
||||
normalize=True,
|
||||
) -> 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: Float[Tensor, "B"], aspect_wh: float, near: float, far: float
|
||||
) -> Float[Tensor, "B 4 4"]:
|
||||
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
|
||||
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_full_projection_matrix(
|
||||
c2w: Float[Tensor, "B 4 4"], proj_mtx: Float[Tensor, "B 4 4"]
|
||||
) -> Float[Tensor, "B 4 4"]:
|
||||
return (c2w.unsqueeze(0).bmm(proj_mtx.unsqueeze(0))).squeeze(0)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
# Implementation from Latent-NeRF
|
||||
# https://github.com/eladrich/latent-nerf/blob/f49ecefcd48972e69a28e3116fe95edf0fac4dc8/src/latent_nerf/models/mesh_utils.py
|
||||
class MeshOBJ:
|
||||
dx = torch.zeros(3).float()
|
||||
dx[0] = 1
|
||||
dy, dz = dx[[1, 0, 2]], dx[[2, 1, 0]]
|
||||
dx, dy, dz = dx[None, :], dy[None, :], dz[None, :]
|
||||
|
||||
def __init__(self, v: np.ndarray, f: np.ndarray):
|
||||
self.v = v
|
||||
self.f = f
|
||||
self.dx, self.dy, self.dz = MeshOBJ.dx, MeshOBJ.dy, MeshOBJ.dz
|
||||
self.v_tensor = torch.from_numpy(self.v)
|
||||
|
||||
vf = self.v[self.f, :]
|
||||
self.f_center = vf.mean(axis=1)
|
||||
self.f_center_tensor = torch.from_numpy(self.f_center).float()
|
||||
|
||||
e1 = vf[:, 1, :] - vf[:, 0, :]
|
||||
e2 = vf[:, 2, :] - vf[:, 0, :]
|
||||
self.face_normals = np.cross(e1, e2)
|
||||
self.face_normals = (
|
||||
self.face_normals / np.linalg.norm(self.face_normals, axis=-1)[:, None]
|
||||
)
|
||||
self.face_normals_tensor = torch.from_numpy(self.face_normals)
|
||||
|
||||
def normalize_mesh(self, target_scale=0.5):
|
||||
verts = self.v
|
||||
|
||||
# Compute center of bounding box
|
||||
# center = torch.mean(torch.column_stack([torch.max(verts, dim=0)[0], torch.min(verts, dim=0)[0]]))
|
||||
center = verts.mean(axis=0)
|
||||
verts = verts - center
|
||||
scale = np.max(np.linalg.norm(verts, axis=1))
|
||||
verts = (verts / scale) * target_scale
|
||||
|
||||
return MeshOBJ(verts, self.f)
|
||||
|
||||
def winding_number(self, query: torch.Tensor):
|
||||
device = query.device
|
||||
shp = query.shape
|
||||
query_np = query.detach().cpu().reshape(-1, 3).numpy()
|
||||
target_alphas = fast_winding_number_for_meshes(
|
||||
self.v.astype(np.float32), self.f, query_np
|
||||
)
|
||||
return torch.from_numpy(target_alphas).reshape(shp[:-1]).to(device)
|
||||
|
||||
def gaussian_weighted_distance(self, query: torch.Tensor, sigma):
|
||||
device = query.device
|
||||
shp = query.shape
|
||||
query_np = query.detach().cpu().reshape(-1, 3).numpy()
|
||||
distances, _, _ = point_mesh_squared_distance(
|
||||
query_np, self.v.astype(np.float32), self.f
|
||||
)
|
||||
distances = torch.from_numpy(distances).reshape(shp[:-1]).to(device)
|
||||
weight = torch.exp(-(distances / (2 * sigma**2)))
|
||||
return weight
|
||||
|
||||
|
||||
def ce_pq_loss(p, q, weight=None):
|
||||
def clamp(v, T=0.0001):
|
||||
return v.clamp(T, 1 - T)
|
||||
|
||||
p = p.view(q.shape)
|
||||
ce = -1 * (p * torch.log(clamp(q)) + (1 - p) * torch.log(clamp(1 - q)))
|
||||
if weight is not None:
|
||||
ce *= weight
|
||||
return ce.sum()
|
||||
|
||||
|
||||
class ShapeLoss(nn.Module):
|
||||
def __init__(self, guide_shape):
|
||||
super().__init__()
|
||||
self.mesh_scale = 0.7
|
||||
self.proximal_surface = 0.3
|
||||
self.delta = 0.2
|
||||
self.shape_path = guide_shape
|
||||
v, _, _, f, _, _ = read_obj(self.shape_path, float)
|
||||
mesh = MeshOBJ(v, f)
|
||||
matrix_rot = np.array([[1, 0, 0], [0, 0, -1], [0, 1, 0]]) @ np.array(
|
||||
[[0, 0, 1], [0, 1, 0], [-1, 0, 0]]
|
||||
)
|
||||
self.sketchshape = mesh.normalize_mesh(self.mesh_scale)
|
||||
self.sketchshape = MeshOBJ(
|
||||
np.ascontiguousarray(
|
||||
(matrix_rot @ self.sketchshape.v.transpose(1, 0)).transpose(1, 0)
|
||||
),
|
||||
f,
|
||||
)
|
||||
|
||||
def forward(self, xyzs, sigmas):
|
||||
mesh_occ = self.sketchshape.winding_number(xyzs)
|
||||
if self.proximal_surface > 0:
|
||||
weight = 1 - self.sketchshape.gaussian_weighted_distance(
|
||||
xyzs, self.proximal_surface
|
||||
)
|
||||
else:
|
||||
weight = None
|
||||
indicator = (mesh_occ > 0.5).float()
|
||||
nerf_occ = 1 - torch.exp(-self.delta * sigmas)
|
||||
nerf_occ = nerf_occ.clamp(min=0, max=1.1)
|
||||
loss = ce_pq_loss(
|
||||
nerf_occ, indicator, weight=weight
|
||||
) # order is important for CE loss + second argument may not be optimized
|
||||
return loss
|
||||
|
||||
|
||||
def shifted_expotional_decay(a, b, c, r):
|
||||
return a * torch.exp(-b * r) + c
|
||||
|
||||
|
||||
def shifted_cosine_decay(a, b, c, r):
|
||||
return a * torch.cos(b * r + c) + a
|
||||
|
||||
|
||||
def perpendicular_component(x: Float[Tensor, "B C H W"], y: Float[Tensor, "B C H W"]):
|
||||
# get the component of x that is perpendicular to y
|
||||
eps = torch.ones_like(x[:, 0, 0, 0]) * 1e-6
|
||||
return (
|
||||
x
|
||||
- (
|
||||
torch.mul(x, y).sum(dim=[1, 2, 3])
|
||||
/ torch.maximum(torch.mul(y, y).sum(dim=[1, 2, 3]), eps)
|
||||
).view(-1, 1, 1, 1)
|
||||
* y
|
||||
)
|
||||
|
||||
|
||||
def validate_empty_rays(ray_indices, t_start, t_end):
|
||||
if ray_indices.nelement() == 0:
|
||||
threestudio.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
|
||||
1
threestudio/utils/perceptual/__init__.py
Normal file
1
threestudio/utils/perceptual/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .perceptual import PerceptualLoss
|
||||
173
threestudio/utils/perceptual/perceptual.py
Normal file
173
threestudio/utils/perceptual/perceptual.py
Normal file
@@ -0,0 +1,173 @@
|
||||
"""Stripped version of https://github.com/richzhang/PerceptualSimilarity/tree/master/models"""
|
||||
|
||||
from collections import namedtuple
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torchvision import models
|
||||
|
||||
import threestudio
|
||||
from threestudio.utils.perceptual.utils import get_ckpt_path
|
||||
from threestudio.utils.base import BaseObject
|
||||
from threestudio.utils.typing import *
|
||||
|
||||
@threestudio.register("perceptual-loss")
|
||||
class PerceptualLossObject(BaseObject):
|
||||
@dataclass
|
||||
class Config(BaseObject.Config):
|
||||
use_dropout: bool = True
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
self.perceptual_loss = PerceptualLoss(self.cfg.use_dropout).to(self.device)
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
x: Float[Tensor, "B 3 256 256"],
|
||||
y: Float[Tensor, "B 3 256 256"],
|
||||
):
|
||||
return self.perceptual_loss(x, y)
|
||||
|
||||
|
||||
class PerceptualLoss(nn.Module):
|
||||
# Learned perceptual metric
|
||||
def __init__(self, use_dropout=True):
|
||||
super().__init__()
|
||||
self.scaling_layer = ScalingLayer()
|
||||
self.chns = [64, 128, 256, 512, 512] # vg16 features
|
||||
self.net = vgg16(pretrained=True, requires_grad=False)
|
||||
self.lin0 = NetLinLayer(self.chns[0], use_dropout=use_dropout)
|
||||
self.lin1 = NetLinLayer(self.chns[1], use_dropout=use_dropout)
|
||||
self.lin2 = NetLinLayer(self.chns[2], use_dropout=use_dropout)
|
||||
self.lin3 = NetLinLayer(self.chns[3], use_dropout=use_dropout)
|
||||
self.lin4 = NetLinLayer(self.chns[4], use_dropout=use_dropout)
|
||||
self.load_from_pretrained()
|
||||
for param in self.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def load_from_pretrained(self, name="vgg_lpips"):
|
||||
ckpt = get_ckpt_path(name, "threestudio/utils/lpips")
|
||||
self.load_state_dict(
|
||||
torch.load(ckpt, map_location=torch.device("cpu")), strict=False
|
||||
)
|
||||
print("loaded pretrained LPIPS loss from {}".format(ckpt))
|
||||
|
||||
@classmethod
|
||||
def from_pretrained(cls, name="vgg_lpips"):
|
||||
if name != "vgg_lpips":
|
||||
raise NotImplementedError
|
||||
model = cls()
|
||||
ckpt = get_ckpt_path(name)
|
||||
model.load_state_dict(
|
||||
torch.load(ckpt, map_location=torch.device("cpu")), strict=False
|
||||
)
|
||||
return model
|
||||
|
||||
def forward(self, input, target):
|
||||
in0_input, in1_input = (self.scaling_layer(input), self.scaling_layer(target))
|
||||
outs0, outs1 = self.net(in0_input), self.net(in1_input)
|
||||
feats0, feats1, diffs = {}, {}, {}
|
||||
lins = [self.lin0, self.lin1, self.lin2, self.lin3, self.lin4]
|
||||
for kk in range(len(self.chns)):
|
||||
feats0[kk], feats1[kk] = normalize_tensor(outs0[kk]), normalize_tensor(
|
||||
outs1[kk]
|
||||
)
|
||||
diffs[kk] = (feats0[kk] - feats1[kk]) ** 2
|
||||
|
||||
res = [
|
||||
spatial_average(lins[kk].model(diffs[kk]), keepdim=True)
|
||||
for kk in range(len(self.chns))
|
||||
]
|
||||
val = res[0]
|
||||
for l in range(1, len(self.chns)):
|
||||
val += res[l]
|
||||
return val
|
||||
|
||||
|
||||
class ScalingLayer(nn.Module):
|
||||
def __init__(self):
|
||||
super(ScalingLayer, self).__init__()
|
||||
self.register_buffer(
|
||||
"shift", torch.Tensor([-0.030, -0.088, -0.188])[None, :, None, None]
|
||||
)
|
||||
self.register_buffer(
|
||||
"scale", torch.Tensor([0.458, 0.448, 0.450])[None, :, None, None]
|
||||
)
|
||||
|
||||
def forward(self, inp):
|
||||
return (inp - self.shift) / self.scale
|
||||
|
||||
|
||||
class NetLinLayer(nn.Module):
|
||||
"""A single linear layer which does a 1x1 conv"""
|
||||
|
||||
def __init__(self, chn_in, chn_out=1, use_dropout=False):
|
||||
super(NetLinLayer, self).__init__()
|
||||
layers = (
|
||||
[
|
||||
nn.Dropout(),
|
||||
]
|
||||
if (use_dropout)
|
||||
else []
|
||||
)
|
||||
layers += [
|
||||
nn.Conv2d(chn_in, chn_out, 1, stride=1, padding=0, bias=False),
|
||||
]
|
||||
self.model = nn.Sequential(*layers)
|
||||
|
||||
|
||||
class vgg16(torch.nn.Module):
|
||||
def __init__(self, requires_grad=False, pretrained=True):
|
||||
super(vgg16, self).__init__()
|
||||
try:
|
||||
vgg_pretrained = models.vgg16(pretrained=True)
|
||||
vgg_pretrained_features = vgg_pretrained.features
|
||||
except:
|
||||
vgg_pretrained_features = models.vgg16(pretrained=pretrained).features
|
||||
|
||||
self.slice1 = torch.nn.Sequential()
|
||||
self.slice2 = torch.nn.Sequential()
|
||||
self.slice3 = torch.nn.Sequential()
|
||||
self.slice4 = torch.nn.Sequential()
|
||||
self.slice5 = torch.nn.Sequential()
|
||||
self.N_slices = 5
|
||||
for x in range(4):
|
||||
self.slice1.add_module(str(x), vgg_pretrained_features[x])
|
||||
for x in range(4, 9):
|
||||
self.slice2.add_module(str(x), vgg_pretrained_features[x])
|
||||
for x in range(9, 16):
|
||||
self.slice3.add_module(str(x), vgg_pretrained_features[x])
|
||||
for x in range(16, 23):
|
||||
self.slice4.add_module(str(x), vgg_pretrained_features[x])
|
||||
for x in range(23, 30):
|
||||
self.slice5.add_module(str(x), vgg_pretrained_features[x])
|
||||
if not requires_grad:
|
||||
for param in self.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def forward(self, X):
|
||||
h = self.slice1(X)
|
||||
h_relu1_2 = h
|
||||
h = self.slice2(h)
|
||||
h_relu2_2 = h
|
||||
h = self.slice3(h)
|
||||
h_relu3_3 = h
|
||||
h = self.slice4(h)
|
||||
h_relu4_3 = h
|
||||
h = self.slice5(h)
|
||||
h_relu5_3 = h
|
||||
vgg_outputs = namedtuple(
|
||||
"VggOutputs", ["relu1_2", "relu2_2", "relu3_3", "relu4_3", "relu5_3"]
|
||||
)
|
||||
out = vgg_outputs(h_relu1_2, h_relu2_2, h_relu3_3, h_relu4_3, h_relu5_3)
|
||||
return out
|
||||
|
||||
|
||||
def normalize_tensor(x, eps=1e-10):
|
||||
norm_factor = torch.sqrt(torch.sum(x**2, dim=1, keepdim=True))
|
||||
return x / (norm_factor + eps)
|
||||
|
||||
|
||||
def spatial_average(x, keepdim=True):
|
||||
return x.mean([2, 3], keepdim=keepdim)
|
||||
154
threestudio/utils/perceptual/utils.py
Normal file
154
threestudio/utils/perceptual/utils.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
import requests
|
||||
from tqdm import tqdm
|
||||
|
||||
URL_MAP = {"vgg_lpips": "https://heibox.uni-heidelberg.de/f/607503859c864bc1b30b/?dl=1"}
|
||||
|
||||
CKPT_MAP = {"vgg_lpips": "vgg.pth"}
|
||||
|
||||
MD5_MAP = {"vgg_lpips": "d507d7349b931f0638a25a48a722f98a"}
|
||||
|
||||
|
||||
def download(url, local_path, chunk_size=1024):
|
||||
os.makedirs(os.path.split(local_path)[0], exist_ok=True)
|
||||
with requests.get(url, stream=True) as r:
|
||||
total_size = int(r.headers.get("content-length", 0))
|
||||
with tqdm(total=total_size, unit="B", unit_scale=True) as pbar:
|
||||
with open(local_path, "wb") as f:
|
||||
for data in r.iter_content(chunk_size=chunk_size):
|
||||
if data:
|
||||
f.write(data)
|
||||
pbar.update(chunk_size)
|
||||
|
||||
|
||||
def md5_hash(path):
|
||||
with open(path, "rb") as f:
|
||||
content = f.read()
|
||||
return hashlib.md5(content).hexdigest()
|
||||
|
||||
|
||||
def get_ckpt_path(name, root, check=False):
|
||||
assert name in URL_MAP
|
||||
path = os.path.join(root, CKPT_MAP[name])
|
||||
if not os.path.exists(path) or (check and not md5_hash(path) == MD5_MAP[name]):
|
||||
print("Downloading {} model from {} to {}".format(name, URL_MAP[name], path))
|
||||
download(URL_MAP[name], path)
|
||||
md5 = md5_hash(path)
|
||||
assert md5 == MD5_MAP[name], md5
|
||||
return path
|
||||
|
||||
|
||||
class KeyNotFoundError(Exception):
|
||||
def __init__(self, cause, keys=None, visited=None):
|
||||
self.cause = cause
|
||||
self.keys = keys
|
||||
self.visited = visited
|
||||
messages = list()
|
||||
if keys is not None:
|
||||
messages.append("Key not found: {}".format(keys))
|
||||
if visited is not None:
|
||||
messages.append("Visited: {}".format(visited))
|
||||
messages.append("Cause:\n{}".format(cause))
|
||||
message = "\n".join(messages)
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
def retrieve(
|
||||
list_or_dict, key, splitval="/", default=None, expand=True, pass_success=False
|
||||
):
|
||||
"""Given a nested list or dict return the desired value at key expanding
|
||||
callable nodes if necessary and :attr:`expand` is ``True``. The expansion
|
||||
is done in-place.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
list_or_dict : list or dict
|
||||
Possibly nested list or dictionary.
|
||||
key : str
|
||||
key/to/value, path like string describing all keys necessary to
|
||||
consider to get to the desired value. List indices can also be
|
||||
passed here.
|
||||
splitval : str
|
||||
String that defines the delimiter between keys of the
|
||||
different depth levels in `key`.
|
||||
default : obj
|
||||
Value returned if :attr:`key` is not found.
|
||||
expand : bool
|
||||
Whether to expand callable nodes on the path or not.
|
||||
|
||||
Returns
|
||||
-------
|
||||
The desired value or if :attr:`default` is not ``None`` and the
|
||||
:attr:`key` is not found returns ``default``.
|
||||
|
||||
Raises
|
||||
------
|
||||
Exception if ``key`` not in ``list_or_dict`` and :attr:`default` is
|
||||
``None``.
|
||||
"""
|
||||
|
||||
keys = key.split(splitval)
|
||||
|
||||
success = True
|
||||
try:
|
||||
visited = []
|
||||
parent = None
|
||||
last_key = None
|
||||
for key in keys:
|
||||
if callable(list_or_dict):
|
||||
if not expand:
|
||||
raise KeyNotFoundError(
|
||||
ValueError(
|
||||
"Trying to get past callable node with expand=False."
|
||||
),
|
||||
keys=keys,
|
||||
visited=visited,
|
||||
)
|
||||
list_or_dict = list_or_dict()
|
||||
parent[last_key] = list_or_dict
|
||||
|
||||
last_key = key
|
||||
parent = list_or_dict
|
||||
|
||||
try:
|
||||
if isinstance(list_or_dict, dict):
|
||||
list_or_dict = list_or_dict[key]
|
||||
else:
|
||||
list_or_dict = list_or_dict[int(key)]
|
||||
except (KeyError, IndexError, ValueError) as e:
|
||||
raise KeyNotFoundError(e, keys=keys, visited=visited)
|
||||
|
||||
visited += [key]
|
||||
# final expansion of retrieved value
|
||||
if expand and callable(list_or_dict):
|
||||
list_or_dict = list_or_dict()
|
||||
parent[last_key] = list_or_dict
|
||||
except KeyNotFoundError as e:
|
||||
if default is None:
|
||||
raise e
|
||||
else:
|
||||
list_or_dict = default
|
||||
success = False
|
||||
|
||||
if not pass_success:
|
||||
return list_or_dict
|
||||
else:
|
||||
return list_or_dict, success
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
config = {
|
||||
"keya": "a",
|
||||
"keyb": "b",
|
||||
"keyc": {
|
||||
"cc1": 1,
|
||||
"cc2": 2,
|
||||
},
|
||||
}
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
config = OmegaConf.create(config)
|
||||
print(config)
|
||||
retrieve(config, "keya")
|
||||
78
threestudio/utils/rasterize.py
Normal file
78
threestudio/utils/rasterize.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import nvdiffrast.torch as dr
|
||||
import torch
|
||||
|
||||
from threestudio.utils.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]:
|
||||
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"]:
|
||||
verts_homo = torch.cat(
|
||||
[verts, torch.ones([verts.shape[0], 1]).to(verts)], dim=-1
|
||||
)
|
||||
return torch.matmul(verts_homo, mvp_mtx.permute(0, 2, 1))
|
||||
|
||||
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)
|
||||
652
threestudio/utils/saving.py
Normal file
652
threestudio/utils/saving.py
Normal file
@@ -0,0 +1,652 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import cv2
|
||||
import imageio
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import torch
|
||||
import trimesh
|
||||
import wandb
|
||||
from matplotlib import cm
|
||||
from matplotlib.colors import LinearSegmentedColormap
|
||||
from PIL import Image, ImageDraw
|
||||
from pytorch_lightning.loggers import WandbLogger
|
||||
|
||||
from threestudio.models.mesh import Mesh
|
||||
from threestudio.utils.typing import *
|
||||
|
||||
|
||||
class SaverMixin:
|
||||
_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):
|
||||
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
|
||||
|
||||
def create_loggers(self, cfg_loggers: DictConfig) -> None:
|
||||
if "wandb" in cfg_loggers.keys() and cfg_loggers.wandb.enable:
|
||||
self._wandb_logger = WandbLogger(
|
||||
project=cfg_loggers.wandb.project, name=cfg_loggers.wandb.name
|
||||
)
|
||||
|
||||
def get_loggers(self) -> List:
|
||||
if self._wandb_logger:
|
||||
return [self._wandb_logger]
|
||||
else:
|
||||
return []
|
||||
|
||||
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:
|
||||
wandb.log(
|
||||
{
|
||||
name: wandb.Image(self.get_save_path(filename)),
|
||||
"trainer/global_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:
|
||||
wandb.log(
|
||||
{
|
||||
name: wandb.Image(self.get_save_path(filename)),
|
||||
"trainer/global_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])
|
||||
w = max([col.shape[1] for col in cols])
|
||||
elif align == "min":
|
||||
h = min([col.shape[0] for col in cols])
|
||||
w = min([col.shape[1] for col in cols])
|
||||
elif isinstance(align, int):
|
||||
h = align
|
||||
w = align
|
||||
elif (
|
||||
isinstance(align, tuple)
|
||||
and isinstance(align[0], int)
|
||||
and isinstance(align[1], int)
|
||||
):
|
||||
h, w = align
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported image grid align: {align}, should be min, max, int or (int, int)"
|
||||
)
|
||||
|
||||
for i in range(len(cols)):
|
||||
if cols[i].shape[0] != h or cols[i].shape[1] != w:
|
||||
cols[i] = cv2.resize(cols[i], (w, h), interpolation=cv2.INTER_LINEAR)
|
||||
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:
|
||||
wandb.log({name: wandb.Image(save_path), "trainer/global_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:
|
||||
wandb.log(
|
||||
{
|
||||
name: wandb.Video(save_path, format="mp4"),
|
||||
"trainer/global_step": step,
|
||||
}
|
||||
)
|
||||
return save_path
|
||||
|
||||
def save_mesh(self, filename, v_pos, t_pos_idx, v_tex=None, t_tex_idx=None) -> str:
|
||||
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 = False,
|
||||
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_file(self, filename, src_path) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
shutil.copyfile(src_path, save_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
|
||||
40
threestudio/utils/typing.py
Normal file
40
threestudio/utils/typing.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user