@pexip/media-processor 20.3.5 → 22.0.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 (201) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +17 -10
  3. package/api-docs/README.mdx +69 -23
  4. package/api-docs/functions/assertMediaStreamVideoTrack.mdx +13 -0
  5. package/api-docs/functions/createBenchmark.mdx +17 -5
  6. package/api-docs/functions/createFrameCallbackRequest.mdx +8 -2
  7. package/api-docs/functions/createMediaStreamTrackInputProcessAdaptor.mdx +10 -0
  8. package/api-docs/functions/createReadWritableStreamFallbackInputProcessAdaptor.mdx +17 -0
  9. package/api-docs/functions/createReadWritableStreamInputProcessAdaptor.mdx +10 -0
  10. package/api-docs/functions/createVideoProcessor.mdx +3 -3
  11. package/api-docs/functions/createWindowedStats.mdx +23 -0
  12. package/api-docs/functions/getDefaultInputProcessAdaptor.mdx +17 -0
  13. package/api-docs/functions/iou.mdx +19 -0
  14. package/api-docs/functions/isMediaStreamVideoTrack.mdx +13 -0
  15. package/api-docs/functions/labelComponents.mdx +20 -0
  16. package/api-docs/functions/nearestPowerOfTwo.mdx +21 -0
  17. package/api-docs/functions/playVideo.mdx +16 -0
  18. package/api-docs/functions/scoreComponent.mdx +22 -0
  19. package/api-docs/functions/setVideoElementSrc.mdx +14 -0
  20. package/api-docs/interfaces/Analyzer.mdx +4 -0
  21. package/api-docs/interfaces/Benchmark.mdx +26 -2
  22. package/api-docs/interfaces/FrameTransformStats.mdx +9 -0
  23. package/api-docs/interfaces/Gain.mdx +4 -0
  24. package/api-docs/interfaces/ImageSegmenterOptions.mdx +0 -2
  25. package/api-docs/interfaces/InputProcessAdaptor.mdx +28 -0
  26. package/api-docs/interfaces/Process.mdx +8 -3
  27. package/api-docs/interfaces/ProcessEventMute.mdx +6 -0
  28. package/api-docs/interfaces/ProcessWorkerEventOpened.mdx +7 -0
  29. package/api-docs/interfaces/ProcessWorkerEventStats.mdx +6 -0
  30. package/api-docs/interfaces/ProcessWorkerEventUpdated.mdx +7 -0
  31. package/api-docs/interfaces/ProcessorEvent.mdx +1 -1
  32. package/api-docs/interfaces/ProcessorOptions.mdx +0 -1
  33. package/api-docs/interfaces/ProcessorProcessOptions.mdx +3 -1
  34. package/api-docs/interfaces/ProcessorUpdateOptions.mdx +15 -0
  35. package/api-docs/interfaces/ReadWritableStream.mdx +12 -0
  36. package/api-docs/interfaces/Renderer.mdx +2 -2
  37. package/api-docs/interfaces/RendererOptions.mdx +25 -12
  38. package/api-docs/interfaces/Segmenter.mdx +89 -10
  39. package/api-docs/interfaces/SegmenterOptions.mdx +0 -1
  40. package/api-docs/interfaces/SelectionOptions.mdx +15 -0
  41. package/api-docs/interfaces/Stats.mdx +11 -0
  42. package/api-docs/interfaces/VideoProcessor.mdx +49 -5
  43. package/api-docs/interfaces/Weights.mdx +12 -0
  44. package/api-docs/type-aliases/OptionalKeys.mdx +10 -0
  45. package/api-docs/type-aliases/ProcessEvents.mdx +1 -0
  46. package/api-docs/type-aliases/ProcessInputType.mdx +3 -1
  47. package/api-docs/type-aliases/ProcessWorkerEvents.mdx +6 -4
  48. package/api-docs/type-aliases/ProcessorWorkerEvents.mdx +1 -1
  49. package/api-docs/type-aliases/Struct.mdx +9 -0
  50. package/api-docs/variables/BACKGROUND_THRESHOLD.mdx +3 -0
  51. package/api-docs/variables/DOWN_SAMPLE_FACTOR.mdx +3 -0
  52. package/api-docs/variables/EDGE_BLUR_AMOUNT.mdx +1 -1
  53. package/api-docs/variables/EXCLUDE_BYSTANDERS.mdx +3 -0
  54. package/api-docs/variables/FOREGROUND_THRESHOLD.mdx +1 -1
  55. package/api-docs/variables/FRAME_RATE.mdx +1 -1
  56. package/api-docs/variables/ImageFetchError.mdx +3 -0
  57. package/api-docs/variables/LIGHT_WRAP_BLUR_AMOUNT.mdx +3 -0
  58. package/api-docs/variables/LIGHT_WRAP_EDGE_BAND.mdx +3 -0
  59. package/api-docs/variables/LIGHT_WRAP_INTENSITY.mdx +3 -0
  60. package/api-docs/variables/LIGHT_WRAP_TIGHTNESS.mdx +3 -0
  61. package/api-docs/variables/MASK_COMBINE_RATIO.mdx +1 -1
  62. package/api-docs/variables/MORPH_DILATE_RADIUS_PX.mdx +3 -0
  63. package/api-docs/variables/MORPH_ERODE_RADIUS_PX.mdx +3 -0
  64. package/api-docs/variables/MORPH_PASS.mdx +3 -0
  65. package/api-docs/variables/MediaStreamTrackTransferable.mdx +5 -0
  66. package/api-docs/variables/PERSON_CENTER.mdx +3 -0
  67. package/api-docs/variables/RENDERER_OPTIONS_STRUCT.mdx +3 -0
  68. package/api-docs/variables/SIGMA_RANGE.mdx +3 -0
  69. package/api-docs/variables/SIGMA_SPACE.mdx +3 -0
  70. package/api-docs/variables/STRONG_EDGE_BLUR_AMOUNT.mdx +3 -0
  71. package/api-docs/variables/cloneImageRecord.mdx +19 -0
  72. package/api-docs/variables/compareImageRecords.mdx +18 -0
  73. package/api-docs/variables/createRemoteImageBitmap.mdx +1 -1
  74. package/api-docs/variables/getCanvasRenderingContext2D.mdx +14 -0
  75. package/api-docs/variables/getImageSize.mdx +13 -0
  76. package/api-docs/variables/isRenderOptionsKey.mdx +13 -0
  77. package/api-docs/variables/isValidGpuAPI.mdx +13 -0
  78. package/dist/common/backends/webgl/binaryMask.d.ts +8 -0
  79. package/dist/common/backends/webgl/binaryMask.js +95 -0
  80. package/dist/common/backends/webgl/blender.d.ts +2 -1
  81. package/dist/common/backends/webgl/blender.js +18 -15
  82. package/dist/common/backends/webgl/blur.d.ts +1 -1
  83. package/dist/common/backends/webgl/blur.js +23 -16
  84. package/dist/common/backends/webgl/canvas.js +9 -4
  85. package/dist/common/backends/webgl/diffAbsMask.d.ts +10 -0
  86. package/dist/common/backends/webgl/diffAbsMask.js +103 -0
  87. package/dist/common/backends/webgl/geodesicReconstruction.d.ts +13 -0
  88. package/dist/common/backends/webgl/geodesicReconstruction.js +176 -0
  89. package/dist/common/backends/webgl/jointBilateralFilter.d.ts +12 -0
  90. package/dist/common/backends/webgl/jointBilateralFilter.js +108 -0
  91. package/dist/common/backends/webgl/morphErode.d.ts +13 -0
  92. package/dist/common/backends/webgl/morphErode.js +121 -0
  93. package/dist/common/backends/webgl/renderer.js +194 -44
  94. package/dist/common/backends/webgl/replace.d.ts +17 -0
  95. package/dist/common/backends/webgl/replace.js +126 -0
  96. package/dist/common/backends/webgl/selectComponent.d.ts +15 -0
  97. package/dist/common/backends/webgl/selectComponent.js +102 -0
  98. package/dist/common/backends/webgl/smoothingMask.d.ts +3 -3
  99. package/dist/common/backends/webgl/smoothingMask.js +22 -21
  100. package/dist/common/backends/webgl/tentBlur.d.ts +9 -0
  101. package/dist/common/backends/webgl/tentBlur.js +118 -0
  102. package/dist/common/backends/webgl/texture.d.ts +2 -3
  103. package/dist/common/backends/webgl/texture.js +21 -19
  104. package/dist/common/backends/webgl/textureToTexture.d.ts +1 -1
  105. package/dist/common/backends/webgl/textureToTexture.js +19 -16
  106. package/dist/common/backends/webgl/webglUtils.d.ts +7 -1
  107. package/dist/common/backends/webgl/webglUtils.js +72 -10
  108. package/dist/common/backends/webgpu/blender.d.ts +6 -1
  109. package/dist/common/backends/webgpu/blender.js +96 -51
  110. package/dist/common/backends/webgpu/constants.js +1 -1
  111. package/dist/common/backends/webgpu/dualFilterBlur.d.ts +2 -3
  112. package/dist/common/backends/webgpu/dualFilterBlur.js +146 -140
  113. package/dist/common/backends/webgpu/jointBilateralFilter.d.ts +7 -0
  114. package/dist/common/backends/webgpu/jointBilateralFilter.js +162 -0
  115. package/dist/common/backends/webgpu/renderer.js +205 -43
  116. package/dist/common/backends/webgpu/replace.d.ts +11 -0
  117. package/dist/common/backends/webgpu/replace.js +175 -0
  118. package/dist/common/backends/webgpu/resize.d.ts +4 -0
  119. package/dist/common/backends/webgpu/resize.js +76 -0
  120. package/dist/common/backends/webgpu/smoothingMask.d.ts +2 -2
  121. package/dist/common/backends/webgpu/smoothingMask.js +74 -40
  122. package/dist/common/backends/webgpu/tentBlur.d.ts +5 -0
  123. package/dist/common/backends/webgpu/tentBlur.js +131 -0
  124. package/dist/common/backends/webgpu/texture.d.ts +2 -3
  125. package/dist/common/backends/webgpu/texture.js +29 -20
  126. package/dist/common/backends/webgpu/textureToTexture.d.ts +1 -1
  127. package/dist/common/backends/webgpu/textureToTexture.js +28 -15
  128. package/dist/common/backends/webgpu/types.d.ts +2 -0
  129. package/dist/common/backends/webgpu/webgpuUtils.d.ts +24 -4
  130. package/dist/common/backends/webgpu/webgpuUtils.js +60 -11
  131. package/dist/common/constants.d.ts +22 -5
  132. package/dist/common/constants.js +23 -5
  133. package/dist/common/index.d.ts +2 -0
  134. package/dist/common/index.js +2 -0
  135. package/dist/common/tsconfig.tsbuildinfo +1 -1
  136. package/dist/common/typeGuards.d.ts +2 -0
  137. package/dist/common/typeGuards.js +7 -0
  138. package/dist/common/types/media.d.ts +5 -1
  139. package/dist/common/types/messageEvents.d.ts +31 -4
  140. package/dist/common/types/processor.d.ts +6 -4
  141. package/dist/common/types/render.d.ts +147 -15
  142. package/dist/common/types/render.js +23 -1
  143. package/dist/common/types/segmentation.d.ts +0 -12
  144. package/dist/common/types/utils.d.ts +21 -0
  145. package/dist/common/utils.d.ts +29 -2
  146. package/dist/common/utils.js +206 -10
  147. package/dist/main/audio.js +3 -3
  148. package/dist/main/benchUtils.d.ts +35 -3
  149. package/dist/main/benchUtils.js +149 -15
  150. package/dist/main/index.d.ts +1 -1
  151. package/dist/main/index.js +1 -1
  152. package/dist/main/tsconfig.tsbuildinfo +1 -1
  153. package/dist/main/types.d.ts +0 -6
  154. package/dist/main/utils.d.ts +4 -0
  155. package/dist/main/utils.js +31 -0
  156. package/dist/main/video/constants.d.ts +1 -4
  157. package/dist/main/video/constants.js +1 -5
  158. package/dist/main/video/index.d.ts +3 -4
  159. package/dist/main/video/index.js +3 -4
  160. package/dist/main/video/inputProcessAdaptor.d.ts +30 -0
  161. package/dist/main/video/inputProcessAdaptor.js +198 -0
  162. package/dist/main/video/segmenter.d.ts +35 -0
  163. package/dist/main/video/{segmenters/mediapipe.js → segmenter.js} +93 -53
  164. package/dist/main/video/typeGuards.d.ts +2 -0
  165. package/dist/main/video/typeGuards.js +8 -1
  166. package/dist/main/video/types.d.ts +36 -16
  167. package/dist/main/video/utils.d.ts +5 -4
  168. package/dist/main/video/utils.js +49 -44
  169. package/dist/main/video/video.d.ts +29 -7
  170. package/dist/main/video/video.js +323 -28
  171. package/dist/workers/mediaWorker.js +35 -162
  172. package/dist/workers/tsconfig.tsbuildinfo +1 -1
  173. package/dist/worklets/tsconfig.tsbuildinfo +1 -1
  174. package/package.json +13 -12
  175. package/api-docs/functions/createCanvasTransform.mdx +0 -14
  176. package/api-docs/functions/createVideoTrackProcessor.mdx +0 -9
  177. package/api-docs/functions/createVideoTrackProcessorWithFallback.mdx +0 -15
  178. package/api-docs/interfaces/Detector.mdx +0 -81
  179. package/api-docs/interfaces/SegmentationParams.mdx +0 -18
  180. package/api-docs/interfaces/Transform.mdx +0 -62
  181. package/api-docs/type-aliases/ProcessVideoTrack.mdx +0 -15
  182. package/api-docs/type-aliases/SegmentationTransform.mdx +0 -3
  183. package/dist/common/backends/webgl/shaders/blendFragmentShader.d.ts +0 -1
  184. package/dist/common/backends/webgl/shaders/blendFragmentShader.js +0 -21
  185. package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.d.ts +0 -1
  186. package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.js +0 -45
  187. package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.d.ts +0 -1
  188. package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.js +0 -57
  189. package/dist/common/backends/webgl/shaders/textureFragmentShader.d.ts +0 -1
  190. package/dist/common/backends/webgl/shaders/textureFragmentShader.js +0 -16
  191. package/dist/common/backends/webgl/shaders/vertexShader.d.ts +0 -1
  192. package/dist/common/backends/webgl/shaders/vertexShader.js +0 -19
  193. package/dist/main/transformer.d.ts +0 -1
  194. package/dist/main/transformer.js +0 -4
  195. package/dist/main/video/canvasTransform.d.ts +0 -17
  196. package/dist/main/video/canvasTransform.js +0 -253
  197. package/dist/main/video/segmenters/mediapipe.d.ts +0 -22
  198. package/dist/main/video/transformer.d.ts +0 -9
  199. package/dist/main/video/transformer.js +0 -68
  200. package/dist/main/video/videoStreamTrackProcessor.d.ts +0 -20
  201. package/dist/main/video/videoStreamTrackProcessor.js +0 -86
@@ -1,14 +1,16 @@
1
1
  import type { SegmentationModel } from './types/segmentation';
2
- import type { ImageMapIterable } from './types/media';
2
+ import type { ImageMapIterable, ImageRecord } from './types/media';
3
3
  import type { RenderingEvents } from './constants';
4
4
  import type { RenderEventHandlers } from './types/render';
5
+ import type { Stats, SelectionOptions } from './types/utils';
5
6
  export declare const isSegmentationModel: (t: unknown) => t is SegmentationModel;
6
7
  export declare const isRenderingEvents: (t: unknown) => t is RenderingEvents;
7
8
  export declare const toRenderingEvents: (t: unknown) => RenderingEvents;
8
9
  export declare const getErrorMessage: (error: unknown) => string;
9
10
  export declare const createObjectUpdater: <T extends { [K in keyof T]: unknown; }>(original: T) => (another: Partial<T>) => void;
11
+ export declare const ImageFetchError = "ImageFetchError";
10
12
  export declare const createRemoteImageBitmap: (map?: ImageMapIterable) => {
11
- tryFetchingImage: (uri: string) => Promise<ImageBitmap | null>;
13
+ tryFetchingImage: (uri: string, abortSignal: AbortSignal) => Promise<ImageBitmap | null | undefined>;
12
14
  setImage: (uri: string, image: ImageBitmap) => void;
13
15
  getImage: (uri: string) => ImageBitmap | null;
14
16
  removeImage: (uri: string) => boolean;
@@ -16,6 +18,12 @@ export declare const createRemoteImageBitmap: (map?: ImageMapIterable) => {
16
18
  };
17
19
  export declare const clamping: (min: number, max: number) => (value: number) => number;
18
20
  export declare const calculateMaxBlurPass: (height: number) => number;
21
+ /**
22
+ * Coerce any positive number to the nearest power of two within [minPow, maxPow]
23
+ * Example: nearestPowerOfTwo(13) => 16; nearestPowerOfTwo(3.2) => 4
24
+ */
25
+ export declare function nearestPowerOfTwo(n: number, minPow?: number, // 2^0 = 1
26
+ maxPow?: number): number;
19
27
  /**
20
28
  * Calculate the coordinates and size of the source and destination to fit the
21
29
  * target aspect ratio.
@@ -37,3 +45,22 @@ export declare const resize: (fromWidth: number, fromHeight: number, toWidth: nu
37
45
  };
38
46
  export declare const createLazyProps: <T extends Record<string, unknown>>(stateBuilder: { [K in keyof T]: (s: Partial<T>) => T[K]; }) => T;
39
47
  export declare const handleWebGLContextLoss: (canvas: OffscreenCanvas, eventHandlers: RenderEventHandlers, contextLostCb?: () => void) => () => void;
48
+ /**
49
+ * Intersection over Union (IoU), how much two masks overlap as a fraction of either's total.
50
+ * 1.0 = perfect match, 0 = no overlap.
51
+ * Used for stable, robust tracking of the main person between frames,
52
+ * especially with bystander removal and temporal instance switching.
53
+ */
54
+ export declare function iou(a: Uint8Array, b: Uint8Array): number;
55
+ export declare function labelComponents(input: Uint8Array, labels: Int32Array, width: number, height: number): Map<number, Stats>;
56
+ export declare function scoreComponent(stats: Stats, overlap: number, weights: SelectionOptions, width: number, height: number): number;
57
+ export declare const cloneImageRecord: (image: ImageRecord) => Promise<{
58
+ image: ImageBitmap;
59
+ key: string;
60
+ }>;
61
+ /**
62
+ * Compare provided ImageRecord
63
+ *
64
+ * @returns true if the two records have the same key and image, false otherwise
65
+ */
66
+ export declare const compareImageRecords: (a: ImageRecord, b: ImageRecord | undefined) => boolean;
@@ -31,26 +31,52 @@ export const createObjectUpdater = (original) => {
31
31
  }
32
32
  };
33
33
  };
34
+ export const ImageFetchError = 'ImageFetchError';
35
+ const isLikelyImageContentType = (ct) => {
36
+ if (!ct) {
37
+ return false;
38
+ }
39
+ // handle e.g. "image/png; charset=binary"
40
+ return ct.toLowerCase().startsWith('image/');
41
+ };
34
42
  export const createRemoteImageBitmap = (map) => {
35
43
  const cache = new Map(map);
36
44
  const fetchingCache = new Set();
37
- const fetchImage = async (uri) => {
38
- const response = await fetch(uri);
39
- const blob = await response.blob();
40
- const image = await createImageBitmap(blob);
41
- return image;
45
+ const fetchImage = async (uri, abortSignal) => {
46
+ const response = await fetch(uri, { signal: abortSignal });
47
+ if (response.ok &&
48
+ isLikelyImageContentType(response.headers.get('content-type'))) {
49
+ const blob = await response.blob();
50
+ const image = await createImageBitmap(blob);
51
+ return image;
52
+ }
42
53
  };
43
- const tryFetchingImage = async (uri) => {
54
+ const tryFetchingImage = async (uri, abortSignal) => {
44
55
  const image = cache.get(uri);
45
56
  if (!image) {
46
57
  if (fetchingCache.has(uri)) {
47
58
  return null;
48
59
  }
49
60
  fetchingCache.add(uri);
50
- const image = await fetchImage(uri);
51
- cache.set(uri, image);
52
- fetchingCache.delete(uri);
53
- return image;
61
+ try {
62
+ const image = await fetchImage(uri, abortSignal);
63
+ if (image) {
64
+ cache.set(uri, image);
65
+ return image;
66
+ }
67
+ }
68
+ catch (error) {
69
+ if (error instanceof Error) {
70
+ if (error.name !== 'AbortError') {
71
+ throw new Error(ImageFetchError, { cause: error });
72
+ }
73
+ return;
74
+ }
75
+ throw error;
76
+ }
77
+ finally {
78
+ fetchingCache.delete(uri);
79
+ }
54
80
  }
55
81
  return image;
56
82
  };
@@ -81,6 +107,18 @@ export const createRemoteImageBitmap = (map) => {
81
107
  };
82
108
  export const clamping = (min, max) => (value) => Math.min(Math.max(value, min), max);
83
109
  export const calculateMaxBlurPass = (height) => Math.trunc(Math.log2(height));
110
+ /**
111
+ * Coerce any positive number to the nearest power of two within [minPow, maxPow]
112
+ * Example: nearestPowerOfTwo(13) => 16; nearestPowerOfTwo(3.2) => 4
113
+ */
114
+ export function nearestPowerOfTwo(n, minPow = 0, // 2^0 = 1
115
+ maxPow = 4) {
116
+ if (n <= 0) {
117
+ return 1;
118
+ }
119
+ const k = clamping(minPow, maxPow)(calculateMaxBlurPass(n));
120
+ return 2 ** k;
121
+ }
84
122
  /**
85
123
  * Calculate the coordinates and size of the source and destination to fit the
86
124
  * target aspect ratio.
@@ -171,3 +209,161 @@ export const handleWebGLContextLoss = (canvas, eventHandlers, contextLostCb) =>
171
209
  canvas.removeEventListener('webglcontextcreationerror', handleContextCreationError);
172
210
  };
173
211
  };
212
+ /**
213
+ * Intersection over Union (IoU), how much two masks overlap as a fraction of either's total.
214
+ * 1.0 = perfect match, 0 = no overlap.
215
+ * Used for stable, robust tracking of the main person between frames,
216
+ * especially with bystander removal and temporal instance switching.
217
+ */
218
+ export function iou(a, b) {
219
+ let inter = 0;
220
+ let union = 0;
221
+ for (let i = 0; i < a.length; i++) {
222
+ // @ts-expect-error TypeArray will not be undefined
223
+ const av = a[i] >= 128;
224
+ // @ts-expect-error TypeArray will not be undefined
225
+ const bv = b[i] >= 128;
226
+ if (av && bv) {
227
+ inter++;
228
+ }
229
+ if (av || bv) {
230
+ union++;
231
+ }
232
+ }
233
+ return union ? inter / union : 0;
234
+ }
235
+ export function labelComponents(input, labels, width, height) {
236
+ // 8-connected component labeling with union-find
237
+ let nextLabel = 1;
238
+ const parent = [0];
239
+ function find(x) {
240
+ if (parent[x] === undefined) {
241
+ return x;
242
+ }
243
+ if (parent[x] !== x) {
244
+ parent[x] = find(parent[x]);
245
+ }
246
+ return parent[x];
247
+ }
248
+ function unite(a, b) {
249
+ if (!a || !b) {
250
+ return;
251
+ }
252
+ a = find(a);
253
+ b = find(b);
254
+ if (a !== b) {
255
+ parent[b] = a; // Or vice versa, as long as consistent
256
+ }
257
+ }
258
+ // First pass: assign and union with neighbors (left, top, top-left, top-right)
259
+ for (let y = 0; y < height; y++) {
260
+ for (let x = 0; x < width; x++) {
261
+ const i = y * width + x;
262
+ //@ts-expect-error will not be undefined
263
+ if (input[i] < 128) {
264
+ labels[i] = 0;
265
+ continue;
266
+ }
267
+ const left = x > 0 ? labels[i - 1] : 0;
268
+ const top = y > 0 ? labels[i - width] : 0;
269
+ const topLeft = x > 0 && y > 0 ? labels[i - width - 1] : 0;
270
+ const topRight = x < width - 1 && y > 0 ? labels[i - width + 1] : 0;
271
+ // Pick an existing neighbor label if present, else new
272
+ let assign = left || top || topLeft || topRight;
273
+ if (!assign) {
274
+ assign = nextLabel++;
275
+ parent[assign] = assign;
276
+ }
277
+ labels[i] = assign;
278
+ // Union all differing neighbors with the assigned label
279
+ if (left && left !== assign) {
280
+ unite(assign, left);
281
+ }
282
+ if (top && top !== assign) {
283
+ unite(assign, top);
284
+ }
285
+ if (topLeft && topLeft !== assign) {
286
+ unite(assign, topLeft);
287
+ }
288
+ if (topRight && topRight !== assign) {
289
+ unite(assign, topRight);
290
+ }
291
+ }
292
+ }
293
+ // Second pass: compress labels to root ids
294
+ for (let i = 0; i < labels.length; i++) {
295
+ const l = labels[i];
296
+ labels[i] = l ? find(l) : 0;
297
+ }
298
+ // Compute component stats for selection
299
+ const stats = new Map();
300
+ for (let y = 0; y < height; y++) {
301
+ for (let x = 0; x < width; x++) {
302
+ const l = labels[y * width + x];
303
+ if (!l) {
304
+ continue;
305
+ }
306
+ const s = stats.get(l) ?? {
307
+ sumX: 0,
308
+ sumY: 0,
309
+ count: 0,
310
+ minX: width,
311
+ minY: height,
312
+ maxX: -1,
313
+ maxY: -1,
314
+ };
315
+ s.sumX += x;
316
+ s.sumY += y;
317
+ s.count++;
318
+ if (x < s.minX) {
319
+ s.minX = x;
320
+ }
321
+ if (x > s.maxX) {
322
+ s.maxX = x;
323
+ }
324
+ if (y < s.minY) {
325
+ s.minY = y;
326
+ }
327
+ if (y > s.maxY) {
328
+ s.maxY = y;
329
+ }
330
+ stats.set(l, s);
331
+ }
332
+ }
333
+ return stats;
334
+ }
335
+ export function scoreComponent(stats, overlap, weights, width, height) {
336
+ const area = stats.count;
337
+ const heightPx = stats.maxY - stats.minY + 1;
338
+ // const widthPx = stats.maxX - stats.minX + 1;
339
+ const mx = stats.sumX / stats.count;
340
+ const my = stats.sumY / stats.count;
341
+ const d2 = (mx - weights.centerX) ** 2 + (my - weights.centerY) ** 2;
342
+ const centerScore = 1.0 - Math.min(1.0, Math.sqrt(d2) / Math.max(width, height)); // normalize
343
+ // Normalize area/height by frame size to [0..1]
344
+ const areaN = area / (width * height);
345
+ const heightN = heightPx / height;
346
+ const score = weights.iou * overlap +
347
+ weights.area * areaN +
348
+ weights.height * heightN +
349
+ weights.center * centerScore;
350
+ return score;
351
+ }
352
+ export const cloneImageRecord = async (image) => {
353
+ const cloned = await createImageBitmap(image.image);
354
+ return { image: cloned, key: image.key };
355
+ };
356
+ /**
357
+ * Compare provided ImageRecord
358
+ *
359
+ * @returns true if the two records have the same key and image, false otherwise
360
+ */
361
+ export const compareImageRecords = (a, b) => {
362
+ if (!b) {
363
+ return false;
364
+ }
365
+ if (a.key !== b.key || a.image !== b.image) {
366
+ return false;
367
+ }
368
+ return true;
369
+ };
@@ -341,8 +341,8 @@ function createBaseAudioNode(name, create, release) {
341
341
  * @param shouldResetEnabled - Whether or not to enable the cloned track
342
342
  */
343
343
  export const createStreamSourceGraphNode = (mediaStream, shouldResetEnabled = true) => {
344
- let stream = undefined;
345
- let unsubscribeUnmutes = undefined;
344
+ let stream;
345
+ let unsubscribeUnmutes;
346
346
  return createBaseAudioNode('source', context => {
347
347
  stream = createMediaStreamAudioClone(mediaStream);
348
348
  // Enabled the track for the cloned stream
@@ -448,7 +448,7 @@ export const createAnalyzerGraphNode = (options) => {
448
448
  * Create a MediaStreamAudioDestinationNodeInit
449
449
  */
450
450
  export const createStreamDestinationGraphNode = (options) => {
451
- let node = undefined;
451
+ let node;
452
452
  return createBaseAudioNode('destination', context => {
453
453
  node = createMediaStreamAudioDestinationNode(context, options);
454
454
  return [node, node];
@@ -2,12 +2,44 @@ import type { Clock } from './types';
2
2
  type FPS = number;
3
3
  type Timestamp = number;
4
4
  export interface Benchmark {
5
- begin(): void;
6
- end(): void;
5
+ begin(timestamp?: number): void;
6
+ end(timestamp?: number): void;
7
+ reset(): void;
7
8
  calculateFps(): [Timestamp, FPS];
8
9
  }
9
10
  interface BenchmarkOptions {
10
11
  calculationThresholdMS?: number;
12
+ warmupCount?: number;
13
+ averageThreshold?: number;
14
+ bufferSize?: number;
11
15
  }
12
- export declare const createBenchmark: (clock?: Clock, { calculationThresholdMS }?: BenchmarkOptions) => Benchmark;
16
+ /**
17
+ * Creates a benchmarking utility for measuring frame durations and calculating frames per second (FPS).
18
+ *
19
+ * @remarks
20
+ * This function is designed for performance measurement in animation or game loops.
21
+ * It records frame start/end times, ignores an initial warmup period, utilizes a circular buffer
22
+ * to average recent frame durations, and computes a smoothed FPS value on demand.
23
+ * The calculation may only occur if sufficient samples have been gathered and the minimum interval between
24
+ * calculations has elapsed.
25
+ *
26
+ * @param clock - An object with a `now()` method returning the current timestamp (in milliseconds). Defaults to `window.performance`.
27
+ * @param options - Optional configuration options for the benchmark utility.
28
+ * @param options.bufferSize - Size of the circular buffer used to store recent frame durations. Defaults to 1024.
29
+ * @param options.calculationThresholdMS - Minimum interval in milliseconds between FPS recalculations. Defaults to 1000.
30
+ * @param options.warmupCount - Number of initial frames to ignore before starting to record durations. Defaults to 100.
31
+ * @param options.averageThreshold - Minimum number of samples in the buffer required before calculating FPS. Defaults to 100.
32
+ *
33
+ * @returns An object with methods to start/end frame measurements, reset the benchmark, and calculate current FPS.
34
+ */
35
+ export declare const createBenchmark: (clock?: Clock, { bufferSize, calculationThresholdMS, warmupCount, averageThreshold, }?: BenchmarkOptions) => Benchmark;
36
+ export declare const createWindowedStats: (windowSize?: number) => {
37
+ add(value: number): void;
38
+ readonly mean: number;
39
+ readonly variance: number;
40
+ readonly stddev: number;
41
+ readonly stdError: number;
42
+ readonly count: number;
43
+ clear(): void;
44
+ };
13
45
  export {};
@@ -1,35 +1,169 @@
1
- export const createBenchmark = (clock = performance, { calculationThresholdMS = 1000 } = {}) => {
1
+ import { createCircularBuffer } from '@pexip/utils';
2
+ /**
3
+ * Creates a benchmarking utility for measuring frame durations and calculating frames per second (FPS).
4
+ *
5
+ * @remarks
6
+ * This function is designed for performance measurement in animation or game loops.
7
+ * It records frame start/end times, ignores an initial warmup period, utilizes a circular buffer
8
+ * to average recent frame durations, and computes a smoothed FPS value on demand.
9
+ * The calculation may only occur if sufficient samples have been gathered and the minimum interval between
10
+ * calculations has elapsed.
11
+ *
12
+ * @param clock - An object with a `now()` method returning the current timestamp (in milliseconds). Defaults to `window.performance`.
13
+ * @param options - Optional configuration options for the benchmark utility.
14
+ * @param options.bufferSize - Size of the circular buffer used to store recent frame durations. Defaults to 1024.
15
+ * @param options.calculationThresholdMS - Minimum interval in milliseconds between FPS recalculations. Defaults to 1000.
16
+ * @param options.warmupCount - Number of initial frames to ignore before starting to record durations. Defaults to 100.
17
+ * @param options.averageThreshold - Minimum number of samples in the buffer required before calculating FPS. Defaults to 100.
18
+ *
19
+ * @returns An object with methods to start/end frame measurements, reset the benchmark, and calculate current FPS.
20
+ */
21
+ export const createBenchmark = (clock = performance, { bufferSize = 2 ** 10, calculationThresholdMS = 1000, warmupCount = 100, averageThreshold = 100, } = {}) => {
2
22
  const props = {
23
+ buffer: createCircularBuffer(bufferSize, Float32Array),
3
24
  beginTime: 0,
4
25
  endTime: 0,
5
- sumDelta: 0,
6
- count: 0,
7
26
  lastCalculateTime: 0,
8
27
  lastResult: 0,
28
+ warmupCount: 0,
29
+ sum: 0,
9
30
  };
10
31
  return {
11
- begin: () => {
12
- props.beginTime = clock.now();
32
+ begin: (timestamp = clock.now()) => {
33
+ if (props.warmupCount < warmupCount) {
34
+ return;
35
+ }
36
+ props.beginTime = timestamp;
37
+ },
38
+ end: (timestamp = clock.now()) => {
39
+ if (props.warmupCount < warmupCount) {
40
+ props.warmupCount += 1;
41
+ return;
42
+ }
43
+ props.endTime = timestamp;
44
+ if (props.buffer.isFull()) {
45
+ const oldest = props.buffer.peek() ?? 0;
46
+ props.sum -= oldest;
47
+ }
48
+ const elapsed = props.endTime - props.beginTime;
49
+ props.buffer.push(elapsed);
50
+ props.sum += elapsed;
13
51
  },
14
- end: () => {
15
- props.endTime = clock.now();
16
- props.sumDelta += props.endTime - props.beginTime;
17
- ++props.count;
52
+ reset: () => {
53
+ props.buffer.clear();
54
+ props.warmupCount = 0;
55
+ props.beginTime = 0;
56
+ props.endTime = 0;
57
+ props.lastCalculateTime = 0;
18
58
  },
19
59
  calculateFps: () => {
60
+ if (props.warmupCount < warmupCount) {
61
+ return [props.lastCalculateTime, props.lastResult];
62
+ }
20
63
  const calculateTime = clock.now();
21
64
  const lastDuration = calculateTime - props.lastCalculateTime;
22
- if (lastDuration >= calculationThresholdMS) {
23
- const result = props.count === 0
24
- ? 0
25
- : (lastDuration * props.count) / props.sumDelta;
26
- props.sumDelta = 0;
27
- props.count = 0;
65
+ if (props.lastCalculateTime &&
66
+ lastDuration >= calculationThresholdMS &&
67
+ props.buffer.size() > averageThreshold) {
68
+ const result = (lastDuration * props.buffer.size()) / props.sum;
28
69
  props.lastCalculateTime = calculateTime;
29
70
  props.lastResult = result;
30
71
  return [calculateTime, result];
31
72
  }
73
+ props.lastCalculateTime = calculateTime;
32
74
  return [props.lastCalculateTime, props.lastResult];
33
75
  },
34
76
  };
35
77
  };
78
+ function mean(sum, count) {
79
+ if (count > 0) {
80
+ return sum / count;
81
+ }
82
+ return 0;
83
+ }
84
+ function variance(count, mean, sumSquare) {
85
+ if (count < 2) {
86
+ return 0;
87
+ }
88
+ return sumSquare / count - mean * mean;
89
+ }
90
+ function stddev(variance) {
91
+ return Math.sqrt(variance);
92
+ }
93
+ function stdError(count, stddev) {
94
+ if (count === 0) {
95
+ return 0;
96
+ }
97
+ return stddev / Math.sqrt(count);
98
+ }
99
+ export const createWindowedStats = (windowSize = 2 ** 10) => {
100
+ const props = {
101
+ buffer: createCircularBuffer(windowSize, Float32Array),
102
+ sum: 0,
103
+ sumSqr: 0,
104
+ };
105
+ const cache = {
106
+ mean: 0,
107
+ variance: 0,
108
+ stddev: 0,
109
+ stdError: 0,
110
+ };
111
+ const dirty = {
112
+ mean: false,
113
+ variance: false,
114
+ stddev: false,
115
+ stdError: false,
116
+ };
117
+ return {
118
+ add(value) {
119
+ if (props.buffer.isFull()) {
120
+ const oldest = props.buffer.peek() ?? 0;
121
+ props.sum -= oldest;
122
+ props.sumSqr -= oldest * oldest;
123
+ }
124
+ props.buffer.push(value);
125
+ props.sum += value;
126
+ props.sumSqr += value * value;
127
+ dirty.mean = true;
128
+ dirty.variance = true;
129
+ dirty.stddev = true;
130
+ dirty.stdError = true;
131
+ },
132
+ get mean() {
133
+ if (dirty.mean) {
134
+ cache.mean = mean(props.sum, this.count);
135
+ dirty.mean = false;
136
+ }
137
+ return cache.mean;
138
+ },
139
+ get variance() {
140
+ if (dirty.variance) {
141
+ cache.variance = variance(this.count, this.mean, props.sumSqr);
142
+ dirty.variance = false;
143
+ }
144
+ return cache.variance;
145
+ },
146
+ get stddev() {
147
+ if (dirty.stddev) {
148
+ cache.stddev = stddev(this.variance);
149
+ dirty.stddev = false;
150
+ }
151
+ return cache.stddev;
152
+ },
153
+ get stdError() {
154
+ if (dirty.stdError) {
155
+ cache.stdError = stdError(this.count, this.stddev);
156
+ dirty.stdError = false;
157
+ }
158
+ return cache.stdError;
159
+ },
160
+ get count() {
161
+ return props.buffer.size();
162
+ },
163
+ clear() {
164
+ props.buffer.clear();
165
+ props.sum = 0;
166
+ props.sumSqr = 0;
167
+ },
168
+ };
169
+ };
@@ -10,7 +10,7 @@ export * from './types';
10
10
  export * from './math';
11
11
  export * from './process';
12
12
  export * from './typeGuards';
13
- export { createAsyncCallbackLoop } from './utils';
13
+ export { createAsyncCallbackLoop, MediaStreamTrackTransferable } from './utils';
14
14
  export * from './benchUtils';
15
15
  export declare const urls: {
16
16
  denoise: () => URL;
@@ -10,7 +10,7 @@ export * from './types';
10
10
  export * from './math';
11
11
  export * from './process';
12
12
  export * from './typeGuards';
13
- export { createAsyncCallbackLoop } from './utils';
13
+ export { createAsyncCallbackLoop, MediaStreamTrackTransferable } from './utils';
14
14
  export * from './benchUtils';
15
15
  export const urls = {
16
16
  denoise: () => new URL('../worklets/denoise.worklet.js', import.meta.url),