@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.
- package/LICENSE +202 -0
- package/NOTICE +29 -0
- package/README.md +68 -0
- package/dist/browserFrames.d.ts +31 -0
- package/dist/browserFrames.js +105 -0
- package/dist/collide.d.ts +59 -0
- package/dist/collide.js +92 -0
- package/dist/decimate.d.ts +42 -0
- package/dist/decimate.js +439 -0
- package/dist/delight.d.ts +42 -0
- package/dist/delight.js +351 -0
- package/dist/delightScene.d.ts +37 -0
- package/dist/delightScene.js +183 -0
- package/dist/depth.d.ts +44 -0
- package/dist/depth.js +53 -0
- package/dist/features.d.ts +33 -0
- package/dist/features.js +260 -0
- package/dist/frames.d.ts +47 -0
- package/dist/frames.js +87 -0
- package/dist/fusion.d.ts +77 -0
- package/dist/fusion.js +152 -0
- package/dist/gaussians/adam.d.ts +57 -0
- package/dist/gaussians/adam.js +88 -0
- package/dist/gaussians/depth.d.ts +8 -0
- package/dist/gaussians/depth.js +73 -0
- package/dist/gaussians/gradients.d.ts +36 -0
- package/dist/gaussians/gradients.js +228 -0
- package/dist/gaussians/harmonics.d.ts +46 -0
- package/dist/gaussians/harmonics.js +99 -0
- package/dist/gaussians/optimise.d.ts +50 -0
- package/dist/gaussians/optimise.js +125 -0
- package/dist/gaussians/parameters.d.ts +35 -0
- package/dist/gaussians/parameters.js +165 -0
- package/dist/gaussians/project.d.ts +72 -0
- package/dist/gaussians/project.js +137 -0
- package/dist/gaussians/projectGradient.d.ts +15 -0
- package/dist/gaussians/projectGradient.js +213 -0
- package/dist/gaussians/rasterise.d.ts +13 -0
- package/dist/gaussians/rasterise.js +65 -0
- package/dist/gaussians/refine.d.ts +36 -0
- package/dist/gaussians/refine.js +136 -0
- package/dist/gaussians/similarity.d.ts +25 -0
- package/dist/gaussians/similarity.js +80 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +96 -0
- package/dist/marching.d.ts +38 -0
- package/dist/marching.js +258 -0
- package/dist/masks.d.ts +59 -0
- package/dist/masks.js +165 -0
- package/dist/math/dense.d.ts +68 -0
- package/dist/math/dense.js +294 -0
- package/dist/math/schur.d.ts +23 -0
- package/dist/math/schur.js +140 -0
- package/dist/models/batchNorm.d.ts +17 -0
- package/dist/models/batchNorm.js +24 -0
- package/dist/models/clipTokenizer.d.ts +40 -0
- package/dist/models/clipTokenizer.js +122 -0
- package/dist/models/clipTower.d.ts +27 -0
- package/dist/models/clipTower.js +45 -0
- package/dist/models/depthAnything2.d.ts +38 -0
- package/dist/models/depthAnything2.js +163 -0
- package/dist/models/depthAnything3.d.ts +26 -0
- package/dist/models/depthAnything3.js +171 -0
- package/dist/models/dualDpt.d.ts +19 -0
- package/dist/models/dualDpt.js +138 -0
- package/dist/models/hiera.d.ts +49 -0
- package/dist/models/hiera.js +120 -0
- package/dist/models/hieraAttention.d.ts +16 -0
- package/dist/models/hieraAttention.js +68 -0
- package/dist/models/miniature.d.ts +36 -0
- package/dist/models/miniature.js +216 -0
- package/dist/models/mobileSam.d.ts +41 -0
- package/dist/models/mobileSam.js +88 -0
- package/dist/models/owlv2.d.ts +49 -0
- package/dist/models/owlv2.js +131 -0
- package/dist/models/owlv2Detect.d.ts +25 -0
- package/dist/models/owlv2Detect.js +80 -0
- package/dist/models/owlv2Miniature.d.ts +27 -0
- package/dist/models/owlv2Miniature.js +87 -0
- package/dist/models/rope.d.ts +23 -0
- package/dist/models/rope.js +84 -0
- package/dist/models/sam21.d.ts +45 -0
- package/dist/models/sam21.js +126 -0
- package/dist/models/sam21Miniature.d.ts +37 -0
- package/dist/models/sam21Miniature.js +194 -0
- package/dist/models/sam21Video.d.ts +30 -0
- package/dist/models/sam21Video.js +101 -0
- package/dist/models/sam2Choice.d.ts +19 -0
- package/dist/models/sam2Choice.js +55 -0
- package/dist/models/sam2Decoder.d.ts +36 -0
- package/dist/models/sam2Decoder.js +108 -0
- package/dist/models/sam2Memories.d.ts +54 -0
- package/dist/models/sam2Memories.js +93 -0
- package/dist/models/sam2Memory.d.ts +30 -0
- package/dist/models/sam2Memory.js +136 -0
- package/dist/models/sam2Positions.d.ts +24 -0
- package/dist/models/sam2Positions.js +94 -0
- package/dist/models/sam2Tracker.d.ts +69 -0
- package/dist/models/sam2Tracker.js +172 -0
- package/dist/models/samDecoder.d.ts +45 -0
- package/dist/models/samDecoder.js +96 -0
- package/dist/models/samMiniature.d.ts +43 -0
- package/dist/models/samMiniature.js +161 -0
- package/dist/models/samPrompt.d.ts +57 -0
- package/dist/models/samPrompt.js +160 -0
- package/dist/models/seeded.d.ts +16 -0
- package/dist/models/seeded.js +48 -0
- package/dist/models/tinyVit.d.ts +38 -0
- package/dist/models/tinyVit.js +82 -0
- package/dist/models/twoWay.d.ts +46 -0
- package/dist/models/twoWay.js +67 -0
- package/dist/models/vit.d.ts +45 -0
- package/dist/models/vit.js +157 -0
- package/dist/models/windowAttention.d.ts +25 -0
- package/dist/models/windowAttention.js +104 -0
- package/dist/poses.d.ts +31 -0
- package/dist/poses.js +750 -0
- package/dist/prepare.d.ts +30 -0
- package/dist/prepare.js +238 -0
- package/dist/propose.d.ts +36 -0
- package/dist/propose.js +63 -0
- package/dist/resample.d.ts +39 -0
- package/dist/resample.js +165 -0
- package/dist/run.d.ts +3 -0
- package/dist/run.js +1 -0
- package/dist/scene.d.ts +104 -0
- package/dist/scene.js +109 -0
- package/dist/segment.d.ts +40 -0
- package/dist/segment.js +184 -0
- package/dist/testScene.d.ts +55 -0
- package/dist/testScene.js +214 -0
- package/dist/twoView.d.ts +22 -0
- package/dist/twoView.js +435 -0
- package/package.json +63 -0
- package/src/browserFrames.ts +145 -0
- package/src/collide.ts +128 -0
- package/src/decimate.ts +510 -0
- package/src/delight.ts +427 -0
- package/src/delightScene.ts +249 -0
- package/src/depth.ts +100 -0
- package/src/features.ts +302 -0
- package/src/frames.ts +111 -0
- package/src/fusion.ts +229 -0
- package/src/gaussians/adam.ts +131 -0
- package/src/gaussians/depth.ts +87 -0
- package/src/gaussians/gradients.ts +285 -0
- package/src/gaussians/harmonics.ts +122 -0
- package/src/gaussians/optimise.ts +191 -0
- package/src/gaussians/parameters.ts +186 -0
- package/src/gaussians/project.ts +207 -0
- package/src/gaussians/projectGradient.ts +234 -0
- package/src/gaussians/rasterise.ts +81 -0
- package/src/gaussians/refine.ts +176 -0
- package/src/gaussians/similarity.ts +87 -0
- package/src/index.ts +225 -0
- package/src/marching.ts +358 -0
- package/src/masks.ts +201 -0
- package/src/math/dense.ts +317 -0
- package/src/math/schur.ts +167 -0
- package/src/models/batchNorm.ts +57 -0
- package/src/models/clipTokenizer.ts +129 -0
- package/src/models/clipTower.ts +87 -0
- package/src/models/depthAnything2.ts +276 -0
- package/src/models/depthAnything3.ts +229 -0
- package/src/models/dualDpt.ts +233 -0
- package/src/models/hiera.ts +231 -0
- package/src/models/hieraAttention.ts +121 -0
- package/src/models/miniature.ts +241 -0
- package/src/models/mobileSam.ts +135 -0
- package/src/models/owlv2.ts +190 -0
- package/src/models/owlv2Detect.ts +116 -0
- package/src/models/owlv2Miniature.ts +108 -0
- package/src/models/rope.ts +111 -0
- package/src/models/sam21.ts +182 -0
- package/src/models/sam21Miniature.ts +225 -0
- package/src/models/sam21Video.ts +154 -0
- package/src/models/sam2Choice.ts +62 -0
- package/src/models/sam2Decoder.ts +218 -0
- package/src/models/sam2Memories.ts +165 -0
- package/src/models/sam2Memory.ts +247 -0
- package/src/models/sam2Positions.ts +106 -0
- package/src/models/sam2Tracker.ts +300 -0
- package/src/models/samDecoder.ts +225 -0
- package/src/models/samMiniature.ts +190 -0
- package/src/models/samPrompt.ts +227 -0
- package/src/models/seeded.ts +50 -0
- package/src/models/tinyVit.ts +178 -0
- package/src/models/twoWay.ts +122 -0
- package/src/models/vit.ts +265 -0
- package/src/models/windowAttention.ts +155 -0
- package/src/poses.ts +894 -0
- package/src/prepare.ts +306 -0
- package/src/propose.ts +96 -0
- package/src/resample.ts +197 -0
- package/src/run.ts +7 -0
- package/src/scene.ts +141 -0
- package/src/segment.ts +220 -0
- package/src/testScene.ts +283 -0
- package/src/twoView.ts +536 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `[2·features, height, width]`: the upstream's normalised sine embedding — a row's and a column's
|
|
3
|
+
* index from one, over the last and times 2π, each divided by `features` frequencies taken in pairs,
|
|
4
|
+
* sine then cosine alternating — with the rows' `features` channels before the columns'.
|
|
5
|
+
*/
|
|
6
|
+
export declare function sam2SinePositions(features: number, height: number, width: number): Float32Array;
|
|
7
|
+
/**
|
|
8
|
+
* `[positions.length, dim]`: the upstream's `get_1d_sine_pe` — each position divided by `dim / 2`
|
|
9
|
+
* frequencies taken in pairs, its sines then its cosines.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sam2PointerPositions(positions: readonly number[], dim: number): Float32Array;
|
|
12
|
+
/**
|
|
13
|
+
* The memory attention's axial rotary tables over a `width` by `height` grid, row by row, for a
|
|
14
|
+
* head of `dim`, at the upstream's base of 10,000 — its configuration's `rope_theta`, the one value
|
|
15
|
+
* any checkpoint here uses: `[cells, dim]` cosines and sines, the first half of the channels turning by the
|
|
16
|
+
* column and the second by the row, each frequency on a pair of channels. **The sine carries the
|
|
17
|
+
* rotation's sign** — negative on a pair's first channel, positive on its second — so a rotation is
|
|
18
|
+
* `x·cos + swap(x)·sin`, `swap` exchanging each pair, which a graph composes from its shape
|
|
19
|
+
* operators.
|
|
20
|
+
*/
|
|
21
|
+
export declare function sam2Rotary(dim: number, width: number, height: number): {
|
|
22
|
+
readonly cos: Float32Array;
|
|
23
|
+
readonly sin: Float32Array;
|
|
24
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAM 2's position tables, which depend only on shapes: the feature pyramid's and the memory's
|
|
3
|
+
* two-dimensional sines, the object pointers' one-dimensional sines, and the memory attention's
|
|
4
|
+
* rotary tables. Each is laid out once per size and used as a constant of a graph or by the host.
|
|
5
|
+
*
|
|
6
|
+
* **Every rounding is the upstream's, in single precision**: a coordinate normalised to the grid's
|
|
7
|
+
* extent, divided by its frequency, and only then taken through `sin` or `cos` — the engine's
|
|
8
|
+
* reproducible ones, since a capture reproduces. A frequency is `10000^t`, taken as `exp(t·ln 10000)`
|
|
9
|
+
* rather than `**`, which is not the same bits on every engine.
|
|
10
|
+
*/
|
|
11
|
+
import { exactCos, exactExp, exactSin } from '@driftengine/core';
|
|
12
|
+
const f = Math.fround;
|
|
13
|
+
const TWO_PI = f(2 * Math.PI);
|
|
14
|
+
/** `ln 10000`, the temperature's logarithm. */
|
|
15
|
+
const LOG_TEMPERATURE = 9.210340371976184;
|
|
16
|
+
/* `10000^(2·⌊i/2⌋/count)` in single precision, as the upstream's `temperature ** (...)` makes it. */
|
|
17
|
+
function frequency(i, count) {
|
|
18
|
+
return f(exactExp(f((2 * Math.floor(i / 2)) / count) * LOG_TEMPERATURE));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* `[2·features, height, width]`: the upstream's normalised sine embedding — a row's and a column's
|
|
22
|
+
* index from one, over the last and times 2π, each divided by `features` frequencies taken in pairs,
|
|
23
|
+
* sine then cosine alternating — with the rows' `features` channels before the columns'.
|
|
24
|
+
*/
|
|
25
|
+
export function sam2SinePositions(features, height, width) {
|
|
26
|
+
const out = new Float32Array(2 * features * height * width);
|
|
27
|
+
const plane = height * width;
|
|
28
|
+
const embed = (index, count) => f(f((index + 1) / f(count + 1e-6)) * TWO_PI);
|
|
29
|
+
for (let i = 0; i < features; i += 1) {
|
|
30
|
+
const divisor = frequency(i, features);
|
|
31
|
+
const wave = i % 2 === 0 ? exactSin : exactCos;
|
|
32
|
+
for (let y = 0; y < height; y += 1) {
|
|
33
|
+
const row = wave(f(embed(y, height) / divisor));
|
|
34
|
+
for (let x = 0; x < width; x += 1) {
|
|
35
|
+
out[i * plane + y * width + x] = row;
|
|
36
|
+
out[(features + i) * plane + y * width + x] = wave(f(embed(x, width) / divisor));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `[positions.length, dim]`: the upstream's `get_1d_sine_pe` — each position divided by `dim / 2`
|
|
44
|
+
* frequencies taken in pairs, its sines then its cosines.
|
|
45
|
+
*/
|
|
46
|
+
export function sam2PointerPositions(positions, dim) {
|
|
47
|
+
const half = dim / 2;
|
|
48
|
+
const out = new Float32Array(positions.length * dim);
|
|
49
|
+
for (let p = 0; p < positions.length; p += 1) {
|
|
50
|
+
for (let k = 0; k < half; k += 1) {
|
|
51
|
+
const angle = f(f(positions[p]) / frequency(k, half));
|
|
52
|
+
out[p * dim + k] = exactSin(angle);
|
|
53
|
+
out[p * dim + half + k] = exactCos(angle);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The memory attention's axial rotary tables over a `width` by `height` grid, row by row, for a
|
|
60
|
+
* head of `dim`, at the upstream's base of 10,000 — its configuration's `rope_theta`, the one value
|
|
61
|
+
* any checkpoint here uses: `[cells, dim]` cosines and sines, the first half of the channels turning by the
|
|
62
|
+
* column and the second by the row, each frequency on a pair of channels. **The sine carries the
|
|
63
|
+
* rotation's sign** — negative on a pair's first channel, positive on its second — so a rotation is
|
|
64
|
+
* `x·cos + swap(x)·sin`, `swap` exchanging each pair, which a graph composes from its shape
|
|
65
|
+
* operators.
|
|
66
|
+
*/
|
|
67
|
+
export function sam2Rotary(dim, width, height) {
|
|
68
|
+
const spatial = dim / 2;
|
|
69
|
+
const count = spatial / 2;
|
|
70
|
+
const inverse = Float32Array.from({ length: count }, (_, k) => f(1 / f(exactExp(f((2 * k) / spatial) * LOG_TEMPERATURE))));
|
|
71
|
+
const cells = width * height;
|
|
72
|
+
const cos = new Float32Array(cells * dim);
|
|
73
|
+
const sin = new Float32Array(cells * dim);
|
|
74
|
+
for (let y = 0; y < height; y += 1) {
|
|
75
|
+
for (let x = 0; x < width; x += 1) {
|
|
76
|
+
const at = (y * width + x) * dim;
|
|
77
|
+
for (let k = 0; k < count; k += 1) {
|
|
78
|
+
for (const [offset, position] of [
|
|
79
|
+
[0, x],
|
|
80
|
+
[spatial, y],
|
|
81
|
+
]) {
|
|
82
|
+
const angle = f(position * inverse[k]);
|
|
83
|
+
const c = exactCos(angle);
|
|
84
|
+
const s = exactSin(angle);
|
|
85
|
+
cos[at + offset + 2 * k] = c;
|
|
86
|
+
cos[at + offset + 2 * k + 1] = c;
|
|
87
|
+
sin[at + offset + 2 * k] = -s;
|
|
88
|
+
sin[at + offset + 2 * k + 1] = s;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { cos, sin };
|
|
94
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAM 2.1 tracking objects through a video: the upstream's `Sam2VideoModel` loop on the host, its
|
|
3
|
+
* networks as the engine's graphs, run by whatever the caller gives — the device's runner, or the
|
|
4
|
+
* CPU's evaluator.
|
|
5
|
+
*
|
|
6
|
+
* **A frame is decoded against what the tracker remembers.** A prompted frame, the first time, reads
|
|
7
|
+
* nothing and is given `no_memory_embedding`; every other frame's features are first conditioned by
|
|
8
|
+
* the memory attention on the frames and pointers `sam2Memories.ts` chooses, and `sam2Choice.ts`
|
|
9
|
+
* keeps one of the four masks decoded.
|
|
10
|
+
*
|
|
11
|
+
* **Then each object's frame becomes a memory**, all of a frame's objects together: the mask
|
|
12
|
+
* brought to the frame's size, binarised when any of them was just prompted and taken through a
|
|
13
|
+
* sigmoid otherwise — the upstream decides it once for the batch — encoded with the frame's
|
|
14
|
+
* features, the occlusion embedding added where the object is absent, and rounded to bfloat16.
|
|
15
|
+
*
|
|
16
|
+
* What it gives up: a mask as a prompt, which the upstream downsamples with an antialiased resize
|
|
17
|
+
* the runtime does not yet have, and more prompted frames than all of them — the upstream's default
|
|
18
|
+
* — are the two options not taken.
|
|
19
|
+
*/
|
|
20
|
+
import type { WeightSource } from '@driftengine/texture';
|
|
21
|
+
import type { GraphRun } from '../run.ts';
|
|
22
|
+
import { type Sam21Config } from './sam21.ts';
|
|
23
|
+
import { type SamPrompt } from './samPrompt.ts';
|
|
24
|
+
export type { GraphRun } from '../run.ts';
|
|
25
|
+
/** The weights each graph reads: a converted file's graphs, or a checkpoint for all four. */
|
|
26
|
+
export interface Sam21Weights {
|
|
27
|
+
readonly encoder: WeightSource;
|
|
28
|
+
readonly decoder: WeightSource;
|
|
29
|
+
readonly memoryEncoder: WeightSource;
|
|
30
|
+
readonly memoryAttention: WeightSource;
|
|
31
|
+
}
|
|
32
|
+
export interface Sam21Video {
|
|
33
|
+
readonly frames: number;
|
|
34
|
+
/** The original frame's size, which prompts are given in. */
|
|
35
|
+
readonly height: number;
|
|
36
|
+
readonly width: number;
|
|
37
|
+
/** A frame prepared as the encoder takes it, `[3, size, size]`. */
|
|
38
|
+
frame(index: number): Float32Array;
|
|
39
|
+
}
|
|
40
|
+
/** A frame's result: every object's mask logits at a quarter of the encoder's size, and its score. */
|
|
41
|
+
export interface Sam21Frame {
|
|
42
|
+
readonly frame: number;
|
|
43
|
+
readonly objects: readonly number[];
|
|
44
|
+
/** `[objects, (size / 4)²]`. */
|
|
45
|
+
readonly masks: Float32Array;
|
|
46
|
+
readonly scores: Float32Array;
|
|
47
|
+
}
|
|
48
|
+
export declare class Sam21Tracker {
|
|
49
|
+
private readonly config;
|
|
50
|
+
private readonly weights;
|
|
51
|
+
private readonly run;
|
|
52
|
+
private readonly video;
|
|
53
|
+
private readonly objects;
|
|
54
|
+
private fresh;
|
|
55
|
+
private readonly graphs;
|
|
56
|
+
private encoded;
|
|
57
|
+
private readonly cellPositions;
|
|
58
|
+
constructor(config: Sam21Config, weights: Sam21Weights, run: GraphRun, video: Sam21Video);
|
|
59
|
+
/** Prompts on `frame`, replacing each object's earlier ones there; the next step decodes them. */
|
|
60
|
+
prompt(frame: number, prompts: ReadonlyMap<number, SamPrompt>): void;
|
|
61
|
+
/** Every object on `frame`, as the upstream's `forward` over its session answers it. */
|
|
62
|
+
step(frame: number, reverse?: boolean): Promise<Sam21Frame>;
|
|
63
|
+
private graph;
|
|
64
|
+
private features;
|
|
65
|
+
private decode;
|
|
66
|
+
private condition;
|
|
67
|
+
private remember;
|
|
68
|
+
private tensor;
|
|
69
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { graphFromWeights } from '@driftengine/texture';
|
|
2
|
+
import { memoriesFor, memoryTokens, pointersFor, toBfloat16, } from './sam2Memories.js';
|
|
3
|
+
import { chooseMask } from './sam2Choice.js';
|
|
4
|
+
import { sam2SinePositions } from './sam2Positions.js';
|
|
5
|
+
import { sam21Decoder, sam21Encoder } from './sam21.js';
|
|
6
|
+
import { sam21MemoryAttention, sam21MemoryEncoder, sam21Upscale } from './sam21Video.js';
|
|
7
|
+
import { SAM2_VIDEO_PROMPT, samPromptTokens, samTokenCount } from './samPrompt.js';
|
|
8
|
+
const NO_WEIGHTS = { get: () => undefined, names: () => [] };
|
|
9
|
+
export class Sam21Tracker {
|
|
10
|
+
config;
|
|
11
|
+
weights;
|
|
12
|
+
run;
|
|
13
|
+
video;
|
|
14
|
+
objects = new Map();
|
|
15
|
+
fresh = [];
|
|
16
|
+
graphs = new Map();
|
|
17
|
+
encoded = null;
|
|
18
|
+
cellPositions;
|
|
19
|
+
constructor(config, weights, run, video) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.weights = weights;
|
|
22
|
+
this.run = run;
|
|
23
|
+
this.video = video;
|
|
24
|
+
const grid = config.size / 16;
|
|
25
|
+
const width = config.memory.dim;
|
|
26
|
+
const sine = sam2SinePositions(width / 2, grid, grid);
|
|
27
|
+
this.cellPositions = new Float32Array(grid * grid * width);
|
|
28
|
+
for (let c = 0; c < width; c += 1) {
|
|
29
|
+
for (let i = 0; i < grid * grid; i += 1)
|
|
30
|
+
this.cellPositions[i * width + c] = sine[c * grid * grid + i];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Prompts on `frame`, replacing each object's earlier ones there; the next step decodes them. */
|
|
34
|
+
prompt(frame, prompts) {
|
|
35
|
+
for (const [object, prompt] of prompts) {
|
|
36
|
+
let state = this.objects.get(object);
|
|
37
|
+
if (state === undefined) {
|
|
38
|
+
state = { prompts: new Map(), prompted: new Map(), tracked: new Map(), seen: new Set() };
|
|
39
|
+
this.objects.set(object, state);
|
|
40
|
+
}
|
|
41
|
+
state.prompts.set(frame, prompt);
|
|
42
|
+
}
|
|
43
|
+
this.fresh = [...prompts.keys()];
|
|
44
|
+
}
|
|
45
|
+
/** Every object on `frame`, as the upstream's `forward` over its session answers it. */
|
|
46
|
+
async step(frame, reverse = false) {
|
|
47
|
+
const objects = [...this.objects.keys()];
|
|
48
|
+
const side = this.config.size / 4;
|
|
49
|
+
const cells = side * side;
|
|
50
|
+
const masks = new Float32Array(objects.length * cells);
|
|
51
|
+
const scores = new Float32Array(objects.length);
|
|
52
|
+
const pending = [];
|
|
53
|
+
let backwards = reverse;
|
|
54
|
+
for (const [i, object] of objects.entries()) {
|
|
55
|
+
const state = this.objects.get(object);
|
|
56
|
+
const isNew = this.fresh.includes(object);
|
|
57
|
+
let output = state.prompted.get(frame);
|
|
58
|
+
let first = true;
|
|
59
|
+
if (isNew || output === undefined) {
|
|
60
|
+
first = false;
|
|
61
|
+
let prompt = null;
|
|
62
|
+
if (isNew) {
|
|
63
|
+
first = !state.seen.has(frame);
|
|
64
|
+
if (first)
|
|
65
|
+
backwards = false;
|
|
66
|
+
prompt = state.prompts.get(frame) ?? null;
|
|
67
|
+
if (prompt !== null)
|
|
68
|
+
this.fresh = this.fresh.filter((id) => id !== object);
|
|
69
|
+
}
|
|
70
|
+
output = await this.decode(state, frame, first, prompt, backwards);
|
|
71
|
+
(first ? state.prompted : state.tracked).set(frame, output);
|
|
72
|
+
pending.push({ output, fromPrompt: prompt !== null });
|
|
73
|
+
}
|
|
74
|
+
masks.set(output.lowRes, i * cells);
|
|
75
|
+
scores[i] = output.score;
|
|
76
|
+
if (!first)
|
|
77
|
+
state.seen.add(frame);
|
|
78
|
+
}
|
|
79
|
+
await this.remember(frame, pending);
|
|
80
|
+
return { frame, objects, masks, scores };
|
|
81
|
+
}
|
|
82
|
+
graph(key, source, build) {
|
|
83
|
+
let graph = this.graphs.get(key);
|
|
84
|
+
if (graph === undefined) {
|
|
85
|
+
graph = graphFromWeights(source, build());
|
|
86
|
+
this.graphs.set(key, graph);
|
|
87
|
+
}
|
|
88
|
+
return graph;
|
|
89
|
+
}
|
|
90
|
+
async features(frame) {
|
|
91
|
+
if (this.encoded?.frame !== frame) {
|
|
92
|
+
const graph = this.graph('encoder', this.weights.encoder, () => sam21Encoder(this.config));
|
|
93
|
+
const out = await this.run(graph, new Map([['image', this.video.frame(frame)]]));
|
|
94
|
+
this.encoded = {
|
|
95
|
+
frame,
|
|
96
|
+
values: new Map([...out].map(([name, values]) => [name, Float32Array.from(values)])),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return this.encoded.values;
|
|
100
|
+
}
|
|
101
|
+
async decode(state, frame, first, prompt, reverse) {
|
|
102
|
+
const { config } = this;
|
|
103
|
+
const { dim } = config.decoder;
|
|
104
|
+
const features = await this.features(frame);
|
|
105
|
+
let embedding = features.get('features');
|
|
106
|
+
if (!first)
|
|
107
|
+
embedding = await this.condition(state, frame, embedding, reverse);
|
|
108
|
+
const request = prompt ?? {};
|
|
109
|
+
const tokens = new Float32Array(samTokenCount(request, SAM2_VIDEO_PROMPT) * dim);
|
|
110
|
+
samPromptTokens(this.weights.decoder, dim, config.size, request, this.video.height, this.video.width, tokens, SAM2_VIDEO_PROMPT);
|
|
111
|
+
const n = tokens.length / dim;
|
|
112
|
+
const decoder = this.graph(`decoder.${n}.${first}`, this.weights.decoder, () => sam21Decoder(config, n, { noMemory: first }));
|
|
113
|
+
const out = await this.run(decoder, new Map([
|
|
114
|
+
['embedding', embedding],
|
|
115
|
+
['high1', features.get('high1')],
|
|
116
|
+
['high0', features.get('high0')],
|
|
117
|
+
['prompt', tokens],
|
|
118
|
+
]));
|
|
119
|
+
const score = out.get('object')[0];
|
|
120
|
+
const { lowRes, pointer } = chooseMask(out.get('masks'), out.get('quality'), out.get('pointers'), score, (prompt?.points?.length ?? 0) + (prompt?.box === undefined ? 0 : 2), this.tensor(this.weights.decoder, 'no_object_pointer'));
|
|
121
|
+
return { lowRes, pointer, score, memory: null };
|
|
122
|
+
}
|
|
123
|
+
async condition(state, frame, features, reverse) {
|
|
124
|
+
const { config } = this;
|
|
125
|
+
const memories = memoriesFor(state, frame, config.memory.frames, reverse);
|
|
126
|
+
const pointers = pointersFor(state, frame, this.video.frames, config.memory.pointers, reverse);
|
|
127
|
+
const { memory, positions, pointerTokens } = memoryTokens(this.weights.memoryAttention, memories, pointers, this.cellPositions, config.memory.dim, config.decoder.dim);
|
|
128
|
+
const graph = this.graph(`attention.${memories.length}.${pointerTokens}`, this.weights.memoryAttention, () => sam21MemoryAttention(config, memories.length, pointerTokens));
|
|
129
|
+
const out = await this.run(graph, new Map([
|
|
130
|
+
['features', features],
|
|
131
|
+
['memory', memory],
|
|
132
|
+
['positions', positions],
|
|
133
|
+
]));
|
|
134
|
+
return Float32Array.from(out.get('conditioned'));
|
|
135
|
+
}
|
|
136
|
+
async remember(frame, pending) {
|
|
137
|
+
if (pending.length === 0)
|
|
138
|
+
return;
|
|
139
|
+
const { config } = this;
|
|
140
|
+
const binary = pending.some(({ fromPrompt }) => fromPrompt);
|
|
141
|
+
const features = (await this.features(frame)).get('features');
|
|
142
|
+
const upscale = this.graph('upscale', NO_WEIGHTS, () => sam21Upscale(config, 1));
|
|
143
|
+
const encoder = this.graph(`memory.${binary}`, this.weights.memoryEncoder, () => sam21MemoryEncoder(config, binary));
|
|
144
|
+
const occlusion = this.tensor(this.weights.memoryEncoder, 'occlusion_spatial_embedding_parameter');
|
|
145
|
+
const width = config.memory.dim;
|
|
146
|
+
for (const { output } of pending) {
|
|
147
|
+
const high = Float32Array.from((await this.run(upscale, new Map([['masks', output.lowRes]]))).get('high'));
|
|
148
|
+
if (binary)
|
|
149
|
+
for (let i = 0; i < high.length; i += 1)
|
|
150
|
+
high[i] = high[i] > 0 ? 1 : 0;
|
|
151
|
+
const encoded = (await this.run(encoder, new Map([
|
|
152
|
+
['features', features],
|
|
153
|
+
['mask', high],
|
|
154
|
+
]))).get('memory');
|
|
155
|
+
const cells = encoded.length / width;
|
|
156
|
+
const memory = new Float32Array(encoded.length);
|
|
157
|
+
for (let c = 0; c < width; c += 1) {
|
|
158
|
+
const add = output.score > 0 ? 0 : occlusion[c];
|
|
159
|
+
for (let i = 0; i < cells; i += 1)
|
|
160
|
+
memory[i * width + c] = Math.fround(encoded[c * cells + i] + add);
|
|
161
|
+
}
|
|
162
|
+
toBfloat16(memory);
|
|
163
|
+
output.memory = memory;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
tensor(source, name) {
|
|
167
|
+
const held = source.get(name);
|
|
168
|
+
if (held === undefined)
|
|
169
|
+
throw new Error(`the tracker's weights have no "${name}"`);
|
|
170
|
+
return held.data;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAM's mask decoder: an image embedding and a prompt's tokens in, masks and their predicted
|
|
3
|
+
* quality out — MobileSAM's, and the original SAM's, which share it.
|
|
4
|
+
*
|
|
5
|
+
* **A two-way transformer** (`twoWay.ts`, under the original's names) over the prompt's tokens,
|
|
6
|
+
* behind a learned token for the quality estimate and one per mask, and the image with its dense
|
|
7
|
+
* prompt added.
|
|
8
|
+
*
|
|
9
|
+
* **Masks come from a product, not a convolution**: the image is upsampled four times by two
|
|
10
|
+
* transposed convolutions, and each mask token, through an MLP of its own, becomes a vector that
|
|
11
|
+
* is dotted with every upsampled pixel. The first mask is the one to take for an unambiguous
|
|
12
|
+
* prompt, and the other three for an ambiguous one, as the upstream's `multimask_output` chooses.
|
|
13
|
+
*
|
|
14
|
+
* **Every prompt weight is read, whether this graph's nodes use it or not**: the prompt's tokens
|
|
15
|
+
* are made on the host from the Fourier weights, and the mask input's downscaling is only in the
|
|
16
|
+
* graph that takes one — but the converted file is built from one graph, and each has to find
|
|
17
|
+
* every weight the others need in it.
|
|
18
|
+
*/
|
|
19
|
+
import type { GraphBuilder, Weights } from '@driftengine/texture';
|
|
20
|
+
export interface SamDecoderConfig {
|
|
21
|
+
readonly dim: number;
|
|
22
|
+
readonly heads: number;
|
|
23
|
+
readonly mlp: number;
|
|
24
|
+
readonly depth: number;
|
|
25
|
+
/** The width attention between tokens and image narrows by. */
|
|
26
|
+
readonly downsample: number;
|
|
27
|
+
/** The mask tokens: one for an unambiguous prompt and three for an ambiguous one. */
|
|
28
|
+
readonly masks: number;
|
|
29
|
+
readonly iouHidden: number;
|
|
30
|
+
readonly iouDepth: number;
|
|
31
|
+
/** The channels a mask input is downscaled through. */
|
|
32
|
+
readonly maskChannels: number;
|
|
33
|
+
}
|
|
34
|
+
/** What the decoder's two outputs are named. */
|
|
35
|
+
export interface SamDecoderOutput {
|
|
36
|
+
/** `[masks, 4·grid, 4·grid]`: each mask's logits at four times the embedding's grid. */
|
|
37
|
+
readonly masks: string;
|
|
38
|
+
/** `[1, masks]`: each mask's predicted intersection over union. */
|
|
39
|
+
readonly quality: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The decoder over `embedding`, `[dim, grid, grid]`, and `promptTokens`, `[tokens, dim]` or null for a
|
|
43
|
+
* prompt of none, with `mask`, `[1, 4·grid, 4·grid]`, the previous logits to refine, or null.
|
|
44
|
+
*/
|
|
45
|
+
export declare function samDecoder(weights: Weights, graph: GraphBuilder, config: SamDecoderConfig, embedding: string, promptTokens: string | null, mask: string | null, grid: number, names: SamDecoderOutput): void;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { samGridPositions } from './samPrompt.js';
|
|
2
|
+
import { SAM_NAMES, twoWay } from './twoWay.js';
|
|
3
|
+
const CHANNEL_EPSILON = 1e-6;
|
|
4
|
+
/**
|
|
5
|
+
* The decoder over `embedding`, `[dim, grid, grid]`, and `promptTokens`, `[tokens, dim]` or null for a
|
|
6
|
+
* prompt of none, with `mask`, `[1, 4·grid, 4·grid]`, the previous logits to refine, or null.
|
|
7
|
+
*/
|
|
8
|
+
export function samDecoder(weights, graph, config, embedding, promptTokens, mask, grid, names) {
|
|
9
|
+
const node = graph.node.bind(graph);
|
|
10
|
+
const { dim, masks } = config;
|
|
11
|
+
const cells = grid * grid;
|
|
12
|
+
const d = 'mask_decoder.';
|
|
13
|
+
const t = `${d}transformer.`;
|
|
14
|
+
const pe = 'prompt_encoder.';
|
|
15
|
+
const read = weights.read.bind(weights);
|
|
16
|
+
const linear = (x, p, out, inputs, name) => node('linear', [x, read(`${p}weight`, [out, inputs]), read(`${p}bias`, [out])], {}, name);
|
|
17
|
+
const tokensOf = (map, channels, count) => node('permute', [node('reshape', [map], { shape: [channels, count] })], { order: [1, 0] });
|
|
18
|
+
const channelNorm = (map, p, channels, rows) => node('reshape', [
|
|
19
|
+
node('permute', [
|
|
20
|
+
node('layerNorm', [
|
|
21
|
+
tokensOf(map, channels, rows * rows),
|
|
22
|
+
read(`${p}weight`, [channels]),
|
|
23
|
+
read(`${p}bias`, [channels]),
|
|
24
|
+
], { epsilon: CHANNEL_EPSILON }),
|
|
25
|
+
], { order: [1, 0] }),
|
|
26
|
+
], { shape: [channels, rows, rows] });
|
|
27
|
+
const perceptron = (x, p, sizes, name) => {
|
|
28
|
+
let y = x;
|
|
29
|
+
for (let i = 0; i + 1 < sizes.length; i += 1) {
|
|
30
|
+
const last = i + 2 === sizes.length;
|
|
31
|
+
y = linear(y, `${p}layers.${i}.`, sizes[i + 1], sizes[i], last ? name : undefined);
|
|
32
|
+
if (i + 2 < sizes.length)
|
|
33
|
+
y = node('relu', [y]);
|
|
34
|
+
}
|
|
35
|
+
return y;
|
|
36
|
+
};
|
|
37
|
+
/* The prompt encoder's weights, all of them: see the header. */
|
|
38
|
+
read(`${pe}pe_layer.positional_encoding_gaussian_matrix`, [2, dim / 2]);
|
|
39
|
+
for (let i = 0; i < 4; i += 1)
|
|
40
|
+
read(`${pe}point_embeddings.${i}.weight`, [1, dim]);
|
|
41
|
+
read(`${pe}not_a_point_embed.weight`, [1, dim]);
|
|
42
|
+
const c = config.maskChannels;
|
|
43
|
+
const downscaled = (input) => {
|
|
44
|
+
const p = `${pe}mask_downscaling.`;
|
|
45
|
+
let x = node('conv2d', [input, read(`${p}0.weight`, [c / 4, 1, 2, 2]), read(`${p}0.bias`, [c / 4])], { stride: 2 });
|
|
46
|
+
x = node('gelu', [channelNorm(x, `${p}1.`, c / 4, 2 * grid)]);
|
|
47
|
+
x = node('conv2d', [x, read(`${p}3.weight`, [c, c / 4, 2, 2]), read(`${p}3.bias`, [c])], {
|
|
48
|
+
stride: 2,
|
|
49
|
+
});
|
|
50
|
+
x = node('gelu', [channelNorm(x, `${p}4.`, c, grid)]);
|
|
51
|
+
x = node('conv2d', [x, read(`${p}6.weight`, [dim, c, 1, 1]), read(`${p}6.bias`, [dim])]);
|
|
52
|
+
return tokensOf(x, dim, cells);
|
|
53
|
+
};
|
|
54
|
+
const noMask = read(`${pe}no_mask_embed.weight`, [1, dim]);
|
|
55
|
+
if (mask === null)
|
|
56
|
+
downscaledWeights(read, `${pe}mask_downscaling.`, dim, c);
|
|
57
|
+
const dense = mask === null ? node('reshape', [noMask], { shape: [dim] }) : downscaled(mask);
|
|
58
|
+
const positions = weights.derive(`${pe}grid_positions.${grid}x${grid}`, [cells, dim], (values) => samGridPositions(values(`${pe}pe_layer.positional_encoding_gaussian_matrix`, [2, dim / 2]), dim, grid));
|
|
59
|
+
const tokens = node('concat', [
|
|
60
|
+
read(`${d}iou_token.weight`, [1, dim]),
|
|
61
|
+
read(`${d}mask_tokens.weight`, [masks, dim]),
|
|
62
|
+
...(promptTokens === null ? [] : [promptTokens]),
|
|
63
|
+
], { axis: 0 });
|
|
64
|
+
const { queries, keys } = twoWay(weights, graph, t, config, SAM_NAMES, tokens, node('add', [tokensOf(embedding, dim, cells), dense]), positions);
|
|
65
|
+
/* Upsampled four times, and each mask token's vector dotted with every pixel. */
|
|
66
|
+
const u = `${d}output_upscaling.`;
|
|
67
|
+
const image = node('reshape', [node('permute', [keys], { order: [1, 0] })], {
|
|
68
|
+
shape: [dim, grid, grid],
|
|
69
|
+
});
|
|
70
|
+
let up = node('convTranspose2d', [image, read(`${u}0.weight`, [dim, dim / 4, 2, 2]), read(`${u}0.bias`, [dim / 4])], { stride: 2 });
|
|
71
|
+
up = node('gelu', [channelNorm(up, `${u}1.`, dim / 4, 2 * grid)]);
|
|
72
|
+
up = node('gelu', [
|
|
73
|
+
node('convTranspose2d', [up, read(`${u}3.weight`, [dim / 4, dim / 8, 2, 2]), read(`${u}3.bias`, [dim / 8])], { stride: 2 }),
|
|
74
|
+
]);
|
|
75
|
+
const vectors = Array.from({ length: masks }, (_, i) => perceptron(node('slice', [queries], { axis: 0, start: 1 + i, end: 2 + i }), `${d}output_hypernetworks_mlps.${i}.`, [dim, dim, dim, dim / 8]));
|
|
76
|
+
const pixels = 16 * cells;
|
|
77
|
+
const products = node('linear', [
|
|
78
|
+
tokensOf(up, dim / 8, pixels),
|
|
79
|
+
node('concat', vectors, { axis: 0 }),
|
|
80
|
+
]);
|
|
81
|
+
node('reshape', [node('permute', [products], { order: [1, 0] })], { shape: [masks, 4 * grid, 4 * grid] }, names.masks);
|
|
82
|
+
perceptron(node('slice', [queries], { axis: 0, start: 0, end: 1 }), `${d}iou_prediction_head.`, [dim, ...Array.from({ length: config.iouDepth - 1 }, () => config.iouHidden), masks], names.quality);
|
|
83
|
+
}
|
|
84
|
+
/* The mask input's downscaling weights, read into a graph that takes no mask input. */
|
|
85
|
+
function downscaledWeights(read, p, dim, c) {
|
|
86
|
+
read(`${p}0.weight`, [c / 4, 1, 2, 2]);
|
|
87
|
+
read(`${p}0.bias`, [c / 4]);
|
|
88
|
+
read(`${p}1.weight`, [c / 4]);
|
|
89
|
+
read(`${p}1.bias`, [c / 4]);
|
|
90
|
+
read(`${p}3.weight`, [c, c / 4, 2, 2]);
|
|
91
|
+
read(`${p}3.bias`, [c]);
|
|
92
|
+
read(`${p}4.weight`, [c]);
|
|
93
|
+
read(`${p}4.bias`, [c]);
|
|
94
|
+
read(`${p}6.weight`, [dim, c, 1, 1]);
|
|
95
|
+
read(`${p}6.bias`, [dim]);
|
|
96
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A seeded miniature of MobileSAM: the upstream's layout, every tensor name and shape of it, at a
|
|
3
|
+
* size the CPU evaluates in a moment — what the model's tests and its hand-run oracle share, as the
|
|
4
|
+
* depth models' miniature is.
|
|
5
|
+
*
|
|
6
|
+
* **Small everywhere the upstream lets it be.** A 64-pixel image, four stages of 8, 16, 24 and 320
|
|
7
|
+
* channels — the last must be 320, since the upstream keeps its last merge's stride at one by that
|
|
8
|
+
* width alone — windows of 3, 4 and 2 over grids of 8, 4 and 4, so one stage pads its windows, one
|
|
9
|
+
* is attended whole and one divides evenly, and one or two blocks a stage. The neck's 256 channels
|
|
10
|
+
* are the upstream's own constant, so the decoder is 256 wide too; its MLP and quality head are
|
|
11
|
+
* narrow, and its grid is 4.
|
|
12
|
+
*
|
|
13
|
+
* **Every tensor the upstream holds is here**, the classification head the definition sets aside
|
|
14
|
+
* and the batch norms' counters included, so the oracle loads it strictly.
|
|
15
|
+
*/
|
|
16
|
+
import type { GraphTensor } from '@driftengine/texture';
|
|
17
|
+
import type { MobileSamConfig } from './mobileSam.ts';
|
|
18
|
+
export declare const MINIATURE_MOBILE_SAM: MobileSamConfig;
|
|
19
|
+
/** The classes of the miniature's classification head, which nothing runs. */
|
|
20
|
+
export declare const MINIATURE_SAM_CLASSES = 10;
|
|
21
|
+
/**
|
|
22
|
+
* The original image the prompts are given in: 36 by 48, prepared at 48 by 64 in the encoder's 64
|
|
23
|
+
* square, so the masks are cropped before they come down to it and coordinates are scaled by 4/3.
|
|
24
|
+
*/
|
|
25
|
+
export declare const MINIATURE_SAM_IMAGE: {
|
|
26
|
+
readonly height: 36;
|
|
27
|
+
readonly width: 48;
|
|
28
|
+
};
|
|
29
|
+
/** A point, which a padding point follows; a box; and two points with a box, which none follows. */
|
|
30
|
+
export declare const MINIATURE_SAM_PROMPTS: {
|
|
31
|
+
readonly point: {
|
|
32
|
+
readonly points: readonly [readonly [20.5, 14.25, 1]];
|
|
33
|
+
};
|
|
34
|
+
readonly box: {
|
|
35
|
+
readonly box: readonly [6, 5, 40.5, 30];
|
|
36
|
+
};
|
|
37
|
+
readonly both: {
|
|
38
|
+
readonly points: readonly [readonly [20.5, 14.25, 1], readonly [33, 25.75, 0]];
|
|
39
|
+
readonly box: readonly [6, 5, 40.5, 30];
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
/** Every tensor the upstream's encoder, prompt encoder and mask decoder hold. */
|
|
43
|
+
export declare function miniatureSamCheckpoint(config: MobileSamConfig, seed: number): Map<string, GraphTensor>;
|