Merge pull request #4802 from priankakariatyml:ios-image-segmenter-basic-tests
PiperOrigin-RevId: 565797057
This commit is contained in:
commit
d5fa4a157e
24
mediapipe/tasks/ios/test/utils/BUILD
Normal file
24
mediapipe/tasks/ios/test/utils/BUILD
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Copyright 2023 The MediaPipe 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.
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
objc_library(
|
||||||
|
name = "MPPFileInfo",
|
||||||
|
srcs = ["sources/MPPFileInfo.m"],
|
||||||
|
hdrs = ["sources/MPPFileInfo.h"],
|
||||||
|
module_name = "MPPFileInfo",
|
||||||
|
)
|
42
mediapipe/tasks/ios/test/utils/sources/MPPFileInfo.h
Normal file
42
mediapipe/tasks/ios/test/utils/sources/MPPFileInfo.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// 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 <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MPPFileInfo : NSObject
|
||||||
|
|
||||||
|
/** The name of the file. */
|
||||||
|
@property(nonatomic, readonly) NSString *name;
|
||||||
|
|
||||||
|
/** The type of the file. */
|
||||||
|
@property(nonatomic, readonly) NSString *type;
|
||||||
|
|
||||||
|
/** The path to file in the app bundle. */
|
||||||
|
@property(nonatomic, readonly, nullable) NSString *path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes an `MPPFileInfo` using the given name and type of file.
|
||||||
|
*
|
||||||
|
* @param name The name of the file.
|
||||||
|
* @param type The type of the file.
|
||||||
|
*
|
||||||
|
* @return The `MPPFileInfo` with the given name and type of file.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithName:(NSString *)name type:(NSString *)type;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
33
mediapipe/tasks/ios/test/utils/sources/MPPFileInfo.m
Normal file
33
mediapipe/tasks/ios/test/utils/sources/MPPFileInfo.m
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2023 The TensorFlow 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 "mediapipe/tasks/ios/test/utils/sources/MPPFileInfo.h"
|
||||||
|
|
||||||
|
@implementation MPPFileInfo
|
||||||
|
|
||||||
|
- (instancetype)initWithName:(NSString *)name type:(NSString *)type {
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
_name = name;
|
||||||
|
_type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)path {
|
||||||
|
return [[NSBundle bundleForClass:self.class] pathForResource:self.name ofType:self.type];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
76
mediapipe/tasks/ios/test/vision/image_segmenter/BUILD
Normal file
76
mediapipe/tasks/ios/test/vision/image_segmenter/BUILD
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
# Copyright 2023 The MediaPipe 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.
|
||||||
|
|
||||||
|
load(
|
||||||
|
"//mediapipe/framework/tool:ios.bzl",
|
||||||
|
"MPP_TASK_MINIMUM_OS_VERSION",
|
||||||
|
)
|
||||||
|
load(
|
||||||
|
"@org_tensorflow//tensorflow/lite:special_rules.bzl",
|
||||||
|
"tflite_ios_lab_runner",
|
||||||
|
)
|
||||||
|
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_unit_test")
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
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 = "MPPImageSegmenterObjcTestLibrary",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = ["MPPImageSegmenterTests.mm"],
|
||||||
|
copts = [
|
||||||
|
"-ObjC++",
|
||||||
|
"-std=c++17",
|
||||||
|
"-x objective-c++",
|
||||||
|
],
|
||||||
|
data = [
|
||||||
|
"//mediapipe/tasks/testdata/vision:test_images",
|
||||||
|
"//mediapipe/tasks/testdata/vision:test_models",
|
||||||
|
"//mediapipe/tasks/testdata/vision:test_protos",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//mediapipe/tasks/ios/test/vision/utils:MPPImageTestUtils",
|
||||||
|
"//mediapipe/tasks/ios/test/vision/utils:MPPMaskTestUtils",
|
||||||
|
"//mediapipe/tasks/ios/vision/image_segmenter:MPPImageSegmenter",
|
||||||
|
"//mediapipe/tasks/ios/vision/image_segmenter:MPPImageSegmenterResult",
|
||||||
|
] + select({
|
||||||
|
"//third_party:opencv_ios_sim_arm64_source_build": ["@ios_opencv_source//:opencv_xcframework"],
|
||||||
|
"//third_party:opencv_ios_arm64_source_build": ["@ios_opencv_source//:opencv_xcframework"],
|
||||||
|
"//third_party:opencv_ios_x86_64_source_build": ["@ios_opencv_source//:opencv_xcframework"],
|
||||||
|
"//conditions:default": ["@ios_opencv//:OpencvFramework"],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
ios_unit_test(
|
||||||
|
name = "MPPImageSegmenterObjcTest",
|
||||||
|
minimum_os_version = MPP_TASK_MINIMUM_OS_VERSION,
|
||||||
|
runner = tflite_ios_lab_runner("IOS_LATEST"),
|
||||||
|
tags = TFL_DEFAULT_TAGS + TFL_DISABLED_SANITIZER_TAGS,
|
||||||
|
deps = [
|
||||||
|
":MPPImageSegmenterObjcTestLibrary",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,242 @@
|
||||||
|
// 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 <Foundation/Foundation.h>
|
||||||
|
#import <XCTest/XCTest.h>
|
||||||
|
|
||||||
|
#import "mediapipe/tasks/ios/test/vision/utils/sources/MPPImage+TestUtils.h"
|
||||||
|
#import "mediapipe/tasks/ios/test/vision/utils/sources/MPPMask+TestUtils.h"
|
||||||
|
#import "mediapipe/tasks/ios/vision/image_segmenter/sources/MPPImageSegmenter.h"
|
||||||
|
#import "mediapipe/tasks/ios/vision/image_segmenter/sources/MPPImageSegmenterResult.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
static MPPFileInfo *const kCatImageFileInfo = [[MPPFileInfo alloc] initWithName:@"cat" type:@"jpg"];
|
||||||
|
static MPPFileInfo *const kCatGoldenImageFileInfo = [[MPPFileInfo alloc] initWithName:@"cat_mask"
|
||||||
|
type:@"jpg"];
|
||||||
|
static MPPFileInfo *const kSegmentationImageFileInfo =
|
||||||
|
[[MPPFileInfo alloc] initWithName:@"segmentation_input_rotation0" type:@"jpg"];
|
||||||
|
static MPPFileInfo *const kSegmentationGoldenImageFileInfo =
|
||||||
|
[[MPPFileInfo alloc] initWithName:@"segmentation_golden_rotation0" type:@"png"];
|
||||||
|
static MPPFileInfo *const kImageSegmenterModel = [[MPPFileInfo alloc] initWithName:@"deeplabv3"
|
||||||
|
type:@"tflite"];
|
||||||
|
static NSString *const kExpectedErrorDomain = @"com.google.mediapipe.tasks";
|
||||||
|
constexpr float kSimilarityThreshold = 0.96f;
|
||||||
|
constexpr NSInteger kMagnificationFactor = 10;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
double sum(const std::vector<float> &mask) {
|
||||||
|
double sum = 0.0;
|
||||||
|
for (const float &maskElement : mask) {
|
||||||
|
sum += maskElement;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<float> multiply(const float *mask1, const float *mask2, size_t size) {
|
||||||
|
std::vector<float> multipliedMask;
|
||||||
|
multipliedMask.reserve(size);
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
multipliedMask.push_back(mask1[i] * mask2[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return multipliedMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
double softIOU(const float *mask1, const float *mask2, size_t size) {
|
||||||
|
std::vector<float> interSectionVector = multiply(mask1, mask2, size);
|
||||||
|
double interSectionSum = sum(interSectionVector);
|
||||||
|
|
||||||
|
std::vector<float> m1m1Vector = multiply(mask1, mask1, size);
|
||||||
|
double m1m1 = sum(m1m1Vector);
|
||||||
|
|
||||||
|
std::vector<float> m2m2Vector = multiply(mask2, mask2, size);
|
||||||
|
double m2m2 = sum(m2m2Vector);
|
||||||
|
|
||||||
|
double unionSum = m1m1 + m2m2 - interSectionSum;
|
||||||
|
|
||||||
|
return unionSum > 0.0 ? interSectionSum / unionSum : 0.0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
@interface MPPImageSegmenterTests : XCTestCase <MPPImageSegmenterLiveStreamDelegate>
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPPImageSegmenterTests
|
||||||
|
|
||||||
|
#pragma mark General Tests
|
||||||
|
|
||||||
|
- (void)setUp {
|
||||||
|
// When expected and actual mask sizes are not equal, iterating through mask data results in a
|
||||||
|
// segmentation fault. Setting this property to `NO`, prevents each test case from executing the
|
||||||
|
// remaining flow after a failure. Since expected and actual mask sizes are compared before
|
||||||
|
// iterating through them, this prevents any illegal memory access.
|
||||||
|
self.continueAfterFailure = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString *)filePathWithName:(NSString *)fileName extension:(NSString *)extension {
|
||||||
|
NSString *filePath =
|
||||||
|
[[NSBundle bundleForClass:[MPPImageSegmenterTests class]] pathForResource:fileName
|
||||||
|
ofType:extension];
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark Image Mode Tests
|
||||||
|
|
||||||
|
- (void)testSegmentWithCategoryMaskSucceeds {
|
||||||
|
MPPImageSegmenterOptions *options =
|
||||||
|
[self imageSegmenterOptionsWithModelFileInfo:kImageSegmenterModel];
|
||||||
|
options.shouldOutputConfidenceMasks = NO;
|
||||||
|
options.shouldOutputCategoryMask = YES;
|
||||||
|
|
||||||
|
MPPImageSegmenter *imageSegmenter = [self createImageSegmenterWithOptionsSucceeds:options];
|
||||||
|
|
||||||
|
[self assertResultsOfSegmentImageWithFileInfo:kSegmentationImageFileInfo
|
||||||
|
usingImageSegmenter:imageSegmenter
|
||||||
|
approximatelyEqualsExpectedCategoryMaskImageWithFileInfo:kSegmentationGoldenImageFileInfo
|
||||||
|
shouldHaveConfidenceMasks:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testSegmentWithConfidenceMaskSucceeds {
|
||||||
|
MPPImageSegmenterOptions *options =
|
||||||
|
[self imageSegmenterOptionsWithModelFileInfo:kImageSegmenterModel];
|
||||||
|
|
||||||
|
MPPImageSegmenter *imageSegmenter = [self createImageSegmenterWithOptionsSucceeds:options];
|
||||||
|
|
||||||
|
[self assertResultsOfSegmentImageWithFileInfo:kCatImageFileInfo
|
||||||
|
usingImageSegmenter:imageSegmenter
|
||||||
|
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:kCatGoldenImageFileInfo
|
||||||
|
atIndex:8
|
||||||
|
shouldHaveCategoryMask:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Image Segmenter Initializers
|
||||||
|
|
||||||
|
- (MPPImageSegmenterOptions *)imageSegmenterOptionsWithModelFileInfo:(MPPFileInfo *)fileInfo {
|
||||||
|
MPPImageSegmenterOptions *options = [[MPPImageSegmenterOptions alloc] init];
|
||||||
|
options.baseOptions.modelAssetPath = fileInfo.path;
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (MPPImageSegmenter *)createImageSegmenterWithOptionsSucceeds:(MPPImageSegmenterOptions *)options {
|
||||||
|
NSError *error;
|
||||||
|
MPPImageSegmenter *imageSegmenter = [[MPPImageSegmenter alloc] initWithOptions:options
|
||||||
|
error:&error];
|
||||||
|
XCTAssertNotNil(imageSegmenter);
|
||||||
|
XCTAssertNil(error);
|
||||||
|
|
||||||
|
return imageSegmenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark Assert Segmenter Results
|
||||||
|
- (void)assertResultsOfSegmentImageWithFileInfo:(MPPFileInfo *)imageFileInfo
|
||||||
|
usingImageSegmenter:(MPPImageSegmenter *)imageSegmenter
|
||||||
|
approximatelyEqualsExpectedCategoryMaskImageWithFileInfo:
|
||||||
|
(MPPFileInfo *)expectedCategoryMaskFileInfo
|
||||||
|
shouldHaveConfidenceMasks:(BOOL)shouldHaveConfidenceMasks {
|
||||||
|
MPPImageSegmenterResult *result = [self segmentImageWithFileInfo:imageFileInfo
|
||||||
|
usingImageSegmenter:imageSegmenter];
|
||||||
|
|
||||||
|
XCTAssertNotNil(result.categoryMask);
|
||||||
|
|
||||||
|
if (shouldHaveConfidenceMasks) {
|
||||||
|
XCTAssertNotNil(result.confidenceMasks);
|
||||||
|
} else {
|
||||||
|
XCTAssertNil(result.confidenceMasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
[self assertCategoryMask:result.categoryMask
|
||||||
|
approximatelyEqualsExpectedCategoryMaskImageWithFileInfo:expectedCategoryMaskFileInfo];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)assertResultsOfSegmentImageWithFileInfo:(MPPFileInfo *)imageFileInfo
|
||||||
|
usingImageSegmenter:(MPPImageSegmenter *)imageSegmenter
|
||||||
|
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:
|
||||||
|
(MPPFileInfo *)expectedConfidenceMaskFileInfo
|
||||||
|
atIndex:(NSInteger)index
|
||||||
|
shouldHaveCategoryMask:(BOOL)shouldHaveCategoryMask {
|
||||||
|
MPPImageSegmenterResult *result = [self segmentImageWithFileInfo:imageFileInfo
|
||||||
|
usingImageSegmenter:imageSegmenter];
|
||||||
|
|
||||||
|
XCTAssertNotNil(result.confidenceMasks);
|
||||||
|
|
||||||
|
if (shouldHaveCategoryMask) {
|
||||||
|
XCTAssertNotNil(result.categoryMask);
|
||||||
|
} else {
|
||||||
|
XCTAssertNil(result.categoryMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertLessThan(index, result.confidenceMasks.count);
|
||||||
|
|
||||||
|
[self assertConfidenceMask:result.confidenceMasks[index]
|
||||||
|
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:expectedConfidenceMaskFileInfo];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (MPPImageSegmenterResult *)segmentImageWithFileInfo:(MPPFileInfo *)fileInfo
|
||||||
|
usingImageSegmenter:(MPPImageSegmenter *)imageSegmenter {
|
||||||
|
MPPImage *image = [MPPImage imageWithFileInfo:fileInfo];
|
||||||
|
XCTAssertNotNil(image);
|
||||||
|
|
||||||
|
NSError *error;
|
||||||
|
MPPImageSegmenterResult *result = [imageSegmenter segmentImage:image error:&error];
|
||||||
|
XCTAssertNil(error);
|
||||||
|
XCTAssertNotNil(result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)assertCategoryMask:(MPPMask *)categoryMask
|
||||||
|
approximatelyEqualsExpectedCategoryMaskImageWithFileInfo:
|
||||||
|
(MPPFileInfo *)expectedCategoryMaskImageFileInfo {
|
||||||
|
MPPMask *expectedCategoryMask =
|
||||||
|
[[MPPMask alloc] initWithImageFileInfo:expectedCategoryMaskImageFileInfo];
|
||||||
|
|
||||||
|
XCTAssertEqual(categoryMask.width, expectedCategoryMask.width);
|
||||||
|
XCTAssertEqual(categoryMask.height, expectedCategoryMask.height);
|
||||||
|
|
||||||
|
size_t maskSize = categoryMask.width * categoryMask.height;
|
||||||
|
|
||||||
|
const UInt8 *categoryMaskPixelData = categoryMask.uint8Data;
|
||||||
|
const UInt8 *expectedCategoryMaskPixelData = expectedCategoryMask.uint8Data;
|
||||||
|
|
||||||
|
NSInteger consistentPixels = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < maskSize; i++) {
|
||||||
|
consistentPixels +=
|
||||||
|
categoryMaskPixelData[i] * kMagnificationFactor == expectedCategoryMaskPixelData[i] ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertGreaterThan((float)consistentPixels / (float)maskSize, kSimilarityThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)assertConfidenceMask:(MPPMask *)confidenceMask
|
||||||
|
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:
|
||||||
|
(MPPFileInfo *)expectedConfidenceMaskImageFileInfo {
|
||||||
|
MPPMask *expectedConfidenceMask =
|
||||||
|
[[MPPMask alloc] initWithImageFileInfo:expectedConfidenceMaskImageFileInfo];
|
||||||
|
|
||||||
|
XCTAssertEqual(confidenceMask.width, expectedConfidenceMask.width);
|
||||||
|
XCTAssertEqual(confidenceMask.height, expectedConfidenceMask.height);
|
||||||
|
|
||||||
|
size_t maskSize = confidenceMask.width * confidenceMask.height;
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(
|
||||||
|
softIOU(confidenceMask.float32Data, expectedConfidenceMask.float32Data, maskSize),
|
||||||
|
kSimilarityThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -1,3 +1,17 @@
|
||||||
|
# Copyright 2023 The MediaPipe 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.
|
||||||
|
|
||||||
package(default_visibility = ["//mediapipe/tasks:internal"])
|
package(default_visibility = ["//mediapipe/tasks:internal"])
|
||||||
|
|
||||||
licenses(["notice"])
|
licenses(["notice"])
|
||||||
|
@ -7,7 +21,22 @@ objc_library(
|
||||||
srcs = ["sources/MPPImage+TestUtils.m"],
|
srcs = ["sources/MPPImage+TestUtils.m"],
|
||||||
hdrs = ["sources/MPPImage+TestUtils.h"],
|
hdrs = ["sources/MPPImage+TestUtils.h"],
|
||||||
module_name = "MPPImageTestUtils",
|
module_name = "MPPImageTestUtils",
|
||||||
deps = ["//mediapipe/tasks/ios/vision/core:MPPImage"],
|
deps = [
|
||||||
|
"//mediapipe/tasks/ios/test/utils:MPPFileInfo",
|
||||||
|
"//mediapipe/tasks/ios/vision/core:MPPImage",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
objc_library(
|
||||||
|
name = "MPPMaskTestUtils",
|
||||||
|
srcs = ["sources/MPPMask+TestUtils.m"],
|
||||||
|
hdrs = ["sources/MPPMask+TestUtils.h"],
|
||||||
|
module_name = "MPPMaskTestUtils",
|
||||||
|
deps = [
|
||||||
|
"//mediapipe/tasks/ios/test/utils:MPPFileInfo",
|
||||||
|
"//mediapipe/tasks/ios/vision/core:MPPMask",
|
||||||
|
"//third_party/apple_frameworks:UIKit",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
cc_library(
|
cc_library(
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#import "mediapipe/tasks/ios/test/utils/sources/MPPFileInfo.h"
|
||||||
#import "mediapipe/tasks/ios/vision/core/sources/MPPImage.h"
|
#import "mediapipe/tasks/ios/vision/core/sources/MPPImage.h"
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
@ -23,6 +24,31 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
*/
|
*/
|
||||||
@interface MPPImage (TestUtils)
|
@interface MPPImage (TestUtils)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an image from a file in an app bundle into a `MPPImage` object.
|
||||||
|
*
|
||||||
|
* @param fileInfo The file info specifying the name and extension of the image
|
||||||
|
* file in the bundle.
|
||||||
|
*
|
||||||
|
* @return The `MPPImage` object contains the loaded image. This method returns
|
||||||
|
* nil if it cannot load the image.
|
||||||
|
*/
|
||||||
|
+ (MPPImage *)imageWithFileInfo:(MPPFileInfo *)fileInfo NS_SWIFT_NAME(image(withFileInfo:));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an image from a file in an app bundle into a `MPPImage` object with the specified
|
||||||
|
* orientation.
|
||||||
|
*
|
||||||
|
* @param fileInfo The file info specifying the name and extension of the image file in the bundle.
|
||||||
|
*
|
||||||
|
* @return The `MPPImage` object contains the loaded image. This method returns nil if it cannot
|
||||||
|
* load the image.
|
||||||
|
*/
|
||||||
|
+ (MPPImage *)imageWithFileInfo:(MPPFileInfo *)fileInfo
|
||||||
|
orientation:(UIImageOrientation)orientation
|
||||||
|
NS_SWIFT_NAME(image(withFileInfo:orientation:));
|
||||||
|
|
||||||
|
// TODO: Remove after all tests are migrated
|
||||||
/**
|
/**
|
||||||
* Loads an image from a file in an app bundle into a `MPPImage` object.
|
* Loads an image from a file in an app bundle into a `MPPImage` object.
|
||||||
*
|
*
|
||||||
|
@ -31,14 +57,15 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
* @param name Name of the image file.
|
* @param name Name of the image file.
|
||||||
* @param type Extension of the image file.
|
* @param type Extension of the image file.
|
||||||
*
|
*
|
||||||
* @return The `MPPImage` object contains the loaded image. This method returns
|
* @return The `MPPImage` object contains the loaded image. This method returns nil if it cannot
|
||||||
* nil if it cannot load the image.
|
* load the image.
|
||||||
*/
|
*/
|
||||||
+ (nullable MPPImage *)imageFromBundleWithClass:(Class)classObject
|
+ (nullable MPPImage *)imageFromBundleWithClass:(Class)classObject
|
||||||
fileName:(NSString *)name
|
fileName:(NSString *)name
|
||||||
ofType:(NSString *)type
|
ofType:(NSString *)type
|
||||||
NS_SWIFT_NAME(imageFromBundle(class:filename:type:));
|
NS_SWIFT_NAME(imageFromBundle(class:filename:type:));
|
||||||
|
|
||||||
|
// TODO: Remove after all tests are migrated
|
||||||
/**
|
/**
|
||||||
* Loads an image from a file in an app bundle into a `MPPImage` object with the specified
|
* Loads an image from a file in an app bundle into a `MPPImage` object with the specified
|
||||||
* orientation.
|
* orientation.
|
||||||
|
@ -49,8 +76,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
* @param type Extension of the image file.
|
* @param type Extension of the image file.
|
||||||
* @param orientation Orientation of the image.
|
* @param orientation Orientation of the image.
|
||||||
*
|
*
|
||||||
* @return The `MPPImage` object contains the loaded image. This method returns
|
* @return The `MPPImage` object contains the loaded image. This method returns nil if it cannot
|
||||||
* nil if it cannot load the image.
|
* load the image.
|
||||||
*/
|
*/
|
||||||
+ (nullable MPPImage *)imageFromBundleWithClass:(Class)classObject
|
+ (nullable MPPImage *)imageFromBundleWithClass:(Class)classObject
|
||||||
fileName:(NSString *)name
|
fileName:(NSString *)name
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
#import "mediapipe/tasks/ios/test/vision/utils/sources/MPPImage+TestUtils.h"
|
#import "mediapipe/tasks/ios/test/vision/utils/sources/MPPImage+TestUtils.h"
|
||||||
|
|
||||||
|
// TODO: Remove this category after all tests are migrated to the new methods.
|
||||||
@interface UIImage (FileUtils)
|
@interface UIImage (FileUtils)
|
||||||
|
|
||||||
+ (nullable UIImage *)imageFromBundleWithClass:(Class)classObject
|
+ (nullable UIImage *)imageFromBundleWithClass:(Class)classObject
|
||||||
|
@ -37,6 +38,28 @@
|
||||||
|
|
||||||
@implementation MPPImage (TestUtils)
|
@implementation MPPImage (TestUtils)
|
||||||
|
|
||||||
|
+ (MPPImage *)imageWithFileInfo:(MPPFileInfo *)fileInfo {
|
||||||
|
if (!fileInfo.path) return nil;
|
||||||
|
|
||||||
|
UIImage *image = [[UIImage alloc] initWithContentsOfFile:fileInfo.path];
|
||||||
|
|
||||||
|
if (!image) return nil;
|
||||||
|
|
||||||
|
return [[MPPImage alloc] initWithUIImage:image error:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (MPPImage *)imageWithFileInfo:(MPPFileInfo *)fileInfo
|
||||||
|
orientation:(UIImageOrientation)orientation {
|
||||||
|
if (!fileInfo.path) return nil;
|
||||||
|
|
||||||
|
UIImage *image = [[UIImage alloc] initWithContentsOfFile:fileInfo.path];
|
||||||
|
|
||||||
|
if (!image) return nil;
|
||||||
|
|
||||||
|
return [[MPPImage alloc] initWithUIImage:image orientation:orientation error:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove after all tests are migrated
|
||||||
+ (nullable MPPImage *)imageFromBundleWithClass:(Class)classObject
|
+ (nullable MPPImage *)imageFromBundleWithClass:(Class)classObject
|
||||||
fileName:(NSString *)name
|
fileName:(NSString *)name
|
||||||
ofType:(NSString *)type {
|
ofType:(NSString *)type {
|
||||||
|
@ -45,6 +68,7 @@
|
||||||
return [[MPPImage alloc] initWithUIImage:image error:nil];
|
return [[MPPImage alloc] initWithUIImage:image error:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove after all tests are migrated
|
||||||
+ (nullable MPPImage *)imageFromBundleWithClass:(Class)classObject
|
+ (nullable MPPImage *)imageFromBundleWithClass:(Class)classObject
|
||||||
fileName:(NSString *)name
|
fileName:(NSString *)name
|
||||||
ofType:(NSString *)type
|
ofType:(NSString *)type
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
// 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 <Foundation/Foundation.h>
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
#import "mediapipe/tasks/ios/test/utils/sources/MPPFileInfo.h"
|
||||||
|
#import "mediapipe/tasks/ios/vision/core/sources/MPPMask.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper utility for initializing `MPPMask` for MediaPipe iOS vision library tests.
|
||||||
|
*/
|
||||||
|
@interface MPPMask (TestUtils)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an image from a file in an app bundle and Creates an `MPPMask` of type
|
||||||
|
* `MPPMaskDataTypeUInt8` using the gray scale pixel data of a `UIImage` loaded from a file with the
|
||||||
|
* given `MPPFileInfo`.
|
||||||
|
*
|
||||||
|
* @param fileInfo The file info specifying the name and type of the image file in the app bundle.
|
||||||
|
*
|
||||||
|
* @return The `MPPMask` with the pixel data of the loaded image. This method returns `nil` if there
|
||||||
|
* is an error in loading the image correctly.
|
||||||
|
*/
|
||||||
|
- (nullable instancetype)initWithImageFileInfo:(MPPFileInfo *)fileInfo;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,60 @@
|
||||||
|
// 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 "mediapipe/tasks/ios/test/vision/utils/sources/MPPMask+TestUtils.h"
|
||||||
|
|
||||||
|
@implementation MPPMask (TestUtils)
|
||||||
|
|
||||||
|
- (instancetype)initWithImageFileInfo:(MPPFileInfo *)fileInfo {
|
||||||
|
UIImage *image = [[UIImage alloc] initWithContentsOfFile:fileInfo.path];
|
||||||
|
|
||||||
|
if (!image.CGImage) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t width = CGImageGetWidth(image.CGImage);
|
||||||
|
size_t height = CGImageGetHeight(image.CGImage);
|
||||||
|
|
||||||
|
NSInteger bitsPerComponent = 8;
|
||||||
|
|
||||||
|
UInt8 *pixelData = NULL;
|
||||||
|
|
||||||
|
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
|
||||||
|
|
||||||
|
// For a gray scale image (single component) with no alpha, the bitmap info is
|
||||||
|
// `kCGImageAlphaNone` in combination with bytesPerRow being equal to width.
|
||||||
|
CGContextRef context = CGBitmapContextCreate(nil, width, height, bitsPerComponent, width,
|
||||||
|
colorSpace, kCGImageAlphaNone);
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
CGColorSpaceRelease(colorSpace);
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGContextDrawImage(context, CGRectMake(0, 0, width, height), image.CGImage);
|
||||||
|
pixelData = (UInt8 *)CGBitmapContextGetData(context);
|
||||||
|
|
||||||
|
// A copy is needed to ensure that the pixel data outlives the `CGContextRelease` call.
|
||||||
|
// Alternative is to make the context, color space instance variables and release them in
|
||||||
|
// `dealloc()`. Since Categories don't allow adding instance variables, choosing to copy rather
|
||||||
|
// than creating a new custom class similar to `MPPMask` only for the tests.
|
||||||
|
MPPMask *mask = [self initWithUInt8Data:pixelData width:width height:height shouldCopy:YES];
|
||||||
|
|
||||||
|
CGColorSpaceRelease(colorSpace);
|
||||||
|
CGContextRelease(context);
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -52,7 +52,7 @@ using ::mediapipe::Packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (categoryMaskPacket.ValidateAsType<Image>().ok()) {
|
if (categoryMaskPacket.ValidateAsType<Image>().ok()) {
|
||||||
const Image &cppCategoryMask = confidenceMasksPacket.Get<Image>();
|
const Image &cppCategoryMask = categoryMaskPacket.Get<Image>();
|
||||||
categoryMask = [[MPPMask alloc]
|
categoryMask = [[MPPMask alloc]
|
||||||
initWithUInt8Data:(UInt8 *)cppCategoryMask.GetImageFrameSharedPtr().get()->PixelData()
|
initWithUInt8Data:(UInt8 *)cppCategoryMask.GetImageFrameSharedPtr().get()->PixelData()
|
||||||
width:cppCategoryMask.width()
|
width:cppCategoryMask.width()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user