diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal.xcodeproj/project.pbxproj b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal.xcodeproj/project.pbxproj index fb51907..1228aff 100644 --- a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal.xcodeproj/project.pbxproj +++ b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal.xcodeproj/project.pbxproj @@ -7,19 +7,33 @@ objects = { /* Begin PBXBuildFile section */ + 492EFF2688481D0773EA77DA /* libPods-HelloWorld-Metal.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C8FA3F26EDC2BFF1CFF70E63 /* libPods-HelloWorld-Metal.a */; }; 8BA1BEFC26C1C13C008AD5B2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BA1BEFB26C1C13C008AD5B2 /* AppDelegate.swift */; }; 8BA1BF0026C1C13C008AD5B2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BA1BEFF26C1C13C008AD5B2 /* ViewController.swift */; }; 8BA1BF0326C1C13C008AD5B2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8BA1BF0126C1C13C008AD5B2 /* Main.storyboard */; }; 8BA1BF0526C1C13D008AD5B2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8BA1BF0426C1C13D008AD5B2 /* Assets.xcassets */; }; 8BA1BF0826C1C13D008AD5B2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8BA1BF0626C1C13D008AD5B2 /* LaunchScreen.storyboard */; }; - 8BA1BF1026C1C2CC008AD5B2 /* image.png in Resources */ = {isa = PBXBuildFile; fileRef = 8BA1BF0F26C1C2CC008AD5B2 /* image.png */; }; + 8BA1BF1026C1C2CC008AD5B2 /* wolf.png in Resources */ = {isa = PBXBuildFile; fileRef = 8BA1BF0F26C1C2CC008AD5B2 /* wolf.png */; }; 8BA1BF1726C1C305008AD5B2 /* words.txt in Resources */ = {isa = PBXBuildFile; fileRef = 8BA1BF1526C1C305008AD5B2 /* words.txt */; }; 8BA1BF1E26C1C356008AD5B2 /* UIImage+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BA1BF1D26C1C356008AD5B2 /* UIImage+Helper.swift */; }; 8BA1BF2926C1C885008AD5B2 /* TorchModule.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8BA1BF2726C1C885008AD5B2 /* TorchModule.mm */; }; 8BA1BF4B26C1DDCD008AD5B2 /* model.pt in Resources */ = {isa = PBXBuildFile; fileRef = 8BA1BF4A26C1DDCD008AD5B2 /* model.pt */; }; + BDCC15C627578D00005A979D /* animal-3.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15BA27578CFF005A979D /* animal-3.jpg */; }; + BDCC15C727578D00005A979D /* food-1.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15BB27578CFF005A979D /* food-1.jpg */; }; + BDCC15C827578D00005A979D /* car-3.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15BC27578CFF005A979D /* car-3.jpg */; }; + BDCC15C927578D00005A979D /* girl-1.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15BD27578CFF005A979D /* girl-1.jpg */; }; + BDCC15CA27578D00005A979D /* girl-2.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15BE27578CFF005A979D /* girl-2.jpg */; }; + BDCC15CC27578D00005A979D /* car-1.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15C027578CFF005A979D /* car-1.jpg */; }; + BDCC15CD27578D00005A979D /* car-2.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15C127578D00005A979D /* car-2.jpg */; }; + BDCC15CE27578D00005A979D /* animal-2.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15C227578D00005A979D /* animal-2.jpg */; }; + BDCC15CF27578D00005A979D /* kitten_small.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15C327578D00005A979D /* kitten_small.jpg */; }; + BDCC15D027578D00005A979D /* girl-3.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15C427578D00005A979D /* girl-3.jpg */; }; + BDCC15D127578D00005A979D /* animal-1.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15C527578D00005A979D /* animal-1.jpg */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 2F88BD040F1A45FD07B00711 /* Pods-HelloWorld-Metal.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HelloWorld-Metal.release.xcconfig"; path = "Target Support Files/Pods-HelloWorld-Metal/Pods-HelloWorld-Metal.release.xcconfig"; sourceTree = ""; }; + 5D06AFB6753649843FB01282 /* Pods-HelloWorld-Metal.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HelloWorld-Metal.debug.xcconfig"; path = "Target Support Files/Pods-HelloWorld-Metal/Pods-HelloWorld-Metal.debug.xcconfig"; sourceTree = ""; }; 8BA1BEF826C1C13C008AD5B2 /* HelloWorld-Metal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "HelloWorld-Metal.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 8BA1BEFB26C1C13C008AD5B2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 8BA1BEFF26C1C13C008AD5B2 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -27,13 +41,25 @@ 8BA1BF0426C1C13D008AD5B2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 8BA1BF0726C1C13D008AD5B2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 8BA1BF0926C1C13D008AD5B2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 8BA1BF0F26C1C2CC008AD5B2 /* image.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = image.png; sourceTree = ""; }; + 8BA1BF0F26C1C2CC008AD5B2 /* wolf.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wolf.png; sourceTree = ""; }; 8BA1BF1526C1C305008AD5B2 /* words.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = words.txt; sourceTree = ""; }; 8BA1BF1D26C1C356008AD5B2 /* UIImage+Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Helper.swift"; sourceTree = ""; }; 8BA1BF2126C1C861008AD5B2 /* HelloWorld-Metal-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "HelloWorld-Metal-Bridging-Header.h"; sourceTree = ""; }; 8BA1BF2726C1C885008AD5B2 /* TorchModule.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TorchModule.mm; sourceTree = ""; }; 8BA1BF2826C1C885008AD5B2 /* TorchModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TorchModule.h; sourceTree = ""; }; 8BA1BF4A26C1DDCD008AD5B2 /* model.pt */ = {isa = PBXFileReference; lastKnownFileType = file; path = model.pt; sourceTree = ""; }; + BDCC15BA27578CFF005A979D /* animal-3.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "animal-3.jpg"; sourceTree = ""; }; + BDCC15BB27578CFF005A979D /* food-1.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "food-1.jpg"; sourceTree = ""; }; + BDCC15BC27578CFF005A979D /* car-3.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "car-3.jpg"; sourceTree = ""; }; + BDCC15BD27578CFF005A979D /* girl-1.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "girl-1.jpg"; sourceTree = ""; }; + BDCC15BE27578CFF005A979D /* girl-2.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "girl-2.jpg"; sourceTree = ""; }; + BDCC15C027578CFF005A979D /* car-1.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "car-1.jpg"; sourceTree = ""; }; + BDCC15C127578D00005A979D /* car-2.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "car-2.jpg"; sourceTree = ""; }; + BDCC15C227578D00005A979D /* animal-2.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "animal-2.jpg"; sourceTree = ""; }; + BDCC15C327578D00005A979D /* kitten_small.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = kitten_small.jpg; sourceTree = ""; }; + BDCC15C427578D00005A979D /* girl-3.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "girl-3.jpg"; sourceTree = ""; }; + BDCC15C527578D00005A979D /* animal-1.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "animal-1.jpg"; sourceTree = ""; }; + C8FA3F26EDC2BFF1CFF70E63 /* libPods-HelloWorld-Metal.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-HelloWorld-Metal.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -41,17 +67,29 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 492EFF2688481D0773EA77DA /* libPods-HelloWorld-Metal.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 6934F693EDBC7C05977F1E39 /* Pods */ = { + isa = PBXGroup; + children = ( + 5D06AFB6753649843FB01282 /* Pods-HelloWorld-Metal.debug.xcconfig */, + 2F88BD040F1A45FD07B00711 /* Pods-HelloWorld-Metal.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; 8BA1BEEF26C1C13C008AD5B2 = { isa = PBXGroup; children = ( 8BA1BEFA26C1C13C008AD5B2 /* HelloWorld-Metal */, 8BA1BEF926C1C13C008AD5B2 /* Products */, + 6934F693EDBC7C05977F1E39 /* Pods */, + D598EFAF0D884557076C0509 /* Frameworks */, ); sourceTree = ""; }; @@ -66,7 +104,7 @@ 8BA1BEFA26C1C13C008AD5B2 /* HelloWorld-Metal */ = { isa = PBXGroup; children = ( - 8BA1BF0F26C1C2CC008AD5B2 /* image.png */, + BDCC15B927578C5D005A979D /* images */, 8BA1BF1326C1C305008AD5B2 /* model */, 8BA1BF2526C1C885008AD5B2 /* TorchBridge */, 8BA1BEFB26C1C13C008AD5B2 /* AppDelegate.swift */, @@ -99,6 +137,33 @@ path = TorchBridge; sourceTree = ""; }; + BDCC15B927578C5D005A979D /* images */ = { + isa = PBXGroup; + children = ( + 8BA1BF0F26C1C2CC008AD5B2 /* wolf.png */, + BDCC15C527578D00005A979D /* animal-1.jpg */, + BDCC15C227578D00005A979D /* animal-2.jpg */, + BDCC15BA27578CFF005A979D /* animal-3.jpg */, + BDCC15C027578CFF005A979D /* car-1.jpg */, + BDCC15C127578D00005A979D /* car-2.jpg */, + BDCC15BC27578CFF005A979D /* car-3.jpg */, + BDCC15BB27578CFF005A979D /* food-1.jpg */, + BDCC15BD27578CFF005A979D /* girl-1.jpg */, + BDCC15BE27578CFF005A979D /* girl-2.jpg */, + BDCC15C427578D00005A979D /* girl-3.jpg */, + BDCC15C327578D00005A979D /* kitten_small.jpg */, + ); + path = images; + sourceTree = ""; + }; + D598EFAF0D884557076C0509 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C8FA3F26EDC2BFF1CFF70E63 /* libPods-HelloWorld-Metal.a */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -106,6 +171,7 @@ isa = PBXNativeTarget; buildConfigurationList = 8BA1BF0C26C1C13D008AD5B2 /* Build configuration list for PBXNativeTarget "HelloWorld-Metal" */; buildPhases = ( + DD3150C9787B8E05005715ED /* [CP] Check Pods Manifest.lock */, 8BA1BEF426C1C13C008AD5B2 /* Sources */, 8BA1BEF526C1C13C008AD5B2 /* Frameworks */, 8BA1BEF626C1C13C008AD5B2 /* Resources */, @@ -159,16 +225,52 @@ buildActionMask = 2147483647; files = ( 8BA1BF0826C1C13D008AD5B2 /* LaunchScreen.storyboard in Resources */, + BDCC15C927578D00005A979D /* girl-1.jpg in Resources */, 8BA1BF0526C1C13D008AD5B2 /* Assets.xcassets in Resources */, + BDCC15CA27578D00005A979D /* girl-2.jpg in Resources */, + BDCC15CC27578D00005A979D /* car-1.jpg in Resources */, + BDCC15D127578D00005A979D /* animal-1.jpg in Resources */, 8BA1BF0326C1C13C008AD5B2 /* Main.storyboard in Resources */, + BDCC15CF27578D00005A979D /* kitten_small.jpg in Resources */, + BDCC15C627578D00005A979D /* animal-3.jpg in Resources */, + BDCC15D027578D00005A979D /* girl-3.jpg in Resources */, 8BA1BF1726C1C305008AD5B2 /* words.txt in Resources */, - 8BA1BF1026C1C2CC008AD5B2 /* image.png in Resources */, + BDCC15CE27578D00005A979D /* animal-2.jpg in Resources */, + 8BA1BF1026C1C2CC008AD5B2 /* wolf.png in Resources */, 8BA1BF4B26C1DDCD008AD5B2 /* model.pt in Resources */, + BDCC15CD27578D00005A979D /* car-2.jpg in Resources */, + BDCC15C827578D00005A979D /* car-3.jpg in Resources */, + BDCC15C727578D00005A979D /* food-1.jpg in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + DD3150C9787B8E05005715ED /* [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-HelloWorld-Metal-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 */ 8BA1BEF426C1C13C008AD5B2 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -321,15 +423,17 @@ }; 8BA1BF0D26C1C13D008AD5B2 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 5D06AFB6753649843FB01282 /* Pods-HelloWorld-Metal.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Manual; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = UP6SS5ES7E; ENABLE_BITCODE = NO; INFOPLIST_FILE = "HelloWorld-Metal/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -346,15 +450,17 @@ }; 8BA1BF0E26C1C13D008AD5B2 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 2F88BD040F1A45FD07B00711 /* Pods-HelloWorld-Metal.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Manual; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = UP6SS5ES7E; ENABLE_BITCODE = NO; INFOPLIST_FILE = "HelloWorld-Metal/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/Base.lproj/Main.storyboard b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/Base.lproj/Main.storyboard index 52e0d13..d42fc05 100644 --- a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/Base.lproj/Main.storyboard +++ b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -28,7 +28,7 @@ - + @@ -39,40 +39,92 @@ - - + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + - + + - - + + + + + - + + + - + + - + - - + + diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/TorchBridge/TorchModule.h b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/TorchBridge/TorchModule.h index 883120f..c19c891 100644 --- a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/TorchBridge/TorchModule.h +++ b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/TorchBridge/TorchModule.h @@ -4,11 +4,11 @@ NS_ASSUME_NONNULL_BEGIN @interface TorchModule : NSObject -- (nullable instancetype)initWithFileAtPath:(NSString*)filePath - NS_SWIFT_NAME(init(fileAtPath:))NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithFileAtPath:(NSString*)filePath width:(long)width height:(long)height + NS_SWIFT_NAME(init(fileAtPath:width:height:))NS_DESIGNATED_INITIALIZER; + (instancetype)new NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; -- (nullable NSArray*)predictImage:(void*)imageBuffer NS_SWIFT_NAME(predict(image:)); +- (nullable float*)predictImage:(void*)imageBuffer NS_SWIFT_NAME(predict(image:)); @end diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/TorchBridge/TorchModule.mm b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/TorchBridge/TorchModule.mm index 81ef769..5e86f11 100644 --- a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/TorchBridge/TorchModule.mm +++ b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/TorchBridge/TorchModule.mm @@ -3,14 +3,20 @@ @implementation TorchModule { @protected - torch::jit::mobile::Module _impl; + torch::jit::mobile::Module _model; + at::Tensor _output; + int64_t _inputSize[4]; } -- (nullable instancetype)initWithFileAtPath:(NSString*)filePath { +- (nullable instancetype)initWithFileAtPath:(NSString*)filePath width:(long)width height:(long)height { self = [super init]; if (self) { + _inputSize[0] = 1; + _inputSize[1] = 3; + _inputSize[2] = width; + _inputSize[3] = height; try { - _impl = torch::jit::_load_for_mobile(filePath.UTF8String); + _model = torch::jit::_load_for_mobile(filePath.UTF8String); } catch (const std::exception& exception) { NSLog(@"%s", exception.what()); return nil; @@ -19,20 +25,12 @@ - (nullable instancetype)initWithFileAtPath:(NSString*)filePath { return self; } -- (NSArray*)predictImage:(void*)imageBuffer { +- (float*)predictImage:(void*)imageBuffer { try { - c10::InferenceMode mode; - at::Tensor tensor = torch::from_blob(imageBuffer, {1, 3, 224, 224}, at::kFloat).metal(); - auto outputTensor = _impl.forward({tensor}).toTensor().cpu(); - float* floatBuffer = outputTensor.data_ptr(); - if (!floatBuffer) { - return nil; - } - NSMutableArray* results = [[NSMutableArray alloc] init]; - for (int i = 0; i < 1000; i++) { - [results addObject:@(floatBuffer[i])]; - } - return [results copy]; + c10::InferenceMode guard(true); + at::Tensor tensor = torch::from_blob(imageBuffer, _inputSize, at::kFloat).metal(); + _output = _model.forward({tensor}).toTensor().cpu(); + return _output.data_ptr(); } catch (const std::exception& exception) { NSLog(@"%s", exception.what()); } diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/UIImage+Helper.swift b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/UIImage+Helper.swift index d8bab86..c4dbd86 100755 --- a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/UIImage+Helper.swift +++ b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/UIImage+Helper.swift @@ -1,47 +1,44 @@ import UIKit extension UIImage { - func resized(to newSize: CGSize, scale: CGFloat = 1) -> UIImage { - let format = UIGraphicsImageRendererFormat.default() - format.scale = scale - let renderer = UIGraphicsImageRenderer(size: newSize, format: format) - let image = renderer.image { _ in - draw(in: CGRect(origin: .zero, size: newSize)) - } - return image + func resized(to newSize: CGSize, scale: CGFloat = 1) -> UIImage { + let format = UIGraphicsImageRendererFormat.default() + format.scale = scale + let renderer = UIGraphicsImageRenderer(size: newSize, format: format) + return renderer.image { _ in draw(in: CGRect(origin: .zero, size: newSize)) } + } + + func normalized() -> [Float32]? { + guard let cgImage = self.cgImage else { return nil } + let w = cgImage.width + let h = cgImage.height + let bytesPerPixel = 4 + let bytesPerRow = bytesPerPixel * w + let bitsPerComponent = 8 + + // Render image into our buffer so we have access to pixel bytes + var rawBytes: [UInt8] = [UInt8](repeating: 0, count: w * h * 4) + rawBytes.withUnsafeMutableBytes { ptr in + if let context = CGContext(data: ptr.baseAddress, + width: w, + height: h, + bitsPerComponent: bitsPerComponent, + bytesPerRow: bytesPerRow, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) { + let rect = CGRect(x: 0, y: 0, width: w, height: h) + context.draw(cgImage, in: rect) + } } - func normalized() -> [Float32]? { - guard let cgImage = self.cgImage else { - return nil - } - let w = cgImage.width - let h = cgImage.height - let bytesPerPixel = 4 - let bytesPerRow = bytesPerPixel * w - let bitsPerComponent = 8 - var rawBytes: [UInt8] = [UInt8](repeating: 0, count: w * h * 4) - rawBytes.withUnsafeMutableBytes { ptr in - if let cgImage = self.cgImage, - let context = CGContext(data: ptr.baseAddress, - width: w, - height: h, - bitsPerComponent: bitsPerComponent, - bytesPerRow: bytesPerRow, - space: CGColorSpaceCreateDeviceRGB(), - bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) { - let rect = CGRect(x: 0, y: 0, width: w, height: h) - context.draw(cgImage, in: rect) - } - } - var normalizedBuffer: [Float32] = [Float32](repeating: 0, count: w * h * 3) - // normalize the pixel buffer - // see https://pytorch.org/hub/pytorch_vision_resnet/ for more detail - for i in 0 ..< w * h { - normalizedBuffer[i] = (Float32(rawBytes[i * 4 + 0]) / 255.0 - 0.485) / 0.229 // R - normalizedBuffer[w * h + i] = (Float32(rawBytes[i * 4 + 1]) / 255.0 - 0.456) / 0.224 // G - normalizedBuffer[w * h * 2 + i] = (Float32(rawBytes[i * 4 + 2]) / 255.0 - 0.406) / 0.225 // B - } - return normalizedBuffer + var normalizedBuffer: [Float32] = [Float32](repeating: 0, count: w * h * 3) + // normalize the pixel buffer + // see https://pytorch.org/hub/pytorch_vision_resnet/ for more detail + for index in 0 ..< w * h { + normalizedBuffer[index] = (Float32(rawBytes[index * 4 + 0]) / 255.0 - 0.485) / 0.229 // R + normalizedBuffer[w * h + index] = (Float32(rawBytes[index * 4 + 1]) / 255.0 - 0.456) / 0.224 // G + normalizedBuffer[w * h * 2 + index] = (Float32(rawBytes[index * 4 + 2]) / 255.0 - 0.406) / 0.225 // B } + return normalizedBuffer + } } diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/ViewController.swift b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/ViewController.swift index 96894eb..d4b8fff 100644 --- a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/ViewController.swift +++ b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/ViewController.swift @@ -1,43 +1,124 @@ import UIKit -class ViewController: UIViewController { - @IBOutlet var imageView: UIImageView! - @IBOutlet var resultView: UITextView! - private lazy var module: TorchModule = { - if let filePath = Bundle.main.path(forResource: "model", ofType: "pt"), - let module = TorchModule(fileAtPath: filePath) { - return module - } else { - fatalError("Can't find the model file!") - } - }() - - private lazy var labels: [String] = { - if let filePath = Bundle.main.path(forResource: "words", ofType: "txt"), - let labels = try? String(contentsOfFile: filePath) { - return labels.components(separatedBy: .newlines) - } else { - fatalError("Can't find the text file!") - } - }() - - override func viewDidLoad() { - super.viewDidLoad() - let image = UIImage(named: "image.png")! - imageView.image = image - let resizedImage = image.resized(to: CGSize(width: 224, height: 224)) - guard var pixelBuffer = resizedImage.normalized() else { - return - } - guard let outputs = module.predict(image: UnsafeMutableRawPointer(&pixelBuffer)) else { - return - } - let zippedResults = zip(labels.indices, outputs) - let sortedResults = zippedResults.sorted { $0.1.floatValue > $1.1.floatValue }.prefix(3) - var text = "" - for result in sortedResults { - text += "\u{2022} \(labels[result.0]) \n\n" +struct Prediction { + let confidence: Float + let label: String +} + +class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { + @IBOutlet weak var imageView: UIImageView! + @IBOutlet weak var predictButton: UIButton! + @IBOutlet weak var imageStepper: UIStepper! + @IBOutlet weak var durationLabel: UILabel! + @IBOutlet weak var resultsView: UITableView! + + var predictions = [Prediction]() + + let width: Int = 224 + let height: Int = 224 + + private lazy var module: TorchModule = { + guard let filePath = Bundle.main.path(forResource: "model", ofType: "pt"), + let module = TorchModule(fileAtPath: filePath, width: width, height: height) + else { + fatalError("Can't find the model file!") + } + return module + }() + + private lazy var labels: [Substring] = { + guard let filePath = Bundle.main.path(forResource: "words", ofType: "txt"), + let labels = try? String(contentsOfFile: filePath) + else { + fatalError("Can't find the text file!") + } + return labels.split(whereSeparator: \.isNewline) + }() + + private lazy var images: [UIImage?] = { + let imagePaths = Bundle.main.paths(forResourcesOfType: "jpg", inDirectory: nil) + + Bundle.main.paths(forResourcesOfType: "png", inDirectory: nil) + print(imagePaths) + return imagePaths.map { UIImage(named: $0) } + }() + + private var activeImage: UIImage? { images[Int(imageStepper.value)] } + + override func viewDidLoad() { + super.viewDidLoad() + resultsView.dataSource = self + imageStepper.value = 0 + imageStepper.minimumValue = 0 + imageStepper.maximumValue = Double(images.count - 1) + updateImageView() + doPrediction() + } + + func updateImageView() { + imageView.image = images[Int(imageStepper.value)] + } + + private var imageFloats: [Float32]? { + guard let resizedImage = activeImage?.resized(to: CGSize(width: width, height: height)) else { return nil } + return resizedImage.normalized() + } + + func doPrediction() { + guard var input = self.imageFloats else { return } + predictions.removeAll() + resultsView.reloadData() + + input.withUnsafeMutableBufferPointer { ptr in + let startTime: DispatchTime = .now() + if let output = module.predict(image: &(ptr.baseAddress!.pointee)) { + let duration = TimeInterval(dispatchTimeInterval: startTime.distance(to: .now()))! + durationLabel.text = String(format: "%.3f seconds", duration) + + let bufferPointer = UnsafeBufferPointer(start: output, count: labels.count) + let indexedResults = bufferPointer.enumerated() + let top3 = indexedResults.sorted { $0.1 > $1.1 }.prefix(4) + + for result in top3 { + predictions.append(Prediction(confidence: result.1, label: String(labels[result.0]))) } - resultView.text = text + resultsView.reloadData() + } + } + } + + @IBAction func changeImage(_ sender: Any) { + updateImageView() + doPrediction() + } + + @IBAction func runPredict(_ sender: Any) { + doPrediction() + } +} + +extension ViewController { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + predictions.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "result", for: indexPath) + cell.textLabel?.text = predictions[indexPath.row].label + cell.detailTextLabel?.text = String(format: "%.2f", predictions[indexPath.row].confidence) + return cell + } +} + +extension TimeInterval { + + init?(dispatchTimeInterval: DispatchTimeInterval) { + switch dispatchTimeInterval { + case .seconds(let value): self = Double(value) + case .milliseconds(let value): self = Double(value) / 1_000 + case .microseconds(let value): self = Double(value) / 1_000_000 + case .nanoseconds(let value): self = Double(value) / 1_000_000_000 + case .never: return nil + @unknown default: return nil } + } } diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/animal-1.jpg b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/animal-1.jpg new file mode 100644 index 0000000..790afbc Binary files /dev/null and b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/animal-1.jpg differ diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/animal-2.jpg b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/animal-2.jpg new file mode 100644 index 0000000..3426175 Binary files /dev/null and b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/animal-2.jpg differ diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/animal-3.jpg b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/animal-3.jpg new file mode 100644 index 0000000..7b2bd05 Binary files /dev/null and b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/animal-3.jpg differ diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/car-1.jpg b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/car-1.jpg new file mode 100644 index 0000000..c6dc1e6 Binary files /dev/null and b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/car-1.jpg differ diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/car-2.jpg b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/car-2.jpg new file mode 100644 index 0000000..b62d31d Binary files /dev/null and b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/car-2.jpg differ diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/car-3.jpg b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/car-3.jpg new file mode 100644 index 0000000..4561529 Binary files /dev/null and b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/car-3.jpg differ diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/food-1.jpg b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/food-1.jpg new file mode 100644 index 0000000..2fb4e06 Binary files /dev/null and b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/food-1.jpg differ diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/girl-1.jpg b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/girl-1.jpg new file mode 100644 index 0000000..4fa80b6 Binary files /dev/null and b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/girl-1.jpg differ diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/girl-2.jpg b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/girl-2.jpg new file mode 100644 index 0000000..6f0fc52 Binary files /dev/null and b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/girl-2.jpg differ diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/girl-3.jpg b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/girl-3.jpg new file mode 100644 index 0000000..00c4ae0 Binary files /dev/null and b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/girl-3.jpg differ diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/kitten_small.jpg b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/kitten_small.jpg new file mode 100644 index 0000000..1592710 Binary files /dev/null and b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/kitten_small.jpg differ diff --git a/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/image.png b/HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/wolf.png similarity index 100% rename from HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/image.png rename to HelloWorld-Metal/HelloWorld-Metal/HelloWorld-Metal/images/wolf.png diff --git a/HelloWorld/HelloWorld/HelloWorld.xcodeproj/project.pbxproj b/HelloWorld/HelloWorld/HelloWorld.xcodeproj/project.pbxproj index 0e93380..f1901e7 100644 --- a/HelloWorld/HelloWorld/HelloWorld.xcodeproj/project.pbxproj +++ b/HelloWorld/HelloWorld/HelloWorld.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - A0497B682352FFA000D70E9E /* image.png in Resources */ = {isa = PBXBuildFile; fileRef = A0497B672352FFA000D70E9E /* image.png */; }; + 0E9C55F7809AF58493258E5E /* libPods-HelloWorld.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FDD5B9DA48EB1C10AB0378E1 /* libPods-HelloWorld.a */; }; A07753C62343DDEE00186969 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07753C52343DDEE00186969 /* AppDelegate.swift */; }; A07753CA2343DDEE00186969 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07753C92343DDEE00186969 /* ViewController.swift */; }; A07753CD2343DDEE00186969 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A07753CB2343DDEE00186969 /* Main.storyboard */; }; @@ -17,10 +17,12 @@ A07753E32343F9AD00186969 /* model.pt in Resources */ = {isa = PBXBuildFile; fileRef = A07753E12343F9AD00186969 /* model.pt */; }; A07753E42343F9AD00186969 /* words.txt in Resources */ = {isa = PBXBuildFile; fileRef = A07753E22343F9AD00186969 /* words.txt */; }; A07753EA2343FDEC00186969 /* UIImage+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07753E92343FDEC00186969 /* UIImage+Helper.swift */; }; + BDCC15D32757ABAF005A979D /* images in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15D22757ABAE005A979D /* images */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - A0497B672352FFA000D70E9E /* image.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = image.png; sourceTree = ""; }; + 1B8CD501236A9FDE6602CC95 /* Pods-HelloWorld.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HelloWorld.release.xcconfig"; path = "Target Support Files/Pods-HelloWorld/Pods-HelloWorld.release.xcconfig"; sourceTree = ""; }; + 7367FACB8B6E8FFF126590B1 /* Pods-HelloWorld.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HelloWorld.debug.xcconfig"; path = "Target Support Files/Pods-HelloWorld/Pods-HelloWorld.debug.xcconfig"; sourceTree = ""; }; A07753C22343DDEE00186969 /* HelloWorld.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HelloWorld.app; sourceTree = BUILT_PRODUCTS_DIR; }; A07753C52343DDEE00186969 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; A07753C92343DDEE00186969 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -34,6 +36,8 @@ A07753E12343F9AD00186969 /* model.pt */ = {isa = PBXFileReference; lastKnownFileType = file; path = model.pt; sourceTree = ""; }; A07753E22343F9AD00186969 /* words.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = words.txt; sourceTree = ""; }; A07753E92343FDEC00186969 /* UIImage+Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Helper.swift"; sourceTree = ""; }; + BDCC15D22757ABAE005A979D /* images */ = {isa = PBXFileReference; lastKnownFileType = folder; path = images; sourceTree = ""; }; + FDD5B9DA48EB1C10AB0378E1 /* libPods-HelloWorld.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-HelloWorld.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -41,6 +45,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0E9C55F7809AF58493258E5E /* libPods-HelloWorld.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -52,6 +57,8 @@ children = ( A07753C42343DDEE00186969 /* HelloWorld */, A07753C32343DDEE00186969 /* Products */, + C13266EB510C04784F97934C /* Pods */, + BE1BE2724AE47886C898562B /* Frameworks */, ); sourceTree = ""; }; @@ -66,7 +73,7 @@ A07753C42343DDEE00186969 /* HelloWorld */ = { isa = PBXGroup; children = ( - A0497B672352FFA000D70E9E /* image.png */, + BDCC15D22757ABAE005A979D /* images */, A07753E02343F9AD00186969 /* model */, A07753D92343EDE700186969 /* TorchBridge */, A07753C52343DDEE00186969 /* AppDelegate.swift */, @@ -99,6 +106,23 @@ path = model; sourceTree = ""; }; + BE1BE2724AE47886C898562B /* Frameworks */ = { + isa = PBXGroup; + children = ( + FDD5B9DA48EB1C10AB0378E1 /* libPods-HelloWorld.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + C13266EB510C04784F97934C /* Pods */ = { + isa = PBXGroup; + children = ( + 7367FACB8B6E8FFF126590B1 /* Pods-HelloWorld.debug.xcconfig */, + 1B8CD501236A9FDE6602CC95 /* Pods-HelloWorld.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -106,6 +130,7 @@ isa = PBXNativeTarget; buildConfigurationList = A07753D62343DDEF00186969 /* Build configuration list for PBXNativeTarget "HelloWorld" */; buildPhases = ( + 5F2C0E985D616F4B61860016 /* [CP] Check Pods Manifest.lock */, A07753BE2343DDEE00186969 /* Sources */, A07753BF2343DDEE00186969 /* Frameworks */, A07753C02343DDEE00186969 /* Resources */, @@ -126,7 +151,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1100; - LastUpgradeCheck = 1100; + LastUpgradeCheck = 1310; ORGANIZATIONNAME = "Tao Xu"; TargetAttributes = { A07753C12343DDEE00186969 = { @@ -158,17 +183,42 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + BDCC15D32757ABAF005A979D /* images in Resources */, A07753D22343DDEF00186969 /* LaunchScreen.storyboard in Resources */, A07753CF2343DDEF00186969 /* Assets.xcassets in Resources */, A07753CD2343DDEE00186969 /* Main.storyboard in Resources */, A07753E42343F9AD00186969 /* words.txt in Resources */, A07753E32343F9AD00186969 /* model.pt in Resources */, - A0497B682352FFA000D70E9E /* image.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 5F2C0E985D616F4B61860016 /* [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-HelloWorld-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 */ A07753BE2343DDEE00186969 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -229,6 +279,7 @@ 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; @@ -289,6 +340,7 @@ 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; @@ -319,13 +371,15 @@ }; A07753D72343DDEF00186969 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 7367FACB8B6E8FFF126590B1 /* Pods-HelloWorld.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = UP6SS5ES7E; ENABLE_BITCODE = NO; INFOPLIST_FILE = HelloWorld/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -335,19 +389,21 @@ SWIFT_OBJC_BRIDGING_HEADER = "HelloWorld/TorchBridge/HelloWorld-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; A07753D82343DDEF00186969 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1B8CD501236A9FDE6602CC95 /* Pods-HelloWorld.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = UP6SS5ES7E; ENABLE_BITCODE = NO; INFOPLIST_FILE = HelloWorld/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -356,7 +412,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "HelloWorld/TorchBridge/HelloWorld-Bridging-Header.h"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; diff --git a/HelloWorld/HelloWorld/HelloWorld/Base.lproj/Main.storyboard b/HelloWorld/HelloWorld/HelloWorld/Base.lproj/Main.storyboard index 21ead02..689298d 100644 --- a/HelloWorld/HelloWorld/HelloWorld/Base.lproj/Main.storyboard +++ b/HelloWorld/HelloWorld/HelloWorld/Base.lproj/Main.storyboard @@ -1,73 +1,133 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HelloWorld/HelloWorld/HelloWorld/TorchBridge/TorchModule.h b/HelloWorld/HelloWorld/HelloWorld/TorchBridge/TorchModule.h index 883120f..78b339b 100644 --- a/HelloWorld/HelloWorld/HelloWorld/TorchBridge/TorchModule.h +++ b/HelloWorld/HelloWorld/HelloWorld/TorchBridge/TorchModule.h @@ -4,11 +4,11 @@ NS_ASSUME_NONNULL_BEGIN @interface TorchModule : NSObject -- (nullable instancetype)initWithFileAtPath:(NSString*)filePath - NS_SWIFT_NAME(init(fileAtPath:))NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithFileAtPath:(NSString*)filePath width:(long)width height:(long)height +NS_SWIFT_NAME(init(fileAtPath:width:height:))NS_DESIGNATED_INITIALIZER; + (instancetype)new NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; -- (nullable NSArray*)predictImage:(void*)imageBuffer NS_SWIFT_NAME(predict(image:)); +- (nullable float*)predictImage:(void*)imageBuffer NS_SWIFT_NAME(predict(image:)); @end diff --git a/HelloWorld/HelloWorld/HelloWorld/TorchBridge/TorchModule.mm b/HelloWorld/HelloWorld/HelloWorld/TorchBridge/TorchModule.mm index 3f6155c..02a9318 100644 --- a/HelloWorld/HelloWorld/HelloWorld/TorchBridge/TorchModule.mm +++ b/HelloWorld/HelloWorld/HelloWorld/TorchBridge/TorchModule.mm @@ -1,14 +1,20 @@ #import "TorchModule.h" -#import +#import "LibTorch-Lite.h" @implementation TorchModule { - @protected +@protected torch::jit::mobile::Module _impl; + at::Tensor _output; + int64_t _inputSize[4]; } -- (nullable instancetype)initWithFileAtPath:(NSString*)filePath { +- (nullable instancetype)initWithFileAtPath:(NSString*)filePath width:(long)width height:(long)height { self = [super init]; if (self) { + _inputSize[0] = 1; + _inputSize[1] = 3; + _inputSize[2] = width; + _inputSize[3] = height; try { _impl = torch::jit::_load_for_mobile(filePath.UTF8String); } catch (const std::exception& exception) { @@ -19,23 +25,16 @@ - (nullable instancetype)initWithFileAtPath:(NSString*)filePath { return self; } -- (NSArray*)predictImage:(void*)imageBuffer { +- (float*)predictImage:(void*)imageBuffer { try { - at::Tensor tensor = torch::from_blob(imageBuffer, {1, 3, 224, 224}, at::kFloat); - c10::InferenceMode guard; - auto outputTensor = _impl.forward({tensor}).toTensor(); - float* floatBuffer = outputTensor.data_ptr(); - if (!floatBuffer) { - return nil; - } - NSMutableArray* results = [[NSMutableArray alloc] init]; - for (int i = 0; i < 1000; i++) { - [results addObject:@(floatBuffer[i])]; - } - return [results copy]; + c10::InferenceMode guard(true); + at::Tensor tensor = torch::from_blob(imageBuffer, _inputSize, at::kFloat); + _output = _impl.forward({tensor}).toTensor().cpu(); + return _output.data_ptr(); } catch (const std::exception& exception) { NSLog(@"%s", exception.what()); } + return nil; } diff --git a/HelloWorld/HelloWorld/HelloWorld/ViewController.swift b/HelloWorld/HelloWorld/HelloWorld/ViewController.swift index 96894eb..2092d88 100644 --- a/HelloWorld/HelloWorld/HelloWorld/ViewController.swift +++ b/HelloWorld/HelloWorld/HelloWorld/ViewController.swift @@ -1,43 +1,124 @@ import UIKit -class ViewController: UIViewController { - @IBOutlet var imageView: UIImageView! - @IBOutlet var resultView: UITextView! - private lazy var module: TorchModule = { - if let filePath = Bundle.main.path(forResource: "model", ofType: "pt"), - let module = TorchModule(fileAtPath: filePath) { - return module - } else { - fatalError("Can't find the model file!") - } - }() - - private lazy var labels: [String] = { - if let filePath = Bundle.main.path(forResource: "words", ofType: "txt"), - let labels = try? String(contentsOfFile: filePath) { - return labels.components(separatedBy: .newlines) - } else { - fatalError("Can't find the text file!") - } - }() - - override func viewDidLoad() { - super.viewDidLoad() - let image = UIImage(named: "image.png")! - imageView.image = image - let resizedImage = image.resized(to: CGSize(width: 224, height: 224)) - guard var pixelBuffer = resizedImage.normalized() else { - return - } - guard let outputs = module.predict(image: UnsafeMutableRawPointer(&pixelBuffer)) else { - return - } - let zippedResults = zip(labels.indices, outputs) - let sortedResults = zippedResults.sorted { $0.1.floatValue > $1.1.floatValue }.prefix(3) - var text = "" - for result in sortedResults { - text += "\u{2022} \(labels[result.0]) \n\n" +struct Prediction { + let confidence: Float + let label: String +} + +class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { + @IBOutlet weak var imageView: UIImageView! + @IBOutlet weak var predictButton: UIButton! + @IBOutlet weak var imageStepper: UIStepper! + @IBOutlet weak var durationLabel: UILabel! + @IBOutlet weak var resultsView: UITableView! + + var predictions = [Prediction]() + + let width: Int = 224 + let height: Int = 224 + + private lazy var module: TorchModule = { + guard let filePath = Bundle.main.path(forResource: "model", ofType: "pt"), + let module = TorchModule(fileAtPath: filePath, width: width, height: height) + else { + fatalError("Can't find the model file!") + } + return module + }() + + private lazy var labels: [Substring] = { + guard let filePath = Bundle.main.path(forResource: "words", ofType: "txt"), + let labels = try? String(contentsOfFile: filePath) + else { + fatalError("Can't find the text file!") + } + return labels.split(whereSeparator: \.isNewline) + }() + + private lazy var images: [UIImage?] = { + let imagePaths = Bundle.main.paths(forResourcesOfType: "jpg", inDirectory: "images") + + Bundle.main.paths(forResourcesOfType: "png", inDirectory: "images") + print(imagePaths) + return imagePaths.map { UIImage(named: $0) } + }() + + private var activeImage: UIImage? { images[Int(imageStepper.value)] } + + override func viewDidLoad() { + super.viewDidLoad() + resultsView.dataSource = self + imageStepper.value = 0 + imageStepper.minimumValue = 0 + imageStepper.maximumValue = Double(images.count - 1) + updateImageView() + doPrediction() + } + + func updateImageView() { + imageView.image = images[Int(imageStepper.value)] + } + + private var imageFloats: [Float32]? { + guard let resizedImage = activeImage?.resized(to: CGSize(width: width, height: height)) else { return nil } + return resizedImage.normalized() + } + + func doPrediction() { + guard var input = self.imageFloats else { return } + predictions.removeAll() + resultsView.reloadData() + + input.withUnsafeMutableBufferPointer { ptr in + let startTime: DispatchTime = .now() + if let output = module.predict(image: &(ptr.baseAddress!.pointee)) { + let duration = TimeInterval(dispatchTimeInterval: startTime.distance(to: .now()))! + durationLabel.text = String(format: "%.3f seconds", duration) + + let bufferPointer = UnsafeBufferPointer(start: output, count: labels.count) + let indexedResults = bufferPointer.enumerated() + let top3 = indexedResults.sorted { $0.1 > $1.1 }.prefix(4) + + for result in top3 { + predictions.append(Prediction(confidence: result.1, label: String(labels[result.0]))) } - resultView.text = text + resultsView.reloadData() + } + } + } + + @IBAction func changeImage(_ sender: Any) { + updateImageView() + doPrediction() + } + + @IBAction func runPredict(_ sender: Any) { + doPrediction() + } +} + +extension ViewController { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + predictions.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "result", for: indexPath) + cell.textLabel?.text = predictions[indexPath.row].label + cell.detailTextLabel?.text = String(format: "%.2f", predictions[indexPath.row].confidence) + return cell + } +} + +extension TimeInterval { + + init?(dispatchTimeInterval: DispatchTimeInterval) { + switch dispatchTimeInterval { + case .seconds(let value): self = Double(value) + case .milliseconds(let value): self = Double(value) / 1_000 + case .microseconds(let value): self = Double(value) / 1_000_000 + case .nanoseconds(let value): self = Double(value) / 1_000_000_000 + case .never: return nil + @unknown default: return nil } + } } diff --git a/HelloWorld/HelloWorld/HelloWorld/images/animal-1.jpg b/HelloWorld/HelloWorld/HelloWorld/images/animal-1.jpg new file mode 100644 index 0000000..790afbc Binary files /dev/null and b/HelloWorld/HelloWorld/HelloWorld/images/animal-1.jpg differ diff --git a/HelloWorld/HelloWorld/HelloWorld/images/animal-2.jpg b/HelloWorld/HelloWorld/HelloWorld/images/animal-2.jpg new file mode 100644 index 0000000..3426175 Binary files /dev/null and b/HelloWorld/HelloWorld/HelloWorld/images/animal-2.jpg differ diff --git a/HelloWorld/HelloWorld/HelloWorld/images/animal-3.jpg b/HelloWorld/HelloWorld/HelloWorld/images/animal-3.jpg new file mode 100644 index 0000000..7b2bd05 Binary files /dev/null and b/HelloWorld/HelloWorld/HelloWorld/images/animal-3.jpg differ diff --git a/HelloWorld/HelloWorld/HelloWorld/images/car-1.jpg b/HelloWorld/HelloWorld/HelloWorld/images/car-1.jpg new file mode 100644 index 0000000..c6dc1e6 Binary files /dev/null and b/HelloWorld/HelloWorld/HelloWorld/images/car-1.jpg differ diff --git a/HelloWorld/HelloWorld/HelloWorld/images/car-2.jpg b/HelloWorld/HelloWorld/HelloWorld/images/car-2.jpg new file mode 100644 index 0000000..b62d31d Binary files /dev/null and b/HelloWorld/HelloWorld/HelloWorld/images/car-2.jpg differ diff --git a/HelloWorld/HelloWorld/HelloWorld/images/car-3.jpg b/HelloWorld/HelloWorld/HelloWorld/images/car-3.jpg new file mode 100644 index 0000000..4561529 Binary files /dev/null and b/HelloWorld/HelloWorld/HelloWorld/images/car-3.jpg differ diff --git a/HelloWorld/HelloWorld/HelloWorld/images/food-1.jpg b/HelloWorld/HelloWorld/HelloWorld/images/food-1.jpg new file mode 100644 index 0000000..2fb4e06 Binary files /dev/null and b/HelloWorld/HelloWorld/HelloWorld/images/food-1.jpg differ diff --git a/HelloWorld/HelloWorld/HelloWorld/images/girl-1.jpg b/HelloWorld/HelloWorld/HelloWorld/images/girl-1.jpg new file mode 100644 index 0000000..4fa80b6 Binary files /dev/null and b/HelloWorld/HelloWorld/HelloWorld/images/girl-1.jpg differ diff --git a/HelloWorld/HelloWorld/HelloWorld/images/girl-2.jpg b/HelloWorld/HelloWorld/HelloWorld/images/girl-2.jpg new file mode 100644 index 0000000..6f0fc52 Binary files /dev/null and b/HelloWorld/HelloWorld/HelloWorld/images/girl-2.jpg differ diff --git a/HelloWorld/HelloWorld/HelloWorld/images/girl-3.jpg b/HelloWorld/HelloWorld/HelloWorld/images/girl-3.jpg new file mode 100644 index 0000000..00c4ae0 Binary files /dev/null and b/HelloWorld/HelloWorld/HelloWorld/images/girl-3.jpg differ diff --git a/HelloWorld/HelloWorld/HelloWorld/images/kitten_small.jpg b/HelloWorld/HelloWorld/HelloWorld/images/kitten_small.jpg new file mode 100644 index 0000000..1592710 Binary files /dev/null and b/HelloWorld/HelloWorld/HelloWorld/images/kitten_small.jpg differ diff --git a/HelloWorld/HelloWorld/HelloWorld/image.png b/HelloWorld/HelloWorld/HelloWorld/images/wolf.png similarity index 100% rename from HelloWorld/HelloWorld/HelloWorld/image.png rename to HelloWorld/HelloWorld/HelloWorld/images/wolf.png diff --git a/PyTorchDemo/Podfile b/PyTorchDemo/Podfile index 5a0aeef..50504d0 100644 --- a/PyTorchDemo/Podfile +++ b/PyTorchDemo/Podfile @@ -1,4 +1,5 @@ platform :ios, '14.3' target 'PyTorchDemo' do - pod 'LibTorch', '~>1.9.0' + # pod 'LibTorch', '~>1.9.0' + pod 'LibTorch-Lite-Nightly' end diff --git a/PyTorchDemo/PyTorchDemo.xcodeproj/project.pbxproj b/PyTorchDemo/PyTorchDemo.xcodeproj/project.pbxproj index 08e6725..26041cd 100644 --- a/PyTorchDemo/PyTorchDemo.xcodeproj/project.pbxproj +++ b/PyTorchDemo/PyTorchDemo.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 6D650F9479F0EB0B7DB8EA95 /* libPods-PyTorchDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6022D6FD02DB575F4D75312F /* libPods-PyTorchDemo.a */; }; A01F52AC233C6C32004039FC /* topics.txt in Resources */ = {isa = PBXBuildFile; fileRef = A01F52AB233C6C32004039FC /* topics.txt */; }; A01F52AE233C985C004039FC /* Predictor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A01F52AD233C985C004039FC /* Predictor.swift */; }; A0296C4A2333178500C77366 /* NLPViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0296C492333178500C77366 /* NLPViewController.swift */; }; @@ -41,9 +42,11 @@ A09E1796233990A0002F8F15 /* VisionModelCard.xib in Resources */ = {isa = PBXBuildFile; fileRef = A09E1795233990A0002F8F15 /* VisionModelCard.xib */; }; A0B12D4E2338944900380B1D /* TorchModule.mm in Sources */ = {isa = PBXBuildFile; fileRef = A0B12D4D2338944900380B1D /* TorchModule.mm */; }; B1C35BA725C99B5200CAB118 /* trace_model.py in Resources */ = {isa = PBXBuildFile; fileRef = B1C35BA625C99B5200CAB118 /* trace_model.py */; }; + BDCC15D52757DCD6005A979D /* mobilenet_quantized2.pt in Resources */ = {isa = PBXBuildFile; fileRef = BDCC15D42757DCD6005A979D /* mobilenet_quantized2.pt */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 6022D6FD02DB575F4D75312F /* libPods-PyTorchDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PyTorchDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A01F52AB233C6C32004039FC /* topics.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = topics.txt; sourceTree = ""; }; A01F52AD233C985C004039FC /* Predictor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Predictor.swift; sourceTree = ""; }; A0296C492333178500C77366 /* NLPViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NLPViewController.swift; sourceTree = ""; }; @@ -82,6 +85,9 @@ A0B12D4C2338944900380B1D /* TorchModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TorchModule.h; sourceTree = ""; }; A0B12D4D2338944900380B1D /* TorchModule.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TorchModule.mm; sourceTree = ""; }; B1C35BA625C99B5200CAB118 /* trace_model.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = trace_model.py; sourceTree = ""; }; + BDCC15D42757DCD6005A979D /* mobilenet_quantized2.pt */ = {isa = PBXFileReference; lastKnownFileType = file; path = mobilenet_quantized2.pt; sourceTree = ""; }; + CE2E63B3E0FE1515FBF41C12 /* Pods-PyTorchDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PyTorchDemo.debug.xcconfig"; path = "Target Support Files/Pods-PyTorchDemo/Pods-PyTorchDemo.debug.xcconfig"; sourceTree = ""; }; + FA2C77CD1130895F363C81F0 /* Pods-PyTorchDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PyTorchDemo.release.xcconfig"; path = "Target Support Files/Pods-PyTorchDemo/Pods-PyTorchDemo.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -89,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 6D650F9479F0EB0B7DB8EA95 /* libPods-PyTorchDemo.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -143,6 +150,8 @@ children = ( A091B19523303F2D0008A5B8 /* PyTorchDemo */, A091B19423303F2D0008A5B8 /* Products */, + FB40E40FC092817D5F1A6B47 /* Pods */, + F39C5143BC568706EC1AA82A /* Frameworks */, ); sourceTree = ""; }; @@ -190,6 +199,7 @@ isa = PBXGroup; children = ( B1C35BA625C99B5200CAB118 /* trace_model.py */, + BDCC15D42757DCD6005A979D /* mobilenet_quantized2.pt */, A065C92A2335711B006BEAAD /* words.txt */, A03349BB233D5E7C003425FD /* mobilenet_quantized.pt */, ); @@ -219,6 +229,23 @@ path = TorchBridge; sourceTree = ""; }; + F39C5143BC568706EC1AA82A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6022D6FD02DB575F4D75312F /* libPods-PyTorchDemo.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + FB40E40FC092817D5F1A6B47 /* Pods */ = { + isa = PBXGroup; + children = ( + CE2E63B3E0FE1515FBF41C12 /* Pods-PyTorchDemo.debug.xcconfig */, + FA2C77CD1130895F363C81F0 /* Pods-PyTorchDemo.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -226,6 +253,7 @@ isa = PBXNativeTarget; buildConfigurationList = A091B1A523303F2D0008A5B8 /* Build configuration list for PBXNativeTarget "PyTorchDemo" */; buildPhases = ( + 83A344EDF737CBA77AA96B94 /* [CP] Check Pods Manifest.lock */, A091B18F23303F2D0008A5B8 /* Sources */, A091B19023303F2D0008A5B8 /* Frameworks */, A091B19123303F2D0008A5B8 /* Resources */, @@ -246,7 +274,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1030; - LastUpgradeCheck = 1030; + LastUpgradeCheck = 1310; TargetAttributes = { A091B19223303F2D0008A5B8 = { CreatedOnToolsVersion = 10.3; @@ -284,6 +312,7 @@ A091B19E23303F2D0008A5B8 /* Assets.xcassets in Resources */, A03349CA233DAE9F003425FD /* NLPResultView.xib in Resources */, A03349C4233D71F2003425FD /* ImageClassificationItemView.xib in Resources */, + BDCC15D52757DCD6005A979D /* mobilenet_quantized2.pt in Resources */, A036EA33233C19F500054A36 /* reddit.pt in Resources */, B1C35BA725C99B5200CAB118 /* trace_model.py in Resources */, A03349CE233DB128003425FD /* NLPItemView.xib in Resources */, @@ -296,6 +325,31 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 83A344EDF737CBA77AA96B94 /* [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-PyTorchDemo-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 */ A091B18F23303F2D0008A5B8 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -372,6 +426,7 @@ 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; @@ -433,6 +488,7 @@ 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; @@ -464,12 +520,13 @@ }; A091B1A623303F2D0008A5B8 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = CE2E63B3E0FE1515FBF41C12 /* Pods-PyTorchDemo.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UP6SS5ES7E; ENABLE_BITCODE = NO; INFOPLIST_FILE = PyTorchDemo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -482,18 +539,19 @@ SWIFT_OBJC_BRIDGING_HEADER = "PyTorchDemo/TorchBridge/PyTorchDemo-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; A091B1A723303F2D0008A5B8 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = FA2C77CD1130895F363C81F0 /* Pods-PyTorchDemo.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UP6SS5ES7E; ENABLE_BITCODE = NO; INFOPLIST_FILE = PyTorchDemo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -505,7 +563,7 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "PyTorchDemo/TorchBridge/PyTorchDemo-Bridging-Header.h"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; diff --git a/PyTorchDemo/PyTorchDemo/ImageClassification/ImagePredictor.swift b/PyTorchDemo/PyTorchDemo/ImageClassification/ImagePredictor.swift index f232cf4..639b326 100644 --- a/PyTorchDemo/PyTorchDemo/ImageClassification/ImagePredictor.swift +++ b/PyTorchDemo/PyTorchDemo/ImageClassification/ImagePredictor.swift @@ -1,38 +1,40 @@ import UIKit class ImagePredictor: Predictor { - private var isRunning: Bool = false - private lazy var module: VisionTorchModule = { - if let filePath = Bundle.main.path(forResource: "mobilenet_quantized", ofType: "pt"), - let module = VisionTorchModule(fileAtPath: filePath) { - return module - } else { - fatalError("Failed to load model!") - } - }() + private var isRunning: Bool = false - private var labels: [String] = { - if let filePath = Bundle.main.path(forResource: "words", ofType: "txt"), - let labels = try? String(contentsOfFile: filePath) { - return labels.components(separatedBy: .newlines) - } else { - fatalError("Label file was not found.") - } - }() + private lazy var module: VisionTorchModule = { + guard let filePath = Bundle.main.path(forResource: "mobilenet_quantized2", ofType: "pt"), + let module = VisionTorchModule(fileAtPath: filePath) + else { + fatalError("Failed to load model!") + } + return module + }() + + private var labels: [String] = { + guard let filePath = Bundle.main.path(forResource: "words", ofType: "txt"), + let labels = try? String(contentsOfFile: filePath) + else { + fatalError("Label file was not found.") + } + return labels.split(whereSeparator: \.isNewline).map { .init($0) } + }() + + func predict(_ buffer: [Float32], resultCount: Int) throws -> ([InferenceResult], Double)? { + guard !isRunning else { return nil } + isRunning = true + defer { isRunning = false } - func predict(_ buffer: [Float32], resultCount: Int) throws -> ([InferenceResult], Double)? { - if isRunning { - return nil - } - isRunning = true - let startTime = CACurrentMediaTime() - var tensorBuffer = buffer; - guard let outputs = module.predict(image: UnsafeMutableRawPointer(&tensorBuffer)) else { - throw PredictorError.invalidInputTensor - } - isRunning = false - let inferenceTime = (CACurrentMediaTime() - startTime) * 1000 - let results = topK(scores: outputs, labels: labels, count: resultCount) - return (results, inferenceTime) + var tmp = buffer + return tmp.withUnsafeMutableBufferPointer { ptr in + let startTime = CACurrentMediaTime() + if let output = module.predict(image: &(ptr.baseAddress!.pointee)) { + let duration = (CACurrentMediaTime() - startTime) * 1000 + let bufferPointer = UnsafeBufferPointer(start: output, count: labels.count) + return (topK(scores: bufferPointer, labels: labels, count: 3), duration) + } + return nil } + } } diff --git a/PyTorchDemo/PyTorchDemo/ImageClassification/model/mobilenet_quantized2.pt b/PyTorchDemo/PyTorchDemo/ImageClassification/model/mobilenet_quantized2.pt new file mode 100644 index 0000000..ad4c5cd Binary files /dev/null and b/PyTorchDemo/PyTorchDemo/ImageClassification/model/mobilenet_quantized2.pt differ diff --git a/PyTorchDemo/PyTorchDemo/ImageClassification/model/trace_model.py b/PyTorchDemo/PyTorchDemo/ImageClassification/model/trace_model.py index 6aa0084..eeb02fc 100644 --- a/PyTorchDemo/PyTorchDemo/ImageClassification/model/trace_model.py +++ b/PyTorchDemo/PyTorchDemo/ImageClassification/model/trace_model.py @@ -3,8 +3,7 @@ from torch.utils.mobile_optimizer import optimize_for_mobile model = torchvision.models.quantization.mobilenet_v2(pretrained=True, quantize=True) -model.eval() example = torch.rand(1, 3, 224, 224) traced_script_module = torch.jit.trace(model, example) torchscript_model_optimized = optimize_for_mobile(traced_script_module) -torchscript_model_optimized.save("mobilenet_quantized.pt") +torchscript_model_optimized._save_for_lite_interpreter("mobilenet_quantized2.pt") diff --git a/PyTorchDemo/PyTorchDemo/NLP/NLPPredictor.swift b/PyTorchDemo/PyTorchDemo/NLP/NLPPredictor.swift index 68a482d..6bd7573 100644 --- a/PyTorchDemo/PyTorchDemo/NLP/NLPPredictor.swift +++ b/PyTorchDemo/PyTorchDemo/NLP/NLPPredictor.swift @@ -1,34 +1,35 @@ import UIKit class NLPPredictor: Predictor { - private var module: NLPTorchModule = { - if let filePath = Bundle.main.path(forResource: "reddit", ofType: "pt"), - let module = NLPTorchModule(fileAtPath: filePath) { - return module - } else { - fatalError("Failed to load model!") - } - }() - - private var topics: [String] = [] - init() { - topics = loadTopics() + private var module: NLPTorchModule = { + if let filePath = Bundle.main.path(forResource: "reddit", ofType: "pt"), + let module = NLPTorchModule(fileAtPath: filePath) { + return module + } else { + fatalError("Failed to load model!") } + }() + + private var topics: [String] = [] - func predict(_ text: String, resultCount: Int) throws -> [InferenceResult]? { - if text.isEmpty { - throw PredictorError.invalidInputTensor - } - guard let outputs = module.predict(text: text) else { - throw PredictorError.invalidInputTensor - } - return topK(scores: outputs, labels: topics, count: resultCount) + init() { + topics = loadTopics() + } + + func predict(_ text: String, resultCount: Int) throws -> [InferenceResult]? { + guard !text.isEmpty, + let output = module.predict(text: text) else { + throw PredictorError.invalidInputTensor } - private func loadTopics() -> [String] { - guard let topics = module.topics() else { - fatalError("Failed to load topics from model") - } - return topics + let floatArray = UnsafeBufferPointer(start: output, count: resultCount) + return topK(scores: floatArray, labels: topics, count: resultCount) + } + + private func loadTopics() -> [String] { + guard let topics = module.topics() else { + fatalError("Failed to load topics from model") } + return topics + } } diff --git a/PyTorchDemo/PyTorchDemo/Predictor.swift b/PyTorchDemo/PyTorchDemo/Predictor.swift index 3ad7351..98dde4d 100644 --- a/PyTorchDemo/PyTorchDemo/Predictor.swift +++ b/PyTorchDemo/PyTorchDemo/Predictor.swift @@ -14,9 +14,9 @@ enum PredictorError: Swift.Error { protocol Predictor {} extension Predictor { - func topK(scores: [NSNumber], labels: [String], count: Int) -> [InferenceResult] { - let zippedResults = zip(labels.indices, scores) - let sortedResults = zippedResults.sorted { $0.1.floatValue > $1.1.floatValue }.prefix(count) - return sortedResults.map { InferenceResult(score: $0.1.floatValue, label: labels[$0.0]) } - } + func topK(scores: UnsafeBufferPointer, labels: [String], count: Int) -> [InferenceResult] { + let zippedResults = zip(scores, labels.indices) + let sortedResults = zippedResults.sorted { $0.0 > $1.0 }.prefix(count) + return sortedResults.map { InferenceResult(score: $0.0, label: labels[$0.1]) } + } } diff --git a/PyTorchDemo/PyTorchDemo/TorchBridge/TorchModule.h b/PyTorchDemo/PyTorchDemo/TorchBridge/TorchModule.h index f4435d3..e14efed 100644 --- a/PyTorchDemo/PyTorchDemo/TorchBridge/TorchModule.h +++ b/PyTorchDemo/PyTorchDemo/TorchBridge/TorchModule.h @@ -12,12 +12,12 @@ NS_ASSUME_NONNULL_BEGIN @end @interface VisionTorchModule : TorchModule -- (nullable NSArray*)predictImage:(void*)imageBuffer NS_SWIFT_NAME(predict(image:)); +- (nullable float*)predictImage:(void*)imageBuffer NS_SWIFT_NAME(predict(image:)); @end @interface NLPTorchModule : TorchModule - (nullable NSArray*)topics; -- (nullable NSArray*)predictText:(NSString*)text NS_SWIFT_NAME(predict(text:)); +- (nullable float*)predictText:(NSString*)text NS_SWIFT_NAME(predict(text:)); @end NS_ASSUME_NONNULL_END diff --git a/PyTorchDemo/PyTorchDemo/TorchBridge/TorchModule.mm b/PyTorchDemo/PyTorchDemo/TorchBridge/TorchModule.mm index b188794..035bf24 100644 --- a/PyTorchDemo/PyTorchDemo/TorchBridge/TorchModule.mm +++ b/PyTorchDemo/PyTorchDemo/TorchBridge/TorchModule.mm @@ -1,21 +1,17 @@ #import "TorchModule.h" -#import +#import @implementation TorchModule { - @protected - torch::jit::script::Module _impl; +@protected + torch::jit::mobile::Module _model; + at::Tensor _output; } - (nullable instancetype)initWithFileAtPath:(NSString*)filePath { self = [super init]; if (self) { try { - auto qengines = at::globalContext().supportedQEngines(); - if (std::find(qengines.begin(), qengines.end(), at::QEngine::QNNPACK) != qengines.end()) { - at::globalContext().setQEngine(at::QEngine::QNNPACK); - } - _impl = torch::jit::load(filePath.UTF8String); - _impl.eval(); + _model = torch::jit::_load_for_mobile(filePath.UTF8String); } catch (const std::exception& exception) { NSLog(@"%s", exception.what()); return nil; @@ -28,21 +24,12 @@ - (nullable instancetype)initWithFileAtPath:(NSString*)filePath { @implementation VisionTorchModule -- (NSArray*)predictImage:(void*)imageBuffer { +- (float*)predictImage:(void*)imageBuffer { try { + c10::InferenceMode guard(true); at::Tensor tensor = torch::from_blob(imageBuffer, {1, 3, 224, 224}, at::kFloat); - torch::autograd::AutoGradMode guard(false); - at::AutoNonVariableTypeMode non_var_type_mode(true); - auto outputTensor = _impl.forward({tensor}).toTensor(); - float* floatBuffer = outputTensor.data_ptr(); - if (!floatBuffer) { - return nil; - } - NSMutableArray* results = [[NSMutableArray alloc] init]; - for (int i = 0; i < 1000; i++) { - [results addObject:@(floatBuffer[i])]; - } - return [results copy]; + _output = _model.forward({tensor}).toTensor(); + return _output.data_ptr(); } catch (const std::exception& exception) { NSLog(@"%s", exception.what()); } @@ -53,22 +40,13 @@ @implementation VisionTorchModule @implementation NLPTorchModule -- (NSArray*)predictText:(NSString*)text { +- (float*)predictText:(NSString*)text { try { + c10::InferenceMode guard(true); const char* buffer = text.UTF8String; - torch::autograd::AutoGradMode guard(false); - at::AutoNonVariableTypeMode non_var_type_mode(true); at::Tensor tensor = torch::from_blob((void*)buffer, {1, (int64_t)(strlen(buffer))}, at::kByte); - auto outputTensor = _impl.forward({tensor}).toTensor(); - float* floatBuffer = outputTensor.data_ptr(); - if (!floatBuffer) { - return nil; - } - NSMutableArray* results = [[NSMutableArray alloc] init]; - for (int i = 0; i < 16; i++) { - [results addObject:@(floatBuffer[i])]; - } - return [results copy]; + _output = _model.forward({tensor}).toTensor(); + return _output.data_ptr(); } catch (const std::exception& exception) { NSLog(@"%s", exception.what()); } @@ -77,7 +55,7 @@ @implementation NLPTorchModule - (NSArray*)topics { try { - auto genericList = _impl.run_method("get_classes").toList(); + auto genericList = _model.run_method("get_classes").toList(); NSMutableArray* topics = [NSMutableArray new]; for (int i = 0; i < genericList.size(); i++) { std::string topic = genericList.get(i).toString()->string(); diff --git a/TorchVideo/TorchVideo.xcodeproj/project.pbxproj b/TorchVideo/TorchVideo.xcodeproj/project.pbxproj index 7d62381..a905c56 100644 --- a/TorchVideo/TorchVideo.xcodeproj/project.pbxproj +++ b/TorchVideo/TorchVideo.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 150FEDD29415E812ECDA20C1 /* Pods_TorchVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 630BF504711D114B0B8F709D /* Pods_TorchVideo.framework */; }; 26024B6426042B2300B6F625 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26024B6326042B2300B6F625 /* AppDelegate.swift */; }; 26024B6626042B2300B6F625 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26024B6526042B2300B6F625 /* SceneDelegate.swift */; }; 26024B6826042B2300B6F625 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26024B6726042B2300B6F625 /* ViewController.swift */; }; @@ -29,6 +30,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 06D85729F77BC3AA53A0C8C4 /* Pods-TorchVideo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TorchVideo.debug.xcconfig"; path = "Target Support Files/Pods-TorchVideo/Pods-TorchVideo.debug.xcconfig"; sourceTree = ""; }; 26024B6026042B2300B6F625 /* TorchVideo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TorchVideo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26024B6326042B2300B6F625 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 26024B6526042B2300B6F625 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -52,6 +54,8 @@ 26024BA6260D0E8A00B6F625 /* CameraPreviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CameraPreviewView.swift; path = TorchVideo/CameraPreviewView.swift; sourceTree = SOURCE_ROOT; }; 26024BA7260D0E8A00B6F625 /* CVPixelBuffer+Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "CVPixelBuffer+Helper.swift"; path = "TorchVideo/CVPixelBuffer+Helper.swift"; sourceTree = SOURCE_ROOT; }; 26A8C10F26E17A3B00F4A58D /* video_classification.ptl */ = {isa = PBXFileReference; lastKnownFileType = file; path = video_classification.ptl; sourceTree = ""; }; + 630BF504711D114B0B8F709D /* Pods_TorchVideo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TorchVideo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 729E5C95BA2DF8C99A950320 /* Pods-TorchVideo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TorchVideo.release.xcconfig"; path = "Target Support Files/Pods-TorchVideo/Pods-TorchVideo.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -59,6 +63,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 150FEDD29415E812ECDA20C1 /* Pods_TorchVideo.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -71,6 +76,7 @@ 26024B6226042B2300B6F625 /* TorchVideo */, 26024B6126042B2300B6F625 /* Products */, EA0C558862607C5A9BA15E19 /* Pods */, + 50FD079E11052576D34D17AB /* Frameworks */, ); sourceTree = ""; }; @@ -135,9 +141,19 @@ name = Utils; sourceTree = ""; }; + 50FD079E11052576D34D17AB /* Frameworks */ = { + isa = PBXGroup; + children = ( + 630BF504711D114B0B8F709D /* Pods_TorchVideo.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; EA0C558862607C5A9BA15E19 /* Pods */ = { isa = PBXGroup; children = ( + 06D85729F77BC3AA53A0C8C4 /* Pods-TorchVideo.debug.xcconfig */, + 729E5C95BA2DF8C99A950320 /* Pods-TorchVideo.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -149,6 +165,7 @@ isa = PBXNativeTarget; buildConfigurationList = 26024B7426042B2500B6F625 /* Build configuration list for PBXNativeTarget "TorchVideo" */; buildPhases = ( + B0C22985D2F2F00E69387BC5 /* [CP] Check Pods Manifest.lock */, 26024B5C26042B2300B6F625 /* Sources */, 26024B5D26042B2300B6F625 /* Frameworks */, 26024B5E26042B2300B6F625 /* Resources */, @@ -213,6 +230,31 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + B0C22985D2F2F00E69387BC5 /* [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-TorchVideo-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 */ 26024B5C26042B2300B6F625 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -372,12 +414,13 @@ }; 26024B7526042B2500B6F625 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 06D85729F77BC3AA53A0C8C4 /* Pods-TorchVideo.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UP6SS5ES7E; ENABLE_BITCODE = NO; INFOPLIST_FILE = TorchVideo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -396,12 +439,13 @@ }; 26024B7626042B2500B6F625 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 729E5C95BA2DF8C99A950320 /* Pods-TorchVideo.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UP6SS5ES7E; ENABLE_BITCODE = NO; INFOPLIST_FILE = TorchVideo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; diff --git a/TorchVideo/TorchVideo/AppDelegate.swift b/TorchVideo/TorchVideo/AppDelegate.swift index e742747..4f92cd0 100644 --- a/TorchVideo/TorchVideo/AppDelegate.swift +++ b/TorchVideo/TorchVideo/AppDelegate.swift @@ -11,26 +11,24 @@ import UIKit @main 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. - } + 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/TorchVideo/TorchVideo/Base.lproj/Main.storyboard b/TorchVideo/TorchVideo/Base.lproj/Main.storyboard index 1aa98b5..605e6ef 100644 --- a/TorchVideo/TorchVideo/Base.lproj/Main.storyboard +++ b/TorchVideo/TorchVideo/Base.lproj/Main.storyboard @@ -1,8 +1,9 @@ - + - + + @@ -16,49 +17,49 @@ - - - - - - - - - - - - + + + + @@ -78,7 +79,7 @@ - +