/**
   * Computes the hessian from the original input image. Only Sobel and Three supported.
   *
   * @param type Type of gradient to compute
   * @param input Input image
   * @param derivXX Output. Derivative XX
   * @param derivYY Output. Derivative YY
   * @param derivXY Output. Derivative XY
   * @param borderType How it should handle borders. null == skip border
   * @param <I> Input image type
   * @param <D> Output image type
   */
  public static <I extends ImageSingleBand, D extends ImageSingleBand> void hessian(
      DerivativeType type, I input, D derivXX, D derivYY, D derivXY, BorderType borderType) {
    ImageBorder<I> border =
        BorderType.SKIP == borderType ? null : FactoryImageBorder.general(input, borderType);

    switch (type) {
      case SOBEL:
        if (input instanceof ImageFloat32) {
          HessianSobel.process(
              (ImageFloat32) input,
              (ImageFloat32) derivXX,
              (ImageFloat32) derivYY,
              (ImageFloat32) derivXY,
              (ImageBorder_F32) border);
        } else if (input instanceof ImageUInt8) {
          HessianSobel.process(
              (ImageUInt8) input,
              (ImageSInt16) derivXX,
              (ImageSInt16) derivYY,
              (ImageSInt16) derivXY,
              (ImageBorder_I32) border);
        } else {
          throw new IllegalArgumentException(
              "Unknown input image type: " + input.getClass().getSimpleName());
        }
        break;

      case THREE:
        if (input instanceof ImageFloat32) {
          HessianThree.process(
              (ImageFloat32) input,
              (ImageFloat32) derivXX,
              (ImageFloat32) derivYY,
              (ImageFloat32) derivXY,
              (ImageBorder_F32) border);
        } else if (input instanceof ImageUInt8) {
          HessianThree.process(
              (ImageUInt8) input,
              (ImageSInt16) derivXX,
              (ImageSInt16) derivYY,
              (ImageSInt16) derivXY,
              (ImageBorder_I32) border);
        } else {
          throw new IllegalArgumentException(
              "Unknown input image type: " + input.getClass().getSimpleName());
        }
        break;

      default:
        throw new IllegalArgumentException("Unsupported derivative type " + type);
    }
  }
  /**
   * Computes the gradient using the specified image type.
   *
   * @param type Type of gradient to compute
   * @param input Input image
   * @param derivX Output. Derivative X
   * @param derivY Output. Derivative Y
   * @param borderType How it should handle borders. null == skip border
   * @param <I> Input image type
   * @param <D> Output image type
   */
  public static <I extends ImageSingleBand, D extends ImageSingleBand> void gradient(
      DerivativeType type, I input, D derivX, D derivY, BorderType borderType) {

    ImageBorder<I> border =
        BorderType.SKIP == borderType ? null : FactoryImageBorder.general(input, borderType);

    switch (type) {
      case PREWITT:
        if (input instanceof ImageFloat32) {
          GradientPrewitt.process(
              (ImageFloat32) input,
              (ImageFloat32) derivX,
              (ImageFloat32) derivY,
              (ImageBorder_F32) border);
        } else if (input instanceof ImageUInt8) {
          GradientPrewitt.process(
              (ImageUInt8) input,
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageBorder_I32) border);
        } else if (input instanceof ImageSInt16) {
          GradientPrewitt.process(
              (ImageSInt16) input,
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageBorder_I32) border);
        } else {
          throw new IllegalArgumentException(
              "Unknown input image type: " + input.getClass().getSimpleName());
        }
        break;
      case SOBEL:
        if (input instanceof ImageFloat32) {
          GradientSobel.process(
              (ImageFloat32) input,
              (ImageFloat32) derivX,
              (ImageFloat32) derivY,
              (ImageBorder_F32) border);
        } else if (input instanceof ImageUInt8) {
          GradientSobel.process(
              (ImageUInt8) input,
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageBorder_I32) border);
        } else if (input instanceof ImageSInt16) {
          GradientSobel.process(
              (ImageSInt16) input,
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageBorder_I32) border);
        } else {
          throw new IllegalArgumentException(
              "Unknown input image type: " + input.getClass().getSimpleName());
        }
        break;
      case THREE:
        if (input instanceof ImageFloat32) {
          GradientThree.process(
              (ImageFloat32) input,
              (ImageFloat32) derivX,
              (ImageFloat32) derivY,
              (ImageBorder_F32) border);
        } else if (input instanceof ImageUInt8) {
          GradientThree.process(
              (ImageUInt8) input,
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageBorder_I32) border);
        } else if (input instanceof ImageSInt16) {
          GradientThree.process(
              (ImageSInt16) input,
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageBorder_I32) border);
        } else {
          throw new IllegalArgumentException(
              "Unknown input image type: " + input.getClass().getSimpleName());
        }
        break;
      case TWO_0:
        if (input instanceof ImageFloat32) {
          GradientTwo0.process(
              (ImageFloat32) input,
              (ImageFloat32) derivX,
              (ImageFloat32) derivY,
              (ImageBorder_F32) border);
        } else if (input instanceof ImageUInt8) {
          GradientTwo0.process(
              (ImageUInt8) input,
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageBorder_I32) border);
        } else if (input instanceof ImageSInt16) {
          GradientTwo0.process(
              (ImageSInt16) input,
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageBorder_I32) border);
        } else {
          throw new IllegalArgumentException(
              "Unknown input image type: " + input.getClass().getSimpleName());
        }
        break;
      case TWO_1:
        if (input instanceof ImageFloat32) {
          GradientTwo1.process(
              (ImageFloat32) input,
              (ImageFloat32) derivX,
              (ImageFloat32) derivY,
              (ImageBorder_F32) border);
        } else if (input instanceof ImageUInt8) {
          GradientTwo1.process(
              (ImageUInt8) input,
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageBorder_I32) border);
        } else if (input instanceof ImageSInt16) {
          GradientTwo1.process(
              (ImageSInt16) input,
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageBorder_I32) border);
        } else {
          throw new IllegalArgumentException(
              "Unknown input image type: " + input.getClass().getSimpleName());
        }
        break;

      default:
        throw new IllegalArgumentException("Unknown type: " + type);
    }
  }
  /**
   * Computes the hessian from the gradient. Only Prewitt, Sobel and Three supported.
   *
   * @param type Type of gradient to compute
   * @param derivX Input derivative X
   * @param derivY Input derivative Y
   * @param derivXX Output. Derivative XX
   * @param derivYY Output. Derivative YY
   * @param derivXY Output. Derivative XY
   * @param borderType How it should handle borders. null == skip border
   */
  public static <D extends ImageSingleBand> void hessian(
      DerivativeType type,
      D derivX,
      D derivY,
      D derivXX,
      D derivYY,
      D derivXY,
      BorderType borderType) {
    ImageBorder<D> border =
        BorderType.SKIP == borderType ? null : FactoryImageBorder.general(derivX, borderType);

    switch (type) {
      case PREWITT:
        if (derivX instanceof ImageFloat32) {
          HessianFromGradient.hessianPrewitt(
              (ImageFloat32) derivX,
              (ImageFloat32) derivY,
              (ImageFloat32) derivXX,
              (ImageFloat32) derivYY,
              (ImageFloat32) derivXY,
              (ImageBorder_F32) border);
        } else if (derivX instanceof ImageSInt16) {
          HessianFromGradient.hessianPrewitt(
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageSInt16) derivXX,
              (ImageSInt16) derivYY,
              (ImageSInt16) derivXY,
              (ImageBorder_I32) border);
        } else {
          throw new IllegalArgumentException(
              "Unknown input image type: " + derivX.getClass().getSimpleName());
        }
        break;

      case SOBEL:
        if (derivX instanceof ImageFloat32) {
          HessianFromGradient.hessianSobel(
              (ImageFloat32) derivX,
              (ImageFloat32) derivY,
              (ImageFloat32) derivXX,
              (ImageFloat32) derivYY,
              (ImageFloat32) derivXY,
              (ImageBorder_F32) border);
        } else if (derivX instanceof ImageSInt16) {
          HessianFromGradient.hessianSobel(
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageSInt16) derivXX,
              (ImageSInt16) derivYY,
              (ImageSInt16) derivXY,
              (ImageBorder_I32) border);
        } else {
          throw new IllegalArgumentException(
              "Unknown input image type: " + derivX.getClass().getSimpleName());
        }
        break;

      case THREE:
        if (derivX instanceof ImageFloat32) {
          HessianFromGradient.hessianThree(
              (ImageFloat32) derivX,
              (ImageFloat32) derivY,
              (ImageFloat32) derivXX,
              (ImageFloat32) derivYY,
              (ImageFloat32) derivXY,
              (ImageBorder_F32) border);
        } else if (derivX instanceof ImageSInt16) {
          HessianFromGradient.hessianThree(
              (ImageSInt16) derivX,
              (ImageSInt16) derivY,
              (ImageSInt16) derivXX,
              (ImageSInt16) derivYY,
              (ImageSInt16) derivXY,
              (ImageBorder_I32) border);
        } else {
          throw new IllegalArgumentException(
              "Unknown input image type: " + derivX.getClass().getSimpleName());
        }
        break;

      default:
        throw new IllegalArgumentException("Unsupported derivative type " + type);
    }
  }