diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86b21e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# OS X +.DS_Store + +# Xcode +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ +*.xccheckout +profile +*.moved-aside +DerivedData +*.hmap +*.ipa + +# Bundler +.bundle + +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +*.xcworkspace + +Example/PerseLiteDemo/Development.xcconfig diff --git a/Example/PerseLiteDemo/PerseLiteDemo.xcodeproj/project.pbxproj b/Example/PerseLiteDemo/PerseLiteDemo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f5a8e23 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo.xcodeproj/project.pbxproj @@ -0,0 +1,706 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 5C06E54825D1F09A00E6770F /* Podfile in Resources */ = {isa = PBXBuildFile; fileRef = 5C06E54725D1F09A00E6770F /* Podfile */; }; + 5C0D7B592656C9CB00A24232 /* human2.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 5C0D7B572656C9CB00A24232 /* human2.jpeg */; }; + 5C0D7B5A2656C9CB00A24232 /* human.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 5C0D7B582656C9CB00A24232 /* human.jpeg */; }; + 5C0D7B662656CAFB00A24232 /* human1.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 5C0D7B652656CAFB00A24232 /* human1.jpeg */; }; + 5C29525426543179008E35F7 /* dog.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 5C29525326543179008E35F7 /* dog.jpeg */; }; + 5C29527E26543FEB008E35F7 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C29527D26543FEB008E35F7 /* FileUtils.swift */; }; + 5C29528226544A7B008E35F7 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C29528126544A7B008E35F7 /* Environment.swift */; }; + 5C38E9FE265D5CB500922B91 /* PerseLiteFaceDetectWithDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C38E9FD265D5CB500922B91 /* PerseLiteFaceDetectWithDataTests.swift */; }; + 5C38EA06265D601B00922B91 /* DataUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C38EA05265D601B00922B91 /* DataUtils.swift */; }; + 5C38EA12265D69C300922B91 /* PerseLiteFaceCompareWithDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C38EA11265D69C300922B91 /* PerseLiteFaceCompareWithDataTests.swift */; }; + 5C41242926554C8C00C02049 /* PerseLiteUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C41242826554C8C00C02049 /* PerseLiteUtils.swift */; }; + 5C412453265594DA00C02049 /* PerseLiteFaceCompareWithFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C412452265594DA00C02049 /* PerseLiteFaceCompareWithFileTests.swift */; }; + 5C73AAD52681184E0028435B /* PerseLiteCameraViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C73AAD42681184E0028435B /* PerseLiteCameraViewController.swift */; }; + 5CB416AB2652F08E00B7D1EC /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB416AA2652F08E00B7D1EC /* Environment.swift */; }; + 5CE123D8264AF36600D484A1 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE123D7264AF36600D484A1 /* FileUtils.swift */; }; + 5CE123ED264B087800D484A1 /* PerseLiteCompareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE123EC264B087800D484A1 /* PerseLiteCompareViewController.swift */; }; + 5CE1241C264C0F4E00D484A1 /* TableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE1241B264C0F4E00D484A1 /* TableCell.swift */; }; + 5CF64E722653F546004763E3 /* PerseLiteFaceDetectWithFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF64E712653F546004763E3 /* PerseLiteFaceDetectWithFileTests.swift */; }; + 6176EFC2252E496D00F4D4DD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6176EFC1252E496D00F4D4DD /* AppDelegate.swift */; }; + 6176EFC4252E496D00F4D4DD /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6176EFC3252E496D00F4D4DD /* SceneDelegate.swift */; }; + 6176EFC9252E496D00F4D4DD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6176EFC7252E496D00F4D4DD /* Main.storyboard */; }; + 6176EFCB252E496F00F4D4DD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6176EFCA252E496F00F4D4DD /* Assets.xcassets */; }; + 6176EFCE252E496F00F4D4DD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6176EFCC252E496F00F4D4DD /* LaunchScreen.storyboard */; }; + 6176EFDE252E4A9100F4D4DD /* PerseLiteDetectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6176EFDB252E4A9100F4D4DD /* PerseLiteDetectViewController.swift */; }; + 9EC5049591C7680A9C4125EA /* libPods-PerseLiteDemoTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F37839225A9266FA591DAFF /* libPods-PerseLiteDemoTests.a */; }; + FDE19DD3C9335706A67CEC91 /* Pods_PerseLiteDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 473A706C75D8CFF00C492CD5 /* Pods_PerseLiteDemo.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 5CF64E6C2653F51F004763E3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6176EFB6252E496C00F4D4DD /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6176EFBD252E496D00F4D4DD; + remoteInfo = PerseLiteDemo; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 473A706C75D8CFF00C492CD5 /* Pods_PerseLiteDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PerseLiteDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5C06E54725D1F09A00E6770F /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 5C06E5A925D1FBC500E6770F /* PerseLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PerseLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5C0D7B572656C9CB00A24232 /* human2.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = human2.jpeg; sourceTree = ""; }; + 5C0D7B582656C9CB00A24232 /* human.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = human.jpeg; sourceTree = ""; }; + 5C0D7B652656CAFB00A24232 /* human1.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = human1.jpeg; sourceTree = ""; }; + 5C29525326543179008E35F7 /* dog.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = dog.jpeg; sourceTree = ""; }; + 5C29527D26543FEB008E35F7 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; + 5C29528126544A7B008E35F7 /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; + 5C38E9FD265D5CB500922B91 /* PerseLiteFaceDetectWithDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerseLiteFaceDetectWithDataTests.swift; sourceTree = ""; }; + 5C38EA05265D601B00922B91 /* DataUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataUtils.swift; sourceTree = ""; }; + 5C38EA11265D69C300922B91 /* PerseLiteFaceCompareWithDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerseLiteFaceCompareWithDataTests.swift; sourceTree = ""; }; + 5C41242826554C8C00C02049 /* PerseLiteUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerseLiteUtils.swift; sourceTree = ""; }; + 5C412452265594DA00C02049 /* PerseLiteFaceCompareWithFileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerseLiteFaceCompareWithFileTests.swift; sourceTree = ""; }; + 5C73AAD42681184E0028435B /* PerseLiteCameraViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerseLiteCameraViewController.swift; sourceTree = ""; }; + 5CB416A82652EEF600B7D1EC /* Development.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Development.xcconfig; sourceTree = ""; }; + 5CB416AA2652F08E00B7D1EC /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; + 5CE123D7264AF36600D484A1 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; + 5CE123EC264B087800D484A1 /* PerseLiteCompareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerseLiteCompareViewController.swift; sourceTree = ""; }; + 5CE1241B264C0F4E00D484A1 /* TableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCell.swift; sourceTree = ""; }; + 5CF64E672653F51F004763E3 /* PerseLiteDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PerseLiteDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 5CF64E712653F546004763E3 /* PerseLiteFaceDetectWithFileTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerseLiteFaceDetectWithFileTests.swift; sourceTree = ""; }; + 5CF64E732653F555004763E3 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6176EFBE252E496D00F4D4DD /* PerseLiteDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PerseLiteDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6176EFC1252E496D00F4D4DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 6176EFC3252E496D00F4D4DD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 6176EFC8252E496D00F4D4DD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 6176EFCA252E496F00F4D4DD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 6176EFCD252E496F00F4D4DD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 6176EFCF252E496F00F4D4DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6176EFDB252E4A9100F4D4DD /* PerseLiteDetectViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerseLiteDetectViewController.swift; sourceTree = ""; }; + 6F37839225A9266FA591DAFF /* libPods-PerseLiteDemoTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PerseLiteDemoTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A2164DCDC156296905EF5987 /* Pods-PerseLiteDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PerseLiteDemo.release.xcconfig"; path = "Target Support Files/Pods-PerseLiteDemo/Pods-PerseLiteDemo.release.xcconfig"; sourceTree = ""; }; + D69FC24FF4DFEAA83A68AFDA /* Pods-PerseLiteDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PerseLiteDemo.debug.xcconfig"; path = "Target Support Files/Pods-PerseLiteDemo/Pods-PerseLiteDemo.debug.xcconfig"; sourceTree = ""; }; + E2A235FBA4AF7C6D5C9E0E00 /* Pods-PerseLiteDemoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PerseLiteDemoTests.debug.xcconfig"; path = "Target Support Files/Pods-PerseLiteDemoTests/Pods-PerseLiteDemoTests.debug.xcconfig"; sourceTree = ""; }; + F9C821E447540F8786B05543 /* Pods-PerseLiteDemoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PerseLiteDemoTests.release.xcconfig"; path = "Target Support Files/Pods-PerseLiteDemoTests/Pods-PerseLiteDemoTests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5CF64E642653F51F004763E3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9EC5049591C7680A9C4125EA /* libPods-PerseLiteDemoTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6176EFBB252E496D00F4D4DD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FDE19DD3C9335706A67CEC91 /* Pods_PerseLiteDemo.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5C2952522654316E008E35F7 /* Assets */ = { + isa = PBXGroup; + children = ( + 5C0D7B652656CAFB00A24232 /* human1.jpeg */, + 5C0D7B582656C9CB00A24232 /* human.jpeg */, + 5C0D7B572656C9CB00A24232 /* human2.jpeg */, + 5C29525326543179008E35F7 /* dog.jpeg */, + ); + path = Assets; + sourceTree = ""; + }; + 5CF64E682653F51F004763E3 /* PerseLiteDemoTests */ = { + isa = PBXGroup; + children = ( + 5CF64E732653F555004763E3 /* Info.plist */, + 5C2952522654316E008E35F7 /* Assets */, + 5C412452265594DA00C02049 /* PerseLiteFaceCompareWithFileTests.swift */, + 5C38EA11265D69C300922B91 /* PerseLiteFaceCompareWithDataTests.swift */, + 5C41242826554C8C00C02049 /* PerseLiteUtils.swift */, + 5C29527D26543FEB008E35F7 /* FileUtils.swift */, + 5C29528126544A7B008E35F7 /* Environment.swift */, + 5C38E9FD265D5CB500922B91 /* PerseLiteFaceDetectWithDataTests.swift */, + 5CF64E712653F546004763E3 /* PerseLiteFaceDetectWithFileTests.swift */, + 5C38EA05265D601B00922B91 /* DataUtils.swift */, + ); + path = PerseLiteDemoTests; + sourceTree = ""; + }; + 6176EFB5252E496C00F4D4DD = { + isa = PBXGroup; + children = ( + 5CB416A82652EEF600B7D1EC /* Development.xcconfig */, + 5C06E54725D1F09A00E6770F /* Podfile */, + 6176EFC0252E496D00F4D4DD /* PerseLiteDemo */, + 5CF64E682653F51F004763E3 /* PerseLiteDemoTests */, + 6176EFBF252E496D00F4D4DD /* Products */, + E43515AC47E0279330A1606A /* Pods */, + FB60D56C12EF86C3A1E81C33 /* Frameworks */, + ); + sourceTree = ""; + }; + 6176EFBF252E496D00F4D4DD /* Products */ = { + isa = PBXGroup; + children = ( + 6176EFBE252E496D00F4D4DD /* PerseLiteDemo.app */, + 5CF64E672653F51F004763E3 /* PerseLiteDemoTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 6176EFC0252E496D00F4D4DD /* PerseLiteDemo */ = { + isa = PBXGroup; + children = ( + 5CE123D7264AF36600D484A1 /* FileUtils.swift */, + 6176EFDB252E4A9100F4D4DD /* PerseLiteDetectViewController.swift */, + 6176EFC1252E496D00F4D4DD /* AppDelegate.swift */, + 6176EFC3252E496D00F4D4DD /* SceneDelegate.swift */, + 6176EFC7252E496D00F4D4DD /* Main.storyboard */, + 6176EFCA252E496F00F4D4DD /* Assets.xcassets */, + 6176EFCC252E496F00F4D4DD /* LaunchScreen.storyboard */, + 6176EFCF252E496F00F4D4DD /* Info.plist */, + 5CE123EC264B087800D484A1 /* PerseLiteCompareViewController.swift */, + 5CE1241B264C0F4E00D484A1 /* TableCell.swift */, + 5CB416AA2652F08E00B7D1EC /* Environment.swift */, + 5C73AAD42681184E0028435B /* PerseLiteCameraViewController.swift */, + ); + path = PerseLiteDemo; + sourceTree = ""; + }; + E43515AC47E0279330A1606A /* Pods */ = { + isa = PBXGroup; + children = ( + D69FC24FF4DFEAA83A68AFDA /* Pods-PerseLiteDemo.debug.xcconfig */, + A2164DCDC156296905EF5987 /* Pods-PerseLiteDemo.release.xcconfig */, + E2A235FBA4AF7C6D5C9E0E00 /* Pods-PerseLiteDemoTests.debug.xcconfig */, + F9C821E447540F8786B05543 /* Pods-PerseLiteDemoTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + FB60D56C12EF86C3A1E81C33 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5C06E5A925D1FBC500E6770F /* PerseLite.framework */, + 473A706C75D8CFF00C492CD5 /* Pods_PerseLiteDemo.framework */, + 6F37839225A9266FA591DAFF /* libPods-PerseLiteDemoTests.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 5CF64E662653F51F004763E3 /* PerseLiteDemoTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5CF64E702653F51F004763E3 /* Build configuration list for PBXNativeTarget "PerseLiteDemoTests" */; + buildPhases = ( + 88BE86C87642A644F290419F /* [CP] Check Pods Manifest.lock */, + 5CF64E632653F51F004763E3 /* Sources */, + 5CF64E642653F51F004763E3 /* Frameworks */, + 5CF64E652653F51F004763E3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5CF64E6D2653F51F004763E3 /* PBXTargetDependency */, + ); + name = PerseLiteDemoTests; + productName = PerseLiteDemoTests; + productReference = 5CF64E672653F51F004763E3 /* PerseLiteDemoTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 6176EFBD252E496D00F4D4DD /* PerseLiteDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6176EFD2252E496F00F4D4DD /* Build configuration list for PBXNativeTarget "PerseLiteDemo" */; + buildPhases = ( + C3280EE24DD8B4C61690ADF2 /* [CP] Check Pods Manifest.lock */, + 6176EFBA252E496D00F4D4DD /* Sources */, + 6176EFBB252E496D00F4D4DD /* Frameworks */, + 6176EFBC252E496D00F4D4DD /* Resources */, + 6061B88DAEE023BEB9512FE7 /* [CP] Embed Pods Frameworks */, + 4DE4F01301D8261F83FB9B00 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PerseLiteDemo; + productName = PerseLiteDemo; + productReference = 6176EFBE252E496D00F4D4DD /* PerseLiteDemo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6176EFB6252E496C00F4D4DD /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1240; + LastUpgradeCheck = 1200; + TargetAttributes = { + 5CF64E662653F51F004763E3 = { + CreatedOnToolsVersion = 12.4; + TestTargetID = 6176EFBD252E496D00F4D4DD; + }; + 6176EFBD252E496D00F4D4DD = { + CreatedOnToolsVersion = 12.0.1; + }; + }; + }; + buildConfigurationList = 6176EFB9252E496C00F4D4DD /* Build configuration list for PBXProject "PerseLiteDemo" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 6176EFB5252E496C00F4D4DD; + productRefGroup = 6176EFBF252E496D00F4D4DD /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 6176EFBD252E496D00F4D4DD /* PerseLiteDemo */, + 5CF64E662653F51F004763E3 /* PerseLiteDemoTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 5CF64E652653F51F004763E3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5C29525426543179008E35F7 /* dog.jpeg in Resources */, + 5C0D7B662656CAFB00A24232 /* human1.jpeg in Resources */, + 5C0D7B592656C9CB00A24232 /* human2.jpeg in Resources */, + 5C0D7B5A2656C9CB00A24232 /* human.jpeg in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6176EFBC252E496D00F4D4DD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6176EFCE252E496F00F4D4DD /* LaunchScreen.storyboard in Resources */, + 6176EFCB252E496F00F4D4DD /* Assets.xcassets in Resources */, + 5C06E54825D1F09A00E6770F /* Podfile in Resources */, + 6176EFC9252E496D00F4D4DD /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 4DE4F01301D8261F83FB9B00 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PerseLiteDemo/Pods-PerseLiteDemo-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PerseLiteDemo/Pods-PerseLiteDemo-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PerseLiteDemo/Pods-PerseLiteDemo-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 6061B88DAEE023BEB9512FE7 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PerseLiteDemo/Pods-PerseLiteDemo-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PerseLiteDemo/Pods-PerseLiteDemo-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PerseLiteDemo/Pods-PerseLiteDemo-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 88BE86C87642A644F290419F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PerseLiteDemoTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C3280EE24DD8B4C61690ADF2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PerseLiteDemo-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5CF64E632653F51F004763E3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5CF64E722653F546004763E3 /* PerseLiteFaceDetectWithFileTests.swift in Sources */, + 5C38E9FE265D5CB500922B91 /* PerseLiteFaceDetectWithDataTests.swift in Sources */, + 5C29528226544A7B008E35F7 /* Environment.swift in Sources */, + 5C38EA06265D601B00922B91 /* DataUtils.swift in Sources */, + 5C38EA12265D69C300922B91 /* PerseLiteFaceCompareWithDataTests.swift in Sources */, + 5C29527E26543FEB008E35F7 /* FileUtils.swift in Sources */, + 5C412453265594DA00C02049 /* PerseLiteFaceCompareWithFileTests.swift in Sources */, + 5C41242926554C8C00C02049 /* PerseLiteUtils.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6176EFBA252E496D00F4D4DD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6176EFC2252E496D00F4D4DD /* AppDelegate.swift in Sources */, + 5C73AAD52681184E0028435B /* PerseLiteCameraViewController.swift in Sources */, + 5CE123ED264B087800D484A1 /* PerseLiteCompareViewController.swift in Sources */, + 6176EFC4252E496D00F4D4DD /* SceneDelegate.swift in Sources */, + 5CB416AB2652F08E00B7D1EC /* Environment.swift in Sources */, + 5CE1241C264C0F4E00D484A1 /* TableCell.swift in Sources */, + 6176EFDE252E4A9100F4D4DD /* PerseLiteDetectViewController.swift in Sources */, + 5CE123D8264AF36600D484A1 /* FileUtils.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 5CF64E6D2653F51F004763E3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6176EFBD252E496D00F4D4DD /* PerseLiteDemo */; + targetProxy = 5CF64E6C2653F51F004763E3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 6176EFC7252E496D00F4D4DD /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 6176EFC8252E496D00F4D4DD /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 6176EFCC252E496F00F4D4DD /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 6176EFCD252E496F00F4D4DD /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 5CF64E6E2653F51F004763E3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E2A235FBA4AF7C6D5C9E0E00 /* Pods-PerseLiteDemoTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2B682PKZVS; + INFOPLIST_FILE = PerseLiteDemoTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.yoonit.handshake.PerseLiteDemoTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PerseLiteDemo.app/PerseLiteDemo"; + }; + name = Debug; + }; + 5CF64E6F2653F51F004763E3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F9C821E447540F8786B05543 /* Pods-PerseLiteDemoTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2B682PKZVS; + INFOPLIST_FILE = PerseLiteDemoTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.yoonit.handshake.PerseLiteDemoTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PerseLiteDemo.app/PerseLiteDemo"; + }; + name = Release; + }; + 6176EFD0252E496F00F4D4DD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5CB416A82652EEF600B7D1EC /* Development.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ""; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 6176EFD1252E496F00F4D4DD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ""; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 6176EFD3252E496F00F4D4DD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D69FC24FF4DFEAA83A68AFDA /* Pods-PerseLiteDemo.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2B682PKZVS; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_CONFIGURATION_BUILD_DIR}/YoonitCamera\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/PerseLite\"", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_CONFIGURATION_BUILD_DIR}/PerseLite/PerseLite.framework/Headers\"", + ); + INFOPLIST_FILE = PerseLiteDemo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = ai.cyberlabs.perselitedemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6176EFD4252E496F00F4D4DD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A2164DCDC156296905EF5987 /* Pods-PerseLiteDemo.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2B682PKZVS; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_CONFIGURATION_BUILD_DIR}/YoonitCamera\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/PerseLite\"", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_CONFIGURATION_BUILD_DIR}/PerseLite/PerseLite.framework/Headers\"", + ); + INFOPLIST_FILE = PerseLiteDemo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = ai.cyberlabs.perselitedemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 5CF64E702653F51F004763E3 /* Build configuration list for PBXNativeTarget "PerseLiteDemoTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5CF64E6E2653F51F004763E3 /* Debug */, + 5CF64E6F2653F51F004763E3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6176EFB9252E496C00F4D4DD /* Build configuration list for PBXProject "PerseLiteDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6176EFD0252E496F00F4D4DD /* Debug */, + 6176EFD1252E496F00F4D4DD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6176EFD2252E496F00F4D4DD /* Build configuration list for PBXNativeTarget "PerseLiteDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6176EFD3252E496F00F4D4DD /* Debug */, + 6176EFD4252E496F00F4D4DD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 6176EFB6252E496C00F4D4DD /* Project object */; +} diff --git a/Example/PerseLiteDemo/PerseLiteDemo.xcodeproj/xcshareddata/xcschemes/PerseLiteDemo.xcscheme b/Example/PerseLiteDemo/PerseLiteDemo.xcodeproj/xcshareddata/xcschemes/PerseLiteDemo.xcscheme new file mode 100644 index 0000000..c09ceb7 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo.xcodeproj/xcshareddata/xcschemes/PerseLiteDemo.xcscheme @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/PerseLiteDemo/PerseLiteDemo/AppDelegate.swift b/Example/PerseLiteDemo/PerseLiteDemo/AppDelegate.swift new file mode 100644 index 0000000..5bf5dea --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/AppDelegate.swift @@ -0,0 +1,24 @@ +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } +} diff --git a/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/AccentColor.colorset/Contents.json b/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/Contents.json b/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/haroldo.imageset/Contents.json b/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/haroldo.imageset/Contents.json new file mode 100644 index 0000000..55dd746 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/haroldo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "haroldo.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/haroldo.imageset/haroldo.jpeg b/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/haroldo.imageset/haroldo.jpeg new file mode 100644 index 0000000..167c123 Binary files /dev/null and b/Example/PerseLiteDemo/PerseLiteDemo/Assets.xcassets/haroldo.imageset/haroldo.jpeg differ diff --git a/Example/PerseLiteDemo/PerseLiteDemo/Base.lproj/LaunchScreen.storyboard b/Example/PerseLiteDemo/PerseLiteDemo/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/PerseLiteDemo/PerseLiteDemo/Base.lproj/Main.storyboard b/Example/PerseLiteDemo/PerseLiteDemo/Base.lproj/Main.storyboard new file mode 100644 index 0000000..cb168f6 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/Base.lproj/Main.storyboard @@ -0,0 +1,493 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/PerseLiteDemo/PerseLiteDemo/Environment.swift b/Example/PerseLiteDemo/PerseLiteDemo/Environment.swift new file mode 100644 index 0000000..a31a8d6 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/Environment.swift @@ -0,0 +1,27 @@ +import Foundation + +public enum Environment { + private static let infoDictionary: [String: Any] = { + guard let dict = Bundle.main.infoDictionary else { + fatalError("Plist file not found") + } + return dict + }() + + static let url: String = { + guard let rootURLstring = Environment.infoDictionary["URL"] as? String else { + fatalError("PerseLite URL not set in plist for this environment") + } + guard let url = URL(string: rootURLstring) else { + fatalError("PerseLite URL is invalid") + } + return rootURLstring + }() + + static let apiKey: String = { + guard let apiKey = Environment.infoDictionary["API_KEY"] as? String else { + fatalError("PerseLite API Key not set in plist for this environment") + } + return apiKey + }() +} diff --git a/Example/PerseLiteDemo/PerseLiteDemo/FileUtils.swift b/Example/PerseLiteDemo/PerseLiteDemo/FileUtils.swift new file mode 100644 index 0000000..c547325 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/FileUtils.swift @@ -0,0 +1,33 @@ +import Foundation +import UIKit + +enum FileUtilsError: Error { + case invalidJPEGData +} + +func getTempfileUrl(_ index: Int = 0) -> URL { + let tempDirectory = FileManager + .default + .urls(for: .documentDirectory, in: .userDomainMask) + .first! + + return tempDirectory.appendingPathComponent("\(index)perse-lite-demo.jpg") +} + +func save(image: UIImage, fileUrl: URL) throws -> String { + guard let data = image.jpegData(compressionQuality: 1) else { + throw FileUtilsError.invalidJPEGData + } + + do { + if FileManager.default.fileExists(atPath: fileUrl.path) { + try FileManager.default.removeItem(atPath: fileUrl.path) + } + + try data.write(to: fileUrl) + + return fileUrl.standardizedFileURL.absoluteString + } catch { + throw FileUtilsError.invalidJPEGData + } +} diff --git a/Example/PerseLiteDemo/PerseLiteDemo/Info.plist b/Example/PerseLiteDemo/PerseLiteDemo/Info.plist new file mode 100644 index 0000000..fe65bd4 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/Info.plist @@ -0,0 +1,76 @@ + + + + + API_KEY + $(API_KEY) + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + PerseLite Demo + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSCameraUsageDescription + Camera usage for PerseLite + NSPerseLiteUsageDescription + The face detection's module for iOS with a lot of awesome features + NSPhotoLibraryUsageDescription + This app requires access to the photo library + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Example/PerseLiteDemo/PerseLiteDemo/PerseLiteCameraViewController.swift b/Example/PerseLiteDemo/PerseLiteDemo/PerseLiteCameraViewController.swift new file mode 100644 index 0000000..e661a1e --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/PerseLiteCameraViewController.swift @@ -0,0 +1,338 @@ +import UIKit +import PerseLite +import YoonitCamera + +class PerseLiteCameraViewController: + UIViewController, + UINavigationControllerDelegate, + CameraEventListenerDelegate +{ + @IBOutlet var cameraView: CameraView! + @IBOutlet var faceImageView: UIImageView! + + @IBOutlet var leftEyeLabel: UILabel! + @IBOutlet var leftEyeIcon: UIImageView! + + @IBOutlet var rightEyeLabel: UILabel! + @IBOutlet var rightEyeIcon: UIImageView! + + @IBOutlet var smilingLabel: UILabel! + @IBOutlet var smillingIcon: UIImageView! + + @IBOutlet var horizontalMovementLabel: UILabel! + @IBOutlet var verticalMovementLabel: UILabel! + @IBOutlet var tiltMovementLabel: UILabel! + + @IBOutlet var faceUnderexposeLabel: UILabel! + @IBOutlet var faceUnderexposeIcon: UIImageView! + + @IBOutlet var faceSharpnessLabel: UILabel! + @IBOutlet var faceSharpnessIcon: UIImageView! + + @IBOutlet var imageUnderexposeLabel: UILabel! + @IBOutlet var imageUnderexposeIcon: UIImageView! + + @IBOutlet var imageSharpnessLabel: UILabel! + @IBOutlet var imageSharpnessIcon: UIImageView! + + let perseLite = PerseLite(apiKey: Environment.apiKey) + var image: UIImage? + + override func viewDidLoad() { + super.viewDidLoad() + + self.reset() + + self.cameraView.cameraEventListener = self + self.cameraView.startPreview() + self.cameraView.setSaveImageCaptured(true) + self.cameraView.setDetectionBox(true) + self.cameraView.setFaceContours(true) + self.cameraView.setTimeBetweenImages(1000) + self.cameraView.startCaptureType("face") + } + + func onImageCaptured( + _ type: String, + _ count: Int, + _ total: Int, + _ imagePath: String, + _ darkness: NSNumber?, + _ lightness: NSNumber?, + _ sharpness: NSNumber? + ) { + let subpath = imagePath + .substring( + from: imagePath.index( + imagePath.startIndex, + offsetBy: 7 + ) + ) + let image = UIImage(contentsOfFile: subpath) + self.faceImageView.image = image + + self.perseLite.face.detect(imagePath) { + detectResponse in + + if detectResponse.totalFaces == 0 { + self.reset() + return + } + + let face: FaceResponse = detectResponse.faces[0] + + self.setSpoofingValidation(valid: face.livenessScore >= 0.7) + self.handleDisplayProbability( + label: self.faceUnderexposeLabel, + icon: self.faceUnderexposeIcon, + validation: face.faceMetrics.underexpose > 0.7, + value: face.faceMetrics.underexpose + ) + self.handleDisplayProbability( + label: self.faceSharpnessLabel, + icon: self.faceSharpnessIcon, + validation: face.faceMetrics.sharpness < 0.07, + value: face.faceMetrics.sharpness + ) + self.handleDisplayProbability( + label: self.imageUnderexposeLabel, + icon: self.imageUnderexposeIcon, + validation: detectResponse.imageMetrics.underexpose > 0.7, + value: detectResponse.imageMetrics.underexpose + ) + self.handleDisplayProbability( + label: self.imageSharpnessLabel, + icon: self.imageSharpnessIcon, + validation: detectResponse.imageMetrics.sharpness < 0.07, + value: detectResponse.imageMetrics.sharpness + ) + } onError: { + error in + debugPrint(error) + self.reset() + } + } + + func setSpoofingValidation(valid: Bool) { + valid + ? self.cameraView.setDetectionBoxColor( + 1, + 0.1882352941, + 0.8196078431, + 0.3450980392 + ) + : self.cameraView.setDetectionBoxColor(1.0, 1, 0, 0) + valid + ? self.cameraView.setFaceContoursColor( + 1, + 0.1882352941, + 0.8196078431, + 0.3450980392 + ) + : self.cameraView.setFaceContoursColor(1.0, 1, 0, 0) + } + + func onFaceDetected( + _ x: Int, + _ y: Int, + _ width: Int, + _ height: Int, + _ leftEyeOpenProbability: NSNumber?, + _ rightEyeOpenProbability: NSNumber?, + _ smilingProbability: NSNumber?, + _ headEulerAngleX: NSNumber?, + _ headEulerAngleY: NSNumber?, + _ headEulerAngleZ: NSNumber? + ) { + self.handleDisplayProbability( + label: self.leftEyeLabel, + icon: self.leftEyeIcon, + value: leftEyeOpenProbability as? Float, + validText: "Open", + invalidText: "Close" + ) + self.handleDisplayProbability( + label: self.rightEyeLabel, + icon: self.rightEyeIcon, + value: rightEyeOpenProbability as? Float, + validText: "Open", + invalidText: "Close" + ) + self.handleDisplayProbability( + label: self.smilingLabel, + icon: self.smillingIcon, + value: smilingProbability as? Float, + validText: "Smiling", + invalidText: "Not Smiling" + ) + if let angle = headEulerAngleX as? Float { + var text = "" + if angle < -36 { + text = "Super Down" + } else if -36 < angle && angle < -12 { + text = "Down" + } else if -12 < angle && angle < 12 { + text = "Frontal" + } else if 12 < angle && angle < 36 { + text = "Up" + } else if 36 < angle { + text = "Super Up" + } + self.verticalMovementLabel.text = text + } + if let angle = headEulerAngleY as? Float { + var text = "" + if angle < -36 { + text = "Super Left" + } else if -36 < angle && angle < -12 { + text = "Left" + } else if -12 < angle && angle < 12 { + text = "Frontal" + } else if 12 < angle && angle < 36 { + text = "Right" + } else if 36 < angle { + text = "Super Right" + } + self.horizontalMovementLabel.text = text + } + if let angle = headEulerAngleZ as? Float { + var text = "" + if angle < -36 { + text = "Super Right" + } else if -36 < angle && angle < -12 { + text = "Right" + } else if -12 < angle && angle < 12 { + text = "Frontal" + } else if 12 < angle && angle < 36 { + text = "Left" + } else if 36 < angle { + text = "Super Left" + } + self.tiltMovementLabel.text = text + } + } + + func onFaceUndetected() { + self.reset() + } + + func onEndCapture() {} + + func onError(_ error: String) {} + + func onMessage(_ message: String) {} + + func onPermissionDenied() {} + + func onQRCodeScanned(_ content: String) {} + + func reset() { + self.faceImageView.image = nil + + self.leftEyeLabel.text = "-" + self.handleResetIcon(icon: self.leftEyeIcon) + self.rightEyeLabel.text = "-" + self.handleResetIcon(icon: self.rightEyeIcon) + self.smilingLabel.text = "-" + self.handleResetIcon(icon: self.smillingIcon) + self.horizontalMovementLabel.text = "-" + self.verticalMovementLabel.text = "-" + self.tiltMovementLabel.text = "-" + self.faceUnderexposeLabel.text = "-" + self.handleResetIcon(icon: self.faceUnderexposeIcon) + self.faceSharpnessLabel.text = "-" + self.handleResetIcon(icon: self.faceSharpnessIcon) + self.imageUnderexposeLabel.text = "-" + self.handleResetIcon(icon: self.imageUnderexposeIcon) + self.imageSharpnessLabel.text = "-" + self.handleResetIcon(icon: self.imageSharpnessIcon) + self.cameraView.setDetectionBoxColor(0, 1, 1, 1) + self.cameraView.setFaceContoursColor(0, 1, 1, 1) + } + + func handleResetIcon(icon: UIImageView) { + icon.image = UIImage(systemName: "minus.circle.fill") + icon.tintColor = UIColor.gray + } + + func handleDisplayProbability( + label: UILabel, + icon: UIImageView, + value: Float?, + validText: String, + invalidText: String + ) { + if let value: Float = value { + let valid = value > 0.8 + + label.text = value.toLabel() + icon.image = valid + ? UIImage(systemName: "checkmark.circle.fill") + : UIImage(systemName: "multiply.circle.fill") + icon.tintColor = valid + ? UIColor.systemGreen + : UIColor.systemRed + } + } + + func handleDisplayProbability( + label: UILabel, + icon: UIImageView, + validation: Bool, + value: Float? + ) { + if let value: Float = value { + label.text = value.description + icon.image = validation + ? UIImage(systemName: "checkmark.circle.fill") + : UIImage(systemName: "multiply.circle.fill") + icon.tintColor = validation + ? UIColor.systemGreen + : UIColor.systemRed + } + } + + func flipImageLeftRight(_ image: UIImage) -> UIImage? { + + UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale) + + let context = UIGraphicsGetCurrentContext()! + + context.translateBy(x: image.size.width, y: image.size.height) + context.scaleBy(x: -image.scale, y: -image.scale) + context.draw(image.cgImage!, in: CGRect(origin:CGPoint.zero, size: image.size)) + + let newImage = UIGraphicsGetImageFromCurrentImageContext() + + UIGraphicsEndImageContext() + + return newImage + } +} + +class InsetLabel: UILabel { + var textEdgeInsets = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 4) { + didSet { invalidateIntrinsicContentSize() } + } + + open override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect { + let insetRect = bounds.inset(by: textEdgeInsets) + let textRect = super.textRect(forBounds: insetRect, limitedToNumberOfLines: numberOfLines) + let invertedInsets = UIEdgeInsets(top: -textEdgeInsets.top, left: -textEdgeInsets.left, bottom: -textEdgeInsets.bottom, right: -textEdgeInsets.right) + return textRect.inset(by: invertedInsets) + } + + override func drawText(in rect: CGRect) { + super.drawText(in: rect.inset(by: textEdgeInsets)) + } +} + +extension Float { + func toLabel() -> String { + return "\(String(format: "%.2f", self * 100))%" + } + + func toText() -> String { + return "\(String(format: "%.2f", self))" + } +} diff --git a/Example/PerseLiteDemo/PerseLiteDemo/PerseLiteCompareViewController.swift b/Example/PerseLiteDemo/PerseLiteDemo/PerseLiteCompareViewController.swift new file mode 100644 index 0000000..ca3102e --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/PerseLiteCompareViewController.swift @@ -0,0 +1,140 @@ +import UIKit +import PerseLite + +class PerseLiteCompareViewController: + UIViewController, + UIImagePickerControllerDelegate, + UINavigationControllerDelegate, + UITableViewDelegate, + UITableViewDataSource +{ + let perseLite = PerseLite(apiKey: Environment.apiKey) + @IBOutlet weak var firstImageView: UIImageView! + @IBOutlet weak var secondImageView: UIImageView! + @IBOutlet var tableView: UITableView! + private var tablecells = Array() + private let cellReuseIdentifier = "cell" + var imageIndex: Int = 0 + var firstFilePath: String? + var secondFilePath: String? + + override func viewDidLoad() { + self.tableView.delegate = self + self.tableView.dataSource = self + self.tableView.register( + UITableViewCell.self, + forCellReuseIdentifier: self.cellReuseIdentifier + ) + } + + func tableView( + _ tableView: UITableView, + numberOfRowsInSection section: Int + ) -> Int { + return self.tablecells.count + } + + func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + + let cell = UITableViewCell( + style: .value1, + reuseIdentifier: self.cellReuseIdentifier + ) + + cell.textLabel?.text = self.tablecells[indexPath.row].label + cell.textLabel?.textColor = UIColor.darkGray + cell.detailTextLabel?.text = self.tablecells[indexPath.row].value + cell.detailTextLabel?.textColor = UIColor.black + + return cell + } + + @IBAction func getFirstImage() { + self.imageIndex = 0 + self.getImage() + } + + @IBAction func getSecondImage() { + self.imageIndex = 1 + self.getImage() + } + + func getImage() { + let isAvailable = UIImagePickerController + .isSourceTypeAvailable(.photoLibrary) + if isAvailable { + let imagePicker = UIImagePickerController() + imagePicker.delegate = self + imagePicker.allowsEditing = false + imagePicker.sourceType = .photoLibrary + + self.present( + imagePicker, + animated: true, + completion: nil + ) + } + } + + func imagePickerController( + _ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any] + ) { + picker.dismiss(animated: true, completion: nil) + + let infoKeyImage = UIImagePickerController + .InfoKey + .originalImage + let image = info[infoKeyImage] as! UIImage + let fileUrl: URL = getTempfileUrl(self.imageIndex) + + if self.imageIndex == 0 { + self.firstFilePath = try! save( + image: image, + fileUrl: fileUrl + ) + self.firstImageView.image = image + } else { + self.secondFilePath = try! save( + image: image, + fileUrl: fileUrl + ) + self.secondImageView.image = image + } + } + + @IBAction func compare() { + self.perseLite.face.compare( + self.firstFilePath!, + self.secondFilePath! + ) { + compareResponse in + + self.tablecells.removeAll() + self.tablecells.append(TableCell( + "Similarity", + compareResponse.similarity + )) + var i = 0 + for imageToken in compareResponse.imageTokens { + i = i + 1 + self.tablecells.append(TableCell( + "Image Token \(i)", + imageToken + )) + } + self.tablecells.append(TableCell( + "Time Taken", + compareResponse.timeTaken + )) + self.tableView.reloadData() + } onError: { + error in + + debugPrint(error) + } + } +} diff --git a/Example/PerseLiteDemo/PerseLiteDemo/PerseLiteDetectViewController.swift b/Example/PerseLiteDemo/PerseLiteDemo/PerseLiteDetectViewController.swift new file mode 100644 index 0000000..d886a40 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/PerseLiteDetectViewController.swift @@ -0,0 +1,177 @@ +import UIKit +import PerseLite + +class PerseLiteDetectViewController: + UIViewController, + UIImagePickerControllerDelegate, + UINavigationControllerDelegate, + UITableViewDelegate, + UITableViewDataSource +{ + let perseLite = PerseLite(apiKey: Environment.apiKey) + @IBOutlet var tableView: UITableView! + @IBOutlet weak var imageView: UIImageView! + + private var tablecells = Array() + private let cellReuseIdentifier = "cell" + + override func viewDidLoad() { + self.tableView.delegate = self + self.tableView.dataSource = self + self.tableView.register( + UITableViewCell.self, + forCellReuseIdentifier: self.cellReuseIdentifier + ) + } + + func tableView( + _ tableView: UITableView, + numberOfRowsInSection section: Int + ) -> Int { + return self.tablecells.count + } + + func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + + var cell: UITableViewCell! + + if self.tablecells[indexPath.row].value.isEmpty { + cell = UITableViewCell( + style: .default, + reuseIdentifier: self.cellReuseIdentifier + ) + cell.backgroundColor = UIColor.lightGray + cell.textLabel?.textColor = UIColor.white + } else { + cell = UITableViewCell( + style: .value1, + reuseIdentifier: self.cellReuseIdentifier + ) + cell.textLabel?.textColor = UIColor.darkGray + cell.detailTextLabel?.text = self.tablecells[indexPath.row].value + cell.detailTextLabel?.textColor = UIColor.black + } + + cell.textLabel?.text = self.tablecells[indexPath.row].label + + return cell + } + + @IBAction func getImage() { + let isAvailable = UIImagePickerController + .isSourceTypeAvailable(.photoLibrary) + if isAvailable { + let imagePicker = UIImagePickerController() + imagePicker.delegate = self + imagePicker.allowsEditing = false + imagePicker.sourceType = .photoLibrary + + self.present( + imagePicker, + animated: true, + completion: nil + ) + } + } + + func imagePickerController( + _ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any] + ) { + picker.dismiss(animated: true, completion: nil) + + let infoKeyImage = UIImagePickerController + .InfoKey + .originalImage + let image = info[infoKeyImage] as! UIImage + let fileUrl: URL = getTempfileUrl() + let filePath = try! save( + image: image, + fileUrl: fileUrl + ) + + DispatchQueue.main.async { + self.imageView.image = image + self.detect(filePath) + } + } + + func detect(_ filePath: String) { + self.perseLite.face.detect(filePath) { + detectResponse in + + self.tablecells.removeAll() + + self.tablecells.append(TableCell( + "Detected faces", + detectResponse.totalFaces + )) + + guard let faces = detectResponse.faces as Array? else { + return + } + + var i = 0 + for face in faces { + i = i + 1 + self.tablecells.append(TableCell("Face \(i)")) + self.tablecells.append(TableCell( + "Liveness score", + face.livenessScore + )) + + let boundingBox = face.boundingBox + if !boundingBox.isEmpty { + self.tablecells.append(TableCell( + "Bounding box", + boundingBox.description + )) + } + self.tablecells.append(TableCell( + "Confidence", + face.confidence + )) + self.tablecells.append(TableCell("Face Metrics \(i)")) + self.tablecells.append(TableCell( + "Overexpose", + face.faceMetrics.overexpose + )) + self.tablecells.append(TableCell( + "Underexpose", + face.faceMetrics.underexpose + )) + self.tablecells.append(TableCell( + "Sharpness", + face.faceMetrics.sharpness + )) + } + self.tablecells.append(TableCell( + "Image Token", + detectResponse.imageToken + )) + self.tablecells.append(TableCell("Image Metrics")) + self.tablecells.append(TableCell( + "Overexpose", + detectResponse.imageMetrics.overexpose + )) + self.tablecells.append(TableCell( + "Underexpose", + detectResponse.imageMetrics.underexpose + )) + self.tablecells.append(TableCell( + "Sharpness", + detectResponse.imageMetrics.sharpness + )) + + self.tableView.reloadData() + } onError: { + error in + + debugPrint(error) + } + } + +} diff --git a/Example/PerseLiteDemo/PerseLiteDemo/SceneDelegate.swift b/Example/PerseLiteDemo/PerseLiteDemo/SceneDelegate.swift new file mode 100644 index 0000000..33ff693 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/SceneDelegate.swift @@ -0,0 +1,44 @@ +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} diff --git a/Example/PerseLiteDemo/PerseLiteDemo/TableCell.swift b/Example/PerseLiteDemo/PerseLiteDemo/TableCell.swift new file mode 100644 index 0000000..1c2f3be --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemo/TableCell.swift @@ -0,0 +1,37 @@ +import Foundation +import UIKit + +public struct TableCell { + + public let label: String + public let value: String + + init( + _ label: String, + _ value: String + ) { + self.label = label + self.value = value + } + + init( + _ label: String, + _ value: Int + ) { + self.label = label + self.value = "\(value)" + } + + init( + _ label: String, + _ value: Float + ) { + self.label = label + self.value = "\(value)" + } + + init(_ label: String) { + self.label = label + self.value = "" + } +} diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/Assets/dog.jpeg b/Example/PerseLiteDemo/PerseLiteDemoTests/Assets/dog.jpeg new file mode 100644 index 0000000..f122f2d Binary files /dev/null and b/Example/PerseLiteDemo/PerseLiteDemoTests/Assets/dog.jpeg differ diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/Assets/human.jpeg b/Example/PerseLiteDemo/PerseLiteDemoTests/Assets/human.jpeg new file mode 100644 index 0000000..980f909 Binary files /dev/null and b/Example/PerseLiteDemo/PerseLiteDemoTests/Assets/human.jpeg differ diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/Assets/human1.jpeg b/Example/PerseLiteDemo/PerseLiteDemoTests/Assets/human1.jpeg new file mode 100644 index 0000000..7a50f25 Binary files /dev/null and b/Example/PerseLiteDemo/PerseLiteDemoTests/Assets/human1.jpeg differ diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/Assets/human2.jpeg b/Example/PerseLiteDemo/PerseLiteDemoTests/Assets/human2.jpeg new file mode 100644 index 0000000..617612e Binary files /dev/null and b/Example/PerseLiteDemo/PerseLiteDemoTests/Assets/human2.jpeg differ diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/DataUtils.swift b/Example/PerseLiteDemo/PerseLiteDemoTests/DataUtils.swift new file mode 100644 index 0000000..9848a66 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemoTests/DataUtils.swift @@ -0,0 +1,18 @@ +import Foundation +import UIKit + +func getTempData(name: String) -> Data? { + guard let fileUrl: URL = Bundle(for: PerseLiteFaceDetectWithDataTests.self) + .url( + forResource: name, + withExtension: "jpeg" + ) else { + return nil + } + + guard let image = UIImage(contentsOfFile: fileUrl.path) else { + return nil + } + + return image.jpegData(compressionQuality: 1.0) +} diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/Environment.swift b/Example/PerseLiteDemo/PerseLiteDemoTests/Environment.swift new file mode 100644 index 0000000..dbaed2f --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemoTests/Environment.swift @@ -0,0 +1,17 @@ +import Foundation + +public enum Environment { + private static let infoDictionary: [String: Any] = { + guard let dict = Bundle.main.infoDictionary else { + fatalError("Plist file not found") + } + return dict + }() + + static let apiKey: String = { + guard let apiKey = Environment.infoDictionary["API_KEY"] as? String else { + fatalError("PerseLite API Key not set in plist for this environment") + } + return apiKey + }() +} diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/FileUtils.swift b/Example/PerseLiteDemo/PerseLiteDemoTests/FileUtils.swift new file mode 100644 index 0000000..4d3de6e --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemoTests/FileUtils.swift @@ -0,0 +1,52 @@ +import Foundation +import UIKit + +enum FileUtilsError: Error { + case invalidJPEGData +} + +func getTempFilePath(name: String) -> String? { + guard let fileUrl: URL = Bundle(for: PerseLiteFaceDetectWithFileTests.self) + .url( + forResource: name, + withExtension: "jpeg" + ) else { + return nil + } + + let tempImagePathUrl: URL = FileManager + .default + .urls(for: .documentDirectory, in: .userDomainMask) + .first! + .appendingPathComponent("perse-lite-demo-\(name).jpg") + + do { + guard let image = UIImage(contentsOfFile: fileUrl.path) else { + return nil + } + return try save( + image: image, + fileUrl: tempImagePathUrl + ) + } catch { + return nil + } +} + +func save(image: UIImage, fileUrl: URL) throws -> String { + guard let data = image.jpegData(compressionQuality: 1) else { + throw FileUtilsError.invalidJPEGData + } + + do { + if FileManager.default.fileExists(atPath: fileUrl.path) { + try FileManager.default.removeItem(atPath: fileUrl.path) + } + + try data.write(to: fileUrl) + + return fileUrl.standardizedFileURL.absoluteString + } catch { + throw FileUtilsError.invalidJPEGData + } +} diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/Info.plist b/Example/PerseLiteDemo/PerseLiteDemoTests/Info.plist new file mode 100644 index 0000000..64d65ca --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemoTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteFaceCompareWithDataTests.swift b/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteFaceCompareWithDataTests.swift new file mode 100644 index 0000000..22b55b1 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteFaceCompareWithDataTests.swift @@ -0,0 +1,113 @@ +import XCTest +import PerseLite +import Foundation + +class PerseLiteFaceCompareWithDataTests: XCTestCase { + + func testWithSameHuman() { + compareWithData( + self, + firstImageName: "human", + secondImageName: "human1", + apiKey: Environment.apiKey + ) { response in + XCTAssertEqual(response.status, 200) + XCTAssertGreaterThan(response.similarity, 70.0) + } onError: { error in + XCTFail("Error on compare: \(error)") + } + } + + func testWithDifferentHumans() { + compareWithData( + self, + firstImageName: "human1", + secondImageName: "human2", + apiKey: Environment.apiKey + ) { response in + XCTAssertEqual(response.status, 200) + XCTAssertLessThan(response.similarity, 80.0) + } onError: { error in + XCTFail("Error on compare: \(error)") + } + } + + func testWithHumanAndNonHuman() { + compareWithData( + self, + firstImageName: "human", + secondImageName: "dog", + apiKey: Environment.apiKey + ) { response in + XCTFail("Found invalid face in non human image.") + } onError: { error in + XCTAssertEqual(error, "402") + } + } + + func testWithNonHumanAndHuman() { + compareWithData( + self, + firstImageName: "dog", + secondImageName: "human", + apiKey: Environment.apiKey + ) { response in + XCTFail("Found invalid face in non human image.") + } onError: { error in + XCTAssertEqual(error, "402") + } + } + + func testWithNonHumanAndNonHuman() { + compareWithData( + self, + firstImageName: "dog", + secondImageName: "dog", + apiKey: Environment.apiKey + ) { response in + XCTFail("Found invalid face in non human image.") + } onError: { error in + XCTAssertEqual(error, "402") + } + } + + func testWithAPIKeyInvalid() { + compareWithData( + self, + firstImageName: "human", + secondImageName: "human2", + apiKey: "" + ) { detectResponse in + XCTFail("Back-end authorized invalid api token.") + } onError: { error in + XCTAssertEqual(error, "403") + } + } + + func testWithImagePathsInvalid() { + compareWithData( + self, + firstImageName: "test0", + secondImageName: "test1", + apiKey: Environment.apiKey + ) { detectResponse in + XCTFail("") + } onError: { error in + XCTAssertEqual(error, PerseLite.Error.INVALID_IMAGE_PATH) + } + } + + func testWithNonHumans() { + compareWithData( + self, + firstImageName: "dog", + secondImageName: "dog", + apiKey: Environment.apiKey + ) { detectResponse in + XCTFail("") + } onError: { error in + XCTAssertEqual(error, "402") + } + } + +} diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteFaceCompareWithFileTests.swift b/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteFaceCompareWithFileTests.swift new file mode 100644 index 0000000..b6897f4 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteFaceCompareWithFileTests.swift @@ -0,0 +1,113 @@ +import XCTest +import PerseLite +import Foundation + +class PerseLiteFaceCompareWithFileTests: XCTestCase { + + func testWithSameHuman() { + compareWithFile( + self, + firstImageName: "human", + secondImageName: "human1", + apiKey: Environment.apiKey + ) { response in + XCTAssertEqual(response.status, 200) + XCTAssertGreaterThan(response.similarity, 70.0) + } onError: { error in + XCTFail("Error on compare: \(error)") + } + } + + func testWithDifferentHumans() { + compareWithFile( + self, + firstImageName: "human1", + secondImageName: "human2", + apiKey: Environment.apiKey + ) { response in + XCTAssertEqual(response.status, 200) + XCTAssertLessThan(response.similarity, 80.0) + } onError: { error in + XCTFail("Error on compare: \(error)") + } + } + + func testWithHumanAndNonHuman() { + compareWithFile( + self, + firstImageName: "human", + secondImageName: "dog", + apiKey: Environment.apiKey + ) { response in + XCTFail("Found invalid face in non human image.") + } onError: { error in + XCTAssertEqual(error, "402") + } + } + + func testWithNonHumanAndHuman() { + compareWithFile( + self, + firstImageName: "dog", + secondImageName: "human", + apiKey: Environment.apiKey + ) { response in + XCTFail("Found invalid face in non human image.") + } onError: { error in + XCTAssertEqual(error, "402") + } + } + + func testWithNonHumanAndNonHuman() { + compareWithFile( + self, + firstImageName: "dog", + secondImageName: "dog", + apiKey: Environment.apiKey + ) { response in + XCTFail("Found invalid face in non human image.") + } onError: { error in + XCTAssertEqual(error, "402") + } + } + + func testWithAPIKeyInvalid() { + compareWithFile( + self, + firstImageName: "human", + secondImageName: "human2", + apiKey: "" + ) { detectResponse in + XCTFail("Back-end authorized invalid api token.") + } onError: { error in + XCTAssertEqual(error, "403") + } + } + + func testWithImagePathsInvalid() { + compareWithFile( + self, + firstImageName: "test0", + secondImageName: "test1", + apiKey: Environment.apiKey + ) { detectResponse in + XCTFail("") + } onError: { error in + XCTAssertEqual(error, PerseLite.Error.INVALID_IMAGE_PATH) + } + } + + func testWithNonHumans() { + compareWithFile( + self, + firstImageName: "dog", + secondImageName: "dog", + apiKey: Environment.apiKey + ) { detectResponse in + XCTFail("") + } onError: { error in + XCTAssertEqual(error, "402") + } + } + +} diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteFaceDetectWithDataTests.swift b/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteFaceDetectWithDataTests.swift new file mode 100644 index 0000000..d8d57bd --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteFaceDetectWithDataTests.swift @@ -0,0 +1,54 @@ +import XCTest +import PerseLite +import Foundation + +class PerseLiteFaceDetectWithDataTests: XCTestCase { + + func testWithHuman() { + detectWithData( + self, + imageName: "human", + apiKey: Environment.apiKey + ) { detectResponse in + XCTAssertEqual(detectResponse.totalFaces, 1) + } onError: { error in + XCTFail("Error on detect: \(error)") + } + } + + func testWithNonHuman() { + detectWithData( + self, + imageName: "dog", + apiKey: Environment.apiKey + ) { detectResponse in + XCTAssertEqual(detectResponse.totalFaces, 0) + } onError: { error in + XCTFail("Error on detect: \(error)") + } + } + + func testWithAPIKeyInvalid() { + detectWithData( + self, + imageName: "human", + apiKey: "" + ) { detectResponse in + XCTFail("Back-end authorized invalid api token.") + } onError: { error in + XCTAssertEqual(error, "403") + } + } + + func testWithImagePathInvalid() { + detectWithData( + self, + imageName: "test", + apiKey: Environment.apiKey + ) { detectResponse in + XCTFail("") + } onError: { error in + XCTAssertEqual(error, PerseLite.Error.INVALID_IMAGE_PATH) + } + } +} diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteFaceDetectWithFileTests.swift b/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteFaceDetectWithFileTests.swift new file mode 100644 index 0000000..4e34919 --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteFaceDetectWithFileTests.swift @@ -0,0 +1,54 @@ +import XCTest +import PerseLite +import Foundation + +class PerseLiteFaceDetectWithFileTests: XCTestCase { + + func testWithHuman() { + detectWithFile( + self, + imageName: "human", + apiKey: Environment.apiKey + ) { detectResponse in + XCTAssertEqual(detectResponse.totalFaces, 1) + } onError: { error in + XCTFail("Error on detect: \(error)") + } + } + + func testWithNonHuman() { + detectWithFile( + self, + imageName: "dog", + apiKey: Environment.apiKey + ) { detectResponse in + XCTAssertEqual(detectResponse.totalFaces, 0) + } onError: { error in + XCTFail("Error on detect: \(error)") + } + } + + func testWithAPIKeyInvalid() { + detectWithFile( + self, + imageName: "human", + apiKey: "" + ) { detectResponse in + XCTFail("Back-end authorized invalid api token.") + } onError: { error in + XCTAssertEqual(error, "403") + } + } + + func testWithImagePathInvalid() { + detectWithFile( + self, + imageName: "test", + apiKey: Environment.apiKey + ) { detectResponse in + XCTFail("") + } onError: { error in + XCTAssertEqual(error, PerseLite.Error.INVALID_IMAGE_PATH) + } + } +} diff --git a/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteUtils.swift b/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteUtils.swift new file mode 100644 index 0000000..36b76ba --- /dev/null +++ b/Example/PerseLiteDemo/PerseLiteDemoTests/PerseLiteUtils.swift @@ -0,0 +1,167 @@ +import XCTest +import PerseLite +import Foundation + +public func detectWithFile( + _ xctest: XCTestCase, + imageName: String, + apiKey: String, + onSuccess: @escaping (DetectResponse) -> Void, + onError: @escaping (String) -> Void +) { + let expectation = XCTestExpectation(description: "PerseLite face detect in a image.") + + // Create a temp file image path from a resource. + guard let tempFilePath: String = getTempFilePath(name: imageName) else { + onError(PerseLite.Error.INVALID_IMAGE_PATH) + expectation.fulfill() + return + } + + // Start the face detect process. + PerseLite(apiKey: apiKey) + .face + .detect(tempFilePath) + { + detectResponse in + onSuccess(detectResponse) + expectation.fulfill() + return + } onError: { + error in + onError(error) + expectation.fulfill() + return + } + + // Wait until the expectation is fulfilled, with a timeout of 10 seconds. + xctest.wait(for: [expectation], timeout: 10.0) +} + +public func detectWithData( + _ xctest: XCTestCase, + imageName: String, + apiKey: String, + onSuccess: @escaping (DetectResponse) -> Void, + onError: @escaping (String) -> Void +) { + let expectation = XCTestExpectation(description: "PerseLite face detect in a image.") + + // Get a temp data from a resource. + guard let data: Data = getTempData(name: imageName) else { + onError(PerseLite.Error.INVALID_IMAGE_PATH) + expectation.fulfill() + return + } + + // Start the face detect process. + PerseLite(apiKey: apiKey) + .face + .detect(data) + { + detectResponse in + onSuccess(detectResponse) + expectation.fulfill() + return + } onError: { + error in + onError(error) + expectation.fulfill() + return + } + + // Wait until the expectation is fulfilled, with a timeout of 10 seconds. + xctest.wait(for: [expectation], timeout: 10.0) +} + +public func compareWithFile( + _ xctest: XCTestCase, + firstImageName: String, + secondImageName: String, + apiKey: String, + onSuccess: @escaping (CompareResponse) -> Void, + onError: @escaping (String) -> Void +) { + let expectation = XCTestExpectation(description: "PerseLite face compare.") + + // Create a temp file image path from a resource. + guard let firstTempFilePath: String = getTempFilePath(name: firstImageName) else { + onError(PerseLite.Error.INVALID_IMAGE_PATH) + expectation.fulfill() + return + } + + // Create a temp file image path from a resource. + guard let secondTempFilePath: String = getTempFilePath(name: secondImageName) else { + onError(PerseLite.Error.INVALID_IMAGE_PATH) + expectation.fulfill() + return + } + + // Start the face detect process. + PerseLite(apiKey: apiKey) + .face + .compare( + firstTempFilePath, + secondTempFilePath + ) { + compareResponse in + onSuccess(compareResponse) + expectation.fulfill() + return + } onError: { + error in + onError(error) + expectation.fulfill() + return + } + + // Wait until the expectation is fulfilled, with a timeout of 10 seconds. + xctest.wait(for: [expectation], timeout: 10.0) +} + +public func compareWithData( + _ xctest: XCTestCase, + firstImageName: String, + secondImageName: String, + apiKey: String, + onSuccess: @escaping (CompareResponse) -> Void, + onError: @escaping (String) -> Void +) { + let expectation = XCTestExpectation(description: "PerseLite face compare.") + + // Get data from file image path from a resource. + guard let firstData: Data = getTempData(name: firstImageName) else { + onError(PerseLite.Error.INVALID_IMAGE_PATH) + expectation.fulfill() + return + } + + // Get data from file image path from a resource. + guard let secondData: Data = getTempData(name: secondImageName) else { + onError(PerseLite.Error.INVALID_IMAGE_PATH) + expectation.fulfill() + return + } + + // Start the face detect process. + PerseLite(apiKey: apiKey) + .face + .compare( + firstData, + secondData + ) { + compareResponse in + onSuccess(compareResponse) + expectation.fulfill() + return + } onError: { + error in + onError(error) + expectation.fulfill() + return + } + + // Wait until the expectation is fulfilled, with a timeout of 10 seconds. + xctest.wait(for: [expectation], timeout: 10.0) +} diff --git a/Example/PerseLiteDemo/Podfile b/Example/PerseLiteDemo/Podfile new file mode 100644 index 0000000..9b46417 --- /dev/null +++ b/Example/PerseLiteDemo/Podfile @@ -0,0 +1,24 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +target 'PerseLiteDemo' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + pod 'YoonitCamera' + + # Pods for PerseLiteDemo + pod 'PerseLite', :path => '../../' +end + +target 'PerseLiteDemoTests' do + pod 'PerseLite', :path => '../../' +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' + end + end +end diff --git a/Example/PerseLiteDemo/Podfile.lock b/Example/PerseLiteDemo/Podfile.lock new file mode 100644 index 0000000..e866d33 --- /dev/null +++ b/Example/PerseLiteDemo/Podfile.lock @@ -0,0 +1,111 @@ +PODS: + - Alamofire (5.4.3) + - GoogleDataTransport (8.4.0): + - GoogleUtilities/Environment (~> 7.2) + - nanopb (~> 2.30908.0) + - PromisesObjC (~> 1.2) + - GoogleMLKit/FaceDetection (2.1.0): + - GoogleMLKit/MLKitCore + - MLKitFaceDetection (~> 1.2.0) + - GoogleMLKit/MLKitCore (2.1.0): + - MLKitCommon (~> 2.1.0) + - GoogleToolboxForMac/DebugUtils (2.3.1): + - GoogleToolboxForMac/Defines (= 2.3.1) + - GoogleToolboxForMac/Defines (2.3.1) + - GoogleToolboxForMac/Logger (2.3.1): + - GoogleToolboxForMac/Defines (= 2.3.1) + - "GoogleToolboxForMac/NSData+zlib (2.3.1)": + - GoogleToolboxForMac/Defines (= 2.3.1) + - "GoogleToolboxForMac/NSDictionary+URLArguments (2.3.1)": + - GoogleToolboxForMac/DebugUtils (= 2.3.1) + - GoogleToolboxForMac/Defines (= 2.3.1) + - "GoogleToolboxForMac/NSString+URLArguments (= 2.3.1)" + - "GoogleToolboxForMac/NSString+URLArguments (2.3.1)" + - GoogleUtilities/Environment (7.4.1): + - PromisesObjC (~> 1.2) + - GoogleUtilities/Logger (7.4.1): + - GoogleUtilities/Environment + - GoogleUtilities/UserDefaults (7.4.1): + - GoogleUtilities/Logger + - GoogleUtilitiesComponents (1.0.0): + - GoogleUtilities/Logger + - GTMSessionFetcher/Core (1.5.0) + - MLKitCommon (2.1.0): + - GoogleDataTransport (~> 8.0) + - GoogleToolboxForMac/Logger (~> 2.1) + - "GoogleToolboxForMac/NSData+zlib (~> 2.1)" + - "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)" + - GoogleUtilities/UserDefaults (~> 7.0) + - GoogleUtilitiesComponents (~> 1.0) + - GTMSessionFetcher/Core (~> 1.1) + - Protobuf (~> 3.12) + - MLKitFaceDetection (1.2.0): + - MLKitCommon (~> 2.1) + - MLKitVision (~> 1.2) + - MLKitVision (1.2.0): + - GoogleToolboxForMac/Logger (~> 2.1) + - "GoogleToolboxForMac/NSData+zlib (~> 2.1)" + - GTMSessionFetcher/Core (~> 1.1) + - MLKitCommon (~> 2.1) + - Protobuf (~> 3.12) + - nanopb (2.30908.0): + - nanopb/decode (= 2.30908.0) + - nanopb/encode (= 2.30908.0) + - nanopb/decode (2.30908.0) + - nanopb/encode (2.30908.0) + - PerseLite (0.0.1): + - Alamofire (~> 5.2) + - PromisesObjC (1.2.12) + - Protobuf (3.17.0) + - YoonitCamera (2.7.0): + - YoonitFacefy + - YoonitFacefy (1.0.6): + - GoogleMLKit/FaceDetection + +DEPENDENCIES: + - PerseLite (from `../../`) + - YoonitCamera + +SPEC REPOS: + trunk: + - Alamofire + - GoogleDataTransport + - GoogleMLKit + - GoogleToolboxForMac + - GoogleUtilities + - GoogleUtilitiesComponents + - GTMSessionFetcher + - MLKitCommon + - MLKitFaceDetection + - MLKitVision + - nanopb + - PromisesObjC + - Protobuf + - YoonitCamera + - YoonitFacefy + +EXTERNAL SOURCES: + PerseLite: + :path: "../../" + +SPEC CHECKSUMS: + Alamofire: e447a2774a40c996748296fa2c55112fdbbc42f9 + GoogleDataTransport: cd9db2180fcecd8da1b561aea31e3e56cf834aa7 + GoogleMLKit: 6ca2a10de262ee1017b52ac045e8967884ace992 + GoogleToolboxForMac: 471e0c05d39506e50e6398f46fa9a12ae0efeff9 + GoogleUtilities: f8a43108b38a68eebe8b3540e1f4f2d28843ce20 + GoogleUtilitiesComponents: a69c0b3b369ba443e988141e75ef49d9010b1c80 + GTMSessionFetcher: b3503b20a988c4e20cc189aa798fd18220133f52 + MLKitCommon: 9ed187a042139d51c0d8bf6a8301bb20b375c576 + MLKitFaceDetection: 5b92261dd6e4205e3dab0df62537ac3f4e90e5db + MLKitVision: 51385878c9100024478971856510f9271ff555b5 + nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 + PerseLite: 4b56169aa2eb8717c4906b3fc600b3d73a1ba1fb + PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97 + Protobuf: 7327d4444215b5f18e560a97f879ff5503c4581c + YoonitCamera: b42e7405363e882952dc5575e74cc40ec088d989 + YoonitFacefy: 2a583328b5cfd74a2bea98e416038091a8b1ec55 + +PODFILE CHECKSUM: 4726b034427dbba0ff1f00a8b2456973664c2515 + +COCOAPODS: 1.10.1 diff --git a/Info.plist b/Info.plist new file mode 100644 index 0000000..7594bab --- /dev/null +++ b/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSApplicationCategoryType + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + diff --git a/PerseLite.podspec b/PerseLite.podspec new file mode 100644 index 0000000..85caf6b --- /dev/null +++ b/PerseLite.podspec @@ -0,0 +1,34 @@ +Pod::Spec.new do |spec| + + # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + + spec.name = "PerseLite" + spec.version = "0.0.1" + spec.summary = "Perse SDK Lite iOS" + spec.description = <<-DESC + "This SDK provides abstracts the communication with the Perse's API endpoints and also convert the response from json to a pre-defined responses." + DESC + + spec.homepage = "https://github.com/cyberlabsai/perse-sdk-lite-ios/" + + # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + + spec.license = "MIT" + + # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + + spec.author = { 'CyberLabs.AI' => 'contato@cyberlabs.ai' } + + # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + + spec.source = { :git => "https://github.com/cyberlabsai/perse-sdk-lite-ios.git", :tag => "#{spec.version}" } + + spec.ios.deployment_target = '10.0' + + + # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # + + spec.source_files = "PerseLite/src/**/*", "Classes", "Classes/**/*.{h,m,swift}" + spec.exclude_files = "Classes/Exclude" + spec.dependency 'Alamofire', '~> 5.2' +end diff --git a/PerseLite.xcodeproj/project.pbxproj b/PerseLite.xcodeproj/project.pbxproj new file mode 100644 index 0000000..0e9db19 --- /dev/null +++ b/PerseLite.xcodeproj/project.pbxproj @@ -0,0 +1,467 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 5C06E5CF25D1FED400E6770F /* PerseLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C06E5CE25D1FED400E6770F /* PerseLite.swift */; }; + 5C2FFC07266E5715000DD190 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 5C2FFC06266E5715000DD190 /* README.md */; }; + 5C5056FF260BA65A00286F91 /* PerseLite.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CB4F8EE25CC7DDC001D3606 /* PerseLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5C6BB2742641D99C007BB893 /* PerseLite.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 5C6BB2732641D99C007BB893 /* PerseLite.podspec */; }; + 5C6BB27E2641EC14007BB893 /* Podfile in Resources */ = {isa = PBXBuildFile; fileRef = 5C6BB27D2641EC14007BB893 /* Podfile */; }; + 5C6BB28E2642C9CF007BB893 /* Face.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6BB28D2642C9CF007BB893 /* Face.swift */; }; + 5C6CEE0B268B95D1008E0F2F /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 5C6CEE0A268B95D1008E0F2F /* LICENSE */; }; + 5C86E4ED268B5F4E00AD0B3B /* perseLite.gif in Resources */ = {isa = PBXBuildFile; fileRef = 5C86E4EC268B5F4E00AD0B3B /* perseLite.gif */; }; + 5CBA83CB264487D800A6E314 /* FaceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBA83CA264487D800A6E314 /* FaceResponse.swift */; }; + 5CE124C2264E108600D484A1 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE124C1264E108600D484A1 /* Util.swift */; }; + 6176EFA8252E476000F4D4DD /* Example in Resources */ = {isa = PBXBuildFile; fileRef = 6176EFA7252E476000F4D4DD /* Example */; }; + D6A5F631B2DAEAE767453F1F /* Pods_PerseLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC7DF46DA52525E48DF71642 /* Pods_PerseLite.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 4479E24251B210451180D9EC /* Pods-PerseLite.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PerseLite.debug.xcconfig"; path = "Target Support Files/Pods-PerseLite/Pods-PerseLite.debug.xcconfig"; sourceTree = ""; }; + 5C06E5CE25D1FED400E6770F /* PerseLite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerseLite.swift; sourceTree = ""; }; + 5C2FFC06266E5715000DD190 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 5C65787D25D32FB2001171F8 /* libGoogleDataTransport.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libGoogleDataTransport.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 5C65787F25D32FB2001171F8 /* libGoogleToolboxForMac.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libGoogleToolboxForMac.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 5C65788125D32FB2001171F8 /* libGoogleUtilities.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libGoogleUtilities.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 5C65788325D32FB2001171F8 /* libGoogleUtilitiesComponents.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libGoogleUtilitiesComponents.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 5C65788525D32FB2001171F8 /* libGTMSessionFetcher.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libGTMSessionFetcher.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 5C65788725D32FB2001171F8 /* libnanopb.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libnanopb.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 5C65788B25D32FB2001171F8 /* libPromisesObjC.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libPromisesObjC.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 5C65788D25D32FB2001171F8 /* libProtobuf.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libProtobuf.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 5C6BB2732641D99C007BB893 /* PerseLite.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PerseLite.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 5C6BB27D2641EC14007BB893 /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 5C6BB28D2642C9CF007BB893 /* Face.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Face.swift; sourceTree = ""; }; + 5C6CEE0A268B95D1008E0F2F /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 5C86E4EC268B5F4E00AD0B3B /* perseLite.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = perseLite.gif; sourceTree = ""; }; + 5CB4F8EE25CC7DDC001D3606 /* PerseLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PerseLite.h; sourceTree = ""; }; + 5CBA83CA264487D800A6E314 /* FaceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceResponse.swift; sourceTree = ""; }; + 5CE124C1264E108600D484A1 /* Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = ""; }; + 6176EEF6252CF9D200F4D4DD /* PerseLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PerseLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6176EEFA252CF9D200F4D4DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6176EFA7252E476000F4D4DD /* Example */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Example; sourceTree = ""; }; + BC7DF46DA52525E48DF71642 /* Pods_PerseLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PerseLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F2612947BF449DA18F290D5B /* Pods-PerseLite.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PerseLite.release.xcconfig"; path = "Target Support Files/Pods-PerseLite/Pods-PerseLite.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6176EEF3252CF9D200F4D4DD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D6A5F631B2DAEAE767453F1F /* Pods_PerseLite.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5C0C6435254208E400EDF95B /* src */ = { + isa = PBXGroup; + children = ( + 5CBA83C9264487A400A6E314 /* model */, + 5C06E5CE25D1FED400E6770F /* PerseLite.swift */, + 5C6BB28D2642C9CF007BB893 /* Face.swift */, + 5CE124C1264E108600D484A1 /* Util.swift */, + ); + path = src; + sourceTree = ""; + }; + 5CBA83C9264487A400A6E314 /* model */ = { + isa = PBXGroup; + children = ( + 5CBA83CA264487D800A6E314 /* FaceResponse.swift */, + ); + path = model; + sourceTree = ""; + }; + 6176EEEC252CF9D200F4D4DD = { + isa = PBXGroup; + children = ( + 5C6CEE0A268B95D1008E0F2F /* LICENSE */, + 6176EFA7252E476000F4D4DD /* Example */, + 6176EEFA252CF9D200F4D4DD /* Info.plist */, + 5C6BB2732641D99C007BB893 /* PerseLite.podspec */, + 5C6BB27D2641EC14007BB893 /* Podfile */, + 5C2FFC06266E5715000DD190 /* README.md */, + 5C86E4EC268B5F4E00AD0B3B /* perseLite.gif */, + 6176EEF8252CF9D200F4D4DD /* PerseLite */, + 6176EEF7252CF9D200F4D4DD /* Products */, + 85073D94B694632C1A966E28 /* Pods */, + A98BB533F39550100000CDED /* Frameworks */, + ); + sourceTree = ""; + }; + 6176EEF7252CF9D200F4D4DD /* Products */ = { + isa = PBXGroup; + children = ( + 6176EEF6252CF9D200F4D4DD /* PerseLite.framework */, + ); + name = Products; + sourceTree = ""; + }; + 6176EEF8252CF9D200F4D4DD /* PerseLite */ = { + isa = PBXGroup; + children = ( + 5C0C6435254208E400EDF95B /* src */, + 5CB4F8EE25CC7DDC001D3606 /* PerseLite.h */, + ); + path = PerseLite; + sourceTree = ""; + }; + 85073D94B694632C1A966E28 /* Pods */ = { + isa = PBXGroup; + children = ( + 4479E24251B210451180D9EC /* Pods-PerseLite.debug.xcconfig */, + F2612947BF449DA18F290D5B /* Pods-PerseLite.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + A98BB533F39550100000CDED /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5C65787D25D32FB2001171F8 /* libGoogleDataTransport.a */, + 5C65787F25D32FB2001171F8 /* libGoogleToolboxForMac.a */, + 5C65788125D32FB2001171F8 /* libGoogleUtilities.a */, + 5C65788325D32FB2001171F8 /* libGoogleUtilitiesComponents.a */, + 5C65788525D32FB2001171F8 /* libGTMSessionFetcher.a */, + 5C65788725D32FB2001171F8 /* libnanopb.a */, + 5C65788B25D32FB2001171F8 /* libPromisesObjC.a */, + 5C65788D25D32FB2001171F8 /* libProtobuf.a */, + BC7DF46DA52525E48DF71642 /* Pods_PerseLite.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 6176EEF1252CF9D200F4D4DD /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 5C5056FF260BA65A00286F91 /* PerseLite.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 6176EEF5252CF9D200F4D4DD /* PerseLite */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6176EEFE252CF9D200F4D4DD /* Build configuration list for PBXNativeTarget "PerseLite" */; + buildPhases = ( + 5DE69138D8911EDA745D78EE /* [CP] Check Pods Manifest.lock */, + 6176EEF1252CF9D200F4D4DD /* Headers */, + 6176EEF2252CF9D200F4D4DD /* Sources */, + 6176EEF3252CF9D200F4D4DD /* Frameworks */, + 6176EEF4252CF9D200F4D4DD /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PerseLite; + productName = PerseLite; + productReference = 6176EEF6252CF9D200F4D4DD /* PerseLite.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6176EEED252CF9D200F4D4DD /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1200; + TargetAttributes = { + 6176EEF5252CF9D200F4D4DD = { + CreatedOnToolsVersion = 12.0.1; + LastSwiftMigration = 1220; + }; + }; + }; + buildConfigurationList = 6176EEF0252CF9D200F4D4DD /* Build configuration list for PBXProject "PerseLite" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 6176EEEC252CF9D200F4D4DD; + productRefGroup = 6176EEF7252CF9D200F4D4DD /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 6176EEF5252CF9D200F4D4DD /* PerseLite */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 6176EEF4252CF9D200F4D4DD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5C6CEE0B268B95D1008E0F2F /* LICENSE in Resources */, + 5C6BB27E2641EC14007BB893 /* Podfile in Resources */, + 5C6BB2742641D99C007BB893 /* PerseLite.podspec in Resources */, + 5C86E4ED268B5F4E00AD0B3B /* perseLite.gif in Resources */, + 5C2FFC07266E5715000DD190 /* README.md in Resources */, + 6176EFA8252E476000F4D4DD /* Example in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 5DE69138D8911EDA745D78EE /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PerseLite-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6176EEF2252CF9D200F4D4DD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5C6BB28E2642C9CF007BB893 /* Face.swift in Sources */, + 5CBA83CB264487D800A6E314 /* FaceResponse.swift in Sources */, + 5C06E5CF25D1FED400E6770F /* PerseLite.swift in Sources */, + 5CE124C2264E108600D484A1 /* Util.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 6176EEFC252CF9D200F4D4DD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 6176EEFD252CF9D200F4D4DD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 6176EEFF252CF9D200F4D4DD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4479E24251B210451180D9EC /* Pods-PerseLite.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = ai.cyberlabs.perselite; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6176EF00252CF9D200F4D4DD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F2612947BF449DA18F290D5B /* Pods-PerseLite.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = ai.cyberlabs.perselite; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6176EEF0252CF9D200F4D4DD /* Build configuration list for PBXProject "PerseLite" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6176EEFC252CF9D200F4D4DD /* Debug */, + 6176EEFD252CF9D200F4D4DD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6176EEFE252CF9D200F4D4DD /* Build configuration list for PBXNativeTarget "PerseLite" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6176EEFF252CF9D200F4D4DD /* Debug */, + 6176EF00252CF9D200F4D4DD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 6176EEED252CF9D200F4D4DD /* Project object */; +} diff --git a/PerseLite.xcodeproj/xcshareddata/xcschemes/PerseLite.xcscheme b/PerseLite.xcodeproj/xcshareddata/xcschemes/PerseLite.xcscheme new file mode 100644 index 0000000..fd4e3ba --- /dev/null +++ b/PerseLite.xcodeproj/xcshareddata/xcschemes/PerseLite.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PerseLite/PerseLite.h b/PerseLite/PerseLite.h new file mode 100644 index 0000000..25d242c --- /dev/null +++ b/PerseLite/PerseLite.h @@ -0,0 +1,9 @@ +#import + +//! Project version number for PerseLite. +FOUNDATION_EXPORT double PerseLiteVersionNumber; + +//! Project version string for PerseLite. +FOUNDATION_EXPORT const unsigned char PerseLiteVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/PerseLite/src/Face.swift b/PerseLite/src/Face.swift new file mode 100644 index 0000000..1089508 --- /dev/null +++ b/PerseLite/src/Face.swift @@ -0,0 +1,153 @@ +import Foundation +import Alamofire + +public class Face { + + public func detect( + _ filePath: String, + onSuccess: @escaping (DetectResponse) -> Void, + onError: @escaping (String) -> Void + ) { + guard let url = URL(string: filePath) else { + onError(PerseLite.Error.INVALID_IMAGE_PATH) + return + } + guard let data = try? Data(contentsOf: url) else { + onError(PerseLite.Error.INVALID_IMAGE_PATH) + return + } + + self.detect(data, onSuccess: onSuccess, onError: onError) + } + + public func detect( + _ data: Data, + onSuccess: @escaping (DetectResponse) -> Void, + onError: @escaping (String) -> Void + ) { + let to = PerseLite.url.appending("face/detect") + let headers: HTTPHeaders = [ + "Content-Type": "multipart/form-data", + "x-api-key" : PerseLite.apiKey, + "Accept": "application/json" + ] + + AF.upload( + multipartFormData: { multipartFormData in + multipartFormData.append( + data, + withName: "image_file", + fileName: "image_file", + mimeType: "image/jpeg" + ) + }, + to: to, + method: .post, + headers: headers + ).responseString { + response in + + guard let uploadResponse: HTTPURLResponse = response.response else { + return + } + + if uploadResponse.statusCode == 200 { + do { + guard let detectResponse: DetectResponse = try response.data?.detectResponse() else { + return + } + onSuccess(detectResponse) + } catch let error { + onError("\(error)") + } + } else { + onError("\(uploadResponse.statusCode)") + } + } + } + + public func compare( + _ firstFilePath: String, + _ secondFilePath: String, + onSuccess: @escaping (CompareResponse) -> Void, + onError: @escaping (String) -> Void + ) { + guard let firstUrl = URL(string: firstFilePath) else { + onError(PerseLite.Error.INVALID_IMAGE_PATH) + return + } + guard let secondUrl = URL(string: secondFilePath) else { + onError(PerseLite.Error.INVALID_IMAGE_PATH) + return + } + guard let firstData = try? Data(contentsOf: firstUrl) else { + onError(PerseLite.Error.INVALID_IMAGE_PATH) + return + } + guard let secondData = try? Data(contentsOf: secondUrl) else { + onError(PerseLite.Error.INVALID_IMAGE_PATH) + return + } + + self.compare( + firstData, + secondData, + onSuccess: onSuccess, + onError: onError + ) + } + + public func compare( + _ firstFile: Data, + _ secondFile: Data, + onSuccess: @escaping (CompareResponse) -> Void, + onError: @escaping (String) -> Void + ) { + let to = PerseLite.url.appending("face/compare") + let headers: HTTPHeaders = [ + "Content-Type": "multipart/form-data", + "x-api-key" : PerseLite.apiKey, + "Accept": "application/json" + ] + + AF.upload( + multipartFormData: { multipartFormData in + multipartFormData.append( + firstFile, + withName: "image_file1", + fileName: "image_file1", + mimeType: "image/jpeg" + ) + multipartFormData.append( + secondFile, + withName: "image_file2", + fileName: "image_file2", + mimeType: "image/jpeg" + ) + }, + to: to, + method: .post, + headers: headers + ).responseString { + response in + + guard let uploadResponse: HTTPURLResponse = response.response else { + return + } + + if uploadResponse.statusCode == 200 { + do { + guard let compareResponse: CompareResponse = try response.data?.compareResponse() else { + return + } + onSuccess(compareResponse) + } catch let error { + onError("\(error)") + } + } else { + onError("\(uploadResponse.statusCode)") + } + } + } + +} diff --git a/PerseLite/src/PerseLite.swift b/PerseLite/src/PerseLite.swift new file mode 100644 index 0000000..a4f96d8 --- /dev/null +++ b/PerseLite/src/PerseLite.swift @@ -0,0 +1,17 @@ +import UIKit + +public class PerseLite { + + public struct Error { + public static let INVALID_IMAGE_PATH = "INVALID_IMAGE_PATH" + } + + static var apiKey: String! + static var url: String = "https://api.getperse.com/v0/" + + public var face = Face() + + public init(apiKey: String) { + PerseLite.apiKey = apiKey + } +} diff --git a/PerseLite/src/Util.swift b/PerseLite/src/Util.swift new file mode 100644 index 0000000..2ebc91b --- /dev/null +++ b/PerseLite/src/Util.swift @@ -0,0 +1,47 @@ +import Foundation +import Alamofire + +extension URL { + public func name() -> String { + return self + .deletingPathExtension() + .lastPathComponent + .appending(".") + .appending(self.pathExtension) + } +} + +extension Data { + + public func detectResponse() throws -> DetectResponse { + do { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + + let response: DetectResponse = try decoder.decode( + DetectResponse.self, + from: self + ) + + return response + } catch(let error) { + throw error + } + } + + public func compareResponse() throws -> CompareResponse { + do { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + + let response: CompareResponse = try decoder.decode( + CompareResponse.self, + from: self + ) + + return response + } catch(let error) { + throw error + } + } +} diff --git a/PerseLite/src/model/FaceResponse.swift b/PerseLite/src/model/FaceResponse.swift new file mode 100644 index 0000000..d86dc01 --- /dev/null +++ b/PerseLite/src/model/FaceResponse.swift @@ -0,0 +1,39 @@ +import Foundation + +public struct CompareResponse: Decodable { + public let status: Int + public let similarity: Float + public let imageTokens: Array + public let timeTaken: Float +} + +public struct LandmarksResponse: Decodable { + public let leftEye: Array + public let mouthLeft: Array + public let mouthRight: Array + public let nose: Array + public let rightEye: Array +} + +public struct MetricsResponse: Decodable { + public let overexpose: Float; + public let sharpness: Float; + public let underexpose: Float; +} + +public struct FaceResponse: Decodable { + public let boundingBox: Array + public let confidence: Int + public let faceMetrics: MetricsResponse + public let landmarks: LandmarksResponse + public let livenessScore: Float +} + +public struct DetectResponse: Decodable { + public let totalFaces: Int + public let faces: Array + public let imageMetrics: MetricsResponse + public let imageToken: String + public let status: Int + public let timeTaken: Float +} diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..7cac08a --- /dev/null +++ b/Podfile @@ -0,0 +1,10 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '10.0' + +target 'PerseLite' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for Perse + pod 'Alamofire', '~> 5.2' +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..e5f9e2c --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - Alamofire (5.4.3) + +DEPENDENCIES: + - Alamofire (~> 5.2) + +SPEC REPOS: + trunk: + - Alamofire + +SPEC CHECKSUMS: + Alamofire: e447a2774a40c996748296fa2c55112fdbbc42f9 + +PODFILE CHECKSUM: 704a795c36d0e65bc483c884848c143bdb459688 + +COCOAPODS: 1.10.1 diff --git a/README.md b/README.md index b7fae17..f57fa67 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,310 @@ -# perse-sdk-lite-ios -Ready to go biometric verification for the internet. + +# Perse SDK Lite iOS +From [CyberLabs.AI](https://cyberlabs.ai/). +_Ready to go biometric verification for the internet._ + +The Perse CocoaPods SDK Lite: +* Top notch facial detection model; +* Anti-spoofing; +* Feedback on image quality; +* Compare the similarity between two faces; +* Doesn't store any photos; + + + +For more details, you can see the [Official Perse](https://www.getperse.com/). + +> #### Soon voice biometric verification. + +## Content of Table + +* [About](#about) +* [Get Started](#get-started) + * [Install](#install) + * [Get API Key](#get-api-key) + * [Demo](#demo) +* [Usage](#usage) + * [Face Detect](#face-detect) + * [Face Compare](#face-compare) + * [Camera Integration](#camera-integration) +* [API](#api) + * [Methods](#methods) + * [face.detect](#face.detect) + * [face.compare](#face.compare) + * [Responses](#responses) + * [Errors](#errors) +* [To Contribute and Make It Better](#to-contribute-and-make-it-better) + +## About + +This SDK provides abstracts the communication with the Perse's API endpoints and also convert the response from json to a pre-defined [responses](#responses). + +> #### Want to test the endpoints? +> You can test our endpoints using this [Swagger UI Playground](https://api.getperse.com/swagger/). + +> #### Want to test a web live demo? +> You can test our web live demos in the [CyberLabs.AI CodePen](https://codepen.io/cyberlabsai) or in the [Perse Oficial Docs](https://docs.getperse.com/sdk-js/demo.html#authentication-demo +). Do not forget your [API Key](#get-api-key). + +> #### Want to try a backend client? +> We have some examples in `Python`, `Go` and `javaScript`. +> You can see documented [here](https://docs.getperse.com/face-api/#introduction). + +## Get Started + +### Install + +1. Create a [Podfile](https://guides.cocoapods.org/using/the-podfile.html). You may need install [CocoaPods](https://guides.cocoapods.org/using/getting-started.html#toc_3) in your environment. + +2. Add the following line to your `Podfile` file: + +``` +pod 'PerseLite' +``` + +3. And run in the project root the following command line: + +``` +pod install +``` + +4. Now, you can open and build your project with the extension `.xcworkspace`; + +> #### How to create a Xcode project with CocoaPods? +> TO create a Xcode project with CocoaPods you can see in the [Official CocoaPods Guide](https://guides.cocoapods.org/using/using-cocoapods.html#creating-a-new-xcode-project-with-cocoapods). + +### API Key + +Perse API authenticates your requests using an API Key. +We are currently in Alpha. So you can get your API Key: +1. Sending an email to [developer@getperse.com](mailto:%20developer@getperse.com); +2. Or in the Perse official site [https://www.getperse.com/](https://www.getperse.com/); + +### Demo + +We have a [Demo](/Example/PerseLiteDemo) in this repository for you: +* Feel free to change the Demo code; +* Not forget to get your [API KEY](#api-key); +* To run, the Demo, it is necessary to follow the [Install](#install) steps; + +## Usage + +### Face Detect + +Detect allows you process images with the intent of detecting human faces. + +```swift +import PerseLite + +func detect(_ file: String) { + let perseLite = PerseLite(apiKey: "API_KEY") + + perseLite.face.detect(filePath) { + detectResponse in + debugPrint(detectResponse) + } onError: { + error in + debugPrint(error) + } +} +``` + +### Face Compare + +Compare accepts two sources for similarity comparison. + +```swift +import PerseLite + +func compare( + _ firstFile: String, + _ secondFile: String +) { + let perseLite = PerseLite(apiKey: "API_KEY") + + perseLite.face.compare( + firstFilePath, + secondFilePath + ) { + compareResponse in + debugPrint(compareResponse) + } onError: { + error in + debugPrint(error) + } +} +``` + +### Camera Integration + +Here we have a example in code how to detect a face from a camera. See the complete code in the [Demo](/Example/PerseLiteDemo) application. +Just follow the Installation in the [`Yoonit Camera`](https://github.com/Yoonit-Labs/ios-yoonit-camera). + +```swift +import UIKit +import PerseLite +import YoonitCamera + +class PerseLiteCameraViewController: + UIViewController, + CameraEventListenerDelegate +{ + override func viewDidLoad() { + super.viewDidLoad() + + self.cameraView.cameraEventListener = self + self.cameraView.startPreview() + self.cameraView.setSaveImageCaptured(true) + self.cameraView.setTimeBetweenImages(300) + self.cameraView.startCaptureType("frame") + } + + func onImageCaptured( + _ type: String, + _ count: Int, + _ total: Int, + _ imagePath: String, + _ darkness: NSNumber?, + _ lightness: NSNumber?, + _ sharpness: NSNumber? + ) { + self.perseLite.face.detect(imagePath) { + detectResponse in + debugPrint(detectResponse) + } onError: { + error in + debugPrint(error) + } + } + + ... +} +``` + +## API + +This section describes the Perse SDK Lite iOS API's, [methods](#methods), your [responses](#responses) and possible [errors](#errors). + +### Methods + +The Perse is in `alpha` version and for now, only the `Face` module is available. + +#### face.detect + +* Has the intent of detecting any number of human faces; +* Can use this resource to evaluate the overall quality of the image; +* The input can be the image file path or his [Data](https://developer.apple.com/documentation/foundation/data); +* The `onSuccess` return type is [DetectResponse](#detectresponse) struct; +* The `onError` return type can see in the [Errors](#errors); + +```swift +func detect( + _ filePath: String, + onSuccess: @escaping (DetectResponse) -> Void, + onError: @escaping (String) -> Void +) +``` + +```swift +func detect( + _ data: Data, + onSuccess: @escaping (DetectResponse) -> Void, + onError: @escaping (String) -> Void +) +``` + +#### face.compare + +* Accepts two sources for similarity comparison; +* The inputs can be the image file paths or his [Data's](https://developer.apple.com/documentation/foundation/data); +* The `onSuccess` return type is [CompareResponse](#compareresponse) struct; +* The `onError` return type can see in the [Errors](#errors); + +```swift +func compare( + _ firstFilePath: String, + _ secondFilePath: String, + onSuccess: @escaping (CompareResponse) -> Void, + onError: @escaping (String) -> Void +) +``` + +```swift +func compare( + _ firstFile: Data, + _ secondFile: Data, + onSuccess: @escaping (CompareResponse) -> Void, + onError: @escaping (String) -> Void +) +``` + +> #### Tip +> We recommend considering a match when similarity is above `71`. + +### Responses + +#### CompareResponse + +| Attribute | Type | Description +| - | - | - +| similarity | `Float` | Similarity between faces. Closer to `1` is better. +| imageTokens | `Array` | The image tokens array. +| timeTaken | `Float` | Time taken to analyze the image. + +#### DetectResponse + +| Attribute | Type | Description +| - | - | - +| totalFaces | `Int` | Total of faces in the image. +| faces | `Array` | Array of [FaceResponse](#faceresponse). +| imageMetrics | [MetricsResponse](#metricsresponse) | Metrics of the detected image. +| imageToken | `String` | The image token. +| timeTaken | `Float` | Time taken to analyze the image. + +#### FaceResponse + +| Attribute | Type | Description +| - | - | - +| landmarks | [LandmarksResponse](#landmarksresponse) | Detected face landmarks. +| confidence | `Int` | Confidence that the face is a real face. +| boundingBox | `Array` | Array with the four values of the face bounding box. The coordinates `x`, `y` and the dimension `width` and `height` respectively. +| faceMetrics | [MetricsResponse](#metricsresponse) | Metrics of the detecting face. +| livenessScore | `Long` | Confidence that a detected face is from a live person (1 means higher confidence). + +#### MetricsResponse + +| Attribute | Type | Description +| - | - | - +| underexpose | `Float` | Indicates loss of shadow detail. Closer to `0` is better. +| overexpose | `Float` | Indicates loss of highlight detail. Closer to `0` is better. +| sharpness | `Float` | Indicates intensity of motion blur. Closer to `1` is better. + +#### LandmarksResponse + +| Attribute | Type | Description +| - | - | - +| rightEye | `Array` | Right eye landmarks. +| leftEye | `Array` | Left eye landmarks. +| nose | `Array` | Nose landmarks. +| mouthRight | `Array` | Right side of mouth landmarks. +| mouthLeft | `Array` | Left side of mouth landmarks. + +### Errors + +| Error Code | Description +| - | - +| 400 | The request was unacceptable, often due to missing a required parameter. +| 401 | API key is missing or invalid. +| 402 | The parameters were valid but the request failed. +| 415 | The content type or encoding is not valid. + +## To Contribute and Make It Better + +Clone the repo, change what you want and send PR. +For commit messages we use Conventional Commits. + +Contributions are always welcome! + +--- + +Made with ❤ by the [**Cyberlabs AI**](https://cyberlabs.ai/) diff --git a/perseLite.gif b/perseLite.gif new file mode 100644 index 0000000..1bae6fa Binary files /dev/null and b/perseLite.gif differ