import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.0-alpha.19'; // Memory & Performance Tuning env.backends.onnx.wasm.numThreads = 2; env.useBrowserCache = true; let objectDetector = null; let targetLanguage = 'spa_Latn'; let detectionThreshold = 0.75; let focusMode = true; const labelTranslations = { spa_Latn: { person: 'persona', bicycle: 'bicicleta', car: 'coche', motorcycle: 'motocicleta', airplane: 'avion', bus: 'autobus', train: 'tren', truck: 'camion', boat: 'barco', 'traffic light': 'semaforo', 'fire hydrant': 'hidrante', 'stop sign': 'senal de alto', bench: 'banco', bird: 'pajaro', cat: 'gato', dog: 'perro', horse: 'caballo', sheep: 'oveja', cow: 'vaca', backpack: 'mochila', umbrella: 'paraguas', handbag: 'bolso', tie: 'corbata', suitcase: 'maleta', frisbee: 'frisbee', skis: 'esquis', snowboard: 'tabla de snowboard', 'sports ball': 'pelota', kite: 'cometa', 'baseball bat': 'bate de beisbol', skateboard: 'patineta', surfboard: 'tabla de surf', bottle: 'botella', 'wine glass': 'copa', cup: 'taza', fork: 'tenedor', knife: 'cuchillo', spoon: 'cuchara', bowl: 'tazon', banana: 'platano', apple: 'manzana', sandwich: 'sandwich', orange: 'naranja', broccoli: 'brocoli', carrot: 'zanahoria', pizza: 'pizza', donut: 'dona', cake: 'pastel', chair: 'silla', couch: 'sofa', bed: 'cama', table: 'mesa', toilet: 'inodoro', tv: 'televisor', laptop: 'portatil', mouse: 'raton', remote: 'control remoto', keyboard: 'teclado', 'cell phone': 'telefono movil', microwave: 'microondas', oven: 'horno', toaster: 'tostadora', sink: 'fregadero', refrigerator: 'refrigerador', book: 'libro', clock: 'reloj', vase: 'florero', scissors: 'tijeras', 'teddy bear': 'oso de peluche', 'hair drier': 'secador de pelo', toothbrush: 'cepillo de dientes' }, fra_Latn: { person: 'personne', bicycle: 'velo', car: 'voiture', motorcycle: 'moto', airplane: 'avion', bus: 'bus', train: 'train', truck: 'camion', boat: 'bateau', 'traffic light': 'feu de circulation', 'stop sign': 'panneau stop', bench: 'banc', bird: 'oiseau', cat: 'chat', dog: 'chien', horse: 'cheval', sheep: 'mouton', cow: 'vache', backpack: 'sac a dos', umbrella: 'parapluie', handbag: 'sac a main', tie: 'cravate', suitcase: 'valise', 'sports ball': 'ballon', bottle: 'bouteille', 'wine glass': 'verre a vin', cup: 'tasse', fork: 'fourchette', knife: 'couteau', spoon: 'cuillere', bowl: 'bol', banana: 'banane', apple: 'pomme', sandwich: 'sandwich', orange: 'orange', broccoli: 'brocoli', carrot: 'carotte', pizza: 'pizza', donut: 'beignet', cake: 'gateau', chair: 'chaise', couch: 'canape', bed: 'lit', table: 'table', toilet: 'toilettes', tv: 'television', laptop: 'ordinateur portable', mouse: 'souris', remote: 'telecommande', keyboard: 'clavier', 'cell phone': 'telephone portable', book: 'livre', clock: 'horloge', vase: 'vase', scissors: 'ciseaux', toothbrush: 'brosse a dents' }, zho_Hans: { person: 'ren', car: 'qiche', bus: 'gonggongqiche', train: 'huoche', truck: 'ka che', bird: 'niao', cat: 'mao', dog: 'gou', horse: 'ma', backpack: 'beibao', umbrella: 'yu san', bottle: 'pingzi', cup: 'beizi', fork: 'chazi', knife: 'dao', spoon: 'shaozi', bowl: 'wan', banana: 'xiangjiao', apple: 'pingguo', orange: 'chengzi', carrot: 'huluobo', pizza: 'pisa', cake: 'dangao', chair: 'yizi', couch: 'shafa', bed: 'chuang', table: 'zhuozi', tv: 'dianshi', laptop: 'bijibendiannao', mouse: 'shubiao', keyboard: 'jianpan', 'cell phone': 'shouji', book: 'shu', clock: 'zhong', toothbrush: 'yashua' }, hin_Deva: { person: 'vyakti', bicycle: 'saikil', car: 'kar', bus: 'bas', train: 'relgadi', truck: 'trak', bird: 'pakshi', cat: 'billi', dog: 'kutta', horse: 'ghoda', backpack: 'basta', umbrella: 'chhata', bottle: 'botal', cup: 'cup', fork: 'kanta', knife: 'chaku', spoon: 'chammach', bowl: 'katora', banana: 'kela', apple: 'seb', orange: 'santra', carrot: 'gajar', pizza: 'pizza', cake: 'cake', chair: 'kursi', couch: 'sofa', bed: 'bistar', table: 'mez', tv: 'TV', laptop: 'laptop', mouse: 'mouse', keyboard: 'keyboard', 'cell phone': 'mobile phone', book: 'kitaab', clock: 'ghadi', toothbrush: 'toothbrush' }, afr_Latn: { person: 'persoon', bicycle: 'fiets', car: 'motor', bus: 'bus', train: 'trein', truck: 'vragmotor', bird: 'voel', cat: 'kat', dog: 'hond', horse: 'perd', backpack: 'rugsak', umbrella: 'sambreel', bottle: 'bottel', cup: 'koppie', fork: 'vurk', knife: 'mes', spoon: 'lepel', bowl: 'bak', banana: 'piesang', apple: 'appel', orange: 'lemoen', carrot: 'wortel', pizza: 'pizza', cake: 'koek', chair: 'stoel', couch: 'bank', bed: 'bed', table: 'tafel', tv: 'televisie', laptop: 'skootrekenaar', mouse: 'muis', keyboard: 'sleutelbord', 'cell phone': 'selfoon', book: 'boek', clock: 'horlosie', toothbrush: 'tandeborsel' }, zul_Latn: { person: 'umuntu', bicycle: 'ibhayisikili', car: 'imoto', bus: 'ibhasi', train: 'isitimela', truck: 'iloli', bird: 'inyoni', cat: 'ikati', dog: 'inja', horse: 'ihhashi', backpack: 'ubhaka', umbrella: 'isambulela', bottle: 'ibhodlela', cup: 'inkomishi', fork: 'imfoloko', knife: 'ummese', spoon: 'isipuni', bowl: 'isitsha', banana: 'ubhanana', apple: 'i-apula', orange: 'iwolintshi', carrot: 'isanqante', pizza: 'iphiza', cake: 'ikhekhe', chair: 'isitulo', couch: 'usofa', bed: 'umbhede', table: 'itafula', tv: 'ithelevishini', laptop: 'ikhompyutha ephathwayo', mouse: 'igundane', keyboard: 'ikhibhodi', 'cell phone': 'umakhalekhukhwini', book: 'incwadi', clock: 'iwashi', toothbrush: 'isixubho' } }; function translateLabel(label) { const normalizedLabel = label.toLowerCase().replaceAll('-', ' '); return labelTranslations[targetLanguage]?.[normalizedLabel] ?? label; } function getBoxArea(result) { const box = result.box; const xmin = Array.isArray(box) ? box[0] : box?.xmin; const ymin = Array.isArray(box) ? box[1] : box?.ymin; const xmax = Array.isArray(box) ? box[2] : box?.xmax; const ymax = Array.isArray(box) ? box[3] : box?.ymax; if (![xmin, ymin, xmax, ymax].every(Number.isFinite)) { return 0; } return Math.max(0, xmax - xmin) * Math.max(0, ymax - ymin); } // Helper to send progress updates to UI const progressCallback = (progress) => { if (progress.status === 'progress') { postMessage({ type: 'status', data: `Downloading ${progress.file}: ${Math.round(progress.progress)}%` }); } else if (progress.status === 'done') { postMessage({ type: 'status', data: `Loaded ${progress.file}` }); } }; async function initModels() { try { postMessage({ type: 'status', data: 'Initializing Vision Model (DETR)...' }); objectDetector = await pipeline('object-detection', 'Xenova/detr-resnet-50', { device: 'webgpu', progress_callback: progressCallback }); postMessage({ type: 'status', data: 'ready' }); } catch (err) { console.error("Model initialization failed:", err); postMessage({ type: 'status', data: 'Error loading models: ' + err.message }); } } initModels(); self.addEventListener('message', async (event) => { const { type, image, language, threshold } = event.data; if (type === 'set-language') { if (targetLanguage !== language) { targetLanguage = language; } } if (type === 'set-threshold') { detectionThreshold = threshold; } if (type === 'set-focus-mode') { focusMode = event.data.enabled; } if (type === 'detect' && objectDetector) { let imageUrl = null; try { imageUrl = URL.createObjectURL(image); const detectionResults = await objectDetector(imageUrl, { threshold: detectionThreshold, percentage: false }); const translatedResults = detectionResults .map((res) => ({ ...res, area: getBoxArea(res), translated_label: translateLabel(res.label) })) .filter((res) => res.area >= 2500) .filter((res) => !focusMode || res.label !== 'person') .sort((a, b) => (b.score * b.area) - (a.score * a.area)) .slice(0, focusMode ? 1 : 3); postMessage({ type: 'results', data: translatedResults }); } catch (err) { console.error("Inference error:", err); postMessage({ type: 'results', data: [] }); } finally { if (imageUrl) { URL.revokeObjectURL(imageUrl); } } } });