@exige/web-scanner-sdk 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +288 -0
- package/dist/assets/document-worker-QjcJjl9-.js +2 -0
- package/dist/assets/document-worker-QjcJjl9-.js.map +1 -0
- package/dist/assets/ort.min-CA5AZ7mL.js +2937 -0
- package/dist/assets/ort.min-CA5AZ7mL.js.map +1 -0
- package/dist/components/ScannerCameraOverlay.d.ts +36 -0
- package/dist/components/scanner-camera-overlay.css +2 -0
- package/dist/finder-Clo7f2gN.js +1447 -0
- package/dist/finder-Clo7f2gN.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/live-overlay-geometry.d.ts +11 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +448 -0
- package/dist/react.js.map +1 -0
- package/dist/scanner-sdk.js +921 -0
- package/dist/scanner-sdk.js.map +1 -0
- package/dist/sdk/camera.d.ts +142 -0
- package/dist/sdk/detect.d.ts +3 -0
- package/dist/sdk/display-quad.d.ts +23 -0
- package/dist/sdk/document-analysis.d.ts +14 -0
- package/dist/sdk/document-geometry.d.ts +21 -0
- package/dist/sdk/field-model.d.ts +4 -0
- package/dist/sdk/finder-simulator.d.ts +17 -0
- package/dist/sdk/finder.d.ts +166 -0
- package/dist/sdk/frame-geometry.d.ts +6 -0
- package/dist/sdk/image.d.ts +18 -0
- package/dist/sdk/license-codec.d.ts +2 -0
- package/dist/sdk/license-public-key.d.ts +9 -0
- package/dist/sdk/license-runtime.d.ts +2 -0
- package/dist/sdk/license.d.ts +76 -0
- package/dist/sdk/lighting.d.ts +22 -0
- package/dist/sdk/ocr.d.ts +75 -0
- package/dist/sdk/programmatic-redaction.d.ts +102 -0
- package/dist/sdk/redact.d.ts +12 -0
- package/dist/sdk/scan.d.ts +32 -0
- package/dist/sdk/types.d.ts +92 -0
- package/package.json +74 -0
package/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Scanner SDK for Web
|
|
2
|
+
|
|
3
|
+
An npm-ready, browser-only TypeScript SDK (`@exige/web-scanner-sdk`) for document detection and perspective crop, Tesseract OCR, YOLOv8 ONNX inference, and image redaction. A responsive Vite demo imports the same public package entry and is built separately from the library (see [Two builds](#two-builds-library-and-demo)).
|
|
4
|
+
|
|
5
|
+
All image processing happens in the browser. Camera, finder, scan, OCR, redaction, vehicle detection, and torch require a signed license key (`setLicenseKey`). Issuing keys is a separate Next.js app in [`license-portal/`](license-portal/README.md), not part of the npm package.
|
|
6
|
+
|
|
7
|
+
## Public API
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
detectDocument,
|
|
12
|
+
startCamera,
|
|
13
|
+
scanDocument,
|
|
14
|
+
recognizeText,
|
|
15
|
+
detectVehicles,
|
|
16
|
+
detectPlates,
|
|
17
|
+
matchRedactionRules,
|
|
18
|
+
redactProgrammatically,
|
|
19
|
+
setLicenseKey,
|
|
20
|
+
} from '@exige/web-scanner-sdk'
|
|
21
|
+
|
|
22
|
+
const licenseKey = '<signed key from the license desk>'
|
|
23
|
+
await setLicenseKey(licenseKey)
|
|
24
|
+
const session = await startCamera(document.querySelector('video')!)
|
|
25
|
+
const cameraFrame = session.capture(2048)
|
|
26
|
+
session.stop()
|
|
27
|
+
|
|
28
|
+
const detection = await detectDocument(file)
|
|
29
|
+
if (detection.status === 'success') {
|
|
30
|
+
console.log(detection.quad?.edges, detection.quad?.topLeft)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const scan = await scanDocument(file)
|
|
34
|
+
if (scan.status !== 'success' || !scan.canvas) {
|
|
35
|
+
console.warn(scan.message) // actionable low-quality/no-document state
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ocr = await recognizeText(scan.canvas, {
|
|
40
|
+
language: 'eng',
|
|
41
|
+
minConfidence: 0.5,
|
|
42
|
+
includeWords: true,
|
|
43
|
+
includeCharacters: false,
|
|
44
|
+
onProgress: (progress) => console.log(progress),
|
|
45
|
+
})
|
|
46
|
+
console.log(ocr.words?.[0])
|
|
47
|
+
// { text, confidence: 0..1, bbox: { x, y, width, height } }
|
|
48
|
+
|
|
49
|
+
const vehicles = await detectVehicles(scan.canvas, {
|
|
50
|
+
modelUrl: '/models/yolov8n.onnx',
|
|
51
|
+
confidence: 0.4,
|
|
52
|
+
})
|
|
53
|
+
const plates = await detectPlates(scan.canvas, {
|
|
54
|
+
modelUrl: '/models/license-plate-yolov8.onnx',
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Every API accepts browser image sources (`Blob`, `HTMLImageElement`, `HTMLCanvasElement`, or `ImageBitmap`) and has exported TypeScript result types.
|
|
59
|
+
|
|
60
|
+
`minConfidence` uses the normalized `0–1` scale and defaults to `0.5`. Words and characters are filtered independently; `text` is rebuilt from retained words and overall `confidence` is their arithmetic mean. When every word is below the threshold, text is empty and confidence is `0`. OCR box extraction is opt-in: `includeWords` and `includeCharacters` default to `false` and can be enabled independently. Returned bounding boxes use pixel coordinates in the exact input canvas passed to `recognizeText`; after scanning, pass `scan.canvas` so boxes map directly to the perspective-corrected crop. `OcrResult` always includes filtered `text`, `confidence`, `language`, `imageWidth`, and `imageHeight`, with filtered `words` and/or `characters` only when requested.
|
|
61
|
+
|
|
62
|
+
Recognition merges Tesseract Auto on the original crop with Sparse Text on a
|
|
63
|
+
bounded Otsu-binarized copy. Boxes are mapped back to original coordinates,
|
|
64
|
+
overlap-deduplicated with higher confidence preferred, and spatially reordered.
|
|
65
|
+
Standalone punctuation and geometrically or visually implausible boxes are
|
|
66
|
+
rejected before the confidence filter, preventing graphic fragments from
|
|
67
|
+
becoming text while recovering isolated display words.
|
|
68
|
+
|
|
69
|
+
`shadowPass` defaults to `true`. A 12×12 grid finds connected dark paper
|
|
70
|
+
regions in any direction, including the central or offset blob commonly cast
|
|
71
|
+
by a phone and hand under an overhead light. Thin rules and isolated artwork
|
|
72
|
+
do not meet the area/printed-interior test. When accepted OCR density inside
|
|
73
|
+
the blob is low relative to the rest of the page, the SDK estimates local
|
|
74
|
+
illumination with a large box blur, divides it out, and lets Tesseract
|
|
75
|
+
re-binarize a padded Sparse Text tile (maximum 2× enlargement and 1800px
|
|
76
|
+
source dimension). Boxes map back into original crop coordinates before the
|
|
77
|
+
normal merge and rejection pipeline. Worst case is one bounded tile
|
|
78
|
+
recognition; pages without a qualifying low-yield blob pay only a 72×72
|
|
79
|
+
analysis sample. `OcrResult.diagnostics` reports whether the pass ran, its
|
|
80
|
+
strength, and the original-pixel shadow region.
|
|
81
|
+
|
|
82
|
+
## Programmatic redaction
|
|
83
|
+
|
|
84
|
+
Developers can combine string, nearby-label, normalized template rectangle, and template anchor rules. Pass prior word-level OCR to avoid a second OCR pass:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const result = await redactProgrammatically(scan.canvas, {
|
|
88
|
+
ocr,
|
|
89
|
+
rules: [
|
|
90
|
+
{ id: 'name', strategy: 'string', mode: 'case-insensitive', values: ['Taylor Morgan'] },
|
|
91
|
+
{ id: 'dob', strategy: 'label', label: 'DOB', direction: 'any' },
|
|
92
|
+
],
|
|
93
|
+
template: {
|
|
94
|
+
id: 'badge-v2',
|
|
95
|
+
fields: [
|
|
96
|
+
{ id: 'photo', strategy: 'rect', rect: { x: 0.05, y: 0.12, width: 0.28, height: 0.52 } },
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
style: { mode: 'blackout' },
|
|
100
|
+
requireAll: true,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
if (result.status === 'unmatched') {
|
|
104
|
+
console.warn(result.unmatchedRuleIds) // no canvas/blob in fail-closed mode
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Use `matchRedactionRules` to preview match geometry without changing pixels. Pixel mutation of matched boxes is internal; there is no public `redact()` for drawing or applying arbitrary rectangles from an end-user UI.
|
|
109
|
+
|
|
110
|
+
Unlabeled and coded IDs can use semantic rules:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const result = await redactProgrammatically(scan.canvas, {
|
|
114
|
+
ocr,
|
|
115
|
+
rules: [
|
|
116
|
+
{ id: 'name', strategy: 'semantic', field: 'person_name' },
|
|
117
|
+
{ id: 'dob', strategy: 'semantic', field: 'date_of_birth' },
|
|
118
|
+
{ id: 'address', strategy: 'semantic', field: 'address' },
|
|
119
|
+
],
|
|
120
|
+
models: { fields: '/models/id-fields-yolov8n.onnx' },
|
|
121
|
+
requireAll: true,
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Without a model, semantic fields use on-device AAMVA aliases, ICAO MRZ evidence, token validation, and conservative layout priors. No ID-field weights are vendored because there is no selected public model with both a sufficiently clear license and stable class contract. A supplied YOLOv8n model is lazy-loaded; its boxes select OCR words and must agree with deterministic evidence when available.
|
|
126
|
+
|
|
127
|
+
`detectDocument` isolates connected edge contours and returns an ordered `DocumentQuad` with `topLeft`, `topRight`, `bottomRight`, `bottomLeft`, plus typed `top`, `right`, `bottom`, and `left` line segments. Outcomes are `success`, `no-document`, `low-confidence`, or `low-quality`. `scanDocument` uses that same quad for a projective perspective warp and includes the quad in its result.
|
|
128
|
+
|
|
129
|
+
On touch/mobile-class devices, edge analysis defaults to a 560px working image and runs in a module Worker when supported. Perspective output is capped at 2048px by default to avoid large transient allocations. Override `maxAnalysisDimension`, `maxOutputDimension`, or `useWorker` in `ScanOptions` when needed.
|
|
130
|
+
|
|
131
|
+
## Live finder and auto-capture
|
|
132
|
+
|
|
133
|
+
Use a real video/stream or the camera-free canvas simulator:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const simulator = createFinderSimulator(canvas)
|
|
137
|
+
simulator.start()
|
|
138
|
+
|
|
139
|
+
const finder = await startFinder({
|
|
140
|
+
source: canvas,
|
|
141
|
+
autoCapture: 'defaultOn',
|
|
142
|
+
onStatus: ({ state, quad, guidance }) => {
|
|
143
|
+
renderOverlay(state, quad)
|
|
144
|
+
instruction.textContent = guidance.hint
|
|
145
|
+
},
|
|
146
|
+
onCapture: async ({ frame }) => {
|
|
147
|
+
const scan = await scanDocument(frame)
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Auto-capture modes are exactly `defaultOn`, `defaultOff`, `alwaysOn`, and `alwaysOff`. Default modes expose a mutable end-user checkbox; always modes hide it. High confidence, stable raw corners, 72% fill on the limiting axis, 1.5% edge clearance, and acceptable perspective are required before an automatic snap. Fill is the larger of the quad's width and height fill, so a tall receipt or wide boarding pass that already fills its long axis is ready instead of being told to move closer; high-aspect documents are asked for 80% of that long side. Separate, lower exit thresholds keep ready stable through ordinary hand motion. `FinderStatus.guidance` exposes one end-user hint—place the document, move closer, move farther away, hold the camera straight, or hold still—plus its typed reason, limiting-axis fill ratio, and the per-axis `fill` breakdown. Manual capture is always available through `finder.capture()`.
|
|
153
|
+
|
|
154
|
+
### Optional ML corner detection
|
|
155
|
+
|
|
156
|
+
`detectDocument`, `scanDocument`, and `startFinder(...).detection` accept `detector: "classical" | "ml" | "auto"`. Classical remains the zero-download default. ML uses Exige's immutable, CORS-enabled DocAligner LCNet100 CDN file by default and accepts a custom `modelUrl`; Auto reports failures and falls back to classical.
|
|
157
|
+
|
|
158
|
+
The worker lazy-loads the existing `onnxruntime-web` dependency, tries WebGPU then WASM, reuses sessions, and caches model bytes in IndexedDB plus the browser cache. The CDN model has neighboring Apache-2.0 attribution/checksum files and is copied only into the demo build; npm package output does not include weights. See the [VitePress ML detector guide](site/guide/ml-document-detector.md).
|
|
159
|
+
|
|
160
|
+
### Full-screen React camera
|
|
161
|
+
|
|
162
|
+
The demo uses the reusable sibling export:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import { ScannerCameraOverlay } from '@exige/web-scanner-sdk/react'
|
|
166
|
+
import '@exige/web-scanner-sdk/camera-overlay.css'
|
|
167
|
+
|
|
168
|
+
<ScannerCameraOverlay
|
|
169
|
+
open={cameraOpen}
|
|
170
|
+
autoCapture="defaultOn"
|
|
171
|
+
onClose={() => setCameraOpen(false)}
|
|
172
|
+
onCapture={({ frame, method, settings, displayedQuad }) => {
|
|
173
|
+
console.log(method, settings.width, settings.height)
|
|
174
|
+
return processFullResolutionFrame(frame, displayedQuad)
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The component covers the viewport, respects safe areas, shows finder/permission/error states, and keeps the existing auto-capture policies. It requests an ideal 4K rear-camera stream, exposes actual track settings, continuously mirrors the last non-black frame, and synchronously copies the unscaled live frame before shutter/flash/track teardown. Capture never calls `ImageCapture.takePhoto()`, which returns black stills on WebKit: the live copy is used when it has visible pixels, the mirrored frame otherwise, and a genuinely blank capture fails loudly instead of exporting black pixels. A live diagnostics panel reports engine/browser, video and track state, luminance, and the chosen capture path, and **Copy report** pastes a shutter-time dump for support. Capture results include the displayed quad and its bounded-analysis dimensions; use `mapDocumentCornersToFrame()` plus `cropDocument()` so the final crop matches what was on screen. The framework-free imperative APIs remain available from the root package.
|
|
180
|
+
|
|
181
|
+
## Run the demo
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
npm install
|
|
185
|
+
npm run dev
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Open [http://localhost:43127](http://localhost:43127). Upload a PNG/JPEG/WebP or use a mobile camera, then detect and crop, run OCR, inspect detection suggestions, apply developer redaction rules, and export the result. There is no draw-your-own box tool.
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
npm test
|
|
192
|
+
npm run typecheck
|
|
193
|
+
npm run build
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Two builds: library and demo
|
|
197
|
+
|
|
198
|
+
The repository produces two independent bundles from the same source. The demo imports `src/index.ts`, the exact public entry published as `@exige/web-scanner-sdk`, so the deployed app exercises the developer-facing API.
|
|
199
|
+
|
|
200
|
+
| Command | Config | Output | Purpose |
|
|
201
|
+
|---|---|---|---|
|
|
202
|
+
| `npm run build:lib` | `vite.config.ts` | `dist/` | Vite library mode: ESM bundle `dist/scanner-sdk.js` plus `.d.ts` declarations. `tesseract.js` and `onnxruntime-web` stay external. This is what `files`, `exports`, and npm publish use. |
|
|
203
|
+
| `npm run build:demo` | `vite.config.app.ts` | `dist-demo/` | Standard Vite application build: real `index.html`, hashed JS/CSS assets, and `public/` files. This is what static hosting serves. |
|
|
204
|
+
| `npm run build` | both | `dist/` and `dist-demo/` | Convenience wrapper that runs the library build then the demo build. |
|
|
205
|
+
|
|
206
|
+
The library build has no `index.html`, so serving `dist/` from a web host makes the browser download the package entry instead of rendering a page. Deploy `dist-demo/`.
|
|
207
|
+
|
|
208
|
+
Serve the production demo locally to confirm it renders:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
npm run build:demo
|
|
212
|
+
npm run preview
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Open [http://localhost:43129](http://localhost:43129) and confirm an HTML page loads with the scan, finder, OCR, and redaction UI.
|
|
216
|
+
|
|
217
|
+
## Vercel deployment
|
|
218
|
+
|
|
219
|
+
`vercel.json` pins the demo build so the deployment cannot fall back to the library output:
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"framework": "vite",
|
|
224
|
+
"buildCommand": "npm run build:demo",
|
|
225
|
+
"outputDirectory": "dist-demo",
|
|
226
|
+
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Matching Vercel project settings (Settings → Build and Deployment):
|
|
231
|
+
|
|
232
|
+
| Setting | Value |
|
|
233
|
+
|---|---|
|
|
234
|
+
| Framework preset | Vite |
|
|
235
|
+
| Root directory | `.` (repository root) |
|
|
236
|
+
| Build command | `npm run build:demo` |
|
|
237
|
+
| Output directory | `dist-demo` |
|
|
238
|
+
| Install command | `npm install` (default) |
|
|
239
|
+
|
|
240
|
+
Leave the project-level build command and output directory overrides off so `vercel.json` applies; if an override is already set in the dashboard, set it to the values above. A `vercel-build` script is also defined as a fallback for zero-config builds. No environment variables or secrets are needed.
|
|
241
|
+
|
|
242
|
+
## Run the public docs
|
|
243
|
+
|
|
244
|
+
The VitePress site includes integration guides and a TypeDoc-generated API reference. API pages are regenerated from `src/index.ts` before development and production builds, so published signatures track the package exports.
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
npm run docs:dev
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Open [http://localhost:43261](http://localhost:43261). To verify the production site:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
npm run docs:build
|
|
254
|
+
npm run docs:preview
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The docs cover the camera/finder-to-export flow, auto-capture policies, programmatic redaction, and mobile/Safari integration. The generated API reference reflects the current public surface and does not include a draw-boxes `redact()` function.
|
|
258
|
+
|
|
259
|
+
## Publish to npm
|
|
260
|
+
|
|
261
|
+
`@exige/web-scanner-sdk` publishes from Origin CI at `.depot/workflows/publish.yml` when a `vX.Y.Z` tag is pushed. A matching GitHub Actions copy lives at `.github/workflows/publish.yml`; Origin runs the Depot path. The job does not run on ordinary pushes to `main`. It installs with `npm ci`, checks that `package.json` `"version"` matches the tag (`v0.1.0` ↔ `"0.1.0"`), runs `npm test`, builds the library with `npm run build:lib`, then `npm publish --access public`. Auth uses Depot secret `NPM_TOKEN` through `actions/setup-node` (`registry-url: https://registry.npmjs.org`) and `NODE_AUTH_TOKEN`. Do not put a token in `.npmrc` in git, and do not print the secret.
|
|
262
|
+
|
|
263
|
+
Do not auto-bump the version in CI. To release:
|
|
264
|
+
|
|
265
|
+
1. Set `"version"` in `package.json` to the new semver (for example `0.1.0`).
|
|
266
|
+
2. Commit that change on `main` (and any other files the release needs).
|
|
267
|
+
3. Tag the commit and push the tag:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
git checkout main
|
|
271
|
+
git pull
|
|
272
|
+
git tag v0.1.0
|
|
273
|
+
git push origin v0.1.0
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Replace `0.1.0` with the version you just committed. A `workflow_dispatch` run is available as a fallback; type the same `vX.Y.Z` tag in the confirmation input. The workflow still refuses to publish if that value does not match `package.json`.
|
|
277
|
+
|
|
278
|
+
## YOLO models
|
|
279
|
+
|
|
280
|
+
ONNX weights are intentionally not vendored: model licenses, class layouts, and file sizes differ by use case. Pass a URL to a YOLOv8 detection model through `modelUrl`; the SDK executes the common `[1, classes + 4, anchors]` output layout in ONNX Runtime Web.
|
|
281
|
+
|
|
282
|
+
Without a model URL, `detectVehicles` and `detectPlates` return clearly marked, low-confidence `source: "heuristic"` suggestions. This keeps the demo functional without silently presenting mock results as model inference. Set `allowFallback: false` to return no fallback detections or surface model errors.
|
|
283
|
+
|
|
284
|
+
Tesseract downloads and caches its language assets on first OCR use.
|
|
285
|
+
|
|
286
|
+
## Browser support and privacy
|
|
287
|
+
|
|
288
|
+
The SDK needs Canvas, `createImageBitmap`, Web Workers, and WebAssembly. Current Chromium, Firefox, and Safari releases support these APIs. Images remain local unless the host application explicitly sends them elsewhere.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t,n){return(t.x-e.x)*(n.y-e.y)-(t.y-e.y)*(n.x-e.x)}function t(t){let n=[...t].sort((e,t)=>e.x-t.x||e.y-t.y);if(n.length<=3)return n;let r=[];for(let t of n){for(;r.length>=2&&e(r.at(-2),r.at(-1),t)<=0;)r.pop();r.push(t)}let i=[];for(let t=n.length-1;t>=0;--t){let r=n[t];for(;i.length>=2&&e(i.at(-2),i.at(-1),r)<=0;)i.pop();i.push(r)}return r.pop(),i.pop(),r.concat(i)}function n(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n],i=e[(n+1)%e.length];t+=r.x*i.y-i.x*r.y}return Math.abs(t)/2}function r(e){if(e.length<4)return;let t=e.reduce((e,t)=>t.x+t.y<e.x+e.y?t:e),n=e.reduce((e,t)=>t.x+t.y>e.x+e.y?t:e),r=e.reduce((e,t)=>t.x-t.y>e.x-e.y?t:e),i=e.reduce((e,t)=>t.x-t.y<e.x-e.y?t:e);if((/* @__PURE__ */ new Set([t,r,n,i])).size===4)return{topLeft:t,topRight:r,bottomRight:n,bottomLeft:i}}function i(e){return Math.max(0,Math.min(1,e))}function a(e,t){return Math.hypot(e.x-t.x,e.y-t.y)}function o(e,t,n){let r=e.x-t.x,i=e.y-t.y,a=n.x-t.x,o=n.y-t.y,s=Math.max(1e-6,Math.hypot(r,i)*Math.hypot(a,o));return Math.acos(Math.max(-1,Math.min(1,(r*a+i*o)/s)))*180/Math.PI}function s(e,t,n,r){let i=Math.atan2(t.y-e.y,t.x-e.x),a=Math.atan2(r.y-n.y,r.x-n.x),o=Math.abs(i-a)%Math.PI;return o>Math.PI/2&&(o=Math.PI-o),o}function c(e){let{topLeft:t,topRight:n,bottomRight:r,bottomLeft:i}=e,a=n.x-r.x,o=i.x-r.x,s=n.y-r.y,c=i.y-r.y,l=t.x-n.x+r.x-i.x,u=t.y-n.y+r.y-i.y,d=a*c-o*s,f=Math.abs(d)<1e-6?0:(l*c-o*u)/d,p=Math.abs(d)<1e-6?0:(a*u-l*s)/d;return{a:n.x-t.x+f*n.x,b:i.x-t.x+p*i.x,c:t.x,d:n.y-t.y+f*n.y,e:i.y-t.y+p*i.y,f:t.y,g:f,h:p}}function l(e,t){let{a:n,b:r,c:i,d:a,e:o,f:s,g:c,h:l}=t,u=n-e.x*c,d=r-e.x*l,f=a-e.y*c,p=o-e.y*l,m=e.x-i,h=e.y-s,g=u*p-d*f;if(!(Math.abs(g)<1e-8))return{x:(m*p-d*h)/g,y:(u*h-m*f)/g}}function u(e,t){if(!t.length)return 0;let n=c(e),r=0,i=0,a=Math.max(1,Math.floor(t.length/80));for(let e=0;e<t.length;e+=a){let a=l(t[e],n);if(!a)return 0;let o=Math.min(Math.abs(a.x),Math.abs(1-a.x),Math.abs(a.y),Math.abs(1-a.y))+Math.max(0,-a.x,a.x-1,-a.y,a.y-1)*2;r+=o*o,i+=1}let o=Math.sqrt(r/Math.max(1,i));return Math.exp(-o/.08)}function d(t,r,c,l=[t.topLeft,t.topRight,t.bottomRight,t.bottomLeft]){let d=[t.topLeft,t.topRight,t.bottomRight,t.bottomLeft],f=Math.max(1,Math.min(r,c)),p=Math.max(0,...d.map(e=>Math.max(-e.x,-e.y,e.x-r,e.y-c)/f)),m=d.map((t,n)=>e(t,d[(n+1)%4],d[(n+2)%4])),h=m.every(e=>e>0)||m.every(e=>e<0),g=n(d)/Math.max(1,r*c);if(!h||g<.04||g>.995||p>.12)return{total:0,areaRatio:g,angleScore:0,oppositeAngleScore:0,parallelScore:0,homographyScore:0,aspectBoost:0};let _=d.map((e,t)=>o(d[(t+3)%4],e,d[(t+1)%4])),v=d.map((e,t)=>a(e,d[(t+1)%4])),y=Math.max(v[0],v[2])/Math.max(1,Math.min(v[0],v[2])),b=Math.max(v[1],v[3])/Math.max(1,Math.min(v[1],v[3]));if(_.some(e=>e<18||e>162)||y>4||b>4)return{total:0,areaRatio:g,angleScore:0,oppositeAngleScore:0,parallelScore:0,homographyScore:0,aspectBoost:0};let x=_.reduce((e,t)=>e+Math.exp(-Math.abs(t-90)/48),0)/4,S=(Math.exp(-Math.abs(_[0]+_[2]-180)/55)+Math.exp(-Math.abs(_[1]+_[3]-180)/55))/2,C=(Math.exp(-s(d[0],d[1],d[3],d[2])/.62)+Math.exp(-s(d[0],d[3],d[1],d[2])/.62))/2,w=u(t,l),T=i((g-.04)/.5)*(g>.94?i((.995-g)/.055):1),E=Math.exp(-Math.max(0,Math.max(y,b)-1.6)/1.5),D=p?Math.exp(-p/.07):1,O=Math.max(a(d[0],d[1]),a(d[3],d[2])),k=Math.max(a(d[0],d[3]),a(d[1],d[2])),A=Math.max(O,k)/Math.max(1,Math.min(O,k)),j=Math.exp(-Math.abs(A-1.58)/.18),M=Math.exp(-Math.abs(A-1.29)/.18),N=A>=2?i((A-2)/2):0,P=Math.max(j,M,N)*.08;return{total:i(T*.18+x*.2+S*.14+C*.18+w*.3+P)*(.72+E*.28)*D,areaRatio:g,angleScore:x,oppositeAngleScore:S,parallelScore:C,homographyScore:w,aspectBoost:P}}function f(e,t,n){let r=new Uint8Array(e.length);for(let i=1;i<n-1;i+=1)for(let n=1;n<t-1;n+=1){let a=i*t+n;if(e[a])for(let e=-1;e<=1;e+=1)for(let n=-1;n<=1;n+=1)r[a+e*t+n]=1}return r}function p(e,t){let n=Math.cos(e.theta),r=Math.sin(e.theta),i=Math.cos(t.theta),a=Math.sin(t.theta),o=n*a-i*r;if(!(Math.abs(o)<.12))return{x:(e.rho*a-t.rho*r)/o,y:(n*t.rho-i*e.rho)/o}}function m(e,t){let n=Math.abs(e-t)%Math.PI;return n>Math.PI/2&&(n=Math.PI-n),n}function h(e,t,n){let r=Math.ceil(Math.hypot(t,n)),i=r*2+1,a=new Uint16Array(72*i),o=Array.from({length:72},(e,t)=>Math.cos(t*Math.PI/72)),s=Array.from({length:72},(e,t)=>Math.sin(t*Math.PI/72)),c=[],l=e.reduce((e,t)=>e+t,0),u=Math.max(1,Math.ceil(l/7e3)),d=0;for(let t=0;t<e.length;t+=1)e[t]&&(d++%u||c.push(t));for(let e of c){let n=e%t,c=Math.floor(e/t);for(let e=0;e<72;e+=1){let t=Math.round(n*o[e]+c*s[e])+r,l=e*i+t;a[l]<65535&&(a[l]+=1)}}let f=[],p=Math.max(12,Math.round(Math.min(t,n)*.08/u));for(let e=0;e<72;e+=1)for(let t=0;t<i;t+=1){let n=a[e*i+t];n<p||f.push({theta:e*Math.PI/72,rho:t-r,votes:n})}f.sort((e,t)=>t.votes-e.votes);let h=[];for(let e of f)if(!h.some(r=>m(r.theta,e.theta)<Math.PI/36&&Math.abs(r.rho-e.rho)<Math.min(t,n)*.035)&&(h.push(e),h.length===12))break;return h}function g(e,t,n){let a=h(e,t,n),o,s=0;for(let e=0;e<a.length;e+=1)for(let c=e+1;c<a.length;c+=1)if(!(m(a[e].theta,a[c].theta)>.38)&&!(Math.abs(a[e].rho-a[c].rho)<Math.min(t,n)*.16)){for(let l=0;l<a.length;l+=1)if(l!==e&&l!==c&&!(m(a[e].theta,a[l].theta)<.52))for(let u=l+1;u<a.length;u+=1){if(u===e||u===c||m(a[l].theta,a[u].theta)>.38||Math.abs(a[l].rho-a[u].rho)<Math.min(t,n)*.16)continue;let f=[p(a[e],a[l]),p(a[e],a[u]),p(a[c],a[l]),p(a[c],a[u])];if(f.some(e=>!e))continue;let h=f;if(h.some(e=>e.x<0||e.y<0||e.x>t-1||e.y>n-1))continue;let g=r(h);if(!g)continue;let _=d(g,t,n);if(!_.total)continue;let v=i((a[e].votes+a[c].votes+a[l].votes+a[u].votes)/Math.max(1,Math.min(t,n)*1.2)),y=i(_.total*.9+v*.1),b=y*Math.sqrt(_.areaRatio);b>s&&(s=b,o={corners:g,confidence:y,areaRatio:_.areaRatio})}}return o}function _(e,i,a,o={}){if(e.length!==i*a)throw Error(`Edge map dimensions do not match.`);let s=f(e,i,a),c=new Uint8Array(s.length),l=new Int32Array(s.length),u,p=0,m=Math.max(24,Math.round(i*a*.0015));for(let e=0;e<s.length;e+=1){if(!s[e]||c[e])continue;let o=0,f=0;l[f++]=e,c[e]=1;let h=[],g=0;for(;o<f;){let e=l[o++],t=e%i,n=Math.floor(e/i);if(t===0||n===0||t===i-1||n===a-1||!s[e-1]||!s[e+1]||!s[e-i]||!s[e+i]){g+=1;let e={x:t,y:n};if(h.length<12e3)h.push(e);else{let t=(g*2654435761>>>0)%g;t<h.length&&(h[t]=e)}}for(let r=-1;r<=1;r+=1)for(let o=-1;o<=1;o+=1){if(!o&&!r||t+o<0||t+o>=i||n+r<0||n+r>=a)continue;let u=e+r*i+o;s[u]&&!c[u]&&(c[u]=1,l[f++]=u)}}if(f<m||f>i*a*.65||h.length<4)continue;let _=t(h),v=r(_);if(!v)continue;let y=n([v.topLeft,v.topRight,v.bottomRight,v.bottomLeft]),b=n(_);if(y<=0)continue;let x=d(v,i,a,_);if(!x.total)continue;let S=Math.min(1,b/y),C=Math.min(1,f/Math.max(1,Math.sqrt(y)*10)),w=Math.min(.98,x.total*.82+S*.08+C*.1),T=w*Math.sqrt(x.areaRatio);T>p&&(p=T,u={corners:v,confidence:w,areaRatio:x.areaRatio})}return u??(o.lineFallback?g(e,i,a):void 0)}function v(e,t){let n=/* @__PURE__ */ new Uint32Array(511),r=0;for(let t of e)t&&(n[Math.min(510,t)]+=1,r+=1);let i=r*t,a=0;for(let e=0;e<n.length;e+=1)if(a+=n[e],a>=i)return e;return 72}function y(e,t,n){let r=-e[t-n-1]+e[t-n+1]-2*e[t-1]+2*e[t+1]-e[t+n-1]+e[t+n+1],i=-e[t-n-1]-2*e[t-n]-e[t-n+1]+e[t+n-1]+2*e[t+n]+e[t+n+1];return(Math.abs(r)+Math.abs(i))/2}function b(e,t,n,r,i){let a=Math.floor(r),o=Math.floor(i),s=r-a,c=i-o,l=Math.max(0,Math.min(t-1,a)),u=Math.max(0,Math.min(t-1,a+1)),d=Math.max(0,Math.min(n-1,o)),f=Math.max(0,Math.min(n-1,o+1)),p=e[d*t+l]*(1-s)+e[d*t+u]*s,m=e[f*t+l]*(1-s)+e[f*t+u]*s;return p*(1-c)+m*c}function x(e,t,n,r,i=[],a=!1){let o=new Uint8Array(t*n),s=new Int16Array(t*n),c=new Int16Array(t*n);for(let t=0;t<o.length;t+=1){let n=t*4;o[t]=Math.round(e[n]*.299+e[n+1]*.587+e[n+2]*.114),s[t]=e[n]-e[n+1],c[t]=e[n+2]-e[n+1]}let l=new Uint8Array(o),u=Math.max(24,Math.round(Math.min(t,n)/12)),d=Math.ceil(t/u),f=Math.ceil(n/u),p=new Float32Array(d*f),m=new Float32Array(d*f);for(let e=0;e<f;e+=1)for(let r=0;r<d;r+=1){let i=r*u,a=e*u,s=Math.min(t,i+u),c=Math.min(n,a+u),l=255,f=0,h=0,g=0;for(let e=a;e<c;e+=1)for(let n=i;n<s;n+=1){let r=o[e*t+n];l=Math.min(l,r),f=Math.max(f,r),h+=r,g+=1}let _=f-l,v=e*d+r;p[v]=h/Math.max(1,g),m[v]=_<10?1:Math.min(3,Math.max(1,72/_))}for(let e=0;e<n;e+=1){let n=e/u-.5;for(let r=0;r<t;r+=1){let i=r/u-.5,a=e*t+r,s=b(p,d,f,i,n),c=b(m,d,f,i,n),h=s+(o[a]-s)*c;l[a]=Math.max(0,Math.min(255,Math.round(o[a]*.58+h*.42)))}}let h=new Uint16Array(l.length),g=0,x=0;for(let e=1;e<n-1;e+=1)for(let n=1;n<t-1;n+=1){let r=e*t+n,i=y(l,r,t),a=Math.max(y(s,r,t),y(c,r,t)),o=Math.min(510,Math.round(i+a*.38));h[r]=o,g+=o,x+=1}let S=g/Math.max(1,x);if(S<3)return{sharpness:S};let C=r??Math.max(26,v(h,.82)),w=new Uint8Array(o.length),T=new Uint8Array(o.length);for(let e=0;e<h.length;e+=1)h[e]>=C?w[e]=1:h[e]>=C*.55&&(T[e]=1);let E=new Uint8Array(w);for(let e=0;e<2;e+=1){let e=new Uint8Array(E);for(let r=1;r<n-1;r+=1)for(let n=1;n<t-1;n+=1){let i=r*t+n;if(T[i]&&!E[i]){for(let n=-1;n<=1&&!e[i];n+=1)for(let r=-1;r<=1;r+=1)if(E[i+n*t+r]){e[i]=1;break}}}E.set(e)}let D=new Uint8Array(E);for(let e of i)if(e.length===D.length)for(let t=0;t<D.length;t+=1)D[t]||=e[t];return{sharpness:S,edgeMap:E,candidate:_(D,t,n,{lineFallback:a})}}async function S(e,t,n){if(e===`classical`)return{result:n(),detector:`classical`};try{let r=await t();if(r!==void 0)return{result:r,detector:`ml`};if(e===`ml`)throw Error(`ML detector returned no geometrically valid corners.`);return{result:n(),detector:`classical`,fallbackError:`ML detector returned no geometrically valid corners.`}}catch(t){if(e===`ml`)throw t;return{result:n(),detector:`classical`,fallbackError:t instanceof Error?t.message:`Document model failed.`}}}function C(e,t,n,r=256){let i=r*r,a=new Float32Array(i*3);for(let o=0;o<r;o+=1){let s=Math.min(n-1,Math.round((o+.5)*n/r-.5));for(let n=0;n<r;n+=1){let c=Math.min(t-1,Math.round((n+.5)*t/r-.5)),l=(s*t+c)*4,u=o*r+n;a[u]=e[l]/255,a[i+u]=e[l+1]/255,a[i*2+u]=e[l+2]/255}}return{data:a,dimensions:[1,3,r,r]}}function w(e,t,n,r,i){let a=new Uint8Array(n*r),o=new Int32Array(n*r),s=0,c,l=0;for(let u=0;u<n*r;u+=1){if(a[u]||e[t+u]<i)continue;let d=0,f=0,p=0,m=0,h=0,g=0;for(o[f++]=u,a[u]=1;d<f;){let s=o[d++],c=s%n,l=Math.floor(s/n),u=Math.max(0,e[t+s]);p+=(c+.5)*u,m+=(l+.5)*u,h+=u,g=Math.max(g,u);let _=[s-1,s+1,s-n,s+n];for(let s of _){if(s<0||s>=n*r||a[s]||e[t+s]<i)continue;let l=s%n;Math.abs(l-c)>1||(a[s]=1,o[f++]=s)}}f>s&&h>0&&(s=f,l=g,c={x:p/h,y:m/h})}return c?{point:c,peak:l}:void 0}function T(e,t,n,i,a=.3){if(t.length!==4||Number(t[0])!==1||Number(t[1])!==4)throw Error(`Expected document heatmaps shaped [1,4,H,W], got [${t.join(`,`)}].`);let o=Number(t[2]),s=Number(t[3]),c=s*o;if(e.length<c*4||!s||!o)throw Error(`Document heatmap tensor is incomplete.`);let l=[],u=[];for(let t=0;t<4;t+=1){let r=w(e,t*c,s,o,a);if(!r)return{cornerConfidences:u};l.push({x:Math.max(0,Math.min(n-1,r.point.x/s*n)),y:Math.max(0,Math.min(i-1,r.point.y/o*i))}),u.push(r.peak)}let f=r(l);if(!f)return{cornerConfidences:u};let p=d(f,n,i);if(p.total<.35)return{cornerConfidences:u};let m=u.reduce((e,t)=>e+t,0)/4;return{candidate:{corners:f,areaRatio:p.areaRatio,confidence:Math.min(.99,m*.55+p.total*.45)},cornerConfidences:u}}const E=globalThis,D=/* @__PURE__ */ new Map,O=/* @__PURE__ */ new Map,k=`model-bytes`;function A(){return new Promise((e,t)=>{let n=indexedDB.open(`exige-scanner-sdk-models`,1);n.onupgradeneeded=()=>{n.result.objectStoreNames.contains(k)||n.result.createObjectStore(k)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)})}async function j(e){if(typeof indexedDB>`u`)return;let t=await A();try{return await new Promise((n,r)=>{let i=t.transaction(k).objectStore(k).get(e);i.onsuccess=()=>n(i.result instanceof ArrayBuffer?i.result:void 0),i.onerror=()=>r(i.error)})}finally{t.close()}}async function M(e,t){if(typeof indexedDB>`u`)return;let n=await A();try{await new Promise((r,i)=>{let a=n.transaction(k,`readwrite`);a.objectStore(k).put(t,e),a.oncomplete=()=>r(),a.onerror=()=>i(a.error)})}finally{n.close()}}async function N(e,t){let n=D.get(e);return n||(n=(async()=>{if(t)try{let t=await j(e);if(t)return t}catch{}let n=await fetch(e,{cache:t?`force-cache`:`no-store`});if(!n.ok)throw Error(`Document model request failed with HTTP ${n.status}.`);let r=await n.arrayBuffer();if(t)try{await M(e,r)}catch{}return r})(),D.set(e,n)),n}async function P(e,t,n){let r=`${e}|${n??``}`,i=O.get(r);return i||(i=(async()=>{let[r,i]=await Promise.all([import(`./ort.min-CA5AZ7mL.js`),N(e,t)]);if(n&&(r.env.wasm.wasmPaths=n),typeof navigator<`u`&&`gpu`in navigator)try{return{session:await r.InferenceSession.create(i,{executionProviders:[`webgpu`],graphOptimizationLevel:`all`}),ort:r,provider:`webgpu`}}catch{}return{session:await r.InferenceSession.create(i,{executionProviders:[`wasm`],graphOptimizationLevel:`all`}),ort:r,provider:`wasm`}})(),O.set(r,i)),i}async function F(e){if(!e.modelUrl)throw Error(`A modelUrl is required when detector is "ml".`);let t=C(new Uint8ClampedArray(e.pixels),e.width,e.height),{session:n,ort:r,provider:i}=await P(e.modelUrl,e.cacheModel!==!1,e.ortWasmPaths),a=performance.now(),o=(await n.run({[n.inputNames[0]]:new r.Tensor(`float32`,t.data,t.dimensions)}))[n.outputNames[0]];if(!o)throw Error(`Document model returned no output tensor.`);return{sharpness:20,candidate:T(o.data,o.dims.map(Number),e.width,e.height,e.heatmapThreshold??.3).candidate,detector:`ml`,modelStatus:`ready`,modelProvider:i,inferenceMs:performance.now()-a}}function I(e){return{...x(new Uint8ClampedArray(e.pixels),e.width,e.height,e.edgeThreshold,e.priorEdgeMaps,e.lineFallback),detector:`classical`,modelStatus:`not-requested`}}async function L(e){let t=e.detector??`classical`;try{let n=await S(t,async()=>{let t=await F(e);return t.candidate?t:void 0},()=>I(e));return n.detector===`ml`?n.result:n.fallbackError?{...n.result,modelStatus:`fallback`,modelError:n.fallbackError}:n.result}catch(e){return{sharpness:20,detector:`ml`,modelStatus:`failed`,modelError:e instanceof Error?e.message:`Document model failed.`}}}let R=Promise.resolve();E.onmessage=e=>{let t=e.data;R=R.then(async()=>{try{E.postMessage({id:t.id,result:await L(t)})}catch(e){E.postMessage({id:t.id,error:e instanceof Error?e.message:`Document analysis failed.`})}})};
|
|
2
|
+
//# sourceMappingURL=document-worker-QjcJjl9-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-worker-QjcJjl9-.js","names":[],"sources":["../../src/sdk/document-geometry.ts","../../src/sdk/document-analysis.ts","../../src/sdk/document-ml.ts","../../src/sdk/document-worker.ts"],"sourcesContent":["import type { DocumentCorners, Point } from './types'\n\nexport interface QuadCandidate {\n corners: DocumentCorners\n confidence: number\n areaRatio: number\n}\n\nexport interface PerspectiveQuadScore {\n total: number\n areaRatio: number\n angleScore: number\n oppositeAngleScore: number\n parallelScore: number\n homographyScore: number\n aspectBoost: number\n}\n\nfunction cross(origin: Point, a: Point, b: Point) {\n return (a.x - origin.x) * (b.y - origin.y) - (a.y - origin.y) * (b.x - origin.x)\n}\n\nfunction convexHull(points: Point[]) {\n const sorted = [...points].sort((a, b) => a.x - b.x || a.y - b.y)\n if (sorted.length <= 3) return sorted\n const lower: Point[] = []\n for (const point of sorted) {\n while (lower.length >= 2 && cross(lower.at(-2)!, lower.at(-1)!, point) <= 0) lower.pop()\n lower.push(point)\n }\n const upper: Point[] = []\n for (let index = sorted.length - 1; index >= 0; index -= 1) {\n const point = sorted[index]\n while (upper.length >= 2 && cross(upper.at(-2)!, upper.at(-1)!, point) <= 0) upper.pop()\n upper.push(point)\n }\n lower.pop()\n upper.pop()\n return lower.concat(upper)\n}\n\nexport function polygonArea(points: Point[]) {\n let sum = 0\n for (let index = 0; index < points.length; index += 1) {\n const current = points[index]\n const next = points[(index + 1) % points.length]\n sum += current.x * next.y - next.x * current.y\n }\n return Math.abs(sum) / 2\n}\n\nexport function orderCorners(points: Point[]): DocumentCorners | undefined {\n if (points.length < 4) return undefined\n const topLeft = points.reduce((best, point) => point.x + point.y < best.x + best.y ? point : best)\n const bottomRight = points.reduce((best, point) => point.x + point.y > best.x + best.y ? point : best)\n const topRight = points.reduce((best, point) => point.x - point.y > best.x - best.y ? point : best)\n const bottomLeft = points.reduce((best, point) => point.x - point.y < best.x - best.y ? point : best)\n const unique = new Set([topLeft, topRight, bottomRight, bottomLeft])\n if (unique.size !== 4) return undefined\n return { topLeft, topRight, bottomRight, bottomLeft }\n}\n\nfunction clamp01(value: number) {\n return Math.max(0, Math.min(1, value))\n}\n\nfunction distance(a: Point, b: Point) {\n return Math.hypot(a.x - b.x, a.y - b.y)\n}\n\nfunction interiorAngle(previous: Point, vertex: Point, next: Point) {\n const ax = previous.x - vertex.x\n const ay = previous.y - vertex.y\n const bx = next.x - vertex.x\n const by = next.y - vertex.y\n const denominator = Math.max(1e-6, Math.hypot(ax, ay) * Math.hypot(bx, by))\n return Math.acos(Math.max(-1, Math.min(1, (ax * bx + ay * by) / denominator))) * 180 / Math.PI\n}\n\nfunction directionDifference(a: Point, b: Point, c: Point, d: Point) {\n const first = Math.atan2(b.y - a.y, b.x - a.x)\n const second = Math.atan2(d.y - c.y, d.x - c.x)\n let difference = Math.abs(first - second) % Math.PI\n if (difference > Math.PI / 2) difference = Math.PI - difference\n return difference\n}\n\nfunction unitToQuadCoefficients(corners: DocumentCorners) {\n const { topLeft: tl, topRight: tr, bottomRight: br, bottomLeft: bl } = corners\n const dx1 = tr.x - br.x\n const dx2 = bl.x - br.x\n const dy1 = tr.y - br.y\n const dy2 = bl.y - br.y\n const sx = tl.x - tr.x + br.x - bl.x\n const sy = tl.y - tr.y + br.y - bl.y\n const denominator = dx1 * dy2 - dx2 * dy1\n const g = Math.abs(denominator) < 1e-6 ? 0 : (sx * dy2 - dx2 * sy) / denominator\n const h = Math.abs(denominator) < 1e-6 ? 0 : (dx1 * sy - sx * dy1) / denominator\n return {\n a: tr.x - tl.x + g * tr.x,\n b: bl.x - tl.x + h * bl.x,\n c: tl.x,\n d: tr.y - tl.y + g * tr.y,\n e: bl.y - tl.y + h * bl.y,\n f: tl.y,\n g,\n h,\n }\n}\n\nfunction quadToUnit(point: Point, coefficients: ReturnType<typeof unitToQuadCoefficients>) {\n const { a, b, c, d, e, f, g, h } = coefficients\n const aa = a - point.x * g\n const bb = b - point.x * h\n const dd = d - point.y * g\n const ee = e - point.y * h\n const xx = point.x - c\n const yy = point.y - f\n const determinant = aa * ee - bb * dd\n if (Math.abs(determinant) < 1e-8) return undefined\n return {\n x: (xx * ee - bb * yy) / determinant,\n y: (aa * yy - xx * dd) / determinant,\n }\n}\n\nfunction homographyBoundaryScore(corners: DocumentCorners, hull: Point[]) {\n if (!hull.length) return 0\n const coefficients = unitToQuadCoefficients(corners)\n let squaredResidual = 0\n let samples = 0\n const stride = Math.max(1, Math.floor(hull.length / 80))\n for (let index = 0; index < hull.length; index += stride) {\n const unit = quadToUnit(hull[index], coefficients)\n if (!unit) return 0\n const boundaryDistance = Math.min(\n Math.abs(unit.x),\n Math.abs(1 - unit.x),\n Math.abs(unit.y),\n Math.abs(1 - unit.y),\n )\n const outside = Math.max(0, -unit.x, unit.x - 1, -unit.y, unit.y - 1)\n const residual = boundaryDistance + outside * 2\n squaredResidual += residual * residual\n samples += 1\n }\n const rms = Math.sqrt(squaredResidual / Math.max(1, samples))\n return Math.exp(-rms / 0.08)\n}\n\nexport function scorePerspectiveQuad(\n corners: DocumentCorners,\n frameWidth: number,\n frameHeight: number,\n hull: Point[] = [corners.topLeft, corners.topRight, corners.bottomRight, corners.bottomLeft],\n): PerspectiveQuadScore {\n const ordered = [corners.topLeft, corners.topRight, corners.bottomRight, corners.bottomLeft]\n const frameScale = Math.max(1, Math.min(frameWidth, frameHeight))\n const outsideRatio = Math.max(\n 0,\n ...ordered.map((point) => Math.max(\n -point.x,\n -point.y,\n point.x - frameWidth,\n point.y - frameHeight,\n ) / frameScale),\n )\n const signedCrosses = ordered.map((point, index) =>\n cross(point, ordered[(index + 1) % 4], ordered[(index + 2) % 4]),\n )\n const convex = signedCrosses.every((value) => value > 0) ||\n signedCrosses.every((value) => value < 0)\n const areaRatio = polygonArea(ordered) / Math.max(1, frameWidth * frameHeight)\n if (\n !convex ||\n areaRatio < 0.04 ||\n areaRatio > 0.995 ||\n outsideRatio > 0.12\n ) {\n return {\n total: 0,\n areaRatio,\n angleScore: 0,\n oppositeAngleScore: 0,\n parallelScore: 0,\n homographyScore: 0,\n aspectBoost: 0,\n }\n }\n\n const angles = ordered.map((point, index) =>\n interiorAngle(ordered[(index + 3) % 4], point, ordered[(index + 1) % 4]),\n )\n const edgeLengths = ordered.map((point, index) => distance(point, ordered[(index + 1) % 4]))\n const horizontalScaleRatio = Math.max(edgeLengths[0], edgeLengths[2]) /\n Math.max(1, Math.min(edgeLengths[0], edgeLengths[2]))\n const verticalScaleRatio = Math.max(edgeLengths[1], edgeLengths[3]) /\n Math.max(1, Math.min(edgeLengths[1], edgeLengths[3]))\n if (\n angles.some((angle) => angle < 18 || angle > 162) ||\n horizontalScaleRatio > 4 ||\n verticalScaleRatio > 4\n ) {\n return {\n total: 0,\n areaRatio,\n angleScore: 0,\n oppositeAngleScore: 0,\n parallelScore: 0,\n homographyScore: 0,\n aspectBoost: 0,\n }\n }\n const angleScore = angles.reduce(\n (sum, angle) => sum + Math.exp(-Math.abs(angle - 90) / 48),\n 0,\n ) / 4\n const oppositeAngleScore = (\n Math.exp(-Math.abs(angles[0] + angles[2] - 180) / 55) +\n Math.exp(-Math.abs(angles[1] + angles[3] - 180) / 55)\n ) / 2\n const parallelScore = (\n Math.exp(-directionDifference(ordered[0], ordered[1], ordered[3], ordered[2]) / 0.62) +\n Math.exp(-directionDifference(ordered[0], ordered[3], ordered[1], ordered[2]) / 0.62)\n ) / 2\n const homographyScore = homographyBoundaryScore(corners, hull)\n const areaScore = clamp01((areaRatio - 0.04) / 0.5) *\n (areaRatio > 0.94 ? clamp01((0.995 - areaRatio) / 0.055) : 1)\n const perspectiveScaleScore = Math.exp(\n -Math.max(0, Math.max(horizontalScaleRatio, verticalScaleRatio) - 1.6) / 1.5,\n )\n const frameVisibilityScore = outsideRatio ? Math.exp(-outsideRatio / 0.07) : 1\n const width = Math.max(distance(ordered[0], ordered[1]), distance(ordered[3], ordered[2]))\n const height = Math.max(distance(ordered[0], ordered[3]), distance(ordered[1], ordered[2]))\n const aspect = Math.max(width, height) / Math.max(1, Math.min(width, height))\n const idBoost = Math.exp(-Math.abs(aspect - 1.58) / 0.18)\n const letterBoost = Math.exp(-Math.abs(aspect - 1.29) / 0.18)\n const receiptBoost = aspect >= 2 ? clamp01((aspect - 2) / 2) : 0\n const aspectBoost = Math.max(idBoost, letterBoost, receiptBoost) * 0.08\n const geometricScore = clamp01(\n areaScore * 0.18 +\n angleScore * 0.2 +\n oppositeAngleScore * 0.14 +\n parallelScore * 0.18 +\n homographyScore * 0.3 +\n aspectBoost\n )\n const total = geometricScore * (0.72 + perspectiveScaleScore * 0.28) * frameVisibilityScore\n return {\n total,\n areaRatio,\n angleScore,\n oppositeAngleScore,\n parallelScore,\n homographyScore,\n aspectBoost,\n }\n}\n\nfunction dilate(input: Uint8Array, width: number, height: number) {\n const output = new Uint8Array(input.length)\n for (let y = 1; y < height - 1; y += 1) {\n for (let x = 1; x < width - 1; x += 1) {\n const index = y * width + x\n if (!input[index]) continue\n for (let offsetY = -1; offsetY <= 1; offsetY += 1) {\n for (let offsetX = -1; offsetX <= 1; offsetX += 1) {\n output[index + offsetY * width + offsetX] = 1\n }\n }\n }\n }\n return output\n}\n\ninterface HoughLine {\n theta: number\n rho: number\n votes: number\n}\n\nfunction lineIntersection(first: HoughLine, second: HoughLine): Point | undefined {\n const firstCos = Math.cos(first.theta)\n const firstSin = Math.sin(first.theta)\n const secondCos = Math.cos(second.theta)\n const secondSin = Math.sin(second.theta)\n const determinant = firstCos * secondSin - secondCos * firstSin\n if (Math.abs(determinant) < 0.12) return undefined\n return {\n x: (first.rho * secondSin - second.rho * firstSin) / determinant,\n y: (firstCos * second.rho - secondCos * first.rho) / determinant,\n }\n}\n\nfunction angularDistance(first: number, second: number) {\n let difference = Math.abs(first - second) % Math.PI\n if (difference > Math.PI / 2) difference = Math.PI - difference\n return difference\n}\n\nfunction strongestHoughLines(edgeMap: Uint8Array, width: number, height: number) {\n const thetaBins = 72\n const diagonal = Math.ceil(Math.hypot(width, height))\n const rhoBins = diagonal * 2 + 1\n const accumulator = new Uint16Array(thetaBins * rhoBins)\n const cosines = Array.from({ length: thetaBins }, (_, index) => Math.cos(index * Math.PI / thetaBins))\n const sines = Array.from({ length: thetaBins }, (_, index) => Math.sin(index * Math.PI / thetaBins))\n const edgeIndexes: number[] = []\n const edgeCount = edgeMap.reduce((sum, value) => sum + value, 0)\n const stride = Math.max(1, Math.ceil(edgeCount / 7000))\n let seen = 0\n for (let index = 0; index < edgeMap.length; index += 1) {\n if (!edgeMap[index]) continue\n if (seen++ % stride) continue\n edgeIndexes.push(index)\n }\n for (const index of edgeIndexes) {\n const x = index % width\n const y = Math.floor(index / width)\n for (let thetaIndex = 0; thetaIndex < thetaBins; thetaIndex += 1) {\n const rho = Math.round(x * cosines[thetaIndex] + y * sines[thetaIndex]) + diagonal\n const offset = thetaIndex * rhoBins + rho\n if (accumulator[offset] < 65_535) accumulator[offset] += 1\n }\n }\n const candidates: HoughLine[] = []\n const minimumVotes = Math.max(12, Math.round(Math.min(width, height) * 0.08 / stride))\n for (let thetaIndex = 0; thetaIndex < thetaBins; thetaIndex += 1) {\n for (let rhoIndex = 0; rhoIndex < rhoBins; rhoIndex += 1) {\n const votes = accumulator[thetaIndex * rhoBins + rhoIndex]\n if (votes < minimumVotes) continue\n candidates.push({\n theta: thetaIndex * Math.PI / thetaBins,\n rho: rhoIndex - diagonal,\n votes,\n })\n }\n }\n candidates.sort((a, b) => b.votes - a.votes)\n const selected: HoughLine[] = []\n for (const candidate of candidates) {\n if (selected.some((line) =>\n angularDistance(line.theta, candidate.theta) < Math.PI / 36 &&\n Math.abs(line.rho - candidate.rho) < Math.min(width, height) * 0.035,\n )) continue\n selected.push(candidate)\n if (selected.length === 12) break\n }\n return selected\n}\n\nfunction detectQuadFromLines(\n edgeMap: Uint8Array,\n width: number,\n height: number,\n): QuadCandidate | undefined {\n const lines = strongestHoughLines(edgeMap, width, height)\n let best: QuadCandidate | undefined\n let bestScore = 0\n for (let firstA = 0; firstA < lines.length; firstA += 1) {\n for (let firstB = firstA + 1; firstB < lines.length; firstB += 1) {\n if (angularDistance(lines[firstA].theta, lines[firstB].theta) > 0.38) continue\n if (Math.abs(lines[firstA].rho - lines[firstB].rho) < Math.min(width, height) * 0.16) continue\n for (let secondA = 0; secondA < lines.length; secondA += 1) {\n if (secondA === firstA || secondA === firstB) continue\n if (angularDistance(lines[firstA].theta, lines[secondA].theta) < 0.52) continue\n for (let secondB = secondA + 1; secondB < lines.length; secondB += 1) {\n if (secondB === firstA || secondB === firstB) continue\n if (angularDistance(lines[secondA].theta, lines[secondB].theta) > 0.38) continue\n if (Math.abs(lines[secondA].rho - lines[secondB].rho) < Math.min(width, height) * 0.16) continue\n const intersections = [\n lineIntersection(lines[firstA], lines[secondA]),\n lineIntersection(lines[firstA], lines[secondB]),\n lineIntersection(lines[firstB], lines[secondA]),\n lineIntersection(lines[firstB], lines[secondB]),\n ]\n if (intersections.some((point) => !point)) continue\n const points = intersections as Point[]\n if (points.some((point) =>\n point.x < 0 || point.y < 0 ||\n point.x > width - 1 || point.y > height - 1,\n )) continue\n const corners = orderCorners(points)\n if (!corners) continue\n const geometry = scorePerspectiveQuad(corners, width, height)\n if (!geometry.total) continue\n const voteScore = clamp01(\n (lines[firstA].votes + lines[firstB].votes + lines[secondA].votes + lines[secondB].votes) /\n Math.max(1, Math.min(width, height) * 1.2),\n )\n const confidence = clamp01(geometry.total * 0.9 + voteScore * 0.1)\n const score = confidence * Math.sqrt(geometry.areaRatio)\n if (score > bestScore) {\n bestScore = score\n best = { corners, confidence, areaRatio: geometry.areaRatio }\n }\n }\n }\n }\n }\n return best\n}\n\nexport function detectQuadFromEdges(\n edgeMap: Uint8Array,\n width: number,\n height: number,\n options: { lineFallback?: boolean } = {},\n): QuadCandidate | undefined {\n if (edgeMap.length !== width * height) throw new Error('Edge map dimensions do not match.')\n const connectedEdges = dilate(edgeMap, width, height)\n const visited = new Uint8Array(connectedEdges.length)\n const queue = new Int32Array(connectedEdges.length)\n let best: QuadCandidate | undefined\n let bestScore = 0\n const minimumComponent = Math.max(24, Math.round(width * height * 0.0015))\n\n for (let seed = 0; seed < connectedEdges.length; seed += 1) {\n if (!connectedEdges[seed] || visited[seed]) continue\n let read = 0\n let write = 0\n queue[write++] = seed\n visited[seed] = 1\n const points: Point[] = []\n let boundaryPoints = 0\n while (read < write) {\n const index = queue[read++]\n const x = index % width\n const y = Math.floor(index / width)\n const boundary =\n x === 0 || y === 0 || x === width - 1 || y === height - 1 ||\n !connectedEdges[index - 1] || !connectedEdges[index + 1] ||\n !connectedEdges[index - width] || !connectedEdges[index + width]\n if (boundary) {\n boundaryPoints += 1\n const point = { x, y }\n if (points.length < 12_000) {\n points.push(point)\n } else {\n const slot = ((boundaryPoints * 2_654_435_761) >>> 0) % boundaryPoints\n if (slot < points.length) points[slot] = point\n }\n }\n for (let offsetY = -1; offsetY <= 1; offsetY += 1) {\n for (let offsetX = -1; offsetX <= 1; offsetX += 1) {\n if ((!offsetX && !offsetY) || x + offsetX < 0 || x + offsetX >= width || y + offsetY < 0 || y + offsetY >= height) continue\n const neighbor = index + offsetY * width + offsetX\n if (connectedEdges[neighbor] && !visited[neighbor]) {\n visited[neighbor] = 1\n queue[write++] = neighbor\n }\n }\n }\n }\n if (write < minimumComponent || write > width * height * 0.65 || points.length < 4) continue\n const hull = convexHull(points)\n const corners = orderCorners(hull)\n if (!corners) continue\n const ordered = [corners.topLeft, corners.topRight, corners.bottomRight, corners.bottomLeft]\n const quadArea = polygonArea(ordered)\n const hullArea = polygonArea(hull)\n if (quadArea <= 0) continue\n const geometry = scorePerspectiveQuad(corners, width, height, hull)\n if (!geometry.total) continue\n const contourFit = Math.min(1, hullArea / quadArea)\n const perimeterSignal = Math.min(1, write / Math.max(1, Math.sqrt(quadArea) * 10))\n const confidence = Math.min(\n 0.98,\n geometry.total * 0.82 + contourFit * 0.08 + perimeterSignal * 0.1,\n )\n const score = confidence * Math.sqrt(geometry.areaRatio)\n if (score > bestScore) {\n bestScore = score\n best = { corners, confidence, areaRatio: geometry.areaRatio }\n }\n }\n return best ?? (options.lineFallback ? detectQuadFromLines(edgeMap, width, height) : undefined)\n}\n","import { detectQuadFromEdges, type QuadCandidate } from './document-geometry'\nimport type { DocumentDetectionResult, DocumentModelStatus } from './types'\n\nexport interface PixelAnalysis {\n sharpness: number\n candidate?: QuadCandidate\n edgeMap?: Uint8Array\n detector?: NonNullable<DocumentDetectionResult['detector']>\n modelStatus?: DocumentModelStatus\n modelProvider?: NonNullable<DocumentDetectionResult['modelProvider']>\n modelError?: string\n inferenceMs?: number\n}\n\nexport const MIN_DOCUMENT_SHARPNESS = 3\n\nfunction percentileThreshold(magnitudes: Uint16Array, percentile: number) {\n const histogram = new Uint32Array(511)\n let samples = 0\n for (const value of magnitudes) {\n if (!value) continue\n histogram[Math.min(510, value)] += 1\n samples += 1\n }\n const target = samples * percentile\n let seen = 0\n for (let value = 0; value < histogram.length; value += 1) {\n seen += histogram[value]\n if (seen >= target) return value\n }\n return 72\n}\n\nfunction sobelMagnitude(channel: ArrayLike<number>, index: number, width: number) {\n const gx =\n -channel[index - width - 1] + channel[index - width + 1] -\n 2 * channel[index - 1] + 2 * channel[index + 1] -\n channel[index + width - 1] + channel[index + width + 1]\n const gy =\n -channel[index - width - 1] - 2 * channel[index - width] -\n channel[index - width + 1] + channel[index + width - 1] +\n 2 * channel[index + width] + channel[index + width + 1]\n return (Math.abs(gx) + Math.abs(gy)) / 2\n}\n\nfunction sampleTileGrid(\n values: Float32Array,\n columns: number,\n rows: number,\n x: number,\n y: number,\n) {\n const left = Math.floor(x)\n const top = Math.floor(y)\n const horizontalMix = x - left\n const verticalMix = y - top\n const leftColumn = Math.max(0, Math.min(columns - 1, left))\n const rightColumn = Math.max(0, Math.min(columns - 1, left + 1))\n const topRow = Math.max(0, Math.min(rows - 1, top))\n const bottomRow = Math.max(0, Math.min(rows - 1, top + 1))\n const upper = values[topRow * columns + leftColumn] * (1 - horizontalMix) +\n values[topRow * columns + rightColumn] * horizontalMix\n const lower = values[bottomRow * columns + leftColumn] * (1 - horizontalMix) +\n values[bottomRow * columns + rightColumn] * horizontalMix\n return upper * (1 - verticalMix) + lower * verticalMix\n}\n\nexport function analyzeDocumentPixels(\n pixels: Uint8ClampedArray,\n width: number,\n height: number,\n requestedThreshold?: number,\n priorEdgeMaps: Uint8Array[] = [],\n lineFallback = false,\n): PixelAnalysis {\n const gray = new Uint8Array(width * height)\n const redGreen = new Int16Array(width * height)\n const blueGreen = new Int16Array(width * height)\n for (let index = 0; index < gray.length; index += 1) {\n const offset = index * 4\n gray[index] = Math.round(\n pixels[offset] * 0.299 + pixels[offset + 1] * 0.587 + pixels[offset + 2] * 0.114,\n )\n redGreen[index] = pixels[offset] - pixels[offset + 1]\n blueGreen[index] = pixels[offset + 2] - pixels[offset + 1]\n }\n\n const enhanced = new Uint8Array(gray)\n const tileSize = Math.max(24, Math.round(Math.min(width, height) / 12))\n const tileColumns = Math.ceil(width / tileSize)\n const tileRows = Math.ceil(height / tileSize)\n const tileMeans = new Float32Array(tileColumns * tileRows)\n const tileGains = new Float32Array(tileColumns * tileRows)\n for (let tileRow = 0; tileRow < tileRows; tileRow += 1) {\n for (let tileColumn = 0; tileColumn < tileColumns; tileColumn += 1) {\n const tileX = tileColumn * tileSize\n const tileY = tileRow * tileSize\n const endX = Math.min(width, tileX + tileSize)\n const endY = Math.min(height, tileY + tileSize)\n let minimum = 255\n let maximum = 0\n let sum = 0\n let count = 0\n for (let y = tileY; y < endY; y += 1) {\n for (let x = tileX; x < endX; x += 1) {\n const value = gray[y * width + x]\n minimum = Math.min(minimum, value)\n maximum = Math.max(maximum, value)\n sum += value\n count += 1\n }\n }\n const range = maximum - minimum\n const tileIndex = tileRow * tileColumns + tileColumn\n tileMeans[tileIndex] = sum / Math.max(1, count)\n tileGains[tileIndex] = range < 10 ? 1 : Math.min(3, Math.max(1, 72 / range))\n }\n }\n for (let y = 0; y < height; y += 1) {\n const tileY = y / tileSize - 0.5\n for (let x = 0; x < width; x += 1) {\n const tileX = x / tileSize - 0.5\n const index = y * width + x\n const mean = sampleTileGrid(tileMeans, tileColumns, tileRows, tileX, tileY)\n const gain = sampleTileGrid(tileGains, tileColumns, tileRows, tileX, tileY)\n const contrasted = mean + (gray[index] - mean) * gain\n enhanced[index] = Math.max(\n 0,\n Math.min(255, Math.round(gray[index] * 0.58 + contrasted * 0.42)),\n )\n }\n }\n const magnitudes = new Uint16Array(enhanced.length)\n let focusEnergy = 0\n let focusSamples = 0\n for (let y = 1; y < height - 1; y += 1) {\n for (let x = 1; x < width - 1; x += 1) {\n const index = y * width + x\n const lumaMagnitude = sobelMagnitude(enhanced, index, width)\n const chromaMagnitude = Math.max(\n sobelMagnitude(redGreen, index, width),\n sobelMagnitude(blueGreen, index, width),\n )\n const magnitude = Math.min(510, Math.round(lumaMagnitude + chromaMagnitude * 0.38))\n magnitudes[index] = magnitude\n focusEnergy += magnitude\n focusSamples += 1\n }\n }\n const sharpness = focusEnergy / Math.max(1, focusSamples)\n if (sharpness < MIN_DOCUMENT_SHARPNESS) return { sharpness }\n const threshold = requestedThreshold ?? Math.max(26, percentileThreshold(magnitudes, 0.82))\n const strongEdges = new Uint8Array(gray.length)\n const weakEdges = new Uint8Array(gray.length)\n for (let index = 0; index < magnitudes.length; index += 1) {\n if (magnitudes[index] >= threshold) strongEdges[index] = 1\n else if (magnitudes[index] >= threshold * 0.55) weakEdges[index] = 1\n }\n const edgeMap = new Uint8Array(strongEdges)\n for (let pass = 0; pass < 2; pass += 1) {\n const next = new Uint8Array(edgeMap)\n for (let y = 1; y < height - 1; y += 1) {\n for (let x = 1; x < width - 1; x += 1) {\n const index = y * width + x\n if (!weakEdges[index] || edgeMap[index]) continue\n for (let offsetY = -1; offsetY <= 1 && !next[index]; offsetY += 1) {\n for (let offsetX = -1; offsetX <= 1; offsetX += 1) {\n if (edgeMap[index + offsetY * width + offsetX]) {\n next[index] = 1\n break\n }\n }\n }\n }\n }\n edgeMap.set(next)\n }\n const combinedEdges = new Uint8Array(edgeMap)\n for (const previous of priorEdgeMaps) {\n if (previous.length !== combinedEdges.length) continue\n for (let index = 0; index < combinedEdges.length; index += 1) {\n combinedEdges[index] ||= previous[index]\n }\n }\n return {\n sharpness,\n edgeMap,\n candidate: detectQuadFromEdges(combinedEdges, width, height, { lineFallback }),\n }\n}\n","import { orderCorners, scorePerspectiveQuad, type QuadCandidate } from './document-geometry'\nimport type { Point } from './types'\nimport type { DocumentDetectorMode } from './types'\n\nexport interface DocumentModelInput {\n data: Float32Array\n dimensions: [1, 3, number, number]\n}\n\nexport interface DecodedDocumentHeatmaps {\n candidate?: QuadCandidate\n cornerConfidences: number[]\n}\n\nexport interface DetectorResolution<T> {\n result: T\n detector: 'classical' | 'ml'\n fallbackError?: string\n}\n\nexport async function resolveDocumentDetector<T>(\n mode: DocumentDetectorMode,\n runMl: () => Promise<T | undefined>,\n runClassical: () => T,\n): Promise<DetectorResolution<T>> {\n if (mode === 'classical') return { result: runClassical(), detector: 'classical' }\n try {\n const result = await runMl()\n if (result !== undefined) return { result, detector: 'ml' }\n if (mode === 'ml') throw new Error('ML detector returned no geometrically valid corners.')\n return {\n result: runClassical(),\n detector: 'classical',\n fallbackError: 'ML detector returned no geometrically valid corners.',\n }\n } catch (error) {\n if (mode === 'ml') throw error\n return {\n result: runClassical(),\n detector: 'classical',\n fallbackError: error instanceof Error ? error.message : 'Document model failed.',\n }\n }\n}\n\nexport function preprocessDocumentModel(\n pixels: Uint8ClampedArray,\n width: number,\n height: number,\n inputSize = 256,\n): DocumentModelInput {\n const planeSize = inputSize * inputSize\n const data = new Float32Array(planeSize * 3)\n for (let y = 0; y < inputSize; y += 1) {\n const sourceY = Math.min(height - 1, Math.round((y + 0.5) * height / inputSize - 0.5))\n for (let x = 0; x < inputSize; x += 1) {\n const sourceX = Math.min(width - 1, Math.round((x + 0.5) * width / inputSize - 0.5))\n const sourceOffset = (sourceY * width + sourceX) * 4\n const targetOffset = y * inputSize + x\n data[targetOffset] = pixels[sourceOffset] / 255\n data[planeSize + targetOffset] = pixels[sourceOffset + 1] / 255\n data[planeSize * 2 + targetOffset] = pixels[sourceOffset + 2] / 255\n }\n }\n return { data, dimensions: [1, 3, inputSize, inputSize] }\n}\n\nfunction largestHeatmapComponent(\n values: Float32Array,\n base: number,\n width: number,\n height: number,\n threshold: number,\n) {\n const visited = new Uint8Array(width * height)\n const queue = new Int32Array(width * height)\n let bestCount = 0\n let bestPoint: Point | undefined\n let bestPeak = 0\n for (let seed = 0; seed < width * height; seed += 1) {\n if (visited[seed] || values[base + seed] < threshold) continue\n let read = 0\n let write = 0\n let weightedX = 0\n let weightedY = 0\n let totalWeight = 0\n let peak = 0\n queue[write++] = seed\n visited[seed] = 1\n while (read < write) {\n const index = queue[read++]\n const x = index % width\n const y = Math.floor(index / width)\n const weight = Math.max(0, values[base + index])\n weightedX += (x + 0.5) * weight\n weightedY += (y + 0.5) * weight\n totalWeight += weight\n peak = Math.max(peak, weight)\n const neighbors = [index - 1, index + 1, index - width, index + width]\n for (const neighbor of neighbors) {\n if (\n neighbor < 0 ||\n neighbor >= width * height ||\n visited[neighbor] ||\n values[base + neighbor] < threshold\n ) continue\n const neighborX = neighbor % width\n if (Math.abs(neighborX - x) > 1) continue\n visited[neighbor] = 1\n queue[write++] = neighbor\n }\n }\n if (write > bestCount && totalWeight > 0) {\n bestCount = write\n bestPeak = peak\n bestPoint = {\n x: weightedX / totalWeight,\n y: weightedY / totalWeight,\n }\n }\n }\n return bestPoint ? { point: bestPoint, peak: bestPeak } : undefined\n}\n\nexport function decodeDocumentHeatmaps(\n heatmaps: Float32Array,\n dimensions: readonly number[],\n imageWidth: number,\n imageHeight: number,\n threshold = 0.3,\n): DecodedDocumentHeatmaps {\n if (\n dimensions.length !== 4 ||\n Number(dimensions[0]) !== 1 ||\n Number(dimensions[1]) !== 4\n ) {\n throw new Error(`Expected document heatmaps shaped [1,4,H,W], got [${dimensions.join(',')}].`)\n }\n const heatmapHeight = Number(dimensions[2])\n const heatmapWidth = Number(dimensions[3])\n const planeSize = heatmapWidth * heatmapHeight\n if (heatmaps.length < planeSize * 4 || !heatmapWidth || !heatmapHeight) {\n throw new Error('Document heatmap tensor is incomplete.')\n }\n const points: Point[] = []\n const cornerConfidences: number[] = []\n for (let channel = 0; channel < 4; channel += 1) {\n const component = largestHeatmapComponent(\n heatmaps,\n channel * planeSize,\n heatmapWidth,\n heatmapHeight,\n threshold,\n )\n if (!component) return { cornerConfidences }\n points.push({\n x: Math.max(0, Math.min(imageWidth - 1, component.point.x / heatmapWidth * imageWidth)),\n y: Math.max(0, Math.min(imageHeight - 1, component.point.y / heatmapHeight * imageHeight)),\n })\n cornerConfidences.push(component.peak)\n }\n const corners = orderCorners(points)\n if (!corners) return { cornerConfidences }\n const geometry = scorePerspectiveQuad(corners, imageWidth, imageHeight)\n if (geometry.total < 0.35) return { cornerConfidences }\n const heatmapConfidence = cornerConfidences.reduce((sum, value) => sum + value, 0) / 4\n return {\n candidate: {\n corners,\n areaRatio: geometry.areaRatio,\n confidence: Math.min(0.99, heatmapConfidence * 0.55 + geometry.total * 0.45),\n },\n cornerConfidences,\n }\n}\n","import { analyzeDocumentPixels, type PixelAnalysis } from './document-analysis'\nimport {\n decodeDocumentHeatmaps,\n preprocessDocumentModel,\n resolveDocumentDetector,\n} from './document-ml'\nimport type { DocumentDetectorMode } from './types'\n\ninterface AnalysisRequest {\n id: number\n pixels: ArrayBuffer\n width: number\n height: number\n edgeThreshold?: number\n priorEdgeMaps?: Uint8Array[]\n lineFallback?: boolean\n detector?: DocumentDetectorMode\n modelUrl?: string\n heatmapThreshold?: number\n cacheModel?: boolean\n ortWasmPaths?: string\n}\n\ninterface AnalysisResponse {\n id: number\n result?: PixelAnalysis\n error?: string\n}\n\ninterface WorkerScope {\n onmessage: ((event: MessageEvent<AnalysisRequest>) => void) | null\n postMessage: (message: AnalysisResponse) => void\n}\n\nconst workerScope = globalThis as unknown as WorkerScope\nconst modelBytes = new Map<string, Promise<ArrayBuffer>>()\nconst sessions = new Map<string, Promise<{\n session: import('onnxruntime-web').InferenceSession\n ort: typeof import('onnxruntime-web')\n provider: 'webgpu' | 'wasm'\n}>>()\n\nconst MODEL_DATABASE = 'exige-scanner-sdk-models'\nconst MODEL_STORE = 'model-bytes'\n\nfunction openModelDatabase() {\n return new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDB.open(MODEL_DATABASE, 1)\n request.onupgradeneeded = () => {\n if (!request.result.objectStoreNames.contains(MODEL_STORE)) {\n request.result.createObjectStore(MODEL_STORE)\n }\n }\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n}\n\nasync function readCachedModel(modelUrl: string) {\n if (typeof indexedDB === 'undefined') return undefined\n const database = await openModelDatabase()\n try {\n return await new Promise<ArrayBuffer | undefined>((resolve, reject) => {\n const request = database.transaction(MODEL_STORE).objectStore(MODEL_STORE).get(modelUrl)\n request.onsuccess = () => resolve(\n request.result instanceof ArrayBuffer ? request.result : undefined,\n )\n request.onerror = () => reject(request.error)\n })\n } finally {\n database.close()\n }\n}\n\nasync function writeCachedModel(modelUrl: string, bytes: ArrayBuffer) {\n if (typeof indexedDB === 'undefined') return\n const database = await openModelDatabase()\n try {\n await new Promise<void>((resolve, reject) => {\n const transaction = database.transaction(MODEL_STORE, 'readwrite')\n transaction.objectStore(MODEL_STORE).put(bytes, modelUrl)\n transaction.oncomplete = () => resolve()\n transaction.onerror = () => reject(transaction.error)\n })\n } finally {\n database.close()\n }\n}\n\nasync function fetchModel(modelUrl: string, cacheModel: boolean) {\n let request = modelBytes.get(modelUrl)\n if (!request) {\n request = (async () => {\n if (cacheModel) {\n try {\n const cached = await readCachedModel(modelUrl)\n if (cached) return cached\n } catch {\n // IndexedDB may be unavailable in private/embedded contexts.\n }\n }\n const response = await fetch(modelUrl, {\n cache: cacheModel ? 'force-cache' : 'no-store',\n })\n if (!response.ok) throw new Error(`Document model request failed with HTTP ${response.status}.`)\n const bytes = await response.arrayBuffer()\n if (cacheModel) {\n try {\n await writeCachedModel(modelUrl, bytes)\n } catch {\n // HTTP caching and the in-worker promise cache still apply.\n }\n }\n return bytes\n })()\n modelBytes.set(modelUrl, request)\n }\n return request\n}\n\nasync function loadSession(modelUrl: string, cacheModel: boolean, ortWasmPaths?: string) {\n const sessionKey = `${modelUrl}|${ortWasmPaths ?? ''}`\n let request = sessions.get(sessionKey)\n if (!request) {\n request = (async () => {\n const [ort, bytes] = await Promise.all([\n import('onnxruntime-web'),\n fetchModel(modelUrl, cacheModel),\n ])\n if (ortWasmPaths) ort.env.wasm.wasmPaths = ortWasmPaths\n const hasWebGpu = typeof navigator !== 'undefined' &&\n 'gpu' in (navigator as Navigator & { gpu?: unknown })\n if (hasWebGpu) {\n try {\n const session = await ort.InferenceSession.create(bytes, {\n executionProviders: ['webgpu'],\n graphOptimizationLevel: 'all',\n })\n return { session, ort, provider: 'webgpu' as const }\n } catch {\n // Fall through to portable WASM.\n }\n }\n const session = await ort.InferenceSession.create(bytes, {\n executionProviders: ['wasm'],\n graphOptimizationLevel: 'all',\n })\n return { session, ort, provider: 'wasm' as const }\n })()\n sessions.set(sessionKey, request)\n }\n return request\n}\n\nasync function analyzeWithModel(request: AnalysisRequest): Promise<PixelAnalysis> {\n if (!request.modelUrl) {\n throw new Error('A modelUrl is required when detector is \"ml\".')\n }\n const pixels = new Uint8ClampedArray(request.pixels)\n const input = preprocessDocumentModel(pixels, request.width, request.height)\n const { session, ort, provider } = await loadSession(\n request.modelUrl,\n request.cacheModel !== false,\n request.ortWasmPaths,\n )\n const startedAt = performance.now()\n const outputs = await session.run({\n [session.inputNames[0]]: new ort.Tensor('float32', input.data, input.dimensions),\n })\n const tensor = outputs[session.outputNames[0]]\n if (!tensor) throw new Error('Document model returned no output tensor.')\n const decoded = decodeDocumentHeatmaps(\n tensor.data as Float32Array,\n tensor.dims.map(Number),\n request.width,\n request.height,\n request.heatmapThreshold ?? 0.3,\n )\n return {\n sharpness: 20,\n candidate: decoded.candidate,\n detector: 'ml',\n modelStatus: 'ready',\n modelProvider: provider,\n inferenceMs: performance.now() - startedAt,\n }\n}\n\nfunction analyzeClassically(request: AnalysisRequest): PixelAnalysis {\n return {\n ...analyzeDocumentPixels(\n new Uint8ClampedArray(request.pixels),\n request.width,\n request.height,\n request.edgeThreshold,\n request.priorEdgeMaps,\n request.lineFallback,\n ),\n detector: 'classical',\n modelStatus: 'not-requested',\n }\n}\n\nasync function analyze(request: AnalysisRequest): Promise<PixelAnalysis> {\n const detector = request.detector ?? 'classical'\n try {\n const resolution = await resolveDocumentDetector(\n detector,\n async () => {\n const ml = await analyzeWithModel(request)\n return ml.candidate ? ml : undefined\n },\n () => analyzeClassically(request),\n )\n if (resolution.detector === 'ml') return resolution.result\n return resolution.fallbackError\n ? {\n ...resolution.result,\n modelStatus: 'fallback',\n modelError: resolution.fallbackError,\n }\n : resolution.result\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Document model failed.'\n return {\n sharpness: 20,\n detector: 'ml',\n modelStatus: 'failed',\n modelError: message,\n }\n }\n}\n\nlet analysisQueue = Promise.resolve()\nworkerScope.onmessage = (event) => {\n const request = event.data\n analysisQueue = analysisQueue.then(async () => {\n try {\n workerScope.postMessage({ id: request.id, result: await analyze(request) })\n } catch (error) {\n workerScope.postMessage({\n id: request.id,\n error: error instanceof Error ? error.message : 'Document analysis failed.',\n })\n }\n })\n}\n"],"mappings":"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"}
|