From c5d994f1d22fba70958dd2147629280a974861cb Mon Sep 17 00:00:00 2001 From: Jakub Kolman <kubokolman@gmail.com> Date: Mon, 2 May 2022 16:49:40 +0200 Subject: [PATCH] [123] feat: procrustes analysis with subset of feature points --- .../procrustes/ProcrustesAnalysis.java | 81 ++++++++++++++++--- .../ProcrustesAnalysisFaceModel.java | 78 ++++++++++++++++-- .../procrustes/ProcrustesVisitor.java | 3 +- .../analyst/tests/ProcrustesVisitorTest.java | 4 +- .../fidentis/analyst/rotation_landmarks.csv | 2 + 5 files changed, 147 insertions(+), 21 deletions(-) create mode 100644 GUI/src/test/resources/cz/fidentis/analyst/rotation_landmarks.csv diff --git a/Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesAnalysis.java b/Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesAnalysis.java index f5c90f37..a200183c 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesAnalysis.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesAnalysis.java @@ -1,5 +1,6 @@ package cz.fidentis.analyst.procrustes; +import cz.fidentis.analyst.Logger; import cz.fidentis.analyst.face.HumanFace; import cz.fidentis.analyst.feature.FeaturePoint; import cz.fidentis.analyst.feature.api.IPosition; @@ -8,13 +9,19 @@ import org.ejml.simple.SimpleMatrix; import org.ejml.simple.SimpleSVD; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.DataFormatException; +import javax.swing.JFrame; +import javax.swing.JOptionPane; import javax.vecmath.Point3d; import javax.vecmath.Vector3d; +import cz.fidentis.analyst.procrustes.ProcrustesAnalysisFaceModel; +import java.util.Comparator; + /** * @author Jakub Kolman @@ -40,16 +47,39 @@ public class ProcrustesAnalysis { HumanFace humanFace1, HumanFace humanFace2) throws DataFormatException { - ProcrustesAnalysisFaceModel model1 = new ProcrustesAnalysisFaceModel(humanFace1); - ProcrustesAnalysisFaceModel model2 = new ProcrustesAnalysisFaceModel(humanFace2); - - if (model1.getFeaturePointsMap().values().size() != model2.getFeaturePointsMap().values().size()) { - throw new DataFormatException("Lists of feature points do not have the same size"); - } - - if (!checkFeaturePointsType( - model1.getFeaturePointValues(), model2.getFeaturePointValues())) { - throw new DataFormatException("Lists of feature points do not have the same feature point types"); + ProcrustesAnalysisFaceModel model1; + ProcrustesAnalysisFaceModel model2; + + if (humanFace1.getFeaturePoints().size() != humanFace2.getFeaturePoints().size() + || !checkFeaturePointsType( + sortListByFeaturePointType(humanFace1.getFeaturePoints()), + sortListByFeaturePointType(humanFace1.getFeaturePoints()))) { + +// int n = 0; + Object[] options = {"Yes", "No"}; + int n = JOptionPane.showOptionDialog( + new JFrame("Warning"), + "The sets of feature points do not correspond with each other. Do you want to continue with a subset of feature points?", + "Warning", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[1]); + if (n == 0) { + ArrayList<Integer> viablePoints = getSubsetOfFeaturePoints(humanFace1.getFeaturePoints(), humanFace2.getFeaturePoints()); + model1 = new ProcrustesAnalysisFaceModel(humanFace1, viablePoints); + model2 = new ProcrustesAnalysisFaceModel(humanFace2, viablePoints); + Logger.print("Yes"); + Logger.print(Integer.valueOf(n).toString()); + } else { + JOptionPane.showMessageDialog(new JFrame("Procrustes cancelled"), + "Lists of feature points do not have the same size and work on subset was cancelled"); + throw new DataFormatException("Lists of feature points do not have the same size and work on subset was cancelled"); + } + } else { + model1 = new ProcrustesAnalysisFaceModel(humanFace1); + model2 = new ProcrustesAnalysisFaceModel(humanFace2); } this.faceModel1 = model1; @@ -426,4 +456,35 @@ public class ProcrustesAnalysis { } } + /** + * Finds corresponding subset of feature points from both faces so the procrustes analysis can be + * applied on the subset. + * + * @param featurePoints + * @param featurePoints0 + * + * @return List of feature point types that are in both sets of feature points + */ + private ArrayList<Integer> getSubsetOfFeaturePoints(List<FeaturePoint> featurePoints1, List<FeaturePoint> featurePoints2) { + HashMap<Integer, FeaturePoint> fpMap = new HashMap<>(); + for (FeaturePoint fp : featurePoints1) { + fpMap.put(fp.getFeaturePointType().getType(), fp); + } + + ArrayList<Integer> vaiablePoints = new ArrayList<>(); + featurePoints2.forEach(fp -> { + if (fpMap.get(fp.getFeaturePointType().getType()) != null) { + vaiablePoints.add(Integer.valueOf(fp.getFeaturePointType().getType())); + } + }); + return vaiablePoints; + } + + private List<FeaturePoint> sortListByFeaturePointType(List<FeaturePoint> featurePointList) { + Collections.sort( + featurePointList, Comparator.comparingInt(fp -> fp.getFeaturePointType().getType()) + ); + return featurePointList; + } + } diff --git a/Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesAnalysisFaceModel.java b/Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesAnalysisFaceModel.java index 35be152d..74f90f39 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesAnalysisFaceModel.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesAnalysisFaceModel.java @@ -44,6 +44,31 @@ public class ProcrustesAnalysisFaceModel { this.featurePointTypeCorrespondence = createFeaturePointTypeCorrespondence(face.getFeaturePoints()); } + /** + * Constructor for face model if there is a need for a subset selection of + * feature points before applying procrustes analysis. + * + * @param face + * @param viablePoints + */ + public ProcrustesAnalysisFaceModel(HumanFace face, List<Integer> viablePoints) { + this.humanFace = face; + // getFeaturePoints() returns unmodifiable List. To sort it we need to make copy first + List<FeaturePoint> modifiableFeaturePointList = getSelectionOfFeaturePoints(face.getFeaturePoints(), viablePoints); + + // Currently set feature points is not working as inteded because gui doesn't repaint them after changing values + face.setFeaturePoints(modifiableFeaturePointList); + + // To be able to move vertices we have to make a copy of them because getFacets() returns unmodifiable List + List<MeshPoint> modifiableVertices = new ArrayList<>(face.getMeshModel().getFacets().get(0).getVertices()); + + this.vertices = modifiableVertices; + this.meshModel = face.getMeshModel(); + this.featurePointsMap = createFeaturePointMap( + sortListByFeaturePointType(modifiableFeaturePointList)); + this.featurePointTypeCorrespondence = createFeaturePointTypeCorrespondence(face.getFeaturePoints()); + } + /** * sets feature points map and also sets feature point on human face for * visualisation @@ -53,7 +78,7 @@ public class ProcrustesAnalysisFaceModel { public void setFeaturePointsMap(HashMap<Integer, FeaturePoint> featurePointsMap) { this.featurePointsMap = featurePointsMap; readjustFeaturePoints(featurePointsMap); - + // this.humanFace.setFeaturePoints(getFeaturePointValues()); } @@ -81,6 +106,16 @@ public class ProcrustesAnalysisFaceModel { return featurePointTypeCorrespondence; } + private List<FeaturePoint> getSelectionOfFeaturePoints(List<FeaturePoint> featurePoints, List<Integer> viablePoints) { + ArrayList<FeaturePoint> selection = new ArrayList<>(); + featurePoints.forEach(fp -> { + if (viablePoints.contains(fp.getFeaturePointType().getType())) { + selection.add(fp); + } + }); + return selection; + } + /** * Creates corresponding key value pair of matrix row index and a feature * point type value. It is used so we can get back type of feature point @@ -128,17 +163,44 @@ public class ProcrustesAnalysisFaceModel { } private void readjustFeaturePoints(HashMap<Integer, FeaturePoint> featurePointsMap) { + if (featurePointsMap.size() != this.humanFace.getFeaturePoints().size()) { + readjustSelection(featurePointsMap); + } else { + this.humanFace.getFeaturePoints().forEach(fp -> { + FeaturePoint movedFp = featurePointsMap.get(fp.getFeaturePointType().getType()); + if (fp.getFeaturePointType().getType() + != movedFp.getFeaturePointType().getType()) { + throw new RuntimeException("Types do not correspond"); + } + fp.getPosition().x = movedFp.getX(); + fp.getPosition().y = movedFp.getY(); + fp.getPosition().z = movedFp.getZ(); + }); + } + } + + /** + * Moves subset of feature points to their new position. If point doesn't belong to subset it is moved to (0,0,0) instead. + * + * @param featurePointsMap + */ + private void readjustSelection(HashMap<Integer, FeaturePoint> featurePointsMap) { this.humanFace.getFeaturePoints().forEach(fp -> { FeaturePoint movedFp = featurePointsMap.get(fp.getFeaturePointType().getType()); - if (fp.getFeaturePointType().getType() - != movedFp.getFeaturePointType().getType()) { - throw new RuntimeException("Types do not correspond"); + if (movedFp != null) { + if (fp.getFeaturePointType().getType() + != movedFp.getFeaturePointType().getType()) { + throw new RuntimeException("Types do not correspond"); + } + fp.getPosition().x = movedFp.getX(); + fp.getPosition().y = movedFp.getY(); + fp.getPosition().z = movedFp.getZ(); + } else { + fp.getPosition().x = 0; + fp.getPosition().y = 0; + fp.getPosition().z = 0; } - fp.getPosition().x = movedFp.getX(); - fp.getPosition().y = movedFp.getY(); - fp.getPosition().z = movedFp.getZ(); }); -// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } } diff --git a/Comparison/src/main/java/cz/fidentis/analyst/visitors/procrustes/ProcrustesVisitor.java b/Comparison/src/main/java/cz/fidentis/analyst/visitors/procrustes/ProcrustesVisitor.java index a86ec99f..063e7533 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/visitors/procrustes/ProcrustesVisitor.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/visitors/procrustes/ProcrustesVisitor.java @@ -67,8 +67,7 @@ public class ProcrustesVisitor extends MeshVisitor { public void visitMeshFacet(MeshFacet transformedFacet) { try { ProcrustesAnalysis procrustes = new ProcrustesAnalysis(this.primaryFace, this.secondaryFace, scale); - this.transformation = procrustes.analyze(); - + this.transformation = procrustes.analyze(); // applyTransformations(this.transformation, transformedFacet); } catch (Exception e) { System.err.println(e.getMessage()); diff --git a/GUI/src/main/java/cz/fidentis/analyst/tests/ProcrustesVisitorTest.java b/GUI/src/main/java/cz/fidentis/analyst/tests/ProcrustesVisitorTest.java index 158a6e07..25cacbe3 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/tests/ProcrustesVisitorTest.java +++ b/GUI/src/main/java/cz/fidentis/analyst/tests/ProcrustesVisitorTest.java @@ -40,6 +40,8 @@ public class ProcrustesVisitorTest { private static final String FACE_2 = "0002_02_ECA.obj"; private static final String FACE_FP_1 = "0002_01_landmarks.csv"; private static final String FACE_FP_2 = "0002_02_landmarks.csv"; + private static final String FACE_FP_ROTATION_1 = "rotation_180_moved_2_landmarks.csv"; + private static final String FACE_FP_ROTATION_2 = "rotation_landmarks.csv"; /** * Main method @@ -52,7 +54,7 @@ public class ProcrustesVisitorTest { FeaturePointImportService featurePointImportService = new FeaturePointImportService(); List<FeaturePoint> fpList1 = featurePointImportService.importFeaturePoints( - DATA_DIR.toString(), FACE_FP_1); + DATA_DIR.toString(), FACE_FP_ROTATION_1); List<FeaturePoint> fpList2= featurePointImportService.importFeaturePoints( DATA_DIR.toString(), FACE_FP_2); diff --git a/GUI/src/test/resources/cz/fidentis/analyst/rotation_landmarks.csv b/GUI/src/test/resources/cz/fidentis/analyst/rotation_landmarks.csv new file mode 100644 index 00000000..95294f66 --- /dev/null +++ b/GUI/src/test/resources/cz/fidentis/analyst/rotation_landmarks.csv @@ -0,0 +1,2 @@ +Scan name,EX_R x,EX_R y,EX_R z,EX_L x,EX_L y,EX_L z,EN_R x,EN_R y,EN_R z,EN_L x,EN_L y,EN_L z,PAS_R x,PAS_R y,PAS_R z +0002_01,0,20,0,0,-20,0,0,0,-20,0,0,50,20,0,0 -- GitLab