Skip to content
Snippets Groups Projects
Commit 4615002e authored by Radek Ošlejšek's avatar Radek Ošlejšek
Browse files

Resolve "Export average face"

parent 36c1cef3
No related branches found
No related tags found
No related merge requests found
Showing
with 628 additions and 247 deletions
......@@ -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.
*
......
......@@ -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) {
......
......@@ -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();
......
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;
}
}
......@@ -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>
......
......@@ -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();
......
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();
}
});
......
......@@ -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];
}
......
......@@ -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();
}
}
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment