Loading LandmarksEngines/src/main/java/cz/fidentis/analyst/engines/landmarks/LandmarkServices.java +3 −3 Original line number Diff line number Diff line Loading @@ -66,12 +66,12 @@ public interface LandmarkServices { } /** * Detects and returns the tip of the nose from mesh model. * Return tip of the nose from mesh model. * * @param meshModel Mesh model * @return Tip of the nose {@link MeshPoint} * @return Nose tip */ static MeshPoint detectNoseTip(MeshModel meshModel) { return NoseTipDetectorImpl.detectNoseTipFromCurvature(meshModel); return NoseTipDetectorImpl.detectNoseTip(meshModel); } } LandmarksEngines/src/main/java/cz/fidentis/analyst/engines/landmarks/impl/NoseTipDetectorImpl.java +38 −62 Original line number Diff line number Diff line Loading @@ -2,119 +2,95 @@ package cz.fidentis.analyst.engines.landmarks.impl; import cz.fidentis.analyst.data.mesh.MeshModel; import cz.fidentis.analyst.data.mesh.MeshPoint; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; /** * Detects tip of the nose in mesh model based on curvature properties. * Detects tip of the nose from mesh model based on curvature properties. * * @author Marketa Schlemmerova */ public class NoseTipDetectorImpl { private static final double MAX_H_CURVATURE = 0; private static final double MIN_K_CURVATURE = 0.0001; private static final double MAX_H_CURVATURE = 0.08; private static final double MIN_K_CURVATURE = 0.008; /** * Detects nose tip from mesh model. Filters candidate points based on curvature properties * and selects point with the highest Gaussian curvature. * Detects nose tip from mesh model using curvature properties. Selects point with the highest Gaussian curvature. * * @param meshModel Mesh model * @throws IllegalArgumentException If mesh model is null * @return Tip of the nose {@link MeshPoint} */ public static MeshPoint detectNoseTipFromCurvature(MeshModel meshModel) { if (meshModel == null) { throw new IllegalArgumentException("mesh model cannot be null"); public static MeshPoint detectNoseTip(MeshModel meshModel) { validateMeshModel(meshModel); return detectNoseTipCandidatesFromCurvature(meshModel).getFirst(); } List<MeshPoint> candidatePoints = filterCandidatesFromCurvature(meshModel); return getMaxGaussianCurvaturePoint(candidatePoints); /** * Filters nose tip candidates sorted by Gaussian curvature. * * @param meshModel Mesh model * @throws IllegalArgumentException If mesh model is null * @return Tip of the nose candidates */ public static List<MeshPoint> detectNoseTipCandidatesFromCurvature(MeshModel meshModel) { validateMeshModel(meshModel); List<MeshPoint> candidatePoints = filterPointsByCurvature(meshModel, MAX_H_CURVATURE, MIN_K_CURVATURE); return sortByGaussianCurvatureDesc(candidatePoints); } /** * Filters nose tip candidates based on HK curvature classification, using Mean and Gaussian curvature. * * @param meshModel Mesh model * @throws IllegalArgumentException If mesh model is null * @return Candidates for nose tip */ private static List<MeshPoint> filterCandidatesFromCurvature(MeshModel meshModel) { if (meshModel == null) { throw new IllegalArgumentException("mesh model cannot be null"); } private static List<MeshPoint> filterPointsByCurvature(MeshModel meshModel, double maxMeanCurvature, double minGaussianCurvature) { validateMeshModel(meshModel); return meshModel.getFacets().parallelStream() .flatMap(facet -> facet.getVertices().stream()) .filter(meshPoint -> { var curvature = meshPoint.getCurvature(); return curvature != null && meshPoint.getCurvature().mean() < MAX_H_CURVATURE && meshPoint.getCurvature().gaussian() > MIN_K_CURVATURE; meshPoint.getCurvature().mean() < maxMeanCurvature && meshPoint.getCurvature().gaussian() > minGaussianCurvature; }) .collect(Collectors.toList()); } /** * Returns mesh point with the highest Gaussian curvature from a list. * If the list is null or empty, returns null. * Sorts a list of mesh points by Gaussian curvature in descending order. * * @param candidatePoints Candidates for nose tip * @return Point with the highest Gaussian curvature {@link MeshPoint} * @param candidatePoints List of points to sort * @return Sorted list of points (highest Gaussian curvature first) */ private static MeshPoint getMaxGaussianCurvaturePoint(List<MeshPoint> candidatePoints) { private static List<MeshPoint> sortByGaussianCurvatureDesc(List<MeshPoint> candidatePoints) { if (candidatePoints == null || candidatePoints.isEmpty()) { return null; return Collections.emptyList(); } return candidatePoints.stream() .max(Comparator.comparingDouble(point -> point.getCurvature().gaussian())) .orElse(null); } /** * Filters mesh points where Mean curvature < MAX_H_CURVATURE for test purposes. * * @param meshModel Mesh model * @throws IllegalArgumentException If mesh model is null * @return Mesh points with Mean curvature below the threshold */ public static List<MeshPoint> filterPointsByMeanCurvature(MeshModel meshModel) { if (meshModel == null) { throw new IllegalArgumentException("mesh model cannot be null"); } return meshModel.getFacets().parallelStream() .flatMap(facet -> facet.getVertices().stream()) .filter(meshPoint -> { var curvature = meshPoint.getCurvature(); return curvature != null && meshPoint.getCurvature().mean() < MAX_H_CURVATURE; }) .sorted(Comparator.comparingDouble(point -> -point.getCurvature().gaussian())) .collect(Collectors.toList()); } /** * Filters mesh points where Gaussian curvature > MIN_K_CURVATURE for test purposes. * Validates that the provided mesh model is not null. * * @param meshModel Mesh model * @throws IllegalArgumentException If mesh model is null * @return Mesh points with Gaussian curvature above the threshold * @param meshModel Mesh model to validate * @throws IllegalArgumentException If the mesh model is null */ public static List<MeshPoint> filterPointsByGaussianCurvature(MeshModel meshModel) { private static void validateMeshModel(MeshModel meshModel) { if (meshModel == null) { throw new IllegalArgumentException("mesh model cannot be null"); throw new IllegalArgumentException("Mesh model cannot be null"); } return meshModel.getFacets().parallelStream() .flatMap(facet -> facet.getVertices().stream()) .filter(meshPoint -> { var curvature = meshPoint.getCurvature(); return curvature != null && meshPoint.getCurvature().gaussian() > MIN_K_CURVATURE; }) .collect(Collectors.toList()); } } LandmarksEngines/src/test/java/cz/fidentis/analyst/engines/landmarks/impl/NoseTipDetectorImplTest.java +43 −41 Original line number Diff line number Diff line Loading @@ -11,15 +11,15 @@ 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.List; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test nose tip detection on Analyst Data Public. * Tests the functionality of the {@link NoseTipDetectorImpl} class on Analyst Data Public. * * @author Marketa Schlemmerova */ Loading @@ -28,93 +28,95 @@ public class NoseTipDetectorImplTest { private static final Path TEST_FILE_DIRECTORY = Paths.get("src", "test", "resources", "cz", "fidentis", "analyst", "engines", "landmarks"); private void detectNoseTip(String fileName) throws IOException { MeshPoint noseTip = getMeshModelFromObjFile(fileName); assertNotNull(noseTip, "nose tip not found"); System.out.printf("Nose Tip for %s: %.5f %.5f %.5f%n", fileName, noseTip.getX(), noseTip.getY(), noseTip.getZ()); private void noseCandidatesDetectionTest(String fileName) throws IOException { MeshModel meshModel = loadMeshModel(fileName); List<MeshPoint> noseCandidates = NoseTipDetectorImpl.detectNoseTipCandidatesFromCurvature(meshModel); assertNotNull(noseCandidates, "Candidate list is null."); assertFalse(noseCandidates.isEmpty(), "No nose tip candidates detected in: " + fileName); printNoseCandidatesDetails(noseCandidates); } private MeshPoint getMeshModelFromObjFile(String fileName) throws IOException { File scanFile = new File(TEST_FILE_DIRECTORY.toFile(), fileName); assertNotNull(scanFile); private MeshModel loadMeshModel(String fileName) throws IOException { File file = TEST_FILE_DIRECTORY.resolve(fileName).toFile(); assertTrue(file.exists(), "File not found: " + fileName); MeshModel meshModel = MeshObjLoader.readFromObj(scanFile); assertNotNull(meshModel); MeshModel meshModel = MeshObjLoader.readFromObj(file); assertNotNull(meshModel, "Failed to load mesh model from file: " + fileName); CurvatureServices.computeAndSet(meshModel, new CurvatureConfig()); assertTrue(meshModel.hasCurvature()); assertTrue(meshModel.hasCurvature(), "Curvature not computed for file: " + fileName); printCurvatureCandidates(meshModel); return NoseTipDetectorImpl.detectNoseTipFromCurvature(meshModel); return meshModel; } private void printCurvatureCandidates(MeshModel meshModel){ List<MeshPoint> meanCurvatureCandidates = new ArrayList<>(NoseTipDetectorImpl.filterPointsByMeanCurvature(meshModel)); System.out.println("Number of candidates with Mean curvature < 0: " + meanCurvatureCandidates.size()); List<MeshPoint> gaussianCurvatureCandidates = new ArrayList<>(NoseTipDetectorImpl.filterPointsByGaussianCurvature(meshModel)); System.out.println("Number of candidates with Gaussian curvature > 0.0001: " + gaussianCurvatureCandidates.size()); private void printNoseCandidatesDetails(List<MeshPoint> candidates) { System.out.printf("Nose tip candidates: %d\n\n", candidates.size()); for (MeshPoint candidate : candidates) { System.out.printf("%.5f, %.5f, %.5f\n", candidate.getX(), candidate.getY(), candidate.getZ()); } } @Test void nullMeshModelTest() { Exception ex = assertThrows(IllegalArgumentException.class, () -> NoseTipDetectorImpl.detectNoseTipFromCurvature(null)); assertTrue(ex.getMessage().contains("mesh model cannot be null")); () -> NoseTipDetectorImpl.detectNoseTipCandidatesFromCurvature(null)); assertTrue(ex.getMessage().contains("Mesh model cannot be null")); } @Test void nose_detection_scan_average_boy() throws IOException { noseCandidatesDetectionTest("average_boy_17-20.obj"); } @Test void nose_detection_scan_average_girl() throws IOException { noseCandidatesDetectionTest("average_girl_17-20.obj"); } @Test void nose_detection_01() throws IOException { detectNoseTip("01.obj"); noseCandidatesDetectionTest("01.obj"); } @Test void nose_detection_02() throws IOException { detectNoseTip("02.obj"); noseCandidatesDetectionTest("02.obj"); } @Test void nose_detection_03() throws IOException { detectNoseTip("03.obj"); noseCandidatesDetectionTest("03.obj"); } @Test void nose_detection_04() throws IOException { detectNoseTip("04.obj"); noseCandidatesDetectionTest("04.obj"); } @Test void nose_detection_scan02() throws IOException { detectNoseTip("00002_01_ECA.obj"); noseCandidatesDetectionTest("00002_01_ECA.obj"); } @Test void nose_detection_scan04() throws IOException { detectNoseTip("00004_01_ECA.obj"); noseCandidatesDetectionTest("00004_01_ECA.obj"); } @Test void nose_detection_scan06() throws IOException { detectNoseTip("00006_01_ECA.obj"); noseCandidatesDetectionTest("00006_01_ECA.obj"); } @Test void nose_detection_scan07() throws IOException { detectNoseTip("00007_01_ECA.obj"); noseCandidatesDetectionTest("00007_01_ECA.obj"); } @Test void nose_detection_scan10() throws IOException { detectNoseTip("00010_01_ECA.obj"); } @Test void nose_detection_scan_average_boy() throws IOException { detectNoseTip("average_boy_17-20.obj"); } @Test void nose_detection_scan_average_girl() throws IOException { detectNoseTip("average_girl_17-20.obj"); noseCandidatesDetectionTest("00010_01_ECA.obj"); } } Loading
LandmarksEngines/src/main/java/cz/fidentis/analyst/engines/landmarks/LandmarkServices.java +3 −3 Original line number Diff line number Diff line Loading @@ -66,12 +66,12 @@ public interface LandmarkServices { } /** * Detects and returns the tip of the nose from mesh model. * Return tip of the nose from mesh model. * * @param meshModel Mesh model * @return Tip of the nose {@link MeshPoint} * @return Nose tip */ static MeshPoint detectNoseTip(MeshModel meshModel) { return NoseTipDetectorImpl.detectNoseTipFromCurvature(meshModel); return NoseTipDetectorImpl.detectNoseTip(meshModel); } }
LandmarksEngines/src/main/java/cz/fidentis/analyst/engines/landmarks/impl/NoseTipDetectorImpl.java +38 −62 Original line number Diff line number Diff line Loading @@ -2,119 +2,95 @@ package cz.fidentis.analyst.engines.landmarks.impl; import cz.fidentis.analyst.data.mesh.MeshModel; import cz.fidentis.analyst.data.mesh.MeshPoint; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; /** * Detects tip of the nose in mesh model based on curvature properties. * Detects tip of the nose from mesh model based on curvature properties. * * @author Marketa Schlemmerova */ public class NoseTipDetectorImpl { private static final double MAX_H_CURVATURE = 0; private static final double MIN_K_CURVATURE = 0.0001; private static final double MAX_H_CURVATURE = 0.08; private static final double MIN_K_CURVATURE = 0.008; /** * Detects nose tip from mesh model. Filters candidate points based on curvature properties * and selects point with the highest Gaussian curvature. * Detects nose tip from mesh model using curvature properties. Selects point with the highest Gaussian curvature. * * @param meshModel Mesh model * @throws IllegalArgumentException If mesh model is null * @return Tip of the nose {@link MeshPoint} */ public static MeshPoint detectNoseTipFromCurvature(MeshModel meshModel) { if (meshModel == null) { throw new IllegalArgumentException("mesh model cannot be null"); public static MeshPoint detectNoseTip(MeshModel meshModel) { validateMeshModel(meshModel); return detectNoseTipCandidatesFromCurvature(meshModel).getFirst(); } List<MeshPoint> candidatePoints = filterCandidatesFromCurvature(meshModel); return getMaxGaussianCurvaturePoint(candidatePoints); /** * Filters nose tip candidates sorted by Gaussian curvature. * * @param meshModel Mesh model * @throws IllegalArgumentException If mesh model is null * @return Tip of the nose candidates */ public static List<MeshPoint> detectNoseTipCandidatesFromCurvature(MeshModel meshModel) { validateMeshModel(meshModel); List<MeshPoint> candidatePoints = filterPointsByCurvature(meshModel, MAX_H_CURVATURE, MIN_K_CURVATURE); return sortByGaussianCurvatureDesc(candidatePoints); } /** * Filters nose tip candidates based on HK curvature classification, using Mean and Gaussian curvature. * * @param meshModel Mesh model * @throws IllegalArgumentException If mesh model is null * @return Candidates for nose tip */ private static List<MeshPoint> filterCandidatesFromCurvature(MeshModel meshModel) { if (meshModel == null) { throw new IllegalArgumentException("mesh model cannot be null"); } private static List<MeshPoint> filterPointsByCurvature(MeshModel meshModel, double maxMeanCurvature, double minGaussianCurvature) { validateMeshModel(meshModel); return meshModel.getFacets().parallelStream() .flatMap(facet -> facet.getVertices().stream()) .filter(meshPoint -> { var curvature = meshPoint.getCurvature(); return curvature != null && meshPoint.getCurvature().mean() < MAX_H_CURVATURE && meshPoint.getCurvature().gaussian() > MIN_K_CURVATURE; meshPoint.getCurvature().mean() < maxMeanCurvature && meshPoint.getCurvature().gaussian() > minGaussianCurvature; }) .collect(Collectors.toList()); } /** * Returns mesh point with the highest Gaussian curvature from a list. * If the list is null or empty, returns null. * Sorts a list of mesh points by Gaussian curvature in descending order. * * @param candidatePoints Candidates for nose tip * @return Point with the highest Gaussian curvature {@link MeshPoint} * @param candidatePoints List of points to sort * @return Sorted list of points (highest Gaussian curvature first) */ private static MeshPoint getMaxGaussianCurvaturePoint(List<MeshPoint> candidatePoints) { private static List<MeshPoint> sortByGaussianCurvatureDesc(List<MeshPoint> candidatePoints) { if (candidatePoints == null || candidatePoints.isEmpty()) { return null; return Collections.emptyList(); } return candidatePoints.stream() .max(Comparator.comparingDouble(point -> point.getCurvature().gaussian())) .orElse(null); } /** * Filters mesh points where Mean curvature < MAX_H_CURVATURE for test purposes. * * @param meshModel Mesh model * @throws IllegalArgumentException If mesh model is null * @return Mesh points with Mean curvature below the threshold */ public static List<MeshPoint> filterPointsByMeanCurvature(MeshModel meshModel) { if (meshModel == null) { throw new IllegalArgumentException("mesh model cannot be null"); } return meshModel.getFacets().parallelStream() .flatMap(facet -> facet.getVertices().stream()) .filter(meshPoint -> { var curvature = meshPoint.getCurvature(); return curvature != null && meshPoint.getCurvature().mean() < MAX_H_CURVATURE; }) .sorted(Comparator.comparingDouble(point -> -point.getCurvature().gaussian())) .collect(Collectors.toList()); } /** * Filters mesh points where Gaussian curvature > MIN_K_CURVATURE for test purposes. * Validates that the provided mesh model is not null. * * @param meshModel Mesh model * @throws IllegalArgumentException If mesh model is null * @return Mesh points with Gaussian curvature above the threshold * @param meshModel Mesh model to validate * @throws IllegalArgumentException If the mesh model is null */ public static List<MeshPoint> filterPointsByGaussianCurvature(MeshModel meshModel) { private static void validateMeshModel(MeshModel meshModel) { if (meshModel == null) { throw new IllegalArgumentException("mesh model cannot be null"); throw new IllegalArgumentException("Mesh model cannot be null"); } return meshModel.getFacets().parallelStream() .flatMap(facet -> facet.getVertices().stream()) .filter(meshPoint -> { var curvature = meshPoint.getCurvature(); return curvature != null && meshPoint.getCurvature().gaussian() > MIN_K_CURVATURE; }) .collect(Collectors.toList()); } }
LandmarksEngines/src/test/java/cz/fidentis/analyst/engines/landmarks/impl/NoseTipDetectorImplTest.java +43 −41 Original line number Diff line number Diff line Loading @@ -11,15 +11,15 @@ 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.List; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test nose tip detection on Analyst Data Public. * Tests the functionality of the {@link NoseTipDetectorImpl} class on Analyst Data Public. * * @author Marketa Schlemmerova */ Loading @@ -28,93 +28,95 @@ public class NoseTipDetectorImplTest { private static final Path TEST_FILE_DIRECTORY = Paths.get("src", "test", "resources", "cz", "fidentis", "analyst", "engines", "landmarks"); private void detectNoseTip(String fileName) throws IOException { MeshPoint noseTip = getMeshModelFromObjFile(fileName); assertNotNull(noseTip, "nose tip not found"); System.out.printf("Nose Tip for %s: %.5f %.5f %.5f%n", fileName, noseTip.getX(), noseTip.getY(), noseTip.getZ()); private void noseCandidatesDetectionTest(String fileName) throws IOException { MeshModel meshModel = loadMeshModel(fileName); List<MeshPoint> noseCandidates = NoseTipDetectorImpl.detectNoseTipCandidatesFromCurvature(meshModel); assertNotNull(noseCandidates, "Candidate list is null."); assertFalse(noseCandidates.isEmpty(), "No nose tip candidates detected in: " + fileName); printNoseCandidatesDetails(noseCandidates); } private MeshPoint getMeshModelFromObjFile(String fileName) throws IOException { File scanFile = new File(TEST_FILE_DIRECTORY.toFile(), fileName); assertNotNull(scanFile); private MeshModel loadMeshModel(String fileName) throws IOException { File file = TEST_FILE_DIRECTORY.resolve(fileName).toFile(); assertTrue(file.exists(), "File not found: " + fileName); MeshModel meshModel = MeshObjLoader.readFromObj(scanFile); assertNotNull(meshModel); MeshModel meshModel = MeshObjLoader.readFromObj(file); assertNotNull(meshModel, "Failed to load mesh model from file: " + fileName); CurvatureServices.computeAndSet(meshModel, new CurvatureConfig()); assertTrue(meshModel.hasCurvature()); assertTrue(meshModel.hasCurvature(), "Curvature not computed for file: " + fileName); printCurvatureCandidates(meshModel); return NoseTipDetectorImpl.detectNoseTipFromCurvature(meshModel); return meshModel; } private void printCurvatureCandidates(MeshModel meshModel){ List<MeshPoint> meanCurvatureCandidates = new ArrayList<>(NoseTipDetectorImpl.filterPointsByMeanCurvature(meshModel)); System.out.println("Number of candidates with Mean curvature < 0: " + meanCurvatureCandidates.size()); List<MeshPoint> gaussianCurvatureCandidates = new ArrayList<>(NoseTipDetectorImpl.filterPointsByGaussianCurvature(meshModel)); System.out.println("Number of candidates with Gaussian curvature > 0.0001: " + gaussianCurvatureCandidates.size()); private void printNoseCandidatesDetails(List<MeshPoint> candidates) { System.out.printf("Nose tip candidates: %d\n\n", candidates.size()); for (MeshPoint candidate : candidates) { System.out.printf("%.5f, %.5f, %.5f\n", candidate.getX(), candidate.getY(), candidate.getZ()); } } @Test void nullMeshModelTest() { Exception ex = assertThrows(IllegalArgumentException.class, () -> NoseTipDetectorImpl.detectNoseTipFromCurvature(null)); assertTrue(ex.getMessage().contains("mesh model cannot be null")); () -> NoseTipDetectorImpl.detectNoseTipCandidatesFromCurvature(null)); assertTrue(ex.getMessage().contains("Mesh model cannot be null")); } @Test void nose_detection_scan_average_boy() throws IOException { noseCandidatesDetectionTest("average_boy_17-20.obj"); } @Test void nose_detection_scan_average_girl() throws IOException { noseCandidatesDetectionTest("average_girl_17-20.obj"); } @Test void nose_detection_01() throws IOException { detectNoseTip("01.obj"); noseCandidatesDetectionTest("01.obj"); } @Test void nose_detection_02() throws IOException { detectNoseTip("02.obj"); noseCandidatesDetectionTest("02.obj"); } @Test void nose_detection_03() throws IOException { detectNoseTip("03.obj"); noseCandidatesDetectionTest("03.obj"); } @Test void nose_detection_04() throws IOException { detectNoseTip("04.obj"); noseCandidatesDetectionTest("04.obj"); } @Test void nose_detection_scan02() throws IOException { detectNoseTip("00002_01_ECA.obj"); noseCandidatesDetectionTest("00002_01_ECA.obj"); } @Test void nose_detection_scan04() throws IOException { detectNoseTip("00004_01_ECA.obj"); noseCandidatesDetectionTest("00004_01_ECA.obj"); } @Test void nose_detection_scan06() throws IOException { detectNoseTip("00006_01_ECA.obj"); noseCandidatesDetectionTest("00006_01_ECA.obj"); } @Test void nose_detection_scan07() throws IOException { detectNoseTip("00007_01_ECA.obj"); noseCandidatesDetectionTest("00007_01_ECA.obj"); } @Test void nose_detection_scan10() throws IOException { detectNoseTip("00010_01_ECA.obj"); } @Test void nose_detection_scan_average_boy() throws IOException { detectNoseTip("average_boy_17-20.obj"); } @Test void nose_detection_scan_average_girl() throws IOException { detectNoseTip("average_girl_17-20.obj"); noseCandidatesDetectionTest("00010_01_ECA.obj"); } }