@webex/ladon-ts 5.6.1 → 5.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### 5.7.1 (2025-06-17)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * use live video element when skipping inference ([#71](https://github.com/beyonddunbar/ladon-ts/issues/71)) ([b916af0](https://github.com/beyonddunbar/ladon-ts/commit/b916af01cd66fd6e54030c95a19156d3b5656bcc))
11
+
12
+ ## 5.7.0 (2025-05-22)
13
+
14
+
15
+ ### Features
16
+
17
+ * overhaul frame syncing ([#69](https://github.com/beyonddunbar/ladon-ts/issues/69)) ([e80969b](https://github.com/beyonddunbar/ladon-ts/commit/e80969bbe0c2a3edc9f592996c5e2179eec370dc))
18
+
5
19
  ### 5.6.1 (2025-05-21)
6
20
 
7
21
 
@@ -26,6 +26,7 @@ export interface WorkerGeneratedResult {
26
26
  }
27
27
  export interface MaskGenerator {
28
28
  getLadonOutput(input: Uint8ClampedArray, timestamp: number): Promise<LadonOutput>;
29
+ getLadonOutputForTimestamp(timestamp: number): Promise<LadonOutput>;
29
30
  load(): Promise<void>;
30
31
  isLoaded(): boolean;
31
32
  postRender(input: Uint8ClampedArray, timestamp: number): Promise<void>;
@@ -24,12 +24,11 @@ export declare class Pipeline implements IPipeline {
24
24
  private loaded;
25
25
  private inputCanvasCtx;
26
26
  private lastMaskResult?;
27
- private frameQueue;
28
- private maskQueue;
29
27
  private frameDataBuffer?;
30
28
  constructor(inputCanvas: HTMLCanvasElement, outputCanvas: HTMLCanvasElement, config: PipelineConfig, videoElement?: HTMLVideoElement | undefined);
31
29
  /**
32
30
  * Processes a single frame and returns the inference result.
31
+ * This method handles the frame data, performs inference if needed, and renders the output.
33
32
  *
34
33
  * @param timestamp - The timestamp of the frame.
35
34
  * @param performInference - Flag to perform mask inference.
@@ -37,10 +36,6 @@ export declare class Pipeline implements IPipeline {
37
36
  * @throws If the pipeline (generator or renderer) is not loaded.
38
37
  */
39
38
  onFrame(timestamp: number, performInference?: boolean): Promise<IFrameInferenceResult>;
40
- /**
41
- * Synchronizes frames and masks to ensure they are rendered together.
42
- */
43
- private renderSyncedFrameAndMask;
44
39
  /**
45
40
  * Gets the raw image data from the input canvas context.
46
41
  *
@@ -74,10 +69,6 @@ export declare class Pipeline implements IPipeline {
74
69
  * Loads the pipeline components such as the generator and renderer.
75
70
  */
76
71
  load(): Promise<void>;
77
- /**
78
- * Resets the frame and mask queues.
79
- */
80
- private resetQueues;
81
72
  reset(): void;
82
73
  /**
83
74
  * Destroys the pipeline and cleans up resources.
@@ -1,47 +1,38 @@
1
1
  import { WorkerGenerator as n } from "./worker-generator.js";
2
- import { BlurRenderer as h } from "./renderer/blur-renderer.js";
3
- import { PassthroughRenderer as u } from "./renderer/passthrough-renderer.js";
4
- import { ReplacementRenderer as o } from "./renderer/replacement-renderer.js";
5
- import { resizeImage as d } from "./util.js";
6
- class k {
7
- constructor(e, t, s, r) {
8
- this.inputCanvas = e, this.outputCanvas = t, this.config = s, this.videoElement = r, this.loaded = !1, this.frameQueue = [], this.maskQueue = [];
9
- const a = this.inputCanvas.getContext("2d", {
2
+ import { BlurRenderer as o } from "./renderer/blur-renderer.js";
3
+ import { PassthroughRenderer as h } from "./renderer/passthrough-renderer.js";
4
+ import { ReplacementRenderer as d } from "./renderer/replacement-renderer.js";
5
+ import { resizeImage as u } from "./util.js";
6
+ class w {
7
+ constructor(t, e, a, r) {
8
+ this.inputCanvas = t, this.outputCanvas = e, this.config = a, this.videoElement = r, this.loaded = !1;
9
+ const s = this.inputCanvas.getContext("2d", {
10
10
  alpha: !1,
11
11
  willReadFrequently: !0
12
12
  });
13
- if (!a)
13
+ if (!s)
14
14
  throw new Error("[ladon-ts] Error getting input context 2d");
15
- this.inputCanvasCtx = a;
15
+ this.inputCanvasCtx = s;
16
16
  }
17
17
  /**
18
18
  * Processes a single frame and returns the inference result.
19
+ * This method handles the frame data, performs inference if needed, and renders the output.
19
20
  *
20
21
  * @param timestamp - The timestamp of the frame.
21
22
  * @param performInference - Flag to perform mask inference.
22
23
  * @returns The result of the frame processing.
23
24
  * @throws If the pipeline (generator or renderer) is not loaded.
24
25
  */
25
- async onFrame(e, t = !0) {
26
+ async onFrame(t, e = !0) {
26
27
  if (!this.generator || !this.renderer)
27
28
  throw new Error("[ladon-ts] Pipeline is not loaded.");
28
- const s = this.getInputFrameData(t);
29
- if (s instanceof Uint8ClampedArray ? ((!this.frameDataBuffer || this.frameDataBuffer.length !== s.length) && (this.frameDataBuffer = new Uint8ClampedArray(s.length)), this.frameDataBuffer.set(s), this.frameQueue.push({ timestamp: e, frameData: this.frameDataBuffer })) : this.frameQueue.push({ timestamp: e, frameData: s }), t || !this.lastMaskResult) {
30
- const r = s, a = this.getMaskImageData(r), i = await this.generator.getLadonOutput(a, e);
31
- this.maskQueue.push({ timestamp: i.timestamp, maskResult: i }), this.lastMaskResult = i, await this.renderSyncedFrameAndMask(), await this.generator.postRender(a, e);
32
- } else
33
- this.maskQueue.push({ timestamp: e, maskResult: this.lastMaskResult }), await this.renderSyncedFrameAndMask();
34
- return this.createInferenceResult(e, t);
35
- }
36
- /**
37
- * Synchronizes frames and masks to ensure they are rendered together.
38
- */
39
- async renderSyncedFrameAndMask() {
40
- var e;
41
- for (; this.frameQueue.length > 0 && this.maskQueue.length > 0; ) {
42
- const t = this.frameQueue[0], s = this.maskQueue[0];
43
- t.timestamp === s.timestamp ? (await ((e = this.renderer) == null ? void 0 : e.render(t.frameData, s.maskResult)), this.frameQueue.shift(), this.maskQueue.shift()) : t.timestamp < s.timestamp ? this.frameQueue.shift() : this.maskQueue.shift();
29
+ const a = this.getInputFrameData(e);
30
+ let r;
31
+ if (a instanceof Uint8ClampedArray ? ((!this.frameDataBuffer || this.frameDataBuffer.length !== a.length) && (this.frameDataBuffer = new Uint8ClampedArray(a.length)), this.frameDataBuffer.set(a), r = this.frameDataBuffer) : r = a, e || !this.lastMaskResult) {
32
+ const s = a, i = this.getMaskImageData(s);
33
+ await this.generator.postRender(i, t), this.lastMaskResult = await this.generator.getLadonOutputForTimestamp(t);
44
34
  }
35
+ return await this.renderer.render(r, this.lastMaskResult), this.createInferenceResult(t, e);
45
36
  }
46
37
  /**
47
38
  * Gets the raw image data from the input canvas context.
@@ -50,8 +41,8 @@ class k {
50
41
  *
51
42
  * @returns The raw image data.
52
43
  */
53
- getInputFrameData(e) {
54
- return e || !this.videoElement ? this.inputCanvasCtx.getImageData(
44
+ getInputFrameData(t) {
45
+ return t || !this.videoElement ? this.inputCanvasCtx.getImageData(
55
46
  0,
56
47
  0,
57
48
  this.config.input.width,
@@ -64,14 +55,14 @@ class k {
64
55
  * @param {Uint8ClampedArray} rawImageData - The raw image data.
65
56
  * @returns {Uint8ClampedArray} - The processed mask image data.
66
57
  */
67
- getMaskImageData(e) {
68
- const t = this.config.mask.inputSize, s = this.config.input;
69
- return t.width === s.width && t.height === s.height ? e : d(
70
- e,
71
- s.width,
72
- s.height,
73
- t.width,
74
- t.height
58
+ getMaskImageData(t) {
59
+ const e = this.config.mask.inputSize, a = this.config.input;
60
+ return e.width === a.width && e.height === a.height ? t : u(
61
+ t,
62
+ a.width,
63
+ a.height,
64
+ e.width,
65
+ e.height
75
66
  );
76
67
  }
77
68
  /**
@@ -81,19 +72,19 @@ class k {
81
72
  * @param {boolean} performedInference - Indicates if the inference was performed.
82
73
  * @returns {IFrameInferenceResult} - The result object containing inference details.
83
74
  */
84
- createInferenceResult(e, t) {
75
+ createInferenceResult(t, e) {
85
76
  return this.lastMaskResult ? {
86
77
  facesAndLandmarks: this.lastMaskResult.facesAndLandmarks,
87
78
  gesture: this.lastMaskResult.gesture,
88
79
  motion: this.lastMaskResult.motion,
89
- skipped: !t,
90
- timestamp: e
80
+ skipped: !e,
81
+ timestamp: t
91
82
  } : {
92
83
  facesAndLandmarks: { data: new Float32Array(0), dims: [] },
93
84
  gesture: new Float32Array(0),
94
85
  motion: 0,
95
86
  skipped: !0,
96
- timestamp: e
87
+ timestamp: t
97
88
  };
98
89
  }
99
90
  /**
@@ -110,37 +101,31 @@ class k {
110
101
  async load() {
111
102
  switch (this.config.render.type) {
112
103
  case "blur":
113
- this.renderer = new h(this.outputCanvas, this.config);
104
+ this.renderer = new o(this.outputCanvas, this.config);
114
105
  break;
115
106
  case "passthrough":
116
- this.renderer = new u(this.outputCanvas, this.config);
107
+ this.renderer = new h(this.outputCanvas, this.config);
117
108
  break;
118
109
  case "replacement":
119
- this.renderer = new o(this.outputCanvas, this.config);
110
+ this.renderer = new d(this.outputCanvas, this.config);
120
111
  break;
121
112
  default:
122
113
  throw new Error("[ladon-ts] Renderer not implemented.");
123
114
  }
124
115
  await this.renderer.load(), this.generator = new n(this.config), await this.generator.load(), this.loaded = !0;
125
116
  }
126
- /**
127
- * Resets the frame and mask queues.
128
- */
129
- resetQueues() {
130
- this.frameQueue = [], this.maskQueue = [];
131
- }
132
117
  reset() {
133
- this.resetQueues(), this.lastMaskResult = void 0;
118
+ this.lastMaskResult = void 0;
134
119
  }
135
120
  /**
136
121
  * Destroys the pipeline and cleans up resources.
137
122
  */
138
123
  async destroy() {
139
- var e, t;
140
- this.reset(), (e = this.renderer) == null || e.destroy(), await ((t = this.generator) == null ? void 0 : t.destroy()), this.frameDataBuffer = void 0, this.loaded = !1;
124
+ var t, e;
125
+ this.reset(), (t = this.renderer) == null || t.destroy(), await ((e = this.generator) == null ? void 0 : e.destroy()), this.frameDataBuffer = void 0, this.loaded = !1;
141
126
  }
142
127
  }
143
128
  export {
144
- k as Pipeline
129
+ w as Pipeline
145
130
  };
146
131
  //# sourceMappingURL=pipeline.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.js","sources":["../../../src/lib/pipeline.ts"],"sourcesContent":["import { PipelineConfig } from './config-types';\nimport {\n FacesAndLandmarks,\n FrameData,\n LadonOutput,\n MaskGenerator,\n WorkerGenerator,\n} from './generator';\nimport { BlurRenderer, LadonRenderer, PassthroughRenderer, ReplacementRenderer } from './renderer';\nimport { resizeImage } from './util';\n\nexport interface IFrameInferenceResult {\n facesAndLandmarks: FacesAndLandmarks;\n gesture: Float32Array;\n motion: number;\n skipped: boolean;\n timestamp: number;\n}\n\nexport interface IPipeline {\n onFrame(timestamp: number, maskNeeded?: boolean): Promise<IFrameInferenceResult>;\n isLoaded(): boolean;\n load(): Promise<void>;\n destroy(): Promise<void>;\n reset(): void;\n}\n\nexport class Pipeline implements IPipeline {\n private generator?: MaskGenerator;\n private renderer?: LadonRenderer;\n private loaded = false;\n\n private inputCanvasCtx: CanvasRenderingContext2D;\n private lastMaskResult?: LadonOutput;\n\n private frameQueue: Array<{ timestamp: number; frameData: FrameData }> = [];\n private maskQueue: Array<{ timestamp: number; maskResult: LadonOutput }> = [];\n\n private frameDataBuffer?: Uint8ClampedArray;\n\n constructor(\n private inputCanvas: HTMLCanvasElement,\n private outputCanvas: HTMLCanvasElement,\n public readonly config: PipelineConfig,\n private readonly videoElement?: HTMLVideoElement\n ) {\n const inputCanvasCtx = this.inputCanvas.getContext('2d', {\n alpha: false,\n willReadFrequently: true,\n });\n\n if (!inputCanvasCtx) {\n throw new Error('[ladon-ts] Error getting input context 2d');\n }\n this.inputCanvasCtx = inputCanvasCtx;\n }\n\n /**\n * Processes a single frame and returns the inference result.\n *\n * @param timestamp - The timestamp of the frame.\n * @param performInference - Flag to perform mask inference.\n * @returns The result of the frame processing.\n * @throws If the pipeline (generator or renderer) is not loaded.\n */\n public async onFrame(\n timestamp: number,\n performInference = true\n ): Promise<IFrameInferenceResult> {\n if (!this.generator || !this.renderer) {\n throw new Error('[ladon-ts] Pipeline is not loaded.');\n }\n\n // Get the current frame (raw image data or video element)\n const frameData = this.getInputFrameData(performInference);\n\n // Reuse or create buffer for frame data copies, which is needed to ensure the data is not modified\n // or garbage collected before the inference is done.\n if (frameData instanceof Uint8ClampedArray) {\n if (!this.frameDataBuffer || this.frameDataBuffer.length !== frameData.length) {\n this.frameDataBuffer = new Uint8ClampedArray(frameData.length);\n }\n this.frameDataBuffer.set(frameData);\n this.frameQueue.push({ timestamp, frameData: this.frameDataBuffer });\n } else {\n // For video elements, just pass the reference\n this.frameQueue.push({ timestamp, frameData });\n }\n\n if (performInference || !this.lastMaskResult) {\n // Perform inference and add the new mask to the mask queue\n const rawImageData = frameData as Uint8ClampedArray;\n const maskImageData = this.getMaskImageData(rawImageData);\n const maskResult = await this.generator.getLadonOutput(maskImageData, timestamp);\n // Add the mask to the queue and use the mask timestamp to sync frames and masks.\n this.maskQueue.push({ timestamp: maskResult.timestamp, maskResult });\n this.lastMaskResult = maskResult;\n\n // Sync frames and masks to render together\n await this.renderSyncedFrameAndMask();\n\n // Post-process the mask after rendering\n await this.generator.postRender(maskImageData, timestamp);\n } else {\n // Skipping inference. Update the last mask result's timestamp to match the current frame.\n this.maskQueue.push({ timestamp, maskResult: this.lastMaskResult });\n // Sync frames and masks to render together\n await this.renderSyncedFrameAndMask();\n }\n\n // Return the inference result\n return this.createInferenceResult(timestamp, performInference);\n }\n\n /**\n * Synchronizes frames and masks to ensure they are rendered together.\n */\n private async renderSyncedFrameAndMask(): Promise<void> {\n while (this.frameQueue.length > 0 && this.maskQueue.length > 0) {\n const frame = this.frameQueue[0];\n const maskEntry = this.maskQueue[0];\n\n if (frame.timestamp === maskEntry.timestamp) {\n // Render the frame and mask together\n await this.renderer?.render(frame.frameData, maskEntry.maskResult);\n this.frameQueue.shift();\n this.maskQueue.shift();\n } else if (frame.timestamp < maskEntry.timestamp) {\n // Discard the older frame\n this.frameQueue.shift();\n } else {\n // Discard the older mask\n this.maskQueue.shift();\n }\n }\n }\n\n /**\n * Gets the raw image data from the input canvas context.\n *\n * @param needFreshImageData - Flag to indicate if fresh image data is needed.\n *\n * @returns The raw image data.\n */\n private getInputFrameData(needFreshImageData: boolean): FrameData {\n if (needFreshImageData || !this.videoElement) {\n return this.inputCanvasCtx.getImageData(\n 0,\n 0,\n this.config.input.width,\n this.config.input.height\n ).data;\n }\n\n return this.videoElement;\n }\n\n /**\n * Resizes the image data if the model expects a fixed size input.\n *\n * @param {Uint8ClampedArray} rawImageData - The raw image data.\n * @returns {Uint8ClampedArray} - The processed mask image data.\n */\n private getMaskImageData(rawImageData: Uint8ClampedArray): Uint8ClampedArray {\n const modelInput = this.config.mask.inputSize;\n const inputImage = this.config.input;\n\n // Check if resizing is needed based on configuration.\n if (modelInput.width === inputImage.width && modelInput.height === inputImage.height) {\n return rawImageData;\n }\n\n // Resize the image data.\n return resizeImage(\n rawImageData,\n inputImage.width,\n inputImage.height,\n modelInput.width,\n modelInput.height\n );\n }\n\n /**\n * Creates the inference result object.\n *\n * @param {number} timestamp - The timestamp of the frame.\n * @param {boolean} performedInference - Indicates if the inference was performed.\n * @returns {IFrameInferenceResult} - The result object containing inference details.\n */\n private createInferenceResult(\n timestamp: number,\n performedInference: boolean\n ): IFrameInferenceResult {\n if (!this.lastMaskResult) {\n return {\n facesAndLandmarks: { data: new Float32Array(0), dims: [] },\n gesture: new Float32Array(0),\n motion: 0,\n skipped: true,\n timestamp,\n };\n }\n\n return {\n facesAndLandmarks: this.lastMaskResult.facesAndLandmarks,\n gesture: this.lastMaskResult.gesture,\n motion: this.lastMaskResult.motion,\n skipped: !performedInference,\n timestamp,\n };\n }\n\n /**\n * Checks if the pipeline is loaded.\n *\n * @returns True if the pipeline is loaded, false otherwise.\n */\n public isLoaded(): boolean {\n return this.loaded;\n }\n\n /**\n * Loads the pipeline components such as the generator and renderer.\n */\n public async load(): Promise<void> {\n switch (this.config.render.type) {\n case 'blur':\n this.renderer = new BlurRenderer(this.outputCanvas, this.config);\n break;\n case 'passthrough':\n this.renderer = new PassthroughRenderer(this.outputCanvas, this.config);\n break;\n case 'replacement':\n this.renderer = new ReplacementRenderer(this.outputCanvas, this.config);\n break;\n default:\n throw new Error('[ladon-ts] Renderer not implemented.');\n }\n\n await this.renderer.load();\n\n this.generator = new WorkerGenerator(this.config);\n await this.generator.load();\n\n this.loaded = true;\n }\n\n /**\n * Resets the frame and mask queues.\n */\n private resetQueues(): void {\n this.frameQueue = [];\n this.maskQueue = [];\n }\n\n public reset(): void {\n this.resetQueues();\n this.lastMaskResult = undefined;\n }\n\n /**\n * Destroys the pipeline and cleans up resources.\n */\n public async destroy(): Promise<void> {\n this.reset();\n this.renderer?.destroy();\n await this.generator?.destroy();\n this.frameDataBuffer = undefined;\n this.loaded = false;\n }\n}\n"],"names":["Pipeline","inputCanvas","outputCanvas","config","videoElement","inputCanvasCtx","timestamp","performInference","frameData","rawImageData","maskImageData","maskResult","frame","maskEntry","_a","needFreshImageData","modelInput","inputImage","resizeImage","performedInference","BlurRenderer","PassthroughRenderer","ReplacementRenderer","WorkerGenerator","_b"],"mappings":"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"}
1
+ {"version":3,"file":"pipeline.js","sources":["../../../src/lib/pipeline.ts"],"sourcesContent":["import { PipelineConfig } from './config-types';\nimport {\n FacesAndLandmarks,\n FrameData,\n LadonOutput,\n MaskGenerator,\n WorkerGenerator,\n} from './generator';\nimport { BlurRenderer, LadonRenderer, PassthroughRenderer, ReplacementRenderer } from './renderer';\nimport { resizeImage } from './util';\n\nexport interface IFrameInferenceResult {\n facesAndLandmarks: FacesAndLandmarks;\n gesture: Float32Array;\n motion: number;\n skipped: boolean;\n timestamp: number;\n}\n\nexport interface IPipeline {\n onFrame(timestamp: number, maskNeeded?: boolean): Promise<IFrameInferenceResult>;\n isLoaded(): boolean;\n load(): Promise<void>;\n destroy(): Promise<void>;\n reset(): void;\n}\n\nexport class Pipeline implements IPipeline {\n private generator?: MaskGenerator;\n private renderer?: LadonRenderer;\n private loaded = false;\n\n private inputCanvasCtx: CanvasRenderingContext2D;\n private lastMaskResult?: LadonOutput;\n\n private frameDataBuffer?: Uint8ClampedArray;\n\n constructor(\n private inputCanvas: HTMLCanvasElement,\n private outputCanvas: HTMLCanvasElement,\n public readonly config: PipelineConfig,\n private readonly videoElement?: HTMLVideoElement\n ) {\n const inputCanvasCtx = this.inputCanvas.getContext('2d', {\n alpha: false,\n willReadFrequently: true,\n });\n\n if (!inputCanvasCtx) {\n throw new Error('[ladon-ts] Error getting input context 2d');\n }\n this.inputCanvasCtx = inputCanvasCtx;\n }\n\n /**\n * Processes a single frame and returns the inference result.\n * This method handles the frame data, performs inference if needed, and renders the output.\n *\n * @param timestamp - The timestamp of the frame.\n * @param performInference - Flag to perform mask inference.\n * @returns The result of the frame processing.\n * @throws If the pipeline (generator or renderer) is not loaded.\n */\n public async onFrame(\n timestamp: number,\n performInference = true\n ): Promise<IFrameInferenceResult> {\n if (!this.generator || !this.renderer) {\n throw new Error('[ladon-ts] Pipeline is not loaded.');\n }\n\n // Get the current frame (raw image data or video element). Use the video element if skipping\n // inference to avoid unnecessary processing when calling canvas getImageData.\n const frameData = this.getInputFrameData(performInference);\n\n // Reuse or create buffer for frame data copies, which is needed to ensure the data is not modified\n // or garbage collected before the inference is done.\n let frameDataCopy: FrameData;\n if (frameData instanceof Uint8ClampedArray) {\n if (!this.frameDataBuffer || this.frameDataBuffer.length !== frameData.length) {\n this.frameDataBuffer = new Uint8ClampedArray(frameData.length);\n }\n this.frameDataBuffer.set(frameData);\n frameDataCopy = this.frameDataBuffer;\n } else {\n frameDataCopy = frameData;\n }\n\n if (performInference || !this.lastMaskResult) {\n // Send frame for inference and wait for the mask for this timestamp\n const rawImageData = frameData as Uint8ClampedArray;\n const maskImageData = this.getMaskImageData(rawImageData);\n await this.generator.postRender(maskImageData, timestamp);\n this.lastMaskResult = await this.generator.getLadonOutputForTimestamp(timestamp);\n }\n\n // Render the frame and mask together (mask may be stale if skipping inference)\n await this.renderer.render(frameDataCopy, this.lastMaskResult);\n\n return this.createInferenceResult(timestamp, performInference);\n }\n\n /**\n * Gets the raw image data from the input canvas context.\n *\n * @param needFreshImageData - Flag to indicate if fresh image data is needed.\n *\n * @returns The raw image data.\n */\n private getInputFrameData(needFreshImageData: boolean): FrameData {\n if (needFreshImageData || !this.videoElement) {\n return this.inputCanvasCtx.getImageData(\n 0,\n 0,\n this.config.input.width,\n this.config.input.height\n ).data;\n }\n\n return this.videoElement;\n }\n\n /**\n * Resizes the image data if the model expects a fixed size input.\n *\n * @param {Uint8ClampedArray} rawImageData - The raw image data.\n * @returns {Uint8ClampedArray} - The processed mask image data.\n */\n private getMaskImageData(rawImageData: Uint8ClampedArray): Uint8ClampedArray {\n const modelInput = this.config.mask.inputSize;\n const inputImage = this.config.input;\n\n // Check if resizing is needed based on configuration.\n if (modelInput.width === inputImage.width && modelInput.height === inputImage.height) {\n return rawImageData;\n }\n\n // Resize the image data.\n return resizeImage(\n rawImageData,\n inputImage.width,\n inputImage.height,\n modelInput.width,\n modelInput.height\n );\n }\n\n /**\n * Creates the inference result object.\n *\n * @param {number} timestamp - The timestamp of the frame.\n * @param {boolean} performedInference - Indicates if the inference was performed.\n * @returns {IFrameInferenceResult} - The result object containing inference details.\n */\n private createInferenceResult(\n timestamp: number,\n performedInference: boolean\n ): IFrameInferenceResult {\n if (!this.lastMaskResult) {\n return {\n facesAndLandmarks: { data: new Float32Array(0), dims: [] },\n gesture: new Float32Array(0),\n motion: 0,\n skipped: true,\n timestamp,\n };\n }\n\n return {\n facesAndLandmarks: this.lastMaskResult.facesAndLandmarks,\n gesture: this.lastMaskResult.gesture,\n motion: this.lastMaskResult.motion,\n skipped: !performedInference,\n timestamp,\n };\n }\n\n /**\n * Checks if the pipeline is loaded.\n *\n * @returns True if the pipeline is loaded, false otherwise.\n */\n public isLoaded(): boolean {\n return this.loaded;\n }\n\n /**\n * Loads the pipeline components such as the generator and renderer.\n */\n public async load(): Promise<void> {\n switch (this.config.render.type) {\n case 'blur':\n this.renderer = new BlurRenderer(this.outputCanvas, this.config);\n break;\n case 'passthrough':\n this.renderer = new PassthroughRenderer(this.outputCanvas, this.config);\n break;\n case 'replacement':\n this.renderer = new ReplacementRenderer(this.outputCanvas, this.config);\n break;\n default:\n throw new Error('[ladon-ts] Renderer not implemented.');\n }\n\n await this.renderer.load();\n\n this.generator = new WorkerGenerator(this.config);\n await this.generator.load();\n\n this.loaded = true;\n }\n\n public reset(): void {\n this.lastMaskResult = undefined;\n }\n\n /**\n * Destroys the pipeline and cleans up resources.\n */\n public async destroy(): Promise<void> {\n this.reset();\n this.renderer?.destroy();\n await this.generator?.destroy();\n this.frameDataBuffer = undefined;\n this.loaded = false;\n }\n}\n"],"names":["Pipeline","inputCanvas","outputCanvas","config","videoElement","inputCanvasCtx","timestamp","performInference","frameData","frameDataCopy","rawImageData","maskImageData","needFreshImageData","modelInput","inputImage","resizeImage","performedInference","BlurRenderer","PassthroughRenderer","ReplacementRenderer","WorkerGenerator","_a","_b"],"mappings":";;;;;AA2BO,MAAMA,EAA8B;AAAA,EAUvC,YACYC,GACAC,GACQC,GACCC,GACnB;AAJU,SAAA,cAAAH,GACA,KAAA,eAAAC,GACQ,KAAA,SAAAC,GACC,KAAA,eAAAC,GAXrB,KAAQ,SAAS;AAab,UAAMC,IAAiB,KAAK,YAAY,WAAW,MAAM;AAAA,MACrD,OAAO;AAAA,MACP,oBAAoB;AAAA,IAAA,CACvB;AAED,QAAI,CAACA;AACK,YAAA,IAAI,MAAM,2CAA2C;AAE/D,SAAK,iBAAiBA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY1B,MAAa,QACTC,GACAC,IAAmB,IACW;AAC9B,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK;AACnB,YAAA,IAAI,MAAM,oCAAoC;AAKlD,UAAAC,IAAY,KAAK,kBAAkBD,CAAgB;AAIrD,QAAAE;AAWA,QAVAD,aAAqB,sBACjB,CAAC,KAAK,mBAAmB,KAAK,gBAAgB,WAAWA,EAAU,YACnE,KAAK,kBAAkB,IAAI,kBAAkBA,EAAU,MAAM,IAE5D,KAAA,gBAAgB,IAAIA,CAAS,GAClCC,IAAgB,KAAK,mBAELA,IAAAD,GAGhBD,KAAoB,CAAC,KAAK,gBAAgB;AAE1C,YAAMG,IAAeF,GACfG,IAAgB,KAAK,iBAAiBD,CAAY;AACxD,YAAM,KAAK,UAAU,WAAWC,GAAeL,CAAS,GACxD,KAAK,iBAAiB,MAAM,KAAK,UAAU,2BAA2BA,CAAS;AAAA,IAAA;AAInF,iBAAM,KAAK,SAAS,OAAOG,GAAe,KAAK,cAAc,GAEtD,KAAK,sBAAsBH,GAAWC,CAAgB;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUzD,kBAAkBK,GAAwC;AAC1D,WAAAA,KAAsB,CAAC,KAAK,eACrB,KAAK,eAAe;AAAA,MACvB;AAAA,MACA;AAAA,MACA,KAAK,OAAO,MAAM;AAAA,MAClB,KAAK,OAAO,MAAM;AAAA,IAAA,EACpB,OAGC,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASR,iBAAiBF,GAAoD;AACnE,UAAAG,IAAa,KAAK,OAAO,KAAK,WAC9BC,IAAa,KAAK,OAAO;AAG/B,WAAID,EAAW,UAAUC,EAAW,SAASD,EAAW,WAAWC,EAAW,SACnEJ,IAIJK;AAAA,MACHL;AAAA,MACAI,EAAW;AAAA,MACXA,EAAW;AAAA,MACXD,EAAW;AAAA,MACXA,EAAW;AAAA,IACf;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUI,sBACJP,GACAU,GACqB;AACjB,WAAC,KAAK,iBAUH;AAAA,MACH,mBAAmB,KAAK,eAAe;AAAA,MACvC,SAAS,KAAK,eAAe;AAAA,MAC7B,QAAQ,KAAK,eAAe;AAAA,MAC5B,SAAS,CAACA;AAAA,MACV,WAAAV;AAAA,IACJ,IAfW;AAAA,MACH,mBAAmB,EAAE,MAAM,IAAI,aAAa,CAAC,GAAG,MAAM,GAAG;AAAA,MACzD,SAAS,IAAI,aAAa,CAAC;AAAA,MAC3B,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAAA;AAAA,IACJ;AAAA,EASJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQG,WAAoB;AACvB,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMhB,MAAa,OAAsB;AACvB,YAAA,KAAK,OAAO,OAAO,MAAM;AAAA,MAC7B,KAAK;AACD,aAAK,WAAW,IAAIW,EAAa,KAAK,cAAc,KAAK,MAAM;AAC/D;AAAA,MACJ,KAAK;AACD,aAAK,WAAW,IAAIC,EAAoB,KAAK,cAAc,KAAK,MAAM;AACtE;AAAA,MACJ,KAAK;AACD,aAAK,WAAW,IAAIC,EAAoB,KAAK,cAAc,KAAK,MAAM;AACtE;AAAA,MACJ;AACU,cAAA,IAAI,MAAM,sCAAsC;AAAA,IAAA;AAGxD,UAAA,KAAK,SAAS,KAAK,GAEzB,KAAK,YAAY,IAAIC,EAAgB,KAAK,MAAM,GAC1C,MAAA,KAAK,UAAU,KAAK,GAE1B,KAAK,SAAS;AAAA,EAAA;AAAA,EAGX,QAAc;AACjB,SAAK,iBAAiB;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAM1B,MAAa,UAAyB;;AAClC,SAAK,MAAM,IACXC,IAAA,KAAK,aAAL,QAAAA,EAAe,WACT,QAAAC,IAAA,KAAK,cAAL,gBAAAA,EAAgB,YACtB,KAAK,kBAAkB,QACvB,KAAK,SAAS;AAAA,EAAA;AAEtB;"}
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "5.6.1";
1
+ export declare const LIB_VERSION = "5.7.1";
@@ -1,4 +1,4 @@
1
- const o = "5.6.1";
1
+ const o = "5.7.1";
2
2
  export {
3
3
  o as LIB_VERSION
4
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sources":["../../../src/lib/version.ts"],"sourcesContent":["export const LIB_VERSION = '5.6.1';\n"],"names":["LIB_VERSION"],"mappings":"AAAO,MAAMA,IAAc;"}
1
+ {"version":3,"file":"version.js","sources":["../../../src/lib/version.ts"],"sourcesContent":["export const LIB_VERSION = '5.7.1';\n"],"names":["LIB_VERSION"],"mappings":"AAAO,MAAMA,IAAc;"}
@@ -2,11 +2,21 @@ import { PipelineConfig } from './config-types';
2
2
  import { LadonOutput, MaskGenerator } from './generator-types';
3
3
  export declare class WorkerGenerator implements MaskGenerator {
4
4
  readonly config: PipelineConfig;
5
+ private pending;
5
6
  private lastResult;
6
7
  private newMask;
7
8
  constructor(config: PipelineConfig);
8
9
  isLoaded(): boolean;
9
10
  getLadonOutput(): Promise<LadonOutput>;
11
+ /**
12
+ * Returns a promise that resolves when the worker returns the mask for the given timestamp.
13
+ * This is useful for synchronizing the processing of frames with their corresponding masks.
14
+ * The promise will be resolved with the mask data when it is available.
15
+ *
16
+ * @param timestamp - The timestamp of the frame for which the mask is requested.
17
+ * @returns A promise that resolves with the mask data when available.
18
+ */
19
+ getLadonOutputForTimestamp(timestamp: number): Promise<LadonOutput>;
10
20
  /**
11
21
  * Loads the worker and initializes it with the provided configuration.
12
22
  * The worker is responsible for processing the input image and generating the mask.
@@ -1,7 +1,7 @@
1
- import { workerManager as t } from "./worker-manager.js";
1
+ import { workerManager as s } from "./worker-manager.js";
2
2
  class m {
3
3
  constructor(e) {
4
- this.config = e, this.newMask = !0, this.lastResult = {
4
+ this.config = e, this.pending = /* @__PURE__ */ new Map(), this.newMask = !0, this.lastResult = {
5
5
  data: new Float32Array(
6
6
  this.config.mask.outputSize.height * this.config.mask.outputSize.width
7
7
  ),
@@ -13,7 +13,7 @@ class m {
13
13
  };
14
14
  }
15
15
  isLoaded() {
16
- return !!t.getWorker();
16
+ return !!s.getWorker();
17
17
  }
18
18
  async getLadonOutput() {
19
19
  const e = {
@@ -26,6 +26,19 @@ class m {
26
26
  };
27
27
  return this.newMask = !1, e;
28
28
  }
29
+ /**
30
+ * Returns a promise that resolves when the worker returns the mask for the given timestamp.
31
+ * This is useful for synchronizing the processing of frames with their corresponding masks.
32
+ * The promise will be resolved with the mask data when it is available.
33
+ *
34
+ * @param timestamp - The timestamp of the frame for which the mask is requested.
35
+ * @returns A promise that resolves with the mask data when available.
36
+ */
37
+ async getLadonOutputForTimestamp(e) {
38
+ return new Promise((a) => {
39
+ this.pending.set(e, a);
40
+ });
41
+ }
29
42
  /**
30
43
  * Loads the worker and initializes it with the provided configuration.
31
44
  * The worker is responsible for processing the input image and generating the mask.
@@ -34,32 +47,38 @@ class m {
34
47
  * @returns A promise that resolves when the worker is loaded and initialized.
35
48
  */
36
49
  async load() {
37
- const e = await t.prepareWorkerConfig(this.config);
50
+ const e = await s.prepareWorkerConfig(this.config);
38
51
  return new Promise((a) => {
39
- t.onInitialized(() => {
52
+ s.onInitialized(() => {
40
53
  a();
41
- }), t.onGenerated((s) => {
42
- this.lastResult = {
43
- data: s.mask,
44
- facesAndLandmarks: s.facesAndLandmarks,
45
- gesture: s.gesture,
46
- motion: s.motion,
47
- timestamp: s.timestamp,
54
+ }), s.onGenerated((t) => {
55
+ const r = {
56
+ data: t.mask,
57
+ facesAndLandmarks: t.facesAndLandmarks,
58
+ gesture: t.gesture,
59
+ motion: t.motion,
60
+ timestamp: t.timestamp,
48
61
  warm: !0
49
- }, this.newMask = !0;
50
- }), t.onLog((s, n) => {
51
- const r = `[ladon-ts] Message received from worker: ${s}`;
52
- switch (n) {
62
+ };
63
+ this.lastResult = r, this.newMask = !0;
64
+ const n = this.pending.get(t.timestamp);
65
+ n && (n(r), this.pending.delete(t.timestamp));
66
+ }), s.onDropped((t) => {
67
+ const r = this.pending.get(t);
68
+ r && (r(this.lastResult), this.pending.delete(t));
69
+ }), s.onLog((t, r) => {
70
+ const n = `[ladon-ts] Message received from worker: ${t}`;
71
+ switch (r) {
53
72
  case "warn":
54
- console.warn(r);
73
+ console.warn(n);
55
74
  break;
56
75
  case "error":
57
- console.error(r);
76
+ console.error(n);
58
77
  break;
59
78
  default:
60
- console.log(r);
79
+ console.log(n);
61
80
  }
62
- }), (async () => (await (t.getWorker() ?? t.initializeWorker(e)), t.sendInit({
81
+ }), (async () => (await (s.getWorker() ?? s.initializeWorker(e)), s.sendInit({
63
82
  baseUrl: e.baseUrl,
64
83
  input: e.input,
65
84
  mask: e.mask,
@@ -77,7 +96,7 @@ class m {
77
96
  * @param timestamp - The timestamp of the input image.
78
97
  */
79
98
  async postRender(e, a) {
80
- t.sendGenerate(e, a);
99
+ s.sendGenerate(e, a);
81
100
  }
82
101
  /**
83
102
  * Destroys the worker and cleans up resources.
@@ -88,7 +107,7 @@ class m {
88
107
  * @returns A promise that resolves when the worker is destroyed.
89
108
  */
90
109
  destroy() {
91
- return t.destroyWorker();
110
+ return s.destroyWorker();
92
111
  }
93
112
  }
94
113
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"worker-generator.js","sources":["../../../src/lib/worker-generator.ts"],"sourcesContent":["import { PipelineConfig } from './config-types';\nimport { LadonOutput, MaskGenerator, WorkerGeneratedResult } from './generator-types';\nimport { workerManager } from './worker-manager';\n\nexport class WorkerGenerator implements MaskGenerator {\n private lastResult: LadonOutput;\n private newMask = true;\n\n constructor(public readonly config: PipelineConfig) {\n this.lastResult = {\n data: new Float32Array(\n this.config.mask.outputSize.height * this.config.mask.outputSize.width\n ),\n facesAndLandmarks: { data: new Float32Array(0), dims: [] },\n gesture: new Float32Array(0),\n motion: 0,\n timestamp: 0,\n warm: false,\n };\n }\n\n public isLoaded(): boolean {\n return !!workerManager.getWorker();\n }\n\n public async getLadonOutput(): Promise<LadonOutput> {\n const toReturn = {\n data: this.lastResult.data,\n facesAndLandmarks: this.lastResult.facesAndLandmarks,\n gesture: this.lastResult.gesture,\n motion: this.lastResult.motion,\n timestamp: this.lastResult.timestamp,\n warm: this.newMask,\n };\n\n this.newMask = false;\n\n return toReturn;\n }\n\n /**\n * Loads the worker and initializes it with the provided configuration.\n * The worker is responsible for processing the input image and generating the mask.\n * Prepares the worker configuration by resolving the final URLs and paths to send to the worker.\n *\n * @returns A promise that resolves when the worker is loaded and initialized.\n */\n public async load(): Promise<void> {\n const config = await workerManager.prepareWorkerConfig(this.config);\n\n // Listen for worker events\n return new Promise<void>((resolve) => {\n workerManager.onInitialized(() => {\n resolve();\n });\n workerManager.onGenerated((data: WorkerGeneratedResult) => {\n this.lastResult = {\n data: data.mask,\n facesAndLandmarks: data.facesAndLandmarks,\n gesture: data.gesture,\n motion: data.motion,\n timestamp: data.timestamp,\n warm: true,\n };\n this.newMask = true;\n });\n workerManager.onLog((message, severity) => {\n const msg = `[ladon-ts] Message received from worker: ${message}`;\n switch (severity) {\n case 'warn':\n console.warn(msg);\n break;\n case 'error':\n console.error(msg);\n break;\n default:\n console.log(msg);\n }\n });\n\n // Initialize the worker if not already done, then always send init\n (async () => {\n await (workerManager.getWorker() ?? workerManager.initializeWorker(config));\n workerManager.sendInit({\n baseUrl: config.baseUrl,\n input: config.input,\n mask: config.mask,\n wasmUri: config.wasmUri,\n render: { type: 'passthrough' },\n });\n })();\n });\n }\n\n /**\n * Sends the input image to the worker for processing.\n * The input image is expected to be a Uint8ClampedArray.\n * Claims ownership of the input tensor's buffer and forwards it to the worker.\n *\n * @param input - The input image as a Uint8ClampedArray.\n * @param timestamp - The timestamp of the input image.\n */\n public async postRender(input: Uint8ClampedArray, timestamp: number): Promise<void> {\n workerManager.sendGenerate(input, timestamp);\n }\n\n /**\n * Destroys the worker and cleans up resources.\n * First it sends a message to the worker to destroy itself,\n * then it waits for the worker to respond with a 'destroyed' message.\n * After that, it terminates the worker and revokes the URL if it exists.\n *\n * @returns A promise that resolves when the worker is destroyed.\n */\n public destroy(): Promise<void> {\n return workerManager.destroyWorker();\n }\n}\n"],"names":["WorkerGenerator","config","workerManager","toReturn","resolve","data","message","severity","msg","input","timestamp"],"mappings":";AAIO,MAAMA,EAAyC;AAAA,EAIlD,YAA4BC,GAAwB;AAAxB,SAAA,SAAAA,GAF5B,KAAQ,UAAU,IAGd,KAAK,aAAa;AAAA,MACd,MAAM,IAAI;AAAA,QACN,KAAK,OAAO,KAAK,WAAW,SAAS,KAAK,OAAO,KAAK,WAAW;AAAA,MACrE;AAAA,MACA,mBAAmB,EAAE,MAAM,IAAI,aAAa,CAAC,GAAG,MAAM,GAAG;AAAA,MACzD,SAAS,IAAI,aAAa,CAAC;AAAA,MAC3B,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,MAAM;AAAA,IACV;AAAA,EAAA;AAAA,EAGG,WAAoB;AAChB,WAAA,CAAC,CAACC,EAAc,UAAU;AAAA,EAAA;AAAA,EAGrC,MAAa,iBAAuC;AAChD,UAAMC,IAAW;AAAA,MACb,MAAM,KAAK,WAAW;AAAA,MACtB,mBAAmB,KAAK,WAAW;AAAA,MACnC,SAAS,KAAK,WAAW;AAAA,MACzB,QAAQ,KAAK,WAAW;AAAA,MACxB,WAAW,KAAK,WAAW;AAAA,MAC3B,MAAM,KAAK;AAAA,IACf;AAEA,gBAAK,UAAU,IAERA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUX,MAAa,OAAsB;AAC/B,UAAMF,IAAS,MAAMC,EAAc,oBAAoB,KAAK,MAAM;AAG3D,WAAA,IAAI,QAAc,CAACE,MAAY;AAClC,MAAAF,EAAc,cAAc,MAAM;AACtB,QAAAE,EAAA;AAAA,MAAA,CACX,GACaF,EAAA,YAAY,CAACG,MAAgC;AACvD,aAAK,aAAa;AAAA,UACd,MAAMA,EAAK;AAAA,UACX,mBAAmBA,EAAK;AAAA,UACxB,SAASA,EAAK;AAAA,UACd,QAAQA,EAAK;AAAA,UACb,WAAWA,EAAK;AAAA,UAChB,MAAM;AAAA,QACV,GACA,KAAK,UAAU;AAAA,MAAA,CAClB,GACaH,EAAA,MAAM,CAACI,GAASC,MAAa;AACjC,cAAAC,IAAM,4CAA4CF,CAAO;AAC/D,gBAAQC,GAAU;AAAA,UACd,KAAK;AACD,oBAAQ,KAAKC,CAAG;AAChB;AAAA,UACJ,KAAK;AACD,oBAAQ,MAAMA,CAAG;AACjB;AAAA,UACJ;AACI,oBAAQ,IAAIA,CAAG;AAAA,QAAA;AAAA,MACvB,CACH,IAGA,aACG,OAAON,EAAc,UAAe,KAAAA,EAAc,iBAAiBD,CAAM,IACzEC,EAAc,SAAS;AAAA,QACnB,SAASD,EAAO;AAAA,QAChB,OAAOA,EAAO;AAAA,QACd,MAAMA,EAAO;AAAA,QACb,SAASA,EAAO;AAAA,QAChB,QAAQ,EAAE,MAAM,cAAc;AAAA,MAAA,CACjC;AAAA,IACF,CACN;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWL,MAAa,WAAWQ,GAA0BC,GAAkC;AAClE,IAAAR,EAAA,aAAaO,GAAOC,CAAS;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWxC,UAAyB;AAC5B,WAAOR,EAAc,cAAc;AAAA,EAAA;AAE3C;"}
1
+ {"version":3,"file":"worker-generator.js","sources":["../../../src/lib/worker-generator.ts"],"sourcesContent":["import { PipelineConfig } from './config-types';\nimport { LadonOutput, MaskGenerator, WorkerGeneratedResult } from './generator-types';\nimport { workerManager } from './worker-manager';\n\nexport class WorkerGenerator implements MaskGenerator {\n private pending: Map<number, (result: LadonOutput) => void> = new Map();\n private lastResult: LadonOutput;\n private newMask = true;\n\n constructor(public readonly config: PipelineConfig) {\n this.lastResult = {\n data: new Float32Array(\n this.config.mask.outputSize.height * this.config.mask.outputSize.width\n ),\n facesAndLandmarks: { data: new Float32Array(0), dims: [] },\n gesture: new Float32Array(0),\n motion: 0,\n timestamp: 0,\n warm: false,\n };\n }\n\n public isLoaded(): boolean {\n return !!workerManager.getWorker();\n }\n\n public async getLadonOutput(): Promise<LadonOutput> {\n const toReturn = {\n data: this.lastResult.data,\n facesAndLandmarks: this.lastResult.facesAndLandmarks,\n gesture: this.lastResult.gesture,\n motion: this.lastResult.motion,\n timestamp: this.lastResult.timestamp,\n warm: this.newMask,\n };\n\n this.newMask = false;\n\n return toReturn;\n }\n\n /**\n * Returns a promise that resolves when the worker returns the mask for the given timestamp.\n * This is useful for synchronizing the processing of frames with their corresponding masks.\n * The promise will be resolved with the mask data when it is available.\n *\n * @param timestamp - The timestamp of the frame for which the mask is requested.\n * @returns A promise that resolves with the mask data when available.\n */\n public async getLadonOutputForTimestamp(timestamp: number): Promise<LadonOutput> {\n return new Promise<LadonOutput>((resolve) => {\n this.pending.set(timestamp, resolve);\n });\n }\n\n /**\n * Loads the worker and initializes it with the provided configuration.\n * The worker is responsible for processing the input image and generating the mask.\n * Prepares the worker configuration by resolving the final URLs and paths to send to the worker.\n *\n * @returns A promise that resolves when the worker is loaded and initialized.\n */\n public async load(): Promise<void> {\n const config = await workerManager.prepareWorkerConfig(this.config);\n\n // Listen for worker events\n return new Promise<void>((resolve) => {\n workerManager.onInitialized(() => {\n resolve();\n });\n workerManager.onGenerated((data: WorkerGeneratedResult) => {\n const result: LadonOutput = {\n data: data.mask,\n facesAndLandmarks: data.facesAndLandmarks,\n gesture: data.gesture,\n motion: data.motion,\n timestamp: data.timestamp,\n warm: true,\n };\n this.lastResult = result;\n this.newMask = true;\n\n // Resolve the pending promise for this timestamp, if any\n const cb = this.pending.get(data.timestamp);\n if (cb) {\n cb(result);\n this.pending.delete(data.timestamp);\n }\n });\n workerManager.onDropped((timestamp: number) => {\n // Resolve the pending promise for this timestamp with the last result\n const cb = this.pending.get(timestamp);\n if (cb) {\n cb(this.lastResult);\n this.pending.delete(timestamp);\n }\n });\n workerManager.onLog((message, severity) => {\n const msg = `[ladon-ts] Message received from worker: ${message}`;\n switch (severity) {\n case 'warn':\n console.warn(msg);\n break;\n case 'error':\n console.error(msg);\n break;\n default:\n console.log(msg);\n }\n });\n\n // Initialize the worker if not already done, then always send init\n (async () => {\n await (workerManager.getWorker() ?? workerManager.initializeWorker(config));\n workerManager.sendInit({\n baseUrl: config.baseUrl,\n input: config.input,\n mask: config.mask,\n wasmUri: config.wasmUri,\n render: { type: 'passthrough' },\n });\n })();\n });\n }\n\n /**\n * Sends the input image to the worker for processing.\n * The input image is expected to be a Uint8ClampedArray.\n * Claims ownership of the input tensor's buffer and forwards it to the worker.\n *\n * @param input - The input image as a Uint8ClampedArray.\n * @param timestamp - The timestamp of the input image.\n */\n public async postRender(input: Uint8ClampedArray, timestamp: number): Promise<void> {\n workerManager.sendGenerate(input, timestamp);\n }\n\n /**\n * Destroys the worker and cleans up resources.\n * First it sends a message to the worker to destroy itself,\n * then it waits for the worker to respond with a 'destroyed' message.\n * After that, it terminates the worker and revokes the URL if it exists.\n *\n * @returns A promise that resolves when the worker is destroyed.\n */\n public destroy(): Promise<void> {\n return workerManager.destroyWorker();\n }\n}\n"],"names":["WorkerGenerator","config","workerManager","toReturn","timestamp","resolve","data","result","cb","message","severity","msg","input"],"mappings":"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"}
@@ -12,6 +12,7 @@ export declare class WorkerManager {
12
12
  private destroyPromise;
13
13
  private onInitializedCallbacks;
14
14
  private onGeneratedCallbacks;
15
+ private onDroppedCallbacks;
15
16
  private onLogCallbacks;
16
17
  /**
17
18
  * Get the current worker instance.
@@ -68,6 +69,14 @@ export declare class WorkerManager {
68
69
  * @param data.motion - The detected motion.
69
70
  */
70
71
  onGenerated(callback: (data: WorkerGeneratedResult) => void): void;
72
+ /**
73
+ * Register a callback for when the worker drops a frame.
74
+ * This is useful for handling cases where the worker cannot keep up with the input frame rate.
75
+ * The callback will be called with the timestamp of the dropped frame.
76
+ *
77
+ * @param callback - The callback function to be called with the timestamp of the dropped frame.
78
+ */
79
+ onDropped(callback: (timestamp: number) => void): void;
71
80
  /**
72
81
  * Register a callback for log messages from the worker.
73
82
  *
@@ -1,7 +1,7 @@
1
1
  import { resourceLoader as a } from "./resource-loader.js";
2
- class l {
2
+ class d {
3
3
  constructor() {
4
- this.destroyPromise = null, this.onInitializedCallbacks = [], this.onGeneratedCallbacks = [], this.onLogCallbacks = [];
4
+ this.destroyPromise = null, this.onInitializedCallbacks = [], this.onGeneratedCallbacks = [], this.onDroppedCallbacks = [], this.onLogCallbacks = [];
5
5
  }
6
6
  /**
7
7
  * Get the current worker instance.
@@ -22,11 +22,11 @@ class l {
22
22
  throw new Error("[ladon-ts] web workers are not supported in this environment");
23
23
  this.destroyPromise && (await this.destroyPromise, this.destroyPromise = null), this.worker && (this.destroyPromise = this.destroyWorker(), await this.destroyPromise, this.destroyPromise = null);
24
24
  const r = await a.resolveUri(e, e.mask.workerUri), o = await (await fetch(r, { cache: "force-cache" })).text();
25
- return this.workerUrl = URL.createObjectURL(new Blob([o], { type: "text/javascript" })), this.worker = new Worker(this.workerUrl, { type: "module" }), this.worker.addEventListener("message", (i) => {
26
- if (i.data.type === "log") {
27
- const { message: t, severity: n } = i.data;
28
- this.onLogCallbacks.forEach((k) => k(t, n));
29
- } else i.data.status === "generated" ? this.onGeneratedCallbacks.forEach((t) => t(i.data)) : i.data.status === "initialized" && this.onInitializedCallbacks.forEach((t) => t());
25
+ return this.workerUrl = URL.createObjectURL(new Blob([o], { type: "text/javascript" })), this.worker = new Worker(this.workerUrl, { type: "module" }), this.worker.addEventListener("message", (t) => {
26
+ if (t.data.type === "log") {
27
+ const { message: s, severity: n } = t.data;
28
+ this.onLogCallbacks.forEach((l) => l(s, n));
29
+ } else t.data.status === "generated" ? this.onGeneratedCallbacks.forEach((s) => s(t.data)) : t.data.status === "dropped" ? this.onDroppedCallbacks.forEach((s) => s(t.data.timestamp)) : t.data.status === "initialized" && this.onInitializedCallbacks.forEach((s) => s());
30
30
  }), this.worker;
31
31
  }
32
32
  /**
@@ -37,9 +37,9 @@ class l {
37
37
  */
38
38
  destroyWorker() {
39
39
  return new Promise((e) => {
40
- const r = (s) => {
40
+ const r = (i) => {
41
41
  var o;
42
- s.data.status === "destroyed" && ((o = this.worker) == null || o.removeEventListener("message", r), this.terminateWorker(), e());
42
+ i.data.status === "destroyed" && ((o = this.worker) == null || o.removeEventListener("message", r), this.terminateWorker(), e());
43
43
  };
44
44
  if (!this.worker) {
45
45
  console.warn("[ladon-ts] No worker to destroy"), e();
@@ -73,12 +73,12 @@ class l {
73
73
  * @returns The worker configuration.
74
74
  */
75
75
  async prepareWorkerConfig(e) {
76
- const r = await a.resolveUri(e, e.mask.modelUri), s = await a.resolveUri(e, e.mask.workerUri), o = await a.resolveUri(e, e.wasmUri), t = { ...{
76
+ const r = await a.resolveUri(e, e.mask.modelUri), i = await a.resolveUri(e, e.mask.workerUri), o = await a.resolveUri(e, e.wasmUri), s = { ...{
77
77
  ...e,
78
- mask: { ...e.mask, modelUri: r, workerUri: s },
78
+ mask: { ...e.mask, modelUri: r, workerUri: i },
79
79
  wasmUri: o
80
80
  }, assetUrlResolver: void 0 };
81
- return JSON.parse(JSON.stringify(t));
81
+ return JSON.parse(JSON.stringify(s));
82
82
  }
83
83
  /**
84
84
  * Register a callback for when the worker is initialized.
@@ -99,6 +99,16 @@ class l {
99
99
  onGenerated(e) {
100
100
  this.onGeneratedCallbacks.push(e);
101
101
  }
102
+ /**
103
+ * Register a callback for when the worker drops a frame.
104
+ * This is useful for handling cases where the worker cannot keep up with the input frame rate.
105
+ * The callback will be called with the timestamp of the dropped frame.
106
+ *
107
+ * @param callback - The callback function to be called with the timestamp of the dropped frame.
108
+ */
109
+ onDropped(e) {
110
+ this.onDroppedCallbacks.push(e);
111
+ }
102
112
  /**
103
113
  * Register a callback for log messages from the worker.
104
114
  *
@@ -120,8 +130,9 @@ class l {
120
130
  * @param config.assetUrlResolver - The function to resolve asset URLs.
121
131
  */
122
132
  sendInit(e) {
123
- var r;
124
- (r = this.worker) == null || r.postMessage({ type: "init", config: e });
133
+ if (!this.worker)
134
+ throw new Error("[ladon-ts] Worker is not initialized, cannot send init");
135
+ this.worker.postMessage({ type: "init", config: e });
125
136
  }
126
137
  /**
127
138
  * Send a generate message to the worker.
@@ -130,8 +141,9 @@ class l {
130
141
  * @param timestamp - The timestamp of the input image.
131
142
  */
132
143
  sendGenerate(e, r) {
133
- var s;
134
- (s = this.worker) == null || s.postMessage(
144
+ if (!this.worker)
145
+ throw new Error("[ladon-ts] Worker is not initialized, cannot send generate");
146
+ this.worker.postMessage(
135
147
  {
136
148
  type: "generate",
137
149
  image: e,
@@ -141,9 +153,9 @@ class l {
141
153
  );
142
154
  }
143
155
  }
144
- const w = new l();
156
+ const w = new d();
145
157
  export {
146
- l as WorkerManager,
158
+ d as WorkerManager,
147
159
  w as workerManager
148
160
  };
149
161
  //# sourceMappingURL=worker-manager.js.map