@webex/ladon-ts 5.8.0 → 5.9.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 +14 -0
- package/build/module/lib/config-builder.d.ts +2 -2
- package/build/module/lib/config-builder.js +1 -1
- package/build/module/lib/config-builder.js.map +1 -1
- package/build/module/lib/config-types.d.ts +2 -1
- package/build/module/lib/config-types.js.map +1 -1
- package/build/module/lib/generator-types.d.ts +1 -1
- package/build/module/lib/pipeline.d.ts +76 -25
- package/build/module/lib/pipeline.js +121 -72
- package/build/module/lib/pipeline.js.map +1 -1
- package/build/module/lib/renderer/replacement-renderer.d.ts +67 -5
- package/build/module/lib/renderer/replacement-renderer.js +99 -49
- package/build/module/lib/renderer/replacement-renderer.js.map +1 -1
- package/build/module/lib/version.d.ts +1 -1
- package/build/module/lib/version.js +1 -1
- package/build/module/lib/version.js.map +1 -1
- package/build/module/lib/worker-generator.d.ts +1 -1
- package/build/module/lib/worker-generator.js +12 -12
- package/build/module/lib/worker-generator.js.map +1 -1
- package/build/worker/segmentation.js +3 -3
- package/build/worker/segmentation.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.js","sources":["../../../src/lib/pipeline.ts"],"sourcesContent":["import { PipelineConfig } from './config-types';\nimport { FrameData, LadonOutput, MaskGenerator, WorkerGenerator } from './generator';\nimport { IFrameInferenceResult, IPipeline } from './pipeline-types';\nimport { BlurRenderer, LadonRenderer, PassthroughRenderer, ReplacementRenderer } from './renderer';\nimport { resizeImage } from './util';\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 * @param frameData - Optional frame data to process.\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 const frameStartTime = performance.now();\n\n if (!this.generator || !this.renderer) {\n throw new Error('[ladon-ts] Pipeline is not loaded.');\n }\n\n // Pre-processing timing\n const preprocessStart = performance.now();\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 const preprocessingTime = performance.now() - preprocessStart;\n\n let modelInferenceTime = 0;\n\n if (performInference || !this.lastMaskResult) {\n // Model inference timing\n const inferenceStart = performance.now();\n\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 modelInferenceTime = performance.now() - inferenceStart;\n }\n\n // Post-processing timing (rendering)\n const postprocessStart = performance.now();\n\n // Render the frame and mask together (mask may be stale if skipping inference)\n const videoInputImage = this.videoElement || frameDataCopy;\n await this.renderer.render(videoInputImage, this.lastMaskResult);\n\n const postprocessingTime = performance.now() - postprocessStart;\n\n const frameEndTime = performance.now();\n const totalFrameTime = frameEndTime - frameStartTime;\n\n const result = this.createInferenceResult(timestamp, performInference);\n\n // Add timing stats to the result\n result.stats = {\n totalFrameTime,\n modelInferenceTime,\n preprocessingTime,\n postprocessingTime,\n frameStartTime,\n frameEndTime,\n inferenceSkipped: !performInference,\n };\n\n return result;\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 timestamp - The timestamp of the frame.\n * @param performedInference - Indicates if the inference was performed.\n * @returns 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 // Stats will be added by the caller if needed\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 // Stats will be added by the caller if needed\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","frameStartTime","preprocessStart","frameData","frameDataCopy","preprocessingTime","modelInferenceTime","inferenceStart","rawImageData","maskImageData","postprocessStart","videoInputImage","postprocessingTime","frameEndTime","totalFrameTime","result","needFreshImageData","modelInput","inputImage","resizeImage","performedInference","BlurRenderer","PassthroughRenderer","ReplacementRenderer","WorkerGenerator","_a","_b"],"mappings":";;;;;AAMO,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;AAAA,EAa1B,MAAa,QACTC,GACAC,IAAmB,IACW;AACxB,UAAAC,IAAiB,YAAY,IAAI;AAEvC,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK;AACnB,YAAA,IAAI,MAAM,oCAAoC;AAIlD,UAAAC,IAAkB,YAAY,IAAI,GAIlCC,IAAY,KAAK,kBAAkBH,CAAgB;AAIrD,QAAAI;AACJ,IAAID,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;AAGd,UAAAE,IAAoB,YAAY,IAAA,IAAQH;AAE9C,QAAII,IAAqB;AAErB,QAAAN,KAAoB,CAAC,KAAK,gBAAgB;AAEpC,YAAAO,IAAiB,YAAY,IAAI,GAGjCC,IAAeL,GACfM,IAAgB,KAAK,iBAAiBD,CAAY;AACxD,YAAM,KAAK,UAAU,WAAWC,GAAeV,CAAS,GACxD,KAAK,iBAAiB,MAAM,KAAK,UAAU,2BAA2BA,CAAS,GAE1DO,IAAA,YAAY,QAAQC;AAAA,IAAA;AAIvC,UAAAG,IAAmB,YAAY,IAAI,GAGnCC,IAAkB,KAAK,gBAAgBP;AAC7C,UAAM,KAAK,SAAS,OAAOO,GAAiB,KAAK,cAAc;AAEzD,UAAAC,IAAqB,YAAY,IAAA,IAAQF,GAEzCG,IAAe,YAAY,IAAI,GAC/BC,IAAiBD,IAAeZ,GAEhCc,IAAS,KAAK,sBAAsBhB,GAAWC,CAAgB;AAGrE,WAAAe,EAAO,QAAQ;AAAA,MACX,gBAAAD;AAAA,MACA,oBAAAR;AAAA,MACA,mBAAAD;AAAA,MACA,oBAAAO;AAAA,MACA,gBAAAX;AAAA,MACA,cAAAY;AAAA,MACA,kBAAkB,CAACb;AAAA,IACvB,GAEOe;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUH,kBAAkBC,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,iBAAiBR,GAAoD;AACnE,UAAAS,IAAa,KAAK,OAAO,KAAK,WAC9BC,IAAa,KAAK,OAAO;AAG/B,WAAID,EAAW,UAAUC,EAAW,SAASD,EAAW,WAAWC,EAAW,SACnEV,IAIJW;AAAA,MACHX;AAAA,MACAU,EAAW;AAAA,MACXA,EAAW;AAAA,MACXD,EAAW;AAAA,MACXA,EAAW;AAAA,IACf;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUI,sBACJlB,GACAqB,GACqB;AACjB,WAAC,KAAK,iBAWH;AAAA,MACH,mBAAmB,KAAK,eAAe;AAAA,MACvC,SAAS,KAAK,eAAe;AAAA,MAC7B,QAAQ,KAAK,eAAe;AAAA,MAC5B,SAAS,CAACA;AAAA,MACV,WAAArB;AAAA;AAAA,IAEJ,IAjBW;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;AAAA,IAEJ;AAAA,EAUJ;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,IAAIsB,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
|
+
{"version":3,"file":"pipeline.js","sources":["../../../src/lib/pipeline.ts"],"sourcesContent":["import { PipelineConfig } from './config-types';\nimport { FrameData, LadonOutput, MaskGenerator, WorkerGenerator } from './generator';\nimport { IFrameInferenceResult, IPipeline } from './pipeline-types';\nimport { BlurRenderer, LadonRenderer, PassthroughRenderer, ReplacementRenderer } from './renderer';\nimport { resizeImage } from './util';\n\n/**\n * Core pipeline implementation for processing video frames with ML inference and rendering.\n * Handles frame processing, mask generation, and output rendering with proper timing controls.\n */\nexport class Pipeline implements IPipeline {\n /** The mask generator responsible for ML inference. */\n private generator?: MaskGenerator;\n\n /** The renderer responsible for compositing the final output. */\n private renderer?: LadonRenderer;\n\n /** Flag indicating whether the pipeline has been fully loaded and initialized. */\n private loaded = false;\n\n /** 2D rendering context for the input canvas used for frame processing. */\n private inputCanvasContext: CanvasRenderingContext2D;\n\n /** The most recent mask result from ML inference, used when skipping inference. */\n private lastMaskResult?: LadonOutput;\n\n /** Flag indicating whether a frame is currently being processed. */\n private isProcessingFrame = false;\n\n /** Promise for the currently pending frame processing operation. */\n private pendingFramePromise: Promise<IFrameInferenceResult> | null = null;\n\n /** Buffer for frame data copies to ensure they are not modified or garbage collected. */\n private frameDataBuffer?: Uint8ClampedArray;\n\n /**\n * Create a new pipeline instance.\n *\n * @param inputCanvas - Canvas element for input frame processing.\n * @param outputCanvas - Canvas element where the final rendered output will be displayed.\n * @param config - Pipeline configuration containing processing and rendering settings.\n * @param inputVideoElement - Video element serving as the input source.\n * @throws Error if the input canvas 2D context cannot be obtained.\n */\n constructor(\n private inputCanvas: HTMLCanvasElement,\n private outputCanvas: HTMLCanvasElement,\n public readonly config: PipelineConfig,\n private readonly inputVideoElement: HTMLVideoElement\n ) {\n const inputCanvasContext = this.inputCanvas.getContext('2d', {\n alpha: false,\n willReadFrequently: true,\n });\n\n if (!inputCanvasContext) {\n throw new Error('[ladon-ts] Error getting input context 2d');\n }\n this.inputCanvasContext = inputCanvasContext;\n }\n\n /**\n * Process a single frame and return the inference result.\n * This method handles frame data, performs inference if needed, and renders the output.\n * Includes frame processing serialization to prevent overlapping operations.\n *\n * @param timestamp - The timestamp of the frame in milliseconds.\n * @param performInference - Whether to perform mask inference or reuse the last result.\n * @param frameData - Optional frame data to process instead of capturing from input.\n * @returns Promise resolving to the frame processing result with timing statistics.\n * @throws Error if the pipeline (generator or renderer) is not loaded.\n */\n public async onFrame(\n timestamp: number,\n performInference = true,\n frameData?: FrameData\n ): Promise<IFrameInferenceResult> {\n // If already processing, wait for the current frame to finish\n if (this.isProcessingFrame && this.pendingFramePromise) {\n await this.pendingFramePromise;\n }\n\n // Set processing flag\n this.isProcessingFrame = true;\n\n try {\n this.pendingFramePromise = this.processFrameInternal(\n timestamp,\n performInference,\n frameData\n );\n return await this.pendingFramePromise;\n } finally {\n this.isProcessingFrame = false;\n this.pendingFramePromise = null;\n }\n }\n\n /**\n * Internal frame processing implementation.\n * Handles the actual frame processing logic including timing measurements.\n *\n * @param timestamp - The timestamp of the frame in milliseconds.\n * @param performInference - Whether to perform mask inference or reuse the last result.\n * @param frameData - Optional frame data to process instead of capturing from input.\n * @returns Promise resolving to the frame processing result with timing statistics.\n * @throws Error if the pipeline (generator or renderer) is not loaded.\n */\n private async processFrameInternal(\n timestamp: number,\n performInference = true,\n frameData?: FrameData\n ): Promise<IFrameInferenceResult> {\n const frameStartTime = performance.now();\n\n if (!this.generator || !this.renderer) {\n throw new Error('[ladon-ts] Pipeline is not loaded.');\n }\n\n let preprocessingTime = 0;\n let modelInferenceTime = 0;\n let renderFrameData: FrameData;\n\n if (performInference || !this.lastMaskResult) {\n // INFERENCE PATH: Capture canvas data, generate mask, render with same data\n const preprocessStart = performance.now();\n\n // Get canvas data for inference\n frameData ??= this.getInputFrameData();\n\n // Create stable copy for rendering synchronization\n renderFrameData = this.createFrameDataCopy(frameData);\n\n preprocessingTime = performance.now() - preprocessStart;\n\n // Generate mask\n const inferenceStart = performance.now();\n const rawImageData = frameData as Uint8ClampedArray;\n const maskImageData = this.getMaskImageData(rawImageData);\n await this.generator.process(maskImageData, timestamp);\n this.lastMaskResult = await this.generator.getLadonOutputForTimestamp(timestamp);\n modelInferenceTime = performance.now() - inferenceStart;\n } else {\n // SKIP INFERENCE PATH: Use video element directly for GPU optimization\n renderFrameData = this.inputVideoElement;\n }\n\n // Render with appropriate frame data\n const postprocessStart = performance.now();\n await this.renderer.render(renderFrameData, this.lastMaskResult);\n const postprocessingTime = performance.now() - postprocessStart;\n\n const frameEndTime = performance.now();\n const totalFrameTime = frameEndTime - frameStartTime;\n\n const result = this.createInferenceResult(timestamp, performInference);\n result.stats = {\n totalFrameTime,\n modelInferenceTime,\n preprocessingTime,\n postprocessingTime,\n frameStartTime,\n frameEndTime,\n inferenceSkipped: !performInference,\n };\n\n return result;\n }\n\n /**\n * Get the raw image data from the input canvas context.\n *\n * @returns The frame data as a Uint8ClampedArray.\n */\n private getInputFrameData(): FrameData {\n return this.inputCanvasContext.getImageData(\n 0,\n 0,\n this.config.input.width,\n this.config.input.height\n ).data;\n }\n\n /**\n * Create a copy of frame data to prevent modifications during async operations.\n */\n private createFrameDataCopy(frameData: FrameData): FrameData {\n if (frameData instanceof Uint8ClampedArray) {\n if (this.frameDataBuffer?.length !== frameData.length) {\n this.frameDataBuffer = new Uint8ClampedArray(frameData.length);\n }\n this.frameDataBuffer.set(frameData);\n return this.frameDataBuffer;\n }\n return frameData;\n }\n\n /**\n * Resize the image data if the model expects a different input size.\n * Handles scaling between input dimensions and model requirements.\n *\n * @param rawImageData - The raw image data to potentially resize.\n * @returns The processed mask image data, resized if necessary.\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 * Create the inference result object with appropriate data.\n * Handles both successful inference results and fallback cases.\n *\n * @param timestamp - The timestamp of the frame in milliseconds.\n * @param performedInference - Whether inference was actually performed for this frame.\n * @returns The inference result object containing mask data and metadata.\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 // Stats will be added by the caller if needed\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 // Stats will be added by the caller if needed\n };\n }\n\n /**\n * Check if the pipeline is fully loaded and ready for processing.\n *\n * @returns True if the pipeline is loaded and ready, false otherwise.\n */\n public isLoaded(): boolean {\n return this.loaded;\n }\n\n /**\n * Load and initialize the pipeline components.\n * Creates the appropriate renderer and generator based on configuration.\n *\n * @returns Promise that resolves when all components are loaded and ready.\n * @throws Error if an unsupported renderer type is specified in the configuration.\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 * Reset the pipeline state by clearing cached mask results.\n * Useful when switching between different processing modes or sources.\n */\n public reset(): void {\n this.lastMaskResult = undefined;\n }\n\n /**\n * Destroy the pipeline and clean up all resources.\n * Properly disposes of the renderer and generator to prevent memory leaks.\n *\n * @returns Promise that resolves when cleanup is complete.\n */\n public async destroy(): Promise<void> {\n this.reset();\n this.renderer?.destroy();\n await this.generator?.destroy();\n this.loaded = false;\n }\n}\n"],"names":["Pipeline","inputCanvas","outputCanvas","config","inputVideoElement","inputCanvasContext","timestamp","performInference","frameData","frameStartTime","preprocessingTime","modelInferenceTime","renderFrameData","preprocessStart","inferenceStart","rawImageData","maskImageData","postprocessStart","postprocessingTime","frameEndTime","totalFrameTime","result","_a","modelInput","inputImage","resizeImage","performedInference","BlurRenderer","PassthroughRenderer","ReplacementRenderer","WorkerGenerator","_b"],"mappings":"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"}
|
|
@@ -2,17 +2,79 @@ import { PipelineConfig } from '../config-types';
|
|
|
2
2
|
import { FrameData, LadonOutput } from '../generator';
|
|
3
3
|
import { LadonRenderer } from './ladon-renderer';
|
|
4
4
|
import { WebGLRenderer } from './webgl-renderer';
|
|
5
|
+
/**
|
|
6
|
+
* WebGL renderer implementation for virtual background replacement effects.
|
|
7
|
+
* Handles rendering of video backgrounds with proper frame rate throttling.
|
|
8
|
+
*/
|
|
5
9
|
export declare class ReplacementRenderer extends WebGLRenderer implements LadonRenderer {
|
|
6
|
-
|
|
10
|
+
outputCanvas: HTMLCanvasElement;
|
|
7
11
|
readonly config: PipelineConfig;
|
|
12
|
+
/** The compiled WebGL shader program used for rendering. */
|
|
8
13
|
private program;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
/** Timestamp of the last video frame update in milliseconds. */
|
|
15
|
+
private lastVideoFrameTime;
|
|
16
|
+
/** Interval between video frame updates in milliseconds. */
|
|
17
|
+
private videoFrameInterval;
|
|
18
|
+
/** WebGL texture containing the background image or video frame. */
|
|
19
|
+
protected backgroundTexture: WebGLTexture;
|
|
20
|
+
/** Fragment shader source code for the replacement effect. */
|
|
21
|
+
protected fragmentShader: string;
|
|
22
|
+
/**
|
|
23
|
+
* Create a new replacement renderer instance.
|
|
24
|
+
*
|
|
25
|
+
* @param outputCanvas - The canvas element where the rendered output will be displayed.
|
|
26
|
+
* @param config - Pipeline configuration containing render settings and background source.
|
|
27
|
+
*/
|
|
28
|
+
constructor(outputCanvas: HTMLCanvasElement, config: PipelineConfig);
|
|
29
|
+
/**
|
|
30
|
+
* Build the fragment shader source code for the replacement effect.
|
|
31
|
+
* Creates a shader that blends the background with the input video based on the mask.
|
|
32
|
+
*
|
|
33
|
+
* @returns The complete fragment shader source code.
|
|
34
|
+
*/
|
|
13
35
|
protected buildFragmentShader(): string;
|
|
36
|
+
/**
|
|
37
|
+
* Set up the WebGL texture for the background image or video.
|
|
38
|
+
* Creates and configures the texture that will be used as the replacement background.
|
|
39
|
+
*/
|
|
14
40
|
protected setupBackgroundTexture(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Render a frame with the replacement background effect.
|
|
43
|
+
* Combines the input video stream with the background based on the provided mask.
|
|
44
|
+
* Includes frame rate throttling for video backgrounds to prevent stuttering.
|
|
45
|
+
* Canvas backgrounds are updated every frame to support dynamic content.
|
|
46
|
+
*
|
|
47
|
+
* @param input - The input frame data (video element or image data).
|
|
48
|
+
* @param mask - Optional mask data for determining foreground/background blend.
|
|
49
|
+
* @returns Promise that resolves when rendering is complete.
|
|
50
|
+
*/
|
|
15
51
|
render(input: FrameData, mask?: LadonOutput): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Throttle video background updates and upload texture only when needed.
|
|
54
|
+
* This prevents stuttering and unnecessary GPU uploads when inference is skipped.
|
|
55
|
+
*
|
|
56
|
+
* @param backgroundSource - The video element to potentially upload.
|
|
57
|
+
* @returns Promise that resolves after appropriate throttling and upload.
|
|
58
|
+
*/
|
|
59
|
+
private updateVideoBackgroundTexture;
|
|
60
|
+
/**
|
|
61
|
+
* Attempt to determine the frame rate of a video element.
|
|
62
|
+
* Tries to extract frame rate from video track settings if available.
|
|
63
|
+
*
|
|
64
|
+
* @param video - The video element to analyze.
|
|
65
|
+
* @returns The video's frame rate in frames per second, or undefined if not determinable.
|
|
66
|
+
*/
|
|
67
|
+
private getVideoFrameRate;
|
|
68
|
+
/**
|
|
69
|
+
* Load and initialize the renderer.
|
|
70
|
+
* Currently a no-op for this renderer implementation.
|
|
71
|
+
*
|
|
72
|
+
* @returns Promise that resolves when loading is complete.
|
|
73
|
+
*/
|
|
16
74
|
load(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Clean up resources and destroy the renderer.
|
|
77
|
+
* Currently a no-op for this renderer implementation.
|
|
78
|
+
*/
|
|
17
79
|
destroy(): void;
|
|
18
80
|
}
|
|
@@ -1,19 +1,30 @@
|
|
|
1
|
-
import { compileShader as
|
|
1
|
+
import { compileShader as a, linkProgram as g } from "../util.js";
|
|
2
2
|
import { WebGLRenderer as h } from "./webgl-renderer.js";
|
|
3
|
-
class
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
class l extends h {
|
|
4
|
+
/**
|
|
5
|
+
* Create a new replacement renderer instance.
|
|
6
|
+
*
|
|
7
|
+
* @param outputCanvas - The canvas element where the rendered output will be displayed.
|
|
8
|
+
* @param config - Pipeline configuration containing render settings and background source.
|
|
9
|
+
*/
|
|
10
|
+
constructor(t, e) {
|
|
11
|
+
super(t, e), this.outputCanvas = t, this.config = e, this.lastVideoFrameTime = 0;
|
|
12
|
+
const s = this.config.render.background;
|
|
13
|
+
if (s instanceof HTMLVideoElement) {
|
|
14
|
+
const n = this.getVideoFrameRate(s) || 30;
|
|
15
|
+
this.videoFrameInterval = 1e3 / n;
|
|
16
|
+
} else
|
|
17
|
+
this.videoFrameInterval = 0;
|
|
18
|
+
this.fragmentShader = this.buildFragmentShader();
|
|
19
|
+
const i = a(this.gl, this.gl.VERTEX_SHADER, this.vertex_shader_src), o = a(this.gl, this.gl.FRAGMENT_SHADER, this.fragmentShader);
|
|
20
|
+
this.program = g(this.gl, i, o), this.gl.useProgram(this.program), this.setSharedUniforms(this.program), this.setupBackgroundTexture();
|
|
16
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Build the fragment shader source code for the replacement effect.
|
|
24
|
+
* Creates a shader that blends the background with the input video based on the mask.
|
|
25
|
+
*
|
|
26
|
+
* @returns The complete fragment shader source code.
|
|
27
|
+
*/
|
|
17
28
|
buildFragmentShader() {
|
|
18
29
|
return `${this.fragment_shader_header}
|
|
19
30
|
|
|
@@ -32,64 +43,103 @@ void main() {
|
|
|
32
43
|
outColor = vec4(color, 1.0);
|
|
33
44
|
}`;
|
|
34
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Set up the WebGL texture for the background image or video.
|
|
48
|
+
* Creates and configures the texture that will be used as the replacement background.
|
|
49
|
+
*/
|
|
35
50
|
setupBackgroundTexture() {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
0,
|
|
39
|
-
this.config.render.background.width,
|
|
40
|
-
this.config.render.background.height
|
|
41
|
-
);
|
|
42
|
-
this.background_texture = this.createTexture(
|
|
51
|
+
const e = this.config.render.background;
|
|
52
|
+
this.backgroundTexture = this.createTexture(
|
|
43
53
|
this.gl.RGBA,
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
e.width,
|
|
55
|
+
e.height,
|
|
46
56
|
() => {
|
|
47
57
|
this.gl.texImage2D(
|
|
48
58
|
this.gl.TEXTURE_2D,
|
|
49
59
|
0,
|
|
50
60
|
this.gl.RGBA,
|
|
51
|
-
t.width,
|
|
52
|
-
t.height,
|
|
53
|
-
0,
|
|
54
61
|
this.gl.RGBA,
|
|
55
62
|
this.gl.UNSIGNED_BYTE,
|
|
56
|
-
|
|
63
|
+
e
|
|
57
64
|
);
|
|
58
65
|
}
|
|
59
66
|
);
|
|
60
67
|
const r = this.gl.getUniformLocation(this.program, "u_background");
|
|
61
68
|
if (!r)
|
|
62
69
|
throw new Error('[ladon-ts] Error finding "u_background" uniform');
|
|
63
|
-
this.gl.activeTexture(this.gl.TEXTURE2), this.gl.bindTexture(this.gl.TEXTURE_2D, this.
|
|
70
|
+
this.gl.activeTexture(this.gl.TEXTURE2), this.gl.bindTexture(this.gl.TEXTURE_2D, this.backgroundTexture), this.gl.uniform1i(r, 2);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Render a frame with the replacement background effect.
|
|
74
|
+
* Combines the input video stream with the background based on the provided mask.
|
|
75
|
+
* Includes frame rate throttling for video backgrounds to prevent stuttering.
|
|
76
|
+
* Canvas backgrounds are updated every frame to support dynamic content.
|
|
77
|
+
*
|
|
78
|
+
* @param input - The input frame data (video element or image data).
|
|
79
|
+
* @param mask - Optional mask data for determining foreground/background blend.
|
|
80
|
+
* @returns Promise that resolves when rendering is complete.
|
|
81
|
+
*/
|
|
82
|
+
async render(t, e) {
|
|
83
|
+
this.uploadVideoStream(t), this.uploadMask(e);
|
|
84
|
+
const r = this.config.render, s = !r.static, i = r.background;
|
|
85
|
+
s && (i instanceof HTMLVideoElement ? await this.updateVideoBackgroundTexture(i) : (this.gl.activeTexture(this.gl.TEXTURE2), this.gl.bindTexture(this.gl.TEXTURE_2D, this.backgroundTexture), this.gl.texImage2D(
|
|
86
|
+
this.gl.TEXTURE_2D,
|
|
87
|
+
0,
|
|
88
|
+
this.gl.RGBA,
|
|
89
|
+
this.gl.RGBA,
|
|
90
|
+
this.gl.UNSIGNED_BYTE,
|
|
91
|
+
i
|
|
92
|
+
))), this.gl.useProgram(this.program), this.gl.drawArrays(this.gl.TRIANGLES, 0, this.verts.length / 3), this.postDraw();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Throttle video background updates and upload texture only when needed.
|
|
96
|
+
* This prevents stuttering and unnecessary GPU uploads when inference is skipped.
|
|
97
|
+
*
|
|
98
|
+
* @param backgroundSource - The video element to potentially upload.
|
|
99
|
+
* @returns Promise that resolves after appropriate throttling and upload.
|
|
100
|
+
*/
|
|
101
|
+
async updateVideoBackgroundTexture(t) {
|
|
102
|
+
if (this.videoFrameInterval <= 0) return;
|
|
103
|
+
const e = performance.now();
|
|
104
|
+
e - this.lastVideoFrameTime >= this.videoFrameInterval && (this.gl.activeTexture(this.gl.TEXTURE2), this.gl.bindTexture(this.gl.TEXTURE_2D, this.backgroundTexture), this.gl.texImage2D(
|
|
105
|
+
this.gl.TEXTURE_2D,
|
|
106
|
+
0,
|
|
107
|
+
this.gl.RGBA,
|
|
108
|
+
this.gl.RGBA,
|
|
109
|
+
this.gl.UNSIGNED_BYTE,
|
|
110
|
+
t
|
|
111
|
+
), this.lastVideoFrameTime = e);
|
|
64
112
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.config.render.background.width,
|
|
78
|
-
this.config.render.background.height,
|
|
79
|
-
0,
|
|
80
|
-
this.gl.RGBA,
|
|
81
|
-
this.gl.UNSIGNED_BYTE,
|
|
82
|
-
e
|
|
83
|
-
);
|
|
113
|
+
/**
|
|
114
|
+
* Attempt to determine the frame rate of a video element.
|
|
115
|
+
* Tries to extract frame rate from video track settings if available.
|
|
116
|
+
*
|
|
117
|
+
* @param video - The video element to analyze.
|
|
118
|
+
* @returns The video's frame rate in frames per second, or undefined if not determinable.
|
|
119
|
+
*/
|
|
120
|
+
getVideoFrameRate(t) {
|
|
121
|
+
if (t.srcObject instanceof MediaStream) {
|
|
122
|
+
const e = t.srcObject.getVideoTracks()[0];
|
|
123
|
+
if (e != null && e.getSettings)
|
|
124
|
+
return e.getSettings().frameRate;
|
|
84
125
|
}
|
|
85
|
-
this.gl.useProgram(this.program), this.gl.drawArrays(this.gl.TRIANGLES, 0, this.verts.length / 3), this.postDraw();
|
|
86
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Load and initialize the renderer.
|
|
129
|
+
* Currently a no-op for this renderer implementation.
|
|
130
|
+
*
|
|
131
|
+
* @returns Promise that resolves when loading is complete.
|
|
132
|
+
*/
|
|
87
133
|
async load() {
|
|
88
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Clean up resources and destroy the renderer.
|
|
137
|
+
* Currently a no-op for this renderer implementation.
|
|
138
|
+
*/
|
|
89
139
|
destroy() {
|
|
90
140
|
}
|
|
91
141
|
}
|
|
92
142
|
export {
|
|
93
|
-
|
|
143
|
+
l as ReplacementRenderer
|
|
94
144
|
};
|
|
95
145
|
//# sourceMappingURL=replacement-renderer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replacement-renderer.js","sources":["../../../../src/lib/renderer/replacement-renderer.ts"],"sourcesContent":["// replacement-renderer.ts\n\nimport { PipelineConfig, ReplacementConfig } from '../config-types';\nimport { FrameData, LadonOutput } from '../generator';\nimport { compileShader, linkProgram } from '../util';\n\nimport { LadonRenderer } from './ladon-renderer';\nimport { WebGLRenderer } from './webgl-renderer';\n\nexport class ReplacementRenderer extends WebGLRenderer implements LadonRenderer {\n private program: WebGLProgram;\n private background_canvas_ctx: CanvasRenderingContext2D;\n\n protected background_texture!: WebGLTexture;\n protected fragment_shader!: string;\n\n constructor(public output_canvas: HTMLCanvasElement, public readonly config: PipelineConfig) {\n super(output_canvas, config);\n\n const background_canvas_ctx = (\n this.config.render as ReplacementConfig\n ).background.getContext('2d', { willReadFrequently: true });\n if (!background_canvas_ctx) {\n throw new Error('[ladon-ts] Could not get background canvas context 2d');\n }\n this.background_canvas_ctx = background_canvas_ctx;\n\n // Build the fragment shader\n this.fragment_shader = this.buildFragmentShader();\n\n // Compile and link shaders\n const vertex_shader = compileShader(this.gl, this.gl.VERTEX_SHADER, this.vertex_shader_src);\n const fragment_shader = compileShader(\n this.gl,\n this.gl.FRAGMENT_SHADER,\n this.fragment_shader\n );\n\n this.program = linkProgram(this.gl, vertex_shader, fragment_shader);\n this.gl.useProgram(this.program);\n\n this.setSharedUniforms(this.program);\n\n // Set up background texture\n this.setupBackgroundTexture();\n }\n\n protected buildFragmentShader(): string {\n return `${this.fragment_shader_header}\n \nuniform sampler2D u_background;\n\n${this.shared_shader_functions}\n\nvoid main() {\n ${this.getUVCalculation()}\n\n vec3 background = texture(u_background, st).rgb;\n vec3 image = texture(u_image, st).rgb;\n float mask = get_mask(st);\n\n vec3 color = mix(background, image, mask);\n outColor = vec4(color, 1.0);\n}`;\n }\n\n protected setupBackgroundTexture() {\n const backgroundData = this.background_canvas_ctx.getImageData(\n 0,\n 0,\n (this.config.render as ReplacementConfig).background.width,\n (this.config.render as ReplacementConfig).background.height\n );\n\n this.background_texture = this.createTexture(\n this.gl.RGBA,\n (this.config.render as ReplacementConfig).background.width,\n (this.config.render as ReplacementConfig).background.height,\n () => {\n this.gl.texImage2D(\n this.gl.TEXTURE_2D,\n 0,\n this.gl.RGBA,\n backgroundData.width,\n backgroundData.height,\n 0,\n this.gl.RGBA,\n this.gl.UNSIGNED_BYTE,\n backgroundData.data\n );\n }\n );\n\n const u_background = this.gl.getUniformLocation(this.program, 'u_background');\n if (!u_background) {\n throw new Error('[ladon-ts] Error finding \"u_background\" uniform');\n }\n this.gl.activeTexture(this.gl.TEXTURE2);\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.background_texture);\n this.gl.uniform1i(u_background, 2);\n }\n\n public async render(input: FrameData, mask?: LadonOutput): Promise<void> {\n this.uploadVideoStream(input);\n this.uploadMask(mask);\n\n if (!(this.config.render as ReplacementConfig).static) {\n const backgroundData = this.background_canvas_ctx.getImageData(\n 0,\n 0,\n (this.config.render as ReplacementConfig).background.width,\n (this.config.render as ReplacementConfig).background.height\n ).data;\n\n this.gl.activeTexture(this.gl.TEXTURE2);\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.background_texture);\n this.gl.texImage2D(\n this.gl.TEXTURE_2D,\n 0,\n this.gl.RGBA,\n (this.config.render as ReplacementConfig).background.width,\n (this.config.render as ReplacementConfig).background.height,\n 0,\n this.gl.RGBA,\n this.gl.UNSIGNED_BYTE,\n backgroundData\n );\n }\n\n this.gl.useProgram(this.program);\n\n this.gl.drawArrays(this.gl.TRIANGLES, 0, this.verts.length / 3);\n\n this.postDraw();\n }\n\n public async load(): Promise<void> {\n // empty\n }\n\n public destroy(): void {\n // empty\n }\n}\n"],"names":["ReplacementRenderer","WebGLRenderer","output_canvas","config","background_canvas_ctx","vertex_shader","compileShader","fragment_shader","linkProgram","backgroundData","u_background","input","mask"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"replacement-renderer.js","sources":["../../../../src/lib/renderer/replacement-renderer.ts"],"sourcesContent":["import { PipelineConfig, ReplacementConfig } from '../config-types';\nimport { FrameData, LadonOutput } from '../generator';\nimport { compileShader, linkProgram } from '../util';\n\nimport { LadonRenderer } from './ladon-renderer';\nimport { WebGLRenderer } from './webgl-renderer';\n\n/**\n * WebGL renderer implementation for virtual background replacement effects.\n * Handles rendering of video backgrounds with proper frame rate throttling.\n */\nexport class ReplacementRenderer extends WebGLRenderer implements LadonRenderer {\n /** The compiled WebGL shader program used for rendering. */\n private program: WebGLProgram;\n\n /** Timestamp of the last video frame update in milliseconds. */\n private lastVideoFrameTime = 0;\n\n /** Interval between video frame updates in milliseconds. */\n private videoFrameInterval: number;\n\n /** WebGL texture containing the background image or video frame. */\n protected backgroundTexture!: WebGLTexture;\n\n /** Fragment shader source code for the replacement effect. */\n protected fragmentShader!: string;\n\n /**\n * Create a new replacement renderer instance.\n *\n * @param outputCanvas - The canvas element where the rendered output will be displayed.\n * @param config - Pipeline configuration containing render settings and background source.\n */\n constructor(public outputCanvas: HTMLCanvasElement, public readonly config: PipelineConfig) {\n super(outputCanvas, config);\n\n // Initialize video frame interval based on background source\n const renderConfig = this.config.render as ReplacementConfig;\n const backgroundSource = renderConfig.background;\n\n if (backgroundSource instanceof HTMLVideoElement) {\n // Use video's frame rate, or default to 30fps\n const videoFPS = this.getVideoFrameRate(backgroundSource) || 30;\n this.videoFrameInterval = 1000 / videoFPS;\n } else {\n this.videoFrameInterval = 0; // No throttling for static backgrounds\n }\n\n // Build the fragment shader\n this.fragmentShader = this.buildFragmentShader();\n\n // Compile and link shaders\n const vertexShader = compileShader(this.gl, this.gl.VERTEX_SHADER, this.vertex_shader_src);\n const fragmentShader = compileShader(this.gl, this.gl.FRAGMENT_SHADER, this.fragmentShader);\n\n this.program = linkProgram(this.gl, vertexShader, fragmentShader);\n this.gl.useProgram(this.program);\n\n this.setSharedUniforms(this.program);\n\n this.setupBackgroundTexture();\n }\n\n /**\n * Build the fragment shader source code for the replacement effect.\n * Creates a shader that blends the background with the input video based on the mask.\n *\n * @returns The complete fragment shader source code.\n */\n protected buildFragmentShader(): string {\n return `${this.fragment_shader_header}\n \nuniform sampler2D u_background;\n\n${this.shared_shader_functions}\n\nvoid main() {\n ${this.getUVCalculation()}\n\n vec3 background = texture(u_background, st).rgb;\n vec3 image = texture(u_image, st).rgb;\n float mask = get_mask(st);\n\n vec3 color = mix(background, image, mask);\n outColor = vec4(color, 1.0);\n}`;\n }\n\n /**\n * Set up the WebGL texture for the background image or video.\n * Creates and configures the texture that will be used as the replacement background.\n */\n protected setupBackgroundTexture(): void {\n const renderConfig = this.config.render as ReplacementConfig;\n const backgroundSource = renderConfig.background;\n\n this.backgroundTexture = this.createTexture(\n this.gl.RGBA,\n backgroundSource.width,\n backgroundSource.height,\n () => {\n this.gl.texImage2D(\n this.gl.TEXTURE_2D,\n 0,\n this.gl.RGBA,\n this.gl.RGBA,\n this.gl.UNSIGNED_BYTE,\n backgroundSource\n );\n }\n );\n\n const uBackground = this.gl.getUniformLocation(this.program, 'u_background');\n if (!uBackground) {\n throw new Error('[ladon-ts] Error finding \"u_background\" uniform');\n }\n this.gl.activeTexture(this.gl.TEXTURE2);\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.backgroundTexture);\n this.gl.uniform1i(uBackground, 2);\n }\n\n /**\n * Render a frame with the replacement background effect.\n * Combines the input video stream with the background based on the provided mask.\n * Includes frame rate throttling for video backgrounds to prevent stuttering.\n * Canvas backgrounds are updated every frame to support dynamic content.\n *\n * @param input - The input frame data (video element or image data).\n * @param mask - Optional mask data for determining foreground/background blend.\n * @returns Promise that resolves when rendering is complete.\n */\n public async render(input: FrameData, mask?: LadonOutput): Promise<void> {\n this.uploadVideoStream(input);\n this.uploadMask(mask);\n\n const renderConfig = this.config.render as ReplacementConfig;\n const shouldDrawVideoBackground = !renderConfig.static;\n const backgroundSource = renderConfig.background;\n\n if (shouldDrawVideoBackground) {\n if (backgroundSource instanceof HTMLVideoElement) {\n // Throttle video backgrounds to prevent stuttering\n await this.updateVideoBackgroundTexture(backgroundSource);\n } else {\n // Always update canvas backgrounds (like dynamic Game of Life)\n this.gl.activeTexture(this.gl.TEXTURE2);\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.backgroundTexture);\n\n this.gl.texImage2D(\n this.gl.TEXTURE_2D,\n 0,\n this.gl.RGBA,\n this.gl.RGBA,\n this.gl.UNSIGNED_BYTE,\n backgroundSource\n );\n }\n }\n\n this.gl.useProgram(this.program);\n this.gl.drawArrays(this.gl.TRIANGLES, 0, this.verts.length / 3);\n this.postDraw();\n }\n\n /**\n * Throttle video background updates and upload texture only when needed.\n * This prevents stuttering and unnecessary GPU uploads when inference is skipped.\n *\n * @param backgroundSource - The video element to potentially upload.\n * @returns Promise that resolves after appropriate throttling and upload.\n */\n private async updateVideoBackgroundTexture(backgroundSource: HTMLVideoElement): Promise<void> {\n if (this.videoFrameInterval <= 0) return;\n\n const now = performance.now();\n const timeSinceLastUpdate = now - this.lastVideoFrameTime;\n\n if (timeSinceLastUpdate >= this.videoFrameInterval) {\n // Only upload texture when enough time has passed\n this.gl.activeTexture(this.gl.TEXTURE2);\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.backgroundTexture);\n\n this.gl.texImage2D(\n this.gl.TEXTURE_2D,\n 0,\n this.gl.RGBA,\n this.gl.RGBA,\n this.gl.UNSIGNED_BYTE,\n backgroundSource\n );\n\n this.lastVideoFrameTime = now;\n }\n }\n\n /**\n * Attempt to determine the frame rate of a video element.\n * Tries to extract frame rate from video track settings if available.\n *\n * @param video - The video element to analyze.\n * @returns The video's frame rate in frames per second, or undefined if not determinable.\n */\n private getVideoFrameRate(video: HTMLVideoElement): number | undefined {\n // Try to get frame rate from video tracks\n if (video.srcObject instanceof MediaStream) {\n const videoTrack = video.srcObject.getVideoTracks()[0];\n if (videoTrack?.getSettings) {\n return videoTrack.getSettings().frameRate;\n }\n }\n return undefined; // Fall back to default\n }\n\n /**\n * Load and initialize the renderer.\n * Currently a no-op for this renderer implementation.\n *\n * @returns Promise that resolves when loading is complete.\n */\n public async load(): Promise<void> {\n // empty\n }\n\n /**\n * Clean up resources and destroy the renderer.\n * Currently a no-op for this renderer implementation.\n */\n public destroy(): void {\n // empty\n }\n}\n"],"names":["ReplacementRenderer","WebGLRenderer","outputCanvas","config","backgroundSource","videoFPS","vertexShader","compileShader","fragmentShader","linkProgram","uBackground","input","mask","renderConfig","shouldDrawVideoBackground","now","video","videoTrack"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LIB_VERSION = "5.
|
|
1
|
+
export declare const LIB_VERSION = "5.9.1";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sources":["../../../src/lib/version.ts"],"sourcesContent":["export const LIB_VERSION = '5.
|
|
1
|
+
{"version":3,"file":"version.js","sources":["../../../src/lib/version.ts"],"sourcesContent":["export const LIB_VERSION = '5.9.1';\n"],"names":["LIB_VERSION"],"mappings":"AAAO,MAAMA,IAAc;"}
|
|
@@ -33,7 +33,7 @@ export declare class WorkerGenerator implements MaskGenerator {
|
|
|
33
33
|
* @param input - The input image as a Uint8ClampedArray.
|
|
34
34
|
* @param timestamp - The timestamp of the input image.
|
|
35
35
|
*/
|
|
36
|
-
|
|
36
|
+
process(input: Uint8ClampedArray, timestamp: number): Promise<void>;
|
|
37
37
|
/**
|
|
38
38
|
* Destroys the worker and cleans up resources.
|
|
39
39
|
* First it sends a message to the worker to destroy itself,
|
|
@@ -35,8 +35,8 @@ class m {
|
|
|
35
35
|
* @returns A promise that resolves with the mask data when available.
|
|
36
36
|
*/
|
|
37
37
|
async getLadonOutputForTimestamp(e) {
|
|
38
|
-
return new Promise((
|
|
39
|
-
this.pending.set(e,
|
|
38
|
+
return new Promise((n) => {
|
|
39
|
+
this.pending.set(e, n);
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
@@ -48,9 +48,9 @@ class m {
|
|
|
48
48
|
*/
|
|
49
49
|
async load() {
|
|
50
50
|
const e = await s.prepareWorkerConfig(this.config);
|
|
51
|
-
return new Promise((
|
|
51
|
+
return new Promise((n) => {
|
|
52
52
|
s.onInitialized(() => {
|
|
53
|
-
|
|
53
|
+
n();
|
|
54
54
|
}), s.onGenerated((t) => {
|
|
55
55
|
const r = {
|
|
56
56
|
data: t.mask,
|
|
@@ -61,22 +61,22 @@ class m {
|
|
|
61
61
|
warm: !0
|
|
62
62
|
};
|
|
63
63
|
this.lastResult = r, this.newMask = !0;
|
|
64
|
-
const
|
|
65
|
-
|
|
64
|
+
const a = this.pending.get(t.timestamp);
|
|
65
|
+
a && (a(r), this.pending.delete(t.timestamp));
|
|
66
66
|
}), s.onDropped((t) => {
|
|
67
67
|
const r = this.pending.get(t);
|
|
68
68
|
r && (r(this.lastResult), this.pending.delete(t));
|
|
69
69
|
}), s.onLog((t, r) => {
|
|
70
|
-
const
|
|
70
|
+
const a = `[ladon-ts] Message received from worker: ${t}`;
|
|
71
71
|
switch (r) {
|
|
72
72
|
case "warn":
|
|
73
|
-
console.warn(
|
|
73
|
+
console.warn(a);
|
|
74
74
|
break;
|
|
75
75
|
case "error":
|
|
76
|
-
console.error(
|
|
76
|
+
console.error(a);
|
|
77
77
|
break;
|
|
78
78
|
default:
|
|
79
|
-
console.log(
|
|
79
|
+
console.log(a);
|
|
80
80
|
}
|
|
81
81
|
}), (async () => (await (s.getWorker() ?? s.initializeWorker(e)), s.sendInit({
|
|
82
82
|
baseUrl: e.baseUrl,
|
|
@@ -95,8 +95,8 @@ class m {
|
|
|
95
95
|
* @param input - The input image as a Uint8ClampedArray.
|
|
96
96
|
* @param timestamp - The timestamp of the input image.
|
|
97
97
|
*/
|
|
98
|
-
async
|
|
99
|
-
s.sendGenerate(e,
|
|
98
|
+
async process(e, n) {
|
|
99
|
+
s.sendGenerate(e, n);
|
|
100
100
|
}
|
|
101
101
|
/**
|
|
102
102
|
* Destroys the worker and cleans up resources.
|
|
@@ -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 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
|
|
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 process(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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"}
|