Skip to content

Commit

Permalink
feat(predictions): add public definitions for no light challenge supp…
Browse files Browse the repository at this point in the history
…ort (#3617)

* feat(predictions): add public definitions for no light challenge support

* Add missing files

* fix swiftlint issue

* feat(predictions): add implementation for no light support (#3618)

* feat(predictions): add implementation for no light support

* fix swiftlint issue

* address review comments

* address review comments

* address review comments
  • Loading branch information
thisisabhash committed Aug 22, 2024
1 parent bdfa37a commit 15489e7
Show file tree
Hide file tree
Showing 16 changed files with 356 additions and 72 deletions.
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.type {
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.type {
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,44 +47,46 @@ func ovalChallenge(from event: ServerSessionInformationEvent) -> FaceLivenessSes
)
}

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

let duration: Double
let shouldScroll: Bool
switch (color.downscrollDuration, color.flatDisplayDuration) {
case (...0, 0...):
duration = Double(color.flatDisplayDuration)
shouldScroll = false
default:
duration = Double(color.downscrollDuration)
shouldScroll = true
}
let duration: Double
let shouldScroll: Bool
switch (color.downscrollDuration, color.flatDisplayDuration) {
case (...0, 0...):
duration = Double(color.flatDisplayDuration)
shouldScroll = false
default:
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)
"""
)
precondition(
color.freshnessColor.rgb.count == 3,
"""
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,
green: Double(color.freshnessColor.rgb[1]) / 255,
blue: Double(color.freshnessColor.rgb[2]) / 255,
_values: color.freshnessColor.rgb
),
duration: duration,
shouldScroll: shouldScroll
)
})
return .init(
colors: displayColors
)
return .init(
rgb: .init(
red: Double(color.freshnessColor.rgb[0]) / 255,
green: Double(color.freshnessColor.rgb[1]) / 255,
blue: Double(color.freshnessColor.rgb[2]) / 255,
_values: color.freshnessColor.rgb
),
duration: duration,
shouldScroll: shouldScroll
)
})
return .init(colors: displayColors)
case .faceMovementChallenge:
return nil
}
}

func sessionConfiguration(from event: ServerSessionInformationEvent) -> FaceLivenessSession.SessionConfiguration {
Expand All @@ -83,3 +95,10 @@ func sessionConfiguration(from event: ServerSessionInformationEvent) -> FaceLive
ovalMatchChallenge: ovalChallenge(from: event)
)
}

func challengeType(from event: ChallengeEvent) -> Challenge {
.init(
version: event.version,
type: event.type
)
}
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] = [
Challenge(version: "2.0.0", type: .faceMovementAndLightChallenge),
Challenge(version: "1.0.0", type: .faceMovementChallenge)
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import Foundation
extension FaceLivenessSession {
@_spi(PredictionsFaceLiveness)
public struct SessionConfiguration {
public let colorChallenge: ColorChallenge
public let colorChallenge: ColorChallenge?
public let ovalMatchChallenge: OvalMatchChallenge

public init(colorChallenge: ColorChallenge, ovalMatchChallenge: OvalMatchChallenge) {
public init(
colorChallenge: ColorChallenge? = nil,
ovalMatchChallenge: OvalMatchChallenge
) {
self.colorChallenge = colorChallenge
self.ovalMatchChallenge = ovalMatchChallenge
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ extension AWSPredictionsPlugin {
let session = FaceLivenessSession(
websocket: WebSocketSession(),
signer: signer,
baseURL: url
baseURL: url,
options: options
)

session.onServiceException = { completion(.failure($0)) }
Expand All @@ -48,7 +49,16 @@ extension AWSPredictionsPlugin {
extension FaceLivenessSession {
@_spi(PredictionsFaceLiveness)
public struct Options {
public init() {}
public let viewId: String
public let preCheckViewEnabled: Bool

public init(
faceLivenessDetectorViewId: String,
preCheckViewEnabled: Bool
) {
self.viewId = faceLivenessDetectorViewId
self.preCheckViewEnabled = preCheckViewEnabled
}
}
}

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

import Foundation

@_spi(PredictionsFaceLiveness)
public struct Challenge {
public let version: String
public let type: ChallengeType

public init(version: String, type: ChallengeType) {
self.version = version
self.type = type
}

public func queryParameterString() -> String {
return self.type.rawValue + "_" + self.version
}
}

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

0 comments on commit 15489e7

Please sign in to comment.