Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NOT FOR MERGING] iOS Image Embedder Objective C++ #830

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
26 changes: 26 additions & 0 deletions tensorflow_lite_support/ios/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ load(
load(
"//tensorflow_lite_support/ios:ios.bzl",
"strip_c_api_include_path_prefix",
"strip_ios_api_include_path_prefix",
)

load(
"@build_bazel_rules_apple//apple:ios.bzl",
"ios_static_framework",
)

package(
Expand Down Expand Up @@ -37,6 +43,13 @@ strip_c_api_include_path_prefix(
],
)

strip_ios_api_include_path_prefix(
name = "strip_ios_api_include_path",
hdr_labels = [
"//tensorflow_lite_support/ios/task/vision:sources/TFLImageEmbedder.h",
],
)

# This target builds a monolithic static framework for the TFLite Text API,
# which includes the TFLite runtime in it.
#
Expand Down Expand Up @@ -120,3 +133,16 @@ objc_library(
"//tensorflow_lite_support/ios:TFLCommon",
],
)

ios_static_framework(
name = "TensorFlowLiteTaskVisionObjCPP_framework",
hdrs = [
":TFLImageEmbedder.h",
],
bundle_name = "TensorFlowLiteTaskVisionObjCPP",
minimum_os_version = TFL_MINIMUM_OS_VERSION,
deps = [
"//tensorflow_lite_support/ios/task/vision:TFLImageEmbedder",
],
)

20 changes: 20 additions & 0 deletions tensorflow_lite_support/ios/TensorFlowLiteTaskVisionObjCPP.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Pod::Spec.new do |s|
s.name = 'TensorFlowLiteTaskVisionObjCPP'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a dedicated podspec for the ObjC++ implementation, can you add the TensorFlowLiteTaskVisionObjCPP.framework as a vendored framework for the current TensorFlowLiteTaskVision pod? We don't want to expose the internal structure of the framework to the end-users if possible.

Copy link
Contributor Author

@priankakariat priankakariat Jun 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just meant for illustrating how it will work. I have added the entire vision framework as a vendored framework now for more clarity. You can refer to the new vision podspec. The vendor framework mentioned here is defined in this build file.
We can also build the text framework in a similar manner going forward. I haven't made that change. Once it is done, the two shell scripts in /tensorflow_lite_support/tools/ci_build/builds can be unified into one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Prianka! LGTM.

@schmidt-sebastian @lu-wang-g Do you have any feedback?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@priankakariatyml Can you check if the new setup can make the Vision pod and Text pod co-exist in the same project? I guess that you may tweak the code to hide the duplicating symbols.

s.version = '0.1.1'
s.authors = 'Google Inc.'
s.license = { :type => 'Apache',:file => "LICENSE"}
s.homepage = 'https://github.com/tensorflow/tflite-support'
s.source = { :http => 'https://dl.dropboxusercontent.com/s/5v2775z785b8114/TensorFlowLiteTaskVisionObjCPP-0.1.1-dev.tar.gz?dl=0' }
s.summary = 'TensorFlow Lite Task Library - Vision'
s.description = 'The Computer Vision APIs of the TFLite Task Library'

s.ios.deployment_target = '10.0'

s.module_name = 'TensorFlowLiteTaskVisionObjCPP'
s.static_framework = true
s.pod_target_xcconfig = {
'VALID_ARCHS' => 'x86_64, arm64, armv7',
}
s.library = 'c++'
s.vendored_frameworks = 'Frameworks/TensorFlowLiteTaskVisionObjCPP.framework'
end
29 changes: 29 additions & 0 deletions tensorflow_lite_support/ios/ios.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,32 @@ def strip_c_api_include_path_prefix(name, hdr_labels, prefix = ""):
> "$@"
""".format(prefix, hdr_label),
)

# When the static framework is built with bazel, the all header files are moved
# to the "Headers" directory with no header path prefixes. This auxiliary rule
# is used for stripping the path prefix to the iOS API header files imported by
# other iOS API header files.
def strip_ios_api_include_path_prefix(name, hdr_labels, prefix = ""):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Do we actually need this new macro?

As far as I can tell, the new iOS header files (e.g., TFLImageEmbedder.h) are all using plain header filenames in their import statements. That is, it's likely that the processed header file will be the same as the original header file.

  1. If you actually need it (which is probably not the case in my understanding..), does this actually need to be separated from the strip_c_api_include_path_prefix helper above?

Copy link
Contributor Author

@priankakariat priankakariat Jun 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Yes stripping will be needed. Right now only one file has been added for reviewing the structure. In a full implementation the header would have a lot of imports in a hierarchical structure like TFLImageClassifier.h. We will have to strip the prefixes and build the final iOS framework like the C framework is built.
    I added a separate method since objective c files are imported using #import as opposed to c files which are included using #include.
  2. I can incorporate this check in strip_c_api_include_path_prefix and call it strip_api_include_path_prefix which will strip path prefixes irrespective of #import or #include

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. If it's needed, I'd recommend consolidating these two functions into a single one as mentioned before.

"""Create modified header files with the import path stripped out.

Args:
name: The name to be used as a prefix to the generated genrules.
hdr_labels: List of header labels to strip out the include path. Each
label must end with a colon followed by the header file name.
prefix: Optional prefix path to prepend to the header inclusion path.
"""
for hdr_label in hdr_labels:
hdr_filename = hdr_label.split(":")[-1]
hdr_filename = hdr_filename.split("/")[-1]
hdr_basename = hdr_filename.split(".")[0]
native.genrule(
name = "{}_{}".format(name, hdr_basename),
srcs = [hdr_label],
outs = [hdr_filename],
cmd = """
sed 's|#import ".*/\\([^/]\\{{1,\\}}\\.h\\)"|#import "{}\\1"|'\
"$(location {})"\
> "$@"
""".format(prefix, hdr_label),
)

16 changes: 16 additions & 0 deletions tensorflow_lite_support/ios/task/vision/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,19 @@ objc_library(
"//tensorflow_lite_support/odml/ios/image:MLImage",
],
)

objc_library(
name = "TFLImageEmbedder",
srcs = [
"sources/TFLImageEmbedder.mm",
],
hdrs = [
"sources/TFLImageEmbedder.h",
],
features = ["-layering_check"],
module_name = "TFLImageEmbedder",
deps = [
"//tensorflow_lite_support/cc/task/vision:image_embedder",
],
copts = ["-ObjC++","-std=c++14"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the -ObjC++ option needed?

Copy link
Contributor Author

@priankakariat priankakariat Jun 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding was that "-ObjC++" is needed when you are building with .mm files. Our source file is .mm.

)
29 changes: 29 additions & 0 deletions tensorflow_lite_support/ios/task/vision/sources/TFLImageEmbedder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* Copyright 2022 The TensorFlow Authors. 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

/**
* A TensorFlow Lite Task Image Classifiier.
*/
NS_SWIFT_NAME(ImageEmbedder)
@interface TFLImageEmbedder : NSObject

- (instancetype)initWithModelPath:(NSString *)modelPath;
@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* Copyright 2022 The TensorFlow Authors. 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 "tensorflow_lite_support/ios/task/vision/sources/TFLImageEmbedder.h"
#include "tensorflow_lite_support/cc/task/vision/image_embedder.h"

namespace {
using ImageEmbedderCpp = ::tflite::task::vision::ImageEmbedder;
using ImageEmbedderOptionsCpp =
::tflite::task::vision::ImageEmbedderOptions;
using ::tflite::support::StatusOr;
}

@implementation TFLImageEmbedder {
std::unique_ptr<ImageEmbedderCpp> cppImageEmbedder;
}

- (instancetype)initWithModelPath:(NSString *)modelPath {
self = [super init];
if (self) {
ImageEmbedderOptionsCpp cc_options;

cc_options.mutable_model_file_with_metadata()->set_file_name(modelPath.UTF8String);

StatusOr<std::unique_ptr<ImageEmbedderCpp>> embedder_status =
ImageEmbedderCpp::CreateFromOptions(cc_options);

if (embedder_status.ok()) {
cppImageEmbedder = std::move(embedder_status.value());
}
else {
Comment on lines +42 to +43
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
else {
} else {

return nil;
}
}
return self;
}

@end
36 changes: 36 additions & 0 deletions tensorflow_lite_support/ios/test/task/vision/image_embedder/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
load("@org_tensorflow//tensorflow/lite/ios:ios.bzl", "TFL_DEFAULT_TAGS", "TFL_DISABLED_SANITIZER_TAGS", "TFL_MINIMUM_OS_VERSION")
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_unit_test")
load("@org_tensorflow//tensorflow/lite:special_rules.bzl", "tflite_ios_lab_runner")

package(
default_visibility = ["//visibility:private"],
licenses = ["notice"], # Apache 2.0
)


objc_library(
name = "TFLImageEmbedderObjcTestLibrary",
testonly = 1,
srcs = ["TFLImageEmbedderTests.m"],
data = [
"//tensorflow_lite_support/cc/test/testdata/task/vision:test_images",
"//tensorflow_lite_support/cc/test/testdata/task/vision:test_models",
],
tags = TFL_DEFAULT_TAGS,
deps = [
"//tensorflow_lite_support/ios/task/vision:TFLImageEmbedder",
"//tensorflow_lite_support/ios/task/vision/utils:GMLImageUtils",
],
)

ios_unit_test(
name = "TFLImageEmbedderObjcTest",
minimum_os_version = TFL_MINIMUM_OS_VERSION,
runner = tflite_ios_lab_runner("IOS_LATEST"),
tags = TFL_DEFAULT_TAGS + TFL_DISABLED_SANITIZER_TAGS,
deps = [
":TFLImageEmbedderObjcTestLibrary",
],
)

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* Copyright 2022 The TensorFlow Authors. 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 <XCTest/XCTest.h>

#import "tensorflow_lite_support/ios/task/vision/sources/TFLImageEmbedder.h"

NS_ASSUME_NONNULL_BEGIN

@interface TFLImageEmbedderTests : XCTestCase
@property(nonatomic, nullable) NSString *modelPath;
@end

@implementation TFLImageEmbedderTests

- (void)setUp {
// Put setup code here. This method is called before the invocation of each test method in the
// class.
[super setUp];
self.modelPath = [[NSBundle bundleForClass:self.class] pathForResource:@"mobilenet_v3_small_100_224_embedder"
ofType:@"tflite"];
XCTAssertNotNil(self.modelPath);
}

- (void)testInitSucceeds {
TFLImageEmbedder *embedder = [[TFLImageEmbedder alloc] initWithModelPath:self.modelPath];
XCTAssertNotNil(embedder);
}
@end

NS_ASSUME_NONNULL_END
Loading