Loading Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceUtils.java +151 −114 Original line number Diff line number Diff line package cz.fidentis.analyst.face; import cz.fidentis.analyst.Logger; import cz.fidentis.analyst.feature.FeaturePoint; import cz.fidentis.analyst.icp.IcpTransformer; import cz.fidentis.analyst.icp.Quaternion; import cz.fidentis.analyst.procrustes.ProcrustesAnalysis; import cz.fidentis.analyst.symmetry.Plane; import cz.fidentis.analyst.visitors.mesh.sampling.PointSampling; import java.util.zip.DataFormatException; import cz.fidentis.analyst.visitors.procrustes.ProcrustesVisitor; import javax.vecmath.Matrix3d; import javax.vecmath.Matrix4d; import javax.vecmath.Point3d; Loading @@ -24,8 +22,8 @@ import javax.vecmath.Vector3d; public class HumanFaceUtils { /** * Superimpose two faces by applying iterative closest points (ICP) algorithm * on their triangular meshes. * Superimpose two faces by applying iterative closest points (ICP) * algorithm on their triangular meshes. * * @param staticFace A face that remains unchanged. * @param transformedFace A face to be transformed. Loading @@ -34,9 +32,11 @@ public class HumanFaceUtils { * @param scale Whether to scale face as well * @param error Acceptable error (a number bigger than or equal to zero). * @param samplingStrategy Downsampling strategy * @param recomputeKdTree If {@code true} and the k-d tree of the {@code transformedFace} exists, * the it automatically re-computed. Otherwise, it is simply removed. * @return ICP visitor that holds the transformations performed on the {@code transformedFace}. * @param recomputeKdTree If {@code true} and the k-d tree of the * {@code transformedFace} exists, the it automatically re-computed. * Otherwise, it is simply removed. * @return ICP visitor that holds the transformations performed on the * {@code transformedFace}. */ public static IcpTransformer alignMeshes( HumanFace staticFace, HumanFace transformedFace, Loading Loading @@ -102,11 +102,14 @@ public class HumanFaceUtils { * Transform the face. * * @param face Face to be transformed. * @param rotation Rotation vector denoting the rotation angle around axes X, Y, and Z. * @param translation Translation vector denoting the translation in the X, Y, and Z direction. * @param rotation Rotation vector denoting the rotation angle around axes * X, Y, and Z. * @param translation Translation vector denoting the translation in the X, * Y, and Z direction. * @param scale Scale factor (1 = no scale). * @param recomputeKdTree If {@code true} and the k-d tree of the {@code transformedFace} exists, * the it automatically re-computed. Otherwise, it is simply removed. * @param recomputeKdTree If {@code true} and the k-d tree of the * {@code transformedFace} exists, the it automatically re-computed. * Otherwise, it is simply removed. */ public static void transformFace(HumanFace face, Vector3d rotation, Vector3d translation, double scale, boolean recomputeKdTree) { Quaternion rot = new Quaternion(rotation.x, rotation.y, rotation.z, 1.0); Loading @@ -120,7 +123,6 @@ public class HumanFaceUtils { transformNormal(meshPoint.getNormal(), rot); }); // update k-d of transformed face: if (face.hasKdTree()) { if (recomputeKdTree) { Loading @@ -147,13 +149,16 @@ public class HumanFaceUtils { } /** * Transforms one face so that its symmetry plane fits the symmetry plane of the other face. * Transforms one face so that its symmetry plane fits the symmetry plane of * the other face. * * @param staticFace a human face that remains unchanged * @param transformedFace a human face that will be transformed * @param recomputeKdTree If {@code true} and the k-d tree of the {@code transformedFace} exists, * the it automatically re-computed. Otherwise, it is simply removed. * @param preserveUpDir If {@code false}, then the object can be rotated around the target's normal arbitrarily. * @param recomputeKdTree If {@code true} and the k-d tree of the * {@code transformedFace} exists, the it automatically re-computed. * Otherwise, it is simply removed. * @param preserveUpDir If {@code false}, then the object can be rotated * around the target's normal arbitrarily. */ public static void alignSymmetryPlanes( HumanFace staticFace, HumanFace transformedFace, Loading Loading @@ -212,36 +217,27 @@ public class HumanFaceUtils { } /** * Superimpose two faces by applying Procrustes on their feature points. * Be aware that both faces are transformed into the origin of the coordinate system! * Superimpose two faces by applying Procrustes on their feature points. Be * aware that both faces are transformed into the origin of the coordinate * system! * * @param firstFace a human face that remains unchanged * @param secondFace a human face that will be transformed * @param scale Whether to scale faces as well * @param recomputeKdTree If {@code true} and the k-d tree of the {@code transformedFace} exists, * the it automatically re-computed. Otherwise, it is simply removed. * @param recomputeKdTree If {@code true} and the k-d tree of the * {@code transformedFace} exists, the it automatically re-computed. * Otherwise, it is simply removed. */ public static void alignFeaturePoints( public static ProcrustesVisitor alignFeaturePoints( HumanFace firstFace, HumanFace secondFace, boolean scale, boolean recomputeKdTree) { ProcrustesAnalysis pa = null; try { pa = new ProcrustesAnalysis(firstFace, secondFace, scale); pa.analyze(); } catch (DataFormatException e) { Logger.print("Procrustes Analysis experienced exception"); return; } // get transformation values ProcrustesVisitor pv = new ProcrustesVisitor(firstFace, scale); // superimpose face towards the static face secondFace.accept(pv); // update k-d of transformed faces: if (firstFace.hasKdTree()) { if (recomputeKdTree) { firstFace.computeKdTree(true); } else { firstFace.removeKdTree(); } } // update k-d of transformed faces: if (secondFace.hasKdTree()) { if (recomputeKdTree) { Loading @@ -252,24 +248,27 @@ public class HumanFaceUtils { } // update bounding box, which is always present: firstFace.updateBoundingBox(); secondFace.updateBoundingBox(); // transform feature points: if (firstFace.hasFeaturePoints()) { // TODO } if (secondFace.hasFeaturePoints()) { // TODO } // transform symmetry plane: if (firstFace.hasSymmetryPlane()) { // TODO } if (secondFace.hasSymmetryPlane()) { // TODO Vector3d adjustment = pv.getTransformations().get(secondFace).getCentroidAdjustment(); double transformationScaleValue = pv.getTransformations().get(secondFace).getScale(); Quaternion rotation = pv.getTransformations().get(secondFace).getMatrixAsQuaternion( pv.getTransformations().get(secondFace).getRotationMatrix()); secondFace.getFeaturePoints().parallelStream().forEach(fp -> { secondFace.setSymmetryPlane(transformPlane( secondFace.getSymmetryPlane(), rotation, adjustment, transformationScaleValue)); }); secondFace.getFeaturePoints().parallelStream().forEach(fp -> { secondFace.setSymmetryPlane(transformPlane( secondFace.getSymmetryPlane(), pv.getTransformations().get(secondFace).getMatrixAsQuaternion( pv.getTransformations().get(secondFace).getIdentityMatrix()), pv.getTransformations().get(secondFace).getSuperImpositionAdjustment(), 1)); }); } return pv; } /** Loading Loading @@ -354,7 +353,45 @@ public class HumanFaceUtils { return new Plane(retPlane.getNormal(), dist); } // /** // * Moves feature points to calculated new position by computing procrustes // * analysis. {@link cz.fidentis.analyst.procrustes.ProcrustesAnalysis}. If // * feature point was not used in the analysis, it's position will be kept as // * it had in the original list. // * // * Move of the feature point is done to remove them from user vision first. // * // * @param secondFace // * @param movedFeaturePoints // */ // private static void moveFeaturePoints(HumanFace secondFace, Map<Integer, FeaturePoint> movedFeaturePoints) { // secondFace.getFeaturePoints().forEach(fp -> { // FeaturePoint movedFp = movedFeaturePoints.get(fp.getFeaturePointType().getType()); // 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(); // } // }); // } /** * Sorts List by featurePoint type property * * @param featurePointList * @return ordered list of feature points by type */ /* protected static List<FeaturePoint> sortListByFeaturePointType(List<FeaturePoint> featurePointList) { Collections.sort( featurePointList, Comparator.comparingInt(fp -> fp.getFeaturePointType().getType()) ); return featurePointList; } */ } Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesAnalysis.java +126 −194 File changed.Preview size limit exceeded, changes collapsed. Show changes Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesAnalysisFaceModel.java +51 −50 Original line number Diff line number Diff line Loading @@ -2,73 +2,64 @@ package cz.fidentis.analyst.procrustes; import cz.fidentis.analyst.face.HumanFace; import cz.fidentis.analyst.feature.FeaturePoint; import cz.fidentis.analyst.mesh.core.MeshModel; import cz.fidentis.analyst.mesh.core.MeshPoint; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; /** * Holds important face attributes that are required for procrustes analysis. * Holds important face attributes that are required for Procrustes analysis. * * @author Jakub Kolman */ public class ProcrustesAnalysisFaceModel { private final HumanFace humanFace; private HashMap<Integer, FeaturePoint> featurePointsMap; // sorted by feature point type private List<MeshPoint> vertices; private MeshModel meshModel; private final HashMap<Integer, Integer> featurePointTypeCorrespondence; /** * constructor creating inner attributes used in procrustes analysis from face * constructor creating inner attributes used in Procrustes analysis from * face * * @param face */ public ProcrustesAnalysisFaceModel(HumanFace face) { this.humanFace = face; // getFeaturePoints() returns unmodifiable List. To sort it we need to make copy first List<FeaturePoint> modifiableFeaturePointList = new ArrayList<>(face.getFeaturePoints()); this.featurePointsMap = createFeaturePointMap( ProcrustesUtils.sortListByFeaturePointType(modifiableFeaturePointList)); this.featurePointTypeCorrespondence = createFeaturePointTypeCorrespondence(face.getFeaturePoints()); } // 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()); /** * 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) { List<FeaturePoint> modifiableFeaturePointList = new ArrayList<>( getSelectionOfFeaturePoints(face.getFeaturePoints(), viablePoints)); this.vertices = modifiableVertices; this.meshModel = face.getMeshModel(); this.featurePointsMap = createFeaturePointMap( sortListByFeaturePointType(modifiableFeaturePointList)); ProcrustesUtils.sortListByFeaturePointType(modifiableFeaturePointList)); this.featurePointTypeCorrespondence = createFeaturePointTypeCorrespondence(face.getFeaturePoints()); } /** * sets feature points map and also sets feature point on human face for visualisation * Sets feature points map and also sets feature point on human face for * visualization. * * @param featurePointsMap */ public void setFeaturePointsMap(HashMap<Integer, FeaturePoint> featurePointsMap) { this.featurePointsMap = featurePointsMap; this.humanFace.setFeaturePoints(getFeaturePointValues()); } public MeshModel getMeshModel() { return meshModel; } public List<FeaturePoint> getFeaturePointValues() { return new ArrayList<>(this.getFeaturePointsMap().values()); } public void setVertices(List<MeshPoint> vertices) { this.vertices = vertices; } public List<MeshPoint> getVertices() { return vertices; } public HashMap<Integer, FeaturePoint> getFeaturePointsMap() { return featurePointsMap; } Loading @@ -78,9 +69,29 @@ public class ProcrustesAnalysisFaceModel { } /** * 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 after working with matrices where the type is not stored, * but instead are used matrix indexes. * Creates a subset of selected feature points that can be used for * Procrustes analysis. Each of the feature point types contained in the * subset is contained in both faces. * * @param featurePoints * @param viablePoints * @return */ 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 * after working with matrices where the type is not stored, but instead are * used matrix indexes. * * @param fpList * @return Loading @@ -93,24 +104,10 @@ public class ProcrustesAnalysisFaceModel { return map; } /** * Sorts List by featurePoint type property * * @param featurePointList * @return ordered list of feature points by type */ protected List<FeaturePoint> sortListByFeaturePointType(List<FeaturePoint> featurePointList) { Collections.sort( featurePointList, Comparator.comparingInt(fp -> fp.getFeaturePointType().getType()) ); return featurePointList; } /** * Creates feature point hash map for internal use in Procrustes Analysis. * * Map key is feature point type (Integer). * Value is feature point itself. * Map key is feature point type (Integer). Value is feature point itself. * * @param featurePointList * @return Loading @@ -118,7 +115,11 @@ public class ProcrustesAnalysisFaceModel { private HashMap<Integer, FeaturePoint> createFeaturePointMap(List<FeaturePoint> featurePointList) { HashMap<Integer, FeaturePoint> map = new HashMap<>(); for (FeaturePoint fp : featurePointList) { map.put(fp.getFeaturePointType().getType(), fp); map.put(fp.getFeaturePointType().getType(), new FeaturePoint( fp.getX(), fp.getY(), fp.getZ(), fp.getFeaturePointType())); } return map; } Loading Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesTransformation.java 0 → 100644 +148 −0 Original line number Diff line number Diff line package cz.fidentis.analyst.procrustes; import cz.fidentis.analyst.icp.Quaternion; import javax.vecmath.Vector3d; import org.ejml.simple.SimpleMatrix; /** * Class used to store information about transformation that needs to be applied * to a second face when computing Procrustes analysis. * * @author Jakub Kolman */ public class ProcrustesTransformation { private double scale; // used for adjustment to move feature points (0,0,0) so the face is rotated around the centroid point private Vector3d centroidAdjustment; // used to move face to superimposed poisition over the original human face private Vector3d superImpositionAdjustment; private SimpleMatrix rotationMatrix; // /** // * constructor // * // * @param scale determines by how much should be faced enlarged or minimized // * @param adjustment determines how should be face moved to make faces // * superimposed considering feature points // * @param rotationMatrix determines how to rotate face // * @param updatedFaceModel holds information about adjusted face // */ // public ProcrustesTransformation( // double scale, // Vector3d centroidAdjustment, // Vector3d superImpositionAdjustment, // SimpleMatrix rotationMatrix, // HashMap<Integer, FeaturePoint> updatedFeaturePointsMap) { // this.scale = scale; // this.centroidAdjustment = centroidAdjustment; // this.superImpositionAdjustment = superImpositionAdjustment; // this.rotationMatrix = rotationMatrix; // this.featurePointsMap = featurePointsMap; // } // // /** // * Constructor (for more info take a look on // * {@link #ProcrustesTransformation(double, javax.vecmath.Vector3d, org.ejml.simple.SimpleMatrix, cz.fidentis.analyst.procrustes.ProcrustesAnalysisFaceModel) } // * // * @param adjustment // * @param rotationMatrix // */ // public ProcrustesTransformation( // Vector3d centroidAdjustment, // Vector3d superImpositionAdjustment, // SimpleMatrix rotationMatrix, // HashMap<Integer, FeaturePoint> updatedFeaturePointsMap) { // this(1d, centroidAdjustment, superImpositionAdjustment, rotationMatrix, updatedFeaturePointsMap); // } /** * Empty constructor with values that won't transform face. To set values later attribute setters can be used. * <pre> * rotation matrix is set to identity matrix * scale is 1 * adjustment is none * </pre> */ public ProcrustesTransformation() { this.scale = 1; this.centroidAdjustment = new Vector3d(0,0,0); this.superImpositionAdjustment = new Vector3d(0,0,0); SimpleMatrix r = new SimpleMatrix(3, 3); // identity matrix r.set(0, 0, 1); r.set(0, 1, 0); r.set(0, 2, 0); r.set(1, 0, 0); r.set(1, 1, 1); r.set(1, 2, 0); r.set(2, 0, 0); r.set(2, 1, 0); r.set(2, 2, 1); this.rotationMatrix = r; } public double getScale() { return this.scale; } public Vector3d getCentroidAdjustment() { return this.centroidAdjustment; } public Vector3d getSuperImpositionAdjustment() { return this.superImpositionAdjustment; } public SimpleMatrix getRotationMatrix() { return this.rotationMatrix; } public void setScale(double scale) { this.scale = scale; } /** * Calculates quaternion from rotation matrix. * * @return quaternion with rotation values */ public Quaternion getMatrixAsQuaternion(SimpleMatrix matrix) { if (matrix != null) { double w = Math.sqrt(1.0 + matrix.get(0, 0) + matrix.get(1, 1) + matrix.get(2, 2)) / 2.0; double w4 = (4.0 * w); double x = (matrix.get(2, 1) - matrix.get(1, 2)) / w4; double y = (matrix.get(0, 2) - matrix.get(2, 0)) / w4; double z = (matrix.get(1, 0) - matrix.get(0, 1)) / w4; return new Quaternion(x, y, z, w); } else { return null; } } public void setCentroidAdjustment(Vector3d centroidAdjustment) { this.centroidAdjustment = centroidAdjustment; } public void setSuperImpositionAdjustment(Vector3d superImpositionAdjustment) { this.superImpositionAdjustment = superImpositionAdjustment; } public void setRotationMatrix(SimpleMatrix rotationMatrix) { this.rotationMatrix = rotationMatrix; } public SimpleMatrix getIdentityMatrix() { SimpleMatrix r = new SimpleMatrix(3, 3); // identity matrix r.set(0, 0, 1); r.set(0, 1, 0); r.set(0, 2, 0); r.set(1, 0, 0); r.set(1, 1, 1); r.set(1, 2, 0); r.set(2, 0, 0); r.set(2, 1, 0); r.set(2, 2, 1); return r; } } Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesUtils.java 0 → 100644 +85 −0 Original line number Diff line number Diff line package cz.fidentis.analyst.procrustes; import cz.fidentis.analyst.Logger; import cz.fidentis.analyst.feature.FeaturePoint; import cz.fidentis.analyst.feature.api.IPosition; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.vecmath.Vector3d; /** * Utility functions that are used in multiple classes to compute * * @author Jakub Kolman */ public class ProcrustesUtils { /** * Sorts given list of feature points by their type * * @param featurePointList * @return sorted list of feature points */ public static List<FeaturePoint> sortListByFeaturePointType(List<FeaturePoint> featurePointList) { Collections.sort( featurePointList, Comparator.comparingInt(fp -> fp.getFeaturePointType().getType()) ); return featurePointList; } /** * Moves point by given vector value. * * @param <T> * @param point * @param vector */ public static <T extends IPosition> void movePoint(T point, Vector3d vector) { point.getPosition().x = point.getX() + vector.x; point.getPosition().y = point.getY() + vector.y; point.getPosition().z = point.getZ() + vector.z; } /** * Scales position of given point by multiplying its coordinates with given * scaleFactor. * * @param point * @param scaleFactor * @param <T> * @return */ public static <T extends IPosition> T scalePointDistance(T point, double scaleFactor) { point.getPosition().x = point.getX() * scaleFactor; point.getPosition().y = point.getY() * scaleFactor; point.getPosition().z = point.getZ() * scaleFactor; return point; } /** * Checks if two feature point lists have the same types of feature points. * <p> * To use this method you need to supply ordered lists by Feature Point type * as parameters. Otherwise even if two lists contain the same feature * points it will return false. * <p> * Use sort function sortListByFeaturePointType on feature point lists to * get correct results. * * @param featurePointList1 * @param featurePointList2 * @return true if two sorted lists by the feature point type contain the * same feature point types */ public static boolean checkFeaturePointsType(List<FeaturePoint> featurePointList1, List<FeaturePoint> featurePointList2) { for (int i = 0; i < featurePointList1.size(); i++) { if (featurePointList1.get(i).getFeaturePointType().getType() != featurePointList2.get(i).getFeaturePointType().getType()) { Logger.print(featurePointList1.get(i).getFeaturePointType().getName()); Logger.print(featurePointList1.get(i).getFeaturePointType().getName()); return false; } } return true; } } Loading
Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceUtils.java +151 −114 Original line number Diff line number Diff line package cz.fidentis.analyst.face; import cz.fidentis.analyst.Logger; import cz.fidentis.analyst.feature.FeaturePoint; import cz.fidentis.analyst.icp.IcpTransformer; import cz.fidentis.analyst.icp.Quaternion; import cz.fidentis.analyst.procrustes.ProcrustesAnalysis; import cz.fidentis.analyst.symmetry.Plane; import cz.fidentis.analyst.visitors.mesh.sampling.PointSampling; import java.util.zip.DataFormatException; import cz.fidentis.analyst.visitors.procrustes.ProcrustesVisitor; import javax.vecmath.Matrix3d; import javax.vecmath.Matrix4d; import javax.vecmath.Point3d; Loading @@ -24,8 +22,8 @@ import javax.vecmath.Vector3d; public class HumanFaceUtils { /** * Superimpose two faces by applying iterative closest points (ICP) algorithm * on their triangular meshes. * Superimpose two faces by applying iterative closest points (ICP) * algorithm on their triangular meshes. * * @param staticFace A face that remains unchanged. * @param transformedFace A face to be transformed. Loading @@ -34,9 +32,11 @@ public class HumanFaceUtils { * @param scale Whether to scale face as well * @param error Acceptable error (a number bigger than or equal to zero). * @param samplingStrategy Downsampling strategy * @param recomputeKdTree If {@code true} and the k-d tree of the {@code transformedFace} exists, * the it automatically re-computed. Otherwise, it is simply removed. * @return ICP visitor that holds the transformations performed on the {@code transformedFace}. * @param recomputeKdTree If {@code true} and the k-d tree of the * {@code transformedFace} exists, the it automatically re-computed. * Otherwise, it is simply removed. * @return ICP visitor that holds the transformations performed on the * {@code transformedFace}. */ public static IcpTransformer alignMeshes( HumanFace staticFace, HumanFace transformedFace, Loading Loading @@ -102,11 +102,14 @@ public class HumanFaceUtils { * Transform the face. * * @param face Face to be transformed. * @param rotation Rotation vector denoting the rotation angle around axes X, Y, and Z. * @param translation Translation vector denoting the translation in the X, Y, and Z direction. * @param rotation Rotation vector denoting the rotation angle around axes * X, Y, and Z. * @param translation Translation vector denoting the translation in the X, * Y, and Z direction. * @param scale Scale factor (1 = no scale). * @param recomputeKdTree If {@code true} and the k-d tree of the {@code transformedFace} exists, * the it automatically re-computed. Otherwise, it is simply removed. * @param recomputeKdTree If {@code true} and the k-d tree of the * {@code transformedFace} exists, the it automatically re-computed. * Otherwise, it is simply removed. */ public static void transformFace(HumanFace face, Vector3d rotation, Vector3d translation, double scale, boolean recomputeKdTree) { Quaternion rot = new Quaternion(rotation.x, rotation.y, rotation.z, 1.0); Loading @@ -120,7 +123,6 @@ public class HumanFaceUtils { transformNormal(meshPoint.getNormal(), rot); }); // update k-d of transformed face: if (face.hasKdTree()) { if (recomputeKdTree) { Loading @@ -147,13 +149,16 @@ public class HumanFaceUtils { } /** * Transforms one face so that its symmetry plane fits the symmetry plane of the other face. * Transforms one face so that its symmetry plane fits the symmetry plane of * the other face. * * @param staticFace a human face that remains unchanged * @param transformedFace a human face that will be transformed * @param recomputeKdTree If {@code true} and the k-d tree of the {@code transformedFace} exists, * the it automatically re-computed. Otherwise, it is simply removed. * @param preserveUpDir If {@code false}, then the object can be rotated around the target's normal arbitrarily. * @param recomputeKdTree If {@code true} and the k-d tree of the * {@code transformedFace} exists, the it automatically re-computed. * Otherwise, it is simply removed. * @param preserveUpDir If {@code false}, then the object can be rotated * around the target's normal arbitrarily. */ public static void alignSymmetryPlanes( HumanFace staticFace, HumanFace transformedFace, Loading Loading @@ -212,36 +217,27 @@ public class HumanFaceUtils { } /** * Superimpose two faces by applying Procrustes on their feature points. * Be aware that both faces are transformed into the origin of the coordinate system! * Superimpose two faces by applying Procrustes on their feature points. Be * aware that both faces are transformed into the origin of the coordinate * system! * * @param firstFace a human face that remains unchanged * @param secondFace a human face that will be transformed * @param scale Whether to scale faces as well * @param recomputeKdTree If {@code true} and the k-d tree of the {@code transformedFace} exists, * the it automatically re-computed. Otherwise, it is simply removed. * @param recomputeKdTree If {@code true} and the k-d tree of the * {@code transformedFace} exists, the it automatically re-computed. * Otherwise, it is simply removed. */ public static void alignFeaturePoints( public static ProcrustesVisitor alignFeaturePoints( HumanFace firstFace, HumanFace secondFace, boolean scale, boolean recomputeKdTree) { ProcrustesAnalysis pa = null; try { pa = new ProcrustesAnalysis(firstFace, secondFace, scale); pa.analyze(); } catch (DataFormatException e) { Logger.print("Procrustes Analysis experienced exception"); return; } // get transformation values ProcrustesVisitor pv = new ProcrustesVisitor(firstFace, scale); // superimpose face towards the static face secondFace.accept(pv); // update k-d of transformed faces: if (firstFace.hasKdTree()) { if (recomputeKdTree) { firstFace.computeKdTree(true); } else { firstFace.removeKdTree(); } } // update k-d of transformed faces: if (secondFace.hasKdTree()) { if (recomputeKdTree) { Loading @@ -252,24 +248,27 @@ public class HumanFaceUtils { } // update bounding box, which is always present: firstFace.updateBoundingBox(); secondFace.updateBoundingBox(); // transform feature points: if (firstFace.hasFeaturePoints()) { // TODO } if (secondFace.hasFeaturePoints()) { // TODO } // transform symmetry plane: if (firstFace.hasSymmetryPlane()) { // TODO } if (secondFace.hasSymmetryPlane()) { // TODO Vector3d adjustment = pv.getTransformations().get(secondFace).getCentroidAdjustment(); double transformationScaleValue = pv.getTransformations().get(secondFace).getScale(); Quaternion rotation = pv.getTransformations().get(secondFace).getMatrixAsQuaternion( pv.getTransformations().get(secondFace).getRotationMatrix()); secondFace.getFeaturePoints().parallelStream().forEach(fp -> { secondFace.setSymmetryPlane(transformPlane( secondFace.getSymmetryPlane(), rotation, adjustment, transformationScaleValue)); }); secondFace.getFeaturePoints().parallelStream().forEach(fp -> { secondFace.setSymmetryPlane(transformPlane( secondFace.getSymmetryPlane(), pv.getTransformations().get(secondFace).getMatrixAsQuaternion( pv.getTransformations().get(secondFace).getIdentityMatrix()), pv.getTransformations().get(secondFace).getSuperImpositionAdjustment(), 1)); }); } return pv; } /** Loading Loading @@ -354,7 +353,45 @@ public class HumanFaceUtils { return new Plane(retPlane.getNormal(), dist); } // /** // * Moves feature points to calculated new position by computing procrustes // * analysis. {@link cz.fidentis.analyst.procrustes.ProcrustesAnalysis}. If // * feature point was not used in the analysis, it's position will be kept as // * it had in the original list. // * // * Move of the feature point is done to remove them from user vision first. // * // * @param secondFace // * @param movedFeaturePoints // */ // private static void moveFeaturePoints(HumanFace secondFace, Map<Integer, FeaturePoint> movedFeaturePoints) { // secondFace.getFeaturePoints().forEach(fp -> { // FeaturePoint movedFp = movedFeaturePoints.get(fp.getFeaturePointType().getType()); // 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(); // } // }); // } /** * Sorts List by featurePoint type property * * @param featurePointList * @return ordered list of feature points by type */ /* protected static List<FeaturePoint> sortListByFeaturePointType(List<FeaturePoint> featurePointList) { Collections.sort( featurePointList, Comparator.comparingInt(fp -> fp.getFeaturePointType().getType()) ); return featurePointList; } */ }
Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesAnalysis.java +126 −194 File changed.Preview size limit exceeded, changes collapsed. Show changes
Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesAnalysisFaceModel.java +51 −50 Original line number Diff line number Diff line Loading @@ -2,73 +2,64 @@ package cz.fidentis.analyst.procrustes; import cz.fidentis.analyst.face.HumanFace; import cz.fidentis.analyst.feature.FeaturePoint; import cz.fidentis.analyst.mesh.core.MeshModel; import cz.fidentis.analyst.mesh.core.MeshPoint; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; /** * Holds important face attributes that are required for procrustes analysis. * Holds important face attributes that are required for Procrustes analysis. * * @author Jakub Kolman */ public class ProcrustesAnalysisFaceModel { private final HumanFace humanFace; private HashMap<Integer, FeaturePoint> featurePointsMap; // sorted by feature point type private List<MeshPoint> vertices; private MeshModel meshModel; private final HashMap<Integer, Integer> featurePointTypeCorrespondence; /** * constructor creating inner attributes used in procrustes analysis from face * constructor creating inner attributes used in Procrustes analysis from * face * * @param face */ public ProcrustesAnalysisFaceModel(HumanFace face) { this.humanFace = face; // getFeaturePoints() returns unmodifiable List. To sort it we need to make copy first List<FeaturePoint> modifiableFeaturePointList = new ArrayList<>(face.getFeaturePoints()); this.featurePointsMap = createFeaturePointMap( ProcrustesUtils.sortListByFeaturePointType(modifiableFeaturePointList)); this.featurePointTypeCorrespondence = createFeaturePointTypeCorrespondence(face.getFeaturePoints()); } // 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()); /** * 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) { List<FeaturePoint> modifiableFeaturePointList = new ArrayList<>( getSelectionOfFeaturePoints(face.getFeaturePoints(), viablePoints)); this.vertices = modifiableVertices; this.meshModel = face.getMeshModel(); this.featurePointsMap = createFeaturePointMap( sortListByFeaturePointType(modifiableFeaturePointList)); ProcrustesUtils.sortListByFeaturePointType(modifiableFeaturePointList)); this.featurePointTypeCorrespondence = createFeaturePointTypeCorrespondence(face.getFeaturePoints()); } /** * sets feature points map and also sets feature point on human face for visualisation * Sets feature points map and also sets feature point on human face for * visualization. * * @param featurePointsMap */ public void setFeaturePointsMap(HashMap<Integer, FeaturePoint> featurePointsMap) { this.featurePointsMap = featurePointsMap; this.humanFace.setFeaturePoints(getFeaturePointValues()); } public MeshModel getMeshModel() { return meshModel; } public List<FeaturePoint> getFeaturePointValues() { return new ArrayList<>(this.getFeaturePointsMap().values()); } public void setVertices(List<MeshPoint> vertices) { this.vertices = vertices; } public List<MeshPoint> getVertices() { return vertices; } public HashMap<Integer, FeaturePoint> getFeaturePointsMap() { return featurePointsMap; } Loading @@ -78,9 +69,29 @@ public class ProcrustesAnalysisFaceModel { } /** * 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 after working with matrices where the type is not stored, * but instead are used matrix indexes. * Creates a subset of selected feature points that can be used for * Procrustes analysis. Each of the feature point types contained in the * subset is contained in both faces. * * @param featurePoints * @param viablePoints * @return */ 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 * after working with matrices where the type is not stored, but instead are * used matrix indexes. * * @param fpList * @return Loading @@ -93,24 +104,10 @@ public class ProcrustesAnalysisFaceModel { return map; } /** * Sorts List by featurePoint type property * * @param featurePointList * @return ordered list of feature points by type */ protected List<FeaturePoint> sortListByFeaturePointType(List<FeaturePoint> featurePointList) { Collections.sort( featurePointList, Comparator.comparingInt(fp -> fp.getFeaturePointType().getType()) ); return featurePointList; } /** * Creates feature point hash map for internal use in Procrustes Analysis. * * Map key is feature point type (Integer). * Value is feature point itself. * Map key is feature point type (Integer). Value is feature point itself. * * @param featurePointList * @return Loading @@ -118,7 +115,11 @@ public class ProcrustesAnalysisFaceModel { private HashMap<Integer, FeaturePoint> createFeaturePointMap(List<FeaturePoint> featurePointList) { HashMap<Integer, FeaturePoint> map = new HashMap<>(); for (FeaturePoint fp : featurePointList) { map.put(fp.getFeaturePointType().getType(), fp); map.put(fp.getFeaturePointType().getType(), new FeaturePoint( fp.getX(), fp.getY(), fp.getZ(), fp.getFeaturePointType())); } return map; } Loading
Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesTransformation.java 0 → 100644 +148 −0 Original line number Diff line number Diff line package cz.fidentis.analyst.procrustes; import cz.fidentis.analyst.icp.Quaternion; import javax.vecmath.Vector3d; import org.ejml.simple.SimpleMatrix; /** * Class used to store information about transformation that needs to be applied * to a second face when computing Procrustes analysis. * * @author Jakub Kolman */ public class ProcrustesTransformation { private double scale; // used for adjustment to move feature points (0,0,0) so the face is rotated around the centroid point private Vector3d centroidAdjustment; // used to move face to superimposed poisition over the original human face private Vector3d superImpositionAdjustment; private SimpleMatrix rotationMatrix; // /** // * constructor // * // * @param scale determines by how much should be faced enlarged or minimized // * @param adjustment determines how should be face moved to make faces // * superimposed considering feature points // * @param rotationMatrix determines how to rotate face // * @param updatedFaceModel holds information about adjusted face // */ // public ProcrustesTransformation( // double scale, // Vector3d centroidAdjustment, // Vector3d superImpositionAdjustment, // SimpleMatrix rotationMatrix, // HashMap<Integer, FeaturePoint> updatedFeaturePointsMap) { // this.scale = scale; // this.centroidAdjustment = centroidAdjustment; // this.superImpositionAdjustment = superImpositionAdjustment; // this.rotationMatrix = rotationMatrix; // this.featurePointsMap = featurePointsMap; // } // // /** // * Constructor (for more info take a look on // * {@link #ProcrustesTransformation(double, javax.vecmath.Vector3d, org.ejml.simple.SimpleMatrix, cz.fidentis.analyst.procrustes.ProcrustesAnalysisFaceModel) } // * // * @param adjustment // * @param rotationMatrix // */ // public ProcrustesTransformation( // Vector3d centroidAdjustment, // Vector3d superImpositionAdjustment, // SimpleMatrix rotationMatrix, // HashMap<Integer, FeaturePoint> updatedFeaturePointsMap) { // this(1d, centroidAdjustment, superImpositionAdjustment, rotationMatrix, updatedFeaturePointsMap); // } /** * Empty constructor with values that won't transform face. To set values later attribute setters can be used. * <pre> * rotation matrix is set to identity matrix * scale is 1 * adjustment is none * </pre> */ public ProcrustesTransformation() { this.scale = 1; this.centroidAdjustment = new Vector3d(0,0,0); this.superImpositionAdjustment = new Vector3d(0,0,0); SimpleMatrix r = new SimpleMatrix(3, 3); // identity matrix r.set(0, 0, 1); r.set(0, 1, 0); r.set(0, 2, 0); r.set(1, 0, 0); r.set(1, 1, 1); r.set(1, 2, 0); r.set(2, 0, 0); r.set(2, 1, 0); r.set(2, 2, 1); this.rotationMatrix = r; } public double getScale() { return this.scale; } public Vector3d getCentroidAdjustment() { return this.centroidAdjustment; } public Vector3d getSuperImpositionAdjustment() { return this.superImpositionAdjustment; } public SimpleMatrix getRotationMatrix() { return this.rotationMatrix; } public void setScale(double scale) { this.scale = scale; } /** * Calculates quaternion from rotation matrix. * * @return quaternion with rotation values */ public Quaternion getMatrixAsQuaternion(SimpleMatrix matrix) { if (matrix != null) { double w = Math.sqrt(1.0 + matrix.get(0, 0) + matrix.get(1, 1) + matrix.get(2, 2)) / 2.0; double w4 = (4.0 * w); double x = (matrix.get(2, 1) - matrix.get(1, 2)) / w4; double y = (matrix.get(0, 2) - matrix.get(2, 0)) / w4; double z = (matrix.get(1, 0) - matrix.get(0, 1)) / w4; return new Quaternion(x, y, z, w); } else { return null; } } public void setCentroidAdjustment(Vector3d centroidAdjustment) { this.centroidAdjustment = centroidAdjustment; } public void setSuperImpositionAdjustment(Vector3d superImpositionAdjustment) { this.superImpositionAdjustment = superImpositionAdjustment; } public void setRotationMatrix(SimpleMatrix rotationMatrix) { this.rotationMatrix = rotationMatrix; } public SimpleMatrix getIdentityMatrix() { SimpleMatrix r = new SimpleMatrix(3, 3); // identity matrix r.set(0, 0, 1); r.set(0, 1, 0); r.set(0, 2, 0); r.set(1, 0, 0); r.set(1, 1, 1); r.set(1, 2, 0); r.set(2, 0, 0); r.set(2, 1, 0); r.set(2, 2, 1); return r; } }
Comparison/src/main/java/cz/fidentis/analyst/procrustes/ProcrustesUtils.java 0 → 100644 +85 −0 Original line number Diff line number Diff line package cz.fidentis.analyst.procrustes; import cz.fidentis.analyst.Logger; import cz.fidentis.analyst.feature.FeaturePoint; import cz.fidentis.analyst.feature.api.IPosition; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.vecmath.Vector3d; /** * Utility functions that are used in multiple classes to compute * * @author Jakub Kolman */ public class ProcrustesUtils { /** * Sorts given list of feature points by their type * * @param featurePointList * @return sorted list of feature points */ public static List<FeaturePoint> sortListByFeaturePointType(List<FeaturePoint> featurePointList) { Collections.sort( featurePointList, Comparator.comparingInt(fp -> fp.getFeaturePointType().getType()) ); return featurePointList; } /** * Moves point by given vector value. * * @param <T> * @param point * @param vector */ public static <T extends IPosition> void movePoint(T point, Vector3d vector) { point.getPosition().x = point.getX() + vector.x; point.getPosition().y = point.getY() + vector.y; point.getPosition().z = point.getZ() + vector.z; } /** * Scales position of given point by multiplying its coordinates with given * scaleFactor. * * @param point * @param scaleFactor * @param <T> * @return */ public static <T extends IPosition> T scalePointDistance(T point, double scaleFactor) { point.getPosition().x = point.getX() * scaleFactor; point.getPosition().y = point.getY() * scaleFactor; point.getPosition().z = point.getZ() * scaleFactor; return point; } /** * Checks if two feature point lists have the same types of feature points. * <p> * To use this method you need to supply ordered lists by Feature Point type * as parameters. Otherwise even if two lists contain the same feature * points it will return false. * <p> * Use sort function sortListByFeaturePointType on feature point lists to * get correct results. * * @param featurePointList1 * @param featurePointList2 * @return true if two sorted lists by the feature point type contain the * same feature point types */ public static boolean checkFeaturePointsType(List<FeaturePoint> featurePointList1, List<FeaturePoint> featurePointList2) { for (int i = 0; i < featurePointList1.size(); i++) { if (featurePointList1.get(i).getFeaturePointType().getType() != featurePointList2.get(i).getFeaturePointType().getType()) { Logger.print(featurePointList1.get(i).getFeaturePointType().getName()); Logger.print(featurePointList1.get(i).getFeaturePointType().getName()); return false; } } return true; } }