import { spawn } from 'child_process'; import { join } from 'path'; const EXE = join('tools', 'OcrDaemon', 'bin', 'Release', 'net8.0-windows10.0.19041.0', 'OcrDaemon.exe'); const TESSDATA = join('tools', 'OcrDaemon', 'bin', 'Release', 'net8.0-windows10.0.19041.0', 'tessdata'); const SAVE_DIR = join('tools', 'OcrDaemon', 'bin', 'Release', 'net8.0-windows10.0.19041.0', 'tessdata', 'images'); const expected = { vertex1: [ 'The Vertex', 'Tribal Mask', 'Helmet', 'Quality: +20%', 'Evasion Rating: 79', 'Energy Shield: 34', 'Requires: Level 33', '16% Increased Life Regeneration Rate', 'Has no Attribute Requirements', '+15% to Chaos Resistance', 'Skill gems have no attribute requirements', '+3 to level of all skills', '15% increased mana cost efficiency', 'Twice Corrupted', 'Asking Price:', '7x Divine Orb', ], vertex2: [ 'The Vertex', 'Tribal Mask', 'Helmet', 'Quality: +20%', 'Evasion Rating: 182', 'Energy Shield: 77', 'Requires: Level 33', '+29 To Spirit', '+1 to Level of All Minion Skills', 'Has no Attribute Requirements', '130% increased Evasion and Energy Shield', '27% Increased Critical Hit Chance', '+13% to Chaos Resistance', '+2 to level of all skills', 'Twice Corrupted', 'Asking Price:', '35x Divine Orb', ], }; function levenshteinSim(a, b) { a = a.toLowerCase(); b = b.toLowerCase(); if (a === b) return 1; const la = a.length, lb = b.length; if (!la || !lb) return 0; const d = Array.from({ length: la + 1 }, (_, i) => { const r = new Array(lb + 1); r[0] = i; return r; }); for (let j = 0; j <= lb; j++) d[0][j] = j; for (let i = 1; i <= la; i++) for (let j = 1; j <= lb; j++) { const cost = a[i-1] === b[j-1] ? 0 : 1; d[i][j] = Math.min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+cost); } return 1 - d[la][lb] / Math.max(la, lb); } async function run() { const proc = spawn(EXE, [], { stdio: ['pipe', 'pipe', 'pipe'] }); let buffer = ''; let resolveNext; proc.stdout.on('data', (data) => { buffer += data.toString(); let idx; while ((idx = buffer.indexOf('\n')) !== -1) { const line = buffer.slice(0, idx).trim(); buffer = buffer.slice(idx + 1); if (!line) continue; try { const p = JSON.parse(line); if (resolveNext) { const r = resolveNext; resolveNext = null; r(p); } } catch {} } }); proc.stderr.on('data', (data) => process.stderr.write(data)); function sendCmd(cmd) { return new Promise((resolve) => { resolveNext = resolve; proc.stdin.write(JSON.stringify(cmd) + '\n'); }); } await new Promise((resolve) => { resolveNext = resolve; }); const cases = [ { id: 'vertex1', image: 'vertex1.png', snapshot: 'vertex-snapshot.png' }, { id: 'vertex2', image: 'vertex2.png', snapshot: 'vertex-snapshot.png' }, ]; for (const tc of cases) { const snapPath = join(TESSDATA, 'images', tc.snapshot); const imgPath = join(TESSDATA, 'images', tc.image); // 3 runs: first saves crop, rest just timing for (let i = 0; i < 3; i++) { await sendCmd({ cmd: 'snapshot', file: snapPath }); const savePath = i === 0 ? join(SAVE_DIR, `${tc.id}_easyocr_crop.png`) : undefined; const t0 = performance.now(); const resp = await sendCmd({ cmd: 'diff-ocr', file: imgPath, engine: 'easyocr', ...(savePath ? { path: savePath } : {}) }); const ms = (performance.now() - t0).toFixed(0); const region = resp.region; const lines = (resp.lines || []).map(l => l.text.trim()).filter(l => l.length > 0); if (i === 0) { // Accuracy check on first run const exp = expected[tc.id]; const used = new Set(); let matched = 0, fuzzy = 0, missed = 0; for (const e of exp) { let bestIdx = -1, bestSim = 0; for (let j = 0; j < lines.length; j++) { if (used.has(j)) continue; const sim = levenshteinSim(e, lines[j]); if (sim > bestSim) { bestSim = sim; bestIdx = j; } } if (bestIdx >= 0 && bestSim >= 0.75) { used.add(bestIdx); if (bestSim >= 0.95) matched++; else fuzzy++; } else { missed++; console.log(` MISS: ${e}${bestIdx >= 0 ? ` (best: "${lines[bestIdx]}", sim=${bestSim.toFixed(2)})` : ''}`); } } console.log(`${tc.id}: ${ms}ms crop=${region?.width}x${region?.height} at (${region?.x},${region?.y}) ${matched} OK / ${fuzzy}~ / ${missed} miss lines=${lines.length}${savePath ? ' [saved]' : ''}`); } else { console.log(`${tc.id}: ${ms}ms crop=${region?.width}x${region?.height}`); } } console.log(); } proc.stdin.end(); proc.kill(); } run().catch(console.error);