@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,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graphs a SAM 2.1 video adds to its image graphs: the memory encoder, the memory attention for
|
|
3
|
+
* each count of remembered frames and pointer tokens, and the mask brought to the frame's size for
|
|
4
|
+
* the memory. `Sam21Tracker` runs them.
|
|
5
|
+
*/
|
|
6
|
+
import type { Architecture } from '@driftengine/texture';
|
|
7
|
+
|
|
8
|
+
import type { Sam21Config } from './sam21.ts';
|
|
9
|
+
import { sam2MemoryAttention, sam2MemoryEncoder } from './sam2Memory.ts';
|
|
10
|
+
|
|
11
|
+
/* Everything but the memory encoder's weights, for a graph that reads only them. */
|
|
12
|
+
const IMAGE = [
|
|
13
|
+
'vision_encoder.',
|
|
14
|
+
'prompt_encoder.',
|
|
15
|
+
'mask_decoder.',
|
|
16
|
+
'shared_image_embedding.',
|
|
17
|
+
'object_pointer_proj.',
|
|
18
|
+
'memory_attention.',
|
|
19
|
+
'mask_downsample.',
|
|
20
|
+
'temporal_positional_encoding_projection_layer.',
|
|
21
|
+
'memory_temporal_positional_encoding',
|
|
22
|
+
'no_memory_embedding',
|
|
23
|
+
'no_memory_positional_encoding',
|
|
24
|
+
'no_object_pointer',
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* `features`, `[dim, grid, grid]`, and `mask`, `[1, size, size]` — logits, or a binary mask when
|
|
29
|
+
* `binary`, as a frame prompted with points is encoded — to `memory`, `[memory.dim, grid, grid]`.
|
|
30
|
+
* The host adds the occlusion embedding where the object is absent, and keeps the memory rounded to
|
|
31
|
+
* bfloat16, as the upstream keeps it; this graph reads the embedding so the file holds it.
|
|
32
|
+
*/
|
|
33
|
+
export function sam21MemoryEncoder(config: Sam21Config, binary: boolean): Architecture {
|
|
34
|
+
return (weights, graph) => {
|
|
35
|
+
for (const name of IMAGE) weights.ignore(name);
|
|
36
|
+
const { dim } = config.decoder;
|
|
37
|
+
const grid = config.size / 16;
|
|
38
|
+
weights.read('occlusion_spatial_embedding_parameter', [1, config.memory.dim]);
|
|
39
|
+
sam2MemoryEncoder(
|
|
40
|
+
weights,
|
|
41
|
+
graph,
|
|
42
|
+
dim,
|
|
43
|
+
config.memory,
|
|
44
|
+
grid,
|
|
45
|
+
'features',
|
|
46
|
+
'mask',
|
|
47
|
+
binary,
|
|
48
|
+
'memory',
|
|
49
|
+
);
|
|
50
|
+
return {
|
|
51
|
+
inputs: [
|
|
52
|
+
{ name: 'features', shape: [dim, grid, grid] },
|
|
53
|
+
{ name: 'mask', shape: [1, config.size, config.size] },
|
|
54
|
+
],
|
|
55
|
+
outputs: ['memory'],
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* `features`, `[dim, grid, grid]`, `memory` and its `positions`, `[frames·grid² + pointers,
|
|
62
|
+
* memory.dim]`, to `conditioned`, `[dim, grid, grid]`: one graph for each count of frames and of
|
|
63
|
+
* pointer tokens a tracker holds. The host makes the memory's positions, so this graph reads the
|
|
64
|
+
* temporal table and the pointers' projection for the file to hold them.
|
|
65
|
+
*/
|
|
66
|
+
export function sam21MemoryAttention(
|
|
67
|
+
config: Sam21Config,
|
|
68
|
+
frames: number,
|
|
69
|
+
pointers: number,
|
|
70
|
+
): Architecture {
|
|
71
|
+
return (weights, graph) => {
|
|
72
|
+
for (const name of [
|
|
73
|
+
'vision_encoder.',
|
|
74
|
+
'prompt_encoder.',
|
|
75
|
+
'mask_decoder.',
|
|
76
|
+
'shared_image_embedding.',
|
|
77
|
+
'object_pointer_proj.',
|
|
78
|
+
'memory_encoder.',
|
|
79
|
+
'mask_downsample.',
|
|
80
|
+
'no_memory_embedding',
|
|
81
|
+
'no_memory_positional_encoding',
|
|
82
|
+
'no_object_pointer',
|
|
83
|
+
'occlusion_spatial_embedding_parameter',
|
|
84
|
+
]) {
|
|
85
|
+
weights.ignore(name);
|
|
86
|
+
}
|
|
87
|
+
const { dim } = config.decoder;
|
|
88
|
+
const grid = config.size / 16;
|
|
89
|
+
const m = config.memory;
|
|
90
|
+
weights.read('memory_temporal_positional_encoding', [m.frames, 1, 1, m.dim]);
|
|
91
|
+
weights.read('temporal_positional_encoding_projection_layer.weight', [m.dim, dim]);
|
|
92
|
+
weights.read('temporal_positional_encoding_projection_layer.bias', [m.dim]);
|
|
93
|
+
const tokens = frames * grid * grid + pointers;
|
|
94
|
+
sam2MemoryAttention(
|
|
95
|
+
weights,
|
|
96
|
+
graph,
|
|
97
|
+
dim,
|
|
98
|
+
m,
|
|
99
|
+
grid,
|
|
100
|
+
frames,
|
|
101
|
+
pointers,
|
|
102
|
+
'features',
|
|
103
|
+
'memory',
|
|
104
|
+
'positions',
|
|
105
|
+
'conditioned',
|
|
106
|
+
);
|
|
107
|
+
return {
|
|
108
|
+
inputs: [
|
|
109
|
+
{ name: 'features', shape: [dim, grid, grid] },
|
|
110
|
+
{ name: 'memory', shape: [tokens, m.dim] },
|
|
111
|
+
{ name: 'positions', shape: [tokens, m.dim] },
|
|
112
|
+
],
|
|
113
|
+
outputs: ['conditioned'],
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** `masks`, `[count, 4·grid, 4·grid]`, to `high`, `[count, size, size]`, bilinearly. Reads no weights. */
|
|
119
|
+
export function sam21Upscale(config: Sam21Config, count: number): Architecture {
|
|
120
|
+
return (_weights, graph) => {
|
|
121
|
+
const low = config.size / 4;
|
|
122
|
+
graph.node(
|
|
123
|
+
'resize',
|
|
124
|
+
['masks'],
|
|
125
|
+
{ height: config.size, width: config.size, mode: 'bilinear', alignCorners: false },
|
|
126
|
+
'high',
|
|
127
|
+
);
|
|
128
|
+
return { inputs: [{ name: 'masks', shape: [count, low, low] }], outputs: ['high'] };
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `masks`, `[count, size / 4, size / 4]`, to `image`, `[count, height, width]`: the tracker's masks
|
|
134
|
+
* brought to the original frame bilinearly, as the upstream's `post_process_masks` brings them —
|
|
135
|
+
* straight from a quarter of the square, since SAM 2 resizes a frame without padding it. Reads no
|
|
136
|
+
* weights.
|
|
137
|
+
*/
|
|
138
|
+
export function sam21MasksToImage(
|
|
139
|
+
config: Sam21Config,
|
|
140
|
+
count: number,
|
|
141
|
+
height: number,
|
|
142
|
+
width: number,
|
|
143
|
+
): Architecture {
|
|
144
|
+
return (_weights, graph) => {
|
|
145
|
+
const low = config.size / 4;
|
|
146
|
+
graph.node(
|
|
147
|
+
'resize',
|
|
148
|
+
['masks'],
|
|
149
|
+
{ height, width, mode: 'bilinear', alignCorners: false },
|
|
150
|
+
'image',
|
|
151
|
+
);
|
|
152
|
+
return { inputs: [{ name: 'masks', shape: [count, low, low] }], outputs: ['image'] };
|
|
153
|
+
};
|
|
154
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which of a frame's four decoded masks a SAM 2 tracker keeps, and which pointer goes with it, as the
|
|
3
|
+
* upstream's video decoder chooses.
|
|
4
|
+
*
|
|
5
|
+
* **A prompt of one point or none keeps the best of the three ambiguous masks** by predicted quality,
|
|
6
|
+
* and that mask token's pointer. **More points keep the single mask**, unless its stability — pixels
|
|
7
|
+
* above +0.05 over pixels above −0.05, one where there are none — is under 0.98, when the best of the
|
|
8
|
+
* three is kept instead; **the pointer is the single mask's either way**, as the upstream slices its
|
|
9
|
+
* tokens before it falls back. **An object whose score is not positive is absent**: its mask is −1024
|
|
10
|
+
* everywhere and its pointer the learned one for no object. A box counts as its two corner points.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const NO_OBJECT = -1024;
|
|
14
|
+
const STABILITY_DELTA = 0.05;
|
|
15
|
+
const STABILITY_THRESHOLD = 0.98;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The kept mask's logits and pointer, from `masks`, `[4, cells]`, `quality`, `[4]`, `pointers`,
|
|
19
|
+
* `[4, dim]`, the object's score, the prompt's point count and the no-object pointer.
|
|
20
|
+
*/
|
|
21
|
+
export function chooseMask(
|
|
22
|
+
masks: Float32Array,
|
|
23
|
+
quality: Float32Array,
|
|
24
|
+
pointers: Float32Array,
|
|
25
|
+
score: number,
|
|
26
|
+
points: number,
|
|
27
|
+
noObject: Float32Array,
|
|
28
|
+
): { readonly lowRes: Float32Array; readonly pointer: Float32Array } {
|
|
29
|
+
const count = quality.length;
|
|
30
|
+
const cells = masks.length / count;
|
|
31
|
+
const dim = pointers.length / count;
|
|
32
|
+
let best = 1;
|
|
33
|
+
for (let m = 2; m < count; m += 1)
|
|
34
|
+
if ((quality[m] as number) > (quality[best] as number)) best = m;
|
|
35
|
+
let mask = best;
|
|
36
|
+
let token = best;
|
|
37
|
+
if (points > 1) {
|
|
38
|
+
mask = stability(masks.subarray(0, cells)) >= STABILITY_THRESHOLD ? 0 : best;
|
|
39
|
+
token = 0;
|
|
40
|
+
}
|
|
41
|
+
if (score <= 0) {
|
|
42
|
+
return {
|
|
43
|
+
lowRes: new Float32Array(cells).fill(NO_OBJECT),
|
|
44
|
+
pointer: Float32Array.from(noObject),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
lowRes: Float32Array.from(masks.subarray(mask * cells, (mask + 1) * cells)),
|
|
49
|
+
pointer: Float32Array.from(pointers.subarray(token * dim, (token + 1) * dim)),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Pixels above +δ over pixels above −δ, and one where none are. */
|
|
54
|
+
function stability(logits: Float32Array): number {
|
|
55
|
+
let inside = 0;
|
|
56
|
+
let union = 0;
|
|
57
|
+
for (let i = 0; i < logits.length; i += 1) {
|
|
58
|
+
if ((logits[i] as number) > STABILITY_DELTA) inside += 1;
|
|
59
|
+
if ((logits[i] as number) > -STABILITY_DELTA) union += 1;
|
|
60
|
+
}
|
|
61
|
+
return union > 0 ? inside / union : 1;
|
|
62
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAM 2's mask decoder: SAM's two-way transformer with the encoder's finer levels brought back in,
|
|
3
|
+
* an object score, and every mask token's object pointer for a video's memory.
|
|
4
|
+
*
|
|
5
|
+
* **The tokens lead with an object-score token**, then the quality token and one per mask, then
|
|
6
|
+
* the prompt's. **The upsampling adds the encoder's skips**: its second level after the first
|
|
7
|
+
* transposed convolution, and its first after the second, each before the activation. Quality is
|
|
8
|
+
* a sigmoid, as the upstream's `sigmoid_output`; the object score is a logit, positive where the
|
|
9
|
+
* object is present. **Each mask token's object pointer is projected here**, all four, so the host
|
|
10
|
+
* can keep the one it chooses without another graph. Norms of channels take ε 1e-6.
|
|
11
|
+
*/
|
|
12
|
+
import type { GraphBuilder, Weights } from '@driftengine/texture';
|
|
13
|
+
|
|
14
|
+
import type { SamDecoderConfig } from './samDecoder.ts';
|
|
15
|
+
import { samGridPositions } from './samPrompt.ts';
|
|
16
|
+
import { SAM2_NAMES, twoWay } from './twoWay.ts';
|
|
17
|
+
|
|
18
|
+
const CHANNEL_EPSILON = 1e-6;
|
|
19
|
+
|
|
20
|
+
export interface Sam2DecoderInputs {
|
|
21
|
+
/** `[dim, grid, grid]`: the features, conditioned on memory or given `no_memory_embedding`. */
|
|
22
|
+
readonly embedding: string;
|
|
23
|
+
/** `[tokens, dim]`, or null for a prompt of none. */
|
|
24
|
+
readonly prompt: string | null;
|
|
25
|
+
/** `[1, 4·grid, 4·grid]`: a mask's logits to refine, or null. */
|
|
26
|
+
readonly mask: string | null;
|
|
27
|
+
/** The encoder's narrowed finer levels, `[dim / 4, 2·grid, 2·grid]` and `[dim / 8, 4·grid, 4·grid]`. */
|
|
28
|
+
readonly high1: string;
|
|
29
|
+
readonly high0: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** What the decoder's four outputs are named. */
|
|
33
|
+
export interface Sam2DecoderOutput {
|
|
34
|
+
/** `[masks, 4·grid, 4·grid]` logits. */
|
|
35
|
+
readonly masks: string;
|
|
36
|
+
/** `[1, masks]`, each mask's predicted intersection over union. */
|
|
37
|
+
readonly quality: string;
|
|
38
|
+
/** `[1, 1]`: the object score's logit. */
|
|
39
|
+
readonly object: string;
|
|
40
|
+
/** `[masks, dim]`: each mask token's object pointer. */
|
|
41
|
+
readonly pointers: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function sam2Decoder(
|
|
45
|
+
weights: Weights,
|
|
46
|
+
graph: GraphBuilder,
|
|
47
|
+
config: SamDecoderConfig,
|
|
48
|
+
grid: number,
|
|
49
|
+
inputs: Sam2DecoderInputs,
|
|
50
|
+
names: Sam2DecoderOutput,
|
|
51
|
+
): void {
|
|
52
|
+
const node = graph.node.bind(graph);
|
|
53
|
+
const read = weights.read.bind(weights);
|
|
54
|
+
const { dim, masks } = config;
|
|
55
|
+
const cells = grid * grid;
|
|
56
|
+
const m = 'mask_decoder.';
|
|
57
|
+
const pe = 'prompt_encoder.';
|
|
58
|
+
const linear = (x: string, p: string, out: number, width: number, name?: string): string =>
|
|
59
|
+
node('linear', [x, read(`${p}weight`, [out, width]), read(`${p}bias`, [out])], {}, name);
|
|
60
|
+
/* The upstream's perceptron: `proj_in`, then `layers`, then `proj_out`, ReLU between. */
|
|
61
|
+
const perceptron = (x: string, p: string, sizes: readonly number[], name?: string): string => {
|
|
62
|
+
const parts = sizes.length - 1;
|
|
63
|
+
let y = x;
|
|
64
|
+
for (let i = 0; i < parts; i += 1) {
|
|
65
|
+
const at = i === 0 ? 'proj_in' : i === parts - 1 ? 'proj_out' : `layers.${i - 1}`;
|
|
66
|
+
const last = i === parts - 1;
|
|
67
|
+
y = linear(
|
|
68
|
+
y,
|
|
69
|
+
`${p}${at}.`,
|
|
70
|
+
sizes[i + 1] as number,
|
|
71
|
+
sizes[i] as number,
|
|
72
|
+
last ? name : undefined,
|
|
73
|
+
);
|
|
74
|
+
if (!last) y = node('relu', [y]);
|
|
75
|
+
}
|
|
76
|
+
return y;
|
|
77
|
+
};
|
|
78
|
+
const tokensOf = (map: string, channels: number, count: number): string =>
|
|
79
|
+
node('permute', [node('reshape', [map], { shape: [channels, count] })], { order: [1, 0] });
|
|
80
|
+
const channelNorm = (map: string, p: string, channels: number, rows: number): string => {
|
|
81
|
+
const normed = node(
|
|
82
|
+
'layerNorm',
|
|
83
|
+
[
|
|
84
|
+
tokensOf(map, channels, rows * rows),
|
|
85
|
+
read(`${p}weight`, [channels]),
|
|
86
|
+
read(`${p}bias`, [channels]),
|
|
87
|
+
],
|
|
88
|
+
{ epsilon: CHANNEL_EPSILON },
|
|
89
|
+
);
|
|
90
|
+
return node('reshape', [node('permute', [normed], { order: [1, 0] })], {
|
|
91
|
+
shape: [channels, rows, rows],
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/* The prompt encoder's weights, all of them, for the host's tokens and the graph that refines. */
|
|
96
|
+
read(`${pe}shared_embedding.positional_embedding`, [2, dim / 2]);
|
|
97
|
+
read(`${pe}point_embed.weight`, [4, dim]);
|
|
98
|
+
read(`${pe}not_a_point_embed.weight`, [1, dim]);
|
|
99
|
+
const c = config.maskChannels;
|
|
100
|
+
const embed = `${pe}mask_embed.`;
|
|
101
|
+
const convs = [
|
|
102
|
+
[`${embed}conv1.`, c / 4, 1, 2],
|
|
103
|
+
[`${embed}conv2.`, c, c / 4, 2],
|
|
104
|
+
[`${embed}conv3.`, dim, c, 1],
|
|
105
|
+
] as const;
|
|
106
|
+
const w = convs.map(([p, out, into, k]) => [
|
|
107
|
+
read(`${p}weight`, [out, into, k, k]),
|
|
108
|
+
read(`${p}bias`, [out]),
|
|
109
|
+
]);
|
|
110
|
+
let dense = node('reshape', [read(`${pe}no_mask_embed.weight`, [1, dim])], { shape: [dim] });
|
|
111
|
+
if (inputs.mask !== null) {
|
|
112
|
+
let x = node('conv2d', [inputs.mask, ...(w[0] as string[])], { stride: 2 });
|
|
113
|
+
x = node('gelu', [channelNorm(x, `${embed}layer_norm1.`, c / 4, 2 * grid)]);
|
|
114
|
+
x = node('conv2d', [x, ...(w[1] as string[])], { stride: 2 });
|
|
115
|
+
x = node('gelu', [channelNorm(x, `${embed}layer_norm2.`, c, grid)]);
|
|
116
|
+
dense = tokensOf(node('conv2d', [x, ...(w[2] as string[])]), dim, cells);
|
|
117
|
+
} else {
|
|
118
|
+
for (const [i, channels] of [
|
|
119
|
+
[1, c / 4],
|
|
120
|
+
[2, c],
|
|
121
|
+
] as const) {
|
|
122
|
+
read(`${embed}layer_norm${i}.weight`, [channels]);
|
|
123
|
+
read(`${embed}layer_norm${i}.bias`, [channels]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const positions = weights.derive(
|
|
128
|
+
`shared_image_embedding.grid_positions.${grid}x${grid}`,
|
|
129
|
+
[cells, dim],
|
|
130
|
+
(values) =>
|
|
131
|
+
samGridPositions(
|
|
132
|
+
values('shared_image_embedding.positional_embedding', [2, dim / 2]),
|
|
133
|
+
dim,
|
|
134
|
+
grid,
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
read('shared_image_embedding.positional_embedding', [2, dim / 2]);
|
|
138
|
+
const tokens = node(
|
|
139
|
+
'concat',
|
|
140
|
+
[
|
|
141
|
+
read(`${m}obj_score_token.weight`, [1, dim]),
|
|
142
|
+
read(`${m}iou_token.weight`, [1, dim]),
|
|
143
|
+
read(`${m}mask_tokens.weight`, [masks, dim]),
|
|
144
|
+
...(inputs.prompt === null ? [] : [inputs.prompt]),
|
|
145
|
+
],
|
|
146
|
+
{ axis: 0 },
|
|
147
|
+
);
|
|
148
|
+
const image = node('add', [tokensOf(inputs.embedding, dim, cells), dense]);
|
|
149
|
+
const { queries, keys } = twoWay(
|
|
150
|
+
weights,
|
|
151
|
+
graph,
|
|
152
|
+
`${m}transformer.`,
|
|
153
|
+
config,
|
|
154
|
+
SAM2_NAMES,
|
|
155
|
+
tokens,
|
|
156
|
+
image,
|
|
157
|
+
positions,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
/* Upsampled four times with the encoder's skips, and each mask token's vector dotted with every pixel. */
|
|
161
|
+
const map = node('reshape', [node('permute', [keys], { order: [1, 0] })], {
|
|
162
|
+
shape: [dim, grid, grid],
|
|
163
|
+
});
|
|
164
|
+
let up = node(
|
|
165
|
+
'convTranspose2d',
|
|
166
|
+
[
|
|
167
|
+
map,
|
|
168
|
+
read(`${m}upscale_conv1.weight`, [dim, dim / 4, 2, 2]),
|
|
169
|
+
read(`${m}upscale_conv1.bias`, [dim / 4]),
|
|
170
|
+
],
|
|
171
|
+
{ stride: 2 },
|
|
172
|
+
);
|
|
173
|
+
up = node('gelu', [
|
|
174
|
+
channelNorm(node('add', [up, inputs.high1]), `${m}upscale_layer_norm.`, dim / 4, 2 * grid),
|
|
175
|
+
]);
|
|
176
|
+
up = node(
|
|
177
|
+
'convTranspose2d',
|
|
178
|
+
[
|
|
179
|
+
up,
|
|
180
|
+
read(`${m}upscale_conv2.weight`, [dim / 4, dim / 8, 2, 2]),
|
|
181
|
+
read(`${m}upscale_conv2.bias`, [dim / 8]),
|
|
182
|
+
],
|
|
183
|
+
{ stride: 2 },
|
|
184
|
+
);
|
|
185
|
+
up = node('gelu', [node('add', [up, inputs.high0])]);
|
|
186
|
+
const maskTokens = node('slice', [queries], { axis: 0, start: 2, end: 2 + masks });
|
|
187
|
+
const vectors = Array.from({ length: masks }, (_, i) =>
|
|
188
|
+
perceptron(
|
|
189
|
+
node('slice', [maskTokens], { axis: 0, start: i, end: i + 1 }),
|
|
190
|
+
`${m}output_hypernetworks_mlps.${i}.`,
|
|
191
|
+
[dim, dim, dim, dim / 8],
|
|
192
|
+
),
|
|
193
|
+
);
|
|
194
|
+
const products = node('linear', [
|
|
195
|
+
tokensOf(up, dim / 8, 16 * cells),
|
|
196
|
+
node('concat', vectors, { axis: 0 }),
|
|
197
|
+
]);
|
|
198
|
+
node(
|
|
199
|
+
'reshape',
|
|
200
|
+
[node('permute', [products], { order: [1, 0] })],
|
|
201
|
+
{ shape: [masks, 4 * grid, 4 * grid] },
|
|
202
|
+
names.masks,
|
|
203
|
+
);
|
|
204
|
+
const hidden = Array.from({ length: config.iouDepth - 1 }, () => config.iouHidden);
|
|
205
|
+
const quality = perceptron(
|
|
206
|
+
node('slice', [queries], { axis: 0, start: 1, end: 2 }),
|
|
207
|
+
`${m}iou_prediction_head.`,
|
|
208
|
+
[dim, ...hidden, masks],
|
|
209
|
+
);
|
|
210
|
+
node('sigmoid', [quality], {}, names.quality);
|
|
211
|
+
perceptron(
|
|
212
|
+
node('slice', [queries], { axis: 0, start: 0, end: 1 }),
|
|
213
|
+
`${m}pred_obj_score_head.`,
|
|
214
|
+
[dim, dim, dim, 1],
|
|
215
|
+
names.object,
|
|
216
|
+
);
|
|
217
|
+
perceptron(maskTokens, 'object_pointer_proj.', [dim, dim, dim, dim], names.pointers);
|
|
218
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a SAM 2 tracker keeps between frames, and how it chooses what a new frame reads: the host's
|
|
3
|
+
* half of the video memory, as the upstream's `Sam2VideoModel` does it.
|
|
4
|
+
*
|
|
5
|
+
* **A frame reads every prompted frame's memory and the six frames before it** — after it, tracking
|
|
6
|
+
* backwards — each at its distance, which picks a row of a learned temporal table; a prompted frame
|
|
7
|
+
* sits at distance zero, which the upstream indexes as the table's last row. **And the object
|
|
8
|
+
* pointers** of the prompted frames up to it and of the frames up to fifteen before it, each placed
|
|
9
|
+
* by a sine of its distance over the most there could be, projected to the memory's width; a
|
|
10
|
+
* pointer wider than a memory token is cut into as many tokens as it takes.
|
|
11
|
+
*
|
|
12
|
+
* **A memory is kept at bfloat16**, rounded to nearest even as the upstream stores it; what the
|
|
13
|
+
* tracker holds is the rounded value, so a frame reads what the upstream's reads.
|
|
14
|
+
*/
|
|
15
|
+
import type { WeightSource } from '@driftengine/texture';
|
|
16
|
+
|
|
17
|
+
import { sam2PointerPositions } from './sam2Positions.ts';
|
|
18
|
+
|
|
19
|
+
/** One object's output on one frame, as the tracker keeps it. */
|
|
20
|
+
export interface Sam2Output {
|
|
21
|
+
/** `[(size / 4)²]`: the mask's logits, −1024 everywhere when the object is absent. */
|
|
22
|
+
readonly lowRes: Float32Array;
|
|
23
|
+
/** `[dim]`: the object pointer, or the learned one for an absent object. */
|
|
24
|
+
readonly pointer: Float32Array;
|
|
25
|
+
readonly score: number;
|
|
26
|
+
/** `[cells, memory.dim]` rounded to bfloat16, once the memory is encoded. */
|
|
27
|
+
memory: Float32Array | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** One object's history: its prompted frames' outputs and the frames it was tracked on. */
|
|
31
|
+
export interface Sam2History {
|
|
32
|
+
readonly prompted: Map<number, Sam2Output>;
|
|
33
|
+
readonly tracked: Map<number, Sam2Output>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Rounds every value to bfloat16, to nearest even, in place. */
|
|
37
|
+
export function toBfloat16(values: Float32Array): void {
|
|
38
|
+
const bits = new Uint32Array(values.buffer, values.byteOffset, values.length);
|
|
39
|
+
for (let i = 0; i < bits.length; i += 1) {
|
|
40
|
+
const word = bits[i] as number;
|
|
41
|
+
if ((word & 0x7f800000) === 0x7f800000) {
|
|
42
|
+
bits[i] = (word & 0xffff0000) | ((word & 0xffff) === 0 ? 0 : 0x400000);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
bits[i] = ((word + 0x7fff + ((word >>> 16) & 1)) & 0xffff0000) >>> 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The memories a frame reads, each with its distance, prompted frames first. */
|
|
50
|
+
export function memoriesFor(
|
|
51
|
+
history: Sam2History,
|
|
52
|
+
frame: number,
|
|
53
|
+
frames: number,
|
|
54
|
+
reverse: boolean,
|
|
55
|
+
): readonly (readonly [distance: number, output: Sam2Output])[] {
|
|
56
|
+
const chosen: (readonly [number, Sam2Output])[] = [...history.prompted.values()].map(
|
|
57
|
+
(output) => [0, output] as const,
|
|
58
|
+
);
|
|
59
|
+
for (let distance = frames - 1; distance > 0; distance -= 1) {
|
|
60
|
+
const output = history.tracked.get(reverse ? frame + distance : frame - distance);
|
|
61
|
+
if (output !== undefined) chosen.push([distance, output]);
|
|
62
|
+
}
|
|
63
|
+
return chosen.filter(([, output]) => output.memory !== null);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The object pointers a frame reads, each with its distance. */
|
|
67
|
+
export function pointersFor(
|
|
68
|
+
history: Sam2History,
|
|
69
|
+
frame: number,
|
|
70
|
+
videoFrames: number,
|
|
71
|
+
most: number,
|
|
72
|
+
reverse: boolean,
|
|
73
|
+
): {
|
|
74
|
+
readonly distances: readonly number[];
|
|
75
|
+
readonly pointers: readonly Float32Array[];
|
|
76
|
+
readonly span: number;
|
|
77
|
+
} {
|
|
78
|
+
const span = Math.min(videoFrames, most);
|
|
79
|
+
const distances: number[] = [];
|
|
80
|
+
const pointers: Float32Array[] = [];
|
|
81
|
+
for (const [at, output] of history.prompted) {
|
|
82
|
+
if (reverse ? at >= frame : at <= frame) {
|
|
83
|
+
distances.push((frame - at) * (reverse ? -1 : 1));
|
|
84
|
+
pointers.push(output.pointer);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (let distance = 1; distance < span; distance += 1) {
|
|
88
|
+
const at = reverse ? frame + distance : frame - distance;
|
|
89
|
+
if (at < 0 || at >= videoFrames) break;
|
|
90
|
+
const output = history.tracked.get(at);
|
|
91
|
+
if (output !== undefined) {
|
|
92
|
+
distances.push(distance);
|
|
93
|
+
pointers.push(output.pointer);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { distances, pointers, span };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The memory tokens and their positions a frame's memory attention reads: the frames' cells, each
|
|
101
|
+
* frame's positions its cells' sines plus its distance's temporal row, then the pointers cut into
|
|
102
|
+
* tokens of `width`, each carrying its pointer's projected sine position.
|
|
103
|
+
*/
|
|
104
|
+
export function memoryTokens(
|
|
105
|
+
weights: WeightSource,
|
|
106
|
+
memories: readonly (readonly [number, Sam2Output])[],
|
|
107
|
+
pointers: {
|
|
108
|
+
readonly distances: readonly number[];
|
|
109
|
+
readonly pointers: readonly Float32Array[];
|
|
110
|
+
readonly span: number;
|
|
111
|
+
},
|
|
112
|
+
cellPositions: Float32Array,
|
|
113
|
+
width: number,
|
|
114
|
+
dim: number,
|
|
115
|
+
): {
|
|
116
|
+
readonly memory: Float32Array;
|
|
117
|
+
readonly positions: Float32Array;
|
|
118
|
+
readonly pointerTokens: number;
|
|
119
|
+
} {
|
|
120
|
+
const temporal = tensor(weights, 'memory_temporal_positional_encoding');
|
|
121
|
+
const cells = cellPositions.length / width;
|
|
122
|
+
const rows = temporal.length / width;
|
|
123
|
+
const splits = dim / width;
|
|
124
|
+
const pointerTokens = pointers.pointers.length * splits;
|
|
125
|
+
const total = memories.length * cells + pointerTokens;
|
|
126
|
+
const memory = new Float32Array(total * width);
|
|
127
|
+
const positions = new Float32Array(total * width);
|
|
128
|
+
memories.forEach(([distance, output], m) => {
|
|
129
|
+
memory.set(output.memory as Float32Array, m * cells * width);
|
|
130
|
+
const row = ((distance - 1 + rows) % rows) * width;
|
|
131
|
+
for (let i = 0; i < cells * width; i += 1) {
|
|
132
|
+
positions[m * cells * width + i] = Math.fround(
|
|
133
|
+
(cellPositions[i] as number) + (temporal[row + (i % width)] as number),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
if (pointerTokens > 0) {
|
|
138
|
+
const at = memories.length * cells * width;
|
|
139
|
+
const sine = sam2PointerPositions(
|
|
140
|
+
pointers.distances.map((distance) => Math.fround(distance / Math.fround(pointers.span - 1))),
|
|
141
|
+
dim,
|
|
142
|
+
);
|
|
143
|
+
const weight = tensor(weights, 'temporal_positional_encoding_projection_layer.weight');
|
|
144
|
+
const bias = tensor(weights, 'temporal_positional_encoding_projection_layer.bias');
|
|
145
|
+
pointers.pointers.forEach((pointer, p) => {
|
|
146
|
+
for (let s = 0; s < splits; s += 1) {
|
|
147
|
+
const token = at + (p * splits + s) * width;
|
|
148
|
+
memory.set(pointer.subarray(s * width, (s + 1) * width), token);
|
|
149
|
+
for (let o = 0; o < width; o += 1) {
|
|
150
|
+
let sum = bias[o] as number;
|
|
151
|
+
for (let c = 0; c < dim; c += 1)
|
|
152
|
+
sum += (weight[o * dim + c] as number) * (sine[p * dim + c] as number);
|
|
153
|
+
positions[token + o] = Math.fround(sum);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return { memory, positions, pointerTokens };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function tensor(weights: WeightSource, name: string): Float32Array {
|
|
162
|
+
const held = weights.get(name);
|
|
163
|
+
if (held === undefined) throw new Error(`the memory has no "${name}"`);
|
|
164
|
+
return held.data;
|
|
165
|
+
}
|