radames HF staff commited on
Commit
7f7bf76
1 Parent(s): 3bf8517

Upload 6 files

Browse files
Files changed (6) hide show
  1. build/m.d.ts +59 -0
  2. build/m.js +336 -0
  3. build/m_bg.wasm +3 -0
  4. build/m_bg.wasm.d.ts +13 -0
  5. index.html +403 -16
  6. samWorker.js +156 -0
build/m.d.ts ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /**
4
+ */
5
+ export class Model {
6
+ free(): void;
7
+ /**
8
+ * @param {Uint8Array} weights
9
+ * @param {boolean} use_tiny
10
+ */
11
+ constructor(weights: Uint8Array, use_tiny: boolean);
12
+ /**
13
+ * @param {Uint8Array} image_data
14
+ */
15
+ set_image_embeddings(image_data: Uint8Array): void;
16
+ /**
17
+ * @param {number} x
18
+ * @param {number} y
19
+ * @returns {string}
20
+ */
21
+ mask_for_point(x: number, y: number): string;
22
+ }
23
+
24
+ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
25
+
26
+ export interface InitOutput {
27
+ readonly memory: WebAssembly.Memory;
28
+ readonly __wbg_model_free: (a: number) => void;
29
+ readonly model_new: (a: number, b: number, c: number, d: number) => void;
30
+ readonly model_set_image_embeddings: (a: number, b: number, c: number, d: number) => void;
31
+ readonly model_mask_for_point: (a: number, b: number, c: number, d: number) => void;
32
+ readonly main: (a: number, b: number) => number;
33
+ readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
34
+ readonly __wbindgen_malloc: (a: number, b: number) => number;
35
+ readonly __wbindgen_free: (a: number, b: number, c: number) => void;
36
+ readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
37
+ readonly __wbindgen_start: () => void;
38
+ }
39
+
40
+ export type SyncInitInput = BufferSource | WebAssembly.Module;
41
+ /**
42
+ * Instantiates the given `module`, which can either be bytes or
43
+ * a precompiled `WebAssembly.Module`.
44
+ *
45
+ * @param {SyncInitInput} module
46
+ *
47
+ * @returns {InitOutput}
48
+ */
49
+ export function initSync(module: SyncInitInput): InitOutput;
50
+
51
+ /**
52
+ * If `module_or_path` is {RequestInfo} or {URL}, makes a request and
53
+ * for everything else, calls `WebAssembly.instantiate` directly.
54
+ *
55
+ * @param {InitInput | Promise<InitInput>} module_or_path
56
+ *
57
+ * @returns {Promise<InitOutput>}
58
+ */
59
+ export default function __wbg_init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;
build/m.js ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let wasm;
2
+
3
+ const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
4
+
5
+ if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
6
+
7
+ let cachedUint8Memory0 = null;
8
+
9
+ function getUint8Memory0() {
10
+ if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
11
+ cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
12
+ }
13
+ return cachedUint8Memory0;
14
+ }
15
+
16
+ function getStringFromWasm0(ptr, len) {
17
+ ptr = ptr >>> 0;
18
+ return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
19
+ }
20
+
21
+ const heap = new Array(128).fill(undefined);
22
+
23
+ heap.push(undefined, null, true, false);
24
+
25
+ let heap_next = heap.length;
26
+
27
+ function addHeapObject(obj) {
28
+ if (heap_next === heap.length) heap.push(heap.length + 1);
29
+ const idx = heap_next;
30
+ heap_next = heap[idx];
31
+
32
+ heap[idx] = obj;
33
+ return idx;
34
+ }
35
+
36
+ function getObject(idx) { return heap[idx]; }
37
+
38
+ function dropObject(idx) {
39
+ if (idx < 132) return;
40
+ heap[idx] = heap_next;
41
+ heap_next = idx;
42
+ }
43
+
44
+ function takeObject(idx) {
45
+ const ret = getObject(idx);
46
+ dropObject(idx);
47
+ return ret;
48
+ }
49
+
50
+ let WASM_VECTOR_LEN = 0;
51
+
52
+ function passArray8ToWasm0(arg, malloc) {
53
+ const ptr = malloc(arg.length * 1, 1) >>> 0;
54
+ getUint8Memory0().set(arg, ptr / 1);
55
+ WASM_VECTOR_LEN = arg.length;
56
+ return ptr;
57
+ }
58
+
59
+ let cachedInt32Memory0 = null;
60
+
61
+ function getInt32Memory0() {
62
+ if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
63
+ cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
64
+ }
65
+ return cachedInt32Memory0;
66
+ }
67
+
68
+ const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
69
+
70
+ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
71
+ ? function (arg, view) {
72
+ return cachedTextEncoder.encodeInto(arg, view);
73
+ }
74
+ : function (arg, view) {
75
+ const buf = cachedTextEncoder.encode(arg);
76
+ view.set(buf);
77
+ return {
78
+ read: arg.length,
79
+ written: buf.length
80
+ };
81
+ });
82
+
83
+ function passStringToWasm0(arg, malloc, realloc) {
84
+
85
+ if (realloc === undefined) {
86
+ const buf = cachedTextEncoder.encode(arg);
87
+ const ptr = malloc(buf.length, 1) >>> 0;
88
+ getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
89
+ WASM_VECTOR_LEN = buf.length;
90
+ return ptr;
91
+ }
92
+
93
+ let len = arg.length;
94
+ let ptr = malloc(len, 1) >>> 0;
95
+
96
+ const mem = getUint8Memory0();
97
+
98
+ let offset = 0;
99
+
100
+ for (; offset < len; offset++) {
101
+ const code = arg.charCodeAt(offset);
102
+ if (code > 0x7F) break;
103
+ mem[ptr + offset] = code;
104
+ }
105
+
106
+ if (offset !== len) {
107
+ if (offset !== 0) {
108
+ arg = arg.slice(offset);
109
+ }
110
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
111
+ const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
112
+ const ret = encodeString(arg, view);
113
+
114
+ offset += ret.written;
115
+ }
116
+
117
+ WASM_VECTOR_LEN = offset;
118
+ return ptr;
119
+ }
120
+ /**
121
+ */
122
+ export class Model {
123
+
124
+ static __wrap(ptr) {
125
+ ptr = ptr >>> 0;
126
+ const obj = Object.create(Model.prototype);
127
+ obj.__wbg_ptr = ptr;
128
+
129
+ return obj;
130
+ }
131
+
132
+ __destroy_into_raw() {
133
+ const ptr = this.__wbg_ptr;
134
+ this.__wbg_ptr = 0;
135
+
136
+ return ptr;
137
+ }
138
+
139
+ free() {
140
+ const ptr = this.__destroy_into_raw();
141
+ wasm.__wbg_model_free(ptr);
142
+ }
143
+ /**
144
+ * @param {Uint8Array} weights
145
+ * @param {boolean} use_tiny
146
+ */
147
+ constructor(weights, use_tiny) {
148
+ try {
149
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
150
+ const ptr0 = passArray8ToWasm0(weights, wasm.__wbindgen_malloc);
151
+ const len0 = WASM_VECTOR_LEN;
152
+ wasm.model_new(retptr, ptr0, len0, use_tiny);
153
+ var r0 = getInt32Memory0()[retptr / 4 + 0];
154
+ var r1 = getInt32Memory0()[retptr / 4 + 1];
155
+ var r2 = getInt32Memory0()[retptr / 4 + 2];
156
+ if (r2) {
157
+ throw takeObject(r1);
158
+ }
159
+ return Model.__wrap(r0);
160
+ } finally {
161
+ wasm.__wbindgen_add_to_stack_pointer(16);
162
+ }
163
+ }
164
+ /**
165
+ * @param {Uint8Array} image_data
166
+ */
167
+ set_image_embeddings(image_data) {
168
+ try {
169
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
170
+ const ptr0 = passArray8ToWasm0(image_data, wasm.__wbindgen_malloc);
171
+ const len0 = WASM_VECTOR_LEN;
172
+ wasm.model_set_image_embeddings(retptr, this.__wbg_ptr, ptr0, len0);
173
+ var r0 = getInt32Memory0()[retptr / 4 + 0];
174
+ var r1 = getInt32Memory0()[retptr / 4 + 1];
175
+ if (r1) {
176
+ throw takeObject(r0);
177
+ }
178
+ } finally {
179
+ wasm.__wbindgen_add_to_stack_pointer(16);
180
+ }
181
+ }
182
+ /**
183
+ * @param {number} x
184
+ * @param {number} y
185
+ * @returns {string}
186
+ */
187
+ mask_for_point(x, y) {
188
+ let deferred2_0;
189
+ let deferred2_1;
190
+ try {
191
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
192
+ wasm.model_mask_for_point(retptr, this.__wbg_ptr, x, y);
193
+ var r0 = getInt32Memory0()[retptr / 4 + 0];
194
+ var r1 = getInt32Memory0()[retptr / 4 + 1];
195
+ var r2 = getInt32Memory0()[retptr / 4 + 2];
196
+ var r3 = getInt32Memory0()[retptr / 4 + 3];
197
+ var ptr1 = r0;
198
+ var len1 = r1;
199
+ if (r3) {
200
+ ptr1 = 0; len1 = 0;
201
+ throw takeObject(r2);
202
+ }
203
+ deferred2_0 = ptr1;
204
+ deferred2_1 = len1;
205
+ return getStringFromWasm0(ptr1, len1);
206
+ } finally {
207
+ wasm.__wbindgen_add_to_stack_pointer(16);
208
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
209
+ }
210
+ }
211
+ }
212
+
213
+ async function __wbg_load(module, imports) {
214
+ if (typeof Response === 'function' && module instanceof Response) {
215
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
216
+ try {
217
+ return await WebAssembly.instantiateStreaming(module, imports);
218
+
219
+ } catch (e) {
220
+ if (module.headers.get('Content-Type') != 'application/wasm') {
221
+ console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
222
+
223
+ } else {
224
+ throw e;
225
+ }
226
+ }
227
+ }
228
+
229
+ const bytes = await module.arrayBuffer();
230
+ return await WebAssembly.instantiate(bytes, imports);
231
+
232
+ } else {
233
+ const instance = await WebAssembly.instantiate(module, imports);
234
+
235
+ if (instance instanceof WebAssembly.Instance) {
236
+ return { instance, module };
237
+
238
+ } else {
239
+ return instance;
240
+ }
241
+ }
242
+ }
243
+
244
+ function __wbg_get_imports() {
245
+ const imports = {};
246
+ imports.wbg = {};
247
+ imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
248
+ const ret = new Error(getStringFromWasm0(arg0, arg1));
249
+ return addHeapObject(ret);
250
+ };
251
+ imports.wbg.__wbg_new_abda76e883ba8a5f = function() {
252
+ const ret = new Error();
253
+ return addHeapObject(ret);
254
+ };
255
+ imports.wbg.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) {
256
+ const ret = getObject(arg1).stack;
257
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
258
+ const len1 = WASM_VECTOR_LEN;
259
+ getInt32Memory0()[arg0 / 4 + 1] = len1;
260
+ getInt32Memory0()[arg0 / 4 + 0] = ptr1;
261
+ };
262
+ imports.wbg.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) {
263
+ let deferred0_0;
264
+ let deferred0_1;
265
+ try {
266
+ deferred0_0 = arg0;
267
+ deferred0_1 = arg1;
268
+ console.error(getStringFromWasm0(arg0, arg1));
269
+ } finally {
270
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
271
+ }
272
+ };
273
+ imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
274
+ takeObject(arg0);
275
+ };
276
+ imports.wbg.__wbg_log_fa2e12d24033131f = function(arg0, arg1) {
277
+ console.log(getStringFromWasm0(arg0, arg1));
278
+ };
279
+ imports.wbg.__wbindgen_throw = function(arg0, arg1) {
280
+ throw new Error(getStringFromWasm0(arg0, arg1));
281
+ };
282
+
283
+ return imports;
284
+ }
285
+
286
+ function __wbg_init_memory(imports, maybe_memory) {
287
+
288
+ }
289
+
290
+ function __wbg_finalize_init(instance, module) {
291
+ wasm = instance.exports;
292
+ __wbg_init.__wbindgen_wasm_module = module;
293
+ cachedInt32Memory0 = null;
294
+ cachedUint8Memory0 = null;
295
+
296
+ wasm.__wbindgen_start();
297
+ return wasm;
298
+ }
299
+
300
+ function initSync(module) {
301
+ if (wasm !== undefined) return wasm;
302
+
303
+ const imports = __wbg_get_imports();
304
+
305
+ __wbg_init_memory(imports);
306
+
307
+ if (!(module instanceof WebAssembly.Module)) {
308
+ module = new WebAssembly.Module(module);
309
+ }
310
+
311
+ const instance = new WebAssembly.Instance(module, imports);
312
+
313
+ return __wbg_finalize_init(instance, module);
314
+ }
315
+
316
+ async function __wbg_init(input) {
317
+ if (wasm !== undefined) return wasm;
318
+
319
+ if (typeof input === 'undefined') {
320
+ input = new URL('m_bg.wasm', import.meta.url);
321
+ }
322
+ const imports = __wbg_get_imports();
323
+
324
+ if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
325
+ input = fetch(input);
326
+ }
327
+
328
+ __wbg_init_memory(imports);
329
+
330
+ const { instance, module } = await __wbg_load(await input, imports);
331
+
332
+ return __wbg_finalize_init(instance, module);
333
+ }
334
+
335
+ export { initSync }
336
+ export default __wbg_init;
build/m_bg.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:04d9e212b7dc7428d55a6d8b61d0c5f1d99787187167ba46217a01235ea88946
3
+ size 2548604
build/m_bg.wasm.d.ts ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ export const memory: WebAssembly.Memory;
4
+ export function __wbg_model_free(a: number): void;
5
+ export function model_new(a: number, b: number, c: number, d: number): void;
6
+ export function model_set_image_embeddings(a: number, b: number, c: number, d: number): void;
7
+ export function model_mask_for_point(a: number, b: number, c: number, d: number): void;
8
+ export function main(a: number, b: number): number;
9
+ export function __wbindgen_add_to_stack_pointer(a: number): number;
10
+ export function __wbindgen_malloc(a: number, b: number): number;
11
+ export function __wbindgen_free(a: number, b: number, c: number): void;
12
+ export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number;
13
+ export function __wbindgen_start(): void;
index.html CHANGED
@@ -1,19 +1,406 @@
 
 
 
 
 
 
 
 
1
  <!DOCTYPE html>
2
  <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <html>
2
+ <head>
3
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
4
+ <title>Candle Segment Anything Model (SAM) Rust/WASM</title>
5
+ </head>
6
+ <body></body>
7
+ </html>
8
+
9
  <!DOCTYPE html>
10
  <html>
11
+ <head>
12
+ <meta charset="UTF-8" />
13
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
14
+ <style>
15
+ @import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@200;300;400&family=Source+Sans+3:wght@100;200;300;400;500;600;700;800;900&display=swap");
16
+ html,
17
+ body {
18
+ font-family: "Source Sans 3", sans-serif;
19
+ }
20
+ </style>
21
+ <script src="https://cdn.tailwindcss.com"></script>
22
+ <script type="module">
23
+ // base url for audio examples
24
+ const MODEL_BASEURL =
25
+ "https://huggingface.co/lmz/candle-sam/resolve/main/";
26
+
27
+ // models base url
28
+ const MODELS = {
29
+ sam_mobile_tiny: {
30
+ url: "mobile_sam-tiny-vitt.safetensors",
31
+ },
32
+ sam_base: {
33
+ url: "sam_vit_b_01ec64.safetensors",
34
+ },
35
+ };
36
+ const samWorker = new Worker("./samWorker.js", { type: "module" });
37
+
38
+ async function segmentPoints(
39
+ modelURL, // URL to the weights file
40
+ modelID, // model ID
41
+ imageURL, // URL to the audio file
42
+ points // {x, y} points to prompt image
43
+ ) {
44
+ return new Promise((resolve, reject) => {
45
+ function messageHandler(event) {
46
+ console.log(event.data);
47
+ if ("status" in event.data) {
48
+ updateStatus(event.data);
49
+ }
50
+ if ("error" in event.data) {
51
+ samWorker.removeEventListener("message", messageHandler);
52
+ reject(new Error(event.data.error));
53
+ }
54
+ if (event.data.status === "complete-embedding") {
55
+ samWorker.removeEventListener("message", messageHandler);
56
+ resolve();
57
+ }
58
+ if (event.data.status === "complete") {
59
+ samWorker.removeEventListener("message", messageHandler);
60
+ resolve(event.data.output);
61
+ }
62
+ }
63
+ samWorker.addEventListener("message", messageHandler);
64
+ samWorker.postMessage({
65
+ modelURL,
66
+ modelID,
67
+ imageURL,
68
+ points,
69
+ });
70
+ });
71
+ }
72
+ function updateStatus(statusMessage) {
73
+ statusOutput.innerText = event.data.message;
74
+ }
75
+
76
+ const clearBtn = document.querySelector("#clear-btn");
77
+ const canvas = document.querySelector("#canvas");
78
+ const mask = document.querySelector("#mask");
79
+ const ctxCanvas = canvas.getContext("2d");
80
+ const ctxMask = mask.getContext("2d");
81
+ const fileUpload = document.querySelector("#file-upload");
82
+ const dropArea = document.querySelector("#drop-area");
83
+ const dropButtons = document.querySelector("#drop-buttons");
84
+ const imagesExamples = document.querySelector("#image-select");
85
+ const modelSelection = document.querySelector("#model");
86
+ const statusOutput = document.querySelector("#output-status");
87
+
88
+ //add event listener to file input
89
+ fileUpload.addEventListener("change", (e) => {
90
+ const target = e.target;
91
+ if (target.files.length > 0) {
92
+ const href = URL.createObjectURL(target.files[0]);
93
+ cleanImageCanvas();
94
+ drawImageCanvas(href);
95
+ setImageEmbeddings(href);
96
+ }
97
+ });
98
+ // add event listener to drop-area
99
+ dropArea.addEventListener("dragenter", (e) => {
100
+ e.preventDefault();
101
+ dropArea.classList.add("border-blue-700");
102
+ });
103
+ dropArea.addEventListener("dragleave", (e) => {
104
+ e.preventDefault();
105
+ dropArea.classList.remove("border-blue-700");
106
+ });
107
+ dropArea.addEventListener("dragover", (e) => {
108
+ e.preventDefault();
109
+ });
110
+ dropArea.addEventListener("drop", (e) => {
111
+ e.preventDefault();
112
+ dropArea.classList.remove("border-blue-700");
113
+ const url = e.dataTransfer.getData("text/uri-list");
114
+ const files = e.dataTransfer.files;
115
+
116
+ if (files.length > 0) {
117
+ const href = URL.createObjectURL(files[0]);
118
+ cleanImageCanvas();
119
+ drawImageCanvas(href);
120
+ setImageEmbeddings(href);
121
+ } else if (url) {
122
+ cleanImageCanvas();
123
+ drawImageCanvas(url);
124
+ setImageEmbeddings(url);
125
+ }
126
+ });
127
+
128
+ let hasImage = false;
129
+ let isSegmenting = false;
130
+ let isEmbedding = false;
131
+ let currentImageURL = "";
132
+ //add event listener to image examples
133
+ imagesExamples.addEventListener("click", (e) => {
134
+ if (isEmbedding || isSegmenting) {
135
+ return;
136
+ }
137
+ const target = e.target;
138
+ if (target.nodeName === "IMG") {
139
+ const href = target.src;
140
+ cleanImageCanvas();
141
+ drawImageCanvas(href);
142
+ setImageEmbeddings(href);
143
+ }
144
+ });
145
+ //add event listener to clear button
146
+ clearBtn.addEventListener("click", () => {
147
+ cleanImageCanvas();
148
+ });
149
+ //add click event to canvas
150
+ canvas.addEventListener("click", async (event) => {
151
+ if (!hasImage || isEmbedding || isSegmenting) {
152
+ return;
153
+ }
154
+ const targetBox = event.target.getBoundingClientRect();
155
+ const x = (event.clientX - targetBox.left) / targetBox.width;
156
+ const y = (event.clientY - targetBox.top) / targetBox.height;
157
+ isSegmenting = true;
158
+ const { maskURL } = await getSegmentationMask({ x, y });
159
+ isSegmenting = false;
160
+ drawMask(maskURL);
161
+ });
162
+
163
+ async function getSegmentationMask(points) {
164
+ const modelID = modelSelection.value;
165
+ const modelURL = MODEL_BASEURL + MODELS[modelID].url;
166
+ const imageURL = currentImageURL;
167
+ const { maskURL } = await segmentPoints(
168
+ modelURL,
169
+ modelID,
170
+ imageURL,
171
+ points
172
+ );
173
+ return { maskURL };
174
+ }
175
+ async function setImageEmbeddings(imageURL) {
176
+ if (isEmbedding) {
177
+ return;
178
+ }
179
+ canvas.classList.remove("cursor-pointer");
180
+ canvas.classList.add("cursor-wait");
181
+ clearBtn.disabled = true;
182
+ const modelID = modelSelection.value;
183
+ const modelURL = MODEL_BASEURL + MODELS[modelID].url;
184
+ isEmbedding = true;
185
+ await segmentPoints(modelURL, modelID, imageURL);
186
+ canvas.classList.remove("cursor-wait");
187
+ canvas.classList.add("cursor-pointer");
188
+ clearBtn.disabled = false;
189
+ isEmbedding = false;
190
+ currentImageURL = imageURL;
191
+ }
192
+
193
+ function cleanImageCanvas() {
194
+ ctxCanvas.clearRect(0, 0, canvas.width, canvas.height);
195
+ ctxMask.clearRect(0, 0, canvas.width, canvas.height);
196
+ hasImage = false;
197
+ isEmbedding = false;
198
+ isSegmenting = false;
199
+ currentImageURL = "";
200
+ clearBtn.classList.add("invisible");
201
+ canvas.parentElement.style.height = "auto";
202
+ dropButtons.classList.remove("invisible");
203
+ }
204
+ function drawMask(maskURL) {
205
+ if (!maskURL) {
206
+ throw new Error("No mask URL provided");
207
+ }
208
+
209
+ const img = new Image();
210
+ img.crossOrigin = "anonymous";
211
+
212
+ img.onload = () => {
213
+ mask.width = canvas.width;
214
+ mask.height = canvas.height;
215
+ ctxMask.drawImage(canvas, 0, 0);
216
+ ctxMask.globalCompositeOperation = "source-atop";
217
+ ctxMask.fillStyle = "rgba(255, 0, 0, 0.6)";
218
+ ctxMask.fillRect(0, 0, canvas.width, canvas.height);
219
+ ctxMask.globalCompositeOperation = "destination-in";
220
+ ctxMask.drawImage(img, 0, 0);
221
+ };
222
+ img.src = maskURL;
223
+ }
224
+ function drawImageCanvas(imgURL) {
225
+ if (!imgURL) {
226
+ throw new Error("No image URL provided");
227
+ }
228
+
229
+ ctxCanvas.clearRect(0, 0, canvas.width, canvas.height);
230
+ ctxCanvas.clearRect(0, 0, canvas.width, canvas.height);
231
+
232
+ const img = new Image();
233
+ img.crossOrigin = "anonymous";
234
+
235
+ img.onload = () => {
236
+ canvas.width = img.width;
237
+ canvas.height = img.height;
238
+ ctxCanvas.drawImage(img, 0, 0);
239
+ hasImage = true;
240
+ clearBtn.classList.remove("invisible");
241
+ dropButtons.classList.add("invisible");
242
+ };
243
+ img.src = imgURL;
244
+ }
245
+
246
+ const observer = new ResizeObserver((entries) => {
247
+ for (let entry of entries) {
248
+ if (entry.target === canvas) {
249
+ canvas.parentElement.style.height = canvas.offsetHeight + "px";
250
+ }
251
+ }
252
+ });
253
+ observer.observe(canvas);
254
+ </script>
255
+ </head>
256
+ <body class="container max-w-4xl mx-auto p-4">
257
+ <main class="grid grid-cols-1 gap-8 relative">
258
+ <span class="absolute text-5xl -ml-[1em]">🕯️</span>
259
+ <div>
260
+ <h1 class="text-5xl font-bold">Candle Segment Anything</h1>
261
+ <h2 class="text-2xl font-bold">Rust/WASM Demo</h2>
262
+ <p class="max-w-lg">
263
+ Zero-shot image segmentation with
264
+ <a
265
+ href="https://segment-anything.com"
266
+ class="underline hover:text-blue-500 hover:no-underline"
267
+ target="_blank"
268
+ >Segment Anything Model (SAM)</a
269
+ >
270
+ and
271
+ <a
272
+ href="https://github.com/ChaoningZhang/MobileSAM"
273
+ class="underline hover:text-blue-500 hover:no-underline"
274
+ target="_blank"
275
+ >MobileSAM </a
276
+ >. It runs in the browser with a WASM runtime built with
277
+ <a
278
+ href="https://github.com/huggingface/candle/"
279
+ target="_blank"
280
+ class="underline hover:text-blue-500 hover:no-underline"
281
+ >Candle
282
+ </a>
283
+ </p>
284
+ </div>
285
+ <div>
286
+ <label for="model" class="font-medium">Models Options: </label>
287
+ <select
288
+ id="model"
289
+ class="border-2 border-gray-500 rounded-md font-light"
290
+ >
291
+ <option value="sam_mobile_tiny" selected>
292
+ Mobile SAM Tiny (40.6 MB)
293
+ </option>
294
+ <option value="sam_base">SAM Base (375 MB)</option>
295
+ </select>
296
+ </div>
297
+ <div>
298
+ <p class="text-xs italic max-w-lg">
299
+ <b>Note:</b>
300
+ The model's first run may take a few seconds as it loads and caches
301
+ the model in the browser, and then creates the image embeddings. Any
302
+ subsequent clicks on points will be significantly faster.
303
+ </p>
304
+ </div>
305
+ <div class="relative max-w-lg">
306
+ <div class="flex justify-between items-center">
307
+ <div class="px-2 rounded-md inline text-xs">
308
+ <span id="output-status" class="m-auto font-light"></span>
309
+ </div>
310
+ <button
311
+ id="clear-btn"
312
+ class="text-xs bg-white rounded-md disabled:opacity-50 flex gap-1 items-center invisible"
313
+ >
314
+ <svg
315
+ class=""
316
+ xmlns="http://www.w3.org/2000/svg"
317
+ viewBox="0 0 13 12"
318
+ height="1em"
319
+ >
320
+ <path
321
+ d="M1.6.7 12 11.1M12 .7 1.6 11.1"
322
+ stroke="#2E3036"
323
+ stroke-width="2"
324
+ />
325
+ </svg>
326
+ Clear image
327
+ </button>
328
+ </div>
329
+ <div
330
+ id="drop-area"
331
+ class="flex flex-col items-center justify-center border-2 border-gray-300 border-dashed rounded-xl relative p-20 w-full overflow-hidden"
332
+ >
333
+ <div
334
+ id="drop-buttons"
335
+ class="flex flex-col items-center justify-center space-y-1 text-center relative z-10"
336
+ >
337
+ <svg
338
+ width="25"
339
+ height="25"
340
+ viewBox="0 0 25 25"
341
+ fill="none"
342
+ xmlns="http://www.w3.org/2000/svg"
343
+ >
344
+ <path
345
+ d="M3.5 24.3a3 3 0 0 1-1.9-.8c-.5-.5-.8-1.2-.8-1.9V2.9c0-.7.3-1.3.8-1.9.6-.5 1.2-.7 2-.7h18.6c.7 0 1.3.2 1.9.7.5.6.7 1.2.7 2v18.6c0 .7-.2 1.4-.7 1.9a3 3 0 0 1-2 .8H3.6Zm0-2.7h18.7V2.9H3.5v18.7Zm2.7-2.7h13.3c.3 0 .5 0 .6-.3v-.7l-3.7-5a.6.6 0 0 0-.6-.2c-.2 0-.4 0-.5.3l-3.5 4.6-2.4-3.3a.6.6 0 0 0-.6-.3c-.2 0-.4.1-.5.3l-2.7 3.6c-.1.2-.2.4 0 .7.1.2.3.3.6.3Z"
346
+ fill="#000"
347
+ />
348
+ </svg>
349
+ <div class="flex text-sm text-gray-600">
350
+ <label
351
+ for="file-upload"
352
+ class="relative cursor-pointer bg-white rounded-md font-medium text-blue-950 hover:text-blue-700"
353
+ >
354
+ <span>Drag and drop your image here</span>
355
+ <span class="block text-xs">or</span>
356
+ <span class="block text-xs">Click to upload</span>
357
+ </label>
358
+ </div>
359
+ <input
360
+ id="file-upload"
361
+ name="file-upload"
362
+ type="file"
363
+ class="sr-only"
364
+ />
365
+ </div>
366
+ <canvas id="canvas" class="absolute w-full"></canvas>
367
+ <canvas
368
+ id="mask"
369
+ class="pointer-events-none absolute w-full"
370
+ ></canvas>
371
+ </div>
372
+ <div class="text-right py-2">
373
+ <button
374
+ id="share-btn"
375
+ class="bg-white rounded-md hover:outline outline-orange-200 disabled:opacity-50 invisible"
376
+ >
377
+ <img
378
+ src="https://huggingface.co/datasets/huggingface/badges/raw/main/share-to-community-sm.svg"
379
+ />
380
+ </button>
381
+ </div>
382
+ </div>
383
+ <div>
384
+ <div
385
+ class="flex gap-3 items-center overflow-x-scroll"
386
+ id="image-select"
387
+ >
388
+ <h3 class="font-medium">Examples:</h3>
389
+
390
+ <img
391
+ src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/candle/examples/sf.jpg"
392
+ class="cursor-pointer w-24 h-24 object-cover"
393
+ />
394
+ <img
395
+ src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/candle/examples/bike.jpeg"
396
+ class="cursor-pointer w-24 h-24 object-cover"
397
+ />
398
+ <img
399
+ src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/candle/examples/000000000077.jpg"
400
+ class="cursor-pointer w-24 h-24 object-cover"
401
+ />
402
+ </div>
403
+ </div>
404
+ </main>
405
+ </body>
406
  </html>
samWorker.js ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //load the candle SAM Model wasm module
2
+ import init, { Model } from "./build/m.js";
3
+
4
+ async function fetchArrayBuffer(url, cacheModel = true) {
5
+ if (!cacheModel)
6
+ return new Uint8Array(await (await fetch(url)).arrayBuffer());
7
+ const cacheName = "sam-candle-cache";
8
+ const cache = await caches.open(cacheName);
9
+ const cachedResponse = await cache.match(url);
10
+ if (cachedResponse) {
11
+ const data = await cachedResponse.arrayBuffer();
12
+ return new Uint8Array(data);
13
+ }
14
+ const res = await fetch(url, { cache: "force-cache" });
15
+ cache.put(url, res.clone());
16
+ return new Uint8Array(await res.arrayBuffer());
17
+ }
18
+ class SAMModel {
19
+ static instance = {};
20
+ // keep current image embeddings state
21
+ static imageArrayHash = {};
22
+ // Add a new property to hold the current modelID
23
+ static currentModelID = null;
24
+
25
+ static async getInstance(modelURL, modelID) {
26
+ if (!this.instance[modelID]) {
27
+ await init();
28
+
29
+ self.postMessage({
30
+ status: "loading",
31
+ message: `Loading Model ${modelID}`,
32
+ });
33
+ const weightsArrayU8 = await fetchArrayBuffer(modelURL);
34
+ this.instance[modelID] = new Model(
35
+ weightsArrayU8,
36
+ /tiny|mobile/.test(modelID)
37
+ );
38
+ } else {
39
+ self.postMessage({ status: "loading", message: "Model Already Loaded" });
40
+ }
41
+ // Set the current modelID to the modelID that was passed in
42
+ this.currentModelID = modelID;
43
+ return this.instance[modelID];
44
+ }
45
+
46
+ // Remove the modelID parameter from setImageEmbeddings
47
+ static setImageEmbeddings(imageArrayU8) {
48
+ // check if image embeddings are already set for this image and model
49
+ const imageArrayHash = this.getSimpleHash(imageArrayU8);
50
+ if (
51
+ this.imageArrayHash[this.currentModelID] === imageArrayHash &&
52
+ this.instance[this.currentModelID]
53
+ ) {
54
+ self.postMessage({
55
+ status: "embedding",
56
+ message: "Embeddings Already Set",
57
+ });
58
+ return;
59
+ }
60
+ this.imageArrayHash[this.currentModelID] = imageArrayHash;
61
+ this.instance[this.currentModelID].set_image_embeddings(imageArrayU8);
62
+ self.postMessage({ status: "embedding", message: "Embeddings Set" });
63
+ }
64
+
65
+ static getSimpleHash(imageArrayU8) {
66
+ // get simple hash of imageArrayU8
67
+ let imageArrayHash = 0;
68
+ for (let i = 0; i < imageArrayU8.length; i += 100) {
69
+ imageArrayHash ^= imageArrayU8[i];
70
+ }
71
+ return imageArrayHash.toString(16);
72
+ }
73
+ }
74
+
75
+ async function createImageCanvas(
76
+ { mask_shape, mask_data }, // mask
77
+ { original_width, original_height, width, height } // original image
78
+ ) {
79
+ const [_, __, shape_width, shape_height] = mask_shape;
80
+ const maskCanvas = new OffscreenCanvas(shape_width, shape_height); // canvas for mask
81
+ const maskCtx = maskCanvas.getContext("2d");
82
+ const canvas = new OffscreenCanvas(original_width, original_height); // canvas for creating mask with original image size
83
+ const ctx = canvas.getContext("2d");
84
+
85
+ const imageData = maskCtx.createImageData(
86
+ maskCanvas.width,
87
+ maskCanvas.height
88
+ );
89
+ const data = imageData.data;
90
+
91
+ for (let p = 0; p < data.length; p += 4) {
92
+ data[p] = 0;
93
+ data[p + 1] = 0;
94
+ data[p + 2] = 0;
95
+ data[p + 3] = mask_data[p / 4] * 255;
96
+ }
97
+ maskCtx.putImageData(imageData, 0, 0);
98
+
99
+ let sx, sy;
100
+ if (original_height < original_width) {
101
+ sy = original_height / original_width;
102
+ sx = 1;
103
+ } else {
104
+ sy = 1;
105
+ sx = original_width / original_height;
106
+ }
107
+ ctx.drawImage(
108
+ maskCanvas,
109
+ 0,
110
+ 0,
111
+ maskCanvas.width * sx,
112
+ maskCanvas.height * sy,
113
+ 0,
114
+ 0,
115
+ original_width,
116
+ original_height
117
+ );
118
+
119
+ const blob = await canvas.convertToBlob();
120
+ return URL.createObjectURL(blob);
121
+ }
122
+
123
+ self.addEventListener("message", async (event) => {
124
+ const { modelURL, modelID, imageURL, points } = event.data;
125
+ try {
126
+ self.postMessage({ status: "loading", message: "Starting SAM" });
127
+ const sam = await SAMModel.getInstance(modelURL, modelID);
128
+
129
+ self.postMessage({ status: "loading", message: "Loading Image" });
130
+ const imageArrayU8 = await fetchArrayBuffer(imageURL, false);
131
+
132
+ self.postMessage({ status: "embedding", message: "Creating Embeddings" });
133
+ SAMModel.setImageEmbeddings(imageArrayU8);
134
+ if (!points) {
135
+ // no points only do the embeddings
136
+ self.postMessage({
137
+ status: "complete-embedding",
138
+ message: "Embeddings Complete",
139
+ });
140
+ return;
141
+ }
142
+
143
+ self.postMessage({ status: "segmenting", message: "Segmenting" });
144
+ const result = sam.mask_for_point(points.x, points.y);
145
+ const { mask, image } = JSON.parse(result);
146
+ const maskDataURL = await createImageCanvas(mask, image);
147
+ // Send the segment back to the main thread as JSON
148
+ self.postMessage({
149
+ status: "complete",
150
+ message: "Segmentation Complete",
151
+ output: { maskURL: maskDataURL },
152
+ });
153
+ } catch (e) {
154
+ self.postMessage({ error: e });
155
+ }
156
+ });