@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,67 @@
1
+ /** The original SAM's names, which MobileSAM keeps. */
2
+ export const SAM_NAMES = {
3
+ out: 'out_proj',
4
+ norm: (i) => `norm${i}`,
5
+ finalNorm: 'norm_final_attn',
6
+ mlp: ['lin1', 'lin2'],
7
+ };
8
+ /** Transformers' names for SAM 2. */
9
+ export const SAM2_NAMES = {
10
+ out: 'o_proj',
11
+ norm: (i) => `layer_norm${i}`,
12
+ finalNorm: 'layer_norm_final_attn',
13
+ mlp: ['proj_in', 'proj_out'],
14
+ };
15
+ const EPSILON = 1e-5;
16
+ /**
17
+ * The transformer under `prefix` over `tokens`, `[count, dim]`, and `image`, `[cells, dim]` in token
18
+ * layout with `positions` of the same shape; answers both, attended.
19
+ */
20
+ export function twoWay(weights, graph, prefix, config, names, tokens, image, positions) {
21
+ const node = graph.node.bind(graph);
22
+ const { dim, heads } = config;
23
+ const read = weights.read.bind(weights);
24
+ const linear = (x, p, out, inputs) => node('linear', [x, read(`${p}weight`, [out, inputs]), read(`${p}bias`, [out])]);
25
+ const norm = (x, p) => node('layerNorm', [x, read(`${p}weight`, [dim]), read(`${p}bias`, [dim])], {
26
+ epsilon: EPSILON,
27
+ });
28
+ const attend = (p, q, k, v, narrow) => {
29
+ const inner = dim / narrow;
30
+ const attended = node('attention', [
31
+ linear(q, `${p}q_proj.`, inner, dim),
32
+ linear(k, `${p}k_proj.`, inner, dim),
33
+ linear(v, `${p}v_proj.`, inner, dim),
34
+ ], { heads });
35
+ return linear(attended, `${p}${names.out}.`, dim, inner);
36
+ };
37
+ const [into, outOf] = names.mlp;
38
+ let queries = tokens;
39
+ let keys = image;
40
+ for (let i = 0; i < config.depth; i += 1) {
41
+ const p = `${prefix}layers.${i}.`;
42
+ if (i === 0) {
43
+ queries = attend(`${p}self_attn.`, queries, queries, queries, 1);
44
+ }
45
+ else {
46
+ const q = node('add', [queries, tokens]);
47
+ queries = node('add', [queries, attend(`${p}self_attn.`, q, q, queries, 1)]);
48
+ }
49
+ queries = norm(queries, `${p}${names.norm(1)}.`);
50
+ let q = node('add', [queries, tokens]);
51
+ let k = node('add', [keys, positions]);
52
+ const toImage = attend(`${p}cross_attn_token_to_image.`, q, k, keys, config.downsample);
53
+ queries = norm(node('add', [queries, toImage]), `${p}${names.norm(2)}.`);
54
+ const hidden = node('relu', [linear(queries, `${p}mlp.${into}.`, config.mlp, dim)]);
55
+ const mixed = linear(hidden, `${p}mlp.${outOf}.`, dim, config.mlp);
56
+ queries = norm(node('add', [queries, mixed]), `${p}${names.norm(3)}.`);
57
+ q = node('add', [queries, tokens]);
58
+ k = node('add', [keys, positions]);
59
+ const toTokens = attend(`${p}cross_attn_image_to_token.`, k, q, queries, config.downsample);
60
+ keys = norm(node('add', [keys, toTokens]), `${p}${names.norm(4)}.`);
61
+ }
62
+ const q = node('add', [queries, tokens]);
63
+ const k = node('add', [keys, positions]);
64
+ const final = attend(`${prefix}final_attn_token_to_image.`, q, k, keys, config.downsample);
65
+ queries = norm(node('add', [queries, final]), `${prefix}${names.finalNorm}.`);
66
+ return { queries, keys };
67
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Depth Anything 3's backbone: DINOv2 as that model runs it, over one view or several at once.
3
+ *
4
+ * **Plain DINOv2 until `altStart`, and something else after it**, which is why this is not a
5
+ * generic vision transformer. From that block the class token of every view is replaced by a
6
+ * learned camera token — one for the first view, the reference, and another for the rest — and
7
+ * blocks alternate: an even one attends within each view, an odd one across all views at once.
8
+ * From `qkNormStart` each head's queries and keys are layer-normed, and from `ropeStart` they are
9
+ * turned by a two-dimensional rotary embedding: by the patch's row and column within a view, and
10
+ * across views by one position shared by every patch, so the global blocks see no geometry but
11
+ * which view a token belongs to.
12
+ *
13
+ * **Each tap is the last within-view output beside the current one**, `2·dim` wide, with only the
14
+ * second half normed — the upstream's `cat_token` — and its first token kept apart as that view's
15
+ * camera token, unnormed, which the camera decoder reads.
16
+ *
17
+ * **The positional embeddings are resized as the upstream resizes them**: bicubically, by the scale
18
+ * factor `(grid + 0.1)/trained`, whose inverse is the step a sample moves — not the ratio of the
19
+ * sizes. Only a square image at the trained grid skips it. The block norms take ε = 1e-6 and every
20
+ * other norm here PyTorch's default, 1e-5, which is a difference the checkpoint was trained with.
21
+ *
22
+ * What it gives up: the upstream reorders views around a chosen reference once there are three or
23
+ * more; here the first view given is the reference, and choosing it is the caller's.
24
+ */
25
+ import type { GraphBuilder, Weights } from '@driftengine/texture';
26
+ export interface VitConfig {
27
+ readonly dim: number;
28
+ readonly heads: number;
29
+ readonly depth: number;
30
+ readonly hidden: number;
31
+ readonly patch: number;
32
+ /** The patch grid the positional embeddings were trained at: 518 / 14 = 37. */
33
+ readonly trainedGrid: number;
34
+ readonly taps: readonly number[];
35
+ readonly altStart: number;
36
+ readonly qkNormStart: number;
37
+ readonly ropeStart: number;
38
+ }
39
+ export interface VitOutput {
40
+ /** Per tap, per view: the patch tokens, `[rows · cols, 2 · dim]`. */
41
+ readonly features: readonly (readonly string[])[];
42
+ /** Per tap: every view's camera token, `[views, 2 · dim]`. */
43
+ readonly cameras: readonly string[];
44
+ }
45
+ export declare function vitBackbone(weights: Weights, graph: GraphBuilder, config: VitConfig, prefix: string, images: readonly string[], rows: number, cols: number): VitOutput;
@@ -0,0 +1,157 @@
1
+ import { applyRope, ropeConstants } from './rope.js';
2
+ /** Block norms are trained with ε = 1e-6; the rest keep PyTorch's default. */
3
+ const BLOCK_EPSILON = 1e-6;
4
+ const DEFAULT_EPSILON = 1e-5;
5
+ export function vitBackbone(weights, graph, config, prefix, images, rows, cols) {
6
+ const { dim, heads } = config;
7
+ const headDim = dim / heads;
8
+ const patches = rows * cols;
9
+ const tokens = patches + 1;
10
+ const w = (name, shape) => weights.read(`${prefix}${name}`, shape);
11
+ const node = graph.node.bind(graph);
12
+ /* Positions, resized to this grid unless it is the trained one on a square image. */
13
+ const trained = config.trainedGrid;
14
+ const table = node('reshape', [w('pos_embed', [1, trained * trained + 1, dim])], {
15
+ shape: [trained * trained + 1, dim],
16
+ });
17
+ let positions = table;
18
+ if (rows !== trained || cols !== trained) {
19
+ const grid = node('permute', [
20
+ node('reshape', [node('slice', [table], { axis: 0, start: 1, end: trained * trained + 1 })], {
21
+ shape: [trained, trained, dim],
22
+ }),
23
+ ], { order: [2, 0, 1] });
24
+ const resized = node('resize', [grid], {
25
+ height: rows,
26
+ width: cols,
27
+ mode: 'bicubic',
28
+ alignCorners: false,
29
+ stepHeight: trained / (rows + 0.1),
30
+ stepWidth: trained / (cols + 0.1),
31
+ });
32
+ const flat = node('reshape', [node('permute', [resized], { order: [1, 2, 0] })], {
33
+ shape: [patches, dim],
34
+ });
35
+ positions = node('concat', [node('slice', [table], { axis: 0, start: 0, end: 1 }), flat], {
36
+ axis: 0,
37
+ });
38
+ }
39
+ const cls = node('reshape', [w('cls_token', [1, 1, dim])], { shape: [1, dim] });
40
+ let views = images.map((image) => {
41
+ const embedded = node('patchEmbed', [
42
+ image,
43
+ w('patch_embed.proj.weight', [dim, 3, config.patch, config.patch]),
44
+ w('patch_embed.proj.bias', [dim]),
45
+ ], { patch: config.patch });
46
+ return node('add', [node('concat', [cls, embedded], { axis: 0 }), positions]);
47
+ });
48
+ /* Rotary tables: within a view by row and column from 1, across views all patches at 1, 1. */
49
+ const local = new Int32Array(2 * tokens);
50
+ const global = new Int32Array(2 * tokens * images.length);
51
+ for (let p = 0; p < patches; p += 1) {
52
+ local[2 * (p + 1)] = Math.floor(p / cols) + 1;
53
+ local[2 * (p + 1) + 1] = (p % cols) + 1;
54
+ }
55
+ for (let v = 0; v < images.length; v += 1) {
56
+ for (let p = 1; p < tokens; p += 1)
57
+ global.fill(1, 2 * (v * tokens + p), 2 * (v * tokens + p) + 2);
58
+ }
59
+ const rope = config.ropeStart >= 0
60
+ ? {
61
+ local: ropeConstants(weights, 'rope.local', local, heads, headDim),
62
+ global: ropeConstants(weights, 'rope.global', global, heads, headDim),
63
+ }
64
+ : null;
65
+ const block = (i, x, count, across) => {
66
+ const p = `blocks.${i}.`;
67
+ const h = node('layerNorm', [x, w(`${p}norm1.weight`, [dim]), w(`${p}norm1.bias`, [dim])], {
68
+ epsilon: BLOCK_EPSILON,
69
+ });
70
+ const qkv = node('linear', [
71
+ h,
72
+ w(`${p}attn.qkv.weight`, [3 * dim, dim]),
73
+ w(`${p}attn.qkv.bias`, [3 * dim]),
74
+ ]);
75
+ let q = node('slice', [qkv], { axis: 1, start: 0, end: dim });
76
+ let k = node('slice', [qkv], { axis: 1, start: dim, end: 2 * dim });
77
+ const v = node('slice', [qkv], { axis: 1, start: 2 * dim, end: 3 * dim });
78
+ if (config.qkNormStart >= 0 && i >= config.qkNormStart) {
79
+ const perHead = (value, norm) => node('reshape', [
80
+ node('layerNorm', [
81
+ node('reshape', [value], { shape: [count * heads, headDim] }),
82
+ w(`${p}attn.${norm}.weight`, [headDim]),
83
+ w(`${p}attn.${norm}.bias`, [headDim]),
84
+ ], { epsilon: DEFAULT_EPSILON }),
85
+ ], { shape: [count, dim] });
86
+ q = perHead(q, 'q_norm');
87
+ k = perHead(k, 'k_norm');
88
+ }
89
+ if (rope !== null && i >= config.ropeStart) {
90
+ const tables = across ? rope.global : rope.local;
91
+ q = applyRope(graph, q, count, heads, headDim, tables.cos, tables.sin);
92
+ k = applyRope(graph, k, count, heads, headDim, tables.cos, tables.sin);
93
+ }
94
+ const attended = node('attention', [q, k, v], { heads });
95
+ const projected = node('linear', [
96
+ attended,
97
+ w(`${p}attn.proj.weight`, [dim, dim]),
98
+ w(`${p}attn.proj.bias`, [dim]),
99
+ ]);
100
+ const x1 = node('add', [x, node('mul', [projected, w(`${p}ls1.gamma`, [dim])])]);
101
+ const h2 = node('layerNorm', [x1, w(`${p}norm2.weight`, [dim]), w(`${p}norm2.bias`, [dim])], {
102
+ epsilon: BLOCK_EPSILON,
103
+ });
104
+ const inner = node('gelu', [
105
+ node('linear', [
106
+ h2,
107
+ w(`${p}mlp.fc1.weight`, [config.hidden, dim]),
108
+ w(`${p}mlp.fc1.bias`, [config.hidden]),
109
+ ]),
110
+ ]);
111
+ const out = node('linear', [
112
+ inner,
113
+ w(`${p}mlp.fc2.weight`, [dim, config.hidden]),
114
+ w(`${p}mlp.fc2.bias`, [dim]),
115
+ ]);
116
+ return node('add', [x1, node('mul', [out, w(`${p}ls2.gamma`, [dim])])]);
117
+ };
118
+ const features = [];
119
+ const cameras = [];
120
+ let latest = views;
121
+ for (let i = 0; i < config.depth; i += 1) {
122
+ if (config.altStart >= 0 && i === config.altStart) {
123
+ const camera = node('reshape', [w('camera_token', [1, 2, dim])], { shape: [2, dim] });
124
+ const reference = node('slice', [camera], { axis: 0, start: 0, end: 1 });
125
+ const source = node('slice', [camera], { axis: 0, start: 1, end: 2 });
126
+ views = views.map((x, v) => node('concat', [v === 0 ? reference : source, node('slice', [x], { axis: 0, start: 1, end: tokens })], { axis: 0 }));
127
+ }
128
+ const across = config.altStart >= 0 && i >= config.altStart && i % 2 === 1;
129
+ if (across) {
130
+ const all = views.length === 1 ? views[0] : node('concat', views, { axis: 0 });
131
+ const mixed = block(i, all, tokens * views.length, true);
132
+ views = views.map((_, v) => views.length === 1
133
+ ? mixed
134
+ : node('slice', [mixed], { axis: 0, start: v * tokens, end: (v + 1) * tokens }));
135
+ }
136
+ else {
137
+ views = views.map((x) => block(i, x, tokens, false));
138
+ latest = views;
139
+ }
140
+ if (config.taps.includes(i)) {
141
+ const tapped = views.map((x, v) => node('concat', [latest[v], x], { axis: 1 }));
142
+ cameras.push(tapped.length === 1
143
+ ? node('slice', [tapped[0]], { axis: 0, start: 0, end: 1 })
144
+ : node('concat', tapped.map((t) => node('slice', [t], { axis: 0, start: 0, end: 1 })), { axis: 0 }));
145
+ features.push(tapped.map((t) => {
146
+ const normed = node('layerNorm', [
147
+ node('slice', [t], { axis: 1, start: dim, end: 2 * dim }),
148
+ w('norm.weight', [dim]),
149
+ w('norm.bias', [dim]),
150
+ ], { epsilon: DEFAULT_EPSILON });
151
+ const joined = node('concat', [node('slice', [t], { axis: 1, start: 0, end: dim }), normed], { axis: 1 });
152
+ return node('slice', [joined], { axis: 0, start: 1, end: tokens });
153
+ }));
154
+ }
155
+ }
156
+ return { features, cameras };
157
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Attention within the windows of a grid, as TinyViT attends: each window a batch of tokens, each
3
+ * head's scores given a learned bias by the two tokens' distance.
4
+ *
5
+ * **Windows are partitioned as the upstream partitions them**: a grid the window does not divide is
6
+ * padded with zeros after its last row and column *before* the attention's own norm, so a padded
7
+ * token is the norm's β and is attended to as a key like any other — and the padding is sliced away
8
+ * afterwards. A grid exactly one window across is attended whole.
9
+ *
10
+ * **The bias table is laid out once, at conversion**, `[heads, n, n]` from the learned vector of
11
+ * `side²` distances, numbered in the order the upstream first meets them. What it gives up: the
12
+ * table costs MobileSAM's file 2.4 MB at half precision, where a gather on the device would keep the
13
+ * 3,000 learned values instead.
14
+ *
15
+ * **Queries, keys and values come out of one projection per head**, `[q k v]` for each head in turn
16
+ * where the attention operator takes all queries, then all keys, then all values; the projection's
17
+ * rows are reordered at conversion, which is where a head could otherwise be read as the wrong one's
18
+ * keys. The norm takes PyTorch's default ε, 1e-5.
19
+ */
20
+ import type { GraphBuilder, Weights } from '@driftengine/texture';
21
+ /**
22
+ * `[rows · cols, dim]` from `x` of that shape: the attention under `p` — `norm.`, `qkv.`, `proj.` and
23
+ * `attention_biases`, the upstream's names — within windows `side` tokens square.
24
+ */
25
+ export declare function windowAttention(weights: Weights, graph: GraphBuilder, p: string, x: string, dim: number, heads: number, side: number, rows: number, cols: number): string;
@@ -0,0 +1,104 @@
1
+ const EPSILON = 1e-5;
2
+ /**
3
+ * `[rows · cols, dim]` from `x` of that shape: the attention under `p` — `norm.`, `qkv.`, `proj.` and
4
+ * `attention_biases`, the upstream's names — within windows `side` tokens square.
5
+ */
6
+ export function windowAttention(weights, graph, p, x, dim, heads, side, rows, cols) {
7
+ const node = graph.node.bind(graph);
8
+ const norm = (value, q) => node('layerNorm', [value, weights.read(`${q}weight`, [dim]), weights.read(`${q}bias`, [dim])], {
9
+ epsilon: EPSILON,
10
+ });
11
+ const linear = (value, q, out, inputs) => node('linear', [
12
+ value,
13
+ weights.read(`${q}weight`, [out, inputs]),
14
+ weights.read(`${q}bias`, [out]),
15
+ ]);
16
+ const n = side * side;
17
+ const whole = rows === side && cols === side;
18
+ const padRows = (side - (rows % side)) % side;
19
+ const padCols = (side - (cols % side)) % side;
20
+ const across = (rows + padRows) / side;
21
+ const down = (cols + padCols) / side;
22
+ const count = whole ? 1 : across * down;
23
+ let tokens = x;
24
+ if (!whole) {
25
+ let grid = node('reshape', [x], { shape: [rows, cols, dim] });
26
+ if (padRows > 0 || padCols > 0)
27
+ grid = node('pad', [grid], { after: [padRows, padCols, 0] });
28
+ const parts = node('reshape', [grid], { shape: [across, side, down, side, dim] });
29
+ tokens = node('reshape', [node('permute', [parts], { order: [0, 2, 1, 3, 4] })], {
30
+ shape: [count * n, dim],
31
+ });
32
+ }
33
+ const qkv = node('linear', [
34
+ norm(tokens, `${p}norm.`),
35
+ splitHeads(weights, `${p}qkv.weight`, [3 * dim, dim], heads),
36
+ splitHeads(weights, `${p}qkv.bias`, [3 * dim], heads),
37
+ ]);
38
+ const part = (i) => node('reshape', [node('slice', [qkv], { axis: 1, start: i * dim, end: (i + 1) * dim })], {
39
+ shape: [count, n, dim],
40
+ });
41
+ const attended = node('attention', [part(0), part(1), part(2), biasTable(weights, p, heads, side)], { heads });
42
+ const projected = linear(node('reshape', [attended], { shape: [count * n, dim] }), `${p}proj.`, dim, dim);
43
+ if (whole)
44
+ return projected;
45
+ const back = node('permute', [node('reshape', [projected], { shape: [across, down, side, side, dim] })], {
46
+ order: [0, 2, 1, 3, 4],
47
+ });
48
+ let grid = node('reshape', [back], { shape: [rows + padRows, cols + padCols, dim] });
49
+ if (padRows > 0)
50
+ grid = node('slice', [grid], { axis: 0, start: 0, end: rows });
51
+ if (padCols > 0)
52
+ grid = node('slice', [grid], { axis: 1, start: 0, end: cols });
53
+ return node('reshape', [grid], { shape: [rows * cols, dim] });
54
+ }
55
+ /**
56
+ * The projection `name` with its rows in the attention operator's order — every head's queries,
57
+ * then keys, then values — from the upstream's, where each head's three sit together.
58
+ */
59
+ function splitHeads(weights, name, shape, heads) {
60
+ const rows = shape[0];
61
+ const width = rows / 3 / heads;
62
+ const columns = shape.length === 2 ? shape[1] : 1;
63
+ return weights.derive(name.replace(/\.(weight|bias)$/, '.split.$1'), shape, (values) => {
64
+ const from = values(name, shape);
65
+ const out = new Float32Array(from.length);
66
+ for (let head = 0; head < heads; head += 1) {
67
+ for (let part = 0; part < 3; part += 1) {
68
+ for (let j = 0; j < width; j += 1) {
69
+ const source = (head * 3 + part) * width + j;
70
+ const target = part * heads * width + head * width + j;
71
+ out.set(from.subarray(source * columns, (source + 1) * columns), target * columns);
72
+ }
73
+ }
74
+ }
75
+ return out;
76
+ });
77
+ }
78
+ /**
79
+ * `[heads, n, n]`: each pair of a window's tokens given its head's learned bias for their distance
80
+ * in rows and in columns, the distances numbered in the order the upstream first meets them.
81
+ */
82
+ function biasTable(weights, p, heads, side) {
83
+ const n = side * side;
84
+ return weights.derive(`${p}attention_bias_table`, [heads, n, n], (values) => {
85
+ const learned = values(`${p}attention_biases`, [heads, n]);
86
+ const numbered = new Map();
87
+ const index = new Int32Array(n * n);
88
+ for (let a = 0; a < n; a += 1) {
89
+ for (let b = 0; b < n; b += 1) {
90
+ const offset = Math.abs(Math.floor(a / side) - Math.floor(b / side)) * side +
91
+ Math.abs((a % side) - (b % side));
92
+ if (!numbered.has(offset))
93
+ numbered.set(offset, numbered.size);
94
+ index[a * n + b] = numbered.get(offset);
95
+ }
96
+ }
97
+ const out = new Float32Array(heads * n * n);
98
+ for (let head = 0; head < heads; head += 1) {
99
+ for (let i = 0; i < n * n; i += 1)
100
+ out[head * n * n + i] = learned[head * n + index[i]];
101
+ }
102
+ return out;
103
+ });
104
+ }
@@ -0,0 +1,31 @@
1
+ export interface CaptureFrame {
2
+ readonly pixels: Uint8Array;
3
+ readonly width: number;
4
+ readonly height: number;
5
+ }
6
+ export interface PoseOptions {
7
+ /** `fx`, `fy`, `cx`, `cy` of the frames as they are given. */
8
+ readonly intrinsics: readonly [number, number, number, number];
9
+ /** The seeded generator RANSAC draws from; a capture answers the same twice. */
10
+ readonly random?: () => number;
11
+ /** Corners a frame. */
12
+ readonly budget?: number;
13
+ /** A depth model's own cameras, 3 × 4 world-to-camera each, where it ran. */
14
+ readonly modelPoses?: readonly ArrayLike<number>[] | null;
15
+ /** A distance in metres between two frames' places, which makes the scale metric. */
16
+ readonly knownLength?: {
17
+ readonly metres: number;
18
+ readonly from: number;
19
+ readonly to: number;
20
+ } | null;
21
+ }
22
+ export interface PoseResult {
23
+ readonly count: number;
24
+ /** Per frame, between zero and one: how much of it the answer rests on. */
25
+ readonly confidence: Float32Array;
26
+ /** False where the clip never moved: the rotations are answered and no baseline is claimed. */
27
+ readonly parallax: boolean;
28
+ readonly scale: 'metric' | 'relative';
29
+ }
30
+ /** The clip's camera path, into `out`: 3 × 4 world-to-camera a frame. */
31
+ export declare function estimatePoses(frames: readonly CaptureFrame[], options: PoseOptions, out: Float32Array): PoseResult;