From 9e8fa5977134299da210264f9f5e9a82d1509587 Mon Sep 17 00:00:00 2001 From: Radek Oslejsek <oslejsek@fi.muni.cz> Date: Sun, 21 Mar 2021 12:17:19 +0100 Subject: [PATCH] Final refactoring of the SymmetryEstimator --- .gitignore | 3 +- .../cz/fidentis/analyst/EfficiencyTests.java | 13 +- .../cz/fidentis/analyst/symmetry/Config.java | 5 +- .../analyst/symmetry/SymmetryEstimator.java | 230 +++++++++--------- .../visitors/mesh/MaxCurvatureVisitor.java | 6 +- .../fidentis/analyst/gui/SymmetryPanel.java | 28 ++- .../analyst/mesh/core/MeshFacetImpl.java | 2 +- preferences.fip | 1 - 8 files changed, 151 insertions(+), 137 deletions(-) delete mode 100644 preferences.fip diff --git a/.gitignore b/.gitignore index c62467a9..91b0b71f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ **/target -# From directories, ignore... +# From files and directories, ignore... +preferences.fip ### Idea: *.iml diff --git a/Comparison/src/main/java/cz/fidentis/analyst/EfficiencyTests.java b/Comparison/src/main/java/cz/fidentis/analyst/EfficiencyTests.java index c4de9f35..ee4253ac 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/EfficiencyTests.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/EfficiencyTests.java @@ -43,8 +43,10 @@ public class EfficiencyTests { System.out.println(measureCurvature(face1, new GaussianCurvatureVisitor(false), false) + "\tmsec:\tGaussian curvature"); System.out.println(measureCurvature(face1, new MaxCurvatureVisitor(false), false) + "\tmsec:\tMax curvature"); - System.out.println(measureSymmetryPlane(face1, true) + "\tmsec:\tSymmetry plane"); + System.out.println(measureSymmetryPlane(face1, true, false) + "\tmsec:\tSymmetry plane with Gaussian curvature"); + System.out.println(measureSymmetryPlane(face1, true, true) + "\tmsec:\tSymmetry plane with MAX curvature"); + System.out.println(); System.out.println(measureKdTreeCreation(face1) + "\tmsec:\tKd-tree creation of first face"); System.out.println(measureKdTreeCreation(face2) + "\tmsec:\tKd-tree creation of second face"); @@ -91,14 +93,15 @@ public class EfficiencyTests { return System.currentTimeMillis() - startTime; } - private static long measureSymmetryPlane(HumanFace face, boolean printDetails) { + private static long measureSymmetryPlane(HumanFace face, boolean printDetails, boolean maxCurvatureAlg) { long startTime = System.currentTimeMillis(); - SymmetryEstimator est = new SymmetryEstimator(face.getMeshModel().getFacets().get(0), Config.getDefault()); - Plane plane = est.getApproxSymmetryPlane(null); + SymmetryEstimator est = new SymmetryEstimator(face.getMeshModel().getFacets().get(0), Config.getDefault(), maxCurvatureAlg); + est.calculateSymmetryPlane(); + Plane plane = est.getSymmetryPlane(); long retTime = System.currentTimeMillis() - startTime; if (printDetails) { - System.out.println(plane.getNormal() + ", " + plane.getDistance()); + System.out.println("Symmetry plane: " + plane.getNormal() + ", " + plane.getDistance()); } return retTime; diff --git a/Comparison/src/main/java/cz/fidentis/analyst/symmetry/Config.java b/Comparison/src/main/java/cz/fidentis/analyst/symmetry/Config.java index ed08d5aa..22af1f4e 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/symmetry/Config.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/symmetry/Config.java @@ -1,13 +1,12 @@ package cz.fidentis.analyst.symmetry; /** - * - * @author Natália Bebjaková - * * Representation of configuration for symmetry estimate. * Default numbers are given due to the best results on tested data. * On many different 3D models, it exists other values of config that will have * better impact on results in estimate of symmetry. + * + * @author Natalia Bebjakova */ public class Config { private static final double DEFAULT_MIN_CURV_RATIO = 0.5; diff --git a/Comparison/src/main/java/cz/fidentis/analyst/symmetry/SymmetryEstimator.java b/Comparison/src/main/java/cz/fidentis/analyst/symmetry/SymmetryEstimator.java index c11b9552..3ed02cce 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/symmetry/SymmetryEstimator.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/symmetry/SymmetryEstimator.java @@ -1,17 +1,15 @@ package cz.fidentis.analyst.symmetry; -import cz.fidentis.analyst.mesh.core.CornerTableRow; import cz.fidentis.analyst.mesh.core.MeshFacet; -import cz.fidentis.analyst.visitors.mesh.BoundingBox; import cz.fidentis.analyst.mesh.core.MeshFacetImpl; import cz.fidentis.analyst.mesh.core.MeshPointImpl; -import cz.fidentis.analyst.mesh.core.MeshTriangle; +import cz.fidentis.analyst.visitors.mesh.BoundingBox; import cz.fidentis.analyst.visitors.mesh.BoundingBoxVisitor; import cz.fidentis.analyst.visitors.mesh.CurvatureVisitor; import cz.fidentis.analyst.visitors.mesh.GaussianCurvatureVisitor; import cz.fidentis.analyst.visitors.mesh.MaxCurvatureVisitor; -import cz.fidentis.analyst.visitors.mesh.TriangleListVisitor; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -20,50 +18,52 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; -import javax.swing.JPanel; import javax.vecmath.Vector3d; /** * Main class for computing approximate plane of symmetry of the 3D model. - * For computing the symmetry, for every * Default values of the configuration are given due to the best results on tested objects. * On many different 3D models, it exists other values of config that will have better impact on result. + * <p> + * Note: this algorithm can be implemented as a mesh visitor because it requires + * complete mesh to compute the symmetry plane. Sequential application on + * multiple meshes does not work. + * </p> * * @author Natalia Bebjakova * @author Radek Oslejsek */ public class SymmetryEstimator { - private final MeshFacet facet; // Facet of the model on which symmetry is computed - - private List<Vector3d> areas; // Representation for areas of Voronoi region of triangles - - private BoundingBox boundingBox; // Represent min-max box. It is automatically maintained by given point array. - + private final MeshFacet facet; private final Config config; + private final boolean maxCurvatureAlg; + + // results: + private List<Double> curvatures; + private BoundingBox boundingBox; + private Plane symmetryPlane; /** * Constructor. * - * @param f facet on which the symmetry will be computed - * @param config configuration of optional parameters of the algorithm + * @param facet Mesh facet for which the symmetry plane is calculated + * @param config Algorighm options + * @param maxCurvatureAlg If {@code true}, then more precise but slower {@link MaxCurvatureVisitor} + * algorithm is used. Otherwise, the faster {@link GaussianCurvatureVisitor} is used. + * See {@link CurvatureVisitor} for more details. + * @throws IllegalArgumentException if some input paramter is missing */ - public SymmetryEstimator(MeshFacet f, Config config) { - this.facet = f; - this.config = config; - - this.areas = new ArrayList<>(f.getNumTriangles()); - int i = 0; - for (MeshTriangle tri: f) { - areas.add(tri.getVoronoiPoint()); + public SymmetryEstimator(MeshFacet facet, Config config, boolean maxCurvatureAlg) { + if (facet == null) { + throw new IllegalArgumentException("facet"); } - - TriangleListVisitor vis = new TriangleListVisitor(false); - f.accept(vis); - - BoundingBoxVisitor visitor = new BoundingBoxVisitor(false); - facet.accept(visitor); - boundingBox = visitor.getBoundingBox(); + if (config == null) { + throw new IllegalArgumentException("config"); + } + this.facet = facet; + this.config = config; + this.maxCurvatureAlg = maxCurvatureAlg; } /** @@ -73,39 +73,22 @@ public class SymmetryEstimator { * @param b B * @param scale Scale */ + /* protected SymmetryEstimator(Vector3d centroid, Vector3d a, Vector3d b, double scale) { this(createMeshFacet(centroid, a, b, scale), Config.getDefault()); } + */ /** - * - * @return configuration of optional parameters of the algorithm - */ - public Config getConfig() { - return config; - } - - /** - * - * @return Facet of the model on which symmetry is computed - */ - public MeshFacet getFacet() { - return facet; - } - - /** - * Computes the approximate plane of symmetry. - * TO DO: ZRUSIT VSTUPNI PARAMETR!!! - * - * @param panel parrent window for the progress window (can be null) - * @return approximate plane of symmtetry + * Calculates the symmetry plane. */ - public Plane getApproxSymmetryPlane(JPanel panel) { + public void calculateSymmetryPlane() { + if (!facet.hasVertexNormals()) { facet.calculateVertexNormals(); } - List<Double> curvatures = calculateCurvatures(facet); + curvatures = calculateCurvatures(facet, maxCurvatureAlg); final SignificantPoints sigPoints = new SignificantPoints(curvatures, config.getSignificantPointCount()); // Initiate structure for concurrent computation: @@ -113,7 +96,7 @@ public class SymmetryEstimator { final List<Future<ApproxSymmetryPlane>> results = new ArrayList<>(sigPoints.size()*sigPoints.size()); // Compute candidate planes concurrently: - final double maxDistance = boundingBox.getMaxDiag() * config.getMaxRelDistance(); + final double maxDistance = calculateMaxRelativeDistance(facet, config); for (int i = 0; i < sigPoints.size(); i++) { for (int j = 0; j < sigPoints.size(); j++) { int finalI = i; @@ -160,48 +143,38 @@ public class SymmetryEstimator { Logger.getLogger(SymmetryEstimator.class.getName()).log(Level.SEVERE, null, ex); } - // Compute the average plane, if required... if (config.isAveraging() && !planes.isEmpty()) { - return new Plane(planes); + symmetryPlane = new Plane(planes); + } else { + symmetryPlane = planes.isEmpty() ? null : planes.get(0); } - - // ... or return the random best-fitting plane: - return planes.isEmpty() ? null : planes.get(0); } - private List<Double> calculateCurvatures(MeshFacet facet) { - CurvatureVisitor vis; - if (facet.getNumberOfVertices() < 2500) { - // searching for Maximum curvature in vertex using Gaussian curvature - // and Mean curvature leads to longer calculation but better results for real face models - //curvatures.add(getMaxCurvature(i)); - vis = new MaxCurvatureVisitor(false); - } else { - //curvatures.add(getGaussianCurvature(i)); - vis = new GaussianCurvatureVisitor(false); - } - facet.accept(vis); - List<Double> curvatures = new ArrayList<>(vis.getCurvatures().get(facet)); - for (int i = 0; i < curvatures.size(); i++) { - if (Double.isNaN(curvatures.get(i))) { - curvatures.set(i, Double.MIN_VALUE); // !!!! - } - } - return curvatures; + /** + * Returns the symmetry plane computed by the {@link SymmetryEstimator#calculateSymmetryPlane()} method. + * @return the symmetry plane or {@code null} + */ + public Plane getSymmetryPlane() { + return symmetryPlane; } /** + * COmputes and returns a triangular mesh for the symmetry plane (a rectangle). + * Its size is restricted by the bounding box of the original mesh facet. * - * @param plane Plane computed as symmetry plane - * @return mesh that represents facet with computed plane of approximate symmetry + * @return mesh facet of the symmetry plane or {@code null} */ - public SymmetryEstimator mergeWithPlane(Plane plane) { - Vector3d normal = plane.getNormal(); + public MeshFacet getSymmetryPlaneMesh() { + if (symmetryPlane == null) { + return null; + } + + Vector3d normal = symmetryPlane.getNormal(); Vector3d midPoint = boundingBox.getMidPoint().getPosition(); double alpha = -((normal.x * midPoint.x) + (normal.y * midPoint.y) + (normal.z * midPoint.z) + - plane.getDistance()) / (normal.dot(normal)); + symmetryPlane.getDistance()) / (normal.dot(normal)); Vector3d midPointOnPlane = new Vector3d(midPoint); Vector3d nn = new Vector3d(normal); @@ -210,7 +183,7 @@ public class SymmetryEstimator { double val = normal.x * midPointOnPlane.x + normal.y * midPointOnPlane.y + normal.z * - midPointOnPlane.z + plane.getDistance(); + midPointOnPlane.z + symmetryPlane.getDistance(); Vector3d a = new Vector3d(); if (Math.abs(normal.dot(new Vector3d(0.0, 1.0, 0.0))) > Math.abs(normal.dot(new Vector3d (1.0, 0.0, 0.0)))) { @@ -224,39 +197,47 @@ public class SymmetryEstimator { b.cross(normal,a); b.normalize(); - SymmetryEstimator planeMesh = new SymmetryEstimator(midPointOnPlane, a, b, - (boundingBox.getMaxPoint().subtractPosition(boundingBox.getMinPoint())).getPosition().x); - - return mergeMeshWith(planeMesh); + double scale = (boundingBox.getMaxPoint().subtractPosition(boundingBox.getMinPoint())).getPosition().x; + + return createMeshFacet(midPointOnPlane, a, b, symmetryPlane.getNormal(), scale); } /** - * - * @param s mesh that will be merged - * @return mesh with merged vertices from both meshes + * Returns the bounding box computed during the {@link SymmetryEstimator#calculateSymmetryPlane()}. + * @return the bounding box or {@code null} */ - public SymmetryEstimator mergeMeshWith(SymmetryEstimator s) { - CornerTableRow row1 = new CornerTableRow(facet.getNumberOfVertices(), -1); - CornerTableRow row2 = new CornerTableRow(facet.getNumberOfVertices() + 1, facet.getNumberOfVertices() + 3); - CornerTableRow row3 = new CornerTableRow(facet.getNumberOfVertices() + 2, -1); - CornerTableRow row4 = new CornerTableRow(facet.getNumberOfVertices() + 2, -1); - CornerTableRow row5 = new CornerTableRow(facet.getNumberOfVertices() + 3, facet.getNumberOfVertices() + 1); - CornerTableRow row6 = new CornerTableRow(facet.getNumberOfVertices(), -1); - - facet.getCornerTable().addRow(row1); - facet.getCornerTable().addRow(row2); - facet.getCornerTable().addRow(row3); - facet.getCornerTable().addRow(row4); - facet.getCornerTable().addRow(row5); - facet.getCornerTable().addRow(row6); + public BoundingBox getBoundingBox() { + return boundingBox; + } + + /** + * Returns curvatures computed during the {@link SymmetryEstimator#calculateSymmetryPlane()}. + * @return the curvatures or {@code null} + */ + public List<Double> getCurvature() { + return Collections.unmodifiableList(curvatures); + } - for(int n = 0; n < 4; n++) { - facet.addVertex(s.getFacet().getVertices().get(n)); - } - return this; + protected double calculateMaxRelativeDistance(MeshFacet facet, Config config) { + BoundingBoxVisitor visitor = new BoundingBoxVisitor(false); + facet.accept(visitor); + boundingBox = visitor.getBoundingBox(); + return boundingBox.getMaxDiag() * config.getMaxRelDistance(); } - private static MeshFacet createMeshFacet(Vector3d centroid, Vector3d a, Vector3d b, double scale) { + protected static List<Double> calculateCurvatures(MeshFacet facet, boolean maxCurvature) { + CurvatureVisitor vis = (maxCurvature) ? new MaxCurvatureVisitor(false) : new GaussianCurvatureVisitor(false); + facet.accept(vis); + List<Double> curvatures = new ArrayList<>(vis.getCurvatures().get(facet)); + for (int i = 0; i < curvatures.size(); i++) { + if (Double.isNaN(curvatures.get(i))) { + curvatures.set(i, 0.0); + } + } + return curvatures; + } + + protected static MeshFacet createMeshFacet(Vector3d centroid, Vector3d a, Vector3d b, Vector3d normal, double scale) { Vector3d[] points = new Vector3d[4]; Vector3d aScaled = new Vector3d(a); @@ -282,11 +263,38 @@ public class SymmetryEstimator { MeshFacet facet = new MeshFacetImpl(); for (Vector3d point : points) { - facet.addVertex(new MeshPointImpl(point, null, null)); + facet.addVertex(new MeshPointImpl(point, normal, null)); } - facet.calculateVertexNormals(); + //facet.calculateVertexNormals(); return facet; } + + /** + * + * @param s mesh that will be merged + * @return mesh with merged vertices from both meshes + */ + /* + protected static SymmetryEstimator mergeMeshWith(SymmetryEstimator s) { + CornerTableRow row1 = new CornerTableRow(facet.getNumberOfVertices(), -1); + CornerTableRow row2 = new CornerTableRow(facet.getNumberOfVertices() + 1, facet.getNumberOfVertices() + 3); + CornerTableRow row3 = new CornerTableRow(facet.getNumberOfVertices() + 2, -1); + CornerTableRow row4 = new CornerTableRow(facet.getNumberOfVertices() + 2, -1); + CornerTableRow row5 = new CornerTableRow(facet.getNumberOfVertices() + 3, facet.getNumberOfVertices() + 1); + CornerTableRow row6 = new CornerTableRow(facet.getNumberOfVertices(), -1); + + facet.getCornerTable().addRow(row1); + facet.getCornerTable().addRow(row2); + facet.getCornerTable().addRow(row3); + facet.getCornerTable().addRow(row4); + facet.getCornerTable().addRow(row5); + facet.getCornerTable().addRow(row6); + for(int n = 0; n < 4; n++) { + facet.addVertex(s.getFacet().getVertices().get(n)); + } + return this; + } + */ } diff --git a/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/MaxCurvatureVisitor.java b/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/MaxCurvatureVisitor.java index f63e40c7..7babaa2e 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/MaxCurvatureVisitor.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/MaxCurvatureVisitor.java @@ -7,8 +7,10 @@ import java.util.List; import javax.vecmath.Vector3d; /** - * Calculates curvatures of mesh vertices more precisely using Laplacian operator. - * This algorithm is roughly 5-times slower than the pure Gaussian curvature algorithm. + * Calculates max curvatures of mesh vertices using the combination + * of Gaussian curvature and mean curvature (based on Laplacian function). + * This algorithm is roughly 4-times slower than the pure Gaussian curvature algorithm, + * but is more precise. * * @author Natalia Bebjakova * @author Radek Oslejsek diff --git a/GUI/src/main/java/cz/fidentis/analyst/gui/SymmetryPanel.java b/GUI/src/main/java/cz/fidentis/analyst/gui/SymmetryPanel.java index 5f91a7ff..cd42b84a 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/gui/SymmetryPanel.java +++ b/GUI/src/main/java/cz/fidentis/analyst/gui/SymmetryPanel.java @@ -1,6 +1,7 @@ package cz.fidentis.analyst.gui; import static cz.fidentis.analyst.gui.UserInterface.frameMain; +import cz.fidentis.analyst.mesh.core.MeshFacet; import cz.fidentis.analyst.mesh.core.MeshModel; import cz.fidentis.analyst.symmetry.Config; import cz.fidentis.analyst.symmetry.Plane; @@ -22,22 +23,21 @@ import javax.swing.event.ChangeEvent; * Panel for estimating approximate symmetry of the model */ public final class SymmetryPanel extends javax.swing.JPanel { + /** * Configuration with optional parameters of the algorithm */ private Config config; + /** * GL Canvas on which model is displayed */ private Canvas canvas; - /** - * Class that is responsible for computing the symmetry - */ - private SymmetryEstimator symCounter; + /** * Computed approximate plane of the symmetry */ - private Plane finalPlane; + private Plane symmetryPlane; /** * @@ -156,12 +156,14 @@ public final class SymmetryPanel extends javax.swing.JPanel { private void countSymmetry() throws InterruptedException { MeshModel model = new MeshModel(); canvas.changeModel(canvas.getLoadedModel()); - symCounter = new SymmetryEstimator(canvas.getModel().getFacets().get(0), config); - finalPlane = symCounter.getApproxSymmetryPlane(this); - SymmetryEstimator counted = symCounter.mergeWithPlane(finalPlane); - model.addFacet(counted.getFacet()); - - this.canvas.changeModel(model); + SymmetryEstimator est = new SymmetryEstimator(canvas.getModel().getFacets().get(0), config, true); // MISTO TRUE MUSI BYT VYBER STRATEGIE VYPOCTU ZAKRIVENI!!! + est.calculateSymmetryPlane(); + symmetryPlane = est.getSymmetryPlane(); + MeshFacet facet = est.getSymmetryPlaneMesh(); + if (facet != null) { + model.addFacet(facet); + this.canvas.changeModel(model); + } } @@ -526,8 +528,8 @@ public final class SymmetryPanel extends javax.swing.JPanel { * @param evt Final computed plane is shown to user */ private void showPlaneLabelMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_showPlaneLabelMouseClicked - JOptionPane.showMessageDialog(frameMain, "Approximate plane of symmetry: \n" + finalPlane.getNormal().x + "\n" + finalPlane.getNormal().y + "\n" + finalPlane.getNormal().z + "\n" + - finalPlane.getDistance() + "\n", "Final plane.", 0, new ImageIcon(getClass().getResource("/showPlanePane.png"))); + JOptionPane.showMessageDialog(frameMain, "Approximate plane of symmetry: \n" + symmetryPlane.getNormal().x + "\n" + symmetryPlane.getNormal().y + "\n" + symmetryPlane.getNormal().z + "\n" + + symmetryPlane.getDistance() + "\n", "Final plane.", 0, new ImageIcon(getClass().getResource("/showPlanePane.png"))); }//GEN-LAST:event_showPlaneLabelMouseClicked /** diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacetImpl.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacetImpl.java index 32fadb52..7ba0598f 100644 --- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacetImpl.java +++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacetImpl.java @@ -11,7 +11,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; /** - * MashFacet + * Mash facet is a compact triangular mesh without duplicit vertices. * * @author Matej Lukes */ diff --git a/preferences.fip b/preferences.fip deleted file mode 100644 index 7f3a24ad..00000000 --- a/preferences.fip +++ /dev/null @@ -1 +0,0 @@ -/home/oslejsek/GIT/HCI/analyst-data/multi-scan-models-anonymized/average-girl-17-20 \ No newline at end of file -- GitLab