/**
   * Runs a canny edge detector on the input image given the provided thresholds. If configured to
   * save a list of trace points then the output image is optional.
   *
   * <p>NOTE: Input and output can be the same instance, if the image type allows it.
   *
   * @param input Input image. Not modified.
   * @param threshLow Lower threshold. >= 0.
   * @param threshHigh Upper threshold. >= 0.
   * @param output (Might be option) Output binary image. Edge pixels are marked with 1 and
   *     everything else 0.
   */
  public void process(T input, float threshLow, float threshHigh, ImageUInt8 output) {

    if (threshLow < 0 || threshHigh < 0)
      throw new IllegalArgumentException("Threshold must be >= zero!");

    if (hysteresisMark != null) {
      if (output == null)
        throw new IllegalArgumentException(
            "An output image must be specified when configured to mark edge points");
    }

    // setup internal data structures
    blurred.reshape(input.width, input.height);
    derivX.reshape(input.width, input.height);
    derivY.reshape(input.width, input.height);
    intensity.reshape(input.width, input.height);
    suppressed.reshape(input.width, input.height);
    angle.reshape(input.width, input.height);
    direction.reshape(input.width, input.height);
    work.reshape(input.width, input.height);

    // run canny edge detector
    blur.process(input, blurred);
    gradient.process(blurred, derivX, derivY);
    GGradientToEdgeFeatures.intensityAbs(derivX, derivY, intensity);
    GGradientToEdgeFeatures.direction(derivX, derivY, angle);
    GradientToEdgeFeatures.discretizeDirection4(angle, direction);
    GradientToEdgeFeatures.nonMaxSuppression4(intensity, direction, suppressed);

    performThresholding(threshLow, threshHigh, output);
  }
  /** Adds Gaussian/normal i.i.d noise to each pixel in the image. */
  public static void addGaussian(ImageSInt8 img, Random rand, double sigma, int min, int max) {
    final int h = img.getHeight();
    final int w = img.getWidth();

    byte[] data = img.data;

    for (int y = 0; y < h; y++) {
      int index = img.getStartIndex() + y * img.getStride();
      for (int x = 0; x < w; x++) {
        int value = (data[index]) + (int) (rand.nextGaussian() * sigma);
        if (value < min) value = min;
        if (value > max) value = max;

        data[index++] = (byte) value;
      }
    }
  }
  /** Adds uniform i.i.d noise to each pixel in the image. Noise range is min <= X < max. */
  public static void addUniform(ImageSInt8 img, Random rand, int min, int max) {
    final int h = img.getHeight();
    final int w = img.getWidth();

    int range = max - min;

    byte[] data = img.data;

    for (int y = 0; y < h; y++) {
      int index = img.getStartIndex() + y * img.getStride();
      for (int x = 0; x < w; x++) {
        int value = (data[index]) + rand.nextInt(range) + min;
        if (value < -128) value = -128;
        if (value > 127) value = 127;

        data[index++] = (byte) value;
      }
    }
  }