Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Release Notes

## Unreleased

### Changed

* Removed selfie quality model on EnhancedSmartSelfie and cleaned up SelfieQualityModel
references from biometric and document verification screens.

## 11.1.2 - September 18, 2025

### Changed
Expand Down
5 changes: 0 additions & 5 deletions lib/lib.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ android {

buildFeatures {
buildConfig = true
mlModelBinding = true
}

lint {
Expand Down Expand Up @@ -219,10 +218,6 @@ dependencies {

// Bundled model
implementation(libs.mlkit.obj.detection)

implementation(libs.litert)
implementation(libs.litert.metadata)
implementation(libs.litert.support)
// Play Integrity
implementation(libs.play.integrity)

Expand Down
9 changes: 0 additions & 9 deletions lib/src/main/java/com/smileidentity/compose/SmileIDExtV2.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,13 @@ package com.smileidentity.compose
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.Typography
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.smileidentity.SmileID
import com.smileidentity.compose.selfie.enhanced.OrchestratedSelfieCaptureScreenEnhanced
import com.smileidentity.compose.theme.SmileThemeSurface
import com.smileidentity.compose.theme.colorScheme
import com.smileidentity.compose.theme.typographyV2
import com.smileidentity.ml.SelfieQualityModel
import com.smileidentity.results.SmartSelfieResult
import com.smileidentity.results.SmileIDCallback
import com.smileidentity.util.randomUserId
Expand Down Expand Up @@ -54,8 +51,6 @@ fun SmileID.SmartSelfieEnrollmentEnhanced(
onResult: SmileIDCallback<SmartSelfieResult> = {},
) {
SmileThemeSurface(colorScheme = colorScheme, typography = typography) {
val context = LocalContext.current
val selfieQualityModel = remember { SelfieQualityModel.newInstance(context) }
OrchestratedSelfieCaptureScreenEnhanced(
modifier = modifier,
userId = userId,
Expand All @@ -64,7 +59,6 @@ fun SmileID.SmartSelfieEnrollmentEnhanced(
isEnroll = true,
showAttribution = showAttribution,
skipApiSubmission = skipApiSubmission,
selfieQualityModel = selfieQualityModel,
extraPartnerParams = extraPartnerParams,
onResult = onResult,
)
Expand Down Expand Up @@ -104,8 +98,6 @@ fun SmileID.SmartSelfieAuthenticationEnhanced(
onResult: SmileIDCallback<SmartSelfieResult> = {},
) {
SmileThemeSurface(colorScheme = colorScheme, typography = typography) {
val context = LocalContext.current
val selfieQualityModel = remember { SelfieQualityModel.newInstance(context) }
// todo provide view model here too
OrchestratedSelfieCaptureScreenEnhanced(
modifier = modifier,
Expand All @@ -115,7 +107,6 @@ fun SmileID.SmartSelfieAuthenticationEnhanced(
showAttribution = showAttribution,
showInstructions = showInstructions,
skipApiSubmission = skipApiSubmission,
selfieQualityModel = selfieQualityModel,
extraPartnerParams = extraPartnerParams,
onResult = onResult,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
Expand All @@ -22,7 +20,6 @@ import com.smileidentity.compose.selfie.OrchestratedSelfieCaptureScreen
import com.smileidentity.compose.selfie.enhanced.OrchestratedSelfieCaptureScreenEnhanced
import com.smileidentity.metadata.LocalMetadataProvider
import com.smileidentity.metadata.models.Metadatum
import com.smileidentity.ml.SelfieQualityModel
import com.smileidentity.models.ConsentInformation
import com.smileidentity.models.IdInfo
import com.smileidentity.results.BiometricKycResult
Expand Down Expand Up @@ -68,15 +65,12 @@ fun OrchestratedBiometricKYCScreen(
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
val selfieCaptureScreen = @Composable {
if (useStrictMode) {
val context = LocalContext.current
val selfieQualityModel = remember { SelfieQualityModel.newInstance(context) }
OrchestratedSelfieCaptureScreenEnhanced(
userId = userId,
allowNewEnroll = false,
showInstructions = showInstructions,
isEnroll = false,
showAttribution = showAttribution,
selfieQualityModel = selfieQualityModel,
skipApiSubmission = true,
onResult = { result ->
when (result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.smileidentity.R
@Composable
fun AnimatedInstructions(modifier: Modifier = Modifier, startFrame: Int = 0, endFrame: Int = 286) {
val composition by rememberLottieComposition(
LottieCompositionSpec.RawRes(R.raw.si_anim_instruction_screen),
LottieCompositionSpec.RawRes(R.raw.si_anim_instruction_screen_v2),
)
val progress by animateLottieCompositionAsState(
composition = composition,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,15 @@ import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.smileidentity.R
import com.smileidentity.compose.components.ProcessingScreen
import com.smileidentity.compose.selfie.OrchestratedSelfieCaptureScreen
import com.smileidentity.compose.selfie.enhanced.OrchestratedSelfieCaptureScreenEnhanced
import com.smileidentity.ml.SelfieQualityModel
import com.smileidentity.models.AutoCapture
import com.smileidentity.models.DocumentCaptureFlow
import com.smileidentity.results.SmileIDCallback
Expand Down Expand Up @@ -56,15 +53,12 @@ internal fun <T : Parcelable> OrchestratedDocumentVerificationScreen(
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val selfieCaptureScreen = @Composable {
if (useStrictMode) {
val context = LocalContext.current
val selfieQualityModel = remember { SelfieQualityModel.newInstance(context) }
OrchestratedSelfieCaptureScreenEnhanced(
userId = userId,
allowNewEnroll = false,
showInstructions = showInstructions,
isEnroll = false,
showAttribution = showAttribution,
selfieQualityModel = selfieQualityModel,
skipApiSubmission = true,
onResult = { result ->
when (result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ import com.smileidentity.compose.preview.Preview
import com.smileidentity.compose.preview.SmilePreviews
import com.smileidentity.metadata.LocalMetadataProvider
import com.smileidentity.metadata.models.Metadatum
import com.smileidentity.ml.SelfieQualityModel
import com.smileidentity.results.SmartSelfieResult
import com.smileidentity.results.SmileIDCallback
import com.smileidentity.results.SmileIDResult
Expand Down Expand Up @@ -112,7 +111,6 @@ import kotlinx.collections.immutable.persistentMapOf
fun OrchestratedSelfieCaptureScreenEnhanced(
userId: String,
isEnroll: Boolean,
selfieQualityModel: SelfieQualityModel,
onResult: SmileIDCallback<SmartSelfieResult>,
modifier: Modifier = Modifier,
showAttribution: Boolean = true,
Expand All @@ -128,7 +126,6 @@ fun OrchestratedSelfieCaptureScreenEnhanced(
isEnroll = isEnroll,
allowNewEnroll = allowNewEnroll,
extraPartnerParams = extraPartnerParams,
selfieQualityModel = selfieQualityModel,
skipApiSubmission = skipApiSubmission,
metadata = metadata,
onResult = onResult,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package com.smileidentity.viewmodel

import android.graphics.Bitmap
import android.graphics.ImageFormat.YUV_420_888
import android.graphics.Rect
import androidx.annotation.OptIn
import androidx.annotation.StringRes
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageProxy
import androidx.core.graphics.scale
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.mlkit.vision.common.InputImage
Expand All @@ -24,7 +22,6 @@ import com.smileidentity.metadata.models.Metadatum
import com.smileidentity.metadata.models.SelfieImageOriginValue.BackCamera
import com.smileidentity.metadata.models.SelfieImageOriginValue.FrontCamera
import com.smileidentity.metadata.updaters.DeviceOrientationMetadata
import com.smileidentity.ml.SelfieQualityModel
import com.smileidentity.models.v2.FailureReason
import com.smileidentity.models.v2.SmartSelfieResponse
import com.smileidentity.networking.doSmartSelfieAuthentication
Expand All @@ -46,7 +43,6 @@ import com.smileidentity.viewmodel.SelfieHint.MoveBack
import com.smileidentity.viewmodel.SelfieHint.MoveCloser
import com.smileidentity.viewmodel.SelfieHint.NeedLight
import com.smileidentity.viewmodel.SelfieHint.OnlyOneFace
import com.smileidentity.viewmodel.SelfieHint.PoorImageQuality
import com.smileidentity.viewmodel.SelfieHint.SearchingForFace
import com.ujizin.camposer.state.CamSelector
import java.io.File
Expand All @@ -66,8 +62,6 @@ import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.tensorflow.lite.DataType
import org.tensorflow.lite.support.image.TensorImage
import retrofit2.HttpException
import timber.log.Timber

Expand All @@ -77,7 +71,6 @@ by the liveness task
*/
const val VIEWFINDER_SCALE = 1.3f
private const val COMPLETED_DELAY_MS = 1500L
private const val FACE_QUALITY_THRESHOLD = 0.5f
private const val FORCED_FAILURE_TIMEOUT_MS = 120_000L
private const val IGNORE_FACES_SMALLER_THAN = 0.03f
private const val INTRA_IMAGE_MIN_DELAY_MS = 250
Expand All @@ -90,9 +83,7 @@ private const val MAX_FACE_ROLL_THRESHOLD = 30
private const val MAX_FACE_YAW_THRESHOLD = 15
private const val MIN_FACE_FILL_THRESHOLD = 0.1f
private const val NO_FACE_RESET_DELAY_MS = 500
private const val NUM_LIVENESS_IMAGES = 8
private const val SELFIE_IMAGE_SIZE = 640
private const val SELFIE_QUALITY_HISTORY_LENGTH = 5

sealed interface SelfieState {
data class Analyzing(val hint: SelfieHint) : SelfieState
Expand Down Expand Up @@ -134,7 +125,6 @@ data class SmartSelfieV2UiState(
class SmartSelfieEnhancedViewModel(
private val userId: String,
private val isEnroll: Boolean,
private val selfieQualityModel: SelfieQualityModel,
private val metadata: MutableList<Metadatum>,
private val allowNewEnroll: Boolean? = null,
private val skipApiSubmission: Boolean,
Expand Down Expand Up @@ -173,8 +163,8 @@ class SmartSelfieEnhancedViewModel(
private var lastValidFaceDetectTime = 0L
private var shouldAnalyzeImages = true
private var selfieCameraOrientation: Int? = null
private val modelInputSize = intArrayOf(1, 120, 120, 3)
private val selfieQualityHistory = mutableListOf<Float>()

// Remove this line entirely
private var forcedFailureTimerExpired = false
private val shouldUseActiveLiveness: Boolean get() = !forcedFailureTimerExpired
private var captureDuration = TimeSource.Monotonic.markNow()
Expand Down Expand Up @@ -393,49 +383,6 @@ class SmartSelfieEnhancedViewModel(
return@addOnSuccessListener
}

// We only run the image quality model on the selfie capture because the liveness
// task requires a turned head, which receives a lower score from the model

// Image Quality Model Inference
// Model input: nx120x120x3 - n images, each cropped to face bounding box
// Model output: nx2 - n images, each with 2 probabilities
// 1st column is the actual quality. 2nd column is 1-(1st_column)

// NB! Model is trained on *face mesh* crop, not face bounding box crop

val input = TensorImage(DataType.FLOAT32).apply {
val modelInputBmp = Bitmap.createBitmap(
bmp,
contourRect.left,
contourRect.top,
contourRect.width(),
contourRect.height(),
// NB! input is not guaranteed to be square, so scale might squish the image
).scale(modelInputSize[1], modelInputSize[2], false)
load(modelInputBmp)
}
val outputs = selfieQualityModel.process(input.tensorBuffer)
val output = outputs.outputFeature0AsTensorBuffer.floatArray.firstOrNull() ?: run {
Timber.w("No image quality output")
return@addOnSuccessListener
}
selfieQualityHistory.add(output)
if (selfieQualityHistory.size > SELFIE_QUALITY_HISTORY_LENGTH) {
// We should only ever exceed history length by 1
selfieQualityHistory.removeAt(0)
}

val averageFaceQuality = selfieQualityHistory.average()

if (averageFaceQuality < FACE_QUALITY_THRESHOLD) {
// We don't want to reset the history here, since the model output is noisy, so
// don't use the helper function
Timber.d("Face quality not met ($averageFaceQuality)")
_uiState.update {
it.copy(selfieState = SelfieState.Analyzing(PoorImageQuality))
}
return@addOnSuccessListener
}
if (shouldUseActiveLiveness) {
_uiState.update {
it.copy(selfieState = SelfieState.Analyzing(activeLiveness.selfieHint))
Expand Down Expand Up @@ -675,7 +622,6 @@ class SmartSelfieEnhancedViewModel(

private fun resetCaptureProgress(reason: SelfieHint) {
_uiState.update { it.copy(selfieState = SelfieState.Analyzing(reason)) }
selfieQualityHistory.clear()
livenessFiles.removeAll { it.delete() }
selfieFile?.delete()
selfieFile = null
Expand Down
1 change: 0 additions & 1 deletion lib/src/main/ml/.gitkeep

This file was deleted.

Binary file removed lib/src/main/ml/selfie_quality_model.tflite
Binary file not shown.
Binary file not shown.
Loading