Spaces:
Runtime error
Runtime error
| """ | |
| MLP-based decoder for reconstructing point clouds from latent codes. | |
| This module provides flexible MLP architectures for decoding latent representations | |
| into point clouds, commonly used in generative models like VAE and cVAE. | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from typing import List, Optional | |
| class PointCloudDecoder(nn.Module): | |
| """ | |
| MLP-based decoder for reconstructing point clouds from latent representations. | |
| This decoder uses a series of fully connected layers with optional dropout | |
| and normalization to transform a latent code into point cloud coordinates. | |
| Args: | |
| latent_dim: Dimensionality of input latent code | |
| num_points: Number of output points in the point cloud | |
| point_dim: Dimensionality of each point (default: 3 for XYZ coordinates) | |
| hidden_dims: List of hidden layer dimensions (default: [1024, 2048]) | |
| dropout_rate: Dropout probability (default: 0.3) | |
| use_batch_norm: Whether to use batch normalization (default: False) | |
| activation: Activation function to use (default: 'relu') | |
| Input: | |
| Latent code of shape (B, latent_dim) | |
| Output: | |
| Point cloud of shape (B, num_points, point_dim) | |
| """ | |
| def __init__( | |
| self, | |
| latent_dim: int, | |
| num_points: int, | |
| point_dim: int = 3, | |
| hidden_dims: Optional[List[int]] = None, | |
| dropout_rate: float = 0.3, | |
| use_batch_norm: bool = False, | |
| activation: str = 'relu' | |
| ): | |
| super(PointCloudDecoder, self).__init__() | |
| self.latent_dim = latent_dim | |
| self.num_points = num_points | |
| self.point_dim = point_dim | |
| self.dropout_rate = dropout_rate | |
| self.use_batch_norm = use_batch_norm | |
| # Default hidden dimensions | |
| if hidden_dims is None: | |
| hidden_dims = [1024, 2048] | |
| self.hidden_dims = hidden_dims | |
| # Select activation function | |
| if activation == 'relu': | |
| self.activation = nn.ReLU() | |
| elif activation == 'leaky_relu': | |
| self.activation = nn.LeakyReLU(0.2) | |
| elif activation == 'elu': | |
| self.activation = nn.ELU() | |
| elif activation == 'gelu': | |
| self.activation = nn.GELU() | |
| else: | |
| raise ValueError(f"Unsupported activation: {activation}") | |
| # Build network layers | |
| self.layers = nn.ModuleList() | |
| self.batch_norms = nn.ModuleList() if use_batch_norm else None | |
| self.dropouts = nn.ModuleList() | |
| # Input layer | |
| prev_dim = latent_dim | |
| for hidden_dim in hidden_dims: | |
| self.layers.append(nn.Linear(prev_dim, hidden_dim)) | |
| if use_batch_norm: | |
| self.batch_norms.append(nn.BatchNorm1d(hidden_dim)) | |
| self.dropouts.append(nn.Dropout(dropout_rate)) | |
| prev_dim = hidden_dim | |
| # Output layer | |
| output_dim = num_points * point_dim | |
| self.output_layer = nn.Linear(prev_dim, output_dim) | |
| def forward(self, z: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Decode latent code into point cloud. | |
| Args: | |
| z: Latent code of shape (B, latent_dim) | |
| Returns: | |
| Reconstructed point cloud of shape (B, num_points, point_dim) | |
| """ | |
| x = z | |
| # Process through hidden layers | |
| for i, layer in enumerate(self.layers): | |
| x = layer(x) | |
| if self.use_batch_norm: | |
| x = self.batch_norms[i](x) | |
| x = self.activation(x) | |
| x = self.dropouts[i](x) | |
| # Output layer | |
| x = self.output_layer(x) | |
| # Reshape to point cloud | |
| x = x.view(-1, self.num_points, self.point_dim) | |
| return x | |
| def get_output_shape(self) -> tuple: | |
| """ | |
| Get the output point cloud shape (excluding batch dimension). | |
| Returns: | |
| Tuple of (num_points, point_dim) | |
| """ | |
| return (self.num_points, self.point_dim) | |
| class ResidualMLPDecoder(nn.Module): | |
| """ | |
| MLP decoder with residual connections for improved gradient flow. | |
| This decoder uses residual blocks to help with training deep networks. | |
| Args: | |
| latent_dim: Dimensionality of input latent code | |
| num_points: Number of output points in the point cloud | |
| point_dim: Dimensionality of each point (default: 3 for XYZ coordinates) | |
| hidden_dim: Hidden layer dimension (default: 1024) | |
| num_blocks: Number of residual blocks (default: 3) | |
| dropout_rate: Dropout probability (default: 0.3) | |
| use_batch_norm: Whether to use batch normalization (default: True) | |
| Input: | |
| Latent code of shape (B, latent_dim) | |
| Output: | |
| Point cloud of shape (B, num_points, point_dim) | |
| """ | |
| def __init__( | |
| self, | |
| latent_dim: int, | |
| num_points: int, | |
| point_dim: int = 3, | |
| hidden_dim: int = 1024, | |
| num_blocks: int = 3, | |
| dropout_rate: float = 0.3, | |
| use_batch_norm: bool = True | |
| ): | |
| super(ResidualMLPDecoder, self).__init__() | |
| self.latent_dim = latent_dim | |
| self.num_points = num_points | |
| self.point_dim = point_dim | |
| self.hidden_dim = hidden_dim | |
| # Initial projection | |
| self.input_proj = nn.Linear(latent_dim, hidden_dim) | |
| # Residual blocks | |
| self.blocks = nn.ModuleList([ | |
| ResidualBlock(hidden_dim, dropout_rate, use_batch_norm) | |
| for _ in range(num_blocks) | |
| ]) | |
| # Output projection | |
| output_dim = num_points * point_dim | |
| self.output_proj = nn.Linear(hidden_dim, output_dim) | |
| def forward(self, z: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Decode latent code into point cloud. | |
| Args: | |
| z: Latent code of shape (B, latent_dim) | |
| Returns: | |
| Reconstructed point cloud of shape (B, num_points, point_dim) | |
| """ | |
| # Initial projection | |
| x = F.relu(self.input_proj(z)) | |
| # Residual blocks | |
| for block in self.blocks: | |
| x = block(x) | |
| # Output projection | |
| x = self.output_proj(x) | |
| x = x.view(-1, self.num_points, self.point_dim) | |
| return x | |
| class ResidualBlock(nn.Module): | |
| """ | |
| Residual block with optional batch normalization and dropout. | |
| Args: | |
| hidden_dim: Dimension of the hidden layer | |
| dropout_rate: Dropout probability | |
| use_batch_norm: Whether to use batch normalization | |
| """ | |
| def __init__( | |
| self, | |
| hidden_dim: int, | |
| dropout_rate: float = 0.3, | |
| use_batch_norm: bool = True | |
| ): | |
| super(ResidualBlock, self).__init__() | |
| self.fc1 = nn.Linear(hidden_dim, hidden_dim) | |
| self.fc2 = nn.Linear(hidden_dim, hidden_dim) | |
| self.dropout = nn.Dropout(dropout_rate) | |
| if use_batch_norm: | |
| self.bn1 = nn.BatchNorm1d(hidden_dim) | |
| self.bn2 = nn.BatchNorm1d(hidden_dim) | |
| else: | |
| self.bn1 = None | |
| self.bn2 = None | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Forward pass through residual block. | |
| Args: | |
| x: Input tensor of shape (B, hidden_dim) | |
| Returns: | |
| Output tensor of shape (B, hidden_dim) | |
| """ | |
| residual = x | |
| out = self.fc1(x) | |
| if self.bn1 is not None: | |
| out = self.bn1(out) | |
| out = F.relu(out) | |
| out = self.dropout(out) | |
| out = self.fc2(out) | |
| if self.bn2 is not None: | |
| out = self.bn2(out) | |
| out = out + residual | |
| out = F.relu(out) | |
| return out | |
| def create_pointcloud_decoder( | |
| latent_dim: int, | |
| num_points: int = 6890, | |
| point_dim: int = 3, | |
| architecture: str = 'mlp', | |
| **kwargs | |
| ) -> nn.Module: | |
| """ | |
| Factory function to create a point cloud decoder with specified architecture. | |
| Args: | |
| latent_dim: Dimensionality of input latent code | |
| num_points: Number of output points (default: 6890 for SMPL mesh) | |
| point_dim: Dimensionality of each point (default: 3 for XYZ) | |
| architecture: Decoder architecture type ('mlp' or 'residual') | |
| **kwargs: Additional arguments passed to the decoder constructor | |
| Returns: | |
| Configured decoder instance | |
| Examples: | |
| >>> # Create a simple MLP decoder | |
| >>> decoder = create_pointcloud_decoder( | |
| ... latent_dim=256, | |
| ... num_points=6890, | |
| ... architecture='mlp', | |
| ... hidden_dims=[1024, 2048], | |
| ... dropout_rate=0.3 | |
| ... ) | |
| >>> # Create a residual decoder | |
| >>> decoder = create_pointcloud_decoder( | |
| ... latent_dim=256, | |
| ... num_points=6890, | |
| ... architecture='residual', | |
| ... hidden_dim=1024, | |
| ... num_blocks=3 | |
| ... ) | |
| """ | |
| if architecture == 'mlp': | |
| return PointCloudDecoder( | |
| latent_dim=latent_dim, | |
| num_points=num_points, | |
| point_dim=point_dim, | |
| **kwargs | |
| ) | |
| elif architecture == 'residual': | |
| return ResidualMLPDecoder( | |
| latent_dim=latent_dim, | |
| num_points=num_points, | |
| point_dim=point_dim, | |
| **kwargs | |
| ) | |
| else: | |
| raise ValueError(f"Unsupported architecture: {architecture}") | |
| if __name__ == '__main__': | |
| print("Testing Point Cloud Decoders...\n") | |
| # Test parameters | |
| batch_size = 32 | |
| latent_dim = 256 | |
| num_points = 6890 # SMPL mesh vertices | |
| point_dim = 3 | |
| # Test standard MLP decoder | |
| print("1. Standard MLP Decoder:") | |
| mlp_decoder = create_pointcloud_decoder( | |
| latent_dim=latent_dim, | |
| num_points=num_points, | |
| point_dim=point_dim, | |
| architecture='mlp', | |
| hidden_dims=[1024, 2048], | |
| dropout_rate=0.3, | |
| use_batch_norm=False | |
| ) | |
| z = torch.randn(batch_size, latent_dim) | |
| output = mlp_decoder(z) | |
| print(f" Input shape: {z.shape}") | |
| print(f" Output shape: {output.shape}") | |
| print(f" Output expected shape: {mlp_decoder.get_output_shape()}") | |
| print() | |
| # Test MLP decoder with batch norm | |
| print("2. MLP Decoder with Batch Normalization:") | |
| mlp_bn_decoder = create_pointcloud_decoder( | |
| latent_dim=latent_dim, | |
| num_points=num_points, | |
| architecture='mlp', | |
| use_batch_norm=True, | |
| activation='leaky_relu' | |
| ) | |
| output = mlp_bn_decoder(z) | |
| print(f" Output shape: {output.shape}") | |
| print() | |
| # Test residual MLP decoder | |
| print("3. Residual MLP Decoder:") | |
| residual_decoder = create_pointcloud_decoder( | |
| latent_dim=latent_dim, | |
| num_points=num_points, | |
| architecture='residual', | |
| hidden_dim=1024, | |
| num_blocks=3, | |
| dropout_rate=0.3 | |
| ) | |
| output = residual_decoder(z) | |
| print(f" Input shape: {z.shape}") | |
| print(f" Output shape: {output.shape}") | |
| print() | |
| # Test with different point dimensions | |
| print("4. Decoder with 6D points (XYZ + RGB):") | |
| decoder_6d = create_pointcloud_decoder( | |
| latent_dim=latent_dim, | |
| num_points=1000, | |
| point_dim=6, | |
| architecture='mlp', | |
| hidden_dims=[512, 1024] | |
| ) | |
| output = decoder_6d(z) | |
| print(f" Output shape: {output.shape}") | |
| print() | |