text: """
  Module: libfmp.c4.c4s4_novelty_kernel
  Author: Meinard Müller, Julian Reck
  License: The MIT license, https://opensource.org/licenses/MIT
  
  This file is part of the FMP Notebooks (https://www.audiolabs-erlangen.de/FMP)
  """
  import numpy as np
  from numba import jit
  
  def compute_kernel_checkerboard_box(L):
      """Compute box-like checkerboard kernel [FMP, Section 4.4.1]
  
      Notebook: C4/C4S4_NoveltySegmentation.ipynb
  
      Args:
          L: Parameter specifying the kernel size 2*L+1
  
      Returns:
          kernel: Kernel matrix of size (2*L+1) x (2*L+1)
      """
      axis = np.arange(-L, L+1)
      kernel = np.outer(np.sign(axis), np.sign(axis))
      return kernel
  
  @jit(nopython=True)
  def compute_kernel_checkerboard_gaussian(L, var=1, normalize=True):
      """Compute Guassian-like checkerboard kernel [FMP, Section 4.4.1]
      See also: https://scipython.com/blog/visualizing-the-bivariate-gaussian-distribution/
  
      Notebook: C4/C4S4_NoveltySegmentation.ipynb
  
      Args:
          L: Parameter specifying the kernel size M=2*L+1
          var: Variance parameter determing the tapering (epsilon)
  
      Returns:
          kernel: Kernel matrix of size M x M
      """
      taper = np.sqrt(1/2) / (L * var)
      axis = np.arange(-L, L+1)
      gaussian1D = np.exp(-taper**2 * (axis**2))
      gaussian2D = np.outer(gaussian1D, gaussian1D)
      kernel_box = np.outer(np.sign(axis), np.sign(axis))
      kernel = kernel_box * gaussian2D
      if normalize:
          kernel = kernel / np.sum(np.abs(kernel))
      return kernel
  
  
  # @jit(nopython=True)
  def compute_novelty_ssm(S, kernel=None, L=10, var=0.5, exclude=False):
      """Compute novelty function from SSM [FMP, Section 4.4.1]
  
      Notebook: C4/C4S4_NoveltySegmentation.ipynb
  
      Args:
          S: SSM
          kernel: Checkerboard kernel (if kernel==None, it will be computed)
          L: Parameter specifying the kernel size M=2*L+1
          var: Variance parameter determing the tapering (epsilon)
          exclude: Sets the first L and last L values of novelty function to zero
  
      Returns:
          nov: Novelty function
      """
      if kernel is None:
          kernel = compute_kernel_checkerboard_gaussian(L=L, var=var)
      N = S.shape[0]
      M = 2*L + 1
      nov = np.zeros(N)
      # np.pad does not work with numba/jit
      S_padded = np.pad(S, L, mode='constant')
  
      for n in range(N):
          # Does not work with numba/jit
          nov[n] = np.sum(S_padded[n:n+M, n:n+M] * kernel)
      if exclude:
          right = np.min([L, N])
          left = np.max([0, N-L])
          nov[0:right] = 0
          nov[left:N] = 0
  
      return nov
  