      gmsd = K.squeeze(gmsd, axis=-1)
      return gmsd
  
  
  def ms_ssim_calc(img1, img2, max_val=1.0, power_factors=(0.0517, 0.3295, 0.3462, 0.2726)):
      """
      Computes the MS-SSIM between img1 and img2.
      This function assumes that `img1` and `img2` are image batches, i.e. the last
      three dimensions are [height, width, channels].
      Note: The true SSIM is only defined on grayscale.  This function does not
      perform any colorspace transform.  (If input is already YUV, then it will
      compute YUV SSIM average.)
      Original paper: Wang, Zhou, Eero P. Simoncelli, and Alan C. Bovik. "Multiscale
      structural similarity for image quality assessment." Signals, Systems and
      Computers, 2004.
      Arguments:
      img1: First image batch.
      img2: Second image batch. Must have the same rank as img1.
      max_val: The dynamic range of the images (i.e., the difference between the
        maximum the and minimum allowed values).
      power_factors: Iterable of weights for each of the scales. The number of
        scales used is the length of the list. Index 0 is the unscaled
        resolution's weight and each increasing scale corresponds to the image
        being downsampled by 2.  Defaults to (0.0448, 0.2856, 0.3001, 0.2363,
        0.1333), which are the values obtained in the original paper.
      Returns:
      A tensor containing an MS-SSIM value for each image in batch.  The values
      are in range [0, 1].  Returns a tensor with shape:
      broadcast(img1.shape[:-3], img2.shape[:-3]).
      """
  
      def _verify_compatible_image_shapes(img1, img2):
          """
          Checks if two image tensors are compatible for applying SSIM or PSNR.
          This function checks if two sets of images have ranks at least 3, and if the
          last three dimensions match.
          Args:
          img1: Tensor containing the first image batch.
          img2: Tensor containing the second image batch.
          Returns:
          A tuple containing: the first tensor shape, the second tensor shape, and a
          list of control_flow_ops.Assert() ops implementing the checks.
          Raises:
          ValueError: When static shape check fails.
          """
          shape1 = img1.get_shape().with_rank_at_least(3)
          shape2 = img2.get_shape().with_rank_at_least(3)
          shape1[-3:].assert_is_compatible_with(shape2[-3:])
  
          if shape1.ndims is not None and shape2.ndims is not None:
              for dim1, dim2 in zip(reversed(shape1[:-3]), reversed(shape2[:-3])):
                  if not (dim1 == 1 or dim2 == 1 or dim1.is_compatible_with(dim2)):
                      raise ValueError('Two images are not compatible: %s and %s' % (shape1, shape2))
  
          # Now assign shape tensors.
          shape1, shape2 = tf.shape_n([img1, img2])
  
          # TODO(sjhwang): Check if shape1[:-3] and shape2[:-3] are broadcastable.
          checks = []
          checks.append(tf.Assert(tf.greater_equal(tf.size(shape1), 3),
                                  [shape1, shape2], summarize=10))
          checks.append(tf.Assert(tf.reduce_all(tf.equal(shape1[-3:], shape2[-3:])),
                                  [shape1, shape2], summarize=10))
  
          return shape1, shape2, checks
  
      def _ssim_per_channel(img1, img2, max_val=1.0):
          """
          Computes SSIM index between img1 and img2 per color channel.
          This function matches the standard SSIM implementation from:
          Wang, Z., Bovik, A. C., Sheikh, H. R., & Simoncelli, E. P. (2004). Image
          quality assessment: from error visibility to structural similarity. IEEE
          transactions on image processing.
          Details:
          - 11x11 Gaussian filter of width 1.5 is used.
          - k1 = 0.01, k2 = 0.03 as in the original paper.
          Args:
          img1: First image batch.
          img2: Second image batch.
          max_val: The dynamic range of the images (i.e., the difference between the
            maximum the and minimum allowed values).
          Returns:
          A pair of tensors containing and channel-wise SSIM and contrast-structure
          values. The shape is [..., channels].
          """
  
          def _fspecial_gauss(size, sigma):
              """ Function to mimic the 'fspecial' gaussian MATLAB function. """
  
              size = tf.convert_to_tensor(size, 'int32')
              sigma = tf.convert_to_tensor(sigma)
              coords = tf.cast(tf.range(size), sigma.dtype)
              coords -= tf.cast(size - 1, sigma.dtype) / 2.0
  
              gauss = tf.square(coords)
              gauss *= -0.5 / tf.square(sigma)
              gauss = tf.reshape(gauss, shape=[1, -1]) + tf.reshape(gauss, shape=[-1, 1])
              gauss = tf.reshape(gauss, shape=[1, -1])  # For tf.nn.softmax().
              gauss = tf.nn.softmax(gauss)
              return tf.reshape(gauss, shape=[size, size, 1, 1])
  
          def _ssim_helper(img1, img2, max_val, kernel, compensation=1.):
              """
              Helper function for computing SSIM.
              SSIM estimates covariances with weighted sums.  The default parameters
              use a biased estimate of the covariance:
              Suppose `reducer` is a weighted sum, then the mean estimators are
              mu_x = sum_i w_i x_i,
              mu_y = sum_i w_i y_i,
              where w_i's are the weighted-sum weights, and covariance estimator is
              cov_{xy} = sum_i w_i (x_i - mu_x) (y_i - mu_y)
              with assumption sum_i w_i = 1. This covariance estimator is biased, since
              E[cov_{xy}] = (1 - sum_i w_i ^ 2) Cov(X, Y).
              For SSIM measure with unbiased covariance estimators, pass as `compensation`
              argument (1 - sum_i w_i ^ 2).
              Arguments:
              img1: First set of images.
              img2: Second set of images.
              reducer: Function that computes 'local' averages from set of images.
                For non-covolutional version, this is usually tf.reduce_mean(img1, [1, 2]),
                and for convolutional version, this is usually tf.nn.avg_pool or
                tf.nn.conv2d with weighted-sum kernel.
              max_val: The dynamic range (i.e., the difference between the maximum
                possible allowed value and the minimum allowed value).
              compensation: Compensation factor. See above.
              Returns:
              A pair containing the luminance measure, and the contrast-structure measure.
              """
  
              def reducer(img1, kernel):
                  shape = tf.shape(img1)
                  img1 = tf.reshape(img1, shape=tf.concat([[-1], shape[-3:]], 0))
                  img2 = tf.nn.depthwise_conv2d(img1, kernel, strides=[1, 1, 1, 1], padding='VALID')
                  return tf.reshape(img2, tf.concat([shape[:-3], tf.shape(img2)[1:]], 0))
  
              c_one = (0.01 * max_val) ** 2
              c_two = ((0.03 * max_val)) ** 2 * compensation
  
              # SSIM luminance measure is
              # (2 * mu_x * mu_y + c_one) / (mu_x ** 2 + mu_y ** 2 + c_one).
              mean0 = reducer(img1, kernel)
              mean1 = reducer(img2, kernel)
              num0 = mean0 * mean1 * 2.
              den0 = tf.square(mean0) + tf.square(mean1)
              luminance = (num0 + c_one) / (den0 + c_one)
  
              # SSIM contrast-structure measure is
              #   (2 * cov_{xy} + c_two) / (cov_{xx} + cov_{yy} + c_two).
              # Note that `reducer` is a weighted sum with weight w_k, \sum_i w_i = 1, then
              #   cov_{xy} = \sum_i w_i (x_i - \mu_x) (y_i - \mu_y)
              #          = \sum_i w_i x_i y_i - (\sum_i w_i x_i) (\sum_j w_j y_j).
              num1 = reducer(img1 * img2, kernel) * 2.0
              den1 = reducer(tf.square(img1) + tf.square(img2), kernel)
              c_s = (num1 - num0 + c_two) / (den1 - den0 + c_two)
  
              # SSIM score is the product of the luminance and contrast-structure measures.
              return luminance, c_s
  
          filter_size = tf.constant(9, dtype='int32')  # changed from 11 to 9 due
          filter_sigma = tf.constant(1.5, dtype=img1.dtype)
  
          shape1, shape2 = tf.shape_n([img1, img2])
          checks = [tf.Assert(tf.reduce_all(tf.greater_equal(shape1[-3:-1], filter_size)),
                              [shape1, filter_size], summarize=8),
                    tf.Assert(tf.reduce_all(tf.greater_equal(shape2[-3:-1], filter_size)),
                              [shape2, filter_size], summarize=8)]
  
          # Enforce the check to run before computation.
          with tf.control_dependencies(checks):
              img1 = tf.identity(img1)
  
          # TODO(sjhwang): Try to cache kernels and compensation factor.
          kernel = _fspecial_gauss(filter_size, filter_sigma)
          kernel = tf.tile(kernel, multiples=[1, 1, shape1[-1], 1])
  
          # The correct compensation factor is `1.0 - tf.reduce_sum(tf.square(kernel))`,
          # but to match MATLAB implementation of MS-SSIM, we use 1.0 instead.
          compensation = 1.
  
          # TODO(sjhwang): Try FFT.
          # TODO(sjhwang): Gaussian kernel is separable in space. Consider applying
          #   1-by-n and n-by-1 Gaussain filters instead of an n-by-n filter.
  
          luminance, c_s = _ssim_helper(img1, img2, max_val, kernel, compensation)
  
          # Average over the second and the third from the last: height, width.
          axes = tf.constant([-3, -2], dtype='int32')
          ssim_val = tf.reduce_mean(luminance * c_s, axes)
          c_s = tf.reduce_mean(c_s, axes)
          return ssim_val, c_s
  
      def do_pad(images, remainder):
          padding = tf.expand_dims(remainder, -1)
          padding = tf.pad(padding, [[1, 0], [1, 0]])
          return [tf.pad(x, padding, mode='SYMMETRIC') for x in images]
  
      # Shape checking.
      shape1 = img1.get_shape().with_rank_at_least(3)
      shape2 = img2.get_shape().with_rank_at_least(3)
      shape1[-3:].merge_with(shape2[-3:])
  
      with tf.name_scope(None, 'MS-SSIM', [img1, img2]):
          shape1, shape2, checks = _verify_compatible_image_shapes(img1, img2)
      with tf.control_dependencies(checks):
          img1 = tf.identity(img1)
  
      # Need to convert the images to float32.  Scale max_val accordingly so that
      # SSIM is computed correctly.
      max_val = tf.cast(max_val, img1.dtype)
      max_val = tf.image.convert_image_dtype(max_val, 'float32')
      img1 = tf.image.convert_image_dtype(img1, 'float32')
      img2 = tf.image.convert_image_dtype(img2, 'float32')
  
      imgs = [img1, img2]
      shapes = [shape1, shape2]
  
      # img1 and img2 are assumed to be a (multi-dimensional) batch of
      # 3-dimensional images (height, width, channels). `heads` contain the batch
      # dimensions, and `tails` contain the image dimensions.
      heads = [s[:-3] for s in shapes]
      tails = [s[-3:] for s in shapes]
  
      divisor = [1, 2, 2, 1]
      divisor_tensor = tf.constant(divisor[1:], dtype='int32')
  
      mc_s = []
      for k in range(len(power_factors)):
          with tf.name_scope(None, 'Scale%d' % k, imgs):
              if k > 0:
                  # Avg pool takes rank 4 tensors. Flatten leading dimensions.
                  zipped = zip(imgs, tails)
                  flat_imgs = [tf.reshape(x, tf.concat([[-1], t], 0)) for x, t in zipped]
                  remainder = tails[0] % divisor_tensor
                  need_padding = tf.reduce_any(tf.not_equal(remainder, 0))
                  padded = tf.cond(need_padding,
                                   lambda: do_pad(flat_imgs, remainder), lambda: flat_imgs)
  
                  downscaled = [tf.nn.avg_pool(x,
                                               ksize=divisor,
                                               strides=divisor,
                                               padding='VALID') for x in padded]
                  tails = [x[1:] for x in tf.shape_n(downscaled)]
                  zipper = zip(downscaled, heads, tails)
                  imgs = [tf.reshape(x, tf.concat([h, t], 0)) for x, h, t in zipper]
  
              # Overwrite previous ssim value since we only need the last one.
              ssim_per_channel, c_s = _ssim_per_channel(*imgs, max_val=max_val)
              mc_s.append(tf.nn.relu(c_s))
  
      # Remove the c_s score for the last scale. In the MS-SSIM calculation,
      # we use the l(p) at the highest scale. l(p) * c_s(p) is ssim(p).
      mc_s.pop()  # Remove the c_s score for the last scale.
      mcs_and_ssim = tf.stack(mc_s + [tf.nn.relu(ssim_per_channel)], axis=-1)
      # Take weighted geometric mean across the scale axis.
      ms_ssim = tf.reduce_prod(tf.pow(mcs_and_ssim, power_factors), [-1])
  
      return tf.reduce_mean(ms_ssim, [-1])  # Avg over color channels.
  
  
  def ms_ssim_loss(y_true, y_pred):
      """ Keras loss function for MS-SSIM """
      expanded = K.expand_dims(1.0 - ms_ssim_calc(y_true, y_pred), axis=-1)
      loss = K.expand_dims(expanded, axis=-1)
      # need to expand to [1,height,width] dimensions for Keras. modify to not be hard-coded
      return K.tile(loss, [1, 64, 64])
  
  
  # Gaussian Blur is here as it is only used for losses.
  # It was previously kept in lib/model/masks but the import of keras backend
  # breaks plaidml
  def gaussian_blur(radius=2.0):
      """ From https://github.com/iperov/DeepFaceLab
 
</s> remove     """
    Calculates the first and second order gradient difference between pixels of
    an image in the x and y dimensions. These gradients are then compared between
    the ground truth and the predicted image and the difference is taken. When
    used as a loss, its minimization will result in predicted images approaching
    the same level of sharpness / blurriness as the ground truth.

    TV+TV2 Regularization with Nonconvex Sparseness-Inducing Penalty
    for Image Restoration, Chengwu Lu & Hua Huang, 2014
    (http://downloads.hindawi.com/journals/mpe/2014/790547.pdf)

    Parameters:
        y_true: The predicted frames at each scale
        y_true: The ground truth frames at each scale
    Return:
        The GD loss
 </s> add     """ Gradient Loss Function.

    Calculates the first and second order gradient difference between pixels of an image in the x
    and y dimensions. These gradients are then compared between the ground truth and the predicted
    image and the difference is taken. When used as a loss, its minimization will result in
    predicted images approaching the same level of sharpness / blurriness as the ground truth.

    Parameters
    ----------
    y_true: tensor or variable
        The ground truth value
    y_pred: tensor or variable
        The predicted value

    Returns
    -------
    tensor
        The loss value

    References
    ----------
    TV+TV2 Regularization with Non-Convex Sparseness-Inducing Penalty for Image Restoration,
    Chengwu Lu & Hua Huang, 2014 - http://downloads.hindawi.com/journals/mpe/2014/790547.pdf </s> remove     """ From https://github.com/iperov/DeepFaceLab
        Used for blurring mask in training """
    def gaussian(var_x, radius, sigma):
 </s> add     """ Apply gaussian blur to an input.

    Used for blurring mask in training.

    Parameters
    ----------
    radius: float, optional
        The kernel radius for applying gaussian blur. Default: `2.0`

    Returns
    -------
    tensor
        The input tensor with gaussian blurring applied

    References
    ----------
    https://github.com/iperov/DeepFaceLab
    """
    def _gaussian(var_x, radius, sigma):
        """ Obtain the gaussian kernel. """ </s> remove     # pylint: disable=C0103
    """ Sub-pixel convolutional upscaling layer based on the paper "Real-Time
    Single Image and Video Super-Resolution Using an Efficient Sub-Pixel
    Convolutional Neural Network" (https://arxiv.org/abs/1609.05158).
    This layer requires a Convolution2D prior to it, having output filters
    computed according to the formula :
        filters = k * (scale_factor * scale_factor)
        where k = a user defined number of filters (generally larger than 32)
              scale_factor = the upscaling factor (generally 2)
    This layer performs the depth to space operation on the convolution
    filters, and returns a tensor with the size as defined below.
    # Example :
    ```python
        # A standard subpixel upscaling block
        x = Convolution2D(256, 3, 3, padding="same", activation="relu")(...)
        u = SubPixelUpscaling(scale_factor=2)(x)
        [Optional]
        x = Convolution2D(256, 3, 3, padding="same", activation="relu")(u)
    ```
        In practice, it is useful to have a second convolution layer after the
        SubPixelUpscaling layer to speed up the learning process.
        However, if you are stacking multiple SubPixelUpscaling blocks,
        it may increase the number of parameters greatly, so the Convolution
        layer after SubPixelUpscaling layer can be removed.
    # Arguments
        scale_factor: Upscaling factor.
        data_format: Can be None, "channels_first" or "channels_last".
    # Input shape
        4D tensor with shape:
        `(samples, k * (scale_factor * scale_factor) channels, rows, cols)`
            if data_format="channels_first"
        or 4D tensor with shape:
        `(samples, rows, cols, k * (scale_factor * scale_factor) channels)`
            if data_format="channels_last".
    # Output shape
        4D tensor with shape:
        `(samples, k channels, rows * scale_factor, cols * scale_factor))`
            if data_format="channels_first"
        or 4D tensor with shape:
        `(samples, rows * scale_factor, cols * scale_factor, k channels)`
            if data_format="channels_last".
 </s> add     """ Sub-pixel convolutional up-scaling layer.

    This layer requires a Convolution2D prior to it, having output filters computed according to
    the formula :math:`filters = k * (scale_factor * scale_factor)` where `k` is a user defined
    number of filters (generally larger than 32) and `scale_factor` is the up-scaling factor
    (generally 2).

    This layer performs the depth to space operation on the convolution filters, and returns a
    tensor with the size as defined below.

    Notes
    -----
    This method is deprecated as it just performs the same as :class:`PixelShuffler`
    using explicit Tensorflow ops. The method is kept in the repository to support legacy
    models that have been created with this layer.

    In practice, it is useful to have a second convolution layer after the
    :class:`SubPixelUpscaling` layer to speed up the learning process. However, if you are stacking
    multiple :class:`SubPixelUpscaling` blocks, it may increase the number of parameters greatly,
    so the Convolution layer after :class:`SubPixelUpscaling` layer can be removed.

    Example
    -------
    >>> # A standard sub-pixel up-scaling block
    >>> x = Convolution2D(256, 3, 3, padding="same", activation="relu")(...)
    >>> u = SubPixelUpscaling(scale_factor=2)(x)
    [Optional]
    >>> x = Convolution2D(256, 3, 3, padding="same", activation="relu")(u)

    Parameters
    ----------
    size: int, optional
        The up-scaling factor. Default: `2`
    data_format: ["channels_first", "channels_last", ``None``], optional
        The data format for the input. Default: ``None``
    kwargs: dict
        The standard Keras Layer keyword arguments (if any)

    References
    ----------
    based on the paper "Real-Time Single Image and Video Super-Resolution Using an Efficient
    Sub-Pixel Convolutional Neural Network" (https://arxiv.org/abs/1609.05158). </s> remove     def inner_loss(y_true, y_pred):
        # Branching because tensorflows broadcasting is wonky and
        # plaidmls concatenate is implemented ineficient.
 </s> add     def _inner_loss(y_true, y_pred):
        """ Apply the loss function to the masked area of the image.

         Parameters
        ----------
        y_true: tensor or variable
            The ground truth value
        y_pred: tensor or variable
            The predicted value

        Returns
        -------
        tensor
            The Loss value

        Notes
        -----
        Branching because TensorFlow's broadcasting is wonky and plaidML's concatenate is
        implemented inefficiently.
        """ </s> remove         # There are additional parameters for this function
        # Note: some of the 'modes' for edge behavior do not yet have a
        # gradient definition in the Theano tree and cannot be used for
        # learning
 </s> add         """ Call the DSSIM Loss Function.

        Parameters
        ----------
        y_true: tensor or variable
            The ground truth value
        y_pred: tensor or variable
            The predicted value

        Returns
        -------
        tensor
            The DSSIM Loss value

        Notes
        -----
        There are additional parameters for this function. some of the 'modes' for edge behavior
        do not yet have a gradient definition in the Theano tree and cannot be used for learning
        """
      gmsd = K.squeeze(gmsd, axis=-1)
      return gmsd
  
  







































































































































































































































































  # Gaussian Blur is here as it is only used for losses.
  # It was previously kept in lib/model/masks but the import of keras backend
  # breaks plaidml
  def gaussian_blur(radius=2.0):
      """ From https://github.com/iperov/DeepFaceLab
keep keep keep keep replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace keep keep keep keep keep
 Simple backend unit tests (#1020)

* Add simple backend tests for lib.model
* Document lib.model
* Fix GMSD Loss for AMD
* Remove obsolete code from lib.model 
lib/model/losses.py