Invoke the FaceStylizer callback even if no faces are detected

PiperOrigin-RevId: 527008261
This commit is contained in:
Sebastian Schmidt 2023-04-25 10:32:49 -07:00 committed by Copybara-Service
parent 3bc8276678
commit 9e30b00685
3 changed files with 45 additions and 21 deletions

View File

@ -25,16 +25,6 @@ import {NormalizedKeypoint} from '../../../../tasks/web/components/containers/ke
*/ */
export type SegmentationMask = Uint8ClampedArray|Float32Array|WebGLTexture; export type SegmentationMask = Uint8ClampedArray|Float32Array|WebGLTexture;
/**
* A callback that receives an `ImageData` object from a Vision task. The
* lifetime of the underlying data is limited to the duration of the callback.
* If asynchronous processing is needed, all data needs to be copied before the
* callback returns.
*
* The `WebGLTexture` output type is reserved for future usage.
*/
export type ImageCallback =
(image: ImageData|WebGLTexture, width: number, height: number) => void;
/** A Region-Of-Interest (ROI) to represent a region within an image. */ /** A Region-Of-Interest (ROI) to represent a region within an image. */
export declare interface RegionOfInterest { export declare interface RegionOfInterest {

View File

@ -20,7 +20,6 @@ import {BaseOptions as BaseOptionsProto} from '../../../../tasks/cc/core/proto/b
import {FaceStylizerGraphOptions as FaceStylizerGraphOptionsProto} from '../../../../tasks/cc/vision/face_stylizer/proto/face_stylizer_graph_options_pb'; import {FaceStylizerGraphOptions as FaceStylizerGraphOptionsProto} from '../../../../tasks/cc/vision/face_stylizer/proto/face_stylizer_graph_options_pb';
import {WasmFileset} from '../../../../tasks/web/core/wasm_fileset'; import {WasmFileset} from '../../../../tasks/web/core/wasm_fileset';
import {ImageProcessingOptions} from '../../../../tasks/web/vision/core/image_processing_options'; import {ImageProcessingOptions} from '../../../../tasks/web/vision/core/image_processing_options';
import {ImageCallback} from '../../../../tasks/web/vision/core/types';
import {VisionGraphRunner, VisionTaskRunner} from '../../../../tasks/web/vision/core/vision_task_runner'; import {VisionGraphRunner, VisionTaskRunner} from '../../../../tasks/web/vision/core/vision_task_runner';
import {ImageSource, WasmModule} from '../../../../web/graph_runner/graph_runner'; import {ImageSource, WasmModule} from '../../../../web/graph_runner/graph_runner';
// Placeholder for internal dependency on trusted resource url // Placeholder for internal dependency on trusted resource url
@ -39,11 +38,20 @@ const FACE_STYLIZER_GRAPH =
// The OSS JS API does not support the builder pattern. // The OSS JS API does not support the builder pattern.
// tslint:disable:jspb-use-builder-pattern // tslint:disable:jspb-use-builder-pattern
export {ImageCallback}; /**
* A callback that receives an image from the face stylizer, or `null` if no
* face was detected. The lifetime of the underlying data is limited to the
* duration of the callback. If asynchronous processing is needed, all data
* needs to be copied before the callback returns.
*
* The `WebGLTexture` output type is reserved for future usage.
*/
export type FaceStylizerCallback =
(image: ImageData|WebGLTexture|null, width: number, height: number) => void;
/** Performs face stylization on images. */ /** Performs face stylization on images. */
export class FaceStylizer extends VisionTaskRunner { export class FaceStylizer extends VisionTaskRunner {
private userCallback: ImageCallback = () => {}; private userCallback: FaceStylizerCallback = () => {};
private readonly options: FaceStylizerGraphOptionsProto; private readonly options: FaceStylizerGraphOptionsProto;
/** /**
@ -134,7 +142,7 @@ export class FaceStylizer extends VisionTaskRunner {
* lifetime of the returned data is only guaranteed for the duration of the * lifetime of the returned data is only guaranteed for the duration of the
* callback. * callback.
*/ */
stylize(image: ImageSource, callback: ImageCallback): void; stylize(image: ImageSource, callback: FaceStylizerCallback): void;
/** /**
* Performs face stylization on the provided single image. The method returns * Performs face stylization on the provided single image. The method returns
* synchronously once the callback returns. Only use this method when the * synchronously once the callback returns. Only use this method when the
@ -158,11 +166,12 @@ export class FaceStylizer extends VisionTaskRunner {
*/ */
stylize( stylize(
image: ImageSource, imageProcessingOptions: ImageProcessingOptions, image: ImageSource, imageProcessingOptions: ImageProcessingOptions,
callback: ImageCallback): void; callback: FaceStylizerCallback): void;
stylize( stylize(
image: ImageSource, image: ImageSource,
imageProcessingOptionsOrCallback: ImageProcessingOptions|ImageCallback, imageProcessingOptionsOrCallback: ImageProcessingOptions|
callback?: ImageCallback): void { FaceStylizerCallback,
callback?: FaceStylizerCallback): void {
const imageProcessingOptions = const imageProcessingOptions =
typeof imageProcessingOptionsOrCallback !== 'function' ? typeof imageProcessingOptionsOrCallback !== 'function' ?
imageProcessingOptionsOrCallback : imageProcessingOptionsOrCallback :
@ -191,7 +200,7 @@ export class FaceStylizer extends VisionTaskRunner {
*/ */
stylizeForVideo( stylizeForVideo(
videoFrame: ImageSource, timestamp: number, videoFrame: ImageSource, timestamp: number,
callback: ImageCallback): void; callback: FaceStylizerCallback): void;
/** /**
* Performs face stylization on the provided video frame. Only use this * Performs face stylization on the provided video frame. Only use this
* method when the FaceStylizer is created with the video running mode. * method when the FaceStylizer is created with the video running mode.
@ -219,12 +228,12 @@ export class FaceStylizer extends VisionTaskRunner {
*/ */
stylizeForVideo( stylizeForVideo(
videoFrame: ImageSource, imageProcessingOptions: ImageProcessingOptions, videoFrame: ImageSource, imageProcessingOptions: ImageProcessingOptions,
timestamp: number, callback: ImageCallback): void; timestamp: number, callback: FaceStylizerCallback): void;
stylizeForVideo( stylizeForVideo(
videoFrame: ImageSource, videoFrame: ImageSource,
timestampOrImageProcessingOptions: number|ImageProcessingOptions, timestampOrImageProcessingOptions: number|ImageProcessingOptions,
timestampOrCallback: number|ImageCallback, timestampOrCallback: number|FaceStylizerCallback,
callback?: ImageCallback): void { callback?: FaceStylizerCallback): void {
const imageProcessingOptions = const imageProcessingOptions =
typeof timestampOrImageProcessingOptions !== 'number' ? typeof timestampOrImageProcessingOptions !== 'number' ?
timestampOrImageProcessingOptions : timestampOrImageProcessingOptions :
@ -272,6 +281,7 @@ export class FaceStylizer extends VisionTaskRunner {
}); });
this.graphRunner.attachEmptyPacketListener( this.graphRunner.attachEmptyPacketListener(
STYLIZED_IMAGE_STREAM, timestamp => { STYLIZED_IMAGE_STREAM, timestamp => {
this.userCallback(null, /* width= */ 0, /* height= */ 0);
this.setLatestOutputTimestamp(timestamp); this.setLatestOutputTimestamp(timestamp);
}); });

View File

@ -30,6 +30,7 @@ class FaceStylizerFake extends FaceStylizer implements MediapipeTasksFake {
fakeWasmModule: SpyWasmModule; fakeWasmModule: SpyWasmModule;
imageListener: ((images: WasmImage, timestamp: number) => void)|undefined; imageListener: ((images: WasmImage, timestamp: number) => void)|undefined;
emptyPacketListener: ((timestamp: number) => void)|undefined;
constructor() { constructor() {
super(createSpyWasmModule(), /* glCanvas= */ null); super(createSpyWasmModule(), /* glCanvas= */ null);
@ -42,6 +43,12 @@ class FaceStylizerFake extends FaceStylizer implements MediapipeTasksFake {
expect(stream).toEqual('stylized_image'); expect(stream).toEqual('stylized_image');
this.imageListener = listener; this.imageListener = listener;
}); });
this.attachListenerSpies[1] =
spyOn(this.graphRunner, 'attachEmptyPacketListener')
.and.callFake((stream, listener) => {
expect(stream).toEqual('stylized_image');
this.emptyPacketListener = listener;
});
spyOn(this.graphRunner, 'setGraph').and.callFake(binaryGraph => { spyOn(this.graphRunner, 'setGraph').and.callFake(binaryGraph => {
this.graph = CalculatorGraphConfig.deserializeBinary(binaryGraph); this.graph = CalculatorGraphConfig.deserializeBinary(binaryGraph);
}); });
@ -111,4 +118,21 @@ describe('FaceStylizer', () => {
done(); done();
}); });
}); });
it('invokes callback even when no faes are detected', (done) => {
// Pass the test data to our listener
faceStylizer.fakeWasmModule._waitUntilIdle.and.callFake(() => {
verifyListenersRegistered(faceStylizer);
faceStylizer.emptyPacketListener!(/* timestamp= */ 1337);
});
// Invoke the face stylizeer
faceStylizer.stylize({} as HTMLImageElement, (image, width, height) => {
expect(faceStylizer.fakeWasmModule._waitUntilIdle).toHaveBeenCalled();
expect(image).toBeNull();
expect(width).toEqual(0);
expect(height).toEqual(0);
done();
});
});
}); });