@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
package/dist/masks.js ADDED
@@ -0,0 +1,165 @@
1
+ const DEFAULT_OVERLAP = 0.25;
2
+ import { planeOf, regionOf } from './segment.js';
3
+ /**
4
+ * A lattice of prompt points over an image, seeded.
5
+ *
6
+ * **A lattice rather than a scatter**, because a scatter leaves gaps a small object falls into and
7
+ * a lattice does not; the seeded jitter is there so that a repeated object does not land on the
8
+ * same part of every instance, which is how a grid misses the same feature twice. Two per pixel
9
+ * pair — `x` then `y` — in reading order, which is the order a model's masks come back in.
10
+ */
11
+ export function promptGrid(width, height, across, down, random) {
12
+ const out = new Float32Array(across * down * 2);
13
+ for (let j = 0; j < down; j += 1) {
14
+ for (let i = 0; i < across; i += 1) {
15
+ const at = (j * across + i) * 2;
16
+ const cellX = (width * (i + 0.5)) / across;
17
+ const cellY = (height * (j + 0.5)) / down;
18
+ /*
19
+ * A sixth of a cell either way. A centre sits half a cell from each of its edges, so a point
20
+ * cannot leave the cell it belongs to however the seed falls — which is what keeps this a
21
+ * lattice, and is why nothing here clamps to the frame.
22
+ */
23
+ const jitterX = ((random() - 0.5) * width) / across / 3;
24
+ const jitterY = ((random() - 0.5) * height) / down / 3;
25
+ out[at] = cellX + jitterX;
26
+ out[at + 1] = cellY + jitterY;
27
+ }
28
+ }
29
+ return out;
30
+ }
31
+ /**
32
+ * Masks from several views lifted onto the mesh, by what most of the views that saw a triangle say.
33
+ *
34
+ * **Voting rather than taking the best view**, because a mask boundary is where a model is least
35
+ * sure and every view puts it somewhere slightly different. A triangle no view claimed is left
36
+ * unassigned rather than given to its neighbour, which is what keeps an unlabelled region
37
+ * unlabelled.
38
+ */
39
+ export function liftMasks(views, mesh, labels) {
40
+ const triangles = mesh.indices.length / 3;
41
+ const planes = new Float64Array(triangles * 4);
42
+ const areas = new Float64Array(triangles);
43
+ for (let face = 0; face < triangles; face += 1)
44
+ planeOf(mesh, face, planes, areas);
45
+ const votes = [];
46
+ for (let face = 0; face < triangles; face += 1)
47
+ votes.push(new Map());
48
+ const middle = new Float64Array(3);
49
+ for (const view of views) {
50
+ const [fx, fy, cx, cy] = view.intrinsics;
51
+ const m = view.worldToCamera;
52
+ for (let face = 0; face < triangles; face += 1) {
53
+ centroid(mesh, face, middle);
54
+ let x = m[3];
55
+ let y = m[7];
56
+ let z = m[11];
57
+ for (let k = 0; k < 3; k += 1) {
58
+ x += m[k] * middle[k];
59
+ y += m[4 + k] * middle[k];
60
+ z += m[8 + k] * middle[k];
61
+ }
62
+ if (!(z > 0))
63
+ continue;
64
+ const px = Math.floor((fx * x) / z + cx);
65
+ const py = Math.floor((fy * y) / z + cy);
66
+ if (px < 0 || px >= view.width || py < 0 || py >= view.height)
67
+ continue;
68
+ const mask = view.mask[py * view.width + px];
69
+ if (mask < 0)
70
+ continue;
71
+ const held = votes[face];
72
+ held.set(mask, (held.get(mask) ?? 0) + 1);
73
+ }
74
+ }
75
+ const members = new Map();
76
+ for (let face = 0; face < triangles; face += 1) {
77
+ const held = votes[face];
78
+ let best = -1;
79
+ let most = 0;
80
+ /* Ties go to the lower mask index, so two views disagreeing give the same answer every run. */
81
+ for (const [mask, count] of [...held.entries()].sort((a, b) => a[0] - b[0])) {
82
+ if (count > most) {
83
+ most = count;
84
+ best = mask;
85
+ }
86
+ }
87
+ if (best < 0)
88
+ continue;
89
+ const list = members.get(best);
90
+ if (list === undefined)
91
+ members.set(best, [face]);
92
+ else
93
+ list.push(face);
94
+ }
95
+ const out = [];
96
+ for (const mask of [...members.keys()].sort((a, b) => a - b)) {
97
+ const region = regionOf(mesh, members.get(mask), planes, areas);
98
+ out.push({ ...region, label: labels[mask] ?? null });
99
+ }
100
+ return out;
101
+ }
102
+ /**
103
+ * A name for each mask index, from a detector's boxes.
104
+ *
105
+ * **The join between the two models a capture runs**: one says *where the things are* and the other
106
+ * says *what they are called*, and neither can do the other's half. A mask takes the name of the
107
+ * detection it agrees with most, measured as intersection over union between the mask's own pixels
108
+ * and the box — the mask's pixels rather than the box around them, because the box around an
109
+ * L-shaped mask is mostly not the mask.
110
+ *
111
+ * **A mask that agrees with nothing gets no name, and that is a result.** `null` here reaches
112
+ * `proposeEntities` as scenery, which is the safe answer; a name taken from the nearest box
113
+ * whatever the overlap is how a chair becomes a table. `overlap` is where a caller draws that line.
114
+ */
115
+ export function labelMasks(view, detections, queries, overlap = DEFAULT_OVERLAP) {
116
+ let masks = 0;
117
+ for (const mask of view.mask)
118
+ if (mask + 1 > masks)
119
+ masks = mask + 1;
120
+ const out = new Array(masks).fill(null);
121
+ const counts = new Int32Array(masks);
122
+ for (const mask of view.mask)
123
+ if (mask >= 0)
124
+ counts[mask] = counts[mask] + 1;
125
+ /* The best agreement each mask has found so far, so a tie keeps the earlier detection. */
126
+ const best = new Float64Array(masks);
127
+ const inside = new Int32Array(masks);
128
+ for (let at = 0; at < detections.length; at += 1) {
129
+ const box = detections[at].box;
130
+ inside.fill(0);
131
+ for (let y = 0; y < view.height; y += 1) {
132
+ for (let x = 0; x < view.width; x += 1) {
133
+ const mask = view.mask[y * view.width + x];
134
+ if (mask < 0)
135
+ continue;
136
+ if (x + 0.5 < box[0] || x + 0.5 >= box[2])
137
+ continue;
138
+ if (y + 0.5 < box[1] || y + 0.5 >= box[3])
139
+ continue;
140
+ inside[mask] = inside[mask] + 1;
141
+ }
142
+ }
143
+ const area = (box[2] - box[0]) * (box[3] - box[1]);
144
+ for (let mask = 0; mask < masks; mask += 1) {
145
+ const met = inside[mask];
146
+ const union = counts[mask] + area - met;
147
+ const score = union > 0 ? met / union : 0;
148
+ if (score < overlap || score <= best[mask])
149
+ continue;
150
+ best[mask] = score;
151
+ out[mask] = queries[detections[at].label] ?? null;
152
+ }
153
+ }
154
+ return out;
155
+ }
156
+ function centroid(mesh, face, out) {
157
+ for (let k = 0; k < 3; k += 1)
158
+ out[k] = 0;
159
+ for (let slot = 0; slot < 3; slot += 1) {
160
+ const vertex = mesh.indices[face * 3 + slot];
161
+ for (let k = 0; k < 3; k += 1) {
162
+ out[k] = out[k] + mesh.positions[vertex * 3 + k] / 3;
163
+ }
164
+ }
165
+ }
@@ -0,0 +1,68 @@
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
+ * One-sided Jacobi: `m`, `rows × cols` row-major with `rows ≥ cols`, into `u` (`rows × cols`),
24
+ * `s` (`cols`, falling) and `v` (`cols × cols`), where `m = u · diag(s) · vᵀ`.
25
+ */
26
+ export declare function svdN(m: Float64Array, rows: number, cols: number, u: Float64Array, s: Float64Array, v: Float64Array): void;
27
+ /** The same for the 3 × 3 matrices geometry is full of. */
28
+ export declare function svd3(m: Float64Array, u: Float64Array, s: Float64Array, v: Float64Array): void;
29
+ /**
30
+ * Jacobi's eigendecomposition of a symmetric `m`, `n × n`: `values` in falling order and `vectors`
31
+ * by column, so `m · vectors[:, k] = values[k] · vectors[:, k]`.
32
+ */
33
+ export declare function symmetricEigen(m: Float64Array, n: number, values: Float64Array, vectors: Float64Array): void;
34
+ /**
35
+ * The lower triangle `l` with `l · lᵀ = m`, or false where `m` is not positive definite — which is
36
+ * how a caller learns that its normal equations are singular, rather than by finding a NaN later.
37
+ *
38
+ * **`out` may be `m`**: only the lower triangle is read, each element before it is written, so a
39
+ * caller with one matrix to spare factors it in place. Zeroing the whole of `out` first would have
40
+ * erased the matrix in that case, which is a silent wrong answer rather than a refusal.
41
+ */
42
+ export declare function cholesky(m: Float64Array, n: number, out: Float64Array): boolean;
43
+ /** Solves `l · lᵀ · x = b` in place over `x`, given the Cholesky factor. */
44
+ export declare function choleskySolve(l: Float64Array, n: number, x: Float64Array): void;
45
+ /** A least-squares problem: how many residuals, how many parameters, and both at a point. */
46
+ export interface LeastSquares {
47
+ readonly residuals: number;
48
+ readonly parameters: number;
49
+ /** The residuals at `x`, and — unless `jacobian` is null — their derivatives, row by residual. */
50
+ evaluate(x: Float64Array, residuals: Float64Array, jacobian: Float64Array | null): void;
51
+ }
52
+ export interface FitOptions {
53
+ /** The first damping; **zero is plain Gauss–Newton**, steps taken whatever they cost. */
54
+ readonly damping?: number;
55
+ readonly maxIterations?: number;
56
+ /** Stop once a step moves every parameter less than this. */
57
+ readonly tolerance?: number;
58
+ }
59
+ /**
60
+ * Levenberg–Marquardt over `x`, in place: the Gauss–Newton step with `damping · diag(JᵀJ)` added,
61
+ * the damping raised where a step would cost more and lowered where it pays, which is the whole
62
+ * reason it is not Gauss–Newton — **a step that overshoots is rejected rather than taken**, and a
63
+ * start far from the answer is where that decides between converging and diverging.
64
+ */
65
+ export declare function levenbergMarquardt(problem: LeastSquares, x: Float64Array, options?: FitOptions): {
66
+ readonly iterations: number;
67
+ readonly cost: number;
68
+ };
@@ -0,0 +1,294 @@
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
+ /** How far off the diagonal a Jacobi sweep tolerates before it stops. */
23
+ const SETTLED = 1e-15;
24
+ const SWEEPS = 60;
25
+ /**
26
+ * One-sided Jacobi: `m`, `rows × cols` row-major with `rows ≥ cols`, into `u` (`rows × cols`),
27
+ * `s` (`cols`, falling) and `v` (`cols × cols`), where `m = u · diag(s) · vᵀ`.
28
+ */
29
+ export function svdN(m, rows, cols, u, s, v) {
30
+ if (rows < cols) {
31
+ throw new RangeError(`a ${rows}×${cols} matrix has more columns than rows; transpose it first`);
32
+ }
33
+ const a = Float64Array.from(m);
34
+ v.fill(0);
35
+ for (let i = 0; i < cols; i += 1)
36
+ v[i * cols + i] = 1;
37
+ for (let sweep = 0; sweep < SWEEPS; sweep += 1) {
38
+ let off = 0;
39
+ for (let p = 0; p < cols - 1; p += 1) {
40
+ for (let q = p + 1; q < cols; q += 1) {
41
+ let alpha = 0;
42
+ let beta = 0;
43
+ let gamma = 0;
44
+ for (let r = 0; r < rows; r += 1) {
45
+ const left = a[r * cols + p];
46
+ const right = a[r * cols + q];
47
+ alpha += left * left;
48
+ beta += right * right;
49
+ gamma += left * right;
50
+ }
51
+ if (gamma === 0)
52
+ continue;
53
+ const scale = Math.sqrt(alpha * beta);
54
+ off = Math.max(off, scale === 0 ? 0 : Math.abs(gamma) / scale);
55
+ if (scale === 0 || Math.abs(gamma) <= SETTLED * scale)
56
+ continue;
57
+ /* The rotation that makes the two columns orthogonal, by the stable root. */
58
+ const zeta = (beta - alpha) / (2 * gamma);
59
+ const t = (zeta >= 0 ? 1 : -1) / (Math.abs(zeta) + Math.sqrt(1 + zeta * zeta));
60
+ const c = 1 / Math.sqrt(1 + t * t);
61
+ const sn = c * t;
62
+ for (let r = 0; r < rows; r += 1) {
63
+ const left = a[r * cols + p];
64
+ const right = a[r * cols + q];
65
+ a[r * cols + p] = c * left - sn * right;
66
+ a[r * cols + q] = sn * left + c * right;
67
+ }
68
+ for (let r = 0; r < cols; r += 1) {
69
+ const left = v[r * cols + p];
70
+ const right = v[r * cols + q];
71
+ v[r * cols + p] = c * left - sn * right;
72
+ v[r * cols + q] = sn * left + c * right;
73
+ }
74
+ }
75
+ }
76
+ if (off <= SETTLED)
77
+ break;
78
+ }
79
+ /* What is left of each column is its singular value times a unit vector. */
80
+ const order = Array.from({ length: cols }, (_, i) => i);
81
+ const lengths = new Float64Array(cols);
82
+ for (let c = 0; c < cols; c += 1) {
83
+ let sum = 0;
84
+ for (let r = 0; r < rows; r += 1) {
85
+ const value = a[r * cols + c];
86
+ sum += value * value;
87
+ }
88
+ lengths[c] = Math.sqrt(sum);
89
+ }
90
+ order.sort((p, q) => lengths[q] - lengths[p]);
91
+ const held = Float64Array.from(v);
92
+ for (const [at, from] of order.entries()) {
93
+ const length = lengths[from];
94
+ s[at] = length;
95
+ for (let r = 0; r < rows; r += 1) {
96
+ u[r * cols + at] = length === 0 ? 0 : a[r * cols + from] / length;
97
+ }
98
+ for (let r = 0; r < cols; r += 1)
99
+ v[r * cols + at] = held[r * cols + from];
100
+ }
101
+ }
102
+ /** The same for the 3 × 3 matrices geometry is full of. */
103
+ export function svd3(m, u, s, v) {
104
+ svdN(m, 3, 3, u, s, v);
105
+ }
106
+ /**
107
+ * Jacobi's eigendecomposition of a symmetric `m`, `n × n`: `values` in falling order and `vectors`
108
+ * by column, so `m · vectors[:, k] = values[k] · vectors[:, k]`.
109
+ */
110
+ export function symmetricEigen(m, n, values, vectors) {
111
+ const a = Float64Array.from(m);
112
+ vectors.fill(0);
113
+ for (let i = 0; i < n; i += 1)
114
+ vectors[i * n + i] = 1;
115
+ for (let sweep = 0; sweep < SWEEPS; sweep += 1) {
116
+ let off = 0;
117
+ for (let p = 0; p < n - 1; p += 1) {
118
+ for (let q = p + 1; q < n; q += 1)
119
+ off = Math.max(off, Math.abs(a[p * n + q]));
120
+ }
121
+ if (off <= SETTLED)
122
+ break;
123
+ for (let p = 0; p < n - 1; p += 1) {
124
+ for (let q = p + 1; q < n; q += 1) {
125
+ const pivot = a[p * n + q];
126
+ if (Math.abs(pivot) <= SETTLED * off)
127
+ continue;
128
+ const zeta = (a[q * n + q] - a[p * n + p]) / (2 * pivot);
129
+ const t = (zeta >= 0 ? 1 : -1) / (Math.abs(zeta) + Math.sqrt(1 + zeta * zeta));
130
+ const c = 1 / Math.sqrt(1 + t * t);
131
+ const sn = c * t;
132
+ for (let k = 0; k < n; k += 1) {
133
+ const left = a[k * n + p];
134
+ const right = a[k * n + q];
135
+ a[k * n + p] = c * left - sn * right;
136
+ a[k * n + q] = sn * left + c * right;
137
+ }
138
+ for (let k = 0; k < n; k += 1) {
139
+ const left = a[p * n + k];
140
+ const right = a[q * n + k];
141
+ a[p * n + k] = c * left - sn * right;
142
+ a[q * n + k] = sn * left + c * right;
143
+ }
144
+ for (let k = 0; k < n; k += 1) {
145
+ const left = vectors[k * n + p];
146
+ const right = vectors[k * n + q];
147
+ vectors[k * n + p] = c * left - sn * right;
148
+ vectors[k * n + q] = sn * left + c * right;
149
+ }
150
+ }
151
+ }
152
+ }
153
+ const order = Array.from({ length: n }, (_, i) => i);
154
+ order.sort((p, q) => a[q * n + q] - a[p * n + p]);
155
+ const held = Float64Array.from(vectors);
156
+ for (const [at, from] of order.entries()) {
157
+ values[at] = a[from * n + from];
158
+ for (let r = 0; r < n; r += 1)
159
+ vectors[r * n + at] = held[r * n + from];
160
+ }
161
+ }
162
+ /**
163
+ * The lower triangle `l` with `l · lᵀ = m`, or false where `m` is not positive definite — which is
164
+ * how a caller learns that its normal equations are singular, rather than by finding a NaN later.
165
+ *
166
+ * **`out` may be `m`**: only the lower triangle is read, each element before it is written, so a
167
+ * caller with one matrix to spare factors it in place. Zeroing the whole of `out` first would have
168
+ * erased the matrix in that case, which is a silent wrong answer rather than a refusal.
169
+ */
170
+ export function cholesky(m, n, out) {
171
+ for (let r = 0; r < n; r += 1) {
172
+ for (let c = r + 1; c < n; c += 1)
173
+ out[r * n + c] = 0;
174
+ for (let c = 0; c <= r; c += 1) {
175
+ let sum = m[r * n + c];
176
+ for (let k = 0; k < c; k += 1)
177
+ sum -= out[r * n + k] * out[c * n + k];
178
+ if (r === c) {
179
+ if (!(sum > 0))
180
+ return false;
181
+ out[r * n + c] = Math.sqrt(sum);
182
+ }
183
+ else {
184
+ out[r * n + c] = sum / out[c * n + c];
185
+ }
186
+ }
187
+ }
188
+ return true;
189
+ }
190
+ /** Solves `l · lᵀ · x = b` in place over `x`, given the Cholesky factor. */
191
+ export function choleskySolve(l, n, x) {
192
+ for (let r = 0; r < n; r += 1) {
193
+ let sum = x[r];
194
+ for (let k = 0; k < r; k += 1)
195
+ sum -= l[r * n + k] * x[k];
196
+ x[r] = sum / l[r * n + r];
197
+ }
198
+ for (let r = n - 1; r >= 0; r -= 1) {
199
+ let sum = x[r];
200
+ for (let k = r + 1; k < n; k += 1)
201
+ sum -= l[k * n + r] * x[k];
202
+ x[r] = sum / l[r * n + r];
203
+ }
204
+ }
205
+ /**
206
+ * Levenberg–Marquardt over `x`, in place: the Gauss–Newton step with `damping · diag(JᵀJ)` added,
207
+ * the damping raised where a step would cost more and lowered where it pays, which is the whole
208
+ * reason it is not Gauss–Newton — **a step that overshoots is rejected rather than taken**, and a
209
+ * start far from the answer is where that decides between converging and diverging.
210
+ */
211
+ export function levenbergMarquardt(problem, x, options = {}) {
212
+ const { residuals: rows, parameters: columns } = problem;
213
+ const maxIterations = options.maxIterations ?? 100;
214
+ const tolerance = options.tolerance ?? 1e-14;
215
+ let damping = options.damping ?? 1e-3;
216
+ const undamped = damping === 0;
217
+ const residual = new Float64Array(rows);
218
+ const jacobian = new Float64Array(rows * columns);
219
+ const normals = new Float64Array(columns * columns);
220
+ const factor = new Float64Array(columns * columns);
221
+ const gradient = new Float64Array(columns);
222
+ const step = new Float64Array(columns);
223
+ const trial = new Float64Array(columns);
224
+ const costOf = (values) => {
225
+ let sum = 0;
226
+ for (let i = 0; i < values.length; i += 1)
227
+ sum += values[i] * values[i];
228
+ return sum;
229
+ };
230
+ problem.evaluate(x, residual, null);
231
+ let cost = costOf(residual);
232
+ let iterations = 0;
233
+ while (iterations < maxIterations) {
234
+ iterations += 1;
235
+ problem.evaluate(x, residual, jacobian);
236
+ normals.fill(0);
237
+ gradient.fill(0);
238
+ for (let r = 0; r < rows; r += 1) {
239
+ for (let p = 0; p < columns; p += 1) {
240
+ const value = jacobian[r * columns + p];
241
+ gradient[p] = gradient[p] - value * residual[r];
242
+ for (let q = p; q < columns; q += 1) {
243
+ normals[p * columns + q] =
244
+ normals[p * columns + q] + value * jacobian[r * columns + q];
245
+ }
246
+ }
247
+ }
248
+ for (let p = 0; p < columns; p += 1) {
249
+ for (let q = 0; q < p; q += 1)
250
+ normals[p * columns + q] = normals[q * columns + p];
251
+ }
252
+ let taken = false;
253
+ for (let attempt = 0; attempt < 12 && !taken; attempt += 1) {
254
+ factor.set(normals);
255
+ for (let p = 0; p < columns; p += 1) {
256
+ /* Marquardt's scaling: the damping rides each parameter's own curvature. */
257
+ const diagonal = normals[p * columns + p];
258
+ factor[p * columns + p] = diagonal * (1 + damping) + damping;
259
+ }
260
+ if (!cholesky(factor, columns, factor)) {
261
+ if (undamped)
262
+ return { iterations, cost };
263
+ damping *= 10;
264
+ continue;
265
+ }
266
+ step.set(gradient);
267
+ choleskySolve(factor, columns, step);
268
+ let moved = 0;
269
+ for (let p = 0; p < columns; p += 1) {
270
+ trial[p] = x[p] + step[p];
271
+ moved = Math.max(moved, Math.abs(step[p]));
272
+ }
273
+ problem.evaluate(trial, residual, null);
274
+ const next = costOf(residual);
275
+ if (undamped || next < cost) {
276
+ x.set(trial);
277
+ cost = next;
278
+ taken = true;
279
+ if (!undamped)
280
+ damping = Math.max(damping / 10, 1e-12);
281
+ if (moved <= tolerance)
282
+ return { iterations, cost };
283
+ }
284
+ else {
285
+ damping *= 10;
286
+ if (damping > 1e12)
287
+ return { iterations, cost };
288
+ }
289
+ }
290
+ if (!taken)
291
+ return { iterations, cost };
292
+ }
293
+ return { iterations, cost };
294
+ }
@@ -0,0 +1,23 @@
1
+ /** The normal equations in blocks: cameras, points of three, and one link per observation. */
2
+ export interface BlockNormals {
3
+ readonly cameras: number;
4
+ /** Parameters a camera; the points are three each. */
5
+ readonly cameraSize: number;
6
+ readonly points: number;
7
+ /** `cameras · cameraSize²`, each symmetric and row-major. */
8
+ readonly cameraBlocks: Float64Array;
9
+ /** `points · 9`, each symmetric and row-major. */
10
+ readonly pointBlocks: Float64Array;
11
+ /** `links · cameraSize · 3`, row-major: a camera's rows against a point's columns. */
12
+ readonly links: Float64Array;
13
+ readonly linkCamera: Int32Array;
14
+ readonly linkPoint: Int32Array;
15
+ /** The right-hand side: `cameras · cameraSize` and `points · 3`. */
16
+ readonly cameraGradient: Float64Array;
17
+ readonly pointGradient: Float64Array;
18
+ }
19
+ /**
20
+ * The step for `damping`, into `cameraStep` (`cameras · cameraSize`) and `pointStep`
21
+ * (`points · 3`); false where a point's block or the reduced system cannot be factored.
22
+ */
23
+ export declare function schurSolve(normals: BlockNormals, damping: number, cameraStep: Float64Array, pointStep: Float64Array): boolean;