text: # Enhanced Deep Residual Networks for Single Image Super-Resolution
  # https://arxiv.org/abs/1707.02921
  
  import math
  import numbers
  import torch
  import torch.nn as nn
  import numpy as np
  import kornia
  from kornia import motion_blur
  from torch.nn import functional as F
  
  class GaussianSmoothing(nn.Module):
      def __init__(self, channels, kernel_size, sigma, dim=2):
          super(GaussianSmoothing, self).__init__()
          if isinstance(kernel_size, numbers.Number):
              kernel_size = [kernel_size] * dim
          if isinstance(sigma, numbers.Number):
              sigma = [sigma] * dim
  
          # The gaussian kernel is the product of the
          # gaussian function of each dimension.
          kernel = 1
          meshgrids = torch.meshgrid(
              [
                  torch.arange(size, dtype=torch.float32)
                  for size in kernel_size
              ]
          )
          for size, std, mgrid in zip(kernel_size, sigma, meshgrids):
              mean = (size - 1) / 2
              kernel *= 1 / (std * math.sqrt(2 * math.pi)) * \
                        torch.exp(-((mgrid - mean) / std) ** 2 / 2)
  
          # Make sure sum of values in gaussian kernel equals 1.
          kernel = kernel / torch.sum(kernel)
          print(kernel.shape)
  
          # Reshape to depthwise convolutional weight
          kernel = kernel.view(1, 1, *kernel.size())
          kernel = kernel.repeat(channels, *[1] * (kernel.dim() - 1))
          print(kernel.shape)
  
          self.register_buffer('weight', kernel)
          self.groups = channels
  
          if dim == 1:
              self.conv = F.conv1d
          elif dim == 2:
              self.conv = F.conv2d
          elif dim == 3:
              self.conv = F.conv3d
          else:
              raise RuntimeError(
                  'Only 1, 2 and 3 dimensions are supported. Received {}.'.format(dim)
              )
  
      def forward(self, input):
          return self.conv(input, weight=self.weight, groups=self.groups)
  
  
  def default_conv(in_channels, out_channels, kernel_size, bias=True):
      return nn.Conv2d(
          in_channels, out_channels, kernel_size,
          padding=(kernel_size//2), bias=bias)
  
  class MeanShift(nn.Conv2d):
      def __init__(
          self, rgb_range=255,
          rgb_mean=(0.4488, 0.4371, 0.4040), rgb_std=(1.0, 1.0, 1.0), sign=-1):
  
          super(MeanShift, self).__init__(3, 3, kernel_size=1)
          std = torch.Tensor(rgb_std)
          self.weight.data = torch.eye(3).view(3, 3, 1, 1) / std.view(3, 1, 1, 1)
          self.bias.data = sign * rgb_range * torch.Tensor(rgb_mean) / std
          for p in self.parameters():
              p.requires_grad = False
  
  class BasicBlock(nn.Sequential):
      def __init__(
          self, conv, in_channels, out_channels, kernel_size, stride=1, bias=False,
          bn=True, act=nn.ReLU(True)):
  
          m = [conv(in_channels, out_channels, kernel_size, bias=bias)]
          if bn:
              m.append(nn.BatchNorm2d(out_channels))
          if act is not None:
              m.append(act)
  
          super(BasicBlock, self).__init__(*m)
  
  class ResBlock(nn.Module):
      def __init__(
          self, conv, n_feats, kernel_size,
          bias=True, bn=False, act=nn.ReLU(True), res_scale=1):
  
          super(ResBlock, self).__init__()
          m = []
          for i in range(2):
              m.append(conv(n_feats, n_feats, kernel_size, bias=bias))
              if bn:
                  m.append(nn.BatchNorm2d(n_feats))
              if i == 0:
                  m.append(act)
  
          self.body = nn.Sequential(*m)
          self.res_scale = res_scale
  
      def forward(self, x):
          res = self.body(x).mul(self.res_scale)
          res += x
  
          return res
  
  class Upsampler(nn.Sequential):
      def __init__(self, conv, scale, n_feats, bn=False, act=False, bias=True):
  
          m = []
          if (scale & (scale - 1)) == 0:    # Is scale = 2^n?
              for _ in range(int(math.log(scale, 2))):
                  m.append(conv(n_feats, 4 * n_feats, 3, bias))
                  m.append(nn.PixelShuffle(2))
                  if bn:
                      m.append(nn.BatchNorm2d(n_feats))
                  if act == 'relu':
                      m.append(nn.ReLU(True))
                  elif act == 'prelu':
                      m.append(nn.PReLU(n_feats))
  
          elif scale == 3:
              m.append(conv(n_feats, 9 * n_feats, 3, bias))
              m.append(nn.PixelShuffle(3))
              if bn:
                  m.append(nn.BatchNorm2d(n_feats))
              if act == 'relu':
                  m.append(nn.ReLU(True))
              elif act == 'prelu':
                  m.append(nn.PReLU(n_feats))
          else:
              raise NotImplementedError
  
          super(Upsampler, self).__init__(*m)
  
  class EDSR(nn.Module):
      def __init__(self, in_channels, out_channels, num_features, num_blocks, res_scale, upscale_factor, conv=default_conv):
          super(EDSR, self).__init__()
  
          n_resblocks = num_blocks
          n_feats = num_features
          kernel_size = 3 
          scale = upscale_factor
          act = nn.ReLU(True)
          self.sub_mean = MeanShift()
          self.add_mean = MeanShift(sign=1)
          # self.kernel = GaussianSmoothing(3, 7, 1.6)   #channel, kernel_size and sigma value
  
          # define head module
          m_head = [conv(in_channels, n_feats, kernel_size)]
  
          # define body module
          m_body = [
              ResBlock(
                  conv, n_feats, kernel_size, act=act, res_scale=res_scale
              ) for _ in range(n_resblocks)
          ]
          m_body.append(conv(n_feats, n_feats, kernel_size))
  
          # define tail module
          m_tail = [
              Upsampler(conv, scale, n_feats, act=False),
              conv(n_feats, out_channels, kernel_size)
          ]
  
          self.head = nn.Sequential(*m_head)
          self.body = nn.Sequential(*m_body)
          self.tail = nn.Sequential(*m_tail)
  
      def forward(self, x):
          # print(x.shape)
          x = self.sub_mean(x)
          x = self.head(x)
  
          res = self.body(x)
          res += x
  
          x = self.tail(res)
          x = self.add_mean(x)
  
          return x 
  
  
      def load_state_dict(self, state_dict, strict=True):
          own_state = self.state_dict()
          for name, param in state_dict.items():
              if name in own_state:
                  if isinstance(param, nn.Parameter):
                      param = param.data
                  try:
                      own_state[name].copy_(param)
                  except Exception:
                      if name.find('tail') == -1:
                          raise RuntimeError('While copying the parameter named {}, '
                                             'whose dimensions in the model are {} and '
                                             'whose dimensions in the checkpoint are {}.'
                                             .format(name, own_state[name].size(), param.size()))
              elif strict:
                  if name.find('tail') == -1:
                      raise KeyError('unexpected key "{}" in state_dict'
                                     .format(name))
  
  