import math from typing import Optional, List import torch from torch import Tensor from torch.nn import Module, ModuleList, Sequential, Upsample from tha3.nn.common.conv_block_factory import ConvBlockFactory from tha3.nn.nonlinearity_factory import LeakyReLUFactory from tha3.nn.normalization import InstanceNorm2dFactory from tha3.nn.util import BlockArgs class ResizeConvEncoderDecoderArgs: def __init__(self, image_size: int, input_channels: int, start_channels: int, bottleneck_image_size, num_bottleneck_blocks, max_channels: int, block_args: Optional[BlockArgs] = None, upsample_mode: str = 'bilinear', use_separable_convolution=False): self.use_separable_convolution = use_separable_convolution self.upsample_mode = upsample_mode self.block_args = block_args self.max_channels = max_channels self.num_bottleneck_blocks = num_bottleneck_blocks self.bottleneck_image_size = bottleneck_image_size self.start_channels = start_channels self.image_size = image_size self.input_channels = input_channels class ResizeConvEncoderDecoder(Module): def __init__(self, args: ResizeConvEncoderDecoderArgs): super().__init__() self.args = args self.num_levels = int(math.log2(args.image_size // args.bottleneck_image_size)) + 1 conv_block_factory = ConvBlockFactory(args.block_args, args.use_separable_convolution) self.downsample_blocks = ModuleList() self.downsample_blocks.append(conv_block_factory.create_conv7_block(args.input_channels, args.start_channels)) current_image_size = args.image_size current_num_channels = args.start_channels while current_image_size > args.bottleneck_image_size: next_image_size = current_image_size // 2 next_num_channels = self.get_num_output_channels_from_image_size(next_image_size) self.downsample_blocks.append(conv_block_factory.create_downsample_block( in_channels=current_num_channels, out_channels=next_num_channels, is_output_1x1=False)) current_image_size = next_image_size current_num_channels = next_num_channels assert len(self.downsample_blocks) == self.num_levels self.bottleneck_blocks = ModuleList() for i in range(args.num_bottleneck_blocks): self.bottleneck_blocks.append(conv_block_factory.create_resnet_block(current_num_channels, is_1x1=False)) self.output_image_sizes = [current_image_size] self.output_num_channels = [current_num_channels] self.upsample_blocks = ModuleList() if args.upsample_mode == 'nearest': align_corners = None else: align_corners = False while current_image_size < args.image_size: next_image_size = current_image_size * 2 next_num_channels = self.get_num_output_channels_from_image_size(next_image_size) self.upsample_blocks.append( Sequential( Upsample(scale_factor=2, mode=args.upsample_mode, align_corners=align_corners), conv_block_factory.create_conv3_block( in_channels=current_num_channels, out_channels=next_num_channels))) current_image_size = next_image_size current_num_channels = next_num_channels self.output_image_sizes.append(current_image_size) self.output_num_channels.append(current_num_channels) def get_num_output_channels_from_level(self, level: int): return self.get_num_output_channels_from_image_size(self.args.image_size // (2 ** level)) def get_num_output_channels_from_image_size(self, image_size: int): return min(self.args.start_channels * (self.args.image_size // image_size), self.args.max_channels) def forward(self, feature: Tensor) -> List[Tensor]: outputs = [] for block in self.downsample_blocks: feature = block(feature) for block in self.bottleneck_blocks: feature = block(feature) outputs.append(feature) for block in self.upsample_blocks: feature = block(feature) outputs.append(feature) return outputs if __name__ == "__main__": device = torch.device('cuda') args = ResizeConvEncoderDecoderArgs( image_size=512, input_channels=4 + 6, start_channels=32, bottleneck_image_size=32, num_bottleneck_blocks=6, max_channels=512, use_separable_convolution=True, block_args=BlockArgs( initialization_method='he', use_spectral_norm=False, normalization_layer_factory=InstanceNorm2dFactory(), nonlinearity_factory=LeakyReLUFactory(inplace=False, negative_slope=0.1))) module = ResizeConvEncoderDecoder(args).to(device) print(module.output_image_sizes) print(module.output_num_channels) input = torch.zeros(8, 4 + 6, 512, 512, device=device) outputs = module(input) for output in outputs: print(output.shape)