From da56c78116536566691fe1e4ae655cf044c75638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20O=C5=A1lej=C5=A1ek?= <oslejsek@fi.muni.cz> Date: Mon, 3 Jan 2022 15:47:35 +0100 Subject: [PATCH] Resolve "Ground truth similarity stats" --- .../visitors/mesh/HausdorffDistance.java | 2 +- .../tests/BatchSimilarityApproxHausdorff.java | 145 ++++++++++++++++++ .../tests/BatchSimilarityGroundTruth.java | 92 +++++++++++ .../cz/fidentis/analyst/tests/BatchTests.java | 96 ------------ .../analyst/mesh/core/MeshFacetImpl.java | 3 + .../analyst/mesh/core/MeshTriangle.java | 2 +- 6 files changed, 242 insertions(+), 98 deletions(-) create mode 100644 GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityApproxHausdorff.java create mode 100644 GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityGroundTruth.java delete mode 100644 GUI/src/main/java/cz/fidentis/analyst/tests/BatchTests.java diff --git a/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistance.java b/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistance.java index 02813452..283eff93 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistance.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistance.java @@ -70,7 +70,7 @@ import javax.vecmath.Point3d; * @author Daniel Schramm * @author Radek Oslejsek */ -public class HausdorffDistance extends MeshVisitor { + public class HausdorffDistance extends MeshVisitor { /** * Distance computation strategies. diff --git a/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityApproxHausdorff.java b/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityApproxHausdorff.java new file mode 100644 index 00000000..32b5bf5b --- /dev/null +++ b/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityApproxHausdorff.java @@ -0,0 +1,145 @@ +package cz.fidentis.analyst.tests; + +import cz.fidentis.analyst.face.HumanFace; +import cz.fidentis.analyst.icp.IcpTransformer; +import cz.fidentis.analyst.icp.NoUndersampling; +import cz.fidentis.analyst.visitors.mesh.HausdorffDistance; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.DoubleSummaryStatistics; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * A class for testing the efficiency of batch processing algorithms. + * The goal of this tool is to measure time and precision of + * approximated Hausdorff distance (HD). + * + * - First, HD of the first face (primary face) with other faces is computed + * - Each secondary face is superimposed using ICP + * - Then it is stored in k-d tree + * - Relative (!) HD from the primary to secondary face is computed and stored. + * The order cannot be reverted because we need HD matrices of the same size. + * - TODO: First face should be replaced with average face + * - Then, the mutual distances are approximated: + * - For each pair of faces A and B, the distance is computed as the difference + * between distances A-P and B-P, where P is the primary face. + * + * @author Radek Oslejsek + */ +public class BatchSimilarityApproxHausdorff { + private static final String DATA_DIR = "../../analyst-data-antropologie/_ECA"; + private static final String OUTPUT_FILE = "../../SIMILARITY_APPROX_HAUSDORFF.csv"; + + /** + * Main method + * @param args Input arguments + * @throws IOException on IO error + */ + public static void main(String[] args) throws IOException, ClassNotFoundException, Exception { + List<Path> faces = Files.list(new File(DATA_DIR).toPath()) + .filter(f -> f.toString().endsWith(".obj")) + .sorted() + .limit(100) + .collect(Collectors.toList()); + + + Map<Integer, List<Double>> distances = new HashMap<>(); + + for (int i = 1; i < faces.size(); i++) { + HumanFace priFace = new HumanFace(faces.get(0).toFile()); + HumanFace secFace = new HumanFace(faces.get(i).toFile()); + + secFace.computeKdTree(false); + + System.out.println(i + "/" + (faces.size()-1) + " (" + priFace.getShortName() + "/" + secFace.getShortName() + ")"); + + IcpTransformer icp = new IcpTransformer(priFace.getMeshModel(), 50, true, 0.3, new NoUndersampling()); + secFace.getMeshModel().compute(icp, true); + + HausdorffDistance hd = new HausdorffDistance(secFace.getKdTree(), HausdorffDistance.Strategy.POINT_TO_POINT, true, true); + priFace.getMeshModel().compute(hd, true); + + //System.out.println(hd.getDistances().get(priFace.getMeshModel().getFacets().get(0)).size()); + distances.put(i, hd.getDistances().get(priFace.getMeshModel().getFacets().get(0))); + } + + BufferedWriter bfw = new BufferedWriter(new FileWriter(OUTPUT_FILE)); + bfw.write("PRI FACE;SEC FACE;MIN;MAX;AVG"); + bfw.newLine(); + + for (int i = 0; i < faces.size(); i++) { + for (int j = 0; j < faces.size(); j++) { + if (i == j) { + continue; + } + + DoubleSummaryStatistics stats; + if (i == 0 || j == 0) { + stats = getStats(distances.get((i == 0) ? j : i)); + } else { + stats = getStats(distances.get(i), distances.get(j)); + } + + bfw.write(getShortName(faces.get(i).toFile())+";"); + bfw.write(getShortName(faces.get(j).toFile())+";"); + if (stats != null) { + bfw.write(String.format("%.20f", stats.getMin())+";"); + bfw.write(String.format("%.20f", stats.getMax())+";"); + bfw.write(String.format("%.20f", stats.getAverage())+""); + bfw.newLine(); + } else { + bfw.write(String.format("%.20f", Double.NaN)+";"); + bfw.write(String.format("%.20f", Double.NaN)+";"); + bfw.write(String.format("%.20f", Double.NaN)+""); + bfw.newLine(); + } + bfw.flush(); + } + } + + bfw.close(); + + } + + private static DoubleSummaryStatistics getStats(List<Double> d1, List<Double> d2) { + if (d1.size() != d2.size()) { + return null; + } + + List<Double> ret = new ArrayList<>(d1.size()); + for (int i = 0; i < d1.size(); i++) { + ret.add(Math.abs(d2.get(i) - d1.get(i))); + } + + return ret.stream() + .mapToDouble(Double::doubleValue) + .summaryStatistics(); + } + + private static DoubleSummaryStatistics getStats(List<Double> d1) { + List<Double> ret = new ArrayList<>(d1.size()); + for (int i = 0; i < d1.size(); i++) { + ret.add(Math.abs(d1.get(i))); + } + + return d1.stream() + .mapToDouble(v -> Math.abs(v)) + .summaryStatistics(); + } + + private static String getShortName(File file) throws IOException { + String id = file.getCanonicalPath(); + String name = id.substring(0, id.lastIndexOf('.')); // remove extention + name = name.substring(id.lastIndexOf('/')+1, name.length()); + name = name.substring(name.lastIndexOf(File.separatorChar) + 1, name.length()); + return name; + } +} diff --git a/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityGroundTruth.java b/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityGroundTruth.java new file mode 100644 index 00000000..85852531 --- /dev/null +++ b/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityGroundTruth.java @@ -0,0 +1,92 @@ +package cz.fidentis.analyst.tests; + +import cz.fidentis.analyst.face.HumanFace; +import cz.fidentis.analyst.icp.IcpTransformer; +import cz.fidentis.analyst.icp.NoUndersampling; +import cz.fidentis.analyst.visitors.mesh.HausdorffDistance; +import cz.fidentis.analyst.visitors.mesh.HausdorffDistance.Strategy; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.DoubleSummaryStatistics; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A class for testing the efficiency of batch processing algorithms. + * The goal of this tool is to create a "ground truth" measurements for other optimization techniques + * + * - All pairs are take one by one from the collection + * - First (primary) face is stored in k-d tree + * - Secondary face is superimposed using ICP + * - Hausdorff distance from the secondary to the primary face is computed using POINT_TO_POINT strategy and absolute distances. + * + * @author Radek Oslejsek + */ +public class BatchSimilarityGroundTruth { + + private static final String DATA_DIR = "../../analyst-data-antropologie/_ECA"; + private static final String OUTPUT_FILE = "../../SIMILARITY_GROUND_TRUTH.csv"; + private static final int MAX_SAMPLES = 100; + + /** + * Main method + * @param args Input arguments + * @throws IOException on IO error + */ + public static void main(String[] args) throws IOException, ClassNotFoundException, Exception { + List<Path> faces = Files.list(new File(DATA_DIR).toPath()) + .filter(f -> f.toString().endsWith(".obj")) + .sorted() + .limit(MAX_SAMPLES) + .collect(Collectors.toList()); + + BufferedWriter bfw = new BufferedWriter(new FileWriter(OUTPUT_FILE)); + bfw.write("SEC FACE;PRI FACE;MIN;MAX;AVG (from SEC to PRI);IPC time (mm:ss,mil);HD time (mm:ss,mil)"); + bfw.newLine(); + + int counter = 1; + for (int i = 0; i < faces.size(); i++) { + HumanFace priFace = new HumanFace(faces.get(i).toFile()); + priFace.computeKdTree(false); + + for (int j = 0; j < faces.size(); j++) { + if (i == j) { + continue; + } + + HumanFace secFace = new HumanFace(faces.get(j).toFile()); + + System.out.println(counter++ + "/" + (faces.size()*faces.size()-faces.size()) + " (" + priFace.getShortName() + "/" + secFace.getShortName() + ")"); + + long icpTime = System.currentTimeMillis(); + IcpTransformer icp = new IcpTransformer(priFace.getMeshModel(), 50, true, 0.3, new NoUndersampling()); + secFace.getMeshModel().compute(icp, true); + Duration icpDuration = Duration.ofMillis(System.currentTimeMillis() - icpTime); + + long hdTime = System.currentTimeMillis(); + HausdorffDistance hd = new HausdorffDistance(priFace.getKdTree(), Strategy.POINT_TO_POINT, false, true); + secFace.getMeshModel().compute(hd, true); + DoubleSummaryStatistics stats = hd.getStats(); + Duration hdDuration = Duration.ofMillis(System.currentTimeMillis() - hdTime); + + bfw.write(secFace.getShortName()+";"); + bfw.write(priFace.getShortName()+";"); + bfw.write(String.format("%.20f", stats.getMin())+";"); + bfw.write(String.format("%.20f", stats.getMax())+";"); + bfw.write(String.format("%.20f", stats.getAverage())+";"); + bfw.write(String.format("%02d:%02d,%03d", icpDuration.toMinutesPart(), icpDuration.toSecondsPart(), icpDuration.toMillisPart())+";"); + bfw.write(String.format("%02d:%02d,%03d", hdDuration.toMinutesPart(), hdDuration.toSecondsPart(), hdDuration.toMillisPart())+""); + bfw.newLine(); + bfw.flush(); + } + } + + bfw.close(); + } + +} diff --git a/GUI/src/main/java/cz/fidentis/analyst/tests/BatchTests.java b/GUI/src/main/java/cz/fidentis/analyst/tests/BatchTests.java deleted file mode 100644 index 8b875288..00000000 --- a/GUI/src/main/java/cz/fidentis/analyst/tests/BatchTests.java +++ /dev/null @@ -1,96 +0,0 @@ -package cz.fidentis.analyst.tests; - -import cz.fidentis.analyst.BatchProcessor; -import cz.fidentis.analyst.face.HumanFace; -import cz.fidentis.analyst.face.HumanFaceFactory; -import cz.fidentis.analyst.icp.NoUndersampling; -import cz.fidentis.analyst.icp.UndersamplingStrategy; -import cz.fidentis.analyst.kdtree.KdTree; -import cz.fidentis.analyst.mesh.io.MeshObjExporter; -import java.io.File; -import java.io.IOException; -import java.util.Collections; -import java.util.List; - -/** - * A class for testing the efficiency of batch processing algorithms. - * - * @author Radek Oslejsek - */ -public class BatchTests { - - private static final String DATA_DIR = "../../analyst-data/multi-scan-models-anonymized-fixed/"; - private static final String PRIMARY_FACE_PATH = DATA_DIR + "/average-boy-17-20/average_boy_17-20.obj"; - //private static final String PRIMARY_FACE_PATH = dataDir + "/average-girl-17-20/average_girl_17-20.obj"; - //private static final String PRIMARY_FACE_PATH = "../../analyst-data/basic-models/02.obj"; - //private static final String PRIMARY_FACE_PATH = dataDir + "/07/00007_01_ECA.obj"; - private static final String TEMPLATE_FACE_PATH = DATA_DIR + "template.obj"; - - /** - * Main method - * @param args Input arguments - * @throws IOException on IO error - */ - public static void main(String[] args) throws IOException, ClassNotFoundException, Exception { - BatchProcessor batch = new BatchProcessor(200); // the best is 500? - long startTime, endTime; - - List<String> faceIDs; - String primaryFaceId = HumanFaceFactory.instance().loadFace(new File(PRIMARY_FACE_PATH)); - HumanFace primaryFace = HumanFaceFactory.instance().getFace(primaryFaceId); - KdTree primaryFacekdTree = primaryFace.computeKdTree(false); - - ////////////////////////////////////////////// - System.out.println(); - System.out.println("Loading files:"); - startTime = System.currentTimeMillis(); - faceIDs = batch.readFaces(DATA_DIR); - endTime = System.currentTimeMillis(); - printTimes(startTime, endTime, faceIDs.size()); - System.out.println(faceIDs.size() + "\t\t Number of files"); - System.out.println(HumanFaceFactory.instance()); - - ////////////////////////////////////////////// - System.out.println(); - System.out.println("Computation of symmetry planes:"); - startTime = System.currentTimeMillis(); - batch.computeSymmetryPlanes(faceIDs); - batch.computeSymmetryPlanes(Collections.singletonList(primaryFaceId)); - endTime = System.currentTimeMillis(); - printTimes(startTime, endTime, faceIDs.size()); - - ////////////////////////////////////////////// - UndersamplingStrategy strategy = new NoUndersampling(); - //UndersamplingStrategy strategy = new RandomStrategy(0.5); - System.out.println(); - System.out.println("ICP registration with " + strategy + ":"); - startTime = System.currentTimeMillis(); - batch.superimposeOnPrimaryFace(primaryFaceId, faceIDs, 10, 0.05, strategy); - endTime = System.currentTimeMillis(); - printTimes(startTime, endTime, faceIDs.size()); - System.out.printf("%.2f \t\t Avg number of iterations", batch.getAvgNumIcpIterations()); - System.out.println(); - - ////////////////////////////////////////////// - System.out.println(); - System.out.println("Computation of averaged template face:"); - startTime = System.currentTimeMillis(); - batch.computeTemplateFace(primaryFaceId, faceIDs); - endTime = System.currentTimeMillis(); - printTimes(startTime, endTime, faceIDs.size()); - System.out.println("Writing average template to " + TEMPLATE_FACE_PATH); - MeshObjExporter exp = new MeshObjExporter(batch.getAvgTemplateFace().getMeshModel()); - exp.exportModelToObj(new File(TEMPLATE_FACE_PATH)); - } - - private static void printTimes(long startTime, long endTime, int numFaces) { - double timeDist = endTime - startTime; - double avgTime = (timeDist/numFaces)/1000.0; - System.out.printf("%.2f sec \t Overall time", timeDist/1000.0); - System.out.println(); - System.out.printf("%.2f sec \t Average time", avgTime); - System.out.println(); - System.out.printf("%.2f min \t Estimated time for 500 faces", (avgTime*500)/60); - System.out.println(); - } -} diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacetImpl.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacetImpl.java index 6af74718..7d701dc0 100644 --- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacetImpl.java +++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacetImpl.java @@ -209,6 +209,9 @@ public class MeshFacetImpl implements MeshFacet { for (MeshTriangle tri: this.getAdjacentTriangles(vertexIndex)) { Point3d projection = tri.getClosestPoint(point); + if (projection == null) { + continue; + } Vector3d aux = new Vector3d(projection); aux.sub(point); double d = aux.length(); diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshTriangle.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshTriangle.java index 803940f1..e2fedd37 100644 --- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshTriangle.java +++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshTriangle.java @@ -118,7 +118,7 @@ public class MeshTriangle implements Iterable<MeshPoint> { * the triangle inside the triangle boundaries. * * @param point 3D point - * @return the closest point or {@code null} if the input parametr is missing + * @return the closest point or {@code null} if the input parameter is missing */ public Point3d getClosestPoint(Point3d point) { if (point == null) { -- GitLab