Skip to content

Commit bc16192

Browse files
committed
feat: add no light challenge implementation (#127)
* feat: add no light challenge implementation * update package.swift for CI build * Fix unit tests * Address review comments
1 parent c24b7f0 commit bc16192

21 files changed

+305
-81
lines changed

HostApp/HostApp.xcodeproj/project.pbxproj

-2
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,6 @@
308308
Base,
309309
);
310310
mainGroup = 9070FF97285112B4009867D5;
311-
packageReferences = (
312-
);
313311
productRefGroup = 9070FFA1285112B4009867D5 /* Products */;
314312
projectDirPath = "";
315313
projectRoot = "";

HostApp/HostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
"kind" : "remoteSourceControl",
66
"location" : "https://github.com/aws-amplify/amplify-swift",
77
"state" : {
8-
"revision" : "7846328106dba471b3fb35170155e92aad50d427",
9-
"version" : "2.33.3"
8+
"branch" : "feat/no-light-support",
9+
"revision" : "7c1fa2f7a766208f5af69ca8dce5fd02e6de4db6"
1010
}
1111
},
1212
{
@@ -50,8 +50,8 @@
5050
"kind" : "remoteSourceControl",
5151
"location" : "https://github.com/stephencelis/SQLite.swift.git",
5252
"state" : {
53-
"revision" : "e78ae0220e17525a15ac68c697a155eb7a672a8e",
54-
"version" : "0.15.0"
53+
"revision" : "5f5ad81ac0d0a0f3e56e39e646e8423c617df523",
54+
"version" : "0.13.2"
5555
}
5656
},
5757
{

HostApp/HostApp/Views/ExampleLivenessView.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ struct ExampleLivenessView: View {
2222
case .liveness:
2323
FaceLivenessDetectorView(
2424
sessionID: viewModel.sessionID,
25-
region: "us-east-1",
25+
// TODO: Change before merging to main
26+
region: "us-west-2",
2627
isPresented: Binding(
2728
get: { viewModel.presentationState == .liveness },
2829
set: { _ in }

Package.resolved

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
"kind" : "remoteSourceControl",
66
"location" : "https://github.com/aws-amplify/amplify-swift",
77
"state" : {
8-
"revision" : "dbc4a0412f4b5cd96f3e756e78bbd1e8e0a35a2f",
9-
"version" : "2.35.4"
8+
"branch" : "feat/no-light-support",
9+
"revision" : "7c1fa2f7a766208f5af69ca8dce5fd02e6de4db6"
1010
}
1111
},
1212
{
@@ -50,8 +50,8 @@
5050
"kind" : "remoteSourceControl",
5151
"location" : "https://github.com/stephencelis/SQLite.swift.git",
5252
"state" : {
53-
"revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8",
54-
"version" : "0.15.3"
53+
"revision" : "5f5ad81ac0d0a0f3e56e39e646e8423c617df523",
54+
"version" : "0.13.2"
5555
}
5656
},
5757
{

Package.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ let package = Package(
1313
targets: ["FaceLiveness"]),
1414
],
1515
dependencies: [
16-
.package(url: "https://github.com/aws-amplify/amplify-swift", exact: "2.35.4")
16+
// TODO: Change this before merge to main
17+
.package(url: "https://github.com/aws-amplify/amplify-swift", branch: "feat/no-light-support")
1718
],
1819
targets: [
1920
.target(

Sources/FaceLiveness/FaceDetection/BlazeFace/DetectedFace.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import Foundation
9+
@_spi(PredictionsFaceLiveness) import AWSPredictionsPlugin
910

1011
struct DetectedFace {
1112
var boundingBox: CGRect
@@ -19,7 +20,8 @@ struct DetectedFace {
1920

2021
let confidence: Float
2122

22-
func boundingBoxFromLandmarks(ovalRect: CGRect) -> CGRect {
23+
func boundingBoxFromLandmarks(ovalRect: CGRect,
24+
ovalMatchChallenge: FaceLivenessSession.OvalMatchChallenge) -> CGRect {
2325
let alpha = 2.0
2426
let gamma = 1.8
2527
let ow = (alpha * pupilDistance + gamma * faceHeight) / 2
@@ -34,7 +36,7 @@ struct DetectedFace {
3436
}
3537

3638
let faceWidth = ow
37-
let faceHeight = 1.618 * faceWidth
39+
let faceHeight = ovalMatchChallenge.oval.heightWidthRatio * faceWidth
3840
let faceBoxBottom = boundingBox.maxY
3941
let faceBoxTop = faceBoxBottom - faceHeight
4042
let faceBoxLeft = min(cx - ow / 2, rightEar.x)

Sources/FaceLiveness/FaceDetection/BlazeFace/FaceDetectorShortRange+Model.swift

+14-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Accelerate
1212
import CoreGraphics
1313
import CoreImage
1414
import VideoToolbox
15+
@_spi(PredictionsFaceLiveness) import AWSPredictionsPlugin
1516

1617
enum FaceDetectorShortRange {}
1718

@@ -33,11 +34,16 @@ extension FaceDetectorShortRange {
3334
)
3435
}
3536

37+
weak var faceDetectionSessionConfiguration: FaceDetectionSessionConfigurationWrapper?
3638
weak var detectionResultHandler: FaceDetectionResultHandler?
3739

3840
func setResultHandler(detectionResultHandler: FaceDetectionResultHandler) {
3941
self.detectionResultHandler = detectionResultHandler
4042
}
43+
44+
func setFaceDetectionSessionConfigurationWrapper(configuration: FaceDetectionSessionConfigurationWrapper) {
45+
self.faceDetectionSessionConfiguration = configuration
46+
}
4147

4248
func detectFaces(from buffer: CVPixelBuffer) {
4349
let faces = prediction(for: buffer)
@@ -105,10 +111,17 @@ extension FaceDetectorShortRange {
105111
count: confidenceScoresCapacity
106112
)
107113
)
114+
115+
let blazeFaceDetectionThreshold: Float
116+
if let sessionConfiguration = faceDetectionSessionConfiguration?.sessionConfiguration {
117+
blazeFaceDetectionThreshold = Float(sessionConfiguration.ovalMatchChallenge.faceDetectionThreshold)
118+
} else {
119+
blazeFaceDetectionThreshold = confidenceScoreThreshold
120+
}
108121

109122
var passingConfidenceScoresIndices = confidenceScores
110123
.enumerated()
111-
.filter { $0.element >= confidenceScoreThreshold }
124+
.filter { $0.element >= blazeFaceDetectionThreshold}
112125
.sorted(by: {
113126
$0.element > $1.element
114127
})

Sources/FaceLiveness/FaceDetection/FaceDetector.swift

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import AVFoundation
9+
@_spi(PredictionsFaceLiveness) import AWSPredictionsPlugin
910

1011
protocol FaceDetector {
1112
func detectFaces(from buffer: CVPixelBuffer)
@@ -16,6 +17,10 @@ protocol FaceDetectionResultHandler: AnyObject {
1617
func process(newResult: FaceDetectionResult)
1718
}
1819

20+
protocol FaceDetectionSessionConfigurationWrapper: AnyObject {
21+
var sessionConfiguration: FaceLivenessSession.SessionConfiguration? { get }
22+
}
23+
1924
enum FaceDetectionResult {
2025
case noFace
2126
case singleFace(DetectedFace)

Sources/FaceLiveness/Views/GetReadyPage/GetReadyPageView.swift

+31-15
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,49 @@
66
//
77

88
import SwiftUI
9+
@_spi(PredictionsFaceLiveness) import AWSPredictionsPlugin
910

1011
struct GetReadyPageView: View {
1112
let beginCheckButtonDisabled: Bool
1213
let onBegin: () -> Void
13-
14+
let challenge: Challenge
15+
1416
init(
1517
onBegin: @escaping () -> Void,
16-
beginCheckButtonDisabled: Bool = false
18+
beginCheckButtonDisabled: Bool = false,
19+
challenge: Challenge
1720
) {
1821
self.onBegin = onBegin
1922
self.beginCheckButtonDisabled = beginCheckButtonDisabled
23+
self.challenge = challenge
2024
}
2125

2226
var body: some View {
2327
VStack {
2428
ZStack {
2529
CameraPreviewView()
26-
VStack {
27-
WarningBox(
28-
titleText: LocalizedStrings.get_ready_photosensitivity_title,
29-
bodyText: LocalizedStrings.get_ready_photosensitivity_description,
30-
popoverContent: { photosensitivityWarningPopoverContent }
31-
)
32-
.accessibilityElement(children: .combine)
33-
Text(LocalizedStrings.preview_center_your_face_text)
34-
.font(.title)
35-
.multilineTextAlignment(.center)
36-
Spacer()
37-
}.padding()
30+
switch self.challenge.type {
31+
case .faceMovementChallenge:
32+
VStack {
33+
Text(LocalizedStrings.preview_center_your_face_text)
34+
.font(.title)
35+
.multilineTextAlignment(.center)
36+
Spacer()
37+
}.padding()
38+
case . faceMovementAndLightChallenge:
39+
VStack {
40+
WarningBox(
41+
titleText: LocalizedStrings.get_ready_photosensitivity_title,
42+
bodyText: LocalizedStrings.get_ready_photosensitivity_description,
43+
popoverContent: { photosensitivityWarningPopoverContent }
44+
)
45+
.accessibilityElement(children: .combine)
46+
Text(LocalizedStrings.preview_center_your_face_text)
47+
.font(.title)
48+
.multilineTextAlignment(.center)
49+
Spacer()
50+
}.padding()
51+
}
3852
}
3953
beginCheckButton
4054
}
@@ -72,6 +86,8 @@ struct GetReadyPageView: View {
7286

7387
struct GetReadyPageView_Previews: PreviewProvider {
7488
static var previews: some View {
75-
GetReadyPageView(onBegin: {})
89+
GetReadyPageView(onBegin: {},
90+
challenge: .init(version: "2.0.0",
91+
type: .faceMovementAndLightChallenge))
7692
}
7793
}

Sources/FaceLiveness/Views/Instruction/InstructionContainerView.swift

+22-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import SwiftUI
99
import Combine
10+
@_spi(PredictionsFaceLiveness) import AWSPredictionsPlugin
1011

1112
struct InstructionContainerView: View {
1213
@ObservedObject var viewModel: FaceLivenessDetectionViewModel
@@ -97,13 +98,29 @@ struct InstructionContainerView: View {
9798
argument: LocalizedStrings.challenge_verifying
9899
)
99100
}
100-
case .faceMatched:
101+
case .completedNoLightCheck:
101102
InstructionView(
102-
text: LocalizedStrings.challenge_instruction_hold_still,
103-
backgroundColor: .livenessPrimaryBackground,
104-
textColor: .livenessPrimaryLabel,
105-
font: .title
103+
text: LocalizedStrings.challenge_verifying,
104+
backgroundColor: .livenessBackground
106105
)
106+
.onAppear {
107+
UIAccessibility.post(
108+
notification: .announcement,
109+
argument: LocalizedStrings.challenge_verifying
110+
)
111+
}
112+
case .faceMatched:
113+
if let challenge = viewModel.challenge,
114+
case .faceMovementAndLightChallenge = challenge.type {
115+
InstructionView(
116+
text: LocalizedStrings.challenge_instruction_hold_still,
117+
backgroundColor: .livenessPrimaryBackground,
118+
textColor: .livenessPrimaryLabel,
119+
font: .title
120+
)
121+
} else {
122+
EmptyView()
123+
}
107124
default:
108125
EmptyView()
109126
}

0 commit comments

Comments
 (0)