quadscan

Troubleshooting

ModelLoadError: Could not fetch model from ...

The .onnx couldn’t be downloaded. Causes, in rough order of likelihood:

  • Offline / blocked CDN. Default model URL is jsdelivr. If your env blocks it, self-host and pass modelUrl.
  • Wrong modelUrl. Double-check the path; the error message includes the URL it tried.
  • CORS. Cross-origin model fetches need Access-Control-Allow-Origin on the response. jsdelivr sets it; self-hosting on a subdomain may not.
  • HTTP status non-200. The error includes the status: (HTTP 404).
import { ModelLoadError } from 'quadscan';
try { await Quadscan.scan(file); }
catch (err) {
  if (err instanceof ModelLoadError) {
    // err.message includes URL + status
    // err.cause is the underlying fetch error (TypeError, AbortError, ...)
  }
}

ORT wasmPaths 404s

Symptom: console errors like GET .../ort-wasm-simd-threaded.wasm 404.

onnxruntime-web defaults to walking up from its own module URL to find the .wasm runtimes, which fails under most modern bundlers. Quadscan overrides this to point at jsdelivr by default. If you set wasmPaths yourself, make sure:

  • The path ends with a trailing slash: /ort/ not /ort.
  • The files exist at that URL: ort-wasm.wasm, ort-wasm-simd-threaded.wasm, plus the matching .mjs loader scripts.
  • They’re served with Content-Type: application/wasm.

WebGPU unavailable, silent fallback to WASM

This is by design. Default executionProviders is ['webgpu', 'wasm']; ORT silently falls back. Detect it explicitly with:

const q = new Quadscan({ executionProviders: ['webgpu'] });
try { await q.initialize(); }
catch { /* WebGPU not supported, retry with ['wasm'] */ }

WebGPU is unavailable in:

  • Firefox < 141 by default,
  • Safari < 18.2 by default,
  • any browser without an unlocked GPU (some CI / headless setups).

”No document detected”

ScanResult came back { success: false, message: 'No document detected...' }. The model ran but the peak confidence on the heatmap was below minDetectionConfidence (default 0.1). Causes:

  • Tightly cropped image. The model expects ~10% padding around the document. Photograph wider, or pre-pad before scanning.
  • Document fills the entire frame. Corners are off-image; the decoder can’t peak. Same fix.
  • Very low contrast. Try the FastViT preset: new Quadscan({ model: 'fastvit' }) (slower, more accurate).

You can also lower the threshold:

await Quadscan.scan(file, { minDetectionConfidence: 0.05 });

DegenerateQuadError

The detected (or supplied) corners form a non-convex / collinear quad. Usually means the document is rotated > 45°. The reorder step can’t tell which corner is which.

Fixes:

  • Pre-rotate the input to the nearest 90°.
  • Implement a Hungarian-assignment wrapper around scan.
  • Or skip detection and provide corners manually via extract().

AbortError

Standard DOM behavior: pass an AbortSignal in ScanOptions and call controller.abort(). scan() throws DOMException('AbortError') at the next checkpoint.

const ctrl = new AbortController();
button.onclick = () => ctrl.abort();
try {
  await q.scan(file, { signal: ctrl.signal });
} catch (err) {
  if (err instanceof DOMException && err.name === 'AbortError') return;
  throw err;
}

Note: ORT inference itself is not interruptible mid-call. Abort takes effect between steps (preprocess → inference → decode → warp).