Download src/face-worker.js from NagaYu/dasumae: direct link, hf CLI and curl.
- Browser
- Download file 3.63 kB
-
https://huggingface.co/spaces/NagaYu/dasumae/resolve/main/src/face-worker.js
- Command line
-
hf download hf://spaces/NagaYu/dasumae/src/face-worker.js
-
curl -L -o face-worker.js https://huggingface.co/spaces/NagaYu/dasumae/resolve/main/src/face-worker.js
3.63 kB
| // Face-detection worker — UltraFace (version-RFB-320) via onnxruntime-web, CPU | |
| // WASM backend, self-hosted. Kept off the main thread so model load + inference | |
| // never freeze the UI. Pre/post-processing mirrors the Phase 0 face spike. | |
| // | |
| // Protocol: | |
| // main -> worker: { type:'init' } | |
| // { type:'detect', rgba:ArrayBuffer(320*240*4), origW, origH, thr } | |
| // worker -> main: { type:'ready' } | { type:'error', message } | |
| // { type:'result', faces:[{x,y,w,h,score}] } (original-image px) | |
| import * as ort from '../vendor/ort/ort.wasm.bundle.min.mjs'; | |
| const IN_W = 320, IN_H = 240; | |
| const MODEL_URL = new URL('../vendor/models/version-RFB-320.onnx', import.meta.url); | |
| // All WASM served from our own origin; single-threaded so no cross-origin | |
| // isolation / SharedArrayBuffer is required on the static host. | |
| ort.env.wasm.wasmPaths = new URL('../vendor/ort/', import.meta.url).href; | |
| ort.env.wasm.numThreads = 1; | |
| ort.env.wasm.proxy = false; | |
| let session = null; | |
| let inputName = null; | |
| async function init() { | |
| if (session) return; | |
| const buf = await (await fetch(MODEL_URL)).arrayBuffer(); | |
| session = await ort.InferenceSession.create(new Uint8Array(buf), { | |
| executionProviders: ['wasm'], | |
| }); | |
| inputName = session.inputNames[0]; | |
| } | |
| function toTensor(rgba) { | |
| const chw = new Float32Array(3 * IN_W * IN_H); | |
| const plane = IN_W * IN_H; | |
| for (let y = 0; y < IN_H; y++) { | |
| for (let x = 0; x < IN_W; x++) { | |
| const i = (y * IN_W + x) * 4; | |
| const o = y * IN_W + x; | |
| chw[o] = (rgba[i] - 127) / 128; // R | |
| chw[plane + o] = (rgba[i + 1] - 127) / 128; // G | |
| chw[2 * plane + o] = (rgba[i + 2] - 127) / 128; // B | |
| } | |
| } | |
| return new ort.Tensor('float32', chw, [1, 3, IN_H, IN_W]); | |
| } | |
| function iou(a, b) { | |
| const ix = Math.max(0, Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x)); | |
| const iy = Math.max(0, Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y)); | |
| const inter = ix * iy; | |
| const uni = a.w * a.h + b.w * b.h - inter; | |
| return uni <= 0 ? 0 : inter / uni; | |
| } | |
| function postprocess(scores, boxes, N, origW, origH, thr) { | |
| const dets = []; | |
| for (let i = 0; i < N; i++) { | |
| const p = scores[i * 2 + 1]; | |
| if (p < thr) continue; | |
| const x1 = boxes[i * 4] * origW, y1 = boxes[i * 4 + 1] * origH; | |
| const x2 = boxes[i * 4 + 2] * origW, y2 = boxes[i * 4 + 3] * origH; | |
| dets.push({ score: +p.toFixed(3), x: Math.round(x1), y: Math.round(y1), w: Math.round(x2 - x1), h: Math.round(y2 - y1) }); | |
| } | |
| dets.sort((a, b) => b.score - a.score); | |
| const keep = []; | |
| for (const d of dets) { | |
| if (d.w <= 1 || d.h <= 1) continue; | |
| if (!keep.some((k) => iou(k, d) > 0.3)) keep.push(d); | |
| } | |
| return keep; | |
| } | |
| async function detect(rgbaBuf, origW, origH, thr) { | |
| await init(); | |
| const input = toTensor(new Uint8ClampedArray(rgbaBuf)); | |
| const out = await session.run({ [inputName]: input }); | |
| const scoresT = out[session.outputNames.find((n) => /score/i.test(n)) || session.outputNames[0]]; | |
| const boxesT = out[session.outputNames.find((n) => /box/i.test(n)) || session.outputNames[1]]; | |
| const N = scoresT.dims[1]; | |
| return postprocess(scoresT.data, boxesT.data, N, origW, origH, thr); | |
| } | |
| self.onmessage = async (e) => { | |
| const msg = e.data || {}; | |
| try { | |
| if (msg.type === 'init') { await init(); self.postMessage({ type: 'ready' }); return; } | |
| if (msg.type === 'detect') { | |
| const faces = await detect(msg.rgba, msg.origW, msg.origH, msg.thr ?? 0.7); | |
| self.postMessage({ type: 'result', faces }); | |
| } | |
| } catch (err) { | |
| self.postMessage({ type: 'error', message: String(err && err.message || err) }); | |
| } | |
| }; | |