MediaWiki:Gadget-LabelScan.js: Unterschied zwischen den Versionen
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung Markierung: Manuelle Zurücksetzung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 7: | Zeile 7: | ||
indexTitle: (window.LabelScanConfig && window.LabelScanConfig.indexTitle) || | indexTitle: (window.LabelScanConfig && window.LabelScanConfig.indexTitle) || | ||
'MediaWiki:Gadget-LabelScan-index.json', | 'MediaWiki:Gadget-LabelScan-index.json', | ||
// Lokaler ESM-Shim (setzt window.transformers): | |||
transformersShim: '/vendor/transformers/esm-shim.js', | |||
localModelPath: '/models', | // Lokale Pfade: | ||
localModelPath: '/models', | |||
wasmDir: '/vendor/transformers/', | |||
modelId: 'Xenova/clip-vit-base-patch32', | |||
// ---- Auto-Crop | topK: 8, | ||
maxSide: 1280, | |||
// ---- Auto-Crop ---- | |||
autoCrop: true, | autoCrop: true, | ||
edgeKeepRatio: 0.10, | edgeKeepRatio: 0.10, | ||
cropPadding: 0.08, | cropPadding: 0.08, | ||
cropMinRel: 0.40, | cropMinRel: 0.40, | ||
// ---- Score-Badges | // ---- Score-Badges ---- | ||
showNumericScore: false, | showNumericScore: false, | ||
confidenceBands: [0.90, 0.80], | confidenceBands: [0.90, 0.80], | ||
// ---- Sonstiges ---- | // ---- Sonstiges ---- | ||
| Zeile 73: | Zeile 77: | ||
} | } | ||
// --------- Transformers (lokal) ---------- | // --------- Transformers (lokal, ESM via Shim) ---------- | ||
let _visionLoadPromise=null; | let _visionLoadPromise=null; | ||
function loadModuleFile(url){ | |||
return new Promise((resolve,reject)=>{ | |||
const s=document.createElement('script'); | |||
s.type='module'; | |||
s.src=url; | |||
s.onload=()=>resolve(); | |||
s.onerror=()=>reject(new Error('Module load failed: '+url)); | |||
document.head.appendChild(s); | |||
}); | |||
} | |||
async function ensureClipVision(){ | async function ensureClipVision(){ | ||
if(_visionLoadPromise) return _visionLoadPromise; | if(_visionLoadPromise) return _visionLoadPromise; | ||
| Zeile 80: | Zeile 94: | ||
setStatus('Modell laden …'); setProgress(0.08); | setStatus('Modell laden …'); setProgress(0.08); | ||
_visionLoadPromise = (async()=>{ | _visionLoadPromise = (async ()=>{ | ||
// ESM-Shim laden (setzt window.transformers) | |||
await loadModuleFile(CFG.transformersShim); | |||
// Warten bis verfügbar | |||
const t0=Date.now(); | |||
while(!(window.transformers && typeof window.transformers==='object')){ | |||
if(Date.now()-t0>10000) throw new Error('Transformers-ESM nicht verfügbar (Timeout).'); | |||
await new Promise(r=>setTimeout(r,50)); | |||
} | |||
const mod = window.transformers; | |||
// Nur lokal laden | // Nur lokal laden | ||
| Zeile 88: | Zeile 111: | ||
mod.env.localModelPath = CFG.localModelPath; | mod.env.localModelPath = CFG.localModelPath; | ||
// | // Stabil: WASM bevorzugen & lokale Pfade setzen | ||
mod.env.backends = mod.env.backends || {}; | mod.env.backends = mod.env.backends || {}; | ||
mod.env.backends.onnx = mod.env.backends.onnx || {}; | mod.env.backends.onnx = mod.env.backends.onnx || {}; | ||
mod.env.backends.onnx.preferredBackend = 'wasm'; | |||
mod.env.backends.onnx.wasm = mod.env.backends.onnx.wasm || {}; | mod.env.backends.onnx.wasm = mod.env.backends.onnx.wasm || {}; | ||
mod.env.backends.onnx.wasm.wasmPaths = | mod.env.backends.onnx.wasm.wasmPaths = CFG.wasmDir; | ||
const [processor, model] = await Promise.all([ | const [processor, model] = await Promise.all([ | ||
| Zeile 143: | Zeile 161: | ||
} | } | ||
// Sobel-Kanten | // Sobel-Kanten | ||
const mag=new Float32Array(w*h); | const mag=new Float32Array(w*h); | ||
const sobelX=[-1,0,1,-2,0,2,-1,0,1]; | const sobelX=[-1,0,1,-2,0,2,-1,0,1]; | ||
| Zeile 160: | Zeile 178: | ||
} | } | ||
// | // oberes x%-Quantil | ||
const vals = Array.from(mag).sort((a,b)=>a-b); | const vals = Array.from(mag).sort((a,b)=>a-b); | ||
const keep = CFG.edgeKeepRatio; | const keep = CFG.edgeKeepRatio; | ||
| Zeile 175: | Zeile 193: | ||
} | } | ||
if(count<50) return inCanvas; | if(count<50) return inCanvas; | ||
// Padding | // Padding | ||
| Zeile 227: | Zeile 245: | ||
} | } | ||
// 3) Canvas → Blob → RawImage | // 3) Canvas → Blob → RawImage | ||
const blob = await new Promise(r => canvas.toBlob(r, 'image/jpeg', 0.95)); | const blob = await new Promise(r => canvas.toBlob(r, 'image/jpeg', 0.95)); | ||
const imageRaw = await mod.RawImage.fromBlob(blob); | const imageRaw = await mod.RawImage.fromBlob(blob); | ||
| Zeile 260: | Zeile 278: | ||
} | } | ||
// --------- Score-Badges | // --------- Score-Badges --------- | ||
function scoreBadge(score){ | function scoreBadge(score){ | ||
if (CFG.showNumericScore) { | if (CFG.showNumericScore) { | ||
| Zeile 272: | Zeile 290: | ||
} | } | ||
// --------- Rendering | // --------- Rendering --------- | ||
function renderResults(ranked){ | function renderResults(ranked){ | ||
const box=qs('ados-scan-results'); | const box=qs('ados-scan-results'); | ||
| Zeile 303: | Zeile 321: | ||
grid.style.gap='12px'; | grid.style.gap='12px'; | ||
ranked.slice(0,3).forEach(({i,score})=>{ | ranked.slice(0,3).forEach(({i,score})=>{ | ||
const it=INDEX[i]; | const it=INDEX[i]; | ||
| Zeile 309: | Zeile 326: | ||
}); | }); | ||
const rest = ranked.slice(3); | const rest = ranked.slice(3); | ||
if(rest.length){ | if(rest.length){ | ||
| Zeile 358: | Zeile 374: | ||
btnCam && btnCam.addEventListener('click', ()=> inCam.click()); | btnCam && btnCam.addEventListener('click', ()=> inCam.click()); | ||
btnGal && btnGal.addEventListener('click', ()=> inGal.click()); | btnGal && btnGal.addEventListener('click', ()=> inGal.click()); | ||
const pick = e => { const f=e.target.files | const pick = e => { const f=e.target.files && e.target.files[0]; if(f) showPreview(f); }; | ||
inCam.addEventListener('change', pick); | inCam.addEventListener('change', pick); | ||
inGal.addEventListener('change', pick); | inGal.addEventListener('change', pick); | ||
| Zeile 367: | Zeile 383: | ||
drop.addEventListener('drop', ev => { | drop.addEventListener('drop', ev => { | ||
ev.preventDefault(); drop.classList.remove('is-over'); | ev.preventDefault(); drop.classList.remove('is-over'); | ||
const f = ev.dataTransfer | const f = ev.dataTransfer && ev.dataTransfer.files && ev.dataTransfer.files[0]; | ||
if(f){ | if(f){ | ||
const dt=new DataTransfer(); dt.items.add(f); | const dt=new DataTransfer(); dt.items.add(f); | ||
| Zeile 393: | Zeile 409: | ||
const inGal = qs('ados-scan-file-gallery'); | const inGal = qs('ados-scan-file-gallery'); | ||
const file = inCam.files | const file = (inCam.files && inCam.files[0]) || (inGal.files && inGal.files[0]); | ||
if(!file){ alert('Bitte zuerst ein Foto auswählen.'); return; } | if(!file){ alert('Bitte zuerst ein Foto auswählen.'); return; } | ||