From ff5495ae6099e41dcbde38f4ba2bc8fe3d1a3a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20O=C5=A1lej=C5=A1ek?= <oslejsek@fi.muni.cz> Date: Tue, 18 Jan 2022 13:05:18 +0100 Subject: [PATCH] Resolve "Fix landmarks initialization from file" --- .../cz/fidentis/analyst/face/HumanFace.java | 66 +++++++++++++++---- .../cz/fidentis/analyst/batch/IcpTask.java | 1 - .../fidentis/analyst/core/ProjectTopComp.java | 14 +--- .../services/FeaturePointCsvLoader.java | 40 +++++++---- 4 files changed, 84 insertions(+), 37 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 318bec54..d4489739 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java @@ -2,6 +2,7 @@ package cz.fidentis.analyst.face; import cz.fidentis.analyst.face.events.HumanFaceListener; import com.google.common.eventbus.EventBus; +import cz.fidentis.analyst.Logger; import cz.fidentis.analyst.face.events.HumanFaceEvent; import cz.fidentis.analyst.feature.FeaturePoint; import cz.fidentis.analyst.feature.services.FeaturePointImportService; @@ -75,9 +76,12 @@ public class HumanFace implements Serializable { * Use {@link restoreFromFile} to restore the human face from a dump file. * * @param file OBJ file + * @param loadLandmarks If {@code true}, then the constructor aims to load + * landmarks along with the mesh. Use {@link #getFeaturePoints()} to check + * whether the landmarks (feature points) has been loaded. * @throws IOException on I/O failure */ - public HumanFace(File file) throws IOException { + public HumanFace(File file, boolean loadLandmarks) throws IOException { meshModel = MeshObjLoader.read(new FileInputStream(file)); meshModel.simplifyModel(); this.id = file.getCanonicalPath(); @@ -87,6 +91,29 @@ public class HumanFace implements Serializable { bbox = visitor.getBoundingBox(); eventBus = new EventBus(); + + if (loadLandmarks) { + File landFile = this.findLandmarks(); + if (landFile != null) { + try { + loadFeaturePoints(landFile.getAbsoluteFile().getParent(), landFile.getName()); + } catch(IOException ex) { + Logger.print(ex.toString()); + } + } + } + } + + /** + * Reads a 3D human face from the given OBJ file. + * Also loads landmarks (feature points) if appropriate file is found. + * Use {@link restoreFromFile} to restore the human face from a dump file. + * + * @param file OBJ file + * @throws IOException on I/O failure + */ + public HumanFace(File file) throws IOException { + this(file, true); } /** @@ -243,21 +270,11 @@ public class HumanFace implements Serializable { } /** - * Returns short name of the face distilled from the file name. May not be unique. - * @return short name of the face distilled from the file name - */ - public String getName() { - String name = id.substring(0, id.lastIndexOf('.')); // remove extention - name = name.substring(id.lastIndexOf('/')+1, name.length()); - return name; - } - - /** - * Returns short name of the face without its path in the name. + * Returns short name of the face without its path in the name. May not be unique. * @return short name of the face without its path in the name */ public String getShortName() { - String name = this.getName(); + String name = id.substring(0, id.lastIndexOf('.')); // remove extention name = name.substring(name.lastIndexOf(File.separatorChar) + 1, name.length()); return name; } @@ -308,6 +325,29 @@ public class HumanFace implements Serializable { return ret; } + /** + * Tries to find a file with landmarks definition based on the name of the face's OBJ file. + * @return The file with landmarks or {@code null} + */ + public final File findLandmarks() { + String filename = getId().split(".obj")[0] + "_landmarks.csv"; + if ((new File(filename)).exists()) { + return new File(filename); + } + + filename = getId().split("_ECA")[0] + "_landmarks.csv"; + if ((new File(filename)).exists()) { + return new File(filename); + } + + filename = getId().split("_CA")[0] + "_landmarks.csv"; + if ((new File(filename)).exists()) { + return new File(filename); + } + + return null; + } + /** * Creates serialized dump of the human face. Event buses are not stored. * Therefore, listeners have to re-register again after recovery. 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 3d9d3f50..093e56ac 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/batch/IcpTask.java +++ b/GUI/src/main/java/cz/fidentis/analyst/batch/IcpTask.java @@ -9,7 +9,6 @@ import cz.fidentis.analyst.face.HumanFaceFactory; import cz.fidentis.analyst.icp.IcpTransformer; import cz.fidentis.analyst.icp.NoUndersampling; import cz.fidentis.analyst.icp.RandomStrategy; -import cz.fidentis.analyst.icp.UndersamplingStrategy; import cz.fidentis.analyst.mesh.core.MeshModel; import cz.fidentis.analyst.scene.DrawableFace; import cz.fidentis.analyst.visitors.kdtree.AvgFaceConstructor; diff --git a/GUI/src/main/java/cz/fidentis/analyst/core/ProjectTopComp.java b/GUI/src/main/java/cz/fidentis/analyst/core/ProjectTopComp.java index da9809f5..d60fc747 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/core/ProjectTopComp.java +++ b/GUI/src/main/java/cz/fidentis/analyst/core/ProjectTopComp.java @@ -15,8 +15,6 @@ 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; import java.util.Collections; import java.util.HashMap; @@ -555,18 +553,10 @@ public final class ProjectTopComp extends TopComponent { for (File file : files) { HumanFace face = null; try { - face = new HumanFace(file); + face = new HumanFace(file, true); out.printDuration("Loaded model " + face.getShortName() +" with " + face.getMeshModel().getNumVertices() + " vertices"); - // simple hack: - Path path = Paths.get(file.getAbsolutePath()); - Path folder = path.getParent(); - Path filename = path.getFileName(); - String filestr = filename.toString(); - filestr = filestr.split("_ECA.obj")[0]; - filestr = filestr + "_landmarks.csv"; - face.loadFeaturePoints(folder.toString(), filestr); } catch (IOException ex) { - ex.printStackTrace(); + Logger.print(ex.toString()); } String name = face.getShortName(); diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/feature/services/FeaturePointCsvLoader.java b/MeshModel/src/main/java/cz/fidentis/analyst/feature/services/FeaturePointCsvLoader.java index 32633708..0e9a7a25 100644 --- a/MeshModel/src/main/java/cz/fidentis/analyst/feature/services/FeaturePointCsvLoader.java +++ b/MeshModel/src/main/java/cz/fidentis/analyst/feature/services/FeaturePointCsvLoader.java @@ -14,13 +14,14 @@ import java.util.List; import java.util.stream.Stream; /** - * Class used to import feature points from file of csv format + * Class used to import feature points from file of CSV format * * @author Jakub Kolman */ public class FeaturePointCsvLoader { - private static final String COLUMN_DELIMETER = ","; + private static final String PRIMARY_COLUMN_DELIMETER = ";"; + private static final String SECONDARY_COLUMN_DELIMETER = ","; private static final String CODE_PREFIX_DELIMETER = " "; /** @@ -36,28 +37,45 @@ public class FeaturePointCsvLoader { try (InputStreamReader streamReader = new InputStreamReader(app.getFileAsStream(path, fileName), StandardCharsets.UTF_8); BufferedReader reader = new BufferedReader(streamReader)) { + + String headerLine = reader.readLine(); + final String delimiter; + if (headerLine.contains(PRIMARY_COLUMN_DELIMETER)) { + delimiter = PRIMARY_COLUMN_DELIMETER; + } else if (headerLine.contains(SECONDARY_COLUMN_DELIMETER)) { + delimiter = SECONDARY_COLUMN_DELIMETER; + } else { + throw new IOException(String.format("Feature point import file '%s' has wrong format - unknown delimiter", fileName)); + } Stream<String> lines = reader.lines(); List<List<String>> linesList = new ArrayList<>(); - - lines - .forEach(line -> { - linesList.add(Arrays.asList(line.split(COLUMN_DELIMETER))); - }); - - if (linesList.size() != 2 - || linesList.get(0).size() != linesList.get(1).size()) { + + linesList.add(Arrays.asList(headerLine.split(delimiter, -1))); + lines.forEach(line -> { + linesList.add(Arrays.asList(line.split(delimiter, -1))); + }); + + if (linesList.stream().anyMatch(list -> list.size() != linesList.get(0).size())) { throw new IOException(String.format("Feature point import file '%s' has wrong format", fileName)); } + // TODO: In real data sets, there can be multiple FP collections (lines) in a single file, + // e.g., FPs before and after the face warping. Currently, we ignore them. List<FeaturePoint> points = new ArrayList<>(); for (int i = 1; i < linesList.get(0).size(); i += 3) { + if (linesList.get(1).get(i).isBlank() + || linesList.get(1).get(i+1).isBlank() + || linesList.get(1).get(i+2).isBlank()) { // skip missing points + continue; + } FeaturePoint point = new FeaturePoint( Double.parseDouble(linesList.get(1).get(i)), Double.parseDouble(linesList.get(1).get(i + 1)), Double.parseDouble(linesList.get(1).get(i + 2)), FeaturePointTypeProvider.getInstance().getFeaturePointTypeByCode( - getCode(linesList.get(0).get(i))) + getCode(linesList.get(0).get(i)) + ) ); points.add(point); } -- GitLab