Skip to content

Commit 7286691

Browse files
committed
add liveness events (#133)
1 parent d3d153d commit 7286691

File tree

8 files changed

+313
-0
lines changed

8 files changed

+313
-0
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
10+
extension Date {
11+
var epochMilliseconds: UInt64 {
12+
UInt64(self.timeIntervalSince1970 * 1_000)
13+
}
14+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
10+
@_spi(PredictionsFaceLiveness)
11+
public struct CompletedEvent<T> {
12+
public init(initialEvent: T, endTimestamp: UInt64) {
13+
self.initialEvent = initialEvent
14+
self.endTimestamp = endTimestamp
15+
}
16+
17+
let initialEvent: T
18+
let endTimestamp: UInt64
19+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
10+
@_spi(PredictionsFaceLiveness)
11+
public struct LivenessEvent<T> {
12+
let payload: Data
13+
let eventKind: LivenessEventKind
14+
let eventTypeHeader: String
15+
}
16+
17+
@_spi(PredictionsFaceLiveness)
18+
public enum LivenessEventKind {
19+
public struct Server: Hashable {
20+
public static let challenge = Server()
21+
public static let disconnect = Server()
22+
}
23+
case server(Server)
24+
25+
public struct Client: Equatable {
26+
let id: UInt8
27+
28+
public static let initialFaceDetected = Client(id: 0)
29+
public static let video = Client(id: 1)
30+
public static let freshness = Client(id: 2)
31+
public static let final = Client(id: 3)
32+
}
33+
case client(Client)
34+
}
35+
36+
extension LivenessEventKind: CustomDebugStringConvertible {
37+
public var debugDescription: String {
38+
switch self {
39+
case .server(.challenge): return ".server(.challenge)"
40+
case .server(.disconnect): return ".server(.disconnect)"
41+
case .client(.initialFaceDetected): return ".client(.initialFaceDetected)"
42+
case .client(.video): return ".client(.video)"
43+
case .client(.freshness): return ".client(.freshness)"
44+
case .client(.final): return ".client(.final)"
45+
default: return "unknown"
46+
}
47+
}
48+
}
49+
50+
extension LivenessEvent: CustomDebugStringConvertible {
51+
public var debugDescription: String {
52+
return """
53+
LivenessEvent<\(T.self)>(
54+
payload: \(payload),
55+
eventKind: \(eventKind),
56+
eventTypeHeader: \(eventTypeHeader)
57+
)
58+
"""
59+
}
60+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
10+
@_spi(PredictionsFaceLiveness)
11+
public struct FaceDetection {
12+
let boundingBox: FaceLivenessSession.BoundingBox
13+
let startTimestamp: UInt64
14+
15+
public init(boundingBox: FaceLivenessSession.BoundingBox, startTimestamp: UInt64) {
16+
self.boundingBox = boundingBox
17+
self.startTimestamp = startTimestamp
18+
}
19+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
10+
@_spi(PredictionsFaceLiveness)
11+
public struct FinalClientEvent {
12+
public init(
13+
initialClientEvent: InitialClientEvent,
14+
targetFace: CompletedEvent<FaceDetection>,
15+
videoEndTimeStamp: UInt64
16+
) {
17+
self.initialClientEvent = initialClientEvent
18+
self.targetFace = targetFace
19+
self.videoEndTimeStamp = videoEndTimeStamp
20+
}
21+
22+
let initialClientEvent: InitialClientEvent
23+
let targetFace: CompletedEvent<FaceDetection>
24+
let videoEndTimeStamp: UInt64
25+
}
26+
27+
extension LivenessEvent where T == FinalClientEvent {
28+
@_spi(PredictionsFaceLiveness)
29+
public static func final(event: FinalClientEvent) throws -> Self {
30+
31+
let clientEvent = ClientSessionInformationEvent(
32+
challenge: .init(
33+
faceMovementAndLightChallenge: .init(
34+
challengeID: event.initialClientEvent.challengeID,
35+
targetFace: .init(
36+
boundingBox: .init(boundingBox: event.targetFace.initialEvent.boundingBox),
37+
faceDetectedInTargetPositionStartTimestamp: event.targetFace.initialEvent.startTimestamp,
38+
faceDetectedInTargetPositionEndTimestamp: event.targetFace.endTimestamp
39+
),
40+
initialFace: .init(
41+
boundingBox: .init(boundingBox: event.initialClientEvent.initialFaceLocation.boundingBox),
42+
initialFaceDetectedTimeStamp: event.initialClientEvent.initialFaceLocation.startTimestamp
43+
),
44+
videoStartTimestamp: nil,
45+
colorDisplayed: nil,
46+
videoEndTimeStamp: Date().epochMilliseconds
47+
)
48+
)
49+
)
50+
let payload = try JSONEncoder().encode(clientEvent)
51+
return .init(
52+
payload: payload,
53+
eventKind: .client(.final),
54+
eventTypeHeader: "ClientSessionInformationEvent"
55+
)
56+
}
57+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
10+
@_spi(PredictionsFaceLiveness)
11+
public struct FreshnessEvent {
12+
let challengeID: String
13+
let color: [Int]
14+
let sequenceNumber: Int
15+
let timestamp: UInt64
16+
let previousColor: [Int]
17+
18+
public init(challengeID: String, color: [Int], sequenceNumber: Int, timestamp: UInt64, previousColor: [Int]) {
19+
self.challengeID = challengeID
20+
self.color = color
21+
self.sequenceNumber = sequenceNumber
22+
self.timestamp = timestamp
23+
self.previousColor = previousColor
24+
}
25+
}
26+
27+
extension LivenessEvent where T == FreshnessEvent {
28+
@_spi(PredictionsFaceLiveness)
29+
public static func freshness(event: FreshnessEvent) throws -> Self {
30+
let clientEvent = ClientSessionInformationEvent(
31+
challenge: .init(
32+
faceMovementAndLightChallenge: .init(
33+
challengeID: event.challengeID,
34+
targetFace: nil,
35+
initialFace: nil,
36+
videoStartTimestamp: nil,
37+
colorDisplayed: .init(
38+
currentColor: .init(rgb: event.color),
39+
sequenceNumber: event.sequenceNumber,
40+
currentColorStartTimeStamp: event.timestamp,
41+
previousColor: .init(rgb: event.previousColor)
42+
),
43+
videoEndTimeStamp: nil
44+
)
45+
)
46+
)
47+
let payload = try JSONEncoder().encode(clientEvent)
48+
return .init(
49+
payload: payload,
50+
eventKind: .client(.freshness),
51+
eventTypeHeader: "ClientSessionInformationEvent"
52+
)
53+
}
54+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
10+
@_spi(PredictionsFaceLiveness)
11+
public struct InitialClientEvent {
12+
public init(
13+
challengeID: String,
14+
initialFaceLocation: FaceDetection,
15+
videoStartTime: UInt64
16+
) {
17+
self.challengeID = challengeID
18+
self.initialFaceLocation = initialFaceLocation
19+
self.videoStartTimestamp = videoStartTime
20+
}
21+
22+
let challengeID: String
23+
let initialFaceLocation: FaceDetection
24+
let videoStartTimestamp: UInt64
25+
}
26+
27+
extension LivenessEvent where T == InitialClientEvent {
28+
@_spi(PredictionsFaceLiveness)
29+
public static func initialFaceDetected(event: InitialClientEvent) throws -> Self {
30+
let initialFace = InitialFace(
31+
boundingBox: .init(boundingBox: event.initialFaceLocation.boundingBox),
32+
initialFaceDetectedTimeStamp: event.initialFaceLocation.startTimestamp
33+
)
34+
35+
let clientSessionInformationEvent = ClientSessionInformationEvent(
36+
challenge: .init(
37+
faceMovementAndLightChallenge: .init(
38+
challengeID: event.challengeID,
39+
targetFace: nil,
40+
initialFace: initialFace,
41+
videoStartTimestamp: event.videoStartTimestamp,
42+
colorDisplayed: nil,
43+
videoEndTimeStamp: nil
44+
)
45+
)
46+
)
47+
48+
let payload = try JSONEncoder().encode(clientSessionInformationEvent)
49+
return .init(
50+
payload: payload,
51+
eventKind: .client(.initialFaceDetected),
52+
eventTypeHeader: "ClientSessionInformationEvent"
53+
)
54+
}
55+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
10+
@_spi(PredictionsFaceLiveness)
11+
public struct VideoEvent {
12+
let chunk: Data
13+
let timestamp: UInt64
14+
15+
public init(chunk: Data, timestamp: UInt64) {
16+
self.chunk = chunk
17+
self.timestamp = timestamp
18+
}
19+
}
20+
21+
extension LivenessEvent where T == VideoEvent {
22+
@_spi(PredictionsFaceLiveness)
23+
public static func video(event: VideoEvent) throws -> Self {
24+
let clientEvent = LivenessVideoEvent(
25+
timestampMillis: event.timestamp,
26+
videoChunk: event.chunk
27+
)
28+
let payload = try JSONEncoder().encode(clientEvent)
29+
return .init(
30+
payload: payload,
31+
eventKind: .client(.video),
32+
eventTypeHeader: "VideoEvent"
33+
)
34+
}
35+
}

0 commit comments

Comments
 (0)