Download worldstrat/src/modules.py from hackvermin/Setellite_super_resolution: direct link, hf CLI and curl.
- Browser
- Download file 34.4 kB
-
https://huggingface.co/spaces/hackvermin/Setellite_super_resolution/resolve/main/worldstrat/src/modules.py
- Command line
-
hf download hf://spaces/hackvermin/Setellite_super_resolution/worldstrat/src/modules.py
-
curl -L -o modules.py https://huggingface.co/spaces/hackvermin/Setellite_super_resolution/resolve/main/worldstrat/src/modules.py
34.4 kB
| from typing_extensions import Self | |
| from src.datasources import JIF_S2_MEAN, JIF_S2_STD, S2_ALL_12BANDS | |
| import numpy as np | |
| import torch | |
| import torch.nn.functional as F | |
| from math import log2 | |
| from kornia.geometry.transform import Resize | |
| from torch import nn, Tensor | |
| from typing import Tuple, Optional | |
| from src.transforms import Shift, WarpPerspective | |
| ############################################################################### | |
| # Layers | |
| ############################################################################### | |
| class OneHot(nn.Module): | |
| """ One-hot encoder. """ | |
| def __init__(self, num_classes): | |
| """ Initialize the OneHot layer. | |
| Parameters | |
| ---------- | |
| num_classes : int | |
| The number of classes to encode. | |
| """ | |
| super().__init__() | |
| self.num_classes = num_classes | |
| def forward(self, x): | |
| """ Forward pass. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor. | |
| Returns | |
| ------- | |
| Tensor | |
| The encoded tensor. | |
| """ | |
| # Current shape of x: (..., 1, H, W). | |
| x = x.to(torch.int64) | |
| # Remove the empty dimension: (..., H, W). | |
| x = x.squeeze(-3) | |
| # One-hot encode: (..., H, W, num_classes) | |
| x = F.one_hot(x, num_classes=self.num_classes) | |
| # Permute the dimensions so the number of classes is before the height and width. | |
| if x.ndim == 5: | |
| x = x.permute(0, 1, 4, 2, 3) # (..., num_classes, H, W) | |
| elif x.ndim == 4: | |
| x = x.permute(0, 3, 1, 2) | |
| return x | |
| class DoubleConv2d(nn.Module): | |
| """ Two-layer 2D convolutional block with a PReLU activation in between. """ | |
| # TODO: verify if we still need reflect padding-mode. If we do, set via constructor. | |
| def __init__(self, in_channels, out_channels, kernel_size=3, use_batchnorm=False): | |
| """ Initialize the DoubleConv2d layer. | |
| Parameters | |
| ---------- | |
| in_channels : int | |
| The number of input channels. | |
| out_channels : int | |
| The number of output channels. | |
| kernel_size : int, optional | |
| The kernel size, by default 3. | |
| use_batchnorm : bool, optional | |
| Whether to use batch normalization, by default False. | |
| """ | |
| super().__init__() | |
| self.doubleconv2d = nn.Sequential( | |
| # ------- First block ------- | |
| # First convolutional layer. | |
| nn.Conv2d( | |
| in_channels=in_channels, | |
| out_channels=out_channels, | |
| kernel_size=kernel_size, | |
| padding="same", | |
| bias=not use_batchnorm, | |
| padding_mode="reflect", | |
| ), | |
| # Batch normalization, if requested. | |
| nn.BatchNorm2d(out_channels) if use_batchnorm else nn.Identity(), | |
| # Parametric ReLU activation. | |
| nn.PReLU(), | |
| # Dropout regularization, keep probability 0.5. | |
| nn.Dropout(p=0.5), | |
| # ------- Second block ------- | |
| # Second convolutional layer. | |
| nn.Conv2d( | |
| in_channels=out_channels, | |
| out_channels=out_channels, | |
| kernel_size=kernel_size, | |
| padding="same", | |
| bias=not use_batchnorm, | |
| padding_mode="reflect", | |
| ), | |
| # Batch normalization, if requested. | |
| nn.BatchNorm2d(out_channels) if use_batchnorm else nn.Identity(), | |
| # Parametric ReLU activation. | |
| nn.PReLU(), | |
| # Dropout regularization, keep probability 0.5. | |
| nn.Dropout(p=0.5), | |
| ) | |
| def forward(self, x: Tensor) -> Tensor: | |
| """ Forward pass of the DoubleConv2d layer. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor of shape (batch_size, in_channels, height, width). | |
| Returns | |
| ------- | |
| Tensor | |
| The output tensor of shape (batch_size, out_channels, height, width). | |
| """ | |
| return self.doubleconv2d(x) | |
| class ResidualBlock(nn.Module): | |
| """ Two-layer 2D convolutional block (DoubleConv2d) | |
| with a skip-connection to a sum.""" | |
| def __init__(self, in_channels, kernel_size=3, **kws): | |
| """ Initialize the ResidualBlock layer. | |
| Parameters | |
| ---------- | |
| in_channels : int | |
| The number of input channels. | |
| kernel_size : int, optional | |
| The kernel size, by default 3. | |
| """ | |
| super().__init__() | |
| self.residualblock = DoubleConv2d( | |
| in_channels=in_channels, | |
| out_channels=in_channels, | |
| kernel_size=kernel_size, | |
| **kws, | |
| ) | |
| def forward(self, x: Tensor) -> Tensor: | |
| """ Forward pass of the ResidualBlock layer. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor of shape (batch_size, in_channels, height, width). | |
| Returns | |
| ------- | |
| Tensor | |
| The output tensor of shape (batch_size, in_channels, height, width). | |
| """ | |
| x = x + self.residualblock(x) | |
| return x | |
| class DenseBlock(ResidualBlock): | |
| """ Two-layer 2D convolutional block (DoubleConv2d) with a skip-connection | |
| to a concatenation (instead of a sum used in ResidualBlock).""" | |
| def forward(self, x: Tensor) -> Tensor: | |
| """ Forward pass of the DenseBlock layer. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor of shape (batch_size, in_channels, height, width). | |
| Returns | |
| ------- | |
| Tensor | |
| The output tensor of shape (batch_size, in_channels, height, width). | |
| """ | |
| return torch.cat([x, self.residualblock(x)], dim=1) | |
| class FusionBlock(nn.Module): | |
| """ A block that fuses two revisits into one. """ | |
| def __init__(self, in_channels, kernel_size=3, use_batchnorm=False): | |
| """ Initialize the FusionBlock layer. | |
| Fuse workflow: | |
| xx ---> xx ---> x | |
| | ^ | |
| |-------^ | |
| Parameters | |
| ---------- | |
| in_channels : int | |
| The number of input channels. | |
| kernel_size : int, optional | |
| The kernel size, by default 3. | |
| use_batchnorm : bool, optional | |
| Whether to use batch normalization, by default False. | |
| """ | |
| super().__init__() | |
| # TODO: it might be better to fuse the encodings in groups - one per channel. | |
| number_of_revisits_to_fuse = 2 | |
| self.fuse = nn.Sequential( | |
| # A two-layer 2D convolutional block with a skip-connection to a sum. | |
| ResidualBlock( | |
| number_of_revisits_to_fuse * in_channels, | |
| kernel_size, | |
| use_batchnorm=use_batchnorm, | |
| ), | |
| # A 2D convolutional layer. | |
| nn.Conv2d( | |
| in_channels=number_of_revisits_to_fuse * in_channels, | |
| out_channels=in_channels, | |
| kernel_size=kernel_size, | |
| padding="same", | |
| bias=not use_batchnorm, | |
| padding_mode="reflect", | |
| ), | |
| # Batch normalization, if requested. | |
| nn.BatchNorm2d(in_channels) if use_batchnorm else nn.Identity(), | |
| # Parametric ReLU activation. | |
| nn.PReLU(), | |
| ) | |
| def split(x): | |
| """ Split the input tensor (revisits) into two parts/halves. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor of shape (batch_size, revisits, in_channels, height, width). | |
| Returns | |
| ------- | |
| tuple of Tensor | |
| The two output tensors of shape (batch_size, revisits//2, in_channels, height, width). | |
| """ | |
| number_of_revisits = x.shape[1] | |
| assert number_of_revisits % 2 == 0, f"number_of_revisits={number_of_revisits}" | |
| # (batch_size, revisits//2, in_channels, height, width) | |
| first_half = x[:, : number_of_revisits // 2].contiguous() | |
| second_half = x[:, number_of_revisits // 2 :].contiguous() | |
| # TODO: return a carry-encoding? | |
| return first_half, second_half | |
| def forward(self, x): | |
| """ Forward pass of the FusionBlock layer. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor of shape (batch_size, revisits, in_channels, height, width). | |
| Revisits encoding of the input. | |
| Returns | |
| ------- | |
| Tensor | |
| The output tensor of shape (batch_size, revisits/2, in_channels, height, width). | |
| Fused encoding of the input. | |
| """ | |
| first_half, second_half = self.split(x) | |
| batch_size, half_revisits, channels, height, width = first_half.shape | |
| first_half = first_half.view( | |
| batch_size * half_revisits, channels, height, width | |
| ) | |
| second_half = second_half.view( | |
| batch_size * half_revisits, channels, height, width | |
| ) | |
| # Current shape of x: (batch_size * revisits//2, 2*in_channels, height, width) | |
| x = torch.cat([first_half, second_half], dim=-3) | |
| # Fused shape of x: (batch_size * revisits//2, in_channels, height, width) | |
| fused_x = self.fuse(x) | |
| # Fused encoding shape of x: (batch_size, revisits/2, channels, width, height) | |
| fused_x = fused_x.view(batch_size, half_revisits, channels, height, width) | |
| return fused_x | |
| class RecursiveFusion(nn.Module): | |
| """ Recursively fuses a set of encodings. """ | |
| def __init__(self, in_channels, kernel_size, revisits): | |
| """ Initialize the RecursiveFusion layer. | |
| Parameters | |
| ---------- | |
| in_channels : int | |
| The number of input channels. | |
| kernel_size : int | |
| The kernel size. | |
| revisits : int | |
| The number of revisits. | |
| """ | |
| super().__init__() | |
| log2_revisits = log2(revisits) | |
| if log2_revisits % 1 == 0: | |
| num_fusion_layers = int(log2_revisits) | |
| else: | |
| num_fusion_layers = int(log2_revisits) + 1 | |
| pairwise_fusion = FusionBlock(in_channels, kernel_size, use_batchnorm=False) | |
| self.fusion = nn.Sequential( | |
| *(pairwise_fusion for _ in range(num_fusion_layers)) | |
| ) | |
| def pad(x): | |
| """ Pad the input tensor with black revisits to a power of 2. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor of shape (batch_size, revisits, in_channels, height, width). | |
| Returns | |
| ------- | |
| Tensor | |
| The output tensor of shape (batch_size, revisits, in_channels, height, width). | |
| """ | |
| # TODO: should we pad with copies of revisits instead of zeros? | |
| # TODO: move to transforms.py | |
| batch_size, revisits, channels, height, width = x.shape | |
| log2_revisits = torch.log2(torch.tensor(revisits)) | |
| if log2_revisits % 1 > 0: | |
| nextpower = torch.ceil(log2_revisits) | |
| number_of_black_padding_revisits = int(2 ** nextpower - revisits) | |
| black_revisits = torch.zeros( | |
| batch_size, | |
| number_of_black_padding_revisits, | |
| channels, | |
| height, | |
| width, | |
| dtype=x.dtype, | |
| device=x.device, | |
| ) | |
| x = torch.cat([x, black_revisits], dim=1) | |
| return x | |
| def forward(self, x): | |
| """ Forward pass of the RecursiveFusion layer. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor of shape (batch_size, revisits, in_channels, height, width). | |
| Returns | |
| ------- | |
| Tensor | |
| The fused output tensor of shape (batch_size, in_channels, height, width). | |
| """ | |
| x = self.pad(x) # Zero-pad if neccessary to ensure power of 2 revisits | |
| x = self.fusion(x) # (batch_size, 1, channels, height, width) | |
| return x.squeeze(1) # (batch_size, channels, height, width) | |
| class ConvTransposeBlock(nn.Module): | |
| """ Upsampler block with ConvTranspose2d. """ | |
| def __init__( | |
| self, | |
| in_channels, | |
| out_channels, | |
| kernel_size, | |
| sr_kernel_size, | |
| zoom_factor, | |
| use_batchnorm=False, | |
| ): | |
| """ Initialize the ConvTransposeBlock layer. | |
| Parameters | |
| ---------- | |
| in_channels : int | |
| The number of input channels. | |
| out_channels : int | |
| The number of output channels. | |
| kernel_size : int | |
| The kernel size. | |
| sr_kernel_size : int | |
| The kernel size of the SR convolution. | |
| zoom_factor : int | |
| The zoom factor. | |
| use_batchnorm : bool, optional | |
| Whether to use batchnorm, by default False. | |
| """ | |
| super().__init__() | |
| self.in_channels = in_channels | |
| self.out_channels = out_channels | |
| self.kernel_size = kernel_size | |
| # TODO: check if sr_kernel_size is the correct name | |
| self.sr_kernel_size = sr_kernel_size | |
| self.zoom_factor = zoom_factor | |
| self.use_batchnorm = use_batchnorm | |
| self.upsample = nn.Sequential( | |
| nn.ConvTranspose2d( | |
| in_channels=self.in_channels, | |
| out_channels=self.in_channels, | |
| kernel_size=self.kernel_size, | |
| stride=self.zoom_factor, | |
| padding=0, | |
| ), | |
| nn.PReLU(), | |
| nn.Conv2d( | |
| in_channels=self.in_channels, | |
| out_channels=self.in_channels, | |
| kernel_size=self.kernel_size, | |
| stride=1, | |
| padding="same", | |
| bias=not use_batchnorm, | |
| padding_mode="reflect", | |
| ), | |
| nn.BatchNorm2d(self.in_channels) if use_batchnorm else nn.Identity(), | |
| nn.PReLU(), | |
| nn.Conv2d( | |
| in_channels=self.in_channels, | |
| out_channels=self.out_channels, | |
| kernel_size=self.sr_kernel_size, | |
| stride=1, | |
| padding="same", | |
| bias=not use_batchnorm, | |
| padding_mode="reflect", | |
| ), | |
| nn.BatchNorm2d(self.out_channels) if use_batchnorm else nn.Identity(), | |
| nn.PReLU(), | |
| ) | |
| def forward(self, x): | |
| """ Forward pass of the ConvTransposeBlock layer. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor of shape (batch_size, in_channels, height, width). | |
| Returns | |
| ------- | |
| Tensor | |
| The output tensor of shape (batch_size, out_channels, height, width). | |
| """ | |
| return self.upsample(x) | |
| class PixelShuffleBlock(ConvTransposeBlock): | |
| """PixelShuffle block with ConvTranspose2d for sub-pixel convolutions. """ | |
| # TODO: add a Dropout layer between the convolution layers? | |
| def __init__(self, **kws): | |
| super().__init__(**kws) | |
| assert self.in_channels % self.zoom_factor ** 2 == 0 | |
| self.in_channels = self.in_channels // self.zoom_factor ** 2 | |
| self.upsample = nn.Sequential( | |
| nn.PixelShuffle(self.zoom_factor), | |
| nn.Conv2d( | |
| in_channels=self.in_channels, | |
| out_channels=self.in_channels, | |
| kernel_size=self.sr_kernel_size, | |
| stride=1, | |
| padding="same", | |
| bias=not self.use_batchnorm, | |
| padding_mode="reflect", | |
| ), | |
| nn.BatchNorm2d(self.in_channels) if self.use_batchnorm else nn.Identity(), | |
| nn.PReLU(), | |
| nn.Conv2d( | |
| in_channels=self.in_channels, | |
| out_channels=self.out_channels, | |
| kernel_size=self.sr_kernel_size, | |
| stride=1, | |
| padding="same", | |
| bias=not self.use_batchnorm, | |
| padding_mode="reflect", | |
| ), | |
| nn.BatchNorm2d(self.out_channels) if self.use_batchnorm else nn.Identity(), | |
| nn.PReLU(), | |
| ) | |
| class HomographyNet(nn.Module): | |
| """ A network that estimates a parametric geometric transformation matrix | |
| between two given images. | |
| Source: https://github.com/mazenmel/Deep-homography-estimation-Pytorch | |
| Reference: https://arxiv.org/pdf/1606.03798.pdf | |
| """ | |
| def __init__(self, input_size, in_channels, fc_size, type="translation"): | |
| """ Initialize the HomographyNet layer. | |
| Parameters | |
| ---------- | |
| input_size : tuple of int | |
| The input size. | |
| in_channels : int | |
| The number of input channels. | |
| fc_size : int | |
| The number of hidden channels. | |
| type : str, optional | |
| The type of transformation, by default 'translation'. | |
| Raises | |
| ------ | |
| ValueError | |
| If the type of transformation is not supported. | |
| """ | |
| super().__init__() | |
| if type not in ["translation", "homography"]: | |
| raise ValueError( | |
| f"Expected ['translation'|'homography'] for 'type'. Got {type}." | |
| ) | |
| self.type = type | |
| self.input_size = input_size | |
| hidden_channels = fc_size // 2 | |
| kernel_size = 3 | |
| stride = 1 | |
| padding = kernel_size // 2 | |
| if self.kind == "translation": | |
| n_transform_params = 2 | |
| self.transform = Shift() | |
| elif self.kind == "homography": | |
| n_transform_params = 8 | |
| self.transform = WarpPerspective() | |
| def convblock(in_channels, out_channels, kernel_size, stride, padding): | |
| return nn.Sequential( | |
| nn.Conv2d( | |
| in_channels, | |
| out_channels, | |
| kernel_size, | |
| stride, | |
| padding, | |
| bias=False, | |
| padding_mode="reflect", | |
| ), | |
| nn.BatchNorm2d(out_channels), | |
| nn.PReLU(), | |
| nn.Conv2d( | |
| out_channels, | |
| out_channels, | |
| kernel_size, | |
| stride, | |
| padding, | |
| bias=False, | |
| padding_mode="reflect", | |
| ), | |
| nn.BatchNorm2d(out_channels), | |
| nn.PReLU(), | |
| ) | |
| self.cnn = nn.Sequential( | |
| convblock( | |
| in_channels=2 * in_channels, | |
| out_channels=hidden_channels, | |
| kernel_size=kernel_size, | |
| stride=stride, | |
| padding=padding, | |
| ), | |
| nn.MaxPool2d(2), | |
| convblock( | |
| in_channels=hidden_channels, | |
| out_channels=hidden_channels, | |
| kernel_size=kernel_size, | |
| stride=stride, | |
| padding=padding, | |
| ), | |
| nn.MaxPool2d(2), | |
| convblock( | |
| in_channels=hidden_channels, | |
| out_channels=2 * hidden_channels, | |
| kernel_size=kernel_size, | |
| stride=stride, | |
| padding=padding, | |
| ), | |
| nn.MaxPool2d(2), | |
| convblock( | |
| in_channels=2 * hidden_channels, | |
| out_channels=2 * hidden_channels, | |
| kernel_size=kernel_size, | |
| stride=stride, | |
| padding=padding, | |
| ), | |
| ) | |
| self.dropout = nn.Dropout(p=0.5) | |
| self.fc = nn.Sequential( | |
| nn.Linear(in_features=fc_size, out_features=n_transform_params, bias=False) | |
| ) | |
| def register(self, image1, image2): | |
| """ Register two images. | |
| Parameters | |
| ---------- | |
| image1 : Tensor | |
| The first image tensor of shape (batch_size, in_channels, height, width). | |
| image2 : Tensor | |
| The second image tensor of shape (batch_size, in_channels, height, width). | |
| Returns | |
| ------- | |
| Tensor | |
| Parametric transformations on image1 with respect to image2 (batch_size, 2). | |
| """ | |
| # Center each image by its own mean. | |
| image1 = image1 - torch.mean(image1, dim=(2, 3), keepdim=True) | |
| image2 = image2 - torch.mean(image2, dim=(2, 3), keepdim=True) | |
| # Concat channels (batch_size, 2 * channels, height, width) | |
| x = torch.cat([image1, image2], dim=1) | |
| x = self.cnn(x) | |
| # (batch_size, channels) global average pooling | |
| x = x.mean(dim=(2, 3)) | |
| x = self.dropout(x) | |
| transformation_parameters = self.fc(x) | |
| transformation_parameters = torch.sigmoid(transformation_parameters) * 2 - 1 | |
| return transformation_parameters | |
| def forward(self, image1, image2): | |
| """ Forward pass (register and transform two images). | |
| Parameters | |
| ---------- | |
| image1 : Tensor | |
| The first image tensor of shape (batch_size, in_channels, height, width). | |
| image2 : Tensor | |
| The second image tensor of shape (batch_size, in_channels, height, width). | |
| Returns | |
| ------- | |
| Tensor | |
| The transformed image tensor of shape (batch_size, in_channels, height, width). | |
| """ | |
| with torch.autograd.set_detect_anomaly(True): | |
| transform_params = self.register(image1, image2) | |
| return self.transform(image1, transform_params) | |
| ############################################################################### | |
| # Models | |
| ############################################################################### | |
| class SRCNN(nn.Module): | |
| """ Super-resolution CNN. | |
| Uses no recursive function, revisits are treated as channels. | |
| """ | |
| def __init__( | |
| self, | |
| in_channels, | |
| mask_channels, | |
| revisits, | |
| hidden_channels, | |
| out_channels, | |
| kernel_size, | |
| residual_layers, | |
| output_size, | |
| zoom_factor, | |
| sr_kernel_size, | |
| registration_kind, | |
| homography_fc_size, | |
| use_reference_frame=False, | |
| **kws, | |
| ) -> None: | |
| """ Initialize the Super-resolution CNN. | |
| Parameters | |
| ---------- | |
| in_channels : int | |
| The number of input channels. | |
| mask_channels : int | |
| The number of mask channels. | |
| revisits : int | |
| The number of revisits. | |
| hidden_channels : int | |
| The number of hidden channels. | |
| out_channels : int | |
| The number of output channels. | |
| kernel_size : int | |
| The kernel size. | |
| residual_layers : int | |
| The number of residual layers. | |
| output_size : tuple of int | |
| The output size. | |
| zoom_factor : int | |
| The zoom factor. | |
| sr_kernel_size : int | |
| The kernel size of the SR convolution. | |
| registration_kind : str, optional | |
| The kind of registration. | |
| homography_fc_size : int | |
| The size of the fully connected layer for the homography. | |
| use_reference_frame : bool, optional | |
| Whether to use the reference frame, by default False. | |
| """ | |
| super().__init__() | |
| self.in_channels = 2 * in_channels if use_reference_frame else in_channels | |
| self.mask_channels = mask_channels | |
| self.revisits = revisits | |
| self.hidden_channels = hidden_channels | |
| self.out_channels = out_channels | |
| self.kernel_size = kernel_size | |
| self.residual_layers = residual_layers | |
| self.output_size = output_size | |
| self.zoom_factor = zoom_factor | |
| self.sr_kernel_size = sr_kernel_size | |
| self.registration_kind = registration_kind | |
| self.homography_fc_size = homography_fc_size | |
| self.use_batchnorm = False | |
| self.use_reference_frame = use_reference_frame | |
| # Image encoder | |
| self.encoder = DoubleConv2d( | |
| in_channels=self.in_channels, | |
| out_channels=hidden_channels, | |
| kernel_size=kernel_size, | |
| use_batchnorm=self.use_batchnorm, | |
| ) | |
| # Mask encoder | |
| self.mask_encoder = nn.Sequential( | |
| OneHot(num_classes=12), | |
| DoubleConv2d(in_channels=self.mask_channels, out_channels=1, kernel_size=3), | |
| nn.Sigmoid(), | |
| ) | |
| # TODO: does it make sense to reuse the same Res-Block object in nn.Sequential? | |
| # Fusion | |
| self.doubleconv2d = DoubleConv2d( | |
| in_channels=hidden_channels * revisits, # revisits as channels | |
| out_channels=hidden_channels, | |
| kernel_size=kernel_size, | |
| use_batchnorm=self.use_batchnorm, | |
| ) | |
| self.residualblocks = nn.Sequential( | |
| *( | |
| ResidualBlock( | |
| in_channels=hidden_channels, | |
| kernel_size=kernel_size, | |
| use_batchnorm=self.use_batchnorm, | |
| ) | |
| for _ in range(residual_layers) | |
| ) | |
| ) | |
| self.fusion = nn.Sequential(self.doubleconv2d, self.residualblocks) | |
| ## Super-resolver (upsampler + renderer) | |
| self.sr = PixelShuffleBlock( | |
| in_channels=hidden_channels, | |
| out_channels=out_channels, | |
| kernel_size=kernel_size, | |
| sr_kernel_size=sr_kernel_size, | |
| zoom_factor=zoom_factor, | |
| use_batchnorm=self.use_batchnorm, | |
| ) | |
| self.resize = Resize( | |
| self.output_size, | |
| interpolation="bilinear", | |
| align_corners=False, | |
| antialias=True, | |
| ) | |
| # TODO: cleanest as reference frame | |
| # TODO: filter for pad-revisits before taking the median | |
| def reference_frame(self, x: Tensor, mask: Optional[Tensor] = None) -> Tensor: | |
| """ Compute the reference frame as the median of all low-res revisits. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor (low-res revisits). | |
| mask : Tensor, optional | |
| The mask tensor of shape. | |
| Returns | |
| ------- | |
| Tensor | |
| The reference frame. | |
| """ | |
| return x.median(dim=-4, keepdim=True).values | |
| def forward( | |
| self, x: Tensor, y: Optional[Tensor] = None, mask: Optional[Tensor] = None, | |
| ) -> Tensor: | |
| """ Forward pass of the Super-resolution CNN. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor (low-res revisits). | |
| Shape: (batch_size, revisits, channels, height, width). | |
| y : Tensor, optional | |
| The target tensor (high-res revisits). | |
| Shape: (batch_size, 1, channels, height, width). | |
| mask : Tensor, optional | |
| The mask tensor.y | |
| Shape: (batch_size, revisits, mask_channels, height, width). | |
| Returns | |
| ------- | |
| Tensor | |
| The output tensor (super-resolved images). | |
| Shape: (batch_size, 1, channels, height, width). | |
| """ | |
| if self.use_reference_frame: | |
| # Concatenated shape: (batch_size, revisits, 2*channels, height, width) | |
| x = self.compute_and_concat_reference_frame_to_input(x) | |
| batch_size, revisits, channels, height, width = x.shape | |
| hidden_channels = self.hidden_channels | |
| x = x.view(batch_size * revisits, channels, height, width) | |
| # Encoded shape: (batch_size * revisits, hidden_channels, height, width) | |
| x = self.encoder(x) | |
| # Concatenated shape: | |
| # (batch_size * revisits, hidden_channels+mask_channels, height, width) | |
| x, mask_channels = self.encode_and_concatenate_masks_to_input( | |
| x, mask, batch_size, revisits, height, width | |
| ) | |
| x = x.view( | |
| batch_size, revisits * (hidden_channels + mask_channels), height, width | |
| ) | |
| # Fused shape: (batch_size, hidden_channels, height, width) | |
| x = self.fusion(x) | |
| x = self.sr(x) | |
| # Ensure output size of (batch_size, channels, height, width) | |
| x = self.resize(x) | |
| # Pad with empty revisit dimension: (batch_size, 1, channels, height, width) | |
| x = x[:, None] | |
| return x | |
| def compute_and_concat_reference_frame_to_input(self, x): | |
| # Current shape: (batch_size, revisits, channels, height, width) | |
| reference_frame = self.reference_frame(x).expand_as(x) | |
| # Concatenated shape: (batch_size, revisits, 2*channels, height, width) | |
| x = torch.cat([x, reference_frame], dim=-3) | |
| return x | |
| def encode_and_concatenate_masks_to_input( | |
| self, x, mask, batch_size, revisits, height, width | |
| ): | |
| if mask is not None: | |
| mask, mask_channels = mask, self.mask_channels | |
| mask = mask.view(batch_size * revisits, mask_channels, height, width) | |
| # Encoded shape: (batch_size * revisits, mask_channels, height, width) | |
| mask = self.mask_encoder(mask) | |
| mask_channels = mask.shape[-3] | |
| # Concatenated shape: | |
| # (batch_size * revisits, hidden_channels+mask_channels, height, width) | |
| x = torch.cat([x, mask], dim=-3) | |
| else: | |
| mask_channels = 0 | |
| return x, mask_channels | |
| class HighResNet(SRCNN): | |
| """ High-resolution CNN. | |
| Inherits as many elements from SRCNN as possible for as fair a comparison: | |
| - DoubleConv2d: the in_channels are doubled by use_reference_frame in SRCNN init | |
| - Encoder: nn.Sequential(DoubleConv2d, ResidualBlock) | |
| """ | |
| def __init__(self, skip_paddings=True, **kws) -> None: | |
| super().__init__(**kws) | |
| self.skip_paddings = skip_paddings | |
| self.fusion = RecursiveFusion( | |
| in_channels=self.hidden_channels, | |
| kernel_size=self.kernel_size, | |
| # skip_paddings=skip_paddings, | |
| revisits=self.revisits, | |
| ) | |
| def forward(self, x, y=None, mask=None): | |
| """ Forward pass of the High-resolution CNN. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor (low-res revisits). | |
| Shape: (batch_size, revisits, channels, height, width). | |
| y : Tensor, optional | |
| The target tensor (high-res revisits). | |
| Shape: (batch_size, 1, channels, height, width). | |
| mask : Tensor, optional | |
| The mask tensor. | |
| Shape: (batch_size, revisits, mask_channels, height, width). | |
| Returns | |
| ------- | |
| Tensor | |
| The output tensor (super-resolved images). | |
| Shape: (batch_size, 1, channels, height, width). | |
| """ | |
| hidden_channels = self.hidden_channels | |
| if self.use_reference_frame: | |
| x = self.compute_and_concat_reference_frame_to_input(x) | |
| # Note: we could put all these layers in a Sequential, but we | |
| # would lose the logging abilities for inspection and lose | |
| # on readability. | |
| batch_size, revisits, channels, height, width = x.shape | |
| x = x.view(batch_size * revisits, channels, height, width) | |
| # Encoded shape: (batch_size * revisits, hidden_channels, height, width) | |
| x = self.encoder(x) | |
| x, mask_channels = self.encode_and_concatenate_masks_to_input( | |
| x, mask, batch_size, revisits, height, width | |
| ) | |
| x = x.view(batch_size, revisits, hidden_channels + mask_channels, height, width) | |
| # Fused shape: (batch_size, hidden_channels, height, width) | |
| x = self.fusion(x) | |
| # Super-resolved shape: | |
| # (batch_size, out_channels, height * zoom_factor, width * zoom_factor) | |
| x = self.sr(x) | |
| # Ensure output size of (batch_size, channels, height, width) | |
| x = self.resize(x) | |
| # Pad with empty revisit dimension: (batch_size, 1, channels, height, width) | |
| x = x[:, None] | |
| return x | |
| class BicubicUpscaledBaseline(nn.Module): | |
| """ Bicubic upscaled single-image baseline. """ | |
| def __init__( | |
| self, input_size, output_size, chip_size, interpolation="bicubic", device=None, **kws | |
| ): | |
| """ Initialize the BicubicUpscaledBaseline. | |
| Parameters | |
| ---------- | |
| input_size : tuple of int | |
| The input size. | |
| output_size : tuple of int | |
| The output size. | |
| chip_size : tuple of int | |
| The chip size. | |
| interpolation : str, optional | |
| The interpolation method, by default 'bicubic'. | |
| Available methods: 'nearest', 'bilinear', 'bicubic'. | |
| """ | |
| super().__init__() | |
| assert interpolation in ["bilinear", "bicubic", "nearest"] | |
| self.resize = Resize(output_size, interpolation=interpolation) | |
| self.output_size = output_size | |
| self.input_size = input_size | |
| self.chip_size = chip_size | |
| self.lr_bands = np.array(S2_ALL_12BANDS["true_color"]) - 1 | |
| self.mean = JIF_S2_MEAN[self.lr_bands] | |
| self.std = JIF_S2_STD[self.lr_bands] | |
| self.device = device | |
| def forward(self, x: Tensor) -> Tensor: | |
| """ Forward pass of the BicubicUpscaledBaseline. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| The input tensor (a batch of low-res revisits). | |
| Shape: (batch_size, revisits, channels, height, width). | |
| Returns | |
| ------- | |
| Tensor | |
| The output tensor (a single upscaled low-res revisit). | |
| """ | |
| # If all bands are used, get only the RGB bands for WandB image logging | |
| if x.shape[2] > 3: | |
| x = x[:, :, S2_ALL_12BANDS["true_color_zero_index"]] | |
| # Select the first revisit | |
| x = x[:, 0, :] | |
| # Pad with empty revisit dimension: (batch_size, 1, channels, height, width) | |
| x = x[:, None, :] | |
| # Normalisation on the channel axis: | |
| # Add the mean and multiply by the standard deviation | |
| device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') if self.device is None else self.device | |
| x += torch.as_tensor(self.mean[None, None, ..., None, None]).to(device) | |
| x *= torch.as_tensor(self.std[None, None, ..., None, None]).to(device) | |
| # Convert to float, and scale to [0, 1]: | |
| x = x.float() | |
| x /= torch.max(x) | |
| torch.clamp_(x, 0, 1) | |
| # Upscale to the output size: | |
| x = self.resize(x) # upscale (..., T, C, H_o, W_o) | |
| return x | |