@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,317 @@
1
+ /**
2
+ * The dense decompositions structure from motion needs, in double precision: a singular value
3
+ * decomposition, a symmetric eigendecomposition, a Cholesky factor, and Levenberg–Marquardt over a
4
+ * least-squares problem.
5
+ *
6
+ * **Written here rather than depended on.** Each is a few dozen lines against a dependency this
7
+ * engine would carry into every game that captures a scene, and each is held to its own identity —
8
+ * a decomposition that reconstructs what it decomposed, a factor whose square is the matrix — which
9
+ * is a stronger check than agreeing with another implementation.
10
+ *
11
+ * **Both decompositions are Jacobi's**, rotating away one off-diagonal at a time. It is not the
12
+ * fastest way to a large spectrum and it is the most accurate on the small, nearly rank-deficient
13
+ * matrices this is for — a homography from four points, an essential matrix, a 3 × 3 covariance —
14
+ * where the answer's smallest value is the one that matters. **What would make it wrong** is a
15
+ * matrix of thousands of rows, which wants a bidiagonal reduction instead; nothing here has one.
16
+ *
17
+ * **Everything is `Float64Array`.** Geometry accumulates: a pose error of a millionth is visible
18
+ * once a hundred frames carry it. Only arithmetic and `Math.sqrt` are reached for, which IEEE 754
19
+ * fixes, so a capture reproduces on any engine — `scripts/determinism.mjs` holds this package to
20
+ * that.
21
+ */
22
+
23
+ /** How far off the diagonal a Jacobi sweep tolerates before it stops. */
24
+ const SETTLED = 1e-15;
25
+ const SWEEPS = 60;
26
+
27
+ /**
28
+ * One-sided Jacobi: `m`, `rows × cols` row-major with `rows ≥ cols`, into `u` (`rows × cols`),
29
+ * `s` (`cols`, falling) and `v` (`cols × cols`), where `m = u · diag(s) · vᵀ`.
30
+ */
31
+ export function svdN(
32
+ m: Float64Array,
33
+ rows: number,
34
+ cols: number,
35
+ u: Float64Array,
36
+ s: Float64Array,
37
+ v: Float64Array,
38
+ ): void {
39
+ if (rows < cols) {
40
+ throw new RangeError(`a ${rows}×${cols} matrix has more columns than rows; transpose it first`);
41
+ }
42
+ const a = Float64Array.from(m);
43
+ v.fill(0);
44
+ for (let i = 0; i < cols; i += 1) v[i * cols + i] = 1;
45
+
46
+ for (let sweep = 0; sweep < SWEEPS; sweep += 1) {
47
+ let off = 0;
48
+ for (let p = 0; p < cols - 1; p += 1) {
49
+ for (let q = p + 1; q < cols; q += 1) {
50
+ let alpha = 0;
51
+ let beta = 0;
52
+ let gamma = 0;
53
+ for (let r = 0; r < rows; r += 1) {
54
+ const left = a[r * cols + p] as number;
55
+ const right = a[r * cols + q] as number;
56
+ alpha += left * left;
57
+ beta += right * right;
58
+ gamma += left * right;
59
+ }
60
+ if (gamma === 0) continue;
61
+ const scale = Math.sqrt(alpha * beta);
62
+ off = Math.max(off, scale === 0 ? 0 : Math.abs(gamma) / scale);
63
+ if (scale === 0 || Math.abs(gamma) <= SETTLED * scale) continue;
64
+ /* The rotation that makes the two columns orthogonal, by the stable root. */
65
+ const zeta = (beta - alpha) / (2 * gamma);
66
+ const t = (zeta >= 0 ? 1 : -1) / (Math.abs(zeta) + Math.sqrt(1 + zeta * zeta));
67
+ const c = 1 / Math.sqrt(1 + t * t);
68
+ const sn = c * t;
69
+ for (let r = 0; r < rows; r += 1) {
70
+ const left = a[r * cols + p] as number;
71
+ const right = a[r * cols + q] as number;
72
+ a[r * cols + p] = c * left - sn * right;
73
+ a[r * cols + q] = sn * left + c * right;
74
+ }
75
+ for (let r = 0; r < cols; r += 1) {
76
+ const left = v[r * cols + p] as number;
77
+ const right = v[r * cols + q] as number;
78
+ v[r * cols + p] = c * left - sn * right;
79
+ v[r * cols + q] = sn * left + c * right;
80
+ }
81
+ }
82
+ }
83
+ if (off <= SETTLED) break;
84
+ }
85
+
86
+ /* What is left of each column is its singular value times a unit vector. */
87
+ const order = Array.from({ length: cols }, (_, i) => i);
88
+ const lengths = new Float64Array(cols);
89
+ for (let c = 0; c < cols; c += 1) {
90
+ let sum = 0;
91
+ for (let r = 0; r < rows; r += 1) {
92
+ const value = a[r * cols + c] as number;
93
+ sum += value * value;
94
+ }
95
+ lengths[c] = Math.sqrt(sum);
96
+ }
97
+ order.sort((p, q) => (lengths[q] as number) - (lengths[p] as number));
98
+ const held = Float64Array.from(v);
99
+ for (const [at, from] of order.entries()) {
100
+ const length = lengths[from] as number;
101
+ s[at] = length;
102
+ for (let r = 0; r < rows; r += 1) {
103
+ u[r * cols + at] = length === 0 ? 0 : (a[r * cols + from] as number) / length;
104
+ }
105
+ for (let r = 0; r < cols; r += 1) v[r * cols + at] = held[r * cols + from] as number;
106
+ }
107
+ }
108
+
109
+ /** The same for the 3 × 3 matrices geometry is full of. */
110
+ export function svd3(m: Float64Array, u: Float64Array, s: Float64Array, v: Float64Array): void {
111
+ svdN(m, 3, 3, u, s, v);
112
+ }
113
+
114
+ /**
115
+ * Jacobi's eigendecomposition of a symmetric `m`, `n × n`: `values` in falling order and `vectors`
116
+ * by column, so `m · vectors[:, k] = values[k] · vectors[:, k]`.
117
+ */
118
+ export function symmetricEigen(
119
+ m: Float64Array,
120
+ n: number,
121
+ values: Float64Array,
122
+ vectors: Float64Array,
123
+ ): void {
124
+ const a = Float64Array.from(m);
125
+ vectors.fill(0);
126
+ for (let i = 0; i < n; i += 1) vectors[i * n + i] = 1;
127
+
128
+ for (let sweep = 0; sweep < SWEEPS; sweep += 1) {
129
+ let off = 0;
130
+ for (let p = 0; p < n - 1; p += 1) {
131
+ for (let q = p + 1; q < n; q += 1) off = Math.max(off, Math.abs(a[p * n + q] as number));
132
+ }
133
+ if (off <= SETTLED) break;
134
+ for (let p = 0; p < n - 1; p += 1) {
135
+ for (let q = p + 1; q < n; q += 1) {
136
+ const pivot = a[p * n + q] as number;
137
+ if (Math.abs(pivot) <= SETTLED * off) continue;
138
+ const zeta = ((a[q * n + q] as number) - (a[p * n + p] as number)) / (2 * pivot);
139
+ const t = (zeta >= 0 ? 1 : -1) / (Math.abs(zeta) + Math.sqrt(1 + zeta * zeta));
140
+ const c = 1 / Math.sqrt(1 + t * t);
141
+ const sn = c * t;
142
+ for (let k = 0; k < n; k += 1) {
143
+ const left = a[k * n + p] as number;
144
+ const right = a[k * n + q] as number;
145
+ a[k * n + p] = c * left - sn * right;
146
+ a[k * n + q] = sn * left + c * right;
147
+ }
148
+ for (let k = 0; k < n; k += 1) {
149
+ const left = a[p * n + k] as number;
150
+ const right = a[q * n + k] as number;
151
+ a[p * n + k] = c * left - sn * right;
152
+ a[q * n + k] = sn * left + c * right;
153
+ }
154
+ for (let k = 0; k < n; k += 1) {
155
+ const left = vectors[k * n + p] as number;
156
+ const right = vectors[k * n + q] as number;
157
+ vectors[k * n + p] = c * left - sn * right;
158
+ vectors[k * n + q] = sn * left + c * right;
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ const order = Array.from({ length: n }, (_, i) => i);
165
+ order.sort((p, q) => (a[q * n + q] as number) - (a[p * n + p] as number));
166
+ const held = Float64Array.from(vectors);
167
+ for (const [at, from] of order.entries()) {
168
+ values[at] = a[from * n + from] as number;
169
+ for (let r = 0; r < n; r += 1) vectors[r * n + at] = held[r * n + from] as number;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * The lower triangle `l` with `l · lᵀ = m`, or false where `m` is not positive definite — which is
175
+ * how a caller learns that its normal equations are singular, rather than by finding a NaN later.
176
+ *
177
+ * **`out` may be `m`**: only the lower triangle is read, each element before it is written, so a
178
+ * caller with one matrix to spare factors it in place. Zeroing the whole of `out` first would have
179
+ * erased the matrix in that case, which is a silent wrong answer rather than a refusal.
180
+ */
181
+ export function cholesky(m: Float64Array, n: number, out: Float64Array): boolean {
182
+ for (let r = 0; r < n; r += 1) {
183
+ for (let c = r + 1; c < n; c += 1) out[r * n + c] = 0;
184
+ for (let c = 0; c <= r; c += 1) {
185
+ let sum = m[r * n + c] as number;
186
+ for (let k = 0; k < c; k += 1) sum -= (out[r * n + k] as number) * (out[c * n + k] as number);
187
+ if (r === c) {
188
+ if (!(sum > 0)) return false;
189
+ out[r * n + c] = Math.sqrt(sum);
190
+ } else {
191
+ out[r * n + c] = sum / (out[c * n + c] as number);
192
+ }
193
+ }
194
+ }
195
+ return true;
196
+ }
197
+
198
+ /** Solves `l · lᵀ · x = b` in place over `x`, given the Cholesky factor. */
199
+ export function choleskySolve(l: Float64Array, n: number, x: Float64Array): void {
200
+ for (let r = 0; r < n; r += 1) {
201
+ let sum = x[r] as number;
202
+ for (let k = 0; k < r; k += 1) sum -= (l[r * n + k] as number) * (x[k] as number);
203
+ x[r] = sum / (l[r * n + r] as number);
204
+ }
205
+ for (let r = n - 1; r >= 0; r -= 1) {
206
+ let sum = x[r] as number;
207
+ for (let k = r + 1; k < n; k += 1) sum -= (l[k * n + r] as number) * (x[k] as number);
208
+ x[r] = sum / (l[r * n + r] as number);
209
+ }
210
+ }
211
+
212
+ /** A least-squares problem: how many residuals, how many parameters, and both at a point. */
213
+ export interface LeastSquares {
214
+ readonly residuals: number;
215
+ readonly parameters: number;
216
+ /** The residuals at `x`, and — unless `jacobian` is null — their derivatives, row by residual. */
217
+ evaluate(x: Float64Array, residuals: Float64Array, jacobian: Float64Array | null): void;
218
+ }
219
+
220
+ export interface FitOptions {
221
+ /** The first damping; **zero is plain Gauss–Newton**, steps taken whatever they cost. */
222
+ readonly damping?: number;
223
+ readonly maxIterations?: number;
224
+ /** Stop once a step moves every parameter less than this. */
225
+ readonly tolerance?: number;
226
+ }
227
+
228
+ /**
229
+ * Levenberg–Marquardt over `x`, in place: the Gauss–Newton step with `damping · diag(JᵀJ)` added,
230
+ * the damping raised where a step would cost more and lowered where it pays, which is the whole
231
+ * reason it is not Gauss–Newton — **a step that overshoots is rejected rather than taken**, and a
232
+ * start far from the answer is where that decides between converging and diverging.
233
+ */
234
+ export function levenbergMarquardt(
235
+ problem: LeastSquares,
236
+ x: Float64Array,
237
+ options: FitOptions = {},
238
+ ): { readonly iterations: number; readonly cost: number } {
239
+ const { residuals: rows, parameters: columns } = problem;
240
+ const maxIterations = options.maxIterations ?? 100;
241
+ const tolerance = options.tolerance ?? 1e-14;
242
+ let damping = options.damping ?? 1e-3;
243
+ const undamped = damping === 0;
244
+
245
+ const residual = new Float64Array(rows);
246
+ const jacobian = new Float64Array(rows * columns);
247
+ const normals = new Float64Array(columns * columns);
248
+ const factor = new Float64Array(columns * columns);
249
+ const gradient = new Float64Array(columns);
250
+ const step = new Float64Array(columns);
251
+ const trial = new Float64Array(columns);
252
+
253
+ const costOf = (values: Float64Array): number => {
254
+ let sum = 0;
255
+ for (let i = 0; i < values.length; i += 1) sum += (values[i] as number) * (values[i] as number);
256
+ return sum;
257
+ };
258
+
259
+ problem.evaluate(x, residual, null);
260
+ let cost = costOf(residual);
261
+ let iterations = 0;
262
+ while (iterations < maxIterations) {
263
+ iterations += 1;
264
+ problem.evaluate(x, residual, jacobian);
265
+ normals.fill(0);
266
+ gradient.fill(0);
267
+ for (let r = 0; r < rows; r += 1) {
268
+ for (let p = 0; p < columns; p += 1) {
269
+ const value = jacobian[r * columns + p] as number;
270
+ gradient[p] = (gradient[p] as number) - value * (residual[r] as number);
271
+ for (let q = p; q < columns; q += 1) {
272
+ normals[p * columns + q] =
273
+ (normals[p * columns + q] as number) + value * (jacobian[r * columns + q] as number);
274
+ }
275
+ }
276
+ }
277
+ for (let p = 0; p < columns; p += 1) {
278
+ for (let q = 0; q < p; q += 1) normals[p * columns + q] = normals[q * columns + p] as number;
279
+ }
280
+
281
+ let taken = false;
282
+ for (let attempt = 0; attempt < 12 && !taken; attempt += 1) {
283
+ factor.set(normals);
284
+ for (let p = 0; p < columns; p += 1) {
285
+ /* Marquardt's scaling: the damping rides each parameter's own curvature. */
286
+ const diagonal = normals[p * columns + p] as number;
287
+ factor[p * columns + p] = diagonal * (1 + damping) + damping;
288
+ }
289
+ if (!cholesky(factor, columns, factor)) {
290
+ if (undamped) return { iterations, cost };
291
+ damping *= 10;
292
+ continue;
293
+ }
294
+ step.set(gradient);
295
+ choleskySolve(factor, columns, step);
296
+ let moved = 0;
297
+ for (let p = 0; p < columns; p += 1) {
298
+ trial[p] = (x[p] as number) + (step[p] as number);
299
+ moved = Math.max(moved, Math.abs(step[p] as number));
300
+ }
301
+ problem.evaluate(trial, residual, null);
302
+ const next = costOf(residual);
303
+ if (undamped || next < cost) {
304
+ x.set(trial);
305
+ cost = next;
306
+ taken = true;
307
+ if (!undamped) damping = Math.max(damping / 10, 1e-12);
308
+ if (moved <= tolerance) return { iterations, cost };
309
+ } else {
310
+ damping *= 10;
311
+ if (damping > 1e12) return { iterations, cost };
312
+ }
313
+ }
314
+ if (!taken) return { iterations, cost };
315
+ }
316
+ return { iterations, cost };
317
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * The Schur complement over points: a bundle adjustment's normal equations solved by eliminating
3
+ * the points first, which leaves a system the size of the cameras.
4
+ *
5
+ * **It is an ordering, not an approximation.** A point appears in the rows of its own observations
6
+ * and nowhere else, so its three parameters can be solved for in terms of the cameras and
7
+ * substituted out exactly; what is left is the cameras' own system with a correction per link, and
8
+ * the points come back by back-substitution. The answer is the answer a dense solve would give —
9
+ * `schur.test.ts` holds it to one — and the saving is that a clip with a thousand points and twenty
10
+ * cameras solves a matrix of 120 rows rather than 3,120.
11
+ *
12
+ * **The damping is the caller's, applied here**, since it has to sit on the diagonal before the
13
+ * points are eliminated rather than after: `diagonal · (1 + λ) + λ`, which is Marquardt's scaling
14
+ * with a floor for a parameter whose curvature is zero.
15
+ *
16
+ * **A block that cannot be factored is refused rather than stepped through.** A point nothing
17
+ * constrains would be a division by nothing, and a reduced system that is singular means the
18
+ * cameras are not determined by what was seen — both are answers a caller must have, so they come
19
+ * back as false rather than as a step full of infinities.
20
+ */
21
+ import { cholesky, choleskySolve } from './dense.ts';
22
+
23
+ /** The normal equations in blocks: cameras, points of three, and one link per observation. */
24
+ export interface BlockNormals {
25
+ readonly cameras: number;
26
+ /** Parameters a camera; the points are three each. */
27
+ readonly cameraSize: number;
28
+ readonly points: number;
29
+ /** `cameras · cameraSize²`, each symmetric and row-major. */
30
+ readonly cameraBlocks: Float64Array;
31
+ /** `points · 9`, each symmetric and row-major. */
32
+ readonly pointBlocks: Float64Array;
33
+ /** `links · cameraSize · 3`, row-major: a camera's rows against a point's columns. */
34
+ readonly links: Float64Array;
35
+ readonly linkCamera: Int32Array;
36
+ readonly linkPoint: Int32Array;
37
+ /** The right-hand side: `cameras · cameraSize` and `points · 3`. */
38
+ readonly cameraGradient: Float64Array;
39
+ readonly pointGradient: Float64Array;
40
+ }
41
+
42
+ const POINT = 3;
43
+
44
+ /**
45
+ * The step for `damping`, into `cameraStep` (`cameras · cameraSize`) and `pointStep`
46
+ * (`points · 3`); false where a point's block or the reduced system cannot be factored.
47
+ */
48
+ export function schurSolve(
49
+ normals: BlockNormals,
50
+ damping: number,
51
+ cameraStep: Float64Array,
52
+ pointStep: Float64Array,
53
+ ): boolean {
54
+ const { cameras, cameraSize, points, links, linkCamera, linkPoint } = normals;
55
+ const width = cameras * cameraSize;
56
+ const linkCount = linkCamera.length;
57
+
58
+ /* Each point's own block, damped and factored once: everything below reads it. */
59
+ const factors = new Float64Array(points * POINT * POINT);
60
+ const block = new Float64Array(POINT * POINT);
61
+ for (let p = 0; p < points; p += 1) {
62
+ for (let r = 0; r < POINT; r += 1) {
63
+ for (let c = 0; c < POINT; c += 1) {
64
+ const value = normals.pointBlocks[(p * POINT + r) * POINT + c] as number;
65
+ block[r * POINT + c] = r === c ? value * (1 + damping) + damping : value;
66
+ }
67
+ }
68
+ if (!cholesky(block, POINT, block)) return false;
69
+ factors.set(block, p * POINT * POINT);
70
+ }
71
+
72
+ /* The reduced system: the cameras' own blocks, less each link's share of its point. */
73
+ const reduced = new Float64Array(width * width);
74
+ const right = new Float64Array(width);
75
+ for (let c = 0; c < cameras; c += 1) {
76
+ for (let r = 0; r < cameraSize; r += 1) {
77
+ right[c * cameraSize + r] = normals.cameraGradient[c * cameraSize + r] as number;
78
+ for (let k = 0; k < cameraSize; k += 1) {
79
+ const value = normals.cameraBlocks[(c * cameraSize + r) * cameraSize + k] as number;
80
+ reduced[(c * cameraSize + r) * width + c * cameraSize + k] =
81
+ r === k ? value * (1 + damping) + damping : value;
82
+ }
83
+ }
84
+ }
85
+
86
+ /* Each link's `w · v⁻¹`, kept for the reduction and for the back-substitution after it. */
87
+ const solved = new Float64Array(linkCount * cameraSize * POINT);
88
+ const column = new Float64Array(POINT);
89
+ for (let link = 0; link < linkCount; link += 1) {
90
+ const point = linkPoint[link] as number;
91
+ const factor = factors.subarray(point * POINT * POINT, (point + 1) * POINT * POINT);
92
+ for (let r = 0; r < cameraSize; r += 1) {
93
+ for (let k = 0; k < POINT; k += 1) {
94
+ column[k] = links[(link * cameraSize + r) * POINT + k] as number;
95
+ }
96
+ choleskySolve(factor, POINT, column);
97
+ for (let k = 0; k < POINT; k += 1) {
98
+ solved[(link * cameraSize + r) * POINT + k] = column[k] as number;
99
+ }
100
+ }
101
+ }
102
+
103
+ /* Two links on one point couple their cameras, which is where the reduction's off-diagonals are. */
104
+ for (let a = 0; a < linkCount; a += 1) {
105
+ for (let b = 0; b < linkCount; b += 1) {
106
+ if ((linkPoint[a] as number) !== (linkPoint[b] as number)) continue;
107
+ const rowCamera = (linkCamera[a] as number) * cameraSize;
108
+ const colCamera = (linkCamera[b] as number) * cameraSize;
109
+ for (let r = 0; r < cameraSize; r += 1) {
110
+ for (let c = 0; c < cameraSize; c += 1) {
111
+ let sum = 0;
112
+ for (let k = 0; k < POINT; k += 1) {
113
+ sum +=
114
+ (solved[(a * cameraSize + r) * POINT + k] as number) *
115
+ (links[(b * cameraSize + c) * POINT + k] as number);
116
+ }
117
+ reduced[(rowCamera + r) * width + colCamera + c] -= sum;
118
+ }
119
+ }
120
+ }
121
+ }
122
+ for (let link = 0; link < linkCount; link += 1) {
123
+ const camera = (linkCamera[link] as number) * cameraSize;
124
+ const point = (linkPoint[link] as number) * POINT;
125
+ for (let r = 0; r < cameraSize; r += 1) {
126
+ let sum = 0;
127
+ for (let k = 0; k < POINT; k += 1) {
128
+ sum +=
129
+ (solved[(link * cameraSize + r) * POINT + k] as number) *
130
+ (normals.pointGradient[point + k] as number);
131
+ }
132
+ right[camera + r] -= sum;
133
+ }
134
+ }
135
+
136
+ const factor = new Float64Array(width * width);
137
+ if (!cholesky(reduced, width, factor)) return false;
138
+ cameraStep.set(right);
139
+ choleskySolve(factor, width, cameraStep);
140
+
141
+ /* And the points come back: their own gradient, less what the cameras just took. */
142
+ for (let p = 0; p < points; p += 1) {
143
+ for (let k = 0; k < POINT; k += 1) {
144
+ pointStep[p * POINT + k] = normals.pointGradient[p * POINT + k] as number;
145
+ }
146
+ }
147
+ for (let link = 0; link < linkCount; link += 1) {
148
+ const camera = (linkCamera[link] as number) * cameraSize;
149
+ const point = (linkPoint[link] as number) * POINT;
150
+ for (let k = 0; k < POINT; k += 1) {
151
+ let sum = 0;
152
+ for (let r = 0; r < cameraSize; r += 1) {
153
+ sum +=
154
+ (links[(link * cameraSize + r) * POINT + k] as number) *
155
+ (cameraStep[camera + r] as number);
156
+ }
157
+ pointStep[point + k] = (pointStep[point + k] as number) - sum;
158
+ }
159
+ }
160
+ for (let p = 0; p < points; p += 1) {
161
+ const factored = factors.subarray(p * POINT * POINT, (p + 1) * POINT * POINT);
162
+ for (let k = 0; k < POINT; k += 1) column[k] = pointStep[p * POINT + k] as number;
163
+ choleskySolve(factored, POINT, column);
164
+ for (let k = 0; k < POINT; k += 1) pointStep[p * POINT + k] = column[k] as number;
165
+ }
166
+ return true;
167
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * A convolution with the batch norm after it folded in, once, where the weights are converted.
3
+ *
4
+ * **Folded as the upstream's `Conv2d_BN.fuse` folds it**: the kernel scaled by `γ/√(var + ε)` per
5
+ * output, and the bias `β − mean·γ/√(var + ε)`. The converted file holds the folded pair under
6
+ * names of its own and none of the statistics, and the step counter a batch norm keeps is set aside
7
+ * by name. ε is PyTorch's default, 1e-5.
8
+ *
9
+ * What it gives up: the fold is computed in double precision and rounded once, where the upstream
10
+ * evaluates the norm after the convolution in single precision — the two agree to a rounding.
11
+ */
12
+ import type { GraphBuilder, Weights } from '@driftengine/texture';
13
+
14
+ const EPSILON = 1e-5;
15
+
16
+ /**
17
+ * `conv2d` of `x` by the convolution and batch norm under `p` — `c.` and `bn.`, the upstream's
18
+ * names — padded to keep the grid at stride one.
19
+ */
20
+ export function convBatchNorm(
21
+ weights: Weights,
22
+ graph: GraphBuilder,
23
+ p: string,
24
+ x: string,
25
+ cin: number,
26
+ cout: number,
27
+ kernel: number,
28
+ stride: number,
29
+ groups: number,
30
+ ): string {
31
+ const shape = [cout, cin / groups, kernel, kernel];
32
+ const scale = (values: (part: string, shape?: readonly number[]) => Float32Array): number[] => {
33
+ const variance = values(`${p}bn.running_var`, [cout]);
34
+ return Array.from(
35
+ values(`${p}bn.weight`, [cout]),
36
+ (gamma, o) => gamma / Math.sqrt((variance[o] as number) + EPSILON),
37
+ );
38
+ };
39
+ const weight = weights.derive(`${p}folded.weight`, shape, (values) => {
40
+ const factor = scale(values);
41
+ const per = (cin / groups) * kernel * kernel;
42
+ return Float32Array.from(
43
+ values(`${p}c.weight`, shape),
44
+ (w, i) => w * (factor[Math.floor(i / per)] as number),
45
+ );
46
+ });
47
+ const bias = weights.derive(`${p}folded.bias`, [cout], (values) => {
48
+ const factor = scale(values);
49
+ const mean = values(`${p}bn.running_mean`, [cout]);
50
+ return Float32Array.from(
51
+ values(`${p}bn.bias`, [cout]),
52
+ (beta, o) => beta - (mean[o] as number) * (factor[o] as number),
53
+ );
54
+ });
55
+ weights.ignore(`${p}bn.num_batches_tracked`);
56
+ return graph.node('conv2d', [x, weight, bias], { stride, padding: (kernel - 1) / 2, groups });
57
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * CLIP's tokenizer — byte-level BPE — as Transformers' `CLIPTokenizer` runs it at the revision the
3
+ * manifest pins, for OWLv2's text queries.
4
+ *
5
+ * **Its whole vocabulary is its merges.** A byte's token is the byte's place in GPT-2's order —
6
+ * the printable bytes first, then the rest — the same byte ending a word is 256 further on, the
7
+ * merge of rank r makes token 512 + r, and the start and end of text follow the last merge. So a
8
+ * converted file carries the merges as pairs of token ids and nothing else, and the conversion
9
+ * checks the upstream's `vocab.json` says the same.
10
+ *
11
+ * **In the upstream's order**: `!` and `<|endoftext|>` are taken out of the raw text as the added
12
+ * tokens they are — `!` is the padding token, 0, wherever it appears; the rest is composed (NFC)
13
+ * and lowercased a character at a time; `<|startoftext|>` is then taken out of that; what is left
14
+ * is split by CLIP's pattern into contractions, runs of letters, single digits and runs of anything
15
+ * else; each piece's UTF-8 bytes become tokens with the last marked as a word's end, and adjacent
16
+ * pairs merge lowest rank first, leftmost among equals. A pair listed twice ranks as its last, as
17
+ * the upstream's map of merges keeps it.
18
+ *
19
+ * **What it leaves out, and why that changes nothing**: the upstream also makes each run of
20
+ * whitespace one space, and splits again by GPT-2's pattern after CLIP's. No piece holds
21
+ * whitespace, so the first cannot change a token; and neither pattern crosses whitespace while
22
+ * every piece CLIP's makes is one GPT-2's matches whole, so the second divides nothing. What would
23
+ * make that wrong is a pattern change on either side, which the hand-run parity would show.
24
+ */
25
+
26
+ export interface ClipTokenizer {
27
+ /** The text's tokens, between the start and end of text. */
28
+ encode(text: string): number[];
29
+ }
30
+
31
+ /* CLIP's pieces: its specials, contractions, letter runs, one digit, and runs of anything else. */
32
+ const PIECES =
33
+ /<\|startoftext\|>|<\|endoftext\|>|'s|'t|'re|'ve|'m|'ll|'d|\p{L}+|\p{N}|[^\s\p{L}\p{N}]+/gu;
34
+
35
+ /* Each byte's token: its place in GPT-2's order, printable bytes first. */
36
+ const BYTE_TOKEN = ((): Uint16Array => {
37
+ const printable = (b: number): boolean =>
38
+ (b >= 33 && b <= 126) || (b >= 161 && b <= 172) || (b >= 174 && b <= 255);
39
+ const table = new Uint16Array(256);
40
+ let next = 0;
41
+ for (let b = 0; b < 256; b += 1) if (printable(b)) table[b] = next++;
42
+ for (let b = 0; b < 256; b += 1) if (!printable(b)) table[b] = next++;
43
+ return table;
44
+ })();
45
+ const WORD_END = 256;
46
+ const MERGED = 512;
47
+ const PADDING = 0;
48
+
49
+ /** A tokenizer over `merges`, `[count, 2]` token ids in rank order. */
50
+ export function clipTokenizer(merges: Float32Array): ClipTokenizer {
51
+ const count = merges.length / 2;
52
+ const ranks = new Map<number, number>();
53
+ for (let r = 0; r < count; r += 1) {
54
+ const key = (merges[2 * r] as number) * 65536 + (merges[2 * r + 1] as number);
55
+ ranks.set(key, r);
56
+ }
57
+ const start = MERGED + count;
58
+ const end = start + 1;
59
+ const bytes = new TextEncoder();
60
+
61
+ const word = (piece: string, out: number[]): void => {
62
+ const symbols = Array.from(bytes.encode(piece), (b) => BYTE_TOKEN[b] as number);
63
+ symbols[symbols.length - 1] = (symbols[symbols.length - 1] as number) + WORD_END;
64
+ for (;;) {
65
+ let best = -1;
66
+ let rank = count;
67
+ for (let i = 0; i + 1 < symbols.length; i += 1) {
68
+ const r = ranks.get((symbols[i] as number) * 65536 + (symbols[i + 1] as number));
69
+ if (r !== undefined && r < rank) {
70
+ rank = r;
71
+ best = i;
72
+ }
73
+ }
74
+ if (best < 0) break;
75
+ symbols.splice(best, 2, MERGED + rank);
76
+ }
77
+ out.push(...symbols);
78
+ };
79
+ const normalised = (text: string, out: number[]): void => {
80
+ const lower = Array.from(text.normalize('NFC'), (c) => c.toLowerCase()).join('');
81
+ lower.split('<|startoftext|>').forEach((part, i) => {
82
+ if (i > 0) out.push(start);
83
+ for (const [piece] of part.matchAll(PIECES)) word(piece, out);
84
+ });
85
+ };
86
+
87
+ return {
88
+ encode(text) {
89
+ const out = [start];
90
+ text.split('<|endoftext|>').forEach((part, i) => {
91
+ if (i > 0) out.push(end);
92
+ part.split('!').forEach((run, j) => {
93
+ if (j > 0) out.push(PADDING);
94
+ normalised(run, out);
95
+ });
96
+ });
97
+ out.push(end);
98
+ return out;
99
+ },
100
+ };
101
+ }
102
+
103
+ /**
104
+ * OWLv2's text queries as its text graph takes them: `tokens`, `[queries · positions]`, each query
105
+ * padded with zeros to `positions`, and `ends`, `[queries]`, the row of each query's end of text in
106
+ * that list — the upstream pools the row of the largest token, which is that one. A query longer
107
+ * than `positions` is refused, as the upstream's position table refuses it.
108
+ */
109
+ export function owlv2Tokens(
110
+ tokenizer: ClipTokenizer,
111
+ texts: readonly string[],
112
+ positions: number,
113
+ ): { readonly tokens: Float32Array; readonly ends: Float32Array } {
114
+ const tokens = new Float32Array(texts.length * positions);
115
+ const ends = new Float32Array(texts.length);
116
+ texts.forEach((text, q) => {
117
+ const ids = tokenizer.encode(text);
118
+ if (ids.length > positions) {
119
+ throw new RangeError(`"${text}" is ${ids.length} tokens and the model reads ${positions}`);
120
+ }
121
+ tokens.set(ids, q * positions);
122
+ let largest = 0;
123
+ ids.forEach((id, i) => {
124
+ if (id > (ids[largest] as number)) largest = i;
125
+ });
126
+ ends[q] = q * positions + largest;
127
+ });
128
+ return { tokens, ends };
129
+ }