@driftengine/capture 4.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 (199) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +29 -0
  3. package/README.md +68 -0
  4. package/dist/browserFrames.d.ts +31 -0
  5. package/dist/browserFrames.js +105 -0
  6. package/dist/collide.d.ts +59 -0
  7. package/dist/collide.js +92 -0
  8. package/dist/decimate.d.ts +42 -0
  9. package/dist/decimate.js +439 -0
  10. package/dist/delight.d.ts +42 -0
  11. package/dist/delight.js +351 -0
  12. package/dist/delightScene.d.ts +37 -0
  13. package/dist/delightScene.js +183 -0
  14. package/dist/depth.d.ts +44 -0
  15. package/dist/depth.js +53 -0
  16. package/dist/features.d.ts +33 -0
  17. package/dist/features.js +260 -0
  18. package/dist/frames.d.ts +47 -0
  19. package/dist/frames.js +87 -0
  20. package/dist/fusion.d.ts +77 -0
  21. package/dist/fusion.js +152 -0
  22. package/dist/gaussians/adam.d.ts +57 -0
  23. package/dist/gaussians/adam.js +88 -0
  24. package/dist/gaussians/depth.d.ts +8 -0
  25. package/dist/gaussians/depth.js +73 -0
  26. package/dist/gaussians/gradients.d.ts +36 -0
  27. package/dist/gaussians/gradients.js +228 -0
  28. package/dist/gaussians/harmonics.d.ts +46 -0
  29. package/dist/gaussians/harmonics.js +99 -0
  30. package/dist/gaussians/optimise.d.ts +50 -0
  31. package/dist/gaussians/optimise.js +125 -0
  32. package/dist/gaussians/parameters.d.ts +35 -0
  33. package/dist/gaussians/parameters.js +165 -0
  34. package/dist/gaussians/project.d.ts +72 -0
  35. package/dist/gaussians/project.js +137 -0
  36. package/dist/gaussians/projectGradient.d.ts +15 -0
  37. package/dist/gaussians/projectGradient.js +213 -0
  38. package/dist/gaussians/rasterise.d.ts +13 -0
  39. package/dist/gaussians/rasterise.js +65 -0
  40. package/dist/gaussians/refine.d.ts +36 -0
  41. package/dist/gaussians/refine.js +136 -0
  42. package/dist/gaussians/similarity.d.ts +25 -0
  43. package/dist/gaussians/similarity.js +80 -0
  44. package/dist/index.d.ts +101 -0
  45. package/dist/index.js +96 -0
  46. package/dist/marching.d.ts +38 -0
  47. package/dist/marching.js +258 -0
  48. package/dist/masks.d.ts +59 -0
  49. package/dist/masks.js +165 -0
  50. package/dist/math/dense.d.ts +68 -0
  51. package/dist/math/dense.js +294 -0
  52. package/dist/math/schur.d.ts +23 -0
  53. package/dist/math/schur.js +140 -0
  54. package/dist/models/batchNorm.d.ts +17 -0
  55. package/dist/models/batchNorm.js +24 -0
  56. package/dist/models/clipTokenizer.d.ts +40 -0
  57. package/dist/models/clipTokenizer.js +122 -0
  58. package/dist/models/clipTower.d.ts +27 -0
  59. package/dist/models/clipTower.js +45 -0
  60. package/dist/models/depthAnything2.d.ts +38 -0
  61. package/dist/models/depthAnything2.js +163 -0
  62. package/dist/models/depthAnything3.d.ts +26 -0
  63. package/dist/models/depthAnything3.js +171 -0
  64. package/dist/models/dualDpt.d.ts +19 -0
  65. package/dist/models/dualDpt.js +138 -0
  66. package/dist/models/hiera.d.ts +49 -0
  67. package/dist/models/hiera.js +120 -0
  68. package/dist/models/hieraAttention.d.ts +16 -0
  69. package/dist/models/hieraAttention.js +68 -0
  70. package/dist/models/miniature.d.ts +36 -0
  71. package/dist/models/miniature.js +216 -0
  72. package/dist/models/mobileSam.d.ts +41 -0
  73. package/dist/models/mobileSam.js +88 -0
  74. package/dist/models/owlv2.d.ts +49 -0
  75. package/dist/models/owlv2.js +131 -0
  76. package/dist/models/owlv2Detect.d.ts +25 -0
  77. package/dist/models/owlv2Detect.js +80 -0
  78. package/dist/models/owlv2Miniature.d.ts +27 -0
  79. package/dist/models/owlv2Miniature.js +87 -0
  80. package/dist/models/rope.d.ts +23 -0
  81. package/dist/models/rope.js +84 -0
  82. package/dist/models/sam21.d.ts +45 -0
  83. package/dist/models/sam21.js +126 -0
  84. package/dist/models/sam21Miniature.d.ts +37 -0
  85. package/dist/models/sam21Miniature.js +194 -0
  86. package/dist/models/sam21Video.d.ts +30 -0
  87. package/dist/models/sam21Video.js +101 -0
  88. package/dist/models/sam2Choice.d.ts +19 -0
  89. package/dist/models/sam2Choice.js +55 -0
  90. package/dist/models/sam2Decoder.d.ts +36 -0
  91. package/dist/models/sam2Decoder.js +108 -0
  92. package/dist/models/sam2Memories.d.ts +54 -0
  93. package/dist/models/sam2Memories.js +93 -0
  94. package/dist/models/sam2Memory.d.ts +30 -0
  95. package/dist/models/sam2Memory.js +136 -0
  96. package/dist/models/sam2Positions.d.ts +24 -0
  97. package/dist/models/sam2Positions.js +94 -0
  98. package/dist/models/sam2Tracker.d.ts +69 -0
  99. package/dist/models/sam2Tracker.js +172 -0
  100. package/dist/models/samDecoder.d.ts +45 -0
  101. package/dist/models/samDecoder.js +96 -0
  102. package/dist/models/samMiniature.d.ts +43 -0
  103. package/dist/models/samMiniature.js +161 -0
  104. package/dist/models/samPrompt.d.ts +57 -0
  105. package/dist/models/samPrompt.js +160 -0
  106. package/dist/models/seeded.d.ts +16 -0
  107. package/dist/models/seeded.js +48 -0
  108. package/dist/models/tinyVit.d.ts +38 -0
  109. package/dist/models/tinyVit.js +82 -0
  110. package/dist/models/twoWay.d.ts +46 -0
  111. package/dist/models/twoWay.js +67 -0
  112. package/dist/models/vit.d.ts +45 -0
  113. package/dist/models/vit.js +157 -0
  114. package/dist/models/windowAttention.d.ts +25 -0
  115. package/dist/models/windowAttention.js +104 -0
  116. package/dist/poses.d.ts +31 -0
  117. package/dist/poses.js +750 -0
  118. package/dist/prepare.d.ts +30 -0
  119. package/dist/prepare.js +238 -0
  120. package/dist/propose.d.ts +36 -0
  121. package/dist/propose.js +63 -0
  122. package/dist/resample.d.ts +39 -0
  123. package/dist/resample.js +165 -0
  124. package/dist/run.d.ts +3 -0
  125. package/dist/run.js +1 -0
  126. package/dist/scene.d.ts +104 -0
  127. package/dist/scene.js +109 -0
  128. package/dist/segment.d.ts +40 -0
  129. package/dist/segment.js +184 -0
  130. package/dist/testScene.d.ts +55 -0
  131. package/dist/testScene.js +214 -0
  132. package/dist/twoView.d.ts +22 -0
  133. package/dist/twoView.js +435 -0
  134. package/package.json +63 -0
  135. package/src/browserFrames.ts +145 -0
  136. package/src/collide.ts +128 -0
  137. package/src/decimate.ts +510 -0
  138. package/src/delight.ts +427 -0
  139. package/src/delightScene.ts +249 -0
  140. package/src/depth.ts +100 -0
  141. package/src/features.ts +302 -0
  142. package/src/frames.ts +111 -0
  143. package/src/fusion.ts +229 -0
  144. package/src/gaussians/adam.ts +131 -0
  145. package/src/gaussians/depth.ts +87 -0
  146. package/src/gaussians/gradients.ts +285 -0
  147. package/src/gaussians/harmonics.ts +122 -0
  148. package/src/gaussians/optimise.ts +191 -0
  149. package/src/gaussians/parameters.ts +186 -0
  150. package/src/gaussians/project.ts +207 -0
  151. package/src/gaussians/projectGradient.ts +234 -0
  152. package/src/gaussians/rasterise.ts +81 -0
  153. package/src/gaussians/refine.ts +176 -0
  154. package/src/gaussians/similarity.ts +87 -0
  155. package/src/index.ts +225 -0
  156. package/src/marching.ts +358 -0
  157. package/src/masks.ts +201 -0
  158. package/src/math/dense.ts +317 -0
  159. package/src/math/schur.ts +167 -0
  160. package/src/models/batchNorm.ts +57 -0
  161. package/src/models/clipTokenizer.ts +129 -0
  162. package/src/models/clipTower.ts +87 -0
  163. package/src/models/depthAnything2.ts +276 -0
  164. package/src/models/depthAnything3.ts +229 -0
  165. package/src/models/dualDpt.ts +233 -0
  166. package/src/models/hiera.ts +231 -0
  167. package/src/models/hieraAttention.ts +121 -0
  168. package/src/models/miniature.ts +241 -0
  169. package/src/models/mobileSam.ts +135 -0
  170. package/src/models/owlv2.ts +190 -0
  171. package/src/models/owlv2Detect.ts +116 -0
  172. package/src/models/owlv2Miniature.ts +108 -0
  173. package/src/models/rope.ts +111 -0
  174. package/src/models/sam21.ts +182 -0
  175. package/src/models/sam21Miniature.ts +225 -0
  176. package/src/models/sam21Video.ts +154 -0
  177. package/src/models/sam2Choice.ts +62 -0
  178. package/src/models/sam2Decoder.ts +218 -0
  179. package/src/models/sam2Memories.ts +165 -0
  180. package/src/models/sam2Memory.ts +247 -0
  181. package/src/models/sam2Positions.ts +106 -0
  182. package/src/models/sam2Tracker.ts +300 -0
  183. package/src/models/samDecoder.ts +225 -0
  184. package/src/models/samMiniature.ts +190 -0
  185. package/src/models/samPrompt.ts +227 -0
  186. package/src/models/seeded.ts +50 -0
  187. package/src/models/tinyVit.ts +178 -0
  188. package/src/models/twoWay.ts +122 -0
  189. package/src/models/vit.ts +265 -0
  190. package/src/models/windowAttention.ts +155 -0
  191. package/src/poses.ts +894 -0
  192. package/src/prepare.ts +306 -0
  193. package/src/propose.ts +96 -0
  194. package/src/resample.ts +197 -0
  195. package/src/run.ts +7 -0
  196. package/src/scene.ts +141 -0
  197. package/src/segment.ts +220 -0
  198. package/src/testScene.ts +283 -0
  199. package/src/twoView.ts +536 -0
@@ -0,0 +1,161 @@
1
+ import { draw } from './seeded.js';
2
+ export const MINIATURE_MOBILE_SAM = {
3
+ size: 64,
4
+ encoder: {
5
+ embedDims: [8, 16, 24, 320],
6
+ depths: [1, 2, 1, 1],
7
+ heads: [1, 2, 3, 10],
8
+ windows: [7, 3, 4, 2],
9
+ mergeStrides: [2, 2, 1],
10
+ mlpRatio: 4,
11
+ expandRatio: 4,
12
+ neck: 256,
13
+ },
14
+ decoder: {
15
+ dim: 256,
16
+ heads: 8,
17
+ mlp: 32,
18
+ depth: 2,
19
+ downsample: 2,
20
+ masks: 4,
21
+ iouHidden: 16,
22
+ iouDepth: 3,
23
+ maskChannels: 16,
24
+ },
25
+ };
26
+ /** The classes of the miniature's classification head, which nothing runs. */
27
+ export const MINIATURE_SAM_CLASSES = 10;
28
+ /**
29
+ * The original image the prompts are given in: 36 by 48, prepared at 48 by 64 in the encoder's 64
30
+ * square, so the masks are cropped before they come down to it and coordinates are scaled by 4/3.
31
+ */
32
+ export const MINIATURE_SAM_IMAGE = { height: 36, width: 48 };
33
+ /** A point, which a padding point follows; a box; and two points with a box, which none follows. */
34
+ export const MINIATURE_SAM_PROMPTS = {
35
+ point: { points: [[20.5, 14.25, 1]] },
36
+ box: { box: [6, 5, 40.5, 30] },
37
+ both: {
38
+ points: [
39
+ [20.5, 14.25, 1],
40
+ [33, 25.75, 0],
41
+ ],
42
+ box: [6, 5, 40.5, 30],
43
+ },
44
+ };
45
+ /** Every tensor the upstream's encoder, prompt encoder and mask decoder hold. */
46
+ export function miniatureSamCheckpoint(config, seed) {
47
+ const out = new Map();
48
+ const add = (name, shape) => {
49
+ out.set(name, { shape, data: draw(name, shape, seed) });
50
+ };
51
+ const convBn = (p, cout, cin, kernel) => {
52
+ add(`${p}c.weight`, [cout, cin, kernel, kernel]);
53
+ for (const part of ['weight', 'bias', 'running_mean', 'running_var'])
54
+ add(`${p}bn.${part}`, [cout]);
55
+ out.set(`${p}bn.num_batches_tracked`, { shape: [], data: new Float32Array(1) });
56
+ };
57
+ const linear = (p, outputs, inputs) => {
58
+ add(`${p}weight`, [outputs, inputs]);
59
+ add(`${p}bias`, [outputs]);
60
+ };
61
+ const norm = (p, dim) => {
62
+ add(`${p}weight`, [dim]);
63
+ add(`${p}bias`, [dim]);
64
+ };
65
+ const { embedDims: dims, depths, windows, mlpRatio, expandRatio, neck } = config.encoder;
66
+ const e = 'image_encoder.';
67
+ convBn(`${e}patch_embed.seq.0.`, dims[0] / 2, 3, 3);
68
+ convBn(`${e}patch_embed.seq.2.`, dims[0], dims[0] / 2, 3);
69
+ for (let i = 0; i < depths[0]; i += 1) {
70
+ const p = `${e}layers.0.blocks.${i}.`;
71
+ const hidden = dims[0] * expandRatio;
72
+ convBn(`${p}conv1.`, hidden, dims[0], 1);
73
+ convBn(`${p}conv2.`, hidden, 1, 3);
74
+ convBn(`${p}conv3.`, dims[0], hidden, 1);
75
+ }
76
+ for (let stage = 0; stage < 4; stage += 1) {
77
+ const dim = dims[stage];
78
+ if (stage > 0) {
79
+ for (let i = 0; i < depths[stage]; i += 1) {
80
+ const p = `${e}layers.${stage}.blocks.${i}.`;
81
+ const window = windows[stage];
82
+ add(`${p}attn.attention_biases`, [config.encoder.heads[stage], window * window]);
83
+ norm(`${p}attn.norm.`, dim);
84
+ linear(`${p}attn.qkv.`, 3 * dim, dim);
85
+ linear(`${p}attn.proj.`, dim, dim);
86
+ norm(`${p}mlp.norm.`, dim);
87
+ linear(`${p}mlp.fc1.`, dim * mlpRatio, dim);
88
+ linear(`${p}mlp.fc2.`, dim, dim * mlpRatio);
89
+ convBn(`${p}local_conv.`, dim, 1, 3);
90
+ }
91
+ }
92
+ if (stage < 3) {
93
+ const next = dims[stage + 1];
94
+ const p = `${e}layers.${stage}.downsample.`;
95
+ convBn(`${p}conv1.`, next, dim, 1);
96
+ convBn(`${p}conv2.`, next, 1, 3);
97
+ convBn(`${p}conv3.`, next, next, 1);
98
+ }
99
+ }
100
+ norm(`${e}norm_head.`, dims[3]);
101
+ linear(`${e}head.`, MINIATURE_SAM_CLASSES, dims[3]);
102
+ add(`${e}neck.0.weight`, [neck, dims[3], 1, 1]);
103
+ norm(`${e}neck.1.`, neck);
104
+ add(`${e}neck.2.weight`, [neck, neck, 3, 3]);
105
+ norm(`${e}neck.3.`, neck);
106
+ const { dim, masks, mlp, maskChannels: c, iouHidden, iouDepth } = config.decoder;
107
+ const pe = 'prompt_encoder.';
108
+ add(`${pe}pe_layer.positional_encoding_gaussian_matrix`, [2, dim / 2]);
109
+ for (let i = 0; i < 4; i += 1)
110
+ add(`${pe}point_embeddings.${i}.weight`, [1, dim]);
111
+ add(`${pe}not_a_point_embed.weight`, [1, dim]);
112
+ add(`${pe}mask_downscaling.0.weight`, [c / 4, 1, 2, 2]);
113
+ add(`${pe}mask_downscaling.0.bias`, [c / 4]);
114
+ norm(`${pe}mask_downscaling.1.`, c / 4);
115
+ add(`${pe}mask_downscaling.3.weight`, [c, c / 4, 2, 2]);
116
+ add(`${pe}mask_downscaling.3.bias`, [c]);
117
+ norm(`${pe}mask_downscaling.4.`, c);
118
+ add(`${pe}mask_downscaling.6.weight`, [dim, c, 1, 1]);
119
+ add(`${pe}mask_downscaling.6.bias`, [dim]);
120
+ add(`${pe}no_mask_embed.weight`, [1, dim]);
121
+ const d = 'mask_decoder.';
122
+ const t = `${d}transformer.`;
123
+ const attention = (p, inner) => {
124
+ for (const projection of ['q_proj', 'k_proj', 'v_proj'])
125
+ linear(`${p}${projection}.`, inner, dim);
126
+ linear(`${p}out_proj.`, dim, inner);
127
+ };
128
+ const inner = dim / config.decoder.downsample;
129
+ for (let i = 0; i < config.decoder.depth; i += 1) {
130
+ const p = `${t}layers.${i}.`;
131
+ attention(`${p}self_attn.`, dim);
132
+ norm(`${p}norm1.`, dim);
133
+ attention(`${p}cross_attn_token_to_image.`, inner);
134
+ norm(`${p}norm2.`, dim);
135
+ linear(`${p}mlp.lin1.`, mlp, dim);
136
+ linear(`${p}mlp.lin2.`, dim, mlp);
137
+ norm(`${p}norm3.`, dim);
138
+ norm(`${p}norm4.`, dim);
139
+ attention(`${p}cross_attn_image_to_token.`, inner);
140
+ }
141
+ attention(`${t}final_attn_token_to_image.`, inner);
142
+ norm(`${t}norm_final_attn.`, dim);
143
+ add(`${d}iou_token.weight`, [1, dim]);
144
+ add(`${d}mask_tokens.weight`, [masks, dim]);
145
+ add(`${d}output_upscaling.0.weight`, [dim, dim / 4, 2, 2]);
146
+ add(`${d}output_upscaling.0.bias`, [dim / 4]);
147
+ norm(`${d}output_upscaling.1.`, dim / 4);
148
+ add(`${d}output_upscaling.3.weight`, [dim / 4, dim / 8, 2, 2]);
149
+ add(`${d}output_upscaling.3.bias`, [dim / 8]);
150
+ for (let i = 0; i < masks; i += 1) {
151
+ const p = `${d}output_hypernetworks_mlps.${i}.layers.`;
152
+ linear(`${p}0.`, dim, dim);
153
+ linear(`${p}1.`, dim, dim);
154
+ linear(`${p}2.`, dim / 8, dim);
155
+ }
156
+ const sizes = [dim, ...Array.from({ length: iouDepth - 1 }, () => iouHidden), masks];
157
+ for (let i = 0; i + 1 < sizes.length; i += 1) {
158
+ linear(`${d}iou_prediction_head.layers.${i}.`, sizes[i + 1], sizes[i]);
159
+ }
160
+ return out;
161
+ }
@@ -0,0 +1,57 @@
1
+ import type { WeightSource } from '@driftengine/texture';
2
+ export interface SamPrompt {
3
+ /** Points in the original image's pixels, each labelled 1 on the object and 0 off it. */
4
+ readonly points?: readonly (readonly [x: number, y: number, label: 0 | 1])[];
5
+ /** A box in the original image's pixels: left, top, right, bottom. */
6
+ readonly box?: readonly [number, number, number, number];
7
+ }
8
+ /**
9
+ * Where a checkpoint keeps its prompt encoder, and how its processor places a prompt: SAM's and
10
+ * MobileSAM's, or Transformers' SAM 2.
11
+ */
12
+ export interface SamPromptLayout {
13
+ /** The Fourier matrix, `[2, dim / 2]`. */
14
+ readonly gaussian: string;
15
+ /** A learned point embedding by kind — 0 off, 1 on, 2 and 3 a box's corners — as name and row. */
16
+ readonly embedding: (kind: number) => readonly [name: string, row: number];
17
+ readonly notAPoint: string;
18
+ /** Whether a box is followed by a padding point, as SAM 2's is. */
19
+ readonly boxPadding: boolean;
20
+ /**
21
+ * Whether a box's corners come first as points and a padding point always ends the prompt, as a
22
+ * SAM 2 video session orders them — where a prompt of nothing is a point meaning none, padded.
23
+ */
24
+ readonly boxAsPoints: boolean;
25
+ /**
26
+ * The prepared image's size for an original one: the longest side to `size` for SAM, the whole
27
+ * frame to a `size` square for SAM 2 — and whether a coordinate is scaled in double precision, as
28
+ * SAM's predictor does with numpy, or single, as SAM 2's processor does with a tensor.
29
+ */
30
+ readonly prepared: (height: number, width: number, size: number) => readonly [number, number];
31
+ readonly singleScale: boolean;
32
+ }
33
+ export declare const SAM_PROMPT: SamPromptLayout;
34
+ export declare const SAM2_PROMPT: SamPromptLayout;
35
+ /** SAM 2 in a video session: the same encoder, a box as its two corner points first. */
36
+ export declare const SAM2_VIDEO_PROMPT: SamPromptLayout;
37
+ /** The upstream's `get_preprocess_shape`: the longest side to `longest`, each side rounded. */
38
+ export declare function samInputSize(height: number, width: number, longest: number): readonly [number, number];
39
+ /** How many tokens a prompt is, in a layout. */
40
+ export declare function samTokenCount(request: SamPrompt, layout?: SamPromptLayout): number;
41
+ /**
42
+ * `[grid · grid, dim]`: the position of each cell of the embedding's grid, at its centre, as the
43
+ * upstream's `get_dense_pe` lays it out and the decoder reads it as tokens.
44
+ */
45
+ export declare function samGridPositions(gaussian: Float32Array, dim: number, grid: number): Float32Array;
46
+ /**
47
+ * The tokens of `request`, `[samTokenCount(request, layout), dim]`, into `out`, for an original
48
+ * image of `height` by `width` prepared at `size` as `layout`'s processor prepares it.
49
+ */
50
+ export declare function samPromptTokens(weights: WeightSource, dim: number, size: number, request: SamPrompt, height: number, width: number, out: Float32Array, layout?: SamPromptLayout): void;
51
+ /**
52
+ * The upstream's stability score: the share of the pixels above `threshold − offset` that are also
53
+ * above `threshold + offset` — one over the other of the mask thresholded high and thresholded low,
54
+ * since one always lies inside the other. A mask with no pixel above the lower threshold scores
55
+ * NaN, as the upstream's does.
56
+ */
57
+ export declare function stabilityScore(logits: Float32Array, threshold: number, offset: number): number;
@@ -0,0 +1,160 @@
1
+ /**
2
+ * What MobileSAM computes on the host rather than the device: a prompt's tokens, the positions of
3
+ * the embedding's grid, the size an image is prepared at, and a mask's stability.
4
+ *
5
+ * **A position is random Fourier features**: a point in the unit square, taken to [−1, 1], times a
6
+ * fixed Gaussian matrix of the checkpoint's, times 2π, and the sine then the cosine of each of those
7
+ * angles. The angles reach tens of radians, which the device's `sin` is only specified to 2^−11
8
+ * over [−π, π] for, so they are taken here, to single precision, with the engine's reproducible
9
+ * functions and in the upstream's order of roundings. That is also why a prompt is tokens given to
10
+ * the decoder rather than coordinates: a few tokens a prompt, against a grid of positions laid out
11
+ * once per size.
12
+ *
13
+ * **A prompt is the upstream's**: each point shifted to its pixel's centre and labelled on the
14
+ * object or off it, a padding point appended when there is no box, and a box as its two corners.
15
+ * Points and boxes are given in the original image's pixels and scaled to the prepared image as the
16
+ * upstream's predictor scales them.
17
+ */
18
+ import { exactCos, exactSin } from '@driftengine/core';
19
+ const f = Math.fround;
20
+ const TWO_PI = f(2 * Math.PI);
21
+ export const SAM_PROMPT = {
22
+ gaussian: 'prompt_encoder.pe_layer.positional_encoding_gaussian_matrix',
23
+ embedding: (kind) => [`prompt_encoder.point_embeddings.${kind}.weight`, 0],
24
+ notAPoint: 'prompt_encoder.not_a_point_embed.weight',
25
+ boxPadding: false,
26
+ boxAsPoints: false,
27
+ prepared: (height, width, size) => samInputSize(height, width, size),
28
+ singleScale: false,
29
+ };
30
+ export const SAM2_PROMPT = {
31
+ gaussian: 'prompt_encoder.shared_embedding.positional_embedding',
32
+ embedding: (kind) => ['prompt_encoder.point_embed.weight', kind],
33
+ notAPoint: 'prompt_encoder.not_a_point_embed.weight',
34
+ boxPadding: true,
35
+ boxAsPoints: false,
36
+ prepared: (_height, _width, size) => [size, size],
37
+ singleScale: true,
38
+ };
39
+ /** SAM 2 in a video session: the same encoder, a box as its two corner points first. */
40
+ export const SAM2_VIDEO_PROMPT = {
41
+ ...SAM2_PROMPT,
42
+ boxPadding: false,
43
+ boxAsPoints: true,
44
+ };
45
+ /** The upstream's `get_preprocess_shape`: the longest side to `longest`, each side rounded. */
46
+ export function samInputSize(height, width, longest) {
47
+ const scale = (longest * 1.0) / Math.max(height, width);
48
+ return [Math.floor(height * scale + 0.5), Math.floor(width * scale + 0.5)];
49
+ }
50
+ /** How many tokens a prompt is, in a layout. */
51
+ export function samTokenCount(request, layout = SAM_PROMPT) {
52
+ const points = request.points?.length ?? 0;
53
+ if (layout.boxAsPoints) {
54
+ const given = points + (request.box === undefined ? 0 : 2);
55
+ return (given === 0 ? 1 : given) + 1;
56
+ }
57
+ const box = request.box === undefined ? 0 : layout.boxPadding ? 3 : 2;
58
+ return points > 0 ? points + (box === 0 ? 1 : box) : box;
59
+ }
60
+ /* Sines then cosines of a point in the unit square's angles, `2 · half` values from `at`. */
61
+ function fourier(gaussian, half, x, y, out, at) {
62
+ const u = f(2 * x - 1);
63
+ const v = f(2 * y - 1);
64
+ for (let k = 0; k < half; k += 1) {
65
+ const angle = f(TWO_PI * f(u * gaussian[k] + v * gaussian[half + k]));
66
+ out[at + k] = exactSin(angle);
67
+ out[at + half + k] = exactCos(angle);
68
+ }
69
+ }
70
+ function weight(weights, name, length, row = 0) {
71
+ const tensor = weights.get(name);
72
+ if (tensor === undefined || tensor.data.length < (row + 1) * length) {
73
+ throw new Error(`the prompt encoder has no "${name}" of ${length} values in row ${row}`);
74
+ }
75
+ return tensor.data.subarray(row * length, (row + 1) * length);
76
+ }
77
+ /**
78
+ * `[grid · grid, dim]`: the position of each cell of the embedding's grid, at its centre, as the
79
+ * upstream's `get_dense_pe` lays it out and the decoder reads it as tokens.
80
+ */
81
+ export function samGridPositions(gaussian, dim, grid) {
82
+ const out = new Float32Array(grid * grid * dim);
83
+ for (let y = 0; y < grid; y += 1) {
84
+ for (let x = 0; x < grid; x += 1) {
85
+ fourier(gaussian, dim / 2, f((x + 0.5) / grid), f((y + 0.5) / grid), out, (y * grid + x) * dim);
86
+ }
87
+ }
88
+ return out;
89
+ }
90
+ /**
91
+ * The tokens of `request`, `[samTokenCount(request, layout), dim]`, into `out`, for an original
92
+ * image of `height` by `width` prepared at `size` as `layout`'s processor prepares it.
93
+ */
94
+ export function samPromptTokens(weights, dim, size, request, height, width, out, layout = SAM_PROMPT) {
95
+ const half = dim / 2;
96
+ const gaussian = weight(weights, layout.gaussian, 2 * half);
97
+ const [inputHeight, inputWidth] = layout.prepared(height, width, size);
98
+ const scaleX = layout.singleScale ? f(inputWidth / width) : inputWidth / width;
99
+ const scaleY = layout.singleScale ? f(inputHeight / height) : inputHeight / height;
100
+ const place = (value, scale) => f(f(f(layout.singleScale ? f(value) * scale : value * scale) + 0.5) / size);
101
+ let at = 0;
102
+ /* A token: the position of a point at its pixel's centre, or none, and a learned embedding. */
103
+ const token = (x, y, positioned, name, row) => {
104
+ if (positioned) {
105
+ fourier(gaussian, half, place(x, scaleX), place(y, scaleY), out, at);
106
+ }
107
+ else {
108
+ out.fill(0, at, at + dim);
109
+ }
110
+ const learned = weight(weights, name, dim, row);
111
+ for (let c = 0; c < dim; c += 1) {
112
+ out[at + c] = f(out[at + c] + learned[c]);
113
+ }
114
+ at += dim;
115
+ };
116
+ if (layout.boxAsPoints) {
117
+ if (request.box !== undefined) {
118
+ const [left, top, right, bottom] = request.box;
119
+ token(left, top, true, ...layout.embedding(2));
120
+ token(right, bottom, true, ...layout.embedding(3));
121
+ }
122
+ for (const [x, y, label] of request.points ?? [])
123
+ token(x, y, true, ...layout.embedding(label));
124
+ if (request.box === undefined && (request.points?.length ?? 0) === 0) {
125
+ token(0, 0, false, layout.notAPoint, 0);
126
+ }
127
+ token(0, 0, false, layout.notAPoint, 0);
128
+ return;
129
+ }
130
+ for (const [x, y, label] of request.points ?? [])
131
+ token(x, y, true, ...layout.embedding(label));
132
+ if ((request.points?.length ?? 0) > 0 && request.box === undefined) {
133
+ token(0, 0, false, layout.notAPoint, 0);
134
+ }
135
+ if (request.box !== undefined) {
136
+ const [left, top, right, bottom] = request.box;
137
+ token(left, top, true, ...layout.embedding(2));
138
+ token(right, bottom, true, ...layout.embedding(3));
139
+ if (layout.boxPadding)
140
+ token(0, 0, false, layout.notAPoint, 0);
141
+ }
142
+ }
143
+ /**
144
+ * The upstream's stability score: the share of the pixels above `threshold − offset` that are also
145
+ * above `threshold + offset` — one over the other of the mask thresholded high and thresholded low,
146
+ * since one always lies inside the other. A mask with no pixel above the lower threshold scores
147
+ * NaN, as the upstream's does.
148
+ */
149
+ export function stabilityScore(logits, threshold, offset) {
150
+ let inside = 0;
151
+ let union = 0;
152
+ for (let i = 0; i < logits.length; i += 1) {
153
+ const value = logits[i];
154
+ if (value > threshold + offset)
155
+ inside += 1;
156
+ if (value > threshold - offset)
157
+ union += 1;
158
+ }
159
+ return inside / union;
160
+ }
@@ -0,0 +1,16 @@
1
+ export declare function nameSeed(name: string, seed: number): number;
2
+ /**
3
+ * The spread a tensor's values are drawn with, by what the name says it is: a norm's scale near
4
+ * one, a layer scale near a half, a bias small, and a weight at 1.7/√fan-in. At 1/√fan-in the
5
+ * head's twenty convolutions shrank the signal until the depth was its biases — 1.08 to 1.11
6
+ * everywhere, and a slip in a norm's ε moved nothing a test could see; at He's √(6/fan-in) the
7
+ * fusion's residual sums compounded it to depths of 10^33. At 1.7 the depth spans 0.96 to 11.7.
8
+ *
9
+ * **A one-dimensional weight is a norm's scale** in every checkpoint here, whatever its name — a
10
+ * channel norm is often a bare index in a sequence — and is drawn near one.
11
+ *
12
+ * **A batch norm's statistics are drawn as statistics**: a running variance between a half and one
13
+ * and a half, so the scale folded from it stays near one, and a running mean small. Drawn as any
14
+ * other vector, a variance near zero would fold into a scale of hundreds.
15
+ */
16
+ export declare function draw(name: string, shape: readonly number[], seed: number): Float32Array;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A miniature checkpoint's values, drawn from a seed and a tensor's name, so each tensor depends on
3
+ * its name and never on the order the tensors were made in.
4
+ */
5
+ import { mulberry32 } from '@driftengine/core';
6
+ /* FNV-1a over a name, so each tensor's values depend on its name and not on the order made. */
7
+ export function nameSeed(name, seed) {
8
+ let hash = 0x811c9dc5 ^ seed;
9
+ for (let i = 0; i < name.length; i += 1) {
10
+ hash = Math.imul(hash ^ name.charCodeAt(i), 0x01000193);
11
+ }
12
+ return hash >>> 0;
13
+ }
14
+ /**
15
+ * The spread a tensor's values are drawn with, by what the name says it is: a norm's scale near
16
+ * one, a layer scale near a half, a bias small, and a weight at 1.7/√fan-in. At 1/√fan-in the
17
+ * head's twenty convolutions shrank the signal until the depth was its biases — 1.08 to 1.11
18
+ * everywhere, and a slip in a norm's ε moved nothing a test could see; at He's √(6/fan-in) the
19
+ * fusion's residual sums compounded it to depths of 10^33. At 1.7 the depth spans 0.96 to 11.7.
20
+ *
21
+ * **A one-dimensional weight is a norm's scale** in every checkpoint here, whatever its name — a
22
+ * channel norm is often a bare index in a sequence — and is drawn near one.
23
+ *
24
+ * **A batch norm's statistics are drawn as statistics**: a running variance between a half and one
25
+ * and a half, so the scale folded from it stays near one, and a running mean small. Drawn as any
26
+ * other vector, a variance near zero would fold into a scale of hundreds.
27
+ */
28
+ export function draw(name, shape, seed) {
29
+ const next = mulberry32(nameSeed(name, seed));
30
+ const count = shape.reduce((total, d) => total * d, 1);
31
+ const fanIn = shape.slice(1).reduce((total, d) => total * d, 1);
32
+ const norm = (/norm|\.bn\./.test(name) || shape.length === 1) && name.endsWith('.weight');
33
+ const gamma = name.endsWith('.gamma') || name.endsWith('.lambda1');
34
+ const variance = name.endsWith('.running_var');
35
+ const centre = norm || variance ? 1 : gamma ? 0.5 : 0;
36
+ const spread = norm
37
+ ? 0.1
38
+ : gamma
39
+ ? 0.2
40
+ : variance
41
+ ? 0.5
42
+ : name.endsWith('.bias') || name.endsWith('.running_mean')
43
+ ? 0.1
44
+ : shape.length >= 2 && name.endsWith('.weight')
45
+ ? 1.7 / Math.sqrt(fanIn)
46
+ : 0.5;
47
+ return Float32Array.from({ length: count }, () => centre + (next() * 2 - 1) * spread);
48
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * TinyViT as MobileSAM runs it: an image in, a 256-channel embedding at a sixteenth of its size out.
3
+ *
4
+ * **Four stages, the first convolutional.** A patch embedding of two strided convolutions takes the
5
+ * image to a quarter; the first stage is inverted residual blocks, each a pointwise expansion, a
6
+ * depthwise convolution and a pointwise projection; the other three are transformer blocks that
7
+ * attend within windows, each followed by a depthwise convolution over the grid and an MLP. A merge
8
+ * between stages widens the channels and halves the grid — except where the upstream keeps its
9
+ * stride at one, which it decides by the output width and this definition takes as `mergeStrides`.
10
+ *
11
+ * **Every convolution carries a batch norm**, folded in at conversion by `convBatchNorm`, and the
12
+ * transformer blocks attend within windows through `windowAttention`, which pads, partitions and
13
+ * biases them as the upstream does. The blocks' MLP norms take PyTorch's default ε, 1e-5, and the
14
+ * neck's channel norms 1e-6.
15
+ */
16
+ import type { GraphBuilder, Weights } from '@driftengine/texture';
17
+ type Four = readonly [number, number, number, number];
18
+ export interface TinyVitConfig {
19
+ readonly embedDims: Four;
20
+ readonly depths: Four;
21
+ /** Heads per stage; the first stage is convolutional and has none. */
22
+ readonly heads: Four;
23
+ readonly windows: Four;
24
+ /** The stride of each merge between stages. */
25
+ readonly mergeStrides: readonly [number, number, number];
26
+ readonly mlpRatio: number;
27
+ readonly expandRatio: number;
28
+ /** The embedding's channels. */
29
+ readonly neck: number;
30
+ }
31
+ /** The grid the encoder's embedding has for an image `size` across. */
32
+ export declare function tinyVitGrid(config: TinyVitConfig, size: number): number;
33
+ /**
34
+ * `[neck, grid, grid]` from `image`, `[3, size, size]`, normalised and padded as the upstream
35
+ * prepares it; the embedding is written as `output`.
36
+ */
37
+ export declare function tinyVit(weights: Weights, graph: GraphBuilder, config: TinyVitConfig, prefix: string, image: string, size: number, output: string): void;
38
+ export {};
@@ -0,0 +1,82 @@
1
+ import { convBatchNorm } from './batchNorm.js';
2
+ import { windowAttention } from './windowAttention.js';
3
+ const EPSILON = 1e-5;
4
+ const CHANNEL_EPSILON = 1e-6;
5
+ /** The grid the encoder's embedding has for an image `size` across. */
6
+ export function tinyVitGrid(config, size) {
7
+ return config.mergeStrides.reduce((grid, stride) => grid / stride, size / 4);
8
+ }
9
+ /**
10
+ * `[neck, grid, grid]` from `image`, `[3, size, size]`, normalised and padded as the upstream
11
+ * prepares it; the embedding is written as `output`.
12
+ */
13
+ export function tinyVit(weights, graph, config, prefix, image, size, output) {
14
+ const node = graph.node.bind(graph);
15
+ const dims = config.embedDims;
16
+ weights.ignore(`${prefix}norm_head.`);
17
+ weights.ignore(`${prefix}head.`);
18
+ const convBn = (p, x, cin, cout, kernel, stride, groups) => convBatchNorm(weights, graph, p, x, cin, cout, kernel, stride, groups);
19
+ const gelu = (x) => node('gelu', [x]);
20
+ const tokensOf = (map, channels, cells) => node('permute', [node('reshape', [map], { shape: [channels, cells] })], { order: [1, 0] });
21
+ const mapOf = (tokens, channels, rows, cols) => node('reshape', [node('permute', [tokens], { order: [1, 0] })], {
22
+ shape: [channels, rows, cols],
23
+ });
24
+ const norm = (x, p, dim, epsilon) => node('layerNorm', [x, weights.read(`${p}weight`, [dim]), weights.read(`${p}bias`, [dim])], {
25
+ epsilon,
26
+ });
27
+ const linear = (x, p, out, inputs) => node('linear', [x, weights.read(`${p}weight`, [out, inputs]), weights.read(`${p}bias`, [out])]);
28
+ const merge = (p, map, dim, out, stride) => {
29
+ const expanded = gelu(convBn(`${p}conv1.`, map, dim, out, 1, 1, 1));
30
+ const strided = gelu(convBn(`${p}conv2.`, expanded, out, out, 3, stride, out));
31
+ return convBn(`${p}conv3.`, strided, out, out, 1, 1, 1);
32
+ };
33
+ let map = gelu(convBn(`${prefix}patch_embed.seq.0.`, image, 3, dims[0] / 2, 3, 2, 1));
34
+ map = convBn(`${prefix}patch_embed.seq.2.`, map, dims[0] / 2, dims[0], 3, 2, 1);
35
+ let grid = size / 4;
36
+ const hidden = dims[0] * config.expandRatio;
37
+ for (let i = 0; i < config.depths[0]; i += 1) {
38
+ const p = `${prefix}layers.0.blocks.${i}.`;
39
+ const inner = gelu(convBn(`${p}conv1.`, map, dims[0], hidden, 1, 1, 1));
40
+ const spread = gelu(convBn(`${p}conv2.`, inner, hidden, hidden, 3, 1, hidden));
41
+ map = gelu(node('add', [convBn(`${p}conv3.`, spread, hidden, dims[0], 1, 1, 1), map]));
42
+ }
43
+ map = merge(`${prefix}layers.0.downsample.`, map, dims[0], dims[1], config.mergeStrides[0]);
44
+ grid /= config.mergeStrides[0];
45
+ let tokens = tokensOf(map, dims[1], grid * grid);
46
+ for (let stage = 1; stage < 4; stage += 1) {
47
+ const dim = dims[stage];
48
+ const heads = config.heads[stage];
49
+ const window = config.windows[stage];
50
+ for (let i = 0; i < config.depths[stage]; i += 1) {
51
+ const p = `${prefix}layers.${stage}.blocks.${i}.`;
52
+ const attended = node('add', [
53
+ tokens,
54
+ windowAttention(weights, graph, `${p}attn.`, tokens, dim, heads, window, grid, grid),
55
+ ]);
56
+ const local = convBn(`${p}local_conv.`, mapOf(attended, dim, grid, grid), dim, dim, 3, 1, dim);
57
+ const mixed = tokensOf(local, dim, grid * grid);
58
+ const inner = gelu(linear(norm(mixed, `${p}mlp.norm.`, dim, EPSILON), `${p}mlp.fc1.`, dim * config.mlpRatio, dim));
59
+ tokens = node('add', [mixed, linear(inner, `${p}mlp.fc2.`, dim, dim * config.mlpRatio)]);
60
+ }
61
+ if (stage < 3) {
62
+ const stride = config.mergeStrides[stage];
63
+ const next = dims[stage + 1];
64
+ const merged = merge(`${prefix}layers.${stage}.downsample.`, mapOf(tokens, dim, grid, grid), dim, next, stride);
65
+ grid /= stride;
66
+ tokens = tokensOf(merged, next, grid * grid);
67
+ }
68
+ }
69
+ /* The neck: a pointwise and a 3×3 convolution, each followed by a norm over the channels. */
70
+ const channelNorm = (x, p, name) => node('reshape', [
71
+ node('permute', [norm(tokensOf(x, config.neck, grid * grid), p, config.neck, CHANNEL_EPSILON)], { order: [1, 0] }),
72
+ ], { shape: [config.neck, grid, grid] }, name);
73
+ const pointwise = node('conv2d', [
74
+ mapOf(tokens, dims[3], grid, grid),
75
+ weights.read(`${prefix}neck.0.weight`, [config.neck, dims[3], 1, 1]),
76
+ ]);
77
+ const spatial = node('conv2d', [
78
+ channelNorm(pointwise, `${prefix}neck.1.`),
79
+ weights.read(`${prefix}neck.2.weight`, [config.neck, config.neck, 3, 3]),
80
+ ], { padding: 1 });
81
+ channelNorm(spatial, `${prefix}neck.3.`, output);
82
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * SAM's two-way transformer: a prompt's tokens and an image's, each attending to the other — the
3
+ * part SAM, MobileSAM and SAM 2 share, under whichever names their checkpoints give it.
4
+ *
5
+ * **The tokens attend to themselves, then to the image, then pass an MLP; then the image attends back
6
+ * to them.** Queries and keys are given their positions afresh at every attention — the tokens as
7
+ * they were first given, the image by its grid's positions — while values are not. **The first
8
+ * layer's self-attention replaces the tokens rather than adding to them**, and gives them no
9
+ * positions, as the upstream's `skip_first_layer_pe` does; every later one is a residual. Attention
10
+ * between tokens and image narrows to `dim / downsample`. Norms take PyTorch's default ε, 1e-5, and
11
+ * the MLP's activation is ReLU in every checkpoint here.
12
+ *
13
+ * **One definition under two sets of names**, not two definitions: the original's checkpoints say
14
+ * `out_proj`, `norm1` and `lin1` where Transformers' SAM 2 says `o_proj`, `layer_norm1` and
15
+ * `proj_in`, and a copy for each would be two implementations of one network, free to drift.
16
+ */
17
+ import type { GraphBuilder, Weights } from '@driftengine/texture';
18
+ export interface TwoWayConfig {
19
+ readonly dim: number;
20
+ readonly heads: number;
21
+ readonly mlp: number;
22
+ readonly depth: number;
23
+ readonly downsample: number;
24
+ }
25
+ /** What the two checkpoint layouts call the transformer's parts. */
26
+ export interface TwoWayNames {
27
+ /** An attention's output projection. */
28
+ readonly out: string;
29
+ /** A layer's `i`-th norm, 1 to 4. */
30
+ readonly norm: (i: number) => string;
31
+ readonly finalNorm: string;
32
+ /** The MLP's two projections. */
33
+ readonly mlp: readonly [string, string];
34
+ }
35
+ /** The original SAM's names, which MobileSAM keeps. */
36
+ export declare const SAM_NAMES: TwoWayNames;
37
+ /** Transformers' names for SAM 2. */
38
+ export declare const SAM2_NAMES: TwoWayNames;
39
+ /**
40
+ * The transformer under `prefix` over `tokens`, `[count, dim]`, and `image`, `[cells, dim]` in token
41
+ * layout with `positions` of the same shape; answers both, attended.
42
+ */
43
+ export declare function twoWay(weights: Weights, graph: GraphBuilder, prefix: string, config: TwoWayConfig, names: TwoWayNames, tokens: string, image: string, positions: string): {
44
+ readonly queries: string;
45
+ readonly keys: string;
46
+ };