Initial media depth project backup

This commit is contained in:
Codex
2026-05-20 12:25:12 +08:00
commit 4a0aebb2bd
358 changed files with 182095 additions and 0 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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'")

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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]

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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)

View File

@@ -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]

View File

@@ -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
}
}

View File

@@ -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)