| import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.0-alpha.19'; |
|
|
| |
| 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); |
| } |
|
|
| |
| 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); |
| } |
| } |
| } |
| }); |
|
|