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

Added HumanFaceCache implementations

parent 37f1bd32
No related branches found
No related tags found
No related merge requests found
...@@ -12,10 +12,10 @@ import java.io.File; ...@@ -12,10 +12,10 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects;
/** /**
* This class encapsulates data for a 3D scan of a single human face. * This class encapsulates data for a 3D scan of a single human face.
...@@ -40,13 +40,15 @@ import java.io.Serializable; ...@@ -40,13 +40,15 @@ import java.io.Serializable;
*/ */
public class HumanFace implements MeshListener, Serializable { public class HumanFace implements MeshListener, Serializable {
private MeshModel meshModel; private final MeshModel meshModel;
private Plane symmetryPlane; private Plane symmetryPlane;
private MeshFacet cuttingPlane; private MeshFacet cuttingPlane;
private transient EventBus eventBus = new EventBus(); private final transient EventBus eventBus = new EventBus();
private final String id;
/** /**
* Reads a 3D human face from the given OBJ file. * Reads a 3D human face from the given OBJ file.
...@@ -56,20 +58,10 @@ public class HumanFace implements MeshListener, Serializable { ...@@ -56,20 +58,10 @@ public class HumanFace implements MeshListener, Serializable {
* @throws IOException on I/O failure * @throws IOException on I/O failure
*/ */
public HumanFace(File file) throws IOException { public HumanFace(File file) throws IOException {
this(new FileInputStream(file)); meshModel = MeshObjLoader.read(new FileInputStream(file));
}
/**
* Reads a 3D human face from the given OBJ stream.
* Use {@link restoreFromFile} to restore the human face from a dump file.
*
* @param is input stream with OBJ data
* @throws IOException on I/O failure
*/
public HumanFace(InputStream is) throws IOException {
meshModel = MeshObjLoader.read(is);
meshModel.simplifyModel(); meshModel.simplifyModel();
meshModel.registerListener(this); meshModel.registerListener(this);
this.id = file.getCanonicalPath();
} }
/** /**
...@@ -142,6 +134,15 @@ public class HumanFace implements MeshListener, Serializable { ...@@ -142,6 +134,15 @@ public class HumanFace implements MeshListener, Serializable {
public MeshFacet getCuttingPlane() { public MeshFacet getCuttingPlane() {
return cuttingPlane; return cuttingPlane;
} }
/**
* Return unique ID of the face.
*
* @return unique ID of the face.
*/
public String getId() {
return this.id;
}
/** /**
* Creates serialized dump of the human face. Event buses are not stored. * Creates serialized dump of the human face. Event buses are not stored.
...@@ -172,4 +173,30 @@ public class HumanFace implements MeshListener, Serializable { ...@@ -172,4 +173,30 @@ public class HumanFace implements MeshListener, Serializable {
return (HumanFace) fos.readObject(); return (HumanFace) fos.readObject();
} }
} }
@Override
public int hashCode() {
int hash = 7;
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final HumanFace other = (HumanFace) obj;
if (!Objects.equals(this.id, other.id)) {
return false;
}
return true;
}
} }
package cz.fidentis.analyst.face;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Singleton flyweight factory that creates and caches human faces. Faces are
* stored in the memory until there is enough space in the Java heap. Then they
* are cached on disk automatically. The dumping strategy is directed by sub-classes
* of this abstract class.
* <p>
* Dumping faces on disk and their recovery is time consuming (we use serialization
* of all data structure, i.e., triangular mesh, k-d tree, etc.). It can be faster
* to re-load the face from OBJ file again and re-compute the data structures from scratch.
* </p>
* <p>
* Currently, listeners registered to {@code HumanFace} and {@code KdTree}
* are neither dumped nor reconstructed!
* </p>
*
* @author Radek Oslejsek
*/
public abstract class HumanFaceCache {
/**
* Keep at least this portion of the Java heap memory free
*/
public static double MIN_FREE_MEMORY = 0.05; // 5%
private final Map<String, HumanFace> inMemoryFaces = new HashMap<>();
private final Map<String, File> dumpedFaces = new HashMap<>();
/**
* Private constructor. Use {@link instance} instead to get and access the instance.
*/
protected HumanFaceCache() {
}
/**
* Loads new face. If the face is already loaded, then the existing instance
* is returned.
*
* @param file OBJ file with human face.
* @return Human face or {@code null}
*/
public HumanFace loadFace(File file) {
try {
String faceId = file.getCanonicalPath();
// In memory face:
HumanFace face = inMemoryFaces.get(faceId);
if (face != null) {
return face;
}
// Dumped face:
face = recoverFace(faceId);
if (face != null) {
return face;
}
// New face:
return storeNewFace(file);
} catch (IOException|ClassNotFoundException ex) {
return null;
}
}
/**
* Returns a human face. Recovers the face from a dump file if necessary.
*
* @param faceId ID of the face
* @return Human face or {@code null}
*/
public HumanFace getFace(String faceId) {
try {
// In memory face:
HumanFace face = inMemoryFaces.get(faceId);
if (face != null) {
return face;
}
// Dumped face:
face = recoverFace(faceId);
if (face != null) {
return face;
}
// New face:
return null;
} catch (IOException|ClassNotFoundException ex) {
return null;
}
}
/**
* Removed the face from the cache.
*
* @param faceId Face ID
*/
public void removeFace(String faceId) {
if (inMemoryFaces.remove(faceId) == null) {
dumpedFaces.remove(faceId);
}
}
@Override
public String toString() {
StringBuilder ret = new StringBuilder();
ret.append(formatSize(Runtime.getRuntime().freeMemory())).
append(" free memory out of ").
append(formatSize(Runtime.getRuntime().maxMemory()));
ret.append(". In memory: ").
append(this.inMemoryFaces.size()).
append(", dumped: ").
append(this.dumpedFaces.size());
return ret.toString();
}
/**
* Recovers the face from the dump file. Heap memory is released (some existing
* face is dumped) if necessary.
*
* @param faceId Face ID
* @return Recovered face or {@code null} if the face was not dumped.
* @throws IOException on I/O error
* @throws ClassNotFoundException on I/O error
*/
protected HumanFace recoverFace(String faceId) throws IOException, ClassNotFoundException {
File dumpFile = dumpedFaces.get(faceId);
if (dumpFile == null) {
return null;
}
freeMemory();
HumanFace face = HumanFace.restoreFromFile(dumpFile);
inMemoryFaces.put(faceId, face);
dumpedFaces.remove(faceId);
return face;
}
/**
* Instantiates new face. Heap memory is released (some existing face is dumped) if necessary.
*
* @param file OBJ file
* @return Face instance.
* @throws IOException on I/O error
*/
protected HumanFace storeNewFace(File file) throws IOException {
HumanFace face = new HumanFace(file);
freeMemory();
inMemoryFaces.put(face.getId(), face);
//inMemoryFaces.put(Long.toHexString(Double.doubleToLongBits(Math.random())), face);
return face;
}
/**
* Checks and releases the heap, if necessary.
*
* @return true if some existing face has been dumped to free the memory.
* @throws IOException on I/O error
*/
protected boolean freeMemory() throws IOException {
double ratio = (double) Runtime.getRuntime().freeMemory() / Runtime.getRuntime().maxMemory();
if (ratio > MIN_FREE_MEMORY) {
return false;
}
String faceId = selectFaceForDump();
HumanFace face = inMemoryFaces.remove(faceId);
dumpedFaces.put(faceId, face.dumpToFile());
return true;
}
/**
* Selects a face that will be dumped to file.
*
* @return ID of the face that will be dumped to file.
*/
protected abstract String selectFaceForDump();
protected static String formatSize(long v) {
if (v < 1024) return v + " B";
int z = (63 - Long.numberOfLeadingZeros(v)) / 10;
return String.format("%.1f %sB", (double)v / (1L << (z*10)), " KMGTPE".charAt(z));
}
protected Map<String, HumanFace> getInMemoryFaces() {
return this.inMemoryFaces;
}
protected Map<String, File> getDumpedFaces() {
return this.dumpedFaces;
}
}
package cz.fidentis.analyst.face;
import java.io.IOException;
/**
* This cache preserves the first X faces (privileged faces) always in the memory.
* Faces that are loaded when there is not enough free memory are always dumped
* to file and only one of them is stored in the memory when used.
*
* @author Radek Oslejsek
*/
public class HumanFacePrivilegedCache extends HumanFaceCache {
private static HumanFacePrivilegedCache factory;
/**
* The only one dumped face currently in the memory
*/
private String recoveredFace;
/**
* Private constructor. Use {@link instance} instead to get and access the instance.
*/
protected HumanFacePrivilegedCache() {
}
/**
* Returns the factory singleton instance.
*
* @return the factory singleton instance
*/
public static HumanFacePrivilegedCache instance() {
if (factory == null) {
factory = new HumanFacePrivilegedCache();
}
return factory;
}
@Override
protected String selectFaceForDump() {
if (this.recoveredFace != null) {
return this.recoveredFace;
} else {
// This should not happen. But return any face from the memory, anyway.
for (String faceId: getInMemoryFaces().keySet()) {
return faceId;
}
}
return null;
}
@Override
public void removeFace(String faceId) {
if (faceId.equals(this.recoveredFace)) {
this.recoveredFace = null;
}
super.removeFace(faceId);
}
@Override
protected HumanFace recoverFace(String faceId) throws IOException, ClassNotFoundException {
HumanFace face = super.recoverFace(faceId);
if (face != null) {
this.recoveredFace = face.getId();
}
return face;
}
}
package cz.fidentis.analyst.tests; package cz.fidentis.analyst.tests;
import cz.fidentis.analyst.face.HumanFace; import cz.fidentis.analyst.face.HumanFace;
import cz.fidentis.analyst.face.HumanFacePrivilegedCache;
import cz.fidentis.analyst.icp.Icp; import cz.fidentis.analyst.icp.Icp;
import cz.fidentis.analyst.kdtree.KdTree; import cz.fidentis.analyst.kdtree.KdTree;
import cz.fidentis.analyst.mesh.core.MeshFacet; import cz.fidentis.analyst.mesh.core.MeshFacet;
...@@ -44,14 +45,11 @@ public class EfficiencyTests { ...@@ -44,14 +45,11 @@ public class EfficiencyTests {
face1.getMeshModel().simplifyModel(); face1.getMeshModel().simplifyModel();
face2.getMeshModel().simplifyModel(); face2.getMeshModel().simplifyModel();
List<HumanFace> list = new ArrayList<>(); for (int i = 0; i < 100; i++) {
for (int i = 0; i < 500; i++) { HumanFace face = HumanFacePrivilegedCache.instance().loadFace(faceFile4);
System.out.print(i+". " + formatSize(Runtime.getRuntime().freeMemory()) + "/" + formatSize(Runtime.getRuntime().maxMemory()) + " ");
HumanFace face = printFaceLoad(faceFile4);
face.getMeshModel().simplifyModel(); face.getMeshModel().simplifyModel();
list.add(face); System.out.println(i+".\t" + HumanFacePrivilegedCache.instance());
} }
list.clear();
boolean relativeDist = false; boolean relativeDist = false;
boolean printDetails = false; boolean printDetails = false;
......
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