From b35e2db9e2e3f68db25c1fd7596a76cdd0cc19b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Radek=20O=C5=A1lej=C5=A1ek?= <oslejsek@fi.muni.cz>
Date: Tue, 15 Feb 2022 10:24:26 +0100
Subject: [PATCH] Resolve "Fix symmetry plane alignment"

---
 .../cz/fidentis/analyst/face/HumanFace.java   |   8 +-
 .../fidentis/analyst/face/HumanFaceUtils.java | 185 +++++-----
 .../fidentis/analyst/icp/IcpTransformer.java  |   1 -
 .../cz/fidentis/analyst/symmetry/Plane.java   | 193 +++++++++-
 .../fidentis/analyst/symmetry/PlaneTest.java  |  69 ++++
 .../cz/fidentis/analyst/batch/BatchPanel.java |   2 +-
 .../cz/fidentis/analyst/batch/IcpTask.java    |  15 +-
 .../registration/RegistrationAction.java      | 112 +++---
 .../registration/RegistrationPanel.form       | 240 +++++--------
 .../registration/RegistrationPanel.java       | 339 +++++++++---------
 .../analyst/registration/Bundle.properties    |  45 ++-
 .../analyst/mesh/core/MeshFacetImpl.java      |   7 +-
 .../fidentis/analyst/mesh/core/MeshModel.java |  24 +-
 .../fidentis/analyst/mesh/core/MeshPoint.java |   6 +
 .../analyst/mesh/core/MeshPointImpl.java      |   5 +
 15 files changed, 730 insertions(+), 521 deletions(-)

diff --git a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java
index 8e089d9c..12707870 100644
--- a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java
+++ b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java
@@ -23,6 +23,7 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.io.RandomAccessFile;
 import java.io.Serializable;
 import java.nio.file.Path;
 import java.util.Collections;
@@ -87,6 +88,10 @@ public class HumanFace implements Serializable {
         meshModel.simplifyModel();
         this.id = file.getCanonicalPath();
         
+        meshModel.getFacets().stream()
+                .filter(f -> !f.hasVertexNormals())
+                .forEach(f -> f.calculateVertexNormals());
+        
         updateBoundingBox();
         eventBus = new EventBus();
         
@@ -412,8 +417,9 @@ public class HumanFace implements Serializable {
     public File dumpToFile() throws IOException {
         File tempFile = File.createTempFile(this.getClass().getSimpleName(), ".ser");
         tempFile.deleteOnExit();
+        RandomAccessFile raf = new RandomAccessFile(tempFile, "rw");
         
-        try (ObjectOutputStream fos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(tempFile)))) {
+        try (ObjectOutputStream fos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(raf.getFD())))) {
             fos.writeObject(this);
             fos.flush();
         }
diff --git a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceUtils.java b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceUtils.java
index 3c91d5b5..700fff54 100644
--- a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceUtils.java
+++ b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceUtils.java
@@ -1,12 +1,14 @@
 package cz.fidentis.analyst.face;
 
+import cz.fidentis.analyst.Logger;
 import cz.fidentis.analyst.feature.FeaturePoint;
-import cz.fidentis.analyst.icp.EigenvalueDecomposition;
 import cz.fidentis.analyst.icp.IcpTransformer;
 import cz.fidentis.analyst.icp.NoUndersampling;
 import cz.fidentis.analyst.icp.Quaternion;
 import cz.fidentis.analyst.icp.RandomStrategy;
+import cz.fidentis.analyst.procrustes.ProcrustesAnalysis;
 import cz.fidentis.analyst.symmetry.Plane;
+import java.util.zip.DataFormatException;
 import javax.vecmath.Matrix3d;
 import javax.vecmath.Matrix4d;
 import javax.vecmath.Point3d;
@@ -22,7 +24,8 @@ import javax.vecmath.Vector3d;
 public class HumanFaceUtils {
     
     /**
-     * Superimpose two faces using ICP applied 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. 
@@ -146,64 +149,36 @@ public class HumanFaceUtils {
      * @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.
      */
-    public static void alignSymmetryPlanes(HumanFace staticFace, HumanFace transformedFace, boolean recomputeKdTree) {
+    public static void alignSymmetryPlanes(
+            HumanFace staticFace, HumanFace transformedFace, 
+            boolean recomputeKdTree, boolean preserveUpDir) {
+        
         Plane statPlane = staticFace.getSymmetryPlane();
         Plane tranPlane = transformedFace.getSymmetryPlane();
         
-        // Get "centroids" of the planes
-        Point3d statCentroid = statPlane.projectToPlane(staticFace.getBoundingBox().getMidPoint());
-        Point3d tranCentroid = tranPlane.projectToPlane(transformedFace.getBoundingBox().getMidPoint());
-        
-        // Compute a 3D transformation of planes. 
-        // However, this transforation rotates the face around the plane axis arbitrarily
-        Vector3d translation = new Vector3d(
-                statCentroid.x - tranCentroid.x,
-                statCentroid.y - tranCentroid.y, 
-                statCentroid.z - tranCentroid.z
-        );        
-        Quaternion rotation = computeRotation(tranPlane.getNormal(), statPlane.getNormal());
-        
-        // Compute rotation around plane's normal so that the face is oriented as before
-        
-        // Get a vector perpendicular to the transformed plane nornal (and then laying at the plane)
-        Point3d pp = getPerpendicularPoint(tranPlane); // point at the original plane
-        Vector3d pv = new Vector3d(pp);
-        pv.sub(tranCentroid);
-        pv.normalize();
-        
-        // transform the point so that it lays at the target plane
-        transformPoint(pp, rotation, translation, 1.0); // point at the transformed plane
-        transformPoint(tranCentroid, rotation, translation, 1.0); // point at the transformed plane
-        Vector3d trPV = new Vector3d(pp);
-        trPV.sub(tranCentroid);
-        trPV.normalize();
-
-        // compute angle of rotation around the plane normal so that the face is oriented as before
-        double cosRot = pv.dot(trPV);
-        pv.cross(pv, trPV);
-        double sinRot = pv.length(); 
-        Vector3d axis = (statPlane.getNormal().dot(tranPlane.getNormal()) < 0)
-                ? statPlane.getNormal()
-                : new Vector3d(-statPlane.getNormal().x, -statPlane.getNormal().y, -statPlane.getNormal().z);
+        if (statPlane.getNormal().dot(tranPlane.getNormal()) < 0) {
+            tranPlane = tranPlane.flip();
+        }
         
-        // get rotation matrix around the plane's normal
-        Matrix3d rotMat = rotMatAroundAxis(axis, sinRot, cosRot);
+        Matrix4d trMat = statPlane.getAlignmentMatrix(tranPlane, preserveUpDir);
         
         // Transform mesh vertices:
         transformedFace.getMeshModel().getFacets().forEach(f -> {
             f.getVertices().stream().forEach(p -> {
-                transformPoint(p.getPosition(), rotation, translation, 1.0);
-                rotMat.transform(p.getPosition()); // rotate around the plane's normal
+                trMat.transform(p.getPosition());
+                //transformPoint(p.getPosition(), rotation, translation, 1.0);
+                //rotMat.transform(p.getPosition()); // rotate around the plane's normal
             });
         });
         
         // update k-d of transformed face:
         if (transformedFace.hasKdTree()) { 
             if (recomputeKdTree) {
-                transformedFace.computeKdTree(true);
+               transformedFace.computeKdTree(true);
             } else {
-                transformedFace.removeKdTree();
+               transformedFace.removeKdTree();
             }
         }
         
@@ -213,16 +188,77 @@ public class HumanFaceUtils {
         // Transform feature points:
         if (transformedFace.hasFeaturePoints()) {
             transformedFace.getFeaturePoints().parallelStream().forEach(fp -> {
-                transformPoint(fp.getPosition(), rotation, translation, 1.0);
-                rotMat.transform(fp.getPosition()); // rotate around the plane's normal
+                trMat.transform(fp.getPosition());
+                //transformPoint(fp.getPosition(), rotation, translation, 1.0);
+                //rotMat.transform(fp.getPosition()); // rotate around the plane's normal
             });
         }        
         
-        // Transform the symmetry plane:
-        if (transformedFace.hasSymmetryPlane()) {
-            transformedFace.setSymmetryPlane(transformPlane(
-                    transformedFace.getSymmetryPlane(), rotation, translation, 1.0)
-            );
+        // Transform the symmetry plane
+        //transformedFace.setSymmetryPlane(new Plane(staticFace.getSymmetryPlane()));
+        Point3d p = transformedFace.getSymmetryPlane().getPlanePoint();
+        trMat.transform(p);
+        transformedFace.setSymmetryPlane(new Plane(p));
+    }
+    
+    /**
+     * 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.
+     */
+    public static void 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;
+        }
+        
+        // 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) {
+                secondFace.computeKdTree(true);
+            } else {
+                secondFace.removeKdTree();
+            }
+        }
+        
+        // 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
         }
     }
     
@@ -255,53 +291,6 @@ public class HumanFaceUtils {
         return rotMat;
     }
     
-    /**
-     * Computes a "random" point at the plan, i.e. a point, which is
-     * perpendicular to the plane's normal and lays at the plane.
-     * 
-     * @param plane the plane
-     * @return point at the plane
-     */
-    protected static Point3d getPerpendicularPoint(Plane plane) {
-        Point3d p;
-        if (plane.getNormal().x >= Math.max(plane.getNormal().y, plane.getNormal().z)) {
-            p = new Point3d(0, 1000, 0);
-        } else if (plane.getNormal().y >= Math.max(plane.getNormal().x, plane.getNormal().y)) {
-            p = new Point3d(1000, 0, 0);
-        } else {
-            p = new Point3d(0, 1000, 0);
-        }
-        return plane.projectToPlane(p);
-    }
-    
-    /**
-     * Computed 3D rotation matrix that transforms one vector onto another.
-     * @param trDir vector to be transformed
-     * @param targetDir target vector
-     * @return rotation
-     */
-    protected static Quaternion computeRotation(Vector3d trDir, Vector3d targetDir) {
-        Matrix4d multipleMatrix = new Matrix4d();
-        Matrix4d sumMatrixComp = new Matrix4d(
-                      0, -trDir.x, -trDir.y, -trDir.z,
-                trDir.x,        0,  trDir.z, -trDir.y,
-                trDir.y, -trDir.z,        0,  trDir.x,
-                trDir.z,  trDir.y, -trDir.x,        0
-        );
-        Matrix4d sumMatrixMain = new Matrix4d(
-                          0, -targetDir.x, -targetDir.y, -targetDir.z,
-                targetDir.x,            0, -targetDir.z,  targetDir.y,
-                targetDir.y,  targetDir.z,            0, -targetDir.x,
-                targetDir.z, -targetDir.y,  targetDir.x,            0
-        );
-        
-        multipleMatrix.mulTransposeLeft(sumMatrixComp, sumMatrixMain);
-        Quaternion rotation = new Quaternion(new EigenvalueDecomposition(multipleMatrix));
-        rotation.normalize();
-        return rotation;
-    }
-    
-    
     /**
      * Transform a single 3d point.
      * 
diff --git a/Comparison/src/main/java/cz/fidentis/analyst/icp/IcpTransformer.java b/Comparison/src/main/java/cz/fidentis/analyst/icp/IcpTransformer.java
index 345c62e1..bd9431a4 100644
--- a/Comparison/src/main/java/cz/fidentis/analyst/icp/IcpTransformer.java
+++ b/Comparison/src/main/java/cz/fidentis/analyst/icp/IcpTransformer.java
@@ -17,7 +17,6 @@ import java.util.Objects;
 import java.util.Set;
 import javax.vecmath.Matrix4d;
 import javax.vecmath.Point3d;
-import javax.vecmath.Tuple3d;
 import javax.vecmath.Tuple4d;
 import javax.vecmath.Vector3d;
 import javax.vecmath.Vector4d;
diff --git a/Comparison/src/main/java/cz/fidentis/analyst/symmetry/Plane.java b/Comparison/src/main/java/cz/fidentis/analyst/symmetry/Plane.java
index 2b9489bf..191c9767 100644
--- a/Comparison/src/main/java/cz/fidentis/analyst/symmetry/Plane.java
+++ b/Comparison/src/main/java/cz/fidentis/analyst/symmetry/Plane.java
@@ -4,6 +4,7 @@ import cz.fidentis.analyst.mesh.core.MeshRectangleFacet;
 import cz.fidentis.analyst.visitors.mesh.BoundingBox.BBox;
 import java.io.Serializable;
 import java.util.List;
+import javax.vecmath.Matrix4d;
 import javax.vecmath.Point3d;
 import javax.vecmath.Tuple3d;
 import javax.vecmath.Vector3d;
@@ -18,7 +19,6 @@ public class Plane implements Serializable {
    
     private Vector3d normal;
     private double   distance;
-    private double   normSquare; // normal.dot(normal)
 
     /**
      * Constructor.
@@ -41,6 +41,18 @@ public class Plane implements Serializable {
         this(plane.getNormal(), plane.getDistance());
     }
     
+    /**
+     * Constructor from a 3D point.
+     * 
+     * @param point point in space
+     * @throws IllegalArgumentException if the @code{plane} argument is null
+     */
+    public Plane(Tuple3d point) {
+        this.normal = new Vector3d(point);
+        setDistance(this.normal.length());
+        this.normal.normalize();
+    }
+    
     /**
      * Creates average plane from existing planes.
      * 
@@ -81,7 +93,6 @@ public class Plane implements Serializable {
         double normalLength = normal.length();
         normal.normalize();
         distance /= normalLength; // Do we really want this? --ro
-        this.normSquare = normal.dot(normal);
     }
     
     /**
@@ -111,16 +122,79 @@ public class Plane implements Serializable {
         ret.scale(distance);
         return ret;
     }
-
+    
     /**
-     * Translate the plane along its normal
-     *
-     * @param value
+     * Returns a normalized vector laying on the plane and directing up (toward the plus Y axis). 
+     * 
+     * @return a normalized vector laying on the plane and directing up 
+     *         (toward the plus Y axis) or {@code null}
      */
-    //public void shift(double value) {
-    //    this.distance += value;
-    //}
+    public Vector3d getUpPlanarVector() {
+        Vector3d v = new Vector3d(projectToPlane(new Point3d(0,1,0)));
+        v.sub(projectToPlane(new Point3d(0,0,0)));
+        if (v.length() == 0) {
+            return null;
+        }
+        v.normalize();
+        return v;
+    }
     
+    /**
+     * Returns a normalized vector laying on the plane and directing right (toward the plus X axis). 
+     * 
+     * @return a normalized vector laying on the plane and directing up 
+     *         (toward the plus X axis) or {@code null}
+     */
+    public Vector3d getRightPlanarVector() {
+        Vector3d v = new Vector3d(projectToPlane(new Point3d(1,0,0)));
+        v.sub(projectToPlane(new Point3d(0,0,0)));
+        if (v.length() == 0) {
+            return null;
+        }
+        v.normalize();
+        return v;
+    }
+    
+    /**
+     * Returns a normalized vector laying on the plane and directing ahead (toward the plus Z axis). 
+     * 
+     * @return a normalized vector laying on the plane and directing ahead 
+     *         (toward the plus Z axis) or {@code null}
+     */
+    public Vector3d getFrontPlanarVector() {
+        Vector3d v = new Vector3d(projectToPlane(new Point3d(0,0,1)));
+        v.sub(projectToPlane(new Point3d(0,0,0)));
+        if (v.length() == 0) {
+            return null;
+        }
+        v.normalize();
+        return v;
+    }
+    
+    /**
+     * Returns a point laying on the plane that is different
+     * from the {@link #getPlanePoint()}
+     * 
+     * @return another point laying on the plane
+     */
+    public Point3d getAnotherPoint() {
+        Point3d p = getPlanePoint();
+        double x = Math.abs(getNormal().x);
+        double y = Math.abs(getNormal().y);
+        double z = Math.abs(getNormal().z);
+        
+        if (x >= Math.max(y, z)) {
+            p.y += 1000;
+        } else if (y >= Math.max(x, y)) {
+            p.x += 1000;
+        } else {
+            p.z += 1000;
+        }
+        
+        return projectToPlane(p);
+    }
+
+
     /**
      * Translate the plane along its normal
      *
@@ -144,6 +218,64 @@ public class Plane implements Serializable {
         return ret;
     }
     
+    /**
+     * Computes and returns transformation matrix that transforms given plane
+     * into this plane.
+     * 
+     * @param other plane to be transformed
+     * @param forbidRotation If {@code false}, then the rotation around 
+     *        the normal is ignored.
+     * @return transformation matrix or {@code null}
+     */
+    public Matrix4d getAlignmentMatrix(Plane other, boolean forbidRotation) {
+        if (other == null) {
+            return null;
+        }
+        
+        Matrix4d retMat = getRotationAroundAxis(this.normal, other.getNormal(), null); // normal's rotation
+        
+        Point3d p = other.getPlanePoint();
+        retMat.transform(p);
+        Vector3d trDir = new Vector3d(this.getPlanePoint());
+        
+        if (p.equals(trDir)) { // no translation
+            return retMat;
+        }
+        
+        trDir.sub(p);
+        
+        Matrix4d trMat = new Matrix4d( // translation
+                0, 0, 0, trDir.x,
+                0, 0, 0, trDir.y,
+                0, 0, 0, trDir.z,
+                0, 0, 0, 0
+        );
+        retMat.add(trMat);
+        
+        if (forbidRotation) {
+            Vector3d myVec = this.getUpPlanarVector();
+            Vector3d otherVec = other.getUpPlanarVector();
+            if (myVec == null || otherVec == null) {
+                myVec = this.getRightPlanarVector();
+                otherVec = other.getRightPlanarVector();
+            }
+            if (myVec == null || otherVec == null) {
+                myVec = this.getFrontPlanarVector();
+                otherVec = other.getFrontPlanarVector();
+            }
+            
+            if (myVec != null && otherVec != null) {
+                retMat.transform(otherVec); // now, it lays on me with random orientation
+                Vector3d rotAxis = new Vector3d(myVec);
+                rotAxis.cross(rotAxis, otherVec);
+                Matrix4d axisTrMat = getRotationAroundAxis(myVec, otherVec, rotAxis);
+                retMat.mul(axisTrMat, retMat);
+            }
+        }
+        
+        return retMat;
+    }
+    
     /**
      * Returns a point laying on the plane.
      * 
@@ -248,7 +380,6 @@ public class Plane implements Serializable {
             throw new IllegalArgumentException("noraml");
         }
         this.normal = new Vector3d(normal);
-        this.normSquare = this.normal.dot(this.normal);
     }
     
     /**
@@ -297,4 +428,46 @@ public class Plane implements Serializable {
 
         return output;
     }
+    
+    /**
+     * Computes transformation matrix that rotates the given normal vector 
+     * so that it fits our normal vector.
+     * Based on https://math.stackexchange.com/questions/180418/calculate-rotation-matrix-to-align-vector-a-to-vector-b-in-3d
+     * 
+     * @param targetV Vector towards which we want to rotate the second vector
+     * @param trV Vector to be rotated
+     * @param axis Rotation axis. If {@code null}, then it's automatically computed
+     * @return transformation matrix
+     */
+    protected static Matrix4d getRotationAroundAxis(Vector3d targetV, Vector3d trV, Vector3d axis) {
+        Matrix4d rotMat = new Matrix4d();
+        rotMat.setIdentity();
+        
+        double cos = targetV.dot(trV); // cosine of the angle
+        if (cos == -1) { // exactly oposite direction
+            rotMat.mul(-1.0);
+            return rotMat;
+        }
+        
+        Vector3d k = axis;
+        if (k == null) {
+            k = new Vector3d();
+            k.cross(trV, targetV);
+        }
+        
+        Matrix4d cpMat = new Matrix4d( // skew-symmetric cross-product matrix of v
+                 0.0, -k.z,  k.y, 0.0,
+                 k.z,  0.0, -k.x, 0.0,
+                -k.y,  k.x,  0.0, 0.0,
+                 0.0,  0.0,  0.0, 0.0
+        );
+        rotMat.add(cpMat);
+        
+        cpMat.mul(cpMat);
+        cpMat.mul(1.0 / (1.0 + cos));
+        rotMat.add(cpMat);
+        
+        return rotMat;
+    }
+    
 }
diff --git a/Comparison/src/test/java/cz/fidentis/analyst/symmetry/PlaneTest.java b/Comparison/src/test/java/cz/fidentis/analyst/symmetry/PlaneTest.java
index 1e053ae7..3d24252b 100644
--- a/Comparison/src/test/java/cz/fidentis/analyst/symmetry/PlaneTest.java
+++ b/Comparison/src/test/java/cz/fidentis/analyst/symmetry/PlaneTest.java
@@ -126,4 +126,73 @@ public class PlaneTest {
         Assertions.assertEquals(-6, p.getPointDistance(new Point3d(-9, -9, -9)));
         Assertions.assertEquals(3, p.getPointDistance(new Point3d(0, 0, 0)));
     }
+    
+    @Test
+    public void getRotationAroundAxis() {
+        Plane p = new Plane(new Vector3d(1, 0, 0), 0);
+        
+        Vector3d n = new Vector3d(1, 0, 0);
+        Plane.getRotationAroundAxis(p.getNormal(), n, null).transform(n);
+        Assertions.assertEquals(p.getNormal(), n);
+        
+        n = new Vector3d(-1, 0, 0);
+        Plane.getRotationAroundAxis(p.getNormal(), n, null).transform(n);
+        Assertions.assertEquals(p.getNormal(), n);
+        
+        n = new Vector3d(0, 1, 0);
+        Plane.getRotationAroundAxis(p.getNormal(), n, null).transform(n);
+        Assertions.assertEquals(p.getNormal(), n);
+        
+        n = new Vector3d(0, -1, 0);
+        Plane.getRotationAroundAxis(p.getNormal(), n, null).transform(n);
+        Assertions.assertEquals(p.getNormal(), n);
+        
+        n = new Vector3d(1, 1, 1);
+        n.normalize();
+        Plane.getRotationAroundAxis(p.getNormal(), n, null).transform(n);
+        Assertions.assertTrue(p.getNormal().epsilonEquals(n, 0.001));
+        
+        n = new Vector3d(-1, -1, -1);
+        n.normalize();
+        Plane.getRotationAroundAxis(p.getNormal(), n, null).transform(n);
+        Assertions.assertTrue(p.getNormal().epsilonEquals(n, 0.001));
+        
+    }
+    
+    @Test
+    public void getAlignmentMatrix() {
+        Vector3d n1 = new Vector3d(1, 0, 0);
+        n1.normalize();
+        Plane plane1 = new Plane(n1, 3);
+        
+        Vector3d n2 = new Vector3d(0, 1, 0);
+        n2.normalize();
+        Plane plane2 = new Plane(n2, 3);
+        Point3d p = plane2.getPlanePoint();
+        plane1.getAlignmentMatrix(plane2, true).transform(p);
+        Assertions.assertEquals(plane1.getPlanePoint(), p);
+        
+        n2 = new Vector3d(0, 1, 0);
+        n2.normalize();
+        plane2 = new Plane(n2, -3);
+        p = plane2.getPlanePoint();
+        plane1.getAlignmentMatrix(plane2, true).transform(p);
+        Assertions.assertEquals(plane1.getPlanePoint(), p);
+        
+        n2 = new Vector3d(-1, -1, -1);
+        n2.normalize();
+        plane2 = new Plane(n2, -10);
+        p = plane2.getPlanePoint();
+        plane1.getAlignmentMatrix(plane2, true).transform(p);
+        //Assertions.assertEquals(plane1.getPlanePoint(), p);
+        Assertions.assertTrue(plane1.getPlanePoint().epsilonEquals(p, 0.001));
+        
+        n2 = new Vector3d(-1, -1, -1);
+        n2.normalize();
+        plane2 = new Plane(n2, 10);
+        p = plane2.getPlanePoint();
+        plane1.getAlignmentMatrix(plane2, true).transform(p);
+        //Assertions.assertEquals(plane1.getPlanePoint(), p);
+        Assertions.assertTrue(plane1.getPlanePoint().epsilonEquals(p, 0.001));
+    }
 }
diff --git a/GUI/src/main/java/cz/fidentis/analyst/batch/BatchPanel.java b/GUI/src/main/java/cz/fidentis/analyst/batch/BatchPanel.java
index 50177304..955780a0 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/batch/BatchPanel.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/batch/BatchPanel.java
@@ -281,7 +281,7 @@ public class BatchPanel extends ControlPanel {
         );
     }
     
-        private void exportAvgFace() {
+    private void exportAvgFace() {
         if (!haveAvgFace) {
             return;
         }
diff --git a/GUI/src/main/java/cz/fidentis/analyst/batch/IcpTask.java b/GUI/src/main/java/cz/fidentis/analyst/batch/IcpTask.java
index d82266ed..d3146792 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/batch/IcpTask.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/batch/IcpTask.java
@@ -98,8 +98,21 @@ public class IcpTask extends SwingWorker<MeshModel, HumanFace> {
                 String faceId = factory.loadFace(faces.get(i).toFile());
                 HumanFace face = factory.getFace(faceId);
                 loadTime.stop();
-
+                
                 if (computeICP) { // ICP registration - face is transformed!
+                    
+                    /*
+                    //////////////////////
+                    SymmetryEstimator se = new SymmetryEstimator(new SymmetryConfig());
+                    face.getMeshModel().compute(se);
+                    face.setSymmetryPlane(se.getSymmetryPlane());
+                    se = new SymmetryEstimator(new SymmetryConfig());
+                    initFace.getMeshModel().compute(se);
+                    initFace.setSymmetryPlane(se.getSymmetryPlane());
+                    HumanFaceUtils.alignSymmetryPlanes(initFace, face, computeAvgFace, true);
+                    //////////////////////
+                    */
+                    
                     icpComputationTime.start();
                     HumanFaceUtils.alignMeshes(
                             initFace,
diff --git a/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationAction.java b/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationAction.java
index a18d4d5e..70cb46e7 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationAction.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationAction.java
@@ -3,17 +3,14 @@ package cz.fidentis.analyst.registration;
 import cz.fidentis.analyst.Logger;
 import cz.fidentis.analyst.canvas.Canvas;
 import cz.fidentis.analyst.core.ControlPanelAction;
-import cz.fidentis.analyst.face.HumanFace;
 import cz.fidentis.analyst.face.HumanFaceUtils;
 import cz.fidentis.analyst.face.events.HausdorffDistanceComputed;
 import cz.fidentis.analyst.face.events.HumanFaceEvent;
 import cz.fidentis.analyst.face.events.HumanFaceListener;
 import cz.fidentis.analyst.face.events.HumanFaceTransformedEvent;
-import cz.fidentis.analyst.procrustes.ProcrustesAnalysis;
+import cz.fidentis.analyst.face.events.SymmetryPlaneChangedEvent;
 import java.awt.Color;
 import java.awt.event.ActionEvent;
-import java.util.zip.DataFormatException;
-import javax.swing.JCheckBox;
 import javax.swing.JFormattedTextField;
 import javax.swing.JOptionPane;
 import javax.swing.JTabbedPane;
@@ -48,14 +45,10 @@ public class RegistrationAction extends ControlPanelAction implements HumanFaceL
     /*
      * Attributes handling the state
      */
-    private boolean scale = true;
-    private int maxIterations = 10;
-    private double error = 0.3;
     private String strategy = RegistrationPanel.STRATEGY_POINT_TO_POINT;
     private boolean relativeDist = false;
     private boolean heatmapRender = false;
-    private boolean procrustesScalingEnabled = false;
-
+    
     /*
      * Coloring threshold and statistical values of feature point distances:
      */
@@ -91,19 +84,26 @@ public class RegistrationAction extends ControlPanelAction implements HumanFaceL
         // Be informed about changes in faces perfomed by other GUI elements
         getPrimaryDrawableFace().getHumanFace().registerListener(this);
         getSecondaryDrawableFace().getHumanFace().registerListener(this);
+        
+        controlPanel.setEnabledPlanesButton(
+                getCanvas().getPrimaryFace().hasSymmetryPlane() &&
+                        getCanvas().getSecondaryFace().hasSymmetryPlane()
+        );
+        
+        controlPanel.setEnabledProcrustesButton(
+                getCanvas().getPrimaryFace().hasFeaturePoints() &&
+                        getCanvas().getSecondaryFace().hasFeaturePoints()
+        );
     }
 
     @Override
     public void actionPerformed(ActionEvent ae) {
-        double value;
         String action = ae.getActionCommand();
 
-//        OutputWindow.print(ae.getActionCommand());
         switch (action) {
             case RegistrationPanel.ACTION_COMMAND_APPLY_ICP:
                 applyICP();
                 highlightCloseFeaturePoints();
-                //announceMeshChange(getSecondaryDrawableFace());
                 getCanvas().getSecondaryFace().announceEvent(new HumanFaceTransformedEvent(
                         getCanvas().getSecondaryFace(), "", this)
                 );
@@ -125,24 +125,11 @@ public class RegistrationAction extends ControlPanelAction implements HumanFaceL
                 getCanvas().getSecondaryFace().announceEvent(new HumanFaceTransformedEvent(
                         getCanvas().getSecondaryFace(), "", this, true) // finished transformation
                 );
-                //announceMeshChange(getSecondaryDrawableFace());
                 break;
             case RegistrationPanel.ACTION_COMMAND_FP_CLOSENESS_THRESHOLD:
                 fpThreshold = ((Number) (((JFormattedTextField) ae.getSource()).getValue())).doubleValue();
                 highlightCloseFeaturePoints();
                 break;
-            case RegistrationPanel.ACTION_COMMAND_ICP_SCALE:
-                this.scale = ((JCheckBox) ae.getSource()).isSelected();
-                break;
-            case RegistrationPanel.ACTION_COMMAND_ICP_MAX_ITERATIONS:
-                maxIterations = ((Number) (((JFormattedTextField) ae.getSource()).getValue())).intValue();
-                break;
-            case RegistrationPanel.ACTION_COMMAND_ICP_ERROR:
-                error = ((Number) (((JFormattedTextField) ae.getSource()).getValue())).doubleValue();
-                break;
-            case RegistrationPanel.ACTION_COMMAND_PROCRUSTES_SCALE:
-                this.procrustesScalingEnabled = ((JCheckBox) ae.getSource()).isSelected();
-                break;
             case RegistrationPanel.ACTION_COMMAND_PROCRUSTES_APPLY:
                 applyProcrustes();
                 highlightCloseFeaturePoints();
@@ -152,8 +139,6 @@ public class RegistrationAction extends ControlPanelAction implements HumanFaceL
                 getCanvas().getSecondaryFace().announceEvent(new HumanFaceTransformedEvent(
                         getCanvas().getSecondaryFace(), "", this)
                 );
-                //announceMeshChange(getPrimaryDrawableFace());
-                //announceMeshChange(getSecondaryDrawableFace());
                 break;
             case RegistrationPanel.ACTION_COMMAND_ALIGN_SYMMETRY_PLANES:
                 alignSymmetryPlanes();
@@ -161,7 +146,6 @@ public class RegistrationAction extends ControlPanelAction implements HumanFaceL
                 getCanvas().getSecondaryFace().announceEvent(new HumanFaceTransformedEvent(
                         getCanvas().getSecondaryFace(), "", this)
                 );
-                //announceMeshChange(getSecondaryDrawableFace());
                 break;
             default:
                 // do nothing
@@ -183,6 +167,13 @@ public class RegistrationAction extends ControlPanelAction implements HumanFaceL
                     hdEvent.getWeightedHusdorffDistStats()
             );
         }
+        
+        if (event instanceof SymmetryPlaneChangedEvent) {
+            controlPanel.setEnabledPlanesButton(
+                    getCanvas().getPrimaryFace().hasSymmetryPlane() &&
+                            getCanvas().getSecondaryFace().hasSymmetryPlane()
+            );
+        }
     }
     
     /**
@@ -196,27 +187,27 @@ public class RegistrationAction extends ControlPanelAction implements HumanFaceL
      * If the {@code procrustesScalingEnabled} attribute is set to true, then one of the faces is scaled as well.
      */
     protected void applyProcrustes() {
+        JOptionPane.showMessageDialog(
+                controlPanel,
+                "This method is not finished yet",
+                "Neither feature points nor symmetry planes are transformed properly",
+                JOptionPane.INFORMATION_MESSAGE);
+        
         Logger out = Logger.measureTime();
-
-        HumanFace primaryFace = getCanvas().getPrimaryFace();
-        HumanFace secondaryFace = getCanvas().getSecondaryFace();
-
-        try {
-            ProcrustesAnalysis procrustesAnalysisInit = new ProcrustesAnalysis(primaryFace, secondaryFace, this.procrustesScalingEnabled);
-            procrustesAnalysisInit.analyze();
-            primaryFace.removeKdTree(); // invalidate k-d tree, if exists
-            secondaryFace.removeKdTree(); // invalidate k-d tree, if exists
-            highlightCloseFeaturePoints();
-        } catch (DataFormatException e) {
-            Logger.print("Procrustes Analysis experienced exception");
-        }
+        
+        HumanFaceUtils.alignFeaturePoints(
+                getCanvas().getPrimaryFace(), 
+                getCanvas().getSecondaryFace(), 
+                controlPanel.getScaleParam(), 
+                false // drop k-d tree, if exists
+        );
 
         out.printDuration("Procrustes for models with "
-                + getPrimaryDrawableFace().getHumanFace().getMeshModel().getNumVertices()
+                + getCanvas().getPrimaryFace().getMeshModel().getNumVertices()
                 + "/"
-                + getSecondaryDrawableFace().getHumanFace().getMeshModel().getNumVertices()
+                + getCanvas().getSecondaryFace().getMeshModel().getNumVertices()
                 + " vertices and "
-                + getPrimaryDrawableFace().getHumanFace().getFeaturePoints().size()
+                + getCanvas().getPrimaryFace().getFeaturePoints().size()
                 + " feature points"
         );
     }
@@ -225,19 +216,19 @@ public class RegistrationAction extends ControlPanelAction implements HumanFaceL
         Logger out = Logger.measureTime();
         
         HumanFaceUtils.alignMeshes(
-                getPrimaryDrawableFace().getHumanFace(),
-                getSecondaryDrawableFace().getHumanFace(), // is transformed
-                maxIterations,
-                scale,
-                error,
+                getCanvas().getPrimaryFace(),
+                getCanvas().getSecondaryFace(), // is transformed
+                controlPanel.getMaxIcpIterParam(),
+                controlPanel.getScaleParam(),
+                controlPanel.getMinIcpErrorParam(),
                 controlPanel.getIcpUndersampling(),
                 false // drop k-d tree, if exists
         );
         
         out.printDuration("ICP for models with "
-                + getPrimaryDrawableFace().getHumanFace().getMeshModel().getNumVertices()
+                + getCanvas().getPrimaryFace().getMeshModel().getNumVertices()
                 + "/"
-                + getSecondaryDrawableFace().getHumanFace().getMeshModel().getNumVertices()
+                + getCanvas().getSecondaryFace().getMeshModel().getNumVertices()
                 + " vertices"
         );
     }
@@ -279,27 +270,12 @@ public class RegistrationAction extends ControlPanelAction implements HumanFaceL
         // to do: show ACF dist
     }
 
-    //protected void announceMeshChange(DrawableFace face) {
-    //    if (face != null) {
-    //        face.getHumanFace().announceEvent(new MeshChangedEvent(face.getHumanFace(), face.getHumanFace().getShortName(), this));
-    //    }
-    //}
-
     private void alignSymmetryPlanes() {
-        if (!getPrimaryDrawableFace().getHumanFace().hasSymmetryPlane() 
-                || !getSecondaryDrawableFace().getHumanFace().hasSymmetryPlane()) {
-            
-            JOptionPane.showMessageDialog(controlPanel,
-                    "Compute symmetry planes first",
-                    "No symmerty planes",
-                    JOptionPane.INFORMATION_MESSAGE);
-            return;
-        }
-        
         HumanFaceUtils.alignSymmetryPlanes(
                 getPrimaryDrawableFace().getHumanFace(),
                 getSecondaryDrawableFace().getHumanFace(), // is transformed
-                false // drops k-d tree, if exists
+                false, // drops k-d tree, if exists
+                true   // forbid rotation around normal
         );
     }
 }
diff --git a/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationPanel.form b/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationPanel.form
index 754e92b2..d7774fda 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationPanel.form
+++ b/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationPanel.form
@@ -47,7 +47,6 @@
                   <Component id="jPanel1" alignment="0" max="32767" attributes="0"/>
                   <Component id="jPanel4" alignment="0" max="32767" attributes="0"/>
                   <Component id="transformationPanel" alignment="0" min="-2" pref="585" max="-2" attributes="0"/>
-                  <Component id="jPanel7" max="32767" attributes="0"/>
               </Group>
               <EmptySpace max="32767" attributes="0"/>
               <Component id="jSeparator7" min="-2" pref="50" max="-2" attributes="0"/>
@@ -69,18 +68,16 @@
                   </Group>
                   <Group type="102" alignment="0" attributes="0">
                       <EmptySpace max="-2" attributes="0"/>
-                      <Component id="jPanel7" min="-2" max="-2" attributes="0"/>
+                      <Component id="jPanel1" min="-2" max="-2" attributes="0"/>
                   </Group>
               </Group>
               <EmptySpace max="-2" attributes="0"/>
-              <Component id="jPanel1" min="-2" max="-2" attributes="0"/>
-              <EmptySpace max="-2" attributes="0"/>
-              <Component id="transformationPanel" min="-2" pref="176" max="-2" attributes="0"/>
+              <Component id="transformationPanel" min="-2" pref="134" max="-2" attributes="0"/>
               <EmptySpace max="-2" attributes="0"/>
               <Component id="jPanel4" min="-2" max="-2" attributes="0"/>
               <EmptySpace max="-2" attributes="0"/>
               <Component id="jPanel2" min="-2" max="-2" attributes="0"/>
-              <EmptySpace min="-2" pref="328" max="-2" attributes="0"/>
+              <EmptySpace min="-2" pref="451" max="-2" attributes="0"/>
               <Component id="jSeparator11" min="-2" pref="10" max="-2" attributes="0"/>
               <EmptySpace min="-2" pref="221" max="-2" attributes="0"/>
               <Component id="jSeparator2" min="-2" pref="10" max="-2" attributes="0"/>
@@ -120,10 +117,6 @@
                           <EmptySpace min="-2" pref="580" max="-2" attributes="0"/>
                           <Component id="shiftPanel" min="-2" max="-2" attributes="0"/>
                       </Group>
-                      <Group type="102" alignment="0" attributes="0">
-                          <EmptySpace max="-2" attributes="0"/>
-                          <Component id="jButton3" min="-2" max="-2" attributes="0"/>
-                      </Group>
                       <Group type="102" alignment="0" attributes="0">
                           <Component id="translationPanel" min="-2" max="-2" attributes="0"/>
                           <EmptySpace max="-2" attributes="0"/>
@@ -146,8 +139,6 @@
                       <Component id="translationPanel" alignment="0" max="32767" attributes="0"/>
                   </Group>
                   <EmptySpace max="32767" attributes="0"/>
-                  <Component id="jButton3" min="-2" max="-2" attributes="0"/>
-                  <EmptySpace min="-2" pref="59" max="-2" attributes="0"/>
                   <Component id="shiftPanel" min="-2" max="-2" attributes="0"/>
                   <EmptySpace max="32767" attributes="0"/>
                   <Component id="jSeparator5" min="-2" pref="10" max="-2" attributes="0"/>
@@ -883,13 +874,6 @@
         </Container>
         <Component class="javax.swing.JSeparator" name="jSeparator5">
         </Component>
-        <Component class="javax.swing.JButton" name="jButton3">
-          <Properties>
-            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-              <ResourceString bundle="cz/fidentis/analyst/registration/Bundle.properties" key="RegistrationPanel.jButton3.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-            </Property>
-          </Properties>
-        </Component>
       </SubComponents>
     </Container>
     <Component class="javax.swing.JSeparator" name="jSeparator2">
@@ -898,7 +882,7 @@
       <Properties>
         <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
           <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
-            <TitledBorder title="Mesh alignment (ICP):">
+            <TitledBorder title="Auto-alignment">
               <ResourceString PropertyName="titleX" bundle="cz/fidentis/analyst/registration/Bundle.properties" key="RegistrationPanel.jPanel1.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
               <Font PropertyName="font" name="Dialog" size="14" style="1"/>
             </TitledBorder>
@@ -915,74 +899,70 @@
               <Group type="102" attributes="0">
                   <EmptySpace max="-2" attributes="0"/>
                   <Group type="103" groupAlignment="0" attributes="0">
-                      <Group type="102" attributes="0">
-                          <Group type="103" groupAlignment="0" attributes="0">
-                              <Group type="102" alignment="0" attributes="0">
-                                  <Component id="jLabel7" min="-2" max="-2" attributes="0"/>
-                                  <EmptySpace max="-2" attributes="0"/>
-                                  <Component id="jCheckBox1" min="-2" max="-2" attributes="0"/>
-                                  <EmptySpace type="separate" max="-2" attributes="0"/>
-                                  <Component id="jLabel5" min="-2" max="-2" attributes="0"/>
-                                  <EmptySpace max="-2" attributes="0"/>
-                                  <Component id="jFormattedTextField1" min="-2" pref="49" max="-2" attributes="0"/>
-                              </Group>
-                              <Component id="comboSliderInteger1" alignment="0" min="-2" max="-2" attributes="0"/>
-                          </Group>
-                          <EmptySpace min="-2" pref="12" max="-2" attributes="0"/>
-                          <Group type="103" groupAlignment="0" attributes="0">
-                              <Group type="102" attributes="0">
-                                  <Component id="jLabel6" min="-2" max="-2" attributes="0"/>
-                                  <EmptySpace max="-2" attributes="0"/>
-                                  <Component id="jFormattedTextField2" min="-2" pref="53" max="-2" attributes="0"/>
-                              </Group>
-                              <Component id="jLabel8" min="-2" max="-2" attributes="0"/>
-                          </Group>
+                      <Group type="102" alignment="0" attributes="0">
+                          <Component id="jButton1" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace max="-2" attributes="0"/>
+                          <Component id="jButton2" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace max="-2" attributes="0"/>
+                          <Component id="jButton3" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace type="unrelated" max="-2" attributes="0"/>
+                          <Component id="jButtonInfo1" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <Group type="102" alignment="0" attributes="0">
+                          <Component id="jCheckBox1" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace type="separate" max="-2" attributes="0"/>
+                          <Component id="jLabel5" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace max="-2" attributes="0"/>
+                          <Component id="jFormattedTextField1" min="-2" pref="49" max="-2" attributes="0"/>
+                          <EmptySpace type="separate" max="-2" attributes="0"/>
+                          <Component id="jLabel6" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace max="-2" attributes="0"/>
+                          <Component id="jFormattedTextField2" min="-2" pref="53" max="-2" attributes="0"/>
                       </Group>
-                      <Component id="jButton1" alignment="0" min="-2" max="-2" attributes="0"/>
                   </Group>
                   <EmptySpace max="32767" attributes="0"/>
               </Group>
+              <Group type="102" alignment="0" attributes="0">
+                  <Component id="comboSliderInteger1" min="-2" max="-2" attributes="0"/>
+                  <EmptySpace min="-2" pref="12" max="-2" attributes="0"/>
+                  <Component id="jLabel8" min="-2" max="-2" attributes="0"/>
+                  <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
+              </Group>
           </Group>
         </DimensionLayout>
         <DimensionLayout dim="1">
           <Group type="103" groupAlignment="0" attributes="0">
               <Group type="102" alignment="0" attributes="0">
                   <EmptySpace max="-2" attributes="0"/>
-                  <Group type="103" groupAlignment="0" attributes="0">
-                      <Component id="jLabel7" min="-2" max="-2" attributes="0"/>
-                      <Component id="jCheckBox1" min="-2" max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="0" max="-2" attributes="0">
                       <Group type="103" groupAlignment="3" attributes="0">
-                          <Component id="jLabel5" alignment="3" min="-2" max="-2" attributes="0"/>
-                          <Component id="jFormattedTextField1" alignment="3" min="-2" pref="22" max="-2" attributes="0"/>
-                          <Component id="jLabel6" alignment="3" min="-2" max="-2" attributes="0"/>
-                          <Component id="jFormattedTextField2" alignment="3" min="-2" pref="20" max="-2" attributes="0"/>
+                          <Component id="jButton1" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="jButton2" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="jButton3" alignment="3" min="-2" max="-2" attributes="0"/>
                       </Group>
+                      <Component id="jButtonInfo1" pref="0" max="32767" attributes="0"/>
                   </Group>
+                  <EmptySpace pref="16" max="32767" attributes="0"/>
+                  <Group type="103" groupAlignment="3" attributes="0">
+                      <Component id="jCheckBox1" alignment="3" min="-2" max="-2" attributes="0"/>
+                      <Component id="jLabel5" alignment="3" min="-2" max="-2" attributes="0"/>
+                      <Component id="jFormattedTextField1" alignment="3" min="-2" pref="22" max="-2" attributes="0"/>
+                      <Component id="jLabel6" alignment="3" min="-2" max="-2" attributes="0"/>
+                      <Component id="jFormattedTextField2" alignment="3" min="-2" pref="20" max="-2" attributes="0"/>
+                  </Group>
+                  <EmptySpace max="-2" attributes="0"/>
                   <Group type="103" groupAlignment="0" attributes="0">
-                      <Group type="102" attributes="0">
-                          <EmptySpace min="-2" pref="5" max="-2" attributes="0"/>
-                          <Component id="comboSliderInteger1" min="-2" max="-2" attributes="0"/>
-                      </Group>
+                      <Component id="comboSliderInteger1" alignment="0" min="-2" max="-2" attributes="0"/>
                       <Group type="102" alignment="0" attributes="0">
-                          <EmptySpace min="-2" pref="26" max="-2" attributes="0"/>
+                          <EmptySpace min="-2" pref="21" max="-2" attributes="0"/>
                           <Component id="jLabel8" min="-2" max="-2" attributes="0"/>
                       </Group>
                   </Group>
-                  <EmptySpace max="-2" attributes="0"/>
-                  <Component id="jButton1" min="-2" max="-2" attributes="0"/>
-                  <EmptySpace max="32767" attributes="0"/>
               </Group>
           </Group>
         </DimensionLayout>
       </Layout>
       <SubComponents>
-        <Component class="javax.swing.JLabel" name="jLabel7">
-          <Properties>
-            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-              <ResourceString bundle="cz/fidentis/analyst/registration/Bundle.properties" key="RegistrationPanel.jLabel7.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-            </Property>
-          </Properties>
-        </Component>
         <Component class="javax.swing.JCheckBox" name="jCheckBox1">
           <Properties>
             <Property name="selected" type="boolean" value="true"/>
@@ -1035,6 +1015,8 @@
             </Property>
           </Properties>
         </Component>
+        <Component class="cz.fidentis.analyst.core.ComboSliderInteger" name="comboSliderInteger1">
+        </Component>
         <Component class="javax.swing.JButton" name="jButton1">
           <Properties>
             <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
@@ -1051,7 +1033,51 @@
             <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton1ActionPerformed"/>
           </Events>
         </Component>
-        <Component class="cz.fidentis.analyst.core.ComboSliderInteger" name="comboSliderInteger1">
+        <Component class="javax.swing.JButton" name="jButton2">
+          <Properties>
+            <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
+              <Font name="Ubuntu" size="15" style="1"/>
+            </Property>
+            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="cz/fidentis/analyst/registration/Bundle.properties" key="RegistrationPanel.jButton2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+            <Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="cz/fidentis/analyst/registration/Bundle.properties" key="RegistrationPanel.jButton2.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+          </Properties>
+          <Events>
+            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton2ActionPerformed"/>
+          </Events>
+        </Component>
+        <Component class="javax.swing.JButton" name="jButton3">
+          <Properties>
+            <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
+              <Font name="Ubuntu" size="15" style="1"/>
+            </Property>
+            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="cz/fidentis/analyst/registration/Bundle.properties" key="RegistrationPanel.jButton3.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+          </Properties>
+        </Component>
+        <Component class="javax.swing.JButton" name="jButtonInfo1">
+          <Properties>
+            <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
+              <Image iconType="3" name="/info.png"/>
+            </Property>
+            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="cz/fidentis/analyst/registration/Bundle.properties" key="RegistrationPanel.jButtonInfo1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+            <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+              <Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
+                <EmptyBorder/>
+              </Border>
+            </Property>
+            <Property name="borderPainted" type="boolean" value="false"/>
+            <Property name="focusPainted" type="boolean" value="false"/>
+            <Property name="focusable" type="boolean" value="false"/>
+            <Property name="requestFocusEnabled" type="boolean" value="false"/>
+            <Property name="rolloverEnabled" type="boolean" value="false"/>
+          </Properties>
         </Component>
       </SubComponents>
     </Container>
@@ -1186,88 +1212,6 @@
     </Container>
     <Component class="javax.swing.JSeparator" name="jSeparator7">
     </Component>
-    <Container class="javax.swing.JPanel" name="jPanel7">
-      <Properties>
-        <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
-          <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
-            <TitledBorder title="Feature points alignment (Procrustes):">
-              <ResourceString PropertyName="titleX" bundle="cz/fidentis/analyst/registration/Bundle.properties" key="RegistrationPanel.jPanel7.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-              <Font PropertyName="font" name="Dialog" size="14" style="1"/>
-            </TitledBorder>
-          </Border>
-        </Property>
-      </Properties>
-
-      <Layout>
-        <DimensionLayout dim="0">
-          <Group type="103" groupAlignment="0" attributes="0">
-              <Group type="102" alignment="0" attributes="0">
-                  <EmptySpace max="-2" attributes="0"/>
-                  <Component id="jButton2" min="-2" max="-2" attributes="0"/>
-                  <EmptySpace type="separate" max="-2" attributes="0"/>
-                  <Component id="jLabel4" min="-2" max="-2" attributes="0"/>
-                  <EmptySpace max="-2" attributes="0"/>
-                  <Component id="jCheckBox2" min="-2" max="-2" attributes="0"/>
-                  <EmptySpace max="32767" attributes="0"/>
-              </Group>
-          </Group>
-        </DimensionLayout>
-        <DimensionLayout dim="1">
-          <Group type="103" groupAlignment="0" attributes="0">
-              <Group type="102" alignment="0" attributes="0">
-                  <EmptySpace max="-2" attributes="0"/>
-                  <Group type="103" groupAlignment="0" max="-2" attributes="0">
-                      <Component id="jCheckBox2" alignment="1" max="32767" attributes="0"/>
-                      <Group type="103" alignment="1" groupAlignment="3" attributes="0">
-                          <Component id="jLabel4" alignment="3" max="32767" attributes="0"/>
-                          <Component id="jButton2" alignment="3" min="-2" max="-2" attributes="0"/>
-                      </Group>
-                  </Group>
-                  <EmptySpace max="32767" attributes="0"/>
-              </Group>
-          </Group>
-        </DimensionLayout>
-      </Layout>
-      <SubComponents>
-        <Component class="javax.swing.JButton" name="jButton2">
-          <Properties>
-            <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
-              <Font name="Ubuntu" size="15" style="1"/>
-            </Property>
-            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-              <ResourceString bundle="cz/fidentis/analyst/registration/Bundle.properties" key="RegistrationPanel.jButton2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-            </Property>
-            <Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-              <ResourceString bundle="cz/fidentis/analyst/registration/Bundle.properties" key="RegistrationPanel.jButton2.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-            </Property>
-          </Properties>
-          <Events>
-            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton2ActionPerformed"/>
-          </Events>
-        </Component>
-        <Component class="javax.swing.JLabel" name="jLabel4">
-          <Properties>
-            <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
-              <Font name="Ubuntu" size="15" style="0"/>
-            </Property>
-            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-              <ResourceString bundle="cz/fidentis/analyst/registration/Bundle.properties" key="RegistrationPanel.jLabel4.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-            </Property>
-          </Properties>
-        </Component>
-        <Component class="javax.swing.JCheckBox" name="jCheckBox2">
-          <Properties>
-            <Property name="selected" type="boolean" value="true"/>
-            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-              <ResourceString bundle="cz/fidentis/analyst/registration/Bundle.properties" key="RegistrationPanel.jCheckBox2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-            </Property>
-          </Properties>
-          <Events>
-            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jCheckBox2ActionPerformed"/>
-          </Events>
-        </Component>
-      </SubComponents>
-    </Container>
     <Container class="javax.swing.JPanel" name="jPanel2">
       <Properties>
         <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
diff --git a/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationPanel.java b/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationPanel.java
index d3eaa22f..82b45c3c 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationPanel.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/registration/RegistrationPanel.java
@@ -6,6 +6,7 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.util.DoubleSummaryStatistics;
 import javax.swing.ImageIcon;
+import javax.swing.JOptionPane;
 import javax.vecmath.Vector3d;
 
 /**
@@ -32,12 +33,7 @@ public class RegistrationPanel extends ControlPanel {
     
     public static final String ACTION_COMMAND_FP_CLOSENESS_THRESHOLD = "fp closeness treshold";
     
-    public static final String ACTION_COMMAND_ICP_SCALE = "ICP scale";
-    public static final String ACTION_COMMAND_ICP_MAX_ITERATIONS = "ICP iterations";
-    public static final String ACTION_COMMAND_ICP_ERROR = "ICP error";
-    
     public static final String ACTION_COMMAND_PROCRUSTES_APPLY = "Procrustes apply";
-    public static final String ACTION_COMMAND_PROCRUSTES_SCALE = "Procrustes scale";
     
     public static final String ACTION_COMMAND_ALIGN_SYMMETRY_PLANES = "align symmetry planes";
     
@@ -82,16 +78,16 @@ public class RegistrationPanel extends ControlPanel {
         thersholdFTF.setValue(5.0);
         thersholdFTF.addActionListener(createListener(action, ACTION_COMMAND_FP_CLOSENESS_THRESHOLD));
 
-        jCheckBox1.addActionListener(createListener(action, ACTION_COMMAND_ICP_SCALE));
-        jFormattedTextField1.addActionListener(createListener(action, ACTION_COMMAND_ICP_ERROR));
-        jFormattedTextField2.addActionListener(createListener(action, ACTION_COMMAND_ICP_MAX_ITERATIONS));
-        
         //comboSliderInteger1.setSliderEast();
         comboSliderInteger1.setRange(10, 100);
         comboSliderInteger1.setValue(this.undersampling);
         comboSliderInteger1.addInputFieldListener((ActionEvent ae) -> { // update slider when the input field changed
             this.undersampling = comboSliderInteger1.getValue();
         });
+        
+        jButtonInfo1.addActionListener((ActionEvent e) -> { 
+            showAutoAlignmentInfo();
+        });
     }
     
     /**
@@ -131,7 +127,24 @@ public class RegistrationPanel extends ControlPanel {
     public int getIcpUndersampling() {
         return undersampling;
     }
-
+    
+    /**
+     * Turns ob or off the plane alignment button
+     * @param on on-off value
+     */
+    public void setEnabledPlanesButton(boolean on) {
+        jButton3.setEnabled(on);
+        jButton3.setFont(new java.awt.Font("Ubuntu", (on) ? 1 : 0, 15)); // NOI18N
+    }
+    
+    /**
+     * Turns ob or off the plane alignment button
+     * @param on on-off value
+     */
+    public void setEnabledProcrustesButton(boolean on) {
+        jButton2.setEnabled(on);
+        jButton2.setFont(new java.awt.Font("Ubuntu", (on) ? 1 : 0, 15)); // NOI18N
+    }
     
     /**
      * Updates GUI elements that display statistical data about the calculated Hausdorff distance.
@@ -144,6 +157,18 @@ public class RegistrationPanel extends ControlPanel {
         jTextField2.setText(String.format("%.3f", whd.getAverage()));
     }
     
+    public boolean getScaleParam() {
+        return jCheckBox1.isSelected();
+    }
+    
+    public int getMaxIcpIterParam() {
+        return ((Number) jFormattedTextField2.getValue()).intValue();
+    }
+    
+    public double getMinIcpErrorParam() {
+        return ((Number) jFormattedTextField2.getValue()).doubleValue();
+    }
+    
     /**
      * Alters transformation based on move amount specified in listener
      * by updating specific formatted field
@@ -219,6 +244,29 @@ public class RegistrationPanel extends ControlPanel {
     public static ImageIcon getStaticIcon() {
         return new ImageIcon(RegistrationPanel.class.getClassLoader().getResource("/" + ICON));
     }
+    
+    private void showAutoAlignmentInfo() {
+        JOptionPane.showMessageDialog(
+                this, 
+                "<html>"
+                        + "<strong>Mesh</strong>: <br/>"
+                        + "Superimposition of two faces by applying iterative closest points (ICP) algorithm<br/>" 
+                        + "on their triangular meshes. This approach is slow, unsharp, but universal.<br/>"
+                        + "<br/>"
+                        + "<strong>Feature points</strong>: <br/>"
+                        + "Superimposition of two faces by applying Procrustes algorithm<br/>" 
+                        + "on feature points. It is fast, precise, but feature points of the same types<br/>"
+                        + "have to be presented for both faces.<br/>"
+                        + "<br/>"
+                        + "<strong>Symmetry planes</strong>: <br/>"
+                        + "Superimposition of two faces by aligning their symmetry planes.<br/>" 
+                        + "Symmetry planes have to be computed on the \"<i>Symmetry</i>\" tab first<br/>"
+                        + "to enable this feature.<br/>"
+                        + "</html>",
+                "Distance computation strategies",
+                JOptionPane.INFORMATION_MESSAGE
+        );
+    }
 
     /**
      * This method is called from within the constructor to initialize the form.
@@ -259,28 +307,25 @@ public class RegistrationPanel extends ControlPanel {
         scaleMinusButton = new javax.swing.JButton();
         shiftPanel = new javax.swing.JPanel();
         jSeparator5 = new javax.swing.JSeparator();
-        jButton3 = new javax.swing.JButton();
         jSeparator2 = new javax.swing.JSeparator();
         jPanel1 = new javax.swing.JPanel();
-        jLabel7 = new javax.swing.JLabel();
         jCheckBox1 = new javax.swing.JCheckBox();
         jLabel5 = new javax.swing.JLabel();
         jFormattedTextField1 = new javax.swing.JFormattedTextField();
         jLabel6 = new javax.swing.JLabel();
         jFormattedTextField2 = new javax.swing.JFormattedTextField();
         jLabel8 = new javax.swing.JLabel();
-        jButton1 = new javax.swing.JButton();
         comboSliderInteger1 = new cz.fidentis.analyst.core.ComboSliderInteger();
+        jButton1 = new javax.swing.JButton();
+        jButton2 = new javax.swing.JButton();
+        jButton3 = new javax.swing.JButton();
+        jButtonInfo1 = new javax.swing.JButton();
         jPanel4 = new javax.swing.JPanel();
         featurePointsLabel = new javax.swing.JLabel();
         thersholdFTF = new javax.swing.JFormattedTextField();
         thersholdUpButton = new javax.swing.JButton();
         thresholdDownButton = new javax.swing.JButton();
         jSeparator7 = new javax.swing.JSeparator();
-        jPanel7 = new javax.swing.JPanel();
-        jButton2 = new javax.swing.JButton();
-        jLabel4 = new javax.swing.JLabel();
-        jCheckBox2 = new javax.swing.JCheckBox();
         jPanel2 = new javax.swing.JPanel();
         jLabel1 = new javax.swing.JLabel();
         jTextField1 = new javax.swing.JTextField();
@@ -685,8 +730,6 @@ public class RegistrationPanel extends ControlPanel {
             .addGap(0, 84, Short.MAX_VALUE)
         );
 
-        org.openide.awt.Mnemonics.setLocalizedText(jButton3, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jButton3.text")); // NOI18N
-
         javax.swing.GroupLayout transformationPanelLayout = new javax.swing.GroupLayout(transformationPanel);
         transformationPanel.setLayout(transformationPanelLayout);
         transformationPanelLayout.setHorizontalGroup(
@@ -699,9 +742,6 @@ public class RegistrationPanel extends ControlPanel {
                     .addGroup(transformationPanelLayout.createSequentialGroup()
                         .addGap(580, 580, 580)
                         .addComponent(shiftPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
-                    .addGroup(transformationPanelLayout.createSequentialGroup()
-                        .addContainerGap()
-                        .addComponent(jButton3))
                     .addGroup(transformationPanelLayout.createSequentialGroup()
                         .addComponent(translationPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                         .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
@@ -719,8 +759,6 @@ public class RegistrationPanel extends ControlPanel {
                     .addComponent(rotationPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                     .addComponent(translationPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-                .addComponent(jButton3)
-                .addGap(59, 59, 59)
                 .addComponent(shiftPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                 .addComponent(jSeparator5, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE))
@@ -729,8 +767,6 @@ public class RegistrationPanel extends ControlPanel {
         jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder(null, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jPanel1.border.title"), javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Dialog", 1, 14))); // NOI18N
         jPanel1.setMaximumSize(new java.awt.Dimension(600, 32767));
 
-        org.openide.awt.Mnemonics.setLocalizedText(jLabel7, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jLabel7.text")); // NOI18N
-
         jCheckBox1.setSelected(true);
         org.openide.awt.Mnemonics.setLocalizedText(jCheckBox1, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jCheckBox1.text")); // NOI18N
         jCheckBox1.addActionListener(new java.awt.event.ActionListener() {
@@ -760,6 +796,27 @@ public class RegistrationPanel extends ControlPanel {
             }
         });
 
+        jButton2.setFont(new java.awt.Font("Ubuntu", 1, 15)); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(jButton2, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jButton2.text")); // NOI18N
+        jButton2.setToolTipText(org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jButton2.toolTipText")); // NOI18N
+        jButton2.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                jButton2ActionPerformed(evt);
+            }
+        });
+
+        jButton3.setFont(new java.awt.Font("Ubuntu", 1, 15)); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(jButton3, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jButton3.text")); // NOI18N
+
+        jButtonInfo1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/info.png"))); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(jButtonInfo1, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jButtonInfo1.text")); // NOI18N
+        jButtonInfo1.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1));
+        jButtonInfo1.setBorderPainted(false);
+        jButtonInfo1.setFocusPainted(false);
+        jButtonInfo1.setFocusable(false);
+        jButtonInfo1.setRequestFocusEnabled(false);
+        jButtonInfo1.setRolloverEnabled(false);
+
         javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
         jPanel1.setLayout(jPanel1Layout);
         jPanel1Layout.setHorizontalGroup(
@@ -768,48 +825,53 @@ public class RegistrationPanel extends ControlPanel {
                 .addContainerGap()
                 .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                     .addGroup(jPanel1Layout.createSequentialGroup()
-                        .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                            .addGroup(jPanel1Layout.createSequentialGroup()
-                                .addComponent(jLabel7)
-                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                                .addComponent(jCheckBox1)
-                                .addGap(18, 18, 18)
-                                .addComponent(jLabel5)
-                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                                .addComponent(jFormattedTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, 49, javax.swing.GroupLayout.PREFERRED_SIZE))
-                            .addComponent(comboSliderInteger1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
-                        .addGap(12, 12, 12)
-                        .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                            .addGroup(jPanel1Layout.createSequentialGroup()
-                                .addComponent(jLabel6)
-                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                                .addComponent(jFormattedTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE))
-                            .addComponent(jLabel8)))
-                    .addComponent(jButton1))
+                        .addComponent(jButton1)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(jButton2)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(jButton3)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+                        .addComponent(jButtonInfo1))
+                    .addGroup(jPanel1Layout.createSequentialGroup()
+                        .addComponent(jCheckBox1)
+                        .addGap(18, 18, 18)
+                        .addComponent(jLabel5)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(jFormattedTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, 49, javax.swing.GroupLayout.PREFERRED_SIZE)
+                        .addGap(18, 18, 18)
+                        .addComponent(jLabel6)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(jFormattedTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)))
                 .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+            .addGroup(jPanel1Layout.createSequentialGroup()
+                .addComponent(comboSliderInteger1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addGap(12, 12, 12)
+                .addComponent(jLabel8)
+                .addGap(0, 0, Short.MAX_VALUE))
         );
         jPanel1Layout.setVerticalGroup(
             jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(jPanel1Layout.createSequentialGroup()
                 .addContainerGap()
-                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                    .addComponent(jLabel7)
-                    .addComponent(jCheckBox1)
+                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                     .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
-                        .addComponent(jLabel5)
-                        .addComponent(jFormattedTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE)
-                        .addComponent(jLabel6)
-                        .addComponent(jFormattedTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)))
+                        .addComponent(jButton1)
+                        .addComponent(jButton2)
+                        .addComponent(jButton3))
+                    .addComponent(jButtonInfo1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 16, Short.MAX_VALUE)
+                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(jCheckBox1)
+                    .addComponent(jLabel5)
+                    .addComponent(jFormattedTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE)
+                    .addComponent(jLabel6)
+                    .addComponent(jFormattedTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(comboSliderInteger1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                     .addGroup(jPanel1Layout.createSequentialGroup()
-                        .addGap(5, 5, 5)
-                        .addComponent(comboSliderInteger1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
-                    .addGroup(jPanel1Layout.createSequentialGroup()
-                        .addGap(26, 26, 26)
-                        .addComponent(jLabel8)))
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jButton1)
-                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+                        .addGap(21, 21, 21)
+                        .addComponent(jLabel8))))
         );
 
         jPanel4.setBorder(javax.swing.BorderFactory.createTitledBorder(null, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jPanel4.border.title"), javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Dialog", 1, 14))); // NOI18N
@@ -875,53 +937,6 @@ public class RegistrationPanel extends ControlPanel {
                 .addGap(0, 8, Short.MAX_VALUE))
         );
 
-        jPanel7.setBorder(javax.swing.BorderFactory.createTitledBorder(null, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jPanel7.border.title"), javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Dialog", 1, 14))); // NOI18N
-
-        jButton2.setFont(new java.awt.Font("Ubuntu", 1, 15)); // NOI18N
-        org.openide.awt.Mnemonics.setLocalizedText(jButton2, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jButton2.text")); // NOI18N
-        jButton2.setToolTipText(org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jButton2.toolTipText")); // NOI18N
-        jButton2.addActionListener(new java.awt.event.ActionListener() {
-            public void actionPerformed(java.awt.event.ActionEvent evt) {
-                jButton2ActionPerformed(evt);
-            }
-        });
-
-        jLabel4.setFont(new java.awt.Font("Ubuntu", 0, 15)); // NOI18N
-        org.openide.awt.Mnemonics.setLocalizedText(jLabel4, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jLabel4.text")); // NOI18N
-
-        jCheckBox2.setSelected(true);
-        org.openide.awt.Mnemonics.setLocalizedText(jCheckBox2, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jCheckBox2.text")); // NOI18N
-        jCheckBox2.addActionListener(new java.awt.event.ActionListener() {
-            public void actionPerformed(java.awt.event.ActionEvent evt) {
-                jCheckBox2ActionPerformed(evt);
-            }
-        });
-
-        javax.swing.GroupLayout jPanel7Layout = new javax.swing.GroupLayout(jPanel7);
-        jPanel7.setLayout(jPanel7Layout);
-        jPanel7Layout.setHorizontalGroup(
-            jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(jPanel7Layout.createSequentialGroup()
-                .addContainerGap()
-                .addComponent(jButton2)
-                .addGap(18, 18, 18)
-                .addComponent(jLabel4)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jCheckBox2)
-                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
-        );
-        jPanel7Layout.setVerticalGroup(
-            jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(jPanel7Layout.createSequentialGroup()
-                .addContainerGap()
-                .addGroup(jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
-                    .addComponent(jCheckBox2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
-                        .addComponent(jLabel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-                        .addComponent(jButton2)))
-                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
-        );
-
         jPanel2.setBorder(javax.swing.BorderFactory.createTitledBorder(null, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jPanel2.border.title"), javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Dialog", 1, 14))); // NOI18N
 
         org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(RegistrationPanel.class, "RegistrationPanel.jLabel1.text")); // NOI18N
@@ -978,8 +993,7 @@ public class RegistrationPanel extends ControlPanel {
                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                     .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                     .addComponent(jPanel4, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-                    .addComponent(transformationPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 585, javax.swing.GroupLayout.PREFERRED_SIZE)
-                    .addComponent(jPanel7, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+                    .addComponent(transformationPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 585, javax.swing.GroupLayout.PREFERRED_SIZE))
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                 .addComponent(jSeparator7, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE))
             .addGroup(layout.createSequentialGroup()
@@ -996,16 +1010,14 @@ public class RegistrationPanel extends ControlPanel {
                         .addComponent(jSeparator7, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE))
                     .addGroup(layout.createSequentialGroup()
                         .addContainerGap()
-                        .addComponent(jPanel7, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                        .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(transformationPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 176, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addComponent(transformationPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 134, javax.swing.GroupLayout.PREFERRED_SIZE)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
-                .addGap(328, 328, 328)
+                .addGap(451, 451, 451)
                 .addComponent(jSeparator11, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE)
                 .addGap(221, 221, 221)
                 .addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE)
@@ -1033,12 +1045,16 @@ public class RegistrationPanel extends ControlPanel {
         thersholdFTF.postActionEvent();
     }//GEN-LAST:event_thersholdUpButtonActionPerformed
 
+    private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed
+        // TODO add your handling code here:
+    }//GEN-LAST:event_jButton2ActionPerformed
+
     private void scaleMinusButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_scaleMinusButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_scaleMinusButtonMouseReleased
 
@@ -1049,9 +1065,9 @@ public class RegistrationPanel extends ControlPanel {
     private void scalePlusButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_scalePlusButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_scalePlusButtonMouseReleased
 
@@ -1062,9 +1078,9 @@ public class RegistrationPanel extends ControlPanel {
     private void rightRotationXButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_rightRotationXButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_rightRotationXButtonMouseReleased
 
@@ -1075,9 +1091,9 @@ public class RegistrationPanel extends ControlPanel {
     private void leftRotationZButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_leftRotationZButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_leftRotationZButtonMouseReleased
 
@@ -1088,9 +1104,9 @@ public class RegistrationPanel extends ControlPanel {
     private void rightRotationYButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_rightRotationYButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_rightRotationYButtonMouseReleased
 
@@ -1101,9 +1117,9 @@ public class RegistrationPanel extends ControlPanel {
     private void rightRotationZButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_rightRotationZButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_rightRotationZButtonMouseReleased
 
@@ -1114,9 +1130,9 @@ public class RegistrationPanel extends ControlPanel {
     private void leftRotationXButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_leftRotationXButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_leftRotationXButtonMouseReleased
 
@@ -1127,9 +1143,9 @@ public class RegistrationPanel extends ControlPanel {
     private void leftRotationYButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_leftRotationYButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_leftRotationYButtonMouseReleased
 
@@ -1140,9 +1156,9 @@ public class RegistrationPanel extends ControlPanel {
     private void leftTranslationZButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_leftTranslationZButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_leftTranslationZButtonMouseReleased
 
@@ -1153,9 +1169,9 @@ public class RegistrationPanel extends ControlPanel {
     private void leftTranslationXButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_leftTranslationXButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_leftTranslationXButtonMouseReleased
 
@@ -1166,9 +1182,9 @@ public class RegistrationPanel extends ControlPanel {
     private void rightTranslationZButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_rightTranslationZButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_rightTranslationZButtonMouseReleased
 
@@ -1179,9 +1195,9 @@ public class RegistrationPanel extends ControlPanel {
     private void rightTranslationYButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_rightTranslationYButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_rightTranslationYButtonMouseReleased
 
@@ -1192,9 +1208,9 @@ public class RegistrationPanel extends ControlPanel {
     private void leftTranslationYButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_leftTranslationYButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_leftTranslationYButtonMouseReleased
 
@@ -1205,9 +1221,9 @@ public class RegistrationPanel extends ControlPanel {
     private void rightTranslationXButtonMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_rightTranslationXButtonMouseReleased
         animator.stopModelAnimation(this);
         action.actionPerformed(new ActionEvent(
-                evt, 
-                ActionEvent.ACTION_PERFORMED, 
-                ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
+            evt,
+            ActionEvent.ACTION_PERFORMED,
+            ACTION_COMMAND_MANUAL_TRANSFORMATION_FINISHED)
         );
     }//GEN-LAST:event_rightTranslationXButtonMouseReleased
 
@@ -1215,14 +1231,6 @@ public class RegistrationPanel extends ControlPanel {
         animator.startModelAnimation(Direction.TRANSLATE_RIGHT, this);
     }//GEN-LAST:event_rightTranslationXButtonMousePressed
 
-    private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed
-        // TODO add your handling code here:
-    }//GEN-LAST:event_jButton2ActionPerformed
-
-    private void jCheckBox2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox2ActionPerformed
-        // TODO add your handling code here:
-    }//GEN-LAST:event_jCheckBox2ActionPerformed
-
 
     // Variables declaration - do not modify//GEN-BEGIN:variables
     private cz.fidentis.analyst.core.ComboSliderInteger comboSliderInteger1;
@@ -1230,21 +1238,18 @@ public class RegistrationPanel extends ControlPanel {
     private javax.swing.JButton jButton1;
     private javax.swing.JButton jButton2;
     private javax.swing.JButton jButton3;
+    private javax.swing.JButton jButtonInfo1;
     private javax.swing.JCheckBox jCheckBox1;
-    private javax.swing.JCheckBox jCheckBox2;
     private javax.swing.JFormattedTextField jFormattedTextField1;
     private javax.swing.JFormattedTextField jFormattedTextField2;
     private javax.swing.JLabel jLabel1;
     private javax.swing.JLabel jLabel2;
-    private javax.swing.JLabel jLabel4;
     private javax.swing.JLabel jLabel5;
     private javax.swing.JLabel jLabel6;
-    private javax.swing.JLabel jLabel7;
     private javax.swing.JLabel jLabel8;
     private javax.swing.JPanel jPanel1;
     private javax.swing.JPanel jPanel2;
     private javax.swing.JPanel jPanel4;
-    private javax.swing.JPanel jPanel7;
     private javax.swing.JSeparator jSeparator11;
     private javax.swing.JSeparator jSeparator2;
     private javax.swing.JSeparator jSeparator5;
diff --git a/GUI/src/main/resources/cz/fidentis/analyst/registration/Bundle.properties b/GUI/src/main/resources/cz/fidentis/analyst/registration/Bundle.properties
index d966ae39..4d2dcd52 100644
--- a/GUI/src/main/resources/cz/fidentis/analyst/registration/Bundle.properties
+++ b/GUI/src/main/resources/cz/fidentis/analyst/registration/Bundle.properties
@@ -1,16 +1,28 @@
-RegistrationPanel.jButton1.text=Apply
-RegistrationPanel.jCheckBox1.text=
+RegistrationPanel.jButton1.text=Mesh
+RegistrationPanel.jCheckBox1.text=scale
 RegistrationPanel.jFormattedTextField1.text=0,3
-RegistrationPanel.jLabel5.text=min. error:
+RegistrationPanel.jLabel5.text=min ICP error:
 RegistrationPanel.jFormattedTextField2.text=50
-RegistrationPanel.jLabel6.text=iterations:
-RegistrationPanel.jLabel7.text=scale:
-RegistrationPanel.jLabel8.text=undersampling (100% = none)
+RegistrationPanel.jLabel6.text=ICP iterations:
+RegistrationPanel.jLabel8.text=ICP undersampling (100% = none)
 RegistrationPanel.thersholdUpButton.text=
 RegistrationPanel.thresholdDownButton.text=
 RegistrationPanel.featurePointsLabel.text=Highlight feature point pairs closer than:
 2
-RegistrationPanel.translationPanel.border.title=translation
+RegistrationPanel.jButton2.text=Feature points
+RegistrationPanel.jButton1.toolTipText=Apply ICP
+RegistrationPanel.jButton2.toolTipText=Apply Procrustes
+RegistrationPanel.jPanel1.border.title=Auto-alignment
+RegistrationPanel.jPanel4.border.title=View:
+RegistrationPanel.jLabel1.text=Hausdorff distance:
+RegistrationPanel.jPanel2.border.title=Measurements:
+RegistrationPanel.jTextField2.text=
+RegistrationPanel.jLabel2.text=Weighted Hausdorff distance:
+RegistrationPanel.jTextField1.text=
+BatchRegistrationPanel.jPanel2.border.title=Dataset
+BatchRegistrationPanel.jCheckBox1.text=compute average face from
+BatchRegistrationPanel.jPanel3.border.title=Similarity
+RegistrationPanel.jButton3.text=Symmetry planes
 RegistrationPanel.scaleMinusButton.text=
 RegistrationPanel.scalePlusButton.text=
 RegistrationPanel.scalePanel.border.title=scale
@@ -36,21 +48,6 @@ RegistrationPanel.translXLabel.text=horizontal
 RegistrationPanel.rightTranslationYButton.text=
 RegistrationPanel.leftTranslationYButton.text=
 RegistrationPanel.rightTranslationXButton.text=
-RegistrationPanel.jButton2.text=Apply
-RegistrationPanel.jButton1.toolTipText=Apply ICP
-RegistrationPanel.jButton2.toolTipText=Apply Procrustes
-RegistrationPanel.jLabel4.text=scale:
-RegistrationPanel.jCheckBox2.text=
-RegistrationPanel.jPanel7.border.title=Feature points alignment (Procrustes):
-RegistrationPanel.jPanel1.border.title=Mesh alignment (ICP):
+RegistrationPanel.translationPanel.border.title=translation
 RegistrationPanel.transformationPanel.border.title=Manual alignment:
-RegistrationPanel.jPanel4.border.title=View:
-RegistrationPanel.jLabel1.text=Hausdorff distance:
-RegistrationPanel.jPanel2.border.title=Measurements:
-RegistrationPanel.jTextField2.text=
-RegistrationPanel.jLabel2.text=Weighted Hausdorff distance:
-RegistrationPanel.jTextField1.text=
-BatchRegistrationPanel.jPanel2.border.title=Dataset
-BatchRegistrationPanel.jCheckBox1.text=compute average face from
-BatchRegistrationPanel.jPanel3.border.title=Similarity
-RegistrationPanel.jButton3.text=Aling symmetry planes
+RegistrationPanel.jButtonInfo1.text=
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 7d701dc0..d55b3132 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
@@ -106,9 +106,11 @@ public class MeshFacetImpl implements MeshFacet {
         for (MeshTriangle t : this) {
             Vector3d triangleNormal = new Vector3d(t.getPoint1().getPosition());
             triangleNormal.sub(t.getPoint3().getPosition());
+            triangleNormal.normalize();
             
             Vector3d edge2 = new Vector3d(t.getPoint1().getPosition());
             edge2.sub(t.getPoint2().getPosition());
+            edge2.normalize();
             
             triangleNormal.cross(triangleNormal, edge2);
             //Vector3d triangleNormal = (t.getPoint3().subtractPosition(t.getPoint1())).crossProduct(t.getPoint2().subtractPosition(t.getPoint1())).getPosition();
@@ -118,10 +120,13 @@ public class MeshFacetImpl implements MeshFacet {
             normalMap.get(t.getPoint3().getPosition()).add(triangleNormal);
         }
         
-        // normalize normals:
         normalMap.values().forEach(normal -> { 
             normal.normalize();
         });
+        
+        for (MeshPoint p: getVertices()) {
+            p.setNormal(normalMap.get(p.getPosition()));
+        }
     }
     
     @Override
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshModel.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshModel.java
index 9d4ee4fe..e7ec534f 100644
--- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshModel.java
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshModel.java
@@ -8,6 +8,7 @@ import java.io.Serializable;
 import java.util.Collection;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import javax.vecmath.Point3d;
 
 /**
  * The main object for triangular meshes. Each mesh model consists  
@@ -157,5 +158,26 @@ public class MeshModel implements Serializable {
                 .stream()
                 .mapToInt(f -> f.getNumberOfVertices())
                 .sum();
-    }    
+    }
+    
+    /**
+     * Returns central point computed from mesh vertices
+     * 
+     * @return centroid
+     */
+    public Point3d getCentroid() {
+        Point3d c = new Point3d(0, 0, 0);
+        final long size = getNumVertices();
+        getFacets().stream()
+                .flatMap(f -> f.getVertices().parallelStream())
+                .map(p -> p.getPosition())
+                .forEach(p -> {
+                    c.x += p.x / size;
+                    c.y += p.y / size;
+                    c.z += p.z / size;
+                });
+        return c;
+    }
+    
+    
 }
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshPoint.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshPoint.java
index ab67c9ed..10b4419d 100644
--- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshPoint.java
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshPoint.java
@@ -42,6 +42,12 @@ public interface MeshPoint extends IPosition, Serializable {
      * @param newPos New position, must not be {@code null}
      */
     void setPosition(Point3d newPos);
+    
+    /**
+     * Sets the normal vector
+     * @param newNormal New normal vector or {@code null}
+     */
+    void setNormal(Vector3d newNormal);
 
     /**
      * @return texture coordinates
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshPointImpl.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshPointImpl.java
index 61dbabf9..4034eb9c 100644
--- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshPointImpl.java
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshPointImpl.java
@@ -63,6 +63,11 @@ public class MeshPointImpl implements MeshPoint {
             this.position = new Point3d(newPos);
         }
     }
+    
+    @Override
+    public void setNormal(Vector3d newNormal) {
+        this.normal = new Vector3d(newNormal);
+    }
 
     @Override
     public Vector3d getTexCoord() {
-- 
GitLab