1//
2//  EXFaceEncoder.m
3//  Exponent
4//
5//  Created by Stanisław Chmiela on 23.10.2017.
6//  Copyright © 2017 650 Industries. All rights reserved.
7//
8
9#import <EXFaceDetector/EXFaceEncoder.h>
10#import <EXFaceDetector/EXFaceDetectorUtils.h>
11#import <GoogleMLKit/MLKit.h>
12
13#define cDefaultFloatComparisonEpsilon 0.0001
14#define cModEqualFloatsWithEpsilon(dividend, divisor, modulo, epsilon) \
15fabs( fmod(dividend, divisor) - modulo ) < epsilon
16#define cModEqualFloats(dividend, divisor, modulo) \
17cModEqualFloatsWithEpsilon(dividend, divisor, modulo, cDefaultFloatComparisonEpsilon)
18
19@interface EXFaceEncoder()
20
21@property (assign, nonatomic) BOOL swapWidthAndHeight;
22@property CGAffineTransform transform;
23@property EXFaceDetectionAngleTransformBlock angleTransformer;
24
25@end
26
27@implementation EXFaceEncoder
28
29- (instancetype)init
30{
31  return [self initWithTransform:CGAffineTransformIdentity];
32}
33
34- (instancetype)initWithTransform:(CGAffineTransform)pointTransformer
35{
36  EXFaceDetectionAngleTransformBlock transformer = ^(float angle) {return angle;};
37  return [self initWithTransform:pointTransformer withRotationTransform:transformer];
38}
39
40- (instancetype)initWithRotationTransform:(EXFaceDetectionAngleTransformBlock)rotationTransform
41{
42  return [self initWithTransform:CGAffineTransformIdentity withRotationTransform:rotationTransform];
43}
44
45- (instancetype)initWithTransform:(CGAffineTransform)transform withRotationTransform:(EXFaceDetectionAngleTransformBlock)rotationTransform
46{
47  self = [super init];
48  if (self) {
49    _transform = transform;
50    _angleTransformer = rotationTransform;
51  }
52  return self;
53}
54
55
56- (NSDictionary *)encode:(MLKFace *)face
57{
58  CGRect bounds = CGRectApplyAffineTransform(face.frame, _transform);
59  NSDictionary *initialDictionary = @{
60                                      @"bounds" : @{
61                                          @"size" : @{
62                                              @"width" : @(bounds.size.width),
63                                              @"height" : @(bounds.size.height)
64                                              },
65                                          @"origin" : @{
66                                              @"x" : @(bounds.origin.x),
67                                              @"y" : @(bounds.origin.y)
68                                              }
69                                          }
70                                      };
71  NSMutableDictionary *encodedFace = [[NSMutableDictionary alloc] initWithDictionary:initialDictionary];
72  [self putAFloat:face.smilingProbability forKey:@"smilingProbability" toDictionary:encodedFace ifValueIsValid:face.hasSmilingProbability];
73  [self putAnInteger:face.trackingID forKey:@"faceID" toDictionary:encodedFace ifValueIsValid:face.hasTrackingID];
74
75  MLKFaceLandmark *leftEar = [face landmarkOfType:MLKFaceLandmarkTypeLeftEar];
76  if(leftEar != nil) {
77    [self putAPoint:leftEar.position forKey:@"leftEarPosition" toDictionary:encodedFace];
78  }
79  MLKFaceLandmark *rightEar = [face landmarkOfType:MLKFaceLandmarkTypeRightEar];
80  if(rightEar != nil) {
81    [self putAPoint:rightEar.position forKey:@"rightEarPosition" toDictionary:encodedFace];
82  }
83
84  MLKFaceLandmark *leftEye = [face landmarkOfType:MLKFaceLandmarkTypeLeftEye];
85  if (leftEye != nil) {
86    [self putAPoint:leftEye.position forKey:@"leftEyePosition" toDictionary:encodedFace];
87  }
88  MLKFaceLandmark *rightEye = [face landmarkOfType:MLKFaceLandmarkTypeRightEye];
89  if(rightEye) {
90    [self putAPoint:rightEye.position forKey:@"rightEyePosition" toDictionary:encodedFace];
91  }
92
93  [self putAFloat:face.leftEyeOpenProbability forKey:@"leftEyeOpenProbability" toDictionary:encodedFace ifValueIsValid:face.hasLeftEyeOpenProbability];
94  [self putAFloat:face.rightEyeOpenProbability forKey:@"rightEyeOpenProbability" toDictionary:encodedFace ifValueIsValid:face.hasRightEyeOpenProbability];
95
96  MLKFaceLandmark *leftCheek = [face landmarkOfType:MLKFaceLandmarkTypeLeftCheek];
97  if(leftCheek) {
98    [self putAPoint:leftCheek.position forKey:@"leftCheekPosition" toDictionary:encodedFace];
99  }
100  MLKFaceLandmark *rightCheek = [face landmarkOfType:MLKFaceLandmarkTypeRightCheek];
101  if(rightCheek) {
102    [self putAPoint:rightCheek.position forKey:@"rightCheekPosition" toDictionary:encodedFace];
103  }
104
105  MLKFaceLandmark *leftMouth = [face landmarkOfType:MLKFaceLandmarkTypeMouthLeft];
106  if(leftMouth) {
107    [self putAPoint:leftMouth.position forKey:@"leftMouthPosition" toDictionary:encodedFace];
108  }
109  MLKFaceLandmark *rightMouth = [face landmarkOfType:MLKFaceLandmarkTypeMouthRight];
110  if(rightMouth) {
111    [self putAPoint:rightMouth.position forKey:@"rightMouthPosition" toDictionary:encodedFace];
112  }
113  MLKFaceLandmark *bottomMouth = [face landmarkOfType:MLKFaceLandmarkTypeMouthBottom];
114  if(bottomMouth) {
115    [self putAPoint:bottomMouth.position forKey:@"bottomMouthPosition" toDictionary:encodedFace];
116  }
117
118  MLKFaceLandmark *noseBase = [face landmarkOfType:MLKFaceLandmarkTypeNoseBase];
119  if(noseBase) {
120    [self putAPoint:noseBase.position forKey:@"noseBasePosition" toDictionary:encodedFace];
121  }
122
123  [self putAFloat:face.headEulerAngleY forKey:@"yawAngle" toDictionary:encodedFace ifValueIsValid:face.hasHeadEulerAngleY];
124
125  [self putAFloat:self.angleTransformer(face.headEulerAngleZ) forKey:@"rollAngle" toDictionary:encodedFace ifValueIsValid:face.hasHeadEulerAngleZ];
126
127  return encodedFace;
128}
129
130- (void)putAPoint:(MLKVisionPoint *)point
131           forKey:(NSString *)key
132     toDictionary:(NSMutableDictionary *)dictionary
133{
134  CGPoint transformedPoint = CGPointApplyAffineTransform([self toPoint:point], _transform);
135  [dictionary setObject:@{ @"x" : @(transformedPoint.x), @"y" : @(transformedPoint.y) } forKey:key];
136}
137
138- (void)putAFloat:(CGFloat)value forKey:(NSString *)key toDictionary:(NSMutableDictionary *)dictionary ifValueIsValid:(BOOL)floatIsValid
139{
140  if (floatIsValid) {
141    [dictionary setObject:@(value) forKey:key];
142  }
143}
144
145- (void)putAnInteger:(NSUInteger)value forKey:(NSString *)key toDictionary:(NSMutableDictionary *)dictionary ifValueIsValid:(BOOL)integerIsValid
146{
147  if (integerIsValid) {
148    [dictionary setObject:@(value) forKey:key];
149  }
150}
151
152- (CGFloat)rollAngleFromTransform:(CGAffineTransform)transform
153{
154  return atan2f(transform.b, transform.a);
155}
156
157- (CGFloat)radianAngleToDegrees:(CGFloat)angle
158{
159  return angle * (180 / M_PI);
160}
161
162- (CGPoint)toPoint:(MLKVisionPoint *)visionPoint
163{
164  return CGPointMake([visionPoint x], [visionPoint y]);
165}
166
167@end
168