"""PyTorch implementation of RainNet v1.0.""" import torch from torch import nn class RainNet(nn.Module): """The 20-convolution encoder-decoder from Ayzel et al. (2020).""" def __init__(self, in_channels=4, out_channels=1, dropout=0.5, mode="regression"): super().__init__() if mode != "regression": raise ValueError("This package implements RainNet regression mode only") self.in_channels = in_channels self.pool = nn.MaxPool2d(2) self.upsample = nn.Upsample(scale_factor=2, mode="nearest") self.relu = nn.ReLU(inplace=True) self.conv1f = nn.Conv2d(in_channels, 64, 3, padding=1) self.conv1s = nn.Conv2d(64, 64, 3, padding=1) self.conv2f = nn.Conv2d(64, 128, 3, padding=1) self.conv2s = nn.Conv2d(128, 128, 3, padding=1) self.conv3f = nn.Conv2d(128, 256, 3, padding=1) self.conv3s = nn.Conv2d(256, 256, 3, padding=1) self.conv4f = nn.Conv2d(256, 512, 3, padding=1) self.conv4s = nn.Conv2d(512, 512, 3, padding=1) self.drop4 = nn.Dropout(dropout) self.conv5f = nn.Conv2d(512, 1024, 3, padding=1) self.conv5s = nn.Conv2d(1024, 1024, 3, padding=1) self.drop5 = nn.Dropout(dropout) self.conv6f = nn.Conv2d(1536, 512, 3, padding=1) self.conv6s = nn.Conv2d(512, 512, 3, padding=1) self.conv7f = nn.Conv2d(768, 256, 3, padding=1) self.conv7s = nn.Conv2d(256, 256, 3, padding=1) self.conv8f = nn.Conv2d(384, 128, 3, padding=1) self.conv8s = nn.Conv2d(128, 128, 3, padding=1) self.conv9f = nn.Conv2d(192, 64, 3, padding=1) self.conv9s = nn.Conv2d(64, 64, 3, padding=1) self.conv9out = nn.Conv2d(64, 2, 3, padding=1) self.output = nn.Conv2d(2, out_channels, 1) self.apply(self._initialize) @staticmethod def _initialize(module): if isinstance(module, nn.Conv2d): nn.init.kaiming_normal_(module.weight, mode="fan_in", nonlinearity="relu") if module.bias is not None: nn.init.zeros_(module.bias) def forward(self, x): if x.ndim != 4: raise ValueError(f"RainNet expects BCHW input, got shape {tuple(x.shape)}") if x.shape[1] != self.in_channels: raise ValueError(f"RainNet expects {self.in_channels} channels, got {x.shape[1]}") if x.shape[-2] % 16 or x.shape[-1] % 16: raise ValueError( f"Spatial dimensions must be divisible by 16, got {tuple(x.shape[-2:])}" ) c1 = self.relu(self.conv1s(self.relu(self.conv1f(x)))) c2 = self.relu(self.conv2s(self.relu(self.conv2f(self.pool(c1))))) c3 = self.relu(self.conv3s(self.relu(self.conv3f(self.pool(c2))))) c4 = self.relu(self.conv4s(self.relu(self.conv4f(self.pool(c3))))) c5 = self.relu(self.conv5s(self.relu(self.conv5f(self.pool(self.drop4(c4)))))) c5 = self.drop5(c5) c6 = torch.cat((self.upsample(c5), c4), dim=1) c6 = self.relu(self.conv6s(self.relu(self.conv6f(c6)))) c7 = torch.cat((self.upsample(c6), c3), dim=1) c7 = self.relu(self.conv7s(self.relu(self.conv7f(c7)))) c8 = torch.cat((self.upsample(c7), c2), dim=1) c8 = self.relu(self.conv8s(self.relu(self.conv8f(c8)))) c9 = torch.cat((self.upsample(c8), c1), dim=1) c9 = self.relu(self.conv9s(self.relu(self.conv9f(c9)))) return self.output(self.relu(self.conv9out(c9))) def build_rainnet(in_channels=4, out_channels=1, dropout=0.5, mode="regression"): return RainNet(in_channels, out_channels, dropout, mode)