dasumae / src /ocr.js
NagaYu's picture
Upload 6 files
c4413a6 verified
Raw History Blame Contribute Delete
2.54 kB
// OCR via Tesseract.js, self-hosted, using the EXACT Phase 0 configuration:
// languages 'jpn+eng', OEM 1 (LSTM_ONLY), PSM 3 (AUTO).
// Tesseract.js runs the wasm in its own Web Worker, so the heavy OCR work stays
// off the main thread and the UI never freezes. All paths point at the bundled
// vendor/ copies — no third-party request is made.
// The vendored ESM build exposes a single default export (the Tesseract object);
// createWorker is a property of it.
import Tesseract from '../vendor/tesseract/tesseract.esm.min.js';
const { createWorker } = Tesseract;
const WORKER_PATH = new URL('../vendor/tesseract/worker.min.js', import.meta.url).href;
const CORE_PATH = new URL('../vendor/tesseract/tesseract-core-simd-lstm.js', import.meta.url).href;
const LANG_PATH = new URL('../vendor/tessdata/', import.meta.url).href;
// Extract word-level boxes exactly as the Phase 0 spike did, so detect.js sees
// the same shape of input it was measured against.
function wordsFromData(data) {
const words = [];
for (const b of data.blocks || []) {
for (const p of b.paragraphs || []) {
for (const l of p.lines || []) {
for (const w of l.words || []) {
if (!w.text || !w.text.trim()) continue;
const { x0, y0, x1, y1 } = w.bbox;
words.push({ text: w.text, confidence: w.confidence, bbox: { x: x0, y: y0, w: x1 - x0, h: y1 - y0 } });
}
}
}
}
return words;
}
let workerPromise = null;
export function initOcr(onProgress) {
if (workerPromise) return workerPromise;
workerPromise = (async () => {
const worker = await createWorker('jpn+eng', 1, {
workerPath: WORKER_PATH,
corePath: CORE_PATH,
langPath: LANG_PATH,
// Run the worker from its real file URL (not a blob:) so the emscripten
// core resolves its sibling .wasm relative to vendor/tesseract/.
workerBlobURL: false,
cacheMethod: 'none', // always load from the bundled vendor copy (offline-safe)
gzip: true,
logger: (m) => { if (onProgress) onProgress(m); },
});
await worker.setParameters({ tessedit_pageseg_mode: '3' }); // PSM 3 AUTO
return worker;
})();
return workerPromise;
}
// Run OCR on a drawable source (canvas at natural size) -> { words, ms }.
export async function runOcr(source, onProgress) {
const worker = await initOcr(onProgress);
const t0 = performance.now();
const { data } = await worker.recognize(source, {}, { blocks: true });
return { words: wordsFromData(data), ms: performance.now() - t0 };
}