npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ryancasas/indra-ocr-wasm

v1.0.2

Published

Indra OCR running locally in browser WebAssembly workers

Readme

Indra OCR WASM

Browser WebAssembly port of Indra OCR 1.1.2. The TypeScript API converts PDFs and images locally into text, Markdown, and JSON with lines, bounding boxes, blocks, reading order, orientation, vertical-writing flags, and timings.

The Python pipeline runs asynchronously on CPython/Pyodide, NumPy, and OpenCV compiled to WASM, with ONNX Runtime Web for inference and PDFium WASM for PDFs. Users need no Python installation or OCR server.

Code and models are pinned; see third-party notices for revisions and licenses. This port's original code is Apache-2.0. Model weights and generated outputs use a separate Modified OpenRAIL-M license with commercial, competition, and redistribution restrictions. Review it before downloading or hosting weights.

Getting started

Node.js 22 or later is required to prepare the static assets.

Install the npm package and prepare assets in your app's static directory:

npm install @ryancasas/indra-ocr-wasm@latest
npx indra-ocr-assets --out-dir public
mkdir -p public/indra
cp -R node_modules/@ryancasas/indra-ocr-wasm/dist/. public/indra/

Serve public/ using the hosting requirements, then import createIndra from /indra/index.js in a browser module. TypeScript projects can import types from @ryancasas/indra-ocr-wasm. The asset command downloads runtime archives and weights separately from the npm package.

Your static directory will contain:

public/
  indra/       # Browser modules, workers, type declarations, and licenses
  runtime/     # Pyodide, NumPy, OpenCV, PDFium, ONNX Runtime, and Python sources
  models/      # Pinned model weights, metadata, manifest, and model license

When upgrading the npm package, rerun the asset command and copy the new dist/ files so the browser modules and prepared runtime stay in sync.

Run the repository demo

To run the demo and tests from source:

git clone https://github.com/RyanCasas/indra-ocr-wasm.git
cd indra-ocr-wasm
npm ci
npm run build
npm run assets
npm run dev

Open http://localhost:5173/ to convert a local file or run the tests. The asset command downloads and verifies the runtime and models; the demo server serves the prepared files.

Browser API

This example uses the npm installation layout above: /indra/ for the browser modules, /runtime/ for the runtime, and /models/ for the weights. The repository demo serves the browser modules at /dist/ instead.

<input id="document" type="file" accept="application/pdf,image/*" disabled>
<pre id="output">Loading OCR runtime…</pre>

<script type="module">
  import { createIndra } from '/indra/index.js';

  const input = document.querySelector('#document');
  const output = document.querySelector('#output');

  const indra = await createIndra({
    runtimeURL: '/runtime/',
    modelURL: '/models/',
    backend: 'auto',
    mode: 'auto',
    onProgress: message => { output.textContent = message; },
  });
  input.disabled = false;
  output.textContent = 'Choose a PDF or image.';

  input.onchange = async () => {
    const file = input.files[0];
    if (!file) return;
    input.disabled = true;
    try {
      const pages = await indra.convert(file, { maxPages: 10 });
      output.textContent = pages.map(page =>
        page.source === 'error' ? page.note : (page.markdown ?? page.text)
      ).join('\n\n');
      console.log(pages); // Includes lines, blocks, coordinates, and timings.
    } catch (error) {
      output.textContent = String(error);
    } finally {
      input.disabled = false;
    }
  };

  // When closing the view: indra.dispose();
</script>

convert accepts a File/Blob, ArrayBuffer, or Uint8Array. It preserves caller-owned buffers and returns Promise<Page[]> with zero-based indices, in page order. An unreadable file rejects the promise; a failed page returns source: 'error' with its message in note, and processing continues with the rest of the document. convertMany(inputs) returns { pages } or { error } for each file, preserving input order. Each instance serializes its conversions; dispose() cancels pending work and releases the workers.

createIndra options:

  • backend: 'auto' | 'webgpu' | 'wasm' (default: auto):
    • auto: WebGPU for non-math models, CPU/WASM for math. A GPU initialization or execution failure retries that model on WASM for this and subsequent calls.
    • webgpu: requests GPU execution for every model, including math. An unavailable adapter or model failure raises an error without whole-model CPU retry. Unsupported individual operators may still use WASM.
    • wasm: CPU-only inference. Selection and recovery appear in onProgress. See math backend selection for the automatic policy's rationale.
  • mode: 'auto' | 'ocr' | 'text' (default: auto).
  • dpi: 200, imageOcr: true, furniture: false.
  • pdfOrder is deprecated and ignored. Indra 1.1 determines reading order from layout, with geometric ordering for vertical writing, instead of preserving PDF content order.
  • math: true enables the classifier and LaTeX recognition; default: false.
  • runtimeURL, modelURL: relative or absolute static asset directories; defaults: /runtime/ and /models/. Set these explicitly for apps hosted under a subpath.
  • onProgress(message) and onLog(message, stream).
  • responseBytes: limit on total tensor bytes per ONNX response; default: 64 MiB.
  • inferenceTimeoutMs: timeout per inference; default: 120,000 ms.

convert options: maxPages (0 = all pages) and password for encrypted PDFs. Exported interfaces are defined in src/index.ts.

Hosting and distribution

Serve over HTTPS or localhost with WebAssembly SIMD and module workers. Cross-origin isolation, COOP/COEP headers, SharedArrayBuffer, service workers, JSPI, and WebGPU are optional. Use HTTP serving rather than file://.

Serve .wasm files as application/wasm; the included development server sets this MIME type. Keep index.js, runtime.worker.js, and inference.worker.js together on the application's origin. Runtime archives and model weights can use another origin with CORS configured.

Inference uses worker messages with transferable buffers and one WASM thread. WebGPU accelerates ONNX inference; Python preprocessing and PDF rendering remain on WASM. Verified model bytes stay in the inference worker for CPU recovery without another download. Apply HTTP caching to versioned assets; the library has no custom persistent cache.

Models occupy about 96 MB, or 76 MB without math; math models load only when math is enabled. The 1.0.0 runtime occupied about 116 MB uncompressed; runtime size varies with dependency versions. This port reuses Python algorithms and synchronous helpers, with asynchronous browser adaptations in python/web_*.py.

Performance comparison (1.0.0)

Measured on 2026-10-20, using Indra 1.1.2 and the pinned v1.1.0 model set. The benchmark report records samples, versions, revisions, browser-module hashes, and GPU dispatch evidence. These are historical results, not measurements of the current dependency pins. Its packageVersion is the development baseline; see release rules.

| Component | Benchmark machine / configuration | | --- | --- | | CPU | AMD Ryzen 7 8845HS, 8 cores / 16 threads, x86-64 | | GPU | Integrated AMD Radeon 780M, RDNA 3; hardware adapter (isFallbackAdapter: false) | | System memory | 64 GB DDR5-5600 | | OS | Ubuntu 26.04.1 LTS, Linux 7.0.0-38-generic | | Graphics stack | Mesa Vulkan 26.0.8-1ubuntu0.3, Vulkan loader 1.4.341.0-1 | | Browser | Playwright Chromium 156.0.8078.4, headless, with the Vulkan flags below | | Runtime | ONNX Runtime Web 1.30.0, Pyodide 0.29.3, PDFium 2.15.1, Node v24.21.0 |

Each value is median milliseconds per complete document, measured around convert() after one warmup per document, with five measured repetitions. Inputs are fetched before timing, and initialization is excluded. Cases execute sequentially; backend order rotates between suites. All four configurations use one WASM thread, the same browser flags, no cross-origin isolation, and no external requests. The fallback configuration makes navigator.gpu unavailable inside the inference worker before initialization.

Columns follow the backend options. The harness observed compute dispatches from all four math models in explicit WebGPU mode and none in automatic mode, and rejects whole-model CPU fallback in explicit mode.

| Document / options | Pages | WASM (ms) | Auto + GPU (ms) | WebGPU incl. math (ms) | Auto without GPU (ms) | | --- | ---: | ---: | ---: | ---: | ---: | | Scanned PNG | 1 | 293.8 | 268.6 | 261.7 | 319.6 | | Chinese PNG | 1 | 245.3 | 279.0 | 285.1 | 263.1 | | Arabic PNG | 1 | 191.9 | 185.9 | 186.0 | 199.4 | | Hindi PNG | 1 | 201.0 | 227.2 | 226.8 | 203.5 | | Light text on dark ground | 1 | 293.2 | 264.4 | 260.6 | 290.8 | | 12-degree tilt | 1 | 343.5 | 307.5 | 312.8 | 362.9 | | Multipage TIFF | 3 | 712.2 | 592.0 | 597.8 | 767.5 | | Digital PDF | 2 | 224.6 | 164.0 | 169.5 | 245.3 | | Hybrid PDF | 1 | 189.0 | 177.9 | 181.7 | 214.1 | | Digital PDF (mode: 'ocr') | 2 | 650.2 | 581.8 | 1306.3 | 627.2 | | Scanned PNG (math: true) | 1 | 333.9 | 266.4 | 276.1 | 307.0 | | Formula PNG (math: true) | 1 | 463.8 | 381.2 | 538.4 | 400.6 | | Digital PDF (math: true) | 2 | 310.8 | 175.7 | 183.2 | 233.3 |

Explicit GPU math was about 41% slower than automatic CPU math on the formula fixture. These whole-document measurements cover one device, not individual operators or a speed guarantee. Performance varies with the document, hardware, load, and browser. Automatic and explicit GPU modes use the same providers for non-math models; their non-math timing differences reflect run variation. The explicit-GPU forced-OCR samples ranged from 638 to 1658 ms, with the last two near 638 ms. One warmup did not establish steady timing for that case; its median should not be interpreted as an inherent cost of explicit selection.

Initialization is measured separately around createIndra() using three fresh engines per configuration (median milliseconds). Assets are served from localhost with Cache-Control: no-store: these include local loading and verification, not an Internet download.

| Initialization | WASM (ms) | Auto + GPU (ms) | WebGPU incl. math (ms) | Auto without GPU (ms) | | --- | ---: | ---: | ---: | ---: | | Default models | 3877.6 | 4039.0 | 4029.2 | 3973.1 | | With math: true | 4273.8 | 4324.1 | 4486.0 | 4214.2 |

All 312 conversions / 432 pages, including warmups, passed the parity comparisons. The harness also checks GPU dispatches, automatic CPU math selection, and recovery from an unavailable adapter.

To reproduce from a prepared checkout:

npm run build
npm run assets
npx playwright install --with-deps chromium
npm run benchmark -- --iterations 5 --startups 3 \
  --gpu-args '["--enable-unsafe-webgpu","--use-angle=vulkan","--enable-features=Vulkan","--disable-vulkan-surface"]'

The Vulkan flags were needed to expose this machine's hardware adapter in headless Chromium. The script explicitly skips GPU variants if no adapter is available. Use --suite math to measure the three math-enabled cases only, or --output <path> to keep another report. Default output is test-results/benchmark.json (ignored by Git).

Math backend selection

Automatic mode keeps mathcls.onnx, math_enc.onnx, math_dec_init.onnx, and math_dec_step.onnx on CPU/WASM. To request GPU math, use:

const indra = await createIndra({ backend: 'webgpu', math: true });

The math decoder generates tokens sequentially. Each step returns logits and attention caches to Python on the CPU, which selects the next token and sends the state into the next inference call. GPU execution therefore includes repeated CPU/GPU transfers and synchronization. This is an implementation-based explanation of the overhead; the benchmark measures whole documents and does not attribute latency to individual transfers or kernels.

The formula result supports CPU math as the automatic default on this device; other hardware or a decoder that keeps state on the GPU could change the tradeoff.

Parity verification

The 74 original tests are copied without modifications into vendor/indra/tests. npm test checks their hashes and those of the upstream code, as well as tensor transport, including 64-bit integers, scalars, response size limits, inference errors, and timeouts. It also tests automatic SemVer decisions and release guards using temporary Git repositories.

The test button runs pytest inside the production WASM worker:

  • 73 original tests pass in WASM.
  • 1 original test is skipped by its existing importorskip: it uses pikepdf to create an encrypted PDF. Its behavior is also checked using a real encrypted PDF generated natively, with missing, incorrect, and correct passwords.
  • 5 additional tests check passwords and PDFium character geometry against native results, including rotation, crop boxes, and invisible text.

In addition, golden.json contains outputs from unmodified native Indra for 24 cases / 33 pages: PNG, Chinese, Arabic, Hindi, dark backgrounds, 12-degree tilt, multipage TIFF, digital PDFs, forced OCR, hybrid documents, HTML tables, encrypted PDFs, rotation, skew, cropping, invisible text, page limits, model-based reading order, and LaTeX formulas. The generator verifies that the CJK, right-to-left, and world recognizers and all four math models execute, including the decoder with cached state.

Text, Markdown/HTML, labels, order, source, and notes are compared exactly. Bounding boxes allow a difference of 0.11 pixels and confidence scores allow 0.0002 to account for floating-point operations across backends. Timings are validated but are not compared between native CPU and WASM. Reports also record the maximum numeric differences and whether each case matches exactly.

These tests establish parity for the checked cases, not a universal guarantee of bit-for-bit identity for every document. Indra's recognition limitations still apply. The CLI, HTTP viewer, Python processes, and operating-system Hugging Face cache are outside the web API; JavaScript handles asset loading. The original cache tests use their original mocks and do not test a persistent browser cache.

Running tests

Use Contributing for setup, browser validation, native tests, and fixture regeneration. The browser runner uses the interactive test HTML, checks operation without isolation or a service worker, blocks external requests, and checks concurrent conversion, invalid-input recovery, buffer ownership, and disposal. Reports go to test-results/browser.json and CI artifacts. CI covers Chromium, Firefox, WebKit, and native upstream tests; tests/validation contains historical reports.

Project guidance

  • Changelog: npm release history, independent of Indra versions.
  • Contributing: validation, fixture regeneration, release rules, and trusted publisher setup.
  • Security: vulnerability reporting and dependency audit scope.
  • Third-party notices: source and model pins, licenses, and fixture provenance. NOTICE records copyright and attribution.

This independent project is not affiliated with or endorsed by Datalab.