diff options
author | A. Unique TensorFlower <gardener@tensorflow.org> | 2018-10-09 17:14:39 -0700 |
---|---|---|
committer | TensorFlower Gardener <gardener@tensorflow.org> | 2018-10-09 17:19:21 -0700 |
commit | 9bd459e4ceba14f9bb1af98d52a109325de952e8 (patch) | |
tree | e8795778c9c1dce36e0d4ddd66783c2aa4994424 | |
parent | bb5fc614a4a358b350ef8dd19cb7010760fa9b29 (diff) |
Adds an Objective-C API to TensorFlow Lite experimental.
PiperOrigin-RevId: 216451263
18 files changed, 1520 insertions, 0 deletions
diff --git a/tensorflow/contrib/lite/experimental/objc/BUILD b/tensorflow/contrib/lite/experimental/objc/BUILD new file mode 100644 index 0000000000..236b96adb5 --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/BUILD @@ -0,0 +1,94 @@ +# TensorFlow Lite Objective-C API. + +package(default_visibility = ["//visibility:private"]) + +licenses(["notice"]) # Apache 2.0 + +load("//tools/build_defs/apple:ios.bzl", "ios_unit_test") + +SOURCES = glob([ + "sources/*.h", + "sources/*.m", + "sources/*.mm", +]) + +API_HEADERS = glob([ + "apis/*.h", +]) + +MINIMUM_OS_VERSION = "8.0" + +# Compiler flags for building regular non-test libraries. +RELEASE_COPTS = [ + # Enables language-specific warnings for Objective-C, Objective-C++, C, and C++. + "-Wall", + # Warns if functions, variables, and types marked with the deprecated attribute are being used. + "-Wdeprecated-declarations", + # Warns for errors in documentation. + "-Wdocumentation", + # Turns all warnings into errors. + "-Werror", + # Enables extra warning flags that are not enabled by -Wall. + "-Wextra", + # Warns if a global function is defined without a previous prototype declaration. + "-Wmissing-prototypes", + # From -Wextra. Disables warning when signed value is converted to unsigned value during comparison. + "-Wno-sign-compare", + # From -Wextra. Disables warning for unused parameters, which are common in delegate methods and block callbacks. + "-Wno-unused-parameter", + # Warns if a global or local variable or type declaration shadows another variable, parameter, type, class member, or instance variable. + "-Wshadow", + # Warns if a function is declared or defined without specifying the argument types. For a block with no args, use (void) instead of (). + "-Wstrict-prototypes", + # Warns if an @selector() expression is encountered with a method name that hasn't been defined yet. + "-Wundeclared-selector", + + # Turn off warnings for headers not part of TensorFlow Lite Objective-C API. + "--system-header-prefix=third_party/tensorflow/contrib/lite/experimental/c/", +] + +# Compiler flags for building test libraries. +TEST_COPTS = RELEASE_COPTS + [ + # From -Wall. Disables warning when passing nil to a callee that requires a non-null argument. + "-Wno-nonnull", + # Disables warning when a global or local variable or type declaration shadows another. + "-Wno-shadow", +] + +objc_library( + name = "TensorFlowLiteObjCLib", + srcs = SOURCES, + hdrs = API_HEADERS, + copts = RELEASE_COPTS, + deps = [ + "//tensorflow/contrib/lite/experimental/c:c_api", + ], + alwayslink = 1, +) + +ios_unit_test( + name = "TensorFlowLiteObjCTests", + size = "small", + minimum_os_version = MINIMUM_OS_VERSION, + deps = [":TensorFlowLiteObjCTestLib"], +) + +objc_library( + name = "TensorFlowLiteObjCTestLib", + testonly = 1, + srcs = glob([ + "tests/*.m", + ]), + hdrs = glob([ + "apis/*.h", + "sources/*.h", + "tests/*.h", + ]), + copts = TEST_COPTS, + resources = [ + "//tensorflow/contrib/lite:testdata/add.bin", + ], + deps = [ + ":TensorFlowLiteObjCLib", + ], +) diff --git a/tensorflow/contrib/lite/experimental/objc/README.md b/tensorflow/contrib/lite/experimental/objc/README.md new file mode 100644 index 0000000000..e8f150b1e8 --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/README.md @@ -0,0 +1,10 @@ +# TensorFlow Lite Objective-C API + +## TensorFlowLiteObjc Tulsi Project + +Open the `TensorFlowLiteObjc.tulsiproj` using the Tulsi application on Mac or by +running the following command in Terminal from the root source directory: + +```shell +generate_xcodeproj.sh --genconfig tensorflow/contrib/lite/experimental/objc/TensorFlowLiteObjc.tulsiproj:TensorFlowLiteObjC --outputfolder ~/path/to/xcodeproj +``` diff --git a/tensorflow/contrib/lite/experimental/objc/TensorFlowLiteObjc.tulsiproj/Configs/TensorFlowLiteObjc.tulsigen b/tensorflow/contrib/lite/experimental/objc/TensorFlowLiteObjc.tulsiproj/Configs/TensorFlowLiteObjc.tulsigen new file mode 100644 index 0000000000..babb5902d3 --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/TensorFlowLiteObjc.tulsiproj/Configs/TensorFlowLiteObjc.tulsigen @@ -0,0 +1,60 @@ +{ + "sourceFilters" : [ + "third_party/tensorflow/contrib/lite", + "third_party/tensorflow/contrib/lite/experimental/c", + "third_party/tensorflow/contrib/lite/experimental/objc", + "third_party/tensorflow/contrib/lite/experimental/objc/apis", + "third_party/tensorflow/contrib/lite/experimental/objc/sources", + "third_party/tensorflow/contrib/lite/experimental/objc/tests", + "third_party/tensorflow/contrib/lite/kernels", + "third_party/tensorflow/contrib/lite/kernels/internal", + "third_party/tensorflow/contrib/lite/nnapi", + "third_party/tensorflow/contrib/lite/schema", + ], + "buildTargets" : [ + "//third_party/tensorflow/contrib/lite/experimental/objc:TensorFlowLiteObjCLib", + "//third_party/tensorflow/contrib/lite/experimental/objc:TensorFlowLiteObjCTests", + ], + "projectName" : "TensorFlowLiteObjC", + "optionSet" : { + "LaunchActionPreActionScript" : { + "p" : "$(inherited)" + }, + "BazelBuildStartupOptionsRelease" : { + "p" : "$(inherited)" + }, + "BazelBuildOptionsRelease" : { + "p" : "$(inherited)" + }, + "BazelBuildOptionsDebug" : { + "p" : "$(inherited)" + }, + "EnvironmentVariables" : { + "p" : "$(inherited)" + }, + "BuildActionPreActionScript" : { + "p" : "$(inherited)" + }, + "CommandlineArguments" : { + "p" : "$(inherited)" + }, + "TestActionPreActionScript" : { + "p" : "$(inherited)" + }, + "BazelBuildStartupOptionsDebug" : { + "p" : "$(inherited)" + }, + "BuildActionPostActionScript" : { + "p" : "$(inherited)" + }, + "TestActionPostActionScript" : { + "p" : "$(inherited)" + }, + "LaunchActionPostActionScript" : { + "p" : "$(inherited)" + } + }, + "additionalFilePaths" : [ + "third_party/tensorflow/contrib/lite/experimental/objc/BUILD", + ] +} diff --git a/tensorflow/contrib/lite/experimental/objc/TensorFlowLiteObjc.tulsiproj/project.tulsiconf b/tensorflow/contrib/lite/experimental/objc/TensorFlowLiteObjc.tulsiproj/project.tulsiconf new file mode 100644 index 0000000000..00299cd4cf --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/TensorFlowLiteObjc.tulsiproj/project.tulsiconf @@ -0,0 +1,17 @@ +{ + "configDefaults" : { + "optionSet" : { + "BazelBuildOptionsDebug" : { + "p" : "--ios_minimum_os=8.0" + }, + "BazelBuildOptionsRelease" : { + "p" : "--ios_minimum_os=8.0" + }, + } + }, + "projectName" : "TensorFlowLiteObjC", + "packages" : [ + "third_party/tensorflow/contrib/lite/experimental/objc" + ], + "workspaceRoot" : "../../../../../../.." +} diff --git a/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreter.h b/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreter.h new file mode 100644 index 0000000000..c07ffc06ff --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreter.h @@ -0,0 +1,188 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import <Foundation/Foundation.h> + +@class TFLInterpreterOptions; +@class TFLTensor; + +NS_ASSUME_NONNULL_BEGIN + +/** + * @enum TFLInterpreterErrorCode + * This enum specifies various error codes related to `TFLInterpreter`. + */ +typedef NS_ENUM(NSUInteger, TFLInterpreterErrorCode) { + /** Provided tensor index is invalid. */ + TFLInterpreterErrorCodeInvalidTensorIndex, + + /** Input data has invalid byte size. */ + TFLInterpreterErrorCodeInvalidInputByteSize, + + /** Provided shape is invalid. It must be a non-empty array of positive unsigned integers. */ + TFLInterpreterErrorCodeInvalidShape, + + /** Provided model cannot be loaded. */ + TFLInterpreterErrorCodeFailedToLoadModel, + + /** Failed to create `TFLInterpreter`. */ + TFLInterpreterErrorCodeFailedToCreateInterpreter, + + /** Failed to invoke `TFLInterpreter`. */ + TFLInterpreterErrorCodeFailedToInvoke, + + /** Failed to retrieve a tensor. */ + TFLInterpreterErrorCodeFailedToGetTensor, + + /** Failed to resize an input tensor. */ + TFLInterpreterErrorCodeFailedToResizeInputTensor, + + /** Failed to copy data into an input tensor. */ + TFLInterpreterErrorCodeFailedToCopyDataToInputTensor, + + /** Failed to get data from an output tensor. */ + TFLInterpreterErrorCodeFailedToGetDataFromOutputTensor, + + /** Failed to allocate memory for tensors. */ + TFLInterpreterErrorCodeFailedToAllocateTensors, + + /** Operaton not allowed without allocating memory for tensors first. */ + TFLInterpreterErrorCodeAllocateTensorsRequired, + + /** Operaton not allowed without invoking the interpreter first. */ + TFLInterpreterErrorCodeInvokeInterpreterRequired, +}; + +/** + * A TensorFlow Lite model interpreter. + */ +@interface TFLInterpreter : NSObject + +/** The total number of input tensors. 0 if the interpreter creation failed. */ +@property(nonatomic, readonly) NSUInteger inputTensorCount; + +/** The total number of output tensors. 0 if the interpreter creation failed. */ +@property(nonatomic, readonly) NSUInteger outputTensorCount; + +/** Unavailable. */ +- (instancetype)init NS_UNAVAILABLE; + +/** + * Initializes a new TensorFlow Lite interpreter instance with the given model file path and the + * default interpreter options. + * + * @param modelPath An absolute path to a TensorFlow Lite model file stored locally on the device. + * + * @return A new instance of `TFLInterpreter` with the given model and the default interpreter + * options. + */ +- (instancetype)initWithModelPath:(NSString *)modelPath; + +/** + * Initializes a new TensorFlow Lite interpreter instance with the given model file path and + * options. + * + * @param modelPath An absolute path to a TensorFlow Lite model file stored locally on the device. + * @param options Options to use for configuring the TensorFlow Lite interpreter. + * + * @return A new instance of `TFLInterpreter` with the given model and options. + */ +- (instancetype)initWithModelPath:(NSString *)modelPath + options:(TFLInterpreterOptions *)options NS_DESIGNATED_INITIALIZER; + +/** + * Invokes the interpreter to run inference. + * + * @param error An optional error parameter populated when there is an error in invoking the + * interpreter. + * + * @return Whether the invocation is successful. Returns NO if an error occurred. + */ +- (BOOL)invokeWithError:(NSError **)error; + +/** + * Returns the input tensor at the given index. + * + * @param index The index of an input tensor. + * @param error An optional error parameter populated when there is an error in looking up the input + * tensor. + * + * @return The input tensor at the given index. `nil` if there is an error. + */ +- (nullable TFLTensor *)inputTensorAtIndex:(NSUInteger)index error:(NSError **)error; + +/** + * Returns the output tensor at the given index. + * + * @param index The index of an output tensor. + * @param error An optional error parameter populated when there is an error in looking up the + * output tensor. + * + * @return The output tensor at the given index. `nil` if there is an error. + */ +- (nullable TFLTensor *)outputTensorAtIndex:(NSUInteger)index error:(NSError **)error; + +/** + * Resizes the input tensor at the given index to the specified shape (an array of positive unsigned + * integers). + * + * @param index The index of an input tensor. + * @param shape Shape that the given input tensor should be resized to. It should be an array of + * positive unsigned integer(s) containing the size of each dimension. + * @param error An optional error parameter populated when there is an error in resizing the input + * tensor. + * + * @return Whether the input tensor was resized successfully. Returns NO if an error occurred. + */ +- (BOOL)resizeInputTensorAtIndex:(NSUInteger)index + toShape:(NSArray<NSNumber *> *)shape + error:(NSError **)error; + +/** + * Copies the given data into the input tensor at the given index. This is allowed only before the + * interpreter is invoked. + * + * @param data The data to set. The byte size of the data must match what's required by the given + * input tensor. + * @param index The index of an input tensor. + * @param error An optional error parameter populated when there is an error in setting the data. + * + * @return Whether the data was set into the input tensor successfully. Returns NO if an error + * occurred. + */ +- (BOOL)copyData:(NSData *)data toInputTensorAtIndex:(NSUInteger)index error:(NSError **)error; + +/** + * Gets the data from the output tensor at the given index. The interpreter invocation has to + * complete before the data can be retrieved from an output tensor. + * + * @param index The index of an output tensor. + * @param error An optional error parameter populated when there is an error in getting the data. + * + * @return The data of the output tensor at the given index. `nil` if there is an error. + */ +- (nullable NSData *)dataFromOutputTensorAtIndex:(NSUInteger)index error:(NSError **)error; + +/** + * Allocates memory for tensors. + * + * @param error An optional error parameter populated when there is an error in allocating memory. + * + * @return Whether memory allocation is successful. Returns NO if an error occurred. + */ +- (BOOL)allocateTensorsWithError:(NSError **)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreterOptions.h b/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreterOptions.h new file mode 100644 index 0000000000..6461fbf017 --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreterOptions.h @@ -0,0 +1,37 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +/** Custom configuration options for a TensorFlow Lite interpreter. */ +@interface TFLInterpreterOptions : NSObject + +/** + * Maximum number of threads that the interpreter should run on. Defaults to 0 (unspecified, letting + * TensorFlow Lite to optimize the threading decision). + */ +@property(nonatomic) NSUInteger numberOfThreads; + +/** + * Initializes a new instance of `TFLInterpreterOptions`. + * + * @return A new instance of `TFLInterpreterOptions`. + */ +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/contrib/lite/experimental/objc/apis/TFLQuantizationParameters.h b/tensorflow/contrib/lite/experimental/objc/apis/TFLQuantizationParameters.h new file mode 100644 index 0000000000..3d5cf793c5 --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/apis/TFLQuantizationParameters.h @@ -0,0 +1,36 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +/** + * Parameters for asymmetric quantization. Quantized values can be converted to float values using: + * `realValue = scale * (quantizedValue - zeroPoint)`. + */ +@interface TFLQuantizationParameters : NSObject + +/** Scale of asymmetric quantization. */ +@property(nonatomic, readonly) float scale; + +/** Zero point of asymmetric quantization. */ +@property(nonatomic, readonly) int32_t zeroPoint; + +/** Unavailable. */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/contrib/lite/experimental/objc/apis/TFLTensor.h b/tensorflow/contrib/lite/experimental/objc/apis/TFLTensor.h new file mode 100644 index 0000000000..d08b8fc0e9 --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/apis/TFLTensor.h @@ -0,0 +1,77 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import <Foundation/Foundation.h> + +@class TFLQuantizationParameters; + +NS_ASSUME_NONNULL_BEGIN + +/** + * @enum TFLTensorDataType + * This enum specifies supported TensorFlow Lite tensor data types. + */ +typedef NS_ENUM(NSUInteger, TFLTensorDataType) { + /** Tensor data type not available. This indicates an error with the model. */ + TFLTensorDataTypeNoType, + + /** 32-bit single precision floating point. */ + TFLTensorDataTypeFloat32, + + /** 32-bit signed integer. */ + TFLTensorDataTypeInt32, + + /** 8-bit unsigned integer. */ + TFLTensorDataTypeUInt8, + + /** 64-bit signed integer. */ + TFLTensorDataTypeInt64, + + /** Boolean. */ + TFLTensorDataTypeBool, + + /** 16-bit signed integer. */ + TFLTensorDataTypeInt16, +}; + +/** + * An input or output tensor in a TensorFlow Lite model. + */ +@interface TFLTensor : NSObject + +/** Name of the tensor. */ +@property(nonatomic, readonly, copy) NSString *name; + +/** Data type of the tensor. */ +@property(nonatomic, readonly) TFLTensorDataType dataType; + +/** + * Shape of the tensor, an array of positive unsigned integer(s) containing the size of each + * dimension. For example: the shape of [[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]] is + * [2, 2, 3]. + */ +@property(nonatomic, readonly, copy) NSArray<NSNumber *> *shape; + +/** Number of bytes for the tensor data. */ +@property(nonatomic, readonly) NSUInteger byteSize; + +/** Parameters for asymmetric quantization. `nil` if the tensor does not use quantization. */ +@property(nonatomic, readonly, nullable) TFLQuantizationParameters *quantizationParameters; + +/** Unavailable. */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/contrib/lite/experimental/objc/sources/TFLErrorUtil.h b/tensorflow/contrib/lite/experimental/objc/sources/TFLErrorUtil.h new file mode 100644 index 0000000000..b6fd4763d6 --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/sources/TFLErrorUtil.h @@ -0,0 +1,51 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import <Foundation/Foundation.h> + +#import "third_party/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreter.h" + +NS_ASSUME_NONNULL_BEGIN + +/** Helper utility for error reporting. */ +@interface TFLErrorUtil : NSObject + +/** + * Creates and returns an interpreter error with the given error code and description. + * + * @param code Error code. + * @param description Error description. + * + * @return The created interpreter error with the given error code and description. + */ ++ (NSError *)interpreterErrorWithCode:(TFLInterpreterErrorCode)code + description:(NSString *)description; + +/** + * Creates and saves an interpreter error with the given error code and description. + * + * @param code Error code. + * @param description Error description. + * @param error Pointer to where to save the created error. If `nil`, no error will be saved. + */ ++ (void)saveInterpreterErrorWithCode:(TFLInterpreterErrorCode)code + description:(NSString *)description + error:(NSError **)error; + +/** Unavailable. */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/contrib/lite/experimental/objc/sources/TFLErrorUtil.m b/tensorflow/contrib/lite/experimental/objc/sources/TFLErrorUtil.m new file mode 100644 index 0000000000..756d69481c --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/sources/TFLErrorUtil.m @@ -0,0 +1,45 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "TFLErrorUtil.h" + +NS_ASSUME_NONNULL_BEGIN + +/** Error domain of TensorFlow Lite interpreter related errors. */ +static NSString *const TFLInterpreterErrorDomain = @"org.tensorflow.lite.interpreter"; + +@implementation TFLErrorUtil + +#pragma mark - Public + ++ (NSError *)interpreterErrorWithCode:(TFLInterpreterErrorCode)code + description:(NSString *)description { + return [NSError errorWithDomain:TFLInterpreterErrorDomain + code:code + userInfo:@{NSLocalizedDescriptionKey : description}]; +} + ++ (void)saveInterpreterErrorWithCode:(TFLInterpreterErrorCode)code + description:(NSString *)description + error:(NSError **)error { + if (error) { + *error = [NSError errorWithDomain:TFLInterpreterErrorDomain + code:code + userInfo:@{NSLocalizedDescriptionKey : description}]; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/contrib/lite/experimental/objc/sources/TFLInterpreter.mm b/tensorflow/contrib/lite/experimental/objc/sources/TFLInterpreter.mm new file mode 100644 index 0000000000..0f940a5cf3 --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/sources/TFLInterpreter.mm @@ -0,0 +1,440 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "third_party/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreter.h" + +#import "TFLErrorUtil.h" +#import "TFLTensor+Internal.h" +#import "third_party/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreterOptions.h" +#import "third_party/tensorflow/contrib/lite/experimental/objc/apis/TFLTensor.h" + +#include "third_party/tensorflow/contrib/lite/experimental/c/c_api.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * @enum TFLTensorType + * This enum specifies input or output tensor types. + */ +typedef NS_ENUM(NSUInteger, TFLTensorType) { + /** Input tensor type. */ + TFLTensorTypeInput, + + /** Output tensor type. */ + TFLTensorTypeOutput, +}; + +// Names used for indicating input or output in error messages. +static NSString *const kTFLInputDirection = @"input"; +static NSString *const kTFLOutputDirection = @"output"; + +/** + * Error reporter for TFLInterpreter. + * + * @param user_data User data. Not used. + * @param format Error message which may contain argument formatting specifiers. + * @param args Values of the arguments in the error message. + */ +static void TFLInterpreterErrorReporter(void *user_data, const char *format, va_list args) { + NSLog(@"%@", [[NSString alloc] initWithFormat:@(format) arguments:args]); +} + +@interface TFLInterpreter () + +/** TFL_Interpreter backed by C API. */ +@property(nonatomic, nullable) TFL_Interpreter *interpreter; + +/** + * An error in initializing the interpreter. If not `nil`, this error will be reported when the + * interpreter is used. + */ +@property(nonatomic, nullable) NSError *initializationError; + +@end + +@implementation TFLInterpreter + +#pragma mark - NSObject + +- (void)dealloc { + TFL_DeleteInterpreter(_interpreter); +} + +#pragma mark - Public + +- (instancetype)initWithModelPath:(NSString *)modelPath { + return [self initWithModelPath:modelPath options:[[TFLInterpreterOptions alloc] init]]; +} + +- (instancetype)initWithModelPath:(NSString *)modelPath options:(TFLInterpreterOptions *)options { + self = [super init]; + + if (self != nil) { + const char *modelPathCString = modelPath.UTF8String; + NSString *pathErrorString = + [NSString stringWithFormat:@"Cannot load model from path (%@).", modelPath]; + if (modelPathCString == nullptr) { + _initializationError = + [TFLErrorUtil interpreterErrorWithCode:TFLInterpreterErrorCodeFailedToLoadModel + description:pathErrorString]; + return self; + } + + TFL_Model *model = TFL_NewModelFromFile(modelPathCString); + if (model == nullptr) { + _initializationError = + [TFLErrorUtil interpreterErrorWithCode:TFLInterpreterErrorCodeFailedToLoadModel + description:pathErrorString]; + return self; + } + + TFL_InterpreterOptions *cOptions = TFL_NewInterpreterOptions(); + if (cOptions == nullptr) { + _initializationError = + [TFLErrorUtil interpreterErrorWithCode:TFLInterpreterErrorCodeFailedToCreateInterpreter + description:@"Failed to create the interpreter."]; + TFL_DeleteModel(model); + return self; + } + + if (options.numberOfThreads > 0) { + TFL_InterpreterOptionsSetNumThreads(cOptions, (int32_t)options.numberOfThreads); + } + TFL_InterpreterOptionsSetErrorReporter(cOptions, TFLInterpreterErrorReporter, nullptr); + + _interpreter = TFL_NewInterpreter(model, cOptions); + if (_interpreter == nullptr) { + _initializationError = + [TFLErrorUtil interpreterErrorWithCode:TFLInterpreterErrorCodeFailedToCreateInterpreter + description:@"Failed to create the interpreter."]; + } else { + _inputTensorCount = (NSUInteger)TFL_InterpreterGetInputTensorCount(_interpreter); + _outputTensorCount = (NSUInteger)TFL_InterpreterGetOutputTensorCount(_interpreter); + if (_inputTensorCount <= 0 || _outputTensorCount <= 0) { + _initializationError = + [TFLErrorUtil interpreterErrorWithCode:TFLInterpreterErrorCodeFailedToCreateInterpreter + description:@"Failed to create the interpreter."]; + } + } + TFL_DeleteInterpreterOptions(cOptions); + TFL_DeleteModel(model); + } + + return self; +} + +- (BOOL)invokeWithError:(NSError **)error { + if (self.initializationError != nil) { + [self saveInitializationErrorToDestination:error]; + return NO; + } + + if (TFL_InterpreterInvoke(self.interpreter) != kTfLiteOk) { + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToInvoke + description:@"Failed to invoke the interpreter." + error:error]; + return NO; + } + + return YES; +} + +- (nullable TFLTensor *)inputTensorAtIndex:(NSUInteger)index error:(NSError **)error { + if (self.initializationError != nil) { + [self saveInitializationErrorToDestination:error]; + return nil; + } + + if (![self isValidTensorIndex:index belowLimit:self.inputTensorCount error:error]) { + return nil; + } + + return [self tensorOfType:TFLTensorTypeInput atIndex:index error:error]; +} + +- (nullable TFLTensor *)outputTensorAtIndex:(NSUInteger)index error:(NSError **)error { + if (self.initializationError != nil) { + [self saveInitializationErrorToDestination:error]; + return nil; + } + + if (![self isValidTensorIndex:index belowLimit:self.outputTensorCount error:error]) { + return nil; + } + + return [self tensorOfType:TFLTensorTypeOutput atIndex:index error:error]; +} + +- (BOOL)resizeInputTensorAtIndex:(NSUInteger)index + toShape:(NSArray<NSNumber *> *)shape + error:(NSError **)error { + if (self.initializationError != nil) { + [self saveInitializationErrorToDestination:error]; + return NO; + } + + if (![self isValidTensorIndex:index belowLimit:self.inputTensorCount error:error]) { + return NO; + } + + if (shape.count == 0) { + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeInvalidShape + description:@"Invalid shape. Must not be empty." + error:error]; + return NO; + } + + int cDimensions[self.inputTensorCount]; + for (int d = 0; d < shape.count; ++d) { + int dimension = shape[d].intValue; + if (dimension <= 0) { + NSString *errorDescription = @"Invalid shape. Dimensions must be positive integers."; + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeInvalidShape + description:errorDescription + error:error]; + return NO; + } + cDimensions[d] = dimension; + } + + if (TFL_InterpreterResizeInputTensor(self.interpreter, (int32_t)index, cDimensions, + (int32_t)shape.count) != kTfLiteOk) { + NSString *errorDescription = [NSString + stringWithFormat:@"Failed to resize input tensor at index (%lu).", (unsigned long)index]; + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToResizeInputTensor + description:errorDescription + error:error]; + return NO; + } + + return YES; +} + +- (BOOL)copyData:(NSData *)data toInputTensorAtIndex:(NSUInteger)index error:(NSError **)error { + if (self.initializationError != nil) { + [self saveInitializationErrorToDestination:error]; + return NO; + } + + if (![self isValidTensorIndex:index belowLimit:self.inputTensorCount error:error]) { + return NO; + } + + TFL_Tensor *tensor = TFL_InterpreterGetInputTensor(self.interpreter, (int32_t)index); + if (tensor == nullptr) { + NSString *errorDescription = [NSString + stringWithFormat:@"Failed to get input tensor at index (%lu).", (unsigned long)index]; + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToCopyDataToInputTensor + description:errorDescription + error:error]; + return NO; + } + + NSUInteger byteSize = (NSUInteger)TFL_TensorByteSize(tensor); + if (data.length != byteSize) { + NSString *errorDescription = [NSString + stringWithFormat:@"Input tensor at index (%lu) expects data size (%lu), but got (%lu).", + (unsigned long)index, byteSize, (unsigned long)data.length]; + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeInvalidInputByteSize + description:errorDescription + error:error]; + return NO; + } + + if (TFL_TensorCopyFromBuffer(tensor, data.bytes, data.length) != kTfLiteOk) { + NSString *errorDescription = + [NSString stringWithFormat:@"Failed to copy data into input tensor at index (%lu).", + (unsigned long)index]; + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToCopyDataToInputTensor + description:errorDescription + error:error]; + return NO; + } + + return YES; +} + +- (nullable NSData *)dataFromOutputTensorAtIndex:(NSUInteger)index error:(NSError **)error { + if (self.initializationError != nil) { + [self saveInitializationErrorToDestination:error]; + return nil; + } + + if (![self isValidTensorIndex:index belowLimit:self.outputTensorCount error:error]) { + return nil; + } + + const TFL_Tensor *tensor = TFL_InterpreterGetOutputTensor(self.interpreter, (int32_t)index); + if (tensor == nullptr) { + NSString *errorDescription = [NSString + stringWithFormat:@"Failed to get output tensor at index (%lu).", (unsigned long)index]; + [TFLErrorUtil + saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToGetDataFromOutputTensor + description:errorDescription + error:error]; + return nil; + } + + void *bytes = TFL_TensorData(tensor); + NSUInteger byteSize = (NSUInteger)TFL_TensorByteSize(tensor); + if (bytes == nullptr || byteSize == 0) { + NSString *errorDescription = [NSString + stringWithFormat:@"Failed to get output tensor data at index (%lu).", (unsigned long)index]; + [TFLErrorUtil + saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToGetDataFromOutputTensor + description:errorDescription + error:error]; + return nil; + } + + return [NSData dataWithBytes:bytes length:byteSize]; +} + +- (BOOL)allocateTensorsWithError:(NSError **)error { + if (self.initializationError != nil) { + [self saveInitializationErrorToDestination:error]; + return NO; + } + + if (TFL_InterpreterAllocateTensors(self.interpreter) != kTfLiteOk) { + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToAllocateTensors + description:@"Failed to allocate memory for tensors." + error:error]; + return NO; + } + return YES; +} + +#pragma mark - Private + +- (nullable TFLTensor *)tensorOfType:(TFLTensorType)type + atIndex:(NSUInteger)index + error:(NSError **)error { + const TFL_Tensor *tensor = nullptr; + NSString *tensorType; + switch (type) { + case TFLTensorTypeInput: + tensor = TFL_InterpreterGetInputTensor(self.interpreter, (int32_t)index); + tensorType = kTFLInputDirection; + break; + case TFLTensorTypeOutput: + tensor = TFL_InterpreterGetOutputTensor(self.interpreter, (int32_t)index); + tensorType = kTFLOutputDirection; + break; + } + + if (tensor == nullptr) { + NSString *errorDescription = + [NSString stringWithFormat:@"Failed to get %@ tensor at index (%lu).", tensorType, + (unsigned long)index]; + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToGetTensor + description:errorDescription + error:error]; + return nil; + } + + const char *cName = TFL_TensorName(tensor); + if (cName == nullptr) { + NSString *errorDescription = + [NSString stringWithFormat:@"Failed to get name of %@ tensor at index (%lu).", tensorType, + (unsigned long)index]; + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToGetTensor + description:errorDescription + error:error]; + return nil; + } + NSString *name = [NSString stringWithUTF8String:cName]; + + TFLTensorDataType dataType = [self tensorDataTypeFromCTensorType:TFL_TensorType(tensor)]; + + int32_t rank = TFL_TensorNumDims(tensor); + if (rank <= 0) { + NSString *errorDescription = + [NSString stringWithFormat:@"%@ tensor at index (%lu) has invalid rank (%d).", tensorType, + (unsigned long)index, rank]; + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToGetTensor + description:errorDescription + error:error]; + return nil; + } + NSMutableArray *shape = [NSMutableArray arrayWithCapacity:rank]; + for (int32_t d = 0; d < rank; d++) { + int32_t dimension = TFL_TensorDim(tensor, d); + if (dimension <= 0) { + NSString *errorDescription = + [NSString stringWithFormat:@"%@ tensor at index (%lu) has invalid %d-th dimension (%d).", + tensorType, (unsigned long)index, d, dimension]; + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeFailedToGetTensor + description:errorDescription + error:error]; + return nil; + } + shape[d] = @((NSUInteger)dimension); + } + + // TODO: Set quantization parameters when C API supports it. + return [[TFLTensor alloc] initWithName:name + dataType:dataType + shape:shape + byteSize:(NSUInteger)TFL_TensorByteSize(tensor) + quantizationParameters:nil]; +} + +- (TFLTensorDataType)tensorDataTypeFromCTensorType:(TFL_Type)cTensorType { + switch (cTensorType) { + case kTfLiteFloat32: + return TFLTensorDataTypeFloat32; + case kTfLiteInt32: + return TFLTensorDataTypeInt32; + case kTfLiteUInt8: + return TFLTensorDataTypeUInt8; + case kTfLiteInt64: + return TFLTensorDataTypeInt64; + case kTfLiteBool: + return TFLTensorDataTypeBool; + case kTfLiteInt16: + return TFLTensorDataTypeInt16; + case kTfLiteNoType: + case kTfLiteString: + case kTfLiteComplex64: + // kTfLiteString and kTfLiteComplex64 are not supported in TensorFlow Lite Objc API. + return TFLTensorDataTypeNoType; + } +} + +- (void)saveInitializationErrorToDestination:(NSError **)destination { + if (destination != NULL) { + *destination = self.initializationError; + } +} + +- (BOOL)isValidTensorIndex:(NSUInteger)index + belowLimit:(NSUInteger)totalTensorCount + error:(NSError **)error { + if (index >= totalTensorCount) { + NSString *errorDescription = + [NSString stringWithFormat:@"Invalid tensor index (%lu) exceeds max (%lu).", + (unsigned long)index, (unsigned long)(totalTensorCount - 1)]; + [TFLErrorUtil saveInterpreterErrorWithCode:TFLInterpreterErrorCodeInvalidTensorIndex + description:errorDescription + error:error]; + return NO; + } + + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/contrib/lite/experimental/objc/sources/TFLInterpreterOptions.m b/tensorflow/contrib/lite/experimental/objc/sources/TFLInterpreterOptions.m new file mode 100644 index 0000000000..1776688288 --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/sources/TFLInterpreterOptions.m @@ -0,0 +1,30 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "third_party/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreterOptions.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation TFLInterpreterOptions + +#pragma mark - Public + +- (instancetype)init { + self = [super init]; + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/contrib/lite/experimental/objc/sources/TFLQuantizationParameters.m b/tensorflow/contrib/lite/experimental/objc/sources/TFLQuantizationParameters.m new file mode 100644 index 0000000000..190f0479ce --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/sources/TFLQuantizationParameters.m @@ -0,0 +1,23 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "third_party/tensorflow/contrib/lite/experimental/objc/apis/TFLQuantizationParameters.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation TFLQuantizationParameters + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/contrib/lite/experimental/objc/sources/TFLTensor+Internal.h b/tensorflow/contrib/lite/experimental/objc/sources/TFLTensor+Internal.h new file mode 100644 index 0000000000..f2f13e5e5f --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/sources/TFLTensor+Internal.h @@ -0,0 +1,42 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "third_party/tensorflow/contrib/lite/experimental/objc/apis/TFLTensor.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface TFLTensor (Internal) + +/** + * Initializes a `TFLTensor` with the given name, data type, shape, and quantization parameters. + * + * @param name Name of the tensor. + * @param dataType Data type of the tensor. + * @param shape Shape of the tensor. + * @param byteSize Size of the tensor data in number of bytes. + * @param quantizationParameters Quantization parameters of the tensor. `nil` if the tensor does not + * use quantization. + * + * @return A new instance of `TFLTensor` with the given name, data type, shape, and quantization + * parameters. + */ +- (instancetype)initWithName:(NSString *)name + dataType:(TFLTensorDataType)dataType + shape:(NSArray<NSNumber *> *)shape + byteSize:(NSUInteger)byteSize + quantizationParameters:(nullable TFLQuantizationParameters *)quantizationParameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/contrib/lite/experimental/objc/sources/TFLTensor.m b/tensorflow/contrib/lite/experimental/objc/sources/TFLTensor.m new file mode 100644 index 0000000000..adb1c5ad2c --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/sources/TFLTensor.m @@ -0,0 +1,54 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "third_party/tensorflow/contrib/lite/experimental/objc/apis/TFLTensor.h" + +#import "TFLTensor+Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface TFLTensor () + +// Redefines readonly properties. +@property(nonatomic, copy) NSString *name; +@property(nonatomic) TFLTensorDataType dataType; +@property(nonatomic, copy) NSArray<NSNumber *> *shape; +@property(nonatomic) NSUInteger byteSize; +@property(nonatomic, nullable) TFLQuantizationParameters *quantizationParameters; + +@end + +@implementation TFLTensor + +#pragma mark - TFLTensor (Internal) + +- (instancetype)initWithName:(NSString *)name + dataType:(TFLTensorDataType)dataType + shape:(NSArray<NSNumber *> *)shape + byteSize:(NSUInteger)byteSize + quantizationParameters:(nullable TFLQuantizationParameters *)quantizationParameters { + self = [super init]; + if (self != nil) { + _name = [name copy]; + _dataType = dataType; + _shape = [shape copy]; + _byteSize = byteSize; + _quantizationParameters = quantizationParameters; + } + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/contrib/lite/experimental/objc/tests/TFLInterpreterOptionsTests.m b/tensorflow/contrib/lite/experimental/objc/tests/TFLInterpreterOptionsTests.m new file mode 100644 index 0000000000..17c495fa18 --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/tests/TFLInterpreterOptionsTests.m @@ -0,0 +1,49 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "third_party/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreterOptions.h" + +#import <XCTest/XCTest.h> + +NS_ASSUME_NONNULL_BEGIN + +/** + * Unit tests for TFLInterpreterOptions. + */ +@interface TFLInterpreterOptionsTests : XCTestCase +@end + +@implementation TFLInterpreterOptionsTests + +#pragma mark - Tests + +- (void)testInit { + TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init]; + XCTAssertNotNil(options); + XCTAssertEqual(options.numberOfThreads, 0); +} + +- (void)testSetNumberOfThread { + TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init]; + options.numberOfThreads = 2; + XCTAssertEqual(options.numberOfThreads, 2); + options.numberOfThreads = 0; + XCTAssertEqual(options.numberOfThreads, 0); + options.numberOfThreads = 3; + XCTAssertEqual(options.numberOfThreads, 3); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/contrib/lite/experimental/objc/tests/TFLInterpreterTests.m b/tensorflow/contrib/lite/experimental/objc/tests/TFLInterpreterTests.m new file mode 100644 index 0000000000..9e6319a732 --- /dev/null +++ b/tensorflow/contrib/lite/experimental/objc/tests/TFLInterpreterTests.m @@ -0,0 +1,266 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "third_party/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreter.h" + +#import <XCTest/XCTest.h> + +#import "third_party/tensorflow/contrib/lite/experimental/objc/apis/TFLInterpreterOptions.h" +#import "third_party/tensorflow/contrib/lite/experimental/objc/apis/TFLTensor.h" + +NS_ASSUME_NONNULL_BEGIN + +/** Model resource name. */ +static NSString *const kAddModelResourceName = @"add"; + +/** Model resource type. */ +static NSString *const kAddModelResourceType = @"bin"; + +/** Rank of the input and output tensor in the Add model. */ +static const NSUInteger kAddModelTensorRank = 1U; + +/** Size of the first (and only) dimension of the input and output tensor in the Add model. */ +static const NSUInteger kAddModelTensorFirstDimensionSize = 2U; + +/** Invalid input tensor index. */ +static const NSUInteger kInvalidInputTensorIndex = 1U; + +/** Invalid output tensor index. */ +static const NSUInteger kInvalidOutputTensorIndex = 1U; + +/** Accurary used in comparing floating numbers. */ +static const float kTestAccuracy = 1E-5F; + +/** + * Unit tests for TFLInterpreter. + */ +@interface TFLInterpreterTests : XCTestCase + +/** Absolute path of the Add model resource. */ +@property(nonatomic, nullable) NSString *modelPath; + +/** Default interpreter using the Add model. */ +@property(nonatomic, nullable) TFLInterpreter *interpreter; + +@end + +@implementation TFLInterpreterTests + +#pragma mark - XCTestCase + +- (void)setUp { + [super setUp]; + + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + self.modelPath = [bundle pathForResource:kAddModelResourceName ofType:kAddModelResourceType]; + self.interpreter = [[TFLInterpreter alloc] initWithModelPath:self.modelPath]; + XCTAssertNotNil(self.interpreter); + XCTAssertTrue([self.interpreter allocateTensorsWithError:nil]); +} + +- (void)tearDown { + self.modelPath = nil; + self.interpreter = nil; + + [super tearDown]; +} + +#pragma mark - Tests + +- (void)testSuccessfulFullRun { + // Shape for both input and output tensor. + NSMutableArray *shape = [NSMutableArray arrayWithCapacity:kAddModelTensorRank]; + shape[0] = [NSNumber numberWithUnsignedInteger:kAddModelTensorFirstDimensionSize]; + + // Creates the interpreter options. + TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init]; + XCTAssertNotNil(options); + options.numberOfThreads = 2; + + // Creates the interpreter. + TFLInterpreter *customInterpreter = [[TFLInterpreter alloc] initWithModelPath:self.modelPath + options:options]; + XCTAssertNotNil(customInterpreter); + + // Allocates memory for tensors. + NSError *error; + XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]); + XCTAssertNil(error); + + // Verifies input and output tensor counts. + XCTAssertEqual(customInterpreter.inputTensorCount, 1); + XCTAssertEqual(customInterpreter.outputTensorCount, 1); + + // Resizes the intput tensor. + XCTAssertTrue([customInterpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]); + XCTAssertNil(error); + + // Re-allocates memory for tensors. + XCTAssertTrue([customInterpreter allocateTensorsWithError:&error]); + XCTAssertNil(error); + + // Verifies the input tensor. + TFLTensor *inputTensor = [customInterpreter inputTensorAtIndex:0 error:&error]; + XCTAssertNotNil(inputTensor); + XCTAssertNil(error); + XCTAssertTrue([inputTensor.name isEqualToString:@"input"]); + XCTAssertEqual(inputTensor.dataType, TFLTensorDataTypeFloat32); + XCTAssertTrue([shape isEqualToArray:inputTensor.shape]); + XCTAssertEqual(inputTensor.byteSize, sizeof(float) * kAddModelTensorFirstDimensionSize); + + // Copies the input data. + NSMutableData *inputData = [NSMutableData dataWithCapacity:0]; + float one = 1.f; + float three = 3.f; + [inputData appendBytes:&one length:sizeof(float)]; + [inputData appendBytes:&three length:sizeof(float)]; + XCTAssertTrue([customInterpreter copyData:inputData toInputTensorAtIndex:0 error:&error]); + XCTAssertNil(error); + + // Invokes the interpreter. + XCTAssertTrue([customInterpreter invokeWithError:&error]); + XCTAssertNil(error); + + // Verifies the output tensor. + TFLTensor *outputTensor = [customInterpreter outputTensorAtIndex:0 error:&error]; + XCTAssertNotNil(outputTensor); + XCTAssertNil(error); + XCTAssertTrue([outputTensor.name isEqualToString:@"output"]); + XCTAssertEqual(outputTensor.dataType, TFLTensorDataTypeFloat32); + XCTAssertTrue([shape isEqualToArray:outputTensor.shape]); + XCTAssertEqual(outputTensor.byteSize, sizeof(float) * kAddModelTensorFirstDimensionSize); + + // Tries to query an invalid output tensor index. + TFLTensor *invalidOutputTensor = [customInterpreter outputTensorAtIndex:kInvalidOutputTensorIndex + error:&error]; + XCTAssertNil(invalidOutputTensor); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex); + + // Gets the output tensor data. + error = nil; + NSData *outputData = [customInterpreter dataFromOutputTensorAtIndex:0 error:&error]; + XCTAssertNotNil(outputData); + XCTAssertNil(error); + float output[kAddModelTensorFirstDimensionSize]; + [outputData getBytes:output length:(sizeof(float) * kAddModelTensorFirstDimensionSize)]; + XCTAssertEqualWithAccuracy(output[0], 3.f, kTestAccuracy); + XCTAssertEqualWithAccuracy(output[1], 9.f, kTestAccuracy); +} + +- (void)testInitWithModelPath_invalidPath { + // Shape for both input and output tensor. + NSMutableArray *shape = [NSMutableArray arrayWithCapacity:kAddModelTensorRank]; + shape[0] = [NSNumber numberWithUnsignedInteger:kAddModelTensorFirstDimensionSize]; + + // Creates the interpreter. + TFLInterpreter *brokenInterpreter = [[TFLInterpreter alloc] initWithModelPath:@"InvalidPath"]; + XCTAssertNotNil(brokenInterpreter); + XCTAssertEqual(brokenInterpreter.inputTensorCount, 0); + XCTAssertEqual(brokenInterpreter.outputTensorCount, 0); + + // Allocates memory for tensors. + NSError *error; + XCTAssertFalse([brokenInterpreter allocateTensorsWithError:&error]); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToLoadModel); + + // Resizes the intput tensor. + XCTAssertFalse([brokenInterpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToLoadModel); + + // Verifies the input tensor. + TFLTensor *inputTensor = [brokenInterpreter inputTensorAtIndex:0 error:&error]; + XCTAssertNil(inputTensor); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToLoadModel); + + // Copies the input data. + NSMutableData *inputData = [NSMutableData dataWithCapacity:0]; + float one = 1.f; + float three = 3.f; + [inputData appendBytes:&one length:sizeof(float)]; + [inputData appendBytes:&three length:sizeof(float)]; + XCTAssertFalse([brokenInterpreter copyData:inputData toInputTensorAtIndex:0 error:&error]); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToLoadModel); + + // Invokes the interpreter. + XCTAssertFalse([brokenInterpreter invokeWithError:&error]); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToLoadModel); + + // Verifies the output tensor. + TFLTensor *outputTensor = [brokenInterpreter outputTensorAtIndex:0 error:&error]; + XCTAssertNil(outputTensor); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToLoadModel); + + // Gets the output tensor data. + NSData *outputData = [brokenInterpreter dataFromOutputTensorAtIndex:0 error:&error]; + XCTAssertNil(outputData); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToLoadModel); +} + +- (void)testInvoke_beforeAllocation { + TFLInterpreter *interpreterWithoutAllocation = + [[TFLInterpreter alloc] initWithModelPath:self.modelPath]; + XCTAssertNotNil(interpreterWithoutAllocation); + + NSError *error; + XCTAssertFalse([interpreterWithoutAllocation invokeWithError:&error]); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeFailedToInvoke); +} + +- (void)testInputTensorAtIndex_invalidIndex { + NSError *error; + TFLTensor *inputTensor = [self.interpreter inputTensorAtIndex:kInvalidInputTensorIndex + error:&error]; + XCTAssertNil(inputTensor); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex); +} + +- (void)testResizeInputTensorAtIndex_invalidIndex { + NSMutableArray *shape = [NSMutableArray arrayWithCapacity:kAddModelTensorRank]; + shape[0] = [NSNumber numberWithUnsignedInteger:kAddModelTensorFirstDimensionSize]; + NSError *error; + XCTAssertFalse([self.interpreter resizeInputTensorAtIndex:kInvalidInputTensorIndex + toShape:shape + error:&error]); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidTensorIndex); +} + +- (void)testResizeInputTensorAtIndex_emptyShape { + NSMutableArray *emptyShape = [NSMutableArray arrayWithCapacity:0]; + NSError *error; + XCTAssertFalse([self.interpreter resizeInputTensorAtIndex:0 toShape:emptyShape error:&error]); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidShape); +} + +- (void)testResizeInputTensorAtIndex_zeroDimensionSize { + NSMutableArray *shape = [NSMutableArray arrayWithCapacity:kAddModelTensorRank]; + shape[0] = [NSNumber numberWithUnsignedInteger:0]; + NSError *error; + XCTAssertFalse([self.interpreter resizeInputTensorAtIndex:0 toShape:shape error:&error]); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidShape); +} + +- (void)testCopyDataToInputTensorAtIndex_invalidInputDataByteSize { + NSMutableData *inputData = [NSMutableData dataWithCapacity:0]; + float one = 1.f; + float three = 3.f; + [inputData appendBytes:&one length:sizeof(float)]; + [inputData appendBytes:&three length:(sizeof(float) - 1)]; + NSError *error; + XCTAssertFalse([self.interpreter copyData:inputData toInputTensorAtIndex:0 error:&error]); + XCTAssertEqual(error.code, TFLInterpreterErrorCodeInvalidInputByteSize); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/tensorflow/tools/pip_package/pip_smoke_test.py b/tensorflow/tools/pip_package/pip_smoke_test.py index c6ef82ccdc..31b68c8f00 100644 --- a/tensorflow/tools/pip_package/pip_smoke_test.py +++ b/tensorflow/tools/pip_package/pip_smoke_test.py @@ -105,6 +105,7 @@ BLACKLIST = [ "//tensorflow/contrib/timeseries/python/timeseries:test_utils", "//tensorflow/contrib/timeseries/python/timeseries/state_space_models:test_utils", # pylint:disable=line-too-long "//tensorflow/contrib/image:sparse_image_warp_test_data", + "//tools/build_defs/apple:ios.bzl", ] |