/**
   * Creates a Gaussian kernel with the specified properties.
   *
   * @param DOF 1 for 1D kernel and 2 for 2D kernel.
   * @param isFloat True for F32 kernel and false for I32.
   * @param numBits Number of bits in each data element. 32 or 64
   * @param sigma The distributions stdev. If <= 0 then the sigma will be computed from the radius.
   * @param radius Number of pixels in the kernel's radius. If <= 0 then the sigma will be computed
   *     from the sigma. @return The computed Gaussian kernel.
   */
  public static <T extends KernelBase> T gaussian(
      int DOF, boolean isFloat, int numBits, double sigma, int radius) {
    if (radius <= 0) radius = FactoryKernelGaussian.radiusForSigma(sigma, 0);
    else if (sigma <= 0) sigma = FactoryKernelGaussian.sigmaForRadius(radius, 0);

    if (DOF == 2) {
      if (numBits == 32) {
        Kernel2D_F32 k = gaussian2D_F32(sigma, radius, isFloat);
        if (isFloat) return (T) k;
        return (T) KernelMath.convert(k, MIN_FRAC);
      } else if (numBits == 64) {
        Kernel2D_F64 k = gaussian2D_F64(sigma, radius, isFloat);
        if (isFloat) return (T) k;
        else throw new IllegalArgumentException("64bit int kernels supported");
      } else {
        throw new IllegalArgumentException("Bits must be 32 or 64");
      }
    } else if (DOF == 1) {
      if (numBits == 32) {
        Kernel1D_F32 k = gaussian1D_F32(sigma, radius, isFloat);
        if (isFloat) return (T) k;
        return (T) KernelMath.convert(k, MIN_FRAC);
      } else {
        throw new IllegalArgumentException("Bits must be 32 ");
      }
    } else {
      throw new IllegalArgumentException("DOF not supported");
    }
  }
  protected static Kernel2D_F64 gaussian2D_F64(double sigma, int radius, boolean normalize) {
    Kernel1D_F64 kernel1D = gaussian1D_F64(sigma, radius, false);
    Kernel2D_F64 ret = KernelMath.convolve(kernel1D, kernel1D);

    if (normalize) {
      KernelMath.normalizeSumToOne(ret);
    }

    return ret;
  }
  protected static Kernel1D_F64 gaussian1D_F64(double sigma, int radius, boolean normalize) {
    Kernel1D_F64 ret = new Kernel1D_F64(radius * 2 + 1);
    double[] gaussian = ret.data;
    int index = 0;

    for (int i = radius; i >= -radius; i--) {
      gaussian[index++] = UtilGaussian.computePDF(0, sigma, i);
    }

    if (normalize) {
      KernelMath.normalizeSumToOne(ret);
    }

    return ret;
  }
  /**
   * Creates a 1D Gaussian kernel with the specified properties.
   *
   * @param order The order of the gaussian derivative.
   * @param isFloat True for F32 kernel and false for I32.
   * @param sigma The distributions stdev. If <= 0 then the sigma will be computed from the radius.
   * @param radius Number of pixels in the kernel's radius. If <= 0 then the sigma will be computed
   *     from the sigma.
   * @return The computed Gaussian kernel.
   */
  public static <T extends Kernel1D> T derivative(
      int order, boolean isFloat, double sigma, int radius) {
    // zero order is a regular gaussian
    if (order == 0) {
      return gaussian(1, isFloat, 32, sigma, radius);
    }

    if (radius <= 0) radius = FactoryKernelGaussian.radiusForSigma(sigma, order);
    else if (sigma <= 0) {
      sigma = FactoryKernelGaussian.sigmaForRadius(radius, order);
    }

    Kernel1D_F32 k = derivative1D_F32(order, sigma, radius, true);

    if (isFloat) return (T) k;
    return (T) KernelMath.convert(k, MIN_FRAC);
  }