diff --git a/Comparison/src/main/java/cz/fidentis/analyst/EfficiencyTests.java b/Comparison/src/main/java/cz/fidentis/analyst/EfficiencyTests.java index 27ecd8c2aa34b11718a24ed53fc4a7a56325ab3f..794fbe1a94b36c7bfe1d1862f47c20edabf362ac 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/EfficiencyTests.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/EfficiencyTests.java @@ -57,13 +57,13 @@ public class EfficiencyTests { System.out.println(); System.out.println("Hausdorff distance sequentially:"); - testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT_ABSOLUTE, false, false, false), printDetails); + testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT_DISTANCE_ONLY, false, false, false), printDetails); testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT, relativeDist, false, false), printDetails); testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_TRIANGLE_APPROXIMATE, relativeDist, false, false), printDetails); System.out.println(); - System.out.println("Hausdorff distance consurrently:"); - testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT_ABSOLUTE, false, false, true), printDetails); + System.out.println("Hausdorff distance concurrently:"); + testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT_DISTANCE_ONLY, false, false, true), printDetails); testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT, relativeDist, false, true), printDetails); testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_TRIANGLE_APPROXIMATE, relativeDist, false, true), printDetails); } diff --git a/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeDistance.java b/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeDistance.java index 5218f3cdda44028a7d1736a59a3850d34a6e9d7e..6d2746d211d24f518aaa68646a04f06e6a1e0f8e 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeDistance.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeDistance.java @@ -1,6 +1,5 @@ package cz.fidentis.analyst.visitors.kdtree; -import cz.fidentis.analyst.kdtree.KdNode; import cz.fidentis.analyst.kdtree.KdTree; import cz.fidentis.analyst.kdtree.KdTreeVisitor; import cz.fidentis.analyst.visitors.Distance; 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 6d2664044e189b7ea649eaa8de5ac7bf512d1640..0e32da5d667fa74a9cdea12d7a3c9da5f04a189e 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 @@ -18,48 +18,47 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import javax.vecmath.Vector3d; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; -import javax.vecmath.Vector3d; - /** * Visitor for Hausdorff distance. * This visitor is instantiated with a single k-d tree (either given as the input * parameter, or automatically created from the triangular mesh). - * When applied to other mesh facets, it computes Huasdorff distance from them to + * When applied to other mesh facets, it computes Hausdorff distance from them to * the instantiated k-d tree. * <p> * This visitor is thread-safe. * </p> * <p> - * The distance is computer aither as absolute or relative. Abosolute + * The distance is computer either as absolute or relative. Absolute * represents Euclidean distance (all numbers are positive). On the contrary, * relative distance considers orientation of the visited mesh (determined by its normal vectors) * and produces positive or negative distances depending on whether the primary * mesh is "in front of" or "behind" the given vertex. * </p> * <p> - * Comparison of methods' efficiency performed with scans of two human faces on + * Comparison of methods efficiency performed with scans of two human faces on * Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz, 8 CPU cores. * </p> * <p> * Hausdorff distance sequentially: * <table> - * <tr><td>2076 msec</td><td>POINT_TO_POINT_ABSOLUTE</td></tr> - * <tr><td>3991 msec</td><td>POINT_TO_POINT</td></tr> - * <tr><td>5528 msec</td><td>POINT_TO_TRIANGLE_APPROXIMATE</td></tr> + * <tr><td>2443 msec</td><td>OINT_TO_POINT_DISTANCE_ONLY</td></tr> + * <tr><td>2495 msec</td><td>POINT_TO_POINT</td></tr> + * <tr><td>3430 msec</td><td>POINT_TO_TRIANGLE_APPROXIMATE</td></tr> * </table> * </p> * <p> - * Hausdorff distance consurrently: + * Hausdorff distance concurrently: * <table> - * <tr><td>1583 msec</td><td>POINT_TO_POINT_ABSOLUTE</td></tr> - * <tr><td>2399 msec</td><td>POINT_TO_POINT</td></tr> - * <tr><td>2745 msec</td><td>POINT_TO_TRIANGLE_APPROXIMATE</td></tr> + * <tr><td>1782 msec</td><td>OINT_TO_POINT_DISTANCE_ONLY</td></tr> + * <tr><td>2248 msec</td><td>POINT_TO_POINT</td></tr> + * <tr><td>2574 msec</td><td>POINT_TO_TRIANGLE_APPROXIMATE</td></tr> * </table> * </p> * @@ -73,41 +72,53 @@ public class HausdorffDistance extends MeshVisitor { * @author Radek Oslejsek */ public enum Strategy { + /** - * The fastest algorithm. Computes abolute distance between mesh vertices. - * Relative distance is not supported. + * The fastest algorithm. Computes absolute distance between mesh vertices. + * Relative distance is not supported, no closest points are returned. */ - POINT_TO_POINT_ABSOLUTE, + POINT_TO_POINT_DISTANCE_ONLY, /** - * A bit slower point-to-point algorithm. - * But supports both relative and absolute distances. - * However, for relative distance can produce noise. If there are multiple - * closest points to the given vertex, then - * {@code Double.POSITIVE_INFINITY} value is set as the distance. - * If this situation happens with human faces, then we consider this part - * of the face as noisy anyway. + * A bit slower point-to-point algorithm. But supports both relative and absolute distances. + * Moreover, the closest points are returned. + * <p> + * This algorithm can can produce "noise". If there are multiple closest points + * at the primary mesh, then it is not possible to choose which of them is correct (better). + * Therefore, the {@code Double.POSITIVE_INFINITY} value is set as the distance for this vertices. + * If this situation happens with human faces, we would consider this part + * of the face as noisy anyway, trying to avoid these parts from further processing. + * </p> */ POINT_TO_POINT, /** - * The fast point-to-triangle distance strategy, which more precise - * than point-to-point strategies. - * Supports both relative and absolute distances. - * However, the algorithm is not geometrically correct. - * Can produce noise (does not found the really closests triangle) on wrinkly sufaces. - * Nevertheless, we aim to omit such noisy parts of human face from analysis - * rather than dealing with this situation. + * The fast point-to-triangle distance strategy. Produces more precise results + * than the {@code POINT_TO_POINT} strategy, but is a bit slower. + * <p> + * The algorithm supports both relative and absolute distances. + * But is not geometrically correct. Can produce "noise" + * in the sense that it may not found the really closest triangle on "wrinkly" surfaces. + * If this situation happens with human faces, we would consider this part + * of the face as noisy anyway, trying to avoid these parts from further processing. + * </p> */ POINT_TO_TRIANGLE_APPROXIMATE } /** - * Result. + * Hausdorff distance for all inspected meshes. * Key = visited mesh facets. - * Vvalue = Hausdorff distance of each vertex of the visited facet to the source facet. + * Value = Hausdorff distance of each vertex of the visited facet to the source facet. */ private final Map<MeshFacet, List<Double>> distances = new HashMap<>(); + + /** + * The closest points for all inspected meshes (empty for the {@code POINT_TO_POINT_ABSOLUTE} strategy + * Key = visited mesh facets. + * Value = The nearest point of each vertex of the visited facet to the source facet. + */ + private final Map<MeshFacet, List<Vector3d>> nearestPoints = new HashMap<>(); private final Strategy strategy; @@ -116,7 +127,7 @@ public class HausdorffDistance extends MeshVisitor { private final boolean parallel; /** - * The source triangular mesh (set of mesh facets) stred in k-d tree. + * The source triangular mesh (set of mesh facets) stored in k-d tree. */ private final KdTree kdTree; @@ -130,7 +141,7 @@ public class HausdorffDistance extends MeshVisitor { * i.e., we can get negative distances. * @param asynchronous If {@code true}, then the inspection of mesh facets is asynchronous (and possibly concurrent). * @param parallel If {@code true}, then the algorithm runs concurrently utilizing all CPU cores - * @throws IllegalArgumentException if some parametr is wrong + * @throws IllegalArgumentException if some parameter is wrong */ public HausdorffDistance(Set<MeshFacet> mainFacets, Strategy strategy, boolean relativeDistance, boolean asynchronous, boolean parallel) { super(asynchronous); @@ -140,7 +151,7 @@ public class HausdorffDistance extends MeshVisitor { if (mainFacets == null) { throw new IllegalArgumentException("mainFacets"); } - if (strategy == Strategy.POINT_TO_POINT_ABSOLUTE && relativeDistance) { + if (strategy == Strategy.POINT_TO_POINT_DISTANCE_ONLY && relativeDistance) { throw new IllegalArgumentException("Point-to-point strategy cannot be used with relative distance"); } this.kdTree = new KdTree(new ArrayList<>(mainFacets)); @@ -156,7 +167,7 @@ public class HausdorffDistance extends MeshVisitor { * i.e., we can get negative distances. * @param asynchronous If {@code true}, then the inspection of mesh facets is asynchronous (and possibly concurrent). * @param parallel If {@code true}, then the algorithm runs concurrently utilizing all CPU cores - * @throws IllegalArgumentException if some parametr is wrong + * @throws IllegalArgumentException if some parameter is wrong */ public HausdorffDistance(MeshFacet mainFacet, Strategy strategy, boolean relativeDistance, boolean asynchronous, boolean parallel) { this(new HashSet<>(Collections.singleton(mainFacet)), strategy, relativeDistance, asynchronous, parallel); @@ -176,7 +187,7 @@ public class HausdorffDistance extends MeshVisitor { * i.e., we can get negative distances. * @param asynchronous If {@code true}, then the inspection of mesh facets is asynchronous (and possibly concurrent). * @param parallel If {@code true}, then the algorithm runs concurrently utilizing all CPU cores - * @throws IllegalArgumentException if some parametr is wrong + * @throws IllegalArgumentException if some parameter is wrong */ public HausdorffDistance(MeshModel mainModel, Strategy strategy, boolean relativeDistance, boolean asynchronous, boolean parallel) { this(new HashSet<>(mainModel.getFacets()), strategy, relativeDistance, asynchronous, parallel); @@ -195,7 +206,7 @@ public class HausdorffDistance extends MeshVisitor { * i.e., we can get negative distances. * @param asynchronous If {@code true}, then the inspection of mesh facets is asynchronous (and possibly concurrent). * @param parallel If {@code true}, then the algorithm runs concurrently utilizing all CPU cores - * @throws IllegalArgumentException if some parametr is wrong + * @throws IllegalArgumentException if some parameter is wrong */ public HausdorffDistance(KdTree mainKdTree, Strategy strategy, boolean relativeDistance, boolean asynchronous, boolean parallel) { super(asynchronous); @@ -205,7 +216,7 @@ public class HausdorffDistance extends MeshVisitor { if (mainKdTree == null) { throw new IllegalArgumentException("mainKdTree"); } - if (strategy == Strategy.POINT_TO_POINT_ABSOLUTE && relativeDistance) { + if (strategy == Strategy.POINT_TO_POINT_DISTANCE_ONLY && relativeDistance) { throw new IllegalArgumentException("Point-to-point strategy cannot be used with relative distance"); } this.kdTree = mainKdTree; @@ -213,9 +224,9 @@ public class HausdorffDistance extends MeshVisitor { /** * Returns Hausdorff distance of mesh facets to the source mesh facets. - * Keys in the map contain mesh facets that were measued with the + * Keys in the map contain mesh facets that were measured with the * source facets. For each measured facet, a list of distances to the source - * facets is stored. The order od distances corresopnds to the order of vertices + * facets is stored. The order of distances corresponds to the order of vertices * in the measured facet, i.e., the i-th value is the distance of the i-th vertex * of the measured facet. * @@ -224,7 +235,20 @@ public class HausdorffDistance extends MeshVisitor { public Map<MeshFacet, List<Double>> getDistances() { return Collections.unmodifiableMap(distances); } - + + /** + * Returns the nearest points of mesh facets to the source mesh facets. + * Keys in the map contain mesh facets that were measured with the + * source facets. For each measured facet, a list of the nearest points to the source + * facets is stored. The order of points corresponds to the order of vertices + * in the measured facet, i.e., the i-th point is the nearest point of the i-th vertex + * of the measured facet. + * + * @return The nearest points for all points of all measured facets + */ + public Map<MeshFacet, List<Vector3d>> getNearestPoints() { + return Collections.unmodifiableMap(nearestPoints); + } /** * Returns {@code true} if the distance was computed as relative * (with possibly negative values). @@ -258,13 +282,19 @@ public class HausdorffDistance extends MeshVisitor { @Override protected void visitMeshFacet(MeshFacet comparedFacet) { - if (distances.containsKey(comparedFacet)) { - return; // multiply visited facet - } - final List<Double> distList = new ArrayList<>(); - distances.put(comparedFacet, distList); + final List<Vector3d> nearestPointsList = new ArrayList<>(); + synchronized (this) { + if (distances.containsKey(comparedFacet)) { + return; // skip repeatedly visited facets + } + distances.put(comparedFacet, distList); + if (getStrategy() != Strategy.POINT_TO_POINT_DISTANCE_ONLY) { + nearestPoints.put(comparedFacet, nearestPointsList); + } + } + final List<MeshPoint> vertices = comparedFacet.getVertices(); final List<Future<KdTreeVisitor>> results = new ArrayList<>(vertices.size()); @@ -277,7 +307,7 @@ public class HausdorffDistance extends MeshVisitor { if (inParallel()) { // fork and continue results.add(executor.submit(visitor)); } else { // process distance results immediately - addDistanceToPoint(visitor, vertices.get(i), distList); + updateResults(visitor, vertices.get(i), comparedFacet); } } @@ -289,7 +319,7 @@ public class HausdorffDistance extends MeshVisitor { int i = 0; for (Future<KdTreeVisitor> res: results) { final KdTreeVisitor visitor = (KdTreeVisitor) res.get(); // waits until all computations are finished - addDistanceToPoint(visitor, vertices.get(i), distList); + updateResults(visitor, vertices.get(i), comparedFacet); i++; } } catch (final InterruptedException | ExecutionException ex) { @@ -300,7 +330,7 @@ public class HausdorffDistance extends MeshVisitor { protected KdTreeVisitor instantiateVisitor(Vector3d inspectedPoint) { switch (getStrategy()) { - case POINT_TO_POINT_ABSOLUTE: + case POINT_TO_POINT_DISTANCE_ONLY: return new KdTreeDistance(inspectedPoint, inParallel()); case POINT_TO_POINT: return new KdTreeDistanceToVertices(inspectedPoint, inParallel()); @@ -309,16 +339,36 @@ public class HausdorffDistance extends MeshVisitor { return new KdTreeApproxDistanceToTriangles(inspectedPoint, inParallel()); } } - - protected void addDistanceToPoint(KdTreeVisitor vis, MeshPoint point, List<Double> pointDistances) { - if (relativeDist && vis instanceof DistanceWithNearestPoints) { - double relDist = checkDist((DistanceWithNearestPoints)vis, point); - pointDistances.add(relDist); - } else { - pointDistances.add(((Distance) vis).getDistance()); + + protected void updateResults(KdTreeVisitor vis, MeshPoint point, MeshFacet facet) { + Vector3d closestV = null; + + if (vis instanceof DistanceWithNearestPoints) { // We have nearest points + closestV = this.getClosestVertex((DistanceWithNearestPoints)vis); + + if (closestV == null) { // unable to get a single closest point + distances.get(facet).add(Double.POSITIVE_INFINITY); + nearestPoints.get(facet).add(null); + return; + } + } + + double dist = ((Distance) vis).getDistance(); + int sign = 1; + + if (relativeDist) { // compute sign for relative distance + Vector3d aux = new Vector3d(closestV); + aux.sub(point.getPosition()); + sign = (int) Math.signum(aux.dot(point.getNormal())); + } + + distances.get(facet).add(sign * dist); + if (nearestPoints.containsKey(facet)) { // only strategies with the closest points are stored in the map + nearestPoints.get(facet).add(closestV); } } + /* protected double checkDist(DistanceWithNearestPoints vis, MeshPoint myPoint) { double dist = vis.getDistance(); @@ -345,4 +395,24 @@ public class HausdorffDistance extends MeshVisitor { return sign * dist; } + */ + + /** + * @param vis visitor + * @return the only one existing closest vertex or {@code null} + */ + protected Vector3d getClosestVertex(DistanceWithNearestPoints vis) { + if (vis.getNearestPoints().size() != 1) { + return null; // somethig is wrong because there should be only my inspected facet + } + + MeshFacet myInspectedFacet = vis.getNearestPoints().keySet().stream().findFirst().get(); + + if (vis.getNearestPoints().get(myInspectedFacet).size() != 1) { + return null; // multiple closest points; we don't know wich one to choose + } + + // return the only one closest vertex + return vis.getNearestPoints().get(myInspectedFacet).get(0); + } } diff --git a/Comparison/src/test/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistanceTest.java b/Comparison/src/test/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistanceTest.java index 44db008a0d3e185e8be5c88fe6020e2e7ffff560..14839231266fe6b716e371678316ff9d3590174e 100644 --- a/Comparison/src/test/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistanceTest.java +++ b/Comparison/src/test/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistanceTest.java @@ -1,17 +1,15 @@ package cz.fidentis.analyst.visitors.mesh; -import cz.fidentis.analyst.mesh.core.CornerTableRow; -import cz.fidentis.analyst.mesh.core.MeshFacet; -import cz.fidentis.analyst.mesh.core.MeshFacetImpl; -import cz.fidentis.analyst.mesh.core.MeshModel; -import cz.fidentis.analyst.mesh.core.MeshPointImpl; +import cz.fidentis.analyst.mesh.core.*; import cz.fidentis.analyst.visitors.mesh.HausdorffDistance.Strategy; +import org.junit.jupiter.api.Test; + +import javax.vecmath.Vector3d; import java.util.List; import java.util.Map; -import javax.vecmath.Vector3d; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; public class HausdorffDistanceTest { @@ -46,6 +44,24 @@ public class HausdorffDistanceTest { assertEquals(expectedDist, results.get(i)); } } + + protected void testNearestPoints(HausdorffDistance vis, MeshModel primaryModel, MeshModel measuredModel) { + MeshFacet measuredFacet = measuredModel.getFacets().get(0); + MeshFacet primaryFacet = primaryModel.getFacets().get(0); + + measuredModel.compute(vis); + Map<MeshFacet, List<Vector3d>> map = vis.getNearestPoints(); + + if(vis.getStrategy() == Strategy.POINT_TO_POINT_DISTANCE_ONLY){ + assertTrue(map.isEmpty()); + } else { + assertTrue(map.containsKey(measuredFacet)); + List<Vector3d> results = map.get(measuredFacet); + for (int i = 0; i < measuredFacet.getNumberOfVertices(); i++) { + assertEquals(primaryFacet.getVertex(i).getPosition(), results.get(i)); + } + } + } @Test @@ -109,4 +125,16 @@ public class HausdorffDistanceTest { testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false, true), firstModel, secondModel, 0.5); } + + @Test + public void correspondentNearestPointTest() { + MeshModel firstModel = getTrivialModel(1, 1); + MeshModel secondModel = getTrivialModel(1, 1); + MeshModel thirdModel = getTrivialModel(1.5, 1); + testNearestPoints(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT_DISTANCE_ONLY, false, false, false), firstModel, secondModel); + testNearestPoints(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, false, false), firstModel, secondModel); + testNearestPoints(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, false, false), firstModel, thirdModel); + testNearestPoints(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false, false), firstModel, secondModel); + } + }