@stabrise/scaledp 0.1.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/LICENSE +661 -0
- package/README.md +218 -0
- package/dist/box-DAfzwfhA.d.ts +119 -0
- package/dist/config-g6IrKlDC.d.ts +80 -0
- package/dist/data-to-image-DoZ4jQ3R.js +54 -0
- package/dist/data-to-image-DoZ4jQ3R.js.map +1 -0
- package/dist/detect/index.d.ts +71 -0
- package/dist/detect/index.js +2 -0
- package/dist/detect-q8AI_Jdj.js +274 -0
- package/dist/detect-q8AI_Jdj.js.map +1 -0
- package/dist/detector-output-C0Qt-jEq.d.ts +13 -0
- package/dist/detector-output-lyF1Mqb8.js +13 -0
- package/dist/detector-output-lyF1Mqb8.js.map +1 -0
- package/dist/display/index.d.ts +66 -0
- package/dist/display/index.js +237 -0
- package/dist/display/index.js.map +1 -0
- package/dist/document-B8I61TiY.d.ts +16 -0
- package/dist/entity-CedtRhU1.d.ts +22 -0
- package/dist/entity-D6Hxaugj.js +13 -0
- package/dist/entity-D6Hxaugj.js.map +1 -0
- package/dist/image-CAH2rLv9.js +511 -0
- package/dist/image-CAH2rLv9.js.map +1 -0
- package/dist/image-Dc5TSg46.d.ts +18 -0
- package/dist/image-DoZDJkcR.js +37 -0
- package/dist/image-DoZDJkcR.js.map +1 -0
- package/dist/image-draw-boxes-De0QbFv9.js +285 -0
- package/dist/image-draw-boxes-De0QbFv9.js.map +1 -0
- package/dist/index.d.ts +269 -0
- package/dist/index.js +11 -0
- package/dist/model-cache-BEaqqRZ9.js +182 -0
- package/dist/model-cache-BEaqqRZ9.js.map +1 -0
- package/dist/model-cache-BhFYpfZz.d.ts +36 -0
- package/dist/ner/index.d.ts +293 -0
- package/dist/ner/index.js +2 -0
- package/dist/ner-SsZLZ6ed.js +1028 -0
- package/dist/ner-SsZLZ6ed.js.map +1 -0
- package/dist/ocr/index.d.ts +440 -0
- package/dist/ocr/index.js +3 -0
- package/dist/ocr-OHX2WM3e.js +1294 -0
- package/dist/ocr-OHX2WM3e.js.map +1 -0
- package/dist/ort-CXDoPrtw.js +73 -0
- package/dist/ort-CXDoPrtw.js.map +1 -0
- package/dist/params-DapwK9Ns.js +37 -0
- package/dist/params-DapwK9Ns.js.map +1 -0
- package/dist/pdf/index.d.ts +123 -0
- package/dist/pdf/index.js +2 -0
- package/dist/pdf-BQl0dneD.js +417 -0
- package/dist/pdf-BQl0dneD.js.map +1 -0
- package/dist/pipeline-DACqGkpN.js +240 -0
- package/dist/pipeline-DACqGkpN.js.map +1 -0
- package/dist/pipeline-DeLO-OCE.d.ts +139 -0
- package/dist/registry/index.d.ts +169 -0
- package/dist/registry/index.js +1061 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/text-ahMLpxN9.js +109 -0
- package/dist/text-ahMLpxN9.js.map +1 -0
- package/dist/worker/index.d.ts +105 -0
- package/dist/worker/index.js +180 -0
- package/dist/worker/index.js.map +1 -0
- package/package.json +135 -0
package/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<br/>
|
|
3
|
+
<a href="https://stabrise.com/scaledp/" target="_blank"><img alt="ScaleDP" src="https://stabrise.com/static/images/projects/scaledp.webp" width="450" style="max-width: 100%;"></a>
|
|
4
|
+
<br/>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<i>Process documents using AI/ML in the browser. No server, no upload.</i>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@stabrise/scaledp" alt="Package on npm"><img src="https://img.shields.io/npm/v/@stabrise/scaledp.svg" /></a>
|
|
13
|
+
<a href="https://github.com/StabRise/scaledp-ts/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/npm/l/@stabrise/scaledp.svg?color=blue"></a>
|
|
14
|
+
<a href="https://stabrise.com"><img alt="StabRise" src="https://img.shields.io/badge/powered%20by-StabRise-orange.svg?style=flat&colorA=E1523D&colorB=blue"></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
**Source Code**: <a href="https://github.com/StabRise/scaledp-ts/" target="_blank">https://github.com/StabRise/scaledp-ts</a>
|
|
20
|
+
|
|
21
|
+
**Python sibling**: <a href="https://github.com/StabRise/ScaleDP/" target="_blank">ScaleDP</a> — the same pipeline model, on Apache Spark
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# scaledp-ts
|
|
26
|
+
|
|
27
|
+
`@stabrise/scaledp` processes PDFs and images entirely in the browser: PDF
|
|
28
|
+
rendering, text detection, OCR and named-entity recognition, composed as a
|
|
29
|
+
pipeline. Inference runs on [onnxruntime-web](https://onnxruntime.ai/docs/tutorials/web/)
|
|
30
|
+
over WebAssembly or WebGPU.
|
|
31
|
+
|
|
32
|
+
It mirrors the [ScaleDP](https://github.com/StabRise/ScaleDP) Python library —
|
|
33
|
+
same stages, same parameter names, same schemas — so a pipeline reads the same
|
|
34
|
+
in both. What differs is the runtime: no Spark, no server, and **no document
|
|
35
|
+
ever leaves the browser**, which is the point for anything sensitive.
|
|
36
|
+
|
|
37
|
+
## Key features
|
|
38
|
+
|
|
39
|
+
### Document processing
|
|
40
|
+
|
|
41
|
+
- Load PDFs and images from a `File`, `Blob`, `ArrayBuffer` or URL
|
|
42
|
+
- Render PDF pages, or read an existing text layer and skip OCR entirely
|
|
43
|
+
- Word-level bounding boxes throughout, so every result maps back to the page
|
|
44
|
+
|
|
45
|
+
### OCR
|
|
46
|
+
|
|
47
|
+
- **PaddleOCR** (PP-OCRv5/v6) via [ppu-paddle-ocr](https://www.npmjs.com/package/ppu-paddle-ocr) — 13 language presets
|
|
48
|
+
- **DBNet ONNX** — the same detection model ScaleDP uses server-side
|
|
49
|
+
- **Tesseract** via [tesseract-wasm](https://www.npmjs.com/package/tesseract-wasm), with independent script detection
|
|
50
|
+
|
|
51
|
+
### NLP
|
|
52
|
+
|
|
53
|
+
- **GLiNER** zero-shot NER: entity types are plain-language labels given at call time, not fixed by the model
|
|
54
|
+
- Both GLiNER1 and GLiNER2 architectures
|
|
55
|
+
- Entities carry the boxes they came from, ready to highlight or redact
|
|
56
|
+
|
|
57
|
+
### CV
|
|
58
|
+
|
|
59
|
+
- YOLO object detection, including signature and face detectors
|
|
60
|
+
|
|
61
|
+
### Displaying results
|
|
62
|
+
|
|
63
|
+
- `ImageDrawBoxes` / `ImageCropBoxes` stages, as in ScaleDP
|
|
64
|
+
- `showText`, `showNer`, `visualizeNer`, `showImage`, `showBoxes` -- the browser
|
|
65
|
+
equivalents of ScaleDP's notebook helpers
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm install @stabrise/scaledp
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Engines are optional peer dependencies — install only what you use:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm install pdfjs-dist # PDF reading
|
|
77
|
+
npm install onnxruntime-web ppu-paddle-ocr # PaddleOCR
|
|
78
|
+
npm install onnxruntime-web @huggingface/transformers # GLiNER NER
|
|
79
|
+
npm install tesseract-wasm tesseract.js # Tesseract + script detection
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quickstart
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { Pipeline, configure } from '@stabrise/scaledp'
|
|
86
|
+
import { PdfToImage } from '@stabrise/scaledp/pdf'
|
|
87
|
+
import { PaddleTextRecognizer } from '@stabrise/scaledp/ocr'
|
|
88
|
+
import { GlinerNer } from '@stabrise/scaledp/ner'
|
|
89
|
+
|
|
90
|
+
configure({
|
|
91
|
+
cache: 'indexeddb',
|
|
92
|
+
pdf: { workerSrc: '/pdf.worker.min.mjs' },
|
|
93
|
+
onProgress: (p) => console.log(`${p.file}: ${p.loaded}/${p.total}`),
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const pipeline = new Pipeline([
|
|
97
|
+
new PdfToImage({ resolution: 300 }),
|
|
98
|
+
new PaddleTextRecognizer({ preset: 'v6-small', keepFormatting: true }),
|
|
99
|
+
new GlinerNer({ labels: ['person', 'organization', 'email', 'phone'] }),
|
|
100
|
+
])
|
|
101
|
+
|
|
102
|
+
const rows = await pipeline.transform(file)
|
|
103
|
+
|
|
104
|
+
for (const row of rows) {
|
|
105
|
+
console.log(row.page, row.text.text)
|
|
106
|
+
for (const entity of row.ner.entities) {
|
|
107
|
+
console.log(entity.entity_group, entity.word, entity.boxes)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Show the results
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { ImageDrawBoxes } from '@stabrise/scaledp'
|
|
116
|
+
import { renderInto, showText, showNer, visualizeNer } from '@stabrise/scaledp/display'
|
|
117
|
+
|
|
118
|
+
// Annotating the page is a pipeline stage, as in ScaleDP -- the result is
|
|
119
|
+
// just another image column.
|
|
120
|
+
pipeline.stages.push(
|
|
121
|
+
new ImageDrawBoxes({ inputCols: ['image', 'text', 'ner'], outputCol: 'annotated' })
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
renderInto('#text', showText(row.text)) // layout-preserving text
|
|
125
|
+
renderInto('#entities', showNer(row.ner)) // table of entities
|
|
126
|
+
renderInto('#inline', visualizeNer(row.text, row.ner)) // highlighted inline
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Compare with the Python original:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
pipeline = PipelineModel(stages=[
|
|
133
|
+
PdfDataToImage(resolution=300),
|
|
134
|
+
TesseractOcr(inputCol="image", outputCol="text", keepFormatting=True),
|
|
135
|
+
Ner(inputCol="text", outputCol="ner"),
|
|
136
|
+
])
|
|
137
|
+
result = pipeline.transform(df)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Skip OCR when the PDF already has text
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { PdfToDocument, hasUsableTextLayer } from '@stabrise/scaledp/pdf'
|
|
144
|
+
|
|
145
|
+
const pipeline = new Pipeline([new PdfToDocument({ resolution: 300 })])
|
|
146
|
+
const rows = await pipeline.transform(file)
|
|
147
|
+
|
|
148
|
+
// Only pages without a text layer need the OCR pipeline.
|
|
149
|
+
const needsOcr = rows.filter((r) => !hasUsableTextLayer(r.document))
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Run off the main thread
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// worker.ts
|
|
156
|
+
import { registerStages, startScaleDpWorker } from '@stabrise/scaledp/worker'
|
|
157
|
+
import { PdfToImage } from '@stabrise/scaledp/pdf'
|
|
158
|
+
import { PaddleTextRecognizer } from '@stabrise/scaledp/ocr'
|
|
159
|
+
|
|
160
|
+
registerStages({ PdfToImage, PaddleTextRecognizer })
|
|
161
|
+
startScaleDpWorker()
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// main.ts
|
|
166
|
+
import { createScaleDpWorker } from '@stabrise/scaledp/worker'
|
|
167
|
+
|
|
168
|
+
const client = createScaleDpWorker({
|
|
169
|
+
worker: new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' }),
|
|
170
|
+
onProgress: (p) => console.log(p),
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const rows = await client.transform(
|
|
174
|
+
[{ type: 'PdfToImage' }, { type: 'PaddleTextRecognizer' }],
|
|
175
|
+
[{ content: bytes, path: 'invoice.pdf' }]
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Errors never abort a pipeline
|
|
180
|
+
|
|
181
|
+
Every output schema carries an `exception` field. A stage that fails records the
|
|
182
|
+
message there and the pipeline continues, so one bad page does not lose the
|
|
183
|
+
other forty. Pass `propagateError: true` to a stage to make it throw instead.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const row = rows[0]
|
|
187
|
+
if (row.text.exception) console.warn('OCR failed:', row.text.exception)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## OCR engines
|
|
191
|
+
|
|
192
|
+
| | Box level | Models fetched | WebGPU | Notes |
|
|
193
|
+
|--------------------|-----------|----------------|--------|-------|
|
|
194
|
+
| PaddleOCR (default)| word | ~6 MB | yes | 13 language presets, best all-rounder |
|
|
195
|
+
| DBNet ONNX | word | ~5 MB | yes | Detection only; mirrors ScaleDP server-side |
|
|
196
|
+
| Tesseract | word | ~15 MB / lang | no | No ONNX; good for clean Latin scans |
|
|
197
|
+
|
|
198
|
+
## Documentation
|
|
199
|
+
|
|
200
|
+
The docs are a site at **[scaledp-ts.stabrise.com](https://scaledp-ts.stabrise.com)**,
|
|
201
|
+
built with [Fumadocs](https://fumadocs.dev) and served next to a live pipeline
|
|
202
|
+
builder — every stage page has an **Open in builder** link that seeds it with a
|
|
203
|
+
working pipeline you can run on your own file.
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
cd examples/vite-demo && npm install && npm run dev
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
- [Quickstart](examples/vite-demo/content/docs/quickstart.mdx)
|
|
210
|
+
- [Stage reference](examples/vite-demo/content/docs/stages/) — one page per stage, with parameter tables generated from the stage registry
|
|
211
|
+
- [Concepts](examples/vite-demo/content/docs/concepts/) — rows and columns, the stage lifecycle, the error contract, schemas, models and caching, workers
|
|
212
|
+
- [Recipes](examples/vite-demo/content/docs/recipes/) — complete pipelines for OCR, redaction, detection and workers
|
|
213
|
+
- [Porting from Python ScaleDP](examples/vite-demo/content/docs/porting-from-python.mdx)
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
AGPL-3.0-or-later, matching the Python library. Contact
|
|
218
|
+
[StabRise](https://stabrise.com) for commercial licensing.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
//#region src/core/geometry.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* OpenCV-compatible planar geometry, hand-ported so the browser build needs no
|
|
4
|
+
* opencv.js. Conventions match cv2 exactly — `Box.from_polygon` and the DBNet
|
|
5
|
+
* post-processor both depend on that, and a divergence here shifts every box.
|
|
6
|
+
*/
|
|
7
|
+
type Point = [number, number];
|
|
8
|
+
interface RotatedRect {
|
|
9
|
+
/** Centre of the rectangle. */
|
|
10
|
+
center: Point;
|
|
11
|
+
/** Side lengths along the rect's own axes. */
|
|
12
|
+
size: [number, number];
|
|
13
|
+
/**
|
|
14
|
+
* Degrees in (0, 90], with `size[0]` (the width) lying along that direction.
|
|
15
|
+
*
|
|
16
|
+
* Verified against cv2 4.11 across 18 orientations. One caveat: for a
|
|
17
|
+
* perfectly axis-aligned rect cv2 itself is inconsistent, returning either
|
|
18
|
+
* `-0.0` or `90` depending on which hull edge its scan lands on. Those two
|
|
19
|
+
* describe the same rectangle (a rectangle is invariant under a 180-degree
|
|
20
|
+
* rotation once the sides are swapped), so we always report the `90` form.
|
|
21
|
+
* Nothing downstream can distinguish them -- ImageDrawBoxes renders angle
|
|
22
|
+
* and angle+180 identically.
|
|
23
|
+
*/
|
|
24
|
+
angle: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Monotone-chain convex hull. Returns hull points counter-clockwise in a
|
|
28
|
+
* y-down image coordinate system, without the duplicated closing point.
|
|
29
|
+
*/
|
|
30
|
+
declare function convexHull(points: readonly Point[]): Point[];
|
|
31
|
+
/**
|
|
32
|
+
* Minimum-area enclosing rectangle via rotating calipers.
|
|
33
|
+
*
|
|
34
|
+
* The minimum-area rectangle always has a side flush with a convex-hull edge,
|
|
35
|
+
* so testing one orientation per hull edge is exhaustive.
|
|
36
|
+
*/
|
|
37
|
+
declare function minAreaRect(points: readonly Point[]): RotatedRect;
|
|
38
|
+
/**
|
|
39
|
+
* The 4 corners of a rotated rect, in cv2.boxPoints order.
|
|
40
|
+
* For an upright rect in image coordinates that is: BL, TL, TR, BR.
|
|
41
|
+
*/
|
|
42
|
+
declare function boxPoints(rect: RotatedRect): [Point, Point, Point, Point];
|
|
43
|
+
/** Shoelace area of a simple polygon; always non-negative. */
|
|
44
|
+
declare function polygonArea(points: readonly Point[]): number;
|
|
45
|
+
/** Perimeter of a closed polygon. */
|
|
46
|
+
declare function polygonPerimeter(points: readonly Point[]): number;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/schemas/box.d.ts
|
|
49
|
+
interface Box {
|
|
50
|
+
text: string;
|
|
51
|
+
score: number;
|
|
52
|
+
/** Top-left x of the axis-aligned box of size width x height centred on the rect centre. */
|
|
53
|
+
x: number;
|
|
54
|
+
y: number;
|
|
55
|
+
/** Always the longer side. */
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
/** Degrees about the box centre, normalised to (-90, 270]. */
|
|
59
|
+
angle: number;
|
|
60
|
+
}
|
|
61
|
+
/** Two-corner shape: [[x0, y0], [x1, y1]]. */
|
|
62
|
+
type BoxShape = [[number, number], [number, number]];
|
|
63
|
+
/** Axis-aligned bounds: [x0, y0, x1, y1]. */
|
|
64
|
+
type BBox = [number, number, number, number];
|
|
65
|
+
/** `abs(angle) >= 3` — matches Python's `is_rotated`, which tolerates OCR jitter. */
|
|
66
|
+
declare const ROTATION_EPSILON_DEGREES = 3;
|
|
67
|
+
declare function createBox(init?: Partial<Box>): Box;
|
|
68
|
+
declare function isRotated(box: Box): boolean;
|
|
69
|
+
declare function bbox(box: Box, padding?: number): BBox;
|
|
70
|
+
declare function shape(box: Box, padding?: number): BoxShape;
|
|
71
|
+
/**
|
|
72
|
+
* Scale a box, then apply padding.
|
|
73
|
+
*
|
|
74
|
+
* Note the padding is asymmetric, and deliberately so: Python subtracts it from
|
|
75
|
+
* the origin and adds it to the size, so the box grows by `padding` on its left
|
|
76
|
+
* and top while its right and bottom edges stay put. Growing all four sides
|
|
77
|
+
* would need `padding * 2` on the size.
|
|
78
|
+
*/
|
|
79
|
+
declare function scaleBox(box: Box, factor: number, padding?: number): Box;
|
|
80
|
+
declare function boxFromBBox(box: BBox, opts?: {
|
|
81
|
+
angle?: number;
|
|
82
|
+
text?: string;
|
|
83
|
+
score?: number;
|
|
84
|
+
}): Box;
|
|
85
|
+
/**
|
|
86
|
+
* Build a Box from exactly 4 polygon points — port of Python `Box.from_polygon`.
|
|
87
|
+
*
|
|
88
|
+
* `width` is forced to be the longer side (subtracting 90 degrees from the angle
|
|
89
|
+
* to compensate), then the angle is normalised to (-90, 270]. `x`/`y` are derived
|
|
90
|
+
* from the centre, NOT from the polygon's bounding box.
|
|
91
|
+
*/
|
|
92
|
+
declare function boxFromPolygon(points: readonly Point[], opts?: {
|
|
93
|
+
text?: string;
|
|
94
|
+
score?: number;
|
|
95
|
+
padding?: number;
|
|
96
|
+
}): Box;
|
|
97
|
+
/** Axis-aligned intersection-over-union. Ignores `angle`, exactly as Python does. */
|
|
98
|
+
declare function boxIou(a: Box, b: Box): number;
|
|
99
|
+
/** Union of two boxes. Merging discards rotation — Python resets `angle` to 0. */
|
|
100
|
+
declare function mergeBoxes(a: Box, b: Box): Box;
|
|
101
|
+
/**
|
|
102
|
+
* Whether two boxes sit on the same text line.
|
|
103
|
+
*
|
|
104
|
+
* For near-horizontal boxes this compares vertical centres against the average
|
|
105
|
+
* height. For rotated boxes it projects the centre offset onto the line's normal
|
|
106
|
+
* (dx = -sin, dy = cos), so the test follows the text's own baseline.
|
|
107
|
+
*/
|
|
108
|
+
declare function isOnSameLine(a: Box, b: Box, angleThresh?: number, lineThresh?: number): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Greedily merge boxes that overlap and share a line. Port of Python
|
|
111
|
+
* `Box.merge_overlapping_boxes`.
|
|
112
|
+
*
|
|
113
|
+
* Restarts the scan after each merge so a chain of boxes collapses fully in one
|
|
114
|
+
* call, matching Python's behaviour.
|
|
115
|
+
*/
|
|
116
|
+
declare function mergeOverlappingBoxes(boxes: readonly Box[], iouThreshold?: number, angleThresh?: number, lineThresh?: number): Box[];
|
|
117
|
+
//#endregion
|
|
118
|
+
export { polygonPerimeter as S, RotatedRect as _, bbox as a, minAreaRect as b, boxIou as c, isRotated as d, mergeBoxes as f, Point as g, shape as h, ROTATION_EPSILON_DEGREES as i, createBox as l, scaleBox as m, Box as n, boxFromBBox as o, mergeOverlappingBoxes as p, BoxShape as r, boxFromPolygon as s, BBox as t, isOnSameLine as u, boxPoints as v, polygonArea as x, convexHull as y };
|
|
119
|
+
//# sourceMappingURL=box-DAfzwfhA.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//#region src/core/config.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Global configuration. Everything an application owns -- asset URLs, model
|
|
4
|
+
* hosts, auth -- is injected here rather than hardcoded, which is the main
|
|
5
|
+
* structural difference from the pdftools prototype this library supersedes.
|
|
6
|
+
*/
|
|
7
|
+
interface ModelProgress {
|
|
8
|
+
repo: string;
|
|
9
|
+
file: string;
|
|
10
|
+
/** Bytes fetched so far across the request set. */
|
|
11
|
+
loaded: number;
|
|
12
|
+
/** Total bytes expected; 0 when the server sent no content-length. */
|
|
13
|
+
total: number;
|
|
14
|
+
phase: 'downloading' | 'initializing' | 'ready';
|
|
15
|
+
}
|
|
16
|
+
interface ScaleDpConfig {
|
|
17
|
+
/** Base URL models resolve against. Point at an origin you control to self-host. */
|
|
18
|
+
modelHost: string;
|
|
19
|
+
/** Where downloaded model files are kept between page loads. */
|
|
20
|
+
cache: 'indexeddb' | 'none';
|
|
21
|
+
/** IndexedDB database name, so consumers can isolate or purge their own store. */
|
|
22
|
+
cacheDbName: string;
|
|
23
|
+
/**
|
|
24
|
+
* Bearer token supplier for private model repos. Returning `undefined`
|
|
25
|
+
* means "fetch anonymously".
|
|
26
|
+
*/
|
|
27
|
+
auth?: (repo: string) => Promise<string | undefined> | string | undefined;
|
|
28
|
+
/** Reports download progress for every model file. */
|
|
29
|
+
onProgress?: (progress: ModelProgress) => void;
|
|
30
|
+
/** Execution providers, in priority order, for ONNX sessions. */
|
|
31
|
+
executionProviders: readonly string[];
|
|
32
|
+
/**
|
|
33
|
+
* WASM threads. `0` lets onnxruntime-web choose from hardwareConcurrency.
|
|
34
|
+
* Threading only engages on a cross-origin-isolated page (COOP/COEP);
|
|
35
|
+
* without that ORT silently falls back to the single-threaded build.
|
|
36
|
+
*/
|
|
37
|
+
numThreads: number;
|
|
38
|
+
/**
|
|
39
|
+
* Directory holding the onnxruntime-web `.wasm`/`.mjs` pair. The loader and
|
|
40
|
+
* the binary must come from the same build variant AND the same version --
|
|
41
|
+
* a mismatch fails at init with an opaque error. Leave unset to let
|
|
42
|
+
* onnxruntime-web resolve its own default.
|
|
43
|
+
*/
|
|
44
|
+
ortWasmPaths?: string;
|
|
45
|
+
/** pdf.js assets. All must be served by the consuming application. */
|
|
46
|
+
pdf: {
|
|
47
|
+
workerSrc?: string;
|
|
48
|
+
cMapUrl?: string;
|
|
49
|
+
standardFontDataUrl?: string;
|
|
50
|
+
wasmUrl?: string;
|
|
51
|
+
};
|
|
52
|
+
/** Tesseract assets. */
|
|
53
|
+
tesseract: {
|
|
54
|
+
workerUrl?: string;
|
|
55
|
+
/** Base URL for `<lang>.traineddata` files. */
|
|
56
|
+
dataUrl?: string;
|
|
57
|
+
};
|
|
58
|
+
/** Hugging Face host overrides, for proxying gated repos through your origin. */
|
|
59
|
+
hf: {
|
|
60
|
+
remoteHost?: string;
|
|
61
|
+
/** e.g. 'api/hf-model/{model}/resolve/{revision}/' */
|
|
62
|
+
remotePathTemplate?: string;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Merge `patch` into the global config. Nested asset maps merge per key. */
|
|
66
|
+
declare function configure(patch: Partial<ScaleDpConfig>): ScaleDpConfig;
|
|
67
|
+
declare function getConfig(): Readonly<ScaleDpConfig>;
|
|
68
|
+
/** Restore defaults. Primarily for tests. */
|
|
69
|
+
declare function resetConfig(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Threads to request from onnxruntime-web.
|
|
72
|
+
*
|
|
73
|
+
* Leaves one core for the UI and caps at 4 -- past that, ORT's own
|
|
74
|
+
* synchronisation overhead outweighs the gain on these model sizes.
|
|
75
|
+
*/
|
|
76
|
+
declare function defaultNumThreads(): number;
|
|
77
|
+
declare function resolveNumThreads(): number;
|
|
78
|
+
//#endregion
|
|
79
|
+
export { getConfig as a, defaultNumThreads as i, ScaleDpConfig as n, resetConfig as o, configure as r, resolveNumThreads as s, ModelProgress as t };
|
|
80
|
+
//# sourceMappingURL=config-g6IrKlDC.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { c as ImageError, i as Stage } from "./pipeline-DACqGkpN.js";
|
|
2
|
+
import { d as probeImageSize } from "./image-CAH2rLv9.js";
|
|
3
|
+
import { i as resolveParams, t as BASE_STAGE_DEFAULTS } from "./params-DapwK9Ns.js";
|
|
4
|
+
import { t as createImage } from "./image-DoZDJkcR.js";
|
|
5
|
+
//#region src/stages/data-to-image.ts
|
|
6
|
+
/**
|
|
7
|
+
* Port of `scaledp/image/DataToImage.py`: raw bytes -> an `Image` record.
|
|
8
|
+
*
|
|
9
|
+
* Dimensions are probed without retaining the decoded bitmap, mirroring
|
|
10
|
+
* Python's use of `imagesize.get` rather than a full PIL decode.
|
|
11
|
+
*/
|
|
12
|
+
const DATA_TO_IMAGE_DEFAULTS = Object.freeze({
|
|
13
|
+
...BASE_STAGE_DEFAULTS,
|
|
14
|
+
inputCol: "content",
|
|
15
|
+
outputCol: "image",
|
|
16
|
+
imageType: "png",
|
|
17
|
+
resolution: 0
|
|
18
|
+
});
|
|
19
|
+
var DataToImage = class extends Stage {
|
|
20
|
+
name = "DataToImage";
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
super(resolveParams(DATA_TO_IMAGE_DEFAULTS, options));
|
|
23
|
+
}
|
|
24
|
+
async apply(input, row) {
|
|
25
|
+
const data = toBytes(input);
|
|
26
|
+
if (data.byteLength === 0) throw new ImageError("Empty image data", this.name);
|
|
27
|
+
const { width, height } = await probeImageSize(data);
|
|
28
|
+
return createImage({
|
|
29
|
+
path: String(row[this.params.pathCol] ?? "memory"),
|
|
30
|
+
resolution: Number(row.resolution ?? this.params.resolution) || 0,
|
|
31
|
+
data,
|
|
32
|
+
imageType: this.params.imageType,
|
|
33
|
+
width,
|
|
34
|
+
height
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
onError(message, row) {
|
|
38
|
+
return createImage({
|
|
39
|
+
path: String(row[this.params.pathCol] ?? "memory"),
|
|
40
|
+
exception: message
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/** Coerce any accepted binary representation into bytes. */
|
|
45
|
+
function toBytes(input) {
|
|
46
|
+
if (input instanceof Uint8Array) return input;
|
|
47
|
+
if (input instanceof ArrayBuffer) return new Uint8Array(input);
|
|
48
|
+
if (ArrayBuffer.isView(input)) return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
|
|
49
|
+
throw new ImageError(`Expected binary image data, received ${typeof input}`, "toBytes");
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { DataToImage as n, toBytes as r, DATA_TO_IMAGE_DEFAULTS as t };
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=data-to-image-DoZ4jQ3R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-to-image-DoZ4jQ3R.js","names":[],"sources":["../src/stages/data-to-image.ts"],"sourcesContent":["/**\n * Port of `scaledp/image/DataToImage.py`: raw bytes -> an `Image` record.\n *\n * Dimensions are probed without retaining the decoded bitmap, mirroring\n * Python's use of `imagesize.get` rather than a full PIL decode.\n */\n\nimport { ImageError } from '../core/errors.js'\nimport { probeImageSize } from '../core/image.js'\nimport { BASE_STAGE_DEFAULTS, type BaseStageParams, resolveParams } from '../core/params.js'\nimport { type Row, Stage } from '../core/pipeline.js'\nimport { createImage, type ImageFormat, type ScaleDpImage } from '../schemas/image.js'\n\nexport interface DataToImageParams extends BaseStageParams {\n /** Encoding recorded on the output; the bytes are passed through unchanged. */\n imageType: ImageFormat\n /** DPI to record when the row carries no `resolution` field. */\n resolution: number\n}\n\nexport const DATA_TO_IMAGE_DEFAULTS: DataToImageParams = Object.freeze({\n ...BASE_STAGE_DEFAULTS,\n inputCol: 'content',\n outputCol: 'image',\n imageType: 'png' as ImageFormat,\n resolution: 0,\n})\n\nexport class DataToImage extends Stage<DataToImageParams> {\n readonly name = 'DataToImage'\n\n constructor(options: Partial<DataToImageParams> = {}) {\n super(resolveParams(DATA_TO_IMAGE_DEFAULTS, options))\n }\n\n protected async apply(input: unknown, row: Row): Promise<ScaleDpImage> {\n const data = toBytes(input)\n if (data.byteLength === 0) {\n throw new ImageError('Empty image data', this.name)\n }\n const { width, height } = await probeImageSize(data)\n return createImage({\n path: String(row[this.params.pathCol] ?? 'memory'),\n resolution: Number(row.resolution ?? this.params.resolution) || 0,\n data,\n imageType: this.params.imageType,\n width,\n height,\n })\n }\n\n protected onError(message: string, row: Row): ScaleDpImage {\n return createImage({\n path: String(row[this.params.pathCol] ?? 'memory'),\n exception: message,\n })\n }\n}\n\n/** Coerce any accepted binary representation into bytes. */\nexport function toBytes(input: unknown): Uint8Array {\n if (input instanceof Uint8Array) return input\n if (input instanceof ArrayBuffer) return new Uint8Array(input)\n if (ArrayBuffer.isView(input)) {\n return new Uint8Array(input.buffer, input.byteOffset, input.byteLength)\n }\n throw new ImageError(`Expected binary image data, received ${typeof input}`, 'toBytes')\n}\n"],"mappings":";;;;;;;;;;;AAoBA,MAAa,yBAA4C,OAAO,OAAO;CACnE,GAAG;CACH,UAAU;CACV,WAAW;CACX,WAAW;CACX,YAAY;AAChB,CAAC;AAED,IAAa,cAAb,cAAiC,MAAyB;CACtD,OAAgB;CAEhB,YAAY,UAAsC,CAAC,GAAG;EAClD,MAAM,cAAc,wBAAwB,OAAO,CAAC;CACxD;CAEA,MAAgB,MAAM,OAAgB,KAAiC;EACnE,MAAM,OAAO,QAAQ,KAAK;EAC1B,IAAI,KAAK,eAAe,GACpB,MAAM,IAAI,WAAW,oBAAoB,KAAK,IAAI;EAEtD,MAAM,EAAE,OAAO,WAAW,MAAM,eAAe,IAAI;EACnD,OAAO,YAAY;GACf,MAAM,OAAO,IAAI,KAAK,OAAO,YAAY,QAAQ;GACjD,YAAY,OAAO,IAAI,cAAc,KAAK,OAAO,UAAU,KAAK;GAChE;GACA,WAAW,KAAK,OAAO;GACvB;GACA;EACJ,CAAC;CACL;CAEA,QAAkB,SAAiB,KAAwB;EACvD,OAAO,YAAY;GACf,MAAM,OAAO,IAAI,KAAK,OAAO,YAAY,QAAQ;GACjD,WAAW;EACf,CAAC;CACL;AACJ;;AAGA,SAAgB,QAAQ,OAA4B;CAChD,IAAI,iBAAiB,YAAY,OAAO;CACxC,IAAI,iBAAiB,aAAa,OAAO,IAAI,WAAW,KAAK;CAC7D,IAAI,YAAY,OAAO,KAAK,GACxB,OAAO,IAAI,WAAW,MAAM,QAAQ,MAAM,YAAY,MAAM,UAAU;CAE1E,MAAM,IAAI,WAAW,wCAAwC,OAAO,SAAS,SAAS;AAC1F"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { l as Stage, m as BaseStageParams, s as Row } from "../pipeline-DeLO-OCE.js";
|
|
2
|
+
import { t as DetectorOutput } from "../detector-output-C0Qt-jEq.js";
|
|
3
|
+
//#region src/detect/yolo-onnx.d.ts
|
|
4
|
+
/** Fallback when the graph declares a symbolic input size. */
|
|
5
|
+
declare const DEFAULT_YOLO_INPUT = 960;
|
|
6
|
+
interface YoloOnnxDetectorParams extends BaseStageParams {
|
|
7
|
+
model: string;
|
|
8
|
+
/** Class index -> label. An empty list falls back to `class_<n>`. */
|
|
9
|
+
labels: readonly string[];
|
|
10
|
+
scoreThreshold: number;
|
|
11
|
+
/** IoU above which two same-class boxes are considered duplicates. */
|
|
12
|
+
iouThreshold: number;
|
|
13
|
+
/** Grow each box by this fraction of its size, to avoid clipping edges. */
|
|
14
|
+
padding: number;
|
|
15
|
+
/** Emitted as DetectorOutput.type. */
|
|
16
|
+
outputType: string;
|
|
17
|
+
}
|
|
18
|
+
declare const YOLO_DETECTOR_DEFAULTS: YoloOnnxDetectorParams;
|
|
19
|
+
interface Detection {
|
|
20
|
+
/** [x0, y0, x1, y1] in source-image coordinates. */
|
|
21
|
+
bbox: [number, number, number, number];
|
|
22
|
+
score: number;
|
|
23
|
+
classId: number;
|
|
24
|
+
label: string;
|
|
25
|
+
}
|
|
26
|
+
/** Axis-aligned IoU over xyxy boxes. */
|
|
27
|
+
declare function iou(a: readonly number[], b: readonly number[]): number;
|
|
28
|
+
/** Per-class non-maximum suppression; different classes never suppress each other. */
|
|
29
|
+
declare function nonMaximumSuppression(detections: readonly Detection[], iouThreshold: number): Detection[];
|
|
30
|
+
/**
|
|
31
|
+
* Decode a YOLO output tensor.
|
|
32
|
+
*
|
|
33
|
+
* Two layouts are handled, because exports differ:
|
|
34
|
+
* - [1, 4 + numClasses, numAnchors] -- the classic v8/v11 transposed form,
|
|
35
|
+
* with cx,cy,w,h then per-class scores.
|
|
36
|
+
* - [1, numDetections, 6] -- an end-to-end NMS export: x0,y0,x1,y1,score,class.
|
|
37
|
+
*/
|
|
38
|
+
declare function decodeYoloOutput(data: Float32Array, dims: readonly number[], scoreThreshold: number): {
|
|
39
|
+
bbox: [number, number, number, number];
|
|
40
|
+
score: number;
|
|
41
|
+
classId: number;
|
|
42
|
+
}[];
|
|
43
|
+
declare class YoloOnnxDetector extends Stage<YoloOnnxDetectorParams> {
|
|
44
|
+
readonly name: string;
|
|
45
|
+
private session;
|
|
46
|
+
private loading;
|
|
47
|
+
private inputSize;
|
|
48
|
+
constructor(options?: Partial<YoloOnnxDetectorParams>);
|
|
49
|
+
init(): Promise<void>;
|
|
50
|
+
private getSession;
|
|
51
|
+
dispose(): Promise<void>;
|
|
52
|
+
protected apply(input: unknown, row: Row): Promise<DetectorOutput>;
|
|
53
|
+
/** Run the model over one decoded image, returning source-space detections. */
|
|
54
|
+
detect(source: ImageBitmap | OffscreenCanvas): Promise<Detection[]>;
|
|
55
|
+
protected onError(message: string, row: Row): DetectorOutput;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/detect/index.d.ts
|
|
59
|
+
/** Signature detection, mirroring ScaleDP's `SignatureDetector`. */
|
|
60
|
+
declare class SignatureDetector extends YoloOnnxDetector {
|
|
61
|
+
readonly name = "SignatureDetector";
|
|
62
|
+
constructor(options?: Partial<YoloOnnxDetectorParams>);
|
|
63
|
+
}
|
|
64
|
+
/** Face detection, mirroring ScaleDP's `FaceDetector`. */
|
|
65
|
+
declare class FaceDetector extends YoloOnnxDetector {
|
|
66
|
+
readonly name = "FaceDetector";
|
|
67
|
+
constructor(options?: Partial<YoloOnnxDetectorParams>);
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { DEFAULT_YOLO_INPUT, type Detection, FaceDetector, SignatureDetector, YOLO_DETECTOR_DEFAULTS, YoloOnnxDetector, type YoloOnnxDetectorParams, decodeYoloOutput, iou, nonMaximumSuppression };
|
|
71
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as YoloOnnxDetector, c as nonMaximumSuppression, i as YOLO_DETECTOR_DEFAULTS, n as SignatureDetector, o as decodeYoloOutput, r as DEFAULT_YOLO_INPUT, s as iou, t as FaceDetector } from "../detect-q8AI_Jdj.js";
|
|
2
|
+
export { DEFAULT_YOLO_INPUT, FaceDetector, SignatureDetector, YOLO_DETECTOR_DEFAULTS, YoloOnnxDetector, decodeYoloOutput, iou, nonMaximumSuppression };
|