/**
   * ****************************************************************************************************************************************
   */
  private static List<List<PointIndex_I32>> getCandidates(
      BufferedImage image,
      int blurRadius,
      float threshLow,
      float threshHigh,
      double toleranceDist,
      double toleranceAngle,
      boolean dynamicThreshold) {

    List<List<PointIndex_I32>> candidates = new ArrayList<List<PointIndex_I32>>();

    ImageFloat32 input = ConvertBufferedImage.convertFromSingle(image, null, ImageFloat32.class);

    ImageUInt8 binary = new ImageUInt8(input.width, input.height);

    // Finds edges inside the image
    CannyEdge<ImageFloat32, ImageFloat32> canny =
        FactoryEdgeDetectors.canny(
            blurRadius, false, dynamicThreshold, ImageFloat32.class, ImageFloat32.class);

    canny.process(input, threshLow, threshHigh, binary);

    List<Contour> contours = BinaryImageOps.contour(binary, rule, null);

    for (Contour c : contours) {
      // Only the external contours are relevant.
      List<PointIndex_I32> vertices =
          ShapeFittingOps.fitPolygon(c.external, true, toleranceDist, toleranceAngle, 100);
      candidates.add(vertices);
    }
    return candidates;
  }
  /**
   * Detects contours inside the binary image generated by canny. Only the external contour is
   * relevant. Often easier to deal with than working with Canny edges directly.
   */
  public static void fitCannyBinary(ImageFloat32 input) {

    BufferedImage displayImage =
        new BufferedImage(input.width, input.height, BufferedImage.TYPE_INT_RGB);
    ImageUInt8 binary = new ImageUInt8(input.width, input.height);

    // Finds edges inside the image
    CannyEdge<ImageFloat32, ImageFloat32> canny =
        FactoryEdgeDetectors.canny(2, false, true, ImageFloat32.class, ImageFloat32.class);

    canny.process(input, 0.1f, 0.3f, binary);

    List<Contour> contours = BinaryImageOps.contour(binary, 8, null);

    Graphics2D g2 = displayImage.createGraphics();
    g2.setStroke(new BasicStroke(2));

    // used to select colors for each line
    Random rand = new Random(234);

    for (Contour c : contours) {
      // Only the external contours are relevant.
      List<PointIndex_I32> vertexes =
          ShapeFittingOps.fitPolygon(c.external, true, toleranceDist, toleranceAngle, 100);

      g2.setColor(new Color(rand.nextInt()));
      VisualizeShapes.drawPolygon(vertexes, true, g2);
    }

    ShowImages.showWindow(displayImage, "Canny Contour");
  }
  /**
   * Fits a sequence of line-segments into a sequence of points found using the Canny edge detector.
   * In this case the points are not connected in a loop. The canny detector produces a more complex
   * tree and the fitted points can be a bit noisy compared to the others.
   */
  public static void fitCannyEdges(ImageFloat32 input) {

    BufferedImage displayImage =
        new BufferedImage(input.width, input.height, BufferedImage.TYPE_INT_RGB);

    // Finds edges inside the image
    CannyEdge<ImageFloat32, ImageFloat32> canny =
        FactoryEdgeDetectors.canny(2, true, true, ImageFloat32.class, ImageFloat32.class);

    canny.process(input, 0.1f, 0.3f, null);
    List<EdgeContour> contours = canny.getContours();

    Graphics2D g2 = displayImage.createGraphics();
    g2.setStroke(new BasicStroke(2));

    // used to select colors for each line
    Random rand = new Random(234);

    for (EdgeContour e : contours) {
      g2.setColor(new Color(rand.nextInt()));

      for (EdgeSegment s : e.segments) {
        // fit line segments to the point sequence.  Note that loop is false
        List<PointIndex_I32> vertexes =
            ShapeFittingOps.fitPolygon(s.points, false, toleranceDist, toleranceAngle, 100);

        VisualizeShapes.drawPolygon(vertexes, false, g2);
      }
    }

    ShowImages.showWindow(displayImage, "Canny Trace");
  }
  /** Fits polygons to found contours around binary blobs. */
  public static void fitBinaryImage(ImageFloat32 input) {

    ImageUInt8 binary = new ImageUInt8(input.width, input.height);
    BufferedImage polygon =
        new BufferedImage(input.width, input.height, BufferedImage.TYPE_INT_RGB);

    // the mean pixel value is often a reasonable threshold when creating a binary image
    double mean = ImageStatistics.mean(input);

    // create a binary image by thresholding
    ThresholdImageOps.threshold(input, binary, (float) mean, true);

    // reduce noise with some filtering
    ImageUInt8 filtered = BinaryImageOps.erode8(binary, null);
    filtered = BinaryImageOps.dilate8(filtered, null);

    // Find the contour around the shapes
    List<Contour> contours = BinaryImageOps.contour(filtered, 8, null);

    // Fit a polygon to each shape and draw the results
    Graphics2D g2 = polygon.createGraphics();
    g2.setStroke(new BasicStroke(2));

    for (Contour c : contours) {
      // Fit the polygon to the found external contour.  Note loop = true
      List<PointIndex_I32> vertexes =
          ShapeFittingOps.fitPolygon(c.external, true, toleranceDist, toleranceAngle, 100);

      g2.setColor(Color.RED);
      VisualizeShapes.drawPolygon(vertexes, true, g2);

      // handle internal contours now
      g2.setColor(Color.BLUE);
      for (List<Point2D_I32> internal : c.internal) {
        vertexes = ShapeFittingOps.fitPolygon(internal, true, toleranceDist, toleranceAngle, 100);
        VisualizeShapes.drawPolygon(vertexes, true, g2);
      }
    }

    ShowImages.showWindow(polygon, "Binary Blob Contours");
  }
  public static void main(String[] args) throws IOException {

    if (args.length == 0) {
      System.out.println(
          "usage: crop-objects [OPTION]... FILE [DIR]\n"
              + "crops detected objects from image FILE and writes their subimages to files. \n"
              + "Can specify the DIR in which to create the files, otherwise subimage files are "
              + "created in the same directory as FILE is in by default. \n"
              + "-t [n] [m] 		set the high and low threshold values. n and m are values between 0 and 1.");
      System.exit(1);
    }
    // interpret options and read arguments
    if (args[0].equals("-t")) {

      threshLow = Float.parseFloat(args[1]);
      threshHigh = Float.parseFloat(args[2]);

      filename = args[3];
      if (args.length == 5) {
        dir = args[4];
      }
    } else {

      filename = args[0];
      if (args.length == 2) {
        dir = args[1];
      }
    }
    // get path to directory file is in
    String name = FilenameUtils.removeExtension(filename);

    // get image
    BufferedImage image = UtilImageIO.loadImage(new File(filename).getAbsolutePath());
    if (image == null) {
      System.out.println(
          "usage: crop-objects [OPTION]... FILE [DIR]\n"
              + "crops detected objects from image FILE and writes their subimages to files. \n"
              + "Can specify the DIR in which to create the files, otherwise subimage files are "
              + "created in the same directory as FILE is in by default. \n"
              + "-t [n] [m] 		set the high and low threshold values. n and m are values between 0 and 1.");
      System.exit(1);
    }

    minsize = (int) (0.1 * Math.min(image.getHeight(), image.getWidth()));

    // find objects in image
    // generate candidate contours

    ArrayList<List<PointIndex_I32>> objects = new ArrayList<List<PointIndex_I32>>();
    List<BufferedImage> results = new ArrayList<BufferedImage>();

    List<List<PointIndex_I32>> candidates = new ArrayList<List<PointIndex_I32>>();

    ImageFloat32 input = ConvertBufferedImage.convertFromSingle(image, null, ImageFloat32.class);

    BufferedImage bw =
        ConvertBufferedImage.convertTo(
            input, new BufferedImage(image.getWidth(), image.getHeight(), image.getType()));
    File binaryfile = new File(name + "_" + "binary.png");
    ImageIO.write(bw, "png", binaryfile);

    ImageUInt8 binary = new ImageUInt8(input.width, input.height);

    // Finds edges inside the image
    CannyEdge<ImageFloat32, ImageFloat32> canny =
        FactoryEdgeDetectors.canny(
            blurRadius, true, dynamicThreshold, ImageFloat32.class, ImageFloat32.class);

    canny.process(input, threshLow, threshHigh, binary);

    List<Contour> contours = BinaryImageOps.contour(binary, rule, null);

    BufferedImage visualBinary = VisualizeBinaryData.renderBinary(binary, null);
    File cannyfile = new File(name + "_" + "canny.png");
    ImageIO.write(visualBinary, "png", cannyfile);

    BufferedImage cannyContour =
        VisualizeBinaryData.renderExternal(contours, null, binary.width, binary.height, null);
    File cannyContourfile = new File(name + "_" + "contour.png");
    ImageIO.write(cannyContour, "png", cannyContourfile);

    for (Contour c : contours) {
      // Only the external contours are relevant.
      List<PointIndex_I32> vertices =
          ShapeFittingOps.fitPolygon(c.external, true, toleranceDist, toleranceAngle, 100);
      candidates.add(vertices);
    }

    for (List<PointIndex_I32> vertices : candidates) {
      try {
        Candidate c = new Candidate(vertices, image);
        if (c.size(minsize)) {
          c.rotate();
          results.add(c.getImage());
          objects.add(vertices);
        }
      } catch (Exception e) {
        System.out.println("Error creating candidate from contour " + e.getMessage());
      }
    }

    // write subimages of objects to files
    int i = 0;
    for (BufferedImage obj : results) {
      // print images to file
      try {
        File outputfile = new File(name + "_" + i + ".png");
        i++;
        ImageIO.write(obj, "png", outputfile);
      } catch (IOException e) {
        System.out.println("Error writing subimages" + e.getMessage());
      }
    }

    // draw objects onto original image and save
    Draw.drawPolygons(objects, image);
    File outputfile = new File(name + "_" + "annotated.png");
    ImageIO.write(image, "png", outputfile);
  }