@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.
Files changed (60) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +218 -0
  3. package/dist/box-DAfzwfhA.d.ts +119 -0
  4. package/dist/config-g6IrKlDC.d.ts +80 -0
  5. package/dist/data-to-image-DoZ4jQ3R.js +54 -0
  6. package/dist/data-to-image-DoZ4jQ3R.js.map +1 -0
  7. package/dist/detect/index.d.ts +71 -0
  8. package/dist/detect/index.js +2 -0
  9. package/dist/detect-q8AI_Jdj.js +274 -0
  10. package/dist/detect-q8AI_Jdj.js.map +1 -0
  11. package/dist/detector-output-C0Qt-jEq.d.ts +13 -0
  12. package/dist/detector-output-lyF1Mqb8.js +13 -0
  13. package/dist/detector-output-lyF1Mqb8.js.map +1 -0
  14. package/dist/display/index.d.ts +66 -0
  15. package/dist/display/index.js +237 -0
  16. package/dist/display/index.js.map +1 -0
  17. package/dist/document-B8I61TiY.d.ts +16 -0
  18. package/dist/entity-CedtRhU1.d.ts +22 -0
  19. package/dist/entity-D6Hxaugj.js +13 -0
  20. package/dist/entity-D6Hxaugj.js.map +1 -0
  21. package/dist/image-CAH2rLv9.js +511 -0
  22. package/dist/image-CAH2rLv9.js.map +1 -0
  23. package/dist/image-Dc5TSg46.d.ts +18 -0
  24. package/dist/image-DoZDJkcR.js +37 -0
  25. package/dist/image-DoZDJkcR.js.map +1 -0
  26. package/dist/image-draw-boxes-De0QbFv9.js +285 -0
  27. package/dist/image-draw-boxes-De0QbFv9.js.map +1 -0
  28. package/dist/index.d.ts +269 -0
  29. package/dist/index.js +11 -0
  30. package/dist/model-cache-BEaqqRZ9.js +182 -0
  31. package/dist/model-cache-BEaqqRZ9.js.map +1 -0
  32. package/dist/model-cache-BhFYpfZz.d.ts +36 -0
  33. package/dist/ner/index.d.ts +293 -0
  34. package/dist/ner/index.js +2 -0
  35. package/dist/ner-SsZLZ6ed.js +1028 -0
  36. package/dist/ner-SsZLZ6ed.js.map +1 -0
  37. package/dist/ocr/index.d.ts +440 -0
  38. package/dist/ocr/index.js +3 -0
  39. package/dist/ocr-OHX2WM3e.js +1294 -0
  40. package/dist/ocr-OHX2WM3e.js.map +1 -0
  41. package/dist/ort-CXDoPrtw.js +73 -0
  42. package/dist/ort-CXDoPrtw.js.map +1 -0
  43. package/dist/params-DapwK9Ns.js +37 -0
  44. package/dist/params-DapwK9Ns.js.map +1 -0
  45. package/dist/pdf/index.d.ts +123 -0
  46. package/dist/pdf/index.js +2 -0
  47. package/dist/pdf-BQl0dneD.js +417 -0
  48. package/dist/pdf-BQl0dneD.js.map +1 -0
  49. package/dist/pipeline-DACqGkpN.js +240 -0
  50. package/dist/pipeline-DACqGkpN.js.map +1 -0
  51. package/dist/pipeline-DeLO-OCE.d.ts +139 -0
  52. package/dist/registry/index.d.ts +169 -0
  53. package/dist/registry/index.js +1061 -0
  54. package/dist/registry/index.js.map +1 -0
  55. package/dist/text-ahMLpxN9.js +109 -0
  56. package/dist/text-ahMLpxN9.js.map +1 -0
  57. package/dist/worker/index.d.ts +105 -0
  58. package/dist/worker/index.js +180 -0
  59. package/dist/worker/index.js.map +1 -0
  60. 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 };