| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import torch |
| import torch.nn as nn |
| from functools import partial |
|
|
| def project_onto_planes(planes, coordinates): |
| """ |
| Does a projection of a 3D point onto a batch of 2D planes, |
| returning 2D plane coordinates. |
| |
| Takes plane axes of shape n_planes, 3, 3 |
| # Takes coordinates of shape N, M, 3 |
| # returns projections of shape N*n_planes, M, 2 |
| """ |
| N, M, C = coordinates.shape |
| n_planes, _, _ = planes.shape |
| coordinates = coordinates.unsqueeze(1).expand(-1, n_planes, -1, -1).reshape(N*n_planes, M, 3) |
| inv_planes = torch.linalg.inv(planes).unsqueeze(0).expand(N, -1, -1, -1).reshape(N*n_planes, 3, 3) |
| projections = torch.bmm(coordinates, inv_planes) |
| return projections[..., :2] |
|
|
| def sample_from_planes(plane_features, coordinates, mode='bilinear', padding_mode='zeros', box_warp=None): |
| plane_axes = torch.tensor([[[1, 0, 0], |
| [0, 1, 0], |
| [0, 0, 1]], |
| [[1, 0, 0], |
| [0, 0, 1], |
| [0, 1, 0]], |
| [[0, 0, 1], |
| [0, 1, 0], |
| [1, 0, 0]]], dtype=torch.float32).cuda() |
| |
| assert padding_mode == 'zeros' |
| N, n_planes, C, H, W = plane_features.shape |
| _, M, _ = coordinates.shape |
| plane_features = plane_features.view(N*n_planes, C, H, W) |
|
|
| projected_coordinates = project_onto_planes(plane_axes, coordinates).unsqueeze(1) |
| output_features = torch.nn.functional.grid_sample(plane_features, projected_coordinates.float(), mode=mode, padding_mode=padding_mode, align_corners=False).permute(0, 3, 2, 1).reshape(N, n_planes, M, C) |
| return output_features |
|
|
| def get_grid_coord(grid_size = 256, align_corners=False): |
| if align_corners == False: |
| coords = torch.linspace(-1 + 1/(grid_size), 1 - 1/(grid_size), steps=grid_size) |
| else: |
| coords = torch.linspace(-1, 1, steps=grid_size) |
| i, j, k = torch.meshgrid(coords, coords, coords, indexing='ij') |
| coordinates = torch.stack((i, j, k), dim=-1).reshape(-1, 3) |
| return coordinates |
|
|
| class BasicBlock(nn.Module): |
| """ |
| Transformer block that is in its simplest form. |
| Designed for PF-LRM architecture. |
| """ |
| |
| def __init__(self, inner_dim: int, num_heads: int, eps: float, |
| attn_drop: float = 0., attn_bias: bool = False, |
| mlp_ratio: float = 4., mlp_drop: float = 0.): |
| super().__init__() |
| self.norm1 = nn.LayerNorm(inner_dim, eps=eps) |
| self.self_attn = nn.MultiheadAttention( |
| embed_dim=inner_dim, num_heads=num_heads, |
| dropout=attn_drop, bias=attn_bias, batch_first=True) |
| self.norm2 = nn.LayerNorm(inner_dim, eps=eps) |
| self.mlp = nn.Sequential( |
| nn.Linear(inner_dim, int(inner_dim * mlp_ratio)), |
| nn.GELU(), |
| nn.Dropout(mlp_drop), |
| nn.Linear(int(inner_dim * mlp_ratio), inner_dim), |
| nn.Dropout(mlp_drop), |
| ) |
|
|
| def forward(self, x): |
| |
| before_sa = self.norm1(x) |
| x = x + self.self_attn(before_sa, before_sa, before_sa, need_weights=False)[0] |
| x = x + self.mlp(self.norm2(x)) |
| return x |
| |
| class ConditionBlock(nn.Module): |
| """ |
| Transformer block that takes in a cross-attention condition. |
| Designed for SparseLRM architecture. |
| """ |
| |
| def __init__(self, inner_dim: int, cond_dim: int, num_heads: int, eps: float, |
| attn_drop: float = 0., attn_bias: bool = False, |
| mlp_ratio: float = 4., mlp_drop: float = 0.): |
| super().__init__() |
| self.norm1 = nn.LayerNorm(inner_dim, eps=eps) |
| self.cross_attn = nn.MultiheadAttention( |
| embed_dim=inner_dim, num_heads=num_heads, kdim=cond_dim, vdim=cond_dim, |
| dropout=attn_drop, bias=attn_bias, batch_first=True) |
| self.norm2 = nn.LayerNorm(inner_dim, eps=eps) |
| self.self_attn = nn.MultiheadAttention( |
| embed_dim=inner_dim, num_heads=num_heads, |
| dropout=attn_drop, bias=attn_bias, batch_first=True) |
| self.norm3 = nn.LayerNorm(inner_dim, eps=eps) |
| self.mlp = nn.Sequential( |
| nn.Linear(inner_dim, int(inner_dim * mlp_ratio)), |
| nn.GELU(), |
| nn.Dropout(mlp_drop), |
| nn.Linear(int(inner_dim * mlp_ratio), inner_dim), |
| nn.Dropout(mlp_drop), |
| ) |
|
|
| def forward(self, x, cond): |
| |
| |
| x = x + self.cross_attn(self.norm1(x), cond, cond, need_weights=False)[0] |
| before_sa = self.norm2(x) |
| x = x + self.self_attn(before_sa, before_sa, before_sa, need_weights=False)[0] |
| x = x + self.mlp(self.norm3(x)) |
| return x |
|
|
| class TransformerDecoder(nn.Module): |
| def __init__(self, block_type: str, |
| num_layers: int, num_heads: int, |
| inner_dim: int, cond_dim: int = None, |
| eps: float = 1e-6): |
| super().__init__() |
| self.block_type = block_type |
| self.layers = nn.ModuleList([ |
| self._block_fn(inner_dim, cond_dim)( |
| num_heads=num_heads, |
| eps=eps, |
| ) |
| for _ in range(num_layers) |
| ]) |
| self.norm = nn.LayerNorm(inner_dim, eps=eps) |
|
|
| @property |
| def block_type(self): |
| return self._block_type |
|
|
| @block_type.setter |
| def block_type(self, block_type): |
| assert block_type in ['cond', 'basic'], \ |
| f"Unsupported block type: {block_type}" |
| self._block_type = block_type |
|
|
| def _block_fn(self, inner_dim, cond_dim): |
| assert inner_dim is not None, f"inner_dim must always be specified" |
| if self.block_type == 'basic': |
| return partial(BasicBlock, inner_dim=inner_dim) |
| elif self.block_type == 'cond': |
| assert cond_dim is not None, f"Condition dimension must be specified for ConditionBlock" |
| return partial(ConditionBlock, inner_dim=inner_dim, cond_dim=cond_dim) |
| else: |
| raise ValueError(f"Unsupported block type during runtime: {self.block_type}") |
|
|
|
|
| def forward_layer(self, layer: nn.Module, x: torch.Tensor, cond: torch.Tensor,): |
| if self.block_type == 'basic': |
| return layer(x) |
| elif self.block_type == 'cond': |
| return layer(x, cond) |
| else: |
| raise NotImplementedError |
|
|
| def forward(self, x: torch.Tensor, cond: torch.Tensor = None): |
| |
| |
| for layer in self.layers: |
| x = self.forward_layer(layer, x, cond) |
| x = self.norm(x) |
| return x |
|
|
| class Voxel2Triplane(nn.Module): |
| """ |
| Full model of the basic single-view large reconstruction model. |
| """ |
| def __init__(self, transformer_dim: int, transformer_layers: int, transformer_heads: int, |
| triplane_low_res: int, triplane_high_res: int, triplane_dim: int, voxel_feat_dim: int, normalize_vox_feat=False, voxel_dim=16): |
| super().__init__() |
| |
| |
| self.triplane_low_res = triplane_low_res |
| self.triplane_high_res = triplane_high_res |
| self.triplane_dim = triplane_dim |
| self.voxel_feat_dim = voxel_feat_dim |
|
|
| |
| self.pos_embed = nn.Parameter(torch.randn(1, 3*triplane_low_res**2, transformer_dim) * (1. / transformer_dim) ** 0.5) |
| self.transformer = TransformerDecoder( |
| block_type='cond', |
| num_layers=transformer_layers, num_heads=transformer_heads, |
| inner_dim=transformer_dim, cond_dim=voxel_feat_dim |
| ) |
| self.upsampler = nn.ConvTranspose2d(transformer_dim, triplane_dim, kernel_size=8, stride=8, padding=0) |
|
|
| self.normalize_vox_feat = normalize_vox_feat |
| if normalize_vox_feat: |
| self.vox_norm = nn.LayerNorm(voxel_feat_dim, eps=1e-6) |
| self.vox_pos_embed = nn.Parameter(torch.randn(1, voxel_dim * voxel_dim * voxel_dim, voxel_feat_dim) * (1. / voxel_feat_dim) ** 0.5) |
|
|
| def forward_transformer(self, voxel_feats): |
| N = voxel_feats.shape[0] |
| x = self.pos_embed.repeat(N, 1, 1) |
| if self.normalize_vox_feat: |
| vox_pos_embed = self.vox_pos_embed.repeat(N, 1, 1) |
| voxel_feats = self.vox_norm(voxel_feats + vox_pos_embed) |
| x = self.transformer( |
| x, |
| cond=voxel_feats |
| ) |
| return x |
|
|
| def reshape_upsample(self, tokens): |
| N = tokens.shape[0] |
| H = W = self.triplane_low_res |
| x = tokens.view(N, 3, H, W, -1) |
| x = torch.einsum('nihwd->indhw', x) |
| x = x.contiguous().view(3*N, -1, H, W) |
| x = self.upsampler(x) |
| x = x.view(3, N, *x.shape[-3:]) |
| x = torch.einsum('indhw->nidhw', x) |
| x = x.contiguous() |
| return x |
|
|
| def forward(self, voxel_feats): |
| N = voxel_feats.shape[0] |
|
|
| |
| assert voxel_feats.shape[-1] == self.voxel_feat_dim, \ |
| f"Feature dimension mismatch: {voxel_feats.shape[-1]} vs {self.voxel_feat_dim}" |
|
|
| |
| tokens = self.forward_transformer(voxel_feats) |
| planes = self.reshape_upsample(tokens) |
| assert planes.shape[0] == N, "Batch size mismatch for planes" |
| assert planes.shape[1] == 3, "Planes should have 3 channels" |
|
|
| return planes |
|
|
|
|
| class TriplaneTransformer(nn.Module): |
| """ |
| Full model of the basic single-view large reconstruction model. |
| """ |
| def __init__(self, input_dim: int, transformer_dim: int, transformer_layers: int, transformer_heads: int, |
| triplane_low_res: int, triplane_high_res: int, triplane_dim: int): |
| super().__init__() |
| |
| |
| self.triplane_low_res = triplane_low_res |
| self.triplane_high_res = triplane_high_res |
| self.triplane_dim = triplane_dim |
|
|
| |
| self.pos_embed = nn.Parameter(torch.randn(1, 3*triplane_low_res**2, transformer_dim) * (1. / transformer_dim) ** 0.5) |
| self.transformer = TransformerDecoder( |
| block_type='basic', |
| num_layers=transformer_layers, num_heads=transformer_heads, |
| inner_dim=transformer_dim, |
| ) |
| |
| self.downsampler = nn.Sequential( |
| nn.Conv2d(input_dim, transformer_dim, kernel_size=3, stride=1, padding=1), |
| nn.ReLU(), |
| nn.MaxPool2d(kernel_size=2, stride=2), |
| |
| nn.Conv2d(transformer_dim, transformer_dim, kernel_size=3, stride=1, padding=1), |
| nn.ReLU(), |
| nn.MaxPool2d(kernel_size=2, stride=2), |
| ) |
|
|
| self.upsampler = nn.ConvTranspose2d(transformer_dim, triplane_dim, kernel_size=4, stride=4, padding=0) |
|
|
| self.mlp = nn.Sequential( |
| nn.Linear(input_dim, triplane_dim), |
| nn.ReLU(), |
| nn.Linear(triplane_dim, triplane_dim) |
| ) |
|
|
| def forward_transformer(self, triplanes): |
| N = triplanes.shape[0] |
| tokens = torch.einsum('nidhw->nihwd', triplanes).reshape(N, self.pos_embed.shape[1], -1) |
| x = self.pos_embed.repeat(N, 1, 1) + tokens |
| x = self.transformer(x) |
| return x |
|
|
| def reshape_downsample(self, triplanes): |
| N = triplanes.shape[0] |
| H = W = self.triplane_high_res |
| x = triplanes.view(N, 3, -1, H, W) |
| x = torch.einsum('nidhw->indhw', x) |
| x = x.contiguous().view(3*N, -1, H, W) |
| x = self.downsampler(x) |
| x = x.view(3, N, *x.shape[-3:]) |
| x = torch.einsum('indhw->nidhw', x) |
| x = x.contiguous() |
| return x |
|
|
| def reshape_upsample(self, tokens): |
| N = tokens.shape[0] |
| H = W = self.triplane_low_res |
| x = tokens.view(N, 3, H, W, -1) |
| x = torch.einsum('nihwd->indhw', x) |
| x = x.contiguous().view(3*N, -1, H, W) |
| x = self.upsampler(x) |
| x = x.view(3, N, *x.shape[-3:]) |
| x = torch.einsum('indhw->nidhw', x) |
| x = x.contiguous() |
| return x |
| |
| def forward(self, triplanes): |
| downsampled_triplanes = self.reshape_downsample(triplanes) |
| tokens = self.forward_transformer(downsampled_triplanes) |
| residual = self.reshape_upsample(tokens) |
| |
| triplanes = triplanes.permute(0, 1, 3, 4, 2).contiguous() |
| triplanes = self.mlp(triplanes) |
| triplanes = triplanes.permute(0, 1, 4, 2, 3).contiguous() |
| planes = triplanes + residual |
| return planes |
|
|