Add .close() method to ImageSegmenterResult/InteractiveSegmenterResult/PoseLandmarkerResult
PiperOrigin-RevId: 530973944
This commit is contained in:
parent
a7ede9235c
commit
1666f3ed80
|
@ -22,6 +22,7 @@ import {ImageSegmenterGraphOptions as ImageSegmenterGraphOptionsProto} from '../
|
||||||
import {SegmenterOptions as SegmenterOptionsProto} from '../../../../tasks/cc/vision/image_segmenter/proto/segmenter_options_pb';
|
import {SegmenterOptions as SegmenterOptionsProto} from '../../../../tasks/cc/vision/image_segmenter/proto/segmenter_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 {MPMask} from '../../../../tasks/web/vision/core/mask';
|
||||||
import {VisionGraphRunner, VisionTaskRunner} from '../../../../tasks/web/vision/core/vision_task_runner';
|
import {VisionGraphRunner, VisionTaskRunner} from '../../../../tasks/web/vision/core/vision_task_runner';
|
||||||
import {LabelMapItem} from '../../../../util/label_map_pb';
|
import {LabelMapItem} from '../../../../util/label_map_pb';
|
||||||
import {ImageSource, WasmModule} from '../../../../web/graph_runner/graph_runner';
|
import {ImageSource, WasmModule} from '../../../../web/graph_runner/graph_runner';
|
||||||
|
@ -58,7 +59,8 @@ export type ImageSegmenterCallback = (result: ImageSegmenterResult) => void;
|
||||||
|
|
||||||
/** Performs image segmentation on images. */
|
/** Performs image segmentation on images. */
|
||||||
export class ImageSegmenter extends VisionTaskRunner {
|
export class ImageSegmenter extends VisionTaskRunner {
|
||||||
private result: ImageSegmenterResult = {};
|
private categoryMask?: MPMask;
|
||||||
|
private confidenceMasks?: MPMask[];
|
||||||
private labels: string[] = [];
|
private labels: string[] = [];
|
||||||
private userCallback?: ImageSegmenterCallback;
|
private userCallback?: ImageSegmenterCallback;
|
||||||
private outputCategoryMask = DEFAULT_OUTPUT_CATEGORY_MASK;
|
private outputCategoryMask = DEFAULT_OUTPUT_CATEGORY_MASK;
|
||||||
|
@ -265,10 +267,7 @@ export class ImageSegmenter extends VisionTaskRunner {
|
||||||
|
|
||||||
this.reset();
|
this.reset();
|
||||||
this.processImageData(image, imageProcessingOptions);
|
this.processImageData(image, imageProcessingOptions);
|
||||||
|
return this.processResults();
|
||||||
if (!this.userCallback) {
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -347,10 +346,7 @@ export class ImageSegmenter extends VisionTaskRunner {
|
||||||
|
|
||||||
this.reset();
|
this.reset();
|
||||||
this.processVideoData(videoFrame, imageProcessingOptions, timestamp);
|
this.processVideoData(videoFrame, imageProcessingOptions, timestamp);
|
||||||
|
return this.processResults();
|
||||||
if (!this.userCallback) {
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -369,21 +365,20 @@ export class ImageSegmenter extends VisionTaskRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
private reset(): void {
|
private reset(): void {
|
||||||
this.result = {};
|
this.categoryMask = undefined;
|
||||||
|
this.confidenceMasks = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Invokes the user callback once all data has been received. */
|
private processResults(): ImageSegmenterResult|void {
|
||||||
private maybeInvokeCallback(): void {
|
try {
|
||||||
if (this.outputConfidenceMasks && !('confidenceMasks' in this.result)) {
|
const result =
|
||||||
return;
|
new ImageSegmenterResult(this.confidenceMasks, this.categoryMask);
|
||||||
}
|
if (this.userCallback) {
|
||||||
if (this.outputCategoryMask && !('categoryMask' in this.result)) {
|
this.userCallback(result);
|
||||||
return;
|
} else {
|
||||||
}
|
return result;
|
||||||
|
}
|
||||||
if (this.userCallback) {
|
} finally {
|
||||||
this.userCallback(this.result);
|
|
||||||
|
|
||||||
// Free the image memory, now that we've kept all streams alive long
|
// Free the image memory, now that we've kept all streams alive long
|
||||||
// enough to be returned in our callbacks.
|
// enough to be returned in our callbacks.
|
||||||
this.freeKeepaliveStreams();
|
this.freeKeepaliveStreams();
|
||||||
|
@ -417,17 +412,15 @@ export class ImageSegmenter extends VisionTaskRunner {
|
||||||
|
|
||||||
this.graphRunner.attachImageVectorListener(
|
this.graphRunner.attachImageVectorListener(
|
||||||
CONFIDENCE_MASKS_STREAM, (masks, timestamp) => {
|
CONFIDENCE_MASKS_STREAM, (masks, timestamp) => {
|
||||||
this.result.confidenceMasks = masks.map(
|
this.confidenceMasks = masks.map(
|
||||||
wasmImage => this.convertToMPMask(
|
wasmImage => this.convertToMPMask(
|
||||||
wasmImage, /* shouldCopyData= */ !this.userCallback));
|
wasmImage, /* shouldCopyData= */ !this.userCallback));
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
this.graphRunner.attachEmptyPacketListener(
|
this.graphRunner.attachEmptyPacketListener(
|
||||||
CONFIDENCE_MASKS_STREAM, timestamp => {
|
CONFIDENCE_MASKS_STREAM, timestamp => {
|
||||||
this.result.confidenceMasks = undefined;
|
this.confidenceMasks = [];
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,16 +431,14 @@ export class ImageSegmenter extends VisionTaskRunner {
|
||||||
|
|
||||||
this.graphRunner.attachImageListener(
|
this.graphRunner.attachImageListener(
|
||||||
CATEGORY_MASK_STREAM, (mask, timestamp) => {
|
CATEGORY_MASK_STREAM, (mask, timestamp) => {
|
||||||
this.result.categoryMask = this.convertToMPMask(
|
this.categoryMask = this.convertToMPMask(
|
||||||
mask, /* shouldCopyData= */ !this.userCallback);
|
mask, /* shouldCopyData= */ !this.userCallback);
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
this.graphRunner.attachEmptyPacketListener(
|
this.graphRunner.attachEmptyPacketListener(
|
||||||
CATEGORY_MASK_STREAM, timestamp => {
|
CATEGORY_MASK_STREAM, timestamp => {
|
||||||
this.result.categoryMask = undefined;
|
this.categoryMask = undefined;
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,18 +17,26 @@
|
||||||
import {MPMask} from '../../../../tasks/web/vision/core/mask';
|
import {MPMask} from '../../../../tasks/web/vision/core/mask';
|
||||||
|
|
||||||
/** The output result of ImageSegmenter. */
|
/** The output result of ImageSegmenter. */
|
||||||
export declare interface ImageSegmenterResult {
|
export class ImageSegmenterResult {
|
||||||
/**
|
constructor(
|
||||||
* Multiple masks represented as `Float32Array` or `WebGLTexture`-backed
|
/**
|
||||||
* `MPImage`s where, for each mask, each pixel represents the prediction
|
* Multiple masks represented as `Float32Array` or `WebGLTexture`-backed
|
||||||
* confidence, usually in the [0, 1] range.
|
* `MPImage`s where, for each mask, each pixel represents the prediction
|
||||||
*/
|
* confidence, usually in the [0, 1] range.
|
||||||
confidenceMasks?: MPMask[];
|
*/
|
||||||
|
readonly confidenceMasks?: MPMask[],
|
||||||
|
/**
|
||||||
|
* A category mask represented as a `Uint8ClampedArray` or
|
||||||
|
* `WebGLTexture`-backed `MPImage` where each pixel represents the class
|
||||||
|
* which the pixel in the original image was predicted to belong to.
|
||||||
|
*/
|
||||||
|
readonly categoryMask?: MPMask) {}
|
||||||
|
|
||||||
/**
|
/** Frees the resources held by the category and confidence masks. */
|
||||||
* A category mask represented as a `Uint8ClampedArray` or
|
close(): void {
|
||||||
* `WebGLTexture`-backed `MPImage` where each pixel represents the class which
|
this.confidenceMasks?.forEach(m => {
|
||||||
* the pixel in the original image was predicted to belong to.
|
m.close();
|
||||||
*/
|
});
|
||||||
categoryMask?: MPMask;
|
this.categoryMask?.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -263,7 +263,7 @@ describe('ImageSegmenter', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invokes listener once masks are available', async () => {
|
it('invokes listener after masks are available', async () => {
|
||||||
const categoryMask = new Uint8Array([1]);
|
const categoryMask = new Uint8Array([1]);
|
||||||
const confidenceMask = new Float32Array([0.0]);
|
const confidenceMask = new Float32Array([0.0]);
|
||||||
let listenerCalled = false;
|
let listenerCalled = false;
|
||||||
|
@ -282,7 +282,7 @@ describe('ImageSegmenter', () => {
|
||||||
{data: confidenceMask, width: 1, height: 1},
|
{data: confidenceMask, width: 1, height: 1},
|
||||||
],
|
],
|
||||||
1337);
|
1337);
|
||||||
expect(listenerCalled).toBeTrue();
|
expect(listenerCalled).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<void>(resolve => {
|
return new Promise<void>(resolve => {
|
||||||
|
@ -307,6 +307,6 @@ describe('ImageSegmenter', () => {
|
||||||
|
|
||||||
const result = imageSegmenter.segment({} as HTMLImageElement);
|
const result = imageSegmenter.segment({} as HTMLImageElement);
|
||||||
expect(result.confidenceMasks![0]).toBeInstanceOf(MPMask);
|
expect(result.confidenceMasks![0]).toBeInstanceOf(MPMask);
|
||||||
result.confidenceMasks![0].close();
|
result.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {ImageSegmenterGraphOptions as ImageSegmenterGraphOptionsProto} from '../
|
||||||
import {SegmenterOptions as SegmenterOptionsProto} from '../../../../tasks/cc/vision/image_segmenter/proto/segmenter_options_pb';
|
import {SegmenterOptions as SegmenterOptionsProto} from '../../../../tasks/cc/vision/image_segmenter/proto/segmenter_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 {MPMask} from '../../../../tasks/web/vision/core/mask';
|
||||||
import {RegionOfInterest} from '../../../../tasks/web/vision/core/types';
|
import {RegionOfInterest} 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 {Color as ColorProto} from '../../../../util/color_pb';
|
import {Color as ColorProto} from '../../../../util/color_pb';
|
||||||
|
@ -83,7 +84,8 @@ export type InteractiveSegmenterCallback =
|
||||||
* - batch is always 1
|
* - batch is always 1
|
||||||
*/
|
*/
|
||||||
export class InteractiveSegmenter extends VisionTaskRunner {
|
export class InteractiveSegmenter extends VisionTaskRunner {
|
||||||
private result: InteractiveSegmenterResult = {};
|
private categoryMask?: MPMask;
|
||||||
|
private confidenceMasks?: MPMask[];
|
||||||
private outputCategoryMask = DEFAULT_OUTPUT_CATEGORY_MASK;
|
private outputCategoryMask = DEFAULT_OUTPUT_CATEGORY_MASK;
|
||||||
private outputConfidenceMasks = DEFAULT_OUTPUT_CONFIDENCE_MASKS;
|
private outputConfidenceMasks = DEFAULT_OUTPUT_CONFIDENCE_MASKS;
|
||||||
private userCallback?: InteractiveSegmenterCallback;
|
private userCallback?: InteractiveSegmenterCallback;
|
||||||
|
@ -276,28 +278,24 @@ export class InteractiveSegmenter extends VisionTaskRunner {
|
||||||
this.reset();
|
this.reset();
|
||||||
this.processRenderData(roi, this.getSynctheticTimestamp());
|
this.processRenderData(roi, this.getSynctheticTimestamp());
|
||||||
this.processImageData(image, imageProcessingOptions);
|
this.processImageData(image, imageProcessingOptions);
|
||||||
|
return this.processResults();
|
||||||
if (!this.userCallback) {
|
|
||||||
return this.result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private reset(): void {
|
private reset(): void {
|
||||||
this.result = {};
|
this.confidenceMasks = undefined;
|
||||||
|
this.categoryMask = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Invokes the user callback once all data has been received. */
|
private processResults(): InteractiveSegmenterResult|void {
|
||||||
private maybeInvokeCallback(): void {
|
try {
|
||||||
if (this.outputConfidenceMasks && !('confidenceMasks' in this.result)) {
|
const result = new InteractiveSegmenterResult(
|
||||||
return;
|
this.confidenceMasks, this.categoryMask);
|
||||||
}
|
if (this.userCallback) {
|
||||||
if (this.outputCategoryMask && !('categoryMask' in this.result)) {
|
this.userCallback(result);
|
||||||
return;
|
} else {
|
||||||
}
|
return result;
|
||||||
|
}
|
||||||
if (this.userCallback) {
|
} finally {
|
||||||
this.userCallback(this.result);
|
|
||||||
|
|
||||||
// Free the image memory, now that we've kept all streams alive long
|
// Free the image memory, now that we've kept all streams alive long
|
||||||
// enough to be returned in our callbacks.
|
// enough to be returned in our callbacks.
|
||||||
this.freeKeepaliveStreams();
|
this.freeKeepaliveStreams();
|
||||||
|
@ -333,17 +331,15 @@ export class InteractiveSegmenter extends VisionTaskRunner {
|
||||||
|
|
||||||
this.graphRunner.attachImageVectorListener(
|
this.graphRunner.attachImageVectorListener(
|
||||||
CONFIDENCE_MASKS_STREAM, (masks, timestamp) => {
|
CONFIDENCE_MASKS_STREAM, (masks, timestamp) => {
|
||||||
this.result.confidenceMasks = masks.map(
|
this.confidenceMasks = masks.map(
|
||||||
wasmImage => this.convertToMPMask(
|
wasmImage => this.convertToMPMask(
|
||||||
wasmImage, /* shouldCopyData= */ !this.userCallback));
|
wasmImage, /* shouldCopyData= */ !this.userCallback));
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
this.graphRunner.attachEmptyPacketListener(
|
this.graphRunner.attachEmptyPacketListener(
|
||||||
CONFIDENCE_MASKS_STREAM, timestamp => {
|
CONFIDENCE_MASKS_STREAM, timestamp => {
|
||||||
this.result.confidenceMasks = undefined;
|
this.confidenceMasks = [];
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,16 +350,14 @@ export class InteractiveSegmenter extends VisionTaskRunner {
|
||||||
|
|
||||||
this.graphRunner.attachImageListener(
|
this.graphRunner.attachImageListener(
|
||||||
CATEGORY_MASK_STREAM, (mask, timestamp) => {
|
CATEGORY_MASK_STREAM, (mask, timestamp) => {
|
||||||
this.result.categoryMask = this.convertToMPMask(
|
this.categoryMask = this.convertToMPMask(
|
||||||
mask, /* shouldCopyData= */ !this.userCallback);
|
mask, /* shouldCopyData= */ !this.userCallback);
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
this.graphRunner.attachEmptyPacketListener(
|
this.graphRunner.attachEmptyPacketListener(
|
||||||
CATEGORY_MASK_STREAM, timestamp => {
|
CATEGORY_MASK_STREAM, timestamp => {
|
||||||
this.result.categoryMask = undefined;
|
this.categoryMask = undefined;
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,18 +17,26 @@
|
||||||
import {MPMask} from '../../../../tasks/web/vision/core/mask';
|
import {MPMask} from '../../../../tasks/web/vision/core/mask';
|
||||||
|
|
||||||
/** The output result of InteractiveSegmenter. */
|
/** The output result of InteractiveSegmenter. */
|
||||||
export declare interface InteractiveSegmenterResult {
|
export class InteractiveSegmenterResult {
|
||||||
/**
|
constructor(
|
||||||
* Multiple masks represented as `Float32Array` or `WebGLTexture`-backed
|
/**
|
||||||
* `MPImage`s where, for each mask, each pixel represents the prediction
|
* Multiple masks represented as `Float32Array` or `WebGLTexture`-backed
|
||||||
* confidence, usually in the [0, 1] range.
|
* `MPImage`s where, for each mask, each pixel represents the prediction
|
||||||
*/
|
* confidence, usually in the [0, 1] range.
|
||||||
confidenceMasks?: MPMask[];
|
*/
|
||||||
|
readonly confidenceMasks?: MPMask[],
|
||||||
|
/**
|
||||||
|
* A category mask represented as a `Uint8ClampedArray` or
|
||||||
|
* `WebGLTexture`-backed `MPImage` where each pixel represents the class
|
||||||
|
* which the pixel in the original image was predicted to belong to.
|
||||||
|
*/
|
||||||
|
readonly categoryMask?: MPMask) {}
|
||||||
|
|
||||||
/**
|
/** Frees the resources held by the category and confidence masks. */
|
||||||
* A category mask represented as a `Uint8ClampedArray` or
|
close(): void {
|
||||||
* `WebGLTexture`-backed `MPImage` where each pixel represents the class which
|
this.confidenceMasks?.forEach(m => {
|
||||||
* the pixel in the original image was predicted to belong to.
|
m.close();
|
||||||
*/
|
});
|
||||||
categoryMask?: MPMask;
|
this.categoryMask?.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,7 +277,7 @@ describe('InteractiveSegmenter', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invokes listener once masks are avaiblae', async () => {
|
it('invokes listener after masks are avaiblae', async () => {
|
||||||
const categoryMask = new Uint8Array([1]);
|
const categoryMask = new Uint8Array([1]);
|
||||||
const confidenceMask = new Float32Array([0.0]);
|
const confidenceMask = new Float32Array([0.0]);
|
||||||
let listenerCalled = false;
|
let listenerCalled = false;
|
||||||
|
@ -296,7 +296,7 @@ describe('InteractiveSegmenter', () => {
|
||||||
{data: confidenceMask, width: 1, height: 1},
|
{data: confidenceMask, width: 1, height: 1},
|
||||||
],
|
],
|
||||||
1337);
|
1337);
|
||||||
expect(listenerCalled).toBeTrue();
|
expect(listenerCalled).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<void>(resolve => {
|
return new Promise<void>(resolve => {
|
||||||
|
@ -322,6 +322,6 @@ describe('InteractiveSegmenter', () => {
|
||||||
const result =
|
const result =
|
||||||
interactiveSegmenter.segment({} as HTMLImageElement, KEYPOINT);
|
interactiveSegmenter.segment({} as HTMLImageElement, KEYPOINT);
|
||||||
expect(result.confidenceMasks![0]).toBeInstanceOf(MPMask);
|
expect(result.confidenceMasks![0]).toBeInstanceOf(MPMask);
|
||||||
result.confidenceMasks![0].close();
|
result.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,9 +21,11 @@ import {BaseOptions as BaseOptionsProto} from '../../../../tasks/cc/core/proto/b
|
||||||
import {PoseDetectorGraphOptions} from '../../../../tasks/cc/vision/pose_detector/proto/pose_detector_graph_options_pb';
|
import {PoseDetectorGraphOptions} from '../../../../tasks/cc/vision/pose_detector/proto/pose_detector_graph_options_pb';
|
||||||
import {PoseLandmarkerGraphOptions} from '../../../../tasks/cc/vision/pose_landmarker/proto/pose_landmarker_graph_options_pb';
|
import {PoseLandmarkerGraphOptions} from '../../../../tasks/cc/vision/pose_landmarker/proto/pose_landmarker_graph_options_pb';
|
||||||
import {PoseLandmarksDetectorGraphOptions} from '../../../../tasks/cc/vision/pose_landmarker/proto/pose_landmarks_detector_graph_options_pb';
|
import {PoseLandmarksDetectorGraphOptions} from '../../../../tasks/cc/vision/pose_landmarker/proto/pose_landmarks_detector_graph_options_pb';
|
||||||
|
import {Landmark, NormalizedLandmark} from '../../../../tasks/web/components/containers/landmark';
|
||||||
import {convertToLandmarks, convertToWorldLandmarks} from '../../../../tasks/web/components/processors/landmark_result';
|
import {convertToLandmarks, convertToWorldLandmarks} from '../../../../tasks/web/components/processors/landmark_result';
|
||||||
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 {MPMask} from '../../../../tasks/web/vision/core/mask';
|
||||||
import {Connection} from '../../../../tasks/web/vision/core/types';
|
import {Connection} 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';
|
||||||
|
@ -61,7 +63,9 @@ export type PoseLandmarkerCallback = (result: PoseLandmarkerResult) => void;
|
||||||
|
|
||||||
/** Performs pose landmarks detection on images. */
|
/** Performs pose landmarks detection on images. */
|
||||||
export class PoseLandmarker extends VisionTaskRunner {
|
export class PoseLandmarker extends VisionTaskRunner {
|
||||||
private result: Partial<PoseLandmarkerResult> = {};
|
private landmarks: NormalizedLandmark[][] = [];
|
||||||
|
private worldLandmarks: Landmark[][] = [];
|
||||||
|
private segmentationMasks?: MPMask[];
|
||||||
private outputSegmentationMasks = false;
|
private outputSegmentationMasks = false;
|
||||||
private userCallback?: PoseLandmarkerCallback;
|
private userCallback?: PoseLandmarkerCallback;
|
||||||
private readonly options: PoseLandmarkerGraphOptions;
|
private readonly options: PoseLandmarkerGraphOptions;
|
||||||
|
@ -268,10 +272,7 @@ export class PoseLandmarker extends VisionTaskRunner {
|
||||||
|
|
||||||
this.resetResults();
|
this.resetResults();
|
||||||
this.processImageData(image, imageProcessingOptions);
|
this.processImageData(image, imageProcessingOptions);
|
||||||
|
return this.processResults();
|
||||||
if (!this.userCallback) {
|
|
||||||
return this.result as PoseLandmarkerResult;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -352,31 +353,25 @@ export class PoseLandmarker extends VisionTaskRunner {
|
||||||
|
|
||||||
this.resetResults();
|
this.resetResults();
|
||||||
this.processVideoData(videoFrame, imageProcessingOptions, timestamp);
|
this.processVideoData(videoFrame, imageProcessingOptions, timestamp);
|
||||||
|
return this.processResults();
|
||||||
if (!this.userCallback) {
|
|
||||||
return this.result as PoseLandmarkerResult;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private resetResults(): void {
|
private resetResults(): void {
|
||||||
this.result = {};
|
this.landmarks = [];
|
||||||
|
this.worldLandmarks = [];
|
||||||
|
this.segmentationMasks = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Invokes the user callback once all data has been received. */
|
private processResults(): PoseLandmarkerResult|void {
|
||||||
private maybeInvokeCallback(): void {
|
try {
|
||||||
if (!('landmarks' in this.result)) {
|
const result = new PoseLandmarkerResult(
|
||||||
return;
|
this.landmarks, this.worldLandmarks, this.segmentationMasks);
|
||||||
}
|
if (this.userCallback) {
|
||||||
if (!('worldLandmarks' in this.result)) {
|
this.userCallback(result);
|
||||||
return;
|
} else {
|
||||||
}
|
return result;
|
||||||
if (this.outputSegmentationMasks && !('segmentationMasks' in this.result)) {
|
}
|
||||||
return;
|
} finally {
|
||||||
}
|
|
||||||
|
|
||||||
if (this.userCallback) {
|
|
||||||
this.userCallback(this.result as Required<PoseLandmarkerResult>);
|
|
||||||
|
|
||||||
// Free the image memory, now that we've finished our callback.
|
// Free the image memory, now that we've finished our callback.
|
||||||
this.freeKeepaliveStreams();
|
this.freeKeepaliveStreams();
|
||||||
}
|
}
|
||||||
|
@ -396,11 +391,11 @@ export class PoseLandmarker extends VisionTaskRunner {
|
||||||
* Converts raw data into a landmark, and adds it to our landmarks list.
|
* Converts raw data into a landmark, and adds it to our landmarks list.
|
||||||
*/
|
*/
|
||||||
private addJsLandmarks(data: Uint8Array[]): void {
|
private addJsLandmarks(data: Uint8Array[]): void {
|
||||||
this.result.landmarks = [];
|
this.landmarks = [];
|
||||||
for (const binaryProto of data) {
|
for (const binaryProto of data) {
|
||||||
const poseLandmarksProto =
|
const poseLandmarksProto =
|
||||||
NormalizedLandmarkList.deserializeBinary(binaryProto);
|
NormalizedLandmarkList.deserializeBinary(binaryProto);
|
||||||
this.result.landmarks.push(convertToLandmarks(poseLandmarksProto));
|
this.landmarks.push(convertToLandmarks(poseLandmarksProto));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,11 +404,11 @@ export class PoseLandmarker extends VisionTaskRunner {
|
||||||
* worldLandmarks list.
|
* worldLandmarks list.
|
||||||
*/
|
*/
|
||||||
private adddJsWorldLandmarks(data: Uint8Array[]): void {
|
private adddJsWorldLandmarks(data: Uint8Array[]): void {
|
||||||
this.result.worldLandmarks = [];
|
this.worldLandmarks = [];
|
||||||
for (const binaryProto of data) {
|
for (const binaryProto of data) {
|
||||||
const poseWorldLandmarksProto =
|
const poseWorldLandmarksProto =
|
||||||
LandmarkList.deserializeBinary(binaryProto);
|
LandmarkList.deserializeBinary(binaryProto);
|
||||||
this.result.worldLandmarks.push(
|
this.worldLandmarks.push(
|
||||||
convertToWorldLandmarks(poseWorldLandmarksProto));
|
convertToWorldLandmarks(poseWorldLandmarksProto));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -448,26 +443,22 @@ export class PoseLandmarker extends VisionTaskRunner {
|
||||||
NORM_LANDMARKS_STREAM, (binaryProto, timestamp) => {
|
NORM_LANDMARKS_STREAM, (binaryProto, timestamp) => {
|
||||||
this.addJsLandmarks(binaryProto);
|
this.addJsLandmarks(binaryProto);
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
this.graphRunner.attachEmptyPacketListener(
|
this.graphRunner.attachEmptyPacketListener(
|
||||||
NORM_LANDMARKS_STREAM, timestamp => {
|
NORM_LANDMARKS_STREAM, timestamp => {
|
||||||
this.result.landmarks = [];
|
this.landmarks = [];
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.graphRunner.attachProtoVectorListener(
|
this.graphRunner.attachProtoVectorListener(
|
||||||
WORLD_LANDMARKS_STREAM, (binaryProto, timestamp) => {
|
WORLD_LANDMARKS_STREAM, (binaryProto, timestamp) => {
|
||||||
this.adddJsWorldLandmarks(binaryProto);
|
this.adddJsWorldLandmarks(binaryProto);
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
this.graphRunner.attachEmptyPacketListener(
|
this.graphRunner.attachEmptyPacketListener(
|
||||||
WORLD_LANDMARKS_STREAM, timestamp => {
|
WORLD_LANDMARKS_STREAM, timestamp => {
|
||||||
this.result.worldLandmarks = [];
|
this.worldLandmarks = [];
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.outputSegmentationMasks) {
|
if (this.outputSegmentationMasks) {
|
||||||
|
@ -477,17 +468,15 @@ export class PoseLandmarker extends VisionTaskRunner {
|
||||||
|
|
||||||
this.graphRunner.attachImageVectorListener(
|
this.graphRunner.attachImageVectorListener(
|
||||||
SEGMENTATION_MASK_STREAM, (masks, timestamp) => {
|
SEGMENTATION_MASK_STREAM, (masks, timestamp) => {
|
||||||
this.result.segmentationMasks = masks.map(
|
this.segmentationMasks = masks.map(
|
||||||
wasmImage => this.convertToMPMask(
|
wasmImage => this.convertToMPMask(
|
||||||
wasmImage, /* shouldCopyData= */ !this.userCallback));
|
wasmImage, /* shouldCopyData= */ !this.userCallback));
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
this.graphRunner.attachEmptyPacketListener(
|
this.graphRunner.attachEmptyPacketListener(
|
||||||
SEGMENTATION_MASK_STREAM, timestamp => {
|
SEGMENTATION_MASK_STREAM, timestamp => {
|
||||||
this.result.segmentationMasks = [];
|
this.segmentationMasks = [];
|
||||||
this.setLatestOutputTimestamp(timestamp);
|
this.setLatestOutputTimestamp(timestamp);
|
||||||
this.maybeInvokeCallback();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,13 +24,18 @@ export {Category, Landmark, NormalizedLandmark};
|
||||||
* Represents the pose landmarks deection results generated by `PoseLandmarker`.
|
* Represents the pose landmarks deection results generated by `PoseLandmarker`.
|
||||||
* Each vector element represents a single pose detected in the image.
|
* Each vector element represents a single pose detected in the image.
|
||||||
*/
|
*/
|
||||||
export declare interface PoseLandmarkerResult {
|
export class PoseLandmarkerResult {
|
||||||
/** Pose landmarks of detected poses. */
|
constructor(/** Pose landmarks of detected poses. */
|
||||||
landmarks: NormalizedLandmark[][];
|
readonly landmarks: NormalizedLandmark[][],
|
||||||
|
/** Pose landmarks in world coordinates of detected poses. */
|
||||||
|
readonly worldLandmarks: Landmark[][],
|
||||||
|
/** Segmentation mask for the detected pose. */
|
||||||
|
readonly segmentationMasks?: MPMask[]) {}
|
||||||
|
|
||||||
/** Pose landmarks in world coordinates of detected poses. */
|
/** Frees the resources held by the segmentation masks. */
|
||||||
worldLandmarks: Landmark[][];
|
close(): void {
|
||||||
|
this.segmentationMasks?.forEach(m => {
|
||||||
/** Segmentation mask for the detected pose. */
|
m.close();
|
||||||
segmentationMasks?: MPMask[];
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -287,7 +287,7 @@ describe('PoseLandmarker', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invokes listener once masks are available', (done) => {
|
it('invokes listener after masks are available', (done) => {
|
||||||
const landmarksProto = [createLandmarks().serializeBinary()];
|
const landmarksProto = [createLandmarks().serializeBinary()];
|
||||||
const worldLandmarksProto = [createWorldLandmarks().serializeBinary()];
|
const worldLandmarksProto = [createWorldLandmarks().serializeBinary()];
|
||||||
const masks = [
|
const masks = [
|
||||||
|
@ -309,13 +309,12 @@ describe('PoseLandmarker', () => {
|
||||||
expect(listenerCalled).toBeFalse();
|
expect(listenerCalled).toBeFalse();
|
||||||
expect(listenerCalled).toBeFalse();
|
expect(listenerCalled).toBeFalse();
|
||||||
poseLandmarker.listeners.get('segmentation_masks')!(masks, 1337);
|
poseLandmarker.listeners.get('segmentation_masks')!(masks, 1337);
|
||||||
expect(listenerCalled).toBeTrue();
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Invoke the pose landmarker
|
// Invoke the pose landmarker
|
||||||
poseLandmarker.detect({} as HTMLImageElement, () => {
|
poseLandmarker.detect({} as HTMLImageElement, () => {
|
||||||
listenerCalled = true;
|
listenerCalled = true;
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -336,5 +335,6 @@ describe('PoseLandmarker', () => {
|
||||||
expect(poseLandmarker.fakeWasmModule._waitUntilIdle).toHaveBeenCalled();
|
expect(poseLandmarker.fakeWasmModule._waitUntilIdle).toHaveBeenCalled();
|
||||||
expect(result.landmarks).toEqual([[{'x': 0, 'y': 0, 'z': 0}]]);
|
expect(result.landmarks).toEqual([[{'x': 0, 'y': 0, 'z': 0}]]);
|
||||||
expect(result.worldLandmarks).toEqual([[{'x': 0, 'y': 0, 'z': 0}]]);
|
expect(result.worldLandmarks).toEqual([[{'x': 0, 'y': 0, 'z': 0}]]);
|
||||||
|
result.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user