@pexip/media-processor 22.1.0 → 22.3.0

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +34 -0
  3. package/api-docs/README.mdx +38 -0
  4. package/api-docs/functions/createBenchmark.mdx +7 -6
  5. package/api-docs/interfaces/Benchmark.mdx +3 -5
  6. package/api-docs/interfaces/ProcessorEvent.mdx +1 -0
  7. package/api-docs/interfaces/ProcessorOptions.mdx +10 -9
  8. package/api-docs/interfaces/ProcessorUpdateOptions.mdx +10 -9
  9. package/api-docs/interfaces/RendererOptions.mdx +1 -1
  10. package/api-docs/interfaces/SegmenterOptions.mdx +7 -6
  11. package/api-docs/interfaces/TensorMetadata.mdx +11 -0
  12. package/api-docs/type-aliases/TensorLayout.mdx +3 -0
  13. package/api-docs/variables/RENDER_BACKEND.mdx +1 -0
  14. package/api-docs/variables/getCanUseWebGL.mdx +7 -0
  15. package/api-docs/variables/getTensorMetadata.mdx +14 -0
  16. package/dist/common/backends/canvas2d/postprocessor.d.ts +30 -0
  17. package/dist/common/backends/canvas2d/postprocessor.js +88 -0
  18. package/dist/common/backends/canvas2d/preprocessor.d.ts +56 -0
  19. package/dist/common/backends/canvas2d/preprocessor.js +139 -0
  20. package/dist/common/backends/canvas2d/renderer.d.ts +2 -0
  21. package/dist/common/backends/canvas2d/renderer.js +36 -0
  22. package/dist/common/backends/webgl/blur.d.ts +1 -1
  23. package/dist/common/backends/webgl/blur.js +11 -25
  24. package/dist/common/backends/webgl/renderer.js +4 -4
  25. package/dist/common/backends/webgpu/dualFilterBlur.js +93 -114
  26. package/dist/common/backends/webgpu/renderer.js +1 -0
  27. package/dist/common/backends/webgpu/webgpuUtils.d.ts +35 -0
  28. package/dist/common/backends/webgpu/webgpuUtils.js +84 -2
  29. package/dist/common/canvasRenderUtils.d.ts +23 -4
  30. package/dist/common/canvasRenderUtils.js +48 -11
  31. package/dist/common/constants.d.ts +1 -0
  32. package/dist/common/constants.js +1 -0
  33. package/dist/common/inferencer.d.ts +27 -0
  34. package/dist/common/inferencer.js +106 -0
  35. package/dist/common/tsconfig.tsbuildinfo +1 -1
  36. package/dist/common/types/processor.d.ts +1 -0
  37. package/dist/common/types/segmentation.d.ts +8 -0
  38. package/dist/common/utils.d.ts +13 -0
  39. package/dist/common/utils.js +56 -0
  40. package/dist/main/audio.js +20 -2
  41. package/dist/main/benchUtils.d.ts +28 -18
  42. package/dist/main/benchUtils.js +66 -56
  43. package/dist/main/tsconfig.tsbuildinfo +1 -1
  44. package/dist/main/utils.d.ts +9 -0
  45. package/dist/main/utils.js +12 -0
  46. package/dist/main/video/segmenter.d.ts +4 -2
  47. package/dist/main/video/segmenter.js +36 -11
  48. package/dist/main/video/video.js +13 -1
  49. package/dist/workers/mediaWorker.js +23 -23
  50. package/dist/workers/tsconfig.tsbuildinfo +1 -1
  51. package/package.json +7 -10
package/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # Changelog
2
2
 
3
+ ## 22.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2f3c6b7: Add pure CPU processing capability
8
+
9
+ ### Patch Changes
10
+
11
+ - b666166: Shares one WebGPU adapter and device across the renderers, and
12
+ destroys the device when the processor is destroyed. One of each was requested
13
+ for every renderer, so each rebuild of it, on a change of the processing
14
+ dimensions or the backend or on a recovery from a lost context, left the
15
+ memory of the pipelines and the shader modules of the previous one allocated.
16
+ Rebuilding a renderer no longer waits for a device either.
17
+ - 53af4ef: Relinquishes the WebGL context of the processing canvases when they
18
+ are replaced. A context is bound to its canvas for life, so restarting the
19
+ video processing repeatedly, e.g. by changing the effect, could exhaust the
20
+ contexts available to the page, at which point the browser drops the oldest
21
+ ones and the video feed stops.
22
+
23
+ ## 22.2.0
24
+
25
+ ### Minor Changes
26
+
27
+ - 746f6b6: Add Canvas2d rendering backend support: alpha mask converter,
28
+ Canvas2d mask renderer, canvas dimension-aware rendering, and proper backend
29
+ propagation through the video processor pipeline.
30
+ - c101d02: Add inferencer
31
+
32
+ ### Patch Changes
33
+
34
+ - f4af45a: Fix how we calculate the FPS
35
+ - 476acbf: Fix the background image disappearing when it is replaced by another
36
+ image with the same key. `setImage` closes the previously cached bitmap, so
37
+ the render options kept pointing at a closed `ImageBitmap` until the pipeline
38
+ restarted. The currently rendered image is now repointed at the new bitmap.
39
+ - 7c99e29: Fix hardware acceleration feature detection
40
+ - 1b82fd5: Fix dynamic texture allocation issue
41
+ - 265c898: Migrate test runner from Jest to Vitest.
42
+ - a5e8026: Fix "We can't find your microphone" error when changing the
43
+ microphone while sharing content without audio. The audio mixing processor no
44
+ longer tries to create an audio source node from a presentation stream that
45
+ has no audio track, and audio nodes belonging to a stale `AudioContext` are
46
+ recreated instead of being reused.
47
+ - 0878b3b: Fix race conditions in the video processing pipeline so effect
48
+ changes made during processor startup are correctly applied and worker option
49
+ diffs are computed against the previous state.
50
+ - Updated dependencies [5a17991]
51
+ - Updated dependencies [265c898]
52
+ - @pexip/utils@17.4.1
53
+
3
54
  ## 22.1.0
4
55
 
5
56
  ### Patch Changes
package/README.md CHANGED
@@ -178,6 +178,40 @@ You can do it with chrome, [See here][profiling web audio].
178
178
  - No `fetch` API in the `AudioWorkletGlobalScope`
179
179
  - No `TextEncoder/Decoder` APIs in the `AudioWorkletGlobalScope`
180
180
 
181
+ ## CPU pipeline (LiteRT + Canvas2D)
182
+
183
+ By default the media processor uses MediaPipe with a WebGL or WebGPU delegate
184
+ for GPU-accelerated background segmentation. On devices without GPU support, or
185
+ when you explicitly want a pure-CPU path, you can switch to the LiteRT inference
186
+ pipeline.
187
+
188
+ Provide `litertjsCoreBasePath` instead of (or in addition to) `basePath` when
189
+ opening the processor. Its presence is the switch that selects the LiteRT
190
+ pipeline:
191
+
192
+ ```typescript
193
+ processor.open({
194
+ litertjsCoreBasePath: urls.litertjsCoreBasePath, // path to @litertjs/core/wasm/
195
+ imageSegmenterOptions: {
196
+ modelAsset: {
197
+ path: urls.selfieModelPath, // path to a .tflite selfie model
198
+ modelName: 'selfie',
199
+ },
200
+ },
201
+ });
202
+ ```
203
+
204
+ The `@litertjs/core` WASM files are bundled inside `@pexip/media-assets` under
205
+ `@litertjs/core/wasm/`. Point `litertjsCoreBasePath` at this directory relative
206
+ to your web root.
207
+
208
+ ### Supported models
209
+
210
+ | Pipeline | Models | Renderer |
211
+ | ------------------- | --------------------- | ------------- |
212
+ | MediaPipe (default) | `selfie`, `deeplabV3` | WebGL, WebGPU |
213
+ | LiteRT CPU | `selfie` | Canvas2D |
214
+
181
215
  ## References
182
216
 
183
217
  - [MDN - Web Audio API][web audio api]
@@ -178,6 +178,40 @@ You can do it with chrome, [See here][profiling web audio].
178
178
  - No `fetch` API in the `AudioWorkletGlobalScope`
179
179
  - No `TextEncoder/Decoder` APIs in the `AudioWorkletGlobalScope`
180
180
 
181
+ ## CPU pipeline (LiteRT + Canvas2D)
182
+
183
+ By default the media processor uses MediaPipe with a WebGL or WebGPU delegate
184
+ for GPU-accelerated background segmentation. On devices without GPU support, or
185
+ when you explicitly want a pure-CPU path, you can switch to the LiteRT inference
186
+ pipeline.
187
+
188
+ Provide `litertjsCoreBasePath` instead of (or in addition to) `basePath` when
189
+ opening the processor. Its presence is the switch that selects the LiteRT
190
+ pipeline:
191
+
192
+ ```typescript
193
+ processor.open({
194
+ litertjsCoreBasePath: urls.litertjsCoreBasePath, // path to @litertjs/core/wasm/
195
+ imageSegmenterOptions: {
196
+ modelAsset: {
197
+ path: urls.selfieModelPath, // path to a .tflite selfie model
198
+ modelName: 'selfie',
199
+ },
200
+ },
201
+ });
202
+ ```
203
+
204
+ The `@litertjs/core` WASM files are bundled inside `@pexip/media-assets` under
205
+ `@litertjs/core/wasm/`. Point `litertjsCoreBasePath` at this directory relative
206
+ to your web root.
207
+
208
+ ### Supported models
209
+
210
+ | Pipeline | Models | Renderer |
211
+ | ------------------- | --------------------- | ------------- |
212
+ | MediaPipe (default) | `selfie`, `deeplabV3` | WebGL, WebGPU |
213
+ | LiteRT CPU | `selfie` | Canvas2D |
214
+
181
215
  ## References
182
216
 
183
217
  - [MDN - Web Audio API][web audio api]
@@ -234,6 +268,7 @@ A library for media analysis using Web APIs.
234
268
  | [Stats](interfaces/Stats.mdx) | - |
235
269
  | [Weights](interfaces/Weights.mdx) | - |
236
270
  | [SelectionOptions](interfaces/SelectionOptions.mdx) | - |
271
+ | [TensorMetadata](interfaces/TensorMetadata.mdx) | - |
237
272
  | [Benchmark](interfaces/Benchmark.mdx) | - |
238
273
  | [Point](interfaces/Point.mdx) | Interface for Point consist of coordinates x and y |
239
274
  | [Size](interfaces/Size.mdx) | - |
@@ -284,6 +319,7 @@ A library for media analysis using Web APIs.
284
319
  | [IncludeMessageEventDataType](type-aliases/IncludeMessageEventDataType.mdx) | - |
285
320
  | [TupleOf](type-aliases/TupleOf.mdx) | From https://github.com/Microsoft/TypeScript/issues/26223#issuecomment-674500430 |
286
321
  | [OptionalKeys](type-aliases/OptionalKeys.mdx) | - |
322
+ | [TensorLayout](type-aliases/TensorLayout.mdx) | - |
287
323
  | [Canvas](type-aliases/Canvas.mdx) | - |
288
324
  | [Unsubscribe](type-aliases/Unsubscribe.mdx) | Unsubscribe the subscription |
289
325
  | [AudioBufferFloats](type-aliases/AudioBufferFloats.mdx) | Same as [AudioBuffer](https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer) Or the return from [AnalyserNode.getFloatFrequencyData()](https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/getFloatFrequencyData) |
@@ -371,6 +407,8 @@ A library for media analysis using Web APIs.
371
407
  | [handleWebGLContextLoss](variables/handleWebGLContextLoss.mdx) | - |
372
408
  | [cloneImageRecord](variables/cloneImageRecord.mdx) | - |
373
409
  | [compareImageRecords](variables/compareImageRecords.mdx) | Compare provided ImageRecord |
410
+ | [getCanUseWebGL](variables/getCanUseWebGL.mdx) | - |
411
+ | [getTensorMetadata](variables/getTensorMetadata.mdx) | - |
374
412
  | [urls](variables/urls.mdx) | - |
375
413
  | [SILENT\_THRESHOLD](variables/SILENT_THRESHOLD.mdx) | Default silent threshold At least one LSB 16-bit data (compare is on absolute value). |
376
414
  | [MONO\_THRESHOLD](variables/MONO_THRESHOLD.mdx) | Default mono detection threshold Data must be identical within one LSB 16-bit to be identified as mono. |
@@ -15,12 +15,13 @@ Creates a benchmarking utility for measuring frame durations and calculating fra
15
15
 
16
16
  [`Benchmark`](../interfaces/Benchmark.mdx)
17
17
 
18
- An object with methods to start/end frame measurements, reset the benchmark, and calculate current FPS.
18
+ An object with methods to start/end frame measurements, reset the benchmark, and get current FPS.
19
19
 
20
20
  ## Remarks
21
21
 
22
- This function is designed for performance measurement in animation or game loops.
23
- It records frame start/end times, ignores an initial warmup period, utilizes a circular buffer
24
- to average recent frame durations, and computes a smoothed FPS value on demand.
25
- The calculation may only occur if sufficient samples have been gathered and the minimum interval between
26
- calculations has elapsed.
22
+ Uses a time-windowed sliding approach: each frame event stores both its timestamp and
23
+ processing duration. Only events within the configured time window contribute to FPS.
24
+ FPS is derived as `windowMs / averageProcessingTime` for events in the window.
25
+
26
+ The `begin()`/`end()` API measures processing time per frame explicitly.
27
+ Call `fps()` at any time to get the current FPS — no priming or two-call pattern needed.
@@ -48,14 +48,12 @@ reset(): void;
48
48
 
49
49
  ***
50
50
 
51
- ### calculateFps()
51
+ ### fps()
52
52
 
53
53
  ```ts
54
- calculateFps(): [number, number];
54
+ fps(): number;
55
55
  ```
56
56
 
57
57
  #### Returns
58
58
 
59
- ```ts
60
- [number, number]
61
- ```
59
+ `number`
@@ -6,3 +6,4 @@
6
6
  | <a id="error"></a> `error?` | `Error` |
7
7
  | <a id="message"></a> `message?` | `string` |
8
8
  | <a id="track"></a> `track?` | `MediaStreamTrack` |
9
+ | <a id="recoverable"></a> `recoverable?` | `boolean` |
@@ -4,12 +4,13 @@
4
4
 
5
5
  ## Properties
6
6
 
7
- | Property | Type | Inherited from |
8
- | ------ | ------ | ------ |
9
- | <a id="renderoptions"></a> `renderOptions?` | [`RendererOptions`](RendererOptions.mdx) | - |
10
- | <a id="backgroundimage"></a> `backgroundImage?` | [`ImageRecord`](ImageRecord.mdx) | - |
11
- | <a id="restart"></a> `restart?` | `boolean` | - |
12
- | <a id="basepath"></a> `basePath?` | `string` | [`SegmenterOptions`](SegmenterOptions.mdx).[`basePath`](SegmenterOptions.mdx#basepath) |
13
- | <a id="processingwidth"></a> `processingWidth?` | `number` | [`SegmenterOptions`](SegmenterOptions.mdx).[`processingWidth`](SegmenterOptions.mdx#processingwidth) |
14
- | <a id="processingheight"></a> `processingHeight?` | `number` | [`SegmenterOptions`](SegmenterOptions.mdx).[`processingHeight`](SegmenterOptions.mdx#processingheight) |
15
- | <a id="imagesegmenteroptions"></a> `imageSegmenterOptions?` | [`ImageSegmenterOptions`](ImageSegmenterOptions.mdx) | [`SegmenterOptions`](SegmenterOptions.mdx).[`imageSegmenterOptions`](SegmenterOptions.mdx#imagesegmenteroptions) |
7
+ | Property | Type | Description | Inherited from |
8
+ | ------ | ------ | ------ | ------ |
9
+ | <a id="renderoptions"></a> `renderOptions?` | [`RendererOptions`](RendererOptions.mdx) | - | - |
10
+ | <a id="backgroundimage"></a> `backgroundImage?` | [`ImageRecord`](ImageRecord.mdx) | - | - |
11
+ | <a id="restart"></a> `restart?` | `boolean` | - | - |
12
+ | <a id="basepath"></a> `basePath?` | `string` | MediaPipe fileset base URL. | [`SegmenterOptions`](SegmenterOptions.mdx).[`basePath`](SegmenterOptions.mdx#basepath) |
13
+ | <a id="litertjscorebasepath"></a> `litertjsCoreBasePath?` | `string` | LiteRT WASM base URL. Providing this selects the CPU inference pipeline (LiteRT + Canvas2D) instead of the default MediaPipe GPU pipeline. | [`SegmenterOptions`](SegmenterOptions.mdx).[`litertjsCoreBasePath`](SegmenterOptions.mdx#litertjscorebasepath) |
14
+ | <a id="processingwidth"></a> `processingWidth?` | `number` | - | [`SegmenterOptions`](SegmenterOptions.mdx).[`processingWidth`](SegmenterOptions.mdx#processingwidth) |
15
+ | <a id="processingheight"></a> `processingHeight?` | `number` | - | [`SegmenterOptions`](SegmenterOptions.mdx).[`processingHeight`](SegmenterOptions.mdx#processingheight) |
16
+ | <a id="imagesegmenteroptions"></a> `imageSegmenterOptions?` | [`ImageSegmenterOptions`](ImageSegmenterOptions.mdx) | - | [`SegmenterOptions`](SegmenterOptions.mdx).[`imageSegmenterOptions`](SegmenterOptions.mdx#imagesegmenteroptions) |
@@ -6,12 +6,13 @@
6
6
 
7
7
  ## Properties
8
8
 
9
- | Property | Type | Inherited from |
10
- | ------ | ------ | ------ |
11
- | <a id="backgroundimage"></a> `backgroundImage?` | [`ImageRecord`](ImageRecord.mdx) | [`ProcessorOptions`](ProcessorOptions.mdx).[`backgroundImage`](ProcessorOptions.mdx#backgroundimage) |
12
- | <a id="restart"></a> `restart?` | `boolean` | [`ProcessorOptions`](ProcessorOptions.mdx).[`restart`](ProcessorOptions.mdx#restart) |
13
- | <a id="basepath"></a> `basePath?` | `string` | [`SegmenterOptions`](SegmenterOptions.mdx).[`basePath`](SegmenterOptions.mdx#basepath) |
14
- | <a id="processingwidth"></a> `processingWidth?` | `number` | [`SegmenterOptions`](SegmenterOptions.mdx).[`processingWidth`](SegmenterOptions.mdx#processingwidth) |
15
- | <a id="processingheight"></a> `processingHeight?` | `number` | [`SegmenterOptions`](SegmenterOptions.mdx).[`processingHeight`](SegmenterOptions.mdx#processingheight) |
16
- | <a id="imagesegmenteroptions"></a> `imageSegmenterOptions?` | [`ImageSegmenterOptions`](ImageSegmenterOptions.mdx) | [`SegmenterOptions`](SegmenterOptions.mdx).[`imageSegmenterOptions`](SegmenterOptions.mdx#imagesegmenteroptions) |
17
- | <a id="renderoptions"></a> `renderOptions?` | `Partial`\<[`RendererOptions`](RendererOptions.mdx)\> | - |
9
+ | Property | Type | Description | Inherited from |
10
+ | ------ | ------ | ------ | ------ |
11
+ | <a id="backgroundimage"></a> `backgroundImage?` | [`ImageRecord`](ImageRecord.mdx) | - | [`ProcessorOptions`](ProcessorOptions.mdx).[`backgroundImage`](ProcessorOptions.mdx#backgroundimage) |
12
+ | <a id="restart"></a> `restart?` | `boolean` | - | [`ProcessorOptions`](ProcessorOptions.mdx).[`restart`](ProcessorOptions.mdx#restart) |
13
+ | <a id="basepath"></a> `basePath?` | `string` | MediaPipe fileset base URL. | [`SegmenterOptions`](SegmenterOptions.mdx).[`basePath`](SegmenterOptions.mdx#basepath) |
14
+ | <a id="litertjscorebasepath"></a> `litertjsCoreBasePath?` | `string` | LiteRT WASM base URL. Providing this selects the CPU inference pipeline (LiteRT + Canvas2D) instead of the default MediaPipe GPU pipeline. | [`SegmenterOptions`](SegmenterOptions.mdx).[`litertjsCoreBasePath`](SegmenterOptions.mdx#litertjscorebasepath) |
15
+ | <a id="processingwidth"></a> `processingWidth?` | `number` | - | [`SegmenterOptions`](SegmenterOptions.mdx).[`processingWidth`](SegmenterOptions.mdx#processingwidth) |
16
+ | <a id="processingheight"></a> `processingHeight?` | `number` | - | [`SegmenterOptions`](SegmenterOptions.mdx).[`processingHeight`](SegmenterOptions.mdx#processingheight) |
17
+ | <a id="imagesegmenteroptions"></a> `imageSegmenterOptions?` | [`ImageSegmenterOptions`](ImageSegmenterOptions.mdx) | - | [`SegmenterOptions`](SegmenterOptions.mdx).[`imageSegmenterOptions`](SegmenterOptions.mdx#imagesegmenteroptions) |
18
+ | <a id="renderoptions"></a> `renderOptions?` | `Partial`\<[`RendererOptions`](RendererOptions.mdx)\> | - | - |
@@ -9,7 +9,7 @@ All values are stable per-frame and can be safely updated in real-time.
9
9
  | <a id="backgroundbluramount"></a> `backgroundBlurAmount` | `number` | Amount of background blur (radius in px): [0,9]. Recommend: 3–6 for portraits. Higher may be slow and over-blurred. **Default** `3` |
10
10
  | <a id="edgebluramount"></a> `edgeBlurAmount` | `number` | Edge blur for mask boundary antialiasing (radius in px): [0,9]. Small values (1–5) smooth mask stair-steps; large values soften edges more visibly. **Default** `4` |
11
11
  | <a id="effects"></a> `effects` | `"blur" \| "none" \| "overlay"` | Effects to apply: "none", "blur", or "overlay". "blur" = background blur. "overlay" = replace BG with backgroundImageUrl. |
12
- | <a id="backend"></a> `backend` | `"webgl" \| "webgpu"` | Backend to render background effects |
12
+ | <a id="backend"></a> `backend` | `"webgl" \| "webgpu" \| "canvas2d"` | Backend to render background effects |
13
13
  | <a id="foregroundthreshold"></a> `foregroundThreshold` | `number` | [0,1] Upper mask confidence: all mask pixels \>= this are considered definite foreground. Adjust to make mask edges tighter or more permissive for foreground inclusion. Example: 0.8 ("mask \> 0.8 = sure foreground"). Must always be \>= backgroundThreshold. **Default** `0.85` |
14
14
  | <a id="backgroundthreshold"></a> `backgroundThreshold` | `number` | [0,1] Lower mask confidence: all mask pixels \<= this are considered definite background. Adjust to make mask edges tighter or more permissive for background inclusion. Example: 0.3 ("mask \< 0.3 = sure background"). **Default** `0.35` |
15
15
  | <a id="sigmaspace"></a> `sigmaSpace` | `number` | Bilateral filter: sigma in pixel space for mask edge snapping. Controls the neighborhood/pixel distance—higher is larger window (5–10 typical). **Default** `5` |
@@ -4,9 +4,10 @@
4
4
 
5
5
  ## Properties
6
6
 
7
- | Property | Type |
8
- | ------ | ------ |
9
- | <a id="basepath"></a> `basePath?` | `string` |
10
- | <a id="processingwidth"></a> `processingWidth?` | `number` |
11
- | <a id="processingheight"></a> `processingHeight?` | `number` |
12
- | <a id="imagesegmenteroptions"></a> `imageSegmenterOptions?` | [`ImageSegmenterOptions`](ImageSegmenterOptions.mdx) |
7
+ | Property | Type | Description |
8
+ | ------ | ------ | ------ |
9
+ | <a id="basepath"></a> `basePath?` | `string` | MediaPipe fileset base URL. |
10
+ | <a id="litertjscorebasepath"></a> `litertjsCoreBasePath?` | `string` | LiteRT WASM base URL. Providing this selects the CPU inference pipeline (LiteRT + Canvas2D) instead of the default MediaPipe GPU pipeline. |
11
+ | <a id="processingwidth"></a> `processingWidth?` | `number` | - |
12
+ | <a id="processingheight"></a> `processingHeight?` | `number` | - |
13
+ | <a id="imagesegmenteroptions"></a> `imageSegmenterOptions?` | [`ImageSegmenterOptions`](ImageSegmenterOptions.mdx) | - |
@@ -0,0 +1,11 @@
1
+ ## Properties
2
+
3
+ | Property | Type |
4
+ | ------ | ------ |
5
+ | <a id="name"></a> `name` | `string` |
6
+ | <a id="shape"></a> `shape` | readonly `number[]` |
7
+ | <a id="width"></a> `width` | `number` |
8
+ | <a id="height"></a> `height` | `number` |
9
+ | <a id="layout"></a> `layout` | [`TensorLayout`](../type-aliases/TensorLayout.mdx) |
10
+ | <a id="dtype"></a> `dtype` | `"float32" \| "int32" \| "uint8"` |
11
+ | <a id="channels"></a> `channels` | `number` |
@@ -0,0 +1,3 @@
1
+ ```ts
2
+ type TensorLayout = "nhwc" | "nchw";
3
+ ```
@@ -8,3 +8,4 @@ const RENDER_BACKEND: object;
8
8
  | ------ | ------ |
9
9
  | <a id="webgl"></a> `WebGL` | `"webgl"` |
10
10
  | <a id="webgpu"></a> `WebGPU` | `"webgpu"` |
11
+ | <a id="canvas2d"></a> `Canvas2D` | `"canvas2d"` |
@@ -0,0 +1,7 @@
1
+ ```ts
2
+ const getCanUseWebGL: () => boolean;
3
+ ```
4
+
5
+ ## Returns
6
+
7
+ `boolean`
@@ -0,0 +1,14 @@
1
+ ```ts
2
+ const getTensorMetadata: (details, supportedChannels?) => TensorMetadata;
3
+ ```
4
+
5
+ ## Parameters
6
+
7
+ | Parameter | Type |
8
+ | ------ | ------ |
9
+ | `details` | `TensorDetails` |
10
+ | `supportedChannels?` | readonly `number[]` |
11
+
12
+ ## Returns
13
+
14
+ [`TensorMetadata`](../interfaces/TensorMetadata.mdx)
@@ -0,0 +1,30 @@
1
+ import type { TypedArray, TensorDetails, Tensor } from '../../inferencer';
2
+ import type { TensorMetadata } from '../../utils';
3
+ /**
4
+ * Extract person confidence into an 8-bit mask.
5
+ *
6
+ * For two-channel outputs, background is category 0 and person is category 1.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * extractPersonConfidence(new Float32Array([0.1, 0.9]), mask, metadata);
11
+ * ```
12
+ */
13
+ export declare const extractPersonConfidence: (source: TypedArray, target: Uint8ClampedArray, metadata: TensorMetadata) => Uint8ClampedArray;
14
+ /**
15
+ * Create a reusable converter from LiteRT selfie output to a mask canvas.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const output = createSelfieOutputProcessor(details, 256, 144);
20
+ * const maskCanvas = await output.process(inferenceOutputs);
21
+ * ```
22
+ */
23
+ export declare const createSelfieOutputProcessor: (output: TensorDetails, createCanvas?: (width: number, height: number) => OffscreenCanvas, createImageData?: (width: number, height: number) => ImageData) => {
24
+ metadata: TensorMetadata;
25
+ process: (inferenceOutputs: Record<string, Tensor>) => Promise<OffscreenCanvas>;
26
+ release: () => void;
27
+ readonly canvas: OffscreenCanvas | undefined;
28
+ readonly maskBuffer: Uint8ClampedArray<ArrayBufferLike> | undefined;
29
+ readonly imageData: ImageData | undefined;
30
+ };
@@ -0,0 +1,88 @@
1
+ import { getTensorMetadata } from '../../utils';
2
+ import { createOffscreenCanvas, getCanvasRenderingContext2D, } from '../../canvasRenderUtils';
3
+ /**
4
+ * Extract person confidence into an 8-bit mask.
5
+ *
6
+ * For two-channel outputs, background is category 0 and person is category 1.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * extractPersonConfidence(new Float32Array([0.1, 0.9]), mask, metadata);
11
+ * ```
12
+ */
13
+ export const extractPersonConfidence = (source, target, metadata) => {
14
+ const pixelCount = metadata.width * metadata.height;
15
+ if (target.length !== pixelCount ||
16
+ source.length !== pixelCount * metadata.channels) {
17
+ throw new Error('Output tensor and mask buffer sizes do not match');
18
+ }
19
+ const isFloat = source instanceof Float32Array;
20
+ for (let pixelIndex = 0; pixelIndex < pixelCount; pixelIndex++) {
21
+ const sourceIndex = metadata.layout === 'nhwc'
22
+ ? pixelIndex * metadata.channels + metadata.channels - 1
23
+ : (metadata.channels - 1) * pixelCount + pixelIndex;
24
+ const confidence = source[sourceIndex] ?? 0;
25
+ target[pixelIndex] = isFloat
26
+ ? Math.round(Math.min(1, Math.max(0, confidence)) * 255)
27
+ : confidence;
28
+ }
29
+ return target;
30
+ };
31
+ /**
32
+ * Create a reusable converter from LiteRT selfie output to a mask canvas.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * const output = createSelfieOutputProcessor(details, 256, 144);
37
+ * const maskCanvas = await output.process(inferenceOutputs);
38
+ * ```
39
+ */
40
+ export const createSelfieOutputProcessor = (output, createCanvas = (width, height) => createOffscreenCanvas(width, height), createImageData = (width, height) => new ImageData(width, height)) => {
41
+ const metadata = getTensorMetadata(output, [1, 2]);
42
+ let canvas = createCanvas(metadata.width, metadata.height);
43
+ let context = getCanvasRenderingContext2D(canvas);
44
+ let maskBuffer = new Uint8ClampedArray(metadata.width * metadata.height);
45
+ let imageData = createImageData(metadata.width, metadata.height);
46
+ let rgbaBuffer = imageData.data;
47
+ const process = async (inferenceOutputs) => {
48
+ if (!canvas || !context || !maskBuffer || !rgbaBuffer || !imageData) {
49
+ throw new Error('The output processor has been released');
50
+ }
51
+ const output = inferenceOutputs[metadata.name];
52
+ if (!output) {
53
+ throw new Error(`Missing model output: ${metadata.name}`);
54
+ }
55
+ const values = await output.data();
56
+ extractPersonConfidence(values, maskBuffer, metadata);
57
+ for (let pixelIndex = 0; pixelIndex < maskBuffer.length; pixelIndex++) {
58
+ rgbaBuffer[pixelIndex * 4 + 3] = maskBuffer[pixelIndex] ?? 0;
59
+ }
60
+ context.putImageData(imageData, 0, 0);
61
+ return canvas;
62
+ };
63
+ const release = () => {
64
+ context = undefined;
65
+ maskBuffer = undefined;
66
+ rgbaBuffer = undefined;
67
+ imageData = undefined;
68
+ if (canvas) {
69
+ canvas.width = 0;
70
+ canvas.height = 0;
71
+ }
72
+ canvas = undefined;
73
+ };
74
+ return {
75
+ metadata,
76
+ process,
77
+ release,
78
+ get canvas() {
79
+ return canvas;
80
+ },
81
+ get maskBuffer() {
82
+ return maskBuffer;
83
+ },
84
+ get imageData() {
85
+ return imageData;
86
+ },
87
+ };
88
+ };
@@ -0,0 +1,56 @@
1
+ import { Tensor, type TensorDetails } from '../../inferencer';
2
+ import { type TensorLayout } from '../../utils';
3
+ export { getTensorMetadata } from '../../utils';
4
+ /**
5
+ * Convert packed RGBA pixels to normalized model RGB input.
6
+ *
7
+ * Float inputs use the selfie model metadata normalization `(value - 0) / 255`.
8
+ * Uint8 inputs retain their original channel values.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * convertRgbaToRgbTensor(
13
+ * new Uint8Array([255, 128, 0, 255]),
14
+ * new Float32Array(3),
15
+ * 'nhwc',
16
+ * );
17
+ * ```
18
+ */
19
+ export declare const convertRgbaToRgbTensor: (rgba: Uint8Array, target: Float32Array | Uint8Array, layout: TensorLayout) => Float32Array | Uint8Array;
20
+ /**
21
+ * Calculate a centered crop that keeps model input and output masks aligned.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * getModelResizeCoordinates(1280, 720, 256, 256);
26
+ * ```
27
+ */
28
+ export declare const getModelResizeCoordinates: (sourceWidth: number, sourceHeight: number, modelWidth: number, modelHeight: number, processingWidth?: number, processingHeight?: number) => {
29
+ sx: number;
30
+ sy: number;
31
+ sw: number;
32
+ sh: number;
33
+ dx: number;
34
+ dy: number;
35
+ dw: number;
36
+ dh: number;
37
+ };
38
+ /**
39
+ * Create a reusable Canvas2D preprocessor for a LiteRT model input.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const preprocessor = createCanvas2dPreprocessor(inputDetails);
44
+ * const input = await preprocessor.process(videoFrame);
45
+ * ```
46
+ */
47
+ export declare const createCanvas2dPreprocessor: (details: TensorDetails, processingWidth?: number, processingHeight?: number) => {
48
+ metadata: import("../..").TensorMetadata;
49
+ process: (frame: VideoFrame) => Promise<{
50
+ [x: string]: Tensor;
51
+ }>;
52
+ release: () => void;
53
+ readonly canvas: OffscreenCanvas | undefined;
54
+ readonly pixelBuffer: Uint8Array<ArrayBufferLike> | undefined;
55
+ readonly tensorStorage: Float32Array<ArrayBuffer> | Uint8Array<ArrayBuffer>;
56
+ };
@@ -0,0 +1,139 @@
1
+ import { Tensor } from '../../inferencer';
2
+ import { createOffscreenCanvas, getCanvasRenderingContext2D, } from '../../canvasRenderUtils';
3
+ import { getImageSize } from '../../canvasRenderUtils';
4
+ import { resize, getTensorMetadata } from '../../utils';
5
+ export { getTensorMetadata } from '../../utils';
6
+ /**
7
+ * Convert packed RGBA pixels to normalized model RGB input.
8
+ *
9
+ * Float inputs use the selfie model metadata normalization `(value - 0) / 255`.
10
+ * Uint8 inputs retain their original channel values.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * convertRgbaToRgbTensor(
15
+ * new Uint8Array([255, 128, 0, 255]),
16
+ * new Float32Array(3),
17
+ * 'nhwc',
18
+ * );
19
+ * ```
20
+ */
21
+ export const convertRgbaToRgbTensor = (rgba, target, layout) => {
22
+ const pixelCount = rgba.length / 4;
23
+ if (!Number.isInteger(pixelCount) || target.length !== pixelCount * 3) {
24
+ throw new Error('RGBA and RGB tensor buffer sizes do not match');
25
+ }
26
+ const normalize = target instanceof Float32Array;
27
+ for (let pixelIndex = 0; pixelIndex < pixelCount; pixelIndex++) {
28
+ const rgbaOffset = pixelIndex * 4;
29
+ for (let channelIndex = 0; channelIndex < 3; channelIndex++) {
30
+ const targetIndex = layout === 'nhwc'
31
+ ? pixelIndex * 3 + channelIndex
32
+ : channelIndex * pixelCount + pixelIndex;
33
+ const value = rgba[rgbaOffset + channelIndex] ?? 0;
34
+ target[targetIndex] = normalize ? value / 255 : value;
35
+ }
36
+ }
37
+ return target;
38
+ };
39
+ /**
40
+ * Calculate a centered crop that keeps model input and output masks aligned.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * getModelResizeCoordinates(1280, 720, 256, 256);
45
+ * ```
46
+ */
47
+ export const getModelResizeCoordinates = (sourceWidth, sourceHeight, modelWidth, modelHeight, processingWidth = modelWidth, processingHeight = modelHeight) => {
48
+ if (sourceWidth <= 0 ||
49
+ sourceHeight <= 0 ||
50
+ modelWidth <= 0 ||
51
+ modelHeight <= 0 ||
52
+ processingWidth <= 0 ||
53
+ processingHeight <= 0) {
54
+ throw new Error('Source, processing, and model dimensions must be positive');
55
+ }
56
+ const sourceCrop = resize(sourceWidth, sourceHeight, processingWidth, processingHeight);
57
+ const scale = Math.min(modelWidth / processingWidth, modelHeight / processingHeight);
58
+ const destinationWidth = processingWidth * scale;
59
+ const destinationHeight = processingHeight * scale;
60
+ return {
61
+ sx: sourceCrop.sx,
62
+ sy: sourceCrop.sy,
63
+ sw: sourceCrop.sw,
64
+ sh: sourceCrop.sh,
65
+ dx: (modelWidth - destinationWidth) / 2,
66
+ dy: (modelHeight - destinationHeight) / 2,
67
+ dw: destinationWidth,
68
+ dh: destinationHeight,
69
+ };
70
+ };
71
+ /**
72
+ * Create a reusable Canvas2D preprocessor for a LiteRT model input.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * const preprocessor = createCanvas2dPreprocessor(inputDetails);
77
+ * const input = await preprocessor.process(videoFrame);
78
+ * ```
79
+ */
80
+ export const createCanvas2dPreprocessor = (details, processingWidth, processingHeight) => {
81
+ const metadata = getTensorMetadata(details);
82
+ let canvas = createOffscreenCanvas(metadata.width, metadata.height);
83
+ let context = getCanvasRenderingContext2D(canvas, { alpha: false });
84
+ let pixelBuffer = new Uint8Array(metadata.width * metadata.height * 4);
85
+ let tensorStorage = metadata.dtype === 'float32'
86
+ ? new Float32Array(metadata.width * metadata.height * 3)
87
+ : new Uint8Array(metadata.width * metadata.height * 3);
88
+ let tensor;
89
+ const process = async (frame) => {
90
+ if (!canvas || !context || !pixelBuffer) {
91
+ throw new Error('The preprocessor has been released');
92
+ }
93
+ const sourceSize = getImageSize(frame);
94
+ const coordinates = getModelResizeCoordinates(sourceSize.width, sourceSize.height, metadata.width, metadata.height, processingWidth ?? metadata.width, processingHeight ?? metadata.height);
95
+ context.clearRect(0, 0, metadata.width, metadata.height);
96
+ context.drawImage(frame, coordinates.sx, coordinates.sy, coordinates.sw, coordinates.sh, coordinates.dx, coordinates.dy, coordinates.dw, coordinates.dh);
97
+ const resizedFrame = new VideoFrame(canvas, {
98
+ // VideoFrame timestamp is in microseconds
99
+ // https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame/timestamp
100
+ timestamp: frame.timestamp ?? performance.now() * 1000,
101
+ });
102
+ try {
103
+ await resizedFrame.copyTo(pixelBuffer, { format: 'RGBA' });
104
+ }
105
+ finally {
106
+ resizedFrame.close();
107
+ }
108
+ convertRgbaToRgbTensor(pixelBuffer, tensorStorage, metadata.layout);
109
+ tensor?.delete();
110
+ tensor = new Tensor(tensorStorage, Array.from(metadata.shape));
111
+ return { [metadata.name]: tensor };
112
+ };
113
+ const release = () => {
114
+ tensor?.delete();
115
+ tensor = undefined;
116
+ tensorStorage = new Uint8Array();
117
+ pixelBuffer = undefined;
118
+ context = undefined;
119
+ if (canvas) {
120
+ canvas.width = 0;
121
+ canvas.height = 0;
122
+ }
123
+ canvas = undefined;
124
+ };
125
+ return {
126
+ metadata,
127
+ process,
128
+ release,
129
+ get canvas() {
130
+ return canvas;
131
+ },
132
+ get pixelBuffer() {
133
+ return pixelBuffer;
134
+ },
135
+ get tensorStorage() {
136
+ return tensorStorage;
137
+ },
138
+ };
139
+ };