From 7de3cde14d2db871d40964229c2eccef8f1fbf97 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Fri, 30 May 2025 15:34:28 +0300 Subject: [PATCH 1/2] Shuffle files to the packages that correspond to library package/groupId --- .../{simface => biometrics}/TestContextExt.kt | 2 +- .../simface/detection}/FaceAlignTest.kt | 20 +++--- .../detection}/FaceDetectionProcessorTest.kt | 10 +-- .../simface/embedding}/CustomModelTest.kt | 21 +++--- .../embedding}/EmbeddingProcessorTest.kt | 31 ++++---- .../simface/embedding}/ExpectedEmbedding.kt | 2 +- .../simface/matcher/IdentificationTest.kt | 44 ++++++++++++ .../simface/matcher/VerificationTest.kt | 59 +++++++++++++++ .../simprints/simface/IdentificationTest.kt | 56 --------------- .../com/simprints/simface/VerificationTest.kt | 71 ------------------- .../simprints/biometrics/simface/Constants.kt | 7 ++ .../core => biometrics/simface}/SimFace.kt | 26 +++---- .../simface}/SimFaceConfig.kt | 2 +- .../core => biometrics/simface}/Utils.kt | 24 +------ .../simface/data/FaceDetection.kt | 6 +- .../simface/data/FacialLandmarks.kt | 2 +- .../{ => biometrics}/simface/data/Point2D.kt | 2 +- .../simface/detection}/FaceAlignment.kt | 6 +- .../detection}/FaceDetectionProcessor.kt | 4 +- .../detection}/MlKitFaceDetectionProcessor.kt | 10 +-- .../simface/embedding/EmbeddingProcessor.kt | 2 +- .../simface/embedding/MLModelManager.kt | 8 +-- .../simface/embedding/ReshapeOp.kt | 2 +- .../embedding/TensorFlowEmbeddingProcessor.kt | 13 ++-- .../matcher/CosineDistanceMatchProcessor.kt | 4 +- .../simface/matcher/MatchProcessor.kt | 2 +- 26 files changed, 203 insertions(+), 233 deletions(-) rename src/androidTest/java/com/simprints/{simface => biometrics}/TestContextExt.kt (97%) rename src/androidTest/java/com/simprints/{simface => biometrics/simface/detection}/FaceAlignTest.kt (79%) rename src/androidTest/java/com/simprints/{simface => biometrics/simface/detection}/FaceDetectionProcessorTest.kt (93%) rename src/androidTest/java/com/simprints/{simface => biometrics/simface/embedding}/CustomModelTest.kt (72%) rename src/androidTest/java/com/simprints/{simface => biometrics/simface/embedding}/EmbeddingProcessorTest.kt (61%) rename src/androidTest/java/com/simprints/{simface => biometrics/simface/embedding}/ExpectedEmbedding.kt (99%) create mode 100644 src/androidTest/java/com/simprints/biometrics/simface/matcher/IdentificationTest.kt create mode 100644 src/androidTest/java/com/simprints/biometrics/simface/matcher/VerificationTest.kt delete mode 100644 src/androidTest/java/com/simprints/simface/IdentificationTest.kt delete mode 100644 src/androidTest/java/com/simprints/simface/VerificationTest.kt create mode 100644 src/main/java/com/simprints/biometrics/simface/Constants.kt rename src/main/java/com/simprints/{simface/core => biometrics/simface}/SimFace.kt (84%) rename src/main/java/com/simprints/{simface/core => biometrics/simface}/SimFaceConfig.kt (91%) rename src/main/java/com/simprints/{simface/core => biometrics/simface}/Utils.kt (74%) rename src/main/java/com/simprints/{ => biometrics}/simface/data/FaceDetection.kt (86%) rename src/main/java/com/simprints/{ => biometrics}/simface/data/FacialLandmarks.kt (78%) rename src/main/java/com/simprints/{ => biometrics}/simface/data/Point2D.kt (56%) rename src/main/java/com/simprints/{simface/quality => biometrics/simface/detection}/FaceAlignment.kt (98%) rename src/main/java/com/simprints/{simface/quality => biometrics/simface/detection}/FaceDetectionProcessor.kt (75%) rename src/main/java/com/simprints/{simface/quality => biometrics/simface/detection}/MlKitFaceDetectionProcessor.kt (95%) rename src/main/java/com/simprints/{ => biometrics}/simface/embedding/EmbeddingProcessor.kt (70%) rename src/main/java/com/simprints/{ => biometrics}/simface/embedding/MLModelManager.kt (88%) rename src/main/java/com/simprints/{ => biometrics}/simface/embedding/ReshapeOp.kt (95%) rename src/main/java/com/simprints/{ => biometrics}/simface/embedding/TensorFlowEmbeddingProcessor.kt (82%) rename src/main/java/com/simprints/{ => biometrics}/simface/matcher/CosineDistanceMatchProcessor.kt (93%) rename src/main/java/com/simprints/{ => biometrics}/simface/matcher/MatchProcessor.kt (84%) diff --git a/src/androidTest/java/com/simprints/simface/TestContextExt.kt b/src/androidTest/java/com/simprints/biometrics/TestContextExt.kt similarity index 97% rename from src/androidTest/java/com/simprints/simface/TestContextExt.kt rename to src/androidTest/java/com/simprints/biometrics/TestContextExt.kt index 9bd1732..ef3b883 100644 --- a/src/androidTest/java/com/simprints/simface/TestContextExt.kt +++ b/src/androidTest/java/com/simprints/biometrics/TestContextExt.kt @@ -1,4 +1,4 @@ -package com.simprints.simface +package com.simprints.biometrics import android.content.Context import android.graphics.Bitmap diff --git a/src/androidTest/java/com/simprints/simface/FaceAlignTest.kt b/src/androidTest/java/com/simprints/biometrics/simface/detection/FaceAlignTest.kt similarity index 79% rename from src/androidTest/java/com/simprints/simface/FaceAlignTest.kt rename to src/androidTest/java/com/simprints/biometrics/simface/detection/FaceAlignTest.kt index ce9eef1..a2aa060 100644 --- a/src/androidTest/java/com/simprints/simface/FaceAlignTest.kt +++ b/src/androidTest/java/com/simprints/biometrics/simface/detection/FaceAlignTest.kt @@ -1,22 +1,24 @@ -package com.simprints.simface +package com.simprints.biometrics.simface.detection import android.content.Context import android.graphics.Bitmap import android.graphics.Rect import androidx.test.core.app.* -import com.simprints.simface.core.SimFace -import com.simprints.simface.core.SimFaceConfig -import com.simprints.simface.core.Utils.IMAGE_SIZE -import com.simprints.simface.data.FaceDetection -import com.simprints.simface.quality.cropAlignFace -import com.simprints.simface.quality.warpAlignFace +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.simprints.biometrics.loadBitmapFromTestResources +import com.simprints.biometrics.simface.Constants +import com.simprints.biometrics.simface.SimFace +import com.simprints.biometrics.simface.SimFaceConfig +import com.simprints.biometrics.simface.data.FaceDetection import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4::class) class FaceAlignTest { private lateinit var context: Context private lateinit var simFace: SimFace @@ -78,10 +80,10 @@ class FaceAlignTest { assertTrue(warpedAlignedImage != null) if (warpedAlignedImage != null) { - assertTrue(warpedAlignedImage.width == IMAGE_SIZE) + assertTrue(warpedAlignedImage.width == Constants.IMAGE_SIZE) } if (warpedAlignedImage != null) { - assertTrue(warpedAlignedImage.height == IMAGE_SIZE) + assertTrue(warpedAlignedImage.height == Constants.IMAGE_SIZE) } } } diff --git a/src/androidTest/java/com/simprints/simface/FaceDetectionProcessorTest.kt b/src/androidTest/java/com/simprints/biometrics/simface/detection/FaceDetectionProcessorTest.kt similarity index 93% rename from src/androidTest/java/com/simprints/simface/FaceDetectionProcessorTest.kt rename to src/androidTest/java/com/simprints/biometrics/simface/detection/FaceDetectionProcessorTest.kt index 7b99858..da0f188 100644 --- a/src/androidTest/java/com/simprints/simface/FaceDetectionProcessorTest.kt +++ b/src/androidTest/java/com/simprints/biometrics/simface/detection/FaceDetectionProcessorTest.kt @@ -1,15 +1,17 @@ -package com.simprints.simface +package com.simprints.biometrics.simface.detection import android.content.Context import android.graphics.Bitmap import androidx.test.core.app.* import androidx.test.ext.junit.runners.* -import com.simprints.simface.core.SimFace -import com.simprints.simface.core.SimFaceConfig -import com.simprints.simface.data.FaceDetection +import com.simprints.biometrics.loadBitmapFromTestResources +import com.simprints.biometrics.simface.SimFace +import com.simprints.biometrics.simface.SimFaceConfig +import com.simprints.biometrics.simface.data.FaceDetection import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.test.runTest import org.junit.After +import org.junit.Assert import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test diff --git a/src/androidTest/java/com/simprints/simface/CustomModelTest.kt b/src/androidTest/java/com/simprints/biometrics/simface/embedding/CustomModelTest.kt similarity index 72% rename from src/androidTest/java/com/simprints/simface/CustomModelTest.kt rename to src/androidTest/java/com/simprints/biometrics/simface/embedding/CustomModelTest.kt index 714b5b1..e4cf692 100644 --- a/src/androidTest/java/com/simprints/simface/CustomModelTest.kt +++ b/src/androidTest/java/com/simprints/biometrics/simface/embedding/CustomModelTest.kt @@ -1,11 +1,12 @@ -package com.simprints.simface +package com.simprints.biometrics.simface.embedding import android.content.Context import android.graphics.Bitmap import androidx.test.core.app.* -import com.simprints.simface.core.SimFace -import com.simprints.simface.core.SimFaceConfig -import com.simprints.simface.core.Utils +import com.simprints.biometrics.loadBitmapFromTestResources +import com.simprints.biometrics.openTestModelFile +import com.simprints.biometrics.simface.SimFaceConfig +import com.simprints.biometrics.simface.Utils import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Assert.assertArrayEquals @@ -20,25 +21,25 @@ import org.junit.Test * 4. Do the testing */ class CustomModelTest { - private lateinit var simFace: SimFace private lateinit var context: Context + private lateinit var modelManager: MLModelManager + private lateinit var embeddingProcessor: EmbeddingProcessor @Before fun setup() { context = ApplicationProvider.getApplicationContext() - simFace = SimFace() } @After fun cleanup() { - simFace.release() + modelManager.close() } @Test fun test_processes_face_with_custom_model() = runTest { val testModelFile = context.openTestModelFile() - simFace.initialize( + modelManager = MLModelManager( SimFaceConfig( context, customModel = SimFaceConfig.CustomModel( @@ -47,13 +48,15 @@ class CustomModelTest { ), ), ) + embeddingProcessor = TensorFlowEmbeddingProcessor(modelManager) + val bitmap: Bitmap = context.loadBitmapFromTestResources("royalty_free_good_face") val resultFloat = getFaceEmbeddingFromBitmap(bitmap) assertArrayEquals(GOOD_FACE_EMBEDDING, resultFloat, 0.1F) } - private fun getFaceEmbeddingFromBitmap(bitmap: Bitmap): FloatArray = simFace + private fun getFaceEmbeddingFromBitmap(bitmap: Bitmap): FloatArray = embeddingProcessor .getEmbedding(bitmap) .let { Utils.byteArrayToFloatArray(it) } } diff --git a/src/androidTest/java/com/simprints/simface/EmbeddingProcessorTest.kt b/src/androidTest/java/com/simprints/biometrics/simface/embedding/EmbeddingProcessorTest.kt similarity index 61% rename from src/androidTest/java/com/simprints/simface/EmbeddingProcessorTest.kt rename to src/androidTest/java/com/simprints/biometrics/simface/embedding/EmbeddingProcessorTest.kt index 452feb3..3e31502 100644 --- a/src/androidTest/java/com/simprints/simface/EmbeddingProcessorTest.kt +++ b/src/androidTest/java/com/simprints/biometrics/simface/embedding/EmbeddingProcessorTest.kt @@ -1,12 +1,12 @@ -package com.simprints.simface +package com.simprints.biometrics.simface.embedding import android.content.Context import android.graphics.Bitmap -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.simprints.simface.core.SimFace -import com.simprints.simface.core.SimFaceConfig -import com.simprints.simface.core.Utils +import androidx.test.core.app.* +import androidx.test.ext.junit.runners.* +import com.simprints.biometrics.loadBitmapFromTestResources +import com.simprints.biometrics.simface.SimFaceConfig +import com.simprints.biometrics.simface.Utils import org.junit.After import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertTrue @@ -16,25 +16,26 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class EmbeddingProcessorTest { - private lateinit var simFace: SimFace private lateinit var context: Context + private lateinit var modelManager: MLModelManager + private lateinit var embeddingProcessor: EmbeddingProcessor @Before fun setup() { context = ApplicationProvider.getApplicationContext() - simFace = SimFace() - simFace.initialize(SimFaceConfig(context)) + modelManager = MLModelManager(SimFaceConfig(context)) + embeddingProcessor = TensorFlowEmbeddingProcessor(modelManager) } @After fun cleanup() { - simFace.release() + modelManager.close() } @Test fun get_embedding_with_image() { val bitmap: Bitmap = context.loadBitmapFromTestResources("royalty_free_good_face") - val result = simFace.getEmbedding(bitmap) + val result = embeddingProcessor.getEmbedding(bitmap) val resultFloat = Utils.byteArrayToFloatArray(result) assertTrue(Utils.byteArrayToFloatArray(result).size == 512) @@ -48,8 +49,8 @@ class EmbeddingProcessorTest { val bitmap1: Bitmap = context.loadBitmapFromTestResources("royalty_free_good_face") val bitmap2: Bitmap = context.loadBitmapFromTestResources("royalty_free_bad_face") - val embedding1 = simFace.getEmbedding(bitmap1) - val embedding2 = simFace.getEmbedding(bitmap2) + val embedding1 = embeddingProcessor.getEmbedding(bitmap1) + val embedding2 = embeddingProcessor.getEmbedding(bitmap2) assertTrue(!embedding1.contentEquals(embedding2)) // Embeddings should be different } @@ -58,8 +59,8 @@ class EmbeddingProcessorTest { fun consistency_test_with_same_image() { val bitmap: Bitmap = context.loadBitmapFromTestResources("royalty_free_good_face") - val embedding1 = simFace.getEmbedding(bitmap) - val embedding2 = simFace.getEmbedding(bitmap) + val embedding1 = embeddingProcessor.getEmbedding(bitmap) + val embedding2 = embeddingProcessor.getEmbedding(bitmap) assertArrayEquals(embedding1, embedding2) // Embeddings should be identical } diff --git a/src/androidTest/java/com/simprints/simface/ExpectedEmbedding.kt b/src/androidTest/java/com/simprints/biometrics/simface/embedding/ExpectedEmbedding.kt similarity index 99% rename from src/androidTest/java/com/simprints/simface/ExpectedEmbedding.kt rename to src/androidTest/java/com/simprints/biometrics/simface/embedding/ExpectedEmbedding.kt index 228adfa..b14a130 100644 --- a/src/androidTest/java/com/simprints/simface/ExpectedEmbedding.kt +++ b/src/androidTest/java/com/simprints/biometrics/simface/embedding/ExpectedEmbedding.kt @@ -1,4 +1,4 @@ -package com.simprints.simface +package com.simprints.biometrics.simface.embedding // Define the expected output for our image (the output is computed on Android) val GOOD_FACE_EMBEDDING = floatArrayOf( diff --git a/src/androidTest/java/com/simprints/biometrics/simface/matcher/IdentificationTest.kt b/src/androidTest/java/com/simprints/biometrics/simface/matcher/IdentificationTest.kt new file mode 100644 index 0000000..cd2b1c4 --- /dev/null +++ b/src/androidTest/java/com/simprints/biometrics/simface/matcher/IdentificationTest.kt @@ -0,0 +1,44 @@ +package com.simprints.biometrics.simface.matcher + +import androidx.test.ext.junit.runners.* +import com.simprints.biometrics.simface.Utils.floatArrayToByteArray +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class IdentificationTest { + private lateinit var matchProcessor: MatchProcessor + + @Before + fun setup() { + matchProcessor = CosineDistanceMatchProcessor() + } + + @Test + fun score_map_should_be_ordered_by_distance() { + val referenceArray = floatArrayToByteArray(floatArrayOf(1.0f, 0.0f)) + val arrayList = listOf( + floatArrayToByteArray(floatArrayOf(-1.0f, 0.0f)), // opposite to referenceArray + floatArrayToByteArray(floatArrayOf(1.0f, 0.0f)), // identical to referenceArray + floatArrayToByteArray(floatArrayOf(0.0f, 1.0f)), // orthogonal to referenceArray + floatArrayToByteArray(floatArrayOf(0.707f, 0.707f)), // 45 degrees to referenceArray + ) + + val sortedScores = matchProcessor.identificationScore(referenceArray, arrayList) + val sortedDistances = sortedScores.map { it.second } + + // Closest match (identical vector) should have a score of 1 + assertEquals(1.0, sortedDistances[0], 0.0001) + + // 45-degree vector (second closest) should have a score of around 0.85355 + assertEquals(0.85355, sortedDistances[1], 0.0001) + + // Orthogonal vector (further away) should have a score of 0.5 + assertEquals(0.5, sortedDistances[2], 0.0001) + + // Opposite vector (furthest away) should have a score of 0.0 + assertEquals(0.0, sortedDistances[3], 0.0001) + } +} diff --git a/src/androidTest/java/com/simprints/biometrics/simface/matcher/VerificationTest.kt b/src/androidTest/java/com/simprints/biometrics/simface/matcher/VerificationTest.kt new file mode 100644 index 0000000..12adf5a --- /dev/null +++ b/src/androidTest/java/com/simprints/biometrics/simface/matcher/VerificationTest.kt @@ -0,0 +1,59 @@ +package com.simprints.biometrics.simface.matcher + +import androidx.test.ext.junit.runners.* +import com.simprints.biometrics.simface.Utils.floatArrayToByteArray +import org.junit.Assert +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class VerificationTest { + private lateinit var matchProcessor: MatchProcessor + + @Before + fun setup() { + matchProcessor = CosineDistanceMatchProcessor() + } + + @Test + fun score_between_identical_vectors_should_be_one() { + val array1 = floatArrayToByteArray(floatArrayOf(1.0f, 0.0f, 0.0f)) + val array2 = floatArrayToByteArray(floatArrayOf(1.0f, 0.0f, 0.0f)) + + val distance = matchProcessor.verificationScore(array1, array2) + + assertEquals(1.0, distance, 0.0001) + } + + @Test + fun score_between_orthogonal_vectors_should_be_one_half() { + val array1 = floatArrayToByteArray(floatArrayOf(1.0f, 0.0f)) + val array2 = floatArrayToByteArray(floatArrayOf(0.0f, 1.0f)) + + val distance = matchProcessor.verificationScore(array1, array2) + + assertEquals(0.5, distance, 0.0001) + } + + @Test + fun score_between_opposite_vectors_should_be_zero() { + val array1 = floatArrayToByteArray(floatArrayOf(1.0f, 0.0f)) + val array2 = floatArrayToByteArray(floatArrayOf(-1.0f, 0.0f)) + + val distance = matchProcessor.verificationScore(array1, array2) + + assertEquals(0.0, distance, 0.0001) + } + + @Test + fun score_between_arbitrary_vectors_should_be_between_zero_and_one() { + val array1 = floatArrayToByteArray(floatArrayOf(1.0f, 2.0f, 3.0f)) + val array2 = floatArrayToByteArray(floatArrayOf(4.0f, 5.0f, 6.0f)) + + val distance = matchProcessor.verificationScore(array1, array2) + + Assert.assertTrue(distance > 0.0 && distance < 1.0) + } +} diff --git a/src/androidTest/java/com/simprints/simface/IdentificationTest.kt b/src/androidTest/java/com/simprints/simface/IdentificationTest.kt deleted file mode 100644 index a6386e5..0000000 --- a/src/androidTest/java/com/simprints/simface/IdentificationTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.simprints.simface - -import android.content.Context -import androidx.test.core.app.* -import androidx.test.ext.junit.runners.* -import com.simprints.simface.core.SimFace -import com.simprints.simface.core.SimFaceConfig -import com.simprints.simface.core.Utils -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class IdentificationTest { - private lateinit var simFace: SimFace - - @Before - fun setup() { - val context: Context = ApplicationProvider.getApplicationContext() - simFace = SimFace() - simFace.initialize(SimFaceConfig(context)) - } - - @After - fun cleanup() { - simFace.release() - } - - @Test - fun score_map_should_be_ordered_by_distance() { - val referenceArray = Utils.floatArrayToByteArray(floatArrayOf(1.0f, 0.0f)) - val arrayList = listOf( - Utils.floatArrayToByteArray(floatArrayOf(-1.0f, 0.0f)), // opposite to referenceArray - Utils.floatArrayToByteArray(floatArrayOf(1.0f, 0.0f)), // identical to referenceArray - Utils.floatArrayToByteArray(floatArrayOf(0.0f, 1.0f)), // orthogonal to referenceArray - Utils.floatArrayToByteArray(floatArrayOf(0.707f, 0.707f)), // 45 degrees to referenceArray - ) - - val sortedScores = simFace.identificationScore(referenceArray, arrayList) - val sortedDistances = sortedScores.map { it.second } - - // Closest match (identical vector) should have a score of 1 - Assert.assertEquals(1.0, sortedDistances[0], 0.0001) - - // 45-degree vector (second closest) should have a score of around 0.85355 - Assert.assertEquals(0.85355, sortedDistances[1], 0.0001) - - // Orthogonal vector (further away) should have a score of 0.5 - Assert.assertEquals(0.5, sortedDistances[2], 0.0001) - - // Opposite vector (furthest away) should have a score of 0.0 - Assert.assertEquals(0.0, sortedDistances[3], 0.0001) - } -} diff --git a/src/androidTest/java/com/simprints/simface/VerificationTest.kt b/src/androidTest/java/com/simprints/simface/VerificationTest.kt deleted file mode 100644 index 787dcd5..0000000 --- a/src/androidTest/java/com/simprints/simface/VerificationTest.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.simprints.simface - -import android.content.Context -import androidx.test.core.app.* -import androidx.test.ext.junit.runners.* -import com.simprints.simface.core.SimFace -import com.simprints.simface.core.SimFaceConfig -import com.simprints.simface.core.Utils -import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class VerificationTest { - private lateinit var simFace: SimFace - - @Before - fun setup() { - val context: Context = ApplicationProvider.getApplicationContext() - simFace = SimFace() - simFace.initialize(SimFaceConfig(context)) - } - - @After - fun cleanup() { - simFace.release() - } - - @Test - fun score_between_identical_vectors_should_be_one() { - val array1 = Utils.floatArrayToByteArray(floatArrayOf(1.0f, 0.0f, 0.0f)) - val array2 = Utils.floatArrayToByteArray(floatArrayOf(1.0f, 0.0f, 0.0f)) - - val distance = simFace.verificationScore(array1, array2) - - assertEquals(1.0, distance, 0.0001) - } - - @Test - fun score_between_orthogonal_vectors_should_be_one_half() { - val array1 = Utils.floatArrayToByteArray(floatArrayOf(1.0f, 0.0f)) - val array2 = Utils.floatArrayToByteArray(floatArrayOf(0.0f, 1.0f)) - - val distance = simFace.verificationScore(array1, array2) - - assertEquals(0.5, distance, 0.0001) - } - - @Test - fun score_between_opposite_vectors_should_be_zero() { - val array1 = Utils.floatArrayToByteArray(floatArrayOf(1.0f, 0.0f)) - val array2 = Utils.floatArrayToByteArray(floatArrayOf(-1.0f, 0.0f)) - - val distance = simFace.verificationScore(array1, array2) - - assertEquals(0.0, distance, 0.0001) - } - - @Test - fun score_between_arbitrary_vectors_should_be_between_zero_and_one() { - val array1 = Utils.floatArrayToByteArray(floatArrayOf(1.0f, 2.0f, 3.0f)) - val array2 = Utils.floatArrayToByteArray(floatArrayOf(4.0f, 5.0f, 6.0f)) - - val distance = simFace.verificationScore(array1, array2) - - assertTrue(distance > 0.0 && distance < 1.0) - } -} diff --git a/src/main/java/com/simprints/biometrics/simface/Constants.kt b/src/main/java/com/simprints/biometrics/simface/Constants.kt new file mode 100644 index 0000000..5c832a5 --- /dev/null +++ b/src/main/java/com/simprints/biometrics/simface/Constants.kt @@ -0,0 +1,7 @@ +package com.simprints.biometrics.simface + +internal object Constants { + const val IMAGE_SIZE = 112 + const val OUTPUT_EMBEDDING_SIZE = 512 + const val DEFAULT_TEMPLATE_VERSION = "SIM_FACE_BASE_1" +} diff --git a/src/main/java/com/simprints/simface/core/SimFace.kt b/src/main/java/com/simprints/biometrics/simface/SimFace.kt similarity index 84% rename from src/main/java/com/simprints/simface/core/SimFace.kt rename to src/main/java/com/simprints/biometrics/simface/SimFace.kt index 901b996..0e932c5 100644 --- a/src/main/java/com/simprints/simface/core/SimFace.kt +++ b/src/main/java/com/simprints/biometrics/simface/SimFace.kt @@ -1,20 +1,22 @@ -package com.simprints.simface.core +package com.simprints.biometrics.simface import android.graphics.Bitmap import com.google.mlkit.vision.face.FaceDetector import com.google.mlkit.vision.face.FaceDetectorOptions -import com.simprints.simface.data.FaceDetection -import com.simprints.simface.embedding.EmbeddingProcessor -import com.simprints.simface.embedding.MLModelManager -import com.simprints.simface.embedding.TensorFlowEmbeddingProcessor -import com.simprints.simface.matcher.CosineDistanceMatchProcessor -import com.simprints.simface.matcher.MatchProcessor -import com.simprints.simface.quality.FaceDetectionProcessor -import com.simprints.simface.quality.MlKitFaceDetectionProcessor +import com.simprints.biometrics.simface.data.FaceDetection +import com.simprints.biometrics.simface.detection.FaceDetectionProcessor +import com.simprints.biometrics.simface.detection.MlKitFaceDetectionProcessor +import com.simprints.biometrics.simface.embedding.EmbeddingProcessor +import com.simprints.biometrics.simface.embedding.MLModelManager +import com.simprints.biometrics.simface.embedding.TensorFlowEmbeddingProcessor +import com.simprints.biometrics.simface.matcher.CosineDistanceMatchProcessor +import com.simprints.biometrics.simface.matcher.MatchProcessor +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock import com.google.mlkit.vision.face.FaceDetection as MlKitFaceDetection class SimFace { - private val initLock = Any() + private val initLock = ReentrantLock() // Internal processors private lateinit var modelManager: MLModelManager @@ -27,7 +29,7 @@ class SimFace { /** * Load the ML model into memory and prepare other resources for work. */ - fun initialize(config: SimFaceConfig): Unit = synchronized(initLock) { + fun initialize(config: SimFaceConfig): Unit = initLock.withLock { try { // Initialize the model manager with the given config modelManager = MLModelManager(config) @@ -55,7 +57,7 @@ class SimFace { /** * Releases used resources and ML model. */ - fun release() = synchronized(initLock) { + fun release() = initLock.withLock { try { if (this::modelManager.isInitialized) { modelManager.close() diff --git a/src/main/java/com/simprints/simface/core/SimFaceConfig.kt b/src/main/java/com/simprints/biometrics/simface/SimFaceConfig.kt similarity index 91% rename from src/main/java/com/simprints/simface/core/SimFaceConfig.kt rename to src/main/java/com/simprints/biometrics/simface/SimFaceConfig.kt index ae724e5..6eeac9f 100644 --- a/src/main/java/com/simprints/simface/core/SimFaceConfig.kt +++ b/src/main/java/com/simprints/biometrics/simface/SimFaceConfig.kt @@ -1,4 +1,4 @@ -package com.simprints.simface.core +package com.simprints.biometrics.simface import android.content.Context import java.io.File diff --git a/src/main/java/com/simprints/simface/core/Utils.kt b/src/main/java/com/simprints/biometrics/simface/Utils.kt similarity index 74% rename from src/main/java/com/simprints/simface/core/Utils.kt rename to src/main/java/com/simprints/biometrics/simface/Utils.kt index 82e7353..3cbfe31 100644 --- a/src/main/java/com/simprints/simface/core/Utils.kt +++ b/src/main/java/com/simprints/biometrics/simface/Utils.kt @@ -1,4 +1,4 @@ -package com.simprints.simface.core +package com.simprints.biometrics.simface import android.graphics.Bitmap import android.graphics.Rect @@ -6,10 +6,6 @@ import java.nio.ByteBuffer import java.nio.ByteOrder internal object Utils { - const val IMAGE_SIZE = 112 - const val OUTPUT_EMBEDDING_SIZE = 512 - const val DEFAULT_TEMPLATE_VERSION = "SIM_FACE_BASE_1" - /** * Converts a FloatArray to a ByteArray. * @@ -52,24 +48,6 @@ internal object Utils { bottom.coerceAtMost(height), ) - /** - * Convert image into a 1D array of pixel color values in RGB order. - */ - internal fun Bitmap.toIntArray(imageSize: Int): IntArray { - val intValues = IntArray(imageSize * imageSize) - val resultArray = IntArray(imageSize * imageSize * 3) - - getPixels(intValues, 0, imageSize, 0, 0, imageSize, imageSize) - - var index = 0 - for (pixel in intValues) { - resultArray[index++] = (pixel shr 16) and 255 // Red - resultArray[index++] = (pixel shr 8) and 255 // Green - resultArray[index++] = pixel and 255 // Blue - } - return resultArray - } - /** * Convert image into a 1D array of pixel color * values in RGB order in [0,1] range. diff --git a/src/main/java/com/simprints/simface/data/FaceDetection.kt b/src/main/java/com/simprints/biometrics/simface/data/FaceDetection.kt similarity index 86% rename from src/main/java/com/simprints/simface/data/FaceDetection.kt rename to src/main/java/com/simprints/biometrics/simface/data/FaceDetection.kt index 2df3495..6de77e9 100644 --- a/src/main/java/com/simprints/simface/data/FaceDetection.kt +++ b/src/main/java/com/simprints/biometrics/simface/data/FaceDetection.kt @@ -1,9 +1,9 @@ -package com.simprints.simface.data +package com.simprints.biometrics.simface.data import android.graphics.Bitmap import android.graphics.Rect -import com.simprints.simface.quality.cropAlignFace -import com.simprints.simface.quality.warpAlignFace +import com.simprints.biometrics.simface.detection.cropAlignFace +import com.simprints.biometrics.simface.detection.warpAlignFace data class FaceDetection( val sourceWidth: Int, diff --git a/src/main/java/com/simprints/simface/data/FacialLandmarks.kt b/src/main/java/com/simprints/biometrics/simface/data/FacialLandmarks.kt similarity index 78% rename from src/main/java/com/simprints/simface/data/FacialLandmarks.kt rename to src/main/java/com/simprints/biometrics/simface/data/FacialLandmarks.kt index e46e360..8015278 100644 --- a/src/main/java/com/simprints/simface/data/FacialLandmarks.kt +++ b/src/main/java/com/simprints/biometrics/simface/data/FacialLandmarks.kt @@ -1,4 +1,4 @@ -package com.simprints.simface.data +package com.simprints.biometrics.simface.data data class FacialLandmarks( val eyeLeft: Point2D, diff --git a/src/main/java/com/simprints/simface/data/Point2D.kt b/src/main/java/com/simprints/biometrics/simface/data/Point2D.kt similarity index 56% rename from src/main/java/com/simprints/simface/data/Point2D.kt rename to src/main/java/com/simprints/biometrics/simface/data/Point2D.kt index 6984d49..451ad47 100644 --- a/src/main/java/com/simprints/simface/data/Point2D.kt +++ b/src/main/java/com/simprints/biometrics/simface/data/Point2D.kt @@ -1,4 +1,4 @@ -package com.simprints.simface.data +package com.simprints.biometrics.simface.data data class Point2D( val x: Float, diff --git a/src/main/java/com/simprints/simface/quality/FaceAlignment.kt b/src/main/java/com/simprints/biometrics/simface/detection/FaceAlignment.kt similarity index 98% rename from src/main/java/com/simprints/simface/quality/FaceAlignment.kt rename to src/main/java/com/simprints/biometrics/simface/detection/FaceAlignment.kt index 4318cb0..67443b9 100644 --- a/src/main/java/com/simprints/simface/quality/FaceAlignment.kt +++ b/src/main/java/com/simprints/biometrics/simface/detection/FaceAlignment.kt @@ -1,12 +1,12 @@ -package com.simprints.simface.quality +package com.simprints.biometrics.simface.detection import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Matrix import android.graphics.Rect import androidx.core.graphics.createBitmap -import com.simprints.simface.core.Utils.IMAGE_SIZE -import com.simprints.simface.data.FacialLandmarks +import com.simprints.biometrics.simface.Constants.IMAGE_SIZE +import com.simprints.biometrics.simface.data.FacialLandmarks import org.ejml.dense.row.SingularOps_DDRM import org.ejml.simple.SimpleMatrix import kotlin.math.sqrt diff --git a/src/main/java/com/simprints/simface/quality/FaceDetectionProcessor.kt b/src/main/java/com/simprints/biometrics/simface/detection/FaceDetectionProcessor.kt similarity index 75% rename from src/main/java/com/simprints/simface/quality/FaceDetectionProcessor.kt rename to src/main/java/com/simprints/biometrics/simface/detection/FaceDetectionProcessor.kt index 5feb4f4..cfde7fc 100644 --- a/src/main/java/com/simprints/simface/quality/FaceDetectionProcessor.kt +++ b/src/main/java/com/simprints/biometrics/simface/detection/FaceDetectionProcessor.kt @@ -1,7 +1,7 @@ -package com.simprints.simface.quality +package com.simprints.biometrics.simface.detection import android.graphics.Bitmap -import com.simprints.simface.data.FaceDetection +import com.simprints.biometrics.simface.data.FaceDetection internal interface FaceDetectionProcessor { fun detectFace( diff --git a/src/main/java/com/simprints/simface/quality/MlKitFaceDetectionProcessor.kt b/src/main/java/com/simprints/biometrics/simface/detection/MlKitFaceDetectionProcessor.kt similarity index 95% rename from src/main/java/com/simprints/simface/quality/MlKitFaceDetectionProcessor.kt rename to src/main/java/com/simprints/biometrics/simface/detection/MlKitFaceDetectionProcessor.kt index 516afdc..99fc493 100644 --- a/src/main/java/com/simprints/simface/quality/MlKitFaceDetectionProcessor.kt +++ b/src/main/java/com/simprints/biometrics/simface/detection/MlKitFaceDetectionProcessor.kt @@ -1,4 +1,4 @@ -package com.simprints.simface.quality +package com.simprints.biometrics.simface.detection import android.graphics.Bitmap import com.google.mlkit.vision.common.InputImage @@ -6,10 +6,10 @@ import com.google.mlkit.vision.face.Face import com.google.mlkit.vision.face.FaceContour import com.google.mlkit.vision.face.FaceDetector import com.google.mlkit.vision.face.FaceLandmark -import com.simprints.simface.core.Utils.clampToBounds -import com.simprints.simface.data.FaceDetection -import com.simprints.simface.data.FacialLandmarks -import com.simprints.simface.data.Point2D +import com.simprints.biometrics.simface.Utils.clampToBounds +import com.simprints.biometrics.simface.data.FaceDetection +import com.simprints.biometrics.simface.data.FacialLandmarks +import com.simprints.biometrics.simface.data.Point2D import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine diff --git a/src/main/java/com/simprints/simface/embedding/EmbeddingProcessor.kt b/src/main/java/com/simprints/biometrics/simface/embedding/EmbeddingProcessor.kt similarity index 70% rename from src/main/java/com/simprints/simface/embedding/EmbeddingProcessor.kt rename to src/main/java/com/simprints/biometrics/simface/embedding/EmbeddingProcessor.kt index b8806e0..1733d90 100644 --- a/src/main/java/com/simprints/simface/embedding/EmbeddingProcessor.kt +++ b/src/main/java/com/simprints/biometrics/simface/embedding/EmbeddingProcessor.kt @@ -1,4 +1,4 @@ -package com.simprints.simface.embedding +package com.simprints.biometrics.simface.embedding import android.graphics.Bitmap diff --git a/src/main/java/com/simprints/simface/embedding/MLModelManager.kt b/src/main/java/com/simprints/biometrics/simface/embedding/MLModelManager.kt similarity index 88% rename from src/main/java/com/simprints/simface/embedding/MLModelManager.kt rename to src/main/java/com/simprints/biometrics/simface/embedding/MLModelManager.kt index 5eacd54..c2d1c69 100644 --- a/src/main/java/com/simprints/simface/embedding/MLModelManager.kt +++ b/src/main/java/com/simprints/biometrics/simface/embedding/MLModelManager.kt @@ -1,7 +1,7 @@ -package com.simprints.simface.embedding +package com.simprints.biometrics.simface.embedding -import com.simprints.simface.core.SimFaceConfig -import com.simprints.simface.core.Utils +import com.simprints.biometrics.simface.Constants +import com.simprints.biometrics.simface.SimFaceConfig import org.tensorflow.lite.Interpreter import java.nio.ByteBuffer import java.nio.channels.Channels @@ -12,7 +12,7 @@ internal class MLModelManager( ) { private val interpreter: Interpreter - val templateVersion: String = config.customModel?.templateVersion ?: Utils.DEFAULT_TEMPLATE_VERSION + val templateVersion: String = config.customModel?.templateVersion ?: Constants.DEFAULT_TEMPLATE_VERSION init { diff --git a/src/main/java/com/simprints/simface/embedding/ReshapeOp.kt b/src/main/java/com/simprints/biometrics/simface/embedding/ReshapeOp.kt similarity index 95% rename from src/main/java/com/simprints/simface/embedding/ReshapeOp.kt rename to src/main/java/com/simprints/biometrics/simface/embedding/ReshapeOp.kt index 2c14d57..227df5e 100644 --- a/src/main/java/com/simprints/simface/embedding/ReshapeOp.kt +++ b/src/main/java/com/simprints/biometrics/simface/embedding/ReshapeOp.kt @@ -1,4 +1,4 @@ -package com.simprints.simface.embedding +package com.simprints.biometrics.simface.embedding import org.tensorflow.lite.DataType import org.tensorflow.lite.support.common.TensorOperator diff --git a/src/main/java/com/simprints/simface/embedding/TensorFlowEmbeddingProcessor.kt b/src/main/java/com/simprints/biometrics/simface/embedding/TensorFlowEmbeddingProcessor.kt similarity index 82% rename from src/main/java/com/simprints/simface/embedding/TensorFlowEmbeddingProcessor.kt rename to src/main/java/com/simprints/biometrics/simface/embedding/TensorFlowEmbeddingProcessor.kt index 4dbcba5..c3a943d 100644 --- a/src/main/java/com/simprints/simface/embedding/TensorFlowEmbeddingProcessor.kt +++ b/src/main/java/com/simprints/biometrics/simface/embedding/TensorFlowEmbeddingProcessor.kt @@ -1,12 +1,11 @@ -package com.simprints.simface.embedding +package com.simprints.biometrics.simface.embedding import android.graphics.Bitmap import androidx.core.graphics.scale -import com.simprints.simface.embedding.MLModelManager -import com.simprints.simface.core.Utils -import com.simprints.simface.core.Utils.IMAGE_SIZE -import com.simprints.simface.core.Utils.OUTPUT_EMBEDDING_SIZE -import com.simprints.simface.core.Utils.toFloatArray +import com.simprints.biometrics.simface.Constants.IMAGE_SIZE +import com.simprints.biometrics.simface.Constants.OUTPUT_EMBEDDING_SIZE +import com.simprints.biometrics.simface.Utils.floatArrayToByteArray +import com.simprints.biometrics.simface.Utils.toFloatArray import org.tensorflow.lite.DataType import org.tensorflow.lite.Interpreter import org.tensorflow.lite.support.common.TensorProcessor @@ -44,7 +43,7 @@ internal class TensorFlowEmbeddingProcessor( return try { interpreter.run(tensorBuffer, outputEmbeddingBuffer) - Utils.floatArrayToByteArray(outputEmbeddingBuffer[0]) + floatArrayToByteArray(outputEmbeddingBuffer[0]) } catch (e: Exception) { println("Error running TFLite model inference, ${e.message}") // Handle error, maybe return an empty array or throw a custom exception diff --git a/src/main/java/com/simprints/simface/matcher/CosineDistanceMatchProcessor.kt b/src/main/java/com/simprints/biometrics/simface/matcher/CosineDistanceMatchProcessor.kt similarity index 93% rename from src/main/java/com/simprints/simface/matcher/CosineDistanceMatchProcessor.kt rename to src/main/java/com/simprints/biometrics/simface/matcher/CosineDistanceMatchProcessor.kt index e319528..d975522 100644 --- a/src/main/java/com/simprints/simface/matcher/CosineDistanceMatchProcessor.kt +++ b/src/main/java/com/simprints/biometrics/simface/matcher/CosineDistanceMatchProcessor.kt @@ -1,6 +1,6 @@ -package com.simprints.simface.matcher +package com.simprints.biometrics.simface.matcher -import com.simprints.simface.core.Utils +import com.simprints.biometrics.simface.Utils import kotlin.math.sqrt internal class CosineDistanceMatchProcessor : MatchProcessor { diff --git a/src/main/java/com/simprints/simface/matcher/MatchProcessor.kt b/src/main/java/com/simprints/biometrics/simface/matcher/MatchProcessor.kt similarity index 84% rename from src/main/java/com/simprints/simface/matcher/MatchProcessor.kt rename to src/main/java/com/simprints/biometrics/simface/matcher/MatchProcessor.kt index 224e1d8..cbf46ca 100644 --- a/src/main/java/com/simprints/simface/matcher/MatchProcessor.kt +++ b/src/main/java/com/simprints/biometrics/simface/matcher/MatchProcessor.kt @@ -1,4 +1,4 @@ -package com.simprints.simface.matcher +package com.simprints.biometrics.simface.matcher internal interface MatchProcessor { fun verificationScore( From b5340170f1716a5acb2ae5acdbbd74b2eb742c98 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Fri, 30 May 2025 15:35:08 +0300 Subject: [PATCH 2/2] Bump major version to signal breaking changes in the API --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index e7dbe7c..fec942d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { val projectGroupId = "com.simprints.biometrics" val projectArtifactId = "simface" -val projectVersion = "2025.2.1" +val projectVersion = "2025.3.0" android {