/**
   * Process all the data in the training data set to learn the classifications. See code for
   * details.
   */
  public void learnAndSave() {
    System.out.println("======== Learning Classifier");

    // Either load pre-computed words or compute the words from the training images
    AssignCluster<double[]> assignment;
    if (new File(CLUSTER_FILE_NAME).exists()) {
      assignment = UtilIO.load(CLUSTER_FILE_NAME);
    } else {
      System.out.println(" Computing clusters");
      assignment = computeClusters();
    }

    // Use these clusters to assign features to words
    FeatureToWordHistogram_F64 featuresToHistogram =
        new FeatureToWordHistogram_F64(assignment, HISTOGRAM_HARD);

    // Storage for the work histogram in each image in the training set and their label
    List<HistogramScene> memory;

    if (!new File(HISTOGRAM_FILE_NAME).exists()) {
      System.out.println(" computing histograms");
      memory = computeHistograms(featuresToHistogram);
      UtilIO.save(memory, HISTOGRAM_FILE_NAME);
    }
  }
  public static void main(String[] args) {

    DescribeImageDense<ImageUInt8, TupleDesc_F64> desc =
        (DescribeImageDense)
            FactoryDescribeImageDense.surfFast(
                null, new ConfigDenseSample(DESC_SCALE, DESC_SKIP, DESC_SKIP), ImageUInt8.class);

    ComputeClusters<double[]> clusterer =
        FactoryClustering.kMeans_F64(null, MAX_KNN_ITERATIONS, 20, 1e-6);
    clusterer.setVerbose(true);

    NearestNeighbor<HistogramScene> nn = FactoryNearestNeighbor.exhaustive();
    ExampleClassifySceneKnn example = new ExampleClassifySceneKnn(desc, clusterer, nn);

    File trainingDir = new File(UtilIO.pathExample("learning/scene/train"));
    File testingDir = new File(UtilIO.pathExample("learning/scene/test"));

    if (!trainingDir.exists() || !testingDir.exists()) {
      System.err.println(
          "Please follow instructions in data/applet/learning/scene and download the");
      System.err.println("required files");
      System.exit(1);
    }

    example.loadSets(trainingDir, null, testingDir);
    // train the classifier
    example.learnAndSave();
    // now load it for evaluation purposes from the files
    example.loadAndCreateClassifier();

    // test the classifier on the test set
    Confusion confusion = example.evaluateTest();
    confusion.getMatrix().print();
    System.out.println("Accuracy = " + confusion.computeAccuracy());

    // Show confusion matrix
    // Not the best coloration scheme...  perfect = red diagonal and blue elsewhere.
    ShowImages.showWindow(
        new ConfusionMatrixPanel(confusion.getMatrix(), 400, true), "Confusion Matrix", true);

    // For  "fast"  SURF descriptor the accuracy is 52.2%
    // For "stable" SURF descriptor the accuracy is 49.4%

    // This is interesting. When matching images "stable" is significantly better than "fast"
    // One explanation is that the descriptor for "fast" samples a smaller region than "stable", by
    // a
    // couple of pixels at scale of 1.  Thus there is less overlap between the features.

    // Reducing the size of "stable" to 0.95 does slightly improve performance to 50.5%, can't scale
    // it down
    // much more without performance going down
  }
  /**
   * Extract dense features across the training set. Then clusters are found within those features.
   */
  private AssignCluster<double[]> computeClusters() {
    System.out.println("Image Features");

    // computes features in the training image set
    features.reset();
    for (String scene : train.keySet()) {
      List<String> imagePaths = train.get(scene);
      System.out.println("   " + scene);

      for (String path : imagePaths) {
        ImageUInt8 image = UtilImageIO.loadImage(path, ImageUInt8.class);
        describeImage.process(image, features, null);
      }
    }
    // add the features to the overall list which the clusters will be found inside of
    for (int i = 0; i < features.size; i++) {
      cluster.addReference(features.get(i));
    }

    System.out.println("Clustering");
    // Find the clusters.  This can take a bit
    cluster.process(NUMBER_OF_WORDS);

    UtilIO.save(cluster.getAssignment(), CLUSTER_FILE_NAME);

    return cluster.getAssignment();
  }
  public void loadAndCreateClassifier() {
    // load results from a file
    List<HistogramScene> memory = UtilIO.load(HISTOGRAM_FILE_NAME);
    AssignCluster<double[]> assignment = UtilIO.load(CLUSTER_FILE_NAME);

    FeatureToWordHistogram_F64 featuresToHistogram =
        new FeatureToWordHistogram_F64(assignment, HISTOGRAM_HARD);

    // Provide the training results to K-NN and it will preprocess these results for quick lookup
    // later on
    // Can use this classifier with saved results and avoid the

    classifier =
        new ClassifierKNearestNeighborsBow<ImageUInt8, TupleDesc_F64>(
            nn, describeImage, featuresToHistogram);
    classifier.setClassificationData(memory, getScenes().size());
    classifier.setNumNeighbors(NUM_NEIGHBORS);
  }
  public static void main(String args[]) {
    DemoBinaryImageOpsApp app = new DemoBinaryImageOpsApp(GrayF32.class);

    java.util.List<PathLabel> inputs = new ArrayList<>();
    inputs.add(new PathLabel("particles", UtilIO.pathExample("particles01.jpg")));
    inputs.add(new PathLabel("shapes", UtilIO.pathExample("shapes/shapes01.png")));

    app.setInputList(inputs);

    // wait for it to process one image so that the size isn't all screwed up
    while (!app.getHasProcessedImage()) {
      Thread.yield();
    }

    ShowImages.showWindow(app, "Binary Image Ops", true);

    System.out.println("Done");
  }
  public static void main(String[] args) {

    // Example with a moving camera.  Highlights why motion estimation is sometimes required
    String fileName = UtilIO.pathExample("tracking/chipmunk.mjpeg");
    // Camera has a bit of jitter in it.  Static kinda works but motion reduces false positives
    //		String fileName = UtilIO.pathExample("background/horse_jitter.mp4");

    // Comment/Uncomment to switch input image type
    ImageType imageType = ImageType.single(GrayF32.class);
    //		ImageType imageType = ImageType.il(3, InterleavedF32.class);
    //		ImageType imageType = ImageType.il(3, InterleavedU8.class);

    // Configure the feature detector
    ConfigGeneralDetector confDetector = new ConfigGeneralDetector();
    confDetector.threshold = 10;
    confDetector.maxFeatures = 300;
    confDetector.radius = 6;

    // Use a KLT tracker
    PointTracker tracker =
        FactoryPointTracker.klt(new int[] {1, 2, 4, 8}, confDetector, 3, GrayF32.class, null);

    // This estimates the 2D image motion
    ImageMotion2D<GrayF32, Homography2D_F64> motion2D =
        FactoryMotion2D.createMotion2D(
            500, 0.5, 3, 100, 0.6, 0.5, false, tracker, new Homography2D_F64());

    ConfigBackgroundBasic configBasic = new ConfigBackgroundBasic(30, 0.005f);

    // Configuration for Gaussian model.  Note that the threshold changes depending on the number of
    // image bands
    // 12 = gray scale and 40 = color
    ConfigBackgroundGaussian configGaussian = new ConfigBackgroundGaussian(12, 0.001f);
    configGaussian.initialVariance = 64;
    configGaussian.minimumDifference = 5;

    // Comment/Uncomment to switch background mode
    BackgroundModelMoving background =
        FactoryBackgroundModel.movingBasic(
            configBasic, new PointTransformHomography_F32(), imageType);
    //				FactoryBackgroundModel.movingGaussian(configGaussian, new PointTransformHomography_F32(),
    // imageType);

    MediaManager media = DefaultMediaManager.INSTANCE;
    SimpleImageSequence video = media.openVideo(fileName, background.getImageType());
    //				media.openCamera(null,640,480,background.getImageType());

    // ====== Initialize Images

    // storage for segmented image.  Background = 0, Foreground = 1
    GrayU8 segmented = new GrayU8(video.getNextWidth(), video.getNextHeight());
    // Grey scale image that's the input for motion estimation
    GrayF32 grey = new GrayF32(segmented.width, segmented.height);

    // coordinate frames
    Homography2D_F32 firstToCurrent32 = new Homography2D_F32();
    Homography2D_F32 homeToWorld = new Homography2D_F32();
    homeToWorld.a13 = grey.width / 2;
    homeToWorld.a23 = grey.height / 2;

    // Create a background image twice the size of the input image.  Tell it that the home is in the
    // center
    background.initialize(grey.width * 2, grey.height * 2, homeToWorld);

    BufferedImage visualized =
        new BufferedImage(segmented.width, segmented.height, BufferedImage.TYPE_INT_RGB);
    ImageGridPanel gui = new ImageGridPanel(1, 2);
    gui.setImages(visualized, visualized);

    ShowImages.showWindow(gui, "Detections", true);

    double fps = 0;
    double alpha = 0.01; // smoothing factor for FPS

    while (video.hasNext()) {
      ImageBase input = video.next();

      long before = System.nanoTime();
      GConvertImage.convert(input, grey);

      if (!motion2D.process(grey)) {
        throw new RuntimeException("Should handle this scenario");
      }

      Homography2D_F64 firstToCurrent64 = motion2D.getFirstToCurrent();
      UtilHomography.convert(firstToCurrent64, firstToCurrent32);

      background.segment(firstToCurrent32, input, segmented);
      background.updateBackground(firstToCurrent32, input);
      long after = System.nanoTime();

      fps = (1.0 - alpha) * fps + alpha * (1.0 / ((after - before) / 1e9));

      VisualizeBinaryData.renderBinary(segmented, false, visualized);
      gui.setImage(0, 0, (BufferedImage) video.getGuiImage());
      gui.setImage(0, 1, visualized);
      gui.repaint();

      System.out.println("FPS = " + fps);

      try {
        Thread.sleep(5);
      } catch (InterruptedException e) {
      }
    }
  }