diff --git a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java index 54f54923256c3b9c93ccbd356f56d38e70482b02..65b805f774e65b32f1b2c6c0892c8e2769f6798d 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java @@ -61,7 +61,7 @@ public class HumanFace implements Serializable { private List<FeaturePoint> featurePoints; - private final transient EventBus eventBus = new EventBus(); + private final transient EventBus eventBus; private final String id; @@ -85,6 +85,8 @@ public class HumanFace implements Serializable { BoundingBox visitor = new BoundingBox(); meshModel.compute(visitor); bbox = visitor.getBoundingBox(); + + eventBus = new EventBus(); } /** @@ -123,7 +125,9 @@ public class HumanFace implements Serializable { * @param listener Listener concerned in the human face changes. */ public void registerListener(HumanFaceListener listener) { - eventBus.register(listener); + if (eventBus != null) { // eventBus is null when the class is deserialized! + eventBus.register(listener); + } } /** @@ -132,7 +136,9 @@ public class HumanFace implements Serializable { * @param listener Registered listener */ public void unregisterListener(HumanFaceListener listener) { - eventBus.unregister(listener); + if (eventBus != null) { // eventBus is null when the class is deserialized! + eventBus.unregister(listener); + } } /** @@ -141,7 +147,7 @@ public class HumanFace implements Serializable { * @param evt Event to be triggered. */ public void announceEvent(HumanFaceEvent evt) { - if (evt != null) { + if (evt != null && eventBus != null) { // eventBus is null when the class is deserialized! eventBus.post(evt); } } @@ -274,7 +280,9 @@ public class HumanFace implements Serializable { public KdTree computeKdTree(boolean recompute) { if (kdTree == null || recompute) { kdTree = new KdTree(new ArrayList<>(meshModel.getFacets())); - eventBus.post(new KdTreeCreated(this, this.getShortName(), this)); + if (eventBus != null) { // eventBus is null when the class is deserialized! + eventBus.post(new KdTreeCreated(this, this.getShortName(), this)); + } } return kdTree; } @@ -286,7 +294,9 @@ public class HumanFace implements Serializable { public KdTree removeKdTree() { KdTree ret = this.kdTree; this.kdTree = null; - eventBus.post(new KdTreeDestroyed(this, this.getShortName(), this)); + if (eventBus != null) { // eventBus is null when the class is deserialized! + eventBus.post(new KdTreeDestroyed(this, this.getShortName(), this)); + } return ret; } diff --git a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceFactory.java b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceFactory.java index 2198510572f6393420ed3d8d8e7e0e9db56eb480..df9dd278ee00b8641ea741bd63e9408e80a131e5 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceFactory.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceFactory.java @@ -6,10 +6,6 @@ import java.util.HashMap; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; -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; @@ -25,14 +21,29 @@ import java.util.logging.Logger; * @author Radek Oslejsek */ public class HumanFaceFactory { + + /** + * Memory clean up, i.e. dumping the file into disk and de-referencing it, is + * very fast. Faster real objects removal by JVM. + * Therefore, the {@link #checkMemAndDump()} method removes almost + * {@code MAX_DUMP_FACES} at once. + */ + private static final int MAX_DUMP_FACES = 3; /** * Dumping strategies. * @author Radek Oslejsek */ public enum Strategy { - LRU, // least recently used faces are dumped first - MRU // most recently used faces are dumped first + /** + * least recently used faces are dumped first + */ + LRU, + + /** + * most recently used faces are dumped first + */ + MRU } /** @@ -77,6 +88,8 @@ public class HumanFaceFactory { */ private Strategy strategy = Strategy.LRU; + private boolean reuseDumpFile = false; + /** * Private constructor. Use {@code instance()} method instead to get the instance. @@ -116,6 +129,18 @@ public class HumanFaceFactory { return this.strategy; } + /** + * I set to {@code true}, then the dump file of a face is created only once + * and then reused for every recovery (it never overwritten). + * It accelerates the dumping face, but can be used only if the state of + * faces never changes between the first dump a consequent recoveries. + * + * @param use If {@code true}, then this optimization is turned on. + */ + public void setReuseDumpFile(boolean use) { + this.reuseDumpFile = use; + } + /** * Loads new face. If the face is already loaded, then the ID of existing instance * is returned. To access the human face instance, use {@link getFace}. @@ -134,24 +159,19 @@ public class HumanFaceFactory { return null; } - // Existing face + // Existing face, possibly recovered from a dump file HumanFace face = getFace(faceId); if (face != null) { return faceId; } - // New face -- free the memory and load human face simultaneously: try { - ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - final Future<HumanFace> result = executor.submit(() -> new HumanFace(file)); // read from file - executor.submit(() -> checkMemAndDump()); // free the memory, if necessary - executor.shutdown(); - while (!executor.isTerminated()){} - face = result.get(); - } catch (InterruptedException|ExecutionException ex) { + face = new HumanFace(file); // read from file + checkMemAndDump(); // free the memory, if necessary + } catch (IOException ex) { Logger.getLogger(HumanFaceFactory.class.getName()).log(Level.SEVERE, null, ex); return null; - } + } // Update data structures: long time = System.currentTimeMillis(); @@ -176,28 +196,45 @@ public class HumanFaceFactory { return null; } - // Free memory and recover human face from dump file silultanously: + // Free memory and recover human face from dump file: HumanFace face; try { - ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - final Future<HumanFace> result = executor.submit(() -> HumanFace.restoreFromFile(dumpFile)); // recover face from disk - executor.submit(() -> checkMemAndDump()); // free the memory, if necessary - executor.shutdown(); - while (!executor.isTerminated()){} - face = result.get(); - } catch (InterruptedException|ExecutionException ex) { + face = HumanFace.restoreFromFile(dumpFile); // recover face from disk + checkMemAndDump(); // free the memory, if necessary + //System.out.println(face.getShortName() + " recovered"); + } catch (ClassNotFoundException|IOException ex) { Logger.getLogger(HumanFaceFactory.class.getName()).log(Level.SEVERE, null, ex); return null; } // Update data structures: long time = System.currentTimeMillis(); - dumpedFaces.remove(faceId); + while (inMemoryFaces.containsKey(time)) { // wait until we get unique ms + time = System.currentTimeMillis(); + } + //dumpedFaces.remove(faceId); inMemoryFaces.put(time, face); usage.put(faceId, time); return face; } + /** + * Removes the face from either memory or swap space. To re-initiate the face, + * use {@link #loadFace(java.io.File)} again. + * + * @param faceId Face ID + * @return true if the face existed and was removed. + */ + public boolean removeFace(String faceId) { + if (usage.containsKey(faceId)) { + inMemoryFaces.remove(usage.remove(faceId)); + dumpedFaces.remove(faceId); + return true; // remove from memory; + } + + return dumpedFaces.remove(faceId) != null; + } + @Override public String toString() { StringBuilder ret = new StringBuilder(); @@ -206,7 +243,7 @@ public class HumanFaceFactory { append(formatSize(Runtime.getRuntime().maxMemory())); ret.append("). In memory: "). append(this.inMemoryFaces.size()). - append(", dumped: "). + append(", dump files: "). append(this.dumpedFaces.size()); return ret.toString(); } @@ -242,27 +279,36 @@ public class HumanFaceFactory { * @return true if some existing face has been dumped to free the memory. * @throws IOException on I/O error */ - protected boolean checkMemAndDump() throws IOException { - double ratio = (double) presumableFreeMemory() / Runtime.getRuntime().maxMemory(); - if (ratio > MIN_FREE_MEMORY) { - return false; - } + protected int checkMemAndDump() throws IOException { + int ret = 0; + int counter = 0; + while (counter++ < this.MAX_DUMP_FACES && + (double) presumableFreeMemory() / Runtime.getRuntime().maxMemory() <= MIN_FREE_MEMORY) { + + Long time = null; + switch (strategy) { + case MRU: + time = inMemoryFaces.lastKey(); + break; + default: + case LRU: + time = inMemoryFaces.firstKey(); + break; + } + + if (time == null) { // no face in the memory + break; + } - Long time = null; - switch (strategy) { - case MRU: - time = inMemoryFaces.lastKey(); - break; - default: - case LRU: - time = inMemoryFaces.firstKey(); - break; + HumanFace faceToDump = this.inMemoryFaces.remove(time); + this.usage.remove(faceToDump.getId()); + if (!reuseDumpFile || !dumpedFaces.containsKey(faceToDump.getId())) { // dump only if it's required + dumpedFaces.put(faceToDump.getId(), faceToDump.dumpToFile()); + } + //System.out.println(faceToDump.getShortName() + " dumped"); } - HumanFace faceToDump = this.inMemoryFaces.remove(time); - this.usage.remove(faceToDump.getId()); - dumpedFaces.put(faceToDump.getId(), faceToDump.dumpToFile()); - return true; + return ret; } protected static String formatSize(long v) { diff --git a/Comparison/src/main/java/cz/fidentis/analyst/face/events/HausdorffDistanceComputed.java b/Comparison/src/main/java/cz/fidentis/analyst/face/events/HausdorffDistanceComputed.java index df1c6bbbfedf138a2d3e587ce21d2acd2a476dfa..8e79c7914ec4b79352b889a7cb790288aae69d41 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/face/events/HausdorffDistanceComputed.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/face/events/HausdorffDistanceComputed.java @@ -77,6 +77,7 @@ public class HausdorffDistanceComputed extends HumanFaceEvent { .stream() .flatMap(List::stream) .mapToDouble(Double::doubleValue) + .filter(v -> Double.isFinite(v)) .summaryStatistics(); } @@ -105,6 +106,7 @@ public class HausdorffDistanceComputed extends HumanFaceEvent { .stream() .flatMap(List::stream) .mapToDouble(Double::doubleValue) + .filter(v -> Double.isFinite(v)) .summaryStatistics(); } diff --git a/Comparison/src/main/java/cz/fidentis/analyst/icp/IcpTransformer.java b/Comparison/src/main/java/cz/fidentis/analyst/icp/IcpTransformer.java index c4775d97a97b01f297f58712233425a57a094b0f..5fabe728978125bb0bbbc646ed7397880a05fdeb 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/icp/IcpTransformer.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/icp/IcpTransformer.java @@ -240,7 +240,8 @@ public class IcpTransformer extends MeshVisitor { this.primaryKdTree, HausdorffDistance.Strategy.POINT_TO_POINT, false, // relative distance - true // parallel computation + true, // parallel computation + false // auto cut ); MeshFacet reducedFacet = new UndersampledMeshFacet(transformedFacet, reductionStrategy); diff --git a/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeApproxDistanceToTriangles.java b/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeApproxDistanceToTriangles.java index a1539dd4b459dd1128ae7bf2e08498eac7754ff1..5122a4ea2561ef7369ffb10d66a003bedac0888c 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeApproxDistanceToTriangles.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeApproxDistanceToTriangles.java @@ -15,7 +15,7 @@ import java.util.Set; import javax.vecmath.Point3d; /** - * This visitor finds the minimal distance between the given 3D point and triangular + * This visitor finds the minimal distance between a given 3D point and triangular * meshes with vertices stored in a k-d tree. * <p> * The minimal distance is computed between the 3D point and triangles of the mesh facets, @@ -32,7 +32,7 @@ import javax.vecmath.Point3d; * explores triangles around the vertex to find the closest triangle. It is more * optimal than computing the distance to all triangles, but it works only for rather * flat surfaces. It is okay for models of human faces where the "wrinkly" parts - * are considered as noise and we aim to remove them from further processing. + * are considered noise and we aim to remove them from further processing anyway. * </p> * <p> * This visitor is thread-safe, i.e., a single instance of the visitor can be used @@ -46,16 +46,25 @@ public class KdTreeApproxDistanceToTriangles extends KdTreeVisitor implements Di private double distance = Double.POSITIVE_INFINITY; private final Point3d point3d; private Map<MeshFacet, List<Point3d>> nearestPoints = new HashMap<>(); + private Map<MeshFacet, List<Integer>> nearestVertices = new HashMap<>(); + private final boolean checkOverlay; /** + * Constructor. + * * @param point A 3D point from which distance is computed. Must not be {@code null} + * @param checkOverlay If {@code true} and and closest point to the given 3D reference point + * lies on the boundary of the mesh stored in the k-d tree, + * then the reference point is considered "outside" of the mesh + * and the distance is set to infinity. * @throws IllegalArgumentException if some parameter is wrong */ - public KdTreeApproxDistanceToTriangles(Point3d point) { + public KdTreeApproxDistanceToTriangles(Point3d point, boolean checkOverlay) { if (point == null) { throw new IllegalArgumentException("point"); } this.point3d = point; + this.checkOverlay = checkOverlay; } @Override @@ -65,6 +74,18 @@ public class KdTreeApproxDistanceToTriangles extends KdTreeVisitor implements Di @Override public double getDistance() { + if (!checkOverlay || distance == Double.POSITIVE_INFINITY) { + return distance; + } + + for (MeshFacet f: nearestVertices.keySet()) { + for (Integer i: nearestVertices.get(f)) { + if (f.getOneRingNeighborhood(i).isBoundary()) { + return Double.POSITIVE_INFINITY; + } + } + } + return distance; } @@ -95,7 +116,9 @@ public class KdTreeApproxDistanceToTriangles extends KdTreeVisitor implements Di nearestPoints.clear(); } nearestPoints.computeIfAbsent(facet, meshFacet -> new ArrayList<>()) - .add(projection); + .add(projection); + nearestVertices.computeIfAbsent(facet, n -> new ArrayList<>()) + .add(vIndex); } } } diff --git a/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeClosestNode.java b/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeClosestNode.java index f5ccb8fbcac9d844b060c118ff1fe9eba99a2c16..30ee0599eeaff139ded00748d70f0330b34c4463 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeClosestNode.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeClosestNode.java @@ -30,6 +30,8 @@ public class KdTreeClosestNode extends KdTreeVisitor { private final Set<KdNode> closest = new HashSet<>(); /** + * Constructor. + * * @param point The 3D location for which the closest nodes are to be computed. * @throws IllegalArgumentException if some parameter is wrong */ 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 ab48a42c6dd51c7ff161eba8501109b2d189ff77..51b0f63349fb2106346271bd3131c4a301f226ee 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 @@ -17,13 +17,15 @@ import javax.vecmath.Point3d; * @author Daniel Schramm * @author Radek Oslejsek */ +@Deprecated public class KdTreeDistance extends KdTreeVisitor implements Distance { private double distance = Double.POSITIVE_INFINITY; - private Point3d point3d; + private final Point3d point3d; /** * Constructor. + * * @param point A 3D point from which distance is computed. Must not be {@code null} * @throws IllegalArgumentException if some parameter is wrong */ @@ -33,8 +35,12 @@ public class KdTreeDistance extends KdTreeVisitor implements Distance { } this.point3d = point; } - - @Override + + /** + * Returns the minimal found distance + * + * @return the minimal found distance + */ public double getDistance() { return distance; } @@ -50,5 +56,4 @@ public class KdTreeDistance extends KdTreeVisitor implements Distance { } } } - } diff --git a/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeDistanceToVertices.java b/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeDistanceToVertices.java index 2f8594cba8b8eac151483250077c634ddd230cdc..daaa434f3e628020054f12a64defc116e3b22ffd 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeDistanceToVertices.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/visitors/kdtree/KdTreeDistanceToVertices.java @@ -4,10 +4,8 @@ import cz.fidentis.analyst.kdtree.KdNode; import cz.fidentis.analyst.kdtree.KdTree; import cz.fidentis.analyst.kdtree.KdTreeVisitor; import cz.fidentis.analyst.mesh.core.MeshFacet; -import cz.fidentis.analyst.mesh.core.MeshPoint; import cz.fidentis.analyst.visitors.DistanceWithNearestPoints; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,29 +29,56 @@ public class KdTreeDistanceToVertices extends KdTreeVisitor implements DistanceW private double distance = Double.POSITIVE_INFINITY; private final Point3d point3d; - private Map<MeshFacet, List<Point3d>> nearestPoints = new HashMap<>(); + private Map<MeshFacet, List<Integer>> nearestVertices = new HashMap<>(); + private final boolean checkOverlay; /** + * Constructor. + * * @param point A 3D point from which distance is computed. Must not be {@code null} + * @param checkOverlay If {@code true} and and closest point to the given 3D reference point + * lies on the boundary of the mesh stored in the k-d tree, + * then the reference point is considered "outside" of the mesh + * and the distance is set to infinity. * @throws IllegalArgumentException if some parameter is wrong */ - public KdTreeDistanceToVertices(Point3d point) { + public KdTreeDistanceToVertices(Point3d point, boolean checkOverlay) { if (point == null) { throw new IllegalArgumentException("point"); } this.point3d = point; + this.checkOverlay = checkOverlay; } @Override public Map<MeshFacet, List<Point3d>> getNearestPoints() { - return Collections.unmodifiableMap(nearestPoints); + Map<MeshFacet, List<Point3d>> ret = new HashMap<>(); + nearestVertices.keySet().forEach(f -> { + nearestVertices.get(f).forEach(i -> { + ret.computeIfAbsent(f, n -> new ArrayList<>()) + .add(f.getVertex(i).getPosition()); + }); + }); + return ret; } - + @Override public double getDistance() { + if (!checkOverlay || distance == Double.POSITIVE_INFINITY) { + return distance; + } + + for (MeshFacet f: nearestVertices.keySet()) { + for (Integer i: nearestVertices.get(f)) { + if (f.getOneRingNeighborhood(i).isBoundary()) { + return Double.POSITIVE_INFINITY; + } + } + } + return distance; } - + @Override public void visitKdTree(KdTree kdTree) { final KdTreeClosestNode visitor = new KdTreeClosestNode(point3d); @@ -64,7 +89,7 @@ public class KdTreeDistanceToVertices extends KdTreeVisitor implements DistanceW synchronized (this) { if (dist < distance) { distance = dist; - nearestPoints.clear(); + nearestVertices.clear(); } } @@ -75,9 +100,8 @@ public class KdTreeDistanceToVertices extends KdTreeVisitor implements DistanceW } for (Entry<MeshFacet, Integer> entry: node.getFacets().entrySet()) { MeshFacet facet = entry.getKey(); - MeshPoint point = facet.getVertex(entry.getValue()); - nearestPoints.computeIfAbsent(facet, meshFacet -> new ArrayList<>()) - .add(point.getPosition()); + nearestVertices.computeIfAbsent(facet, n -> new ArrayList<>()) + .add(entry.getValue()); } } } diff --git a/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/BoundingBox.java b/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/BoundingBox.java index a1d5efe5d9e6d2e5bc3ac4f70f5cb8a4f271fcc8..5bfa197cc70d50c693b7c0b9e37123e50933a48a 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/BoundingBox.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/BoundingBox.java @@ -3,6 +3,7 @@ package cz.fidentis.analyst.visitors.mesh; import cz.fidentis.analyst.mesh.MeshVisitor; import cz.fidentis.analyst.mesh.core.MeshFacet; import cz.fidentis.analyst.mesh.core.MeshPoint; +import java.io.Serializable; import java.util.List; import javax.vecmath.Point3d; import javax.vecmath.Vector3d; @@ -15,7 +16,7 @@ import javax.vecmath.Vector3d; * * @author Radek Oslejsek */ -public class BoundingBox extends MeshVisitor { +public class BoundingBox extends MeshVisitor implements Serializable { private BBox bbox; @@ -42,7 +43,7 @@ public class BoundingBox extends MeshVisitor { * * @author Natalia Bebjakova */ - public class BBox { + public class BBox implements Serializable { private Point3d maxPoint; private Point3d minPoint; 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 283eff9345f620618674e6675914d42c2b0c57f5..d877c569436d9064fc9bf8ce6d3e4b31473aad52 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 @@ -1,5 +1,6 @@ package cz.fidentis.analyst.visitors.mesh; +import cz.fidentis.analyst.kdtree.KdNode; import cz.fidentis.analyst.kdtree.KdTree; import cz.fidentis.analyst.kdtree.KdTreeVisitor; import cz.fidentis.analyst.mesh.MeshVisitor; @@ -9,6 +10,7 @@ import cz.fidentis.analyst.mesh.core.MeshPoint; import cz.fidentis.analyst.visitors.Distance; import cz.fidentis.analyst.visitors.DistanceWithNearestPoints; import cz.fidentis.analyst.visitors.kdtree.KdTreeApproxDistanceToTriangles; +import cz.fidentis.analyst.visitors.kdtree.KdTreeClosestNode; import cz.fidentis.analyst.visitors.kdtree.KdTreeDistance; import cz.fidentis.analyst.visitors.kdtree.KdTreeDistanceToVertices; import java.util.ArrayList; @@ -53,7 +55,7 @@ import javax.vecmath.Point3d; * <p> * Hausdorff distance sequentially: * <table> - * <tr><td>2443 msec</td><td>OINT_TO_POINT_DISTANCE_ONLY</td></tr> + * <tr><td>2443 msec</td><td>POINT_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> @@ -61,7 +63,7 @@ import javax.vecmath.Point3d; * <p> * Hausdorff distance concurrently: * <table> - * <tr><td>1782 msec</td><td>OINT_TO_POINT_DISTANCE_ONLY</td></tr> + * <tr><td>1782 msec</td><td>POINT_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> @@ -131,6 +133,8 @@ import javax.vecmath.Point3d; private final boolean parallel; + private final boolean crop; + /** * The source triangular mesh (set of mesh facets) stored in k-d tree. */ @@ -145,9 +149,14 @@ import javax.vecmath.Point3d; * to the normal vectors of source facets (normal vectors have to present), * i.e., we can get negative distances. * @param parallel If {@code true}, then the algorithm runs concurrently utilizing all CPU cores + * @param crop If {@code true}, then only parts of the visited secondary faces that overlay the primary face are + * taken into account. Parts (vertices) that are out of the surface of the primary face are ignored + * (their distance is set to {@code NaN}). + * This feature makes the Hausdorff distance computation more symmetric. + * This optimization cannot be used for POINT_TO_POINT_DISTANCE_ONLY strategy * @throws IllegalArgumentException if some parameter is wrong */ - public HausdorffDistance(Set<MeshFacet> mainFacets, Strategy strategy, boolean relativeDistance, boolean parallel) { + public HausdorffDistance(Set<MeshFacet> mainFacets, Strategy strategy, boolean relativeDistance, boolean parallel, boolean crop) { this.strategy = strategy; this.relativeDist = relativeDistance; this.parallel = parallel; @@ -155,9 +164,13 @@ import javax.vecmath.Point3d; throw new IllegalArgumentException("mainFacets"); } if (strategy == Strategy.POINT_TO_POINT_DISTANCE_ONLY && relativeDistance) { - throw new IllegalArgumentException("Point-to-point strategy cannot be used with relative distance"); + throw new IllegalArgumentException("POINT_TO_POINT_DISTANCE_ONLY strategy cannot be used with relative distance"); + } + if (strategy == Strategy.POINT_TO_POINT_DISTANCE_ONLY && crop) { + throw new IllegalArgumentException("POINT_TO_POINT_DISTANCE_ONLY strategy cannot be used with auto cutting parameter"); } this.kdTree = new KdTree(new ArrayList<>(mainFacets)); + this.crop = crop; } /** @@ -169,10 +182,15 @@ import javax.vecmath.Point3d; * to the normal vectors of source facets (normal vectors have to present), * i.e., we can get negative distances. * @param parallel If {@code true}, then the algorithm runs concurrently utilizing all CPU cores + * @param crop If {@code true}, then only parts of the visited secondary faces that overlay the primary face are + * taken into account. Parts (vertices) that are out of the surface of the primary face are ignored + * (their distance is set to {@code NaN}). + * This feature makes the Hausdorff distance computation more symmetric. + * This optimization cannot be used for POINT_TO_POINT_DISTANCE_ONLY strategy * @throws IllegalArgumentException if some parameter is wrong */ - public HausdorffDistance(MeshFacet mainFacet, Strategy strategy, boolean relativeDistance, boolean parallel) { - this(new HashSet<>(Collections.singleton(mainFacet)), strategy, relativeDistance, parallel); + public HausdorffDistance(MeshFacet mainFacet, Strategy strategy, boolean relativeDistance, boolean parallel, boolean crop) { + this(new HashSet<>(Collections.singleton(mainFacet)), strategy, relativeDistance, parallel, crop); if (mainFacet == null) { throw new IllegalArgumentException("mainFacet"); } @@ -188,10 +206,15 @@ import javax.vecmath.Point3d; * to the normal vectors of source facets (normal vectors have to present), * i.e., we can get negative distances. * @param parallel If {@code true}, then the algorithm runs concurrently utilizing all CPU cores + * @param crop If {@code true}, then only parts of the visited secondary faces that overlay the primary face are + * taken into account. Parts (vertices) that are out of the surface of the primary face are ignored + * (their distance is set to {@code NaN}). + * This feature makes the Hausdorff distance computation more symmetric. + * This optimization cannot be used for POINT_TO_POINT_DISTANCE_ONLY strategy * @throws IllegalArgumentException if some parameter is wrong */ - public HausdorffDistance(MeshModel mainModel, Strategy strategy, boolean relativeDistance, boolean parallel) { - this(new HashSet<>(mainModel.getFacets()), strategy, relativeDistance, parallel); + public HausdorffDistance(MeshModel mainModel, Strategy strategy, boolean relativeDistance, boolean parallel, boolean crop) { + this(new HashSet<>(mainModel.getFacets()), strategy, relativeDistance, parallel, crop); if (mainModel.getFacets().isEmpty()) { throw new IllegalArgumentException("mainModel"); } @@ -206,9 +229,14 @@ import javax.vecmath.Point3d; * to the normal vectors of source facets (normal vectors have to present), * i.e., we can get negative distances. * @param parallel If {@code true}, then the algorithm runs concurrently utilizing all CPU cores + * @param crop If {@code true}, then only parts of the visited secondary faces that overlay the primary face are + * taken into account. Parts (vertices) that are out of the surface of the primary face are ignored + * (their distance is set to {@code NaN}). + * This feature makes the Hausdorff distance computation more symmetric. + * This optimization cannot be used for POINT_TO_POINT_DISTANCE_ONLY strategy * @throws IllegalArgumentException if some parameter is wrong */ - public HausdorffDistance(KdTree mainKdTree, Strategy strategy, boolean relativeDistance, boolean parallel) { + public HausdorffDistance(KdTree mainKdTree, Strategy strategy, boolean relativeDistance, boolean parallel, boolean crop) { this.strategy = strategy; this.relativeDist = relativeDistance; this.parallel = parallel; @@ -216,9 +244,13 @@ import javax.vecmath.Point3d; throw new IllegalArgumentException("mainKdTree"); } if (strategy == Strategy.POINT_TO_POINT_DISTANCE_ONLY && relativeDistance) { - throw new IllegalArgumentException("Point-to-point strategy cannot be used with relative distance"); + throw new IllegalArgumentException("POINT_TO_POINT_DISTANCE_ONLY strategy cannot be used with relative distance"); + } + if (strategy == Strategy.POINT_TO_POINT_DISTANCE_ONLY && crop) { + throw new IllegalArgumentException("POINT_TO_POINT_DISTANCE_ONLY strategy cannot be used with auto cutting parameter"); } this.kdTree = mainKdTree; + this.crop = crop; } /** @@ -293,6 +325,7 @@ import javax.vecmath.Point3d; .stream() .flatMap(List::stream) .mapToDouble(Double::doubleValue) + .filter(v -> Double.isFinite(v)) .summaryStatistics(); } @@ -347,10 +380,10 @@ import javax.vecmath.Point3d; case POINT_TO_POINT_DISTANCE_ONLY: return new KdTreeDistance(inspectedPoint); case POINT_TO_POINT: - return new KdTreeDistanceToVertices(inspectedPoint); + return new KdTreeDistanceToVertices(inspectedPoint, crop); default: case POINT_TO_TRIANGLE_APPROXIMATE: - return new KdTreeApproxDistanceToTriangles(inspectedPoint); + return new KdTreeApproxDistanceToTriangles(inspectedPoint, crop); } } @@ -401,6 +434,22 @@ import javax.vecmath.Point3d; return vis.getNearestPoints().get(myInspectedFacet).get(0); } + /* + protected boolean isOnBoundary(Point3d point3d) { + KdTreeClosestNode vis = new KdTreeClosestNode(point3d); + this.kdTree.accept(vis); + for (KdNode node: vis.getClosestNodes()) { + for (MeshFacet facet: node.getFacets().keySet()) { + int vIndex = node.getFacets().get(facet); + if (facet.getOneRingNeighborhood(vIndex).isBoundary()) { + return true; + } + } + } + return false; + } + */ + /** * Helper call for asynchronous invocation of visitors. * diff --git a/Comparison/src/test/java/cz/fidentis/analyst/visitors/face/HausdorffDistancePrioritizedTest.java b/Comparison/src/test/java/cz/fidentis/analyst/visitors/face/HausdorffDistancePrioritizedTest.java index 49229055ec89e63e0aed8fae5b64eb12b9efb440..e6204b567d9cbe2834403dfd91d6897a1bb8249d 100644 --- a/Comparison/src/test/java/cz/fidentis/analyst/visitors/face/HausdorffDistancePrioritizedTest.java +++ b/Comparison/src/test/java/cz/fidentis/analyst/visitors/face/HausdorffDistancePrioritizedTest.java @@ -227,7 +227,7 @@ public class HausdorffDistancePrioritizedTest { } final HausdorffDistancePrioritized visitor = new HausdorffDistancePrioritized( - new HausdorffDistance(getHumanFace(facets2, List.of(), List.of()).getMeshModel(), POINT_TO_POINT, true, false), + new HausdorffDistance(getHumanFace(facets2, List.of(), List.of()).getMeshModel(), POINT_TO_POINT, true, false, false), featurePointTypes); performTest(face, expectedPriorities, expectedFeaturePointsWeights, expectedMergedPrioritiesMap, visitor); @@ -262,7 +262,7 @@ public class HausdorffDistancePrioritizedTest { } final HausdorffDistancePrioritized visitor = new HausdorffDistancePrioritized( - new HausdorffDistance(getHumanFace(facets2, List.of(), List.of()).getMeshModel(), POINT_TO_POINT, false, false), + new HausdorffDistance(getHumanFace(facets2, List.of(), List.of()).getMeshModel(), POINT_TO_POINT, false, false, false), featurePointTypes); performTest(face, expectedPriorities, expectedFeaturePointsWeights, expectedMergedPrioritiesMap, visitor); @@ -306,7 +306,7 @@ public class HausdorffDistancePrioritizedTest { expectedMergedPrioritiesMap.put(facet1, List.of(1d, 0.9, 0.8, 0.7, 0.6, 0.5, 0.6, 0.7, 0.8, 0.9, 1d, 0.9)); final HausdorffDistancePrioritized visitor = new HausdorffDistancePrioritized( - new HausdorffDistance(getHumanFace(facets2, List.of(), List.of()).getMeshModel(), POINT_TO_POINT, true, false), + new HausdorffDistance(getHumanFace(facets2, List.of(), List.of()).getMeshModel(), POINT_TO_POINT, true, false, false), featurePointTypes); performTest(face, expectedPriorities, expectedFeaturePointsWeights, expectedMergedPrioritiesMap, visitor); @@ -370,7 +370,7 @@ public class HausdorffDistancePrioritizedTest { expectedMergedPrioritiesMap.put(facet5, mergedPriorities); final HausdorffDistancePrioritized visitor = new HausdorffDistancePrioritized( - new HausdorffDistance(getHumanFace(facets2, List.of(), List.of()).getMeshModel(), POINT_TO_POINT, true, false), + new HausdorffDistance(getHumanFace(facets2, List.of(), List.of()).getMeshModel(), POINT_TO_POINT, true, false, false), featurePointTypes); performTest(face, expectedPriorities, expectedFeaturePointsWeights, expectedMergedPrioritiesMap, visitor); @@ -433,7 +433,7 @@ public class HausdorffDistancePrioritizedTest { expectedMergedPrioritiesMap.put(facet4, mergedPriorities); final HausdorffDistancePrioritized visitor = new HausdorffDistancePrioritized( - new HausdorffDistance(getHumanFace(facets2, List.of(), List.of()).getMeshModel(), POINT_TO_POINT, true, false), + new HausdorffDistance(getHumanFace(facets2, List.of(), List.of()).getMeshModel(), POINT_TO_POINT, true, false, false), featurePointTypes); performTest(face, expectedPriorities, expectedFeaturePointsWeights, expectedMergedPrioritiesMap, visitor); @@ -497,7 +497,7 @@ public class HausdorffDistancePrioritizedTest { expectedMergedPrioritiesMap.put(facet5, mergedPriorities); final HausdorffDistancePrioritized visitor = new HausdorffDistancePrioritized( - new HausdorffDistance(getHumanFace(facets2, List.of(), List.of()).getMeshModel(), POINT_TO_POINT, true, true), + new HausdorffDistance(getHumanFace(facets2, List.of(), List.of()).getMeshModel(), POINT_TO_POINT, true, true, false), featurePointTypes); performTest(face, expectedPriorities, expectedFeaturePointsWeights, expectedMergedPrioritiesMap, visitor); diff --git a/Comparison/src/test/java/cz/fidentis/analyst/visitors/kdtree/KdTreeApproxDistToTriTest.java b/Comparison/src/test/java/cz/fidentis/analyst/visitors/kdtree/KdTreeApproxDistToTriTest.java index 1151119ee965afe396dcb75cc6e3bc92bc6a18b6..6f75b170422364e8dea44b369ec4890e09ec4f27 100644 --- a/Comparison/src/test/java/cz/fidentis/analyst/visitors/kdtree/KdTreeApproxDistToTriTest.java +++ b/Comparison/src/test/java/cz/fidentis/analyst/visitors/kdtree/KdTreeApproxDistToTriTest.java @@ -33,13 +33,13 @@ public class KdTreeApproxDistToTriTest { @Test public void distTest() { - KdTreeApproxDistanceToTriangles vis = new KdTreeApproxDistanceToTriangles(new Point3d(0,0,0)); // sequentially + KdTreeApproxDistanceToTriangles vis = new KdTreeApproxDistanceToTriangles(new Point3d(0,0,0), false); // sequentially distTest(1.5, vis); } @Test public void exactMatchTest() { - KdTreeApproxDistanceToTriangles vis = new KdTreeApproxDistanceToTriangles(new Point3d(0, 0, 1.5)); // relative dist, sequentially + KdTreeApproxDistanceToTriangles vis = new KdTreeApproxDistanceToTriangles(new Point3d(0, 0, 1.5), false); // relative dist, sequentially distTest(0, vis); } } diff --git a/Comparison/src/test/java/cz/fidentis/analyst/visitors/kdtree/KdTreeDistanceToVerticesTest.java b/Comparison/src/test/java/cz/fidentis/analyst/visitors/kdtree/KdTreeDistanceToVerticesTest.java index 09740fb2c600e02b56e832d5d9ab363d8e77a2c0..f4e5a4b4773d31fd3cc8c4bbe7b1ae59935efe9a 100644 --- a/Comparison/src/test/java/cz/fidentis/analyst/visitors/kdtree/KdTreeDistanceToVerticesTest.java +++ b/Comparison/src/test/java/cz/fidentis/analyst/visitors/kdtree/KdTreeDistanceToVerticesTest.java @@ -34,14 +34,14 @@ public class KdTreeDistanceToVerticesTest { @Test public void distTest() { - KdTreeDistanceToVertices vis = new KdTreeDistanceToVertices(new Point3d(0,0,0)); // sequentially + KdTreeDistanceToVertices vis = new KdTreeDistanceToVertices(new Point3d(0,0,0), false); // sequentially distTest(1.5, vis); } @Test public void exactMatchTest() { MeshPoint point = new MeshPointImpl(new Point3d(0, 0, 1.5), new Vector3d(0, 0, 1), new Vector3d()); - KdTreeDistanceToVertices vis = new KdTreeDistanceToVertices(new Point3d(0, 0, 1.5)); // relative dist, sequentially + KdTreeDistanceToVertices vis = new KdTreeDistanceToVertices(new Point3d(0, 0, 1.5), false); // relative dist, sequentially distTest(0, vis); } } 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 dc114435700f688fa33799081a2a6b7d6308210c..cd04a576af7367d2494380eb44d3179173fc5e37 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 @@ -69,46 +69,46 @@ public class HausdorffDistanceTest { public void visitToVerticesTest() { MeshModel firstModel = getTrivialModel(1, 1); MeshModel secondModel = getTrivialModel(1.5, 1); - testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, false), firstModel, secondModel, 0.5); - testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_POINT, false, false), secondModel, firstModel, 0.5); - testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false), firstModel, secondModel, 0.5); - testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false), secondModel, firstModel, 0.5); + testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, false, false), firstModel, secondModel, 0.5); + testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_POINT, false, false, false), secondModel, firstModel, 0.5); + testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false, false), firstModel, secondModel, 0.5); + testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false, false), secondModel, firstModel, 0.5); } @Test public void visitToVerticesBehindMeshTest() { MeshModel firstModel = getTrivialModel(1, 1); MeshModel secondModel = getTrivialModel(-1.5, 1); - testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, false), firstModel, secondModel, 2.5); - testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_POINT, false, false), secondModel, firstModel, 2.5); - testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false), firstModel, secondModel, 2.5); - testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false), secondModel, firstModel, 2.5); + testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, false, false), firstModel, secondModel, 2.5); + testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_POINT, false, false, false), secondModel, firstModel, 2.5); + testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false, false), firstModel, secondModel, 2.5); + testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false, false), secondModel, firstModel, 2.5); } @Test public void visitToVerticesRelativeDistanceTest() { MeshModel firstModel = getTrivialModel(1, 1); MeshModel secondModel = getTrivialModel(1.5, 1); - testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, true, false), firstModel, secondModel, -0.5); - testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, true, false), secondModel, firstModel, 0.5); + testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, true, false, false), firstModel, secondModel, -0.5); + testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, true, false, false), secondModel, firstModel, 0.5); } @Test public void visitToVerticesBehindMeshRelativeDistanceTest() { MeshModel firstModel = getTrivialModel(1, 1); MeshModel secondModel = getTrivialModel(-1.5, 1); - testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, true, false), firstModel, secondModel, 2.5); - testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, true, false), secondModel, firstModel, -2.5); + testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, true, false, false), firstModel, secondModel, 2.5); + testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, true, false, false), secondModel, firstModel, -2.5); } @Test public void concurrencyTest() { MeshModel firstModel = getTrivialModel(1, 1); MeshModel secondModel = getTrivialModel(1.5, 1); - testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, true), firstModel, secondModel, 0.5); - testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_POINT, false, true), secondModel, firstModel, 0.5); - testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, true), firstModel, secondModel, 0.5); - testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, true), secondModel, firstModel, 0.5); + testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, true, false), firstModel, secondModel, 0.5); + testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_POINT, false, true, false), secondModel, firstModel, 0.5); + testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, true, false), firstModel, secondModel, 0.5); + testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, true, false), secondModel, firstModel, 0.5); } @Test @@ -118,8 +118,8 @@ public class HausdorffDistanceTest { firstModel.addFacet(getTrivialFacet(4, 1)); MeshModel secondModel = getTrivialModel(1.5, 1); - testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, true), firstModel, secondModel, 0.5); - testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, true), firstModel, secondModel, 0.5); + testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, true, false), firstModel, secondModel, 0.5); + testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, true, false), firstModel, secondModel, 0.5); } @@ -128,10 +128,10 @@ public class HausdorffDistanceTest { 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), firstModel, secondModel); - testNearestPoints(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, false), firstModel, secondModel); - testNearestPoints(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, false), firstModel, thirdModel); - testNearestPoints(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false), firstModel, secondModel); + 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); } } diff --git a/GUI/src/main/java/cz/fidentis/analyst/distance/DistanceAction.java b/GUI/src/main/java/cz/fidentis/analyst/distance/DistanceAction.java index f1c070aee34b55b269eebe7513883c6073281ee8..1c13b9dc58653ed4f7097d2fb08e5d30ba1d8498 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/distance/DistanceAction.java +++ b/GUI/src/main/java/cz/fidentis/analyst/distance/DistanceAction.java @@ -20,6 +20,7 @@ import java.awt.event.ActionEvent; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JTabbedPane; import javax.swing.JToggleButton; @@ -61,6 +62,7 @@ public class DistanceAction extends ControlPanelAction implements HumanFaceListe private boolean relativeDist = false; private String heatmapDisplayed = DistancePanel.SEL_STD_HAUSDORFF_DISTANCE; private String weightedFPsShow = DistancePanel.SEL_SHOW_NO_SPHERES; + private boolean crop = false; private FeaturePointType hoveredFeaturePoint = null; @@ -132,18 +134,18 @@ public class DistanceAction extends ControlPanelAction implements HumanFaceListe break; case DistancePanel.ACTION_COMMAND_SET_DISTANCE_STRATEGY: strategy = (String) ((JComboBox) ae.getSource()).getSelectedItem(); - computeAndUpdateHausdorffDistance(); + computeAndUpdateHausdorffDistance(true); break; case DistancePanel.ACTION_COMMAND_RELATIVE_ABSOLUTE_DIST: this.relativeDist = ((String) ((JComboBox) ae.getSource()).getSelectedItem()) .equals(DistancePanel.SEL_RELATIVE_DISTANCE); - computeAndUpdateHausdorffDistance(); + computeAndUpdateHausdorffDistance(true); break; case FeaturePointsPanel.ACTION_COMMAND_FEATURE_POINT_SELECT: selectFeaturePoint((LoadedActionEvent) ae); setVisibleSpheres(); highlightSelectedSpheres(); - computeAndUpdateHausdorffDistance(); + computeAndUpdateHausdorffDistance(false); break; case DistancePanel.ACTION_COMMAND_FEATURE_POINT_SELECT_ALL: for (int i = 0; i < featurePoints.size(); i++) { @@ -152,20 +154,20 @@ public class DistanceAction extends ControlPanelAction implements HumanFaceListe controlPanel.getFeaturePointsListPanel().selectAllFeaturePoints(true); setVisibleSpheres(); highlightSelectedSpheres(); - computeAndUpdateHausdorffDistance(); + computeAndUpdateHausdorffDistance(false); break; case DistancePanel.ACTION_COMMAND_FEATURE_POINT_SELECT_NONE: featurePointTypes.clear(); controlPanel.getFeaturePointsListPanel().selectAllFeaturePoints(false); setVisibleSpheres(); highlightSelectedSpheres(); - computeAndUpdateHausdorffDistance(); + computeAndUpdateHausdorffDistance(false); break; case DistancePanel.ACTION_COMMAND_FEATURE_POINT_SELECT_AUTO: for (int i = 0; i < featurePoints.size(); i++) { // select all featurePointTypes.put(featurePoints.get(i).getFeaturePointType(), fpSpheres.getSize(i)); } - calculateHausdorffDistance(featurePointTypes); + calculateHausdorffDistance(featurePointTypes, false); selectSignificantFPs(); for (int i = 0; i < featurePoints.size(); i++) { // update the check list controlPanel.getFeaturePointsListPanel().selectFeaturePoint( @@ -175,7 +177,11 @@ public class DistanceAction extends ControlPanelAction implements HumanFaceListe } setVisibleSpheres(); highlightSelectedSpheres(); - computeAndUpdateHausdorffDistance(); + computeAndUpdateHausdorffDistance(false); + break; + case DistancePanel.ACTION_COMMAND_HD_AUTO_CROP: + this.crop = ((JCheckBox) ae.getSource()).isSelected(); + computeAndUpdateHausdorffDistance(true); break; case FeaturePointsPanel.ACTION_COMMAND_FEATURE_POINT_HOVER_IN: hoverFeaturePoint((LoadedActionEvent) ae, true); @@ -187,7 +193,7 @@ public class DistanceAction extends ControlPanelAction implements HumanFaceListe resizeFeaturePoint((LoadedActionEvent) ae); break; case FeaturePointsPanel.ACTION_COMMAND_DISTANCE_RECOMPUTE: - computeAndUpdateHausdorffDistance(); + computeAndUpdateHausdorffDistance(true); break; default: // to nothing @@ -200,7 +206,7 @@ public class DistanceAction extends ControlPanelAction implements HumanFaceListe if (event instanceof MeshChangedEvent && event.getIssuer() != this) { // recompte (W)HD hdVisitor = null; whdVisitor = null; - computeAndUpdateHausdorffDistance(); + computeAndUpdateHausdorffDistance(true); // Relocate weight speheres: final List<FeaturePoint> secondaryFPs = getSecondaryFeaturePoints().getFeaturePoints(); final List<FeaturePoint> weightedFPs = fpSpheres.getFeaturePoints(); @@ -220,8 +226,8 @@ public class DistanceAction extends ControlPanelAction implements HumanFaceListe * Recalculates the Hausdorff distance and updates all GUI elements * of {@link DistancePanel}. */ - private void computeAndUpdateHausdorffDistance() { - calculateHausdorffDistance(featurePointTypes); + private void computeAndUpdateHausdorffDistance(boolean recomputeHD) { + calculateHausdorffDistance(featurePointTypes, recomputeHD); setFeaturePointWeigths(); // Updates GUI elements that display the calculated Hausdorff distance metrics updateHeatmap(); } @@ -256,7 +262,7 @@ public class DistanceAction extends ControlPanelAction implements HumanFaceListe * points of the corresponding type. * Must not be {@code null}. */ - private void calculateHausdorffDistance(Map<FeaturePointType, Double> featurePoints) { + private void calculateHausdorffDistance(Map<FeaturePointType, Double> featurePoints, boolean recomputeHD) { final Strategy useStrategy; switch (strategy) { case DistancePanel.SEL_STRATEGY_POINT_TO_POINT: @@ -269,9 +275,15 @@ public class DistanceAction extends ControlPanelAction implements HumanFaceListe throw new UnsupportedOperationException(strategy); } - if (hdVisitor == null) { + if (hdVisitor == null || recomputeHD) { getPrimaryDrawableFace().getHumanFace().computeKdTree(false); // recompute if does not exist - hdVisitor = new HausdorffDistance(getPrimaryDrawableFace().getHumanFace().getKdTree(), useStrategy, relativeDist, true); + hdVisitor = new HausdorffDistance( + getPrimaryDrawableFace().getHumanFace().getKdTree(), + useStrategy, + relativeDist, + true, + crop + ); } whdVisitor = new HausdorffDistancePrioritized(hdVisitor, featurePoints); diff --git a/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.form b/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.form index e51e857d682fa025e33c2a8bdc4fa9142882e56f..1c20f1877f70d23d91677847a3d819bf687230ed 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.form +++ b/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.form @@ -83,14 +83,18 @@ </Group> <EmptySpace max="-2" attributes="0"/> <Group type="103" groupAlignment="0" attributes="0"> - <Group type="102" attributes="0"> + <Group type="102" alignment="0" attributes="0"> <Component id="jLabel3" min="-2" max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/> <Component id="jComboBox4" min="-2" max="-2" attributes="0"/> </Group> - <Component id="jComboBox3" alignment="0" min="-2" max="-2" attributes="0"/> + <Group type="102" alignment="0" attributes="0"> + <Component id="jComboBox3" min="-2" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="jCheckBox1" min="-2" max="-2" attributes="0"/> + </Group> </Group> - <EmptySpace min="-2" pref="102" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> </Group> </Group> </DimensionLayout> @@ -102,6 +106,7 @@ <Component id="jComboBox2" alignment="3" min="-2" max="-2" attributes="0"/> <Component id="jComboBox3" alignment="3" min="-2" max="-2" attributes="0"/> <Component id="jLabel5" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="jCheckBox1" alignment="3" min="-2" max="-2" attributes="0"/> </Group> <EmptySpace min="-2" pref="15" max="-2" attributes="0"/> <Group type="103" groupAlignment="3" attributes="0"> @@ -191,6 +196,13 @@ </Property> </Properties> </Component> + <Component class="javax.swing.JCheckBox" name="jCheckBox1"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="cz/fidentis/analyst/distance/Bundle.properties" key="DistancePanel.jCheckBox1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + </Component> </SubComponents> </Container> <Container class="javax.swing.JScrollPane" name="jScrollPane1"> diff --git a/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.java b/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.java index 2a8f87c144e74d2a7f25c7d9ddefdcc065338db9..31b8980cd0f9402fe819384100761e2a2e882b85 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.java +++ b/GUI/src/main/java/cz/fidentis/analyst/distance/DistancePanel.java @@ -47,6 +47,7 @@ public class DistancePanel extends ControlPanel { public static final String ACTION_COMMAND_FEATURE_POINT_SELECT_ALL = "select all feature points"; public static final String ACTION_COMMAND_FEATURE_POINT_SELECT_NONE = "deselect all feature points"; public static final String ACTION_COMMAND_FEATURE_POINT_SELECT_AUTO = "Select significant feature points"; + public static final String ACTION_COMMAND_HD_AUTO_CROP = "Crop"; /** * Constructor. @@ -118,6 +119,8 @@ public class DistancePanel extends ControlPanel { jButton1.addActionListener(createListener(action, ACTION_COMMAND_FEATURE_POINT_SELECT_ALL)); jButton2.addActionListener(createListener(action, ACTION_COMMAND_FEATURE_POINT_SELECT_NONE)); jButton3.addActionListener(createListener(action, ACTION_COMMAND_FEATURE_POINT_SELECT_AUTO)); + + jCheckBox1.addActionListener(createListener(action, ACTION_COMMAND_HD_AUTO_CROP)); } /** @@ -170,6 +173,7 @@ public class DistancePanel extends ControlPanel { jLabel3 = new javax.swing.JLabel(); jLabel4 = new javax.swing.JLabel(); jLabel5 = new javax.swing.JLabel(); + jCheckBox1 = new javax.swing.JCheckBox(); jScrollPane1 = new javax.swing.JScrollPane(); featurePointsPanel1 = new cz.fidentis.analyst.distance.FeaturePointsPanel(); jPanel2 = new javax.swing.JPanel(); @@ -200,6 +204,8 @@ public class DistancePanel extends ControlPanel { org.openide.awt.Mnemonics.setLocalizedText(jLabel5, org.openide.util.NbBundle.getMessage(DistancePanel.class, "DistancePanel.jLabel5.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jCheckBox1, org.openide.util.NbBundle.getMessage(DistancePanel.class, "DistancePanel.jCheckBox1.text")); // NOI18N + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( @@ -219,8 +225,11 @@ public class DistancePanel extends ControlPanel { .addComponent(jLabel3) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jComboBox4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(jComboBox3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(102, 102, 102)) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(jComboBox3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jCheckBox1))) + .addContainerGap()) ); jPanel1Layout.setVerticalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -229,7 +238,8 @@ public class DistancePanel extends ControlPanel { .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jComboBox2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jComboBox3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jLabel5)) + .addComponent(jLabel5) + .addComponent(jCheckBox1)) .addGap(15, 15, 15) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel3) @@ -345,6 +355,7 @@ public class DistancePanel extends ControlPanel { private javax.swing.JButton jButton1; private javax.swing.JButton jButton2; private javax.swing.JButton jButton3; + private javax.swing.JCheckBox jCheckBox1; private javax.swing.JComboBox<String> jComboBox1; private javax.swing.JComboBox<String> jComboBox2; private javax.swing.JComboBox<String> jComboBox3; diff --git a/GUI/src/main/java/cz/fidentis/analyst/scene/DrawableFace.java b/GUI/src/main/java/cz/fidentis/analyst/scene/DrawableFace.java index a49c59f31809e815a58d54d55fe325f5f5c2048b..28307ee24f8efe10b4e8788a91704abed0e9e4e5 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/scene/DrawableFace.java +++ b/GUI/src/main/java/cz/fidentis/analyst/scene/DrawableFace.java @@ -1,6 +1,7 @@ package cz.fidentis.analyst.scene; import com.jogamp.opengl.GL2; +import cz.fidentis.analyst.Logger; import cz.fidentis.analyst.face.HumanFace; import cz.fidentis.analyst.mesh.core.MeshFacet; import java.awt.Color; diff --git a/GUI/src/main/java/cz/fidentis/analyst/scene/HeatmapRenderer.java b/GUI/src/main/java/cz/fidentis/analyst/scene/HeatmapRenderer.java index 015692b2bf614d9ec54f6a6d660f0032f7cae946..e112a874237943050feeceaf31b3faf0d5e425f4 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/scene/HeatmapRenderer.java +++ b/GUI/src/main/java/cz/fidentis/analyst/scene/HeatmapRenderer.java @@ -17,8 +17,8 @@ import javax.vecmath.Vector3d; */ public class HeatmapRenderer { - private Color minColor = Color.RED; - private Color maxColor = Color.BLUE; + private Color minColor = Color.BLUE; + private Color maxColor = Color.RED; public void setMinColor(Color color) { minColor = color; @@ -35,8 +35,11 @@ public class HeatmapRenderer { * @param distances Distances in mesh model vertices * @param saturation Saturation of colors at mesh model vertices */ - public void drawMeshModel(GL2 gl, MeshModel model, - Map<MeshFacet, List<Double>> distances, Map<MeshFacet, List<Double>> saturation) { + public void drawMeshModel( + GL2 gl, MeshModel model, + Map<MeshFacet, List<Double>> distances, + Map<MeshFacet, List<Double>> saturation) { + double minDistance = Double.POSITIVE_INFINITY; double maxDistance = Double.NEGATIVE_INFINITY; @@ -47,6 +50,7 @@ public class HeatmapRenderer { minDistance, distanceList.parallelStream() .mapToDouble(Double::doubleValue) + .filter(v -> Double.isFinite(v)) .min() .getAsDouble() ); @@ -54,6 +58,7 @@ public class HeatmapRenderer { maxDistance, distanceList.parallelStream() .mapToDouble(Double::doubleValue) + .filter(v -> Double.isFinite(v)) .max() .getAsDouble() ); @@ -76,8 +81,14 @@ public class HeatmapRenderer { * @param minDistance Minimal distance threshold (smaller distances are cut-off) * @param maxDistance Maxim distance threshold (bigger distances are cut-off) */ - public void renderMeshFacet(GL2 gl, MeshFacet facet, List<Double> distancesList, - List<Double> saturationList, double minDistance, double maxDistance) { + public void renderMeshFacet( + GL2 gl, + MeshFacet facet, + List<Double> distancesList, + List<Double> saturationList, + double minDistance, + double maxDistance) { + gl.glBegin(GL2.GL_TRIANGLES); //vertices are rendered as triangles // get the normal and tex coords indicies for face i @@ -96,7 +107,9 @@ public class HeatmapRenderer { //get color of vertex double currentDistance = distancesList.get(vertexIndex); double currentSaturation = saturationList == null ? 1d : saturationList.get(vertexIndex); - Color c = getColor(currentDistance, currentSaturation, minDistance, maxDistance); + Color c = Double.isFinite(currentDistance) ? + getColor(currentDistance, currentSaturation, minDistance, maxDistance) : + Color.BLACK; gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, c.getComponents(null), 0); gl.glVertex3d(vert.x, vert.y, vert.z); diff --git a/GUI/src/main/java/cz/fidentis/analyst/symmetry/SymmetryAction.java b/GUI/src/main/java/cz/fidentis/analyst/symmetry/SymmetryAction.java index 81599d7b530d280517d00b9738667d2a1651909f..feb0d0d2d9aeecc2198f101d526ac483234f663d 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/symmetry/SymmetryAction.java +++ b/GUI/src/main/java/cz/fidentis/analyst/symmetry/SymmetryAction.java @@ -83,7 +83,7 @@ public class SymmetryAction extends ControlPanelAction implements HumanFaceListe sPlane = 2; recomputeFromFeaturePoints(0); recomputeFromFeaturePoints(1); - break; + break; default: // do nothing } @@ -187,7 +187,13 @@ public class SymmetryAction extends ControlPanelAction implements HumanFaceListe } face.computeKdTree(false); - HausdorffDistance visitor = new HausdorffDistance(face.getKdTree(), Strategy.POINT_TO_POINT_DISTANCE_ONLY, false, true); + HausdorffDistance visitor = new HausdorffDistance( + face.getKdTree(), + Strategy.POINT_TO_POINT_DISTANCE_ONLY, + false, + true, + false + ); clone.compute(visitor); controlPanel.updateHausdorffDistanceStats(visitor.getStats(), getScene().getHumanFace(0) == face); } diff --git a/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityApproxHausdorff.java b/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityApproxHausdorff.java index 32b5bf5b44d4aadc275b7fedf2e06cb2ef093b1c..7cc8988b4220d79f87e5aed318cecbd92856f6d2 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityApproxHausdorff.java +++ b/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityApproxHausdorff.java @@ -64,7 +64,7 @@ public class BatchSimilarityApproxHausdorff { 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); + HausdorffDistance hd = new HausdorffDistance(secFace.getKdTree(), HausdorffDistance.Strategy.POINT_TO_POINT, true, true, true); priFace.getMeshModel().compute(hd, true); //System.out.println(hd.getDistances().get(priFace.getMeshModel().getFacets().get(0)).size()); diff --git a/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityGroundTruth.java b/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityGroundTruth.java index 85852531a2c7d4875628088ed84550b918393dd3..d6c43c62d5431ca7486f4d681f20e64a746154c9 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityGroundTruth.java +++ b/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityGroundTruth.java @@ -12,18 +12,29 @@ 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. + * <ul> + * <li>All pairs of faces are taken one by one from the collection.</li> + * <li>ICP registration is performed for each pair, but only in one direction (the second to the first)</li> + * <li>Hausdorff distance for each pair is computed in both direction (A-B and B-A). HD uses POINT_TO_POINT strategy and absolute distances.</li> + * </ul> + * Stats for 100 faces WITH CROP: + * <pre> + * ICP computation time: 02:27:32,170 + * HD computation time: 02:08:34,001 + * Total computation time: 05:29:55,321 + * </pre> + * Stats for 100 faces WITHOUT CROP: + * <pre> + * ICP computation time: 02:23:06,495 + * HD computation time: 01:59:55,520 + * Total computation time: 05:16:46,154 + * </pre> * * @author Radek Oslejsek */ @@ -32,6 +43,7 @@ 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; + private static final boolean CROP_HD = false; /** * Main method @@ -45,48 +57,78 @@ public class BatchSimilarityGroundTruth { .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(); + double[][] distances = new double[faces.size()][faces.size()]; + String[] names = new String[faces.size()]; + + long totalTime = System.currentTimeMillis(); + long icpComputationTime = 0; + long hdComputationTime = 0; - int counter = 1; + int counter = 0; for (int i = 0; i < faces.size(); i++) { HumanFace priFace = new HumanFace(faces.get(i).toFile()); - priFace.computeKdTree(false); + names[i] = priFace.getShortName().replaceAll("_01_ECA", ""); - for (int j = 0; j < faces.size(); j++) { - if (i == j) { - continue; - } - + for (int j = i; j < faces.size(); j++) { // starts with "i"! HumanFace secFace = new HumanFace(faces.get(j).toFile()); - System.out.println(counter++ + "/" + (faces.size()*faces.size()-faces.size()) + " (" + priFace.getShortName() + "/" + secFace.getShortName() + ")"); + System.out.println(++counter + ": " + 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); + // transform secondary face + if (i != j) { // register only different faces + long icpTime = System.currentTimeMillis(); + IcpTransformer icp = new IcpTransformer(priFace.getMeshModel(), 100, false, 0.3, new NoUndersampling()); + secFace.getMeshModel().compute(icp, true); + icpComputationTime += System.currentTimeMillis() - icpTime; + } long hdTime = System.currentTimeMillis(); - HausdorffDistance hd = new HausdorffDistance(priFace.getKdTree(), Strategy.POINT_TO_POINT, false, true); + // compute HD from secondary to primary: + priFace.computeKdTree(true); + HausdorffDistance hd = new HausdorffDistance(priFace.getKdTree(), Strategy.POINT_TO_POINT, false, true, CROP_HD); 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(); + distances[j][i] = hd.getStats().getAverage(); + // compute HD from primary to secondary: + secFace.computeKdTree(true); + hd = new HausdorffDistance(secFace.getKdTree(), Strategy.POINT_TO_POINT, false, true, CROP_HD); + priFace.getMeshModel().compute(hd, true); + distances[i][j] = hd.getStats().getAverage(); + hdComputationTime += System.currentTimeMillis() - hdTime; } } - bfw.close(); + BufferedWriter w = new BufferedWriter(new FileWriter(OUTPUT_FILE)); + w.write("PRI FACE;SEC FACE;AVG HD from PRI to SEC;AVG HD from SEC to PRI;AVG HD lower; AVG HD higher"); + w.newLine(); + for (int i = 0; i < faces.size(); i++) { + for (int j = i; j < faces.size(); j++) { + w.write(names[i] + ";"); + w.write(names[j] + ";"); + w.write(String.format("%.8f", distances[i][j]) + ";"); + w.write(String.format("%.8f", distances[j][i]) + ";"); + if (distances[i][j] > distances[j][i]) { + w.write(String.format("%.8f", distances[i][j]) + ";"); + w.write(String.format("%.8f", distances[j][i]) + ""); + } else { + w.write(String.format("%.8f", distances[j][i]) + ";"); + w.write(String.format("%.8f", distances[i][j]) + ""); + } + w.newLine(); + } + } + w.close(); + + System.out.println(); + Duration duration = Duration.ofMillis(icpComputationTime); + System.out.println("ICP computation time: " + + String.format("%02d:%02d:%02d,%03d", duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart(), duration.toMillisPart())); + duration = Duration.ofMillis(hdComputationTime); + System.out.println("HD computation time: " + + String.format("%02d:%02d:%02d,%03d", duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart(), duration.toMillisPart())); + duration = Duration.ofMillis(System.currentTimeMillis() - totalTime); + System.out.println("Total computation time: " + + String.format("%02d:%02d:%02d,%03d", duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart(), duration.toMillisPart())); + } } diff --git a/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityGroundTruthOpt.java b/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityGroundTruthOpt.java new file mode 100644 index 0000000000000000000000000000000000000000..20afd4b44965ae9b6823d1656600eeea615a07db --- /dev/null +++ b/GUI/src/main/java/cz/fidentis/analyst/tests/BatchSimilarityGroundTruthOpt.java @@ -0,0 +1,168 @@ +package cz.fidentis.analyst.tests; + +import cz.fidentis.analyst.face.AvgFaceConstructor; +import cz.fidentis.analyst.face.HumanFace; +import cz.fidentis.analyst.face.HumanFaceFactory; +import cz.fidentis.analyst.icp.IcpTransformer; +import cz.fidentis.analyst.icp.NoUndersampling; +import cz.fidentis.analyst.mesh.io.MeshObjExporter; +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.List; +import java.util.stream.Collectors; + +/** + * A class for testing the efficiency of batch processing algorithms. + * It works in the same way as {@link BatchSimilarityGroundTruth} with the following changes: + * <ul> + * <li>All faces are transformed (ICP) to the very first face of the collection. They are not transformed mutually. + * It enables us to compute ICP only once for every face, but with possible loss of precision. + * Moreover, {@code HumanFaceFactory} is used to quickly load/swap faces when used multiple times.</li> + * <ul> + * Stats for 100 faces WITH CROP: + * <pre> + * Time of AVG face computation time: 00:00:19,529 + * ICP computation time: 00:05:19,096 + * HD computation time: 03:17:29,446 + * Total computation time: 03:32:30,671 + * </pre> + * Stats for 100 faces WITHOUT CROP: + * <pre> + * Time of AVG face computation time: 00:00:19,386 + * ICP computation time: 00:05:39,318 + * HD computation time: 03:11:49,226 + * Total computation time: 03:25:50,957 + * </pre> + * + * @author Radek Oslejsek + */ +public class BatchSimilarityGroundTruthOpt { + + private static final String DATA_DIR = "../../analyst-data-antropologie/_ECA"; + private static final String OUTPUT_FILE = "../../SIMILARITY_GROUND_TRUTH_OPT.csv"; + private static final String TEMPLATE_FACE_PATH = "../../analyst-data-antropologie/template_face.obj"; + private static final int MAX_SAMPLES = 100; + private static final boolean CROP_HD = false; + + /** + * 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()); + + double[][] distances = new double[faces.size()][faces.size()]; + String[] names = new String[faces.size()]; + + long totalTime = System.currentTimeMillis(); + long avgFaceComputationTime = 0; + long icpComputationTime = 0; + long hdComputationTime = 0; + + AvgFaceConstructor avgFaceConstructor = new AvgFaceConstructor(new HumanFace(faces.get(0).toFile()).getMeshModel()); + + HumanFaceFactory.instance().setReuseDumpFile(true); + HumanFaceFactory.instance().setStrategy(HumanFaceFactory.Strategy.MRU); + + int counter = 0; + for (int i = 0; i < faces.size(); i++) { + String priFaceId = HumanFaceFactory.instance().loadFace(faces.get(i).toFile()); + HumanFace priFace = HumanFaceFactory.instance().getFace(priFaceId); + names[i] = priFace.getShortName().replaceAll("_01_ECA", ""); + + for (int j = i; j < faces.size(); j++) { // starts with "i"! + priFace = HumanFaceFactory.instance().getFace(priFaceId); // re-read if dumped, at leat update the access time + + String secFaceId = HumanFaceFactory.instance().loadFace(faces.get(j).toFile()); + HumanFace secFace = HumanFaceFactory.instance().getFace(secFaceId); + + System.out.print(++counter + ": " + priFace.getShortName() + " - " + secFace.getShortName()); + + // transform secondary face, but only once. Transformed faces are stored in the HumanFaceFactory! Don't transform the same face + if (i == 0 && i != j) { + System.out.print(", ICP"); + long icpTime = System.currentTimeMillis(); + IcpTransformer icp = new IcpTransformer(priFace.getMeshModel(), 100, true, 0.3, new NoUndersampling()); + secFace.getMeshModel().compute(icp, true); + icpComputationTime += System.currentTimeMillis() - icpTime; + } + + long hdTime = System.currentTimeMillis(); + // compute HD from secondary to primary: + priFace.computeKdTree(true); + HausdorffDistance hd = new HausdorffDistance(priFace.getKdTree(), Strategy.POINT_TO_POINT, false, true, CROP_HD); + secFace.getMeshModel().compute(hd, true); + distances[j][i] = hd.getStats().getAverage(); + // compute HD from primary to secondary: + secFace.computeKdTree(true); + hd = new HausdorffDistance(secFace.getKdTree(), Strategy.POINT_TO_POINT, false, true, CROP_HD); + priFace.getMeshModel().compute(hd, true); + distances[i][j] = hd.getStats().getAverage(); + hdComputationTime += System.currentTimeMillis() - hdTime; + + // Compute AVG face. Use each tranfromed face only once. Skip the very first face + if (i == 0 && j != 0) { + System.out.print(", AVG"); + long avgFaceTime = System.currentTimeMillis(); + priFace.getKdTree().accept(avgFaceConstructor); + avgFaceComputationTime += System.currentTimeMillis() - avgFaceTime; + } + + System.out.println(", " + HumanFaceFactory.instance().toString()); + } + + HumanFaceFactory.instance().removeFace(priFaceId); // the face is no longer needed + } + + MeshObjExporter exp = new MeshObjExporter(avgFaceConstructor.getAveragedMeshModel()); + exp.exportModelToObj(new File(TEMPLATE_FACE_PATH)); + + BufferedWriter w = new BufferedWriter(new FileWriter(OUTPUT_FILE)); + w.write("PRI FACE;SEC FACE;AVG HD from PRI to SEC;AVG HD from SEC to PRI"); + w.newLine(); + for (int i = 0; i < faces.size(); i++) { + for (int j = i; j < faces.size(); j++) { + w.write(names[i] + ";"); + w.write(names[j] + ";"); + w.write(String.format("%.8f", distances[i][j]) + ";"); + w.write(String.format("%.8f", distances[j][i]) + ";"); + if (distances[i][j] > distances[j][i]) { + w.write(String.format("%.8f", distances[i][j]) + ";"); + w.write(String.format("%.8f", distances[j][i]) + ""); + } else { + w.write(String.format("%.8f", distances[j][i]) + ";"); + w.write(String.format("%.8f", distances[i][j]) + ""); + } + w.newLine(); + } + } + w.close(); + + System.out.println(); + Duration duration = Duration.ofMillis(avgFaceComputationTime); + System.out.println("Time of AVG face computation time: " + + String.format("%02d:%02d:%02d,%03d", duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart(), duration.toMillisPart())); + duration = Duration.ofMillis(icpComputationTime); + System.out.println("ICP computation time: " + + String.format("%02d:%02d:%02d,%03d", duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart(), duration.toMillisPart())); + duration = Duration.ofMillis(hdComputationTime); + System.out.println("HD computation time: " + + String.format("%02d:%02d:%02d,%03d", duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart(), duration.toMillisPart())); + duration = Duration.ofMillis(System.currentTimeMillis() - totalTime); + System.out.println("Total computation time: " + + String.format("%02d:%02d:%02d,%03d", duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart(), duration.toMillisPart())); + } + +} diff --git a/GUI/src/main/java/cz/fidentis/analyst/tests/EfficiencyTests.java b/GUI/src/main/java/cz/fidentis/analyst/tests/EfficiencyTests.java index 3d3fc9a06236ade1b8fc77c24f063a650fe6158c..1de0990da9bbac754363edc8dcae0e3b62aa528d 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/tests/EfficiencyTests.java +++ b/GUI/src/main/java/cz/fidentis/analyst/tests/EfficiencyTests.java @@ -63,16 +63,16 @@ public class EfficiencyTests { System.out.println(); System.out.println("Hausdorff distance sequentially:"); - testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT, relativeDist, false), printDetails); - testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT_DISTANCE_ONLY, false, false), printDetails); - testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT, relativeDist, false), printDetails); - testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_TRIANGLE_APPROXIMATE, relativeDist, 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_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 concurrently:"); - testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT_DISTANCE_ONLY, false, true), printDetails); - testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT, relativeDist, true), printDetails); - testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_TRIANGLE_APPROXIMATE, relativeDist, true), printDetails); + testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT_DISTANCE_ONLY, false, true, false), printDetails); + testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_POINT, relativeDist, true, false), printDetails); + testAndPrint(face1, new HausdorffDistance(face2.getMeshModel(), Strategy.POINT_TO_TRIANGLE_APPROXIMATE, relativeDist, true, false), printDetails); } protected static void testAndPrint(HumanFace face, HausdorffDistance vis, boolean printDetails) { diff --git a/GUI/src/main/resources/cz/fidentis/analyst/distance/Bundle.properties b/GUI/src/main/resources/cz/fidentis/analyst/distance/Bundle.properties index ca75b48c570b1dec8dc57b115265bbd56de05fec..09e41c8dda811818341d9a4357e25a4bf223b952 100644 --- a/GUI/src/main/resources/cz/fidentis/analyst/distance/Bundle.properties +++ b/GUI/src/main/resources/cz/fidentis/analyst/distance/Bundle.properties @@ -12,3 +12,4 @@ DistancePanel.jLabel3.text=Spheres: DistancePanel.jPanel2.border.title=Measurements: DistancePanel.jLabel4.text=Show heatmap: DistancePanel.jLabel5.text=Algorithm options: +DistancePanel.jCheckBox1.text=crop