From 5642980ab01466b1fce7c1abad701ba2f0f13a76 Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Fri, 13 Jan 2023 21:04:03 +0530 Subject: [PATCH 01/14] Updated iOS error implementation to mimic java --- .../tasks/ios/common/sources/MPPCommon.h | 159 +++--------------- .../common/utils/sources/MPPCommonUtils.mm | 125 +++++++++----- 2 files changed, 104 insertions(+), 180 deletions(-) diff --git a/mediapipe/tasks/ios/common/sources/MPPCommon.h b/mediapipe/tasks/ios/common/sources/MPPCommon.h index 09a61e20..f8047fc3 100644 --- a/mediapipe/tasks/ios/common/sources/MPPCommon.h +++ b/mediapipe/tasks/ios/common/sources/MPPCommon.h @@ -25,153 +25,44 @@ typedef NS_ENUM(NSUInteger, MPPTasksErrorCode) { // Generic error codes. - // Unspecified error. - MPPTasksErrorCodeError = 1, - // Invalid argument specified. - MPPTasksErrorCodeInvalidArgumentError = 2, - // Invalid FlatBuffer file or buffer specified. - MPPTasksErrorCodeInvalidFlatBufferError = 3, - // Model contains a builtin op that isn't supported by the OpResolver or - // delegates. - MPPTasksErrorCodeUnsupportedBuiltinOp = 4, - // Model contains a custom op that isn't supported by the OpResolver or - // delegates. - MPPTasksErrorCodeUnsupportedCustomOp = 5, + /** Indicates the operation was cancelled, typically by the caller. */ + MPPTasksErrorCodeCancelledError = 1, + /** Indicates an unknown error occurred. */ + MPPTasksErrorCodeUnknownError = 2, + /** Indicates the caller specified an invalid argument, such as a malformed filename. */ + MPPTasksErrorCodeInvalidArgumentError = 3, + /** Indicates a deadline expired before the operation could complete. */ + MPPTasksErrorCodeDeadlineExceededError = 4, + /** Indicates some requested entity (such as a file or directory) was not found. */ + MPPTasksErrorCodeNotFoundError = 5, + /** Indicates that the entity a caller attempted to create (such as a file or directory) is already present. */ + MPPTasksErrorCodeAlreadyExistsError = 6, + /** Indicates that the caller does not have permission to execute the specified operation. */ + MPPTasksErrorCodePermissionDeniedError = 7, - // File I/O error codes. + MPPTasksErrorCodeResourceExhaustedError = 8, - // No such file. - MPPTasksErrorCodeFileNotFoundError = 100, - // Permission issue. - MPPTasksErrorCodeFilePermissionDeniedError, - // I/O error when reading file. - MPPTasksErrorCodeFileReadError, - // I/O error when mmap-ing file. - MPPTasksErrorCodeFileMmapError, - // ZIP I/O error when unpacking the zip file. - MPPTasksErrorCodeFileZipError, + MPPTasksErrorCodeFailedPreconditionError = 9, - // TensorFlow Lite metadata error codes. + MPPTasksErrorCodeAbortedError = 10, - // Unexpected schema version (aka file_identifier) in the Metadata FlatBuffer. - MPPTasksErrorCodeMetadataInvalidSchemaVersionError = 200, - // No such associated file within metadata, or file has not been packed. - MPPTasksErrorCodeMetadataAssociatedFileNotFoundError, - // ZIP I/O error when unpacking an associated file. - MPPTasksErrorCodeMetadataAssociatedFileZipError, - // Inconsistency error between the metadata and actual TF Lite model. - // E.g.: number of labels and output tensor values differ. - MPPTasksErrorCodeMetadataInconsistencyError, - // Invalid process units specified. - // E.g.: multiple ProcessUnits with the same type for a given tensor. - MPPTasksErrorCodeMetadataInvalidProcessUnitsError, - // Inconsistency error with the number of labels. - // E.g.: label files for different locales have a different number of labels. - MPPTasksErrorCodeMetadataNumLabelsMismatchError, - // Score calibration parameters parsing error. - // E.g.: too many parameters provided in the corresponding associated file. - MPPTasksErrorCodeMetadataMalformedScoreCalibrationError, - // Unexpected number of subgraphs for the current task. - // E.g.: image classification expects a single subgraph. - MPPTasksErrorCodeMetadataInvalidNumSubgraphsError, - // A given tensor requires NormalizationOptions but none were found. - // E.g.: float input tensor requires normalization to preprocess input images. - MPPTasksErrorCodeMetadataMissingNormalizationOptionsError, - // Invalid ContentProperties specified. - // E.g. expected ImageProperties, got BoundingBoxProperties. - MPPTasksErrorCodeMetadataInvalidContentPropertiesError, - // Metadata is mandatory but was not found. - // E.g. current task requires TFLite Model Metadata but none was found. - MPPTasksErrorCodeMetadataNotFoundError, - // Associated TENSOR_AXIS_LABELS or TENSOR_VALUE_LABELS file is mandatory but - // none was found or it was empty. - // E.g. current task requires labels but none were found. - MPPTasksErrorCodeMetadataMissingLabelsError, - // The ProcessingUnit for tokenizer is not correctly configured. - // E.g BertTokenizer doesn't have a valid vocab file associated. - MPPTasksErrorCodeMetadataInvalidTokenizerError, + MPPTasksErrorCodeOutOfRangeError = 11, - // Input tensor(s) error codes. + MPPTasksErrorCodeUnimplementedError = 12, - // Unexpected number of input tensors for the current task. - // E.g. current task expects a single input tensor. - MPPTasksErrorCodeInvalidNumInputTensorsError = 300, - // Unexpected input tensor dimensions for the current task. - // E.g.: only 4D input tensors supported. - MPPTasksErrorCodeInvalidInputTensorDimensionsError, - // Unexpected input tensor type for the current task. - // E.g.: current task expects a uint8 pixel image as input. - MPPTasksErrorCodeInvalidInputTensorTypeError, - // Unexpected input tensor bytes size. - // E.g.: size in bytes does not correspond to the expected number of pixels. - MPPTasksErrorCodeInvalidInputTensorSizeError, - // No correct input tensor found for the model. - // E.g.: input tensor name is not part of the text model's input tensors. - MPPTasksErrorCodeInputTensorNotFoundError, + MPPTasksErrorCodeInternalError = 13, - // Output tensor(s) error codes. + MPPTasksErrorCodeUnavailableError = 14, - // Unexpected output tensor dimensions for the current task. - // E.g.: only a batch size of 1 is supported. - MPPTasksErrorCodeInvalidOutputTensorDimensionsError = 400, - // Unexpected input tensor type for the current task. - // E.g.: multi-head model with different output tensor types. - MPPTasksErrorCodeInvalidOutputTensorTypeError, - // No correct output tensor found for the model. - // E.g.: output tensor name is not part of the text model's output tensors. - MPPTasksErrorCodeOutputTensorNotFoundError, - // Unexpected number of output tensors for the current task. - // E.g.: current task expects a single output tensor. - MPPTasksErrorCodeInvalidNumOutputTensorsError, + MPPTasksErrorCodeDataLossError = 15, - // Image processing error codes. - - // Unspecified image processing failures. - MPPTasksErrorCodeImageProcessingError = 500, - // Unexpected input or output buffer metadata. - // E.g.: rotate RGBA buffer to Grayscale buffer by 90 degrees. - MPPTasksErrorCodeImageProcessingInvalidArgumentError, - // Image processing operation failures. - // E.g. libyuv rotation failed for an unknown reason. - MPPTasksErrorCodeImageProcessingBackendError, - - // Task runner error codes. - MPPTasksErrorCodeRunnerError = 600, - // Task runner is not initialized. - MPPTasksErrorCodeRunnerInitializationError, - // Task runner is not started successfully. - MPPTasksErrorCodeRunnerFailsToStartError, - // Task runner is not started. - MPPTasksErrorCodeRunnerNotStartedError, - // Task runner API is called in the wrong processing mode. - MPPTasksErrorCodeRunnerApiCalledInWrongModeError, - // Task runner receives/produces invalid MediaPipe packet timestamp. - MPPTasksErrorCodeRunnerInvalidTimestampError, - // Task runner receives unexpected MediaPipe graph input packet. - // E.g. The packet type doesn't match the graph input stream's data type. - MPPTasksErrorCodeRunnerUnexpectedInputError, - // Task runner produces unexpected MediaPipe graph output packet. - // E.g. The number of output packets is not equal to the number of graph - // output streams. - MPPTasksErrorCodeRunnerUnexpectedOutputError, - // Task runner is not closed successfully. - MPPTasksErrorCodeRunnerFailsToCloseError, - // Task runner's model resources cache service is unavailable or the - // targeting model resources bundle is not found. - MPPTasksErrorCodeRunnerModelResourcesCacheServiceError, - - // Task graph error codes. - MPPTasksErrorCodeGraphError = 700, - // Task graph is not implemented. - MPPTasksErrorCodeTaskGraphNotImplementedError, - // Task graph config is invalid. - MPPTasksErrorCodeInvalidTaskGraphConfigError, + MPPTasksErrorCodeUnauthenticatedError = 16, // The first error code in MPPTasksErrorCode (for internal use only). - MPPTasksErrorCodeFirst = MPPTasksErrorCodeError, + MPPTasksErrorCodeFirst = MPPTasksErrorCodeCancelledError, // The last error code in MPPTasksErrorCode (for internal use only). - MPPTasksErrorCodeLast = MPPTasksErrorCodeInvalidTaskGraphConfigError, + MPPTasksErrorCodeLast = MPPTasksErrorCodeUnauthenticatedError, } NS_SWIFT_NAME(TasksErrorCode); diff --git a/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm b/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm index 1a37f846..9932dd13 100644 --- a/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm +++ b/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm @@ -25,6 +25,10 @@ /** Error domain of MediaPipe task library errors. */ NSString *const MPPTasksErrorDomain = @"com.google.mediapipe.tasks"; +namespace { + using absl::StatusCode; +} + @implementation MPPCommonUtils + (void)createCustomError:(NSError **)error @@ -67,52 +71,6 @@ NSString *const MPPTasksErrorDomain = @"com.google.mediapipe.tasks"; if (status.ok()) { return YES; } - // Payload of absl::Status created by the MediaPipe task library stores an appropriate value of - // the enum MediaPipeTasksStatus. The integer value corresponding to the MediaPipeTasksStatus enum - // stored in the payload is extracted here to later map to the appropriate error code to be - // returned. In cases where the enum is not stored in (payload is NULL or the payload string - // cannot be converted to an integer), we set the error code value to be 1 - // (MPPTasksErrorCodeError of MPPTasksErrorCode used in the iOS library to signify - // any errors not falling into other categories.) Since payload is of type absl::Cord that can be - // type cast into an absl::optional, we use the std::stoi function to convert it into - // an integer code if possible. - NSUInteger genericErrorCode = MPPTasksErrorCodeError; - NSUInteger errorCode; - try { - // Try converting payload to integer if payload is not empty. Otherwise convert a string - // signifying generic error code MPPTasksErrorCodeError to integer. - errorCode = - (NSUInteger)std::stoi(static_cast>( - status.GetPayload(mediapipe::tasks::kMediaPipeTasksPayload)) - .value_or(std::to_string(genericErrorCode))); - } catch (std::invalid_argument &e) { - // If non empty payload string cannot be converted to an integer. Set error code to 1(kError). - errorCode = MPPTasksErrorCodeError; - } - - // If errorCode is outside the range of enum values possible or is - // MPPTasksErrorCodeError, we try to map the absl::Status::code() to assign - // appropriate MPPTasksErrorCode in default cases. Note: - // The mapping to absl::Status::code() is done to generate a more specific error code than - // MPPTasksErrorCodeError in cases when the payload can't be mapped to - // MPPTasksErrorCode. This can happen when absl::Status returned by TFLite library are in turn - // returned without modification by MediaPipe cc library methods. - if (errorCode > MPPTasksErrorCodeLast || errorCode <= MPPTasksErrorCodeFirst) { - switch (status.code()) { - case absl::StatusCode::kInternal: - errorCode = MPPTasksErrorCodeError; - break; - case absl::StatusCode::kInvalidArgument: - errorCode = MPPTasksErrorCodeInvalidArgumentError; - break; - case absl::StatusCode::kNotFound: - errorCode = MPPTasksErrorCodeError; - break; - default: - errorCode = MPPTasksErrorCodeError; - break; - } - } // Creates the NSEror with the appropriate error // MPPTasksErrorCode and message. MPPTasksErrorCode has a one to one @@ -129,6 +87,81 @@ NSString *const MPPTasksErrorDomain = @"com.google.mediapipe.tasks"; NSString *description = [NSString stringWithCString:status.ToString(absl::StatusToStringMode::kWithNoExtraData).c_str() encoding:NSUTF8StringEncoding]; + + // Payload of absl::Status created by the MediaPipe task library stores an appropriate value of + // the enum MediaPipeTasksStatus. The integer value corresponding to the MediaPipeTasksStatus enum + // stored in the payload is extracted here to later map to the appropriate error code to be + // returned. In cases where the enum is not stored in (payload is NULL or the payload string + // cannot be converted to an integer), we set the error code value to be 1 + // (MPPTasksErrorCodeError of MPPTasksErrorCode used in the iOS library to signify + // any errors not falling into other categories.) Since payload is of type absl::Cord that can be + // type cast into an absl::optional, we use the std::stoi function to convert it into + // an integer code if possible. + MPPTasksErrorCode genericErrorCode = MPPTasksErrorCodeUnknownError; + + MPPTasksErrorCode errorCode = genericErrorCode; + + // If errorCode is outside the range of enum values possible or is + // MPPTasksErrorCodeError, we try to map the absl::Status::code() to assign + // appropriate MPPTasksErrorCode in default cases. Note: + // The mapping to absl::Status::code() is done to generate a more specific error code than + // MPPTasksErrorCodeError in cases when the payload can't be mapped to + // MPPTasksErrorCode. This can happen when absl::Status returned by TFLite library are in turn + // returned without modification by MediaPipe cc library methods. + switch (status.code()) { + case StatusCode::kCancelled: + errorCode = MPPTasksErrorCodeCancelledError; + break; + case StatusCode::kUnknown: + errorCode = MPPTasksErrorCodeUnknownError; + break; + case StatusCode::kInvalidArgument: + errorCode = MPPTasksErrorCodeInvalidArgumentError; + break; + case StatusCode::kDeadlineExceeded: + errorCode = MPPTasksErrorCodeDeadlineExceededError; + break; + case StatusCode::kNotFound: + errorCode = MPPTasksErrorCodeNotFoundError; + break; + case StatusCode::kAlreadyExists: + errorCode = MPPTasksErrorCodeAlreadyExistsError; + break; + case StatusCode::kPermissionDenied: + errorCode = MPPTasksErrorCodePermissionDeniedError; + break; + case StatusCode::kResourceExhausted: + errorCode = MPPTasksErrorCodeResourceExhaustedError; + break; + case StatusCode::kFailedPrecondition: + errorCode = MPPTasksErrorCodeFailedPreconditionError; + break; + case StatusCode::kAborted: + errorCode = MPPTasksErrorCodeAbortedError; + break; + case StatusCode::kOutOfRange: + errorCode = MPPTasksErrorCodeOutOfRangeError; + break; + case StatusCode::kUnimplemented: + errorCode = MPPTasksErrorCodeUnimplementedError; + break; + case StatusCode::kInternal: + errorCode = MPPTasksErrorCodeInternalError; + break; + case StatusCode::kUnavailable: + errorCode = MPPTasksErrorCodeUnavailableError; + break; + case StatusCode::kDataLoss: + errorCode = MPPTasksErrorCodeDataLossError; + break; + case StatusCode::kUnauthenticated: + errorCode = MPPTasksErrorCodeUnauthenticatedError; + break; + default: + errorCode = genericErrorCode; + break; + } + [MPPCommonUtils createCustomError:error withCode:errorCode description:description]; return NO; } From fa30100059330e9498469e4ca5065686a2079ee7 Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Fri, 13 Jan 2023 21:04:17 +0530 Subject: [PATCH 02/14] Changed swift name of MPPCategory --- mediapipe/tasks/ios/components/containers/sources/MPPCategory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediapipe/tasks/ios/components/containers/sources/MPPCategory.h b/mediapipe/tasks/ios/components/containers/sources/MPPCategory.h index d05cfe13..f360d46d 100644 --- a/mediapipe/tasks/ios/components/containers/sources/MPPCategory.h +++ b/mediapipe/tasks/ios/components/containers/sources/MPPCategory.h @@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN * index of the label in the corresponding label file. Typically it's used as the result of * classification tasks. */ -NS_SWIFT_NAME(ClassificationCategory) +NS_SWIFT_NAME(ResultCategory) @interface MPPCategory : NSObject /** From 0a707256e3b6a993447bf9b6206688e1e6bb58f0 Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Fri, 13 Jan 2023 21:04:43 +0530 Subject: [PATCH 03/14] Updates to method signatures of iOS text classifier --- .../ios/text/text_classifier/sources/MPPTextClassifier.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mediapipe/tasks/ios/text/text_classifier/sources/MPPTextClassifier.h b/mediapipe/tasks/ios/text/text_classifier/sources/MPPTextClassifier.h index 60aa9461..e33615da 100644 --- a/mediapipe/tasks/ios/text/text_classifier/sources/MPPTextClassifier.h +++ b/mediapipe/tasks/ios/text/text_classifier/sources/MPPTextClassifier.h @@ -65,7 +65,7 @@ NS_SWIFT_NAME(TextClassifier) * @return A new instance of `MPPTextClassifier` with the given model path. `nil` if there is an * error in initializing the text classifier. */ -- (instancetype)initWithModelPath:(NSString *)modelPath error:(NSError **)error; +- (nullable instancetype)initWithModelPath:(NSString *)modelPath error:(NSError **)error; /** * Creates a new instance of `MPPTextClassifier` from the given `MPPTextClassifierOptions`. @@ -78,7 +78,7 @@ NS_SWIFT_NAME(TextClassifier) * @return A new instance of `MPPTextClassifier` with the given options. `nil` if there is an * error in initializing the text classifier. */ -- (instancetype)initWithOptions:(MPPTextClassifierOptions *)options +- (nullable instancetype)initWithOptions:(MPPTextClassifierOptions *)options error:(NSError **)error NS_DESIGNATED_INITIALIZER; /** @@ -90,7 +90,8 @@ NS_SWIFT_NAME(TextClassifier) * * @return A `MPPTextClassifierResult` object that contains a list of text classifications. */ -- (nullable MPPTextClassifierResult *)classifyText:(NSString *)text error:(NSError **)error; +- (nullable MPPTextClassifierResult *)classifyText:(NSString *)text error:(NSError **)error NS_SWIFT_NAME(classify(text:)); + - (instancetype)init NS_UNAVAILABLE; From c40356c62852f9f174b04d790303511d8264fcef Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Fri, 13 Jan 2023 21:04:56 +0530 Subject: [PATCH 04/14] Added ios.bzl --- mediapipe/tasks/ios/ios.bzl | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 mediapipe/tasks/ios/ios.bzl diff --git a/mediapipe/tasks/ios/ios.bzl b/mediapipe/tasks/ios/ios.bzl new file mode 100644 index 00000000..8fe2a24a --- /dev/null +++ b/mediapipe/tasks/ios/ios.bzl @@ -0,0 +1,3 @@ +"""MediaPipe Task Library Helper Rules for iOS""" + +MPP_TASK_MINIMUM_OS_VERSION = "11.0" From 9e0b85c9b58b0395442c3e8cdeee46e45c8af380 Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Fri, 13 Jan 2023 21:05:17 +0530 Subject: [PATCH 05/14] Added module name for iOS text classifier --- mediapipe/tasks/ios/text/text_classifier/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/mediapipe/tasks/ios/text/text_classifier/BUILD b/mediapipe/tasks/ios/text/text_classifier/BUILD index aef68c9f..1afddb5d 100644 --- a/mediapipe/tasks/ios/text/text_classifier/BUILD +++ b/mediapipe/tasks/ios/text/text_classifier/BUILD @@ -46,6 +46,7 @@ objc_library( "-std=c++17", "-x objective-c++", ], + module_name = "MPPTextClassifier", deps = [ ":MPPTextClassifierOptions", ":MPPTextClassifierResult", From 2a53d78ae44bf27ec81ef795e51a5eb6fb863398 Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Fri, 13 Jan 2023 21:05:44 +0530 Subject: [PATCH 06/14] Added swift and objective tests for iOS text classifier --- .../tasks/ios/test/text/text_classifier/BUILD | 82 +++++ .../text_classifier/MPPTextClassifierTests.m | 281 ++++++++++++++++++ .../text_classifier/TextClassifierTests.swift | 237 +++++++++++++++ 3 files changed, 600 insertions(+) create mode 100644 mediapipe/tasks/ios/test/text/text_classifier/BUILD create mode 100644 mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m create mode 100644 mediapipe/tasks/ios/test/text/text_classifier/TextClassifierTests.swift diff --git a/mediapipe/tasks/ios/test/text/text_classifier/BUILD b/mediapipe/tasks/ios/test/text/text_classifier/BUILD new file mode 100644 index 00000000..b69202b6 --- /dev/null +++ b/mediapipe/tasks/ios/test/text/text_classifier/BUILD @@ -0,0 +1,82 @@ +load( + "@build_bazel_rules_apple//apple:ios.bzl", + "ios_unit_test", +) +load( + "@org_tensorflow//tensorflow/lite:special_rules.bzl", + "tflite_ios_lab_runner" +) +load( + "@build_bazel_rules_swift//swift:swift.bzl", + "swift_library" +) +load( + "//mediapipe/tasks:ios/ios.bzl", + "MPP_TASK_MINIMUM_OS_VERSION" +) + +package(default_visibility = ["//mediapipe/tasks:internal"]) + +licenses(["notice"]) + +# Default tags for filtering iOS targets. Targets are restricted to Apple platforms. +TFL_DEFAULT_TAGS = [ + "apple", +] + +# Following sanitizer tests are not supported by iOS test targets. +TFL_DISABLED_SANITIZER_TAGS = [ + "noasan", + "nomsan", + "notsan", +] + +objc_library( + name = "MPPTextClassifierObjcTestLibrary", + testonly = 1, + srcs = ["MPPTextClassifierTests.m"], + data = [ + "//mediapipe/tasks/testdata/text:bert_text_classifier_models", + "//mediapipe/tasks/testdata/text:text_classifier_models", + ], + tags = [], + deps = [ + "//mediapipe/tasks/ios/text/text_classifier:MPPTextClassifier", + ], + +) + +ios_unit_test( + name = "MPPTextClassifierObjcTest", + minimum_os_version = MPP_TASK_MINIMUM_OS_VERSION, + runner = tflite_ios_lab_runner("IOS_LATEST"), + tags =[], + deps = [ + ":MPPTextClassifierObjcTestLibrary", + ], +) + +swift_library( + name = "MPPTextClassifierSwiftTestLibrary", + testonly = 1, + srcs = ["TextClassifierTests.swift"], + data = [ + "//mediapipe/tasks/testdata/text:bert_text_classifier_models", + "//mediapipe/tasks/testdata/text:text_classifier_models", + ], + tags = TFL_DEFAULT_TAGS, + deps = [ + "//mediapipe/tasks/ios/common:MPPCommon", + "//mediapipe/tasks/ios/text/text_classifier:MPPTextClassifier", + ], +) + +ios_unit_test( + name = "MPPTextClassifierSwiftTest", + minimum_os_version = MPP_TASK_MINIMUM_OS_VERSION, + runner = tflite_ios_lab_runner("IOS_LATEST"), + tags = TFL_DEFAULT_TAGS + TFL_DISABLED_SANITIZER_TAGS, + deps = [ + ":MPPTextClassifierSwiftTestLibrary", + ], +) diff --git a/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m b/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m new file mode 100644 index 00000000..3e2fe4be --- /dev/null +++ b/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m @@ -0,0 +1,281 @@ +// Copyright 2023 The MediaPipe Authors. +// +// 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 + +#import "mediapipe/tasks/ios/common/sources/MPPCommon.h" +#import "mediapipe/tasks/ios/text/text_classifier/sources/MPPTextClassifier.h" + +static NSString *const kBertTextClassifierModelName = @"bert_text_classifier"; +static NSString *const kRegexTextClassifierModelName = + @"test_model_text_classifier_with_regex_tokenizer"; +static NSString *const kNegativeText = @"unflinchingly bleak and desperate"; +static NSString *const kPositiveText = @"it's a charming and often affecting journey"; +static NSString *const kExpectedErrorDomain = @"com.google.mediapipe.tasks"; + +#define AssertEqualErrors(error, expectedError) \ + XCTAssertNotNil(error); \ + XCTAssertEqualObjects(error.domain, expectedError.domain); \ + XCTAssertEqual(error.code, expectedError.code); \ + XCTAssertNotEqual( \ + [error.localizedDescription rangeOfString:expectedError.localizedDescription].location, \ + NSNotFound) + +#define AssertEqualCategoryArrays(categories, expectedCategories) \ + XCTAssertEqual(categories.count, expectedCategories.count); \ + for (int i = 0; i < categories.count; i++) { \ + XCTAssertEqual(categories[i].index, expectedCategories[i].index); \ + XCTAssertEqualWithAccuracy(categories[i].score, expectedCategories[i].score, 1e-6); \ + XCTAssertEqualObjects(categories[i].categoryName, expectedCategories[i].categoryName); \ + XCTAssertEqualObjects(categories[i].displayName, expectedCategories[i].displayName); \ + } + +#define AssertTextClassifierResultHasOneHead(textClassifierResult) \ + XCTAssertNotNil(textClassifierResult); \ + \ + XCTAssertNotNil(textClassifierResult.classificationResult); \ + XCTAssertEqual(textClassifierResult.classificationResult.classifications.count, 1); \ + XCTAssertEqual(textClassifierResult.classificationResult.classifications[0].headIndex, 0); + +@interface MPPTextClassifierTests : XCTestCase +@end + +@implementation MPPTextClassifierTests + +- (void)setUp { +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the + // class. +} + ++ (NSArray *)expectedBertResultCategoriesForNegativeText { + return @[ + [[MPPCategory alloc] initWithIndex:0 score:0.956187f categoryName:@"negative" displayName:nil], + [[MPPCategory alloc] initWithIndex:1 score:0.043812f categoryName:@"positive" displayName:nil] + ]; +} + ++ (NSArray *)expectedBertResultCategoriesForPositiveText { + return @[ + [[MPPCategory alloc] initWithIndex:1 score:0.999945f categoryName:@"positive" displayName:nil], + [[MPPCategory alloc] initWithIndex:0 score:0.000055f categoryName:@"negative" displayName:nil] + ]; +} + ++ (NSArray *)expectedRegexResultCategoriesForNegativeText { + return @[ + [[MPPCategory alloc] initWithIndex:0 score:0.6647746f categoryName:@"Negative" displayName:nil], + [[MPPCategory alloc] initWithIndex:1 score:0.33522537 categoryName:@"Positive" displayName:nil] + ]; +} + ++ (NSArray *)expectedRegexResultCategoriesForPositiveText { + return @[ + [[MPPCategory alloc] initWithIndex:0 score:0.5120041f categoryName:@"Negative" displayName:nil], + [[MPPCategory alloc] initWithIndex:1 score:0.48799595 categoryName:@"Positive" displayName:nil] + ]; +} + ++ (NSArray *)expectedBertResultCategoriesForEdgeCaseTests { + return @[ [[MPPCategory alloc] initWithIndex:0 + score:0.956187f + categoryName:@"negative" + displayName:nil] ]; +} + +- (NSString *)filePathWithName:(NSString *)fileName extension:(NSString *)extension { + NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:fileName + ofType:extension]; + return filePath; +} + +- (MPPTextClassifierOptions *)textClassifierOptionsWithModelName:(NSString *)modelName { + NSString *modelPath = [self filePathWithName:modelName extension:@"tflite"]; + MPPTextClassifierOptions *textClassifierOptions = [[MPPTextClassifierOptions alloc] init]; + textClassifierOptions.baseOptions.modelAssetPath = modelPath; + + return textClassifierOptions; +} + +- (MPPTextClassifier *)textClassifierFromModelFileWithName:(NSString *)modelName { + NSString *modelPath = [self filePathWithName:modelName extension:@"tflite"]; + MPPTextClassifier *textClassifier = [[MPPTextClassifier alloc] initWithModelPath:modelPath + error:nil]; + XCTAssertNotNil(textClassifier); + + return textClassifier; +} + +- (void)assertCreateTextClassifierWithOptions:(MPPTextClassifierOptions *)textClassifierOptions + failsWithExpectedError:(NSError *)expectedError { + NSError *error = nil; + MPPTextClassifier *textClassifier = + [[MPPTextClassifier alloc] initWithOptions:textClassifierOptions error:&error]; + XCTAssertNil(textClassifier); + AssertEqualErrors(error, expectedError); +} + +- (void)assertResultsOfClassifyText:(NSString *)text + usingTextClassifier:(MPPTextClassifier *)textClassifier + equalsCategories:(NSArray *)expectedCategories { + MPPTextClassifierResult *negativeResult = [textClassifier classifyText:text error:nil]; + AssertTextClassifierResultHasOneHead(negativeResult); + AssertEqualCategoryArrays(negativeResult.classificationResult.classifications[0].categories, + expectedCategories); +} + +- (void)testCreateTextClassifierFailsWithMissingModelPath { + NSString *modelPath = [self filePathWithName:@"" extension:@""]; + + NSError *error = nil; + MPPTextClassifier *textClassifier = [[MPPTextClassifier alloc] initWithModelPath:modelPath + error:&error]; + XCTAssertNil(textClassifier); + + NSError *expectedError = [NSError + errorWithDomain:kExpectedErrorDomain + code:MPPTasksErrorCodeInvalidArgumentError + userInfo:@{ + NSLocalizedDescriptionKey : + @"INVALID_ARGUMENT: ExternalFile must specify at least one of 'file_content', " + @"'file_name', 'file_pointer_meta' or 'file_descriptor_meta'." + }]; + AssertEqualErrors(error, expectedError); +} + +- (void)testCreateTextClassifierFailsWithBothAllowListAndDenyList { + MPPTextClassifierOptions *options = + [self textClassifierOptionsWithModelName:kBertTextClassifierModelName]; + options.categoryAllowlist = @[ @"positive" ]; + options.categoryDenylist = @[ @"negative" ]; + + [self assertCreateTextClassifierWithOptions:options + failsWithExpectedError: + [NSError + errorWithDomain:kExpectedErrorDomain + code:MPPTasksErrorCodeInvalidArgumentError + userInfo:@{ + NSLocalizedDescriptionKey : + @"INVALID_ARGUMENT: `category_allowlist` and " + @"`category_denylist` are mutually exclusive options." + }]]; +} + +- (void)testCreateTextClassifierFailsWithInvalidMaxResults { + MPPTextClassifierOptions *options = + [self textClassifierOptionsWithModelName:kBertTextClassifierModelName]; + options.maxResults = 0; + + [self assertCreateTextClassifierWithOptions:options + failsWithExpectedError: + [NSError errorWithDomain:kExpectedErrorDomain + code:MPPTasksErrorCodeInvalidArgumentError + userInfo:@{ + NSLocalizedDescriptionKey : + @"INVALID_ARGUMENT: Invalid `max_results` option: " + @"value must be != 0." + }]]; +} + +- (void)testClassifyWithBertSucceeds { + MPPTextClassifier *textClassifier = + [self textClassifierFromModelFileWithName:kBertTextClassifierModelName]; + + [self assertResultsOfClassifyText:kNegativeText + usingTextClassifier:textClassifier + equalsCategories:[MPPTextClassifierTests + expectedBertResultCategoriesForNegativeText]]; + + [self assertResultsOfClassifyText:kPositiveText + usingTextClassifier:textClassifier + equalsCategories:[MPPTextClassifierTests + expectedBertResultCategoriesForPositiveText]]; +} + +- (void)testClassifyWithRegexSucceeds { + MPPTextClassifier *textClassifier = + [self textClassifierFromModelFileWithName:kRegexTextClassifierModelName]; + + [self assertResultsOfClassifyText:kNegativeText + usingTextClassifier:textClassifier + equalsCategories:[MPPTextClassifierTests + expectedRegexResultCategoriesForNegativeText]]; + [self assertResultsOfClassifyText:kPositiveText + usingTextClassifier:textClassifier + equalsCategories:[MPPTextClassifierTests + expectedRegexResultCategoriesForPositiveText]]; +} + +- (void)testClassifyWithMaxResultsSucceeds { + MPPTextClassifierOptions *options = + [self textClassifierOptionsWithModelName:kBertTextClassifierModelName]; + options.maxResults = 1; + + MPPTextClassifier *textClassifier = [[MPPTextClassifier alloc] initWithOptions:options error:nil]; + XCTAssertNotNil(textClassifier); + + [self assertResultsOfClassifyText:kNegativeText + usingTextClassifier:textClassifier + equalsCategories:[MPPTextClassifierTests + expectedBertResultCategoriesForEdgeCaseTests]]; +} + +- (void)testClassifyWithCategoryAllowListSucceeds { + MPPTextClassifierOptions *options = + [self textClassifierOptionsWithModelName:kBertTextClassifierModelName]; + options.categoryAllowlist = @[ @"negative" ]; + + NSError *error = nil; + MPPTextClassifier *textClassifier = [[MPPTextClassifier alloc] initWithOptions:options + error:&error]; + XCTAssertNotNil(textClassifier); + XCTAssertNil(error); + + [self assertResultsOfClassifyText:kNegativeText + usingTextClassifier:textClassifier + equalsCategories:[MPPTextClassifierTests + expectedBertResultCategoriesForEdgeCaseTests]]; +} + +- (void)testClassifyWithCategoryDenyListSucceeds { + MPPTextClassifierOptions *options = + [self textClassifierOptionsWithModelName:kBertTextClassifierModelName]; + options.categoryDenylist = @[ @"positive" ]; + + MPPTextClassifier *textClassifier = [[MPPTextClassifier alloc] initWithOptions:options error:nil]; + XCTAssertNotNil(textClassifier); + + [self assertResultsOfClassifyText:kNegativeText + usingTextClassifier:textClassifier + equalsCategories:[MPPTextClassifierTests + expectedBertResultCategoriesForEdgeCaseTests]]; +} + +- (void)testClassifyWithScoreThresholdSucceeds { + MPPTextClassifierOptions *options = + [self textClassifierOptionsWithModelName:kBertTextClassifierModelName]; + options.scoreThreshold = 0.5f; + + MPPTextClassifier *textClassifier = [[MPPTextClassifier alloc] initWithOptions:options error:nil]; + XCTAssertNotNil(textClassifier); + + [self assertResultsOfClassifyText:kNegativeText + usingTextClassifier:textClassifier + equalsCategories:[MPPTextClassifierTests + expectedBertResultCategoriesForEdgeCaseTests]]; +} + +@end diff --git a/mediapipe/tasks/ios/test/text/text_classifier/TextClassifierTests.swift b/mediapipe/tasks/ios/test/text/text_classifier/TextClassifierTests.swift new file mode 100644 index 00000000..d2d433c2 --- /dev/null +++ b/mediapipe/tasks/ios/test/text/text_classifier/TextClassifierTests.swift @@ -0,0 +1,237 @@ +// Copyright 2023 The MediaPipe Authors. +// +// 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 XCTest + +import MPPCommon + +@testable import MPPTextClassifier + +class TextClassifierTests: XCTestCase { + + static let bundle = Bundle(for: TextClassifierTests.self) + + static let kBertModelPath = bundle.path( + forResource: "bert_text_classifier", + ofType: "tflite") + + static let kPositiveText = "it's a charming and often affecting journey" + + static let kNegativeText = "unflinchingly bleak and desperate" + + static let kBertNegativeTextResults = [ + ResultCategory( + index: 0, + score: 0.956187, + categoryName: "negative", + displayName: nil), + ResultCategory( + index: 1, + score: 0.043812, + categoryName: "positive", + displayName: nil) + ] + + static let kBertNegativeTextResultsForEdgeTestCases = [ + ResultCategory( + index: 0, + score: 0.956187, + categoryName: "negative", + displayName: nil), + ] + + func assertEqualErrorDescriptions( + _ error: Error, expectedLocalizedDescription:String) { + XCTAssertEqual( + error.localizedDescription, + expectedLocalizedDescription) + } + + func assertCategoriesAreEqual( + category: ResultCategory, + expectedCategory: ResultCategory) { + XCTAssertEqual( + category.index, + expectedCategory.index) + XCTAssertEqual( + category.score, + expectedCategory.score, + accuracy:1e-6) + XCTAssertEqual( + category.categoryName, + expectedCategory.categoryName) + XCTAssertEqual( + category.displayName, + expectedCategory.displayName) + } + + func assertEqualCategoryArrays( + categoryArray: [ResultCategory], + expectedCategoryArray:[ResultCategory]) { + + XCTAssertEqual(categoryArray.count, expectedCategoryArray.count) + + for (category, expectedCategory) in + zip(categoryArray, expectedCategoryArray) { + assertCategoriesAreEqual( + category:category, + expectedCategory:expectedCategory) + } + } + + func assertTextClassifierResultHasOneHead( + _ textClassifierResult: TextClassifierResult) { + XCTAssertEqual(textClassifierResult.classificationResult.classifications.count, 1); + XCTAssertEqual(textClassifierResult.classificationResult.classifications[0].headIndex, 0); + } + + func textClassifierOptionsWithModelPath( + _ modelPath: String?) throws -> TextClassifierOptions { + let modelPath = try XCTUnwrap(modelPath) + + let textClassifierOptions = TextClassifierOptions(); + textClassifierOptions.baseOptions.modelAssetPath = modelPath; + + return textClassifierOptions + } + + func assertCreateTextClassifierThrowsError( + textClassifierOptions: TextClassifierOptions, + expectedErrorDescription: String) { + do { + let textClassifier = try TextClassifier(options:textClassifierOptions) + XCTAssertNil(textClassifier) + } + catch { + assertEqualErrorDescriptions( + error, + expectedLocalizedDescription: expectedErrorDescription) + } + } + + func assertResultsForClassify( + text: String, + using textClassifier: TextClassifier, + equals expectedCategories: [ResultCategory]) throws { + let textClassifierResult = + try XCTUnwrap( + textClassifier.classify(text: text)); + assertTextClassifierResultHasOneHead(textClassifierResult); + assertEqualCategoryArrays( + categoryArray: + textClassifierResult.classificationResult.classifications[0].categories, + expectedCategoryArray: expectedCategories); + } + + func testCreateTextClassifierWithInvalidMaxResultsFails() throws { + let textClassifierOptions = + try XCTUnwrap( + textClassifierOptionsWithModelPath(TextClassifierTests.kBertModelPath)) + textClassifierOptions.maxResults = 0 + + assertCreateTextClassifierThrowsError( + textClassifierOptions: textClassifierOptions, + expectedErrorDescription: """ + INVALID_ARGUMENT: Invalid `max_results` option: value must be != 0. + """) + } + + func testCreateTextClassifierWithCategoryAllowlistandDenylistFails() throws { + + let textClassifierOptions = + try XCTUnwrap( + textClassifierOptionsWithModelPath(TextClassifierTests.kBertModelPath)) + textClassifierOptions.categoryAllowlist = ["positive"] + textClassifierOptions.categoryDenylist = ["positive"] + + assertCreateTextClassifierThrowsError( + textClassifierOptions: textClassifierOptions, + expectedErrorDescription: """ + INVALID_ARGUMENT: `category_allowlist` and `category_denylist` are \ + mutually exclusive options. + """) + } + + func testClassifyWithBertSucceeds() throws { + + let modelPath = try XCTUnwrap(TextClassifierTests.kBertModelPath) + let textClassifier = try XCTUnwrap(TextClassifier(modelPath: modelPath)) + + try assertResultsForClassify( + text: TextClassifierTests.kNegativeText, + using: textClassifier, + equals: TextClassifierTests.kBertNegativeTextResults) + } + + func testClassifyWithMaxResultsSucceeds() throws { + let textClassifierOptions = + try XCTUnwrap( + textClassifierOptionsWithModelPath(TextClassifierTests.kBertModelPath)) + textClassifierOptions.maxResults = 1 + + let textClassifier = + try XCTUnwrap(TextClassifier(options: textClassifierOptions)) + + try assertResultsForClassify( + text: TextClassifierTests.kNegativeText, + using: textClassifier, + equals: TextClassifierTests.kBertNegativeTextResultsForEdgeTestCases) + } + + func testClassifyWithCategoryAllowlistSucceeds() throws { + let textClassifierOptions = + try XCTUnwrap( + textClassifierOptionsWithModelPath(TextClassifierTests.kBertModelPath)) + textClassifierOptions.categoryAllowlist = ["negative"]; + + let textClassifier = + try XCTUnwrap(TextClassifier(options: textClassifierOptions)) + + try assertResultsForClassify( + text: TextClassifierTests.kNegativeText, + using: textClassifier, + equals: TextClassifierTests.kBertNegativeTextResultsForEdgeTestCases) + } + + func testClassifyWithCategoryDenylistSucceeds() throws { + let textClassifierOptions = + try XCTUnwrap( + textClassifierOptionsWithModelPath(TextClassifierTests.kBertModelPath)) + textClassifierOptions.categoryDenylist = ["positive"]; + + let textClassifier = + try XCTUnwrap(TextClassifier(options: textClassifierOptions)) + + try assertResultsForClassify( + text: TextClassifierTests.kNegativeText, + using: textClassifier, + equals: TextClassifierTests.kBertNegativeTextResultsForEdgeTestCases) + } + + func testClassifyWithScoreThresholdSucceeds() throws { + let textClassifierOptions = + try XCTUnwrap( + textClassifierOptionsWithModelPath(TextClassifierTests.kBertModelPath)) + textClassifierOptions.scoreThreshold = 0.5; + + let textClassifier = + try XCTUnwrap(TextClassifier(options: textClassifierOptions)) + + try assertResultsForClassify( + text: TextClassifierTests.kNegativeText, + using: textClassifier, + equals: TextClassifierTests.kBertNegativeTextResultsForEdgeTestCases) + } + +} From c4c07acc1e5b2dbc37965b9c714fad2102705dbd Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Fri, 13 Jan 2023 21:18:01 +0530 Subject: [PATCH 07/14] Updated comments of MPPCommonUtils --- .../common/utils/sources/MPPCommonUtils.mm | 141 +++++++----------- 1 file changed, 58 insertions(+), 83 deletions(-) diff --git a/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm b/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm index 9932dd13..27b75515 100644 --- a/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm +++ b/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm @@ -26,7 +26,7 @@ NSString *const MPPTasksErrorDomain = @"com.google.mediapipe.tasks"; namespace { - using absl::StatusCode; +using absl::StatusCode; } @implementation MPPCommonUtils @@ -72,95 +72,70 @@ namespace { return YES; } - // Creates the NSEror with the appropriate error - // MPPTasksErrorCode and message. MPPTasksErrorCode has a one to one - // mapping with MediaPipeTasksStatus starting from the value 1(MPPTasksErrorCodeError) - // and hence will be correctly initialized if directly cast from the integer code derived from - // MediaPipeTasksStatus stored in its payload. MPPTasksErrorCode omits kOk = 0 of - // MediaPipeTasksStatusx. - // - // Stores a string including absl status code and message(if non empty) as the - // error message See - // https://github.com/abseil/abseil-cpp/blob/master/absl/status/status.h#L514 - // for explanation. absl::Status::message() can also be used but not always - // guaranteed to be non empty. + /** Converts the absl status message to an NSString. */ NSString *description = [NSString stringWithCString:status.ToString(absl::StatusToStringMode::kWithNoExtraData).c_str() encoding:NSUTF8StringEncoding]; - - // Payload of absl::Status created by the MediaPipe task library stores an appropriate value of - // the enum MediaPipeTasksStatus. The integer value corresponding to the MediaPipeTasksStatus enum - // stored in the payload is extracted here to later map to the appropriate error code to be - // returned. In cases where the enum is not stored in (payload is NULL or the payload string - // cannot be converted to an integer), we set the error code value to be 1 - // (MPPTasksErrorCodeError of MPPTasksErrorCode used in the iOS library to signify - // any errors not falling into other categories.) Since payload is of type absl::Cord that can be - // type cast into an absl::optional, we use the std::stoi function to convert it into - // an integer code if possible. + MPPTasksErrorCode genericErrorCode = MPPTasksErrorCodeUnknownError; MPPTasksErrorCode errorCode = genericErrorCode; - // If errorCode is outside the range of enum values possible or is - // MPPTasksErrorCodeError, we try to map the absl::Status::code() to assign - // appropriate MPPTasksErrorCode in default cases. Note: - // The mapping to absl::Status::code() is done to generate a more specific error code than - // MPPTasksErrorCodeError in cases when the payload can't be mapped to - // MPPTasksErrorCode. This can happen when absl::Status returned by TFLite library are in turn - // returned without modification by MediaPipe cc library methods. - switch (status.code()) { - case StatusCode::kCancelled: - errorCode = MPPTasksErrorCodeCancelledError; - break; - case StatusCode::kUnknown: - errorCode = MPPTasksErrorCodeUnknownError; - break; - case StatusCode::kInvalidArgument: - errorCode = MPPTasksErrorCodeInvalidArgumentError; - break; - case StatusCode::kDeadlineExceeded: - errorCode = MPPTasksErrorCodeDeadlineExceededError; - break; - case StatusCode::kNotFound: - errorCode = MPPTasksErrorCodeNotFoundError; - break; - case StatusCode::kAlreadyExists: - errorCode = MPPTasksErrorCodeAlreadyExistsError; - break; - case StatusCode::kPermissionDenied: - errorCode = MPPTasksErrorCodePermissionDeniedError; - break; - case StatusCode::kResourceExhausted: - errorCode = MPPTasksErrorCodeResourceExhaustedError; - break; - case StatusCode::kFailedPrecondition: - errorCode = MPPTasksErrorCodeFailedPreconditionError; - break; - case StatusCode::kAborted: - errorCode = MPPTasksErrorCodeAbortedError; - break; - case StatusCode::kOutOfRange: - errorCode = MPPTasksErrorCodeOutOfRangeError; - break; - case StatusCode::kUnimplemented: - errorCode = MPPTasksErrorCodeUnimplementedError; - break; - case StatusCode::kInternal: - errorCode = MPPTasksErrorCodeInternalError; - break; - case StatusCode::kUnavailable: - errorCode = MPPTasksErrorCodeUnavailableError; - break; - case StatusCode::kDataLoss: - errorCode = MPPTasksErrorCodeDataLossError; - break; - case StatusCode::kUnauthenticated: - errorCode = MPPTasksErrorCodeUnauthenticatedError; - break; - default: - errorCode = genericErrorCode; - break; - } + /** Maps the absl::StatusCode to the appropriate MPPTasksErrorCode. Note: MPPTasksErrorCode omits + * absl::StatusCode::kOk. */ + switch (status.code()) { + case StatusCode::kCancelled: + errorCode = MPPTasksErrorCodeCancelledError; + break; + case StatusCode::kUnknown: + errorCode = MPPTasksErrorCodeUnknownError; + break; + case StatusCode::kInvalidArgument: + errorCode = MPPTasksErrorCodeInvalidArgumentError; + break; + case StatusCode::kDeadlineExceeded: + errorCode = MPPTasksErrorCodeDeadlineExceededError; + break; + case StatusCode::kNotFound: + errorCode = MPPTasksErrorCodeNotFoundError; + break; + case StatusCode::kAlreadyExists: + errorCode = MPPTasksErrorCodeAlreadyExistsError; + break; + case StatusCode::kPermissionDenied: + errorCode = MPPTasksErrorCodePermissionDeniedError; + break; + case StatusCode::kResourceExhausted: + errorCode = MPPTasksErrorCodeResourceExhaustedError; + break; + case StatusCode::kFailedPrecondition: + errorCode = MPPTasksErrorCodeFailedPreconditionError; + break; + case StatusCode::kAborted: + errorCode = MPPTasksErrorCodeAbortedError; + break; + case StatusCode::kOutOfRange: + errorCode = MPPTasksErrorCodeOutOfRangeError; + break; + case StatusCode::kUnimplemented: + errorCode = MPPTasksErrorCodeUnimplementedError; + break; + case StatusCode::kInternal: + errorCode = MPPTasksErrorCodeInternalError; + break; + case StatusCode::kUnavailable: + errorCode = MPPTasksErrorCodeUnavailableError; + break; + case StatusCode::kDataLoss: + errorCode = MPPTasksErrorCodeDataLossError; + break; + case StatusCode::kUnauthenticated: + errorCode = MPPTasksErrorCodeUnauthenticatedError; + break; + default: + errorCode = genericErrorCode; + break; + } [MPPCommonUtils createCustomError:error withCode:errorCode description:description]; return NO; From 95f9f0fb88c209b147b7822c102f5003c22d3c16 Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Fri, 13 Jan 2023 21:18:10 +0530 Subject: [PATCH 08/14] Updated formatting --- .../tasks/ios/common/sources/MPPCommon.h | 30 +++++++++++++++++-- .../sources/MPPTextClassifier.h | 6 ++-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/mediapipe/tasks/ios/common/sources/MPPCommon.h b/mediapipe/tasks/ios/common/sources/MPPCommon.h index f8047fc3..0f885a8c 100644 --- a/mediapipe/tasks/ios/common/sources/MPPCommon.h +++ b/mediapipe/tasks/ios/common/sources/MPPCommon.h @@ -18,8 +18,7 @@ NS_ASSUME_NONNULL_BEGIN /** * @enum MPPTasksErrorCode - * This enum specifies error codes for MediaPipe Task Library. - * It maintains a 1:1 mapping to MediaPipeTasksStatus of the C ++libray. + * This enum specifies error codes for errors thrown by iOS MediaPipe Task Library. */ typedef NS_ENUM(NSUInteger, MPPTasksErrorCode) { @@ -27,35 +26,60 @@ typedef NS_ENUM(NSUInteger, MPPTasksErrorCode) { /** Indicates the operation was cancelled, typically by the caller. */ MPPTasksErrorCodeCancelledError = 1, + /** Indicates an unknown error occurred. */ MPPTasksErrorCodeUnknownError = 2, + /** Indicates the caller specified an invalid argument, such as a malformed filename. */ MPPTasksErrorCodeInvalidArgumentError = 3, + /** Indicates a deadline expired before the operation could complete. */ MPPTasksErrorCodeDeadlineExceededError = 4, + /** Indicates some requested entity (such as a file or directory) was not found. */ MPPTasksErrorCodeNotFoundError = 5, - /** Indicates that the entity a caller attempted to create (such as a file or directory) is already present. */ + + /** Indicates that the entity a caller attempted to create (such as a file or directory) is + already present. */ MPPTasksErrorCodeAlreadyExistsError = 6, + /** Indicates that the caller does not have permission to execute the specified operation. */ MPPTasksErrorCodePermissionDeniedError = 7, + /** Indicates some resource has been exhausted, perhaps a per-user quota, or perhaps the entire + file system is out of space. */ MPPTasksErrorCodeResourceExhaustedError = 8, + /** Indicates that the operation was rejected because the system is not in a state required for + the operation's execution. For example, a directory to be deleted may be non-empty, an "rmdir" + operation is applied to a non-directory, etc. */ MPPTasksErrorCodeFailedPreconditionError = 9, + /** Indicates the operation was aborted, typically due to a concurrency issue such as a sequencer + check failure or a failed transaction. */ MPPTasksErrorCodeAbortedError = 10, + /** Indicates the operation was attempted past the valid range, such as seeking or reading past an + end-of-file. */ MPPTasksErrorCodeOutOfRangeError = 11, + /** Indicates the operation is not implemented or supported in this service. In this case, the + operation should not be re-attempted. */ MPPTasksErrorCodeUnimplementedError = 12, + /** Indicates an internal error has occurred and some invariants expected by the underlying system + have not been satisfied. This error code is reserved for serious errors. */ MPPTasksErrorCodeInternalError = 13, + /** Indicates the service is currently unavailable and that this is most likely a transient + condition. */ MPPTasksErrorCodeUnavailableError = 14, + /** Indicates that unrecoverable data loss or corruption has occurred. */ MPPTasksErrorCodeDataLossError = 15, + /** Indicates that the request does not have valid authentication credentials for the operation. + */ MPPTasksErrorCodeUnauthenticatedError = 16, // The first error code in MPPTasksErrorCode (for internal use only). diff --git a/mediapipe/tasks/ios/text/text_classifier/sources/MPPTextClassifier.h b/mediapipe/tasks/ios/text/text_classifier/sources/MPPTextClassifier.h index e33615da..33d3c897 100644 --- a/mediapipe/tasks/ios/text/text_classifier/sources/MPPTextClassifier.h +++ b/mediapipe/tasks/ios/text/text_classifier/sources/MPPTextClassifier.h @@ -79,7 +79,7 @@ NS_SWIFT_NAME(TextClassifier) * error in initializing the text classifier. */ - (nullable instancetype)initWithOptions:(MPPTextClassifierOptions *)options - error:(NSError **)error NS_DESIGNATED_INITIALIZER; + error:(NSError **)error NS_DESIGNATED_INITIALIZER; /** * Performs classification on the input text. @@ -90,8 +90,8 @@ NS_SWIFT_NAME(TextClassifier) * * @return A `MPPTextClassifierResult` object that contains a list of text classifications. */ -- (nullable MPPTextClassifierResult *)classifyText:(NSString *)text error:(NSError **)error NS_SWIFT_NAME(classify(text:)); - +- (nullable MPPTextClassifierResult *)classifyText:(NSString *)text + error:(NSError **)error NS_SWIFT_NAME(classify(text:)); - (instancetype)init NS_UNAVAILABLE; From 30533be321744ddea7f37fea0bf77298596b9b92 Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Mon, 16 Jan 2023 13:00:10 +0530 Subject: [PATCH 09/14] Reformatted comments --- .../tasks/ios/common/sources/MPPCommon.h | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/mediapipe/tasks/ios/common/sources/MPPCommon.h b/mediapipe/tasks/ios/common/sources/MPPCommon.h index 0f885a8c..3f0a1a7b 100644 --- a/mediapipe/tasks/ios/common/sources/MPPCommon.h +++ b/mediapipe/tasks/ios/common/sources/MPPCommon.h @@ -39,53 +39,70 @@ typedef NS_ENUM(NSUInteger, MPPTasksErrorCode) { /** Indicates some requested entity (such as a file or directory) was not found. */ MPPTasksErrorCodeNotFoundError = 5, - /** Indicates that the entity a caller attempted to create (such as a file or directory) is - already present. */ + /** + * Indicates that the entity a caller attempted to create (such as a file or directory) is + * already present. + */ MPPTasksErrorCodeAlreadyExistsError = 6, /** Indicates that the caller does not have permission to execute the specified operation. */ MPPTasksErrorCodePermissionDeniedError = 7, - /** Indicates some resource has been exhausted, perhaps a per-user quota, or perhaps the entire - file system is out of space. */ + /** + * Indicates some resource has been exhausted, perhaps a per-user quota, or perhaps the entire + * file system is out of space. + */ MPPTasksErrorCodeResourceExhaustedError = 8, - /** Indicates that the operation was rejected because the system is not in a state required for - the operation's execution. For example, a directory to be deleted may be non-empty, an "rmdir" - operation is applied to a non-directory, etc. */ + /** + * Indicates that the operation was rejected because the system is not in a state required for + * the operation's execution. For example, a directory to be deleted may be non-empty, an "rmdir" + * operation is applied to a non-directory, etc. + */ MPPTasksErrorCodeFailedPreconditionError = 9, - /** Indicates the operation was aborted, typically due to a concurrency issue such as a sequencer - check failure or a failed transaction. */ + /** + * Indicates the operation was aborted, typically due to a concurrency issue such as a sequencer + * check failure or a failed transaction. + */ MPPTasksErrorCodeAbortedError = 10, - /** Indicates the operation was attempted past the valid range, such as seeking or reading past an - end-of-file. */ + /** + * Indicates the operation was attempted past the valid range, such as seeking or reading past an + * end-of-file. + */ MPPTasksErrorCodeOutOfRangeError = 11, - /** Indicates the operation is not implemented or supported in this service. In this case, the - operation should not be re-attempted. */ + /** + * Indicates the operation is not implemented or supported in this service. In this case, the + * operation should not be re-attempted. + */ MPPTasksErrorCodeUnimplementedError = 12, - /** Indicates an internal error has occurred and some invariants expected by the underlying system - have not been satisfied. This error code is reserved for serious errors. */ + /** + * Indicates an internal error has occurred and some invariants expected by the underlying system + * have not been satisfied. This error code is reserved for serious errors. + */ MPPTasksErrorCodeInternalError = 13, - /** Indicates the service is currently unavailable and that this is most likely a transient - condition. */ + /** + * Indicates the service is currently unavailable and that this is most likely a transient + * condition. + */ MPPTasksErrorCodeUnavailableError = 14, /** Indicates that unrecoverable data loss or corruption has occurred. */ MPPTasksErrorCodeDataLossError = 15, - /** Indicates that the request does not have valid authentication credentials for the operation. + /** + * Indicates that the request does not have valid authentication credentials for the operation. */ MPPTasksErrorCodeUnauthenticatedError = 16, - // The first error code in MPPTasksErrorCode (for internal use only). + /** The first error code in MPPTasksErrorCode (for internal use only). */ MPPTasksErrorCodeFirst = MPPTasksErrorCodeCancelledError, - // The last error code in MPPTasksErrorCode (for internal use only). + /** The last error code in MPPTasksErrorCode (for internal use only). */ MPPTasksErrorCodeLast = MPPTasksErrorCodeUnauthenticatedError, } NS_SWIFT_NAME(TasksErrorCode); From 8ecf77f760c49fd319b80a2bd5daefaba5a7cd72 Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Mon, 16 Jan 2023 13:02:33 +0530 Subject: [PATCH 10/14] Updated comment style in methods --- mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm b/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm index 27b75515..538023df 100644 --- a/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm +++ b/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm @@ -72,7 +72,7 @@ using absl::StatusCode; return YES; } - /** Converts the absl status message to an NSString. */ + // Converts the absl status message to an NSString. NSString *description = [NSString stringWithCString:status.ToString(absl::StatusToStringMode::kWithNoExtraData).c_str() encoding:NSUTF8StringEncoding]; @@ -81,8 +81,8 @@ using absl::StatusCode; MPPTasksErrorCode errorCode = genericErrorCode; - /** Maps the absl::StatusCode to the appropriate MPPTasksErrorCode. Note: MPPTasksErrorCode omits - * absl::StatusCode::kOk. */ + // Maps the absl::StatusCode to the appropriate MPPTasksErrorCode. Note: MPPTasksErrorCode omits + // absl::StatusCode::kOk. switch (status.code()) { case StatusCode::kCancelled: errorCode = MPPTasksErrorCodeCancelledError; From f7fc8a6eca14b2c93fbe7a8c1c5162a1f9d59223 Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Mon, 16 Jan 2023 13:05:29 +0530 Subject: [PATCH 11/14] Updated method names in tests --- .../ios/test/text/text_classifier/MPPTextClassifierTests.m | 7 +++---- .../test/text/text_classifier/TextClassifierTests.swift | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m b/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m index 3e2fe4be..a8e54101 100644 --- a/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m +++ b/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m @@ -43,7 +43,6 @@ static NSString *const kExpectedErrorDomain = @"com.google.mediapipe.tasks"; #define AssertTextClassifierResultHasOneHead(textClassifierResult) \ XCTAssertNotNil(textClassifierResult); \ - \ XCTAssertNotNil(textClassifierResult.classificationResult); \ XCTAssertEqual(textClassifierResult.classificationResult.classifications.count, 1); \ XCTAssertEqual(textClassifierResult.classificationResult.classifications[0].headIndex, 0); @@ -156,7 +155,7 @@ static NSString *const kExpectedErrorDomain = @"com.google.mediapipe.tasks"; AssertEqualErrors(error, expectedError); } -- (void)testCreateTextClassifierFailsWithBothAllowListAndDenyList { +- (void)testCreateTextClassifierFailsWithBothAllowlistAndDenylist { MPPTextClassifierOptions *options = [self textClassifierOptionsWithModelName:kBertTextClassifierModelName]; options.categoryAllowlist = @[ @"positive" ]; @@ -233,7 +232,7 @@ static NSString *const kExpectedErrorDomain = @"com.google.mediapipe.tasks"; expectedBertResultCategoriesForEdgeCaseTests]]; } -- (void)testClassifyWithCategoryAllowListSucceeds { +- (void)testClassifyWithCategoryAllowlistSucceeds { MPPTextClassifierOptions *options = [self textClassifierOptionsWithModelName:kBertTextClassifierModelName]; options.categoryAllowlist = @[ @"negative" ]; @@ -250,7 +249,7 @@ static NSString *const kExpectedErrorDomain = @"com.google.mediapipe.tasks"; expectedBertResultCategoriesForEdgeCaseTests]]; } -- (void)testClassifyWithCategoryDenyListSucceeds { +- (void)testClassifyWithCategoryDenylistSucceeds { MPPTextClassifierOptions *options = [self textClassifierOptionsWithModelName:kBertTextClassifierModelName]; options.categoryDenylist = @[ @"positive" ]; diff --git a/mediapipe/tasks/ios/test/text/text_classifier/TextClassifierTests.swift b/mediapipe/tasks/ios/test/text/text_classifier/TextClassifierTests.swift index d2d433c2..01b5748c 100644 --- a/mediapipe/tasks/ios/test/text/text_classifier/TextClassifierTests.swift +++ b/mediapipe/tasks/ios/test/text/text_classifier/TextClassifierTests.swift @@ -147,7 +147,7 @@ class TextClassifierTests: XCTestCase { """) } - func testCreateTextClassifierWithCategoryAllowlistandDenylistFails() throws { + func testCreateTextClassifierWithCategoryAllowlistAndDenylistFails() throws { let textClassifierOptions = try XCTUnwrap( From a0b3e620e4d024259bd2637198dd3141767f12d9 Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Mon, 16 Jan 2023 13:12:27 +0530 Subject: [PATCH 12/14] Removed unused methods --- .../test/text/text_classifier/MPPTextClassifierTests.m | 8 -------- 1 file changed, 8 deletions(-) diff --git a/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m b/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m index a8e54101..5c0964e6 100644 --- a/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m +++ b/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m @@ -52,14 +52,6 @@ static NSString *const kExpectedErrorDomain = @"com.google.mediapipe.tasks"; @implementation MPPTextClassifierTests -- (void)setUp { -} - -- (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the - // class. -} - + (NSArray *)expectedBertResultCategoriesForNegativeText { return @[ [[MPPCategory alloc] initWithIndex:0 score:0.956187f categoryName:@"negative" displayName:nil], From cf945d3aebc0b705117946cddd583ae1066ef97b Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Mon, 16 Jan 2023 13:59:51 +0530 Subject: [PATCH 13/14] Removed unused variable --- mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm b/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm index 538023df..f3d9ecc7 100644 --- a/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm +++ b/mediapipe/tasks/ios/common/utils/sources/MPPCommonUtils.mm @@ -77,9 +77,7 @@ using absl::StatusCode; stringWithCString:status.ToString(absl::StatusToStringMode::kWithNoExtraData).c_str() encoding:NSUTF8StringEncoding]; - MPPTasksErrorCode genericErrorCode = MPPTasksErrorCodeUnknownError; - - MPPTasksErrorCode errorCode = genericErrorCode; + MPPTasksErrorCode errorCode = MPPTasksErrorCodeUnknownError; // Maps the absl::StatusCode to the appropriate MPPTasksErrorCode. Note: MPPTasksErrorCode omits // absl::StatusCode::kOk. @@ -133,7 +131,6 @@ using absl::StatusCode; errorCode = MPPTasksErrorCodeUnauthenticatedError; break; default: - errorCode = genericErrorCode; break; } From 67735a6fd30518bb68843a140841547540b0ee61 Mon Sep 17 00:00:00 2001 From: Prianka Liz Kariat Date: Mon, 16 Jan 2023 14:01:10 +0530 Subject: [PATCH 14/14] Added category indices in iOS failure description --- .../text_classifier/MPPTextClassifierTests.m | 17 ++++---- .../text_classifier/TextClassifierTests.swift | 40 ++++++++++++++----- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m b/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m index 5c0964e6..ebeaf863 100644 --- a/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m +++ b/mediapipe/tasks/ios/test/text/text_classifier/MPPTextClassifierTests.m @@ -32,13 +32,16 @@ static NSString *const kExpectedErrorDomain = @"com.google.mediapipe.tasks"; [error.localizedDescription rangeOfString:expectedError.localizedDescription].location, \ NSNotFound) -#define AssertEqualCategoryArrays(categories, expectedCategories) \ - XCTAssertEqual(categories.count, expectedCategories.count); \ - for (int i = 0; i < categories.count; i++) { \ - XCTAssertEqual(categories[i].index, expectedCategories[i].index); \ - XCTAssertEqualWithAccuracy(categories[i].score, expectedCategories[i].score, 1e-6); \ - XCTAssertEqualObjects(categories[i].categoryName, expectedCategories[i].categoryName); \ - XCTAssertEqualObjects(categories[i].displayName, expectedCategories[i].displayName); \ +#define AssertEqualCategoryArrays(categories, expectedCategories) \ + XCTAssertEqual(categories.count, expectedCategories.count); \ + for (int i = 0; i < categories.count; i++) { \ + XCTAssertEqual(categories[i].index, expectedCategories[i].index, @"index i = %d", i); \ + XCTAssertEqualWithAccuracy(categories[i].score, expectedCategories[i].score, 1e-6, \ + @"index i = %d", i); \ + XCTAssertEqualObjects(categories[i].categoryName, expectedCategories[i].categoryName, \ + @"index i = %d", i); \ + XCTAssertEqualObjects(categories[i].displayName, expectedCategories[i].displayName, \ + @"index i = %d", i); \ } #define AssertTextClassifierResultHasOneHead(textClassifierResult) \ diff --git a/mediapipe/tasks/ios/test/text/text_classifier/TextClassifierTests.swift b/mediapipe/tasks/ios/test/text/text_classifier/TextClassifierTests.swift index 01b5748c..18688777 100644 --- a/mediapipe/tasks/ios/test/text/text_classifier/TextClassifierTests.swift +++ b/mediapipe/tasks/ios/test/text/text_classifier/TextClassifierTests.swift @@ -60,33 +60,55 @@ class TextClassifierTests: XCTestCase { func assertCategoriesAreEqual( category: ResultCategory, - expectedCategory: ResultCategory) { + expectedCategory: ResultCategory, + indexInCategoryList: Int) { XCTAssertEqual( category.index, - expectedCategory.index) + expectedCategory.index, + String( + format: """ + category[%d].index and expectedCategory[%d].index are not equal. + """, indexInCategoryList)) XCTAssertEqual( category.score, expectedCategory.score, - accuracy:1e-6) + accuracy:1e-6, + String( + format: """ + category[%d].score and expectedCategory[%d].score are not equal. + """, indexInCategoryList)) XCTAssertEqual( category.categoryName, - expectedCategory.categoryName) + expectedCategory.categoryName, + String( + format: """ + category[%d].categoryName and expectedCategory[%d].categoryName are \ + not equal. + """, indexInCategoryList)) XCTAssertEqual( category.displayName, - expectedCategory.displayName) + expectedCategory.displayName, + String( + format: """ + category[%d].displayName and expectedCategory[%d].displayName are \ + not equal. + """, indexInCategoryList)) } func assertEqualCategoryArrays( categoryArray: [ResultCategory], expectedCategoryArray:[ResultCategory]) { - XCTAssertEqual(categoryArray.count, expectedCategoryArray.count) + XCTAssertEqual( + categoryArray.count, + expectedCategoryArray.count) - for (category, expectedCategory) in - zip(categoryArray, expectedCategoryArray) { + for (index, (category, expectedCategory)) in + zip(categoryArray, expectedCategoryArray).enumerated() { assertCategoriesAreEqual( category:category, - expectedCategory:expectedCategory) + expectedCategory:expectedCategory, + indexInCategoryList:index) } }