Open Source the first set of MediaPipe Tasks tests for Web
PiperOrigin-RevId: 493673279
This commit is contained in:
committed by
Copybara-Service
parent
3c0ddf16b4
commit
2811e0c5c8
@@ -228,6 +228,8 @@ def mediapipe_ts_library(
|
||||
srcs = srcs,
|
||||
visibility = visibility,
|
||||
deps = deps + [
|
||||
"@npm//@types/jasmine",
|
||||
"@npm//@types/node",
|
||||
"@npm//@types/offscreencanvas",
|
||||
"@npm//@types/google-protobuf",
|
||||
],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# This package contains options shared by all MediaPipe Tasks for Web.
|
||||
|
||||
load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test")
|
||||
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_ts_library")
|
||||
|
||||
package(default_visibility = ["//mediapipe/tasks:internal"])
|
||||
@@ -13,6 +14,22 @@ mediapipe_ts_library(
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_ts_library(
|
||||
name = "classifier_options_test_lib",
|
||||
testonly = True,
|
||||
srcs = ["classifier_options.test.ts"],
|
||||
deps = [
|
||||
":classifier_options",
|
||||
"//mediapipe/tasks/cc/components/processors/proto:classifier_options_jspb_proto",
|
||||
"//mediapipe/tasks/web/core:classifier_options",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "classifier_options_test",
|
||||
deps = [":classifier_options_test_lib"],
|
||||
)
|
||||
|
||||
mediapipe_ts_library(
|
||||
name = "classifier_result",
|
||||
srcs = ["classifier_result.ts"],
|
||||
@@ -22,6 +39,22 @@ mediapipe_ts_library(
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_ts_library(
|
||||
name = "classifier_result_test_lib",
|
||||
testonly = True,
|
||||
srcs = ["classifier_result.test.ts"],
|
||||
deps = [
|
||||
":classifier_result",
|
||||
"//mediapipe/framework/formats:classification_jspb_proto",
|
||||
"//mediapipe/tasks/cc/components/containers/proto:classifications_jspb_proto",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "classifier_result_test",
|
||||
deps = [":classifier_result_test_lib"],
|
||||
)
|
||||
|
||||
mediapipe_ts_library(
|
||||
name = "embedder_result",
|
||||
srcs = ["embedder_result.ts"],
|
||||
@@ -31,6 +64,21 @@ mediapipe_ts_library(
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_ts_library(
|
||||
name = "embedder_result_test_lib",
|
||||
testonly = True,
|
||||
srcs = ["embedder_result.test.ts"],
|
||||
deps = [
|
||||
":embedder_result",
|
||||
"//mediapipe/tasks/cc/components/containers/proto:embeddings_jspb_proto",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "embedder_result_test",
|
||||
deps = [":embedder_result_test_lib"],
|
||||
)
|
||||
|
||||
mediapipe_ts_library(
|
||||
name = "embedder_options",
|
||||
srcs = ["embedder_options.ts"],
|
||||
@@ -40,6 +88,22 @@ mediapipe_ts_library(
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_ts_library(
|
||||
name = "embedder_options_test_lib",
|
||||
testonly = True,
|
||||
srcs = ["embedder_options.test.ts"],
|
||||
deps = [
|
||||
":embedder_options",
|
||||
"//mediapipe/tasks/cc/components/processors/proto:embedder_options_jspb_proto",
|
||||
"//mediapipe/tasks/web/core:embedder_options",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "embedder_options_test",
|
||||
deps = [":embedder_options_test_lib"],
|
||||
)
|
||||
|
||||
mediapipe_ts_library(
|
||||
name = "base_options",
|
||||
srcs = [
|
||||
@@ -53,3 +117,15 @@ mediapipe_ts_library(
|
||||
"//mediapipe/tasks/web/core",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_ts_library(
|
||||
name = "base_options_test_lib",
|
||||
testonly = True,
|
||||
srcs = ["base_options.test.ts"],
|
||||
deps = [":base_options"],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "base_options_test",
|
||||
deps = [":base_options_test_lib"],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
import 'jasmine';
|
||||
|
||||
// Placeholder for internal dependency on encodeByteArray
|
||||
// Placeholder for internal dependency on trusted resource URL builder
|
||||
|
||||
import {convertBaseOptionsToProto} from './base_options';
|
||||
|
||||
describe('convertBaseOptionsToProto()', () => {
|
||||
const mockBytes = new Uint8Array([0, 1, 2, 3]);
|
||||
const mockBytesResult = {
|
||||
modelAsset: {
|
||||
fileContent: Buffer.from(mockBytes).toString('base64'),
|
||||
fileName: undefined,
|
||||
fileDescriptorMeta: undefined,
|
||||
filePointerMeta: undefined,
|
||||
},
|
||||
useStreamMode: false,
|
||||
acceleration: {
|
||||
xnnpack: undefined,
|
||||
gpu: undefined,
|
||||
tflite: {},
|
||||
},
|
||||
};
|
||||
|
||||
let fetchSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
fetchSpy = jasmine.createSpy().and.callFake(async url => {
|
||||
expect(url).toEqual('foo');
|
||||
return {
|
||||
arrayBuffer: () => mockBytes.buffer,
|
||||
} as unknown as Response;
|
||||
});
|
||||
global.fetch = fetchSpy;
|
||||
});
|
||||
|
||||
it('verifies that at least one model asset option is provided', async () => {
|
||||
await expectAsync(convertBaseOptionsToProto({}))
|
||||
.toBeRejectedWithError(
|
||||
/Either baseOptions.modelAssetPath or baseOptions.modelAssetBuffer must be set/);
|
||||
});
|
||||
|
||||
it('verifies that no more than one model asset option is provided', async () => {
|
||||
await expectAsync(convertBaseOptionsToProto({
|
||||
modelAssetPath: `foo`,
|
||||
modelAssetBuffer: new Uint8Array([])
|
||||
}))
|
||||
.toBeRejectedWithError(
|
||||
/Cannot set both baseOptions.modelAssetPath and baseOptions.modelAssetBuffer/);
|
||||
});
|
||||
|
||||
it('downloads model', async () => {
|
||||
const baseOptionsProto = await convertBaseOptionsToProto({
|
||||
modelAssetPath: `foo`,
|
||||
});
|
||||
|
||||
expect(fetchSpy).toHaveBeenCalled();
|
||||
expect(baseOptionsProto.toObject()).toEqual(mockBytesResult);
|
||||
});
|
||||
|
||||
it('does not download model when bytes are provided', async () => {
|
||||
const baseOptionsProto = await convertBaseOptionsToProto({
|
||||
modelAssetBuffer: new Uint8Array(mockBytes),
|
||||
});
|
||||
|
||||
expect(fetchSpy).not.toHaveBeenCalled();
|
||||
expect(baseOptionsProto.toObject()).toEqual(mockBytesResult);
|
||||
});
|
||||
|
||||
it('can enable CPU delegate', async () => {
|
||||
const baseOptionsProto = await convertBaseOptionsToProto({
|
||||
modelAssetBuffer: new Uint8Array(mockBytes),
|
||||
delegate: 'cpu',
|
||||
});
|
||||
expect(baseOptionsProto.toObject()).toEqual(mockBytesResult);
|
||||
});
|
||||
|
||||
it('can enable GPU delegate', async () => {
|
||||
const baseOptionsProto = await convertBaseOptionsToProto({
|
||||
modelAssetBuffer: new Uint8Array(mockBytes),
|
||||
delegate: 'gpu',
|
||||
});
|
||||
expect(baseOptionsProto.toObject()).toEqual({
|
||||
...mockBytesResult,
|
||||
acceleration: {
|
||||
xnnpack: undefined,
|
||||
gpu: {
|
||||
useAdvancedGpuApi: false,
|
||||
api: 0,
|
||||
allowPrecisionLoss: true,
|
||||
cachedKernelPath: undefined,
|
||||
serializedModelDir: undefined,
|
||||
modelToken: undefined,
|
||||
usage: 2,
|
||||
},
|
||||
tflite: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('can reset delegate', async () => {
|
||||
let baseOptionsProto = await convertBaseOptionsToProto({
|
||||
modelAssetBuffer: new Uint8Array(mockBytes),
|
||||
delegate: 'gpu',
|
||||
});
|
||||
// Clear backend
|
||||
baseOptionsProto =
|
||||
await convertBaseOptionsToProto({delegate: undefined}, baseOptionsProto);
|
||||
expect(baseOptionsProto.toObject()).toEqual(mockBytesResult);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
import 'jasmine';
|
||||
|
||||
import {ClassifierOptions as ClassifierOptionsProto} from '../../../../tasks/cc/components/processors/proto/classifier_options_pb';
|
||||
import {ClassifierOptions} from '../../../../tasks/web/core/classifier_options';
|
||||
|
||||
import {convertClassifierOptionsToProto} from './classifier_options';
|
||||
|
||||
interface TestCase {
|
||||
optionName: keyof ClassifierOptions;
|
||||
protoName: string;
|
||||
customValue: unknown;
|
||||
defaultValue: unknown;
|
||||
}
|
||||
|
||||
describe('convertClassifierOptionsToProto()', () => {
|
||||
function verifyOption(
|
||||
actualClassifierOptions: ClassifierOptionsProto,
|
||||
expectedClassifierOptions: Record<string, unknown> = {}): void {
|
||||
expect(actualClassifierOptions.toObject())
|
||||
.toEqual(jasmine.objectContaining(expectedClassifierOptions));
|
||||
}
|
||||
|
||||
const testCases: TestCase[] = [
|
||||
{
|
||||
optionName: 'maxResults',
|
||||
protoName: 'maxResults',
|
||||
customValue: 5,
|
||||
defaultValue: -1
|
||||
},
|
||||
{
|
||||
optionName: 'displayNamesLocale',
|
||||
protoName: 'displayNamesLocale',
|
||||
customValue: 'en',
|
||||
defaultValue: 'en'
|
||||
},
|
||||
{
|
||||
optionName: 'scoreThreshold',
|
||||
protoName: 'scoreThreshold',
|
||||
customValue: 0.1,
|
||||
defaultValue: undefined
|
||||
},
|
||||
{
|
||||
optionName: 'categoryAllowlist',
|
||||
protoName: 'categoryAllowlistList',
|
||||
customValue: ['foo'],
|
||||
defaultValue: []
|
||||
},
|
||||
{
|
||||
optionName: 'categoryDenylist',
|
||||
protoName: 'categoryDenylistList',
|
||||
customValue: ['bar'],
|
||||
defaultValue: []
|
||||
},
|
||||
];
|
||||
|
||||
for (const testCase of testCases) {
|
||||
it(`can set ${testCase.optionName}`, () => {
|
||||
const classifierOptionsProto = convertClassifierOptionsToProto(
|
||||
{[testCase.optionName]: testCase.customValue});
|
||||
verifyOption(
|
||||
classifierOptionsProto, {[testCase.protoName]: testCase.customValue});
|
||||
});
|
||||
|
||||
it(`can clear ${testCase.optionName}`, () => {
|
||||
let classifierOptionsProto = convertClassifierOptionsToProto(
|
||||
{[testCase.optionName]: testCase.customValue});
|
||||
verifyOption(
|
||||
classifierOptionsProto, {[testCase.protoName]: testCase.customValue});
|
||||
|
||||
classifierOptionsProto =
|
||||
convertClassifierOptionsToProto({[testCase.optionName]: undefined});
|
||||
verifyOption(
|
||||
classifierOptionsProto,
|
||||
{[testCase.protoName]: testCase.defaultValue});
|
||||
});
|
||||
}
|
||||
|
||||
it('overwrites options', () => {
|
||||
let classifierOptionsProto =
|
||||
convertClassifierOptionsToProto({maxResults: 1});
|
||||
verifyOption(classifierOptionsProto, {'maxResults': 1});
|
||||
|
||||
classifierOptionsProto = convertClassifierOptionsToProto(
|
||||
{maxResults: 2}, classifierOptionsProto);
|
||||
verifyOption(classifierOptionsProto, {'maxResults': 2});
|
||||
});
|
||||
|
||||
it('merges options', () => {
|
||||
let classifierOptionsProto =
|
||||
convertClassifierOptionsToProto({maxResults: 1});
|
||||
verifyOption(classifierOptionsProto, {'maxResults': 1});
|
||||
|
||||
classifierOptionsProto = convertClassifierOptionsToProto(
|
||||
{displayNamesLocale: 'en'}, classifierOptionsProto);
|
||||
verifyOption(
|
||||
classifierOptionsProto, {'maxResults': 1, 'displayNamesLocale': 'en'});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
import 'jasmine';
|
||||
|
||||
import {Classification, ClassificationList} from '../../../../framework/formats/classification_pb';
|
||||
import {ClassificationResult, Classifications} from '../../../../tasks/cc/components/containers/proto/classifications_pb';
|
||||
|
||||
import {convertFromClassificationResultProto} from './classifier_result';
|
||||
|
||||
// The OSS JS API does not support the builder pattern.
|
||||
// tslint:disable:jspb-use-builder-pattern
|
||||
|
||||
describe('convertFromClassificationResultProto()', () => {
|
||||
it('transforms custom values', () => {
|
||||
const classificationResult = new ClassificationResult();
|
||||
classificationResult.setTimestampMs(1);
|
||||
const classifcations = new Classifications();
|
||||
classifcations.setHeadIndex(1);
|
||||
classifcations.setHeadName('headName');
|
||||
const classificationList = new ClassificationList();
|
||||
const clasification = new Classification();
|
||||
clasification.setIndex(2);
|
||||
clasification.setScore(0.3);
|
||||
clasification.setDisplayName('displayName');
|
||||
clasification.setLabel('categoryName');
|
||||
classificationList.addClassification(clasification);
|
||||
classifcations.setClassificationList(classificationList);
|
||||
classificationResult.addClassifications(classifcations);
|
||||
|
||||
const result = convertFromClassificationResultProto(classificationResult);
|
||||
|
||||
expect(result).toEqual({
|
||||
classifications: [{
|
||||
categories: [{
|
||||
index: 2,
|
||||
score: 0.3,
|
||||
displayName: 'displayName',
|
||||
categoryName: 'categoryName'
|
||||
}],
|
||||
headIndex: 1,
|
||||
headName: 'headName'
|
||||
}],
|
||||
timestampMs: 1
|
||||
});
|
||||
});
|
||||
|
||||
it('transforms default values', () => {
|
||||
const classificationResult = new ClassificationResult();
|
||||
const classifcations = new Classifications();
|
||||
const classificationList = new ClassificationList();
|
||||
const clasification = new Classification();
|
||||
classificationList.addClassification(clasification);
|
||||
classifcations.setClassificationList(classificationList);
|
||||
classificationResult.addClassifications(classifcations);
|
||||
|
||||
const result = convertFromClassificationResultProto(classificationResult);
|
||||
|
||||
expect(result).toEqual({
|
||||
classifications: [{
|
||||
categories: [{index: 0, score: 0, displayName: '', categoryName: ''}],
|
||||
headIndex: 0,
|
||||
headName: ''
|
||||
}],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
import 'jasmine';
|
||||
|
||||
import {EmbedderOptions as EmbedderOptionsProto} from '../../../../tasks/cc/components/processors/proto/embedder_options_pb';
|
||||
import {EmbedderOptions} from '../../../../tasks/web/core/embedder_options';
|
||||
|
||||
import {convertEmbedderOptionsToProto} from './embedder_options';
|
||||
|
||||
interface TestCase {
|
||||
optionName: keyof EmbedderOptions;
|
||||
protoName: string;
|
||||
customValue: unknown;
|
||||
defaultValue: unknown;
|
||||
}
|
||||
|
||||
describe('convertEmbedderOptionsToProto()', () => {
|
||||
function verifyOption(
|
||||
actualEmbedderOptions: EmbedderOptionsProto,
|
||||
expectedEmbedderOptions: Record<string, unknown> = {}): void {
|
||||
expect(actualEmbedderOptions.toObject())
|
||||
.toEqual(jasmine.objectContaining(expectedEmbedderOptions));
|
||||
}
|
||||
|
||||
const testCases: TestCase[] = [
|
||||
{
|
||||
optionName: 'l2Normalize',
|
||||
protoName: 'l2Normalize',
|
||||
customValue: true,
|
||||
defaultValue: undefined
|
||||
},
|
||||
{
|
||||
optionName: 'quantize',
|
||||
protoName: 'quantize',
|
||||
customValue: true,
|
||||
defaultValue: undefined
|
||||
},
|
||||
];
|
||||
|
||||
for (const testCase of testCases) {
|
||||
it(`can set ${testCase.optionName}`, () => {
|
||||
const embedderOptionsProto = convertEmbedderOptionsToProto(
|
||||
{[testCase.optionName]: testCase.customValue});
|
||||
verifyOption(
|
||||
embedderOptionsProto, {[testCase.protoName]: testCase.customValue});
|
||||
});
|
||||
|
||||
it(`can clear ${testCase.optionName}`, () => {
|
||||
let embedderOptionsProto = convertEmbedderOptionsToProto(
|
||||
{[testCase.optionName]: testCase.customValue});
|
||||
verifyOption(
|
||||
embedderOptionsProto, {[testCase.protoName]: testCase.customValue});
|
||||
|
||||
embedderOptionsProto =
|
||||
convertEmbedderOptionsToProto({[testCase.optionName]: undefined});
|
||||
verifyOption(
|
||||
embedderOptionsProto, {[testCase.protoName]: testCase.defaultValue});
|
||||
});
|
||||
}
|
||||
|
||||
it('overwrites options', () => {
|
||||
let embedderOptionsProto =
|
||||
convertEmbedderOptionsToProto({l2Normalize: true});
|
||||
verifyOption(embedderOptionsProto, {'l2Normalize': true});
|
||||
|
||||
embedderOptionsProto = convertEmbedderOptionsToProto(
|
||||
{l2Normalize: false}, embedderOptionsProto);
|
||||
verifyOption(embedderOptionsProto, {'l2Normalize': false});
|
||||
});
|
||||
|
||||
it('replaces options', () => {
|
||||
let embedderOptionsProto = convertEmbedderOptionsToProto({quantize: true});
|
||||
verifyOption(embedderOptionsProto, {'quantize': true});
|
||||
|
||||
embedderOptionsProto = convertEmbedderOptionsToProto(
|
||||
{l2Normalize: true}, embedderOptionsProto);
|
||||
verifyOption(embedderOptionsProto, {'l2Normalize': true, 'quantize': true});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
import 'jasmine';
|
||||
|
||||
import {Embedding, EmbeddingResult, FloatEmbedding, QuantizedEmbedding} from '../../../../tasks/cc/components/containers/proto/embeddings_pb';
|
||||
|
||||
import {convertFromEmbeddingResultProto} from './embedder_result';
|
||||
|
||||
// The OSS JS API does not support the builder pattern.
|
||||
// tslint:disable:jspb-use-builder-pattern
|
||||
|
||||
describe('convertFromEmbeddingResultProto()', () => {
|
||||
it('transforms custom values', () => {
|
||||
const embedding = new Embedding();
|
||||
embedding.setHeadIndex(1);
|
||||
embedding.setHeadName('headName');
|
||||
|
||||
const floatEmbedding = new FloatEmbedding();
|
||||
floatEmbedding.setValuesList([0.1, 0.9]);
|
||||
|
||||
embedding.setFloatEmbedding(floatEmbedding);
|
||||
const resultProto = new EmbeddingResult();
|
||||
resultProto.addEmbeddings(embedding);
|
||||
resultProto.setTimestampMs(1);
|
||||
|
||||
const embedderResult = convertFromEmbeddingResultProto(resultProto);
|
||||
const embeddings = embedderResult.embeddings;
|
||||
const timestampMs = embedderResult.timestampMs;
|
||||
expect(embeddings.length).toEqual(1);
|
||||
expect(embeddings[0])
|
||||
.toEqual(
|
||||
{floatEmbedding: [0.1, 0.9], headIndex: 1, headName: 'headName'});
|
||||
expect(timestampMs).toEqual(1);
|
||||
});
|
||||
|
||||
it('transforms custom quantized values', () => {
|
||||
const embedding = new Embedding();
|
||||
embedding.setHeadIndex(1);
|
||||
embedding.setHeadName('headName');
|
||||
|
||||
const quantizedEmbedding = new QuantizedEmbedding();
|
||||
const quantizedValues = new Uint8Array([1, 2, 3]);
|
||||
quantizedEmbedding.setValues(quantizedValues);
|
||||
|
||||
embedding.setQuantizedEmbedding(quantizedEmbedding);
|
||||
const resultProto = new EmbeddingResult();
|
||||
resultProto.addEmbeddings(embedding);
|
||||
resultProto.setTimestampMs(1);
|
||||
|
||||
const embedderResult = convertFromEmbeddingResultProto(resultProto);
|
||||
const embeddings = embedderResult.embeddings;
|
||||
const timestampMs = embedderResult.timestampMs;
|
||||
expect(embeddings.length).toEqual(1);
|
||||
expect(embeddings[0]).toEqual({
|
||||
quantizedEmbedding: new Uint8Array([1, 2, 3]),
|
||||
headIndex: 1,
|
||||
headName: 'headName'
|
||||
});
|
||||
expect(timestampMs).toEqual(1);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_ts_library")
|
||||
load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test")
|
||||
|
||||
package(default_visibility = ["//mediapipe/tasks:internal"])
|
||||
|
||||
@@ -9,3 +10,18 @@ mediapipe_ts_library(
|
||||
"//mediapipe/tasks/web/components/containers:embedding_result",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_ts_library(
|
||||
name = "cosine_similarity_test_lib",
|
||||
testonly = True,
|
||||
srcs = ["cosine_similarity.test.ts"],
|
||||
deps = [
|
||||
":cosine_similarity",
|
||||
"//mediapipe/tasks/web/components/containers:embedding_result",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "cosine_similarity_test",
|
||||
deps = [":cosine_similarity_test_lib"],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright 2022 The MediaPipe Authors. All Rights Reserved.
|
||||
*
|
||||
* <p>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
|
||||
*
|
||||
* <p>http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* <p>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 {Embedding} from '../../../../tasks/web/components/containers/embedding_result';
|
||||
|
||||
import {computeCosineSimilarity} from './cosine_similarity';
|
||||
|
||||
describe('computeCosineSimilarity', () => {
|
||||
it('fails with quantized and float embeddings', () => {
|
||||
const u: Embedding = {floatEmbedding: [1.0], headIndex: 0, headName: ''};
|
||||
const v: Embedding = {
|
||||
quantizedEmbedding: new Uint8Array([1.0]),
|
||||
headIndex: 0,
|
||||
headName: ''
|
||||
};
|
||||
|
||||
expect(() => computeCosineSimilarity(u, v))
|
||||
.toThrowError(
|
||||
/Cannot compute cosine similarity between quantized and float embeddings/);
|
||||
});
|
||||
|
||||
it('fails with zero norm', () => {
|
||||
const u = {floatEmbedding: [0.0], headIndex: 0, headName: ''};
|
||||
expect(() => computeCosineSimilarity(u, u))
|
||||
.toThrowError(
|
||||
/Cannot compute cosine similarity on embedding with 0 norm/);
|
||||
});
|
||||
|
||||
it('fails with different sizes', () => {
|
||||
const u:
|
||||
Embedding = {floatEmbedding: [1.0, 2.0], headIndex: 0, headName: ''};
|
||||
const v: Embedding = {
|
||||
floatEmbedding: [1.0, 2.0, 3.0],
|
||||
headIndex: 0,
|
||||
headName: ''
|
||||
};
|
||||
|
||||
expect(() => computeCosineSimilarity(u, v))
|
||||
.toThrowError(
|
||||
/Cannot compute cosine similarity between embeddings of different sizes/);
|
||||
});
|
||||
|
||||
it('succeeds with float embeddings', () => {
|
||||
const u: Embedding = {
|
||||
floatEmbedding: [1.0, 0.0, 0.0, 0.0],
|
||||
headIndex: 0,
|
||||
headName: ''
|
||||
};
|
||||
const v: Embedding = {
|
||||
floatEmbedding: [0.5, 0.5, 0.5, 0.5],
|
||||
headIndex: 0,
|
||||
headName: ''
|
||||
};
|
||||
|
||||
expect(computeCosineSimilarity(u, v)).toEqual(0.5);
|
||||
});
|
||||
|
||||
it('succeeds with quantized embeddings', () => {
|
||||
const u: Embedding = {
|
||||
quantizedEmbedding: new Uint8Array([255, 128, 128, 128]),
|
||||
headIndex: 0,
|
||||
headName: ''
|
||||
};
|
||||
const v: Embedding = {
|
||||
quantizedEmbedding: new Uint8Array([0, 128, 128, 128]),
|
||||
headIndex: 0,
|
||||
headName: ''
|
||||
};
|
||||
|
||||
expect(computeCosineSimilarity(u, v)).toEqual(-1.0);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
# This package contains options shared by all MediaPipe Tasks for Web.
|
||||
|
||||
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_ts_declaration", "mediapipe_ts_library")
|
||||
load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test")
|
||||
|
||||
package(default_visibility = ["//mediapipe/tasks:internal"])
|
||||
|
||||
@@ -32,6 +33,38 @@ mediapipe_ts_library(
|
||||
deps = [":core"],
|
||||
)
|
||||
|
||||
mediapipe_ts_library(
|
||||
name = "task_runner_test_utils",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"task_runner_test_utils.ts",
|
||||
],
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_jspb_proto",
|
||||
"//mediapipe/web/graph_runner:graph_runner_ts",
|
||||
"//mediapipe/web/graph_runner:register_model_resources_graph_service_ts",
|
||||
],
|
||||
)
|
||||
|
||||
mediapipe_ts_library(
|
||||
name = "task_runner_test_lib",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"task_runner_test.ts",
|
||||
],
|
||||
deps = [
|
||||
":task_runner",
|
||||
":task_runner_test_utils",
|
||||
"//mediapipe/tasks/cc/core/proto:base_options_jspb_proto",
|
||||
"//mediapipe/web/graph_runner:graph_runner_ts",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "task_runner_test",
|
||||
deps = [":task_runner_test_lib"],
|
||||
)
|
||||
|
||||
mediapipe_ts_declaration(
|
||||
name = "classifier_options",
|
||||
srcs = ["classifier_options.d.ts"],
|
||||
|
||||
@@ -77,9 +77,10 @@ export abstract class TaskRunner {
|
||||
}
|
||||
|
||||
constructor(
|
||||
wasmModule: WasmModule,
|
||||
glCanvas?: HTMLCanvasElement|OffscreenCanvas|null) {
|
||||
this.graphRunner = new GraphRunnerImageLib(wasmModule, glCanvas);
|
||||
wasmModule: WasmModule, glCanvas?: HTMLCanvasElement|OffscreenCanvas|null,
|
||||
graphRunner?: GraphRunnerImageLib) {
|
||||
this.graphRunner =
|
||||
graphRunner ?? new GraphRunnerImageLib(wasmModule, glCanvas);
|
||||
|
||||
// Disables the automatic render-to-screen code, which allows for pure
|
||||
// CPU processing.
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
import 'jasmine';
|
||||
|
||||
import {BaseOptions as BaseOptionsProto} from '../../../tasks/cc/core/proto/base_options_pb';
|
||||
import {TaskRunner} from '../../../tasks/web/core/task_runner';
|
||||
import {createSpyWasmModule, SpyWasmModule} from '../../../tasks/web/core/task_runner_test_utils';
|
||||
import {ErrorListener} from '../../../web/graph_runner/graph_runner';
|
||||
|
||||
import {GraphRunnerImageLib} from './task_runner';
|
||||
|
||||
class TaskRunnerFake extends TaskRunner {
|
||||
protected baseOptions = new BaseOptionsProto();
|
||||
private errorListener: ErrorListener|undefined;
|
||||
private errors: string[] = [];
|
||||
|
||||
static createFake(): TaskRunnerFake {
|
||||
const wasmModule = createSpyWasmModule();
|
||||
return new TaskRunnerFake(wasmModule);
|
||||
}
|
||||
|
||||
constructor(wasmModuleFake: SpyWasmModule) {
|
||||
super(
|
||||
wasmModuleFake, /* glCanvas= */ null,
|
||||
jasmine.createSpyObj<GraphRunnerImageLib>([
|
||||
'setAutoRenderToScreen', 'setGraph', 'finishProcessing',
|
||||
'registerModelResourcesGraphService', 'attachErrorListener'
|
||||
]));
|
||||
const graphRunner = this.graphRunner as jasmine.SpyObj<GraphRunnerImageLib>;
|
||||
expect(graphRunner.registerModelResourcesGraphService).toHaveBeenCalled();
|
||||
expect(graphRunner.setAutoRenderToScreen).toHaveBeenCalled();
|
||||
graphRunner.attachErrorListener.and.callFake(listener => {
|
||||
this.errorListener = listener;
|
||||
});
|
||||
graphRunner.setGraph.and.callFake(() => {
|
||||
this.throwErrors();
|
||||
});
|
||||
graphRunner.finishProcessing.and.callFake(() => {
|
||||
this.throwErrors();
|
||||
});
|
||||
}
|
||||
|
||||
enqueueError(message: string): void {
|
||||
this.errors.push(message);
|
||||
}
|
||||
|
||||
override finishProcessing(): void {
|
||||
super.finishProcessing();
|
||||
}
|
||||
|
||||
override setGraph(graphData: Uint8Array, isBinary: boolean): void {
|
||||
super.setGraph(graphData, isBinary);
|
||||
}
|
||||
|
||||
private throwErrors(): void {
|
||||
expect(this.errorListener).toBeDefined();
|
||||
for (const error of this.errors) {
|
||||
this.errorListener!(/* errorCode= */ -1, error);
|
||||
}
|
||||
this.errors = [];
|
||||
}
|
||||
}
|
||||
|
||||
describe('TaskRunner', () => {
|
||||
it('handles errors during graph update', () => {
|
||||
const taskRunner = TaskRunnerFake.createFake();
|
||||
taskRunner.enqueueError('Test error');
|
||||
|
||||
expect(() => {
|
||||
taskRunner.setGraph(new Uint8Array(0), /* isBinary= */ true);
|
||||
}).toThrowError('Test error');
|
||||
});
|
||||
|
||||
it('handles errors during graph execution', () => {
|
||||
const taskRunner = TaskRunnerFake.createFake();
|
||||
taskRunner.setGraph(new Uint8Array(0), /* isBinary= */ true);
|
||||
|
||||
taskRunner.enqueueError('Test error');
|
||||
|
||||
expect(() => {
|
||||
taskRunner.finishProcessing();
|
||||
}).toThrowError('Test error');
|
||||
});
|
||||
|
||||
it('can handle multiple errors', () => {
|
||||
const taskRunner = TaskRunnerFake.createFake();
|
||||
taskRunner.enqueueError('Test error 1');
|
||||
taskRunner.enqueueError('Test error 2');
|
||||
|
||||
expect(() => {
|
||||
taskRunner.setGraph(new Uint8Array(0), /* isBinary= */ true);
|
||||
}).toThrowError(/Test error 1, Test error 2/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
import 'jasmine';
|
||||
|
||||
import {CalculatorGraphConfig} from '../../../framework/calculator_pb';
|
||||
import {WasmModule} from '../../../web/graph_runner/graph_runner';
|
||||
import {WasmModuleRegisterModelResources} from '../../../web/graph_runner/register_model_resources_graph_service';
|
||||
|
||||
type SpyWasmModuleInternal = WasmModule&WasmModuleRegisterModelResources;
|
||||
|
||||
/**
|
||||
* Convenience type for our fake WasmModule for Jasmine testing.
|
||||
*/
|
||||
export declare type SpyWasmModule = jasmine.SpyObj<SpyWasmModuleInternal>;
|
||||
|
||||
/**
|
||||
* Factory function for creating a fake WasmModule for our Jasmine tests,
|
||||
* allowing our APIs to no longer rely on the Wasm layer so they can run tests
|
||||
* in pure JS/TS (and optionally spy on the calls).
|
||||
*/
|
||||
export function createSpyWasmModule(): SpyWasmModule {
|
||||
return jasmine.createSpyObj<SpyWasmModuleInternal>([
|
||||
'_setAutoRenderToScreen', 'stringToNewUTF8', '_attachProtoListener',
|
||||
'_attachProtoVectorListener', '_free', '_waitUntilIdle',
|
||||
'_addStringToInputStream', '_registerModelResourcesGraphService',
|
||||
'_configureAudio'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up our equality testing to use a custom float equality checking function
|
||||
* to avoid incorrect test results due to minor floating point inaccuracies.
|
||||
*/
|
||||
export function addJasmineCustomFloatEqualityTester() {
|
||||
jasmine.addCustomEqualityTester((a, b) => { // Custom float equality
|
||||
if (a === +a && b === +b && (a !== (a | 0) || b !== (b | 0))) {
|
||||
return Math.abs(a - b) < 5e-8;
|
||||
}
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
/** The minimum interface provided by a test fake. */
|
||||
export interface MediapipeTasksFake {
|
||||
graph: CalculatorGraphConfig|undefined;
|
||||
calculatorName: string;
|
||||
attachListenerSpies: jasmine.Spy[];
|
||||
}
|
||||
|
||||
/** An map of field paths to values */
|
||||
export type FieldPathToValue = [string[] | string, unknown];
|
||||
|
||||
/**
|
||||
* Verifies that the graph has been initialized and that it contains the
|
||||
* provided options.
|
||||
*/
|
||||
export function verifyGraph(
|
||||
tasksFake: MediapipeTasksFake,
|
||||
expectedCalculatorOptions?: FieldPathToValue,
|
||||
expectedBaseOptions?: FieldPathToValue,
|
||||
): void {
|
||||
expect(tasksFake.graph).toBeDefined();
|
||||
expect(tasksFake.graph!.getNodeList().length).toBe(1);
|
||||
const node = tasksFake.graph!.getNodeList()[0].toObject();
|
||||
expect(node).toEqual(
|
||||
jasmine.objectContaining({calculator: tasksFake.calculatorName}));
|
||||
|
||||
if (expectedBaseOptions) {
|
||||
const [fieldPath, value] = expectedBaseOptions;
|
||||
let proto = (node.options as {ext: {baseOptions: unknown}}).ext.baseOptions;
|
||||
for (const fieldName of (
|
||||
Array.isArray(fieldPath) ? fieldPath : [fieldPath])) {
|
||||
proto = ((proto ?? {}) as Record<string, unknown>)[fieldName];
|
||||
}
|
||||
expect(proto).toEqual(value);
|
||||
}
|
||||
|
||||
if (expectedCalculatorOptions) {
|
||||
const [fieldPath, value] = expectedCalculatorOptions;
|
||||
let proto = (node.options as {ext: unknown}).ext;
|
||||
for (const fieldName of (
|
||||
Array.isArray(fieldPath) ? fieldPath : [fieldPath])) {
|
||||
proto = ((proto ?? {}) as Record<string, unknown>)[fieldName];
|
||||
}
|
||||
expect(proto).toEqual(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies all listeners (as exposed by `.attachListenerSpies`) have been
|
||||
* attached at least once since the last call to `verifyListenersRegistered()`.
|
||||
* This helps us to ensure that listeners are re-registered with every graph
|
||||
* update.
|
||||
*/
|
||||
export function verifyListenersRegistered(tasksFake: MediapipeTasksFake): void {
|
||||
for (const spy of tasksFake.attachListenerSpies) {
|
||||
expect(spy.calls.count()).toBeGreaterThanOrEqual(1);
|
||||
spy.calls.reset();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user