Skip to content

feat(predictions): No-Light/FaceMovementOnly challenge support #3622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ public enum LivenessEventKind {
self.rawValue = rawValue
}

public static let challenge = Self(rawValue: "ServerSessionInformationEvent")
public static let sessionInformation = Self(rawValue: "ServerSessionInformationEvent")
public static let disconnect = Self(rawValue: "DisconnectionEvent")
public static let challenge = Self(rawValue: "ChallengeEvent")
}
case server(Server)

Expand Down Expand Up @@ -60,6 +61,7 @@ extension LivenessEventKind: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .server(.challenge): return ".server(.challenge)"
case .server(.sessionInformation): return ".server(.sessionInformation)"
case .server(.disconnect): return ".server(.disconnect)"
case .client(.initialFaceDetected): return ".client(.initialFaceDetected)"
case .client(.video): return ".client(.video)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ public struct FinalClientEvent {

extension LivenessEvent where T == FinalClientEvent {
@_spi(PredictionsFaceLiveness)
public static func final(event: FinalClientEvent) throws -> Self {

let clientEvent = ClientSessionInformationEvent(
challenge: .init(
faceMovementAndLightChallenge: .init(
public static func final(event: FinalClientEvent,
challenge: Challenge) throws -> Self {
let clientChallengeType: ClientChallenge.ChallengeType
switch challenge {
case .faceMovementAndLightChallenge:
clientChallengeType = .faceMovementAndLightChallenge(
challenge: .init(
challengeID: event.initialClientEvent.challengeID,
targetFace: .init(
boundingBox: .init(boundingBox: event.targetFace.initialEvent.boundingBox),
Expand All @@ -46,7 +48,26 @@ extension LivenessEvent where T == FinalClientEvent {
videoEndTimeStamp: Date().epochMilliseconds
)
)
)
case .faceMovementChallenge:
clientChallengeType = .faceMovementChallenge(
challenge: .init(
challengeID: event.initialClientEvent.challengeID,
targetFace: .init(
boundingBox: .init(boundingBox: event.targetFace.initialEvent.boundingBox),
faceDetectedInTargetPositionStartTimestamp: event.targetFace.initialEvent.startTimestamp,
faceDetectedInTargetPositionEndTimestamp: event.targetFace.endTimestamp
),
initialFace: .init(
boundingBox: .init(boundingBox: event.initialClientEvent.initialFaceLocation.boundingBox),
initialFaceDetectedTimeStamp: event.initialClientEvent.initialFaceLocation.startTimestamp
),
videoStartTimestamp: nil,
videoEndTimeStamp: Date().epochMilliseconds
)
)
}

let clientEvent = ClientSessionInformationEvent(challenge: .init(clientChallengeType: clientChallengeType))
let payload = try JSONEncoder().encode(clientEvent)
return .init(
payload: payload,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,20 @@ extension LivenessEvent where T == FreshnessEvent {
public static func freshness(event: FreshnessEvent) throws -> Self {
let clientEvent = ClientSessionInformationEvent(
challenge: .init(
faceMovementAndLightChallenge: .init(
challengeID: event.challengeID,
targetFace: nil,
initialFace: nil,
videoStartTimestamp: nil,
colorDisplayed: .init(
currentColor: .init(rgb: event.color),
sequenceNumber: event.sequenceNumber,
currentColorStartTimeStamp: event.timestamp,
previousColor: .init(rgb: event.previousColor)
),
videoEndTimeStamp: nil
clientChallengeType: .faceMovementAndLightChallenge(
challenge: .init(
challengeID: event.challengeID,
targetFace: nil,
initialFace: nil,
videoStartTimestamp: nil,
colorDisplayed: .init(
currentColor: .init(rgb: event.color),
sequenceNumber: event.sequenceNumber,
currentColorStartTimeStamp: event.timestamp,
previousColor: .init(rgb: event.previousColor)
),
videoEndTimeStamp: nil
)
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,20 @@ public struct InitialClientEvent {

extension LivenessEvent where T == InitialClientEvent {
@_spi(PredictionsFaceLiveness)
public static func initialFaceDetected(event: InitialClientEvent) throws -> Self {
public static func initialFaceDetected(
event: InitialClientEvent,
challenge: Challenge
) throws -> Self {
let initialFace = InitialFace(
boundingBox: .init(boundingBox: event.initialFaceLocation.boundingBox),
initialFaceDetectedTimeStamp: event.initialFaceLocation.startTimestamp
)

let clientSessionInformationEvent = ClientSessionInformationEvent(
challenge: .init(
faceMovementAndLightChallenge: .init(
let clientChallengeType: ClientChallenge.ChallengeType
switch challenge {
case .faceMovementAndLightChallenge:
clientChallengeType = .faceMovementAndLightChallenge(
challenge: .init(
challengeID: event.challengeID,
targetFace: nil,
initialFace: initialFace,
Expand All @@ -43,8 +48,21 @@ extension LivenessEvent where T == InitialClientEvent {
videoEndTimeStamp: nil
)
)
case .faceMovementChallenge:
clientChallengeType = .faceMovementChallenge(
challenge: .init(
challengeID: event.challengeID,
targetFace: nil,
initialFace: initialFace,
videoStartTimestamp: event.videoStartTimestamp,
videoEndTimeStamp: nil
)
)
}

let clientSessionInformationEvent = ClientSessionInformationEvent(
challenge: .init(clientChallengeType: clientChallengeType)
)

let payload = try JSONEncoder().encode(clientSessionInformationEvent)
return .init(
payload: payload,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,18 @@
import Foundation

func ovalChallenge(from event: ServerSessionInformationEvent) -> FaceLivenessSession.OvalMatchChallenge {
let challengeConfig = event.sessionInformation.challenge.faceMovementAndLightChallenge.challengeConfig
let ovalParameters = event.sessionInformation.challenge.faceMovementAndLightChallenge.ovalParameters
let challengeConfig: ChallengeConfig
let ovalParameters: OvalParameters

switch event.sessionInformation.challenge.type {
case .faceMovementAndLightChallenge(let challenge):
challengeConfig = challenge.challengeConfig
ovalParameters = challenge.ovalParameters
case .faceMovementChallenge(let challenge):
challengeConfig = challenge.challengeConfig
ovalParameters = challenge.ovalParameters
}

let ovalBoundingBox = FaceLivenessSession.BoundingBox.init(
x: Double(ovalParameters.centerX - ovalParameters.width / 2),
y: Double(ovalParameters.centerY - ovalParameters.height / 2),
Expand Down Expand Up @@ -37,11 +47,10 @@ func ovalChallenge(from event: ServerSessionInformationEvent) -> FaceLivenessSes
)
}

func colorChallenge(from event: ServerSessionInformationEvent) -> FaceLivenessSession.ColorChallenge {
let displayColors = event.sessionInformation.challenge
.faceMovementAndLightChallenge.colorSequences
func colorChallenge(from challenge: FaceMovementAndLightServerChallenge) -> FaceLivenessSession.ColorChallenge {
let displayColors = challenge.colorSequences
.map({ color -> FaceLivenessSession.DisplayColor in

let duration: Double
let shouldScroll: Bool
switch (color.downscrollDuration, color.flatDisplayDuration) {
Expand All @@ -52,15 +61,15 @@ func colorChallenge(from event: ServerSessionInformationEvent) -> FaceLivenessSe
duration = Double(color.downscrollDuration)
shouldScroll = true
}

precondition(
color.freshnessColor.rgb.count == 3,
"""
Received invalid freshness colors.
Expected 3 values (r, g, b), received: \(color.freshnessColor.rgb.count)
"""
"""
Received invalid freshness colors.
Expected 3 values (r, g, b), received: \(color.freshnessColor.rgb.count)
"""
)

return .init(
rgb: .init(
red: Double(color.freshnessColor.rgb[0]) / 255,
Expand All @@ -72,14 +81,23 @@ func colorChallenge(from event: ServerSessionInformationEvent) -> FaceLivenessSe
shouldScroll: shouldScroll
)
})
return .init(
colors: displayColors
)
return .init(colors: displayColors)
}

func sessionConfiguration(from event: ServerSessionInformationEvent) -> FaceLivenessSession.SessionConfiguration {
.init(
colorChallenge: colorChallenge(from: event),
ovalMatchChallenge: ovalChallenge(from: event)
)
switch event.sessionInformation.challenge.type {
case .faceMovementAndLightChallenge(let challenge):
return .faceMovementAndLight(colorChallenge(from: challenge), ovalChallenge(from: event))
case .faceMovementChallenge:
return .faceMovement(ovalChallenge(from: event))
}
}

func challenge(from event: ChallengeEvent) -> Challenge {
switch event.type {
case .faceMovementAndLightChallenge:
return .faceMovementAndLightChallenge(event.version)
case .faceMovementChallenge:
return .faceMovementChallenge(event.version)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation

extension FaceLivenessSession {
public static let supportedChallenges: [Challenge] = [
.faceMovementAndLightChallenge("2.0.0"),
.faceMovementChallenge("1.0.0")
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@ import Foundation

extension FaceLivenessSession {
@_spi(PredictionsFaceLiveness)
public struct SessionConfiguration {
public let colorChallenge: ColorChallenge
public let ovalMatchChallenge: OvalMatchChallenge

public init(colorChallenge: ColorChallenge, ovalMatchChallenge: OvalMatchChallenge) {
self.colorChallenge = colorChallenge
self.ovalMatchChallenge = ovalMatchChallenge
}
public enum SessionConfiguration {
case faceMovement(OvalMatchChallenge)
case faceMovementAndLight(ColorChallenge, OvalMatchChallenge)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ extension AWSPredictionsPlugin {
withID sessionID: String,
credentialsProvider: AWSCredentialsProvider? = nil,
region: String,
options: FaceLivenessSession.Options,
completion: @escaping (Result<Void, FaceLivenessSessionError>) -> Void
) async throws -> FaceLivenessSession {

Expand Down Expand Up @@ -48,7 +47,16 @@ extension AWSPredictionsPlugin {
extension FaceLivenessSession {
@_spi(PredictionsFaceLiveness)
public struct Options {
public init() {}
public let attemptCount: Int
public let preCheckViewEnabled: Bool

public init(
attemptCount: Int,
preCheckViewEnabled: Bool
) {
self.attemptCount = attemptCount
self.preCheckViewEnabled = preCheckViewEnabled
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
public typealias Version = String

@_spi(PredictionsFaceLiveness)
public enum Challenge: Equatable {
case faceMovementChallenge(Version)
case faceMovementAndLightChallenge(Version)

public func queryParameterString() -> String {
switch(self) {
case .faceMovementChallenge(let version):
return "FaceMovementChallenge" + "_" + version
case .faceMovementAndLightChallenge(let version):
return "FaceMovementAndLightChallenge" + "_" + version
}
}

public static func ==(lhs: Challenge, rhs: Challenge) -> Bool {
switch (lhs, rhs) {
case (.faceMovementChallenge(let lhsVersion), .faceMovementChallenge(let rhsVersion)):
return lhsVersion == rhsVersion
case (.faceMovementAndLightChallenge(let lhsVersion), .faceMovementAndLightChallenge(let rhsVersion)):
return lhsVersion == rhsVersion
default:
return false
}
}
}

@_spi(PredictionsFaceLiveness)
public enum ChallengeType: String, Codable {
case faceMovementChallenge = "FaceMovementChallenge"
case faceMovementAndLightChallenge = "FaceMovementAndLightChallenge"
}
Loading
Loading