Spaces:
Running
Running
File size: 2,341 Bytes
e2c452c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | // Sightfern on-device detector: Google MediaPipe Object Detector (EfficientDet-Lite0, COCO classes, Apache-2.0),
// loaded only when the player opens the camera or picks a photo. The library and model files are downloaded once
// (then cached for offline use by sw.js); every image is analysed inside the browser and never uploaded.
// (c) 2026 CyberMax. All rights reserved. MediaPipe (c) Google LLC, Apache License 2.0.
import { SPECIES } from './core.js';
export const MP_VERSION = '0.10.14';
export const MP_BASE = `https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@${MP_VERSION}`;
export const MODEL = 'https://storage.googleapis.com/mediapipe-models/object_detector/efficientdet_lite0/int8/1/efficientdet_lite0.tflite';
export const MIN_SCORE = 0.4;
let pending = null;
/** Load once. Returns { detect(source) -> { ms, items }, delegate }. Rejects when the device cannot run it. */
export function loadDetector(prefer = 'CPU') { // CPU (WASM) by default: measured fast and exact (tests/detect-speed.mjs); GPU is opt-in
if (pending) return pending;
pending = (async () => {
const vision = await import(/* @vite-ignore */ `${MP_BASE}/vision_bundle.mjs`);
const files = await vision.FilesetResolver.forVisionTasks(`${MP_BASE}/wasm`);
const opts = (delegate) => ({ baseOptions: { modelAssetPath: MODEL, delegate }, runningMode: 'IMAGE', scoreThreshold: 0.3, maxResults: 5, categoryAllowlist: Object.keys(SPECIES) });
let det, delegate = prefer;
try { det = await vision.ObjectDetector.createFromOptions(files, opts(prefer)); } catch { delegate = 'CPU'; det = await vision.ObjectDetector.createFromOptions(files, opts('CPU')); }
return {
delegate,
/** source: <canvas>, <img> or ImageBitmap. Boxes are in the source's pixel space. */
detect(source) {
const t = performance.now();
const r = det.detect(source);
const items = (r.detections || []).map((d) => ({ species: d.categories[0].categoryName, score: d.categories[0].score, box: { x: d.boundingBox.originX, y: d.boundingBox.originY, w: d.boundingBox.width, h: d.boundingBox.height } }))
.filter((d) => d.species in SPECIES).sort((a, b) => b.score - a.score);
return { ms: performance.now() - t, items };
},
};
})();
pending.catch(() => { pending = null; });
return pending;
}
|