@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.
- package/CHANGELOG.md +51 -0
- package/README.md +34 -0
- package/api-docs/README.mdx +38 -0
- package/api-docs/functions/createBenchmark.mdx +7 -6
- package/api-docs/interfaces/Benchmark.mdx +3 -5
- package/api-docs/interfaces/ProcessorEvent.mdx +1 -0
- package/api-docs/interfaces/ProcessorOptions.mdx +10 -9
- package/api-docs/interfaces/ProcessorUpdateOptions.mdx +10 -9
- package/api-docs/interfaces/RendererOptions.mdx +1 -1
- package/api-docs/interfaces/SegmenterOptions.mdx +7 -6
- package/api-docs/interfaces/TensorMetadata.mdx +11 -0
- package/api-docs/type-aliases/TensorLayout.mdx +3 -0
- package/api-docs/variables/RENDER_BACKEND.mdx +1 -0
- package/api-docs/variables/getCanUseWebGL.mdx +7 -0
- package/api-docs/variables/getTensorMetadata.mdx +14 -0
- package/dist/common/backends/canvas2d/postprocessor.d.ts +30 -0
- package/dist/common/backends/canvas2d/postprocessor.js +88 -0
- package/dist/common/backends/canvas2d/preprocessor.d.ts +56 -0
- package/dist/common/backends/canvas2d/preprocessor.js +139 -0
- package/dist/common/backends/canvas2d/renderer.d.ts +2 -0
- package/dist/common/backends/canvas2d/renderer.js +36 -0
- package/dist/common/backends/webgl/blur.d.ts +1 -1
- package/dist/common/backends/webgl/blur.js +11 -25
- package/dist/common/backends/webgl/renderer.js +4 -4
- package/dist/common/backends/webgpu/dualFilterBlur.js +93 -114
- package/dist/common/backends/webgpu/renderer.js +1 -0
- package/dist/common/backends/webgpu/webgpuUtils.d.ts +35 -0
- package/dist/common/backends/webgpu/webgpuUtils.js +84 -2
- package/dist/common/canvasRenderUtils.d.ts +23 -4
- package/dist/common/canvasRenderUtils.js +48 -11
- package/dist/common/constants.d.ts +1 -0
- package/dist/common/constants.js +1 -0
- package/dist/common/inferencer.d.ts +27 -0
- package/dist/common/inferencer.js +106 -0
- package/dist/common/tsconfig.tsbuildinfo +1 -1
- package/dist/common/types/processor.d.ts +1 -0
- package/dist/common/types/segmentation.d.ts +8 -0
- package/dist/common/utils.d.ts +13 -0
- package/dist/common/utils.js +56 -0
- package/dist/main/audio.js +20 -2
- package/dist/main/benchUtils.d.ts +28 -18
- package/dist/main/benchUtils.js +66 -56
- package/dist/main/tsconfig.tsbuildinfo +1 -1
- package/dist/main/utils.d.ts +9 -0
- package/dist/main/utils.js +12 -0
- package/dist/main/video/segmenter.d.ts +4 -2
- package/dist/main/video/segmenter.js +36 -11
- package/dist/main/video/video.js +13 -1
- package/dist/workers/mediaWorker.js +23 -23
- package/dist/workers/tsconfig.tsbuildinfo +1 -1
- 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]
|
package/api-docs/README.mdx
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]
|
|
@@ -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
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
|
@@ -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="
|
|
14
|
-
| <a id="
|
|
15
|
-
| <a id="
|
|
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="
|
|
15
|
-
| <a id="
|
|
16
|
-
| <a id="
|
|
17
|
-
| <a id="
|
|
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="
|
|
11
|
-
| <a id="
|
|
12
|
-
| <a id="
|
|
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,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
|
+
};
|