Merge pull request #4815 from priankakariatyml:ios-image-segmenter-basic-tests
PiperOrigin-RevId: 567102079
This commit is contained in:
commit
19c9d328cb
|
@ -53,6 +53,7 @@ objc_library(
|
||||||
"//mediapipe/tasks/testdata/vision:test_protos",
|
"//mediapipe/tasks/testdata/vision:test_protos",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//mediapipe/tasks/ios/common:MPPCommon",
|
||||||
"//mediapipe/tasks/ios/test/vision/utils:MPPImageTestUtils",
|
"//mediapipe/tasks/ios/test/vision/utils:MPPImageTestUtils",
|
||||||
"//mediapipe/tasks/ios/test/vision/utils:MPPMaskTestUtils",
|
"//mediapipe/tasks/ios/test/vision/utils:MPPMaskTestUtils",
|
||||||
"//mediapipe/tasks/ios/vision/image_segmenter:MPPImageSegmenter",
|
"//mediapipe/tasks/ios/vision/image_segmenter:MPPImageSegmenter",
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <XCTest/XCTest.h>
|
#import <XCTest/XCTest.h>
|
||||||
|
|
||||||
|
#import "mediapipe/tasks/ios/common/sources/MPPCommon.h"
|
||||||
#import "mediapipe/tasks/ios/test/vision/utils/sources/MPPImage+TestUtils.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/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/MPPImageSegmenter.h"
|
||||||
|
@ -30,11 +31,36 @@ static MPPFileInfo *const kSegmentationImageFileInfo =
|
||||||
[[MPPFileInfo alloc] initWithName:@"segmentation_input_rotation0" type:@"jpg"];
|
[[MPPFileInfo alloc] initWithName:@"segmentation_input_rotation0" type:@"jpg"];
|
||||||
static MPPFileInfo *const kSegmentationGoldenImageFileInfo =
|
static MPPFileInfo *const kSegmentationGoldenImageFileInfo =
|
||||||
[[MPPFileInfo alloc] initWithName:@"segmentation_golden_rotation0" type:@"png"];
|
[[MPPFileInfo alloc] initWithName:@"segmentation_golden_rotation0" type:@"png"];
|
||||||
static MPPFileInfo *const kImageSegmenterModel = [[MPPFileInfo alloc] initWithName:@"deeplabv3"
|
|
||||||
type:@"tflite"];
|
static MPPFileInfo *const kMozartImageFileInfo = [[MPPFileInfo alloc] initWithName:@"mozart_square"
|
||||||
|
type:@"jpg"];
|
||||||
|
static MPPFileInfo *const kMozart128x128SegmentationGoldenImageFileInfo =
|
||||||
|
[[MPPFileInfo alloc] initWithName:@"selfie_segm_128_128_3_expected_mask" type:@"jpg"];
|
||||||
|
static MPPFileInfo *const kMozart144x256SegmentationGoldenImageFileInfo =
|
||||||
|
[[MPPFileInfo alloc] initWithName:@"selfie_segm_144_256_3_expected_mask" type:@"jpg"];
|
||||||
|
|
||||||
|
static MPPFileInfo *const kImageSegmenterModelFileInfo =
|
||||||
|
[[MPPFileInfo alloc] initWithName:@"deeplabv3" type:@"tflite"];
|
||||||
|
static MPPFileInfo *const kSelfie128x128ModelFileInfo =
|
||||||
|
[[MPPFileInfo alloc] initWithName:@"selfie_segm_128_128_3" type:@"tflite"];
|
||||||
|
static MPPFileInfo *const kSelfie144x256ModelFileInfo =
|
||||||
|
[[MPPFileInfo alloc] initWithName:@"selfie_segm_144_256_3" type:@"tflite"];
|
||||||
|
|
||||||
static NSString *const kExpectedErrorDomain = @"com.google.mediapipe.tasks";
|
static NSString *const kExpectedErrorDomain = @"com.google.mediapipe.tasks";
|
||||||
|
static NSString *const kLiveStreamTestsDictImageSegmenterKey = @"image_segmenter";
|
||||||
|
static NSString *const kLiveStreamTestsDictExpectationKey = @"expectation";
|
||||||
|
|
||||||
constexpr float kSimilarityThreshold = 0.96f;
|
constexpr float kSimilarityThreshold = 0.96f;
|
||||||
constexpr NSInteger kMagnificationFactor = 10;
|
constexpr NSInteger kMagnificationFactor = 10;
|
||||||
|
constexpr NSInteger kExpectedDeeplabV3ConfidenceMaskCount = 21;
|
||||||
|
constexpr NSInteger kExpected128x128SelfieSegmentationConfidenceMaskCount = 2;
|
||||||
|
constexpr NSInteger kExpected144x256SelfieSegmentationConfidenceMaskCount = 1;
|
||||||
|
|
||||||
|
#define AssertEqualErrors(error, expectedError) \
|
||||||
|
XCTAssertNotNil(error); \
|
||||||
|
XCTAssertEqualObjects(error.domain, expectedError.domain); \
|
||||||
|
XCTAssertEqual(error.code, expectedError.code); \
|
||||||
|
XCTAssertEqualObjects(error.localizedDescription, expectedError.localizedDescription)
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
double sum(const std::vector<float> &mask) {
|
double sum(const std::vector<float> &mask) {
|
||||||
|
@ -70,9 +96,12 @@ double softIOU(const float *mask1, const float *mask2, size_t size) {
|
||||||
|
|
||||||
return unionSum > 0.0 ? interSectionSum / unionSum : 0.0;
|
return unionSum > 0.0 ? interSectionSum / unionSum : 0.0;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@interface MPPImageSegmenterTests : XCTestCase <MPPImageSegmenterLiveStreamDelegate>
|
@interface MPPImageSegmenterTests : XCTestCase <MPPImageSegmenterLiveStreamDelegate> {
|
||||||
|
NSDictionary<NSString *, id> *_liveStreamSucceedsTestDict;
|
||||||
|
NSDictionary<NSString *, id> *_outOfOrderTimestampTestDict;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -99,7 +128,7 @@ double softIOU(const float *mask1, const float *mask2, size_t size) {
|
||||||
|
|
||||||
- (void)testSegmentWithCategoryMaskSucceeds {
|
- (void)testSegmentWithCategoryMaskSucceeds {
|
||||||
MPPImageSegmenterOptions *options =
|
MPPImageSegmenterOptions *options =
|
||||||
[self imageSegmenterOptionsWithModelFileInfo:kImageSegmenterModel];
|
[self imageSegmenterOptionsWithModelFileInfo:kImageSegmenterModelFileInfo];
|
||||||
options.shouldOutputConfidenceMasks = NO;
|
options.shouldOutputConfidenceMasks = NO;
|
||||||
options.shouldOutputCategoryMask = YES;
|
options.shouldOutputCategoryMask = YES;
|
||||||
|
|
||||||
|
@ -113,17 +142,332 @@ double softIOU(const float *mask1, const float *mask2, size_t size) {
|
||||||
|
|
||||||
- (void)testSegmentWithConfidenceMaskSucceeds {
|
- (void)testSegmentWithConfidenceMaskSucceeds {
|
||||||
MPPImageSegmenterOptions *options =
|
MPPImageSegmenterOptions *options =
|
||||||
[self imageSegmenterOptionsWithModelFileInfo:kImageSegmenterModel];
|
[self imageSegmenterOptionsWithModelFileInfo:kImageSegmenterModelFileInfo];
|
||||||
|
|
||||||
MPPImageSegmenter *imageSegmenter = [self createImageSegmenterWithOptionsSucceeds:options];
|
MPPImageSegmenter *imageSegmenter = [self createImageSegmenterWithOptionsSucceeds:options];
|
||||||
|
|
||||||
[self assertResultsOfSegmentImageWithFileInfo:kCatImageFileInfo
|
[self assertResultsOfSegmentImageWithFileInfo:kCatImageFileInfo
|
||||||
usingImageSegmenter:imageSegmenter
|
usingImageSegmenter:imageSegmenter
|
||||||
|
hasConfidenceMasksCount:
|
||||||
|
kExpectedDeeplabV3ConfidenceMaskCount
|
||||||
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:kCatGoldenImageFileInfo
|
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:kCatGoldenImageFileInfo
|
||||||
atIndex:8
|
atIndex:8
|
||||||
shouldHaveCategoryMask:NO];
|
shouldHaveCategoryMask:NO];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)testSegmentWith128x128SegmentationSucceeds {
|
||||||
|
MPPImageSegmenterOptions *options =
|
||||||
|
[self imageSegmenterOptionsWithModelFileInfo:kSelfie128x128ModelFileInfo];
|
||||||
|
|
||||||
|
MPPImageSegmenter *imageSegmenter = [self createImageSegmenterWithOptionsSucceeds:options];
|
||||||
|
|
||||||
|
[self assertResultsOfSegmentImageWithFileInfo:kMozartImageFileInfo
|
||||||
|
usingImageSegmenter:imageSegmenter
|
||||||
|
hasConfidenceMasksCount:
|
||||||
|
kExpected128x128SelfieSegmentationConfidenceMaskCount
|
||||||
|
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:
|
||||||
|
kMozart128x128SegmentationGoldenImageFileInfo
|
||||||
|
atIndex:1
|
||||||
|
shouldHaveCategoryMask:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testSegmentWith144x256SegmentationSucceeds {
|
||||||
|
MPPImageSegmenterOptions *options =
|
||||||
|
[self imageSegmenterOptionsWithModelFileInfo:kSelfie144x256ModelFileInfo];
|
||||||
|
|
||||||
|
MPPImageSegmenter *imageSegmenter = [self createImageSegmenterWithOptionsSucceeds:options];
|
||||||
|
|
||||||
|
[self assertResultsOfSegmentImageWithFileInfo:kMozartImageFileInfo
|
||||||
|
usingImageSegmenter:imageSegmenter
|
||||||
|
hasConfidenceMasksCount:
|
||||||
|
kExpected144x256SelfieSegmentationConfidenceMaskCount
|
||||||
|
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:
|
||||||
|
kMozart144x256SegmentationGoldenImageFileInfo
|
||||||
|
atIndex:0
|
||||||
|
shouldHaveCategoryMask:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark Running Mode Tests
|
||||||
|
|
||||||
|
- (void)testCreateImageSegmenterFailsWithDelegateInNonLiveStreamMode {
|
||||||
|
MPPRunningMode runningModesToTest[] = {MPPRunningModeImage, MPPRunningModeVideo};
|
||||||
|
for (int i = 0; i < sizeof(runningModesToTest) / sizeof(runningModesToTest[0]); i++) {
|
||||||
|
MPPImageSegmenterOptions *options =
|
||||||
|
[self imageSegmenterOptionsWithModelFileInfo:kSelfie128x128ModelFileInfo];
|
||||||
|
|
||||||
|
options.runningMode = runningModesToTest[i];
|
||||||
|
options.imageSegmenterLiveStreamDelegate = self;
|
||||||
|
|
||||||
|
[self
|
||||||
|
assertCreateImageSegmenterWithOptions:options
|
||||||
|
failsWithExpectedError:
|
||||||
|
[NSError errorWithDomain:kExpectedErrorDomain
|
||||||
|
code:MPPTasksErrorCodeInvalidArgumentError
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey :
|
||||||
|
@"The vision task is in image or video mode. The "
|
||||||
|
@"delegate must not be set in the task's options."
|
||||||
|
}]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testCreateImageSegmenterFailsWithMissingDelegateInLiveStreamMode {
|
||||||
|
MPPImageSegmenterOptions *options =
|
||||||
|
[self imageSegmenterOptionsWithModelFileInfo:kSelfie128x128ModelFileInfo];
|
||||||
|
|
||||||
|
options.runningMode = MPPRunningModeLiveStream;
|
||||||
|
|
||||||
|
[self assertCreateImageSegmenterWithOptions:options
|
||||||
|
failsWithExpectedError:
|
||||||
|
[NSError
|
||||||
|
errorWithDomain:kExpectedErrorDomain
|
||||||
|
code:MPPTasksErrorCodeInvalidArgumentError
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey :
|
||||||
|
@"The vision task is in live stream mode. An object "
|
||||||
|
@"must be set as the delegate of the task in its "
|
||||||
|
@"options to ensure asynchronous delivery of results."
|
||||||
|
}]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testSegmentFailsWithCallingWrongApiInImageMode {
|
||||||
|
MPPImageSegmenterOptions *options =
|
||||||
|
[self imageSegmenterOptionsWithModelFileInfo:kImageSegmenterModelFileInfo];
|
||||||
|
|
||||||
|
MPPImageSegmenter *imageSegmenter = [self createImageSegmenterWithOptionsSucceeds:options];
|
||||||
|
|
||||||
|
MPPImage *image = [MPPImage imageWithFileInfo:kCatImageFileInfo];
|
||||||
|
XCTAssertNotNil(image);
|
||||||
|
|
||||||
|
NSError *liveStreamApiCallError;
|
||||||
|
XCTAssertFalse([imageSegmenter segmentAsyncImage:image
|
||||||
|
timestampInMilliseconds:0
|
||||||
|
error:&liveStreamApiCallError]);
|
||||||
|
|
||||||
|
NSError *expectedLiveStreamApiCallError =
|
||||||
|
[NSError errorWithDomain:kExpectedErrorDomain
|
||||||
|
code:MPPTasksErrorCodeInvalidArgumentError
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey : @"The vision task is not initialized with live "
|
||||||
|
@"stream mode. Current Running Mode: Image"
|
||||||
|
}];
|
||||||
|
|
||||||
|
AssertEqualErrors(liveStreamApiCallError, expectedLiveStreamApiCallError);
|
||||||
|
|
||||||
|
NSError *videoApiCallError;
|
||||||
|
XCTAssertFalse([imageSegmenter segmentVideoFrame:image
|
||||||
|
timestampInMilliseconds:0
|
||||||
|
error:&videoApiCallError]);
|
||||||
|
|
||||||
|
NSError *expectedVideoApiCallError =
|
||||||
|
[NSError errorWithDomain:kExpectedErrorDomain
|
||||||
|
code:MPPTasksErrorCodeInvalidArgumentError
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey : @"The vision task is not initialized with "
|
||||||
|
@"video mode. Current Running Mode: Image"
|
||||||
|
}];
|
||||||
|
AssertEqualErrors(videoApiCallError, expectedVideoApiCallError);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testSegmentFailsWithCallingWrongApiInVideoMode {
|
||||||
|
MPPImageSegmenterOptions *options =
|
||||||
|
[self imageSegmenterOptionsWithModelFileInfo:kImageSegmenterModelFileInfo];
|
||||||
|
options.runningMode = MPPRunningModeVideo;
|
||||||
|
|
||||||
|
MPPImageSegmenter *imageSegmenter = [self createImageSegmenterWithOptionsSucceeds:options];
|
||||||
|
|
||||||
|
MPPImage *image = [MPPImage imageWithFileInfo:kCatImageFileInfo];
|
||||||
|
XCTAssertNotNil(image);
|
||||||
|
|
||||||
|
NSError *liveStreamApiCallError;
|
||||||
|
XCTAssertFalse([imageSegmenter segmentAsyncImage:image
|
||||||
|
timestampInMilliseconds:0
|
||||||
|
error:&liveStreamApiCallError]);
|
||||||
|
|
||||||
|
NSError *expectedLiveStreamApiCallError =
|
||||||
|
[NSError errorWithDomain:kExpectedErrorDomain
|
||||||
|
code:MPPTasksErrorCodeInvalidArgumentError
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey : @"The vision task is not initialized with live "
|
||||||
|
@"stream mode. Current Running Mode: Video"
|
||||||
|
}];
|
||||||
|
|
||||||
|
AssertEqualErrors(liveStreamApiCallError, expectedLiveStreamApiCallError);
|
||||||
|
|
||||||
|
NSError *imageApiCallError;
|
||||||
|
XCTAssertFalse([imageSegmenter segmentImage:image error:&imageApiCallError]);
|
||||||
|
|
||||||
|
NSError *expectedImageApiCallError =
|
||||||
|
[NSError errorWithDomain:kExpectedErrorDomain
|
||||||
|
code:MPPTasksErrorCodeInvalidArgumentError
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey : @"The vision task is not initialized with "
|
||||||
|
@"image mode. Current Running Mode: Video"
|
||||||
|
}];
|
||||||
|
AssertEqualErrors(imageApiCallError, expectedImageApiCallError);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testSegmentFailsWithCallingWrongApiInLiveStreamMode {
|
||||||
|
MPPImageSegmenterOptions *options =
|
||||||
|
[self imageSegmenterOptionsWithModelFileInfo:kImageSegmenterModelFileInfo];
|
||||||
|
options.runningMode = MPPRunningModeLiveStream;
|
||||||
|
options.imageSegmenterLiveStreamDelegate = self;
|
||||||
|
|
||||||
|
MPPImageSegmenter *imageSegmenter = [self createImageSegmenterWithOptionsSucceeds:options];
|
||||||
|
|
||||||
|
MPPImage *image = [MPPImage imageWithFileInfo:kCatImageFileInfo];
|
||||||
|
XCTAssertNotNil(image);
|
||||||
|
|
||||||
|
NSError *imageApiCallError;
|
||||||
|
XCTAssertFalse([imageSegmenter segmentImage:image error:&imageApiCallError]);
|
||||||
|
|
||||||
|
NSError *expectedImageApiCallError =
|
||||||
|
[NSError errorWithDomain:kExpectedErrorDomain
|
||||||
|
code:MPPTasksErrorCodeInvalidArgumentError
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey : @"The vision task is not initialized with "
|
||||||
|
@"image mode. Current Running Mode: Live Stream"
|
||||||
|
}];
|
||||||
|
AssertEqualErrors(imageApiCallError, expectedImageApiCallError);
|
||||||
|
|
||||||
|
NSError *videoApiCallError;
|
||||||
|
XCTAssertFalse([imageSegmenter segmentVideoFrame:image
|
||||||
|
timestampInMilliseconds:0
|
||||||
|
error:&videoApiCallError]);
|
||||||
|
|
||||||
|
NSError *expectedVideoApiCallError =
|
||||||
|
[NSError errorWithDomain:kExpectedErrorDomain
|
||||||
|
code:MPPTasksErrorCodeInvalidArgumentError
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey : @"The vision task is not initialized with "
|
||||||
|
@"video mode. Current Running Mode: Live Stream"
|
||||||
|
}];
|
||||||
|
AssertEqualErrors(videoApiCallError, expectedVideoApiCallError);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testSegmentWithVideoModeSucceeds {
|
||||||
|
MPPImageSegmenterOptions *options =
|
||||||
|
[self imageSegmenterOptionsWithModelFileInfo:kImageSegmenterModelFileInfo];
|
||||||
|
options.runningMode = MPPRunningModeVideo;
|
||||||
|
|
||||||
|
MPPImageSegmenter *imageSegmenter = [self createImageSegmenterWithOptionsSucceeds:options];
|
||||||
|
|
||||||
|
MPPImage *image = [MPPImage imageWithFileInfo:kCatImageFileInfo];
|
||||||
|
XCTAssertNotNil(image);
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
MPPImageSegmenterResult *result = [imageSegmenter segmentVideoFrame:image
|
||||||
|
timestampInMilliseconds:i
|
||||||
|
error:nil];
|
||||||
|
[self assertImageSegmenterResult:result
|
||||||
|
hasConfidenceMasksCount:
|
||||||
|
kExpectedDeeplabV3ConfidenceMaskCount
|
||||||
|
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:kCatGoldenImageFileInfo
|
||||||
|
atIndex:8
|
||||||
|
shouldHaveCategoryMask:NO];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testSegmentWithOutOfOrderTimestampsAndLiveStreamModeFails {
|
||||||
|
MPPImageSegmenterOptions *options =
|
||||||
|
[self imageSegmenterOptionsWithModelFileInfo:kImageSegmenterModelFileInfo];
|
||||||
|
options.runningMode = MPPRunningModeLiveStream;
|
||||||
|
options.imageSegmenterLiveStreamDelegate = self;
|
||||||
|
|
||||||
|
XCTestExpectation *expectation = [[XCTestExpectation alloc]
|
||||||
|
initWithDescription:@"segmentWithOutOfOrderTimestampsAndLiveStream"];
|
||||||
|
|
||||||
|
expectation.expectedFulfillmentCount = 1;
|
||||||
|
|
||||||
|
MPPImageSegmenter *imageSegmenter = [self createImageSegmenterWithOptionsSucceeds:options];
|
||||||
|
|
||||||
|
_outOfOrderTimestampTestDict = @{
|
||||||
|
kLiveStreamTestsDictImageSegmenterKey : imageSegmenter,
|
||||||
|
kLiveStreamTestsDictExpectationKey : expectation
|
||||||
|
};
|
||||||
|
|
||||||
|
MPPImage *image = [MPPImage imageWithFileInfo:kCatImageFileInfo];
|
||||||
|
XCTAssertNotNil(image);
|
||||||
|
|
||||||
|
XCTAssertTrue([imageSegmenter segmentAsyncImage:image timestampInMilliseconds:1 error:nil]);
|
||||||
|
|
||||||
|
NSError *error;
|
||||||
|
XCTAssertFalse([imageSegmenter segmentAsyncImage:image timestampInMilliseconds:0 error:&error]);
|
||||||
|
|
||||||
|
NSError *expectedError =
|
||||||
|
[NSError errorWithDomain:kExpectedErrorDomain
|
||||||
|
code:MPPTasksErrorCodeInvalidArgumentError
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey :
|
||||||
|
@"INVALID_ARGUMENT: Input timestamp must be monotonically increasing."
|
||||||
|
}];
|
||||||
|
AssertEqualErrors(error, expectedError);
|
||||||
|
|
||||||
|
NSTimeInterval timeout = 0.5f;
|
||||||
|
[self waitForExpectations:@[ expectation ] timeout:timeout];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testSegmentWithLiveStreamModeSucceeds {
|
||||||
|
MPPImageSegmenterOptions *options =
|
||||||
|
[self imageSegmenterOptionsWithModelFileInfo:kImageSegmenterModelFileInfo];
|
||||||
|
options.runningMode = MPPRunningModeLiveStream;
|
||||||
|
options.imageSegmenterLiveStreamDelegate = self;
|
||||||
|
|
||||||
|
NSInteger iterationCount = 100;
|
||||||
|
|
||||||
|
// Because of flow limiting, we cannot ensure that the callback will be invoked `iterationCount`
|
||||||
|
// times. An normal expectation will fail if expectation.fulfill() is not called
|
||||||
|
// `expectation.expectedFulfillmentCount` times. If `expectation.isInverted = true`, the test will
|
||||||
|
// only succeed if expectation is not fulfilled for the specified `expectedFulfillmentCount`.
|
||||||
|
// Since in our case we cannot predict how many times the expectation is supposed to be fulfilled
|
||||||
|
// setting, `expectation.expectedFulfillmentCount` = `iterationCount` + 1 and
|
||||||
|
// `expectation.isInverted = true` ensures that test succeeds ifexpectation is fulfilled <=
|
||||||
|
// `iterationCount` times.
|
||||||
|
XCTestExpectation *expectation =
|
||||||
|
[[XCTestExpectation alloc] initWithDescription:@"segmentWithLiveStream"];
|
||||||
|
|
||||||
|
expectation.expectedFulfillmentCount = iterationCount + 1;
|
||||||
|
expectation.inverted = YES;
|
||||||
|
|
||||||
|
MPPImageSegmenter *imageSegmenter = [self createImageSegmenterWithOptionsSucceeds:options];
|
||||||
|
|
||||||
|
_outOfOrderTimestampTestDict = @{
|
||||||
|
kLiveStreamTestsDictImageSegmenterKey : imageSegmenter,
|
||||||
|
kLiveStreamTestsDictExpectationKey : expectation
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Mimic initialization from CMSampleBuffer as live stream mode is most likely to be used
|
||||||
|
// with the iOS camera. AVCaptureVideoDataOutput sample buffer delegates provide frames of type
|
||||||
|
// `CMSampleBuffer`.
|
||||||
|
MPPImage *image = [MPPImage imageWithFileInfo:kCatImageFileInfo];
|
||||||
|
XCTAssertNotNil(image);
|
||||||
|
|
||||||
|
for (int i = 0; i < iterationCount; i++) {
|
||||||
|
XCTAssertTrue([imageSegmenter segmentAsyncImage:image timestampInMilliseconds:i error:nil]);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSTimeInterval timeout = 0.5f;
|
||||||
|
[self waitForExpectations:@[ expectation ] timeout:timeout];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)imageSegmenter:(MPPImageSegmenter *)imageSegmenter
|
||||||
|
didFinishSegmentationWithResult:(MPPImageSegmenterResult *)imageSegmenterResult
|
||||||
|
timestampInMilliseconds:(NSInteger)timestampInMilliseconds
|
||||||
|
error:(NSError *)error {
|
||||||
|
[self assertImageSegmenterResult:imageSegmenterResult
|
||||||
|
hasConfidenceMasksCount:
|
||||||
|
kExpectedDeeplabV3ConfidenceMaskCount
|
||||||
|
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:kCatGoldenImageFileInfo
|
||||||
|
atIndex:8
|
||||||
|
shouldHaveCategoryMask:NO];
|
||||||
|
|
||||||
|
if (imageSegmenter == _outOfOrderTimestampTestDict[kLiveStreamTestsDictImageSegmenterKey]) {
|
||||||
|
[_outOfOrderTimestampTestDict[kLiveStreamTestsDictExpectationKey] fulfill];
|
||||||
|
} else if (imageSegmenter == _liveStreamSucceedsTestDict[kLiveStreamTestsDictImageSegmenterKey]) {
|
||||||
|
[_liveStreamSucceedsTestDict[kLiveStreamTestsDictExpectationKey] fulfill];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Image Segmenter Initializers
|
#pragma mark - Image Segmenter Initializers
|
||||||
|
|
||||||
- (MPPImageSegmenterOptions *)imageSegmenterOptionsWithModelFileInfo:(MPPFileInfo *)fileInfo {
|
- (MPPImageSegmenterOptions *)imageSegmenterOptionsWithModelFileInfo:(MPPFileInfo *)fileInfo {
|
||||||
|
@ -142,6 +486,16 @@ double softIOU(const float *mask1, const float *mask2, size_t size) {
|
||||||
return imageSegmenter;
|
return imageSegmenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)assertCreateImageSegmenterWithOptions:(MPPImageSegmenterOptions *)options
|
||||||
|
failsWithExpectedError:(NSError *)expectedError {
|
||||||
|
NSError *error = nil;
|
||||||
|
MPPImageSegmenter *imageSegmenter = [[MPPImageSegmenter alloc] initWithOptions:options
|
||||||
|
error:&error];
|
||||||
|
|
||||||
|
XCTAssertNil(imageSegmenter);
|
||||||
|
AssertEqualErrors(error, expectedError);
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark Assert Segmenter Results
|
#pragma mark Assert Segmenter Results
|
||||||
- (void)assertResultsOfSegmentImageWithFileInfo:(MPPFileInfo *)imageFileInfo
|
- (void)assertResultsOfSegmentImageWithFileInfo:(MPPFileInfo *)imageFileInfo
|
||||||
usingImageSegmenter:(MPPImageSegmenter *)imageSegmenter
|
usingImageSegmenter:(MPPImageSegmenter *)imageSegmenter
|
||||||
|
@ -165,6 +519,8 @@ double softIOU(const float *mask1, const float *mask2, size_t size) {
|
||||||
|
|
||||||
- (void)assertResultsOfSegmentImageWithFileInfo:(MPPFileInfo *)imageFileInfo
|
- (void)assertResultsOfSegmentImageWithFileInfo:(MPPFileInfo *)imageFileInfo
|
||||||
usingImageSegmenter:(MPPImageSegmenter *)imageSegmenter
|
usingImageSegmenter:(MPPImageSegmenter *)imageSegmenter
|
||||||
|
hasConfidenceMasksCount:
|
||||||
|
(NSUInteger)expectedConfidenceMasksCount
|
||||||
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:
|
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:
|
||||||
(MPPFileInfo *)expectedConfidenceMaskFileInfo
|
(MPPFileInfo *)expectedConfidenceMaskFileInfo
|
||||||
atIndex:(NSInteger)index
|
atIndex:(NSInteger)index
|
||||||
|
@ -172,8 +528,24 @@ double softIOU(const float *mask1, const float *mask2, size_t size) {
|
||||||
MPPImageSegmenterResult *result = [self segmentImageWithFileInfo:imageFileInfo
|
MPPImageSegmenterResult *result = [self segmentImageWithFileInfo:imageFileInfo
|
||||||
usingImageSegmenter:imageSegmenter];
|
usingImageSegmenter:imageSegmenter];
|
||||||
|
|
||||||
|
[self assertImageSegmenterResult:result
|
||||||
|
hasConfidenceMasksCount:expectedConfidenceMasksCount
|
||||||
|
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:expectedConfidenceMaskFileInfo
|
||||||
|
atIndex:index
|
||||||
|
shouldHaveCategoryMask:shouldHaveCategoryMask];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)assertImageSegmenterResult:(MPPImageSegmenterResult *)result
|
||||||
|
hasConfidenceMasksCount:
|
||||||
|
(NSUInteger)expectedConfidenceMasksCount
|
||||||
|
approximatelyEqualsExpectedConfidenceMaskImageWithFileInfo:
|
||||||
|
(MPPFileInfo *)expectedConfidenceMaskFileInfo
|
||||||
|
atIndex:(NSInteger)index
|
||||||
|
shouldHaveCategoryMask:(BOOL)shouldHaveCategoryMask {
|
||||||
XCTAssertNotNil(result.confidenceMasks);
|
XCTAssertNotNil(result.confidenceMasks);
|
||||||
|
|
||||||
|
XCTAssertEqual(result.confidenceMasks.count, expectedConfidenceMasksCount);
|
||||||
|
|
||||||
if (shouldHaveCategoryMask) {
|
if (shouldHaveCategoryMask) {
|
||||||
XCTAssertNotNil(result.categoryMask);
|
XCTAssertNotNil(result.categoryMask);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -203,7 +203,7 @@ NS_SWIFT_NAME(ImageSegmenter)
|
||||||
*
|
*
|
||||||
* @return `YES` if the image was sent to the task successfully, otherwise `NO`.
|
* @return `YES` if the image was sent to the task successfully, otherwise `NO`.
|
||||||
*/
|
*/
|
||||||
- (BOOL)segmentAsyncInImage:(MPPImage *)image
|
- (BOOL)segmentAsyncImage:(MPPImage *)image
|
||||||
timestampInMilliseconds:(NSInteger)timestampInMilliseconds
|
timestampInMilliseconds:(NSInteger)timestampInMilliseconds
|
||||||
error:(NSError **)error
|
error:(NSError **)error
|
||||||
NS_SWIFT_NAME(segmentAsync(image:timestampInMilliseconds:));
|
NS_SWIFT_NAME(segmentAsync(image:timestampInMilliseconds:));
|
||||||
|
|
|
@ -187,7 +187,7 @@ using ::mediapipe::tasks::core::PacketsCallback;
|
||||||
shouldCopyMaskPacketData:NO];
|
shouldCopyMaskPacketData:NO];
|
||||||
completionHandler(result, error);
|
completionHandler(result, error);
|
||||||
}
|
}
|
||||||
- (BOOL)segmentAsyncInImage:(MPPImage *)image
|
- (BOOL)segmentAsyncImage:(MPPImage *)image
|
||||||
timestampInMilliseconds:(NSInteger)timestampInMilliseconds
|
timestampInMilliseconds:(NSInteger)timestampInMilliseconds
|
||||||
error:(NSError **)error {
|
error:(NSError **)error {
|
||||||
return [_visionTaskRunner processLiveStreamImage:image
|
return [_visionTaskRunner processLiveStreamImage:image
|
||||||
|
@ -243,16 +243,19 @@ using ::mediapipe::tasks::core::PacketsCallback;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PacketMap &outputPacketMap = liveStreamResult.value();
|
// Output packet map is moved to a block variable that will not be deallocated for the lifetime of
|
||||||
|
// the `dispatch_async` call. Since masks are not copied, this ensures that they are only
|
||||||
|
// deallocated after the delegate call completes.
|
||||||
|
__block PacketMap outputPacketMap = std::move(liveStreamResult.value());
|
||||||
if (outputPacketMap[kImageOutStreamName.cppString].IsEmpty()) {
|
if (outputPacketMap[kImageOutStreamName.cppString].IsEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MPPImageSegmenterResult *result =
|
|
||||||
[MPPImageSegmenter imageSegmenterResultWithOutputPacketMap:outputPacketMap
|
|
||||||
shouldCopyMaskPacketData:NO];
|
|
||||||
|
|
||||||
dispatch_async(_callbackQueue, ^{
|
dispatch_async(_callbackQueue, ^{
|
||||||
|
MPPImageSegmenterResult *result =
|
||||||
|
[MPPImageSegmenter imageSegmenterResultWithOutputPacketMap:outputPacketMap
|
||||||
|
shouldCopyMaskPacketData:NO];
|
||||||
|
|
||||||
[self.imageSegmenterLiveStreamDelegate imageSegmenter:self
|
[self.imageSegmenterLiveStreamDelegate imageSegmenter:self
|
||||||
didFinishSegmentationWithResult:result
|
didFinishSegmentationWithResult:result
|
||||||
timestampInMilliseconds:result.timestampInMilliseconds
|
timestampInMilliseconds:result.timestampInMilliseconds
|
||||||
|
|
Loading…
Reference in New Issue
Block a user