Initial media depth project backup
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import numpy as np
|
||||
from torchvision.transforms import Normalize
|
||||
from zoedepth.models.base_models.dpt_dinov2.dpt import DPT_DINOv2
|
||||
|
||||
|
||||
def denormalize(x):
|
||||
"""Reverses the imagenet normalization applied to the input.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor - shape(N,3,H,W)): input tensor
|
||||
|
||||
Returns:
|
||||
torch.Tensor - shape(N,3,H,W): Denormalized input
|
||||
"""
|
||||
mean = torch.Tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1).to(x.device)
|
||||
std = torch.Tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1).to(x.device)
|
||||
return x * std + mean
|
||||
|
||||
def get_activation(name, bank):
|
||||
def hook(model, input, output):
|
||||
bank[name] = output
|
||||
return hook
|
||||
|
||||
|
||||
class Resize(object):
|
||||
"""Resize sample to given size (width, height).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width,
|
||||
height,
|
||||
resize_target=True,
|
||||
keep_aspect_ratio=False,
|
||||
ensure_multiple_of=1,
|
||||
resize_method="lower_bound",
|
||||
):
|
||||
"""Init.
|
||||
Args:
|
||||
width (int): desired output width
|
||||
height (int): desired output height
|
||||
resize_target (bool, optional):
|
||||
True: Resize the full sample (image, mask, target).
|
||||
False: Resize image only.
|
||||
Defaults to True.
|
||||
keep_aspect_ratio (bool, optional):
|
||||
True: Keep the aspect ratio of the input sample.
|
||||
Output sample might not have the given width and height, and
|
||||
resize behaviour depends on the parameter 'resize_method'.
|
||||
Defaults to False.
|
||||
ensure_multiple_of (int, optional):
|
||||
Output width and height is constrained to be multiple of this parameter.
|
||||
Defaults to 1.
|
||||
resize_method (str, optional):
|
||||
"lower_bound": Output will be at least as large as the given size.
|
||||
"upper_bound": Output will be at max as large as the given size. (Output size might be smaller than given size.)
|
||||
"minimal": Scale as least as possible. (Output size might be smaller than given size.)
|
||||
Defaults to "lower_bound".
|
||||
"""
|
||||
print("Params passed to Resize transform:")
|
||||
print("\twidth: ", width)
|
||||
print("\theight: ", height)
|
||||
print("\tresize_target: ", resize_target)
|
||||
print("\tkeep_aspect_ratio: ", keep_aspect_ratio)
|
||||
print("\tensure_multiple_of: ", ensure_multiple_of)
|
||||
print("\tresize_method: ", resize_method)
|
||||
|
||||
self.__width = width
|
||||
self.__height = height
|
||||
|
||||
self.__keep_aspect_ratio = keep_aspect_ratio
|
||||
self.__multiple_of = ensure_multiple_of
|
||||
self.__resize_method = resize_method
|
||||
|
||||
def constrain_to_multiple_of(self, x, min_val=0, max_val=None):
|
||||
y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int)
|
||||
|
||||
if max_val is not None and y > max_val:
|
||||
y = (np.floor(x / self.__multiple_of)
|
||||
* self.__multiple_of).astype(int)
|
||||
|
||||
if y < min_val:
|
||||
y = (np.ceil(x / self.__multiple_of)
|
||||
* self.__multiple_of).astype(int)
|
||||
|
||||
return y
|
||||
|
||||
def get_size(self, width, height):
|
||||
# determine new height and width
|
||||
scale_height = self.__height / height
|
||||
scale_width = self.__width / width
|
||||
|
||||
if self.__keep_aspect_ratio:
|
||||
if self.__resize_method == "lower_bound":
|
||||
# scale such that output size is lower bound
|
||||
if scale_width > scale_height:
|
||||
# fit width
|
||||
scale_height = scale_width
|
||||
else:
|
||||
# fit height
|
||||
scale_width = scale_height
|
||||
elif self.__resize_method == "upper_bound":
|
||||
# scale such that output size is upper bound
|
||||
if scale_width < scale_height:
|
||||
# fit width
|
||||
scale_height = scale_width
|
||||
else:
|
||||
# fit height
|
||||
scale_width = scale_height
|
||||
elif self.__resize_method == "minimal":
|
||||
# scale as least as possbile
|
||||
if abs(1 - scale_width) < abs(1 - scale_height):
|
||||
# fit width
|
||||
scale_height = scale_width
|
||||
else:
|
||||
# fit height
|
||||
scale_width = scale_height
|
||||
else:
|
||||
raise ValueError(
|
||||
f"resize_method {self.__resize_method} not implemented"
|
||||
)
|
||||
|
||||
if self.__resize_method == "lower_bound":
|
||||
new_height = self.constrain_to_multiple_of(
|
||||
scale_height * height, min_val=self.__height
|
||||
)
|
||||
new_width = self.constrain_to_multiple_of(
|
||||
scale_width * width, min_val=self.__width
|
||||
)
|
||||
elif self.__resize_method == "upper_bound":
|
||||
new_height = self.constrain_to_multiple_of(
|
||||
scale_height * height, max_val=self.__height
|
||||
)
|
||||
new_width = self.constrain_to_multiple_of(
|
||||
scale_width * width, max_val=self.__width
|
||||
)
|
||||
elif self.__resize_method == "minimal":
|
||||
new_height = self.constrain_to_multiple_of(scale_height * height)
|
||||
new_width = self.constrain_to_multiple_of(scale_width * width)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"resize_method {self.__resize_method} not implemented")
|
||||
|
||||
return (new_width, new_height)
|
||||
|
||||
def __call__(self, x):
|
||||
width, height = self.get_size(*x.shape[-2:][::-1])
|
||||
return nn.functional.interpolate(x, (height, width), mode='bilinear', align_corners=True)
|
||||
|
||||
class PrepForMidas(object):
|
||||
def __init__(self, resize_mode="minimal", keep_aspect_ratio=True, img_size=384, do_resize=True):
|
||||
if isinstance(img_size, int):
|
||||
img_size = (img_size, img_size)
|
||||
net_h, net_w = img_size
|
||||
# self.normalization = Normalize(
|
||||
# mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
|
||||
self.normalization = Normalize(
|
||||
mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
|
||||
self.resizer = Resize(net_w, net_h, keep_aspect_ratio=keep_aspect_ratio, ensure_multiple_of=14, resize_method=resize_mode) \
|
||||
if do_resize else nn.Identity()
|
||||
|
||||
def __call__(self, x):
|
||||
return self.normalization(self.resizer(x))
|
||||
|
||||
|
||||
class DepthAnythingCore(nn.Module):
|
||||
def __init__(self, midas, trainable=False, fetch_features=True, layer_names=('out_conv', 'l4_rn', 'r4', 'r3', 'r2', 'r1'), freeze_bn=False, keep_aspect_ratio=True,
|
||||
img_size=384, **kwargs):
|
||||
"""Midas Base model used for multi-scale feature extraction.
|
||||
|
||||
Args:
|
||||
midas (torch.nn.Module): Midas model.
|
||||
trainable (bool, optional): Train midas model. Defaults to False.
|
||||
fetch_features (bool, optional): Extract multi-scale features. Defaults to True.
|
||||
layer_names (tuple, optional): Layers used for feature extraction. Order = (head output features, last layer features, ...decoder features). Defaults to ('out_conv', 'l4_rn', 'r4', 'r3', 'r2', 'r1').
|
||||
freeze_bn (bool, optional): Freeze BatchNorm. Generally results in better finetuning performance. Defaults to False.
|
||||
keep_aspect_ratio (bool, optional): Keep the aspect ratio of input images while resizing. Defaults to True.
|
||||
img_size (int, tuple, optional): Input resolution. Defaults to 384.
|
||||
"""
|
||||
super().__init__()
|
||||
self.core = midas
|
||||
self.output_channels = None
|
||||
self.core_out = {}
|
||||
self.trainable = trainable
|
||||
self.fetch_features = fetch_features
|
||||
# midas.scratch.output_conv = nn.Identity()
|
||||
self.handles = []
|
||||
# self.layer_names = ['out_conv','l4_rn', 'r4', 'r3', 'r2', 'r1']
|
||||
self.layer_names = layer_names
|
||||
|
||||
self.set_trainable(trainable)
|
||||
self.set_fetch_features(fetch_features)
|
||||
|
||||
self.prep = PrepForMidas(keep_aspect_ratio=keep_aspect_ratio,
|
||||
img_size=img_size, do_resize=kwargs.get('do_resize', True))
|
||||
|
||||
if freeze_bn:
|
||||
self.freeze_bn()
|
||||
|
||||
def set_trainable(self, trainable):
|
||||
self.trainable = trainable
|
||||
if trainable:
|
||||
self.unfreeze()
|
||||
else:
|
||||
self.freeze()
|
||||
return self
|
||||
|
||||
def set_fetch_features(self, fetch_features):
|
||||
self.fetch_features = fetch_features
|
||||
if fetch_features:
|
||||
if len(self.handles) == 0:
|
||||
self.attach_hooks(self.core)
|
||||
else:
|
||||
self.remove_hooks()
|
||||
return self
|
||||
|
||||
def freeze(self):
|
||||
for p in self.parameters():
|
||||
p.requires_grad = False
|
||||
self.trainable = False
|
||||
return self
|
||||
|
||||
def unfreeze(self):
|
||||
for p in self.parameters():
|
||||
p.requires_grad = True
|
||||
self.trainable = True
|
||||
return self
|
||||
|
||||
def freeze_bn(self):
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.BatchNorm2d):
|
||||
m.eval()
|
||||
return self
|
||||
|
||||
def forward(self, x, denorm=False, return_rel_depth=False):
|
||||
# print('input to midas:', x.shape)
|
||||
with torch.no_grad():
|
||||
if denorm:
|
||||
x = denormalize(x)
|
||||
x = self.prep(x)
|
||||
|
||||
with torch.set_grad_enabled(self.trainable):
|
||||
|
||||
rel_depth = self.core(x)
|
||||
if not self.fetch_features:
|
||||
return rel_depth
|
||||
out = [self.core_out[k] for k in self.layer_names]
|
||||
|
||||
if return_rel_depth:
|
||||
return rel_depth, out
|
||||
return out
|
||||
|
||||
def get_rel_pos_params(self):
|
||||
for name, p in self.core.pretrained.named_parameters():
|
||||
if "pos_embed" in name:
|
||||
yield p
|
||||
|
||||
def get_enc_params_except_rel_pos(self):
|
||||
for name, p in self.core.pretrained.named_parameters():
|
||||
if "pos_embed" not in name:
|
||||
yield p
|
||||
|
||||
def freeze_encoder(self, freeze_rel_pos=False):
|
||||
if freeze_rel_pos:
|
||||
for p in self.core.pretrained.parameters():
|
||||
p.requires_grad = False
|
||||
else:
|
||||
for p in self.get_enc_params_except_rel_pos():
|
||||
p.requires_grad = False
|
||||
return self
|
||||
|
||||
def attach_hooks(self, midas):
|
||||
if len(self.handles) > 0:
|
||||
self.remove_hooks()
|
||||
if "out_conv" in self.layer_names:
|
||||
self.handles.append(list(midas.depth_head.scratch.output_conv2.children())[
|
||||
1].register_forward_hook(get_activation("out_conv", self.core_out)))
|
||||
if "r4" in self.layer_names:
|
||||
self.handles.append(midas.depth_head.scratch.refinenet4.register_forward_hook(
|
||||
get_activation("r4", self.core_out)))
|
||||
if "r3" in self.layer_names:
|
||||
self.handles.append(midas.depth_head.scratch.refinenet3.register_forward_hook(
|
||||
get_activation("r3", self.core_out)))
|
||||
if "r2" in self.layer_names:
|
||||
self.handles.append(midas.depth_head.scratch.refinenet2.register_forward_hook(
|
||||
get_activation("r2", self.core_out)))
|
||||
if "r1" in self.layer_names:
|
||||
self.handles.append(midas.depth_head.scratch.refinenet1.register_forward_hook(
|
||||
get_activation("r1", self.core_out)))
|
||||
if "l4_rn" in self.layer_names:
|
||||
self.handles.append(midas.depth_head.scratch.layer4_rn.register_forward_hook(
|
||||
get_activation("l4_rn", self.core_out)))
|
||||
|
||||
return self
|
||||
|
||||
def remove_hooks(self):
|
||||
for h in self.handles:
|
||||
h.remove()
|
||||
return self
|
||||
|
||||
def __del__(self):
|
||||
self.remove_hooks()
|
||||
|
||||
def set_output_channels(self):
|
||||
self.output_channels = [256, 256, 256, 256, 256]
|
||||
|
||||
@staticmethod
|
||||
def build(midas_model_type="dinov2_large", train_midas=False, use_pretrained_midas=True, fetch_features=False, freeze_bn=True, force_keep_ar=False, force_reload=False, **kwargs):
|
||||
if "img_size" in kwargs:
|
||||
kwargs = DepthAnythingCore.parse_img_size(kwargs)
|
||||
img_size = kwargs.pop("img_size", [384, 384])
|
||||
|
||||
depth_anything = DPT_DINOv2(out_channels=[256, 512, 1024, 1024], use_clstoken=False)
|
||||
|
||||
state_dict = torch.load('./checkpoints/depth_anything_vitl14.pth', map_location='cpu')
|
||||
depth_anything.load_state_dict(state_dict)
|
||||
|
||||
kwargs.update({'keep_aspect_ratio': force_keep_ar})
|
||||
|
||||
depth_anything_core = DepthAnythingCore(depth_anything, trainable=train_midas, fetch_features=fetch_features,
|
||||
freeze_bn=freeze_bn, img_size=img_size, **kwargs)
|
||||
|
||||
depth_anything_core.set_output_channels()
|
||||
return depth_anything_core
|
||||
|
||||
@staticmethod
|
||||
def parse_img_size(config):
|
||||
assert 'img_size' in config
|
||||
if isinstance(config['img_size'], str):
|
||||
assert "," in config['img_size'], "img_size should be a string with comma separated img_size=H,W"
|
||||
config['img_size'] = list(map(int, config['img_size'].split(",")))
|
||||
assert len(
|
||||
config['img_size']) == 2, "img_size should be a string with comma separated img_size=H,W"
|
||||
elif isinstance(config['img_size'], int):
|
||||
config['img_size'] = [config['img_size'], config['img_size']]
|
||||
else:
|
||||
assert isinstance(config['img_size'], list) and len(
|
||||
config['img_size']) == 2, "img_size should be a list of H,W"
|
||||
return config
|
||||
|
||||
|
||||
nchannels2models = {
|
||||
tuple([256]*5): ["DPT_BEiT_L_384", "DPT_BEiT_L_512", "DPT_BEiT_B_384", "DPT_SwinV2_L_384", "DPT_SwinV2_B_384", "DPT_SwinV2_T_256", "DPT_Large", "DPT_Hybrid"],
|
||||
(512, 256, 128, 64, 64): ["MiDaS_small"]
|
||||
}
|
||||
|
||||
# Model name to number of output channels
|
||||
MIDAS_SETTINGS = {m: k for k, v in nchannels2models.items()
|
||||
for m in v
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
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
|
||||
if len(in_shape) >= 4:
|
||||
out_shape4 = out_shape
|
||||
|
||||
if expand:
|
||||
out_shape1 = out_shape
|
||||
out_shape2 = out_shape*2
|
||||
out_shape3 = out_shape*4
|
||||
if len(in_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
|
||||
)
|
||||
if len(in_shape) >= 4:
|
||||
scratch.layer4_rn = nn.Conv2d(
|
||||
in_shape[3], out_shape4, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
|
||||
return scratch
|
||||
|
||||
|
||||
class ResidualConvUnit(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)
|
||||
|
||||
|
||||
class FeatureFusionBlock(nn.Module):
|
||||
"""Feature fusion block.
|
||||
"""
|
||||
|
||||
def __init__(self, features, activation, deconv=False, bn=False, expand=False, align_corners=True, size=None):
|
||||
"""Init.
|
||||
|
||||
Args:
|
||||
features (int): number of features
|
||||
"""
|
||||
super(FeatureFusionBlock, 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(features, activation, bn)
|
||||
self.resConfUnit2 = ResidualConvUnit(features, activation, bn)
|
||||
|
||||
self.skip_add = nn.quantized.FloatFunctional()
|
||||
|
||||
self.size=size
|
||||
|
||||
def forward(self, *xs, size=None):
|
||||
"""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 = self.resConfUnit2(output)
|
||||
|
||||
if (size is None) and (self.size is None):
|
||||
modifier = {"scale_factor": 2}
|
||||
elif size is None:
|
||||
modifier = {"size": self.size}
|
||||
else:
|
||||
modifier = {"size": size}
|
||||
|
||||
output = nn.functional.interpolate(
|
||||
output, **modifier, mode="bilinear", align_corners=self.align_corners
|
||||
)
|
||||
|
||||
output = self.out_conv(output)
|
||||
|
||||
return output
|
||||
@@ -0,0 +1,157 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from .blocks import FeatureFusionBlock, _make_scratch
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
def _make_fusion_block(features, use_bn, size = None):
|
||||
return FeatureFusionBlock(
|
||||
features,
|
||||
nn.ReLU(False),
|
||||
deconv=False,
|
||||
bn=use_bn,
|
||||
expand=False,
|
||||
align_corners=True,
|
||||
size=size,
|
||||
)
|
||||
|
||||
|
||||
class DPTHead(nn.Module):
|
||||
def __init__(self, in_channels, features=256, use_bn=False, out_channels=[256, 512, 1024, 1024], use_clstoken=False):
|
||||
super(DPTHead, self).__init__()
|
||||
|
||||
self.use_clstoken = use_clstoken
|
||||
|
||||
# out_channels = [in_channels // 8, in_channels // 4, in_channels // 2, in_channels]
|
||||
# out_channels = [in_channels // 4, in_channels // 2, in_channels, in_channels]
|
||||
# out_channels = [in_channels, in_channels, in_channels, in_channels]
|
||||
|
||||
self.projects = nn.ModuleList([
|
||||
nn.Conv2d(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channel,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
) for out_channel in out_channels
|
||||
])
|
||||
|
||||
self.resize_layers = nn.ModuleList([
|
||||
nn.ConvTranspose2d(
|
||||
in_channels=out_channels[0],
|
||||
out_channels=out_channels[0],
|
||||
kernel_size=4,
|
||||
stride=4,
|
||||
padding=0),
|
||||
nn.ConvTranspose2d(
|
||||
in_channels=out_channels[1],
|
||||
out_channels=out_channels[1],
|
||||
kernel_size=2,
|
||||
stride=2,
|
||||
padding=0),
|
||||
nn.Identity(),
|
||||
nn.Conv2d(
|
||||
in_channels=out_channels[3],
|
||||
out_channels=out_channels[3],
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1)
|
||||
])
|
||||
|
||||
if use_clstoken:
|
||||
self.readout_projects = nn.ModuleList()
|
||||
for _ in range(len(self.projects)):
|
||||
self.readout_projects.append(
|
||||
nn.Sequential(
|
||||
nn.Linear(2 * in_channels, in_channels),
|
||||
nn.GELU()))
|
||||
|
||||
self.scratch = _make_scratch(
|
||||
out_channels,
|
||||
features,
|
||||
groups=1,
|
||||
expand=False,
|
||||
)
|
||||
|
||||
self.scratch.stem_transpose = None
|
||||
|
||||
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)
|
||||
|
||||
head_features_1 = features
|
||||
head_features_2 = 32
|
||||
|
||||
self.scratch.output_conv1 = nn.Conv2d(head_features_1, head_features_1 // 2, kernel_size=3, stride=1, padding=1)
|
||||
|
||||
self.scratch.output_conv2 = nn.Sequential(
|
||||
nn.Conv2d(head_features_1 // 2, head_features_2, kernel_size=3, stride=1, padding=1),
|
||||
nn.ReLU(True),
|
||||
nn.Conv2d(head_features_2, 1, kernel_size=1, stride=1, padding=0),
|
||||
nn.ReLU(True),
|
||||
nn.Identity(),
|
||||
)
|
||||
|
||||
def forward(self, out_features, patch_h, patch_w):
|
||||
out = []
|
||||
for i, x in enumerate(out_features):
|
||||
if self.use_clstoken:
|
||||
x, cls_token = x[0], x[1]
|
||||
readout = cls_token.unsqueeze(1).expand_as(x)
|
||||
x = self.readout_projects[i](torch.cat((x, readout), -1))
|
||||
else:
|
||||
x = x[0]
|
||||
|
||||
x = x.permute(0, 2, 1).reshape((x.shape[0], x.shape[-1], patch_h, patch_w))
|
||||
|
||||
x = self.projects[i](x)
|
||||
x = self.resize_layers[i](x)
|
||||
|
||||
out.append(x)
|
||||
|
||||
layer_1, layer_2, layer_3, layer_4 = out
|
||||
|
||||
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, size=layer_3_rn.shape[2:])
|
||||
path_3 = self.scratch.refinenet3(path_4, layer_3_rn, size=layer_2_rn.shape[2:])
|
||||
path_2 = self.scratch.refinenet2(path_3, layer_2_rn, size=layer_1_rn.shape[2:])
|
||||
path_1 = self.scratch.refinenet1(path_2, layer_1_rn)
|
||||
|
||||
out = self.scratch.output_conv1(path_1)
|
||||
out = F.interpolate(out, (int(patch_h * 14), int(patch_w * 14)), mode="bilinear", align_corners=True)
|
||||
out = self.scratch.output_conv2(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class DPT_DINOv2(nn.Module):
|
||||
def __init__(self, encoder='vitl', features=256, use_bn=False, out_channels=[256, 512, 1024, 1024], use_clstoken=False):
|
||||
|
||||
super(DPT_DINOv2, self).__init__()
|
||||
|
||||
torch.manual_seed(1)
|
||||
|
||||
self.pretrained = torch.hub.load('../torchhub/facebookresearch_dinov2_main', 'dinov2_{:}14'.format(encoder), source='local', pretrained=False)
|
||||
|
||||
dim = self.pretrained.blocks[0].attn.qkv.in_features
|
||||
|
||||
self.depth_head = DPTHead(dim, features, use_bn, out_channels=out_channels, use_clstoken=use_clstoken)
|
||||
|
||||
def forward(self, x):
|
||||
h, w = x.shape[-2:]
|
||||
|
||||
features = self.pretrained.get_intermediate_layers(x, 4, return_class_token=True)
|
||||
|
||||
patch_h, patch_w = h // 14, w // 14
|
||||
|
||||
depth = self.depth_head(features, patch_h, patch_w)
|
||||
depth = F.interpolate(depth, size=(h, w), mode="bilinear", align_corners=True)
|
||||
depth = F.relu(depth)
|
||||
|
||||
return depth.squeeze(1)
|
||||
@@ -0,0 +1,380 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import numpy as np
|
||||
from torchvision.transforms import Normalize
|
||||
|
||||
|
||||
def denormalize(x):
|
||||
"""Reverses the imagenet normalization applied to the input.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor - shape(N,3,H,W)): input tensor
|
||||
|
||||
Returns:
|
||||
torch.Tensor - shape(N,3,H,W): Denormalized input
|
||||
"""
|
||||
mean = torch.Tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1).to(x.device)
|
||||
std = torch.Tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1).to(x.device)
|
||||
return x * std + mean
|
||||
|
||||
def get_activation(name, bank):
|
||||
def hook(model, input, output):
|
||||
bank[name] = output
|
||||
return hook
|
||||
|
||||
|
||||
class Resize(object):
|
||||
"""Resize sample to given size (width, height).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width,
|
||||
height,
|
||||
resize_target=True,
|
||||
keep_aspect_ratio=False,
|
||||
ensure_multiple_of=1,
|
||||
resize_method="lower_bound",
|
||||
):
|
||||
"""Init.
|
||||
Args:
|
||||
width (int): desired output width
|
||||
height (int): desired output height
|
||||
resize_target (bool, optional):
|
||||
True: Resize the full sample (image, mask, target).
|
||||
False: Resize image only.
|
||||
Defaults to True.
|
||||
keep_aspect_ratio (bool, optional):
|
||||
True: Keep the aspect ratio of the input sample.
|
||||
Output sample might not have the given width and height, and
|
||||
resize behaviour depends on the parameter 'resize_method'.
|
||||
Defaults to False.
|
||||
ensure_multiple_of (int, optional):
|
||||
Output width and height is constrained to be multiple of this parameter.
|
||||
Defaults to 1.
|
||||
resize_method (str, optional):
|
||||
"lower_bound": Output will be at least as large as the given size.
|
||||
"upper_bound": Output will be at max as large as the given size. (Output size might be smaller than given size.)
|
||||
"minimal": Scale as least as possible. (Output size might be smaller than given size.)
|
||||
Defaults to "lower_bound".
|
||||
"""
|
||||
print("Params passed to Resize transform:")
|
||||
print("\twidth: ", width)
|
||||
print("\theight: ", height)
|
||||
print("\tresize_target: ", resize_target)
|
||||
print("\tkeep_aspect_ratio: ", keep_aspect_ratio)
|
||||
print("\tensure_multiple_of: ", ensure_multiple_of)
|
||||
print("\tresize_method: ", resize_method)
|
||||
|
||||
self.__width = width
|
||||
self.__height = height
|
||||
|
||||
self.__keep_aspect_ratio = keep_aspect_ratio
|
||||
self.__multiple_of = ensure_multiple_of
|
||||
self.__resize_method = resize_method
|
||||
|
||||
def constrain_to_multiple_of(self, x, min_val=0, max_val=None):
|
||||
y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int)
|
||||
|
||||
if max_val is not None and y > max_val:
|
||||
y = (np.floor(x / self.__multiple_of)
|
||||
* self.__multiple_of).astype(int)
|
||||
|
||||
if y < min_val:
|
||||
y = (np.ceil(x / self.__multiple_of)
|
||||
* self.__multiple_of).astype(int)
|
||||
|
||||
return y
|
||||
|
||||
def get_size(self, width, height):
|
||||
# determine new height and width
|
||||
scale_height = self.__height / height
|
||||
scale_width = self.__width / width
|
||||
|
||||
if self.__keep_aspect_ratio:
|
||||
if self.__resize_method == "lower_bound":
|
||||
# scale such that output size is lower bound
|
||||
if scale_width > scale_height:
|
||||
# fit width
|
||||
scale_height = scale_width
|
||||
else:
|
||||
# fit height
|
||||
scale_width = scale_height
|
||||
elif self.__resize_method == "upper_bound":
|
||||
# scale such that output size is upper bound
|
||||
if scale_width < scale_height:
|
||||
# fit width
|
||||
scale_height = scale_width
|
||||
else:
|
||||
# fit height
|
||||
scale_width = scale_height
|
||||
elif self.__resize_method == "minimal":
|
||||
# scale as least as possbile
|
||||
if abs(1 - scale_width) < abs(1 - scale_height):
|
||||
# fit width
|
||||
scale_height = scale_width
|
||||
else:
|
||||
# fit height
|
||||
scale_width = scale_height
|
||||
else:
|
||||
raise ValueError(
|
||||
f"resize_method {self.__resize_method} not implemented"
|
||||
)
|
||||
|
||||
if self.__resize_method == "lower_bound":
|
||||
new_height = self.constrain_to_multiple_of(
|
||||
scale_height * height, min_val=self.__height
|
||||
)
|
||||
new_width = self.constrain_to_multiple_of(
|
||||
scale_width * width, min_val=self.__width
|
||||
)
|
||||
elif self.__resize_method == "upper_bound":
|
||||
new_height = self.constrain_to_multiple_of(
|
||||
scale_height * height, max_val=self.__height
|
||||
)
|
||||
new_width = self.constrain_to_multiple_of(
|
||||
scale_width * width, max_val=self.__width
|
||||
)
|
||||
elif self.__resize_method == "minimal":
|
||||
new_height = self.constrain_to_multiple_of(scale_height * height)
|
||||
new_width = self.constrain_to_multiple_of(scale_width * width)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"resize_method {self.__resize_method} not implemented")
|
||||
|
||||
return (new_width, new_height)
|
||||
|
||||
def __call__(self, x):
|
||||
width, height = self.get_size(*x.shape[-2:][::-1])
|
||||
return nn.functional.interpolate(x, (height, width), mode='bilinear', align_corners=True)
|
||||
|
||||
class PrepForMidas(object):
|
||||
def __init__(self, resize_mode="minimal", keep_aspect_ratio=True, img_size=384, do_resize=True):
|
||||
if isinstance(img_size, int):
|
||||
img_size = (img_size, img_size)
|
||||
net_h, net_w = img_size
|
||||
self.normalization = Normalize(
|
||||
mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
|
||||
self.resizer = Resize(net_w, net_h, keep_aspect_ratio=keep_aspect_ratio, ensure_multiple_of=32, resize_method=resize_mode) \
|
||||
if do_resize else nn.Identity()
|
||||
|
||||
def __call__(self, x):
|
||||
return self.normalization(self.resizer(x))
|
||||
|
||||
|
||||
class MidasCore(nn.Module):
|
||||
def __init__(self, midas, trainable=False, fetch_features=True, layer_names=('out_conv', 'l4_rn', 'r4', 'r3', 'r2', 'r1'), freeze_bn=False, keep_aspect_ratio=True,
|
||||
img_size=384, **kwargs):
|
||||
"""Midas Base model used for multi-scale feature extraction.
|
||||
|
||||
Args:
|
||||
midas (torch.nn.Module): Midas model.
|
||||
trainable (bool, optional): Train midas model. Defaults to False.
|
||||
fetch_features (bool, optional): Extract multi-scale features. Defaults to True.
|
||||
layer_names (tuple, optional): Layers used for feature extraction. Order = (head output features, last layer features, ...decoder features). Defaults to ('out_conv', 'l4_rn', 'r4', 'r3', 'r2', 'r1').
|
||||
freeze_bn (bool, optional): Freeze BatchNorm. Generally results in better finetuning performance. Defaults to False.
|
||||
keep_aspect_ratio (bool, optional): Keep the aspect ratio of input images while resizing. Defaults to True.
|
||||
img_size (int, tuple, optional): Input resolution. Defaults to 384.
|
||||
"""
|
||||
super().__init__()
|
||||
self.core = midas
|
||||
self.output_channels = None
|
||||
self.core_out = {}
|
||||
self.trainable = trainable
|
||||
self.fetch_features = fetch_features
|
||||
# midas.scratch.output_conv = nn.Identity()
|
||||
self.handles = []
|
||||
# self.layer_names = ['out_conv','l4_rn', 'r4', 'r3', 'r2', 'r1']
|
||||
self.layer_names = layer_names
|
||||
|
||||
self.set_trainable(trainable)
|
||||
self.set_fetch_features(fetch_features)
|
||||
|
||||
self.prep = PrepForMidas(keep_aspect_ratio=keep_aspect_ratio,
|
||||
img_size=img_size, do_resize=kwargs.get('do_resize', True))
|
||||
|
||||
if freeze_bn:
|
||||
self.freeze_bn()
|
||||
|
||||
def set_trainable(self, trainable):
|
||||
self.trainable = trainable
|
||||
if trainable:
|
||||
self.unfreeze()
|
||||
else:
|
||||
self.freeze()
|
||||
return self
|
||||
|
||||
def set_fetch_features(self, fetch_features):
|
||||
self.fetch_features = fetch_features
|
||||
if fetch_features:
|
||||
if len(self.handles) == 0:
|
||||
self.attach_hooks(self.core)
|
||||
else:
|
||||
self.remove_hooks()
|
||||
return self
|
||||
|
||||
def freeze(self):
|
||||
for p in self.parameters():
|
||||
p.requires_grad = False
|
||||
self.trainable = False
|
||||
return self
|
||||
|
||||
def unfreeze(self):
|
||||
for p in self.parameters():
|
||||
p.requires_grad = True
|
||||
self.trainable = True
|
||||
return self
|
||||
|
||||
def freeze_bn(self):
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.BatchNorm2d):
|
||||
m.eval()
|
||||
return self
|
||||
|
||||
def forward(self, x, denorm=False, return_rel_depth=False):
|
||||
# print('input to midas:', x.shape)
|
||||
with torch.no_grad():
|
||||
if denorm:
|
||||
x = denormalize(x)
|
||||
x = self.prep(x)
|
||||
# print("Shape after prep: ", x.shape)
|
||||
# print('pre-processed:', x.shape)
|
||||
|
||||
with torch.set_grad_enabled(self.trainable):
|
||||
|
||||
# print("Input size to Midascore", x.shape)
|
||||
rel_depth = self.core(x)
|
||||
# print("Output from midas shape", rel_depth.shape)
|
||||
if not self.fetch_features:
|
||||
return rel_depth
|
||||
out = [self.core_out[k] for k in self.layer_names]
|
||||
|
||||
if return_rel_depth:
|
||||
return rel_depth, out
|
||||
return out
|
||||
|
||||
def get_rel_pos_params(self):
|
||||
for name, p in self.core.pretrained.named_parameters():
|
||||
if "relative_position" in name:
|
||||
yield p
|
||||
|
||||
def get_enc_params_except_rel_pos(self):
|
||||
for name, p in self.core.pretrained.named_parameters():
|
||||
if "relative_position" not in name:
|
||||
yield p
|
||||
|
||||
def freeze_encoder(self, freeze_rel_pos=False):
|
||||
if freeze_rel_pos:
|
||||
for p in self.core.pretrained.parameters():
|
||||
p.requires_grad = False
|
||||
else:
|
||||
for p in self.get_enc_params_except_rel_pos():
|
||||
p.requires_grad = False
|
||||
return self
|
||||
|
||||
def attach_hooks(self, midas):
|
||||
if len(self.handles) > 0:
|
||||
self.remove_hooks()
|
||||
if "out_conv" in self.layer_names:
|
||||
self.handles.append(list(midas.scratch.output_conv.children())[
|
||||
3].register_forward_hook(get_activation("out_conv", self.core_out)))
|
||||
if "r4" in self.layer_names:
|
||||
self.handles.append(midas.scratch.refinenet4.register_forward_hook(
|
||||
get_activation("r4", self.core_out)))
|
||||
if "r3" in self.layer_names:
|
||||
self.handles.append(midas.scratch.refinenet3.register_forward_hook(
|
||||
get_activation("r3", self.core_out)))
|
||||
if "r2" in self.layer_names:
|
||||
self.handles.append(midas.scratch.refinenet2.register_forward_hook(
|
||||
get_activation("r2", self.core_out)))
|
||||
if "r1" in self.layer_names:
|
||||
self.handles.append(midas.scratch.refinenet1.register_forward_hook(
|
||||
get_activation("r1", self.core_out)))
|
||||
if "l4_rn" in self.layer_names:
|
||||
self.handles.append(midas.scratch.layer4_rn.register_forward_hook(
|
||||
get_activation("l4_rn", self.core_out)))
|
||||
|
||||
return self
|
||||
|
||||
def remove_hooks(self):
|
||||
for h in self.handles:
|
||||
h.remove()
|
||||
return self
|
||||
|
||||
def __del__(self):
|
||||
self.remove_hooks()
|
||||
|
||||
def set_output_channels(self, model_type):
|
||||
self.output_channels = MIDAS_SETTINGS[model_type]
|
||||
|
||||
@staticmethod
|
||||
def build(midas_model_type="DPT_BEiT_L_384", train_midas=False, use_pretrained_midas=True, fetch_features=False, freeze_bn=True, force_keep_ar=False, force_reload=False, **kwargs):
|
||||
if midas_model_type not in MIDAS_SETTINGS:
|
||||
raise ValueError(
|
||||
f"Invalid model type: {midas_model_type}. Must be one of {list(MIDAS_SETTINGS.keys())}")
|
||||
if "img_size" in kwargs:
|
||||
kwargs = MidasCore.parse_img_size(kwargs)
|
||||
img_size = kwargs.pop("img_size", [384, 384])
|
||||
# print("img_size", img_size)
|
||||
midas = torch.hub.load("intel-isl/MiDaS", midas_model_type,
|
||||
pretrained=use_pretrained_midas, force_reload=force_reload)
|
||||
kwargs.update({'keep_aspect_ratio': force_keep_ar})
|
||||
midas_core = MidasCore(midas, trainable=train_midas, fetch_features=fetch_features,
|
||||
freeze_bn=freeze_bn, img_size=img_size, **kwargs)
|
||||
midas_core.set_output_channels(midas_model_type)
|
||||
return midas_core
|
||||
|
||||
@staticmethod
|
||||
def build_from_config(config):
|
||||
return MidasCore.build(**config)
|
||||
|
||||
@staticmethod
|
||||
def parse_img_size(config):
|
||||
assert 'img_size' in config
|
||||
if isinstance(config['img_size'], str):
|
||||
assert "," in config['img_size'], "img_size should be a string with comma separated img_size=H,W"
|
||||
config['img_size'] = list(map(int, config['img_size'].split(",")))
|
||||
assert len(
|
||||
config['img_size']) == 2, "img_size should be a string with comma separated img_size=H,W"
|
||||
elif isinstance(config['img_size'], int):
|
||||
config['img_size'] = [config['img_size'], config['img_size']]
|
||||
else:
|
||||
assert isinstance(config['img_size'], list) and len(
|
||||
config['img_size']) == 2, "img_size should be a list of H,W"
|
||||
return config
|
||||
|
||||
|
||||
nchannels2models = {
|
||||
tuple([256]*5): ["DPT_BEiT_L_384", "DPT_BEiT_L_512", "DPT_BEiT_B_384", "DPT_SwinV2_L_384", "DPT_SwinV2_B_384", "DPT_SwinV2_T_256", "DPT_Large", "DPT_Hybrid"],
|
||||
(512, 256, 128, 64, 64): ["MiDaS_small"]
|
||||
}
|
||||
|
||||
# Model name to number of output channels
|
||||
MIDAS_SETTINGS = {m: k for k, v in nchannels2models.items()
|
||||
for m in v
|
||||
}
|
||||
# print('MIDAS_SETTINGS:', MIDAS_SETTINGS)
|
||||
@@ -0,0 +1,51 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
from importlib import import_module
|
||||
from zoedepth.models.depth_model import DepthModel
|
||||
|
||||
def build_model(config) -> DepthModel:
|
||||
"""Builds a model from a config. The model is specified by the model name and version in the config. The model is then constructed using the build_from_config function of the model interface.
|
||||
This function should be used to construct models for training and evaluation.
|
||||
|
||||
Args:
|
||||
config (dict): Config dict. Config is constructed in utils/config.py. Each model has its own config file(s) saved in its root model folder.
|
||||
|
||||
Returns:
|
||||
torch.nn.Module: Model corresponding to name and version as specified in config
|
||||
"""
|
||||
module_name = f"zoedepth.models.{config.model}"
|
||||
try:
|
||||
module = import_module(module_name)
|
||||
except ModuleNotFoundError as e:
|
||||
# print the original error message
|
||||
print(e)
|
||||
raise ValueError(
|
||||
f"Model {config.model} not found. Refer above error for details.") from e
|
||||
try:
|
||||
get_version = getattr(module, "get_version")
|
||||
except AttributeError as e:
|
||||
raise ValueError(
|
||||
f"Model {config.model} has no get_version function.") from e
|
||||
return get_version(config.version_name).build_from_config(config)
|
||||
@@ -0,0 +1,152 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from torchvision import transforms
|
||||
import PIL.Image
|
||||
from PIL import Image
|
||||
from typing import Union
|
||||
|
||||
|
||||
class DepthModel(nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.device = 'cpu'
|
||||
|
||||
def to(self, device) -> nn.Module:
|
||||
self.device = device
|
||||
return super().to(device)
|
||||
|
||||
def forward(self, x, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
def _infer(self, x: torch.Tensor):
|
||||
"""
|
||||
Inference interface for the model
|
||||
Args:
|
||||
x (torch.Tensor): input tensor of shape (b, c, h, w)
|
||||
Returns:
|
||||
torch.Tensor: output tensor of shape (b, 1, h, w)
|
||||
"""
|
||||
return self(x)['metric_depth']
|
||||
|
||||
def _infer_with_pad_aug(self, x: torch.Tensor, pad_input: bool=True, fh: float=3, fw: float=3, upsampling_mode: str='bicubic', padding_mode="reflect", **kwargs) -> torch.Tensor:
|
||||
"""
|
||||
Inference interface for the model with padding augmentation
|
||||
Padding augmentation fixes the boundary artifacts in the output depth map.
|
||||
Boundary artifacts are sometimes caused by the fact that the model is trained on NYU raw dataset which has a black or white border around the image.
|
||||
This augmentation pads the input image and crops the prediction back to the original size / view.
|
||||
|
||||
Note: This augmentation is not required for the models trained with 'avoid_boundary'=True.
|
||||
Args:
|
||||
x (torch.Tensor): input tensor of shape (b, c, h, w)
|
||||
pad_input (bool, optional): whether to pad the input or not. Defaults to True.
|
||||
fh (float, optional): height padding factor. The padding is calculated as sqrt(h/2) * fh. Defaults to 3.
|
||||
fw (float, optional): width padding factor. The padding is calculated as sqrt(w/2) * fw. Defaults to 3.
|
||||
upsampling_mode (str, optional): upsampling mode. Defaults to 'bicubic'.
|
||||
padding_mode (str, optional): padding mode. Defaults to "reflect".
|
||||
Returns:
|
||||
torch.Tensor: output tensor of shape (b, 1, h, w)
|
||||
"""
|
||||
# assert x is nchw and c = 3
|
||||
assert x.dim() == 4, "x must be 4 dimensional, got {}".format(x.dim())
|
||||
assert x.shape[1] == 3, "x must have 3 channels, got {}".format(x.shape[1])
|
||||
|
||||
if pad_input:
|
||||
assert fh > 0 or fw > 0, "atlease one of fh and fw must be greater than 0"
|
||||
pad_h = int(np.sqrt(x.shape[2]/2) * fh)
|
||||
pad_w = int(np.sqrt(x.shape[3]/2) * fw)
|
||||
padding = [pad_w, pad_w]
|
||||
if pad_h > 0:
|
||||
padding += [pad_h, pad_h]
|
||||
|
||||
x = F.pad(x, padding, mode=padding_mode, **kwargs)
|
||||
out = self._infer(x)
|
||||
if out.shape[-2:] != x.shape[-2:]:
|
||||
out = F.interpolate(out, size=(x.shape[2], x.shape[3]), mode=upsampling_mode, align_corners=False)
|
||||
if pad_input:
|
||||
# crop to the original size, handling the case where pad_h and pad_w is 0
|
||||
if pad_h > 0:
|
||||
out = out[:, :, pad_h:-pad_h,:]
|
||||
if pad_w > 0:
|
||||
out = out[:, :, :, pad_w:-pad_w]
|
||||
return out
|
||||
|
||||
def infer_with_flip_aug(self, x, pad_input: bool=True, **kwargs) -> torch.Tensor:
|
||||
"""
|
||||
Inference interface for the model with horizontal flip augmentation
|
||||
Horizontal flip augmentation improves the accuracy of the model by averaging the output of the model with and without horizontal flip.
|
||||
Args:
|
||||
x (torch.Tensor): input tensor of shape (b, c, h, w)
|
||||
pad_input (bool, optional): whether to use padding augmentation. Defaults to True.
|
||||
Returns:
|
||||
torch.Tensor: output tensor of shape (b, 1, h, w)
|
||||
"""
|
||||
# infer with horizontal flip and average
|
||||
out = self._infer_with_pad_aug(x, pad_input=pad_input, **kwargs)
|
||||
out_flip = self._infer_with_pad_aug(torch.flip(x, dims=[3]), pad_input=pad_input, **kwargs)
|
||||
out = (out + torch.flip(out_flip, dims=[3])) / 2
|
||||
return out
|
||||
|
||||
def infer(self, x, pad_input: bool=True, with_flip_aug: bool=True, **kwargs) -> torch.Tensor:
|
||||
"""
|
||||
Inference interface for the model
|
||||
Args:
|
||||
x (torch.Tensor): input tensor of shape (b, c, h, w)
|
||||
pad_input (bool, optional): whether to use padding augmentation. Defaults to True.
|
||||
with_flip_aug (bool, optional): whether to use horizontal flip augmentation. Defaults to True.
|
||||
Returns:
|
||||
torch.Tensor: output tensor of shape (b, 1, h, w)
|
||||
"""
|
||||
if with_flip_aug:
|
||||
return self.infer_with_flip_aug(x, pad_input=pad_input, **kwargs)
|
||||
else:
|
||||
return self._infer_with_pad_aug(x, pad_input=pad_input, **kwargs)
|
||||
|
||||
@torch.no_grad()
|
||||
def infer_pil(self, pil_img, pad_input: bool=True, with_flip_aug: bool=True, output_type: str="numpy", **kwargs) -> Union[np.ndarray, PIL.Image.Image, torch.Tensor]:
|
||||
"""
|
||||
Inference interface for the model for PIL image
|
||||
Args:
|
||||
pil_img (PIL.Image.Image): input PIL image
|
||||
pad_input (bool, optional): whether to use padding augmentation. Defaults to True.
|
||||
with_flip_aug (bool, optional): whether to use horizontal flip augmentation. Defaults to True.
|
||||
output_type (str, optional): output type. Supported values are 'numpy', 'pil' and 'tensor'. Defaults to "numpy".
|
||||
"""
|
||||
x = transforms.ToTensor()(pil_img).unsqueeze(0).to(self.device)
|
||||
out_tensor = self.infer(x, pad_input=pad_input, with_flip_aug=with_flip_aug, **kwargs)
|
||||
if output_type == "numpy":
|
||||
return out_tensor.squeeze().cpu().numpy()
|
||||
elif output_type == "pil":
|
||||
# uint16 is required for depth pil image
|
||||
out_16bit_numpy = (out_tensor.squeeze().cpu().numpy()*256).astype(np.uint16)
|
||||
return Image.fromarray(out_16bit_numpy)
|
||||
elif output_type == "tensor":
|
||||
return out_tensor.squeeze().cpu()
|
||||
else:
|
||||
raise ValueError(f"output_type {output_type} not supported. Supported values are 'numpy', 'pil' and 'tensor'")
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
@torch.jit.script
|
||||
def exp_attractor(dx, alpha: float = 300, gamma: int = 2):
|
||||
"""Exponential attractor: dc = exp(-alpha*|dx|^gamma) * dx , where dx = a - c, a = attractor point, c = bin center, dc = shift in bin centermmary for exp_attractor
|
||||
|
||||
Args:
|
||||
dx (torch.Tensor): The difference tensor dx = Ai - Cj, where Ai is the attractor point and Cj is the bin center.
|
||||
alpha (float, optional): Proportional Attractor strength. Determines the absolute strength. Lower alpha = greater attraction. Defaults to 300.
|
||||
gamma (int, optional): Exponential Attractor strength. Determines the "region of influence" and indirectly number of bin centers affected. Lower gamma = farther reach. Defaults to 2.
|
||||
|
||||
Returns:
|
||||
torch.Tensor : Delta shifts - dc; New bin centers = Old bin centers + dc
|
||||
"""
|
||||
return torch.exp(-alpha*(torch.abs(dx)**gamma)) * (dx)
|
||||
|
||||
|
||||
@torch.jit.script
|
||||
def inv_attractor(dx, alpha: float = 300, gamma: int = 2):
|
||||
"""Inverse attractor: dc = dx / (1 + alpha*dx^gamma), where dx = a - c, a = attractor point, c = bin center, dc = shift in bin center
|
||||
This is the default one according to the accompanying paper.
|
||||
|
||||
Args:
|
||||
dx (torch.Tensor): The difference tensor dx = Ai - Cj, where Ai is the attractor point and Cj is the bin center.
|
||||
alpha (float, optional): Proportional Attractor strength. Determines the absolute strength. Lower alpha = greater attraction. Defaults to 300.
|
||||
gamma (int, optional): Exponential Attractor strength. Determines the "region of influence" and indirectly number of bin centers affected. Lower gamma = farther reach. Defaults to 2.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Delta shifts - dc; New bin centers = Old bin centers + dc
|
||||
"""
|
||||
return dx.div(1+alpha*dx.pow(gamma))
|
||||
|
||||
|
||||
class AttractorLayer(nn.Module):
|
||||
def __init__(self, in_features, n_bins, n_attractors=16, mlp_dim=128, min_depth=1e-3, max_depth=10,
|
||||
alpha=300, gamma=2, kind='sum', attractor_type='exp', memory_efficient=False):
|
||||
"""
|
||||
Attractor layer for bin centers. Bin centers are bounded on the interval (min_depth, max_depth)
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.n_attractors = n_attractors
|
||||
self.n_bins = n_bins
|
||||
self.min_depth = min_depth
|
||||
self.max_depth = max_depth
|
||||
self.alpha = alpha
|
||||
self.gamma = gamma
|
||||
self.kind = kind
|
||||
self.attractor_type = attractor_type
|
||||
self.memory_efficient = memory_efficient
|
||||
|
||||
self._net = nn.Sequential(
|
||||
nn.Conv2d(in_features, mlp_dim, 1, 1, 0),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(mlp_dim, n_attractors*2, 1, 1, 0), # x2 for linear norm
|
||||
nn.ReLU(inplace=True)
|
||||
)
|
||||
|
||||
def forward(self, x, b_prev, prev_b_embedding=None, interpolate=True, is_for_query=False):
|
||||
"""
|
||||
Args:
|
||||
x (torch.Tensor) : feature block; shape - n, c, h, w
|
||||
b_prev (torch.Tensor) : previous bin centers normed; shape - n, prev_nbins, h, w
|
||||
|
||||
Returns:
|
||||
tuple(torch.Tensor,torch.Tensor) : new bin centers normed and scaled; shape - n, nbins, h, w
|
||||
"""
|
||||
if prev_b_embedding is not None:
|
||||
if interpolate:
|
||||
prev_b_embedding = nn.functional.interpolate(
|
||||
prev_b_embedding, x.shape[-2:], mode='bilinear', align_corners=True)
|
||||
x = x + prev_b_embedding
|
||||
|
||||
A = self._net(x)
|
||||
eps = 1e-3
|
||||
A = A + eps
|
||||
n, c, h, w = A.shape
|
||||
A = A.view(n, self.n_attractors, 2, h, w)
|
||||
A_normed = A / A.sum(dim=2, keepdim=True) # n, a, 2, h, w
|
||||
A_normed = A[:, :, 0, ...] # n, na, h, w
|
||||
|
||||
b_prev = nn.functional.interpolate(
|
||||
b_prev, (h, w), mode='bilinear', align_corners=True)
|
||||
b_centers = b_prev
|
||||
|
||||
if self.attractor_type == 'exp':
|
||||
dist = exp_attractor
|
||||
else:
|
||||
dist = inv_attractor
|
||||
|
||||
if not self.memory_efficient:
|
||||
func = {'mean': torch.mean, 'sum': torch.sum}[self.kind]
|
||||
# .shape N, nbins, h, w
|
||||
delta_c = func(dist(A_normed.unsqueeze(
|
||||
2) - b_centers.unsqueeze(1)), dim=1)
|
||||
else:
|
||||
delta_c = torch.zeros_like(b_centers, device=b_centers.device)
|
||||
for i in range(self.n_attractors):
|
||||
# .shape N, nbins, h, w
|
||||
delta_c += dist(A_normed[:, i, ...].unsqueeze(1) - b_centers)
|
||||
|
||||
if self.kind == 'mean':
|
||||
delta_c = delta_c / self.n_attractors
|
||||
|
||||
b_new_centers = b_centers + delta_c
|
||||
B_centers = (self.max_depth - self.min_depth) * \
|
||||
b_new_centers + self.min_depth
|
||||
B_centers, _ = torch.sort(B_centers, dim=1)
|
||||
B_centers = torch.clip(B_centers, self.min_depth, self.max_depth)
|
||||
return b_new_centers, B_centers
|
||||
|
||||
|
||||
class AttractorLayerUnnormed(nn.Module):
|
||||
def __init__(self, in_features, n_bins, n_attractors=16, mlp_dim=128, min_depth=1e-3, max_depth=10,
|
||||
alpha=300, gamma=2, kind='sum', attractor_type='exp', memory_efficient=False):
|
||||
"""
|
||||
Attractor layer for bin centers. Bin centers are unbounded
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.n_attractors = n_attractors
|
||||
self.n_bins = n_bins
|
||||
self.min_depth = min_depth
|
||||
self.max_depth = max_depth
|
||||
self.alpha = alpha
|
||||
self.gamma = gamma
|
||||
self.kind = kind
|
||||
self.attractor_type = attractor_type
|
||||
self.memory_efficient = memory_efficient
|
||||
|
||||
self._net = nn.Sequential(
|
||||
nn.Conv2d(in_features, mlp_dim, 1, 1, 0),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(mlp_dim, n_attractors, 1, 1, 0),
|
||||
nn.Softplus()
|
||||
)
|
||||
|
||||
def forward(self, x, b_prev, prev_b_embedding=None, interpolate=True, is_for_query=False):
|
||||
"""
|
||||
Args:
|
||||
x (torch.Tensor) : feature block; shape - n, c, h, w
|
||||
b_prev (torch.Tensor) : previous bin centers normed; shape - n, prev_nbins, h, w
|
||||
|
||||
Returns:
|
||||
tuple(torch.Tensor,torch.Tensor) : new bin centers unbounded; shape - n, nbins, h, w. Two outputs just to keep the API consistent with the normed version
|
||||
"""
|
||||
if prev_b_embedding is not None:
|
||||
if interpolate:
|
||||
prev_b_embedding = nn.functional.interpolate(
|
||||
prev_b_embedding, x.shape[-2:], mode='bilinear', align_corners=True)
|
||||
x = x + prev_b_embedding
|
||||
|
||||
A = self._net(x)
|
||||
n, c, h, w = A.shape
|
||||
|
||||
b_prev = nn.functional.interpolate(
|
||||
b_prev, (h, w), mode='bilinear', align_corners=True)
|
||||
b_centers = b_prev
|
||||
|
||||
if self.attractor_type == 'exp':
|
||||
dist = exp_attractor
|
||||
else:
|
||||
dist = inv_attractor
|
||||
|
||||
if not self.memory_efficient:
|
||||
func = {'mean': torch.mean, 'sum': torch.sum}[self.kind]
|
||||
# .shape N, nbins, h, w
|
||||
delta_c = func(
|
||||
dist(A.unsqueeze(2) - b_centers.unsqueeze(1)), dim=1)
|
||||
else:
|
||||
delta_c = torch.zeros_like(b_centers, device=b_centers.device)
|
||||
for i in range(self.n_attractors):
|
||||
delta_c += dist(A[:, i, ...].unsqueeze(1) -
|
||||
b_centers) # .shape N, nbins, h, w
|
||||
|
||||
if self.kind == 'mean':
|
||||
delta_c = delta_c / self.n_attractors
|
||||
|
||||
b_new_centers = b_centers + delta_c
|
||||
B_centers = b_new_centers
|
||||
|
||||
return b_new_centers, B_centers
|
||||
@@ -0,0 +1,121 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
def log_binom(n, k, eps=1e-7):
|
||||
""" log(nCk) using stirling approximation """
|
||||
n = n + eps
|
||||
k = k + eps
|
||||
return n * torch.log(n) - k * torch.log(k) - (n-k) * torch.log(n-k+eps)
|
||||
|
||||
|
||||
class LogBinomial(nn.Module):
|
||||
def __init__(self, n_classes=256, act=torch.softmax):
|
||||
"""Compute log binomial distribution for n_classes
|
||||
|
||||
Args:
|
||||
n_classes (int, optional): number of output classes. Defaults to 256.
|
||||
"""
|
||||
super().__init__()
|
||||
self.K = n_classes
|
||||
self.act = act
|
||||
self.register_buffer('k_idx', torch.arange(
|
||||
0, n_classes).view(1, -1, 1, 1))
|
||||
self.register_buffer('K_minus_1', torch.Tensor(
|
||||
[self.K-1]).view(1, -1, 1, 1))
|
||||
|
||||
def forward(self, x, t=1., eps=1e-4):
|
||||
"""Compute log binomial distribution for x
|
||||
|
||||
Args:
|
||||
x (torch.Tensor - NCHW): probabilities
|
||||
t (float, torch.Tensor - NCHW, optional): Temperature of distribution. Defaults to 1..
|
||||
eps (float, optional): Small number for numerical stability. Defaults to 1e-4.
|
||||
|
||||
Returns:
|
||||
torch.Tensor -NCHW: log binomial distribution logbinomial(p;t)
|
||||
"""
|
||||
if x.ndim == 3:
|
||||
x = x.unsqueeze(1) # make it nchw
|
||||
|
||||
one_minus_x = torch.clamp(1 - x, eps, 1)
|
||||
x = torch.clamp(x, eps, 1)
|
||||
y = log_binom(self.K_minus_1, self.k_idx) + self.k_idx * \
|
||||
torch.log(x) + (self.K - 1 - self.k_idx) * torch.log(one_minus_x)
|
||||
return self.act(y/t, dim=1)
|
||||
|
||||
|
||||
class ConditionalLogBinomial(nn.Module):
|
||||
def __init__(self, in_features, condition_dim, n_classes=256, bottleneck_factor=2, p_eps=1e-4, max_temp=50, min_temp=1e-7, act=torch.softmax):
|
||||
"""Conditional Log Binomial distribution
|
||||
|
||||
Args:
|
||||
in_features (int): number of input channels in main feature
|
||||
condition_dim (int): number of input channels in condition feature
|
||||
n_classes (int, optional): Number of classes. Defaults to 256.
|
||||
bottleneck_factor (int, optional): Hidden dim factor. Defaults to 2.
|
||||
p_eps (float, optional): small eps value. Defaults to 1e-4.
|
||||
max_temp (float, optional): Maximum temperature of output distribution. Defaults to 50.
|
||||
min_temp (float, optional): Minimum temperature of output distribution. Defaults to 1e-7.
|
||||
"""
|
||||
super().__init__()
|
||||
self.p_eps = p_eps
|
||||
self.max_temp = max_temp
|
||||
self.min_temp = min_temp
|
||||
self.log_binomial_transform = LogBinomial(n_classes, act=act)
|
||||
bottleneck = (in_features + condition_dim) // bottleneck_factor
|
||||
self.mlp = nn.Sequential(
|
||||
nn.Conv2d(in_features + condition_dim, bottleneck,
|
||||
kernel_size=1, stride=1, padding=0),
|
||||
nn.GELU(),
|
||||
# 2 for p linear norm, 2 for t linear norm
|
||||
nn.Conv2d(bottleneck, 2+2, kernel_size=1, stride=1, padding=0),
|
||||
nn.Softplus()
|
||||
)
|
||||
|
||||
def forward(self, x, cond):
|
||||
"""Forward pass
|
||||
|
||||
Args:
|
||||
x (torch.Tensor - NCHW): Main feature
|
||||
cond (torch.Tensor - NCHW): condition feature
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Output log binomial distribution
|
||||
"""
|
||||
pt = self.mlp(torch.concat((x, cond), dim=1))
|
||||
p, t = pt[:, :2, ...], pt[:, 2:, ...]
|
||||
|
||||
p = p + self.p_eps
|
||||
p = p[:, 0, ...] / (p[:, 0, ...] + p[:, 1, ...])
|
||||
|
||||
t = t + self.p_eps
|
||||
t = t[:, 0, ...] / (t[:, 0, ...] + t[:, 1, ...])
|
||||
t = t.unsqueeze(1)
|
||||
t = (self.max_temp - self.min_temp) * t + self.min_temp
|
||||
|
||||
return self.log_binomial_transform(p, t)
|
||||
@@ -0,0 +1,169 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class SeedBinRegressor(nn.Module):
|
||||
def __init__(self, in_features, n_bins=16, mlp_dim=256, min_depth=1e-3, max_depth=10):
|
||||
"""Bin center regressor network. Bin centers are bounded on (min_depth, max_depth) interval.
|
||||
|
||||
Args:
|
||||
in_features (int): input channels
|
||||
n_bins (int, optional): Number of bin centers. Defaults to 16.
|
||||
mlp_dim (int, optional): Hidden dimension. Defaults to 256.
|
||||
min_depth (float, optional): Min depth value. Defaults to 1e-3.
|
||||
max_depth (float, optional): Max depth value. Defaults to 10.
|
||||
"""
|
||||
super().__init__()
|
||||
self.version = "1_1"
|
||||
self.min_depth = min_depth
|
||||
self.max_depth = max_depth
|
||||
|
||||
self._net = nn.Sequential(
|
||||
nn.Conv2d(in_features, mlp_dim, 1, 1, 0),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(mlp_dim, n_bins, 1, 1, 0),
|
||||
nn.ReLU(inplace=True)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
"""
|
||||
Returns tensor of bin_width vectors (centers). One vector b for every pixel
|
||||
"""
|
||||
B = self._net(x)
|
||||
eps = 1e-3
|
||||
B = B + eps
|
||||
B_widths_normed = B / B.sum(dim=1, keepdim=True)
|
||||
B_widths = (self.max_depth - self.min_depth) * \
|
||||
B_widths_normed # .shape NCHW
|
||||
# pad has the form (left, right, top, bottom, front, back)
|
||||
B_widths = nn.functional.pad(
|
||||
B_widths, (0, 0, 0, 0, 1, 0), mode='constant', value=self.min_depth)
|
||||
B_edges = torch.cumsum(B_widths, dim=1) # .shape NCHW
|
||||
|
||||
B_centers = 0.5 * (B_edges[:, :-1, ...] + B_edges[:, 1:, ...])
|
||||
return B_widths_normed, B_centers
|
||||
|
||||
|
||||
class SeedBinRegressorUnnormed(nn.Module):
|
||||
def __init__(self, in_features, n_bins=16, mlp_dim=256, min_depth=1e-3, max_depth=10):
|
||||
"""Bin center regressor network. Bin centers are unbounded
|
||||
|
||||
Args:
|
||||
in_features (int): input channels
|
||||
n_bins (int, optional): Number of bin centers. Defaults to 16.
|
||||
mlp_dim (int, optional): Hidden dimension. Defaults to 256.
|
||||
min_depth (float, optional): Not used. (for compatibility with SeedBinRegressor)
|
||||
max_depth (float, optional): Not used. (for compatibility with SeedBinRegressor)
|
||||
"""
|
||||
super().__init__()
|
||||
self.version = "1_1"
|
||||
self._net = nn.Sequential(
|
||||
nn.Conv2d(in_features, mlp_dim, 1, 1, 0),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(mlp_dim, n_bins, 1, 1, 0),
|
||||
nn.Softplus()
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
"""
|
||||
Returns tensor of bin_width vectors (centers). One vector b for every pixel
|
||||
"""
|
||||
B_centers = self._net(x)
|
||||
return B_centers, B_centers
|
||||
|
||||
|
||||
class Projector(nn.Module):
|
||||
def __init__(self, in_features, out_features, mlp_dim=128):
|
||||
"""Projector MLP
|
||||
|
||||
Args:
|
||||
in_features (int): input channels
|
||||
out_features (int): output channels
|
||||
mlp_dim (int, optional): hidden dimension. Defaults to 128.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self._net = nn.Sequential(
|
||||
nn.Conv2d(in_features, mlp_dim, 1, 1, 0),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(mlp_dim, out_features, 1, 1, 0),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self._net(x)
|
||||
|
||||
|
||||
|
||||
class LinearSplitter(nn.Module):
|
||||
def __init__(self, in_features, prev_nbins, split_factor=2, mlp_dim=128, min_depth=1e-3, max_depth=10):
|
||||
super().__init__()
|
||||
|
||||
self.prev_nbins = prev_nbins
|
||||
self.split_factor = split_factor
|
||||
self.min_depth = min_depth
|
||||
self.max_depth = max_depth
|
||||
|
||||
self._net = nn.Sequential(
|
||||
nn.Conv2d(in_features, mlp_dim, 1, 1, 0),
|
||||
nn.GELU(),
|
||||
nn.Conv2d(mlp_dim, prev_nbins * split_factor, 1, 1, 0),
|
||||
nn.ReLU()
|
||||
)
|
||||
|
||||
def forward(self, x, b_prev, prev_b_embedding=None, interpolate=True, is_for_query=False):
|
||||
"""
|
||||
x : feature block; shape - n, c, h, w
|
||||
b_prev : previous bin widths normed; shape - n, prev_nbins, h, w
|
||||
"""
|
||||
if prev_b_embedding is not None:
|
||||
if interpolate:
|
||||
prev_b_embedding = nn.functional.interpolate(prev_b_embedding, x.shape[-2:], mode='bilinear', align_corners=True)
|
||||
x = x + prev_b_embedding
|
||||
S = self._net(x)
|
||||
eps = 1e-3
|
||||
S = S + eps
|
||||
n, c, h, w = S.shape
|
||||
S = S.view(n, self.prev_nbins, self.split_factor, h, w)
|
||||
S_normed = S / S.sum(dim=2, keepdim=True) # fractional splits
|
||||
|
||||
b_prev = nn.functional.interpolate(b_prev, (h,w), mode='bilinear', align_corners=True)
|
||||
|
||||
|
||||
b_prev = b_prev / b_prev.sum(dim=1, keepdim=True) # renormalize for gurantees
|
||||
# print(b_prev.shape, S_normed.shape)
|
||||
# if is_for_query:(1).expand(-1, b_prev.size(0)//n, -1, -1, -1, -1).flatten(0,1) # TODO ? can replace all this with a single torch.repeat?
|
||||
b = b_prev.unsqueeze(2) * S_normed
|
||||
b = b.flatten(1,2) # .shape n, prev_nbins * split_factor, h, w
|
||||
|
||||
# calculate bin centers for loss calculation
|
||||
B_widths = (self.max_depth - self.min_depth) * b # .shape N, nprev * splitfactor, H, W
|
||||
# pad has the form (left, right, top, bottom, front, back)
|
||||
B_widths = nn.functional.pad(B_widths, (0,0,0,0,1,0), mode='constant', value=self.min_depth)
|
||||
B_edges = torch.cumsum(B_widths, dim=1) # .shape NCHW
|
||||
|
||||
B_centers = 0.5 * (B_edges[:, :-1, ...] + B_edges[:,1:,...])
|
||||
return b, B_centers
|
||||
@@ -0,0 +1,91 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class PatchTransformerEncoder(nn.Module):
|
||||
def __init__(self, in_channels, patch_size=10, embedding_dim=128, num_heads=4, use_class_token=False):
|
||||
"""ViT-like transformer block
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels
|
||||
patch_size (int, optional): patch size. Defaults to 10.
|
||||
embedding_dim (int, optional): Embedding dimension in transformer model. Defaults to 128.
|
||||
num_heads (int, optional): number of attention heads. Defaults to 4.
|
||||
use_class_token (bool, optional): Whether to use extra token at the start for global accumulation (called as "class token"). Defaults to False.
|
||||
"""
|
||||
super(PatchTransformerEncoder, self).__init__()
|
||||
self.use_class_token = use_class_token
|
||||
encoder_layers = nn.TransformerEncoderLayer(
|
||||
embedding_dim, num_heads, dim_feedforward=1024)
|
||||
self.transformer_encoder = nn.TransformerEncoder(
|
||||
encoder_layers, num_layers=4) # takes shape S,N,E
|
||||
|
||||
self.embedding_convPxP = nn.Conv2d(in_channels, embedding_dim,
|
||||
kernel_size=patch_size, stride=patch_size, padding=0)
|
||||
|
||||
def positional_encoding_1d(self, sequence_length, batch_size, embedding_dim, device='cpu'):
|
||||
"""Generate positional encodings
|
||||
|
||||
Args:
|
||||
sequence_length (int): Sequence length
|
||||
embedding_dim (int): Embedding dimension
|
||||
|
||||
Returns:
|
||||
torch.Tensor SBE: Positional encodings
|
||||
"""
|
||||
position = torch.arange(
|
||||
0, sequence_length, dtype=torch.float32, device=device).unsqueeze(1)
|
||||
index = torch.arange(
|
||||
0, embedding_dim, 2, dtype=torch.float32, device=device).unsqueeze(0)
|
||||
div_term = torch.exp(index * (-torch.log(torch.tensor(10000.0, device=device)) / embedding_dim))
|
||||
pos_encoding = position * div_term
|
||||
pos_encoding = torch.cat([torch.sin(pos_encoding), torch.cos(pos_encoding)], dim=1)
|
||||
pos_encoding = pos_encoding.unsqueeze(1).repeat(1, batch_size, 1)
|
||||
return pos_encoding
|
||||
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward pass
|
||||
|
||||
Args:
|
||||
x (torch.Tensor - NCHW): Input feature tensor
|
||||
|
||||
Returns:
|
||||
torch.Tensor - SNE: Transformer output embeddings. S - sequence length (=HW/patch_size^2), N - batch size, E - embedding dim
|
||||
"""
|
||||
embeddings = self.embedding_convPxP(x).flatten(
|
||||
2) # .shape = n,c,s = n, embedding_dim, s
|
||||
if self.use_class_token:
|
||||
# extra special token at start ?
|
||||
embeddings = nn.functional.pad(embeddings, (1, 0))
|
||||
|
||||
# change to S,N,E format required by transformer
|
||||
embeddings = embeddings.permute(2, 0, 1)
|
||||
S, N, E = embeddings.shape
|
||||
embeddings = embeddings + self.positional_encoding_1d(S, N, E, device=embeddings.device)
|
||||
x = self.transformer_encoder(embeddings) # .shape = S, N, E
|
||||
return x
|
||||
@@ -0,0 +1,92 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
import torch
|
||||
|
||||
def load_state_dict(model, state_dict):
|
||||
"""Load state_dict into model, handling DataParallel and DistributedDataParallel. Also checks for "model" key in state_dict.
|
||||
|
||||
DataParallel prefixes state_dict keys with 'module.' when saving.
|
||||
If the model is not a DataParallel model but the state_dict is, then prefixes are removed.
|
||||
If the model is a DataParallel model but the state_dict is not, then prefixes are added.
|
||||
"""
|
||||
state_dict = state_dict.get('model', state_dict)
|
||||
# if model is a DataParallel model, then state_dict keys are prefixed with 'module.'
|
||||
|
||||
do_prefix = isinstance(
|
||||
model, (torch.nn.DataParallel, torch.nn.parallel.DistributedDataParallel))
|
||||
state = {}
|
||||
for k, v in state_dict.items():
|
||||
if k.startswith('module.') and not do_prefix:
|
||||
k = k[7:]
|
||||
|
||||
if not k.startswith('module.') and do_prefix:
|
||||
k = 'module.' + k
|
||||
|
||||
state[k] = v
|
||||
|
||||
model.load_state_dict(state)
|
||||
print("Loaded successfully")
|
||||
return model
|
||||
|
||||
|
||||
def load_wts(model, checkpoint_path):
|
||||
ckpt = torch.load(checkpoint_path, map_location='cpu')
|
||||
return load_state_dict(model, ckpt)
|
||||
|
||||
|
||||
def load_state_dict_from_url(model, url, **kwargs):
|
||||
state_dict = torch.hub.load_state_dict_from_url(url, map_location='cpu', **kwargs)
|
||||
return load_state_dict(model, state_dict)
|
||||
|
||||
|
||||
def load_state_from_resource(model, resource: str):
|
||||
"""Loads weights to the model from a given resource. A resource can be of following types:
|
||||
1. URL. Prefixed with "url::"
|
||||
e.g. url::http(s)://url.resource.com/ckpt.pt
|
||||
|
||||
2. Local path. Prefixed with "local::"
|
||||
e.g. local::/path/to/ckpt.pt
|
||||
|
||||
|
||||
Args:
|
||||
model (torch.nn.Module): Model
|
||||
resource (str): resource string
|
||||
|
||||
Returns:
|
||||
torch.nn.Module: Model with loaded weights
|
||||
"""
|
||||
print(f"Using pretrained resource {resource}")
|
||||
|
||||
if resource.startswith('url::'):
|
||||
url = resource.split('url::')[1]
|
||||
return load_state_dict_from_url(model, url, progress=True)
|
||||
|
||||
elif resource.startswith('local::'):
|
||||
path = resource.split('local::')[1]
|
||||
return load_wts(model, path)
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid resource type, only url:: and local:: are supported")
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
from .zoedepth_v1 import ZoeDepth
|
||||
|
||||
all_versions = {
|
||||
"v1": ZoeDepth,
|
||||
}
|
||||
|
||||
get_version = lambda v : all_versions[v]
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"model": {
|
||||
"name": "ZoeDepth",
|
||||
"version_name": "v1",
|
||||
"n_bins": 64,
|
||||
"bin_embedding_dim": 128,
|
||||
"bin_centers_type": "softplus",
|
||||
"n_attractors":[16, 8, 4, 1],
|
||||
"attractor_alpha": 1000,
|
||||
"attractor_gamma": 2,
|
||||
"attractor_kind" : "mean",
|
||||
"attractor_type" : "inv",
|
||||
"midas_model_type" : "DPT_BEiT_L_384",
|
||||
"min_temp": 0.0212,
|
||||
"max_temp": 50.0,
|
||||
"output_distribution": "logbinomial",
|
||||
"memory_efficient": true,
|
||||
"inverse_midas": false,
|
||||
"img_size": [392, 518]
|
||||
},
|
||||
|
||||
"train": {
|
||||
"train_midas": true,
|
||||
"use_pretrained_midas": true,
|
||||
"trainer": "zoedepth",
|
||||
"epochs": 5,
|
||||
"bs": 16,
|
||||
"optim_kwargs": {"lr": 0.000161, "wd": 0.01},
|
||||
"sched_kwargs": {"div_factor": 1, "final_div_factor": 10000, "pct_start": 0.7, "three_phase":false, "cycle_momentum": true},
|
||||
"same_lr": false,
|
||||
"w_si": 1,
|
||||
"w_domain": 0.2,
|
||||
"w_reg": 0,
|
||||
"w_grad": 0,
|
||||
"avoid_boundary": false,
|
||||
"random_crop": false,
|
||||
"input_width": 640,
|
||||
"input_height": 480,
|
||||
"midas_lr_factor": 50,
|
||||
"encoder_lr_factor":50,
|
||||
"pos_enc_lr_factor":50,
|
||||
"freeze_midas_bn": true
|
||||
|
||||
},
|
||||
|
||||
"infer":{
|
||||
"train_midas": false,
|
||||
"use_pretrained_midas": false,
|
||||
"pretrained_resource" : "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_N.pt",
|
||||
"force_keep_ar": true
|
||||
},
|
||||
|
||||
"eval":{
|
||||
"train_midas": false,
|
||||
"use_pretrained_midas": false,
|
||||
"pretrained_resource" : "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_N.pt"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"model": {
|
||||
"bin_centers_type": "normed",
|
||||
"img_size": [384, 768]
|
||||
},
|
||||
|
||||
"train": {
|
||||
},
|
||||
|
||||
"infer":{
|
||||
"train_midas": false,
|
||||
"use_pretrained_midas": false,
|
||||
"pretrained_resource" : "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_K.pt",
|
||||
"force_keep_ar": true
|
||||
},
|
||||
|
||||
"eval":{
|
||||
"train_midas": false,
|
||||
"use_pretrained_midas": false,
|
||||
"pretrained_resource" : "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_K.pt"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
import itertools
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from zoedepth.models.depth_model import DepthModel
|
||||
from zoedepth.models.base_models.midas import MidasCore
|
||||
from zoedepth.models.base_models.depth_anything import DepthAnythingCore
|
||||
from zoedepth.models.layers.attractor import AttractorLayer, AttractorLayerUnnormed
|
||||
from zoedepth.models.layers.dist_layers import ConditionalLogBinomial
|
||||
from zoedepth.models.layers.localbins_layers import (Projector, SeedBinRegressor,
|
||||
SeedBinRegressorUnnormed)
|
||||
from zoedepth.models.model_io import load_state_from_resource
|
||||
|
||||
|
||||
class ZoeDepth(DepthModel):
|
||||
def __init__(self, core, n_bins=64, bin_centers_type="softplus", bin_embedding_dim=128, min_depth=1e-3, max_depth=10,
|
||||
n_attractors=[16, 8, 4, 1], attractor_alpha=300, attractor_gamma=2, attractor_kind='sum', attractor_type='exp', min_temp=5, max_temp=50, train_midas=True,
|
||||
midas_lr_factor=10, encoder_lr_factor=10, pos_enc_lr_factor=10, inverse_midas=False, **kwargs):
|
||||
"""ZoeDepth model. This is the version of ZoeDepth that has a single metric head
|
||||
|
||||
Args:
|
||||
core (models.base_models.midas.MidasCore): The base midas model that is used for extraction of "relative" features
|
||||
n_bins (int, optional): Number of bin centers. Defaults to 64.
|
||||
bin_centers_type (str, optional): "normed" or "softplus". Activation type used for bin centers. For "normed" bin centers, linear normalization trick is applied. This results in bounded bin centers.
|
||||
For "softplus", softplus activation is used and thus are unbounded. Defaults to "softplus".
|
||||
bin_embedding_dim (int, optional): bin embedding dimension. Defaults to 128.
|
||||
min_depth (float, optional): Lower bound for normed bin centers. Defaults to 1e-3.
|
||||
max_depth (float, optional): Upper bound for normed bin centers. Defaults to 10.
|
||||
n_attractors (List[int], optional): Number of bin attractors at decoder layers. Defaults to [16, 8, 4, 1].
|
||||
attractor_alpha (int, optional): Proportional attractor strength. Refer to models.layers.attractor for more details. Defaults to 300.
|
||||
attractor_gamma (int, optional): Exponential attractor strength. Refer to models.layers.attractor for more details. Defaults to 2.
|
||||
attractor_kind (str, optional): Attraction aggregation "sum" or "mean". Defaults to 'sum'.
|
||||
attractor_type (str, optional): Type of attractor to use; "inv" (Inverse attractor) or "exp" (Exponential attractor). Defaults to 'exp'.
|
||||
min_temp (int, optional): Lower bound for temperature of output probability distribution. Defaults to 5.
|
||||
max_temp (int, optional): Upper bound for temperature of output probability distribution. Defaults to 50.
|
||||
train_midas (bool, optional): Whether to train "core", the base midas model. Defaults to True.
|
||||
midas_lr_factor (int, optional): Learning rate reduction factor for base midas model except its encoder and positional encodings. Defaults to 10.
|
||||
encoder_lr_factor (int, optional): Learning rate reduction factor for the encoder in midas model. Defaults to 10.
|
||||
pos_enc_lr_factor (int, optional): Learning rate reduction factor for positional encodings in the base midas model. Defaults to 10.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.core = core
|
||||
self.max_depth = max_depth
|
||||
self.min_depth = min_depth
|
||||
self.min_temp = min_temp
|
||||
self.bin_centers_type = bin_centers_type
|
||||
|
||||
self.midas_lr_factor = midas_lr_factor
|
||||
self.encoder_lr_factor = encoder_lr_factor
|
||||
self.pos_enc_lr_factor = pos_enc_lr_factor
|
||||
self.train_midas = train_midas
|
||||
self.inverse_midas = inverse_midas
|
||||
|
||||
if self.encoder_lr_factor <= 0:
|
||||
self.core.freeze_encoder(
|
||||
freeze_rel_pos=self.pos_enc_lr_factor <= 0)
|
||||
|
||||
N_MIDAS_OUT = 32
|
||||
btlnck_features = self.core.output_channels[0]
|
||||
num_out_features = self.core.output_channels[1:]
|
||||
|
||||
# print('core output channels:', self.core.output_channels)
|
||||
|
||||
self.conv2 = nn.Conv2d(btlnck_features, btlnck_features,
|
||||
kernel_size=1, stride=1, padding=0) # btlnck conv
|
||||
|
||||
if bin_centers_type == "normed":
|
||||
SeedBinRegressorLayer = SeedBinRegressor
|
||||
Attractor = AttractorLayer
|
||||
elif bin_centers_type == "softplus":
|
||||
SeedBinRegressorLayer = SeedBinRegressorUnnormed
|
||||
Attractor = AttractorLayerUnnormed
|
||||
elif bin_centers_type == "hybrid1":
|
||||
SeedBinRegressorLayer = SeedBinRegressor
|
||||
Attractor = AttractorLayerUnnormed
|
||||
elif bin_centers_type == "hybrid2":
|
||||
SeedBinRegressorLayer = SeedBinRegressorUnnormed
|
||||
Attractor = AttractorLayer
|
||||
else:
|
||||
raise ValueError(
|
||||
"bin_centers_type should be one of 'normed', 'softplus', 'hybrid1', 'hybrid2'")
|
||||
|
||||
self.seed_bin_regressor = SeedBinRegressorLayer(
|
||||
btlnck_features, n_bins=n_bins, min_depth=min_depth, max_depth=max_depth)
|
||||
self.seed_projector = Projector(btlnck_features, bin_embedding_dim)
|
||||
self.projectors = nn.ModuleList([
|
||||
Projector(num_out, bin_embedding_dim)
|
||||
for num_out in num_out_features
|
||||
])
|
||||
self.attractors = nn.ModuleList([
|
||||
Attractor(bin_embedding_dim, n_bins, n_attractors=n_attractors[i], min_depth=min_depth, max_depth=max_depth,
|
||||
alpha=attractor_alpha, gamma=attractor_gamma, kind=attractor_kind, attractor_type=attractor_type)
|
||||
for i in range(len(num_out_features))
|
||||
])
|
||||
|
||||
last_in = N_MIDAS_OUT + 1 # +1 for relative depth
|
||||
|
||||
# use log binomial instead of softmax
|
||||
self.conditional_log_binomial = ConditionalLogBinomial(
|
||||
last_in, bin_embedding_dim, n_classes=n_bins, min_temp=min_temp, max_temp=max_temp)
|
||||
|
||||
def forward(self, x, return_final_centers=False, denorm=False, return_probs=False, **kwargs):
|
||||
"""
|
||||
Args:
|
||||
x (torch.Tensor): Input image tensor of shape (B, C, H, W)
|
||||
return_final_centers (bool, optional): Whether to return the final bin centers. Defaults to False.
|
||||
denorm (bool, optional): Whether to denormalize the input image. This reverses ImageNet normalization as midas normalization is different. Defaults to False.
|
||||
return_probs (bool, optional): Whether to return the output probability distribution. Defaults to False.
|
||||
|
||||
Returns:
|
||||
dict: Dictionary containing the following keys:
|
||||
- rel_depth (torch.Tensor): Relative depth map of shape (B, H, W)
|
||||
- metric_depth (torch.Tensor): Metric depth map of shape (B, 1, H, W)
|
||||
- bin_centers (torch.Tensor): Bin centers of shape (B, n_bins). Present only if return_final_centers is True
|
||||
- probs (torch.Tensor): Output probability distribution of shape (B, n_bins, H, W). Present only if return_probs is True
|
||||
|
||||
"""
|
||||
# print('input shape', x.shape)
|
||||
|
||||
b, c, h, w = x.shape
|
||||
# print("input shape:", x.shape)
|
||||
self.orig_input_width = w
|
||||
self.orig_input_height = h
|
||||
rel_depth, out = self.core(x, denorm=denorm, return_rel_depth=True)
|
||||
# print("output shapes", rel_depth.shape, out.shape)
|
||||
# print('rel_depth shape:', rel_depth.shape)
|
||||
# print('out type:', type(out))
|
||||
# for k in range(len(out)):
|
||||
# print(k, out[k].shape)
|
||||
|
||||
outconv_activation = out[0]
|
||||
btlnck = out[1]
|
||||
x_blocks = out[2:]
|
||||
|
||||
x_d0 = self.conv2(btlnck)
|
||||
x = x_d0
|
||||
_, seed_b_centers = self.seed_bin_regressor(x)
|
||||
|
||||
if self.bin_centers_type == 'normed' or self.bin_centers_type == 'hybrid2':
|
||||
b_prev = (seed_b_centers - self.min_depth) / \
|
||||
(self.max_depth - self.min_depth)
|
||||
else:
|
||||
b_prev = seed_b_centers
|
||||
|
||||
prev_b_embedding = self.seed_projector(x)
|
||||
|
||||
# unroll this loop for better performance
|
||||
for projector, attractor, x in zip(self.projectors, self.attractors, x_blocks):
|
||||
b_embedding = projector(x)
|
||||
b, b_centers = attractor(
|
||||
b_embedding, b_prev, prev_b_embedding, interpolate=True)
|
||||
b_prev = b.clone()
|
||||
prev_b_embedding = b_embedding.clone()
|
||||
|
||||
last = outconv_activation
|
||||
|
||||
if self.inverse_midas:
|
||||
# invert depth followed by normalization
|
||||
rel_depth = 1.0 / (rel_depth + 1e-6)
|
||||
rel_depth = (rel_depth - rel_depth.min()) / \
|
||||
(rel_depth.max() - rel_depth.min())
|
||||
# concat rel depth with last. First interpolate rel depth to last size
|
||||
rel_cond = rel_depth.unsqueeze(1)
|
||||
rel_cond = nn.functional.interpolate(
|
||||
rel_cond, size=last.shape[2:], mode='bilinear', align_corners=True)
|
||||
last = torch.cat([last, rel_cond], dim=1)
|
||||
|
||||
b_embedding = nn.functional.interpolate(
|
||||
b_embedding, last.shape[-2:], mode='bilinear', align_corners=True)
|
||||
x = self.conditional_log_binomial(last, b_embedding)
|
||||
|
||||
# Now depth value is Sum px * cx , where cx are bin_centers from the last bin tensor
|
||||
# print(x.shape, b_centers.shape)
|
||||
b_centers = nn.functional.interpolate(
|
||||
b_centers, x.shape[-2:], mode='bilinear', align_corners=True)
|
||||
out = torch.sum(x * b_centers, dim=1, keepdim=True)
|
||||
|
||||
# Structure output dict
|
||||
output = dict(metric_depth=out)
|
||||
if return_final_centers or return_probs:
|
||||
output['bin_centers'] = b_centers
|
||||
|
||||
if return_probs:
|
||||
output['probs'] = x
|
||||
|
||||
return output
|
||||
|
||||
def get_lr_params(self, lr):
|
||||
"""
|
||||
Learning rate configuration for different layers of the model
|
||||
Args:
|
||||
lr (float) : Base learning rate
|
||||
Returns:
|
||||
list : list of parameters to optimize and their learning rates, in the format required by torch optimizers.
|
||||
"""
|
||||
param_conf = []
|
||||
if self.train_midas:
|
||||
if self.encoder_lr_factor > 0:
|
||||
param_conf.append({'params': self.core.get_enc_params_except_rel_pos(
|
||||
), 'lr': lr / self.encoder_lr_factor})
|
||||
|
||||
if self.pos_enc_lr_factor > 0:
|
||||
param_conf.append(
|
||||
{'params': self.core.get_rel_pos_params(), 'lr': lr / self.pos_enc_lr_factor})
|
||||
|
||||
# midas_params = self.core.core.scratch.parameters()
|
||||
midas_params = self.core.core.depth_head.parameters()
|
||||
midas_lr_factor = self.midas_lr_factor
|
||||
param_conf.append(
|
||||
{'params': midas_params, 'lr': lr / midas_lr_factor})
|
||||
|
||||
remaining_modules = []
|
||||
for name, child in self.named_children():
|
||||
if name != 'core':
|
||||
remaining_modules.append(child)
|
||||
remaining_params = itertools.chain(
|
||||
*[child.parameters() for child in remaining_modules])
|
||||
|
||||
param_conf.append({'params': remaining_params, 'lr': lr})
|
||||
|
||||
return param_conf
|
||||
|
||||
@staticmethod
|
||||
def build(midas_model_type="DPT_BEiT_L_384", pretrained_resource=None, use_pretrained_midas=False, train_midas=False, freeze_midas_bn=True, **kwargs):
|
||||
# core = MidasCore.build(midas_model_type=midas_model_type, use_pretrained_midas=use_pretrained_midas,
|
||||
# train_midas=train_midas, fetch_features=True, freeze_bn=freeze_midas_bn, **kwargs)
|
||||
|
||||
core = DepthAnythingCore.build(midas_model_type=midas_model_type, use_pretrained_midas=use_pretrained_midas,
|
||||
train_midas=train_midas, fetch_features=True, freeze_bn=freeze_midas_bn, **kwargs)
|
||||
|
||||
model = ZoeDepth(core, **kwargs)
|
||||
if pretrained_resource:
|
||||
assert isinstance(pretrained_resource, str), "pretrained_resource must be a string"
|
||||
model = load_state_from_resource(model, pretrained_resource)
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
def build_from_config(config):
|
||||
return ZoeDepth.build(**config)
|
||||
@@ -0,0 +1,31 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
from .zoedepth_nk_v1 import ZoeDepthNK
|
||||
|
||||
all_versions = {
|
||||
"v1": ZoeDepthNK,
|
||||
}
|
||||
|
||||
get_version = lambda v : all_versions[v]
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"model": {
|
||||
"name": "ZoeDepthNK",
|
||||
"version_name": "v1",
|
||||
"bin_conf" : [
|
||||
{
|
||||
"name": "nyu",
|
||||
"n_bins": 64,
|
||||
"min_depth": 1e-3,
|
||||
"max_depth": 10.0
|
||||
},
|
||||
{
|
||||
"name": "kitti",
|
||||
"n_bins": 64,
|
||||
"min_depth": 1e-3,
|
||||
"max_depth": 80.0
|
||||
}
|
||||
],
|
||||
"bin_embedding_dim": 128,
|
||||
"bin_centers_type": "softplus",
|
||||
"n_attractors":[16, 8, 4, 1],
|
||||
"attractor_alpha": 1000,
|
||||
"attractor_gamma": 2,
|
||||
"attractor_kind" : "mean",
|
||||
"attractor_type" : "inv",
|
||||
"min_temp": 0.0212,
|
||||
"max_temp": 50.0,
|
||||
"memory_efficient": true,
|
||||
"midas_model_type" : "DPT_BEiT_L_384",
|
||||
"img_size": [392, 518]
|
||||
},
|
||||
|
||||
"train": {
|
||||
"train_midas": true,
|
||||
"use_pretrained_midas": true,
|
||||
"trainer": "zoedepth_nk",
|
||||
"epochs": 10,
|
||||
"bs": 16,
|
||||
"optim_kwargs": {"lr": 0.0002512, "wd": 0.01},
|
||||
"sched_kwargs": {"div_factor": 1, "final_div_factor": 10000, "pct_start": 0.7, "three_phase":false, "cycle_momentum": true},
|
||||
"same_lr": false,
|
||||
"w_si": 1,
|
||||
"w_domain": 100,
|
||||
"avoid_boundary": false,
|
||||
"random_crop": false,
|
||||
"input_width": 640,
|
||||
"input_height": 480,
|
||||
"w_grad": 0,
|
||||
"w_reg": 0,
|
||||
"midas_lr_factor": 50,
|
||||
"encoder_lr_factor": 50,
|
||||
"pos_enc_lr_factor": 50
|
||||
},
|
||||
|
||||
"infer": {
|
||||
"train_midas": false,
|
||||
"pretrained_resource": "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_NK.pt",
|
||||
"use_pretrained_midas": false,
|
||||
"force_keep_ar": true
|
||||
},
|
||||
|
||||
"eval": {
|
||||
"train_midas": false,
|
||||
"pretrained_resource": "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_NK.pt",
|
||||
"use_pretrained_midas": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2022 Intelligent Systems Lab Org
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# File author: Shariq Farooq Bhat
|
||||
|
||||
import itertools
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from zoedepth.models.depth_model import DepthModel
|
||||
from zoedepth.models.base_models.midas import MidasCore
|
||||
from zoedepth.models.base_models.depth_anything import DepthAnythingCore
|
||||
from zoedepth.models.layers.attractor import AttractorLayer, AttractorLayerUnnormed
|
||||
from zoedepth.models.layers.dist_layers import ConditionalLogBinomial
|
||||
from zoedepth.models.layers.localbins_layers import (Projector, SeedBinRegressor,
|
||||
SeedBinRegressorUnnormed)
|
||||
from zoedepth.models.layers.patch_transformer import PatchTransformerEncoder
|
||||
from zoedepth.models.model_io import load_state_from_resource
|
||||
|
||||
|
||||
class ZoeDepthNK(DepthModel):
|
||||
def __init__(self, core, bin_conf, bin_centers_type="softplus", bin_embedding_dim=128,
|
||||
n_attractors=[16, 8, 4, 1], attractor_alpha=300, attractor_gamma=2, attractor_kind='sum', attractor_type='exp',
|
||||
min_temp=5, max_temp=50,
|
||||
memory_efficient=False, train_midas=True,
|
||||
is_midas_pretrained=True, midas_lr_factor=1, encoder_lr_factor=10, pos_enc_lr_factor=10, inverse_midas=False, **kwargs):
|
||||
"""ZoeDepthNK model. This is the version of ZoeDepth that has two metric heads and uses a learned router to route to experts.
|
||||
|
||||
Args:
|
||||
core (models.base_models.midas.MidasCore): The base midas model that is used for extraction of "relative" features
|
||||
|
||||
bin_conf (List[dict]): A list of dictionaries that contain the bin configuration for each metric head. Each dictionary should contain the following keys:
|
||||
"name" (str, typically same as the dataset name), "n_bins" (int), "min_depth" (float), "max_depth" (float)
|
||||
|
||||
The length of this list determines the number of metric heads.
|
||||
bin_centers_type (str, optional): "normed" or "softplus". Activation type used for bin centers. For "normed" bin centers, linear normalization trick is applied. This results in bounded bin centers.
|
||||
For "softplus", softplus activation is used and thus are unbounded. Defaults to "normed".
|
||||
bin_embedding_dim (int, optional): bin embedding dimension. Defaults to 128.
|
||||
|
||||
n_attractors (List[int], optional): Number of bin attractors at decoder layers. Defaults to [16, 8, 4, 1].
|
||||
attractor_alpha (int, optional): Proportional attractor strength. Refer to models.layers.attractor for more details. Defaults to 300.
|
||||
attractor_gamma (int, optional): Exponential attractor strength. Refer to models.layers.attractor for more details. Defaults to 2.
|
||||
attractor_kind (str, optional): Attraction aggregation "sum" or "mean". Defaults to 'sum'.
|
||||
attractor_type (str, optional): Type of attractor to use; "inv" (Inverse attractor) or "exp" (Exponential attractor). Defaults to 'exp'.
|
||||
|
||||
min_temp (int, optional): Lower bound for temperature of output probability distribution. Defaults to 5.
|
||||
max_temp (int, optional): Upper bound for temperature of output probability distribution. Defaults to 50.
|
||||
|
||||
memory_efficient (bool, optional): Whether to use memory efficient version of attractor layers. Memory efficient version is slower but is recommended incase of multiple metric heads in order save GPU memory. Defaults to False.
|
||||
|
||||
train_midas (bool, optional): Whether to train "core", the base midas model. Defaults to True.
|
||||
is_midas_pretrained (bool, optional): Is "core" pretrained? Defaults to True.
|
||||
midas_lr_factor (int, optional): Learning rate reduction factor for base midas model except its encoder and positional encodings. Defaults to 10.
|
||||
encoder_lr_factor (int, optional): Learning rate reduction factor for the encoder in midas model. Defaults to 10.
|
||||
pos_enc_lr_factor (int, optional): Learning rate reduction factor for positional encodings in the base midas model. Defaults to 10.
|
||||
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.core = core
|
||||
self.bin_conf = bin_conf
|
||||
self.min_temp = min_temp
|
||||
self.max_temp = max_temp
|
||||
self.memory_efficient = memory_efficient
|
||||
self.train_midas = train_midas
|
||||
self.is_midas_pretrained = is_midas_pretrained
|
||||
self.midas_lr_factor = midas_lr_factor
|
||||
self.encoder_lr_factor = encoder_lr_factor
|
||||
self.pos_enc_lr_factor = pos_enc_lr_factor
|
||||
self.inverse_midas = inverse_midas
|
||||
|
||||
N_MIDAS_OUT = 32
|
||||
btlnck_features = self.core.output_channels[0]
|
||||
num_out_features = self.core.output_channels[1:]
|
||||
# self.scales = [16, 8, 4, 2] # spatial scale factors
|
||||
|
||||
self.conv2 = nn.Conv2d(
|
||||
btlnck_features, btlnck_features, kernel_size=1, stride=1, padding=0)
|
||||
|
||||
# Transformer classifier on the bottleneck
|
||||
self.patch_transformer = PatchTransformerEncoder(
|
||||
btlnck_features, 1, 128, use_class_token=True)
|
||||
self.mlp_classifier = nn.Sequential(
|
||||
nn.Linear(128, 128),
|
||||
nn.ReLU(),
|
||||
nn.Linear(128, 2)
|
||||
)
|
||||
|
||||
if bin_centers_type == "normed":
|
||||
SeedBinRegressorLayer = SeedBinRegressor
|
||||
Attractor = AttractorLayer
|
||||
elif bin_centers_type == "softplus":
|
||||
SeedBinRegressorLayer = SeedBinRegressorUnnormed
|
||||
Attractor = AttractorLayerUnnormed
|
||||
elif bin_centers_type == "hybrid1":
|
||||
SeedBinRegressorLayer = SeedBinRegressor
|
||||
Attractor = AttractorLayerUnnormed
|
||||
elif bin_centers_type == "hybrid2":
|
||||
SeedBinRegressorLayer = SeedBinRegressorUnnormed
|
||||
Attractor = AttractorLayer
|
||||
else:
|
||||
raise ValueError(
|
||||
"bin_centers_type should be one of 'normed', 'softplus', 'hybrid1', 'hybrid2'")
|
||||
self.bin_centers_type = bin_centers_type
|
||||
# We have bins for each bin conf.
|
||||
# Create a map (ModuleDict) of 'name' -> seed_bin_regressor
|
||||
self.seed_bin_regressors = nn.ModuleDict(
|
||||
{conf['name']: SeedBinRegressorLayer(btlnck_features, conf["n_bins"], mlp_dim=bin_embedding_dim//2, min_depth=conf["min_depth"], max_depth=conf["max_depth"])
|
||||
for conf in bin_conf}
|
||||
)
|
||||
|
||||
self.seed_projector = Projector(
|
||||
btlnck_features, bin_embedding_dim, mlp_dim=bin_embedding_dim//2)
|
||||
self.projectors = nn.ModuleList([
|
||||
Projector(num_out, bin_embedding_dim, mlp_dim=bin_embedding_dim//2)
|
||||
for num_out in num_out_features
|
||||
])
|
||||
|
||||
# Create a map (ModuleDict) of 'name' -> attractors (ModuleList)
|
||||
self.attractors = nn.ModuleDict(
|
||||
{conf['name']: nn.ModuleList([
|
||||
Attractor(bin_embedding_dim, n_attractors[i],
|
||||
mlp_dim=bin_embedding_dim, alpha=attractor_alpha,
|
||||
gamma=attractor_gamma, kind=attractor_kind,
|
||||
attractor_type=attractor_type, memory_efficient=memory_efficient,
|
||||
min_depth=conf["min_depth"], max_depth=conf["max_depth"])
|
||||
for i in range(len(n_attractors))
|
||||
])
|
||||
for conf in bin_conf}
|
||||
)
|
||||
|
||||
last_in = N_MIDAS_OUT
|
||||
# conditional log binomial for each bin conf
|
||||
self.conditional_log_binomial = nn.ModuleDict(
|
||||
{conf['name']: ConditionalLogBinomial(last_in, bin_embedding_dim, conf['n_bins'], bottleneck_factor=4, min_temp=self.min_temp, max_temp=self.max_temp)
|
||||
for conf in bin_conf}
|
||||
)
|
||||
|
||||
def forward(self, x, return_final_centers=False, denorm=False, return_probs=False, **kwargs):
|
||||
"""
|
||||
Args:
|
||||
x (torch.Tensor): Input image tensor of shape (B, C, H, W). Assumes all images are from the same domain.
|
||||
return_final_centers (bool, optional): Whether to return the final centers of the attractors. Defaults to False.
|
||||
denorm (bool, optional): Whether to denormalize the input image. Defaults to False.
|
||||
return_probs (bool, optional): Whether to return the probabilities of the bins. Defaults to False.
|
||||
|
||||
Returns:
|
||||
dict: Dictionary of outputs with keys:
|
||||
- "rel_depth": Relative depth map of shape (B, 1, H, W)
|
||||
- "metric_depth": Metric depth map of shape (B, 1, H, W)
|
||||
- "domain_logits": Domain logits of shape (B, 2)
|
||||
- "bin_centers": Bin centers of shape (B, N, H, W). Present only if return_final_centers is True
|
||||
- "probs": Bin probabilities of shape (B, N, H, W). Present only if return_probs is True
|
||||
"""
|
||||
b, c, h, w = x.shape
|
||||
self.orig_input_width = w
|
||||
self.orig_input_height = h
|
||||
rel_depth, out = self.core(x, denorm=denorm, return_rel_depth=True)
|
||||
|
||||
outconv_activation = out[0]
|
||||
btlnck = out[1]
|
||||
x_blocks = out[2:]
|
||||
|
||||
x_d0 = self.conv2(btlnck)
|
||||
x = x_d0
|
||||
|
||||
# Predict which path to take
|
||||
embedding = self.patch_transformer(x)[0] # N, E
|
||||
domain_logits = self.mlp_classifier(embedding) # N, 2
|
||||
domain_vote = torch.softmax(domain_logits.sum(
|
||||
dim=0, keepdim=True), dim=-1) # 1, 2
|
||||
|
||||
# Get the path
|
||||
bin_conf_name = ["nyu", "kitti"][torch.argmax(
|
||||
domain_vote, dim=-1).squeeze().item()]
|
||||
|
||||
try:
|
||||
conf = [c for c in self.bin_conf if c.name == bin_conf_name][0]
|
||||
except IndexError:
|
||||
raise ValueError(
|
||||
f"bin_conf_name {bin_conf_name} not found in bin_confs")
|
||||
|
||||
min_depth = conf['min_depth']
|
||||
max_depth = conf['max_depth']
|
||||
|
||||
seed_bin_regressor = self.seed_bin_regressors[bin_conf_name]
|
||||
_, seed_b_centers = seed_bin_regressor(x)
|
||||
if self.bin_centers_type == 'normed' or self.bin_centers_type == 'hybrid2':
|
||||
b_prev = (seed_b_centers - min_depth)/(max_depth - min_depth)
|
||||
else:
|
||||
b_prev = seed_b_centers
|
||||
prev_b_embedding = self.seed_projector(x)
|
||||
|
||||
attractors = self.attractors[bin_conf_name]
|
||||
for projector, attractor, x in zip(self.projectors, attractors, x_blocks):
|
||||
b_embedding = projector(x)
|
||||
b, b_centers = attractor(
|
||||
b_embedding, b_prev, prev_b_embedding, interpolate=True)
|
||||
b_prev = b
|
||||
prev_b_embedding = b_embedding
|
||||
|
||||
last = outconv_activation
|
||||
|
||||
b_centers = nn.functional.interpolate(
|
||||
b_centers, last.shape[-2:], mode='bilinear', align_corners=True)
|
||||
b_embedding = nn.functional.interpolate(
|
||||
b_embedding, last.shape[-2:], mode='bilinear', align_corners=True)
|
||||
|
||||
clb = self.conditional_log_binomial[bin_conf_name]
|
||||
x = clb(last, b_embedding)
|
||||
|
||||
# Now depth value is Sum px * cx , where cx are bin_centers from the last bin tensor
|
||||
# print(x.shape, b_centers.shape)
|
||||
# b_centers = nn.functional.interpolate(b_centers, x.shape[-2:], mode='bilinear', align_corners=True)
|
||||
out = torch.sum(x * b_centers, dim=1, keepdim=True)
|
||||
|
||||
output = dict(domain_logits=domain_logits, metric_depth=out)
|
||||
if return_final_centers or return_probs:
|
||||
output['bin_centers'] = b_centers
|
||||
|
||||
if return_probs:
|
||||
output['probs'] = x
|
||||
return output
|
||||
|
||||
def get_lr_params(self, lr):
|
||||
"""
|
||||
Learning rate configuration for different layers of the model
|
||||
|
||||
Args:
|
||||
lr (float) : Base learning rate
|
||||
Returns:
|
||||
list : list of parameters to optimize and their learning rates, in the format required by torch optimizers.
|
||||
"""
|
||||
param_conf = []
|
||||
if self.train_midas:
|
||||
def get_rel_pos_params():
|
||||
for name, p in self.core.core.pretrained.named_parameters():
|
||||
# if "relative_position" in name:
|
||||
if "pos_embed" in name:
|
||||
yield p
|
||||
|
||||
def get_enc_params_except_rel_pos():
|
||||
for name, p in self.core.core.pretrained.named_parameters():
|
||||
# if "relative_position" not in name:
|
||||
if "pos_embed" not in name:
|
||||
yield p
|
||||
|
||||
encoder_params = get_enc_params_except_rel_pos()
|
||||
rel_pos_params = get_rel_pos_params()
|
||||
# midas_params = self.core.core.scratch.parameters()
|
||||
midas_params = self.core.core.depth_head.parameters()
|
||||
midas_lr_factor = self.midas_lr_factor if self.is_midas_pretrained else 1.0
|
||||
param_conf.extend([
|
||||
{'params': encoder_params, 'lr': lr / self.encoder_lr_factor},
|
||||
{'params': rel_pos_params, 'lr': lr / self.pos_enc_lr_factor},
|
||||
{'params': midas_params, 'lr': lr / midas_lr_factor}
|
||||
])
|
||||
|
||||
remaining_modules = []
|
||||
for name, child in self.named_children():
|
||||
if name != 'core':
|
||||
remaining_modules.append(child)
|
||||
remaining_params = itertools.chain(
|
||||
*[child.parameters() for child in remaining_modules])
|
||||
param_conf.append({'params': remaining_params, 'lr': lr})
|
||||
return param_conf
|
||||
|
||||
def get_conf_parameters(self, conf_name):
|
||||
"""
|
||||
Returns parameters of all the ModuleDicts children that are exclusively used for the given bin configuration
|
||||
"""
|
||||
params = []
|
||||
for name, child in self.named_children():
|
||||
if isinstance(child, nn.ModuleDict):
|
||||
for bin_conf_name, module in child.items():
|
||||
if bin_conf_name == conf_name:
|
||||
params += list(module.parameters())
|
||||
return params
|
||||
|
||||
def freeze_conf(self, conf_name):
|
||||
"""
|
||||
Freezes all the parameters of all the ModuleDicts children that are exclusively used for the given bin configuration
|
||||
"""
|
||||
for p in self.get_conf_parameters(conf_name):
|
||||
p.requires_grad = False
|
||||
|
||||
def unfreeze_conf(self, conf_name):
|
||||
"""
|
||||
Unfreezes all the parameters of all the ModuleDicts children that are exclusively used for the given bin configuration
|
||||
"""
|
||||
for p in self.get_conf_parameters(conf_name):
|
||||
p.requires_grad = True
|
||||
|
||||
def freeze_all_confs(self):
|
||||
"""
|
||||
Freezes all the parameters of all the ModuleDicts children
|
||||
"""
|
||||
for name, child in self.named_children():
|
||||
if isinstance(child, nn.ModuleDict):
|
||||
for bin_conf_name, module in child.items():
|
||||
for p in module.parameters():
|
||||
p.requires_grad = False
|
||||
|
||||
@staticmethod
|
||||
def build(midas_model_type="DPT_BEiT_L_384", pretrained_resource=None, use_pretrained_midas=False, train_midas=False, freeze_midas_bn=True, **kwargs):
|
||||
# core = MidasCore.build(midas_model_type=midas_model_type, use_pretrained_midas=use_pretrained_midas,
|
||||
# train_midas=train_midas, fetch_features=True, freeze_bn=freeze_midas_bn, **kwargs)
|
||||
|
||||
core = DepthAnythingCore.build(midas_model_type='dinov2_large', use_pretrained_midas=use_pretrained_midas,
|
||||
train_midas=train_midas, fetch_features=True, freeze_bn=freeze_midas_bn, **kwargs)
|
||||
|
||||
model = ZoeDepthNK(core, **kwargs)
|
||||
if pretrained_resource:
|
||||
assert isinstance(pretrained_resource, str), "pretrained_resource must be a string"
|
||||
model = load_state_from_resource(model, pretrained_resource)
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
def build_from_config(config):
|
||||
return ZoeDepthNK.build(**config)
|
||||
Reference in New Issue
Block a user