// Path: src/main/java/com/tom_e_white/set_game/preprocess/CardDetector.java
// public class CardDetector {
// 
//   private final int medianBlur;
//   private final int areaTolerancePct;
// 
//   public CardDetector() {
//     this(16, 25);
//   }
// 
//   public CardDetector(int areaTolerancePct) {
//     this(-1, areaTolerancePct);
//   }
// 
//   public CardDetector(int medianBlur, int areaTolerancePct) {
//     this.medianBlur = medianBlur;
//     this.areaTolerancePct = areaTolerancePct;
//   }
// 
//   public List<CardImage> detect(String filename) throws IOException {
//     return detect(filename, false);
//   }
// 
//   public List<CardImage> detect(String filename, boolean debug) throws IOException {
//     return detect(filename, debug, false);
//   }
// 
//   public List<CardImage> detect(String filename, boolean debug, boolean allowRotated) throws IOException {
//     return detect(filename, debug, allowRotated, -1, -1);
//   }
// 
//   public List<CardImage> detect(String filename, boolean debug, boolean allowRotated, int expectedRows, int expectedColumns) throws IOException {
//     return detect(UtilImageIO.loadImage(filename), filename, debug, allowRotated, expectedRows, expectedColumns);
//   }
// 
//   public List<CardImage> detect(BufferedImage image, String filename, boolean debug, boolean allowRotated) throws IOException {
//     return detect(image, filename, debug, allowRotated, -1, -1);
//   }
// 
//   public List<CardImage> detect(BufferedImage image, String filename, boolean debug, boolean allowRotated, int expectedRows, int expectedColumns) throws IOException {
//     // Based on code from http://boofcv.org/index.php?title=Example_Binary_Image
// 
//     String imageInfo = filename == null ? image.toString() : filename;
// 
//     if (!allowRotated && image.getWidth() > image.getHeight()) {
//       throw new IllegalArgumentException("Image height must be greater than width: " + imageInfo);
//     }
//     int medianBlur = this.medianBlur != -1 ? this.medianBlur : Math.max(image.getHeight(), image.getWidth()) / 200; // heuristic
// 
//     ListDisplayPanel panel = debug ? new ListDisplayPanel() : null;
// 
//     List<Quadrilateral_F64> quads = ImageProcessingPipeline.fromBufferedImage(image, panel)
//             .gray()
//             .medianBlur(medianBlur)
//             .binarize(0, 255)
//             .erode()
//             .dilate()
//             .contours()
//             .polygons(0.05, 0.1)
//             .getExternalQuadrilaterals();
// 
//     // Only include shapes that are within given percentage of the mean area. This filters out image artifacts that
//     // happen to be quadrilaterals that are not cards (since they are usually a different size).
//     List<Quadrilateral_F64> cards = GeometryUtils.filterByArea(quads, areaTolerancePct);
//     List<List<Quadrilateral_F64>> rows = GeometryUtils.sortRowWise(cards);// sort into a stable order
//     if (expectedRows != -1 && rows.size() != expectedRows) {
//       throw new IllegalArgumentException(String.format("Expected %s rows, but detected %s: %s", expectedRows, rows.size(), imageInfo));
//     }
//     if (expectedColumns != -1) {
//       rows.forEach(row -> {
//         if (row.size() != expectedColumns) {
//           throw new IllegalArgumentException(String.format("Expected %s columns, but detected %s: %s", expectedColumns, row.size(), imageInfo));
//         }
//       });
//     }
//     List<CardImage> cardImages = rows.stream()
//             .flatMap(List::stream)
//             .map(q -> {
//               // Remove perspective distortion
//               Planar<GrayF32> output = GeometryUtils.removePerspectiveDistortion(image, q, 57 * 3, 89 * 3); // actual cards measure 57 mm x 89 mm
//               BufferedImage im = ConvertBufferedImage.convertTo_F32(output, null, true);
//               return new CardImage(im, q);
//             }
//     ).collect(Collectors.toList());
// 
//     if (debug) {
//       int i = 1;
//       for (CardImage card : cardImages) {
//         panel.addImage(card.getImage(), "Card " + i++);
//       }
//       ShowImages.showWindow(panel, getClass().getSimpleName(), true);
//     }
// 
//     return cardImages;
// 
//   }
// 
//   public static void main(String[] args) throws IOException {
//     new CardDetector(66).detect(args[0], true, true);
//   }
// }
// 
// Path: src/main/java/com/tom_e_white/set_game/preprocess/CardImage.java
// public class CardImage {
//     private final BufferedImage image;
//     private final Quadrilateral_F64 externalQuadrilateral;
// 
//     public CardImage(BufferedImage image, Quadrilateral_F64 externalQuadrilateral) {
//         this.image = image;
//         this.externalQuadrilateral = externalQuadrilateral;
//     }
// 
//     public BufferedImage getImage() {
//         return image;
//     }
// 
//     public Quadrilateral_F64 getExternalQuadrilateral() {
//         return externalQuadrilateral;
//     }
// }


import com.tom_e_white.set_game.preprocess.CardDetector;
import com.tom_e_white.set_game.preprocess.CardImage;
import com.tom_e_white.set_game.train.*;
import smile.classification.Classifier;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.List;
import java.util.stream.Collectors;
package com.tom_e_white.set_game.predict;



/**
 * Use feature finders to predict the features of each card in the test set.
 */
public class PredictCardFeaturesOnTestData {

    public static double[] predict(File testFile) throws IOException, ParseException {
        return predict(testFile, 1);
    }

    public static double[] predict(File testFile, int version) throws IOException, ParseException {

        FeatureFinder[] finders = new FeatureFinder[] {
                new FindCardNumberFeatures(),
                new FindCardColourFeatures(version),
                new FindCardShadingFeatures(),
                new FindCardShapeFeatures()
        };
        double[] accuracies = new double[finders.length];
        int index = 0;
        for (FeatureFinder finder : finders) {
            System.out.println(finder.getClass().getSimpleName());

            Classifier<double[]> classifier = finder.getClassifier();

// Path: src/main/java/com/tom_e_white/set_game/preprocess/CardDetector.java
// public class CardDetector {
// 
//   private final int medianBlur;
//   private final int areaTolerancePct;
// 
//   public CardDetector() {
//     this(16, 25);
//   }
// 
//   public CardDetector(int areaTolerancePct) {
//     this(-1, areaTolerancePct);
//   }
// 
//   public CardDetector(int medianBlur, int areaTolerancePct) {
//     this.medianBlur = medianBlur;
//     this.areaTolerancePct = areaTolerancePct;
//   }
// 
//   public List<CardImage> detect(String filename) throws IOException {
//     return detect(filename, false);
//   }
// 
//   public List<CardImage> detect(String filename, boolean debug) throws IOException {
//     return detect(filename, debug, false);
//   }
// 
//   public List<CardImage> detect(String filename, boolean debug, boolean allowRotated) throws IOException {
//     return detect(filename, debug, allowRotated, -1, -1);
//   }
// 
//   public List<CardImage> detect(String filename, boolean debug, boolean allowRotated, int expectedRows, int expectedColumns) throws IOException {
//     return detect(UtilImageIO.loadImage(filename), filename, debug, allowRotated, expectedRows, expectedColumns);
//   }
// 
//   public List<CardImage> detect(BufferedImage image, String filename, boolean debug, boolean allowRotated) throws IOException {
//     return detect(image, filename, debug, allowRotated, -1, -1);
//   }
// 
//   public List<CardImage> detect(BufferedImage image, String filename, boolean debug, boolean allowRotated, int expectedRows, int expectedColumns) throws IOException {
//     // Based on code from http://boofcv.org/index.php?title=Example_Binary_Image
// 
//     String imageInfo = filename == null ? image.toString() : filename;
// 
//     if (!allowRotated && image.getWidth() > image.getHeight()) {
//       throw new IllegalArgumentException("Image height must be greater than width: " + imageInfo);
//     }
//     int medianBlur = this.medianBlur != -1 ? this.medianBlur : Math.max(image.getHeight(), image.getWidth()) / 200; // heuristic
// 
//     ListDisplayPanel panel = debug ? new ListDisplayPanel() : null;
// 
//     List<Quadrilateral_F64> quads = ImageProcessingPipeline.fromBufferedImage(image, panel)
//             .gray()
//             .medianBlur(medianBlur)
//             .binarize(0, 255)
//             .erode()
//             .dilate()
//             .contours()
//             .polygons(0.05, 0.1)
//             .getExternalQuadrilaterals();
// 
//     // Only include shapes that are within given percentage of the mean area. This filters out image artifacts that
//     // happen to be quadrilaterals that are not cards (since they are usually a different size).
//     List<Quadrilateral_F64> cards = GeometryUtils.filterByArea(quads, areaTolerancePct);
//     List<List<Quadrilateral_F64>> rows = GeometryUtils.sortRowWise(cards);// sort into a stable order
//     if (expectedRows != -1 && rows.size() != expectedRows) {
//       throw new IllegalArgumentException(String.format("Expected %s rows, but detected %s: %s", expectedRows, rows.size(), imageInfo));
//     }
//     if (expectedColumns != -1) {
//       rows.forEach(row -> {
//         if (row.size() != expectedColumns) {
//           throw new IllegalArgumentException(String.format("Expected %s columns, but detected %s: %s", expectedColumns, row.size(), imageInfo));
//         }
//       });
//     }
//     List<CardImage> cardImages = rows.stream()
//             .flatMap(List::stream)
//             .map(q -> {
//               // Remove perspective distortion
//               Planar<GrayF32> output = GeometryUtils.removePerspectiveDistortion(image, q, 57 * 3, 89 * 3); // actual cards measure 57 mm x 89 mm
//               BufferedImage im = ConvertBufferedImage.convertTo_F32(output, null, true);
//               return new CardImage(im, q);
//             }
//     ).collect(Collectors.toList());
// 
//     if (debug) {
//       int i = 1;
//       for (CardImage card : cardImages) {
//         panel.addImage(card.getImage(), "Card " + i++);
//       }
//       ShowImages.showWindow(panel, getClass().getSimpleName(), true);
//     }
// 
//     return cardImages;
// 
//   }
// 
//   public static void main(String[] args) throws IOException {
//     new CardDetector(66).detect(args[0], true, true);
//   }
// }
// 
// Path: src/main/java/com/tom_e_white/set_game/preprocess/CardImage.java
// public class CardImage {
//     private final BufferedImage image;
//     private final Quadrilateral_F64 externalQuadrilateral;
// 
//     public CardImage(BufferedImage image, Quadrilateral_F64 externalQuadrilateral) {
//         this.image = image;
//         this.externalQuadrilateral = externalQuadrilateral;
//     }
// 
//     public BufferedImage getImage() {
//         return image;
//     }
// 
//     public Quadrilateral_F64 getExternalQuadrilateral() {
//         return externalQuadrilateral;
//     }
// }


// Path: src/main/java/com/tom_e_white/set_game/predict/PredictCardFeaturesOnTestData.java
import com.tom_e_white.set_game.preprocess.CardDetector;
import com.tom_e_white.set_game.preprocess.CardImage;
import com.tom_e_white.set_game.train.*;
import smile.classification.Classifier;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.List;
import java.util.stream.Collectors;

package com.tom_e_white.set_game.predict;



/**
 * Use feature finders to predict the features of each card in the test set.
 */
public class PredictCardFeaturesOnTestData {

    public static double[] predict(File testFile) throws IOException, ParseException {
        return predict(testFile, 1);
    }

    public static double[] predict(File testFile, int version) throws IOException, ParseException {

        FeatureFinder[] finders = new FeatureFinder[] {
                new FindCardNumberFeatures(),
                new FindCardColourFeatures(version),
                new FindCardShadingFeatures(),
                new FindCardShapeFeatures()
        };
        double[] accuracies = new double[finders.length];
        int index = 0;
        for (FeatureFinder finder : finders) {
            System.out.println(finder.getClass().getSimpleName());

            Classifier<double[]> classifier = finder.getClassifier();

            CardDetector cardDetector = new CardDetector();