Commit 63355ca1 authored by Jakub Kolman's avatar Jakub Kolman
Browse files

[#123] feat: reworking procrustes visitor to extend HumanFaceVisitor

parent 796d7224
Loading
Loading
Loading
Loading
+31 −29
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.vecmath.Matrix3d;
import javax.vecmath.Matrix4d;
import javax.vecmath.Point3d;
@@ -240,7 +241,8 @@ public class HumanFaceUtils {
        ProcrustesVisitor pv = new ProcrustesVisitor(firstFace, secondFace, scale);

        // superimpose face towards the static face
        secondFace.getMeshModel().compute(pv, false);
//        secondFace.getMeshModel().compute(pv, false);
        secondFace.accept(pv);

        // update k-d of transformed faces:
        if (secondFace.hasKdTree()) {
@@ -256,12 +258,12 @@ public class HumanFaceUtils {

        // transform feature points:
        // better option would be use of secondFace.setFeaturePoints(), but it doesnt repaint them to their new locations
        if (secondFace.hasFeaturePoints() && pv.getTransformation().getFaceModel().getFeaturePointsMap() != null) {
            moveFeaturePoints(secondFace, pv.getTransformation().getFaceModel().getFeaturePointsMap());
//        if (secondFace.hasFeaturePoints() && pv.getTransformation().getFeaturePointsMap()!= null) {
//            moveFeaturePoints(secondFace, pv.getTransformation().getFeaturePointsMap());

//            secondFace.setFeaturePoints(
//                    new ArrayList<>(pv.getTransformation().getFaceModel().getFeaturePointsMap().values()));
        }
//        }
        // transform symmetry plane:
        if (secondFace.hasSymmetryPlane()) {
            Vector3d adjustment = pv.getTransformation().getCentroidAdjustment();
@@ -364,31 +366,31 @@ 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, HashMap<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();
            }
        });
    }
//    /**
//     * 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
+85 −123
Original line number Diff line number Diff line
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,19 +7,27 @@ 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 java.util.Comparator;

/**
 * Procrustes analysis computing transformation of a face.
 * <p>
 * Superimposes two faces over each other so their all of the feature points are
 * as close to their corespondent type from the other face as they can be
 * without changing the general shape itself.
 * <p>
 * Transformations that class computes are adjustment by vector, scaling and
 * rotation of feature points. By computing these values they can later be
 * applied on the face (vertices of face).
 * <p>
 * For more information take a look at:
 * https://en.wikipedia.org/wiki/Procrustes_analysis
 *
 * @author Jakub Kolman
 */
public class ProcrustesAnalysis {
@@ -42,7 +49,7 @@ public class ProcrustesAnalysis {
     */
    public ProcrustesAnalysis(
            HumanFace humanFace1,
            HumanFace humanFace2) throws DataFormatException {
            HumanFace humanFace2) {

        ProcrustesAnalysisFaceModel model1;
        ProcrustesAnalysisFaceModel model2;
@@ -50,42 +57,20 @@ public class ProcrustesAnalysis {
        // check if feature points lists of both faces have the same size and same types
        // if not, than we will be working with subset or cancel the analysis
        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) {
                || !ProcrustesUtils.checkFeaturePointsType(
                        ProcrustesUtils.sortListByFeaturePointType(humanFace1.getFeaturePoints()),
                        ProcrustesUtils.sortListByFeaturePointType(humanFace1.getFeaturePoints()))) {
            ArrayList<Integer> viablePoints = getSubsetOfFeaturePoints(humanFace1.getFeaturePoints(), humanFace2.getFeaturePoints());
            model1 = new ProcrustesAnalysisFaceModel(humanFace1, viablePoints);
            model2 = new ProcrustesAnalysisFaceModel(humanFace2, viablePoints);
                Logger.print("Yes");
                Logger.print(Integer.toString(n));
            } 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.modelCentroid1 = findCentroidOfFeaturePoints(model1.getFeaturePointValues());
        this.modelCentroid2 = findCentroidOfFeaturePoints(model2.getFeaturePointValues());
        
        this.faceModel1 = model1;
        this.faceModel2 = model2;

    }

    /**
@@ -127,12 +112,9 @@ public class ProcrustesAnalysis {

            // calculation of scaling vector
            if (scale) {
                double scaleFactorValue = this.calculateScalingValue();
                if (scaleFactorValue != 1) {
//                    scaleFace(faceModel2, scaleFactorValue);
                    calculateScaledList(this.faceModel2.getFeaturePointValues(), scaleFactorValue);
                    transformation.setScale(scaleFactorValue);
                }
                double scaleFactor = this.calculateScalingValue();
                transformation.setScale(scaleFactor);
                calculateScaledList(faceModel2.getFeaturePointValues(), scaleFactor);
            }
            // calculation of rotation matrix
            transformation.setRotationMatrix(this.rotate());
@@ -140,11 +122,12 @@ public class ProcrustesAnalysis {
            // move face vertices back, so the original place, 
            // but set feture points map to already superimposed position
            moveFaceModel(faceModel1, new Vector3d(this.modelCentroid1.x, this.modelCentroid1.y, this.modelCentroid1.z));
            moveFaceModel(faceModel2,
                    new Vector3d(this.modelCentroid2.x, this.modelCentroid2.y, this.modelCentroid2.z),
                    new Vector3d(this.modelCentroid1.x, this.modelCentroid1.y, this.modelCentroid1.z));
            moveFaceModel(faceModel2, new Vector3d(this.modelCentroid1.x, this.modelCentroid1.y, this.modelCentroid1.z));
//            moveFaceModel(faceModel2,
//                    new Vector3d(this.modelCentroid2.x, this.modelCentroid2.y, this.modelCentroid2.z),
//                    new Vector3d(this.modelCentroid1.x, this.modelCentroid1.y, this.modelCentroid1.z));

            transformation.setFaceModel(faceModel2);
            transformation.setFeaturePointsMap(faceModel2.getFeaturePointsMap());

        } else {
            throw new DataFormatException("Faces have less than 3 feature points.");
@@ -152,13 +135,6 @@ public class ProcrustesAnalysis {
        return transformation;
    }

//    private Vector3d getPointDistance(Point3d point1, Point3d point2) {
//        if (point1 == null || point2 == null) {
//            return null;
//        }
//        return new Vector3d((point2.x - point1.x), (point2.y - point1.y), (point2.z - point1.z));
//    }

    /**
     * Moves all vertices and feature points of a face by given vector value
     *
@@ -167,45 +143,44 @@ public class ProcrustesAnalysis {
     */
    private void moveFaceModel(ProcrustesAnalysisFaceModel faceModel, Vector3d vector) {
        faceModel.getFeaturePointsMap().values().forEach(fp -> {
            movePoint(fp, vector);
        });
        faceModel.getVertices().forEach(v -> {
            movePoint(v, vector);
            ProcrustesUtils.movePoint(fp, vector);
        });
//        faceModel.getVertices().forEach(v -> {
//            movePoint(v, vector);
//        });
    }

    /**
     * Moves face model by given vector values.
     * Different vector value is used for vertices and feature points.
     * This method is used to fit feature points to already superimposed position over given face1, but move vertices 
     * of face back to original position of face so all the vertex movement is done by procrustes visitor.
     * Moves face model by given vector values. Different vector value is used
     * for vertices and feature points. This method is used to fit feature
     * points to already superimposed position over given face1, but move
     * vertices of face back to original position of face so all the vertex
     * movement is done by procrustes visitor.
     *
     * @param faceModel on which adjustment will be applied
     * @param vertexVector vertex adjustment
     * @param featurePointVector feature points adjustment
     */
    private void moveFaceModel(ProcrustesAnalysisFaceModel faceModel, Vector3d vertexVector, Vector3d featurePointVector) {
        faceModel.getFeaturePointsMap().values().forEach(fp -> {
            movePoint(fp, featurePointVector);
        });
        faceModel.getVertices().forEach(v -> {
            movePoint(v, vertexVector);
        });
    }

    /**
     * Moves point by given vector value.
     *
     * @param <T>
     * @param point
     * @param vector
     */
    private <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;
    }

//    private void moveFaceModel(ProcrustesAnalysisFaceModel faceModel, Vector3d vertexVector, Vector3d featurePointVector) {
//        faceModel.getFeaturePointsMap().values().forEach(fp -> {
//            movePoint(fp, featurePointVector);
//        });
////        faceModel.getVertices().forEach(v -> {
////            movePoint(v, vertexVector);
////        });
//    }
//    /**
//     * Moves point by given vector value.
//     *
//     * @param <T>
//     * @param point
//     * @param vector
//     */
//    private <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;
//    }
    /**
     * By rotation of matrices solves orthogonal procrustes problem.
     */
@@ -224,7 +199,6 @@ public class ProcrustesAnalysis {
                createFeaturePointMapFromMatrix(
                        primaryMatrix, this.faceModel2));

//        rotateVertices(this.faceModel2.getVertices(), rotationMatrix);
        return rotationMatrix;
    }

@@ -278,7 +252,6 @@ public class ProcrustesAnalysis {
//        calculateScaledList(faceModel.getVertices(), scaleFactor);
//        calculateScaledList(faceModel.getFeaturePointValues(), scaleFactor);
//    }

    /**
     * Scales each given point from list by multiplying its position coordinates
     * with scaleFactor.
@@ -290,10 +263,9 @@ public class ProcrustesAnalysis {
    private <T extends IPosition> void calculateScaledList(List<T> list, double scaleFactor) {
        List<T> scaledList = new ArrayList<>();
        for (T point : list) {
            scaledList.add(scalePointDistance(point, scaleFactor));
            scaledList.add(ProcrustesUtils.scalePointDistance(point, scaleFactor));
        }
    }

    /**
     * Rotates all vertices.
     * <p>
@@ -311,7 +283,6 @@ public class ProcrustesAnalysis {
//            }
//        }
//    }

    /**
     * Rotates vertex v by simulating matrix multiplication with given matrix
     *
@@ -332,23 +303,21 @@ public class ProcrustesAnalysis {
//        v.getPosition().y = y;
//        v.getPosition().z = z;
//    }

    /**
     * Scales position of given point by multiplying its coordinates with given
     * scaleFactor.
     *
     * @param point
     * @param scaleFactor
     * @param <T>
     * @return
     */
    private <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;
    }

//    /**
//     * Scales position of given point by multiplying its coordinates with given
//     * scaleFactor.
//     *
//     * @param point
//     * @param scaleFactor
//     * @param <T>
//     * @return
//     */
//    private <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>
@@ -364,16 +333,16 @@ public class ProcrustesAnalysis {
     * @return true if two sorted lists by the feature point type contain the
     * same feature point types
     */
    private 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()) {
                System.out.print(featurePointList1.get(i).getFeaturePointType().getType());
                System.out.print(featurePointList2.get(i).getFeaturePointType().getType());
                return false;
            }
        }
        return true;
    }
//    private 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()) {
//                System.out.print(featurePointList1.get(i).getFeaturePointType().getType());
//                System.out.print(featurePointList2.get(i).getFeaturePointType().getType());
//                return false;
//            }
//        }
//        return true;
//    }

    /**
     * Finds centroid from given feature point List
@@ -493,11 +462,4 @@ public class ProcrustesAnalysis {
        return vaiablePoints;
    }

    private List<FeaturePoint> sortListByFeaturePointType(List<FeaturePoint> featurePointList) {
        Collections.sort(
                featurePointList, Comparator.comparingInt(fp -> fp.getFeaturePointType().getType())
        );
        return featurePointList;
    }

}
+2 −120
Original line number Diff line number Diff line
@@ -2,11 +2,7 @@ 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;

@@ -19,8 +15,6 @@ public class ProcrustesAnalysisFaceModel {

    private final HumanFace humanFace;
    private HashMap<Integer, FeaturePoint> featurePointsMap;        // sorted by feature point type
    private List<MeshPoint> vertices;
    private final MeshModel meshModel;
    private final HashMap<Integer, Integer> featurePointTypeCorrespondence;

    /**
@@ -34,13 +28,8 @@ public class ProcrustesAnalysisFaceModel {
        // getFeaturePoints() returns unmodifiable List. To sort it we need to make copy first
        List<FeaturePoint> modifiableFeaturePointList = new ArrayList<>(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());

        this.vertices = modifiableVertices;
        this.meshModel = face.getMeshModel();
        this.featurePointsMap = createFeaturePointMap(
                sortListByFeaturePointType(modifiableFeaturePointList));
                ProcrustesUtils.sortListByFeaturePointType(modifiableFeaturePointList));
        this.featurePointTypeCorrespondence = createFeaturePointTypeCorrespondence(face.getFeaturePoints());
    }

@@ -59,44 +48,11 @@ public class ProcrustesAnalysisFaceModel {
        // 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));
                ProcrustesUtils.sortListByFeaturePointType(modifiableFeaturePointList));
        this.featurePointTypeCorrespondence = createFeaturePointTypeCorrespondence(face.getFeaturePoints());
    }

    /**
     * Use this constructor instead of the
     * {@link #ProcrustesAnalysisFaceModel(cz.fidentis.analyst.face.HumanFace, java.util.List)}
     * until the
     * {@link cz.fidentis.analyst.face.HumanFace#setFeaturePoints(java.util.List)}
     * is fixed and fires event to render in GUI a new set of feature points.
     *
     * Feature points that are not used in subset are moved to position of
     * modelCentroid.
     *
     * @param humanFace1
     * @param viablePoints
     * @param modelCentroid1
     */
//    ProcrustesAnalysisFaceModel(HumanFace face, ArrayList<Integer> viablePoints, Point3d modelCentroid) {
//        this.humanFace = face;
//        // getFeaturePoints() returns unmodifiable List. To sort it we need to make copy first
//        List<FeaturePoint> modifiableFeaturePointList = getSelectionOfFeaturePoints(face.getFeaturePoints(), viablePoints);
//
//        // 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
@@ -110,22 +66,10 @@ public class ProcrustesAnalysisFaceModel {
//        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;
    }
@@ -153,30 +97,6 @@ public class ProcrustesAnalysisFaceModel {
        return selection;
    }

    /**
     * {@link #getSelectionOfFeaturePoints(java.util.List, java.util.List)}
     * Feature points that are not in subset are moved to position of
     * modelCentroid.
     *
     * @param featurePoints
     * @param viablePoints
     * @param modelCentroid
     * @return
     */
//    private List<FeaturePoint> getSelectionOfFeaturePoints(List<FeaturePoint> featurePoints, ArrayList<Integer> viablePoints, Point3d modelCentroid) {
//        ArrayList<FeaturePoint> selection = new ArrayList<>();
//        featurePoints.forEach(fp -> {
//            if (viablePoints.contains(fp.getFeaturePointType().getType())) {
//                selection.add(fp);
//            } else {
//                fp.getPosition().x = modelCentroid.x;
//                fp.getPosition().y = modelCentroid.y;
//                fp.getPosition().z = modelCentroid.z;
//            }
//        });
//        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
@@ -194,19 +114,6 @@ 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.
     *
@@ -242,29 +149,4 @@ public class ProcrustesAnalysisFaceModel {
//    }
    }

//    /**
//     * 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 (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;
//            }
//        });
//    }

}
+50 −47

File changed.

Preview size limit exceeded, changes collapsed.

+90 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading