text: ###############################################################################
  # Original source code for the FBA Matting model used in the refinement stage 
  # can be found at https://github.com/MarcoForte/FBA_Matting
  #
  # Pre-trained model weights can also be found at the url above. Note that these
  # are covered by the Deep Image Matting Dataset License Agreement for which
  # the reader should refer to https://sites.google.com/view/deepimagematting
  #
  # This code leverages the FBA matting model trained with GroupNorm and Weight
  # Standardisation specifically (filenames are preserved as per the source)
  ###############################################################################
  
  # local application libraries
  import matting_network.layers_WS as L
  
  # external libraries
  import torch.nn as nn
  
  
  def conv3x3(in_planes, out_planes, stride=1):
      """3x3 convolution with padding"""
      return L.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
                      padding=1, bias=False)
  
  
  def conv1x1(in_planes, out_planes, stride=1):
      """1x1 convolution"""
      return L.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
  
  
  class BasicBlock(nn.Module):
      expansion = 1
  
      def __init__(self, inplanes, planes, stride=1, downsample=None):
          super(BasicBlock, self).__init__()
          self.conv1 = conv3x3(inplanes, planes, stride)
          self.bn1 = L.BatchNorm2d(planes) # actually group norm
          self.relu = nn.ReLU(inplace=True)
          self.conv2 = conv3x3(planes, planes)
          self.bn2 = L.BatchNorm2d(planes)
          self.downsample = downsample
          self.stride = stride
  
      def forward(self, x):
          identity = x
  
          out = self.conv1(x)
          out = self.bn1(out)
          out = self.relu(out)
  
          out = self.conv2(out)
          out = self.bn2(out)
  
          if self.downsample is not None:
              identity = self.downsample(x)
  
          out += identity
          out = self.relu(out)
  
          return out
  
  
  class Bottleneck(nn.Module):
      expansion = 4
  
      def __init__(self, inplanes, planes, stride=1, downsample=None):
          super(Bottleneck, self).__init__()
          self.conv1 = conv1x1(inplanes, planes)
          self.bn1 = L.BatchNorm2d(planes)
          self.conv2 = conv3x3(planes, planes, stride)
          self.bn2 = L.BatchNorm2d(planes)
          self.conv3 = conv1x1(planes, planes * self.expansion)
          self.bn3 = L.BatchNorm2d(planes * self.expansion)
          self.relu = nn.ReLU(inplace=True)
          self.downsample = downsample
          self.stride = stride
  
      def forward(self, x):
          identity = x
  
          out = self.conv1(x)
          out = self.bn1(out)
          out = self.relu(out)
  
          out = self.conv2(out)
          out = self.bn2(out)
          out = self.relu(out)
  
          out = self.conv3(out)
          out = self.bn3(out)
  
          if self.downsample is not None:
              identity = self.downsample(x)
  
          out += identity
          out = self.relu(out)
  
          return out
  
  
  class ResNet(nn.Module):
  
      def __init__(self, block, layers, num_classes=1000):
          super(ResNet, self).__init__()
          self.inplanes = 64
          self.conv1 = L.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
                                bias=False)
          self.bn1 = L.BatchNorm2d(64)
          self.relu = nn.ReLU(inplace=True)
          self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1, return_indices=True)
          self.layer1 = self._make_layer(block, 64, layers[0])
          self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
          self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
          self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
          self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
          self.fc = nn.Linear(512 * block.expansion, num_classes)
  
      def _make_layer(self, block, planes, blocks, stride=1):
          downsample = None
          if stride != 1 or self.inplanes != planes * block.expansion:
              downsample = nn.Sequential(
                  conv1x1(self.inplanes, planes * block.expansion, stride),
                  L.BatchNorm2d(planes * block.expansion),
              )
  
          layers = []
          layers.append(block(self.inplanes, planes, stride, downsample))
          self.inplanes = planes * block.expansion
          for _ in range(1, blocks):
              layers.append(block(self.inplanes, planes))
  
          return nn.Sequential(*layers)
  
      def forward(self, x):
          x = self.conv1(x)
          x = self.bn1(x)
          x = self.relu(x)
          x = self.maxpool(x)
  
          x = self.layer1(x)
          x = self.layer2(x)
          x = self.layer3(x)
          x = self.layer4(x)
  
          x = self.avgpool(x)
          x = x.view(x.size(0), -1)
          x = self.fc(x)
  
          return x
  
  
  class ResNetDilated(nn.Module):
      def __init__(self, orig_resnet, dilate_scale=8):
          super(ResNetDilated, self).__init__()
          from functools import partial
  
          # increase dilation
          orig_resnet.layer3.apply(partial(self._nostride_dilate, dilate=2))
          orig_resnet.layer4.apply(partial(self._nostride_dilate, dilate=4))
  
          # take pretrained resnet, except AvgPool and FC 
          self.conv1 = orig_resnet.conv1
          self.bn1 = orig_resnet.bn1
          self.relu = orig_resnet.relu
          self.maxpool = orig_resnet.maxpool
          self.layer1 = orig_resnet.layer1
          self.layer2 = orig_resnet.layer2
          self.layer3 = orig_resnet.layer3
          self.layer4 = orig_resnet.layer4
  
      def _nostride_dilate(self, m, dilate):
          classname = m.__class__.__name__
          if classname.find('Conv') != -1:
              # the convolution with stride
              if m.stride == (2, 2):
                  m.stride = (1, 1)
                  if m.kernel_size == (3, 3):
                      m.dilation = (dilate // 2, dilate // 2)
                      m.padding = (dilate // 2, dilate // 2)
              # other convoluions
              else:
                  if m.kernel_size == (3, 3):
                      m.dilation = (dilate, dilate)
                      m.padding = (dilate, dilate)
  
      def forward(self, x, return_feature_maps=False):
          conv_out = [x]
          x = self.relu(self.bn1(self.conv1(x)))
          conv_out.append(x)
          x, indices = self.maxpool(x)
          x = self.layer1(x)
          conv_out.append(x)
          x = self.layer2(x)
          conv_out.append(x)
          x = self.layer3(x)
          conv_out.append(x)
          x = self.layer4(x)
          conv_out.append(x)
  
          if return_feature_maps:
              return conv_out, indices
          return [x]