From 4615002ea61d308d894e8a0268cdfd9b5ec7655c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Radek=20O=C5=A1lej=C5=A1ek?= <oslejsek@fi.muni.cz>
Date: Sat, 22 Jan 2022 10:05:14 +0100
Subject: [PATCH] Resolve "Export average face"

---
 .../cz/fidentis/analyst/face/HumanFace.java   |  26 ++
 .../analyst/face/HumanFaceFactory.java        |   8 +-
 .../batch/ApproxHausdorffDistTask.java        |  67 ++--
 .../fidentis/analyst/batch/BatchAction.java   | 156 ++++++++--
 .../cz/fidentis/analyst/batch/BatchPanel.form | 160 ++++++----
 .../cz/fidentis/analyst/batch/BatchPanel.java | 294 ++++++++++++++----
 .../batch/CompleteHausdorffDistTask.java      |   4 +-
 .../cz/fidentis/analyst/batch/IcpTask.java    | 122 ++++----
 .../analyst/batch/SimilarityTask.java         |   2 +-
 .../java/cz/fidentis/analyst/scene/Scene.java |  17 +-
 .../fidentis/analyst/batch/Bundle.properties  |  19 +-
 11 files changed, 628 insertions(+), 247 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 d4489739..3d9275be 100644
--- a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java
+++ b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java
@@ -116,6 +116,32 @@ public class HumanFace implements Serializable {
         this(file, true);
     }
     
+    /**
+     * Creates a human face from existing mesh model. The mesh model is
+     * stored directly (not copied).
+     * 
+     * @param model Mesh model
+     * @param id Canonical path to the OBJ file
+     * @throws IllegalArgumentException if the {@code model} is {@code null}
+     */
+    public HumanFace(MeshModel model, String id) {
+        if (model == null) {
+            throw new IllegalArgumentException("model");
+        }
+        if (id == null || id.isBlank()) {
+            throw new IllegalArgumentException("id");
+        }
+        this.meshModel = model;
+        
+        BoundingBox visitor = new BoundingBox();
+        meshModel.compute(visitor);
+        bbox = visitor.getBoundingBox();
+        
+        this.id = id;
+        
+        eventBus = new EventBus();
+    }
+    
     /**
      * Returns the triangular mesh model of the human face.
      * 
diff --git a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceFactory.java b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceFactory.java
index 451bffa7..176fd5da 100644
--- a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceFactory.java
+++ b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFaceFactory.java
@@ -105,11 +105,13 @@ public class HumanFaceFactory {
     }
     
     /**
-     * I set to {@code true}, then the dump file of a face is created only once
-     * and then reused for every recovery (it never overwritten).
+     * If set to {@code true}, then the dump file of a face is created only once
+     * and then reused for every recovery (it is never overwritten).
      * It accelerates the dumping face, but can be used only if the state of
      * faces never changes between the first dump a consequent recoveries. 
-     * 
+     * If set to {@code false}, then every dump of the face to the disk overwrites 
+     * the file.
+     
      * @param use If {@code true}, then this optimization is turned on.
      */
     public void setReuseDumpFile(boolean use) {
diff --git a/GUI/src/main/java/cz/fidentis/analyst/batch/ApproxHausdorffDistTask.java b/GUI/src/main/java/cz/fidentis/analyst/batch/ApproxHausdorffDistTask.java
index 06ae0a52..d4c063bf 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/batch/ApproxHausdorffDistTask.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/batch/ApproxHausdorffDistTask.java
@@ -22,7 +22,8 @@ public class ApproxHausdorffDistTask extends SimilarityTask {
     private final Stopwatch hdComputationTime = new Stopwatch("Hausdorff distance time:\t");
     private final Stopwatch loadTime = new Stopwatch("Disk access time:\t");
     
-    private HumanFace avgFace;
+    //private HumanFace avgFace;
+    private int templateFaceIndex;
     
     /**
      * Constructor.
@@ -31,50 +32,64 @@ public class ApproxHausdorffDistTask extends SimilarityTask {
      * @param controlPanel A control panel with computation parameters. Must not be {@code null}
      * @param avgFace average face
      */
-    public ApproxHausdorffDistTask(ProgressDialog progressDialog, BatchPanel controlPanel, HumanFace avgFace) {
+    public ApproxHausdorffDistTask(ProgressDialog progressDialog, BatchPanel controlPanel, int templateFaceIndex) {
         super(progressDialog, controlPanel);
-        this.avgFace = avgFace;
+        this.templateFaceIndex = templateFaceIndex;
     }
     
     @Override
     protected Void doInBackground() throws Exception {
         HumanFaceFactory factory = getControlPanel().getHumanFaceFactory();
-        List<Path> faces = getControlPanel().getFaces();
-
-        getControlPanel().getHumanFaceFactory().setReuseDumpFile(true);
-        factory.setStrategy(HumanFaceFactory.Strategy.LRU);
-        
-        double[] dist = new double[faces.size()];
+        List<Path> faces = getControlPanel().getFacePaths();
         
         totalTime.start();
         
+        factory.setReuseDumpFile(true); // it's safe because no changes are made to models 
+        factory.setStrategy(HumanFaceFactory.Strategy.MRU); // keep first X faces in the memory
+        
+        // We don't need to reaload the initFace periodically for two reasons:
+        //   - It is never dumped from memory to disk because we use MRU
+        //   - Even if dumped, the face keeps in the mempry until we hold the pointer to it
+        loadTime.start();
+        String templateFaceId = factory.loadFace(faces.get(templateFaceIndex).toFile());
+        HumanFace templateFace = factory.getFace(templateFaceId);
+        loadTime.stop();
+        
+        
+        double[] dist = new double[faces.size()];
+        
         for (int i = 0; i < faces.size(); i++) {
             
             if (isCancelled()) { // the user canceled the process
                 return null;
             }
             
-            loadTime.start();
-            String faceId = factory.loadFace(faces.get(i).toFile());
-            HumanFace face = factory.getFace(faceId);
-            loadTime.stop();
-            
-            hdComputationTime.start();
-            //avgFace.computeKdTree(true);
-            HausdorffDistance hd = new HausdorffDistance(
-                    avgFace.getMeshModel(), 
-                    HausdorffDistance.Strategy.POINT_TO_POINT, 
-                    true,  // relative
-                    true,  // parallel
-                    false  // crop
-            );
-            face.getMeshModel().compute(hd, true);
-            dist[i] = hd.getStats().getAverage();
-            hdComputationTime.stop();
+            if (i != templateFaceIndex) {
+                
+                loadTime.start();
+                String faceId = factory.loadFace(faces.get(i).toFile());
+                HumanFace face = factory.getFace(faceId);
+                loadTime.stop();
+
+                hdComputationTime.start();
+                templateFace.computeKdTree(false);
+                HausdorffDistance hd = new HausdorffDistance(
+                        templateFace.getKdTree(),
+                        HausdorffDistance.Strategy.POINT_TO_POINT,
+                        true, // relative
+                        true, // parallel
+                        true  // crop
+                );
+                face.getMeshModel().compute(hd, true);
+                dist[i] = hd.getStats().getAverage();
+                hdComputationTime.stop();
+            }
             
             // update progress bar
             int progress = (int) Math.round(100.0 * (i+1) / faces.size());
             getProgressDialog().setValue(progress);
+            
+            //Logger.print(factory.toString());
         }
         
         hdComputationTime.start();
diff --git a/GUI/src/main/java/cz/fidentis/analyst/batch/BatchAction.java b/GUI/src/main/java/cz/fidentis/analyst/batch/BatchAction.java
index 7b7b8fa4..9d3e0786 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/batch/BatchAction.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/batch/BatchAction.java
@@ -1,5 +1,6 @@
 package cz.fidentis.analyst.batch;
 
+import com.jogamp.opengl.GL2;
 import cz.fidentis.analyst.Logger;
 import cz.fidentis.analyst.canvas.Canvas;
 import cz.fidentis.analyst.core.ControlPanelAction;
@@ -7,12 +8,20 @@ import cz.fidentis.analyst.face.HumanFace;
 import cz.fidentis.analyst.face.events.HumanFaceEvent;
 import cz.fidentis.analyst.face.events.HumanFaceListener;
 import cz.fidentis.analyst.core.ProgressDialog;
+import cz.fidentis.analyst.core.ProjectTopComp;
+import cz.fidentis.analyst.scene.DrawableFace;
 import java.awt.event.ActionEvent;
 import java.beans.PropertyChangeEvent;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
 import java.util.Arrays;
 import javax.swing.JOptionPane;
 import javax.swing.JTabbedPane;
 import javax.swing.SwingWorker;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import org.openide.filesystems.FileChooserBuilder;
 
 /**
  * Action listener for batch registration phase.
@@ -22,8 +31,9 @@ import javax.swing.SwingWorker;
 public class BatchAction extends ControlPanelAction implements HumanFaceListener {
     
     private final BatchPanel controlPanel;
-    private HumanFace avgFace = null;
-    private int avgFaceIndex = -1;
+    private int faceSceneSlot = -1;
+    
+    private double[][] distances;
     
     /**
      * Constructor.
@@ -58,8 +68,14 @@ public class BatchAction extends ControlPanelAction implements HumanFaceListener
                 computeRegistrationAndTemplate();
                 break;
             case BatchPanel.ACTION_COMMAND_COMPUTE_SIMILARITY:
-                 computeSimilarity();
-                 break;
+                computeSimilarity();
+                break;
+            case BatchPanel.ACTION_COMMAND_SHOW_SELECTED_FACE:
+                showSelectedIcpFace();
+                break;
+            case BatchPanel.ACTION_COMMAND_EXPORT_SIMILARITY:
+                exportDistanceResults();
+                break;
             default:
          }
         renderScene();
@@ -71,35 +87,56 @@ public class BatchAction extends ControlPanelAction implements HumanFaceListener
     }
 
     private void computeRegistrationAndTemplate() {
-        ProgressDialog progressBar = new ProgressDialog(controlPanel, "ICP registration and average face computation");
-        
-        if (controlPanel.showIcpPreview() && this.avgFaceIndex != -1) {
-            getScene().showDrawableFace(avgFaceIndex, false); // hide temporarily
+        if (controlPanel.getSelectedFaceIndex() == -1) {
+            JOptionPane.showMessageDialog(
+                    controlPanel,
+                    "No faces in the data set",
+                    null,
+                    JOptionPane.WARNING_MESSAGE);
+            return;
         }
         
+        ProgressDialog progressBar = new ProgressDialog(controlPanel, "ICP registration and average face computation");
+        
         IcpTask task = new IcpTask(
+                controlPanel.getSelectedFaceIndex(),
                 progressBar, 
                 controlPanel, 
-                controlPanel.showIcpPreview() ? getCanvas() : null);
+                getCanvas());
         
-        task.addPropertyChangeListener((PropertyChangeEvent evt) -> { // when done ...
+        // The following code will be executed when the task is done:
+        task.addPropertyChangeListener((PropertyChangeEvent evt) -> { 
             if ("state".equals(evt.getPropertyName()) && (SwingWorker.StateValue.DONE.equals(evt.getNewValue()))) {
-                avgFace = task.getAverageFace();
-                if (avgFaceIndex == -1) { // first successful computation
-                    avgFaceIndex = getScene().getFreeIndex();
-                }
-                getScene().setDrawableFace(avgFaceIndex, avgFace); // either shows or removes the face
-                getScene().setFaceAsPrimary(avgFaceIndex);
-                getScene().showDrawableFace(avgFaceIndex, true);
-                renderScene();
+                HumanFace avgFace = task.getAverageFace();
+                getScene().clearScene();
+                faceSceneSlot = -1;
+                getCanvas().getCamera().initLocation();
+                controlPanel.addAndSelectAvgFace(avgFace); // show selected face (either the average ot other)
             }
         });
+
+        // Prepare the scene - show the selected face towards which other faces are transformed:
+        HumanFace selectedFace = controlPanel.getSelectedFace();
+        if (controlPanel.showIcpPreview() && selectedFace != null) {
+            getScene().clearScene(); // scene is recovered automatically - see the event handler above
+            faceSceneSlot = getScene().getFreeIndex();
+            
+            // Add inital face to the scene:
+            getScene().setDrawableFace(faceSceneSlot, selectedFace);
+            getScene().getDrawableFace(faceSceneSlot).setTransparency(0.7f);
+            getScene().getDrawableFace(faceSceneSlot).setRenderMode(GL2.GL_POINT);
+            getScene().getDrawableFace(faceSceneSlot).setColor(DrawableFace.SKIN_COLOR_PRIMARY);
+            
+            // locate the camera to the best angle:
+            getCanvas().getCamera().initLocation(); 
+            getCanvas().getCamera().rotate(10, -80);
+            getCanvas().getCamera().move(40, 20);
+        }
         
         progressBar.runTask(task);
     } 
     
     private void computeSimilarity() {
-        
         ProgressDialog progressBar = new ProgressDialog(controlPanel, "Similarity computation");
         final SimilarityTask task;
         
@@ -108,15 +145,7 @@ public class BatchAction extends ControlPanelAction implements HumanFaceListener
                 task = new CompleteHausdorffDistTask(progressBar, controlPanel);
                 break;
             case BatchPanel.SIMILARITY_APPROX_HD:
-                if (avgFace == null) {
-                    JOptionPane.showMessageDialog(
-                            controlPanel, 
-                            "Compute average face first",
-                            "No average face",
-                            JOptionPane.WARNING_MESSAGE);
-                    return;
-                }
-                task = new ApproxHausdorffDistTask(progressBar, controlPanel, avgFace);
+                task = new ApproxHausdorffDistTask(progressBar, controlPanel, controlPanel.getSelectedFaceIndex());
                 break;
             default:
                 return;
@@ -125,12 +154,77 @@ public class BatchAction extends ControlPanelAction implements HumanFaceListener
         task.addPropertyChangeListener((PropertyChangeEvent evt) -> { // when done ...
             if ("state".equals(evt.getPropertyName()) && (SwingWorker.StateValue.DONE.equals(evt.getNewValue()))) {
                 double[][] result = task.getSimilarities();
-                Logger.print(Arrays.deepToString(result));
-                // TO DO ...
-            }
+                if (result != null) {
+                    this.distances = result;
+                }
+                controlPanel.enableSimilatiryExport(this.distances != null);
+           }
         });
         
         progressBar.runTask(task);
     } 
+
+    private void showSelectedIcpFace() {
+        HumanFace face = controlPanel.getSelectedFace();
+        if (face != null) {
+            if (faceSceneSlot == -1) {
+                faceSceneSlot = getScene().getFreeIndex();
+            }
+            getScene().setDrawableFace(faceSceneSlot, face); 
+            getScene().setFaceAsPrimary(faceSceneSlot);
+            getScene().showDrawableFace(faceSceneSlot, true);
+        }
+    }
+
+    private void exportDistanceResults() {
+        if (this.distances == null) {
+            return;
+        }
+        
+        File file = new FileChooserBuilder(ProjectTopComp.class)
+                .setTitle("Specify a file to save")
+                .setDefaultWorkingDirectory(new File(System.getProperty("user.home")))
+                .setFileFilter(new FileNameExtensionFilter("CSV files (*.csv)", "csv"))
+                .showSaveDialog();
+        
+        if (file != null) {
+            try {
+                BufferedWriter w = new BufferedWriter(new FileWriter(file));
+                w.write("PRI FACE;SEC FACE;Dist from PRI to SEC;Dist from SEC to PRI;lower HD;higher HD");
+                w.newLine();
+                
+                for (int i = 0; i < distances.length; i++) {
+                    for (int j = i; j < distances.length; j++) {
+                        if (distances[i][j] != distances[j][i]) {
+                            Logger.print("SIMILARITY ERROR for face " + i + " and " + j);
+                        }
+                        w.write(getShortName(controlPanel.getFacePaths().get(i).toFile()) + ";");
+                        w.write(getShortName(controlPanel.getFacePaths().get(j).toFile()) + ";");
+                        w.write(String.format("%.8f", distances[i][j]) + ";");
+                        w.write(String.format("%.8f", distances[j][i]) + ";");
+                        if (distances[i][j] > distances[j][i]) {
+                            w.write(String.format("%.8f", distances[i][j]) + ";");
+                            w.write(String.format("%.8f", distances[j][i]) + "");
+                        } else {
+                            w.write(String.format("%.8f", distances[j][i]) + ";");
+                            w.write(String.format("%.8f", distances[i][j]) + "");
+                        }
+                        w.newLine();
+                    }
+                }
+
+                w.close();
+            } catch (IOException ex) {
+                Logger.print(ex.toString());
+            }
+        }
+    }
+    
+    private static String getShortName(File file) throws IOException {
+        String name = file.toString();
+        name = name.substring(name.lastIndexOf(File.separatorChar) + 1, name.length());
+        return name;
+    }
+
     
 }
diff --git a/GUI/src/main/java/cz/fidentis/analyst/batch/BatchPanel.form b/GUI/src/main/java/cz/fidentis/analyst/batch/BatchPanel.form
index 9c119401..83d97f0b 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/batch/BatchPanel.form
+++ b/GUI/src/main/java/cz/fidentis/analyst/batch/BatchPanel.form
@@ -41,7 +41,7 @@
               <Component id="jPanel1" min="-2" max="-2" attributes="0"/>
               <EmptySpace max="-2" attributes="0"/>
               <Component id="jPanel3" min="-2" max="-2" attributes="0"/>
-              <EmptySpace pref="194" max="32767" attributes="0"/>
+              <EmptySpace max="32767" attributes="0"/>
           </Group>
       </Group>
     </DimensionLayout>
@@ -65,33 +65,24 @@
               <Group type="102" attributes="0">
                   <EmptySpace max="-2" attributes="0"/>
                   <Group type="103" groupAlignment="0" attributes="0">
-                      <Component id="jSeparator1" alignment="1" max="32767" attributes="0"/>
+                      <Component id="jCheckBox3" alignment="0" min="-2" max="-2" attributes="0"/>
+                      <Component id="jCheckBox1" alignment="0" min="-2" max="-2" attributes="0"/>
+                      <Component id="jCheckBox2" alignment="0" min="-2" max="-2" attributes="0"/>
                       <Group type="102" alignment="0" attributes="0">
-                          <Component id="jCheckBox3" min="-2" max="-2" attributes="0"/>
-                          <EmptySpace max="32767" attributes="0"/>
                           <Component id="jButton1" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace max="-2" attributes="0"/>
+                          <Component id="jButton5" min="-2" max="-2" attributes="0"/>
                       </Group>
                       <Group type="102" attributes="0">
                           <Group type="103" groupAlignment="0" attributes="0">
-                              <Group type="102" alignment="0" attributes="0">
-                                  <Group type="103" groupAlignment="0" attributes="0">
-                                      <Component id="jLabel2" min="-2" max="-2" attributes="0"/>
-                                      <Component id="jCheckBox4" alignment="0" min="-2" max="-2" attributes="0"/>
-                                  </Group>
-                                  <EmptySpace max="-2" attributes="0"/>
-                                  <Component id="comboSliderInteger1" min="-2" max="-2" attributes="0"/>
-                              </Group>
-                              <Component id="jCheckBox2" alignment="0" min="-2" max="-2" attributes="0"/>
-                              <Group type="102" alignment="0" attributes="0">
-                                  <Component id="jCheckBox1" min="-2" max="-2" attributes="0"/>
-                                  <EmptySpace max="-2" attributes="0"/>
-                                  <Component id="jComboBox1" min="-2" max="-2" attributes="0"/>
-                              </Group>
+                              <Component id="jLabel2" min="-2" max="-2" attributes="0"/>
+                              <Component id="jCheckBox4" alignment="0" min="-2" max="-2" attributes="0"/>
                           </Group>
-                          <EmptySpace min="0" pref="10" max="32767" attributes="0"/>
+                          <EmptySpace max="-2" attributes="0"/>
+                          <Component id="comboSliderInteger1" min="-2" max="-2" attributes="0"/>
                       </Group>
                   </Group>
-                  <EmptySpace max="-2" attributes="0"/>
+                  <EmptySpace pref="22" max="32767" attributes="0"/>
               </Group>
           </Group>
         </DimensionLayout>
@@ -107,19 +98,16 @@
                       </Group>
                       <Component id="comboSliderInteger1" alignment="0" min="-2" max="-2" attributes="0"/>
                   </Group>
-                  <EmptySpace max="-2" attributes="0"/>
-                  <Component id="jSeparator1" min="-2" pref="10" max="-2" attributes="0"/>
-                  <EmptySpace max="-2" attributes="0"/>
+                  <EmptySpace type="separate" max="-2" attributes="0"/>
                   <Component id="jCheckBox2" min="-2" max="-2" attributes="0"/>
                   <EmptySpace max="-2" attributes="0"/>
-                  <Group type="103" groupAlignment="3" attributes="0">
-                      <Component id="jCheckBox1" alignment="3" min="-2" max="-2" attributes="0"/>
-                      <Component id="jComboBox1" alignment="3" min="-2" max="-2" attributes="0"/>
-                  </Group>
+                  <Component id="jCheckBox1" min="-2" max="-2" attributes="0"/>
                   <EmptySpace max="-2" attributes="0"/>
+                  <Component id="jCheckBox3" min="-2" max="-2" attributes="0"/>
+                  <EmptySpace type="separate" max="-2" attributes="0"/>
                   <Group type="103" groupAlignment="3" attributes="0">
-                      <Component id="jCheckBox3" alignment="3" min="-2" max="-2" attributes="0"/>
                       <Component id="jButton1" alignment="3" min="-2" max="-2" attributes="0"/>
+                      <Component id="jButton5" alignment="3" min="-2" max="-2" attributes="0"/>
                   </Group>
                   <EmptySpace max="32767" attributes="0"/>
               </Group>
@@ -127,16 +115,6 @@
         </DimensionLayout>
       </Layout>
       <SubComponents>
-        <Component class="javax.swing.JComboBox" name="jComboBox1">
-          <Properties>
-            <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
-              <StringArray count="0"/>
-            </Property>
-          </Properties>
-          <AuxValues>
-            <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
-          </AuxValues>
-        </Component>
         <Component class="javax.swing.JButton" name="jButton1">
           <Properties>
             <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
@@ -174,6 +152,9 @@
               <ResourceString bundle="cz/fidentis/analyst/batch/Bundle.properties" key="BatchPanel.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>
         <Component class="javax.swing.JCheckBox" name="jCheckBox3">
           <Properties>
@@ -190,11 +171,12 @@
             </Property>
           </Properties>
         </Component>
-        <Component class="javax.swing.JSeparator" name="jSeparator1">
+        <Component class="javax.swing.JButton" name="jButton5">
           <Properties>
-            <Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
-              <Color blue="74" green="74" red="76" type="rgb"/>
+            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="cz/fidentis/analyst/batch/Bundle.properties" key="BatchPanel.jButton5.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
             </Property>
+            <Property name="enabled" type="boolean" value="false"/>
           </Properties>
         </Component>
       </SubComponents>
@@ -219,6 +201,10 @@
                   <Component id="jButton2" min="-2" max="-2" attributes="0"/>
                   <EmptySpace max="-2" attributes="0"/>
                   <Component id="jButton3" min="-2" max="-2" attributes="0"/>
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Component id="jLabel1" min="-2" max="-2" attributes="0"/>
+                  <EmptySpace max="-2" attributes="0"/>
+                  <Component id="jComboBox1" min="-2" max="-2" attributes="0"/>
                   <EmptySpace max="32767" attributes="0"/>
               </Group>
           </Group>
@@ -227,11 +213,17 @@
           <Group type="103" groupAlignment="0" attributes="0">
               <Group type="102" alignment="0" attributes="0">
                   <EmptySpace max="-2" attributes="0"/>
-                  <Group type="103" groupAlignment="3" 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 type="103" groupAlignment="0" attributes="0">
+                      <Group type="103" alignment="0" groupAlignment="3" attributes="0">
+                          <Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="jComboBox1" alignment="3" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <Group type="103" groupAlignment="3" 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>
                   </Group>
-                  <EmptySpace max="32767" attributes="0"/>
+                  <EmptySpace pref="16" max="32767" attributes="0"/>
               </Group>
           </Group>
         </DimensionLayout>
@@ -251,13 +243,33 @@
             </Property>
           </Properties>
         </Component>
+        <Component class="javax.swing.JLabel" name="jLabel1">
+          <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/batch/Bundle.properties" key="BatchPanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+          </Properties>
+        </Component>
+        <Component class="javax.swing.JComboBox" name="jComboBox1">
+          <Properties>
+            <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
+              <StringArray count="0"/>
+            </Property>
+          </Properties>
+          <AuxValues>
+            <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
+          </AuxValues>
+        </Component>
       </SubComponents>
     </Container>
     <Container class="javax.swing.JPanel" name="jPanel3">
       <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="Similarity">
+            <TitledBorder title="Similarity computation">
               <ResourceString PropertyName="titleX" bundle="cz/fidentis/analyst/batch/Bundle.properties" key="BatchPanel.jPanel3.border.title_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
               <Font PropertyName="font" name="Dialog" size="12" style="1"/>
             </TitledBorder>
@@ -268,22 +280,36 @@
       <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="jComboBox2" min="-2" max="-2" attributes="0"/>
-                  <EmptySpace max="32767" attributes="0"/>
-                  <Component id="jButton4" min="-2" max="-2" attributes="0"/>
+              <Group type="102" attributes="0">
                   <EmptySpace max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="0" attributes="0">
+                      <Group type="102" alignment="0" attributes="0">
+                          <Component id="jComboBox2" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace max="-2" attributes="0"/>
+                          <Component id="jButtonInfo1" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <Group type="102" alignment="0" attributes="0">
+                          <Component id="jButton4" min="-2" max="-2" attributes="0"/>
+                          <EmptySpace type="unrelated" max="-2" attributes="0"/>
+                          <Component id="jButton6" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                  </Group>
+                  <EmptySpace pref="359" 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"/>
+                  <EmptySpace min="-2" pref="14" max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="0" max="-2" attributes="0">
+                      <Component id="jButtonInfo1" max="32767" attributes="0"/>
+                      <Component id="jComboBox2" max="32767" attributes="0"/>
+                  </Group>
+                  <EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
                   <Group type="103" groupAlignment="3" attributes="0">
-                      <Component id="jComboBox2" alignment="3" min="-2" max="-2" attributes="0"/>
                       <Component id="jButton4" alignment="3" min="-2" max="-2" attributes="0"/>
+                      <Component id="jButton6" alignment="3" min="-2" max="-2" attributes="0"/>
                   </Group>
                   <EmptySpace max="32767" attributes="0"/>
               </Group>
@@ -308,6 +334,34 @@
             </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/batch/Bundle.properties" key="BatchPanel.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>
+        <Component class="javax.swing.JButton" name="jButton6">
+          <Properties>
+            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="cz/fidentis/analyst/batch/Bundle.properties" key="BatchPanel.jButton6.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+            <Property name="enabled" type="boolean" value="false"/>
+          </Properties>
+        </Component>
       </SubComponents>
     </Container>
   </SubComponents>
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 f7f7cc3f..0bf9da45 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/batch/BatchPanel.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/batch/BatchPanel.java
@@ -1,17 +1,16 @@
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
 package cz.fidentis.analyst.batch;
 
+import cz.fidentis.analyst.Logger;
 import cz.fidentis.analyst.core.ControlPanel;
 import cz.fidentis.analyst.core.ProjectTopComp;
+import cz.fidentis.analyst.face.HumanFace;
 import cz.fidentis.analyst.face.HumanFaceFactory;
+import cz.fidentis.analyst.mesh.io.MeshObjExporter;
 import static cz.fidentis.analyst.registration.RegistrationPanel.getStaticIcon;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.io.File;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -19,6 +18,7 @@ import java.util.Collections;
 import java.util.List;
 import javax.swing.AbstractAction;
 import javax.swing.ImageIcon;
+import javax.swing.JOptionPane;
 import javax.swing.filechooser.FileNameExtensionFilter;
 import org.openide.filesystems.FileChooserBuilder;
 
@@ -29,11 +29,13 @@ import org.openide.filesystems.FileChooserBuilder;
  */
 public class BatchPanel extends ControlPanel {
     
-    public static final String SIMILARITY_COMPLETE_HD = "Two-way HD of all pairs";
-    public static final String SIMILARITY_APPROX_HD = "Approximate HD via average face";
+    public static final String SIMILARITY_COMPLETE_HD = "Two-way HD of all pairs (very slow)";
+    public static final String SIMILARITY_APPROX_HD = "Approximate HD using selected face (fast)";
     
     public static final String ACTION_COMMAND_COMPUTE_ICP = "Compute ICP and average face";
     public static final String ACTION_COMMAND_COMPUTE_SIMILARITY = "Compute similarity";
+    public static final String ACTION_COMMAND_SHOW_SELECTED_FACE = "Show selected face";
+    public static final String ACTION_COMMAND_EXPORT_SIMILARITY = "Export similarity";
     
     /*
      * Mandatory design elements
@@ -41,13 +43,14 @@ public class BatchPanel extends ControlPanel {
     public static final String ICON = "registration28x28.png";
     public static final String NAME = "Batch Processing";
     
-    private List<Path> faces = new ArrayList<>();
+    private List<Path> paths = new ArrayList<>();
     private HumanFaceFactory factory = new HumanFaceFactory();
+    private boolean haveAvgFace = false;
     
     /**
      * ICP undersampling. 100 = none
      */
-    private int undersampling = 10;
+    private int undersampling = 100;
     
 
     /**
@@ -60,7 +63,7 @@ public class BatchPanel extends ControlPanel {
         
         initComponents();
         
-        jButton2.addActionListener(new AbstractAction() {
+        jButton2.addActionListener(new AbstractAction() { // [Add faces]
             @Override
             public void actionPerformed(ActionEvent e) {
                 File[] files = new FileChooserBuilder(ProjectTopComp.class)
@@ -72,25 +75,21 @@ public class BatchPanel extends ControlPanel {
 
                 if (files != null) {
                     for (File file : files) {
-                        faces.add(Paths.get(file.getAbsolutePath()));
+                        paths.add(Paths.get(file.getAbsolutePath()));
                     }
                 }
 
-                faces.stream().forEach(f -> {
+                paths.stream().forEach(f -> {
                     String name = f.toString();
                     name = name.substring(name.lastIndexOf(File.separatorChar) + 1, name.length());
                     jComboBox1.addItem(name);
-                });
-                jComboBox1.setSelectedIndex(0);
+                }); // Action event is triggered automatically by the jComboBox1 with the first item set as selected
             }
         });
         
-        jButton3.addActionListener(new AbstractAction() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                faces.clear();
-                jComboBox1.removeAllItems();
-            }
+        jButton3.addActionListener((ActionEvent e) -> { 
+            paths.clear();
+            jComboBox1.removeAllItems();
         });
         
         jButton1.addActionListener(createListener(action, ACTION_COMMAND_COMPUTE_ICP));
@@ -102,10 +101,22 @@ public class BatchPanel extends ControlPanel {
             this.undersampling = comboSliderInteger1.getValue();
         });
         
-        jComboBox2.addItem(SIMILARITY_COMPLETE_HD);
         jComboBox2.addItem(SIMILARITY_APPROX_HD);
+        jComboBox2.addItem(SIMILARITY_COMPLETE_HD);
         
         jButton4.addActionListener(createListener(action, ACTION_COMMAND_COMPUTE_SIMILARITY));
+        
+        jButton5.addActionListener((ActionEvent e) -> { // [Export AVG face]
+            exportAvgFace();
+        });
+        
+        jButton6.addActionListener(createListener(action, ACTION_COMMAND_EXPORT_SIMILARITY)); // [Export results]
+        
+        jComboBox1.addActionListener(createListener(action, ACTION_COMMAND_SHOW_SELECTED_FACE));
+        
+        jButtonInfo1.addActionListener((ActionEvent e) -> { 
+            showSimilarityInfo();
+        });
     }
     
     @Override
@@ -114,19 +125,87 @@ public class BatchPanel extends ControlPanel {
     }
     
     /**
-     * Returns index of a face that should be used for the average face computation
-     * @return index of a face that should be used for the average face computation
+     * Sets the similarity export button
+     * 
+     * @param on set on or off
+     */
+    public void enableSimilatiryExport(boolean on) {
+        jButton6.setEnabled(on);
+    }
+    
+    /**
+     * Returns index of a face that is selected or -1
+     * @return index of a face that is selected or -1
+     */
+    public int getSelectedFaceIndex() {
+        return (jComboBox1.getItemCount() == 0) ? -1 : jComboBox1.getSelectedIndex();
+    }
+
+    /**
+     * Returns the face select in the combo box
+     * @return the face select in the combo box
+     */
+    public HumanFace getSelectedFace() {
+        if (jComboBox1.getItemCount() == 0) {
+            return null;
+        }
+        
+        //Logger.print("AAA " + paths.get(jComboBox1.getSelectedIndex()).toString());
+        String id = factory.loadFace(paths.get(jComboBox1.getSelectedIndex()).toFile());
+        return factory.getFace(id);
+    }
+    
+    /**
+     * Method logic:
+     * <ul>
+     * <li>If {@code face} is not {@code null} and the previous AVG face exists, 
+     * then the AVG face is replaced and selected.</li>
+     * <li>If {@code face} is not {@code null} and no previous AVG face exists, 
+     * then the AVG face is added and selected.</li>
+     * <li>If {@code face} is {@code null} and the previous AVG face exists, 
+     * then the previous AVG face is preserved and selected.</li>
+     * <li>If {@code face} is {@code null} and no previous AVG face exists, 
+     * then nothing happens.</li>
+     * </ul>
+     *
+     * @param face new AVG face
      */
-    public int getTemplateFaceIndex() {
-        return this.jComboBox1.getSelectedIndex();
+    public void addAndSelectAvgFace(HumanFace face) {
+        //this.avgFace = face;
+        
+        // Copy existing ites of the combobox and clear it
+        List<String> items = new ArrayList<>();
+        for (int i = 0; i < jComboBox1.getItemCount(); i++) {
+            items.add(jComboBox1.getItemAt(i));
+        }
+        jComboBox1.removeAllItems();
+        
+        // the logic:
+        if (face != null && haveAvgFace) {
+            paths.set(0, new File(face.getPath()).toPath()); // replace path
+            // do nothing with the combobox
+        } else if (face != null && !haveAvgFace) {
+            paths.add(0, new File(face.getPath()).toPath());
+            items.add(0, "Average face");
+        } else if (face == null && haveAvgFace) {
+        } else {
+            // do nothing at all
+        }
+        
+        // Copy items back to the combobox.
+        // Action event is triggered automatically by the jComboBox1 with the first item set as selected
+        items.stream().forEach(i -> jComboBox1.addItem(i));
+        
+        haveAvgFace = (face != null || haveAvgFace);
+        jButton5.setEnabled(haveAvgFace);
     }
     
     /**
      * Returns paths to faces to be processed
      * @return paths to faces to be processed
      */
-    public List<Path> getFaces() {
-        return Collections.unmodifiableList(faces);
+    public List<Path> getFacePaths() {
+        return Collections.unmodifiableList(paths);
     }
     
     /**
@@ -185,6 +264,43 @@ public class BatchPanel extends ControlPanel {
     public String getSimilarityStrategy() {
         return jComboBox2.getSelectedItem().toString();
     }
+    
+    private void showSimilarityInfo() {
+        JOptionPane.showMessageDialog(
+                this, 
+                "<html>"
+                        + "<strong>" + SIMILARITY_COMPLETE_HD + "</strong>: <br/>"
+                        + "Hausdorff distance is computed for all paris in both directions.<br/>"
+                        + "<br/>"
+                        + "<strong>" + SIMILARITY_APPROX_HD + "</strong>: <br/>"
+                        + "First, a relative Hausdorff distance is computed between all faces and the selected face T.<br/>"
+                        + "Then, the distance of each pair A,B is estimated as the difference between A,T and B,T."
+                        + "</html>",
+                "Distance computation strategies",
+                JOptionPane.INFORMATION_MESSAGE
+        );
+    }
+    
+        private void exportAvgFace() {
+        if (!haveAvgFace) {
+            return;
+        }
+        
+        File file = new FileChooserBuilder(ProjectTopComp.class)
+                .setTitle("Specify a file to save")
+                .setDefaultWorkingDirectory(new File(System.getProperty("user.home")))
+                .setFileFilter(new FileNameExtensionFilter("obj files (*.obj)", "obj"))
+                .showSaveDialog();
+        
+        if (file != null) {
+            try {
+                String id = factory.loadFace(paths.get(0).toFile()); // first face is average
+                new MeshObjExporter(factory.getFace(id).getMeshModel()).exportModelToObj(file);
+            } catch (IOException ex) {
+                Logger.print(ex.toString());
+            }
+        }
+    }    
 
     /**
      * This method is called from within the constructor to initialize the form.
@@ -196,7 +312,6 @@ public class BatchPanel extends ControlPanel {
     private void initComponents() {
 
         jPanel1 = new javax.swing.JPanel();
-        jComboBox1 = new javax.swing.JComboBox<>();
         jButton1 = new javax.swing.JButton();
         jLabel2 = new javax.swing.JLabel();
         comboSliderInteger1 = new cz.fidentis.analyst.core.ComboSliderInteger();
@@ -204,13 +319,17 @@ public class BatchPanel extends ControlPanel {
         jCheckBox2 = new javax.swing.JCheckBox();
         jCheckBox3 = new javax.swing.JCheckBox();
         jCheckBox4 = new javax.swing.JCheckBox();
-        jSeparator1 = new javax.swing.JSeparator();
+        jButton5 = new javax.swing.JButton();
         jPanel2 = new javax.swing.JPanel();
         jButton2 = new javax.swing.JButton();
         jButton3 = new javax.swing.JButton();
+        jLabel1 = new javax.swing.JLabel();
+        jComboBox1 = new javax.swing.JComboBox<>();
         jPanel3 = new javax.swing.JPanel();
         jComboBox2 = new javax.swing.JComboBox<>();
         jButton4 = new javax.swing.JButton();
+        jButtonInfo1 = new javax.swing.JButton();
+        jButton6 = new javax.swing.JButton();
 
         setPreferredSize(new java.awt.Dimension(600, 600));
 
@@ -235,13 +354,19 @@ public class BatchPanel extends ControlPanel {
 
         jCheckBox2.setSelected(true);
         org.openide.awt.Mnemonics.setLocalizedText(jCheckBox2, org.openide.util.NbBundle.getMessage(BatchPanel.class, "BatchPanel.jCheckBox2.text")); // NOI18N
+        jCheckBox2.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                jCheckBox2ActionPerformed(evt);
+            }
+        });
 
         jCheckBox3.setSelected(true);
         org.openide.awt.Mnemonics.setLocalizedText(jCheckBox3, org.openide.util.NbBundle.getMessage(BatchPanel.class, "BatchPanel.jCheckBox3.text")); // NOI18N
 
         org.openide.awt.Mnemonics.setLocalizedText(jCheckBox4, org.openide.util.NbBundle.getMessage(BatchPanel.class, "BatchPanel.jCheckBox4.text")); // NOI18N
 
-        jSeparator1.setForeground(new java.awt.Color(118, 116, 116));
+        org.openide.awt.Mnemonics.setLocalizedText(jButton5, org.openide.util.NbBundle.getMessage(BatchPanel.class, "BatchPanel.jButton5.text")); // NOI18N
+        jButton5.setEnabled(false);
 
         javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
         jPanel1.setLayout(jPanel1Layout);
@@ -250,26 +375,20 @@ public class BatchPanel extends ControlPanel {
             .addGroup(jPanel1Layout.createSequentialGroup()
                 .addContainerGap()
                 .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                    .addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.TRAILING)
+                    .addComponent(jCheckBox3)
+                    .addComponent(jCheckBox1)
+                    .addComponent(jCheckBox2)
                     .addGroup(jPanel1Layout.createSequentialGroup()
-                        .addComponent(jCheckBox3)
-                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-                        .addComponent(jButton1))
+                        .addComponent(jButton1)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(jButton5))
                     .addGroup(jPanel1Layout.createSequentialGroup()
                         .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                            .addGroup(jPanel1Layout.createSequentialGroup()
-                                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                                    .addComponent(jLabel2)
-                                    .addComponent(jCheckBox4))
-                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                                .addComponent(comboSliderInteger1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
-                            .addComponent(jCheckBox2)
-                            .addGroup(jPanel1Layout.createSequentialGroup()
-                                .addComponent(jCheckBox1)
-                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                                .addComponent(jComboBox1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
-                        .addGap(0, 10, Short.MAX_VALUE)))
-                .addContainerGap())
+                            .addComponent(jLabel2)
+                            .addComponent(jCheckBox4))
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(comboSliderInteger1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
+                .addContainerGap(22, Short.MAX_VALUE))
         );
         jPanel1Layout.setVerticalGroup(
             jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -281,18 +400,16 @@ public class BatchPanel extends ControlPanel {
                         .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                         .addComponent(jCheckBox4))
                     .addComponent(comboSliderInteger1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGap(18, 18, 18)
                 .addComponent(jCheckBox2)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
-                    .addComponent(jCheckBox1)
-                    .addComponent(jComboBox1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+                .addComponent(jCheckBox1)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(jCheckBox3)
+                .addGap(18, 18, 18)
                 .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
-                    .addComponent(jCheckBox3)
-                    .addComponent(jButton1))
+                    .addComponent(jButton1)
+                    .addComponent(jButton5))
                 .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
         );
 
@@ -302,6 +419,9 @@ public class BatchPanel extends ControlPanel {
 
         org.openide.awt.Mnemonics.setLocalizedText(jButton3, org.openide.util.NbBundle.getMessage(BatchPanel.class, "BatchPanel.jButton3.text")); // NOI18N
 
+        jLabel1.setFont(new java.awt.Font("Ubuntu", 1, 15)); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(BatchPanel.class, "BatchPanel.jLabel1.text")); // NOI18N
+
         javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
         jPanel2.setLayout(jPanel2Layout);
         jPanel2Layout.setHorizontalGroup(
@@ -311,40 +431,70 @@ public class BatchPanel extends ControlPanel {
                 .addComponent(jButton2)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addComponent(jButton3)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(jLabel1)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(jComboBox1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                 .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
         );
         jPanel2Layout.setVerticalGroup(
             jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(jPanel2Layout.createSequentialGroup()
                 .addContainerGap()
-                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
-                    .addComponent(jButton2)
-                    .addComponent(jButton3))
-                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                        .addComponent(jLabel1)
+                        .addComponent(jComboBox1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+                    .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                        .addComponent(jButton2)
+                        .addComponent(jButton3)))
+                .addContainerGap(16, Short.MAX_VALUE))
         );
 
         jPanel3.setBorder(javax.swing.BorderFactory.createTitledBorder(null, org.openide.util.NbBundle.getMessage(BatchPanel.class, "BatchPanel.jPanel3.border.title_1"), javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Dialog", 1, 12))); // NOI18N
 
         org.openide.awt.Mnemonics.setLocalizedText(jButton4, org.openide.util.NbBundle.getMessage(BatchPanel.class, "BatchPanel.jButton4.text")); // NOI18N
 
+        jButtonInfo1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/info.png"))); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(jButtonInfo1, org.openide.util.NbBundle.getMessage(BatchPanel.class, "BatchPanel.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);
+
+        org.openide.awt.Mnemonics.setLocalizedText(jButton6, org.openide.util.NbBundle.getMessage(BatchPanel.class, "BatchPanel.jButton6.text")); // NOI18N
+        jButton6.setEnabled(false);
+
         javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
         jPanel3.setLayout(jPanel3Layout);
         jPanel3Layout.setHorizontalGroup(
             jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(jPanel3Layout.createSequentialGroup()
                 .addContainerGap()
-                .addComponent(jComboBox2, 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(jButton4)
-                .addContainerGap())
+                .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addGroup(jPanel3Layout.createSequentialGroup()
+                        .addComponent(jComboBox2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(jButtonInfo1))
+                    .addGroup(jPanel3Layout.createSequentialGroup()
+                        .addComponent(jButton4)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+                        .addComponent(jButton6)))
+                .addContainerGap(359, Short.MAX_VALUE))
         );
         jPanel3Layout.setVerticalGroup(
             jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(jPanel3Layout.createSequentialGroup()
-                .addContainerGap()
+                .addGap(14, 14, 14)
+                .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
+                    .addComponent(jButtonInfo1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                    .addComponent(jComboBox2))
+                .addGap(8, 8, 8)
                 .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
-                    .addComponent(jComboBox2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
-                    .addComponent(jButton4))
+                    .addComponent(jButton4)
+                    .addComponent(jButton6))
                 .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
         );
 
@@ -369,10 +519,14 @@ public class BatchPanel extends ControlPanel {
                 .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
-                .addContainerGap(194, Short.MAX_VALUE))
+                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
         );
     }// </editor-fold>//GEN-END:initComponents
 
+    private void jCheckBox2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox2ActionPerformed
+        // TODO add your handling code here:
+    }//GEN-LAST:event_jCheckBox2ActionPerformed
+
     private void jCheckBox1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox1ActionPerformed
         // TODO add your handling code here:
     }//GEN-LAST:event_jCheckBox1ActionPerformed
@@ -388,16 +542,20 @@ public class BatchPanel extends ControlPanel {
     private javax.swing.JButton jButton2;
     private javax.swing.JButton jButton3;
     private javax.swing.JButton jButton4;
+    private javax.swing.JButton jButton5;
+    private javax.swing.JButton jButton6;
+    private javax.swing.JButton jButtonInfo1;
     private javax.swing.JCheckBox jCheckBox1;
     private javax.swing.JCheckBox jCheckBox2;
     private javax.swing.JCheckBox jCheckBox3;
     private javax.swing.JCheckBox jCheckBox4;
     private javax.swing.JComboBox<String> jComboBox1;
     private javax.swing.JComboBox<String> jComboBox2;
+    private javax.swing.JLabel jLabel1;
     private javax.swing.JLabel jLabel2;
     private javax.swing.JPanel jPanel1;
     private javax.swing.JPanel jPanel2;
     private javax.swing.JPanel jPanel3;
-    private javax.swing.JSeparator jSeparator1;
     // End of variables declaration//GEN-END:variables
+
 }
diff --git a/GUI/src/main/java/cz/fidentis/analyst/batch/CompleteHausdorffDistTask.java b/GUI/src/main/java/cz/fidentis/analyst/batch/CompleteHausdorffDistTask.java
index 7ae9cb3c..095c9e2a 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/batch/CompleteHausdorffDistTask.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/batch/CompleteHausdorffDistTask.java
@@ -34,9 +34,9 @@ public class CompleteHausdorffDistTask extends SimilarityTask {
     @Override
     protected Void doInBackground() throws Exception {
         HumanFaceFactory factory = getControlPanel().getHumanFaceFactory();
-        List<Path> faces = getControlPanel().getFaces();
+        List<Path> faces = getControlPanel().getFacePaths();
 
-        getControlPanel().getHumanFaceFactory().setReuseDumpFile(true);
+        factory.setReuseDumpFile(true); // it's safe because no changes are made to models 
         factory.setStrategy(HumanFaceFactory.Strategy.MRU);
         
         totalTime.start();
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 093e56ac..59a6d71e 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/batch/IcpTask.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/batch/IcpTask.java
@@ -1,6 +1,5 @@
 package cz.fidentis.analyst.batch;
 
-import com.jogamp.opengl.GL2;
 import cz.fidentis.analyst.Logger;
 import cz.fidentis.analyst.canvas.Canvas;
 import cz.fidentis.analyst.core.ProgressDialog;
@@ -10,8 +9,10 @@ import cz.fidentis.analyst.icp.IcpTransformer;
 import cz.fidentis.analyst.icp.NoUndersampling;
 import cz.fidentis.analyst.icp.RandomStrategy;
 import cz.fidentis.analyst.mesh.core.MeshModel;
+import cz.fidentis.analyst.mesh.io.MeshObjExporter;
 import cz.fidentis.analyst.scene.DrawableFace;
 import cz.fidentis.analyst.visitors.kdtree.AvgFaceConstructor;
+import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.List;
@@ -30,28 +31,37 @@ public class IcpTask extends SwingWorker<MeshModel, HumanFace> {
     private final ProgressDialog progressDialog;
     private final BatchPanel controlPanel;
     private final Canvas canvas;
-    private int avgFaceSceneIndex = -1;
-    private int icpFaceSceneIndex = -1;
+    
+    private int faceSceneSlot = -1; // scene slot to show the transformed face
+    
+    private final int initialFaceIndex; // index of initial face in the list of available faces
     
     private final Stopwatch totalTime = new Stopwatch("Total computation time:\t");
     private final Stopwatch avgFaceComputationTime = new Stopwatch("AVG face computation time:\t");
     private final Stopwatch icpComputationTime = new Stopwatch("ICP registration time:\t");
-    private final Stopwatch loadTime = new Stopwatch("File (re-)loading time:\t");
+    private final Stopwatch loadTime = new Stopwatch("Disk access time:\t");
     private final Stopwatch kdTreeConstructionTime = new Stopwatch("KD trees construction time:\t");
     
     /**
      * Constructor.
      * 
+     * @param initialFaceIndex Index to the {@code controlPanel.getFaces()} list of faces
      * @param progressDialog A window that show the progress of the computation. Must not be {@code null}
      * @param controlPanel A control panel with computation parameters. Must not be {@code null}
-     * @param canvas A canvas with 3D scene. If not {@code null}, then the transformed faces are rendered one by one.
+     * @param canvas A canvas with 3D scene. 
      */
-    public IcpTask(ProgressDialog progressDialog, BatchPanel controlPanel, Canvas canvas) {
-        try {
-            avgFace = new HumanFace(controlPanel.getFaces().get(controlPanel.getTemplateFaceIndex()).toFile());
-        } catch (IOException ex) {
-            throw new IllegalArgumentException(ex);
+    public IcpTask(int initialFaceIndex, ProgressDialog progressDialog, BatchPanel controlPanel, Canvas canvas) {
+        if (initialFaceIndex < 0 || initialFaceIndex >= controlPanel.getFacePaths().size()) {
+            throw new IllegalArgumentException("initFace");
         }
+        
+        this.initialFaceIndex = initialFaceIndex;
+        
+        //try {
+        //    avgFace = new HumanFace(controlPanel.getFaces().get(controlPanel.getTemplateFaceIndex()).toFile());
+        //} catch (IOException ex) {
+        //    throw new IllegalArgumentException(ex);
+        //}
 
         this.progressDialog = progressDialog;
         this.controlPanel = controlPanel;
@@ -61,26 +71,33 @@ public class IcpTask extends SwingWorker<MeshModel, HumanFace> {
     @Override
     protected MeshModel doInBackground() throws Exception {
         HumanFaceFactory factory = controlPanel.getHumanFaceFactory();
-        List<Path> faces = controlPanel.getFaces();
+        List<Path> faces = controlPanel.getFacePaths();
         int undersampling = controlPanel.getIcpUndersampling();
         boolean computeICP = controlPanel.computeICP();
         boolean computeAvgFace = controlPanel.computeAvgFace();
 
-        controlPanel.getHumanFaceFactory().setReuseDumpFile(true);
-        factory.setStrategy(HumanFaceFactory.Strategy.LRU);
+        factory.setReuseDumpFile(false); // we can't use this optimization because changes are made in models
+        factory.setStrategy(HumanFaceFactory.Strategy.MRU); // keep first X faces in the memory
+        
+        // We don't need to reaload the initFace periodically for two reasons:
+        //   - It is never dumped from memory to disk because we use MRU
+        //   - Even if dumped, the face keeps in the mempry until we hold the pointer to it
+        loadTime.start();
+        String initFaceId = factory.loadFace(faces.get(initialFaceIndex).toFile());
+        HumanFace initFace = factory.getFace(initFaceId);
+        loadTime.stop();
         
         totalTime.start();
-
-        AvgFaceConstructor avgFaceConstructor = new AvgFaceConstructor(avgFace.getMeshModel());
-
+        AvgFaceConstructor avgFaceConstructor = null;
+        
         for (int i = 0; i < faces.size(); i++) {
 
             if (isCancelled()) {
                 return null;
             }
-
+            
             // Compute AVG template face. Use each tranfromed face only once. Skip the original face
-            if (i != controlPanel.getTemplateFaceIndex() && (computeICP || computeAvgFace)) {
+            if (i != initialFaceIndex && (computeICP || computeAvgFace)) {
 
                 loadTime.start();
                 String faceId = factory.loadFace(faces.get(i).toFile());
@@ -91,40 +108,57 @@ public class IcpTask extends SwingWorker<MeshModel, HumanFace> {
                 face.computeKdTree(false);
                 kdTreeConstructionTime.stop();
 
-                if (computeICP) {// ICP registration:
+                if (computeICP) { // ICP registration:
                     icpComputationTime.start();
                     IcpTransformer icp = new IcpTransformer(
-                            avgFace.getMeshModel(), 
+                            initFace.getMeshModel(), 
                             100, 
                             controlPanel.scaleIcp(), 
                             0.3, 
                             (undersampling == 100) ? new NoUndersampling() : new RandomStrategy(undersampling));
-                    face.getMeshModel().compute(icp, true);
+                    face.getMeshModel().compute(icp, true); // superimpose face towards the initFace
                     icpComputationTime.stop();
                 }
 
                 if (computeAvgFace) { // AVG template face
                     avgFaceComputationTime.start();
+                    if (avgFaceConstructor == null) {
+                        avgFaceConstructor = new AvgFaceConstructor(initFace.getMeshModel());
+                    }
                     face.getKdTree().accept(avgFaceConstructor);
                     avgFaceComputationTime.stop();
                 }
+                
+                // Preserving k-d tree could accelerate the recomputation of ICP or average face, 
+                // but slows down the approximative computation of distace where
+                // only k-d tree of the template face is necessary (or is not used at all)
+                face.removeKdTree();
 
-                publish(face);
+                publish(face); // update progress bar and possibly render the transformed face
             }
 
             int progress = (int) Math.round(100.0 * (i + 1.0) / faces.size());
             progressDialog.setValue(progress);
+            
+            //Logger.print(factory.toString());
         }
 
         if (computeAvgFace) {
-            avgFace.setMeshModel(avgFaceConstructor.getAveragedMeshModel());
+            File tempFile = File.createTempFile(this.getClass().getSimpleName(), ".obj");
+            tempFile.deleteOnExit();
+            avgFace = new HumanFace(avgFaceConstructor.getAveragedMeshModel(), tempFile.getCanonicalPath());
+            try {
+                new MeshObjExporter(avgFace.getMeshModel()).exportModelToObj(tempFile);
+            } catch (IOException ex) {
+                Logger.print(ex.toString());
+            }
         }
         
         totalTime.stop();
 
         printTimeStats();
 
-        return avgFace.getMeshModel();
+        return (avgFace == null) ? null : avgFace.getMeshModel();
     }
 
     @Override
@@ -133,41 +167,21 @@ public class IcpTask extends SwingWorker<MeshModel, HumanFace> {
         if (isCancelled()) {
             avgFace = null;
         }
-        if (canvas != null && avgFaceSceneIndex >= 0) {
-            canvas.getScene().setDrawableFace(avgFaceSceneIndex, null); // remove from scene
-            avgFaceSceneIndex = -1;
-        }
-        if (canvas != null && icpFaceSceneIndex >= 0) {
-            canvas.getScene().setDrawableFace(icpFaceSceneIndex, null); // remove from scene
-            icpFaceSceneIndex = -1;
-        }
-        if (canvas != null) {
-            canvas.getCamera().initLocation();
-        }
     }
 
     @Override
-    protected void process(List<HumanFace> chunks) {        
+    protected void process(List<HumanFace> chunks) {
         chunks.stream().forEach(f -> {
-            if (canvas != null) {
-                if (icpFaceSceneIndex == -1) { // first rendering
-                    avgFaceSceneIndex = canvas.getScene().getFreeIndex();
-                    
-                    // locate the camera to the best angle:
-                    canvas.getCamera().initLocation(); 
-                    canvas.getCamera().rotate(10, -80);
-                    canvas.getCamera().move(40, 20);
-                    
-                    canvas.getScene().setDrawableFace(avgFaceSceneIndex, avgFace);
-                    canvas.getScene().getDrawableFace(avgFaceSceneIndex).setRenderMode(GL2.GL_POINT);
-                    canvas.getScene().getDrawableFace(avgFaceSceneIndex).setTransparency(0.7f);
-                    
-                    icpFaceSceneIndex = canvas.getScene().getFreeIndex();                    
+            if (isCancelled()) {
+                return;
+            }
+            if (controlPanel.showIcpPreview()) {
+                if (faceSceneSlot == -1) {
+                    faceSceneSlot = canvas.getScene().getFreeIndex();
                 }
-                
-                canvas.getScene().setDrawableFace(icpFaceSceneIndex, f);
-                canvas.getScene().getDrawableFace(icpFaceSceneIndex).setTransparency(0.5f);
-                canvas.getScene().getDrawableFace(icpFaceSceneIndex).setColor(DrawableFace.SKIN_COLOR_SECONDARY);
+                canvas.getScene().setDrawableFace(faceSceneSlot, f);
+                canvas.getScene().getDrawableFace(faceSceneSlot).setTransparency(0.5f);
+                canvas.getScene().getDrawableFace(faceSceneSlot).setColor(DrawableFace.SKIN_COLOR_SECONDARY);
                 canvas.renderScene();
             }
         });
diff --git a/GUI/src/main/java/cz/fidentis/analyst/batch/SimilarityTask.java b/GUI/src/main/java/cz/fidentis/analyst/batch/SimilarityTask.java
index 5a570ccc..d57085fd 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/batch/SimilarityTask.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/batch/SimilarityTask.java
@@ -24,7 +24,7 @@ public abstract class SimilarityTask extends SwingWorker<Void, Integer> {
     public SimilarityTask(ProgressDialog progressDialog, BatchPanel controlPanel) {
         this.progressDialog = progressDialog;
         this.controlPanel = controlPanel;
-        int nFaces = getControlPanel().getFaces().size();
+        int nFaces = getControlPanel().getFacePaths().size();
         similarities = new double[nFaces][nFaces];
     }
     
diff --git a/GUI/src/main/java/cz/fidentis/analyst/scene/Scene.java b/GUI/src/main/java/cz/fidentis/analyst/scene/Scene.java
index acd044bd..0a319fc0 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/scene/Scene.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/scene/Scene.java
@@ -27,6 +27,18 @@ public class Scene {
     
     public static final int MAX_FACES_IN_SCENE = 20;
     
+    /**
+     * Removes all objects.
+     */
+    public void clearScene() {
+        this.drawableFaces.clear();
+        this.drawableFeaturePoints.clear();
+        this.drawableSymmetryPlanes.clear();
+        this.otherDrawables.clear();
+        primaryFaceIndex = -1;
+        secondaryFaceIndex = -1;
+    }
+    
     /**
      * Finds and returns a first free index for human face and its drawables.
      * @return a first free index for human face and its drawables.
@@ -391,5 +403,8 @@ public class Scene {
         return this.drawableFaces.size();
     }
     
-    
+    @Override
+    public String toString() {
+        return this.drawableFaces.toString();
+    }
 }
diff --git a/GUI/src/main/resources/cz/fidentis/analyst/batch/Bundle.properties b/GUI/src/main/resources/cz/fidentis/analyst/batch/Bundle.properties
index 554d822c..abf208f3 100644
--- a/GUI/src/main/resources/cz/fidentis/analyst/batch/Bundle.properties
+++ b/GUI/src/main/resources/cz/fidentis/analyst/batch/Bundle.properties
@@ -1,16 +1,19 @@
-
-BatchPanel.jCheckBox1.text_1=compute average face from
-BatchPanel.jLabel2.text=ICP undersampling (100% = none):
-BatchPanel.jButton1.text=Compute
-BatchPanel.jPanel1.border.title=Registration and average face computation
-BatchPanel.jPanel3.border.title_1=Similarity
+BatchPanel.jPanel3.border.title_1=Similarity computation
 BatchPanel.jButton3.text=Clear faces
 # To change this license header, choose License Headers in Project Properties.
 # To change this template file, choose Tools | Templates
 # and open the template in the editor.
 BatchPanel.jButton2.text=Add faces
 BatchPanel.jPanel2.border.title_1=Dataset
-BatchPanel.jCheckBox2.text=compute ICP transformations
 BatchPanel.jButton4.text=Compute
-BatchPanel.jCheckBox3.text=show ICP preview in 3D
+BatchPanel.jPanel1.border.title=Registration and average face computation
+BatchPanel.jLabel1.text=Selected face:
+BatchPanel.jButton5.text=Export AVG face
 BatchPanel.jCheckBox4.text=ICP scaling
+BatchPanel.jCheckBox3.text=Show ICP transformations in the 3D preview
+BatchPanel.jCheckBox2.text=Transform faces towards the selected one using ICP
+BatchPanel.jCheckBox1.text_1=Compute an average face from the selected  one
+BatchPanel.jLabel2.text=ICP undersampling (100% = none):
+BatchPanel.jButton1.text=Compute
+BatchPanel.jButtonInfo1.text=
+BatchPanel.jButton6.text=Export results
-- 
GitLab